summaryrefslogtreecommitdiff
path: root/doc/archive/AUDIO_LIFECYCLE_REFACTOR.md
diff options
context:
space:
mode:
Diffstat (limited to 'doc/archive/AUDIO_LIFECYCLE_REFACTOR.md')
-rw-r--r--doc/archive/AUDIO_LIFECYCLE_REFACTOR.md105
1 files changed, 105 insertions, 0 deletions
diff --git a/doc/archive/AUDIO_LIFECYCLE_REFACTOR.md b/doc/archive/AUDIO_LIFECYCLE_REFACTOR.md
new file mode 100644
index 0000000..f5e0045
--- /dev/null
+++ b/doc/archive/AUDIO_LIFECYCLE_REFACTOR.md
@@ -0,0 +1,105 @@
+# Audio System Architecture
+
+## Problem Statement
+
+The legacy audio system had a fragile initialization order dependency: `tracker_init()` had to be called after `synth_init()` because the synth would clear all registered spectrograms. This was brittle, non-obvious, and made testing difficult, as tests needed to respect this internal implementation detail.
+
+## Implemented Solution: The AudioEngine
+
+To solve this, the audio system was refactored into a unified, lifecycle-managed architecture centered around two new classes: `AudioEngine` and `SpectrogramResourceManager`. This design eliminates initialization-order dependencies and provides clear ownership of resources.
+
+### Architecture Overview
+
+The final architecture separates responsibilities into distinct, manageable components:
+
+```
+ ┌─────────────────┐
+ │ AudioEngine │
+ │ (Facade) │
+ └────────┬────────┘
+ │
+ ┌─────────────────┼─────────────────┐
+ │ │ │
+ ┌──────▼──────┐ ┌─────▼──────┐ ┌─────▼────────┐
+ │ Synth │ │ Tracker │ │ Resource │
+ │ │ │ │ │ Manager │
+ │ (Playback) │ │ (Sequence) │ │ (Loading) │
+ └─────────────┘ └────────────┘ └──────┬───────┘
+ │
+ ┌──────────┼──────────┐
+ │ │
+ ┌──────▼──────┐ ┌──────▼──────┐
+ │ AssetManager│ │ Procedural │
+ │ (Assets) │ │ Generator │
+ └─────────────┘ └─────────────┘
+```
+
+### 1. AudioEngine
+
+The `AudioEngine` acts as a high-level facade for the entire audio subsystem. It is the single entry point for initialization, updates, and shutdown.
+
+**Responsibilities:**
+- Manages the lifecycle of the `Synth`, `Tracker`, and `SpectrogramResourceManager`.
+- Guarantees the correct initialization order internally.
+- Provides a unified API for updating the audio state (`update()`) and seeking (`seek()`).
+- Abstracts away the complexity of resource loading and synth registration.
+
+**Before (Fragile Initialization):**
+```cpp
+// In main.cc or tests
+synth_init();
+tracker_init();
+// ...
+tracker_update(music_time);
+```
+
+**After (Robust Initialization):**
+```cpp
+// In main.cc or tests
+#include "audio/audio_engine.h"
+
+AudioEngine g_audio_engine;
+g_audio_engine.init();
+// ...
+g_audio_engine.update(music_time);
+```
+
+### 2. SpectrogramResourceManager
+
+This class centralizes all resource loading and memory management for spectrograms, for both binary assets and procedurally generated notes.
+
+**Responsibilities:**
+- **Loading:** Handles loading `.spec` files from the `AssetManager` and generating procedural spectrograms from `NoteParams`.
+- **Ownership:** Enforces clear memory ownership rules. It *borrows* pointers to static assets but *owns* the memory for any generated procedural spectrograms, which it frees upon shutdown.
+- **Lazy Loading & Caching:** Resources are not loaded when the application starts. Instead, their metadata is registered. The actual data is loaded on-demand the first time a sample is needed. Once loaded, it is cached for future use. This results in faster startup times and more efficient memory usage.
+
+---
+
+## Lazy Loading and Pre-warming Strategy
+
+The system uses a lazy-loading approach to minimize startup delay and memory footprint.
+
+**Workflow:**
+1. **`AudioEngine::init()`**: The resource manager is initialized, but no samples are loaded.
+2. **`AudioEngine::load_music_data()`**: The tracker score is parsed, and metadata for all required samples (both asset-based and procedural) is *registered* with the `SpectrogramResourceManager`. No data is loaded at this stage.
+3. **`AudioEngine::update()`**: During the main loop, the tracker identifies which samples will be needed in the near future (a 1-2 second lookahead window). It then asks the resource manager to "pre-warm" these samples, loading them into the cache just before they are needed.
+4. **Triggering a sample**: When a tracker event fires, the `AudioEngine` requests the sample from the resource manager. Since it was pre-warmed, it's a fast cache hit. The engine then ensures the spectrogram is registered with the low-level synth and triggers the voice.
+
+**Benefits:**
+- **Fast Startup:** The application doesn't pay the cost of loading all audio assets up front.
+- **No Stutter:** Pre-warming ensures that samples are already in memory when they need to be played, preventing stutter caused by load-on-trigger.
+- **Memory Efficient:** Only the samples that are actively being used or are coming up soon are held in memory.
+
+---
+
+## Timeline Seeking & Scrubbing (For Debugging)
+
+A key feature enabled by this refactor is robust timeline seeking, which is invaluable for development and debugging. The `AudioEngine::seek()` method (`#if !defined(STRIP_ALL)`) allows jumping to any point in the demo timeline.
+
+**`seek(target_time)` Process:**
+1. **Reset:** The synth and tracker states are completely reset, clearing all active voices and pattern states.
+2. **State Reconstruction:** The tracker re-scans the musical score from the beginning up to `target_time` to determine which patterns should be active.
+3. **Pre-warming:** The resource manager pre-loads all samples needed for the time range around `target_time`.
+4. **Ready:** The audio system is now in the exact state it would have been if the demo had run normally up to `target_time`, ready for playback to resume.
+
+This allows developers to instantly jump to a specific scene to debug audio or visual issues without having to watch the demo from the start.