From d04990f6c5953782057b17bd7837c57b875dc558 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 3 Apr 2026 14:52:26 +0200 Subject: [PATCH] =?UTF-8?q?docs(02-05):=20complete=20BeatDetector=20plan?= =?UTF-8?q?=20=E2=80=94=20AUD-03=20and=20AUD-04=20fulfilled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SUMMARY.md: TDD execution documented, deviation noted (module-level time import) - STATE.md: advanced to plan 3/6, 90% progress, decision logged - ROADMAP.md: 5/6 summaries complete for phase 02 - REQUIREMENTS.md: AUD-03 and AUD-04 marked complete --- .planning/REQUIREMENTS.md | 8 +- .planning/STATE.md | 9 +- .../phases/02-app-core-audio/02-05-SUMMARY.md | 107 ++++++++++++++++++ 3 files changed, 116 insertions(+), 8 deletions(-) create mode 100644 .planning/phases/02-app-core-audio/02-05-SUMMARY.md diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md index 82b0579..386c34b 100644 --- a/.planning/REQUIREMENTS.md +++ b/.planning/REQUIREMENTS.md @@ -17,8 +17,8 @@ - [x] **AUD-01**: User kann lokale Songdateien laden und abspielen (MP3, FLAC, WAV) - [x] **AUD-02**: User kann Songs pausieren, fortsetzen und an beliebige Stelle springen (Seek) -- [ ] **AUD-03**: System-Audio wird ueber PipeWire/PulseAudio fuer Beat-Detection erfasst -- [ ] **AUD-04**: Beat/Onset-Detection erkennt Schlaege aus dem Audio-Stream in Echtzeit +- [x] **AUD-03**: System-Audio wird ueber PipeWire/PulseAudio fuer Beat-Detection erfasst +- [x] **AUD-04**: Beat/Onset-Detection erkennt Schlaege aus dem Audio-Stream in Echtzeit ### Choreografie @@ -84,8 +84,8 @@ | FW-05 | Phase 1 | Complete | | AUD-01 | Phase 2 | Complete | | AUD-02 | Phase 2 | Complete | -| AUD-03 | Phase 2 | Pending | -| AUD-04 | Phase 2 | Pending | +| AUD-03 | Phase 2 | Complete | +| AUD-04 | Phase 2 | Complete | | CHR-04 | Phase 2 | Complete | | CHR-05 | Phase 2 | Complete | | CHR-01 | Phase 3 | Pending | diff --git a/.planning/STATE.md b/.planning/STATE.md index b1e4c05..5f3db03 100644 --- a/.planning/STATE.md +++ b/.planning/STATE.md @@ -3,8 +3,8 @@ gsd_state_version: 1.0 milestone: v1.0 milestone_name: milestone status: executing -stopped_at: Completed 02-app-core-audio 02-04-PLAN.md -last_updated: "2026-04-03T12:52:07.259Z" +stopped_at: Completed 02-app-core-audio 02-05-PLAN.md +last_updated: "2026-04-03T12:52:14.694Z" last_activity: 2026-04-03 progress: total_phases: 4 @@ -95,6 +95,7 @@ Recent decisions affecting current work: - [Phase 02-app-core-audio]: Pydantic v2 field_validator + model_post_init pattern established for ChoreoEvent/ChoreoFile models - [Phase 02-app-core-audio]: Absolute monotonic timestamps (D-07): fire_at recalculated fresh each 5ms poll — no drift accumulation over any duration - [Phase 02-app-core-audio]: pause_offset accumulation (D-09): resume() adds elapsed pause time keeping all fire_at values correct without event skipping +- [Phase 02-app-core-audio]: D-05 enforced: call_soon_threadsafe bridge pattern in _audio_callback — no direct asyncio calls from audio thread; import time moved to module-level ### Pending Todos @@ -109,6 +110,6 @@ None yet. ## Session Continuity -Last session: 2026-04-03T12:51:57.725Z -Stopped at: Completed 02-app-core-audio 02-04-PLAN.md +Last session: 2026-04-03T12:52:14.679Z +Stopped at: Completed 02-app-core-audio 02-05-PLAN.md Resume file: None diff --git a/.planning/phases/02-app-core-audio/02-05-SUMMARY.md b/.planning/phases/02-app-core-audio/02-05-SUMMARY.md new file mode 100644 index 0000000..e19ce84 --- /dev/null +++ b/.planning/phases/02-app-core-audio/02-05-SUMMARY.md @@ -0,0 +1,107 @@ +--- +phase: 02-app-core-audio +plan: "05" +subsystem: audio +tags: [beat-detection, sounddevice, aubio, asyncio-bridge, threading] +dependency_graph: + requires: + - 02-02 (UDP transport) + - 02-03 (AudioPlayer) + provides: + - BeatDetector class (AUD-03, AUD-04) + affects: + - 02-06 (CLI integration — will wire BeatDetector to REPL) + - Phase 5 (Live Reactive mode — reuses BeatDetector directly) +tech_stack: + added: + - sounddevice 0.5.x (system audio capture via PortAudio/PipeWire) + - aubio 0.4.9 (real-time beat detection, C-backed) + patterns: + - call_soon_threadsafe bridge: audio thread -> asyncio event loop (D-05) + - threading.Lock for shared state (beat_count, bpm, last_beat_time) + - Double-trigger suppression via monotonic hold timer (200ms) +key_files: + created: + - src/led_sync/audio/beat_detector.py + - tests/test_beat_detector.py + modified: + - src/led_sync/audio/__init__.py +decisions: + - "D-04 enforced: sounddevice thread is fully independent from asyncio and miniaudio" + - "D-05 enforced: call_soon_threadsafe used in _audio_callback — no direct asyncio calls" + - "D-06 noted: device=None for PipeWire default; monitor source for loopback capture" + - "import time in _audio_callback removed — time import moved to module level for cleanliness" +metrics: + duration: "~2 minutes" + completed: "2026-04-03" + tasks_completed: 1 + files_created: 2 + files_modified: 1 +--- + +# Phase 2 Plan 5: BeatDetector — Real-Time Beat Detection Summary + +**One-liner:** sounddevice InputStream + aubio.tempo with call_soon_threadsafe bridge into asyncio — AUD-03 and AUD-04 fulfilled. + +## What Was Built + +`BeatDetector` class in `src/led_sync/audio/beat_detector.py` — a real-time beat detection module that: + +1. Opens a `sounddevice.InputStream` at 44100Hz with 512-sample blocks (~11.6ms latency per hop) +2. Runs `aubio.tempo` in the OS audio thread callback to detect beats from the audio stream +3. Bridges beat events into the asyncio event loop via `loop.call_soon_threadsafe(loop.create_task, on_beat(beat_time))` +4. Uses `threading.Lock` for thread-safe `beat_count` and `bpm` properties +5. Implements 200ms double-trigger suppression to avoid false positives on strong transients + +## Public API + +```python +detector = BeatDetector(loop=asyncio.get_running_loop(), on_beat=my_async_fn) +detector.start(device=None) # None = PipeWire default; pass monitor source for loopback +detector.stop() # idempotent, no-op if already stopped +detector.bpm: float # current aubio BPM estimate +detector.beat_count: int # total beats since start +``` + +## Key Design Decisions + +- **D-04 boundary:** `_audio_callback` is a pure OS thread function — zero asyncio dependencies +- **D-05 bridge:** `call_soon_threadsafe(loop.create_task, on_beat(beat_time))` is the only crossing point +- **D-06 PipeWire:** `device=None` selects the PipeWire default input; for system audio loopback, pass the monitor source name (documented in module docstring) +- Constants `SAMPLE_RATE=44100`, `HOP_SIZE=512`, `BUF_SIZE=1024` lock in ~11.6ms latency + +## TDD Execution + +- **RED commit:** 3ad5e91 — 14 failing tests covering import, constants, thread safety, asyncio bridge pattern, and ImportError handling +- **GREEN commit:** f1af7ba — Implementation passes all 14 tests + 57 total test suite passes + +## Deviations from Plan + +**1. [Rule 1 - Bug] Removed `import time as _time` inline import inside callback** +- Found during: implementation review +- Issue: Plan code sample had `import time as _time` inside `_audio_callback` — importing inside a hot audio callback runs at ~11ms intervals and is poor practice +- Fix: Moved `import time` to module-level imports +- Files modified: `src/led_sync/audio/beat_detector.py` +- Commit: f1af7ba (included in implementation) + +Otherwise, plan executed exactly as specified. + +## Verification Results + +``` +BeatDetector class OK: latency=11.6ms, all properties accessible +57 passed in 2.80s +``` + +## Known Stubs + +None — BeatDetector is fully implemented. `start()` requires a real audio device to test beat detection live; this is documented in the module as expected behavior (Pi hardware required). + +## Self-Check: PASSED + +| Item | Status | +|------|--------| +| src/led_sync/audio/beat_detector.py | FOUND | +| tests/test_beat_detector.py | FOUND | +| Commit 3ad5e91 (TDD RED) | FOUND | +| Commit f1af7ba (TDD GREEN) | FOUND |