1 module midifile.midifile; 2 3 import std.file; 4 import std.string; 5 import std.range; 6 import std.exception; 7 8 enum MIDIEventType : ubyte 9 { 10 NoteOff = 0x80, 11 NoteOn = 0x90, 12 KeyAfterTouch = 0xA0, 13 ControlChange = 0xB0, 14 ProgramChange = 0xC0, 15 ChannelAfterTouch = 0xD0, 16 PitchWheel = 0xE0, 17 SYSEX = 0xF0, 18 Custom = 0xFF 19 } 20 21 enum MIDIEvents : ubyte 22 { 23 SequenceNumber = 0x00, // Sequence Number 24 Text = 0x01, // Text 25 Copyright = 0x02, // Copyright 26 TrackName = 0x03, // Sequence/Track Name 27 Instrument = 0x04, // Instrument 28 Lyric = 0x05, // Lyric 29 Marker = 0x06, // Marker 30 CuePoint = 0x07, // Cue Point 31 PatchName = 0x08, // Program (Patch) Name 32 PortName = 0x09, // Device (Port) Name 33 EndOfTrack = 0x2F, // End of Track 34 Tempo = 0x51, // Tempo 35 SMPTE = 0x54, // SMPTE Offset 36 TimeSignature = 0x58, // Time Signature 37 KeySignature = 0x59, // Key Signature 38 Custom = 0x7F, // Proprietary Event 39 } 40 41 struct MThd_Chunk 42 { 43 ubyte[4] id; // 'M','T','h','d' 44 uint length; 45 46 ushort format; 47 ushort numTracks; 48 ushort ticksPerBeat; 49 } 50 51 struct MTrk_Chunk 52 { 53 ubyte[4] id; // 'M','T','r','k' */ 54 uint length; 55 } 56 57 auto getFront(R)(ref R range) 58 { 59 auto f = range.front(); 60 range.popFront(); 61 return f; 62 } 63 64 T[] getFrontN(R, T = ElementType!R)(ref R range, size_t n) 65 { 66 T[] f = range[0..n]; 67 range.popFrontN(n); 68 return f; 69 } 70 71 As frontAs(As, R)(R range) 72 { 73 As r; 74 (cast(ubyte*)&r)[0..As.sizeof] = range[0..As.sizeof]; 75 return r; 76 } 77 78 As getFrontAs(As, R)(ref R range) 79 { 80 As r; 81 (cast(ubyte*)&r)[0..As.sizeof] = range.getFrontN(As.sizeof)[]; 82 return r; 83 } 84 85 class MIDIFile 86 { 87 this(const(char)[] filename) 88 { 89 void[] file = enforce(read(filename), "Couldn't load .midi file!"); 90 this(cast(ubyte[])file); 91 } 92 93 this(const(ubyte)[] buffer) 94 { 95 if(buffer[0..4] == "RIFF") 96 { 97 buffer.popFrontN(8); 98 assert(buffer[0..4] == "RMID", "Not a midi file..."); 99 buffer.popFrontN(4); 100 } 101 102 MThd_Chunk *pHd = cast(MThd_Chunk*)buffer.ptr; 103 104 assert(pHd.id[] == "MThd", "Not a midi file..."); 105 bigToHostEndian(pHd); 106 107 format = pHd.format; 108 ticksPerBeat = pHd.ticksPerBeat; 109 tracks = new MIDIEvent[][pHd.numTracks]; 110 111 buffer.popFrontN(8 + pHd.length); 112 113 // we will only deal with type 1 midi files here.. 114 assert(pHd.format == 1, "Invalid midi type."); 115 116 for(size_t t = 0; t < pHd.numTracks && !buffer.empty; ++t) 117 { 118 MTrk_Chunk *pTh = cast(MTrk_Chunk*)buffer.ptr; 119 bigToHostEndian(pTh); 120 121 buffer.popFrontN(MTrk_Chunk.sizeof); 122 123 if(pTh.id[] == "MTrk") 124 { 125 const(ubyte)[] track = buffer[0..pTh.length]; 126 uint tick = 0; 127 ubyte lastStatus = 0; 128 129 while(!track.empty) 130 { 131 uint delta = readVarLen(track); 132 tick += delta; 133 ubyte status = track.getFront(); 134 135 MIDIEvent ev; 136 bool appendEvent = true; 137 138 if(status == 0xFF) 139 { 140 // non-midi event 141 MIDIEvents type = cast(MIDIEvents)track.getFront(); 142 uint bytes = readVarLen(track); 143 144 // get the event bytes 145 const(ubyte)[] event = track.getFrontN(bytes); 146 147 // read event 148 switch(type) with(MIDIEvents) 149 { 150 case MIDIEvents.SequenceNumber: 151 { 152 static int sequence = 0; 153 154 if(!bytes) 155 ev.sequenceNumber = sequence++; 156 else 157 { 158 ushort seq = event.getFrontAs!ushort; 159 bigToHostEndian(&seq); 160 ev.sequenceNumber = cast(int)seq; 161 } 162 break; 163 } 164 case Text: 165 case Copyright: 166 case TrackName: 167 case Instrument: 168 case Lyric: 169 case Marker: 170 case CuePoint: 171 case PatchName: 172 case PortName: 173 { 174 ev.text = (cast(const(char)[])event).idup; 175 break; 176 } 177 case EndOfTrack: 178 { 179 // is it valid to have data remaining after the end of track marker? 180 break; 181 } 182 case Tempo: 183 { 184 ev.tempo.microsecondsPerBeat = event[0] << 16; 185 ev.tempo.microsecondsPerBeat |= event[1] << 8; 186 ev.tempo.microsecondsPerBeat |= event[2]; 187 break; 188 } 189 case SMPTE: 190 { 191 ev.smpte.hours = event[0]; 192 ev.smpte.minutes = event[1]; 193 ev.smpte.seconds = event[2]; 194 ev.smpte.frames = event[3]; 195 ev.smpte.subFrames = event[4]; 196 break; 197 } 198 case TimeSignature: 199 { 200 ev.timeSignature.numerator = event[0]; 201 ev.timeSignature.denominator = event[1]; 202 ev.timeSignature.clocks = event[2]; 203 ev.timeSignature.d = event[3]; 204 break; 205 } 206 case KeySignature: 207 { 208 ev.keySignature.sf = event[0]; 209 ev.keySignature.minor = event[1]; 210 break; 211 } 212 case Custom: 213 { 214 ev.data = event.idup; 215 break; 216 } 217 default: 218 // TODO: are there any we missed? 219 appendEvent = false; 220 } 221 222 if(appendEvent) 223 ev.subType = type; 224 } 225 else if(status == 0xF0) 226 { 227 uint bytes = readVarLen(track); 228 229 // get the SYSEX bytes 230 const(ubyte)[] event = track.getFrontN(bytes); 231 ev.data = event.idup; 232 } 233 else 234 { 235 if(status < 0x80) 236 { 237 // HACK: stick the last byte we popped back on the front... 238 track = (track.ptr - 1)[0..track.length+1]; 239 status = lastStatus; 240 } 241 lastStatus = status; 242 243 int eventType = status & 0xF0; 244 245 int param1 = readVarLen(track); 246 int param2 = 0; 247 if(eventType != MIDIEventType.ProgramChange && eventType != MIDIEventType.ChannelAfterTouch) 248 param2 = readVarLen(track); 249 250 switch(eventType) 251 { 252 case MIDIEventType.NoteOn: 253 case MIDIEventType.NoteOff: 254 { 255 ev.note.channel = status & 0x0F; 256 ev.note.note = param1; 257 ev.note.velocity = param2; 258 break; 259 } 260 default: 261 // TODO: handle other event types? 262 appendEvent = false; 263 } 264 } 265 266 // append event to track 267 if(appendEvent) 268 { 269 ev.tick = tick; 270 ev.delta = delta; 271 ev.type = status != 0xFF ? status & 0xF0 : status; 272 if(status != 0xFF) 273 ev.subType = status & 0x0F; 274 275 tracks[t] ~= ev; 276 } 277 } 278 } 279 280 buffer.popFrontN(pTh.length); 281 } 282 } 283 284 int format; 285 int ticksPerBeat; 286 287 MIDIEvent[][] tracks; 288 } 289 290 struct MIDIEvent 291 { 292 bool isEvent(MIDIEvents e) 293 { 294 return type == MIDIEventType.Custom && subType == e; 295 } 296 297 struct Note 298 { 299 ubyte channel; 300 int note; 301 int velocity; 302 } 303 struct Tempo 304 { 305 int microsecondsPerBeat; 306 } 307 struct SMPTE 308 { 309 ubyte hours, minutes, seconds, frames, subFrames; 310 } 311 struct TimeSignature 312 { 313 ubyte numerator, denominator; 314 ubyte clocks; 315 ubyte d; 316 } 317 struct KeySignature 318 { 319 ubyte sf; 320 ubyte minor; 321 } 322 323 uint tick; 324 uint delta; 325 ubyte type; 326 ubyte subType; 327 328 union 329 { 330 Note note; 331 int sequenceNumber; 332 string text; 333 Tempo tempo; 334 SMPTE smpte; 335 TimeSignature timeSignature; 336 KeySignature keySignature; 337 immutable(ubyte)[] data; 338 } 339 } 340 341 private: 342 343 void writeVarLen(ref ubyte[] buffer, uint value) 344 { 345 uint buf; 346 buf = value & 0x7F; 347 348 while((value >>= 7)) 349 { 350 buf <<= 8; 351 buf |= ((value & 0x7F) | 0x80); 352 } 353 354 while(1) 355 { 356 buffer ~= cast(ubyte)(buf & 0xFF); 357 if(buf & 0x80) 358 buf >>= 8; 359 else 360 break; 361 } 362 } 363 364 uint readVarLen(ref const(ubyte)[] buffer) 365 { 366 uint value; 367 ubyte c; 368 369 value = buffer[0]; 370 buffer = buffer[1..$]; 371 372 if(value & 0x80) 373 { 374 value &= 0x7F; 375 do 376 { 377 c = buffer[0]; 378 buffer = buffer[1..$]; 379 value = (value << 7) + (c & 0x7F); 380 } 381 while(c & 0x80); 382 } 383 384 return value; 385 } 386 387 void flipEndian(T)(T* pData) 388 { 389 static if(is(T == struct)) 390 { 391 foreach(ref m; (*pData).tupleof) 392 { 393 alias M = typeof(m); 394 static if(M.sizeof > 1 && (is(M == struct) || std.traits.isNumeric!M || std.traits.isSomeChar!M)) 395 flipEndian(&m); 396 } 397 } 398 else 399 { 400 T copy = *pData; 401 402 ubyte* pBytes = cast(ubyte*)pData; 403 const(ubyte)* pCopy = cast(const(ubyte)*)© 404 foreach(a; 0 .. T.sizeof) 405 pBytes[a] = pCopy[T.sizeof-1-a]; 406 } 407 } 408 409 version(LittleEndian) 410 { 411 void hostToBigEndian(T)(T* x) { flipEndian(x); } 412 void hostToLittleEndian(T)(T* x) {} 413 void littleToHostEndian(T)(T* x) {} 414 void bigToHostEndian(T)(T* x) { flipEndian(x); } 415 } 416 else version(BigEndian) 417 { 418 void hostToBigEndian(T)(T* x) {} 419 void hostToLittleEndian(T)(T* x) { flipEndian(x); } 420 void littleToHostEndian(T)(T* x) { flipEndian(x); } 421 void bigToHostEndian(T)(T* x) {} 422 } 423 else 424 static assert("Unknown endian!");