tools:tonhoehentrainer2
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.
tools:tonhoehentrainer2 [19/05/2025 18:04] – angelegt Eric Weber | tools:tonhoehentrainer2 [20/05/2025 08:42] (aktuell) – gelöscht Eric Weber | ||
---|---|---|---|
Zeile 1: | Zeile 1: | ||
- | < | ||
- | < | ||
- | <meta charset=" | ||
- | <meta name=" | ||
- | < | ||
- | < | ||
- | .ear-training-app { | ||
- | font-family: | ||
- | max-width: 800px; | ||
- | margin: 0 auto; | ||
- | padding: 20px; | ||
- | background-color: | ||
- | } | ||
- | .ear-training-app h1, .ear-training-app h2 { | ||
- | color: #333; | ||
- | text-align: center; | ||
- | } | ||
- | .ear-training-app .container { | ||
- | background-color: | ||
- | border-radius: | ||
- | box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); | ||
- | padding: 20px; | ||
- | margin-bottom: | ||
- | } | ||
- | .ear-training-app .settings { | ||
- | display: flex; | ||
- | flex-direction: | ||
- | gap: 15px; | ||
- | } | ||
- | .ear-training-app .difficulty-options { | ||
- | display: flex; | ||
- | gap: 10px; | ||
- | justify-content: | ||
- | margin: 10px 0; | ||
- | } | ||
- | .ear-training-app .difficulty-btn { | ||
- | padding: 10px 15px; | ||
- | border: none; | ||
- | border-radius: | ||
- | cursor: pointer; | ||
- | font-weight: | ||
- | transition: background-color 0.3s; | ||
- | background-color: | ||
- | } | ||
- | .ear-training-app .difficulty-btn.active { | ||
- | background-color: | ||
- | color: white; | ||
- | } | ||
- | .ear-training-app .start-btn { | ||
- | background-color: | ||
- | color: black; | ||
- | padding: 12px 20px; | ||
- | border: none; | ||
- | border-radius: | ||
- | cursor: pointer; | ||
- | font-size: 16px; | ||
- | font-weight: | ||
- | margin: 10px auto; | ||
- | display: block; | ||
- | } | ||
- | .ear-training-app .play-tones-btn { | ||
- | background-color: | ||
- | color: black; | ||
- | padding: 12px 20px; | ||
- | border: none; | ||
- | border-radius: | ||
- | cursor: pointer; | ||
- | font-size: 16px; | ||
- | font-weight: | ||
- | margin: 10px auto; | ||
- | display: block; | ||
- | } | ||
- | .ear-training-app .answer-btns { | ||
- | display: flex; | ||
- | justify-content: | ||
- | gap: 20px; | ||
- | margin: 20px 0; | ||
- | } | ||
- | .ear-training-app .answer-btn { | ||
- | padding: 15px 30px; | ||
- | border: none; | ||
- | border-radius: | ||
- | cursor: pointer; | ||
- | font-size: 16px; | ||
- | font-weight: | ||
- | transition: background-color 0.3s; | ||
- | } | ||
- | .ear-training-app .higher-btn { | ||
- | background-color: | ||
- | } | ||
- | .ear-training-app .lower-btn { | ||
- | background-color: | ||
- | } | ||
- | .ear-training-app .correct { | ||
- | background-color: | ||
- | color: white !important; | ||
- | } | ||
- | .ear-training-app .incorrect { | ||
- | background-color: | ||
- | color: white !important; | ||
- | } | ||
- | .ear-training-app .progress { | ||
- | display: flex; | ||
- | justify-content: | ||
- | margin-top: 20px; | ||
- | } | ||
- | .ear-training-app .progress-dots { | ||
- | display: flex; | ||
- | gap: 10px; | ||
- | margin-bottom: | ||
- | } | ||
- | .ear-training-app .dot { | ||
- | width: 20px; | ||
- | height: 20px; | ||
- | border-radius: | ||
- | background-color: | ||
- | } | ||
- | .ear-training-app .dot.correct { | ||
- | background-color: | ||
- | } | ||
- | .ear-training-app .dot.incorrect { | ||
- | background-color: | ||
- | } | ||
- | .ear-training-app .dot.current { | ||
- | border: 2px solid #2196F3; | ||
- | } | ||
- | .ear-training-app .stats { | ||
- | text-align: center; | ||
- | font-weight: | ||
- | margin-top: 10px; | ||
- | } | ||
- | .ear-training-app .hidden { | ||
- | display: none; | ||
- | } | ||
- | .ear-training-app .cancel-btn { | ||
- | background-color: | ||
- | color: black; | ||
- | padding: 10px 15px; | ||
- | border: none; | ||
- | border-radius: | ||
- | cursor: pointer; | ||
- | font-weight: | ||
- | margin-top: 10px; | ||
- | display: block; | ||
- | margin: 10px auto; | ||
- | } | ||
- | .ear-training-app .result-message { | ||
- | text-align: center; | ||
- | font-size: 18px; | ||
- | margin-top: 20px; | ||
- | font-weight: | ||
- | } | ||
- | .ear-training-app .audio-status { | ||
- | text-align: center; | ||
- | margin: 10px 0; | ||
- | padding: 10px; | ||
- | background-color: | ||
- | border-radius: | ||
- | } | ||
- | .ear-training-app .difficulty-description { | ||
- | font-size: 14px; | ||
- | text-align: center; | ||
- | margin-top: 5px; | ||
- | color: #666; | ||
- | } | ||
- | .ear-training-app .next-btn { | ||
- | background-color: | ||
- | color: black; | ||
- | padding: 10px 15px; | ||
- | border: none; | ||
- | border-radius: | ||
- | cursor: pointer; | ||
- | font-weight: | ||
- | margin: 10px 5px; | ||
- | display: inline-block; | ||
- | } | ||
- | .ear-training-app #retry-next { | ||
- | text-align: center; | ||
- | } | ||
- | </ | ||
- | </ | ||
- | < | ||
- | <div class=" | ||
- | < | ||
- | | ||
- | <div class=" | ||
- | < | ||
- | <div> | ||
- | < | ||
- | <div class=" | ||
- | <button class=" | ||
- | <button class=" | ||
- | <button class=" | ||
- | </ | ||
- | <div class=" | ||
- | Große Terz (maximal 4 Halbtöne Unterschied) | ||
- | </ | ||
- | </ | ||
- | <div id=" | ||
- | Bitte klicke hier um Audio zu aktivieren | ||
- | </ | ||
- | <button id=" | ||
- | </ | ||
- | <div id=" | ||
- | <button id=" | ||
- | | ||
- | <div class=" | ||
- | <button id=" | ||
- | <button id=" | ||
- | </ | ||
- | | ||
- | <div id=" | ||
- | <button id=" | ||
- | <button id=" | ||
- | </ | ||
- | | ||
- | <div class=" | ||
- | <div class=" | ||
- | </ | ||
- | <div class=" | ||
- | Frage 1 von 10 | Richtig: 0% | Falsch: 0% | ||
- | </ | ||
- | | ||
- | <button id=" | ||
- | </ | ||
- | | ||
- | <div id=" | ||
- | < | ||
- | <div class=" | ||
- | <button id=" | ||
- | </ | ||
- | </ | ||
- | |||
- | < | ||
- | // Warten, bis die Seite vollständig geladen ist | ||
- | window.addEventListener(' | ||
- | // DOM-Elemente | ||
- | const startTestBtn = document.getElementById(' | ||
- | const testArea = document.getElementById(' | ||
- | const playTonesBtn = document.getElementById(' | ||
- | const higherBtn = document.getElementById(' | ||
- | const lowerBtn = document.getElementById(' | ||
- | const progressDots = document.getElementById(' | ||
- | const statsDiv = document.getElementById(' | ||
- | const finalResult = document.getElementById(' | ||
- | const resultMessage = document.getElementById(' | ||
- | const restartBtn = document.getElementById(' | ||
- | const audioPermission = document.getElementById(' | ||
- | const cancelTestBtn = document.getElementById(' | ||
- | const difficultyDescription = document.getElementById(' | ||
- | const retryNextDiv = document.getElementById(' | ||
- | const retryBtn = document.getElementById(' | ||
- | const nextBtn = document.getElementById(' | ||
- | | ||
- | // Audio Context | ||
- | let audioContext = null; | ||
- | let audioInitialized = false; | ||
- | | ||
- | // Schwierigkeitsgrad-Buttons | ||
- | const difficultyBtns = document.querySelectorAll(' | ||
- | | ||
- | // Variablen für das Spiel | ||
- | let difficulty = ' | ||
- | let currentQuestion = 0; | ||
- | let correctAnswers = 0; | ||
- | let totalQuestions = 10; | ||
- | let questions = []; | ||
- | let currentFirstNote = null; | ||
- | let currentSecondNote = null; | ||
- | let isSecondNoteHigher = false; | ||
- | | ||
- | // Tonhöhen (C4 bis A5) | ||
- | const notes = [' | ||
- | ' | ||
- | | ||
- | // Frequenzen der Noten | ||
- | const noteFrequencies = { | ||
- | ' | ||
- | ' | ||
- | ' | ||
- | ' | ||
- | ' | ||
- | }; | ||
- | | ||
- | // Schwierigkeitsgradbeschreibungen | ||
- | const difficultyDescriptions = { | ||
- | ' | ||
- | ' | ||
- | ' | ||
- | }; | ||
- | | ||
- | // Audio initialisieren | ||
- | function initializeAudio() { | ||
- | if (!audioInitialized) { | ||
- | try { | ||
- | audioContext = new (window.AudioContext || window.webkitAudioContext)(); | ||
- | audioInitialized = true; | ||
- | audioPermission.textContent = "Audio aktiviert ✓"; | ||
- | audioPermission.style.backgroundColor = "# | ||
- | } catch (e) { | ||
- | console.error(" | ||
- | audioPermission.textContent = "Audio nicht verfügbar. Bitte verwende einen modernen Browser."; | ||
- | audioPermission.style.backgroundColor = "# | ||
- | } | ||
- | } | ||
- | } | ||
- | | ||
- | // Audio-Erlaubnis anfordern | ||
- | audioPermission.addEventListener(' | ||
- | | ||
- | // Ton abspielen mit Web Audio API | ||
- | function playNote(note, | ||
- | if (!audioContext) { | ||
- | alert(" | ||
- | return; | ||
- | } | ||
- | | ||
- | const frequency = noteFrequencies[note]; | ||
- | if (!frequency) return; | ||
- | | ||
- | // Zeit für den Start des Tons | ||
- | const startTime = audioContext.currentTime + delay; | ||
- | | ||
- | // Oszillator erstellen | ||
- | const oscillator = audioContext.createOscillator(); | ||
- | oscillator.type = ' | ||
- | oscillator.frequency.setValueAtTime(frequency, | ||
- | | ||
- | // Envelope für einen Piano-ähnlichen Klang | ||
- | const gainNode = audioContext.createGain(); | ||
- | gainNode.gain.setValueAtTime(0, | ||
- | gainNode.gain.linearRampToValueAtTime(0.7, | ||
- | gainNode.gain.linearRampToValueAtTime(0.6, | ||
- | gainNode.gain.exponentialRampToValueAtTime(0.001, | ||
- | | ||
- | // Verbindungen herstellen | ||
- | oscillator.connect(gainNode); | ||
- | gainNode.connect(audioContext.destination); | ||
- | | ||
- | // Ton starten und stoppen | ||
- | oscillator.start(startTime); | ||
- | oscillator.stop(startTime + 1.5); | ||
- | } | ||
- | | ||
- | // Zwei Töne hintereinander abspielen | ||
- | function playTones() { | ||
- | if (!currentFirstNote || !currentSecondNote) return; | ||
- | | ||
- | // Audio initialisieren, | ||
- | if (!audioInitialized) { | ||
- | initializeAudio(); | ||
- | } | ||
- | | ||
- | // Alle Schaltflächen deaktivieren während des Abspielens | ||
- | higherBtn.disabled = true; | ||
- | lowerBtn.disabled = true; | ||
- | | ||
- | // Ersten Ton abspielen | ||
- | playNote(currentFirstNote, | ||
- | | ||
- | // Zweiten Ton nach 1,5 Sekunden abspielen | ||
- | playNote(currentSecondNote, | ||
- | | ||
- | // Nach 3 Sekunden Schaltflächen wieder aktivieren | ||
- | setTimeout(() => { | ||
- | higherBtn.disabled = false; | ||
- | lowerBtn.disabled = false; | ||
- | }, 3000); | ||
- | } | ||
- | | ||
- | // Zufälligen Ton zwischen C4 und A5 generieren | ||
- | function getRandomNote() { | ||
- | const randomIndex = Math.floor(Math.random() * notes.length); | ||
- | return notes[randomIndex]; | ||
- | } | ||
- | | ||
- | // Zweiten Ton basierend auf dem Schwierigkeitsgrad generieren | ||
- | function generateSecondNote(firstNote) { | ||
- | const firstNoteIndex = notes.indexOf(firstNote); | ||
- | let maxDistance; | ||
- | | ||
- | // Maximale Entfernung basierend auf dem Schwierigkeitsgrad | ||
- | switch (difficulty) { | ||
- | case ' | ||
- | maxDistance = 4; | ||
- | break; | ||
- | case ' | ||
- | maxDistance = 7; | ||
- | break; | ||
- | case ' | ||
- | maxDistance = 12; | ||
- | break; | ||
- | default: | ||
- | maxDistance = 4; | ||
- | } | ||
- | | ||
- | // Zufällige Richtung (höher oder tiefer) | ||
- | const direction = Math.random() < 0.5 ? -1 : 1; | ||
- | | ||
- | // Zufällige Distanz innerhalb der maximalen Entfernung | ||
- | let distance = Math.floor(Math.random() * maxDistance) + 1; | ||
- | distance *= direction; | ||
- | | ||
- | // Sicherstellen, | ||
- | let newIndex = firstNoteIndex + distance; | ||
- | if (newIndex < 0) newIndex = 0; | ||
- | if (newIndex >= notes.length) newIndex = notes.length - 1; | ||
- | | ||
- | // Vermeiden, dass beide Töne gleich sind | ||
- | if (newIndex === firstNoteIndex) { | ||
- | newIndex += (direction > 0) ? 1 : -1; | ||
- | if (newIndex < 0) newIndex = 1; | ||
- | if (newIndex >= notes.length) newIndex = notes.length - 2; | ||
- | } | ||
- | | ||
- | // Ist der zweite Ton höher? | ||
- | isSecondNoteHigher = newIndex > firstNoteIndex; | ||
- | | ||
- | return notes[newIndex]; | ||
- | } | ||
- | | ||
- | // Neue Frage generieren | ||
- | function generateQuestion() { | ||
- | const firstNote = getRandomNote(); | ||
- | const secondNote = generateSecondNote(firstNote); | ||
- | | ||
- | return { | ||
- | firstNote, | ||
- | secondNote, | ||
- | isSecondNoteHigher | ||
- | }; | ||
- | } | ||
- | | ||
- | // Fortschrittsanzeige aktualisieren | ||
- | function updateProgress() { | ||
- | // Fortschrittspunkte aktualisieren | ||
- | progressDots.innerHTML = ''; | ||
- | for (let i = 0; i < totalQuestions; | ||
- | const dot = document.createElement(' | ||
- | dot.classList.add(' | ||
- | | ||
- | // Status des Punktes basierend auf dem Spielfortschritt | ||
- | if (i < currentQuestion) { | ||
- | if (questions[i].correct) { | ||
- | dot.classList.add(' | ||
- | } else { | ||
- | dot.classList.add(' | ||
- | } | ||
- | } else if (i === currentQuestion) { | ||
- | dot.classList.add(' | ||
- | } | ||
- | | ||
- | progressDots.appendChild(dot); | ||
- | } | ||
- | | ||
- | // Statistiken aktualisieren | ||
- | const answered = Math.min(currentQuestion, | ||
- | let correctPercentage = 0; | ||
- | let incorrectPercentage = 0; | ||
- | | ||
- | if (answered > 0) { | ||
- | correctPercentage = Math.round((correctAnswers / answered) * 100); | ||
- | incorrectPercentage = 100 - correctPercentage; | ||
- | } | ||
- | | ||
- | statsDiv.textContent = `Frage ${currentQuestion + 1} von ${totalQuestions} | ` + | ||
- | `Richtig: ${correctPercentage}% | ` + | ||
- | `Falsch: ${incorrectPercentage}%`; | ||
- | } | ||
- | | ||
- | // Prüfung starten | ||
- | function startTest() { | ||
- | // Audio initialisieren, | ||
- | if (!audioInitialized) { | ||
- | initializeAudio(); | ||
- | } | ||
- | | ||
- | // Fragen generieren | ||
- | questions = []; | ||
- | for (let i = 0; i < totalQuestions; | ||
- | questions.push(generateQuestion()); | ||
- | } | ||
- | | ||
- | // Variablen zurücksetzen | ||
- | currentQuestion = 0; | ||
- | correctAnswers = 0; | ||
- | | ||
- | // Aktuelle Frage setzen | ||
- | currentFirstNote = questions[0].firstNote; | ||
- | currentSecondNote = questions[0].secondNote; | ||
- | isSecondNoteHigher = questions[0].isSecondNoteHigher; | ||
- | | ||
- | // UI aktualisieren | ||
- | testArea.classList.remove(' | ||
- | document.querySelector(' | ||
- | finalResult.classList.add(' | ||
- | retryNextDiv.classList.add(' | ||
- | higherBtn.classList.remove(' | ||
- | lowerBtn.classList.remove(' | ||
- | | ||
- | // Fortschritt aktualisieren | ||
- | updateProgress(); | ||
- | | ||
- | // Töne automatisch abspielen | ||
- | setTimeout(playTones, | ||
- | } | ||
- | | ||
- | // Prüfung abbrechen | ||
- | function cancelTest() { | ||
- | testArea.classList.add(' | ||
- | document.querySelector(' | ||
- | } | ||
- | | ||
- | // Zur nächsten Frage gehen | ||
- | function nextQuestion() { | ||
- | currentQuestion++; | ||
- | | ||
- | // Prüfen, ob alle Fragen beantwortet wurden | ||
- | if (currentQuestion >= totalQuestions) { | ||
- | showFinalResult(); | ||
- | return; | ||
- | } | ||
- | | ||
- | // Aktuelle Frage setzen | ||
- | currentFirstNote = questions[currentQuestion].firstNote; | ||
- | currentSecondNote = questions[currentQuestion].secondNote; | ||
- | isSecondNoteHigher = questions[currentQuestion].isSecondNoteHigher; | ||
- | | ||
- | // UI zurücksetzen | ||
- | retryNextDiv.classList.add(' | ||
- | higherBtn.classList.remove(' | ||
- | lowerBtn.classList.remove(' | ||
- | | ||
- | // Fortschritt aktualisieren | ||
- | updateProgress(); | ||
- | | ||
- | // Töne automatisch abspielen | ||
- | setTimeout(playTones, | ||
- | } | ||
- | | ||
- | // Endergebnis anzeigen | ||
- | function showFinalResult() { | ||
- | testArea.classList.add(' | ||
- | finalResult.classList.remove(' | ||
- | | ||
- | const percentage = Math.round((correctAnswers / totalQuestions) * 100); | ||
- | | ||
- | let message; | ||
- | if (percentage >= 90) { | ||
- | message = `Ausgezeichnet! Du hast ${percentage}% der Fragen richtig beantwortet!`; | ||
- | } else if (percentage >= 70) { | ||
- | message = `Gut gemacht! Du hast ${percentage}% der Fragen richtig beantwortet.`; | ||
- | } else if (percentage >= 50) { | ||
- | message = `Nicht schlecht! Du hast ${percentage}% der Fragen richtig beantwortet.`; | ||
- | } else { | ||
- | message = `Du hast ${percentage}% der Fragen richtig beantwortet. Übe weiter!`; | ||
- | } | ||
- | | ||
- | resultMessage.textContent = message; | ||
- | } | ||
- | | ||
- | // Schwierigkeitsgradbeschreibung aktualisieren | ||
- | function updateDifficultyDescription(selectedDifficulty) { | ||
- | difficultyDescription.textContent = difficultyDescriptions[selectedDifficulty]; | ||
- | } | ||
- | | ||
- | // Event-Listener | ||
- | | ||
- | // Schwierigkeitsgrad wählen | ||
- | difficultyBtns.forEach(btn => { | ||
- | btn.addEventListener(' | ||
- | // Aktiven Button zurücksetzen | ||
- | difficultyBtns.forEach(b => b.classList.remove(' | ||
- | | ||
- | // Neuen Button aktivieren | ||
- | btn.classList.add(' | ||
- | | ||
- | // Schwierigkeitsgrad setzen | ||
- | difficulty = btn.dataset.difficulty; | ||
- | | ||
- | // Beschreibung aktualisieren | ||
- | updateDifficultyDescription(difficulty); | ||
- | }); | ||
- | }); | ||
- | | ||
- | // Prüfung starten | ||
- | startTestBtn.addEventListener(' | ||
- | | ||
- | // Töne abspielen | ||
- | playTonesBtn.addEventListener(' | ||
- | | ||
- | // Prüfung abbrechen | ||
- | cancelTestBtn.addEventListener(' | ||
- | | ||
- | // Antwort: Höher | ||
- | higherBtn.addEventListener(' | ||
- | // Antwort überprüfen | ||
- | const isCorrect = isSecondNoteHigher; | ||
- | | ||
- | // Antwort speichern | ||
- | questions[currentQuestion].correct = isCorrect; | ||
- | | ||
- | if (isCorrect) { | ||
- | correctAnswers++; | ||
- | higherBtn.classList.add(' | ||
- | lowerBtn.classList.remove(' | ||
- | | ||
- | // Bei richtiger Antwort nach 1 Sekunde automatisch zur nächsten Frage | ||
- | setTimeout(nextQuestion, | ||
- | } else { | ||
- | higherBtn.classList.add(' | ||
- | lowerBtn.classList.add(' | ||
- | | ||
- | // Bei falscher Antwort Optionen anzeigen | ||
- | retryNextDiv.classList.remove(' | ||
- | } | ||
- | | ||
- | // Buttons deaktivieren nach Antwort | ||
- | higherBtn.disabled = true; | ||
- | lowerBtn.disabled = true; | ||
- | | ||
- | // Fortschritt aktualisieren | ||
- | updateProgress(); | ||
- | }); | ||
- | | ||
- | // Antwort: Tiefer | ||
- | lowerBtn.addEventListener(' | ||
- | // Antwort überprüfen | ||
- | const isCorrect = !isSecondNoteHigher; | ||
- | | ||
- | // Antwort speichern | ||
- | questions[currentQuestion].correct = isCorrect; | ||
- | | ||
- | if (isCorrect) { | ||
- | correctAnswers++; | ||
- | lowerBtn.classList.add(' | ||
- | higherBtn.classList.remove(' | ||
- | | ||
- | // Bei richtiger Antwort nach 1 Sekunde automatisch zur nächsten Frage | ||
- | setTimeout(nextQuestion, | ||
- | } else { | ||
- | lowerBtn.classList.add(' | ||
- | higherBtn.classList.add(' | ||
- | | ||
- | // Bei falscher Antwort Optionen anzeigen | ||
- | retryNextDiv.classList.remove(' | ||
- | } | ||
- | | ||
- | // Buttons deaktivieren nach Antwort | ||
- | higherBtn.disabled = true; | ||
- | lowerBtn.disabled = true; | ||
- | | ||
- | // Fortschritt aktualisieren | ||
- | updateProgress(); | ||
- | }); | ||
- | | ||
- | // " | ||
- | retryBtn.addEventListener(' | ||
- | // Die gleichen Töne erneut abspielen | ||
- | playTones(); | ||
- | }); | ||
- | | ||
- | // " | ||
- | nextBtn.addEventListener(' | ||
- | // Zur nächsten Frage gehen | ||
- | nextQuestion(); | ||
- | }); | ||
- | | ||
- | // Prüfung neu starten | ||
- | restartBtn.addEventListener(' | ||
- | finalResult.classList.add(' | ||
- | document.querySelector(' | ||
- | }); | ||
- | }); | ||
- | </ | ||
- | </ | ||
- | </ |
tools/tonhoehentrainer2.1747670671.txt.gz · Zuletzt geändert: von Eric Weber