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
{

MidiBuffer MPEMessages::addZone (MPEZone zone)
{
    MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(),
                                                   zoneLayoutMessagesRpnNumber,
                                                   zone.getNumNoteChannels(),
                                                   false, false));

    buffer.addEvents (perNotePitchbendRange (zone), 0, -1, 0);
    buffer.addEvents (masterPitchbendRange (zone), 0, -1, 0);

    return buffer;
}

MidiBuffer MPEMessages::perNotePitchbendRange (MPEZone zone)
{
    return MidiRPNGenerator::generate (zone.getFirstNoteChannel(), 0,
                                       zone.getPerNotePitchbendRange(),
                                       false, false);
}

MidiBuffer MPEMessages::masterPitchbendRange (MPEZone zone)
{
    return MidiRPNGenerator::generate (zone.getMasterChannel(), 0,
                                       zone.getMasterPitchbendRange(),
                                       false, false);
}

MidiBuffer MPEMessages::clearAllZones()
{
    return MidiRPNGenerator::generate (1, zoneLayoutMessagesRpnNumber, 16, false, false);
}

MidiBuffer MPEMessages::setZoneLayout (const MPEZoneLayout& layout)
{
    MidiBuffer buffer;

    buffer.addEvents (clearAllZones(), 0, -1, 0);

    for (int i = 0; i < layout.getNumZones(); ++i)
        buffer.addEvents (addZone (*layout.getZoneByIndex (i)), 0, -1, 0);

    return buffer;
}

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

class MPEMessagesTests  : public UnitTest
{
public:
    MPEMessagesTests() : UnitTest ("MPEMessages class", "MIDI/MPE") {}

    void runTest() override
    {
        beginTest ("add zone");
        {
            {
                MidiBuffer buffer = MPEMessages::addZone (MPEZone (1, 7));

                const uint8 expectedBytes[] =
                {
                    0xb1, 0x64, 0x06, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x07, // set up zone
                    0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x30, // per-note pbrange (default = 48)
                    0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x02  // master pbrange (default = 2)
                };

                testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
            }
            {
                MidiBuffer buffer = MPEMessages::addZone (MPEZone (11, 5, 96, 0));

                const uint8 expectedBytes[] =
                {
                    0xbb, 0x64, 0x06, 0xbb, 0x65, 0x00, 0xbb, 0x06, 0x05, // set up zone
                    0xbb, 0x64, 0x00, 0xbb, 0x65, 0x00, 0xbb, 0x06, 0x60, // per-note pbrange (custom)
                    0xba, 0x64, 0x00, 0xba, 0x65, 0x00, 0xba, 0x06, 0x00  // master pbrange (custom)
                };

                testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
            }
        }

        beginTest ("set per-note pitchbend range");
        {
            MPEZone zone (3, 7, 96);
            MidiBuffer buffer = MPEMessages::perNotePitchbendRange (zone);

            const uint8 expectedBytes[] = { 0xb3, 0x64, 0x00, 0xb3, 0x65, 0x00, 0xb3, 0x06, 0x60 };

            testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
        }

        beginTest ("set master pitchbend range");
        {
            MPEZone zone (3, 7, 48, 60);
            MidiBuffer buffer = MPEMessages::masterPitchbendRange (zone);

            const uint8 expectedBytes[] = { 0xb2, 0x64, 0x00, 0xb2, 0x65, 0x00, 0xb2, 0x06, 0x3c };

            testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
        }

        beginTest ("clear all zones");
        {
            MidiBuffer buffer = MPEMessages::clearAllZones();

            const uint8 expectedBytes[] = { 0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x10 };

            testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
        }

        beginTest ("set complete state");
        {
            MPEZoneLayout layout;
            layout.addZone (MPEZone (1, 7, 96, 0));
            layout.addZone (MPEZone (9, 7));
            layout.addZone (MPEZone (5, 3));
            layout.addZone (MPEZone (5, 4));
            layout.addZone (MPEZone (6, 4));

            MidiBuffer buffer = MPEMessages::setZoneLayout (layout);

            const uint8 expectedBytes[] = {
                0xb0, 0x64, 0x06, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x10,  // clear all zones
                0xb1, 0x64, 0x06, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x03,  // set zone 1 (1, 3)
                0xb1, 0x64, 0x00, 0xb1, 0x65, 0x00, 0xb1, 0x06, 0x60,  // per-note pbrange (custom)
                0xb0, 0x64, 0x00, 0xb0, 0x65, 0x00, 0xb0, 0x06, 0x00,  // master pbrange (custom)
                0xb6, 0x64, 0x06, 0xb6, 0x65, 0x00, 0xb6, 0x06, 0x04,  // set zone 2 (6, 4)
                0xb6, 0x64, 0x00, 0xb6, 0x65, 0x00, 0xb6, 0x06, 0x30,  // per-note pbrange (default = 48)
                0xb5, 0x64, 0x00, 0xb5, 0x65, 0x00, 0xb5, 0x06, 0x02   // master pbrange (default = 2)
            };

            testMidiBuffer (buffer, expectedBytes, sizeof (expectedBytes));
        }
    }

private:
    //==============================================================================
    void testMidiBuffer (MidiBuffer& buffer, const uint8* expectedBytes, int expectedBytesSize)
    {
        uint8 actualBytes[128] = { 0 };
        extractRawBinaryData (buffer, actualBytes, sizeof (actualBytes));

        expectEquals (std::memcmp (actualBytes, expectedBytes, (std::size_t) expectedBytesSize), 0);
    }

    //==============================================================================
    void extractRawBinaryData (const MidiBuffer& midiBuffer, const uint8* bufferToCopyTo, std::size_t maxBytes)
    {
        std::size_t pos = 0;
        MidiBuffer::Iterator iter (midiBuffer);
        MidiMessage midiMessage;
        int samplePosition; // Note: not actually used, so no need to initialise.

        while (iter.getNextEvent (midiMessage, samplePosition))
        {
            const uint8* data = midiMessage.getRawData();
            std::size_t dataSize = (std::size_t) midiMessage.getRawDataSize();

            if (pos + dataSize > maxBytes)
                return;

            std::memcpy ((void*) (bufferToCopyTo + pos), data, dataSize);
            pos += dataSize;
        }
    }
};

static MPEMessagesTests MPEMessagesUnitTests;

#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
{

MPEZoneLayout::MPEZoneLayout() noexcept
{
}

MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other)
    : zones (other.zones)
{
}

MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other)
{
    zones = other.zones;
    listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this);
    return *this;
}

//==============================================================================
bool MPEZoneLayout::addZone (MPEZone newZone)
{
    bool noOtherZonesModified = true;

    for (int i = zones.size(); --i >= 0;)
    {
        MPEZone& zone = zones.getReference (i);

        if (zone.overlapsWith (newZone))
        {
            if (! zone.truncateToFit (newZone))
                zones.removeRange (i, 1);
                // can't use zones.remove (i) because that requires a default c'tor :-(

            noOtherZonesModified = false;
        }
    }

    zones.add (newZone);
    listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this);
    return noOtherZonesModified;
}

//==============================================================================
int MPEZoneLayout::getNumZones() const noexcept
{
    return zones.size();
}

MPEZone* MPEZoneLayout::getZoneByIndex (int index) const noexcept
{
    if (zones.size() < index)
        return nullptr;

    return &(zones.getReference (index));
}

void MPEZoneLayout::clearAllZones()
{
    zones.clear();
    listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this);
}

//==============================================================================
void MPEZoneLayout::processNextMidiEvent (const MidiMessage& message)
{
    if (! message.isController())
        return;

    MidiRPNMessage rpn;

    if (rpnDetector.parseControllerMessage (message.getChannel(),
                                            message.getControllerNumber(),
                                            message.getControllerValue(),
                                            rpn))
    {
        processRpnMessage (rpn);
    }
}

