text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   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.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

#if JUCE_MAC || JUCE_IOS

struct CoreAudioLayouts
{
    //==============================================================================
    /** Convert CoreAudio's native AudioChannelLayout to JUCE's AudioChannelSet.

        Note that this method cannot preserve the order of channels.
    */
    static AudioChannelSet fromCoreAudio (const AudioChannelLayout& layout)
    {
        return AudioChannelSet::channelSetWithChannels (getCoreAudioLayoutChannels (layout));
    }

    /** Convert CoreAudio's native AudioChannelLayoutTag to JUCE's AudioChannelSet.

        Note that this method cannot preserve the order of channels.
    */
    static AudioChannelSet fromCoreAudio (AudioChannelLayoutTag layoutTag)
    {
        return AudioChannelSet::channelSetWithChannels (getSpeakerLayoutForCoreAudioTag (layoutTag));
    }

    /** Convert JUCE's AudioChannelSet to CoreAudio's AudioChannelLayoutTag.

        Note that this method cannot preserve the order of channels.
    */
    static AudioChannelLayoutTag toCoreAudio (const AudioChannelSet& set)
    {
        for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl)
        {
            AudioChannelSet caSet;

            for (int i = 0; i < numElementsInArray (tbl->channelTypes)
                 && tbl->channelTypes[i] != AudioChannelSet::unknown; ++i)
                caSet.addChannel (tbl->channelTypes[i]);

            if (caSet == set)
                return tbl->tag;
        }

        return kAudioChannelLayoutTag_DiscreteInOrder | static_cast<AudioChannelLayoutTag> (set.size());
    }

    static const Array<AudioChannelLayoutTag>& getKnownCoreAudioTags()
    {
        static Array<AudioChannelLayoutTag> tags (createKnownCoreAudioTags());
        return tags;
    }

    //==============================================================================
    /** Convert CoreAudio's native AudioChannelLayout to an array of JUCE ChannelTypes. */
    static Array<AudioChannelSet::ChannelType> getCoreAudioLayoutChannels (const AudioChannelLayout& layout)
    {
        switch (layout.mChannelLayoutTag & 0xffff0000)
        {
            case kAudioChannelLayoutTag_UseChannelBitmap:
                return AudioChannelSet::fromWaveChannelMask (static_cast<int> (layout.mChannelBitmap)).getChannelTypes();
            case kAudioChannelLayoutTag_UseChannelDescriptions:
            {
                Array<AudioChannelSet::ChannelType> channels;

                for (UInt32 i = 0; i < layout.mNumberChannelDescriptions; ++i)
                    channels.addIfNotAlreadyThere (getChannelTypeFromAudioChannelLabel (layout.mChannelDescriptions[i].mChannelLabel));

                // different speaker mappings may point to the same JUCE speaker so fill up
                // this array with discrete channels
                for (int j = 0; channels.size() < static_cast<int> (layout.mNumberChannelDescriptions); ++j)
                    channels.addIfNotAlreadyThere (static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + j));

                return channels;
            }
            case kAudioChannelLayoutTag_DiscreteInOrder:
                return AudioChannelSet::discreteChannels (static_cast<int> (layout.mChannelLayoutTag) & 0xffff).getChannelTypes();
            default:
                break;
        }

        return getSpeakerLayoutForCoreAudioTag (layout.mChannelLayoutTag);
    }

    static Array<AudioChannelSet::ChannelType> getSpeakerLayoutForCoreAudioTag (AudioChannelLayoutTag tag)
    {
        // You need to specify the full AudioChannelLayout when using
        // the UseChannelBitmap and UseChannelDescriptions layout tag
        jassert (tag != kAudioChannelLayoutTag_UseChannelBitmap && tag != kAudioChannelLayoutTag_UseChannelDescriptions);

        Array<AudioChannelSet::ChannelType> speakers;

        for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl)
        {
            if (tag == tbl->tag)
            {
                for (int i = 0; i < numElementsInArray (tbl->channelTypes)
                                  && tbl->channelTypes[i] != AudioChannelSet::unknown; ++i)
                    speakers.add (tbl->channelTypes[i]);

                return speakers;
            }
        }

        auto numChannels = tag & 0xffff;
        for (UInt32 i = 0; i < numChannels; ++i)
            speakers.add (static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + i));

        return speakers;
    }

