Skip to content

Commit cd874a7

Browse files
authored
Merge pull request #11658 from adafruit/synthio-miditrack-meta-events
synthio: stop MidiTrack treating every 0xF. byte as End of Track
2 parents 178e9ee + 679e970 commit cd874a7

1 file changed

Lines changed: 67 additions & 23 deletions

File tree

‎shared-module/synthio/MidiTrack.c‎

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,25 @@
1010

1111

1212
static void record_midi_stream_error(synthio_miditrack_obj_t *self) {
13-
self->error_location = self->pos;
13+
// keep the first error; later ones are usually fallout from this one
14+
if (self->error_location < 0) {
15+
self->error_location = self->pos;
16+
}
1417
self->pos = self->track.len;
1518
}
1619

20+
// Check that `count` more bytes are available, ending the track if not.
21+
static bool need_bytes(synthio_miditrack_obj_t *self, size_t count) {
22+
if (count > self->track.len - self->pos) {
23+
record_midi_stream_error(self);
24+
return false;
25+
}
26+
return true;
27+
}
28+
1729
static mp_obj_t parse_note(synthio_miditrack_obj_t *self) {
1830
uint8_t *buffer = self->track.buf;
19-
size_t len = self->track.len;
20-
if (self->pos + 1 >= len) {
21-
record_midi_stream_error(self);
31+
if (!need_bytes(self, 2)) {
2232
return MP_OBJ_NEW_SMALL_INT(0);
2333
}
2434
uint8_t note = buffer[(self->pos)++];
@@ -28,23 +38,58 @@ static mp_obj_t parse_note(synthio_miditrack_obj_t *self) {
2838
return MP_OBJ_NEW_SMALL_INT(note);
2939
}
3040

41+
// MIDI variable-length quantity: up to 4 bytes of 7 bits each.
42+
static uint32_t decode_varint(synthio_miditrack_obj_t *self) {
43+
uint8_t *buffer = self->track.buf;
44+
uint32_t value = 0;
45+
for (size_t i = 0; i < 4; i++) {
46+
if (!need_bytes(self, 1)) {
47+
return 0;
48+
}
49+
uint8_t c = buffer[self->pos++];
50+
value = (value << 7) | (c & 0x7f);
51+
if (!(c & 0x80)) {
52+
return value;
53+
}
54+
}
55+
// a fifth continuation byte cannot be part of a valid quantity
56+
record_midi_stream_error(self);
57+
return 0;
58+
}
59+
3160
static int decode_duration(synthio_miditrack_obj_t *self) {
61+
uint32_t delta = decode_varint(self);
62+
return delta * self->synth.base.sample_rate / self->tempo;
63+
}
64+
65+
// Meta (0xFF) and system exclusive (0xF0/0xF7) events both carry an explicit
66+
// length, so they can be skipped exactly rather than guessed at.
67+
static void decode_system_event(synthio_miditrack_obj_t *self, uint8_t status) {
3268
uint8_t *buffer = self->track.buf;
33-
size_t len = self->track.len;
34-
uint8_t c;
35-
uint32_t delta = 0;
36-
do {
37-
c = buffer[self->pos++];
38-
delta <<= 7;
39-
delta |= c & 0x7f;
40-
} while ((c & 0x80) && (self->pos < len));
41-
42-
// errors cannot be raised from the background task, so simply end the track.
43-
if (c & 0x80) {
44-
self->pos = self->track.len;
69+
if (status == 0xFF) {
70+
if (!need_bytes(self, 1)) {
71+
return;
72+
}
73+
uint8_t type = buffer[self->pos++];
74+
uint32_t length = decode_varint(self);
75+
if (!need_bytes(self, length)) {
76+
return;
77+
}
78+
if (type == 0x2F) { // End of Track
79+
self->pos = self->track.len;
80+
return;
81+
}
82+
self->pos += length;
83+
} else if (status == 0xF0 || status == 0xF7) {
84+
uint32_t length = decode_varint(self);
85+
if (!need_bytes(self, length)) {
86+
return;
87+
}
88+
self->pos += length;
89+
} else {
90+
// system common and real-time bytes have no meaning in a file
4591
record_midi_stream_error(self);
4692
}
47-
return delta * self->synth.base.sample_rate / self->tempo;
4893
}
4994

5095
// invariant: pointing at a MIDI message
@@ -55,7 +100,8 @@ static void decode_until_pause(synthio_miditrack_obj_t *self) {
55100
if (self->pos >= len) {
56101
break;
57102
}
58-
switch (buffer[self->pos++] >> 4) {
103+
uint8_t status = buffer[self->pos++];
104+
switch (status >> 4) {
59105
case 8: { // Note Off
60106
mp_obj_t note = parse_note(self);
61107
synthio_span_change_note(&self->synth, note, SYNTHIO_SILENCE);
@@ -73,15 +119,13 @@ static void decode_until_pause(synthio_miditrack_obj_t *self) {
73119
break;
74120
case 12:
75121
case 13: // one data byte to ignore
76-
if (self->pos >= len || buffer[self->pos++] > 127) {
122+
if (need_bytes(self, 1) && buffer[self->pos++] > 127) {
77123
record_midi_stream_error(self);
78124
}
79125
break;
80-
case 15: // the full syntax is too complicated, just assume it's "End of Track" event
81-
self->pos = len;
126+
default: // 15: meta or system exclusive
127+
decode_system_event(self, status);
82128
break;
83-
default: // invalid event
84-
record_midi_stream_error(self);
85129
}
86130
if (self->pos < len) {
87131
self->synth.span.dur = decode_duration(self);

0 commit comments

Comments
 (0)