* [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