Multiple preview panels via dock "+" (v2.1.1)#238
Merged
Conversation
Add a General MIDI wavetable instrument alongside the simple oscillator synth, selectable per MIDI track, behind a shared synth seam. - Types: MidiInstrument is now a discriminated union (simple-synth | gm); createDefaultMidiInstrument(kind?) is kind-aware. - Seam: IMidiSynth interface + createSynthForInstrument factory route all three note producers (live per-track bus, piano-roll preview, offline export renderer) through one place. MidiSynth implements IMidiSynth. - GmSampleBank: shared HMR-persisted singleton storing raw Float32 PCM so a buffer builds synchronously for any context rate (live vs export) without decodeAudioData. Pure, unit-tested base64->PCM / zone-select / playbackRate helpers. Lazy-fetches public/instruments/gm/NNNN.json via BASE_URL; missing program degrades gracefully (silent, no crash). - WavetableSynth: IMidiSynth over the bank (looped/one-shot sample + gain envelope from the zone). - Preload orchestration: export preloads all GM programs once before the clip loop; the scheduler preloads on instrument-select (before play) and on start, so GM renders with sound and the first live note doesn't drop. - UI/store: GM in the instrument picker; properties tab branches on kind; setTrackMidiInstrument handles kind changes cleanly. - Asset: hand-built placeholder sine (program 0) proving the pipeline; the real FluidR3 .sf2 -> JSON converter is deferred (see GM-Sampler-Plan.md). Browser-verified: GM audible live and in export, simple-synth regression intact, graceful missing-asset handling. build + lint + 3315 tests green. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 5 — drums: - GmSampleBank is drum-aware: caches re-keyed by refId(program,isDrum) so a melodic program and a drum kit at the same program number are distinct assets; drum kits load from their own namespace (instruments/gm/drums/NNNN.json). - New GmSoundRef type; ensureLoaded/isLoaded/preload are drum-aware. - selectZoneIndex (pure, tested): covering zone wins; melodic falls back to the nearest zone by root key; drums get NO fallback, so an unmapped percussion note is silent rather than substituting a wrong drum. - WavetableSynth plays drum hits as one-shots out to the sample's full length (the PCM encodes the decay), not gated by note duration. - Percussion toggle in the properties tab; placeholder drum kit (0000.json). Phase 6 — full program set + UI: - gmPrograms.ts: 128 GM program names, the 16 families, 9 GM/GS drum kits, and name lookups. getMidiInstrumentLabel now reports the concrete program/kit name. - MidiInstrumentTab: grouped program picker (16 optgroups / 128 programs) for melodic, drum-kit picker for percussion; toggling percussion resets to a valid program. The picker lists every program even though only built assets resolve to sound (the rest degrade gracefully to silence). - Persistence test covers GM program/kit round-trip + clean kind-swap. Browser-verified end to end (picker renders 16 optgroups/128 programs + 9 kits, labels resolve, drums audible, unbuilt programs silent with no crash). build + lint + 3320 tests green. Real FluidR3 assets (the .sf2 converter) remain the only outstanding work. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… auto-approve toggle - FlashBoardChatService: replace thin system prompt with full harness (batching/step-budget, tool-family map, ~10 recipes, vision self-verify, audio awareness, autonomy + no-downscope rules, subfolder recursion, large-N chunking around the 2048-token cap) - aiTools policy/dispatch: normalizeToolName() strips the OpenAI `functions.` prefix so addClipSegment/executeBatch no longer fail as "Unknown tool" - FlashBoardComposer: add Auto-approve toggle pill, drop redundant "1 cr/round" credit chip - tests: aiToolNamePrefix + flashboardChatHarness invariants Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… 2b) The offline converter and the runtime/schema groundwork for real GM sounds. Generated assets are NOT committed — they're large, produced locally from the .sf2, and will move to a CDN later (GmSampleBank isolates the location). The picker degrades gracefully when an asset is absent. - scripts/build-gm-instruments.mjs: offline FluidR3 GM (.sf2) -> JSON converter. Resolves SF2 generators (instrument-absolute + preset-offset, range intersection), envelope unit conversions (timecents->seconds, centibels->level), loop/tune/root-key, and collapses velocity layers to one zone per key range. Verified: the grand piano converts to 20 keyboard zones and plays in tune across the keyboard with correct loop + note-off. Includes a `measure` dry-run mode for sizing the full set. - gmAsset.ts / GmSampleBank.ts: per-zone `sampleRate` (SF2 samples have differing rates), falling back to the asset rate. Backward compatible. - WavetableSynth: release a melodic note at the actual note-off via cancelAndHoldAtTime, instead of waiting for the (often tens-of-seconds) decay to finish — real GM decays are long, so a short note must not ring on. - .gitignore: ignore the source `.sf2`/`soundfonts/` and the generated public/instruments/gm/**/*.json (README stays tracked). Untrack the prior placeholder assets. - soundfont2 added as a dev dependency (offline use only). build + lint green; tests reused (3320, src unchanged since the last run). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ix (#193) Asset pipeline & committed lite set: - Store PCM as Int16 (pcmFormat: 'i16'), lossless and half the size of Float32; GmSampleBank decodes via new decodeBase64ToInt16. Legacy/absent = 'f32'. - Converter gains --profile lite|full. lite = curated 21-instrument set (20 melodic + Standard drum kit) downsampled to 22.05kHz (area-averaging decimation, loop points rescaled); full = native rate, any program set. - Commit the lite set (~28MB, 22kHz) into public/instruments/gm so clones/deploys have sound with no hosting. .gitignore keeps the full set ignored except an explicit allowlist of exactly the 21 lite files. Full set stays reproducible from the .sf2. - Fix converter drum-kit selection: prefer bank 128 preset 0 (real Standard kit) over the first bank-128 preset in file order. UI: - Rename the GM instrument kind label 'General MIDI' -> 'Wavetable Synth'. - Restrict the program/drum pickers to the lite set without dropping the full GM data: GM_LITE_PROGRAMS / GM_PICKER_FAMILIES / GM_PICKER_DRUM_KITS. Widen to the full lists when the full set is hosted. Bug fix (sustained instruments didn't sustain): - WavetableSynth note-off used cancelAndHoldAtTime, which in Chrome failed to anchor the sustain, so the release ramp bled across the whole note (organ/pad/strings faded out). Anchor the level explicitly with cancelScheduledValues + setValueAtTime(holdLevel), matching MidiSynth. Verified via offline render: organ/pad/strings now hold then release; piano decays naturally. Docs: GM-Sampler-Plan.md asset-profiles/sizing/git section. Tests: Int16 decode. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The on-disk project save/load rebuilds tracks from an explicit field whitelist (unlike the in-memory loadState spread), and midiInstrument was never in it — so the synth + GM program were dropped when written to project.ln.json and not restored on a hard refresh. Pre-existing gap from #182, surfaced by the GM instrument. Add midiInstrument to both the projectSave and projectLoad track mappings (mirroring audioState). Add a save-side regression test and correct the midiPersistence test's comment that wrongly claimed the in-memory path was the on-disk path. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New MIDI tracks now default to the GM Wavetable Synth (program 0, Acoustic Grand Piano, in the committed lite set) instead of the simple oscillator synth. Changed the default kind in createDefaultMidiInstrument; the kind-switch path passes kind explicitly so it is unaffected. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
193 gm sound for midi tracks
The tab-bar "+" now spawns a fresh, independent Preview side-by-side instead of focusing the existing one. Adds MULTI_INSTANCE_PANEL_TYPES (preview only; timeline stays single), routes preview through addPreviewPanel with the clicked group as the split target, and lists Preview first in the add menu. Each instance keeps its own panelId / render target / composition selector. Removes the now-redundant +/- buttons from the preview toolbar (PreviewControls) and their unused props/selectors. Bumps version to 2.1.1 and adds the changelog entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Spawn multiple independent Preview panels from the tab-bar "+" (side-by-side), each with its own render target and composition selector. Timeline stays single-instance. Removes the redundant +/- buttons from the preview toolbar. Bumps to 2.1.1 + changelog.
Build + lint verified locally before commit.
🤖 Generated with Claude Code