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 = ¬eOnInfo[(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 = ¬eOnInfo[(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 }