* [PATCH BlueZ 1/2] profiles/midi: Fix running status across channels
@ 2026-11-02 22:12 RasmusML
2026-11-02 22:12 ` [PATCH BlueZ 2/2] unit/test-midi: Add multi-channel writer test RasmusML
` (2 more replies)
0 siblings, 3 replies; 4+ messages in thread
From: RasmusML @ 2026-11-02 22:12 UTC (permalink / raw)
To: linux-bluetooth; +Cc: RasmusML
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
^ permalink raw reply related [flat|nested] 4+ messages in thread
* [PATCH BlueZ 2/2] unit/test-midi: Add multi-channel writer test
2026-11-02 22:12 [PATCH BlueZ 1/2] profiles/midi: Fix running status across channels RasmusML
@ 2026-11-02 22:12 ` 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
2 siblings, 0 replies; 4+ messages in thread
From: RasmusML @ 2026-11-02 22:12 UTC (permalink / raw)
To: linux-bluetooth; +Cc: RasmusML
From: RasmusML <rasmusml@hotmail.dk>
Add a writer test with messages of the same type on different channels
for Note On, Note Off, Control Change and Pitch Bend. Each of these
needs its own status byte preceded by timestampLow, and running status
must only be used when the channel matches too.
The existing writer tests use a single channel for every event, which
is why the missing timestampLow was not caught before.
Assisted-by: Claude:claude-opus-5-5
---
unit/test-midi.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/unit/test-midi.c b/unit/test-midi.c
index 702133e59..79cb03123 100644
--- a/unit/test-midi.c
+++ b/unit/test-midi.c
@@ -525,6 +525,24 @@ static const snd_seq_event_t event5_expect[] = {
static const struct midi_write_test midi5 = BLE_WRITE_TEST_INIT(event5, event5_expect);
+/*
+ * Same event type on different channels: each needs its own status byte and
+ * timestampLow, so running status must not carry over between them.
+ */
+static const snd_seq_event_t event6[] = {
+ NOTE_EVENT(NOTEON, 0, 60, 100), /* Note On */
+ NOTE_EVENT(NOTEON, 1, 64, 100), /* Note On, other channel */
+ NOTE_EVENT(NOTEON, 1, 67, 100), /* Note On, running status */
+ NOTE_EVENT(NOTEOFF, 0, 60, 0), /* Note Off */
+ NOTE_EVENT(NOTEOFF, 1, 64, 0), /* Note Off, other channel */
+ CONTROL_EVENT(CONTROLLER, 2, 0, 123), /* All Notes Off */
+ CONTROL_EVENT(CONTROLLER, 3, 0, 123), /* All Notes Off, other channel */
+ CONTROL_EVENT(PITCHBEND, 4, 0, 0), /* Pitch Bend */
+ CONTROL_EVENT(PITCHBEND, 5, 0, 0), /* Pitch Bend, other channel */
+};
+
+static const struct midi_write_test midi6 = BLE_WRITE_TEST_INIT_BASIC(event6);
+
struct midi_data {
size_t events_tested;
const struct midi_write_test *midi_test;
@@ -618,6 +636,8 @@ int main(int argc, char *argv[])
&midi4, NULL, test_midi_writer, NULL);
tester_add("Split ALSA SysEx events to raw BLE packets",
&midi5, NULL, test_midi_writer, NULL);
+ tester_add("ALSA Seq events on several channels to Raw BLE packets",
+ &midi6, NULL, test_midi_writer, NULL);
return tester_run();
}
--
2.43.0
^ permalink raw reply related [flat|nested] 4+ messages in thread
* RE: [BlueZ,1/2] profiles/midi: Fix running status across channels
2026-11-02 22:12 [PATCH BlueZ 1/2] profiles/midi: Fix running status across channels RasmusML
2026-11-02 22:12 ` [PATCH BlueZ 2/2] unit/test-midi: Add multi-channel writer test RasmusML
@ 2026-11-03 1:57 ` bluez.test.bot
2026-11-03 16:30 ` [PATCH BlueZ 1/2] " patchwork-bot+bluetooth
2 siblings, 0 replies; 4+ messages in thread
From: bluez.test.bot @ 2026-11-03 1:57 UTC (permalink / raw)
To: linux-bluetooth, rasmusml0501
[-- Attachment #1: Type: text/plain, Size: 1020 bytes --]
This is automated email and please do not reply to this email!
Dear submitter,
Thank you for submitting the patches to the linux bluetooth mailing list.
This is a CI test results with your patch series:
PW Link:https://patchwork.kernel.org/series/1199554/
---Test result---
Test Summary:
CheckPatch PASS 0.73 seconds
GitLint PASS 0.51 seconds
BuildEll PASS 14.55 seconds
BluezMake PASS 193.26 seconds
MakeCheck PASS 13.40 seconds
MakeDistcheck PASS 110.90 seconds
CheckValgrind PASS 168.74 seconds
CheckSmatch PASS 192.40 seconds
bluezmakeextell PASS 67.73 seconds
TestFunctional PASS 846.42 seconds
IncrementalBuild PASS 185.30 seconds
ScanBuild PASS 646.37 seconds
https://github.com/bluez/bluez/pull/2750
---
Regards,
Linux Bluetooth
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [PATCH BlueZ 1/2] profiles/midi: Fix running status across channels
2026-11-02 22:12 [PATCH BlueZ 1/2] profiles/midi: Fix running status across channels RasmusML
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 ` patchwork-bot+bluetooth
2 siblings, 0 replies; 4+ messages in thread
From: patchwork-bot+bluetooth @ 2026-11-03 16:30 UTC (permalink / raw)
To: RasmusML; +Cc: linux-bluetooth, rasmusml
Hello:
This series was applied to bluetooth/bluez.git (master)
by Luiz Augusto von Dentz <luiz.von.dentz@intel.com>:
On Mon, 2 Nov 2026 23:12:22 +0100 you wrote:
> 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.:
>
> [...]
Here is the summary with links:
- [BlueZ,1/2] profiles/midi: Fix running status across channels
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=737e84da251a
- [BlueZ,2/2] unit/test-midi: Add multi-channel writer test
https://git.kernel.org/pub/scm/bluetooth/bluez.git/?id=ae27e4a2baf4
You are awesome, thank you!
--
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-11-03 16:30 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-02 22:12 [PATCH BlueZ 1/2] profiles/midi: Fix running status across channels RasmusML
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
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox