mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/4] ALSA: ump: Fix RPN/NRPN handling at MIDI 1.0 CC conversion
@ 2026-11-08 12:47 HyeongJun An
  2026-11-08 12:47 ` [PATCH 1/4] ALSA: ump: Clear stale RPN selector on NRPN change HyeongJun An
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: HyeongJun An @ 2026-11-08 12:47 UTC (permalink / raw)
  To: Takashi Iwai
  Cc: Jaroslav Kysela, Alyandra Zahran Nadhir, linux-sound,
	linux-kernel, HyeongJun An

Hello Takashi,

As you asked in [1], this puts Alyandra's selector fix and my fixes for
the other RPN/NRPN cases into one series, for both the UMP core
converter and the sequencer.

Patches 1 and 2 are Alyandra's [2], without the KUnit test. Selecting
an RPN now clears the NRPN selection and vice versa. Patches 3 and 4
keep the selection after a send and clear only the data, drop data when
nothing is selected, and handle a Null NRPN like a Null RPN.

What a MIDI 2.0 receiver gets, before and after the series:

  Input                                    Before             After
  Null RPN, Data Entry, select RPN 0/1     RPN 0/0 = 0x40     nothing
  Data Entry, select RPN 0/1               RPN 0/0 = 0x40     nothing
  RPN 0/2 + data, data again, RPN 0/1      0/2, then 0/0      0/2, 0/2
  Null RPN, Data Entry, select NRPN 0/5    NRPN 0/0 = 0x40    nothing
  select RPN 0/0, NRPN 1/2 + data          RPN 0/0            NRPN 1/2
  Null NRPN, Data Entry                    NRPN 127/127       nothing
  select RPN 0/127, Null RPN, data         RPN 0/127          nothing
  RPN 0/1 + data, CC 38, select RPN 0/2    0/1, then 0/0      0/1, 0/1
  RPN 1/1 + data, RPN LSB 2, data          1/1, then 0/2      1/1, 1/2
  Null RPN, RPN MSB 5, data                RPN 5/0            RPN 5/127
  RPN 0/1 + data (unchanged)               RPN 0/1            RPN 0/1

The 0/127 row and the last two changes come from holding the RPN/NRPN
numbers, as a MIDI 1.0 receiver does.

I checked the core converter with a local KUnit test over 35 input
sequences. The 17 that changed are all of the kinds above, and the
others give the same packets as before. The sequencer side was run
with a legacy client sending CC and CONTROL14 events to a MIDI 2.0
client on a patched kernel under qemu. That test will become the
selftest, with Alyandra's cases added, as a separate patch.

Two cases are left as they were. A CC 6 alone waits for a CC 38 or
the next CC 98 to 101, so a second CC 6 replaces the first instead of
sending it. A CC 38 alone after a send waits the same way, and when
the next CC 98 to 101 flushes it, it still goes out with MSB 0, now
under the kept selection. Changing those would be a separate patch.

[1] https://lore.kernel.org/all/878q33y4z4.wl-tiwai@suse.de/
[2] https://lore.kernel.org/all/20261107111056.667-1-alyandra5b@gmail.com/

Prepared with Claude Code (Claude Opus 5.5). Tested on x86_64.

Best regards,
HyeongJun An

Alyandra Zahran Nadhir (2):
  ALSA: ump: Clear stale RPN selector on NRPN change
  ALSA: seq: ump: Clear stale selectors in sequencer conversion

HyeongJun An (2):
  ALSA: ump: Fix RPN/NRPN state handling at MIDI 1.0 CC conversion
  ALSA: seq: ump: Fix RPN/NRPN state handling at MIDI 1.0 CC conversion

 sound/core/seq/seq_ump_convert.c | 32 ++++++++++++++++++++++++++++----
 sound/core/ump_convert.c         | 28 ++++++++++++++++++++++++----
 2 files changed, 52 insertions(+), 8 deletions(-)

-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/4] ALSA: ump: Clear stale RPN selector on NRPN change
  2026-11-08 12:47 [PATCH 0/4] ALSA: ump: Fix RPN/NRPN handling at MIDI 1.0 CC conversion HyeongJun An
@ 2026-11-08 12:47 ` HyeongJun An
  2026-11-08 12:47 ` [PATCH 2/4] ALSA: seq: ump: Clear stale selectors in sequencer conversion HyeongJun An
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: HyeongJun An @ 2026-11-08 12:47 UTC (permalink / raw)
  To: Takashi Iwai
  Cc: Jaroslav Kysela, Alyandra Zahran Nadhir, linux-sound,
	linux-kernel, stable, HyeongJun An

From: Alyandra Zahran Nadhir <alyandra5b@gmail.com>

The legacy-to-UMP converter keeps separate RPN and NRPN selector
flags. When a stream changes selector type without sending data first,
both flags remain set and fill_rpn() gives RPN priority, so subsequent
NRPN data is emitted as RPN.

Clear the previous selector flag after flushing any pending parameter
data and before recording the newly selected parameter type.

Fixes: 0b5288f5fe63 ("ALSA: ump: Add legacy raw MIDI support")
Cc: <stable@vger.kernel.org>
Signed-off-by: Alyandra Zahran Nadhir <alyandra5b@gmail.com>
[HyeongJun An: capitalize the subject, add Fixes and stable Cc]
Signed-off-by: HyeongJun An <sammiee5311@gmail.com>
---
 sound/core/ump_convert.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c
index ecbd894f4c83..fe7d3ee3c6b1 100644
--- a/sound/core/ump_convert.c
+++ b/sound/core/ump_convert.c
@@ -378,6 +378,7 @@ static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt,
 		switch (buf[1]) {
 		case UMP_CC_RPN_MSB:
 			ret = fill_rpn(cc, midi2, true);
+			cc->nrpn_set = 0;
 			cc->rpn_set = 1;
 			cc->cc_rpn_msb = buf[2];
 			if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
@@ -385,6 +386,7 @@ static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt,
 			return ret;
 		case UMP_CC_RPN_LSB:
 			ret = fill_rpn(cc, midi2, true);
+			cc->nrpn_set = 0;
 			cc->rpn_set = 1;
 			cc->cc_rpn_lsb = buf[2];
 			if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
@@ -392,11 +394,13 @@ static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt,
 			return ret;
 		case UMP_CC_NRPN_MSB:
 			ret = fill_rpn(cc, midi2, true);
+			cc->rpn_set = 0;
 			cc->nrpn_set = 1;
 			cc->cc_nrpn_msb = buf[2];
 			return ret;
 		case UMP_CC_NRPN_LSB:
 			ret = fill_rpn(cc, midi2, true);
+			cc->rpn_set = 0;
 			cc->nrpn_set = 1;
 			cc->cc_nrpn_lsb = buf[2];
 			return ret;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 2/4] ALSA: seq: ump: Clear stale selectors in sequencer conversion
  2026-11-08 12:47 [PATCH 0/4] ALSA: ump: Fix RPN/NRPN handling at MIDI 1.0 CC conversion HyeongJun An
  2026-11-08 12:47 ` [PATCH 1/4] ALSA: ump: Clear stale RPN selector on NRPN change HyeongJun An
