166 lines
8.1 KiB
Markdown
166 lines
8.1 KiB
Markdown
---
|
|
phase: 04-polish-audio
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- src/audio/sounds.ts
|
|
- src/audio/speech.ts
|
|
- src/types.ts
|
|
- index.html
|
|
- src/styles/main.css
|
|
- src/main.ts
|
|
autonomous: true
|
|
requirements: [AUDI-01, AUDI-02, AUDI-03, AUDI-04, AUDI-05]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "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)"
|
|
artifacts:
|
|
- path: "src/audio/sounds.ts"
|
|
provides: "Web Audio API sound effects"
|
|
exports: ["playCorrectSound", "playRewardSound", "playForestElementSound", "initAudioOnInteraction"]
|
|
- path: "src/audio/speech.ts"
|
|
provides: "Web Speech API text-to-speech"
|
|
exports: ["speakText"]
|
|
- path: "index.html"
|
|
provides: "Mute button element outside screens"
|
|
contains: "mute-btn"
|
|
key_links:
|
|
- from: "src/audio/sounds.ts"
|
|
to: "src/storage/db.ts"
|
|
via: "getSettings() check for audioEnabled"
|
|
pattern: "getSettings.*audioEnabled"
|
|
- from: "src/audio/speech.ts"
|
|
to: "src/storage/db.ts"
|
|
via: "getSettings() check for speechEnabled"
|
|
pattern: "getSettings.*speechEnabled"
|
|
---
|
|
|
|
<objective>
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<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
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Audio module + Speech module + Settings extension</name>
|
|
<files>src/audio/sounds.ts, src/audio/speech.ts, src/types.ts</files>
|
|
<read_first>src/types.ts, src/storage/db.ts, .planning/phases/04-polish-audio/04-CONTEXT.md</read_first>
|
|
<action>
|
|
1. In `src/types.ts`: Add `speechEnabled: boolean` to the `Settings` interface (per D-05). Keep existing fields unchanged.
|
|
|
|
2. 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).
|
|
|
|
3. 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.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit 2>&1 | head -20</automated>
|
|
</verify>
|
|
<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>
|
|
<done>Three sound functions and speakText function exist, all check Settings before executing, TypeScript compiles clean</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Mute button in HTML + CSS + wiring in main.ts</name>
|
|
<files>index.html, src/styles/main.css, src/main.ts</files>
|
|
<read_first>index.html, src/main.ts, src/styles/main.css, src/storage/db.ts</read_first>
|
|
<action>
|
|
1. In `index.html` (per D-03): Add a mute button BEFORE the `<script>` tag, outside all `<section>` screens:
|
|
```html
|
|
<button id="mute-btn" class="mute-btn" aria-label="Audio stumm schalten" title="Audio">
|
|
<span class="mute-btn__icon" id="mute-icon">🔊</span>
|
|
</button>
|
|
```
|
|
Use speaker emoji (U+1F50A for on, U+1F507 for muted).
|
|
|
|
2. 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; }`
|
|
|
|
3. 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
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/dev/workspace/zauberwald && npx tsc --noEmit 2>&1 | head -20</automated>
|
|
</verify>
|
|
<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>
|
|
<done>Mute button visible in fixed position, toggles audio state, persists across reload, AudioContext initialized on first interaction</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- `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
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
Audio system ready to be wired into lesson/reward/forest screens (Plan 04). Mute button functional and persisting state.
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/04-polish-audio/04-01-SUMMARY.md`
|
|
</output>
|