OpenShot Library | libopenshot-audio 0.2.0
juce_MPEInstrument.cpp
1/*
2 ==============================================================================
3
4 This file is part of the JUCE library.
5 Copyright (c) 2017 - ROLI Ltd.
6
7 JUCE is an open source library subject to commercial or open-source
8 licensing.
9
10 The code included in this file is provided under the terms of the ISC license
11 http://www.isc.org/downloads/software-support-policy/isc-license. Permission
12 To use, copy, modify, and/or distribute this software for any purpose with or
13 without fee is hereby granted provided that the above copyright notice and
14 this permission notice appear in all copies.
15
16 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
17 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
18 DISCLAIMED.
19
20 ==============================================================================
21*/
22
23namespace juce
24{
25
26namespace
27{
28 const uint8 noLSBValueReceived = 0xff;
29 const Range<int> allChannels { 1, 17 };
30}
31
32//==============================================================================
34{
35 std::fill_n (lastPressureLowerBitReceivedOnChannel, 16, noLSBValueReceived);
36 std::fill_n (lastTimbreLowerBitReceivedOnChannel, 16, noLSBValueReceived);
37 std::fill_n (isMemberChannelSustained, 16, false);
38
39 pitchbendDimension.value = &MPENote::pitchbend;
40 pressureDimension.value = &MPENote::pressure;
41 timbreDimension.value = &MPENote::timbre;
42
43 // the default value for pressure is 0, for all other dimension it is centre (= default MPEValue)
44 std::fill_n (pressureDimension.lastValueReceivedOnChannel, 16, MPEValue::minValue());
45
46 legacyMode.isEnabled = false;
47 legacyMode.pitchbendRange = 2;
48 legacyMode.channelRange = allChannels;
49}
50
54
55//==============================================================================
60
62{
64
65 const ScopedLock sl (lock);
66 legacyMode.isEnabled = false;
67 zoneLayout = newLayout;
68}
69
70//==============================================================================
71void MPEInstrument::enableLegacyMode (int pitchbendRange, Range<int> channelRange)
72{
74
75 const ScopedLock sl (lock);
76 legacyMode.isEnabled = true;
77 legacyMode.pitchbendRange = pitchbendRange;
78 legacyMode.channelRange = channelRange;
79 zoneLayout.clearAllZones();
80}
81
83{
84 return legacyMode.isEnabled;
85}
86
88{
89 return legacyMode.channelRange;
90}
91
93{
94 jassert (allChannels.contains (channelRange));
95
97 const ScopedLock sl (lock);
98 legacyMode.channelRange = channelRange;
99}
100
102{
103 return legacyMode.pitchbendRange;
104}
105
107{
108 jassert (pitchbendRange >= 0 && pitchbendRange <= 96);
109
111 const ScopedLock sl (lock);
112 legacyMode.pitchbendRange = pitchbendRange;
113}
114
115//==============================================================================
117{
118 pressureDimension.trackingMode = modeToUse;
119}
120
122{
123 pitchbendDimension.trackingMode = modeToUse;
124}
125
127{
128 timbreDimension.trackingMode = modeToUse;
129}
130
131//==============================================================================
136
141
142//==============================================================================
144{
145 zoneLayout.processNextMidiEvent (message);
146
147 if (message.isNoteOn (true)) processMidiNoteOnMessage (message);
148 else if (message.isNoteOff (false)) processMidiNoteOffMessage (message);
149 else if (message.isResetAllControllers()
150 || message.isAllNotesOff()) processMidiResetAllControllersMessage (message);
151 else if (message.isPitchWheel()) processMidiPitchWheelMessage (message);
152 else if (message.isChannelPressure()) processMidiChannelPressureMessage (message);
153 else if (message.isController()) processMidiControllerMessage (message);
154}
155
156//==============================================================================
157void MPEInstrument::processMidiNoteOnMessage (const MidiMessage& message)
158{
159 // Note: If a note-on with velocity = 0 is used to convey a note-off,
160 // then the actual note-off velocity is not known. In this case,
161 // the MPE convention is to use note-off velocity = 64.
162
163 if (message.getVelocity() == 0)
164 {
165 noteOff (message.getChannel(),
166 message.getNoteNumber(),
168 }
169 else
170 {
171 noteOn (message.getChannel(),
172 message.getNoteNumber(),
174 }
175}
176
177//==============================================================================
178void MPEInstrument::processMidiNoteOffMessage (const MidiMessage& message)
179{
180 noteOff (message.getChannel(),
181 message.getNoteNumber(),
182 MPEValue::from7BitInt (message.getVelocity()));
183}
184
185//==============================================================================
186void MPEInstrument::processMidiPitchWheelMessage (const MidiMessage& message)
187{
188 pitchbend (message.getChannel(),
189 MPEValue::from14BitInt (message.getPitchWheelValue()));
190}
191
192//==============================================================================
193void MPEInstrument::processMidiChannelPressureMessage (const MidiMessage& message)
194{
195 pressure (message.getChannel(),
196 MPEValue::from7BitInt (message.getChannelPressureValue()));
197}
198
199//==============================================================================
200void MPEInstrument::processMidiControllerMessage (const MidiMessage& message)
201{
202 switch (message.getControllerNumber())
203 {
204 case 64: sustainPedal (message.getChannel(), message.isSustainPedalOn()); break;
205 case 66: sostenutoPedal (message.getChannel(), message.isSostenutoPedalOn()); break;
206 case 70: handlePressureMSB (message.getChannel(), message.getControllerValue()); break;
207 case 74: handleTimbreMSB (message.getChannel(), message.getControllerValue()); break;
208 case 102: handlePressureLSB (message.getChannel(), message.getControllerValue()); break;
209 case 106: handleTimbreLSB (message.getChannel(), message.getControllerValue()); break;
210 default: break;
211 }
212}
213
214//==============================================================================
215void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& message)
216{
217 // in MPE mode, "reset all controllers" is per-zone and expected on the master channel;
218 // in legacy mode, it is per MIDI channel (within the channel range used).
219
220 if (legacyMode.isEnabled && legacyMode.channelRange.contains (message.getChannel()))
221 {
222 for (auto i = notes.size(); --i >= 0;)
223 {
224 auto& note = notes.getReference (i);
225
226 if (note.midiChannel == message.getChannel())
227 {
228 note.keyState = MPENote::off;
229 note.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number
230 listeners.call ([&] (Listener& l) { l.noteReleased (note); });
231 notes.remove (i);
232 }
233 }
234 }
235 else if (isMasterChannel (message.getChannel()))
236 {
237 auto zone = (message.getChannel() == 1 ? zoneLayout.getLowerZone()
238 : zoneLayout.getUpperZone());
239
240 for (auto i = notes.size(); --i >= 0;)
241 {
242 auto& note = notes.getReference (i);
243
244 if (zone.isUsingChannelAsMemberChannel (note.midiChannel))
245 {
246 note.keyState = MPENote::off;
247 note.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number
248 listeners.call ([&] (Listener& l) { l.noteReleased (note); });
249 notes.remove (i);
250 }
251 }
252 }
253}
254
255//==============================================================================
256void MPEInstrument::handlePressureMSB (int midiChannel, int value) noexcept
257{
258 auto lsb = lastPressureLowerBitReceivedOnChannel[midiChannel - 1];
259
260 pressure (midiChannel, lsb == noLSBValueReceived ? MPEValue::from7BitInt (value)
261 : MPEValue::from14BitInt (lsb + (value << 7)));
262}
263
264void MPEInstrument::handlePressureLSB (int midiChannel, int value) noexcept
265{
266 lastPressureLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value);
267}
268
269void MPEInstrument::handleTimbreMSB (int midiChannel, int value) noexcept
270{
271 auto lsb = lastTimbreLowerBitReceivedOnChannel[midiChannel - 1];
272
273 timbre (midiChannel, lsb == noLSBValueReceived ? MPEValue::from7BitInt (value)
274 : MPEValue::from14BitInt (lsb + (value << 7)));
275}
276
277void MPEInstrument::handleTimbreLSB (int midiChannel, int value) noexcept
278{
279 lastTimbreLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value);
280}
281
282//==============================================================================
283void MPEInstrument::noteOn (int midiChannel,
284 int midiNoteNumber,
286{
287 if (! isMemberChannel (midiChannel))
288 return;
289
290 MPENote newNote (midiChannel,
293 getInitialValueForNewNote (midiChannel, pitchbendDimension),
294 getInitialValueForNewNote (midiChannel, pressureDimension),
295 getInitialValueForNewNote (midiChannel, timbreDimension),
296 isMemberChannelSustained[midiChannel - 1] ? MPENote::keyDownAndSustained : MPENote::keyDown);
297
298 const ScopedLock sl (lock);
299 updateNoteTotalPitchbend (newNote);
300
301 if (auto* alreadyPlayingNote = getNotePtr (midiChannel, midiNoteNumber))
302 {
303 // pathological case: second note-on received for same note -> retrigger it
305 alreadyPlayingNote->noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number
306 listeners.call ([=] (Listener& l) { l.noteReleased (*alreadyPlayingNote); });
308 }
309
310 notes.add (newNote);
311 listeners.call ([&] (Listener& l) { l.noteAdded (newNote); });
312}
313
314//==============================================================================
315void MPEInstrument::noteOff (int midiChannel,
316 int midiNoteNumber,
318{
319 if (notes.isEmpty() || ! isMemberChannel (midiChannel))
320 return;
321
322 const ScopedLock sl (lock);
323
324 if (auto* note = getNotePtr (midiChannel, midiNoteNumber))
325 {
327 note->noteOffVelocity = midiNoteOffVelocity;
328
329 // last dimension values received for this note should not be re-used for
330 // any new notes, so reset them:
331 pressureDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::minValue();
332 pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue();
333 timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue();
334
335 if (note->keyState == MPENote::off)
336 {
337 listeners.call ([=] (Listener& l) { l.noteReleased (*note); });
338 notes.remove (note);
339 }
340 else
341 {
342 listeners.call ([=] (Listener& l) { l.noteKeyStateChanged (*note); });
343 }
344 }
345}
346
347//==============================================================================
348void MPEInstrument::pitchbend (int midiChannel, MPEValue value)
349{
350 const ScopedLock sl (lock);
351 updateDimension (midiChannel, pitchbendDimension, value);
352}
353
354void MPEInstrument::pressure (int midiChannel, MPEValue value)
355{
356 const ScopedLock sl (lock);
357 updateDimension (midiChannel, pressureDimension, value);
358}
359
360void MPEInstrument::timbre (int midiChannel, MPEValue value)
361{
362 const ScopedLock sl (lock);
363 updateDimension (midiChannel, timbreDimension, value);
364}
365
366MPEValue MPEInstrument::getInitialValueForNewNote (int midiChannel, MPEDimension& dimension) const
367{
368 if (getLastNotePlayedPtr (midiChannel) != nullptr)
369 return &dimension == &pressureDimension ? MPEValue::minValue() : MPEValue::centreValue();
370
371 return dimension.lastValueReceivedOnChannel[midiChannel - 1];
372}
373
374//==============================================================================
375void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, MPEValue value)
376{
377 dimension.lastValueReceivedOnChannel[midiChannel - 1] = value;
378
379 if (notes.isEmpty())
380 return;
381
382 if (isMemberChannel (midiChannel))
383 {
384 if (dimension.trackingMode == allNotesOnChannel)
385 {
386 for (auto i = notes.size(); --i >= 0;)
387 {
388 auto& note = notes.getReference (i);
389
390 if (note.midiChannel == midiChannel)
391 updateDimensionForNote (note, dimension, value);
392 }
393 }
394 else
395 {
396 if (auto* note = getNotePtr (midiChannel, dimension.trackingMode))
397 updateDimensionForNote (*note, dimension, value);
398 }
399 }
400 else if (isMasterChannel (midiChannel))
401 {
402 updateDimensionMaster (midiChannel == 1, dimension, value);
403 }
404}
405
406//==============================================================================
407void MPEInstrument::updateDimensionMaster (bool isLowerZone, MPEDimension& dimension, MPEValue value)
408{
409 auto zone = (isLowerZone ? zoneLayout.getLowerZone()
410 : zoneLayout.getUpperZone());
411
412 if (! zone.isActive())
413 return;
414
415 for (auto i = notes.size(); --i >= 0;)
416 {
417 auto& note = notes.getReference (i);
418
419 if (! zone.isUsingChannelAsMemberChannel (note.midiChannel))
420 continue;
421
422 if (&dimension == &pitchbendDimension)
423 {
424 // master pitchbend is a special case: we don't change the note's own pitchbend,
425 // instead we have to update its total (master + note) pitchbend.
426 updateNoteTotalPitchbend (note);
427 listeners.call ([&] (Listener& l) { l.notePitchbendChanged (note); });
428 }
429 else if (dimension.getValue (note) != value)
430 {
431 dimension.getValue (note) = value;
432 callListenersDimensionChanged (note, dimension);
433 }
434 }
435}
436
437//==============================================================================
438void MPEInstrument::updateDimensionForNote (MPENote& note, MPEDimension& dimension, MPEValue value)
439{
440 if (dimension.getValue (note) != value)
441 {
442 dimension.getValue (note) = value;
443
444 if (&dimension == &pitchbendDimension)
445 updateNoteTotalPitchbend (note);
446
447 callListenersDimensionChanged (note, dimension);
448 }
449}
450
451//==============================================================================
452void MPEInstrument::callListenersDimensionChanged (const MPENote& note, const MPEDimension& dimension)
453{
454 if (&dimension == &pressureDimension) { listeners.call ([&] (Listener& l) { l.notePressureChanged (note); }); return; }
455 if (&dimension == &timbreDimension) { listeners.call ([&] (Listener& l) { l.noteTimbreChanged (note); }); return; }
456 if (&dimension == &pitchbendDimension) { listeners.call ([&] (Listener& l) { l.notePitchbendChanged (note); }); return; }
457}
458
459//==============================================================================
460void MPEInstrument::updateNoteTotalPitchbend (MPENote& note)
461{
462 if (legacyMode.isEnabled)
463 {
464 note.totalPitchbendInSemitones = note.pitchbend.asSignedFloat() * legacyMode.pitchbendRange;
465 }
466 else
467 {
468 auto zone = zoneLayout.getLowerZone();
469
470 if (! zone.isUsingChannelAsMemberChannel (note.midiChannel))
471 {
472 if (zoneLayout.getUpperZone().isUsingChannelAsMemberChannel (note.midiChannel))
473 {
474 zone = zoneLayout.getUpperZone();
475 }
476 else
477 {
478 // this note doesn't belong to any zone!
479 jassertfalse;
480 return;
481 }
482 }
483
484 auto notePitchbendInSemitones = note.pitchbend.asSignedFloat() * zone.perNotePitchbendRange;
485
486 auto masterPitchbendInSemitones = pitchbendDimension.lastValueReceivedOnChannel[zone.getMasterChannel() - 1]
487 .asSignedFloat()
488 * zone.masterPitchbendRange;
489
490 note.totalPitchbendInSemitones = notePitchbendInSemitones + masterPitchbendInSemitones;
491 }
492}
493
494//==============================================================================
495void MPEInstrument::sustainPedal (int midiChannel, bool isDown)
496{
497 const ScopedLock sl (lock);
498 handleSustainOrSostenuto (midiChannel, isDown, false);
499}
500
501void MPEInstrument::sostenutoPedal (int midiChannel, bool isDown)
502{
503 const ScopedLock sl (lock);
504 handleSustainOrSostenuto (midiChannel, isDown, true);
505}
506
507//==============================================================================
508void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto)
509{
510 // in MPE mode, sustain/sostenuto is per-zone and expected on the master channel;
511 // in legacy mode, sustain/sostenuto is per MIDI channel (within the channel range used).
512
513 if (legacyMode.isEnabled ? (! legacyMode.channelRange.contains (midiChannel)) : (! isMasterChannel (midiChannel)))
514 return;
515
516 auto zone = (midiChannel == 1 ? zoneLayout.getLowerZone()
517 : zoneLayout.getUpperZone());
518
519 for (auto i = notes.size(); --i >= 0;)
520 {
521 auto& note = notes.getReference (i);
522
523 if (legacyMode.isEnabled ? (note.midiChannel == midiChannel) : zone.isUsingChannelAsMemberChannel (note.midiChannel))
524 {
525 if (note.keyState == MPENote::keyDown && isDown)
527 else if (note.keyState == MPENote::sustained && ! isDown)
528 note.keyState = MPENote::off;
529 else if (note.keyState == MPENote::keyDownAndSustained && ! isDown)
530 note.keyState = MPENote::keyDown;
531
532 if (note.keyState == MPENote::off)
533 {
534 listeners.call ([&] (Listener& l) { l.noteReleased (note); });
535 notes.remove (i);
536 }
537 else
538 {
539 listeners.call ([&] (Listener& l) { l.noteKeyStateChanged (note); });
540 }
541 }
542 }
543
544 if (! isSostenuto)
545 {
546 if (legacyMode.isEnabled)
547 {
548 isMemberChannelSustained[midiChannel - 1] = isDown;
549 }
550 else
551 {
552 if (zone.isLowerZone())
553 for (auto i = zone.getFirstMemberChannel(); i <= zone.getLastMemberChannel(); ++i)
554 isMemberChannelSustained[i - 1] = isDown;
555 else
556 for (auto i = zone.getFirstMemberChannel(); i >= zone.getLastMemberChannel(); --i)
557 isMemberChannelSustained[i - 1] = isDown;
558 }
559 }
560}
561
562//==============================================================================
563bool MPEInstrument::isMemberChannel (int midiChannel) noexcept
564{
565 if (legacyMode.isEnabled)
566 return legacyMode.channelRange.contains (midiChannel);
567
568 return zoneLayout.getLowerZone().isUsingChannelAsMemberChannel (midiChannel)
569 || zoneLayout.getUpperZone().isUsingChannelAsMemberChannel (midiChannel);
570}
571
572bool MPEInstrument::isMasterChannel (int midiChannel) const noexcept
573{
574 if (legacyMode.isEnabled)
575 return false;
576
577 return (midiChannel == 1 || midiChannel == 16);
578}
579//==============================================================================
581{
582 return notes.size();
583}
584
585MPENote MPEInstrument::getNote (int midiChannel, int midiNoteNumber) const noexcept
586{
587 if (auto* note = getNotePtr (midiChannel, midiNoteNumber))
588 return *note;
589
590 return {};
591}
592
593MPENote MPEInstrument::getNote (int index) const noexcept
594{
595 return notes[index];
596}
597
598//==============================================================================
599MPENote MPEInstrument::getMostRecentNote (int midiChannel) const noexcept
600{
601 if (auto* note = getLastNotePlayedPtr (midiChannel))
602 return *note;
603
604 return {};
605}
606
608{
609 for (auto i = notes.size(); --i >= 0;)
610 {
611 auto& note = notes.getReference (i);
612
613 if (note != otherThanThisNote)
614 return note;
615 }
616
617 return {};
618}
619
620//==============================================================================
621const MPENote* MPEInstrument::getNotePtr (int midiChannel, int midiNoteNumber) const noexcept
622{
623 for (int i = 0; i < notes.size(); ++i)
624 {
625 auto& note = notes.getReference (i);
626
627 if (note.midiChannel == midiChannel && note.initialNote == midiNoteNumber)
628 return &note;
629 }
630
631 return nullptr;
632}
633
634MPENote* MPEInstrument::getNotePtr (int midiChannel, int midiNoteNumber) noexcept
635{
636 return const_cast<MPENote*> (static_cast<const MPEInstrument&> (*this).getNotePtr (midiChannel, midiNoteNumber));
637}
638
639//==============================================================================
640const MPENote* MPEInstrument::getNotePtr (int midiChannel, TrackingMode mode) const noexcept
641{
642 // for the "all notes" tracking mode, this method can never possibly
643 // work because it returns 0 or 1 note but there might be more than one!
644 jassert (mode != allNotesOnChannel);
645
646 if (mode == lastNotePlayedOnChannel) return getLastNotePlayedPtr (midiChannel);
647 if (mode == lowestNoteOnChannel) return getLowestNotePtr (midiChannel);
648 if (mode == highestNoteOnChannel) return getHighestNotePtr (midiChannel);
649
650 return nullptr;
651}
652
653MPENote* MPEInstrument::getNotePtr (int midiChannel, TrackingMode mode) noexcept
654{
655 return const_cast<MPENote*> (static_cast<const MPEInstrument&> (*this).getNotePtr (midiChannel, mode));
656}
657
658//==============================================================================
659const MPENote* MPEInstrument::getLastNotePlayedPtr (int midiChannel) const noexcept
660{
661 for (auto i = notes.size(); --i >= 0;)
662 {
663 auto& note = notes.getReference (i);
664
665 if (note.midiChannel == midiChannel
666 && (note.keyState == MPENote::keyDown || note.keyState == MPENote::keyDownAndSustained))
667 return &note;
668 }
669
670 return nullptr;
671}
672
673MPENote* MPEInstrument::getLastNotePlayedPtr (int midiChannel) noexcept
674{
675 return const_cast<MPENote*> (static_cast<const MPEInstrument&> (*this).getLastNotePlayedPtr (midiChannel));
676}
677
678//==============================================================================
679const MPENote* MPEInstrument::getHighestNotePtr (int midiChannel) const noexcept
680{
681 int initialNoteMax = -1;
682 MPENote* result = nullptr;
683
684 for (auto i = notes.size(); --i >= 0;)
685 {
686 auto& note = notes.getReference (i);
687
688 if (note.midiChannel == midiChannel
689 && (note.keyState == MPENote::keyDown || note.keyState == MPENote::keyDownAndSustained)
690 && note.initialNote > initialNoteMax)
691 {
692 result = &note;
693 initialNoteMax = note.initialNote;
694 }
695 }
696
697 return result;
698}
699
700MPENote* MPEInstrument::getHighestNotePtr (int midiChannel) noexcept
701{
702 return const_cast<MPENote*> (static_cast<const MPEInstrument&> (*this).getHighestNotePtr (midiChannel));
703}
704
705const MPENote* MPEInstrument::getLowestNotePtr (int midiChannel) const noexcept
706{
707 int initialNoteMin = 128;
708 MPENote* result = nullptr;
709
710 for (auto i = notes.size(); --i >= 0;)
711 {
712 auto& note = notes.getReference (i);
713
714 if (note.midiChannel == midiChannel
715 && (note.keyState == MPENote::keyDown || note.keyState == MPENote::keyDownAndSustained)
716 && note.initialNote < initialNoteMin)
717 {
718 result = &note;
719 initialNoteMin = note.initialNote;
720 }
721 }
722
723 return result;
724}
725
726MPENote* MPEInstrument::getLowestNotePtr (int midiChannel) noexcept
727{
728 return const_cast<MPENote*> (static_cast<const MPEInstrument&> (*this).getLowestNotePtr (midiChannel));
729}
730
731//==============================================================================
733{
734 const ScopedLock sl (lock);
735
736 for (auto i = notes.size(); --i >= 0;)
737 {
738 auto& note = notes.getReference (i);
739 note.keyState = MPENote::off;
740 note.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number
741 listeners.call ([&] (Listener& l) { l.noteReleased (note); });
742 }
743
744 notes.clear();
745}
746
747//==============================================================================
748//==============================================================================
749#if JUCE_UNIT_TESTS
750
751class MPEInstrumentTests : public UnitTest
752{
753public:
755 : UnitTest ("MPEInstrument class", "MIDI/MPE")
756 {
757 // using lower and upper MPE zones with the following layout for testing
758 //
759 // 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
760 // * ...................| |........................ *
761
762 testLayout.setLowerZone (5);
763 testLayout.setUpperZone (6);
764 }
765
766 void runTest() override
767 {
768 beginTest ("initial zone layout");
769 {
770 MPEInstrument test;
771 expect (! test.getZoneLayout().getLowerZone().isActive());
772 expect (! test.getZoneLayout().getUpperZone().isActive());
773 }
774
775 beginTest ("get/setZoneLayout");
776 {
777 MPEInstrument test;
778 test.setZoneLayout (testLayout);
779
780 auto newLayout = test.getZoneLayout();
781
782 expect (test.getZoneLayout().getLowerZone().isActive());
783 expect (test.getZoneLayout().getUpperZone().isActive());
784 expectEquals (newLayout.getLowerZone().getMasterChannel(), 1);
785 expectEquals (newLayout.getLowerZone().numMemberChannels, 5);
786 expectEquals (newLayout.getUpperZone().getMasterChannel(), 16);
787 expectEquals (newLayout.getUpperZone().numMemberChannels, 6);
788 }
789
790 beginTest ("noteOn / noteOff");
791 {
792 {
793 MPEInstrument test;
794 test.setZoneLayout (testLayout);
795 expectEquals (test.getNumPlayingNotes(), 0);
796 }
797 {
799 test.setZoneLayout (testLayout);
800
801 // note-on on master channel - ignore
802 test.noteOn (1, 60, MPEValue::from7BitInt (100));
803 expectEquals (test.getNumPlayingNotes(), 0);
804 expectEquals (test.noteAddedCallCounter, 0);
805
806 // note-on on any other channel - ignore
807 test.noteOn (7, 60, MPEValue::from7BitInt (100));
808 expectEquals (test.getNumPlayingNotes(), 0);
809 expectEquals (test.noteAddedCallCounter, 0);
810
811 // note-on on member channel - create new note
812 test.noteOn (3, 60, MPEValue::from7BitInt (100));
813 expectEquals (test.getNumPlayingNotes(), 1);
814 expectEquals (test.noteAddedCallCounter, 1);
815 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
816
817 // note-off
818 test.noteOff (3, 60, MPEValue::from7BitInt (33));
819 expectEquals (test.getNumPlayingNotes(), 0);
820 expectEquals (test.noteReleasedCallCounter, 1);
821 expectHasFinishedNote (test, 3, 60, 33);
822 }
823 {
825 test.setZoneLayout (testLayout);
826 test.noteOn (3, 60, MPEValue::from7BitInt (100));
827
828 // note off with non-matching note number shouldn't do anything
829 test.noteOff (3, 61, MPEValue::from7BitInt (33));
830 expectEquals (test.getNumPlayingNotes(), 1);
831 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
832 expectEquals (test.noteReleasedCallCounter, 0);
833
834 // note off with non-matching midi channel shouldn't do anything
835 test.noteOff (2, 60, MPEValue::from7BitInt (33));
836 expectEquals (test.getNumPlayingNotes(), 1);
837 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
838 expectEquals (test.noteReleasedCallCounter, 0);
839 }
840 {
841 // can have multiple notes on the same channel
843 test.setZoneLayout (testLayout);
844 test.noteOn (3, 0, MPEValue::from7BitInt (100));
845 test.noteOn (3, 1, MPEValue::from7BitInt (100));
846 test.noteOn (3, 2, MPEValue::from7BitInt (100));
847 expectEquals (test.getNumPlayingNotes(), 3);
848 expectNote (test.getNote (3, 0), 100, 0, 8192, 64, MPENote::keyDown);
849 expectNote (test.getNote (3, 1), 100, 0, 8192, 64, MPENote::keyDown);
850 expectNote (test.getNote (3, 2), 100, 0, 8192, 64, MPENote::keyDown);
851 }
852 {
853 // pathological case: second note-on for same note should retrigger it.
855 test.setZoneLayout (testLayout);
856 test.noteOn (3, 0, MPEValue::from7BitInt (100));
857 test.noteOn (3, 0, MPEValue::from7BitInt (60));
858 expectEquals (test.getNumPlayingNotes(), 1);
859 expectNote (test.getNote (3, 0), 60, 0, 8192, 64, MPENote::keyDown);
860 }
861 }
862
863 beginTest ("noteReleased after setZoneLayout");
864 {
866 test.setZoneLayout (testLayout);
867
868 test.noteOn (3, 60, MPEValue::from7BitInt (100));
869 test.noteOn (3, 61, MPEValue::from7BitInt (100));
870 test.noteOn (4, 61, MPEValue::from7BitInt (100));
871 expectEquals (test.getNumPlayingNotes(), 3);
872 expectEquals (test.noteReleasedCallCounter, 0);
873
874 test.setZoneLayout (testLayout);
875 expectEquals (test.getNumPlayingNotes(), 0);
876 expectEquals (test.noteReleasedCallCounter, 3);
877 }
878
879 beginTest ("releaseAllNotes");
880 {
882 test.setZoneLayout (testLayout);
883 test.noteOn (3, 60, MPEValue::from7BitInt (100));
884 test.noteOn (4, 61, MPEValue::from7BitInt (100));
885 test.noteOn (15, 62, MPEValue::from7BitInt (100));
886 expectEquals (test.getNumPlayingNotes(), 3);
887
888 test.releaseAllNotes();
889 expectEquals (test.getNumPlayingNotes(), 0);
890 }
891
892 beginTest ("sustainPedal");
893 {
895 test.setZoneLayout (testLayout);
896 test.noteOn (3, 60, MPEValue::from7BitInt (100)); // note in lower zone
897 test.noteOn (10, 60, MPEValue::from7BitInt (100)); // note in upper zone
898
899 // sustain pedal on per-note channel shouldn't do anything.
900 test.sustainPedal (3, true);
901 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
902
903 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
904 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
905 expectEquals (test.noteKeyStateChangedCallCounter, 0);
906
907 // sustain pedal on non-zone channel shouldn't do anything either.
908 test.sustainPedal (7, true);
909 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
910 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
911 expectEquals (test.noteKeyStateChangedCallCounter, 0);
912
913 // sustain pedal on master channel should sustain notes on _that_ zone.
914 test.sustainPedal (1, true);
915 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained);
916 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
917 expectEquals (test.noteKeyStateChangedCallCounter, 1);
918
919 // release
920 test.sustainPedal (1, false);
921 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
922 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
923 expectEquals (test.noteKeyStateChangedCallCounter, 2);
924
925 // should also sustain new notes added after the press
926 test.sustainPedal (1, true);
927 expectEquals (test.noteKeyStateChangedCallCounter, 3);
928 test.noteOn (4, 51, MPEValue::from7BitInt (100));
929 expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDownAndSustained);
930 expectEquals (test.noteKeyStateChangedCallCounter, 3);
931
932 // ...but only if that sustain came on the master channel of that zone!
933 test.sustainPedal (11, true);
934 test.noteOn (11, 52, MPEValue::from7BitInt (100));
935 expectNote (test.getNote (11, 52), 100, 0, 8192, 64, MPENote::keyDown);
936 test.noteOff (11, 52, MPEValue::from7BitInt (100));
937 expectEquals (test.noteReleasedCallCounter, 1);
938
939 // note-off should not turn off sustained notes inside the same zone
940 test.noteOff (3, 60, MPEValue::from7BitInt (100));
941 test.noteOff (4, 51, MPEValue::from7BitInt (100));
942 test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected!
943 expectEquals (test.getNumPlayingNotes(), 2);
944 expectEquals (test.noteReleasedCallCounter, 2);
945 expectEquals (test.noteKeyStateChangedCallCounter, 5);
946 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained);
947 expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::sustained);
948
949 // notes should be turned off when pedal is released
950 test.sustainPedal (1, false);
951 expectEquals (test.getNumPlayingNotes(), 0);
952 expectEquals (test.noteReleasedCallCounter, 4);
953 }
954
955 beginTest ("sostenutoPedal");
956 {
958 test.setZoneLayout (testLayout);
959 test.noteOn (3, 60, MPEValue::from7BitInt (100)); // note in lower zone
960 test.noteOn (10, 60, MPEValue::from7BitInt (100)); // note in upper zone
961
962 // sostenuto pedal on per-note channel shouldn't do anything.
963 test.sostenutoPedal (3, true);
964 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
965 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
966 expectEquals (test.noteKeyStateChangedCallCounter, 0);
967
968 // sostenuto pedal on non-zone channel shouldn't do anything either.
969 test.sostenutoPedal (9, true);
970 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
971 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
972 expectEquals (test.noteKeyStateChangedCallCounter, 0);
973
974 // sostenuto pedal on master channel should sustain notes on *that* zone.
975 test.sostenutoPedal (1, true);
976 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained);
977 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
978 expectEquals (test.noteKeyStateChangedCallCounter, 1);
979
980 // release
981 test.sostenutoPedal (1, false);
982 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
983 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
984 expectEquals (test.noteKeyStateChangedCallCounter, 2);
985
986 // should only sustain notes turned on *before* the press (difference to sustain pedal)
987 test.sostenutoPedal (1, true);
988 expectEquals (test.noteKeyStateChangedCallCounter, 3);
989 test.noteOn (4, 51, MPEValue::from7BitInt (100));
990 expectEquals (test.getNumPlayingNotes(), 3);
991 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained);
992 expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDown);
993 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
994 expectEquals (test.noteKeyStateChangedCallCounter, 3);
995
996 // note-off should not turn off sustained notes inside the same zone,
997 // but only if they were turned on *before* the sostenuto pedal (difference to sustain pedal)
998 test.noteOff (3, 60, MPEValue::from7BitInt (100));
999 test.noteOff (4, 51, MPEValue::from7BitInt (100));
1000 test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected!
1001 expectEquals (test.getNumPlayingNotes(), 1);
1002 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained);
1003 expectEquals (test.noteReleasedCallCounter, 2);
1004 expectEquals (test.noteKeyStateChangedCallCounter, 4);
1005
1006 // notes should be turned off when pedal is released
1007 test.sustainPedal (1, false);
1008 expectEquals (test.getNumPlayingNotes(), 0);
1009 expectEquals (test.noteReleasedCallCounter, 3);
1010 }
1011
1012 beginTest ("getMostRecentNote");
1013 {
1014 MPEInstrument test;
1015 test.setZoneLayout (testLayout);
1016
1017 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1018 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1019
1020 {
1021 auto note = test.getMostRecentNote (2);
1022 expect (! note.isValid());
1023 }
1024 {
1025 auto note = test.getMostRecentNote (3);
1026 expect (note.isValid());
1027 expectEquals (int (note.midiChannel), 3);
1028 expectEquals (int (note.initialNote), 61);
1029 }
1030
1031 test.sustainPedal (1, true);
1032 test.noteOff (3, 61, MPEValue::from7BitInt (100));
1033
1034 {
1035 auto note = test.getMostRecentNote (3);
1036 expect (note.isValid());
1037 expectEquals (int (note.midiChannel), 3);
1038 expectEquals (int (note.initialNote), 60);
1039 }
1040
1041 test.sustainPedal (1, false);
1042 test.noteOff (3, 60, MPEValue::from7BitInt (100));
1043
1044 {
1045 auto note = test.getMostRecentNote (3);
1046 expect (! note.isValid());
1047 }
1048 }
1049
1050 beginTest ("getMostRecentNoteOtherThan");
1051 {
1052 MPENote testNote (3, 60,
1055
1056 {
1057 // case 1: the note to exclude is not the most recent one.
1058
1059 MPEInstrument test;
1060 test.setZoneLayout (testLayout);
1061 expect (! test.getMostRecentNoteOtherThan (testNote).isValid());
1062
1063 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1064 expect (! test.getMostRecentNoteOtherThan (testNote).isValid());
1065
1066 test.noteOn (4, 61, MPEValue::from7BitInt (100));
1067 expect (test.getMostRecentNoteOtherThan (testNote).isValid());
1068 expect (test.getMostRecentNoteOtherThan (testNote).midiChannel == 4);
1069 expect (test.getMostRecentNoteOtherThan (testNote).initialNote == 61);
1070 }
1071 {
1072 // case 2: the note to exclude is the most recent one.
1073
1074 MPEInstrument test;
1075 test.setZoneLayout (testLayout);
1076 expect (! test.getMostRecentNoteOtherThan (testNote).isValid());
1077
1078 test.noteOn (4, 61, MPEValue::from7BitInt (100));
1079 expect (test.getMostRecentNoteOtherThan (testNote).isValid());
1080 expect (test.getMostRecentNoteOtherThan (testNote).midiChannel == 4);
1081 expect (test.getMostRecentNoteOtherThan (testNote).initialNote == 61);
1082
1083 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1084 expect (test.getMostRecentNoteOtherThan (testNote).isValid());
1085 expect (test.getMostRecentNoteOtherThan (testNote).midiChannel == 4);
1086 expect (test.getMostRecentNoteOtherThan (testNote).initialNote == 61);
1087 }
1088 }
1089
1090 beginTest ("pressure");
1091 {
1092 {
1094 test.setZoneLayout (testLayout);
1095
1096 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1097 test.noteOn (4, 60, MPEValue::from7BitInt (100));
1098 test.noteOn (10, 60, MPEValue::from7BitInt (100));
1099
1100 // applying pressure on a per-note channel should modulate one note
1101 test.pressure (3, MPEValue::from7BitInt (33));
1102 expectNote (test.getNote (3, 60), 100, 33, 8192, 64, MPENote::keyDown);
1103 expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown);
1104 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
1105 expectEquals (test.notePressureChangedCallCounter, 1);
1106
1107 // applying pressure on a master channel should modulate all notes in this zone
1108 test.pressure (1, MPEValue::from7BitInt (44));
1109 expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown);
1110 expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown);
1111 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
1112 expectEquals (test.notePressureChangedCallCounter, 3);
1113
1114 // applying pressure on an unrelated channel should be ignored
1115 test.pressure (8, MPEValue::from7BitInt (55));
1116 expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown);
1117 expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown);
1118 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
1119 expectEquals (test.notePressureChangedCallCounter, 3);
1120 }
1121 {
1123 test.setZoneLayout (testLayout);
1124
1125 // two notes on same channel - only last added should be modulated
1126 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1127 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1128 test.pressure (3, MPEValue::from7BitInt (66));
1129 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1130 expectNote (test.getNote (3, 61), 100, 66, 8192, 64, MPENote::keyDown);
1131 expectEquals (test.notePressureChangedCallCounter, 1);
1132 }
1133 {
1135 test.setZoneLayout (testLayout);
1136
1137 // edge case: two notes on same channel, one gets released,
1138 // then the other should be modulated
1139 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1140 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1141 test.noteOff (3, 61, MPEValue::from7BitInt (100));
1142 test.pressure (3, MPEValue::from7BitInt (77));
1143 expectEquals (test.getNumPlayingNotes(), 1);
1144 expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown);
1145 expectEquals (test.notePressureChangedCallCounter, 1);
1146 }
1147 {
1149 test.setZoneLayout (testLayout);
1150
1151 // if no pressure is sent before note-on, default = 0 should be used
1152 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1153 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1154 }
1155 {
1157 test.setZoneLayout (testLayout);
1158
1159 // if pressure is sent before note-on, use that
1160 test.pressure (3, MPEValue::from7BitInt (77));
1161 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1162 expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown);
1163 }
1164 {
1166 test.setZoneLayout (testLayout);
1167
1168 // if pressure is sent before note-on, but it belonged to another note
1169 // on the same channel that has since been turned off, use default = 0
1170 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1171 test.pressure (3, MPEValue::from7BitInt (77));
1172 test.noteOff (3, 61, MPEValue::from7BitInt (100));
1173 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1174 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1175 }
1176 {
1178 test.setZoneLayout (testLayout);
1179
1180 // edge case: two notes on the same channel simultaneously. the second one should use
1181 // pressure = 0 initially but then react to additional pressure messages
1182 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1183 test.pressure (3, MPEValue::from7BitInt (77));
1184 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1185 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1186 test.pressure (3, MPEValue::from7BitInt (78));
1187 expectNote (test.getNote (3, 60), 100, 78, 8192, 64, MPENote::keyDown);
1188 expectNote (test.getNote (3, 61), 100, 77, 8192, 64, MPENote::keyDown);
1189 }
1190 }
1191
1192 beginTest ("pitchbend");
1193 {
1194 {
1196 test.setZoneLayout (testLayout);
1197
1198 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1199 test.noteOn (4, 60, MPEValue::from7BitInt (100));
1200 test.noteOn (10, 60, MPEValue::from7BitInt (100));
1201
1202 // applying pitchbend on a per-note channel should modulate one note
1203 test.pitchbend (3, MPEValue::from14BitInt (1111));
1204 expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown);
1205 expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown);
1206 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
1207 expectEquals (test.notePitchbendChangedCallCounter, 1);
1208
1209 // applying pitchbend on a master channel should be ignored for the
1210 // value of per-note pitchbend. Tests covering master pitchbend below.
1211 // Note: noteChanged will be called anyway for notes in that zone
1212 // because the total pitchbend for those notes has changed
1213 test.pitchbend (1, MPEValue::from14BitInt (2222));
1214 expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown);
1215 expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown);
1216 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
1217 expectEquals (test.notePitchbendChangedCallCounter, 3);
1218
1219 // applying pitchbend on an unrelated channel should do nothing.
1220 test.pitchbend (8, MPEValue::from14BitInt (3333));
1221 expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown);
1222 expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown);
1223 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
1224 expectEquals (test.notePitchbendChangedCallCounter, 3);
1225 }
1226 {
1228 test.setZoneLayout (testLayout);
1229
1230 // two notes on same channel - only last added should be bent
1231 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1232 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1233 test.pitchbend (3, MPEValue::from14BitInt (4444));
1234 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1235 expectNote (test.getNote (3, 61), 100, 0, 4444, 64, MPENote::keyDown);
1236 expectEquals (test.notePitchbendChangedCallCounter, 1);
1237 }
1238 {
1240 test.setZoneLayout (testLayout);
1241
1242 // edge case: two notes on same channel, one gets released,
1243 // then the other should be bent
1244 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1245 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1246 test.noteOff (3, 61, MPEValue::from7BitInt (100));
1247 test.pitchbend (3, MPEValue::from14BitInt (5555));
1248 expectEquals (test.getNumPlayingNotes(), 1);
1249 expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown);
1250 expectEquals (test.notePitchbendChangedCallCounter, 1);
1251 }
1252 {
1254 test.setZoneLayout (testLayout);
1255
1256 // Richard's edge case:
1257 // - press one note
1258 // - press sustain (careful: must be sent on master channel)
1259 // - release first note (is still sustained!)
1260 // - press another note (happens to be on the same MIDI channel!)
1261 // - pitchbend that other note
1262 // - the first note should not be bent, only the second one.
1263
1264 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1265 test.sustainPedal (1, true);
1266 test.noteOff (3, 60, MPEValue::from7BitInt (64));
1267 expectEquals (test.getNumPlayingNotes(), 1);
1268 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained);
1269 expectEquals (test.noteKeyStateChangedCallCounter, 2);
1270
1271 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1272 test.pitchbend (3, MPEValue::from14BitInt (6666));
1273 expectEquals (test.getNumPlayingNotes(), 2);
1274 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained);
1275 expectNote (test.getNote (3, 61), 100, 0, 6666, 64, MPENote::keyDownAndSustained);
1276 expectEquals (test.notePitchbendChangedCallCounter, 1);
1277 }
1278 {
1280 test.setZoneLayout (testLayout);
1281
1282 // Zsolt's edge case:
1283 // - press one note
1284 // - modulate pitchbend or timbre
1285 // - release the note
1286 // - press same note again without sending a pitchbend or timbre message before the note-on
1287 // - the note should be turned on with a default value for pitchbend/timbre,
1288 // and *not* the last value received on channel.
1289
1290 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1291 test.pitchbend (3, MPEValue::from14BitInt (5555));
1292 expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown);
1293
1294 test.noteOff (3, 60, MPEValue::from7BitInt (100));
1295 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1296 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1297 }
1298 {
1299 // applying per-note pitchbend should set the note's totalPitchbendInSemitones
1300 // correctly depending on the per-note pitchbend range of the zone.
1302
1303 MPEZoneLayout layout = testLayout;
1304 test.setZoneLayout (layout); // default should be +/- 48 semitones
1305 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1306 test.pitchbend (3, MPEValue::from14BitInt (4096));
1307 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -24.0, 0.01);
1308
1309 layout.setLowerZone (5, 96);
1310 test.setZoneLayout (layout);
1311 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1312 test.pitchbend (3, MPEValue::from14BitInt (0)); // -max
1313 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -96.0, 0.01);
1314
1315 layout.setLowerZone (5, 1);
1316 test.setZoneLayout (layout);
1317 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1318 test.pitchbend (3, MPEValue::from14BitInt (16383)); // +max
1319 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 1.0, 0.01);
1320
1321 layout.setLowerZone (5, 0); // pitchbendrange = 0 --> no pitchbend at all
1322 test.setZoneLayout (layout);
1323 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1324 test.pitchbend (3, MPEValue::from14BitInt (12345));
1325 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 0.0, 0.01);
1326 }
1327 {
1328 // applying master pitchbend should set the note's totalPitchbendInSemitones
1329 // correctly depending on the master pitchbend range of the zone.
1331
1332 MPEZoneLayout layout = testLayout;
1333 test.setZoneLayout (layout); // default should be +/- 2 semitones
1334 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1335 test.pitchbend (1, MPEValue::from14BitInt (4096)); //halfway between -max and centre
1336 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -1.0, 0.01);
1337
1338 layout.setLowerZone (5, 48, 96);
1339 test.setZoneLayout (layout);
1340 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1341 test.pitchbend (1, MPEValue::from14BitInt (0)); // -max
1342 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -96.0, 0.01);
1343
1344 layout.setLowerZone (5, 48, 1);
1345 test.setZoneLayout (layout);
1346 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1347 test.pitchbend (1, MPEValue::from14BitInt (16383)); // +max
1348 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 1.0, 0.01);
1349
1350 layout.setLowerZone (5, 48, 0); // pitchbendrange = 0 --> no pitchbend at all
1351 test.setZoneLayout (layout);
1352 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1353 test.pitchbend (1, MPEValue::from14BitInt (12345));
1354 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, 0.0, 0.01);
1355 }
1356 {
1357 // applying both per-note and master pitchbend simultaneously should set
1358 // the note's totalPitchbendInSemitones to the sum of both, correctly
1359 // weighted with the per-note and master pitchbend range, respectively.
1361
1362 MPEZoneLayout layout = testLayout;
1363 layout.setLowerZone (5, 12, 1);
1364 test.setZoneLayout (layout);
1365
1366 test.pitchbend (1, MPEValue::from14BitInt (4096)); // master pitchbend 0.5 semitones down
1367 test.pitchbend (3, MPEValue::from14BitInt (0)); // per-note pitchbend 12 semitones down
1368 // additionally, note should react to both pitchbend messages
1369 // correctly even if they arrived before the note-on.
1370 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1371 expectDoubleWithinRelativeError (test.getMostRecentNote (3).totalPitchbendInSemitones, -12.5, 0.01);
1372 }
1373 }
1374
1375 beginTest ("timbre");
1376 {
1377 {
1379 test.setZoneLayout (testLayout);
1380
1381 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1382 test.noteOn (4, 60, MPEValue::from7BitInt (100));
1383 test.noteOn (10, 60, MPEValue::from7BitInt (100));
1384
1385 // modulating timbre on a per-note channel should modulate one note
1386 test.timbre (3, MPEValue::from7BitInt (33));
1387 expectNote (test.getNote (3, 60), 100, 0, 8192, 33, MPENote::keyDown);
1388 expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown);
1389 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
1390 expectEquals (test.noteTimbreChangedCallCounter, 1);
1391
1392 // modulating timbre on a master channel should modulate all notes in this zone
1393 test.timbre (1, MPEValue::from7BitInt (44));
1394 expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown);
1395 expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown);
1396 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
1397 expectEquals (test.noteTimbreChangedCallCounter, 3);
1398
1399 // modulating timbre on an unrelated channel should be ignored
1400 test.timbre (9, MPEValue::from7BitInt (55));
1401 expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown);
1402 expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown);
1403 expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown);
1404 expectEquals (test.noteTimbreChangedCallCounter, 3);
1405 }
1406 {
1408 test.setZoneLayout (testLayout);
1409
1410 // two notes on same channel - only last added should be modulated
1411 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1412 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1413 test.timbre (3, MPEValue::from7BitInt (66));
1414 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1415 expectNote (test.getNote (3, 61), 100, 0, 8192, 66, MPENote::keyDown);
1416 expectEquals (test.noteTimbreChangedCallCounter, 1);
1417 }
1418 {
1420 test.setZoneLayout (testLayout);
1421
1422 // edge case: two notes on same channel, one gets released,
1423 // then the other should be modulated
1424 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1425 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1426 test.noteOff (3, 61, MPEValue::from7BitInt (100));
1427 test.timbre (3, MPEValue::from7BitInt (77));
1428 expectEquals (test.getNumPlayingNotes(), 1);
1429 expectNote (test.getNote (3, 60), 100, 0, 8192, 77, MPENote::keyDown);
1430 expectEquals (test.noteTimbreChangedCallCounter, 1);
1431 }
1432 {
1434 test.setZoneLayout (testLayout);
1435
1436 // Zsolt's edge case for timbre
1437 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1438 test.timbre (3, MPEValue::from7BitInt (42));
1439 expectNote (test.getNote (3, 60), 100, 0, 8192, 42, MPENote::keyDown);
1440
1441 test.noteOff (3, 60, MPEValue::from7BitInt (100));
1442 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1443 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1444 }
1445 }
1446
1447 beginTest ("setPressureTrackingMode");
1448 {
1449 {
1450 // last note played (= default)
1452 test.setZoneLayout (testLayout);
1453
1454 test.setPressureTrackingMode (MPEInstrument::lastNotePlayedOnChannel);
1455 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1456 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1457 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1458 test.pressure (3, MPEValue::from7BitInt (99));
1459 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1460 expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown);
1461 expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown);
1462 expectEquals (test.notePressureChangedCallCounter, 1);
1463 }
1464 {
1465 // lowest note
1467 test.setZoneLayout (testLayout);
1468
1469 test.setPressureTrackingMode (MPEInstrument::lowestNoteOnChannel);
1470 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1471 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1472 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1473 test.pressure (3, MPEValue::from7BitInt (99));
1474 expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown);
1475 expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown);
1476 expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown);
1477 expectEquals (test.notePressureChangedCallCounter, 1);
1478 }
1479 {
1480 // highest note
1482 test.setZoneLayout (testLayout);
1483
1484 test.setPressureTrackingMode (MPEInstrument::highestNoteOnChannel);
1485 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1486 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1487 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1488 test.pressure (3, MPEValue::from7BitInt (99));
1489 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1490 expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown);
1491 expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown);
1492 expectEquals (test.notePressureChangedCallCounter, 1);
1493 }
1494 {
1495 // all notes
1497 test.setZoneLayout (testLayout);
1498
1499 test.setPressureTrackingMode (MPEInstrument::allNotesOnChannel);
1500 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1501 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1502 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1503 test.pressure (3, MPEValue::from7BitInt (99));
1504 expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown);
1505 expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown);
1506 expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown);
1507 expectEquals (test.notePressureChangedCallCounter, 3);
1508 }
1509 }
1510
1511 beginTest ("setPitchbendTrackingMode");
1512 {
1513 {
1514 // last note played (= default)
1516 test.setZoneLayout (testLayout);
1517
1518 test.setPitchbendTrackingMode (MPEInstrument::lastNotePlayedOnChannel);
1519 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1520 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1521 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1522 test.pitchbend (3, MPEValue::from14BitInt (9999));
1523 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1524 expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown);
1525 expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown);
1526 expectEquals (test.notePitchbendChangedCallCounter, 1);
1527 }
1528 {
1529 // lowest note
1531 test.setZoneLayout (testLayout);
1532
1533 test.setPitchbendTrackingMode (MPEInstrument::lowestNoteOnChannel);
1534 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1535 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1536 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1537 test.pitchbend (3, MPEValue::from14BitInt (9999));
1538 expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown);
1539 expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown);
1540 expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown);
1541 expectEquals (test.notePitchbendChangedCallCounter, 1);
1542 }
1543 {
1544 // highest note
1546 test.setZoneLayout (testLayout);
1547
1548 test.setPitchbendTrackingMode (MPEInstrument::highestNoteOnChannel);
1549 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1550 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1551 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1552 test.pitchbend (3, MPEValue::from14BitInt (9999));
1553 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1554 expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown);
1555 expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown);
1556 expectEquals (test.notePitchbendChangedCallCounter, 1);
1557 }
1558 {
1559 // all notes
1561 test.setZoneLayout (testLayout);
1562
1563 test.setPitchbendTrackingMode (MPEInstrument::allNotesOnChannel);
1564 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1565 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1566 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1567 test.pitchbend (3, MPEValue::from14BitInt (9999));
1568 expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown);
1569 expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown);
1570 expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown);
1571 expectEquals (test.notePitchbendChangedCallCounter, 3);
1572 }
1573 }
1574
1575 beginTest ("setTimbreTrackingMode");
1576 {
1577 {
1578 // last note played (= default)
1580 test.setZoneLayout (testLayout);
1581
1582 test.setTimbreTrackingMode (MPEInstrument::lastNotePlayedOnChannel);
1583 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1584 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1585 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1586 test.timbre (3, MPEValue::from7BitInt (99));
1587 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1588 expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown);
1589 expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown);
1590 expectEquals (test.noteTimbreChangedCallCounter, 1);
1591 }
1592 {
1593 // lowest note
1595 test.setZoneLayout (testLayout);
1596
1597 test.setTimbreTrackingMode (MPEInstrument::lowestNoteOnChannel);
1598 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1599 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1600 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1601 test.timbre (3, MPEValue::from7BitInt (99));
1602 expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown);
1603 expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown);
1604 expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown);
1605 expectEquals (test.noteTimbreChangedCallCounter, 1);
1606 }
1607 {
1608 // highest note
1610 test.setZoneLayout (testLayout);
1611
1612 test.setTimbreTrackingMode (MPEInstrument::highestNoteOnChannel);
1613 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1614 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1615 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1616 test.timbre (3, MPEValue::from7BitInt (99));
1617 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1618 expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown);
1619 expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown);
1620 expectEquals (test.noteTimbreChangedCallCounter, 1);
1621 }
1622 {
1623 // all notes
1625 test.setZoneLayout (testLayout);
1626
1627 test.setTimbreTrackingMode (MPEInstrument::allNotesOnChannel);
1628 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1629 test.noteOn (3, 62, MPEValue::from7BitInt (100));
1630 test.noteOn (3, 61, MPEValue::from7BitInt (100));
1631 test.timbre (3, MPEValue::from7BitInt (99));
1632 expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown);
1633 expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown);
1634 expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown);
1635 expectEquals (test.noteTimbreChangedCallCounter, 3);
1636 }
1637 }
1638
1639 beginTest ("processNextMidiEvent");
1640 {
1642
1643 // note on should trigger noteOn method call
1644
1645 test.processNextMidiEvent (MidiMessage::noteOn (3, 42, uint8 (92)));
1646 expectEquals (test.noteOnCallCounter, 1);
1647 expectEquals (test.lastMidiChannelReceived, 3);
1648 expectEquals (test.lastMidiNoteNumberReceived, 42);
1649 expectEquals (test.lastMPEValueReceived.as7BitInt(), 92);
1650
1651 // note off should trigger noteOff method call
1652
1653 test.processNextMidiEvent (MidiMessage::noteOff (4, 12, uint8 (33)));
1654 expectEquals (test.noteOffCallCounter, 1);
1655 expectEquals (test.lastMidiChannelReceived, 4);
1656 expectEquals (test.lastMidiNoteNumberReceived, 12);
1657 expectEquals (test.lastMPEValueReceived.as7BitInt(), 33);
1658
1659 // note on with velocity = 0 should trigger noteOff method call
1660 // with a note off velocity of 64 (centre value)
1661
1662 test.processNextMidiEvent (MidiMessage::noteOn (5, 11, uint8 (0)));
1663 expectEquals (test.noteOffCallCounter, 2);
1664 expectEquals (test.lastMidiChannelReceived, 5);
1665 expectEquals (test.lastMidiNoteNumberReceived, 11);
1666 expectEquals (test.lastMPEValueReceived.as7BitInt(), 64);
1667
1668 // pitchwheel message should trigger pitchbend method call
1669
1670 test.processNextMidiEvent (MidiMessage::pitchWheel (1, 3333));
1671 expectEquals (test.pitchbendCallCounter, 1);
1672 expectEquals (test.lastMidiChannelReceived, 1);
1673 expectEquals (test.lastMPEValueReceived.as14BitInt(), 3333);
1674
1675 // pressure using channel pressure message (7-bit value) should
1676 // trigger pressure method call
1677
1678 test.processNextMidiEvent (MidiMessage::channelPressureChange (10, 35));
1679 expectEquals (test.pressureCallCounter, 1);
1680 expectEquals (test.lastMidiChannelReceived, 10);
1681 expectEquals (test.lastMPEValueReceived.as7BitInt(), 35);
1682
1683 // pressure using 14-bit value over CC70 and CC102 should trigger
1684 // pressure method call after the MSB is sent
1685
1686 // a) sending only the MSB
1687 test.processNextMidiEvent (MidiMessage::controllerEvent (3, 70, 120));
1688 expectEquals (test.pressureCallCounter, 2);
1689 expectEquals (test.lastMidiChannelReceived, 3);
1690 expectEquals (test.lastMPEValueReceived.as7BitInt(), 120);
1691
1692 // b) sending LSB and MSB (only the MSB should trigger the call) - per MIDI channel!
1693 test.processNextMidiEvent (MidiMessage::controllerEvent (4, 102, 121));
1694 expectEquals (test.pressureCallCounter, 2);
1695 test.processNextMidiEvent (MidiMessage::controllerEvent (5, 102, 122));
1696 expectEquals (test.pressureCallCounter, 2);
1697 test.processNextMidiEvent (MidiMessage::controllerEvent (4, 70, 123));
1698 expectEquals (test.pressureCallCounter, 3);
1699 expectEquals (test.lastMidiChannelReceived, 4);
1700 expectEquals (test.lastMPEValueReceived.as14BitInt(), 121 + (123 << 7));
1701 test.processNextMidiEvent (MidiMessage::controllerEvent (5, 70, 124));
1702 expectEquals (test.pressureCallCounter, 4);
1703 expectEquals (test.lastMidiChannelReceived, 5);
1704 expectEquals (test.lastMPEValueReceived.as14BitInt(), 122 + (124 << 7));
1705 test.processNextMidiEvent (MidiMessage::controllerEvent (5, 70, 64));
1706 expectEquals (test.pressureCallCounter, 5);
1707 expectEquals (test.lastMidiChannelReceived, 5);
1708 expectEquals (test.lastMPEValueReceived.as7BitInt(), 64);
1709
1710 // same for timbre 14-bit value over CC74 and CC106
1711 test.processNextMidiEvent (MidiMessage::controllerEvent (3, 74, 120));
1712 expectEquals (test.timbreCallCounter, 1);
1713 expectEquals (test.lastMidiChannelReceived, 3);
1714 expectEquals (test.lastMPEValueReceived.as7BitInt(), 120);
1715 test.processNextMidiEvent (MidiMessage::controllerEvent (4, 106, 121));
1716 expectEquals (test.timbreCallCounter, 1);
1717 test.processNextMidiEvent (MidiMessage::controllerEvent (5, 106, 122));
1718 expectEquals (test.timbreCallCounter, 1);
1719 test.processNextMidiEvent (MidiMessage::controllerEvent (4, 74, 123));
1720 expectEquals (test.timbreCallCounter, 2);
1721 expectEquals (test.lastMidiChannelReceived, 4);
1722 expectEquals (test.lastMPEValueReceived.as14BitInt(), 121 + (123 << 7));
1723 test.processNextMidiEvent (MidiMessage::controllerEvent (5, 74, 124));
1724 expectEquals (test.timbreCallCounter, 3);
1725 expectEquals (test.lastMidiChannelReceived, 5);
1726 expectEquals (test.lastMPEValueReceived.as14BitInt(), 122 + (124 << 7));
1727 test.processNextMidiEvent (MidiMessage::controllerEvent (5, 74, 64));
1728 expectEquals (test.timbreCallCounter, 4);
1729 expectEquals (test.lastMidiChannelReceived, 5);
1730 expectEquals (test.lastMPEValueReceived.as7BitInt(), 64);
1731
1732 // sustain pedal message (CC64) should trigger sustainPedal method call
1733 test.processNextMidiEvent (MidiMessage::controllerEvent (1, 64, 127));
1734 expectEquals (test.sustainPedalCallCounter, 1);
1735 expectEquals (test.lastMidiChannelReceived, 1);
1736 expect (test.lastSustainPedalValueReceived);
1737 test.processNextMidiEvent (MidiMessage::controllerEvent (16, 64, 0));
1738 expectEquals (test.sustainPedalCallCounter, 2);
1739 expectEquals (test.lastMidiChannelReceived, 16);
1740 expect (! test.lastSustainPedalValueReceived);
1741
1742 // sostenuto pedal message (CC66) should trigger sostenutoPedal method call
1743 test.processNextMidiEvent (MidiMessage::controllerEvent (1, 66, 127));
1744 expectEquals (test.sostenutoPedalCallCounter, 1);
1745 expectEquals (test.lastMidiChannelReceived, 1);
1746 expect (test.lastSostenutoPedalValueReceived);
1747 test.processNextMidiEvent (MidiMessage::controllerEvent (16, 66, 0));
1748 expectEquals (test.sostenutoPedalCallCounter, 2);
1749 expectEquals (test.lastMidiChannelReceived, 16);
1750 expect (! test.lastSostenutoPedalValueReceived);
1751 }
1752 {
1753 // MIDI messages modifying the zone layout should be correctly
1754 // forwarded to the internal zone layout and modify it.
1755 // (testing the actual logic of the zone layout is done in the
1756 // MPEZoneLayout unit tests)
1757 MPEInstrument test;
1758
1759 MidiBuffer buffer;
1760 buffer.addEvents (MPEMessages::setLowerZone (5), 0, -1, 0);
1761 buffer.addEvents (MPEMessages::setUpperZone (6), 0, -1, 0);
1762
1763 MidiBuffer::Iterator iter (buffer);
1764 MidiMessage message;
1765 int samplePosition; // not actually used, so no need to initialise.
1766
1767 while (iter.getNextEvent (message, samplePosition))
1768 test.processNextMidiEvent (message);
1769
1770 expect (test.getZoneLayout().getLowerZone().isActive());
1771 expect (test.getZoneLayout().getUpperZone().isActive());
1772 expectEquals (test.getZoneLayout().getLowerZone().getMasterChannel(), 1);
1773 expectEquals (test.getZoneLayout().getLowerZone().numMemberChannels, 5);
1774 expectEquals (test.getZoneLayout().getUpperZone().getMasterChannel(), 16);
1775 expectEquals (test.getZoneLayout().getUpperZone().numMemberChannels, 6);
1776 }
1777
1778 beginTest ("MIDI all notes off");
1779 {
1781 test.setZoneLayout (testLayout);
1782 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1783 test.noteOn (4, 61, MPEValue::from7BitInt (100));
1784 test.noteOn (15, 62, MPEValue::from7BitInt (100));
1785 test.noteOn (15, 63, MPEValue::from7BitInt (100));
1786 expectEquals (test.getNumPlayingNotes(), 4);
1787
1788 // on note channel: ignore.
1789 test.processNextMidiEvent (MidiMessage::allControllersOff (3));
1790 expectEquals (test.getNumPlayingNotes(), 4);
1791
1792 // on unused channel: ignore.
1793 test.processNextMidiEvent (MidiMessage::allControllersOff (9));
1794 expectEquals (test.getNumPlayingNotes(), 4);
1795
1796 // on master channel: release notes in that zone only.
1797 test.processNextMidiEvent (MidiMessage::allControllersOff (1));
1798 expectEquals (test.getNumPlayingNotes(), 2);
1799 test.processNextMidiEvent (MidiMessage::allControllersOff (16));
1800 expectEquals (test.getNumPlayingNotes(), 0);
1801 }
1802
1803 beginTest ("MIDI all notes off (legacy mode)");
1804 {
1806 test.enableLegacyMode();
1807 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1808 test.noteOn (4, 61, MPEValue::from7BitInt (100));
1809 test.noteOn (15, 62, MPEValue::from7BitInt (100));
1810 test.noteOn (15, 63, MPEValue::from7BitInt (100));
1811 expectEquals (test.getNumPlayingNotes(), 4);
1812
1813 test.processNextMidiEvent (MidiMessage::allControllersOff (3));
1814 expectEquals (test.getNumPlayingNotes(), 3);
1815
1816 test.processNextMidiEvent (MidiMessage::allControllersOff (15));
1817 expectEquals (test.getNumPlayingNotes(), 1);
1818
1819 test.processNextMidiEvent (MidiMessage::allControllersOff (4));
1820 expectEquals (test.getNumPlayingNotes(), 0);
1821 }
1822
1823 beginTest ("default initial values for pitchbend and timbre");
1824 {
1825 MPEInstrument test;
1826 test.setZoneLayout (testLayout);
1827
1828 test.pitchbend (3, MPEValue::from14BitInt (3333)); // use for next note-on on ch. 3
1829 test.pitchbend (2, MPEValue::from14BitInt (4444)); // ignore
1830 test.pitchbend (2, MPEValue::from14BitInt (5555)); // ignore
1831
1832 test.timbre (3, MPEValue::from7BitInt (66)); // use for next note-on on ch. 3
1833 test.timbre (2, MPEValue::from7BitInt (77)); // ignore
1834 test.timbre (2, MPEValue::from7BitInt (88)); // ignore
1835
1836 test.noteOn (3, 60, MPEValue::from7BitInt (100));
1837
1838 expectNote (test.getMostRecentNote (3), 100, 0, 3333, 66, MPENote::keyDown);
1839 }
1840
1841 beginTest ("Legacy mode");
1842 {
1843 {
1844 // basic check
1845 MPEInstrument test;
1846 expect (! test.isLegacyModeEnabled());
1847
1848 test.setZoneLayout (testLayout);
1849 expect (! test.isLegacyModeEnabled());
1850
1851 test.enableLegacyMode();
1852 expect (test.isLegacyModeEnabled());
1853
1854 test.setZoneLayout (testLayout);
1855 expect (! test.isLegacyModeEnabled());
1856 }
1857 {
1858 // constructor w/o default arguments
1859 MPEInstrument test;
1860 test.enableLegacyMode (0, Range<int> (1, 11));
1861 expectEquals (test.getLegacyModePitchbendRange(), 0);
1862 expect (test.getLegacyModeChannelRange() == Range<int> (1, 11));
1863 }
1864 {
1865 // getters and setters
1866 MPEInstrument test;
1867 test.enableLegacyMode();
1868
1869 expectEquals (test.getLegacyModePitchbendRange(), 2);
1870 expect (test.getLegacyModeChannelRange() == Range<int> (1, 17));
1871
1872 test.setLegacyModePitchbendRange (96);
1873 expectEquals (test.getLegacyModePitchbendRange(), 96);
1874
1875 test.setLegacyModeChannelRange (Range<int> (10, 12));
1876 expect (test.getLegacyModeChannelRange() == Range<int> (10, 12));
1877 }
1878 {
1879 // note on should trigger notes on all 16 channels
1880
1882 test.enableLegacyMode();
1883
1884 test.noteOn (1, 60, MPEValue::from7BitInt (100));
1885 test.noteOn (2, 60, MPEValue::from7BitInt (100));
1886 test.noteOn (15, 60, MPEValue::from7BitInt (100));
1887 test.noteOn (16, 60, MPEValue::from7BitInt (100));
1888 expectEquals (test.getNumPlayingNotes(), 4);
1889
1890 // polyphonic modulation should work across all 16 channels
1891
1892 test.pitchbend (1, MPEValue::from14BitInt (9999));
1893 test.pressure (2, MPEValue::from7BitInt (88));
1894 test.timbre (15, MPEValue::from7BitInt (77));
1895
1896 expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown);
1897 expectNote (test.getNote (2, 60), 100, 88, 8192, 64, MPENote::keyDown);
1898 expectNote (test.getNote (15, 60), 100, 0, 8192, 77, MPENote::keyDown);
1899 expectNote (test.getNote (16, 60), 100, 0, 8192, 64, MPENote::keyDown);
1900
1901 // note off should work in legacy mode
1902
1903 test.noteOff (15, 60, MPEValue::from7BitInt (0));
1904 test.noteOff (1, 60, MPEValue::from7BitInt (0));
1905 test.noteOff (2, 60, MPEValue::from7BitInt (0));
1906 test.noteOff (16, 60, MPEValue::from7BitInt (0));
1907 expectEquals (test.getNumPlayingNotes(), 0);
1908 }
1909 {
1910 // legacy mode w/ custom channel range: note on should trigger notes only within range
1911
1913 test.enableLegacyMode (2, Range<int> (3, 8)); // channels 3-7
1914
1915 test.noteOn (1, 60, MPEValue::from7BitInt (100));
1916 test.noteOn (2, 60, MPEValue::from7BitInt (100));
1917 test.noteOn (3, 60, MPEValue::from7BitInt (100)); // should trigger
1918 test.noteOn (4, 60, MPEValue::from7BitInt (100)); // should trigger
1919 test.noteOn (6, 60, MPEValue::from7BitInt (100)); // should trigger
1920 test.noteOn (7, 60, MPEValue::from7BitInt (100)); // should trigger
1921 test.noteOn (8, 60, MPEValue::from7BitInt (100));
1922 test.noteOn (16, 60, MPEValue::from7BitInt (100));
1923
1924 expectEquals (test.getNumPlayingNotes(), 4);
1925 expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown);
1926 expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown);
1927 expectNote (test.getNote (6, 60), 100, 0, 8192, 64, MPENote::keyDown);
1928 expectNote (test.getNote (7, 60), 100, 0, 8192, 64, MPENote::keyDown);
1929 }
1930 {
1931 // tracking mode in legacy mode
1932 {
1934 test.enableLegacyMode();
1935
1936 test.setPitchbendTrackingMode (MPEInstrument::lastNotePlayedOnChannel);
1937 test.noteOn (1, 60, MPEValue::from7BitInt (100));
1938 test.noteOn (1, 62, MPEValue::from7BitInt (100));
1939 test.noteOn (1, 61, MPEValue::from7BitInt (100));
1940 test.pitchbend (1, MPEValue::from14BitInt (9999));
1941 expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown);
1942 expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown);
1943 expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown);
1944 }
1945 {
1947 test.enableLegacyMode();
1948
1949 test.setPitchbendTrackingMode (MPEInstrument::lowestNoteOnChannel);
1950 test.noteOn (1, 60, MPEValue::from7BitInt (100));
1951 test.noteOn (1, 62, MPEValue::from7BitInt (100));
1952 test.noteOn (1, 61, MPEValue::from7BitInt (100));
1953 test.pitchbend (1, MPEValue::from14BitInt (9999));
1954 expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown);
1955 expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown);
1956 expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown);
1957 }
1958 {
1960 test.enableLegacyMode();
1961
1962 test.setPitchbendTrackingMode (MPEInstrument::highestNoteOnChannel);
1963 test.noteOn (1, 60, MPEValue::from7BitInt (100));
1964 test.noteOn (1, 62, MPEValue::from7BitInt (100));
1965 test.noteOn (1, 61, MPEValue::from7BitInt (100));
1966 test.pitchbend (1, MPEValue::from14BitInt (9999));
1967 expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown);
1968 expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown);
1969 expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown);
1970 }
1971 {
1973 test.enableLegacyMode();
1974
1975 test.setPitchbendTrackingMode (MPEInstrument::allNotesOnChannel);
1976 test.noteOn (1, 60, MPEValue::from7BitInt (100));
1977 test.noteOn (1, 62, MPEValue::from7BitInt (100));
1978 test.noteOn (1, 61, MPEValue::from7BitInt (100));
1979 test.pitchbend (1, MPEValue::from14BitInt (9999));
1980 expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown);
1981 expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown);
1982 expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown);
1983 }
1984 }
1985 {
1986 // custom pitchbend range in legacy mode.
1988 test.enableLegacyMode (11);
1989
1990 test.pitchbend (1, MPEValue::from14BitInt (4096));
1991 test.noteOn (1, 60, MPEValue::from7BitInt (100));
1992 expectDoubleWithinRelativeError (test.getMostRecentNote (1).totalPitchbendInSemitones, -5.5, 0.01);
1993 }
1994 {
1995 // sustain pedal should be per channel in legacy mode.
1997 test.enableLegacyMode();
1998
1999 test.sustainPedal (1, true);
2000 test.noteOn (2, 61, MPEValue::from7BitInt (100));
2001 test.noteOff (2, 61, MPEValue::from7BitInt (100));
2002 test.noteOn (1, 60, MPEValue::from7BitInt (100));
2003 test.noteOff (1, 60, MPEValue::from7BitInt (100));
2004
2005 expectEquals (test.getNumPlayingNotes(), 1);
2006 expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained);
2007
2008 test.sustainPedal (1, false);
2009 expectEquals (test.getNumPlayingNotes(), 0);
2010
2011 test.noteOn (2, 61, MPEValue::from7BitInt (100));
2012 test.sustainPedal (1, true);
2013 test.noteOff (2, 61, MPEValue::from7BitInt (100));
2014 expectEquals (test.getNumPlayingNotes(), 0);
2015
2016 }
2017 {
2018 // sostenuto pedal should be per channel in legacy mode.
2020 test.enableLegacyMode();
2021
2022 test.noteOn (1, 60, MPEValue::from7BitInt (100));
2023 test.sostenutoPedal (1, true);
2024 test.noteOff (1, 60, MPEValue::from7BitInt (100));
2025 test.noteOn (2, 61, MPEValue::from7BitInt (100));
2026 test.noteOff (2, 61, MPEValue::from7BitInt (100));
2027
2028 expectEquals (test.getNumPlayingNotes(), 1);
2029 expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained);
2030
2031 test.sostenutoPedal (1, false);
2032 expectEquals (test.getNumPlayingNotes(), 0);
2033
2034 test.noteOn (2, 61, MPEValue::from7BitInt (100));
2035 test.sostenutoPedal (1, true);
2036 test.noteOff (2, 61, MPEValue::from7BitInt (100));
2037 expectEquals (test.getNumPlayingNotes(), 0);
2038 }
2039 {
2040 // all notes released when switching layout
2042 test.setZoneLayout (testLayout);
2043 test.noteOn (3, 60, MPEValue::from7BitInt (100));
2044 expectEquals (test.getNumPlayingNotes(), 1);
2045
2046 test.enableLegacyMode();
2047 expectEquals (test.getNumPlayingNotes(), 0);
2048 test.noteOn (3, 60, MPEValue::from7BitInt (100));
2049 expectEquals (test.getNumPlayingNotes(), 1);
2050
2051 test.setZoneLayout (testLayout);
2052 expectEquals (test.getNumPlayingNotes(), 0);
2053 }
2054 }
2055 }
2056
2057private:
2058 //==============================================================================
2059 /* This mock class is used for unit testing whether the methods of
2060 MPEInstrument are called correctly.
2061 */
2062 class UnitTestInstrument : public MPEInstrument,
2063 private MPEInstrument::Listener
2064 {
2065 using Base = MPEInstrument;
2066
2067 public:
2068 UnitTestInstrument()
2069 : noteOnCallCounter (0), noteOffCallCounter (0), pitchbendCallCounter (0),
2070 pressureCallCounter (0), timbreCallCounter (0), sustainPedalCallCounter (0),
2071 sostenutoPedalCallCounter (0), noteAddedCallCounter (0), notePressureChangedCallCounter (0),
2072 notePitchbendChangedCallCounter (0), noteTimbreChangedCallCounter (0),
2073 noteKeyStateChangedCallCounter (0), noteReleasedCallCounter (0),
2074 lastMidiChannelReceived (-1), lastMidiNoteNumberReceived (-1),
2075 lastSustainPedalValueReceived (false), lastSostenutoPedalValueReceived (false)
2076 {
2077 addListener (this);
2078 }
2079
2080 void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity) override
2081 {
2082 Base::noteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity);
2083
2084 noteOnCallCounter++;
2085 lastMidiChannelReceived = midiChannel;
2086 lastMidiNoteNumberReceived = midiNoteNumber;
2087 lastMPEValueReceived = midiNoteOnVelocity;
2088 }
2089
2090 void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity) override
2091 {
2092 Base::noteOff (midiChannel, midiNoteNumber, midiNoteOffVelocity);
2093
2094 noteOffCallCounter++;
2095 lastMidiChannelReceived = midiChannel;
2096 lastMidiNoteNumberReceived = midiNoteNumber;
2097 lastMPEValueReceived = midiNoteOffVelocity;
2098 }
2099
2100 void pitchbend (int midiChannel, MPEValue value) override
2101 {
2102 Base::pitchbend (midiChannel, value);
2103
2104 pitchbendCallCounter++;
2105 lastMidiChannelReceived = midiChannel;
2106 lastMPEValueReceived = value;
2107 }
2108
2109 void pressure (int midiChannel, MPEValue value) override
2110 {
2111 Base::pressure (midiChannel, value);
2112
2113 pressureCallCounter++;
2114 lastMidiChannelReceived = midiChannel;
2115 lastMPEValueReceived = value;
2116 }
2117
2118 void timbre (int midiChannel, MPEValue value) override
2119 {
2120 Base::timbre (midiChannel, value);
2121
2122 timbreCallCounter++;
2123 lastMidiChannelReceived = midiChannel;
2124 lastMPEValueReceived = value;
2125 }
2126
2127 void sustainPedal (int midiChannel, bool value) override
2128 {
2129 Base::sustainPedal (midiChannel, value);
2130
2131 sustainPedalCallCounter++;
2132 lastMidiChannelReceived = midiChannel;
2133 lastSustainPedalValueReceived = value;
2134 }
2135
2136 void sostenutoPedal (int midiChannel, bool value) override
2137 {
2138 Base::sostenutoPedal (midiChannel, value);
2139
2140 sostenutoPedalCallCounter++;
2141 lastMidiChannelReceived = midiChannel;
2142 lastSostenutoPedalValueReceived = value;
2143 }
2144
2145 int noteOnCallCounter, noteOffCallCounter, pitchbendCallCounter,
2146 pressureCallCounter, timbreCallCounter, sustainPedalCallCounter,
2147 sostenutoPedalCallCounter, noteAddedCallCounter,
2148 notePressureChangedCallCounter, notePitchbendChangedCallCounter,
2149 noteTimbreChangedCallCounter, noteKeyStateChangedCallCounter,
2150 noteReleasedCallCounter, lastMidiChannelReceived, lastMidiNoteNumberReceived;
2151
2152 bool lastSustainPedalValueReceived, lastSostenutoPedalValueReceived;
2153 MPEValue lastMPEValueReceived;
2154 std::unique_ptr<MPENote> lastNoteFinished;
2155
2156 private:
2157 //==============================================================================
2158 void noteAdded (MPENote) override { noteAddedCallCounter++; }
2159
2160 void notePressureChanged (MPENote) override { notePressureChangedCallCounter++; }
2161 void notePitchbendChanged (MPENote) override { notePitchbendChangedCallCounter++; }
2162 void noteTimbreChanged (MPENote) override { noteTimbreChangedCallCounter++; }
2163 void noteKeyStateChanged (MPENote) override { noteKeyStateChangedCallCounter++; }
2164
2165 void noteReleased (MPENote finishedNote) override
2166 {
2167 noteReleasedCallCounter++;
2168 lastNoteFinished.reset (new MPENote (finishedNote));
2169 }
2170 };
2171
2172 //==============================================================================
2173 void expectNote (MPENote noteToTest,
2175 int pressure7Bit,
2176 int pitchbend14Bit,
2177 int timbre7Bit,
2178 MPENote::KeyState keyState)
2179 {
2180 expect (noteToTest.isValid());
2181 expectEquals (noteToTest.noteOnVelocity.as7BitInt(), noteOnVelocity7Bit);
2182 expectEquals (noteToTest.pressure.as7BitInt(), pressure7Bit);
2183 expectEquals (noteToTest.pitchbend.as14BitInt(), pitchbend14Bit);
2184 expectEquals (noteToTest.timbre.as7BitInt(),timbre7Bit);
2185 expect (noteToTest.keyState == keyState);
2186 }
2187
2189 int channel, int noteNumber, int noteOffVelocity7Bit)
2190 {
2191 expect (test.lastNoteFinished != nullptr);
2192 expectEquals (int (test.lastNoteFinished->midiChannel), channel);
2193 expectEquals (int (test.lastNoteFinished->initialNote), noteNumber);
2194 expectEquals (test.lastNoteFinished->noteOffVelocity.as7BitInt(), noteOffVelocity7Bit);
2195 expect (test.lastNoteFinished->keyState == MPENote::off);
2196 }
2197
2199 {
2200 const double maxAbsoluteError = jmax (1.0, std::abs (expected)) * maxRelativeError;
2201 expect (std::abs (expected - actual) < maxAbsoluteError);
2202 }
2203
2204 //==============================================================================
2205 MPEZoneLayout testLayout;
2206};
2207
2208static MPEInstrumentTests MPEInstrumentUnitTests;
2209
2210#endif // JUCE_UNIT_TESTS
2211
2212} // namespace juce
Holds a resizable array of primitive or copy-by-value objects.
Definition juce_Array.h:60
bool isEmpty() const noexcept
Returns true if the array is empty, false otherwise.
Definition juce_Array.h:226
int size() const noexcept
Returns the current number of elements in the array.
Definition juce_Array.h:219
void remove(int indexToRemove)
Removes an element from the array.
Definition juce_Array.h:724
Array()=default
Creates an empty array.
void add(const ElementType &newElement)
Appends a new element at the end of the array.
Definition juce_Array.h:375
ElementType & getReference(int index) const noexcept
Returns a direct reference to one of the elements in the array, without checking the index passed in.
Definition juce_Array.h:271
bool contains(ParameterType elementToLookFor) const
Returns true if the array contains at least one occurrence of an object.
Definition juce_Array.h:357
void clear()
Removes all elements from the array.
Definition juce_Array.h:192
Derive from this class to be informed about any changes in the expressive MIDI notes played by this i...
void setPitchbendTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the pitchbend dimension.
void setLegacyModeChannelRange(Range< int > channelRange)
Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode.
MPENote getMostRecentNoteOtherThan(MPENote otherThanThisNote) const noexcept
Returns the most recent note that is not the note passed in.
MPEZoneLayout getZoneLayout() const noexcept
Returns the current zone layout of the instrument.
virtual void sostenutoPedal(int midiChannel, bool isDown)
Request a sostenuto pedal press or release.
virtual void pitchbend(int midiChannel, MPEValue pitchbend)
Request a pitchbend on the given channel with the given value (in units of MIDI pitchwheel position).
virtual ~MPEInstrument()
Destructor.
void enableLegacyMode(int pitchbendRange=2, Range< int > channelRange=Range< int >(1, 17))
Puts the instrument into legacy mode.
MPENote getNote(int index) const noexcept
Returns the note at the given index.
TrackingMode
The MPE note tracking mode.
@ highestNoteOnChannel
The highest note (by initialNote) on the channel with the note key still down.
@ lowestNoteOnChannel
The lowest note (by initialNote) on the channel with the note key still down.
@ lastNotePlayedOnChannel
The most recent note on the channel that is still played (key down and/or sustained).
@ allNotesOnChannel
All notes on the channel (key down and/or sustained).
MPEInstrument() noexcept
Constructor.
void setZoneLayout(MPEZoneLayout newLayout)
Re-sets the zone layout of the instrument to the one passed in.
virtual void processNextMidiEvent(const MidiMessage &message)
Process a MIDI message and trigger the appropriate method calls (noteOn, noteOff etc....
void setLegacyModePitchbendRange(int pitchbendRange)
Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode.
bool isLegacyModeEnabled() const noexcept
Returns true if the instrument is in legacy mode, false otherwise.
void setPressureTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the pressure dimension.
void addListener(Listener *listenerToAdd)
Adds a listener.
void removeListener(Listener *listenerToRemove)
Removes a listener.
virtual void sustainPedal(int midiChannel, bool isDown)
Request a sustain pedal press or release.
void setTimbreTrackingMode(TrackingMode modeToUse)
Set the MPE tracking mode for the timbre dimension.
int getNumPlayingNotes() const noexcept
Returns the number of MPE notes currently played by the instrument.
void releaseAllNotes()
Discard all currently playing notes.
virtual void timbre(int midiChannel, MPEValue value)
Request a third dimension (timbre) change on the given channel with the given value.
MPENote getMostRecentNote(int midiChannel) const noexcept
Returns the most recent note that is playing on the given midiChannel (this will be the note which ha...
Range< int > getLegacyModeChannelRange() const noexcept
Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode.
virtual void noteOn(int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity)
Request a note-on on the given channel, with the given initial note number and velocity.
int getLegacyModePitchbendRange() const noexcept
Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode.
virtual void noteOff(int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity)
Request a note-off.
virtual void pressure(int midiChannel, MPEValue value)
Request a pressure change on the given channel with the given value.
bool isMasterChannel(int midiChannel) const noexcept
Returns true if the given MIDI channel (1-16) is a master channel (channel 1 or 16).
bool isMemberChannel(int midiChannel) noexcept
Returns true if the given MIDI channel (1-16) is a note channel in any of the MPEInstrument's MPE zon...
static MidiBuffer setUpperZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2)
Returns the sequence of MIDI messages that, if sent to an Expressive MIDI device, will set the upper ...
static MidiBuffer setLowerZone(int numMemberChannels=0, int perNotePitchbendRange=48, int masterPitchbendRange=2)
Returns the sequence of MIDI messages that, if sent to an Expressive MIDI device, will set the lower ...
This class represents a single value for any of the MPE dimensions of control.
static MPEValue centreValue() noexcept
Constructs an MPEValue corresponding to the centre value.
static MPEValue from14BitInt(int value) noexcept
Constructs an MPEValue from an integer between 0 and 16383 (using 14-bit precision).
static MPEValue minValue() noexcept
Constructs an MPEValue corresponding to the minimum value.
static MPEValue from7BitInt(int value) noexcept
Constructs an MPEValue from an integer between 0 and 127 (using 7-bit precision).
This class represents the current MPE zone layout of a device capable of handling MPE.
void clearAllZones()
Clears the lower and upper zones of this layout, making them both inactive and disabling MPE mode.
const Zone getUpperZone() const noexcept
Returns a struct representing the upper MPE zone.
const Zone getLowerZone() const noexcept
Returns a struct representing the lower MPE zone.
void processNextMidiEvent(const MidiMessage &message)
Pass incoming MIDI messages to an object of this class if you want the zone layout to properly react ...
Encapsulates a MIDI message.
bool isNoteOn(bool returnTrueForVelocity0=false) const noexcept
Returns true if this message is a 'key-down' event.
int getChannel() const noexcept
Returns the midi channel associated with the message.
bool isController() const noexcept
Returns true if this is a midi controller message.
static MidiMessage pitchWheel(int channel, int position) noexcept
Creates a pitch-wheel move message.
bool isNoteOff(bool returnTrueForNoteOnVelocity0=true) const noexcept
Returns true if this message is a 'key-up' event.
static MidiMessage noteOn(int channel, int noteNumber, float velocity) noexcept
Creates a key-down message (using a floating-point velocity).
bool isPitchWheel() const noexcept
Returns true if the message is a pitch-wheel move.
int getNoteNumber() const noexcept
Returns the midi note number for note-on and note-off messages.
static MidiMessage controllerEvent(int channel, int controllerType, int value) noexcept
Creates a controller message.
bool isResetAllControllers() const noexcept
Checks whether this message is a reset all controllers message.
static MidiMessage channelPressureChange(int channel, int pressure) noexcept
Creates a channel-pressure change event.
bool isAllNotesOff() const noexcept
Checks whether this message is an all-notes-off message.
static MidiMessage noteOff(int channel, int noteNumber, float velocity) noexcept
Creates a key-up message.
uint8 getVelocity() const noexcept
Returns the velocity of a note-on or note-off message.
bool isChannelPressure() const noexcept
Returns true if the message is a channel-pressure change event.
static MidiMessage allControllersOff(int channel) noexcept
Creates an all-controllers-off message.
This is a base class for classes that perform a unit test.
This struct represents a playing MPE note.
KeyState
Possible values for the note key state.
@ keyDown
The note key is currently down (pressed).
@ off
The key is up (off).
@ keyDownAndSustained
The note key is down and sustained (by a sustain or sostenuto pedal).
@ sustained
The note is sustained (by a sustain or sostenuto pedal).
MPEValue timbre
Current value of the note's third expressive dimension, typically encoding some kind of timbre parame...
MPEValue pitchbend
Current per-note pitchbend of the note (in units of MIDI pitchwheel position).
MPEValue pressure
Current pressure with which the note is held down.