| 
							- /*
 -   ==============================================================================
 - 
 -    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:             SamplerPlugin
 -  version:          1.0.0
 -  vendor:           JUCE
 -  website:          http://juce.com
 -  description:      Sampler audio plugin.
 - 
 -  dependencies:     juce_audio_basics, juce_audio_devices, juce_audio_formats,
 -                    juce_audio_plugin_client, 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
 - 
 -  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1
 - 
 -  type:             AudioProcessor
 -  mainClass:        SamplerAudioProcessor
 - 
 -  useLocalCopy:     1
 - 
 -  pluginCharacteristics: pluginIsSynth, pluginWantsMidiIn
 - 
 -  END_JUCE_PIP_METADATA
 - 
 - *******************************************************************************/
 - 
 - #pragma once
 - 
 - #include "../Assets/DemoUtilities.h"
 - 
 - #include <array>
 - #include <atomic>
 - #include <memory>
 - #include <vector>
 - #include <tuple>
 - #include <iomanip>
 - #include <sstream>
 - #include <functional>
 - #include <mutex>
 - 
 - namespace IDs
 - {
 - 
 - #define DECLARE_ID(name) const juce::Identifier name (#name);
 - 
 - DECLARE_ID (DATA_MODEL)
 - DECLARE_ID (sampleReader)
 - DECLARE_ID (centreFrequencyHz)
 - DECLARE_ID (loopMode)
 - DECLARE_ID (loopPointsSeconds)
 - 
 - DECLARE_ID (MPE_SETTINGS)
 - DECLARE_ID (synthVoices)
 - DECLARE_ID (voiceStealingEnabled)
 - DECLARE_ID (legacyModeEnabled)
 - DECLARE_ID (mpeZoneLayout)
 - DECLARE_ID (legacyFirstChannel)
 - DECLARE_ID (legacyLastChannel)
 - DECLARE_ID (legacyPitchbendRange)
 - 
 - DECLARE_ID (VISIBLE_RANGE)
 - DECLARE_ID (totalRange)
 - DECLARE_ID (visibleRange)
 - 
 - #undef DECLARE_ID
 - 
 - } // namespace IDs
 - 
 - enum class LoopMode
 - {
 -     none,
 -     forward,
 -     pingpong
 - };
 - 
 - // We want to send type-erased commands to the audio thread, but we also
 - // want those commands to contain move-only resources, so that we can
 - // construct resources on the gui thread, and then transfer ownership
 - // cheaply to the audio thread. We can't do this with std::function
 - // because it enforces that functions are copy-constructible.
 - // Therefore, we use a very simple templated type-eraser here.
 - template <typename Proc>
 - struct Command
 - {
 -     virtual ~Command() noexcept                    = default;
 -     virtual void run (Proc& proc) = 0;
 - };
 - 
 - template <typename Proc, typename Func>
 - class TemplateCommand  : public Command<Proc>,
 -                          private Func
 - {
 - public:
 -     template <typename FuncPrime>
 -     explicit TemplateCommand (FuncPrime&& funcPrime)
 -         : Func (std::forward<FuncPrime> (funcPrime))
 -     {}
 - 
 -     void run (Proc& proc) override { (*this) (proc); }
 - };
 - 
 - template <typename Proc>
 - class CommandFifo final
 - {
 - public:
 -     explicit CommandFifo (int size)
 -         : buffer ((size_t) size),
 -           abstractFifo (size)
 -     {}
 - 
 -     CommandFifo()
 -         : CommandFifo (1024)
 -     {}
 - 
 -     template <typename Item>
 -     void push (Item&& item) noexcept
 -     {
 -         auto command = makeCommand (std::forward<Item> (item));
 - 
 -         abstractFifo.write (1).forEach ([&] (int index)
 -         {
 -             buffer[size_t (index)] = std::move (command);
 -         });
 -     }
 - 
 -     void call (Proc& proc) noexcept
 -     {
 -         abstractFifo.read (abstractFifo.getNumReady()).forEach ([&] (int index)
 -         {
 -             buffer[size_t (index)]->run (proc);
 -         });
 -     }
 - 
 - private:
 -     template <typename Func>
 -     static std::unique_ptr<Command<Proc>> makeCommand (Func&& func)
 -     {
 -         using Decayed = typename std::decay<Func>::type;
 -         return std::make_unique<TemplateCommand<Proc, Decayed>> (std::forward<Func> (func));
 -     }
 - 
 -     std::vector<std::unique_ptr<Command<Proc>>> buffer;
 -     AbstractFifo abstractFifo;
 - };
 - 
 - //==============================================================================
 - // Represents the constant parts of an audio sample: its name, sample rate,
 - // length, and the audio sample data itself.
 - // Samples might be pretty big, so we'll keep shared_ptrs to them most of the
 - // time, to reduce duplication and copying.
 - class Sample final
 - {
 - public:
 -     Sample (AudioFormatReader& source, double maxSampleLengthSecs)
 -         : sourceSampleRate (source.sampleRate),
 -           length (jmin (int (source.lengthInSamples),
 -                         int (maxSampleLengthSecs * sourceSampleRate))),
 -           data (jmin (2, int (source.numChannels)), length + 4)
 -     {
 -         if (length == 0)
 -             throw std::runtime_error ("Unable to load sample");
 - 
 -         source.read (&data, 0, length + 4, 0, true, true);
 -     }
 - 
 -     double getSampleRate() const                    { return sourceSampleRate; }
 -     int getLength() const                           { return length; }
 -     const AudioBuffer<float>& getBuffer() const     { return data; }
 - 
 - private:
 -     double sourceSampleRate;
 -     int length;
 -     AudioBuffer<float> data;
 - };
 - 
 - //==============================================================================
 - // A class which contains all the information related to sample-playback, such
 - // as sample data, loop points, and loop kind.
 - // It is expected that multiple sampler voices will maintain pointers to a
 - // single instance of this class, to avoid redundant duplication of sample
 - // data in memory.
 - class MPESamplerSound final
 - {
 - public:
 -     void setSample (std::unique_ptr<Sample> value)
 -     {
 -         sample = move (value);
 -         setLoopPointsInSeconds (loopPoints);
 -     }
 - 
 -     Sample* getSample() const
 -     {
 -         return sample.get();
 -     }
 - 
 -     void setLoopPointsInSeconds (Range<double> value)
 -     {
 -         loopPoints = sample == nullptr ? value
 -                                        : Range<double> (0, sample->getLength() / sample->getSampleRate())
 -                                                         .constrainRange (value);
 -     }
 - 
 -     Range<double> getLoopPointsInSeconds() const
 -     {
 -         return loopPoints;
 -     }
 - 
 -     void setCentreFrequencyInHz (double centre)
 -     {
 -         centreFrequencyInHz = centre;
 -     }
 - 
 -     double getCentreFrequencyInHz() const
 -     {
 -         return centreFrequencyInHz;
 -     }
 - 
 -     void setLoopMode (LoopMode type)
 -     {
 -         loopMode = type;
 -     }
 - 
 -     LoopMode getLoopMode() const
 -     {
 -         return loopMode;
 -     }
 - 
 - private:
 -     std::unique_ptr<Sample> sample;
 -     double centreFrequencyInHz { 440.0 };
 -     Range<double> loopPoints;
 -     LoopMode loopMode { LoopMode::none };
 - };
 - 
 - //==============================================================================
 - class MPESamplerVoice  : public MPESynthesiserVoice
 - {
 - public:
 -     explicit MPESamplerVoice (std::shared_ptr<const MPESamplerSound> sound)
 -         : samplerSound (std::move (sound))
 -     {
 -         jassert (samplerSound != nullptr);
 -     }
 - 
 -     void noteStarted() override
 -     {
 -         jassert (currentlyPlayingNote.isValid());
 -         jassert (currentlyPlayingNote.keyState == MPENote::keyDown
 -               || currentlyPlayingNote.keyState == MPENote::keyDownAndSustained);
 - 
 -         level    .setTargetValue (currentlyPlayingNote.noteOnVelocity.asUnsignedFloat());
 -         frequency.setTargetValue (currentlyPlayingNote.getFrequencyInHertz());
 - 
 -         auto loopPoints = samplerSound->getLoopPointsInSeconds();
 -         loopBegin.setTargetValue (loopPoints.getStart() * samplerSound->getSample()->getSampleRate());
 -         loopEnd  .setTargetValue (loopPoints.getEnd()   * samplerSound->getSample()->getSampleRate());
 - 
 -         for (auto smoothed : { &level, &frequency, &loopBegin, &loopEnd })
 -             smoothed->reset (currentSampleRate, smoothingLengthInSeconds);
 - 
 -         previousPressure = currentlyPlayingNote.pressure.asUnsignedFloat();
 -         currentSamplePos = 0.0;
 -         tailOff          = 0.0;
 -     }
 - 
 -     void noteStopped (bool allowTailOff) override
 -     {
 -         jassert (currentlyPlayingNote.keyState == MPENote::off);
 - 
 -         if (allowTailOff && tailOff == 0.0)
 -             tailOff = 1.0;
 -         else
 -             stopNote();
 -     }
 - 
 -     void notePressureChanged() override
 -     {
 -         const auto currentPressure = static_cast<double> (currentlyPlayingNote.pressure.asUnsignedFloat());
 -         const auto deltaPressure = currentPressure - previousPressure;
 -         level.setTargetValue (jlimit (0.0, 1.0, level.getCurrentValue() + deltaPressure));
 -         previousPressure = currentPressure;
 -     }
 - 
 -     void notePitchbendChanged() override
 -     {
 -         frequency.setTargetValue (currentlyPlayingNote.getFrequencyInHertz());
 -     }
 - 
 -     void noteTimbreChanged()   override {}
 -     void noteKeyStateChanged() override {}
 - 
 -     void renderNextBlock (AudioBuffer<float>& outputBuffer,
 -                           int startSample,
 -                           int numSamples) override
 -     {
 -         render (outputBuffer, startSample, numSamples);
 -     }
 - 
 -     void renderNextBlock (AudioBuffer<double>& outputBuffer,
 -                           int startSample,
 -                           int numSamples) override
 -     {
 -         render (outputBuffer, startSample, numSamples);
 -     }
 - 
 -     double getCurrentSamplePosition() const
 -     {
 -         return currentSamplePos;
 -     }
 - 
 - private:
 -     template <typename Element>
 -     void render (AudioBuffer<Element>& outputBuffer, int startSample, int numSamples)
 -     {
 -         jassert (samplerSound->getSample() != nullptr);
 - 
 -         auto loopPoints = samplerSound->getLoopPointsInSeconds();
 -         loopBegin.setTargetValue (loopPoints.getStart() * samplerSound->getSample()->getSampleRate());
 -         loopEnd  .setTargetValue (loopPoints.getEnd()   * samplerSound->getSample()->getSampleRate());
 - 
 -         auto& data = samplerSound->getSample()->getBuffer();
 - 
 -         auto inL = data.getReadPointer (0);
 -         auto inR = data.getNumChannels() > 1 ? data.getReadPointer (1) : nullptr;
 - 
 -         auto outL = outputBuffer.getWritePointer (0, startSample);
 - 
 -         if (outL == nullptr)
 -             return;
 - 
 -         auto outR = outputBuffer.getNumChannels() > 1 ? outputBuffer.getWritePointer (1, startSample)
 -                                                       : nullptr;
 - 
 -         size_t writePos = 0;
 - 
 -         while (--numSamples >= 0 && renderNextSample (inL, inR, outL, outR, writePos))
 -             writePos += 1;
 -     }
 - 
 -     template <typename Element>
 -     bool renderNextSample (const float* inL,
 -                            const float* inR,
 -                            Element* outL,
 -                            Element* outR,
 -                            size_t writePos)
 -     {
 -         auto currentLevel     = level.getNextValue();
 -         auto currentFrequency = frequency.getNextValue();
 -         auto currentLoopBegin = loopBegin.getNextValue();
 -         auto currentLoopEnd   = loopEnd.getNextValue();
 - 
 -         if (isTailingOff())
 -         {
 -             currentLevel *= tailOff;
 -             tailOff *= 0.9999;
 - 
 -             if (tailOff < 0.005)
 -             {
 -                 stopNote();
 -                 return false;
 -             }
 -         }
 - 
 -         auto pos      = (int) currentSamplePos;
 -         auto nextPos  = pos + 1;
 -         auto alpha    = (Element) (currentSamplePos - pos);
 -         auto invAlpha = 1.0f - alpha;
 - 
 -         // just using a very simple linear interpolation here..
 -         auto l = static_cast<Element> (currentLevel * (inL[pos] * invAlpha + inL[nextPos] * alpha));
 -         auto r = static_cast<Element> ((inR != nullptr) ? currentLevel * (inR[pos] * invAlpha + inR[nextPos] * alpha)
 -                                                         : l);
 - 
 -         if (outR != nullptr)
 -         {
 -             outL[writePos] += l;
 -             outR[writePos] += r;
 -         }
 -         else
 -         {
 -             outL[writePos] += (l + r) * 0.5f;
 -         }
 - 
 -         std::tie (currentSamplePos, currentDirection) = getNextState (currentFrequency,
 -                                                                       currentLoopBegin,
 -                                                                       currentLoopEnd);
 - 
 -         if (currentSamplePos > samplerSound->getSample()->getLength())
 -         {
 -             stopNote();
 -             return false;
 -         }
 - 
 -         return true;
 -     }
 - 
 -     double getSampleValue() const;
 - 
 -     bool isTailingOff() const
 -     {
 -         return tailOff != 0.0;
 -     }
 - 
 -     void stopNote()
 -     {
 -         clearCurrentNote();
 -         currentSamplePos = 0.0;
 -     }
 - 
 -     enum class Direction
 -     {
 -         forward,
 -         backward
 -     };
 - 
 -     std::tuple<double, Direction> getNextState (double freq,
 -                                                 double begin,
 -                                                 double end) const
 -     {
 -         auto nextPitchRatio = freq / samplerSound->getCentreFrequencyInHz();
 - 
 -         auto nextSamplePos = currentSamplePos;
 -         auto nextDirection = currentDirection;
 - 
 -         // Move the current sample pos in the correct direction
 -         switch (currentDirection)
 -         {
 -             case Direction::forward:
 -                 nextSamplePos += nextPitchRatio;
 -                 break;
 - 
 -             case Direction::backward:
 -                 nextSamplePos -= nextPitchRatio;
 -                 break;
 - 
 -             default:
 -                 break;
 -         }
 - 
 -         // Update current sample position, taking loop mode into account
 -         // If the loop mode was changed while we were travelling backwards, deal
 -         // with it gracefully.
 -         if (nextDirection == Direction::backward && nextSamplePos < begin)
 -         {
 -             nextSamplePos = begin;
 -             nextDirection = Direction::forward;
 - 
 -             return std::tuple<double, Direction> (nextSamplePos, nextDirection);
 -         }
 - 
 -         if (samplerSound->getLoopMode() == LoopMode::none)
 -             return std::tuple<double, Direction> (nextSamplePos, nextDirection);
 - 
 -         if (nextDirection == Direction::forward && end < nextSamplePos && !isTailingOff())
 -         {
 -             if (samplerSound->getLoopMode() == LoopMode::forward)
 -                 nextSamplePos = begin;
 -             else if (samplerSound->getLoopMode() == LoopMode::pingpong)
 -             {
 -                 nextSamplePos = end;
 -                 nextDirection = Direction::backward;
 -             }
 -         }
 -         return std::tuple<double, Direction> (nextSamplePos, nextDirection);
 -     }
 - 
 -     std::shared_ptr<const MPESamplerSound> samplerSound;
 -     SmoothedValue<double> level { 0 };
 -     SmoothedValue<double> frequency { 0 };
 -     SmoothedValue<double> loopBegin;
 -     SmoothedValue<double> loopEnd;
 -     double previousPressure { 0 };
 -     double currentSamplePos { 0 };
 -     double tailOff { 0 };
 -     Direction currentDirection { Direction::forward };
 -     double smoothingLengthInSeconds { 0.01 };
 - };
 - 
 - template <typename Contents>
 - class ReferenceCountingAdapter  : public ReferenceCountedObject
 - {
 - public:
 -     template <typename... Args>
 -     explicit ReferenceCountingAdapter (Args&&... args)
 -         : contents (std::forward<Args> (args)...)
 -     {}
 - 
 -     const Contents& get() const
 -     {
 -         return contents;
 -     }
 - 
 -     Contents& get()
 -     {
 -         return contents;
 -     }
 - 
 - private:
 -     Contents contents;
 - };
 - 
 - template <typename Contents, typename... Args>
 - std::unique_ptr<ReferenceCountingAdapter<Contents>>
 - make_reference_counted (Args&&... args)
 - {
 -     auto adapter = new ReferenceCountingAdapter<Contents> (std::forward<Args> (args)...);
 -     return std::unique_ptr<ReferenceCountingAdapter<Contents>> (adapter);
 - }
 - 
 - //==============================================================================
 - inline std::unique_ptr<AudioFormatReader> makeAudioFormatReader (AudioFormatManager& manager,
 -                                                                  const void* sampleData,
 -                                                                  size_t dataSize)
 - {
 -     return std::unique_ptr<AudioFormatReader> (manager.createReaderFor (std::make_unique<MemoryInputStream> (sampleData,
 -                                                                                                              dataSize,
 -                                                                                                              false)));
 - }
 - 
 - inline std::unique_ptr<AudioFormatReader> makeAudioFormatReader (AudioFormatManager& manager,
 -                                                                  const File& file)
 - {
 -     return std::unique_ptr<AudioFormatReader> (manager.createReaderFor (file));
 - }
 - 
 - //==============================================================================
 - class AudioFormatReaderFactory
 - {
 - public:
 -     AudioFormatReaderFactory() = default;
 -     AudioFormatReaderFactory (const AudioFormatReaderFactory&) = default;
 -     AudioFormatReaderFactory (AudioFormatReaderFactory&&) = default;
 -     AudioFormatReaderFactory& operator= (const AudioFormatReaderFactory&) = default;
 -     AudioFormatReaderFactory& operator= (AudioFormatReaderFactory&&) = default;
 - 
 -     virtual ~AudioFormatReaderFactory() noexcept = default;
 - 
 -     virtual std::unique_ptr<AudioFormatReader> make (AudioFormatManager&) const = 0;
 -     virtual std::unique_ptr<AudioFormatReaderFactory> clone() const = 0;
 - };
 - 
 - //==============================================================================
 - class MemoryAudioFormatReaderFactory  : public AudioFormatReaderFactory
 - {
 - public:
 -     MemoryAudioFormatReaderFactory (const void* sampleDataIn, size_t dataSizeIn)
 -         : sampleData (sampleDataIn),
 -           dataSize (dataSizeIn)
 -     {}
 - 
 -     std::unique_ptr<AudioFormatReader> make (AudioFormatManager& manager) const override
 -     {
 -         return makeAudioFormatReader (manager, sampleData, dataSize);
 -     }
 - 
 -     std::unique_ptr<AudioFormatReaderFactory> clone() const override
 -     {
 -         return std::unique_ptr<AudioFormatReaderFactory> (new MemoryAudioFormatReaderFactory (*this));
 -     }
 - 
 - private:
 -     const void* sampleData;
 -     size_t dataSize;
 - };
 - 
 - //==============================================================================
 - class FileAudioFormatReaderFactory  : public AudioFormatReaderFactory
 - {
 - public:
 -     explicit FileAudioFormatReaderFactory (File fileIn)
 -         : file (std::move (fileIn))
 -     {}
 - 
 -     std::unique_ptr<AudioFormatReader> make (AudioFormatManager& manager) const override
 -     {
 -         return makeAudioFormatReader (manager, file);
 -     }
 - 
 -     std::unique_ptr<AudioFormatReaderFactory> clone() const override
 -     {
 -         return std::unique_ptr<AudioFormatReaderFactory> (new FileAudioFormatReaderFactory (*this));
 -     }
 - 
 - private:
 -     File file;
 - };
 - 
 - namespace juce
 - {
 - 
 - template<>
 - struct VariantConverter<LoopMode>
 - {
 -     static LoopMode fromVar (const var& v)
 -     {
 -         return static_cast<LoopMode> (int (v));
 -     }
 - 
 -     static var toVar (LoopMode loopMode)
 -     {
 -         return static_cast<int> (loopMode);
 -     }
 - };
 - 
 - template <typename Wrapped>
 - struct GenericVariantConverter
 - {
 -     static Wrapped fromVar (const var& v)
 -     {
 -         auto cast = dynamic_cast<ReferenceCountingAdapter<Wrapped>*> (v.getObject());
 -         jassert (cast != nullptr);
 -         return cast->get();
 -     }
 - 
 -     static var toVar (Wrapped range)
 -     {
 -         return { make_reference_counted<Wrapped> (std::move (range)).release() };
 -     }
 - };
 - 
 - template <typename Numeric>
 - struct VariantConverter<Range<Numeric>>  : GenericVariantConverter<Range<Numeric>> {};
 - 
 - template<>
 - struct VariantConverter<MPEZoneLayout>  : GenericVariantConverter<MPEZoneLayout> {};
 - 
 - template<>
 - struct VariantConverter<std::shared_ptr<AudioFormatReaderFactory>>
 -     : GenericVariantConverter<std::shared_ptr<AudioFormatReaderFactory>>
 - {};
 - 
 - } // namespace juce
 - 
 - //==============================================================================
 - class VisibleRangeDataModel  : private ValueTree::Listener
 - {
 - public:
 -     class Listener
 -     {
 -     public:
 -         virtual ~Listener() noexcept = default;
 -         virtual void totalRangeChanged   (Range<double>) {}
 -         virtual void visibleRangeChanged (Range<double>) {}
 -     };
 - 
 -     VisibleRangeDataModel()
 -         : VisibleRangeDataModel (ValueTree (IDs::VISIBLE_RANGE))
 -     {}
 - 
 -     explicit VisibleRangeDataModel (const ValueTree& vt)
 -         : valueTree (vt),
 -           totalRange   (valueTree, IDs::totalRange,   nullptr),
 -           visibleRange (valueTree, IDs::visibleRange, nullptr)
 -     {
 -         jassert (valueTree.hasType (IDs::VISIBLE_RANGE));
 -         valueTree.addListener (this);
 -     }
 - 
 -     VisibleRangeDataModel (const VisibleRangeDataModel& other)
 -         : VisibleRangeDataModel (other.valueTree)
 -     {}
 - 
 -     VisibleRangeDataModel& operator= (const VisibleRangeDataModel& other)
 -     {
 -         auto copy (other);
 -         swap (copy);
 -         return *this;
 -     }
 - 
 -     Range<double> getTotalRange() const
 -     {
 -         return totalRange;
 -     }
 - 
 -     void setTotalRange (Range<double> value, UndoManager* undoManager)
 -     {
 -         totalRange.setValue (value, undoManager);
 -         setVisibleRange (visibleRange, undoManager);
 -     }
 - 
 -     Range<double> getVisibleRange() const
 -     {
 -         return visibleRange;
 -     }
 - 
 -     void setVisibleRange (Range<double> value, UndoManager* undoManager)
 -     {
 -         visibleRange.setValue (totalRange.get().constrainRange (value), undoManager);
 -     }
 - 
 -     void addListener (Listener& listener)
 -     {
 -         listenerList.add (&listener);
 -     }
 - 
 -     void removeListener (Listener& listener)
 -     {
 -         listenerList.remove (&listener);
 -     }
 - 
 -     void swap (VisibleRangeDataModel& other) noexcept
 -     {
 -         using std::swap;
 -         swap (other.valueTree, valueTree);
 -     }
 - 
 - private:
 -     void valueTreePropertyChanged (ValueTree&, const Identifier& property) override
 -     {
 -         if (property == IDs::totalRange)
 -         {
 -             totalRange.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.totalRangeChanged (totalRange); });
 -         }
 -         else if (property == IDs::visibleRange)
 -         {
 -             visibleRange.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.visibleRangeChanged (visibleRange); });
 -         }
 -     }
 - 
 -     void valueTreeChildAdded        (ValueTree&, ValueTree&)      override { jassertfalse; }
 -     void valueTreeChildRemoved      (ValueTree&, ValueTree&, int) override { jassertfalse; }
 -     void valueTreeChildOrderChanged (ValueTree&, int, int)        override { jassertfalse; }
 -     void valueTreeParentChanged     (ValueTree&)                  override { jassertfalse; }
 - 
 -     ValueTree valueTree;
 - 
 -     CachedValue<Range<double>> totalRange;
 -     CachedValue<Range<double>> visibleRange;
 - 
 -     ListenerList<Listener> listenerList;
 - };
 - 
 - //==============================================================================
 - class MPESettingsDataModel  : private ValueTree::Listener
 - {
 - public:
 -     class Listener
 -     {
 -     public:
 -         virtual ~Listener() noexcept = default;
 -         virtual void synthVoicesChanged (int) {}
 -         virtual void voiceStealingEnabledChanged (bool) {}
 -         virtual void legacyModeEnabledChanged (bool) {}
 -         virtual void mpeZoneLayoutChanged (const MPEZoneLayout&) {}
 -         virtual void legacyFirstChannelChanged (int) {}
 -         virtual void legacyLastChannelChanged (int) {}
 -         virtual void legacyPitchbendRangeChanged (int) {}
 -     };
 - 
 -     MPESettingsDataModel()
 -         : MPESettingsDataModel (ValueTree (IDs::MPE_SETTINGS))
 -     {}
 - 
 -     explicit MPESettingsDataModel (const ValueTree& vt)
 -         : valueTree (vt),
 -           synthVoices          (valueTree, IDs::synthVoices,          nullptr, 15),
 -           voiceStealingEnabled (valueTree, IDs::voiceStealingEnabled, nullptr, false),
 -           legacyModeEnabled    (valueTree, IDs::legacyModeEnabled,    nullptr, true),
 -           mpeZoneLayout        (valueTree, IDs::mpeZoneLayout,        nullptr, {}),
 -           legacyFirstChannel   (valueTree, IDs::legacyFirstChannel,   nullptr, 1),
 -           legacyLastChannel    (valueTree, IDs::legacyLastChannel,    nullptr, 15),
 -           legacyPitchbendRange (valueTree, IDs::legacyPitchbendRange, nullptr, 48)
 -     {
 -         jassert (valueTree.hasType (IDs::MPE_SETTINGS));
 -         valueTree.addListener (this);
 -     }
 - 
 -     MPESettingsDataModel (const MPESettingsDataModel& other)
 -         : MPESettingsDataModel (other.valueTree)
 -     {}
 - 
 -     MPESettingsDataModel& operator= (const MPESettingsDataModel& other)
 -     {
 -         auto copy (other);
 -         swap (copy);
 -         return *this;
 -     }
 - 
 -     int getSynthVoices() const
 -     {
 -         return synthVoices;
 -     }
 - 
 -     void setSynthVoices (int value, UndoManager* undoManager)
 -     {
 -         synthVoices.setValue (Range<int> (1, 20).clipValue (value), undoManager);
 -     }
 - 
 -     bool getVoiceStealingEnabled() const
 -     {
 -         return voiceStealingEnabled;
 -     }
 - 
 -     void setVoiceStealingEnabled (bool value, UndoManager* undoManager)
 -     {
 -         voiceStealingEnabled.setValue (value, undoManager);
 -     }
 - 
 -     bool getLegacyModeEnabled() const
 -     {
 -         return legacyModeEnabled;
 -     }
 - 
 -     void setLegacyModeEnabled (bool value, UndoManager* undoManager)
 -     {
 -         legacyModeEnabled.setValue (value, undoManager);
 -     }
 - 
 -     MPEZoneLayout getMPEZoneLayout() const
 -     {
 -         return mpeZoneLayout;
 -     }
 - 
 -     void setMPEZoneLayout (MPEZoneLayout value, UndoManager* undoManager)
 -     {
 -         mpeZoneLayout.setValue (value, undoManager);
 -     }
 - 
 -     int getLegacyFirstChannel() const
 -     {
 -         return legacyFirstChannel;
 -     }
 - 
 -     void setLegacyFirstChannel (int value, UndoManager* undoManager)
 -     {
 -         legacyFirstChannel.setValue (Range<int> (1, legacyLastChannel).clipValue (value), undoManager);
 -     }
 - 
 -     int getLegacyLastChannel() const
 -     {
 -         return legacyLastChannel;
 -     }
 - 
 -     void setLegacyLastChannel (int value, UndoManager* undoManager)
 -     {
 -         legacyLastChannel.setValue (Range<int> (legacyFirstChannel, 15).clipValue (value), undoManager);
 -     }
 - 
 -     int getLegacyPitchbendRange() const
 -     {
 -         return legacyPitchbendRange;
 -     }
 - 
 -     void setLegacyPitchbendRange (int value, UndoManager* undoManager)
 -     {
 -         legacyPitchbendRange.setValue (Range<int> (0, 95).clipValue (value), undoManager);
 -     }
 - 
 -     void addListener (Listener& listener)
 -     {
 -         listenerList.add (&listener);
 -     }
 - 
 -     void removeListener (Listener& listener)
 -     {
 -         listenerList.remove (&listener);
 -     }
 - 
 -     void swap (MPESettingsDataModel& other) noexcept
 -     {
 -         using std::swap;
 -         swap (other.valueTree, valueTree);
 -     }
 - 
 - private:
 -     void valueTreePropertyChanged (ValueTree&, const Identifier& property) override
 -     {
 -         if (property == IDs::synthVoices)
 -         {
 -             synthVoices.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.synthVoicesChanged (synthVoices); });
 -         }
 -         else if (property == IDs::voiceStealingEnabled)
 -         {
 -             voiceStealingEnabled.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.voiceStealingEnabledChanged (voiceStealingEnabled); });
 -         }
 -         else if (property == IDs::legacyModeEnabled)
 -         {
 -             legacyModeEnabled.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.legacyModeEnabledChanged (legacyModeEnabled); });
 -         }
 -         else if (property == IDs::mpeZoneLayout)
 -         {
 -             mpeZoneLayout.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.mpeZoneLayoutChanged (mpeZoneLayout); });
 -         }
 -         else if (property == IDs::legacyFirstChannel)
 -         {
 -             legacyFirstChannel.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.legacyFirstChannelChanged (legacyFirstChannel); });
 -         }
 -         else if (property == IDs::legacyLastChannel)
 -         {
 -             legacyLastChannel.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.legacyLastChannelChanged (legacyLastChannel); });
 -         }
 -         else if (property == IDs::legacyPitchbendRange)
 -         {
 -             legacyPitchbendRange.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.legacyPitchbendRangeChanged (legacyPitchbendRange); });
 -         }
 -     }
 - 
 -     void valueTreeChildAdded        (ValueTree&, ValueTree&)      override { jassertfalse; }
 -     void valueTreeChildRemoved      (ValueTree&, ValueTree&, int) override { jassertfalse; }
 -     void valueTreeChildOrderChanged (ValueTree&, int, int)        override { jassertfalse; }
 -     void valueTreeParentChanged     (ValueTree&)                  override { jassertfalse; }
 - 
 -     ValueTree valueTree;
 - 
 -     CachedValue<int> synthVoices;
 -     CachedValue<bool> voiceStealingEnabled;
 -     CachedValue<bool> legacyModeEnabled;
 -     CachedValue<MPEZoneLayout> mpeZoneLayout;
 -     CachedValue<int> legacyFirstChannel;
 -     CachedValue<int> legacyLastChannel;
 -     CachedValue<int> legacyPitchbendRange;
 - 
 -     ListenerList<Listener> listenerList;
 - };
 - 
 - //==============================================================================
 - class DataModel  : private ValueTree::Listener
 - {
 - public:
 -     class Listener
 -     {
 -     public:
 -         virtual ~Listener() noexcept = default;
 -         virtual void sampleReaderChanged (std::shared_ptr<AudioFormatReaderFactory>) {}
 -         virtual void centreFrequencyHzChanged (double) {}
 -         virtual void loopModeChanged (LoopMode) {}
 -         virtual void loopPointsSecondsChanged (Range<double>) {}
 -     };
 - 
 -     explicit DataModel (AudioFormatManager& audioFormatManagerIn)
 -         : DataModel (audioFormatManagerIn, ValueTree (IDs::DATA_MODEL))
 -     {}
 - 
 -     DataModel (AudioFormatManager& audioFormatManagerIn, const ValueTree& vt)
 -         : audioFormatManager (&audioFormatManagerIn),
 -           valueTree (vt),
 -           sampleReader      (valueTree, IDs::sampleReader,      nullptr),
 -           centreFrequencyHz (valueTree, IDs::centreFrequencyHz, nullptr),
 -           loopMode          (valueTree, IDs::loopMode,          nullptr, LoopMode::none),
 -           loopPointsSeconds (valueTree, IDs::loopPointsSeconds, nullptr)
 -     {
 -         jassert (valueTree.hasType (IDs::DATA_MODEL));
 -         valueTree.addListener (this);
 -     }
 - 
 -     DataModel (const DataModel& other)
 -         : DataModel (*other.audioFormatManager, other.valueTree)
 -     {}
 - 
 -     DataModel& operator= (const DataModel& other)
 -     {
 -         auto copy (other);
 -         swap (copy);
 -         return *this;
 -     }
 - 
 -     std::unique_ptr<AudioFormatReader> getSampleReader() const
 -     {
 -         return sampleReader != nullptr ? sampleReader.get()->make (*audioFormatManager) : nullptr;
 -     }
 - 
 -     void setSampleReader (std::unique_ptr<AudioFormatReaderFactory> readerFactory,
 -                           UndoManager* undoManager)
 -     {
 -         sampleReader.setValue (move (readerFactory), undoManager);
 -         setLoopPointsSeconds (Range<double> (0, getSampleLengthSeconds()).constrainRange (loopPointsSeconds),
 -                               undoManager);
 -     }
 - 
 -     double getSampleLengthSeconds() const
 -     {
 -         if (auto r = getSampleReader())
 -             return (double) r->lengthInSamples / r->sampleRate;
 - 
 -         return 1.0;
 -     }
 - 
 -     double getCentreFrequencyHz() const
 -     {
 -         return centreFrequencyHz;
 -     }
 - 
 -     void setCentreFrequencyHz (double value, UndoManager* undoManager)
 -     {
 -         centreFrequencyHz.setValue (Range<double> (20, 20000).clipValue (value),
 -                                     undoManager);
 -     }
 - 
 -     LoopMode getLoopMode() const
 -     {
 -         return loopMode;
 -     }
 - 
 -     void setLoopMode (LoopMode value, UndoManager* undoManager)
 -     {
 -         loopMode.setValue (value, undoManager);
 -     }
 - 
 -     Range<double> getLoopPointsSeconds() const
 -     {
 -         return loopPointsSeconds;
 -     }
 - 
 -     void setLoopPointsSeconds (Range<double> value, UndoManager* undoManager)
 -     {
 -         loopPointsSeconds.setValue (Range<double> (0, getSampleLengthSeconds()).constrainRange (value),
 -                                     undoManager);
 -     }
 - 
 -     MPESettingsDataModel mpeSettings()
 -     {
 -         return MPESettingsDataModel (valueTree.getOrCreateChildWithName (IDs::MPE_SETTINGS, nullptr));
 -     }
 - 
 -     void addListener (Listener& listener)
 -     {
 -         listenerList.add (&listener);
 -     }
 - 
 -     void removeListener (Listener& listener)
 -     {
 -         listenerList.remove (&listener);
 -     }
 - 
 -     void swap (DataModel& other) noexcept
 -     {
 -         using std::swap;
 -         swap (other.valueTree, valueTree);
 -     }
 - 
 -     AudioFormatManager& getAudioFormatManager() const
 -     {
 -         return *audioFormatManager;
 -     }
 - 
 - private:
 -     void valueTreePropertyChanged (ValueTree&, const Identifier& property) override
 -     {
 -         if (property == IDs::sampleReader)
 -         {
 -             sampleReader.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.sampleReaderChanged (sampleReader); });
 -         }
 -         else if (property == IDs::centreFrequencyHz)
 -         {
 -             centreFrequencyHz.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.centreFrequencyHzChanged (centreFrequencyHz); });
 -         }
 -         else if (property == IDs::loopMode)
 -         {
 -             loopMode.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.loopModeChanged (loopMode); });
 -         }
 -         else if (property == IDs::loopPointsSeconds)
 -         {
 -             loopPointsSeconds.forceUpdateOfCachedValue();
 -             listenerList.call ([this] (Listener& l) { l.loopPointsSecondsChanged (loopPointsSeconds); });
 -         }
 -     }
 - 
 -     void valueTreeChildAdded        (ValueTree&, ValueTree&)      override {}
 -     void valueTreeChildRemoved      (ValueTree&, ValueTree&, int) override { jassertfalse; }
 -     void valueTreeChildOrderChanged (ValueTree&, int, int)        override { jassertfalse; }
 -     void valueTreeParentChanged     (ValueTree&)                  override { jassertfalse; }
 - 
 -     AudioFormatManager* audioFormatManager;
 - 
 -     ValueTree valueTree;
 - 
 -     CachedValue<std::shared_ptr<AudioFormatReaderFactory>> sampleReader;
 -     CachedValue<double> centreFrequencyHz;
 -     CachedValue<LoopMode> loopMode;
 -     CachedValue<Range<double>> loopPointsSeconds;
 - 
 -     ListenerList<Listener> listenerList;
 - };
 - 
 - namespace
 - {
 - void initialiseComboBoxWithConsecutiveIntegers (Component& owner,
 -                                                 ComboBox& comboBox,
 -                                                 Label& label,
 -                                                 int firstValue,
 -                                                 int numValues,
 -                                                 int valueToSelect)
 - {
 -     for (auto i = 0; i < numValues; ++i)
 -         comboBox.addItem (String (i + firstValue), i + 1);
 - 
 -     comboBox.setSelectedId (valueToSelect - firstValue + 1);
 - 
 -     label.attachToComponent (&comboBox, true);
 -     owner.addAndMakeVisible (comboBox);
 - }
 - 
 - constexpr int controlHeight     = 24;
 - constexpr int controlSeparation = 6;
 - 
 - } // namespace
 - 
 - //==============================================================================
 - class MPELegacySettingsComponent final  : public Component,
 -                                           private MPESettingsDataModel::Listener
 - {
 - public:
 -     explicit MPELegacySettingsComponent (const MPESettingsDataModel& model,
 -                                          UndoManager& um)
 -         : dataModel (model),
 -           undoManager (&um)
 -     {
 -         dataModel.addListener (*this);
 - 
 -         initialiseComboBoxWithConsecutiveIntegers (*this, legacyStartChannel, legacyStartChannelLabel, 1, 16, 1);
 -         initialiseComboBoxWithConsecutiveIntegers (*this, legacyEndChannel, legacyEndChannelLabel, 1, 16, 16);
 -         initialiseComboBoxWithConsecutiveIntegers (*this, legacyPitchbendRange, legacyPitchbendRangeLabel, 0, 96, 2);
 - 
 -         legacyStartChannel.onChange = [this]
 -         {
 -             if (isLegacyModeValid())
 -             {
 -                 undoManager->beginNewTransaction();
 -                 dataModel.setLegacyFirstChannel (getFirstChannel(), undoManager);
 -             }
 -         };
 - 
 -         legacyEndChannel.onChange = [this]
 -         {
 -             if (isLegacyModeValid())
 -             {
 -                 undoManager->beginNewTransaction();
 -                 dataModel.setLegacyLastChannel (getLastChannel(), undoManager);
 -             }
 -         };
 - 
 -         legacyPitchbendRange.onChange = [this]
 -         {
 -             if (isLegacyModeValid())
 -             {
 -                 undoManager->beginNewTransaction();
 -                 dataModel.setLegacyPitchbendRange (legacyPitchbendRange.getText().getIntValue(), undoManager);
 -             }
 -         };
 -     }
 - 
 -     int getMinHeight() const
 -     {
 -         return (controlHeight * 3) + (controlSeparation * 2);
 -     }
 - 
 - private:
 -     void resized() override
 -     {
 -         Rectangle<int> r (proportionOfWidth (0.65f), 0, proportionOfWidth (0.25f), getHeight());
 - 
 -         for (auto& comboBox : { &legacyStartChannel, &legacyEndChannel, &legacyPitchbendRange })
 -         {
 -             comboBox->setBounds (r.removeFromTop (controlHeight));
 -             r.removeFromTop (controlSeparation);
 -         }
 -     }
 - 
 -     bool isLegacyModeValid() const
 -     {
 -         if (! areLegacyModeParametersValid())
 -         {
 -             handleInvalidLegacyModeParameters();
 -             return false;
 -         }
 - 
 -         return true;
 -     }
 - 
 -     void legacyFirstChannelChanged (int value) override
 -     {
 -         legacyStartChannel.setSelectedId (value, dontSendNotification);
 -     }
 - 
 -     void legacyLastChannelChanged (int value) override
 -     {
 -         legacyEndChannel.setSelectedId (value, dontSendNotification);
 -     }
 - 
 -     void legacyPitchbendRangeChanged (int value) override
 -     {
 -         legacyPitchbendRange.setSelectedId (value + 1, dontSendNotification);
 -     }
 - 
 -     int getFirstChannel() const
 -     {
 -         return legacyStartChannel.getText().getIntValue();
 -     }
 - 
 -     int getLastChannel() const
 -     {
 -         return legacyEndChannel.getText().getIntValue();
 -     }
 - 
 -     bool areLegacyModeParametersValid() const
 -     {
 -         return getFirstChannel() <= getLastChannel();
 -     }
 - 
 -     void handleInvalidLegacyModeParameters() const
 -     {
 -         AlertWindow::showMessageBoxAsync (AlertWindow::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");
 -     }
 - 
 -     MPESettingsDataModel dataModel;
 - 
 -     ComboBox legacyStartChannel, legacyEndChannel, legacyPitchbendRange;
 - 
 -     Label legacyStartChannelLabel   { {}, "First channel" },
 -           legacyEndChannelLabel     { {}, "Last channel" },
 -           legacyPitchbendRangeLabel { {}, "Pitchbend range (semitones)" };
 - 
 -     UndoManager* undoManager;
 - };
 - 
 - //==============================================================================
 - class MPENewSettingsComponent final  : public Component,
 -                                        private MPESettingsDataModel::Listener
 - {
 - public:
 -     MPENewSettingsComponent (const MPESettingsDataModel& model,
 -                              UndoManager& um)
 -         : dataModel (model),
 -           undoManager (&um)
 -     {
 -         dataModel.addListener (*this);
 - 
 -         addAndMakeVisible (isLowerZoneButton);
 -         isLowerZoneButton.setToggleState (true, NotificationType::dontSendNotification);
 - 
 -         initialiseComboBoxWithConsecutiveIntegers (*this, memberChannels, memberChannelsLabel, 0, 16, 15);
 -         initialiseComboBoxWithConsecutiveIntegers (*this, masterPitchbendRange, masterPitchbendRangeLabel, 0, 96, 2);
 -         initialiseComboBoxWithConsecutiveIntegers (*this, notePitchbendRange, notePitchbendRangeLabel, 0, 96, 48);
 - 
 -         for (auto& button : { &setZoneButton, &clearAllZonesButton })
 -             addAndMakeVisible (button);
 - 
 -         setZoneButton.onClick = [this]
 -         {
 -             auto isLowerZone = isLowerZoneButton.getToggleState();
 -             auto numMemberChannels = memberChannels.getText().getIntValue();
 -             auto perNotePb = notePitchbendRange.getText().getIntValue();
 -             auto masterPb = masterPitchbendRange.getText().getIntValue();
 - 
 -             if (isLowerZone)
 -                 zoneLayout.setLowerZone (numMemberChannels, perNotePb, masterPb);
 -             else
 -                 zoneLayout.setUpperZone (numMemberChannels, perNotePb, masterPb);
 - 
 -             undoManager->beginNewTransaction();
 -             dataModel.setMPEZoneLayout (zoneLayout, undoManager);
 -         };
 - 
 -         clearAllZonesButton.onClick = [this]
 -         {
 -             zoneLayout.clearAllZones();
 -             undoManager->beginNewTransaction();
 -             dataModel.setMPEZoneLayout (zoneLayout, undoManager);
 -         };
 -     }
 - 
 -     int getMinHeight() const
 -     {
 -         return (controlHeight * 6) + (controlSeparation * 6);
 -     }
 - 
 - private:
 -     void resized() override
 -     {
 -         Rectangle<int> r (proportionOfWidth (0.65f), 0, proportionOfWidth (0.25f), getHeight());
 - 
 -         isLowerZoneButton.setBounds (r.removeFromTop (controlHeight));
 -         r.removeFromTop (controlSeparation);
 - 
 -         for (auto& comboBox : { &memberChannels, &masterPitchbendRange, ¬ePitchbendRange })
 -         {
 -             comboBox->setBounds (r.removeFromTop (controlHeight));
 -             r.removeFromTop (controlSeparation);
 -         }
 - 
 -         r.removeFromTop (controlSeparation);
 - 
 -         auto buttonLeft = proportionOfWidth (0.5f);
 - 
 -         setZoneButton.setBounds (r.removeFromTop (controlHeight).withLeft (buttonLeft));
 -         r.removeFromTop (controlSeparation);
 -         clearAllZonesButton.setBounds (r.removeFromTop (controlHeight).withLeft (buttonLeft));
 -     }
 - 
 -     void mpeZoneLayoutChanged (const MPEZoneLayout& value) override
 -     {
 -         zoneLayout = value;
 -     }
 - 
 -     MPESettingsDataModel dataModel;
 -     MPEZoneLayout zoneLayout;
 - 
 -     ComboBox memberChannels, masterPitchbendRange, notePitchbendRange;
 - 
 -     ToggleButton isLowerZoneButton  { "Lower zone" };
 - 
 -     Label memberChannelsLabel       { {}, "Nr. of member channels" },
 -           masterPitchbendRangeLabel { {}, "Master pitchbend range (semitones)" },
 -           notePitchbendRangeLabel   { {}, "Note pitchbend range (semitones)" };
 - 
 -     TextButton setZoneButton       { "Set zone" },
 -                clearAllZonesButton { "Clear all zones" };
 - 
 -     UndoManager* undoManager;
 - };
 - 
 - //==============================================================================
 - class MPESettingsComponent final  : public Component,
 -                                     private MPESettingsDataModel::Listener
 - {
 - public:
 -     MPESettingsComponent (const MPESettingsDataModel& model,
 -                           UndoManager& um)
 -         : dataModel (model),
 -           legacySettings (dataModel, um),
 -           newSettings (dataModel, um),
 -           undoManager (&um)
 -     {
 -         dataModel.addListener (*this);
 - 
 -         addAndMakeVisible (newSettings);
 -         addChildComponent (legacySettings);
 - 
 -         initialiseComboBoxWithConsecutiveIntegers (*this, numberOfVoices, numberOfVoicesLabel, 1, 20, 15);
 -         numberOfVoices.onChange = [this]
 -         {
 -             undoManager->beginNewTransaction();
 -             dataModel.setSynthVoices (numberOfVoices.getText().getIntValue(), undoManager);
 -         };
 - 
 -         for (auto& button : { &legacyModeEnabledToggle, &voiceStealingEnabledToggle })
 -         {
 -             addAndMakeVisible (button);
 -         }
 - 
 -         legacyModeEnabledToggle.onClick = [this]
 -         {
 -             undoManager->beginNewTransaction();
 -             dataModel.setLegacyModeEnabled (legacyModeEnabledToggle.getToggleState(), undoManager);
 -         };
 - 
 -         voiceStealingEnabledToggle.onClick = [this]
 -         {
 -             undoManager->beginNewTransaction();
 -             dataModel.setVoiceStealingEnabled (voiceStealingEnabledToggle.getToggleState(), undoManager);
 -         };
 -     }
 - 
 - private:
 -     void resized() override
 -     {
 -         auto topHeight = jmax (legacySettings.getMinHeight(), newSettings.getMinHeight());
 -         auto r = getLocalBounds();
 -         r.removeFromTop (15);
 -         auto top = r.removeFromTop (topHeight);
 -         legacySettings.setBounds (top);
 -         newSettings.setBounds (top);
 - 
 -         r.removeFromLeft (proportionOfWidth (0.65f));
 -         r = r.removeFromLeft (proportionOfWidth (0.25f));
 - 
 -         auto toggleLeft = proportionOfWidth (0.25f);
 - 
 -         legacyModeEnabledToggle.setBounds (r.removeFromTop (controlHeight).withLeft (toggleLeft));
 -         r.removeFromTop (controlSeparation);
 -         voiceStealingEnabledToggle.setBounds (r.removeFromTop (controlHeight).withLeft (toggleLeft));
 -         r.removeFromTop (controlSeparation);
 -         numberOfVoices.setBounds (r.removeFromTop (controlHeight));
 -     }
 - 
 -     void legacyModeEnabledChanged (bool value) override
 -     {
 -         legacySettings.setVisible (value);
 -         newSettings.setVisible (! value);
 -         legacyModeEnabledToggle.setToggleState (value, dontSendNotification);
 -     }
 - 
 -     void voiceStealingEnabledChanged (bool value) override
 -     {
 -         voiceStealingEnabledToggle.setToggleState (value, dontSendNotification);
 -     }
 - 
 -     void synthVoicesChanged (int value) override
 -     {
 -         numberOfVoices.setSelectedId (value, dontSendNotification);
 -     }
 - 
 -     MPESettingsDataModel dataModel;
 -     MPELegacySettingsComponent legacySettings;
 -     MPENewSettingsComponent newSettings;
 - 
 -     ToggleButton legacyModeEnabledToggle    { "Enable Legacy Mode" },
 -                  voiceStealingEnabledToggle { "Enable synth voice stealing" };
 - 
 -     ComboBox numberOfVoices;
 -     Label numberOfVoicesLabel { {}, "Number of synth voices" };
 - 
 -     UndoManager* undoManager;
 - };
 - 
 - //==============================================================================
 - class LoopPointMarker  : public Component
 - {
 - public:
 -     using MouseCallback = std::function<void (LoopPointMarker&, const MouseEvent&)>;
 - 
 -     LoopPointMarker (String marker,
 -                      MouseCallback onMouseDownIn,
 -                      MouseCallback onMouseDragIn,
 -                      MouseCallback onMouseUpIn)
 -         : text (std::move (marker)),
 -           onMouseDown (std::move (onMouseDownIn)),
 -           onMouseDrag (std::move (onMouseDragIn)),
 -           onMouseUp (std::move (onMouseUpIn))
 -     {
 -         setMouseCursor (MouseCursor::LeftRightResizeCursor);
 -     }
 - 
 - private:
 -     void resized() override
 -     {
 -         auto height = 20;
 -         auto triHeight = 6;
 - 
 -         auto bounds = getLocalBounds();
 -         Path newPath;
 -         newPath.addRectangle (bounds.removeFromBottom (height));
 - 
 -         newPath.startNewSubPath (bounds.getBottomLeft().toFloat());
 -         newPath.lineTo (bounds.getBottomRight().toFloat());
 -         Point<float> apex (static_cast<float> (bounds.getX() + (bounds.getWidth() / 2)),
 -                            static_cast<float> (bounds.getBottom() - triHeight));
 -         newPath.lineTo (apex);
 -         newPath.closeSubPath();
 - 
 -         newPath.addLineSegment (Line<float> (apex, Point<float> (apex.getX(), 0)), 1);
 - 
 -         path = newPath;
 -     }
 - 
 -     void paint (Graphics& g) override
 -     {
 -         g.setColour (Colours::deepskyblue);
 -         g.fillPath (path);
 - 
 -         auto height = 20;
 -         g.setColour (Colours::white);
 -         g.drawText (text, getLocalBounds().removeFromBottom (height), Justification::centred);
 -     }
 - 
 -     bool hitTest (int x, int y) override
 -     {
 -         return path.contains ((float) x, (float) y);
 -     }
 - 
 -     void mouseDown (const MouseEvent& e) override
 -     {
 -         onMouseDown (*this, e);
 -     }
 - 
 -     void mouseDrag (const MouseEvent& e) override
 -     {
 -         onMouseDrag (*this, e);
 -     }
 - 
 -     void mouseUp (const MouseEvent& e) override
 -     {
 -         onMouseUp (*this, e);
 -     }
 - 
 -     String text;
 -     Path path;
 -     MouseCallback onMouseDown;
 -     MouseCallback onMouseDrag;
 -     MouseCallback onMouseUp;
 - };
 - 
 - //==============================================================================
 - class Ruler  : public Component,
 -                private VisibleRangeDataModel::Listener
 - {
 - public:
 -     explicit Ruler (const VisibleRangeDataModel& model)
 -         : visibleRange (model)
 -     {
 -         visibleRange.addListener (*this);
 -         setMouseCursor (MouseCursor::LeftRightResizeCursor);
 -     }
 - 
 - private:
 -     void paint (Graphics& g) override
 -     {
 -         auto minDivisionWidth = 50.0f;
 -         auto maxDivisions     = (float) getWidth() / minDivisionWidth;
 - 
 -         auto lookFeel = dynamic_cast<LookAndFeel_V4*> (&getLookAndFeel());
 -         auto bg = lookFeel->getCurrentColourScheme()
 -                            .getUIColour (LookAndFeel_V4::ColourScheme::UIColour::widgetBackground);
 - 
 -         g.setGradientFill (ColourGradient (bg.brighter(),
 -                                            0,
 -                                            0,
 -                                            bg.darker(),
 -                                            0,
 -                                            (float) getHeight(),
 -                                            false));
 - 
 -         g.fillAll();
 -         g.setColour (bg.brighter());
 -         g.drawHorizontalLine (0, 0.0f, (float) getWidth());
 -         g.setColour (bg.darker());
 -         g.drawHorizontalLine (1, 0.0f, (float) getWidth());
 -         g.setColour (Colours::lightgrey);
 - 
 -         auto minLog = std::ceil (std::log10 (visibleRange.getVisibleRange().getLength() / maxDivisions));
 -         auto precision = 2 + std::abs (minLog);
 -         auto divisionMagnitude = std::pow (10, minLog);
 -         auto startingDivision = std::ceil (visibleRange.getVisibleRange().getStart() / divisionMagnitude);
 - 
 -         for (auto div = startingDivision; div * divisionMagnitude < visibleRange.getVisibleRange().getEnd(); ++div)
 -         {
 -             auto time = div * divisionMagnitude;
 -             auto xPos = (time - visibleRange.getVisibleRange().getStart()) * getWidth()
 -                               / visibleRange.getVisibleRange().getLength();
 - 
 -             std::ostringstream outStream;
 -             outStream << std::setprecision (roundToInt (precision)) << time;
 - 
 -             const auto bounds = Rectangle<int> (Point<int> (roundToInt (xPos) + 3, 0),
 -                                                 Point<int> (roundToInt (xPos + minDivisionWidth), getHeight()));
 - 
 -             g.drawText (outStream.str(), bounds, Justification::centredLeft, false);
 - 
 -             g.drawVerticalLine (roundToInt (xPos), 2.0f, (float) getHeight());
 -         }
 -     }
 - 
 -     void mouseDown (const MouseEvent& e) override
 -     {
 -         visibleRangeOnMouseDown = visibleRange.getVisibleRange();
 -         timeOnMouseDown = visibleRange.getVisibleRange().getStart()
 -                        + (visibleRange.getVisibleRange().getLength() * e.getMouseDownX()) / getWidth();
 -     }
 - 
 -     void mouseDrag (const MouseEvent& e) override
 -     {
 -         // Work out the scale of the new range
 -         auto unitDistance = 100.0f;
 -         auto scaleFactor  = 1.0 / std::pow (2, (float) e.getDistanceFromDragStartY() / unitDistance);
 - 
 -         // Now position it so that the mouse continues to point at the same
 -         // place on the ruler.
 -         auto visibleLength = std::max (0.12, visibleRangeOnMouseDown.getLength() * scaleFactor);
 -         auto rangeBegin = timeOnMouseDown - visibleLength * e.x / getWidth();
 -         const Range<double> range (rangeBegin, rangeBegin + visibleLength);
 -         visibleRange.setVisibleRange (range, nullptr);
 -     }
 - 
 -     void visibleRangeChanged (Range<double>) override
 -     {
 -         repaint();
 -     }
 - 
 -     VisibleRangeDataModel visibleRange;
 -     Range<double> visibleRangeOnMouseDown;
 -     double timeOnMouseDown;
 - };
 - 
 - //==============================================================================
 - class LoopPointsOverlay  : public Component,
 -                            private DataModel::Listener,
 -                            private VisibleRangeDataModel::Listener
 - {
 - public:
 -     LoopPointsOverlay (const DataModel& dModel,
 -                        const VisibleRangeDataModel& vModel,
 -                        UndoManager& undoManagerIn)
 -         : dataModel (dModel),
 -           visibleRange (vModel),
 -           beginMarker ("B",
 -                        [this] (LoopPointMarker& m, const MouseEvent& e) { this->loopPointMouseDown (m, e); },
 -                        [this] (LoopPointMarker& m, const MouseEvent& e) { this->loopPointDragged   (m, e); },
 -                        [this] (LoopPointMarker& m, const MouseEvent& e) { this->loopPointMouseUp   (m, e); }),
 -           endMarker   ("E",
 -                        [this] (LoopPointMarker& m, const MouseEvent& e) { this->loopPointMouseDown (m, e); },
 -                        [this] (LoopPointMarker& m, const MouseEvent& e) { this->loopPointDragged   (m, e); },
 -                        [this] (LoopPointMarker& m, const MouseEvent& e) { this->loopPointMouseUp   (m, e); }),
 -           undoManager (&undoManagerIn)
 -     {
 -         dataModel   .addListener (*this);
 -         visibleRange.addListener (*this);
 - 
 -         for (auto ptr : { &beginMarker, &endMarker })
 -             addAndMakeVisible (ptr);
 -     }
 - 
 - private:
 -     void resized() override
 -     {
 -         positionLoopPointMarkers();
 -     }
 - 
 -     void loopPointMouseDown (LoopPointMarker&, const MouseEvent&)
 -     {
 -         loopPointsOnMouseDown = dataModel.getLoopPointsSeconds();
 -         undoManager->beginNewTransaction();
 -     }
 - 
 -     void loopPointDragged (LoopPointMarker& marker, const MouseEvent& e)
 -     {
 -         auto x = xPositionToTime (e.getEventRelativeTo (this).position.x);
 -         const Range<double> newLoopRange (&marker == &beginMarker ? x : loopPointsOnMouseDown.getStart(),
 -                                           &marker == &endMarker   ? x : loopPointsOnMouseDown.getEnd());
 - 
 -         dataModel.setLoopPointsSeconds (newLoopRange, undoManager);
 -     }
 - 
 -     void loopPointMouseUp (LoopPointMarker& marker, const MouseEvent& e)
 -     {
 -         auto x = xPositionToTime (e.getEventRelativeTo (this).position.x);
 -         const Range<double> newLoopRange (&marker == &beginMarker ? x : loopPointsOnMouseDown.getStart(),
 -                                           &marker == &endMarker   ? x : loopPointsOnMouseDown.getEnd());
 - 
 -         dataModel.setLoopPointsSeconds (newLoopRange, undoManager);
 -     }
 - 
 -     void loopPointsSecondsChanged (Range<double>) override
 -     {
 -         positionLoopPointMarkers();
 -     }
 - 
 -     void visibleRangeChanged (Range<double>) override
 -     {
 -         positionLoopPointMarkers();
 -     }
 - 
 -     double timeToXPosition (double time) const
 -     {
 -         return (time - visibleRange.getVisibleRange().getStart()) * getWidth()
 -                      / visibleRange.getVisibleRange().getLength();
 -     }
 - 
 -     double xPositionToTime (double xPosition) const
 -     {
 -         return ((xPosition * visibleRange.getVisibleRange().getLength()) / getWidth())
 -                            + visibleRange.getVisibleRange().getStart();
 -     }
 - 
 -     void positionLoopPointMarkers()
 -     {
 -         auto halfMarkerWidth = 7;
 - 
 -         for (auto tup : { std::make_tuple (&beginMarker, dataModel.getLoopPointsSeconds().getStart()),
 -                           std::make_tuple (&endMarker,   dataModel.getLoopPointsSeconds().getEnd()) })
 -         {
 -             auto ptr  = std::get<0> (tup);
 -             auto time = std::get<1> (tup);
 -             ptr->setSize (halfMarkerWidth * 2, getHeight());
 -             ptr->setTopLeftPosition (roundToInt (timeToXPosition (time) - halfMarkerWidth), 0);
 -         }
 -     }
 - 
 -     DataModel dataModel;
 -     VisibleRangeDataModel visibleRange;
 -     Range<double> loopPointsOnMouseDown;
 -     LoopPointMarker beginMarker, endMarker;
 -     UndoManager* undoManager;
 - };
 - 
 - //==============================================================================
 - class PlaybackPositionOverlay  : public Component,
 -                                  private Timer,
 -                                  private VisibleRangeDataModel::Listener
 - {
 - public:
 -     using Provider = std::function<std::vector<float>()>;
 -     PlaybackPositionOverlay (const VisibleRangeDataModel& model,
 -                              Provider providerIn)
 -         : visibleRange (model),
 -           provider (std::move (providerIn))
 -     {
 -         visibleRange.addListener (*this);
 -         startTimer (16);
 -     }
 - 
 - private:
 -     void paint (Graphics& g) override
 -     {
 -         g.setColour (Colours::red);
 - 
 -         for (auto position : provider())
 -         {
 -             g.drawVerticalLine (roundToInt (timeToXPosition (position)), 0.0f, (float) getHeight());
 -         }
 -     }
 - 
 -     void timerCallback() override
 -     {
 -         repaint();
 -     }
 - 
 -     void visibleRangeChanged (Range<double>) override
 -     {
 -         repaint();
 -     }
 - 
 -     double timeToXPosition (double time) const
 -     {
 -         return (time - visibleRange.getVisibleRange().getStart()) * getWidth()
 -                      / visibleRange.getVisibleRange().getLength();
 -     }
 - 
 -     VisibleRangeDataModel visibleRange;
 -     Provider provider;
 - };
 - 
 - //==============================================================================
 - class WaveformView  : public Component,
 -                       private ChangeListener,
 -                       private DataModel::Listener,
 -                       private VisibleRangeDataModel::Listener
 - {
 - public:
 -     WaveformView (const DataModel& model,
 -                   const VisibleRangeDataModel& vr)
 -         : dataModel (model),
 -           visibleRange (vr),
 -           thumbnailCache (4),
 -           thumbnail (4, dataModel.getAudioFormatManager(), thumbnailCache)
 -     {
 -         dataModel   .addListener (*this);
 -         visibleRange.addListener (*this);
 -         thumbnail   .addChangeListener (this);
 -     }
 - 
 - private:
 -     void paint (Graphics& g) override
 -     {
 -         // Draw the waveforms
 -         g.fillAll (Colours::black);
 -         auto numChannels = thumbnail.getNumChannels();
 - 
 -         if (numChannels == 0)
 -         {
 -             g.setColour (Colours::white);
 -             g.drawFittedText ("No File Loaded", getLocalBounds(), Justification::centred, 1);
 -             return;
 -         }
 - 
 -         auto bounds = getLocalBounds();
 -         auto channelHeight = bounds.getHeight() / numChannels;
 - 
 -         for (auto i = 0; i != numChannels; ++i)
 -         {
 -             drawChannel (g, i, bounds.removeFromTop (channelHeight));
 -         }
 -     }
 - 
 -     void changeListenerCallback (ChangeBroadcaster* source) override
 -     {
 -         if (source == &thumbnail)
 -             repaint();
 -     }
 - 
 -     void sampleReaderChanged (std::shared_ptr<AudioFormatReaderFactory> value) override
 -     {
 -         if (value != nullptr)
 -         {
 -             if (auto reader = value->make (dataModel.getAudioFormatManager()))
 -             {
 -                 thumbnail.setReader (reader.release(), currentHashCode);
 -                 currentHashCode += 1;
 - 
 -                 return;
 -             }
 -         }
 - 
 -         thumbnail.clear();
 -     }
 - 
 -     void visibleRangeChanged (Range<double>) override
 -     {
 -         repaint();
 -     }
 - 
 -     void drawChannel (Graphics& g, int channel, Rectangle<int> bounds)
 -     {
 -         g.setGradientFill (ColourGradient (Colours::lightblue,
 -                                            bounds.getTopLeft().toFloat(),
 -                                            Colours::darkgrey,
 -                                            bounds.getBottomLeft().toFloat(),
 -                                            false));
 -         thumbnail.drawChannel (g,
 -                                bounds,
 -                                visibleRange.getVisibleRange().getStart(),
 -                                visibleRange.getVisibleRange().getEnd(),
 -                                channel,
 -                                1.0f);
 -     }
 - 
 -     DataModel dataModel;
 -     VisibleRangeDataModel visibleRange;
 -     AudioThumbnailCache thumbnailCache;
 -     AudioThumbnail thumbnail;
 -     int64 currentHashCode = 0;
 - };
 - 
 - //==============================================================================
 - class WaveformEditor  : public Component,
 -                         private DataModel::Listener
 - {
 - public:
 -     WaveformEditor (const DataModel& model,
 -                     PlaybackPositionOverlay::Provider provider,
 -                     UndoManager& undoManager)
 -         : dataModel (model),
 -           waveformView (model, visibleRange),
 -           playbackOverlay (visibleRange, move (provider)),
 -           loopPoints (dataModel, visibleRange, undoManager),
 -           ruler (visibleRange)
 -     {
 -         dataModel.addListener (*this);
 - 
 -         addAndMakeVisible (waveformView);
 -         addAndMakeVisible (playbackOverlay);
 -         addChildComponent (loopPoints);
 -         loopPoints.setAlwaysOnTop (true);
 - 
 -         waveformView.toBack();
 - 
 -         addAndMakeVisible (ruler);
 -     }
 - 
 - private:
 -     void resized() override
 -     {
 -         auto bounds = getLocalBounds();
 -         ruler          .setBounds (bounds.removeFromTop (25));
 -         waveformView   .setBounds (bounds);
 -         playbackOverlay.setBounds (bounds);
 -         loopPoints     .setBounds (bounds);
 -     }
 - 
 -     void loopModeChanged (LoopMode value) override
 -     {
 -         loopPoints.setVisible (value != LoopMode::none);
 -     }
 - 
 -     void sampleReaderChanged (std::shared_ptr<AudioFormatReaderFactory>) override
 -     {
 -         auto lengthInSeconds = dataModel.getSampleLengthSeconds();
 -         visibleRange.setTotalRange   (Range<double> (0, lengthInSeconds), nullptr);
 -         visibleRange.setVisibleRange (Range<double> (0, lengthInSeconds), nullptr);
 -     }
 - 
 -     DataModel dataModel;
 -     VisibleRangeDataModel visibleRange;
 -     WaveformView waveformView;
 -     PlaybackPositionOverlay playbackOverlay;
 -     LoopPointsOverlay loopPoints;
 -     Ruler ruler;
 - };
 - 
 - //==============================================================================
 - class MainSamplerView  : public Component,
 -                          private DataModel::Listener,
 -                          private ChangeListener
 - {
 - public:
 -     MainSamplerView (const DataModel& model,
 -                      PlaybackPositionOverlay::Provider provider,
 -                      UndoManager& um)
 -         : dataModel (model),
 -           waveformEditor (dataModel, move (provider), um),
 -           undoManager (um)
 -     {
 -         dataModel.addListener (*this);
 - 
 -         addAndMakeVisible (waveformEditor);
 -         addAndMakeVisible (loadNewSampleButton);
 -         addAndMakeVisible (undoButton);
 -         addAndMakeVisible (redoButton);
 - 
 -         auto setReader = [this] (const FileChooser& fc)
 -         {
 -             const auto result = fc.getResult();
 - 
 -             if (result != File())
 -             {
 -                 undoManager.beginNewTransaction();
 -                 auto readerFactory = new FileAudioFormatReaderFactory (result);
 -                 dataModel.setSampleReader (std::unique_ptr<AudioFormatReaderFactory> (readerFactory),
 -                                            &undoManager);
 -             }
 -         };
 - 
 -         loadNewSampleButton.onClick = [this, setReader]
 -         {
 -             fileChooser.launchAsync (FileBrowserComponent::FileChooserFlags::openMode |
 -                                      FileBrowserComponent::FileChooserFlags::canSelectFiles,
 -                                      setReader);
 -         };
 - 
 -         addAndMakeVisible (centreFrequency);
 -         centreFrequency.onValueChange = [this]
 -         {
 -             undoManager.beginNewTransaction();
 -             dataModel.setCentreFrequencyHz (centreFrequency.getValue(),
 -                                             centreFrequency.isMouseButtonDown() ? nullptr : &undoManager);
 -         };
 - 
 -         centreFrequency.setRange (20, 20000, 1);
 -         centreFrequency.setSliderStyle (Slider::SliderStyle::IncDecButtons);
 -         centreFrequency.setIncDecButtonsMode (Slider::IncDecButtonMode::incDecButtonsDraggable_Vertical);
 - 
 -         auto radioGroupId = 1;
 - 
 -         for (auto buttonPtr : { &loopKindNone, &loopKindForward, &loopKindPingpong })
 -         {
 -             addAndMakeVisible (buttonPtr);
 -             buttonPtr->setRadioGroupId (radioGroupId, dontSendNotification);
 -             buttonPtr->setClickingTogglesState (true);
 -         }
 - 
 -         loopKindNone.onClick = [this]
 -         {
 -             if (loopKindNone.getToggleState())
 -             {
 -                 undoManager.beginNewTransaction();
 -                 dataModel.setLoopMode (LoopMode::none, &undoManager);
 -             }
 -         };
 - 
 -         loopKindForward.onClick = [this]
 -         {
 -             if (loopKindForward.getToggleState())
 -             {
 -                 undoManager.beginNewTransaction();
 -                 dataModel.setLoopMode (LoopMode::forward, &undoManager);
 -             }
 -         };
 - 
 -         loopKindPingpong.onClick = [this]
 -         {
 -             if (loopKindPingpong.getToggleState())
 -             {
 -                 undoManager.beginNewTransaction();
 -                 dataModel.setLoopMode (LoopMode::pingpong, &undoManager);
 -             }
 -         };
 - 
 -         undoButton.onClick = [this] { undoManager.undo(); };
 -         redoButton.onClick = [this] { undoManager.redo(); };
 - 
 -         addAndMakeVisible (centreFrequencyLabel);
 -         addAndMakeVisible (loopKindLabel);
 - 
 -         changeListenerCallback (&undoManager);
 -         undoManager.addChangeListener (this);
 -     }
 - 
 -     ~MainSamplerView() override
 -     {
 -         undoManager.removeChangeListener (this);
 -     }
 - 
 - private:
 -     void changeListenerCallback (ChangeBroadcaster* source) override
 -     {
 -         if (source == &undoManager)
 -         {
 -             undoButton.setEnabled (undoManager.canUndo());
 -             redoButton.setEnabled (undoManager.canRedo());
 -         }
 -     }
 - 
 -     void resized() override
 -     {
 -         auto bounds = getLocalBounds();
 - 
 -         auto topBar = bounds.removeFromTop (50);
 -         auto padding = 4;
 -         loadNewSampleButton .setBounds (topBar.removeFromRight (100).reduced (padding));
 -         redoButton          .setBounds (topBar.removeFromRight (100).reduced (padding));
 -         undoButton          .setBounds (topBar.removeFromRight (100).reduced (padding));
 -         centreFrequencyLabel.setBounds (topBar.removeFromLeft  (100).reduced (padding));
 -         centreFrequency     .setBounds (topBar.removeFromLeft  (100).reduced (padding));
 - 
 -         auto bottomBar = bounds.removeFromBottom (50);
 -         loopKindLabel   .setBounds (bottomBar.removeFromLeft (100).reduced (padding));
 -         loopKindNone    .setBounds (bottomBar.removeFromLeft (80) .reduced (padding));
 -         loopKindForward .setBounds (bottomBar.removeFromLeft (80) .reduced (padding));
 -         loopKindPingpong.setBounds (bottomBar.removeFromLeft (80) .reduced (padding));
 - 
 -         waveformEditor.setBounds (bounds);
 -     }
 - 
 -     void loopModeChanged (LoopMode value) override
 -     {
 -         switch (value)
 -         {
 -             case LoopMode::none:
 -                 loopKindNone.setToggleState (true, dontSendNotification);
 -                 break;
 -             case LoopMode::forward:
 -                 loopKindForward.setToggleState (true, dontSendNotification);
 -                 break;
 -             case LoopMode::pingpong:
 -                 loopKindPingpong.setToggleState (true, dontSendNotification);
 -                 break;
 - 
 -             default:
 -                 break;
 -         }
 -     }
 - 
 -     void centreFrequencyHzChanged (double value) override
 -     {
 -         centreFrequency.setValue (value, dontSendNotification);
 -     }
 - 
 -     DataModel dataModel;
 -     WaveformEditor waveformEditor;
 -     TextButton loadNewSampleButton { "Load New Sample" };
 -     TextButton undoButton { "Undo" };
 -     TextButton redoButton { "Redo" };
 -     Slider centreFrequency;
 - 
 -     TextButton loopKindNone        { "None" },
 -                loopKindForward     { "Forward" },
 -                loopKindPingpong    { "Ping Pong" };
 - 
 -     Label centreFrequencyLabel     { {}, "Sample Centre Freq / Hz" },
 -           loopKindLabel            { {}, "Looping Mode" };
 - 
 - 
 -     FileChooser fileChooser { "Select a file to load...", File(),
 -                               dataModel.getAudioFormatManager().getWildcardForAllFormats() };
 - 
 -     UndoManager& undoManager;
 - };
 - 
 - //==============================================================================
 - struct ProcessorState
 - {
 -     int synthVoices;
 -     bool legacyModeEnabled;
 -     Range<int> legacyChannels;
 -     int legacyPitchbendRange;
 -     bool voiceStealingEnabled;
 -     MPEZoneLayout mpeZoneLayout;
 -     std::unique_ptr<AudioFormatReaderFactory> readerFactory;
 -     Range<double> loopPointsSeconds;
 -     double centreFrequencyHz;
 -     LoopMode loopMode;
 - };
 - 
 - //==============================================================================
 - class SamplerAudioProcessor  : public AudioProcessor
 - {
 - public:
 -     SamplerAudioProcessor()
 -         : AudioProcessor (BusesProperties().withOutput ("Output", AudioChannelSet::stereo(), true))
 -     {
 -         if (auto inputStream = createAssetInputStream ("cello.wav"))
 -         {
 -             inputStream->readIntoMemoryBlock (mb);
 -             readerFactory.reset (new MemoryAudioFormatReaderFactory (mb.getData(), mb.getSize()));
 -         }
 - 
 -         // Set up initial sample, which we load from a binary resource
 -         AudioFormatManager manager;
 -         manager.registerBasicFormats();
 -         auto reader = readerFactory->make (manager);
 -         jassert (reader != nullptr); // Failed to load resource!
 - 
 -         auto sound = samplerSound;
 -         auto sample = std::unique_ptr<Sample> (new Sample (*reader, 10.0));
 -         auto lengthInSeconds = sample->getLength() / sample->getSampleRate();
 -         sound->setLoopPointsInSeconds ({lengthInSeconds * 0.1, lengthInSeconds * 0.9 });
 -         sound->setSample (move (sample));
 - 
 -         // Start with the max number of voices
 -         for (auto i = 0; i != maxVoices; ++i)
 -             synthesiser.addVoice (new MPESamplerVoice (sound));
 -     }
 - 
 -     void prepareToPlay (double sampleRate, int) override
 -     {
 -         synthesiser.setCurrentPlaybackSampleRate (sampleRate);
 -     }
 - 
 -     void releaseResources() override {}
 - 
 -     bool isBusesLayoutSupported (const BusesLayout& layouts) const override
 -     {
 -         return layouts.getMainOutputChannelSet() == AudioChannelSet::mono()
 -             || layouts.getMainOutputChannelSet() == AudioChannelSet::stereo();
 -     }
 - 
 -     //==============================================================================
 -     AudioProcessorEditor* createEditor() override
 -     {
 -         // This function will be called from the message thread. We lock the command
 -         // queue to ensure that no messages are processed for the duration of this
 -         // call.
 -         SpinLock::ScopedLockType lock (commandQueueMutex);
 - 
 -         ProcessorState state;
 -         state.synthVoices          = synthesiser.getNumVoices();
 -         state.legacyModeEnabled    = synthesiser.isLegacyModeEnabled();
 -         state.legacyChannels       = synthesiser.getLegacyModeChannelRange();
 -         state.legacyPitchbendRange = synthesiser.getLegacyModePitchbendRange();
 -         state.voiceStealingEnabled = synthesiser.isVoiceStealingEnabled();
 -         state.mpeZoneLayout        = synthesiser.getZoneLayout();
 -         state.readerFactory        = readerFactory == nullptr ? nullptr : readerFactory->clone();
 - 
 -         auto sound = samplerSound;
 -         state.loopPointsSeconds = sound->getLoopPointsInSeconds();
 -         state.centreFrequencyHz = sound->getCentreFrequencyInHz();
 -         state.loopMode          = sound->getLoopMode();
 - 
 -         return new SamplerAudioProcessorEditor (*this, std::move (state));
 -     }
 - 
 -     bool hasEditor() const override                                       { return true; }
 - 
 -     //==============================================================================
 -     const String getName() const override                                 { return "SamplerPlugin"; }
 -     bool acceptsMidi() const override                                     { return true; }
 -     bool producesMidi() const override                                    { return false; }
 -     bool isMidiEffect() const override                                    { return false; }
 -     double getTailLengthSeconds() const override                          { return 0.0; }
 - 
 -     //==============================================================================
 -     int getNumPrograms() override                                         { return 1; }
 -     int getCurrentProgram() override                                      { return 0; }
 -     void setCurrentProgram (int) override                                 {}
 -     const String getProgramName (int) override                            { return "None"; }
 -     void changeProgramName (int, const String&) override                  {}
 - 
 -     //==============================================================================
 -     void getStateInformation (MemoryBlock&) override                      {}
 -     void setStateInformation (const void*, int) override                  {}
 - 
 -     //==============================================================================
 -     void processBlock (AudioBuffer<float>& buffer, MidiBuffer& midi) override
 -     {
 -         process (buffer, midi);
 -     }
 - 
 -     void processBlock (AudioBuffer<double>& buffer, MidiBuffer& midi) override
 -     {
 -         process (buffer, midi);
 -     }
 - 
 -     // These should be called from the GUI thread, and will block until the
 -     // command buffer has enough room to accept a command.
 -     void setSample (std::unique_ptr<AudioFormatReaderFactory> fact, AudioFormatManager& formatManager)
 -     {
 -         class SetSampleCommand
 -         {
 -         public:
 -             SetSampleCommand (std::unique_ptr<AudioFormatReaderFactory> r,
 -                               std::unique_ptr<Sample> sampleIn,
 -                               std::vector<std::unique_ptr<MPESamplerVoice>> newVoicesIn)
 -                 : readerFactory (std::move (r)),
 -                   sample (std::move (sampleIn)),
 -                   newVoices (std::move (newVoicesIn))
 -             {}
 - 
 -             void operator() (SamplerAudioProcessor& proc)
 -             {
 -                 proc.readerFactory = move (readerFactory);
 -                 auto sound = proc.samplerSound;
 -                 sound->setSample (std::move (sample));
 -                 auto numberOfVoices = proc.synthesiser.getNumVoices();
 -                 proc.synthesiser.clearVoices();
 - 
 -                 for (auto it = begin (newVoices); proc.synthesiser.getNumVoices() < numberOfVoices; ++it)
 -                 {
 -                     proc.synthesiser.addVoice (it->release());
 -                 }
 -             }
 - 
 -         private:
 -             std::unique_ptr<AudioFormatReaderFactory> readerFactory;
 -             std::unique_ptr<Sample> sample;
 -             std::vector<std::unique_ptr<MPESamplerVoice>> newVoices;
 -         };
 - 
 -         // Note that all allocation happens here, on the main message thread. Then,
 -         // we transfer ownership across to the audio thread.
 -         auto loadedSamplerSound = samplerSound;
 -         std::vector<std::unique_ptr<MPESamplerVoice>> newSamplerVoices;
 -         newSamplerVoices.reserve (maxVoices);
 - 
 -         for (auto i = 0; i != maxVoices; ++i)
 -             newSamplerVoices.emplace_back (new MPESamplerVoice (loadedSamplerSound));
 - 
 -         if (fact == nullptr)
 -         {
 -             commands.push (SetSampleCommand (move (fact),
 -                                              nullptr,
 -                                              move (newSamplerVoices)));
 -         }
 -         else if (auto reader = fact->make (formatManager))
 -         {
 -             commands.push (SetSampleCommand (move (fact),
 -                                              std::unique_ptr<Sample> (new Sample (*reader, 10.0)),
 -                                              move (newSamplerVoices)));
 -         }
 -     }
 - 
 -     void setCentreFrequency (double centreFrequency)
 -     {
 -         commands.push ([centreFrequency] (SamplerAudioProcessor& proc)
 -                        {
 -                            auto loaded = proc.samplerSound;
 -                            if (loaded != nullptr)
 -                                loaded->setCentreFrequencyInHz (centreFrequency);
 -                        });
 -     }
 - 
 -     void setLoopMode (LoopMode loopMode)
 -     {
 -         commands.push ([loopMode] (SamplerAudioProcessor& proc)
 -                        {
 -                            auto loaded = proc.samplerSound;
 -                            if (loaded != nullptr)
 -                                loaded->setLoopMode (loopMode);
 -                        });
 -     }
 - 
 -     void setLoopPoints (Range<double> loopPoints)
 -     {
 -         commands.push ([loopPoints] (SamplerAudioProcessor& proc)
 -                        {
 -                            auto loaded = proc.samplerSound;
 -                            if (loaded != nullptr)
 -                                loaded->setLoopPointsInSeconds (loopPoints);
 -                        });
 -     }
 - 
 -     void setMPEZoneLayout (MPEZoneLayout layout)
 -     {
 -         commands.push ([layout] (SamplerAudioProcessor& proc)
 -                        {
 -                            // setZoneLayout will lock internally, so we don't care too much about
 -                            // ensuring that the layout doesn't get copied or destroyed on the
 -                            // audio thread. If the audio glitches while updating midi settings
 -                            // it doesn't matter too much.
 -                            proc.synthesiser.setZoneLayout (layout);
 -                        });
 -     }
 - 
 -     void setLegacyModeEnabled (int pitchbendRange, Range<int> channelRange)
 -     {
 -         commands.push ([pitchbendRange, channelRange] (SamplerAudioProcessor& proc)
 -                        {
 -                            proc.synthesiser.enableLegacyMode (pitchbendRange, channelRange);
 -                        });
 -     }
 - 
 -     void setVoiceStealingEnabled (bool voiceStealingEnabled)
 -     {
 -         commands.push ([voiceStealingEnabled] (SamplerAudioProcessor& proc)
 -                        {
 -                            proc.synthesiser.setVoiceStealingEnabled (voiceStealingEnabled);
 -                        });
 -     }
 - 
 -     void setNumberOfVoices (int numberOfVoices)
 -     {
 -         // We don't want to call 'new' on the audio thread. Normally, we'd
 -         // construct things here, on the GUI thread, and then move them into the
 -         // command lambda. Unfortunately, C++11 doesn't have extended lambda
 -         // capture, so we use a custom struct instead.
 - 
 -         class SetNumVoicesCommand
 -         {
 -         public:
 -             SetNumVoicesCommand (std::vector<std::unique_ptr<MPESamplerVoice>> newVoicesIn)
 -                 : newVoices (std::move (newVoicesIn))
 -             {}
 - 
 -             void operator() (SamplerAudioProcessor& proc)
 -             {
 -                 if ((int) newVoices.size() < proc.synthesiser.getNumVoices())
 -                     proc.synthesiser.reduceNumVoices (int (newVoices.size()));
 -                 else
 -                     for (auto it = begin (newVoices); (size_t) proc.synthesiser.getNumVoices() < newVoices.size(); ++it)
 -                         proc.synthesiser.addVoice (it->release());
 -             }
 - 
 -         private:
 -             std::vector<std::unique_ptr<MPESamplerVoice>> newVoices;
 -         };
 - 
 -         numberOfVoices = std::min ((int) maxVoices, numberOfVoices);
 -         auto loadedSamplerSound = samplerSound;
 -         std::vector<std::unique_ptr<MPESamplerVoice>> newSamplerVoices;
 -         newSamplerVoices.reserve ((size_t) numberOfVoices);
 - 
 -         for (auto i = 0; i != numberOfVoices; ++i)
 -             newSamplerVoices.emplace_back (new MPESamplerVoice (loadedSamplerSound));
 - 
 -         commands.push (SetNumVoicesCommand (move (newSamplerVoices)));
 -     }
 - 
 -     // These accessors are just for an 'overview' and won't give the exact
 -     // state of the audio engine at a particular point in time.
 -     // If you call getNumVoices(), get the result '10', and then call
 -     // getPlaybackPosiiton(9), there's a chance the audio engine will have
 -     // been updated to remove some voices in the meantime, so the returned
 -     // value won't correspond to an existing voice.
 -     int getNumVoices() const                    { return synthesiser.getNumVoices(); }
 -     float getPlaybackPosition (int voice) const { return playbackPositions.at ((size_t) voice); }
 - 
 - private:
 -     //==============================================================================
 -     class SamplerAudioProcessorEditor  : public AudioProcessorEditor,
 -                                          public FileDragAndDropTarget,
 -                                          private DataModel::Listener,
 -                                          private MPESettingsDataModel::Listener
 -     {
 -     public:
 -         SamplerAudioProcessorEditor (SamplerAudioProcessor& p, ProcessorState state)
 -             : AudioProcessorEditor (&p),
 -               samplerAudioProcessor (p),
 -               mainSamplerView (dataModel,
 -                                [&p]
 -                                {
 -                                    std::vector<float> ret;
 -                                    auto voices = p.getNumVoices();
 -                                    ret.reserve ((size_t) voices);
 - 
 -                                    for (auto i = 0; i != voices; ++i)
 -                                        ret.emplace_back (p.getPlaybackPosition (i));
 - 
 -                                    return ret;
 -                                },
 -                                undoManager)
 -         {
 -             dataModel.addListener (*this);
 -             mpeSettings.addListener (*this);
 - 
 -             formatManager.registerBasicFormats();
 - 
 -             addAndMakeVisible (tabbedComponent);
 - 
 -             auto lookFeel = dynamic_cast<LookAndFeel_V4*> (&getLookAndFeel());
 -             auto bg = lookFeel->getCurrentColourScheme()
 -                                .getUIColour (LookAndFeel_V4::ColourScheme::UIColour::widgetBackground);
 - 
 -             tabbedComponent.addTab ("Sample Editor", bg, &mainSamplerView, false);
 -             tabbedComponent.addTab ("MPE Settings", bg, &settingsComponent, false);
 - 
 -             mpeSettings.setSynthVoices          (state.synthVoices,               nullptr);
 -             mpeSettings.setLegacyModeEnabled    (state.legacyModeEnabled,         nullptr);
 -             mpeSettings.setLegacyFirstChannel   (state.legacyChannels.getStart(), nullptr);
 -             mpeSettings.setLegacyLastChannel    (state.legacyChannels.getEnd(),   nullptr);
 -             mpeSettings.setLegacyPitchbendRange (state.legacyPitchbendRange,      nullptr);
 -             mpeSettings.setVoiceStealingEnabled (state.voiceStealingEnabled,      nullptr);
 -             mpeSettings.setMPEZoneLayout        (state.mpeZoneLayout,             nullptr);
 - 
 -             dataModel.setSampleReader (move (state.readerFactory),    nullptr);
 -             dataModel.setLoopPointsSeconds  (state.loopPointsSeconds, nullptr);
 -             dataModel.setCentreFrequencyHz  (state.centreFrequencyHz, nullptr);
 -             dataModel.setLoopMode           (state.loopMode,          nullptr);
 - 
 -             // Make sure that before the constructor has finished, you've set the
 -             // editor's size to whatever you need it to be.
 -             setResizable (true, true);
 -             setResizeLimits (640, 480, 2560, 1440);
 -             setSize (640, 480);
 -         }
 - 
 -     private:
 -         void resized() override
 -         {
 -             tabbedComponent.setBounds (getLocalBounds());
 -         }
 - 
 -         bool keyPressed (const KeyPress& key) override
 -         {
 -             if (key == KeyPress ('z', ModifierKeys::commandModifier, 0))
 -             {
 -                 undoManager.undo();
 -                 return true;
 -             }
 - 
 -             if (key == KeyPress ('z', ModifierKeys::commandModifier | ModifierKeys::shiftModifier, 0))
 -             {
 -                 undoManager.redo();
 -                 return true;
 -             }
 - 
 -             return Component::keyPressed (key);
 -         }
 - 
 -         bool isInterestedInFileDrag (const StringArray& files) override
 -         {
 -             WildcardFileFilter filter (formatManager.getWildcardForAllFormats(), {}, "Known Audio Formats");
 -             return files.size() == 1 && filter.isFileSuitable (files[0]);
 -         }
 - 
 -         void filesDropped (const StringArray& files, int, int) override
 -         {
 -             jassert (files.size() == 1);
 -             undoManager.beginNewTransaction();
 -             auto r = new FileAudioFormatReaderFactory (files[0]);
 -             dataModel.setSampleReader (std::unique_ptr<AudioFormatReaderFactory> (r),
 -                                        &undoManager);
 - 
 -         }
 - 
 -         void sampleReaderChanged (std::shared_ptr<AudioFormatReaderFactory> value) override
 -         {
 -             samplerAudioProcessor.setSample (value == nullptr ? nullptr : value->clone(),
 -                                              dataModel.getAudioFormatManager());
 -         }
 - 
 -         void centreFrequencyHzChanged (double value) override
 -         {
 -             samplerAudioProcessor.setCentreFrequency (value);
 -         }
 - 
 -         void loopPointsSecondsChanged (Range<double> value) override
 -         {
 -             samplerAudioProcessor.setLoopPoints (value);
 -         }
 - 
 -         void loopModeChanged (LoopMode value) override
 -         {
 -             samplerAudioProcessor.setLoopMode (value);
 -         }
 - 
 -         void synthVoicesChanged (int value) override
 -         {
 -             samplerAudioProcessor.setNumberOfVoices (value);
 -         }
 - 
 -         void voiceStealingEnabledChanged (bool value) override
 -         {
 -             samplerAudioProcessor.setVoiceStealingEnabled (value);
 -         }
 - 
 -         void legacyModeEnabledChanged (bool value) override
 -         {
 -             if (value)
 -                 setProcessorLegacyMode();
 -             else
 -                 setProcessorMPEMode();
 -         }
 - 
 -         void mpeZoneLayoutChanged (const MPEZoneLayout&) override
 -         {
 -             setProcessorMPEMode();
 -         }
 - 
 -         void legacyFirstChannelChanged (int) override
 -         {
 -             setProcessorLegacyMode();
 -         }
 - 
 -         void legacyLastChannelChanged (int) override
 -         {
 -             setProcessorLegacyMode();
 -         }
 - 
 -         void legacyPitchbendRangeChanged (int) override
 -         {
 -             setProcessorLegacyMode();
 -         }
 - 
 -         void setProcessorLegacyMode()
 -         {
 -             samplerAudioProcessor.setLegacyModeEnabled (mpeSettings.getLegacyPitchbendRange(),
 -                                                         Range<int> (mpeSettings.getLegacyFirstChannel(),
 -                                                         mpeSettings.getLegacyLastChannel()));
 -         }
 - 
 -         void setProcessorMPEMode()
 -         {
 -             samplerAudioProcessor.setMPEZoneLayout (mpeSettings.getMPEZoneLayout());
 -         }
 - 
 -         SamplerAudioProcessor& samplerAudioProcessor;
 -         AudioFormatManager formatManager;
 -         DataModel dataModel { formatManager };
 -         UndoManager undoManager;
 -         MPESettingsDataModel mpeSettings { dataModel.mpeSettings() };
 - 
 -         TabbedComponent tabbedComponent { TabbedButtonBar::Orientation::TabsAtTop };
 -         MPESettingsComponent settingsComponent { dataModel.mpeSettings(), undoManager };
 -         MainSamplerView mainSamplerView;
 - 
 -         JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SamplerAudioProcessorEditor)
 -     };
 - 
 -     //==============================================================================
 -     template <typename Element>
 -     void process (AudioBuffer<Element>& buffer, MidiBuffer& midiMessages)
 -     {
 -         // Try to acquire a lock on the command queue.
 -         // If we were successful, we pop all pending commands off the queue and
 -         // apply them to the processor.
 -         // If we weren't able to acquire the lock, it's because someone called
 -         // createEditor, which requires that the processor data model stays in
 -         // a valid state for the duration of the call.
 -         const GenericScopedTryLock<SpinLock> lock (commandQueueMutex);
 - 
 -         if (lock.isLocked())
 -             commands.call (*this);
 - 
 -         synthesiser.renderNextBlock (buffer, midiMessages, 0, buffer.getNumSamples());
 - 
 -         auto loadedSamplerSound = samplerSound;
 - 
 -         if (loadedSamplerSound->getSample() == nullptr)
 -             return;
 - 
 -         auto numVoices = synthesiser.getNumVoices();
 - 
 -         // Update the current playback positions
 -         for (auto i = 0; i < maxVoices; ++i)
 -         {
 -             auto* voicePtr = dynamic_cast<MPESamplerVoice*> (synthesiser.getVoice (i));
 - 
 -             if (i < numVoices && voicePtr != nullptr)
 -                 playbackPositions[(size_t) i] = static_cast<float> (voicePtr->getCurrentSamplePosition() / loadedSamplerSound->getSample()->getSampleRate());
 -             else
 -                 playbackPositions[(size_t) i] = 0.0f;
 -         }
 - 
 -     }
 - 
 -     CommandFifo<SamplerAudioProcessor> commands;
 - 
 -     MemoryBlock mb;
 -     std::unique_ptr<AudioFormatReaderFactory> readerFactory;
 -     std::shared_ptr<MPESamplerSound> samplerSound = std::make_shared<MPESamplerSound>();
 -     MPESynthesiser synthesiser;
 - 
 -     // This mutex is used to ensure we don't modify the processor state during
 -     // a call to createEditor, which would cause the UI to become desynched
 -     // with the real state of the processor.
 -     SpinLock commandQueueMutex;
 - 
 -     enum { maxVoices = 20 };
 - 
 -     // This is used for visualising the current playback position of each voice.
 -     std::array<std::atomic<float>, maxVoices> playbackPositions;
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (SamplerAudioProcessor)
 - };
 
 
  |