@ 2026-11-08 12:47 ` HyeongJun An
  2026-11-08 12:47 ` [PATCH 3/4] ALSA: ump: Fix RPN/NRPN state handling at MIDI 1.0 CC conversion HyeongJun An
  2026-11-08 12:47 ` [PATCH 4/4] ALSA: seq: " HyeongJun An
  3 siblings, 0 replies; 5+ messages in thread
From: HyeongJun An @ 2026-11-08 12:47 UTC (permalink / raw)
  To: Takashi Iwai
  Cc: Jaroslav Kysela, Alyandra Zahran Nadhir, linux-sound,
	linux-kernel, stable, HyeongJun An

From: Alyandra Zahran Nadhir <alyandra5b@gmail.com>

The sequencer MIDI 2.0 converter tracks RPN and NRPN selection flags
separately. When the controller stream switches selector type, the
previous flag remains set; fill_rpn() checks RPN first and can encode
subsequent NRPN data as RPN.

Flush pending parameter data with the old selector, then clear the
opposite selector before recording new state in both CC and Control14
event paths.

Fixes: e9e02819a98a ("ALSA: seq: Automatic conversion of UMP events")
Cc: <stable@vger.kernel.org>
Signed-off-by: Alyandra Zahran Nadhir <alyandra5b@gmail.com>
[HyeongJun An: drop the KUnit test, fix the subject, add Fixes and stable Cc]
Signed-off-by: HyeongJun An <sammiee5311@gmail.com>
---
 sound/core/seq/seq_ump_convert.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c
index ff4ee26adad1..bac37d4ef43c 100644
--- a/sound/core/seq/seq_ump_convert.c
+++ b/sound/core/seq/seq_ump_convert.c
@@ -847,6 +847,7 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event,
 	switch (index) {
 	case UMP_CC_RPN_MSB:
 		ret = fill_rpn(cc, data, channel, true);
+		cc->nrpn_set = 0;
 		cc->rpn_set = 1;
 		cc->cc_rpn_msb = val;
 		if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
@@ -854,6 +855,7 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event,
 		break;
 	case UMP_CC_RPN_LSB:
 		ret = fill_rpn(cc, data, channel, true);
+		cc->nrpn_set = 0;
 		cc->rpn_set = 1;
 		cc->cc_rpn_lsb = val;
 		if (cc->cc_rpn_msb == 0x7f && cc->cc_rpn_lsb == 0x7f)
@@ -861,11 +863,13 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event,
 		break;
 	case UMP_CC_NRPN_MSB:
 		ret = fill_rpn(cc, data, channel, true);
+		cc->rpn_set = 0;
 		cc->nrpn_set = 1;
 		cc->cc_nrpn_msb = val;
 		break;
 	case UMP_CC_NRPN_LSB:
 		ret = fill_rpn(cc, data, channel, true);
+		cc->rpn_set = 0;
 		cc->nrpn_set = 1;
 		cc->cc_nrpn_lsb = val;
 		break;
@@ -975,6 +979,7 @@ static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event,
 	case UMP_CC_RPN_MSB:
 	case UMP_CC_RPN_LSB:
 		ret = fill_rpn(cc, data, channel, true);
+		cc->nrpn_set = 0;
 		cc->cc_rpn_msb = msb;
 		cc->cc_rpn_lsb = lsb;
 		cc->rpn_set = 1;
@@ -984,6 +989,7 @@ static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event,
 	case UMP_CC_NRPN_MSB:
 	case UMP_CC_NRPN_LSB:
 		ret = fill_rpn(cc, data, channel, true);
+		cc->rpn_set = 0;
 		cc->cc_nrpn_msb = msb;
 		cc->nrpn_set = 1;
 		cc->cc_nrpn_lsb = lsb;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 3/4] ALSA: ump: Fix RPN/NRPN state handling at MIDI 1.0 CC conversion
  2026-11-08 12:47 [PATCH 0/4] ALSA: ump: Fix RPN/NRPN handling at MIDI 1.0 CC conversion HyeongJun An
  2026-11-08 12:47 ` [PATCH 1/4] ALSA: ump: Clear stale RPN selector on NRPN change HyeongJun An
  2026-11-08 12:47 ` [PATCH 2/4] ALSA: seq: ump: Clear stale selectors in sequencer conversion HyeongJun An
@ 2026-11-08 12:47 ` HyeongJun An
  2026-11-08 12:47 ` [PATCH 4/4] ALSA: seq: " HyeongJun An
  3 siblings, 0 replies; 5+ messages in thread
From: HyeongJun An @ 2026-11-08 12:47 UTC (permalink / raw)
  To: Takashi Iwai
  Cc: Jaroslav Kysela, Alyandra Zahran Nadhir, linux-sound,
	linux-kernel, HyeongJun An, stable

The MIDI 1.0 to UMP converter keeps the RPN/NRPN selection and the
Data Entry values per channel. Since commit e6ce8a28c768 ("ALSA: ump:
Transmit RPN/NRPN message at each MSB/LSB data reception"), a CC 98 to
101 also sends the pending data. The state handling has a few problems:

- Data Entry after a Null RPN, or before any selection, stays pending.
  The next RPN/NRPN selection then sends it, with the number only
  half updated.
- The fill_rpn() clears the selection after each send. A second Data
  Entry for the same RPN is not sent and ends up as above.
- A Null NRPN is not handled, and its data goes to NRPN 127/127.

Keep the selection after a send and clear only the data, and drop the
data when nothing is selected. On a Null RPN or NRPN, clear the
selection and the data but keep the 7f/7f numbers, as a MIDI 1.0
receiver does. Commit 50a6dd19dca9 ("ALSA: ump: Explicitly reset RPN
with Null RPN") reset them to 0, so when the LSB was already 127, the
second message of a Null selected RPN 0/127 again. This follows the UMP
spec (M2-104-UM D.3.3), which holds the latest RPN/NRPN numbers and does
not translate an RPN/NRPN Null.

Fixes: 0b5288f5fe63 ("ALSA: ump: Add legacy raw MIDI support")
Fixes: e6ce8a28c768 ("ALSA: ump: Transmit RPN/NRPN message at each MSB/LSB data reception")
Cc: <stable@vger.kernel.org>
Link: https://lore.kernel.org/all/20261104121508.3972314-1-sammiee5311@gmail.com/
Signed-off-by: HyeongJun An <sammiee5311@gmail.com>
---
 sound/core/ump_convert.c | 24 ++++++++++++++++++++----
 1 file changed, 20 insertions(+), 4 deletions(-)

diff --git a/sound/core/ump_convert.c b/sound/core/ump_convert.c
index fe7d3ee3c6b1..2f19efdc31fc 100644
--- a/sound/core/ump_convert.c
+++ b/sound/core/ump_convert.c
@@ -288,13 +288,22 @@ static int cvt_legacy_system_to_ump(struct ump_cvt_to_ump *cvt,
 	return 4;
 }
 
+static void reset_rpn_data(struct ump_cvt_to_ump_bank *cc)
+{
+	cc->cc_data_msb = cc->cc_data_lsb = 0;
+	cc->cc_data_msb_set = cc->cc_data_lsb_set = 0;
+}
+
+/*
+ * Deselect on a Null RPN/NRPN, but keep the 7f/7f numbers as a MIDI 1.0
+ * receiver does. Otherwise, when the LSB was already 127, the second
+ * message of a Null would select RPN or NRPN 0/127.
+ */
 static void reset_rpn(struct ump_cvt_to_ump_bank *cc)
 {
 	cc->rpn_set = 0;
 	cc->nrpn_set = 0;
-	cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
-	cc->cc_data_msb = cc->cc_data_lsb = 0;
-	cc->cc_data_msb_set = cc->cc_data_lsb_set = 0;
+	reset_rpn_data(cc);
 }
 
 static int fill_rpn(struct ump_cvt_to_ump_bank *cc,
@@ -316,13 +325,16 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc,
 		midi2->rpn.bank = cc->cc_nrpn_msb;
 		midi2->rpn.index = cc->cc_nrpn_lsb;
 	} else {
+		/* no RPN/NRPN selected (e.g. after a Null), drop the data */
+		reset_rpn_data(cc);
 		return 0; // skip
 	}
 
 	midi2->rpn.data = upscale_14_to_32bit((cc->cc_data_msb << 7) |
 					      cc->cc_data_lsb);
 
-	reset_rpn(cc);
+	/* keep the RPN/NRPN selection for a following Data Entry */
+	reset_rpn_data(cc);
 	return 8;
 }
 
@@ -397,12 +409,16 @@ static int cvt_legacy_cmd_to_ump(struct ump_cvt_to_ump *cvt,
 			cc->rpn_set = 0;
 			cc->nrpn_set = 1;
 			cc->cc_nrpn_msb = buf[2];
+			if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f)
+				reset_rpn(cc);
 			return ret;
 		case UMP_CC_NRPN_LSB:
 			ret = fill_rpn(cc, midi2, true);
 			cc->rpn_set = 0;
 			cc->nrpn_set = 1;
 			cc->cc_nrpn_lsb = buf[2];
+			if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f)
+				reset_rpn(cc);
 			return ret;
 		case UMP_CC_DATA:
 			cc->cc_data_msb_set = 1;
-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 4/4] ALSA: seq: ump: Fix RPN/NRPN state handling at MIDI 1.0 CC conversion
  2026-11-08 12:47 [PATCH 0/4] ALSA: ump: Fix RPN/NRPN handling at MIDI 1.0 CC conversion HyeongJun An
                   ` (2 preceding siblings ...)
  2026-11-08 12:47 ` [PATCH 3/4] ALSA: ump: Fix RPN/NRPN state handling at MIDI 1.0 CC conversion HyeongJun An
@ 2026-11-08 12:47 ` HyeongJun An
  3 siblings, 0 replies; 5+ messages in thread
