Skip to content

Commit e01b6ab

Browse files
authored
Merge pull request #51 from boostcampwm-snu-2026-1/docs/plan-persistence-length-export-features
docs: plan persistence length and export features
2 parents 5fa802f + c48051a commit e01b6ab

11 files changed

Lines changed: 418 additions & 14 deletions

PLANS.md

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ M1 should include:
2929

3030
Use this section as the current execution order for agent work. Feature document numbering describes the planned product sequence, but actual issue order may change as dependencies, review feedback, or implementation risks become clearer.
3131

32-
1. #41 Arrangement clip placement and playback -> `docs/features/16-arrangement-clip-placement-and-playback.md`
33-
2. #43 Mixer audio routing and track controls -> `docs/features/17-mixer-audio-routing-and-track-controls.md`
32+
1. #43 Mixer audio routing and track controls -> `docs/features/17-mixer-audio-routing-and-track-controls.md`
33+
2. #47 IndexedDB project persistence -> `docs/features/18-indexeddb-project-persistence.md`
34+
3. #48 Variable hybrid clip length -> `docs/features/19-variable-hybrid-clip-length.md`
35+
4. #49 Adjustable arrangement length -> `docs/features/20-adjustable-arrangement-length.md`
36+
5. #50 Arrangement WAV export -> `docs/features/21-arrangement-wav-export.md`
3437

3538
## Planned Milestones
3639

@@ -51,10 +54,12 @@ Use this section as the current execution order for agent work. Feature document
5154
15. WAV file import as audio clip.
5255
16. Arrangement clip placement and playback.
5356
17. Mixer audio routing and track controls.
54-
18. Project export/import.
55-
19. IndexedDB autosave.
56-
20. Arrangement data model refinements.
57-
21. Basic effects.
57+
18. IndexedDB project persistence.
58+
19. Variable hybrid clip length.
59+
20. Adjustable arrangement length.
60+
21. Arrangement WAV export.
61+
22. Project JSON export/import.
62+
23. Basic effects.
5863

5964
## Backlog
6065

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ Feature task specs:
6969
- [15 WAV File Import as Audio Clip](docs/features/15-wav-file-import-as-audio-clip.md)
7070
- [16 Arrangement Clip Placement and Playback](docs/features/16-arrangement-clip-placement-and-playback.md)
7171
- [17 Mixer Audio Routing and Track Controls](docs/features/17-mixer-audio-routing-and-track-controls.md)
72+
- [18 IndexedDB Project Persistence](docs/features/18-indexeddb-project-persistence.md)
73+
- [19 Variable Hybrid Clip Length](docs/features/19-variable-hybrid-clip-length.md)
74+
- [20 Adjustable Arrangement Length](docs/features/20-adjustable-arrangement-length.md)
75+
- [21 Arrangement WAV Export](docs/features/21-arrangement-wav-export.md)
7276

7377
## Initial Roadmap
7478

@@ -81,7 +85,9 @@ Feature task specs:
8185
7. Import local WAV files as audio clips.
8286
8. Place clips on the arrangement timeline and play the arranged song.
8387
9. Connect mixer faders, mute, solo, and meters to real audio routing.
84-
10. Add project export/import and persistence features.
88+
10. Add browser-local project persistence with IndexedDB.
89+
11. Support longer hybrid clips and adjustable arrangement length.
90+
12. Render the arrangement to a downloadable WAV file.
8591

8692
## Notes
8793

docs/architecture.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ The main rule is separation of concerns: UI rendering, persistence, and audio sc
1515
- Display audio state such as stopped, playing, paused, and playhead position.
1616
- Render song-level UI shells such as arrangement and mixer panels.
1717
- Let users place, move, select, and delete arrangement clip instances.
18+
- Let users adjust clip and arrangement lengths through model-backed controls.
1819
- Dispatch mixer control edits such as volume, mute, solo, and master volume.
20+
- Trigger save, restore, import, and export workflows through persistence/audio APIs.
1921
- Use `requestAnimationFrame` for visual playheads where needed.
2022
- Avoid owning exact audio timing.
2123

@@ -24,6 +26,7 @@ The main rule is separation of concerns: UI rendering, persistence, and audio sc
2426
- Define serializable project data types.
2527
- Store musical time in ticks.
2628
- Provide pure transformations for creating, editing, duplicating, and deleting clips and events.
29+
- Store clip length and arrangement length as serializable musical values.
2730
- Provide pure transformations for creating, moving, and deleting arrangement clip instances.
2831
- Store serializable mixer settings such as track volume, mute, solo, and master volume when mixer routing exists.
2932
- Avoid references to Web Audio runtime objects.
@@ -36,6 +39,7 @@ The main rule is separation of concerns: UI rendering, persistence, and audio sc
3639
- Schedule audio against `AudioContext.currentTime`.
3740
- Schedule arrangement playback from placed clip instances when `SONG` mode is active.
3841
- Own mixer routing, track gain nodes, master gain, runtime meters, and effect nodes when those features exist.
42+
- Provide an offline rendering path for arrangement WAV export when that feature exists.
3943
- Expose a small typed API to the UI and feature code.
4044
- Never depend on React components.
4145

