The OpenD Programming Language

1 /**
2 	This file is a port of some old C code I had for reading and writing .mid files. Not much docs, but viewing the source may be helpful.
3 
4 	I'll eventually refactor it into something more D-like
5 
6 	History:
7 		Written in C in August 2008
8 
9 		Minimally ported to D in September 2017
10 
11 		Updated May 2020 with significant changes.
12 */
13 module arsd.midi;
14 
15 
16 /+
17 	So the midi ticks are defined in terms of per quarter note so that's good stuff.
18 
19 	If you're reading live though you have milliseconds, and probably want to round them
20 	off a little to fit the beat.
21 +/
22 
23 import core.time;
24 
25 version(NewMidiDemo)
26 void main(string[] args) {
27 	auto f = new MidiFile();
28 
29 	import std.file;
30 
31 	//f.loadFromBytes(cast(ubyte[]) read("test.mid"));
32 	f.loadFromBytes(cast(ubyte[]) read(args[1]));
33 
34 	import arsd.simpleaudio;
35 	import core.thread;
36 
37 	auto o = MidiOutput(0);
38 	setSigIntHandler();
39 	scope(exit) {
40 		o.silenceAllNotes();
41 		o.reset();
42 		restoreSigIntHandler();
43 	}
44 
45 	import std.stdio : writeln;
46 	foreach(item; f.playbackStream) {
47 		if(interrupted) return;
48 
49 		Thread.sleep(item.wait);
50 		if(!item.event.isMeta)
51 			o.writeMidiMessage(item.event.status, item.event.data1, item.event.data2);
52 		else
53 			writeln(item);
54 	}
55 
56 	return;
57 
58 	auto t = new MidiTrack();
59 	auto t2 = new MidiTrack();
60 
61 	f.tracks ~= t;
62 	f.tracks ~= t2;
63 
64 	t.events ~= MidiEvent(0, 0x90, C, 127);
65 	t.events ~= MidiEvent(256, 0x90, C, 0);
66 	t.events ~= MidiEvent(256, 0x90, D, 127);
67 	t.events ~= MidiEvent(256, 0x90, D, 0);
68 	t.events ~= MidiEvent(256, 0x90, E, 127);
69 	t.events ~= MidiEvent(256, 0x90, E, 0);
70 	t.events ~= MidiEvent(256, 0x90, F, 127);
71 	t.events ~= MidiEvent(0, 0xff, 0x05, 0 /* unused */, ['h', 'a', 'm']);
72 	t.events ~= MidiEvent(256, 0x90, F, 0);
73 
74 	t2.events ~= MidiEvent(0, (MIDI_EVENT_PROGRAM_CHANGE << 4) | 0x01, 68);
75 	t2.events ~= MidiEvent(128, 0x91, E, 127);
76 	t2.events ~= MidiEvent(0, 0xff, 0x05, 0 /* unused */, ['a', 'd', 'r']);
77 	t2.events ~= MidiEvent(1024, 0x91, E, 0);
78 
79 	write("test.mid", f.toBytes());
80 }
81 
82 @safe:
83 
84 class MidiFile {
85 	///
86 	ubyte[] toBytes() {
87 		MidiWriteBuffer buf;
88 
89 		buf.write("MThd");
90 		buf.write4(6);
91 
92 		buf.write2(format);
93 		buf.write2(cast(ushort) tracks.length);
94 		buf.write2(timing);
95 
96 		foreach(track; tracks) {
97 			auto data = track.toBytes();
98 			buf.write("MTrk");
99 			buf.write4(cast(int) data.length);
100 			buf.write(data);
101 		}
102 
103 		return buf.bytes;
104 	}
105 
106 	///
107 	void loadFromBytes(ubyte[] bytes) {
108 		// FIXME: actually read the riff header to skip properly
109 		if(bytes.length && bytes[0] == 'R')
110 			bytes = bytes[0x14 .. $];
111 
112 		MidiReadBuffer buf = MidiReadBuffer(bytes);
113 		if(buf.readChars(4) != "MThd")
114 			throw new Exception("not midi");
115 		if(buf.read4() != 6)
116 			throw new Exception("idk what this even is");
117 		this.format = buf.read2();
118 		this.tracks = new MidiTrack[](buf.read2());
119 		this.timing = buf.read2();
120 
121 		foreach(ref track; tracks) {
122 			track = new MidiTrack();
123 			track.loadFromBuffer(buf);
124 		}
125 	}
126 
127 	// when I read, I plan to cut the end of track marker off.
128 
129 	// 0 == combined into one track
130 	// 1 == multiple tracks
131 	// 2 == multiple one-track patterns
132 	ushort format = 1;
133 
134 	// FIXME
135 	ushort timing = 0x80; // 128 ticks per quarter note
136 
137 	MidiTrack[] tracks;
138 
139 	/++
140 		Returns a forward range for playback. Each item is a command, which
141 		is like the midi event but with some more annotations and control methods.
142 
143 		Modifying this MidiFile object or any of its children during playback
144 		may cause trouble.
145 
146 		Note that you do not need to handle any meta events, it keeps the
147 		tempo internally, but you can look at it if you like.
148 	+/
149 	const(PlayStreamEvent)[] playbackStream() {
150 		PlayStreamEvent[] stream;
151 		size_t size;
152 		foreach(track; tracks)
153 			size += track.events.length;
154 		stream.reserve(size);
155 
156 		Duration position;
157 
158 		static struct NoteOnInfo {
159 			PlayStreamEvent* event;
160 			int turnedOnTicks;
161 			Duration turnedOnPosition;
162 		}
163 		NoteOnInfo[] noteOnInfo = new NoteOnInfo[](128 * 16);
164 		scope(exit) noteOnInfo = null;
165 
166 		static struct LastNoteInfo {
167 			PlayStreamEvent*[6] event; // in case there's a chord
168 			int eventCount;
169 			int turnedOnTicks;
170 		}
171 		LastNoteInfo[/*16*/] lastNoteInfo = new LastNoteInfo[](16); // it doesn't allow the static array cuz of @safe and i don't wanna deal with that so just doing this, nbd alloc anyway
172 
173 		void recordOff(scope NoteOnInfo* noi, int midiClockPosition) {
174 			noi.event.noteOnDuration = position - noi.turnedOnPosition;
175 
176 			noi.event = null;
177 		}
178 
179 		// FIXME: what about rests?
180 		foreach(item; flattenedTrackStream) {
181 			position += item.wait;
182 
183 			stream ~= item;
184 
185 			if(item.event.event == MIDI_EVENT_NOTE_ON) {
186 				if(item.event.data2 == 0)
187 					goto off;
188 
189 				auto ptr = &stream[$-1];
190 
191 				auto noi = &noteOnInfo[(item.event.channel & 0x0f) * 128 + (item.event.data1 & 0x7f)];
192 
193 				if(noi.event) {
194 					recordOff(noi, item.midiClockPosition);
195 				}
196 
197 				noi.event = ptr;
198 				noi.turnedOnTicks = item.midiClockPosition;
199 				noi.turnedOnPosition = position;
200 
201 				auto lni = &lastNoteInfo[(item.event.channel & 0x0f)];
202 				if(lni.eventCount) {
203 					if(item.midiClockPosition == lni.turnedOnTicks) {
204 						if(lni.eventCount == lni.event.length)
205 							goto maxedOut;
206 						lni.event[lni.eventCount++] = ptr;
207 					} else {
208 						maxedOut:
209 						foreach(ref e; lni.event[0 .. lni.eventCount])
210 							e.midiTicksToNextNoteOnChannel = item.midiClockPosition - lni.turnedOnTicks;
211 
212 						goto frist;
213 					}
214 				} else {
215 					frist:
216 					lni.event[0] = ptr;
217 					lni.eventCount = 1;
218 					lni.turnedOnTicks = item.midiClockPosition;
219 				}
220 
221 			} else if(item.event.event == MIDI_EVENT_NOTE_OFF) {
222 				off:
223 				auto noi = &noteOnInfo[(item.event.channel & 0x0f) * 128 + (item.event.data1 & 0x7f)];
224 
225 				if(noi.event) {
226 					recordOff(noi, item.midiClockPosition);
227 				}
228 			}
229 		}
230 
231 		return stream;
232 	}
233 
234 	/++
235 		Returns a forward range for playback or analysis that flattens the midi
236 		tracks into a single stream. Each item is a command, which
237 		is like the midi event but with some more annotations and control methods.
238 
239 		Modifying this MidiFile object or any of its children during iteration
240 		may cause trouble.
241 
242 		Note that you do not need to handle any meta events, it keeps the
243 		tempo internally, but you can look at it if you like.
244 	+/
245 	FlattenedTrackStream flattenedTrackStream() {
246 		return FlattenedTrackStream(this);
247 	}
248 }
249 
250 static struct PlayStreamEvent {
251 	/// This is how long you wait until triggering this event.
252 	/// Note it may be zero.
253 	Duration wait;
254 
255 	/// And this is the midi event message.
256 	MidiEvent event;
257 
258 	string toString() const {
259 		return event.toString();
260 	}
261 
262 	/// informational. May be null if the stream didn't come from a file or tracks.
263 	MidiFile file;
264 	/// ditto
265 	MidiTrack track;
266 
267 	/++
268 		Gives the position ot the global midi clock for this event. The `event.deltaTime`
269 		is in units of the midi clock, but the actual event has the clock per-track whereas
270 		this value is global, meaning it might not be the sum of event.deltaTime to this point.
271 		(It should add up if you only sum ones with the same [track] though.
272 
273 		The midi clock is used in conjunction with the [MidiFile.timing] and current tempo
274 		state to determine a real time wait value, which you can find in the [wait] member.
275 
276 		This position is probably less useful than the running sum of [wait]s, but is provided
277 		just in case it is useful to you.
278 	+/
279 	int midiClockPosition;
280 
281 	/++
282 		The duration between this non-zero velocity note on and its associated note off.
283 
284 		Will be zero if this isn't actually a note on, the input stream was not seekable (e.g.
285 		a real time recording), or if a note off was not found ahead in the stream.
286 
287 		It is basically how long the pianist held down the key.
288 
289 		Be aware that that the note on to note off is not necessarily associated with the
290 		note you'd see on sheet music. It is more about the time the sound actually rings,
291 		but it may not exactly be that either due to the time it takes for the note to
292 		fade out.
293 	+/
294 	Duration noteOnDuration;
295 	/++
296 		This is the count of midi clock ticks after this non-zero velocity note on event (if
297 		it is not one of those, this value will be zero) and the next note that will be sounded
298 		on its same channel.
299 
300 		While rests may throw this off, this number is the most help in this struct for determining
301 		the note length you'd put on sheet music. Divide it by [MidiFile.timing] to get the number
302 		of midi quarter notes, which is directly correlated to the musical beat.
303 
304 		Will be zero if this isn't actually a note on, the input stream was not seekable (e.g.
305 		a real time recording where the next note hasn't been struck yet), or if a note off was
306 		not found ahead in the stream.
307 	+/
308 	int midiTicksToNextNoteOnChannel;
309 
310 	// when recording and working in milliseconds we prolly want to round off to the nearest 64th note, or even less fine grained at user command todeal with bad musicians (i.e. me) being off beat
311 }
312 
313 static immutable(PlayStreamEvent)[] longWait = [{wait: 1.weeks, event: {status: 0xff, data1: 0x01, meta: null}}];
314 
315 struct FlattenedTrackStream {
316 
317 	FlattenedTrackStream save() {
318 		auto copy = this;
319 		copy.trackPositions = this.trackPositions.dup;
320 		return copy;
321 	}
322 
323 	MidiFile file;
324 	this(MidiFile file) {
325 		this.file = file;
326 		this.trackPositions.length = file.tracks.length;
327 		foreach(idx, ref tp; this.trackPositions) {
328 			tp.remaining = file.tracks[idx].events[];
329 			tp.track = file.tracks[idx];
330 		}
331 
332 		this.currentTrack = -1;
333 		this.tempo = 500000; // microseconds per quarter note
334 		popFront();
335 	}
336 
337 	//@nogc:
338 
339 	int midiClock;
340 
341 	void popFront() {
342 		done = true;
343 		for(auto c = currentTrack + 1; c < trackPositions.length; c++) {
344 			auto tp = trackPositions[c];
345 
346 			if(tp.remaining.length && tp.remaining[0].deltaTime == tp.clock) {
347 				auto f = tp.remaining[0];
348 				trackPositions[c].remaining = tp.remaining[1 .. $];
349 				trackPositions[c].clock = 0;
350 				if(tp.remaining.length == 0 || tp.remaining[0].deltaTime > 0) {
351 					currentTrack += 1;
352 				}
353 
354 				pending = PlayStreamEvent(0.seconds, f, file, tp.track, midiClock);
355 				processPending();
356 				done = false;
357 				return;
358 			}
359 		}
360 
361 		// if nothing happened there, time to advance the clock
362 		int minWait = int.max;
363 		int minWaitTrack = -1;
364 		foreach(idx, track; trackPositions) {
365 			if(track.remaining.length) {
366 				auto dt = track.remaining[0].deltaTime - track.clock;
367 				if(dt < minWait) {
368 					minWait = dt;
369 					minWaitTrack = cast(int) idx;
370 				}
371 			}
372 		}
373 
374 		if(minWaitTrack == -1) {
375 			done = true;
376 			return;
377 		}
378 
379 		foreach(ref tp; trackPositions) {
380 			tp.clock += minWait;
381 		}
382 
383 		done = false;
384 
385 		// file.timing, if high bit clear, is ticks per quarter note
386 		// if high bit set... idk it is different.
387 		//
388 		// then the temp is microseconds per quarter note.
389 
390 		auto time = (cast(long) minWait * tempo / file.timing).usecs;
391 		midiClock += minWait;
392 
393 		pending = PlayStreamEvent(time, trackPositions[minWaitTrack].remaining[0], file, trackPositions[minWaitTrack].track, midiClock);
394 		processPending();
395 		trackPositions[minWaitTrack].remaining = trackPositions[minWaitTrack].remaining[1 .. $];
396 		trackPositions[minWaitTrack].clock = 0;
397 		currentTrack = minWaitTrack;
398 
399 		return;
400 	}
401 
402 	private struct TrackPosition {
403 		MidiEvent[] remaining;
404 		int clock;
405 		MidiTrack track;
406 	}
407 	private TrackPosition[] trackPositions;
408 	private int currentTrack;
409 
410 	private void processPending() {
411 		if(pending.event.status == 0xff && pending.event.data1 == MetaEvent.Tempo) {
412 			this.tempo = 0;
413 			foreach(i; pending.event.meta) {
414 				this.tempo <<= 8;
415 				this.tempo |= i;
416 			}
417 		}
418 	}
419 
420 	@property
421 	PlayStreamEvent front() {
422 		return pending;
423 	}
424 
425 	private uint tempo;
426 	private PlayStreamEvent pending;
427 	private bool done;
428 
429 	@property
430 	bool empty() {
431 		return done;
432 	}
433 
434 }
435 
436 class MidiTrack {
437 	ubyte[] toBytes() {
438 		MidiWriteBuffer buf;
439 		foreach(event; events)
440 			event.writeToBuffer(buf);
441 
442 		MidiEvent end;
443 		end.status = 0xff;
444 		end.data1 = 0x2f;
445 		end.meta = null;
446 
447 		end.writeToBuffer(buf);
448 
449 		return buf.bytes;
450 	}
451 
452 	void loadFromBuffer(ref MidiReadBuffer buf) {
453 		if(buf.readChars(4) != "MTrk")
454 			throw new Exception("wtf no track header");
455 
456 		auto trackLength = buf.read4();
457 		auto begin = buf.bytes.length;
458 
459 		ubyte runningStatus;
460 
461 		while(buf.bytes.length) {
462 			MidiEvent newEvent = MidiEvent.fromBuffer(buf, runningStatus);
463 
464 			if(newEvent.isMeta && newEvent.data1 == MetaEvent.Name)
465 				name_ = cast(string) newEvent.meta.idup;
466 
467 			if(newEvent.status == 0xff && newEvent.data1 == MetaEvent.EndOfTrack) {
468 				break;
469 			}
470 			events ~= newEvent;
471 		}
472 		//assert(begin - trackLength == buf.bytes.length);
473 	}
474 
475 	/++
476 		All the midi events found in the track.
477 	+/
478 	MidiEvent[] events;
479 	/++
480 		The name of the track, as found from metadata at load time.
481 
482 		This may change to scan events to see updates without the cache in the future.
483 	+/
484 	@property string name() {
485 		return name_;
486 	}
487 
488 	private string name_;
489 
490 	/++
491 		This field is not used or stored in a midi file; it is just
492 		a place to store some state in your player.
493 
494 		I use it to keep flags like if the track is currently enabled.
495 	+/
496 	int customPlayerInfo;
497 
498 	override string toString() const {
499 		string s;
500 		foreach(event; events)
501 			s ~= event.toString ~ "\n";
502 		return s;
503 	}
504 }
505 
506 enum MetaEvent {
507 	SequenceNumber = 0,
508 	// these take a text param
509 	Text = 1,
510 	Copyright = 2,
511 	Name = 3,
512 	Instrument = 4,
513 	Lyric = 5,
514 	Marker = 6,
515 	CuePoint = 7,
516 	PatchName = 8,
517 	DeviceName = 9,
518 
519 	// no param
520 	EndOfTrack = 0x2f,
521 
522 	// different ones
523 	Tempo = 0x51, // 3 bytes form big-endian micro-seconds per quarter note. 120 BPM default.
524 	SMPTEOffset = 0x54, // 5 bytes. I don't get this one....
525 	TimeSignature = 0x58, // 4 bytes: numerator, denominator, clocks per click, 32nd notes per quarter note. (8 == quarter note gets the beat)
526 	KeySignature = 0x59, // 2 bytes: first byte is signed offset from C in semitones, second byte is 0 for major, 1 for minor
527 
528 	// arbitrary length custom param
529 	Proprietary = 0x7f,
530 
531 }
532 
533 struct MidiEvent {
534 	int deltaTime;
535 
536 	ubyte status;
537 
538 	ubyte data1; // if meta, this is the identifier
539 
540 	//union {
541 		//struct {
542 			ubyte data2;
543 		//}
544 
545 		const(ubyte)[] meta; // iff status == 0xff
546 	//}
547 
548 	invariant () {
549 		assert(status & 0x80);
550 		assert(!(data1 & 0x80));
551 		assert(!(data2 & 0x80));
552 		assert(status == 0xff || meta is null);
553 	}
554 
555 	/// Convenience factories for various meta-events
556 	static MidiEvent Text(string t) { return MidiEvent(0, 0xff, MetaEvent.Text, 0, cast(const(ubyte)[]) t); }
557 	/// ditto
558 	static MidiEvent Copyright(string t) { return MidiEvent(0, 0xff, MetaEvent.Copyright, 0, cast(const(ubyte)[]) t); }
559 	/// ditto
560 	static MidiEvent Name(string t) { return MidiEvent(0, 0xff, MetaEvent.Name, 0, cast(const(ubyte)[]) t); }
561 	/// ditto
562 	static MidiEvent Lyric(string t) { return MidiEvent(0, 0xff, MetaEvent.Lyric, 0, cast(const(ubyte)[]) t); }
563 	/// ditto
564 	static MidiEvent Marker(string t) { return MidiEvent(0, 0xff, MetaEvent.Marker, 0, cast(const(ubyte)[]) t); }
565 	/// ditto
566 	static MidiEvent CuePoint(string t) { return MidiEvent(0, 0xff, MetaEvent.CuePoint, 0, cast(const(ubyte)[]) t); }
567 
568 	/++
569 		Conveneince factories for normal events. These just put your given values into the event as raw data so you're responsible to know what they do.
570 
571 		History:
572 			Added January 2, 2022 (dub v10.5)
573 	+/
574 	static MidiEvent NoteOn(int channel, int note, int velocity) { return MidiEvent(0, (MIDI_EVENT_NOTE_ON << 4) | (channel & 0x0f), note & 0x7f, velocity & 0x7f); }
575 	/// ditto
576 	static MidiEvent NoteOff(int channel, int note, int velocity) { return MidiEvent(0, (MIDI_EVENT_NOTE_OFF << 4) | (channel & 0x0f), note & 0x7f, velocity & 0x7f); }
577 
578 	/+
579 	// FIXME: this is actually a relatively complicated one i should fix, it combines bits... 8192 == 0.
580 	// This is a bit of a magical function, it takes a signed bend between 0 and 81
581 	static MidiEvent PitchBend(int channel, int bend) {
582 		return MidiEvent(0, (MIDI_EVENT_PITCH_BEND << 4) | (channel & 0x0f), bend & 0x7f, bend & 0x7f);
583 	}
584 	+/
585 	// this overload ok, it is what the thing actually tells. coarse == 64 means we're at neutral.
586 	/// ditto
587 	static MidiEvent PitchBend(int channel, int fine, int coarse) { return MidiEvent(0, (MIDI_EVENT_PITCH_BEND << 4) | (channel & 0x0f), fine & 0x7f, coarse & 0x7f); }
588 
589 	/// ditto
590 	static MidiEvent NoteAftertouch(int channel, int note, int velocity) { return MidiEvent(0, (MIDI_EVENT_NOTE_AFTERTOUCH << 4) | (channel & 0x0f), note & 0x7f, velocity & 0x7f); }
591 	// FIXME the different controllers do have standard IDs we could look up in an enum... and many of them have coarse/fine things you can send as two messages.
592 	/// ditto
593 	static MidiEvent Controller(int channel, int controller, int value) { return MidiEvent(0, (MIDI_EVENT_CONTROLLER << 4) | (channel & 0x0f), controller & 0x7f, value & 0x7f); }
594 
595 	// the two byte ones
596 	/// ditto
597 	static MidiEvent ProgramChange(int channel, int program) { return MidiEvent(0, (MIDI_EVENT_PROGRAM_CHANGE << 4) | (channel & 0x0f), program & 0x7f); }
598 	/// ditto
599 	static MidiEvent ChannelAftertouch(int channel, int param) { return MidiEvent(0, (MIDI_EVENT_CHANNEL_AFTERTOUCH << 4) | (channel & 0x0f), param & 0x7f); }
600 
601 	///
602 	bool isMeta() const {
603 		return status == 0xff;
604 	}
605 
606 	///
607 	ubyte event() const {
608 		return status >> 4;
609 	}
610 
611 	///
612 	ubyte channel() const {
613 		return status & 0x0f;
614 	}
615 
616 	///
617 	string toString() const {
618 
619 		static string tos(int a) {
620 			char[16] buffer;
621 			auto bufferPos = buffer.length;
622 			do {
623 				buffer[--bufferPos] = a % 10 + '0';
624 				a /= 10;
625 			} while(a);
626 
627 			return buffer[bufferPos .. $].idup;
628 		}
629 
630 		static string toh(ubyte b) {
631 			char[2] buffer;
632 			buffer[0] = (b >> 4) & 0x0f;
633 			if(buffer[0] < 10)
634 				buffer[0] += '0';
635 			else
636 				buffer[0] += 'A' - 10;
637 			buffer[1] = b & 0x0f;
638 			if(buffer[1] < 10)
639 				buffer[1] += '0';
640 			else
641 				buffer[1] += 'A' - 10;
642 
643 			return buffer.idup;
644 		}
645 
646 		string s;
647 		s ~= tos(deltaTime);
648 		s ~= ": ";
649 		s ~= toh(status);
650 		s ~= " ";
651 		s ~= toh(data1);
652 		s ~= " ";
653 
654 		if(isMeta) {
655 			switch(data1) {
656 				case MetaEvent.Text:
657 				case MetaEvent.Copyright:
658 				case MetaEvent.Name:
659 				case MetaEvent.Instrument:
660 				case MetaEvent.Lyric:
661 				case MetaEvent.Marker:
662 				case MetaEvent.CuePoint:
663 				case MetaEvent.PatchName:
664 				case MetaEvent.DeviceName:
665 					s ~= cast(const(char)[]) meta;
666 				break;
667 				case MetaEvent.TimeSignature:
668 					ubyte numerator = meta[0];
669 					ubyte denominator = meta[1];
670 					ubyte clocksPerClick = meta[2];
671 					ubyte notesPerQuarter = meta[3]; // 32nd notes / Q so 8 = quarter note gets the beat
672 
673 					s ~= tos(numerator);
674 					s ~= "/";
675 					s ~= tos(denominator);
676 					s ~= " ";
677 					s ~= tos(clocksPerClick);
678 					s ~= " ";
679 					s ~= tos(notesPerQuarter);
680 				break;
681 				case MetaEvent.KeySignature:
682 					byte offset = meta[0];
683 					ubyte minor = meta[1];
684 
685 					if(offset < 0) {
686 						s ~= "-";
687 						s ~= tos(-cast(int) offset);
688 					} else {
689 						s ~= tos(offset);
690 					}
691 					s ~= minor ? " minor" : " major";
692 				break;
693 				// case MetaEvent.Tempo:
694 					// could process this but idk if it needs to be shown
695 				// break;
696 				case MetaEvent.Proprietary:
697 					foreach(m; meta) {
698 						s ~= toh(m);
699 						s ~= " ";
700 					}
701 				break;
702 				default:
703 					s ~= cast(const(char)[]) meta;
704 			}
705 		} else {
706 			s ~= toh(data2);
707 
708 			s ~= " ";
709 			s ~= tos(channel);
710 			s ~= " ";
711 			switch(event) {
712 				case MIDI_EVENT_NOTE_OFF: s ~= "NOTE_OFF"; break;
713 				case MIDI_EVENT_NOTE_ON: s ~=  data2 ? "NOTE_ON" : "NOTE_ON_ZERO"; break;
714 				case MIDI_EVENT_NOTE_AFTERTOUCH: s ~= "NOTE_AFTERTOUCH"; break;
715 				case MIDI_EVENT_CONTROLLER: s ~= "CONTROLLER"; break;
716 				case MIDI_EVENT_PROGRAM_CHANGE: s ~= "PROGRAM_CHANGE"; break;
717 				case MIDI_EVENT_CHANNEL_AFTERTOUCH: s ~= "CHANNEL_AFTERTOUCH"; break;
718 				case MIDI_EVENT_PITCH_BEND: s ~= "PITCH_BEND"; break;
719 				default:
720 			}
721 		}
722 
723 		return s;
724 	}
725 
726 	static MidiEvent fromBuffer(ref MidiReadBuffer buf, ref ubyte runningStatus) {
727 		MidiEvent event;
728 
729 		start_over:
730 
731 		event.deltaTime = buf.readv();
732 
733 		auto nb = buf.read1();
734 
735 		if(nb == 0xff) {
736 			// meta...
737 			event.status = 0xff;
738 			event.data1 = buf.read1(); // the type
739 			int len = buf.readv();
740 			auto meta = new ubyte[](len);
741 			foreach(idx; 0 .. len)
742 				meta[idx] = buf.read1();
743 			event.meta = meta;
744 		} else if(nb >= 0xf0) {
745 			// FIXME I'm just skipping this entirely but there might be value in here
746 			nb = buf.read1();
747 			while(nb < 0xf0)
748 				nb = buf.read1();
749 			goto start_over;
750 		} else if(nb & 0b1000_0000) {
751 			event.status = nb;
752 			runningStatus = nb;
753 			event.data1 = buf.read1();
754 
755 			if(event.event != MIDI_EVENT_CHANNEL_AFTERTOUCH &&
756 				event.event != MIDI_EVENT_PROGRAM_CHANGE)
757 			{
758 				event.data2 = buf.read1();
759 			}
760 		} else {
761 			event.status = runningStatus;
762 			event.data1 = nb;
763 
764 			if(event.event != MIDI_EVENT_CHANNEL_AFTERTOUCH &&
765 				event.event != MIDI_EVENT_PROGRAM_CHANGE)
766 			{
767 				event.data2 = buf.read1();
768 			}
769 		}
770 
771 		return event;
772 	}
773 
774 	void writeToBuffer(ref MidiWriteBuffer buf) const {
775 		buf.writev(deltaTime);
776 		buf.write1(status);
777 		// FIXME: what about other sysex stuff?
778 		if(meta) {
779 			buf.write1(data1);
780 			buf.writev(cast(int) meta.length);
781 			buf.write(meta);
782 		} else {
783 			buf.write1(data1);
784 
785 			if(event != MIDI_EVENT_CHANNEL_AFTERTOUCH &&
786 				event != MIDI_EVENT_PROGRAM_CHANGE)
787 			{
788 				buf.write1(data2);
789 			}
790 		}
791 	}
792 }
793 
794 struct MidiReadBuffer {
795 	ubyte[] bytes;
796 
797 	char[] readChars(int len) {
798 		auto c = bytes[0 .. len];
799 		bytes = bytes[len .. $];
800 		return cast(char[]) c;
801 	}
802 	ubyte[] readBytes(int len) {
803 		auto c = bytes[0 .. len];
804 		bytes = bytes[len .. $];
805 		return c;
806 	}
807 	int read4() {
808 		int i;
809 		foreach(a; 0 .. 4) {
810 			i <<= 8;
811 			i |= bytes[0];
812 			bytes = bytes[1 .. $];
813 		}
814 		return i;
815 	}
816 	ushort read2() {
817 		ushort i;
818 		foreach(a; 0 .. 2) {
819 			i <<= 8;
820 			i |= bytes[0];
821 			bytes = bytes[1 .. $];
822 		}
823 		return i;
824 	}
825 	ubyte read1() {
826 		auto b = bytes[0];
827 		bytes = bytes[1 .. $];
828 		return b;
829 	}
830 	int readv() {
831 		int value = read1();
832 		ubyte c;
833 		if(value & 0x80) {
834 			value &= 0x7f;
835 			do
836 				value = (value << 7) | ((c = read1) & 0x7f);
837 			while(c & 0x80);
838 		}
839 		return value;
840 	}
841 }
842 
843 struct MidiWriteBuffer {
844 	ubyte[] bytes;
845 
846 	void write(const char[] a) {
847 		bytes ~= a;
848 	}
849 
850 	void write(const ubyte[] a) {
851 		bytes ~= a;
852 	}
853 
854 	void write4(int v) {
855 		// big endian
856 		bytes ~= (v >> 24) & 0xff;
857 		bytes ~= (v >> 16) & 0xff;
858 		bytes ~= (v >> 8) & 0xff;
859 		bytes ~= v & 0xff;
860 	}
861 
862 	void write2(ushort v) {
863 		// big endian
864 		bytes ~= v >> 8;
865 		bytes ~= v & 0xff;
866 	}
867 
868 	void write1(ubyte v) {
869 		bytes ~= v;
870 	}
871 
872 	void writev(int v) {
873 		// variable
874 		uint buffer = v & 0x7f;
875 		while((v >>= 7)) {
876 			buffer <<= 8;
877 			buffer |= ((v & 0x7f) | 0x80);
878 		}
879 
880 		while(true) {
881 			bytes ~= buffer & 0xff;
882 			if(buffer & 0x80)
883 				buffer >>= 8;
884 			else
885 				break;
886 		}
887 	}
888 }
889 
890 import core.stdc.stdio;
891 import core.stdc.stdlib;
892 
893 int freq(int note){
894 	import std.math;
895 	float r = note - 69;
896 	r /= 12;
897 	r = pow(2, r);
898 	r*= 440;
899 	return cast(int) r;
900 }
901 
902 enum A =  69; // 440 hz per midi spec
903 enum As = 70;
904 enum B =  71;
905 enum C =  72; // middle C + 1 octave
906 enum Cs = 73;
907 enum D =  74;
908 enum Ds = 75;
909 enum E =  76;
910 enum F =  77;
911 enum Fs = 78;
912 enum G =  79;
913 enum Gs = 80;
914 
915 immutable string[] noteNames = [ // just do note % 12 to index this
916         "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
917 ];
918 
919 enum MIDI_EVENT_NOTE_OFF =		0x08;
920 enum MIDI_EVENT_NOTE_ON =		0x09;
921 enum MIDI_EVENT_NOTE_AFTERTOUCH =	0x0a;
922 enum MIDI_EVENT_CONTROLLER =		0x0b;
923 enum MIDI_EVENT_PROGRAM_CHANGE =	0x0c;// only one param
924 enum MIDI_EVENT_CHANNEL_AFTERTOUCH =	0x0d;// only one param
925 enum MIDI_EVENT_PITCH_BEND =		0x0e;
926 
927 
928  /+
929  35   Acoustic Bass Drum     59   Ride Cymbal 2
930  36   Bass Drum 1            60   Hi Bongo
931  37   Side Stick             61   Low Bongo
932  38   Acoustic Snare         62   Mute Hi Conga
933  39   Hand Clap              63   Open Hi Conga
934  40   Electric Snare         64   Low Conga
935  41   Low Floor Tom          65   High Timbale
936  42   Closed Hi-Hat          66   Low Timbale
937  43   High Floor Tom         67   High Agogo
938  44   Pedal Hi-Hat           68   Low Agogo
939  45   Low Tom                69   Cabasa
940  46   Open Hi-Hat            70   Maracas
941  47   Low-Mid Tom            71   Short Whistle
942  48   Hi-Mid Tom             72   Long Whistle
943  49   Crash Cymbal 1         73   Short Guiro
944  50   High Tom               74   Long Guiro
945  51   Ride Cymbal 1          75   Claves
946  52   Chinese Cymbal         76   Hi Wood Block
947  53   Ride Bell              77   Low Wood Block
948  54   Tambourine             78   Mute Cuica
949  55   Splash Cymbal          79   Open Cuica
950  56   Cowbell                80   Mute Triangle
951  57   Crash Cymbal 2         81   Open Triangle
952  58   Vibraslap
953  +/
954 
955 static immutable string[] instrumentNames = [
956 "", // 0 is nothing
957 // Piano:
958 "Acoustic Grand Piano",
959 "Bright Acoustic Piano",
960 "Electric Grand Piano",
961 "Honky-tonk Piano",
962 "Electric Piano 1",
963 "Electric Piano 2",
964 "Harpsichord",
965 "Clavinet",
966 
967 // Chromatic Percussion:
968 "Celesta",
969 "Glockenspiel",
970 "Music Box",
971 "Vibraphone",
972 "Marimba",
973 "Xylophone",
974 "Tubular Bells",
975 "Dulcimer",
976 
977 // Organ:
978 "Drawbar Organ",
979 "Percussive Organ",
980 "Rock Organ",
981 "Church Organ",
982 "Reed Organ",
983 "Accordion",
984 "Harmonica",
985 "Tango Accordion",
986 
987 // Guitar:
988 "Acoustic Guitar (nylon)",
989 "Acoustic Guitar (steel)",
990 "Electric Guitar (jazz)",
991 "Electric Guitar (clean)",
992 "Electric Guitar (muted)",
993 "Overdriven Guitar",
994 "Distortion Guitar",
995 "Guitar harmonics",
996 
997 // Bass:
998 "Acoustic Bass",
999 "Electric Bass (finger)",
1000 "Electric Bass (pick)",
1001 "Fretless Bass",
1002 "Slap Bass 1",
1003 "Slap Bass 2",
1004 "Synth Bass 1",
1005 "Synth Bass 2",
1006 
1007 // Strings:
1008 "Violin",
1009 "Viola",
1010 "Cello",
1011 "Contrabass",
1012 "Tremolo Strings",
1013 "Pizzicato Strings",
1014 "Orchestral Harp",
1015 "Timpani",
1016 
1017 // Strings (continued):
1018 "String Ensemble 1",
1019 "String Ensemble 2",
1020 "Synth Strings 1",
1021 "Synth Strings 2",
1022 "Choir Aahs",
1023 "Voice Oohs",
1024 "Synth Voice",
1025 "Orchestra Hit",
1026 
1027 // Brass:
1028 "Trumpet",
1029 "Trombone",
1030 "Tuba",
1031 "Muted Trumpet",
1032 "French Horn",
1033 "Brass Section",
1034 "Synth Brass 1",
1035 "Synth Brass 2",
1036 
1037 // Reed:
1038 "Soprano Sax",
1039 "Alto Sax",
1040 "Tenor Sax",
1041 "Baritone Sax",
1042 "Oboe",
1043 "English Horn",
1044 "Bassoon",
1045 "Clarinet",
1046 
1047 // Pipe:
1048 "Piccolo",
1049 "Flute",
1050 "Recorder",
1051 "Pan Flute",
1052 "Blown Bottle",
1053 "Shakuhachi",
1054 "Whistle",
1055 "Ocarina",
1056 
1057 // Synth Lead:
1058 "Lead 1 (square)",
1059 "Lead 2 (sawtooth)",
1060 "Lead 3 (calliope)",
1061 "Lead 4 (chiff)",
1062 "Lead 5 (charang)",
1063 "Lead 6 (voice)",
1064 "Lead 7 (fifths)",
1065 "Lead 8 (bass + lead)",
1066 
1067 // Synth Pad:
1068 "Pad 1 (new age)",
1069 "Pad 2 (warm)",
1070 "Pad 3 (polysynth)",
1071 "Pad 4 (choir)",
1072 "Pad 5 (bowed)",
1073 "Pad 6 (metallic)",
1074 "Pad 7 (halo)",
1075 "Pad 8 (sweep)",
1076 
1077 // Synth Effects:
1078 "FX 1 (rain)",
1079 "FX 2 (soundtrack)",
1080 "FX 3 (crystal)",
1081 "FX 4 (atmosphere)",
1082 "FX 5 (brightness)",
1083 "FX 6 (goblins)",
1084 "FX 7 (echoes)",
1085 "FX 8 (sci-fi)",
1086 
1087 // Ethnic:
1088 "Sitar",
1089 "Banjo",
1090 "Shamisen",
1091 "Koto",
1092 "Kalimba",
1093 "Bag pipe",
1094 "Fiddle",
1095 "Shanai",
1096 
1097 // Percussive:
1098 "Tinkle Bell",
1099 "Agogo",
1100 "Steel Drums",
1101 "Woodblock",
1102 "Taiko Drum",
1103 "Melodic Tom",
1104 "Synth Drum",
1105 
1106 // Sound effects:
1107 "Reverse Cymbal",
1108 "Guitar Fret Noise",
1109 "Breath Noise",
1110 "Seashore",
1111 "Bird Tweet",
1112 "Telephone Ring",
1113 "Helicopter",
1114 "Applause",
1115 "Gunshot"
1116 ];
1117 
1118 version(MidiDemo) {
1119 
1120 
1121 enum SKIP_MAX = 3000; // allow no more than about 3 seconds of silence
1122 			 // if the -k option is set
1123 
1124 // Potential FIXME: it doesn't support more than 128 tracks.
1125 
1126 void awesome(void* midiptr, int note, int wait) {
1127 	printf("%d %d ", wait, note);
1128 	fflush(stdout);
1129 }
1130 
1131 // FIXME: add support for displaying lyrics
1132 extern(C) int main(int argc, char** argv){
1133 
1134 	for(a = 1; a < argc; a++){
1135 		if(argv[a][0] == '-')
1136 		switch(argv[a][1]){
1137 			case 't':
1138 				for(b = 0; b< 128; b++)
1139 					playtracks[b] = 0;
1140 				num = 0;
1141 				b = 0;
1142 				a++;
1143 				if(a == argc){
1144 					printf("%s: option %s requires an argument\n", argv[0], argv[a-1]);
1145 					return 1;
1146 				}
1147 				for(b = 0; argv[a][b]; b++){
1148 					if(argv[a][b] == ','){
1149 						playtracks[num] = 1;
1150 						num = 0;
1151 						continue;
1152 					}
1153 					num *= 10;
1154 					num += argv[a][b] - '0';
1155 				}
1156 				playtracks[num] = 1;
1157 			break;
1158 			case 's':
1159 				a++;
1160 				if(a == argc){
1161 					printf("%s: option %s requires an argument\n", argv[0], argv[a-1]);
1162 					return 1;
1163 				}
1164 				tempoMultiplier = atof(argv[a]);
1165 			break;
1166 			case 'i': // FIXME
1167 				displayinfo = 1;
1168 				// tracks, guesstimated length
1169 			break;
1170 			// -o loop to from
1171 			// -b begin at
1172 			// -e end at
1173 			case 'l':
1174 				tracing = 1;
1175 			break;
1176 			case 'n':
1177 				play = 0;
1178 			break;
1179 			case 'k':
1180 				skip = 1;
1181 			break;
1182 			case 'c':
1183 				channelMask = 0;
1184 				// channels
1185 				num = 0;
1186 				b = 0;
1187 				a++;
1188 				if(a == argc){
1189 					printf("%s: option %s requires an argument\n", argv[0], argv[a-1]);
1190 					return 1;
1191 				}
1192 				for(b = 0; argv[a][b]; b++){
1193 					if(argv[a][b] == ','){
1194 						channelMask |= (1 << num);
1195 						num = 0;
1196 						continue;
1197 					}
1198 					num *= 10;
1199 					num += argv[a][b] - '0';
1200 				}
1201 					channelMask |= (1 << num);
1202 			break;
1203 			case 'r':
1204 				a++;
1205 				if(a == argc){
1206 					printf("%s: option %s requires an argument\n", argv[0], argv[a-1]);
1207 					return 1;
1208 				}
1209 				transpose = atoi(argv[a]);
1210 			break;
1211 			case 'v':
1212 				verbose = 1;
1213 			break;
1214 			case 'h':
1215 				printf("Usage: %s [options...] file\n", argv[0]);
1216 				printf("  Options:\n");
1217 				printf("  -t comma separated list of tracks to play (default: all)\n");
1218 				printf("  -s tempo (speed) multiplier (default: 1.0)\n");
1219 				printf("  -i file info (track list)\n");
1220 				printf("  -l list notes as they are played (in the format totablature expects)\n");
1221 				printf("  -n no sound; don't actually play the midi\n");
1222 				printf("  -c comma separated list of channels to play (default: all)\n");
1223 				printf("  -r transpose notes by amount (default: 0)\n");
1224 				printf("  -k skip long sections of silence (good for playing single tracks)\n");
1225 
1226 				printf("  -v verbose; list all events except note on / note off\n");
1227 				printf("  -h shows this help screen\n");
1228 
1229 				return 0;
1230 			break;
1231 			default:
1232 				printf("%s: unknown command line option: %s\n", argv[0], argv[1]);
1233 				return 1;
1234 		}
1235 		else
1236 			filename = argv[a];
1237 	}
1238 
1239 	if(filename == null){
1240 		printf("%s: no file given. Try %s -h for help.\n", argv[0], argv[0]);
1241 		return 1;
1242 	}
1243 
1244 	loadMidi(&mid, filename);
1245 	if(mid == null){
1246 		printf("%s: unable to read file %s\n", argv[0], filename);
1247 		return 1;
1248 	}
1249 
1250 	if(displayinfo){
1251 		int len = getMidiLength(mid);
1252 		printf("File: %s\n", filename);
1253 		printf("Ticks per quarter note: %d\n", mid.speed);
1254 		printf("Initial tempo: %d\n", getMidiTempo(mid));
1255 		printf("Length: %d:%d\n", len / 60, len%60);
1256 		printf("Tracks:\n");
1257 		for(a = 0; a < mid.numTracks; a++){
1258 			c[0] = getTrackNameChunk(mid, a);
1259 			if(c[0] != null){
1260 				printf("%d: ", a);
1261 				for(b = 0; b < c[0].length; b++)
1262 					fputc(c[0].data[b], stdout);
1263 				printf("\n");
1264 			}
1265 		}
1266 
1267 		freeMidi(&mid);
1268 		return 0;
1269 	}
1270 
1271 	return 0;
1272 }
1273 }