Linux bluetooth development
 help / color / mirror / Atom feed
* [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