From aad79fe5ebf68fd58b6f36c63db4d55a2b8c2e54 Mon Sep 17 00:00:00 2001 From: tyeth Date: Fri, 23 Oct 2026 01:34:28 +0100 Subject: [PATCH] synthio: treat a Note On of velocity 0 as a Note Off in MidiTrack The MIDI specification lets a Note On with a velocity of zero stand in for a Note Off, and files commonly use it so that a run of notes can share one status byte. MidiTrack ignored velocity entirely, so such a note was started and never stopped, and the voice sounded until the track ended. Read the velocity that was already being parsed and discarded, and release the note when it is zero. Co-Authored-By: Claude Opus 5 --- shared-module/synthio/MidiTrack.c | 33 +++++++++++++++++++------------ 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/shared-module/synthio/MidiTrack.c b/shared-module/synthio/MidiTrack.c index 96d9ac6168a..22058976ae8 100644 --- a/shared-module/synthio/MidiTrack.c +++ b/shared-module/synthio/MidiTrack.c @@ -26,16 +26,20 @@ static bool need_bytes(synthio_miditrack_obj_t *self, size_t count) { return true; } -static mp_obj_t parse_note(synthio_miditrack_obj_t *self) { +// Returns the note in the low 8 bits and the velocity in the next 8, or -1 +// if the pair could not be read. +static int parse_note(synthio_miditrack_obj_t *self) { uint8_t *buffer = self->track.buf; if (!need_bytes(self, 2)) { - return MP_OBJ_NEW_SMALL_INT(0); + return -1; } - uint8_t note = buffer[(self->pos)++]; - if (note > 127 || buffer[(self->pos)++] > 127) { + uint8_t note = buffer[self->pos++]; + uint8_t velocity = buffer[self->pos++]; + if (note > 127 || velocity > 127) { record_midi_stream_error(self); + return -1; } - return MP_OBJ_NEW_SMALL_INT(note); + return note | (velocity << 8); } // MIDI variable-length quantity: up to 4 bytes of 7 bits each. @@ -115,14 +119,17 @@ static void decode_until_pause(synthio_miditrack_obj_t *self) { } } switch (status >> 4) { - case 8: { // Note Off - mp_obj_t note = parse_note(self); - synthio_span_change_note(&self->synth, note, SYNTHIO_SILENCE); - break; - } - case 9: { // Note On - mp_obj_t note = parse_note(self); - synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, note); + case 8: // Note Off + case 9: { // Note On, but a velocity of zero means Note Off + int parsed = parse_note(self); + if (parsed >= 0) { + mp_obj_t note = MP_OBJ_NEW_SMALL_INT(parsed & 0xff); + if ((status >> 4) == 9 && (parsed >> 8) != 0) { + synthio_span_change_note(&self->synth, SYNTHIO_SILENCE, note); + } else { + synthio_span_change_note(&self->synth, note, SYNTHIO_SILENCE); + } + } break; } case 10: