Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -0,0 +1,193 @@
/*
==============================================================================
This file is part of the JUCE library.
Copyright (c) 2020 - Raw Material Software Limited
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
{

#ifndef JUCE_SNAP_TO_ZERO
#if JUCE_INTEL
#define JUCE_SNAP_TO_ZERO(n) if (! (n < -1.0e-8f || n > 1.0e-8f)) n = 0;
#else
#define JUCE_SNAP_TO_ZERO(n) ignoreUnused (n)
#endif
#endif
class ScopedNoDenormals;

#if ! DOXYGEN
namespace detail
{

template <typename FloatType, typename CountType>
struct FloatVectorOperationsBase
{
static void JUCE_CALLTYPE clear (FloatType* dest, CountType numValues) noexcept;
static void JUCE_CALLTYPE fill (FloatType* dest, FloatType valueToFill, CountType numValues) noexcept;
static void JUCE_CALLTYPE copy (FloatType* dest, const FloatType* src, CountType numValues) noexcept;
static void JUCE_CALLTYPE copyWithMultiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType numValues) noexcept;
static void JUCE_CALLTYPE add (FloatType* dest, FloatType amountToAdd, CountType numValues) noexcept;
static void JUCE_CALLTYPE add (FloatType* dest, const FloatType* src, FloatType amount, CountType numValues) noexcept;
static void JUCE_CALLTYPE add (FloatType* dest, const FloatType* src, CountType numValues) noexcept;
static void JUCE_CALLTYPE add (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept;
static void JUCE_CALLTYPE subtract (FloatType* dest, const FloatType* src, CountType numValues) noexcept;
static void JUCE_CALLTYPE subtract (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept;
static void JUCE_CALLTYPE addWithMultiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType numValues) noexcept;
static void JUCE_CALLTYPE addWithMultiply (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept;
static void JUCE_CALLTYPE subtractWithMultiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType numValues) noexcept;
static void JUCE_CALLTYPE subtractWithMultiply (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept;
static void JUCE_CALLTYPE multiply (FloatType* dest, const FloatType* src, CountType numValues) noexcept;
static void JUCE_CALLTYPE multiply (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType numValues) noexcept;
static void JUCE_CALLTYPE multiply (FloatType* dest, FloatType multiplier, CountType numValues) noexcept;
static void JUCE_CALLTYPE multiply (FloatType* dest, const FloatType* src, FloatType multiplier, CountType num) noexcept;
static void JUCE_CALLTYPE negate (FloatType* dest, const FloatType* src, CountType numValues) noexcept;
static void JUCE_CALLTYPE abs (FloatType* dest, const FloatType* src, CountType numValues) noexcept;
static void JUCE_CALLTYPE min (FloatType* dest, const FloatType* src, FloatType comp, CountType num) noexcept;
static void JUCE_CALLTYPE min (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept;
static void JUCE_CALLTYPE max (FloatType* dest, const FloatType* src, FloatType comp, CountType num) noexcept;
static void JUCE_CALLTYPE max (FloatType* dest, const FloatType* src1, const FloatType* src2, CountType num) noexcept;
static void JUCE_CALLTYPE clip (FloatType* dest, const FloatType* src, FloatType low, FloatType high, CountType num) noexcept;
static Range<FloatType> JUCE_CALLTYPE findMinAndMax (const FloatType* src, CountType numValues) noexcept;
static FloatType JUCE_CALLTYPE findMinimum (const FloatType* src, CountType numValues) noexcept;
static FloatType JUCE_CALLTYPE findMaximum (const FloatType* src, CountType numValues) noexcept;
};

template <typename...>
struct NameForwarder;

template <typename Head>
struct NameForwarder<Head> : Head {};

template <typename Head, typename... Tail>
struct NameForwarder<Head, Tail...> : Head, NameForwarder<Tail...>
{
using Head::clear;
using NameForwarder<Tail...>::clear;

using Head::fill;
using NameForwarder<Tail...>::fill;

using Head::copy;
using NameForwarder<Tail...>::copy;

using Head::copyWithMultiply;
using NameForwarder<Tail...>::copyWithMultiply;

using Head::add;
using NameForwarder<Tail...>::add;

using Head::subtract;
using NameForwarder<Tail...>::subtract;

using Head::addWithMultiply;
using NameForwarder<Tail...>::addWithMultiply;

using Head::subtractWithMultiply;
using NameForwarder<Tail...>::subtractWithMultiply;

using Head::multiply;
using NameForwarder<Tail...>::multiply;

using Head::negate;
using NameForwarder<Tail...>::negate;

using Head::abs;
using NameForwarder<Tail...>::abs;

using Head::min;
using NameForwarder<Tail...>::min;

using Head::max;
using NameForwarder<Tail...>::max;

using Head::clip;
using NameForwarder<Tail...>::clip;

using Head::findMinAndMax;
using NameForwarder<Tail...>::findMinAndMax;

using Head::findMinimum;
using NameForwarder<Tail...>::findMinimum;

using Head::findMaximum;
using NameForwarder<Tail...>::findMaximum;
};

} // namespace detail
#endif

//==============================================================================
/**
A collection of simple vector operations on arrays of floats, accelerated with
SIMD instructions where possible.
@tags{Audio}
*/
class JUCE_API FloatVectorOperations : public detail::NameForwarder<detail::FloatVectorOperationsBase<float, int>,
detail::FloatVectorOperationsBase<float, size_t>,
detail::FloatVectorOperationsBase<double, int>,
detail::FloatVectorOperationsBase<double, size_t>>
{
public:
static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, int num) noexcept;

static void JUCE_CALLTYPE convertFixedToFloat (float* dest, const int* src, float multiplier, size_t num) noexcept;

/** This method enables or disables the SSE/NEON flush-to-zero mode. */
static void JUCE_CALLTYPE enableFlushToZeroMode (bool shouldEnable) noexcept;

/** On Intel CPUs, this method enables the SSE flush-to-zero and denormalised-are-zero modes.
This effectively sets the DAZ and FZ bits of the MXCSR register. On arm CPUs this will
enable flush to zero mode.
It's a convenient thing to call before audio processing code where you really want to
avoid denormalisation performance hits.
*/
static void JUCE_CALLTYPE disableDenormalisedNumberSupport (bool shouldDisable = true) noexcept;

/** This method returns true if denormals are currently disabled. */
static bool JUCE_CALLTYPE areDenormalsDisabled() noexcept;

private:
friend ScopedNoDenormals;

static intptr_t JUCE_CALLTYPE getFpStatusRegister() noexcept;
static void JUCE_CALLTYPE setFpStatusRegister (intptr_t) noexcept;
};

//==============================================================================
/**
Helper class providing an RAII-based mechanism for temporarily disabling
denormals on your CPU.
@tags{Audio}
*/
class ScopedNoDenormals
{
public:
ScopedNoDenormals() noexcept;
~ScopedNoDenormals() noexcept;

private:
#if JUCE_USE_SSE_INTRINSICS || (JUCE_USE_ARM_NEON || defined (__arm64__) || defined (__aarch64__))
intptr_t fpsr;
#endif
};

} // namespace juce
Expand Up @@ -86,3 +86,14 @@
#include "sources/juce_ReverbAudioSource.cpp"
#include "sources/juce_ToneGeneratorAudioSource.cpp"
#include "synthesisers/juce_Synthesiser.cpp"

#include "midi/ump/juce_UMP.h"
#include "midi/ump/juce_UMPUtils.cpp"
#include "midi/ump/juce_UMPView.cpp"
#include "midi/ump/juce_UMPSysEx7.cpp"
#include "midi/ump/juce_UMPMidi1ToMidi2DefaultTranslator.cpp"

#if JUCE_UNIT_TESTS
#include "utilities/juce_ADSR_test.cpp"
#include "midi/ump/juce_UMPTests.cpp"
#endif
Expand Up @@ -32,11 +32,12 @@
ID: juce_audio_basics
vendor: juce
version: 6.0.7
version: 6.1.6
name: JUCE audio and MIDI data classes
description: Classes for audio buffer manipulation, midi message handling, synthesis, etc.
website: http://www.juce.com/juce
license: ISC
minimumCppStandard: 14
dependencies: juce_core
OSXFrameworks: Accelerate
Expand Down
File renamed without changes.
Expand Up @@ -119,29 +119,37 @@ void MidiBuffer::clear (int startSample, int numSamples)
data.removeRange ((int) (start - data.begin()), (int) (end - start));
}

void MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber)
bool MidiBuffer::addEvent (const MidiMessage& m, int sampleNumber)
{
addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber);
return addEvent (m.getRawData(), m.getRawDataSize(), sampleNumber);
}

void MidiBuffer::addEvent (const void* newData, int maxBytes, int sampleNumber)
bool MidiBuffer::addEvent (const void* newData, int maxBytes, int sampleNumber)
{
auto numBytes = MidiBufferHelpers::findActualEventLength (static_cast<const uint8*> (newData), maxBytes);

if (numBytes > 0)
if (numBytes <= 0)
return true;

if (std::numeric_limits<uint16>::max() < numBytes)
{
auto newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16);
auto offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin());
// This method only supports messages smaller than (1 << 16) bytes
return false;
}

