diff --git a/PLANS.md b/PLANS.md index 7fa3c49..7c102e9 100644 --- a/PLANS.md +++ b/PLANS.md @@ -29,7 +29,9 @@ M1 should include: 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. -1. #19 Pitched instrument selection and Iowa Piano sustain -> `docs/features/08-pitched-instruments-and-sustain.md` (PR #22 in review) +1. #25 Tempo control and live BPM updates -> `docs/features/10-tempo-control-and-bpm-slider.md` +2. #26 Arrangement view UI shell -> `docs/features/11-arrangement-view-ui-shell.md` +3. #23 Sampler for advanced sustain -> `docs/features/09-sampler-advanced-sustain.md` ## Planned Milestones @@ -39,14 +41,17 @@ Use this section as the current execution order for agent work. Feature document 4. Drum step sequencer. 5. Basic piano roll. 6. Transport pause/resume and editor playhead. -7. Pitched instrument selection and Iowa Piano sustain. -8. Hybrid clip loop playback. -9. Sample import. -10. Project export/import. -11. IndexedDB autosave. -12. Arrangement data model. -13. Arrangement view. -14. Mixer and basic effects. +7. Pitched instrument selection and Iowa Piano one-shot playback. +8. Tempo control and live BPM updates. +9. Arrangement view UI shell. +10. Sampler advanced sustain. +11. Hybrid clip loop playback. +12. Sample import. +13. Project export/import. +14. IndexedDB autosave. +15. Arrangement data model. +16. Arrangement editing and playback. +17. Mixer and basic effects. ## Backlog @@ -54,7 +59,6 @@ Use this section as the current execution order for agent work. Feature document - Basic undo and redo. - Velocity editing for drum and note events. - Clip duplication. -- BPM and transport controls. - Starter project template. - Metronome. - Quantize utilities. diff --git a/docs/audio-engine.md b/docs/audio-engine.md index 45d7f43..5e34894 100644 --- a/docs/audio-engine.md +++ b/docs/audio-engine.md @@ -76,13 +76,13 @@ Sample-based pitched playback should: The first Iowa Piano implementation should avoid sustain looping. The bundled one-second piano samples are not reliable loop-ready sustain samples yet, and early loop points can make long notes sound like repeated strikes. Iowa Piano notes should currently play the sample once from the configured start offset, then stop or naturally decay. -A future sampler sustain task may add explicit `loopStartSeconds` and `loopEndSeconds`; if those values are present, the source node may use `loop = true` with those loop points. Loop metadata is serializable instrument/sample metadata, but decoded buffers and active source nodes are runtime-only. +A future sampler sustain task may add explicit sustain metadata with `loopStartSeconds` and `loopEndSeconds`; if those values are present and valid, the source node may use `loop = true` with those loop points. Loop metadata is serializable instrument/sample metadata, but decoded buffers and active source nodes are runtime-only. Do not attempt automatic loop point detection in the first pass. If loop points are added later, update the explicit metadata and document the decision. Current Iowa Piano sample zones use explicit `sampleStartSeconds` offsets because the bundled one-second C4-C5 WAV files contain leading silence before the audible note attack. The engine should pass that value as the second argument to `AudioBufferSourceNode.start(when, offset)`. -Current Iowa Piano sample zones intentionally omit `loopStartSeconds` and `loopEndSeconds`, so the audio engine does not enable `AudioBufferSourceNode.loop` for Iowa Piano. +Current Iowa Piano sample zones intentionally omit sustain metadata, so the audio engine does not enable `AudioBufferSourceNode.loop` for Iowa Piano. The audio engine should load sample zones needed by selected note events before handing them to the lookahead scheduler. If a sample-based instrument has no zone for a note, the engine may fall back to `Default Synth` behavior for that note rather than storing any runtime fallback state in project data. @@ -90,6 +90,23 @@ Each scheduled note event carries an `instrumentId`. The scheduler should play a When `decodeAudioData` cannot decode a bundled PCM WAV file, the browser engine may fall back to a local PCM WAV decoder and create an `AudioBuffer` manually. This fallback is runtime-only and does not change project data. +## Sampler Sustain + +Sampler sustain should be a reusable audio-engine capability for sample-based pitched instruments, not an `Iowa Piano` special case. + +Sampler sustain should: + +- Read serializable sample-zone metadata such as `sampleStartSeconds`, `sampleEndSeconds`, sustain loop mode, loop start/end points, optional crossfade length, and envelope settings. +- Treat sample-local offsets, loop points, and envelope lengths as seconds because they describe positions inside a decoded sample buffer. +- Keep musical event positions and durations in ticks and convert them to seconds only at scheduling time. +- Validate loop metadata before enabling looping. +- Fall back to one-shot sample playback when loop metadata is missing or invalid. +- Apply release behavior at note end, transport pause, and transport stop so sustained voices do not remain stuck. + +The first implementation should prefer explicit metadata over automatic analysis. Automatic loop point detection, visual loop point editing, velocity layers, round-robin selection, and full sampler preset management belong in later tasks. + +If a zone uses a simple forward loop, the engine may use `AudioBufferSourceNode.loop`, `loopStart`, and `loopEnd`. If a future task adds crossfaded looping, it may need multiple scheduled source nodes and gain ramps because Web Audio buffer source looping does not provide native loop crossfade behavior. + ## Lookahead Scheduler Concept Do not rely on UI timers for exact playback. Sequenced playback should use a timer that wakes frequently, looks ahead by a short scheduling window, and schedules Web Audio events against `AudioContext.currentTime`. @@ -139,6 +156,20 @@ Pause and stop have different meanings: The audio engine should expose pause separately from stop. A pause operation preserves the scheduler object and its current tick snapshot; a stop operation clears the active loop and returns the transport to the loop start. +## Tempo Control + +Tempo is musical project state, not UI-only state. The transport BPM slider should update the current `tempoBpm` value used by audio scheduling. + +Tempo changes should follow these rules: + +- When stopped, the next playback start uses the current BPM. +- When paused, resume uses the current BPM from the preserved paused tick. +- When playing, BPM changes should affect future lookahead scheduler windows. +- Existing musical event positions stay in ticks. Tempo changes alter tick-to-seconds conversion, not event tick positions. +- React may own the visible slider value, but exact event scheduling must continue to use audio-engine transport state and `AudioContext.currentTime`. + +Prefer an audio-engine API that can update active scheduler tempo while preserving the current runtime tick. If the first implementation restarts the selected clip loop from the current tick to apply tempo changes, document the limitation and keep the schedule-ahead window short enough for interactive use. + ## Looping Behavior For M1, loop playback targets a selected 1-bar clip. The default loop range is 0 to 1920 ticks. diff --git a/docs/data-model.md b/docs/data-model.md index d51162b..beccb4e 100644 --- a/docs/data-model.md +++ b/docs/data-model.md @@ -12,6 +12,14 @@ For 4/4: Seconds are derived at playback time from ticks and tempo. Do not store seconds as the primary event position. +## Tempo + +Project tempo should be represented as serializable BPM data, using a field such as `tempoBpm` on the project or current app-level project state until full persistence exists. + +Changing tempo must not rewrite clip event positions. Drum events and note events keep their `startTick` and `durationTicks`; the audio engine converts those ticks to seconds using the current `tempoBpm` at scheduling time. + +The initial transport UI range is 60 to 180 BPM. Implementations should validate or clamp tempo values before passing them to scheduler or tick/time conversion utilities. + ## Core Entities - `Project`: top-level serializable project document. @@ -130,16 +138,29 @@ export interface SampleZone { midiNote: number; rootMidiNote: number; sampleStartSeconds?: number; + sampleEndSeconds?: number; + sustain?: SamplerSustainMeta; + envelope?: SamplerEnvelopeMeta; +} + +export interface SamplerSustainMeta { + mode: "none" | "forward-loop" | "crossfade-loop"; loopStartSeconds?: number; loopEndSeconds?: number; + crossfadeSeconds?: number; +} + +export interface SamplerEnvelopeMeta { + attackSeconds?: number; + releaseSeconds?: number; } ``` -The `loopStartSeconds` and `loopEndSeconds` fields are serializable metadata. They describe how the runtime audio engine may configure `AudioBufferSourceNode.loopStart` and `loopEnd` for sustained sample playback. +The `sustain` fields are serializable metadata. They describe how the runtime audio engine may configure sample sustain playback. A simple `forward-loop` mode may map to `AudioBufferSourceNode.loopStart` and `loopEnd`; a future `crossfade-loop` mode may require additional scheduled source nodes and gain ramps. -The `sampleStartSeconds` field skips leading silence before note attack. The bundled Iowa Piano WAV files contain substantial leading silence, so sample zones use explicit start offsets. +The `sampleStartSeconds` field skips leading silence before note attack. `sampleEndSeconds`, sustain loop points, crossfade length, and envelope values are sample-local seconds because they describe positions or durations inside a sample, not musical event time. -Current Iowa Piano sample zones intentionally omit `loopStartSeconds` and `loopEndSeconds`. The samples play once from their configured start offsets and do not loop in the initial implementation. Advanced sampler sustain may add explicit loop metadata later if the loop points are tuned well enough to avoid repeated-strike artifacts. +Current Iowa Piano sample zones intentionally omit `sustain` metadata. The samples play once from their configured start offsets and do not loop in the initial implementation. Advanced sampler sustain may add explicit loop metadata later if the loop points are tuned well enough to avoid repeated-strike artifacts. ## Initial Piano Roll Implementation @@ -247,8 +268,22 @@ export interface SampleZone { sampleId: string; midiNote: number; rootMidiNote: number; + sampleStartSeconds?: number; + sampleEndSeconds?: number; + sustain?: SamplerSustainMeta; + envelope?: SamplerEnvelopeMeta; +} + +export interface SamplerSustainMeta { + mode: "none" | "forward-loop" | "crossfade-loop"; loopStartSeconds?: number; loopEndSeconds?: number; + crossfadeSeconds?: number; +} + +export interface SamplerEnvelopeMeta { + attackSeconds?: number; + releaseSeconds?: number; } ``` diff --git a/docs/features/08-pitched-instruments-and-sustain.md b/docs/features/08-pitched-instruments-and-sustain.md index 18773a0..7bb4239 100644 --- a/docs/features/08-pitched-instruments-and-sustain.md +++ b/docs/features/08-pitched-instruments-and-sustain.md @@ -1,7 +1,7 @@ # Feature: 08 Pitched Instruments and Sustain ## Status -In Review +Done ## Goal diff --git a/docs/features/09-sampler-advanced-sustain.md b/docs/features/09-sampler-advanced-sustain.md new file mode 100644 index 0000000..b85fab8 --- /dev/null +++ b/docs/features/09-sampler-advanced-sustain.md @@ -0,0 +1,130 @@ +# Feature: 09 Sampler Advanced Sustain + +## Status +Planned + +## Goal + +Add a reusable sampler sustain path for sample-based pitched instruments. + +The first target is `Iowa Piano`, but the implementation should not be hard-coded to piano. Future bundled or imported sample instruments should be able to use the same sampler metadata and playback path. + +## Context + +`Default Synth` can hold long notes because it uses an oscillator. `Iowa Piano` currently plays bundled WAV samples once from explicit `sampleStartSeconds` offsets so the engine skips leading silence before the attack. + +Earlier sustain looping was deferred because simple loop points made long notes sound like repeated strikes. The next sampler step should support longer sample-based notes without coupling React UI to timing and without storing runtime audio objects in project data. + +This feature should add a general, metadata-driven sampler sustain mechanism. It is not a full sampler product. + +## Scope + +Included: +- Define serializable sampler zone metadata for sample start offset, loop region, loop mode, optional crossfade duration, and basic envelope values. +- Implement reusable sampled-note scheduling for pitched sample instruments. +- Use the general sampler path for `Iowa Piano`. +- Keep one-shot sample playback as the fallback when a zone has no valid sustain metadata. +- Support explicit loop metadata only. +- Use gain envelopes for attack and release so note starts and note-offs avoid clicks. +- Stop sustained sample voices cleanly when note duration ends, transport pauses, or transport stops. +- Keep note start and duration in ticks and convert to seconds at scheduling time. +- Keep decoded buffers, source nodes, gain nodes, and active voice state runtime-only. +- Add focused tests for sampler metadata validation, loop-region calculation, fallback behavior, and release timing helpers where practical. +- Update audio engine, data model, and testing docs. + +Excluded: +- User sample import UI. +- Visual loop point editor. +- Automatic loop point detection or sample analysis. +- Full sampler preset browser. +- Multisample velocity layers. +- Round-robin sample selection. +- Time-stretching. +- Pitch-shifting beyond playback-rate mapping from `rootMidiNote`. +- Arrangement playback changes. +- New runtime dependencies unless the implementation clearly justifies them. + +## Constraints + +- React UI must not own exact audio timing. +- Schedule playback against `AudioContext.currentTime`. +- Store musical event time in ticks, not seconds. +- Sample-local metadata such as start offsets, loop points, and envelope lengths may be stored in seconds. +- Keep project data serializable. +- Do not store `AudioBuffer`, `AudioNode`, object URLs, or decoded sample data in project JSON. +- Preserve `Default Synth` behavior. +- If sampler sustain metadata is invalid or sounds unsafe, fall back to one-shot sample playback instead of producing stuck notes. + +## Sampler Metadata Approach + +Sample zones may evolve toward this shape: + +```ts +export interface SampleZone { + sampleId: string; + midiNote: number; + rootMidiNote: number; + sampleStartSeconds?: number; + sampleEndSeconds?: number; + sustain?: SamplerSustainMeta; + envelope?: SamplerEnvelopeMeta; +} + +export interface SamplerSustainMeta { + mode: "none" | "forward-loop" | "crossfade-loop"; + loopStartSeconds?: number; + loopEndSeconds?: number; + crossfadeSeconds?: number; +} + +export interface SamplerEnvelopeMeta { + attackSeconds?: number; + releaseSeconds?: number; +} +``` + +Use these as planning types. The implementation may refine names, but any semantic changes should update `docs/data-model.md`. + +## Playback Approach + +- Load and decode the required sample before scheduling the note. +- Start playback at `sampleStartSeconds` when present. +- If `sustain.mode` is `none` or loop metadata is missing, play the sample once with the note envelope. +- If `sustain.mode` is `forward-loop`, use `AudioBufferSourceNode.loop`, `loopStart`, and `loopEnd` after validating the region. +- If `sustain.mode` is `crossfade-loop`, implement a simple scheduled crossfade strategy only if it can be done safely in this feature. Otherwise document the limitation and keep `forward-loop` as the first pass. +- Always apply release behavior at the note end so sustained voices do not stay stuck. +- Transport pause and stop must clear active sampler voices. + +## Done when + +- Sample-based pitched instruments use shared sampler sustain logic instead of Iowa-Piano-only sustain code. +- `Iowa Piano` can use explicit sampler sustain metadata through the shared path. +- Long Iowa Piano notes sustain more smoothly than one-shot playback when valid metadata is present. +- Long notes do not sound like repeated attacks as much as the available sample material allows. +- Invalid or missing loop metadata falls back to one-shot sample playback. +- Note release and transport stop clear sustained sample voices. +- Serializable metadata is documented and runtime audio objects remain runtime-only. +- Relevant docs and tests are updated. + +## Verification + +Run: +- `npm run typecheck` +- `npm run lint` +- `npm run test` +- `npm run build` + +Manual check: +- Create short and long Iowa Piano notes. +- Confirm short notes still start at the audible attack. +- Confirm long notes sustain without obvious repeated-strike artifacts when sustain metadata is valid. +- Confirm invalid or missing sustain metadata still plays as one-shot sample playback. +- Stop playback and confirm no sustained sample voice remains stuck. +- Confirm `Default Synth` behavior is unchanged. + +## PR notes + +- Explain the sampler metadata fields added or changed. +- Explain whether the implementation uses forward looping, crossfaded looping, or a documented fallback. +- List any Iowa Piano samples whose loop metadata still sounds weak. +- Mention manual audio checks and any remaining sample-material limitations. diff --git a/docs/features/10-tempo-control-and-bpm-slider.md b/docs/features/10-tempo-control-and-bpm-slider.md new file mode 100644 index 0000000..29cef82 --- /dev/null +++ b/docs/features/10-tempo-control-and-bpm-slider.md @@ -0,0 +1,101 @@ +# Feature: 10 Tempo Control and BPM Slider + +## Status +Planned + +## Goal + +Make the transport BPM slider control the app's actual playback tempo. + +The slider should not be only visual state. It should update the tempo used by the audio engine for drum and note scheduling. + +## Context + +The main UI shell added a BPM display and slider. Later playback features connected clip playback to the audio engine, but tempo control has not been planned as its own implementation task. + +The current app state has a `bpm` value and passes that value when playback starts. A focused tempo-control feature is still needed so BPM changes behave consistently across stopped, paused, and playing transport states. + +This should happen before advanced sampler sustain because tempo affects all scheduled drum and note events. + +## Scope + +Included: +- Treat BPM as the current project/transport tempo state. +- Keep tempo serializable as `tempoBpm` in project-level data or documented app-level project state until full project persistence exists. +- Keep the BPM slider and displayed BPM in sync. +- Start playback with the current BPM. +- Resume playback from pause using the current BPM. +- Apply BPM changes during active playback to future scheduler windows. +- Preserve or recompute the current runtime playhead tick consistently when tempo changes. +- Validate or clamp BPM to the supported UI range. +- Add focused tests for tempo updates, tick/time conversion, and scheduler behavior where practical. +- Update relevant data model, audio engine, UI, and testing docs. + +Excluded: +- Tempo automation lanes. +- Arrangement tempo maps. +- Smooth tempo ramps. +- Tap tempo. +- Metronome. +- Swing or groove timing. +- Keyboard shortcuts for tempo editing. +- Project export/import changes beyond documenting the serializable tempo field. + +## Constraints + +- React UI must not own exact audio timing. +- Musical event positions stay in ticks, not seconds. +- Schedule playback against `AudioContext.currentTime`. +- Tempo is serializable project data, but `AudioContext`, scheduler timers, Web Audio nodes, and runtime transport snapshots are runtime-only. +- Do not introduce runtime dependencies unless clearly justified. +- Keep the audio engine API small and typed. + +## Tempo Change Approach + +When the transport is stopped: +- Updating BPM should update the displayed value and the stored current tempo. +- The next play action should start with that tempo. + +When the transport is paused: +- Updating BPM should update the displayed value and stored current tempo. +- Resume should continue from the paused tick using the new tempo. + +When the transport is playing: +- Updating BPM should affect future scheduled windows. +- Prefer an audio-engine method that updates scheduler tempo while preserving the current runtime tick. +- If restarting the loop from the current tick is simpler and safer for the first implementation, keep the scheduling window short and document any audible limitation. + +Do not use React timers as the source of exact tempo or event timing. + +## Done when + +- Moving the BPM slider updates the displayed BPM. +- Starting playback uses the displayed BPM. +- Pausing and resuming playback uses the current BPM. +- Changing BPM during playback changes future drum and note scheduling. +- The visual playhead remains aligned with the audio engine transport snapshot after tempo changes. +- Invalid BPM values are rejected or clamped safely. +- Event positions remain tick-based and unchanged by tempo edits. +- Relevant docs and tests are updated. + +## Verification + +Run: +- `npm run typecheck` +- `npm run lint` +- `npm run test` +- `npm run build` + +Manual check: +- Move the BPM slider while stopped and confirm playback starts at that tempo. +- Move the BPM slider while paused and confirm resume uses the new tempo. +- Move the BPM slider while playing and confirm future steps and notes speed up or slow down. +- Confirm the playhead remains aligned with loop playback. +- Confirm drum and piano roll events still trigger at their tick positions. + +## PR notes + +- Explain how tempo is represented in state and passed to the audio engine. +- Explain how tempo changes are applied while stopped, paused, and playing. +- Note whether active playback updates scheduler tempo directly or restarts from the current tick. +- Mention any audible limitation when changing tempo during playback. diff --git a/docs/features/11-arrangement-view-ui-shell.md b/docs/features/11-arrangement-view-ui-shell.md new file mode 100644 index 0000000..4ce378f --- /dev/null +++ b/docs/features/11-arrangement-view-ui-shell.md @@ -0,0 +1,133 @@ +# Feature: 11 Arrangement View UI Shell + +## Status +Planned + +## Goal + +Add an arrangement view UI shell that appears when the transport mode is set to `SONG`. + +Keep the existing top transport bar and left project sidebar. The `PAT` mode should continue to show the current 1-bar hybrid clip editor. + +## Context + +The app already has a `PAT` / `SONG` toggle in the transport bar. `PAT` currently represents the focused pattern or clip editor workflow. `SONG` should start to represent the later arrangement workflow where clips are placed horizontally in time and vertically across tracks. + +A Google Stitch prototype provides a visual reference for the arrangement view. It shows: + +- Arrangement toolbar. +- Timeline ruler with bar numbers. +- Fixed left track headers. +- Scrollable grid with track lanes. +- Demo clip blocks placed on the timeline. +- Bottom scrollbar and zoom area. + +The prototype uses Tailwind and includes layout issues, including track and clip alignment problems. Treat it as visual reference only. + +## Scope + +Included: +- Preserve the existing top transport bar. +- Preserve the existing left project sidebar. +- Render the current clip editor when mode is `PAT`. +- Render an arrangement view UI shell when mode is `SONG`. +- Add an arrangement toolbar with title and snap display. +- Add a timeline ruler with bar numbers. +- Add fixed-width track headers. +- Add a scrollable timeline grid with horizontal track lanes and vertical bar/beat lines. +- Add a few static/demo clip blocks to communicate intended layout. +- Add a bottom horizontal scrollbar or scrollbar-like visual area and zoom placeholder. +- Keep track headers, grid rows, and clip blocks vertically aligned through shared row-height constants. +- Keep ruler, grid columns, and clip left/width values horizontally aligned through shared timeline geometry constants. +- Use CSS Modules and semantic design tokens. +- Use inline styles only for dynamic arrangement geometry such as clip `left`, clip `width`, clip `top`, grid width, and playhead position. +- Update relevant UI/design docs. + +Excluded: +- Real arrangement data model changes. +- Persisted `ClipInstance` editing. +- Arrangement playback. +- Timeline playhead behavior beyond optional static visual placeholder. +- Drag and drop arrangement editing. +- Clip duplication, clip splitting, or clip creation in the arrangement. +- Real audio clips or generated waveforms. +- Track mute, solo, arm, or routing behavior beyond nonfunctional visual controls. +- Arrangement zoom implementation beyond a visual placeholder. +- Tailwind installation, Tailwind config, Tailwind CDN, or Tailwind utility-class-heavy React markup. + +## Constraints + +- React UI must not own exact audio timing. +- Store musical arrangement time in ticks when real arrangement data is introduced later. +- Keep current clip editor behavior unchanged in `PAT` mode. +- Preserve existing transport and sidebar components instead of duplicating them. +- Do not introduce runtime dependencies unless clearly justified. +- Follow the primitive-to-semantic token policy. +- Component CSS Modules should use semantic tokens, not raw primitive values, unless there is a documented exception. +- Do not copy the Stitch HTML directly into React. + +## UI Reference Policy + +Use the provided Stitch prototype for visual direction only: + +- Dark DAW workspace. +- Compact dense layout. +- Muted lime and teal accents. +- Thin borders and clear panel separation. +- Arrangement toolbar. +- Fixed track header column. +- Timeline ruler and grid. +- Clip blocks with small headers and simple content hints. + +Correct prototype issues during implementation. In particular, track headers, timeline lanes, and clip blocks must align exactly. + +## Geometry Approach + +Define shared arrangement geometry constants in component code or a local feature helper: + +- Track header width. +- Track row height. +- Ruler height. +- Bar width. +- Beat subdivision width. +- Timeline content width. + +Use those constants to compute both CSS grid/background dimensions and clip block positions. Do not duplicate unrelated magic numbers across track headers, grid rows, and clips. + +## Done when + +- Selecting `PAT` shows the existing hybrid clip editor. +- Selecting `SONG` shows the arrangement view UI shell. +- The existing transport bar and project sidebar remain visible in both modes. +- The arrangement view includes toolbar, ruler, track headers, timeline grid, demo clips, and bottom scrollbar/zoom placeholder. +- Track headers and timeline lanes align vertically. +- Clip blocks align to track rows and timeline columns. +- Horizontal scrolling, if present, is contained inside the arrangement panel. +- The app shell still avoids document-level scrolling. +- No Tailwind dependency, config, CDN script, or Tailwind utility-heavy React markup is added. +- Relevant docs are updated. + +## Verification + +Run: +- `npm run typecheck` +- `npm run lint` +- `npm run test` +- `npm run build` + +Manual check: +- Start the dev server. +- Confirm `PAT` mode still renders the current clip editor. +- Toggle to `SONG` and confirm the arrangement view renders. +- Confirm the existing transport and sidebar remain visible. +- Confirm track headers, grid rows, clip blocks, and ruler lines align. +- Confirm the page itself does not scroll. +- Confirm horizontal scrolling stays inside the arrangement panel. +- Confirm Tailwind was not added. + +## PR notes + +- Explain how the Stitch prototype was converted into CSS Modules. +- Mention that arrangement data model, persistence, editing, and playback are intentionally excluded. +- Describe how shared geometry constants keep track rows and clips aligned. +- Include screenshots or a short recording if practical. diff --git a/docs/testing.md b/docs/testing.md index df921ef..e9d1441 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -19,7 +19,9 @@ CI uses `--if-present` while the repository is still before the Vite scaffold. - Tick/time conversion: unit tests. - Data model transformations: unit tests. - Pitched instrument metadata and sample-zone mapping: unit tests. +- Tempo control and scheduler tempo update behavior: unit tests where practical. - Sustain loop point calculations: unit tests. +- Sampler sustain metadata validation and fallback decisions: unit tests. - Scheduler calculations: unit tests where possible. - UI interactions: component tests later. - Critical flows: browser end-to-end tests later, after the UI and workflows are stable enough to justify the framework. @@ -47,8 +49,10 @@ tests/unit/utils/tick-time.test.ts - Loop boundaries. - Pause/resume tick offsets. - Playhead wrapping at loop boundaries. +- BPM changes while stopped, paused, and playing. - Pitched instrument selection. - Sample start offsets, optional sustain loop points, and note release behavior. +- Sampler sustain fallback behavior when loop metadata is missing or invalid. - Clip duplication. - Sample import. - Project export/import. @@ -63,8 +67,13 @@ Manual audio checks should verify: - Loop playback does not double-trigger events at the loop boundary. - UI playhead movement roughly matches audible playback. - Pause preserves the runtime playhead position, resume continues from that position, and stop resets to the start. +- BPM changes while stopped affect the next playback start. +- BPM changes while paused affect resume from the paused tick. +- BPM changes while playing affect future scheduled drum and note events without using UI timers for exact playback. - Starting, stopping, and restarting transport leaves no stuck sounds. - Long sample-based piano notes behave as documented for the selected instrument. For the current Iowa Piano implementation, they should not retrigger or sound like repeated strikes. +- When sampler sustain metadata exists, long sample-based notes should sustain without obvious repeated attacks as much as the sample material allows. +- If sampler sustain metadata is missing or invalid, sample-based notes should fall back to one-shot playback rather than stuck or unstable sustain. - Instrument switching changes piano roll playback sound without mutating existing note events. - Tempo changes behave as documented for the current milestone. diff --git a/docs/ui-design.md b/docs/ui-design.md index 03a07dd..20b5ff3 100644 --- a/docs/ui-design.md +++ b/docs/ui-design.md @@ -88,6 +88,31 @@ Later milestones can add: The arrangement should support horizontal time and vertical track lanes when that milestone arrives. +## Arrangement View + +The `PAT` / `SONG` toggle should switch the main workspace mode: + +- `PAT`: show the current selected hybrid clip editor with the drum step sequencer and piano roll. +- `SONG`: show an arrangement view UI shell. + +The existing top transport bar and left project sidebar should stay visible in both modes. + +The first arrangement view should be a UI shell, not a full arrangement editor. It should include: + +- Arrangement toolbar. +- Snap display. +- Timeline ruler with bar numbers. +- Fixed left track header column. +- Scrollable timeline grid. +- Horizontal track lanes. +- Demo clip blocks. +- Bottom scrollbar or scrollbar-like visual area. +- Zoom placeholder. + +Track headers, timeline lanes, and clip blocks must align exactly. Use shared geometry constants for row height, ruler height, bar width, beat subdivision width, and timeline content width. Inline styles are acceptable for dynamic arrangement geometry such as clip positions, clip widths, grid widths, and playhead positions. + +Do not introduce real arrangement data, persisted clip instances, arrangement playback, or drag-and-drop editing in the first arrangement UI shell unless a feature spec explicitly expands the scope. + ## Component Naming Recommendations Prefer names that match the product domain: @@ -180,6 +205,8 @@ If a design prototype uses Tailwind, inline styles, or CDN assets, treat it as v For the main DAW UI shell, the Tailwind prototype should inform visual direction only: dark DAW workspace, compact editor spacing, muted lime active states, muted teal MIDI notes, thin borders, and clear panel separation. +For arrangement view prototypes from tools such as Google Stitch, preserve visual intent but correct layout problems during implementation. Do not copy Tailwind utility markup into React. Rebuild the view as semantic components with CSS Modules and shared tokens. + ## Main UI Shell Styling The initial main DAW UI shell converts the Tailwind-based prototype into React components with CSS Modules.