# Design: audio-clip-playlist ## Context The current `FLPBuilder` emits pattern-based playlist items (single 32-byte `ArrangementItem`) for all tracks. Melodic tracks with `source_type="audio"` need a different encoding: 3 consecutive 32-byte items per clip, with fixed indices `0x3FF0` / `0x8080` / `0x6440`. --- ## Architecture Decisions | Decision | Choice | Rationale | |----------|--------|-----------| | Polymorphism | `to_items()` returns `list[bytes]` (3 items) vs `to_bytes()` returns `bytes` (1 item) | Avoids union-type juggling; builder collects all items uniformly | | `source_type` default | `"pattern"` | Existing callers unchanged; audio is opt-in | | Audio clip item indices | Treat `0x8080`/`0x6440`/`0x3FF0` as constants | Per-proposal risk mitigation; verified against `audio_clip_reference.flp` | | ChSamplePath correlation | Audio clip item precedes its `ChSamplePath` event in channel data | Builder writes items first, then channel events with sample paths already handled by `ChannelSkeletonLoader` | --- ## File Changes | File | Action | |------|--------| | `src/flp_builder/arrangement.py` | Add `AudioClipItem`, constants `AUDIO_CLIP_HEADER_INDEX`, `AUDIO_CLIP_DATA_INDEX` | | `src/flp_builder/schema.py` | Add `source_type: str = "pattern"` field to `MelodicTrack` | | `src/flp_builder/builder.py` | Route on `track.source_type`; append `AudioClipItem` objects to arrangement items list | --- ## Binary Format Each audio clip placement = **96 bytes** (3 × 32): ### Item 0 — PATTERN positioning ``` struct.pack(" list[bytes]: """Return 3 consecutive 32-byte items: [PATTERN, HEADER, DATA].""" ... # Convenience: to_bytes() returns concatenation for polymorphic use def to_bytes(self, ppq: int = PPQ_DEFAULT, max_tracks: int = MAX_TRACKS_DEFAULT) -> bytes: return b"".join(self.to_items(ppq, max_tracks)) ``` --- ## Schema Change ```python # src/flp_builder/schema.py — MelodicTrack @dataclass class MelodicTrack: role: str sample_path: str notes: list[MelodicNote] channel_index: int volume: float = 0.85 pan: float = 0.0 source_type: str = "pattern" # NEW: "pattern" | "audio" ``` `SongDefinition.validate()` also validates: - `source_type in ("pattern", "audio")` - If `source_type == "audio"`: `channel_index >= 17` --- ## Builder Routing ```python # src/flp_builder/builder.py — _build_arrangement def _build_arrangement(self, song, track_data_template): items: list[ArrangementItem | AudioClipItem] = [] # Pattern tracks → ArrangementItem (existing) for item in song.items: items.append(ArrangementItem( pattern_id=item.pattern, bar=item.bar, num_bars=item.bars, track_index=item.track - 1, muted=item.muted, )) # Melodic tracks → route on source_type drum_pattern_count = len(song.patterns) max_drum_track = max((it.track for it in song.items), default=1) for i, mt in enumerate(song.melodic_tracks): pattern_id = drum_pattern_count + i + 1 track_index = max_drum_track + i # 0-based if mt.source_type == "audio": items.append(AudioClipItem( pattern_id=pattern_id, bar=mt.bar, # from MelodicTrack.bar (default 0.0) num_bars=mt.num_bars, # from MelodicTrack.num_bars (default 4.0) track_index=track_index, muted=False, )) else: # "pattern" items.append(ArrangementItem( pattern_id=pattern_id, bar=mt.bar or 0.0, num_bars=mt.num_bars or 4.0, track_index=track_index, muted=False, )) return build_arrangement_section(items, track_data_template, ppq=song.meta.ppq) ``` **Note**: `MelodicTrack` currently has no `bar`/`num_bars` fields. The proposal scope does not include adding them; for now audio clip items use `bar=0.0, num_bars=4.0` as placeholders until a future change extends `MelodicTrack` with timeline placement fields. --- ## ChSamplePath Correlation `AudioClipItem` places a reference on the playlist. The actual sample file path is carried by `ChSamplePath` events (ID 196) in the channel data, already handled by `ChannelSkeletonLoader.load(melodic_map=...)` which patches sampler channels with the correct `.wav` paths. No additional correlation is needed: the channel index on the melodic track (e.g., ch17) maps to the same channel that carries the `ChSamplePath` event. The builder ensures channel events precede arrangement events in the FLP binary. --- ## Validation Strategy Binary diff against `audio_clip_reference.flp`: 1. Generate a minimal FLP with one audio clip item 2. Extract playlist bytes (ID 233 data, after `encode_data_event` wrapper) 3. Compare first 96 bytes of playlist data against reference 4. Specifically verify: - Bytes 0–31: pattern positioning (`PATTERN_BASE = 0x5000`) - Bytes 32–63: header (`item_index = 0x3FF0`) - Bytes 64–95: data (`pattern_base = 0x6440`, `item_index = 0x8080`) If mismatch at 0x8080/0x6440: derive values from reference via `build_track_data_template`-style extraction, then update constants. --- ## Open Questions | Question | Status | |----------|--------| | `MelodicTrack.bar`/`num_bars` fields needed? | Not in scope; placeholders used. Future change to add placement fields. | | Index pool (0x8080/0x6440) per-project vs constant? | Treated as constants per proposal risk mitigation. If FLP fails to open, switch to derivation. |