FIX: sửa lỗi không load soundfont trên window, MIDI item không play được qua carla

This commit is contained in:
2026-08-10 10:50:02 +07:00
parent 1db892bb99
commit 1cf5b18492
5 changed files with 87 additions and 38 deletions
+59 -37
View File
@@ -278,8 +278,6 @@ class PluginManager:
return load_soundfont_cached(path)
def list_soundfont_instruments(self, sf_id: str):
if not ensure_pyfluidsynth():
return []
if sf_id in _SF_INSTRUMENTS_CACHE:
return _SF_INSTRUMENTS_CACHE[sf_id]
search_dirs = []
@@ -293,12 +291,18 @@ class PluginManager:
for d in (self.extra_vst_dirs or []):
if d and os.path.isdir(d) and d not in search_dirs:
search_dirs.append(d)
for d in search_dirs:
for f in os.listdir(d):
if not (f.endswith(".sf2") or f.endswith(".sf3")):
continue
base = os.path.splitext(f)[0]
if base == sf_id or base == sf_id.replace("sf_", ""):
presets = []
# ── 1) FluidSynth (nếu có lib) — đọc bank/program/name từ engine ──
if ensure_pyfluidsynth():
for d in search_dirs:
if presets:
break
for f in os.listdir(d):
if not (f.endswith(".sf2") or f.endswith(".sf3")):
continue
base = os.path.splitext(f)[0]
if base != sf_id and base != sf_id.replace("sf_", ""):
continue
path = os.path.join(d, f)
try:
import fluidsynth as _fs
@@ -308,35 +312,31 @@ class PluginManager:
_synth = _fs.new_fluid_synth(_settings)
try:
fid = _fs.fluid_synth_sfload(_synth, path.encode("utf-8"), 1)
if fid < 0:
continue
sfont = _fs.fluid_synth_get_sfont_by_id(_synth, fid)
presets = []
if sfont:
for bank in range(0, 2):
for prog_num in range(0, 128):
try:
preset = _fs.fluid_sfont_get_preset(sfont, bank, prog_num)
except Exception:
break
if preset:
if fid >= 0:
sfont = _fs.fluid_synth_get_sfont_by_id(_synth, fid)
if sfont:
for bank in range(0, 2):
for prog_num in range(0, 128):
try:
name_ptr = _fs.fluid_preset_get_name(preset)
if name_ptr:
if hasattr(_fs, "ffi"):
raw = _fs.ffi.string(name_ptr)
else:
raw = c_char_p(name_ptr).value
if raw:
presets.append({
"bank": bank,
"program": prog_num,
"name": raw.decode("utf-8", errors="replace")
})
preset = _fs.fluid_sfont_get_preset(sfont, bank, prog_num)
except Exception:
continue
_SF_INSTRUMENTS_CACHE[sf_id] = presets[:256]
return presets[:256]
break
if preset:
try:
name_ptr = _fs.fluid_preset_get_name(preset)
if name_ptr:
if hasattr(_fs, "ffi"):
raw = _fs.ffi.string(name_ptr)
else:
raw = c_char_p(name_ptr).value
if raw:
presets.append({
"bank": bank,
"program": prog_num,
"name": raw.decode("utf-8", errors="replace"),
})
except Exception:
continue
finally:
try:
_fs.delete_fluid_synth(_synth)
@@ -344,8 +344,30 @@ class PluginManager:
pass
except Exception:
import traceback; traceback.print_exc()
_SF_INSTRUMENTS_CACHE[sf_id] = []
return []
break # đã xử lý file khớp
# ── 2) Fallback: sf2utils đọc TRỰC TIẾP file (thuần Python) ──
# Không cần libfluidsynth — quan trọng trên Windows khi thiếu DLL.
# Đọc preset header (pdta/phdr) → bank/program/name như nhau.
if not presets:
try:
from app.core.soundfont_inspector import SoundFontInspector
insp = SoundFontInspector(system_sf_dir=self.sf_dir, upload_sf_dir=self.upload_sf_dir)
for d in search_dirs:
if presets:
break
for f in os.listdir(d):
if not (f.endswith(".sf2") or f.endswith(".sf3")):
continue
base = os.path.splitext(f)[0]
if base == sf_id or base == sf_id.replace("sf_", ""):
info = insp.inspect_sf2_file(os.path.join(d, f))
if info and info.get("instruments"):
presets = info["instruments"]
break
except Exception:
presets = []
_SF_INSTRUMENTS_CACHE[sf_id] = presets[:256]
return presets[:256]
def list_available(self) -> dict:
return {
+10
View File
@@ -21133,6 +21133,16 @@ const App = () => {
if (window.SonicSF) {
window.SonicSF.playNote(note.pitch || 60, note.velocity || 0.8, durMs, scheduledTime, instrumentProgram, destNode, mainCh, synthEngine);
}
// MIDI items Carla (track VSTi + Carla local): phát VSTi realtime
// (schedule theo audio clock bng setTimeout preview, timing gn đúng).
if (window.SonicCarlaMidi && window.SonicCarlaMidi.shouldRoutePlayback(synthEngine)) {
const carlaDelay = Math.max(0, (scheduledTime - context.currentTime) * 1000);
const carlaVel = Math.round((note.velocity || 0.8) * 127);
const carlaCh = (synthEngine && synthEngine.midi_channel !== undefined) ? synthEngine.midi_channel : mainCh;
const carlaPitch = note.pitch || 60;
setTimeout(function () { window.SonicCarlaMidi.noteOn(carlaCh, carlaPitch, carlaVel); }, carlaDelay);
setTimeout(function () { window.SonicCarlaMidi.noteOff(carlaCh, carlaPitch); }, carlaDelay + durMs + 30);
}
}
});
// Play ghost notes from active tracks
+3 -1
View File
@@ -1319,7 +1319,9 @@ const instCtx=resolveTrackInstrumentCtx(track,activeTracksRef.current||[]);const
// items placed later in the project play `item.startTime` seconds in the
// future (silence when pressing play). Ghost notes are already relative to
// the item window, so no absolute-session offset is applied anywhere here.
midiNotes.forEach(note=>{const noteOnBeat=note.start_beat||0;const noteDurBeat=note.duration_beats||1;const noteStartSec=noteOnBeat*secondsPerBeat;const noteDurSec=noteDurBeat*secondsPerBeat;if(noteStartSec+noteDurSec>offsetSeconds){const effectiveStart=Math.max(0,noteStartSec-offsetSeconds);const effectiveDur=noteDurSec-Math.max(0,offsetSeconds-noteStartSec);const scheduledTime=startWallTime+effectiveStart;const durMs=effectiveDur*1000;if(window.SonicSF){window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,durMs,scheduledTime,instrumentProgram,destNode,mainCh,synthEngine);}}});// Play ghost notes from active tracks
midiNotes.forEach(note=>{const noteOnBeat=note.start_beat||0;const noteDurBeat=note.duration_beats||1;const noteStartSec=noteOnBeat*secondsPerBeat;const noteDurSec=noteDurBeat*secondsPerBeat;if(noteStartSec+noteDurSec>offsetSeconds){const effectiveStart=Math.max(0,noteStartSec-offsetSeconds);const effectiveDur=noteDurSec-Math.max(0,offsetSeconds-noteStartSec);const scheduledTime=startWallTime+effectiveStart;const durMs=effectiveDur*1000;if(window.SonicSF){window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,durMs,scheduledTime,instrumentProgram,destNode,mainCh,synthEngine);}// MIDI items → Carla (track VSTi + Carla local): phát VSTi realtime
// (schedule theo audio clock bằng setTimeout — preview, timing gần đúng).
if(window.SonicCarlaMidi&&window.SonicCarlaMidi.shouldRoutePlayback(synthEngine)){const carlaDelay=Math.max(0,(scheduledTime-context.currentTime)*1000);const carlaVel=Math.round((note.velocity||0.8)*127);const carlaCh=synthEngine&&synthEngine.midi_channel!==undefined?synthEngine.midi_channel:mainCh;const carlaPitch=note.pitch||60;setTimeout(function(){window.SonicCarlaMidi.noteOn(carlaCh,carlaPitch,carlaVel);},carlaDelay);setTimeout(function(){window.SonicCarlaMidi.noteOff(carlaCh,carlaPitch);},carlaDelay+durMs+30);}}});// Play ghost notes from active tracks
var ghostLayers=st.ghostPlayLayers||[];ghostLayers.forEach(function(layer){var ghostTrack=allTracks.find(function(t){return t.id===layer.trackId;});var ghostProg=layer.instrumentProgram!==undefined?layer.instrumentProgram:ghostTrack?ghostTrack.instrumentProgram:undefined;var ghostSynth=layer.synthEngine||(ghostTrack?ghostTrack.synth_engine:undefined);if(ghostProg===undefined&&!ghostSynth)return;var ghostDest=getOrCreateTrackNode(ghostTrack,context);var ghostCh=ghostTrack?assignTrackMidiChannel(ghostTrack,allTracks):0;layer.notes.forEach(function(note){var beat=note.start_beat||0;var dur=note.duration_beats||1;var startSec=beat*secondsPerBeat;var durSec=dur*secondsPerBeat;if(startSec+durSec>offsetSeconds){var effStart=Math.max(0,startSec-offsetSeconds);var effDur=durSec-Math.max(0,offsetSeconds-startSec);var schedTime=startWallTime+effStart;var durMs=effDur*1000;if(window.SonicSF){window.SonicSF.playNote(note.pitch||60,note.velocity||0.8,durMs,schedTime,ghostProg,ghostDest,ghostCh,ghostSynth);}}});});};const handlePlayPause=()=>{console.log('[Play] click activeTab=',activeTab,'isPlaying=',isPlaying,'subPlaying=',subTabs.filter(s=>s.isPlaying).length);if(activeTab!=='main'&&!activeTab.startsWith('session_')){// Sub-tab playback transport
const st=subTabs.find(s=>s.id===activeTab);if(!st||!st.buffer&&st.type!=='PIANO_ROLL')return;// MAIN/SECTION đang play (ngầm — MIDI section/main còn kêu sau khi mở
// tab): Space phải STOP trước — không được play sub-tab (user: trong
+10
View File
@@ -75,6 +75,16 @@ window.SonicCarlaMidi = window.SonicCarlaMidi || {
return String(se.type || '').indexOf('vst') !== -1 && !!se.plugin_id;
} catch (e) { return false; }
},
// Playback MIDI items: route khi track VSTi + Carla local (không cần ARM —
// user đã chủ động bấm Play trên item đó).
shouldRoutePlayback: function (synthEngine) {
try {
var c = window.SonicRuntime && window.SonicRuntime.capabilities;
if (!c || !c.features || !c.features.carla_local) return false;
var se = synthEngine || {};
return String(se.type || '').indexOf('vst') !== -1 && !!se.plugin_id;
} catch (e) { return false; }
},
noteOn: function (channel, note, velocity) {
if (!window.SonicAPI || !window.SonicAPI.carlaMidi) return;
window.SonicAPI.carlaMidi({ event: 'note_on', note: note, velocity: velocity || 100, channel: channel || 0 }).catch(function () {});