From: RasmusML <rasmusml0501@gmail.com>
To: linux-bluetooth@vger.kernel.org
Cc: RasmusML <rasmusml@hotmail.dk>
Subject: [PATCH BlueZ 1/2] profiles/midi: Fix running status across channels
Date: Mon, 2 Nov 2026 23:12:22 +0100 [thread overview]
Message-ID: <20261102221223.16079-1-rasmusml0501@gmail.com> (raw)
From: RasmusML <rasmusml@hotmail.dk>
When two MIDI messages of the same type but on different channels end
up in the same BLE-MIDI packet, read_ev_others() writes the second
status byte without the timestampLow byte that must precede every
status byte. A receiver then reads that status byte as the timestamp
and applies the following data bytes to the previous message's channel
through running status, e.g.:
sent: ch1 NoteOff 60, ch2 NoteOff 64, ch3 CC123, ch4 CC123
packet: B5 94 81 3C 00 82 40 00 94 B3 7B 00 B4 7B 00
decoded: ch1 NoteOff 60, ch1 NoteOff 64, ch3 CC123, ch3 CC123
This happens because rstatus holds the ALSA event type, which carries
no channel, so the writer assumes running status while
snd_midi_event_decode() correctly emits a new status byte.
Fix this by decoding each message into a local buffer first and
appending timestampLow whenever the decoder actually wrote a status
byte. If the message does not fit, flush the packet and, if it relied
on running status, decode it again with a reset decoder so the new
packet starts with a full status byte. This replaces the -ENOMEM retry
path.
Closes: https://github.com/bluez/bluez/issues/2630
Assisted-by: Claude:claude-opus-5-5
---
profiles/midi/libmidi.c | 59 +++++++++++++++++++++++------------------
1 file changed, 33 insertions(+), 26 deletions(-)
diff --git a/profiles/midi/libmidi.c b/profiles/midi/libmidi.c
index ef33f7dfd..349f5f7b2 100644
--- a/profiles/midi/libmidi.c
+++ b/profiles/midi/libmidi.c
@@ -191,40 +191,47 @@ static void read_ev_sysex(struct midi_write_parser *parser, const snd_seq_event_
static void read_ev_others(struct midi_write_parser *parser, const snd_seq_event_t *ev,
midi_read_ev_cb write_cb, void *user_data)
{
- int length;
+ uint8_t msg[MIDI_MSG_MAX_SIZE];
+ long length;
+ bool status;
- /* check for running status */
- if (parser->rstatus != ev->type) {
+ /* running status only ever continues the same event type */
+ if (parser->rstatus != ev->type)
snd_midi_event_reset_decode(parser->midi_ev);
- append_timestamp_low(parser);
- }
- /* each midi message has timestampLow byte to follow */
- length = snd_midi_event_decode(parser->midi_ev,
- parser->midi_stream.data +
- parser->midi_stream.len,
- parser_get_available_size(parser),
- ev);
-
- if (length == -ENOMEM) {
- /* remove previously added timestampLow */
- if (parser->rstatus != ev->type)
- parser->midi_stream.len--;
+ length = snd_midi_event_decode(parser->midi_ev, msg, sizeof(msg), ev);
+ if (length <= 0)
+ return;
+
+ /*
+ * The decoder leaves the status byte out only when it repeats the
+ * previous one, channel included, and every status byte it does write
+ * needs a timestampLow before it. Decide that from the decoded message,
+ * not from the event type: two note-ons on different channels share a
+ * type but not a status byte.
+ */
+ status = msg[0] & 0x80;
+
+ if (parser_get_available_size(parser) < (size_t) length + status) {
write_cb(parser, user_data);
- /* cleanup state for next packet */
- snd_midi_event_reset_decode(parser->midi_ev);
midi_write_reset(parser);
append_timestamp_high_maybe(parser);
- append_timestamp_low(parser);
- length = snd_midi_event_decode(parser->midi_ev,
- parser->midi_stream.data +
- parser->midi_stream.len,
- parser_get_available_size(parser),
- ev);
+
+ /* a new packet cannot continue running status */
+ if (!status) {
+ snd_midi_event_reset_decode(parser->midi_ev);
+ length = snd_midi_event_decode(parser->midi_ev, msg,
+ sizeof(msg), ev);
+ if (length <= 0)
+ return;
+ status = true;
+ }
}
- if (length > 0)
- parser->midi_stream.len += length;
+ if (status)
+ append_timestamp_low(parser);
+
+ buffer_append_data(&parser->midi_stream, msg, length);
}
void midi_read_ev(struct midi_write_parser *parser, const snd_seq_event_t *ev,
--
2.43.0
next reply other threads:[~2026-11-02 22:12 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-02 22:12 RasmusML [this message]
2026-11-02 22:12 ` [PATCH BlueZ 2/2] unit/test-midi: Add multi-channel writer test RasmusML
2026-11-03 1:57 ` [BlueZ,1/2] profiles/midi: Fix running status across channels bluez.test.bot
2026-11-03 16:30 ` [PATCH BlueZ 1/2] " patchwork-bot+bluetooth
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261102221223.16079-1-rasmusml0501@gmail.com \
--to=rasmusml0501@gmail.com \
--cc=linux-bluetooth@vger.kernel.org \
--cc=rasmusml@hotmail.dk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox