tools:intervalltrainer
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.
Nächste Überarbeitung | Vorherige Überarbeitung | ||
tools:intervalltrainer [10/05/2025 14:32] – angelegt - Externe Bearbeitung 127.0.0.1 | tools:intervalltrainer [22/05/2025 16:49] (aktuell) – angelegt Eric Weber | ||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
+ | < | ||
+ | < | ||
+ | <meta charset=" | ||
+ | <meta name=" | ||
+ | < | ||
+ | < | ||
+ | /* Reset und Container-Isolation */ | ||
+ | .interval-trainer-app { | ||
+ | all: initial; | ||
+ | font-family: | ||
+ | color: #1a1a1a; | ||
+ | background: #f8f9fa; | ||
+ | min-height: 100vh; | ||
+ | display: flex; | ||
+ | flex-direction: | ||
+ | align-items: | ||
+ | padding: 20px; | ||
+ | box-sizing: border-box; | ||
+ | } | ||
+ | .interval-trainer-app * { | ||
+ | box-sizing: border-box; | ||
+ | } | ||
+ | |||
+ | .app-container { | ||
+ | width: 100%; | ||
+ | max-width: 800px; | ||
+ | background: white; | ||
+ | border-radius: | ||
+ | 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: | ||
+ | font-weight: | ||
+ | } | ||
+ | |||
+ | h1 { | ||
+ | font-size: 2.5rem; | ||
+ | background: linear-gradient(135deg, | ||
+ | -webkit-background-clip: | ||
+ | -webkit-text-fill-color: | ||
+ | background-clip: | ||
+ | margin-bottom: | ||
+ | } | ||
+ | |||
+ | h2 { | ||
+ | font-size: 1.8rem; | ||
+ | margin-bottom: | ||
+ | } | ||
+ | |||
+ | .settings-section { | ||
+ | margin-bottom: | ||
+ | padding: 25px; | ||
+ | background: rgba(255, 255, 255, 0.8); | ||
+ | border-radius: | ||
+ | border: 1px solid rgba(102, 126, 234, 0.2); | ||
+ | transition: all 0.3s ease; | ||
+ | } | ||
+ | |||
+ | .settings-section: | ||
+ | box-shadow: 0 8px 25px rgba(102, 126, 234, 0.15); | ||
+ | transform: translateY(-2px); | ||
+ | } | ||
+ | |||
+ | label { | ||
+ | display: block; | ||
+ | margin-bottom: | ||
+ | font-weight: | ||
+ | color: #2d3748; | ||
+ | font-size: 1.1rem; | ||
+ | } | ||
+ | |||
+ | .interval-checkboxes { | ||
+ | display: grid; | ||
+ | grid-template-columns: | ||
+ | gap: 10px; | ||
+ | margin-bottom: | ||
+ | } | ||
+ | |||
+ | .interval-checkbox-item { | ||
+ | display: flex; | ||
+ | align-items: | ||
+ | padding: 8px 12px; | ||
+ | background: rgba(255, 255, 255, 0.7); | ||
+ | border-radius: | ||
+ | transition: all 0.2s ease; | ||
+ | cursor: pointer; | ||
+ | } | ||
+ | |||
+ | .interval-checkbox-item: | ||
+ | background: rgba(102, 126, 234, 0.1); | ||
+ | transform: translateX(5px); | ||
+ | } | ||
+ | |||
+ | .interval-checkbox-item label { | ||
+ | display: flex; | ||
+ | align-items: | ||
+ | margin: 0; | ||
+ | cursor: pointer; | ||
+ | font-weight: | ||
+ | font-size: 0.95rem; | ||
+ | width: 100%; | ||
+ | } | ||
+ | |||
+ | .interval-checkbox-item input[type=" | ||
+ | margin-right: | ||
+ | width: 18px; | ||
+ | height: 18px; | ||
+ | accent-color: | ||
+ | } | ||
+ | |||
+ | .interval-name { | ||
+ | flex: 1; | ||
+ | } | ||
+ | |||
+ | .play-interval-sample { | ||
+ | background: linear-gradient(135deg, | ||
+ | color: white; | ||
+ | border: none; | ||
+ | border-radius: | ||
+ | width: 30px; | ||
+ | height: 30px; | ||
+ | display: flex; | ||
+ | align-items: | ||
+ | justify-content: | ||
+ | cursor: pointer; | ||
+ | font-size: 12px; | ||
+ | transition: all 0.2s ease; | ||
+ | margin-left: | ||
+ | } | ||
+ | |||
+ | .play-interval-sample: | ||
+ | transform: scale(1.1); | ||
+ | box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4); | ||
+ | } | ||
+ | |||
+ | .play-interval-sample: | ||
+ | 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: | ||
+ | margin: 0; | ||
+ | font-weight: | ||
+ | font-size: 0.95rem; | ||
+ | cursor: pointer; | ||
+ | padding: 8px 15px; | ||
+ | background: rgba(255, 255, 255, 0.7); | ||
+ | border-radius: | ||
+ | transition: all 0.2s ease; | ||
+ | } | ||
+ | |||
+ | .radio-group label:hover { | ||
+ | background: rgba(102, 126, 234, 0.1); | ||
+ | } | ||
+ | |||
+ | .radio-group input[type=" | ||
+ | margin-right: | ||
+ | accent-color: | ||
+ | } | ||
+ | |||
+ | button { | ||
+ | padding: 12px 24px; | ||
+ | border: none; | ||
+ | border-radius: | ||
+ | cursor: pointer; | ||
+ | font-size: 1rem; | ||
+ | font-weight: | ||
+ | transition: all 0.3s ease; | ||
+ | margin: 5px; | ||
+ | box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); | ||
+ | } | ||
+ | |||
+ | button: | ||
+ | transform: translateY(-2px); | ||
+ | box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15); | ||
+ | } | ||
+ | |||
+ | button: | ||
+ | transform: translateY(0); | ||
+ | } | ||
+ | |||
+ | /* Primäre Buttons */ | ||
+ | .btn-primary { | ||
+ | background: linear-gradient(135deg, | ||
+ | color: white; | ||
+ | } | ||
+ | |||
+ | .btn-primary: | ||
+ | background: linear-gradient(135deg, | ||
+ | } | ||
+ | |||
+ | /* Sekundäre Buttons */ | ||
+ | .btn-secondary { | ||
+ | background: linear-gradient(135deg, | ||
+ | color: white; | ||
+ | } | ||
+ | |||
+ | .btn-secondary: | ||
+ | background: linear-gradient(135deg, | ||
+ | } | ||
+ | |||
+ | /* Disabled Buttons */ | ||
+ | button: | ||
+ | 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: | ||
+ | min-width: 150px; | ||
+ | } | ||
+ | |||
+ | .answer-button: | ||
+ | border-color: | ||
+ | background: rgba(102, 126, 234, 0.05); | ||
+ | } | ||
+ | |||
+ | .answer-button.correct { | ||
+ | background: linear-gradient(135deg, | ||
+ | color: white; | ||
+ | border-color: | ||
+ | } | ||
+ | |||
+ | .answer-button.incorrect { | ||
+ | background: linear-gradient(135deg, | ||
+ | color: white; | ||
+ | border-color: | ||
+ | } | ||
+ | |||
+ | .answer-button.revealed-correct { | ||
+ | background: linear-gradient(135deg, | ||
+ | color: white; | ||
+ | border-color: | ||
+ | } | ||
+ | |||
+ | .answer-options { | ||
+ | display: flex; | ||
+ | flex-wrap: wrap; | ||
+ | justify-content: | ||
+ | margin: 20px 0; | ||
+ | } | ||
+ | |||
+ | .test-area { | ||
+ | text-align: center; | ||
+ | padding: 20px; | ||
+ | } | ||
+ | |||
+ | .feedback { | ||
+ | margin: 20px 0; | ||
+ | padding: 15px 20px; | ||
+ | border-radius: | ||
+ | font-size: 1.1rem; | ||
+ | font-weight: | ||
+ | text-align: center; | ||
+ | } | ||
+ | |||
+ | .feedback.correct { | ||
+ | background: linear-gradient(135deg, | ||
+ | color: #22543d; | ||
+ | border: 2px solid #68d391; | ||
+ | } | ||
+ | |||
+ | .feedback.incorrect { | ||
+ | background: linear-gradient(135deg, | ||
+ | color: #742a2a; | ||
+ | border: 2px solid #fc8181; | ||
+ | } | ||
+ | |||
+ | .feedback.revealed { | ||
+ | background: linear-gradient(135deg, | ||
+ | color: #2a4365; | ||
+ | border: 2px solid #63b3ed; | ||
+ | } | ||
+ | |||
+ | .progress-area { | ||
+ | background: rgba(255, 255, 255, 0.9); | ||
+ | border-radius: | ||
+ | padding: 20px; | ||
+ | margin: 20px 0; | ||
+ | box-shadow: 0 4px 15px rgba(0, 0, 0, 0.05); | ||
+ | } | ||
+ | |||
+ | .progress-grid { | ||
+ | display: grid; | ||
+ | grid-template-columns: | ||
+ | gap: 15px; | ||
+ | margin-top: 15px; | ||
+ | } | ||
+ | |||
+ | .progress-item { | ||
+ | text-align: center; | ||
+ | padding: 15px; | ||
+ | background: rgba(102, 126, 234, 0.1); | ||
+ | border-radius: | ||
+ | } | ||
+ | |||
+ | .progress-value { | ||
+ | font-size: 1.5rem; | ||
+ | font-weight: | ||
+ | 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: | ||
+ | } | ||
+ | |||
+ | .results-score { | ||
+ | font-size: 3rem; | ||
+ | font-weight: | ||
+ | background: linear-gradient(135deg, | ||
+ | -webkit-background-clip: | ||
+ | -webkit-text-fill-color: | ||
+ | background-clip: | ||
+ | margin: 20px 0; | ||
+ | } | ||
+ | |||
+ | .hidden { | ||
+ | display: none; | ||
+ | } | ||
+ | |||
+ | .task-description { | ||
+ | font-size: 1.3rem; | ||
+ | margin: 20px 0; | ||
+ | color: #2d3748; | ||
+ | font-weight: | ||
+ | } | ||
+ | |||
+ | .play-main-button { | ||
+ | background: linear-gradient(135deg, | ||
+ | color: white; | ||
+ | font-size: 1.2rem; | ||
+ | padding: 15px 30px; | ||
+ | margin: 20px 0; | ||
+ | } | ||
+ | |||
+ | .play-main-button: | ||
+ | background: linear-gradient(135deg, | ||
+ | } | ||
+ | |||
+ | .question-header { | ||
+ | background: rgba(102, 126, 234, 0.1); | ||
+ | padding: 15px; | ||
+ | border-radius: | ||
+ | margin-bottom: | ||
+ | } | ||
+ | |||
+ | .question-number { | ||
+ | font-size: 1.2rem; | ||
+ | font-weight: | ||
+ | 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: | ||
+ | } | ||
+ | |||
+ | .radio-group { | ||
+ | flex-direction: | ||
+ | } | ||
+ | |||
+ | .answer-options { | ||
+ | flex-direction: | ||
+ | align-items: | ||
+ | } | ||
+ | |||
+ | .answer-button { | ||
+ | width: 100%; | ||
+ | max-width: 300px; | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | </ | ||
+ | < | ||
+ | <div class=" | ||
+ | <div class=" | ||
+ | < | ||
+ | |||
+ | <div id=" | ||
+ | < | ||
+ | |||
+ | <div class=" | ||
+ | < | ||
+ | <div class=" | ||
+ | <!-- Wird dynamisch gefüllt --> | ||
+ | </ | ||
+ | <button id=" | ||
+ | <button id=" | ||
+ | </ | ||
+ | |||
+ | <div class=" | ||
+ | < | ||
+ | <div class=" | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <div class=" | ||
+ | < | ||
+ | <div class=" | ||
+ | < | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <div class=" | ||
+ | < | ||
+ | <div class=" | ||
+ | < | ||
+ | < | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <button id=" | ||
+ | </ | ||
+ | |||
+ | <div id=" | ||
+ | <div class=" | ||
+ | <div class=" | ||
+ | </ | ||
+ | | ||
+ | <p id=" | ||
+ | <button id=" | ||
+ | | ||
+ | <div id=" | ||
+ | <!-- Wird dynamisch gefüllt --> | ||
+ | </ | ||
+ | | ||
+ | <div id=" | ||
+ | | ||
+ | <div id=" | ||
+ | <button id=" | ||
+ | <button id=" | ||
+ | <button id=" | ||
+ | <button id=" | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <div id=" | ||
+ | < | ||
+ | <div class=" | ||
+ | <div class=" | ||
+ | <span class=" | ||
+ | <div class=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <span class=" | ||
+ | <div class=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <span class=" | ||
+ | <div class=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <span class=" | ||
+ | <div class=" | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <div id=" | ||
+ | < | ||
+ | <p>Du hast die Prüfung abgeschlossen!</ | ||
+ | <div class=" | ||
+ | < | ||
+ | <button id=" | ||
+ | <button id=" | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | const intervalDefinitions = [ | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: "Reine Quarte", | ||
+ | { name: " | ||
+ | { name: "Reine Quinte", | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: "Reine Oktave", | ||
+ | ]; | ||
+ | |||
+ | const noteNamesDE = [ | ||
+ | " | ||
+ | " | ||
+ | ]; | ||
+ | |||
+ | const baseMidiNotes = { | ||
+ | " | ||
+ | }; | ||
+ | | ||
+ | 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 = ' | ||
+ | let difficultySetting = ' | ||
+ | let testModeSetting = ' | ||
+ | |||
+ | let currentQuestionNumber = 0; | ||
+ | const totalQuestions = 10; | ||
+ | let correctAnswersCount = 0; | ||
+ | let wrongAnswersCount = 0; | ||
+ | let questionAnswered = false; | ||
+ | |||
+ | // DOM Elements | ||
+ | const settingsContainer = document.getElementById(' | ||
+ | const testArea = document.getElementById(' | ||
+ | const progressArea = document.getElementById(' | ||
+ | const resultsArea = document.getElementById(' | ||
+ | |||
+ | const intervalCheckboxesContainer = document.getElementById(' | ||
+ | const selectAllButton = document.getElementById(' | ||
+ | const deselectAllButton = document.getElementById(' | ||
+ | const startTestButton = document.getElementById(' | ||
+ | |||
+ | const currentQuestionNumberDisplay = document.getElementById(' | ||
+ | const totalQuestionNumberDisplay = document.getElementById(' | ||
+ | const taskDescriptionDisplay = document.getElementById(' | ||
+ | const playIntervalButton = document.getElementById(' | ||
+ | const answerOptionsContainer = document.getElementById(' | ||
+ | const feedbackMessageDisplay = document.getElementById(' | ||
+ | const actionButtonsDiv = document.getElementById(' | ||
+ | const nextQuestionButton = document.getElementById(' | ||
+ | const repeatQuestionButton = document.getElementById(' | ||
+ | const showCorrectAnswerButton = document.getElementById(' | ||
+ | const newSettingsButton = document.getElementById(' | ||
+ | |||
+ | const answeredQuestionsDisplay = document.getElementById(' | ||
+ | const correctAnswersDisplay = document.getElementById(' | ||
+ | const wrongAnswersDisplay = document.getElementById(' | ||
+ | const successRateDisplay = document.getElementById(' | ||
+ | | ||
+ | const finalCorrectDisplay = document.getElementById(' | ||
+ | const finalTotalDisplay = document.getElementById(' | ||
+ | const finalPercentageDisplay = document.getElementById(' | ||
+ | const restartTestButton = document.getElementById(' | ||
+ | const changeSettingsButton = document.getElementById(' | ||
+ | |||
+ | function initializeAudio() { | ||
+ | if (!audioContext) { | ||
+ | try { | ||
+ | audioContext = new (window.AudioContext || window.webkitAudioContext)(); | ||
+ | } catch (e) { | ||
+ | alert(' | ||
+ | console.error(' | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function playNote(frequency, | ||
+ | if (!audioContext) return; | ||
+ | const oscillator = audioContext.createOscillator(); | ||
+ | const gainNode = audioContext.createGain(); | ||
+ | |||
+ | oscillator.type = waveType; | ||
+ | oscillator.frequency.setValueAtTime(frequency, | ||
+ | | ||
+ | gainNode.gain.setValueAtTime(0, | ||
+ | gainNode.gain.linearRampToValueAtTime(0.3, | ||
+ | gainNode.gain.setValueAtTime(0.3, | ||
+ | gainNode.gain.linearRampToValueAtTime(0, | ||
+ | |||
+ | oscillator.connect(gainNode); | ||
+ | gainNode.connect(audioContext.destination); | ||
+ | |||
+ | oscillator.start(audioContext.currentTime + startTime); | ||
+ | oscillator.stop(audioContext.currentTime + startTime + duration + 0.1); | ||
+ | } | ||
+ | |||
+ | function playIntervalSample(intervalDef, | ||
+ | initializeAudio(); | ||
+ | if (!audioContext || audioContext.state === ' | ||
+ | audioContext.resume().then(() => { | ||
+ | actuallyPlayIntervalSample(intervalDef, | ||
+ | }).catch(err => console.error(" | ||
+ | } else { | ||
+ | actuallyPlayIntervalSample(intervalDef, | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function actuallyPlayIntervalSample(intervalDef, | ||
+ | const startMidi = baseMidiNotes[" | ||
+ | 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(' | ||
+ | let totalPlaybackTime = noteDuration; | ||
+ | |||
+ | if (currentPlayback === ' | ||
+ | playNote(freq1, | ||
+ | playNote(freq2, | ||
+ | totalPlaybackTime = noteDuration * 1.8; | ||
+ | } else if (currentPlayback === ' | ||
+ | playNote(freq2, | ||
+ | playNote(freq1, | ||
+ | totalPlaybackTime = noteDuration * 1.8; | ||
+ | } else { | ||
+ | playNote(freq1, | ||
+ | playNote(freq2, | ||
+ | totalPlaybackTime = noteDuration + 0.3; | ||
+ | } | ||
+ | |||
+ | setTimeout(() => { | ||
+ | button.disabled = false; | ||
+ | button.textContent = ' | ||
+ | }, totalPlaybackTime * 1000 + 200); | ||
+ | } | ||
+ | |||
+ | function playCurrentInterval() { | ||
+ | initializeAudio(); | ||
+ | if (!audioContext || audioContext.state === ' | ||
+ | audioContext.resume().then(() => { | ||
+ | actuallyPlayInterval(); | ||
+ | }).catch(err => console.error(" | ||
+ | } else { | ||
+ | actuallyPlayInterval(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | function actuallyPlayInterval() { | ||
+ | if (!currentTask.startNoteMidi || typeof currentTask.endNoteMidi === ' | ||
+ | |||
+ | 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 === ' | ||
+ | playNote(freq1, | ||
+ | playNote(freq2, | ||
+ | totalPlaybackTime = noteDuration * 1.8; | ||
+ | } else if (playbackSetting === ' | ||
+ | playNote(freq1, | ||
+ | playNote(freq2, | ||
+ | totalPlaybackTime = noteDuration * 1.8; | ||
+ | } else { | ||
+ | playNote(freq1, | ||
+ | playNote(freq2, | ||
+ | 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.className = ' | ||
+ | | ||
+ | const label = document.createElement(' | ||
+ | const checkbox = document.createElement(' | ||
+ | checkbox.type = ' | ||
+ | checkbox.value = interval.id; | ||
+ | checkbox.id = `interval-${interval.id}`; | ||
+ | checkbox.dataset.semitones = interval.semitones; | ||
+ | checkbox.checked = true; | ||
+ | | ||
+ | const nameSpan = document.createElement(' | ||
+ | nameSpan.className = ' | ||
+ | nameSpan.textContent = interval.name; | ||
+ | | ||
+ | const playButton = document.createElement(' | ||
+ | playButton.className = ' | ||
+ | playButton.textContent = ' | ||
+ | playButton.title = ' | ||
+ | playButton.onclick = (e) => { | ||
+ | e.preventDefault(); | ||
+ | playIntervalSample(interval, | ||
+ | }; | ||
+ | | ||
+ | label.appendChild(checkbox); | ||
+ | label.appendChild(nameSpan); | ||
+ | div.appendChild(label); | ||
+ | div.appendChild(playButton); | ||
+ | intervalCheckboxesContainer.appendChild(div); | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | function toggleAllIntervals(select) { | ||
+ | intervalCheckboxesContainer.querySelectorAll(' | ||
+ | cb.checked = select; | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | function collectSettings() { | ||
+ | selectedIntervalsForTest = Array.from(intervalCheckboxesContainer.querySelectorAll(' | ||
+ | .map(cb => intervalDefinitions.find(i => i.id === cb.value)) | ||
+ | .sort((a, b) => a.semitones - b.semitones); | ||
+ | |||
+ | playbackSetting = document.querySelector(' | ||
+ | difficultySetting = document.querySelector(' | ||
+ | testModeSetting = document.querySelector(' | ||
+ | |||
+ | if (selectedIntervalsForTest.length === 0) { | ||
+ | alert(" | ||
+ | return false; | ||
+ | } | ||
+ | return true; | ||
+ | } | ||
+ | | ||
+ | function generateRandomMidiNote(minMidi, | ||
+ | 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[" | ||
+ | const c4Midi = baseMidiNotes[" | ||
+ | const c5Midi = baseMidiNotes[" | ||
+ | const c6Midi = baseMidiNotes[" | ||
+ | |||
+ | if (difficultySetting === ' | ||
+ | if (playbackSetting === ' | ||
+ | startNoteMidi = c5Midi; | ||
+ | } else { | ||
+ | startNoteMidi = c4Midi; | ||
+ | } | ||
+ | } else if (difficultySetting === ' | ||
+ | const ascStarts = [c3Midi, c4Midi, c5Midi]; | ||
+ | const descStarts = [c4Midi, c5Midi, c6Midi]; | ||
+ | if (playbackSetting === ' | ||
+ | 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 === ' | ||
+ | const minStart = minPlayableMidi + randomIntervalDef.semitones; | ||
+ | startNoteMidi = generateRandomMidiNote(Math.max(minStart, | ||
+ | } else { | ||
+ | const maxStart = maxPlayableMidi - randomIntervalDef.semitones; | ||
+ | startNoteMidi = generateRandomMidiNote(c3Midi, | ||
+ | } | ||
+ | } | ||
+ | |||
+ | currentTask.startNoteMidi = startNoteMidi; | ||
+ | if (playbackSetting === ' | ||
+ | currentTask.endNoteMidi = startNoteMidi - randomIntervalDef.semitones; | ||
+ | } else { | ||
+ | currentTask.endNoteMidi = startNoteMidi + randomIntervalDef.semitones; | ||
+ | } | ||
+ | | ||
+ | if (playbackSetting === ' | ||
+ | if (currentTask.startNoteMidi > 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 = ' | ||
+ | actionButtonsDiv.classList.add(' | ||
+ | nextQuestionButton.classList.add(' | ||
+ | repeatQuestionButton.classList.add(' | ||
+ | showCorrectAnswerButton.classList.add(' | ||
+ | newSettingsButton.classList.add(' | ||
+ | playIntervalButton.disabled = false; | ||
+ | playIntervalButton.textContent = '🔊 Intervall abspielen'; | ||
+ | |||
+ | if (testModeSetting === ' | ||
+ | taskDescriptionDisplay.textContent = " | ||
+ | const options = [...selectedIntervalsForTest]; | ||
+ | |||
+ | options.forEach(interval => { | ||
+ | const button = document.createElement(' | ||
+ | button.textContent = interval.name; | ||
+ | button.classList.add(' | ||
+ | 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 === ' | ||
+ | | ||
+ | } else if (playbackSetting === ' | ||
+ | // 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[" | ||
+ | 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; | ||
+ | let otherTargetMidi; | ||
+ | const referenceStartMidi = (playbackSetting === ' | ||
+ | |||
+ | if (playbackSetting === ' | ||
+ | otherTargetMidi = referenceStartMidi - otherIntervalChoices[i].semitones; | ||
+ | } else { | ||
+ | otherTargetMidi = referenceStartMidi + otherIntervalChoices[i].semitones; | ||
+ | } | ||
+ | if (!noteOptionsMidi.includes(otherTargetMidi) && otherTargetMidi >= baseMidiNotes[" | ||
+ | noteOptionsMidi.push(otherTargetMidi); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | | ||
+ | if (!noteOptionsMidi.includes(currentTask.endNoteMidi)) { | ||
+ | if (noteOptionsMidi.length >= numTotalOptions) noteOptionsMidi.pop(); | ||
+ | noteOptionsMidi.push(currentTask.endNoteMidi); | ||
+ | } | ||
+ | |||
+ | noteOptionsMidi.sort((a, | ||
+ | |||
+ | noteOptionsMidi.forEach(noteMidi => { | ||
+ | const button = document.createElement(' | ||
+ | const noteName = midiToGermanNoteName(noteMidi); | ||
+ | button.textContent = noteName; | ||
+ | button.classList.add(' | ||
+ | button.dataset.noteName = noteName; | ||
+ | button.onclick = () => checkAnswer(noteName); | ||
+ | answerOptionsContainer.appendChild(button); | ||
+ | }); | ||
+ | } | ||
+ | if (currentQuestionNumber > 0) setTimeout(playCurrentInterval, | ||
+ | } | ||
+ | |||
+ | function checkAnswer(selectedValue) { | ||
+ | if (questionAnswered) return; | ||
+ | questionAnswered = true; | ||
+ | |||
+ | const isCorrect = (selectedValue === currentTask.correctAnswer); | ||
+ | const clickedButton = Array.from(answerOptionsContainer.querySelectorAll(' | ||
+ | return testModeSetting === ' | ||
+ | }); | ||
+ | |||
+ | playIntervalButton.disabled = true; | ||
+ | |||
+ | if (isCorrect) { | ||
+ | correctAnswersCount++; | ||
+ | feedbackMessageDisplay.textContent = "🎉 Richtig!"; | ||
+ | feedbackMessageDisplay.className = ' | ||
+ | if(clickedButton) clickedButton.classList.add(' | ||
+ | | ||
+ | actionButtonsDiv.classList.remove(' | ||
+ | nextQuestionButton.classList.remove(' | ||
+ | repeatQuestionButton.classList.add(' | ||
+ | showCorrectAnswerButton.classList.add(' | ||
+ | newSettingsButton.classList.remove(' | ||
+ | |||
+ | } else { | ||
+ | wrongAnswersCount++; | ||
+ | feedbackMessageDisplay.textContent = "❌ Leider falsch."; | ||
+ | feedbackMessageDisplay.className = ' | ||
+ | if(clickedButton) clickedButton.classList.add(' | ||
+ | |||
+ | actionButtonsDiv.classList.remove(' | ||
+ | repeatQuestionButton.classList.remove(' | ||
+ | showCorrectAnswerButton.classList.remove(' | ||
+ | nextQuestionButton.classList.add(' | ||
+ | newSettingsButton.classList.remove(' | ||
+ | } | ||
+ | updateProgress(); | ||
+ | disableAnswerButtons(); | ||
+ | |||
+ | if (currentQuestionNumber >= totalQuestions) { | ||
+ | | ||
+ | if (!isCorrect) { | ||
+ | showCorrectAnswerButton.onclick = () => { | ||
+ | revealCorrectAnswer(true); | ||
+ | }; | ||
+ | } else { | ||
+ | actionButtonsDiv.classList.remove(' | ||
+ | nextQuestionButton.classList.remove(' | ||
+ | newSettingsButton.classList.remove(' | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | | ||
+ | function disableAnswerButtons() { | ||
+ | answerOptionsContainer.querySelectorAll(' | ||
+ | } | ||
+ | | ||
+ | function enableAnswerButtons() { | ||
+ | answerOptionsContainer.querySelectorAll(' | ||
+ | } | ||
+ | |||
+ | function handleRepeatQuestion() { | ||
+ | feedbackMessageDisplay.textContent = ''; | ||
+ | feedbackMessageDisplay.className = ' | ||
+ | enableAnswerButtons(); | ||
+ | questionAnswered = false; | ||
+ | playIntervalButton.disabled = false; | ||
+ | playIntervalButton.textContent = '🔊 Intervall abspielen'; | ||
+ | actionButtonsDiv.classList.add(' | ||
+ | newSettingsButton.classList.add(' | ||
+ | |||
+ | answerOptionsContainer.querySelectorAll(' | ||
+ | btn.classList.remove(' | ||
+ | }); | ||
+ | playCurrentInterval(); | ||
+ | } | ||
+ | |||
+ | function revealCorrectAnswer(isEndOfTestPath = false) { | ||
+ | const correctButton = Array.from(answerOptionsContainer.querySelectorAll(' | ||
+ | | ||
+ | }); | ||
+ | if (correctButton && !correctButton.classList.contains(' | ||
+ | correctButton.classList.add(' | ||
+ | } | ||
+ | | ||
+ | const correctAnswerText = testModeSetting === ' | ||
+ | feedbackMessageDisplay.textContent = `💡 Die richtige Antwort war: ${correctAnswerText}`; | ||
+ | feedbackMessageDisplay.className = ' | ||
+ | | ||
+ | actionButtonsDiv.classList.remove(' | ||
+ | nextQuestionButton.classList.remove(' | ||
+ | if (currentQuestionNumber >= totalQuestions || isEndOfTestPath) { | ||
+ | nextQuestionButton.textContent = " | ||
+ | } | ||
+ | repeatQuestionButton.classList.add(' | ||
+ | showCorrectAnswerButton.classList.add(' | ||
+ | newSettingsButton.classList.add(' | ||
+ | } | ||
+ | |||
+ | function handleNextQuestion() { | ||
+ | if (currentQuestionNumber >= totalQuestions) { | ||
+ | showResults(); | ||
+ | } else { | ||
+ | playIntervalButton.disabled = false; | ||
+ | playIntervalButton.textContent = '🔊 Intervall abspielen'; | ||
+ | enableAnswerButtons(); | ||
+ | answerOptionsContainer.querySelectorAll(' | ||
+ | btn.classList.remove(' | ||
+ | }); | ||
+ | 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(' | ||
+ | progressArea.classList.add(' | ||
+ | resultsArea.classList.remove(' | ||
+ | |||
+ | finalCorrectDisplay.textContent = correctAnswersCount; | ||
+ | finalTotalDisplay.textContent = totalQuestions; | ||
+ | const percentage = totalQuestions > 0 ? Math.round((correctAnswersCount / totalQuestions) * 100) : 0; | ||
+ | finalPercentageDisplay.textContent = percentage; | ||
+ | } | ||
+ | |||
+ | function goToSettings() { | ||
+ | testArea.classList.add(' | ||
+ | progressArea.classList.add(' | ||
+ | resultsArea.classList.add(' | ||
+ | settingsContainer.classList.remove(' | ||
+ | } | ||
+ | |||
+ | function resetAndRestart() { | ||
+ | currentQuestionNumber = 0; | ||
+ | correctAnswersCount = 0; | ||
+ | wrongAnswersCount = 0; | ||
+ | questionAnswered = false; | ||
+ | currentTask = {}; | ||
+ | |||
+ | resultsArea.classList.add(' | ||
+ | settingsContainer.classList.remove(' | ||
+ | progressArea.classList.add(' | ||
+ | testArea.classList.add(' | ||
+ | |||
+ | nextQuestionButton.textContent = " | ||
+ | showCorrectAnswerButton.onclick = () => revealCorrectAnswer(false); | ||
+ | newSettingsButton.classList.add(' | ||
+ | | ||
+ | answeredQuestionsDisplay.textContent = 0; | ||
+ | correctAnswersDisplay.textContent = 0; | ||
+ | wrongAnswersDisplay.textContent = 0; | ||
+ | successRateDisplay.textContent = 0; | ||
+ | } | ||
+ | |||
+ | document.addEventListener(' | ||
+ | populateIntervalCheckboxes(); | ||
+ | totalQuestionNumberDisplay.textContent = totalQuestions; | ||
+ | |||
+ | selectAllButton.addEventListener(' | ||
+ | deselectAllButton.addEventListener(' | ||
+ | | ||
+ | startTestButton.addEventListener(' | ||
+ | initializeAudio(); | ||
+ | if (audioContext && audioContext.state === ' | ||
+ | audioContext.resume().catch(err => console.error(" | ||
+ | } | ||
+ | |||
+ | if (collectSettings()) { | ||
+ | settingsContainer.classList.add(' | ||
+ | testArea.classList.remove(' | ||
+ | progressArea.classList.remove(' | ||
+ | resultsArea.classList.add(' | ||
+ | currentQuestionNumber = 0; | ||
+ | correctAnswersCount = 0; | ||
+ | wrongAnswersCount = 0; | ||
+ | | ||
+ | answeredQuestionsDisplay.textContent = 0; | ||
+ | correctAnswersDisplay.textContent = 0; | ||
+ | wrongAnswersDisplay.textContent = 0; | ||
+ | successRateDisplay.textContent = 0; | ||
+ | generateQuestion(); | ||
+ | } | ||
+ | }); | ||
+ | |||
+ | playIntervalButton.addEventListener(' | ||
+ | nextQuestionButton.addEventListener(' | ||
+ | repeatQuestionButton.addEventListener(' | ||
+ | showCorrectAnswerButton.addEventListener(' | ||
+ | newSettingsButton.addEventListener(' | ||
+ | restartTestButton.addEventListener(' | ||
+ | changeSettingsButton.addEventListener(' | ||
+ | }); | ||
+ | </ | ||
+ | </ | ||
+ | </ |