data.insertMultiple (offset, 0, (int) newItemSize);
auto newItemSize = (size_t) numBytes + sizeof (int32) + sizeof (uint16);
auto offset = (int) (MidiBufferHelpers::findEventAfter (data.begin(), data.end(), sampleNumber) - data.begin());

auto* d = data.begin() + offset;
writeUnaligned<int32> (d, sampleNumber);
d += sizeof (int32);
writeUnaligned<uint16> (d, static_cast<uint16> (numBytes));
d += sizeof (uint16);
memcpy (d, newData, (size_t) numBytes);
}
data.insertMultiple (offset, 0, (int) newItemSize);

auto* d = data.begin() + offset;
writeUnaligned<int32> (d, sampleNumber);
d += sizeof (int32);
writeUnaligned<uint16> (d, static_cast<uint16> (numBytes));
d += sizeof (uint16);
memcpy (d, newData, (size_t) numBytes);

return true;
}

void MidiBuffer::addEvents (const MidiBuffer& otherBuffer,
Expand Down Expand Up @@ -201,13 +209,14 @@ MidiBufferIterator MidiBuffer::findNextSamplePosition (int samplePosition) const
}

//==============================================================================
JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
JUCE_BEGIN_IGNORE_WARNINGS_MSVC (4996)

MidiBuffer::Iterator::Iterator (const MidiBuffer& b) noexcept
: buffer (b), iterator (b.data.begin())
{
}

MidiBuffer::Iterator::~Iterator() noexcept {}

void MidiBuffer::Iterator::setNextSamplePosition (int samplePosition) noexcept
{
iterator = buffer.findNextSamplePosition (samplePosition);
Expand Down Expand Up @@ -236,6 +245,9 @@ bool MidiBuffer::Iterator::getNextEvent (MidiMessage& result, int& samplePositio
return true;
}

JUCE_END_IGNORE_WARNINGS_MSVC
JUCE_END_IGNORE_WARNINGS_GCC_LIKE

//==============================================================================
//==============================================================================
#if JUCE_UNIT_TESTS
Expand Down
Expand Up @@ -184,9 +184,11 @@ class JUCE_API MidiBuffer
If an event is added whose sample position is the same as one or more events
already in the buffer, the new event will be placed after the existing ones.
To retrieve events, use a MidiBufferIterator object
To retrieve events, use a MidiBufferIterator object.
Returns true on success, or false on failure.
*/
void addEvent (const MidiMessage& midiMessage, int sampleNumber);
bool addEvent (const MidiMessage& midiMessage, int sampleNumber);

/** Adds an event to the buffer from raw midi data.
Expand All @@ -202,9 +204,11 @@ class JUCE_API MidiBuffer
it'll actually only store 3 bytes. If the midi data is invalid, it might not
add an event at all.
To retrieve events, use a MidiBufferIterator object
To retrieve events, use a MidiBufferIterator object.
Returns true on success, or false on failure.
*/
void addEvent (const void* rawMidiData,
bool addEvent (const void* rawMidiData,
int maxBytesOfMidiData,
int sampleNumber);

Expand Down Expand Up @@ -269,28 +273,22 @@ class JUCE_API MidiBuffer
MidiBufferIterator findNextSamplePosition (int samplePosition) const noexcept;

//==============================================================================
/**
#ifndef DOXYGEN
/** This class is now deprecated in favour of MidiBufferIterator.
Used to iterate through the events in a MidiBuffer.
Note that altering the buffer while an iterator is using it will produce
undefined behaviour.
@see MidiBuffer
*/
class JUCE_API Iterator
class [[deprecated]] JUCE_API Iterator
{
public:
//==============================================================================
/** Creates an Iterator for this MidiBuffer.
This class has been deprecated in favour of MidiBufferIterator.
*/
JUCE_DEPRECATED (Iterator (const MidiBuffer&) noexcept);

/** Creates a copy of an iterator. */
Iterator (const Iterator&) = default;

/** Destructor. */
~Iterator() noexcept;
/** Creates an Iterator for this MidiBuffer. */
Iterator (const MidiBuffer& b) noexcept;

//==============================================================================
/** Repositions the iterator so that the next event retrieved will be the first
Expand Down Expand Up @@ -332,6 +330,7 @@ class JUCE_API MidiBuffer
const MidiBuffer& buffer;
MidiBufferIterator iterator;
};
#endif

/** The raw data holding this buffer.
Obviously access to this data is provided at your own risk. Its internal format could
Expand Down
File renamed without changes.
Expand Up @@ -264,7 +264,6 @@ namespace MidiFileHelpers

//==============================================================================
MidiFile::MidiFile() : timeFormat ((short) (unsigned short) 0xe728) {}
MidiFile::~MidiFile() {}

MidiFile::MidiFile (const MidiFile& other) : timeFormat (other.timeFormat)
{
Expand Down Expand Up @@ -356,7 +355,9 @@ double MidiFile::getLastTimestamp() const
}

//==============================================================================
bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs)
bool MidiFile::readFrom (InputStream& sourceStream,
bool createMatchingNoteOffs,
int* fileType)
{
clear();
MemoryBlock data;
Expand Down Expand Up @@ -405,7 +406,12 @@ bool MidiFile::readFrom (InputStream& sourceStream, bool createMatchingNoteOffs)
d += chunkSize;
}

return size == 0;
const auto successful = (size == 0);

if (successful && fileType != nullptr)
*fileType = header.fileType;

return successful;
}

void MidiFile::readNextTrack (const uint8* data, int size, bool createMatchingNoteOffs)
Expand Down Expand Up @@ -778,7 +784,9 @@ struct MidiFileTest : public UnitTest
MemoryInputStream is (os.getData(), os.getDataSize(), false);
MidiFile mf;

if (mf.readFrom (is))
int fileType = 0;

if (mf.readFrom (is, true, &fileType))
return mf;

return {};
Expand Down
Expand Up @@ -45,9 +45,6 @@ class JUCE_API MidiFile
/** Creates an empty MidiFile object. */
MidiFile();

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

/** Creates a copy of another MidiFile. */
MidiFile (const MidiFile&);

Expand Down Expand Up @@ -136,7 +133,7 @@ class JUCE_API MidiFile
*/
void findAllTimeSigEvents (MidiMessageSequence& timeSigEvents) const;

/** Makes a list of all the time-signature meta-events from all tracks in the midi file.
/** Makes a list of all the key-signature meta-events from all tracks in the midi file.
@param keySigEvents a list to which all the events will be added
*/
void findAllKeySigEvents (MidiMessageSequence& keySigEvents) const;
Expand All @@ -160,10 +157,14 @@ class JUCE_API MidiFile
@param createMatchingNoteOffs if true, any missing note-offs for previous note-ons will
be automatically added at the end of the file by calling
MidiMessageSequence::updateMatchedPairs on each track.
@param midiFileType if not nullptr, the integer at this address will be set
to 0, 1, or 2 depending on the type of the midi file
@returns true if the stream was read successfully
*/
bool readFrom (InputStream& sourceStream, bool createMatchingNoteOffs = true);
bool readFrom (InputStream& sourceStream,
bool createMatchingNoteOffs = true,
int* midiFileType = nullptr);

/** Writes the midi tracks as a standard midi file.
The midiFileType value is written as the file's format type, which can be 0, 1
Expand Down
Expand Up @@ -28,10 +28,6 @@ MidiKeyboardState::MidiKeyboardState()
zerostruct (noteStates);
}

MidiKeyboardState::~MidiKeyboardState()
{
}

//==============================================================================
void MidiKeyboardState::reset()
{
Expand Down
Expand Up @@ -43,7 +43,6 @@ class JUCE_API MidiKeyboardState
public:
//==============================================================================
MidiKeyboardState();
~MidiKeyboardState();

//==============================================================================
/** Resets the state of the object.
Expand Down
Expand Up @@ -287,11 +287,14 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& other)
{
if (other.isHeapAllocated())
{
if (isHeapAllocated())
packedData.allocatedData = static_cast<uint8*> (std::realloc (packedData.allocatedData, (size_t) other.size));
else
packedData.allocatedData = static_cast<uint8*> (std::malloc ((size_t) other.size));
auto* newStorage = static_cast<uint8*> (isHeapAllocated()
? std::realloc (packedData.allocatedData, (size_t) other.size)
: std::malloc ((size_t) other.size));

if (newStorage == nullptr)
throw std::bad_alloc{}; // The midi message has not been adjusted at this point

packedData.allocatedData = newStorage;
memcpy (packedData.allocatedData, other.packedData.allocatedData, (size_t) other.size);
}
else
Expand Down
Expand Up @@ -102,7 +102,8 @@ class JUCE_API MidiMessage
double timeStamp = 0,
bool sysexHasEmbeddedLength = true);

/** Creates an active-sense message.
/** Creates an empty sysex message.
Since the MidiMessage has to contain a valid message, this default constructor
just initialises it with an empty sysex message.
*/
Expand Down Expand Up @@ -856,17 +857,16 @@ class JUCE_API MidiMessage


//==============================================================================
#ifndef DOXYGEN
/** Reads a midi variable-length integer.
This signature has been deprecated in favour of the safer
readVariableLengthValue.
The `data` argument indicates the data to read the number from,
and `numBytesUsed` is used as an out-parameter to indicate the number
of bytes that were read.
*/
JUCE_DEPRECATED (static int readVariableLengthVal (const uint8* data,
int& numBytesUsed) noexcept);
[[deprecated ("This signature has been deprecated in favour of the safer readVariableLengthValue.")]]
static int readVariableLengthVal (const uint8* data, int& numBytesUsed) noexcept;
#endif

/** Holds information about a variable-length value which was parsed
from a stream of bytes.
Expand Down

Large diffs are not rendered by default.

Expand Up @@ -53,9 +53,6 @@ class JUCE_API MidiMessageSequence
/** Move assignment operator */
MidiMessageSequence& operator= (MidiMessageSequence&&) noexcept;

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

//==============================================================================
/** Structure used to hold midi events in the sequence.
Expand All @@ -68,9 +65,6 @@ class JUCE_API MidiMessageSequence
{
public:
//==============================================================================
/** Destructor. */
~MidiEventHolder();

/** The message itself, whose timestamp is used to specify the event's time. */
MidiMessage message;

Expand Down Expand Up @@ -277,6 +271,21 @@ class JUCE_API MidiMessageSequence
As well as controllers, it will also recreate the midi program number
and pitch bend position.
This function has special handling for the "bank select" and "data entry"
controllers (0x00, 0x20, 0x06, 0x26, 0x60, 0x61, 0x62, 0x63, 0x64, 0x65).
If the sequence contains multiple bank select and program change messages,
only the bank select messages immediately preceding the final program change
message will be kept.
All "data increment" and "data decrement" messages will be retained. Some hardware will
ignore the requested increment/decrement values, so retaining all messages is the only
way to ensure compatibility with all hardware.
"Parameter number" changes will be slightly condensed. Only the parameter number
events immediately preceding each data entry event will be kept. The parameter number
will also be set to its final value at the end of the sequence, if necessary.
@param channelNumber the midi channel to look for, in the range 1 to 16. Controllers
for other channels will be ignored.
@param time the time at which you want to find out the state - there are
Expand Down
File renamed without changes.
File renamed without changes.
Expand Up @@ -20,22 +20,28 @@
==============================================================================
*/

#if JUCE_PROJUCER_LIVE_BUILD && (defined (__APPLE_CPP__) || defined(__APPLE_CC__))

// This hack is a workaround for a bug (?) in Apple's 10.11 SDK headers
// which cause some configurations of Clang to throw out a spurious error..
#include <CoreFoundation/CFAvailability.h>
#undef CF_OPTIONS
#define CF_OPTIONS(_type, _name) _type _name; enum

// This is a workaround for the Xcode 9 version of NSUUID.h causing some errors
// in the live-build engine.
#define _Nullable
#define _Nonnull

// A workaround for compiling the 10.15 headers with an older compiler version
#undef API_UNAVAILABLE_BEGIN
#define API_UNAVAILABLE_BEGIN(...)
#undef API_UNAVAILABLE_END
#define API_UNAVAILABLE_END
#include "../juce_MidiDataConcatenator.h"

#include "juce_UMPProtocols.h"
#include "juce_UMPUtils.h"
#include "juce_UMPacket.h"
#include "juce_UMPSysEx7.h"
#include "juce_UMPView.h"
#include "juce_UMPIterator.h"
#include "juce_UMPackets.h"
#include "juce_UMPFactory.h"
#include "juce_UMPConversion.h"
#include "juce_UMPMidi1ToBytestreamTranslator.h"
#include "juce_UMPMidi1ToMidi2DefaultTranslator.h"
#include "juce_UMPConverters.h"
#include "juce_UMPDispatcher.h"
#include "juce_UMPReceiver.h"

#ifndef DOXYGEN

namespace juce
{
namespace ump = universal_midi_packets;
}

#endif
Expand Up @@ -20,6 +20,8 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
Expand Down Expand Up @@ -324,3 +326,5 @@ struct Conversion

}
}

#endif
Expand Up @@ -20,6 +20,8 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
Expand Down Expand Up @@ -163,3 +165,5 @@ namespace universal_midi_packets
};
}
}

#endif
Expand Up @@ -20,6 +20,8 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
Expand Down Expand Up @@ -108,7 +110,13 @@ class BytestreamToUMPDispatcher
{
using CallbackPtr = decltype (std::addressof (callback));

struct Callback
#if JUCE_MINGW
#define JUCE_MINGW_HIDDEN_VISIBILITY __attribute__ ((visibility ("hidden")))
#else
#define JUCE_MINGW_HIDDEN_VISIBILITY
#endif

struct JUCE_MINGW_HIDDEN_VISIBILITY Callback
{
Callback (BytestreamToUMPDispatcher& d, CallbackPtr c)
: dispatch (d), callbackPtr (c) {}
Expand All @@ -127,6 +135,8 @@ class BytestreamToUMPDispatcher
CallbackPtr callbackPtr = nullptr;
};

#undef JUCE_MINGW_HIDDEN_VISIBILITY

Callback inputCallback { *this, &callback };
concatenator.pushMidiData (begin, int (end - begin), timestamp, (void*) nullptr, inputCallback);
}
Expand Down Expand Up @@ -188,3 +198,5 @@ class ToBytestreamDispatcher

}
}

#endif
Expand Up @@ -20,6 +20,8 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
Expand Down Expand Up @@ -532,3 +534,5 @@ struct Factory

}
}

#endif
Expand Up @@ -20,6 +20,8 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
Expand Down Expand Up @@ -54,7 +56,7 @@ class Iterator
using value_type = View;
using reference = const View&;
using pointer = const View*;
using iterator_category = std::input_iterator_tag;
using iterator_category = std::forward_iterator_tag;

/** Moves this iterator to the next packet in the range. */
Iterator& operator++() noexcept
Expand Down Expand Up @@ -124,3 +126,5 @@ class Iterator

}
}

#endif
Expand Up @@ -20,6 +20,8 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
Expand Down Expand Up @@ -211,3 +213,5 @@ class Midi1ToBytestreamTranslator

}
}

#endif
File renamed without changes.
Expand Up @@ -20,6 +20,8 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
Expand Down Expand Up @@ -185,3 +187,5 @@ class Midi1ToMidi2DefaultTranslator

}
}

#endif
Expand Up @@ -20,6 +20,8 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
Expand All @@ -42,3 +44,5 @@ enum class MidiProtocol

}
}

#endif
Expand Up @@ -20,13 +20,17 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
{

/**
A base class for classes which receive Universal MIDI Packets from an input.
@tags{Audio}
*/
struct Receiver
{
Expand All @@ -38,3 +42,5 @@ struct Receiver

}
}

#endif
Expand Up @@ -39,12 +39,12 @@ SysEx7::PacketBytes SysEx7::getDataBytes (const PacketX2& packet)

return
{
{ packet.getU8<2>(),
packet.getU8<3>(),
packet.getU8<4>(),
packet.getU8<5>(),
packet.getU8<6>(),
packet.getU8<7>() },
{ { packet.getU8<2>(),
packet.getU8<3>(),
packet.getU8<4>(),
packet.getU8<5>(),
packet.getU8<6>(),
packet.getU8<7>() } },
jmin (numBytes, maxBytes)
};
}
Expand Down
Expand Up @@ -20,13 +20,15 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
{

/**
This struct acts as a single-file namespace for Univeral MIDI Packet
This struct acts as a single-file namespace for Universal MIDI Packet
functionality related to 7-bit SysEx.
@tags{Audio}
Expand Down Expand Up @@ -71,3 +73,5 @@ struct SysEx7

}
}

#endif
Expand Up @@ -25,8 +25,6 @@ namespace juce
namespace universal_midi_packets
{

#if JUCE_UNIT_TESTS

constexpr uint8_t operator""_u8 (unsigned long long int i) { return static_cast<uint8_t> (i); }
constexpr uint16_t operator""_u16 (unsigned long long int i) { return static_cast<uint16_t> (i); }
constexpr uint32_t operator""_u32 (unsigned long long int i) { return static_cast<uint32_t> (i); }
Expand Down Expand Up @@ -697,7 +695,7 @@ class UniversalMidiPacketTests : public UnitTest
for (const auto typecode : typecodesX1)
{
Packets p;
p.add (PacketX1 { (uint32_t) (typecode << 0x1c | (random.nextInt64() & 0xffffff)) });
p.add (PacketX1 { (uint32_t) ((int64_t) typecode << 0x1c | (random.nextInt64() & 0xffffff)) });

checkMidi2ToMidi1Conversion (p, p);
}
Expand Down Expand Up @@ -966,8 +964,10 @@ class UniversalMidiPacketTests : public UnitTest
template <typename Fn>
void forEachNonSysExTestMessage (Random& random, Fn&& fn)
{
for (uint8_t firstByte = 0x80; firstByte != 0x00; ++firstByte)
for (uint16_t counter = 0x80; counter != 0x100; ++counter)
{
const auto firstByte = (uint8_t) counter;

if (firstByte == 0xf0 || firstByte == 0xf7)
continue; // sysEx is tested separately

Expand All @@ -990,9 +990,9 @@ class UniversalMidiPacketTests : public UnitTest
}
}

#if JUCE_WINDOWS
#if JUCE_WINDOWS && ! JUCE_MINGW
#define JUCE_CHECKED_ITERATOR(msg, size) \
stdext::checked_array_iterator<typename std::remove_reference<decltype (msg)>::type> ((msg), (size))
stdext::checked_array_iterator<typename std::remove_reference<decltype (msg)>::type> ((msg), (size_t) (size))
#else
#define JUCE_CHECKED_ITERATOR(msg, size) (msg)
#endif
Expand All @@ -1014,7 +1014,5 @@ class UniversalMidiPacketTests : public UnitTest

static UniversalMidiPacketTests universalMidiPacketTests;

#endif

}
}
File renamed without changes.
Expand Up @@ -20,6 +20,8 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
Expand Down Expand Up @@ -111,3 +113,5 @@ struct Utils

}
}

#endif
File renamed without changes.
Expand Up @@ -20,6 +20,8 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
Expand Down Expand Up @@ -86,3 +88,5 @@ class View

}
}

#endif
Expand Up @@ -20,13 +20,17 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
{

/**
Holds a single Universal MIDI Packet.
@tags{Audio}
*/
template <size_t numWords>
class Packet
Expand Down Expand Up @@ -185,3 +189,5 @@ using PacketX4 = Packet<4>;

}
}

