Benutzer-Werkzeuge

Webseiten-Werkzeuge


tools:intervalltrainer

Unterschiede

Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.

Link zu der Vergleichsansicht

Nächste Überarbeitung
Vorherige Überarbeitung
tools:intervalltrainer [10/05/2025 14:32] – angelegt - Externe Bearbeitung 127.0.0.1tools:intervalltrainer [22/05/2025 16:49] (aktuell) – angelegt Eric Weber
Zeile 1: Zeile 1:
 +<html>
 +<head>
 +    <meta charset="UTF-8">
 +    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 +    <title>Intervalltrainer</title>
 +    <style>
 +        /* Reset und Container-Isolation */
 +        .interval-trainer-app {
 +            all: initial;
 +            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
 +            color: #1a1a1a;
 +            background: #f8f9fa;
 +            min-height: 100vh;
 +            display: flex;
 +            flex-direction: column;
 +            align-items: center;
 +            padding: 20px;
 +            box-sizing: border-box;
 +        }
  
 +        .interval-trainer-app * {
 +            box-sizing: border-box;
 +        }
 +
 +        .app-container {
 +            width: 100%;
 +            max-width: 800px;
 +            background: white;
 +            border-radius: 20px;
 +            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
 +            padding: 30px;
 +            margin: 20px 0;
 +        }
 +
 +        h1, h2 {
 +            text-align: center;
 +            color: #2d3748;
 +            margin-bottom: 25px;
 +            font-weight: 700;
 +        }
 +
 +        h1 {
 +            font-size: 2.5rem;
 +            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 +            -webkit-background-clip: text;
 +            -webkit-text-fill-color: transparent;
 +            background-clip: text;
 +            margin-bottom: 40px;
 +        }
 +
 +        h2 {
 +            font-size: 1.8rem;
 +            margin-bottom: 30px;
 +        }
 +
 +        .settings-section {
 +            margin-bottom: 35px;
 +            padding: 25px;
 +            background: rgba(255, 255, 255, 0.8);
 +            border-radius: 15px;
 +            border: 1px solid rgba(102, 126, 234, 0.2);
 +            transition: all 0.3s ease;
 +        }
 +
 +        .settings-section:hover {
 +            box-shadow: 0 8px 25px rgba(102, 126, 234, 0.15);
 +            transform: translateY(-2px);
 +        }
 +
 +        label {
 +            display: block;
 +            margin-bottom: 15px;
 +            font-weight: 600;
 +            color: #2d3748;
 +            font-size: 1.1rem;
 +        }
 +
 +        .interval-checkboxes {
 +            display: grid;
 +            grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
 +            gap: 10px;
 +            margin-bottom: 20px;
 +        }
 +
 +        .interval-checkbox-item {
 +            display: flex;
 +            align-items: center;
 +            padding: 8px 12px;
 +            background: rgba(255, 255, 255, 0.7);
 +            border-radius: 8px;
 +            transition: all 0.2s ease;
 +            cursor: pointer;
 +        }
 +
 +        .interval-checkbox-item:hover {
 +            background: rgba(102, 126, 234, 0.1);
 +            transform: translateX(5px);
 +        }
 +
 +        .interval-checkbox-item label {
 +            display: flex;
 +            align-items: center;
 +            margin: 0;
 +            cursor: pointer;
 +            font-weight: 500;
 +            font-size: 0.95rem;
 +            width: 100%;
 +        }
 +
 +        .interval-checkbox-item input[type="checkbox"] {
 +            margin-right: 10px;
 +            width: 18px;
 +            height: 18px;
 +            accent-color: #667eea;
 +        }
 +
 +        .interval-name {
 +            flex: 1;
 +        }
 +
 +        .play-interval-sample {
 +            background: linear-gradient(135deg, #667eea, #764ba2);
 +            color: white;
 +            border: none;
 +            border-radius: 50%;
 +            width: 30px;
 +            height: 30px;
 +            display: flex;
 +            align-items: center;
 +            justify-content: center;
 +            cursor: pointer;
 +            font-size: 12px;
 +            transition: all 0.2s ease;
 +            margin-left: 10px;
 +        }
 +
 +        .play-interval-sample:hover {
 +            transform: scale(1.1);
 +            box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
 +        }
 +
 +        .play-interval-sample:disabled {
 +            background: #cbd5e0;
 +            cursor: not-allowed;
 +            transform: none;
 +            box-shadow: none;
 +        }
 +
 +        .radio-group {
 +            display: flex;
 +            flex-wrap: wrap;
 +            gap: 15px;
 +        }
 +
 +        .radio-group label {
 +            display: flex;
 +            align-items: center;
 +            margin: 0;
 +            font-weight: 500;
 +            font-size: 0.95rem;
 +            cursor: pointer;
 +            padding: 8px 15px;
 +            background: rgba(255, 255, 255, 0.7);
 +            border-radius: 25px;
 +            transition: all 0.2s ease;
 +        }
 +
 +        .radio-group label:hover {
 +            background: rgba(102, 126, 234, 0.1);
 +        }
 +
 +        .radio-group input[type="radio"] {
 +            margin-right: 8px;
 +            accent-color: #667eea;
 +        }
 +
 +        button {
 +            padding: 12px 24px;
 +            border: none;
 +            border-radius: 25px;
 +            cursor: pointer;
 +            font-size: 1rem;
 +            font-weight: 600;
 +            transition: all 0.3s ease;
 +            margin: 5px;
 +            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
 +        }
 +
 +        button:hover {
 +            transform: translateY(-2px);
 +            box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
 +        }
 +
 +        button:active {
 +            transform: translateY(0);
 +        }
 +
 +        /* Primäre Buttons */
 +        .btn-primary {
 +            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 +            color: white;
 +        }
 +
 +        .btn-primary:hover {
 +            background: linear-gradient(135deg, #5a67d8 0%, #6b46c1 100%);
 +        }
 +
 +        /* Sekundäre Buttons */
 +        .btn-secondary {
 +            background: linear-gradient(135deg, #718096 0%, #4a5568 100%);
 +            color: white;
 +        }
 +
 +        .btn-secondary:hover {
 +            background: linear-gradient(135deg, #4a5568 0%, #2d3748 100%);
 +        }
 +
 +        /* Disabled Buttons */
 +        button:disabled {
 +            background: #e2e8f0 !important;
 +            color: #a0aec0 !important;
 +            cursor: not-allowed !important;
 +            transform: none !important;
 +            box-shadow: none !important;
 +        }
 +
 +        /* Antwort-Buttons */
 +        .answer-button {
 +            background: white;
 +            color: #2d3748;
 +            border: 2px solid #e2e8f0;
 +            margin: 8px;
 +            padding: 15px 20px;
 +            font-weight: 500;
 +            min-width: 150px;
 +        }
 +
 +        .answer-button:hover {
 +            border-color: #667eea;
 +            background: rgba(102, 126, 234, 0.05);
 +        }
 +
 +        .answer-button.correct {
 +            background: linear-gradient(135deg, #48bb78 0%, #38a169 100%);
 +            color: white;
 +            border-color: #38a169;
 +        }
 +
 +        .answer-button.incorrect {
 +            background: linear-gradient(135deg, #f56565 0%, #e53e3e 100%);
 +            color: white;
 +            border-color: #e53e3e;
 +        }
 +
 +        .answer-button.revealed-correct {
 +            background: linear-gradient(135deg, #4299e1 0%, #3182ce 100%);
 +            color: white;
 +            border-color: #3182ce;
 +        }
 +
 +        .answer-options {
 +            display: flex;
 +            flex-wrap: wrap;
 +            justify-content: center;
 +            margin: 20px 0;
 +        }
 +
 +        .test-area {
 +            text-align: center;
 +            padding: 20px;
 +        }
 +
 +        .feedback {
 +            margin: 20px 0;
 +            padding: 15px 20px;
 +            border-radius: 15px;
 +            font-size: 1.1rem;
 +            font-weight: 600;
 +            text-align: center;
 +        }
 +
 +        .feedback.correct {
 +            background: linear-gradient(135deg, #c6f6d5 0%, #9ae6b4 100%);
 +            color: #22543d;
 +            border: 2px solid #68d391;
 +        }
 +
 +        .feedback.incorrect {
 +            background: linear-gradient(135deg, #fed7d7 0%, #feb2b2 100%);
 +            color: #742a2a;
 +            border: 2px solid #fc8181;
 +        }
 +
 +        .feedback.revealed {
 +            background: linear-gradient(135deg, #bee3f8 0%, #90cdf4 100%);
 +            color: #2a4365;
 +            border: 2px solid #63b3ed;
 +        }
 +
 +        .progress-area {
 +            background: rgba(255, 255, 255, 0.9);
 +            border-radius: 15px;
 +            padding: 20px;
 +            margin: 20px 0;
 +            box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05);
 +        }
 +
 +        .progress-grid {
 +            display: grid;
 +            grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
 +            gap: 15px;
 +            margin-top: 15px;
 +        }
 +
 +        .progress-item {
 +            text-align: center;
 +            padding: 15px;
 +            background: rgba(102, 126, 234, 0.1);
 +            border-radius: 10px;
 +        }
 +
 +        .progress-value {
 +            font-size: 1.5rem;
 +            font-weight: 700;
 +            color: #667eea;
 +            display: block;
 +        }
 +
 +        .progress-label {
 +            font-size: 0.9rem;
 +            color: #4a5568;
 +            margin-top: 5px;
 +        }
 +
 +        .results-area {
 +            text-align: center;
 +            padding: 30px;
 +            background: rgba(255, 255, 255, 0.9);
 +            border-radius: 20px;
 +        }
 +
 +        .results-score {
 +            font-size: 3rem;
 +            font-weight: 700;
 +            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
 +            -webkit-background-clip: text;
 +            -webkit-text-fill-color: transparent;
 +            background-clip: text;
 +            margin: 20px 0;
 +        }
 +
 +        .hidden {
 +            display: none;
 +        }
 +
 +        .task-description {
 +            font-size: 1.3rem;
 +            margin: 20px 0;
 +            color: #2d3748;
 +            font-weight: 500;
 +        }
 +
 +        .play-main-button {
 +            background: linear-gradient(135deg, #ed8936 0%, #dd6b20 100%);
 +            color: white;
 +            font-size: 1.2rem;
 +            padding: 15px 30px;
 +            margin: 20px 0;
 +        }
 +
 +        .play-main-button:hover {
 +            background: linear-gradient(135deg, #dd6b20 0%, #c05621 100%);
 +        }
 +
 +        .question-header {
 +            background: rgba(102, 126, 234, 0.1);
 +            padding: 15px;
 +            border-radius: 15px;
 +            margin-bottom: 20px;
 +        }
 +
 +        .question-number {
 +            font-size: 1.2rem;
 +            font-weight: 600;
 +            color: #667eea;
 +        }
 +
 +        @media (max-width: 768px) {
 +            .interval-trainer-app {
 +                padding: 10px;
 +            }
 +
 +            .app-container {
 +                padding: 20px;
 +                margin: 10px 0;
 +            }
 +
 +            h1 {
 +                font-size: 2rem;
 +            }
 +
 +            .interval-checkboxes {
 +                grid-template-columns: 1fr;
 +            }
 +
 +            .radio-group {
 +                flex-direction: column;
 +            }
 +
 +            .answer-options {
 +                flex-direction: column;
 +                align-items: center;
 +            }
 +
 +            .answer-button {
 +                width: 100%;
 +                max-width: 300px;
 +            }
 +        }
 +    </style>
 +</head>
 +<body>
 +    <div class="interval-trainer-app">
 +        <div class="app-container">
 +            <h1>🎵 Intervalltrainer</h1>
 +
 +            <div id="settings-container">
 +                <h2>Einstellungen</h2>
 +
 +                <div class="settings-section">
 +                    <label>Welche Intervalle möchtest du üben?</label>
 +                    <div class="interval-checkboxes" id="interval-checkboxes">
 +                        <!-- Wird dynamisch gefüllt -->
 +                    </div>
 +                    <button id="select-all-intervals" class="btn-primary">Alle auswählen</button>
 +                    <button id="deselect-all-intervals" class="btn-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 bewegen sich in der eingestrichenen Oktave - aufsteigend ab c1, absteigend ab c2)</label>
 +                        <label><input type="radio" name="difficulty" value="advanced"> Fortgeschritten (Alle Intervalle starten ab einem beliebigen "C")</label>
 +                        <label><input type="radio" name="difficulty" value="pro"> Profi (Alle Intervalle starten von einem beliebigen chromatischen 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" class="btn-primary">Prüfung starten</button>
 +            </div>
 +
 +            <div id="test-area" class="test-area hidden">
 +                <div class="question-header">
 +                    <div class="question-number">Frage <span id="current-question-number">1</span> / <span id="total-question-number">10</span></div>
 +                </div>
 +                
 +                <p id="task-description" class="task-description"></p>
 +                <button id="play-interval-button" class="play-main-button">🔊 Intervall abspielen</button>
 +                
 +                <div id="answer-options" class="answer-options">
 +                    <!-- Wird dynamisch gefüllt -->
 +                </div>
 +                
 +                <div id="feedback-message" class="feedback"></div>
 +                
 +                <div id="action-buttons">
 +                    <button id="next-question-button" class="btn-primary hidden">Nächste Frage</button>
 +                    <button id="repeat-question-button" class="btn-secondary hidden">Frage wiederholen</button>
 +                    <button id="show-correct-answer-button" class="btn-secondary hidden">Richtige Antwort zeigen</button>
 +                    <button id="new-settings-button" class="btn-secondary hidden">Neue Einstellungen</button>
 +                </div>
 +            </div>
 +
 +            <div id="progress-area" class="progress-area hidden">
 +                <h2>Fortschritt</h2>
 +                <div class="progress-grid">
 +                    <div class="progress-item">
 +                        <span class="progress-value" id="answered-questions">0</span>
 +                        <div class="progress-label">Beantwortet</div>
 +                    </div>
 +                    <div class="progress-item">
 +                        <span class="progress-value" id="correct-answers-count">0</span>
 +                        <div class="progress-label">Richtig</div>
 +                    </div>
 +                    <div class="progress-item">
 +                        <span class="progress-value" id="wrong-answers-count">0</span>
 +                        <div class="progress-label">Falsch</div>
 +                    </div>
 +                    <div class="progress-item">
 +                        <span class="progress-value" id="success-rate">0</span>%
 +                        <div class="progress-label">Erfolgsquote</div>
 +                    </div>
 +                </div>
 +            </div>
 +
 +            <div id="results-area" class="results-area hidden">
 +                <h2>🎉 Ergebnis</h2>
 +                <p>Du hast die Prüfung abgeschlossen!</p>
 +                <div class="results-score" id="final-percentage">0</div>
 +                <p>Richtige Antworten: <span id="final-correct"></span> / <span id="final-total"></span></p>
 +                <button id="restart-test-button" class="btn-primary">Neue Prüfung starten</button>
 +                <button id="change-settings-button" class="btn-secondary">Einstellungen ändern</button>
 +            </div>
 +        </div>
 +    </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.getElementById('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 newSettingsButton = document.getElementById('new-settings-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');
 +        const changeSettingsButton = document.getElementById('change-settings-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 playIntervalSample(intervalDef, button) {
 +            initializeAudio();
 +            if (!audioContext || audioContext.state === 'suspended') {
 +                audioContext.resume().then(() => {
 +                    actuallyPlayIntervalSample(intervalDef, button);
 +                }).catch(err => console.error("Error resuming AudioContext:", err));
 +            } else {
 +                actuallyPlayIntervalSample(intervalDef, button);
 +            }
 +        }
 +
 +        function actuallyPlayIntervalSample(intervalDef, button) {
 +            const startMidi = baseMidiNotes["c'"];
 +            const endMidi = startMidi + intervalDef.semitones;
 +            
 +            const freq1 = midiToFreq(startMidi);
 +            const freq2 = midiToFreq(endMidi);
 +            const noteDuration = 0.7;
 +
 +            button.disabled = true;
 +            button.textContent = '⏸';
 +
 +            const currentPlayback = document.querySelector('input[name="playback"]:checked').value;
 +            let totalPlaybackTime = noteDuration;
 +
 +            if (currentPlayback === 'ascending') {
 +                playNote(freq1, noteDuration, 0);
 +                playNote(freq2, noteDuration, noteDuration * 0.8);
 +                totalPlaybackTime = noteDuration * 1.8;
 +            } else if (currentPlayback === 'descending') {
 +                playNote(freq2, noteDuration, 0);
 +                playNote(freq1, 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(() => {
 +                button.disabled = false;
 +                button.textContent = '▶';
 +            }, totalPlaybackTime * 1000 + 200);
 +        }
 +
 +        function playCurrentInterval() {
 +            initializeAudio(); 
 +            if (!audioContext || audioContext.state === 'suspended') {
 +                audioContext.resume().then(() => {
 +                    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;
 +            playIntervalButton.textContent = '⏸ Spielt ab...';
 +            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; 
 +                playIntervalButton.textContent = '🔊 Intervall abspielen';
 +            }, totalPlaybackTime * 1000 + 200);
 +        }
 +
 +        function populateIntervalCheckboxes() {
 +            intervalDefinitions.forEach(interval => {
 +                const div = document.createElement('div');
 +                div.className = 'interval-checkbox-item';
 +                
 +                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;
 +                
 +                const nameSpan = document.createElement('span');
 +                nameSpan.className = 'interval-name';
 +                nameSpan.textContent = interval.name;
 +                
 +                const playButton = document.createElement('button');
 +                playButton.className = 'play-interval-sample';
 +                playButton.textContent = '▶';
 +                playButton.title = 'Intervall anhören';
 +                playButton.onclick = (e) => {
 +                    e.preventDefault();
 +                    playIntervalSample(interval, playButton);
 +                };
 +                
 +                label.appendChild(checkbox);
 +                label.appendChild(nameSpan);
 +                div.appendChild(label);
 +                div.appendChild(playButton);
 +                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();
 +
 +            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');
 +            newSettingsButton.classList.add('hidden');
 +            playIntervalButton.disabled = false;
 +            playIntervalButton.textContent = '🔊 Intervall abspielen';
 +
 +            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');
 +                newSettingsButton.classList.remove('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');
 +                newSettingsButton.classList.remove('hidden');
 +            }
 +            updateProgress();
 +            disableAnswerButtons();
 +
 +            if (currentQuestionNumber >= totalQuestions) { 
 +                 nextQuestionButton.textContent = "Ergebnisse anzeigen"; 
 +                 if (!isCorrect) {
 +                    showCorrectAnswerButton.onclick = () => {
 +                        revealCorrectAnswer(true); 
 +                    };
 +                 } else {
 +                    actionButtonsDiv.classList.remove('hidden');
 +                    nextQuestionButton.classList.remove('hidden');
 +                    newSettingsButton.classList.remove('hidden');
 +                 }
 +            }
 +        }
 +        
 +        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;
 +            playIntervalButton.textContent = '🔊 Intervall abspielen';
 +            actionButtonsDiv.classList.add('hidden'); 
 +            newSettingsButton.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';
 +            
 +            actionButtonsDiv.classList.remove('hidden');
 +            nextQuestionButton.classList.remove('hidden');
 +            if (currentQuestionNumber >= totalQuestions || isEndOfTestPath) {
 +                nextQuestionButton.textContent = "Ergebnisse anzeigen";
 +            }
 +            repeatQuestionButton.classList.add('hidden');
 +            showCorrectAnswerButton.classList.add('hidden');
 +            newSettingsButton.classList.add('hidden');
 +        }
 +
 +        function handleNextQuestion() {
 +            if (currentQuestionNumber >= totalQuestions) {
 +                showResults();
 +            } else {
 +                playIntervalButton.disabled = false;
 +                playIntervalButton.textContent = '🔊 Intervall abspielen';
 +                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 goToSettings() {
 +            testArea.classList.add('hidden');
 +            progressArea.classList.add('hidden');
 +            resultsArea.classList.add('hidden');
 +            settingsContainer.classList.remove('hidden');
 +        }
 +
 +        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); 
 +            newSettingsButton.classList.add('hidden');
 +            
 +            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));
 +            newSettingsButton.addEventListener('click', goToSettings);
 +            restartTestButton.addEventListener('click', resetAndRestart);
 +            changeSettingsButton.addEventListener('click', goToSettings);
 +        });
 +    </script>
 +</body>
 +</html>