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)*)&copy;
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!");