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 }