void MPEZoneLayout::processRpnMessage (MidiRPNMessage rpn)
{
    if (rpn.parameterNumber == MPEMessages::zoneLayoutMessagesRpnNumber)
        processZoneLayoutRpnMessage (rpn);
    else if (rpn.parameterNumber == 0)
        processPitchbendRangeRpnMessage (rpn);
}

void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn)
{
    if (rpn.value < 16)
        addZone (MPEZone (rpn.channel - 1, rpn.value));
    else
        clearAllZones();
}

//==============================================================================
void MPEZoneLayout::processPitchbendRangeRpnMessage (MidiRPNMessage rpn)
{
    if (MPEZone* zone = getZoneByFirstNoteChannel (rpn.channel))
    {
        if (zone->getPerNotePitchbendRange() != rpn.value)
        {
            zone->setPerNotePitchbendRange (rpn.value);
            listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this);
            return;
        }
    }

    if (MPEZone* zone = getZoneByMasterChannel (rpn.channel))
    {
        if (zone->getMasterPitchbendRange() != rpn.value)
        {
            zone->setMasterPitchbendRange (rpn.value);
            listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this);
            return;
        }
    }
}

//==============================================================================
void MPEZoneLayout::processNextMidiBuffer (const MidiBuffer& buffer)
{
    MidiBuffer::Iterator iter (buffer);
    MidiMessage message;
    int samplePosition; // not actually used, so no need to initialise.

    while (iter.getNextEvent (message, samplePosition))
        processNextMidiEvent (message);
}

//==============================================================================
MPEZone* MPEZoneLayout::getZoneByChannel (int channel) const noexcept
{
    for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone)
        if (zone->isUsingChannel (channel))
            return zone;

    return nullptr;
}

MPEZone* MPEZoneLayout::getZoneByMasterChannel (int channel) const noexcept
{
    for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone)
        if (zone->getMasterChannel() == channel)
            return zone;

    return nullptr;
}

MPEZone* MPEZoneLayout::getZoneByFirstNoteChannel (int channel) const noexcept
{
    for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone)
        if (zone->getFirstNoteChannel() == channel)
            return zone;

    return nullptr;
}

MPEZone* MPEZoneLayout::getZoneByNoteChannel (int channel) const noexcept
{
    for (MPEZone* zone = zones.begin(); zone != zones.end(); ++zone)
        if (zone->isUsingChannelAsNoteChannel (channel))
            return zone;

    return nullptr;
}

//==============================================================================
void MPEZoneLayout::addListener (Listener* const listenerToAdd) noexcept
{
    listeners.add (listenerToAdd);
}

void MPEZoneLayout::removeListener (Listener* const listenerToRemove) noexcept
{
    listeners.remove (listenerToRemove);
}

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

class MPEZoneLayoutTests  : public UnitTest
{
public:
    MPEZoneLayoutTests() : UnitTest ("MPEZoneLayout class", "MIDI/MPE") {}

    void runTest() override
    {
        beginTest ("initialisation");
        {
            MPEZoneLayout layout;
            expectEquals (layout.getNumZones(), 0);
        }

        beginTest ("adding zones");
        {
            MPEZoneLayout layout;

            expect (layout.addZone (MPEZone (1, 7)));

            expectEquals (layout.getNumZones(), 1);
            expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1);
            expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7);

            expect (layout.addZone (MPEZone (9, 7)));

            expectEquals (layout.getNumZones(), 2);
            expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1);
            expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7);
            expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9);
            expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7);

            expect (! layout.addZone (MPEZone (5, 3)));

            expectEquals (layout.getNumZones(), 3);
            expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1);
            expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3);
            expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9);
            expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7);
            expectEquals (layout.getZoneByIndex (2)->getMasterChannel(), 5);
            expectEquals (layout.getZoneByIndex (2)->getNumNoteChannels(), 3);

            expect (! layout.addZone (MPEZone (5, 4)));

            expectEquals (layout.getNumZones(), 2);
            expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1);
            expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3);
            expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 5);
            expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 4);

            expect (! layout.addZone (MPEZone (6, 4)));

            expectEquals (layout.getNumZones(), 2);
            expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1);
            expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3);
            expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 6);
            expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 4);
        }

        beginTest ("querying zones");
        {
            MPEZoneLayout layout;

            layout.addZone (MPEZone (2, 5));
            layout.addZone (MPEZone (9, 4));

            expect (layout.getZoneByMasterChannel (1)  == nullptr);
            expect (layout.getZoneByMasterChannel (2)  != nullptr);
            expect (layout.getZoneByMasterChannel (3)  == nullptr);
            expect (layout.getZoneByMasterChannel (8)  == nullptr);
            expect (layout.getZoneByMasterChannel (9)  != nullptr);
            expect (layout.getZoneByMasterChannel (10) == nullptr);

            expectEquals (layout.getZoneByMasterChannel (2)->getNumNoteChannels(), 5);
            expectEquals (layout.getZoneByMasterChannel (9)->getNumNoteChannels(), 4);

            expect (layout.getZoneByFirstNoteChannel (2)  == nullptr);
            expect (layout.getZoneByFirstNoteChannel (3)  != nullptr);
            expect (layout.getZoneByFirstNoteChannel (4)  == nullptr);
            expect (layout.getZoneByFirstNoteChannel (9)  == nullptr);
            expect (layout.getZoneByFirstNoteChannel (10) != nullptr);
            expect (layout.getZoneByFirstNoteChannel (11) == nullptr);

            expectEquals (layout.getZoneByFirstNoteChannel (3)->getNumNoteChannels(), 5);
            expectEquals (layout.getZoneByFirstNoteChannel (10)->getNumNoteChannels(), 4);

            expect (layout.getZoneByNoteChannel (2)  == nullptr);
            expect (layout.getZoneByNoteChannel (3)  != nullptr);
            expect (layout.getZoneByNoteChannel (4)  != nullptr);
            expect (layout.getZoneByNoteChannel (6)  != nullptr);
            expect (layout.getZoneByNoteChannel (7)  != nullptr);
            expect (layout.getZoneByNoteChannel (8)  == nullptr);
            expect (layout.getZoneByNoteChannel (9)  == nullptr);
            expect (layout.getZoneByNoteChannel (10) != nullptr);
            expect (layout.getZoneByNoteChannel (11) != nullptr);
            expect (layout.getZoneByNoteChannel (12) != nullptr);
            expect (layout.getZoneByNoteChannel (13) != nullptr);
            expect (layout.getZoneByNoteChannel (14) == nullptr);

            expectEquals (layout.getZoneByNoteChannel (5)->getNumNoteChannels(), 5);
            expectEquals (layout.getZoneByNoteChannel (13)->getNumNoteChannels(), 4);
        }

        beginTest ("clear all zones");
        {
            MPEZoneLayout layout;

            expect (layout.addZone (MPEZone (1, 7)));
            expect (layout.addZone (MPEZone (10, 2)));
            layout.clearAllZones();

            expectEquals (layout.getNumZones(), 0);
        }

        beginTest ("process MIDI buffers");
        {
            MPEZoneLayout layout;
            MidiBuffer buffer;

            buffer = MPEMessages::addZone (MPEZone (1, 7));
            layout.processNextMidiBuffer (buffer);

            expectEquals (layout.getNumZones(), 1);
            expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1);
            expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7);

            buffer = MPEMessages::addZone (MPEZone (9, 7));
            layout.processNextMidiBuffer (buffer);

            expectEquals (layout.getNumZones(), 2);
            expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1);
            expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 7);
            expectEquals (layout.getZoneByIndex (1)->getMasterChannel(), 9);
            expectEquals (layout.getZoneByIndex (1)->getNumNoteChannels(), 7);

            MPEZone zone (1, 10);

            buffer = MPEMessages::addZone (zone);
            layout.processNextMidiBuffer (buffer);

            expectEquals (layout.getNumZones(), 1);
            expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1);
            expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 10);

            zone.setPerNotePitchbendRange (33);
            zone.setMasterPitchbendRange (44);

            buffer = MPEMessages::masterPitchbendRange (zone);
            buffer.addEvents (MPEMessages::perNotePitchbendRange (zone), 0, -1, 0);

            layout.processNextMidiBuffer (buffer);

            expectEquals (layout.getZoneByIndex (0)->getPerNotePitchbendRange(), 33);
            expectEquals (layout.getZoneByIndex (0)->getMasterPitchbendRange(), 44);
        }

        beginTest ("process individual MIDI messages");
        {
            MPEZoneLayout layout;

            layout.processNextMidiEvent (MidiMessage (0x80, 0x59, 0xd0));  // unrelated note-off msg
            layout.processNextMidiEvent (MidiMessage (0xb1, 0x64, 0x06));  // RPN part 1
            layout.processNextMidiEvent (MidiMessage (0xb1, 0x65, 0x00));  // RPN part 2
            layout.processNextMidiEvent (MidiMessage (0xb8, 0x0b, 0x66));  // unrelated CC msg
            layout.processNextMidiEvent (MidiMessage (0xb1, 0x06, 0x03));  // RPN part 3
            layout.processNextMidiEvent (MidiMessage (0x90, 0x60, 0x00));  // unrelated note-on msg

            expectEquals (layout.getNumZones(), 1);
            expectEquals (layout.getZoneByIndex (0)->getMasterChannel(), 1);
            expectEquals (layout.getZoneByIndex (0)->getNumNoteChannels(), 3);
        }
    }
};

