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 330 { 331 bool copyPerformed = false; 332 333 // some midis do weird things 334 // see: https://github.com/adamdruppe/arsd/issues/508 335 // to correct: 336 // first need to segment by the deltaTime - a non-zero, then all zeros that follow it. 337 // then inside the same timestamp segments, put the note off (or note on with data2 == 0) first in the stream 338 // make sure the first item has the non-zero deltaTime for the segment, then all others have 0. 339 340 // returns true if you need to copy then try again 341 bool sortSegment(MidiEvent[] events) { 342 if(events.length <= 1) 343 return false; 344 345 bool hasNoteOn = false; 346 bool needsChange = false; 347 foreach(event; events) { 348 if(hasNoteOn) { 349 if(event.isNoteOff) { 350 needsChange = true; 351 break; 352 } 353 } else if(event.isNoteOn) { 354 hasNoteOn = true; 355 } 356 } 357 358 if(!needsChange) 359 return false; 360 361 if(!copyPerformed) { 362 // so we don't modify the original file unnecessarily... 363 return true; 364 } 365 366 auto dt = events[0].deltaTime; 367 368 MidiEvent[8] staticBuffer; 369 MidiEvent[] buffer; 370 if(events.length < staticBuffer.length) 371 buffer = staticBuffer[0 .. events.length]; 372 else 373 buffer = new MidiEvent[](events.length); 374 375 size_t bufferPos; 376 377 // first pass, keep the note offs 378 foreach(event; events) { 379 if(event.isNoteOff) 380 buffer[bufferPos++] = event; 381 } 382 383 // second pass, keep the rest 384 foreach(event; events) { 385 if(!event.isNoteOff) 386 buffer[bufferPos++] = event; 387 } 388 389 assert(bufferPos == events.length); 390 events[] = buffer[]; 391 392 foreach(ref e; events) 393 e.deltaTime = 0; 394 events[0].deltaTime = dt; 395 396 return false; 397 } 398 399 size_t first = 0; 400 foreach(sortIndex, f; tp.remaining) { 401 if(f.deltaTime != 0) { 402 if(sortSegment(tp.remaining[first .. sortIndex])) { 403 // if it returns true, it needs to modify the array 404 // but it doesn't change the iteration result, just we need to send it the copy after making it 405 tp.remaining = tp.remaining.dup; 406 copyPerformed = true; 407 sortSegment(tp.remaining[first .. sortIndex]); 408 } 409 first = sortIndex; 410 } 411 } 412 413 if(sortSegment(tp.remaining[first .. $])) { 414 tp.remaining = tp.remaining.dup; 415 copyPerformed = true; 416 sortSegment(tp.remaining[first .. $]); 417 } 418 } 419 420 tp.track = file.tracks[idx]; 421 } 422 423 this.currentTrack = -1; 424 this.tempo = 500000; // microseconds per quarter note 425 popFront(); 426 } 427 428 //@nogc: 429 430 int midiClock; 431 432 void popFront() { 433 done = true; 434 for(auto c = currentTrack + 1; c < trackPositions.length; c++) { 435 auto tp = trackPositions[c]; 436 437 if(tp.remaining.length && tp.remaining[0].deltaTime == tp.clock) { 438 auto f = tp.remaining[0]; 439 trackPositions[c].remaining = tp.remaining[1 .. $]; 440 trackPositions[c].clock = 0; 441 if(tp.remaining.length == 0 || tp.remaining[0].deltaTime > 0) { 442 currentTrack += 1; 443 } 444 445 // import arsd.core; debug writeln(c, " ", f); 446 447 pending = PlayStreamEvent(0.seconds, f, file, tp.track, midiClock); 448 processPending(); 449 done = false; 450 return; 451 } 452 } 453 454 // if nothing happened there, time to advance the clock 455 int minWait = int.max; 456 int minWaitTrack = -1; 457 foreach(idx, track; trackPositions) { 458 if(track.remaining.length) { 459 auto dt = track.remaining[0].deltaTime - track.clock; 460 if(dt < minWait) { 461 minWait = dt; 462 minWaitTrack = cast(int) idx; 463 } 464 } 465 } 466 467 if(minWaitTrack == -1) { 468 done = true; 469 return; 470 } 471 472 foreach(ref tp; trackPositions) { 473 tp.clock += minWait; 474 } 475 476 done = false; 477 478 // file.timing, if high bit clear, is ticks per quarter note 479 // if high bit set... idk it is different. 480 // 481 // then the temp is microseconds per quarter note. 482 483 auto time = (cast(long) minWait * tempo / file.timing).usecs; 484 midiClock += minWait; 485 486 pending = PlayStreamEvent(time, trackPositions[minWaitTrack].remaining[0], file, trackPositions[minWaitTrack].track, midiClock); 487 processPending(); 488 trackPositions[minWaitTrack].remaining = trackPositions[minWaitTrack].remaining[1 .. $]; 489 trackPositions[minWaitTrack].clock = 0; 490 currentTrack = minWaitTrack; 491 492 return; 493 } 494 495 private struct TrackPosition { 496 MidiEvent[] remaining; 497 int clock; 498 MidiTrack track; 499 } 500 private TrackPosition[] trackPositions; 501 private int currentTrack; 502 503 private void processPending() { 504 if(pending.event.status == 0xff && pending.event.data1 == MetaEvent.Tempo) { 505 this.tempo = 0; 506 foreach(i; pending.event.meta) { 507 this.tempo <<= 8; 508 this.tempo |= i; 509 } 510 } 511 } 512 513 @property 514 PlayStreamEvent front() { 515 return pending; 516 } 517 518 private uint tempo; 519 private PlayStreamEvent pending; 520 private bool done; 521 522 @property 523 bool empty() { 524 return done; 525 } 526 527 } 528 529 class MidiTrack { 530 ubyte[] toBytes() { 531 MidiWriteBuffer buf; 532 foreach(event; events) 533 event.writeToBuffer(buf); 534 535 MidiEvent end; 536 end.status = 0xff; 537 end.data1 = 0x2f; 538 end.meta = null; 539 540 end.writeToBuffer(buf); 541 542 return buf.bytes; 543 } 544 545 void loadFromBuffer(ref MidiReadBuffer buf) { 546 if(buf.readChars(4) != "MTrk") 547 throw new Exception("wtf no track header"); 548 549 auto trackLength = buf.read4(); 550 auto begin = buf.bytes.length; 551 552 ubyte runningStatus; 553 554 while(buf.bytes.length) { 555 MidiEvent newEvent = MidiEvent.fromBuffer(buf, runningStatus); 556 557 if(newEvent.isMeta && newEvent.data1 == MetaEvent.Name) 558 name_ = cast(string) newEvent.meta.idup; 559 560 if(newEvent.status == 0xff && newEvent.data1 == MetaEvent.EndOfTrack) { 561 break; 562 } 563 events ~= newEvent; 564 } 565 //assert(begin - trackLength == buf.bytes.length); 566 } 567 568 /++ 569 All the midi events found in the track. 570 +/ 571 MidiEvent[] events; 572 /++ 573 The name of the track, as found from metadata at load time. 574 575 This may change to scan events to see updates without the cache in the future. 576 +/ 577 @property string name() { 578 return name_; 579 } 580 581 private string name_; 582 583 /++ 584 This field is not used or stored in a midi file; it is just 585 a place to store some state in your player. 586 587 I use it to keep flags like if the track is currently enabled. 588 +/ 589 int customPlayerInfo; 590 591 override string toString() const { 592 string s; 593 foreach(event; events) 594 s ~= event.toString ~ "\n"; 595 return s; 596 } 597 } 598 599 enum MetaEvent { 600 SequenceNumber = 0, 601 // these take a text param 602 Text = 1, 603 Copyright = 2, 604 Name = 3, 605 Instrument = 4, 606 Lyric = 5, 607 Marker = 6, 608 CuePoint = 7, 609 PatchName = 8, 610 DeviceName = 9, 611 612 // no param 613 EndOfTrack = 0x2f, 614 615 // different ones 616 Tempo = 0x51, // 3 bytes form big-endian micro-seconds per quarter note. 120 BPM default. 617 SMPTEOffset = 0x54, // 5 bytes. I don't get this one.... 618 TimeSignature = 0x58, // 4 bytes: numerator, denominator, clocks per click, 32nd notes per quarter note. (8 == quarter note gets the beat) 619 KeySignature = 0x59, // 2 bytes: first byte is signed offset from C in semitones, second byte is 0 for major, 1 for minor 620 621 // arbitrary length custom param 622 Proprietary = 0x7f, 623 624 } 625 626 struct MidiEvent { 627 int deltaTime; 628 629 ubyte status; 630 631 ubyte data1; // if meta, this is the identifier 632 633 //union { 634 //struct { 635 ubyte data2; 636 //} 637 638 const(ubyte)[] meta; // iff status == 0xff 639 //} 640 641 invariant () { 642 assert(status & 0x80); 643 assert(!(data1 & 0x80)); 644 assert(!(data2 & 0x80)); 645 assert(status == 0xff || meta is null); 646 } 647 648 /// Convenience factories for various meta-events 649 static MidiEvent Text(string t) { return MidiEvent(0, 0xff, MetaEvent.Text, 0, cast(const(ubyte)[]) t); } 650 /// ditto 651 static MidiEvent Copyright(string t) { return MidiEvent(0, 0xff, MetaEvent.Copyright, 0, cast(const(ubyte)[]) t); } 652 /// ditto 653 static MidiEvent Name(string t) { return MidiEvent(0, 0xff, MetaEvent.Name, 0, cast(const(ubyte)[]) t); } 654 /// ditto 655 static MidiEvent Lyric(string t) { return MidiEvent(0, 0xff, MetaEvent.Lyric, 0, cast(const(ubyte)[]) t); } 656 /// ditto 657 static MidiEvent Marker(string t) { return MidiEvent(0, 0xff, MetaEvent.Marker, 0, cast(const(ubyte)[]) t); } 658 /// ditto 659 static MidiEvent CuePoint(string t) { return MidiEvent(0, 0xff, MetaEvent.CuePoint, 0, cast(const(ubyte)[]) t); } 660 661 /++ 662 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. 663 664 History: 665 Added January 2, 2022 (dub v10.5) 666 +/ 667 static MidiEvent NoteOn(int channel, int note, int velocity) { return MidiEvent(0, (MIDI_EVENT_NOTE_ON << 4) | (channel & 0x0f), note & 0x7f, velocity & 0x7f); } 668 /// ditto 669 static MidiEvent NoteOff(int channel, int note, int velocity) { return MidiEvent(0, (MIDI_EVENT_NOTE_OFF << 4) | (channel & 0x0f), note & 0x7f, velocity & 0x7f); } 670 671 /+ 672 // FIXME: this is actually a relatively complicated one i should fix, it combines bits... 8192 == 0. 673 // This is a bit of a magical function, it takes a signed bend between 0 and 81 674 static MidiEvent PitchBend(int channel, int bend) { 675 return MidiEvent(0, (MIDI_EVENT_PITCH_BEND << 4) | (channel & 0x0f), bend & 0x7f, bend & 0x7f); 676 } 677 +/ 678 // this overload ok, it is what the thing actually tells. coarse == 64 means we're at neutral. 679 /// ditto 680 static MidiEvent PitchBend(int channel, int fine, int coarse) { return MidiEvent(0, (MIDI_EVENT_PITCH_BEND << 4) | (channel & 0x0f), fine & 0x7f, coarse & 0x7f); } 681 682 /// ditto 683 static MidiEvent NoteAftertouch(int channel, int note, int velocity) { return MidiEvent(0, (MIDI_EVENT_NOTE_AFTERTOUCH << 4) | (channel & 0x0f), note & 0x7f, velocity & 0x7f); } 684 // 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. 685 /// ditto 686 static MidiEvent Controller(int channel, int controller, int value) { return MidiEvent(0, (MIDI_EVENT_CONTROLLER << 4) | (channel & 0x0f), controller & 0x7f, value & 0x7f); } 687 688 // the two byte ones 689 /// ditto 690 static MidiEvent ProgramChange(int channel, int program) { return MidiEvent(0, (MIDI_EVENT_PROGRAM_CHANGE << 4) | (channel & 0x0f), program & 0x7f); } 691 /// ditto 692 static MidiEvent ChannelAftertouch(int channel, int param) { return MidiEvent(0, (MIDI_EVENT_CHANNEL_AFTERTOUCH << 4) | (channel & 0x0f), param & 0x7f); } 693 694 /// 695 bool isMeta() const { 696 return status == 0xff; 697 } 698 699 /// 700 ubyte event() const { 701 return status >> 4; 702 } 703 704 /// 705 ubyte channel() const { 706 return status & 0x0f; 707 } 708 709 /++ 710 Returns true if it is either note off or note on with zero velocity, both of which should silence the note. 711 712 History: 713 Added July 20, 2025 714 +/ 715 bool isNoteOff() const { 716 // data1 is the note fyi 717 return this.event == MIDI_EVENT_NOTE_OFF || (this.event == MIDI_EVENT_NOTE_ON && this.data2 == 0); 718 } 719 720 /++ 721 Returns true if it is a note on with non-zero velocity, which should sound a note. 722 723 History: 724 Added July 20, 2025 725 726 +/ 727 bool isNoteOn() const { 728 return this.event == MIDI_EVENT_NOTE_ON && this.data2 != 0; 729 } 730 731 /// 732 string toString() const { 733 734 static string tos(int a) { 735 char[16] buffer; 736 auto bufferPos = buffer.length; 737 do { 738 buffer[--bufferPos] = a % 10 + '0'; 739 a /= 10; 740 } while(a); 741 742 return buffer[bufferPos .. $].idup; 743 } 744 745 static string toh(ubyte b) { 746 char[2] buffer; 747 buffer[0] = (b >> 4) & 0x0f; 748 if(buffer[0] < 10) 749 buffer[0] += '0'; 750 else 751 buffer[0] += 'A' - 10; 752 buffer[1] = b & 0x0f; 753 if(buffer[1] < 10) 754 buffer[1] += '0'; 755 else 756 buffer[1] += 'A' - 10; 757 758 return buffer.idup; 759 } 760 761 string s; 762 s ~= tos(deltaTime); 763 s ~= ": "; 764 s ~= toh(status); 765 s ~= " "; 766 s ~= toh(data1); 767 s ~= " "; 768 769 if(isMeta) { 770 switch(data1) { 771 case MetaEvent.Text: 772 case MetaEvent.Copyright: 773 case MetaEvent.Name: 774 case MetaEvent.Instrument: 775 case MetaEvent.Lyric: 776 case MetaEvent.Marker: 777 case MetaEvent.CuePoint: 778 case MetaEvent.PatchName: 779 case MetaEvent.DeviceName: 780 s ~= cast(const(char)[]) meta; 781 break; 782 case MetaEvent.TimeSignature: 783 ubyte numerator = meta[0]; 784 ubyte denominator = meta[1]; 785 ubyte clocksPerClick = meta[2]; 786 ubyte notesPerQuarter = meta[3]; // 32nd notes / Q so 8 = quarter note gets the beat 787 788 s ~= tos(numerator); 789 s ~= "/"; 790 s ~= tos(denominator); 791 s ~= " "; 792 s ~= tos(clocksPerClick); 793 s ~= " "; 794 s ~= tos(notesPerQuarter); 795 break; 796 case MetaEvent.KeySignature: 797 byte offset = meta[0]; 798 ubyte minor = meta[1]; 799 800 if(offset < 0) { 801 s ~= "-"; 802 s ~= tos(-cast(int) offset); 803 } else { 804 s ~= tos(offset); 805 } 806 s ~= minor ? " minor" : " major"; 807 break; 808 // case MetaEvent.Tempo: 809 // could process this but idk if it needs to be shown 810 // break; 811 case MetaEvent.Proprietary: 812 foreach(m; meta) { 813 s ~= toh(m); 814 s ~= " "; 815 } 816 break; 817 default: 818 s ~= cast(const(char)[]) meta; 819 } 820 } else { 821 s ~= toh(data2); 822 823 s ~= " "; 824 s ~= tos(channel); 825 s ~= " "; 826 switch(event) { 827 case MIDI_EVENT_NOTE_OFF: s ~= "NOTE_OFF"; break; 828 case MIDI_EVENT_NOTE_ON: s ~= data2 ? "NOTE_ON" : "NOTE_ON_ZERO"; break; 829 case MIDI_EVENT_NOTE_AFTERTOUCH: s ~= "NOTE_AFTERTOUCH"; break; 830 case MIDI_EVENT_CONTROLLER: s ~= "CONTROLLER"; break; 831 case MIDI_EVENT_PROGRAM_CHANGE: s ~= "PROGRAM_CHANGE"; break; 832 case MIDI_EVENT_CHANNEL_AFTERTOUCH: s ~= "CHANNEL_AFTERTOUCH"; break; 833 case MIDI_EVENT_PITCH_BEND: s ~= "PITCH_BEND"; break; 834 default: 835 } 836 } 837 838 return s; 839 } 840 841 static MidiEvent fromBuffer(ref MidiReadBuffer buf, ref ubyte runningStatus) { 842 MidiEvent event; 843 844 start_over: 845 846 event.deltaTime = buf.readv(); 847 848 auto nb = buf.read1(); 849 850 if(nb == 0xff) { 851 // meta... 852 event.status = 0xff; 853 event.data1 = buf.read1(); // the type 854 int len = buf.readv(); 855 auto meta = new ubyte[](len); 856 foreach(idx; 0 .. len) 857 meta[idx] = buf.read1(); 858 event.meta = meta; 859 } else if(nb >= 0xf0) { 860 // FIXME I'm just skipping this entirely but there might be value in here 861 nb = buf.read1(); 862 while(nb < 0xf0) 863 nb = buf.read1(); 864 goto start_over; 865 } else if(nb & 0b1000_0000) { 866 event.status = nb; 867 runningStatus = nb; 868 event.data1 = buf.read1(); 869 870 if(event.event != MIDI_EVENT_CHANNEL_AFTERTOUCH && 871 event.event != MIDI_EVENT_PROGRAM_CHANGE) 872 { 873 event.data2 = buf.read1(); 874 } 875 } else { 876 event.status = runningStatus; 877 event.data1 = nb; 878 879 if(event.event != MIDI_EVENT_CHANNEL_AFTERTOUCH && 880 event.event != MIDI_EVENT_PROGRAM_CHANGE) 881 { 882 event.data2 = buf.read1(); 883 } 884 } 885 886 return event; 887 } 888 889 void writeToBuffer(ref MidiWriteBuffer buf) const { 890 buf.writev(deltaTime); 891 buf.write1(status); 892 // FIXME: what about other sysex stuff? 893 if(meta) { 894 buf.write1(data1); 895 buf.writev(cast(int) meta.length); 896 buf.write(meta); 897 } else { 898 buf.write1(data1); 899 900 if(event != MIDI_EVENT_CHANNEL_AFTERTOUCH && 901 event != MIDI_EVENT_PROGRAM_CHANGE) 902 { 903 buf.write1(data2); 904 } 905 } 906 } 907 } 908 909 struct MidiReadBuffer { 910 ubyte[] bytes; 911 912 char[] readChars(int len) { 913 auto c = bytes[0 .. len]; 914 bytes = bytes[len .. $]; 915 return cast(char[]) c; 916 } 917 ubyte[] readBytes(int len) { 918 auto c = bytes[0 .. len]; 919 bytes = bytes[len .. $]; 920 return c; 921 } 922 int read4() { 923 int i; 924 foreach(a; 0 .. 4) { 925 i <<= 8; 926 i |= bytes[0]; 927 bytes = bytes[1 .. $]; 928 } 929 return i; 930 } 931 ushort read2() { 932 ushort i; 933 foreach(a; 0 .. 2) { 934 i <<= 8; 935 i |= bytes[0]; 936 bytes = bytes[1 .. $]; 937 } 938 return i; 939 } 940 ubyte read1() { 941 auto b = bytes[0]; 942 bytes = bytes[1 .. $]; 943 return b; 944 } 945 int readv() { 946 int value = read1(); 947 ubyte c; 948 if(value & 0x80) { 949 value &= 0x7f; 950 do 951 value = (value << 7) | ((c = read1) & 0x7f); 952 while(c & 0x80); 953 } 954 return value; 955 } 956 } 957 958 struct MidiWriteBuffer { 959 ubyte[] bytes; 960 961 void write(const char[] a) { 962 bytes ~= a; 963 } 964 965 void write(const ubyte[] a) { 966 bytes ~= a; 967 } 968 969 void write4(int v) { 970 // big endian 971 bytes ~= (v >> 24) & 0xff; 972 bytes ~= (v >> 16) & 0xff; 973 bytes ~= (v >> 8) & 0xff; 974 bytes ~= v & 0xff; 975 } 976 977 void write2(ushort v) { 978 // big endian 979 bytes ~= v >> 8; 980 bytes ~= v & 0xff; 981 } 982 983 void write1(ubyte v) { 984 bytes ~= v; 985 } 986 987 void writev(int v) { 988 // variable 989 uint buffer = v & 0x7f; 990 while((v >>= 7)) { 991 buffer <<= 8; 992 buffer |= ((v & 0x7f) | 0x80); 993 } 994 995 while(true) { 996 bytes ~= buffer & 0xff; 997 if(buffer & 0x80) 998 buffer >>= 8; 999 else 1000 break; 1001 } 1002 } 1003 } 1004 1005 import core.stdc.stdio; 1006 import core.stdc.stdlib; 1007 1008 int freq(int note){ 1009 import std.math; 1010 float r = note - 69; 1011 r /= 12; 1012 r = pow(2, r); 1013 r*= 440; 1014 return cast(int) r; 1015 } 1016 1017 enum A = 69; // 440 hz per midi spec 1018 enum As = 70; 1019 enum B = 71; 1020 enum C = 72; // middle C + 1 octave 1021 enum Cs = 73; 1022 enum D = 74; 1023 enum Ds = 75; 1024 enum E = 76; 1025 enum F = 77; 1026 enum Fs = 78; 1027 enum G = 79; 1028 enum Gs = 80; 1029 1030 immutable string[] noteNames = [ // just do note % 12 to index this 1031 "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" 1032 ]; 1033 1034 enum MIDI_EVENT_NOTE_OFF = 0x08; 1035 enum MIDI_EVENT_NOTE_ON = 0x09; 1036 enum MIDI_EVENT_NOTE_AFTERTOUCH = 0x0a; 1037 enum MIDI_EVENT_CONTROLLER = 0x0b; 1038 enum MIDI_EVENT_PROGRAM_CHANGE = 0x0c;// only one param 1039 enum MIDI_EVENT_CHANNEL_AFTERTOUCH = 0x0d;// only one param 1040 enum MIDI_EVENT_PITCH_BEND = 0x0e; 1041 1042 1043 /+ 1044 35 Acoustic Bass Drum 59 Ride Cymbal 2 1045 36 Bass Drum 1 60 Hi Bongo 1046 37 Side Stick 61 Low Bongo 1047 38 Acoustic Snare 62 Mute Hi Conga 1048 39 Hand Clap 63 Open Hi Conga 1049 40 Electric Snare 64 Low Conga 1050 41 Low Floor Tom 65 High Timbale 1051 42 Closed Hi-Hat 66 Low Timbale 1052 43 High Floor Tom 67 High Agogo 1053 44 Pedal Hi-Hat 68 Low Agogo 1054 45 Low Tom 69 Cabasa 1055 46 Open Hi-Hat 70 Maracas 1056 47 Low-Mid Tom 71 Short Whistle 1057 48 Hi-Mid Tom 72 Long Whistle 1058 49 Crash Cymbal 1 73 Short Guiro 1059 50 High Tom 74 Long Guiro 1060 51 Ride Cymbal 1 75 Claves 1061 52 Chinese Cymbal 76 Hi Wood Block 1062 53 Ride Bell 77 Low Wood Block 1063 54 Tambourine 78 Mute Cuica 1064 55 Splash Cymbal 79 Open Cuica 1065 56 Cowbell 80 Mute Triangle 1066 57 Crash Cymbal 2 81 Open Triangle 1067 58 Vibraslap 1068 +/ 1069 1070 static immutable string[] instrumentNames = [ 1071 "", // 0 is nothing 1072 // Piano: 1073 "Acoustic Grand Piano", 1074 "Bright Acoustic Piano", 1075 "Electric Grand Piano", 1076 "Honky-tonk Piano", 1077 "Electric Piano 1", 1078 "Electric Piano 2", 1079 "Harpsichord", 1080 "Clavinet", 1081 1082 // Chromatic Percussion: 1083 "Celesta", 1084 "Glockenspiel", 1085 "Music Box", 1086 "Vibraphone", 1087 "Marimba", 1088 "Xylophone", 1089 "Tubular Bells", 1090 "Dulcimer", 1091 1092 // Organ: 1093 "Drawbar Organ", 1094 "Percussive Organ", 1095 "Rock Organ", 1096 "Church Organ", 1097 "Reed Organ", 1098 "Accordion", 1099 "Harmonica", 1100 "Tango Accordion", 1101 1102 // Guitar: 1103 "Acoustic Guitar (nylon)", 1104 "Acoustic Guitar (steel)", 1105 "Electric Guitar (jazz)", 1106 "Electric Guitar (clean)", 1107 "Electric Guitar (muted)", 1108 "Overdriven Guitar", 1109 "Distortion Guitar", 1110 "Guitar harmonics", 1111 1112 // Bass: 1113 "Acoustic Bass", 1114 "Electric Bass (finger)", 1115 "Electric Bass (pick)", 1116 "Fretless Bass", 1117 "Slap Bass 1", 1118 "Slap Bass 2", 1119 "Synth Bass 1", 1120 "Synth Bass 2", 1121 1122 // Strings: 1123 "Violin", 1124 "Viola", 1125 "Cello", 1126 "Contrabass", 1127 "Tremolo Strings", 1128 "Pizzicato Strings", 1129 "Orchestral Harp", 1130 "Timpani", 1131 1132 // Strings (continued): 1133 "String Ensemble 1", 1134 "String Ensemble 2", 1135 "Synth Strings 1", 1136 "Synth Strings 2", 1137 "Choir Aahs", 1138 "Voice Oohs", 1139 "Synth Voice", 1140 "Orchestra Hit", 1141 1142 // Brass: 1143 "Trumpet", 1144 "Trombone", 1145 "Tuba", 1146 "Muted Trumpet", 1147 "French Horn", 1148 "Brass Section", 1149 "Synth Brass 1", 1150 "Synth Brass 2", 1151 1152 // Reed: 1153 "Soprano Sax", 1154 "Alto Sax", 1155 "Tenor Sax", 1156 "Baritone Sax", 1157 "Oboe", 1158 "English Horn", 1159 "Bassoon", 1160 "Clarinet", 1161 1162 // Pipe: 1163 "Piccolo", 1164 "Flute", 1165 "Recorder", 1166 "Pan Flute", 1167 "Blown Bottle", 1168 "Shakuhachi", 1169 "Whistle", 1170 "Ocarina", 1171 1172 // Synth Lead: 1173 "Lead 1 (square)", 1174 "Lead 2 (sawtooth)", 1175 "Lead 3 (calliope)", 1176 "Lead 4 (chiff)", 1177 "Lead 5 (charang)", 1178 "Lead 6 (voice)", 1179 "Lead 7 (fifths)", 1180 "Lead 8 (bass + lead)", 1181 1182 // Synth Pad: 1183 "Pad 1 (new age)", 1184 "Pad 2 (warm)", 1185 "Pad 3 (polysynth)", 1186 "Pad 4 (choir)", 1187 "Pad 5 (bowed)", 1188 "Pad 6 (metallic)", 1189 "Pad 7 (halo)", 1190 "Pad 8 (sweep)", 1191 1192 // Synth Effects: 1193 "FX 1 (rain)", 1194 "FX 2 (soundtrack)", 1195 "FX 3 (crystal)", 1196 "FX 4 (atmosphere)", 1197 "FX 5 (brightness)", 1198 "FX 6 (goblins)", 1199 "FX 7 (echoes)", 1200 "FX 8 (sci-fi)", 1201 1202 // Ethnic: 1203 "Sitar", 1204 "Banjo", 1205 "Shamisen", 1206 "Koto", 1207 "Kalimba", 1208 "Bag pipe", 1209 "Fiddle", 1210 "Shanai", 1211 1212 // Percussive: 1213 "Tinkle Bell", 1214 "Agogo", 1215 "Steel Drums", 1216 "Woodblock", 1217 "Taiko Drum", 1218 "Melodic Tom", 1219 "Synth Drum", 1220 1221 // Sound effects: 1222 "Reverse Cymbal", 1223 "Guitar Fret Noise", 1224 "Breath Noise", 1225 "Seashore", 1226 "Bird Tweet", 1227 "Telephone Ring", 1228 "Helicopter", 1229 "Applause", 1230 "Gunshot" 1231 ]; 1232 1233 version(MidiDemo) { 1234 1235 1236 enum SKIP_MAX = 3000; // allow no more than about 3 seconds of silence 1237 // if the -k option is set 1238 1239 // Potential FIXME: it doesn't support more than 128 tracks. 1240 1241 void awesome(void* midiptr, int note, int wait) { 1242 printf("%d %d ", wait, note); 1243 fflush(stdout); 1244 } 1245 1246 // FIXME: add support for displaying lyrics 1247 extern(C) int main(int argc, char** argv){ 1248 1249 for(a = 1; a < argc; a++){ 1250 if(argv[a][0] == '-') 1251 switch(argv[a][1]){ 1252 case 't': 1253 for(b = 0; b< 128; b++) 1254 playtracks[b] = 0; 1255 num = 0; 1256 b = 0; 1257 a++; 1258 if(a == argc){ 1259 printf("%s: option %s requires an argument\n", argv[0], argv[a-1]); 1260 return 1; 1261 } 1262 for(b = 0; argv[a][b]; b++){ 1263 if(argv[a][b] == ','){ 1264 playtracks[num] = 1; 1265 num = 0; 1266 continue; 1267 } 1268 num *= 10; 1269 num += argv[a][b] - '0'; 1270 } 1271 playtracks[num] = 1; 1272 break; 1273 case 's': 1274 a++; 1275 if(a == argc){ 1276 printf("%s: option %s requires an argument\n", argv[0], argv[a-1]); 1277 return 1; 1278 } 1279 tempoMultiplier = atof(argv[a]); 1280 break; 1281 case 'i': // FIXME 1282 displayinfo = 1; 1283 // tracks, guesstimated length 1284 break; 1285 // -o loop to from 1286 // -b begin at 1287 // -e end at 1288 case 'l': 1289 tracing = 1; 1290 break; 1291 case 'n': 1292 play = 0; 1293 break; 1294 case 'k': 1295 skip = 1; 1296 break; 1297 case 'c': 1298 channelMask = 0; 1299 // channels 1300 num = 0; 1301 b = 0; 1302 a++; 1303 if(a == argc){ 1304 printf("%s: option %s requires an argument\n", argv[0], argv[a-1]); 1305 return 1; 1306 } 1307 for(b = 0; argv[a][b]; b++){ 1308 if(argv[a][b] == ','){ 1309 channelMask |= (1 << num); 1310 num = 0; 1311 continue; 1312 } 1313 num *= 10; 1314 num += argv[a][b] - '0'; 1315 } 1316 channelMask |= (1 << num); 1317 break; 1318 case 'r': 1319 a++; 1320 if(a == argc){ 1321 printf("%s: option %s requires an argument\n", argv[0], argv[a-1]); 1322 return 1; 1323 } 1324 transpose = atoi(argv[a]); 1325 break; 1326 case 'v': 1327 verbose = 1; 1328 break; 1329 case 'h': 1330 printf("Usage: %s [options...] file\n", argv[0]); 1331 printf(" Options:\n"); 1332 printf(" -t comma separated list of tracks to play (default: all)\n"); 1333 printf(" -s tempo (speed) multiplier (default: 1.0)\n"); 1334 printf(" -i file info (track list)\n"); 1335 printf(" -l list notes as they are played (in the format totablature expects)\n"); 1336 printf(" -n no sound; don't actually play the midi\n"); 1337 printf(" -c comma separated list of channels to play (default: all)\n"); 1338 printf(" -r transpose notes by amount (default: 0)\n"); 1339 printf(" -k skip long sections of silence (good for playing single tracks)\n"); 1340 1341 printf(" -v verbose; list all events except note on / note off\n"); 1342 printf(" -h shows this help screen\n"); 1343 1344 return 0; 1345 break; 1346 default: 1347 printf("%s: unknown command line option: %s\n", argv[0], argv[1]); 1348 return 1; 1349 } 1350 else 1351 filename = argv[a]; 1352 } 1353 1354 if(filename == null){ 1355 printf("%s: no file given. Try %s -h for help.\n", argv[0], argv[0]); 1356 return 1; 1357 } 1358 1359 loadMidi(&mid, filename); 1360 if(mid == null){ 1361 printf("%s: unable to read file %s\n", argv[0], filename); 1362 return 1; 1363 } 1364 1365 if(displayinfo){ 1366 int len = getMidiLength(mid); 1367 printf("File: %s\n", filename); 1368 printf("Ticks per quarter note: %d\n", mid.speed); 1369 printf("Initial tempo: %d\n", getMidiTempo(mid)); 1370 printf("Length: %d:%d\n", len / 60, len%60); 1371 printf("Tracks:\n"); 1372 for(a = 0; a < mid.numTracks; a++){ 1373 c[0] = getTrackNameChunk(mid, a); 1374 if(c[0] != null){ 1375 printf("%d: ", a); 1376 for(b = 0; b < c[0].length; b++) 1377 fputc(c[0].data[b], stdout); 1378 printf("\n"); 1379 } 1380 } 1381 1382 freeMidi(&mid); 1383 return 0; 1384 } 1385 1386 return 0; 1387 } 1388 }