#endif
Expand Up @@ -20,6 +20,8 @@
==============================================================================
*/

#ifndef DOXYGEN

namespace juce
{
namespace universal_midi_packets
Expand Down Expand Up @@ -90,3 +92,5 @@ class Packets

}
}

#endif
Expand Up @@ -43,16 +43,20 @@ MPEInstrument::MPEInstrument() noexcept
mpeInstrumentFill (isMemberChannelSustained, false);

pitchbendDimension.value = &MPENote::pitchbend;
pressureDimension.value = &MPENote::pressure;
timbreDimension.value = &MPENote::timbre;
pressureDimension.value = &MPENote::pressure;
timbreDimension.value = &MPENote::timbre;

resetLastReceivedValues();

legacyMode.isEnabled = false;
legacyMode.pitchbendRange = 2;
legacyMode.channelRange = allChannels;
}

MPEInstrument::MPEInstrument (MPEZoneLayout layout)
: MPEInstrument()
{
setZoneLayout (layout);
}

MPEInstrument::~MPEInstrument() = default;

//==============================================================================
Expand Down Expand Up @@ -84,21 +88,30 @@ void MPEInstrument::setZoneLayout (MPEZoneLayout newLayout)

const ScopedLock sl (lock);
legacyMode.isEnabled = false;
zoneLayout = newLayout;

resetLastReceivedValues();
if (zoneLayout != newLayout)
{
zoneLayout = newLayout;
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); });
}
}

//==============================================================================
void MPEInstrument::enableLegacyMode (int pitchbendRange, Range<int> channelRange)
{
if (legacyMode.isEnabled)
return;

releaseAllNotes();

const ScopedLock sl (lock);

legacyMode.isEnabled = true;
legacyMode.pitchbendRange = pitchbendRange;
legacyMode.channelRange = channelRange;

zoneLayout.clearAllZones();
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); });
}

bool MPEInstrument::isLegacyModeEnabled() const noexcept
Expand All @@ -117,7 +130,12 @@ void MPEInstrument::setLegacyModeChannelRange (Range<int> channelRange)

releaseAllNotes();
const ScopedLock sl (lock);
legacyMode.channelRange = channelRange;

if (legacyMode.channelRange != channelRange)
{
legacyMode.channelRange = channelRange;
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); });
}
}

int MPEInstrument::getLegacyModePitchbendRange() const noexcept
Expand All @@ -131,7 +149,12 @@ void MPEInstrument::setLegacyModePitchbendRange (int pitchbendRange)

releaseAllNotes();
const ScopedLock sl (lock);
legacyMode.pitchbendRange = pitchbendRange;

if (legacyMode.pitchbendRange != pitchbendRange)
{
legacyMode.pitchbendRange = pitchbendRange;
listeners.call ([=] (Listener& l) { l.zoneLayoutChanged(); });
}
}

//==============================================================================
Expand Down Expand Up @@ -242,7 +265,7 @@ void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& me

