| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE examples.
 -    Copyright (c) 2022 - Raw Material Software Limited
 - 
 -    The code included in this file is provided under the terms of the ISC license
 -    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
 -    To use, copy, modify, and/or distribute this software for any purpose with or
 -    without fee is hereby granted provided that the above copyright notice and
 -    this permission notice appear in all copies.
 - 
 -    THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
 -    WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
 -    PURPOSE, ARE DISCLAIMED.
 - 
 -   ==============================================================================
 - */
 - 
 - /*******************************************************************************
 -  The block below describes the properties of this PIP. A PIP is a short snippet
 -  of code that can be read by the Projucer and used to generate a JUCE project.
 - 
 -  BEGIN_JUCE_PIP_METADATA
 - 
 -  name:             MPEDemo
 -  version:          1.0.0
 -  vendor:           JUCE
 -  website:          http://juce.com
 -  description:      Simple MPE synthesiser application.
 - 
 -  dependencies:     juce_audio_basics, juce_audio_devices, juce_audio_formats,
 -                    juce_audio_processors, juce_audio_utils, juce_core,
 -                    juce_data_structures, juce_events, juce_graphics,
 -                    juce_gui_basics, juce_gui_extra
 -  exporters:        xcode_mac, vs2022, linux_make, androidstudio, xcode_iphone
 - 
 -  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1
 - 
 -  type:             Component
 -  mainClass:        MPEDemo
 - 
 -  useLocalCopy:     1
 - 
 -  END_JUCE_PIP_METADATA
 - 
 - *******************************************************************************/
 - 
 - #pragma once
 - 
 - 
 - //==============================================================================
 - class ZoneColourPicker
 - {
 - public:
 -     ZoneColourPicker() {}
 - 
 -     //==============================================================================
 -     Colour getColourForMidiChannel (int midiChannel) noexcept
 -     {
 -         if (legacyModeEnabled)
 -             return Colours::white;
 - 
 -         if (zoneLayout.getLowerZone().isUsingChannelAsMemberChannel (midiChannel))
 -             return getColourForZone (true);
 - 
 -         if (zoneLayout.getUpperZone().isUsingChannelAsMemberChannel (midiChannel))
 -             return getColourForZone (false);
 - 
 -         return Colours::transparentBlack;
 -     }
 - 
 -     //==============================================================================
 -     Colour getColourForZone (bool isLowerZone) const noexcept
 -     {
 -         if (legacyModeEnabled)
 -             return Colours::white;
 - 
 -         if (isLowerZone)
 -             return Colours::blue;
 - 
 -         return Colours::red;
 -     }
 - 
 -     //==============================================================================
 -     void setZoneLayout (MPEZoneLayout layout) noexcept          { zoneLayout = layout; }
 -     void setLegacyModeEnabled (bool shouldBeEnabled) noexcept   { legacyModeEnabled = shouldBeEnabled; }
 - 
 - private:
 -     //==============================================================================
 -     MPEZoneLayout zoneLayout;
 -     bool legacyModeEnabled = false;
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ZoneColourPicker)
 - };
 - 
 - //==============================================================================
 - class MPESetupComponent : public Component
 - {
 - public:
 -     //==============================================================================
 -     MPESetupComponent (MPEInstrument& instr)
 -         : instrument (instr)
 -     {
 -         addAndMakeVisible (isLowerZoneButton);
 -         isLowerZoneButton.setToggleState (true, NotificationType::dontSendNotification);
 - 
 -         initialiseComboBoxWithConsecutiveIntegers (memberChannels, memberChannelsLabel, 0, 16, defaultMemberChannels);
 -         initialiseComboBoxWithConsecutiveIntegers (masterPitchbendRange, masterPitchbendRangeLabel, 0, 96, defaultMasterPitchbendRange);
 -         initialiseComboBoxWithConsecutiveIntegers (notePitchbendRange, notePitchbendRangeLabel, 0, 96, defaultNotePitchbendRange);
 - 
 -         initialiseComboBoxWithConsecutiveIntegers (legacyStartChannel, legacyStartChannelLabel, 1, 16, 1, false);
 -         initialiseComboBoxWithConsecutiveIntegers (legacyEndChannel, legacyEndChannelLabel, 1, 16, 16, false);
 -         initialiseComboBoxWithConsecutiveIntegers (legacyPitchbendRange, legacyPitchbendRangeLabel, 0, 96, 2, false);
 - 
 -         addAndMakeVisible (setZoneButton);
 -         setZoneButton.onClick = [this] { setZoneButtonClicked(); };
 - 
 -         addAndMakeVisible (clearAllZonesButton);
 -         clearAllZonesButton.onClick = [this] { clearAllZonesButtonClicked(); };
 - 
 -         addAndMakeVisible (legacyModeEnabledToggle);
 -         legacyModeEnabledToggle.onClick = [this] { legacyModeEnabledToggleClicked(); };
 - 
 -         addAndMakeVisible (voiceStealingEnabledToggle);
 -         voiceStealingEnabledToggle.onClick = [this] { voiceStealingEnabledToggleClicked(); };
 - 
 -         initialiseComboBoxWithConsecutiveIntegers (numberOfVoices, numberOfVoicesLabel, 1, 20, 15);
 -     }
 - 
 -     //==============================================================================
 -     void resized() override
 -     {
 -         Rectangle<int> r (proportionOfWidth (0.65f), 15, proportionOfWidth (0.25f), 3000);
 -         auto h = 24;
 -         auto hspace = 6;
 -         auto hbigspace = 18;
 - 
 -         isLowerZoneButton.setBounds (r.removeFromTop (h));
 -         r.removeFromTop (hspace);
 -         memberChannels.setBounds (r.removeFromTop (h));
 -         r.removeFromTop (hspace);
 -         notePitchbendRange.setBounds (r.removeFromTop (h));
 -         r.removeFromTop (hspace);
 -         masterPitchbendRange.setBounds (r.removeFromTop (h));
 - 
 -         legacyStartChannel  .setBounds (isLowerZoneButton .getBounds());
 -         legacyEndChannel    .setBounds (memberChannels    .getBounds());
 -         legacyPitchbendRange.setBounds (notePitchbendRange.getBounds());
 - 
 -         r.removeFromTop (hbigspace);
 - 
 -         auto buttonLeft = proportionOfWidth (0.5f);
 - 
 -         setZoneButton.setBounds (r.removeFromTop (h).withLeft (buttonLeft));
 -         r.removeFromTop (hspace);
 -         clearAllZonesButton.setBounds (r.removeFromTop (h).withLeft (buttonLeft));
 - 
 -         r.removeFromTop (hbigspace);
 - 
 -         auto toggleLeft = proportionOfWidth (0.25f);
 - 
 -         legacyModeEnabledToggle.setBounds (r.removeFromTop (h).withLeft (toggleLeft));
 -         r.removeFromTop (hspace);
 -         voiceStealingEnabledToggle.setBounds (r.removeFromTop (h).withLeft (toggleLeft));
 -         r.removeFromTop (hspace);
 -         numberOfVoices.setBounds (r.removeFromTop (h));
 -     }
 - 
 -     //==============================================================================
 -     bool isVoiceStealingEnabled() const  { return voiceStealingEnabledToggle.getToggleState(); }
 -     int getNumVoices() const             { return numberOfVoices.getText().getIntValue(); }
 - 
 -     std::function<void()> onSynthParametersChange;
 - 
 - private:
 -     //==============================================================================
 -     void initialiseComboBoxWithConsecutiveIntegers (ComboBox& comboBox, Label& labelToAttach,
 -                                                     int firstValue, int numValues, int valueToSelect,
 -                                                     bool makeVisible = true)
 -     {
 -         for (auto i = 0; i < numValues; ++i)
 -             comboBox.addItem (String (i + firstValue), i + 1);
 - 
 -         comboBox.setSelectedId (valueToSelect - firstValue + 1);
 -         labelToAttach.attachToComponent (&comboBox, true);
 - 
 -         if (makeVisible)
 -             addAndMakeVisible (comboBox);
 -         else
 -             addChildComponent (comboBox);
 - 
 -         if (&comboBox == &numberOfVoices)
 -             comboBox.onChange = [this] { numberOfVoicesChanged(); };
 -         else if (&comboBox == &legacyPitchbendRange)
 -             comboBox.onChange = [this] { if (legacyModeEnabledToggle.getToggleState()) legacyModePitchbendRangeChanged(); };
 -         else if (&comboBox == &legacyStartChannel || &comboBox == &legacyEndChannel)
 -             comboBox.onChange = [this] { if (legacyModeEnabledToggle.getToggleState()) legacyModeChannelRangeChanged(); };
 -     }
 - 
 -     //==============================================================================
 -     void setZoneButtonClicked()
 -     {
 -         auto isLowerZone = isLowerZoneButton.getToggleState();
 -         auto numMemberChannels = memberChannels.getText().getIntValue();
 -         auto perNotePb = notePitchbendRange.getText().getIntValue();
 -         auto masterPb = masterPitchbendRange.getText().getIntValue();
 - 
 -         auto zoneLayout = instrument.getZoneLayout();
 - 
 -         if (isLowerZone)
 -             zoneLayout.setLowerZone (numMemberChannels, perNotePb, masterPb);
 -         else
 -             zoneLayout.setUpperZone (numMemberChannels, perNotePb, masterPb);
 - 
 -         instrument.setZoneLayout (zoneLayout);
 -     }
 - 
 -     void clearAllZonesButtonClicked()
 -     {
 -         instrument.setZoneLayout ({});
 -     }
 - 
 -     void legacyModeEnabledToggleClicked()
 -     {
 -         auto legacyModeEnabled = legacyModeEnabledToggle.getToggleState();
 - 
 -         isLowerZoneButton   .setVisible (! legacyModeEnabled);
 -         memberChannels      .setVisible (! legacyModeEnabled);
 -         notePitchbendRange  .setVisible (! legacyModeEnabled);
 -         masterPitchbendRange.setVisible (! legacyModeEnabled);
 -         setZoneButton       .setVisible (! legacyModeEnabled);
 -         clearAllZonesButton .setVisible (! legacyModeEnabled);
 - 
 -         legacyStartChannel  .setVisible (legacyModeEnabled);
 -         legacyEndChannel    .setVisible (legacyModeEnabled);
 -         legacyPitchbendRange.setVisible (legacyModeEnabled);
 - 
 -         if (legacyModeEnabled)
 -         {
 -             if (areLegacyModeParametersValid())
 -             {
 -                 instrument.enableLegacyMode();
 - 
 -                 instrument.setLegacyModeChannelRange   (getLegacyModeChannelRange());
 -                 instrument.setLegacyModePitchbendRange (getLegacyModePitchbendRange());
 -             }
 -             else
 -             {
 -                 handleInvalidLegacyModeParameters();
 -             }
 -         }
 -         else
 -         {
 -             instrument.setZoneLayout ({ MPEZone (MPEZone::Type::lower, 15) });
 -         }
 -     }
 - 
 -     //==============================================================================
 -     void legacyModePitchbendRangeChanged()
 -     {
 -         jassert (legacyModeEnabledToggle.getToggleState() == true);
 - 
 -         instrument.setLegacyModePitchbendRange (getLegacyModePitchbendRange());
 -     }
 - 
 -     void legacyModeChannelRangeChanged()
 -     {
 -         jassert (legacyModeEnabledToggle.getToggleState() == true);
 - 
 -         if (areLegacyModeParametersValid())
 -             instrument.setLegacyModeChannelRange (getLegacyModeChannelRange());
 -         else
 -             handleInvalidLegacyModeParameters();
 -     }
 - 
 -     bool areLegacyModeParametersValid() const
 -     {
 -         return legacyStartChannel.getText().getIntValue() <= legacyEndChannel.getText().getIntValue();
 -     }
 - 
 -     void handleInvalidLegacyModeParameters() const
 -     {
 -         AlertWindow::showMessageBoxAsync (MessageBoxIconType::WarningIcon,
 -                                           "Invalid legacy mode channel layout",
 -                                           "Cannot set legacy mode start/end channel:\n"
 -                                           "The end channel must not be less than the start channel!",
 -                                           "Got it");
 -     }
 - 
 -     Range<int> getLegacyModeChannelRange() const
 -     {
 -         return { legacyStartChannel.getText().getIntValue(),
 -                  legacyEndChannel.getText().getIntValue() + 1 };
 -     }
 - 
 -     int getLegacyModePitchbendRange() const
 -     {
 -         return legacyPitchbendRange.getText().getIntValue();
 -     }
 - 
 -     //==============================================================================
 -     void voiceStealingEnabledToggleClicked()
 -     {
 -         jassert (onSynthParametersChange != nullptr);
 -         onSynthParametersChange();
 -     }
 - 
 -     void numberOfVoicesChanged()
 -     {
 -         jassert (onSynthParametersChange != nullptr);
 -         onSynthParametersChange();
 -     }
 - 
 -     //==============================================================================
 -     MPEInstrument& instrument;
 - 
 -     ComboBox memberChannels, masterPitchbendRange, notePitchbendRange;
 - 
 -     ToggleButton isLowerZoneButton  { "Lower zone" };
 - 
 -     Label memberChannelsLabel       { {}, "Nr. of member channels:" };
 -     Label masterPitchbendRangeLabel { {}, "Master pitchbend range (semitones):" };
 -     Label notePitchbendRangeLabel   { {}, "Note pitchbend range (semitones):" };
 - 
 -     TextButton setZoneButton        { "Set zone" };
 -     TextButton clearAllZonesButton  { "Clear all zones" };
 - 
 -     ComboBox legacyStartChannel, legacyEndChannel, legacyPitchbendRange;
 - 
 -     Label legacyStartChannelLabel   { {}, "First channel:" };
 -     Label legacyEndChannelLabel     { {}, "Last channel:" };
 -     Label legacyPitchbendRangeLabel { {}, "Pitchbend range (semitones):"};
 - 
 -     ToggleButton legacyModeEnabledToggle    { "Enable Legacy Mode" };
 -     ToggleButton voiceStealingEnabledToggle { "Enable synth voice stealing" };
 - 
 -     ComboBox numberOfVoices;
 -     Label numberOfVoicesLabel { {}, "Number of synth voices"};
 - 
 -     static constexpr int defaultMemberChannels       = 15,
 -                          defaultMasterPitchbendRange = 2,
 -                          defaultNotePitchbendRange   = 48;
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESetupComponent)
 - };
 - 
 - //==============================================================================
 - class ZoneLayoutComponent : public Component,
 -                             private MPEInstrument::Listener
 - {
 - public:
 -     //==============================================================================
 -     ZoneLayoutComponent (MPEInstrument& instr, ZoneColourPicker& zoneColourPicker)
 -         : instrument (instr),
 -           colourPicker (zoneColourPicker)
 -     {
 -         instrument.addListener (this);
 -     }
 - 
 -     ~ZoneLayoutComponent() override
 -     {
 -         instrument.removeListener (this);
 -     }
 - 
 -     //==============================================================================
 -     void paint (Graphics& g) override
 -     {
 -         paintBackground (g);
 - 
 -         if (instrument.isLegacyModeEnabled())
 -             paintLegacyMode (g);
 -         else
 -             paintZones (g);
 -     }
 - 
 - private:
 -     //==============================================================================
 -     void zoneLayoutChanged() override
 -     {
 -         repaint();
 -     }
 - 
 -     //==============================================================================
 -     void paintBackground (Graphics& g)
 -     {
 -         g.setColour (Colours::black);
 -         auto channelWidth = getChannelRectangleWidth();
 - 
 -         for (auto i = 0; i < numMidiChannels; ++i)
 -         {
 -             auto x = float (i) * channelWidth;
 -             Rectangle<int> channelArea ((int) x, 0, (int) channelWidth, getHeight());
 - 
 -             g.drawLine ({ x, 0.0f, x, float (getHeight()) });
 -             g.drawText (String (i + 1), channelArea.reduced (4, 4), Justification::topLeft, false);
 -         }
 -     }
 - 
 -     //==============================================================================
 -     void paintZones (Graphics& g)
 -     {
 -         auto channelWidth = getChannelRectangleWidth();
 - 
 -         auto zoneLayout = instrument.getZoneLayout();
 - 
 -         Array<MPEZoneLayout::Zone> activeZones;
 -         if (zoneLayout.getLowerZone().isActive())  activeZones.add (zoneLayout.getLowerZone());
 -         if (zoneLayout.getUpperZone().isActive())  activeZones.add (zoneLayout.getUpperZone());
 - 
 -         for (auto zone : activeZones)
 -         {
 -             auto zoneColour = colourPicker.getColourForZone (zone.isLowerZone());
 - 
 -             auto xPos = zone.isLowerZone() ? 0 : zone.getLastMemberChannel() - 1;
 - 
 -             Rectangle<int> zoneRect { int (channelWidth * (float) xPos), 20,
 -                                       int (channelWidth * (float) (zone.numMemberChannels + 1)), getHeight() - 20 };
 - 
 -             g.setColour (zoneColour);
 -             g.drawRect (zoneRect, 3);
 - 
 -             auto masterRect = zone.isLowerZone() ? zoneRect.removeFromLeft ((int) channelWidth) : zoneRect.removeFromRight ((int) channelWidth);
 - 
 -             g.setColour (zoneColour.withAlpha (0.3f));
 -             g.fillRect (masterRect);
 - 
 -             g.setColour (zoneColour.contrasting());
 -             g.drawText ("<>" + String (zone.masterPitchbendRange),  masterRect.reduced (4), Justification::top,    false);
 -             g.drawText ("<>" + String (zone.perNotePitchbendRange), masterRect.reduced (4), Justification::bottom, false);
 -         }
 -     }
 - 
 -     //==============================================================================
 -     void paintLegacyMode (Graphics& g)
 -     {
 -         auto channelRange = instrument.getLegacyModeChannelRange();
 -         auto startChannel = channelRange.getStart() - 1;
 -         auto numChannels  = channelRange.getEnd() - startChannel - 1;
 - 
 -         Rectangle<int> zoneRect (int (getChannelRectangleWidth() * (float) startChannel), 0,
 -                                  int (getChannelRectangleWidth() * (float) numChannels), getHeight());
 - 
 -         zoneRect.removeFromTop (20);
 - 
 -         g.setColour (Colours::white);
 -         g.drawRect (zoneRect, 3);
 -         g.drawText ("LGCY", zoneRect.reduced (4, 4), Justification::topLeft, false);
 -         g.drawText ("<>" + String (instrument.getLegacyModePitchbendRange()), zoneRect.reduced (4, 4), Justification::bottomLeft, false);
 -     }
 - 
 -     //==============================================================================
 -     float getChannelRectangleWidth() const noexcept
 -     {
 -         return (float) getWidth() / (float) numMidiChannels;
 -     }
 - 
 -     //==============================================================================
 -     static constexpr int numMidiChannels = 16;
 - 
 -     MPEInstrument& instrument;
 -     ZoneColourPicker& colourPicker;
 - };
 - 
 - //==============================================================================
 - class MPEDemoSynthVoice : public MPESynthesiserVoice
 - {
 - public:
 -     //==============================================================================
 -     MPEDemoSynthVoice() {}
 - 
 -     //==============================================================================
 -     void noteStarted() override
 -     {
 -         jassert (currentlyPlayingNote.isValid());
 -         jassert (currentlyPlayingNote.keyState == MPENote::keyDown
 -                  || currentlyPlayingNote.keyState == MPENote::keyDownAndSustained);
 - 
 -         level    .setTargetValue (currentlyPlayingNote.pressure.asUnsignedFloat());
 -         frequency.setTargetValue (currentlyPlayingNote.getFrequencyInHertz());
 -         timbre   .setTargetValue (currentlyPlayingNote.timbre.asUnsignedFloat());
 - 
 -         phase = 0.0;
 -         auto cyclesPerSample = frequency.getNextValue() / currentSampleRate;
 -         phaseDelta = MathConstants<double>::twoPi * cyclesPerSample;
 - 
 -         tailOff = 0.0;
 -     }
 - 
 -     void noteStopped (bool allowTailOff) override
 -     {
 -         jassert (currentlyPlayingNote.keyState == MPENote::off);
 - 
 -         if (allowTailOff)
 -         {
 -             // start a tail-off by setting this flag. The render callback will pick up on
 -             // this and do a fade out, calling clearCurrentNote() when it's finished.
 - 
 -             if (tailOff == 0.0) // we only need to begin a tail-off if it's not already doing so - the
 -                                 // stopNote method could be called more than once.
 -                 tailOff = 1.0;
 -         }
 -         else
 -         {
 -             // we're being told to stop playing immediately, so reset everything..
 -             clearCurrentNote();
 -             phaseDelta = 0.0;
 -         }
 -     }
 - 
 -     void notePressureChanged() override
 -     {
 -         level.setTargetValue (currentlyPlayingNote.pressure.asUnsignedFloat());
 -     }
 - 
 -     void notePitchbendChanged() override
 -     {
 -         frequency.setTargetValue (currentlyPlayingNote.getFrequencyInHertz());
 -     }
 - 
 -     void noteTimbreChanged() override
 -     {
 -         timbre.setTargetValue (currentlyPlayingNote.timbre.asUnsignedFloat());
 -     }
 - 
 -     void noteKeyStateChanged() override {}
 - 
 -     void setCurrentSampleRate (double newRate) override
 -     {
 -         if (currentSampleRate != newRate)
 -         {
 -             noteStopped (false);
 -             currentSampleRate = newRate;
 - 
 -             level    .reset (currentSampleRate, smoothingLengthInSeconds);
 -             timbre   .reset (currentSampleRate, smoothingLengthInSeconds);
 -             frequency.reset (currentSampleRate, smoothingLengthInSeconds);
 -         }
 -     }
 - 
 -     //==============================================================================
 -     virtual void renderNextBlock (AudioBuffer<float>& outputBuffer,
 -                                   int startSample,
 -                                   int numSamples) override
 -     {
 -         if (phaseDelta != 0.0)
 -         {
 -             if (tailOff > 0.0)
 -             {
 -                 while (--numSamples >= 0)
 -                 {
 -                     auto currentSample = getNextSample() * (float) tailOff;
 - 
 -                     for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
 -                         outputBuffer.addSample (i, startSample, currentSample);
 - 
 -                     ++startSample;
 - 
 -                     tailOff *= 0.99;
 - 
 -                     if (tailOff <= 0.005)
 -                     {
 -                         clearCurrentNote();
 - 
 -                         phaseDelta = 0.0;
 -                         break;
 -                     }
 -                 }
 -             }
 -             else
 -             {
 -                 while (--numSamples >= 0)
 -                 {
 -                     auto currentSample = getNextSample();
 - 
 -                     for (auto i = outputBuffer.getNumChannels(); --i >= 0;)
 -                         outputBuffer.addSample (i, startSample, currentSample);
 - 
 -                     ++startSample;
 -                 }
 -             }
 -         }
 -     }
 - 
 -     using MPESynthesiserVoice::renderNextBlock;
 - 
 - private:
 -     //==============================================================================
 -     float getNextSample() noexcept
 -     {
 -         auto levelDb = (level.getNextValue() - 1.0) * maxLevelDb;
 -         auto amplitude = pow (10.0f, 0.05f * levelDb) * maxLevel;
 - 
 -         // timbre is used to blend between a sine and a square.
 -         auto f1 = std::sin (phase);
 -         auto f2 = copysign (1.0, f1);
 -         auto a2 = timbre.getNextValue();
 -         auto a1 = 1.0 - a2;
 - 
 -         auto nextSample = float (amplitude * ((a1 * f1) + (a2 * f2)));
 - 
 -         auto cyclesPerSample = frequency.getNextValue() / currentSampleRate;
 -         phaseDelta = MathConstants<double>::twoPi * cyclesPerSample;
 -         phase = std::fmod (phase + phaseDelta, MathConstants<double>::twoPi);
 - 
 -         return nextSample;
 -     }
 - 
 -     //==============================================================================
 -     SmoothedValue<double> level, timbre, frequency;
 - 
 -     double phase      = 0.0;
 -     double phaseDelta = 0.0;
 -     double tailOff    = 0.0;
 - 
 -     const double maxLevel   = 0.05;
 -     const double maxLevelDb = 31.0;
 -     const double smoothingLengthInSeconds = 0.01;
 - };
 - 
 - //==============================================================================
 - class MPEDemo : public Component,
 -                 private AudioIODeviceCallback,
 -                 private MidiInputCallback,
 -                 private MPEInstrument::Listener
 - {
 - public:
 -     //==============================================================================
 -     MPEDemo()
 -     {
 -        #ifndef JUCE_DEMO_RUNNER
 -         audioDeviceManager.initialise (0, 2, nullptr, true, {}, nullptr);
 -        #endif
 - 
 -         audioDeviceManager.addMidiInputDeviceCallback ({}, this);
 -         audioDeviceManager.addAudioCallback (this);
 - 
 -         addAndMakeVisible (audioSetupComp);
 -         addAndMakeVisible (mpeSetupComp);
 -         addAndMakeVisible (zoneLayoutComp);
 -         addAndMakeVisible (keyboardComponent);
 - 
 -         synth.setVoiceStealingEnabled (false);
 -         for (auto i = 0; i < 15; ++i)
 -             synth.addVoice (new MPEDemoSynthVoice());
 - 
 -         mpeSetupComp.onSynthParametersChange = [this]
 -         {
 -             synth.setVoiceStealingEnabled (mpeSetupComp.isVoiceStealingEnabled());
 - 
 -             auto numVoices = mpeSetupComp.getNumVoices();
 - 
 -             if (numVoices < synth.getNumVoices())
 -             {
 -                 synth.reduceNumVoices (numVoices);
 -             }
 -             else
 -             {
 -                 while (synth.getNumVoices() < numVoices)
 -                     synth.addVoice (new MPEDemoSynthVoice());
 -             }
 -         };
 - 
 -         instrument.addListener (this);
 - 
 -         setSize (880, 720);
 -     }
 - 
 -     ~MPEDemo() override
 -     {
 -         audioDeviceManager.removeMidiInputDeviceCallback ({}, this);
 -         audioDeviceManager.removeAudioCallback (this);
 -     }
 - 
 -     //==============================================================================
 -     void resized() override
 -     {
 -         auto zoneLayoutCompHeight = 60;
 -         auto audioSetupCompRelativeWidth = 0.55f;
 - 
 -         auto r = getLocalBounds();
 - 
 -         keyboardComponent.setBounds (r.removeFromBottom (150));
 -         r.reduce (10, 10);
 - 
 -         zoneLayoutComp.setBounds (r.removeFromBottom (zoneLayoutCompHeight));
 -         audioSetupComp.setBounds (r.removeFromLeft (proportionOfWidth (audioSetupCompRelativeWidth)));
 -         mpeSetupComp  .setBounds (r);
 -     }
 - 
 -     //==============================================================================
 -     void audioDeviceIOCallbackWithContext (const float* const* inputChannelData, int numInputChannels,
 -                                            float* const* outputChannelData, int numOutputChannels,
 -                                            int numSamples, const AudioIODeviceCallbackContext& context) override
 -     {
 -         ignoreUnused (inputChannelData, numInputChannels, context);
 - 
 -         AudioBuffer<float> buffer (outputChannelData, numOutputChannels, numSamples);
 -         buffer.clear();
 - 
 -         MidiBuffer incomingMidi;
 -         midiCollector.removeNextBlockOfMessages (incomingMidi, numSamples);
 -         synth.renderNextBlock (buffer, incomingMidi, 0, numSamples);
 -     }
 - 
 -     void audioDeviceAboutToStart (AudioIODevice* device) override
 -     {
 -         auto sampleRate = device->getCurrentSampleRate();
 -         midiCollector.reset (sampleRate);
 -         synth.setCurrentPlaybackSampleRate (sampleRate);
 -     }
 - 
 -     void audioDeviceStopped() override {}
 - 
 - private:
 -     //==============================================================================
 -     void handleIncomingMidiMessage (MidiInput* /*source*/,
 -                                     const MidiMessage& message) override
 -     {
 -         instrument.processNextMidiEvent (message);
 -         midiCollector.addMessageToQueue (message);
 -     }
 - 
 -     //==============================================================================
 -     void zoneLayoutChanged() override
 -     {
 -         if (instrument.isLegacyModeEnabled())
 -         {
 -             colourPicker.setLegacyModeEnabled (true);
 - 
 -             synth.enableLegacyMode (instrument.getLegacyModePitchbendRange(),
 -                                     instrument.getLegacyModeChannelRange());
 -         }
 -         else
 -         {
 -             colourPicker.setLegacyModeEnabled (false);
 - 
 -             auto zoneLayout = instrument.getZoneLayout();
 - 
 -             if (auto* midiOutput = audioDeviceManager.getDefaultMidiOutput())
 -                 midiOutput->sendBlockOfMessagesNow (MPEMessages::setZoneLayout (zoneLayout));
 - 
 -             synth.setZoneLayout (zoneLayout);
 -             colourPicker.setZoneLayout (zoneLayout);
 -         }
 -     }
 - 
 -     //==============================================================================
 -     // if this PIP is running inside the demo runner, we'll use the shared device manager instead
 -    #ifndef JUCE_DEMO_RUNNER
 -     AudioDeviceManager audioDeviceManager;
 -    #else
 -     AudioDeviceManager& audioDeviceManager { getSharedAudioDeviceManager (0, 2) };
 -    #endif
 - 
 -     AudioDeviceSelectorComponent audioSetupComp { audioDeviceManager, 0, 0, 0, 256, true, true, true, false };
 -     MidiMessageCollector midiCollector;
 - 
 -     MPEInstrument instrument  { MPEZone (MPEZone::Type::lower, 15) };
 - 
 -     ZoneColourPicker colourPicker;
 -     MPESetupComponent mpeSetupComp      { instrument };
 -     ZoneLayoutComponent zoneLayoutComp  { instrument, colourPicker};
 - 
 -     MPESynthesiser synth                   { instrument };
 -     MPEKeyboardComponent keyboardComponent { instrument, MPEKeyboardComponent::horizontalKeyboard };
 - 
 -     //==============================================================================
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEDemo)
 - };
 
 
  |