|
| 1 | +# Feature: 16 Arrangement Clip Placement and Playback |
| 2 | + |
| 3 | +## Status |
| 4 | +Planned |
| 5 | + |
| 6 | +## Goal |
| 7 | + |
| 8 | +Allow users to place reusable clips on the `SONG` arrangement timeline and play the arranged song from the transport. |
| 9 | + |
| 10 | +This turns the arrangement view from a static shell into the first usable song-building surface. |
| 11 | + |
| 12 | +## Context |
| 13 | + |
| 14 | +The app already has: |
| 15 | + |
| 16 | +- A `PAT` mode for focused clip editing. |
| 17 | +- A `SONG` mode arrangement UI shell. |
| 18 | +- A left sidebar that manages clips and clip child items. |
| 19 | +- Transport play, pause, resume, stop, playhead, and tempo behavior for clip playback. |
| 20 | +- Planned WAV import as audio clips in `docs/features/15-wav-file-import-as-audio-clip.md`. |
| 21 | + |
| 22 | +Users now need to place clips horizontally in time and vertically across tracks. Playback in `SONG` mode should schedule the placed clip instances instead of looping only the selected `PAT` clip. |
| 23 | + |
| 24 | +## Scope |
| 25 | + |
| 26 | +Included: |
| 27 | +- Make sidebar clips draggable into the arrangement timeline where practical. |
| 28 | +- Create serializable `ClipInstance` entries when clips are placed. |
| 29 | +- Store arrangement placement with `clipId`, `trackId`, `startTick`, and `lengthTicks`. |
| 30 | +- Render arrangement clip blocks from project or app model state instead of static demo blocks. |
| 31 | +- Move existing clip instances horizontally and vertically by drag where practical. |
| 32 | +- Snap dropped and moved clip instances to the arrangement grid. |
| 33 | +- Select and delete placed clip instances. |
| 34 | +- Keep the existing arrangement track list and timeline layout aligned. |
| 35 | +- Add a visual arrangement playhead in `SONG` mode. |
| 36 | +- Make transport play, pause, resume, and stop operate on arrangement playback when `SONG` mode is active. |
| 37 | +- Schedule placed hybrid clips by expanding their drum and note events relative to each `ClipInstance.startTick`. |
| 38 | +- Schedule placed audio clips when imported audio runtime data is available. |
| 39 | +- Keep imported audio playback at original speed; no time stretching in this feature. |
| 40 | +- Add focused tests for clip instance creation, moving, deletion, snapping, and scheduler event expansion where practical. |
| 41 | +- Update relevant product, architecture, data model, audio, UI, and testing docs. |
| 42 | + |
| 43 | +Excluded: |
| 44 | +- Time stretching or tempo matching imported audio. |
| 45 | +- Clip instance resize handles. |
| 46 | +- Clip splitting. |
| 47 | +- Arrangement recording. |
| 48 | +- Track creation, deletion, or reordering. |
| 49 | +- Multi-select, copy/paste, duplicate shortcuts, or marquee selection. |
| 50 | +- Arrangement loop region editing. |
| 51 | +- Persistent project export/import beyond serializable in-memory model changes. |
| 52 | +- IndexedDB persistence for imported audio bytes. |
| 53 | +- Full mixer audio routing, real level metering, or effects. |
| 54 | + |
| 55 | +## Constraints |
| 56 | + |
| 57 | +- React UI must not own exact audio timing. |
| 58 | +- Store arrangement positions and lengths in ticks. |
| 59 | +- Use PPQ 480, where one 4/4 bar is 1920 ticks. |
| 60 | +- Keep project data serializable. |
| 61 | +- Do not store `AudioBuffer`, `AudioNode`, `File`, `Blob`, object URLs, scheduler timers, or drag runtime objects in project JSON. |
| 62 | +- Playback must schedule audio against `AudioContext.currentTime`. |
| 63 | +- Audio clip source duration may be seconds because it describes media length, but arrangement placement still uses ticks. |
| 64 | +- Imported audio clips must fail clearly if their runtime file data is missing. |
| 65 | +- Use CSS Modules and semantic design tokens for UI changes. |
| 66 | +- Preserve existing `PAT` mode clip editing behavior. |
| 67 | + |
| 68 | +## Data Model Notes |
| 69 | + |
| 70 | +The arrangement should use serializable clip instances: |
| 71 | + |
| 72 | +```ts |
| 73 | +export interface ClipInstance { |
| 74 | + id: string; |
| 75 | + clipId: string; |
| 76 | + trackId: string; |
| 77 | + startTick: Tick; |
| 78 | + lengthTicks: Tick; |
| 79 | + sourceOffsetSeconds?: number; |
| 80 | +} |
| 81 | +``` |
| 82 | + |
| 83 | +For hybrid clips, the first implementation should use the clip's `lengthTicks` as the default instance length. Hybrid clip events play once relative to the instance start. Repeating a pattern should be represented by multiple placed instances until a later feature adds looping or repeat handles. |
| 84 | + |
| 85 | +For imported audio clips, `lengthTicks` controls the visible and scheduled clip-instance duration. Without time stretching, playback uses the decoded audio at original speed: |
| 86 | + |
| 87 | +- If the instance is shorter than the source, playback is cropped. |
| 88 | +- If the instance is longer than the source, the remaining duration is silence. |
| 89 | +- If runtime imported file data is unavailable after refresh, the clip instance should remain visible but audio playback should report a clear missing-source limitation. |
| 90 | + |
| 91 | +The first implementation may seed a small set of arrangement tracks in app state. Track creation, deletion, and reordering are separate tasks. |
| 92 | + |
| 93 | +## UI Notes |
| 94 | + |
| 95 | +Use the existing `SONG` arrangement layout: |
| 96 | + |
| 97 | +- Sidebar clip rows are drag sources. |
| 98 | +- Arrangement track lanes are drop targets. |
| 99 | +- Placed clips render as timeline blocks. |
| 100 | +- Dragging a placed clip moves it to another snapped start time or track. |
| 101 | +- Selecting a placed clip shows a clear selected state. |
| 102 | +- Deleting a placed clip instance removes only that arrangement placement, not the source clip from the sidebar. |
| 103 | + |
| 104 | +Snap should start simple. Prefer beat-level snapping, using 480 ticks, unless the existing arrangement snap control already supports a narrower value. Later features may add user-selectable snap resolution. |
| 105 | + |
| 106 | +If browser drag-and-drop is awkward for track timeline geometry, pointer-based dragging is acceptable. The feature should still behave like drag placement from the user's perspective. |
| 107 | + |
| 108 | +## Audio Notes |
| 109 | + |
| 110 | +`SONG` playback should schedule arrangement events, not selected-clip loop events. |
| 111 | + |
| 112 | +The audio engine or feature orchestration should expand clip instances into runtime scheduler events: |
| 113 | + |
| 114 | +- Hybrid clip drum events: `instance.startTick + drumEvent.startTick`. |
| 115 | +- Hybrid clip note events: `instance.startTick + noteEvent.startTick`. |
| 116 | +- Audio clips: source starts at `instance.startTick`, optionally from `sourceOffsetSeconds`. |
| 117 | + |
| 118 | +The arrangement transport may initially play linearly from tick 0 through the end of the last clip instance and then stop. Arrangement loop ranges can be added later. |
| 119 | + |
| 120 | +Events already scheduled inside the lookahead window may still play briefly after edits, pause, or stop. Keep the scheduling window short enough for interactive editing. |
| 121 | + |
| 122 | +## Done when |
| 123 | + |
| 124 | +- Users can drag a sidebar clip into a `SONG` arrangement track. |
| 125 | +- A placed clip block appears at the snapped time and correct track. |
| 126 | +- Placed clip blocks are backed by serializable `ClipInstance` data. |
| 127 | +- Static demo arrangement clips are removed or replaced by seeded serializable model state. |
| 128 | +- Users can move a placed clip instance to another snapped time or track. |
| 129 | +- Users can select and delete a placed clip instance without deleting the source clip. |
| 130 | +- `SONG` transport playback schedules placed clip instances. |
| 131 | +- Pause, resume, and stop work in `SONG` mode. |
| 132 | +- A visual arrangement playhead follows playback and resets or hides appropriately when stopped. |
| 133 | +- Hybrid clip drum and note events play at their arrangement positions. |
| 134 | +- Audio clip instances play when runtime imported audio data is available. |
| 135 | +- Missing imported audio runtime data is reported clearly instead of silently failing. |
| 136 | +- `PAT` mode clip editing and clip playback still work. |
| 137 | +- Relevant tests and docs are updated. |
| 138 | + |
| 139 | +## Verification |
| 140 | + |
| 141 | +Run: |
| 142 | +- `npm run typecheck` |
| 143 | +- `npm run lint` |
| 144 | +- `npm run test` |
| 145 | +- `npm run build` |
| 146 | + |
| 147 | +Manual check: |
| 148 | +- Switch to `SONG` mode. |
| 149 | +- Drag a sidebar clip into an arrangement track. |
| 150 | +- Confirm the placed clip aligns to the snapped grid. |
| 151 | +- Drag the placed clip to another time and track. |
| 152 | +- Select and delete the placed clip. |
| 153 | +- Place multiple clips across tracks and press play. |
| 154 | +- Confirm the arrangement playhead roughly matches audible playback. |
| 155 | +- Confirm pause, resume, and stop behavior in `SONG` mode. |
| 156 | +- Confirm `PAT` mode still edits and plays the selected clip. |
| 157 | +- If WAV import is implemented, place an imported audio clip and confirm it plays when runtime data is available. |
| 158 | +- Refresh the page and confirm missing imported audio data is handled clearly if persistence is not implemented. |
| 159 | + |
| 160 | +## PR notes |
| 161 | + |
| 162 | +- Reference issue #41. |
| 163 | +- Explain the `ClipInstance` model changes or model usage. |
| 164 | +- Explain how sidebar drag placement and arrangement move/delete work. |
| 165 | +- Explain how `SONG` playback differs from `PAT` clip loop playback. |
| 166 | +- State clearly that time stretching, resizing, arrangement loop regions, and persistent imported audio bytes are deferred. |
0 commit comments