static MPEZoneLayoutTests MPEZoneLayoutUnitTests;

#endif // JUCE_UNIT_TESTS

} // 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
{

MPESynthesiserBase::MPESynthesiserBase()
    : instrument (new MPEInstrument),
      sampleRate (0),
      minimumSubBlockSize (32),
      subBlockSubdivisionIsStrict (false)
{
    instrument->addListener (this);
}

MPESynthesiserBase::MPESynthesiserBase (MPEInstrument* inst)
    : instrument (inst),
      sampleRate (0),
      minimumSubBlockSize (32)
{
    jassert (instrument != nullptr);
    instrument->addListener (this);
}

//==============================================================================
MPEZoneLayout MPESynthesiserBase::getZoneLayout() const noexcept
{
    return instrument->getZoneLayout();
}

void MPESynthesiserBase::setZoneLayout (MPEZoneLayout newLayout)
{
    instrument->setZoneLayout (newLayout);
}

//==============================================================================
void MPESynthesiserBase::enableLegacyMode (int pitchbendRange, Range<int> channelRange)
{
    instrument->enableLegacyMode (pitchbendRange, channelRange);
}

bool MPESynthesiserBase::isLegacyModeEnabled() const noexcept
{
    return instrument->isLegacyModeEnabled();
}

Range<int> MPESynthesiserBase::getLegacyModeChannelRange() const noexcept
{
    return instrument->getLegacyModeChannelRange();
}

void MPESynthesiserBase::setLegacyModeChannelRange (Range<int> channelRange)
{
    instrument->setLegacyModeChannelRange (channelRange);
}

int MPESynthesiserBase::getLegacyModePitchbendRange() const noexcept
{
    return instrument->getLegacyModePitchbendRange();
}

void MPESynthesiserBase::setLegacyModePitchbendRange (int pitchbendRange)
{
    instrument->setLegacyModePitchbendRange (pitchbendRange);
}

//==============================================================================
void MPESynthesiserBase::setPressureTrackingMode (TrackingMode modeToUse)
{
    instrument->setPressureTrackingMode (modeToUse);
}

void MPESynthesiserBase::setPitchbendTrackingMode (TrackingMode modeToUse)
{
    instrument->setPitchbendTrackingMode (modeToUse);
}

void MPESynthesiserBase::setTimbreTrackingMode (TrackingMode modeToUse)
{
    instrument->setTimbreTrackingMode (modeToUse);
}

//==============================================================================
void MPESynthesiserBase::handleMidiEvent (const MidiMessage& m)
{
    instrument->processNextMidiEvent (m);
}

//==============================================================================
template <typename floatType>
void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio,
                                          const MidiBuffer& inputMidi,
                                          int startSample,
                                          int numSamples)
{
    // you must set the sample rate before using this!
    jassert (sampleRate != 0);

    MidiBuffer::Iterator midiIterator (inputMidi);
    midiIterator.setNextSamplePosition (startSample);

    bool firstEvent = true;
    int midiEventPos;
    MidiMessage m;

    const ScopedLock sl (noteStateLock);

    while (numSamples > 0)
    {
        if (! midiIterator.getNextEvent (m, midiEventPos))
        {
            renderNextSubBlock (outputAudio, startSample, numSamples);
            return;
        }

        const int samplesToNextMidiMessage = midiEventPos - startSample;

        if (samplesToNextMidiMessage >= numSamples)
        {
            renderNextSubBlock (outputAudio, startSample, numSamples);
            handleMidiEvent (m);
            break;
        }

        if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize))
        {
            handleMidiEvent (m);
            continue;
        }

        firstEvent = false;

        renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage);
        handleMidiEvent (m);
        startSample += samplesToNextMidiMessage;
        numSamples  -= samplesToNextMidiMessage;
    }

    while (midiIterator.getNextEvent (m, midiEventPos))
        handleMidiEvent (m);
}

// explicit instantiation for supported float types:
template void MPESynthesiserBase::renderNextBlock<float> (AudioBuffer<float>&, const MidiBuffer&, int, int);
template void MPESynthesiserBase::renderNextBlock<double> (AudioBuffer<double>&, const MidiBuffer&, int, int);

//==============================================================================
void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate)
{
    if (sampleRate != newRate)
    {
        const ScopedLock sl (noteStateLock);
        instrument->releaseAllNotes();
        sampleRate = newRate;
    }
}

//==============================================================================
void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept
{
    jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1
    minimumSubBlockSize = numSamples;
    subBlockSubdivisionIsStrict = shouldBeStrict;
}

} // 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 an MPE Zone.

    An MPE Zone occupies one master MIDI channel and an arbitrary
    number of note channels that immediately follow the master channel.
    It also defines a pitchbend range (in semitones) to be applied for per-note
    pitchbends and master pitchbends, respectively.

    @see MPEZoneLayout
*/
struct JUCE_API  MPEZone
{
    /** Constructor.
        Creates an MPE zone with the given master channel and
        number of note channels.

        @param masterChannel          The master MIDI channel of the new zone.
                                      All master (not per-note) messages should be send to this channel.
                                      Must be between 1 and 15. Otherwise, the behaviour
                                      is undefined.

        @param numNoteChannels        The number of note channels that the new zone
                                      should use. The first note channel will be one higher
                                      than the master channel. The number of note channels
                                      must be at least 1 and no greater than 16 - masterChannel.
                                      Otherwise, the behaviour is undefined.

        @param perNotePitchbendRange  The per-note pitchbend range in semitones of the new zone.
                                      Must be between 0 and 96. Otherwise the behaviour is undefined.
                                      If unspecified, the default setting of +/- 48 semitones
                                      will be used.

        @param masterPitchbendRange  The master pitchbend range in semitones of the new zone.
                                      Must be between 0 and 96. Otherwise the behaviour is undefined.
                                      If unspecified, the default setting of +/- 2 semitones
                                      will be used.
    */
    MPEZone (int masterChannel,
             int numNoteChannels,
             int perNotePitchbendRange = 48,
             int masterPitchbendRange = 2) noexcept;

