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] – ↷ Seitename wurde von tools:skalentrainer-pruefung auf tools:skalentrainer geändert Eric Weber | tools:skalentrainer [05/04/2026 15:55] (aktuell) – Eric Weber | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| - | ====== Skalentrainer ====== | ||
| - | |||
| - | |||
| < | < | ||
| < | < | ||
| Zeile 8: | Zeile 5: | ||
| < | < | ||
| < | < | ||
| - | | + | |
| font-family: | font-family: | ||
| line-height: | line-height: | ||
| Zeile 16: | Zeile 13: | ||
| padding: 20px; | padding: 20px; | ||
| background-color: | background-color: | ||
| + | box-sizing: border-box; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app * { | ||
| + | box-sizing: border-box; | ||
| + | } | ||
| + | |||
| + | .skalen-trainer-app | ||
| + | .skalen-trainer-app | ||
| color: #2c3e50; | color: #2c3e50; | ||
| text-align: center; | text-align: center; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| border-radius: | border-radius: | ||
| Zeile 28: | Zeile 33: | ||
| margin-bottom: | margin-bottom: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| display: flex; | display: flex; | ||
| flex-wrap: wrap; | flex-wrap: wrap; | ||
| Zeile 35: | Zeile 41: | ||
| margin-bottom: | margin-bottom: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| display: flex; | display: flex; | ||
| flex-wrap: wrap; | flex-wrap: wrap; | ||
| Zeile 41: | Zeile 48: | ||
| margin-bottom: | margin-bottom: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| display: flex; | display: flex; | ||
| align-items: | align-items: | ||
| Zeile 50: | Zeile 58: | ||
| transition: background-color 0.2s; | transition: background-color 0.2s; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| margin-right: | margin-right: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| margin-right: | margin-right: | ||
| font-weight: | font-weight: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| color: black; | color: black; | ||
| Zeile 71: | Zeile 83: | ||
| font-weight: | font-weight: | ||
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); | box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); | ||
| + | font-family: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| transform: translateY(-2px); | transform: translateY(-2px); | ||
| box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| transform: translateY(0); | transform: translateY(0); | ||
| box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| border-color: | border-color: | ||
| Zeile 87: | Zeile 103: | ||
| box-shadow: none; | box-shadow: none; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| margin-left: | margin-left: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| display: flex; | display: flex; | ||
| justify-content: | justify-content: | ||
| Zeile 96: | Zeile 114: | ||
| margin-bottom: | margin-bottom: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| padding: 10px 20px; | padding: 10px 20px; | ||
| background-color: | background-color: | ||
| Zeile 103: | Zeile 122: | ||
| font-weight: | font-weight: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| color: black; | color: black; | ||
| border-color: | border-color: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| + | .skalen-trainer-app | ||
| margin-top: 30px; | margin-top: 30px; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| display: none; | display: none; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app .practice-scales-container { | ||
| + | display: grid; | ||
| + | grid-template-columns: | ||
| + | gap: 15px; | ||
| + | margin: 20px 0; | ||
| + | } | ||
| + | |||
| + | .skalen-trainer-app .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; | ||
| + | } | ||
| + | |||
| + | .skalen-trainer-app .practice-scale-btn: | ||
| + | background-color: | ||
| + | transform: translateY(-2px); | ||
| + | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); | ||
| + | } | ||
| + | |||
| + | .skalen-trainer-app .practice-scale-btn: | ||
| + | transform: translateY(0); | ||
| + | box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); | ||
| + | } | ||
| + | |||
| + | .skalen-trainer-app .practice-scale-btn.playing { | ||
| + | background-color: | ||
| + | color: black; | ||
| + | border-color: | ||
| + | } | ||
| + | |||
| + | .skalen-trainer-app | ||
| display: grid; | display: grid; | ||
| grid-template-columns: | grid-template-columns: | ||
| Zeile 120: | Zeile 181: | ||
| margin: 20px 0; | margin: 20px 0; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| padding: 12px; | padding: 12px; | ||
| Zeile 127: | Zeile 189: | ||
| cursor: pointer; | cursor: pointer; | ||
| transition: all 0.2s; | transition: all 0.2s; | ||
| + | font-family: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| - | color: | + | color: |
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| - | color: | + | color: |
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| margin-top: 30px; | margin-top: 30px; | ||
| text-align: center; | text-align: center; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| height: 10px; | height: 10px; | ||
| background-color: | background-color: | ||
| Zeile 150: | Zeile 218: | ||
| overflow: hidden; | overflow: hidden; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| height: 100%; | height: 100%; | ||
| background-color: | background-color: | ||
| Zeile 156: | Zeile 225: | ||
| transition: width 0.3s; | transition: width 0.3s; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| display: flex; | display: flex; | ||
| justify-content: | justify-content: | ||
| margin-top: 5px; | margin-top: 5px; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| width: 20px; | width: 20px; | ||
| height: 20px; | height: 20px; | ||
| Zeile 171: | Zeile 242: | ||
| font-size: 12px; | font-size: 12px; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| - | color: | + | color: |
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| - | color: | + | color: |
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| background-color: | background-color: | ||
| - | color: | + | color: |
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| display: none; | display: none; | ||
| margin-top: 15px; | margin-top: 15px; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| margin: 0 10px; | margin: 0 10px; | ||
| color: #7f8c8d; | color: #7f8c8d; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| display: flex; | display: flex; | ||
| gap: 10px; | gap: 10px; | ||
| margin: 20px 0; | margin: 20px 0; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| + | .skalen-trainer-app | ||
| + | .skalen-trainer-app | ||
| display: none; | display: none; | ||
| } | } | ||
| - | | + | |
| + | .skalen-trainer-app | ||
| font-weight: | font-weight: | ||
| font-size: 18px; | font-size: 18px; | ||
| margin-top: 20px; | margin-top: 20px; | ||
| text-align: center; | text-align: center; | ||
| + | } | ||
| + | |||
| + | @media (max-width: 768px) { | ||
| + | .skalen-trainer-app .mode-selector { | ||
| + | flex-direction: | ||
| + | align-items: | ||
| + | } | ||
| } | } | ||
| </ | </ | ||
| </ | </ | ||
| < | < | ||
| - | + | | |
| - | <div class=" | + | < |
| - | < | + | |
| - | + | | |
| - | < | + | < |
| - | | + | |
| - | <button class=" | + | <div class=" |
| - | <button class=" | + | <button class=" |
| - | <button class=" | + | <button class=" |
| - | </ | + | <button class=" |
| - | + | ||
| - | <!-- Scales selection --> | + | |
| - | <div class=" | + | |
| - | < | + | |
| - | <button id=" | + | |
| - | </ | + | |
| - | + | ||
| - | <div class=" | + | |
| - | <div class=" | + | |
| - | <input type=" | + | |
| - | <label for=" | + | |
| </ | </ | ||
| - | | + | |
| - | <input type=" | + | |
| - | <label for="scale-aeolian">Moll natürlich (Aeolisch)</label> | + | <label> |
| + | <button id="toggleAllBtn" | ||
| </ | </ | ||
| - | | + | |
| - | <input type=" | + | <div class=" |
| - | <label for=" | + | |
| + | <input type=" | ||
| + | <label for=" | ||
| + | | ||
| + | <div class=" | ||
| + | <input type=" | ||
| + | <label for=" | ||
| + | </ | ||
| + | <div class=" | ||
| + | | ||
| + | <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> | ||
| </ | </ | ||
| - | | + | |
| - | <input type="checkbox" | + | |
| - | <label for="scale-melodic-minor">Moll melodisch</ | + | |
| + | <h2> | ||
| + | < | ||
| + | |||
| + | <div class="practice-scales-container" | ||
| </ | </ | ||
| - | | + | |
| - | <input type="checkbox" | + | |
| - | <label for="scale-dorian">Dorisch</label> | + | <button id="startQuizBtn">Prüfung starten</button> |
| </ | </ | ||
| - | | + | |
| - | <input type="checkbox" id="scale-lydian"> | + | |
| - | <label for="scale-lydian"> | + | <div id=" |
| + | <h2> | ||
| + | <p id="quizInstruction">Höre die Skala und wähle die richtige Antwort:</ | ||
| + | |||
| + | < | ||
| + | |||
| + | <div class="quiz-options" | ||
| </ | </ | ||
| - | | + | |
| - | <input type="checkbox" id="scale-mixolydian"> | + | |
| - | <label for="scale-mixolydian">Mixolydisch</label> | + | <button id="playAgainBtn">Noch einmal abspielen</ |
| + | < | ||
| + | <button id="nextQuestionBtn">Nächste Frage</button> | ||
| </ | </ | ||
| - | | + | |
| - | <input type="checkbox" | + | |
| - | <label for="scale-phrygian">Phrygisch</label> | + | <div id="progressText">Frage 0 von 10</ |
| - | </ | + | <div class="progress-bar"> |
| - | <div class=" | + | <div class="progress-fill" id=" |
| - | <input type=" | + | </ |
| - | <label for="scale-locrian">Lokrisch</label> | + | <div class=" |
| + | </div> | ||
| + | <div id="results"></ | ||
| </ | </ | ||
| </ | </ | ||
| - | </ | ||
| - | | + | |
| - | < | + | |
| - | <div class=" | + | |
| - | <button id="startPracticeBtn"> | + | |
| - | <span class=" | + | |
| - | <button id="startQuizBtn"> | + | |
| - | </ | + | |
| - | </ | + | |
| - | + | ||
| - | <div id="quizSection" | + | |
| - | < | + | |
| - | <p id=" | + | |
| - | + | ||
| - | <button id=" | + | |
| - | + | ||
| - | <div class=" | + | |
| - | <!-- Options will be generated here --> | + | |
| - | | + | |
| - | + | ||
| - | <div class=" | + | |
| - | <button id="playAgainBtn"> | + | |
| - | <button id=" | + | |
| - | <button id="nextQuestionBtn"> | + | |
| - | </ | + | |
| - | + | ||
| - | <div class="progress-container"> | + | |
| - | <div id="progressText"> | + | |
| - | <div class=" | + | |
| - | <div class=" | + | |
| - | </ | + | |
| - | <div class=" | + | |
| - | <!-- Markers will be generated here --> | + | |
| - | </ | + | |
| - | <div id=" | + | |
| </ | </ | ||
| </ | </ | ||
| < | < | ||
| - | // Audio context and main variables | ||
| let audioContext; | let audioContext; | ||
| let currentMode = " | let currentMode = " | ||
| - | let allScalesSelected = true; | + | let allScalesSelected = false; |
| let quizInProgress = false; | let quizInProgress = false; | ||
| let currentQuizQuestion = 0; | let currentQuizQuestion = 0; | ||
| Zeile 314: | Zeile 408: | ||
| let currentScale; | let currentScale; | ||
| let questionAnswered = false; | let questionAnswered = false; | ||
| + | let currentlyPlayingScale = null; | ||
| - | // Function to stop all audio | ||
| function stopAllAudio() { | function stopAllAudio() { | ||
| - | // If there' | ||
| if (quizInProgress) { | if (quizInProgress) { | ||
| resetQuiz(); | resetQuiz(); | ||
| } | } | ||
| - | | + | |
| - | // If audio context exists, cancel all scheduled sounds | + | |
| if (audioContext) { | if (audioContext) { | ||
| - | // Create a new audio context to stop all sounds | ||
| audioContext.close().then(() => { | audioContext.close().then(() => { | ||
| audioContext = null; | audioContext = null; | ||
| Zeile 330: | Zeile 421: | ||
| } | } | ||
| } | } | ||
| - | + | ||
| - | // Reset quiz state | + | |
| function resetQuiz() { | function resetQuiz() { | ||
| if (quizInProgress) { | if (quizInProgress) { | ||
| Zeile 347: | Zeile 437: | ||
| } | } | ||
| - | | + | |
| + | document.querySelectorAll(" | ||
| + | btn.classList.remove(" | ||
| + | }); | ||
| + | currentlyPlayingScale = null; | ||
| + | } | ||
| document.addEventListener(" | document.addEventListener(" | ||
| - | // Set up event listeners | ||
| document.getElementById(" | document.getElementById(" | ||
| - | document.getElementById(" | ||
| document.getElementById(" | document.getElementById(" | ||
| document.getElementById(" | document.getElementById(" | ||
| Zeile 358: | Zeile 452: | ||
| document.getElementById(" | document.getElementById(" | ||
| - | // Mode buttons | ||
| document.querySelectorAll(" | document.querySelectorAll(" | ||
| btn.addEventListener(" | btn.addEventListener(" | ||
| setMode(this.dataset.mode); | setMode(this.dataset.mode); | ||
| stopAllAudio(); | stopAllAudio(); | ||
| + | updatePracticeScales(); | ||
| }); | }); | ||
| }); | }); | ||
| - | // Add event listeners to stop audio when settings change | ||
| document.querySelectorAll(" | document.querySelectorAll(" | ||
| - | checkbox.addEventListener(" | + | checkbox.addEventListener(" |
| + | | ||
| + | updatePracticeScales(); | ||
| + | }); | ||
| }); | }); | ||
| - | // Initialize progress markers | ||
| initializeProgressMarkers(); | initializeProgressMarkers(); | ||
| + | updatePracticeScales(); | ||
| }); | }); | ||
| - | // Toggle all scales selection | ||
| function toggleAllScales() { | function toggleAllScales() { | ||
| const scaleCheckboxes = document.querySelectorAll(" | const scaleCheckboxes = document.querySelectorAll(" | ||
| allScalesSelected = !allScalesSelected; | allScalesSelected = !allScalesSelected; | ||
| - | | ||
| scaleCheckboxes.forEach(checkbox => { | scaleCheckboxes.forEach(checkbox => { | ||
| checkbox.checked = allScalesSelected; | checkbox.checked = allScalesSelected; | ||
| }); | }); | ||
| - | | ||
| document.getElementById(" | document.getElementById(" | ||
| + | updatePracticeScales(); | ||
| } | } | ||
| - | // Set the difficulty mode | ||
| function setMode(mode) { | function setMode(mode) { | ||
| currentMode = mode; | currentMode = mode; | ||
| - | | ||
| - | // Update UI | ||
| document.querySelectorAll(" | document.querySelectorAll(" | ||
| btn.classList.remove(" | btn.classList.remove(" | ||
| Zeile 398: | Zeile 489: | ||
| } | } | ||
| }); | }); | ||
| - | | + | } |
| + | 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); | ||
| + | }); | ||
| + | } | ||
| + | |||
| + | function playPracticeScale(scale, | ||
| + | resetPracticeScaleButtons(); | ||
| + | buttonElement.classList.add(" | ||
| + | currentlyPlayingScale = scale; | ||
| + | const startNote = getScaleStartNote(scale); | ||
| + | playScale(scale, | ||
| + | if (currentlyPlayingScale === scale) { | ||
| + | buttonElement.classList.remove(" | ||
| + | currentlyPlayingScale = null; | ||
| + | } | ||
| + | }); | ||
| } | } | ||
| - | // Check if at least one scale is selected | ||
| function isAnyScaleSelected() { | function isAnyScaleSelected() { | ||
| const scaleCheckboxes = document.querySelectorAll(" | const scaleCheckboxes = document.querySelectorAll(" | ||
| Zeile 408: | Zeile 529: | ||
| } | } | ||
| - | // Get selected scales | ||
| function getSelectedScales() { | function getSelectedScales() { | ||
| const scales = []; | const scales = []; | ||
| - | | ||
| if (document.getElementById(" | if (document.getElementById(" | ||
| if (document.getElementById(" | if (document.getElementById(" | ||
| Zeile 421: | Zeile 540: | ||
| if (document.getElementById(" | if (document.getElementById(" | ||
| if (document.getElementById(" | if (document.getElementById(" | ||
| - | | ||
| return scales; | return scales; | ||
| } | } | ||
| - | // Initialize the audio context (must be triggered by user interaction) | ||
| function initAudioContext() { | function initAudioContext() { | ||
| if (!audioContext) { | if (!audioContext) { | ||
| Zeile 433: | Zeile 550: | ||
| } | } | ||
| - | // Scale definitions (intervals in semitones) | ||
| const scaleDefinitions = { | const scaleDefinitions = { | ||
| " | " | ||
| Zeile 449: | Zeile 565: | ||
| " | " | ||
| ascending: [0, 2, 3, 5, 7, 9, 11, 12], | 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] | descending: [12, 10, 8, 7, 5, 3, 2, 0] | ||
| }, | }, | ||
| Zeile 474: | Zeile 589: | ||
| }; | }; | ||
| - | // Scale starting notes for easy mode | ||
| const easyModeStartNotes = { | const easyModeStartNotes = { | ||
| - | " | + | " |
| - | " | + | " |
| - | " | + | " |
| - | " | + | " |
| - | " | + | " |
| - | " | + | " |
| - | " | + | " |
| - | " | + | " |
| - | " | + | " |
| }; | }; | ||
| - | // Keys for intermediate mode (0-3 sharps/ | ||
| const intermediateModeKeys = [ | const intermediateModeKeys = [ | ||
| - | { name: " | + | { name: " |
| - | { name: " | + | { name: " |
| - | { name: " | + | { name: " |
| - | { name: " | + | { 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() { | function getRandomChromatic() { | ||
| - | // C3 is MIDI 48, C5 is MIDI 72 | ||
| return Math.floor(Math.random() * 25) + 48; | return Math.floor(Math.random() * 25) + 48; | ||
| } | } | ||
| - | // Get scale start note based on selected mode | ||
| function getScaleStartNote(scale) { | function getScaleStartNote(scale) { | ||
| if (currentMode === " | if (currentMode === " | ||
| return easyModeStartNotes[scale]; | return easyModeStartNotes[scale]; | ||
| } else if (currentMode === " | } else if (currentMode === " | ||
| - | // Choose random key from intermediate keys | ||
| const randomKey = intermediateModeKeys[Math.floor(Math.random() * intermediateModeKeys.length)]; | const randomKey = intermediateModeKeys[Math.floor(Math.random() * intermediateModeKeys.length)]; | ||
| - | // Adjust to ensure it's between C3 and C5 | ||
| let midiNote = randomKey.midiBase; | let midiNote = randomKey.midiBase; | ||
| while (midiNote < 48) midiNote += 12; | while (midiNote < 48) midiNote += 12; | ||
| while (midiNote > 72) midiNote -= 12; | while (midiNote > 72) midiNote -= 12; | ||
| return midiNote; | return midiNote; | ||
| - | } else { // advanced | + | } else { |
| return getRandomChromatic(); | return getRandomChromatic(); | ||
| } | } | ||
| } | } | ||
| - | // Play a note with Web Audio API | ||
| function playNote(midiNote, | function playNote(midiNote, | ||
| const oscillator = audioContext.createOscillator(); | const oscillator = audioContext.createOscillator(); | ||
| const gainNode = audioContext.createGain(); | const gainNode = audioContext.createGain(); | ||
| - | | ||
| oscillator.type = ' | oscillator.type = ' | ||
| oscillator.frequency.value = 440 * Math.pow(2, (midiNote - 69) / 12); | oscillator.frequency.value = 440 * Math.pow(2, (midiNote - 69) / 12); | ||
| - | | ||
| - | // Apply a simple envelope | ||
| gainNode.gain.setValueAtTime(0, | gainNode.gain.setValueAtTime(0, | ||
| gainNode.gain.linearRampToValueAtTime(0.5, | gainNode.gain.linearRampToValueAtTime(0.5, | ||
| gainNode.gain.linearRampToValueAtTime(0.3, | gainNode.gain.linearRampToValueAtTime(0.3, | ||
| gainNode.gain.linearRampToValueAtTime(0, | gainNode.gain.linearRampToValueAtTime(0, | ||
| - | | ||
| oscillator.connect(gainNode); | oscillator.connect(gainNode); | ||
| gainNode.connect(audioContext.destination); | gainNode.connect(audioContext.destination); | ||
| - | | ||
| oscillator.start(time); | oscillator.start(time); | ||
| oscillator.stop(time + duration); | oscillator.stop(time + duration); | ||
| } | } | ||
| - | // Play a scale | ||
| function playScale(scale, | function playScale(scale, | ||
| initAudioContext(); | initAudioContext(); | ||
| - | | ||
| const scaleData = scaleDefinitions[scale]; | const scaleData = scaleDefinitions[scale]; | ||
| const noteDuration = 0.4; | const noteDuration = 0.4; | ||
| let time = audioContext.currentTime; | let time = audioContext.currentTime; | ||
| - | | ||
| - | // Play ascending | ||
| for (let i = 0; i < scaleData.ascending.length; | for (let i = 0; i < scaleData.ascending.length; | ||
| playNote(startNoteMidi + scaleData.ascending[i], | playNote(startNoteMidi + scaleData.ascending[i], | ||
| time += noteDuration; | time += noteDuration; | ||
| } | } | ||
| - | | ||
| - | // Play descending | ||
| for (let i = 0; i < scaleData.descending.length; | for (let i = 0; i < scaleData.descending.length; | ||
| - | if (i > 0) { // Skip first note as it's the same as last ascending note | + | if (i > 0) { |
| playNote(startNoteMidi + scaleData.descending[i], | playNote(startNoteMidi + scaleData.descending[i], | ||
| time += noteDuration; | time += noteDuration; | ||
| } | } | ||
| } | } | ||
| - | | ||
| - | // Execute callback after scale has played | ||
| if (callback) { | if (callback) { | ||
| setTimeout(callback, | setTimeout(callback, | ||
| Zeile 581: | Zeile 664: | ||
| } | } | ||
| - | // Practice all selected scales | ||
| - | function startPractice() { | ||
| - | if (!isAnyScaleSelected()) { | ||
| - | alert(" | ||
| - | return; | ||
| - | } | ||
| - | | ||
| - | const scales = getSelectedScales(); | ||
| - | initAudioContext(); | ||
| - | | ||
| - | let index = 0; | ||
| - | const playNextScale = () => { | ||
| - | if (index < scales.length) { | ||
| - | const scale = scales[index]; | ||
| - | const startNote = getScaleStartNote(scale); | ||
| - | playScale(scale, | ||
| - | index++; | ||
| - | setTimeout(playNextScale, | ||
| - | }); | ||
| - | } | ||
| - | }; | ||
| - | | ||
| - | playNextScale(); | ||
| - | } | ||
| - | |||
| - | // Initialize quiz | ||
| function startQuiz() { | function startQuiz() { | ||
| if (!isAnyScaleSelected()) { | if (!isAnyScaleSelected()) { | ||
| Zeile 612: | Zeile 669: | ||
| return; | return; | ||
| } | } | ||
| - | | ||
| - | // Set up quiz environment | ||
| document.getElementById(" | document.getElementById(" | ||
| document.getElementById(" | document.getElementById(" | ||
| - | | ||
| - | // Reset quiz variables | ||
| quizInProgress = true; | quizInProgress = true; | ||
| currentQuizQuestion = 0; | currentQuizQuestion = 0; | ||
| correctAnswers = 0; | correctAnswers = 0; | ||
| - | | ||
| - | // Create quiz questions | ||
| const selectedScales = getSelectedScales(); | const selectedScales = getSelectedScales(); | ||
| quizScales = []; | quizScales = []; | ||
| - | | ||
| - | // Generate 10 random questions from selected scales | ||
| for (let i = 0; i < totalQuizQuestions; | for (let i = 0; i < totalQuizQuestions; | ||
| const randomScaleIndex = Math.floor(Math.random() * selectedScales.length); | const randomScaleIndex = Math.floor(Math.random() * selectedScales.length); | ||
| - | | + | quizScales.push(selectedScales[randomScaleIndex]); |
| - | | + | |
| } | } | ||
| - | | ||
| - | // Reset progress markers | ||
| resetProgressMarkers(); | resetProgressMarkers(); | ||
| - | | ||
| - | // Start first question | ||
| nextQuestion(); | nextQuestion(); | ||
| } | } | ||
| - | // Play the current quiz scale | ||
| function playCurrentQuizScale() { | function playCurrentQuizScale() { | ||
| if (!quizInProgress || currentQuizQuestion >= quizScales.length) return; | if (!quizInProgress || currentQuizQuestion >= quizScales.length) return; | ||
| - | | ||
| currentScale = quizScales[currentQuizQuestion]; | currentScale = quizScales[currentQuizQuestion]; | ||
| const startNote = getScaleStartNote(currentScale); | const startNote = getScaleStartNote(currentScale); | ||
| - | | ||
| playScale(currentScale, | playScale(currentScale, | ||
| - | | ||
| - | // Enable play again button | ||
| document.getElementById(" | document.getElementById(" | ||
| } | } | ||
| - | // Show quiz options | ||
| function showQuizOptions() { | function showQuizOptions() { | ||
| const optionsContainer = document.getElementById(" | const optionsContainer = document.getElementById(" | ||
| optionsContainer.innerHTML = ""; | optionsContainer.innerHTML = ""; | ||
| - | | ||
| - | // Get selected scales for options | ||
| const scales = getSelectedScales(); | const scales = getSelectedScales(); | ||
| - | | ||
| - | // Shuffle the options | ||
| const shuffledScales = [...scales].sort(() => Math.random() - 0.5); | const shuffledScales = [...scales].sort(() => Math.random() - 0.5); | ||
| - | | ||
| - | // Create option buttons | ||
| shuffledScales.forEach(scale => { | shuffledScales.forEach(scale => { | ||
| const optionElement = document.createElement(" | const optionElement = document.createElement(" | ||
| Zeile 670: | Zeile 702: | ||
| optionElement.dataset.scale = scale; | optionElement.dataset.scale = scale; | ||
| optionElement.textContent = getScaleName(scale); | optionElement.textContent = getScaleName(scale); | ||
| - | | ||
| optionElement.addEventListener(" | optionElement.addEventListener(" | ||
| if (questionAnswered) return; | if (questionAnswered) return; | ||
| - | | ||
| questionAnswered = true; | questionAnswered = true; | ||
| checkAnswer(scale); | checkAnswer(scale); | ||
| }); | }); | ||
| - | | ||
| optionsContainer.appendChild(optionElement); | optionsContainer.appendChild(optionElement); | ||
| }); | }); | ||
| } | } | ||
| - | // Get human-readable scale name | ||
| function getScaleName(scale) { | function getScaleName(scale) { | ||
| const scaleNames = { | const scaleNames = { | ||
| Zeile 695: | Zeile 723: | ||
| " | " | ||
| }; | }; | ||
| - | | ||
| return scaleNames[scale] || scale; | return scaleNames[scale] || scale; | ||
| } | } | ||
| - | // Check the selected answer | ||
| function checkAnswer(selectedScale) { | function checkAnswer(selectedScale) { | ||
| const isCorrect = selectedScale === currentScale; | const isCorrect = selectedScale === currentScale; | ||
| - | | ||
| - | // Update progress markers | ||
| updateProgressMarker(currentQuizQuestion, | updateProgressMarker(currentQuizQuestion, | ||
| - | | + | if (isCorrect) correctAnswers++; |
| - | // Update score | + | |
| - | | + | |
| - | | + | |
| - | } | + | |
| - | + | ||
| - | // Show visual feedback | + | |
| const options = document.querySelectorAll(" | const options = document.querySelectorAll(" | ||
| options.forEach(option => { | options.forEach(option => { | ||
| Zeile 718: | Zeile 736: | ||
| } | } | ||
| }); | }); | ||
| - | | ||
| - | // Show control buttons | ||
| document.getElementById(" | document.getElementById(" | ||
| document.getElementById(" | document.getElementById(" | ||
| Zeile 725: | Zeile 741: | ||
| } | } | ||
| - | // Show the correct answer | ||
| function showCorrectAnswer() { | function showCorrectAnswer() { | ||
| const options = document.querySelectorAll(" | const options = document.querySelectorAll(" | ||
| Zeile 733: | Zeile 748: | ||
| } | } | ||
| }); | }); | ||
| - | | ||
| - | // Hide show answer button | ||
| document.getElementById(" | document.getElementById(" | ||
| } | } | ||
| - | // Move to the next question | ||
| function nextQuestion() { | function nextQuestion() { | ||
| - | // Reset question state | ||
| questionAnswered = false; | questionAnswered = false; | ||
| - | | ||
| - | // Increment question counter | ||
| currentQuizQuestion++; | currentQuizQuestion++; | ||
| - | | ||
| - | // Update progress | ||
| document.getElementById(" | document.getElementById(" | ||
| document.getElementById(" | document.getElementById(" | ||
| - | | ||
| - | // Reset controls | ||
| document.getElementById(" | document.getElementById(" | ||
| document.getElementById(" | document.getElementById(" | ||
| document.getElementById(" | document.getElementById(" | ||
| - | | ||
| - | // Check if quiz is complete | ||
| if (currentQuizQuestion >= totalQuizQuestions) { | if (currentQuizQuestion >= totalQuizQuestions) { | ||
| finishQuiz(); | finishQuiz(); | ||
| return; | return; | ||
| } | } | ||
| - | | ||
| - | // Show options for the next question | ||
| showQuizOptions(); | showQuizOptions(); | ||
| - | | ||
| - | // Play the scale automatically | ||
| playCurrentQuizScale(); | playCurrentQuizScale(); | ||
| } | } | ||
| - | // Finish the quiz and show results | ||
| function finishQuiz() { | function finishQuiz() { | ||
| quizInProgress = false; | quizInProgress = false; | ||
| - | | ||
| - | // Hide quiz controls | ||
| document.getElementById(" | document.getElementById(" | ||
| document.getElementById(" | document.getElementById(" | ||
| document.getElementById(" | document.getElementById(" | ||
| - | | ||
| - | // Show results | ||
| const resultsElement = document.getElementById(" | const resultsElement = document.getElementById(" | ||
| const percentage = Math.round((correctAnswers / totalQuizQuestions) * 100); | const percentage = Math.round((correctAnswers / totalQuizQuestions) * 100); | ||
| resultsElement.textContent = `Quiz beendet! Ergebnis: ${correctAnswers} von ${totalQuizQuestions} richtig (${percentage}%)`; | resultsElement.textContent = `Quiz beendet! Ergebnis: ${correctAnswers} von ${totalQuizQuestions} richtig (${percentage}%)`; | ||
| - | | ||
| - | // Show restart button | ||
| const restartBtn = document.createElement(" | const restartBtn = document.createElement(" | ||
| restartBtn.textContent = "Neues Quiz starten"; | restartBtn.textContent = "Neues Quiz starten"; | ||
| restartBtn.addEventListener(" | restartBtn.addEventListener(" | ||
| - | // Reset UI | ||
| document.getElementById(" | document.getElementById(" | ||
| document.getElementById(" | document.getElementById(" | ||
| Zeile 792: | Zeile 783: | ||
| document.getElementById(" | document.getElementById(" | ||
| document.getElementById(" | document.getElementById(" | ||
| - | | ||
| - | // Remove restart button | ||
| this.remove(); | this.remove(); | ||
| }); | }); | ||
| - | | ||
| resultsElement.appendChild(document.createElement(" | resultsElement.appendChild(document.createElement(" | ||
| resultsElement.appendChild(restartBtn); | resultsElement.appendChild(restartBtn); | ||
| } | } | ||
| - | // Initialize progress markers | ||
| function initializeProgressMarkers() { | function initializeProgressMarkers() { | ||
| const container = document.getElementById(" | const container = document.getElementById(" | ||
| container.innerHTML = ""; | container.innerHTML = ""; | ||
| - | | ||
| for (let i = 0; i < totalQuizQuestions; | for (let i = 0; i < totalQuizQuestions; | ||
| const marker = document.createElement(" | const marker = document.createElement(" | ||
| Zeile 814: | Zeile 800: | ||
| } | } | ||
| - | // Reset progress markers | ||
| function resetProgressMarkers() { | function resetProgressMarkers() { | ||
| const markers = document.querySelectorAll(" | const markers = document.querySelectorAll(" | ||
| Zeile 822: | Zeile 807: | ||
| } | } | ||
| - | // Update progress marker | ||
| function updateProgressMarker(index, | function updateProgressMarker(index, | ||
| const markers = document.querySelectorAll(" | const markers = document.querySelectorAll(" | ||
tools/skalentrainer.1747292374.txt.gz · Zuletzt geändert: von Eric Weber
