feat: SF3 conversion + SpessaSynth client player

Server:
- soundfont_converter.py: Python SF2->SF3 via ffmpeg Ogg compression
- batch_convert_all runs on startup (daemon thread)
- GET /soundfonts/download/{sf_id} serves SF3 with SF2 fallback
- Dockerfile: add fluidsynth, vorbis-tools

Client:
- soundfontStorage.js: IndexedDB cache for SF3 buffers
- soundfontPlayer.js: dual-mode (SpessaSynth + oscillator fallback)
- app.jsx: init SpessaSynth, loadSF on instrument select
- index.html: SpessaSynth CDN import + storage script tag

Compression: DSK 11M->1.1M (90%), SGM 529M->18M (97%)
This commit is contained in:
2026-07-26 17:51:37 +07:00
parent 44e0a6d736
commit e51b7fd355
8 changed files with 426 additions and 26 deletions
+11
View File
@@ -37,6 +37,9 @@ function getAudioContext() {
if (window.SonicAudio && window.SonicAudio.initAudioWorklet) {
window.SonicAudio.initAudioWorklet();
}
if (window.SonicSF && window.SonicSF.init) {
window.SonicSF.init(audioCtx);
}
}
if (audioCtx.state === 'suspended') {
audioCtx.resume();
@@ -6519,6 +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_')) {
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 };
@@ -13867,6 +13875,9 @@ const App = () => {
if (window.SonicSF && window.SonicSF.applyAITrackInstrument) {
window.SonicSF.applyAITrackInstrument(aiTrack.soundfont_bank, aiTrack.soundfont_program);
}
if (window.SonicSF && window.SonicSF.loadSoundFont && aiTrack.soundfont_id) {
window.SonicSF.loadSoundFont(aiTrack.soundfont_id);
}
}
});
return updatedTracks;