private:
    //==============================================================================
    struct LayoutTagSpeakerList
    {
        AudioChannelLayoutTag tag;
        AudioChannelSet::ChannelType channelTypes[16];
    };

    static Array<AudioChannelLayoutTag> createKnownCoreAudioTags()
    {
        Array<AudioChannelLayoutTag> tags;

        for (auto* tbl = SpeakerLayoutTable::get(); tbl->tag != 0; ++tbl)
            tags.addIfNotAlreadyThere (tbl->tag);

        return tags;
    }

    //==============================================================================
    // This list has been derived from https://pastebin.com/24dQ4BPJ
    // Apple channel labels have been replaced by JUCE channel names
    // This means that some layouts will be identical in JUCE but not in CoreAudio

    // In Apple's official definition the following tags exist with the same speaker layout and order
    // even when *not* represented in JUCE channels
    // kAudioChannelLayoutTag_Binaural = kAudioChannelLayoutTag_Stereo
    // kAudioChannelLayoutTag_MPEG_5_0_B = kAudioChannelLayoutTag_Pentagonal
    // kAudioChannelLayoutTag_ITU_2_2 = kAudioChannelLayoutTag_Quadraphonic
    // kAudioChannelLayoutTag_AudioUnit_6_0 = kAudioChannelLayoutTag_Hexagonal
    struct SpeakerLayoutTable : AudioChannelSet // save us some typing
    {
        static LayoutTagSpeakerList* get() noexcept
        {
            static LayoutTagSpeakerList tbl[] = {
                // list layouts for which there is a corresponding named AudioChannelSet first
                { kAudioChannelLayoutTag_Mono, { centre } },
                { kAudioChannelLayoutTag_Stereo, { left, right } },
                { kAudioChannelLayoutTag_MPEG_3_0_A, { left, right, centre } },
                { kAudioChannelLayoutTag_ITU_2_1, { left, right, centreSurround } },
                { kAudioChannelLayoutTag_MPEG_4_0_A, { left, right, centre, centreSurround } },
                { kAudioChannelLayoutTag_MPEG_5_0_A, { left, right, centre, leftSurround, rightSurround } },
                { kAudioChannelLayoutTag_MPEG_5_1_A, { left, right, centre, LFE, leftSurround, rightSurround } },
                { kAudioChannelLayoutTag_AudioUnit_6_0, { left, right, leftSurround, rightSurround, centre, centreSurround } },
                { kAudioChannelLayoutTag_MPEG_6_1_A, { left, right, centre, LFE, leftSurround, rightSurround, centreSurround } },
                { kAudioChannelLayoutTag_DTS_6_0_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround } },
                { kAudioChannelLayoutTag_DTS_6_1_A, { leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround, LFE } },
                { kAudioChannelLayoutTag_AudioUnit_7_0, { left, right, leftSurroundSide, rightSurroundSide, centre, leftSurroundRear, rightSurroundRear } },
                { kAudioChannelLayoutTag_AudioUnit_7_0_Front, { left, right, leftSurround, rightSurround, centre, leftCentre, rightCentre } },
                { kAudioChannelLayoutTag_MPEG_7_1_C, { left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear } },
                { kAudioChannelLayoutTag_MPEG_7_1_A, { left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre } },
                { kAudioChannelLayoutTag_Ambisonic_B_Format, { ambisonicW, ambisonicX, ambisonicY, ambisonicZ } },
                { kAudioChannelLayoutTag_Quadraphonic, { left, right, leftSurround, rightSurround } },
                { kAudioChannelLayoutTag_Pentagonal, { left, right, leftSurroundRear, rightSurroundRear, centre } },
                { kAudioChannelLayoutTag_Hexagonal, { left, right, leftSurroundRear, rightSurroundRear, centre, centreSurround } },
                { kAudioChannelLayoutTag_Octagonal, { left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight } },

                // more uncommon layouts
                { kAudioChannelLayoutTag_StereoHeadphones, { left, right } },
                { kAudioChannelLayoutTag_MatrixStereo, { left, right } },
                { kAudioChannelLayoutTag_MidSide, { centre, discreteChannel0 } },
                { kAudioChannelLayoutTag_XY, { ambisonicX, ambisonicY } },
                { kAudioChannelLayoutTag_Binaural, { left, right } },
                { kAudioChannelLayoutTag_Cube, { left, right, leftSurround, rightSurround, topFrontLeft, topFrontRight, topRearLeft, topRearRight } },
                { kAudioChannelLayoutTag_MPEG_3_0_B, { centre, left, right } },
                { kAudioChannelLayoutTag_MPEG_4_0_B, { centre, left, right, centreSurround } },
                { kAudioChannelLayoutTag_MPEG_5_0_B, { left, right, leftSurround, rightSurround, centre } },
                { kAudioChannelLayoutTag_MPEG_5_0_C, { left, centre, right, leftSurround, rightSurround } },
                { kAudioChannelLayoutTag_MPEG_5_0_D, { centre, left, right, leftSurround, rightSurround } },
                { kAudioChannelLayoutTag_MPEG_5_1_B, { left, right, leftSurround, rightSurround, centre, LFE } },
                { kAudioChannelLayoutTag_MPEG_5_1_C, { left, centre, right, leftSurround, rightSurround, LFE } },
                { kAudioChannelLayoutTag_MPEG_5_1_D, { centre, left, right, leftSurround, rightSurround, LFE } },
                { kAudioChannelLayoutTag_MPEG_7_1_B, { centre, leftCentre, rightCentre, left, right, leftSurround, rightSurround, LFE } },
                { kAudioChannelLayoutTag_Emagic_Default_7_1, { left, right, leftSurround, rightSurround, centre, LFE, leftCentre, rightCentre } },
                { kAudioChannelLayoutTag_SMPTE_DTV, { left, right, centre, LFE, leftSurround, rightSurround, discreteChannel0 /* leftMatrixTotal */, (ChannelType) (discreteChannel0 + 1) /* rightMatrixTotal */} },
                { kAudioChannelLayoutTag_ITU_2_2, { left, right, leftSurround, rightSurround } },
                { kAudioChannelLayoutTag_DVD_4, { left, right, LFE } },
                { kAudioChannelLayoutTag_DVD_5, { left, right, LFE, centreSurround } },
                { kAudioChannelLayoutTag_DVD_6, { left, right, LFE, leftSurround, rightSurround } },
                { kAudioChannelLayoutTag_DVD_10, { left, right, centre, LFE } },
                { kAudioChannelLayoutTag_DVD_11, { left, right, centre, LFE, centreSurround } },
                { kAudioChannelLayoutTag_DVD_18, { left, right, leftSurround, rightSurround, LFE } },
                { kAudioChannelLayoutTag_AAC_6_0, { centre, left, right, leftSurround, rightSurround, centreSurround } },
                { kAudioChannelLayoutTag_AAC_6_1, { centre, left, right, leftSurround, rightSurround, centreSurround, LFE } },
                { kAudioChannelLayoutTag_AAC_7_0, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } },
                { kAudioChannelLayoutTag_AAC_7_1_B, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } },
                { kAudioChannelLayoutTag_AAC_7_1_C, { centre, left, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } },
                { kAudioChannelLayoutTag_AAC_Octagonal, { centre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, centreSurround } },
                { kAudioChannelLayoutTag_TMH_10_2_std, { left, right, centre, topFrontCentre, leftSurroundSide, rightSurroundSide, leftSurround, rightSurround, topFrontLeft, topFrontRight, wideLeft, wideRight, topRearCentre, centreSurround, LFE, LFE2 } },
                { kAudioChannelLayoutTag_AC3_1_0_1, { centre, LFE } },
                { kAudioChannelLayoutTag_AC3_3_0, { left, centre, right } },
                { kAudioChannelLayoutTag_AC3_3_1, { left, centre, right, centreSurround } },
                { kAudioChannelLayoutTag_AC3_3_0_1, { left, centre, right, LFE } },
                { kAudioChannelLayoutTag_AC3_2_1_1, { left, right, centreSurround, LFE } },
                { kAudioChannelLayoutTag_AC3_3_1_1, { left, centre, right, centreSurround, LFE } },
                { kAudioChannelLayoutTag_EAC_6_0_A, { left, centre, right, leftSurround, rightSurround, centreSurround } },
                { kAudioChannelLayoutTag_EAC_7_0_A, { left, centre, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } },
                { kAudioChannelLayoutTag_EAC3_6_1_A, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } },
                { kAudioChannelLayoutTag_EAC3_6_1_B, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround } },
                { kAudioChannelLayoutTag_EAC3_6_1_C, { left, centre, right, leftSurround, rightSurround, LFE, topFrontCentre } },
                { kAudioChannelLayoutTag_EAC3_7_1_A, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundRear, rightSurroundRear } },
                { kAudioChannelLayoutTag_EAC3_7_1_B, { left, centre, right, leftSurround, rightSurround, LFE, leftCentre, rightCentre } },
                { kAudioChannelLayoutTag_EAC3_7_1_C, { left, centre, right, leftSurround, rightSurround, LFE, leftSurroundSide, rightSurroundSide } },
                { kAudioChannelLayoutTag_EAC3_7_1_D, { left, centre, right, leftSurround, rightSurround, LFE, wideLeft, wideRight } },
                { kAudioChannelLayoutTag_EAC3_7_1_E, { left, centre, right, leftSurround, rightSurround, LFE, topFrontLeft, topFrontRight } },
                { kAudioChannelLayoutTag_EAC3_7_1_F, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topMiddle } },
                { kAudioChannelLayoutTag_EAC3_7_1_G, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } },
                { kAudioChannelLayoutTag_EAC3_7_1_H, { left, centre, right, leftSurround, rightSurround, LFE, centreSurround, topFrontCentre } },
                { kAudioChannelLayoutTag_DTS_3_1, { centre, left, right, LFE } },
                { kAudioChannelLayoutTag_DTS_4_1, { centre, left, right, centreSurround, LFE } },
                { kAudioChannelLayoutTag_DTS_6_0_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround } },
                { kAudioChannelLayoutTag_DTS_6_0_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear } },
                { kAudioChannelLayoutTag_DTS_6_1_B, { centre, left, right, leftSurroundRear, rightSurroundRear, centreSurround, LFE } },
                { kAudioChannelLayoutTag_DTS_6_1_C, { centre, centreSurround, left, right, leftSurroundRear, rightSurroundRear, LFE } },
                { kAudioChannelLayoutTag_DTS_6_1_D, { centre, left, right, leftSurround, rightSurround, LFE, centreSurround } },
                { kAudioChannelLayoutTag_DTS_7_0, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround } },
                { kAudioChannelLayoutTag_DTS_7_1, { leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround, LFE } },
                { kAudioChannelLayoutTag_DTS_8_0_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear } },
                { kAudioChannelLayoutTag_DTS_8_0_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround } },
                { kAudioChannelLayoutTag_DTS_8_1_A, { leftCentre, rightCentre, left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, LFE } },
                { kAudioChannelLayoutTag_DTS_8_1_B, { leftCentre, centre, rightCentre, left, right, leftSurround, centreSurround, rightSurround, LFE } },
                { 0, {} }
            };

            return tbl;
        }
    };

    //==============================================================================
    static AudioChannelSet::ChannelType getChannelTypeFromAudioChannelLabel (AudioChannelLabel label) noexcept
    {
        if (label >= kAudioChannelLabel_Discrete_0 && label <= kAudioChannelLabel_Discrete_65535)
        {
            const unsigned int discreteChannelNum = label - kAudioChannelLabel_Discrete_0;
            return static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + discreteChannelNum);
        }

        switch (label)
        {
            case kAudioChannelLabel_Center:
            case kAudioChannelLabel_Mono:                   return AudioChannelSet::centre;
            case kAudioChannelLabel_Left:
            case kAudioChannelLabel_HeadphonesLeft:         return AudioChannelSet::left;
            case kAudioChannelLabel_Right:
            case kAudioChannelLabel_HeadphonesRight:        return AudioChannelSet::right;
            case kAudioChannelLabel_LFEScreen:              return AudioChannelSet::LFE;
            case kAudioChannelLabel_LeftSurround:           return AudioChannelSet::leftSurround;
            case kAudioChannelLabel_RightSurround:          return AudioChannelSet::rightSurround;
            case kAudioChannelLabel_LeftCenter:             return AudioChannelSet::leftCentre;
            case kAudioChannelLabel_RightCenter:            return AudioChannelSet::rightCentre;
            case kAudioChannelLabel_CenterSurround:         return AudioChannelSet::surround;
            case kAudioChannelLabel_LeftSurroundDirect:     return AudioChannelSet::leftSurroundSide;
            case kAudioChannelLabel_RightSurroundDirect:    return AudioChannelSet::rightSurroundSide;
            case kAudioChannelLabel_TopCenterSurround:      return AudioChannelSet::topMiddle;
            case kAudioChannelLabel_VerticalHeightLeft:     return AudioChannelSet::topFrontLeft;
            case kAudioChannelLabel_VerticalHeightRight:    return AudioChannelSet::topFrontRight;
            case kAudioChannelLabel_VerticalHeightCenter:   return AudioChannelSet::topFrontCentre;
            case kAudioChannelLabel_TopBackLeft:            return AudioChannelSet::topRearLeft;
            case kAudioChannelLabel_RearSurroundLeft:       return AudioChannelSet::leftSurroundRear;
            case kAudioChannelLabel_TopBackRight:           return AudioChannelSet::topRearRight;
            case kAudioChannelLabel_RearSurroundRight:      return AudioChannelSet::rightSurroundRear;
            case kAudioChannelLabel_TopBackCenter:          return AudioChannelSet::topRearCentre;
            case kAudioChannelLabel_LFE2:                   return AudioChannelSet::LFE2;
            case kAudioChannelLabel_LeftWide:               return AudioChannelSet::wideLeft;
            case kAudioChannelLabel_RightWide:              return AudioChannelSet::wideRight;
            case kAudioChannelLabel_Ambisonic_W:            return AudioChannelSet::ambisonicW;
            case kAudioChannelLabel_Ambisonic_X:            return AudioChannelSet::ambisonicX;
            case kAudioChannelLabel_Ambisonic_Y:            return AudioChannelSet::ambisonicY;
            case kAudioChannelLabel_Ambisonic_Z:            return AudioChannelSet::ambisonicZ;
            default:                                        return AudioChannelSet::unknown;
        }
    }
};

