@@ -139,7 +139,7 @@ | |||
@see VSTPluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU, JUCE_PLUGINHOST_VST3 | |||
*/ | |||
#define JUCE_PLUGINHOST_VST 1 | |||
#define JUCE_PLUGINHOST_VST 0 | |||
/** Config: JUCE_PLUGINHOST_VST3 | |||
Enables the VST3 audio plugin hosting classes. This requires the Steinberg VST3 SDK to be | |||
@@ -99,7 +99,7 @@ public: | |||
class Int8 | |||
{ | |||
public: | |||
inline Int8 (void* d) noexcept : data (static_cast <int8*> (d)) {} | |||
inline Int8 (void* d) noexcept : data (static_cast<int8*> (d)) {} | |||
inline void advance() noexcept { ++data; } | |||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||
@@ -124,7 +124,7 @@ public: | |||
class UInt8 | |||
{ | |||
public: | |||
inline UInt8 (void* d) noexcept : data (static_cast <uint8*> (d)) {} | |||
inline UInt8 (void* d) noexcept : data (static_cast<uint8*> (d)) {} | |||
inline void advance() noexcept { ++data; } | |||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||
@@ -149,7 +149,7 @@ public: | |||
class Int16 | |||
{ | |||
public: | |||
inline Int16 (void* d) noexcept : data (static_cast <uint16*> (d)) {} | |||
inline Int16 (void* d) noexcept : data (static_cast<uint16*> (d)) {} | |||
inline void advance() noexcept { ++data; } | |||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||
@@ -174,7 +174,7 @@ public: | |||
class Int24 | |||
{ | |||
public: | |||
inline Int24 (void* d) noexcept : data (static_cast <char*> (d)) {} | |||
inline Int24 (void* d) noexcept : data (static_cast<char*> (d)) {} | |||
inline void advance() noexcept { data += 3; } | |||
inline void skip (int numSamples) noexcept { data += 3 * numSamples; } | |||
@@ -199,7 +199,7 @@ public: | |||
class Int32 | |||
{ | |||
public: | |||
inline Int32 (void* d) noexcept : data (static_cast <uint32*> (d)) {} | |||
inline Int32 (void* d) noexcept : data (static_cast<uint32*> (d)) {} | |||
inline void advance() noexcept { ++data; } | |||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||
@@ -245,7 +245,7 @@ public: | |||
class Float32 | |||
{ | |||
public: | |||
inline Float32 (void* d) noexcept : data (static_cast <float*> (d)) {} | |||
inline Float32 (void* d) noexcept : data (static_cast<float*> (d)) {} | |||
inline void advance() noexcept { ++data; } | |||
inline void skip (int numSamples) noexcept { data += numSamples; } | |||
@@ -318,7 +318,7 @@ public: | |||
{ | |||
public: | |||
typedef const void VoidType; | |||
static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast <void*> (v); } | |||
static inline void* toVoidPtr (VoidType* v) noexcept { return const_cast<void*> (v); } | |||
enum { isConst = 1 }; | |||
}; | |||
#endif | |||
@@ -815,11 +815,13 @@ void FloatVectorOperations::abs (float* dest, const float* src, int num) noexcep | |||
#if JUCE_USE_VDSP_FRAMEWORK | |||
vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num); | |||
#else | |||
union {float f; uint32 i;} signMask; | |||
union { float f; uint32 i; } signMask; | |||
signMask.i = 0x7fffffffUL; | |||
JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabsf (src[i]), Mode::bit_and (s, mask), | |||
JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
const Mode::ParallelType mask = Mode::load1 (signMask.f);) | |||
ignoreUnused (signMask); | |||
#endif | |||
} | |||
@@ -834,6 +836,8 @@ void FloatVectorOperations::abs (double* dest, const double* src, int num) noexc | |||
JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabs (src[i]), Mode::bit_and (s, mask), | |||
JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
const Mode::ParallelType mask = Mode::load1 (signMask.d);) | |||
ignoreUnused (signMask); | |||
#endif | |||
} | |||
@@ -1001,7 +1005,7 @@ void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnab | |||
if (FloatVectorHelpers::isSSE2Available()) | |||
_MM_SET_FLUSH_ZERO_MODE (shouldEnable ? _MM_FLUSH_ZERO_ON : _MM_FLUSH_ZERO_OFF); | |||
#endif | |||
(void) shouldEnable; | |||
ignoreUnused (shouldEnable); | |||
} | |||
//============================================================================== | |||
@@ -248,7 +248,7 @@ void FFT::performRealOnlyInverseTransform (float* d) const noexcept | |||
if (scratchSize < maxFFTScratchSpaceToAlloca) | |||
{ | |||
performRealOnlyForwardTransform (static_cast<Complex*> (alloca (scratchSize)), d); | |||
performRealOnlyInverseTransform (static_cast<Complex*> (alloca (scratchSize)), d); | |||
} | |||
else | |||
{ | |||
@@ -0,0 +1,97 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_LINEARSMOOTHEDVALUE_H_INCLUDED | |||
#define JUCE_LINEARSMOOTHEDVALUE_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Utility class for linearly smoothed values like volume etc. that should | |||
not change abruptly but as a linear ramp, to avoid audio glitches. | |||
*/ | |||
//============================================================================== | |||
template<typename FloatType> | |||
class JUCE_API LinearSmoothedValue | |||
{ | |||
public: | |||
/** Constructor. */ | |||
LinearSmoothedValue() noexcept | |||
: currentValue (0), target (0), step (0), countdown (0), stepsToTarget (0) | |||
{ | |||
} | |||
/** Constructor. */ | |||
LinearSmoothedValue (FloatType initialValue) noexcept | |||
: currentValue (initialValue), target (initialValue), step (0), countdown (0), stepsToTarget (0) | |||
{ | |||
} | |||
//========================================================================== | |||
/** Reset to a new sample rate and ramp length. */ | |||
void reset (double sampleRate, double rampLengthInSeconds) noexcept | |||
{ | |||
jassert (sampleRate > 0 && rampLengthInSeconds >= 0); | |||
stepsToTarget = (int) std::floor (rampLengthInSeconds * sampleRate); | |||
currentValue = target; | |||
countdown = 0; | |||
} | |||
//========================================================================== | |||
/** Set a new target value. */ | |||
void setValue (FloatType newValue) noexcept | |||
{ | |||
if (target != newValue) | |||
{ | |||
target = newValue; | |||
countdown = stepsToTarget; | |||
if (countdown <= 0) | |||
currentValue = target; | |||
else | |||
step = (target - currentValue) / (FloatType) countdown; | |||
} | |||
} | |||
//========================================================================== | |||
/** Compute the next value. */ | |||
FloatType getNextValue() noexcept | |||
{ | |||
if (countdown <= 0) | |||
return target; | |||
--countdown; | |||
currentValue += step; | |||
return currentValue; | |||
} | |||
private: | |||
//========================================================================== | |||
FloatType currentValue, target, step; | |||
int countdown, stepsToTarget; | |||
}; | |||
#endif // JUCE_LINEARSMOOTHEDVALUE_H_INCLUDED |
@@ -306,54 +306,6 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE (AllPassFilter) | |||
}; | |||
//============================================================================== | |||
class LinearSmoothedValue | |||
{ | |||
public: | |||
LinearSmoothedValue() noexcept | |||
: currentValue (0), target (0), step (0), countdown (0), stepsToTarget (0) | |||
{ | |||
} | |||
void reset (double sampleRate, double fadeLengthSeconds) noexcept | |||
{ | |||
jassert (sampleRate > 0 && fadeLengthSeconds >= 0); | |||
stepsToTarget = (int) std::floor (fadeLengthSeconds * sampleRate); | |||
currentValue = target; | |||
countdown = 0; | |||
} | |||
void setValue (float newValue) noexcept | |||
{ | |||
if (target != newValue) | |||
{ | |||
target = newValue; | |||
countdown = stepsToTarget; | |||
if (countdown <= 0) | |||
currentValue = target; | |||
else | |||
step = (target - currentValue) / (float) countdown; | |||
} | |||
} | |||
float getNextValue() noexcept | |||
{ | |||
if (countdown <= 0) | |||
return target; | |||
--countdown; | |||
currentValue += step; | |||
return currentValue; | |||
} | |||
private: | |||
float currentValue, target, step; | |||
int countdown, stepsToTarget; | |||
JUCE_DECLARE_NON_COPYABLE (LinearSmoothedValue) | |||
}; | |||
//============================================================================== | |||
enum { numCombs = 8, numAllPasses = 4, numChannels = 2 }; | |||
@@ -363,7 +315,7 @@ private: | |||
CombFilter comb [numChannels][numCombs]; | |||
AllPassFilter allPass [numChannels][numAllPasses]; | |||
LinearSmoothedValue damping, feedback, dryGain, wetGain1, wetGain2; | |||
LinearSmoothedValue<float> damping, feedback, dryGain, wetGain1, wetGain2; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Reverb) | |||
}; | |||
@@ -22,7 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
#if defined (JUCE_AUDIO_BASICS_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE | |||
#ifdef JUCE_AUDIO_BASICS_H_INCLUDED | |||
/* When you add this cpp file to your project, you mustn't include it in a file where you've | |||
already included any other headers - just put it inside a file on its own, possibly with your config | |||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||
@@ -31,9 +31,6 @@ | |||
#error "Incorrect use of JUCE cpp file" | |||
#endif | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "juce_audio_basics.h" | |||
#if JUCE_MINGW && ! defined (__SSE2__) | |||
@@ -90,6 +87,16 @@ namespace juce | |||
#include "midi/juce_MidiKeyboardState.cpp" | |||
#include "midi/juce_MidiMessage.cpp" | |||
#include "midi/juce_MidiMessageSequence.cpp" | |||
#include "midi/juce_MidiRPN.cpp" | |||
#include "mpe/juce_MPEValue.cpp" | |||
#include "mpe/juce_MPENote.cpp" | |||
#include "mpe/juce_MPEZone.cpp" | |||
#include "mpe/juce_MPEZoneLayout.cpp" | |||
#include "mpe/juce_MPEInstrument.cpp" | |||
#include "mpe/juce_MPEMessages.cpp" | |||
#include "mpe/juce_MPESynthesiserBase.cpp" | |||
#include "mpe/juce_MPESynthesiserVoice.cpp" | |||
#include "mpe/juce_MPESynthesiser.cpp" | |||
#include "sources/juce_BufferingAudioSource.cpp" | |||
#include "sources/juce_ChannelRemappingAudioSource.cpp" | |||
#include "sources/juce_IIRFilterAudioSource.cpp" | |||
@@ -42,12 +42,23 @@ namespace juce | |||
#include "effects/juce_IIRFilterOld.h" | |||
#include "effects/juce_LagrangeInterpolator.h" | |||
#include "effects/juce_FFT.h" | |||
#include "effects/juce_LinearSmoothedValue.h" | |||
#include "effects/juce_Reverb.h" | |||
#include "midi/juce_MidiMessage.h" | |||
#include "midi/juce_MidiBuffer.h" | |||
#include "midi/juce_MidiMessageSequence.h" | |||
#include "midi/juce_MidiFile.h" | |||
#include "midi/juce_MidiKeyboardState.h" | |||
#include "midi/juce_MidiRPN.h" | |||
#include "mpe/juce_MPEValue.h" | |||
#include "mpe/juce_MPENote.h" | |||
#include "mpe/juce_MPEZone.h" | |||
#include "mpe/juce_MPEZoneLayout.h" | |||
#include "mpe/juce_MPEInstrument.h" | |||
#include "mpe/juce_MPEMessages.h" | |||
#include "mpe/juce_MPESynthesiserBase.h" | |||
#include "mpe/juce_MPESynthesiserVoice.h" | |||
#include "mpe/juce_MPESynthesiser.h" | |||
#include "sources/juce_AudioSource.h" | |||
#include "sources/juce_PositionableAudioSource.h" | |||
#include "sources/juce_BufferingAudioSource.h" | |||
@@ -1,12 +1,12 @@ | |||
{ | |||
"id": "juce_audio_basics", | |||
"name": "JUCE audio and midi data classes", | |||
"version": "4.0.2", | |||
"version": "4.1.0", | |||
"description": "Classes for audio buffer manipulation, midi message handling, synthesis, etc", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
"dependencies": [ { "id": "juce_core", "version": "matching" } ], | |||
"dependencies": [ { "id": "juce_core", "version": "matching" }], | |||
"include": "juce_audio_basics.h", | |||
@@ -14,8 +14,9 @@ | |||
{ "file": "juce_audio_basics.mm", "target": "xcode" } ], | |||
"browse": [ "buffers/*", | |||
"midi/*", | |||
"effects/*", | |||
"midi/*", | |||
"mpe/*", | |||
"sources/*", | |||
"synthesisers/*" ], | |||
@@ -254,12 +254,12 @@ bool MidiFile::readFrom (InputStream& sourceStream) | |||
if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | |||
{ | |||
size_t size = data.getSize(); | |||
const uint8* d = static_cast <const uint8*> (data.getData()); | |||
const uint8* d = static_cast<const uint8*> (data.getData()); | |||
short fileType, expectedTracks; | |||
if (size > 16 && MidiFileHelpers::parseMidiHeader (d, timeFormat, fileType, expectedTracks)) | |||
{ | |||
size -= (size_t) (d - static_cast <const uint8*> (data.getData())); | |||
size -= (size_t) (d - static_cast<const uint8*> (data.getData())); | |||
int track = 0; | |||
@@ -672,7 +672,7 @@ bool MidiMessage::isTextMetaEvent() const noexcept | |||
String MidiMessage::getTextFromTextMetaEvent() const | |||
{ | |||
const char* const textData = reinterpret_cast <const char*> (getMetaEventData()); | |||
const char* const textData = reinterpret_cast<const char*> (getMetaEventData()); | |||
return String (CharPointer_UTF8 (textData), | |||
CharPointer_UTF8 (textData + getMetaEventLength())); | |||
} | |||
@@ -982,7 +982,7 @@ String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctav | |||
return s; | |||
} | |||
return String::empty; | |||
return String(); | |||
} | |||
double MidiMessage::getMidiNoteInHertz (int noteNumber, const double frequencyOfA) noexcept | |||
@@ -0,0 +1,374 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
MidiRPNDetector::MidiRPNDetector() noexcept | |||
{ | |||
} | |||
MidiRPNDetector::~MidiRPNDetector() noexcept | |||
{ | |||
} | |||
bool MidiRPNDetector::parseControllerMessage (int midiChannel, | |||
int controllerNumber, | |||
int controllerValue, | |||
MidiRPNMessage& result) noexcept | |||
{ | |||
jassert (midiChannel >= 1 && midiChannel <= 16); | |||
jassert (controllerNumber >= 0 && controllerNumber < 128); | |||
jassert (controllerValue >= 0 && controllerValue < 128); | |||
return states[midiChannel - 1].handleController (midiChannel, controllerNumber, controllerValue, result); | |||
} | |||
void MidiRPNDetector::reset() noexcept | |||
{ | |||
for (int i = 0; i < 16; ++i) | |||
{ | |||
states[i].parameterMSB = 0xff; | |||
states[i].parameterLSB = 0xff; | |||
states[i].resetValue(); | |||
states[i].isNRPN = false; | |||
} | |||
} | |||
//============================================================================== | |||
MidiRPNDetector::ChannelState::ChannelState () noexcept | |||
: parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false) | |||
{ | |||
} | |||
bool MidiRPNDetector::ChannelState::handleController (int channel, | |||
int controllerNumber, | |||
int value, | |||
MidiRPNMessage& result) noexcept | |||
{ | |||
switch (controllerNumber) | |||
{ | |||
case 0x62: parameterLSB = uint8 (value); resetValue(); isNRPN = true; break; | |||
case 0x63: parameterMSB = uint8 (value); resetValue(); isNRPN = true; break; | |||
case 0x64: parameterLSB = uint8 (value); resetValue(); isNRPN = false; break; | |||
case 0x65: parameterMSB = uint8 (value); resetValue(); isNRPN = false; break; | |||
case 0x06: valueMSB = uint8 (value); return sendIfReady (channel, result); | |||
case 0x26: valueLSB = uint8 (value); break; | |||
default: break; | |||
} | |||
return false; | |||
} | |||
void MidiRPNDetector::ChannelState::resetValue() noexcept | |||
{ | |||
valueMSB = 0xff; | |||
valueLSB = 0xff; | |||
} | |||
//============================================================================== | |||
bool MidiRPNDetector::ChannelState::sendIfReady (int channel, MidiRPNMessage& result) noexcept | |||
{ | |||
if (parameterMSB < 0x80 && parameterLSB < 0x80) | |||
{ | |||
if (valueMSB < 0x80) | |||
{ | |||
result.channel = channel; | |||
result.parameterNumber = (parameterMSB << 7) + parameterLSB; | |||
result.isNRPN = isNRPN; | |||
if (valueLSB < 0x80) | |||
{ | |||
result.value = (valueMSB << 7) + valueLSB; | |||
result.is14BitValue = true; | |||
} | |||
else | |||
{ | |||
result.value = valueMSB; | |||
result.is14BitValue = false; | |||
} | |||
return true; | |||
} | |||
} | |||
return false; | |||
} | |||
//============================================================================== | |||
MidiBuffer MidiRPNGenerator::generate (MidiRPNMessage message) | |||
{ | |||
return generate (message.channel, | |||
message.parameterNumber, | |||
message.value, | |||
message.isNRPN, | |||
message.is14BitValue); | |||
} | |||
MidiBuffer MidiRPNGenerator::generate (int midiChannel, | |||
int parameterNumber, | |||
int value, | |||
bool isNRPN, | |||
bool use14BitValue) | |||
{ | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
jassert (parameterNumber >= 0 && parameterNumber < 16384); | |||
jassert (value >= 0 && value < (use14BitValue ? 16384 : 128)); | |||
uint8 parameterLSB = uint8 (parameterNumber & 0x0000007f); | |||
uint8 parameterMSB = uint8 (parameterNumber >> 7); | |||
uint8 valueLSB = use14BitValue ? uint8 (value & 0x0000007f) : 0x00; | |||
uint8 valueMSB = use14BitValue ? uint8 (value >> 7) : uint8 (value); | |||
uint8 channelByte = uint8 (0xb0 + midiChannel - 1); | |||
MidiBuffer buffer; | |||
buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x62 : 0x64, parameterLSB), 0); | |||
buffer.addEvent (MidiMessage (channelByte, isNRPN ? 0x63 : 0x65, parameterMSB), 0); | |||
// sending the value LSB is optional, but must come before sending the value MSB: | |||
if (use14BitValue) | |||
buffer.addEvent (MidiMessage (channelByte, 0x26, valueLSB), 0); | |||
buffer.addEvent (MidiMessage (channelByte, 0x06, valueMSB), 0); | |||
return buffer; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
class MidiRPNDetectorTests : public UnitTest | |||
{ | |||
public: | |||
MidiRPNDetectorTests() : UnitTest ("MidiRPNDetector class") {} | |||
void runTest() override | |||
{ | |||
beginTest ("7-bit RPN"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||
expect (detector.parseControllerMessage (2, 6, 42, rpn)); | |||
expectEquals (rpn.channel, 2); | |||
expectEquals (rpn.parameterNumber, 7); | |||
expectEquals (rpn.value, 42); | |||
expect (! rpn.isNRPN); | |||
expect (! rpn.is14BitValue); | |||
} | |||
beginTest ("14-bit RPN"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (1, 100, 44, rpn)); | |||
expect (! detector.parseControllerMessage (1, 101, 2, rpn)); | |||
expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | |||
expect (detector.parseControllerMessage (1, 6, 1, rpn)); | |||
expectEquals (rpn.channel, 1); | |||
expectEquals (rpn.parameterNumber, 300); | |||
expectEquals (rpn.value, 222); | |||
expect (! rpn.isNRPN); | |||
expect (rpn.is14BitValue); | |||
} | |||
beginTest ("RPNs on multiple channels simultaneously"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (1, 100, 44, rpn)); | |||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||
expect (! detector.parseControllerMessage (1, 101, 2, rpn)); | |||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||
expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | |||
expect (detector.parseControllerMessage (2, 6, 42, rpn)); | |||
expectEquals (rpn.channel, 2); | |||
expectEquals (rpn.parameterNumber, 7); | |||
expectEquals (rpn.value, 42); | |||
expect (! rpn.isNRPN); | |||
expect (! rpn.is14BitValue); | |||
expect (detector.parseControllerMessage (1, 6, 1, rpn)); | |||
expectEquals (rpn.channel, 1); | |||
expectEquals (rpn.parameterNumber, 300); | |||
expectEquals (rpn.value, 222); | |||
expect (! rpn.isNRPN); | |||
expect (rpn.is14BitValue); | |||
} | |||
beginTest ("14-bit RPN with value within 7-bit range"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); | |||
expect (! detector.parseControllerMessage (16, 101, 0, rpn)); | |||
expect (! detector.parseControllerMessage (16, 38, 3, rpn)); | |||
expect (detector.parseControllerMessage (16, 6, 0, rpn)); | |||
expectEquals (rpn.channel, 16); | |||
expectEquals (rpn.parameterNumber, 0); | |||
expectEquals (rpn.value, 3); | |||
expect (! rpn.isNRPN); | |||
expect (rpn.is14BitValue); | |||
} | |||
beginTest ("invalid RPN (wrong order)"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (2, 6, 42, rpn)); | |||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||
} | |||
beginTest ("14-bit RPN interspersed with unrelated CC messages"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (16, 3, 80, rpn)); | |||
expect (! detector.parseControllerMessage (16, 100, 0 , rpn)); | |||
expect (! detector.parseControllerMessage (16, 4, 81, rpn)); | |||
expect (! detector.parseControllerMessage (16, 101, 0, rpn)); | |||
expect (! detector.parseControllerMessage (16, 5, 82, rpn)); | |||
expect (! detector.parseControllerMessage (16, 5, 83, rpn)); | |||
expect (! detector.parseControllerMessage (16, 38, 3, rpn)); | |||
expect (! detector.parseControllerMessage (16, 4, 84, rpn)); | |||
expect (! detector.parseControllerMessage (16, 3, 85, rpn)); | |||
expect (detector.parseControllerMessage (16, 6, 0, rpn)); | |||
expectEquals (rpn.channel, 16); | |||
expectEquals (rpn.parameterNumber, 0); | |||
expectEquals (rpn.value, 3); | |||
expect (! rpn.isNRPN); | |||
expect (rpn.is14BitValue); | |||
} | |||
beginTest ("14-bit NRPN"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (1, 98, 44, rpn)); | |||
expect (! detector.parseControllerMessage (1, 99 , 2, rpn)); | |||
expect (! detector.parseControllerMessage (1, 38, 94, rpn)); | |||
expect (detector.parseControllerMessage (1, 6, 1, rpn)); | |||
expectEquals (rpn.channel, 1); | |||
expectEquals (rpn.parameterNumber, 300); | |||
expectEquals (rpn.value, 222); | |||
expect (rpn.isNRPN); | |||
expect (rpn.is14BitValue); | |||
} | |||
beginTest ("reset"); | |||
{ | |||
MidiRPNDetector detector; | |||
MidiRPNMessage rpn; | |||
expect (! detector.parseControllerMessage (2, 101, 0, rpn)); | |||
detector.reset(); | |||
expect (! detector.parseControllerMessage (2, 100, 7, rpn)); | |||
expect (! detector.parseControllerMessage (2, 6, 42, rpn)); | |||
} | |||
} | |||
}; | |||
static MidiRPNDetectorTests MidiRPNDetectorUnitTests; | |||
//============================================================================== | |||
class MidiRPNGeneratorTests : public UnitTest | |||
{ | |||
public: | |||
MidiRPNGeneratorTests() : UnitTest ("MidiRPNGenerator class") {} | |||
void runTest() override | |||
{ | |||
beginTest ("generating RPN/NRPN"); | |||
{ | |||
{ | |||
MidiBuffer buffer = MidiRPNGenerator::generate (1, 23, 1337, true, true); | |||
expectContainsRPN (buffer, 1, 23, 1337, true, true); | |||
} | |||
{ | |||
MidiBuffer buffer = MidiRPNGenerator::generate (16, 101, 34, false, false); | |||
expectContainsRPN (buffer, 16, 101, 34, false, false); | |||
} | |||
{ | |||
MidiRPNMessage message = { 16, 101, 34, false, false }; | |||
MidiBuffer buffer = MidiRPNGenerator::generate (message); | |||
expectContainsRPN (buffer, message); | |||
} | |||
} | |||
} | |||
private: | |||
//========================================================================== | |||
void expectContainsRPN (const MidiBuffer& midiBuffer, | |||
int channel, | |||
int parameterNumber, | |||
int value, | |||
bool isNRPN, | |||
bool is14BitValue) | |||
{ | |||
MidiRPNMessage expected = { channel, parameterNumber, value, isNRPN, is14BitValue }; | |||
expectContainsRPN (midiBuffer, expected); | |||
} | |||
//========================================================================== | |||
void expectContainsRPN (const MidiBuffer& midiBuffer, MidiRPNMessage expected) | |||
{ | |||
MidiBuffer::Iterator iter (midiBuffer); | |||
MidiMessage midiMessage; | |||
MidiRPNMessage result = MidiRPNMessage(); | |||
MidiRPNDetector detector; | |||
int samplePosition; // not actually used, so no need to initialise. | |||
while (iter.getNextEvent (midiMessage, samplePosition)) | |||
{ | |||
if (detector.parseControllerMessage (midiMessage.getChannel(), | |||
midiMessage.getControllerNumber(), | |||
midiMessage.getControllerValue(), | |||
result)) | |||
break; | |||
} | |||
expectEquals (result.channel, expected.channel); | |||
expectEquals (result.parameterNumber, expected.parameterNumber); | |||
expectEquals (result.value, expected.value); | |||
expect (result.isNRPN == expected.isNRPN), | |||
expect (result.is14BitValue == expected.is14BitValue); | |||
} | |||
}; | |||
static MidiRPNGeneratorTests MidiRPNGeneratorUnitTests; | |||
#endif // JUCE_UNIT_TESTS |
@@ -0,0 +1,152 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_MIDIRPNDETECTOR_H_INCLUDED | |||
#define JUCE_MIDIRPNDETECTOR_H_INCLUDED | |||
//========================================================================== | |||
/** Represents a MIDI RPN (registered parameter number) or NRPN (non-registered | |||
parameter number) message. | |||
*/ | |||
struct MidiRPNMessage | |||
{ | |||
/** Midi channel of the message, in the range 1 to 16. */ | |||
int channel; | |||
/** The 14-bit parameter index, in the range 0 to 16383 (0x3fff). */ | |||
int parameterNumber; | |||
/** The parameter value, in the range 0 to 16383 (0x3fff). | |||
If the message contains no value LSB, the value will be in the range | |||
0 to 127 (0x7f). | |||
*/ | |||
int value; | |||
/** True if this message is an NRPN; false if it is an RPN. */ | |||
bool isNRPN; | |||
/** True if the value uses 14-bit resolution (LSB + MSB); false if | |||
the value is 7-bit (MSB only). | |||
*/ | |||
bool is14BitValue; | |||
}; | |||
//============================================================================== | |||
/** | |||
Parses a stream of MIDI data to assemble RPN and NRPN messages from their | |||
constituent MIDI CC messages. | |||
The detector uses the following parsing rules: the parameter number | |||
LSB/MSB can be sent/received in either order and must both come before the | |||
parameter value; for the parameter value, LSB always has to be sent/received | |||
before the value MSB, otherwise it will be treated as 7-bit (MSB only). | |||
*/ | |||
class JUCE_API MidiRPNDetector | |||
{ | |||
public: | |||
/** Constructor. */ | |||
MidiRPNDetector() noexcept; | |||
/** Destructor. */ | |||
~MidiRPNDetector() noexcept; | |||
/** Resets the RPN detector's internal state, so that it forgets about | |||
previously received MIDI CC messages. | |||
*/ | |||
void reset() noexcept; | |||
//========================================================================== | |||
/** Takes the next in a stream of incoming MIDI CC messages and returns true | |||
if it forms the last of a sequence that makes an RPN or NPRN. | |||
If this returns true, then the RPNMessage object supplied will be | |||
filled-out with the message's details. | |||
(If it returns false then the RPNMessage object will be unchanged). | |||
*/ | |||
bool parseControllerMessage (int midiChannel, | |||
int controllerNumber, | |||
int controllerValue, | |||
MidiRPNMessage& result) noexcept; | |||
private: | |||
//========================================================================== | |||
struct ChannelState | |||
{ | |||
ChannelState() noexcept; | |||
bool handleController (int channel, int controllerNumber, | |||
int value, MidiRPNMessage&) noexcept; | |||
void resetValue() noexcept; | |||
bool sendIfReady (int channel, MidiRPNMessage&) noexcept; | |||
uint8 parameterMSB, parameterLSB, valueMSB, valueLSB; | |||
bool isNRPN; | |||
}; | |||
//========================================================================== | |||
ChannelState states[16]; | |||
JUCE_LEAK_DETECTOR (MidiRPNDetector) | |||
}; | |||
//============================================================================== | |||
/** | |||
Generates an appropriate sequence of MIDI CC messages to represent an RPN | |||
or NRPN message. | |||
This sequence (as a MidiBuffer) can then be directly sent to a MidiOutput. | |||
*/ | |||
class JUCE_API MidiRPNGenerator | |||
{ | |||
public: | |||
//========================================================================== | |||
/** Generates a MIDI sequence representing the given RPN or NRPN message. */ | |||
static MidiBuffer generate (MidiRPNMessage message); | |||
//========================================================================== | |||
/** Generates a MIDI sequence representing an RPN or NRPN message with the | |||
given parameters. | |||
@param channel The MIDI channel of the RPN/NRPN message. | |||
@param parameterNumber The parameter number, in the range 0 to 16383. | |||
@param value The parameter value, in the range 0 to 16383, or | |||
in the range 0 to 127 if sendAs14BitValue is false. | |||
@param isNRPN Whether you need a MIDI RPN or NRPN sequence (RPN is default). | |||
@param use14BitValue If true (default), the value will have 14-bit precision | |||
(two MIDI bytes). If false, instead the value will have | |||
7-bit presision (a single MIDI byte). | |||
*/ | |||
static MidiBuffer generate (int channel, | |||
int parameterNumber, | |||
int value, | |||
bool isNRPN = false, | |||
bool use14BitValue = true); | |||
}; | |||
#endif // JUCE_MIDIRPNDETECTOR_H_INCLUDED |
@@ -0,0 +1,414 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_MPEINSTRUMENT_H_INCLUDED | |||
#define JUCE_MPEINSTRUMENT_H_INCLUDED | |||
//============================================================================== | |||
/* | |||
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 Listener | |||
{ | |||
public: | |||
/** Constructor. */ | |||
Listener(); | |||
/** 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* const listenerToAdd) noexcept; | |||
/** Removes a listener. */ | |||
void removeListener (Listener* const 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: | |||
//========================================================================== | |||
/** This method defines what initial pitchbend value should be used for newly | |||
triggered notes. The default is to use the last pitchbend value | |||
that has been received on the same MIDI channel (or no pitchbend | |||
if no pitchbend messages have been received so far). | |||
Override this method if you need different behaviour. | |||
*/ | |||
virtual MPEValue getInitialPitchbendForNoteOn (int midiChannel, | |||
int midiNoteNumber, | |||
MPEValue midiNoteOnVelocity) const; | |||
/** This method defines what initial pressure value should be used for newly | |||
triggered notes. The default is to re-use the note-on velocity value. | |||
Override this method if you need different behaviour. | |||
*/ | |||
virtual MPEValue getInitialPressureForNoteOn (int midiChannel, | |||
int midiNoteNumber, | |||
MPEValue midiNoteOnVelocity) const; | |||
/** This method defines what initial timbre value should be used for newly | |||
triggered notes. The default is to use the last timbre value that has | |||
that has been received on the same MIDI channel (or a neutral centred value | |||
if no pitchbend messages have been received so far). | |||
Override this method if you need different behaviour. | |||
*/ | |||
virtual MPEValue getInitialTimbreForNoteOn (int midiChannel, | |||
int midiNoteNumber, | |||
MPEValue midiNoteOnVelocity) const; | |||
private: | |||
//========================================================================== | |||
CriticalSection lock; | |||
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&); | |||
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) | |||
}; | |||
#endif // JUCE_MPE_H_INCLUDED |
@@ -0,0 +1,198 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
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") {} | |||
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 |
@@ -0,0 +1,96 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_MPEMESSAGES_H_INCLUDED | |||
#define JUCE_MPEMESSAGES_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
This helper class contains the necessary helper functions to generate | |||
MIDI messages that are exclusive to MPE, such as defining | |||
MPE zones and setting per-note and master pitchbend ranges. | |||
You can then send them to your MPE device using | |||
MidiOutput::sendBlockOfMessagesNow. | |||
All other MPE messages like per-note pitchbend, pressure, and third | |||
dimension, are ordinary MIDI messages that should be created using the MidiMessage | |||
class instead. You just need to take care to send them to the appropriate | |||
per-note MIDI channel. | |||
Note: if you are working with an MPEZoneLayout object inside your app, | |||
you should not use the message sequences provided here. Instead, you should | |||
change the zone layout programmatically with the member functions provided in the | |||
MPEZoneLayout class itself. You should also make sure that the Expressive | |||
MIDI zone layout of your C++ code and of the MPE device are kept in sync. | |||
@see MidiMessage, MPEZoneLayout, MPEZone | |||
*/ | |||
class JUCE_API MPEMessages | |||
{ | |||
public: | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will define a new MPE zone. | |||
*/ | |||
static MidiBuffer addZone (MPEZone zone); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will change the per-note pitchbend range of an | |||
existing MPE zone. | |||
*/ | |||
static MidiBuffer perNotePitchbendRange (MPEZone zone); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will change the master pitchbend range of an | |||
existing MPE zone. | |||
*/ | |||
static MidiBuffer masterPitchbendRange (MPEZone zone); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will erase all currently defined MPE zones. | |||
*/ | |||
static MidiBuffer clearAllZones(); | |||
/** Returns the sequence of MIDI messages that, if sent to an Expressive | |||
MIDI device, will reset the whole MPE zone layout of the | |||
device to the laoyut passed in. This will first clear all currently | |||
defined MPE zones, then add all zones contained in the | |||
passed-in zone layout, and set their per-note and master pitchbend | |||
ranges to their current values. | |||
*/ | |||
static MidiBuffer setZoneLayout (const MPEZoneLayout& layout); | |||
/** The RPN number used for MPE zone layout messages. | |||
Note: This number can change in later versions of MPE. | |||
Pitchbend range messages (both per-note and master) are instead sent | |||
on RPN 0 as in standard MIDI 1.0. | |||
*/ | |||
static const int zoneLayoutMessagesRpnNumber = 6; | |||
}; | |||
#endif // JUCE_MPEMESSAGES_H_INCLUDED |
@@ -0,0 +1,132 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
namespace | |||
{ | |||
uint16 generateNoteID (int midiChannel, int midiNoteNumber) noexcept | |||
{ | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
jassert (midiNoteNumber >= 0 && midiNoteNumber < 128); | |||
return uint16 ((midiChannel << 7) + midiNoteNumber); | |||
} | |||
} | |||
//============================================================================== | |||
MPENote::MPENote (int midiChannel_, | |||
int initialNote_, | |||
MPEValue noteOnVelocity_, | |||
MPEValue pitchbend_, | |||
MPEValue pressure_, | |||
MPEValue timbre_, | |||
KeyState keyState_) noexcept | |||
: noteID (generateNoteID (midiChannel_, initialNote_)), | |||
midiChannel (uint8 (midiChannel_)), | |||
initialNote (uint8 (initialNote_)), | |||
noteOnVelocity (noteOnVelocity_), | |||
pitchbend (pitchbend_), | |||
pressure (pressure_), | |||
timbre (timbre_), | |||
noteOffVelocity (MPEValue::minValue()), | |||
keyState (keyState_) | |||
{ | |||
jassert (keyState != MPENote::off); | |||
jassert (isValid()); | |||
} | |||
MPENote::MPENote() noexcept | |||
: noteID (0), | |||
midiChannel (0), | |||
initialNote (0), | |||
noteOnVelocity (MPEValue::minValue()), | |||
pitchbend (MPEValue::centreValue()), | |||
pressure (MPEValue::centreValue()), | |||
timbre (MPEValue::centreValue()), | |||
noteOffVelocity (MPEValue::minValue()), | |||
keyState (MPENote::off) | |||
{ | |||
} | |||
//============================================================================== | |||
bool MPENote::isValid() const noexcept | |||
{ | |||
return midiChannel > 0 && midiChannel <= 16 && initialNote >= 0 && initialNote <= 127; | |||
} | |||
//============================================================================== | |||
double MPENote::getFrequencyInHertz (double frequencyOfA) const noexcept | |||
{ | |||
double pitchInSemitones = double (initialNote) + totalPitchbendInSemitones; | |||
return frequencyOfA * std::pow (2.0, (pitchInSemitones - 69.0) / 12.0); | |||
} | |||
//============================================================================== | |||
bool MPENote::operator== (const MPENote& other) const noexcept | |||
{ | |||
jassert (isValid() && other.isValid()); | |||
return noteID == other.noteID; | |||
} | |||
bool MPENote::operator!= (const MPENote& other) const noexcept | |||
{ | |||
jassert (isValid() && other.isValid()); | |||
return noteID != other.noteID; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
class MPENoteTests : public UnitTest | |||
{ | |||
public: | |||
MPENoteTests() : UnitTest ("MPENote class") {} | |||
//========================================================================== | |||
void runTest() override | |||
{ | |||
beginTest ("getFrequencyInHertz"); | |||
{ | |||
MPENote note; | |||
note.initialNote = 60; | |||
note.totalPitchbendInSemitones = -0.5; | |||
expectEqualsWithinOneCent (note.getFrequencyInHertz(), 254.178); | |||
} | |||
} | |||
private: | |||
//========================================================================== | |||
void expectEqualsWithinOneCent (double frequencyInHertzActual, | |||
double frequencyInHertzExpected) | |||
{ | |||
double ratio = frequencyInHertzActual / frequencyInHertzExpected; | |||
double oneCent = 1.0005946; | |||
expect (ratio < oneCent); | |||
expect (ratio > 1.0 / oneCent); | |||
} | |||
}; | |||
static MPENoteTests MPENoteUnitTests; | |||
#endif // JUCE_UNIT_TESTS |
@@ -0,0 +1,180 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_MPENOTE_H_INCLUDED | |||
#define JUCE_MPENOTE_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
This struct represents a playing MPE note. | |||
A note is identified by a unique ID, or alternatively, by a MIDI channel | |||
and an initial note. It is characterised by five dimensions of continuous | |||
expressive control. Their current values are represented as | |||
MPEValue objects. | |||
@see MPEValue | |||
*/ | |||
struct JUCE_API MPENote | |||
{ | |||
//========================================================================== | |||
enum KeyState | |||
{ | |||
off = 0, | |||
keyDown = 1, | |||
sustained = 2, | |||
keyDownAndSustained = 3 | |||
}; | |||
//========================================================================== | |||
/** Constructor. | |||
@param midiChannel The MIDI channel of the note, between 2 and 16. | |||
(Channel 1 can never be a note channel in MPE). | |||
@param initialNote The MIDI note number, between 0 and 127. | |||
@param velocity The note-on velocity of the note. | |||
@param pitchbend The initial per-note pitchbend of the note. | |||
@param pressure The initial pressure of the note. | |||
@param timbre The timbre value of the note. | |||
@param keyState The key state of the note (whether the key is down | |||
and/or the note is sustained). This value must not | |||
be MPENote::off, since you are triggering a new note. | |||
(If not specified, the default value will be MPENOte::keyDown.) | |||
*/ | |||
MPENote (int midiChannel, | |||
int initialNote, | |||
MPEValue velocity, | |||
MPEValue pitchbend, | |||
MPEValue pressure, | |||
MPEValue timbre, | |||
KeyState keyState = MPENote::keyDown) noexcept; | |||
/** Default constructor. | |||
Constructs an invalid MPE note (a note with the key state MPENote::off | |||
and an invalid MIDI channel. The only allowed use for such a note is to | |||
call isValid() on it; everything else is undefined behaviour. | |||
*/ | |||
MPENote() noexcept; | |||
/** Checks whether the MPE note is valid. */ | |||
bool isValid() const noexcept; | |||
//========================================================================== | |||
// Invariants that define the note. | |||
/** A unique ID. Useful to distinguish the note from other simultaneously | |||
sounding notes that may use the same note number or MIDI channel. | |||
This should never change during the lifetime of a note object. | |||
*/ | |||
uint16 noteID; | |||
/** The MIDI channel which this note uses. | |||
This should never change during the lifetime of an MPENote object. | |||
*/ | |||
uint8 midiChannel; | |||
/** The MIDI note number that was sent when the note was triggered. | |||
This should never change during the lifetime of an MPENote object. | |||
*/ | |||
uint8 initialNote; | |||
//========================================================================== | |||
// The five dimensions of continuous expressive control | |||
/** The velocity ("strike") of the note-on. | |||
This dimension will stay constant after the note has been turned on. | |||
*/ | |||
MPEValue noteOnVelocity; | |||
/** Current per-note pitchbend of the note (in units of MIDI pitchwheel | |||
position). This dimension can be modulated while the note sounds. | |||
Note: This value is not aware of the currently used pitchbend range, | |||
or an additional master pitchbend that may be simultaneously applied. | |||
To compute the actual effective pitchbend of an MPENote, you should | |||
probably use the member totalPitchbendInSemitones instead. | |||
@see totalPitchbendInSemitones, getFrequencyInHertz | |||
*/ | |||
MPEValue pitchbend; | |||
/** Current pressure with which the note is held down. | |||
This dimension can be modulated while the note sounds. | |||
*/ | |||
MPEValue pressure; | |||
/** Current value of the note's third expressive dimension, tyically | |||
encoding some kind of timbre parameter. | |||
This dimension can be modulated while the note sounds. | |||
*/ | |||
MPEValue timbre; | |||
/** The release velocity ("lift") of the note after a note-off has been | |||
received. | |||
This dimension will only have a meaningful value after a note-off has | |||
been received for the note (and keyState is set to MPENote::off or | |||
MPENOte::sustained). Initially, the value is undefined. | |||
*/ | |||
MPEValue noteOffVelocity; | |||
//========================================================================== | |||
/** Current effective pitchbend of the note in units of semitones, relative | |||
to initialNote. You should use this to compute the actual effective pitch | |||
of the note. This value is computed and set by an MPEInstrument to the | |||
sum of the per-note pitchbend value (stored in MPEValue::pitchbend) | |||
and the master pitchbend of the MPE zone, weighted with the per-note | |||
pitchbend range and master pitchbend range of the zone, respectively. | |||
@see getFrequencyInHertz | |||
*/ | |||
double totalPitchbendInSemitones; | |||
/** Current key state. Indicates whether the note key is currently down (pressed) | |||
and/or the note is sustained (by a sustain or sostenuto pedal). | |||
*/ | |||
KeyState keyState; | |||
//========================================================================== | |||
/** Returns the current frequency of the note in Hertz. This is the a sum of | |||
the initialNote and the totalPitchbendInSemitones, converted to Hertz. | |||
*/ | |||
double getFrequencyInHertz (double frequencyOfA = 440.0) const noexcept; | |||
/** Returns true if two notes are the same, determined by their unique ID. */ | |||
bool operator== (const MPENote& other) const noexcept; | |||
/** Returns true if two notes are different notes, determined by their unique ID. */ | |||
bool operator!= (const MPENote& other) const noexcept; | |||
}; | |||
#endif // JUCE_MPENOTE_H_INCLUDED |
@@ -0,0 +1,356 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
MPESynthesiser::MPESynthesiser() | |||
{ | |||
} | |||
MPESynthesiser::MPESynthesiser (MPEInstrument* instrument) : MPESynthesiserBase (instrument) | |||
{ | |||
} | |||
MPESynthesiser::~MPESynthesiser() | |||
{ | |||
} | |||
//============================================================================== | |||
void MPESynthesiser::startVoice (MPESynthesiserVoice* voice, MPENote noteToStart) | |||
{ | |||
jassert (voice != nullptr); | |||
voice->currentlyPlayingNote = noteToStart; | |||
voice->noteStarted(); | |||
} | |||
void MPESynthesiser::stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff) | |||
{ | |||
jassert (voice != nullptr); | |||
voice->currentlyPlayingNote = noteToStop; | |||
voice->noteStopped (allowTailOff); | |||
} | |||
//============================================================================== | |||
void MPESynthesiser::noteAdded (MPENote newNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
if (MPESynthesiserVoice* voice = findFreeVoice (newNote, shouldStealVoices)) | |||
startVoice (voice, newNote); | |||
} | |||
void MPESynthesiser::notePressureChanged (MPENote changedNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (int i = 0; i < voices.size(); ++i) | |||
{ | |||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||
{ | |||
voice->currentlyPlayingNote = changedNote; | |||
voice->notePressureChanged(); | |||
} | |||
} | |||
} | |||
void MPESynthesiser::notePitchbendChanged (MPENote changedNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (int i = 0; i < voices.size(); ++i) | |||
{ | |||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||
{ | |||
voice->currentlyPlayingNote = changedNote; | |||
voice->notePitchbendChanged(); | |||
} | |||
} | |||
} | |||
void MPESynthesiser::noteTimbreChanged (MPENote changedNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (int i = 0; i < voices.size(); ++i) | |||
{ | |||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||
{ | |||
voice->currentlyPlayingNote = changedNote; | |||
voice->noteTimbreChanged(); | |||
} | |||
} | |||
} | |||
void MPESynthesiser::noteKeyStateChanged (MPENote changedNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (int i = 0; i < voices.size(); ++i) | |||
{ | |||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
if (voice->isCurrentlyPlayingNote (changedNote)) | |||
{ | |||
voice->currentlyPlayingNote = changedNote; | |||
voice->noteKeyStateChanged(); | |||
} | |||
} | |||
} | |||
void MPESynthesiser::noteReleased (MPENote finishedNote) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (int i = voices.size(); --i >= 0;) | |||
{ | |||
MPESynthesiserVoice* const voice = voices.getUnchecked (i); | |||
if (voice->isCurrentlyPlayingNote(finishedNote)) | |||
stopVoice (voice, finishedNote, true); | |||
} | |||
} | |||
void MPESynthesiser::setCurrentPlaybackSampleRate (const double newRate) | |||
{ | |||
MPESynthesiserBase::setCurrentPlaybackSampleRate (newRate); | |||
const ScopedLock sl (voicesLock); | |||
turnOffAllVoices (false); | |||
for (int i = voices.size(); --i >= 0;) | |||
voices.getUnchecked (i)->setCurrentSampleRate (newRate); | |||
} | |||
void MPESynthesiser::handleMidiEvent (const MidiMessage& m) | |||
{ | |||
if (m.isController()) | |||
handleController (m.getChannel(), m.getControllerNumber(), m.getControllerValue()); | |||
else if (m.isProgramChange()) | |||
handleProgramChange (m.getChannel(), m.getProgramChangeNumber()); | |||
MPESynthesiserBase::handleMidiEvent (m); | |||
} | |||
MPESynthesiserVoice* MPESynthesiser::findFreeVoice (MPENote noteToFindVoiceFor, bool stealIfNoneAvailable) const | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
for (int i = 0; i < voices.size(); ++i) | |||
{ | |||
MPESynthesiserVoice* const voice = voices.getUnchecked (i); | |||
if (! voice->isActive()) | |||
return voice; | |||
} | |||
if (stealIfNoneAvailable) | |||
return findVoiceToSteal (noteToFindVoiceFor); | |||
return nullptr; | |||
} | |||
struct MPEVoiceAgeSorter | |||
{ | |||
static int compareElements (MPESynthesiserVoice* v1, MPESynthesiserVoice* v2) noexcept | |||
{ | |||
return v1->wasStartedBefore (*v2) ? -1 : (v2->wasStartedBefore (*v1) ? 1 : 0); | |||
} | |||
}; | |||
MPESynthesiserVoice* MPESynthesiser::findVoiceToSteal (MPENote noteToStealVoiceFor) const | |||
{ | |||
// This voice-stealing algorithm applies the following heuristics: | |||
// - Re-use the oldest notes first | |||
// - Protect the lowest & topmost notes, even if sustained, but not if they've been released. | |||
// apparently you are trying to render audio without having any voices... | |||
jassert (voices.size() > 0); | |||
// These are the voices we want to protect (ie: only steal if unavoidable) | |||
MPESynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase | |||
MPESynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase | |||
// this is a list of voices we can steal, sorted by how long they've been running | |||
Array<MPESynthesiserVoice*> usableVoices; | |||
usableVoices.ensureStorageAllocated (voices.size()); | |||
for (int i = 0; i < voices.size(); ++i) | |||
{ | |||
MPESynthesiserVoice* const voice = voices.getUnchecked (i); | |||
jassert (voice->isActive()); // We wouldn't be here otherwise | |||
MPEVoiceAgeSorter sorter; | |||
usableVoices.addSorted (sorter, voice); | |||
if (! voice->isPlayingButReleased()) // Don't protect released notes | |||
{ | |||
const int noteNumber = voice->getCurrentlyPlayingNote().initialNote; | |||
if (low == nullptr || noteNumber < low->getCurrentlyPlayingNote().initialNote) | |||
low = voice; | |||
if (top == nullptr || noteNumber > top->getCurrentlyPlayingNote().initialNote) | |||
top = voice; | |||
} | |||
} | |||
// Eliminate pathological cases (ie: only 1 note playing): we always give precedence to the lowest note(s) | |||
if (top == low) | |||
top = nullptr; | |||
const int numUsableVoices = usableVoices.size(); | |||
// If we want to re-use the voice to trigger a new note, | |||
// then The oldest note that's playing the same note number is ideal. | |||
if (noteToStealVoiceFor.isValid()) | |||
{ | |||
for (int i = 0; i < numUsableVoices; ++i) | |||
{ | |||
MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
if (voice->getCurrentlyPlayingNote().initialNote == noteToStealVoiceFor.initialNote) | |||
return voice; | |||
} | |||
} | |||
// Oldest voice that has been released (no finger on it and not held by sustain pedal) | |||
for (int i = 0; i < numUsableVoices; ++i) | |||
{ | |||
MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
if (voice != low && voice != top && voice->isPlayingButReleased()) | |||
return voice; | |||
} | |||
// Oldest voice that doesn't have a finger on it: | |||
for (int i = 0; i < numUsableVoices; ++i) | |||
{ | |||
MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
if (voice != low && voice != top | |||
&& voice->getCurrentlyPlayingNote().keyState != MPENote::keyDown | |||
&& voice->getCurrentlyPlayingNote().keyState != MPENote::keyDownAndSustained) | |||
return voice; | |||
} | |||
// Oldest voice that isn't protected | |||
for (int i = 0; i < numUsableVoices; ++i) | |||
{ | |||
MPESynthesiserVoice* const voice = usableVoices.getUnchecked (i); | |||
if (voice != low && voice != top) | |||
return voice; | |||
} | |||
// We've only got "protected" voices now: lowest note takes priority | |||
jassert (low != nullptr); | |||
// Duophonic synth: give priority to the bass note: | |||
if (top != nullptr) | |||
return top; | |||
return low; | |||
} | |||
//============================================================================== | |||
void MPESynthesiser::addVoice (MPESynthesiserVoice* const newVoice) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
newVoice->setCurrentSampleRate (getSampleRate()); | |||
voices.add (newVoice); | |||
} | |||
void MPESynthesiser::clearVoices() | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
voices.clear(); | |||
} | |||
MPESynthesiserVoice* MPESynthesiser::getVoice (const int index) const | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
return voices [index]; | |||
} | |||
void MPESynthesiser::removeVoice (const int index) | |||
{ | |||
const ScopedLock sl (voicesLock); | |||
voices.remove (index); | |||
} | |||
void MPESynthesiser::reduceNumVoices (const int newNumVoices) | |||
{ | |||
// we can't possibly get to a negative number of voices... | |||
jassert (newNumVoices >= 0); | |||
const ScopedLock sl (voicesLock); | |||
while (voices.size() > newNumVoices) | |||
{ | |||
if (MPESynthesiserVoice* voice = findFreeVoice (MPENote(), true)) | |||
voices.removeObject (voice); | |||
else | |||
voices.remove (0); // if there's no voice to steal, kill the oldest voice | |||
} | |||
} | |||
void MPESynthesiser::turnOffAllVoices (bool allowTailOff) | |||
{ | |||
// first turn off all voices (it's more efficient to do this immediately | |||
// rather than to go through the MPEInstrument for this). | |||
for (int i = voices.size(); --i >= 0;) | |||
voices.getUnchecked (i)->noteStopped (allowTailOff); | |||
// finally make sure the MPE Instrument also doesn't have any notes anymore. | |||
instrument->releaseAllNotes(); | |||
} | |||
//============================================================================== | |||
void MPESynthesiser::renderNextSubBlock (AudioBuffer<float>& buffer, int startSample, int numSamples) | |||
{ | |||
for (int i = voices.size(); --i >= 0;) | |||
{ | |||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
if (voice->isActive()) | |||
voice->renderNextBlock (buffer, startSample, numSamples); | |||
} | |||
} | |||
void MPESynthesiser::renderNextSubBlock (AudioBuffer<double>& buffer, int startSample, int numSamples) | |||
{ | |||
for (int i = voices.size(); --i >= 0;) | |||
{ | |||
MPESynthesiserVoice* voice = voices.getUnchecked (i); | |||
if (voice->isActive()) | |||
voice->renderNextBlock (buffer, startSample, numSamples); | |||
} | |||
} |
@@ -0,0 +1,313 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_MPESynthesiser_H_INCLUDED | |||
#define JUCE_MPESynthesiser_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Base class for an MPE-compatible musical device that can play sounds. | |||
This class extends MPESynthesiserBase by adding the concept of voices, | |||
each of which can play a sound triggered by a MPENote that can be modulated | |||
by MPE dimensions like pressure, pitchbend, and timbre, while the note is | |||
sounding. | |||
To create a synthesiser, you'll need to create a subclass of MPESynthesiserVoice | |||
which can play back one of these sounds at a time. | |||
Then you can use the addVoice() methods to give the synthesiser a set of voices | |||
it can use to play notes. If you only give it one voice it will be monophonic - | |||
the more voices it has, the more polyphony it'll have available. | |||
Then repeatedly call the renderNextBlock() method to produce the audio (inherited | |||
from MPESynthesiserBase). The voices will be started, stopped, and modulated | |||
automatically, based on the MPE/MIDI messages that the synthesiser receives. | |||
Before rendering, be sure to call the setCurrentPlaybackSampleRate() to tell it | |||
what the target playback rate is. This value is passed on to the voices so that | |||
they can pitch their output correctly. | |||
@see MPESynthesiserBase, MPESythesiserVoice, MPENote, MPEInstrument | |||
*/ | |||
class JUCE_API MPESynthesiser : public MPESynthesiserBase | |||
{ | |||
public: | |||
//========================================================================== | |||
/** Constructor. | |||
You'll need to add some voices before it'll make any sound. | |||
@see addVoice | |||
*/ | |||
MPESynthesiser(); | |||
/** Constructor to pass to the synthesiser a custom MPEInstrument object | |||
to handle the MPE note state, MIDI channel assignment etc. | |||
(in case you need custom logic for this that goes beyond MIDI and MPE). | |||
The synthesiser will take ownership of this object. | |||
@see MPESynthesiserBase, MPEInstrument | |||
*/ | |||
MPESynthesiser (MPEInstrument* instrument); | |||
/** Destructor. */ | |||
~MPESynthesiser(); | |||
//========================================================================== | |||
/** Deletes all voices. */ | |||
void clearVoices(); | |||
/** Returns the number of voices that have been added. */ | |||
int getNumVoices() const noexcept { return voices.size(); } | |||
/** Returns one of the voices that have been added. */ | |||
MPESynthesiserVoice* getVoice (int index) const; | |||
/** Adds a new voice to the synth. | |||
All the voices should be the same class of object and are treated equally. | |||
The object passed in will be managed by the synthesiser, which will delete | |||
it later on when no longer needed. The caller should not retain a pointer to the | |||
voice. | |||
*/ | |||
void addVoice (MPESynthesiserVoice* newVoice); | |||
/** Deletes one of the voices. */ | |||
void removeVoice (int index); | |||
/** Reduces the number of voices to newNumVoices. | |||
This will repeatedly call findVoiceToSteal() and remove that voice, until | |||
the total number of voices equals newNumVoices. If newNumVoices is greater than | |||
or equal to the current number of voices, this method does nothing. | |||
*/ | |||
void reduceNumVoices (int newNumVoices); | |||
/** Release all MPE notes and turn off all voices. | |||
If allowTailOff is true, the voices will be allowed to fade out the notes gracefully | |||
(if they can do). If this is false, the notes will all be cut off immediately. | |||
This method is meant to be called by the user, for example to implement | |||
a MIDI panic button in a synth. | |||
*/ | |||
virtual void turnOffAllVoices (bool allowTailOff); | |||
//========================================================================== | |||
/** If set to true, then the synth will try to take over an existing voice if | |||
it runs out and needs to play another note. | |||
The value of this boolean is passed into findFreeVoice(), so the result will | |||
depend on the implementation of this method. | |||
*/ | |||
void setVoiceStealingEnabled (bool shouldSteal) noexcept { shouldStealVoices = shouldSteal; } | |||
/** Returns true if note-stealing is enabled. */ | |||
bool isVoiceStealingEnabled() const noexcept { return shouldStealVoices; } | |||
//========================================================================== | |||
/** Tells the synthesiser what the sample rate is for the audio it's being used to render. | |||
This overrides the implementation in MPESynthesiserBase, to additionally | |||
propagate the new value to the voices so that they can use it to render the correct | |||
pitches. | |||
*/ | |||
void setCurrentPlaybackSampleRate (double newRate) override; | |||
//========================================================================== | |||
/** Handle incoming MIDI events. | |||
This method will be called automatically according to the MIDI data passed | |||
into renderNextBlock(), but you can also call it yourself to manually | |||
inject MIDI events. | |||
This implementation forwards program change messages and non-MPE-related | |||
controller messages to handleProgramChange and handleController, respectively, | |||
and then simply calls through to MPESynthesiserBase::handleMidiEvent to deal | |||
with MPE-related MIDI messages used for MPE notes, zones etc. | |||
This method can be overridden further if you need to do custom MIDI | |||
handling on top of what is provided here. | |||
*/ | |||
void handleMidiEvent (const MidiMessage&) override; | |||
/** Callback for MIDI controller messages. The default implementation | |||
provided here does nothing; override this method if you need custom | |||
MIDI controller handling on top of MPE. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). | |||
*/ | |||
virtual void handleController (int /*midiChannel*/, | |||
int /*controllerNumber*/, | |||
int /*controllerValue*/) {} | |||
/** Callback for MIDI program change messages. The default implementation | |||
provided here does nothing; override this method if you need to handle | |||
those messages. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). | |||
*/ | |||
virtual void handleProgramChange (int /*midiChannel*/, | |||
int /*programNumber*/) {} | |||
protected: | |||
//============================================================================== | |||
/** Attempts to start playing a new note. | |||
The default method here will find a free voice that is appropriate for | |||
playing the given MPENote, and use that voice to start playing the sound. | |||
If isNoteStealingEnabled returns true (set this by calling setNoteStealingEnabled), | |||
the synthesiser will use the voice stealing algorithm to find a free voice for | |||
the note (if no voices are free otherwise). | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state | |||
will become inconsistent. | |||
*/ | |||
virtual void noteAdded (MPENote newNote) override; | |||
/** Stops playing a note. | |||
This will be called whenever an MPE note is released (either by a note-off message, | |||
or by a sustain/sostenuto pedal release for a note that already received a note-off), | |||
and should therefore stop playing. | |||
This will find any voice that is currently playing finishedNote, | |||
turn its currently playing note off, and call its noteStopped callback. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself, otherwise the internal MPE note state | |||
will become inconsistent. | |||
*/ | |||
virtual void noteReleased (MPENote finishedNote) override; | |||
/** Will find any voice that is currently playing changedNote, update its | |||
currently playing note, and call its notePressureChanged method. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself. | |||
*/ | |||
virtual void notePressureChanged (MPENote changedNote) override; | |||
/** Will find any voice that is currently playing changedNote, update its | |||
currently playing note, and call its notePitchbendChanged method. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself. | |||
*/ | |||
virtual void notePitchbendChanged (MPENote changedNote) override; | |||
/** Will find any voice that is currently playing changedNote, update its | |||
currently playing note, and call its noteTimbreChanged method. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself. | |||
*/ | |||
virtual void noteTimbreChanged (MPENote changedNote) override; | |||
/** Will find any voice that is currently playing changedNote, update its | |||
currently playing note, and call its noteKeyStateChanged method. | |||
This method will be called automatically according to the midi data passed into | |||
renderNextBlock(). Do not call it yourself. | |||
*/ | |||
virtual void noteKeyStateChanged (MPENote changedNote) override; | |||
//========================================================================== | |||
/** This will simply call renderNextBlock for each currently active | |||
voice and fill the buffer with the sum. | |||
Override this method if you need to do more work to render your audio. | |||
*/ | |||
virtual void renderNextSubBlock (AudioBuffer<float>& outputAudio, | |||
int startSample, | |||
int numSamples) override; | |||
/** This will simply call renderNextBlock for each currently active | |||
voice and fill the buffer with the sum. (souble-precision version) | |||
Override this method if you need to do more work to render your audio. | |||
*/ | |||
virtual void renderNextSubBlock (AudioBuffer<double>& outputAudio, | |||
int startSample, | |||
int numSamples) override; | |||
//========================================================================== | |||
/** Searches through the voices to find one that's not currently playing, and | |||
which can play the given MPE note. | |||
If all voices are active and stealIfNoneAvailable is false, this returns | |||
a nullptr. If all voices are active and stealIfNoneAvailable is true, | |||
this will call findVoiceToSteal() to find a voice. | |||
If you need to find a free voice for something else than playing a note | |||
(e.g. for deleting it), you can pass an invalid (default-constructed) MPENote. | |||
*/ | |||
virtual MPESynthesiserVoice* findFreeVoice (MPENote noteToFindVoiceFor, | |||
bool stealIfNoneAvailable) const; | |||
/** Chooses a voice that is most suitable for being re-used to play a new | |||
note, or for being deleted by reduceNumVoices. | |||
The default method will attempt to find the oldest voice that isn't the | |||
bottom or top note being played. If that's not suitable for your synth, | |||
you can override this method and do something more cunning instead. | |||
If you pass a valid MPENote for the optional argument, then the note number | |||
of that note will be taken into account for finding the ideal voice to steal. | |||
If you pass an invalid (default-constructed) MPENote instead, this part of | |||
the algorithm will be ignored. | |||
*/ | |||
virtual MPESynthesiserVoice* findVoiceToSteal (MPENote noteToStealVoiceFor = MPENote()) const; | |||
/** Starts a specified voice and tells it to play a particular MPENote. | |||
You should never need to call this, it's called internally by | |||
MPESynthesiserBase::instrument via the noteStarted callback, | |||
but is protected in case it's useful for some custom subclasses. | |||
*/ | |||
void startVoice (MPESynthesiserVoice* voice, MPENote noteToStart); | |||
/** Stops a given voice and tells it to stop playing a particular MPENote | |||
(which should be the same note it is actually playing). | |||
You should never need to call this, it's called internally by | |||
MPESynthesiserBase::instrument via the noteReleased callback, | |||
but is protected in case it's useful for some custom subclasses. | |||
*/ | |||
void stopVoice (MPESynthesiserVoice* voice, MPENote noteToStop, bool allowTailOff); | |||
//========================================================================== | |||
OwnedArray<MPESynthesiserVoice> voices; | |||
private: | |||
//========================================================================== | |||
bool shouldStealVoices; | |||
CriticalSection voicesLock; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiser) | |||
}; | |||
#endif // JUCE_MPESynthesiser_H_INCLUDED |
@@ -0,0 +1,161 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
MPESynthesiserBase::MPESynthesiserBase() | |||
: instrument (new MPEInstrument), | |||
sampleRate (0), | |||
minimumSubBlockSize (32) | |||
{ | |||
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::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); | |||
int midiEventPos; | |||
MidiMessage m; | |||
const ScopedLock sl (renderAudioLock); | |||
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 < minimumSubBlockSize) | |||
{ | |||
handleMidiEvent (m); | |||
continue; | |||
} | |||
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 (renderAudioLock); | |||
instrument->releaseAllNotes(); | |||
sampleRate = newRate; | |||
} | |||
} | |||
//============================================================================== | |||
void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples) noexcept | |||
{ | |||
jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | |||
minimumSubBlockSize = numSamples; | |||
} |
@@ -0,0 +1,194 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_MPESynthesiserBase_H_INCLUDED | |||
#define JUCE_MPESynthesiserBase_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Derive from this class to create a basic audio generator capable of MPE. | |||
Implement the callbacks of MPEInstrument::Listener (noteAdded, notePressureChanged | |||
etc.) to let your audio generator know that MPE notes were triggered, modulated, | |||
or released. What to do inside them, and how that influences your audio generator, | |||
is up to you! | |||
This class uses an instance of MPEInstrument internally to handle the MPE | |||
note state logic. | |||
This class is a very low-level base class for an MPE instrument. If you need | |||
something more sophisticated, have a look at MPESynthesiser. This class extends | |||
MPESynthesiserBase by adding the concept of voices that can play notes, | |||
a voice stealing algorithm, and much more. | |||
@see MPESynthesiser, MPEInstrument | |||
*/ | |||
struct JUCE_API MPESynthesiserBase : public MPEInstrument::Listener | |||
{ | |||
public: | |||
//========================================================================== | |||
/** Constructor. */ | |||
MPESynthesiserBase(); | |||
/** Constructor. | |||
If you use this constructor, the synthesiser will take ownership of the | |||
provided instrument object, and will use it internally to handle the | |||
MPE note state logic. | |||
This is useful if you want to use an instance of your own class derived | |||
from MPEInstrument for the MPE logic. | |||
*/ | |||
MPESynthesiserBase (MPEInstrument* instrument); | |||
//========================================================================== | |||
/** Returns the synthesiser's internal MPE zone layout. | |||
This happens by value, to enforce thread-safety and class invariants. | |||
*/ | |||
MPEZoneLayout getZoneLayout() const noexcept; | |||
/** Re-sets the synthesiser's internal MPE zone layout to the one passed in. | |||
As a side effect, this will discard all currently playing notes, | |||
call noteReleased for all of them, and disable legacy mode (if previously enabled). | |||
*/ | |||
void setZoneLayout (MPEZoneLayout newLayout); | |||
//========================================================================== | |||
/** Tells the synthesiser what the sample rate is for the audio it's being | |||
used to render. | |||
*/ | |||
virtual void setCurrentPlaybackSampleRate (double sampleRate); | |||
/** Returns the current target sample rate at which rendering is being done. | |||
Subclasses may need to know this so that they can pitch things correctly. | |||
*/ | |||
double getSampleRate() const noexcept { return sampleRate; } | |||
//========================================================================== | |||
/** Creates the next block of audio output. | |||
Call this to make sound. This will chop up the AudioBuffer into subBlock | |||
pieces separated by events in the MIDI buffer, and then call | |||
processNextSubBlock on each one of them. In between you will get calls | |||
to noteAdded/Changed/Finished, where you can update parameters that | |||
depend on those notes to use for your audio rendering. | |||
*/ | |||
template <typename floatType> | |||
void renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||
const MidiBuffer& inputMidi, | |||
int startSample, | |||
int numSamples); | |||
//========================================================================== | |||
/** Handle incoming MIDI events (called from renderNextBlock). | |||
The default implementation provided here simply forwards everything | |||
to MPEInstrument::processNextMidiEvent, where it is used to update the | |||
MPE notes, zones etc. MIDI messages not relevant for MPE are ignored. | |||
This method can be overridden if you need to do custom MIDI handling | |||
on top of MPE. The MPESynthesiser class overrides this to implement | |||
callbacks for MIDI program changes and non-MPE-related MIDI controller | |||
messages. | |||
*/ | |||
virtual void handleMidiEvent (const MidiMessage&); | |||
//========================================================================== | |||
/** Sets a minimum limit on the size to which audio sub-blocks will be divided when rendering. | |||
When rendering, the audio blocks that are passed into renderNextBlock() will be split up | |||
into smaller blocks that lie between all the incoming midi messages, and it is these smaller | |||
sub-blocks that are rendered with multiple calls to renderVoices(). | |||
Obviously in a pathological case where there are midi messages on every sample, then | |||
renderVoices() could be called once per sample and lead to poor performance, so this | |||
setting allows you to set a lower limit on the block size. | |||
The default setting is 32, which means that midi messages are accurate to about < 1ms | |||
accuracy, which is probably fine for most purposes, but you may want to increase or | |||
decrease this value for your synth. | |||
*/ | |||
void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; | |||
//========================================================================== | |||
/** Puts the synthesiser into legacy mode. | |||
@param pitchbendRange The note pitchbend range in semitones to use when in legacy mode. | |||
Must be between 0 and 96, otherwise behaviour is undefined. | |||
The default pitchbend range in legacy mode is +/- 2 semitones. | |||
@param channelRange The range of MIDI channels to use for notes when in legacy mode. | |||
The default is to use all MIDI channels (1-16). | |||
To get out of legacy mode, set a new MPE zone layout using setZoneLayout. | |||
*/ | |||
void enableLegacyMode (int pitchbendRange = 2, | |||
Range<int> channelRange = Range<int> (1, 17)); | |||
/** Returns true if the instrument is in legacy mode, false otherwise. */ | |||
bool isLegacyModeEnabled() const noexcept; | |||
/** Returns the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||
Range<int> getLegacyModeChannelRange() const noexcept; | |||
/** Re-sets the range of MIDI channels (1-16) to be used for notes when in legacy mode. */ | |||
void setLegacyModeChannelRange (Range<int> channelRange); | |||
/** Returns the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||
int getLegacyModePitchbendRange() const noexcept; | |||
/** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||
void setLegacyModePitchbendRange (int pitchbendRange); | |||
protected: | |||
//========================================================================== | |||
/** Implement this method to render your audio inside. | |||
@see renderNextBlock | |||
*/ | |||
virtual void renderNextSubBlock (AudioBuffer<float>& outputAudio, | |||
int startSample, | |||
int numSamples) = 0; | |||
/** Implement this method if you want to render 64-bit audio as well; | |||
otherwise leave blank. | |||
*/ | |||
virtual void renderNextSubBlock (AudioBuffer<double>& /*outputAudio*/, | |||
int /*startSample*/, | |||
int /*numSamples*/) {} | |||
protected: | |||
//========================================================================== | |||
/** @internal */ | |||
ScopedPointer<MPEInstrument> instrument; | |||
/** @internal */ | |||
CriticalSection renderAudioLock; | |||
private: | |||
//========================================================================== | |||
double sampleRate; | |||
int minimumSubBlockSize; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) | |||
}; | |||
#endif // JUCE_MPESynthesiserBase_H_INCLUDED |
@@ -0,0 +1,53 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
MPESynthesiserVoice::MPESynthesiserVoice() | |||
: currentSampleRate (0), noteStartTime (0) | |||
{ | |||
} | |||
MPESynthesiserVoice::~MPESynthesiserVoice() | |||
{ | |||
} | |||
//============================================================================== | |||
bool MPESynthesiserVoice::isCurrentlyPlayingNote (MPENote note) const noexcept | |||
{ | |||
return isActive() && currentlyPlayingNote.noteID == note.noteID; | |||
} | |||
bool MPESynthesiserVoice::isPlayingButReleased() const noexcept | |||
{ | |||
return isActive() && currentlyPlayingNote.keyState == MPENote::off; | |||
} | |||
bool MPESynthesiserVoice::wasStartedBefore (const MPESynthesiserVoice& other) const noexcept | |||
{ | |||
return noteStartTime < other.noteStartTime; | |||
} | |||
void MPESynthesiserVoice::clearCurrentNote() noexcept | |||
{ | |||
currentlyPlayingNote = MPENote(); | |||
} |
@@ -0,0 +1,191 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_MPEVoice_H_INCLUDED | |||
#define JUCE_MPEVoice_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
Represents an MPE voice that an MPESynthesiser can use to play a sound. | |||
A voice plays a single sound at a time, and a synthesiser holds an array of | |||
voices so that it can play polyphonically. | |||
@see MPESynthesiser, MPENote | |||
*/ | |||
class JUCE_API MPESynthesiserVoice | |||
{ | |||
public: | |||
//======================================================================== | |||
/** Constructor. */ | |||
MPESynthesiserVoice(); | |||
/** Destructor. */ | |||
virtual ~MPESynthesiserVoice(); | |||
/** Returns the MPENote that this voice is currently playing. | |||
Returns an invalid MPENote if no note is playing | |||
(you can check this using MPENote::isValid() or MPEVoice::isActive()). | |||
*/ | |||
MPENote getCurrentlyPlayingNote() const noexcept { return currentlyPlayingNote; } | |||
/** Returns true if the voice is currently playing the given MPENote | |||
(as identified by the note's initial note number and MIDI channel). | |||
*/ | |||
bool isCurrentlyPlayingNote (MPENote note) const noexcept; | |||
/** Returns true if this voice is currently busy playing a sound. | |||
By default this just checks whether getCurrentlyPlayingNote() | |||
returns a valid MPE note, but can be overridden for more advanced checking. | |||
*/ | |||
virtual bool isActive() const { return currentlyPlayingNote.isValid(); } | |||
/** Returns true if a voice is sounding in its release phase. **/ | |||
bool isPlayingButReleased() const noexcept; | |||
/** Called by the MPESynthesiser to let the voice know that a new note has started on it. | |||
This will be called during the rendering callback, so must be fast and thread-safe. | |||
*/ | |||
virtual void noteStarted() = 0; | |||
/** Called by the MPESynthesiser to let the voice know that its currently playing note has stopped. | |||
This will be called during the rendering callback, so must be fast and thread-safe. | |||
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 noteStopped (bool allowTailOff) = 0; | |||
/** Called by the MPESynthesiser to let the voice know that its currently playing note | |||
has changed its pressure value. | |||
This will be called during the rendering callback, so must be fast and thread-safe. | |||
*/ | |||
virtual void notePressureChanged() = 0; | |||
/** Called by the MPESynthesiser to let the voice know that its currently playing note | |||
has changed its pitchbend value. | |||
This will be called during the rendering callback, so must be fast and thread-safe. | |||
Note: You can call currentlyPlayingNote.getFrequencyInHertz() to find out the effective frequency | |||
of the note, as a sum of the initial note number, the per-note pitchbend and the master pitchbend. | |||
*/ | |||
virtual void notePitchbendChanged() = 0; | |||
/** Called by the MPESynthesiser to let the voice know that its currently playing note | |||
has changed its timbre value. | |||
This will be called during the rendering callback, so must be fast and thread-safe. | |||
*/ | |||
virtual void noteTimbreChanged() = 0; | |||
/** Called by the MPESynthesiser to let the voice know that its currently playing note | |||
has changed its key state. | |||
This typically happens when a sustain or sostenuto pedal is pressed or released (on | |||
an MPE channel relevant for this note), or if the note key is lifted while the sustained | |||
or sostenuto pedal is still held down. | |||
This will be called during the rendering callback, so must be fast and thread-safe. | |||
*/ | |||
virtual void noteKeyStateChanged() = 0; | |||
/** 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; | |||
/** Renders the next block of 64-bit data for this voice. | |||
Support for 64-bit audio is optional. You can choose to not override this method if | |||
you don't need it (the default implementation simply does nothing). | |||
*/ | |||
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 setCurrentSampleRate (double newRate) { currentSampleRate = newRate; } | |||
/** 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 this voice started playing its current note before the other voice did. */ | |||
bool wasStartedBefore (const MPESynthesiserVoice& 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() noexcept; | |||
//========================================================================== | |||
double currentSampleRate; | |||
MPENote currentlyPlayingNote; | |||
private: | |||
//========================================================================== | |||
friend class MPESynthesiser; | |||
uint32 noteStartTime; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserVoice) | |||
}; | |||
#endif // JUCE_MPEVoice_H_INCLUDED |
@@ -0,0 +1,170 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
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") {} | |||
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::fabs (expectedValue)) * maxRelativeError; | |||
expect (std::fabs (expectedValue - actualValue) < maxAbsoluteError); | |||
} | |||
}; | |||
static MPEValueTests MPEValueUnitTests; | |||
#endif // JUCE_UNIT_TESTS |
@@ -0,0 +1,96 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_MPEVALUE_H_INCLUDED | |||
#define JUCE_MPEVALUE_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
This class represents a single value for any of the MPE | |||
dimensions of control. It supports values with 7-bit or 14-bit resolutions | |||
(corresponding to 1 or 2 MIDI bytes, respectively). It also offers helper | |||
functions to query the value in a variety of representations that can be | |||
useful in an audio or MIDI context. | |||
*/ | |||
class JUCE_API MPEValue | |||
{ | |||
public: | |||
//========================================================================== | |||
/** Default constructor. Constructs an MPEValue corresponding | |||
to the centre value. | |||
*/ | |||
MPEValue() noexcept; | |||
/** Constructs an MPEValue from an integer between 0 and 127 | |||
(using 7-bit precision). | |||
*/ | |||
static MPEValue from7BitInt (int value) noexcept; | |||
/** Constructs an MPEValue from an integer between 0 and 16383 | |||
(using 14-bit precision). | |||
*/ | |||
static MPEValue from14BitInt (int value) noexcept; | |||
/** Constructs an MPEValue corresponding to the centre value. */ | |||
static MPEValue centreValue() noexcept; | |||
/** Constructs an MPEValue corresponding to the minimum value. */ | |||
static MPEValue minValue() noexcept; | |||
/** Constructs an MPEValue corresponding to the maximum value. */ | |||
static MPEValue maxValue() noexcept; | |||
/** Retrieves the current value as an integer between 0 and 127. | |||
Information will be lost if the value was initialised with a precision | |||
higher than 7-bit. | |||
*/ | |||
int as7BitInt() const noexcept; | |||
/** Retrieves the current value as an integer between 0 and 16383. | |||
Resolution will be lost if the value was initialised with a precision | |||
higher than 14-bit. | |||
*/ | |||
int as14BitInt() const noexcept; | |||
/** Retrieves the current value mapped to a float between -1.0f and 1.0f. */ | |||
float asSignedFloat() const noexcept; | |||
/** Retrieves the current value mapped to a float between 0.0f and 1.0f. */ | |||
float asUnsignedFloat() const noexcept; | |||
/** Returns true if two values are equal. */ | |||
bool operator== (const MPEValue& other) const noexcept; | |||
/** Returns true if two values are not equal. */ | |||
bool operator!= (const MPEValue& other) const noexcept; | |||
private: | |||
//========================================================================== | |||
MPEValue (int normalisedValue); | |||
int normalisedValue; | |||
}; | |||
#endif // JUCE_MPEVALUE_H_INCLUDED |
@@ -0,0 +1,302 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
namespace | |||
{ | |||
void checkAndLimitZoneParameters (int minValue, | |||
int maxValue, | |||
int& valueToCheckAndLimit) noexcept | |||
{ | |||
if (valueToCheckAndLimit < minValue || valueToCheckAndLimit > maxValue) | |||
{ | |||
// if you hit this, one of the parameters you supplied for MPEZone | |||
// was not within the allowed range! | |||
// we fit this back into the allowed range here to maintain a valid | |||
// state for the zone, but probably the resulting zone is not what you | |||
//wanted it to be! | |||
jassertfalse; | |||
valueToCheckAndLimit = jlimit (minValue, maxValue, valueToCheckAndLimit); | |||
} | |||
} | |||
} | |||
//============================================================================== | |||
MPEZone::MPEZone (int masterChannel_, | |||
int numNoteChannels_, | |||
int perNotePitchbendRange_, | |||
int masterPitchbendRange_) noexcept | |||
: masterChannel (masterChannel_), | |||
numNoteChannels (numNoteChannels_), | |||
perNotePitchbendRange (perNotePitchbendRange_), | |||
masterPitchbendRange (masterPitchbendRange_) | |||
{ | |||
checkAndLimitZoneParameters (1, 15, masterChannel); | |||
checkAndLimitZoneParameters (1, 16 - masterChannel, numNoteChannels); | |||
checkAndLimitZoneParameters (0, 96, perNotePitchbendRange); | |||
checkAndLimitZoneParameters (0, 96, masterPitchbendRange); | |||
} | |||
//============================================================================== | |||
int MPEZone::getMasterChannel() const noexcept | |||
{ | |||
return masterChannel; | |||
} | |||
int MPEZone::getNumNoteChannels() const noexcept | |||
{ | |||
return numNoteChannels; | |||
} | |||
int MPEZone::getFirstNoteChannel() const noexcept | |||
{ | |||
return masterChannel + 1; | |||
} | |||
int MPEZone::getLastNoteChannel() const noexcept | |||
{ | |||
return masterChannel + numNoteChannels; | |||
} | |||
Range<int> MPEZone::getNoteChannelRange() const noexcept | |||
{ | |||
return Range<int>::withStartAndLength (getFirstNoteChannel(), getNumNoteChannels()); | |||
} | |||
bool MPEZone::isUsingChannel (int channel) const noexcept | |||
{ | |||
jassert (channel > 0 && channel <= 16); | |||
return channel >= masterChannel && channel <= masterChannel + numNoteChannels; | |||
} | |||
bool MPEZone::isUsingChannelAsNoteChannel (int channel) const noexcept | |||
{ | |||
jassert (channel > 0 && channel <= 16); | |||
return channel > masterChannel && channel <= masterChannel + numNoteChannels; | |||
} | |||
int MPEZone::getPerNotePitchbendRange() const noexcept | |||
{ | |||
return perNotePitchbendRange; | |||
} | |||
int MPEZone::getMasterPitchbendRange() const noexcept | |||
{ | |||
return masterPitchbendRange; | |||
} | |||
void MPEZone::setPerNotePitchbendRange (int rangeInSemitones) noexcept | |||
{ | |||
checkAndLimitZoneParameters (0, 96, rangeInSemitones); | |||
perNotePitchbendRange = rangeInSemitones; | |||
} | |||
void MPEZone::setMasterPitchbendRange (int rangeInSemitones) noexcept | |||
{ | |||
checkAndLimitZoneParameters (0, 96, rangeInSemitones); | |||
masterPitchbendRange = rangeInSemitones; | |||
} | |||
//============================================================================== | |||
bool MPEZone::overlapsWith (MPEZone other) const noexcept | |||
{ | |||
if (masterChannel == other.masterChannel) | |||
return true; | |||
if (masterChannel > other.masterChannel) | |||
return other.overlapsWith (*this); | |||
return masterChannel + numNoteChannels >= other.masterChannel; | |||
} | |||
//============================================================================== | |||
bool MPEZone::truncateToFit (MPEZone other) noexcept | |||
{ | |||
const int masterChannelDiff = other.masterChannel - masterChannel; | |||
// we need at least 2 channels to be left after truncation: | |||
// 1 master channel and 1 note channel. otherwise we can't truncate. | |||
if (masterChannelDiff < 2) | |||
return false; | |||
numNoteChannels = jmin (numNoteChannels, masterChannelDiff - 1); | |||
return true; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
class MPEZoneTests : public UnitTest | |||
{ | |||
public: | |||
MPEZoneTests() : UnitTest ("MPEZone class") {} | |||
void runTest() override | |||
{ | |||
beginTest ("initialisation"); | |||
{ | |||
{ | |||
MPEZone zone (1, 10); | |||
expectEquals (zone.getMasterChannel(), 1); | |||
expectEquals (zone.getNumNoteChannels(), 10); | |||
expectEquals (zone.getFirstNoteChannel(), 2); | |||
expectEquals (zone.getLastNoteChannel(), 11); | |||
expectEquals (zone.getPerNotePitchbendRange(), 48); | |||
expectEquals (zone.getMasterPitchbendRange(), 2); | |||
expect (zone.isUsingChannel (1)); | |||
expect (zone.isUsingChannel (2)); | |||
expect (zone.isUsingChannel (10)); | |||
expect (zone.isUsingChannel (11)); | |||
expect (! zone.isUsingChannel (12)); | |||
expect (! zone.isUsingChannel (16)); | |||
expect (! zone.isUsingChannelAsNoteChannel (1)); | |||
expect (zone.isUsingChannelAsNoteChannel (2)); | |||
expect (zone.isUsingChannelAsNoteChannel (10)); | |||
expect (zone.isUsingChannelAsNoteChannel (11)); | |||
expect (! zone.isUsingChannelAsNoteChannel (12)); | |||
expect (! zone.isUsingChannelAsNoteChannel (16)); | |||
} | |||
{ | |||
MPEZone zone (5, 4); | |||
expectEquals (zone.getMasterChannel(), 5); | |||
expectEquals (zone.getNumNoteChannels(), 4); | |||
expectEquals (zone.getFirstNoteChannel(), 6); | |||
expectEquals (zone.getLastNoteChannel(), 9); | |||
expectEquals (zone.getPerNotePitchbendRange(), 48); | |||
expectEquals (zone.getMasterPitchbendRange(), 2); | |||
expect (! zone.isUsingChannel (1)); | |||
expect (! zone.isUsingChannel (4)); | |||
expect (zone.isUsingChannel (5)); | |||
expect (zone.isUsingChannel (6)); | |||
expect (zone.isUsingChannel (8)); | |||
expect (zone.isUsingChannel (9)); | |||
expect (! zone.isUsingChannel (10)); | |||
expect (! zone.isUsingChannel (16)); | |||
expect (! zone.isUsingChannelAsNoteChannel (5)); | |||
expect (zone.isUsingChannelAsNoteChannel (6)); | |||
expect (zone.isUsingChannelAsNoteChannel (8)); | |||
expect (zone.isUsingChannelAsNoteChannel (9)); | |||
expect (! zone.isUsingChannelAsNoteChannel (10)); | |||
} | |||
} | |||
beginTest ("getNoteChannelRange"); | |||
{ | |||
MPEZone zone (2, 10); | |||
Range<int> noteChannelRange = zone.getNoteChannelRange(); | |||
expectEquals (noteChannelRange.getStart(), 3); | |||
expectEquals (noteChannelRange.getEnd(), 13); | |||
} | |||
beginTest ("setting master pitchbend range"); | |||
{ | |||
MPEZone zone (1, 10); | |||
zone.setMasterPitchbendRange (96); | |||
expectEquals (zone.getMasterPitchbendRange(), 96); | |||
zone.setMasterPitchbendRange (0); | |||
expectEquals (zone.getMasterPitchbendRange(), 0); | |||
expectEquals (zone.getPerNotePitchbendRange(), 48); | |||
} | |||
beginTest ("setting per-note pitchbend range"); | |||
{ | |||
MPEZone zone (1, 10); | |||
zone.setPerNotePitchbendRange (96); | |||
expectEquals (zone.getPerNotePitchbendRange(), 96); | |||
zone.setPerNotePitchbendRange (0); | |||
expectEquals (zone.getPerNotePitchbendRange(), 0); | |||
expectEquals (zone.getMasterPitchbendRange(), 2); | |||
} | |||
beginTest ("checking overlap"); | |||
{ | |||
testOverlapsWith (1, 10, 1, 10, true); | |||
testOverlapsWith (1, 4, 6, 3, false); | |||
testOverlapsWith (1, 4, 8, 3, false); | |||
testOverlapsWith (2, 10, 2, 8, true); | |||
testOverlapsWith (1, 10, 3, 2, true); | |||
testOverlapsWith (3, 10, 5, 9, true); | |||
} | |||
beginTest ("truncating"); | |||
{ | |||
testTruncateToFit (1, 10, 3, 10, true, 1, 1); | |||
testTruncateToFit (3, 10, 1, 10, false, 3, 10); | |||
testTruncateToFit (1, 10, 5, 8, true, 1, 3); | |||
testTruncateToFit (5, 8, 1, 10, false, 5, 8); | |||
testTruncateToFit (1, 10, 4, 3, true, 1, 2); | |||
testTruncateToFit (4, 3, 1, 10, false, 4, 3); | |||
testTruncateToFit (1, 3, 5, 3, true, 1, 3); | |||
testTruncateToFit (5, 3, 1, 3, false, 5, 3); | |||
testTruncateToFit (1, 3, 7, 3, true, 1, 3); | |||
testTruncateToFit (7, 3, 1, 3, false, 7, 3); | |||
testTruncateToFit (1, 10, 2, 10, false, 1, 10); | |||
testTruncateToFit (2, 10, 1, 10, false, 2, 10); | |||
} | |||
} | |||
private: | |||
//========================================================================== | |||
void testOverlapsWith (int masterChannelFirst, int numNoteChannelsFirst, | |||
int masterChannelSecond, int numNoteChannelsSecond, | |||
bool expectedRetVal) | |||
{ | |||
MPEZone first (masterChannelFirst, numNoteChannelsFirst); | |||
MPEZone second (masterChannelSecond, numNoteChannelsSecond); | |||
expect (first.overlapsWith (second) == expectedRetVal); | |||
expect (second.overlapsWith (first) == expectedRetVal); | |||
} | |||
//========================================================================== | |||
void testTruncateToFit (int masterChannelFirst, int numNoteChannelsFirst, | |||
int masterChannelSecond, int numNoteChannelsSecond, | |||
bool expectedRetVal, | |||
int masterChannelFirstAfter, int numNoteChannelsFirstAfter) | |||
{ | |||
MPEZone first (masterChannelFirst, numNoteChannelsFirst); | |||
MPEZone second (masterChannelSecond, numNoteChannelsSecond); | |||
expect (first.truncateToFit (second) == expectedRetVal); | |||
expectEquals (first.getMasterChannel(), masterChannelFirstAfter); | |||
expectEquals (first.getNumNoteChannels(), numNoteChannelsFirstAfter); | |||
} | |||
}; | |||
static MPEZoneTests MPEZoneUnitTests; | |||
#endif // JUCE_UNIT_TESTS |
@@ -0,0 +1,132 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_MPEZONE_H_INCLUDED | |||
#define JUCE_MPEZONE_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
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 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 of the lowest-numbered note channel of this zone. */ | |||
int getFirstNoteChannel() const noexcept; | |||
/* Returns the MIDI channel number of the highest-numbered note channel of this zone. */ | |||
int getLastNoteChannel() const noexcept; | |||
/** Returns the MIDI channel numbers 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; | |||
private: | |||
//========================================================================== | |||
int masterChannel; | |||
int numNoteChannels; | |||
int perNotePitchbendRange; | |||
int masterPitchbendRange; | |||
}; | |||
#endif // JUCE_MPEZONE_H_INCLUDED |
@@ -0,0 +1,346 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
MPEZoneLayout::MPEZoneLayout() noexcept | |||
{ | |||
} | |||
//============================================================================== | |||
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); | |||
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(); | |||
} | |||
//============================================================================== | |||
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)) | |||
{ | |||
zone->setPerNotePitchbendRange (rpn.value); | |||
return; | |||
} | |||
if (MPEZone* zone = getZoneByMasterChannel (rpn.channel)) | |||
zone->setMasterPitchbendRange (rpn.value); | |||
} | |||
//============================================================================== | |||
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; | |||
} | |||
//============================================================================== | |||
//============================================================================== | |||
#if JUCE_UNIT_TESTS | |||
class MPEZoneLayoutTests : public UnitTest | |||
{ | |||
public: | |||
MPEZoneLayoutTests() : UnitTest ("MPEZoneLayout class") {} | |||
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 |
@@ -0,0 +1,129 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
#ifndef JUCE_MPEZONELAYOUT_H_INCLUDED | |||
#define JUCE_MPEZONELAYOUT_H_INCLUDED | |||
//============================================================================== | |||
/** | |||
This class represents the current MPE zone layout of a device | |||
capable of handling MPE. | |||
Use the MPEMessages helper class to convert the zone layout represented | |||
by this object to MIDI message sequences that you can send to an Expressive | |||
MIDI device to set its zone layout, add zones etc. | |||
@see MPEZone, MPEInstrument | |||
*/ | |||
class JUCE_API MPEZoneLayout | |||
{ | |||
public: | |||
/** Default constructor. | |||
This will create a layout with no MPE zones. | |||
You can add an MPE zone using the method addZone. | |||
*/ | |||
MPEZoneLayout() noexcept; | |||
/** Adds a new MPE zone to the layout. | |||
@param newZone The zone to add. | |||
@return true if the zone was added without modifying any other zones | |||
added previously to the same zone layout object (if any); | |||
false if any existing MPE zones had to be truncated | |||
or deleted entirely in order to to add this new zone. | |||
(Note: the zone itself will always be added with the channel bounds | |||
that were specified; this will not fail.) | |||
*/ | |||
bool addZone (MPEZone newZone); | |||
/** Removes all currently present MPE zones. */ | |||
void clearAllZones(); | |||
/** Pass incoming MIDI messages to an object of this class if you want the | |||
zone layout to properly react to MPE RPN messages like an | |||
MPE device. | |||
MPEMessages::rpnNumber will add or remove zones; RPN 0 will | |||
set the per-note or master pitchbend ranges. | |||
Any other MIDI messages will be ignored by this class. | |||
@see MPEMessages | |||
*/ | |||
void processNextMidiEvent (const MidiMessage& message); | |||
/** Pass incoming MIDI buffers to an object of this class if you want the | |||
zone layout to properly react to MPE RPN messages like an | |||
MPE device. | |||
MPEMessages::rpnNumber will add or remove zones; RPN 0 will | |||
set the per-note or master pitchbend ranges. | |||
Any other MIDI messages will be ignored by this class. | |||
@see MPEMessages | |||
*/ | |||
void processNextMidiBuffer (const MidiBuffer& buffer); | |||
/** Returns the current number of MPE zones. */ | |||
int getNumZones() const noexcept; | |||
/** Returns a pointer to the MPE zone at the given index, | |||
or nullptr if there is no such zone. | |||
*/ | |||
MPEZone* getZoneByIndex (int index) const noexcept; | |||
/** Returns a pointer to the zone which uses the specified channel (1-16), | |||
or nullptr if there is no such zone. | |||
*/ | |||
MPEZone* getZoneByChannel (int midiChannel) const noexcept; | |||
/** Returns a pointer to the zone which has the specified channel (1-16) | |||
as its master channel, or nullptr if there is no such zone. | |||
*/ | |||
MPEZone* getZoneByMasterChannel (int midiChannel) const noexcept; | |||
/** Returns a pointer to the zone which has the specified channel (1-16) | |||
as its first note channel, or nullptr if there is no such zone. | |||
*/ | |||
MPEZone* getZoneByFirstNoteChannel (int midiChannel) const noexcept; | |||
/** Returns a pointer to the zone which has the specified channel (1-16) | |||
as one of its note channels, or nullptr if there is no such zone. | |||
*/ | |||
MPEZone* getZoneByNoteChannel (int midiChannel) const noexcept; | |||
private: | |||
//========================================================================== | |||
Array<MPEZone> zones; | |||
MidiRPNDetector rpnDetector; | |||
void processRpnMessage (MidiRPNMessage); | |||
void processZoneLayoutRpnMessage (MidiRPNMessage); | |||
void processPitchbendRangeRpnMessage (MidiRPNMessage); | |||
}; | |||
#endif // JUCE_MPEZONELAYOUT_H_INCLUDED |
@@ -71,7 +71,7 @@ public: | |||
virtual bool isLooping() const = 0; | |||
/** Tells the source whether you'd like it to play in a loop. */ | |||
virtual void setLooping (bool shouldLoop) { (void) shouldLoop; } | |||
virtual void setLooping (bool shouldLoop) { ignoreUnused (shouldLoop); } | |||
}; | |||
@@ -114,6 +114,7 @@ void Synthesiser::clearVoices() | |||
SynthesiserVoice* Synthesiser::addVoice (SynthesiserVoice* const newVoice) | |||
{ | |||
const ScopedLock sl (lock); | |||
newVoice->setCurrentPlaybackSampleRate (sampleRate); | |||
return voices.add (newVoice); | |||
} | |||
@@ -511,13 +512,13 @@ void Synthesiser::handleSostenutoPedal (int midiChannel, bool isDown) | |||
void Synthesiser::handleSoftPedal (int midiChannel, bool /*isDown*/) | |||
{ | |||
(void) midiChannel; | |||
ignoreUnused (midiChannel); | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
} | |||
void Synthesiser::handleProgramChange (int midiChannel, int programNumber) | |||
{ | |||
(void) midiChannel; (void) programNumber; | |||
ignoreUnused (midiChannel, programNumber); | |||
jassert (midiChannel > 0 && midiChannel <= 16); | |||
} | |||
@@ -557,6 +558,9 @@ SynthesiserVoice* Synthesiser::findVoiceToSteal (SynthesiserSound* soundToPlay, | |||
// - Re-use the oldest notes first | |||
// - Protect the lowest & topmost notes, even if sustained, but not if they've been released. | |||
// apparently you are trying to render audio without having any voices... | |||
jassert (voices.size() > 0); | |||
// These are the voices we want to protect (ie: only steal if unavoidable) | |||
SynthesiserVoice* low = nullptr; // Lowest sounding note, might be sustained, but NOT in release phase | |||
SynthesiserVoice* top = nullptr; // Highest sounding note, might be sustained, but NOT in release phase | |||
@@ -151,7 +151,7 @@ private: | |||
File volumeDir; | |||
Array<File> tracks; | |||
int currentReaderTrack; | |||
ScopedPointer <AudioFormatReader> reader; | |||
ScopedPointer<AudioFormatReader> reader; | |||
AudioCDReader (const File& volume); | |||
#elif JUCE_WINDOWS | |||
@@ -86,6 +86,65 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) | |||
}; | |||
//============================================================================== | |||
// This is an AudioTransportSource which will own it's assigned source | |||
struct AudioSourceOwningTransportSource : public AudioTransportSource | |||
{ | |||
AudioSourceOwningTransportSource (PositionableAudioSource* s) : source (s) | |||
{ | |||
AudioTransportSource::setSource (s); | |||
} | |||
~AudioSourceOwningTransportSource() | |||
{ | |||
setSource (nullptr); | |||
} | |||
private: | |||
ScopedPointer<PositionableAudioSource> source; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource) | |||
}; | |||
//============================================================================== | |||
// An AudioSourcePlayer which will remove itself from the AudioDeviceManager's | |||
// callback list once it finishes playing its source | |||
struct AutoRemovingSourcePlayer : public AudioSourcePlayer, | |||
private Timer | |||
{ | |||
AutoRemovingSourcePlayer (AudioDeviceManager& dm, AudioTransportSource* ts, bool ownSource) | |||
: manager (dm), transportSource (ts, ownSource) | |||
{ | |||
jassert (ts != nullptr); | |||
manager.addAudioCallback (this); | |||
AudioSourcePlayer::setSource (transportSource); | |||
startTimerHz (10); | |||
} | |||
~AutoRemovingSourcePlayer() | |||
{ | |||
setSource (nullptr); | |||
manager.removeAudioCallback (this); | |||
} | |||
void timerCallback() override | |||
{ | |||
if (getCurrentSource() == nullptr || ! transportSource->isPlaying()) | |||
delete this; | |||
} | |||
void audioDeviceStopped() override | |||
{ | |||
AudioSourcePlayer::audioDeviceStopped(); | |||
setSource (nullptr); | |||
} | |||
private: | |||
AudioDeviceManager& manager; | |||
OptionalScopedPointer<AudioTransportSource> transportSource; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer) | |||
}; | |||
//============================================================================== | |||
AudioDeviceManager::AudioDeviceManager() | |||
@@ -103,8 +162,11 @@ AudioDeviceManager::~AudioDeviceManager() | |||
{ | |||
currentAudioDevice = nullptr; | |||
defaultMidiOutput = nullptr; | |||
} | |||
for (int i = 0; i < callbacks.size(); ++i) | |||
if (AutoRemovingSourcePlayer* p = dynamic_cast<AutoRemovingSourcePlayer*> (callbacks.getUnchecked(i))) | |||
delete p; | |||
} | |||
//============================================================================== | |||
void AudioDeviceManager::createDeviceTypesIfNeeded() | |||
@@ -926,143 +988,16 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) | |||
} | |||
} | |||
//============================================================================== | |||
// This is an AudioTransportSource which will own it's assigned source | |||
class AudioSourceOwningTransportSource : public AudioTransportSource | |||
{ | |||
public: | |||
AudioSourceOwningTransportSource() {} | |||
~AudioSourceOwningTransportSource() { setSource (nullptr); } | |||
void setSource (PositionableAudioSource* newSource) | |||
{ | |||
if (src != newSource) | |||
{ | |||
ScopedPointer<PositionableAudioSource> oldSourceDeleter (src); | |||
src = newSource; | |||
// tell the base class about the new source before deleting the old one | |||
AudioTransportSource::setSource (newSource); | |||
} | |||
} | |||
private: | |||
ScopedPointer<PositionableAudioSource> src; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource) | |||
}; | |||
//============================================================================== | |||
// An Audio player which will remove itself from the AudioDeviceManager's | |||
// callback list once it finishes playing its source | |||
class AutoRemovingSourcePlayer : public AudioSourcePlayer, | |||
private ChangeListener | |||
{ | |||
public: | |||
struct DeleteOnMessageThread : public CallbackMessage | |||
{ | |||
DeleteOnMessageThread (AutoRemovingSourcePlayer* p) : parent (p) {} | |||
void messageCallback() override { delete parent; } | |||
AutoRemovingSourcePlayer* parent; | |||
}; | |||
//============================================================================== | |||
AutoRemovingSourcePlayer (AudioDeviceManager& deviceManager, bool ownSource) | |||
: manager (deviceManager), | |||
deleteWhenDone (ownSource), | |||
hasAddedCallback (false), | |||
recursiveEntry (false) | |||
{ | |||
} | |||
void changeListenerCallback (ChangeBroadcaster* newSource) override | |||
{ | |||
if (AudioTransportSource* currentTransport | |||
= dynamic_cast<AudioTransportSource*> (getCurrentSource())) | |||
{ | |||
ignoreUnused (newSource); | |||
jassert (newSource == currentTransport); | |||
if (! currentTransport->isPlaying()) | |||
{ | |||
// this will call audioDeviceStopped! | |||
manager.removeAudioCallback (this); | |||
} | |||
else if (! hasAddedCallback) | |||
{ | |||
hasAddedCallback = true; | |||
manager.addAudioCallback (this); | |||
} | |||
} | |||
} | |||
void audioDeviceStopped() override | |||
{ | |||
if (! recursiveEntry) | |||
{ | |||
ScopedValueSetter<bool> s (recursiveEntry, true, false); | |||
manager.removeAudioCallback (this); | |||
AudioSourcePlayer::audioDeviceStopped(); | |||
if (MessageManager* mm = MessageManager::getInstanceWithoutCreating()) | |||
{ | |||
if (mm->isThisTheMessageThread()) | |||
delete this; | |||
else | |||
(new DeleteOnMessageThread (this))->post(); | |||
} | |||
} | |||
} | |||
void setSource (AudioTransportSource* newSource) | |||
{ | |||
AudioSource* oldSource = getCurrentSource(); | |||
if (AudioTransportSource* oldTransport = dynamic_cast<AudioTransportSource*> (oldSource)) | |||
oldTransport->removeChangeListener (this); | |||
if (newSource != nullptr) | |||
newSource->addChangeListener (this); | |||
AudioSourcePlayer::setSource (newSource); | |||
if (deleteWhenDone) | |||
delete oldSource; | |||
} | |||
private: | |||
// only allow myself to be deleted when my audio callback has been removed | |||
~AutoRemovingSourcePlayer() | |||
{ | |||
setSource (nullptr); | |||
} | |||
AudioDeviceManager& manager; | |||
bool deleteWhenDone, hasAddedCallback, recursiveEntry; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer) | |||
}; | |||
//============================================================================== | |||
// An AudioSource which simply outputs a buffer | |||
class AudioSampleBufferSource : public PositionableAudioSource | |||
class AudioSampleBufferSource : public PositionableAudioSource | |||
{ | |||
public: | |||
AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool shouldLoop, bool ownBuffer) | |||
: position (0), | |||
buffer (audioBuffer), | |||
looping (shouldLoop), | |||
deleteWhenDone (ownBuffer) | |||
AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer) | |||
: buffer (audioBuffer, ownBuffer), | |||
position (0), looping (false) | |||
{} | |||
~AudioSampleBufferSource() | |||
{ | |||
if (deleteWhenDone) | |||
delete buffer; | |||
} | |||
//============================================================================== | |||
void setNextReadPosition (int64 newPosition) override | |||
{ | |||
@@ -1074,69 +1009,45 @@ public: | |||
position = jmin (buffer->getNumSamples(), static_cast<int> (newPosition)); | |||
} | |||
int64 getNextReadPosition() const override | |||
{ | |||
return static_cast<int64> (position); | |||
} | |||
int64 getTotalLength() const override | |||
{ | |||
return static_cast<int64> (buffer->getNumSamples()); | |||
} | |||
bool isLooping() const override | |||
{ | |||
return looping; | |||
} | |||
int64 getNextReadPosition() const override { return static_cast<int64> (position); } | |||
int64 getTotalLength() const override { return static_cast<int64> (buffer->getNumSamples()); } | |||
void setLooping (bool shouldLoop) override | |||
{ | |||
looping = shouldLoop; | |||
} | |||
bool isLooping() const override { return looping; } | |||
void setLooping (bool shouldLoop) override { looping = shouldLoop; } | |||
//============================================================================== | |||
void prepareToPlay (int samplesPerBlockExpected, double sampleRate) override | |||
{ | |||
ignoreUnused (samplesPerBlockExpected, sampleRate); | |||
} | |||
void releaseResources() override | |||
{} | |||
void prepareToPlay (int, double) override {} | |||
void releaseResources() override {} | |||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override | |||
{ | |||
int max = jmin (buffer->getNumSamples() - position, bufferToFill.numSamples); | |||
jassert (max >= 0); | |||
{ | |||
int ch; | |||
int maxInChannels = buffer->getNumChannels(); | |||
int maxOutChannels = jmin (bufferToFill.buffer->getNumChannels(), | |||
jmax (maxInChannels, 2)); | |||
bufferToFill.clearActiveBufferRegion(); | |||
for (ch = 0; ch < maxOutChannels; ch++) | |||
{ | |||
int inChannel = ch % maxInChannels; | |||
const int bufferSize = buffer->getNumSamples(); | |||
const int samplesNeeded = bufferToFill.numSamples; | |||
const int samplesToCopy = jmin (bufferSize - position, samplesNeeded); | |||
if (max > 0) | |||
bufferToFill.buffer->copyFrom (ch, bufferToFill.startSample, *buffer, inChannel, position, max); | |||
} | |||
if (samplesToCopy > 0) | |||
{ | |||
const int maxInChannels = buffer->getNumChannels(); | |||
const int maxOutChannels = jmin (bufferToFill.buffer->getNumChannels(), jmax (maxInChannels, 2)); | |||
for (; ch < bufferToFill.buffer->getNumChannels(); ++ch) | |||
bufferToFill.buffer->clear (ch, bufferToFill.startSample, bufferToFill.numSamples); | |||
for (int i = 0; i < maxOutChannels; ++i) | |||
bufferToFill.buffer->copyFrom (i, bufferToFill.startSample, *buffer, | |||
i % maxInChannels, position, samplesToCopy); | |||
} | |||
position += max; | |||
position += samplesNeeded; | |||
if (looping) | |||
position = position % buffer->getNumSamples(); | |||
position %= bufferSize; | |||
} | |||
private: | |||
//============================================================================== | |||
OptionalScopedPointer<AudioSampleBuffer> buffer; | |||
int position; | |||
AudioSampleBuffer* buffer; | |||
bool looping, deleteWhenDone; | |||
bool looping; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource) | |||
}; | |||
@@ -1165,38 +1076,38 @@ void AudioDeviceManager::playSound (const void* resourceData, size_t resourceSiz | |||
void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFinished) | |||
{ | |||
playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); | |||
if (reader != nullptr) | |||
playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); | |||
} | |||
void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished) | |||
{ | |||
if (buffer != nullptr) | |||
playSound (new AudioSampleBufferSource (buffer, deleteWhenFinished), true); | |||
} | |||
void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished) | |||
{ | |||
if (audioSource != nullptr && currentAudioDevice != nullptr) | |||
{ | |||
if (AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource)) | |||
{ | |||
AutoRemovingSourcePlayer* player = new AutoRemovingSourcePlayer (*this, deleteWhenFinished); | |||
player->setSource (transport); | |||
} | |||
else | |||
{ | |||
AudioTransportSource* transportSource; | |||
AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource); | |||
if (transport == nullptr) | |||
{ | |||
if (deleteWhenFinished) | |||
{ | |||
AudioSourceOwningTransportSource* owningTransportSource = new AudioSourceOwningTransportSource(); | |||
owningTransportSource->setSource (audioSource); | |||
transportSource = owningTransportSource; | |||
transport = new AudioSourceOwningTransportSource (audioSource); | |||
} | |||
else | |||
{ | |||
transportSource = new AudioTransportSource; | |||
transportSource->setSource (audioSource); | |||
transport = new AudioTransportSource(); | |||
transport->setSource (audioSource); | |||
deleteWhenFinished = true; | |||
} | |||
// recursively call myself | |||
playSound (transportSource, true); | |||
transportSource->start(); | |||
} | |||
transport->start(); | |||
new AutoRemovingSourcePlayer (*this, transport, deleteWhenFinished); | |||
} | |||
else | |||
{ | |||
@@ -1205,11 +1116,6 @@ void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool d | |||
} | |||
} | |||
void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished) | |||
{ | |||
playSound (new AudioSampleBufferSource (buffer, false, deleteWhenFinished), true); | |||
} | |||
void AudioDeviceManager::playTestSound() | |||
{ | |||
const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||
@@ -22,7 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
#if defined (JUCE_AUDIO_DEVICES_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE | |||
#ifdef JUCE_AUDIO_DEVICES_H_INCLUDED | |||
/* When you add this cpp file to your project, you mustn't include it in a file where you've | |||
already included any other headers - just put it inside a file on its own, possibly with your config | |||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||
@@ -31,10 +31,6 @@ | |||
#error "Incorrect use of JUCE cpp file" | |||
#endif | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../juce_core/native/juce_BasicNativeHeaders.h" | |||
#include "juce_audio_devices.h" | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_devices", | |||
"name": "JUCE audio and midi I/O device classes", | |||
"version": "4.0.2", | |||
"version": "4.1.0", | |||
"description": "Classes to play and record from audio and midi i/o devices.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -74,8 +74,7 @@ public: | |||
int numBytesSoFar, | |||
double timestamp) | |||
{ | |||
// (this bit is just to avoid compiler warnings about unused variables) | |||
(void) source; (void) messageData; (void) numBytesSoFar; (void) timestamp; | |||
ignoreUnused (source, messageData, numBytesSoFar, timestamp); | |||
} | |||
}; | |||
@@ -40,6 +40,16 @@ MidiOutput::MidiOutput(const String& midiName) | |||
{ | |||
} | |||
void MidiOutput::sendBlockOfMessagesNow (const MidiBuffer& buffer) | |||
{ | |||
MidiBuffer::Iterator i (buffer); | |||
MidiMessage message; | |||
int samplePosition; // Note: not actually used, so no need to initialise. | |||
while (i.getNextEvent (message, samplePosition)) | |||
sendMessageNow (message); | |||
} | |||
void MidiOutput::sendBlockOfMessages (const MidiBuffer& buffer, | |||
const double millisecondCounterToStartAt, | |||
double samplesPerSecondForBuffer) | |||
@@ -87,11 +87,12 @@ public: | |||
/** Returns the name of this device. */ | |||
const String& getName() const noexcept { return name; } | |||
/** Makes this device output a midi message. | |||
@see MidiMessage | |||
*/ | |||
/** Sends out a MIDI message immediately. */ | |||
void sendMessageNow (const MidiMessage& message); | |||
/** Sends out a sequence of MIDI messages immediately. */ | |||
void sendBlockOfMessagesNow (const MidiBuffer& buffer); | |||
//============================================================================== | |||
/** This lets you supply a block of messages that will be sent out at some point | |||
in the future. | |||
@@ -51,7 +51,7 @@ public: | |||
void pushMidiData (const void* inputData, int numBytes, double time, | |||
UserDataType* input, CallbackType& callback) | |||
{ | |||
const uint8* d = static_cast <const uint8*> (inputData); | |||
const uint8* d = static_cast<const uint8*> (inputData); | |||
while (numBytes > 0) | |||
{ | |||
@@ -22,6 +22,8 @@ | |||
============================================================================== | |||
*/ | |||
#undef check | |||
const char* const openSLTypeName = "Android OpenSL"; | |||
bool isOpenSLAvailable() | |||
@@ -43,7 +45,7 @@ public: | |||
{ | |||
// OpenSL has piss-poor support for determining latency, so the only way I can find to | |||
// get a number for this is by asking the AudioTrack/AudioRecord classes.. | |||
AndroidAudioIODevice javaDevice (String::empty); | |||
AndroidAudioIODevice javaDevice (deviceName); | |||
// this is a total guess about how to calculate the latency, but seems to vaguely agree | |||
// with the devices I've tested.. YMMV | |||
@@ -550,7 +552,7 @@ private: | |||
static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) noexcept | |||
{ | |||
jassert (queue == static_cast<Player*> (context)->playerBufferQueue); (void) queue; | |||
jassert (queue == static_cast<Player*> (context)->playerBufferQueue); ignoreUnused (queue); | |||
static_cast<Player*> (context)->bufferList.bufferReturned(); | |||
} | |||
@@ -685,7 +687,7 @@ private: | |||
static void staticCallback (SLAndroidSimpleBufferQueueItf queue, void* context) noexcept | |||
{ | |||
jassert (queue == static_cast<Recorder*> (context)->recorderBufferQueue); (void) queue; | |||
jassert (queue == static_cast<Recorder*> (context)->recorderBufferQueue); ignoreUnused (queue); | |||
static_cast<Recorder*> (context)->bufferList.bufferReturned(); | |||
} | |||
@@ -393,13 +393,13 @@ private: | |||
static void interruptionListenerCallback (void* client, UInt32 interruptionType) | |||
{ | |||
const Array <iOSAudioIODevice*>& activeDevices = static_cast <AudioSessionHolder*> (client)->activeDevices; | |||
const Array<iOSAudioIODevice*>& activeDevices = static_cast<AudioSessionHolder*> (client)->activeDevices; | |||
for (int i = activeDevices.size(); --i >= 0;) | |||
activeDevices.getUnchecked(i)->interruptionListener (interruptionType); | |||
} | |||
Array <iOSAudioIODevice*> activeDevices; | |||
Array<iOSAudioIODevice*> activeDevices; | |||
}; | |||
static AudioSessionHolder& getSessionHolder() | |||
@@ -993,7 +993,7 @@ public: | |||
{ | |||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||
if (ALSAAudioIODevice* d = dynamic_cast <ALSAAudioIODevice*> (device)) | |||
if (ALSAAudioIODevice* d = dynamic_cast<ALSAAudioIODevice*> (device)) | |||
return asInput ? inputIds.indexOf (d->inputId) | |||
: outputIds.indexOf (d->outputId); | |||
@@ -388,7 +388,7 @@ private: | |||
if (callback != nullptr) | |||
{ | |||
if ((numActiveInChans + numActiveOutChans) > 0) | |||
callback->audioDeviceIOCallback (const_cast <const float**> (inChans.getData()), numActiveInChans, | |||
callback->audioDeviceIOCallback (const_cast<const float**> (inChans.getData()), numActiveInChans, | |||
outChans, numActiveOutChans, numSamples); | |||
} | |||
else | |||
@@ -437,7 +437,7 @@ private: | |||
static void portConnectCallback (jack_port_id_t, jack_port_id_t, int, void* arg) | |||
{ | |||
if (JackAudioIODevice* device = static_cast <JackAudioIODevice*> (arg)) | |||
if (JackAudioIODevice* device = static_cast<JackAudioIODevice*> (arg)) | |||
device->updateActivePorts(); | |||
} | |||
@@ -470,7 +470,7 @@ private: | |||
AudioIODeviceCallback* callback; | |||
CriticalSection callbackLock; | |||
HeapBlock <float*> inChans, outChans; | |||
HeapBlock<float*> inChans, outChans; | |||
int totalNumberOfInputChannels; | |||
int totalNumberOfOutputChannels; | |||
Array<void*> inputPorts, outputPorts; | |||
@@ -557,7 +557,7 @@ public: | |||
{ | |||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||
if (JackAudioIODevice* d = dynamic_cast <JackAudioIODevice*> (device)) | |||
if (JackAudioIODevice* d = dynamic_cast<JackAudioIODevice*> (device)) | |||
return asInput ? inputIds.indexOf (d->inputId) | |||
: outputIds.indexOf (d->outputId); | |||
@@ -136,7 +136,7 @@ private: | |||
HeapBlock<pollfd> pfd ((size_t) numPfds); | |||
snd_seq_poll_descriptors (seqHandle, pfd, (unsigned int) numPfds, POLLIN); | |||
HeapBlock <uint8> buffer (maxEventSize); | |||
HeapBlock<uint8> buffer (maxEventSize); | |||
while (! threadShouldExit()) | |||
{ | |||
@@ -169,7 +169,7 @@ void AudioCDReader::refreshTrackLengths() | |||
{ | |||
XmlDocument doc (toc); | |||
const char* error = CDReaderHelpers::getTrackOffsets (doc, trackStartSamples); | |||
(void) error; // could be logged.. | |||
ignoreUnused (error); // could be logged.. | |||
lengthInSamples = trackStartSamples.getLast() - trackStartSamples.getFirst(); | |||
} | |||
@@ -155,9 +155,7 @@ public: | |||
outputLatency (0), | |||
bitDepth (32), | |||
callback (nullptr), | |||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 | |||
audioProcID (0), | |||
#endif | |||
deviceID (id), | |||
started (false), | |||
sampleRate (0), | |||
@@ -625,11 +623,7 @@ public: | |||
if (deviceID != 0) | |||
{ | |||
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 | |||
if (OK (AudioDeviceAddIOProc (deviceID, audioIOProc, this))) | |||
#else | |||
if (OK (AudioDeviceCreateIOProcID (deviceID, audioIOProc, this, &audioProcID))) | |||
#endif | |||
{ | |||
if (OK (AudioDeviceStart (deviceID, audioIOProc))) | |||
{ | |||
@@ -637,12 +631,8 @@ public: | |||
} | |||
else | |||
{ | |||
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 | |||
OK (AudioDeviceRemoveIOProc (deviceID, audioIOProc)); | |||
#else | |||
OK (AudioDeviceDestroyIOProcID (deviceID, audioProcID)); | |||
audioProcID = 0; | |||
#endif | |||
} | |||
} | |||
} | |||
@@ -669,13 +659,8 @@ public: | |||
&& ! leaveInterruptRunning) | |||
{ | |||
OK (AudioDeviceStop (deviceID, audioIOProc)); | |||
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 | |||
OK (AudioDeviceRemoveIOProc (deviceID, audioIOProc)); | |||
#else | |||
OK (AudioDeviceDestroyIOProcID (deviceID, audioProcID)); | |||
audioProcID = 0; | |||
#endif | |||
started = false; | |||
@@ -793,9 +778,7 @@ public: | |||
Array<double> sampleRates; | |||
Array<int> bufferSizes; | |||
AudioIODeviceCallback* callback; | |||
#if MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5 | |||
AudioDeviceIOProcID audioProcID; | |||
#endif | |||
private: | |||
CriticalSection callbackLock; | |||
@@ -37,7 +37,7 @@ namespace CoreMidiHelpers | |||
Logger::writeToLog ("CoreMIDI error: " + String (lineNum) + " - " + String::toHexString ((int) err)); | |||
#endif | |||
(void) lineNum; | |||
ignoreUnused (lineNum); | |||
return false; | |||
} | |||
@@ -67,7 +67,7 @@ namespace ASIODebugging | |||
#else | |||
static void dummyLog() {} | |||
#define JUCE_ASIO_LOG(msg) ASIODebugging::dummyLog() | |||
#define JUCE_ASIO_LOG_ERROR(msg, errNum) (void) errNum; ASIODebugging::dummyLog() | |||
#define JUCE_ASIO_LOG_ERROR(msg, errNum) ignoreUnused (errNum); ASIODebugging::dummyLog() | |||
#endif | |||
} | |||
@@ -690,31 +690,24 @@ public: | |||
JUCE_ASIO_LOG ("showing control panel"); | |||
bool done = false; | |||
insideControlPanelModalLoop = true; | |||
JUCE_TRY | |||
{ | |||
// are there are devices that need to be closed before showing their control panel? | |||
// close(); | |||
insideControlPanelModalLoop = true; | |||
const uint32 started = Time::getMillisecondCounter(); | |||
const uint32 started = Time::getMillisecondCounter(); | |||
if (asioObject != nullptr) | |||
{ | |||
asioObject->controlPanel(); | |||
if (asioObject != nullptr) | |||
{ | |||
asioObject->controlPanel(); | |||
const int spent = (int) Time::getMillisecondCounter() - (int) started; | |||
const int spent = (int) Time::getMillisecondCounter() - (int) started; | |||
JUCE_ASIO_LOG ("spent: " + String (spent)); | |||
JUCE_ASIO_LOG ("spent: " + String (spent)); | |||
if (spent > 300) | |||
{ | |||
shouldUsePreferredSize = true; | |||
done = true; | |||
} | |||
if (spent > 300) | |||
{ | |||
shouldUsePreferredSize = true; | |||
done = true; | |||
} | |||
} | |||
JUCE_CATCH_ALL | |||
insideControlPanelModalLoop = false; | |||
return done; | |||
@@ -785,7 +778,7 @@ private: | |||
HeapBlock<ASIOSampleFormat> inputFormat, outputFormat; | |||
WaitableEvent event1; | |||
HeapBlock <float> tempBuffer; | |||
HeapBlock<float> tempBuffer; | |||
int volatile bufferIndex, numActiveInputChans, numActiveOutputChans; | |||
bool deviceIsOpen, isStarted, buffersCreated; | |||
@@ -367,7 +367,7 @@ bool AudioCDBurner::addAudioTrack (AudioSource* audioSource, int numSamples) | |||
hr = pimpl->redbook->CreateAudioTrack ((long) numSamples / (bytesPerBlock * 4)); | |||
HeapBlock <byte> buffer (bytesPerBlock); | |||
HeapBlock<byte> buffer (bytesPerBlock); | |||
AudioSampleBuffer sourceBuffer (2, samplesPerBlock); | |||
int samplesDone = 0; | |||
@@ -1027,7 +1027,7 @@ AudioCDReader::AudioCDReader (void* handle_) | |||
AudioCDReader::~AudioCDReader() | |||
{ | |||
using namespace CDReaderHelpers; | |||
CDDeviceWrapper* const device = static_cast <CDDeviceWrapper*> (handle); | |||
CDDeviceWrapper* const device = static_cast<CDDeviceWrapper*> (handle); | |||
delete device; | |||
} | |||
@@ -1035,7 +1035,7 @@ bool AudioCDReader::readSamples (int** destSamples, int numDestChannels, int sta | |||
int64 startSampleInFile, int numSamples) | |||
{ | |||
using namespace CDReaderHelpers; | |||
CDDeviceWrapper* const device = static_cast <CDDeviceWrapper*> (handle); | |||
CDDeviceWrapper* const device = static_cast<CDDeviceWrapper*> (handle); | |||
bool ok = true; | |||
@@ -1131,7 +1131,7 @@ bool AudioCDReader::isCDStillPresent() const | |||
{ | |||
using namespace CDReaderHelpers; | |||
TOC toc = { 0 }; | |||
return static_cast <CDDeviceWrapper*> (handle)->deviceHandle.readTOC (&toc); | |||
return static_cast<CDDeviceWrapper*> (handle)->deviceHandle.readTOC (&toc); | |||
} | |||
void AudioCDReader::refreshTrackLengths() | |||
@@ -1142,7 +1142,7 @@ void AudioCDReader::refreshTrackLengths() | |||
TOC toc = { 0 }; | |||
if (static_cast <CDDeviceWrapper*> (handle)->deviceHandle.readTOC (&toc)) | |||
if (static_cast<CDDeviceWrapper*> (handle)->deviceHandle.readTOC (&toc)) | |||
{ | |||
int numTracks = 1 + toc.lastTrack - toc.firstTrack; | |||
@@ -1174,7 +1174,7 @@ int AudioCDReader::getLastIndex() const | |||
int AudioCDReader::getIndexAt (int samplePos) | |||
{ | |||
using namespace CDReaderHelpers; | |||
CDDeviceWrapper* const device = static_cast <CDDeviceWrapper*> (handle); | |||
CDDeviceWrapper* const device = static_cast<CDDeviceWrapper*> (handle); | |||
const int frameNeeded = samplePos / samplesPerFrame; | |||
@@ -1255,7 +1255,7 @@ Array<int> AudioCDReader::findIndexesInTrack (const int trackNumber) | |||
if (needToScan) | |||
{ | |||
CDDeviceWrapper* const device = static_cast <CDDeviceWrapper*> (handle); | |||
CDDeviceWrapper* const device = static_cast<CDDeviceWrapper*> (handle); | |||
int pos = trackStart; | |||
int last = -1; | |||
@@ -1305,5 +1305,5 @@ Array<int> AudioCDReader::findIndexesInTrack (const int trackNumber) | |||
void AudioCDReader::ejectDisk() | |||
{ | |||
using namespace CDReaderHelpers; | |||
static_cast <CDDeviceWrapper*> (handle)->deviceHandle.openDrawer (true); | |||
static_cast<CDDeviceWrapper*> (handle)->deviceHandle.openDrawer (true); | |||
} |
@@ -182,11 +182,9 @@ namespace DSoundLogging | |||
} | |||
} | |||
#define CATCH JUCE_CATCH_EXCEPTION | |||
#define JUCE_DS_LOG(a) DSoundLogging::logMessage(a); | |||
#define JUCE_DS_LOG_ERROR(a) DSoundLogging::logError(a, __LINE__); | |||
#else | |||
#define CATCH JUCE_CATCH_ALL | |||
#define JUCE_DS_LOG(a) | |||
#define JUCE_DS_LOG_ERROR(a) | |||
#endif | |||
@@ -251,7 +249,7 @@ public: | |||
{ | |||
JUCE_DS_LOG ("closing output: " + name); | |||
HRESULT hr = pOutputBuffer->Stop(); | |||
JUCE_DS_LOG_ERROR (hr); (void) hr; | |||
JUCE_DS_LOG_ERROR (hr); ignoreUnused (hr); | |||
pOutputBuffer->Release(); | |||
pOutputBuffer = nullptr; | |||
@@ -354,7 +352,7 @@ public: | |||
hr = pOutputBuffer->Play (0, 0, 1 /* DSBPLAY_LOOPING */); | |||
if (SUCCEEDED (hr)) | |||
return String::empty; | |||
return String(); | |||
} | |||
} | |||
} | |||
@@ -534,7 +532,7 @@ public: | |||
{ | |||
JUCE_DS_LOG ("closing input: " + name); | |||
HRESULT hr = pInputBuffer->Stop(); | |||
JUCE_DS_LOG_ERROR (hr); (void) hr; | |||
JUCE_DS_LOG_ERROR (hr); ignoreUnused (hr); | |||
pInputBuffer->Release(); | |||
pInputBuffer = nullptr; | |||
@@ -597,7 +595,7 @@ public: | |||
hr = pInputBuffer->Start (1 /* DSCBSTART_LOOPING */); | |||
if (SUCCEEDED (hr)) | |||
return String::empty; | |||
return String(); | |||
} | |||
} | |||
@@ -1226,7 +1224,7 @@ public: | |||
{ | |||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||
if (DSoundAudioIODevice* const d = dynamic_cast <DSoundAudioIODevice*> (device)) | |||
if (DSoundAudioIODevice* const d = dynamic_cast<DSoundAudioIODevice*> (device)) | |||
return asInput ? d->inputDeviceIndex | |||
: d->outputDeviceIndex; | |||
@@ -116,7 +116,7 @@ public: | |||
static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, | |||
DWORD_PTR midiMessage, DWORD_PTR timeStamp) | |||
{ | |||
MidiInCollector* const collector = reinterpret_cast <MidiInCollector*> (dwInstance); | |||
MidiInCollector* const collector = reinterpret_cast<MidiInCollector*> (dwInstance); | |||
if (activeMidiCollectors.contains (collector)) | |||
{ | |||
@@ -270,8 +270,8 @@ MidiInput* MidiInput::openDevice (const int index, MidiInputCallback* const call | |||
} | |||
} | |||
ScopedPointer <MidiInput> in (new MidiInput (name)); | |||
ScopedPointer <MidiInCollector> collector (new MidiInCollector (in, *callback)); | |||
ScopedPointer<MidiInput> in (new MidiInput (name)); | |||
ScopedPointer<MidiInCollector> collector (new MidiInCollector (in, *callback)); | |||
HMIDIIN h; | |||
MMRESULT err = midiInOpen (&h, deviceId, | |||
@@ -32,7 +32,7 @@ namespace WasapiClasses | |||
void logFailure (HRESULT hr) | |||
{ | |||
(void) hr; | |||
ignoreUnused (hr); | |||
jassert (hr != (HRESULT) 0x800401f0); // If you hit this, it means you're trying to call from | |||
// a thread which hasn't been initialised with CoInitialize(). | |||
@@ -531,7 +531,7 @@ public: | |||
} | |||
else if (type == chunkName ("INST")) | |||
{ | |||
HeapBlock <InstChunk> inst; | |||
HeapBlock<InstChunk> inst; | |||
inst.calloc (jmax ((size_t) length + 1, sizeof (InstChunk)), 1); | |||
input->read (inst, (int) length); | |||
inst->copyTo (metadataValues); | |||
@@ -715,7 +715,7 @@ private: | |||
using namespace AiffFileHelpers; | |||
const bool couldSeekOk = output->setPosition (headerPosition); | |||
(void) couldSeekOk; | |||
ignoreUnused (couldSeekOk); | |||
// if this fails, you've given it an output stream that can't seek! It needs | |||
// to be able to seek back to write the header | |||
@@ -972,7 +972,7 @@ bool AiffAudioFormat::canHandleFile (const File& f) | |||
AudioFormatReader* AiffAudioFormat::createReaderFor (InputStream* sourceStream, const bool deleteStreamIfOpeningFails) | |||
{ | |||
ScopedPointer <AiffAudioFormatReader> w (new AiffAudioFormatReader (sourceStream)); | |||
ScopedPointer<AiffAudioFormatReader> w (new AiffAudioFormatReader (sourceStream)); | |||
if (w->sampleRate > 0 && w->numChannels > 0) | |||
return w.release(); | |||
@@ -1003,7 +1003,7 @@ AudioFormatWriter* AiffAudioFormat::createWriterFor (OutputStream* out, | |||
const StringPairArray& metadataValues, | |||
int /*qualityOptionIndex*/) | |||
{ | |||
if (getPossibleBitDepths().contains (bitsPerSample)) | |||
if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample)) | |||
return new AiffAudioFormatWriter (out, sampleRate, numberOfChannels, (unsigned int) bitsPerSample, metadataValues); | |||
return nullptr; | |||
@@ -433,7 +433,7 @@ public: | |||
memcpy (buffer + 18, info.md5sum, 16); | |||
const bool seekOk = output->setPosition (4); | |||
(void) seekOk; | |||
ignoreUnused (seekOk); | |||
// if this fails, you've given it an output stream that can't seek! It needs | |||
// to be able to seek back to write the header | |||
@@ -536,7 +536,7 @@ AudioFormatWriter* FlacAudioFormat::createWriterFor (OutputStream* out, | |||
const StringPairArray& /*metadataValues*/, | |||
int qualityOptionIndex) | |||
{ | |||
if (getPossibleBitDepths().contains (bitsPerSample)) | |||
if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample)) | |||
{ | |||
ScopedPointer<FlacWriter> w (new FlacWriter (out, sampleRate, numberOfChannels, | |||
(uint32) bitsPerSample, qualityOptionIndex)); | |||
@@ -110,7 +110,7 @@ private: | |||
if (cp.start (processArgs)) | |||
{ | |||
const String childOutput (cp.readAllProcessOutput()); | |||
DBG (childOutput); (void) childOutput; | |||
DBG (childOutput); ignoreUnused (childOutput); | |||
cp.waitForProcessToFinish (10000); | |||
return tempMP3.getFile().getSize() > 0; | |||
@@ -205,6 +205,9 @@ AudioFormatWriter* LAMEEncoderAudioFormat::createWriterFor (OutputStream* stream | |||
const StringPairArray& metadataValues, | |||
int qualityOptionIndex) | |||
{ | |||
if (streamToWriteTo == nullptr) | |||
return nullptr; | |||
int vbr = 4; | |||
int cbr = 0; | |||
@@ -1613,7 +1613,7 @@ private: | |||
headerParsed = sideParsed = dataParsed = isFreeFormat = wasFreeFormat = false; | |||
lastFrameSize = -1; | |||
needToSyncBitStream = true; | |||
frameSize = sideInfoSize = dataSize = frameSize = bitIndex = 0; | |||
frameSize = sideInfoSize = dataSize = bitIndex = 0; | |||
lastFrameSizeNoPadding = bufferSpaceIndex = 0; | |||
bufferPointer = bufferSpace[bufferSpaceIndex] + 512; | |||
synthBo = 1; | |||
@@ -3020,7 +3020,7 @@ public: | |||
} | |||
const int numToCopy = jmin (decodedEnd - decodedStart, numSamples); | |||
float* const* const dst = reinterpret_cast <float**> (destSamples); | |||
float* const* const dst = reinterpret_cast<float**> (destSamples); | |||
memcpy (dst[0] + startOffsetInDestBuffer, decoded0 + decodedStart, sizeof (float) * (size_t) numToCopy); | |||
if (numDestChannels > 1 && dst[1] != nullptr) | |||
@@ -428,7 +428,7 @@ private: | |||
const String s (metadata [name]); | |||
if (s.isNotEmpty()) | |||
vorbis_comment_add_tag (&vc, vorbisName, const_cast <char*> (s.toRawUTF8())); | |||
vorbis_comment_add_tag (&vc, vorbisName, const_cast<char*> (s.toRawUTF8())); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OggWriter) | |||
@@ -483,8 +483,12 @@ AudioFormatWriter* OggVorbisAudioFormat::createWriterFor (OutputStream* out, | |||
const StringPairArray& metadataValues, | |||
int qualityOptionIndex) | |||
{ | |||
ScopedPointer <OggWriter> w (new OggWriter (out, sampleRate, numChannels, | |||
(unsigned int) bitsPerSample, qualityOptionIndex, metadataValues)); | |||
if (out == nullptr) | |||
return nullptr; | |||
ScopedPointer<OggWriter> w (new OggWriter (out, sampleRate, numChannels, | |||
(unsigned int) bitsPerSample, | |||
qualityOptionIndex, metadataValues)); | |||
return w->ok ? w.release() : nullptr; | |||
} | |||
@@ -142,7 +142,7 @@ public: | |||
if (err != noErr) | |||
return; | |||
HeapBlock <AudioChannelLayout> qt_audio_channel_layout; | |||
HeapBlock<AudioChannelLayout> qt_audio_channel_layout; | |||
qt_audio_channel_layout.calloc (output_layout_size, 1); | |||
MovieAudioExtractionGetProperty (extractor, | |||
@@ -322,8 +322,8 @@ private: | |||
Thread::ThreadID lastThreadId; | |||
MovieAudioExtractionRef extractor; | |||
AudioStreamBasicDescription inputStreamDesc; | |||
HeapBlock <AudioBufferList> bufferList; | |||
HeapBlock <char> dataBuffer; | |||
HeapBlock<AudioBufferList> bufferList; | |||
HeapBlock<char> dataBuffer; | |||
Handle dataHandle; | |||
//============================================================================== | |||
@@ -845,7 +845,7 @@ namespace WavFileHelpers | |||
static MemoryBlock createFrom (const StringPairArray& values) | |||
{ | |||
const String ISRC (values.getValue (WavAudioFormat::ISRC, String::empty)); | |||
const String ISRC (values.getValue (WavAudioFormat::ISRC, String())); | |||
MemoryOutputStream xml; | |||
if (ISRC.isNotEmpty()) | |||
@@ -1606,7 +1606,7 @@ AudioFormatWriter* WavAudioFormat::createWriterFor (OutputStream* out, double sa | |||
unsigned int numChannels, int bitsPerSample, | |||
const StringPairArray& metadataValues, int /*qualityOptionIndex*/) | |||
{ | |||
if (getPossibleBitDepths().contains (bitsPerSample)) | |||
if (out != nullptr && getPossibleBitDepths().contains (bitsPerSample)) | |||
return new WavAudioFormatWriter (out, sampleRate, (unsigned int) numChannels, | |||
(unsigned int) bitsPerSample, metadataValues); | |||
@@ -151,7 +151,7 @@ AudioFormatReader* AudioFormatManager::createReaderFor (InputStream* audioFileSt | |||
// use them to open a file! | |||
jassert (getNumKnownFormats() > 0); | |||
ScopedPointer <InputStream> in (audioFileStream); | |||
ScopedPointer<InputStream> in (audioFileStream); | |||
if (in != nullptr) | |||
{ | |||
@@ -126,8 +126,8 @@ public: | |||
The stream that is passed-in must be capable of being repositioned so | |||
that all the formats can have a go at opening it. | |||
If none of the registered formats can open the stream, it'll return 0. If it | |||
returns a reader, it's the caller's responsibility to delete the reader. | |||
If none of the registered formats can open the stream, it'll return nullptr. | |||
If it returns a reader, it's the caller's responsibility to delete the reader. | |||
*/ | |||
AudioFormatReader* createReaderFor (InputStream* audioFileStream); | |||
@@ -95,7 +95,7 @@ bool AudioFormatWriter::writeFromAudioReader (AudioFormatReader& reader, | |||
} | |||
} | |||
if (! write (const_cast <const int**> (buffers), numToDo)) | |||
if (! write (const_cast<const int**> (buffers), numToDo)) | |||
return false; | |||
numSamplesToRead -= numToDo; | |||
@@ -22,7 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
#if defined (JUCE_AUDIO_FORMATS_H_INCLUDED) && ! JUCE_AMALGAMATED_INCLUDE | |||
#ifdef JUCE_AUDIO_FORMATS_H_INCLUDED | |||
/* When you add this cpp file to your project, you mustn't include it in a file where you've | |||
already included any other headers - just put it inside a file on its own, possibly with your config | |||
flags preceding it, but don't include anything else. That also includes avoiding any automatic prefix | |||
@@ -31,10 +31,6 @@ | |||
#error "Incorrect use of JUCE cpp file" | |||
#endif | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../juce_core/native/juce_BasicNativeHeaders.h" | |||
#include "juce_audio_formats.h" | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_formats", | |||
"name": "JUCE audio file format codecs", | |||
"version": "4.0.2", | |||
"version": "4.1.0", | |||
"description": "Classes for reading and writing various audio file formats.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -93,7 +93,7 @@ void SamplerVoice::startNote (const int midiNoteNumber, | |||
SynthesiserSound* s, | |||
const int /*currentPitchWheelPosition*/) | |||
{ | |||
if (const SamplerSound* const sound = dynamic_cast <const SamplerSound*> (s)) | |||
if (const SamplerSound* const sound = dynamic_cast<const SamplerSound*> (s)) | |||
{ | |||
pitchRatio = pow (2.0, (midiNoteNumber - sound->midiRootNote) / 12.0) | |||
* sound->sourceSampleRate / getSampleRate(); | |||
@@ -152,7 +152,7 @@ void SamplerVoice::controllerMoved (const int /*controllerNumber*/, | |||
//============================================================================== | |||
void SamplerVoice::renderNextBlock (AudioSampleBuffer& outputBuffer, int startSample, int numSamples) | |||
{ | |||
if (const SamplerSound* const playingSound = static_cast <SamplerSound*> (getCurrentlyPlayingSound().get())) | |||
if (const SamplerSound* const playingSound = static_cast<SamplerSound*> (getCurrentlyPlayingSound().get())) | |||
{ | |||
const float* const inL = playingSound->data->getReadPointer (0); | |||
const float* const inR = playingSound->data->getNumChannels() > 1 | |||
@@ -22,21 +22,14 @@ | |||
============================================================================== | |||
*/ | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../../juce_core/system/juce_TargetPlatform.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#if JucePlugin_Build_AAX && (JUCE_INCLUDED_AAX_IN_MM || defined (_WIN32) || defined (_WIN64)) | |||
#ifdef _MSC_VER | |||
#include <windows.h> | |||
#else | |||
#include <Cocoa/Cocoa.h> | |||
#endif | |||
#include "../utility/juce_IncludeSystemHeaders.h" | |||
#include "../utility/juce_IncludeModuleHeaders.h" | |||
#include "../utility/juce_PluginBusUtilities.h" | |||
#undef Component | |||
#ifdef __clang__ | |||
@@ -98,6 +91,8 @@ | |||
#pragma comment(lib, JucePlugin_AAXLibs_path JUCE_AAX_LIB_PATH JUCE_AAX_LIB JUCE_AAX_LIB_SUFFIX ".lib") | |||
#endif | |||
#undef check | |||
using juce::Component; | |||
const int32_t juceChunkType = 'juce'; | |||
@@ -107,7 +102,7 @@ struct AAXClasses | |||
{ | |||
static void check (AAX_Result result) | |||
{ | |||
jassert (result == AAX_SUCCESS); (void) result; | |||
jassert (result == AAX_SUCCESS); ignoreUnused (result); | |||
} | |||
static int getParamIndexFromID (AAX_CParamID paramID) noexcept | |||
@@ -120,47 +115,75 @@ struct AAXClasses | |||
return AAX::IsParameterIDEqual (paramID, cDefaultMasterBypassID) != 0; | |||
} | |||
static AAX_EStemFormat getFormatForChans (const int numChans) noexcept | |||
static AAX_EStemFormat getFormatForAudioChannelSet (const AudioChannelSet& set, bool ignoreLayout) noexcept | |||
{ | |||
switch (numChans) | |||
// if the plug-in ignores layout, it is ok to convert between formats only by their numchannnels | |||
if (ignoreLayout) | |||
{ | |||
case 0: return AAX_eStemFormat_None; | |||
case 1: return AAX_eStemFormat_Mono; | |||
case 2: return AAX_eStemFormat_Stereo; | |||
case 3: return AAX_eStemFormat_LCR; | |||
case 4: return AAX_eStemFormat_Quad; | |||
case 5: return AAX_eStemFormat_5_0; | |||
case 6: return AAX_eStemFormat_5_1; | |||
case 7: return AAX_eStemFormat_7_0_DTS; | |||
case 8: return AAX_eStemFormat_7_1_DTS; | |||
default: jassertfalse; break; | |||
switch (set.size()) | |||
{ | |||
case 0: return AAX_eStemFormat_None; | |||
case 1: return AAX_eStemFormat_Mono; | |||
case 2: return AAX_eStemFormat_Stereo; | |||
case 3: return AAX_eStemFormat_LCR; | |||
case 4: return AAX_eStemFormat_Quad; | |||
case 5: return AAX_eStemFormat_5_0; | |||
case 6: return AAX_eStemFormat_5_1; | |||
case 7: return AAX_eStemFormat_7_0_DTS; | |||
case 8: return AAX_eStemFormat_7_1_DTS; | |||
default: | |||
break; | |||
} | |||
return AAX_eStemFormat_INT32_MAX; | |||
} | |||
return AAX_eStemFormat_None; | |||
if (set == AudioChannelSet::disabled()) return AAX_eStemFormat_None; | |||
if (set == AudioChannelSet::mono()) return AAX_eStemFormat_Mono; | |||
if (set == AudioChannelSet::stereo()) return AAX_eStemFormat_Stereo; | |||
if (set == AudioChannelSet::createLCR()) return AAX_eStemFormat_LCR; | |||
if (set == AudioChannelSet::createLCRS()) return AAX_eStemFormat_LCRS; | |||
if (set == AudioChannelSet::quadraphonic()) return AAX_eStemFormat_Quad; | |||
if (set == AudioChannelSet::create5point0()) return AAX_eStemFormat_5_0; | |||
if (set == AudioChannelSet::create5point1()) return AAX_eStemFormat_5_1; | |||
if (set == AudioChannelSet::create6point0()) return AAX_eStemFormat_6_0; | |||
if (set == AudioChannelSet::create6point1()) return AAX_eStemFormat_6_1; | |||
if (set == AudioChannelSet::create7point0()) return AAX_eStemFormat_7_0_DTS; | |||
if (set == AudioChannelSet::create7point1()) return AAX_eStemFormat_7_1_DTS; | |||
if (set == AudioChannelSet::createFront7point0()) return AAX_eStemFormat_7_0_SDDS; | |||
if (set == AudioChannelSet::createFront7point1()) return AAX_eStemFormat_7_1_SDDS; | |||
return AAX_eStemFormat_INT32_MAX; | |||
} | |||
static int getNumChannelsForStemFormat (AAX_EStemFormat format) noexcept | |||
static AudioChannelSet channelSetFromStemFormat (AAX_EStemFormat format, bool ignoreLayout) noexcept | |||
{ | |||
switch (format) | |||
if (! ignoreLayout) | |||
{ | |||
case AAX_eStemFormat_None: return 0; | |||
case AAX_eStemFormat_Mono: return 1; | |||
case AAX_eStemFormat_Stereo: return 2; | |||
case AAX_eStemFormat_LCR: return 3; | |||
case AAX_eStemFormat_LCRS: | |||
case AAX_eStemFormat_Quad: return 4; | |||
case AAX_eStemFormat_5_0: return 5; | |||
case AAX_eStemFormat_5_1: | |||
case AAX_eStemFormat_6_0: return 6; | |||
case AAX_eStemFormat_6_1: | |||
case AAX_eStemFormat_7_0_SDDS: | |||
case AAX_eStemFormat_7_0_DTS: return 7; | |||
case AAX_eStemFormat_7_1_SDDS: | |||
case AAX_eStemFormat_7_1_DTS: return 8; | |||
default: jassertfalse; break; | |||
switch (format) | |||
{ | |||
case AAX_eStemFormat_None: return AudioChannelSet::disabled(); | |||
case AAX_eStemFormat_Mono: return AudioChannelSet::mono(); | |||
case AAX_eStemFormat_Stereo: return AudioChannelSet::stereo(); | |||
case AAX_eStemFormat_LCR: return AudioChannelSet::createLCR(); | |||
case AAX_eStemFormat_LCRS: return AudioChannelSet::createLCRS(); | |||
case AAX_eStemFormat_Quad: return AudioChannelSet::quadraphonic(); | |||
case AAX_eStemFormat_5_0: return AudioChannelSet::create5point0(); | |||
case AAX_eStemFormat_5_1: return AudioChannelSet::create5point1(); | |||
case AAX_eStemFormat_6_0: return AudioChannelSet::create6point0(); | |||
case AAX_eStemFormat_6_1: return AudioChannelSet::create6point1(); | |||
case AAX_eStemFormat_7_0_SDDS: return AudioChannelSet::createFront7point0(); | |||
case AAX_eStemFormat_7_0_DTS: return AudioChannelSet::create7point0(); | |||
case AAX_eStemFormat_7_1_SDDS: return AudioChannelSet::createFront7point1(); | |||
case AAX_eStemFormat_7_1_DTS: return AudioChannelSet::create7point1(); | |||
default: | |||
break; | |||
} | |||
return AudioChannelSet::disabled(); | |||
} | |||
return 0; | |||
return AudioChannelSet::discreteChannels (jmax (0, static_cast<int> (AAX_STEM_FORMAT_CHANNEL_COUNT (format)))); | |||
} | |||
static const char* getSpeakerArrangementString (AAX_EStemFormat format) noexcept | |||
@@ -220,16 +243,17 @@ struct AAXClasses | |||
int32_t* bufferSize; | |||
int32_t* bypass; | |||
#if JucePlugin_WantsMidiInput | |||
#if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect | |||
AAX_IMIDINode* midiNodeIn; | |||
#endif | |||
#if JucePlugin_ProducesMidiOutput | |||
#if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect | |||
AAX_IMIDINode* midiNodeOut; | |||
#endif | |||
PluginInstanceInfo* pluginInstance; | |||
int32_t* isPrepared; | |||
int32_t* sideChainBuffers; | |||
}; | |||
struct JUCEAlgorithmIDs | |||
@@ -241,26 +265,28 @@ struct AAXClasses | |||
bufferSize = AAX_FIELD_INDEX (JUCEAlgorithmContext, bufferSize), | |||
bypass = AAX_FIELD_INDEX (JUCEAlgorithmContext, bypass), | |||
#if JucePlugin_WantsMidiInput | |||
#if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect | |||
midiNodeIn = AAX_FIELD_INDEX (JUCEAlgorithmContext, midiNodeIn), | |||
#endif | |||
#if JucePlugin_ProducesMidiOutput | |||
#if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect | |||
midiNodeOut = AAX_FIELD_INDEX (JUCEAlgorithmContext, midiNodeOut), | |||
#endif | |||
pluginInstance = AAX_FIELD_INDEX (JUCEAlgorithmContext, pluginInstance), | |||
preparedFlag = AAX_FIELD_INDEX (JUCEAlgorithmContext, isPrepared) | |||
preparedFlag = AAX_FIELD_INDEX (JUCEAlgorithmContext, isPrepared), | |||
sideChainBuffers = AAX_FIELD_INDEX (JUCEAlgorithmContext, sideChainBuffers) | |||
}; | |||
}; | |||
#if JucePlugin_WantsMidiInput | |||
#if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect | |||
static AAX_IMIDINode* getMidiNodeIn (const JUCEAlgorithmContext& c) noexcept { return c.midiNodeIn; } | |||
#else | |||
static AAX_IMIDINode* getMidiNodeIn (const JUCEAlgorithmContext&) noexcept { return nullptr; } | |||
#endif | |||
#if JucePlugin_ProducesMidiOutput | |||
#if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect | |||
AAX_IMIDINode* midiNodeOut; | |||
static AAX_IMIDINode* getMidiNodeOut (const JUCEAlgorithmContext& c) noexcept { return c.midiNodeOut; } | |||
#else | |||
@@ -438,12 +464,16 @@ struct AAXClasses | |||
public AudioProcessorListener | |||
{ | |||
public: | |||
JuceAAX_Processor() : sampleRate (0), lastBufferSize (1024), maxBufferSize (1024) | |||
JuceAAX_Processor() : pluginInstance (createPluginFilterOfType (AudioProcessor::wrapperType_AAX)), | |||
busUtils (*pluginInstance, false), | |||
sampleRate (0), lastBufferSize (1024), maxBufferSize (1024), | |||
hasSidechain (false) | |||
{ | |||
pluginInstance = createPluginFilterOfType (AudioProcessor::wrapperType_AAX); | |||
pluginInstance->setPlayHead (this); | |||
pluginInstance->addListener (this); | |||
busUtils.findAllCompatibleLayouts(); | |||
AAX_CEffectParameters::GetNumberOfChunks (&juceChunkIndex); | |||
} | |||
@@ -451,9 +481,13 @@ struct AAXClasses | |||
AAX_Result EffectInit() override | |||
{ | |||
AAX_Result err; | |||
check (Controller()->GetSampleRate (&sampleRate)); | |||
preparePlugin(); | |||
if ((err = preparePlugin()) != AAX_SUCCESS) | |||
return err; | |||
addBypassParameter(); | |||
addAudioProcessorParameters(); | |||
@@ -800,39 +834,63 @@ struct AAXClasses | |||
return AAX_CEffectParameters::NotificationReceived (type, data, size); | |||
} | |||
void process (const float* const* inputs, float* const* outputs, const int bufferSize, | |||
const bool bypass, AAX_IMIDINode* midiNodeIn, AAX_IMIDINode* midiNodesOut) | |||
const float* getAudioBufferForInput (const float* const* inputs, const int sidechain, const int mainNumIns, int idx) const noexcept | |||
{ | |||
const int numIns = pluginInstance->getNumInputChannels(); | |||
const int numOuts = pluginInstance->getNumOutputChannels(); | |||
jassert (idx < (mainNumIns + 1)); | |||
if (numOuts >= numIns) | |||
{ | |||
for (int i = 0; i < numIns; ++i) | |||
memcpy (outputs[i], inputs[i], (size_t) bufferSize * sizeof (float)); | |||
if (idx < mainNumIns) | |||
return inputs[idx]; | |||
return (sidechain != -1 ? inputs[sidechain] : sideChainBuffer.getData()); | |||
} | |||
void process (const float* const* inputs, float* const* outputs, const int sideChainBufferIdx, | |||
const int bufferSize, const bool bypass, | |||
AAX_IMIDINode* midiNodeIn, AAX_IMIDINode* midiNodesOut) | |||
{ | |||
const int numIns = pluginInstance->getTotalNumInputChannels(); | |||
const int numOuts = pluginInstance->getTotalNumOutputChannels(); | |||
process (outputs, numOuts, bufferSize, bypass, midiNodeIn, midiNodesOut); | |||
if (pluginInstance->isSuspended()) | |||
{ | |||
for (int i = 0; i < numOuts; ++i) | |||
FloatVectorOperations::clear (outputs[i], bufferSize); | |||
} | |||
else | |||
{ | |||
if (channelList.size() <= numIns) | |||
channelList.insertMultiple (-1, nullptr, 1 + numIns - channelList.size()); | |||
float** channels = channelList.getRawDataPointer(); | |||
const int mainNumIns = numIns > 0 ? pluginInstance->busArrangement.inputBuses.getReference (0).channels.size() : 0; | |||
const int sidechain = busUtils.getNumEnabledBuses (true) >= 2 ? sideChainBufferIdx : -1; | |||
for (int i = 0; i < numOuts; ++i) | |||
if (numOuts >= numIns) | |||
{ | |||
memcpy (outputs[i], inputs[i], (size_t) bufferSize * sizeof (float)); | |||
channels[i] = outputs[i]; | |||
for (int i = 0; i < numIns; ++i) | |||
memcpy (outputs[i], getAudioBufferForInput (inputs, sidechain, mainNumIns, i), (size_t) bufferSize * sizeof (float)); | |||
process (outputs, numOuts, bufferSize, bypass, midiNodeIn, midiNodesOut); | |||
} | |||
else | |||
{ | |||
if (channelList.size() <= numIns) | |||
channelList.insertMultiple (-1, nullptr, 1 + numIns - channelList.size()); | |||
float** channels = channelList.getRawDataPointer(); | |||
for (int i = 0; i < numOuts; ++i) | |||
{ | |||
memcpy (outputs[i], getAudioBufferForInput (inputs, sidechain, mainNumIns, i), (size_t) bufferSize * sizeof (float)); | |||
channels[i] = outputs[i]; | |||
} | |||
for (int i = numOuts; i < numIns; ++i) | |||
channels[i] = const_cast<float*> (inputs[i]); | |||
for (int i = numOuts; i < numIns; ++i) | |||
channels[i] = const_cast<float*> (getAudioBufferForInput (inputs, sidechain, mainNumIns, i)); | |||
process (channels, numIns, bufferSize, bypass, midiNodeIn, midiNodesOut); | |||
process (channels, numIns, bufferSize, bypass, midiNodeIn, midiNodesOut); | |||
} | |||
} | |||
} | |||
bool supportsSidechain() const noexcept { return hasSidechain; }; | |||
private: | |||
void process (float* const* channels, const int numChans, const int bufferSize, | |||
const bool bypass, AAX_IMIDINode* midiNodeIn, AAX_IMIDINode* midiNodesOut) | |||
@@ -841,10 +899,9 @@ struct AAXClasses | |||
midiBuffer.clear(); | |||
(void) midiNodeIn; | |||
(void) midiNodesOut; | |||
ignoreUnused (midiNodeIn, midiNodesOut); | |||
#if JucePlugin_WantsMidiInput | |||
#if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect | |||
{ | |||
AAX_CMidiStream* const midiStream = midiNodeIn->GetNodeBuffer(); | |||
const uint32_t numMidiEvents = midiStream->mBufferSize; | |||
@@ -864,9 +921,7 @@ struct AAXClasses | |||
if (lastBufferSize != bufferSize) | |||
{ | |||
lastBufferSize = bufferSize; | |||
pluginInstance->setPlayConfigDetails (pluginInstance->getNumInputChannels(), | |||
pluginInstance->getNumOutputChannels(), | |||
sampleRate, bufferSize); | |||
pluginInstance->setRateAndBufferSizeDetails (sampleRate, bufferSize); | |||
if (bufferSize > maxBufferSize) | |||
{ | |||
@@ -877,6 +932,7 @@ struct AAXClasses | |||
// value during initialisation. | |||
pluginInstance->prepareToPlay (sampleRate, bufferSize); | |||
maxBufferSize = bufferSize; | |||
sideChainBuffer.realloc (static_cast<size_t> (maxBufferSize)); | |||
} | |||
} | |||
@@ -888,7 +944,7 @@ struct AAXClasses | |||
pluginInstance->processBlock (buffer, midiBuffer); | |||
} | |||
#if JucePlugin_ProducesMidiOutput | |||
#if JucePlugin_ProducesMidiOutput || JucePlugin_IsMidiEffect | |||
{ | |||
const juce::uint8* midiEventData; | |||
int midiEventSize, midiEventPosition; | |||
@@ -911,8 +967,6 @@ struct AAXClasses | |||
} | |||
} | |||
} | |||
#else | |||
(void) midiNodesOut; | |||
#endif | |||
} | |||
@@ -964,36 +1018,70 @@ struct AAXClasses | |||
} | |||
} | |||
void preparePlugin() | |||
AAX_Result preparePlugin() | |||
{ | |||
AudioProcessor& audioProcessor = getPluginInstance(); | |||
#if JucePlugin_IsMidiEffect | |||
// MIDI effect plug-ins do not support any audio channels | |||
jassert (audioProcessor.busArrangement.getTotalNumInputChannels() == 0 | |||
&& audioProcessor.busArrangement.getTotalNumOutputChannels() == 0); | |||
#else | |||
AAX_EStemFormat inputStemFormat = AAX_eStemFormat_None; | |||
check (Controller()->GetInputStemFormat (&inputStemFormat)); | |||
const int numberOfInputChannels = getNumChannelsForStemFormat (inputStemFormat); | |||
AAX_EStemFormat outputStemFormat = AAX_eStemFormat_None; | |||
check (Controller()->GetOutputStemFormat (&outputStemFormat)); | |||
const int numberOfOutputChannels = getNumChannelsForStemFormat (outputStemFormat); | |||
AudioProcessor& audioProcessor = getPluginInstance(); | |||
const AudioChannelSet inputSet = channelSetFromStemFormat (inputStemFormat, busUtils.busIgnoresLayout (true, 0)); | |||
const AudioChannelSet outputSet = channelSetFromStemFormat (outputStemFormat, busUtils.busIgnoresLayout (false, 0)); | |||
audioProcessor.setSpeakerArrangement (getSpeakerArrangementString (inputStemFormat), | |||
getSpeakerArrangementString (outputStemFormat)); | |||
if ( (inputSet == AudioChannelSet::disabled() && inputStemFormat != AAX_eStemFormat_None) | |||
|| (outputSet == AudioChannelSet::disabled() && outputStemFormat != AAX_eStemFormat_None)) | |||
return AAX_ERROR_UNIMPLEMENTED; | |||
audioProcessor.setPlayConfigDetails (numberOfInputChannels, numberOfOutputChannels, sampleRate, lastBufferSize); | |||
bool success = true; | |||
if (busUtils.getBusCount (true) > 0) | |||
success = audioProcessor.setPreferredBusArrangement (true, 0, inputSet); | |||
if (success && busUtils.getBusCount (false) > 0) | |||
success = audioProcessor.setPreferredBusArrangement (false, 0, outputSet); | |||
// This should never happen as the plugin reported that this layout is supported | |||
jassert (success); | |||
hasSidechain = enableAuxBusesForCurrentFormat (busUtils, inputSet, outputSet); | |||
if (hasSidechain) | |||
sideChainBuffer.realloc (static_cast<size_t> (maxBufferSize)); | |||
// recheck the format | |||
if ( (busUtils.getBusCount (true) > 0 && busUtils.getChannelSet (true, 0) != inputSet) | |||
|| (busUtils.getBusCount (false) > 0 && busUtils.getChannelSet (false, 0) != outputSet) | |||
|| (hasSidechain && busUtils.getNumChannels(true, 1) != 1)) | |||
return AAX_ERROR_UNIMPLEMENTED; | |||
#endif | |||
audioProcessor.setRateAndBufferSizeDetails (sampleRate, maxBufferSize); | |||
audioProcessor.prepareToPlay (sampleRate, lastBufferSize); | |||
maxBufferSize = lastBufferSize; | |||
check (Controller()->SetSignalLatency (audioProcessor.getLatencySamples())); | |||
return AAX_SUCCESS; | |||
} | |||
ScopedJuceInitialiser_GUI libraryInitialiser; | |||
ScopedPointer<AudioProcessor> pluginInstance; | |||
PluginBusUtilities busUtils; | |||
MidiBuffer midiBuffer; | |||
Array<float*> channelList; | |||
int32_t juceChunkIndex; | |||
AAX_CSampleRate sampleRate; | |||
int lastBufferSize, maxBufferSize; | |||
bool hasSidechain; | |||
HeapBlock<float> sideChainBuffer; | |||
struct ChunkMemoryBlock : public ReferenceCountedObject | |||
{ | |||
@@ -1044,6 +1132,25 @@ struct AAXClasses | |||
JUCE_DECLARE_NON_COPYABLE (IndexAsParamID) | |||
}; | |||
//============================================================================== | |||
struct AAXFormatConfiguration | |||
{ | |||
AAXFormatConfiguration() noexcept | |||
: inputFormat (AAX_eStemFormat_None), outputFormat (AAX_eStemFormat_None) {} | |||
AAXFormatConfiguration (AAX_EStemFormat inFormat, AAX_EStemFormat outFormat) noexcept | |||
: inputFormat (inFormat), outputFormat (outFormat) {} | |||
AAX_EStemFormat inputFormat, outputFormat; | |||
bool operator== (const AAXFormatConfiguration other) const noexcept { return (inputFormat == other.inputFormat) && (outputFormat == other.outputFormat); } | |||
bool operator< (const AAXFormatConfiguration other) const noexcept | |||
{ | |||
return (inputFormat == other.inputFormat) ? (outputFormat < other.outputFormat) : (inputFormat < other.inputFormat); | |||
} | |||
}; | |||
//============================================================================== | |||
static void AAX_CALLBACK algorithmProcessCallback (JUCEAlgorithmContext* const instancesBegin[], | |||
const void* const instancesEnd) | |||
@@ -1052,32 +1159,124 @@ struct AAXClasses | |||
{ | |||
const JUCEAlgorithmContext& i = **iter; | |||
i.pluginInstance->parameters.process (i.inputChannels, i.outputChannels, | |||
int sideChainBufferIdx = i.pluginInstance->parameters.supportsSidechain() && i.sideChainBuffers != nullptr | |||
? static_cast<int> (*i.sideChainBuffers) | |||
: -1; | |||
i.pluginInstance->parameters.process (i.inputChannels, i.outputChannels, sideChainBufferIdx, | |||
*(i.bufferSize), *(i.bypass) != 0, | |||
getMidiNodeIn(i), getMidiNodeOut(i)); | |||
} | |||
} | |||
static bool enableAuxBusesForCurrentFormat (PluginBusUtilities& busUtils, const AudioChannelSet& inputLayout, | |||
const AudioChannelSet& outputLayout) | |||
{ | |||
const int numOutBuses = busUtils.getBusCount (false); | |||
const int numInputBuses = busUtils.getBusCount(true); | |||
if (numOutBuses > 1) | |||
{ | |||
PluginBusUtilities::ScopedBusRestorer layoutRestorer (busUtils); | |||
// enable all possible output buses | |||
for (int busIdx = 1; busIdx < busUtils.getBusCount (false); ++busIdx) | |||
{ | |||
AudioChannelSet layout = busUtils.getChannelSet (false, busIdx); | |||
// bus disabled by default? try to enable it with the default layout | |||
if (layout == AudioChannelSet::disabled()) | |||
{ | |||
layout = busUtils.getDefaultLayoutForBus (false, busIdx); | |||
busUtils.processor.setPreferredBusArrangement (false, busIdx, layout); | |||
} | |||
} | |||
// changing output buses may have changed main bus layout | |||
bool success = true; | |||
if (numInputBuses > 0) | |||
success = busUtils.processor.setPreferredBusArrangement (true, 0, inputLayout); | |||
if (success) | |||
success = busUtils.processor.setPreferredBusArrangement (false, 0, outputLayout); | |||
// was the above successful | |||
if (success && (numInputBuses == 0 || busUtils.getChannelSet (true, 0) == inputLayout) | |||
&& busUtils.getChannelSet (false, 0) == outputLayout) | |||
layoutRestorer.release(); | |||
} | |||
// does the plug-in have side-chain support? Check the following: | |||
// 1) does it have an input bus with index = 1 which supports mono | |||
// 2) can all other input buses be disabled | |||
// 3) does the format of the main buses not change when enabling the first bus | |||
if (numInputBuses > 1) | |||
{ | |||
bool success = true; | |||
bool hasSidechain = false; | |||
if (const AudioChannelSet* set = busUtils.getSupportedBusLayouts (true, 1).getDefaultLayoutForChannelNum (1)) | |||
hasSidechain = busUtils.processor.setPreferredBusArrangement (true, 1, *set); | |||
if (! hasSidechain) | |||
success = busUtils.processor.setPreferredBusArrangement (true, 1, AudioChannelSet::disabled()); | |||
// AAX requires your processor's first sidechain to be either mono or that | |||
// it can be disabled | |||
jassert(success); | |||
// disable all other input buses | |||
for (int busIdx = 2; busIdx < numInputBuses; ++busIdx) | |||
{ | |||
success = busUtils.processor.setPreferredBusArrangement (true, busIdx, AudioChannelSet::disabled()); | |||
// AAX can only have a single side-chain input. Therefore, your processor must either | |||
// only have a single side-chain input or allow disabling all other side-chains | |||
jassert (success); | |||
} | |||
if (hasSidechain) | |||
{ | |||
if (busUtils.getBusCount (false) == 0 || busUtils.getBusCount (true) == 0 || | |||
(busUtils.getChannelSet (true, 0) == inputLayout && busUtils.getChannelSet (false, 0) == outputLayout)) | |||
return true; | |||
// restore the old layout | |||
if (busUtils.getBusCount(true) > 0) | |||
busUtils.processor.setPreferredBusArrangement (true, 0, inputLayout); | |||
if (busUtils.getBusCount (false) > 0) | |||
busUtils.processor.setPreferredBusArrangement (false, 0, outputLayout); | |||
} | |||
} | |||
return false; | |||
} | |||
//============================================================================== | |||
static void createDescriptor (AAX_IComponentDescriptor& desc, int channelConfigIndex, | |||
int numInputs, int numOutputs) | |||
static void createDescriptor (AAX_IComponentDescriptor& desc, int configIndex, PluginBusUtilities& busUtils, | |||
const AudioChannelSet& inputLayout, const AudioChannelSet& outputLayout, | |||
const AAX_EStemFormat aaxInputFormat, const AAX_EStemFormat aaxOutputFormat) | |||
{ | |||
check (desc.AddAudioIn (JUCEAlgorithmIDs::inputChannels)); | |||
check (desc.AddAudioOut (JUCEAlgorithmIDs::outputChannels)); | |||
check (desc.AddAudioBufferLength (JUCEAlgorithmIDs::bufferSize)); | |||
check (desc.AddDataInPort (JUCEAlgorithmIDs::bypass, sizeof (int32_t))); | |||
#if JucePlugin_WantsMidiInput | |||
#if JucePlugin_WantsMidiInput || JucePlugin_IsMidiEffect | |||
check (desc.AddMIDINode (JUCEAlgorithmIDs::midiNodeIn, AAX_eMIDINodeType_LocalInput, | |||
JucePlugin_Name, 0xffff)); | |||
#endif | |||
#if JucePlugin_ProducesMidiOutput | |||
#if JucePlugin_ProducesMidiOutput || JucePlugin_IsSynth || JucePlugin_IsMidiEffect | |||
check (desc.AddMIDINode (JUCEAlgorithmIDs::midiNodeOut, AAX_eMIDINodeType_LocalOutput, | |||
JucePlugin_Name " Out", 0xffff)); | |||
#endif | |||
check (desc.AddPrivateData (JUCEAlgorithmIDs::pluginInstance, sizeof (PluginInstanceInfo))); | |||
check (desc.AddPrivateData (JUCEAlgorithmIDs::preparedFlag, sizeof (int32_t))); | |||
// Create a property map | |||
AAX_IPropertyMap* const properties = desc.NewPropertyMap(); | |||
@@ -1092,15 +1291,15 @@ struct AAXClasses | |||
properties->AddProperty (AAX_eProperty_CanBypass, true); | |||
#endif | |||
properties->AddProperty (AAX_eProperty_InputStemFormat, getFormatForChans (numInputs)); | |||
properties->AddProperty (AAX_eProperty_OutputStemFormat, getFormatForChans (numOutputs)); | |||
properties->AddProperty (AAX_eProperty_InputStemFormat, static_cast<AAX_CPropertyValue> (aaxInputFormat)); | |||
properties->AddProperty (AAX_eProperty_OutputStemFormat, static_cast<AAX_CPropertyValue> (aaxOutputFormat)); | |||
// This value needs to match the RTAS wrapper's Type ID, so that | |||
// the host knows that the RTAS/AAX plugins are equivalent. | |||
properties->AddProperty (AAX_eProperty_PlugInID_Native, 'jcaa' + channelConfigIndex); | |||
properties->AddProperty (AAX_eProperty_PlugInID_Native, 'jcaa' + configIndex); | |||
#if ! JucePlugin_AAXDisableAudioSuite | |||
properties->AddProperty (AAX_eProperty_PlugInID_AudioSuite, 'jyaa' + channelConfigIndex); | |||
properties->AddProperty (AAX_eProperty_PlugInID_AudioSuite, 'jyaa' + configIndex); | |||
#endif | |||
#if JucePlugin_AAXDisableMultiMono | |||
@@ -1109,50 +1308,137 @@ struct AAXClasses | |||
properties->AddProperty (AAX_eProperty_Constraint_MultiMonoSupport, true); | |||
#endif | |||
if (enableAuxBusesForCurrentFormat (busUtils, inputLayout, outputLayout)) | |||
{ | |||
check (desc.AddSideChainIn (JUCEAlgorithmIDs::sideChainBuffers)); | |||
properties->AddProperty (AAX_eProperty_SupportsSideChainInput, true); | |||
} | |||
// add the output buses | |||
// This is incrdibly dumb: the output bus format must be well defined | |||
// for every main bus in/out format pair. This means that there cannot | |||
// be two configurations with different aux formats but | |||
// identical main bus in/out formats. | |||
for (int busIdx = 1; busIdx < busUtils.getBusCount (false); ++busIdx) | |||
{ | |||
AudioChannelSet outBusLayout = busUtils.getChannelSet (false, busIdx); | |||
if (outBusLayout != AudioChannelSet::disabled()) | |||
{ | |||
AAX_EStemFormat auxFormat = getFormatForAudioChannelSet (outBusLayout, busUtils.busIgnoresLayout (false, busIdx)); | |||
if (auxFormat != AAX_eStemFormat_INT32_MAX && auxFormat != AAX_eStemFormat_None) | |||
{ | |||
const String& name = busUtils.processor.busArrangement.outputBuses.getReference (busIdx).name; | |||
check (desc.AddAuxOutputStem (0, static_cast<int32_t> (auxFormat), name.toRawUTF8())); | |||
} | |||
} | |||
} | |||
// this assertion should be covered by the assertions above | |||
// if not please report a bug | |||
jassert (busUtils.getNumEnabledBuses (true) <= 2); | |||
check (desc.AddProcessProc_Native (algorithmProcessCallback, properties)); | |||
} | |||
static void getPlugInDescription (AAX_IEffectDescriptor& descriptor) | |||
{ | |||
ScopedPointer<AudioProcessor> plugin = createPluginFilterOfType (AudioProcessor::wrapperType_AAX); | |||
PluginBusUtilities busUtils (*plugin, false); | |||
busUtils.findAllCompatibleLayouts(); | |||
descriptor.AddName (JucePlugin_Desc); | |||
descriptor.AddName (JucePlugin_Name); | |||
descriptor.AddCategory (JucePlugin_AAXCategory); | |||
#ifdef JucePlugin_AAXPageTableFile | |||
// optional page table setting - define this macro in your AppConfig.h if you | |||
// want to set this value - see Avid documentation for details about its format. | |||
// optional page table setting - define this macro in your project if you want | |||
// to set this value - see Avid documentation for details about its format. | |||
descriptor.AddResourceInfo (AAX_eResourceType_PageTable, JucePlugin_AAXPageTableFile); | |||
#endif | |||
check (descriptor.AddProcPtr ((void*) JuceAAX_GUI::Create, kAAX_ProcPtrID_Create_EffectGUI)); | |||
check (descriptor.AddProcPtr ((void*) JuceAAX_Processor::Create, kAAX_ProcPtrID_Create_EffectParameters)); | |||
#ifdef JucePlugin_PreferredChannelConfigurations_AAX | |||
const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations_AAX }; | |||
#else | |||
const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; | |||
#endif | |||
SortedSet<AAXFormatConfiguration> aaxFormats; | |||
SortedSet<AudioChannelSet> inLayouts = busUtils.getBusCount (true) > 0 ? busUtils.getSupportedBusLayouts (true, 0).supportedLayouts : SortedSet<AudioChannelSet>(); | |||
SortedSet<AudioChannelSet> outLayouts = busUtils.getBusCount (false) > 0 ? busUtils.getSupportedBusLayouts (false, 0).supportedLayouts : SortedSet<AudioChannelSet>(); | |||
const int numConfigs = numElementsInArray (channelConfigs); | |||
const int numIns = inLayouts. size(); | |||
const int numOuts = outLayouts.size(); | |||
#if JucePlugin_IsMidiEffect | |||
// MIDI effect plug-ins do not support any audio channels | |||
jassert (numIns == 0 && numOuts == 0); | |||
if (AAX_IComponentDescriptor* const desc = descriptor.NewComponentDescriptor()) | |||
{ | |||
createDescriptor (*desc, 0, busUtils, | |||
AudioChannelSet::disabled(), AudioChannelSet::disabled(), | |||
AAX_eStemFormat_Mono, AAX_eStemFormat_Mono); | |||
check (descriptor.AddComponent (desc)); | |||
} | |||
// You need to actually add some configurations to the JucePlugin_PreferredChannelConfigurations | |||
// value in your JucePluginCharacteristics.h file.. | |||
jassert (numConfigs > 0); | |||
#else | |||
int configIndex = 0; | |||
for (int i = 0; i < numConfigs; ++i) | |||
for (int inIdx = 0; inIdx < jmax (numIns, 1); ++inIdx) | |||
{ | |||
if (AAX_IComponentDescriptor* const desc = descriptor.NewComponentDescriptor()) | |||
for (int outIdx = 0; outIdx < jmax (numOuts, 1); ++outIdx) | |||
{ | |||
const int numIns = channelConfigs [i][0]; | |||
const int numOuts = channelConfigs [i][1]; | |||
bool success = true; | |||
if (numIns > 0) | |||
success = busUtils.processor.setPreferredBusArrangement (true, 0, inLayouts.getReference (inIdx)); | |||
if (numOuts > 0 && success) | |||
success = busUtils.processor.setPreferredBusArrangement (false, 0, outLayouts.getReference (outIdx)); | |||
// We should never hit this assertion: PluginBusUtilities reported this as supported. | |||
// Please report this as a bug! | |||
jassert (success); | |||
AudioChannelSet inLayout = numIns > 0 ? busUtils.getChannelSet (true, 0) : AudioChannelSet(); | |||
AudioChannelSet outLayout = numOuts > 0 ? busUtils.getChannelSet (false, 0) : AudioChannelSet(); | |||
// if we can't set both in AND out formats simultaneously then ignore this format! | |||
if (numIns > 0 && numOuts > 0 && (inLayout != inLayouts.getReference (inIdx) || (outLayout != outLayouts.getReference (outIdx)))) | |||
continue; | |||
AAX_EStemFormat aaxInFormat = getFormatForAudioChannelSet (inLayout, busUtils.busIgnoresLayout (true, 0)); | |||
AAX_EStemFormat aaxOutFormat = getFormatForAudioChannelSet (outLayout, busUtils.busIgnoresLayout (false, 0)); | |||
// does AAX support this layout? | |||
if (aaxInFormat == AAX_eStemFormat_INT32_MAX || aaxOutFormat == AAX_eStemFormat_INT32_MAX) | |||
continue; | |||
if (numIns <= 8 && numOuts <= 8) // AAX doesn't seem to handle more than 8 chans | |||
// AAX requires a single input if this plug-in is a synth | |||
#if JucePlugin_IsSynth | |||
if (numIns == 0) | |||
aaxInFormat = aaxOutFormat; | |||
#endif | |||
if (aaxInFormat == AAX_eStemFormat_None && aaxOutFormat == AAX_eStemFormat_None) | |||
continue; | |||
AAXFormatConfiguration aaxFormat (aaxInFormat, aaxOutFormat); | |||
if (aaxFormats.indexOf (aaxFormat) < 0) | |||
{ | |||
createDescriptor (*desc, i, numIns, numOuts); | |||
check (descriptor.AddComponent (desc)); | |||
aaxFormats.add (aaxFormat); | |||
if (AAX_IComponentDescriptor* const desc = descriptor.NewComponentDescriptor()) | |||
{ | |||
createDescriptor (*desc, configIndex++, busUtils, inLayout, outLayout, aaxInFormat, aaxOutFormat); | |||
check (descriptor.AddComponent (desc)); | |||
} | |||
} | |||
} | |||
} | |||
// You don't have any supported layouts | |||
jassert (configIndex > 0); | |||
#endif | |||
} | |||
}; | |||
@@ -1450,14 +1450,14 @@ OSStatus AUBase::DoRender( AudioUnitRenderActionFlags & ioActionFlags, | |||
} | |||
ca_require (!UsesFixedBlockSize() || inFramesToProcess == GetMaxFramesPerSlice(), ParamErr); | |||
AUOutputElement *output = GetOutput(inBusNumber); // will throw if non-existant | |||
if (output->GetStreamFormat().NumberChannelStreams() != ioData.mNumberBuffers) { | |||
AUOutputElement *output = GetScope(kAudioUnitScope_Output).GetNumberOfElements() > 0 ? GetOutput(inBusNumber) : NULL; // will throw if non-existant | |||
if (output != NULL && output->GetStreamFormat().NumberChannelStreams() != ioData.mNumberBuffers) { | |||
DebugMessageN4("%s:%d ioData.mNumberBuffers=%u, output->GetStreamFormat().NumberChannelStreams()=%u; kAudio_ParamError", | |||
__FILE__, __LINE__, (unsigned)ioData.mNumberBuffers, (unsigned)output->GetStreamFormat().NumberChannelStreams()); | |||
goto ParamErr; | |||
} | |||
unsigned expectedBufferByteSize = inFramesToProcess * output->GetStreamFormat().mBytesPerFrame; | |||
unsigned expectedBufferByteSize = output != NULL ? inFramesToProcess * output->GetStreamFormat().mBytesPerFrame : 0; | |||
for (unsigned ibuf = 0; ibuf < ioData.mNumberBuffers; ++ibuf) { | |||
AudioBuffer &buf = ioData.mBuffers[ibuf]; | |||
if (buf.mData != NULL) { | |||
@@ -50,6 +50,7 @@ | |||
#pragma clang diagnostic ignored "-Wdeprecated-declarations" | |||
#include <TargetConditionals.h> | |||
#include "../../../juce_core/native/juce_mac_ClangBugWorkaround.h" | |||
#if TARGET_OS_MAC | |||
#include <pthread.h> | |||
@@ -802,14 +803,18 @@ private: | |||
UInt32 inNumberFrames, | |||
AudioBufferList & ioData) | |||
{ | |||
if (ioData.mBuffers[0].mData == NULL || (theOutput->WillAllocateBuffer() && Outputs().GetNumberOfElements() > 1)) | |||
// will render into cache buffer | |||
theOutput->PrepareBuffer(inNumberFrames); | |||
else | |||
// will render into caller's buffer | |||
theOutput->SetBufferList(ioData); | |||
if (theOutput != NULL) | |||
{ | |||
if (ioData.mBuffers[0].mData == NULL || (theOutput->WillAllocateBuffer() && Outputs().GetNumberOfElements() > 1)) | |||
// will render into cache buffer | |||
theOutput->PrepareBuffer(inNumberFrames); | |||
else | |||
// will render into caller's buffer | |||
theOutput->SetBufferList(ioData); | |||
} | |||
OSStatus result = RenderBus(ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames); | |||
if (result == noErr) { | |||
if (result == noErr && theOutput != NULL) { | |||
if (ioData.mBuffers[0].mData == NULL) { | |||
theOutput->CopyBufferListTo(ioData); | |||
AUTRACE(kCATrace_AUBaseDoRenderBus, mComponentInstance, inNumberFrames, (intptr_t)theOutput->GetBufferList().mBuffers[0].mData, 0, *(UInt32 *)ioData.mBuffers[0].mData); | |||
@@ -47,6 +47,8 @@ | |||
#ifndef __AUBuffer_h__ | |||
#define __AUBuffer_h__ | |||
#include "../../../juce_core/native/juce_mac_ClangBugWorkaround.h" | |||
#include <TargetConditionals.h> | |||
#if !defined(__COREAUDIO_USE_FLAT_INCLUDES__) | |||
#include <AudioUnit/AudioUnit.h> | |||
@@ -48,6 +48,7 @@ | |||
#define __AUCarbonViewBase_h__ | |||
#pragma clang diagnostic ignored "-Wdeprecated-declarations" | |||
#include "../../../juce_core/native/juce_mac_ClangBugWorkaround.h" | |||
#include <vector> | |||
#include "AUCarbonViewControl.h" | |||
@@ -47,6 +47,7 @@ | |||
#ifndef __AUCarbonViewControl_h__ | |||
#define __AUCarbonViewControl_h__ | |||
#include "../../../juce_core/native/juce_mac_ClangBugWorkaround.h" | |||
#include <Carbon/Carbon.h> | |||
#include <AudioUnit/AudioUnitCarbonView.h> | |||
#include <AudioToolbox/AudioUnitUtilities.h> | |||
@@ -1,465 +0,0 @@ | |||
/* | |||
File: AUEffectBase.cpp | |||
Abstract: AUEffectBase.h | |||
Version: 1.1 | |||
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple | |||
Inc. ("Apple") in consideration of your agreement to the following | |||
terms, and your use, installation, modification or redistribution of | |||
this Apple software constitutes acceptance of these terms. If you do | |||
not agree with these terms, please do not use, install, modify or | |||
redistribute this Apple software. | |||
In consideration of your agreement to abide by the following terms, and | |||
subject to these terms, Apple grants you a personal, non-exclusive | |||
license, under Apple's copyrights in this original Apple software (the | |||
"Apple Software"), to use, reproduce, modify and redistribute the Apple | |||
Software, with or without modifications, in source and/or binary forms; | |||
provided that if you redistribute the Apple Software in its entirety and | |||
without modifications, you must retain this notice and the following | |||
text and disclaimers in all such redistributions of the Apple Software. | |||
Neither the name, trademarks, service marks or logos of Apple Inc. may | |||
be used to endorse or promote products derived from the Apple Software | |||
without specific prior written permission from Apple. Except as | |||
expressly stated in this notice, no other rights or licenses, express or | |||
implied, are granted by Apple herein, including but not limited to any | |||
patent rights that may be infringed by your derivative works or by other | |||
works in which the Apple Software may be incorporated. | |||
The Apple Software is provided by Apple on an "AS IS" basis. APPLE | |||
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION | |||
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS | |||
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND | |||
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. | |||
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL | |||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, | |||
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED | |||
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), | |||
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE | |||
POSSIBILITY OF SUCH DAMAGE. | |||
Copyright (C) 2014 Apple Inc. All Rights Reserved. | |||
*/ | |||
#include "AUEffectBase.h" | |||
/* | |||
This class does not deal as well as it should with N-M effects... | |||
The problem areas are (if the channels don't match): | |||
ProcessInPlace if the channels don't match - there will be problems if InputChan != OutputChan | |||
Bypass - its just passing the buffers through when not processing them | |||
This will be fixed in a future update... | |||
*/ | |||
//_____________________________________________________________________________ | |||
// | |||
AUEffectBase::AUEffectBase( AudioComponentInstance audioUnit, | |||
bool inProcessesInPlace ) : | |||
AUBase(audioUnit, 1, 1), // 1 in bus, 1 out bus | |||
mBypassEffect(false), | |||
mParamSRDep (false), | |||
mProcessesInPlace(inProcessesInPlace), | |||
mMainOutput(NULL), mMainInput(NULL) | |||
#if TARGET_OS_IPHONE | |||
, mOnlyOneKernel(false) | |||
#endif | |||
{ | |||
} | |||
//_____________________________________________________________________________ | |||
// | |||
AUEffectBase::~AUEffectBase() | |||
{ | |||
Cleanup(); | |||
} | |||
//_____________________________________________________________________________ | |||
// | |||
void AUEffectBase::Cleanup() | |||
{ | |||
for (KernelList::iterator it = mKernelList.begin(); it != mKernelList.end(); ++it) | |||
delete *it; | |||
mKernelList.clear(); | |||
mMainOutput = NULL; | |||
mMainInput = NULL; | |||
} | |||
//_____________________________________________________________________________ | |||
// | |||
OSStatus AUEffectBase::Initialize() | |||
{ | |||
// get our current numChannels for input and output | |||
SInt16 auNumInputs = (SInt16) GetInput(0)->GetStreamFormat().mChannelsPerFrame; | |||
SInt16 auNumOutputs = (SInt16) GetOutput(0)->GetStreamFormat().mChannelsPerFrame; | |||
// does the unit publish specific information about channel configurations? | |||
const AUChannelInfo *auChannelConfigs = NULL; | |||
UInt32 numIOconfigs = SupportedNumChannels(&auChannelConfigs); | |||
if ((numIOconfigs > 0) && (auChannelConfigs != NULL)) | |||
{ | |||
bool foundMatch = false; | |||
for (UInt32 i = 0; (i < numIOconfigs) && !foundMatch; ++i) | |||
{ | |||
SInt16 configNumInputs = auChannelConfigs[i].inChannels; | |||
SInt16 configNumOutputs = auChannelConfigs[i].outChannels; | |||
if ((configNumInputs < 0) && (configNumOutputs < 0)) | |||
{ | |||
// unit accepts any number of channels on input and output | |||
if (((configNumInputs == -1) && (configNumOutputs == -2)) | |||
|| ((configNumInputs == -2) && (configNumOutputs == -1))) | |||
{ | |||
foundMatch = true; | |||
// unit accepts any number of channels on input and output IFF they are the same number on both scopes | |||
} | |||
else if (((configNumInputs == -1) && (configNumOutputs == -1)) && (auNumInputs == auNumOutputs)) | |||
{ | |||
foundMatch = true; | |||
// unit has specified a particular number of channels on both scopes | |||
} | |||
else | |||
continue; | |||
} | |||
else | |||
{ | |||
// the -1 case on either scope is saying that the unit doesn't care about the | |||
// number of channels on that scope | |||
bool inputMatch = (auNumInputs == configNumInputs) || (configNumInputs == -1); | |||
bool outputMatch = (auNumOutputs == configNumOutputs) || (configNumOutputs == -1); | |||
if (inputMatch && outputMatch) | |||
foundMatch = true; | |||
} | |||
} | |||
if (!foundMatch) | |||
return kAudioUnitErr_FormatNotSupported; | |||
} | |||
else | |||
{ | |||
// there is no specifically published channel info | |||
// so for those kinds of effects, the assumption is that the channels (whatever their number) | |||
// should match on both scopes | |||
if ((auNumOutputs != auNumInputs) || (auNumOutputs == 0)) | |||
{ | |||
return kAudioUnitErr_FormatNotSupported; | |||
} | |||
} | |||
MaintainKernels(); | |||
mMainOutput = GetOutput(0); | |||
mMainInput = GetInput(0); | |||
const CAStreamBasicDescription& format = GetStreamFormat(kAudioUnitScope_Output, 0); | |||
format.IdentifyCommonPCMFormat(mCommonPCMFormat, NULL); | |||
mBytesPerFrame = format.mBytesPerFrame; | |||
return noErr; | |||
} | |||
OSStatus AUEffectBase::Reset( AudioUnitScope inScope, | |||
AudioUnitElement inElement) | |||
{ | |||
for (KernelList::iterator it = mKernelList.begin(); it != mKernelList.end(); ++it) { | |||
AUKernelBase *kernel = *it; | |||
if (kernel != NULL) | |||
kernel->Reset(); | |||
} | |||
return AUBase::Reset(inScope, inElement); | |||
} | |||
OSStatus AUEffectBase::GetPropertyInfo (AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
UInt32 & outDataSize, | |||
Boolean & outWritable) | |||
{ | |||
if (inScope == kAudioUnitScope_Global) { | |||
switch (inID) { | |||
case kAudioUnitProperty_BypassEffect: | |||
outWritable = true; | |||
outDataSize = sizeof (UInt32); | |||
return noErr; | |||
case kAudioUnitProperty_InPlaceProcessing: | |||
outWritable = true; | |||
outDataSize = sizeof (UInt32); | |||
return noErr; | |||
} | |||
} | |||
return AUBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); | |||
} | |||
OSStatus AUEffectBase::GetProperty (AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
void * outData) | |||
{ | |||
if (inScope == kAudioUnitScope_Global) { | |||
switch (inID) { | |||
case kAudioUnitProperty_BypassEffect: | |||
*((UInt32*)outData) = (IsBypassEffect() ? 1 : 0); | |||
return noErr; | |||
case kAudioUnitProperty_InPlaceProcessing: | |||
*((UInt32*)outData) = (mProcessesInPlace ? 1 : 0); | |||
return noErr; | |||
} | |||
} | |||
return AUBase::GetProperty (inID, inScope, inElement, outData); | |||
} | |||
OSStatus AUEffectBase::SetProperty( AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
const void * inData, | |||
UInt32 inDataSize) | |||
{ | |||
if (inScope == kAudioUnitScope_Global) { | |||
switch (inID) { | |||
case kAudioUnitProperty_BypassEffect: | |||
{ | |||
if (inDataSize < sizeof(UInt32)) | |||
return kAudioUnitErr_InvalidPropertyValue; | |||
bool tempNewSetting = *((UInt32*)inData) != 0; | |||
// we're changing the state of bypass | |||
if (tempNewSetting != IsBypassEffect()) | |||
{ | |||
if (!tempNewSetting && IsBypassEffect() && IsInitialized()) // turning bypass off and we're initialized | |||
Reset(0, 0); | |||
SetBypassEffect (tempNewSetting); | |||
} | |||
return noErr; | |||
} | |||
case kAudioUnitProperty_InPlaceProcessing: | |||
mProcessesInPlace = (*((UInt32*)inData) != 0); | |||
return noErr; | |||
} | |||
} | |||
return AUBase::SetProperty (inID, inScope, inElement, inData, inDataSize); | |||
} | |||
void AUEffectBase::MaintainKernels() | |||
{ | |||
#if TARGET_OS_IPHONE | |||
UInt32 nKernels = mOnlyOneKernel ? 1 : GetNumberOfChannels(); | |||
#else | |||
UInt32 nKernels = GetNumberOfChannels(); | |||
#endif | |||
if (mKernelList.size() < nKernels) { | |||
mKernelList.reserve(nKernels); | |||
for (UInt32 i = (UInt32)mKernelList.size(); i < nKernels; ++i) | |||
mKernelList.push_back(NewKernel()); | |||
} else { | |||
while (mKernelList.size() > nKernels) { | |||
AUKernelBase *kernel = mKernelList.back(); | |||
delete kernel; | |||
mKernelList.pop_back(); | |||
} | |||
} | |||
for(unsigned int i = 0; i < nKernels; i++ ) | |||
{ | |||
if(mKernelList[i]) { | |||
mKernelList[i]->SetChannelNum (i); | |||
} | |||
} | |||
} | |||
bool AUEffectBase::StreamFormatWritable( AudioUnitScope scope, | |||
AudioUnitElement element) | |||
{ | |||
return IsInitialized() ? false : true; | |||
} | |||
OSStatus AUEffectBase::ChangeStreamFormat( AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
const CAStreamBasicDescription & inPrevFormat, | |||
const CAStreamBasicDescription & inNewFormat) | |||
{ | |||
OSStatus result = AUBase::ChangeStreamFormat(inScope, inElement, inPrevFormat, inNewFormat); | |||
if (result == noErr) | |||
{ | |||
// for the moment this only dependency we know about | |||
// where a parameter's range may change is with the sample rate | |||
// and effects are only publishing parameters in the global scope! | |||
if (GetParamHasSampleRateDependency() && fnotequal(inPrevFormat.mSampleRate, inNewFormat.mSampleRate)) | |||
PropertyChanged(kAudioUnitProperty_ParameterList, kAudioUnitScope_Global, 0); | |||
} | |||
return result; | |||
} | |||
// ____________________________________________________________________________ | |||
// | |||
// This method is called (potentially repeatedly) by ProcessForScheduledParams() | |||
// in order to perform the actual DSP required for this portion of the entire buffer | |||
// being processed. The entire buffer can be divided up into smaller "slices" | |||
// according to the timestamps on the scheduled parameters... | |||
// | |||
OSStatus AUEffectBase::ProcessScheduledSlice( void *inUserData, | |||
UInt32 inStartFrameInBuffer, | |||
UInt32 inSliceFramesToProcess, | |||
UInt32 inTotalBufferFrames ) | |||
{ | |||
ScheduledProcessParams &sliceParams = *((ScheduledProcessParams*)inUserData); | |||
AudioUnitRenderActionFlags &actionFlags = *sliceParams.actionFlags; | |||
AudioBufferList &inputBufferList = *sliceParams.inputBufferList; | |||
AudioBufferList &outputBufferList = *sliceParams.outputBufferList; | |||
UInt32 channelSize = inSliceFramesToProcess * mBytesPerFrame; | |||
// fix the size of the buffer we're operating on before we render this slice of time | |||
for(unsigned int i = 0; i < inputBufferList.mNumberBuffers; i++ ) { | |||
inputBufferList.mBuffers[i].mDataByteSize = inputBufferList.mBuffers[i].mNumberChannels * channelSize; | |||
} | |||
for(unsigned int i = 0; i < outputBufferList.mNumberBuffers; i++ ) { | |||
outputBufferList.mBuffers[i].mDataByteSize = outputBufferList.mBuffers[i].mNumberChannels * channelSize; | |||
} | |||
// process the buffer | |||
OSStatus result = ProcessBufferLists(actionFlags, inputBufferList, outputBufferList, inSliceFramesToProcess ); | |||
// we just partially processed the buffers, so increment the data pointers to the next part of the buffer to process | |||
for(unsigned int i = 0; i < inputBufferList.mNumberBuffers; i++ ) { | |||
inputBufferList.mBuffers[i].mData = | |||
(char *)inputBufferList.mBuffers[i].mData + inputBufferList.mBuffers[i].mNumberChannels * channelSize; | |||
} | |||
for(unsigned int i = 0; i < outputBufferList.mNumberBuffers; i++ ) { | |||
outputBufferList.mBuffers[i].mData = | |||
(char *)outputBufferList.mBuffers[i].mData + outputBufferList.mBuffers[i].mNumberChannels * channelSize; | |||
} | |||
return result; | |||
} | |||
// ____________________________________________________________________________ | |||
// | |||
OSStatus AUEffectBase::Render( AudioUnitRenderActionFlags &ioActionFlags, | |||
const AudioTimeStamp & inTimeStamp, | |||
UInt32 nFrames) | |||
{ | |||
if (!HasInput(0)) | |||
return kAudioUnitErr_NoConnection; | |||
OSStatus result = noErr; | |||
result = mMainInput->PullInput(ioActionFlags, inTimeStamp, 0 /* element */, nFrames); | |||
if (result == noErr) | |||
{ | |||
if(ProcessesInPlace() && mMainOutput->WillAllocateBuffer()) | |||
{ | |||
mMainOutput->SetBufferList(mMainInput->GetBufferList() ); | |||
} | |||
if (ShouldBypassEffect()) | |||
{ | |||
// leave silence bit alone | |||
if(!ProcessesInPlace() ) | |||
{ | |||
mMainInput->CopyBufferContentsTo (mMainOutput->GetBufferList()); | |||
} | |||
} | |||
else | |||
{ | |||
if(mParamList.size() == 0 ) | |||
{ | |||
// this will read/write silence bit | |||
result = ProcessBufferLists(ioActionFlags, mMainInput->GetBufferList(), mMainOutput->GetBufferList(), nFrames); | |||
} | |||
else | |||
{ | |||
// deal with scheduled parameters... | |||
AudioBufferList &inputBufferList = mMainInput->GetBufferList(); | |||
AudioBufferList &outputBufferList = mMainOutput->GetBufferList(); | |||
ScheduledProcessParams processParams; | |||
processParams.actionFlags = &ioActionFlags; | |||
processParams.inputBufferList = &inputBufferList; | |||
processParams.outputBufferList = &outputBufferList; | |||
// divide up the buffer into slices according to scheduled params then | |||
// do the DSP for each slice (ProcessScheduledSlice() called for each slice) | |||
result = ProcessForScheduledParams( mParamList, | |||
nFrames, | |||
&processParams ); | |||
// fixup the buffer pointers to how they were before we started | |||
UInt32 channelSize = nFrames * mBytesPerFrame; | |||
for(unsigned int i = 0; i < inputBufferList.mNumberBuffers; i++ ) { | |||
UInt32 size = inputBufferList.mBuffers[i].mNumberChannels * channelSize; | |||
inputBufferList.mBuffers[i].mData = (char *)inputBufferList.mBuffers[i].mData - size; | |||
inputBufferList.mBuffers[i].mDataByteSize = size; | |||
} | |||
for(unsigned int i = 0; i < outputBufferList.mNumberBuffers; i++ ) { | |||
UInt32 size = outputBufferList.mBuffers[i].mNumberChannels * channelSize; | |||
outputBufferList.mBuffers[i].mData = (char *)outputBufferList.mBuffers[i].mData - size; | |||
outputBufferList.mBuffers[i].mDataByteSize = size; | |||
} | |||
} | |||
} | |||
if ( (ioActionFlags & kAudioUnitRenderAction_OutputIsSilence) && !ProcessesInPlace() ) | |||
{ | |||
AUBufferList::ZeroBuffer(mMainOutput->GetBufferList() ); | |||
} | |||
} | |||
return result; | |||
} | |||
OSStatus AUEffectBase::ProcessBufferLists( | |||
AudioUnitRenderActionFlags & ioActionFlags, | |||
const AudioBufferList & inBuffer, | |||
AudioBufferList & outBuffer, | |||
UInt32 inFramesToProcess ) | |||
{ | |||
if (ShouldBypassEffect()) | |||
return noErr; | |||
// interleaved (or mono) | |||
switch (mCommonPCMFormat) { | |||
case CAStreamBasicDescription::kPCMFormatFloat32 : | |||
ProcessBufferListsT<Float32>(ioActionFlags, inBuffer, outBuffer, inFramesToProcess); | |||
break; | |||
case CAStreamBasicDescription::kPCMFormatFixed824 : | |||
ProcessBufferListsT<SInt32>(ioActionFlags, inBuffer, outBuffer, inFramesToProcess); | |||
break; | |||
case CAStreamBasicDescription::kPCMFormatInt16 : | |||
ProcessBufferListsT<SInt16>(ioActionFlags, inBuffer, outBuffer, inFramesToProcess); | |||
break; | |||
default : | |||
throw CAException(kAudio_UnimplementedError); | |||
} | |||
return noErr; | |||
} | |||
Float64 AUEffectBase::GetSampleRate() | |||
{ | |||
return GetOutput(0)->GetStreamFormat().mSampleRate; | |||
} | |||
UInt32 AUEffectBase::GetNumberOfChannels() | |||
{ | |||
return GetOutput(0)->GetStreamFormat().mChannelsPerFrame; | |||
} |
@@ -1,377 +0,0 @@ | |||
/* | |||
File: AUEffectBase.h | |||
Abstract: Part of CoreAudio Utility Classes | |||
Version: 1.1 | |||
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple | |||
Inc. ("Apple") in consideration of your agreement to the following | |||
terms, and your use, installation, modification or redistribution of | |||
this Apple software constitutes acceptance of these terms. If you do | |||
not agree with these terms, please do not use, install, modify or | |||
redistribute this Apple software. | |||
In consideration of your agreement to abide by the following terms, and | |||
subject to these terms, Apple grants you a personal, non-exclusive | |||
license, under Apple's copyrights in this original Apple software (the | |||
"Apple Software"), to use, reproduce, modify and redistribute the Apple | |||
Software, with or without modifications, in source and/or binary forms; | |||
provided that if you redistribute the Apple Software in its entirety and | |||
without modifications, you must retain this notice and the following | |||
text and disclaimers in all such redistributions of the Apple Software. | |||
Neither the name, trademarks, service marks or logos of Apple Inc. may | |||
be used to endorse or promote products derived from the Apple Software | |||
without specific prior written permission from Apple. Except as | |||
expressly stated in this notice, no other rights or licenses, express or | |||
implied, are granted by Apple herein, including but not limited to any | |||
patent rights that may be infringed by your derivative works or by other | |||
works in which the Apple Software may be incorporated. | |||
The Apple Software is provided by Apple on an "AS IS" basis. APPLE | |||
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION | |||
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS | |||
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND | |||
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. | |||
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL | |||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, | |||
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED | |||
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), | |||
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE | |||
POSSIBILITY OF SUCH DAMAGE. | |||
Copyright (C) 2014 Apple Inc. All Rights Reserved. | |||
*/ | |||
#ifndef __AUEffectBase_h__ | |||
#define __AUEffectBase_h__ | |||
#include "AUBase.h" | |||
#include "AUSilentTimeout.h" | |||
#include "CAException.h" | |||
class AUKernelBase; | |||
// Base class for an effect with one input stream, one output stream, | |||
// any number of channels. | |||
/*! @class AUEffectBase */ | |||
class AUEffectBase : public AUBase { | |||
public: | |||
/*! @ctor AUEffectBase */ | |||
AUEffectBase( AudioComponentInstance audioUnit, | |||
bool inProcessesInPlace = true ); | |||
/*! @dtor ~AUEffectBase */ | |||
~AUEffectBase(); | |||
/*! @method Initialize */ | |||
virtual OSStatus Initialize(); | |||
/*! @method Cleanup */ | |||
virtual void Cleanup(); | |||
/*! @method Reset */ | |||
virtual OSStatus Reset( AudioUnitScope inScope, | |||
AudioUnitElement inElement); | |||
/*! @method GetPropertyInfo */ | |||
virtual OSStatus GetPropertyInfo (AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
UInt32 & outDataSize, | |||
Boolean & outWritable); | |||
/*! @method GetProperty */ | |||
virtual OSStatus GetProperty (AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
void * outData); | |||
/*! @method SetProperty */ | |||
virtual OSStatus SetProperty(AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
const void * inData, | |||
UInt32 inDataSize); | |||
/*! @method StreamFormatWritable */ | |||
virtual bool StreamFormatWritable (AudioUnitScope scope, | |||
AudioUnitElement element); | |||
/*! @method ChangeStreamFormat */ | |||
virtual OSStatus ChangeStreamFormat ( | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
const CAStreamBasicDescription & inPrevFormat, | |||
const CAStreamBasicDescription & inNewFormat); | |||
/*! @method Render */ | |||
virtual OSStatus Render(AudioUnitRenderActionFlags & ioActionFlags, | |||
const AudioTimeStamp & inTimeStamp, | |||
UInt32 inNumberFrames); | |||
// our virtual methods | |||
// If your unit processes N to N channels, and there are no interactions between channels, | |||
// it can override NewKernel to create a mono processing object per channel. Otherwise, | |||
// don't override NewKernel, and instead, override ProcessBufferLists. | |||
/*! @method NewKernel */ | |||
virtual AUKernelBase * NewKernel() { return NULL; } | |||
/*! @method ProcessBufferLists */ | |||
virtual OSStatus ProcessBufferLists( | |||
AudioUnitRenderActionFlags & ioActionFlags, | |||
const AudioBufferList & inBuffer, | |||
AudioBufferList & outBuffer, | |||
UInt32 inFramesToProcess ); | |||
// convenience format accessors (use output 0's format) | |||
/*! @method GetSampleRate */ | |||
Float64 GetSampleRate(); | |||
/*! @method GetNumberOfChannels */ | |||
UInt32 GetNumberOfChannels(); | |||
// convenience wrappers for accessing parameters in the global scope | |||
/*! @method SetParameter */ | |||
using AUBase::SetParameter; | |||
void SetParameter( AudioUnitParameterID paramID, | |||
AudioUnitParameterValue value) | |||
{ | |||
Globals()->SetParameter(paramID, value); | |||
} | |||
/*! @method GetParameter */ | |||
using AUBase::GetParameter; | |||
AudioUnitParameterValue GetParameter( AudioUnitParameterID paramID ) | |||
{ | |||
return Globals()->GetParameter(paramID ); | |||
} | |||
/*! @method CanScheduleParameters */ | |||
virtual bool CanScheduleParameters() const { return true; } | |||
/*! @method IsBypassEffect */ | |||
// This is used for the property value - to reflect to the UI if an effect is bypassed | |||
bool IsBypassEffect () { return mBypassEffect; } | |||
protected: | |||
/*! @method MaintainKernels */ | |||
void MaintainKernels(); | |||
/*! @method ShouldBypassEffect */ | |||
// This is used in the render call to see if an effect is bypassed | |||
// It can return a different status than IsBypassEffect (though it MUST take that into account) | |||
virtual bool ShouldBypassEffect () { return IsBypassEffect(); } | |||
public: | |||
/*! @method SetBypassEffect */ | |||
virtual void SetBypassEffect (bool inFlag) { mBypassEffect = inFlag; } | |||
/*! @method SetParamHasSampleRateDependency */ | |||
void SetParamHasSampleRateDependency (bool inFlag) | |||
{ | |||
mParamSRDep = inFlag; | |||
} | |||
/*! @method GetParamHasSampleRateDependency */ | |||
bool GetParamHasSampleRateDependency () const { return mParamSRDep; } | |||
struct ScheduledProcessParams // pointer passed in as void* userData param for ProcessScheduledSlice() | |||
{ | |||
AudioUnitRenderActionFlags *actionFlags; | |||
AudioBufferList *inputBufferList; | |||
AudioBufferList *outputBufferList; | |||
}; | |||
virtual OSStatus ProcessScheduledSlice( void *inUserData, | |||
UInt32 inStartFrameInBuffer, | |||
UInt32 inSliceFramesToProcess, | |||
UInt32 inTotalBufferFrames ); | |||
bool ProcessesInPlace() const {return mProcessesInPlace;}; | |||
void SetProcessesInPlace(bool inProcessesInPlace) {mProcessesInPlace = inProcessesInPlace;}; | |||
typedef std::vector<AUKernelBase *> KernelList; | |||
protected: | |||
/*! @var mKernelList */ | |||
KernelList mKernelList; | |||
AUKernelBase* GetKernel(UInt32 index) { return mKernelList[index]; } | |||
/*! @method IsInputSilent */ | |||
bool IsInputSilent (AudioUnitRenderActionFlags inActionFlags, UInt32 inFramesToProcess) | |||
{ | |||
bool inputSilent = (inActionFlags & kAudioUnitRenderAction_OutputIsSilence) != 0; | |||
// take latency and tail time into account when propagating the silent bit | |||
UInt32 silentTimeoutFrames = UInt32(GetSampleRate() * (GetLatency() + GetTailTime())); | |||
mSilentTimeout.Process (inFramesToProcess, silentTimeoutFrames, inputSilent); | |||
return inputSilent; | |||
} | |||
#if TARGET_OS_IPHONE | |||
void SetOnlyOneKernel(bool inUseOnlyOneKernel) { mOnlyOneKernel = inUseOnlyOneKernel; } // set in ctor of subclass that wants it. | |||
#endif | |||
template <typename T> | |||
void ProcessBufferListsT( | |||
AudioUnitRenderActionFlags & ioActionFlags, | |||
const AudioBufferList & inBuffer, | |||
AudioBufferList & outBuffer, | |||
UInt32 inFramesToProcess ); | |||
CAStreamBasicDescription::CommonPCMFormat GetCommonPCMFormat() const { return mCommonPCMFormat; } | |||
private: | |||
/*! @var mBypassEffect */ | |||
bool mBypassEffect; | |||
/*! @var mParamSRDep */ | |||
bool mParamSRDep; | |||
/*! @var mProcessesInplace */ | |||
bool mProcessesInPlace; | |||
/*! @var mSilentTimeout */ | |||
AUSilentTimeout mSilentTimeout; | |||
/*! @var mMainOutput */ | |||
AUOutputElement * mMainOutput; | |||
/*! @var mMainInput */ | |||
AUInputElement * mMainInput; | |||
#if TARGET_OS_IPHONE | |||
/*! @var mOnlyOneKernel */ | |||
bool mOnlyOneKernel; | |||
#endif | |||
/*! @var mCommonPCMFormat */ | |||
CAStreamBasicDescription::CommonPCMFormat mCommonPCMFormat; | |||
UInt32 mBytesPerFrame; | |||
}; | |||
// Base class for a "kernel", an object that performs DSP on one channel of an interleaved stream. | |||
/*! @class AUKernelBase */ | |||
class AUKernelBase { | |||
public: | |||
/*! @ctor AUKernelBase */ | |||
AUKernelBase(AUEffectBase *inAudioUnit ) : | |||
mAudioUnit(inAudioUnit) { } | |||
/*! @dtor ~AUKernelBase */ | |||
virtual ~AUKernelBase() { } | |||
/*! @method Reset */ | |||
virtual void Reset() { } | |||
/*! @method Process */ | |||
virtual void Process( const Float32 * inSourceP, | |||
Float32 * inDestP, | |||
UInt32 inFramesToProcess, | |||
UInt32 inNumChannels, | |||
bool & ioSilence) { throw CAException(kAudio_UnimplementedError ); } | |||
/*! @method Process */ | |||
virtual void Process( const SInt32 * inSourceP, | |||
SInt32 * inDestP, | |||
UInt32 inFramesToProcess, | |||
UInt32 inNumChannels, | |||
bool & ioSilence) { throw CAException(kAudio_UnimplementedError ); } | |||
/*! @method Process */ | |||
virtual void Process( const SInt16 * inSourceP, | |||
SInt16 * inDestP, | |||
UInt32 inFramesToProcess, | |||
UInt32 inNumChannels, | |||
bool & ioSilence) { throw CAException(kAudio_UnimplementedError ); } | |||
/*! @method GetSampleRate */ | |||
Float64 GetSampleRate() | |||
{ | |||
return mAudioUnit->GetSampleRate(); | |||
} | |||
/*! @method GetParameter */ | |||
AudioUnitParameterValue GetParameter (AudioUnitParameterID paramID) | |||
{ | |||
return mAudioUnit->GetParameter(paramID); | |||
} | |||
void SetChannelNum (UInt32 inChan) { mChannelNum = inChan; } | |||
UInt32 GetChannelNum () { return mChannelNum; } | |||
protected: | |||
/*! @var mAudioUnit */ | |||
AUEffectBase * mAudioUnit; | |||
UInt32 mChannelNum; | |||
}; | |||
template <typename T> | |||
void AUEffectBase::ProcessBufferListsT( | |||
AudioUnitRenderActionFlags & ioActionFlags, | |||
const AudioBufferList & inBuffer, | |||
AudioBufferList & outBuffer, | |||
UInt32 inFramesToProcess ) | |||
{ | |||
bool ioSilence; | |||
bool silentInput = IsInputSilent (ioActionFlags, inFramesToProcess); | |||
ioActionFlags |= kAudioUnitRenderAction_OutputIsSilence; | |||
// call the kernels to handle either interleaved or deinterleaved | |||
if (inBuffer.mNumberBuffers == 1) { | |||
if (inBuffer.mBuffers[0].mNumberChannels == 0) | |||
throw CAException(kAudio_ParamError); | |||
for (UInt32 channel = 0; channel < mKernelList.size(); ++channel) { | |||
AUKernelBase *kernel = mKernelList[channel]; | |||
if (kernel == NULL) continue; | |||
ioSilence = silentInput; | |||
// process each interleaved channel individually | |||
kernel->Process( | |||
(const T *)inBuffer.mBuffers[0].mData + channel, | |||
(T *)outBuffer.mBuffers[0].mData + channel, | |||
inFramesToProcess, | |||
inBuffer.mBuffers[0].mNumberChannels, | |||
ioSilence); | |||
if (!ioSilence) | |||
ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; | |||
} | |||
} else { | |||
for (UInt32 channel = 0; channel < mKernelList.size(); ++channel) { | |||
AUKernelBase *kernel = mKernelList[channel]; | |||
if (kernel == NULL) continue; | |||
ioSilence = silentInput; | |||
const AudioBuffer *srcBuffer = &inBuffer.mBuffers[channel]; | |||
AudioBuffer *destBuffer = &outBuffer.mBuffers[channel]; | |||
kernel->Process( | |||
(const T *)srcBuffer->mData, | |||
(T *)destBuffer->mData, | |||
inFramesToProcess, | |||
1, | |||
ioSilence); | |||
if (!ioSilence) | |||
ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; | |||
} | |||
} | |||
} | |||
#endif // __AUEffectBase_h__ |
@@ -1,164 +0,0 @@ | |||
/* | |||
File: AUMIDIEffectBase.cpp | |||
Abstract: AUMIDIEffectBase.h | |||
Version: 1.1 | |||
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple | |||
Inc. ("Apple") in consideration of your agreement to the following | |||
terms, and your use, installation, modification or redistribution of | |||
this Apple software constitutes acceptance of these terms. If you do | |||
not agree with these terms, please do not use, install, modify or | |||
redistribute this Apple software. | |||
In consideration of your agreement to abide by the following terms, and | |||
subject to these terms, Apple grants you a personal, non-exclusive | |||
license, under Apple's copyrights in this original Apple software (the | |||
"Apple Software"), to use, reproduce, modify and redistribute the Apple | |||
Software, with or without modifications, in source and/or binary forms; | |||
provided that if you redistribute the Apple Software in its entirety and | |||
without modifications, you must retain this notice and the following | |||
text and disclaimers in all such redistributions of the Apple Software. | |||
Neither the name, trademarks, service marks or logos of Apple Inc. may | |||
be used to endorse or promote products derived from the Apple Software | |||
without specific prior written permission from Apple. Except as | |||
expressly stated in this notice, no other rights or licenses, express or | |||
implied, are granted by Apple herein, including but not limited to any | |||
patent rights that may be infringed by your derivative works or by other | |||
works in which the Apple Software may be incorporated. | |||
The Apple Software is provided by Apple on an "AS IS" basis. APPLE | |||
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION | |||
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS | |||
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND | |||
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. | |||
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL | |||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, | |||
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED | |||
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), | |||
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE | |||
POSSIBILITY OF SUCH DAMAGE. | |||
Copyright (C) 2014 Apple Inc. All Rights Reserved. | |||
*/ | |||
#include "AUMIDIEffectBase.h" | |||
// compatibility with older OS SDK releases | |||
typedef OSStatus | |||
(*TEMP_MusicDeviceMIDIEventProc)( void * inComponentStorage, | |||
UInt32 inStatus, | |||
UInt32 inData1, | |||
UInt32 inData2, | |||
UInt32 inOffsetSampleFrame); | |||
static OSStatus AUMIDIEffectBaseMIDIEvent(void * inComponentStorage, | |||
UInt32 inStatus, | |||
UInt32 inData1, | |||
UInt32 inData2, | |||
UInt32 inOffsetSampleFrame); | |||
AUMIDIEffectBase::AUMIDIEffectBase( AudioComponentInstance inInstance, | |||
bool inProcessesInPlace ) | |||
: AUEffectBase(inInstance, inProcessesInPlace), | |||
AUMIDIBase(this) | |||
{ | |||
} | |||
OSStatus AUMIDIEffectBase::GetPropertyInfo(AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
UInt32 & outDataSize, | |||
Boolean & outWritable) | |||
{ | |||
OSStatus result; | |||
result = AUEffectBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); | |||
if (result == kAudioUnitErr_InvalidProperty) | |||
result = AUMIDIBase::DelegateGetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); | |||
return result; | |||
} | |||
OSStatus AUMIDIEffectBase::GetProperty( AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
void * outData) | |||
{ | |||
OSStatus result; | |||
#if !CA_USE_AUDIO_PLUGIN_ONLY | |||
if (inID == kAudioUnitProperty_FastDispatch) { | |||
if (inElement == kMusicDeviceMIDIEventSelect) { | |||
*(TEMP_MusicDeviceMIDIEventProc *)outData = AUMIDIEffectBaseMIDIEvent; | |||
return noErr; | |||
} | |||
return kAudioUnitErr_InvalidElement; | |||
} | |||
#endif | |||
result = AUEffectBase::GetProperty (inID, inScope, inElement, outData); | |||
if (result == kAudioUnitErr_InvalidProperty) | |||
result = AUMIDIBase::DelegateGetProperty (inID, inScope, inElement, outData); | |||
return result; | |||
} | |||
OSStatus AUMIDIEffectBase::SetProperty( AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
const void * inData, | |||
UInt32 inDataSize) | |||
{ | |||
OSStatus result = AUEffectBase::SetProperty (inID, inScope, inElement, inData, inDataSize); | |||
if (result == kAudioUnitErr_InvalidProperty) | |||
result = AUMIDIBase::DelegateSetProperty (inID, inScope, inElement, inData, inDataSize); | |||
return result; | |||
} | |||
#if !TARGET_OS_IPHONE | |||
OSStatus AUMIDIEffectBase::ComponentEntryDispatch(ComponentParameters * params, | |||
AUMIDIEffectBase * This) | |||
{ | |||
if (This == NULL) return paramErr; | |||
OSStatus result; | |||
switch (params->what) { | |||
case kMusicDeviceMIDIEventSelect: | |||
case kMusicDeviceSysExSelect: | |||
result = AUMIDIBase::ComponentEntryDispatch (params, This); | |||
break; | |||
default: | |||
result = AUEffectBase::ComponentEntryDispatch(params, This); | |||
break; | |||
} | |||
return result; | |||
} | |||
#endif | |||
// fast dispatch | |||
static OSStatus AUMIDIEffectBaseMIDIEvent(void * inComponentStorage, | |||
UInt32 inStatus, | |||
UInt32 inData1, | |||
UInt32 inData2, | |||
UInt32 inOffsetSampleFrame) | |||
{ | |||
OSStatus result = noErr; | |||
try { | |||
AUMIDIEffectBase *This = static_cast<AUMIDIEffectBase *>(inComponentStorage); | |||
if (This == NULL) return paramErr; | |||
result = This->AUMIDIBase::MIDIEvent(inStatus, inData1, inData2, inOffsetSampleFrame); | |||
} | |||
COMPONENT_CATCH | |||
return result; | |||
} |
@@ -1,104 +0,0 @@ | |||
/* | |||
File: AUMIDIEffectBase.h | |||
Abstract: Part of CoreAudio Utility Classes | |||
Version: 1.1 | |||
Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple | |||
Inc. ("Apple") in consideration of your agreement to the following | |||
terms, and your use, installation, modification or redistribution of | |||
this Apple software constitutes acceptance of these terms. If you do | |||
not agree with these terms, please do not use, install, modify or | |||
redistribute this Apple software. | |||
In consideration of your agreement to abide by the following terms, and | |||
subject to these terms, Apple grants you a personal, non-exclusive | |||
license, under Apple's copyrights in this original Apple software (the | |||
"Apple Software"), to use, reproduce, modify and redistribute the Apple | |||
Software, with or without modifications, in source and/or binary forms; | |||
provided that if you redistribute the Apple Software in its entirety and | |||
without modifications, you must retain this notice and the following | |||
text and disclaimers in all such redistributions of the Apple Software. | |||
Neither the name, trademarks, service marks or logos of Apple Inc. may | |||
be used to endorse or promote products derived from the Apple Software | |||
without specific prior written permission from Apple. Except as | |||
expressly stated in this notice, no other rights or licenses, express or | |||
implied, are granted by Apple herein, including but not limited to any | |||
patent rights that may be infringed by your derivative works or by other | |||
works in which the Apple Software may be incorporated. | |||
The Apple Software is provided by Apple on an "AS IS" basis. APPLE | |||
MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION | |||
THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS | |||
FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND | |||
OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. | |||
IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL | |||
OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF | |||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS | |||
INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, | |||
MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED | |||
AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), | |||
STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE | |||
POSSIBILITY OF SUCH DAMAGE. | |||
Copyright (C) 2014 Apple Inc. All Rights Reserved. | |||
*/ | |||
#ifndef __AUMIDIEffectBase_h__ | |||
#define __AUMIDIEffectBase_h__ | |||
#include "AUMIDIBase.h" | |||
#include "AUEffectBase.h" | |||
// ________________________________________________________________________ | |||
// AUMIDIEffectBase | |||
// | |||
/*! @class AUMIDIEffectBase */ | |||
class AUMIDIEffectBase : public AUEffectBase, public AUMIDIBase { | |||
public: | |||
/*! @ctor AUMIDIEffectBase */ | |||
AUMIDIEffectBase( AudioComponentInstance inInstance, | |||
bool inProcessesInPlace = false ); | |||
/*! @method MIDIEvent */ | |||
virtual OSStatus MIDIEvent(UInt32 inStatus, | |||
UInt32 inData1, | |||
UInt32 inData2, | |||
UInt32 inOffsetSampleFrame) | |||
{ | |||
return AUMIDIBase::MIDIEvent (inStatus, inData1, inData2, inOffsetSampleFrame); | |||
} | |||
/*! @method SysEx */ | |||
virtual OSStatus SysEx(const UInt8 * inData, | |||
UInt32 inLength) | |||
{ | |||
return AUMIDIBase::SysEx (inData, inLength); | |||
} | |||
/*! @method GetPropertyInfo */ | |||
virtual OSStatus GetPropertyInfo(AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
UInt32 & outDataSize, | |||
Boolean & outWritable); | |||
/*! @method GetProperty */ | |||
virtual OSStatus GetProperty( AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
void * outData); | |||
/*! @method SetProperty */ | |||
virtual OSStatus SetProperty( AudioUnitPropertyID inID, | |||
AudioUnitScope inScope, | |||
AudioUnitElement inElement, | |||
const void * inData, | |||
UInt32 inDataSize); | |||
#if !TARGET_OS_IPHONE | |||
// component dispatcher | |||
/*! @method ComponentEntryDispatch */ | |||
static OSStatus ComponentEntryDispatch( ComponentParameters * params, | |||
AUMIDIEffectBase * This); | |||
#endif | |||
}; | |||
#endif // __AUMIDIEffectBase_h__ |
@@ -44,6 +44,8 @@ | |||
Copyright (C) 2014 Apple Inc. All Rights Reserved. | |||
*/ | |||
#include "../../../juce_core/native/juce_mac_ClangBugWorkaround.h" | |||
#include "AUOutputElement.h" | |||
#include "AUBase.h" | |||
@@ -44,6 +44,8 @@ | |||
Copyright (C) 2014 Apple Inc. All Rights Reserved. | |||
*/ | |||
#include "../../../juce_core/native/juce_mac_ClangBugWorkaround.h" | |||
#include "AUScopeElement.h" | |||
#include "AUBase.h" | |||
@@ -44,6 +44,8 @@ | |||
Copyright (C) 2014 Apple Inc. All Rights Reserved. | |||
*/ | |||
#include "../../../juce_core/native/juce_mac_ClangBugWorkaround.h" | |||
#include "CAAUParameter.h" | |||
CAAUParameter::CAAUParameter() | |||
@@ -48,6 +48,8 @@ | |||
#define __CADebugMacros_h__ | |||
#pragma clang diagnostic ignored "-Wdeprecated-declarations" | |||
#include "../../../juce_core/native/juce_mac_ClangBugWorkaround.h" | |||
//============================================================================= | |||
// Includes | |||
@@ -44,6 +44,8 @@ | |||
Copyright (C) 2014 Apple Inc. All Rights Reserved. | |||
*/ | |||
#include "../../../juce_core/native/juce_mac_ClangBugWorkaround.h" | |||
#include "CarbonEventHandler.h" | |||
static pascal OSStatus TheEventHandler(EventHandlerCallRef inHandlerRef, EventRef inEvent, void *inUserData) | |||
@@ -22,10 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../../juce_core/system/juce_TargetPlatform.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#if JucePlugin_Build_RTAS | |||
@@ -22,10 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../../juce_core/system/juce_TargetPlatform.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#if JucePlugin_Build_RTAS | |||
@@ -22,10 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../../juce_core/system/juce_TargetPlatform.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#if JucePlugin_Build_RTAS | |||
@@ -64,6 +64,8 @@ | |||
#define PLUGIN_SDK_DIRECTMIDI 1 | |||
#define DIGI_PASCAL | |||
#define Point CarbonDummyPointName | |||
#define Component CarbonDummyCompName | |||
#include <MacAlwaysInclude.h> | |||
#endif | |||
@@ -22,10 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../../juce_core/system/juce_TargetPlatform.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#if JucePlugin_Build_RTAS | |||
@@ -22,10 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../../juce_core/system/juce_TargetPlatform.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
// (these functions are in their own file because of problems including windows.h | |||
@@ -22,10 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../../juce_core/system/juce_TargetPlatform.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#if JucePlugin_Build_RTAS | |||
@@ -89,6 +86,9 @@ | |||
#include <FicProcessTokens.h> | |||
#include <ExternalVersionDefines.h> | |||
#undef Point | |||
#undef Component | |||
//============================================================================== | |||
#ifdef _MSC_VER | |||
#pragma pack (push, 8) | |||
@@ -421,7 +421,7 @@ public: | |||
JuceCustomUIView* getView() const | |||
{ | |||
return dynamic_cast <JuceCustomUIView*> (fOurPlugInView); | |||
return dynamic_cast<JuceCustomUIView*> (fOurPlugInView); | |||
} | |||
void GetViewRect (Rect* size) override | |||
@@ -478,11 +478,11 @@ public: | |||
if (MIDILogIn() == noErr) | |||
{ | |||
#if JucePlugin_WantsMidiInput | |||
if (CEffectType* const type = dynamic_cast <CEffectType*> (this->GetProcessType())) | |||
if (CEffectType* const type = dynamic_cast<CEffectType*> (this->GetProcessType())) | |||
{ | |||
char nodeName [64]; | |||
type->GetProcessTypeName (63, nodeName); | |||
p2cstrcpy (nodeName, reinterpret_cast <unsigned char*> (nodeName)); | |||
p2cstrcpy (nodeName, reinterpret_cast<unsigned char*> (nodeName)); | |||
midiBufferNode = new CEffectMIDIOtherBufferedNode (&mMIDIWorld, | |||
8192, | |||
@@ -498,8 +498,8 @@ public: | |||
midiTransport = new CEffectMIDITransport (&mMIDIWorld); | |||
midiEvents.ensureSize (2048); | |||
channels.calloc (jmax (juceFilter->getNumInputChannels(), | |||
juceFilter->getNumOutputChannels())); | |||
channels.calloc (jmax (juceFilter->getTotalNumInputChannels(), | |||
juceFilter->getTotalNumOutputChannels())); | |||
juceFilter->setPlayHead (this); | |||
juceFilter->addListener (this); | |||
@@ -539,14 +539,14 @@ public: | |||
#if JUCE_DEBUG || JUCE_LOG_ASSERTIONS | |||
const int numMidiEventsComingIn = midiEvents.getNumEvents(); | |||
(void) numMidiEventsComingIn; | |||
ignoreUnused (numMidiEventsComingIn); | |||
#endif | |||
{ | |||
const ScopedLock sl (juceFilter->getCallbackLock()); | |||
const int numIn = juceFilter->getNumInputChannels(); | |||
const int numOut = juceFilter->getNumOutputChannels(); | |||
const int numIn = juceFilter->getTotalNumInputChannels(); | |||
const int numOut = juceFilter->getTotalNumOutputChannels(); | |||
const int totalChans = jmax (numIn, numOut); | |||
if (juceFilter->isSuspended()) | |||
@@ -163,10 +163,10 @@ public: | |||
{ | |||
DialogWindow::LaunchOptions o; | |||
o.content.setOwned (new AudioDeviceSelectorComponent (deviceManager, | |||
processor->getNumInputChannels(), | |||
processor->getNumInputChannels(), | |||
processor->getNumOutputChannels(), | |||
processor->getNumOutputChannels(), | |||
processor->getTotalNumInputChannels(), | |||
processor->getTotalNumInputChannels(), | |||
processor->getTotalNumOutputChannels(), | |||
processor->getTotalNumOutputChannels(), | |||
true, false, | |||
true, false)); | |||
o.content->setSize (500, 450); | |||
@@ -196,8 +196,8 @@ public: | |||
if (settings != nullptr) | |||
savedState = settings->getXmlValue ("audioSetup"); | |||
deviceManager.initialise (processor->getNumInputChannels(), | |||
processor->getNumOutputChannels(), | |||
deviceManager.initialise (processor->getTotalNumInputChannels(), | |||
processor->getTotalNumOutputChannels(), | |||
savedState, | |||
true); | |||
} | |||
@@ -22,10 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../../juce_core/system/juce_TargetPlatform.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#if JucePlugin_Build_VST | |||
@@ -34,27 +31,13 @@ | |||
#pragma warning (disable : 4996 4100) | |||
#endif | |||
#ifdef _WIN32 | |||
#undef _WIN32_WINNT | |||
#define _WIN32_WINNT 0x500 | |||
#undef STRICT | |||
#define STRICT 1 | |||
#include <windows.h> | |||
#elif defined (LINUX) | |||
#include <X11/Xlib.h> | |||
#include <X11/Xutil.h> | |||
#include <X11/Xatom.h> | |||
#undef KeyPress | |||
#else | |||
#include <Carbon/Carbon.h> | |||
#endif | |||
#include "../utility/juce_IncludeSystemHeaders.h" | |||
#ifdef PRAGMA_ALIGN_SUPPORTED | |||
#undef PRAGMA_ALIGN_SUPPORTED | |||
#define PRAGMA_ALIGN_SUPPORTED 1 | |||
#endif | |||
//============================================================================== | |||
#ifndef _MSC_VER | |||
#define __cdecl | |||
#endif | |||
@@ -117,6 +100,7 @@ | |||
#include "../utility/juce_IncludeModuleHeaders.h" | |||
#include "../utility/juce_FakeMouseMoveGenerator.h" | |||
#include "../utility/juce_WindowsHooks.h" | |||
#include "../utility/juce_PluginBusUtilities.h" | |||
#ifdef _MSC_VER | |||
#pragma pack (pop) | |||
@@ -278,11 +262,8 @@ public: | |||
JuceVSTWrapper (audioMasterCallback audioMasterCB, AudioProcessor* const af) | |||
: AudioEffectX (audioMasterCB, af->getNumPrograms(), af->getNumParameters()), | |||
filter (af), | |||
busUtils (*filter, false), | |||
chunkMemoryTime (0), | |||
speakerIn (kSpeakerArrEmpty), | |||
speakerOut (kSpeakerArrEmpty), | |||
numInChans (JucePlugin_MaxNumInputChannels), | |||
numOutChans (JucePlugin_MaxNumOutputChannels), | |||
isProcessing (false), | |||
isBypassed (false), | |||
hasShutdown (false), | |||
@@ -296,7 +277,16 @@ public: | |||
#endif | |||
hostWindow (0) | |||
{ | |||
filter->setPlayConfigDetails (numInChans, numOutChans, 0, 0); | |||
busUtils.findAllCompatibleLayouts(); | |||
// VST-2 does not support disabling buses: so always enable all of them | |||
if (busUtils.hasDynamicInBuses() || busUtils.hasDynamicOutBuses()) | |||
busUtils.enableAllBuses(); | |||
const int totalNumInChannels = busUtils.findTotalNumChannels(true); | |||
const int totalNumOutChannels = busUtils.findTotalNumChannels(false); | |||
filter->setRateAndBufferSizeDetails (0, 0); | |||
filter->setPlayHead (this); | |||
filter->addListener (this); | |||
@@ -305,8 +295,8 @@ public: | |||
setUniqueID ((int) (JucePlugin_VSTUniqueID)); | |||
setNumInputs (numInChans); | |||
setNumOutputs (numOutChans); | |||
setNumInputs (totalNumInChannels); | |||
setNumOutputs (totalNumOutChannels); | |||
canProcessReplacing (true); | |||
canDoubleReplacing (filter->supportsDoublePrecisionProcessing()); | |||
@@ -443,7 +433,7 @@ public: | |||
VstIntPtr vendorSpecific (VstInt32 lArg, VstIntPtr lArg2, void* ptrArg, float floatArg) override | |||
{ | |||
(void) lArg; (void) lArg2; (void) ptrArg; (void) floatArg; | |||
ignoreUnused (lArg, lArg2, ptrArg, floatArg); | |||
#if JucePlugin_Build_VST3 && JUCE_VST3_CAN_REPLACE_VST2 | |||
if ((lArg == 'stCA' || lArg == 'stCa') && lArg2 == 'FUID' && ptrArg != nullptr) | |||
@@ -456,47 +446,6 @@ public: | |||
return 0; | |||
} | |||
bool getInputProperties (VstInt32 index, VstPinProperties* properties) override | |||
{ | |||
if (filter == nullptr || index >= JucePlugin_MaxNumInputChannels) | |||
return false; | |||
setPinProperties (*properties, filter->getInputChannelName ((int) index), | |||
speakerIn, filter->isInputChannelStereoPair ((int) index)); | |||
return true; | |||
} | |||
bool getOutputProperties (VstInt32 index, VstPinProperties* properties) override | |||
{ | |||
if (filter == nullptr || index >= JucePlugin_MaxNumOutputChannels) | |||
return false; | |||
setPinProperties (*properties, filter->getOutputChannelName ((int) index), | |||
speakerOut, filter->isOutputChannelStereoPair ((int) index)); | |||
return true; | |||
} | |||
static void setPinProperties (VstPinProperties& properties, const String& name, | |||
VstSpeakerArrangementType type, const bool isPair) | |||
{ | |||
name.copyToUTF8 (properties.label, (size_t) (kVstMaxLabelLen - 1)); | |||
name.copyToUTF8 (properties.shortLabel, (size_t) (kVstMaxShortLabelLen - 1)); | |||
if (type != kSpeakerArrEmpty) | |||
{ | |||
properties.flags = kVstPinUseSpeaker; | |||
properties.arrangementType = type; | |||
} | |||
else | |||
{ | |||
properties.flags = kVstPinIsActive; | |||
properties.arrangementType = 0; | |||
if (isPair) | |||
properties.flags |= kVstPinIsStereo; | |||
} | |||
} | |||
bool setBypass (bool b) override | |||
{ | |||
isBypassed = b; | |||
@@ -518,31 +467,11 @@ public: | |||
VSTMidiEventList::addEventsToMidiBuffer (events, midiEvents); | |||
return 1; | |||
#else | |||
(void) events; | |||
ignoreUnused (events); | |||
return 0; | |||
#endif | |||
} | |||
void process (float** inputs, float** outputs, VstInt32 numSamples) | |||
{ | |||
VstTempBuffers<float>& tmpBuffers = floatTempBuffers; | |||
const int numIn = numInChans; | |||
const int numOut = numOutChans; | |||
tmpBuffers.processTempBuffer.setSize (numIn, numSamples, false, false, true); | |||
for (int i = numIn; --i >= 0;) | |||
tmpBuffers.processTempBuffer.copyFrom (i, 0, outputs[i], numSamples); | |||
processReplacing (inputs, outputs, numSamples); | |||
AudioSampleBuffer dest (outputs, numOut, numSamples); | |||
for (int i = jmin (numIn, numOut); --i >= 0;) | |||
dest.addFrom (i, 0, tmpBuffers.processTempBuffer, i, 0, numSamples); | |||
} | |||
template <typename FloatType> | |||
void internalProcessReplacing (FloatType** inputs, FloatType** outputs, | |||
VstInt32 numSamples, VstTempBuffers<FloatType>& tmpBuffers) | |||
@@ -560,12 +489,6 @@ public: | |||
resume(); | |||
filter->setNonRealtime (getCurrentProcessLevel() == 4 /* kVstProcessLevelOffline */); | |||
#if JUCE_WINDOWS | |||
if (GetThreadPriority (GetCurrentThread()) <= THREAD_PRIORITY_NORMAL | |||
&& GetThreadPriority (GetCurrentThread()) >= THREAD_PRIORITY_LOWEST) | |||
filter->setNonRealtime (true); | |||
#endif | |||
} | |||
#if JUCE_DEBUG && ! JucePlugin_ProducesMidiOutput | |||
@@ -575,10 +498,10 @@ public: | |||
jassert (activePlugins.contains (this)); | |||
{ | |||
const ScopedLock sl (filter->getCallbackLock()); | |||
const int numIn = filter->getTotalNumInputChannels(); | |||
const int numOut = filter->getTotalNumOutputChannels(); | |||
const int numIn = numInChans; | |||
const int numOut = numOutChans; | |||
const ScopedLock sl (filter->getCallbackLock()); | |||
if (filter->isSuspended()) | |||
{ | |||
@@ -716,6 +639,13 @@ public: | |||
if (filter != nullptr) | |||
{ | |||
isProcessing = true; | |||
const int numInChans = filter->busArrangement.getTotalNumInputChannels(); | |||
const int numOutChans = filter->busArrangement.getTotalNumOutputChannels(); | |||
setNumInputs (numInChans); | |||
setNumOutputs (numOutChans); | |||
floatTempBuffers.channels.calloc ((size_t) (numInChans + numOutChans)); | |||
doubleTempBuffers.channels.calloc ((size_t) (numInChans + numOutChans)); | |||
@@ -730,7 +660,7 @@ public: | |||
firstProcessCallback = true; | |||
filter->setNonRealtime (getCurrentProcessLevel() == 4 /* kVstProcessLevelOffline */); | |||
filter->setPlayConfigDetails (numInChans, numOutChans, rate, currentBlockSize); | |||
filter->setRateAndBufferSizeDetails (rate, currentBlockSize); | |||
deleteTempChannels(); | |||
@@ -967,94 +897,296 @@ public: | |||
return filter != nullptr && filter->isParameterAutomatable ((int) index); | |||
} | |||
struct ChannelConfigComparator | |||
bool setSpeakerArrangement (VstSpeakerArrangement* pluginInput, | |||
VstSpeakerArrangement* pluginOutput) override | |||
{ | |||
static int compareElements (const short* const first, const short* const second) noexcept | |||
if ((busUtils.getBusCount (true) == 0 || busUtils.busIgnoresLayout(true, 0)) | |||
&& (busUtils.getBusCount (false) == 0 || busUtils.busIgnoresLayout(false, 0))) | |||
return false; | |||
if (pluginInput != nullptr && filter->busArrangement.inputBuses.size() == 0) | |||
return false; | |||
if (pluginOutput != nullptr && filter->busArrangement.outputBuses.size() == 0) | |||
return false; | |||
PluginBusUtilities::ScopedBusRestorer busRestorer (busUtils); | |||
if (pluginInput != nullptr) | |||
{ | |||
if (first[0] < second[0]) return -1; | |||
if (first[0] > second[0]) return 1; | |||
if (first[1] < second[1]) return -1; | |||
if (first[1] > second[1]) return 1; | |||
return 0; | |||
AudioChannelSet newType = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginInput); | |||
if (busUtils.getChannelSet (true, 0) != newType) | |||
if (! filter->setPreferredBusArrangement (true, 0, newType)) | |||
return false; | |||
} | |||
}; | |||
bool setSpeakerArrangement (VstSpeakerArrangement* pluginInput, | |||
VstSpeakerArrangement* pluginOutput) override | |||
if (pluginOutput != nullptr) | |||
{ | |||
AudioChannelSet newType = SpeakerMappings::vstArrangementTypeToChannelSet (*pluginOutput); | |||
if (busUtils.getChannelSet (false, 0) != newType) | |||
if (! filter->setPreferredBusArrangement (false, 0, newType)) | |||
return false; | |||
} | |||
busRestorer.release(); | |||
const int totalNumInChannels = busUtils.findTotalNumChannels(true); | |||
const int totalNumOutChannels = busUtils.findTotalNumChannels(false); | |||
filter->setRateAndBufferSizeDetails(0, 0); | |||
setNumInputs (totalNumInChannels); | |||
setNumOutputs(totalNumOutChannels); | |||
ioChanged(); | |||
return true; | |||
} | |||
bool getSpeakerArrangement (VstSpeakerArrangement** pluginInput, VstSpeakerArrangement** pluginOutput) override | |||
{ | |||
short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; | |||
*pluginInput = 0; | |||
*pluginOutput = 0; | |||
Array<short*> channelConfigsSorted; | |||
ChannelConfigComparator comp; | |||
if ((busUtils.getBusCount (true) == 0 || busUtils.busIgnoresLayout(true, 0)) | |||
&& (busUtils.getBusCount (false) == 0 || busUtils.busIgnoresLayout(false, 0))) | |||
return false; | |||
for (int i = 0; i < numElementsInArray (channelConfigs); ++i) | |||
channelConfigsSorted.addSorted (comp, channelConfigs[i]); | |||
if (! AudioEffectX::allocateArrangement (pluginInput, busUtils.getNumChannels (true, 0))) | |||
return false; | |||
for (int i = channelConfigsSorted.size(); --i >= 0;) | |||
if (! AudioEffectX::allocateArrangement (pluginOutput, busUtils.getNumChannels (false, 0))) | |||
{ | |||
const short* const config = channelConfigsSorted.getUnchecked(i); | |||
bool inCountMatches = (config[0] == pluginInput->numChannels); | |||
bool outCountMatches = (config[1] == pluginOutput->numChannels); | |||
AudioEffectX::deallocateArrangement (pluginInput); | |||
*pluginInput = 0; | |||
return false; | |||
} | |||
SpeakerMappings::channelSetToVstArrangement (busUtils.getChannelSet (true, 0), **pluginInput); | |||
SpeakerMappings::channelSetToVstArrangement (busUtils.getChannelSet (false, 0), **pluginOutput); | |||
if (inCountMatches && outCountMatches) | |||
return true; | |||
} | |||
bool getInputProperties (VstInt32 index, VstPinProperties* properties) override | |||
{ | |||
return filter != nullptr | |||
&& getPinProperties (*properties, true, (int) index); | |||
} | |||
bool getOutputProperties (VstInt32 index, VstPinProperties* properties) override | |||
{ | |||
return filter != nullptr | |||
&& getPinProperties (*properties, false, (int) index); | |||
} | |||
bool getPinProperties (VstPinProperties& properties, bool direction, int index) const | |||
{ | |||
// index refers to the absolute index when combining all channels of every bus | |||
const int n = busUtils.getBusCount(direction); | |||
int busIdx; | |||
for (busIdx = 0; busIdx < n; ++busIdx) | |||
{ | |||
const int numChans = busUtils.getNumChannels(direction, busIdx); | |||
if (index < numChans) | |||
break; | |||
index -= numChans; | |||
} | |||
if (busIdx >= n) | |||
return false; | |||
const AudioProcessor::AudioProcessorBus& busInfo = busUtils.getFilterBus (direction).getReference (busIdx); | |||
busInfo.name.copyToUTF8 (properties.label, (size_t) (kVstMaxLabelLen - 1)); | |||
busInfo.name.copyToUTF8 (properties.shortLabel, (size_t) (kVstMaxShortLabelLen - 1)); | |||
VstInt32 type = SpeakerMappings::channelSetToVstArrangementType (busInfo.channels); | |||
if (type != kSpeakerArrEmpty) | |||
{ | |||
properties.flags = kVstPinUseSpeaker | kVstPinIsActive; | |||
properties.arrangementType = type; | |||
} | |||
else | |||
{ | |||
properties.flags = 0; | |||
properties.arrangementType = 0; | |||
} | |||
if (busInfo.channels.size() == 2) | |||
properties.flags |= kVstPinIsStereo; | |||
return true; | |||
} | |||
//============================================================================== | |||
struct SpeakerMappings : private AudioChannelSet // (inheritance only to give easier access to items in the namespace) | |||
{ | |||
struct Mapping | |||
{ | |||
VstInt32 vst2; | |||
ChannelType channels[13]; | |||
bool matches (const Array<ChannelType>& chans) const noexcept | |||
{ | |||
speakerIn = (VstSpeakerArrangementType) pluginInput->type; | |||
speakerOut = (VstSpeakerArrangementType) pluginOutput->type; | |||
numInChans = pluginInput->numChannels; | |||
numOutChans = pluginOutput->numChannels; | |||
const int n = sizeof (channels) / sizeof (ChannelType); | |||
filter->setPlayConfigDetails (numInChans, numOutChans, | |||
filter->getSampleRate(), | |||
filter->getBlockSize()); | |||
for (int i = 0; i < n; ++i) | |||
{ | |||
if (channels[i] == unknown) return (i == chans.size()); | |||
if (i == chans.size()) return (channels[i] == unknown); | |||
if (channels[i] != chans.getUnchecked(i)) | |||
return false; | |||
} | |||
filter->setSpeakerArrangement (getSpeakerArrangementString (speakerIn), | |||
getSpeakerArrangementString (speakerOut)); | |||
return true; | |||
} | |||
}; | |||
static AudioChannelSet vstArrangementTypeToChannelSet (const VstSpeakerArrangement& arr) | |||
{ | |||
for (const Mapping* m = getMappings(); m->vst2 != kSpeakerArrEmpty; ++m) | |||
{ | |||
if (m->vst2 == arr.type) | |||
{ | |||
AudioChannelSet s; | |||
for (int i = 0; m->channels[i] != 0; ++i) | |||
s.addChannel (m->channels[i]); | |||
return s; | |||
} | |||
} | |||
return AudioChannelSet::discreteChannels (arr.numChannels); | |||
} | |||
filter->setSpeakerArrangement (String::empty, String::empty); | |||
return false; | |||
} | |||
static VstInt32 channelSetToVstArrangementType (AudioChannelSet channels) | |||
{ | |||
Array<AudioChannelSet::ChannelType> chans (channels.getChannelTypes()); | |||
for (const Mapping* m = getMappings(); m->vst2 != kSpeakerArrEmpty; ++m) | |||
if (m->matches (chans)) | |||
return m->vst2; | |||
static const char* getSpeakerArrangementString (VstSpeakerArrangementType type) noexcept | |||
{ | |||
switch (type) | |||
{ | |||
case kSpeakerArrMono: return "M"; | |||
case kSpeakerArrStereo: return "L R"; | |||
case kSpeakerArrStereoSurround: return "Ls Rs"; | |||
case kSpeakerArrStereoCenter: return "Lc Rc"; | |||
case kSpeakerArrStereoSide: return "Sl Sr"; | |||
case kSpeakerArrStereoCLfe: return "C Lfe"; | |||
case kSpeakerArr30Cine: return "L R C"; | |||
case kSpeakerArr30Music: return "L R S"; | |||
case kSpeakerArr31Cine: return "L R C Lfe"; | |||
case kSpeakerArr31Music: return "L R Lfe S"; | |||
case kSpeakerArr40Cine: return "L R C S"; | |||
case kSpeakerArr40Music: return "L R Ls Rs"; | |||
case kSpeakerArr41Cine: return "L R C Lfe S"; | |||
case kSpeakerArr41Music: return "L R Lfe Ls Rs"; | |||
case kSpeakerArr50: return "L R C Ls Rs" ; | |||
case kSpeakerArr51: return "L R C Lfe Ls Rs"; | |||
case kSpeakerArr60Cine: return "L R C Ls Rs Cs"; | |||
case kSpeakerArr60Music: return "L R Ls Rs Sl Sr "; | |||
case kSpeakerArr61Cine: return "L R C Lfe Ls Rs Cs"; | |||
case kSpeakerArr61Music: return "L R Lfe Ls Rs Sl Sr"; | |||
case kSpeakerArr70Cine: return "L R C Ls Rs Lc Rc "; | |||
case kSpeakerArr70Music: return "L R C Ls Rs Sl Sr"; | |||
case kSpeakerArr71Cine: return "L R C Lfe Ls Rs Lc Rc"; | |||
case kSpeakerArr71Music: return "L R C Lfe Ls Rs Sl Sr"; | |||
case kSpeakerArr80Cine: return "L R C Ls Rs Lc Rc Cs"; | |||
case kSpeakerArr80Music: return "L R C Ls Rs Cs Sl Sr"; | |||
case kSpeakerArr81Cine: return "L R C Lfe Ls Rs Lc Rc Cs"; | |||
case kSpeakerArr81Music: return "L R C Lfe Ls Rs Cs Sl Sr" ; | |||
case kSpeakerArr102: return "L R C Lfe Ls Rs Tfl Tfc Tfr Trl Trr Lfe2"; | |||
default: break; | |||
return kSpeakerArrEmpty; | |||
} | |||
return nullptr; | |||
} | |||
static void channelSetToVstArrangement (const AudioChannelSet& channels, VstSpeakerArrangement& result) | |||
{ | |||
result.type = channelSetToVstArrangementType (channels); | |||
result.numChannels = channels.size(); | |||
for (int i = 0; i < result.numChannels; ++i) | |||
{ | |||
VstSpeakerProperties& speaker = result.speakers[i]; | |||
zeromem (&speaker, sizeof (VstSpeakerProperties)); | |||
speaker.type = getSpeakerType (channels.getTypeOfChannel (i)); | |||
} | |||
} | |||
static const Mapping* getMappings() noexcept | |||
{ | |||
static const Mapping mappings[] = | |||
{ | |||
{ kSpeakerArrMono, { centre, unknown } }, | |||
{ kSpeakerArrStereo, { left, right, unknown } }, | |||
{ kSpeakerArrStereoSurround, { surroundLeft, surroundRight, unknown } }, | |||
{ kSpeakerArrStereoCenter, { centreLeft, centreRight, unknown } }, | |||
{ kSpeakerArrStereoSide, { sideLeft, sideRight, unknown } }, | |||
{ kSpeakerArrStereoCLfe, { centre, subbass, unknown } }, | |||
{ kSpeakerArr30Cine, { left, right, centre, unknown } }, | |||
{ kSpeakerArr30Music, { left, right, surround, unknown } }, | |||
{ kSpeakerArr31Cine, { left, right, centre, subbass, unknown } }, | |||
{ kSpeakerArr31Music, { left, right, subbass, surround, unknown } }, | |||
{ kSpeakerArr40Cine, { left, right, centre, surround, unknown } }, | |||
{ kSpeakerArr40Music, { left, right, surroundLeft, surroundRight, unknown } }, | |||
{ kSpeakerArr41Cine, { left, right, centre, subbass, surround, unknown } }, | |||
{ kSpeakerArr41Music, { left, right, subbass, surroundLeft, surroundRight, unknown } }, | |||
{ kSpeakerArr50, { left, right, centre, surroundLeft, surroundRight, unknown } }, | |||
{ kSpeakerArr51, { left, right, centre, subbass, surroundLeft, surroundRight, unknown } }, | |||
{ kSpeakerArr60Cine, { left, right, centre, surroundLeft, surroundRight, surround, unknown } }, | |||
{ kSpeakerArr60Music, { left, right, surroundLeft, surroundRight, sideLeft, sideRight, unknown } }, | |||
{ kSpeakerArr61Cine, { left, right, centre, subbass, surroundLeft, surroundRight, surround, unknown } }, | |||
{ kSpeakerArr61Music, { left, right, subbass, surroundLeft, surroundRight, sideLeft, sideRight, unknown } }, | |||
{ kSpeakerArr70Cine, { left, right, centre, surroundLeft, surroundRight, topFrontLeft, topFrontRight, unknown } }, | |||
{ kSpeakerArr70Music, { left, right, centre, surroundLeft, surroundRight, sideLeft, sideRight, unknown } }, | |||
{ kSpeakerArr71Cine, { left, right, centre, subbass, surroundLeft, surroundRight, topFrontLeft, topFrontRight, unknown } }, | |||
{ kSpeakerArr71Music, { left, right, centre, subbass, surroundLeft, surroundRight, sideLeft, sideRight, unknown } }, | |||
{ kSpeakerArr80Cine, { left, right, centre, surroundLeft, surroundRight, topFrontLeft, topFrontRight, surround, unknown } }, | |||
{ kSpeakerArr80Music, { left, right, centre, surroundLeft, surroundRight, surround, sideLeft, sideRight, unknown } }, | |||
{ kSpeakerArr81Cine, { left, right, centre, subbass, surroundLeft, surroundRight, topFrontLeft, topFrontRight, surround, unknown } }, | |||
{ kSpeakerArr81Music, { left, right, centre, subbass, surroundLeft, surroundRight, surround, sideLeft, sideRight, unknown } }, | |||
{ kSpeakerArr102, { left, right, centre, subbass, surroundLeft, surroundRight, topFrontLeft, topFrontCentre, topFrontRight, topRearLeft, topRearRight, subbass2, unknown } }, | |||
{ kSpeakerArrEmpty, { unknown } } | |||
}; | |||
return mappings; | |||
} | |||
static inline VstInt32 getSpeakerType (AudioChannelSet::ChannelType type) noexcept | |||
{ | |||
switch (type) | |||
{ | |||
case AudioChannelSet::ChannelType::left: return kSpeakerL; | |||
case AudioChannelSet::ChannelType::right: return kSpeakerR; | |||
case AudioChannelSet::ChannelType::centre: return kSpeakerC; | |||
case AudioChannelSet::ChannelType::subbass: return kSpeakerLfe; | |||
case AudioChannelSet::ChannelType::surroundLeft: return kSpeakerLs; | |||
case AudioChannelSet::ChannelType::surroundRight: return kSpeakerRs; | |||
case AudioChannelSet::ChannelType::centreLeft: return kSpeakerLc; | |||
case AudioChannelSet::ChannelType::centreRight: return kSpeakerRc; | |||
case AudioChannelSet::ChannelType::surround: return kSpeakerS; | |||
case AudioChannelSet::ChannelType::sideLeft: return kSpeakerSl; | |||
case AudioChannelSet::ChannelType::sideRight: return kSpeakerSr; | |||
case AudioChannelSet::ChannelType::topMiddle: return kSpeakerTm; | |||
case AudioChannelSet::ChannelType::topFrontLeft: return kSpeakerTfl; | |||
case AudioChannelSet::ChannelType::topFrontCentre: return kSpeakerTfc; | |||
case AudioChannelSet::ChannelType::topFrontRight: return kSpeakerTfr; | |||
case AudioChannelSet::ChannelType::topRearLeft: return kSpeakerTrl; | |||
case AudioChannelSet::ChannelType::topRearCentre: return kSpeakerTrc; | |||
case AudioChannelSet::ChannelType::topRearRight: return kSpeakerTrr; | |||
case AudioChannelSet::ChannelType::subbass2: return kSpeakerLfe2; | |||
default: break; | |||
} | |||
return 0; | |||
} | |||
static inline AudioChannelSet::ChannelType getChannelType (VstInt32 type) noexcept | |||
{ | |||
switch (type) | |||
{ | |||
case kSpeakerL: return AudioChannelSet::ChannelType::left; | |||
case kSpeakerR: return AudioChannelSet::ChannelType::right; | |||
case kSpeakerC: return AudioChannelSet::ChannelType::centre; | |||
case kSpeakerLfe: return AudioChannelSet::ChannelType::subbass; | |||
case kSpeakerLs: return AudioChannelSet::ChannelType::surroundLeft; | |||
case kSpeakerRs: return AudioChannelSet::ChannelType::surroundRight; | |||
case kSpeakerLc: return AudioChannelSet::ChannelType::centreLeft; | |||
case kSpeakerRc: return AudioChannelSet::ChannelType::centreRight; | |||
case kSpeakerS: return AudioChannelSet::ChannelType::surround; | |||
case kSpeakerSl: return AudioChannelSet::ChannelType::sideLeft; | |||
case kSpeakerSr: return AudioChannelSet::ChannelType::sideRight; | |||
case kSpeakerTm: return AudioChannelSet::ChannelType::topMiddle; | |||
case kSpeakerTfl: return AudioChannelSet::ChannelType::topFrontLeft; | |||
case kSpeakerTfc: return AudioChannelSet::ChannelType::topFrontCentre; | |||
case kSpeakerTfr: return AudioChannelSet::ChannelType::topFrontRight; | |||
case kSpeakerTrl: return AudioChannelSet::ChannelType::topRearLeft; | |||
case kSpeakerTrc: return AudioChannelSet::ChannelType::topRearCentre; | |||
case kSpeakerTrr: return AudioChannelSet::ChannelType::topRearRight; | |||
case kSpeakerLfe2: return AudioChannelSet::ChannelType::subbass2; | |||
default: break; | |||
} | |||
return AudioChannelSet::ChannelType::unknown; | |||
} | |||
}; | |||
//============================================================================== | |||
VstInt32 getChunk (void** data, bool onlyStoreCurrentProgramData) override | |||
@@ -1508,14 +1640,13 @@ public: | |||
//============================================================================== | |||
private: | |||
AudioProcessor* filter; | |||
PluginBusUtilities busUtils; | |||
juce::MemoryBlock chunkMemory; | |||
juce::uint32 chunkMemoryTime; | |||
ScopedPointer<EditorCompWrapper> editorComp; | |||
ERect editorSize; | |||
MidiBuffer midiEvents; | |||
VSTMidiEventList outgoingEvents; | |||
VstSpeakerArrangementType speakerIn, speakerOut; | |||
int numInChans, numOutChans; | |||
bool isProcessing, isBypassed, hasShutdown, isInSizeWindow, firstProcessCallback; | |||
bool shouldDeleteEditor, useNSView; | |||
VstTempBuffers<float> floatTempBuffers; | |||
@@ -1554,17 +1685,11 @@ private: | |||
{ | |||
MessageManager::getInstance()->setCurrentThreadAsMessageThread(); | |||
class MessageThreadCallback : public CallbackMessage | |||
struct MessageThreadCallback : public CallbackMessage | |||
{ | |||
public: | |||
MessageThreadCallback (bool& tr) : triggered (tr) {} | |||
void messageCallback() override { triggered = true; } | |||
void messageCallback() override | |||
{ | |||
triggered = true; | |||
} | |||
private: | |||
bool& triggered; | |||
}; | |||
@@ -1584,7 +1709,7 @@ private: | |||
if (filter != nullptr) | |||
{ | |||
int numChannels = filter->getNumInputChannels() + filter->getNumOutputChannels(); | |||
int numChannels = filter->getTotalNumInputChannels() + filter->getTotalNumOutputChannels(); | |||
tmpBuffers.tempChannels.insertMultiple (0, nullptr, numChannels); | |||
} | |||
} | |||
@@ -1595,6 +1720,7 @@ private: | |||
deleteTempChannels (doubleTempBuffers); | |||
} | |||
//============================================================================== | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (JuceVSTWrapper) | |||
}; | |||
@@ -22,10 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../../juce_core/system/juce_TargetPlatform.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#if JucePlugin_Build_VST || JucePlugin_Build_VST3 | |||
@@ -22,9 +22,7 @@ | |||
============================================================================== | |||
*/ | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../../juce_core/system/juce_TargetPlatform.h" | |||
//============================================================================== | |||
#if JucePlugin_Build_VST3 && (__APPLE_CPP__ || __APPLE_CC__ || _WIN32 || _WIN64) | |||
@@ -33,6 +31,7 @@ | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#include "../utility/juce_IncludeModuleHeaders.h" | |||
#include "../utility/juce_WindowsHooks.h" | |||
#include "../utility/juce_PluginBusUtilities.h" | |||
#include "../../juce_audio_processors/format_types/juce_VST3Common.h" | |||
#ifndef JUCE_VST3_CAN_REPLACE_VST2 | |||
@@ -640,7 +639,7 @@ private: | |||
private: | |||
//============================================================================== | |||
class ContentWrapperComponent : public juce::Component | |||
class ContentWrapperComponent : public juce::Component | |||
{ | |||
public: | |||
ContentWrapperComponent (JuceVST3Editor& editor, AudioProcessor& plugin) | |||
@@ -692,11 +691,12 @@ private: | |||
{ | |||
const int w = pluginEditor->getWidth(); | |||
const int h = pluginEditor->getHeight(); | |||
const PluginHostType host (getHostType()); | |||
#if JUCE_WINDOWS | |||
setSize (w, h); | |||
#else | |||
if (owner.macHostWindow != nullptr && ! getHostType().isWavelab()) | |||
if (owner.macHostWindow != nullptr && ! (host.isWavelab() || host.isReaper())) | |||
juce::setNativeHostWindowSizeVST (owner.macHostWindow, this, w, h, owner.isNSView); | |||
#endif | |||
@@ -705,7 +705,11 @@ private: | |||
ViewRect newSize (0, 0, w, h); | |||
owner.plugFrame->resizeView (&owner, &newSize); | |||
if (getHostType().isWavelab()) | |||
#if JUCE_MAC | |||
if (host.isWavelab() || host.isReaper()) | |||
#else | |||
if (host.isWavelab()) | |||
#endif | |||
setBounds (0, 0, w, h); | |||
} | |||
} | |||
@@ -761,13 +765,25 @@ class JuceVST3Component : public Vst::IComponent, | |||
public: | |||
JuceVST3Component (Vst::IHostApplication* h) | |||
: refCount (1), | |||
pluginInstance (createPluginFilterOfType (AudioProcessor::wrapperType_VST3)), | |||
host (h), | |||
audioInputs (Vst::kAudio, Vst::kInput), | |||
audioOutputs (Vst::kAudio, Vst::kOutput), | |||
eventInputs (Vst::kEvent, Vst::kInput), | |||
eventOutputs (Vst::kEvent, Vst::kOutput) | |||
isMidiInputBusEnabled (false), | |||
isMidiOutputBusEnabled (false), | |||
busUtils (*pluginInstance, false) | |||
{ | |||
pluginInstance = createPluginFilterOfType (AudioProcessor::wrapperType_VST3); | |||
#if JucePlugin_WantsMidiInput | |||
isMidiInputBusEnabled = true; | |||
#endif | |||
#if JucePlugin_ProducesMidiOutput | |||
isMidiOutputBusEnabled = true; | |||
#endif | |||
busUtils.findAllCompatibleLayouts(); | |||
copyEnabledBuses (lastEnabledBusStates.inputBuses, pluginInstance->busArrangement.inputBuses, Vst::kInput); | |||
copyEnabledBuses (lastEnabledBusStates.outputBuses, pluginInstance->busArrangement.outputBuses, Vst::kOutput); | |||
comPluginInstance = new JuceAudioProcessor (pluginInstance); | |||
zerostruct (processContext); | |||
@@ -787,11 +803,6 @@ public: | |||
if (pluginInstance != nullptr) | |||
if (pluginInstance->getPlayHead() == this) | |||
pluginInstance->setPlayHead (nullptr); | |||
audioInputs.removeAll(); | |||
audioOutputs.removeAll(); | |||
eventInputs.removeAll(); | |||
eventOutputs.removeAll(); | |||
} | |||
//============================================================================== | |||
@@ -829,26 +840,7 @@ public: | |||
if (host != hostContext) | |||
host.loadFrom (hostContext); | |||
#if JucePlugin_MaxNumInputChannels > 0 | |||
addAudioBusTo (audioInputs, TRANS("Audio Input"), | |||
getArrangementForNumChannels (JucePlugin_MaxNumInputChannels)); | |||
#endif | |||
#if JucePlugin_MaxNumOutputChannels > 0 | |||
addAudioBusTo (audioOutputs, TRANS("Audio Output"), | |||
getArrangementForNumChannels (JucePlugin_MaxNumOutputChannels)); | |||
#endif | |||
#if JucePlugin_WantsMidiInput | |||
addEventBusTo (eventInputs, TRANS("MIDI Input")); | |||
#endif | |||
#if JucePlugin_ProducesMidiOutput | |||
addEventBusTo (eventOutputs, TRANS("MIDI Output")); | |||
#endif | |||
processContext.sampleRate = processSetup.sampleRate; | |||
preparePlugin (processSetup.sampleRate, (int) processSetup.maxSamplesPerBlock); | |||
return kResultTrue; | |||
@@ -895,56 +887,13 @@ public: | |||
return kResultTrue; | |||
} | |||
//============================================================================== | |||
tresult PLUGIN_API getControllerClassId (TUID classID) override | |||
{ | |||
memcpy (classID, JuceVST3EditController::iid, sizeof (TUID)); | |||
return kResultTrue; | |||
} | |||
Steinberg::int32 PLUGIN_API getBusCount (Vst::MediaType type, Vst::BusDirection dir) override | |||
{ | |||
if (Vst::BusList* const busList = getBusListFor (type, dir)) | |||
return busList->total(); | |||
return 0; | |||
} | |||
tresult PLUGIN_API getBusInfo (Vst::MediaType type, Vst::BusDirection dir, | |||
Steinberg::int32 index, Vst::BusInfo& info) override | |||
{ | |||
if (Vst::BusList* const busList = getBusListFor (type, dir)) | |||
{ | |||
if (Vst::Bus* const bus = (Vst::Bus*) busList->at (index)) | |||
{ | |||
info.mediaType = type; | |||
info.direction = dir; | |||
if (bus->getInfo (info)) | |||
return kResultTrue; | |||
} | |||
} | |||
zerostruct (info); | |||
return kResultFalse; | |||
} | |||
tresult PLUGIN_API activateBus (Vst::MediaType type, Vst::BusDirection dir, | |||
Steinberg::int32 index, TBool state) override | |||
{ | |||
if (Vst::BusList* const busList = getBusListFor (type, dir)) | |||
{ | |||
if (Vst::Bus* const bus = (Vst::Bus*) busList->at (index)) | |||
{ | |||
bus->setActive (state); | |||
return kResultTrue; | |||
} | |||
} | |||
jassertfalse; | |||
return kResultFalse; | |||
} | |||
//============================================================================== | |||
tresult PLUGIN_API setActive (TBool state) override | |||
{ | |||
if (! state) | |||
@@ -1039,7 +988,7 @@ public: | |||
uint64 privateDataSize; | |||
std::memcpy (&privateDataSize, | |||
buffer + (size - jucePrivDataIdentifierSize - sizeof (uint64)), | |||
buffer + ((size_t) size - jucePrivDataIdentifierSize - sizeof (uint64)), | |||
sizeof (uint64)); | |||
privateDataSize = ByteOrder::swapIfBigEndian (privateDataSize); | |||
@@ -1062,7 +1011,7 @@ public: | |||
{ | |||
const int headerLen = static_cast<int> (htonl (*(juce::int32*) (data + 4))); | |||
const struct fxBank* bank = (const struct fxBank*) (data + (8 + headerLen)); | |||
const int version = static_cast<int> (htonl (bank->version)); (void) version; | |||
const int version = static_cast<int> (htonl (bank->version)); ignoreUnused (version); | |||
jassert ('VstW' == htonl (*(juce::int32*) data)); | |||
jassert (1 == htonl (*(juce::int32*) (data + 8))); // version should be 1 according to Steinberg's docs | |||
@@ -1331,9 +1280,7 @@ public: | |||
tresult PLUGIN_API setUnitProgramData (Steinberg::int32, Steinberg::int32, IBStream*) override { return kNotImplemented; } | |||
Vst::UnitID PLUGIN_API getSelectedUnit() override { return Vst::kRootUnitId; } | |||
tresult PLUGIN_API getUnitByBus (Vst::MediaType, Vst::BusDirection, | |||
Steinberg::int32, Steinberg::int32, | |||
Vst::UnitID& unitId) override | |||
tresult PLUGIN_API getUnitByBus (Vst::MediaType, Vst::BusDirection, Steinberg::int32, Steinberg::int32, Vst::UnitID& unitId) override | |||
{ | |||
zerostruct (unitId); | |||
return kNotImplemented; | |||
@@ -1382,70 +1329,178 @@ public: | |||
} | |||
//============================================================================== | |||
static tresult setBusArrangementFor (Vst::BusList& list, | |||
Vst::SpeakerArrangement* arrangement, | |||
Steinberg::int32 numBusses) | |||
Steinberg::int32 PLUGIN_API getBusCount (Vst::MediaType type, Vst::BusDirection dir) override | |||
{ | |||
if (arrangement != nullptr && numBusses == 1) //Should only be 1 bus per BusList | |||
if (type == Vst::kAudio) | |||
return (dir == Vst::kInput ? pluginInstance->busArrangement.inputBuses | |||
: pluginInstance->busArrangement.outputBuses).size(); | |||
if (type == Vst::kEvent) | |||
{ | |||
if (dir == Vst::kInput) | |||
return isMidiInputBusEnabled ? 1 : 0; | |||
if (dir == Vst::kOutput) | |||
return isMidiOutputBusEnabled ? 1 : 0; | |||
} | |||
return 0; | |||
} | |||
static const AudioProcessor::AudioProcessorBus* getAudioBus (AudioProcessor::AudioBusArrangement& busArrangement, | |||
Vst::BusDirection dir, Steinberg::int32 index) noexcept | |||
{ | |||
const Array<AudioProcessor::AudioProcessorBus>& buses = dir == Vst::kInput ? busArrangement.inputBuses | |||
: busArrangement.outputBuses; | |||
return isPositiveAndBelow (index, static_cast<Steinberg::int32> (buses.size())) ? &buses.getReference (index) : nullptr; | |||
} | |||
const AudioProcessor::AudioProcessorBus* getAudioBus (Vst::BusDirection dir, Steinberg::int32 index) const noexcept | |||
{ | |||
return getAudioBus (pluginInstance->busArrangement, dir, index); | |||
} | |||
tresult PLUGIN_API getBusInfo (Vst::MediaType type, Vst::BusDirection dir, | |||
Steinberg::int32 index, Vst::BusInfo& info) override | |||
{ | |||
if (type == Vst::kAudio) | |||
{ | |||
if (const AudioProcessor::AudioProcessorBus* bus = getAudioBus (lastEnabledBusStates, dir, index)) | |||
{ | |||
info.mediaType = Vst::kAudio; | |||
info.direction = dir; | |||
info.channelCount = bus->channels.size(); | |||
toString128 (info.name, bus->name); | |||
info.busType = index == 0 ? Vst::kMain : Vst::kAux; | |||
info.flags = busUtils.getSupportedBusLayouts (dir == Vst::kInput, index).isEnabledByDefault ? Vst::BusInfo::kDefaultActive : 0; | |||
return kResultTrue; | |||
} | |||
} | |||
if (type == Vst::kEvent) | |||
{ | |||
Steinberg::int32 counter = 0; | |||
info.flags = Vst::BusInfo::kDefaultActive; | |||
FOREACH_CAST (IPtr<Vst::Bus>, Vst::AudioBus, bus, list) | |||
#if JucePlugin_WantsMidiInput | |||
if (dir == Vst::kInput) | |||
{ | |||
if (counter < numBusses) | |||
bus->setArrangement (arrangement[counter]); | |||
info.mediaType = Vst::kEvent; | |||
info.direction = dir; | |||
info.channelCount = 0; | |||
toString128 (info.name, TRANS("MIDI Input")); | |||
info.busType = Vst::kMain; | |||
return kResultTrue; | |||
} | |||
#endif | |||
counter++; | |||
#if JucePlugin_ProducesMidiOutput | |||
if (dir == Vst::kOutput) | |||
{ | |||
info.mediaType = Vst::kEvent; | |||
info.direction = dir; | |||
info.channelCount = 0; | |||
toString128 (info.name, TRANS("MIDI Output")); | |||
info.busType = Vst::kMain; | |||
return kResultTrue; | |||
} | |||
ENDFOR | |||
#endif | |||
} | |||
zerostruct (info); | |||
return kResultFalse; | |||
} | |||
tresult PLUGIN_API activateBus (Vst::MediaType type, Vst::BusDirection dir, Steinberg::int32 index, TBool state) override | |||
{ | |||
if (type == Vst::kEvent) | |||
{ | |||
if (index != 0) | |||
return kResultFalse; | |||
if (dir == Vst::kInput) | |||
isMidiInputBusEnabled = (state != 0); | |||
else | |||
isMidiOutputBusEnabled = (state != 0); | |||
return kResultTrue; | |||
} | |||
if (type == Vst::kAudio) | |||
{ | |||
if (const AudioProcessor::AudioProcessorBus* bus = getAudioBus (dir, index)) | |||
{ | |||
if (state == (bus->channels.size() > 0)) | |||
return kResultTrue; | |||
AudioChannelSet newChannels; | |||
if (state) | |||
if (const AudioProcessor::AudioProcessorBus* lastBusState = getAudioBus (lastEnabledBusStates, dir, index)) | |||
newChannels = lastBusState->channels; | |||
if (pluginInstance->setPreferredBusArrangement (dir == Vst::kInput, index, newChannels)) | |||
return kResultTrue; | |||
} | |||
} | |||
return kResultFalse; | |||
} | |||
void copyEnabledBuses (Array<AudioProcessor::AudioProcessorBus>& copies, | |||
const Array<AudioProcessor::AudioProcessorBus>& source, | |||
Vst::BusDirection dir) | |||
{ | |||
for (int i = 0; i < source.size(); ++i) | |||
{ | |||
AudioProcessor::AudioProcessorBus bus = source.getReference (i); | |||
if (bus.channels.size() == 0 && i < copies.size()) | |||
bus = AudioProcessor::AudioProcessorBus (bus.name, copies.getReference (i).channels); | |||
if (bus.channels.size() == 0) | |||
bus = AudioProcessor::AudioProcessorBus (bus.name, busUtils.getDefaultLayoutForBus (dir == Vst::kInput, i)); | |||
copies.set (i, bus); | |||
} | |||
} | |||
tresult PLUGIN_API setBusArrangements (Vst::SpeakerArrangement* inputs, Steinberg::int32 numIns, | |||
Vst::SpeakerArrangement* outputs, Steinberg::int32 numOuts) override | |||
{ | |||
(void) inputs; (void) outputs; | |||
PluginBusUtilities::ScopedBusRestorer restorer (busUtils); | |||
#if JucePlugin_MaxNumInputChannels > 0 | |||
if (setBusArrangementFor (audioInputs, inputs, numIns) != kResultTrue) | |||
return kResultFalse; | |||
#else | |||
if (numIns != 0) | |||
return kResultFalse; | |||
#endif | |||
for (int i = 0; i < numIns; ++i) | |||
if (! pluginInstance->setPreferredBusArrangement (true, i, getChannelSetForSpeakerArrangement (inputs[i]))) | |||
return kInvalidArgument; | |||
#if JucePlugin_MaxNumOutputChannels > 0 | |||
if (setBusArrangementFor (audioOutputs, outputs, numOuts) != kResultTrue) | |||
return kResultFalse; | |||
#else | |||
if (numOuts != 0) | |||
return kResultFalse; | |||
#endif | |||
for (int i = 0; i < numOuts; ++i) | |||
if (! pluginInstance->setPreferredBusArrangement (false, i, getChannelSetForSpeakerArrangement (outputs[i]))) | |||
return kInvalidArgument; | |||
restorer.release(); | |||
preparePlugin (getPluginInstance().getSampleRate(), | |||
getPluginInstance().getBlockSize()); | |||
copyEnabledBuses (lastEnabledBusStates.inputBuses, pluginInstance->busArrangement.inputBuses, Vst::kInput); | |||
copyEnabledBuses (lastEnabledBusStates.outputBuses, pluginInstance->busArrangement.outputBuses, Vst::kOutput); | |||
return kResultTrue; | |||
} | |||
tresult PLUGIN_API getBusArrangement (Vst::BusDirection dir, Steinberg::int32 index, Vst::SpeakerArrangement& arr) override | |||
{ | |||
if (Vst::BusList* const busList = getBusListFor (Vst::kAudio, dir)) | |||
if (const AudioProcessor::AudioProcessorBus* bus = getAudioBus (lastEnabledBusStates, dir, index)) | |||
{ | |||
if (Vst::AudioBus* const audioBus = FCast<Vst::AudioBus> (busList->at (index))) | |||
{ | |||
arr = audioBus->getArrangement(); | |||
return kResultTrue; | |||
} | |||
arr = getSpeakerArrangement (bus->channels); | |||
return kResultTrue; | |||
} | |||
return kResultFalse; | |||
} | |||
//============================================================================== | |||
tresult PLUGIN_API canProcessSampleSize (Steinberg::int32 symbolicSampleSize) override | |||
{ | |||
return (symbolicSampleSize == Vst::kSample32 | |||
@@ -1574,8 +1629,8 @@ public: | |||
const int numInputChans = (data.inputs != nullptr && data.inputs[0].channelBuffers32 != nullptr) ? (int) data.inputs[0].numChannels : 0; | |||
const int numOutputChans = (data.outputs != nullptr && data.outputs[0].channelBuffers32 != nullptr) ? (int) data.outputs[0].numChannels : 0; | |||
if ((pluginInstance->getNumInputChannels() + pluginInstance->getNumOutputChannels()) > 0 | |||
&& (numInputChans + numOutputChans) == 0) | |||
if ((pluginInstance->getTotalNumInputChannels() + pluginInstance->getTotalNumOutputChannels()) > 0 | |||
&& (numInputChans + numOutputChans) == 0) | |||
return kResultFalse; | |||
} | |||
@@ -1623,11 +1678,14 @@ private: | |||
Vst::ProcessContext processContext; | |||
Vst::ProcessSetup processSetup; | |||
Vst::BusList audioInputs, audioOutputs, eventInputs, eventOutputs; | |||
MidiBuffer midiBuffer; | |||
Array<float*> channelListFloat; | |||
Array<double*> channelListDouble; | |||
AudioProcessor::AudioBusArrangement lastEnabledBusStates; | |||
bool isMidiInputBusEnabled, isMidiOutputBusEnabled; | |||
PluginBusUtilities busUtils; | |||
ScopedJuceInitialiser_GUI libraryInitialiser; | |||
int vstBypassParameterId; | |||
@@ -1638,11 +1696,49 @@ private: | |||
template <typename FloatType> | |||
void processAudio (Vst::ProcessData& data, Array<FloatType*>& channelList) | |||
{ | |||
const int totalChans = prepareChannelLists (channelList, data); | |||
int totalInputChans = 0; | |||
const int plugInInputChannels = pluginInstance->getTotalNumInputChannels(); | |||
const int plugInOutputChannels = pluginInstance->getTotalNumOutputChannels(); | |||
if (data.inputs != nullptr) | |||
{ | |||
for (int bus = 0; bus < data.numInputs && totalInputChans < plugInInputChannels; ++bus) | |||
{ | |||
if (FloatType** const busChannels = getPointerForAudioBus<FloatType> (data.inputs[bus])) | |||
{ | |||
const int numChans = jmin ((int) data.inputs[bus].numChannels, plugInInputChannels - totalInputChans); | |||
for (int i = 0; i < numChans; ++i) | |||
channelList.set (totalInputChans++, busChannels[i]); | |||
} | |||
} | |||
} | |||
int totalOutputChans = 0; | |||
if (data.outputs != nullptr) | |||
{ | |||
for (int bus = 0; bus < data.numOutputs && totalOutputChans < plugInOutputChannels; ++bus) | |||
{ | |||
if (FloatType** const busChannels = getPointerForAudioBus<FloatType> (data.outputs[bus])) | |||
{ | |||
const int numChans = jmin ((int) data.outputs[bus].numChannels, plugInOutputChannels - totalOutputChans); | |||
for (int i = 0; i < numChans; ++i) | |||
{ | |||
if (totalOutputChans >= totalInputChans) | |||
channelList.set (totalOutputChans, busChannels[i]); | |||
++totalOutputChans; | |||
} | |||
} | |||
} | |||
} | |||
AudioBuffer<FloatType> buffer; | |||
if (totalChans != 0) | |||
if (int totalChans = jmax (totalOutputChans, totalInputChans)) | |||
buffer.setDataToReferTo (channelList.getRawDataPointer(), totalChans, (int) data.numSamples); | |||
{ | |||
@@ -1659,57 +1755,47 @@ private: | |||
} | |||
else | |||
{ | |||
if (isBypassed()) | |||
pluginInstance->processBlockBypassed (buffer, midiBuffer); | |||
else | |||
pluginInstance->processBlock (buffer, midiBuffer); | |||
if (totalInputChans == pluginInstance->getTotalNumInputChannels() | |||
&& totalOutputChans == pluginInstance->getTotalNumOutputChannels()) | |||
{ | |||
if (isBypassed()) | |||
pluginInstance->processBlockBypassed (buffer, midiBuffer); | |||
else | |||
pluginInstance->processBlock (buffer, midiBuffer); | |||
} | |||
} | |||
} | |||
if (data.outputs != nullptr) | |||
{ | |||
for (int i = 0; i < data.numOutputs; ++i) | |||
FloatVectorOperations::copy (getPointerForAudioBus<FloatType> (data.outputs[0])[i], | |||
buffer.getReadPointer (i), (int) data.numSamples); | |||
} | |||
// clear extra busses.. | |||
if (data.outputs != nullptr) | |||
for (int i = 1; i < data.numOutputs; ++i) | |||
for (int f = 0; f < data.outputs[i].numChannels; ++f) | |||
FloatVectorOperations::clear (getPointerForAudioBus<FloatType> (data.outputs[i])[f], (int) data.numSamples); | |||
} | |||
int outChanIndex = 0; | |||
//============================================================================== | |||
void addBusTo (Vst::BusList& busList, Vst::Bus* newBus) | |||
{ | |||
busList.append (IPtr<Vst::Bus> (newBus, false)); | |||
} | |||
void addAudioBusTo (Vst::BusList& busList, const juce::String& name, Vst::SpeakerArrangement arr) | |||
{ | |||
addBusTo (busList, new Vst::AudioBus (toString (name), Vst::kMain, Vst::BusInfo::kDefaultActive, arr)); | |||
} | |||
void addEventBusTo (Vst::BusList& busList, const juce::String& name) | |||
{ | |||
addBusTo (busList, new Vst::EventBus (toString (name), Vst::kMain, Vst::BusInfo::kDefaultActive, 16)); | |||
} | |||
for (int bus = 0; bus < data.numOutputs; ++bus) | |||
{ | |||
if (FloatType** const busChannels = getPointerForAudioBus<FloatType> (data.outputs[bus])) | |||
{ | |||
const int numChans = (int) data.outputs[bus].numChannels; | |||
Vst::BusList* getBusListFor (Vst::MediaType type, Vst::BusDirection dir) | |||
{ | |||
if (type == Vst::kAudio) return dir == Vst::kInput ? &audioInputs : &audioOutputs; | |||
if (type == Vst::kEvent) return dir == Vst::kInput ? &eventInputs : &eventOutputs; | |||
for (int i = 0; i < numChans; ++i) | |||
{ | |||
if (outChanIndex < totalInputChans) | |||
FloatVectorOperations::copy (busChannels[i], buffer.getReadPointer (outChanIndex), (int) data.numSamples); | |||
else if (outChanIndex >= totalOutputChans) | |||
FloatVectorOperations::clear (busChannels[i], (int) data.numSamples); | |||
return nullptr; | |||
++outChanIndex; | |||
} | |||
} | |||
} | |||
} | |||
} | |||
//============================================================================== | |||
template <typename FloatType> | |||
void allocateChannelLists (Array<FloatType*>& channelList) | |||
{ | |||
channelList.clear(); | |||
channelList.insertMultiple (0, nullptr, jmax (JucePlugin_MaxNumInputChannels, JucePlugin_MaxNumOutputChannels) + 1); | |||
channelList.clearQuick(); | |||
channelList.insertMultiple (0, nullptr, 128); | |||
} | |||
template <typename FloatType> | |||
@@ -1718,60 +1804,18 @@ private: | |||
return AudioBusPointerHelper<FloatType>::impl (data); | |||
} | |||
template <typename FloatType> | |||
static int prepareChannelLists (Array<FloatType*>& channelList, Vst::ProcessData& data) noexcept | |||
{ | |||
int totalChans = 0; | |||
FloatType** inChannelBuffers = | |||
data.inputs != nullptr ? getPointerForAudioBus<FloatType> (data.inputs[0]) : nullptr; | |||
FloatType** outChannelBuffers = | |||
data.outputs != nullptr ? getPointerForAudioBus<FloatType> (data.outputs[0]) : nullptr; | |||
const int numInputChans = (data.inputs != nullptr && inChannelBuffers != nullptr) ? (int) data.inputs[0].numChannels : 0; | |||
const int numOutputChans = (data.outputs != nullptr && outChannelBuffers != nullptr) ? (int) data.outputs[0].numChannels : 0; | |||
for (int idx = 0; totalChans < numInputChans; ++idx) | |||
{ | |||
channelList.set (totalChans, inChannelBuffers[idx]); | |||
++totalChans; | |||
} | |||
// note that the loop bounds are correct: as VST-3 is always process replacing | |||
// we already know the output channel buffers of the first numInputChans channels | |||
for (int idx = 0; totalChans < numOutputChans; ++idx) | |||
{ | |||
channelList.set (totalChans, outChannelBuffers[idx]); | |||
++totalChans; | |||
} | |||
return totalChans; | |||
} | |||
//============================================================================== | |||
enum InternalParameters | |||
{ | |||
paramPreset = 'prst' | |||
}; | |||
static int getNumChannels (Vst::BusList& busList) | |||
{ | |||
Vst::BusInfo info; | |||
info.channelCount = 0; | |||
if (Vst::Bus* bus = busList.first()) | |||
bus->getInfo (info); | |||
return (int) info.channelCount; | |||
} | |||
void preparePlugin (double sampleRate, int bufferSize) | |||
{ | |||
getPluginInstance().setPlayConfigDetails (getNumChannels (audioInputs), | |||
getNumChannels (audioOutputs), | |||
sampleRate, bufferSize); | |||
AudioProcessor& p = getPluginInstance(); | |||
getPluginInstance().prepareToPlay (sampleRate, bufferSize); | |||
p.setRateAndBufferSizeDetails (sampleRate, bufferSize); | |||
p.prepareToPlay (sampleRate, bufferSize); | |||
} | |||
//============================================================================== | |||
@@ -1,7 +1,7 @@ | |||
{ | |||
"id": "juce_audio_plugin_client", | |||
"name": "JUCE audio plugin wrapper classes", | |||
"version": "4.0.2", | |||
"version": "4.1.0", | |||
"description": "Classes for building VST, VST3, RTAS, AAX and AU plugins.", | |||
"website": "http://www.juce.com/juce", | |||
"license": "GPL/Commercial", | |||
@@ -31,10 +31,8 @@ | |||
{ "file": "AU/CoreAudioUtilityClasses/AUCarbonViewControl.cpp", "warnings": "disabled", "target": "xcode", "AudioUnitOnly": "1" }, | |||
{ "file": "AU/CoreAudioUtilityClasses/AUCarbonViewDispatch.cpp", "warnings": "disabled", "target": "xcode", "AudioUnitOnly": "1" }, | |||
{ "file": "AU/CoreAudioUtilityClasses/AUDispatch.cpp", "warnings": "disabled", "target": "xcode", "AudioUnitOnly": "1" }, | |||
{ "file": "AU/CoreAudioUtilityClasses/AUEffectBase.cpp", "warnings": "disabled", "target": "xcode", "AudioUnitOnly": "1" }, | |||
{ "file": "AU/CoreAudioUtilityClasses/AUInputElement.cpp", "warnings": "disabled", "target": "xcode", "AudioUnitOnly": "1" }, | |||
{ "file": "AU/CoreAudioUtilityClasses/AUMIDIBase.cpp", "warnings": "disabled", "target": "xcode", "AudioUnitOnly": "1" }, | |||
{ "file": "AU/CoreAudioUtilityClasses/AUMIDIEffectBase.cpp", "warnings": "disabled", "target": "xcode", "AudioUnitOnly": "1" }, | |||
{ "file": "AU/CoreAudioUtilityClasses/AUOutputBase.cpp", "warnings": "disabled", "target": "xcode", "AudioUnitOnly": "1" }, | |||
{ "file": "AU/CoreAudioUtilityClasses/AUOutputElement.cpp", "warnings": "disabled", "target": "xcode", "AudioUnitOnly": "1" }, | |||
{ "file": "AU/CoreAudioUtilityClasses/AUScopeElement.cpp", "warnings": "disabled", "target": "xcode", "AudioUnitOnly": "1" }, | |||
@@ -51,18 +51,6 @@ | |||
#error "You need to define the JucePlugin_WantsMidiInput value!" | |||
#endif | |||
#ifndef JucePlugin_MaxNumInputChannels | |||
#error "You need to define the JucePlugin_MaxNumInputChannels value!" | |||
#endif | |||
#ifndef JucePlugin_MaxNumOutputChannels | |||
#error "You need to define the JucePlugin_MaxNumOutputChannels value!" | |||
#endif | |||
#ifndef JucePlugin_PreferredChannelConfigurations | |||
#error "You need to define the JucePlugin_PreferredChannelConfigurations value!" | |||
#endif | |||
#ifdef JucePlugin_Latency | |||
#error "JucePlugin_Latency is now deprecated - instead, call the AudioProcessor::setLatencySamples() method if your plugin has a non-zero delay" | |||
#endif | |||
@@ -50,16 +50,23 @@ | |||
#define JUCE_SUPPORT_CARBON 1 | |||
#endif | |||
#define Point CarbonDummyPointName | |||
#if JUCE_SUPPORT_CARBON | |||
#define Point CarbonDummyPointName | |||
#define Component CarbonDummyCompName | |||
#endif | |||
#ifdef __OBJC__ | |||
#include <Cocoa/Cocoa.h> | |||
#endif | |||
#if JUCE_SUPPORT_CARBON | |||
#include <Carbon/Carbon.h> | |||
#undef Point | |||
#undef Component | |||
#else | |||
#include <Cocoa/Cocoa.h> | |||
#endif | |||
#undef Point | |||
#include <objc/runtime.h> | |||
#include <objc/objc.h> | |||
#include <objc/message.h> | |||
@@ -0,0 +1,372 @@ | |||
/* | |||
============================================================================== | |||
This file is part of the JUCE library. | |||
Copyright (c) 2015 - ROLI Ltd. | |||
Permission is granted to use this software under the terms of either: | |||
a) the GPL v2 (or any later version) | |||
b) the Affero GPL v3 | |||
Details of these licenses can be found at: www.gnu.org/licenses | |||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
------------------------------------------------------------------------------ | |||
To release a closed-source product which uses JUCE, commercial licenses are | |||
available: visit www.juce.com for more information. | |||
============================================================================== | |||
*/ | |||
struct PluginBusUtilities | |||
{ | |||
//============================================================================== | |||
typedef Array<AudioProcessor::AudioProcessorBus> AudioBusArray; | |||
//============================================================================== | |||
PluginBusUtilities (AudioProcessor& plugin, bool markDiscreteLayoutsAsSupported) | |||
: processor (plugin), | |||
dynamicInBuses (false), | |||
dynamicOutBuses (false), | |||
addDiscreteLayouts (markDiscreteLayoutsAsSupported) | |||
{ | |||
} | |||
//============================================================================== | |||
// the first layout is the default layout | |||
struct SupportedBusLayouts | |||
{ | |||
enum | |||
{ | |||
pseudoChannelBitNum = 90 // use this bit index to check if plug-in really doesn't care about layouts | |||
}; | |||
//============================================================================== | |||
SupportedBusLayouts() : defaultLayoutIndex (0), busIgnoresLayout (true), canBeDisabled (false) {} | |||
AudioChannelSet& getDefault() noexcept { return supportedLayouts.getReference (defaultLayoutIndex); } | |||
const AudioChannelSet& getDefault() const noexcept { return supportedLayouts.getReference (defaultLayoutIndex); } | |||
void updateDefaultLayout (const AudioChannelSet& defaultLayout) noexcept { defaultLayoutIndex = jmax (supportedLayouts.indexOf (defaultLayout), 0); } | |||
bool busSupportsNumChannels (int numChannels) const noexcept { return getDefaultLayoutForChannelNum (numChannels) != nullptr; } | |||
//============================================================================== | |||
const AudioChannelSet* getDefaultLayoutForChannelNum (int channelNum) const noexcept | |||
{ | |||
const AudioChannelSet& dflt = getDefault(); | |||
if (dflt.size() == channelNum) | |||
return &dflt; | |||
for (int i = 0; i < supportedLayouts.size(); ++i) | |||
{ | |||
const AudioChannelSet& layout = supportedLayouts.getReference (i); | |||
if (layout.size() == channelNum) | |||
return &layout; | |||
} | |||
return nullptr; | |||
} | |||
int defaultLayoutIndex; | |||
bool busIgnoresLayout, canBeDisabled, isEnabledByDefault; | |||
SortedSet<AudioChannelSet> supportedLayouts; | |||
}; | |||
//============================================================================== | |||
AudioBusArray& getFilterBus (bool inputBus) noexcept { return inputBus ? processor.busArrangement.inputBuses : processor.busArrangement.outputBuses; } | |||
const AudioBusArray& getFilterBus (bool inputBus) const noexcept { return inputBus ? processor.busArrangement.inputBuses : processor.busArrangement.outputBuses; } | |||
int getBusCount (bool inputBus) const noexcept { return getFilterBus (inputBus).size(); } | |||
AudioChannelSet getChannelSet (bool inputBus, int bus) noexcept { return getFilterBus (inputBus).getReference (bus).channels; } | |||
int getNumChannels (bool inp, int bus) const noexcept { return isPositiveAndBelow (bus, getBusCount (inp)) ? getFilterBus (inp).getReference (bus).channels.size() : 0; } | |||
bool isBusEnabled (bool inputBus, int bus) const noexcept { return (getNumChannels (inputBus, bus) > 0); } | |||
bool hasInputs (int bus) const noexcept { return isBusEnabled (true, bus); } | |||
bool hasOutputs (int bus) const noexcept { return isBusEnabled (false, bus); } | |||
int getNumEnabledBuses (bool inputBus) const noexcept { int i; for (i = 0; i < getBusCount (inputBus); ++i) if (! isBusEnabled (inputBus, i)) break; return i; } | |||
int findTotalNumChannels (bool isInput) const noexcept | |||
{ | |||
int total = 0; | |||
const AudioBusArray& ioBuses = getFilterBus (isInput); | |||
for (int i = 0; i < ioBuses.size(); ++i) | |||
total += ioBuses.getReference (i).channels.size(); | |||
return total; | |||
} | |||
//============================================================================== | |||
void restoreBusArrangement (const AudioProcessor::AudioBusArrangement& original) const | |||
{ | |||
const int numInputBuses = getBusCount (true); | |||
const int numOutputBuses = getBusCount (false); | |||
jassert (original.inputBuses. size() == numInputBuses); | |||
jassert (original.outputBuses.size() == numOutputBuses); | |||
for (int busNr = 0; busNr < numInputBuses; ++busNr) | |||
processor.setPreferredBusArrangement (true, busNr, original.inputBuses.getReference (busNr).channels); | |||
for (int busNr = 0; busNr < numOutputBuses; ++busNr) | |||
processor.setPreferredBusArrangement (false, busNr, original.outputBuses.getReference (busNr).channels); | |||
} | |||
//============================================================================== | |||
Array<SupportedBusLayouts>& getSupportedLayouts (bool isInput) noexcept { return isInput ? inputLayouts : outputLayouts; } | |||
const Array<SupportedBusLayouts>& getSupportedLayouts (bool isInput) const noexcept { return isInput ? inputLayouts : outputLayouts; } | |||
SupportedBusLayouts& getSupportedBusLayouts (bool isInput, int busNr) noexcept { return getSupportedLayouts (isInput).getReference (busNr); } | |||
const SupportedBusLayouts& getSupportedBusLayouts (bool isInput, int busNr) const noexcept { return getSupportedLayouts (isInput).getReference (busNr); } | |||
bool busIgnoresLayout (bool inp, int bus) const noexcept | |||
{ | |||
return isPositiveAndBelow (bus, getSupportedLayouts (inp).size()) ? getSupportedBusLayouts (inp, bus).busIgnoresLayout : true; | |||
} | |||
const AudioChannelSet& getDefaultLayoutForBus (bool isInput, int busIdx) const noexcept { return getSupportedBusLayouts (isInput, busIdx).getDefault(); } | |||
bool hasDynamicInBuses() const noexcept { return dynamicInBuses; } | |||
bool hasDynamicOutBuses() const noexcept { return dynamicOutBuses; } | |||
void clear (int inputCount, int outputCount) | |||
{ | |||
inputLayouts.clear(); | |||
inputLayouts.resize (inputCount); | |||
outputLayouts.clear(); | |||
outputLayouts.resize (outputCount); | |||
} | |||
//============================================================================== | |||
AudioChannelSet getDefaultLayoutForChannelNumAndBus (bool isInput, int busIdx, int channelNum) const noexcept | |||
{ | |||
if (const AudioChannelSet* set = getSupportedBusLayouts (isInput, busIdx).getDefaultLayoutForChannelNum (channelNum)) | |||
return *set; | |||
return AudioChannelSet::canonicalChannelSet (channelNum); | |||
} | |||
void findAllCompatibleLayouts() | |||
{ | |||
{ | |||
ScopedBusRestorer restorer (*this); | |||
clear (getBusCount (true), getBusCount (false)); | |||
for (int i = 0; i < getBusCount (true); ++i) findAllCompatibleLayoutsForBus (true, i); | |||
for (int i = 0; i < getBusCount (false); ++i) findAllCompatibleLayoutsForBus (false, i); | |||
} | |||
// find the defaults | |||
for (int i = 0; i < getBusCount (true); ++i) | |||
updateDefaultLayout (true, i); | |||
for (int i = 0; i < getBusCount (false); ++i) | |||
updateDefaultLayout (false, i); | |||
// can any of the buses be disabled/enabled | |||
dynamicInBuses = doesPlugInHaveDynamicBuses (true); | |||
dynamicOutBuses = doesPlugInHaveDynamicBuses (false); | |||
} | |||
//============================================================================== | |||
void enableAllBuses() | |||
{ | |||
for (int busIdx = 1; busIdx < getBusCount (true); ++busIdx) | |||
if (getChannelSet (true, busIdx) == AudioChannelSet::disabled()) | |||
processor.setPreferredBusArrangement (true, busIdx, getDefaultLayoutForBus (true, busIdx)); | |||
for (int busIdx = 1; busIdx < getBusCount (false); ++busIdx) | |||
if (getChannelSet (false, busIdx) == AudioChannelSet::disabled()) | |||
processor.setPreferredBusArrangement (false, busIdx, getDefaultLayoutForBus (false, busIdx)); | |||
} | |||
//============================================================================== | |||
// Helper class which restores the original arrangement when it leaves scope | |||
class ScopedBusRestorer | |||
{ | |||
public: | |||
ScopedBusRestorer (PluginBusUtilities& bUtils) | |||
: busUtils (bUtils), | |||
originalArr (bUtils.processor.busArrangement), | |||
shouldRestore (true) | |||
{} | |||
~ScopedBusRestorer() | |||
{ | |||
if (shouldRestore) | |||
busUtils.restoreBusArrangement (originalArr); | |||
} | |||
void release() noexcept { shouldRestore = false; } | |||
private: | |||
PluginBusUtilities& busUtils; | |||
const AudioProcessor::AudioBusArrangement originalArr; | |||
bool shouldRestore; | |||
JUCE_DECLARE_NON_COPYABLE (ScopedBusRestorer) | |||
}; | |||
//============================================================================== | |||
AudioProcessor& processor; | |||
private: | |||
friend class ScopedBusRestorer; | |||
//============================================================================== | |||
Array<SupportedBusLayouts> inputLayouts, outputLayouts; | |||
bool dynamicInBuses, dynamicOutBuses, addDiscreteLayouts; | |||
//============================================================================== | |||
bool busIgnoresLayoutForChannelNum (bool isInput, int busNr, int channelNum) | |||
{ | |||
AudioChannelSet set; | |||
// If the plug-in does not complain about setting it's layout to an undefined layout | |||
// then we assume that the plug-in ignores the layout alltogether | |||
for (int i = 0; i < channelNum; ++i) | |||
set.addChannel (static_cast<AudioChannelSet::ChannelType> (SupportedBusLayouts::pseudoChannelBitNum + i)); | |||
return processor.setPreferredBusArrangement (isInput, busNr, set); | |||
} | |||
void findAllCompatibleLayoutsForBus (bool isInput, int busNr) | |||
{ | |||
const int maxNumChannels = 9; | |||
SupportedBusLayouts& layouts = getSupportedBusLayouts (isInput, busNr); | |||
layouts.supportedLayouts.clear(); | |||
// check if the plug-in bus can be disabled | |||
layouts.canBeDisabled = processor.setPreferredBusArrangement (isInput, busNr, AudioChannelSet()); | |||
layouts.busIgnoresLayout = true; | |||
for (int i = 1; i <= maxNumChannels; ++i) | |||
{ | |||
const bool ignoresLayoutForChannel = busIgnoresLayoutForChannelNum (isInput, busNr, i); | |||
Array<AudioChannelSet> sets = layoutListCompatibleWithChannelCount (addDiscreteLayouts, i); | |||
for (int j = 0; j < sets.size(); ++j) | |||
{ | |||
const AudioChannelSet& layout = sets.getReference (j); | |||
if (processor.setPreferredBusArrangement (isInput, busNr, layout)) | |||
{ | |||
if (! ignoresLayoutForChannel) | |||
layouts.busIgnoresLayout = false; | |||
layouts.supportedLayouts.add (layout); | |||
} | |||
} | |||
} | |||
// You cannot add a bus in your processor wich does not support any layouts! It must at least support one. | |||
jassert (layouts.supportedLayouts.size() > 0); | |||
} | |||
bool doesPlugInHaveDynamicBuses (bool isInput) const | |||
{ | |||
for (int i = 0; i < getBusCount (isInput); ++i) | |||
if (getSupportedBusLayouts (isInput, i).canBeDisabled) | |||
return true; | |||
return false; | |||
} | |||
void updateDefaultLayout (bool isInput, int busIdx) | |||
{ | |||
SupportedBusLayouts& layouts = getSupportedBusLayouts (isInput, busIdx); | |||
AudioChannelSet set = getChannelSet (isInput, busIdx); | |||
layouts.isEnabledByDefault = (set.size() > 0); | |||
if (layouts.isEnabledByDefault) | |||
layouts.supportedLayouts.add (set); | |||
// If you hit this assertion then you are disabling the main bus by default | |||
// which is unsupported | |||
jassert (layouts.isEnabledByDefault || busIdx >= 0); | |||
if (set == AudioChannelSet()) | |||
{ | |||
const bool mainBusHasInputs = hasInputs (0); | |||
const bool mainBusHasOutputs = hasOutputs (0); | |||
if (busIdx != 0 && (mainBusHasInputs || mainBusHasOutputs)) | |||
{ | |||
// the AudioProcessor does not give us any default layout | |||
// for an aux bus. Use the same number of channels as the | |||
// default layout on the main bus as a sensible default for | |||
// the aux bus | |||
const bool useInput = mainBusHasInputs && mainBusHasOutputs ? isInput : mainBusHasInputs; | |||
const AudioChannelSet& dfltLayout = getSupportedBusLayouts (useInput, 0).getDefault(); | |||
if ((layouts.defaultLayoutIndex = layouts.supportedLayouts.indexOf (dfltLayout)) >= 0) | |||
return; | |||
// no exact match: try at least to match the number of channels | |||
for (int i = 0; i < layouts.supportedLayouts.size(); ++i) | |||
{ | |||
if (layouts.supportedLayouts.getReference (i).size() == dfltLayout.size()) | |||
{ | |||
layouts.defaultLayoutIndex = i; | |||
return; | |||
} | |||
} | |||
} | |||
if (layouts.busIgnoresLayout) | |||
set = AudioChannelSet::discreteChannels (set.size()); | |||
} | |||
layouts.updateDefaultLayout (set); | |||
} | |||
static Array<AudioChannelSet> layoutListCompatibleWithChannelCount (bool addDiscrete, const int channelCount) noexcept | |||
{ | |||
jassert (channelCount > 0); | |||
Array<AudioChannelSet> sets; | |||
if (addDiscrete) | |||
sets.add (AudioChannelSet::discreteChannels (channelCount)); | |||
switch (channelCount) | |||
{ | |||
case 1: | |||
sets.add (AudioChannelSet::mono()); | |||
break; | |||
case 2: | |||
sets.add (AudioChannelSet::stereo()); | |||
break; | |||
case 4: | |||
sets.add (AudioChannelSet::quadraphonic()); | |||
sets.add (AudioChannelSet::ambisonic()); | |||
break; | |||
case 5: | |||
sets.add (AudioChannelSet::pentagonal()); | |||
sets.add (AudioChannelSet::create5point0()); | |||
break; | |||
case 6: | |||
sets.add (AudioChannelSet::hexagonal()); | |||
sets.add (AudioChannelSet::create6point0()); | |||
break; | |||
case 7: | |||
sets.add (AudioChannelSet::create6point1()); | |||
sets.add (AudioChannelSet::create7point0()); | |||
sets.add (AudioChannelSet::createFront7point0()); | |||
break; | |||
case 8: | |||
sets.add (AudioChannelSet::octagonal()); | |||
sets.add (AudioChannelSet::create7point1()); | |||
sets.add (AudioChannelSet::createFront7point1()); | |||
break; | |||
} | |||
return sets; | |||
} | |||
JUCE_DECLARE_NON_COPYABLE (PluginBusUtilities) | |||
}; |
@@ -26,10 +26,7 @@ | |||
#include <windows.h> | |||
#endif | |||
// Your project must contain an AppConfig.h file with your project-specific settings in it, | |||
// and your header search path must make it accessible to the module's files. | |||
#include "AppConfig.h" | |||
#include "../../juce_core/system/juce_TargetPlatform.h" | |||
#include "../utility/juce_CheckSettingMacros.h" | |||
#include "juce_IncludeModuleHeaders.h" | |||
@@ -54,7 +51,7 @@ extern "C" BOOL WINAPI DllMain (HINSTANCE instance, DWORD reason, LPVOID reserve | |||
} | |||
#endif | |||
(void) reserved; | |||
ignoreUnused (reserved); | |||
return TRUE; | |||
} | |||