From: HyeongJun An @ 2026-11-08 12:47 UTC (permalink / raw)
  To: Takashi Iwai
  Cc: Jaroslav Kysela, Alyandra Zahran Nadhir, linux-sound,
	linux-kernel, HyeongJun An, stable

The sequencer converts MIDI 1.0 CC and CONTROL14 events to MIDI 2.0 UMP
with the same RPN/NRPN state handling as the UMP core helper. It has
the same problems:

- Data Entry without a selection is sent at the next RPN/NRPN
  selection, with the number only half updated.
- A repeated Data Entry for the same RPN is not sent right away and
  ends up as above.
- A Null NRPN is not handled.

Apply the same fix as for the UMP core helper to both the CC and the
CONTROL14 paths.

Fixes: e9e02819a98a ("ALSA: seq: Automatic conversion of UMP events")
Fixes: a4ff92ff0bdd ("ALSA: seq: ump: Transmit RPN/NRPN message at each MSB/LSB data reception")
Cc: <stable@vger.kernel.org>
Link: https://lore.kernel.org/all/20261104121508.3972314-1-sammiee5311@gmail.com/
Signed-off-by: HyeongJun An <sammiee5311@gmail.com>
---
 sound/core/seq/seq_ump_convert.c | 26 ++++++++++++++++++++++----
 1 file changed, 22 insertions(+), 4 deletions(-)