#endif

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   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.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    Derive from this class to create a basic audio generator capable of MPE.
    Implement the callbacks of MPEInstrument::Listener (noteAdded, notePressureChanged
    etc.) to let your audio generator know that MPE notes were triggered, modulated,
    or released. What to do inside them, and how that influences your audio generator,
    is up to you!

    This class uses an instance of MPEInstrument internally to handle the MPE
    note state logic.

    This class is a very low-level base class for an MPE instrument. If you need
    something more sophisticated, have a look at MPESynthesiser. This class extends
    MPESynthesiserBase by adding the concept of voices that can play notes,
    a voice stealing algorithm, and much more.

    @see MPESynthesiser, MPEInstrument
*/
struct JUCE_API  MPESynthesiserBase   : public MPEInstrument::Listener
{
public:
    //==============================================================================
    /** Constructor. */
    MPESynthesiserBase();

    /** Constructor.

        If you use this constructor, the synthesiser will take ownership of the
        provided instrument object, and will use it internally to handle the
        MPE note state logic.
        This is useful if you want to use an instance of your own class derived
        from MPEInstrument for the MPE logic.
    */
    MPESynthesiserBase (MPEInstrument* instrument);

    //==============================================================================
    /** Returns the synthesiser's internal MPE zone layout.
        This happens by value, to enforce thread-safety and class invariants.
    */
    MPEZoneLayout getZoneLayout() const noexcept;

    /** Re-sets the synthesiser's internal MPE zone layout to the one passed in.
        As a side effect, this will discard all currently playing notes,
        call noteReleased for all of them, and disable legacy mode (if previously enabled).
    */
    void setZoneLayout (MPEZoneLayout newLayout);

    //==============================================================================
    /** Tells the synthesiser what the sample rate is for the audio it's being
        used to render.
    */
    virtual void setCurrentPlaybackSampleRate (double sampleRate);

    /** Returns the current target sample rate at which rendering is being done.
        Subclasses may need to know this so that they can pitch things correctly.
    */
    double getSampleRate() const noexcept          { return sampleRate; }

    //==============================================================================
    /** Creates the next block of audio output.

        Call this to make sound. This will chop up the AudioBuffer into subBlock
        pieces separated by events in the MIDI buffer, and then call
        processNextSubBlock on each one of them. In between you will get calls
        to noteAdded/Changed/Finished, where you can update parameters that
        depend on those notes to use for your audio rendering.
    */
    template <typename floatType>
    void renderNextBlock (AudioBuffer<floatType>& outputAudio,
                          const MidiBuffer& inputMidi,
                          int startSample,
                          int numSamples);

    //==============================================================================
    /** Handle incoming MIDI events (called from renderNextBlock).

        The default implementation provided here simply forwards everything
        to MPEInstrument::processNextMidiEvent, where it is used to update the
        MPE notes, zones etc. MIDI messages not relevant for MPE are ignored.

        This method can be overridden if you need to do custom MIDI handling
        on top of MPE. The MPESynthesiser class overrides this to implement
        callbacks for MIDI program changes and non-MPE-related MIDI controller
        messages.
    */
    virtual void handleMidiEvent (const MidiMessage&);

    //==============================================================================
    /** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering.

        When rendering, the audio blocks that are passed into renderNextBlock() will be split up
        into smaller blocks that lie between all the incoming midi messages, and it is these smaller
        sub-blocks that are rendered with multiple calls to renderVoices().

        Obviously in a pathological case where there are midi messages on every sample, then
        renderVoices() could be called once per sample and lead to poor performance, so this
        setting allows you to set a lower limit on the block size.

        The default setting is 32, which means that midi messages are accurate to about < 1ms
        accuracy, which is probably fine for most purposes, but you may want to increase or
        decrease this value for your synth.

        If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples.

        If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed
        to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate
        (this can sometimes help to avoid quantisation or phasing issues).
    */
    void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept;

    //==============================================================================
    /** Puts the synthesiser into legacy mode.

        @param pitchbendRange   The note pitchbend range in semitones to use when in legacy mode.
                                Must be between 0 and 96, otherwise behaviour is undefined.
                                The default pitchbend range in legacy mode is +/- 2 semitones.
        @param channelRange     The range of MIDI channels to use for notes when in legacy mode.
                                The default is to use all MIDI channels (1-16).

        To get out of legacy mode, set a new MPE zone layout using setZoneLayout.
    */
    void enableLegacyMode (int pitchbendRange = 2,
                           Range<int> channelRange = Range<int> (1, 17));

    /** Returns true if the instrument is in legacy mode, false otherwise. */
    bool isLegacyModeEnabled() const noexcept;

    /** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
    Range<int> getLegacyModeChannelRange() const noexcept;

    /** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */
    void setLegacyModeChannelRange (Range<int> channelRange);

    /** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
    int getLegacyModePitchbendRange() const noexcept;

    /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */
    void setLegacyModePitchbendRange (int pitchbendRange);

    //==============================================================================
    typedef MPEInstrument::TrackingMode TrackingMode;

    /** Set the MPE tracking mode for the pressure dimension. */
    void setPressureTrackingMode (TrackingMode modeToUse);

    /** Set the MPE tracking mode for the pitchbend dimension. */
    void setPitchbendTrackingMode (TrackingMode modeToUse);

    /** Set the MPE tracking mode for the timbre dimension. */
    void setTimbreTrackingMode (TrackingMode modeToUse);

protected:
    //==============================================================================
    /** Implement this method to render your audio inside.
        @see renderNextBlock
    */
    virtual void renderNextSubBlock (AudioBuffer<float>& outputAudio,
                                     int startSample,
                                     int numSamples) = 0;

    /** Implement this method if you want to render 64-bit audio as well;
        otherwise leave blank.
     */
    virtual void renderNextSubBlock (AudioBuffer<double>& /*outputAudio*/,
                                     int /*startSample*/,
                                     int /*numSamples*/) {}

protected:
    //==============================================================================
    /** @internal */
    ScopedPointer<MPEInstrument> instrument;

private:
    //==============================================================================
    CriticalSection noteStateLock;
    double sampleRate;
    int minimumSubBlockSize;
    bool subBlockSubdivisionIsStrict;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase)
};

} // namespace juce

repo_name: fedden/RenderMan

mime_type: text/x-c++


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   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.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

MPESynthesiserVoice::MPESynthesiserVoice()
    : currentSampleRate (0), noteStartTime (0)
{
}

MPESynthesiserVoice::~MPESynthesiserVoice()
{
}

//==============================================================================
bool MPESynthesiserVoice::isCurrentlyPlayingNote (MPENote note) const noexcept
{
    return isActive() && currentlyPlayingNote.noteID == note.noteID;
}

bool MPESynthesiserVoice::isPlayingButReleased() const noexcept
{
    return isActive() && currentlyPlayingNote.keyState == MPENote::off;
}

bool MPESynthesiserVoice::wasStartedBefore (const MPESynthesiserVoice& other) const noexcept
{
    return noteStartTime < other.noteStartTime;
}

