@@ -169,20 +169,31 @@ void MPEZoneLayout::updatePerNotePitchbendRange (MPEZone& zone, int value)
169169
170170void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn)
171171{
172+ // When the range is specified using both MSB and LSB, then MSB corresponds to whole semitones
173+ // and LSB corresponds to cents.
174+ const auto range = rpn.is14BitValue
175+ ? std::div (rpn.value , 128 )
176+ : div_t { rpn.value , 0 };
177+
178+ // If this is hit, the requested pitchbend range is not a whole number of semitones.
179+ // This isn't currently supported by JUCE - adding support would require
180+ // public API updates.
181+ jassert (range.rem == 0 );
182+
172183 if (rpn.channel == 1 )
173184 {
174- updateMasterPitchbend (lowerZone, rpn. value );
185+ updateMasterPitchbend (lowerZone, range. quot );
175186 }
176187 else if (rpn.channel == 16 )
177188 {
178- updateMasterPitchbend (upperZone, rpn. value );
189+ updateMasterPitchbend (upperZone, range. quot );
179190 }
180191 else
181192 {
182193 if (lowerZone.isUsingChannelAsMemberChannel (rpn.channel ))
183- updatePerNotePitchbendRange (lowerZone, rpn. value );
194+ updatePerNotePitchbendRange (lowerZone, range. quot );
184195 else if (upperZone.isUsingChannelAsMemberChannel (rpn.channel ))
185- updatePerNotePitchbendRange (upperZone, rpn. value );
196+ updatePerNotePitchbendRange (upperZone, range. quot );
186197 }
187198}
188199
@@ -424,6 +435,33 @@ class MPEZoneLayoutTests final : public UnitTest
424435
425436 expectEquals (layout.getLowerZone ().masterPitchbendRange , newPitchBend);
426437 }
438+
439+ beginTest (" process 14-bit pitch bend sensitivity" );
440+ {
441+ MPEZoneLayout layout;
442+ layout.setLowerZone (15 );
443+ expect (layout.getLowerZone ().isActive ());
444+
445+ constexpr auto masterPitchBendA = 0x60 ;
446+
447+ // LSB first
448+ layout.processNextMidiEvent ({ 0xb0 , 0x64 , 0x00 }); // RPN part 1
449+ layout.processNextMidiEvent ({ 0xb0 , 0x65 , 0x00 }); // PRN part 2
450+ layout.processNextMidiEvent ({ 0xb0 , 0x26 , 0x00 }); // pitch bend cents
451+ layout.processNextMidiEvent ({ 0xb0 , 0x06 , masterPitchBendA }); // pitch bend semis
452+
453+ expectEquals (layout.getLowerZone ().masterPitchbendRange , masterPitchBendA);
454+
455+ constexpr auto masterPitchBendB = 0x50 ;
456+
457+ // MSB first
458+ layout.processNextMidiEvent ({ 0xb0 , 0x64 , 0x00 }); // RPN part 1
459+ layout.processNextMidiEvent ({ 0xb0 , 0x65 , 0x00 }); // PRN part 2
460+ layout.processNextMidiEvent ({ 0xb0 , 0x06 , masterPitchBendB }); // pitch bend semis
461+ layout.processNextMidiEvent ({ 0xb0 , 0x26 , 0x00 }); // pitch bend cents
462+
463+ expectEquals (layout.getLowerZone ().masterPitchbendRange , masterPitchBendB);
464+ }
427465 }
428466};
429467
0 commit comments