if (legacyMode.isEnabled && legacyMode.channelRange.contains (message.getChannel()))
{
for (auto i = notes.size(); --i >= 0;)
for (int i = notes.size(); --i >= 0;)
{
auto& note = notes.getReference (i);

Expand All @@ -260,7 +283,7 @@ void MPEInstrument::processMidiResetAllControllersMessage (const MidiMessage& me
auto zone = (message.getChannel() == 1 ? zoneLayout.getLowerZone()
: zoneLayout.getUpperZone());

for (auto i = notes.size(); --i >= 0;)
for (int i = notes.size(); --i >= 0;)
{
auto& note = notes.getReference (i);

Expand Down Expand Up @@ -348,11 +371,11 @@ void MPEInstrument::noteOff (int midiChannel,
int midiNoteNumber,
MPEValue midiNoteOffVelocity)
{
const ScopedLock sl (lock);

if (notes.isEmpty() || ! isUsingChannel (midiChannel))
return;

const ScopedLock sl (lock);

if (auto* note = getNotePtr (midiChannel, midiNoteNumber))
{
note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off;
Expand Down Expand Up @@ -401,7 +424,7 @@ void MPEInstrument::polyAftertouch (int midiChannel, int midiNoteNumber, MPEValu
{
const ScopedLock sl (lock);

for (auto i = notes.size(); --i >= 0;)
for (int i = notes.size(); --i >= 0;)
{
auto& note = notes.getReference (i);

Expand Down Expand Up @@ -435,7 +458,7 @@ void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, M
{
if (dimension.trackingMode == allNotesOnChannel)
{
for (auto i = notes.size(); --i >= 0;)
for (int i = notes.size(); --i >= 0;)
{
auto& note = notes.getReference (i);

Expand Down Expand Up @@ -464,7 +487,7 @@ void MPEInstrument::updateDimensionMaster (bool isLowerZone, MPEDimension& dimen
if (! zone.isActive())
return;

for (auto i = notes.size(); --i >= 0;)
for (int i = notes.size(); --i >= 0;)
{
auto& note = notes.getReference (i);

Expand Down Expand Up @@ -573,7 +596,7 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool
auto zone = (midiChannel == 1 ? zoneLayout.getLowerZone()
: zoneLayout.getUpperZone());

for (auto i = notes.size(); --i >= 0;)
for (int i = notes.size(); --i >= 0;)
{
auto& note = notes.getReference (i);

Expand Down Expand Up @@ -605,11 +628,15 @@ void MPEInstrument::handleSustainOrSostenuto (int midiChannel, bool isDown, bool
if (! legacyMode.isEnabled)
{
if (zone.isLowerZone())
for (auto i = zone.getFirstMemberChannel(); i <= zone.getLastMemberChannel(); ++i)
{
for (int i = zone.getFirstMemberChannel(); i <= zone.getLastMemberChannel(); ++i)
isMemberChannelSustained[i - 1] = isDown;
}
else
for (auto i = zone.getFirstMemberChannel(); i >= zone.getLastMemberChannel(); --i)
{
for (int i = zone.getFirstMemberChannel(); i >= zone.getLastMemberChannel(); --i)
isMemberChannelSustained[i - 1] = isDown;
}
}
}
}
Expand Down Expand Up @@ -664,6 +691,17 @@ MPENote MPEInstrument::getNote (int index) const noexcept
return notes[index];
}

MPENote MPEInstrument::getNoteWithID (uint16 noteID) const noexcept
{
const ScopedLock sl (lock);

for (auto& note : notes)
if (note.noteID == noteID)
return note;

return {};
}

//==============================================================================
MPENote MPEInstrument::getMostRecentNote (int midiChannel) const noexcept
{
Expand Down Expand Up @@ -727,6 +765,8 @@ MPENote* MPEInstrument::getNotePtr (int midiChannel, TrackingMode mode) noexcept
//==============================================================================
const MPENote* MPEInstrument::getLastNotePlayedPtr (int midiChannel) const noexcept
{
const ScopedLock sl (lock);

for (auto i = notes.size(); --i >= 0;)
{
auto& note = notes.getReference (i);
Expand Down Expand Up @@ -833,6 +873,7 @@ class MPEInstrumentTests : public UnitTest
testLayout.setUpperZone (6);
}

JUCE_BEGIN_IGNORE_WARNINGS_MSVC (6262)
void runTest() override
{
beginTest ("initial zone layout");
Expand Down Expand Up @@ -2145,6 +2186,7 @@ class MPEInstrumentTests : public UnitTest
}
}
}
JUCE_END_IGNORE_WARNINGS_MSVC

private:
//==============================================================================
Expand Down
Expand Up @@ -38,10 +38,8 @@ namespace juce
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.
The class has a Listener class that can be used to react to note and
state changes and trigger some functionality for your application.
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,
Expand All @@ -59,11 +57,14 @@ class JUCE_API MPEInstrument
This will construct an MPE instrument with inactive lower and upper zones.
In order to process incoming MIDI, call setZoneLayout, define the layout
via MIDI RPN messages, or set the instrument to legacy mode.
In order to process incoming MIDI messages call setZoneLayout, use the MPEZoneLayout
constructor, define the layout via MIDI RPN messages, or set the instrument to legacy mode.
*/
MPEInstrument() noexcept;

/** Constructs an MPE instrument with the specified zone layout. */
MPEInstrument (MPEZoneLayout layout);

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

Expand Down Expand Up @@ -229,6 +230,9 @@ class JUCE_API MPEInstrument
*/
MPENote getNote (int midiChannel, int midiNoteNumber) const noexcept;

/** Returns the note with a given ID. */
MPENote getNoteWithID (uint16 noteID) 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
Expand All @@ -244,8 +248,8 @@ class JUCE_API MPEInstrument
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.
/** Derive from this class to be informed about any changes in the MPE notes played
by this instrument, and any changes to its zone layout.
Note: This listener type receives its callbacks immediately, and not
via the message thread (so you might be for example in the MIDI thread).
Expand Down Expand Up @@ -297,6 +301,11 @@ class JUCE_API MPEInstrument
and should therefore stop playing.
*/
virtual void noteReleased (MPENote finishedNote) { ignoreUnused (finishedNote); }

/** Implement this callback to be informed whenever the MPE zone layout
or legacy mode settings of this instrument have been changed.
*/
virtual void zoneLayoutChanged() {}
};

//==============================================================================
Expand All @@ -307,7 +316,9 @@ class JUCE_API MPEInstrument
void removeListener (Listener* listenerToRemove);

//==============================================================================
/** Puts the instrument into legacy mode.
/** Puts the instrument into legacy mode. If legacy mode is already enabled this method
does nothing.
As a side effect, this will discard all currently playing notes,
and call noteReleased for all of them.
Expand Down Expand Up @@ -360,9 +371,9 @@ class JUCE_API MPEInstrument

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

struct MPEDimension
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
Expand Up @@ -115,7 +115,7 @@ struct JUCE_API MPENote
*/
MPEValue noteOnVelocity { MPEValue::minValue() };

/** Current per-note pitchbend of the note (in units of MIDI pitchwheel
/** 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,
Expand Down
Expand Up @@ -25,12 +25,10 @@ namespace juce

MPESynthesiser::MPESynthesiser()
{
MPEZoneLayout zoneLayout;
zoneLayout.setLowerZone (15);
setZoneLayout (zoneLayout);
}

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

Expand Down Expand Up @@ -129,7 +127,7 @@ void MPESynthesiser::noteReleased (MPENote finishedNote)
{
auto* voice = voices.getUnchecked (i);

if (voice->isCurrentlyPlayingNote(finishedNote))
if (voice->isCurrentlyPlayingNote (finishedNote))
stopVoice (voice, finishedNote, true);
}
}
Expand Down Expand Up @@ -305,11 +303,16 @@ 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 (auto* voice : voices)
{
voice->currentlyPlayingNote.noteOffVelocity = MPEValue::from7BitInt (64); // some reasonable number
voice->currentlyPlayingNote.keyState = MPENote::off;

voice->noteStopped (allowTailOff);
}
}

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

//==============================================================================
Expand Down
Expand Up @@ -65,11 +65,10 @@ class JUCE_API MPESynthesiser : public MPESynthesiserBase
/** 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* instrumentToUse);
MPESynthesiser (MPEInstrument& instrumentToUse);

/** Destructor. */
~MPESynthesiser() override;
Expand Down Expand Up @@ -303,7 +302,7 @@ class JUCE_API MPESynthesiser : public MPESynthesiserBase

private:
//==============================================================================
bool shouldStealVoices = false;
std::atomic<bool> shouldStealVoices { false };
uint32 lastNoteOnCounter = 0;

JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser)
Expand Down
Expand Up @@ -24,80 +24,79 @@ namespace juce
{

MPESynthesiserBase::MPESynthesiserBase()
: instrument (new MPEInstrument)
: instrument (defaultInstrument)
{
instrument->addListener (this);
instrument.addListener (this);
}

MPESynthesiserBase::MPESynthesiserBase (MPEInstrument* inst)
MPESynthesiserBase::MPESynthesiserBase (MPEInstrument& inst)
: instrument (inst)
{
jassert (instrument != nullptr);
instrument->addListener (this);
instrument.addListener (this);
}

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

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

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

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

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

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

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

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

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

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

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

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

//==============================================================================
Expand Down Expand Up @@ -148,7 +147,7 @@ void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate)
if (sampleRate != newRate)
{
const ScopedLock sl (noteStateLock);
instrument->releaseAllNotes();
instrument.releaseAllNotes();
sampleRate = newRate;
}
}
Expand Down
Expand Up @@ -52,13 +52,12 @@ struct JUCE_API MPESynthesiserBase : public MPEInstrument::Listener

/** 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.
If you use this constructor, the synthesiser will use the provided instrument
object 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);
MPESynthesiserBase (MPEInstrument& instrument);

//==============================================================================
/** Returns the synthesiser's internal MPE zone layout.
Expand Down Expand Up @@ -200,10 +199,12 @@ struct JUCE_API MPESynthesiserBase : public MPEInstrument::Listener
protected:
//==============================================================================
/** @internal */
std::unique_ptr<MPEInstrument> instrument;
MPEInstrument& instrument;

private:
//==============================================================================
MPEInstrument defaultInstrument { MPEZone (MPEZone::Type::lower, 15) };

CriticalSection noteStateLock;
double sampleRate = 0.0;
int minimumSubBlockSize = 32;
Expand Down
File renamed without changes.
File renamed without changes.
Expand Up @@ -52,25 +52,25 @@ int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept
if (numChannels <= 1)
return firstChannel;

for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
for (int ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
{
if (midiChannels[ch].isFree() && midiChannels[ch].lastNotePlayed == noteNumber)
if (midiChannels[(size_t) ch].isFree() && midiChannels[(size_t) ch].lastNotePlayed == noteNumber)
{
midiChannelLastAssigned = ch;
midiChannels[ch].notes.add (noteNumber);
midiChannels[(size_t) ch].notes.add (noteNumber);
return ch;
}
}

for (auto ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement)
for (int ch = midiChannelLastAssigned + channelIncrement; ; ch += channelIncrement)
{
if (ch == lastChannel + channelIncrement) // loop wrap-around
ch = firstChannel;

if (midiChannels[ch].isFree())
if (midiChannels[(size_t) ch].isFree())
{
midiChannelLastAssigned = ch;
midiChannels[ch].notes.add (noteNumber);
midiChannels[(size_t) ch].notes.add (noteNumber);
return ch;
}

Expand All @@ -79,11 +79,21 @@ int MPEChannelAssigner::findMidiChannelForNewNote (int noteNumber) noexcept
}

midiChannelLastAssigned = findMidiChannelPlayingClosestNonequalNote (noteNumber);
midiChannels[midiChannelLastAssigned].notes.add (noteNumber);
midiChannels[(size_t) midiChannelLastAssigned].notes.add (noteNumber);

return midiChannelLastAssigned;
}

int MPEChannelAssigner::findMidiChannelForExistingNote (int noteNumber) noexcept
{
const auto iter = std::find_if (midiChannels.cbegin(), midiChannels.cend(), [&] (auto& ch)
{
return std::find (ch.notes.begin(), ch.notes.end(), noteNumber) != ch.notes.end();
});

return iter != midiChannels.cend() ? (int) std::distance (midiChannels.cbegin(), iter) : -1;
}

void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel)
{
const auto removeNote = [] (MidiChannel& ch, int noteNum)
Expand All @@ -99,7 +109,7 @@ void MPEChannelAssigner::noteOff (int noteNumber, int midiChannel)

if (midiChannel >= 0 && midiChannel <= 16)
{
removeNote (midiChannels[midiChannel], noteNumber);
removeNote (midiChannels[(size_t) midiChannel], noteNumber);
return;
}

Expand All @@ -126,9 +136,9 @@ int MPEChannelAssigner::findMidiChannelPlayingClosestNonequalNote (int noteNumbe
auto channelWithClosestNote = firstChannel;
int closestNoteDistance = 127;

for (auto ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
for (int ch = firstChannel; (isLegacy || zone->isLowerZone() ? ch <= lastChannel : ch >= lastChannel); ch += channelIncrement)
{
for (auto note : midiChannels[ch].notes)
for (auto note : midiChannels[(size_t) ch].notes)
{
auto noteDistance = std::abs (note - noteNumber);

Expand Down Expand Up @@ -296,24 +306,35 @@ struct MPEUtilsUnitTests : public UnitTest
// check that channels are assigned in correct order
int noteNum = 60;
for (int ch = 2; ch <= 16; ++ch)
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
{
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch);
expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch);

++noteNum;
}

// check that note-offs are processed
channelAssigner.noteOff (60);
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 2);
expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 2);

channelAssigner.noteOff (61);
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 3);
expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 3);

// check that assigned channel was last to play note
channelAssigner.noteOff (65);
channelAssigner.noteOff (66);
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 8);
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 8);
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 7);

// find closest channel playing nonequal note
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 2);

// all notes off
channelAssigner.allNotesOff();
Expand All @@ -323,10 +344,16 @@ struct MPEUtilsUnitTests : public UnitTest
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 7);
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 2);
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 8);
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 7);
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 2);

