MidiFile.hpp 7.7 KB

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