Arduino MIDI File Parser
MidiFileParser.h
Go to the documentation of this file.
1 
13 #pragma once
14 
15 #include "MidiFileParserState.h"
16 #include <math.h>
17 #include <stdio.h>
18 #include <string.h>
19 
27 // Compiling outside of Arduino requires the millis() function.
28 #ifndef ARDUINO
29 #include <chrono>
30 #include <cstdint>
31 #include <iostream>
32 
33 inline uint64_t millis() {
34  using namespace std::chrono;
35  return duration_cast<milliseconds>(system_clock::now().time_since_epoch())
36  .count();
37 }
38 #endif
39 
40 #define MIDI_BUFFER_SIZE 1024 * 2
41 #define MIDI_MIN_REFILL_SIZE 512
42 
43 namespace midi {
44 
45 #ifndef ARDUINO
46 class Print {
47  virtual size_t write(uint8_t) = 0;
48 };
49 #endif
50 
52 
53 
61 class MidiFileParser : public Print {
62 public:
64  bool begin(bool log = true, int bufferSize = MIDI_BUFFER_SIZE) {
65  log_active = log;
66  track_no = 0;
67  parser_state.in.resize(bufferSize);
68  is_ok = true;
69  reset();
70  return true;
71  }
72 
73  size_t write(uint8_t c) override { return write(&c, 1); }
74 
76  virtual size_t write(const uint8_t *data, size_t len) {
77  // store actual processing length
78  if (len > 0) {
79  write_len = len;
80  }
81  // write only if data is available
82  if (len <= parser_state.in.availableForWrite()) {
83  return parser_state.in.write(data, len);
84  }
85  return 0;
86  }
87 
89  int availableForWrite() { return parser_state.in.availableForWrite(); }
90 
93  // parse next element
94  parser_state.status = MIDI_PARSER_EOB;
95  if (!parser_state.in.isEmpty()) {
96  midi_parser_status status = midi_parse();
97  parser_state.status = status;
98  if (log_active) {
99  logStatus(status);
100  }
101  }
102  // update is_ok
103  if (parser_state.status == MIDI_PARSER_EOB ||
104  parser_state.status == MIDI_PARSER_ERROR) {
105  is_ok = false;
106  }
107  return parser_state;
108  }
109 
113  static uint64_t timeout = 0l;
114 
115  // Wait for event to become relevant
116  if (timeout != 0l) {
117  if (millis() < timeout) {
118  return not_ready;
119  }
120 
121  timeout = 0;
122  return parser_state;
123  }
124 
125  // Parse next element
126  if (!parser_state.in.isEmpty()) {
127  midi_parser_status status = midi_parse();
128  // delay result ?
129  if (parser_state.timeInMs() > 0) {
130  timeout = millis() + parser_state.timeInMs();
131  return not_ready;
132  } else {
133  parser_state.status = status;
134  }
135  }
136 
137  // Update is_ok to false when we have an error or we are at the end
138  if (parser_state.status == MIDI_PARSER_EOB ||
139  parser_state.status == MIDI_PARSER_ERROR) {
140  is_ok = false;
141  return eob;
142  }
143  return parser_state;
144  }
145 
147  operator bool() { return is_ok; }
148 
150  void end() {}
151 
153  const char *midi_status_name(int status) {
154  switch (status) {
155  case MIDI_STATUS_NOTE_OFF:
156  return "Note Off";
157  case MIDI_STATUS_NOTE_ON:
158  return "Note On";
159  case MIDI_STATUS_NOTE_AT:
160  return "Note Aftertouch";
161  case MIDI_STATUS_CC:
162  return "CC";
163  case MIDI_STATUS_PGM_CHANGE:
164  return "Program Change";
165  case MIDI_STATUS_CHANNEL_AT:
166  return "Channel Aftertouch";
167  case MIDI_STATUS_PITCH_BEND:
168  return "Pitch Bend";
169 
170  default:
171  return "(unknown)";
172  }
173  }
174 
176  const char *midi_file_format_name(int fmt) {
177  switch (fmt) {
178  case MIDI_FILE_FORMAT_SINGLE_TRACK:
179  return "single track";
180  case MIDI_FILE_FORMAT_MULTIPLE_TRACKS:
181  return "multiple tracks";
182  case MIDI_FILE_FORMAT_MULTIPLE_SONGS:
183  return "multiple songs";
184 
185  default:
186  return "(unknown)";
187  }
188  }
189 
191  const char *midi_meta_name(int type) {
192  switch (type) {
193  case MIDI_META_SEQ_NUM:
194  return "Sequence Number";
195  case MIDI_META_TEXT:
196  return "Text";
197  case MIDI_META_COPYRIGHT:
198  return "Copyright";
199  case MIDI_META_TRACK_NAME:
200  return "Track Name";
201  case MIDI_META_INSTRUMENT_NAME:
202  return "Instrument Name";
203  case MIDI_META_LYRICS:
204  return "Lyrics";
205  case MIDI_META_MAKER:
206  return "Maker";
207  case MIDI_META_CUE_POINT:
208  return "Cue Point";
209  case MIDI_META_CHANNEL_PREFIX:
210  return "Channel Prefix";
211  case MIDI_META_END_OF_TRACK:
212  return "End of Track";
213  case MIDI_META_SET_TEMPO:
214  return "Set Tempo";
215  case MIDI_META_SMPTE_OFFSET:
216  return "SMPTE Offset";
217  case MIDI_META_TIME_SIGNATURE:
218  return "Time Signature";
219  case MIDI_META_KEY_SIGNATURE:
220  return "Key Signature";
221  case MIDI_META_SEQ_SPECIFIC:
222  return "Sequencer Specific";
223 
224  default:
225  return "(unknown)";
226  }
227  }
228 
229 
230 protected:
232  bool log_active = false;
233  int write_len = 256;
234  midi_parser_state parser_state;
235  bool is_ok = true;
236  int track_no = 0;
237  midi_parser_state not_ready{MIDI_PARSER_DELAY};
238  midi_parser_state eob{MIDI_PARSER_EOB};
239 
240  void setState(midi_parser_status state){
241  parser_state.status = state;
242  if (parser_state.status == MIDI_PARSER_EOB ||
243  parser_state.status == MIDI_PARSER_ERROR) {
244  is_ok = false;
245  }
246  }
247 
248  void logStatus(midi_parser_status status) {
249  switch (status) {
250  case MIDI_PARSER_EOB:
251  printf("MIDI_PARSER_EOB\n");
252  break;
253 
254  case MIDI_PARSER_ERROR:
255  printf("MIDI_PARSER_ERROR\n");
256  break;
257 
258  case MIDI_PARSER_INIT:
259  printf("MIDI_PARSER_INIT\n");
260  break;
261 
262  case MIDI_PARSER_HEADER:
263  printf("\nheader\n");
264  printf(" size: %d\n", parser_state.header.size);
265  printf(" format: %d [%s]\n", parser_state.header.format,
266  midi_file_format_name(parser_state.header.format));
267  printf(" tracks count: %d\n", parser_state.header.tracks_count);
268  printf(" time division: %d\n", parser_state.header.time_division);
269  break;
270 
271  case MIDI_PARSER_TRACK:
272  printf("\ntrack\n");
273  printf(" length: %d\n", parser_state.track.size);
274  break;
275 
276  case MIDI_PARSER_TRACK_MIDI:
277  printf("\ntrack-midi\n");
278  printf(" time: %ld\n", (long)parser_state.vtime);
279  printf(" status: %d [%s]\n", parser_state.midi.status,
280  midi_status_name(parser_state.midi.status));
281  printf(" channel: %d\n", parser_state.midi.channel);
282  printf(" param1: %d\n", parser_state.midi.param1);
283  printf(" param2: %d\n", parser_state.midi.param2);
284  break;
285 
286  case MIDI_PARSER_TRACK_META:
287  printf("\ntrack-meta\n");
288  printf(" time: %ld\n", (long)parser_state.vtime);
289  printf(" type: %d [%s]\n", parser_state.meta.type,
290  midi_meta_name(parser_state.meta.type));
291  printf(" length: %d\n", parser_state.meta.length);
292  break;
293 
294  case MIDI_PARSER_TRACK_SYSEX:
295  printf("\ntrack-sysex\n");
296  printf(" time: %ld\n", (long)parser_state.vtime);
297  break;
298 
299  case MIDI_PARSER_DELAY:
300  break;
301 
302  default:
303  printf("\nunhandled state: %d\n", status);
304  break;
305  }
306  }
307 
308  void reset() {
309  parser_state.in.reset();
310  parser_state.status = MIDI_PARSER_INIT;
311  parser_state.status_internal = MIDI_PARSER_INIT;
312  }
313 
314  int midi_event_datalen(int status) {
315  switch (status) {
316  case MIDI_STATUS_PGM_CHANGE:
317  return 1;
318  case MIDI_STATUS_CHANNEL_AT:
319  return 1;
320  default:
321  return 2;
322  }
323  }
324 
325  uint16_t midi_parse_be16(const uint8_t *in) {
326  return (static_cast<uint16_t>(in[0]) << 8) | in[1];
327  }
328 
329  uint32_t midi_parse_be32(const uint8_t *in) {
330  return (static_cast<uint32_t>(in[0]) << 24) |
331  (static_cast<uint32_t>(in[1]) << 16) |
332  static_cast<uint32_t>((in[2]) << 8) | in[3];
333  }
334 
335  uint64_t midi_parse_N(const uint8_t *from_bytes, int len) {
336  uint64_t tmp = 0;
337  for (int j = 0; j < len; j++) {
338  uint64_t byte = from_bytes[j];
339  int shift = 8 * (len - j - 1);
340  tmp |= byte << shift;
341  }
342  return tmp;
343  }
344 
345  uint64_t midi_parse_variable_length(int32_t *offset) {
346  uint64_t value = 0;
347  int32_t i = *offset;
348 
349  for (; i < parser_state.in.available(); ++i) {
350  value = (value << 7) | (parser_state.in[i] & 0x7f);
351  if (!(parser_state.in[i] & 0x80))
352  break;
353  }
354  *offset = i + 1;
355  return value;
356  }
357 
358  enum midi_parser_status midi_parse_header() {
359  if (parser_state.in.available() < 14)
360  return MIDI_PARSER_EOB;
361 
362  if (!parser_state.in.equals("MThd"))
363  return MIDI_PARSER_ERROR;
364 
365  parser_state.header.size = midi_parse_be32(parser_state.in.peekStr(4, 4));
366  parser_state.header.format = midi_parse_be16(parser_state.in.peekStr(8, 2));
367  parser_state.header.tracks_count =
368  midi_parse_be16(parser_state.in.peekStr(10, 2));
369  int time_div = midi_parse_be16(parser_state.in.peekStr(12, 2));
370  if (time_div > 0) {
371  parser_state.header.time_division = time_div;
372  }
373 
374  parser_state.in.consume(14);
375  parser_state.status_internal = MIDI_PARSER_HEADER;
376  return MIDI_PARSER_HEADER;
377  }
378 
379  enum midi_parser_status midi_parse_track() {
380  if (parser_state.in.available() < 8)
381  return MIDI_PARSER_EOB;
382 
383  parser_state.track.size = midi_parse_be32(parser_state.in.peekStr(0, 4));
384  parser_state.track.number = ++track_no;
385  parser_state.status_internal = MIDI_PARSER_TRACK;
386  parser_state.in.consume(8);
387  // parser.in.available() -= 8;
388  parser_state.buffered_status = MIDI_STATUS_NA;
389  return MIDI_PARSER_TRACK;
390  }
391 
392  bool midi_parse_vtime() {
393  uint8_t nbytes = 0;
394  uint8_t cont = 1; // continue flag
395 
396  parser_state.vtime = 0;
397  while (cont) {
398  ++nbytes;
399 
400  if (parser_state.in.available() < nbytes ||
401  parser_state.track.size < nbytes)
402  return false;
403 
404  uint8_t b = parser_state.in[nbytes - 1];
405  parser_state.vtime = (parser_state.vtime << 7) | (b & 0x7f);
406 
407  // The largest value allowed within a MIDI file is 0x0FFFFFFF. A lot of
408  // leading bytes with the highest bit set might overflow the nbytes
409  // counter and create an endless loop. If one would use 0x80 bytes for
410  // padding the check on parser.vtime would not terminate the endless
411  // loop. Since the maximum value can be encoded in 5 bytes or less, we can
412  // assume bad input if more bytes were used.
413  if (parser_state.vtime > 0x0fffffff || nbytes > 5)
414  return false;
415 
416  cont = b & 0x80;
417  }
418 
419  if (parser_state.in.available() < nbytes) {
420  return false;
421  }
422 
423  parser_state.in.consume(nbytes);
424  // parser.in.available() -= nbytes;
425  parser_state.track.size -= nbytes;
426 
427  return true;
428  }
429 
430  enum midi_parser_status midi_parse_channel_event() {
431  if (parser_state.in.available() < 2)
432  return MIDI_PARSER_EOB;
433 
434  if ((parser_state.in[0] & 0x80) == 0) {
435  // Shortened event with running status.
436  if (parser_state.buffered_status == 0)
437  return MIDI_PARSER_EOB;
438  parser_state.midi.status = parser_state.buffered_status;
439  int datalen = midi_event_datalen(parser_state.midi.status);
440  if (parser_state.in.available() < datalen)
441  return MIDI_PARSER_EOB;
442  parser_state.midi.channel = parser_state.buffered_channel;
443  parser_state.midi.param1 = (datalen > 0 ? parser_state.in[0] : 0);
444  parser_state.midi.param2 = (datalen > 1 ? parser_state.in[1] : 0);
445 
446  parser_state.in.consume(datalen);
447  // parser.in.available() -= datalen;
448  parser_state.track.size -= datalen;
449  } else {
450  // Full event with its own status.
451  if (parser_state.in.available() < 3)
452  return MIDI_PARSER_EOB;
453  parser_state.midi.status = (parser_state.in[0] >> 4) & 0xf;
454  int datalen = midi_event_datalen(parser_state.midi.status);
455  if (parser_state.in.available() < 1 + datalen)
456  return MIDI_PARSER_EOB;
457  parser_state.midi.channel = parser_state.in[0] & 0xf;
458  parser_state.midi.param1 = (datalen > 0 ? parser_state.in[1] : 0);
459  parser_state.midi.param2 = (datalen > 1 ? parser_state.in[2] : 0);
460  parser_state.buffered_status =
461  (midi_status)parser_state.midi.status; // ps?
462  parser_state.buffered_channel = parser_state.midi.channel;
463 
464  parser_state.in.consume(1 + datalen);
465  // parser.in.available() -= 1 + datalen;
466  parser_state.track.size -= 1 + datalen;
467  }
468 
469  return MIDI_PARSER_TRACK_MIDI;
470  }
471 
472  int midi_parse_sysex_event() {
473  assert(parser_state.in.available() == 0 || parser_state.in[0] == 0xf0);
474 
475  if (parser_state.in.available() < 2)
476  return MIDI_PARSER_ERROR;
477 
478  int offset = 1;
479  parser_state.sysex.length = midi_parse_variable_length(&offset);
480  if (offset < 1 || offset > parser_state.in.available())
481  return MIDI_PARSER_ERROR;
482  parser_state.in.consume(offset);
483  // parser.in.available() -= offset;
484  parser_state.track.size -= offset;
485 
486  // Length should be positive and not more than the remaining size
487  if (parser_state.sysex.length <= 0 ||
488  parser_state.sysex.length > parser_state.in.available())
489  return MIDI_PARSER_ERROR;
490 
491  parser_state.sysex.bytes =
492  parser_state.in.peekStr(0, parser_state.sysex.length);
493  parser_state.in.consume(parser_state.sysex.length);
494  // parser.in.available() -= parser.sysex.length;
495  parser_state.track.size -= parser_state.sysex.length;
496  // Don't count the 0xF7 ending byte as data, if given:
497  if (parser_state.sysex.bytes[parser_state.sysex.length - 1] == 0xF7)
498  parser_state.sysex.length--;
499 
500  return MIDI_PARSER_TRACK_SYSEX;
501  }
502 
503  enum midi_parser_status midi_parse_meta_event() {
504  assert(parser_state.in.available() == 0 || parser_state.in[0] == 0xff);
505  if (parser_state.in.available() < 2)
506  return MIDI_PARSER_ERROR;
507 
508  parser_state.meta.type = parser_state.in[1];
509  int32_t offset = 2;
510  parser_state.meta.length = midi_parse_variable_length(&offset);
511 
512  // Length should never be negative or more than the remaining size
513  if (parser_state.meta.length < 0 ||
514  parser_state.meta.length > parser_state.in.available())
515  return MIDI_PARSER_ERROR;
516 
517  // Check buffer size
518  if (parser_state.in.available() < offset ||
519  parser_state.in.available() - offset < parser_state.meta.length)
520  return MIDI_PARSER_ERROR;
521 
522  parser_state.meta.bytes =
523  parser_state.in.peekStr(offset, parser_state.meta.length);
524  offset += parser_state.meta.length;
525 
526  // set tempo meta
527  if (parser_state.meta.type == 0x51) {
528  parser_state.tempo =
529  midi_parse_N(parser_state.meta.bytes, parser_state.meta.length);
530  }
531 
532  parser_state.in.consume(offset);
533  // parser.in.available() -= offset;
534  parser_state.track.size -= offset;
535  return MIDI_PARSER_TRACK_META;
536  }
537 
538  enum midi_parser_status midi_parse_event() {
539  parser_state.meta.bytes = NULL;
540  if (!midi_parse_vtime())
541  return MIDI_PARSER_EOB;
542 
543  // Make sure the parser has not consumed the entire file or track, else
544  // `parser-in[0]` might access heap-memory after the allocated buffer.
545  if (parser_state.in.available() <= 0 || parser_state.track.size <= 0)
546  return MIDI_PARSER_ERROR;
547 
548  if (parser_state.in[0] < 0xf0) { // Regular channel events:
549  return midi_parse_channel_event();
550  } else { // Special event types:
551  parser_state.buffered_status = MIDI_STATUS_NA; // (cancels running status)
552 
553  if (parser_state.in[0] == 0xf0)
554  return (midi_parser_status)midi_parse_sysex_event(); // ps?
555 
556  if (parser_state.in[0] == 0xff)
557  return midi_parse_meta_event();
558  }
559  return MIDI_PARSER_ERROR;
560  }
561 
562  enum midi_parser_status midi_parse() {
563  if (parser_state.in.isEmpty())
564  return MIDI_PARSER_EOB;
565 
566  switch (parser_state.status_internal) {
567  case MIDI_PARSER_INIT:
568  return midi_parse_header();
569 
570  case MIDI_PARSER_HEADER:
571  return midi_parse_track();
572 
573  case MIDI_PARSER_TRACK:
574  if (parser_state.track.size == 0) {
575  // we reached the end of the track
576  parser_state.status_internal = MIDI_PARSER_HEADER;
577  return midi_parse();
578  }
579  return midi_parse_event();
580 
581  default:
582  return MIDI_PARSER_ERROR;
583  }
584  }
585 };
586 
587 } // namespace midi
Midi file parser which stores the data in RAM before making them available for parsing....
Definition: MidiFileParserMultiTrack.h:130
Midi File parser. Provide the data via write: You should try to keep the buffer as full as possible w...
Definition: MidiFileParser.h:61
midi_parser_state & parse()
Parse data in order to provide the next midi element.
Definition: MidiFileParser.h:92
int availableForWrite()
Max number of bytes that we can write.
Definition: MidiFileParser.h:89
const char * midi_meta_name(int type)
Provides the string description for the midi_meta value.
Definition: MidiFileParser.h:191
const char * midi_status_name(int status)
Provides the string description for the midi_status value.
Definition: MidiFileParser.h:153
const char * midi_file_format_name(int fmt)
Provides the string description for the file format.
Definition: MidiFileParser.h:176
bool begin(bool log=true, int bufferSize=MIDI_BUFFER_SIZE)
Initializes & starts the processing.
Definition: MidiFileParser.h:64
virtual size_t write(const uint8_t *data, size_t len)
Feed/Provide the midi data to the parser.
Definition: MidiFileParser.h:76
midi_parser_state & parseTimed()
Definition: MidiFileParser.h:112
void end()
Ends the processing: currently does nothing.
Definition: MidiFileParser.h:150
Definition: MidiFileParser.h:46
void consume(int offset)
Removes the next n characters from the ringbuffer.
Definition: RingBuffer.h:145
bool equals(const char *str)
Compares the string with the current peek values.
Definition: RingBuffer.h:139
uint8_t * peekStr(int idx, int len)
returns a temporary copy of the requested bytes
Definition: RingBuffer.h:119
MIDI Parser State Information.
Definition: MidiFileParserState.h:119