diff --git a/sound/core/seq/seq_ump_convert.c b/sound/core/seq/seq_ump_convert.c
index bac37d4ef43c..62bdef6dca58 100644
--- a/sound/core/seq/seq_ump_convert.c
+++ b/sound/core/seq/seq_ump_convert.c
@@ -790,13 +790,22 @@ static int paf_ev_to_ump_midi2(const struct snd_seq_event *event,
 	return 1;
 }
 
+static void reset_rpn_data(struct ump_cvt_to_ump_bank *cc)
+{
+	cc->cc_data_msb = cc->cc_data_lsb = 0;
+	cc->cc_data_msb_set = cc->cc_data_lsb_set = 0;
+}
+
+/*
+ * Deselect on a Null RPN/NRPN, but keep the 7f/7f numbers as a MIDI 1.0
+ * receiver does. Otherwise, when the LSB was already 127, the second
+ * message of a Null would select RPN or NRPN 0/127.
+ */
 static void reset_rpn(struct ump_cvt_to_ump_bank *cc)
 {
 	cc->rpn_set = 0;
 	cc->nrpn_set = 0;
-	cc->cc_rpn_msb = cc->cc_rpn_lsb = 0;
-	cc->cc_data_msb = cc->cc_data_lsb = 0;
-	cc->cc_data_msb_set = cc->cc_data_lsb_set = 0;
+	reset_rpn_data(cc);
 }
 
 /* set up the MIDI2 RPN/NRPN packet data from the parsed info */
