[PATCH 3/4] ALSA: ump: Fix RPN/NRPN state handling at MIDI 1.0 CC conversion
From: HyeongJun An
Date: Sun Nov 08 2026 - 07:50:28 EST
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@xxxxxxxxxxxxxxx>
Link: https://lore.kernel.org/all/20261104121508.3972314-1-sammiee5311@xxxxxxxxx/
Signed-off-by: HyeongJun An <sammiee5311@xxxxxxxxx>
---
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