fix: explicit selectInstrument for SpessaSynth bank+program routing
Per md/37_FIXVST3_SUGGEST.md: - Add SonicSF.selectInstrument(channel, bank, program, sfId) method - Always sends controllerChange(CC0, bank) + programChange(prog) to SpessaSynth - _playNoteSpessa also sends CC32 (bank LSB) before programChange - Remove default bank auto-load from init (cleaner startup) - Fix syntax error (double }} from previous edit) - playNote Spessa: use program arg + synthEngine bank/program properly
This commit is contained in:
@@ -6522,10 +6522,11 @@ const App = () => {
|
||||
setInstrumentSelectorTrackId(null);
|
||||
setSynthCategory(null);
|
||||
setSelectedSoundFontId(null);
|
||||
// Trigger SpessaSynth SF3 load when soundfont instrument selected
|
||||
if (window.SonicSF && window.SonicSF.loadSoundFont && instrumentId && typeof instrumentId === 'string' && instrumentId.startsWith('sf_')) {
|
||||
// Trigger SpessaSynth load + program change when soundfont instrument selected
|
||||
if (window.SonicSF && window.SonicSF.selectInstrument && instrumentId && typeof instrumentId === 'string' && instrumentId.startsWith('sf_')) {
|
||||
const sfId = instrumentId.replace('sf_', '');
|
||||
window.SonicSF.loadSoundFont(sfId);
|
||||
const ch = sfBank === 128 ? 9 : 0;
|
||||
window.SonicSF.selectInstrument(ch, sfBank || 0, sfProg || 0, sfId);
|
||||
}
|
||||
setSubTabs(prev => prev.map(s => {
|
||||
if (s.trackId !== trackId) return s;
|
||||
|
||||
@@ -170,8 +170,8 @@ const[hoveredTrackId,setHoveredTrackId]=useState(null);const openPanel=id=>{if(i
|
||||
setSynthCategory('soundfont');setSelectedSoundFontId(track.instrumentId);setSfPresets(null);const sfIdParam=track.instrumentId.replace('sf_','');window.SonicAPI.listSoundfontInstruments(sfIdParam).then(data=>setSfPresets(data.presets||[])).catch(()=>setSfPresets([]));}else{// Reset synth state and always reload plugin data
|
||||
setSynthCategory(null);setSelectedSoundFontId(null);setSfPresetSearchQuery('');window.SonicAPI.listPlugins().then(data=>setInstrumentSelectorData(data)).catch(e=>console.error('listPlugins failed:',e));}};const closeInstrumentSelector=()=>{setInstrumentSelectorTrackId(null);setSynthCategory(null);setSelectedSoundFontId(null);setSfPresetSearchQuery('');};const[synthCategory,setSynthCategory]=useState(null);// 'vst' | 'soundfont'
|
||||
const[selectedSoundFontId,setSelectedSoundFontId]=useState(null);const[sfPresets,setSfPresets]=useState(null);// presets from SoundFont
|
||||
const[instrumentDropdownTrackId,setInstrumentDropdownTrackId]=useState(null);const[instrumentDropdownBtnRect,setInstrumentDropdownBtnRect]=useState(null);const[instrumentSearchQuery,setInstrumentSearchQuery]=useState('');const[sfPresetSearchQuery,setSfPresetSearchQuery]=useState('');const filteredInstruments=useMemo(()=>{if(!instrumentSelectorData||!instrumentSearchQuery)return{soundfonts:instrumentSelectorData?.soundfonts||[],vst:instrumentSelectorData?.vst_instruments||[]};const q=instrumentSearchQuery.toLowerCase();return{soundfonts:(instrumentSelectorData.soundfonts||[]).filter(sf=>(sf.display||sf.name||sf.id).toLowerCase().includes(q)),vst:(instrumentSelectorData.vst_instruments||[]).filter(v=>(v.name||v.id).toLowerCase().includes(q))};},[instrumentSearchQuery,instrumentSelectorData]);useEffect(()=>{if(!instrumentSelectorData){window.SonicAPI.listPlugins().then(data=>setInstrumentSelectorData(data)).catch(()=>{});}},[]);useEffect(()=>{if(!instrumentDropdownTrackId)return;const handler=e=>{setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);setInstrumentSearchQuery('');};document.addEventListener('click',handler);return()=>document.removeEventListener('click',handler);},[instrumentDropdownTrackId]);const GM_INSTRUMENTS=["Acoustic Grand Piano","Bright Acoustic Piano","Electric Grand Piano","Honky-tonk Piano","Electric Piano 1","Electric Piano 2","Harpsichord","Clavi","Celesta","Glockenspiel","Music Box","Vibraphone","Marimba","Xylophone","Tubular Bells","Dulcimer","Drawbar Organ","Percussive Organ","Rock Organ","Church Organ","Reed Organ","Accordion","Harmonica","Tango Accordion","Acoustic Guitar (nylon)","Acoustic Guitar (steel)","Electric Guitar (jazz)","Electric Guitar (clean)","Electric Guitar (muted)","Overdriven Guitar","Distortion Guitar","Guitar harmonics","Acoustic Bass","Electric Bass (finger)","Electric Bass (pick)","Fretless Bass","Slap Bass 1","Slap Bass 2","Synth Bass 1","Synth Bass 2","Violin","Viola","Cello","Contrabass","Tremolo Strings","Pizzicato Strings","Orchestral Harp","Timpani","String Ensemble 1","String Ensemble 2","Synth Strings 1","Synth Strings 2","Choir Aahs","Voice Oohs","Synth Voice","Orchestra Hit","Trumpet","Trombone","Tuba","Muted Trumpet","French Horn","Brass Section","Synth Brass 1","Synth Brass 2","Soprano Sax","Alto Sax","Tenor Sax","Baritone Sax","Oboe","English Horn","Bassoon","Clarinet","Piccolo","Flute","Recorder","Pan Flute","Blown Bottle","Shakuhachi","Whistle","Ocarina","Lead 1 (square)","Lead 2 (sawtooth)","Lead 3 (calliope)","Lead 4 (chiff)","Lead 5 (charang)","Lead 6 (voice)","Lead 7 (fifths)","Lead 8 (bass+lead)","Pad 1 (new age)","Pad 2 (warm)","Pad 3 (polysynth)","Pad 4 (choir)","Pad 5 (bowed)","Pad 6 (metallic)","Pad 7 (halo)","Pad 8 (sweep)","FX 1 (rain)","FX 2 (soundtrack)","FX 3 (crystal)","FX 4 (atmosphere)","FX 5 (brightness)","FX 6 (goblins)","FX 7 (echoes)","FX 8 (sci-fi)","Sitar","Banjo","Shamisen","Koto","Kalimba","Bag pipe","Fiddle","Shanai","Tinkle Bell","Agogo","Steel Drums","Woodblock","Taiko Drum","Melodic Tom","Synth Drum","Reverse Cymbal","Guitar Fret Noise","Breath Noise","Seashore","Bird Tweet","Telephone Ring","Helicopter","Applause","Gunshot"];const setTrackInstrumentWithProgram=(trackId,instrumentId,programNumber,displayName,bankNumber)=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const hasInstrument=!!instrumentId;const isSfInstrument=instrumentId&&typeof instrumentId==='string'&&instrumentId.startsWith('sf_');const sfBank=bankNumber!==undefined?bankNumber:isSfInstrument?0:undefined;const sfProg=programNumber!==undefined?programNumber:undefined;const instrType=isSfInstrument?'soundfont':hasInstrument?'vst3':'default';const synthEngine=hasInstrument?{type:instrType,plugin_id:instrumentId,soundfont_bank:sfBank!==undefined?sfBank:0,soundfont_program:sfProg!==undefined?sfProg:0,soundfont_id:isSfInstrument?instrumentId.replace('sf_',''):''}:undefined;return{...t,instrumentId,instrumentProgram:sfProg,instrumentName:displayName,soundfont_bank:sfBank,soundfont_program:sfProg,synth_engine:synthEngine,type:hasInstrument?'MIDI':t.type==='MIDI'?'audio':t.type};}));setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);setInstrumentSelectorTrackId(null);setSynthCategory(null);setSelectedSoundFontId(null);// Trigger SpessaSynth SF3 load when soundfont instrument selected
|
||||
if(window.SonicSF&&window.SonicSF.loadSoundFont&&instrumentId&&typeof instrumentId==='string'&&instrumentId.startsWith('sf_')){const sfId=instrumentId.replace('sf_','');window.SonicSF.loadSoundFont(sfId);}setSubTabs(prev=>prev.map(s=>{if(s.trackId!==trackId)return s;return{...s,instrumentProgram:programNumber!==undefined?programNumber:undefined,instrumentName:displayName,instrumentId};}));const playingSub=subTabs.find(s=>s.trackId===trackId&&s.type==='PIANO_ROLL'&&s.isPlaying);if(playingSub){const pid=playingSub.id;const ctx=getAudioContext();const tNode=activeTrackNodesRef.current[trackId];if(tNode&&tNode.gainNode){tNode.gainNode.gain.setValueAtTime(tNode.gainNode.gain.value||1,ctx.currentTime);tNode.gainNode.gain.linearRampToValueAtTime(0.001,ctx.currentTime+0.04);}setTimeout(()=>{stopAllPlayback();if(tNode&&tNode.gainNode){const trackData=activeTracksRef.current?activeTracksRef.current.find(t=>t.id===trackId):null;const volDb=trackData?trackData.volumeDb??0:0;const volLinear=volDb<=-50?0:Math.pow(10,volDb/20);tNode.gainNode.gain.setValueAtTime(0.001,ctx.currentTime);tNode.gainNode.gain.linearRampToValueAtTime(volLinear||0.8,ctx.currentTime+0.015);}const context=getAudioContext();const offset=playingSub.currentTime||0;startOffsetTimeRef.current=offset;startAudioTimeRef.current=context.currentTime;startBufferOffsetRef.current=offset*(playingSub.speed||1.0);schedulePianoRollMidi(playingSub,offset);startSubTabPlayback(playingSub,offset);setSubTabs(prev=>prev.map(s=>s.id===pid?{...s,isPlaying:true}:s));if(subTabsRef.current){subTabsRef.current=subTabsRef.current.map(s=>s.id===pid?{...s,isPlaying:true}:s);}animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);},60);}setTimeout(()=>lucide.createIcons(),50);};const setTrackInstrument=(trackId,instrumentId,displayName)=>{setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);if(instrumentId&&instrumentId.startsWith('sf_')){// Set instrument on track immediately so Synth button shows the name
|
||||
const[instrumentDropdownTrackId,setInstrumentDropdownTrackId]=useState(null);const[instrumentDropdownBtnRect,setInstrumentDropdownBtnRect]=useState(null);const[instrumentSearchQuery,setInstrumentSearchQuery]=useState('');const[sfPresetSearchQuery,setSfPresetSearchQuery]=useState('');const filteredInstruments=useMemo(()=>{if(!instrumentSelectorData||!instrumentSearchQuery)return{soundfonts:instrumentSelectorData?.soundfonts||[],vst:instrumentSelectorData?.vst_instruments||[]};const q=instrumentSearchQuery.toLowerCase();return{soundfonts:(instrumentSelectorData.soundfonts||[]).filter(sf=>(sf.display||sf.name||sf.id).toLowerCase().includes(q)),vst:(instrumentSelectorData.vst_instruments||[]).filter(v=>(v.name||v.id).toLowerCase().includes(q))};},[instrumentSearchQuery,instrumentSelectorData]);useEffect(()=>{if(!instrumentSelectorData){window.SonicAPI.listPlugins().then(data=>setInstrumentSelectorData(data)).catch(()=>{});}},[]);useEffect(()=>{if(!instrumentDropdownTrackId)return;const handler=e=>{setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);setInstrumentSearchQuery('');};document.addEventListener('click',handler);return()=>document.removeEventListener('click',handler);},[instrumentDropdownTrackId]);const GM_INSTRUMENTS=["Acoustic Grand Piano","Bright Acoustic Piano","Electric Grand Piano","Honky-tonk Piano","Electric Piano 1","Electric Piano 2","Harpsichord","Clavi","Celesta","Glockenspiel","Music Box","Vibraphone","Marimba","Xylophone","Tubular Bells","Dulcimer","Drawbar Organ","Percussive Organ","Rock Organ","Church Organ","Reed Organ","Accordion","Harmonica","Tango Accordion","Acoustic Guitar (nylon)","Acoustic Guitar (steel)","Electric Guitar (jazz)","Electric Guitar (clean)","Electric Guitar (muted)","Overdriven Guitar","Distortion Guitar","Guitar harmonics","Acoustic Bass","Electric Bass (finger)","Electric Bass (pick)","Fretless Bass","Slap Bass 1","Slap Bass 2","Synth Bass 1","Synth Bass 2","Violin","Viola","Cello","Contrabass","Tremolo Strings","Pizzicato Strings","Orchestral Harp","Timpani","String Ensemble 1","String Ensemble 2","Synth Strings 1","Synth Strings 2","Choir Aahs","Voice Oohs","Synth Voice","Orchestra Hit","Trumpet","Trombone","Tuba","Muted Trumpet","French Horn","Brass Section","Synth Brass 1","Synth Brass 2","Soprano Sax","Alto Sax","Tenor Sax","Baritone Sax","Oboe","English Horn","Bassoon","Clarinet","Piccolo","Flute","Recorder","Pan Flute","Blown Bottle","Shakuhachi","Whistle","Ocarina","Lead 1 (square)","Lead 2 (sawtooth)","Lead 3 (calliope)","Lead 4 (chiff)","Lead 5 (charang)","Lead 6 (voice)","Lead 7 (fifths)","Lead 8 (bass+lead)","Pad 1 (new age)","Pad 2 (warm)","Pad 3 (polysynth)","Pad 4 (choir)","Pad 5 (bowed)","Pad 6 (metallic)","Pad 7 (halo)","Pad 8 (sweep)","FX 1 (rain)","FX 2 (soundtrack)","FX 3 (crystal)","FX 4 (atmosphere)","FX 5 (brightness)","FX 6 (goblins)","FX 7 (echoes)","FX 8 (sci-fi)","Sitar","Banjo","Shamisen","Koto","Kalimba","Bag pipe","Fiddle","Shanai","Tinkle Bell","Agogo","Steel Drums","Woodblock","Taiko Drum","Melodic Tom","Synth Drum","Reverse Cymbal","Guitar Fret Noise","Breath Noise","Seashore","Bird Tweet","Telephone Ring","Helicopter","Applause","Gunshot"];const setTrackInstrumentWithProgram=(trackId,instrumentId,programNumber,displayName,bankNumber)=>{updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const hasInstrument=!!instrumentId;const isSfInstrument=instrumentId&&typeof instrumentId==='string'&&instrumentId.startsWith('sf_');const sfBank=bankNumber!==undefined?bankNumber:isSfInstrument?0:undefined;const sfProg=programNumber!==undefined?programNumber:undefined;const instrType=isSfInstrument?'soundfont':hasInstrument?'vst3':'default';const synthEngine=hasInstrument?{type:instrType,plugin_id:instrumentId,soundfont_bank:sfBank!==undefined?sfBank:0,soundfont_program:sfProg!==undefined?sfProg:0,soundfont_id:isSfInstrument?instrumentId.replace('sf_',''):''}:undefined;return{...t,instrumentId,instrumentProgram:sfProg,instrumentName:displayName,soundfont_bank:sfBank,soundfont_program:sfProg,synth_engine:synthEngine,type:hasInstrument?'MIDI':t.type==='MIDI'?'audio':t.type};}));setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);setInstrumentSelectorTrackId(null);setSynthCategory(null);setSelectedSoundFontId(null);// Trigger SpessaSynth load + program change when soundfont instrument selected
|
||||
if(window.SonicSF&&window.SonicSF.selectInstrument&&instrumentId&&typeof instrumentId==='string'&&instrumentId.startsWith('sf_')){const sfId=instrumentId.replace('sf_','');const ch=sfBank===128?9:0;window.SonicSF.selectInstrument(ch,sfBank||0,sfProg||0,sfId);}setSubTabs(prev=>prev.map(s=>{if(s.trackId!==trackId)return s;return{...s,instrumentProgram:programNumber!==undefined?programNumber:undefined,instrumentName:displayName,instrumentId};}));const playingSub=subTabs.find(s=>s.trackId===trackId&&s.type==='PIANO_ROLL'&&s.isPlaying);if(playingSub){const pid=playingSub.id;const ctx=getAudioContext();const tNode=activeTrackNodesRef.current[trackId];if(tNode&&tNode.gainNode){tNode.gainNode.gain.setValueAtTime(tNode.gainNode.gain.value||1,ctx.currentTime);tNode.gainNode.gain.linearRampToValueAtTime(0.001,ctx.currentTime+0.04);}setTimeout(()=>{stopAllPlayback();if(tNode&&tNode.gainNode){const trackData=activeTracksRef.current?activeTracksRef.current.find(t=>t.id===trackId):null;const volDb=trackData?trackData.volumeDb??0:0;const volLinear=volDb<=-50?0:Math.pow(10,volDb/20);tNode.gainNode.gain.setValueAtTime(0.001,ctx.currentTime);tNode.gainNode.gain.linearRampToValueAtTime(volLinear||0.8,ctx.currentTime+0.015);}const context=getAudioContext();const offset=playingSub.currentTime||0;startOffsetTimeRef.current=offset;startAudioTimeRef.current=context.currentTime;startBufferOffsetRef.current=offset*(playingSub.speed||1.0);schedulePianoRollMidi(playingSub,offset);startSubTabPlayback(playingSub,offset);setSubTabs(prev=>prev.map(s=>s.id===pid?{...s,isPlaying:true}:s));if(subTabsRef.current){subTabsRef.current=subTabsRef.current.map(s=>s.id===pid?{...s,isPlaying:true}:s);}animationFrameIdRef.current=requestAnimationFrame(updatePlayhead);},60);}setTimeout(()=>lucide.createIcons(),50);};const setTrackInstrument=(trackId,instrumentId,displayName)=>{setInstrumentDropdownTrackId(null);setInstrumentDropdownBtnRect(null);if(instrumentId&&instrumentId.startsWith('sf_')){// Set instrument on track immediately so Synth button shows the name
|
||||
updateActiveTracks(prev=>prev.map(t=>{if(t.id!==trackId)return t;const sfClean=instrumentId.replace('sf_','');const synthEngine={type:'soundfont',plugin_id:instrumentId,soundfont_bank:0,soundfont_program:0,soundfont_id:sfClean};return{...t,instrumentId,instrumentProgram:undefined,instrumentName:displayName,synth_engine:synthEngine};}));setSelectedSoundFontId(instrumentId);setSynthCategory('soundfont');setInstrumentSelectorTrackId(trackId);setSfPresets(null);setSfPresetSearchQuery('');// Fetch actual presets from the SoundFont
|
||||
const sfIdParam=instrumentId.replace('sf_','');window.SonicAPI.listSoundfontInstruments(sfIdParam).then(data=>setSfPresets(data.presets||[])).catch(e=>{console.error('listSoundfontInstruments failed:',e);setSfPresets([]);});}else{setTrackInstrumentWithProgram(trackId,instrumentId,undefined,displayName);}};const[activeTool,setActiveTool]=useState('select');// 'select' | 'grab' | 'razor'
|
||||
const[snapValue,setSnapValue]=useState('free');// 'free', '1', '1/2', '1/4', '1/8', '1/16', '1/32'
|
||||
|
||||
@@ -31,7 +31,6 @@
|
||||
const _channels = Array.from({ length: 16 }, () => ({ bank: 0, program: 0, isPercussion: false }));
|
||||
let _nextMelodicChannel = 0;
|
||||
|
||||
// ── SpessaSynth integration state ──
|
||||
let _synthInstance = null;
|
||||
let _initialized = false;
|
||||
let _initInProgress = false;
|
||||
@@ -40,18 +39,15 @@
|
||||
const SonicSF = {
|
||||
loadedFonts: {},
|
||||
|
||||
// ── SpessaSynth init ──
|
||||
init: async function (audioContext) {
|
||||
if (_initialized && _synthInstance) return;
|
||||
if (_initInProgress) return;
|
||||
_initInProgress = true;
|
||||
if (!window.SpessaSynthClass || !window.__SpessaSynthCDN) {
|
||||
console.warn("[SonicSF] SpessaSynth not loaded yet. Retrying in 2s...");
|
||||
console.warn("[SonicSF] SpessaSynth CDN not loaded. Retrying in 2s...");
|
||||
_initInProgress = false;
|
||||
setTimeout(() => {
|
||||
if (!_initialized && window.SpessaSynthClass && audioContext) {
|
||||
this.init(audioContext);
|
||||
}
|
||||
if (!_initialized && window.SpessaSynthClass && audioContext) this.init(audioContext);
|
||||
}, 2000);
|
||||
return;
|
||||
}
|
||||
@@ -60,46 +56,16 @@
|
||||
await audioContext.audioWorklet.addModule(procUrl);
|
||||
_synthInstance = new window.SpessaSynthClass(audioContext);
|
||||
await _synthInstance.isReady;
|
||||
// Load default SF2 to verify parsing works
|
||||
try {
|
||||
const resp = await fetch("/api/v1/plugins/soundfonts/download/sgm_v2.01?t=" + Date.now());
|
||||
if (resp.ok) {
|
||||
const buf = await resp.arrayBuffer();
|
||||
await _synthInstance.soundBankManager.addSoundBank(buf.slice(0), "sgm_v2.01");
|
||||
await _synthInstance.isReady;
|
||||
// Check if presets were loaded by querying soundBankList
|
||||
const bankCount = _synthInstance.soundBankManager?.soundBankList?.length ?? 0;
|
||||
console.log("[SonicSF] SpessaSynth initialized. Banks loaded:", bankCount);
|
||||
if (bankCount > 0) {
|
||||
_initialized = true;
|
||||
_initInProgress = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (bankErr) {
|
||||
console.warn("[SonicSF] Default bank load failed:", bankErr);
|
||||
}
|
||||
// Fallback: init without bank — oscillator will handle preview
|
||||
_initialized = true;
|
||||
_initInProgress = false;
|
||||
console.log("[SonicSF] SpessaSynth initialized (no sound bank). Oscillator fallback active.");
|
||||
console.log("[SonicSF] SpessaSynth initialized.");
|
||||
} catch (e) {
|
||||
_initInProgress = false;
|
||||
console.warn("[SonicSF] AudioWorklet failed:", e);
|
||||
// Try WorkerSynthesizer as fallback
|
||||
console.warn("[SonicSF] AudioWorklet init failed:", e);
|
||||
try {
|
||||
const mod = await import("https://cdn.jsdelivr.net/npm/spessasynth_lib@4.3.1/dist/index.js");
|
||||
_synthInstance = new mod.WorkerSynthesizer(audioContext);
|
||||
await _synthInstance.isReady;
|
||||
try {
|
||||
const resp = await fetch("/api/v1/plugins/soundfonts/download/sgm_v2.01?t=" + Date.now());
|
||||
if (resp.ok) {
|
||||
const buf = await resp.arrayBuffer();
|
||||
await _synthInstance.soundBankManager.addSoundBank(buf.slice(0), "sgm_v2.01");
|
||||
await _synthInstance.isReady;
|
||||
console.log("[SonicSF] Worker synth with bank OK.");
|
||||
}
|
||||
} catch (e2) {}
|
||||
_initialized = true;
|
||||
console.log("[SonicSF] SpessaSynth initialized via Worker.");
|
||||
} catch (e2) {
|
||||
@@ -107,9 +73,18 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
selectInstrument: async function (channel, bank, program, sfId) {
|
||||
if (!_initialized || !_synthInstance) return;
|
||||
if (sfId) await this.loadSoundFont(sfId);
|
||||
try { _synthInstance.controllerChange(channel, 0, bank); } catch (e) {}
|
||||
try { _synthInstance.controllerChange(channel, 32, 0); } catch (e) {}
|
||||
try { _synthInstance.programChange(channel, program); } catch (e) {}
|
||||
this.controllerChange(channel, 0, bank);
|
||||
this.programChange(channel, program);
|
||||
console.log("[SonicSF] selectInstrument ch", channel, "bank", bank, "prog", program, "sf", sfId);
|
||||
},
|
||||
|
||||
// ── Load SF3 from IndexedDB cache or server ──
|
||||
loadSoundFont: async function (sfId) {
|
||||
if (!_initialized || !_synthInstance) return;
|
||||
if (_currentSfId === sfId) return;
|
||||
@@ -128,36 +103,28 @@
|
||||
await window.SonicSFStorage.saveBuffer(sfId, buffer);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[SonicSF] Failed to load SoundFont:", sfId, e);
|
||||
return;
|
||||
console.error("[SonicSF] Failed to download:", sfId, e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
try {
|
||||
// Clone buffer before sending to worklet (avoids transfer neutering)
|
||||
const bufCopy = buffer.slice(0);
|
||||
await _synthInstance.soundBankManager.addSoundBank(bufCopy, sfId);
|
||||
await _synthInstance.isReady;
|
||||
// Force bank+program select on all channels to activate loaded bank
|
||||
for (let ch = 0; ch < 16; ch++) {
|
||||
try { _synthInstance.controllerChange(ch, 0, 0); } catch (e) {}
|
||||
try { _synthInstance.controllerChange(ch, 32, 0); } catch (e) {}
|
||||
try { _synthInstance.programChange(ch, 0); } catch (e) {}
|
||||
}
|
||||
_currentSfId = sfId;
|
||||
console.log("[SonicSF] SoundFont loaded:", sfId);
|
||||
return true;
|
||||
} catch (e) {
|
||||
console.error("[SonicSF] Error parsing SF in SpessaSynth:", sfId, e);
|
||||
// Invalidate cache and retry once with fresh download
|
||||
console.error("[SonicSF] Error loading SF in SpessaSynth:", sfId, e);
|
||||
if (window.SonicSFStorage) {
|
||||
try {
|
||||
const db = await window.SonicSFStorage.openDB();
|
||||
const tx = db.transaction(window.SonicSFStorage.storeName, "readwrite");
|
||||
tx.objectStore(window.SonicSFStorage.storeName).delete(sfId);
|
||||
console.log("[SonicSF] Invalidated cache for:", sfId);
|
||||
} catch (ce) {}
|
||||
}
|
||||
// Clear current SF so next load will actually run
|
||||
_currentSfId = null;
|
||||
return false;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -193,11 +160,7 @@
|
||||
program = program !== undefined ? program : (synthEngine.soundfont_program || 0);
|
||||
}
|
||||
const channel = this.allocateChannel(bank);
|
||||
this.controllerChange(channel, 0, bank);
|
||||
this.programChange(channel, program);
|
||||
if (_initialized && synthEngine && synthEngine.soundfont_id) {
|
||||
this.loadSoundFont(synthEngine.soundfont_id);
|
||||
}
|
||||
this.selectInstrument(channel, bank, program, synthEngine?.soundfont_id);
|
||||
return channel;
|
||||
},
|
||||
|
||||
@@ -206,33 +169,23 @@
|
||||
return { ..._channels[channel] };
|
||||
},
|
||||
|
||||
loadSoundFontLegacy: async function (url) {
|
||||
if (this.loadedFonts[url]) return this.loadedFonts[url];
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) throw new Error('Failed to load SoundFont: ' + url);
|
||||
const buffer = await resp.arrayBuffer();
|
||||
this.loadedFonts[url] = buffer;
|
||||
return buffer;
|
||||
},
|
||||
|
||||
playNote: function (note, velocity, durationMs, startTime, program, destinationNode, channel, synthEngine) {
|
||||
// SpessaSynth path
|
||||
if (_initialized && _synthInstance && _currentSfId) {
|
||||
return this._playNoteSpessa(note, velocity, durationMs, startTime, program, destinationNode, channel, synthEngine);
|
||||
return this._playNoteSpessa(note, velocity, durationMs, startTime, program, channel, synthEngine);
|
||||
}
|
||||
// Fallback oscillator path
|
||||
return this._playNoteOsc(note, velocity, durationMs, startTime, program, destinationNode, channel, synthEngine);
|
||||
},
|
||||
|
||||
_playNoteSpessa: function (note, velocity, durationMs, startTime, program, destinationNode, channel, synthEngine) {
|
||||
_playNoteSpessa: function (note, velocity, durationMs, startTime, program, channel, synthEngine) {
|
||||
const ctx = getCtx();
|
||||
const midiPitch = Math.min(127, Math.max(0, parseInt(note) || 60));
|
||||
const midiVel = Math.min(127, Math.max(1, Math.floor((typeof velocity === 'number' ? (velocity > 1 ? velocity : velocity * 127) : 100))));
|
||||
|
||||
if (synthEngine) {
|
||||
const ch = channel !== undefined ? channel : (synthEngine.soundfont_bank === 128 ? 9 : 0);
|
||||
this.controllerChange(ch, 0, synthEngine.soundfont_bank || 0);
|
||||
this.programChange(ch, synthEngine.soundfont_program || 0);
|
||||
try { _synthInstance.controllerChange(ch, 0, synthEngine.soundfont_bank || 0); } catch (e) {}
|
||||
try { _synthInstance.controllerChange(ch, 32, 0); } catch (e) {}
|
||||
try { _synthInstance.programChange(ch, synthEngine.soundfont_program || 0); } catch (e) {}
|
||||
if (channel === undefined) channel = ch;
|
||||
}
|
||||
if (channel === undefined) channel = 0;
|
||||
@@ -241,20 +194,13 @@
|
||||
const now = ctx.currentTime;
|
||||
const scheduledTime = (typeof startTime === 'number' && startTime > now) ? (startTime - now) : 0;
|
||||
|
||||
if (scheduledTime > 0) {
|
||||
setTimeout(() => {
|
||||
if (!_synthInstance) return;
|
||||
try {
|
||||
_synthInstance.noteOn(channel, midiPitch, midiVel);
|
||||
setTimeout(() => { try { _synthInstance.noteOff(channel, midiPitch); } catch (e) {} }, durSec * 1000);
|
||||
} catch (e) {}
|
||||
}, scheduledTime * 1000);
|
||||
} else {
|
||||
const doNoteOn = () => {
|
||||
try {
|
||||
_synthInstance.noteOn(channel, midiPitch, midiVel);
|
||||
setTimeout(() => { try { _synthInstance.noteOff(channel, midiPitch); } catch (e) {} }, durSec * 1000);
|
||||
} catch (e) {}
|
||||
}
|
||||
};
|
||||
if (scheduledTime > 0) setTimeout(doNoteOn, scheduledTime * 1000); else doNoteOn();
|
||||
},
|
||||
|
||||
_playNoteOsc: function (note, velocity, durationMs, startTime, program, destinationNode, channel, synthEngine) {
|
||||
@@ -389,9 +335,7 @@
|
||||
stopAll: function () {
|
||||
if (_initialized && _synthInstance) {
|
||||
try {
|
||||
for (let ch = 0; ch < 16; ch++) {
|
||||
_synthInstance.allNotesOff(ch);
|
||||
}
|
||||
for (let ch = 0; ch < 16; ch++) _synthInstance.allNotesOff(ch);
|
||||
} catch (e) {}
|
||||
}
|
||||
const ctx = getCtx();
|
||||
@@ -402,9 +346,7 @@
|
||||
entry.gain.gain.cancelScheduledValues(now);
|
||||
entry.gain.gain.setValueAtTime(0, now);
|
||||
}
|
||||
if (entry.osc) {
|
||||
try { entry.osc.stop(now); } catch (e) { }
|
||||
}
|
||||
if (entry.osc) try { entry.osc.stop(now); } catch (e) { }
|
||||
} catch (e) { }
|
||||
});
|
||||
Object.keys(activeOscillators).forEach(k => delete activeOscillators[k]);
|
||||
|
||||
Reference in New Issue
Block a user