Files
Zauberwald/.planning/phases/04-polish-audio/04-01-PLAN.md
T

8.1 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
phase plan type wave depends_on files_modified autonomous requirements must_haves
04-polish-audio 01 execute 1
src/audio/sounds.ts
src/audio/speech.ts
src/types.ts
index.html
src/styles/main.css
src/main.ts
true
AUDI-01
AUDI-02
AUDI-03
AUDI-04
AUDI-05
truths artifacts key_links
playCorrectSound() erzeugt 800Hz Sinus-Ton fuer 0.1s bei audioEnabled=true
playRewardSound() erzeugt aufsteigende Tonfolge fuer 0.8s
playForestElementSound() erzeugt gefiltertes weisses Rauschen fuer 0.5s
Mute-Button im DOM sichtbar, toggle aendert audioEnabled in Settings und persistiert
speakText() liest deutschen Text via Web Speech API vor wenn speechEnabled=true
AudioContext wird lazy bei erster User-Interaktion erstellt (Autoplay Policy)
path provides exports
src/audio/sounds.ts Web Audio API sound effects
playCorrectSound
playRewardSound
playForestElementSound
initAudioOnInteraction
path provides exports
src/audio/speech.ts Web Speech API text-to-speech
speakText
path provides contains
index.html Mute button element outside screens mute-btn
from to via pattern
src/audio/sounds.ts src/storage/db.ts getSettings() check for audioEnabled getSettings.*audioEnabled
from to via pattern
src/audio/speech.ts src/storage/db.ts getSettings() check for speechEnabled getSettings.*speechEnabled
Audio-System fuer Zauberwald: Web Audio API Sound-Effekte (3 Sounds), Mute-Button, optionale Sprachausgabe via Web Speech API.

Purpose: Auditives Feedback macht das Tipperlebnis lebendig und belohnend -- per D-01 bis D-05 aus CONTEXT.md. Output: Zwei neue Module (sounds.ts, speech.ts), Mute-Button in index.html, speechEnabled Feld in Settings.

<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/04-polish-audio/04-CONTEXT.md

@src/types.ts @src/storage/db.ts @src/main.ts @index.html @src/styles/main.css

Task 1: Audio module + Speech module + Settings extension src/audio/sounds.ts, src/audio/speech.ts, src/types.ts src/types.ts, src/storage/db.ts, .planning/phases/04-polish-audio/04-CONTEXT.md 1. In `src/types.ts`: Add `speechEnabled: boolean` to the `Settings` interface (per D-05). Keep existing fields unchanged.
  1. Create src/audio/sounds.ts (per D-01, D-02):

    • Module-level let audioCtx: AudioContext | null = null
    • initAudioOnInteraction(): Creates/resumes AudioContext on first call. Intended to be called from a user-interaction event handler. Export this.
    • Helper isAudioEnabled(): async, calls getSettings(db) from db.ts (needs db reference -- use a module-level let db: IDBDatabase | null set via an setAudioDB(d) function, or import getDB() from app.ts). Check settings?.audioEnabled !== false. Return boolean.
    • playCorrectSound(): async. Check isAudioEnabled(). If false, return. Ensure audioCtx exists (call initAudioOnInteraction if needed). Create OscillatorNode: type sine, frequency 800Hz, connect to gain (0.15), start, stop after 0.1s. Ramp gain to 0 over last 0.02s for click-free cutoff.
    • playRewardSound(): async. Check enabled. Ascending tone sequence: play 3-4 notes (e.g. C5=523, E5=659, G5=784, C6=1047) each ~0.2s apart, each 0.15s duration, sine wave, gain 0.12. Total ~0.8s.
    • playForestElementSound(): async. Check enabled. Create white noise buffer (AudioBuffer, fill with Math.random()*2-1), play through BiquadFilterNode (bandpass, frequency 3000Hz, Q 0.5), gain 0.1, duration 0.5s with gain ramp-down.
    • All functions: wrap in try/catch (never throw to caller).
  2. Create src/audio/speech.ts (per D-04, D-05):

    • speakText(text: string): async. Check speechEnabled from Settings via getSettings. If not enabled, return. Check window.speechSynthesis exists. Create SpeechSynthesisUtterance(text), set lang='de-DE', rate=0.9, pitch=1.1. Try to find a German voice (getVoices().find(v => v.lang.startsWith('de'))), set if found. Call speechSynthesis.speak(utterance). Wrap in try/catch, silent fallback.
    • Handle voice loading: voices may load async. Use speechSynthesis.onvoiceschanged if getVoices() returns empty initially. cd /home/dev/workspace/zauberwald && npx tsc --noEmit 2>&1 | head -20 <acceptance_criteria>
    • src/audio/sounds.ts exports playCorrectSound, playRewardSound, playForestElementSound, initAudioOnInteraction
    • src/audio/speech.ts exports speakText
    • Settings interface in types.ts includes speechEnabled: boolean
    • All three sound functions check audioEnabled before playing
    • speakText checks speechEnabled before speaking
    • TypeScript compiles without errors </acceptance_criteria> Three sound functions and speakText function exist, all check Settings before executing, TypeScript compiles clean
Task 2: Mute button in HTML + CSS + wiring in main.ts index.html, src/styles/main.css, src/main.ts index.html, src/main.ts, src/styles/main.css, src/storage/db.ts 1. In `index.html` (per D-03): Add a mute button BEFORE the `<script>` tag, outside all `
` screens: ```html 🔊 ``` Use speaker emoji (U+1F50A for on, U+1F507 for muted).
  1. In src/styles/main.css: Add .mute-btn styles:

    • position: fixed; bottom: 16px; right: 16px; z-index: 100;
    • width: 44px; height: 44px; border-radius: 50%;
    • background: var(--bg-cream); border: 2px solid var(--accent-green);
    • cursor: pointer; font-size: 20px; display: flex; align-items: center; justify-content: center;
    • box-shadow: 0 2px 8px rgba(0,0,0,0.1);
    • transition: transform 0.2s ease;
    • .mute-btn:hover { transform: scale(1.1); }
    • .mute-btn--muted { border-color: var(--text-light); opacity: 0.6; }
  2. In src/main.ts: After initApp, wire up the mute button:

    • Import getSettings, saveSettings from db.ts, getDB from app.ts, initAudioOnInteraction from audio/sounds.ts
    • On DOMContentLoaded (after initApp): read settings, set initial icon state
    • muteBtn.onclick: toggle audioEnabled, save to settings, update icon (U+1F50A vs U+1F507), toggle .mute-btn--muted class
    • Also call initAudioOnInteraction() on first mute-btn click (satisfies autoplay policy per D-02)
    • Add a one-time click/keydown listener on document body that calls initAudioOnInteraction() for autoplay policy compliance cd /home/dev/workspace/zauberwald && npx tsc --noEmit 2>&1 | head -20 <acceptance_criteria>
    • index.html contains element with id="mute-btn" outside all section.screen elements
    • main.css contains .mute-btn with position: fixed
    • main.ts imports from audio/sounds and wires mute button click handler
    • Mute button toggles audioEnabled in Settings store
    • initAudioOnInteraction called on first user interaction </acceptance_criteria> Mute button visible in fixed position, toggles audio state, persists across reload, AudioContext initialized on first interaction
- `npx tsc --noEmit` passes - `npm run lint` passes (biome) - index.html has mute-btn element - src/audio/sounds.ts and src/audio/speech.ts exist with correct exports

<success_criteria> Audio system ready to be wired into lesson/reward/forest screens (Plan 04). Mute button functional and persisting state. </success_criteria>

After completion, create `.planning/phases/04-polish-audio/04-01-SUMMARY.md`