summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJulian Hurst <julian.hurst@digdash.com>2024-11-20 17:25:35 +0100
committerJulian Hurst <julian.hurst@digdash.com>2024-11-20 18:02:28 +0100
commit4c6b13faaabd6d2860e44732abe5c245acade1f8 (patch)
treee5e4ada3e3b27315bc5e163ecec9cf9677bdcdee
downloadhare-midi-4c6b13faaabd6d2860e44732abe5c245acade1f8.tar.gz
Initial commit
-rw-r--r--.gitignore2
-rw-r--r--cmd/example/simple.ha17
-rw-r--r--midi/midi.ha205
-rw-r--r--midi_test-c-major-scale.midbin0 -> 473 bytes
4 files changed, 224 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a2f359d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+f.mid
+midi/midi
diff --git a/cmd/example/simple.ha b/cmd/example/simple.ha
new file mode 100644
index 0000000..3a2a32b
--- /dev/null
+++ b/cmd/example/simple.ha
@@ -0,0 +1,17 @@
+use midi;
+
+export fn main() void = {
+ const th = midi::hchunk {
+ chk = midi::HEADERCHUNK,
+ format = 0,
+ ntrks = 1,
+ division = 4,
+ };
+
+ let tc = midi::newtrack();
+ defer midi::finishtrack(tc);
+ midi::noteon(&tc, 0, midi::note::C);
+ midi::noteoff(&tc, 4, midi::note::C);
+ midi::writeheader(th);
+ midi::writechunk(tc);
+};
diff --git a/midi/midi.ha b/midi/midi.ha
new file mode 100644
index 0000000..7da6689
--- /dev/null
+++ b/midi/midi.ha
@@ -0,0 +1,205 @@
+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 noteon(c: *trchunk, d: vlen, n: note, vel: u8 = 0x40, oct: uint = 0) void = {
+ const np = n + (oct * 12): u8;
+ const e = event {
+ deltatime = d,
+ ev = midi {
+ status = 0x90,
+ data = [n, vel],
+ },
+ };
+ append(c.ev, e);
+};
+
+export fn noteoff(c: *trchunk, d: vlen, n: note, vel: u8 = 0x40, oct: uint = 0) void = {
+ const np = n + (oct * 12): u8;
+ const e = event {
+ deltatime = d,
+ ev = midi {
+ status = 0x80,
+ data = [n, vel],
+ },
+ };
+ append(c.ev, e);
+};
+
+export fn writeheader(c: hchunk) void = {
+ io::writeall(os::stdout, c.chk.chktype)!;
+ let buf: [4]u8 = [0...];
+ let l = c.chk.length;
+ endian::beputu32(buf, l);
+ io::writeall(os::stdout, buf)!;
+
+ let bf: [2]u8 = [0...];
+ endian::beputu16(bf, c.format);
+ io::writeall(os::stdout, bf)!;
+ let bf: [2]u8 = [0...];
+ endian::beputu16(bf, c.ntrks);
+ io::writeall(os::stdout, bf)!;
+ let bf: [2]u8 = [0...];
+ endian::beputu16(bf, c.division);
+ io::writeall(os::stdout, bf)!;
+};
+
+export fn writechunk(c: trchunk) void = {
+ io::writeall(os::stdout, c.chk.chktype)!;
+ let buf: [4]u8 = [0...];
+ let l = calclength(c);
+ //let l = c.chk.length;
+ endian::beputu32(buf, l);
+ io::writeall(os::stdout, buf)!;
+
+ for (const ev .. c.ev) {
+ let dt = encodevlen(ev.deltatime);
+ endian::beputu32(buf, dt);
+ if (dt <= 127) {
+ io::writeall(os::stdout, [buf[3]])!;
+ } else if (dt <= 0xFF7F) {
+ io::writeall(os::stdout, buf[2..])!;
+ } else if (dt <= 0xFFFF7F) {
+ io::writeall(os::stdout, buf[1..])!;
+ } else {
+ io::writeall(os::stdout, buf)!;
+ };
+ match (ev.ev) {
+ case let m: midi =>
+ io::writeall(os::stdout, [m.status])!;
+ io::writeall(os::stdout, m.data)!;
+ case =>
+ abort();
+ };
+ };
+};
+
+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);
+};
diff --git a/midi_test-c-major-scale.mid b/midi_test-c-major-scale.mid
new file mode 100644
index 0000000..4f61e8d
--- /dev/null
+++ b/midi_test-c-major-scale.mid
Binary files differ