From f4ba4c1ad93c3e4da5d8940077d6b9bb4534da8e Mon Sep 17 00:00:00 2001 From: reuk Date: Tue, 6 Jan 2026 13:09:26 +0000 Subject: [PATCH] MPEZoneLayout: Correctly handle 14-bit pitch-bend ranges Previously, the MPEZoneLayout could only handle pitch-bend range adjustments that ended with the MSB. If the final controller message was the LSB, this resulted in the range being set as a 14-bit value, with a value 128 times higher than intended. --- .../mpe/juce_MPEZoneLayout.cpp | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp b/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp index c9a909ce70..044c337386 100644 --- a/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp +++ b/modules/juce_audio_basics/mpe/juce_MPEZoneLayout.cpp @@ -169,20 +169,31 @@ void MPEZoneLayout::updatePerNotePitchbendRange (MPEZone& zone, int value) void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn) { + // When the range is specified using both MSB and LSB, then MSB corresponds to whole semitones + // and LSB corresponds to cents. + const auto range = rpn.is14BitValue + ? std::div (rpn.value, 128) + : div_t { rpn.value, 0 }; + + // If this is hit, the requested pitchbend range is not a whole number of semitones. + // This isn't currently supported by JUCE - adding support would require + // public API updates. + jassert (range.rem == 0); + if (rpn.channel == 1) { - updateMasterPitchbend (lowerZone, rpn.value); + updateMasterPitchbend (lowerZone, range.quot); } else if (rpn.channel == 16) { - updateMasterPitchbend (upperZone, rpn.value); + updateMasterPitchbend (upperZone, range.quot); } else { if (lowerZone.isUsingChannelAsMemberChannel (rpn.channel)) - updatePerNotePitchbendRange (lowerZone, rpn.value); + updatePerNotePitchbendRange (lowerZone, range.quot); else if (upperZone.isUsingChannelAsMemberChannel (rpn.channel)) - updatePerNotePitchbendRange (upperZone, rpn.value); + updatePerNotePitchbendRange (upperZone, range.quot); } } @@ -424,6 +435,33 @@ public: expectEquals (layout.getLowerZone().masterPitchbendRange, newPitchBend); } + + beginTest ("process 14-bit pitch bend sensitivity"); + { + MPEZoneLayout layout; + layout.setLowerZone (15); + expect (layout.getLowerZone().isActive()); + + constexpr auto masterPitchBendA = 0x60; + + // LSB first + layout.processNextMidiEvent ({ 0xb0, 0x64, 0x00 }); // RPN part 1 + layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 }); // PRN part 2 + layout.processNextMidiEvent ({ 0xb0, 0x26, 0x00 }); // pitch bend cents + layout.processNextMidiEvent ({ 0xb0, 0x06, masterPitchBendA }); // pitch bend semis + + expectEquals (layout.getLowerZone().masterPitchbendRange, masterPitchBendA); + + constexpr auto masterPitchBendB = 0x50; + + // MSB first + layout.processNextMidiEvent ({ 0xb0, 0x64, 0x00 }); // RPN part 1 + layout.processNextMidiEvent ({ 0xb0, 0x65, 0x00 }); // PRN part 2 + layout.processNextMidiEvent ({ 0xb0, 0x06, masterPitchBendB }); // pitch bend semis + layout.processNextMidiEvent ({ 0xb0, 0x26, 0x00 }); // pitch bend cents + + expectEquals (layout.getLowerZone().masterPitchbendRange, masterPitchBendB); + } } };