void MPESynthesiserVoice::clearCurrentNote() noexcept
{
    currentlyPlayingNote = MPENote();
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   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.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    Base class for an MPE-compatible musical device that can play sounds.

    This class extends MPESynthesiserBase by adding the concept of voices,
    each of which can play a sound triggered by a MPENote that can be modulated
    by MPE dimensions like pressure, pitchbend, and timbre, while the note is
    sounding.

    To create a synthesiser, you'll need to create a subclass of MPESynthesiserVoice
    which can play back one of these sounds at a time.

    Then you can use the addVoice() methods to give the synthesiser a set of voices
    it can use to play notes. If you only give it one voice it will be monophonic -
    the more voices it has, the more polyphony it'll have available.

    Then repeatedly call the renderNextBlock() method to produce the audio (inherited
    from MPESynthesiserBase). The voices will be started, stopped, and modulated
    automatically, based on the MPE/MIDI messages that the synthesiser receives.

    Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it
    what the target playback rate is. This value is passed on to the voices so that
    they can pitch their output correctly.

    @see MPESynthesiserBase, MPESythesiserVoice, MPENote, MPEInstrument
*/
class JUCE_API  MPESynthesiser   : public MPESynthesiserBase
{
public:
    //==============================================================================
    /** Constructor.
        You'll need to add some voices before it'll make any sound.

        @see addVoice
    */
    MPESynthesiser();

    /** Constructor to pass to the synthesiser a custom MPEInstrument object
        to handle the MPE note state, MIDI channel assignment etc.
        (in case you need custom logic for this that goes beyond MIDI and MPE).
        The synthesiser will take ownership of this object.

        @see MPESynthesiserBase, MPEInstrument
    */
    MPESynthesiser (MPEInstrument* instrument);

    /** Destructor. */
    ~MPESynthesiser();

    //==============================================================================
    /** Deletes all voices. */
    void clearVoices();

    /** Returns the number of voices that have been added. */
    int getNumVoices() const noexcept                               { return voices.size(); }

    /** Returns one of the voices that have been added. */
    MPESynthesiserVoice* getVoice (int index) const;

    /** Adds a new voice to the synth.

        All the voices should be the same class of object and are treated equally.

        The object passed in will be managed by the synthesiser, which will delete
        it later on when no longer needed. The caller should not retain a pointer to the
        voice.
    */
    void addVoice (MPESynthesiserVoice* newVoice);

    /** Deletes one of the voices. */
    void removeVoice (int index);

    /** Reduces the number of voices to newNumVoices.

        This will repeatedly call findVoiceToSteal() and remove that voice, until
        the total number of voices equals newNumVoices. If newNumVoices is greater than
        or equal to the current number of voices, this method does nothing.
    */
    void reduceNumVoices (int newNumVoices);

    /** Release all MPE notes and turn off all voices.

        If allowTailOff is true, the voices will be allowed to fade out the notes gracefully
        (if they can do). If this is false, the notes will all be cut off immediately.

        This method is meant to be called by the user, for example to implement
        a MIDI panic button in a synth.
    */
    virtual void turnOffAllVoices (bool allowTailOff);

    //==============================================================================
    /** If set to true, then the synth will try to take over an existing voice if
        it runs out and needs to play another note.

        The value of this boolean is passed into findFreeVoice(), so the result will
        depend on the implementation of this method.
    */
    void setVoiceStealingEnabled (bool shouldSteal) noexcept    { shouldStealVoices = shouldSteal; }

    /** Returns true if note-stealing is enabled. */
    bool isVoiceStealingEnabled() const noexcept                { return shouldStealVoices; }

    //==============================================================================
    /** Tells the synthesiser what the sample rate is for the audio it's being used to render.

        This overrides the implementation in MPESynthesiserBase, to additionally
        propagate the new value to the voices so that they can use it to render the correct
        pitches.
    */
    void setCurrentPlaybackSampleRate (double newRate) override;

    //==============================================================================
    /** Handle incoming MIDI events.

        This method will be called automatically according to the MIDI data passed
        into renderNextBlock(), but you can also call it yourself to manually
        inject MIDI events.

        This implementation forwards program change messages and non-MPE-related
        controller messages to handleProgramChange and handleController, respectively,
        and then simply calls through to MPESynthesiserBase::handleMidiEvent to deal
        with MPE-related MIDI messages used for MPE notes, zones etc.

        This method can be overridden further if you need to do custom MIDI
        handling on top of what is provided here.
    */
    void handleMidiEvent (const MidiMessage&) override;

    /** Callback for MIDI controller messages. The default implementation
        provided here does nothing; override this method if you need custom
        MIDI controller handling on top of MPE.

        This method will be called automatically according to the midi data passed into
        renderNextBlock().
    */
    virtual void handleController (int /*midiChannel*/,
                                   int /*controllerNumber*/,
                                   int /*controllerValue*/) {}

    /** Callback for MIDI program change messages. The default implementation
        provided here does nothing; override this method if you need to handle
        those messages.

        This method will be called automatically according to the midi data passed into
        renderNextBlock().
    */
    virtual void handleProgramChange (int /*midiChannel*/,
                                      int /*programNumber*/) {}

protected:
    //==============================================================================
    /** Attempts to start playing a new note.

        The default method here will find a free voice that is appropriate for
        playing the given MPENote, and use that voice to start playing the sound.
        If isNoteStealingEnabled returns true (set this by calling setNoteStealingEnabled),
        the synthesiser will use the voice stealing algorithm to find a free voice for
        the note (if no voices are free otherwise).

        This method will be called automatically according to the midi data passed into
        renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state
        will become inconsistent.
    */
    virtual void noteAdded (MPENote newNote) override;

    /** Stops playing a note.

        This will be called whenever an  MPE note is released (either by a note-off message,
        or by a sustain/sostenuto pedal release for a note that already received a note-off),
        and should therefore stop playing.

        This will find any voice that is currently playing finishedNote,
        turn its currently playing note off, and call its noteStopped callback.

        This method will be called automatically according to the midi data passed into
        renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state
        will become inconsistent.
    */
    virtual void noteReleased (MPENote finishedNote) override;

    /** Will find any voice that is currently playing changedNote, update its
        currently playing note, and call its notePressureChanged method.

        This method will be called automatically according to the midi data passed into
        renderNextBlock(). Do not call it yourself.
    */
    virtual void notePressureChanged (MPENote changedNote) override;

    /** Will find any voice that is currently playing changedNote, update its
        currently playing note, and call its notePitchbendChanged method.

        This method will be called automatically according to the midi data passed into
        renderNextBlock(). Do not call it yourself.
    */
    virtual void notePitchbendChanged (MPENote changedNote) override;

    /** Will find any voice that is currently playing changedNote, update its
        currently playing note, and call its noteTimbreChanged method.

        This method will be called automatically according to the midi data passed into
        renderNextBlock(). Do not call it yourself.
    */
    virtual void noteTimbreChanged (MPENote changedNote) override;

    /** Will find any voice that is currently playing changedNote, update its
        currently playing note, and call its noteKeyStateChanged method.

        This method will be called automatically according to the midi data passed into
        renderNextBlock(). Do not call it yourself.
     */
    virtual void noteKeyStateChanged (MPENote changedNote) override;

    //==============================================================================
    /** This will simply call renderNextBlock for each currently active
        voice and fill the buffer with the sum.
        Override this method if you need to do more work to render your audio.
    */
    virtual void renderNextSubBlock (AudioBuffer<float>& outputAudio,
                                     int startSample,
                                     int numSamples) override;

    /** This will simply call renderNextBlock for each currently active
        voice and fill the buffer with the sum. (souble-precision version)
        Override this method if you need to do more work to render your audio.
    */
    virtual void renderNextSubBlock (AudioBuffer<double>& outputAudio,
                                     int startSample,
                                     int numSamples) override;

    //==============================================================================
    /** Searches through the voices to find one that's not currently playing, and
        which can play the given MPE note.

        If all voices are active and stealIfNoneAvailable is false, this returns
        a nullptr. If all voices are active and stealIfNoneAvailable is true,
        this will call findVoiceToSteal() to find a voice.

        If you need to find a free voice for something else than playing a note
        (e.g. for deleting it), you can pass an invalid (default-constructed) MPENote.
    */
    virtual MPESynthesiserVoice* findFreeVoice (MPENote noteToFindVoiceFor,
                                                bool stealIfNoneAvailable) const;

    /** Chooses a voice that is most suitable for being re-used to play a new
        note, or for being deleted by reduceNumVoices.

        The default method will attempt to find the oldest voice that isn't the
        bottom or top note being played. If that's not suitable for your synth,
        you can override this method and do something more cunning instead.

        If you pass a valid MPENote for the optional argument, then the note number
        of that note will be taken into account for finding the ideal voice to steal.
        If you pass an invalid (default-constructed) MPENote instead, this part of
        the algorithm will be ignored.
    */
    virtual MPESynthesiserVoice* findVoiceToSteal (MPENote noteToStealVoiceFor = MPENote()) const;

    /** Starts a specified voice and tells it to play a particular MPENote.
        You should never need to call this, it's called internally by
        MPESynthesiserBase::instrument via the noteStarted callback,
        but is protected in case it's useful for some custom subclasses.
    */
    void startVoice (MPESynthesiserVoice* voice, MPENote noteToStart);

    /** Stops a given voice and tells it to stop playing a particular MPENote
        (which should be the same note it is actually playing).
        You should never need to call this, it's called internally by
        MPESynthesiserBase::instrument via the noteReleased callback,
        but is protected in case it's useful for some custom subclasses.
    */
    void stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff);

    //==============================================================================
    OwnedArray<MPESynthesiserVoice> voices;
    CriticalSection voicesLock;

private:
    //==============================================================================
    bool shouldStealVoices;

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser)
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   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.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

namespace
{
    uint16 generateNoteID (int midiChannel, int midiNoteNumber) noexcept
    {
        jassert (midiChannel > 0 && midiChannel <= 16);
        jassert (midiNoteNumber >= 0 && midiNoteNumber < 128);

        return uint16 ((midiChannel << 7) + midiNoteNumber);
    }
}