@@ -46,7 +50,8 @@ The main rule is separation of concerns: UI rendering, persistence, and audio sc
4650
- Store sample references by stable IDs or metadata, not decoded buffers.
4751
- Handle browser file import boundaries for user-provided audio files.
4852
- Keep `File`, `Blob`, object URL, IndexedDB handles, and decoded audio buffers out of model data.
49-
- Later, support local storage or IndexedDB.
53+
- Persist the active browser project and imported sample blobs through IndexedDB.
54+
- Export rendered arrangement audio as WAV without storing runtime audio objects in project JSON.
5055

5156
### Utilities
5257

@@ -79,12 +84,14 @@ src/
7984

8085
## Runtime vs Serializable Data
8186

82-
Serializable data includes projects, tracks, clips, clip instances, drum events, note events, sample metadata, tempo, and time signature.
87+
Serializable data includes projects, tracks, clips, clip instances, drum events, note events, sample metadata, tempo, time signature, clip length, arrangement length, arrangement loop ranges, and mixer settings.
8388

8489
Runtime data includes `AudioContext`, `AudioBuffer`, audio nodes, scheduler timers, decoded sample caches, and currently playing source nodes. Runtime data must not be written into project JSON.
8590

8691
Imported browser files are also runtime or persistence-layer data. Project JSON may reference imported audio by stable sample IDs and metadata such as file name, MIME type, and duration, but it must not embed `File`, `Blob`, object URL, or decoded PCM data.
8792

93+
IndexedDB may store imported sample blobs or bytes outside the project JSON document. The model should reference those blobs by stable sample IDs so the audio engine can rebuild decoded runtime caches after restore.
94+
8895
Mixer settings such as volume, mute, solo, and master volume are serializable project or app-model data once real mixer routing exists. Mixer runtime data, including `GainNode`, `AnalyserNode`, effect nodes, meter buffers, and active routing graphs, belongs to the audio engine runtime and must not be stored in project JSON.
8996

9097
Arrangement placement data is serializable. A placed clip should be represented by a `ClipInstance` with stable IDs, `startTick`, `lengthTicks`, and track membership. Drag state, pointer coordinates, DOM measurements, scheduler timers, decoded buffers, and active audio nodes are runtime-only and must not be persisted.

docs/audio-engine.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ In the browser implementation, decoded buffers should live in the audio engine r
3434

3535
Imported WAV files should follow the same runtime rule. The file picker may provide a `File` or `Blob`, but the audio engine should only keep decoded buffers in a runtime cache keyed by a stable imported sample ID. Project JSON stores metadata such as file name, MIME type, and duration, not the `File`, `Blob`, object URL, or `AudioBuffer`.
3636

37-
Until IndexedDB or another persistence layer stores imported file bytes, imported audio clips may be session-only. The audio engine should fail clearly if project metadata references an imported sample whose runtime file data is no longer available.
37+
IndexedDB persistence may store imported file bytes outside project JSON. After restore, the audio engine should rebuild decoded buffers from those persistent blobs on demand. If project metadata references an imported sample whose bytes are unavailable, the engine should fail clearly instead of silently skipping playback.
3838

3939
## Imported Audio Clip Playback
4040

@@ -65,7 +65,7 @@ Arrangement playback should:
6565
- Respect `clipInstance.lengthTicks` as the visible and playable duration boundary.
6666
- Keep play, pause, resume, and stop behavior separate from React render timing.
6767

68-
The first arrangement playback implementation may play linearly from tick 0 through the end of the last placed clip instance, then stop. Arrangement loop regions can be added later.
68+
Arrangement playback should derive its outer bounds from arrangement state, such as `arrangement.lengthBars`, when that state exists. Before adjustable arrangement length exists, a fixed or inferred arrangement range is acceptable if it is documented in the feature PR.
6969

7070
The current first pass reuses the existing lookahead loop scheduler for `SONG` mode by setting a loop range that covers the visible arrangement. This keeps scheduling independent from React and enables pause/resume with the existing transport API, but it is not yet a true one-shot linear song transport. A later transport task should add non-looping arrangement playback that stops at the arrangement end.
7171

@@ -75,6 +75,22 @@ Imported audio clips should play at original speed unless a later time-stretchin
7575

7676
Arrangement playback still uses the lookahead scheduler. UI drag state, arrangement DOM geometry, visual playheads, decoded buffers, and active source nodes remain runtime-only.
7777

78+
## Offline WAV Export
79+
80+
Arrangement WAV export should use an offline audio rendering path, not the live React UI or visual playhead.
81+
82+
The first export target should be WAV because the browser can produce it from PCM data without a compressed-audio encoder dependency. A predictable first format is stereo 44.1 kHz 16-bit PCM WAV unless implementation constraints justify another choice in the PR.
83+
84+
Export rendering should:
85+
86+
- Render from arrangement tick 0 through the configured arrangement length.
87+
- Use the same tick-to-audio-time conversion rules as live playback.
88+
- Include the clip types, instruments, and mixer routing available at the time of implementation.
89+
- Block with a clear error when required sample data is missing.
90+
- Avoid mutating live transport state, active source nodes, or React component state during rendering.
91+
92+
WAV encoding can be a small utility that converts rendered PCM into a Blob. MP3, FLAC, stem export, cloud export, and mastering processors are separate features.
93+
7894
## Mixer Routing
7995

8096
The first functional mixer should apply to `SONG` arrangement playback. It depends on scheduled sources knowing their arrangement `trackId`.

docs/data-model.md

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ For 4/4:
88

99
- 1 beat = 480 ticks.
1010
- 1 bar = 1920 ticks.
11+
- 2 bars = 3840 ticks.
12+
- 4 bars = 7680 ticks.
1113
- 16-step grid step = 120 ticks.
1214

1315
Seconds are derived at playback time from ticks and tempo. Do not store seconds as the primary event position.
@@ -36,10 +38,12 @@ The initial transport UI range is 60 to 180 BPM. Implementations should validate
3638

3739
## Hybrid Clips
3840

39-
Early clips may contain both drum events and note events. This keeps the M1 editor focused: one 1-bar clip can hold a drum pattern and a piano roll phrase.
41+
Early clips may contain both drum events and note events. This keeps the M1 editor focused: one clip can hold a drum pattern and a piano roll phrase.
4042

4143
Later, the model can evolve toward separate drum, MIDI, and audio clip types if arrangement and editing workflows need stronger separation.
4244

45+
The initial clip length is 1 bar, represented as `lengthTicks: 1920`. Later clip editing should support 1, 2, and 4 bar hybrid clips by changing `lengthTicks` to 1920, 3840, or 7680 ticks. Event positions and note durations remain tick-based and must stay inside the clip length.
46+
4347
Imported WAV files should use a separate audio clip shape rather than forcing audio file state into the M1 hybrid clip fields.
4448

4549
## Mixer State
@@ -80,7 +84,7 @@ Effect slots may remain UI placeholders until a basic effects feature introduces
8084

8185
## M1 Clip Collection and Sidebar Membership
8286

83-
The M1 browser app should maintain an ordered collection of reusable 1-bar hybrid clips. This may live in app-level project state before full export/import exists, but the data itself should be serializable and compatible with the future `Project.clips` field.
87+
The M1 browser app should maintain an ordered collection of reusable hybrid clips. This may live in app-level project state before full export/import exists, but the data itself should be serializable and compatible with the future `Project.clips` field.
8488

8589
Runtime UI selection, such as `selectedClipId` and the selected sidebar item, may remain app state. The clip list, clip names, drum lane settings, pitched instrument membership, drum events, and note events should be serializable.
8690

@@ -121,7 +125,7 @@ The model should keep these concepts separate:
121125
- Runtime media data: `File`, `Blob`, object URL, decoded `AudioBuffer`, and active source nodes.
122126
- Future arrangement placement: where a clip instance appears in song time and how long that instance lasts.
123127

124-
Imported file bytes and decoded sample data are not project JSON. Until IndexedDB or another persistence feature stores imported blobs, imported audio clips may be session-only and should be documented in the UI.
128+
Imported file bytes and decoded sample data are not project JSON. IndexedDB persistence may store imported blobs outside the project document and connect them back through stable sample IDs. Until that persistence feature exists, imported audio clips may be session-only and should be documented in the UI.
125129

126130
Illustrative shape:
127131

@@ -158,6 +162,13 @@ The arrangement view places reusable clips on tracks using `ClipInstance` object
158162
- How long the placed instance lasts in arrangement ticks.
159163
- Optional source offset for audio clips.
160164

165+
`ArrangementState` owns arrangement-level song settings such as:
166+
167+
- Total arrangement length in bars.
168+
- Current loop range.
169+
170+
The first adjustable arrangement length feature should default to 16 bars, keep a minimum of 1 bar, and use a practical maximum such as 128 bars. The arrangement ruler, grid, scroll width, loop bounds, playback bounds, and export duration should derive from this state.
171+
161172
`ArrangementLoopRange` owns the current song playback loop boundaries:
162173

