Benutzer-Werkzeuge

Webseiten-Werkzeuge


tools:intervalltrainer-pruefung

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.

Link zu der Vergleichsansicht

Beide Seiten, vorherige ÜberarbeitungVorherige Überarbeitung
Nächste Überarbeitung
Vorherige Überarbeitung
tools:intervalltrainer-pruefung [10/05/2025 18:33] – gelöscht - Externe Bearbeitung (Unknown date) 127.0.0.1tools:intervalltrainer-pruefung [10/05/2025 18:39] (aktuell) Eric Weber
Zeile 1: Zeile 1:
 +====== Intervalltrainer | Prüfungsmodus ======
  
 +;#;
 +[[tools:intervalltrainer-pruefung|neu laden]]
 +;#;
 +
 +
 +
 +<html>
 +<head>
 +    <meta charset="UTF-8">
 +    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 +    <title>Intervalltrainer</title>
 +    <style>
 +        body {
 +            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
 +            display: flex;
 +            flex-direction: column;
 +            align-items: center;
 +            margin: 20px;
 +            background-color: #f8f9fa;
 +            color: #212529;
 +        }
 +        h1, h2 {
 +            text-align: center;
 +            color: #343a40;
 +            margin-bottom: 20px;
 +        }
 +        h1 {
 +            margin-bottom: 30px;
 +        }
 +        .settings-section, .options-group {
 +            margin-bottom: 25px;
 +            padding-bottom: 20px;
 +            border-bottom: 1px solid #dee2e6;
 +        }
 +        .settings-section:last-child {
 +            border-bottom: none;
 +            margin-bottom: 0;
 +        }
 +        label {
 +            display: block;
 +            margin-bottom: 10px;
 +            font-weight: 600;
 +            color: #495057;
 +        }
 +        .interval-checkboxes label, .radio-group label {
 +            display: inline-block;
 +            margin-right: 18px;
 +            margin-bottom: 8px;
 +            font-weight: normal;
 +            color: #495057;
 +        }
 +        .interval-checkboxes input[type="checkbox"], .radio-group input[type="radio"] {
 +            margin-right: 6px;
 +            vertical-align: middle;
 +        }
 +
 +        /* --- Button Styling --- */
 +        button {
 +            padding: 10px 18px; /* Etwas kompakter */
 +            color: black;
 +            border-radius: 5px;
 +            cursor: pointer;
 +            margin-top: 10px;
 +            margin-right: 8px;
 +            font-size: 15px; /* Etwas kleiner für mehr Buttons nebeneinander */
 +            font-weight: 500;
 +            transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
 +            box-shadow: 0 1px 3px rgba(0,0,0,0.07);
 +            border-width: 1px;
 +            border-style: solid;
 +        }
 +
 +        /* Primäre Buttons (Default) */
 +        button, button#start-test-button, button#play-interval-button, button#next-question-button, button#select-all-intervals, button#restart-test-button {
 +            background-color: #007bff;
 +            border-color: #007bff;
 +            color: black;
 +        }
 +        button:hover, button#start-test-button:hover, button#play-interval-button:hover, button#next-question-button:hover, button#select-all-intervals:hover, button#restart-test-button:hover {
 +            background-color: #0056b3;
 +            border-color: #0056b3;
 +            box-shadow: 0 2px 6px rgba(0,0,0,0.1);
 +        }
 +        button:focus, button#start-test-button:focus, button#play-interval-button:focus, button#next-question-button:focus, button#select-all-intervals:focus, button#restart-test-button:focus {
 +            outline: none;
 +            box-shadow: 0 0 0 0.2rem rgba(38,143,255,.5); /* Hellerer Fokusring */
 +        }
 +
 +        /* Sekundäre Buttons */
 +        button.secondary, button#deselect-all-intervals, button#repeat-question-button, button#show-correct-answer-button {
 +             background-color: #6c757d;
 +             border-color: #6c757d;
 +             color: black;
 +        }
 +        button.secondary:hover, button#deselect-all-intervals:hover, button#repeat-question-button:hover, button#show-correct-answer-button:hover {
 +            background-color: #545b62;
 +            border-color: #545b62;
 +            box-shadow: 0 2px 6px rgba(0,0,0,0.1);
 +        }
 +        button.secondary:focus, button#deselect-all-intervals:focus, button#repeat-question-button:focus, button#show-correct-answer-button:focus {
 +            outline: none;
 +            box-shadow: 0 0 0 0.2rem rgba(108,117,125,.5);
 +        }
 +
 +        /* Disabled Zustand für alle Buttons */
 +        button:disabled {
 +            background-color: #adb5bd !important; /* Wichtig, um andere Styles zu überschreiben */
 +            border-color: #adb5bd !important;
 +            color: #939596 !important; /* Hellerer Text für besseren Kontrast auf Grau */
 +            cursor: not-allowed !important;
 +            box-shadow: none !important;
 +            opacity: 0.75 !important;
 +        }
 +
 +        /* Antwort-Buttons */
 +        .answer-button {
 +            margin: 6px;
 +            padding: 10px 15px; /* Angepasst an andere Buttons */
 +            background-color: #ffffff;
 +            color: #212529; /* Dunkler Text für Lesbarkeit */
 +            border: 1px solid #ced4da; /* Klarer Rand */
 +            font-weight: 500;
 +        }
 +        .answer-button:hover {
 +            background-color: #e9ecef;
 +            border-color: #adb5bd;
 +            color: #0056b3; /* Akzentfarbe bei Hover */
 +        }
 +        .answer-button:focus { /* Eigener Fokus für Antwort-Buttons */
 +            outline: none;
 +            border-color: #007bff;
 +            box-shadow: 0 0 0 0.2rem rgba(0,123,255,.25);
 +        }
 +
 +        /* Feedback-Buttons (richtig/falsch/aufgedeckt) */
 +        .answer-button.correct {
 +            background-color: #28a745 !important;
 +            color: green !important;
 +            border-color: #1e7e34 !important;
 +        }
 +        .answer-button.incorrect {
 +            background-color: #dc3545 !important;
 +            color: red !important;
 +            border-color: #bd2130 !important;
 +        }
 +        .answer-button.revealed-correct {
 +            background-color: #17a2b8 !important; /* Info Blau */
 +            color: black !important;
 +            border-color: #117a8b !important;
 +        }
 +        /* --- Ende Button Styling --- */
 +
 +
 +        #test-area {
 +            text-align: center;
 +        }
 +        .feedback {
 +            margin-top: 20px;
 +            padding: 12px; /* Etwas mehr Padding */
 +            border-radius: 5px;
 +            font-size: 1.15em; /* Etwas größer */
 +            font-weight: 500; /* Semi-bold */
 +            border-width: 1px;
 +            border-style: solid;
 +        }
 +        .feedback.correct {
 +            color: #155724;
 +            background-color: #d4edda;
 +            border-color: #c3e6cb;
 +        }
 +        .feedback.incorrect {
 +            color: #721c24;
 +            background-color: #f8d7da;
 +            border-color: #f5c6cb;
 +        }
 +        .feedback.revealed { /* Für die Anzeige der richtigen Antwort */
 +            color: #0c5460; /* Dunkleres Cyan */
 +            background-color: #d1ecf1; /* Helleres Cyan */
 +            border-color: #bee5eb;
 +        }
 +
 +
 +        #progress-area {
 +            margin-top: 25px;
 +            padding: 20px;
 +            background-color: #e9ecef;
 +            border-radius: 8px;
 +        }
 +        #progress-area p {
 +            margin: 8px 0;
 +            font-size: 1.05em;
 +            color: #495057;
 +        }
 +        .hidden {
 +            display: none;
 +        }
 +        #task-description {
 +            font-size: 1.1em;
 +            margin-bottom: 15px;
 +            color: #343a40;
 +        }
 +        #action-buttons button {
 +            margin-top: 20px;
 +        }
 +    </style>
 +</head>
 +<body>
 +
 +    <div id="settings-container" class="container">
 +        <h2>Einstellungen</h2>
 +
 +        <div class="settings-section">
 +            <label>Welche Intervalle möchtest du üben?</label>
 +            <div class="interval-checkboxes">
 +                </div>
 +            <button id="select-all-intervals">Alle auswählen</button>
 +            <button id="deselect-all-intervals" class="secondary">Alle abwählen</button>
 +        </div>
 +
 +        <div class="settings-section">
 +            <label>Wiedergabeart der Intervalle:</label>
 +            <div class="radio-group" id="playback-type">
 +                <label><input type="radio" name="playback" value="ascending" checked> Aufsteigend</label>
 +                <label><input type="radio" name="playback" value="descending"> Absteigend</label>
 +                <label><input type="radio" name="playback" value="simultaneous"> Simultan</label>
 +            </div>
 +        </div>
 +
 +        <div class="settings-section">
 +            <label>Schwierigkeitsgrad:</label>
 +            <div class="radio-group" id="difficulty-level">
 +                <label><input type="radio" name="difficulty" value="easy" checked> Einfach (Alle Intervalle starten ab c1/c2)</label>
 +                <label><input type="radio" name="difficulty" value="advanced"> Fortgeschritten (Alle Intervalle starten ab c/c1/c2/c3)</label>
 +                <label><input type="radio" name="difficulty" value="pro"> Profi (Alle Intervalle starten von einem beliebigen Ton)</label>
 +            </div>
 +        </div>
 +
 +        <div class="settings-section">
 +            <label>Prüfungsmodus:</label>
 +            <div class="radio-group" id="test-mode">
 +                <label><input type="radio" name="mode" value="interval_recognition" checked> Intervallbestimmung</label>
 +                <label><input type="radio" name="mode" value="target_note_recognition"> Zieltonbestimmung</label>
 +            </div>
 +        </div>
 +
 +        <button id="start-test-button">Prüfung starten</button>
 +    </div>
 +
 +<div id="test-area" class="container hidden">
 +        <h2>Frage <span id="current-question-number">1</span> / <span id="total-question-number">10</span></h2>
 +        <p id="task-description"></p>
 +        <button id="play-interval-button">Intervall abspielen</button>
 +        <div id="answer-options" class="options-group">
 +            </div>
 +        <div id="feedback-message" class="feedback"></div>
 +        <div id="action-buttons">
 +            <button id="next-question-button" class="hidden">Nächste Frage</button>
 +            <button id="repeat-question-button" class="hidden secondary">Frage wiederholen</button>
 +            <button id="show-correct-answer-button" class="hidden secondary">Richtige Antwort zeigen</button>
 +    </div>
 +
 +    <div id="progress-area" class="container hidden">
 +        <h2>Fortschritt</h2>
 +        <p>Beantwortete Fragen: <span id="answered-questions">0</span></p>
 +        <p>Richtige Antworten: <span id="correct-answers-count">0</span></p>
 +        <p>Falsche Antworten: <span id="wrong-answers-count">0</span></p>
 +        <p>Erfolgsquote: <span id="success-rate">0</span>%</p>
 +    </div>
 +
 +    <div id="results-area" class="container hidden">
 +        <h2>Ergebnis</h2>
 +        <p>Du hast die Prüfung abgeschlossen!</p>
 +        <p>Gesamtergebnis:</p>
 +        <p>Richtige Antworten: <span id="final-correct"></span> / <span id="final-total"></span></p>
 +        <p>Erfolgsquote: <span id="final-percentage"></span>%</p>
 +        <button id="restart-test-button">Neue Prüfung starten</button>
 +    </div>
 +
 +
 +    <script>
 +        const intervalDefinitions = [
 +            { name: "Prime", semitones: 0, id: "p1" },
 +            { name: "Kleine Sekunde", semitones: 1, id: "m2" },
 +            { name: "Große Sekunde", semitones: 2, id: "M2" },
 +            { name: "Kleine Terz", semitones: 3, id: "m3" },
 +            { name: "Große Terz", semitones: 4, id: "M3" },
 +            { name: "Reine Quarte", semitones: 5, id: "p4" },
 +            { name: "Tritonus", semitones: 6, id: "tr" },
 +            { name: "Reine Quinte", semitones: 7, id: "p5" },
 +            { name: "Kleine Sexte", semitones: 8, id: "m6" },
 +            { name: "Große Sexte", semitones: 9, id: "M6" },
 +            { name: "Kleine Septime", semitones: 10, id: "m7" },
 +            { name: "Große Septime", semitones: 11, id: "M7" },
 +            { name: "Reine Oktave", semitones: 12, id: "p8" }
 +        ];
 +
 +        const noteNamesDE = [
 +            "c", "cis/des", "d", "dis/es", "e", "f", 
 +            "fis/ges", "g", "gis/as", "a", "b", "h"
 +        ];
 +
 +        const baseMidiNotes = {
 +            "c": 48, "c'": 60, "c''": 72, "c'''": 84
 +        };
 +        
 +        const midiToGermanNoteName = (midiNote) => {
 +            const noteIndex = midiNote % 12;
 +            const octaveVal = Math.floor(midiNote / 12);
 +            let noteBase = noteNamesDE[noteIndex];
 +            
 +            if (octaveVal === 4) return noteBase; 
 +            if (octaveVal === 5) return noteBase + "'"; 
 +            if (octaveVal === 6) return noteBase + "''"; 
 +            if (octaveVal === 7) return noteBase + "'''"; 
 +            
 +            return noteBase + (octaveVal - 1); 
 +        };
 +
 +
 +        const A4_FREQ = 440; 
 +        const A4_MIDI = 69; 
 +
 +        const midiToFreq = (midiNote) => {
 +            return A4_FREQ * Math.pow(2, (midiNote - A4_MIDI) / 12);
 +        };
 +
 +        let audioContext;
 +        let currentTask = {};
 +        let selectedIntervalsForTest = [];
 +        let playbackSetting = 'ascending';
 +        let difficultySetting = 'easy';
 +        let testModeSetting = 'interval_recognition';
 +
 +        let currentQuestionNumber = 0;
 +        const totalQuestions = 10;
 +        let correctAnswersCount = 0;
 +        let wrongAnswersCount = 0;
 +        let questionAnswered = false;
 +
 +
 +        // DOM Elements
 +        const settingsContainer = document.getElementById('settings-container');
 +        const testArea = document.getElementById('test-area');
 +        const progressArea = document.getElementById('progress-area');
 +        const resultsArea = document.getElementById('results-area');
 +
 +        const intervalCheckboxesContainer = document.querySelector('.interval-checkboxes');
 +        const selectAllButton = document.getElementById('select-all-intervals');
 +        const deselectAllButton = document.getElementById('deselect-all-intervals');
 +        const startTestButton = document.getElementById('start-test-button');
 +
 +        const currentQuestionNumberDisplay = document.getElementById('current-question-number');
 +        const totalQuestionNumberDisplay = document.getElementById('total-question-number');
 +        const taskDescriptionDisplay = document.getElementById('task-description');
 +        const playIntervalButton = document.getElementById('play-interval-button');
 +        const answerOptionsContainer = document.getElementById('answer-options');
 +        const feedbackMessageDisplay = document.getElementById('feedback-message');
 +        const actionButtonsDiv = document.getElementById('action-buttons');
 +        const nextQuestionButton = document.getElementById('next-question-button');
 +        const repeatQuestionButton = document.getElementById('repeat-question-button');
 +        const showCorrectAnswerButton = document.getElementById('show-correct-answer-button');
 +
 +        const answeredQuestionsDisplay = document.getElementById('answered-questions');
 +        const correctAnswersDisplay = document.getElementById('correct-answers-count');
 +        const wrongAnswersDisplay = document.getElementById('wrong-answers-count');
 +        const successRateDisplay = document.getElementById('success-rate');
 +        
 +        const finalCorrectDisplay = document.getElementById('final-correct');
 +        const finalTotalDisplay = document.getElementById('final-total');
 +        const finalPercentageDisplay = document.getElementById('final-percentage');
 +        const restartTestButton = document.getElementById('restart-test-button');
 +
 +
 +        function initializeAudio() {
 +            if (!audioContext) {
 +                try {
 +                    audioContext = new (window.AudioContext || window.webkitAudioContext)();
 +                } catch (e) {
 +                    alert('Web Audio API wird von diesem Browser nicht unterstützt.');
 +                    console.error('Error initializing AudioContext:', e);
 +                }
 +            }
 +        }
 +
 +        function playNote(frequency, duration = 0.5, startTime = 0, waveType = 'sine') {
 +            if (!audioContext) return;
 +            const oscillator = audioContext.createOscillator();
 +            const gainNode = audioContext.createGain();
 +
 +            oscillator.type = waveType;
 +            oscillator.frequency.setValueAtTime(frequency, audioContext.currentTime + startTime);
 +            
 +            gainNode.gain.setValueAtTime(0, audioContext.currentTime + startTime);
 +            gainNode.gain.linearRampToValueAtTime(0.3, audioContext.currentTime + startTime + 0.05); 
 +            gainNode.gain.setValueAtTime(0.3, audioContext.currentTime + startTime + duration - 0.1); 
 +            gainNode.gain.linearRampToValueAtTime(0, audioContext.currentTime + startTime + duration); 
 +
 +            oscillator.connect(gainNode);
 +            gainNode.connect(audioContext.destination);
 +
 +            oscillator.start(audioContext.currentTime + startTime);
 +            oscillator.stop(audioContext.currentTime + startTime + duration + 0.1); 
 +        }
 +
 +        function playCurrentInterval() {
 +            initializeAudio(); 
 +            if (!audioContext || audioContext.state === 'suspended') {
 +                audioContext.resume().then(() => {
 +                    // console.log("AudioContext resumed."); // Optional: für Debugging
 +                    actuallyPlayInterval();
 +                }).catch(err => console.error("Error resuming AudioContext:", err));
 +            } else {
 +                actuallyPlayInterval();
 +            }
 +        }
 +
 +        function actuallyPlayInterval() {
 +            if (!currentTask.startNoteMidi || typeof currentTask.endNoteMidi === 'undefined') return;
 +
 +            const freq1 = midiToFreq(currentTask.startNoteMidi);
 +            const freq2 = midiToFreq(currentTask.endNoteMidi);
 +            const noteDuration = 0.7;
 +
 +            playIntervalButton.disabled = true;
 +            let totalPlaybackTime = noteDuration;
 +
 +            if (playbackSetting === 'ascending') {
 +                playNote(freq1, noteDuration, 0);
 +                playNote(freq2, noteDuration, noteDuration * 0.8); 
 +                totalPlaybackTime = noteDuration * 1.8;
 +            } else if (playbackSetting === 'descending') {
 +                playNote(freq1, noteDuration, 0); 
 +                playNote(freq2, noteDuration, noteDuration * 0.8);
 +                totalPlaybackTime = noteDuration * 1.8;
 +            } else { 
 +                playNote(freq1, noteDuration + 0.3, 0);
 +                playNote(freq2, noteDuration + 0.3, 0);
 +                totalPlaybackTime = noteDuration + 0.3;
 +            }
 +            setTimeout(() => { playIntervalButton.disabled = false; }, totalPlaybackTime * 1000 + 200);
 +        }
 +
 +
 +        function populateIntervalCheckboxes() {
 +            intervalDefinitions.forEach(interval => {
 +                const div = document.createElement('div'); 
 +                const label = document.createElement('label');
 +                const checkbox = document.createElement('input');
 +                checkbox.type = 'checkbox';
 +                checkbox.value = interval.id;
 +                checkbox.id = `interval-${interval.id}`;
 +                checkbox.dataset.semitones = interval.semitones;
 +                checkbox.checked = true; 
 +                label.appendChild(checkbox);
 +                label.appendChild(document.createTextNode(` ${interval.name}`));
 +                div.appendChild(label);
 +                intervalCheckboxesContainer.appendChild(div);
 +            });
 +        }
 +
 +        function toggleAllIntervals(select) {
 +            intervalCheckboxesContainer.querySelectorAll('input[type="checkbox"]').forEach(cb => {
 +                cb.checked = select;
 +            });
 +        }
 +
 +        function collectSettings() {
 +            selectedIntervalsForTest = Array.from(intervalCheckboxesContainer.querySelectorAll('input[type="checkbox"]:checked'))
 +                .map(cb => intervalDefinitions.find(i => i.id === cb.value))
 +                .sort((a, b) => a.semitones - b.semitones); 
 +
 +            playbackSetting = document.querySelector('input[name="playback"]:checked').value;
 +            difficultySetting = document.querySelector('input[name="difficulty"]:checked').value;
 +            testModeSetting = document.querySelector('input[name="mode"]:checked').value;
 +
 +            if (selectedIntervalsForTest.length === 0) {
 +                alert("Bitte wählen Sie mindestens ein Intervall aus.");
 +                return false;
 +            }
 +            return true;
 +        }
 +        
 +        function generateRandomMidiNote(minMidi, maxMidi) {
 +            return Math.floor(Math.random() * (maxMidi - minMidi + 1)) + minMidi;
 +        }
 +
 +
 +        function generateQuestion() {
 +            questionAnswered = false;
 +            currentQuestionNumber++;
 +            updateProgress(); // Update progress before displaying new question numbers
 +
 +            const randomIntervalDef = selectedIntervalsForTest[Math.floor(Math.random() * selectedIntervalsForTest.length)];
 +            currentTask.interval = randomIntervalDef;
 +            let startNoteMidi;
 +
 +            const c3Midi = baseMidiNotes["c"];    
 +            const c4Midi = baseMidiNotes["c'"];   
 +            const c5Midi = baseMidiNotes["c''"];  
 +            const c6Midi = baseMidiNotes["c'''"]; 
 +
 +
 +            if (difficultySetting === 'easy') {
 +                if (playbackSetting === 'descending') {
 +                    startNoteMidi = c5Midi; 
 +                } else {
 +                    startNoteMidi = c4Midi; 
 +                }
 +            } else if (difficultySetting === 'advanced') {
 +                const ascStarts = [c3Midi, c4Midi, c5Midi]; 
 +                const descStarts = [c4Midi, c5Midi, c6Midi];
 +                if (playbackSetting === 'descending') {
 +                    startNoteMidi = descStarts[Math.floor(Math.random() * descStarts.length)];
 +                } else {
 +                    startNoteMidi = ascStarts[Math.floor(Math.random() * ascStarts.length)];
 +                }
 +            } else { 
 +                const minPlayableMidi = c3Midi; 
 +                const maxPlayableMidi = c6Midi + 11; 
 +
 +                if (playbackSetting === 'descending') {
 +                    const minStart = minPlayableMidi + randomIntervalDef.semitones;
 +                    startNoteMidi = generateRandomMidiNote(Math.max(minStart, c3Midi), c6Midi); 
 +                } else { 
 +                    const maxStart = maxPlayableMidi - randomIntervalDef.semitones;
 +                    startNoteMidi = generateRandomMidiNote(c3Midi, Math.min(maxStart, c6Midi));
 +                }
 +            }
 +
 +            currentTask.startNoteMidi = startNoteMidi;
 +            if (playbackSetting === 'descending') {
 +                currentTask.endNoteMidi = startNoteMidi - randomIntervalDef.semitones;
 +            } else { 
 +                currentTask.endNoteMidi = startNoteMidi + randomIntervalDef.semitones;
 +            }
 +            
 +            if (playbackSetting === 'simultaneous') {
 +                 if (currentTask.startNoteMidi > currentTask.endNoteMidi) {
 +                    [currentTask.startNoteMidi, currentTask.endNoteMidi] = [currentTask.endNoteMidi, currentTask.startNoteMidi];
 +                 }
 +            }
 +
 +            currentTask.startNoteName = midiToGermanNoteName(currentTask.startNoteMidi);
 +            currentTask.endNoteName = midiToGermanNoteName(currentTask.endNoteMidi);
 +            
 +            displayQuestion();
 +        }
 +
 +
 +        function displayQuestion() {
 +            currentQuestionNumberDisplay.textContent = currentQuestionNumber;
 +            totalQuestionNumberDisplay.textContent = totalQuestions;
 +            answerOptionsContainer.innerHTML = ''; 
 +            feedbackMessageDisplay.textContent = '';
 +            feedbackMessageDisplay.className = 'feedback'; 
 +            actionButtonsDiv.classList.add('hidden'); 
 +            nextQuestionButton.classList.add('hidden');
 +            repeatQuestionButton.classList.add('hidden');
 +            showCorrectAnswerButton.classList.add('hidden');
 +            playIntervalButton.disabled = false;
 +
 +
 +            if (testModeSetting === 'interval_recognition') {
 +                taskDescriptionDisplay.textContent = "Welches Intervall hörst du?";
 +                const options = [...selectedIntervalsForTest]; 
 +
 +                options.forEach(interval => {
 +                    const button = document.createElement('button');
 +                    button.textContent = interval.name;
 +                    button.classList.add('answer-button');
 +                    button.dataset.intervalId = interval.id;
 +                    button.onclick = () => checkAnswer(interval.id);
 +                    answerOptionsContainer.appendChild(button);
 +                });
 +                currentTask.correctAnswer = currentTask.interval.id;
 +
 +            } else { 
 +                let displayStartNote = currentTask.startNoteName;
 +                if (playbackSetting === 'simultaneous') {
 +                     displayStartNote = midiToGermanNoteName(Math.min(currentTask.startNoteMidi, currentTask.endNoteMidi));
 +                } else if (playbackSetting === 'descending') {
 +                    // displayStartNote remains currentTask.startNoteName (which is the higher note)
 +                }
 +
 +                taskDescriptionDisplay.textContent = `Der Startton ist ${displayStartNote}. Welches ist der Zielton?`;
 +                currentTask.correctAnswer = currentTask.endNoteName;
 +
 +                let noteOptionsMidi = [currentTask.endNoteMidi];
 +                const numTotalOptions = Math.min(5, Math.max(3, selectedIntervalsForTest.length > 0 ? selectedIntervalsForTest.length : 3)); 
 +
 +                let attempts = 0;
 +                const maxAttempts = 30; 
 +                const deviationRange = 4; 
 +
 +                while (noteOptionsMidi.length < numTotalOptions && attempts < maxAttempts) {
 +                    const randomDeviation = Math.floor(Math.random() * (2 * deviationRange + 1)) - deviationRange;
 +                    if (randomDeviation === 0 && noteOptionsMidi.includes(currentTask.endNoteMidi + randomDeviation) ) { 
 +                        attempts++;
 +                        continue;
 +                    }
 +                    const potentialWrongNoteMidi = currentTask.endNoteMidi + randomDeviation;
 +                    
 +                    if (potentialWrongNoteMidi >= baseMidiNotes["c"] - 12 && potentialWrongNoteMidi <= baseMidiNotes["c'''"] + 12 && !noteOptionsMidi.includes(potentialWrongNoteMidi)) {
 +                        noteOptionsMidi.push(potentialWrongNoteMidi);
 +                    }
 +                    attempts++;
 +                }
 +                
 +                if (noteOptionsMidi.length < numTotalOptions) {
 +                    let otherIntervalChoices = [...selectedIntervalsForTest].filter(i => i.id !== currentTask.interval.id);
 +                    otherIntervalChoices.sort(() => 0.5 - Math.random()); 
 +
 +                    for (let i = 0; i < otherIntervalChoices.length && noteOptionsMidi.length < numTotalOptions; i++) {
 +                        let otherTargetMidi;
 +                        const referenceStartMidi = (playbackSetting === 'simultaneous') ? Math.min(currentTask.startNoteMidi, currentTask.endNoteMidi) : currentTask.startNoteMidi;
 +
 +                        if (playbackSetting === 'descending') {
 +                            otherTargetMidi = referenceStartMidi - otherIntervalChoices[i].semitones;
 +                        } else { 
 +                            otherTargetMidi = referenceStartMidi + otherIntervalChoices[i].semitones;
 +                        }
 +                        if (!noteOptionsMidi.includes(otherTargetMidi) && otherTargetMidi >= baseMidiNotes["c"]-12 && otherTargetMidi <= baseMidiNotes["c'''"]+12) {
 +                            noteOptionsMidi.push(otherTargetMidi);
 +                        }
 +                    }
 +                }
 +                
 +                if (!noteOptionsMidi.includes(currentTask.endNoteMidi)) {
 +                    if (noteOptionsMidi.length >= numTotalOptions) noteOptionsMidi.pop(); 
 +                    noteOptionsMidi.push(currentTask.endNoteMidi);
 +                }
 +
 +                noteOptionsMidi.sort((a, b) => a - b); 
 +
 +                noteOptionsMidi.forEach(noteMidi => {
 +                    const button = document.createElement('button');
 +                    const noteName = midiToGermanNoteName(noteMidi);
 +                    button.textContent = noteName;
 +                    button.classList.add('answer-button');
 +                    button.dataset.noteName = noteName;
 +                    button.onclick = () => checkAnswer(noteName);
 +                    answerOptionsContainer.appendChild(button);
 +                });
 +            }
 +            if (currentQuestionNumber > 0) setTimeout(playCurrentInterval, 100); 
 +        }
 +
 +        function checkAnswer(selectedValue) {
 +            if (questionAnswered) return; 
 +            questionAnswered = true;
 +
 +            const isCorrect = (selectedValue === currentTask.correctAnswer);
 +            const clickedButton = Array.from(answerOptionsContainer.querySelectorAll('.answer-button')).find(btn => {
 +                return testModeSetting === 'interval_recognition' ? btn.dataset.intervalId === selectedValue : btn.dataset.noteName === selectedValue;
 +            });
 +
 +            playIntervalButton.disabled = true; 
 +
 +            if (isCorrect) {
 +                correctAnswersCount++;
 +                feedbackMessageDisplay.textContent = "Richtig!";
 +                feedbackMessageDisplay.className = 'feedback correct';
 +                if(clickedButton) clickedButton.classList.add('correct');
 +                
 +                actionButtonsDiv.classList.remove('hidden');
 +                nextQuestionButton.classList.remove('hidden');
 +                repeatQuestionButton.classList.add('hidden');
 +                showCorrectAnswerButton.classList.add('hidden');
 +
 +            } else {
 +                wrongAnswersCount++;
 +                feedbackMessageDisplay.textContent = "Leider Falsch.";
 +                feedbackMessageDisplay.className = 'feedback incorrect';
 +                if(clickedButton) clickedButton.classList.add('incorrect');
 +
 +                actionButtonsDiv.classList.remove('hidden');
 +                repeatQuestionButton.classList.remove('hidden');
 +                showCorrectAnswerButton.classList.remove('hidden');
 +                nextQuestionButton.classList.add('hidden');
 +            }
 +            updateProgress();
 +            disableAnswerButtons();
 +
 +            if (currentQuestionNumber >= totalQuestions) { 
 +                 nextQuestionButton.textContent = "Ergebnisse anzeigen"; 
 +                 if (!isCorrect) {
 +                    showCorrectAnswerButton.onclick = () => {
 +                        revealCorrectAnswer(true); 
 +                    };
 +                 } else { // if correct and last question
 +                    actionButtonsDiv.classList.remove('hidden');
 +                    nextQuestionButton.classList.remove('hidden'); // Ensure it's visible
 +                 }
 +            }
 +        }
 +        
 +        function disableAnswerButtons() {
 +            answerOptionsContainer.querySelectorAll('.answer-button').forEach(btn => btn.disabled = true);
 +        }
 +        function enableAnswerButtons() {
 +            answerOptionsContainer.querySelectorAll('.answer-button').forEach(btn => btn.disabled = false);
 +        }
 +
 +
 +        function handleRepeatQuestion() {
 +            feedbackMessageDisplay.textContent = '';
 +            feedbackMessageDisplay.className = 'feedback';
 +            enableAnswerButtons();
 +            questionAnswered = false;
 +            playIntervalButton.disabled = false;
 +            actionButtonsDiv.classList.add('hidden'); 
 +
 +            answerOptionsContainer.querySelectorAll('.answer-button').forEach(btn => {
 +                btn.classList.remove('correct', 'incorrect', 'revealed-correct');
 +            });
 +            playCurrentInterval();
 +        }
 +
 +        function revealCorrectAnswer(isEndOfTestPath = false) {
 +            const correctButton = Array.from(answerOptionsContainer.querySelectorAll('.answer-button')).find(btn => {
 +                 return testModeSetting === 'interval_recognition' ? btn.dataset.intervalId === currentTask.correctAnswer : btn.dataset.noteName === currentTask.correctAnswer;
 +            });
 +            if (correctButton && !correctButton.classList.contains('correct')) { 
 +                correctButton.classList.add('revealed-correct');
 +            }
 +            
 +            const correctAnswerText = testModeSetting === 'interval_recognition' ? currentTask.interval.name : currentTask.correctAnswer;
 +            feedbackMessageDisplay.textContent = `Die richtige Antwort war: ${correctAnswerText}.`;
 +            feedbackMessageDisplay.className = 'feedback revealed'; // Use .revealed for specific styling
 +            
 +            actionButtonsDiv.classList.remove('hidden');
 +            nextQuestionButton.classList.remove('hidden');
 +            if (currentQuestionNumber >= totalQuestions || isEndOfTestPath) {
 +                nextQuestionButton.textContent = "Ergebnisse anzeigen";
 +            }
 +            repeatQuestionButton.classList.add('hidden');
 +            showCorrectAnswerButton.classList.add('hidden');
 +        }
 +
 +
 +        function handleNextQuestion() {
 +            if (currentQuestionNumber >= totalQuestions) {
 +                showResults();
 +            } else {
 +                playIntervalButton.disabled = false;
 +                enableAnswerButtons();
 +                answerOptionsContainer.querySelectorAll('.answer-button').forEach(btn => {
 +                    btn.classList.remove('correct', 'incorrect', 'revealed-correct');
 +                });
 +                generateQuestion();
 +            }
 +        }
 +
 +
 +        function updateProgress() {
 +            const answeredTotal = correctAnswersCount + wrongAnswersCount;
 +            answeredQuestionsDisplay.textContent = answeredTotal;
 +            correctAnswersDisplay.textContent = correctAnswersCount;
 +            wrongAnswersDisplay.textContent = wrongAnswersCount;
 +            const rate = answeredTotal > 0 ? Math.round((correctAnswersCount / answeredTotal) * 100) : 0;
 +            successRateDisplay.textContent = rate;
 +        }
 +        
 +        function showResults() {
 +            testArea.classList.add('hidden');
 +            progressArea.classList.add('hidden');
 +            resultsArea.classList.remove('hidden');
 +
 +            finalCorrectDisplay.textContent = correctAnswersCount;
 +            finalTotalDisplay.textContent = totalQuestions;
 +            const percentage = totalQuestions > 0 ? Math.round((correctAnswersCount / totalQuestions) * 100) : 0;
 +            finalPercentageDisplay.textContent = percentage;
 +        }
 +
 +        function resetAndRestart() {
 +            currentQuestionNumber = 0;
 +            correctAnswersCount = 0;
 +            wrongAnswersCount = 0;
 +            questionAnswered = false;
 +            currentTask = {};
 +
 +            resultsArea.classList.add('hidden');
 +            settingsContainer.classList.remove('hidden');
 +            progressArea.classList.add('hidden'); 
 +            testArea.classList.add('hidden'); 
 +
 +            nextQuestionButton.textContent = "Nächste Frage";
 +            showCorrectAnswerButton.onclick = () => revealCorrectAnswer(false); 
 +            
 +            answeredQuestionsDisplay.textContent = 0;
 +            correctAnswersDisplay.textContent = 0;
 +            wrongAnswersDisplay.textContent = 0;
 +            successRateDisplay.textContent = 0;
 +        }
 +
 +
 +        document.addEventListener('DOMContentLoaded', () => {
 +            populateIntervalCheckboxes();
 +            totalQuestionNumberDisplay.textContent = totalQuestions; 
 +
 +            selectAllButton.addEventListener('click', () => toggleAllIntervals(true));
 +            deselectAllButton.addEventListener('click', () => toggleAllIntervals(false));
 +            
 +            startTestButton.addEventListener('click', () => {
 +                initializeAudio(); 
 +                if (audioContext && audioContext.state === 'suspended') {
 +                    audioContext.resume().catch(err => console.error("Error resuming AudioContext on start:", err));
 +                }
 +
 +                if (collectSettings()) {
 +                    settingsContainer.classList.add('hidden');
 +                    testArea.classList.remove('hidden');
 +                    progressArea.classList.remove('hidden');
 +                    resultsArea.classList.add('hidden');
 +                    currentQuestionNumber = 0; 
 +                    correctAnswersCount = 0;
 +                    wrongAnswersCount = 0;
 +                    
 +                    answeredQuestionsDisplay.textContent = 0;
 +                    correctAnswersDisplay.textContent = 0;
 +                    wrongAnswersDisplay.textContent = 0;
 +                    successRateDisplay.textContent = 0;
 +                    generateQuestion(); 
 +                }
 +            });
 +
 +            playIntervalButton.addEventListener('click', playCurrentInterval);
 +            nextQuestionButton.addEventListener('click', handleNextQuestion);
 +            repeatQuestionButton.addEventListener('click', handleRepeatQuestion);
 +            showCorrectAnswerButton.addEventListener('click', () => revealCorrectAnswer(false));
 +            restartTestButton.addEventListener('click', resetAndRestart);
 +        });
 +
 +    </script>
 +</body>
 +</html>