//==============================================================================
MPENote::MPENote (int midiChannel_,
                  int initialNote_,
                  MPEValue noteOnVelocity_,
                  MPEValue pitchbend_,
                  MPEValue pressure_,
                  MPEValue timbre_,
                  KeyState keyState_) noexcept
    : noteID (generateNoteID (midiChannel_, initialNote_)),
      midiChannel (uint8 (midiChannel_)),
      initialNote (uint8 (initialNote_)),
      noteOnVelocity (noteOnVelocity_),
      pitchbend (pitchbend_),
      pressure (pressure_),
      timbre (timbre_),
      noteOffVelocity (MPEValue::minValue()),
      keyState (keyState_)
{
    jassert (keyState != MPENote::off);
    jassert (isValid());
}

MPENote::MPENote() noexcept
    : noteID (0),
      midiChannel (0),
      initialNote (0),
      noteOnVelocity (MPEValue::minValue()),
      pitchbend (MPEValue::centreValue()),
      pressure (MPEValue::centreValue()),
      timbre (MPEValue::centreValue()),
      noteOffVelocity (MPEValue::minValue()),
      keyState (MPENote::off)
{
}

//==============================================================================
bool MPENote::isValid() const noexcept
{
    return midiChannel > 0 && midiChannel <= 16 && initialNote < 128;
}

//==============================================================================
double MPENote::getFrequencyInHertz (double frequencyOfA) const noexcept
{
    double pitchInSemitones = double (initialNote) + totalPitchbendInSemitones;
    return frequencyOfA * std::pow (2.0, (pitchInSemitones - 69.0) / 12.0);
}

//==============================================================================
bool MPENote::operator== (const MPENote& other) const noexcept
{
    jassert (isValid() && other.isValid());
    return noteID == other.noteID;
}

bool MPENote::operator!= (const MPENote& other) const noexcept
{
    jassert (isValid() && other.isValid());
    return noteID != other.noteID;
}

//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS

class MPENoteTests : public UnitTest
{
public:
    MPENoteTests() : UnitTest ("MPENote class", "MIDI/MPE") {}

    //==============================================================================
    void runTest() override
    {
        beginTest ("getFrequencyInHertz");
        {
            MPENote note;
            note.initialNote = 60;
            note.totalPitchbendInSemitones = -0.5;
            expectEqualsWithinOneCent (note.getFrequencyInHertz(), 254.178);
        }
    }

private:
    //==============================================================================
    void expectEqualsWithinOneCent (double frequencyInHertzActual,
                                    double frequencyInHertzExpected)
    {
        double ratio = frequencyInHertzActual / frequencyInHertzExpected;
        double oneCent = 1.0005946;
        expect (ratio < oneCent);
        expect (ratio > 1.0 / oneCent);
    }
};

static MPENoteTests MPENoteUnitTests;

#endif // JUCE_UNIT_TESTS

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   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.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    This helper class contains the necessary helper functions to generate
    MIDI messages that are exclusive to MPE, such as defining
    MPE zones and setting per-note and master pitchbend ranges.
    You can then send them to your MPE device using
    MidiOutput::sendBlockOfMessagesNow.

    All other MPE messages like per-note pitchbend, pressure, and third
    dimension, are ordinary MIDI messages that should be created using the MidiMessage
    class instead. You just need to take care to send them to the appropriate
    per-note MIDI channel.

    Note: if you are working with an MPEZoneLayout object inside your app,
    you should not use the message sequences provided here. Instead, you should
    change the zone layout programmatically with the member functions provided in the
    MPEZoneLayout class itself. You should also make sure that the Expressive
    MIDI zone layout of your C++ code and of the MPE device are kept in sync.

    @see MidiMessage, MPEZoneLayout, MPEZone
*/
class JUCE_API  MPEMessages
{
public:
    /** Returns the sequence of MIDI messages that, if sent to an Expressive
        MIDI device, will define a new MPE zone.
    */
    static MidiBuffer addZone (MPEZone zone);

    /** Returns the sequence of MIDI messages that, if sent to an Expressive
        MIDI device, will change the per-note pitchbend range of an
        existing MPE zone.
    */
    static MidiBuffer perNotePitchbendRange (MPEZone zone);

    /** Returns the sequence of MIDI messages that, if sent to an Expressive
        MIDI device, will change the master pitchbend range of an
        existing MPE zone.
    */
    static MidiBuffer masterPitchbendRange (MPEZone zone);

    /** Returns the sequence of MIDI messages that, if sent to an Expressive
        MIDI device, will erase all currently defined MPE zones.
    */
    static MidiBuffer clearAllZones();

    /** Returns the sequence of MIDI messages that, if sent to an Expressive
        MIDI device, will reset the whole MPE zone layout of the
        device to the laoyut passed in. This will first clear all currently
        defined MPE zones, then add all zones contained in the
        passed-in zone layout, and set their per-note and master pitchbend
        ranges to their current values.
    */
    static MidiBuffer setZoneLayout (const MPEZoneLayout& layout);

    /** The RPN number used for MPE zone layout messages.

        Note: This number can change in later versions of MPE.

        Pitchbend range messages (both per-note and master) are instead sent
        on RPN 0 as in standard MIDI 1.0.
    */
    static const int zoneLayoutMessagesRpnNumber = 6;
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   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.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    This class represents a single value for any of the MPE
    dimensions of control. It supports values with 7-bit or 14-bit resolutions
    (corresponding to 1 or 2 MIDI bytes, respectively). It also offers helper
    functions to query the value in a variety of representations that can be
    useful in an audio or MIDI context.
*/
class JUCE_API  MPEValue
{
public:
    //==============================================================================
    /** Default constructor. Constructs an MPEValue corresponding
        to the centre value.
    */
    MPEValue() noexcept;

    /** Constructs an MPEValue from an integer between 0 and 127
        (using 7-bit precision).
    */
    static MPEValue from7BitInt (int value) noexcept;

    /** Constructs an MPEValue from an integer between 0 and 16383
        (using 14-bit precision).
    */
    static MPEValue from14BitInt (int value) noexcept;

    /** Constructs an MPEValue corresponding to the centre value. */
    static MPEValue centreValue() noexcept;

    /** Constructs an MPEValue corresponding to the minimum value. */
    static MPEValue minValue() noexcept;

    /** Constructs an MPEValue corresponding to the maximum value. */
    static MPEValue maxValue() noexcept;

    /** Retrieves the current value as an integer between 0 and 127.
        Information will be lost if the value was initialised with a precision
        higher than 7-bit.
    */
    int as7BitInt() const noexcept;

    /** Retrieves the current value as an integer between 0 and 16383.
        Resolution will be lost if the value was initialised with a precision
        higher than 14-bit.
    */
    int as14BitInt() const noexcept;

    /** Retrieves the current value mapped to a float between -1.0f and 1.0f. */
    float asSignedFloat() const noexcept;

    /** Retrieves the current value mapped to a float between 0.0f and 1.0f. */
    float asUnsignedFloat() const noexcept;

    /** Returns true if two values are equal. */
    bool operator== (const MPEValue& other) const noexcept;

    /** Returns true if two values are not equal. */
    bool operator!= (const MPEValue& other) const noexcept;

private:
    //==============================================================================
    MPEValue (int normalisedValue);
    int normalisedValue;
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   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.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    This class represents the current MPE zone layout of a device
    capable of handling MPE.

    Use the MPEMessages helper class to convert the zone layout represented
    by this object to MIDI message sequences that you can send to an Expressive
    MIDI device to set its zone layout, add zones etc.

    @see MPEZone, MPEInstrument
*/
class JUCE_API  MPEZoneLayout
{
public:
    /** Default constructor.

        This will create a layout with no MPE zones.
        You can add an MPE zone using the method addZone.
    */
    MPEZoneLayout() noexcept;

    /** Copy constuctor.
        This will not copy the listeners registered to the MPEZoneLayout.
    */
    MPEZoneLayout (const MPEZoneLayout& other);

    /** Copy assignment operator.
        This will not copy the listeners registered to the MPEZoneLayout.
    */
    MPEZoneLayout& operator= (const MPEZoneLayout& other);

    /** Adds a new MPE zone to the layout.

        @param newZone  The zone to add.

        @return  true if the zone was added without modifying any other zones
                 added previously to the same zone layout object (if any);
                 false if any existing MPE zones had to be truncated
                 or deleted entirely in order to to add this new zone.
                 (Note: the zone itself will always be added with the channel bounds
                 that were specified; this will not fail.)
    */
    bool addZone (MPEZone newZone);

    /** Removes all currently present MPE zones. */
    void clearAllZones();

    /** Pass incoming MIDI messages to an object of this class if you want the
        zone layout to properly react to MPE RPN messages like an
        MPE device.
        MPEMessages::rpnNumber will add or remove zones; RPN 0 will
        set the per-note or master pitchbend ranges.

        Any other MIDI messages will be ignored by this class.

        @see MPEMessages
    */
    void processNextMidiEvent (const MidiMessage& message);

    /** Pass incoming MIDI buffers to an object of this class if you want the
        zone layout to properly react to MPE RPN messages like an
        MPE device.
        MPEMessages::rpnNumber will add or remove zones; RPN 0 will
        set the per-note or master pitchbend ranges.

        Any other MIDI messages will be ignored by this class.

        @see MPEMessages
     */
    void processNextMidiBuffer (const MidiBuffer& buffer);