// normal assignment
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 3);
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 4);
expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 3);
expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 4);
}

// upper
Expand All @@ -339,24 +366,35 @@ struct MPEUtilsUnitTests : public UnitTest
// check that channels are assigned in correct order
int noteNum = 60;
for (int ch = 15; ch >= 1; --ch)
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
{
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch);
expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch);

++noteNum;
}

// check that note-offs are processed
channelAssigner.noteOff (60);
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 15);
expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 15);

channelAssigner.noteOff (61);
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 14);
expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 14);

// check that assigned channel was last to play note
channelAssigner.noteOff (65);
channelAssigner.noteOff (66);
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 9);
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 9);
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 10);

// find closest channel playing nonequal note
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 1);
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 15);

// all notes off
channelAssigner.allNotesOff();
Expand All @@ -366,10 +404,16 @@ struct MPEUtilsUnitTests : public UnitTest
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 10);
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 1);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 15);
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 9);
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 10);
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 1);
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 15);

// normal assignment
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 14);
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 13);
expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 14);
expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 13);
}

// legacy
Expand All @@ -379,24 +423,35 @@ struct MPEUtilsUnitTests : public UnitTest
// check that channels are assigned in correct order
int noteNum = 60;
for (int ch = 1; ch <= 16; ++ch)
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum++), ch);
{
expectEquals (channelAssigner.findMidiChannelForNewNote (noteNum), ch);
expectEquals (channelAssigner.findMidiChannelForExistingNote (noteNum), ch);

++noteNum;
}

// check that note-offs are processed
channelAssigner.noteOff (60);
expectEquals (channelAssigner.findMidiChannelForNewNote (60), 1);
expectEquals (channelAssigner.findMidiChannelForExistingNote (60), 1);

channelAssigner.noteOff (61);
expectEquals (channelAssigner.findMidiChannelForNewNote (61), 2);
expectEquals (channelAssigner.findMidiChannelForExistingNote (61), 2);

// check that assigned channel was last to play note
channelAssigner.noteOff (65);
channelAssigner.noteOff (66);
expectEquals (channelAssigner.findMidiChannelForNewNote (66), 7);
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 7);
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 6);

// find closest channel playing nonequal note
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 1);

// all notes off
channelAssigner.allNotesOff();
Expand All @@ -406,10 +461,16 @@ struct MPEUtilsUnitTests : public UnitTest
expectEquals (channelAssigner.findMidiChannelForNewNote (65), 6);
expectEquals (channelAssigner.findMidiChannelForNewNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForNewNote (55), 1);
expectEquals (channelAssigner.findMidiChannelForExistingNote (66), 7);
expectEquals (channelAssigner.findMidiChannelForExistingNote (65), 6);
expectEquals (channelAssigner.findMidiChannelForExistingNote (80), 16);
expectEquals (channelAssigner.findMidiChannelForExistingNote (55), 1);

// normal assignment
expectEquals (channelAssigner.findMidiChannelForNewNote (101), 2);
expectEquals (channelAssigner.findMidiChannelForNewNote (20), 3);
expectEquals (channelAssigner.findMidiChannelForExistingNote (101), 2);
expectEquals (channelAssigner.findMidiChannelForExistingNote (20), 3);
}
}

Expand Down
Expand Up @@ -63,6 +63,11 @@ class MPEChannelAssigner
*/
int findMidiChannelForNewNote (int noteNumber) noexcept;

/** If a note has been added using findMidiChannelForNewNote() this will return the channel
to which it was assigned, otherwise it will return -1.
*/
int findMidiChannelForExistingNote (int initialNoteOnNumber) noexcept;

/** You must call this method for all note-offs that you receive so that this class
can keep track of the currently playing notes internally.
Expand All @@ -86,7 +91,7 @@ class MPEChannelAssigner
int lastNotePlayed = -1;
bool isFree() const noexcept { return notes.isEmpty(); }
};
MidiChannel midiChannels[17];
std::array<MidiChannel, 17> midiChannels;

//==============================================================================
int findMidiChannelPlayingClosestNonequalNote (int noteNumber) noexcept;
Expand Down
Expand Up @@ -43,6 +43,18 @@ MPEValue MPEValue::from14BitInt (int value) noexcept
return { value };
}

MPEValue MPEValue::fromUnsignedFloat (float value) noexcept
{
jassert (0.0f <= value && value <= 1.0f);
return { roundToInt (value * 16383.0f) };
}

MPEValue MPEValue::fromSignedFloat (float value) noexcept
{
jassert (-1.0f <= value && value <= 1.0f);
return { roundToInt (((value + 1.0f) * 16383.0f) / 2.0f) };
}

//==============================================================================
MPEValue MPEValue::minValue() noexcept { return MPEValue::from7BitInt (0); }
MPEValue MPEValue::centreValue() noexcept { return MPEValue::from7BitInt (64); }
Expand Down Expand Up @@ -121,26 +133,34 @@ class MPEValueTests : public UnitTest

beginTest ("zero/minimum value");
{
expectValuesConsistent (MPEValue::from7BitInt (0), 0, 0, -1.0f, 0.0f);
expectValuesConsistent (MPEValue::from14BitInt (0), 0, 0, -1.0f, 0.0f);
expectValuesConsistent (MPEValue::from7BitInt (0), 0, 0, -1.0f, 0.0f);
expectValuesConsistent (MPEValue::from14BitInt (0), 0, 0, -1.0f, 0.0f);
expectValuesConsistent (MPEValue::fromUnsignedFloat (0.0f), 0, 0, -1.0f, 0.0f);
expectValuesConsistent (MPEValue::fromSignedFloat (-1.0f), 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);
expectValuesConsistent (MPEValue::from7BitInt (127), 127, 16383, 1.0f, 1.0f);
expectValuesConsistent (MPEValue::from14BitInt (16383), 127, 16383, 1.0f, 1.0f);
expectValuesConsistent (MPEValue::fromUnsignedFloat (1.0f), 127, 16383, 1.0f, 1.0f);
expectValuesConsistent (MPEValue::fromSignedFloat (1.0f), 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);
expectValuesConsistent (MPEValue::from7BitInt (64), 64, 8192, 0.0f, 0.5f);
expectValuesConsistent (MPEValue::from14BitInt (8192), 64, 8192, 0.0f, 0.5f);
expectValuesConsistent (MPEValue::fromUnsignedFloat (0.5f), 64, 8192, 0.0f, 0.5f);
expectValuesConsistent (MPEValue::fromSignedFloat (0.0f), 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);
expectValuesConsistent (MPEValue::from7BitInt (32), 32, 4096, -0.5f, 0.25f);
expectValuesConsistent (MPEValue::from14BitInt (4096), 32, 4096, -0.5f, 0.25f);
expectValuesConsistent (MPEValue::fromUnsignedFloat (0.25f), 32, 4096, -0.5f, 0.25f);
expectValuesConsistent (MPEValue::fromSignedFloat (-0.5f), 32, 4096, -0.5f, 0.25f);
}
}

Expand Down
Expand Up @@ -53,6 +53,12 @@ class JUCE_API MPEValue
*/
static MPEValue from14BitInt (int value) noexcept;

/** Constructs an MPEValue from a float between 0.0f and 1.0f. */
static MPEValue fromUnsignedFloat (float value) noexcept;

/** Constructs an MPEValue from a float between -1.0f and 1.0f. */
static MPEValue fromSignedFloat (float value) noexcept;

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

