Midi2lua Patched

Save this as midi2lua_patched.py:

#!/usr/bin/env python3
"""
midi2lua_patched.py
Converts MIDI file to Lua table for LÖVE2D/FNF.
Patched features: tempo mapping, channel filtering, note grouping.
"""

import sys import struct import math

def read_var_length(f): value = 0 while True: byte = f.read(1) if not byte: break byte = byte[0] value = (value << 7) | (byte & 0x7F) if not (byte & 0x80): break return value

def parse_midi(filename, track_idx=0, channel_include=None): with open(filename, 'rb') as f: if f.read(4) != b'MThd': raise ValueError("Not a MIDI file") header_len = struct.unpack('>I', f.read(4))[0] format_type = struct.unpack('>H', f.read(2))[0] num_tracks = struct.unpack('>H', f.read(2))[0] division = struct.unpack('>H', f.read(2))[0] ticks_per_beat = division & 0x7FFF

    tracks = []
    for _ in range(num_tracks):
        if f.read(4) != b'MTrk':
            raise ValueError("Bad track chunk")
        track_len = struct.unpack('>I', f.read(4))[0]
        track_data = f.read(track_len)
        tracks.append(track_data)
# Parse selected track
data = tracks[track_idx]
pos = 0
tick = 0
events = []
tempo = 500000  # default microseconds per quarter
bpm = 120
time_sig_num = 4
time_sig_denom = 4
while pos < len(data):
    delta = read_var_length(bytearray([data[pos]])) if isinstance(data, bytes) else read_var_length(bytearray([data[pos]]))
    # Actually parse delta correctly
    delta_bytes = 0
    delta_val = 0
    while True:
        b = data[pos]
        delta_val = (delta_val << 7) | (b & 0x7F)
        pos += 1
        if not (b & 0x80):
            break
    tick += delta_val
if pos >= len(data):
        break
    cmd = data[pos]
    pos += 1
    if cmd == 0xFF:  # meta event
        meta_type = data[pos]
        pos += 1
        length = read_var_length(bytearray([data[pos]])) if isinstance(data, bytes) else read_var_length(bytearray([data[pos]]))
        # Actually read length
        len_val = 0
        while True:
            b = data[pos]
            len_val = (len_val << 7) | (b & 0x7F)
            pos += 1
            if not (b & 0x80):
                break
        meta_data = data[pos:pos+len_val]
        pos += len_val
        if meta_type == 0x51:  # set tempo
            tempo = int.from_bytes(meta_data, byteorder='big')
            bpm = 60000000 / tempo
        elif meta_type == 0x58:  # time signature
            time_sig_num = meta_data[0]
            time_sig_denom = 2 ** meta_data[1]
        continue
elif cmd & 0xF0 == 0x90:  # note on
        channel = cmd & 0x0F
        if channel_include is not None and channel not in channel_include:
            continue
        note = data[pos]
        velocity = data[pos+1]
        pos += 2
        if velocity > 0:
            events.append(('note_on', tick, channel, note, velocity, tempo, ticks_per_beat))
    elif cmd & 0xF0 == 0x80:  # note off
        channel = cmd & 0x0F
        if channel_include is not None and channel not in channel_include:
            continue
        note = data[pos]
        pos += 2
        events.append(('note_off', tick, channel, note))
    else:
        # skip other events
        if cmd & 0xF0 in [0xC0, 0xD0]:
            pos += 1
        else:
            pos += 2
return events, ticks_per_beat, bpm, time_sig_num, time_sig_denom

def tick_to_seconds(tick, tempo_us, ticks_per_beat): return (tick * tempo_us) / (ticks_per_beat * 1_000_000)

def generate_lua(events, ticks_per_beat, bpm, filename_out, sample_rate=44100, ppq=480): notes = [] active = {} midi2lua patched

for ev in events:
    if ev[0] == 'note_on':
        _, tick, ch, note, vel, tempo, tpb = ev
        start_sec = tick_to_seconds(tick, tempo, tpb)
        active[(ch, note)] = start_sec
    elif ev[0] == 'note_off':
        _, tick, ch, note = ev
        if (ch, note) in active:
            start_sec = active.pop((ch, note))
            # find end tempo (simplified: use last tempo)
            end_sec = tick_to_seconds(tick, tempo, tpb) if 'tempo' in locals() else start_sec + 0.5
            duration = end_sec - start_sec
            if duration > 0:
                notes.append(
                    'pitch': note,
                    'start': start_sec,
                    'duration': duration,
                    'channel': ch,
                    'velocity': 100
                )
# Generate Lua
lua_code = f"""-- Auto-generated by midi2lua_patched

-- BPM: bpm:.2f PPQ: ticks_per_beat

local notes = { """ for n in notes: lua_code += f" pitch = n['pitch'], start = n['start']:.6f, duration = n['duration']:.6f, channel = n['channel'] ,\n" lua_code += """

function play_sequence(source) for _, note in ipairs(notes) do local timer = love.timer.getTime() local delay = note.start - timer if delay < 0 then delay = 0 end love.timer.after(delay, function() local frequency = 440 * 2 ^ ((note.pitch - 69) / 12) local sound = love.audio.newSource(love.sound.newSoundData(1, 44100)) -- actual synth logic here end) end end

return notes """ with open(filename_out, 'w') as f: f.write(lua_code) print(f"✅ Generated filename_out with len(notes) notes")

if name == 'main': if len(sys.argv) < 3: print("Usage: midi2lua_patched.py input.mid output.lua [channel1,channel2]") sys.exit(1) midi_file = sys.argv[1] lua_file = sys.argv[2] channels = None if len(sys.argv) > 3: channels = [int(c) for c in sys.argv[3].split(',')] events, tpb, bpm, _, _ = parse_midi(midi_file, track_idx=0, channel_include=channels) generate_lua(events, tpb, bpm, lua_file) Save this as midi2lua_patched


A patched version implies community‑ or developer‑driven modifications that fix limitations, add features, or adapt the tool to newer environments. Common patches include:

MIDI2Lua Patched does not contain any Nintendo copyrighted code. It is a transformative tool that converts open-standard MIDI files into Lua tables. However, using it to distribute full game ROMs with copyrighted soundtracks (e.g., replacing Mario music with a pop song) may violate fair use. Most modders use it for original compositions or public domain MIDIs.

The Problem: Standard MIDI-to-Lua converters often output a repetitive, line-by-line stream of function calls. This results in huge file sizes and poor runtime performance because the Lua interpreter has to process thousands of individual function calls for simple chord strikes or parameter changes.

The Solution: A patch that detects simultaneous events (like a chord) or rapid parameter changes and "batches" them into single table-driven function calls, optionally quantizing the timing to clean up sloppy performances. -- BPM: bpm:


MIDI clips from a DAW are transformed into Lua sequences that send DMX values over UDP – perfect for open‑source lighting consoles.

First, let’s break down the name. MIDI (Musical Instrument Digital Interface) is the universal format for sequenced music. Lua is a lightweight scripting language used extensively in Nintendo’s proprietary engines (like the LunchPack engine for 3DS/Wii U) and in homebrew frameworks such as LÖVE.

The original midi2lua tool was a command-line utility that parsed a MIDI file and outputted a .lua file containing large arrays of note events, durations, and velocities. The game engine would then iterate through these tables to play custom music.

The problem? The original version was alpha-quality at best.

Frustrated modders began sharing unofficial edits. Several forks appeared, but one rose above the rest: the MIDI2Lua Patched build (often version 1.2.3p or higher), maintained by a collective of German and Japanese ROM hackers.

Open your MIDI in a DAW like Reaper or FL Studio. Ensure:

CaliberTV