tools:rhythmustrainer-fortgeschritten
Unterschiede
Hier werden die Unterschiede zwischen zwei Versionen der Seite angezeigt.
| Beide Seiten, vorherige ÜberarbeitungVorherige Überarbeitung | |||
| tools:rhythmustrainer-fortgeschritten [12/08/2025 21:29] – Eric Weber | tools:rhythmustrainer-fortgeschritten [12/08/2025 21:37] (aktuell) – Eric Weber | ||
|---|---|---|---|
| Zeile 1: | Zeile 1: | ||
| - | FIXME | + | < |
| + | <html lang=" | ||
| + | < | ||
| + | <meta charset=" | ||
| + | <meta name=" | ||
| + | < | ||
| + | < | ||
| + | .rhythm-trainer { | ||
| + | font-family: | ||
| + | max-width: 800px; | ||
| + | margin: 0 auto; | ||
| + | padding: 20px; | ||
| + | color: #333; | ||
| + | box-sizing: border-box; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer * { | ||
| + | box-sizing: border-box; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer h1 { | ||
| + | text-align: center; | ||
| + | color: #4a5568; | ||
| + | margin: 0 0 30px 0; | ||
| + | font-size: 2.5em; | ||
| + | background: linear-gradient(45deg, | ||
| + | -webkit-background-clip: | ||
| + | -webkit-text-fill-color: | ||
| + | background-clip: | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .controls { | ||
| + | display: flex; | ||
| + | gap: 15px; | ||
| + | justify-content: | ||
| + | margin-bottom: | ||
| + | flex-wrap: wrap; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer button { | ||
| + | padding: 12px 24px; | ||
| + | font-size: 16px; | ||
| + | border: none; | ||
| + | border-radius: | ||
| + | cursor: pointer; | ||
| + | transition: all 0.3s ease; | ||
| + | font-weight: | ||
| + | text-transform: | ||
| + | letter-spacing: | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .primary-btn { | ||
| + | background: linear-gradient(45deg, | ||
| + | color: white; | ||
| + | box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .primary-btn: | ||
| + | transform: translateY(-2px); | ||
| + | box-shadow: 0 8px 25px rgba(102, 126, 234, 0.6); | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .secondary-btn { | ||
| + | background: linear-gradient(45deg, | ||
| + | color: white; | ||
| + | box-shadow: 0 4px 15px rgba(72, 187, 120, 0.4); | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .secondary-btn: | ||
| + | transform: translateY(-2px); | ||
| + | box-shadow: 0 8px 25px rgba(72, 187, 120, 0.6); | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .show-btn { | ||
| + | background: linear-gradient(45deg, | ||
| + | color: white; | ||
| + | box-shadow: 0 4px 15px rgba(237, 137, 54, 0.4); | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .show-btn: | ||
| + | transform: translateY(-2px); | ||
| + | box-shadow: 0 8px 25px rgba(237, 137, 54, 0.6); | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer button: | ||
| + | opacity: 0.5; | ||
| + | cursor: not-allowed; | ||
| + | transform: none !important; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .rhythm-display { | ||
| + | background: #f7fafc; | ||
| + | border: 3px solid #e2e8f0; | ||
| + | border-radius: | ||
| + | padding: 30px; | ||
| + | margin: 20px 0; | ||
| + | text-align: center; | ||
| + | min-height: 120px; | ||
| + | display: flex; | ||
| + | align-items: | ||
| + | justify-content: | ||
| + | position: relative; | ||
| + | overflow: hidden; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .rhythm-display:: | ||
| + | content: ''; | ||
| + | position: absolute; | ||
| + | top: 0; | ||
| + | left: -100%; | ||
| + | width: 100%; | ||
| + | height: 100%; | ||
| + | background: linear-gradient(90deg, | ||
| + | transition: left 0.5s ease; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .rhythm-display.playing:: | ||
| + | left: 100%; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .rhythm-notation { | ||
| + | font-size: 3em; | ||
| + | font-family: | ||
| + | font-weight: | ||
| + | color: #2d3748; | ||
| + | letter-spacing: | ||
| + | text-shadow: | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .beat-indicator { | ||
| + | display: flex; | ||
| + | justify-content: | ||
| + | gap: 20px; | ||
| + | margin: 20px 0; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .beat { | ||
| + | width: 60px; | ||
| + | height: 60px; | ||
| + | border: 3px solid #cbd5e0; | ||
| + | border-radius: | ||
| + | display: flex; | ||
| + | align-items: | ||
| + | justify-content: | ||
| + | font-weight: | ||
| + | font-size: 20px; | ||
| + | color: #4a5568; | ||
| + | transition: all 0.3s ease; | ||
| + | background: white; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .beat.active { | ||
| + | background: linear-gradient(45deg, | ||
| + | color: white; | ||
| + | transform: scale(1.2); | ||
| + | box-shadow: 0 0 20px rgba(102, 126, 234, 0.6); | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .info { | ||
| + | background: #ebf8ff; | ||
| + | border-left: | ||
| + | padding: 15px; | ||
| + | margin: 20px 0; | ||
| + | border-radius: | ||
| + | font-size: 14px; | ||
| + | color: #2c5282; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .tempo-control { | ||
| + | display: flex; | ||
| + | align-items: | ||
| + | justify-content: | ||
| + | gap: 15px; | ||
| + | margin: 20px 0; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .tempo-control input { | ||
| + | width: 200px; | ||
| + | height: 8px; | ||
| + | border-radius: | ||
| + | background: #e2e8f0; | ||
| + | outline: none; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .tempo-display { | ||
| + | font-weight: | ||
| + | font-size: 18px; | ||
| + | color: #4a5568; | ||
| + | min-width: 80px; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .legend { | ||
| + | background: #f0fff4; | ||
| + | border: 1px solid #9ae6b4; | ||
| + | border-radius: | ||
| + | padding: 15px; | ||
| + | margin: 20px 0; | ||
| + | display: flex; | ||
| + | justify-content: | ||
| + | flex-wrap: wrap; | ||
| + | gap: 15px; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .legend-item { | ||
| + | display: flex; | ||
| + | align-items: | ||
| + | gap: 8px; | ||
| + | font-size: 14px; | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .legend-symbol { | ||
| + | font-size: 24px; | ||
| + | font-family: | ||
| + | font-weight: | ||
| + | } | ||
| + | |||
| + | @keyframes rhythm-pulse { | ||
| + | 0% { transform: scale(1); } | ||
| + | 50% { transform: scale(1.1); } | ||
| + | 100% { transform: scale(1); } | ||
| + | } | ||
| + | |||
| + | .rhythm-trainer .rhythm-display.playing .rhythm-notation { | ||
| + | animation: rhythm-pulse 0.5s ease-in-out; | ||
| + | } | ||
| + | </ | ||
| + | </ | ||
| + | < | ||
| + | <div class=" | ||
| + | < | ||
| + | < | ||
| + | |||
| + | <div class=" | ||
| + | < | ||
| + | Hören Sie sich den Rhythmus an und versuchen Sie ihn zu notieren. Mit " | ||
| + | </ | ||
| + | |||
| + | <div class=" | ||
| + | <div class=" | ||
| + | <span class=" | ||
| + | < | ||
| + | </ | ||
| + | <div class=" | ||
| + | <span class=" | ||
| + | < | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | <div class=" | ||
| + | < | ||
| + | <input type=" | ||
| + | <span class=" | ||
| + | </ | ||
| + | |||
| + | <div class=" | ||
| + | <button class=" | ||
| + | <button class=" | ||
| + | <button class=" | ||
| + | </ | ||
| + | |||
| + | <div class=" | ||
| + | <div class=" | ||
| + | <div class=" | ||
| + | <div class=" | ||
| + | <div class=" | ||
| + | </ | ||
| + | |||
| + | <div class=" | ||
| + | <div class=" | ||
| + | </ | ||
| + | </ | ||
| + | |||
| + | < | ||
| + | let currentRhythm = []; | ||
| + | let isPlaying = false; | ||
| + | let solutionVisible = false; | ||
| + | let tempo = 120; | ||
| + | |||
| + | // UI Elements | ||
| + | const generateBtn = document.getElementById(' | ||
| + | const playBtn = document.getElementById(' | ||
| + | const showBtn = document.getElementById(' | ||
| + | const rhythmDisplay = document.getElementById(' | ||
| + | const rhythmNotation = document.getElementById(' | ||
| + | const tempoSlider = document.getElementById(' | ||
| + | const tempoDisplay = document.getElementById(' | ||
| + | const beats = [ | ||
| + | document.getElementById(' | ||
| + | document.getElementById(' | ||
| + | document.getElementById(' | ||
| + | document.getElementById(' | ||
| + | ]; | ||
| + | |||
| + | // Audio Setup with Web Audio API | ||
| + | let audioContext; | ||
| + | let isAudioReady = false; | ||
| + | |||
| + | // Note types with their durations and symbols | ||
| + | const noteTypes = [ | ||
| + | { symbol: ' | ||
| + | { symbol: ' | ||
| + | ]; | ||
| + | |||
| + | async function initAudio() { | ||
| + | if (!isAudioReady) { | ||
| + | audioContext = new (window.AudioContext || window.webkitAudioContext)(); | ||
| + | isAudioReady = true; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | function playBeep(frequency = 800, duration = 0.2, startTime = 0, volume = 0.3) { | ||
| + | if (!audioContext) return; | ||
| + | |||
| + | const oscillator = audioContext.createOscillator(); | ||
| + | const gainNode = audioContext.createGain(); | ||
| + | |||
| + | oscillator.connect(gainNode); | ||
| + | gainNode.connect(audioContext.destination); | ||
| + | |||
| + | oscillator.frequency.setValueAtTime(frequency, | ||
| + | oscillator.type = ' | ||
| + | |||
| + | gainNode.gain.setValueAtTime(0, | ||
| + | gainNode.gain.linearRampToValueAtTime(volume, | ||
| + | gainNode.gain.exponentialRampToValueAtTime(0.001, | ||
| + | |||
| + | oscillator.start(audioContext.currentTime + startTime); | ||
| + | oscillator.stop(audioContext.currentTime + startTime + duration); | ||
| + | } | ||
| + | |||
| + | // Generate random rhythm that adds up to exactly 4 beats | ||
| + | function generateRhythm() { | ||
| + | currentRhythm = []; | ||
| + | let totalDuration = 0; | ||
| + | const targetDuration = 4; // 4/4 Takt | ||
| + | |||
| + | // Generate rhythm until we reach exactly 4 beats | ||
| + | while (totalDuration < targetDuration) { | ||
| + | const remainingDuration = targetDuration - totalDuration; | ||
| + | |||
| + | // Filter notes that fit in remaining time | ||
| + | const validNotes = noteTypes.filter(note => note.duration <= remainingDuration); | ||
| + | |||
| + | if (validNotes.length === 0) { | ||
| + | // If no note fits, fill with eighth notes | ||
| + | while (totalDuration < targetDuration) { | ||
| + | currentRhythm.push(noteTypes[0]); | ||
| + | totalDuration += 0.5; | ||
| + | } | ||
| + | break; | ||
| + | } | ||
| + | |||
| + | const randomNote = validNotes[Math.floor(Math.random() * validNotes.length)]; | ||
| + | currentRhythm.push(randomNote); | ||
| + | totalDuration += randomNote.duration; | ||
| + | } | ||
| + | |||
| + | // Reset solution state | ||
| + | solutionVisible = false; | ||
| + | showBtn.textContent = ' | ||
| + | |||
| + | playBtn.disabled = false; | ||
| + | showBtn.disabled = false; | ||
| + | rhythmNotation.textContent = " | ||
| + | rhythmDisplay.classList.remove(' | ||
| + | } | ||
| + | |||
| + | // Convert rhythm to notation | ||
| + | function rhythmToNotation(rhythm) { | ||
| + | return rhythm.map(note => note.symbol).join(' | ||
| + | } | ||
| + | |||
| + | // Play rhythm with visual beat indication | ||
| + | async function playRhythm() { | ||
| + | if (isPlaying) return; | ||
| + | |||
| + | await initAudio(); | ||
| + | isPlaying = true; | ||
| + | playBtn.disabled = true; | ||
| + | rhythmDisplay.classList.add(' | ||
| + | |||
| + | const beatDuration = 60 / tempo; // Duration of one beat in seconds | ||
| + | |||
| + | // Highlight beats independently (every beat for 4 beats) | ||
| + | for (let beat = 0; beat < 4; beat++) { | ||
| + | setTimeout(() => { | ||
| + | beats.forEach(b => b.classList.remove(' | ||
| + | beats[beat].classList.add(' | ||
| + | }, beat * beatDuration * 1000); | ||
| + | } | ||
| + | |||
| + | // Play the rhythm notes | ||
| + | let currentTime = 0; | ||
| + | for (let i = 0; i < currentRhythm.length; | ||
| + | const note = currentRhythm[i]; | ||
| + | |||
| + | // Play the note sound | ||
| + | playBeep(523, | ||
| + | |||
| + | currentTime += note.duration; | ||
| + | } | ||
| + | |||
| + | // Clean up after rhythm is finished (after 4 beats) | ||
| + | const totalTime = 4 * beatDuration; | ||
| + | setTimeout(() => { | ||
| + | beats.forEach(beat => beat.classList.remove(' | ||
| + | rhythmDisplay.classList.remove(' | ||
| + | isPlaying = false; | ||
| + | playBtn.disabled = false; | ||
| + | }, totalTime * 1000 + 200); | ||
| + | } | ||
| + | |||
| + | // Show/hide solution | ||
| + | function showSolution() { | ||
| + | if (!solutionVisible) { | ||
| + | const notation = rhythmToNotation(currentRhythm); | ||
| + | rhythmNotation.textContent = notation; | ||
| + | solutionVisible = true; | ||
| + | showBtn.textContent = ' | ||
| + | } else { | ||
| + | rhythmNotation.textContent = " | ||
| + | solutionVisible = false; | ||
| + | showBtn.textContent = ' | ||
| + | } | ||
| + | } | ||
| + | |||
| + | // Update tempo | ||
| + | function updateTempo() { | ||
| + | tempo = parseInt(tempoSlider.value); | ||
| + | tempoDisplay.textContent = tempo + ' BPM'; | ||
| + | } | ||
| + | |||
| + | // Event Listeners | ||
| + | generateBtn.addEventListener(' | ||
| + | playBtn.addEventListener(' | ||
| + | showBtn.addEventListener(' | ||
| + | tempoSlider.addEventListener(' | ||
| + | |||
| + | // Initialize | ||
| + | updateTempo(); | ||
| + | </ | ||
| + | </ | ||
| + | </ | ||
tools/rhythmustrainer-fortgeschritten.1755026998.txt.gz · Zuletzt geändert: von Eric Weber
