tools:skalentrainer
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.
Beide Seiten, vorherige ÜberarbeitungVorherige ÜberarbeitungNächste Überarbeitung | Vorherige Überarbeitung | ||
tools:skalentrainer [15/05/2025 08:59] – gelöscht - Externe Bearbeitung (Unknown date) 127.0.0.1 | tools:skalentrainer [22/05/2025 16:31] (aktuell) – Eric Weber | ||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
+ | < | ||
+ | < | ||
+ | <meta charset=" | ||
+ | <meta name=" | ||
+ | < | ||
+ | < | ||
+ | body { | ||
+ | font-family: | ||
+ | line-height: | ||
+ | color: #333; | ||
+ | max-width: 1000px; | ||
+ | margin: 0 auto; | ||
+ | padding: 20px; | ||
+ | background-color: | ||
+ | } | ||
+ | h1, h2 { | ||
+ | color: #2c3e50; | ||
+ | text-align: center; | ||
+ | } | ||
+ | .container { | ||
+ | background-color: | ||
+ | border-radius: | ||
+ | padding: 20px; | ||
+ | box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | ||
+ | margin-bottom: | ||
+ | } | ||
+ | .flex-row { | ||
+ | display: flex; | ||
+ | flex-wrap: wrap; | ||
+ | gap: 10px; | ||
+ | align-items: | ||
+ | margin-bottom: | ||
+ | } | ||
+ | .scales-container { | ||
+ | display: flex; | ||
+ | flex-wrap: wrap; | ||
+ | gap: 10px; | ||
+ | margin-bottom: | ||
+ | } | ||
+ | .checkbox-container { | ||
+ | display: flex; | ||
+ | align-items: | ||
+ | background-color: | ||
+ | padding: 8px 12px; | ||
+ | border-radius: | ||
+ | cursor: pointer; | ||
+ | transition: background-color 0.2s; | ||
+ | } | ||
+ | .checkbox-container: | ||
+ | background-color: | ||
+ | } | ||
+ | .checkbox-container input { | ||
+ | margin-right: | ||
+ | } | ||
+ | label { | ||
+ | margin-right: | ||
+ | font-weight: | ||
+ | } | ||
+ | button { | ||
+ | background-color: | ||
+ | color: black; | ||
+ | border: 2px solid #1a2530; | ||
+ | padding: 10px 15px; | ||
+ | border-radius: | ||
+ | cursor: pointer; | ||
+ | font-size: 16px; | ||
+ | transition: all 0.2s; | ||
+ | font-weight: | ||
+ | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); | ||
+ | } | ||
+ | button: | ||
+ | background-color: | ||
+ | transform: translateY(-2px); | ||
+ | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); | ||
+ | } | ||
+ | button: | ||
+ | transform: translateY(0); | ||
+ | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); | ||
+ | } | ||
+ | button: | ||
+ | background-color: | ||
+ | border-color: | ||
+ | cursor: not-allowed; | ||
+ | box-shadow: none; | ||
+ | } | ||
+ | .select-all-btn { | ||
+ | margin-left: | ||
+ | } | ||
+ | .mode-selector { | ||
+ | display: flex; | ||
+ | justify-content: | ||
+ | gap: 15px; | ||
+ | margin-bottom: | ||
+ | } | ||
+ | .mode-btn { | ||
+ | padding: 10px 20px; | ||
+ | background-color: | ||
+ | color: #2c3e50; | ||
+ | border: 2px solid #bdc3c7; | ||
+ | font-weight: | ||
+ | } | ||
+ | .mode-btn.active { | ||
+ | background-color: | ||
+ | color: black; | ||
+ | border-color: | ||
+ | } | ||
+ | # | ||
+ | margin-top: 30px; | ||
+ | } | ||
+ | # | ||
+ | display: none; | ||
+ | } | ||
+ | .practice-scales-container { | ||
+ | display: grid; | ||
+ | grid-template-columns: | ||
+ | gap: 15px; | ||
+ | margin: 20px 0; | ||
+ | } | ||
+ | .practice-scale-btn { | ||
+ | background-color: | ||
+ | color: #2c3e50; | ||
+ | border: 2px solid #bdc3c7; | ||
+ | padding: 15px; | ||
+ | border-radius: | ||
+ | cursor: pointer; | ||
+ | font-size: 16px; | ||
+ | font-weight: | ||
+ | text-align: center; | ||
+ | transition: all 0.2s; | ||
+ | } | ||
+ | .practice-scale-btn: | ||
+ | background-color: | ||
+ | transform: translateY(-2px); | ||
+ | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); | ||
+ | } | ||
+ | .practice-scale-btn: | ||
+ | transform: translateY(0); | ||
+ | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); | ||
+ | } | ||
+ | .practice-scale-btn.playing { | ||
+ | background-color: | ||
+ | color: black; | ||
+ | border-color: | ||
+ | } | ||
+ | .quiz-options { | ||
+ | display: grid; | ||
+ | grid-template-columns: | ||
+ | gap: 10px; | ||
+ | margin: 20px 0; | ||
+ | } | ||
+ | .quiz-option { | ||
+ | background-color: | ||
+ | padding: 12px; | ||
+ | border-radius: | ||
+ | text-align: center; | ||
+ | cursor: pointer; | ||
+ | transition: all 0.2s; | ||
+ | } | ||
+ | .quiz-option: | ||
+ | background-color: | ||
+ | } | ||
+ | .correct { | ||
+ | background-color: | ||
+ | color: black; | ||
+ | } | ||
+ | .incorrect { | ||
+ | background-color: | ||
+ | color: black; | ||
+ | } | ||
+ | .progress-container { | ||
+ | margin-top: 30px; | ||
+ | text-align: center; | ||
+ | } | ||
+ | .progress-bar { | ||
+ | height: 10px; | ||
+ | background-color: | ||
+ | border-radius: | ||
+ | margin-top: 10px; | ||
+ | overflow: hidden; | ||
+ | } | ||
+ | .progress-fill { | ||
+ | height: 100%; | ||
+ | background-color: | ||
+ | width: 0%; | ||
+ | transition: width 0.3s; | ||
+ | } | ||
+ | .progress-markers { | ||
+ | display: flex; | ||
+ | justify-content: | ||
+ | margin-top: 5px; | ||
+ | } | ||
+ | .progress-marker { | ||
+ | width: 20px; | ||
+ | height: 20px; | ||
+ | border-radius: | ||
+ | background-color: | ||
+ | display: flex; | ||
+ | align-items: | ||
+ | justify-content: | ||
+ | font-size: 12px; | ||
+ | } | ||
+ | .progress-marker.completed { | ||
+ | background-color: | ||
+ | color: black; | ||
+ | } | ||
+ | .progress-marker.correct { | ||
+ | background-color: | ||
+ | color: black; | ||
+ | } | ||
+ | .progress-marker.incorrect { | ||
+ | background-color: | ||
+ | color: black; | ||
+ | } | ||
+ | # | ||
+ | display: none; | ||
+ | margin-top: 15px; | ||
+ | } | ||
+ | .separator { | ||
+ | margin: 0 10px; | ||
+ | color: #7f8c8d; | ||
+ | } | ||
+ | .control-section { | ||
+ | display: flex; | ||
+ | gap: 10px; | ||
+ | margin: 20px 0; | ||
+ | } | ||
+ | # | ||
+ | display: none; | ||
+ | } | ||
+ | #results { | ||
+ | font-weight: | ||
+ | font-size: 18px; | ||
+ | margin-top: 20px; | ||
+ | text-align: center; | ||
+ | } | ||
+ | </ | ||
+ | </ | ||
+ | < | ||
+ | < | ||
+ | | ||
+ | <div class=" | ||
+ | < | ||
+ | | ||
+ | <!-- Mode selection --> | ||
+ | <div class=" | ||
+ | <button class=" | ||
+ | <button class=" | ||
+ | <button class=" | ||
+ | </ | ||
+ | | ||
+ | <!-- Scales selection --> | ||
+ | <div class=" | ||
+ | < | ||
+ | <button id=" | ||
+ | </ | ||
+ | | ||
+ | <div class=" | ||
+ | <div class=" | ||
+ | <input type=" | ||
+ | <label for=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <input type=" | ||
+ | <label for=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <input type=" | ||
+ | <label for=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <input type=" | ||
+ | <label for=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <input type=" | ||
+ | <label for=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <input type=" | ||
+ | <label for=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <input type=" | ||
+ | <label for=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <input type=" | ||
+ | <label for=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <input type=" | ||
+ | <label for=" | ||
+ | </ | ||
+ | </ | ||
+ | </ | ||
+ | <div id=" | ||
+ | < | ||
+ | < | ||
+ | | ||
+ | <div class=" | ||
+ | <!-- Practice scale buttons will be generated here --> | ||
+ | </ | ||
+ | | ||
+ | <div class=" | ||
+ | <button id=" | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | <div id=" | ||
+ | < | ||
+ | <p id=" | ||
+ | | ||
+ | <button id=" | ||
+ | | ||
+ | <div class=" | ||
+ | <!-- Options will be generated here --> | ||
+ | </ | ||
+ | | ||
+ | <div class=" | ||
+ | <button id=" | ||
+ | <button id=" | ||
+ | <button id=" | ||
+ | </ | ||
+ | | ||
+ | <div class=" | ||
+ | <div id=" | ||
+ | <div class=" | ||
+ | <div class=" | ||
+ | </ | ||
+ | <div class=" | ||
+ | <!-- Markers will be generated here --> | ||
+ | </ | ||
+ | <div id=" | ||
+ | </ | ||
+ | </ | ||
+ | |||
+ | < | ||
+ | // Audio context and main variables | ||
+ | let audioContext; | ||
+ | let currentMode = " | ||
+ | let allScalesSelected = false; | ||
+ | let quizInProgress = false; | ||
+ | let currentQuizQuestion = 0; | ||
+ | let totalQuizQuestions = 10; | ||
+ | let correctAnswers = 0; | ||
+ | let quizScales = []; | ||
+ | let currentScale; | ||
+ | let questionAnswered = false; | ||
+ | let currentlyPlayingScale = null; | ||
+ | |||
+ | // Function to stop all audio | ||
+ | function stopAllAudio() { | ||
+ | // If there' | ||
+ | if (quizInProgress) { | ||
+ | resetQuiz(); | ||
+ | } | ||
+ | | ||
+ | // Reset practice scale visual state | ||
+ | resetPracticeScaleButtons(); | ||
+ | | ||
+ | // If audio context exists, cancel all scheduled sounds | ||
+ | if (audioContext) { | ||
+ | // Create a new audio context to stop all sounds | ||
+ | audioContext.close().then(() => { | ||
+ | audioContext = null; | ||
+ | }); | ||
+ | } | ||
+ | } | ||
+ | | ||
+ | // Reset quiz state | ||
+ | function resetQuiz() { | ||
+ | if (quizInProgress) { | ||
+ | quizInProgress = false; | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Reset practice scale button visual states | ||
+ | function resetPracticeScaleButtons() { | ||
+ | document.querySelectorAll(" | ||
+ | btn.classList.remove(" | ||
+ | }); | ||
+ | currentlyPlayingScale = null; | ||
+ | } | ||
+ | |||
+ | // Initialize the application | ||
+ | document.addEventListener(" | ||
+ | // Set up event listeners | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | |||
+ | // Mode buttons | ||
+ | document.querySelectorAll(" | ||
+ | btn.addEventListener(" | ||
+ | setMode(this.dataset.mode); | ||
+ | stopAllAudio(); | ||
+ | updatePracticeScales(); | ||
+ | }); | ||
+ | }); | ||
+ | |||
+ | // Add event listeners to stop audio when settings change | ||
+ | document.querySelectorAll(" | ||
+ | checkbox.addEventListener(" | ||
+ | stopAllAudio(); | ||
+ | updatePracticeScales(); | ||
+ | }); | ||
+ | }); | ||
+ | |||
+ | // Initialize progress markers and practice scales | ||
+ | initializeProgressMarkers(); | ||
+ | updatePracticeScales(); | ||
+ | }); | ||
+ | |||
+ | // Toggle all scales selection | ||
+ | function toggleAllScales() { | ||
+ | const scaleCheckboxes = document.querySelectorAll(" | ||
+ | allScalesSelected = !allScalesSelected; | ||
+ | | ||
+ | scaleCheckboxes.forEach(checkbox => { | ||
+ | checkbox.checked = allScalesSelected; | ||
+ | }); | ||
+ | | ||
+ | document.getElementById(" | ||
+ | updatePracticeScales(); | ||
+ | } | ||
+ | |||
+ | // Set the difficulty mode | ||
+ | function setMode(mode) { | ||
+ | currentMode = mode; | ||
+ | | ||
+ | // Update UI | ||
+ | document.querySelectorAll(" | ||
+ | btn.classList.remove(" | ||
+ | if (btn.dataset.mode === mode) { | ||
+ | btn.classList.add(" | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | // Update practice scales display | ||
+ | function updatePracticeScales() { | ||
+ | const container = document.getElementById(" | ||
+ | container.innerHTML = ""; | ||
+ | | ||
+ | const selectedScales = getSelectedScales(); | ||
+ | | ||
+ | if (selectedScales.length === 0) { | ||
+ | container.innerHTML = "<p style=' | ||
+ | return; | ||
+ | } | ||
+ | | ||
+ | selectedScales.forEach(scale => { | ||
+ | const button = document.createElement(" | ||
+ | button.className = " | ||
+ | button.textContent = getScaleName(scale); | ||
+ | button.dataset.scale = scale; | ||
+ | | ||
+ | button.addEventListener(" | ||
+ | playPracticeScale(scale, | ||
+ | }); | ||
+ | | ||
+ | container.appendChild(button); | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | // Play a practice scale | ||
+ | function playPracticeScale(scale, | ||
+ | // Reset all button states | ||
+ | resetPracticeScaleButtons(); | ||
+ | | ||
+ | // Set current button as playing | ||
+ | buttonElement.classList.add(" | ||
+ | currentlyPlayingScale = scale; | ||
+ | | ||
+ | const startNote = getScaleStartNote(scale); | ||
+ | | ||
+ | playScale(scale, | ||
+ | // Remove playing state after scale finishes | ||
+ | if (currentlyPlayingScale === scale) { | ||
+ | buttonElement.classList.remove(" | ||
+ | currentlyPlayingScale = null; | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | // Check if at least one scale is selected | ||
+ | function isAnyScaleSelected() { | ||
+ | const scaleCheckboxes = document.querySelectorAll(" | ||
+ | return Array.from(scaleCheckboxes).some(checkbox => checkbox.checked); | ||
+ | } | ||
+ | |||
+ | // Get selected scales | ||
+ | function getSelectedScales() { | ||
+ | const scales = []; | ||
+ | | ||
+ | if (document.getElementById(" | ||
+ | if (document.getElementById(" | ||
+ | if (document.getElementById(" | ||
+ | if (document.getElementById(" | ||
+ | if (document.getElementById(" | ||
+ | if (document.getElementById(" | ||
+ | if (document.getElementById(" | ||
+ | if (document.getElementById(" | ||
+ | if (document.getElementById(" | ||
+ | | ||
+ | return scales; | ||
+ | } | ||
+ | |||
+ | // Initialize the audio context (must be triggered by user interaction) | ||
+ | function initAudioContext() { | ||
+ | if (!audioContext) { | ||
+ | audioContext = new (window.AudioContext || window.webkitAudioContext)(); | ||
+ | } | ||
+ | return audioContext; | ||
+ | } | ||
+ | |||
+ | // Scale definitions (intervals in semitones) | ||
+ | const scaleDefinitions = { | ||
+ | " | ||
+ | ascending: [0, 2, 4, 5, 7, 9, 11, 12], | ||
+ | descending: [12, 11, 9, 7, 5, 4, 2, 0] | ||
+ | }, | ||
+ | " | ||
+ | ascending: [0, 2, 3, 5, 7, 8, 10, 12], | ||
+ | descending: [12, 10, 8, 7, 5, 3, 2, 0] | ||
+ | }, | ||
+ | " | ||
+ | ascending: [0, 2, 3, 5, 7, 8, 11, 12], | ||
+ | descending: [12, 11, 8, 7, 5, 3, 2, 0] | ||
+ | }, | ||
+ | " | ||
+ | ascending: [0, 2, 3, 5, 7, 9, 11, 12], | ||
+ | // Melodic minor descending is the same as natural minor (aeolian) | ||
+ | descending: [12, 10, 8, 7, 5, 3, 2, 0] | ||
+ | }, | ||
+ | " | ||
+ | ascending: [0, 2, 3, 5, 7, 9, 10, 12], | ||
+ | descending: [12, 10, 9, 7, 5, 3, 2, 0] | ||
+ | }, | ||
+ | " | ||
+ | ascending: [0, 2, 4, 6, 7, 9, 11, 12], | ||
+ | descending: [12, 11, 9, 7, 6, 4, 2, 0] | ||
+ | }, | ||
+ | " | ||
+ | ascending: [0, 2, 4, 5, 7, 9, 10, 12], | ||
+ | descending: [12, 10, 9, 7, 5, 4, 2, 0] | ||
+ | }, | ||
+ | " | ||
+ | ascending: [0, 1, 3, 5, 7, 8, 10, 12], | ||
+ | descending: [12, 10, 8, 7, 5, 3, 1, 0] | ||
+ | }, | ||
+ | " | ||
+ | ascending: [0, 1, 3, 5, 6, 8, 10, 12], | ||
+ | descending: [12, 10, 8, 6, 5, 3, 1, 0] | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | // Scale starting notes for easy mode | ||
+ | const easyModeStartNotes = { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }; | ||
+ | |||
+ | // Keys for intermediate mode (0-3 sharps/ | ||
+ | const intermediateModeKeys = [ | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: " | ||
+ | { name: " | ||
+ | ]; | ||
+ | |||
+ | // Convert note name to MIDI number | ||
+ | function noteToMidi(note, | ||
+ | const noteValues = { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }; | ||
+ | | ||
+ | return 12 + (octave * 12) + noteValues[note]; | ||
+ | } | ||
+ | |||
+ | // Get random chromatic note between C3 and C5 | ||
+ | function getRandomChromatic() { | ||
+ | // C3 is MIDI 48, C5 is MIDI 72 | ||
+ | return Math.floor(Math.random() * 25) + 48; | ||
+ | } | ||
+ | |||
+ | // Get scale start note based on selected mode | ||
+ | function getScaleStartNote(scale) { | ||
+ | if (currentMode === " | ||
+ | return easyModeStartNotes[scale]; | ||
+ | } else if (currentMode === " | ||
+ | // Choose random key from intermediate keys | ||
+ | const randomKey = intermediateModeKeys[Math.floor(Math.random() * intermediateModeKeys.length)]; | ||
+ | // Adjust to ensure it's between C3 and C5 | ||
+ | let midiNote = randomKey.midiBase; | ||
+ | while (midiNote < 48) midiNote += 12; | ||
+ | while (midiNote > 72) midiNote -= 12; | ||
+ | return midiNote; | ||
+ | } else { // advanced | ||
+ | return getRandomChromatic(); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Play a note with Web Audio API | ||
+ | function playNote(midiNote, | ||
+ | const oscillator = audioContext.createOscillator(); | ||
+ | const gainNode = audioContext.createGain(); | ||
+ | | ||
+ | oscillator.type = ' | ||
+ | oscillator.frequency.value = 440 * Math.pow(2, (midiNote - 69) / 12); | ||
+ | | ||
+ | // Apply a simple envelope | ||
+ | gainNode.gain.setValueAtTime(0, | ||
+ | gainNode.gain.linearRampToValueAtTime(0.5, | ||
+ | gainNode.gain.linearRampToValueAtTime(0.3, | ||
+ | gainNode.gain.linearRampToValueAtTime(0, | ||
+ | | ||
+ | oscillator.connect(gainNode); | ||
+ | gainNode.connect(audioContext.destination); | ||
+ | | ||
+ | oscillator.start(time); | ||
+ | oscillator.stop(time + duration); | ||
+ | } | ||
+ | |||
+ | // Play a scale | ||
+ | function playScale(scale, | ||
+ | initAudioContext(); | ||
+ | | ||
+ | const scaleData = scaleDefinitions[scale]; | ||
+ | const noteDuration = 0.4; | ||
+ | let time = audioContext.currentTime; | ||
+ | | ||
+ | // Play ascending | ||
+ | for (let i = 0; i < scaleData.ascending.length; | ||
+ | playNote(startNoteMidi + scaleData.ascending[i], | ||
+ | time += noteDuration; | ||
+ | } | ||
+ | | ||
+ | // Play descending | ||
+ | for (let i = 0; i < scaleData.descending.length; | ||
+ | if (i > 0) { // Skip first note as it's the same as last ascending note | ||
+ | playNote(startNoteMidi + scaleData.descending[i], | ||
+ | time += noteDuration; | ||
+ | } | ||
+ | } | ||
+ | | ||
+ | // Execute callback after scale has played | ||
+ | if (callback) { | ||
+ | setTimeout(callback, | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Initialize quiz | ||
+ | function startQuiz() { | ||
+ | if (!isAnyScaleSelected()) { | ||
+ | alert(" | ||
+ | return; | ||
+ | } | ||
+ | | ||
+ | // Set up quiz environment | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | | ||
+ | // Reset quiz variables | ||
+ | quizInProgress = true; | ||
+ | currentQuizQuestion = 0; | ||
+ | correctAnswers = 0; | ||
+ | | ||
+ | // Create quiz questions | ||
+ | const selectedScales = getSelectedScales(); | ||
+ | quizScales = []; | ||
+ | | ||
+ | // Generate 10 random questions from selected scales | ||
+ | for (let i = 0; i < totalQuizQuestions; | ||
+ | const randomScaleIndex = Math.floor(Math.random() * selectedScales.length); | ||
+ | const scale = selectedScales[randomScaleIndex]; | ||
+ | quizScales.push(scale); | ||
+ | } | ||
+ | | ||
+ | // Reset progress markers | ||
+ | resetProgressMarkers(); | ||
+ | | ||
+ | // Start first question | ||
+ | nextQuestion(); | ||
+ | } | ||
+ | |||
+ | // Play the current quiz scale | ||
+ | function playCurrentQuizScale() { | ||
+ | if (!quizInProgress || currentQuizQuestion >= quizScales.length) return; | ||
+ | | ||
+ | currentScale = quizScales[currentQuizQuestion]; | ||
+ | const startNote = getScaleStartNote(currentScale); | ||
+ | | ||
+ | playScale(currentScale, | ||
+ | | ||
+ | // Enable play again button | ||
+ | document.getElementById(" | ||
+ | } | ||
+ | |||
+ | // Show quiz options | ||
+ | function showQuizOptions() { | ||
+ | const optionsContainer = document.getElementById(" | ||
+ | optionsContainer.innerHTML = ""; | ||
+ | | ||
+ | // Get selected scales for options | ||
+ | const scales = getSelectedScales(); | ||
+ | | ||
+ | // Shuffle the options | ||
+ | const shuffledScales = [...scales].sort(() => Math.random() - 0.5); | ||
+ | | ||
+ | // Create option buttons | ||
+ | shuffledScales.forEach(scale => { | ||
+ | const optionElement = document.createElement(" | ||
+ | optionElement.className = " | ||
+ | optionElement.dataset.scale = scale; | ||
+ | optionElement.textContent = getScaleName(scale); | ||
+ | | ||
+ | optionElement.addEventListener(" | ||
+ | if (questionAnswered) return; | ||
+ | | ||
+ | questionAnswered = true; | ||
+ | checkAnswer(scale); | ||
+ | }); | ||
+ | | ||
+ | optionsContainer.appendChild(optionElement); | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | // Get human-readable scale name | ||
+ | function getScaleName(scale) { | ||
+ | const scaleNames = { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }; | ||
+ | | ||
+ | return scaleNames[scale] || scale; | ||
+ | } | ||
+ | |||
+ | // Check the selected answer | ||
+ | function checkAnswer(selectedScale) { | ||
+ | const isCorrect = selectedScale === currentScale; | ||
+ | | ||
+ | // Update progress markers | ||
+ | updateProgressMarker(currentQuizQuestion, | ||
+ | | ||
+ | // Update score | ||
+ | if (isCorrect) { | ||
+ | correctAnswers++; | ||
+ | } | ||
+ | | ||
+ | // Show visual feedback | ||
+ | const options = document.querySelectorAll(" | ||
+ | options.forEach(option => { | ||
+ | if (option.dataset.scale === selectedScale) { | ||
+ | option.classList.add(isCorrect ? " | ||
+ | } | ||
+ | }); | ||
+ | | ||
+ | // Show control buttons | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | } | ||
+ | |||
+ | // Show the correct answer | ||
+ | function showCorrectAnswer() { | ||
+ | const options = document.querySelectorAll(" | ||
+ | options.forEach(option => { | ||
+ | if (option.dataset.scale === currentScale) { | ||
+ | option.classList.add(" | ||
+ | } | ||
+ | }); | ||
+ | | ||
+ | // Hide show answer button | ||
+ | document.getElementById(" | ||
+ | } | ||
+ | |||
+ | // Move to the next question | ||
+ | function nextQuestion() { | ||
+ | // Reset question state | ||
+ | questionAnswered = false; | ||
+ | | ||
+ | // Increment question counter | ||
+ | currentQuizQuestion++; | ||
+ | | ||
+ | // Update progress | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | | ||
+ | // Reset controls | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | | ||
+ | // Check if quiz is complete | ||
+ | if (currentQuizQuestion >= totalQuizQuestions) { | ||
+ | finishQuiz(); | ||
+ | return; | ||
+ | } | ||
+ | | ||
+ | // Show options for the next question | ||
+ | showQuizOptions(); | ||
+ | | ||
+ | // Play the scale automatically | ||
+ | playCurrentQuizScale(); | ||
+ | } | ||
+ | |||
+ | // Finish the quiz and show results | ||
+ | function finishQuiz() { | ||
+ | quizInProgress = false; | ||
+ | | ||
+ | // Hide quiz controls | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | | ||
+ | // Show results | ||
+ | const resultsElement = document.getElementById(" | ||
+ | const percentage = Math.round((correctAnswers / totalQuizQuestions) * 100); | ||
+ | resultsElement.textContent = `Quiz beendet! Ergebnis: ${correctAnswers} von ${totalQuizQuestions} richtig (${percentage}%)`; | ||
+ | | ||
+ | // Show restart button | ||
+ | const restartBtn = document.createElement(" | ||
+ | restartBtn.textContent = "Neues Quiz starten"; | ||
+ | restartBtn.addEventListener(" | ||
+ | // Reset UI | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | document.getElementById(" | ||
+ | | ||
+ | // Remove restart button | ||
+ | this.remove(); | ||
+ | }); | ||
+ | | ||
+ | resultsElement.appendChild(document.createElement(" | ||
+ | resultsElement.appendChild(restartBtn); | ||
+ | } | ||
+ | |||
+ | // Initialize progress markers | ||
+ | function initializeProgressMarkers() { | ||
+ | const container = document.getElementById(" | ||
+ | container.innerHTML = ""; | ||
+ | | ||
+ | for (let i = 0; i < totalQuizQuestions; | ||
+ | const marker = document.createElement(" | ||
+ | marker.className = " | ||
+ | marker.textContent = i + 1; | ||
+ | container.appendChild(marker); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // Reset progress markers | ||
+ | function resetProgressMarkers() { | ||
+ | const markers = document.querySelectorAll(" | ||
+ | markers.forEach(marker => { | ||
+ | marker.className = " | ||
+ | }); | ||
+ | } | ||
+ | |||
+ | // Update progress marker | ||
+ | function updateProgressMarker(index, | ||
+ | const markers = document.querySelectorAll(" | ||
+ | if (markers[index]) { | ||
+ | markers[index].classList.add(" | ||
+ | markers[index].classList.add(isCorrect ? " | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | </ | ||
+ | </ |