Expand Down
Expand Up @@ -23,7 +23,17 @@
namespace juce
{

MPEZoneLayout::MPEZoneLayout() noexcept {}
MPEZoneLayout::MPEZoneLayout (MPEZone lower, MPEZone upper)
: lowerZone (lower), upperZone (upper)
{
}

MPEZoneLayout::MPEZoneLayout (MPEZone zone)
: lowerZone (zone.isLowerZone() ? zone : MPEZone()),
upperZone (! zone.isLowerZone() ? zone : MPEZone())
{
}


MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other)
: lowerZone (other.lowerZone),
Expand Down Expand Up @@ -54,9 +64,9 @@ void MPEZoneLayout::setZone (bool isLower, int numMemberChannels, int perNotePit
checkAndLimitZoneParameters (0, 96, masterPitchbendRange);

if (isLower)
lowerZone = { true, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
lowerZone = { MPEZone::Type::lower, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
else
upperZone = { false, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };
upperZone = { MPEZone::Type::upper, numMemberChannels, perNotePitchbendRange, masterPitchbendRange };

if (numMemberChannels > 0)
{
Expand Down Expand Up @@ -86,8 +96,8 @@ void MPEZoneLayout::setUpperZone (int numMemberChannels, int perNotePitchbendRan

void MPEZoneLayout::clearAllZones()
{
lowerZone = { true, 0 };
upperZone = { false, 0 };
lowerZone = { MPEZone::Type::lower, 0 };
upperZone = { MPEZone::Type::upper, 0 };

sendLayoutChangeMessage();
}
Expand Down Expand Up @@ -128,7 +138,7 @@ void MPEZoneLayout::processZoneLayoutRpnMessage (MidiRPNMessage rpn)
}
}

void MPEZoneLayout::updateMasterPitchbend (Zone& zone, int value)
void MPEZoneLayout::updateMasterPitchbend (MPEZone& zone, int value)
{
if (zone.masterPitchbendRange != value)
{
Expand All @@ -138,7 +148,7 @@ void MPEZoneLayout::updateMasterPitchbend (Zone& zone, int value)
}
}

void MPEZoneLayout::updatePerNotePitchbendRange (Zone& zone, int value)
void MPEZoneLayout::updatePerNotePitchbendRange (MPEZone& zone, int value)
{
if (zone.perNotePitchbendRange != value)
{
Expand Down
Expand Up @@ -23,6 +23,83 @@
namespace juce
{

//==============================================================================
/**
This struct represents an MPE zone.
It can either be a lower or an upper zone, where:
- A lower zone encompasses master channel 1 and an arbitrary number of ascending
MIDI channels, increasing from channel 2.
- An upper zone encompasses master channel 16 and an arbitrary number of descending
MIDI channels, decreasing from channel 15.
It also defines a pitchbend range (in semitones) to be applied for per-note pitchbends and
master pitchbends, respectively.
*/
struct MPEZone
{
enum class Type { lower, upper };

MPEZone() = default;
MPEZone (const MPEZone& other) = default;

MPEZone (Type type, int memberChannels = 0, int perNotePitchbend = 48, int masterPitchbend = 2)
: zoneType (type),
numMemberChannels (memberChannels),
perNotePitchbendRange (perNotePitchbend),
masterPitchbendRange (masterPitchbend)
{}

bool isLowerZone() const noexcept { return zoneType == Type::lower; }
bool isUpperZone() const noexcept { return zoneType == Type::upper; }

bool isActive() const noexcept { return numMemberChannels > 0; }

int getMasterChannel() const noexcept { return isLowerZone() ? lowerZoneMasterChannel : upperZoneMasterChannel; }
int getFirstMemberChannel() const noexcept { return isLowerZone() ? lowerZoneMasterChannel + 1 : upperZoneMasterChannel - 1; }
int getLastMemberChannel() const noexcept { return isLowerZone() ? (lowerZoneMasterChannel + numMemberChannels)
: (upperZoneMasterChannel - numMemberChannels); }

bool isUsingChannelAsMemberChannel (int channel) const noexcept
{
return isLowerZone() ? (lowerZoneMasterChannel < channel && channel <= getLastMemberChannel())
: (channel < upperZoneMasterChannel && getLastMemberChannel() <= channel);
}

bool isUsing (int channel) const noexcept
{
return isUsingChannelAsMemberChannel (channel) || channel == getMasterChannel();
}

static auto tie (const MPEZone& z)
{
return std::tie (z.zoneType,
z.numMemberChannels,
z.perNotePitchbendRange,
z.masterPitchbendRange);
}

bool operator== (const MPEZone& other) const
{
return tie (*this) == tie (other);
}

bool operator!= (const MPEZone& other) const
{
return tie (*this) != tie (other);
}

//==============================================================================
static constexpr int lowerZoneMasterChannel = 1,
upperZoneMasterChannel = 16;

Type zoneType = Type::lower;

int numMemberChannels = 0;
int perNotePitchbendRange = 48;
int masterPitchbendRange = 2;
};

//==============================================================================
/**
This class represents the current MPE zone layout of a device capable of handling MPE.
Expand All @@ -44,89 +121,28 @@ namespace juce
class JUCE_API MPEZoneLayout
{
public:
/** Default constructor.
//==============================================================================
/** Creates a layout with inactive upper and lower zones. */
MPEZoneLayout() = default;

This will create a layout with inactive lower and upper zones, representing
a device with MPE mode disabled.
/** Creates a layout with the given upper and lower zones. */
MPEZoneLayout (MPEZone lower, MPEZone upper);

You can set the lower or upper MPE zones using the setZone() method.
/** Creates a layout with a single upper or lower zone, leaving the other zone uninitialised. */
MPEZoneLayout (MPEZone singleZone);

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

//==============================================================================
/**
This struct represents an MPE zone.
It can either be a lower or an upper zone, where:
- A lower zone encompasses master channel 1 and an arbitrary number of ascending
MIDI channels, increasing from channel 2.
- An upper zone encompasses master channel 16 and an arbitrary number of descending
MIDI channels, decreasing from channel 15.
It also defines a pitchbend range (in semitones) to be applied for per-note pitchbends and
master pitchbends, respectively.
*/
struct Zone
{
Zone (const Zone& other) = default;

bool isLowerZone() const noexcept { return lowerZone; }
bool isUpperZone() const noexcept { return ! lowerZone; }

bool isActive() const noexcept { return numMemberChannels > 0; }

int getMasterChannel() const noexcept { return lowerZone ? 1 : 16; }
int getFirstMemberChannel() const noexcept { return lowerZone ? 2 : 15; }
int getLastMemberChannel() const noexcept { return lowerZone ? (1 + numMemberChannels)
: (16 - numMemberChannels); }

bool isUsingChannelAsMemberChannel (int channel) const noexcept
{
return lowerZone ? (channel > 1 && channel <= 1 + numMemberChannels)
: (channel < 16 && channel >= 16 - numMemberChannels);
}

bool isUsing (int channel) const noexcept
{
return isUsingChannelAsMemberChannel (channel) || channel == getMasterChannel();
}
bool operator== (const MPEZoneLayout& other) const { return lowerZone == other.lowerZone && upperZone == other.upperZone; }
bool operator!= (const MPEZoneLayout& other) const { return ! operator== (other); }

bool operator== (const Zone& other) const noexcept { return lowerZone == other.lowerZone
&& numMemberChannels == other.numMemberChannels
&& perNotePitchbendRange == other.perNotePitchbendRange
&& masterPitchbendRange == other.masterPitchbendRange; }

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

int numMemberChannels;
int perNotePitchbendRange;
int masterPitchbendRange;

private:
friend class MPEZoneLayout;

Zone (bool lower, int memberChans = 0, int perNotePb = 48, int masterPb = 2) noexcept
: numMemberChannels (memberChans),
perNotePitchbendRange (perNotePb),
masterPitchbendRange (masterPb),
lowerZone (lower)
{
}
//==============================================================================
/** Returns a struct representing the lower MPE zone. */
MPEZone getLowerZone() const noexcept { return lowerZone; }

bool lowerZone;
};
/** Returns a struct representing the upper MPE zone. */
MPEZone getUpperZone() const noexcept { return upperZone; }

/** Sets the lower zone of this layout. */
void setLowerZone (int numMemberChannels = 0,
Expand All @@ -138,17 +154,14 @@ class JUCE_API MPEZoneLayout
int perNotePitchbendRange = 48,
int masterPitchbendRange = 2) noexcept;

/** Returns a struct representing the lower MPE zone. */
const Zone getLowerZone() const noexcept { return lowerZone; }

/** Returns a struct representing the upper MPE zone. */
const Zone getUpperZone() const noexcept { return upperZone; }

/** Clears the lower and upper zones of this layout, making them both inactive
and disabling MPE mode.
*/
void clearAllZones();

/** Returns true if either of the zones are active. */
bool isActive() const { return lowerZone.isActive() || upperZone.isActive(); }

//==============================================================================
/** 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
Expand Down Expand Up @@ -200,10 +213,14 @@ class JUCE_API MPEZoneLayout
/** Removes a listener. */
void removeListener (Listener* const listenerToRemove) noexcept;

#ifndef DOXYGEN
using Zone = MPEZone;
#endif

private:
//==============================================================================
Zone lowerZone { true, 0 };
Zone upperZone { false, 0 };
MPEZone lowerZone { MPEZone::Type::lower, 0 };
MPEZone upperZone { MPEZone::Type::upper, 0 };

MidiRPNDetector rpnDetector;
ListenerList<Listener> listeners;
Expand All @@ -215,8 +232,8 @@ class JUCE_API MPEZoneLayout
void processZoneLayoutRpnMessage (MidiRPNMessage);
void processPitchbendRangeRpnMessage (MidiRPNMessage);

void updateMasterPitchbend (Zone&, int);
void updatePerNotePitchbendRange (Zone&, int);
void updateMasterPitchbend (MPEZone&, int);
void updatePerNotePitchbendRange (MPEZone&, int);

void sendLayoutChangeMessage();
void checkAndLimitZoneParameters (int, int, int&) noexcept;
Expand Down

Large diffs are not rendered by default.

File renamed without changes.
Expand Up @@ -65,13 +65,17 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne
buffer.setSize (numberOfChannels, bufferSizeNeeded);
buffer.clear();

const ScopedLock sl (bufferRangeLock);

bufferValidStart = 0;
bufferValidEnd = 0;

backgroundThread.addTimeSliceClient (this);

do
{
const ScopedUnlock ul (bufferRangeLock);

backgroundThread.moveToFrontOfQueue (this);
Thread::sleep (5);
}
Expand All @@ -96,100 +100,97 @@ void BufferingAudioSource::releaseResources()

void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info)
{
const ScopedLock sl (bufferStartPosLock);

auto start = bufferValidStart.load();
auto end = bufferValidEnd.load();
auto pos = nextPlayPos.load();

auto validStart = (int) (jlimit (start, end, pos) - pos);
auto validEnd = (int) (jlimit (start, end, pos + info.numSamples) - pos);
const auto bufferRange = getValidBufferRange (info.numSamples);

if (validStart == validEnd)
if (bufferRange.isEmpty())
{
// total cache miss
info.clearActiveBufferRegion();
return;
}
else
{
if (validStart > 0)
info.buffer->clear (info.startSample, validStart); // partial cache miss at start

if (validEnd < info.numSamples)
info.buffer->clear (info.startSample + validEnd,
info.numSamples - validEnd); // partial cache miss at end
const auto validStart = bufferRange.getStart();
const auto validEnd = bufferRange.getEnd();

const ScopedLock sl (callbackLock);

if (validStart > 0)
info.buffer->clear (info.startSample, validStart); // partial cache miss at start

if (validStart < validEnd)
if (validEnd < info.numSamples)
info.buffer->clear (info.startSample + validEnd,
info.numSamples - validEnd); // partial cache miss at end

if (validStart < validEnd)
{
for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;)
{
for (int chan = jmin (numberOfChannels, info.buffer->getNumChannels()); --chan >= 0;)
jassert (buffer.getNumSamples() > 0);

const auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples());
const auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples());

if (startBufferIndex < endBufferIndex)
{
jassert (buffer.getNumSamples() > 0);
auto startBufferIndex = (int) ((validStart + nextPlayPos) % buffer.getNumSamples());
auto endBufferIndex = (int) ((validEnd + nextPlayPos) % buffer.getNumSamples());

if (startBufferIndex < endBufferIndex)
{
info.buffer->copyFrom (chan, info.startSample + validStart,
buffer,
chan, startBufferIndex,
validEnd - validStart);
}
else
{
auto initialSize = buffer.getNumSamples() - startBufferIndex;

info.buffer->copyFrom (chan, info.startSample + validStart,
buffer,
chan, startBufferIndex,
initialSize);

info.buffer->copyFrom (chan, info.startSample + validStart + initialSize,
buffer,
chan, 0,
(validEnd - validStart) - initialSize);
}
info.buffer->copyFrom (chan, info.startSample + validStart,
buffer,
chan, startBufferIndex,
validEnd - validStart);
}
}
else
{
const auto initialSize = buffer.getNumSamples() - startBufferIndex;

nextPlayPos += info.numSamples;
info.buffer->copyFrom (chan, info.startSample + validStart,
buffer,
chan, startBufferIndex,
initialSize);

info.buffer->copyFrom (chan, info.startSample + validStart + initialSize,
buffer,
chan, 0,
(validEnd - validStart) - initialSize);
}
}
}

nextPlayPos += info.numSamples;
}

bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, uint32 timeout)
{
if (!source || source->getTotalLength() <= 0)
if (source == nullptr || source->getTotalLength() <= 0)
return false;

if (nextPlayPos + info.numSamples < 0)
return true;

if (! isLooping() && nextPlayPos > getTotalLength())
if ((nextPlayPos + info.numSamples < 0)
|| (! isLooping() && nextPlayPos > getTotalLength()))
return true;

auto now = Time::getMillisecondCounter();
auto startTime = now;
const auto startTime = Time::getMillisecondCounter();
auto now = startTime;

auto elapsed = (now >= startTime ? now - startTime
: (std::numeric_limits<uint32>::max() - startTime) + now);

while (elapsed <= timeout)
{
{
const ScopedLock sl (bufferStartPosLock);

auto start = bufferValidStart.load();
auto end = bufferValidEnd.load();
auto pos = nextPlayPos.load();
const auto bufferRange = getValidBufferRange (info.numSamples);

auto validStart = static_cast<int> (jlimit (start, end, pos) - pos);
auto validEnd = static_cast<int> (jlimit (start, end, pos + info.numSamples) - pos);
const auto validStart = bufferRange.getStart();
const auto validEnd = bufferRange.getEnd();

if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples)
return true;
if (validStart <= 0
&& validStart < validEnd
&& validEnd >= info.numSamples)
{
return true;
}

if (elapsed < timeout && (! bufferReadyEvent.wait (static_cast<int> (timeout - elapsed))))
if (elapsed < timeout
&& ! bufferReadyEvent.wait (static_cast<int> (timeout - elapsed)))
{
return false;
}

now = Time::getMillisecondCounter();
elapsed = (now >= startTime ? now - startTime
Expand All @@ -202,7 +203,7 @@ bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelI
int64 BufferingAudioSource::getNextReadPosition() const
{
jassert (source->getTotalLength() > 0);
auto pos = nextPlayPos.load();
const auto pos = nextPlayPos.load();

return (source->isLooping() && nextPlayPos > 0)
? pos % source->getTotalLength()
Expand All @@ -211,18 +212,28 @@ int64 BufferingAudioSource::getNextReadPosition() const

void BufferingAudioSource::setNextReadPosition (int64 newPosition)
{
const ScopedLock sl (bufferStartPosLock);
const ScopedLock sl (bufferRangeLock);

nextPlayPos = newPosition;
backgroundThread.moveToFrontOfQueue (this);
}

Range<int> BufferingAudioSource::getValidBufferRange (int numSamples) const
{
const ScopedLock sl (bufferRangeLock);

const auto pos = nextPlayPos.load();

return { (int) (jlimit (bufferValidStart, bufferValidEnd, pos) - pos),
(int) (jlimit (bufferValidStart, bufferValidEnd, pos + numSamples) - pos) };
}

bool BufferingAudioSource::readNextBufferChunk()
{
int64 newBVS, newBVE, sectionToReadStart, sectionToReadEnd;

{
const ScopedLock sl (bufferStartPosLock);
const ScopedLock sl (bufferRangeLock);

if (wasSourceLooping != isLooping())
{
Expand All @@ -236,7 +247,7 @@ bool BufferingAudioSource::readNextBufferChunk()
sectionToReadStart = 0;
sectionToReadEnd = 0;

const int maxChunkSize = 2048;
constexpr int maxChunkSize = 2048;

if (newBVS < bufferValidStart || newBVS >= bufferValidEnd)
{
Expand All @@ -257,16 +268,17 @@ bool BufferingAudioSource::readNextBufferChunk()
sectionToReadEnd = newBVE;

bufferValidStart = newBVS;
bufferValidEnd = jmin (bufferValidEnd.load(), newBVE);
bufferValidEnd = jmin (bufferValidEnd, newBVE);
}
}

if (sectionToReadStart == sectionToReadEnd)
return false;

jassert (buffer.getNumSamples() > 0);
auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples());
auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples());

const auto bufferIndexStart = (int) (sectionToReadStart % buffer.getNumSamples());
const auto bufferIndexEnd = (int) (sectionToReadEnd % buffer.getNumSamples());

if (bufferIndexStart < bufferIndexEnd)
{
Expand All @@ -276,7 +288,7 @@ bool BufferingAudioSource::readNextBufferChunk()
}
else
{
auto initialSize = buffer.getNumSamples() - bufferIndexStart;
const auto initialSize = buffer.getNumSamples() - bufferIndexStart;

readBufferSection (sectionToReadStart,
initialSize,
Expand All @@ -288,7 +300,7 @@ bool BufferingAudioSource::readNextBufferChunk()
}

{
const ScopedLock sl2 (bufferStartPosLock);
const ScopedLock sl2 (bufferRangeLock);

bufferValidStart = newBVS;
bufferValidEnd = newBVE;
Expand All @@ -304,6 +316,8 @@ void BufferingAudioSource::readBufferSection (int64 start, int length, int buffe
source->setNextReadPosition (start);

AudioSourceChannelInfo info (&buffer, bufferOffset, length);

const ScopedLock sl (callbackLock);
source->getNextAudioBlock (info);
}

Expand Down
Expand Up @@ -98,21 +98,26 @@ class JUCE_API BufferingAudioSource : public PositionableAudioSource,
bool waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout);

private:
//==============================================================================
Range<int> getValidBufferRange (int numSamples) const;
bool readNextBufferChunk();
void readBufferSection (int64 start, int length, int bufferOffset);
int useTimeSlice() override;

//==============================================================================
OptionalScopedPointer<PositionableAudioSource> source;
TimeSliceThread& backgroundThread;
int numberOfSamplesToBuffer, numberOfChannels;
AudioBuffer<float> buffer;
CriticalSection bufferStartPosLock;
CriticalSection callbackLock, bufferRangeLock;
WaitableEvent bufferReadyEvent;
std::atomic<int64> bufferValidStart { 0 }, bufferValidEnd { 0 }, nextPlayPos { 0 };
int64 bufferValidStart = 0, bufferValidEnd = 0;
std::atomic<int64> nextPlayPos { 0 };
double sampleRate = 0;
bool wasSourceLooping = false, isPrepared = false, prefillBuffer;

bool readNextBufferChunk();
void readBufferSection (int64 start, int length, int bufferOffset);
int useTimeSlice() override;
bool wasSourceLooping = false, isPrepared = false;
const bool prefillBuffer;

//==============================================================================
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (BufferingAudioSource)
};

Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Expand Up @@ -631,14 +631,6 @@ class JUCE_API Synthesiser
template <typename floatType>
void processNextBlock (AudioBuffer<floatType>&, const MidiBuffer&, int startSample, int numSamples);

#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)
};

Expand Down