Files
2026-05-27 23:29:53 +01:00

161 lines
7.7 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Design — audio-shipped-file-size-cleanup
This change is a pure mechanical refactor across three packages. The design is the **split strategy** per file: every public symbol stays exported from its current module path; the splits happen *below* the public surface.
---
## 1. `@suaveplan/audio-timeline` — `engine/engine.ts` (548 → ≤300)
`TimelineEngine` is the single class in this file and currently owns:
- Construction + adapter wiring (`setAdapter`, `dispose`)
- Track CRUD (`addTrack`, `removeTrack`, `updateTrack`, `setTracks`)
- Clip CRUD (`addClip`, `removeClip`, `updateClip`, `splitClip`, `joinClip`)
- Mixer mutations (`setMasterVolume`, `setTrackVolume`, `setTrackMute`, `setTrackSolo`, `setTrackPan`)
- Transport (`play`, `pause`, `stop`, `seek`, `setLoop`, `setTempo`)
- Snapshot / restore + undo/redo dispatch
- Event emission
### Proposed split
Keep `TimelineEngine` in `engine.ts` as the public class, but extract helper modules called from its methods:
```text
src/engine/
engine.ts ← class shell + delegating methods (≤300)
engine-tracks.ts ← addTrack/removeTrack/updateTrack/setTracks helpers
engine-tracks.md
engine-clips.ts ← addClip/removeClip/updateClip/splitClip/joinClip helpers
engine-clips.md
engine-mixer.ts ← setMasterVolume/setTrackVolume/Mute/Solo/Pan helpers
engine-mixer.md
engine-transport.ts ← play/pause/stop/seek/setLoop/setTempo helpers
engine-transport.md
engine.test.ts ← class-level invariants only (≤500)
engine-tracks.test.ts ← extracted track tests
engine-clips.test.ts ← extracted clip tests
engine-mixer.test.ts ← extracted mixer tests
engine-transport.test.ts ← extracted transport tests
```
Each helper module exports pure functions of `(state, args) => newState` (where `state` is `TimelineEngine`'s internal record) plus any side-effect-free derived values. `TimelineEngine` methods become 1–3 liners that call the helper and forward the result. This pattern matches how `audio-core`'s `process/` directory is structured.
The original `engine.test.ts` splits along the same four axes; the residual `engine.test.ts` keeps construction, adapter wiring, snapshot/restore, undo/redo dispatch, and event emission tests — the cross-cutting concerns no single helper owns.
---
## 2. `@suaveplan/audio-transport` — `transport/transport.ts` (419 → ≤300)
`Transport` is the single class and owns:
- Construction + clock injection
- BPM + time-signature + PPQ state
- Tick advance + lookahead scheduling loop
- Schedule / cancel API
- Loop region + swing
- Tempo automation
### Proposed split
```text
src/transport/
transport.ts ← class shell (≤300)
transport-schedule.ts ← schedule/cancel/lookahead helpers
transport-schedule.md
transport-tempo.ts ← BPM + tempo automation + tick conversion helpers
transport-tempo.md
transport.test.ts ← stays as-is (already under cap)
```
No test splitting needed — `transport.test.ts` is already within the 500-line cap.
---
## 3. `@suaveplan/audio-metadata` — `parsers/id3v2/id3v2.ts` (536 → ≤300)
`id3v2.ts` is the most over-budget src file. It owns:
- Magic-byte detection
- Header + extended-header parsing
- Frame iteration
- Per-frame-type decoders (TXXX, T..., COMM, USLT, APIC, UFID, WCOM, WCOP, …)
- Text-encoding decoders (ISO-8859-1, UTF-16, UTF-16BE, UTF-8)
- Unsynchronisation reversal
### Proposed split
```text
src/parsers/id3v2/
id3v2.ts ← public parseId3v2() + frame dispatch (≤300)
id3v2-header.ts ← header + extended-header parsing
id3v2-header.md
id3v2-frames-text.ts ← T... / TXXX text frame decoders
id3v2-frames-text.md
id3v2-frames-binary.ts ← APIC / UFID / GEOB binary frame decoders
id3v2-frames-binary.md
id3v2-frames-url.ts ← W... URL frame decoders
id3v2-frames-url.md
id3v2-text-encoding.ts ← ISO-8859-1 / UTF-16 / UTF-16BE / UTF-8 decoders
id3v2-text-encoding.md
id3v2-unsync.ts ← unsynchronisation reversal
id3v2-unsync.md
id3v2.test.ts ← integration parses on real fixture files (≤500)
id3v2-header.test.ts ← header-specific
id3v2-frames-text.test.ts ← text-frame-specific
id3v2-frames-binary.test.ts ← binary-frame-specific
id3v2-frames-url.test.ts ← url-frame-specific
id3v2-text-encoding.test.ts ← encoding-specific
id3v2-unsync.test.ts ← unsync-specific
```
The split mirrors the spec sections of the ID3v2.4 standard, so the file boundaries match how future maintainers will reason about the format.
### `id3v2.test.ts` (1340 → 7 files of ≤500 each)
The current `id3v2.test.ts` mixes header tests, every frame type, encoding round-trips, and integration parses. Splitting by frame family (per the source split above) gives ~150–200 lines per file. The residual `id3v2.test.ts` keeps the end-to-end "parse a real fixture file" cases.
---
## 4. `@suaveplan/audio-metadata` — `parsers/mp4-atom/mp4-atom.ts` (351 → ≤300)
Lightest split. The file owns atom-walking + per-atom decoders (`ftyp`, `moov`, `mvhd`, `mdat`, `ilst` and the iTunes metadata sub-atoms). Extract the iTunes-specific decoders:
```text
src/parsers/mp4-atom/
mp4-atom.ts ← public parseMp4Atom() + container walker (≤300)
mp4-atom-itunes.ts ← iTunes/ilst metadata decoders
mp4-atom-itunes.md
mp4-atom.test.ts ← container + non-iTunes tests (≤500)
mp4-atom-itunes.test.ts ← iTunes-specific
```
---
## 5. `@suaveplan/audio-metadata` — `parsers/ogg-vorbis/ogg-vorbis.test.ts` (803 → ≤500)
Source file is already within cap. Only the test file needs splitting:
```text
src/parsers/ogg-vorbis/
ogg-vorbis.ts ← unchanged
ogg-vorbis.test.ts ← page parsing + comment header (≤500)
ogg-vorbis-frames.test.ts ← extracted frame-level tests
```
---
## 6. Cross-cutting rules for the split
- **Public surface is invariant.** Every existing `export` from `<pkg>/src/index.ts` must continue to resolve to the same symbol with the same signature. The split is *internal*.
- **Subpath exports unchanged.** The `package.json` `exports` map for each package keeps every existing entry. New internal helper modules are NOT added to `exports` — they are implementation details.
- **Barrels use explicit named re-exports** (§6a). No `export * from` in new feature barrels.
- **Sibling `.md` files** added for every new `.ts` per §4 (200-word minimum, six required sections).
- **Coverage stays 100/100/100/100** per `vitest.config.ts` thresholds.
- **No new behavior.** A diff of `dist/*.d.ts` must show only path changes — no type-shape changes.
## 7. Risk
Low. The packages have no published consumers; the refactor is mechanical; the tests are the contract and they all must continue to pass. The largest file (`id3v2.test.ts` at 1340 lines) is the highest-leverage win: shrinking it makes the format-spec correspondence explicit and gives future maintainers per-frame-family focus.
The one non-mechanical risk is in `audio-timeline/engine.ts` — extracting helper modules implies extracting a `TimelineEngineState` shape, which is currently inlined as private class fields. The helpers operate on that shape; if the extraction inadvertently widens the visibility of private state, the public surface widens. Mitigation: keep `TimelineEngineState` as a non-exported type in `engine-state.ts` (or inline in `engine.ts`) and pass it explicitly to helpers; do not export it.