    /** Returns the current number of MPE zones. */
    int getNumZones() const noexcept;

    /** Returns a pointer to the MPE zone at the given index, or nullptr if there
        is no such zone. Zones are sorted by insertion order (most recently added
        zone last).
    */
    MPEZone* getZoneByIndex (int index) const noexcept;

    /** Returns a pointer to the zone which uses the specified channel (1-16),
        or nullptr if there is no such zone.
    */
    MPEZone* getZoneByChannel (int midiChannel) const noexcept;

    /** Returns a pointer to the zone which has the specified channel (1-16)
        as its master channel, or nullptr if there is no such zone.
    */
    MPEZone* getZoneByMasterChannel (int midiChannel) const noexcept;

    /** Returns a pointer to the zone which has the specified channel (1-16)
        as its first note channel, or nullptr if there is no such zone.
    */
    MPEZone* getZoneByFirstNoteChannel (int midiChannel) const noexcept;

    /** Returns a pointer to the zone which has the specified channel (1-16)
        as one of its note channels, or nullptr if there is no such zone.
    */
    MPEZone* getZoneByNoteChannel (int midiChannel) const noexcept;

    //==============================================================================
    /** Listener class. Derive from this class to allow your class to be
        notified about changes to the zone layout.
    */
    class Listener
    {
    public:
        /** Destructor. */
        virtual ~Listener() {}

        /** Implement this callback to be notified about any changes to this
            MPEZoneLayout. Will be called whenever a zone is added, zones are
            removed, or any zone's master or note pitchbend ranges change.
        */
        virtual void zoneLayoutChanged (const MPEZoneLayout& layout) = 0;
    };

    //==============================================================================
    /** Adds a listener. */
    void addListener (Listener* const listenerToAdd) noexcept;

    /** Removes a listener. */
    void removeListener (Listener* const listenerToRemove) noexcept;

private:
    //==============================================================================
    Array<MPEZone> zones;
    MidiRPNDetector rpnDetector;
    ListenerList<Listener> listeners;

    void processRpnMessage (MidiRPNMessage);
    void processZoneLayoutRpnMessage (MidiRPNMessage);
    void processPitchbendRangeRpnMessage (MidiRPNMessage);
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   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.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

//==============================================================================
/**
    This struct represents a playing MPE note.

    A note is identified by a unique ID, or alternatively, by a MIDI channel
    and an initial note. It is characterised by five dimensions of continuous
    expressive control. Their current values are represented as
    MPEValue objects.

    @see MPEValue
*/
struct JUCE_API  MPENote
{
    //==============================================================================
    enum KeyState
    {
        off                  = 0,
        keyDown              = 1,
        sustained            = 2,
        keyDownAndSustained  = 3
    };

    //==============================================================================
    /** Constructor.

        @param midiChannel    The MIDI channel of the note, between 2 and 16.
                              (Channel 1 can never be a note channel in MPE).

        @param initialNote    The MIDI note number, between 0 and 127.

        @param velocity       The note-on velocity of the note.

        @param pitchbend      The initial per-note pitchbend of the note.

        @param pressure       The initial pressure of the note.

        @param timbre         The timbre value of the note.

        @param keyState       The key state of the note (whether the key is down
                              and/or the note is sustained). This value must not
                              be MPENote::off, since you are triggering a new note.
                              (If not specified, the default value will be MPENOte::keyDown.)
    */
    MPENote (int midiChannel,
             int initialNote,
             MPEValue velocity,
             MPEValue pitchbend,
             MPEValue pressure,
             MPEValue timbre,
             KeyState keyState = MPENote::keyDown) noexcept;

    /** Default constructor.

        Constructs an invalid MPE note (a note with the key state MPENote::off
        and an invalid MIDI channel. The only allowed use for such a note is to
        call isValid() on it; everything else is undefined behaviour.
    */
    MPENote() noexcept;

    /** Checks whether the MPE note is valid. */
    bool isValid() const noexcept;

    //==============================================================================
    // Invariants that define the note.

    /** A unique ID. Useful to distinguish the note from other simultaneously
        sounding notes that may use the same note number or MIDI channel.
        This should never change during the lifetime of a note object.
    */
    uint16 noteID;

    /** The MIDI channel which this note uses.
        This should never change during the lifetime of an MPENote object.
    */
    uint8 midiChannel;

    /** The MIDI note number that was sent when the note was triggered.
        This should never change during the lifetime of an MPENote object.
    */
    uint8 initialNote;

    //==============================================================================
    // The five dimensions of continuous expressive control

    /** The velocity ("strike") of the note-on.
        This dimension will stay constant after the note has been turned on.
    */
    MPEValue noteOnVelocity;

    /** Current per-note pitchbend of the note  (in units of MIDI pitchwheel
        position). This dimension can be modulated while the note sounds.

        Note: This value is not aware of the currently used pitchbend range,
        or an additional master pitchbend that may be simultaneously applied.
        To compute the actual effective pitchbend of an MPENote, you should
        probably use the member totalPitchbendInSemitones instead.

        @see totalPitchbendInSemitones, getFrequencyInHertz
    */
    MPEValue pitchbend;

    /** Current pressure with which the note is held down.
        This dimension can be modulated while the note sounds.
    */
    MPEValue pressure;

    /** Current value of the note's third expressive dimension, tyically
         encoding some kind of timbre parameter.
        This dimension can be modulated while the note sounds.
    */
    MPEValue timbre;

    /** The release velocity ("lift") of the note after a note-off has been
        received.
        This dimension will only have a meaningful value after a note-off has
        been received for the note (and keyState is set to MPENote::off or
        MPENOte::sustained). Initially, the value is undefined.
    */
    MPEValue noteOffVelocity;

    //==============================================================================
    /** Current effective pitchbend of the note in units of semitones, relative
        to initialNote. You should use this to compute the actual effective pitch
        of the note. This value is computed and set by an MPEInstrument to the
        sum of the per-note pitchbend value (stored in MPEValue::pitchbend)
        and the master pitchbend of the MPE zone, weighted with the per-note
        pitchbend range and master pitchbend range of the zone, respectively.

        @see getFrequencyInHertz
    */
    double totalPitchbendInSemitones;

    /** Current key state. Indicates whether the note key is currently down (pressed)
        and/or the note is sustained (by a sustain or sostenuto pedal).
    */
    KeyState keyState;

    //==============================================================================
    /** Returns the current frequency of the note in Hertz. This is the a sum of
        the initialNote and the totalPitchbendInSemitones, converted to Hertz.
    */
    double getFrequencyInHertz (double frequencyOfA = 440.0) const noexcept;

    /** Returns true if two notes are the same, determined by their unique ID. */
    bool operator== (const MPENote& other) const noexcept;

    /** Returns true if two notes are different notes, determined by their unique ID. */
    bool operator!= (const MPENote& other) const noexcept;
};

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   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.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

MPESynthesiser::MPESynthesiser()
{
}

MPESynthesiser::MPESynthesiser (MPEInstrument* mpeInstrument)  : MPESynthesiserBase (mpeInstrument)
{
}

MPESynthesiser::~MPESynthesiser()
{
}

//==============================================================================
void MPESynthesiser::startVoice (MPESynthesiserVoice* voice, MPENote noteToStart)
{
    jassert (voice != nullptr);
    voice->currentlyPlayingNote = noteToStart;
    voice->noteStarted();
}

void MPESynthesiser::stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff)
{
    jassert (voice != nullptr);
    voice->currentlyPlayingNote = noteToStop;
    voice->noteStopped (allowTailOff);
}

//==============================================================================
void MPESynthesiser::noteAdded (MPENote newNote)
{
    const ScopedLock sl (voicesLock);

    if (MPESynthesiserVoice* voice = findFreeVoice (newNote, shouldStealVoices))
        startVoice (voice, newNote);
}

void MPESynthesiser::notePressureChanged (MPENote changedNote)
{
    const ScopedLock sl (voicesLock);

    for (int i = 0; i < voices.size(); ++i)
    {
        MPESynthesiserVoice* voice = voices.getUnchecked (i);

        if (voice->isCurrentlyPlayingNote (changedNote))
        {
            voice->currentlyPlayingNote = changedNote;
            voice->notePressureChanged();
        }
    }
}

void MPESynthesiser::notePitchbendChanged (MPENote changedNote)
{
    const ScopedLock sl (voicesLock);

    for (int i = 0; i < voices.size(); ++i)
    {
        MPESynthesiserVoice* voice = voices.getUnchecked (i);

        if (voice->isCurrentlyPlayingNote (changedNote))
        {
            voice->currentlyPlayingNote = changedNote;
            voice->notePitchbendChanged();
        }
    }
}

void MPESynthesiser::noteTimbreChanged (MPENote changedNote)
{
    const ScopedLock sl (voicesLock);

    for (int i = 0; i < voices.size(); ++i)
    {
        MPESynthesiserVoice* voice = voices.getUnchecked (i);

        if (voice->isCurrentlyPlayingNote (changedNote))
        {
            voice->currentlyPlayingNote = changedNote;
            voice->noteTimbreChanged();
        }
    }
}

void MPESynthesiser::noteKeyStateChanged (MPENote changedNote)
{
    const ScopedLock sl (voicesLock);

    for (int i = 0; i < voices.size(); ++i)
    {
        MPESynthesiserVoice* voice = voices.getUnchecked (i);

        if (voice->isCurrentlyPlayingNote (changedNote))
        {
            voice->currentlyPlayingNote = changedNote;
            voice->noteKeyStateChanged();
        }
    }
}

void MPESynthesiser::noteReleased (MPENote finishedNote)
{
    const ScopedLock sl (voicesLock);

    for (int i = voices.size(); --i >= 0;)
    {
        MPESynthesiserVoice* const voice = voices.getUnchecked (i);

        if (voice->isCurrentlyPlayingNote(finishedNote))
            stopVoice (voice, finishedNote, true);
    }
}

void MPESynthesiser::setCurrentPlaybackSampleRate (const double newRate)
{
    MPESynthesiserBase::setCurrentPlaybackSampleRate (newRate);

    const ScopedLock sl (voicesLock);

    turnOffAllVoices (false);

    for (int i = voices.size(); --i >= 0;)
        voices.getUnchecked (i)->setCurrentSampleRate (newRate);
}

void MPESynthesiser::handleMidiEvent (const MidiMessage& m)
{
    if (m.isController())
        handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue());
    else if (m.isProgramChange())
        handleProgramChange (m.getChannel(), m.getProgramChangeNumber());

