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 ""
|
||||
|
||||
if sf_path and os.path.exists(sf_path) and HAS_PYFLUIDSYNTH:
|
||||
import fluidsynth
|
||||
fl = fluidsynth.FluidSynth(sample_rate=self.sample_rate, gain=0.5)
|
||||
fid = fl.sfload(sf_path)
|
||||
fl.program_select(midi_channel, fid, soundfont_bank, soundfont_program)
|
||||
import fluidsynth as _fs
|
||||
_settings = _fs.new_fluid_settings()
|
||||
_fs.fluid_settings_setnum(_settings, b'synth.sample-rate', float(self.sample_rate))
|
||||
_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
|
||||
total_sec = 0
|
||||
for ev in midi_events:
|
||||
@@ -242,25 +244,33 @@ class PythonRenderEngine:
|
||||
total_sec = end_sec
|
||||
sf_total_samples = int((total_sec + 1.0) * self.sample_rate)
|
||||
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)
|
||||
velocity = ev.get("velocity", 100)
|
||||
start_beat = ev.get("start_beat", 0.0)
|
||||
dur_beats = ev.get("duration_beats", 1.0)
|
||||
start_sec = start_beat * beat_sec
|
||||
dur_sec = dur_beats * beat_sec
|
||||
fl.noteon(midi_channel, note, velocity)
|
||||
start_s = int(start_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)
|
||||
fl.noteoff(midi_channel, note)
|
||||
if block.shape[1] > 0:
|
||||
end_s = min(start_s + block.shape[1], sf_total_samples)
|
||||
actual = end_s - start_s
|
||||
if actual > 0:
|
||||
midi_data[:, start_s:end_s] += block[:, :actual]
|
||||
# Advance synth time by rendering silence
|
||||
if start_s > _cursor:
|
||||
gap = start_s - _cursor
|
||||
_fs.fluid_synth_write_s16_stereo(_fl, gap)
|
||||
_cursor = start_s
|
||||
# Start note
|
||||
_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
|
||||
fl.delete()
|
||||
_fs.delete_fluid_synth(_fl)
|
||||
else:
|
||||
if not HAS_PYFLUIDSYNTH:
|
||||
logger.warning("[RenderEngine] pyfluidsynth not available, falling back to oscillator synth")
|
||||
|
||||
Reference in New Issue
Block a user