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