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 ? " | ||
| + | } | ||
| + | } | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
