1 #ifndef MIDIFILE_HPP
2 #define MIDIFILE_HPP
3
4 /**
5 * Name: MidiFile.hpp
6 * Purpose: C++ re-write of the python module MidiFile.py
7 * Author: Mohamed Abdel Maksoud <mohamed at amaksoud.com>
8 *-----------------------------------------------------------------------------
9 * Name: MidiFile.py
10 * Purpose: MIDI file manipulation utilities
11 *
12 * Author: Mark Conway Wirt <emergentmusics) at (gmail . com>
13 *
14 * Created: 2008/04/17
15 * Copyright: (c) 2009 Mark Conway Wirt
16 * License: Please see License.txt for the terms under which this
17 * software is distributed.
18 *-----------------------------------------------------------------------------
19 */
20
21 #include <string.h>
22 #include <stdint.h>
23 #include <string>
24 #include <vector>
25 #include <set>
26 #include <algorithm>
27 #include <assert.h>
28
29 using std::string;
30 using std::vector;
31 using std::set;
32
33 namespace MidiFile
34 {
35
36 const int TICKSPERBEAT = 128;
37
38
writeVarLength(uint32_t val,uint8_t * buffer)39 int writeVarLength(uint32_t val, uint8_t *buffer)
40 {
41 /*
42 Accept an input, and write a MIDI-compatible variable length stream
43
44 The MIDI format is a little strange, and makes use of so-called variable
45 length quantities. These quantities are a stream of bytes. If the most
46 significant bit is 1, then more bytes follow. If it is zero, then the
47 byte in question is the last in the stream
48 */
49 int size = 0;
50 uint8_t result, little_endian[4];
51 result = val & 0x7F;
52 little_endian[size++] = result;
53 val = val >> 7;
54 while (val > 0)
55 {
56 result = val & 0x7F;
57 result = result | 0x80;
58 little_endian[size++] = result;
59 val = val >> 7;
60 }
61 for (int i=0; i<size; i++)
62 {
63 buffer[i] = little_endian[size-i-1];
64 }
65
66 return size;
67 }
68
writeBigEndian4(uint32_t val,uint8_t * buf)69 int writeBigEndian4(uint32_t val, uint8_t *buf)
70 {
71 buf[0] = val >> 24;
72 buf[1] = val >> 16 & 0xff;
73 buf[2] = val >> 8 & 0xff;
74 buf[3] = val & 0xff;
75 return 4;
76 }
77
writeBigEndian2(uint16_t val,uint8_t * buf)78 int writeBigEndian2(uint16_t val, uint8_t *buf)
79 {
80 buf[0] = val >> 8 & 0xff;
81 buf[1] = val & 0xff;
82 return 2;
83 }
84
85
86 class MIDIHeader
87 {
88 // Class to encapsulate the MIDI header structure.
89 uint16_t numTracks;
90 uint16_t ticksPerBeat;
91
92 public:
93
MIDIHeader(uint16_t nTracks,uint16_t ticksPB=TICKSPERBEAT)94 MIDIHeader(uint16_t nTracks, uint16_t ticksPB=TICKSPERBEAT): numTracks(nTracks), ticksPerBeat(ticksPB) {}
95
writeToBuffer(uint8_t * buffer,int start=0) const96 inline int writeToBuffer(uint8_t *buffer, int start=0) const
97 {
98 // chunk ID
99 buffer[start++] = 'M'; buffer[start++] = 'T'; buffer[start++] = 'h'; buffer[start++] = 'd';
100 // chunk size (6 bytes always)
101 buffer[start++] = 0; buffer[start++] = 0; buffer[start++] = 0; buffer[start++] = 0x06;
102 // format: 1 (multitrack)
103 buffer[start++] = 0; buffer[start++] = 0x01;
104
105 start += writeBigEndian2(numTracks, buffer+start);
106
107 start += writeBigEndian2(ticksPerBeat, buffer+start);
108
109 return start;
110 }
111
112 };
113
114
115 struct Event
116 {
117 uint32_t time;
118 uint32_t tempo;
119 string trackName;
120 enum {NOTE_ON, NOTE_OFF, TEMPO, PROG_CHANGE, TRACK_NAME} type;
121 // TODO make a union to save up space
122 uint8_t pitch;
123 uint8_t programNumber;
124 uint8_t duration;
125 uint8_t volume;
126 uint8_t channel;
127
EventMidiFile::Event128 Event() {time=tempo=pitch=programNumber=duration=volume=channel=0; trackName="";}
129
writeToBufferMidiFile::Event130 inline int writeToBuffer(uint8_t *buffer) const
131 {
132 uint8_t code, fourbytes[4];
133 int size=0;
134 switch (type)
135 {
136 case NOTE_ON:
137 code = 0x9 << 4 | channel;
138 size += writeVarLength(time, buffer+size);
139 buffer[size++] = code;
140 buffer[size++] = pitch;
141 buffer[size++] = volume;
142 break;
143 case NOTE_OFF:
144 code = 0x8 << 4 | channel;
145 size += writeVarLength(time, buffer+size);
146 buffer[size++] = code;
147 buffer[size++] = pitch;
148 buffer[size++] = volume;
149 break;
150 case TEMPO:
151 code = 0xFF;
152 size += writeVarLength(time, buffer+size);
153 buffer[size++] = code;
154 buffer[size++] = 0x51;
155 buffer[size++] = 0x03;
156 writeBigEndian4(int(60000000.0 / tempo), fourbytes);
157
158 //printf("tempo of %x translates to ", tempo);
159 /*
160 for (int i=0; i<3; i++) printf("%02x ", fourbytes[i+1]);
161 printf("\n");
162 */
163 buffer[size++] = fourbytes[1];
164 buffer[size++] = fourbytes[2];
165 buffer[size++] = fourbytes[3];
166 break;
167 case PROG_CHANGE:
168 code = 0xC << 4 | channel;
169 size += writeVarLength(time, buffer+size);
170 buffer[size++] = code;
171 buffer[size++] = programNumber;
172 break;
173 case TRACK_NAME:
174 size += writeVarLength(time, buffer+size);
175 buffer[size++] = 0xFF;
176 buffer[size++] = 0x03;
177 size += writeVarLength(trackName.size(), buffer+size);
178 trackName.copy((char *)(&buffer[size]), trackName.size());
179 size += trackName.size();
180 // buffer[size++] = '\0';
181 // buffer[size++] = '\0';
182
183 break;
184 }
185 return size;
186 } // writeEventsToBuffer
187
188
189 // events are sorted by their time
operator <MidiFile::Event190 inline bool operator < (const Event& b) const {
191 return this->time < b.time ||
192 (this->time == b.time && this->type > b.type);
193 }
194 };
195
196 template<const int MAX_TRACK_SIZE>
197 class MIDITrack
198 {
199 // A class that encapsulates a MIDI track
200 // Nested class definitions.
201 vector<Event> events;
202
203 public:
204 uint8_t channel;
205
MIDITrack()206 MIDITrack(): channel(0) {}
207
addEvent(const Event & e)208 inline void addEvent(const Event &e)
209 {
210 Event E = e;
211 events.push_back(E);
212 }
213
addNote(uint8_t pitch,uint8_t volume,double time,double duration)214 inline void addNote(uint8_t pitch, uint8_t volume, double time, double duration)
215 {
216 Event event; event.channel = channel;
217 event.volume = volume;
218
219 event.type = Event::NOTE_ON; event.pitch = pitch; event.time= (uint32_t) (time * TICKSPERBEAT);
220 addEvent(event);
221
222 event.type = Event::NOTE_OFF; event.pitch = pitch; event.time=(uint32_t) ((time+duration) * TICKSPERBEAT);
223 addEvent(event);
224
225 //printf("note: %d-%d\n", (uint32_t) time * TICKSPERBEAT, (uint32_t)((time+duration) * TICKSPERBEAT));
226 }
227
addName(const string & name,uint32_t time)228 inline void addName(const string &name, uint32_t time)
229 {
230 Event event; event.channel = channel;
231 event.type = Event::TRACK_NAME; event.time=time; event.trackName = name;
232 addEvent(event);
233 }
234
addProgramChange(uint8_t prog,uint32_t time)235 inline void addProgramChange(uint8_t prog, uint32_t time)
236 {
237 Event event; event.channel = channel;
238 event.type = Event::PROG_CHANGE; event.time=time; event.programNumber = prog;
239 addEvent(event);
240 }
241
addTempo(uint8_t tempo,uint32_t time)242 inline void addTempo(uint8_t tempo, uint32_t time)
243 {
244 Event event; event.channel = channel;
245 event.type = Event::TEMPO; event.time=time; event.tempo = tempo;
246 addEvent(event);
247 }
248
writeMIDIToBuffer(uint8_t * buffer,int start=0) const249 inline int writeMIDIToBuffer(uint8_t *buffer, int start=0) const
250 {
251 // Write the meta data and note data to the packed MIDI stream.
252 // Process the events in the eventList
253
254 start += writeEventsToBuffer(buffer, start);
255
256 // Write MIDI close event.
257 buffer[start++] = 0x00;
258 buffer[start++] = 0xFF;
259 buffer[start++] = 0x2F;
260 buffer[start++] = 0x00;
261
262 // return the entire length of the data and write to the header
263
264 return start;
265 }
266
writeEventsToBuffer(uint8_t * buffer,int start=0) const267 inline int writeEventsToBuffer(uint8_t *buffer, int start=0) const
268 {
269 // Write the events in MIDIEvents to the MIDI stream.
270 vector<Event> _events = events;
271 std::sort(_events.begin(), _events.end());
272 vector<Event>::const_iterator it;
273 uint32_t time_last = 0, tmp;
274 for (it = _events.begin(); it!=_events.end(); ++it)
275 {
276 Event e = *it;
277 if (e.time < time_last){
278 printf("error: e.time=%d time_last=%d\n", e.time, time_last);
279 assert(false);
280 }
281 tmp = e.time;
282 e.time -= time_last;
283 time_last = tmp;
284 start += e.writeToBuffer(buffer+start);
285 if (start >= MAX_TRACK_SIZE) {
286 break;
287 }
288 }
289 return start;
290 }
291
writeToBuffer(uint8_t * buffer,int start=0) const292 inline int writeToBuffer(uint8_t *buffer, int start=0) const
293 {
294 uint8_t eventsBuffer[MAX_TRACK_SIZE];
295 uint32_t events_size = writeMIDIToBuffer(eventsBuffer);
296 //printf(">> track %lu events took 0x%x bytes\n", events.size(), events_size);
297
298 // chunk ID
299 buffer[start++] = 'M'; buffer[start++] = 'T'; buffer[start++] = 'r'; buffer[start++] = 'k';
300 // chunk size
301 start += writeBigEndian4(events_size, buffer+start);
302 // copy events data
303 memmove(buffer+start, eventsBuffer, events_size);
304 start += events_size;
305 return start;
306 }
307 };
308
309 }; // namespace
310
311 #endif
312