sintetizador_fm_para_archivos_midi_en_python
This is an old revision of the document!
poly synth
import mido import numpy as np import sounddevice as sd import time class Note: def __init__(self, note_num, velocity, start_time, program_num=None): self.note_num = note_num self.velocity = velocity self.start_time = start_time self.end_time = None self.samples = None self.position = 0 self.program_num = program_num class FMSynthesizer: def __init__(self, sample_rate=44100, block_size=1024, max_polyphony=24): self.sample_rate = sample_rate self.block_size = block_size self.max_polyphony = max_polyphony self.active_notes = {} self.note_buffer = np.zeros(block_size) def fm_synth(self, note, velocity, duration): carrier_freq = 440 * 2**((note - 69) / 12) modulator_freq = carrier_freq * 1/2 + carrier_freq * 2/6 + carrier_freq * 4/10 modulation_index = 0.5 # Add a tiny bit of padding to avoid edge clicks duration += 0.01 t = np.linspace(0, duration, int(self.sample_rate * duration), endpoint=False) modulator = np.sin(2 * np.pi * modulator_freq * t) * modulation_index carrier = np.sin(2 * np.pi * carrier_freq * t + modulator) # Simple envelope attack_time = 0.05 release_time = 0.05 attack_samples = int(self.sample_rate * attack_time) release_samples = int(self.sample_rate * release_time) envelope = np.ones_like(t) # Linear attack and release if attack_samples > 0: envelope[:attack_samples] = np.linspace(0, 1, attack_samples) if release_samples > 0: envelope[-release_samples:] = np.linspace(1, 0, release_samples) return carrier * envelope * (velocity / 127) def note_on(self, note_num, velocity, start_time, program_num): if len(self.active_notes) >= self.max_polyphony: oldest_time = float('inf') oldest_note = None for num, note in self.active_notes.items(): if note.start_time < oldest_time: oldest_time = note.start_time oldest_note = num if oldest_note is not None: del self.active_notes[oldest_note] new_note = Note(note_num, velocity, start_time, program_num) self.active_notes[note_num] = new_note def note_off(self, note_num, end_time): if note_num in self.active_notes: self.active_notes[note_num].end_time = end_time def get_audio_block(self): self.note_buffer.fill(0) notes_to_process = list(self.active_notes.items()) finished_notes = [] for note_num, note in notes_to_process: if note_num not in self.active_notes: continue if note.samples is None: duration = 1 if note.end_time is not None: duration = max(1, note.end_time - note.start_time) try: note.samples = self.fm_synth(note.note_num, note.velocity, duration) except Exception as e: print(f"Error generating samples for note {note_num}: {e}") finished_notes.append(note_num) continue remaining = len(note.samples) - note.position if remaining <= 0: finished_notes.append(note_num) continue samples_to_add = min(remaining, self.block_size) try: self.note_buffer[:samples_to_add] += \ note.samples[note.position:note.position + samples_to_add] note.position += samples_to_add except Exception as e: print(f"Error mixing samples for note {note_num}: {e}") finished_notes.append(note_num) for note_num in finished_notes: self.active_notes.pop(note_num, None) # Gentle limiting to prevent harsh clipping max_val = np.max(np.abs(self.note_buffer)) if max_val > 1.0: self.note_buffer = self.note_buffer / (max_val * 1.2) # Add a tiny headroom return self.note_buffer def play_midi_file(midi_path, selected_instruments=None): midi = mido.MidiFile(midi_path) synth = FMSynthesizer() program_changes = {} def audio_callback(outdata, frames, time, status): try: block = synth.get_audio_block() outdata[:, 0] = block except Exception as e: print(f"Error in audio callback: {e}") outdata.fill(0) with sd.OutputStream(channels=1, callback=audio_callback, samplerate=synth.sample_rate, blocksize=synth.block_size): start_time = time.time() current_time = 0 for msg in midi: current_time += msg.time wait_time = start_time + current_time - time.time() if wait_time > 0: time.sleep(wait_time) try: if msg.type == 'program_change': program_changes[msg.channel] = msg.program print(f"Program change: Channel {msg.channel} -> Program {msg.program}") elif msg.type == 'note_on': if msg.velocity > 0: if selected_instruments is None or program_changes.get(msg.channel) in selected_instruments: synth.note_on(msg.note, msg.velocity, current_time, program_changes.get(msg.channel)) else: synth.note_off(msg.note, current_time) elif msg.type == 'note_off': synth.note_off(msg.note, current_time) except Exception as e: print(f"Error processing MIDI message: {e}") time.sleep(2) if __name__ == "__main__": play_midi_file("C:/temp/canyon.mid", selected_instruments=None) # Example: Play only piano (Program 1) and electric piano (Program 5), [1, 5] #play_midi_file("C:/Users/unapa/Downloads/DXdiag.mid", selected_instruments=[33, 78, 16, 18, 53, 27, 29, 30])
sintetizador_fm_para_archivos_midi_en_python.1736714404.txt.gz · Last modified: 2025/01/12 20:40 by oso
