#!/usr/bin/env python3 import sys import mido def convert_midi_to_c(midi_filename): try: mid = mido.MidiFile(midi_filename) except Exception as e: print(f"Error loading MIDI: {e}") sys.exit(1) safe_name = midi_filename.split('/')[-1].replace('.mid', '').replace('-', '_').replace(' ', '_') print(f"// Converted from: {midi_filename} (RLE Compressed & Bass-Routed Edition)") print(f"const SequenceStep midi_{safe_name}[] = {{") active_melody_notes = set() time_accum = 0.0 # Incoming MIDI drum triggers trigger_kick = False trigger_hat = False # === RLE COMPRESSION STATE === rle_freq = -1 rle_dur = 0 rle_kick = False rle_hat = False output_lines = [] line_buffer = " " items_on_line = 0 # Function to print the compressed note once it finally changes def flush_rle(): nonlocal rle_freq, rle_dur, rle_kick, rle_hat, line_buffer, items_on_line, output_lines if rle_dur > 0: k_str = "true" if rle_kick else "false" h_str = "true" if rle_hat else "false" line_buffer += f"{{{rle_freq}, {rle_dur}, {k_str}, {h_str}}}, " items_on_line += 1 if items_on_line >= 4: output_lines.append(line_buffer) line_buffer = " " items_on_line = 0 for msg in mid: time_accum += msg.time if msg.type in ['note_on', 'note_off'] and time_accum > 0.01: duration_ms = int(time_accum * 1000) if active_melody_notes: note = max(active_melody_notes) freq = int(440.0 * (2.0 ** ((note - 69) / 12.0))) else: freq = 0 # === RLE ENGINE === # If nothing changed, just add the time to the current note! if freq == rle_freq and trigger_kick == rle_kick and trigger_hat == rle_hat: rle_dur += duration_ms else: # The note changed! Flush the old one and start tracking the new one. flush_rle() rle_freq = freq rle_dur = duration_ms rle_kick = trigger_kick rle_hat = trigger_hat # Reset triggers for the next loop time_accum = 0.0 trigger_kick = False trigger_hat = False if hasattr(msg, 'note'): if msg.type == 'note_on' and msg.velocity > 0: if msg.channel == 9: if msg.note in [35, 36, 38, 40]: trigger_kick = True else: trigger_hat = True else: if msg.note >= 60: active_melody_notes.add(msg.note) elif msg.note < 50: trigger_kick = True else: trigger_hat = True elif msg.type == 'note_off' or (msg.type == 'note_on' and msg.velocity == 0): if msg.channel != 9 and msg.note >= 60: active_melody_notes.discard(msg.note) # Flush the very last note flush_rle() if items_on_line > 0: output_lines.append(line_buffer) for line in output_lines: print(line) print(" {0, 2000, false, false}\n};") if __name__ == "__main__": if len(sys.argv) < 2: print("Usage: python midi_to_c.py ") sys.exit(1) convert_midi_to_c(sys.argv[1])