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);
|
setInstrumentSelectorTrackId(null);
|
||||||
setSynthCategory(null);
|
setSynthCategory(null);
|
||||||
setSelectedSoundFontId(null);
|
setSelectedSoundFontId(null);
|
||||||
// Trigger SpessaSynth SF3 load when soundfont instrument selected
|
// Trigger SpessaSynth load + program change when soundfont instrument selected
|
||||||
if (window.SonicSF && window.SonicSF.loadSoundFont && instrumentId && typeof instrumentId === 'string' && instrumentId.startsWith('sf_')) {
|
if (window.SonicSF && window.SonicSF.selectInstrument && instrumentId && typeof instrumentId === 'string' && instrumentId.startsWith('sf_')) {
|
||||||
const sfId = instrumentId.replace('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 => {
|
setSubTabs(prev => prev.map(s => {
|
||||||
if (s.trackId !== trackId) return 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('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'
|
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[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
|
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.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
|
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
|
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 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'
|
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 }));
|
const _channels = Array.from({ length: 16 }, () => ({ bank: 0, program: 0, isPercussion: false }));
|
||||||
let _nextMelodicChannel = 0;
|
let _nextMelodicChannel = 0;
|
||||||
|
|
||||||
// ── SpessaSynth integration state ──
|
|
||||||
let _synthInstance = null;
|
let _synthInstance = null;
|
||||||
let _initialized = false;
|
let _initialized = false;
|
||||||
let _initInProgress = false;
|
let _initInProgress = false;
|
||||||
@@ -40,18 +39,15 @@
|
|||||||
const SonicSF = {
|
const SonicSF = {
|
||||||
loadedFonts: {},
|
loadedFonts: {},
|
||||||
|
|
||||||
// ── SpessaSynth init ──
|
|
||||||
init: async function (audioContext) {
|
init: async function (audioContext) {
|
||||||
if (_initialized && _synthInstance) return;
|
if (_initialized && _synthInstance) return;
|
||||||
if (_initInProgress) return;
|
if (_initInProgress) return;
|
||||||
_initInProgress = true;
|
_initInProgress = true;
|
||||||
if (!window.SpessaSynthClass || !window.__SpessaSynthCDN) {
|
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;
|
_initInProgress = false;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!_initialized && window.SpessaSynthClass && audioContext) {
|
if (!_initialized && window.SpessaSynthClass && audioContext) this.init(audioContext);
|
||||||
this.init(audioContext);
|
|
||||||
}
|
|
||||||
}, 2000);
|
}, 2000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -60,46 +56,16 @@
|
|||||||
await audioContext.audioWorklet.addModule(procUrl);
|
await audioContext.audioWorklet.addModule(procUrl);
|
||||||
_synthInstance = new window.SpessaSynthClass(audioContext);
|
_synthInstance = new window.SpessaSynthClass(audioContext);
|
||||||
await _synthInstance.isReady;
|
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;
|
_initialized = true;
|
||||||
_initInProgress = false;
|
_initInProgress = false;
|
||||||
return;
|
console.log("[SonicSF] SpessaSynth initialized.");
|
||||||
}
|
|
||||||
}
|
|
||||||
} 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.");
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_initInProgress = false;
|
_initInProgress = false;
|
||||||
console.warn("[SonicSF] AudioWorklet failed:", e);
|
console.warn("[SonicSF] AudioWorklet init failed:", e);
|
||||||
// Try WorkerSynthesizer as fallback
|
|
||||||
try {
|
try {
|
||||||
const mod = await import("https://cdn.jsdelivr.net/npm/spessasynth_lib@4.3.1/dist/index.js");
|
const mod = await import("https://cdn.jsdelivr.net/npm/spessasynth_lib@4.3.1/dist/index.js");
|
||||||
_synthInstance = new mod.WorkerSynthesizer(audioContext);
|
_synthInstance = new mod.WorkerSynthesizer(audioContext);
|
||||||
await _synthInstance.isReady;
|
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;
|
_initialized = true;
|
||||||
console.log("[SonicSF] SpessaSynth initialized via Worker.");
|
console.log("[SonicSF] SpessaSynth initialized via Worker.");
|
||||||
} catch (e2) {
|
} 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) {
|
loadSoundFont: async function (sfId) {
|
||||||
if (!_initialized || !_synthInstance) return;
|
if (!_initialized || !_synthInstance) return;
|
||||||
if (_currentSfId === sfId) return;
|
if (_currentSfId === sfId) return;
|
||||||
@@ -128,36 +103,28 @@
|
|||||||
await window.SonicSFStorage.saveBuffer(sfId, buffer);
|
await window.SonicSFStorage.saveBuffer(sfId, buffer);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[SonicSF] Failed to load SoundFont:", sfId, e);
|
console.error("[SonicSF] Failed to download:", sfId, e);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
// Clone buffer before sending to worklet (avoids transfer neutering)
|
|
||||||
const bufCopy = buffer.slice(0);
|
const bufCopy = buffer.slice(0);
|
||||||
await _synthInstance.soundBankManager.addSoundBank(bufCopy, sfId);
|
await _synthInstance.soundBankManager.addSoundBank(bufCopy, sfId);
|
||||||
await _synthInstance.isReady;
|
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;
|
_currentSfId = sfId;
|
||||||
console.log("[SonicSF] SoundFont loaded:", sfId);
|
console.log("[SonicSF] SoundFont loaded:", sfId);
|
||||||
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error("[SonicSF] Error parsing SF in SpessaSynth:", sfId, e);
|
console.error("[SonicSF] Error loading SF in SpessaSynth:", sfId, e);
|
||||||
// Invalidate cache and retry once with fresh download
|
|
||||||
if (window.SonicSFStorage) {
|
if (window.SonicSFStorage) {
|
||||||
try {
|
try {
|
||||||
const db = await window.SonicSFStorage.openDB();
|
const db = await window.SonicSFStorage.openDB();
|
||||||
const tx = db.transaction(window.SonicSFStorage.storeName, "readwrite");
|
const tx = db.transaction(window.SonicSFStorage.storeName, "readwrite");
|
||||||
tx.objectStore(window.SonicSFStorage.storeName).delete(sfId);
|
tx.objectStore(window.SonicSFStorage.storeName).delete(sfId);
|
||||||
console.log("[SonicSF] Invalidated cache for:", sfId);
|
|
||||||
} catch (ce) {}
|
} catch (ce) {}
|
||||||
}
|
}
|
||||||
// Clear current SF so next load will actually run
|
|
||||||
_currentSfId = null;
|
_currentSfId = null;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -193,11 +160,7 @@
|
|||||||
program = program !== undefined ? program : (synthEngine.soundfont_program || 0);
|
program = program !== undefined ? program : (synthEngine.soundfont_program || 0);
|
||||||
}
|
}
|
||||||
const channel = this.allocateChannel(bank);
|
const channel = this.allocateChannel(bank);
|
||||||
this.controllerChange(channel, 0, bank);
|
this.selectInstrument(channel, bank, program, synthEngine?.soundfont_id);
|
||||||
this.programChange(channel, program);
|
|
||||||
if (_initialized && synthEngine && synthEngine.soundfont_id) {
|
|
||||||
this.loadSoundFont(synthEngine.soundfont_id);
|
|
||||||
}
|
|
||||||
return channel;
|
return channel;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -206,33 +169,23 @@
|
|||||||
return { ..._channels[channel] };
|
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) {
|
playNote: function (note, velocity, durationMs, startTime, program, destinationNode, channel, synthEngine) {
|
||||||
// SpessaSynth path
|
|
||||||
if (_initialized && _synthInstance && _currentSfId) {
|
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);
|
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 ctx = getCtx();
|
||||||
const midiPitch = Math.min(127, Math.max(0, parseInt(note) || 60));
|
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))));
|
const midiVel = Math.min(127, Math.max(1, Math.floor((typeof velocity === 'number' ? (velocity > 1 ? velocity : velocity * 127) : 100))));
|
||||||
|
|
||||||
if (synthEngine) {
|
if (synthEngine) {
|
||||||
const ch = channel !== undefined ? channel : (synthEngine.soundfont_bank === 128 ? 9 : 0);
|
const ch = channel !== undefined ? channel : (synthEngine.soundfont_bank === 128 ? 9 : 0);
|
||||||
this.controllerChange(ch, 0, synthEngine.soundfont_bank || 0);
|
try { _synthInstance.controllerChange(ch, 0, synthEngine.soundfont_bank || 0); } catch (e) {}
|
||||||
this.programChange(ch, synthEngine.soundfont_program || 0);
|
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 = ch;
|
||||||
}
|
}
|
||||||
if (channel === undefined) channel = 0;
|
if (channel === undefined) channel = 0;
|
||||||
@@ -241,20 +194,13 @@
|
|||||||
const now = ctx.currentTime;
|
const now = ctx.currentTime;
|
||||||
const scheduledTime = (typeof startTime === 'number' && startTime > now) ? (startTime - now) : 0;
|
const scheduledTime = (typeof startTime === 'number' && startTime > now) ? (startTime - now) : 0;
|
||||||
|
|
||||||
if (scheduledTime > 0) {
|
const doNoteOn = () => {
|
||||||
setTimeout(() => {
|
|
||||||
if (!_synthInstance) return;
|
|
||||||
try {
|
try {
|
||||||
_synthInstance.noteOn(channel, midiPitch, midiVel);
|
_synthInstance.noteOn(channel, midiPitch, midiVel);
|
||||||
setTimeout(() => { try { _synthInstance.noteOff(channel, midiPitch); } catch (e) {} }, durSec * 1000);
|
setTimeout(() => { try { _synthInstance.noteOff(channel, midiPitch); } catch (e) {} }, durSec * 1000);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}, scheduledTime * 1000);
|
};
|
||||||
} else {
|
if (scheduledTime > 0) setTimeout(doNoteOn, scheduledTime * 1000); else doNoteOn();
|
||||||
try {
|
|
||||||
_synthInstance.noteOn(channel, midiPitch, midiVel);
|
|
||||||
setTimeout(() => { try { _synthInstance.noteOff(channel, midiPitch); } catch (e) {} }, durSec * 1000);
|
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
_playNoteOsc: function (note, velocity, durationMs, startTime, program, destinationNode, channel, synthEngine) {
|
_playNoteOsc: function (note, velocity, durationMs, startTime, program, destinationNode, channel, synthEngine) {
|
||||||
@@ -389,9 +335,7 @@
|
|||||||
stopAll: function () {
|
stopAll: function () {
|
||||||
if (_initialized && _synthInstance) {
|
if (_initialized && _synthInstance) {
|
||||||
try {
|
try {
|
||||||
for (let ch = 0; ch < 16; ch++) {
|
for (let ch = 0; ch < 16; ch++) _synthInstance.allNotesOff(ch);
|
||||||
_synthInstance.allNotesOff(ch);
|
|
||||||
}
|
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
const ctx = getCtx();
|
const ctx = getCtx();
|
||||||
@@ -402,9 +346,7 @@
|
|||||||
entry.gain.gain.cancelScheduledValues(now);
|
entry.gain.gain.cancelScheduledValues(now);
|
||||||
entry.gain.gain.setValueAtTime(0, now);
|
entry.gain.gain.setValueAtTime(0, now);
|
||||||
}
|
}
|
||||||
if (entry.osc) {
|
if (entry.osc) try { entry.osc.stop(now); } catch (e) { }
|
||||||
try { entry.osc.stop(now); } catch (e) { }
|
|
||||||
}
|
|
||||||
} catch (e) { }
|
} catch (e) { }
|
||||||
});
|
});
|
||||||
Object.keys(activeOscillators).forEach(k => delete activeOscillators[k]);
|
Object.keys(activeOscillators).forEach(k => delete activeOscillators[k]);
|
||||||
|
|||||||
Reference in New Issue
Block a user