Skip to content

Multiple preview panels via dock "+" (v2.1.1)#238

Merged
Sportinger merged 10 commits into
masterfrom
staging
Jun 1, 2026
Merged

Multiple preview panels via dock "+" (v2.1.1)#238
Sportinger merged 10 commits into
masterfrom
staging

Conversation

@Sportinger

Copy link
Copy Markdown
Owner

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

Sportinger and others added 10 commits May 31, 2026 17:58
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>
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>
@Sportinger Sportinger merged commit a17f870 into master Jun 1, 2026
6 of 7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants