#!/usr/bin/env python3 # Junta todas as faixas de um MIDI (formato 1) numa so (formato 0), sem mudar nenhum evento. # # Escrito para o fredygames.com: o default.mid do Crimson Fields tem 380 faixas e o FluidSynth so le # ate 128, entao a musica e convertida para o formato 0 antes de ser tocada pelo render-music.sh. # Copyright (C) 2026 FredyGames. Mesma licenca do Crimson Fields: GNU GPL versao 2 ou posterior. # # midi-to-type0.py entrada.mid saida.mid # # A ordem dos eventos de mesmo tick segue a ordem das faixas e, dentro da faixa, a do arquivo, entao a # saida e sempre a mesma. import struct import sys def read_vlq(data, pos): value = 0 while True: byte = data[pos] pos += 1 value = (value << 7) | (byte & 0x7F) if not byte & 0x80: return value, pos def write_vlq(value): out = [value & 0x7F] value >>= 7 while value: out.append((value & 0x7F) | 0x80) value >>= 7 return bytes(reversed(out)) def parse_track(data): """Eventos (tick absoluto, bytes do evento sem delta), com o running status expandido.""" events = [] pos = tick = 0 status = None while pos < len(data): delta, pos = read_vlq(data, pos) tick += delta if data[pos] & 0x80: status = data[pos] pos += 1 if status == 0xFF: kind = data[pos] length, end = read_vlq(data, pos + 1) body = data[pos:end + length] pos = end + length if kind == 0x2F: break events.append((tick, bytes([0xFF]) + body)) status = None elif status in (0xF0, 0xF7): length, end = read_vlq(data, pos) body = data[pos:end + length] pos = end + length events.append((tick, bytes([status]) + body)) status = None else: size = 1 if status & 0xF0 in (0xC0, 0xD0) else 2 events.append((tick, bytes([status]) + data[pos:pos + size])) pos += size return events, tick def main(): src, dst = sys.argv[1], sys.argv[2] data = open(src, 'rb').read() if data[:4] != b'MThd': sys.exit('nao e um arquivo MIDI') hlen, fmt, ntracks, division = struct.unpack('>IHHH', data[4:14]) pos = 8 + hlen merged = [] last = 0 for index in range(ntracks): if data[pos:pos + 4] != b'MTrk': sys.exit('faixa %d invalida' % index) tlen = struct.unpack('>I', data[pos + 4:pos + 8])[0] events, end = parse_track(data[pos + 8:pos + 8 + tlen]) last = max(last, end) merged.extend((tick, index, order, ev) for order, (tick, ev) in enumerate(events)) pos += 8 + tlen merged.sort(key=lambda e: (e[0], e[1], e[2])) body = bytearray() now = 0 for tick, _, _, ev in merged: body += write_vlq(tick - now) + ev now = tick body += write_vlq(last - now) + b'\xff\x2f\x00' with open(dst, 'wb') as out: out.write(b'MThd' + struct.pack('>IHHH', 6, 0, 1, division)) out.write(b'MTrk' + struct.pack('>I', len(body)) + bytes(body)) if __name__ == '__main__': main()