OpenShot Audio Library | OpenShotAudio: juce_MPEInstrument.cpp Source File

OpenShot Audio Library | OpenShotAudio 0.4.0

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