docs(02-05): complete BeatDetector plan — AUD-03 and AUD-04 fulfilled
- 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
This commit is contained in:
@@ -17,8 +17,8 @@
|
|||||||
|
|
||||||
- [x] **AUD-01**: User kann lokale Songdateien laden und abspielen (MP3, FLAC, WAV)
|
- [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)
|
- [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
|
- [x] **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-04**: Beat/Onset-Detection erkennt Schlaege aus dem Audio-Stream in Echtzeit
|
||||||
|
|
||||||
### Choreografie
|
### Choreografie
|
||||||
|
|
||||||
@@ -84,8 +84,8 @@
|
|||||||
| FW-05 | Phase 1 | Complete |
|
| FW-05 | Phase 1 | Complete |
|
||||||
| AUD-01 | Phase 2 | Complete |
|
| AUD-01 | Phase 2 | Complete |
|
||||||
| AUD-02 | Phase 2 | Complete |
|
| AUD-02 | Phase 2 | Complete |
|
||||||
| AUD-03 | Phase 2 | Pending |
|
| AUD-03 | Phase 2 | Complete |
|
||||||
| AUD-04 | Phase 2 | Pending |
|
| AUD-04 | Phase 2 | Complete |
|
||||||
| CHR-04 | Phase 2 | Complete |
|
| CHR-04 | Phase 2 | Complete |
|
||||||
| CHR-05 | Phase 2 | Complete |
|
| CHR-05 | Phase 2 | Complete |
|
||||||
| CHR-01 | Phase 3 | Pending |
|
| CHR-01 | Phase 3 | Pending |
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ gsd_state_version: 1.0
|
|||||||
milestone: v1.0
|
milestone: v1.0
|
||||||
milestone_name: milestone
|
milestone_name: milestone
|
||||||
status: executing
|
status: executing
|
||||||
stopped_at: Completed 02-app-core-audio 02-04-PLAN.md
|
stopped_at: Completed 02-app-core-audio 02-05-PLAN.md
|
||||||
last_updated: "2026-04-03T12:52:07.259Z"
|
last_updated: "2026-04-03T12:52:14.694Z"
|
||||||
last_activity: 2026-04-03
|
last_activity: 2026-04-03
|
||||||
progress:
|
progress:
|
||||||
total_phases: 4
|
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]: 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]: 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]: 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
|
### Pending Todos
|
||||||
|
|
||||||
@@ -109,6 +110,6 @@ None yet.
|
|||||||
|
|
||||||
## Session Continuity
|
## Session Continuity
|
||||||
|
|
||||||
Last session: 2026-04-03T12:51:57.725Z
|
Last session: 2026-04-03T12:52:14.679Z
|
||||||
Stopped at: Completed 02-app-core-audio 02-04-PLAN.md
|
Stopped at: Completed 02-app-core-audio 02-05-PLAN.md
|
||||||
Resume file: None
|
Resume file: None
|
||||||
|
|||||||
107
.planning/phases/02-app-core-audio/02-05-SUMMARY.md
Normal file
107
.planning/phases/02-app-core-audio/02-05-SUMMARY.md
Normal file
@@ -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 |
|
||||||
Reference in New Issue
Block a user