163174
- Loop start tick.
@@ -180,6 +191,11 @@ export interface ArrangementLoopRange {
180191
startTick: Tick;
181192
endTick: Tick;
182193
}
194+
195+
export interface ArrangementState {
196+
lengthBars: number;
197+
loopRange: ArrangementLoopRange;
198+
}
183199
```
184200

185201
The first arrangement placement feature should create, move, select, and delete `ClipInstance` objects without mutating the source `Clip`. Deleting a placed clip from the arrangement removes only that instance. It does not delete the sidebar clip.
@@ -385,6 +401,7 @@ export interface Project {
385401
denominator: 4;
386402
};
387403
ppq: 480;
404+
arrangement: ArrangementState;
388405
tracks: Track[];
389406
clips: Clip[];
390407
samples: SampleMeta[];
@@ -436,6 +453,16 @@ export interface ClipInstance {
436453
sourceOffsetSeconds?: number;
437454
}
438455

456+
export interface ArrangementState {
457+
lengthBars: number;
458+
loopRange: ArrangementLoopRange;
459+
}
460+
461+
export interface ArrangementLoopRange {
462+
startTick: Tick;
463+
endTick: Tick;
464+
}
465+
439466
export interface DrumEvent {
440467
id: string;
441468
laneId: "kick" | "snare" | "closedHat" | "openHat" | string;
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Feature: 18 IndexedDB Project Persistence
2+
3+
## Status
4+
Planned
5+
6+
## Goal
7+
8+
Persist the user's current project in the browser so a refresh or reopened tab can restore clips, arrangement data, mixer settings, imported sample metadata, and imported sample audio bytes.
9+
10+
## Context
11+
12+
The app is browser-first and project data must remain serializable. Runtime audio objects such as `AudioContext`, `AudioBuffer`, node graphs, object URLs, and decoded sample caches must not be stored in project JSON.
13+
14+
Imported audio files need separate durable storage because object URLs and decoded buffers are session-only. IndexedDB is the first local persistence target.
15+
16+
## Scope
17+
18+
Included:
19+
20+
- Add an IndexedDB persistence layer under `src/persistence/`.
21+
- Store a versioned project document for the active project.
22+
- Store imported sample metadata in serializable project state.
23+
- Store imported sample file bytes or blobs in IndexedDB using stable sample IDs.
24+
- Restore the last active project on app startup.
25+
- Rebuild runtime audio caches from stored blobs only when needed.
26+
- Add debounced autosave for project edits.
27+
- Show minimal save status, such as saved, saving, or save failed.
28+
- Add a baseline migration/version field for stored project documents.
29+
- Handle unavailable storage or quota errors with visible user feedback.
30+
31+
Excluded:
32+
33+
- Cloud sync.
34+
- User accounts.
35+
- Multi-device collaboration.
36+
- A full project browser or multi-project dashboard.
37+
- File-system project export/import.
38+
- Final audio rendering/export.
39+
- Storing `AudioBuffer`, `AudioNode`, `File`, or object URL values in project JSON.
40+
41+
## Constraints
42+
43+
- Keep project JSON serializable.
44+
- Use stable IDs to connect project sample metadata to IndexedDB blobs.
45+
- Keep persistence separate from React rendering and audio scheduling.
46+
- Do not make the audio engine depend on IndexedDB.
47+
- Do not introduce a runtime dependency unless the implementation clearly needs it and the PR explains why.
48+
- Tests may use lightweight mocks or adapters for IndexedDB behavior.
49+
50+
## Done when
51+
52+
- Refreshing the browser restores the active project state.
53+
- Imported WAV clips can still be played after refresh if their blobs were saved successfully.
54+
- Autosave does not block UI interactions.
55+
- Save failures are visible and do not corrupt in-memory state.
56+
- Stored project records include a schema version.
57+
- Runtime audio caches are rebuilt from persistent data rather than serialized directly.
58+
59+
## Verification
60+
61+
Run:
62+
63+
- `npm run typecheck --if-present`
64+
- `npm run lint --if-present`
65+
- `npm run test --if-present`
66+
- `npm run build --if-present`
67+
68+
Manual check:
69+
70+
- Create or modify a clip.
71+
- Import a WAV file.
72+
- Place clips in the arrangement.
73+
- Refresh the page.
74+
- Confirm the project, clips, placement, and imported audio references are restored.
75+
- Confirm the console has no IndexedDB or decoding errors.
76+
77+
## PR notes
78+
79+
- Summarize the IndexedDB stores and schema version.
80+
- Explain what is persisted and what remains runtime-only.
81+
- Mention any browser storage limitations or quota behavior.

0 commit comments

Comments
 (0)