    MPESynthesiserBase::handleMidiEvent (m);
}

MPESynthesiserVoice* MPESynthesiser::findFreeVoice (MPENote noteToFindVoiceFor, bool stealIfNoneAvailable) const
{
    const ScopedLock sl (voicesLock);

    for (int i = 0; i < voices.size(); ++i)
    {
        MPESynthesiserVoice* const voice = voices.getUnchecked (i);

        if (! voice->isActive())
            return voice;
    }

    if (stealIfNoneAvailable)
        return findVoiceToSteal (noteToFindVoiceFor);

    return nullptr;
}

struct MPEVoiceAgeSorter
{
    static int compareElements (MPESynthesiserVoice* v1, MPESynthesiserVoice* v2) noexcept
    {
        return v1->wasStartedBefore (*v2) ? -1 : (v2->wasStartedBefore (*v1) ? 1 : 0);
    }
};

MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceFor) const
{
    // This voice-stealing algorithm applies the following heuristics:
    // - Re-use the oldest notes first
    // - Protect the lowest & topmost notes, even if sustained, but not if they've been released.

    // apparently you are trying to render audio without having any voices...
    jassert (voices.size() > 0);

    // These are the voices we want to protect (ie: only steal if unavoidable)
    MPESynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase
    MPESynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase

    // this is a list of voices we can steal, sorted by how long they've been running
    Array<MPESynthesiserVoice*> usableVoices;
    usableVoices.ensureStorageAllocated (voices.size());

    for (int i = 0; i < voices.size(); ++i)
    {
        MPESynthesiserVoice* const voice = voices.getUnchecked (i);
        jassert (voice->isActive()); // We wouldn't be here otherwise

        MPEVoiceAgeSorter sorter;
        usableVoices.addSorted (sorter, voice);

        if (! voice->isPlayingButReleased()) // Don't protect released notes
        {
            const int noteNumber = voice->getCurrentlyPlayingNote().initialNote;

            if (low == nullptr || noteNumber < low->getCurrentlyPlayingNote().initialNote)
                low = voice;

            if (top == nullptr || noteNumber > top->getCurrentlyPlayingNote().initialNote)
                top = voice;
        }
    }

    // Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s)
    if (top == low)
        top = nullptr;

    const int numUsableVoices = usableVoices.size();

    // If we want to re-use the voice to trigger a new note,
    // then The oldest note that's playing the same note number is ideal.
    if (noteToStealVoiceFor.isValid())
    {
        for (int i = 0; i < numUsableVoices; ++i)
        {
            MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i);

            if (voice->getCurrentlyPlayingNote().initialNote == noteToStealVoiceFor.initialNote)
                return voice;
        }
    }

    // Oldest voice that has been released (no finger on it and not held by sustain pedal)
    for (int i = 0; i < numUsableVoices; ++i)
    {
        MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i);

        if (voice != low && voice != top && voice->isPlayingButReleased())
            return voice;
    }

    // Oldest voice that doesn't have a finger on it:
    for (int i = 0; i < numUsableVoices; ++i)
    {
        MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i);

        if (voice != low && voice != top
             && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDown
             && voice->getCurrentlyPlayingNote().keyState != MPENote::keyDownAndSustained)
            return voice;
    }

    // Oldest voice that isn't protected
    for (int i = 0; i < numUsableVoices; ++i)
    {
        MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i);

        if (voice != low && voice != top)
            return voice;
    }

    // We've only got "protected" voices now: lowest note takes priority
    jassert (low != nullptr);

    // Duophonic synth: give priority to the bass note:
    if (top != nullptr)
        return top;

    return low;
}

//==============================================================================
void MPESynthesiser::addVoice (MPESynthesiserVoice* const newVoice)
{
    const ScopedLock sl (voicesLock);
    newVoice->setCurrentSampleRate (getSampleRate());
    voices.add (newVoice);
}

void MPESynthesiser::clearVoices()
{
    const ScopedLock sl (voicesLock);
    voices.clear();
}

MPESynthesiserVoice* MPESynthesiser::getVoice (const int index) const
{
    const ScopedLock sl (voicesLock);
    return voices [index];
}

void MPESynthesiser::removeVoice (const int index)
{
    const ScopedLock sl (voicesLock);
    voices.remove (index);
}

void MPESynthesiser::reduceNumVoices (const int newNumVoices)
{
    // we can't possibly get to a negative number of voices...
    jassert (newNumVoices >= 0);

    const ScopedLock sl (voicesLock);

    while (voices.size() > newNumVoices)
    {
        if (MPESynthesiserVoice* voice = findFreeVoice (MPENote(), true))
            voices.removeObject (voice);
        else
            voices.remove (0); // if there's no voice to steal, kill the oldest voice
    }
}

void MPESynthesiser::turnOffAllVoices (bool allowTailOff)
{
    // first turn off all voices (it's more efficient to do this immediately
    // rather than to go through the MPEInstrument for this).
    for (int i = voices.size(); --i >= 0;)
        voices.getUnchecked (i)->noteStopped (allowTailOff);

    // finally make sure the MPE Instrument also doesn't have any notes anymore.
    instrument->releaseAllNotes();
}

//==============================================================================
void MPESynthesiser::renderNextSubBlock (AudioBuffer<float>& buffer, int startSample, int numSamples)
{
    for (int i = voices.size(); --i >= 0;)
    {
        MPESynthesiserVoice* voice = voices.getUnchecked (i);

        if (voice->isActive())
            voice->renderNextBlock (buffer, startSample, numSamples);
    }
}

void MPESynthesiser::renderNextSubBlock (AudioBuffer<double>& buffer, int startSample, int numSamples)
{
    for (int i = voices.size(); --i >= 0;)
    {
        MPESynthesiserVoice* voice = voices.getUnchecked (i);

        if (voice->isActive())
            voice->renderNextBlock (buffer, startSample, numSamples);
    }
}

} // namespace juce


text:
/*
  ==============================================================================

   This file is part of the JUCE library.
   Copyright (c) 2017 - ROLI Ltd.

   JUCE is an open source library subject to commercial or open-source
   licensing.

   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.

   JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
   EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
   DISCLAIMED.

  ==============================================================================
*/