    /* Returns the MIDI master channel number (in the range 1-16) of this zone. */
    int getMasterChannel() const noexcept;

    /** Returns the number of note channels occupied by this zone. */
    int getNumNoteChannels() const noexcept;

    /* Returns the MIDI channel number (in the range 1-16) of the
       lowest-numbered note channel of this zone.
    */
    int getFirstNoteChannel() const noexcept;

    /* Returns the MIDI channel number (in the range 1-16) of the
       highest-numbered note channel of this zone.
    */
    int getLastNoteChannel() const noexcept;

    /** Returns the MIDI channel numbers (in the range 1-16) of the
        note channels of this zone as a Range.
    */
    Range<int> getNoteChannelRange() const noexcept;

    /** Returns true if the MIDI channel (in the range 1-16) is used by this zone
        either as a note channel or as the master channel; false otherwise.
    */
    bool isUsingChannel (int channel) const noexcept;

    /** Returns true if the MIDI channel (in the range 1-16) is used by this zone
        as a note channel; false otherwise.
    */
    bool isUsingChannelAsNoteChannel (int channel) const noexcept;

    /** Returns the per-note pitchbend range in semitones set for this zone. */
    int getPerNotePitchbendRange() const noexcept;

    /** Returns the master pitchbend range in semitones set for this zone. */
    int getMasterPitchbendRange() const noexcept;

    /** Sets the per-note pitchbend range in semitones for this zone. */
    void setPerNotePitchbendRange (int rangeInSemitones) noexcept;

    /** Sets the master pitchbend range in semitones for this zone. */
    void setMasterPitchbendRange (int rangeInSemitones) noexcept;

    /** Returns true if the MIDI channels occupied by this zone
        overlap with those occupied by the other zone.
    */
    bool overlapsWith (MPEZone other) const noexcept;

    /** Tries to truncate this zone in such a way that the range of MIDI channels
        it occupies do not overlap with the other zone, by reducing this zone's
        number of note channels.

        @returns true if the truncation succeeded or if no truncation is necessary
                 because the zones do not overlap. False if the zone cannot be truncated
                 in a way that would remove the overlap (in this case you need to delete
                 the zone to remove the overlap).
    */
    bool truncateToFit (MPEZone zoneToAvoid) noexcept;

    /** @returns true if this zone is equal to the one passed in. */
    bool operator== (const MPEZone& other) const noexcept;

