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 import core.time;
16 
17 version(NewMidiDemo)
18 void main(string[] args) {
19 	auto f = new MidiFile();
20 
21 	import std.file;
22 
23 	//f.loadFromBytes(cast(ubyte[]) read("test.mid"));
24 	f.loadFromBytes(cast(ubyte[]) read(args[1]));
25 
26 	import arsd.simpleaudio;
27 	import core.thread;
28 
29 	auto o = MidiOutput(0);
30 	setSigIntHandler();
31 	scope(exit) {
32 		o.silenceAllNotes();
33 		o.reset();
34 		restoreSigIntHandler();
35 	}
36 
37 	import std.stdio : writeln;
38 	foreach(item; f.playbackStream) {
39 		if(interrupted) return;
40 
41 		Thread.sleep(item.wait);
42 		if(!item.event.isMeta)
43 			o.writeMidiMessage(item.event.status, item.event.data1, item.event.data2);
44 		else
45 			writeln(item);
46 	}
47 
48 	return;
49 
50 	auto t = new MidiTrack();
51 	auto t2 = new MidiTrack();
52 
53 	f.tracks ~= t;
54 	f.tracks ~= t2;
55 
56 	t.events ~= MidiEvent(0, 0x90, C, 127);
57 	t.events ~= MidiEvent(256, 0x90, C, 0);
58 	t.events ~= MidiEvent(256, 0x90, D, 127);
59 	t.events ~= MidiEvent(256, 0x90, D, 0);
60 	t.events ~= MidiEvent(256, 0x90, E, 127);
61 	t.events ~= MidiEvent(256, 0x90, E, 0);
62 	t.events ~= MidiEvent(256, 0x90, F, 127);
63 	t.events ~= MidiEvent(0, 0xff, 0x05, 0 /* unused */, ['h', 'a', 'm']);
64 	t.events ~= MidiEvent(256, 0x90, F, 0);
65 
66 	t2.events ~= MidiEvent(0, (MIDI_EVENT_PROGRAM_CHANGE << 4) | 0x01, 68);
67 	t2.events ~= MidiEvent(128, 0x91, E, 127);
68 	t2.events ~= MidiEvent(0, 0xff, 0x05, 0 /* unused */, ['a', 'd', 'r']);
69 	t2.events ~= MidiEvent(1024, 0x91, E, 0);
70 
71 	write("test.mid", f.toBytes());
72 }
73 
74 @safe:
75 
76 class MidiFile {
77 	///
78 	ubyte[] toBytes() {
79 		MidiWriteBuffer buf;
80 
81 		buf.write("MThd");
82 		buf.write4(6);
83 
84 		buf.write2(format);
85 		buf.write2(cast(ushort) tracks.length);
86 		buf.write2(timing);
87 
88 		foreach(track; tracks) {
89 			auto data = track.toBytes();
90 			buf.write("MTrk");
91 			buf.write4(cast(int) data.length);
92 			buf.write(data);
93 		}
94 
95 		return buf.bytes;
96 	}
97 
98 	///
99 	void loadFromBytes(ubyte[] bytes) {
100 		// FIXME: actually read the riff header to skip properly
101 		if(bytes.length && bytes[0] == 'R')
102 			bytes = bytes[0x14 .. $];
103 
104 		MidiReadBuffer buf = MidiReadBuffer(bytes);
105 		if(buf.readChars(4) != "MThd")
106 			throw new Exception("not midi");
107 		if(buf.read4() != 6)
108 			throw new Exception("idk what this even is");
109 		this.format = buf.read2();
110 		this.tracks = new MidiTrack[](buf.read2());
111 		this.timing = buf.read2();
112 
113 		foreach(ref track; tracks) {
114 			track = new MidiTrack();
115 			track.loadFromBuffer(buf);
116 		}
117 	}
118 
119 	// when I read, I plan to cut the end of track marker off.
120 
121 	// 0 == combined into one track
122 	// 1 == multiple tracks
123 	// 2 == multiple one-track patterns
124 	ushort format = 1;
125 
126 	// FIXME
127 	ushort timing = 0x80; // 128 ticks per quarter note
128 
129 	MidiTrack[] tracks;
130 
131 	/++
132 		Returns a forward range for playback. Each item is a command, which
133 		is like the midi event but with some more annotations and control methods.
134 
135 		Modifying this MidiFile object or any of its children during playback
136 		may cause trouble.
137 
138 		Note that you do not need to handle any meta events, it keeps the
139 		tempo internally, but you can look at it if you like.
140 	+/
141 	PlayStream playbackStream() {
142 		return PlayStream(this);
143 	}
144 }
145 
146 struct PlayStream {
147 	static struct Event {
148 		/// This is how long you wait until triggering this event.
149 		/// Note it may be zero.
150 		Duration wait;
151 
152 		/// And this is the event.
153 		MidiEvent event;
154 
155 		string toString() {
156 			return event.toString();
157 		}
158 
159 		/// informational
160 		MidiFile file;
161 		/// ditto
162 		MidiTrack track;
163 	}
164 
165 	PlayStream save() {
166 		auto copy = this;
167 		copy.trackPositions = this.trackPositions.dup;
168 		return copy;
169 	}
170 
171 	MidiFile file;
172 	this(MidiFile file) {
173 		this.file = file;
174 		this.trackPositions.length = file.tracks.length;
175 		foreach(idx, ref tp; this.trackPositions) {
176 			tp.remaining = file.tracks[idx].events[];
177 			tp.track = file.tracks[idx];
178 		}
179 
180 		this.currentTrack = -1;
181 		this.tempo = 500000;
182 		popFront();
183 	}
184 
185 	//@nogc:
186 
187 	void popFront() {
188 		done = true;
189 		for(auto c = currentTrack + 1; c < trackPositions.length; c++) {
190 			auto tp = trackPositions[c];
191 
192 			if(tp.remaining.length && tp.remaining[0].deltaTime == tp.clock) {
193 				auto f = tp.remaining[0];
194 				trackPositions[c].remaining = tp.remaining[1 .. $];
195 				trackPositions[c].clock = 0;
196 				if(tp.remaining.length == 0 || tp.remaining[0].deltaTime > 0) {
197 					currentTrack += 1;
198 				}
199 
200 				pending = Event(0.seconds, f, file, tp.track);
201 				processPending();
202 				done = false;
203 				return;
204 			}
205 		}
206 
207 		// if nothing happened there, time to advance the clock
208 		int minWait = int.max;
209 		int minWaitTrack = -1;
210 		foreach(idx, track; trackPositions) {
211 			if(track.remaining.length) {
212 				auto dt = track.remaining[0].deltaTime - track.clock;
213 				if(dt < minWait) {
214 					minWait = dt;
215 					minWaitTrack = cast(int) idx;
216 				}
217 			}
218 		}
219 
220 		if(minWaitTrack == -1) {
221 			done = true;
222 			return;
223 		}
224 
225 		foreach(ref tp; trackPositions) {
226 			tp.clock += minWait;
227 		}
228 
229 		done = false;
230 
231 		// file.timing, if high bit clear, is ticks per quarter note
232 		// if high bit set... idk it is different.
233 		//
234 		// then the temp is microseconds per quarter note.
235 		auto time = (minWait * tempo / file.timing).usecs;
236 
237 		pending = Event(time, trackPositions[minWaitTrack].remaining[0], file, trackPositions[minWaitTrack].track);
238 		processPending();
239 		trackPositions[minWaitTrack].remaining = trackPositions[minWaitTrack].remaining[1 .. $];
240 		trackPositions[minWaitTrack].clock = 0;
241 		currentTrack = minWaitTrack;
242 
243 		return;
244 	}
245 
246 	private struct TrackPosition {
247 		MidiEvent[] remaining;
248 		int clock;
249 		MidiTrack track;
250 	}
251 	private TrackPosition[] trackPositions;
252 	private int currentTrack;
253 
254 	private void processPending() {
255 		if(pending.event.status == 0xff && pending.event.data1 == MetaEvent.Tempo) {
256 			this.tempo = 0;
257 			foreach(i; pending.event.meta) {
258 				this.tempo <<= 8;
259 				this.tempo |= i;
260 			}
261 		}
262 	}
263 
264 	@property
265 	Event front() {
266 		return pending;
267 	}
268 
269 	private uint tempo;
270 	private Event pending;
271 	private bool done;
272 
273 	@property
274 	bool empty() {
275 		return done;
276 	}
277 }
278 
279 class MidiTrack {
280 	ubyte[] toBytes() {
281 		MidiWriteBuffer buf;
282 		foreach(event; events)
283 			event.writeToBuffer(buf);
284 
285 		MidiEvent end;
286 		end.status = 0xff;
287 		end.data1 = 0x2f;
288 		end.meta = null;
289 
290 		end.writeToBuffer(buf);
291 
292 		return buf.bytes;
293 	}
294 
295 	void loadFromBuffer(ref MidiReadBuffer buf) {
296 		if(buf.readChars(4) != "MTrk")
297 			throw new Exception("wtf no track header");
298 
299 		auto trackLength = buf.read4();
300 		auto begin = buf.bytes.length;
301 
302 		ubyte runningStatus;
303 
304 		while(buf.bytes.length) {
305 			MidiEvent newEvent = MidiEvent.fromBuffer(buf, runningStatus);
306 			if(newEvent.status == 0xff && newEvent.data1 == MetaEvent.EndOfTrack) {
307 				break;
308 			}
309 			events ~= newEvent;
310 		}
311 		//assert(begin - trackLength == buf.bytes.length);
312 	}
313 
314 	MidiEvent[] events;
315 
316 	override string toString() const {
317 		string s;
318 		foreach(event; events)
319 			s ~= event.toString ~ "\n";
320 		return s;
321 	}
322 }
323 
324 enum MetaEvent {
325 	SequenceNumber = 0,
326 	// these take a text param
327 	Text = 1,
328 	Copyright = 2,
329 	Name = 3,
330 	Instrument = 4,
331 	Lyric = 5,
332 	Marker = 6,
333 	CuePoint = 7,
334 	PatchName = 8,
335 	DeviceName = 9,
336 
337 	// no param
338 	EndOfTrack = 0x2f,
339 
340 	// different ones
341 	Tempo = 0x51, // 3 bytes form big-endian micro-seconds per quarter note. 120 BPM default.
342 	SMPTEOffset = 0x54, // 5 bytes. I don't get this one....
343 	TimeSignature = 0x58, // 4 bytes: numerator, denominator, clocks per click, 32nd notes per quarter note. (8 == quarter note gets the beat)
344 	KeySignature = 0x59, // 2 bytes: first byte is signed offset from C in semitones, second byte is 0 for major, 1 for minor
345 
346 	// arbitrary length custom param
347 	Proprietary = 0x7f,
348 
349 }
350 
351 struct MidiEvent {
352 	int deltaTime;
353 
354 	ubyte status;
355 
356 	ubyte data1; // if meta, this is the identifier
357 
358 	//union {
359 		//struct {
360 			ubyte data2;
361 		//}
362 
363 		const(ubyte)[] meta; // iff status == 0xff
364 	//}
365 
366 	invariant () {
367 		assert(status & 0x80);
368 		assert(!(data1 & 0x80));
369 		assert(!(data2 & 0x80));
370 		assert(status == 0xff || meta is null);
371 	}
372 
373 	/// Convenience factories for various meta-events
374 	static MidiEvent Text(string t) { return MidiEvent(0, 0xff, MetaEvent.Text, 0, cast(const(ubyte)[]) t); }
375 	/// ditto
376 	static MidiEvent Copyright(string t) { return MidiEvent(0, 0xff, MetaEvent.Copyright, 0, cast(const(ubyte)[]) t); }
377 	/// ditto
378 	static MidiEvent Name(string t) { return MidiEvent(0, 0xff, MetaEvent.Name, 0, cast(const(ubyte)[]) t); }
379 	/// ditto
380 	static MidiEvent Lyric(string t) { return MidiEvent(0, 0xff, MetaEvent.Lyric, 0, cast(const(ubyte)[]) t); }
381 	/// ditto
382 	static MidiEvent Marker(string t) { return MidiEvent(0, 0xff, MetaEvent.Marker, 0, cast(const(ubyte)[]) t); }
383 	/// ditto
384 	static MidiEvent CuePoint(string t) { return MidiEvent(0, 0xff, MetaEvent.CuePoint, 0, cast(const(ubyte)[]) t); }
385 
386 	///
387 	bool isMeta() const {
388 		return status == 0xff;
389 	}
390 
391 	///
392 	ubyte event() const {
393 		return status >> 4;
394 	}
395 
396 	///
397 	ubyte channel() const {
398 		return status & 0x0f;
399 	}
400 
401 	///
402 	string toString() const {
403 
404 		static string tos(int a) {
405 			char[16] buffer;
406 			auto bufferPos = buffer.length;
407 			do {
408 				buffer[--bufferPos] = a % 10 + '0';
409 				a /= 10;
410 			} while(a);
411 
412 			return buffer[bufferPos .. $].idup;
413 		}
414 
415 		static string toh(ubyte b) {
416 			char[2] buffer;
417 			buffer[0] = (b >> 4) & 0x0f;
418 			if(buffer[0] < 10)
419 				buffer[0] += '0';
420 			else
421 				buffer[0] += 'A' - 10;
422 			buffer[1] = b & 0x0f;
423 			if(buffer[1] < 10)
424 				buffer[1] += '0';
425 			else
426 				buffer[1] += 'A' - 10;
427 
428 			return buffer.idup;
429 		}
430 
431 		string s;
432 		s ~= tos(deltaTime);
433 		s ~= ": ";
434 		s ~= toh(status);
435 		s ~= " ";
436 		s ~= toh(data1);
437 		s ~= " ";
438 		if(isMeta) {
439 			switch(data1) {
440 				case MetaEvent.Text:
441 				case MetaEvent.Copyright:
442 				case MetaEvent.Name:
443 				case MetaEvent.Instrument:
444 				case MetaEvent.Lyric:
445 				case MetaEvent.Marker:
446 				case MetaEvent.CuePoint:
447 				case MetaEvent.PatchName:
448 				case MetaEvent.DeviceName:
449 					s ~= cast(const(char)[]) meta;
450 				break;
451 				case MetaEvent.TimeSignature:
452 					ubyte numerator = meta[0];
453 					ubyte denominator = meta[1];
454 					ubyte clocksPerClick = meta[2];
455 					ubyte notesPerQuarter = meta[3]; // 32nd notes / Q so 8 = quarter note gets the beat
456 
457 					s ~= tos(numerator);
458 					s ~= "/";
459 					s ~= tos(denominator);
460 					s ~= " ";
461 					s ~= tos(clocksPerClick);
462 					s ~= " ";
463 					s ~= tos(notesPerQuarter);
464 				break;
465 				case MetaEvent.KeySignature:
466 					byte offset = meta[0];
467 					ubyte minor = meta[1];
468 
469 					if(offset < 0) {
470 						s ~= "-";
471 						s ~= tos(-cast(int) offset);
472 					} else {
473 						s ~= tos(offset);
474 					}
475 					s ~= minor ? " minor" : " major";
476 				break;
477 				// case MetaEvent.Tempo:
478 					// could process this but idk if it needs to be shown
479 				// break;
480 				case MetaEvent.Proprietary:
481 					foreach(m; meta) {
482 						s ~= toh(m);
483 						s ~= " ";
484 					}
485 				break;
486 				default:
487 					s ~= cast(const(char)[]) meta;
488 			}
489 		} else {
490 			s ~= toh(data2);
491 		}
492 
493 		return s;
494 	}
495 
496 	static MidiEvent fromBuffer(ref MidiReadBuffer buf, ref ubyte runningStatus) {
497 		MidiEvent event;
498 
499 		start_over:
500 
501 		event.deltaTime = buf.readv();
502 
503 		auto nb = buf.read1();
504 
505 		if(nb == 0xff) {
506 			// meta...
507 			event.status = 0xff;
508 			event.data1 = buf.read1(); // the type
509 			int len = buf.readv();
510 			auto meta = new ubyte[](len);
511 			foreach(idx; 0 .. len)
512 				meta[idx] = buf.read1();
513 			event.meta = meta;
514 		} else if(nb >= 0xf0) {
515 			// FIXME I'm just skipping this entirely but there might be value in here
516 			nb = buf.read1();
517 			while(nb < 0xf0)
518 				nb = buf.read1();
519 			goto start_over;
520 		} else if(nb & 0b1000_0000) {
521 			event.status = nb;
522 			runningStatus = nb;
523 			event.data1 = buf.read1();
524 
525 			if(event.event != MIDI_EVENT_CHANNEL_AFTERTOUCH &&
526 				event.event != MIDI_EVENT_PROGRAM_CHANGE)
527 			{
528 				event.data2 = buf.read1();
529 			}
530 		} else {
531 			event.status = runningStatus;
532 			event.data1 = nb;
533 
534 			if(event.event != MIDI_EVENT_CHANNEL_AFTERTOUCH &&
535 				event.event != MIDI_EVENT_PROGRAM_CHANGE)
536 			{
537 				event.data2 = buf.read1();
538 			}
539 		}
540 
541 		return event;
542 	}
543 
544 	void writeToBuffer(ref MidiWriteBuffer buf) const {
545 		buf.writev(deltaTime);
546 		buf.write1(status);
547 		// FIXME: what about other sysex stuff?
548 		if(meta) {
549 			buf.write1(data1);
550 			buf.writev(cast(int) meta.length);
551 			buf.write(meta);
552 		} else {
553 			buf.write1(data1);
554 
555 			if(event != MIDI_EVENT_CHANNEL_AFTERTOUCH &&
556 				event != MIDI_EVENT_PROGRAM_CHANGE)
557 			{
558 				buf.write1(data2);
559 			}
560 		}
561 	}
562 }
563 
564 struct MidiReadBuffer {
565 	ubyte[] bytes;
566 
567 	char[] readChars(int len) {
568 		auto c = bytes[0 .. len];
569 		bytes = bytes[len .. $];
570 		return cast(char[]) c;
571 	}
572 	ubyte[] readBytes(int len) {
573 		auto c = bytes[0 .. len];
574 		bytes = bytes[len .. $];
575 		return c;
576 	}
577 	int read4() {
578 		int i;
579 		foreach(a; 0 .. 4) {
580 			i <<= 8;
581 			i |= bytes[0];
582 			bytes = bytes[1 .. $];
583 		}
584 		return i;
585 	}
586 	ushort read2() {
587 		ushort i;
588 		foreach(a; 0 .. 2) {
589 			i <<= 8;
590 			i |= bytes[0];
591 			bytes = bytes[1 .. $];
592 		}
593 		return i;
594 	}
595 	ubyte read1() {
596 		auto b = bytes[0];
597 		bytes = bytes[1 .. $];
598 		return b;
599 	}
600 	int readv() {
601 		int value = read1();
602 		ubyte c;
603 		if(value & 0x80) {
604 			value &= 0x7f;
605 			do
606 				value = (value << 7) | ((c = read1) & 0x7f);
607 			while(c & 0x80);
608 		}
609 		return value;
610 	}
611 }
612 
613 struct MidiWriteBuffer {
614 	ubyte[] bytes;
615 
616 	void write(const char[] a) {
617 		bytes ~= a;
618 	}
619 
620 	void write(const ubyte[] a) {
621 		bytes ~= a;
622 	}
623 
624 	void write4(int v) {
625 		// big endian
626 		bytes ~= (v >> 24) & 0xff;
627 		bytes ~= (v >> 16) & 0xff;
628 		bytes ~= (v >> 8) & 0xff;
629 		bytes ~= v & 0xff;
630 	}
631 
632 	void write2(ushort v) {
633 		// big endian
634 		bytes ~= v >> 8;
635 		bytes ~= v & 0xff;
636 	}
637 
638 	void write1(ubyte v) {
639 		bytes ~= v;
640 	}
641 
642 	void writev(int v) {
643 		// variable
644 		uint buffer = v & 0x7f;
645 		while((v >>= 7)) {
646 			buffer <<= 8;
647 			buffer |= ((v & 0x7f) | 0x80);
648 		}
649 
650 		while(true) {
651 			bytes ~= buffer & 0xff;
652 			if(buffer & 0x80)
653 				buffer >>= 8;
654 			else
655 				break;
656 		}
657 	}
658 }
659 
660 import core.stdc.stdio;
661 import core.stdc.stdlib;
662 
663 int freq(int note){
664 	import std.math;
665 	float r = note - 69;
666 	r /= 12;
667 	r = pow(2, r);
668 	r*= 440;
669 	return cast(int) r;
670 }
671 
672 enum A =  69; // 440 hz per midi spec
673 enum As = 70;
674 enum B =  71;
675 enum C =  72; // middle C + 1 octave
676 enum Cs = 73;
677 enum D =  74;
678 enum Ds = 75;
679 enum E =  76;
680 enum F =  77;
681 enum Fs = 78;
682 enum G =  79;
683 enum Gs = 80;
684 
685 immutable string[] noteNames = [ // just do note % 12 to index this
686         "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"
687 ];
688 
689 enum MIDI_EVENT_NOTE_OFF =		0x08;
690 enum MIDI_EVENT_NOTE_ON =		0x09;
691 enum MIDI_EVENT_NOTE_AFTERTOUCH =	0x0a;
692 enum MIDI_EVENT_CONTROLLER =		0x0b;
693 enum MIDI_EVENT_PROGRAM_CHANGE =	0x0c;// only one param
694 enum MIDI_EVENT_CHANNEL_AFTERTOUCH =	0x0d;// only one param
695 enum MIDI_EVENT_PITCH_BEND =		0x0e;
696 
697 
698  /+
699  35   Acoustic Bass Drum     59   Ride Cymbal 2
700  36   Bass Drum 1            60   Hi Bongo
701  37   Side Stick             61   Low Bongo
702  38   Acoustic Snare         62   Mute Hi Conga
703  39   Hand Clap              63   Open Hi Conga
704  40   Electric Snare         64   Low Conga
705  41   Low Floor Tom          65   High Timbale
706  42   Closed Hi-Hat          66   Low Timbale
707  43   High Floor Tom         67   High Agogo
708  44   Pedal Hi-Hat           68   Low Agogo
709  45   Low Tom                69   Cabasa
710  46   Open Hi-Hat            70   Maracas
711  47   Low-Mid Tom            71   Short Whistle
712  48   Hi-Mid Tom             72   Long Whistle
713  49   Crash Cymbal 1         73   Short Guiro
714  50   High Tom               74   Long Guiro
715  51   Ride Cymbal 1          75   Claves
716  52   Chinese Cymbal         76   Hi Wood Block
717  53   Ride Bell              77   Low Wood Block
718  54   Tambourine             78   Mute Cuica
719  55   Splash Cymbal          79   Open Cuica
720  56   Cowbell                80   Mute Triangle
721  57   Crash Cymbal 2         81   Open Triangle
722  58   Vibraslap
723  +/
724 
725 static immutable string[] instrumentNames = [
726 "", // 0 is nothing
727 // Piano:
728 "Acoustic Grand Piano",
729 "Bright Acoustic Piano",
730 "Electric Grand Piano",
731 "Honky-tonk Piano",
732 "Electric Piano 1",
733 "Electric Piano 2",
734 "Harpsichord",
735 "Clavinet",
736 
737 // Chromatic Percussion:
738 "Celesta",
739 "Glockenspiel",
740 "Music Box",
741 "Vibraphone",
742 "Marimba",
743 "Xylophone",
744 "Tubular Bells",
745 "Dulcimer",
746 
747 // Organ:
748 "Drawbar Organ",
749 "Percussive Organ",
750 "Rock Organ",
751 "Church Organ",
752 "Reed Organ",
753 "Accordion",
754 "Harmonica",
755 "Tango Accordion",
756 
757 // Guitar:
758 "Acoustic Guitar (nylon)",
759 "Acoustic Guitar (steel)",
760 "Electric Guitar (jazz)",
761 "Electric Guitar (clean)",
762 "Electric Guitar (muted)",
763 "Overdriven Guitar",
764 "Distortion Guitar",
765 "Guitar harmonics",
766 
767 // Bass:
768 "Acoustic Bass",
769 "Electric Bass (finger)",
770 "Electric Bass (pick)",
771 "Fretless Bass",
772 "Slap Bass 1",
773 "Slap Bass 2",
774 "Synth Bass 1",
775 "Synth Bass 2",
776 
777 // Strings:
778 "Violin",
779 "Viola",
780 "Cello",
781 "Contrabass",
782 "Tremolo Strings",
783 "Pizzicato Strings",
784 "Orchestral Harp",
785 "Timpani",
786 
787 // Strings (continued):
788 "String Ensemble 1",
789 "String Ensemble 2",
790 "Synth Strings 1",
791 "Synth Strings 2",
792 "Choir Aahs",
793 "Voice Oohs",
794 "Synth Voice",
795 "Orchestra Hit",
796 
797 // Brass:
798 "Trumpet",
799 "Trombone",
800 "Tuba",
801 "Muted Trumpet",
802 "French Horn",
803 "Brass Section",
804 "Synth Brass 1",
805 "Synth Brass 2",
806 
807 // Reed:
808 "Soprano Sax",
809 "Alto Sax",
810 "Tenor Sax",
811 "Baritone Sax",
812 "Oboe",
813 "English Horn",
814 "Bassoon",
815 "Clarinet",
816 
817 // Pipe:
818 "Piccolo",
819 "Flute",
820 "Recorder",
821 "Pan Flute",
822 "Blown Bottle",
823 "Shakuhachi",
824 "Whistle",
825 "Ocarina",
826 
827 // Synth Lead:
828 "Lead 1 (square)",
829 "Lead 2 (sawtooth)",
830 "Lead 3 (calliope)",
831 "Lead 4 (chiff)",
832 "Lead 5 (charang)",
833 "Lead 6 (voice)",
834 "Lead 7 (fifths)",
835 "Lead 8 (bass + lead)",
836 
837 // Synth Pad:
838 "Pad 1 (new age)",
839 "Pad 2 (warm)",
840 "Pad 3 (polysynth)",
841 "Pad 4 (choir)",
842 "Pad 5 (bowed)",
843 "Pad 6 (metallic)",
844 "Pad 7 (halo)",
845 "Pad 8 (sweep)",
846 
847 // Synth Effects:
848 "FX 1 (rain)",
849 "FX 2 (soundtrack)",
850 "FX 3 (crystal)",
851 "FX 4 (atmosphere)",
852 "FX 5 (brightness)",
853 "FX 6 (goblins)",
854 "FX 7 (echoes)",
855 "FX 8 (sci-fi)",
856 
857 // Ethnic:
858 "Sitar",
859 "Banjo",
860 "Shamisen",
861 "Koto",
862 "Kalimba",
863 "Bag pipe",
864 "Fiddle",
865 "Shanai",
866 
867 // Percussive:
868 "Tinkle Bell",
869 "Agogo",
870 "Steel Drums",
871 "Woodblock",
872 "Taiko Drum",
873 "Melodic Tom",
874 "Synth Drum",
875 
876 // Sound effects:
877 "Reverse Cymbal",
878 "Guitar Fret Noise",
879 "Breath Noise",
880 "Seashore",
881 "Bird Tweet",
882 "Telephone Ring",
883 "Helicopter",
884 "Applause",
885 "Gunshot"
886 ];
887 
888 version(MidiDemo) {
889 
890 
891 enum SKIP_MAX = 3000; // allow no more than about 3 seconds of silence
892 			 // if the -k option is set
893 
894 // Potential FIXME: it doesn't support more than 128 tracks.
895 
896 void awesome(void* midiptr, int note, int wait) {
897 	printf("%d %d ", wait, note);
898 	fflush(stdout);
899 }
900 
901 // FIXME: add support for displaying lyrics
902 extern(C) int main(int argc, char** argv){
903 
904 	for(a = 1; a < argc; a++){
905 		if(argv[a][0] == '-')
906 		switch(argv[a][1]){
907 			case 't':
908 				for(b = 0; b< 128; b++)
909 					playtracks[b] = 0;
910 				num = 0;
911 				b = 0;
912 				a++;
913 				if(a == argc){
914 					printf("%s: option %s requires an argument\n", argv[0], argv[a-1]);
915 					return 1;
916 				}
917 				for(b = 0; argv[a][b]; b++){
918 					if(argv[a][b] == ','){
919 						playtracks[num] = 1;
920 						num = 0;
921 						continue;
922 					}
923 					num *= 10;
924 					num += argv[a][b] - '0';
925 				}
926 				playtracks[num] = 1;
927 			break;
928 			case 's':
929 				a++;
930 				if(a == argc){
931 					printf("%s: option %s requires an argument\n", argv[0], argv[a-1]);
932 					return 1;
933 				}
934 				tempoMultiplier = atof(argv[a]);
935 			break;
936 			case 'i': // FIXME
937 				displayinfo = 1;
938 				// tracks, guesstimated length
939 			break;
940 			// -o loop to from
941 			// -b begin at
942 			// -e end at
943 			case 'l':
944 				tracing = 1;
945 			break;
946 			case 'n':
947 				play = 0;
948 			break;
949 			case 'k':
950 				skip = 1;
951 			break;
952 			case 'c':
953 				channelMask = 0;
954 				// channels
955 				num = 0;
956 				b = 0;
957 				a++;
958 				if(a == argc){
959 					printf("%s: option %s requires an argument\n", argv[0], argv[a-1]);
960 					return 1;
961 				}
962 				for(b = 0; argv[a][b]; b++){
963 					if(argv[a][b] == ','){
964 						channelMask |= (1 << num);
965 						num = 0;
966 						continue;
967 					}
968 					num *= 10;
969 					num += argv[a][b] - '0';
970 				}
971 					channelMask |= (1 << num);
972 			break;
973 			case 'r':
974 				a++;
975 				if(a == argc){
976 					printf("%s: option %s requires an argument\n", argv[0], argv[a-1]);
977 					return 1;
978 				}
979 				transpose = atoi(argv[a]);
980 			break;
981 			case 'v':
982 				verbose = 1;
983 			break;
984 			case 'h':
985 				printf("Usage: %s [options...] file\n", argv[0]);
986 				printf("  Options:\n");
987 				printf("  -t comma separated list of tracks to play (default: all)\n");
988 				printf("  -s tempo (speed) multiplier (default: 1.0)\n");
989 				printf("  -i file info (track list)\n");
990 				printf("  -l list notes as they are played (in the format totablature expects)\n");
991 				printf("  -n no sound; don't actually play the midi\n");
992 				printf("  -c comma separated list of channels to play (default: all)\n");
993 				printf("  -r transpose notes by amount (default: 0)\n");
994 				printf("  -k skip long sections of silence (good for playing single tracks)\n");
995 
996 				printf("  -v verbose; list all events except note on / note off\n");
997 				printf("  -h shows this help screen\n");
998 
999 				return 0;
1000 			break;
1001 			default:
1002 				printf("%s: unknown command line option: %s\n", argv[0], argv[1]);
1003 				return 1;
1004 		}
1005 		else
1006 			filename = argv[a];
1007 	}
1008 
1009 	if(filename == null){
1010 		printf("%s: no file given. Try %s -h for help.\n", argv[0], argv[0]);
1011 		return 1;
1012 	}
1013 
1014 	loadMidi(&mid, filename);
1015 	if(mid == null){
1016 		printf("%s: unable to read file %s\n", argv[0], filename);
1017 		return 1;
1018 	}
1019 
1020 	if(displayinfo){
1021 		int len = getMidiLength(mid);
1022 		printf("File: %s\n", filename);
1023 		printf("Ticks per quarter note: %d\n", mid.speed);
1024 		printf("Initial tempo: %d\n", getMidiTempo(mid));
1025 		printf("Length: %d:%d\n", len / 60, len%60);
1026 		printf("Tracks:\n");
1027 		for(a = 0; a < mid.numTracks; a++){
1028 			c[0] = getTrackNameChunk(mid, a);
1029 			if(c[0] != null){
1030 				printf("%d: ", a);
1031 				for(b = 0; b < c[0].length; b++)
1032 					fputc(c[0].data[b], stdout);
1033 				printf("\n");
1034 			}
1035 		}
1036 
1037 		freeMidi(&mid);
1038 		return 0;
1039 	}
1040 
1041 	return 0;
1042 }
1043 }
Suggestion Box / Bug Report