ertuncozbay.dev/assets/downloads/midi_to_c.py

107 lines
3.4 KiB
Python

#!/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 <file.mid>")
sys.exit(1)
convert_midi_to_c(sys.argv[1])