fix(engine): replace pyfluidsynth high-level API with low-level CFFI
Export installed pyfluidsynth is actually the low-level ctypes wrapper (fluidsynth), not the high-level FluidSynth class. The broken call fluidsynth.FluidSynth() caused silent fallback to oscillator synth for ALL SoundFont renders. Replace with: new_fluid_settings / new_fluid_synth / fluid_synth_sfload / fluid_synth_program_select / fluid_synth_write_s16_stereo. Also fix synth time advancement: call write_s16_stereo for silence gaps instead of just advancing a cursor.
This commit is contained in:
+24
-14
@@ -230,10 +230,12 @@ class PythonRenderEngine:
|
|||||||
sf_path = _find_default_sf2() if HAS_PYFLUIDSYNTH else ""
|
sf_path = _find_default_sf2() if HAS_PYFLUIDSYNTH else ""
|
||||||
|
|
||||||
if sf_path and os.path.exists(sf_path) and HAS_PYFLUIDSYNTH:
|
if sf_path and os.path.exists(sf_path) and HAS_PYFLUIDSYNTH:
|
||||||
import fluidsynth
|
import fluidsynth as _fs
|
||||||
fl = fluidsynth.FluidSynth(sample_rate=self.sample_rate, gain=0.5)
|
_settings = _fs.new_fluid_settings()
|
||||||
fid = fl.sfload(sf_path)
|
_fs.fluid_settings_setnum(_settings, b'synth.sample-rate', float(self.sample_rate))
|
||||||
fl.program_select(midi_channel, fid, soundfont_bank, soundfont_program)
|
_fl = _fs.new_fluid_synth(_settings)
|
||||||
|
_fid = _fs.fluid_synth_sfload(_fl, sf_path.encode("utf-8"), 1)
|
||||||
|
_fs.fluid_synth_program_select(_fl, midi_channel, _fid, soundfont_bank, soundfont_program)
|
||||||
beat_sec = 60.0 / bpm
|
beat_sec = 60.0 / bpm
|
||||||
total_sec = 0
|
total_sec = 0
|
||||||
for ev in midi_events:
|
for ev in midi_events:
|
||||||
@@ -242,25 +244,33 @@ class PythonRenderEngine:
|
|||||||
total_sec = end_sec
|
total_sec = end_sec
|
||||||
sf_total_samples = int((total_sec + 1.0) * self.sample_rate)
|
sf_total_samples = int((total_sec + 1.0) * self.sample_rate)
|
||||||
midi_data = np.zeros((2, sf_total_samples), dtype=np.float32)
|
midi_data = np.zeros((2, sf_total_samples), dtype=np.float32)
|
||||||
for ev in midi_events:
|
_cursor = 0
|
||||||
|
for ev in sorted(midi_events, key=lambda e: e.get("start_beat", 0)):
|
||||||
note = ev.get("note", 60)
|
note = ev.get("note", 60)
|
||||||
velocity = ev.get("velocity", 100)
|
velocity = ev.get("velocity", 100)
|
||||||
start_beat = ev.get("start_beat", 0.0)
|
start_beat = ev.get("start_beat", 0.0)
|
||||||
dur_beats = ev.get("duration_beats", 1.0)
|
dur_beats = ev.get("duration_beats", 1.0)
|
||||||
start_sec = start_beat * beat_sec
|
start_sec = start_beat * beat_sec
|
||||||
dur_sec = dur_beats * beat_sec
|
dur_sec = dur_beats * beat_sec
|
||||||
fl.noteon(midi_channel, note, velocity)
|
|
||||||
start_s = int(start_sec * self.sample_rate)
|
start_s = int(start_sec * self.sample_rate)
|
||||||
dur_s = int(dur_sec * self.sample_rate)
|
dur_s = int(dur_sec * self.sample_rate)
|
||||||
block = fl.get_samples(int(dur_s)) if hasattr(fl, 'get_samples') else np.zeros((2, dur_s), dtype=np.float32)
|
# Advance synth time by rendering silence
|
||||||
fl.noteoff(midi_channel, note)
|
if start_s > _cursor:
|
||||||
if block.shape[1] > 0:
|
gap = start_s - _cursor
|
||||||
end_s = min(start_s + block.shape[1], sf_total_samples)
|
_fs.fluid_synth_write_s16_stereo(_fl, gap)
|
||||||
actual = end_s - start_s
|
_cursor = start_s
|
||||||
if actual > 0:
|
# Start note
|
||||||
midi_data[:, start_s:end_s] += block[:, :actual]
|
_fs.fluid_synth_noteon(_fl, midi_channel, note, min(velocity, 127))
|
||||||
|
block_s16 = _fs.fluid_synth_write_s16_stereo(_fl, dur_s)
|
||||||
|
_fs.fluid_synth_noteoff(_fl, midi_channel, note)
|
||||||
|
block = block_s16.astype(np.float32).reshape(-1, 2).T / 32768.0
|
||||||
|
end_s = min(_cursor + block.shape[1], sf_total_samples)
|
||||||
|
actual = end_s - _cursor
|
||||||
|
if actual > 0 and block.shape[1] > 0:
|
||||||
|
midi_data[:, _cursor:end_s] += block[:, :actual]
|
||||||
|
_cursor = end_s
|
||||||
synth_buffer = midi_data
|
synth_buffer = midi_data
|
||||||
fl.delete()
|
_fs.delete_fluid_synth(_fl)
|
||||||
else:
|
else:
|
||||||
if not HAS_PYFLUIDSYNTH:
|
if not HAS_PYFLUIDSYNTH:
|
||||||
logger.warning("[RenderEngine] pyfluidsynth not available, falling back to oscillator synth")
|
logger.warning("[RenderEngine] pyfluidsynth not available, falling back to oscillator synth")
|
||||||
|
|||||||
Reference in New Issue
Block a user