    /** @returns true if this zone is not equal to the one passed in. */
    bool operator!= (const MPEZone& other) const noexcept;

private:
    //==============================================================================
    int masterChannel;
    int numNoteChannels;
    int perNotePitchbendRange;
    int masterPitchbendRange;
};

} // 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
{

MPEValue::MPEValue() noexcept  : normalisedValue (8192)
{
}

MPEValue::MPEValue (int value)  : normalisedValue (value)
{
}

//==============================================================================
MPEValue MPEValue::from7BitInt (int value) noexcept
{
    jassert (value >= 0 && value <= 127);

    const int valueAs14Bit = value <= 64 ? value << 7 : int (jmap<float> (float (value - 64), 0.0f, 63.0f, 0.0f, 8191.0f)) + 8192;
    return MPEValue (valueAs14Bit);
}

MPEValue MPEValue::from14BitInt (int value) noexcept
{
    jassert (value >= 0 && value <= 16383);
    return MPEValue (value);
}

//==============================================================================
MPEValue MPEValue::minValue() noexcept      { return MPEValue::from7BitInt (0); }
MPEValue MPEValue::centreValue() noexcept   { return MPEValue::from7BitInt (64); }
MPEValue MPEValue::maxValue() noexcept      { return MPEValue::from7BitInt (127); }

int MPEValue::as7BitInt() const noexcept
{
    return normalisedValue >> 7;
}

int MPEValue::as14BitInt() const noexcept
{
    return normalisedValue;
}

//==============================================================================
float MPEValue::asSignedFloat() const noexcept
{
    return (normalisedValue < 8192)
           ? jmap<float> (float (normalisedValue), 0.0f, 8192.0f, -1.0f, 0.0f)
           : jmap<float> (float (normalisedValue), 8192.0f, 16383.0f, 0.0f, 1.0f);
}

float MPEValue::asUnsignedFloat() const noexcept
{
    return jmap<float> (float (normalisedValue), 0.0f, 16383.0f, 0.0f, 1.0f);
}

//==============================================================================
bool MPEValue::operator== (const MPEValue& other) const noexcept
{
    return normalisedValue == other.normalisedValue;
}

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

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

class MPEValueTests  : public UnitTest
{
public:
    MPEValueTests() : UnitTest ("MPEValue class", "MIDI/MPE") {}

    void runTest() override
    {
        beginTest ("comparison operator");
        {
            MPEValue value1 = MPEValue::from7BitInt (7);
            MPEValue value2 = MPEValue::from7BitInt (7);
            MPEValue value3 = MPEValue::from7BitInt (8);

            expect (value1 == value1);
            expect (value1 == value2);
            expect (value1 != value3);
        }

        beginTest ("special values");
        {
            expectEquals (MPEValue::minValue().as7BitInt(), 0);
            expectEquals (MPEValue::minValue().as14BitInt(), 0);

            expectEquals (MPEValue::centreValue().as7BitInt(), 64);
            expectEquals (MPEValue::centreValue().as14BitInt(), 8192);

            expectEquals (MPEValue::maxValue().as7BitInt(), 127);
            expectEquals (MPEValue::maxValue().as14BitInt(), 16383);
        }

        beginTest ("zero/minimum value");
        {
            expectValuesConsistent (MPEValue::from7BitInt (0),  0, 0, -1.0f, 0.0f);
            expectValuesConsistent (MPEValue::from14BitInt (0), 0, 0, -1.0f, 0.0f);
        }

        beginTest ("maximum value");
        {
            expectValuesConsistent (MPEValue::from7BitInt (127),    127, 16383, 1.0f, 1.0f);
            expectValuesConsistent (MPEValue::from14BitInt (16383), 127, 16383, 1.0f, 1.0f);
        }

        beginTest ("centre value");
        {
            expectValuesConsistent (MPEValue::from7BitInt (64),    64, 8192, 0.0f, 0.5f);
            expectValuesConsistent (MPEValue::from14BitInt (8192), 64, 8192, 0.0f, 0.5f);
        }

        beginTest ("value halfway between min and centre");
        {
            expectValuesConsistent (MPEValue::from7BitInt (32),    32, 4096, -0.5f, 0.25f);
            expectValuesConsistent (MPEValue::from14BitInt (4096), 32, 4096, -0.5f, 0.25f);
        }
    }

private:
    //==============================================================================
    void expectValuesConsistent (MPEValue value,
                                 int expectedValueAs7BitInt,
                                 int expectedValueAs14BitInt,
                                 float expectedValueAsSignedFloat,
                                 float expectedValueAsUnsignedFloat)
    {
        expectEquals (value.as7BitInt(), expectedValueAs7BitInt);
        expectEquals (value.as14BitInt(), expectedValueAs14BitInt);
        expectFloatWithinRelativeError (value.asSignedFloat(), expectedValueAsSignedFloat, 0.0001f);
        expectFloatWithinRelativeError (value.asUnsignedFloat(), expectedValueAsUnsignedFloat, 0.0001f);
    }

    //==============================================================================
    void expectFloatWithinRelativeError (float actualValue, float expectedValue, float maxRelativeError)
    {
        const float maxAbsoluteError = jmax (1.0f, std::abs (expectedValue)) * maxRelativeError;
        expect (std::abs (expectedValue - actualValue) < maxAbsoluteError);
    }
};

static MPEValueTests MPEValueUnitTests;

#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 class represents an instrument handling MPE.

    It has an MPE zone layout and maintans a state of currently
    active (playing) notes and the values of their dimensions of expression.

    You can trigger and modulate notes:
     - by passing MIDI messages with the method processNextMidiEvent;
     - by directly calling the methods noteOn, noteOff etc.

    The class implements the channel and note management logic specified in
    MPE. If you pass it a message, it will know what notes on what
    channels (if any) should be affected by that message.

    The class has a Listener class with the three callbacks MPENoteAdded,
    MPENoteChanged, and MPENoteFinished. Implement such a
    Listener class to react to note changes and trigger some functionality for
    your application that depends on the MPE note state.
    For example, you can use this class to write an MPE visualiser.

    If you want to write a real-time audio synth with MPE functionality,
    you should instead use the classes MPESynthesiserBase, which adds
    the ability to render audio and to manage voices.

    @see MPENote, MPEZoneLayout, MPESynthesiser
*/
class JUCE_API  MPEInstrument
{
public:

    /** Constructor.
        This will construct an MPE instrument with initially no MPE zones.

        In order to process incoming MIDI, call setZoneLayout, define the layout
        via MIDI RPN messages, or set the instrument to legacy mode.
    */
    MPEInstrument() noexcept;

    /** Destructor. */
    virtual ~MPEInstrument();

    //==============================================================================
    /** Returns the current zone layout of the instrument.
        This happens by value, to enforce thread-safety and class invariants.

        Note: If the instrument is in legacy mode, the return value of this
        method is unspecified.
    */
    MPEZoneLayout getZoneLayout() const noexcept;

    /** Re-sets the zone layout of the instrument to the one passed in.
        As a side effect, this will discard all currently playing notes,
        and call noteReleased for all of them.

        This will also disable legacy mode in case it was enabled previously.
    */
    void setZoneLayout (MPEZoneLayout newLayout);

    /** Returns true if the given MIDI channel (1-16) is a note channel in any
        of the MPEInstrument's MPE zones; false otherwise.
        When in legacy mode, this will return true if the given channel is
        contained in the current legacy mode channel range; false otherwise.
    */
    bool isNoteChannel (int midiChannel) const noexcept;

    /** Returns true if the given MIDI channel (1-16) is a master channel in any
        of the MPEInstrument's MPE zones; false otherwise.
        When in legacy mode, this will always return false.
    */
    bool isMasterChannel (int midiChannel) const noexcept;

    //==============================================================================
    /** The MPE note tracking mode. In case there is more than one note playing
        simultaneously on the same MIDI channel, this determines which of these
        notes will be modulated by an incoming MPE message on that channel
        (pressure, pitchbend, or timbre).

        The default is lastNotePlayedOnChannel.
    */
    enum TrackingMode
    {
        lastNotePlayedOnChannel, //! The most recent note on the channel that is still played (key down and/or sustained)
        lowestNoteOnChannel,     //! The lowest note (by initialNote) on the channel with the note key still down
        highestNoteOnChannel,    //! The highest note (by initialNote) on the channel with the note key still down
        allNotesOnChannel        //! All notes on the channel (key down and/or sustained)
    };

    /** 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);

    //==============================================================================
    /** Process a MIDI message and trigger the appropriate method calls
        (noteOn, noteOff etc.)

        You can override this method if you need some special MIDI message
        treatment on top of the standard MPE logic implemented here.
    */
    virtual void processNextMidiEvent (const MidiMessage& message);

    //==============================================================================
    /** Request a note-on on the given channel, with the given initial note
        number and velocity.
        If the message arrives on a valid note channel, this will create a
        new MPENote and call the noteAdded callback.
    */
    virtual void noteOn (int midiChannel, int midiNoteNumber, MPEValue midiNoteOnVelocity);

    /** Request a note-off. If there is a matching playing note, this will
        release the note (except if it is sustained by a sustain or sostenuto
        pedal) and call the noteReleased callback.
    */
    virtual void noteOff (int midiChannel, int midiNoteNumber, MPEValue midiNoteOffVelocity);

    /** Request a pitchbend on the given channel with the given value (in units
        of MIDI pitchwheel position).
        Internally, this will determine whether the pitchwheel move is a
        per-note pitchbend or a master pitchbend (depending on midiChannel),
        take the correct per-note or master pitchbend range of the affected MPE
        zone, and apply the resulting pitchbend to the affected note(s) (if any).
    */
    virtual void pitchbend (int midiChannel, MPEValue pitchbend);

    /** Request a pressure change on the given channel with the given value.
        This will modify the pressure dimension of the note currently held down
        on this channel (if any). If the channel is a zone master channel,
        the pressure change will be broadcast to all notes in this zone.
    */
    virtual void pressure (int midiChannel, MPEValue value);

    /** Request a third dimension (timbre) change on the given channel with the
        given value.
        This will modify the timbre dimension of the note currently held down
        on this channel (if any). If the channel is a zone master channel,
        the timbre change will be broadcast to all notes in this zone.
    */
    virtual void timbre (int midiChannel, MPEValue value);

    /** Request a sustain pedal press or release. If midiChannel is a zone's
        master channel, this will act on all notes in that zone; otherwise,
        nothing will happen.
    */
    virtual void sustainPedal (int midiChannel, bool isDown);

    /** Request a sostenuto pedal press or release. If midiChannel is a zone's
        master channel, this will act on all notes in that zone; otherwise,
        nothing will happen.
    */
    virtual void sostenutoPedal (int midiChannel, bool isDown);

    /** Discard all currently playing notes.
        This will also call the noteReleased listener callback for all of them.
    */
    void releaseAllNotes();

    //==============================================================================
    /** Returns the number of MPE notes currently played by the
        instrument.
    */
    int getNumPlayingNotes() const noexcept;

    /** Returns the note at the given index. If there is no such note, returns
        an invalid MPENote. The notes are sorted such that the most recently
        added note is the last element.
    */
    MPENote getNote (int index) const noexcept;

    /** Returns the note currently playing on the given midiChannel with the
        specified initial MIDI note number, if there is such a note.
        Otherwise, this returns an invalid MPENote
        (check with note.isValid() before use!)
    */
    MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept;

    /** Returns the most recent note that is playing on the given midiChannel
        (this will be the note which has received the most recent note-on without
        a corresponding note-off), if there is such a note.
        Otherwise, this returns an invalid MPENote
        (check with note.isValid() before use!)
    */
    MPENote getMostRecentNote (int midiChannel) const noexcept;

    /** Returns the most recent note that is not the note passed in.
        If there is no such note, this returns an invalid MPENote
        (check with note.isValid() before use!)
        This helper method might be useful for some custom voice handling algorithms.
    */
    MPENote getMostRecentNoteOtherThan (MPENote otherThanThisNote) const noexcept;

    //==============================================================================
    /** Derive from this class to be informed about any changes in the expressive
        MIDI notes played by this instrument.

        Note: This listener type receives its callbacks immediately, and not
        via the message thread (so you might be for example in the MIDI thread).
        Therefore you should never do heavy work such as graphics rendering etc.
        inside those callbacks.
    */
    class JUCE_API  Listener
    {
    public:
        /** Destructor. */
        virtual ~Listener() {}

        /** Implement this callback to be informed whenever a new expressive
            MIDI note is triggered.
        */
        virtual void noteAdded (MPENote newNote) = 0;

        /** Implement this callback to be informed whenever a currently
            playing MPE note's pressure value changes.
        */
        virtual void notePressureChanged (MPENote changedNote) = 0;

        /** Implement this callback to be informed whenever a currently
            playing MPE note's pitchbend value changes.
            Note: This can happen if the note itself is bent, if there is a
            master channel pitchbend event, or if both occur simultaneously.
            Call MPENote::getFrequencyInHertz to get the effective note frequency.
        */
        virtual void notePitchbendChanged (MPENote changedNote) = 0;

        /** Implement this callback to be informed whenever a currently
            playing MPE note's timbre value changes.
        */
        virtual void noteTimbreChanged (MPENote changedNote) = 0;

        /** Implement this callback to be informed whether a currently playing
            MPE note's key state (whether the key is down and/or the note is
            sustained) has changed.
            Note: if the key state changes to MPENote::off, noteReleased is
            called instead.
        */
        virtual void noteKeyStateChanged (MPENote changedNote) = 0;

        /** Implement this callback to be informed 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.
        */
        virtual void noteReleased (MPENote finishedNote) = 0;
    };

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

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

    //==============================================================================
    /** Puts the instrument into legacy mode.
        As a side effect, this will discard all currently playing notes,
        and call noteReleased for all of them.

        This special zone layout mode is for backwards compatibility with
        non-MPE MIDI devices. In this mode, the instrument will ignore the
        current MPE zone layout. It will instead take a range of MIDI channels
        (default: all channels 1-16) and treat them as note channels, with no
        master channel. MIDI channels outside of this range will be ignored.

        @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);

protected:
    //==============================================================================
    CriticalSection lock;

private:
    //==============================================================================
    Array<MPENote> notes;
    MPEZoneLayout zoneLayout;
    ListenerList<Listener> listeners;

    uint8 lastPressureLowerBitReceivedOnChannel[16];
    uint8 lastTimbreLowerBitReceivedOnChannel[16];
    bool isNoteChannelSustained[16];

    struct LegacyMode
    {
        bool isEnabled;
        Range<int> channelRange;
        int pitchbendRange;
    };

    struct MPEDimension
    {
        MPEDimension() noexcept  : trackingMode (lastNotePlayedOnChannel) {}
        TrackingMode trackingMode;
        MPEValue lastValueReceivedOnChannel[16];
        MPEValue MPENote::* value;
        MPEValue& getValue (MPENote& note) noexcept   { return note.*(value); }
    };

    LegacyMode legacyMode;
    MPEDimension pitchbendDimension, pressureDimension, timbreDimension;

    void updateDimension (int midiChannel, MPEDimension&, MPEValue);
    void updateDimensionMaster (MPEZone&, MPEDimension&, MPEValue);
    void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue);
    void callListenersDimensionChanged (MPENote&, MPEDimension&);
    MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const;

    void processMidiNoteOnMessage (const MidiMessage&);
    void processMidiNoteOffMessage (const MidiMessage&);
    void processMidiPitchWheelMessage (const MidiMessage&);
    void processMidiChannelPressureMessage (const MidiMessage&);
    void processMidiControllerMessage (const MidiMessage&);
    void processMidiAllNotesOffMessage (const MidiMessage&);
    void handlePressureMSB (int midiChannel, int value) noexcept;
    void handlePressureLSB (int midiChannel, int value) noexcept;
    void handleTimbreMSB (int midiChannel, int value) noexcept;
    void handleTimbreLSB (int midiChannel, int value) noexcept;
    void handleSustainOrSostenuto (int midiChannel, bool isDown, bool isSostenuto);

    MPENote* getNotePtr (int midiChannel, int midiNoteNumber) const noexcept;
    MPENote* getNotePtr (int midiChannel, TrackingMode) const noexcept;
    MPENote* getLastNotePlayedPtr (int midiChannel) const noexcept;
    MPENote* getHighestNotePtr (int midiChannel) const noexcept;
    MPENote* getLowestNotePtr (int midiChannel) const noexcept;
    void updateNoteTotalPitchbend (MPENote&);

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPEInstrument)
};

} // 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
{

//==============================================================================
/**
    Describes one of the sounds that a Synthesiser can play.

    A synthesiser can contain one or more sounds, and a sound can choose which
    midi notes and channels can trigger it.

    The SynthesiserSound is a passive class that just describes what the sound is -
    the actual audio rendering for a sound is done by a SynthesiserVoice. This allows
    more than one SynthesiserVoice to play the same sound at the same time.

    @see Synthesiser, SynthesiserVoice
*/
class JUCE_API  SynthesiserSound    : public ReferenceCountedObject
{
protected:
    //==============================================================================
    SynthesiserSound();

public:
    /** Destructor. */
    virtual ~SynthesiserSound();

    //==============================================================================
    /** Returns true if this sound should be played when a given midi note is pressed.

        The Synthesiser will use this information when deciding which sounds to trigger
        for a given note.
    */
    virtual bool appliesToNote (int midiNoteNumber) = 0;

    /** Returns true if the sound should be triggered by midi events on a given channel.

        The Synthesiser will use this information when deciding which sounds to trigger
        for a given note.
    */
    virtual bool appliesToChannel (int midiChannel) = 0;

    /** The class is reference-counted, so this is a handy pointer class for it. */
    typedef ReferenceCountedObjectPtr<SynthesiserSound> Ptr;

private:
    //==============================================================================
    JUCE_LEAK_DETECTOR (SynthesiserSound)
};

//==============================================================================
/**
    Represents a voice that a Synthesiser can use to play a SynthesiserSound.

    A voice plays a single sound at a time, and a synthesiser holds an array of
    voices so that it can play polyphonically.

    @see Synthesiser, SynthesiserSound
*/
class JUCE_API  SynthesiserVoice
{
public:
    //==============================================================================
    /** Creates a voice. */
    SynthesiserVoice();

    /** Destructor. */
    virtual ~SynthesiserVoice();

    //==============================================================================
    /** Returns the midi note that this voice is currently playing.
        Returns a value less than 0 if no note is playing.
    */
    int getCurrentlyPlayingNote() const noexcept                        { return currentlyPlayingNote; }

    /** Returns the sound that this voice is currently playing.
        Returns nullptr if it's not playing.
    */
    SynthesiserSound::Ptr getCurrentlyPlayingSound() const noexcept     { return currentlyPlayingSound; }

    /** Must return true if this voice object is capable of playing the given sound.

        If there are different classes of sound, and different classes of voice, a voice can
        choose which ones it wants to take on.

        A typical implementation of this method may just return true if there's only one type
        of voice and sound, or it might check the type of the sound object passed-in and
        see if it's one that it understands.
    */
    virtual bool canPlaySound (SynthesiserSound*) = 0;

    /** Called to start a new note.
        This will be called during the rendering callback, so must be fast and thread-safe.
    */
    virtual void startNote (int midiNoteNumber,
                            float velocity,
                            SynthesiserSound* sound,
                            int currentPitchWheelPosition) = 0;

    /** Called to stop a note.

        This will be called during the rendering callback, so must be fast and thread-safe.

        The velocity indicates how quickly the note was released - 0 is slowly, 1 is quickly.

        If allowTailOff is false or the voice doesn't want to tail-off, then it must stop all
        sound immediately, and must call clearCurrentNote() to reset the state of this voice
        and allow the synth to reassign it another sound.

        If allowTailOff is true and the voice decides to do a tail-off, then it's allowed to
        begin fading out its sound, and it can stop playing until it's finished. As soon as it
        finishes playing (during the rendering callback), it must make sure that it calls
        clearCurrentNote().
    */
    virtual void stopNote (float velocity, bool allowTailOff) = 0;

    /** Returns true if this voice is currently busy playing a sound.
        By default this just checks the getCurrentlyPlayingNote() value, but can
        be overridden for more advanced checking.
    */
    virtual bool isVoiceActive() const;

    /** Called to let the voice know that the pitch wheel has been moved.
        This will be called during the rendering callback, so must be fast and thread-safe.
    */
    virtual void pitchWheelMoved (int newPitchWheelValue) = 0;

    /** Called to let the voice know that a midi controller has been moved.
        This will be called during the rendering callback, so must be fast and thread-safe.
    */
    virtual void controllerMoved (int controllerNumber, int newControllerValue) = 0;

    /** Called to let the voice know that the aftertouch has changed.
        This will be called during the rendering callback, so must be fast and thread-safe.
    */
    virtual void aftertouchChanged (int newAftertouchValue);

    /** Called to let the voice know that the channel pressure has changed.
        This will be called during the rendering callback, so must be fast and thread-safe.
    */
    virtual void channelPressureChanged (int newChannelPressureValue);

    //==============================================================================
    /** Renders the next block of data for this voice.

        The output audio data must be added to the current contents of the buffer provided.
        Only the region of the buffer between startSample and (startSample + numSamples)
        should be altered by this method.

        If the voice is currently silent, it should just return without doing anything.

        If the sound that the voice is playing finishes during the course of this rendered
        block, it must call clearCurrentNote(), to tell the synthesiser that it has finished.

        The size of the blocks that are rendered can change each time it is called, and may
        involve rendering as little as 1 sample at a time. In between rendering callbacks,
        the voice's methods will be called to tell it about note and controller events.
    */
    virtual void renderNextBlock (AudioBuffer<float>& outputBuffer,
                                  int startSample,
                                  int numSamples) = 0;

    /** A double-precision version of renderNextBlock() */
    virtual void renderNextBlock (AudioBuffer<double>& outputBuffer,
                                  int startSample,
                                  int numSamples);

    /** Changes the voice's reference sample rate.

        The rate is set so that subclasses know the output rate and can set their pitch
        accordingly.

        This method is called by the synth, and subclasses can access the current rate with
        the currentSampleRate member.
    */
    virtual void setCurrentPlaybackSampleRate (double newRate);

    /** Returns true if the voice is currently playing a sound which is mapped to the given
        midi channel.

        If it's not currently playing, this will return false.
    */
    virtual bool isPlayingChannel (int midiChannel) const;

    /** 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 currentSampleRate; }

    /** Returns true if the key that triggered this voice is still held down.
        Note that the voice may still be playing after the key was released (e.g because the
        sostenuto pedal is down).
    */
    bool isKeyDown() const noexcept                             { return keyIsDown; }

    /** Allows you to modify the flag indicating that the key that triggered this voice is still held down.
        @see isKeyDown
    */
    void setKeyDown (bool isNowDown) noexcept                   { keyIsDown = isNowDown; }

    /** Returns true if the sustain pedal is currently active for this voice. */
    bool isSustainPedalDown() const noexcept                    { return sustainPedalDown; }

    /** Modifies the sustain pedal flag. */
    void setSustainPedalDown (bool isNowDown) noexcept          { sustainPedalDown = isNowDown; }

    /** Returns true if the sostenuto pedal is currently active for this voice. */
    bool isSostenutoPedalDown() const noexcept                  { return sostenutoPedalDown; }

    /** Modifies the sostenuto pedal flag. */
    void setSostenutoPedalDown (bool isNowDown) noexcept        { sostenutoPedalDown = isNowDown; }

    /** Returns true if a voice is sounding in its release phase **/
    bool isPlayingButReleased() const noexcept
    {
        return isVoiceActive() && ! (isKeyDown() || isSostenutoPedalDown() || isSustainPedalDown());
    }

    /** Returns true if this voice started playing its current note before the other voice did. */
    bool wasStartedBefore (const SynthesiserVoice& other) const noexcept;

protected:
    /** Resets the state of this voice after a sound has finished playing.

        The subclass must call this when it finishes playing a note and becomes available
        to play new ones.

        It must either call it in the stopNote() method, or if the voice is tailing off,
        then it should call it later during the renderNextBlock method, as soon as it
        finishes its tail-off.

        It can also be called at any time during the render callback if the sound happens
        to have finished, e.g. if it's playing a sample and the sample finishes.
    */
    void clearCurrentNote();

private:
    //==============================================================================
    friend class Synthesiser;

    double currentSampleRate = 44100.0;
    int currentlyPlayingNote = -1, currentPlayingMidiChannel = 0;
    uint32 noteOnTime = 0;
    SynthesiserSound::Ptr currentlyPlayingSound;
    bool keyIsDown = false, sustainPedalDown = false, sostenutoPedalDown = false;

    AudioBuffer<float> tempBuffer;

   #if JUCE_CATCH_DEPRECATED_CODE_MISUSE
    // Note the new parameters for this method.
    virtual int stopNote (bool) { return 0; }
   #endif

    JUCE_LEAK_DETECTOR (SynthesiserVoice)
};

//==============================================================================
/**
    Base class for a musical device that can play sounds.

    To create a synthesiser, you'll need to create a subclass of SynthesiserSound
    to describe each sound available to your synth, and a subclass of SynthesiserVoice
    which can play back one of these sounds.

    Then you can use the addVoice() and addSound() methods to give the synthesiser a
    set of sounds, and a set of voices it can use to play them. 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. Any midi
    events that go in will be scanned for note on/off messages, and these are used to
    start and stop the voices playing the appropriate sounds.

    While it's playing, you can also cause notes to be triggered by calling the noteOn(),
    noteOff() and other controller methods.

    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.
*/
class JUCE_API  Synthesiser
{
public:
    //==============================================================================
    /** Creates a new synthesiser.
        You'll need to add some sounds and voices before it'll make any sound.
    */
    Synthesiser();

    /** Destructor. */
    virtual ~Synthesiser();

    //==============================================================================
    /** 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. */
    SynthesiserVoice* 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.
    */
    SynthesiserVoice* addVoice (SynthesiserVoice* newVoice);

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

    //==============================================================================
    /** Deletes all sounds. */
    void clearSounds();

    /** Returns the number of sounds that have been added to the synth. */
    int getNumSounds() const noexcept                               { return sounds.size(); }

    /** Returns one of the sounds. */
    SynthesiserSound* getSound (int index) const noexcept           { return sounds [index]; }

    /** Adds a new sound to the synthesiser.

        The object passed in is reference counted, so will be deleted when the
        synthesiser and all voices are no longer using it.
    */
    SynthesiserSound* addSound (const SynthesiserSound::Ptr& newSound);

    /** Removes and deletes one of the sounds. */
    void removeSound (int index);

    //==============================================================================
    /** 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 setNoteStealingEnabled (bool shouldStealNotes);

    /** Returns true if note-stealing is enabled.
        @see setNoteStealingEnabled
    */
    bool isNoteStealingEnabled() const noexcept                     { return shouldStealNotes; }

    //==============================================================================
    /** Triggers a note-on event.

        The default method here will find all the sounds that want to be triggered by
        this note/channel. For each sound, it'll try to find a free voice, and use the
        voice to start playing the sound.

        Subclasses might want to override this if they need a more complex algorithm.

        This method will be called automatically according to the midi data passed into
        renderNextBlock(), but may be called explicitly too.

        The midiChannel parameter is the channel, between 1 and 16 inclusive.
    */
    virtual void noteOn (int midiChannel,
                         int midiNoteNumber,
                         float velocity);

    /** Triggers a note-off event.

        This will turn off any voices that are playing a sound for the given note/channel.

        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 will be called automatically according to the midi data passed into
        renderNextBlock(), but may be called explicitly too.

        The midiChannel parameter is the channel, between 1 and 16 inclusive.
    */
    virtual void noteOff (int midiChannel,
                          int midiNoteNumber,
                          float velocity,
                          bool allowTailOff);

    /** Turns off all notes.

        This will turn off any voices that are playing a sound on the given midi channel.

        If midiChannel is 0 or less, then all voices will be turned off, regardless of
        which channel they're playing. Otherwise it represents a valid midi channel, from
        1 to 16 inclusive.

        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 will be called automatically according to the midi data passed into
        renderNextBlock(), but may be called explicitly too.
    */
    virtual void allNotesOff (int midiChannel,
                              bool allowTailOff);

    /** Sends a pitch-wheel message to any active voices.

        This will send a pitch-wheel message to any voices that are playing sounds on
        the given midi channel.

        This method will be called automatically according to the midi data passed into
        renderNextBlock(), but may be called explicitly too.

        @param midiChannel          the midi channel, from 1 to 16 inclusive
        @param wheelValue           the wheel position, from 0 to 0x3fff, as returned by MidiMessage::getPitchWheelValue()
    */
    virtual void handlePitchWheel (int midiChannel,
                                   int wheelValue);

    /** Sends a midi controller message to any active voices.

        This will send a midi controller message to any voices that are playing sounds on
        the given midi channel.

        This method will be called automatically according to the midi data passed into
        renderNextBlock(), but may be called explicitly too.

        @param midiChannel          the midi channel, from 1 to 16 inclusive
        @param controllerNumber     the midi controller type, as returned by MidiMessage::getControllerNumber()
        @param controllerValue      the midi controller value, between 0 and 127, as returned by MidiMessage::getControllerValue()
    */
    virtual void handleController (int midiChannel,
                                   int controllerNumber,
                                   int controllerValue);

    /** Sends an aftertouch message.

        This will send an aftertouch message to any voices that are playing sounds on
        the given midi channel and note number.

        This method will be called automatically according to the midi data passed into
        renderNextBlock(), but may be called explicitly too.

        @param midiChannel          the midi channel, from 1 to 16 inclusive
        @param midiNoteNumber       the midi note number, 0 to 127
        @param aftertouchValue      the aftertouch value, between 0 and 127,
                                    as returned by MidiMessage::getAftertouchValue()
    */
    virtual void handleAftertouch (int midiChannel, int midiNoteNumber, int aftertouchValue);

    /** Sends a channel pressure message.

        This will send a channel pressure message to any voices that are playing sounds on
        the given midi channel.

        This method will be called automatically according to the midi data passed into
        renderNextBlock(), but may be called explicitly too.

        @param midiChannel              the midi channel, from 1 to 16 inclusive
        @param channelPressureValue     the pressure value, between 0 and 127, as returned
                                        by MidiMessage::getChannelPressureValue()
    */
    virtual void handleChannelPressure (int midiChannel, int channelPressureValue);

    /** Handles a sustain pedal event. */
    virtual void handleSustainPedal (int midiChannel, bool isDown);

    /** Handles a sostenuto pedal event. */
    virtual void handleSostenutoPedal (int midiChannel, bool isDown);

    /** Can be overridden to handle soft pedal events. */
    virtual void handleSoftPedal (int midiChannel, bool isDown);

    /** Can be overridden to handle an incoming program change message.
        The base class implementation of this has no effect, but you may want to make your
        own synth react to program changes.
    */
    virtual void handleProgramChange (int midiChannel,
                                      int programNumber);

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

        This value is propagated to the voices so that they can use it to render the correct
        pitches.
    */
    virtual void setCurrentPlaybackSampleRate (double sampleRate);

    /** Creates the next block of audio output.

        This will process the next numSamples of data from all the voices, and add that output
        to the audio block supplied, starting from the offset specified. Note that the
        data will be added to the current contents of the buffer, so you should clear it
        before calling this method if necessary.

        The midi events in the inputMidi buffer are parsed for note and controller events,
        and these are used to trigger the voices. Note that the startSample offset applies
        both to the audio output buffer and the midi input buffer, so any midi events
        with timestamps outside the specified region will be ignored.
    */
    inline void renderNextBlock (AudioBuffer<float>& outputAudio,
                                 const MidiBuffer& inputMidi,
                                 int startSample,
                                 int numSamples)
        { processNextBlock (outputAudio, inputMidi, startSample, numSamples); }

    inline void renderNextBlock (AudioBuffer<double>& outputAudio,
                                 const MidiBuffer& inputMidi,
                                 int startSample,
                                 int numSamples)
        { processNextBlock (outputAudio, inputMidi, startSample, numSamples); }

    /** 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; }

    /** 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;

protected:
    //==============================================================================
    /** This is used to control access to the rendering callback and the note trigger methods. */
    CriticalSection lock;

    OwnedArray<SynthesiserVoice> voices;
    ReferenceCountedArray<SynthesiserSound> sounds;

    /** The last pitch-wheel values for each midi channel. */
    int lastPitchWheelValues [16];

    /** Renders the voices for the given range.
        By default this just calls renderNextBlock() on each voice, but you may need
        to override it to handle custom cases.
    */
    virtual void renderVoices (AudioBuffer<float>& outputAudio,
                               int startSample, int numSamples);
    virtual void renderVoices (AudioBuffer<double>& outputAudio,
                               int startSample, int numSamples);

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

        Returns nullptr if all voices are busy and stealing isn't enabled.

        To implement a custom note-stealing algorithm, you can either override this
        method, or (preferably) override findVoiceToSteal().
    */
    virtual SynthesiserVoice* findFreeVoice (SynthesiserSound* soundToPlay,
                                             int midiChannel,
                                             int midiNoteNumber,
                                             bool stealIfNoneAvailable) const;

    /** Chooses a voice that is most suitable for being re-used.
        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.
    */
    virtual SynthesiserVoice* findVoiceToSteal (SynthesiserSound* soundToPlay,
                                                int midiChannel,
                                                int midiNoteNumber) const;

    /** Starts a specified voice playing a particular sound.
        You'll probably never need to call this, it's used internally by noteOn(), but
        may be needed by subclasses for custom behaviours.
    */
    void startVoice (SynthesiserVoice* voice,
                     SynthesiserSound* sound,
                     int midiChannel,
                     int midiNoteNumber,
                     float velocity);

    /** Stops a given voice.
        You should never need to call this, it's used internally by noteOff, but is protected
        in case it's useful for some custom subclasses. It basically just calls through to
        SynthesiserVoice::stopNote(), and has some assertions to sanity-check a few things.
    */
    void stopVoice (SynthesiserVoice*, float velocity, bool allowTailOff);

    /** Can be overridden to do custom handling of incoming midi events. */
    virtual void handleMidiEvent (const MidiMessage&);

private:
    //==============================================================================
    template <typename floatType>
    void processNextBlock (AudioBuffer<floatType>& outputAudio,
                           const MidiBuffer& inputMidi,
                           int startSample,
                           int numSamples);
    //==============================================================================
    double sampleRate = 0;
    uint32 lastNoteOnCounter = 0;
    int minimumSubBlockSize = 32;
    bool subBlockSubdivisionIsStrict = false;
    bool shouldStealNotes = true;
    BigInteger sustainPedalsDown;

   #if JUCE_CATCH_DEPRECATED_CODE_MISUSE
    // Note the new parameters for these methods.
    virtual int findFreeVoice (const bool) const { return 0; }
    virtual int noteOff (int, int, int) { return 0; }
    virtual int findFreeVoice (SynthesiserSound*, const bool) { return 0; }
    virtual int findVoiceToSteal (SynthesiserSound*) const { return 0; }
   #endif

    JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Synthesiser)
};

} // namespace juce