- SpessaSynth bank loading fails in AudioWorklet for unknown reason
- Set _initialized=false after init so playNote uses oscillator path
- Server-side FluidSynth render produces authentic SoundFont audio on Export
- playNote logs warning when _currentSfId is null (no bank)
- _playNoteSpessa catches noteOn errors, falls back to _playNoteOsc
- Prevents 'No preset found' from breaking playback
- Add importmap in index.html to map spessasynth_core to CDN URL
- Load spessasynth_lib from CDN directly (with importmap resolving deps)
- WorkerSynthesizer fallback also uses CDN import
- Ensures library and processor use same spessasynth_core version
- Download spessasynth_processor.min.js from CDN, serve as static file
- Change __SpessaSynthCDN to /static/js/services/
- Fix version mismatch between esm.sh library and CDN processor
- Clone ArrayBuffer before sendToWorklet (transfer neuters original)
- Re-await synthInstance.isReady after addSoundBank
- Force controllerChange(0,0,0) + programChange(0,0) after load
- Add _initInProgress flag to prevent concurrent init calls
- Remove auto-loadSoundFont from init (confuses user when loading DSK)
- SF3 loading happens only when user selects an instrument
- jdelivr raw index.js has bare import 'spessasynth_core' which browser can't resolve
- esm.sh automatically rewrites bare imports to full URLs
- Keep jdelivr for worklet processor (self-contained, no imports)
- WorkerSynthesizer fallback also uses esm.sh
- Fix CDN URL: spessasynth_lib@4.3.1/dist/index.js (was @latest with wrong path)
- Use WorkletSynthesizer instead of Synthesizer (correct class name)
- Add audioWorklet.addModule() for processor CDN URL
- Await synth.isReady before use
- Use soundBankManager.addSoundBank() (correct API)
- Fallback to WorkerSynthesizer if AudioWorklet fails
- getAudioContext() calls SonicSF.init on every call (not just first)
- soundfontPlayer.init retries after 1s if SpessaSynth module not loaded yet
- Auto-loads sgm_v2.01 SF3 on successful init
- Fixes race condition: module script loads after regular scripts
- Sub-tab selection loop only active when st.isLooping is true
- End-of-playback loop uses st.isLooping, not global isLoopingSelection
- SonicSF.stopAll() sets gain 0 and stops oscillators immediately
- Piano roll ruler click during playback now seeks and continues playing
- ADSR envelope uses linearRampToValueAtTime for crack-free release
- stopAll ramps gain to 0 in 20ms before stopping oscillators