use fmt; use endian; use strings; use io; use os; use strconv; def HEADER: [4]u8 = ['M', 'T', 'h', 'd']; def TRACK: [4]u8 = ['M', 'T', 'r', 'k']; def SOUNDOFF: [2]u8 = [0x78, 0x00]; export type chunk = struct { chktype: [4]u8, length: u32, }; export type hchunk = struct { chk: chunk, format: u16, ntrks: u16, division: u16, }; export type trchunk = struct { chk: chunk, ev: []event, }; export type vlen = u32; export type event = struct { deltatime: vlen, ev: (midi | sysex | meta), }; export type midi = struct { status: u8, data: [2]u8, }; export type sysex = void; export type meta = void; export def HEADERCHUNK = chunk { chktype = HEADER, // MIDI 1.0 length = 6, }; def TRACKCHUNK = chunk { chktype = TRACK, ... }; export type note = enum u8 { A = 0x39, AS = 0x3A, B = 0x3B, BS = 0x3C, C = 0x3C, CS = 0x3D, D = 0x3E, DS = 0x3F, E = 0x40, ES = 0x41, F = 0x41, FS = 0x42, G = 0x43, GS = 0x44, }; export fn finishtrack(c: trchunk) void = { free(c.ev); }; export fn newtrack() trchunk = { return trchunk { chk = TRACKCHUNK, ev = [], }; }; export fn addevent(c: *trchunk, e: event) void = { append(c.ev, e); }; export fn playnote(d: vlen, n: note, vel: u8 = 0x40, oct: uint = 0) (event, event) = { const eon = noteon(0, n, vel, oct); const eoff = noteoff(d, n, vel, oct); return (eon, eoff); }; export fn noteon(d: vlen, n: note, vel: u8 = 0x40, oct: uint = 0) event = { const np = n + (oct * 12): u8; const e = event { deltatime = d, ev = midi { status = 0x90, data = [np, vel], }, }; return e; }; export fn noteoff(d: vlen, n: note, vel: u8 = 0x40, oct: uint = 0) event= { const np = n + (oct * 12): u8; const e = event { deltatime = d, ev = midi { status = 0x80, data = [np, vel], }, }; return e; }; export fn writeheader(h: io::handle, c: hchunk) void = { io::writeall(h, c.chk.chktype)!; let buf: [4]u8 = [0...]; let l = c.chk.length; endian::beputu32(buf, l); io::writeall(h, buf)!; let bf: [2]u8 = [0...]; endian::beputu16(bf, c.format); io::writeall(h, bf)!; let bf: [2]u8 = [0...]; endian::beputu16(bf, c.ntrks); io::writeall(h, bf)!; let bf: [2]u8 = [0...]; endian::beputu16(bf, c.division); io::writeall(h, bf)!; }; export fn writechunk(h: io::handle, c: trchunk) void = { io::writeall(h, c.chk.chktype)!; let buf: [4]u8 = [0...]; let l = calclength(c); //let l = c.chk.length; endian::beputu32(buf, l); io::writeall(h, buf)!; writeevents(h, c.ev...); }; export fn writeevents(h: io::handle, evs: event...) void = { let buf: [4]u8 = [0...]; for (const ev .. evs) { let dt = encodevlen(ev.deltatime); endian::beputu32(buf, dt); if (dt <= 127) { io::writeall(h, [buf[3]])!; } else if (dt <= 0xFF7F) { io::writeall(h, buf[2..])!; } else if (dt <= 0xFFFF7F) { io::writeall(h, buf[1..])!; } else { io::writeall(h, buf)!; }; match (ev.ev) { case let m: midi => writemidievents(h, m); case => abort(); }; }; }; export fn writemidievents(h: io::handle, evs: midi...) void = { let buf: [4]u8 = [0...]; for (const ev .. evs) { io::writeall(h, [ev.status])!; io::writeall(h, ev.data)!; }; }; fn calclength(c: trchunk) u32 = { let length = 0u32; for (const ev .. c.ev) { let dt = encodevlen(ev.deltatime); if (dt <= 127) { length += 1; } else if (dt <= 0xFF7F) { length += 2; } else if (dt <= 0xFFFF7F) { length += 3; } else { length += 4; }; match (ev.ev) { case let m: midi => length += 3; case => abort(); }; }; return length; }; fn encodevlen(v: vlen) u32 = { if (v < 128) { return v; }; let n = v: u32; let b: [4]u8 = [0...]; endian::beputu32(b, v); for (let i = 3z; i >= 0; i -= 1) { b[i] = (n & 0x7F): u8; if (i < 3) { b[i] |= 0x80; }; n >>= 7; if (n < 1) { break; }; }; return endian::begetu32(b); };