107 lines
3.4 KiB
Python
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])
|