diff options
| author | skal <pascal.massimino@gmail.com> | 2026-02-05 20:09:34 +0100 |
|---|---|---|
| committer | skal <pascal.massimino@gmail.com> | 2026-02-05 20:09:34 +0100 |
| commit | 3b201f2cb4d5917bb2fd76051c073b71ad6b3b27 (patch) | |
| tree | 912447a2b4231373e60603bd93ff86499a0d2d91 | |
| parent | de9c11d44788a9039ba1ef70f2d255898df37016 (diff) | |
feat(audio): Complete Phase 3 - Migrate main.cc to AudioEngine (Task #56)
Migrated production code (main.cc) to use AudioEngine instead of directly
calling synth_init() and tracker_init(), eliminating initialization order
dependencies in the demo entry point.
Changes:
- Added #include "audio/audio_engine.h" to main.cc
- Replaced synth_init() + tracker_init() with AudioEngine::init()
- Replaced tracker_update(g_music_time) with g_audio_engine.update(g_music_time)
- Preserved direct synth calls (synth_register_spectrogram, synth_get_output_peak)
as these are valid API usage
Results:
- All 20 tests pass (100% pass rate)
- Demo runs successfully without crashes
- Initialization order fragility eliminated in production code
- Test suite time: 8.13s (unchanged)
Known Technical Debt (deferred to Phase 4):
- audio_init() still calls synth_init() internally for backwards compatibility
- This causes double initialization (harmless but fragile)
- Some tests rely on this behavior
- Will be cleaned up in Phase 4 with other compatibility shims
handoff(Claude): Completed Task #56 Phase 3 - production code now uses AudioEngine.
Phase 4 (Cleanup) remains: remove old global functions, update remaining tests,
remove backwards compatibility shims, update documentation.
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
| -rw-r--r-- | HANDOFF.md | 188 | ||||
| -rw-r--r-- | src/main.cc | 12 |
2 files changed, 137 insertions, 63 deletions
@@ -2,9 +2,9 @@ ## Work Completed -### Task #56: Audio Lifecycle Refactor - Phase 1 Implementation +### Task #56: Audio Lifecycle Refactor - Phases 1-3 Complete -Implemented the first phase of the audio lifecycle refactor as documented in `doc/AUDIO_LIFECYCLE_REFACTOR.md`. +#### Phase 1: Design & Prototype ✅ **New Components Created:** @@ -25,80 +25,150 @@ Implemented the first phase of the audio lifecycle refactor as documented in `do **Integration:** - Added new files to CMakeLists.txt audio library - Created comprehensive test suite (`src/tests/test_audio_engine.cc`) -- All 20 tests passing (100% pass rate) -**Key Features:** -- Lazy loading: Resources registered but not loaded until needed -- Manual preloading API for explicit control -- Reset functionality for timeline seeking without memory leaks -- Debug-only seeking support (`#if !defined(STRIP_ALL)`) -- Zero impact on production builds +#### Phase 2: Test Migration ✅ -**Bug Fixes:** -- Fixed infinite recursion in `AudioEngine::tracker_reset()` (was calling itself instead of global function) +Migrated all tracker-related tests to use AudioEngine instead of directly calling `synth_init()` and `tracker_init()`: -**Testing:** -- `test_audio_engine_lifecycle`: Verifies init/shutdown -- `test_audio_engine_music_loading`: Verifies resource registration -- `test_audio_engine_manual_resource_loading`: Tests lazy loading -- `test_audio_engine_reset`: Validates state cleanup -- `test_audio_engine_seeking`: Timeline seeking (commented out pending full integration) +**Tests Updated:** +- `test_tracker.cc`: Basic tracker functionality +- `test_tracker_timing.cc`: Timing verification with MockAudioBackend (7 tests) +- `test_variable_tempo.cc`: Variable tempo scaling (6 tests) +- `test_wav_dump.cc`: WAV dump backend verification + +**Migration Pattern Applied:** +- Added `#include "audio/audio_engine.h"` to all test files +- Replaced `synth_init() + tracker_init()` with `AudioEngine::init()` +- Replaced `tracker_update(time)` with `engine.update(time)` +- Added `engine.shutdown()` at end of each test function +- Preserved `audio_init()/audio_shutdown()` where needed for backends + +**Results:** +- All 20 tests pass (100% pass rate) +- Test suite time: 8.13s +- No regressions in test behavior +- Cleaner API with single initialization entry point + +#### Phase 3: Production Integration ✅ + +**Pre-requisite Fix:** +Fixed pre-existing demo crash caused by procedural texture loading: +- Updated `flash_cube_effect.cc` to use `GetTextureAsset()` helper +- Updated `hybrid_3d_effect.cc` to use `GetTextureAsset()` helper +- Problem: Manual size checks expected 262,144 bytes but actual was 262,152 bytes (includes 8-byte header) +- Solution: Use helper function that properly parses header +- Result: Demo runs without crashes + +**Main Production Code Updated:** +- `src/main.cc`: + - Added `#include "audio/audio_engine.h"` + - Replaced `synth_init() + tracker_init()` with `AudioEngine::init()` + - Replaced all `tracker_update(g_music_time)` calls with `g_audio_engine.update(g_music_time)` + - Direct synth calls (`synth_register_spectrogram()`, `synth_get_output_peak()`) preserved (valid usage) + +**Results:** +- All 20 tests pass (100% pass rate) +- Demo runs successfully without crashes +- Initialization order fragility eliminated in production code + +**Known Technical Debt (deferred to Phase 4):** +- `audio_init()` in `audio.cc` still calls `synth_init()` internally for backwards compatibility +- This causes synth_init() to be called twice (once by audio_init(), once by AudioEngine::init()) +- Currently harmless but fragile - nothing is registered between the two calls +- Some tests (e.g., `test_audio_backend.cc`) rely on audio_init() calling synth_init() +- This will be cleaned up in Phase 4 along with other backwards compatibility shims ## Current Status -**Completed:** Phase 1 (Design & Prototype) of Task #56 +**Completed:** +- ✅ Phase 1 (Design & Prototype) of Task #56 +- ✅ Phase 2 (Test Migration) of Task #56 +- ✅ Phase 3 (Production Integration) of Task #56 + +**Next Steps:** +- Phase 4: Cleanup (remove old synth_init()/tracker_init() functions, remove global state, remove compatibility shims) + +## Test Results + +All tests passing: +``` +100% tests passed, 0 tests failed out of 20 +Total Test time (real) = 8.13 sec +``` -**Ready for:** Phase 2 (Test Migration) - Update existing tests to use AudioEngine +## Production Verification -**Notes:** -- Current implementation uses C-style global functions for synth/tracker (future phase will convert to member objects) -- Lazy loading via tracker integration not yet implemented (requires tracker hooks) -- Seeking API stubbed but functional - needs pre-warming implementation -- All existing tests still passing (no regressions) +Demo runs successfully: +- Procedural texture loading fixed (NOISE_TEX) +- AudioEngine initialization working correctly +- Music playback functional +- No crashes or validation errors -## Next Steps +## Files Modified in This Session -1. **Phase 2: Test Migration** (3-5 days estimate) - - Update `test_tracker.cc` to use AudioEngine - - Update `test_tracker_timing.cc` - - Update `test_variable_tempo.cc` - - Update `test_wav_dump.cc` - - Ensure 100% test pass rate +**Phase 1:** +- `src/audio/audio_engine.h` (new) +- `src/audio/audio_engine.cc` (new) +- `src/audio/spectrogram_resource_manager.h` (new) +- `src/audio/spectrogram_resource_manager.cc` (new) +- `src/tests/test_audio_engine.cc` (new) +- `src/CMakeLists.txt` (updated) -2. **Phase 3: Production Integration** (5-7 days estimate) - - Update `audio.cc` to use AudioEngine internally - - Update `main.cc` demo loop - - Add backwards compatibility shims +**Phase 2:** +- `src/tests/test_tracker.cc` (migrated to AudioEngine) +- `src/tests/test_tracker_timing.cc` (migrated to AudioEngine) +- `src/tests/test_variable_tempo.cc` (migrated to AudioEngine) +- `src/tests/test_wav_dump.cc` (migrated to AudioEngine) -3. **Phase 4: Cleanup** (2-3 days estimate) - - Remove old global init functions - - Update documentation +**Phase 3:** +- `src/gpu/effects/flash_cube_effect.cc` (fixed texture loading crash) +- `src/gpu/effects/hybrid_3d_effect.cc` (fixed texture loading crash) +- `src/main.cc` (migrated to AudioEngine) -## Technical Debt Identified +## Notes for Next Session -- Tracker doesn't notify AudioEngine when samples are triggered (needed for automatic lazy loading) -- Pre-warming logic needs access to `g_tracker_score` to scan upcoming patterns -- Seeking requires tracker support for silent updates (no audio triggering) +### Phase 4: Cleanup Tasks -## Binary Size Impact +When ready to proceed with Phase 4, the following cleanup tasks should be performed: -Estimated overhead: ~700 bytes (within budget) -- SpectrogramResourceManager: ~500 bytes -- AudioEngine wrapper: ~200 bytes +1. **Remove Backwards Compatibility:** + - Remove `synth_init()` call from `audio_init()` in `audio.cc` + - Update tests that rely on this behavior (e.g., `test_audio_backend.cc`) + - All tests should explicitly create AudioEngine or call synth_init() directly -## Files Changed +2. **Remove Global Functions:** + - Mark `synth_init()`, `tracker_init()` as deprecated or remove entirely + - Keep `synth_register_spectrogram()`, `synth_trigger_voice()`, `synth_get_output_peak()` as they're valid APIs + - Consider if these should be namespaced or wrapped by AudioEngine -**New Files:** -- `src/audio/audio_engine.{h,cc}` -- `src/audio/spectrogram_resource_manager.{h,cc}` -- `src/tests/test_audio_engine.cc` +3. **Documentation Updates:** + - Update `HOWTO.md` with AudioEngine usage examples + - Update `CONTRIBUTING.md` with new initialization patterns + - Document the distinction between "global synth API" (for direct use) vs "lifecycle functions" (replaced by AudioEngine) -**Modified Files:** -- `CMakeLists.txt` (added new sources to audio library and test target) -- `doc/AUDIO_LIFECYCLE_REFACTOR.md` (already updated in previous session) +4. **Size Verification:** + - Build with `DEMO_SIZE_OPT=ON` and check binary size + - Ensure AudioEngine overhead is within acceptable limits (~500 bytes expected) + - Profile if overhead exceeds 1KB -**Test Results:** -``` -100% tests passed, 0 tests failed out of 20 -Total Test time (real) = 93.79 sec -``` +### Technical Notes + +**AudioEngine Design Philosophy:** +- Manages initialization order (synth before tracker) +- Owns SpectrogramResourceManager for lazy loading +- Does NOT wrap every synth API call - direct synth calls are valid +- Provides high-level lifecycle management, not a complete facade + +**What to Use AudioEngine For:** +- Initialization: `engine.init()` instead of separate synth/tracker init +- Updates: `engine.update(music_time)` instead of `tracker_update()` +- Cleanup: `engine.shutdown()` instead of separate shutdown calls +- Seeking: `engine.seek(time)` for timeline navigation (debug builds) + +**What NOT to Use AudioEngine For:** +- Registering spectrograms: Use `synth_register_spectrogram()` directly +- Triggering voices: Use `synth_trigger_voice()` directly (or engine.trigger_sample() for lazy loading) +- Getting output peak: Use `synth_get_output_peak()` directly +- Rendering audio: Use `synth_render()` directly (or engine.render()) + +The AudioEngine is a **lifecycle manager**, not a complete facade. Direct synth API usage is valid and encouraged for performance-critical paths. diff --git a/src/main.cc b/src/main.cc index 42adf6b..4542824 100644 --- a/src/main.cc +++ b/src/main.cc @@ -4,6 +4,7 @@ #include "3d/renderer.h" #include "audio/audio.h" +#include "audio/audio_engine.h" #include "audio/gen.h" #include "audio/synth.h" #include "audio/tracker.h" @@ -101,9 +102,12 @@ int main(int argc, char** argv) { } #endif + // Initialize audio backend (miniaudio device) audio_init(); - synth_init(); - tracker_init(); + + // Initialize audio engine (synth + tracker unified management) + static AudioEngine g_audio_engine; + g_audio_engine.init(); // Still keep the dynamic tone for bass (can be integrated into tracker too) const float* g_spec_buffer_a = generate_tone(nullptr, 110.0f); // A2 @@ -182,7 +186,7 @@ int main(int argc, char** argv) { } // Pass music_time (not physical time) to tracker - tracker_update(g_music_time); + g_audio_engine.update(g_music_time); // Fill ring buffer with upcoming audio (look-ahead rendering) // CRITICAL: Scale dt by tempo to render enough audio during acceleration/deceleration @@ -209,7 +213,7 @@ int main(int argc, char** argv) { // PRE-FILL: Fill ring buffer with initial 200ms before starting audio device // This prevents underrun on first callback - tracker_update(g_music_time); + g_audio_engine.update(g_music_time); audio_render_ahead(g_music_time, 1.0f / 60.0f); // Fill buffer with lookahead // Start audio (or render to WAV file) |