@@ -820,6 +829,8 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc,
 		data->rpn.bank = cc->cc_nrpn_msb;
 		data->rpn.index = cc->cc_nrpn_lsb;
 	} else {
+		/* no RPN/NRPN selected (e.g. after a Null), drop the data */
+		reset_rpn_data(cc);
 		return 0; // skip
 	}
 
@@ -827,7 +838,8 @@ static int fill_rpn(struct ump_cvt_to_ump_bank *cc,
 					     cc->cc_data_lsb);
 	data->rpn.channel = channel;
 
-	reset_rpn(cc);
+	/* keep the RPN/NRPN selection for a following Data Entry */
+	reset_rpn_data(cc);
 	return 1;
 }
 
@@ -866,12 +878,16 @@ static int cc_ev_to_ump_midi2(const struct snd_seq_event *event,
 		cc->rpn_set = 0;
 		cc->nrpn_set = 1;
 		cc->cc_nrpn_msb = val;
+		if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f)
+			reset_rpn(cc);
 		break;
 	case UMP_CC_NRPN_LSB:
 		ret = fill_rpn(cc, data, channel, true);
 		cc->rpn_set = 0;
 		cc->nrpn_set = 1;
 		cc->cc_nrpn_lsb = val;
+		if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f)
+			reset_rpn(cc);
 		break;
 	case UMP_CC_DATA:
 		cc->cc_data_msb_set = 1;
@@ -993,6 +1009,8 @@ static int ctrl14_ev_to_ump_midi2(const struct snd_seq_event *event,
 		cc->cc_nrpn_msb = msb;
 		cc->nrpn_set = 1;
 		cc->cc_nrpn_lsb = lsb;
+		if (cc->cc_nrpn_msb == 0x7f && cc->cc_nrpn_lsb == 0x7f)
+			reset_rpn(cc);
 		return ret;
 	case UMP_CC_DATA:
 	case UMP_CC_DATA_LSB:
-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-11-08 12:50 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-08 12:47 [PATCH 0/4] ALSA: ump: Fix RPN/NRPN handling at MIDI 1.0 CC conversion HyeongJun An
2026-11-08 12:47 ` [PATCH 1/4] ALSA: ump: Clear stale RPN selector on NRPN change HyeongJun An
2026-11-08 12:47 ` [PATCH 2/4] ALSA: seq: ump: Clear stale selectors in sequencer conversion HyeongJun An
2026-11-08 12:47 ` [PATCH 3/4] ALSA: ump: Fix RPN/NRPN state handling at MIDI 1.0 CC conversion HyeongJun An
2026-11-08 12:47 ` [PATCH 4/4] ALSA: seq: " HyeongJun An

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®