namespace juce
{

namespace
{
    void checkAndLimitZoneParameters (int minValue,
                                      int maxValue,
                                      int& valueToCheckAndLimit) noexcept
    {
        if (valueToCheckAndLimit < minValue || valueToCheckAndLimit > maxValue)
        {
            // if you hit this, one of the parameters you supplied for MPEZone
            // was not within the allowed range!
            // we fit this back into the allowed range here to maintain a valid
            // state for the zone, but probably the resulting zone is not what you
            //wanted it to be!
            jassertfalse;

            valueToCheckAndLimit = jlimit (minValue, maxValue, valueToCheckAndLimit);
        }
    }
}

//==============================================================================
MPEZone::MPEZone (int masterChannel_,
                  int numNoteChannels_,
                  int perNotePitchbendRange_,
                  int masterPitchbendRange_) noexcept
    : masterChannel (masterChannel_),
      numNoteChannels (numNoteChannels_),
      perNotePitchbendRange (perNotePitchbendRange_),
      masterPitchbendRange (masterPitchbendRange_)
{
    checkAndLimitZoneParameters (1, 15,                  masterChannel);
    checkAndLimitZoneParameters (1, 16 - masterChannel,  numNoteChannels);
    checkAndLimitZoneParameters (0, 96,                  perNotePitchbendRange);
    checkAndLimitZoneParameters (0, 96,                  masterPitchbendRange);
}

//==============================================================================
int MPEZone::getMasterChannel() const noexcept
{
    return masterChannel;
}

int MPEZone::getNumNoteChannels() const noexcept
{
    return numNoteChannels;
}

int MPEZone::getFirstNoteChannel() const noexcept
{
    return masterChannel + 1;
}

int MPEZone::getLastNoteChannel() const noexcept
{
    return masterChannel + numNoteChannels;
}

Range<int> MPEZone::getNoteChannelRange() const noexcept
{
    return Range<int>::withStartAndLength (getFirstNoteChannel(), getNumNoteChannels());
}

bool MPEZone::isUsingChannel (int channel) const noexcept
{
    jassert (channel > 0 && channel <= 16);
    return channel >= masterChannel && channel <= masterChannel + numNoteChannels;
}

bool MPEZone::isUsingChannelAsNoteChannel (int channel) const noexcept
{
    jassert (channel > 0 && channel <= 16);
    return channel > masterChannel && channel <= masterChannel + numNoteChannels;
}

int MPEZone::getPerNotePitchbendRange() const noexcept
{
    return perNotePitchbendRange;
}

int MPEZone::getMasterPitchbendRange() const noexcept
{
    return masterPitchbendRange;
}

void MPEZone::setPerNotePitchbendRange (int rangeInSemitones) noexcept
{
    checkAndLimitZoneParameters (0, 96, rangeInSemitones);
    perNotePitchbendRange = rangeInSemitones;
}

void MPEZone::setMasterPitchbendRange (int rangeInSemitones) noexcept
{
    checkAndLimitZoneParameters (0, 96, rangeInSemitones);
    masterPitchbendRange = rangeInSemitones;
}

//==============================================================================
bool MPEZone::overlapsWith (MPEZone other) const noexcept
{
    if (masterChannel == other.masterChannel)
        return true;

    if (masterChannel > other.masterChannel)
        return other.overlapsWith (*this);

    return masterChannel + numNoteChannels >= other.masterChannel;
}

//==============================================================================
bool MPEZone::truncateToFit (MPEZone other) noexcept
{
    const int masterChannelDiff = other.masterChannel - masterChannel;

    // we need at least 2 channels to be left after truncation:
    // 1 master channel and 1 note channel. otherwise we can't truncate.
    if (masterChannelDiff < 2)
        return false;

    numNoteChannels = jmin (numNoteChannels, masterChannelDiff - 1);
    return true;
}

//==============================================================================
bool MPEZone::operator== (const MPEZone& other) const noexcept
{
    return masterChannel         == other.masterChannel
        && numNoteChannels       == other.numNoteChannels
        && perNotePitchbendRange == other.perNotePitchbendRange
        && masterPitchbendRange  == other.masterPitchbendRange;
}

bool MPEZone::operator!= (const MPEZone& other) const noexcept
{
    return ! operator== (other);
}

//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS

class MPEZoneTests   : public UnitTest
{
public:
    MPEZoneTests()  : UnitTest ("MPEZone class", "MIDI/MPE") {}

    void runTest() override
    {
        beginTest ("initialisation");
        {
            {
                MPEZone zone (1, 10);

                expectEquals (zone.getMasterChannel(), 1);
                expectEquals (zone.getNumNoteChannels(), 10);
                expectEquals (zone.getFirstNoteChannel(), 2);
                expectEquals (zone.getLastNoteChannel(), 11);
                expectEquals (zone.getPerNotePitchbendRange(), 48);
                expectEquals (zone.getMasterPitchbendRange(), 2);

                expect (zone.isUsingChannel (1));
                expect (zone.isUsingChannel (2));
                expect (zone.isUsingChannel (10));
                expect (zone.isUsingChannel (11));
                expect (! zone.isUsingChannel (12));
                expect (! zone.isUsingChannel (16));

                expect (! zone.isUsingChannelAsNoteChannel (1));
                expect (zone.isUsingChannelAsNoteChannel (2));
                expect (zone.isUsingChannelAsNoteChannel (10));
                expect (zone.isUsingChannelAsNoteChannel (11));
                expect (! zone.isUsingChannelAsNoteChannel (12));
                expect (! zone.isUsingChannelAsNoteChannel (16));
            }
            {
                MPEZone zone (5, 4);

                expectEquals (zone.getMasterChannel(), 5);
                expectEquals (zone.getNumNoteChannels(), 4);
                expectEquals (zone.getFirstNoteChannel(), 6);
                expectEquals (zone.getLastNoteChannel(), 9);
                expectEquals (zone.getPerNotePitchbendRange(), 48);
                expectEquals (zone.getMasterPitchbendRange(), 2);

                expect (! zone.isUsingChannel (1));
                expect (! zone.isUsingChannel (4));
                expect (zone.isUsingChannel (5));
                expect (zone.isUsingChannel (6));
                expect (zone.isUsingChannel (8));
                expect (zone.isUsingChannel (9));
                expect (! zone.isUsingChannel (10));
                expect (! zone.isUsingChannel (16));

                expect (! zone.isUsingChannelAsNoteChannel (5));
                expect (zone.isUsingChannelAsNoteChannel (6));
                expect (zone.isUsingChannelAsNoteChannel (8));
                expect (zone.isUsingChannelAsNoteChannel (9));
                expect (! zone.isUsingChannelAsNoteChannel (10));
            }

        }

        beginTest ("getNoteChannelRange");
        {
            MPEZone zone (2, 10);

            Range<int> noteChannelRange = zone.getNoteChannelRange();
            expectEquals (noteChannelRange.getStart(), 3);
            expectEquals (noteChannelRange.getEnd(), 13);
        }

        beginTest ("setting master pitchbend range");
        {
            MPEZone zone (1, 10);

            zone.setMasterPitchbendRange (96);
            expectEquals (zone.getMasterPitchbendRange(), 96);
            zone.setMasterPitchbendRange (0);
            expectEquals (zone.getMasterPitchbendRange(), 0);

            expectEquals (zone.getPerNotePitchbendRange(), 48);
        }

        beginTest ("setting per-note pitchbend range");
        {
            MPEZone zone (1, 10);

            zone.setPerNotePitchbendRange (96);
            expectEquals (zone.getPerNotePitchbendRange(), 96);
            zone.setPerNotePitchbendRange (0);
            expectEquals (zone.getPerNotePitchbendRange(), 0);

            expectEquals (zone.getMasterPitchbendRange(), 2);
        }

        beginTest ("checking overlap");
        {
            testOverlapsWith (1, 10, 1, 10, true);
            testOverlapsWith (1, 4,  6, 3,  false);
            testOverlapsWith (1, 4,  8, 3,  false);
            testOverlapsWith (2, 10, 2, 8,  true);
            testOverlapsWith (1, 10, 3, 2,  true);
            testOverlapsWith (3, 10, 5, 9,  true);
        }

        beginTest ("truncating");
        {
            testTruncateToFit (1, 10, 3, 10, true,  1, 1);
            testTruncateToFit (3, 10, 1, 10, false, 3, 10);
            testTruncateToFit (1, 10, 5, 8,  true,  1, 3);
            testTruncateToFit (5, 8,  1, 10, false, 5, 8);
            testTruncateToFit (1, 10, 4, 3,  true,  1, 2);
            testTruncateToFit (4, 3,  1, 10, false, 4, 3);
            testTruncateToFit (1, 3,  5, 3,  true,  1, 3);
            testTruncateToFit (5, 3,  1, 3,  false, 5, 3);
            testTruncateToFit (1, 3,  7, 3,  true,  1, 3);
            testTruncateToFit (7, 3,  1, 3,  false, 7, 3);
            testTruncateToFit (1, 10, 2, 10, false, 1, 10);
            testTruncateToFit (2, 10, 1, 10, false, 2, 10);
        }
    }

private:
    //==============================================================================
    void testOverlapsWith (int masterChannelFirst, int numNoteChannelsFirst,
                           int masterChannelSecond, int numNoteChannelsSecond,
                           bool expectedRetVal)
    {
        MPEZone first (masterChannelFirst, numNoteChannelsFirst);
        MPEZone second (masterChannelSecond, numNoteChannelsSecond);

        expect (first.overlapsWith (second) == expectedRetVal);
        expect (second.overlapsWith (first) == expectedRetVal);
    }

    //==============================================================================
    void testTruncateToFit (int masterChannelFirst, int numNoteChannelsFirst,
                            int masterChannelSecond, int numNoteChannelsSecond,
                            bool expectedRetVal,
                            int masterChannelFirstAfter, int numNoteChannelsFirstAfter)
    {
        MPEZone first (masterChannelFirst, numNoteChannelsFirst);
        MPEZone second (masterChannelSecond, numNoteChannelsSecond);

        expect (first.truncateToFit (second) == expectedRetVal);
        expectEquals (first.getMasterChannel(), masterChannelFirstAfter);
        expectEquals (first.getNumNoteChannels(), numNoteChannelsFirstAfter);
    }
};

static MPEZoneTests MPEZoneUnitTests;

#endif // JUCE_UNIT_TESTS

} // namespace juce