diff --git a/app/core/render_engine.py b/app/core/render_engine.py index 2bdb4b4..a337a4d 100644 --- a/app/core/render_engine.py +++ b/app/core/render_engine.py @@ -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")