@@ -2,8 +2,8 @@ | |||||
set -e | set -e | ||||
JUCE_MODULES_DIR="/home/falktx/Projects/FOSS/GIT-mine/DISTRHO-Ports/libs/juce/source/modules/" | |||||
CARLA_MODULES_DIR="/home/falktx/Projects/FOSS/GIT-mine/Carla/source/modules/" | |||||
JUCE_MODULES_DIR="/Shared/Personal/FOSS/GIT/DISTRHO/DISTRHO-Ports/libs/juce/source/modules/" | |||||
CARLA_MODULES_DIR="/home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/" | |||||
MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") | MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") | ||||
@@ -60,6 +60,7 @@ | |||||
// misc | // misc | ||||
#define JUCE_DISABLE_JUCE_VERSION_PRINTING 1 | #define JUCE_DISABLE_JUCE_VERSION_PRINTING 1 | ||||
#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1 | |||||
#define JUCE_STANDALONE_APPLICATION 0 | #define JUCE_STANDALONE_APPLICATION 0 | ||||
#define JUCE_STRING_UTF_TYPE 8 | #define JUCE_STRING_UTF_TYPE 8 | ||||
#define JUCE_USE_VFORK 1 | #define JUCE_USE_VFORK 1 | ||||
@@ -0,0 +1,336 @@ | |||||
/* | |||||
============================================================================== | |||||
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. | |||||
============================================================================== | |||||
*/ | |||||
AudioChannelSet::AudioChannelSet (uint32 c) : channels (c) {} | |||||
bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept { return channels == other.channels; } | |||||
bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; } | |||||
bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; } | |||||
String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) | |||||
{ | |||||
if (type >= discreteChannel0) | |||||
return String ("Discrete ") + String (type - discreteChannel0 + 1); | |||||
switch (type) | |||||
{ | |||||
case left: return NEEDS_TRANS("Left"); | |||||
case right: return NEEDS_TRANS("Right"); | |||||
case centre: return NEEDS_TRANS("Centre"); | |||||
case LFE: return NEEDS_TRANS("LFE"); | |||||
case leftSurround: return NEEDS_TRANS("Left Surround"); | |||||
case rightSurround: return NEEDS_TRANS("Right Surround"); | |||||
case leftCentre: return NEEDS_TRANS("Left Centre"); | |||||
case rightCentre: return NEEDS_TRANS("Right Centre"); | |||||
case centreSurround: return NEEDS_TRANS("Centre Surround"); | |||||
case leftSurroundRear: return NEEDS_TRANS("Left Surround Rear"); | |||||
case rightSurroundRear: return NEEDS_TRANS("Right Surround Rear"); | |||||
case topMiddle: return NEEDS_TRANS("Top Middle"); | |||||
case topFrontLeft: return NEEDS_TRANS("Top Front Left"); | |||||
case topFrontCentre: return NEEDS_TRANS("Top Front Centre"); | |||||
case topFrontRight: return NEEDS_TRANS("Top Front Right"); | |||||
case topRearLeft: return NEEDS_TRANS("Top Rear Left"); | |||||
case topRearCentre: return NEEDS_TRANS("Top Rear Centre"); | |||||
case topRearRight: return NEEDS_TRANS("Top Rear Right"); | |||||
case wideLeft: return NEEDS_TRANS("Wide Left"); | |||||
case wideRight: return NEEDS_TRANS("Wide Right"); | |||||
case LFE2: return NEEDS_TRANS("LFE 2"); | |||||
case leftSurroundSide: return NEEDS_TRANS ("Left Surround Side"); | |||||
case rightSurroundSide: return NEEDS_TRANS ("Right Surround Side"); | |||||
case ambisonicW: return NEEDS_TRANS("Ambisonic W"); | |||||
case ambisonicX: return NEEDS_TRANS("Ambisonic X"); | |||||
case ambisonicY: return NEEDS_TRANS("Ambisonic Y"); | |||||
case ambisonicZ: return NEEDS_TRANS("Ambisonic Z"); | |||||
default: break; | |||||
} | |||||
return "Unknown"; | |||||
} | |||||
String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) | |||||
{ | |||||
if (type >= discreteChannel0) | |||||
return String (type - discreteChannel0 + 1); | |||||
switch (type) | |||||
{ | |||||
case left: return "L"; | |||||
case right: return "R"; | |||||
case centre: return "C"; | |||||
case LFE: return "Lfe"; | |||||
case leftSurround: return "Ls"; | |||||
case rightSurround: return "Rs"; | |||||
case leftCentre: return "Lc"; | |||||
case rightCentre: return "Rc"; | |||||
case centreSurround: return "Cs"; | |||||
case leftSurroundRear: return "Lrs"; | |||||
case rightSurroundRear: return "Rrs"; | |||||
case topMiddle: return "Tm"; | |||||
case topFrontLeft: return "Tfl"; | |||||
case topFrontCentre: return "Tfc"; | |||||
case topFrontRight: return "Tfr"; | |||||
case topRearLeft: return "Trl"; | |||||
case topRearCentre: return "Trc"; | |||||
case topRearRight: return "Trr"; | |||||
case wideLeft: return "Wl"; | |||||
case wideRight: return "Wr"; | |||||
case LFE2: return "Lfe2"; | |||||
case leftSurroundSide: return "Lss"; | |||||
case rightSurroundSide: return "Rss"; | |||||
case ambisonicW: return "W"; | |||||
case ambisonicX: return "X"; | |||||
case ambisonicY: return "Y"; | |||||
case ambisonicZ: return "Z"; | |||||
default: break; | |||||
} | |||||
return ""; | |||||
} | |||||
String AudioChannelSet::getSpeakerArrangementAsString() const | |||||
{ | |||||
StringArray speakerTypes; | |||||
Array<AudioChannelSet::ChannelType> speakers = getChannelTypes(); | |||||
for (int i = 0; i < speakers.size(); ++i) | |||||
{ | |||||
String name = getAbbreviatedChannelTypeName (speakers.getReference (i)); | |||||
if (name.isNotEmpty()) | |||||
speakerTypes.add (name); | |||||
} | |||||
return speakerTypes.joinIntoString (" "); | |||||
} | |||||
String AudioChannelSet::getDescription() const | |||||
{ | |||||
if (isDiscreteLayout()) return String ("Discrete #") + String (size()); | |||||
if (*this == disabled()) return "Disabled"; | |||||
if (*this == mono()) return "Mono"; | |||||
if (*this == stereo()) return "Stereo"; | |||||
if (*this == createLCR()) return "LCR"; | |||||
if (*this == createLRS()) return "LRS"; | |||||
if (*this == createLCRS()) return "LCRS"; | |||||
if (*this == create5point0()) return "5.1 Surround"; | |||||
if (*this == create5point1()) return "5.1 Surround (+Lfe)"; | |||||
if (*this == create6point0()) return "6.1 Surround"; | |||||
if (*this == create6point1()) return "6.1 Surround (+Lfe)"; | |||||
if (*this == create6point0Music()) return "6.1 (Music) Surround"; | |||||
if (*this == create6point1Music()) return "6.1 (Music) Surround (+Lfe)"; | |||||
if (*this == create7point0()) return "7.1 Surround"; | |||||
if (*this == create7point1()) return "7.1 Surround (Lfe)"; | |||||
if (*this == create7point0SDDS()) return "7.1 Surround SDDS"; | |||||
if (*this == create7point1SDDS()) return "7.1 Surround SDDS (+Lfe)"; | |||||
if (*this == quadraphonic()) return "Quadraphonic"; | |||||
if (*this == pentagonal()) return "Pentagonal"; | |||||
if (*this == hexagonal()) return "Hexagonal"; | |||||
if (*this == octagonal()) return "Octagonal"; | |||||
if (*this == ambisonic()) return "Ambisonic"; | |||||
return "Unknown"; | |||||
} | |||||
bool AudioChannelSet::isDiscreteLayout() const noexcept | |||||
{ | |||||
Array<AudioChannelSet::ChannelType> speakers = getChannelTypes(); | |||||
for (int i = 0; i < speakers.size(); ++i) | |||||
if (speakers.getReference (i) > ambisonicZ) | |||||
return true; | |||||
return false; | |||||
} | |||||
int AudioChannelSet::size() const noexcept | |||||
{ | |||||
return channels.countNumberOfSetBits(); | |||||
} | |||||
AudioChannelSet::ChannelType AudioChannelSet::getTypeOfChannel (int index) const noexcept | |||||
{ | |||||
int bit = channels.findNextSetBit(0); | |||||
for (int i = 0; i < index && bit >= 0; ++i) | |||||
bit = channels.findNextSetBit (bit + 1); | |||||
return static_cast<ChannelType> (bit); | |||||
} | |||||
int AudioChannelSet::getChannelIndexForType (AudioChannelSet::ChannelType type) const noexcept | |||||
{ | |||||
int idx = 0; | |||||
for (int bit = channels.findNextSetBit (0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) | |||||
{ | |||||
if (static_cast<ChannelType> (bit) == type) | |||||
return idx; | |||||
idx++; | |||||
} | |||||
return -1; | |||||
} | |||||
Array<AudioChannelSet::ChannelType> AudioChannelSet::getChannelTypes() const | |||||
{ | |||||
Array<ChannelType> result; | |||||
for (int bit = channels.findNextSetBit(0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) | |||||
result.add (static_cast<ChannelType> (bit)); | |||||
return result; | |||||
} | |||||
void AudioChannelSet::addChannel (ChannelType newChannel) | |||||
{ | |||||
const int bit = static_cast<int> (newChannel); | |||||
jassert (bit >= 0 && bit < 1024); | |||||
channels.setBit (bit); | |||||
} | |||||
void AudioChannelSet::removeChannel (ChannelType newChannel) | |||||
{ | |||||
const int bit = static_cast<int> (newChannel); | |||||
jassert (bit >= 0 && bit < 1024); | |||||
channels.clearBit (bit); | |||||
} | |||||
AudioChannelSet AudioChannelSet::disabled() { return AudioChannelSet(); } | |||||
AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet (1u << centre); } | |||||
AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); } | |||||
AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre)); } | |||||
AudioChannelSet AudioChannelSet::createLRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surround)); } | |||||
AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surround)); } | |||||
AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround)); } | |||||
AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << LFE)); } | |||||
AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); } | |||||
AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround) | (1u << LFE)); } | |||||
AudioChannelSet AudioChannelSet::create6point0Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); } | |||||
AudioChannelSet AudioChannelSet::create6point1Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << LFE)); } | |||||
AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } | |||||
AudioChannelSet AudioChannelSet::create7point0SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); } | |||||
AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << LFE)); } | |||||
AudioChannelSet AudioChannelSet::create7point1SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre) | (1u << LFE)); } | |||||
AudioChannelSet AudioChannelSet::ambisonic() { return AudioChannelSet ((1u << ambisonicW) | (1u << ambisonicX) | (1u << ambisonicY) | (1u << ambisonicZ)); } | |||||
AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround)); } | |||||
AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } | |||||
AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << centre) | (1u << centreSurround)); } | |||||
AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << centre) | (1u << centreSurround) | (1u << wideLeft) | (1u << wideRight)); } | |||||
AudioChannelSet AudioChannelSet::discreteChannels (int numChannels) | |||||
{ | |||||
AudioChannelSet s; | |||||
s.channels.setRange (discreteChannel0, numChannels, true); | |||||
return s; | |||||
} | |||||
AudioChannelSet AudioChannelSet::canonicalChannelSet (int numChannels) | |||||
{ | |||||
if (numChannels == 1) return AudioChannelSet::mono(); | |||||
if (numChannels == 2) return AudioChannelSet::stereo(); | |||||
if (numChannels == 3) return AudioChannelSet::createLCR(); | |||||
if (numChannels == 4) return AudioChannelSet::quadraphonic(); | |||||
if (numChannels == 5) return AudioChannelSet::create5point0(); | |||||
if (numChannels == 6) return AudioChannelSet::create5point1(); | |||||
if (numChannels == 7) return AudioChannelSet::create7point0(); | |||||
if (numChannels == 8) return AudioChannelSet::create7point1(); | |||||
return discreteChannels (numChannels); | |||||
} | |||||
AudioChannelSet AudioChannelSet::namedChannelSet (int numChannels) | |||||
{ | |||||
if (numChannels == 1) return AudioChannelSet::mono(); | |||||
if (numChannels == 2) return AudioChannelSet::stereo(); | |||||
if (numChannels == 3) return AudioChannelSet::createLCR(); | |||||
if (numChannels == 4) return AudioChannelSet::quadraphonic(); | |||||
if (numChannels == 5) return AudioChannelSet::create5point0(); | |||||
if (numChannels == 6) return AudioChannelSet::create5point1(); | |||||
if (numChannels == 7) return AudioChannelSet::create7point0(); | |||||
if (numChannels == 8) return AudioChannelSet::create7point1(); | |||||
return AudioChannelSet(); | |||||
} | |||||
Array<AudioChannelSet> AudioChannelSet::channelSetsWithNumberOfChannels (int numChannels) | |||||
{ | |||||
Array<AudioChannelSet> retval; | |||||
if (numChannels != 0) | |||||
{ | |||||
retval.add (AudioChannelSet::discreteChannels (numChannels)); | |||||
if (numChannels == 1) | |||||
{ | |||||
retval.add (AudioChannelSet::mono()); | |||||
} | |||||
else if (numChannels == 2) | |||||
{ | |||||
retval.add (AudioChannelSet::stereo()); | |||||
} | |||||
else if (numChannels == 3) | |||||
{ | |||||
retval.add (AudioChannelSet::createLCR()); | |||||
retval.add (AudioChannelSet::createLRS()); | |||||
} | |||||
else if (numChannels == 4) | |||||
{ | |||||
retval.add (AudioChannelSet::quadraphonic()); | |||||
retval.add (AudioChannelSet::createLCRS()); | |||||
retval.add (AudioChannelSet::ambisonic()); | |||||
} | |||||
else if (numChannels == 5) | |||||
{ | |||||
retval.add (AudioChannelSet::create5point0()); | |||||
retval.add (AudioChannelSet::pentagonal()); | |||||
} | |||||
else if (numChannels == 6) | |||||
{ | |||||
retval.add (AudioChannelSet::create5point1()); | |||||
retval.add (AudioChannelSet::create6point0()); | |||||
retval.add (AudioChannelSet::create6point0Music()); | |||||
retval.add (AudioChannelSet::hexagonal()); | |||||
} | |||||
else if (numChannels == 7) | |||||
{ | |||||
retval.add (AudioChannelSet::create7point0()); | |||||
retval.add (AudioChannelSet::create7point0SDDS()); | |||||
retval.add (AudioChannelSet::create6point1()); | |||||
retval.add (AudioChannelSet::create6point1Music()); | |||||
} | |||||
else if (numChannels == 8) | |||||
{ | |||||
retval.add (AudioChannelSet::create7point1()); | |||||
retval.add (AudioChannelSet::create7point1SDDS()); | |||||
retval.add (AudioChannelSet::octagonal()); | |||||
} | |||||
} | |||||
return retval; | |||||
} |
@@ -0,0 +1,361 @@ | |||||
/* | |||||
============================================================================== | |||||
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_AUDIOCHANNELSET_H_INCLUDED | |||||
#define JUCE_AUDIOCHANNELSET_H_INCLUDED | |||||
//============================================================================== | |||||
/** | |||||
Represents a set of audio channel types. | |||||
For example, you might have a set of left + right channels, which is a stereo | |||||
channel set. It is a collection of values from the AudioChannelSet::ChannelType | |||||
enum, where each type may only occur once within the set. | |||||
The documentation below lists which AudioChannelSet corresponds to which native | |||||
layouts used by AAX, VST2/VST3 and CoreAudio/AU. The layout tags in CoreAudio | |||||
are particularly confusing. For example, the layout which is labeled as "7.1 SDDS" | |||||
in Logic Pro, corresponds to CoreAudio/AU's kAudioChannelLayoutTag_DTS_7_0 tag, whereas | |||||
AAX's DTS 7.1 Layout corresponds to CoreAudio/AU's | |||||
kAudioChannelLayoutTag_MPEG_7_1_A format, etc. Please do not use the CoreAudio tag | |||||
as an indication to the actual layout of the speakers. | |||||
@see Bus | |||||
*/ | |||||
class JUCE_API AudioChannelSet | |||||
{ | |||||
public: | |||||
/** Creates an empty channel set. | |||||
You can call addChannel to add channels to the set. | |||||
*/ | |||||
AudioChannelSet() noexcept {} | |||||
/** Creates a zero-channel set which can be used to indicate that a | |||||
bus is disabled. */ | |||||
static AudioChannelSet disabled(); | |||||
//============================================================================== | |||||
/** Creates a one-channel mono set (centre). | |||||
Is equivalent to: kMonoAAX (VST), AAX_eStemFormat_Mono (AAX), kAudioChannelLayoutTag_Mono (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet mono(); | |||||
/** Creates a set containing a stereo set (left, right). | |||||
Is equivalent to: kStereo (VST), AAX_eStemFormat_Stereo (AAX), kAudioChannelLayoutTag_Stereo (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet stereo(); | |||||
//============================================================================== | |||||
/** Creates a set containing an LCR set (left, right, centre). | |||||
Is equivalent to: k30Cine (VST), AAX_eStemFormat_LCR (AAX), kAudioChannelLayoutTag_MPEG_3_0_A (CoreAudio) | |||||
This format is referred to as "LRC" in Cubase. | |||||
This format is referred to as "LCR" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet createLCR(); | |||||
/** Creates a set containing an LRS set (left, right, surround). | |||||
Is equivalent to: k30Music (VST), n/a (AAX), kAudioChannelLayoutTag_ITU_2_1 (CoreAudio) | |||||
This format is referred to as "LRS" in Cubase. | |||||
*/ | |||||
static AudioChannelSet createLRS(); | |||||
/** Creates a set containing an LCRS set (left, right, centre, surround). | |||||
Is equivalent to: k40Cine (VST), AAX_eStemFormat_LCRS (AAX), kAudioChannelLayoutTag_MPEG_4_0_A (CoreAudio) | |||||
This format is referred to as "LCRS (Pro Logic)" in Logic Pro. | |||||
This format is referred to as "LRCS" in Cubase. | |||||
This format is referred to as "LCRS" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet createLCRS(); | |||||
//============================================================================== | |||||
/** Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround). | |||||
Is equivalent to: k50 (VST), AAX_eStemFormat_5_0 (AAX), kAudioChannelLayoutTag_MPEG_5_0_A (CoreAudio) | |||||
This format is referred to as "5.0" in Cubase. | |||||
This format is referred to as "5.0" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet create5point0(); | |||||
/** Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE). | |||||
Is equivalent to: k51 (VST), AAX_eStemFormat_5_1 (AAX), kAudioChannelLayoutTag_MPEG_5_1_A (CoreAudio) | |||||
This format is referred to as "5.1 (ITU 775)" in Logic Pro. | |||||
This format is referred to as "5.1" in Cubase. | |||||
This format is referred to as "5.1" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet create5point1(); | |||||
/** Creates a set for a 6.0 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround). | |||||
Is equivalent to: k60Cine (VST), AAX_eStemFormat_6_0 (AAX), kAudioChannelLayoutTag_AudioUnit_6_0 (CoreAudio) | |||||
Logic Pro incorrectly uses this for the surround format labeled "6.1 (ES/EX)". | |||||
This format is referred to as "6.0 Cine" in Cubase. | |||||
This format is referred to as "6.0" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet create6point0(); | |||||
/** Creates a set for a 6.1 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround, LFE). | |||||
Is equivalent to: k61Cine (VST), AAX_eStemFormat_6_1 (AAX), kAudioChannelLayoutTag_MPEG_6_1_A (CoreAudio) | |||||
This format is referred to as "6.1" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet create6point1(); | |||||
/** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide). | |||||
Is equivalent to: k60Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_0_A (CoreAudio) | |||||
This format is referred to as "6.0 Music" in Cubase. | |||||
*/ | |||||
static AudioChannelSet create6point0Music(); | |||||
/** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, LFE). | |||||
Is equivalent to: k61Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_1_A (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet create6point1Music(); | |||||
/** Creates a set for a DTS 7.0 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear). | |||||
Is equivalent to: k70Music (VST), AAX_eStemFormat_7_0_DTS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0 (CoreAudio) | |||||
This format is referred to as "7.0" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet create7point0(); | |||||
/** Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre). | |||||
Is equivalent to: k70Cine (VST), AAX_eStemFormat_7_0_SDDS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0_Front (CoreAudio) | |||||
This format is referred to as "7.0 SDDS" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet create7point0SDDS(); | |||||
/** Creates a set for a DTS 7.1 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE). | |||||
Is equivalent to: k71CineSideFill (VST), AAX_eStemFormat_7_1_DTS (AAX), kAudioChannelLayoutTag_MPEG_7_1_C/kAudioChannelLayoutTag_ITU_3_4_1 (CoreAudio) | |||||
This format is referred to as "7.1 (3/4.1)" in Logic Pro. | |||||
This format is referred to as "7.1" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet create7point1(); | |||||
/** Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE). | |||||
Is equivalent to: k71Cine (VST), AAX_eStemFormat_7_1_SDDS (AAX), kAudioChannelLayoutTag_MPEG_7_1_A (CoreAudio) | |||||
This format is referred to as "7.1 (SDDS)" in Logic Pro. | |||||
This format is referred to as "7.1 SDDS" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet create7point1SDDS(); | |||||
//============================================================================== | |||||
/** Creates a set for ambisonic surround setups (ambisonicW, ambisonicX, ambisonicY, ambisonicZ). | |||||
Is equivalent to: kBFormat (VST), n/a (AAX), kAudioChannelLayoutTag_Ambisonic_B_Format (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet ambisonic(); | |||||
/** Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround) | |||||
Is equivalent to: k40Music (VST), AAX_eStemFormat_Quad (AAX), kAudioChannelLayoutTag_Quadraphonic (CoreAudio) | |||||
This format is referred to as "Quadraphonic" in Logic Pro. | |||||
This format is referred to as "Quadro" in Cubase. | |||||
This format is referred to as "Quad" in Pro Tools. | |||||
*/ | |||||
static AudioChannelSet quadraphonic(); | |||||
/** Creates a set for pentagonal surround setup (left, right, centre, leftSurroundRear, rightSurroundRear). | |||||
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Pentagonal (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet pentagonal(); | |||||
/** Creates a set for hexagonal surround setup (left, right, leftSurroundRear, rightSurroundRear, centre, surroundCentre). | |||||
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Hexagonal (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet hexagonal(); | |||||
/** Creates a set for octagonal surround setup (left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight). | |||||
Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Octagonal (CoreAudio) | |||||
*/ | |||||
static AudioChannelSet octagonal(); | |||||
//============================================================================== | |||||
/** Creates a set of untyped discrete channels. */ | |||||
static AudioChannelSet discreteChannels (int numChannels); | |||||
/** Create a canonical channel set for a given number of channels. | |||||
For example, numChannels = 1 will return mono, numChannels = 2 will return stereo, etc. */ | |||||
static AudioChannelSet canonicalChannelSet (int numChannels); | |||||
/** Create a channel set for a given number of channels which is non-discrete. | |||||
If numChannels is larger than the number of channels of the surround format | |||||
with the maximum amount of channels (currently 7.1 Surround), then this | |||||
function returns an empty set.*/ | |||||
static AudioChannelSet namedChannelSet (int numChannels); | |||||
/** Return an array of channel sets which have a given number of channels */ | |||||
static Array<AudioChannelSet> channelSetsWithNumberOfChannels (int numChannels); | |||||
//============================================================================== | |||||
/** Represents different audio channel types. */ | |||||
enum ChannelType | |||||
{ | |||||
unknown = 0, | |||||
left = 1, // L | |||||
right = 2, // R | |||||
centre = 3, // C (sometimes M for mono) | |||||
LFE = 4, | |||||
leftSurround = 5, // Ls | |||||
rightSurround = 6, // Rs | |||||
leftCentre = 7, // Lc (AAX/VST), Lc used as Lss in AU for most layouts | |||||
rightCentre = 8, // Rc (AAX/VST), Rc used as Rss in AU for most layouts | |||||
centreSurround = 9, // Cs/S | |||||
surround = centreSurround, // Cs/S | |||||
leftSurroundSide = 10, // Lss (AXX), Side Left "Sl" (VST), Left Centre "LC" (AU) | |||||
rightSurroundSide = 11, // Rss (AXX), Side right "Sr" (VST), Right Centre "Rc" (AU) | |||||
topMiddle = 12, | |||||
topFrontLeft = 13, | |||||
topFrontCentre = 14, | |||||
topFrontRight = 15, | |||||
topRearLeft = 16, | |||||
topRearCentre = 17, | |||||
topRearRight = 18, | |||||
LFE2 = 19, | |||||
leftSurroundRear = 20, // Lsr (AAX), Lcs (VST), Rls (AU) | |||||
rightSurroundRear = 21, // Rsr (AAX), Rcs (VST), Rrs (AU) | |||||
wideLeft = 22, | |||||
wideRight = 23, | |||||
ambisonicW = 24, | |||||
ambisonicX = 25, | |||||
ambisonicY = 26, | |||||
ambisonicZ = 27, | |||||
discreteChannel0 = 64 /**< Non-typed individual channels are indexed upwards from this value. */ | |||||
}; | |||||
/** Returns the name of a given channel type. For example, this method may return "Surround Left". */ | |||||
static String getChannelTypeName (ChannelType); | |||||
/** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */ | |||||
static String getAbbreviatedChannelTypeName (ChannelType); | |||||
//============================================================================== | |||||
enum | |||||
{ | |||||
maxChannelsOfNamedLayout = 8 | |||||
}; | |||||
/** Adds a channel to the set. */ | |||||
void addChannel (ChannelType newChannelType); | |||||
/** Removes a channel from the set. */ | |||||
void removeChannel (ChannelType newChannelType); | |||||
/** Returns the number of channels in the set. */ | |||||
int size() const noexcept; | |||||
/** Returns true if there are no channels in the set. */ | |||||
bool isDisabled() const noexcept { return size() == 0; } | |||||
/** Returns an array of all the types in this channel set. */ | |||||
Array<ChannelType> getChannelTypes() const; | |||||
/** Returns the type of one of the channels in the set, by index. */ | |||||
ChannelType getTypeOfChannel (int channelIndex) const noexcept; | |||||
/** Returns the index for a particular channel-type. | |||||
Will return -1 if the this set does not contain a channel of this type. */ | |||||
int getChannelIndexForType (ChannelType type) const noexcept; | |||||
/** Returns a string containing a whitespace-separated list of speaker types | |||||
corresponding to each channel. For example in a 5.1 arrangement, | |||||
the string may be "L R C Lfe Ls Rs". If the speaker arrangement is unknown, | |||||
the returned string will be empty.*/ | |||||
String getSpeakerArrangementAsString() const; | |||||
/** Returns the description of the current layout. For example, this method may return | |||||
"Quadraphonic". Note that the returned string may not be unique. */ | |||||
String getDescription() const; | |||||
/** Returns if this is a channel layout made-up of discrete channels. */ | |||||
bool isDiscreteLayout() const noexcept; | |||||
/** Intersect two channel layouts. */ | |||||
void intersect (const AudioChannelSet& other) { channels &= other.channels; } | |||||
//============================================================================== | |||||
bool operator== (const AudioChannelSet&) const noexcept; | |||||
bool operator!= (const AudioChannelSet&) const noexcept; | |||||
bool operator< (const AudioChannelSet&) const noexcept; | |||||
private: | |||||
BigInteger channels; | |||||
explicit AudioChannelSet (uint32); | |||||
}; | |||||
#endif // JUCE_AUDIOCHANNELSET_H_INCLUDED |
@@ -397,9 +397,9 @@ public: | |||||
convert between 32 and 64 bit float buffer types. | convert between 32 and 64 bit float buffer types. | ||||
*/ | */ | ||||
template <typename OtherType> | template <typename OtherType> | ||||
void makeCopyOf (const AudioBuffer<OtherType>& other) | |||||
void makeCopyOf (const AudioBuffer<OtherType>& other, bool avoidReallocating = false) | |||||
{ | { | ||||
setSize (other.getNumChannels(), other.getNumSamples()); | |||||
setSize (other.getNumChannels(), other.getNumSamples(), false, false, avoidReallocating); | |||||
if (other.hasBeenCleared()) | if (other.hasBeenCleared()) | ||||
{ | { | ||||
@@ -204,10 +204,11 @@ namespace FloatVectorHelpers | |||||
typedef float Type; | typedef float Type; | ||||
typedef float32x4_t ParallelType; | typedef float32x4_t ParallelType; | ||||
typedef uint32x4_t IntegerType; | typedef uint32x4_t IntegerType; | ||||
union signMaskUnion { ParallelType f; IntegerType i; }; | |||||
enum { numParallel = 4 }; | enum { numParallel = 4 }; | ||||
static forcedinline IntegerType toint (ParallelType v) noexcept { union { ParallelType f; IntegerType i; } u; u.f = v; return u.i; } | |||||
static forcedinline ParallelType toflt (IntegerType v) noexcept { union { ParallelType f; IntegerType i; } u; u.i = v; return u.f; } | |||||
static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } | |||||
static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } | |||||
static forcedinline ParallelType load1 (Type v) noexcept { return vld1q_dup_f32 (&v); } | static forcedinline ParallelType load1 (Type v) noexcept { return vld1q_dup_f32 (&v); } | ||||
static forcedinline ParallelType loadA (const Type* v) noexcept { return vld1q_f32 (v); } | static forcedinline ParallelType loadA (const Type* v) noexcept { return vld1q_f32 (v); } | ||||
@@ -235,10 +236,11 @@ namespace FloatVectorHelpers | |||||
typedef double Type; | typedef double Type; | ||||
typedef double ParallelType; | typedef double ParallelType; | ||||
typedef uint64 IntegerType; | typedef uint64 IntegerType; | ||||
union signMaskUnion { ParallelType f; IntegerType i; }; | |||||
enum { numParallel = 1 }; | enum { numParallel = 1 }; | ||||
static forcedinline IntegerType toint (ParallelType v) noexcept { union { ParallelType f; IntegerType i; } u; u.f = v; return u.i; } | |||||
static forcedinline ParallelType toflt (IntegerType v) noexcept { union { ParallelType f; IntegerType i; } u; u.i = v; return u.f; } | |||||
static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } | |||||
static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } | |||||
static forcedinline ParallelType load1 (Type v) noexcept { return v; } | static forcedinline ParallelType load1 (Type v) noexcept { return v; } | ||||
static forcedinline ParallelType loadA (const Type* v) noexcept { return *v; } | static forcedinline ParallelType loadA (const Type* v) noexcept { return *v; } | ||||
@@ -346,6 +348,9 @@ namespace FloatVectorHelpers | |||||
#define JUCE_LOAD_SRC1_SRC2_DEST(src1Load, src2Load, dstLoad) const Mode::ParallelType d = dstLoad (dest), s1 = src1Load (src1), s2 = src2Load (src2); | #define JUCE_LOAD_SRC1_SRC2_DEST(src1Load, src2Load, dstLoad) const Mode::ParallelType d = dstLoad (dest), s1 = src1Load (src1), s2 = src2Load (src2); | ||||
#define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src); | #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src); | ||||
union signMask32 { float f; uint32 i; }; | |||||
union signMask64 { double d; uint64 i; }; | |||||
#if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | ||||
template<int typeSize> struct ModeType { typedef BasicOps32 Mode; }; | template<int typeSize> struct ModeType { typedef BasicOps32 Mode; }; | ||||
template<> struct ModeType<8> { typedef BasicOps64 Mode; }; | template<> struct ModeType<8> { typedef BasicOps64 Mode; }; | ||||
@@ -481,6 +486,17 @@ namespace FloatVectorHelpers | |||||
#endif | #endif | ||||
} | } | ||||
//============================================================================== | |||||
namespace | |||||
{ | |||||
#if JUCE_USE_VDSP_FRAMEWORK | |||||
// This casts away constness to account for slightly different vDSP function signatures | |||||
// in OSX 10.8 SDK and below. Can be safely removed once those SDKs are obsolete. | |||||
template <typename ValueType> | |||||
ValueType* osx108sdkCompatibilityCast (const ValueType* arg) noexcept { return const_cast<ValueType*> (arg); } | |||||
#endif | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept | void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept | ||||
{ | { | ||||
@@ -568,10 +584,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double amount, int | |||||
const Mode::ParallelType amountToAdd = Mode::load1 (amount);) | const Mode::ParallelType amountToAdd = Mode::load1 (amount);) | ||||
} | } | ||||
void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float amount, int num) noexcept | |||||
void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, float amount, int num) noexcept | |||||
{ | { | ||||
#if JUCE_USE_VDSP_FRAMEWORK | #if JUCE_USE_VDSP_FRAMEWORK | ||||
vDSP_vsadd (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||||
vDSP_vsadd (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); | |||||
#else | #else | ||||
JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | ||||
JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | ||||
@@ -579,10 +595,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float am | |||||
#endif | #endif | ||||
} | } | ||||
void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double* src, double amount, int num) noexcept | |||||
void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src, double amount, int num) noexcept | |||||
{ | { | ||||
#if JUCE_USE_VDSP_FRAMEWORK | #if JUCE_USE_VDSP_FRAMEWORK | ||||
vDSP_vsaddD (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||||
vDSP_vsaddD (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); | |||||
#else | #else | ||||
JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | ||||
JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | ||||
@@ -795,7 +811,7 @@ void FloatVectorOperations::abs (float* dest, const float* src, int num) noexcep | |||||
#if JUCE_USE_VDSP_FRAMEWORK | #if JUCE_USE_VDSP_FRAMEWORK | ||||
vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num); | vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num); | ||||
#else | #else | ||||
union { float f; uint32 i; } signMask; | |||||
FloatVectorHelpers::signMask32 signMask; | |||||
signMask.i = 0x7fffffffUL; | signMask.i = 0x7fffffffUL; | ||||
JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabsf (src[i]), Mode::bit_and (s, mask), | JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabsf (src[i]), Mode::bit_and (s, mask), | ||||
JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | ||||
@@ -810,7 +826,7 @@ void FloatVectorOperations::abs (double* dest, const double* src, int num) noexc | |||||
#if JUCE_USE_VDSP_FRAMEWORK | #if JUCE_USE_VDSP_FRAMEWORK | ||||
vDSP_vabsD ((double*) src, 1, dest, 1, (vDSP_Length) num); | vDSP_vabsD ((double*) src, 1, dest, 1, (vDSP_Length) num); | ||||
#else | #else | ||||
union {double d; uint64 i;} signMask; | |||||
FloatVectorHelpers::signMask64 signMask; | |||||
signMask.i = 0x7fffffffffffffffULL; | signMask.i = 0x7fffffffffffffffULL; | ||||
JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabs (src[i]), Mode::bit_and (s, mask), | JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabs (src[i]), Mode::bit_and (s, mask), | ||||
@@ -991,7 +1007,7 @@ void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnab | |||||
void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport() noexcept | void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport() noexcept | ||||
{ | { | ||||
#if JUCE_USE_SSE_INTRINSICS | #if JUCE_USE_SSE_INTRINSICS | ||||
const int mxcsr = _mm_getcsr(); | |||||
const unsigned int mxcsr = _mm_getcsr(); | |||||
_mm_setcsr (mxcsr | 0x8040); // add the DAZ and FZ bits | _mm_setcsr (mxcsr | 0x8040); // add the DAZ and FZ bits | ||||
#endif | #endif | ||||
} | } | ||||
@@ -66,10 +66,10 @@ public: | |||||
static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; | static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; | ||||
/** Adds a fixed value to each source value and stores it in the destination array. */ | /** Adds a fixed value to each source value and stores it in the destination array. */ | ||||
static void JUCE_CALLTYPE add (float* dest, float* src, float amount, int numValues) noexcept; | |||||
static void JUCE_CALLTYPE add (float* dest, const float* src, float amount, int numValues) noexcept; | |||||
/** Adds a fixed value to each source value and stores it in the destination array. */ | /** Adds a fixed value to each source value and stores it in the destination array. */ | ||||
static void JUCE_CALLTYPE add (double* dest, double* src, double amount, int numValues) noexcept; | |||||
static void JUCE_CALLTYPE add (double* dest, const double* src, double amount, int numValues) noexcept; | |||||
/** Adds the source values to the destination values. */ | /** Adds the source values to the destination values. */ | ||||
static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | ||||
@@ -22,7 +22,6 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
struct CatmullRomAlgorithm | struct CatmullRomAlgorithm | ||||
{ | { | ||||
static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept | static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept | ||||
@@ -22,7 +22,6 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
/** | /** | ||||
Interpolator for resampling a stream of floats using Catmull-Rom interpolation. | Interpolator for resampling a stream of floats using Catmull-Rom interpolation. | ||||
@@ -62,33 +62,131 @@ IIRCoefficients::IIRCoefficients (double c1, double c2, double c3, | |||||
IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | ||||
const double frequency) noexcept | const double frequency) noexcept | ||||
{ | { | ||||
jassert (sampleRate > 0); | |||||
return makeLowPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | |||||
const double frequency, | |||||
const double Q) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | ||||
const double nSquared = n * n; | const double nSquared = n * n; | ||||
const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||||
const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
return IIRCoefficients (c1, | return IIRCoefficients (c1, | ||||
c1 * 2.0, | c1 * 2.0, | ||||
c1, | c1, | ||||
1.0, | 1.0, | ||||
c1 * 2.0 * (1.0 - nSquared), | c1 * 2.0 * (1.0 - nSquared), | ||||
c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||||
} | } | ||||
IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, | IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, | ||||
const double frequency) noexcept | const double frequency) noexcept | ||||
{ | { | ||||
return makeHighPass (sampleRate, frequency, 1.0 / std::sqrt(2.0)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, | |||||
const double frequency, | |||||
const double Q) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double n = std::tan (double_Pi * frequency / sampleRate); | const double n = std::tan (double_Pi * frequency / sampleRate); | ||||
const double nSquared = n * n; | const double nSquared = n * n; | ||||
const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||||
const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
return IIRCoefficients (c1, | return IIRCoefficients (c1, | ||||
c1 * -2.0, | c1 * -2.0, | ||||
c1, | c1, | ||||
1.0, | 1.0, | ||||
c1 * 2.0 * (nSquared - 1.0), | c1 * 2.0 * (nSquared - 1.0), | ||||
c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeBandPass (const double sampleRate, | |||||
const double frequency) noexcept | |||||
{ | |||||
return makeBandPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeBandPass (const double sampleRate, | |||||
const double frequency, | |||||
const double Q) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
return IIRCoefficients (c1 * n / Q, | |||||
0.0, | |||||
-c1 * n / Q, | |||||
1.0, | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeNotchFilter (const double sampleRate, | |||||
const double frequency) noexcept | |||||
{ | |||||
return makeNotchFilter (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeNotchFilter (const double sampleRate, | |||||
const double frequency, | |||||
const double Q) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + n / Q + nSquared); | |||||
return IIRCoefficients (c1 * (1.0 + nSquared), | |||||
2.0 * c1 * (1.0 - nSquared), | |||||
c1 * (1.0 + nSquared), | |||||
1.0, | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
c1 * (1.0 - n / Q + nSquared)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeAllPass (const double sampleRate, | |||||
const double frequency) noexcept | |||||
{ | |||||
return makeAllPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
} | |||||
IIRCoefficients IIRCoefficients::makeAllPass (const double sampleRate, | |||||
const double frequency, | |||||
const double Q) noexcept | |||||
{ | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||||
const double nSquared = n * n; | |||||
const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
return IIRCoefficients (c1 * (1.0 - n / Q + nSquared), | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
1.0, | |||||
1.0, | |||||
c1 * 2.0 * (1.0 - nSquared), | |||||
c1 * (1.0 - n / Q + nSquared)); | |||||
} | } | ||||
IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, | IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, | ||||
@@ -96,8 +194,9 @@ IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, | |||||
const double Q, | const double Q, | ||||
const float gainFactor) noexcept | const float gainFactor) noexcept | ||||
{ | { | ||||
jassert (sampleRate > 0); | |||||
jassert (Q > 0); | |||||
jassert (sampleRate > 0.0); | |||||
jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double A = jmax (0.0f, std::sqrt (gainFactor)); | const double A = jmax (0.0f, std::sqrt (gainFactor)); | ||||
const double aminus1 = A - 1.0; | const double aminus1 = A - 1.0; | ||||
@@ -120,8 +219,9 @@ IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate, | |||||
const double Q, | const double Q, | ||||
const float gainFactor) noexcept | const float gainFactor) noexcept | ||||
{ | { | ||||
jassert (sampleRate > 0); | |||||
jassert (Q > 0); | |||||
jassert (sampleRate > 0.0); | |||||
jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double A = jmax (0.0f, std::sqrt (gainFactor)); | const double A = jmax (0.0f, std::sqrt (gainFactor)); | ||||
const double aminus1 = A - 1.0; | const double aminus1 = A - 1.0; | ||||
@@ -140,15 +240,16 @@ IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate, | |||||
} | } | ||||
IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, | IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, | ||||
const double centreFrequency, | |||||
const double frequency, | |||||
const double Q, | const double Q, | ||||
const float gainFactor) noexcept | const float gainFactor) noexcept | ||||
{ | { | ||||
jassert (sampleRate > 0); | |||||
jassert (Q > 0); | |||||
jassert (sampleRate > 0.0); | |||||
jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
jassert (Q > 0.0); | |||||
const double A = jmax (0.0f, std::sqrt (gainFactor)); | const double A = jmax (0.0f, std::sqrt (gainFactor)); | ||||
const double omega = (double_Pi * 2.0 * jmax (centreFrequency, 2.0)) / sampleRate; | |||||
const double omega = (double_Pi * 2.0 * jmax (frequency, 2.0)) / sampleRate; | |||||
const double alpha = 0.5 * std::sin (omega) / Q; | const double alpha = 0.5 * std::sin (omega) / Q; | ||||
const double c2 = -2.0 * std::cos (omega); | const double c2 = -2.0 * std::cos (omega); | ||||
const double alphaTimesA = alpha * A; | const double alphaTimesA = alpha * A; | ||||
@@ -164,12 +265,12 @@ IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, | |||||
//============================================================================== | //============================================================================== | ||||
IIRFilter::IIRFilter() noexcept | IIRFilter::IIRFilter() noexcept | ||||
: v1 (0), v2 (0), active (false) | |||||
: v1 (0.0), v2 (0.0), active (false) | |||||
{ | { | ||||
} | } | ||||
IIRFilter::IIRFilter (const IIRFilter& other) noexcept | IIRFilter::IIRFilter (const IIRFilter& other) noexcept | ||||
: v1 (0), v2 (0), active (other.active) | |||||
: v1 (0.0), v2 (0.0), active (other.active) | |||||
{ | { | ||||
const SpinLock::ScopedLockType sl (other.processLock); | const SpinLock::ScopedLockType sl (other.processLock); | ||||
coefficients = other.coefficients; | coefficients = other.coefficients; | ||||
@@ -198,7 +299,7 @@ void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcep | |||||
void IIRFilter::reset() noexcept | void IIRFilter::reset() noexcept | ||||
{ | { | ||||
const SpinLock::ScopedLockType sl (processLock); | const SpinLock::ScopedLockType sl (processLock); | ||||
v1 = v2 = 0; | |||||
v1 = v2 = 0.0; | |||||
} | } | ||||
float IIRFilter::processSingleSampleRaw (const float in) noexcept | float IIRFilter::processSingleSampleRaw (const float in) noexcept | ||||
@@ -55,14 +55,53 @@ public: | |||||
/** Destructor. */ | /** Destructor. */ | ||||
~IIRCoefficients() noexcept; | ~IIRCoefficients() noexcept; | ||||
//============================================================================== | |||||
/** Returns the coefficients for a low-pass filter. */ | /** Returns the coefficients for a low-pass filter. */ | ||||
static IIRCoefficients makeLowPass (double sampleRate, | static IIRCoefficients makeLowPass (double sampleRate, | ||||
double frequency) noexcept; | double frequency) noexcept; | ||||
/** Returns the coefficients for a low-pass filter with variable Q. */ | |||||
static IIRCoefficients makeLowPass (double sampleRate, | |||||
double frequency, | |||||
double Q) noexcept; | |||||
//============================================================================== | |||||
/** Returns the coefficients for a high-pass filter. */ | /** Returns the coefficients for a high-pass filter. */ | ||||
static IIRCoefficients makeHighPass (double sampleRate, | static IIRCoefficients makeHighPass (double sampleRate, | ||||
double frequency) noexcept; | double frequency) noexcept; | ||||
/** Returns the coefficients for a high-pass filter with variable Q. */ | |||||
static IIRCoefficients makeHighPass (double sampleRate, | |||||
double frequency, | |||||
double Q) noexcept; | |||||
//============================================================================== | |||||
/** Returns the coefficients for a band-pass filter. */ | |||||
static IIRCoefficients makeBandPass (double sampleRate, double frequency) noexcept; | |||||
/** Returns the coefficients for a band-pass filter with variable Q. */ | |||||
static IIRCoefficients makeBandPass (double sampleRate, | |||||
double frequency, | |||||
double Q) noexcept; | |||||
//============================================================================== | |||||
/** Returns the coefficients for a notch filter. */ | |||||
static IIRCoefficients makeNotchFilter (double sampleRate, double frequency) noexcept; | |||||
/** Returns the coefficients for a notch filter with variable Q. */ | |||||
static IIRCoefficients makeNotchFilter (double sampleRate, | |||||
double frequency, | |||||
double Q) noexcept; | |||||
//============================================================================== | |||||
/** Returns the coefficients for an all-pass filter. */ | |||||
static IIRCoefficients makeAllPass (double sampleRate, double frequency) noexcept; | |||||
/** Returns the coefficients for an all-pass filter with variable Q. */ | |||||
static IIRCoefficients makeAllPass (double sampleRate, | |||||
double frequency, | |||||
double Q) noexcept; | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns the coefficients for a low-pass shelf filter with variable Q and gain. | /** Returns the coefficients for a low-pass shelf filter with variable Q and gain. | ||||
@@ -22,7 +22,6 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
/** | /** | ||||
Interpolator for resampling a stream of floats using 4-point lagrange interpolation. | Interpolator for resampling a stream of floats using 4-point lagrange interpolation. | ||||
@@ -33,8 +33,8 @@ | |||||
*/ | */ | ||||
//============================================================================== | //============================================================================== | ||||
template<typename FloatType> | |||||
class JUCE_API LinearSmoothedValue | |||||
template <typename FloatType> | |||||
class LinearSmoothedValue | |||||
{ | { | ||||
public: | public: | ||||
/** Constructor. */ | /** Constructor. */ | ||||
@@ -59,7 +59,6 @@ public: | |||||
countdown = 0; | countdown = 0; | ||||
} | } | ||||
//============================================================================== | |||||
/** Set a new target value. */ | /** Set a new target value. */ | ||||
void setValue (FloatType newValue) noexcept | void setValue (FloatType newValue) noexcept | ||||
{ | { | ||||
@@ -75,7 +74,6 @@ public: | |||||
} | } | ||||
} | } | ||||
//============================================================================== | |||||
/** Compute the next value. */ | /** Compute the next value. */ | ||||
FloatType getNextValue() noexcept | FloatType getNextValue() noexcept | ||||
{ | { | ||||
@@ -87,6 +85,18 @@ public: | |||||
return currentValue; | return currentValue; | ||||
} | } | ||||
/** Returns true if the current value is currently being interpolated. */ | |||||
bool isSmoothing() const noexcept | |||||
{ | |||||
return countdown > 0; | |||||
} | |||||
/** Returns the target value towards which the smoothed value is currently moving. */ | |||||
FloatType getTargetValue() const noexcept | |||||
{ | |||||
return target; | |||||
} | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
FloatType currentValue, target, step; | FloatType currentValue, target, step; | ||||
@@ -31,6 +31,8 @@ | |||||
#error "Incorrect use of JUCE cpp file" | #error "Incorrect use of JUCE cpp file" | ||||
#endif | #endif | ||||
#include "AppConfig.h" | |||||
#include "juce_audio_basics.h" | #include "juce_audio_basics.h" | ||||
#if JUCE_MINGW && ! defined (__SSE2__) | #if JUCE_MINGW && ! defined (__SSE2__) | ||||
@@ -67,6 +69,13 @@ | |||||
#define JUCE_USE_ARM_NEON 1 | #define JUCE_USE_ARM_NEON 1 | ||||
#endif | #endif | ||||
#if TARGET_IPHONE_SIMULATOR | |||||
#ifdef JUCE_USE_ARM_NEON | |||||
#undef JUCE_USE_ARM_NEON | |||||
#endif | |||||
#define JUCE_USE_ARM_NEON 0 | |||||
#endif | |||||
#if JUCE_USE_ARM_NEON | #if JUCE_USE_ARM_NEON | ||||
#include <arm_neon.h> | #include <arm_neon.h> | ||||
#endif | #endif | ||||
@@ -76,6 +85,7 @@ namespace juce | |||||
#include "buffers/juce_AudioDataConverters.cpp" | #include "buffers/juce_AudioDataConverters.cpp" | ||||
#include "buffers/juce_FloatVectorOperations.cpp" | #include "buffers/juce_FloatVectorOperations.cpp" | ||||
#include "buffers/juce_AudioChannelSet.cpp" | |||||
#include "effects/juce_IIRFilter.cpp" | #include "effects/juce_IIRFilter.cpp" | ||||
#include "effects/juce_IIRFilterOld.cpp" | #include "effects/juce_IIRFilterOld.cpp" | ||||
#include "effects/juce_LagrangeInterpolator.cpp" | #include "effects/juce_LagrangeInterpolator.cpp" | ||||
@@ -22,12 +22,37 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
/******************************************************************************* | |||||
The block below describes the properties of this module, and is read by | |||||
the Projucer to automatically generate project code that uses it. | |||||
For details about the syntax and how to create or use a module, see the | |||||
JUCE Module Format.txt file. | |||||
BEGIN_JUCE_MODULE_DECLARATION | |||||
ID: juce_audio_basics | |||||
vendor: juce | |||||
version: 4.3.0 | |||||
name: JUCE audio and MIDI data classes | |||||
description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | |||||
website: http://www.juce.com/juce | |||||
license: GPL/Commercial | |||||
dependencies: juce_core | |||||
OSXFrameworks: Accelerate | |||||
iOSFrameworks: Accelerate | |||||
END_JUCE_MODULE_DECLARATION | |||||
*******************************************************************************/ | |||||
#ifndef JUCE_AUDIO_BASICS_H_INCLUDED | #ifndef JUCE_AUDIO_BASICS_H_INCLUDED | ||||
#define JUCE_AUDIO_BASICS_H_INCLUDED | #define JUCE_AUDIO_BASICS_H_INCLUDED | ||||
#include "../juce_core/juce_core.h" | |||||
#include "juce_core/juce_core.h" | |||||
//============================================================================== | |||||
namespace juce | namespace juce | ||||
{ | { | ||||
@@ -37,6 +62,7 @@ namespace juce | |||||
#include "buffers/juce_AudioDataConverters.h" | #include "buffers/juce_AudioDataConverters.h" | ||||
#include "buffers/juce_FloatVectorOperations.h" | #include "buffers/juce_FloatVectorOperations.h" | ||||
#include "buffers/juce_AudioSampleBuffer.h" | #include "buffers/juce_AudioSampleBuffer.h" | ||||
#include "buffers/juce_AudioChannelSet.h" | |||||
#include "effects/juce_Decibels.h" | #include "effects/juce_Decibels.h" | ||||
#include "effects/juce_IIRFilter.h" | #include "effects/juce_IIRFilter.h" | ||||
#include "effects/juce_IIRFilterOld.h" | #include "effects/juce_IIRFilterOld.h" | ||||
@@ -248,7 +248,7 @@ bool MidiFile::readFrom (InputStream& sourceStream) | |||||
clear(); | clear(); | ||||
MemoryBlock data; | MemoryBlock data; | ||||
const int maxSensibleMidiFileSize = 2 * 1024 * 1024; | |||||
const int maxSensibleMidiFileSize = 200 * 1024 * 1024; | |||||
// (put a sanity-check on the file size, as midi files are generally small) | // (put a sanity-check on the file size, as midi files are generally small) | ||||
if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | ||||
@@ -33,11 +33,23 @@ namespace MidiHelpers | |||||
{ | { | ||||
return (uint8) jlimit (0, 127, v); | return (uint8) jlimit (0, 127, v); | ||||
} | } | ||||
} | |||||
inline uint8 floatVelocityToByte (const float v) noexcept | |||||
{ | |||||
return validVelocity (roundToInt (v * 127.0f)); | |||||
} | |||||
//============================================================================== | |||||
uint8 MidiMessage::floatValueToMidiByte (const float v) noexcept | |||||
{ | |||||
return MidiHelpers::validVelocity (roundToInt (v * 127.0f)); | |||||
} | |||||
uint16 MidiMessage::pitchbendToPitchwheelPos (const float pitchbend, | |||||
const float pitchbendRange) noexcept | |||||
{ | |||||
// can't translate a pitchbend value that is outside of the given range! | |||||
jassert (std::abs (pitchbend) <= pitchbendRange); | |||||
return static_cast<uint16> (pitchbend > 0.0f | |||||
? jmap (pitchbend, 0.0f, pitchbendRange, 8192.0f, 16383.0f) | |||||
: jmap (pitchbend, -pitchbendRange, 0.0f, 0.0f, 8192.0f)); | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -84,25 +96,24 @@ int MidiMessage::getMessageLengthFromFirstByte (const uint8 firstByte) noexcept | |||||
MidiMessage::MidiMessage() noexcept | MidiMessage::MidiMessage() noexcept | ||||
: timeStamp (0), size (2) | : timeStamp (0), size (2) | ||||
{ | { | ||||
preallocatedData.asBytes[0] = 0xf0; | |||||
preallocatedData.asBytes[1] = 0xf7; | |||||
packedData.asBytes[0] = 0xf0; | |||||
packedData.asBytes[1] = 0xf7; | |||||
} | } | ||||
MidiMessage::MidiMessage (const void* const d, const int dataSize, const double t) | MidiMessage::MidiMessage (const void* const d, const int dataSize, const double t) | ||||
: timeStamp (t), | |||||
size (dataSize) | |||||
: timeStamp (t), size (dataSize) | |||||
{ | { | ||||
jassert (dataSize > 0); | jassert (dataSize > 0); | ||||
memcpy (allocateSpace (dataSize), d, (size_t) dataSize); | |||||
// this checks that the length matches the data.. | |||||
jassert (dataSize > 3 || *(uint8*)d >= 0xf0 || getMessageLengthFromFirstByte (*(uint8*)d) == size); | |||||
// check that the length matches the data.. | |||||
jassert (size > 3 || *(uint8*)d >= 0xf0 || getMessageLengthFromFirstByte (*(uint8*)d) == size); | |||||
memcpy (allocateSpace (dataSize), d, (size_t) dataSize); | |||||
} | } | ||||
MidiMessage::MidiMessage (const int byte1, const double t) noexcept | MidiMessage::MidiMessage (const int byte1, const double t) noexcept | ||||
: timeStamp (t), size (1) | : timeStamp (t), size (1) | ||||
{ | { | ||||
preallocatedData.asBytes[0] = (uint8) byte1; | |||||
packedData.asBytes[0] = (uint8) byte1; | |||||
// check that the length matches the data.. | // check that the length matches the data.. | ||||
jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 1); | jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 1); | ||||
@@ -111,8 +122,8 @@ MidiMessage::MidiMessage (const int byte1, const double t) noexcept | |||||
MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noexcept | MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noexcept | ||||
: timeStamp (t), size (2) | : timeStamp (t), size (2) | ||||
{ | { | ||||
preallocatedData.asBytes[0] = (uint8) byte1; | |||||
preallocatedData.asBytes[1] = (uint8) byte2; | |||||
packedData.asBytes[0] = (uint8) byte1; | |||||
packedData.asBytes[1] = (uint8) byte2; | |||||
// check that the length matches the data.. | // check that the length matches the data.. | ||||
jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 2); | jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 2); | ||||
@@ -121,9 +132,9 @@ MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noex | |||||
MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, const double t) noexcept | MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, const double t) noexcept | ||||
: timeStamp (t), size (3) | : timeStamp (t), size (3) | ||||
{ | { | ||||
preallocatedData.asBytes[0] = (uint8) byte1; | |||||
preallocatedData.asBytes[1] = (uint8) byte2; | |||||
preallocatedData.asBytes[2] = (uint8) byte3; | |||||
packedData.asBytes[0] = (uint8) byte1; | |||||
packedData.asBytes[1] = (uint8) byte2; | |||||
packedData.asBytes[2] = (uint8) byte3; | |||||
// check that the length matches the data.. | // check that the length matches the data.. | ||||
jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 3); | jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 3); | ||||
@@ -132,29 +143,19 @@ MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, con | |||||
MidiMessage::MidiMessage (const MidiMessage& other) | MidiMessage::MidiMessage (const MidiMessage& other) | ||||
: timeStamp (other.timeStamp), size (other.size) | : timeStamp (other.timeStamp), size (other.size) | ||||
{ | { | ||||
if (other.allocatedData != nullptr) | |||||
{ | |||||
allocatedData.malloc ((size_t) size); | |||||
memcpy (allocatedData, other.allocatedData, (size_t) size); | |||||
} | |||||
if (isHeapAllocated()) | |||||
memcpy (allocateSpace (size), other.getData(), (size_t) size); | |||||
else | else | ||||
{ | |||||
preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||||
} | |||||
packedData.allocatedData = other.packedData.allocatedData; | |||||
} | } | ||||
MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) | MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) | ||||
: timeStamp (newTimeStamp), size (other.size) | : timeStamp (newTimeStamp), size (other.size) | ||||
{ | { | ||||
if (other.allocatedData != nullptr) | |||||
{ | |||||
allocatedData.malloc ((size_t) size); | |||||
memcpy (allocatedData, other.allocatedData, (size_t) size); | |||||
} | |||||
if (isHeapAllocated()) | |||||
memcpy (allocateSpace (size), other.getData(), (size_t) size); | |||||
else | else | ||||
{ | |||||
preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||||
} | |||||
packedData.allocatedData = other.packedData.allocatedData; | |||||
} | } | ||||
MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, | MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, | ||||
@@ -229,16 +230,15 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
preallocatedData.asInt32 = 0; | |||||
size = getMessageLengthFromFirstByte ((uint8) byte); | size = getMessageLengthFromFirstByte ((uint8) byte); | ||||
preallocatedData.asBytes[0] = (uint8) byte; | |||||
packedData.asBytes[0] = (uint8) byte; | |||||
if (size > 1) | if (size > 1) | ||||
{ | { | ||||
preallocatedData.asBytes[1] = src[0]; | |||||
packedData.asBytes[1] = src[0]; | |||||
if (size > 2) | if (size > 2) | ||||
preallocatedData.asBytes[2] = src[1]; | |||||
packedData.asBytes[2] = src[1]; | |||||
} | } | ||||
} | } | ||||
@@ -246,7 +246,7 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
preallocatedData.asInt32 = 0; | |||||
packedData.allocatedData = nullptr; | |||||
size = 0; | size = 0; | ||||
} | } | ||||
} | } | ||||
@@ -255,19 +255,25 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& other) | |||||
{ | { | ||||
if (this != &other) | if (this != &other) | ||||
{ | { | ||||
timeStamp = other.timeStamp; | |||||
size = other.size; | |||||
if (other.allocatedData != nullptr) | |||||
if (other.isHeapAllocated()) | |||||
{ | { | ||||
allocatedData.malloc ((size_t) size); | |||||
memcpy (allocatedData, other.allocatedData, (size_t) size); | |||||
if (isHeapAllocated()) | |||||
packedData.allocatedData = static_cast<uint8*> (std::realloc (packedData.allocatedData, (size_t) other.size)); | |||||
else | |||||
packedData.allocatedData = static_cast<uint8*> (std::malloc ((size_t) other.size)); | |||||
memcpy (packedData.allocatedData, other.packedData.allocatedData, (size_t) other.size); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
allocatedData.free(); | |||||
preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||||
if (isHeapAllocated()) | |||||
std::free (packedData.allocatedData); | |||||
packedData.allocatedData = other.packedData.allocatedData; | |||||
} | } | ||||
timeStamp = other.timeStamp; | |||||
size = other.size; | |||||
} | } | ||||
return *this; | return *this; | ||||
@@ -277,36 +283,61 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& other) | |||||
MidiMessage::MidiMessage (MidiMessage&& other) noexcept | MidiMessage::MidiMessage (MidiMessage&& other) noexcept | ||||
: timeStamp (other.timeStamp), size (other.size) | : timeStamp (other.timeStamp), size (other.size) | ||||
{ | { | ||||
if (other.allocatedData != nullptr) | |||||
allocatedData.swapWith (other.allocatedData); | |||||
else | |||||
preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||||
packedData.allocatedData = other.packedData.allocatedData; | |||||
other.size = 0; | |||||
} | } | ||||
MidiMessage& MidiMessage::operator= (MidiMessage&& other) noexcept | MidiMessage& MidiMessage::operator= (MidiMessage&& other) noexcept | ||||
{ | { | ||||
jassert (this != &other); // shouldn't be possible | |||||
packedData.allocatedData = other.packedData.allocatedData; | |||||
timeStamp = other.timeStamp; | timeStamp = other.timeStamp; | ||||
size = other.size; | size = other.size; | ||||
allocatedData.swapWith (other.allocatedData); | |||||
preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||||
other.size = 0; | |||||
return *this; | return *this; | ||||
} | } | ||||
#endif | #endif | ||||
MidiMessage::~MidiMessage() {} | |||||
MidiMessage::~MidiMessage() noexcept | |||||
{ | |||||
if (isHeapAllocated()) | |||||
std::free (packedData.allocatedData); | |||||
} | |||||
uint8* MidiMessage::allocateSpace (int bytes) | uint8* MidiMessage::allocateSpace (int bytes) | ||||
{ | { | ||||
if (bytes > 4) | |||||
if (bytes > (int) sizeof (packedData)) | |||||
{ | |||||
uint8* d = static_cast<uint8*> (std::malloc ((size_t) bytes)); | |||||
packedData.allocatedData = d; | |||||
return d; | |||||
} | |||||
return packedData.asBytes; | |||||
} | |||||
String MidiMessage::getDescription() const | |||||
{ | |||||
if (isNoteOn()) return "Note on " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); | |||||
if (isNoteOff()) return "Note off " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); | |||||
if (isProgramChange()) return "Program change " + String (getProgramChangeNumber()) + " Channel " + String (getChannel()); | |||||
if (isPitchWheel()) return "Pitch wheel " + String (getPitchWheelValue()) + " Channel " + String (getChannel()); | |||||
if (isAftertouch()) return "Aftertouch " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + ": " + String (getAfterTouchValue()) + " Channel " + String (getChannel()); | |||||
if (isChannelPressure()) return "Channel pressure " + String (getChannelPressureValue()) + " Channel " + String (getChannel()); | |||||
if (isAllNotesOff()) return "All notes off Channel " + String (getChannel()); | |||||
if (isAllSoundOff()) return "All sound off Channel " + String (getChannel()); | |||||
if (isMetaEvent()) return "Meta event"; | |||||
if (isController()) | |||||
{ | { | ||||
allocatedData.malloc ((size_t) bytes); | |||||
return allocatedData; | |||||
String name (MidiMessage::getControllerName (getControllerNumber())); | |||||
if (name.isEmpty()) | |||||
name = String (getControllerNumber()); | |||||
return "Controller " + name + ": " + String (getControllerValue()) + " Channel " + String (getChannel()); | |||||
} | } | ||||
return preallocatedData.asBytes; | |||||
return String::toHexString (getRawData(), getRawDataSize()); | |||||
} | } | ||||
int MidiMessage::getChannel() const noexcept | int MidiMessage::getChannel() const noexcept | ||||
@@ -391,7 +422,7 @@ float MidiMessage::getFloatVelocity() const noexcept | |||||
void MidiMessage::setVelocity (const float newVelocity) noexcept | void MidiMessage::setVelocity (const float newVelocity) noexcept | ||||
{ | { | ||||
if (isNoteOnOrOff()) | if (isNoteOnOrOff()) | ||||
getData()[2] = MidiHelpers::floatVelocityToByte (newVelocity); | |||||
getData()[2] = floatValueToMidiByte (newVelocity); | |||||
} | } | ||||
void MidiMessage::multiplyVelocity (const float scaleFactor) noexcept | void MidiMessage::multiplyVelocity (const float scaleFactor) noexcept | ||||
@@ -538,7 +569,7 @@ MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const | |||||
MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) noexcept | MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) noexcept | ||||
{ | { | ||||
return noteOn (channel, noteNumber, MidiHelpers::floatVelocityToByte (velocity)); | |||||
return noteOn (channel, noteNumber, floatValueToMidiByte (velocity)); | |||||
} | } | ||||
MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 velocity) noexcept | MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 velocity) noexcept | ||||
@@ -552,7 +583,7 @@ MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 | |||||
MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, float velocity) noexcept | MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, float velocity) noexcept | ||||
{ | { | ||||
return noteOff (channel, noteNumber, MidiHelpers::floatVelocityToByte (velocity)); | |||||
return noteOff (channel, noteNumber, floatValueToMidiByte (velocity)); | |||||
} | } | ||||
MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) noexcept | MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) noexcept | ||||
@@ -697,9 +728,10 @@ MidiMessage MidiMessage::textMetaEvent (int type, StringRef text) | |||||
header[--n] = 0xff; | header[--n] = 0xff; | ||||
const size_t headerLen = sizeof (header) - n; | const size_t headerLen = sizeof (header) - n; | ||||
const int totalSize = (int) (headerLen + textSize); | |||||
uint8* const dest = result.allocateSpace ((int) (headerLen + textSize)); | |||||
result.size = (int) (headerLen + textSize); | |||||
uint8* const dest = result.allocateSpace (totalSize); | |||||
result.size = totalSize; | |||||
memcpy (dest, header + n, headerLen); | memcpy (dest, header + n, headerLen); | ||||
memcpy (dest + headerLen, text.text.getAddress(), textSize); | memcpy (dest + headerLen, text.text.getAddress(), textSize); | ||||
@@ -816,7 +848,7 @@ bool MidiMessage::isKeySignatureMetaEvent() const noexcept | |||||
int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const noexcept | int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const noexcept | ||||
{ | { | ||||
return (int) getMetaEventData()[0]; | |||||
return (int) (int8) getMetaEventData()[0]; | |||||
} | } | ||||
bool MidiMessage::isKeySignatureMajorKey() const noexcept | bool MidiMessage::isKeySignatureMajorKey() const noexcept | ||||
@@ -985,7 +1017,7 @@ String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctav | |||||
return String(); | return String(); | ||||
} | } | ||||
double MidiMessage::getMidiNoteInHertz (int noteNumber, const double frequencyOfA) noexcept | |||||
double MidiMessage::getMidiNoteInHertz (const int noteNumber, const double frequencyOfA) noexcept | |||||
{ | { | ||||
return frequencyOfA * pow (2.0, (noteNumber - 69) / 12.0); | return frequencyOfA * pow (2.0, (noteNumber - 69) / 12.0); | ||||
} | } | ||||
@@ -104,7 +104,7 @@ public: | |||||
MidiMessage (const MidiMessage&, double newTimeStamp); | MidiMessage (const MidiMessage&, double newTimeStamp); | ||||
/** Destructor. */ | /** Destructor. */ | ||||
~MidiMessage(); | |||||
~MidiMessage() noexcept; | |||||
/** Copies this message from another one. */ | /** Copies this message from another one. */ | ||||
MidiMessage& operator= (const MidiMessage& other); | MidiMessage& operator= (const MidiMessage& other); | ||||
@@ -118,13 +118,19 @@ public: | |||||
/** Returns a pointer to the raw midi data. | /** Returns a pointer to the raw midi data. | ||||
@see getRawDataSize | @see getRawDataSize | ||||
*/ | */ | ||||
const uint8* getRawData() const noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; } | |||||
const uint8* getRawData() const noexcept { return getData(); } | |||||
/** Returns the number of bytes of data in the message. | /** Returns the number of bytes of data in the message. | ||||
@see getRawData | @see getRawData | ||||
*/ | */ | ||||
int getRawDataSize() const noexcept { return size; } | int getRawDataSize() const noexcept { return size; } | ||||
//============================================================================== | |||||
/** Returns a human-readable description of the midi message as a string, | |||||
for example "Note On C#3 Velocity 120 Channel 1". | |||||
*/ | |||||
String getDescription() const; | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns the timestamp associated with this message. | /** Returns the timestamp associated with this message. | ||||
@@ -845,7 +851,7 @@ public: | |||||
The value passed in must be 0x80 or higher. | The value passed in must be 0x80 or higher. | ||||
*/ | */ | ||||
static int getMessageLengthFromFirstByte (const uint8 firstByte) noexcept; | |||||
static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept; | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns the name of a midi note number. | /** Returns the name of a midi note number. | ||||
@@ -872,7 +878,7 @@ public: | |||||
The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. | The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. | ||||
@see getMidiNoteName | @see getMidiNoteName | ||||
*/ | */ | ||||
static double getMidiNoteInHertz (int noteNumber, const double frequencyOfA = 440.0) noexcept; | |||||
static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept; | |||||
/** Returns true if the given midi note number is a black key. */ | /** Returns true if the given midi note number is a black key. */ | ||||
static bool isMidiNoteBlack (int noteNumber) noexcept; | static bool isMidiNoteBlack (int noteNumber) noexcept; | ||||
@@ -899,21 +905,29 @@ public: | |||||
*/ | */ | ||||
static const char* getControllerName (int controllerNumber); | static const char* getControllerName (int controllerNumber); | ||||
/** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */ | |||||
static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept; | |||||
/** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */ | |||||
static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones, | |||||
float pitchbendRangeInSemitones) noexcept; | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
double timeStamp; | |||||
HeapBlock<uint8> allocatedData; | |||||
int size; | |||||
#ifndef DOXYGEN | #ifndef DOXYGEN | ||||
union | |||||
union PackedData | |||||
{ | { | ||||
uint8 asBytes[4]; | |||||
uint32 asInt32; | |||||
} preallocatedData; | |||||
uint8* allocatedData; | |||||
uint8 asBytes[sizeof (uint8*)]; | |||||
}; | |||||
PackedData packedData; | |||||
double timeStamp; | |||||
int size; | |||||
#endif | #endif | ||||
inline uint8* getData() noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; } | |||||
inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } | |||||
inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; } | |||||
uint8* allocateSpace (int); | uint8* allocateSpace (int); | ||||
}; | }; | ||||
@@ -80,7 +80,7 @@ int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const noexcep | |||||
return -1; | return -1; | ||||
} | } | ||||
int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const noexcept | |||||
int MidiMessageSequence::getIndexOf (const MidiEventHolder* const event) const noexcept | |||||
{ | { | ||||
return list.indexOf (event); | return list.indexOf (event); | ||||
} | } | ||||
@@ -48,6 +48,18 @@ public: | |||||
/** Replaces this sequence with another one. */ | /** Replaces this sequence with another one. */ | ||||
MidiMessageSequence& operator= (const MidiMessageSequence&); | MidiMessageSequence& operator= (const MidiMessageSequence&); | ||||
#if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
MidiMessageSequence (MidiMessageSequence&& other) noexcept | |||||
: list (static_cast<OwnedArray<MidiEventHolder>&&> (other.list)) | |||||
{} | |||||
MidiMessageSequence& operator= (MidiMessageSequence&& other) noexcept | |||||
{ | |||||
list = static_cast<OwnedArray<MidiEventHolder>&&> (other.list); | |||||
return *this; | |||||
} | |||||
#endif | |||||
/** Destructor. */ | /** Destructor. */ | ||||
~MidiMessageSequence(); | ~MidiMessageSequence(); | ||||
@@ -109,7 +121,7 @@ public: | |||||
int getIndexOfMatchingKeyUp (int index) const noexcept; | int getIndexOfMatchingKeyUp (int index) const noexcept; | ||||
/** Returns the index of an event. */ | /** Returns the index of an event. */ | ||||
int getIndexOf (MidiEventHolder* event) const noexcept; | |||||
int getIndexOf (const MidiEventHolder* event) const noexcept; | |||||
/** Returns the index of the first event on or after the given timestamp. | /** Returns the index of the first event on or after the given timestamp. | ||||
If the time is beyond the end of the sequence, this will return the | If the time is beyond the end of the sequence, this will return the | ||||
@@ -22,7 +22,6 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
MidiRPNDetector::MidiRPNDetector() noexcept | MidiRPNDetector::MidiRPNDetector() noexcept | ||||
{ | { | ||||
} | } | ||||
@@ -55,7 +54,7 @@ void MidiRPNDetector::reset() noexcept | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
MidiRPNDetector::ChannelState::ChannelState () noexcept | |||||
MidiRPNDetector::ChannelState::ChannelState() noexcept | |||||
: parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false) | : parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false) | ||||
{ | { | ||||
} | } | ||||
@@ -39,6 +39,9 @@ MPEInstrument::MPEInstrument() noexcept | |||||
pressureDimension.value = &MPENote::pressure; | pressureDimension.value = &MPENote::pressure; | ||||
timbreDimension.value = &MPENote::timbre; | timbreDimension.value = &MPENote::timbre; | ||||
// the default value for pressure is 0, for all other dimension it is centre (= default MPEValue) | |||||
std::fill_n (pressureDimension.lastValueReceivedOnChannel, 16, MPEValue::minValue()); | |||||
legacyMode.isEnabled = false; | legacyMode.isEnabled = false; | ||||
legacyMode.pitchbendRange = 2; | legacyMode.pitchbendRange = 2; | ||||
legacyMode.channelRange = Range<int> (1, 17); | legacyMode.channelRange = Range<int> (1, 17); | ||||
@@ -271,22 +274,6 @@ void MPEInstrument::handleTimbreLSB (int midiChannel, int value) noexcept | |||||
lastTimbreLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value); | lastTimbreLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value); | ||||
} | } | ||||
//============================================================================== | |||||
MPEValue MPEInstrument::getInitialPitchbendForNoteOn (int midiChannel, int /*midiNoteNumber*/, MPEValue /*midiNoteOnVelocity*/) const | |||||
{ | |||||
return pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1]; | |||||
} | |||||
MPEValue MPEInstrument::getInitialPressureForNoteOn (int /*midiChannel*/, int /*midiNoteNumber*/, MPEValue midiNoteOnVelocity) const | |||||
{ | |||||
return midiNoteOnVelocity; | |||||
} | |||||
MPEValue MPEInstrument::getInitialTimbreForNoteOn (int midiChannel, int /*midiNoteNumber*/, MPEValue /*midiNoteOnVelocity*/) const | |||||
{ | |||||
return timbreDimension.lastValueReceivedOnChannel[midiChannel - 1]; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
void MPEInstrument::noteOn (int midiChannel, | void MPEInstrument::noteOn (int midiChannel, | ||||
int midiNoteNumber, | int midiNoteNumber, | ||||
@@ -298,9 +285,9 @@ void MPEInstrument::noteOn (int midiChannel, | |||||
MPENote newNote (midiChannel, | MPENote newNote (midiChannel, | ||||
midiNoteNumber, | midiNoteNumber, | ||||
midiNoteOnVelocity, | midiNoteOnVelocity, | ||||
getInitialPitchbendForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), | |||||
getInitialPressureForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), | |||||
getInitialTimbreForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), | |||||
getInitialValueForNewNote (midiChannel, pitchbendDimension), | |||||
getInitialValueForNewNote (midiChannel, pressureDimension), | |||||
getInitialValueForNewNote (midiChannel, timbreDimension), | |||||
isNoteChannelSustained[midiChannel - 1] ? MPENote::keyDownAndSustained : MPENote::keyDown); | isNoteChannelSustained[midiChannel - 1] ? MPENote::keyDownAndSustained : MPENote::keyDown); | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
@@ -324,7 +311,7 @@ void MPEInstrument::noteOff (int midiChannel, | |||||
int midiNoteNumber, | int midiNoteNumber, | ||||
MPEValue midiNoteOffVelocity) | MPEValue midiNoteOffVelocity) | ||||
{ | { | ||||
if (notes.empty() || ! isNoteChannel (midiChannel)) | |||||
if (notes.isEmpty() || ! isNoteChannel (midiChannel)) | |||||
return; | return; | ||||
const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
@@ -334,10 +321,11 @@ void MPEInstrument::noteOff (int midiChannel, | |||||
note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off; | note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off; | ||||
note->noteOffVelocity = midiNoteOffVelocity; | note->noteOffVelocity = midiNoteOffVelocity; | ||||
// last pitchbend and timbre values received for this note should not be re-used for | |||||
// last dimension values received for this note should not be re-used for | |||||
// any new notes, so reset them: | // any new notes, so reset them: | ||||
pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue(); | |||||
timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue(); | |||||
pressureDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::minValue(); | |||||
pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); | |||||
timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); | |||||
if (note->keyState == MPENote::off) | if (note->keyState == MPENote::off) | ||||
{ | { | ||||
@@ -370,12 +358,20 @@ void MPEInstrument::timbre (int midiChannel, MPEValue value) | |||||
updateDimension (midiChannel, timbreDimension, value); | updateDimension (midiChannel, timbreDimension, value); | ||||
} | } | ||||
MPEValue MPEInstrument::getInitialValueForNewNote (int midiChannel, MPEDimension& dimension) const | |||||
{ | |||||
if (getLastNotePlayedPtr (midiChannel) != nullptr) | |||||
return &dimension == &pressureDimension ? MPEValue::minValue() : MPEValue::centreValue(); | |||||
return dimension.lastValueReceivedOnChannel[midiChannel - 1]; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, MPEValue value) | void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, MPEValue value) | ||||
{ | { | ||||
dimension.lastValueReceivedOnChannel[midiChannel - 1] = value; | dimension.lastValueReceivedOnChannel[midiChannel - 1] = value; | ||||
if (notes.empty()) | |||||
if (notes.isEmpty()) | |||||
return; | return; | ||||
if (MPEZone* zone = zoneLayout.getZoneByMasterChannel (midiChannel)) | if (MPEZone* zone = zoneLayout.getZoneByMasterChannel (midiChannel)) | ||||
@@ -758,7 +754,7 @@ public: | |||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
expectEquals (test.noteAddedCallCounter, 1); | expectEquals (test.noteAddedCallCounter, 1); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
// note-off | // note-off | ||||
test.noteOff (3, 60, MPEValue::from7BitInt (33)); | test.noteOff (3, 60, MPEValue::from7BitInt (33)); | ||||
@@ -774,13 +770,13 @@ public: | |||||
// note off with non-matching note number shouldn't do anything | // note off with non-matching note number shouldn't do anything | ||||
test.noteOff (3, 61, MPEValue::from7BitInt (33)); | test.noteOff (3, 61, MPEValue::from7BitInt (33)); | ||||
expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteReleasedCallCounter, 0); | expectEquals (test.noteReleasedCallCounter, 0); | ||||
// note off with non-matching midi channel shouldn't do anything | // note off with non-matching midi channel shouldn't do anything | ||||
test.noteOff (2, 60, MPEValue::from7BitInt (33)); | test.noteOff (2, 60, MPEValue::from7BitInt (33)); | ||||
expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteReleasedCallCounter, 0); | expectEquals (test.noteReleasedCallCounter, 0); | ||||
} | } | ||||
{ | { | ||||
@@ -791,9 +787,9 @@ public: | |||||
test.noteOn (3, 1, MPEValue::from7BitInt (100)); | test.noteOn (3, 1, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 2, MPEValue::from7BitInt (100)); | test.noteOn (3, 2, MPEValue::from7BitInt (100)); | ||||
expectEquals (test.getNumPlayingNotes(), 3); | expectEquals (test.getNumPlayingNotes(), 3); | ||||
expectNote (test.getNote (3, 0), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 1), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 2), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 0), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 1), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 2), 100, 0, 8192, 64, MPENote::keyDown); | |||||
} | } | ||||
{ | { | ||||
// pathological case: second note-on for same note should retrigger it. | // pathological case: second note-on for same note should retrigger it. | ||||
@@ -802,7 +798,7 @@ public: | |||||
test.noteOn (3, 0, MPEValue::from7BitInt (100)); | test.noteOn (3, 0, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 0, MPEValue::from7BitInt (60)); | test.noteOn (3, 0, MPEValue::from7BitInt (60)); | ||||
expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
expectNote (test.getNote (3, 0), 60, 60, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 0), 60, 0, 8192, 64, MPENote::keyDown); | |||||
} | } | ||||
} | } | ||||
@@ -844,42 +840,42 @@ public: | |||||
// sustain pedal on per-note channel shouldn't do anything. | // sustain pedal on per-note channel shouldn't do anything. | ||||
test.sustainPedal (3, true); | test.sustainPedal (3, true); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 0); | expectEquals (test.noteKeyStateChangedCallCounter, 0); | ||||
// sustain pedal on non-zone channel shouldn't do anything either. | // sustain pedal on non-zone channel shouldn't do anything either. | ||||
test.sustainPedal (1, true); | test.sustainPedal (1, true); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 0); | expectEquals (test.noteKeyStateChangedCallCounter, 0); | ||||
// sustain pedal on master channel should sustain notes on *that* zone. | // sustain pedal on master channel should sustain notes on *that* zone. | ||||
test.sustainPedal (2, true); | test.sustainPedal (2, true); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 1); | expectEquals (test.noteKeyStateChangedCallCounter, 1); | ||||
// release | // release | ||||
test.sustainPedal (2, false); | test.sustainPedal (2, false); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 2); | expectEquals (test.noteKeyStateChangedCallCounter, 2); | ||||
// should also sustain new notes added after the press | // should also sustain new notes added after the press | ||||
test.sustainPedal (2, true); | test.sustainPedal (2, true); | ||||
expectEquals (test.noteKeyStateChangedCallCounter, 3); | expectEquals (test.noteKeyStateChangedCallCounter, 3); | ||||
test.noteOn (4, 51, MPEValue::from7BitInt (100)); | test.noteOn (4, 51, MPEValue::from7BitInt (100)); | ||||
expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||||
expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 3); | expectEquals (test.noteKeyStateChangedCallCounter, 3); | ||||
// ...but only if that sustain came on the master channel of that zone! | // ...but only if that sustain came on the master channel of that zone! | ||||
test.sustainPedal (11, true); | test.sustainPedal (11, true); | ||||
test.noteOn (11, 52, MPEValue::from7BitInt (100)); | test.noteOn (11, 52, MPEValue::from7BitInt (100)); | ||||
expectNote (test.getNote (11, 52), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (11, 52), 100, 0, 8192, 64, MPENote::keyDown); | |||||
test.noteOff (11, 52, MPEValue::from7BitInt (100)); | test.noteOff (11, 52, MPEValue::from7BitInt (100)); | ||||
expectEquals (test.noteReleasedCallCounter, 1); | expectEquals (test.noteReleasedCallCounter, 1); | ||||
@@ -890,8 +886,8 @@ public: | |||||
expectEquals (test.getNumPlayingNotes(), 2); | expectEquals (test.getNumPlayingNotes(), 2); | ||||
expectEquals (test.noteReleasedCallCounter, 2); | expectEquals (test.noteReleasedCallCounter, 2); | ||||
expectEquals (test.noteKeyStateChangedCallCounter, 5); | expectEquals (test.noteKeyStateChangedCallCounter, 5); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::sustained); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::sustained); | |||||
// notes should be turned off when pedal is released | // notes should be turned off when pedal is released | ||||
test.sustainPedal (2, false); | test.sustainPedal (2, false); | ||||
@@ -908,26 +904,26 @@ public: | |||||
// sostenuto pedal on per-note channel shouldn't do anything. | // sostenuto pedal on per-note channel shouldn't do anything. | ||||
test.sostenutoPedal (3, true); | test.sostenutoPedal (3, true); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 0); | expectEquals (test.noteKeyStateChangedCallCounter, 0); | ||||
// sostenuto pedal on non-zone channel shouldn't do anything either. | // sostenuto pedal on non-zone channel shouldn't do anything either. | ||||
test.sostenutoPedal (1, true); | test.sostenutoPedal (1, true); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 0); | expectEquals (test.noteKeyStateChangedCallCounter, 0); | ||||
// sostenuto pedal on master channel should sustain notes on *that* zone. | // sostenuto pedal on master channel should sustain notes on *that* zone. | ||||
test.sostenutoPedal (2, true); | test.sostenutoPedal (2, true); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 1); | expectEquals (test.noteKeyStateChangedCallCounter, 1); | ||||
// release | // release | ||||
test.sostenutoPedal (2, false); | test.sostenutoPedal (2, false); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 2); | expectEquals (test.noteKeyStateChangedCallCounter, 2); | ||||
// should only sustain notes turned on *before* the press (difference to sustain pedal) | // should only sustain notes turned on *before* the press (difference to sustain pedal) | ||||
@@ -935,9 +931,9 @@ public: | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 3); | expectEquals (test.noteKeyStateChangedCallCounter, 3); | ||||
test.noteOn (4, 51, MPEValue::from7BitInt (100)); | test.noteOn (4, 51, MPEValue::from7BitInt (100)); | ||||
expectEquals (test.getNumPlayingNotes(), 3); | expectEquals (test.getNumPlayingNotes(), 3); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||||
expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||||
expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 3); | expectEquals (test.noteKeyStateChangedCallCounter, 3); | ||||
// note-off should not turn off sustained notes inside the same zone, | // note-off should not turn off sustained notes inside the same zone, | ||||
@@ -946,7 +942,7 @@ public: | |||||
test.noteOff (4, 51, MPEValue::from7BitInt (100)); | test.noteOff (4, 51, MPEValue::from7BitInt (100)); | ||||
test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected! | test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected! | ||||
expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
expectEquals (test.noteReleasedCallCounter, 2); | expectEquals (test.noteReleasedCallCounter, 2); | ||||
expectEquals (test.noteKeyStateChangedCallCounter, 4); | expectEquals (test.noteKeyStateChangedCallCounter, 4); | ||||
@@ -1047,22 +1043,22 @@ public: | |||||
// applying pressure on a per-note channel should modulate one note | // applying pressure on a per-note channel should modulate one note | ||||
test.pressure (3, MPEValue::from7BitInt (33)); | test.pressure (3, MPEValue::from7BitInt (33)); | ||||
expectNote (test.getNote (3, 60), 100, 33, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 60), 100, 33, 8192, 64, MPENote::keyDown); | ||||
expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
// applying pressure on a master channel should modulate all notes in this zone | // applying pressure on a master channel should modulate all notes in this zone | ||||
test.pressure (2, MPEValue::from7BitInt (44)); | test.pressure (2, MPEValue::from7BitInt (44)); | ||||
expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); | ||||
expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); | ||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.notePressureChangedCallCounter, 3); | expectEquals (test.notePressureChangedCallCounter, 3); | ||||
// applying pressure on an unrelated channel should be ignored | // applying pressure on an unrelated channel should be ignored | ||||
test.pressure (1, MPEValue::from7BitInt (55)); | test.pressure (1, MPEValue::from7BitInt (55)); | ||||
expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); | ||||
expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); | ||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.notePressureChangedCallCounter, 3); | expectEquals (test.notePressureChangedCallCounter, 3); | ||||
} | } | ||||
{ | { | ||||
@@ -1073,7 +1069,7 @@ public: | |||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.pressure (3, MPEValue::from7BitInt (66)); | test.pressure (3, MPEValue::from7BitInt (66)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 66, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 61), 100, 66, 8192, 64, MPENote::keyDown); | ||||
expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
} | } | ||||
@@ -1091,6 +1087,49 @@ public: | |||||
expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); | ||||
expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
} | } | ||||
{ | |||||
UnitTestInstrument test; | |||||
test.setZoneLayout (testLayout); | |||||
// if no pressure is sent before note-on, default = 0 should be used | |||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
} | |||||
{ | |||||
UnitTestInstrument test; | |||||
test.setZoneLayout (testLayout); | |||||
// if pressure is sent before note-on, use that | |||||
test.pressure (3, MPEValue::from7BitInt (77)); | |||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||||
expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); | |||||
} | |||||
{ | |||||
UnitTestInstrument test; | |||||
test.setZoneLayout (testLayout); | |||||
// if pressure is sent before note-on, but it belonged to another note | |||||
// on the same channel that has since been turned off, use default = 0 | |||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||||
test.pressure (3, MPEValue::from7BitInt (77)); | |||||
test.noteOff (3, 61, MPEValue::from7BitInt (100)); | |||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
} | |||||
{ | |||||
UnitTestInstrument test; | |||||
test.setZoneLayout (testLayout); | |||||
// edge case: two notes on the same channel simultaneously. the second one should use | |||||
// pressure = 0 initially but then react to additional pressure messages | |||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||||
test.pressure (3, MPEValue::from7BitInt (77)); | |||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
test.pressure (3, MPEValue::from7BitInt (78)); | |||||
expectNote (test.getNote (3, 60), 100, 78, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 77, 8192, 64, MPENote::keyDown); | |||||
} | |||||
} | } | ||||
beginTest ("pitchbend"); | beginTest ("pitchbend"); | ||||
@@ -1105,9 +1144,9 @@ public: | |||||
// applying pitchbend on a per-note channel should modulate one note | // applying pitchbend on a per-note channel should modulate one note | ||||
test.pitchbend (3, MPEValue::from14BitInt (1111)); | test.pitchbend (3, MPEValue::from14BitInt (1111)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
// applying pitchbend on a master channel should be ignored for the | // applying pitchbend on a master channel should be ignored for the | ||||
@@ -1115,16 +1154,16 @@ public: | |||||
// Note: noteChanged will be called anyway for notes in that zone | // Note: noteChanged will be called anyway for notes in that zone | ||||
// because the total pitchbend for those notes has changed | // because the total pitchbend for those notes has changed | ||||
test.pitchbend (2, MPEValue::from14BitInt (2222)); | test.pitchbend (2, MPEValue::from14BitInt (2222)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.notePitchbendChangedCallCounter, 3); | expectEquals (test.notePitchbendChangedCallCounter, 3); | ||||
// applying pitchbend on an unrelated channel should do nothing. | // applying pitchbend on an unrelated channel should do nothing. | ||||
test.pitchbend (1, MPEValue::from14BitInt (3333)); | test.pitchbend (1, MPEValue::from14BitInt (3333)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.notePitchbendChangedCallCounter, 3); | expectEquals (test.notePitchbendChangedCallCounter, 3); | ||||
} | } | ||||
{ | { | ||||
@@ -1135,8 +1174,8 @@ public: | |||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (3, MPEValue::from14BitInt (4444)); | test.pitchbend (3, MPEValue::from14BitInt (4444)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 100, 4444, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 4444, 64, MPENote::keyDown); | |||||
expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1150,7 +1189,7 @@ public: | |||||
test.noteOff (3, 61, MPEValue::from7BitInt (100)); | test.noteOff (3, 61, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (3, MPEValue::from14BitInt (5555)); | test.pitchbend (3, MPEValue::from14BitInt (5555)); | ||||
expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
expectNote (test.getNote (3, 60), 100, 100, 5555, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); | |||||
expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1169,14 +1208,14 @@ public: | |||||
test.sustainPedal (2, true); | test.sustainPedal (2, true); | ||||
test.noteOff (3, 60, MPEValue::from7BitInt (64)); | test.noteOff (3, 60, MPEValue::from7BitInt (64)); | ||||
expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
expectEquals (test.noteKeyStateChangedCallCounter, 2); | expectEquals (test.noteKeyStateChangedCallCounter, 2); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (3, MPEValue::from14BitInt (6666)); | test.pitchbend (3, MPEValue::from14BitInt (6666)); | ||||
expectEquals (test.getNumPlayingNotes(), 2); | expectEquals (test.getNumPlayingNotes(), 2); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
expectNote (test.getNote (3, 61), 100, 100, 6666, 64, MPENote::keyDownAndSustained); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
expectNote (test.getNote (3, 61), 100, 0, 6666, 64, MPENote::keyDownAndSustained); | |||||
expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1193,11 +1232,11 @@ public: | |||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (3, MPEValue::from14BitInt (5555)); | test.pitchbend (3, MPEValue::from14BitInt (5555)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 5555, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); | |||||
test.noteOff (3, 60, MPEValue::from7BitInt (100)); | test.noteOff (3, 60, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
} | } | ||||
{ | { | ||||
// applying per-note pitchbend should set the note's totalPitchbendInSemitones | // applying per-note pitchbend should set the note's totalPitchbendInSemitones | ||||
@@ -1289,23 +1328,23 @@ public: | |||||
// modulating timbre on a per-note channel should modulate one note | // modulating timbre on a per-note channel should modulate one note | ||||
test.timbre (3, MPEValue::from7BitInt (33)); | test.timbre (3, MPEValue::from7BitInt (33)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 33, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 33, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
// modulating timbre on a master channel should modulate all notes in this zone | // modulating timbre on a master channel should modulate all notes in this zone | ||||
test.timbre (2, MPEValue::from7BitInt (44)); | test.timbre (2, MPEValue::from7BitInt (44)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteTimbreChangedCallCounter, 3); | expectEquals (test.noteTimbreChangedCallCounter, 3); | ||||
// modulating timbre on an unrelated channel should be ignored | // modulating timbre on an unrelated channel should be ignored | ||||
test.timbre (1, MPEValue::from7BitInt (55)); | test.timbre (1, MPEValue::from7BitInt (55)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteTimbreChangedCallCounter, 3); | expectEquals (test.noteTimbreChangedCallCounter, 3); | ||||
} | } | ||||
{ | { | ||||
@@ -1316,8 +1355,8 @@ public: | |||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.timbre (3, MPEValue::from7BitInt (66)); | test.timbre (3, MPEValue::from7BitInt (66)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 100, 8192, 66, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 8192, 66, MPENote::keyDown); | |||||
expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1331,7 +1370,7 @@ public: | |||||
test.noteOff (3, 61, MPEValue::from7BitInt (100)); | test.noteOff (3, 61, MPEValue::from7BitInt (100)); | ||||
test.timbre (3, MPEValue::from7BitInt (77)); | test.timbre (3, MPEValue::from7BitInt (77)); | ||||
expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 77, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 77, MPENote::keyDown); | |||||
expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1341,11 +1380,11 @@ public: | |||||
// Zsolt's edge case for timbre | // Zsolt's edge case for timbre | ||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
test.timbre (3, MPEValue::from7BitInt (42)); | test.timbre (3, MPEValue::from7BitInt (42)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 42, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 42, MPENote::keyDown); | |||||
test.noteOff (3, 60, MPEValue::from7BitInt (100)); | test.noteOff (3, 60, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
} | } | ||||
} | } | ||||
@@ -1361,8 +1400,8 @@ public: | |||||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.pressure (3, MPEValue::from7BitInt (99)); | test.pressure (3, MPEValue::from7BitInt (99)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown); | ||||
expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
} | } | ||||
@@ -1377,8 +1416,8 @@ public: | |||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.pressure (3, MPEValue::from7BitInt (99)); | test.pressure (3, MPEValue::from7BitInt (99)); | ||||
expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown); | ||||
expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1391,9 +1430,9 @@ public: | |||||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.pressure (3, MPEValue::from7BitInt (99)); | test.pressure (3, MPEValue::from7BitInt (99)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown); | ||||
expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1425,9 +1464,9 @@ public: | |||||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (3, MPEValue::from14BitInt (9999)); | test.pitchbend (3, MPEValue::from14BitInt (9999)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||||
expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1440,9 +1479,9 @@ public: | |||||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (3, MPEValue::from14BitInt (9999)); | test.pitchbend (3, MPEValue::from14BitInt (9999)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1455,9 +1494,9 @@ public: | |||||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (3, MPEValue::from14BitInt (9999)); | test.pitchbend (3, MPEValue::from14BitInt (9999)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1470,9 +1509,9 @@ public: | |||||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (3, MPEValue::from14BitInt (9999)); | test.pitchbend (3, MPEValue::from14BitInt (9999)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||||
expectEquals (test.notePitchbendChangedCallCounter, 3); | expectEquals (test.notePitchbendChangedCallCounter, 3); | ||||
} | } | ||||
} | } | ||||
@@ -1489,9 +1528,9 @@ public: | |||||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.timbre (3, MPEValue::from7BitInt (99)); | test.timbre (3, MPEValue::from7BitInt (99)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 100, 8192, 99, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); | |||||
expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1504,9 +1543,9 @@ public: | |||||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.timbre (3, MPEValue::from7BitInt (99)); | test.timbre (3, MPEValue::from7BitInt (99)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 99, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1519,9 +1558,9 @@ public: | |||||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.timbre (3, MPEValue::from7BitInt (99)); | test.timbre (3, MPEValue::from7BitInt (99)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 100, 8192, 99, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
} | } | ||||
{ | { | ||||
@@ -1534,9 +1573,9 @@ public: | |||||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
test.timbre (3, MPEValue::from7BitInt (99)); | test.timbre (3, MPEValue::from7BitInt (99)); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 99, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 100, 8192, 99, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 100, 8192, 99, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); | |||||
expectEquals (test.noteTimbreChangedCallCounter, 3); | expectEquals (test.noteTimbreChangedCallCounter, 3); | ||||
} | } | ||||
} | } | ||||
@@ -1724,7 +1763,7 @@ public: | |||||
expectEquals (test.getNumPlayingNotes(), 0); | expectEquals (test.getNumPlayingNotes(), 0); | ||||
} | } | ||||
beginTest ("default getInitial...ForNoteOn"); | |||||
beginTest ("default initial values for pitchbend and timbre"); | |||||
{ | { | ||||
MPEInstrument test; | MPEInstrument test; | ||||
test.setZoneLayout (testLayout); | test.setZoneLayout (testLayout); | ||||
@@ -1739,16 +1778,7 @@ public: | |||||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
expectNote (test.getMostRecentNote (3), 100, 100, 3333, 66, MPENote::keyDown); | |||||
} | |||||
beginTest ("overriding getInitial...ForNoteOn"); | |||||
{ | |||||
CustomInitialValuesTest<33, 4444, 55> test; | |||||
test.setZoneLayout (testLayout); | |||||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||||
expectNote (test.getMostRecentNote (3), 100, 33, 4444, 55, MPENote::keyDown); | |||||
expectNote (test.getMostRecentNote (3), 100, 0, 3333, 66, MPENote::keyDown); | |||||
} | } | ||||
beginTest ("Legacy mode"); | beginTest ("Legacy mode"); | ||||
@@ -1806,10 +1836,10 @@ public: | |||||
test.pressure (2, MPEValue::from7BitInt (88)); | test.pressure (2, MPEValue::from7BitInt (88)); | ||||
test.timbre (15, MPEValue::from7BitInt (77)); | test.timbre (15, MPEValue::from7BitInt (77)); | ||||
expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (2, 60), 100, 88, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (2, 60), 100, 88, 8192, 64, MPENote::keyDown); | ||||
expectNote (test.getNote (15, 60), 100, 100, 8192, 77, MPENote::keyDown); | |||||
expectNote (test.getNote (16, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (15, 60), 100, 0, 8192, 77, MPENote::keyDown); | |||||
expectNote (test.getNote (16, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
// note off should work in legacy mode | // note off should work in legacy mode | ||||
@@ -1835,10 +1865,10 @@ public: | |||||
test.noteOn (16, 60, MPEValue::from7BitInt (100)); | test.noteOn (16, 60, MPEValue::from7BitInt (100)); | ||||
expectEquals (test.getNumPlayingNotes(), 4); | expectEquals (test.getNumPlayingNotes(), 4); | ||||
expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (6, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (7, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (6, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (7, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
} | } | ||||
{ | { | ||||
// tracking mode in legacy mode | // tracking mode in legacy mode | ||||
@@ -1851,9 +1881,9 @@ public: | |||||
test.noteOn (1, 62, MPEValue::from7BitInt (100)); | test.noteOn (1, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (1, 61, MPEValue::from7BitInt (100)); | test.noteOn (1, 61, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (1, MPEValue::from14BitInt (9999)); | test.pitchbend (1, MPEValue::from14BitInt (9999)); | ||||
expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
} | } | ||||
{ | { | ||||
UnitTestInstrument test; | UnitTestInstrument test; | ||||
@@ -1864,9 +1894,9 @@ public: | |||||
test.noteOn (1, 62, MPEValue::from7BitInt (100)); | test.noteOn (1, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (1, 61, MPEValue::from7BitInt (100)); | test.noteOn (1, 61, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (1, MPEValue::from14BitInt (9999)); | test.pitchbend (1, MPEValue::from14BitInt (9999)); | ||||
expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
} | } | ||||
{ | { | ||||
UnitTestInstrument test; | UnitTestInstrument test; | ||||
@@ -1877,9 +1907,9 @@ public: | |||||
test.noteOn (1, 62, MPEValue::from7BitInt (100)); | test.noteOn (1, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (1, 61, MPEValue::from7BitInt (100)); | test.noteOn (1, 61, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (1, MPEValue::from14BitInt (9999)); | test.pitchbend (1, MPEValue::from14BitInt (9999)); | ||||
expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||||
} | } | ||||
{ | { | ||||
UnitTestInstrument test; | UnitTestInstrument test; | ||||
@@ -1890,9 +1920,9 @@ public: | |||||
test.noteOn (1, 62, MPEValue::from7BitInt (100)); | test.noteOn (1, 62, MPEValue::from7BitInt (100)); | ||||
test.noteOn (1, 61, MPEValue::from7BitInt (100)); | test.noteOn (1, 61, MPEValue::from7BitInt (100)); | ||||
test.pitchbend (1, MPEValue::from14BitInt (9999)); | test.pitchbend (1, MPEValue::from14BitInt (9999)); | ||||
expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||||
expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||||
} | } | ||||
} | } | ||||
{ | { | ||||
@@ -1916,7 +1946,7 @@ public: | |||||
test.noteOff (1, 60, MPEValue::from7BitInt (100)); | test.noteOff (1, 60, MPEValue::from7BitInt (100)); | ||||
expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
test.sustainPedal (1, false); | test.sustainPedal (1, false); | ||||
expectEquals (test.getNumPlayingNotes(), 0); | expectEquals (test.getNumPlayingNotes(), 0); | ||||
@@ -1939,7 +1969,7 @@ public: | |||||
test.noteOff (2, 61, MPEValue::from7BitInt (100)); | test.noteOff (2, 61, MPEValue::from7BitInt (100)); | ||||
expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
test.sostenutoPedal (1, false); | test.sostenutoPedal (1, false); | ||||
expectEquals (test.getNumPlayingNotes(), 0); | expectEquals (test.getNumPlayingNotes(), 0); | ||||
@@ -2081,26 +2111,6 @@ private: | |||||
} | } | ||||
}; | }; | ||||
//============================================================================== | |||||
template <int initial7BitPressure, int initial14BitPitchbend, int initial7BitTimbre> | |||||
class CustomInitialValuesTest : public MPEInstrument | |||||
{ | |||||
MPEValue getInitialPitchbendForNoteOn (int, int, MPEValue) const override | |||||
{ | |||||
return MPEValue::from14BitInt (initial14BitPitchbend); | |||||
} | |||||
MPEValue getInitialPressureForNoteOn (int, int, MPEValue) const override | |||||
{ | |||||
return MPEValue::from7BitInt (initial7BitPressure); | |||||
} | |||||
MPEValue getInitialTimbreForNoteOn (int, int, MPEValue) const override | |||||
{ | |||||
return MPEValue::from7BitInt (initial7BitTimbre); | |||||
} | |||||
}; | |||||
//============================================================================== | //============================================================================== | ||||
void expectNote (MPENote noteToTest, | void expectNote (MPENote noteToTest, | ||||
int noteOnVelocity7Bit, | int noteOnVelocity7Bit, | ||||
@@ -320,36 +320,6 @@ public: | |||||
/** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | ||||
void setLegacyModePitchbendRange (int pitchbendRange); | 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: | private: | ||||
//============================================================================== | //============================================================================== | ||||
CriticalSection lock; | CriticalSection lock; | ||||
@@ -384,6 +354,7 @@ private: | |||||
void updateDimensionMaster (MPEZone&, MPEDimension&, MPEValue); | void updateDimensionMaster (MPEZone&, MPEDimension&, MPEValue); | ||||
void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | ||||
void callListenersDimensionChanged (MPENote&, MPEDimension&); | void callListenersDimensionChanged (MPENote&, MPEDimension&); | ||||
MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const; | |||||
void processMidiNoteOnMessage (const MidiMessage&); | void processMidiNoteOnMessage (const MidiMessage&); | ||||
void processMidiNoteOffMessage (const MidiMessage&); | void processMidiNoteOffMessage (const MidiMessage&); | ||||
@@ -22,7 +22,6 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
MidiBuffer MPEMessages::addZone (MPEZone zone) | MidiBuffer MPEMessages::addZone (MPEZone zone) | ||||
{ | { | ||||
MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(), | MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(), | ||||
@@ -25,7 +25,8 @@ | |||||
MPESynthesiserBase::MPESynthesiserBase() | MPESynthesiserBase::MPESynthesiserBase() | ||||
: instrument (new MPEInstrument), | : instrument (new MPEInstrument), | ||||
sampleRate (0), | sampleRate (0), | ||||
minimumSubBlockSize (32) | |||||
minimumSubBlockSize (32), | |||||
subBlockSubdivisionIsStrict (false) | |||||
{ | { | ||||
instrument->addListener (this); | instrument->addListener (this); | ||||
} | } | ||||
@@ -100,10 +101,11 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
MidiBuffer::Iterator midiIterator (inputMidi); | MidiBuffer::Iterator midiIterator (inputMidi); | ||||
midiIterator.setNextSamplePosition (startSample); | midiIterator.setNextSamplePosition (startSample); | ||||
bool firstEvent = true; | |||||
int midiEventPos; | int midiEventPos; | ||||
MidiMessage m; | MidiMessage m; | ||||
const ScopedLock sl (renderAudioLock); | |||||
const ScopedLock sl (noteStateLock); | |||||
while (numSamples > 0) | while (numSamples > 0) | ||||
{ | { | ||||
@@ -122,12 +124,14 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
break; | break; | ||||
} | } | ||||
if (samplesToNextMidiMessage < minimumSubBlockSize) | |||||
if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||||
{ | { | ||||
handleMidiEvent (m); | handleMidiEvent (m); | ||||
continue; | continue; | ||||
} | } | ||||
firstEvent = false; | |||||
renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage); | renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage); | ||||
handleMidiEvent (m); | handleMidiEvent (m); | ||||
startSample += samplesToNextMidiMessage; | startSample += samplesToNextMidiMessage; | ||||
@@ -147,15 +151,16 @@ void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) | |||||
{ | { | ||||
if (sampleRate != newRate) | if (sampleRate != newRate) | ||||
{ | { | ||||
const ScopedLock sl (renderAudioLock); | |||||
const ScopedLock sl (noteStateLock); | |||||
instrument->releaseAllNotes(); | instrument->releaseAllNotes(); | ||||
sampleRate = newRate; | sampleRate = newRate; | ||||
} | } | ||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples) noexcept | |||||
void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept | |||||
{ | { | ||||
jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | ||||
minimumSubBlockSize = numSamples; | minimumSubBlockSize = numSamples; | ||||
subBlockSubdivisionIsStrict = shouldBeStrict; | |||||
} | } |
@@ -127,8 +127,14 @@ public: | |||||
The default setting is 32, which means that midi messages are accurate to about < 1ms | 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 | accuracy, which is probably fine for most purposes, but you may want to increase or | ||||
decrease this value for your synth. | decrease this value for your synth. | ||||
If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||||
If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||||
to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||||
(this can sometimes help to avoid quantisation or phasing issues). | |||||
*/ | */ | ||||
void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; | |||||
void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||||
//============================================================================== | //============================================================================== | ||||
/** Puts the synthesiser into legacy mode. | /** Puts the synthesiser into legacy mode. | ||||
@@ -179,13 +185,13 @@ protected: | |||||
//============================================================================== | //============================================================================== | ||||
/** @internal */ | /** @internal */ | ||||
ScopedPointer<MPEInstrument> instrument; | ScopedPointer<MPEInstrument> instrument; | ||||
/** @internal */ | |||||
CriticalSection renderAudioLock; | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
CriticalSection noteStateLock; | |||||
double sampleRate; | double sampleRate; | ||||
int minimumSubBlockSize; | int minimumSubBlockSize; | ||||
bool subBlockSubdivisionIsStrict; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) | ||||
}; | }; | ||||
@@ -69,27 +69,35 @@ struct JUCE_API MPEZone | |||||
int perNotePitchbendRange = 48, | int perNotePitchbendRange = 48, | ||||
int masterPitchbendRange = 2) noexcept; | int masterPitchbendRange = 2) noexcept; | ||||
/* Returns the MIDI master channel of this zone. */ | |||||
/* Returns the MIDI master channel number (in the range 1-16) of this zone. */ | |||||
int getMasterChannel() const noexcept; | int getMasterChannel() const noexcept; | ||||
/** Returns the number of note channels occupied by this zone. */ | /** Returns the number of note channels occupied by this zone. */ | ||||
int getNumNoteChannels() const noexcept; | int getNumNoteChannels() const noexcept; | ||||
/* Returns the MIDI channel number of the lowest-numbered note channel of this zone. */ | |||||
/* Returns the MIDI channel number (in the range 1-16) of the | |||||
lowest-numbered note channel of this zone. | |||||
*/ | |||||
int getFirstNoteChannel() const noexcept; | int getFirstNoteChannel() const noexcept; | ||||
/* Returns the MIDI channel number of the highest-numbered note channel of this zone. */ | |||||
/* Returns the MIDI channel number (in the range 1-16) of the | |||||
highest-numbered note channel of this zone. | |||||
*/ | |||||
int getLastNoteChannel() const noexcept; | int getLastNoteChannel() const noexcept; | ||||
/** Returns the MIDI channel numbers of the note channels of this zone as a Range. */ | |||||
/** Returns the MIDI channel numbers (in the range 1-16) of the | |||||
note channels of this zone as a Range. | |||||
*/ | |||||
Range<int> getNoteChannelRange() const noexcept; | Range<int> getNoteChannelRange() const noexcept; | ||||
/** Returns true if the MIDI channel (in the range 1-16) is used by this zone | /** 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. */ | |||||
either as a note channel or as the master channel; false otherwise. | |||||
*/ | |||||
bool isUsingChannel (int channel) const noexcept; | bool isUsingChannel (int channel) const noexcept; | ||||
/** Returns true if the MIDI channel (in the range 1-16) is used by this zone | /** Returns true if the MIDI channel (in the range 1-16) is used by this zone | ||||
as a note channel; false otherwise. */ | |||||
as a note channel; false otherwise. | |||||
*/ | |||||
bool isUsingChannelAsNoteChannel (int channel) const noexcept; | bool isUsingChannelAsNoteChannel (int channel) const noexcept; | ||||
/** Returns the per-note pitchbend range in semitones set for this zone. */ | /** Returns the per-note pitchbend range in semitones set for this zone. */ | ||||
@@ -34,6 +34,7 @@ MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other) | |||||
MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) | MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) | ||||
{ | { | ||||
zones = other.zones; | zones = other.zones; | ||||
listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); | |||||
return *this; | return *this; | ||||
} | } | ||||
@@ -100,8 +100,9 @@ public: | |||||
/** Returns the current number of MPE zones. */ | /** Returns the current number of MPE zones. */ | ||||
int getNumZones() const noexcept; | int getNumZones() const noexcept; | ||||
/** Returns a pointer to the MPE zone at the given index, | |||||
or nullptr if there is no such zone. | |||||
/** Returns a pointer to the MPE zone at the given index, or nullptr if there | |||||
is no such zone. Zones are sorted by insertion order (most recently added | |||||
zone last). | |||||
*/ | */ | ||||
MPEZone* getZoneByIndex (int index) const noexcept; | MPEZone* getZoneByIndex (int index) const noexcept; | ||||
@@ -26,7 +26,8 @@ BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, | |||||
TimeSliceThread& thread, | TimeSliceThread& thread, | ||||
const bool deleteSourceWhenDeleted, | const bool deleteSourceWhenDeleted, | ||||
const int bufferSizeSamples, | const int bufferSizeSamples, | ||||
const int numChannels) | |||||
const int numChannels, | |||||
bool prefillBufferOnPrepareToPlay) | |||||
: source (s, deleteSourceWhenDeleted), | : source (s, deleteSourceWhenDeleted), | ||||
backgroundThread (thread), | backgroundThread (thread), | ||||
numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)), | numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)), | ||||
@@ -36,7 +37,8 @@ BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, | |||||
nextPlayPos (0), | nextPlayPos (0), | ||||
sampleRate (0), | sampleRate (0), | ||||
wasSourceLooping (false), | wasSourceLooping (false), | ||||
isPrepared (false) | |||||
isPrepared (false), | |||||
prefillBuffer (prefillBufferOnPrepareToPlay) | |||||
{ | { | ||||
jassert (source != nullptr); | jassert (source != nullptr); | ||||
@@ -73,12 +75,13 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne | |||||
backgroundThread.addTimeSliceClient (this); | backgroundThread.addTimeSliceClient (this); | ||||
while (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, | |||||
buffer.getNumSamples() / 2)) | |||||
do | |||||
{ | { | ||||
backgroundThread.moveToFrontOfQueue (this); | backgroundThread.moveToFrontOfQueue (this); | ||||
Thread::sleep (5); | Thread::sleep (5); | ||||
} | } | ||||
while (prefillBuffer | |||||
&& (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2))); | |||||
} | } | ||||
} | } | ||||
@@ -88,7 +91,12 @@ void BufferingAudioSource::releaseResources() | |||||
backgroundThread.removeTimeSliceClient (this); | backgroundThread.removeTimeSliceClient (this); | ||||
buffer.setSize (numberOfChannels, 0); | buffer.setSize (numberOfChannels, 0); | ||||
source->releaseResources(); | |||||
// MSVC2015 seems to need this if statement to not generate a warning during linking. | |||||
// As source is set in the constructor, there is no way that source could | |||||
// ever equal this, but it seems to make MSVC2015 happy. | |||||
if (source != this) | |||||
source->releaseResources(); | |||||
} | } | ||||
void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | ||||
@@ -148,6 +156,41 @@ void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info | |||||
} | } | ||||
} | } | ||||
bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout) | |||||
{ | |||||
if (!source || source->getTotalLength() <= 0) | |||||
return false; | |||||
if (nextPlayPos + info.numSamples < 0) | |||||
return true; | |||||
if (! isLooping() && nextPlayPos > getTotalLength()) | |||||
return true; | |||||
const uint32 endTime = Time::getMillisecondCounter() + timeout; | |||||
uint32 now = Time::getMillisecondCounter(); | |||||
while (now < endTime) | |||||
{ | |||||
{ | |||||
const ScopedLock sl (bufferStartPosLock); | |||||
const int validStart = static_cast<int> (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos); | |||||
const int validEnd = static_cast<int> (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos); | |||||
if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples) | |||||
return true; | |||||
} | |||||
if (! bufferReadyEvent.wait (static_cast<int> (endTime - now))) | |||||
return false; | |||||
now = Time::getMillisecondCounter(); | |||||
} | |||||
return false; | |||||
} | |||||
int64 BufferingAudioSource::getNextReadPosition() const | int64 BufferingAudioSource::getNextReadPosition() const | ||||
{ | { | ||||
jassert (source->getTotalLength() > 0); | jassert (source->getTotalLength() > 0); | ||||
@@ -241,6 +284,8 @@ bool BufferingAudioSource::readNextBufferChunk() | |||||
bufferValidEnd = newBVE; | bufferValidEnd = newBVE; | ||||
} | } | ||||
bufferReadyEvent.signal(); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -43,21 +43,24 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
/** Creates a BufferingAudioSource. | /** Creates a BufferingAudioSource. | ||||
@param source the input source to read from | |||||
@param backgroundThread a background thread that will be used for the | |||||
background read-ahead. This object must not be deleted | |||||
until after any BufferingAudioSources that are using it | |||||
have been deleted! | |||||
@param deleteSourceWhenDeleted if true, then the input source object will | |||||
be deleted when this object is deleted | |||||
@param numberOfSamplesToBuffer the size of buffer to use for reading ahead | |||||
@param numberOfChannels the number of channels that will be played | |||||
@param source the input source to read from | |||||
@param backgroundThread a background thread that will be used for the | |||||
background read-ahead. This object must not be deleted | |||||
until after any BufferingAudioSources that are using it | |||||
have been deleted! | |||||
@param deleteSourceWhenDeleted if true, then the input source object will | |||||
be deleted when this object is deleted | |||||
@param numberOfSamplesToBuffer the size of buffer to use for reading ahead | |||||
@param numberOfChannels the number of channels that will be played | |||||
@param prefillBufferOnPrepareToPlay if true, then calling prepareToPlay on this object will | |||||
block until the buffer has been filled | |||||
*/ | */ | ||||
BufferingAudioSource (PositionableAudioSource* source, | BufferingAudioSource (PositionableAudioSource* source, | ||||
TimeSliceThread& backgroundThread, | TimeSliceThread& backgroundThread, | ||||
bool deleteSourceWhenDeleted, | bool deleteSourceWhenDeleted, | ||||
int numberOfSamplesToBuffer, | int numberOfSamplesToBuffer, | ||||
int numberOfChannels = 2); | |||||
int numberOfChannels = 2, | |||||
bool prefillBufferOnPrepareToPlay = true); | |||||
/** Destructor. | /** Destructor. | ||||
@@ -89,6 +92,12 @@ public: | |||||
/** Implements the PositionableAudioSource method. */ | /** Implements the PositionableAudioSource method. */ | ||||
bool isLooping() const override { return source->isLooping(); } | bool isLooping() const override { return source->isLooping(); } | ||||
/** A useful function to block until the next the buffer info can be filled. | |||||
This is useful for offline rendering. | |||||
*/ | |||||
bool waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout); | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
OptionalScopedPointer<PositionableAudioSource> source; | OptionalScopedPointer<PositionableAudioSource> source; | ||||
@@ -96,9 +105,10 @@ private: | |||||
int numberOfSamplesToBuffer, numberOfChannels; | int numberOfSamplesToBuffer, numberOfChannels; | ||||
AudioSampleBuffer buffer; | AudioSampleBuffer buffer; | ||||
CriticalSection bufferStartPosLock; | CriticalSection bufferStartPosLock; | ||||
WaitableEvent bufferReadyEvent; | |||||
int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; | int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; | ||||
double volatile sampleRate; | double volatile sampleRate; | ||||
bool wasSourceLooping, isPrepared; | |||||
bool wasSourceLooping, isPrepared, prefillBuffer; | |||||
bool readNextBufferChunk(); | bool readNextBufferChunk(); | ||||
void readBufferSection (int64 start, int length, int bufferOffset); | void readBufferSection (int64 start, int length, int bufferOffset); | ||||
@@ -30,7 +30,7 @@ | |||||
/** | /** | ||||
A type of AudioSource that takes an input source and changes its sample rate. | A type of AudioSource that takes an input source and changes its sample rate. | ||||
@see AudioSource | |||||
@see AudioSource, LagrangeInterpolator, CatmullRomInterpolator | |||||
*/ | */ | ||||
class JUCE_API ResamplingAudioSource : public AudioSource | class JUCE_API ResamplingAudioSource : public AudioSource | ||||
{ | { | ||||
@@ -88,6 +88,7 @@ Synthesiser::Synthesiser() | |||||
: sampleRate (0), | : sampleRate (0), | ||||
lastNoteOnCounter (0), | lastNoteOnCounter (0), | ||||
minimumSubBlockSize (32), | minimumSubBlockSize (32), | ||||
subBlockSubdivisionIsStrict (false), | |||||
shouldStealNotes (true) | shouldStealNotes (true) | ||||
{ | { | ||||
for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | ||||
@@ -147,10 +148,11 @@ void Synthesiser::setNoteStealingEnabled (const bool shouldSteal) | |||||
shouldStealNotes = shouldSteal; | shouldStealNotes = shouldSteal; | ||||
} | } | ||||
void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples) noexcept | |||||
void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept | |||||
{ | { | ||||
jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | ||||
minimumSubBlockSize = numSamples; | minimumSubBlockSize = numSamples; | ||||
subBlockSubdivisionIsStrict = shouldBeStrict; | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -177,10 +179,12 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
{ | { | ||||
// must set the sample rate before using this! | // must set the sample rate before using this! | ||||
jassert (sampleRate != 0); | jassert (sampleRate != 0); | ||||
const int targetChannels = outputAudio.getNumChannels(); | |||||
MidiBuffer::Iterator midiIterator (midiData); | MidiBuffer::Iterator midiIterator (midiData); | ||||
midiIterator.setNextSamplePosition (startSample); | midiIterator.setNextSamplePosition (startSample); | ||||
bool firstEvent = true; | |||||
int midiEventPos; | int midiEventPos; | ||||
MidiMessage m; | MidiMessage m; | ||||
@@ -190,7 +194,9 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
{ | { | ||||
if (! midiIterator.getNextEvent (m, midiEventPos)) | if (! midiIterator.getNextEvent (m, midiEventPos)) | ||||
{ | { | ||||
renderVoices (outputAudio, startSample, numSamples); | |||||
if (targetChannels > 0) | |||||
renderVoices (outputAudio, startSample, numSamples); | |||||
return; | return; | ||||
} | } | ||||
@@ -198,18 +204,24 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
if (samplesToNextMidiMessage >= numSamples) | if (samplesToNextMidiMessage >= numSamples) | ||||
{ | { | ||||
renderVoices (outputAudio, startSample, numSamples); | |||||
if (targetChannels > 0) | |||||
renderVoices (outputAudio, startSample, numSamples); | |||||
handleMidiEvent (m); | handleMidiEvent (m); | ||||
break; | break; | ||||
} | } | ||||
if (samplesToNextMidiMessage < minimumSubBlockSize) | |||||
if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||||
{ | { | ||||
handleMidiEvent (m); | handleMidiEvent (m); | ||||
continue; | continue; | ||||
} | } | ||||
renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||||
firstEvent = false; | |||||
if (targetChannels > 0) | |||||
renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||||
handleMidiEvent (m); | handleMidiEvent (m); | ||||
startSample += samplesToNextMidiMessage; | startSample += samplesToNextMidiMessage; | ||||
numSamples -= samplesToNextMidiMessage; | numSamples -= samplesToNextMidiMessage; | ||||
@@ -539,8 +539,14 @@ public: | |||||
The default setting is 32, which means that midi messages are accurate to about < 1ms | 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 | accuracy, which is probably fine for most purposes, but you may want to increase or | ||||
decrease this value for your synth. | decrease this value for your synth. | ||||
If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||||
If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||||
to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||||
(this can sometimes help to avoid quantisation or phasing issues). | |||||
*/ | */ | ||||
void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; | |||||
void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||||
protected: | protected: | ||||
//============================================================================== | //============================================================================== | ||||
@@ -615,6 +621,7 @@ private: | |||||
double sampleRate; | double sampleRate; | ||||
uint32 lastNoteOnCounter; | uint32 lastNoteOnCounter; | ||||
int minimumSubBlockSize; | int minimumSubBlockSize; | ||||
bool subBlockSubdivisionIsStrict; | |||||
bool shouldStealNotes; | bool shouldStealNotes; | ||||
BigInteger sustainPedalsDown; | BigInteger sustainPedalsDown; | ||||
@@ -86,72 +86,12 @@ private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) | 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() | AudioDeviceManager::AudioDeviceManager() | ||||
: numInputChansNeeded (0), | : numInputChansNeeded (0), | ||||
numOutputChansNeeded (2), | numOutputChansNeeded (2), | ||||
listNeedsScanning (true), | listNeedsScanning (true), | ||||
inputLevel (0), | |||||
testSoundPosition (0), | |||||
cpuUsageMs (0), | cpuUsageMs (0), | ||||
timeToCpuScale (0) | timeToCpuScale (0) | ||||
{ | { | ||||
@@ -162,10 +102,6 @@ AudioDeviceManager::~AudioDeviceManager() | |||||
{ | { | ||||
currentAudioDevice = nullptr; | currentAudioDevice = nullptr; | ||||
defaultMidiOutput = nullptr; | defaultMidiOutput = nullptr; | ||||
for (int i = 0; i < callbacks.size(); ++i) | |||||
if (AutoRemovingSourcePlayer* p = dynamic_cast<AutoRemovingSourcePlayer*> (callbacks.getUnchecked(i))) | |||||
delete p; | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
@@ -346,8 +282,8 @@ String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, | |||||
currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | ||||
} | } | ||||
setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize"); | |||||
setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate"); | |||||
setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize", setup.bufferSize); | |||||
setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate", setup.sampleRate); | |||||
setup.inputChannels .parseString (xml.getStringAttribute ("audioDeviceInChans", "11"), 2); | setup.inputChannels .parseString (xml.getStringAttribute ("audioDeviceInChans", "11"), 2); | ||||
setup.outputChannels.parseString (xml.getStringAttribute ("audioDeviceOutChans", "11"), 2); | setup.outputChannels.parseString (xml.getStringAttribute ("audioDeviceOutChans", "11"), 2); | ||||
@@ -650,6 +586,8 @@ void AudioDeviceManager::stopDevice() | |||||
{ | { | ||||
if (currentAudioDevice != nullptr) | if (currentAudioDevice != nullptr) | ||||
currentAudioDevice->stop(); | currentAudioDevice->stop(); | ||||
testSound = nullptr; | |||||
} | } | ||||
void AudioDeviceManager::closeAudioDevice() | void AudioDeviceManager::closeAudioDevice() | ||||
@@ -761,31 +699,8 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||||
{ | { | ||||
const ScopedLock sl (audioCallbackLock); | const ScopedLock sl (audioCallbackLock); | ||||
if (inputLevelMeasurementEnabledCount.get() > 0 && numInputChannels > 0) | |||||
{ | |||||
for (int j = 0; j < numSamples; ++j) | |||||
{ | |||||
float s = 0; | |||||
for (int i = 0; i < numInputChannels; ++i) | |||||
s += std::abs (inputChannelData[i][j]); | |||||
s /= numInputChannels; | |||||
const double decayFactor = 0.99992; | |||||
if (s > inputLevel) | |||||
inputLevel = s; | |||||
else if (inputLevel > 0.001f) | |||||
inputLevel *= decayFactor; | |||||
else | |||||
inputLevel = 0; | |||||
} | |||||
} | |||||
else | |||||
{ | |||||
inputLevel = 0; | |||||
} | |||||
inputLevelMeter.updateLevel (inputChannelData, numInputChannels, numSamples); | |||||
outputLevelMeter.updateLevel (const_cast<const float**> (outputChannelData), numOutputChannels, numSamples); | |||||
if (callbacks.size() > 0) | if (callbacks.size() > 0) | ||||
{ | { | ||||
@@ -821,6 +736,20 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||||
for (int i = 0; i < numOutputChannels; ++i) | for (int i = 0; i < numOutputChannels; ++i) | ||||
zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); | zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); | ||||
} | } | ||||
if (testSound != nullptr) | |||||
{ | |||||
const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); | |||||
const float* const src = testSound->getReadPointer (0, testSoundPosition); | |||||
for (int i = 0; i < numOutputChannels; ++i) | |||||
for (int j = 0; j < numSamps; ++j) | |||||
outputChannelData [i][j] += src[j]; | |||||
testSoundPosition += numSamps; | |||||
if (testSoundPosition >= testSound->getNumSamples()) | |||||
testSound = nullptr; | |||||
} | |||||
} | } | ||||
void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) | void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) | ||||
@@ -989,170 +918,87 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
// An AudioSource which simply outputs a buffer | |||||
class AudioSampleBufferSource : public PositionableAudioSource | |||||
{ | |||||
public: | |||||
AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer, bool playOnAllChannels) | |||||
: buffer (audioBuffer, ownBuffer), | |||||
position (0), looping (false), playAcrossAllChannels (playOnAllChannels) | |||||
{} | |||||
AudioDeviceManager::LevelMeter::LevelMeter() noexcept : level() {} | |||||
//============================================================================== | |||||
void setNextReadPosition (int64 newPosition) override | |||||
{ | |||||
jassert (newPosition >= 0); | |||||
if (looping) | |||||
newPosition = newPosition % static_cast<int64> (buffer->getNumSamples()); | |||||
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; } | |||||
void setLooping (bool shouldLoop) override { looping = shouldLoop; } | |||||
//============================================================================== | |||||
void prepareToPlay (int, double) override {} | |||||
void releaseResources() override {} | |||||
void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override | |||||
void AudioDeviceManager::LevelMeter::updateLevel (const float* const* channelData, int numChannels, int numSamples) noexcept | |||||
{ | |||||
if (enabled.get() != 0 && numChannels > 0) | |||||
{ | { | ||||
bufferToFill.clearActiveBufferRegion(); | |||||
const int bufferSize = buffer->getNumSamples(); | |||||
const int samplesNeeded = bufferToFill.numSamples; | |||||
const int samplesToCopy = jmin (bufferSize - position, samplesNeeded); | |||||
if (samplesToCopy > 0) | |||||
for (int j = 0; j < numSamples; ++j) | |||||
{ | { | ||||
int maxInChannels = buffer->getNumChannels(); | |||||
int maxOutChannels = bufferToFill.buffer->getNumChannels(); | |||||
if (! playAcrossAllChannels) | |||||
maxOutChannels = jmin (maxOutChannels, maxInChannels); | |||||
for (int i = 0; i < maxOutChannels; ++i) | |||||
bufferToFill.buffer->copyFrom (i, bufferToFill.startSample, *buffer, | |||||
i % maxInChannels, position, samplesToCopy); | |||||
} | |||||
position += samplesNeeded; | |||||
float s = 0; | |||||
if (looping) | |||||
position %= bufferSize; | |||||
} | |||||
for (int i = 0; i < numChannels; ++i) | |||||
s += std::abs (channelData[i][j]); | |||||
private: | |||||
//============================================================================== | |||||
OptionalScopedPointer<AudioSampleBuffer> buffer; | |||||
int position; | |||||
bool looping, playAcrossAllChannels; | |||||
s /= numChannels; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource) | |||||
}; | |||||
void AudioDeviceManager::playSound (const File& file) | |||||
{ | |||||
if (file.existsAsFile()) | |||||
{ | |||||
AudioFormatManager formatManager; | |||||
const double decayFactor = 0.99992; | |||||
formatManager.registerBasicFormats(); | |||||
playSound (formatManager.createReaderFor (file), true); | |||||
if (s > level) | |||||
level = s; | |||||
else if (level > 0.001f) | |||||
level *= decayFactor; | |||||
else | |||||
level = 0; | |||||
} | |||||
} | } | ||||
} | |||||
void AudioDeviceManager::playSound (const void* resourceData, size_t resourceSize) | |||||
{ | |||||
if (resourceData != nullptr && resourceSize > 0) | |||||
else | |||||
{ | { | ||||
AudioFormatManager formatManager; | |||||
formatManager.registerBasicFormats(); | |||||
MemoryInputStream* mem = new MemoryInputStream (resourceData, resourceSize, false); | |||||
playSound (formatManager.createReaderFor (mem), true); | |||||
level = 0; | |||||
} | } | ||||
} | } | ||||
void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFinished) | |||||
void AudioDeviceManager::LevelMeter::setEnabled (bool shouldBeEnabled) noexcept | |||||
{ | { | ||||
if (reader != nullptr) | |||||
playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); | |||||
enabled.set (shouldBeEnabled ? 1 : 0); | |||||
level = 0; | |||||
} | } | ||||
void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels) | |||||
double AudioDeviceManager::LevelMeter::getCurrentLevel() const noexcept | |||||
{ | { | ||||
if (buffer != nullptr) | |||||
playSound (new AudioSampleBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true); | |||||
jassert (enabled.get() != 0); // you need to call setEnabled (true) before using this! | |||||
return level; | |||||
} | } | ||||
void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished) | |||||
void AudioDeviceManager::playTestSound() | |||||
{ | { | ||||
if (audioSource != nullptr && currentAudioDevice != nullptr) | |||||
{ | |||||
AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource); | |||||
{ // cunningly nested to swap, unlock and delete in that order. | |||||
ScopedPointer<AudioSampleBuffer> oldSound; | |||||
if (transport == nullptr) | |||||
{ | { | ||||
if (deleteWhenFinished) | |||||
{ | |||||
transport = new AudioSourceOwningTransportSource (audioSource); | |||||
} | |||||
else | |||||
{ | |||||
transport = new AudioTransportSource(); | |||||
transport->setSource (audioSource); | |||||
deleteWhenFinished = true; | |||||
} | |||||
const ScopedLock sl (audioCallbackLock); | |||||
oldSound = testSound; | |||||
} | } | ||||
transport->start(); | |||||
new AutoRemovingSourcePlayer (*this, transport, deleteWhenFinished); | |||||
} | } | ||||
else | |||||
{ | |||||
if (deleteWhenFinished) | |||||
delete audioSource; | |||||
} | |||||
} | |||||
void AudioDeviceManager::playTestSound() | |||||
{ | |||||
const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||||
const int soundLength = (int) sampleRate; | |||||
testSoundPosition = 0; | |||||
const double frequency = 440.0; | |||||
const float amplitude = 0.5f; | |||||
const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||||
if (currentAudioDevice != nullptr) | |||||
{ | |||||
const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||||
const int soundLength = (int) sampleRate; | |||||
AudioSampleBuffer* newSound = new AudioSampleBuffer (1, soundLength); | |||||
const double frequency = 440.0; | |||||
const float amplitude = 0.5f; | |||||
for (int i = 0; i < soundLength; ++i) | |||||
newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); | |||||
const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||||
newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); | |||||
newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); | |||||
AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); | |||||
playSound (newSound, true, true); | |||||
} | |||||
for (int i = 0; i < soundLength; ++i) | |||||
newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); | |||||
//============================================================================== | |||||
void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement) | |||||
{ | |||||
if (enableMeasurement) | |||||
++inputLevelMeasurementEnabledCount; | |||||
else | |||||
--inputLevelMeasurementEnabledCount; | |||||
newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); | |||||
newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); | |||||
inputLevel = 0; | |||||
const ScopedLock sl (audioCallbackLock); | |||||
testSound = newSound; | |||||
} | |||||
} | } | ||||
double AudioDeviceManager::getCurrentInputLevel() const | |||||
{ | |||||
jassert (inputLevelMeasurementEnabledCount.get() > 0); // you need to call enableInputLevelMeasurement() before using this! | |||||
return inputLevel; | |||||
} | |||||
double AudioDeviceManager::getCurrentInputLevel() const noexcept { return inputLevelMeter.getCurrentLevel(); } | |||||
double AudioDeviceManager::getCurrentOutputLevel() const noexcept { return outputLevelMeter.getCurrentLevel(); } | |||||
void AudioDeviceManager::enableInputLevelMeasurement (bool enable) noexcept { inputLevelMeter.setEnabled (enable); } | |||||
void AudioDeviceManager::enableOutputLevelMeasurement (bool enable) noexcept { outputLevelMeter.setEnabled (enable); } |
@@ -404,78 +404,28 @@ public: | |||||
*/ | */ | ||||
void playTestSound(); | void playTestSound(); | ||||
/** Plays a sound from a file. */ | |||||
void playSound (const File& file); | |||||
/** Convenient method to play sound from a JUCE resource. */ | |||||
void playSound (const void* resourceData, size_t resourceSize); | |||||
/** Plays the sound from an audio format reader. | |||||
If deleteWhenFinished is true then the format reader will be | |||||
automatically deleted once the sound has finished playing. | |||||
*/ | |||||
void playSound (AudioFormatReader* buffer, bool deleteWhenFinished = false); | |||||
/** Plays the sound from a positionable audio source. | |||||
This will output the sound coming from a positionable audio source. | |||||
This gives you slightly more control over the sound playback compared | |||||
to the other playSound methods. For example, if you would like to | |||||
stop the sound prematurely you can call this method with a | |||||
TransportAudioSource and then call audioSource->stop. Note that, | |||||
you must call audioSource->start to start the playback, if your | |||||
audioSource is a TransportAudioSource. | |||||
The audio device manager will not hold any references to this audio | |||||
source once the audio source has stopped playing for any reason, | |||||
for example when the sound has finished playing or when you have | |||||
called audioSource->stop. Therefore, calling audioSource->start() on | |||||
a finished audioSource will not restart the sound again. If this is | |||||
desired simply call playSound with the same audioSource again. | |||||
@param audioSource the audio source to play | |||||
@param deleteWhenFinished If this is true then the audio source will | |||||
be deleted once the device manager has finished playing. | |||||
*/ | |||||
void playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished = false); | |||||
/** Plays the sound from an audio sample buffer. | |||||
This will output the sound contained in an audio sample buffer. If | |||||
deleteWhenFinished is true then the audio sample buffer will be | |||||
automatically deleted once the sound has finished playing. | |||||
If playOnAllOutputChannels is true, then if there are more output channels | |||||
than buffer channels, then the ones that are available will be re-used on | |||||
multiple outputs so that something is sent to all output channels. If it | |||||
is false, then the buffer will just be played on the first output channels. | |||||
*/ | |||||
void playSound (AudioSampleBuffer* buffer, | |||||
bool deleteWhenFinished = false, | |||||
bool playOnAllOutputChannels = false); | |||||
//============================================================================== | //============================================================================== | ||||
/** Turns on level-measuring. | |||||
When enabled, the device manager will measure the peak input level | |||||
across all channels, and you can get this level by calling getCurrentInputLevel(). | |||||
This is mainly intended for audio setup UI panels to use to create a mic | |||||
level display, so that the user can check that they've selected the right | |||||
device. | |||||
/** Turns on level-measuring for input channels. | |||||
@see getCurrentInputLevel() | |||||
*/ | |||||
void enableInputLevelMeasurement (bool enableMeasurement) noexcept; | |||||
A simple filter is used to make the level decay smoothly, but this is | |||||
only intended for giving rough feedback, and not for any kind of accurate | |||||
measurement. | |||||
/** Turns on level-measuring for output channels. | |||||
@see getCurrentOutputLevel() | |||||
*/ | */ | ||||
void enableInputLevelMeasurement (bool enableMeasurement); | |||||
void enableOutputLevelMeasurement (bool enableMeasurement) noexcept; | |||||
/** Returns the current input level. | /** Returns the current input level. | ||||
To use this, you must first enable it by calling enableInputLevelMeasurement(). | To use this, you must first enable it by calling enableInputLevelMeasurement(). | ||||
See enableInputLevelMeasurement() for more info. | |||||
@see enableInputLevelMeasurement() | |||||
*/ | */ | ||||
double getCurrentInputLevel() const; | |||||
double getCurrentInputLevel() const noexcept; | |||||
/** Returns the current output level. | |||||
To use this, you must first enable it by calling enableOutputLevelMeasurement(). | |||||
@see enableOutputLevelMeasurement() | |||||
*/ | |||||
double getCurrentOutputLevel() const noexcept; | |||||
/** Returns the a lock that can be used to synchronise access to the audio callback. | /** Returns the a lock that can be used to synchronise access to the audio callback. | ||||
Obviously while this is locked, you're blocking the audio thread from running, so | Obviously while this is locked, you're blocking the audio thread from running, so | ||||
@@ -502,8 +452,6 @@ private: | |||||
BigInteger inputChannels, outputChannels; | BigInteger inputChannels, outputChannels; | ||||
ScopedPointer<XmlElement> lastExplicitSettings; | ScopedPointer<XmlElement> lastExplicitSettings; | ||||
mutable bool listNeedsScanning; | mutable bool listNeedsScanning; | ||||
Atomic<int> inputLevelMeasurementEnabledCount; | |||||
double inputLevel; | |||||
AudioSampleBuffer tempBuffer; | AudioSampleBuffer tempBuffer; | ||||
struct MidiCallbackInfo | struct MidiCallbackInfo | ||||
@@ -520,8 +468,24 @@ private: | |||||
ScopedPointer<MidiOutput> defaultMidiOutput; | ScopedPointer<MidiOutput> defaultMidiOutput; | ||||
CriticalSection audioCallbackLock, midiCallbackLock; | CriticalSection audioCallbackLock, midiCallbackLock; | ||||
ScopedPointer<AudioSampleBuffer> testSound; | |||||
int testSoundPosition; | |||||
double cpuUsageMs, timeToCpuScale; | double cpuUsageMs, timeToCpuScale; | ||||
struct LevelMeter | |||||
{ | |||||
LevelMeter() noexcept; | |||||
void updateLevel (const float* const*, int numChannels, int numSamples) noexcept; | |||||
void setEnabled (bool) noexcept; | |||||
double getCurrentLevel() const noexcept; | |||||
Atomic<int> enabled; | |||||
double level; | |||||
}; | |||||
LevelMeter inputLevelMeter, outputLevelMeter; | |||||
//============================================================================== | //============================================================================== | ||||
class CallbackHandler; | class CallbackHandler; | ||||
friend class CallbackHandler; | friend class CallbackHandler; | ||||
@@ -31,6 +31,8 @@ | |||||
#error "Incorrect use of JUCE cpp file" | #error "Incorrect use of JUCE cpp file" | ||||
#endif | #endif | ||||
#include "AppConfig.h" | |||||
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | ||||
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | ||||
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | ||||
@@ -45,7 +47,6 @@ | |||||
#define Component CarbonDummyCompName | #define Component CarbonDummyCompName | ||||
#import <CoreAudio/AudioHardware.h> | #import <CoreAudio/AudioHardware.h> | ||||
#import <CoreMIDI/MIDIServices.h> | #import <CoreMIDI/MIDIServices.h> | ||||
#import <DiscRecording/DiscRecording.h> | |||||
#import <AudioToolbox/AudioServices.h> | #import <AudioToolbox/AudioServices.h> | ||||
#undef Point | #undef Point | ||||
#undef Component | #undef Component | ||||
@@ -55,10 +56,14 @@ | |||||
#import <AVFoundation/AVFoundation.h> | #import <AVFoundation/AVFoundation.h> | ||||
#import <CoreMIDI/MIDIServices.h> | #import <CoreMIDI/MIDIServices.h> | ||||
#if TARGET_OS_SIMULATOR | |||||
#import <CoreMIDI/MIDINetworkSession.h> | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
#elif JUCE_WINDOWS | #elif JUCE_WINDOWS | ||||
#if JUCE_WASAPI | #if JUCE_WASAPI | ||||
#include <MMReg.h> | |||||
#include <mmreg.h> | |||||
#endif | #endif | ||||
#if JUCE_ASIO | #if JUCE_ASIO | ||||
@@ -84,15 +89,6 @@ | |||||
#include <iasiodrv.h> | #include <iasiodrv.h> | ||||
#endif | #endif | ||||
#if JUCE_USE_CDBURNER | |||||
/* You'll need the Platform SDK for these headers - if you don't have it and don't | |||||
need to use CD-burning, then you might just want to set the JUCE_USE_CDBURNER flag | |||||
to 0, to avoid these includes. | |||||
*/ | |||||
#include <imapi.h> | |||||
#include <imapierror.h> | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
#if JUCE_ALSA | #if JUCE_ALSA | ||||
@@ -139,7 +135,6 @@ namespace juce | |||||
#include "audio_io/juce_AudioIODeviceType.cpp" | #include "audio_io/juce_AudioIODeviceType.cpp" | ||||
#include "midi_io/juce_MidiMessageCollector.cpp" | #include "midi_io/juce_MidiMessageCollector.cpp" | ||||
#include "midi_io/juce_MidiOutput.cpp" | #include "midi_io/juce_MidiOutput.cpp" | ||||
#include "audio_cd/juce_AudioCDReader.cpp" | |||||
#include "sources/juce_AudioSourcePlayer.cpp" | #include "sources/juce_AudioSourcePlayer.cpp" | ||||
#include "sources/juce_AudioTransportSource.cpp" | #include "sources/juce_AudioTransportSource.cpp" | ||||
#include "native/juce_MidiDataConcatenator.h" | #include "native/juce_MidiDataConcatenator.h" | ||||
@@ -149,14 +144,6 @@ namespace juce | |||||
#include "native/juce_mac_CoreAudio.cpp" | #include "native/juce_mac_CoreAudio.cpp" | ||||
#include "native/juce_mac_CoreMidi.cpp" | #include "native/juce_mac_CoreMidi.cpp" | ||||
#if JUCE_USE_CDREADER | |||||
#include "native/juce_mac_AudioCDReader.mm" | |||||
#endif | |||||
#if JUCE_USE_CDBURNER | |||||
#include "native/juce_mac_AudioCDBurner.mm" | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
#elif JUCE_IOS | #elif JUCE_IOS | ||||
#include "native/juce_ios_Audio.cpp" | #include "native/juce_ios_Audio.cpp" | ||||
@@ -179,14 +166,6 @@ namespace juce | |||||
#include "native/juce_win32_ASIO.cpp" | #include "native/juce_win32_ASIO.cpp" | ||||
#endif | #endif | ||||
#if JUCE_USE_CDREADER | |||||
#include "native/juce_win32_AudioCDReader.cpp" | |||||
#endif | |||||
#if JUCE_USE_CDBURNER | |||||
#include "native/juce_win32_AudioCDBurner.cpp" | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
#elif JUCE_LINUX | #elif JUCE_LINUX | ||||
#if JUCE_ALSA | #if JUCE_ALSA | ||||
@@ -199,10 +178,6 @@ namespace juce | |||||
#include "native/juce_linux_JackAudio.cpp" | #include "native/juce_linux_JackAudio.cpp" | ||||
#endif | #endif | ||||
#if JUCE_USE_CDREADER | |||||
#include "native/juce_linux_AudioCDReader.cpp" | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
#elif JUCE_ANDROID | #elif JUCE_ANDROID | ||||
#include "native/juce_android_Audio.cpp" | #include "native/juce_android_Audio.cpp" | ||||
@@ -22,12 +22,39 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
/******************************************************************************* | |||||
The block below describes the properties of this module, and is read by | |||||
the Projucer to automatically generate project code that uses it. | |||||
For details about the syntax and how to create or use a module, see the | |||||
JUCE Module Format.txt file. | |||||
BEGIN_JUCE_MODULE_DECLARATION | |||||
ID: juce_audio_devices | |||||
vendor: juce | |||||
version: 4.3.0 | |||||
name: JUCE audio and MIDI I/O device classes | |||||
description: Classes to play and record from audio and MIDI I/O devices | |||||
website: http://www.juce.com/juce | |||||
license: GPL/Commercial | |||||
dependencies: juce_audio_basics, juce_events | |||||
OSXFrameworks: CoreAudio CoreMIDI AudioToolbox | |||||
iOSFrameworks: CoreAudio CoreMIDI AudioToolbox AVFoundation | |||||
linuxPackages: alsa | |||||
mingwLibs: winmm | |||||
END_JUCE_MODULE_DECLARATION | |||||
*******************************************************************************/ | |||||
#ifndef JUCE_AUDIO_DEVICES_H_INCLUDED | #ifndef JUCE_AUDIO_DEVICES_H_INCLUDED | ||||
#define JUCE_AUDIO_DEVICES_H_INCLUDED | #define JUCE_AUDIO_DEVICES_H_INCLUDED | ||||
#include "../juce_events/juce_events.h" | |||||
#include "../juce_audio_basics/juce_audio_basics.h" | |||||
#include "../juce_audio_formats/juce_audio_formats.h" | |||||
#include <juce_events/juce_events.h> | |||||
#include <juce_audio_basics/juce_audio_basics.h> | |||||
//============================================================================== | //============================================================================== | ||||
/** Config: JUCE_ASIO | /** Config: JUCE_ASIO | ||||
@@ -90,21 +117,6 @@ | |||||
#endif | #endif | ||||
#endif | #endif | ||||
//============================================================================== | |||||
/** Config: JUCE_USE_CDREADER | |||||
Enables the AudioCDReader class (on supported platforms). | |||||
*/ | |||||
#ifndef JUCE_USE_CDREADER | |||||
#define JUCE_USE_CDREADER 0 | |||||
#endif | |||||
/** Config: JUCE_USE_CDBURNER | |||||
Enables the AudioCDBurner class (on supported platforms). | |||||
*/ | |||||
#ifndef JUCE_USE_CDBURNER | |||||
#define JUCE_USE_CDBURNER 0 | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
namespace juce | namespace juce | ||||
{ | { | ||||
@@ -117,8 +129,6 @@ namespace juce | |||||
#include "midi_io/juce_MidiOutput.h" | #include "midi_io/juce_MidiOutput.h" | ||||
#include "sources/juce_AudioSourcePlayer.h" | #include "sources/juce_AudioSourcePlayer.h" | ||||
#include "sources/juce_AudioTransportSource.h" | #include "sources/juce_AudioTransportSource.h" | ||||
#include "audio_cd/juce_AudioCDBurner.h" | |||||
#include "audio_cd/juce_AudioCDReader.h" | |||||
#include "audio_io/juce_AudioDeviceManager.h" | #include "audio_io/juce_AudioDeviceManager.h" | ||||
} | } | ||||
@@ -71,8 +71,7 @@ public: | |||||
// the normal message, handle it now.. | // the normal message, handle it now.. | ||||
if (*d >= 0xf8 && *d <= 0xfe) | if (*d >= 0xf8 && *d <= 0xfe) | ||||
{ | { | ||||
const MidiMessage m (*d++, time); | |||||
callback.handleIncomingMidiMessage (input, m); | |||||
callback.handleIncomingMidiMessage (input, MidiMessage (*d++, time)); | |||||
--numBytes; | --numBytes; | ||||
} | } | ||||
else | else | ||||
@@ -83,7 +82,15 @@ public: | |||||
data[len++] = *d++; | data[len++] = *d++; | ||||
--numBytes; | --numBytes; | ||||
if (len >= MidiMessage::getMessageLengthFromFirstByte (data[0])) | |||||
const uint8 firstByte = data[0]; | |||||
if (firstByte < 0x80 || firstByte == 0xf7) | |||||
{ | |||||
len = 0; | |||||
break; // ignore this malformed MIDI message.. | |||||
} | |||||
if (len >= MidiMessage::getMessageLengthFromFirstByte (firstByte)) | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
@@ -153,7 +153,7 @@ JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceM | |||||
class AndroidMidiDeviceManager | class AndroidMidiDeviceManager | ||||
{ | { | ||||
public: | public: | ||||
AndroidMidiDeviceManager () | |||||
AndroidMidiDeviceManager() | |||||
: deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager)) | : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager)) | ||||
{ | { | ||||
} | } | ||||
@@ -332,7 +332,7 @@ private: | |||||
void run() override | void run() override | ||||
{ | { | ||||
setThreadToAudioPriority (); | |||||
setThreadToAudioPriority(); | |||||
if (recorder != nullptr) recorder->start(); | if (recorder != nullptr) recorder->start(); | ||||
if (player != nullptr) player->start(); | if (player != nullptr) player->start(); | ||||
@@ -341,7 +341,7 @@ private: | |||||
processBuffers(); | processBuffers(); | ||||
} | } | ||||
void setThreadToAudioPriority () | |||||
void setThreadToAudioPriority() | |||||
{ | { | ||||
// see android.os.Process.THREAD_PRIORITY_AUDIO | // see android.os.Process.THREAD_PRIORITY_AUDIO | ||||
const int THREAD_PRIORITY_AUDIO = -16; | const int THREAD_PRIORITY_AUDIO = -16; | ||||
@@ -287,7 +287,14 @@ public: | |||||
return r; | return r; | ||||
} | } | ||||
int getDefaultBufferSize() override { return 256; } | |||||
int getDefaultBufferSize() override | |||||
{ | |||||
#if TARGET_IPHONE_SIMULATOR | |||||
return 512; | |||||
#else | |||||
return 256; | |||||
#endif | |||||
} | |||||
String open (const BigInteger& inputChannelsWanted, | String open (const BigInteger& inputChannelsWanted, | ||||
const BigInteger& outputChannelsWanted, | const BigInteger& outputChannelsWanted, | ||||
@@ -431,6 +438,8 @@ public: | |||||
void handleStatusChange (bool enabled, const char* reason) | void handleStatusChange (bool enabled, const char* reason) | ||||
{ | { | ||||
const ScopedLock myScopedLock (callbackLock); | |||||
JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason); | JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason); | ||||
isRunning = enabled; | isRunning = enabled; | ||||
@@ -447,6 +456,8 @@ public: | |||||
void handleRouteChange (const char* reason) | void handleRouteChange (const char* reason) | ||||
{ | { | ||||
const ScopedLock myScopedLock (callbackLock); | |||||
JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason); | JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason); | ||||
fixAudioRouteIfSetToReceiver(); | fixAudioRouteIfSetToReceiver(); | ||||
@@ -518,9 +529,9 @@ private: | |||||
if (audioInputIsAvailable && numInputChannels > 0) | if (audioInputIsAvailable && numInputChannels > 0) | ||||
err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data); | err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data); | ||||
const ScopedLock sl (callbackLock); | |||||
const ScopedTryLock stl (callbackLock); | |||||
if (callback != nullptr) | |||||
if (stl.isLocked() && callback != nullptr) | |||||
{ | { | ||||
if ((int) numFrames > floatData.getNumSamples()) | if ((int) numFrames > floatData.getNumSamples()) | ||||
prepareFloatBuffers ((int) numFrames); | prepareFloatBuffers ((int) numFrames); | ||||
@@ -679,7 +690,23 @@ private: | |||||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format)); | AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format)); | ||||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format)); | AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format)); | ||||
UInt32 framesPerSlice; | |||||
UInt32 dataSize = sizeof (framesPerSlice); | |||||
AudioUnitInitialize (audioUnit); | AudioUnitInitialize (audioUnit); | ||||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, | |||||
kAudioUnitScope_Global, 0, &actualBufferSize, sizeof (actualBufferSize)); | |||||
if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, | |||||
kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr | |||||
&& dataSize == sizeof (framesPerSlice) && static_cast<int> (framesPerSlice) != actualBufferSize) | |||||
{ | |||||
actualBufferSize = static_cast<int> (framesPerSlice); | |||||
prepareFloatBuffers (actualBufferSize); | |||||
} | |||||
return true; | return true; | ||||
} | } | ||||
@@ -755,8 +782,24 @@ void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) c | |||||
void AudioSessionHolder::handleRouteChange (const char* reason) const | void AudioSessionHolder::handleRouteChange (const char* reason) const | ||||
{ | { | ||||
for (auto device: activeDevices) | |||||
device->handleRouteChange (reason); | |||||
struct RouteChangeMessage : public CallbackMessage | |||||
{ | |||||
RouteChangeMessage (Array<iOSAudioIODevice*> devs, const char* r) | |||||
: devices (devs), changeReason (r) | |||||
{ | |||||
} | |||||
void messageCallback() override | |||||
{ | |||||
for (auto device: devices) | |||||
device->handleRouteChange (changeReason); | |||||
} | |||||
Array<iOSAudioIODevice*> devices; | |||||
const char* changeReason; | |||||
}; | |||||
(new RouteChangeMessage (activeDevices, reason))->post(); | |||||
} | } | ||||
#undef JUCE_NSERROR_CHECK | #undef JUCE_NSERROR_CHECK |
@@ -183,7 +183,7 @@ public: | |||||
// open output ports | // open output ports | ||||
const StringArray outputChannels (getOutputChannelNames()); | const StringArray outputChannels (getOutputChannelNames()); | ||||
for (int i = 0; i < outputChannels.size (); ++i) | |||||
for (int i = 0; i < outputChannels.size(); ++i) | |||||
{ | { | ||||
String outputName; | String outputName; | ||||
outputName << "out_" << ++totalNumberOfOutputChannels; | outputName << "out_" << ++totalNumberOfOutputChannels; | ||||
@@ -290,6 +290,8 @@ public: | |||||
} | } | ||||
} | } | ||||
updateActivePorts(); | |||||
return lastError; | return lastError; | ||||
} | } | ||||
@@ -45,37 +45,17 @@ class AlsaClient : public ReferenceCountedObject | |||||
public: | public: | ||||
typedef ReferenceCountedObjectPtr<AlsaClient> Ptr; | typedef ReferenceCountedObjectPtr<AlsaClient> Ptr; | ||||
AlsaClient (bool forInput) | |||||
: input (forInput), handle (nullptr) | |||||
static Ptr getInstance (bool forInput) | |||||
{ | { | ||||
snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||||
: SND_SEQ_OPEN_OUTPUT, 0); | |||||
} | |||||
~AlsaClient() | |||||
{ | |||||
if (handle != nullptr) | |||||
{ | |||||
snd_seq_close (handle); | |||||
handle = nullptr; | |||||
} | |||||
jassert (activeCallbacks.size() == 0); | |||||
AlsaClient*& instance = (forInput ? inInstance : outInstance); | |||||
if (instance == nullptr) | |||||
instance = new AlsaClient (forInput); | |||||
if (inputThread) | |||||
{ | |||||
inputThread->stopThread (3000); | |||||
inputThread = nullptr; | |||||
} | |||||
return instance; | |||||
} | } | ||||
bool isInput() const noexcept { return input; } | bool isInput() const noexcept { return input; } | ||||
void setName (const String& name) | |||||
{ | |||||
snd_seq_set_client_name (handle, name.toUTF8()); | |||||
} | |||||
void registerCallback (AlsaPortAndCallback* cb) | void registerCallback (AlsaPortAndCallback* cb) | ||||
{ | { | ||||
if (cb != nullptr) | if (cb != nullptr) | ||||
@@ -103,7 +83,8 @@ public: | |||||
inputThread->signalThreadShouldExit(); | inputThread->signalThreadShouldExit(); | ||||
} | } | ||||
void handleIncomingMidiMessage (const MidiMessage& message, int port); | |||||
void handleIncomingMidiMessage (snd_seq_event*, const MidiMessage&); | |||||
void handlePartialSysexMessage (snd_seq_event*, const uint8*, int, double); | |||||
snd_seq_t* get() const noexcept { return handle; } | snd_seq_t* get() const noexcept { return handle; } | ||||
@@ -114,12 +95,56 @@ private: | |||||
Array<AlsaPortAndCallback*> activeCallbacks; | Array<AlsaPortAndCallback*> activeCallbacks; | ||||
CriticalSection callbackLock; | CriticalSection callbackLock; | ||||
static AlsaClient* inInstance; | |||||
static AlsaClient* outInstance; | |||||
//============================================================================== | |||||
friend class ReferenceCountedObjectPtr<AlsaClient>; | |||||
friend struct ContainerDeletePolicy<AlsaClient>; | |||||
AlsaClient (bool forInput) | |||||
: input (forInput), handle (nullptr) | |||||
{ | |||||
AlsaClient*& instance = (input ? inInstance : outInstance); | |||||
jassert (instance == nullptr); | |||||
instance = this; | |||||
snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||||
: SND_SEQ_OPEN_OUTPUT, 0); | |||||
snd_seq_set_client_name (handle, forInput ? JUCE_ALSA_MIDI_INPUT_NAME | |||||
: JUCE_ALSA_MIDI_OUTPUT_NAME); | |||||
} | |||||
~AlsaClient() | |||||
{ | |||||
AlsaClient*& instance = (input ? inInstance : outInstance); | |||||
jassert (instance != nullptr); | |||||
instance = nullptr; | |||||
if (handle != nullptr) | |||||
{ | |||||
snd_seq_close (handle); | |||||
handle = nullptr; | |||||
} | |||||
jassert (activeCallbacks.size() == 0); | |||||
if (inputThread) | |||||
{ | |||||
inputThread->stopThread (3000); | |||||
inputThread = nullptr; | |||||
} | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
class MidiInputThread : public Thread | class MidiInputThread : public Thread | ||||
{ | { | ||||
public: | public: | ||||
MidiInputThread (AlsaClient& c) | MidiInputThread (AlsaClient& c) | ||||
: Thread ("Juce MIDI Input"), client (c) | |||||
: Thread ("Juce MIDI Input"), client (c), concatenator (2048) | |||||
{ | { | ||||
jassert (client.input && client.get() != nullptr); | jassert (client.input && client.get() != nullptr); | ||||
} | } | ||||
@@ -159,13 +184,9 @@ private: | |||||
snd_midi_event_reset_decode (midiParser); | snd_midi_event_reset_decode (midiParser); | ||||
if (numBytes > 0) | |||||
{ | |||||
const MidiMessage message ((const uint8*) buffer, (int) numBytes, | |||||
Time::getMillisecondCounter() * 0.001); | |||||
client.handleIncomingMidiMessage (message, inputEvent->dest.port); | |||||
} | |||||
concatenator.pushMidiData (buffer, (int) numBytes, | |||||
Time::getMillisecondCounter() * 0.001, | |||||
inputEvent, client); | |||||
snd_seq_free_event (inputEvent); | snd_seq_free_event (inputEvent); | ||||
} | } | ||||
@@ -180,29 +201,14 @@ private: | |||||
private: | private: | ||||
AlsaClient& client; | AlsaClient& client; | ||||
MidiDataConcatenator concatenator; | |||||
}; | }; | ||||
ScopedPointer<MidiInputThread> inputThread; | ScopedPointer<MidiInputThread> inputThread; | ||||
}; | }; | ||||
static AlsaClient::Ptr globalAlsaSequencerIn() | |||||
{ | |||||
static AlsaClient::Ptr global (new AlsaClient (true)); | |||||
return global; | |||||
} | |||||
static AlsaClient::Ptr globalAlsaSequencerOut() | |||||
{ | |||||
static AlsaClient::Ptr global (new AlsaClient (false)); | |||||
return global; | |||||
} | |||||
static AlsaClient::Ptr globalAlsaSequencer (bool input) | |||||
{ | |||||
return input ? globalAlsaSequencerIn() | |||||
: globalAlsaSequencerOut(); | |||||
} | |||||
AlsaClient* AlsaClient::inInstance = nullptr; | |||||
AlsaClient* AlsaClient::outInstance = nullptr; | |||||
//============================================================================== | //============================================================================== | ||||
// represents an input or output port of the supplied AlsaClient | // represents an input or output port of the supplied AlsaClient | ||||
@@ -282,6 +288,11 @@ public: | |||||
callback->handleIncomingMidiMessage (midiInput, message); | callback->handleIncomingMidiMessage (midiInput, message); | ||||
} | } | ||||
void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp) | |||||
{ | |||||
callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp); | |||||
} | |||||
private: | private: | ||||
AlsaPort port; | AlsaPort port; | ||||
MidiInput* midiInput; | MidiInput* midiInput; | ||||
@@ -291,14 +302,22 @@ private: | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlsaPortAndCallback) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlsaPortAndCallback) | ||||
}; | }; | ||||
void AlsaClient::handleIncomingMidiMessage (const MidiMessage& message, int port) | |||||
void AlsaClient::handleIncomingMidiMessage (snd_seq_event_t* event, const MidiMessage& message) | |||||
{ | { | ||||
const ScopedLock sl (callbackLock); | const ScopedLock sl (callbackLock); | ||||
if (AlsaPortAndCallback* const cb = activeCallbacks[port]) | |||||
if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port]) | |||||
cb->handleIncomingMidiMessage (message); | cb->handleIncomingMidiMessage (message); | ||||
} | } | ||||
void AlsaClient::handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp) | |||||
{ | |||||
const ScopedLock sl (callbackLock); | |||||
if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port]) | |||||
cb->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, | static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, | ||||
snd_seq_client_info_t* clientInfo, | snd_seq_client_info_t* clientInfo, | ||||
@@ -325,19 +344,23 @@ static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, | |||||
&& (snd_seq_port_info_get_capability (portInfo) & (forInput ? SND_SEQ_PORT_CAP_READ | && (snd_seq_port_info_get_capability (portInfo) & (forInput ? SND_SEQ_PORT_CAP_READ | ||||
: SND_SEQ_PORT_CAP_WRITE)) != 0) | : SND_SEQ_PORT_CAP_WRITE)) != 0) | ||||
{ | { | ||||
deviceNamesFound.add (snd_seq_client_info_get_name (clientInfo)); | |||||
const String clientName = snd_seq_client_info_get_name (clientInfo); | |||||
const String portName = snd_seq_port_info_get_name(portInfo); | |||||
if (clientName == portName) | |||||
deviceNamesFound.add (clientName); | |||||
else | |||||
deviceNamesFound.add (clientName + ": " + portName); | |||||
if (deviceNamesFound.size() == deviceIndexToOpen + 1) | if (deviceNamesFound.size() == deviceIndexToOpen + 1) | ||||
{ | { | ||||
const int sourcePort = snd_seq_port_info_get_port (portInfo); | const int sourcePort = snd_seq_port_info_get_port (portInfo); | ||||
const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||||
if (sourcePort != -1) | if (sourcePort != -1) | ||||
{ | { | ||||
const String name (forInput ? JUCE_ALSA_MIDI_INPUT_NAME | |||||
: JUCE_ALSA_MIDI_OUTPUT_NAME); | |||||
seq->setName (name); | |||||
port.createPort (seq, name, forInput); | |||||
const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||||
port.createPort (seq, portName, forInput); | |||||
port.connectWith (sourceClient, sourcePort); | port.connectWith (sourceClient, sourcePort); | ||||
} | } | ||||
} | } | ||||
@@ -355,7 +378,7 @@ static AlsaPort iterateMidiDevices (const bool forInput, | |||||
const int deviceIndexToOpen) | const int deviceIndexToOpen) | ||||
{ | { | ||||
AlsaPort port; | AlsaPort port; | ||||
const AlsaClient::Ptr client (globalAlsaSequencer (forInput)); | |||||
const AlsaClient::Ptr client (AlsaClient::getInstance (forInput)); | |||||
if (snd_seq_t* const seqHandle = client->get()) | if (snd_seq_t* const seqHandle = client->get()) | ||||
{ | { | ||||
@@ -387,19 +410,6 @@ static AlsaPort iterateMidiDevices (const bool forInput, | |||||
return port; | return port; | ||||
} | } | ||||
AlsaPort createMidiDevice (const bool forInput, const String& deviceNameToOpen) | |||||
{ | |||||
AlsaPort port; | |||||
AlsaClient::Ptr client (new AlsaClient (forInput)); | |||||
if (client->get()) | |||||
{ | |||||
client->setName (deviceNameToOpen + (forInput ? " Input" : " Output")); | |||||
port.createPort (client, forInput ? "in" : "out", forInput); | |||||
} | |||||
return port; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
class MidiOutputDevice | class MidiOutputDevice | ||||
@@ -450,7 +460,7 @@ public: | |||||
numBytes -= numSent; | numBytes -= numSent; | ||||
data += numSent; | data += numSent; | ||||
snd_seq_ev_set_source (&event, 0); | |||||
snd_seq_ev_set_source (&event, port.portId); | |||||
snd_seq_ev_set_subs (&event); | snd_seq_ev_set_subs (&event); | ||||
snd_seq_ev_set_direct (&event); | snd_seq_ev_set_direct (&event); | ||||
@@ -507,8 +517,11 @@ MidiOutput* MidiOutput::openDevice (int deviceIndex) | |||||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | ||||
{ | { | ||||
MidiOutput* newDevice = nullptr; | MidiOutput* newDevice = nullptr; | ||||
AlsaPort port; | |||||
const AlsaClient::Ptr client (AlsaClient::getInstance (false)); | |||||
AlsaPort port (createMidiDevice (false, deviceName)); | |||||
port.createPort (client, deviceName, false); | |||||
if (port.isValid()) | if (port.isValid()) | ||||
{ | { | ||||
@@ -584,8 +597,11 @@ MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | |||||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | ||||
{ | { | ||||
MidiInput* newDevice = nullptr; | MidiInput* newDevice = nullptr; | ||||
AlsaPort port; | |||||
const AlsaClient::Ptr client (AlsaClient::getInstance (true)); | |||||
AlsaPort port (createMidiDevice (true, deviceName)); | |||||
port.createPort (client, deviceName, true); | |||||
if (port.isValid()) | if (port.isValid()) | ||||
{ | { | ||||
@@ -149,6 +149,7 @@ bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool mute) { return SystemVol | |||||
struct CoreAudioClasses | struct CoreAudioClasses | ||||
{ | { | ||||
class CoreAudioIODeviceType; | |||||
class CoreAudioIODevice; | class CoreAudioIODevice; | ||||
//============================================================================== | //============================================================================== | ||||
@@ -691,7 +692,7 @@ public: | |||||
{ const ScopedLock sl (callbackLock); } | { const ScopedLock sl (callbackLock); } | ||||
// wait until it's definately stopped calling back.. | |||||
// wait until it's definitely stopped calling back.. | |||||
for (int i = 40; --i >= 0;) | for (int i = 40; --i >= 0;) | ||||
{ | { | ||||
Thread::sleep (50); | Thread::sleep (50); | ||||
@@ -847,6 +848,11 @@ private: | |||||
intern->deviceDetailsChanged(); | intern->deviceDetailsChanged(); | ||||
break; | break; | ||||
case kAudioObjectPropertyOwnedObjects: | |||||
intern->stop (false); | |||||
intern->owner.deviceType.triggerAsyncAudioDeviceListChange(); | |||||
break; | |||||
case kAudioDevicePropertyBufferSizeRange: | case kAudioDevicePropertyBufferSizeRange: | ||||
case kAudioDevicePropertyVolumeScalar: | case kAudioDevicePropertyVolumeScalar: | ||||
case kAudioDevicePropertyMute: | case kAudioDevicePropertyMute: | ||||
@@ -902,10 +908,12 @@ private: | |||||
class CoreAudioIODevice : public AudioIODevice | class CoreAudioIODevice : public AudioIODevice | ||||
{ | { | ||||
public: | public: | ||||
CoreAudioIODevice (const String& deviceName, | |||||
CoreAudioIODevice (CoreAudioIODeviceType& dt, | |||||
const String& deviceName, | |||||
AudioDeviceID inputDeviceId, const int inputIndex_, | AudioDeviceID inputDeviceId, const int inputIndex_, | ||||
AudioDeviceID outputDeviceId, const int outputIndex_) | AudioDeviceID outputDeviceId, const int outputIndex_) | ||||
: AudioIODevice (deviceName, "CoreAudio"), | : AudioIODevice (deviceName, "CoreAudio"), | ||||
deviceType (dt), | |||||
inputIndex (inputIndex_), | inputIndex (inputIndex_), | ||||
outputIndex (outputIndex_), | outputIndex (outputIndex_), | ||||
isOpen_ (false), | isOpen_ (false), | ||||
@@ -1060,6 +1068,12 @@ public: | |||||
return lastError; | return lastError; | ||||
} | } | ||||
void audioDeviceListChanged() | |||||
{ | |||||
deviceType.audioDeviceListChanged(); | |||||
} | |||||
CoreAudioIODeviceType& deviceType; | |||||
int inputIndex, outputIndex; | int inputIndex, outputIndex; | ||||
private: | private: | ||||
@@ -1745,7 +1759,8 @@ private: | |||||
//============================================================================== | //============================================================================== | ||||
class CoreAudioIODeviceType : public AudioIODeviceType | |||||
class CoreAudioIODeviceType : public AudioIODeviceType, | |||||
private AsyncUpdater | |||||
{ | { | ||||
public: | public: | ||||
CoreAudioIODeviceType() | CoreAudioIODeviceType() | ||||
@@ -1771,7 +1786,7 @@ public: | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
void scanForDevices() | |||||
void scanForDevices() override | |||||
{ | { | ||||
hasScanned = true; | hasScanned = true; | ||||
@@ -1827,7 +1842,7 @@ public: | |||||
outputDeviceNames.appendNumbersToDuplicates (false, true); | outputDeviceNames.appendNumbersToDuplicates (false, true); | ||||
} | } | ||||
StringArray getDeviceNames (bool wantInputNames) const | |||||
StringArray getDeviceNames (bool wantInputNames) const override | |||||
{ | { | ||||
jassert (hasScanned); // need to call scanForDevices() before doing this | jassert (hasScanned); // need to call scanForDevices() before doing this | ||||
@@ -1835,7 +1850,7 @@ public: | |||||
: outputDeviceNames; | : outputDeviceNames; | ||||
} | } | ||||
int getDefaultDeviceIndex (bool forInput) const | |||||
int getDefaultDeviceIndex (bool forInput) const override | |||||
{ | { | ||||
jassert (hasScanned); // need to call scanForDevices() before doing this | jassert (hasScanned); // need to call scanForDevices() before doing this | ||||
@@ -1870,7 +1885,7 @@ public: | |||||
return 0; | return 0; | ||||
} | } | ||||
int getIndexOfDevice (AudioIODevice* device, bool asInput) const | |||||
int getIndexOfDevice (AudioIODevice* device, bool asInput) const override | |||||
{ | { | ||||
jassert (hasScanned); // need to call scanForDevices() before doing this | jassert (hasScanned); // need to call scanForDevices() before doing this | ||||
@@ -1894,10 +1909,10 @@ public: | |||||
return -1; | return -1; | ||||
} | } | ||||
bool hasSeparateInputsAndOutputs() const { return true; } | |||||
bool hasSeparateInputsAndOutputs() const override { return true; } | |||||
AudioIODevice* createDevice (const String& outputDeviceName, | AudioIODevice* createDevice (const String& outputDeviceName, | ||||
const String& inputDeviceName) | |||||
const String& inputDeviceName) override | |||||
{ | { | ||||
jassert (hasScanned); // need to call scanForDevices() before doing this | jassert (hasScanned); // need to call scanForDevices() before doing this | ||||
@@ -1913,15 +1928,15 @@ public: | |||||
String combinedName (outputDeviceName.isEmpty() ? inputDeviceName : outputDeviceName); | String combinedName (outputDeviceName.isEmpty() ? inputDeviceName : outputDeviceName); | ||||
if (inputDeviceID == outputDeviceID) | if (inputDeviceID == outputDeviceID) | ||||
return new CoreAudioIODevice (combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); | |||||
return new CoreAudioIODevice (*this, combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); | |||||
ScopedPointer<CoreAudioIODevice> in, out; | ScopedPointer<CoreAudioIODevice> in, out; | ||||
if (inputDeviceID != 0) | if (inputDeviceID != 0) | ||||
in = new CoreAudioIODevice (inputDeviceName, inputDeviceID, inputIndex, 0, -1); | |||||
in = new CoreAudioIODevice (*this, inputDeviceName, inputDeviceID, inputIndex, 0, -1); | |||||
if (outputDeviceID != 0) | if (outputDeviceID != 0) | ||||
out = new CoreAudioIODevice (outputDeviceName, 0, -1, outputDeviceID, outputIndex); | |||||
out = new CoreAudioIODevice (*this, outputDeviceName, 0, -1, outputDeviceID, outputIndex); | |||||
if (in == nullptr) return out.release(); | if (in == nullptr) return out.release(); | ||||
if (out == nullptr) return in.release(); | if (out == nullptr) return in.release(); | ||||
@@ -1932,6 +1947,17 @@ public: | |||||
return combo.release(); | return combo.release(); | ||||
} | } | ||||
void audioDeviceListChanged() | |||||
{ | |||||
scanForDevices(); | |||||
callDeviceChangeListeners(); | |||||
} | |||||
void triggerAsyncAudioDeviceListChange() | |||||
{ | |||||
triggerAsyncUpdate(); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
private: | private: | ||||
StringArray inputDeviceNames, outputDeviceNames; | StringArray inputDeviceNames, outputDeviceNames; | ||||
@@ -1969,18 +1995,17 @@ private: | |||||
return total; | return total; | ||||
} | } | ||||
void audioDeviceListChanged() | |||||
{ | |||||
scanForDevices(); | |||||
callDeviceChangeListeners(); | |||||
} | |||||
static OSStatus hardwareListenerProc (AudioDeviceID, UInt32, const AudioObjectPropertyAddress*, void* clientData) | static OSStatus hardwareListenerProc (AudioDeviceID, UInt32, const AudioObjectPropertyAddress*, void* clientData) | ||||
{ | { | ||||
static_cast<CoreAudioIODeviceType*> (clientData)->audioDeviceListChanged(); | static_cast<CoreAudioIODeviceType*> (clientData)->audioDeviceListChanged(); | ||||
return noErr; | return noErr; | ||||
} | } | ||||
void handleAsyncUpdate() override | |||||
{ | |||||
audioDeviceListChanged(); | |||||
} | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODeviceType) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODeviceType) | ||||
}; | }; | ||||
@@ -171,6 +171,10 @@ namespace CoreMidiHelpers | |||||
static StringArray findDevices (const bool forInput) | static StringArray findDevices (const bool forInput) | ||||
{ | { | ||||
// It seems that OSX can be a bit picky about the thread that's first used to | |||||
// search for devices. It's safest to use the message thread for calling this. | |||||
jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||||
const ItemCount num = forInput ? MIDIGetNumberOfSources() | const ItemCount num = forInput ? MIDIGetNumberOfSources() | ||||
: MIDIGetNumberOfDestinations(); | : MIDIGetNumberOfDestinations(); | ||||
StringArray s; | StringArray s; | ||||
@@ -216,6 +220,14 @@ namespace CoreMidiHelpers | |||||
// correctly when called from the message thread! | // correctly when called from the message thread! | ||||
jassert (MessageManager::getInstance()->isThisTheMessageThread()); | jassert (MessageManager::getInstance()->isThisTheMessageThread()); | ||||
#if TARGET_OS_SIMULATOR | |||||
// Enable MIDI for iOS simulator | |||||
MIDINetworkSession* session = [MIDINetworkSession defaultSession]; | |||||
session.enabled = YES; | |||||
session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; | |||||
#endif | |||||
CoreMidiHelpers::ScopedCFString name; | CoreMidiHelpers::ScopedCFString name; | ||||
name.cfString = getGlobalMidiClientName().toCFString(); | name.cfString = getGlobalMidiClientName().toCFString(); | ||||
CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | ||||
@@ -365,17 +365,18 @@ public: | |||||
void updateSampleRates() | void updateSampleRates() | ||||
{ | { | ||||
// find a list of sample rates.. | // find a list of sample rates.. | ||||
const int possibleSampleRates[] = { 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; | |||||
Array<double> newRates; | Array<double> newRates; | ||||
if (asioObject != nullptr) | if (asioObject != nullptr) | ||||
{ | { | ||||
const int possibleSampleRates[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; | |||||
for (int index = 0; index < numElementsInArray (possibleSampleRates); ++index) | for (int index = 0; index < numElementsInArray (possibleSampleRates); ++index) | ||||
if (asioObject->canSampleRate ((double) possibleSampleRates[index]) == 0) | if (asioObject->canSampleRate ((double) possibleSampleRates[index]) == 0) | ||||
newRates.add ((double) possibleSampleRates[index]); | newRates.add ((double) possibleSampleRates[index]); | ||||
} | } | ||||
if (newRates.size() == 0) | |||||
if (newRates.isEmpty()) | |||||
{ | { | ||||
double cr = getSampleRate(); | double cr = getSampleRate(); | ||||
JUCE_ASIO_LOG ("No sample rates supported - current rate: " + String ((int) cr)); | JUCE_ASIO_LOG ("No sample rates supported - current rate: " + String ((int) cr)); | ||||
@@ -1571,7 +1572,7 @@ private: | |||||
DWORD dsize = sizeof (pathName); | DWORD dsize = sizeof (pathName); | ||||
if (RegQueryValueEx (pathKey, 0, 0, &dtype, (LPBYTE) pathName, &dsize) == ERROR_SUCCESS) | if (RegQueryValueEx (pathKey, 0, 0, &dtype, (LPBYTE) pathName, &dsize) == ERROR_SUCCESS) | ||||
// In older code, this used to check for the existance of the file, but there are situations | |||||
// In older code, this used to check for the existence of the file, but there are situations | |||||
// where our process doesn't have access to it, but where the driver still loads ok.. | // where our process doesn't have access to it, but where the driver still loads ok.. | ||||
ok = (pathName[0] != 0); | ok = (pathName[0] != 0); | ||||
@@ -96,11 +96,18 @@ bool check (HRESULT hr) | |||||
} | } | ||||
#if JUCE_MINGW | #if JUCE_MINGW | ||||
#define JUCE_COMCLASS(name, guid) \ | #define JUCE_COMCLASS(name, guid) \ | ||||
struct name; \ | struct name; \ | ||||
template<> struct UUIDGetter<name> { static CLSID get() { return uuidFromString (guid); } }; \ | template<> struct UUIDGetter<name> { static CLSID get() { return uuidFromString (guid); } }; \ | ||||
struct name | struct name | ||||
#ifdef __uuidof | |||||
#undef __uuidof | |||||
#endif | |||||
#define __uuidof(cls) UUIDGetter<cls>::get() | |||||
struct PROPERTYKEY | struct PROPERTYKEY | ||||
{ | { | ||||
GUID fmtid; | GUID fmtid; | ||||
@@ -60,7 +60,7 @@ public: | |||||
The source passed in will not be deleted by this object, so must be managed by | The source passed in will not be deleted by this object, so must be managed by | ||||
the caller. | the caller. | ||||
@param newSource the new input source to use. This may be zero | |||||
@param newSource the new input source to use. This may be a nullptr | |||||
@param readAheadBufferSize a size of buffer to use for reading ahead. If this | @param readAheadBufferSize a size of buffer to use for reading ahead. If this | ||||
is zero, no reading ahead will be done; if it's | is zero, no reading ahead will be done; if it's | ||||
greater than zero, a BufferingAudioSource will be used | greater than zero, a BufferingAudioSource will be used | ||||
@@ -41,11 +41,14 @@ | |||||
* before #including this file, otherwise SIZE_MAX might not be defined | * before #including this file, otherwise SIZE_MAX might not be defined | ||||
*/ | */ | ||||
#include <limits.h> /* for SIZE_MAX */ | |||||
#if HAVE_STDINT_H | |||||
#include <stdint.h> /* for SIZE_MAX in case limits.h didn't get it */ | |||||
#endif | |||||
#include <stdlib.h> /* for size_t, malloc(), etc */ | |||||
// JUCE: removed as JUCE already includes standard headers and including | |||||
// these in FlacNamespace will cause problems | |||||
//#include <limits.h> /* for SIZE_MAX */ | |||||
//#if HAVE_STDINT_H | |||||
//#include <stdint.h> /* for SIZE_MAX in case limits.h didn't get it */ | |||||
//#endif | |||||
//#include <stdlib.h> /* for size_t, malloc(), etc */ | |||||
#include "compat.h" | #include "compat.h" | ||||
#ifndef SIZE_MAX | #ifndef SIZE_MAX | ||||
@@ -35,7 +35,10 @@ | |||||
/* we need this since some compilers (like MSVC) leave assert()s on release code (and we don't want to use their ASSERT) */ | /* we need this since some compilers (like MSVC) leave assert()s on release code (and we don't want to use their ASSERT) */ | ||||
#ifdef DEBUG | #ifdef DEBUG | ||||
#include <assert.h> | |||||
// JUCE: removed as JUCE already includes standard headers and including | |||||
// these in FlacNamespace will cause problems | |||||
//#include <assert.h> | |||||
#define FLAC__ASSERT(x) assert(x) | #define FLAC__ASSERT(x) assert(x) | ||||
#define FLAC__ASSERT_DECLARATION(x) x | #define FLAC__ASSERT_DECLARATION(x) x | ||||
#else | #else | ||||
@@ -34,7 +34,10 @@ | |||||
#define FLAC__CALLBACK_H | #define FLAC__CALLBACK_H | ||||
#include "ordinals.h" | #include "ordinals.h" | ||||
#include <stdlib.h> /* for size_t */ | |||||
// JUCE: removed as JUCE already includes this and including stdlib | |||||
// in FlacNamespace will cause problems | |||||
//#include <stdlib.h> | |||||
/** \file include/FLAC/callback.h | /** \file include/FLAC/callback.h | ||||
* | * | ||||
@@ -39,15 +39,7 @@ | |||||
#ifndef FLAC__SHARE__COMPAT_H | #ifndef FLAC__SHARE__COMPAT_H | ||||
#define FLAC__SHARE__COMPAT_H | #define FLAC__SHARE__COMPAT_H | ||||
#if defined _WIN32 && !defined __CYGWIN__ | |||||
/* where MSVC puts unlink() */ | |||||
# include <io.h> | |||||
#else | |||||
# include <unistd.h> | |||||
#endif | |||||
#if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | ||||
#include <sys/types.h> /* for off_t */ | |||||
#define FLAC__off_t __int64 /* use this instead of off_t to fix the 2 GB limit */ | #define FLAC__off_t __int64 /* use this instead of off_t to fix the 2 GB limit */ | ||||
#if !defined __MINGW32__ | #if !defined __MINGW32__ | ||||
#define fseeko _fseeki64 | #define fseeko _fseeki64 | ||||
@@ -62,11 +54,6 @@ | |||||
#define FLAC__off_t off_t | #define FLAC__off_t off_t | ||||
#endif | #endif | ||||
#if HAVE_INTTYPES_H | |||||
#define __STDC_FORMAT_MACROS | |||||
#include <inttypes.h> | |||||
#endif | |||||
#if defined(_MSC_VER) | #if defined(_MSC_VER) | ||||
#define strtoll _strtoi64 | #define strtoll _strtoi64 | ||||
#define strtoull _strtoui64 | #define strtoull _strtoui64 | ||||
@@ -95,33 +82,13 @@ | |||||
#define FLAC__STRNCASECMP strncasecmp | #define FLAC__STRNCASECMP strncasecmp | ||||
#endif | #endif | ||||
#if defined _MSC_VER || defined __MINGW32__ || defined __CYGWIN__ || defined __EMX__ | |||||
#include <io.h> /* for _setmode(), chmod() */ | |||||
#include <fcntl.h> /* for _O_BINARY */ | |||||
#else | |||||
#include <unistd.h> /* for chown(), unlink() */ | |||||
#endif | |||||
#if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | |||||
#if defined __BORLANDC__ | |||||
#include <utime.h> /* for utime() */ | |||||
#else | |||||
#include <sys/utime.h> /* for utime() */ | |||||
#endif | |||||
#else | |||||
#include <sys/types.h> /* some flavors of BSD (like OS X) require this to get time_t */ | |||||
#include <utime.h> /* for utime() */ | |||||
#endif | |||||
#if defined _MSC_VER | #if defined _MSC_VER | ||||
# if _MSC_VER >= 1600 | # if _MSC_VER >= 1600 | ||||
/* Visual Studio 2010 has decent C99 support */ | /* Visual Studio 2010 has decent C99 support */ | ||||
# include <stdint.h> | |||||
# define PRIu64 "llu" | # define PRIu64 "llu" | ||||
# define PRId64 "lld" | # define PRId64 "lld" | ||||
# define PRIx64 "llx" | # define PRIx64 "llx" | ||||
# else | # else | ||||
# include <limits.h> | |||||
# ifndef UINT32_MAX | # ifndef UINT32_MAX | ||||
# define UINT32_MAX _UI32_MAX | # define UINT32_MAX _UI32_MAX | ||||
# endif | # endif | ||||
@@ -51,7 +51,9 @@ static inline unsigned short __builtin_bswap16(unsigned short a) | |||||
#elif defined HAVE_BYTESWAP_H /* Linux */ | #elif defined HAVE_BYTESWAP_H /* Linux */ | ||||
#include <byteswap.h> | |||||
// JUCE: removed as JUCE already includes standard headers and including | |||||
// these in FlacNamespace will cause problems | |||||
//#include <byteswap.h> | |||||
#define ENDSWAP_16(x) (bswap_16 (x)) | #define ENDSWAP_16(x) (bswap_16 (x)) | ||||
#define ENDSWAP_32(x) (bswap_32 (x)) | #define ENDSWAP_32(x) (bswap_32 (x)) | ||||
@@ -33,7 +33,6 @@ | |||||
#ifndef FLAC__METADATA_H | #ifndef FLAC__METADATA_H | ||||
#define FLAC__METADATA_H | #define FLAC__METADATA_H | ||||
#include <sys/types.h> /* for off_t */ | |||||
#include "export.h" | #include "export.h" | ||||
#include "callback.h" | #include "callback.h" | ||||
#include "format.h" | #include "format.h" | ||||
@@ -33,7 +33,6 @@ | |||||
#ifndef FLAC__STREAM_DECODER_H | #ifndef FLAC__STREAM_DECODER_H | ||||
#define FLAC__STREAM_DECODER_H | #define FLAC__STREAM_DECODER_H | ||||
#include <stdio.h> /* for FILE */ | |||||
#include "export.h" | #include "export.h" | ||||
#include "format.h" | #include "format.h" | ||||
@@ -33,7 +33,6 @@ | |||||
#ifndef FLAC__STREAM_ENCODER_H | #ifndef FLAC__STREAM_ENCODER_H | ||||
#define FLAC__STREAM_ENCODER_H | #define FLAC__STREAM_ENCODER_H | ||||
#include <stdio.h> /* for FILE */ | |||||
#include "export.h" | #include "export.h" | ||||
#include "format.h" | #include "format.h" | ||||
#include "stream_decoder.h" | #include "stream_decoder.h" | ||||
@@ -38,11 +38,6 @@ | |||||
extern "C" { | extern "C" { | ||||
#endif | #endif | ||||
#include <stdio.h> | |||||
#include <sys/stat.h> | |||||
#include <stdarg.h> | |||||
#include <windows.h> | |||||
int get_utf8_argv(int *argc, char ***argv); | int get_utf8_argv(int *argc, char ***argv); | ||||
int printf_utf8(const char *format, ...); | int printf_utf8(const char *format, ...); | ||||
@@ -668,13 +668,14 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
bool write (const int** data, int numSamples) override | bool write (const int** data, int numSamples) override | ||||
{ | { | ||||
jassert (numSamples >= 0); | |||||
jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel! | jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel! | ||||
if (writeFailed) | if (writeFailed) | ||||
return false; | return false; | ||||
const size_t bytes = (size_t) numSamples * numChannels * bitsPerSample / 8; | |||||
tempBlock.ensureSize ((size_t) bytes, false); | |||||
const size_t bytes = numChannels * (size_t) numSamples * bitsPerSample / 8; | |||||
tempBlock.ensureSize (bytes, false); | |||||
switch (bitsPerSample) | switch (bitsPerSample) | ||||
{ | { | ||||
@@ -695,13 +696,10 @@ public: | |||||
writeFailed = true; | writeFailed = true; | ||||
return false; | return false; | ||||
} | } | ||||
else | |||||
{ | |||||
bytesWritten += bytes; | |||||
lengthInSamples += (uint64) numSamples; | |||||
return true; | |||||
} | |||||
bytesWritten += bytes; | |||||
lengthInSamples += (uint64) numSamples; | |||||
return true; | |||||
} | } | ||||
private: | private: | ||||
@@ -24,17 +24,79 @@ | |||||
#if JUCE_USE_FLAC | #if JUCE_USE_FLAC | ||||
} | |||||
#if defined _WIN32 && !defined __CYGWIN__ | |||||
#include <io.h> | |||||
#else | |||||
#include <unistd.h> | |||||
#endif | |||||
#if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | |||||
#include <sys/types.h> /* for off_t */ | |||||
#endif | |||||
#if HAVE_INTTYPES_H | |||||
#define __STDC_FORMAT_MACROS | |||||
#include <inttypes.h> | |||||
#endif | |||||
#if defined _MSC_VER || defined __MINGW32__ || defined __CYGWIN__ || defined __EMX__ | |||||
#include <io.h> /* for _setmode(), chmod() */ | |||||
#include <fcntl.h> /* for _O_BINARY */ | |||||
#else | |||||
#include <unistd.h> /* for chown(), unlink() */ | |||||
#endif | |||||
#if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | |||||
#if defined __BORLANDC__ | |||||
#include <utime.h> /* for utime() */ | |||||
#else | |||||
#include <sys/utime.h> /* for utime() */ | |||||
#endif | |||||
#else | |||||
#include <sys/types.h> /* some flavors of BSD (like OS X) require this to get time_t */ | |||||
#include <utime.h> /* for utime() */ | |||||
#endif | |||||
#if defined _MSC_VER | |||||
#if _MSC_VER >= 1600 | |||||
#include <stdint.h> | |||||
#else | |||||
#include <limits.h> | |||||
#endif | |||||
#endif | |||||
#ifdef _WIN32 | |||||
#include <stdio.h> | |||||
#include <sys/stat.h> | |||||
#include <stdarg.h> | |||||
#include <windows.h> | |||||
#endif | |||||
#ifdef DEBUG | |||||
#include <assert.h> | |||||
#endif | |||||
#include <stdlib.h> | |||||
#include <stdio.h> | |||||
namespace juce | |||||
{ | |||||
namespace FlacNamespace | namespace FlacNamespace | ||||
{ | { | ||||
#if JUCE_INCLUDE_FLAC_CODE || ! defined (JUCE_INCLUDE_FLAC_CODE) | #if JUCE_INCLUDE_FLAC_CODE || ! defined (JUCE_INCLUDE_FLAC_CODE) | ||||
#undef VERSION | #undef VERSION | ||||
#define VERSION "1.2.1" | |||||
#define VERSION "1.3.1" | |||||
#define FLAC__NO_DLL 1 | #define FLAC__NO_DLL 1 | ||||
#if JUCE_MSVC | #if JUCE_MSVC | ||||
#pragma warning (disable: 4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4312 4505 4365 4005 4334 181 111) | #pragma warning (disable: 4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4312 4505 4365 4005 4334 181 111) | ||||
#else | |||||
#define HAVE_LROUND 1 | |||||
#endif | #endif | ||||
#if JUCE_MAC | #if JUCE_MAC | ||||
@@ -66,6 +128,7 @@ namespace FlacNamespace | |||||
#define __STDC_LIMIT_MACROS 1 | #define __STDC_LIMIT_MACROS 1 | ||||
#define flac_max jmax | #define flac_max jmax | ||||
#define flac_min jmin | #define flac_min jmin | ||||
#undef DEBUG // (some flac code dumps debug trace if the app defines this macro) | |||||
#include "flac/all.h" | #include "flac/all.h" | ||||
#include "flac/libFLAC/bitmath.c" | #include "flac/libFLAC/bitmath.c" | ||||
#include "flac/libFLAC/bitreader.c" | #include "flac/libFLAC/bitreader.c" | ||||
@@ -324,7 +387,8 @@ class FlacWriter : public AudioFormatWriter | |||||
{ | { | ||||
public: | public: | ||||
FlacWriter (OutputStream* const out, double rate, uint32 numChans, uint32 bits, int qualityOptionIndex) | FlacWriter (OutputStream* const out, double rate, uint32 numChans, uint32 bits, int qualityOptionIndex) | ||||
: AudioFormatWriter (out, flacFormatName, rate, numChans, bits) | |||||
: AudioFormatWriter (out, flacFormatName, rate, numChans, bits), | |||||
streamStartPos (output != nullptr ? jmax (output->getPosition(), 0ll) : 0ll) | |||||
{ | { | ||||
using namespace FlacNamespace; | using namespace FlacNamespace; | ||||
encoder = FLAC__stream_encoder_new(); | encoder = FLAC__stream_encoder_new(); | ||||
@@ -432,7 +496,7 @@ public: | |||||
packUint32 ((FLAC__uint32) info.total_samples, buffer + 14, 4); | packUint32 ((FLAC__uint32) info.total_samples, buffer + 14, 4); | ||||
memcpy (buffer + 18, info.md5sum, 16); | memcpy (buffer + 18, info.md5sum, 16); | ||||
const bool seekOk = output->setPosition (4); | |||||
const bool seekOk = output->setPosition (streamStartPos + 4); | |||||
ignoreUnused (seekOk); | ignoreUnused (seekOk); | ||||
// if this fails, you've given it an output stream that can't seek! It needs | // if this fails, you've given it an output stream that can't seek! It needs | ||||
@@ -482,6 +546,7 @@ public: | |||||
private: | private: | ||||
FlacNamespace::FLAC__StreamEncoder* encoder; | FlacNamespace::FLAC__StreamEncoder* encoder; | ||||
int64 streamStartPos; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacWriter) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacWriter) | ||||
}; | }; | ||||
@@ -222,7 +222,7 @@ public: | |||||
DisposeMovie (movie); | DisposeMovie (movie); | ||||
#if JUCE_MAC | #if JUCE_MAC | ||||
ExitMoviesOnThread (); | |||||
ExitMoviesOnThread(); | |||||
#endif | #endif | ||||
} | } | ||||
} | } | ||||
@@ -656,7 +656,10 @@ namespace WavFileHelpers | |||||
if (infoLength > 0) | if (infoLength > 0) | ||||
{ | { | ||||
infoLength = jlimit ((int64) 0, infoLength, (int64) input.readInt()); | |||||
infoLength = jmin (infoLength, (int64) input.readInt()); | |||||
if (infoLength <= 0) | |||||
return; | |||||
for (int i = 0; i < numElementsInArray (types); ++i) | for (int i = 0; i < numElementsInArray (types); ++i) | ||||
{ | { | ||||
@@ -664,7 +667,8 @@ namespace WavFileHelpers | |||||
{ | { | ||||
MemoryBlock mb; | MemoryBlock mb; | ||||
input.readIntoMemoryBlock (mb, (ssize_t) infoLength); | input.readIntoMemoryBlock (mb, (ssize_t) infoLength); | ||||
values.set (types[i], mb.toString()); | |||||
values.set (types[i], String::createStringFromData ((const char*) mb.getData(), | |||||
(int) mb.getSize())); | |||||
break; | break; | ||||
} | } | ||||
} | } | ||||
@@ -1258,7 +1262,7 @@ public: | |||||
if (writeFailed) | if (writeFailed) | ||||
return false; | return false; | ||||
const size_t bytes = numChannels * (unsigned int) numSamples * bitsPerSample / 8; | |||||
const size_t bytes = numChannels * (size_t) numSamples * bitsPerSample / 8; | |||||
tempBlock.ensureSize (bytes, false); | tempBlock.ensureSize (bytes, false); | ||||
switch (bitsPerSample) | switch (bitsPerSample) | ||||
@@ -166,6 +166,9 @@ public: | |||||
checkCoInitialiseCalled(); | checkCoInitialiseCalled(); | ||||
clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, | |||||
startSampleInFile, numSamples, lengthInSamples); | |||||
const int stride = numChannels * sizeof (int16); | const int stride = numChannels * sizeof (int16); | ||||
while (numSamples > 0) | while (numSamples > 0) | ||||
@@ -297,7 +300,7 @@ private: | |||||
sampleRate = inputFormat->nSamplesPerSec; | sampleRate = inputFormat->nSamplesPerSec; | ||||
numChannels = inputFormat->nChannels; | numChannels = inputFormat->nChannels; | ||||
bitsPerSample = inputFormat->wBitsPerSample; | |||||
bitsPerSample = inputFormat->wBitsPerSample != 0 ? inputFormat->wBitsPerSample : 16; | |||||
lengthInSamples = (lengthInNanoseconds * (int) sampleRate) / 10000000; | lengthInSamples = (lengthInNanoseconds * (int) sampleRate) / 10000000; | ||||
} | } | ||||
} | } | ||||
@@ -31,6 +31,8 @@ | |||||
#error "Incorrect use of JUCE cpp file" | #error "Incorrect use of JUCE cpp file" | ||||
#endif | #endif | ||||
#include "AppConfig.h" | |||||
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | ||||
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | ||||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | ||||
@@ -22,10 +22,36 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
/******************************************************************************* | |||||
The block below describes the properties of this module, and is read by | |||||
the Projucer to automatically generate project code that uses it. | |||||
For details about the syntax and how to create or use a module, see the | |||||
JUCE Module Format.txt file. | |||||
BEGIN_JUCE_MODULE_DECLARATION | |||||
ID: juce_audio_formats | |||||
vendor: juce | |||||
version: 4.3.0 | |||||
name: JUCE audio file format codecs | |||||
description: Classes for reading and writing various audio file formats. | |||||
website: http://www.juce.com/juce | |||||
license: GPL/Commercial | |||||
dependencies: juce_audio_basics | |||||
OSXFrameworks: CoreAudio CoreMIDI QuartzCore AudioToolbox | |||||
iOSFrameworks: AudioToolbox QuartzCore | |||||
END_JUCE_MODULE_DECLARATION | |||||
*******************************************************************************/ | |||||
#ifndef JUCE_AUDIO_FORMATS_H_INCLUDED | #ifndef JUCE_AUDIO_FORMATS_H_INCLUDED | ||||
#define JUCE_AUDIO_FORMATS_H_INCLUDED | #define JUCE_AUDIO_FORMATS_H_INCLUDED | ||||
#include "../juce_audio_basics/juce_audio_basics.h" | |||||
#include <juce_audio_basics/juce_audio_basics.h> | |||||
//============================================================================== | //============================================================================== | ||||
/** Config: JUCE_USE_FLAC | /** Config: JUCE_USE_FLAC | ||||
@@ -22,5 +22,188 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
namespace AudioPluginFormatHelpers | |||||
{ | |||||
struct CallbackInvoker | |||||
{ | |||||
struct InvokeOnMessageThread : public CallbackMessage | |||||
{ | |||||
InvokeOnMessageThread (AudioPluginInstance* inInstance, const String& inError, | |||||
AudioPluginFormat::InstantiationCompletionCallback* inCompletion, | |||||
CallbackInvoker* invoker) | |||||
: instance (inInstance), error (inError), compCallback (inCompletion), owner (invoker) | |||||
{} | |||||
void messageCallback() override { compCallback->completionCallback (instance, error); } | |||||
//============================================================================== | |||||
AudioPluginInstance* instance; | |||||
String error; | |||||
ScopedPointer<AudioPluginFormat::InstantiationCompletionCallback> compCallback; | |||||
ScopedPointer<CallbackInvoker> owner; | |||||
}; | |||||
//============================================================================== | |||||
CallbackInvoker (AudioPluginFormat::InstantiationCompletionCallback* cc) : completion (cc) | |||||
{} | |||||
void completionCallback (AudioPluginInstance* instance, const String& error) | |||||
{ | |||||
(new InvokeOnMessageThread (instance, error, completion, this))->post(); | |||||
} | |||||
static void staticCompletionCallback (void* userData, AudioPluginInstance* instance, const String& error) | |||||
{ | |||||
reinterpret_cast<CallbackInvoker*> (userData)->completionCallback (instance, error); | |||||
} | |||||
//============================================================================== | |||||
AudioPluginFormat::InstantiationCompletionCallback* completion; | |||||
}; | |||||
} | |||||
AudioPluginFormat::AudioPluginFormat() noexcept {} | AudioPluginFormat::AudioPluginFormat() noexcept {} | ||||
AudioPluginFormat::~AudioPluginFormat() {} | AudioPluginFormat::~AudioPluginFormat() {} | ||||
AudioPluginInstance* AudioPluginFormat::createInstanceFromDescription (const PluginDescription& desc, | |||||
double initialSampleRate, | |||||
int initialBufferSize) | |||||
{ | |||||
String errorMessage; | |||||
return createInstanceFromDescription (desc, initialSampleRate, initialBufferSize, errorMessage); | |||||
} | |||||
//============================================================================== | |||||
struct EventSignaler : public AudioPluginFormat::InstantiationCompletionCallback | |||||
{ | |||||
EventSignaler (WaitableEvent& inEvent, AudioPluginInstance*& inInstance, String& inErrorMessage) | |||||
: event (inEvent), outInstance (inInstance), outErrorMessage (inErrorMessage) | |||||
{} | |||||
void completionCallback (AudioPluginInstance* newInstance, const String& result) override | |||||
{ | |||||
outInstance = newInstance; | |||||
outErrorMessage = result; | |||||
event.signal(); | |||||
} | |||||
static void staticCompletionCallback (void* userData, AudioPluginInstance* pluginInstance, const String& error) | |||||
{ | |||||
reinterpret_cast<EventSignaler*> (userData)->completionCallback (pluginInstance, error); | |||||
} | |||||
WaitableEvent& event; | |||||
AudioPluginInstance*& outInstance; | |||||
String& outErrorMessage; | |||||
JUCE_DECLARE_NON_COPYABLE (EventSignaler) | |||||
}; | |||||
AudioPluginInstance* AudioPluginFormat::createInstanceFromDescription (const PluginDescription& desc, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
String& errorMessage) | |||||
{ | |||||
if (MessageManager::getInstance()->isThisTheMessageThread() | |||||
&& requiresUnblockedMessageThreadDuringCreation(desc)) | |||||
{ | |||||
errorMessage = NEEDS_TRANS ("This plug-in cannot be instantiated synchronously"); | |||||
return nullptr; | |||||
} | |||||
WaitableEvent waitForCreation; | |||||
AudioPluginInstance* instance = nullptr; | |||||
ScopedPointer<EventSignaler> eventSignaler (new EventSignaler (waitForCreation, instance, errorMessage)); | |||||
if (! MessageManager::getInstance()->isThisTheMessageThread()) | |||||
createPluginInstanceAsync (desc, initialSampleRate, initialBufferSize, eventSignaler.release()); | |||||
else | |||||
createPluginInstance (desc, initialSampleRate, initialBufferSize, | |||||
eventSignaler, EventSignaler::staticCompletionCallback); | |||||
waitForCreation.wait(); | |||||
return instance; | |||||
} | |||||
void AudioPluginFormat::createPluginInstanceAsync (const PluginDescription& description, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
AudioPluginFormat::InstantiationCompletionCallback* callback) | |||||
{ | |||||
if (MessageManager::getInstance()->isThisTheMessageThread()) | |||||
{ | |||||
createPluginInstanceOnMessageThread (description, initialSampleRate, initialBufferSize, callback); | |||||
return; | |||||
} | |||||
//============================================================================== | |||||
struct InvokeOnMessageThread : public CallbackMessage | |||||
{ | |||||
InvokeOnMessageThread (AudioPluginFormat* myself, | |||||
const PluginDescription& descriptionParam, | |||||
double initialSampleRateParam, | |||||
int initialBufferSizeParam, | |||||
AudioPluginFormat::InstantiationCompletionCallback* callbackParam) | |||||
: owner (myself), descr (descriptionParam), sampleRate (initialSampleRateParam), | |||||
bufferSize (initialBufferSizeParam), call (callbackParam) | |||||
{} | |||||
void messageCallback() override | |||||
{ | |||||
owner->createPluginInstanceOnMessageThread (descr, sampleRate, bufferSize, call); | |||||
} | |||||
AudioPluginFormat* owner; | |||||
PluginDescription descr; | |||||
double sampleRate; | |||||
int bufferSize; | |||||
AudioPluginFormat::InstantiationCompletionCallback* call; | |||||
}; | |||||
(new InvokeOnMessageThread (this, description, initialSampleRate, initialBufferSize, callback))->post(); | |||||
} | |||||
#if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||||
void AudioPluginFormat::createPluginInstanceAsync (const PluginDescription& description, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
std::function<void (AudioPluginInstance*, const String&)> f) | |||||
{ | |||||
struct CallbackInvoker : public AudioPluginFormat::InstantiationCompletionCallback | |||||
{ | |||||
CallbackInvoker (std::function<void (AudioPluginInstance*, const String&)> inCompletion) | |||||
: completion (inCompletion) | |||||
{} | |||||
void completionCallback (AudioPluginInstance* instance, const String& error) override | |||||
{ | |||||
completion (instance, error); | |||||
} | |||||
std::function<void (AudioPluginInstance*, const String&)> completion; | |||||
}; | |||||
createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, new CallbackInvoker (f)); | |||||
} | |||||
#endif | |||||
void AudioPluginFormat::createPluginInstanceOnMessageThread (const PluginDescription& description, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
AudioPluginFormat::InstantiationCompletionCallback* callback) | |||||
{ | |||||
jassert (callback != nullptr); | |||||
jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||||
//============================================================================== | |||||
//============================================================================== | |||||
AudioPluginFormatHelpers::CallbackInvoker* completion = new AudioPluginFormatHelpers::CallbackInvoker (callback); | |||||
createPluginInstance (description, initialSampleRate, initialBufferSize, completion, | |||||
AudioPluginFormatHelpers::CallbackInvoker::staticCompletionCallback); | |||||
} |
@@ -30,11 +30,20 @@ | |||||
/** | /** | ||||
The base class for a type of plugin format, such as VST, AudioUnit, LADSPA, etc. | The base class for a type of plugin format, such as VST, AudioUnit, LADSPA, etc. | ||||
@see AudioFormatManager | |||||
@see AudioPluginFormatManager | |||||
*/ | */ | ||||
class JUCE_API AudioPluginFormat | class JUCE_API AudioPluginFormat | ||||
{ | { | ||||
public: | public: | ||||
//============================================================================== | |||||
struct JUCE_API InstantiationCompletionCallback | |||||
{ | |||||
virtual ~InstantiationCompletionCallback() {} | |||||
virtual void completionCallback (AudioPluginInstance* instance, const String& error) = 0; | |||||
JUCE_LEAK_DETECTOR (InstantiationCompletionCallback) | |||||
}; | |||||
//============================================================================== | //============================================================================== | ||||
/** Destructor. */ | /** Destructor. */ | ||||
virtual ~AudioPluginFormat(); | virtual ~AudioPluginFormat(); | ||||
@@ -58,11 +67,36 @@ public: | |||||
const String& fileOrIdentifier) = 0; | const String& fileOrIdentifier) = 0; | ||||
/** Tries to recreate a type from a previously generated PluginDescription. | /** Tries to recreate a type from a previously generated PluginDescription. | ||||
@see PluginDescription::createInstance | |||||
@see AudioPluginFormatManager::createInstance | |||||
*/ | |||||
AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, | |||||
double initialSampleRate, | |||||
int initialBufferSize); | |||||
/** Same as above but with the possibility of returning an error message. | |||||
@see AudioPluginFormatManager::createInstance | |||||
*/ | */ | ||||
virtual AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc, | |||||
double initialSampleRate, | |||||
int initialBufferSize) = 0; | |||||
AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
String& errorMessage); | |||||
/** Tries to recreate a type from a previously generated PluginDescription. | |||||
@see AudioPluginFormatManager::createInstanceAsync | |||||
*/ | |||||
void createPluginInstanceAsync (const PluginDescription& description, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
InstantiationCompletionCallback* completionCallback); | |||||
#if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||||
void createPluginInstanceAsync (const PluginDescription& description, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
std::function<void (AudioPluginInstance*, const String&)> completionCallback); | |||||
#endif | |||||
/** Should do a quick check to see if this file or directory might be a plugin of | /** Should do a quick check to see if this file or directory might be a plugin of | ||||
this format. | this format. | ||||
@@ -82,7 +116,7 @@ public: | |||||
It doesn't actually need to load it, just to check whether the file or component | It doesn't actually need to load it, just to check whether the file or component | ||||
still exists. | still exists. | ||||
*/ | */ | ||||
virtual bool doesPluginStillExist (const PluginDescription& desc) = 0; | |||||
virtual bool doesPluginStillExist (const PluginDescription&) = 0; | |||||
/** Returns true if this format needs to run a scan to find its list of plugins. */ | /** Returns true if this format needs to run a scan to find its list of plugins. */ | ||||
virtual bool canScanForPlugins() const = 0; | virtual bool canScanForPlugins() const = 0; | ||||
@@ -90,9 +124,17 @@ public: | |||||
/** Searches a suggested set of directories for any plugins in this format. | /** Searches a suggested set of directories for any plugins in this format. | ||||
The path might be ignored, e.g. by AUs, which are found by the OS rather | The path might be ignored, e.g. by AUs, which are found by the OS rather | ||||
than manually. | than manually. | ||||
@param directoriesToSearch This specifies which directories shall be | |||||
searched for plug-ins. | |||||
@param recursive Should the search recursively traverse folders. | |||||
@param allowPluginsWhichRequireAsynchronousInstantiation | |||||
If this is false then plug-ins which require | |||||
asynchronous creation will be excluded. | |||||
*/ | */ | ||||
virtual StringArray searchPathsForPlugins (const FileSearchPath& directoriesToSearch, | virtual StringArray searchPathsForPlugins (const FileSearchPath& directoriesToSearch, | ||||
bool recursive) = 0; | |||||
bool recursive, | |||||
bool allowPluginsWhichRequireAsynchronousInstantiation = false) = 0; | |||||
/** Returns the typical places to look for this kind of plugin. | /** Returns the typical places to look for this kind of plugin. | ||||
@@ -103,8 +145,24 @@ public: | |||||
protected: | protected: | ||||
//============================================================================== | //============================================================================== | ||||
friend class AudioPluginFormatManager; | |||||
AudioPluginFormat() noexcept; | AudioPluginFormat() noexcept; | ||||
/** Implementors must override this function. This is guaranteed to be called on | |||||
the message thread. You may call the callback on any thread. | |||||
*/ | |||||
virtual void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||||
int initialBufferSize, void* userData, | |||||
void (*callback) (void*, AudioPluginInstance*, const String&)) = 0; | |||||
virtual bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept = 0; | |||||
private: | |||||
/** @internal */ | |||||
void createPluginInstanceOnMessageThread (const PluginDescription&, double rate, int size, | |||||
AudioPluginFormat::InstantiationCompletionCallback*); | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormat) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormat) | ||||
}; | }; | ||||
@@ -22,6 +22,39 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
namespace PluginFormatManagerHelpers | |||||
{ | |||||
struct ErrorCallbackOnMessageThread : public CallbackMessage | |||||
{ | |||||
ErrorCallbackOnMessageThread (const String& inError, | |||||
AudioPluginFormat::InstantiationCompletionCallback* inCallback) | |||||
: error (inError), callback (inCallback) | |||||
{ | |||||
} | |||||
void messageCallback() override { callback->completionCallback (nullptr, error); } | |||||
String error; | |||||
ScopedPointer<AudioPluginFormat::InstantiationCompletionCallback> callback; | |||||
}; | |||||
#if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||||
struct ErrorLambdaOnMessageThread : public CallbackMessage | |||||
{ | |||||
ErrorLambdaOnMessageThread (const String& inError, | |||||
std::function<void (AudioPluginInstance*, const String&)> f) | |||||
: error (inError), lambda (f) | |||||
{ | |||||
} | |||||
void messageCallback() override { lambda (nullptr, error); } | |||||
String error; | |||||
std::function<void (AudioPluginInstance*, const String&)> lambda; | |||||
}; | |||||
#endif | |||||
} | |||||
AudioPluginFormatManager::AudioPluginFormatManager() {} | AudioPluginFormatManager::AudioPluginFormatManager() {} | ||||
AudioPluginFormatManager::~AudioPluginFormatManager() {} | AudioPluginFormatManager::~AudioPluginFormatManager() {} | ||||
@@ -32,15 +65,15 @@ void AudioPluginFormatManager::addDefaultFormats() | |||||
// you should only call this method once! | // you should only call this method once! | ||||
for (int i = formats.size(); --i >= 0;) | for (int i = formats.size(); --i >= 0;) | ||||
{ | { | ||||
#if JUCE_PLUGINHOST_VST | |||||
#if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) | |||||
jassert (dynamic_cast<VSTPluginFormat*> (formats[i]) == nullptr); | jassert (dynamic_cast<VSTPluginFormat*> (formats[i]) == nullptr); | ||||
#endif | #endif | ||||
#if JUCE_PLUGINHOST_VST3 | |||||
#if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) | |||||
jassert (dynamic_cast<VST3PluginFormat*> (formats[i]) == nullptr); | jassert (dynamic_cast<VST3PluginFormat*> (formats[i]) == nullptr); | ||||
#endif | #endif | ||||
#if JUCE_PLUGINHOST_AU && JUCE_MAC | |||||
#if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | |||||
jassert (dynamic_cast<AudioUnitPluginFormat*> (formats[i]) == nullptr); | jassert (dynamic_cast<AudioUnitPluginFormat*> (formats[i]) == nullptr); | ||||
#endif | #endif | ||||
@@ -50,15 +83,15 @@ void AudioPluginFormatManager::addDefaultFormats() | |||||
} | } | ||||
#endif | #endif | ||||
#if JUCE_PLUGINHOST_AU && JUCE_MAC | |||||
#if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | |||||
formats.add (new AudioUnitPluginFormat()); | formats.add (new AudioUnitPluginFormat()); | ||||
#endif | #endif | ||||
#if JUCE_PLUGINHOST_VST | |||||
#if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) | |||||
formats.add (new VSTPluginFormat()); | formats.add (new VSTPluginFormat()); | ||||
#endif | #endif | ||||
#if JUCE_PLUGINHOST_VST3 | |||||
#if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) | |||||
formats.add (new VST3PluginFormat()); | formats.add (new VST3PluginFormat()); | ||||
#endif | #endif | ||||
@@ -85,12 +118,55 @@ void AudioPluginFormatManager::addFormat (AudioPluginFormat* const format) | |||||
AudioPluginInstance* AudioPluginFormatManager::createPluginInstance (const PluginDescription& description, double rate, | AudioPluginInstance* AudioPluginFormatManager::createPluginInstance (const PluginDescription& description, double rate, | ||||
int blockSize, String& errorMessage) const | int blockSize, String& errorMessage) const | ||||
{ | { | ||||
if (AudioPluginFormat* format = findFormatForDescription (description, errorMessage)) | |||||
return format->createInstanceFromDescription (description, rate, blockSize, errorMessage); | |||||
return nullptr; | |||||
} | |||||
void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
AudioPluginFormat::InstantiationCompletionCallback* callback) | |||||
{ | |||||
String error; | |||||
if (AudioPluginFormat* format = findFormatForDescription (description, error)) | |||||
return format->createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, callback); | |||||
(new PluginFormatManagerHelpers::ErrorCallbackOnMessageThread (error, callback))->post(); | |||||
} | |||||
#if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||||
void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
std::function<void (AudioPluginInstance*, const String&)> f) | |||||
{ | |||||
String error; | |||||
if (AudioPluginFormat* format = findFormatForDescription (description, error)) | |||||
return format->createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, f); | |||||
(new PluginFormatManagerHelpers::ErrorLambdaOnMessageThread (error, f))->post(); | |||||
} | |||||
#endif | |||||
AudioPluginFormat* AudioPluginFormatManager::findFormatForDescription (const PluginDescription& description, String& errorMessage) const | |||||
{ | |||||
errorMessage = String(); | |||||
for (int i = 0; i < formats.size(); ++i) | for (int i = 0; i < formats.size(); ++i) | ||||
if (AudioPluginInstance* result = formats.getUnchecked(i)->createInstanceFromDescription (description, rate, blockSize)) | |||||
return result; | |||||
{ | |||||
AudioPluginFormat* format; | |||||
if ((format = formats.getUnchecked (i))->getName() == description.pluginFormatName | |||||
&& format->fileMightContainThisPluginType (description.fileOrIdentifier)) | |||||
return format; | |||||
} | |||||
errorMessage = NEEDS_TRANS ("No compatible plug-in format exists for this plug-in"); | |||||
errorMessage = doesPluginStillExist (description) ? TRANS ("This plug-in failed to load correctly") | |||||
: TRANS ("This plug-in file no longer exists"); | |||||
return nullptr; | return nullptr; | ||||
} | } | ||||
@@ -75,12 +75,51 @@ public: | |||||
If it can't load the plugin, it returns nullptr and leaves a message in the | If it can't load the plugin, it returns nullptr and leaves a message in the | ||||
errorMessage string. | errorMessage string. | ||||
If you intend to instantiate a AudioUnit v3 plug-in then you must either | |||||
use the non-blocking asynchrous version below - or call this method from a | |||||
thread other than the message thread and without blocking the message | |||||
thread. | |||||
*/ | */ | ||||
AudioPluginInstance* createPluginInstance (const PluginDescription& description, | AudioPluginInstance* createPluginInstance (const PluginDescription& description, | ||||
double initialSampleRate, | double initialSampleRate, | ||||
int initialBufferSize, | int initialBufferSize, | ||||
String& errorMessage) const; | String& errorMessage) const; | ||||
/** Tries to asynchronously load the type for this description, by trying | |||||
all the formats that this manager knows about. | |||||
The caller must supply a callback object which will be called when | |||||
the instantantiation has completed. | |||||
If it can't load the plugin then the callback function will be called | |||||
passing a nullptr as the instance argument along with an error message. | |||||
The callback function will be called on the message thread so the caller | |||||
must not block the message thread. | |||||
The callback object will be deleted automatically after it has been | |||||
invoked. | |||||
The caller is responsible for deleting the instance that is passed to | |||||
the callback function. | |||||
If you intend to instantiate a AudioUnit v3 plug-in then you must use | |||||
this non-blocking asynchrous version - or call the synchrous method | |||||
from an auxiliary thread. | |||||
*/ | |||||
void createPluginInstanceAsync (const PluginDescription& description, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
AudioPluginFormat::InstantiationCompletionCallback* callback); | |||||
#if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||||
void createPluginInstanceAsync (const PluginDescription& description, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
std::function<void (AudioPluginInstance*, const String&)> completionCallback); | |||||
#endif | |||||
/** Checks that the file or component for this plugin actually still exists. | /** Checks that the file or component for this plugin actually still exists. | ||||
(This won't try to load the plugin) | (This won't try to load the plugin) | ||||
@@ -89,6 +128,9 @@ public: | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
//@internal | |||||
AudioPluginFormat* findFormatForDescription (const PluginDescription&, String& errorMessage) const; | |||||
OwnedArray<AudioPluginFormat> formats; | OwnedArray<AudioPluginFormat> formats; | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormatManager) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormatManager) | ||||
@@ -0,0 +1,778 @@ | |||||
/* | |||||
============================================================================== | |||||
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. | |||||
============================================================================== | |||||
*/ | |||||
// This macro can be set if you need to override this internal name for some reason.. | |||||
#ifndef JUCE_STATE_DICTIONARY_KEY | |||||
#define JUCE_STATE_DICTIONARY_KEY "jucePluginState" | |||||
#endif | |||||
struct AudioUnitHelpers | |||||
{ | |||||
// maps a channel index into an AU format to an index of a juce format | |||||
struct AUChannelStreamOrder | |||||
{ | |||||
AudioChannelLayoutTag auLayoutTag; | |||||
AudioChannelSet::ChannelType speakerOrder[8]; | |||||
}; | |||||
struct StreamOrder : public AudioChannelSet | |||||
{ | |||||
static AUChannelStreamOrder auChannelStreamOrder[]; | |||||
}; | |||||
static AudioChannelSet::ChannelType CoreAudioChannelLabelToJuceType (AudioChannelLabel label) noexcept | |||||
{ | |||||
if (label >= kAudioChannelLabel_Discrete_0 && label <= kAudioChannelLabel_Discrete_65535) | |||||
{ | |||||
const unsigned int discreteChannelNum = label - kAudioChannelLabel_Discrete_0; | |||||
return static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + discreteChannelNum); | |||||
} | |||||
switch (label) | |||||
{ | |||||
case kAudioChannelLabel_Center: | |||||
case kAudioChannelLabel_Mono: return AudioChannelSet::centre; | |||||
case kAudioChannelLabel_Left: | |||||
case kAudioChannelLabel_HeadphonesLeft: return AudioChannelSet::left; | |||||
case kAudioChannelLabel_Right: | |||||
case kAudioChannelLabel_HeadphonesRight: return AudioChannelSet::right; | |||||
case kAudioChannelLabel_LFEScreen: return AudioChannelSet::LFE; | |||||
case kAudioChannelLabel_LeftSurround: return AudioChannelSet::leftSurround; | |||||
case kAudioChannelLabel_RightSurround: return AudioChannelSet::rightSurround; | |||||
case kAudioChannelLabel_LeftCenter: return AudioChannelSet::leftCentre; | |||||
case kAudioChannelLabel_RightCenter: return AudioChannelSet::rightCentre; | |||||
case kAudioChannelLabel_CenterSurround: return AudioChannelSet::surround; | |||||
case kAudioChannelLabel_LeftSurroundDirect: return AudioChannelSet::leftSurroundSide; | |||||
case kAudioChannelLabel_RightSurroundDirect: return AudioChannelSet::rightSurroundSide; | |||||
case kAudioChannelLabel_TopCenterSurround: return AudioChannelSet::topMiddle; | |||||
case kAudioChannelLabel_VerticalHeightLeft: return AudioChannelSet::topFrontLeft; | |||||
case kAudioChannelLabel_VerticalHeightRight: return AudioChannelSet::topFrontRight; | |||||
case kAudioChannelLabel_VerticalHeightCenter: return AudioChannelSet::topFrontCentre; | |||||
case kAudioChannelLabel_TopBackLeft: return AudioChannelSet::topRearLeft; | |||||
case kAudioChannelLabel_RearSurroundLeft: return AudioChannelSet::leftSurroundRear; | |||||
case kAudioChannelLabel_TopBackRight: return AudioChannelSet::topRearRight; | |||||
case kAudioChannelLabel_RearSurroundRight: return AudioChannelSet::rightSurroundRear; | |||||
case kAudioChannelLabel_TopBackCenter: return AudioChannelSet::topRearCentre; | |||||
case kAudioChannelLabel_LFE2: return AudioChannelSet::LFE2; | |||||
case kAudioChannelLabel_LeftWide: return AudioChannelSet::wideLeft; | |||||
case kAudioChannelLabel_RightWide: return AudioChannelSet::wideRight; | |||||
case kAudioChannelLabel_Ambisonic_W: return AudioChannelSet::ambisonicW; | |||||
case kAudioChannelLabel_Ambisonic_X: return AudioChannelSet::ambisonicX; | |||||
case kAudioChannelLabel_Ambisonic_Y: return AudioChannelSet::ambisonicY; | |||||
case kAudioChannelLabel_Ambisonic_Z: return AudioChannelSet::ambisonicZ; | |||||
default: return AudioChannelSet::unknown; | |||||
} | |||||
} | |||||
static AudioChannelLabel JuceChannelTypeToCoreAudioLabel (const AudioChannelSet::ChannelType& label) noexcept | |||||
{ | |||||
if (label >= AudioChannelSet::discreteChannel0) | |||||
{ | |||||
const unsigned int discreteChannelNum = label - AudioChannelSet::discreteChannel0;; | |||||
return static_cast<AudioChannelLabel> (kAudioChannelLabel_Discrete_0 + discreteChannelNum); | |||||
} | |||||
switch (label) | |||||
{ | |||||
case AudioChannelSet::centre: return kAudioChannelLabel_Center; | |||||
case AudioChannelSet::left: return kAudioChannelLabel_Left; | |||||
case AudioChannelSet::right: return kAudioChannelLabel_Right; | |||||
case AudioChannelSet::LFE: return kAudioChannelLabel_LFEScreen; | |||||
case AudioChannelSet::leftSurroundRear: return kAudioChannelLabel_RearSurroundLeft; | |||||
case AudioChannelSet::rightSurroundRear: return kAudioChannelLabel_RearSurroundRight; | |||||
case AudioChannelSet::leftCentre: return kAudioChannelLabel_LeftCenter; | |||||
case AudioChannelSet::rightCentre: return kAudioChannelLabel_RightCenter; | |||||
case AudioChannelSet::surround: return kAudioChannelLabel_CenterSurround; | |||||
case AudioChannelSet::leftSurround: return kAudioChannelLabel_LeftSurround; | |||||
case AudioChannelSet::rightSurround: return kAudioChannelLabel_RightSurround; | |||||
case AudioChannelSet::topMiddle: return kAudioChannelLabel_TopCenterSurround; | |||||
case AudioChannelSet::topFrontLeft: return kAudioChannelLabel_VerticalHeightLeft; | |||||
case AudioChannelSet::topFrontRight: return kAudioChannelLabel_VerticalHeightRight; | |||||
case AudioChannelSet::topFrontCentre: return kAudioChannelLabel_VerticalHeightCenter; | |||||
case AudioChannelSet::topRearLeft: return kAudioChannelLabel_TopBackLeft; | |||||
case AudioChannelSet::topRearRight: return kAudioChannelLabel_TopBackRight; | |||||
case AudioChannelSet::topRearCentre: return kAudioChannelLabel_TopBackCenter; | |||||
case AudioChannelSet::LFE2: return kAudioChannelLabel_LFE2; | |||||
case AudioChannelSet::wideLeft: return kAudioChannelLabel_LeftWide; | |||||
case AudioChannelSet::wideRight: return kAudioChannelLabel_RightWide; | |||||
case AudioChannelSet::ambisonicW: return kAudioChannelLabel_Ambisonic_W; | |||||
case AudioChannelSet::ambisonicX: return kAudioChannelLabel_Ambisonic_X; | |||||
case AudioChannelSet::ambisonicY: return kAudioChannelLabel_Ambisonic_Y; | |||||
case AudioChannelSet::ambisonicZ: return kAudioChannelLabel_Ambisonic_Z; | |||||
case AudioChannelSet::leftSurroundSide: return kAudioChannelLabel_LeftSurroundDirect; | |||||
case AudioChannelSet::rightSurroundSide: return kAudioChannelLabel_RightSurroundDirect; | |||||
case AudioChannelSet::unknown: return kAudioChannelLabel_Unknown; | |||||
case AudioChannelSet::discreteChannel0: return kAudioChannelLabel_Discrete_0; | |||||
} | |||||
return kAudioChannelLabel_Unknown; | |||||
} | |||||
static AudioChannelSet CoreAudioChannelBitmapToJuceType (UInt32 bitmap) noexcept | |||||
{ | |||||
AudioChannelSet set; | |||||
if ((bitmap & kAudioChannelBit_Left) != 0) set.addChannel (AudioChannelSet::left); | |||||
if ((bitmap & kAudioChannelBit_Right) != 0) set.addChannel (AudioChannelSet::right); | |||||
if ((bitmap & kAudioChannelBit_Center) != 0) set.addChannel (AudioChannelSet::centre); | |||||
if ((bitmap & kAudioChannelBit_LFEScreen) != 0) set.addChannel (AudioChannelSet::LFE); | |||||
if ((bitmap & kAudioChannelBit_LeftSurroundDirect) != 0) set.addChannel (AudioChannelSet::leftSurroundSide); | |||||
if ((bitmap & kAudioChannelBit_RightSurroundDirect) != 0) set.addChannel (AudioChannelSet::rightSurroundSide); | |||||
if ((bitmap & kAudioChannelBit_LeftCenter) != 0) set.addChannel (AudioChannelSet::leftCentre); | |||||
if ((bitmap & kAudioChannelBit_RightCenter) != 0) set.addChannel (AudioChannelSet::rightCentre); | |||||
if ((bitmap & kAudioChannelBit_CenterSurround) != 0) set.addChannel (AudioChannelSet::surround); | |||||
if ((bitmap & kAudioChannelBit_LeftSurround) != 0) set.addChannel (AudioChannelSet::leftSurround); | |||||
if ((bitmap & kAudioChannelBit_RightSurround) != 0) set.addChannel (AudioChannelSet::rightSurround); | |||||
if ((bitmap & kAudioChannelBit_TopCenterSurround) != 0) set.addChannel (AudioChannelSet::topMiddle); | |||||
if ((bitmap & kAudioChannelBit_VerticalHeightLeft) != 0) set.addChannel (AudioChannelSet::topFrontLeft); | |||||
if ((bitmap & kAudioChannelBit_VerticalHeightCenter) != 0) set.addChannel (AudioChannelSet::topFrontCentre); | |||||
if ((bitmap & kAudioChannelBit_VerticalHeightRight) != 0) set.addChannel (AudioChannelSet::topFrontRight); | |||||
if ((bitmap & kAudioChannelBit_TopBackLeft) != 0) set.addChannel (AudioChannelSet::topRearLeft); | |||||
if ((bitmap & kAudioChannelBit_TopBackCenter) != 0) set.addChannel (AudioChannelSet::topRearCentre); | |||||
if ((bitmap & kAudioChannelBit_TopBackRight) != 0) set.addChannel (AudioChannelSet::topRearRight); | |||||
return set; | |||||
} | |||||
static AudioChannelSet CoreAudioChannelLayoutToJuceType (const AudioChannelLayout& layout) noexcept | |||||
{ | |||||
const AudioChannelLayoutTag tag = layout.mChannelLayoutTag; | |||||
if (tag == kAudioChannelLayoutTag_UseChannelBitmap) return CoreAudioChannelBitmapToJuceType (layout.mChannelBitmap); | |||||
if (tag == kAudioChannelLayoutTag_UseChannelDescriptions) | |||||
{ | |||||
if (layout.mNumberChannelDescriptions <= 8) | |||||
{ | |||||
// first try to convert the layout via the auChannelStreamOrder array | |||||
int layoutIndex; | |||||
for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != 0; ++layoutIndex) | |||||
{ | |||||
const AUChannelStreamOrder& streamOrder = StreamOrder::auChannelStreamOrder[layoutIndex]; | |||||
int numChannels; | |||||
for (numChannels = 0; numChannels < 8 && streamOrder.speakerOrder[numChannels] != 0;) | |||||
++numChannels; | |||||
if (numChannels != (int) layout.mNumberChannelDescriptions) | |||||
continue; | |||||
int ch; | |||||
for (ch = 0; ch < numChannels; ++ch) | |||||
if (JuceChannelTypeToCoreAudioLabel (streamOrder.speakerOrder[ch]) != layout.mChannelDescriptions[ch].mChannelLabel) | |||||
break; | |||||
// match! | |||||
if (ch == numChannels) | |||||
break; | |||||
} | |||||
if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != 0) | |||||
return CALayoutTagToChannelSet (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag); | |||||
} | |||||
AudioChannelSet set; | |||||
for (unsigned int i = 0; i < layout.mNumberChannelDescriptions; ++i) | |||||
set.addChannel (CoreAudioChannelLabelToJuceType (layout.mChannelDescriptions[i].mChannelLabel)); | |||||
return set; | |||||
} | |||||
return CALayoutTagToChannelSet (tag); | |||||
} | |||||
static AudioChannelSet CALayoutTagToChannelSet (AudioChannelLayoutTag tag) noexcept | |||||
{ | |||||
switch (tag) | |||||
{ | |||||
case kAudioChannelLayoutTag_Unknown: return AudioChannelSet::disabled(); | |||||
case kAudioChannelLayoutTag_Mono: return AudioChannelSet::mono(); | |||||
case kAudioChannelLayoutTag_Stereo: | |||||
case kAudioChannelLayoutTag_StereoHeadphones: | |||||
case kAudioChannelLayoutTag_Binaural: return AudioChannelSet::stereo(); | |||||
case kAudioChannelLayoutTag_Quadraphonic: return AudioChannelSet::quadraphonic(); | |||||
case kAudioChannelLayoutTag_Pentagonal: return AudioChannelSet::pentagonal(); | |||||
case kAudioChannelLayoutTag_Hexagonal: return AudioChannelSet::hexagonal(); | |||||
case kAudioChannelLayoutTag_Octagonal: return AudioChannelSet::octagonal(); | |||||
case kAudioChannelLayoutTag_Ambisonic_B_Format: return AudioChannelSet::ambisonic(); | |||||
case kAudioChannelLayoutTag_AudioUnit_6_0: return AudioChannelSet::create6point0(); | |||||
case kAudioChannelLayoutTag_DTS_6_0_A: return AudioChannelSet::create6point0Music(); | |||||
case kAudioChannelLayoutTag_MPEG_6_1_A: return AudioChannelSet::create6point1(); | |||||
case kAudioChannelLayoutTag_DTS_6_1_A: return AudioChannelSet::create6point1Music(); | |||||
case kAudioChannelLayoutTag_MPEG_5_0_B: | |||||
case kAudioChannelLayoutTag_MPEG_5_0_A: | |||||
return AudioChannelSet::create5point0(); | |||||
case kAudioChannelLayoutTag_MPEG_5_1_A: return AudioChannelSet::create5point1(); | |||||
case kAudioChannelLayoutTag_DTS_7_1: | |||||
case kAudioChannelLayoutTag_AudioUnit_7_0: return AudioChannelSet::create7point0(); | |||||
case kAudioChannelLayoutTag_AudioUnit_7_0_Front: return AudioChannelSet::create7point0SDDS(); | |||||
case kAudioChannelLayoutTag_MPEG_7_1_A: return AudioChannelSet::create7point1SDDS(); | |||||
case kAudioChannelLayoutTag_MPEG_3_0_A: | |||||
case kAudioChannelLayoutTag_MPEG_3_0_B: return AudioChannelSet::createLCR(); | |||||
case kAudioChannelLayoutTag_MPEG_4_0_A: | |||||
case kAudioChannelLayoutTag_MPEG_4_0_B: return AudioChannelSet::createLCRS(); | |||||
case kAudioChannelLayoutTag_ITU_2_1: return AudioChannelSet::createLRS(); | |||||
case kAudioChannelLayoutTag_MPEG_7_1_C: return AudioChannelSet::create7point1(); | |||||
} | |||||
if (int numChannels = static_cast<int> (tag) & 0xffff) | |||||
return AudioChannelSet::discreteChannels (numChannels); | |||||
// Bitmap and channel description array layout tags are currently unsupported :-( | |||||
jassertfalse; | |||||
return AudioChannelSet(); | |||||
} | |||||
static AudioChannelLayoutTag ChannelSetToCALayoutTag (const AudioChannelSet& set) noexcept | |||||
{ | |||||
if (set == AudioChannelSet::mono()) return kAudioChannelLayoutTag_Mono; | |||||
if (set == AudioChannelSet::stereo()) return kAudioChannelLayoutTag_Stereo; | |||||
if (set == AudioChannelSet::createLCR()) return kAudioChannelLayoutTag_MPEG_3_0_A; | |||||
if (set == AudioChannelSet::createLRS()) return kAudioChannelLayoutTag_ITU_2_1; | |||||
if (set == AudioChannelSet::createLCRS()) return kAudioChannelLayoutTag_MPEG_4_0_A; | |||||
if (set == AudioChannelSet::quadraphonic()) return kAudioChannelLayoutTag_Quadraphonic; | |||||
if (set == AudioChannelSet::pentagonal()) return kAudioChannelLayoutTag_Pentagonal; | |||||
if (set == AudioChannelSet::hexagonal()) return kAudioChannelLayoutTag_Hexagonal; | |||||
if (set == AudioChannelSet::octagonal()) return kAudioChannelLayoutTag_Octagonal; | |||||
if (set == AudioChannelSet::ambisonic()) return kAudioChannelLayoutTag_Ambisonic_B_Format; | |||||
if (set == AudioChannelSet::create5point0()) return kAudioChannelLayoutTag_MPEG_5_0_A; | |||||
if (set == AudioChannelSet::create5point1()) return kAudioChannelLayoutTag_MPEG_5_1_A; | |||||
if (set == AudioChannelSet::create6point0()) return kAudioChannelLayoutTag_AudioUnit_6_0; | |||||
if (set == AudioChannelSet::create6point0Music()) return kAudioChannelLayoutTag_DTS_6_0_A; | |||||
if (set == AudioChannelSet::create6point1Music()) return kAudioChannelLayoutTag_DTS_6_1_A; | |||||
if (set == AudioChannelSet::create6point1()) return kAudioChannelLayoutTag_MPEG_6_1_A; | |||||
if (set == AudioChannelSet::create7point0()) return kAudioChannelLayoutTag_AudioUnit_7_0; | |||||
if (set == AudioChannelSet::create7point1()) return kAudioChannelLayoutTag_MPEG_7_1_C; | |||||
if (set == AudioChannelSet::create7point0SDDS()) return kAudioChannelLayoutTag_AudioUnit_7_0_Front; | |||||
if (set == AudioChannelSet::create7point1SDDS()) return kAudioChannelLayoutTag_MPEG_7_1_A; | |||||
if (set == AudioChannelSet::disabled()) return kAudioChannelLayoutTag_Unknown; | |||||
return static_cast<AudioChannelLayoutTag> ((int) kAudioChannelLayoutTag_DiscreteInOrder | set.size()); | |||||
} | |||||
static int auChannelIndexToJuce (int auIndex, const AudioChannelSet& channelSet) | |||||
{ | |||||
if (auIndex >= 8) return auIndex; | |||||
AudioChannelLayoutTag currentLayout = ChannelSetToCALayoutTag (channelSet); | |||||
int layoutIndex; | |||||
for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != currentLayout; ++layoutIndex) | |||||
if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag == 0) return auIndex; | |||||
AudioChannelSet::ChannelType channelType | |||||
= StreamOrder::auChannelStreamOrder[layoutIndex].speakerOrder[auIndex]; | |||||
const int juceIndex = channelSet.getChannelTypes().indexOf (channelType); | |||||
jassert (juceIndex >= 0); | |||||
return juceIndex >= 0 ? juceIndex : auIndex; | |||||
} | |||||
static int juceChannelIndexToAu (int juceIndex, const AudioChannelSet& channelSet) | |||||
{ | |||||
if (channelSet.isDiscreteLayout()) | |||||
return juceIndex; | |||||
AudioChannelLayoutTag currentLayout = ChannelSetToCALayoutTag (channelSet); | |||||
int layoutIndex; | |||||
for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != currentLayout; ++layoutIndex) | |||||
{ | |||||
if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag == 0) | |||||
{ | |||||
jassertfalse; | |||||
return juceIndex; | |||||
} | |||||
} | |||||
const AUChannelStreamOrder& channelOrder = StreamOrder::auChannelStreamOrder[layoutIndex]; | |||||
AudioChannelSet::ChannelType channelType = channelSet.getTypeOfChannel (juceIndex); | |||||
for (int i = 0; i < 8 && channelOrder.speakerOrder[i] != 0; ++i) | |||||
if (channelOrder.speakerOrder[i] == channelType) | |||||
return i; | |||||
jassertfalse; | |||||
return juceIndex; | |||||
} | |||||
class ChannelRemapper | |||||
{ | |||||
public: | |||||
ChannelRemapper (AudioProcessor& p) : processor (p), inputLayoutMap (nullptr), outputLayoutMap (nullptr) {} | |||||
~ChannelRemapper() {} | |||||
void alloc() | |||||
{ | |||||
const int numInputBuses = processor.getBusCount (true); | |||||
const int numOutputBuses = processor.getBusCount (false); | |||||
initializeChannelMapArray (true, numInputBuses); | |||||
initializeChannelMapArray (false, numOutputBuses); | |||||
for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) | |||||
fillLayoutChannelMaps (true, busIdx); | |||||
for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) | |||||
fillLayoutChannelMaps (false, busIdx); | |||||
} | |||||
void release() | |||||
{ | |||||
inputLayoutMap = outputLayoutMap = nullptr; | |||||
inputLayoutMapPtrStorage.free(); | |||||
outputLayoutMapPtrStorage.free(); | |||||
inputLayoutMapStorage.free(); | |||||
outputLayoutMapStorage.free(); | |||||
} | |||||
inline const int* get (bool input, int bus) const noexcept { return (input ? inputLayoutMap : outputLayoutMap) [bus]; } | |||||
private: | |||||
//============================================================================== | |||||
AudioProcessor& processor; | |||||
HeapBlock<int*> inputLayoutMapPtrStorage, outputLayoutMapPtrStorage; | |||||
HeapBlock<int> inputLayoutMapStorage, outputLayoutMapStorage; | |||||
int** inputLayoutMap; | |||||
int** outputLayoutMap; | |||||
//============================================================================== | |||||
void initializeChannelMapArray (bool isInput, const int numBuses) | |||||
{ | |||||
HeapBlock<int*>& layoutMapPtrStorage = isInput ? inputLayoutMapPtrStorage : outputLayoutMapPtrStorage; | |||||
HeapBlock<int>& layoutMapStorage = isInput ? inputLayoutMapStorage : outputLayoutMapStorage; | |||||
int**& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; | |||||
const int totalInChannels = processor.getTotalNumInputChannels(); | |||||
const int totalOutChannels = processor.getTotalNumOutputChannels(); | |||||
layoutMapPtrStorage.calloc (static_cast<size_t> (numBuses)); | |||||
layoutMapStorage.calloc (static_cast<size_t> (isInput ? totalInChannels : totalOutChannels)); | |||||
layoutMap = layoutMapPtrStorage. getData(); | |||||
int ch = 0; | |||||
for (int busIdx = 0; busIdx < numBuses; ++busIdx) | |||||
{ | |||||
layoutMap[busIdx] = layoutMapStorage.getData() + ch; | |||||
ch += processor.getChannelCountOfBus (isInput, busIdx); | |||||
} | |||||
} | |||||
void fillLayoutChannelMaps (bool isInput, int busNr) | |||||
{ | |||||
int* layoutMap = (isInput ? inputLayoutMap : outputLayoutMap)[busNr]; | |||||
const AudioChannelSet& channelFormat = processor.getChannelLayoutOfBus (isInput, busNr); | |||||
const int numChannels = channelFormat.size(); | |||||
for (int i = 0; i < numChannels; ++i) | |||||
layoutMap[i] = AudioUnitHelpers::juceChannelIndexToAu (i, channelFormat); | |||||
} | |||||
}; | |||||
//============================================================================== | |||||
class CoreAudioBufferList | |||||
{ | |||||
public: | |||||
CoreAudioBufferList() { reset(); } | |||||
//============================================================================== | |||||
void prepare (int inChannels, int outChannels, int maxFrames) | |||||
{ | |||||
const int numChannels = jmax (inChannels, outChannels); | |||||
scratch.setSize (numChannels, maxFrames); | |||||
channels.calloc (static_cast<size_t> (numChannels)); | |||||
reset(); | |||||
} | |||||
void release() | |||||
{ | |||||
scratch.setSize (0, 0); | |||||
channels.free(); | |||||
} | |||||
void reset() noexcept | |||||
{ | |||||
pushIdx = 0; | |||||
popIdx = 0; | |||||
zeromem (channels.getData(), sizeof(float*) * static_cast<size_t> (scratch.getNumChannels())); | |||||
} | |||||
//============================================================================== | |||||
float* setBuffer (const int idx, float* ptr = nullptr) noexcept | |||||
{ | |||||
jassert (idx < scratch.getNumChannels()); | |||||
return (channels [idx] = uniqueBuffer (idx, ptr)); | |||||
} | |||||
//============================================================================== | |||||
float* push() noexcept | |||||
{ | |||||
jassert (pushIdx < scratch.getNumChannels()); | |||||
return channels [pushIdx++]; | |||||
} | |||||
void push (AudioBufferList& bufferList, const int* channelMap) noexcept | |||||
{ | |||||
jassert (pushIdx < scratch.getNumChannels()); | |||||
if (bufferList.mNumberBuffers > 0) | |||||
{ | |||||
const UInt32 n = bufferList.mBuffers [0].mDataByteSize / | |||||
(bufferList.mBuffers [0].mNumberChannels * sizeof (float)); | |||||
const bool isInterleaved = isAudioBufferInterleaved (bufferList); | |||||
const int numChannels = static_cast<int> (isInterleaved ? bufferList.mBuffers [0].mNumberChannels | |||||
: bufferList.mNumberBuffers); | |||||
for (int ch = 0; ch < numChannels; ++ch) | |||||
{ | |||||
float* data = push(); | |||||
int mappedChannel = channelMap [ch]; | |||||
if (isInterleaved || static_cast<float*> (bufferList.mBuffers [mappedChannel].mData) != data) | |||||
copyAudioBuffer (bufferList, mappedChannel, n, data); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
float* pop() noexcept | |||||
{ | |||||
jassert (popIdx < scratch.getNumChannels()); | |||||
return channels[popIdx++]; | |||||
} | |||||
void pop (AudioBufferList& buffer, const int* channelMap) noexcept | |||||
{ | |||||
if (buffer.mNumberBuffers > 0) | |||||
{ | |||||
const UInt32 n = buffer.mBuffers [0].mDataByteSize / (buffer.mBuffers [0].mNumberChannels * sizeof (float)); | |||||
const bool isInterleaved = isAudioBufferInterleaved (buffer); | |||||
const int numChannels = static_cast<int> (isInterleaved ? buffer.mBuffers [0].mNumberChannels : buffer.mNumberBuffers); | |||||
for (int ch = 0; ch < numChannels; ++ch) | |||||
{ | |||||
int mappedChannel = channelMap [ch]; | |||||
float* nextBuffer = pop(); | |||||
if (nextBuffer == buffer.mBuffers [mappedChannel].mData && ! isInterleaved) | |||||
continue; // no copying necessary | |||||
if (buffer.mBuffers [mappedChannel].mData == nullptr && ! isInterleaved) | |||||
buffer.mBuffers [mappedChannel].mData = nextBuffer; | |||||
else | |||||
copyAudioBuffer (nextBuffer, mappedChannel, n, buffer); | |||||
} | |||||
} | |||||
} | |||||
//============================================================================== | |||||
AudioSampleBuffer& getBuffer (UInt32 frames) noexcept | |||||
{ | |||||
jassert (pushIdx == scratch.getNumChannels()); | |||||
#if JUCE_DEBUG | |||||
for (int i = 0; i < pushIdx; ++i) | |||||
jassert (channels [i] != nullptr); | |||||
#endif | |||||
mutableBuffer.setDataToReferTo (channels, pushIdx, static_cast<int> (frames)); | |||||
return mutableBuffer; | |||||
} | |||||
private: | |||||
float* uniqueBuffer (int idx, float* buffer) noexcept | |||||
{ | |||||
if (buffer == nullptr) | |||||
return scratch.getWritePointer (idx); | |||||
for (int ch = 0; ch < idx; ++ch) | |||||
if (buffer == channels[ch]) | |||||
return scratch.getWritePointer (idx); | |||||
return buffer; | |||||
} | |||||
//============================================================================== | |||||
AudioSampleBuffer scratch; | |||||
AudioSampleBuffer mutableBuffer; | |||||
HeapBlock<float*> channels; | |||||
int pushIdx, popIdx; | |||||
}; | |||||
static bool isAudioBufferInterleaved (const AudioBufferList& audioBuffer) noexcept | |||||
{ | |||||
return (audioBuffer.mNumberBuffers == 1 && audioBuffer.mBuffers[0].mNumberChannels > 1); | |||||
} | |||||
static void clearAudioBuffer (const AudioBufferList& audioBuffer) noexcept | |||||
{ | |||||
for (unsigned int ch = 0; ch < audioBuffer.mNumberBuffers; ++ch) | |||||
zeromem (audioBuffer.mBuffers[ch].mData, audioBuffer.mBuffers[ch].mDataByteSize); | |||||
} | |||||
static void copyAudioBuffer (const AudioBufferList& audioBuffer, const int channel, const UInt32 size, float* dst) noexcept | |||||
{ | |||||
if (! isAudioBufferInterleaved (audioBuffer)) | |||||
{ | |||||
jassert (channel < static_cast<int> (audioBuffer.mNumberBuffers)); | |||||
jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); | |||||
memcpy (dst, audioBuffer.mBuffers[channel].mData, size * sizeof (float)); | |||||
} | |||||
else | |||||
{ | |||||
const int numChannels = static_cast<int> (audioBuffer.mBuffers[0].mNumberChannels); | |||||
const UInt32 n = static_cast<UInt32> (numChannels) * size; | |||||
const float* src = static_cast<const float*> (audioBuffer.mBuffers[0].mData); | |||||
jassert (channel < numChannels); | |||||
jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); | |||||
for (const float* inData = src; inData < (src + n); inData += numChannels) | |||||
*dst++ = inData[channel]; | |||||
} | |||||
} | |||||
static void copyAudioBuffer (const float *src, const int channel, const UInt32 size, AudioBufferList& audioBuffer) noexcept | |||||
{ | |||||
if (! isAudioBufferInterleaved (audioBuffer)) | |||||
{ | |||||
jassert (channel < static_cast<int> (audioBuffer.mNumberBuffers)); | |||||
jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); | |||||
memcpy (audioBuffer.mBuffers[channel].mData, src, size * sizeof (float)); | |||||
} | |||||
else | |||||
{ | |||||
const int numChannels = static_cast<int> (audioBuffer.mBuffers[0].mNumberChannels); | |||||
const UInt32 n = static_cast<UInt32> (numChannels) * size; | |||||
float* dst = static_cast<float*> (audioBuffer.mBuffers[0].mData); | |||||
jassert (channel < numChannels); | |||||
jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); | |||||
for (float* outData = dst; outData < (dst + n); outData += numChannels) | |||||
outData[channel] = *src++; | |||||
} | |||||
} | |||||
template <int numLayouts> | |||||
static bool isLayoutSupported (const AudioProcessor& processor, | |||||
bool isInput, int busIdx, | |||||
int numChannels, | |||||
const short (&channelLayoutList) [numLayouts][2], | |||||
bool hasLayoutMap = true) | |||||
{ | |||||
if (const AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) | |||||
{ | |||||
if (! bus->isNumberOfChannelsSupported (numChannels)) | |||||
return false; | |||||
if (! hasLayoutMap) | |||||
return true; | |||||
const int numConfigs = sizeof (channelLayoutList) / sizeof (short[2]); | |||||
for (int i = 0; i < numConfigs; ++i) | |||||
{ | |||||
if (channelLayoutList[i][isInput ? 0 : 1] == numChannels) | |||||
return true; | |||||
} | |||||
} | |||||
return false; | |||||
} | |||||
static Array<AUChannelInfo> getAUChannelInfo (const AudioProcessor& processor) | |||||
{ | |||||
Array<AUChannelInfo> channelInfo; | |||||
const bool hasMainInputBus = (processor.getBusCount (true) > 0); | |||||
const bool hasMainOutputBus = (processor.getBusCount (false) > 0); | |||||
if ((! hasMainInputBus) && (! hasMainOutputBus)) | |||||
{ | |||||
// midi effect plug-in: no audio | |||||
AUChannelInfo info; | |||||
info.inChannels = 0; | |||||
info.outChannels = 0; | |||||
channelInfo.add (info); | |||||
return channelInfo; | |||||
} | |||||
else | |||||
{ | |||||
const uint32_t maxNumChanToCheckFor = 9; | |||||
uint32_t defaultInputs = static_cast<uint32_t> (processor.getChannelCountOfBus (true, 0)); | |||||
uint32_t defaultOutputs = static_cast<uint32_t> (processor.getChannelCountOfBus (false, 0)); | |||||
uint32_t lastInputs = defaultInputs; | |||||
uint32_t lastOutputs = defaultOutputs; | |||||
SortedSet<uint32_t> supportedChannels; | |||||
// add the current configuration | |||||
if (lastInputs != 0 || lastOutputs != 0) | |||||
supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||||
for (uint32_t inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | |||||
{ | |||||
const AudioProcessor::Bus* inBus = processor.getBus (true, 0); | |||||
if (inBus != nullptr && (! inBus->isNumberOfChannelsSupported ((int) inChanNum))) | |||||
continue; | |||||
for (uint32_t outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) | |||||
{ | |||||
const AudioProcessor::Bus* outBus = processor.getBus (false, 0); | |||||
if (outBus != nullptr && (! outBus->isNumberOfChannelsSupported ((int) outChanNum))) | |||||
continue; | |||||
uint32_t channelConfiguration = (inChanNum << 16) | outChanNum; | |||||
// did we already try this configuration? | |||||
if (supportedChannels.contains (channelConfiguration)) continue; | |||||
if (lastInputs != inChanNum && (inChanNum > 0 && inBus != nullptr)) | |||||
{ | |||||
AudioChannelSet set = inBus->supportedLayoutWithChannels ((int) inChanNum); | |||||
AudioProcessor::BusesLayout layouts = inBus->getBusesLayoutForLayoutChangeOfBus (set); | |||||
lastInputs = inChanNum; | |||||
lastOutputs = hasMainOutputBus ? static_cast<uint32_t> (layouts.outputBuses.getReference (0).size()) : 0; | |||||
supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||||
} | |||||
if (lastOutputs != outChanNum && (outChanNum > 0 && outBus != nullptr)) | |||||
{ | |||||
AudioChannelSet set = outBus->supportedLayoutWithChannels ((int) outChanNum); | |||||
AudioProcessor::BusesLayout layouts = outBus->getBusesLayoutForLayoutChangeOfBus (set); | |||||
lastOutputs = outChanNum; | |||||
lastInputs = hasMainInputBus ? static_cast<uint32_t> (layouts.inputBuses.getReference (0).size()) : 0; | |||||
supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||||
} | |||||
} | |||||
} | |||||
bool hasInOutMismatch = false; | |||||
for (int i = 0; i < supportedChannels.size(); ++i) | |||||
{ | |||||
const uint32_t numInputs = (supportedChannels[i] >> 16) & 0xffff; | |||||
const uint32_t numOutputs = (supportedChannels[i] >> 0) & 0xffff; | |||||
if (numInputs != numOutputs) | |||||
{ | |||||
hasInOutMismatch = true; | |||||
break; | |||||
} | |||||
} | |||||
bool hasUnsupportedInput = ! hasMainOutputBus, hasUnsupportedOutput = ! hasMainInputBus; | |||||
for (uint32_t inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | |||||
{ | |||||
uint32_t channelConfiguration = (inChanNum << 16) | (hasInOutMismatch ? defaultOutputs : inChanNum); | |||||
if (! supportedChannels.contains (channelConfiguration)) | |||||
{ | |||||
hasUnsupportedInput = true; | |||||
break; | |||||
} | |||||
} | |||||
for (uint32_t outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) | |||||
{ | |||||
uint32_t channelConfiguration = ((hasInOutMismatch ? defaultInputs : outChanNum) << 16) | outChanNum; | |||||
if (! supportedChannels.contains (channelConfiguration)) | |||||
{ | |||||
hasUnsupportedOutput = true; | |||||
break; | |||||
} | |||||
} | |||||
for (int i = 0; i < supportedChannels.size(); ++i) | |||||
{ | |||||
const int numInputs = (supportedChannels[i] >> 16) & 0xffff; | |||||
const int numOutputs = (supportedChannels[i] >> 0) & 0xffff; | |||||
AUChannelInfo info; | |||||
// see here: https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html | |||||
info.inChannels = static_cast<SInt16> (hasMainInputBus ? (hasUnsupportedInput ? numInputs : (hasInOutMismatch && (! hasUnsupportedOutput) ? -2 : -1)) : 0); | |||||
info.outChannels = static_cast<SInt16> (hasMainOutputBus ? (hasUnsupportedOutput ? numOutputs : (hasInOutMismatch && (! hasUnsupportedInput) ? -2 : -1)) : 0); | |||||
if (info.inChannels == -2 && info.outChannels == -2) | |||||
info.inChannels = -1; | |||||
int j; | |||||
for (j = 0; j < channelInfo.size(); ++j) | |||||
if (channelInfo[j].inChannels == info.inChannels && channelInfo[j].outChannels == info.outChannels) | |||||
break; | |||||
if (j >= channelInfo.size()) | |||||
channelInfo.add (info); | |||||
} | |||||
} | |||||
return channelInfo; | |||||
} | |||||
}; | |||||
AudioUnitHelpers::AUChannelStreamOrder AudioUnitHelpers::StreamOrder::auChannelStreamOrder[] = | |||||
{ | |||||
{kAudioChannelLayoutTag_Mono, {centre, unknown, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_Stereo, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_StereoHeadphones, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_Binaural, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_Quadraphonic, {left, right, leftSurround, rightSurround, unknown, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_Pentagonal, {left, right, leftSurroundRear, rightSurroundRear, centre, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_Hexagonal, {left, right, leftSurroundRear, rightSurroundRear, centre, centreSurround, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_Octagonal, {left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight}}, | |||||
{kAudioChannelLayoutTag_Ambisonic_B_Format, {ambisonicW, ambisonicX, ambisonicY, ambisonicZ, unknown, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_MPEG_5_0_A, {left, right, centre, leftSurround, rightSurround, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_MPEG_5_0_B, {left, right, leftSurround, rightSurround, centre, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_MPEG_5_1_A, {left, right, centre, LFE, leftSurround, rightSurround, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_AudioUnit_6_0, {left, right, leftSurround, rightSurround, centre, centreSurround, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_DTS_6_0_A, {left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, unknown, unknown}}, // TODO check this one | |||||
{kAudioChannelLayoutTag_MPEG_6_1_A, {left, right, centre, LFE, leftSurround, rightSurround, centre, unknown}}, | |||||
{kAudioChannelLayoutTag_DTS_6_1_A, {leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround, LFE, unknown}}, | |||||
{kAudioChannelLayoutTag_AudioUnit_7_0, {left, right, leftSurroundSide, rightSurroundSide, centre, leftSurroundRear, rightSurroundRear, unknown}}, | |||||
{kAudioChannelLayoutTag_MPEG_7_1_C, {left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear}}, | |||||
{kAudioChannelLayoutTag_AudioUnit_7_0_Front,{left, right, leftSurround, rightSurround, centre, leftCentre, rightCentre, unknown}}, | |||||
{kAudioChannelLayoutTag_MPEG_7_1_A, {left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre}}, | |||||
{kAudioChannelLayoutTag_DTS_7_1, {leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround, LFE}}, | |||||
{kAudioChannelLayoutTag_MPEG_3_0_A, {left, right, centre, unknown, unknown, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_MPEG_3_0_B, {centre, left, right, unknown, unknown, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_MPEG_4_0_A, {left, right, centre, centreSurround, unknown, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_MPEG_4_0_B, {centre, left, right, centreSurround, unknown, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_ITU_2_1, {left, right, centreSurround, unknown, unknown, unknown, unknown, unknown}}, | |||||
{kAudioChannelLayoutTag_EAC3_7_1_C, {left, centre, right, leftSurround, rightSurround, LFE, leftSurroundSide, rightSurroundSide}}, | |||||
{unknown, {unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown}} | |||||
}; |
@@ -22,7 +22,7 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#if (JUCE_PLUGINHOST_AU && JUCE_MAC) || DOXYGEN | |||||
#if (JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS)) || DOXYGEN | |||||
//============================================================================== | //============================================================================== | ||||
/** | /** | ||||
@@ -38,16 +38,24 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
String getName() const override { return "AudioUnit"; } | String getName() const override { return "AudioUnit"; } | ||||
void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | ||||
AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc, double, int) override; | |||||
bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | ||||
String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | ||||
bool pluginNeedsRescanning (const PluginDescription&) override; | bool pluginNeedsRescanning (const PluginDescription&) override; | ||||
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; | |||||
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||||
bool doesPluginStillExist (const PluginDescription&) override; | bool doesPluginStillExist (const PluginDescription&) override; | ||||
FileSearchPath getDefaultLocationsToSearch() override; | FileSearchPath getDefaultLocationsToSearch() override; | ||||
bool canScanForPlugins() const override { return true; } | bool canScanForPlugins() const override { return true; } | ||||
private: | private: | ||||
//============================================================================== | |||||
void createPluginInstance (const PluginDescription&, | |||||
double initialSampleRate, | |||||
int initialBufferSize, | |||||
void* userData, | |||||
void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||||
bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||||
//============================================================================== | //============================================================================== | ||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginFormat) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginFormat) | ||||
}; | }; | ||||
@@ -55,8 +63,10 @@ private: | |||||
#endif | #endif | ||||
//============================================================================== | //============================================================================== | ||||
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 | |||||
enum | enum | ||||
{ | { | ||||
/** Custom AudioUnit property used to indicate MPE support */ | /** Custom AudioUnit property used to indicate MPE support */ | ||||
kAudioUnitProperty_SupportsMPE = 75001 | |||||
kAudioUnitProperty_SupportsMPE = 58 | |||||
}; | }; | ||||
#endif |
@@ -610,11 +610,14 @@ void LADSPAPluginFormat::findAllTypesForFile (OwnedArray <PluginDescription>& re | |||||
} | } | ||||
} | } | ||||
AudioPluginInstance* LADSPAPluginFormat::createInstanceFromDescription (const PluginDescription& desc, | |||||
double sampleRate, int blockSize) | |||||
void LADSPAPluginFormat::createPluginInstance (const PluginDescription& desc, | |||||
double sampleRate, int blockSize, | |||||
void* userData, | |||||
void (*callback) (void*, AudioPluginInstance*, const String&)) | |||||
{ | { | ||||
ScopedPointer<LADSPAPluginInstance> result; | ScopedPointer<LADSPAPluginInstance> result; | ||||
if (fileMightContainThisPluginType (desc.fileOrIdentifier)) | if (fileMightContainThisPluginType (desc.fileOrIdentifier)) | ||||
{ | { | ||||
File file (desc.fileOrIdentifier); | File file (desc.fileOrIdentifier); | ||||
@@ -639,7 +642,17 @@ AudioPluginInstance* LADSPAPluginFormat::createInstanceFromDescription (const Pl | |||||
previousWorkingDirectory.setAsCurrentWorkingDirectory(); | previousWorkingDirectory.setAsCurrentWorkingDirectory(); | ||||
} | } | ||||
return result.release(); | |||||
String errorMsg; | |||||
if (result == nullptr) | |||||
errorMsg = String (NEEDS_TRANS ("Unable to load XXX plug-in file")).replace ("XXX", "LADSPA"); | |||||
callback (userData, result.release(), errorMsg); | |||||
} | |||||
bool LADSPAPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept | |||||
{ | |||||
return false; | |||||
} | } | ||||
bool LADSPAPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) | bool LADSPAPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) | ||||
@@ -663,7 +676,7 @@ bool LADSPAPluginFormat::doesPluginStillExist (const PluginDescription& desc) | |||||
return File::createFileWithoutCheckingPath (desc.fileOrIdentifier).exists(); | return File::createFileWithoutCheckingPath (desc.fileOrIdentifier).exists(); | ||||
} | } | ||||
StringArray LADSPAPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive) | |||||
StringArray LADSPAPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive, bool) | |||||
{ | { | ||||
StringArray results; | StringArray results; | ||||
@@ -31,22 +31,28 @@ | |||||
class JUCE_API LADSPAPluginFormat : public AudioPluginFormat | class JUCE_API LADSPAPluginFormat : public AudioPluginFormat | ||||
{ | { | ||||
public: | public: | ||||
//============================================================================== | |||||
LADSPAPluginFormat(); | LADSPAPluginFormat(); | ||||
~LADSPAPluginFormat(); | ~LADSPAPluginFormat(); | ||||
//============================================================================== | //============================================================================== | ||||
String getName() const override { return "LADSPA"; } | String getName() const override { return "LADSPA"; } | ||||
void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | ||||
AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, double, int) override; | |||||
bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | ||||
String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | ||||
bool pluginNeedsRescanning (const PluginDescription&) override; | bool pluginNeedsRescanning (const PluginDescription&) override; | ||||
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; | |||||
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||||
bool doesPluginStillExist (const PluginDescription&) override; | bool doesPluginStillExist (const PluginDescription&) override; | ||||
FileSearchPath getDefaultLocationsToSearch() override; | FileSearchPath getDefaultLocationsToSearch() override; | ||||
bool canScanForPlugins() const override { return true; } | bool canScanForPlugins() const override { return true; } | ||||
private: | |||||
//============================================================================== | |||||
void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||||
int initialBufferSize, void* userData, | |||||
void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||||
bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||||
private: | private: | ||||
void recursiveFileSearch (StringArray&, const File&, bool recursive); | void recursiveFileSearch (StringArray&, const File&, bool recursive); | ||||
@@ -67,17 +67,17 @@ inline juce::String toString (const Steinberg::char16* string) noexcept { re | |||||
inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | ||||
inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | ||||
static void toString128 (Steinberg::Vst::String128 result, const char* source) | |||||
inline void toString128 (Steinberg::Vst::String128 result, const char* source) | |||||
{ | { | ||||
Steinberg::UString (result, 128).fromAscii (source); | Steinberg::UString (result, 128).fromAscii (source); | ||||
} | } | ||||
static void toString128 (Steinberg::Vst::String128 result, const juce::String& source) | |||||
inline void toString128 (Steinberg::Vst::String128 result, const juce::String& source) | |||||
{ | { | ||||
Steinberg::UString (result, 128).fromAscii (source.toUTF8()); | Steinberg::UString (result, 128).fromAscii (source.toUTF8()); | ||||
} | } | ||||
static Steinberg::Vst::TChar* toString (const juce::String& source) noexcept | |||||
inline Steinberg::Vst::TChar* toString (const juce::String& source) noexcept | |||||
{ | { | ||||
return reinterpret_cast<Steinberg::Vst::TChar*> (source.toUTF16().getAddress()); | return reinterpret_cast<Steinberg::Vst::TChar*> (source.toUTF16().getAddress()); | ||||
} | } | ||||
@@ -146,22 +146,22 @@ static inline Steinberg::Vst::Speaker getSpeakerType (AudioChannelSet::ChannelTy | |||||
case AudioChannelSet::left: return kSpeakerL; | case AudioChannelSet::left: return kSpeakerL; | ||||
case AudioChannelSet::right: return kSpeakerR; | case AudioChannelSet::right: return kSpeakerR; | ||||
case AudioChannelSet::centre: return kSpeakerC; | case AudioChannelSet::centre: return kSpeakerC; | ||||
case AudioChannelSet::subbass: return kSpeakerLfe; | |||||
case AudioChannelSet::surroundLeft: return kSpeakerLs; | |||||
case AudioChannelSet::surroundRight: return kSpeakerRs; | |||||
case AudioChannelSet::centreLeft: return kSpeakerLc; | |||||
case AudioChannelSet::centreRight: return kSpeakerRc; | |||||
case AudioChannelSet::surround: return kSpeakerS; | |||||
case AudioChannelSet::sideLeft: return kSpeakerSl; | |||||
case AudioChannelSet::sideRight: return kSpeakerSr; | |||||
case AudioChannelSet::topMiddle: return kSpeakerTm; | |||||
case AudioChannelSet::LFE: return kSpeakerLfe; | |||||
case AudioChannelSet::leftSurround: return kSpeakerLs; | |||||
case AudioChannelSet::rightSurround: return kSpeakerRs; | |||||
case AudioChannelSet::leftCentre: return kSpeakerLc; | |||||
case AudioChannelSet::rightCentre: return kSpeakerRc; | |||||
case AudioChannelSet::centreSurround: return kSpeakerCs; | |||||
case AudioChannelSet::leftSurroundRear: return kSpeakerSl; | |||||
case AudioChannelSet::rightSurroundRear: return kSpeakerSr; | |||||
case AudioChannelSet::topMiddle: return (1 << 11); /* kSpeakerTm */ | |||||
case AudioChannelSet::topFrontLeft: return kSpeakerTfl; | case AudioChannelSet::topFrontLeft: return kSpeakerTfl; | ||||
case AudioChannelSet::topFrontCentre: return kSpeakerTfc; | case AudioChannelSet::topFrontCentre: return kSpeakerTfc; | ||||
case AudioChannelSet::topFrontRight: return kSpeakerTfr; | case AudioChannelSet::topFrontRight: return kSpeakerTfr; | ||||
case AudioChannelSet::topRearLeft: return kSpeakerTrl; | case AudioChannelSet::topRearLeft: return kSpeakerTrl; | ||||
case AudioChannelSet::topRearCentre: return kSpeakerTrc; | case AudioChannelSet::topRearCentre: return kSpeakerTrc; | ||||
case AudioChannelSet::topRearRight: return kSpeakerTrr; | case AudioChannelSet::topRearRight: return kSpeakerTrr; | ||||
case AudioChannelSet::subbass2: return kSpeakerLfe2; | |||||
case AudioChannelSet::LFE2: return kSpeakerLfe2; | |||||
default: break; | default: break; | ||||
} | } | ||||
@@ -177,30 +177,49 @@ static inline AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::Speak | |||||
case kSpeakerL: return AudioChannelSet::left; | case kSpeakerL: return AudioChannelSet::left; | ||||
case kSpeakerR: return AudioChannelSet::right; | case kSpeakerR: return AudioChannelSet::right; | ||||
case kSpeakerC: return AudioChannelSet::centre; | case kSpeakerC: return AudioChannelSet::centre; | ||||
case kSpeakerLfe: return AudioChannelSet::subbass; | |||||
case kSpeakerLs: return AudioChannelSet::surroundLeft; | |||||
case kSpeakerRs: return AudioChannelSet::surroundRight; | |||||
case kSpeakerLc: return AudioChannelSet::centreLeft; | |||||
case kSpeakerRc: return AudioChannelSet::centreRight; | |||||
case kSpeakerS: return AudioChannelSet::surround; | |||||
case kSpeakerSl: return AudioChannelSet::sideLeft; | |||||
case kSpeakerSr: return AudioChannelSet::sideRight; | |||||
case kSpeakerTm: return AudioChannelSet::topMiddle; | |||||
case kSpeakerLfe: return AudioChannelSet::LFE; | |||||
case kSpeakerLs: return AudioChannelSet::leftSurround; | |||||
case kSpeakerRs: return AudioChannelSet::rightSurround; | |||||
case kSpeakerLc: return AudioChannelSet::leftCentre; | |||||
case kSpeakerRc: return AudioChannelSet::rightCentre; | |||||
case kSpeakerCs: return AudioChannelSet::centreSurround; | |||||
case kSpeakerSl: return AudioChannelSet::leftSurroundRear; | |||||
case kSpeakerSr: return AudioChannelSet::rightSurroundRear; | |||||
case (1 << 11): return AudioChannelSet::topMiddle; /* kSpeakerTm */ | |||||
case kSpeakerTfl: return AudioChannelSet::topFrontLeft; | case kSpeakerTfl: return AudioChannelSet::topFrontLeft; | ||||
case kSpeakerTfc: return AudioChannelSet::topFrontCentre; | case kSpeakerTfc: return AudioChannelSet::topFrontCentre; | ||||
case kSpeakerTfr: return AudioChannelSet::topFrontRight; | case kSpeakerTfr: return AudioChannelSet::topFrontRight; | ||||
case kSpeakerTrl: return AudioChannelSet::topRearLeft; | case kSpeakerTrl: return AudioChannelSet::topRearLeft; | ||||
case kSpeakerTrc: return AudioChannelSet::topRearCentre; | case kSpeakerTrc: return AudioChannelSet::topRearCentre; | ||||
case kSpeakerTrr: return AudioChannelSet::topRearRight; | case kSpeakerTrr: return AudioChannelSet::topRearRight; | ||||
case kSpeakerLfe2: return AudioChannelSet::subbass2; | |||||
case kSpeakerLfe2: return AudioChannelSet::LFE2; | |||||
default: break; | default: break; | ||||
} | } | ||||
return AudioChannelSet::unknown; | return AudioChannelSet::unknown; | ||||
} | } | ||||
static inline Steinberg::Vst::SpeakerArrangement getSpeakerArrangement (const AudioChannelSet& channels) noexcept | |||||
static inline Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept | |||||
{ | { | ||||
if (channels == AudioChannelSet::disabled()) return Steinberg::Vst::SpeakerArr::kEmpty; | |||||
else if (channels == AudioChannelSet::mono()) return Steinberg::Vst::SpeakerArr::kMono; | |||||
else if (channels == AudioChannelSet::stereo()) return Steinberg::Vst::SpeakerArr::kStereo; | |||||
else if (channels == AudioChannelSet::createLCR()) return Steinberg::Vst::SpeakerArr::k30Cine; | |||||
else if (channels == AudioChannelSet::createLRS()) return Steinberg::Vst::SpeakerArr::k30Music; | |||||
else if (channels == AudioChannelSet::createLCRS()) return Steinberg::Vst::SpeakerArr::k40Cine; | |||||
else if (channels == AudioChannelSet::create5point0()) return Steinberg::Vst::SpeakerArr::k50; | |||||
else if (channels == AudioChannelSet::create5point1()) return Steinberg::Vst::SpeakerArr::k51; | |||||
else if (channels == AudioChannelSet::create6point0()) return Steinberg::Vst::SpeakerArr::k60Cine; | |||||
else if (channels == AudioChannelSet::create6point1()) return Steinberg::Vst::SpeakerArr::k61Cine; | |||||
else if (channels == AudioChannelSet::create6point0Music()) return Steinberg::Vst::SpeakerArr::k60Music; | |||||
else if (channels == AudioChannelSet::create6point1Music()) return Steinberg::Vst::SpeakerArr::k61Music; | |||||
else if (channels == AudioChannelSet::create7point0()) return Steinberg::Vst::SpeakerArr::k70Music; | |||||
else if (channels == AudioChannelSet::create7point0SDDS()) return Steinberg::Vst::SpeakerArr::k70Cine; | |||||
else if (channels == AudioChannelSet::create7point1()) return Steinberg::Vst::SpeakerArr::k71CineSideFill; | |||||
else if (channels == AudioChannelSet::create7point1SDDS()) return Steinberg::Vst::SpeakerArr::k71Cine; | |||||
else if (channels == AudioChannelSet::ambisonic()) return Steinberg::Vst::SpeakerArr::kBFormat; | |||||
else if (channels == AudioChannelSet::quadraphonic()) return Steinberg::Vst::SpeakerArr::k40Music; | |||||
Steinberg::Vst::SpeakerArrangement result = 0; | Steinberg::Vst::SpeakerArrangement result = 0; | ||||
Array<AudioChannelSet::ChannelType> types (channels.getChannelTypes()); | Array<AudioChannelSet::ChannelType> types (channels.getChannelTypes()); | ||||
@@ -213,6 +232,25 @@ static inline Steinberg::Vst::SpeakerArrangement getSpeakerArrangement (const Au | |||||
static inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept | static inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept | ||||
{ | { | ||||
if (arr == Steinberg::Vst::SpeakerArr::kEmpty) return AudioChannelSet::disabled(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::kMono) return AudioChannelSet::mono(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::kStereo) return AudioChannelSet::stereo(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k30Cine) return AudioChannelSet::createLCR(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k30Music) return AudioChannelSet::createLRS(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k40Cine) return AudioChannelSet::createLCRS(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k50) return AudioChannelSet::create5point0(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k51) return AudioChannelSet::create5point1(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k60Cine) return AudioChannelSet::create6point0(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k61Cine) return AudioChannelSet::create6point1(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k60Music) return AudioChannelSet::create6point0Music(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k61Music) return AudioChannelSet::create6point1Music(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k70Music) return AudioChannelSet::create7point0(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k70Cine) return AudioChannelSet::create7point0SDDS(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k71CineSideFill) return AudioChannelSet::create7point1(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k71Cine) return AudioChannelSet::create7point1SDDS(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::kBFormat) return AudioChannelSet::ambisonic(); | |||||
else if (arr == Steinberg::Vst::SpeakerArr::k40Music) return AudioChannelSet::quadraphonic(); | |||||
AudioChannelSet result; | AudioChannelSet result; | ||||
for (Steinberg::Vst::Speaker speaker = 1; speaker <= Steinberg::Vst::kSpeakerRcs; speaker <<= 1) | for (Steinberg::Vst::Speaker speaker = 1; speaker <= Steinberg::Vst::kSpeakerRcs; speaker <<= 1) | ||||
@@ -462,12 +500,12 @@ struct VST3BufferExchange | |||||
vstBuffers.silenceFlags = 0; | vstBuffers.silenceFlags = 0; | ||||
} | } | ||||
static void mapArrangementToBusses (int& channelIndexOffset, int index, | |||||
static void mapArrangementToBuses (int& channelIndexOffset, int index, | |||||
Array<Steinberg::Vst::AudioBusBuffers>& result, | Array<Steinberg::Vst::AudioBusBuffers>& result, | ||||
BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, | |||||
BusMap& busMapToUse, const AudioChannelSet& arrangement, | |||||
AudioBuffer<FloatType>& source) | AudioBuffer<FloatType>& source) | ||||
{ | { | ||||
const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); | |||||
const int numChansForBus = arrangement.size(); | |||||
if (index >= result.size()) | if (index >= result.size()) | ||||
result.add (Steinberg::Vst::AudioBusBuffers()); | result.add (Steinberg::Vst::AudioBusBuffers()); | ||||
@@ -483,26 +521,26 @@ struct VST3BufferExchange | |||||
channelIndexOffset += numChansForBus; | channelIndexOffset += numChansForBus; | ||||
} | } | ||||
static inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse, | |||||
const Array<Steinberg::Vst::SpeakerArrangement>& arrangements, | |||||
static inline void mapBufferToBuses (Array<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse, | |||||
const Array<AudioChannelSet>& arrangements, | |||||
AudioBuffer<FloatType>& source) | AudioBuffer<FloatType>& source) | ||||
{ | { | ||||
int channelIndexOffset = 0; | int channelIndexOffset = 0; | ||||
for (int i = 0; i < arrangements.size(); ++i) | for (int i = 0; i < arrangements.size(); ++i) | ||||
mapArrangementToBusses (channelIndexOffset, i, result, busMapToUse, | |||||
mapArrangementToBuses (channelIndexOffset, i, result, busMapToUse, | |||||
arrangements.getUnchecked (i), source); | arrangements.getUnchecked (i), source); | ||||
} | } | ||||
static inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, | |||||
static inline void mapBufferToBuses (Array<Steinberg::Vst::AudioBusBuffers>& result, | |||||
Steinberg::Vst::IAudioProcessor& processor, | Steinberg::Vst::IAudioProcessor& processor, | ||||
BusMap& busMapToUse, bool isInput, int numBusses, | |||||
BusMap& busMapToUse, bool isInput, int numBuses, | |||||
AudioBuffer<FloatType>& source) | AudioBuffer<FloatType>& source) | ||||
{ | { | ||||
int channelIndexOffset = 0; | int channelIndexOffset = 0; | ||||
for (int i = 0; i < numBusses; ++i) | |||||
mapArrangementToBusses (channelIndexOffset, i, | |||||
for (int i = 0; i < numBuses; ++i) | |||||
mapArrangementToBuses (channelIndexOffset, i, | |||||
result, busMapToUse, | result, busMapToUse, | ||||
getArrangementForBus (&processor, isInput, i), | getArrangementForBus (&processor, isInput, i), | ||||
source); | source); | ||||
@@ -47,12 +47,15 @@ | |||||
#pragma clang diagnostic ignored "-Wdelete-non-virtual-dtor" | #pragma clang diagnostic ignored "-Wdelete-non-virtual-dtor" | ||||
#endif | #endif | ||||
#undef DEVELOPMENT | |||||
#define DEVELOPMENT 0 // This avoids a Clang warning in Steinberg code about unused values | |||||
/* These files come with the Steinberg VST3 SDK - to get them, you'll need to | /* These files come with the Steinberg VST3 SDK - to get them, you'll need to | ||||
visit the Steinberg website and agree to whatever is currently required to | visit the Steinberg website and agree to whatever is currently required to | ||||
get them. | get them. | ||||
Then, you'll need to make sure your include path contains your "VST3 SDK" | Then, you'll need to make sure your include path contains your "VST3 SDK" | ||||
directory (or whatever you've named it on your machine). The Introjucer has | |||||
directory (or whatever you've named it on your machine). The Projucer has | |||||
a special box for setting this path. | a special box for setting this path. | ||||
*/ | */ | ||||
#if JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY | #if JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY | ||||
@@ -78,6 +81,7 @@ | |||||
#include <pluginterfaces/vst/ivstunits.h> | #include <pluginterfaces/vst/ivstunits.h> | ||||
#include <pluginterfaces/vst/ivstmidicontrollers.h> | #include <pluginterfaces/vst/ivstmidicontrollers.h> | ||||
#include <public.sdk/source/common/memorystream.h> | #include <public.sdk/source/common/memorystream.h> | ||||
#include <public.sdk/source/vst/vsteditcontroller.h> | |||||
#else | #else | ||||
#if JUCE_MINGW | #if JUCE_MINGW | ||||
#define _set_abort_behavior(...) | #define _set_abort_behavior(...) | ||||
@@ -128,6 +132,10 @@ namespace Steinberg | |||||
#pragma clang diagnostic pop | #pragma clang diagnostic pop | ||||
#endif | #endif | ||||
#if JUCE_WINDOWS | |||||
#include <windows.h> | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
#undef ASSERT | #undef ASSERT | ||||
#undef WARNING | #undef WARNING | ||||
@@ -25,11 +25,12 @@ | |||||
#ifndef JUCE_VST3PLUGINFORMAT_H_INCLUDED | #ifndef JUCE_VST3PLUGINFORMAT_H_INCLUDED | ||||
#define JUCE_VST3PLUGINFORMAT_H_INCLUDED | #define JUCE_VST3PLUGINFORMAT_H_INCLUDED | ||||
#if JUCE_PLUGINHOST_VST3 | |||||
#if (JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS)) || DOXYGEN | |||||
/** | /** | ||||
Implements a plugin format for VST3s. | Implements a plugin format for VST3s. | ||||
*/ | */ | ||||
class JUCE_API VST3PluginFormat : public AudioPluginFormat | |||||
class JUCE_API VST3PluginFormat : public AudioPluginFormat | |||||
{ | { | ||||
public: | public: | ||||
/** Constructor */ | /** Constructor */ | ||||
@@ -39,32 +40,27 @@ public: | |||||
~VST3PluginFormat(); | ~VST3PluginFormat(); | ||||
//============================================================================== | //============================================================================== | ||||
/** @internal */ | |||||
String getName() const override { return "VST3"; } | |||||
/** @internal */ | |||||
void findAllTypesForFile (OwnedArray<PluginDescription>& results, const String& fileOrIdentifier) override; | |||||
/** @internal */ | |||||
AudioPluginInstance* createInstanceFromDescription (const PluginDescription& description, double, int) override; | |||||
/** @internal */ | |||||
String getName() const override { return "VST3"; } | |||||
void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | |||||
bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | ||||
/** @internal */ | |||||
String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | ||||
/** @internal */ | |||||
bool pluginNeedsRescanning (const PluginDescription& description) override; | |||||
/** @internal */ | |||||
StringArray searchPathsForPlugins (const FileSearchPath& searchPath, bool recursive) override; | |||||
/** @internal */ | |||||
bool doesPluginStillExist (const PluginDescription& description) override; | |||||
/** @internal */ | |||||
bool pluginNeedsRescanning (const PluginDescription&) override; | |||||
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||||
bool doesPluginStillExist (const PluginDescription&) override; | |||||
FileSearchPath getDefaultLocationsToSearch() override; | FileSearchPath getDefaultLocationsToSearch() override; | ||||
/** @internal */ | |||||
bool canScanForPlugins() const override { return true; } | |||||
bool canScanForPlugins() const override { return true; } | |||||
private: | |||||
void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||||
int initialBufferSize, void* userData, | |||||
void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||||
bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||||
private: | private: | ||||
//============================================================================== | //============================================================================== | ||||
void recursiveFileSearch (StringArray&, const File&, bool recursive); | void recursiveFileSearch (StringArray&, const File&, bool recursive); | ||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginFormat) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginFormat) | ||||
}; | }; | ||||
@@ -0,0 +1,238 @@ | |||||
/* | |||||
============================================================================== | |||||
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_VSTCOMMON_H_INCLUDED | |||||
#define JUCE_VSTCOMMON_H_INCLUDED | |||||
//============================================================================== | |||||
struct SpeakerMappings : private AudioChannelSet // (inheritance only to give easier access to items in the namespace) | |||||
{ | |||||
struct Mapping | |||||
{ | |||||
int32 vst2; | |||||
ChannelType channels[13]; | |||||
bool matches (const Array<ChannelType>& chans) const noexcept | |||||
{ | |||||
const int n = sizeof (channels) / sizeof (ChannelType); | |||||
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; | |||||
} | |||||
return true; | |||||
} | |||||
}; | |||||
static AudioChannelSet vstArrangementTypeToChannelSet (int32 arr, int fallbackNumChannels) | |||||
{ | |||||
if (arr == vstSpeakerConfigTypeEmpty) return AudioChannelSet::disabled(); | |||||
else if (arr == vstSpeakerConfigTypeMono) return AudioChannelSet::mono(); | |||||
else if (arr == vstSpeakerConfigTypeLR) return AudioChannelSet::stereo(); | |||||
else if (arr == vstSpeakerConfigTypeLRC) return AudioChannelSet::createLCR(); | |||||
else if (arr == vstSpeakerConfigTypeLRS) return AudioChannelSet::createLRS(); | |||||
else if (arr == vstSpeakerConfigTypeLRCS) return AudioChannelSet::createLCRS(); | |||||
else if (arr == vstSpeakerConfigTypeLRCLsRs) return AudioChannelSet::create5point0(); | |||||
else if (arr == vstSpeakerConfigTypeLRCLfeLsRs) return AudioChannelSet::create5point1(); | |||||
else if (arr == vstSpeakerConfigTypeLRCLsRsCs) return AudioChannelSet::create6point0(); | |||||
else if (arr == vstSpeakerConfigTypeLRCLfeLsRsCs) return AudioChannelSet::create6point1(); | |||||
else if (arr == vstSpeakerConfigTypeLRLsRsSlSr) return AudioChannelSet::create6point0Music(); | |||||
else if (arr == vstSpeakerConfigTypeLRLfeLsRsSlSr) return AudioChannelSet::create6point1Music(); | |||||
else if (arr == vstSpeakerConfigTypeLRCLsRsSlSr) return AudioChannelSet::create7point0(); | |||||
else if (arr == vstSpeakerConfigTypeLRCLsRsLcRc) return AudioChannelSet::create7point0SDDS(); | |||||
else if (arr == vstSpeakerConfigTypeLRCLfeLsRsSlSr) return AudioChannelSet::create7point1(); | |||||
else if (arr == vstSpeakerConfigTypeLRCLfeLsRsLcRc) return AudioChannelSet::create7point1SDDS(); | |||||
else if (arr == vstSpeakerConfigTypeLRLsRs) return AudioChannelSet::quadraphonic(); | |||||
for (const Mapping* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m) | |||||
{ | |||||
if (m->vst2 == arr) | |||||
{ | |||||
AudioChannelSet s; | |||||
for (int i = 0; m->channels[i] != 0; ++i) | |||||
s.addChannel (m->channels[i]); | |||||
return s; | |||||
} | |||||
} | |||||
return AudioChannelSet::discreteChannels (fallbackNumChannels); | |||||
} | |||||
static AudioChannelSet vstArrangementTypeToChannelSet (const VstSpeakerConfiguration& arr) | |||||
{ | |||||
return vstArrangementTypeToChannelSet (arr.type, arr.numberOfChannels); | |||||
} | |||||
static int32 channelSetToVstArrangementType (AudioChannelSet channels) | |||||
{ | |||||
if (channels == AudioChannelSet::disabled()) return vstSpeakerConfigTypeEmpty; | |||||
else if (channels == AudioChannelSet::mono()) return vstSpeakerConfigTypeMono; | |||||
else if (channels == AudioChannelSet::stereo()) return vstSpeakerConfigTypeLR; | |||||
else if (channels == AudioChannelSet::createLCR()) return vstSpeakerConfigTypeLRC; | |||||
else if (channels == AudioChannelSet::createLRS()) return vstSpeakerConfigTypeLRS; | |||||
else if (channels == AudioChannelSet::createLCRS()) return vstSpeakerConfigTypeLRCS; | |||||
else if (channels == AudioChannelSet::create5point0()) return vstSpeakerConfigTypeLRCLsRs; | |||||
else if (channels == AudioChannelSet::create5point1()) return vstSpeakerConfigTypeLRCLfeLsRs; | |||||
else if (channels == AudioChannelSet::create6point0()) return vstSpeakerConfigTypeLRCLsRsCs; | |||||
else if (channels == AudioChannelSet::create6point1()) return vstSpeakerConfigTypeLRCLfeLsRsCs; | |||||
else if (channels == AudioChannelSet::create6point0Music()) return vstSpeakerConfigTypeLRLsRsSlSr; | |||||
else if (channels == AudioChannelSet::create6point1Music()) return vstSpeakerConfigTypeLRLfeLsRsSlSr; | |||||
else if (channels == AudioChannelSet::create7point0()) return vstSpeakerConfigTypeLRCLsRsSlSr; | |||||
else if (channels == AudioChannelSet::create7point0SDDS()) return vstSpeakerConfigTypeLRCLsRsLcRc; | |||||
else if (channels == AudioChannelSet::create7point1()) return vstSpeakerConfigTypeLRCLfeLsRsSlSr; | |||||
else if (channels == AudioChannelSet::create7point1SDDS()) return vstSpeakerConfigTypeLRCLfeLsRsLcRc; | |||||
else if (channels == AudioChannelSet::quadraphonic()) return vstSpeakerConfigTypeLRLsRs; | |||||
Array<AudioChannelSet::ChannelType> chans (channels.getChannelTypes()); | |||||
if (channels == AudioChannelSet::disabled()) | |||||
return vstSpeakerConfigTypeEmpty; | |||||
for (const Mapping* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m) | |||||
if (m->matches (chans)) | |||||
return m->vst2; | |||||
return vstSpeakerConfigTypeUser; | |||||
} | |||||
static void channelSetToVstArrangement (const AudioChannelSet& channels, VstSpeakerConfiguration& result) | |||||
{ | |||||
result.type = channelSetToVstArrangementType (channels); | |||||
result.numberOfChannels = channels.size(); | |||||
for (int i = 0; i < result.numberOfChannels; ++i) | |||||
{ | |||||
VstIndividualSpeakerInfo& speaker = result.speakers[i]; | |||||
zeromem (&speaker, sizeof (VstIndividualSpeakerInfo)); | |||||
speaker.type = getSpeakerType (channels.getTypeOfChannel (i)); | |||||
} | |||||
} | |||||
static const Mapping* getMappings() noexcept | |||||
{ | |||||
static const Mapping mappings[] = | |||||
{ | |||||
{ vstSpeakerConfigTypeMono, { centre, unknown } }, | |||||
{ vstSpeakerConfigTypeLR, { left, right, unknown } }, | |||||
{ vstSpeakerConfigTypeLsRs, { leftSurround, rightSurround, unknown } }, | |||||
{ vstSpeakerConfigTypeLcRc, { leftCentre, rightCentre, unknown } }, | |||||
{ vstSpeakerConfigTypeSlSr, { leftSurroundRear, rightSurroundRear, unknown } }, | |||||
{ vstSpeakerConfigTypeCLfe, { centre, LFE, unknown } }, | |||||
{ vstSpeakerConfigTypeLRC, { left, right, centre, unknown } }, | |||||
{ vstSpeakerConfigTypeLRS, { left, right, surround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLfe, { left, right, centre, LFE, unknown } }, | |||||
{ vstSpeakerConfigTypeLRLfeS, { left, right, LFE, surround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCS, { left, right, centre, surround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRLsRs, { left, right, leftSurround, rightSurround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLfeS, { left, right, centre, LFE, surround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRLfeLsRs, { left, right, LFE, leftSurround, rightSurround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLsRs, { left, right, centre, leftSurround, rightSurround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLfeLsRs, { left, right, centre, LFE, leftSurround, rightSurround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLsRsCs, { left, right, centre, leftSurround, rightSurround, surround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRLsRsSlSr, { left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLfeLsRsCs, { left, right, centre, LFE, leftSurround, rightSurround, surround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRLfeLsRsSlSr, { left, right, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLsRsLcRc, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLsRsSlSr, { left, right, centre, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLfeLsRsLcRc, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLfeLsRsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLsRsLcRcCs, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLsRsCsSlSr, { left, right, centre, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
{ vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontCentre, topFrontRight, topRearLeft, topRearRight, LFE2, unknown } }, | |||||
{ vstSpeakerConfigTypeEmpty, { unknown } } | |||||
}; | |||||
return mappings; | |||||
} | |||||
static inline int32 getSpeakerType (AudioChannelSet::ChannelType type) noexcept | |||||
{ | |||||
switch (type) | |||||
{ | |||||
case AudioChannelSet::left: return vstIndividualSpeakerTypeLeft; | |||||
case AudioChannelSet::right: return vstIndividualSpeakerTypeRight; | |||||
case AudioChannelSet::centre: return vstIndividualSpeakerTypeCentre; | |||||
case AudioChannelSet::LFE: return vstIndividualSpeakerTypeLFE; | |||||
case AudioChannelSet::leftSurround: return vstIndividualSpeakerTypeLeftSurround; | |||||
case AudioChannelSet::rightSurround: return vstIndividualSpeakerTypeRightSurround; | |||||
case AudioChannelSet::leftCentre: return vstIndividualSpeakerTypeLeftCentre; | |||||
case AudioChannelSet::rightCentre: return vstIndividualSpeakerTypeRightCentre; | |||||
case AudioChannelSet::surround: return vstIndividualSpeakerTypeSurround; | |||||
case AudioChannelSet::leftSurroundRear: return vstIndividualSpeakerTypeLeftRearSurround; | |||||
case AudioChannelSet::rightSurroundRear: return vstIndividualSpeakerTypeRightRearSurround; | |||||
case AudioChannelSet::topMiddle: return vstIndividualSpeakerTypeTopMiddle; | |||||
case AudioChannelSet::topFrontLeft: return vstIndividualSpeakerTypeTopFrontLeft; | |||||
case AudioChannelSet::topFrontCentre: return vstIndividualSpeakerTypeTopFrontCentre; | |||||
case AudioChannelSet::topFrontRight: return vstIndividualSpeakerTypeTopFrontRight; | |||||
case AudioChannelSet::topRearLeft: return vstIndividualSpeakerTypeTopRearLeft; | |||||
case AudioChannelSet::topRearCentre: return vstIndividualSpeakerTypeTopRearCentre; | |||||
case AudioChannelSet::topRearRight: return vstIndividualSpeakerTypeTopRearRight; | |||||
case AudioChannelSet::LFE2: return vstIndividualSpeakerTypeLFE2; | |||||
default: break; | |||||
} | |||||
return 0; | |||||
} | |||||
static inline AudioChannelSet::ChannelType getChannelType (int32 type) noexcept | |||||
{ | |||||
switch (type) | |||||
{ | |||||
case vstIndividualSpeakerTypeLeft: return AudioChannelSet::left; | |||||
case vstIndividualSpeakerTypeRight: return AudioChannelSet::right; | |||||
case vstIndividualSpeakerTypeCentre: return AudioChannelSet::centre; | |||||
case vstIndividualSpeakerTypeLFE: return AudioChannelSet::LFE; | |||||
case vstIndividualSpeakerTypeLeftSurround: return AudioChannelSet::leftSurround; | |||||
case vstIndividualSpeakerTypeRightSurround: return AudioChannelSet::rightSurround; | |||||
case vstIndividualSpeakerTypeLeftCentre: return AudioChannelSet::leftCentre; | |||||
case vstIndividualSpeakerTypeRightCentre: return AudioChannelSet::rightCentre; | |||||
case vstIndividualSpeakerTypeSurround: return AudioChannelSet::surround; | |||||
case vstIndividualSpeakerTypeLeftRearSurround: return AudioChannelSet::leftSurroundRear; | |||||
case vstIndividualSpeakerTypeRightRearSurround: return AudioChannelSet::rightSurroundRear; | |||||
case vstIndividualSpeakerTypeTopMiddle: return AudioChannelSet::topMiddle; | |||||
case vstIndividualSpeakerTypeTopFrontLeft: return AudioChannelSet::topFrontLeft; | |||||
case vstIndividualSpeakerTypeTopFrontCentre: return AudioChannelSet::topFrontCentre; | |||||
case vstIndividualSpeakerTypeTopFrontRight: return AudioChannelSet::topFrontRight; | |||||
case vstIndividualSpeakerTypeTopRearLeft: return AudioChannelSet::topRearLeft; | |||||
case vstIndividualSpeakerTypeTopRearCentre: return AudioChannelSet::topRearCentre; | |||||
case vstIndividualSpeakerTypeTopRearRight: return AudioChannelSet::topRearRight; | |||||
case vstIndividualSpeakerTypeLFE2: return AudioChannelSet::LFE2; | |||||
default: break; | |||||
} | |||||
return AudioChannelSet::unknown; | |||||
} | |||||
}; | |||||
#endif // JUCE_VSTCOMMON_H_INCLUDED |
@@ -0,0 +1,458 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2016 - 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_VSTINTERFACE_H_INCLUDED | |||||
#define JUCE_VSTINTERFACE_H_INCLUDED | |||||
#include "../../juce_core/juce_core.h" | |||||
using namespace juce; | |||||
#if JUCE_MSVC | |||||
#define VSTINTERFACECALL __cdecl | |||||
#pragma pack(push) | |||||
#pragma pack(8) | |||||
#elif JUCE_MAC || JUCE_IOS | |||||
#define VSTINTERFACECALL | |||||
#if JUCE_64BIT | |||||
#pragma options align=power | |||||
#else | |||||
#pragma options align=mac68k | |||||
#endif | |||||
#else | |||||
#define VSTINTERFACECALL | |||||
#pragma pack(push, 8) | |||||
#endif | |||||
const int32 juceVstInterfaceVersion = 2400; | |||||
const int32 juceVstInterfaceIdentifier = 0x56737450; // The "magic" identifier in the SDK is 'VstP'. | |||||
//============================================================================== | |||||
struct VstEffectInterface | |||||
{ | |||||
int32 interfaceIdentifier; | |||||
pointer_sized_int (VSTINTERFACECALL* dispatchFunction) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); | |||||
void (VSTINTERFACECALL* processAudioFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); | |||||
void (VSTINTERFACECALL* setParameterValueFunction) (VstEffectInterface*, int32 parameterIndex, float value); | |||||
float (VSTINTERFACECALL* getParameterValueFunction) (VstEffectInterface*, int32 parameterIndex); | |||||
int32 numPrograms; | |||||
int32 numParameters; | |||||
int32 numInputChannels; | |||||
int32 numOutputChannels; | |||||
int32 flags; | |||||
pointer_sized_int hostSpace1; | |||||
pointer_sized_int hostSpace2; | |||||
int32 latency; | |||||
int32 deprecated1; | |||||
int32 deprecated2; | |||||
float deprecated3; | |||||
void* effectPointer; | |||||
void* userPointer; | |||||
int32 plugInIdentifier; | |||||
int32 plugInVersion; | |||||
void (VSTINTERFACECALL* processAudioInplaceFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); | |||||
void (VSTINTERFACECALL* processDoubleAudioInplaceFunction) (VstEffectInterface*, double** inputs, double** outputs, int32 numSamples); | |||||
char emptySpace[56]; | |||||
}; | |||||
typedef pointer_sized_int (VSTINTERFACECALL* VstHostCallback) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); | |||||
enum VstEffectInterfaceFlags | |||||
{ | |||||
vstEffectFlagHasEditor = 1, | |||||
vstEffectFlagInplaceAudio = 16, | |||||
vstEffectFlagDataInChunks = 32, | |||||
vstEffectFlagIsSynth = 256, | |||||
vstEffectFlagInplaceDoubleAudio = 4096 | |||||
}; | |||||
//============================================================================== | |||||
enum VstHostToPlugInOpcodes | |||||
{ | |||||
plugInOpcodeOpen, | |||||
plugInOpcodeClose, | |||||
plugInOpcodeSetCurrentProgram, | |||||
plugInOpcodeGetCurrentProgram, | |||||
plugInOpcodeSetCurrentProgramName, | |||||
plugInOpcodeGetCurrentProgramName, | |||||
plugInOpcodeGetParameterLabel, | |||||
plugInOpcodeGetParameterText, | |||||
plugInOpcodeGetParameterName, | |||||
plugInOpcodeSetSampleRate = plugInOpcodeGetParameterName + 2, | |||||
plugInOpcodeSetBlockSize, | |||||
plugInOpcodeResumeSuspend, | |||||
plugInOpcodeGetEditorBounds, | |||||
plugInOpcodeOpenEditor, | |||||
plugInOpcodeCloseEditor, | |||||
plugInOpcodeDrawEditor, | |||||
plugInOpcodeGetMouse, | |||||
plugInOpcodeEditorIdle = plugInOpcodeGetMouse + 2, | |||||
plugInOpcodeeffEditorTop, | |||||
plugInOpcodeSleepEditor, | |||||
plugInOpcodeIdentify, | |||||
plugInOpcodeGetData, | |||||
plugInOpcodeSetData, | |||||
plugInOpcodePreAudioProcessingEvents, | |||||
plugInOpcodeIsParameterAutomatable, | |||||
plugInOpcodeParameterValueForText, | |||||
plugInOpcodeGetProgramName = plugInOpcodeParameterValueForText + 2, | |||||
plugInOpcodeConnectInput = plugInOpcodeGetProgramName + 2, | |||||
plugInOpcodeConnectOutput, | |||||
plugInOpcodeGetInputPinProperties, | |||||
plugInOpcodeGetOutputPinProperties, | |||||
plugInOpcodeGetPlugInCategory, | |||||
plugInOpcodeSetSpeakerConfiguration = plugInOpcodeGetPlugInCategory + 7, | |||||
plugInOpcodeSetBypass = plugInOpcodeSetSpeakerConfiguration + 2, | |||||
plugInOpcodeGetPlugInName, | |||||
plugInOpcodeGetManufacturerName = plugInOpcodeGetPlugInName + 2, | |||||
plugInOpcodeGetManufacturerProductName, | |||||
plugInOpcodeGetManufacturerVersion, | |||||
plugInOpcodeManufacturerSpecific, | |||||
plugInOpcodeCanPlugInDo, | |||||
plugInOpcodeGetTailSize, | |||||
plugInOpcodeIdle, | |||||
plugInOpcodeKeyboardFocusRequired = plugInOpcodeIdle + 4, | |||||
plugInOpcodeGetVstInterfaceVersion, | |||||
plugInOpcodeGetCurrentMidiProgram = plugInOpcodeGetVstInterfaceVersion + 5, | |||||
plugInOpcodeGetSpeakerArrangement = plugInOpcodeGetCurrentMidiProgram + 6, | |||||
plugInOpcodeNextPlugInUniqueID, | |||||
plugInOpcodeStartProcess, | |||||
plugInOpcodeStopProcess, | |||||
plugInOpcodeSetNumberOfSamplesToProcess, | |||||
plugInOpcodeSetSampleFloatType = plugInOpcodeSetNumberOfSamplesToProcess + 4, | |||||
plugInOpcodeMaximum = plugInOpcodeSetSampleFloatType | |||||
}; | |||||
enum VstPlugInToHostOpcodes | |||||
{ | |||||
hostOpcodeParameterChanged, | |||||
hostOpcodeVstVersion, | |||||
hostOpcodeCurrentId, | |||||
hostOpcodeIdle, | |||||
hostOpcodePinConnected, | |||||
hostOpcodePlugInWantsMidi = hostOpcodePinConnected + 2, | |||||
hostOpcodeGetTimingInfo, | |||||
hostOpcodePreAudioProcessingEvents, | |||||
hostOpcodeSetTime, | |||||
hostOpcodeTempoAt, | |||||
hostOpcodeGetNumberOfAutomatableParameters, | |||||
hostOpcodeGetParameterInterval, | |||||
hostOpcodeIOModified, | |||||
hostOpcodeNeedsIdle, | |||||
hostOpcodeWindowSize, | |||||
hostOpcodeGetSampleRate, | |||||
hostOpcodeGetBlockSize, | |||||
hostOpcodeGetInputLatency, | |||||
hostOpcodeGetOutputLatency, | |||||
hostOpcodeGetPreviousPlugIn, | |||||
hostOpcodeGetNextPlugIn, | |||||
hostOpcodeWillReplace, | |||||
hostOpcodeGetCurrentAudioProcessingLevel, | |||||
hostOpcodeGetAutomationState, | |||||
hostOpcodeOfflineStart, | |||||
hostOpcodeOfflineReadSource, | |||||
hostOpcodeOfflineWrite, | |||||
hostOpcodeOfflineGetCurrentPass, | |||||
hostOpcodeOfflineGetCurrentMetaPass, | |||||
hostOpcodeSetOutputSampleRate, | |||||
hostOpcodeGetOutputSpeakerConfiguration, | |||||
hostOpcodeGetManufacturerName, | |||||
hostOpcodeGetProductName, | |||||
hostOpcodeGetManufacturerVersion, | |||||
hostOpcodeManufacturerSpecific, | |||||
hostOpcodeSetIcon, | |||||
hostOpcodeCanHostDo, | |||||
hostOpcodeGetLanguage, | |||||
hostOpcodeOpenEditorWindow, | |||||
hostOpcodeCloseEditorWindow, | |||||
hostOpcodeGetDirectory, | |||||
hostOpcodeUpdateView, | |||||
hostOpcodeParameterChangeGestureBegin, | |||||
hostOpcodeParameterChangeGestureEnd, | |||||
}; | |||||
//============================================================================== | |||||
enum VstProcessingSampleType | |||||
{ | |||||
vstProcessingSampleTypeFloat, | |||||
vstProcessingSampleTypeDouble | |||||
}; | |||||
//============================================================================== | |||||
// These names must be identical to the Steinberg SDK so JUCE users can set | |||||
// exactly what they want. | |||||
enum VstPlugInCategory | |||||
{ | |||||
kPlugCategUnknown, | |||||
kPlugCategEffect, | |||||
kPlugCategSynth, | |||||
kPlugCategAnalysis, | |||||
kPlugCategMastering, | |||||
kPlugCategSpacializer, | |||||
kPlugCategRoomFx, | |||||
kPlugSurroundFx, | |||||
kPlugCategRestoration, | |||||
kPlugCategOfflineProcess, | |||||
kPlugCategShell, | |||||
kPlugCategGenerator | |||||
}; | |||||
//============================================================================== | |||||
struct VstEditorBounds | |||||
{ | |||||
int16 upper; | |||||
int16 leftmost; | |||||
int16 lower; | |||||
int16 rightmost; | |||||
}; | |||||
//============================================================================== | |||||
enum VstMaxStringLengths | |||||
{ | |||||
vstMaxNameLength = 64, | |||||
vstMaxParameterOrPinLabelLength = 64, | |||||
vstMaxParameterOrPinShortLabelLength = 8, | |||||
vstMaxCategoryLength = 24, | |||||
vstMaxManufacturerStringLength = 64, | |||||
vstMaxPlugInNameStringLength = 64 | |||||
}; | |||||
//============================================================================== | |||||
struct VstPinInfo | |||||
{ | |||||
char text[vstMaxParameterOrPinLabelLength]; | |||||
int32 flags; | |||||
int32 configurationType; | |||||
char shortText[vstMaxParameterOrPinShortLabelLength]; | |||||
char unused[48]; | |||||
}; | |||||
enum VstPinInfoFlags | |||||
{ | |||||
vstPinInfoFlagIsActive = 1, | |||||
vstPinInfoFlagIsStereo = 2, | |||||
vstPinInfoFlagValid = 4 | |||||
}; | |||||
//============================================================================== | |||||
struct VstEvent | |||||
{ | |||||
int32 type; | |||||
int32 size; | |||||
int32 sampleOffset; | |||||
int32 flags; | |||||
char content[16]; | |||||
}; | |||||
enum VstEventTypes | |||||
{ | |||||
vstMidiEventType = 1, | |||||
vstSysExEventType = 6 | |||||
}; | |||||
struct VstEventBlock | |||||
{ | |||||
int32 numberOfEvents; | |||||
pointer_sized_int future; | |||||
VstEvent* events[2]; | |||||
}; | |||||
struct VstMidiEvent | |||||
{ | |||||
int32 type; | |||||
int32 size; | |||||
int32 sampleOffset; | |||||
int32 flags; | |||||
int32 noteSampleLength; | |||||
int32 noteSampleOffset; | |||||
char midiData[4]; | |||||
char tuning; | |||||
char noteVelocityOff; | |||||
char future1; | |||||
char future2; | |||||
}; | |||||
enum VstMidiEventFlags | |||||
{ | |||||
vstMidiEventIsRealtime = 1 | |||||
}; | |||||
struct VstSysExEvent | |||||
{ | |||||
int32 type; | |||||
int32 size; | |||||
int32 offsetSamples; | |||||
int32 flags; | |||||
int32 sysExDumpSize; | |||||
pointer_sized_int future1; | |||||
char* sysExDump; | |||||
pointer_sized_int future2; | |||||
}; | |||||
//============================================================================== | |||||
struct VstTimingInformation | |||||
{ | |||||
double samplePosition; | |||||
double sampleRate; | |||||
double systemTimeNanoseconds; | |||||
double musicalPosition; | |||||
double tempoBPM; | |||||
double lastBarPosition; | |||||
double loopStartPosition; | |||||
double loopEndPosition; | |||||
int32 timeSignatureNumerator; | |||||
int32 timeSignatureDenominator; | |||||
int32 smpteOffset; | |||||
int32 smpteRate; | |||||
int32 samplesToNearestClock; | |||||
int32 flags; | |||||
}; | |||||
enum VstTimingInformationFlags | |||||
{ | |||||
vstTimingInfoFlagTransportChanged = 1, | |||||
vstTimingInfoFlagCurrentlyPlaying = 2, | |||||
vstTimingInfoFlagLoopActive = 4, | |||||
vstTimingInfoFlagCurrentlyRecording = 8, | |||||
vstTimingInfoFlagAutomationWriteModeActive = 64, | |||||
vstTimingInfoFlagAutomationReadModeActive = 128, | |||||
vstTimingInfoFlagNanosecondsValid = 256, | |||||
vstTimingInfoFlagMusicalPositionValid = 512, | |||||
vstTimingInfoFlagTempoValid = 1024, | |||||
vstTimingInfoFlagLastBarPositionValid = 2056, | |||||
vstTimingInfoFlagLoopPositionValid = 4096, | |||||
vstTimingInfoFlagTimeSignatureValid = 8192, | |||||
vstTimingInfoFlagSmpteValid = 16384, | |||||
vstTimingInfoFlagNearestClockValid = 32768 | |||||
}; | |||||
//============================================================================== | |||||
enum VstSmpteRates | |||||
{ | |||||
vstSmpteRateFps24, | |||||
vstSmpteRateFps25, | |||||
vstSmpteRateFps2997, | |||||
vstSmpteRateFps30, | |||||
vstSmpteRateFps2997drop, | |||||
vstSmpteRateFps30drop, | |||||
vstSmpteRate16mmFilm, | |||||
vstSmpteRate35mmFilm, | |||||
vstSmpteRateFps239 = vstSmpteRate35mmFilm + 3, | |||||
vstSmpteRateFps249 , | |||||
vstSmpteRateFps599, | |||||
vstSmpteRateFps60 | |||||
}; | |||||
//============================================================================== | |||||
struct VstIndividualSpeakerInfo | |||||
{ | |||||
float azimuthalAngle; | |||||
float elevationAngle; | |||||
float radius; | |||||
float reserved; | |||||
char label[vstMaxNameLength]; | |||||
int32 type; | |||||
char unused[28]; | |||||
}; | |||||
enum VstIndividualSpeakerType | |||||
{ | |||||
vstIndividualSpeakerTypeUndefined = 0x7fffffff, | |||||
vstIndividualSpeakerTypeMono = 0, | |||||
vstIndividualSpeakerTypeLeft, | |||||
vstIndividualSpeakerTypeRight, | |||||
vstIndividualSpeakerTypeCentre, | |||||
vstIndividualSpeakerTypeLFE, | |||||
vstIndividualSpeakerTypeLeftSurround, | |||||
vstIndividualSpeakerTypeRightSurround, | |||||
vstIndividualSpeakerTypeLeftCentre, | |||||
vstIndividualSpeakerTypeRightCentre, | |||||
vstIndividualSpeakerTypeSurround, | |||||
vstIndividualSpeakerTypeCentreSurround = vstIndividualSpeakerTypeSurround, | |||||
vstIndividualSpeakerTypeLeftRearSurround, | |||||
vstIndividualSpeakerTypeRightRearSurround, | |||||
vstIndividualSpeakerTypeTopMiddle, | |||||
vstIndividualSpeakerTypeTopFrontLeft, | |||||
vstIndividualSpeakerTypeTopFrontCentre, | |||||
vstIndividualSpeakerTypeTopFrontRight, | |||||
vstIndividualSpeakerTypeTopRearLeft, | |||||
vstIndividualSpeakerTypeTopRearCentre, | |||||
vstIndividualSpeakerTypeTopRearRight, | |||||
vstIndividualSpeakerTypeLFE2 | |||||
}; | |||||
struct VstSpeakerConfiguration | |||||
{ | |||||
int32 type; | |||||
int32 numberOfChannels; | |||||
VstIndividualSpeakerInfo speakers[8]; | |||||
}; | |||||
enum VstSpeakerConfigurationType | |||||
{ | |||||
vstSpeakerConfigTypeUser = -2, | |||||
vstSpeakerConfigTypeEmpty = -1, | |||||
vstSpeakerConfigTypeMono = 0, | |||||
vstSpeakerConfigTypeLR, | |||||
vstSpeakerConfigTypeLsRs, | |||||
vstSpeakerConfigTypeLcRc, | |||||
vstSpeakerConfigTypeSlSr, | |||||
vstSpeakerConfigTypeCLfe, | |||||
vstSpeakerConfigTypeLRC, | |||||
vstSpeakerConfigTypeLRS, | |||||
vstSpeakerConfigTypeLRCLfe, | |||||
vstSpeakerConfigTypeLRLfeS, | |||||
vstSpeakerConfigTypeLRCS, | |||||
vstSpeakerConfigTypeLRLsRs, | |||||
vstSpeakerConfigTypeLRCLfeS, | |||||
vstSpeakerConfigTypeLRLfeLsRs, | |||||
vstSpeakerConfigTypeLRCLsRs, | |||||
vstSpeakerConfigTypeLRCLfeLsRs, | |||||
vstSpeakerConfigTypeLRCLsRsCs, | |||||
vstSpeakerConfigTypeLRLsRsSlSr, | |||||
vstSpeakerConfigTypeLRCLfeLsRsCs, | |||||
vstSpeakerConfigTypeLRLfeLsRsSlSr, | |||||
vstSpeakerConfigTypeLRCLsRsLcRc, | |||||
vstSpeakerConfigTypeLRCLsRsSlSr, | |||||
vstSpeakerConfigTypeLRCLfeLsRsLcRc, | |||||
vstSpeakerConfigTypeLRCLfeLsRsSlSr, | |||||
vstSpeakerConfigTypeLRCLsRsLcRcCs, | |||||
vstSpeakerConfigTypeLRCLsRsCsSlSr, | |||||
vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, | |||||
vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, | |||||
vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2 | |||||
}; | |||||
#if JUCE_MSVC | |||||
#pragma pack(pop) | |||||
#elif JUCE_MAC || JUCE_IOS | |||||
#pragma options align=reset | |||||
#else | |||||
#pragma pack(pop) | |||||
#endif | |||||
#endif // JUCE_VSTINTERFACE_H_INCLUDED |
@@ -22,7 +22,8 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#ifdef __aeffect__ // NB: this must come first, *before* the header-guard. | |||||
// NB: this must come first, *before* the header-guard. | |||||
#ifdef JUCE_VSTINTERFACE_H_INCLUDED | |||||
#ifndef JUCE_VSTMIDIEVENTLIST_H_INCLUDED | #ifndef JUCE_VSTMIDIEVENTLIST_H_INCLUDED | ||||
#define JUCE_VSTMIDIEVENTLIST_H_INCLUDED | #define JUCE_VSTMIDIEVENTLIST_H_INCLUDED | ||||
@@ -53,7 +54,7 @@ public: | |||||
numEventsUsed = 0; | numEventsUsed = 0; | ||||
if (events != nullptr) | if (events != nullptr) | ||||
events->numEvents = 0; | |||||
events->numberOfEvents = 0; | |||||
} | } | ||||
void addEvent (const void* const midiData, const int numBytes, const int frameOffset) | void addEvent (const void* const midiData, const int numBytes, const int frameOffset) | ||||
@@ -61,65 +62,65 @@ public: | |||||
ensureSize (numEventsUsed + 1); | ensureSize (numEventsUsed + 1); | ||||
VstMidiEvent* const e = (VstMidiEvent*) (events->events [numEventsUsed]); | VstMidiEvent* const e = (VstMidiEvent*) (events->events [numEventsUsed]); | ||||
events->numEvents = ++numEventsUsed; | |||||
events->numberOfEvents = ++numEventsUsed; | |||||
if (numBytes <= 4) | if (numBytes <= 4) | ||||
{ | { | ||||
if (e->type == kVstSysExType) | |||||
if (e->type == vstSysExEventType) | |||||
{ | { | ||||
delete[] (((VstMidiSysexEvent*) e)->sysexDump); | |||||
e->type = kVstMidiType; | |||||
e->byteSize = sizeof (VstMidiEvent); | |||||
e->noteLength = 0; | |||||
e->noteOffset = 0; | |||||
e->detune = 0; | |||||
e->noteOffVelocity = 0; | |||||
delete[] (((VstSysExEvent*) e)->sysExDump); | |||||
e->type = vstMidiEventType; | |||||
e->size = sizeof (VstMidiEvent); | |||||
e->noteSampleLength = 0; | |||||
e->noteSampleOffset = 0; | |||||
e->tuning = 0; | |||||
e->noteVelocityOff = 0; | |||||
} | } | ||||
e->deltaFrames = frameOffset; | |||||
e->sampleOffset = frameOffset; | |||||
memcpy (e->midiData, midiData, (size_t) numBytes); | memcpy (e->midiData, midiData, (size_t) numBytes); | ||||
} | } | ||||
else | else | ||||
{ | { | ||||
VstMidiSysexEvent* const se = (VstMidiSysexEvent*) e; | |||||
VstSysExEvent* const se = (VstSysExEvent*) e; | |||||
if (se->type == kVstSysExType) | |||||
delete[] se->sysexDump; | |||||
if (se->type == vstSysExEventType) | |||||
delete[] se->sysExDump; | |||||
se->sysexDump = new char [numBytes]; | |||||
memcpy (se->sysexDump, midiData, (size_t) numBytes); | |||||
se->sysExDump = new char [(size_t) numBytes]; | |||||
memcpy (se->sysExDump, midiData, (size_t) numBytes); | |||||
se->type = kVstSysExType; | |||||
se->byteSize = sizeof (VstMidiSysexEvent); | |||||
se->deltaFrames = frameOffset; | |||||
se->type = vstSysExEventType; | |||||
se->size = sizeof (VstSysExEvent); | |||||
se->offsetSamples = frameOffset; | |||||
se->flags = 0; | se->flags = 0; | ||||
se->dumpBytes = numBytes; | |||||
se->resvd1 = 0; | |||||
se->resvd2 = 0; | |||||
se->sysExDumpSize = numBytes; | |||||
se->future1 = 0; | |||||
se->future2 = 0; | |||||
} | } | ||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
// Handy method to pull the events out of an event buffer supplied by the host | // Handy method to pull the events out of an event buffer supplied by the host | ||||
// or plugin. | // or plugin. | ||||
static void addEventsToMidiBuffer (const VstEvents* events, MidiBuffer& dest) | |||||
static void addEventsToMidiBuffer (const VstEventBlock* events, MidiBuffer& dest) | |||||
{ | { | ||||
for (int i = 0; i < events->numEvents; ++i) | |||||
for (int i = 0; i < events->numberOfEvents; ++i) | |||||
{ | { | ||||
const VstEvent* const e = events->events[i]; | const VstEvent* const e = events->events[i]; | ||||
if (e != nullptr) | if (e != nullptr) | ||||
{ | { | ||||
if (e->type == kVstMidiType) | |||||
if (e->type == vstMidiEventType) | |||||
{ | { | ||||
dest.addEvent ((const juce::uint8*) ((const VstMidiEvent*) e)->midiData, | dest.addEvent ((const juce::uint8*) ((const VstMidiEvent*) e)->midiData, | ||||
4, e->deltaFrames); | |||||
4, e->sampleOffset); | |||||
} | } | ||||
else if (e->type == kVstSysExType) | |||||
else if (e->type == vstSysExEventType) | |||||
{ | { | ||||
dest.addEvent ((const juce::uint8*) ((const VstMidiSysexEvent*) e)->sysexDump, | |||||
(int) ((const VstMidiSysexEvent*) e)->dumpBytes, | |||||
e->deltaFrames); | |||||
dest.addEvent ((const juce::uint8*) ((const VstSysExEvent*) e)->sysExDump, | |||||
(int) ((const VstSysExEvent*) e)->sysExDumpSize, | |||||
e->sampleOffset); | |||||
} | } | ||||
} | } | ||||
} | } | ||||
@@ -160,24 +161,24 @@ public: | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
HeapBlock<VstEvents> events; | |||||
HeapBlock<VstEventBlock> events; | |||||
private: | private: | ||||
int numEventsUsed, numEventsAllocated; | int numEventsUsed, numEventsAllocated; | ||||
static VstEvent* allocateVSTEvent() | static VstEvent* allocateVSTEvent() | ||||
{ | { | ||||
VstEvent* const e = (VstEvent*) std::calloc (1, sizeof (VstMidiEvent) > sizeof (VstMidiSysexEvent) ? sizeof (VstMidiEvent) | |||||
: sizeof (VstMidiSysexEvent)); | |||||
e->type = kVstMidiType; | |||||
e->byteSize = sizeof (VstMidiEvent); | |||||
VstEvent* const e = (VstEvent*) std::calloc (1, sizeof (VstMidiEvent) > sizeof (VstSysExEvent) ? sizeof (VstMidiEvent) | |||||
: sizeof (VstSysExEvent)); | |||||
e->type = vstMidiEventType; | |||||
e->size = sizeof (VstMidiEvent); | |||||
return e; | return e; | ||||
} | } | ||||
static void freeVSTEvent (VstEvent* e) | static void freeVSTEvent (VstEvent* e) | ||||
{ | { | ||||
if (e->type == kVstSysExType) | |||||
delete[] (((VstMidiSysexEvent*) e)->sysexDump); | |||||
if (e->type == vstSysExEventType) | |||||
delete[] (((VstSysExEvent*) e)->sysExDump); | |||||
std::free (e); | std::free (e); | ||||
} | } | ||||
@@ -22,7 +22,7 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
#if JUCE_PLUGINHOST_VST || DOXYGEN | |||||
#if (JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS)) || DOXYGEN | |||||
//============================================================================== | //============================================================================== | ||||
/** | /** | ||||
@@ -36,7 +36,7 @@ public: | |||||
~VSTPluginFormat(); | ~VSTPluginFormat(); | ||||
//============================================================================== | //============================================================================== | ||||
/** Attempts to retreive the VSTXML data from a plugin. | |||||
/** Attempts to retrieve the VSTXML data from a plugin. | |||||
Will return nullptr if the plugin isn't a VST, or if it doesn't have any VSTXML. | Will return nullptr if the plugin isn't a VST, or if it doesn't have any VSTXML. | ||||
*/ | */ | ||||
static const XmlElement* getVSTXML (AudioPluginInstance* plugin); | static const XmlElement* getVSTXML (AudioPluginInstance* plugin); | ||||
@@ -53,6 +53,13 @@ public: | |||||
/** Attempts to set a VST's state from a chunk of memory. */ | /** Attempts to set a VST's state from a chunk of memory. */ | ||||
static bool setChunkData (AudioPluginInstance* plugin, const void* data, int size, bool isPreset); | static bool setChunkData (AudioPluginInstance* plugin, const void* data, int size, bool isPreset); | ||||
/** Given a suitable function pointer to a VSTPluginMain function, this will attempt to | |||||
instantiate and return a plugin for it. | |||||
*/ | |||||
static AudioPluginInstance* createCustomVSTFromMainCall (void* entryPointFunction, | |||||
double initialSampleRate, | |||||
int initialBufferSize); | |||||
//============================================================================== | //============================================================================== | ||||
/** Base class for some extra functions that can be attached to a VST plugin instance. */ | /** Base class for some extra functions that can be attached to a VST plugin instance. */ | ||||
class ExtraFunctions | class ExtraFunctions | ||||
@@ -75,23 +82,21 @@ public: | |||||
static void setExtraFunctions (AudioPluginInstance* plugin, ExtraFunctions* functions); | static void setExtraFunctions (AudioPluginInstance* plugin, ExtraFunctions* functions); | ||||
//============================================================================== | //============================================================================== | ||||
#if JUCE_64BIT | |||||
typedef int64 VstIntPtr; | |||||
#else | |||||
typedef int32 VstIntPtr; | |||||
#endif | |||||
/** This simply calls directly to the VST's AEffect::dispatcher() function. */ | /** This simply calls directly to the VST's AEffect::dispatcher() function. */ | ||||
static VstIntPtr JUCE_CALLTYPE dispatcher (AudioPluginInstance*, int32, int32, VstIntPtr, void*, float); | |||||
static pointer_sized_int JUCE_CALLTYPE dispatcher (AudioPluginInstance*, int32, int32, pointer_sized_int, void*, float); | |||||
/** Given a VstEffectInterface* (aka vst::AEffect*), this will return the juce AudioPluginInstance | |||||
that is being used to wrap it | |||||
*/ | |||||
static AudioPluginInstance* getPluginInstanceFromVstEffectInterface (void* aEffect); | |||||
//============================================================================== | //============================================================================== | ||||
String getName() const override { return "VST"; } | String getName() const override { return "VST"; } | ||||
void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | ||||
AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, double, int) override; | |||||
bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | ||||
String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | ||||
bool pluginNeedsRescanning (const PluginDescription&) override; | bool pluginNeedsRescanning (const PluginDescription&) override; | ||||
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; | |||||
StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||||
bool doesPluginStillExist (const PluginDescription&) override; | bool doesPluginStillExist (const PluginDescription&) override; | ||||
FileSearchPath getDefaultLocationsToSearch() override; | FileSearchPath getDefaultLocationsToSearch() override; | ||||
bool canScanForPlugins() const override { return true; } | bool canScanForPlugins() const override { return true; } | ||||
@@ -103,6 +108,14 @@ public: | |||||
*/ | */ | ||||
virtual void aboutToScanVSTShellPlugin (const PluginDescription&); | virtual void aboutToScanVSTShellPlugin (const PluginDescription&); | ||||
private: | |||||
//============================================================================== | |||||
void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||||
int initialBufferSize, void* userData, | |||||
void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||||
bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||||
private: | private: | ||||
void recursiveFileSearch (StringArray&, const File&, bool recursive); | void recursiveFileSearch (StringArray&, const File&, bool recursive); | ||||
@@ -31,10 +31,15 @@ | |||||
#error "Incorrect use of JUCE cpp file" | #error "Incorrect use of JUCE cpp file" | ||||
#endif | #endif | ||||
#include "AppConfig.h" | |||||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | ||||
#include "juce_audio_processors.h" | #include "juce_audio_processors.h" | ||||
#include "../juce_gui_extra/juce_gui_extra.h" | |||||
#if ! JUCE_AUDIO_PROCESSOR_NO_GUI | |||||
#include <juce_gui_extra/juce_gui_extra.h> | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
#if JUCE_MAC | #if JUCE_MAC | ||||
@@ -70,9 +75,18 @@ static inline bool arrayContainsPlugin (const OwnedArray<PluginDescription>& lis | |||||
return false; | return false; | ||||
} | } | ||||
#if JUCE_MAC | |||||
#if JUCE_MAC || JUCE_IOS | |||||
#if JUCE_IOS | |||||
#define JUCE_IOS_MAC_VIEW UIView | |||||
typedef UIViewComponent ViewComponentBaseClass; | |||||
#else | |||||
#define JUCE_IOS_MAC_VIEW NSView | |||||
typedef NSViewComponent ViewComponentBaseClass; | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
struct AutoResizingNSViewComponent : public NSViewComponent, | struct AutoResizingNSViewComponent : public NSViewComponent, | ||||
private AsyncUpdater { | private AsyncUpdater { | ||||
AutoResizingNSViewComponent(); | AutoResizingNSViewComponent(); | ||||
@@ -84,7 +98,7 @@ struct AutoResizingNSViewComponent : public NSViewComponent, | |||||
struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewComponent, | struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewComponent, | ||||
private Timer { | private Timer { | ||||
AutoResizingNSViewComponentWithParent(); | AutoResizingNSViewComponentWithParent(); | ||||
NSView* getChildView() const; | |||||
JUCE_IOS_MAC_VIEW* getChildView() const; | |||||
void timerCallback() override; | void timerCallback() override; | ||||
}; | }; | ||||
@@ -112,27 +126,29 @@ void AutoResizingNSViewComponent::handleAsyncUpdate() | |||||
resizeToFitView(); | resizeToFitView(); | ||||
} | } | ||||
//============================================================================== | |||||
AutoResizingNSViewComponentWithParent::AutoResizingNSViewComponentWithParent() | AutoResizingNSViewComponentWithParent::AutoResizingNSViewComponentWithParent() | ||||
{ | { | ||||
NSView* v = [[NSView alloc] init]; | |||||
JUCE_IOS_MAC_VIEW* v = [[JUCE_IOS_MAC_VIEW alloc] init]; | |||||
setView (v); | setView (v); | ||||
[v release]; | [v release]; | ||||
startTimer(500); | |||||
startTimer(30); | |||||
} | } | ||||
NSView* AutoResizingNSViewComponentWithParent::getChildView() const | |||||
JUCE_IOS_MAC_VIEW* AutoResizingNSViewComponentWithParent::getChildView() const | |||||
{ | { | ||||
if (NSView* parent = (NSView*)getView()) | |||||
if (JUCE_IOS_MAC_VIEW* parent = (JUCE_IOS_MAC_VIEW*)getView()) | |||||
if ([[parent subviews] count] > 0) | if ([[parent subviews] count] > 0) | ||||
return [[parent subviews] objectAtIndex: 0]; | return [[parent subviews] objectAtIndex: 0]; | ||||
return nil; | return nil; | ||||
} | } | ||||
void AutoResizingNSViewComponentWithParent::timerCallback() | void AutoResizingNSViewComponentWithParent::timerCallback() | ||||
{ | { | ||||
if (NSView* child = getChildView()) | |||||
if (JUCE_IOS_MAC_VIEW* child = getChildView()) | |||||
{ | { | ||||
stopTimer(); | stopTimer(); | ||||
setView(child); | setView(child); | ||||
@@ -148,12 +164,10 @@ void AutoResizingNSViewComponentWithParent::timerCallback() | |||||
#include "format/juce_AudioPluginFormat.cpp" | #include "format/juce_AudioPluginFormat.cpp" | ||||
#include "format/juce_AudioPluginFormatManager.cpp" | #include "format/juce_AudioPluginFormatManager.cpp" | ||||
#include "processors/juce_AudioProcessor.cpp" | #include "processors/juce_AudioProcessor.cpp" | ||||
#include "processors/juce_AudioChannelSet.cpp" | |||||
#include "processors/juce_AudioProcessorEditor.cpp" | #include "processors/juce_AudioProcessorEditor.cpp" | ||||
#include "processors/juce_AudioProcessorGraph.cpp" | #include "processors/juce_AudioProcessorGraph.cpp" | ||||
#include "processors/juce_GenericAudioProcessorEditor.cpp" | #include "processors/juce_GenericAudioProcessorEditor.cpp" | ||||
#include "processors/juce_PluginDescription.cpp" | #include "processors/juce_PluginDescription.cpp" | ||||
#include "processors/AudioProcessorGraphMultiThreaded.cpp" | |||||
#include "format_types/juce_LADSPAPluginFormat.cpp" | #include "format_types/juce_LADSPAPluginFormat.cpp" | ||||
#include "format_types/juce_VSTPluginFormat.cpp" | #include "format_types/juce_VSTPluginFormat.cpp" | ||||
#include "format_types/juce_VST3PluginFormat.cpp" | #include "format_types/juce_VST3PluginFormat.cpp" | ||||
@@ -22,17 +22,42 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
/******************************************************************************* | |||||
The block below describes the properties of this module, and is read by | |||||
the Projucer to automatically generate project code that uses it. | |||||
For details about the syntax and how to create or use a module, see the | |||||
JUCE Module Format.txt file. | |||||
BEGIN_JUCE_MODULE_DECLARATION | |||||
ID: juce_audio_processors | |||||
vendor: juce | |||||
version: 4.3.0 | |||||
name: JUCE audio processor classes | |||||
description: Classes for loading and playing VST, AU, or internally-generated audio processors. | |||||
website: http://www.juce.com/juce | |||||
license: GPL/Commercial | |||||
dependencies: juce_gui_extra, juce_audio_basics | |||||
OSXFrameworks: CoreAudio CoreMIDI AudioToolbox | |||||
iOSFrameworks: AudioToolbox | |||||
END_JUCE_MODULE_DECLARATION | |||||
*******************************************************************************/ | |||||
#ifndef JUCE_AUDIO_PROCESSORS_H_INCLUDED | #ifndef JUCE_AUDIO_PROCESSORS_H_INCLUDED | ||||
#define JUCE_AUDIO_PROCESSORS_H_INCLUDED | #define JUCE_AUDIO_PROCESSORS_H_INCLUDED | ||||
#include "../juce_gui_basics/juce_gui_basics.h" | |||||
#include "../juce_audio_basics/juce_audio_basics.h" | |||||
#include <juce_gui_basics/juce_gui_basics.h> | |||||
#include <juce_audio_basics/juce_audio_basics.h> | |||||
//============================================================================== | //============================================================================== | ||||
/** Config: JUCE_PLUGINHOST_VST | /** Config: JUCE_PLUGINHOST_VST | ||||
Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be | |||||
installed on your machine. | |||||
Enables the VST audio plugin hosting classes. | |||||
@see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU, JUCE_PLUGINHOST_VST3 | @see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU, JUCE_PLUGINHOST_VST3 | ||||
*/ | */ | ||||
@@ -63,10 +88,14 @@ | |||||
// #error "You need to set either the JUCE_PLUGINHOST_AU and/or JUCE_PLUGINHOST_VST and/or JUCE_PLUGINHOST_VST3 flags if you're using this module!" | // #error "You need to set either the JUCE_PLUGINHOST_AU and/or JUCE_PLUGINHOST_VST and/or JUCE_PLUGINHOST_VST3 flags if you're using this module!" | ||||
#endif | #endif | ||||
#if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT) | |||||
#if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT || JUCE_IOS) | |||||
#define JUCE_SUPPORT_CARBON 1 | #define JUCE_SUPPORT_CARBON 1 | ||||
#endif | #endif | ||||
#ifndef JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR | |||||
#define JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR 1 | |||||
#endif | |||||
//============================================================================== | //============================================================================== | ||||
//============================================================================== | //============================================================================== | ||||
namespace juce | namespace juce | ||||
@@ -77,13 +106,11 @@ class AudioProcessor; | |||||
#include "processors/juce_AudioProcessorEditor.h" | #include "processors/juce_AudioProcessorEditor.h" | ||||
#include "processors/juce_AudioProcessorListener.h" | #include "processors/juce_AudioProcessorListener.h" | ||||
#include "processors/juce_AudioProcessorParameter.h" | #include "processors/juce_AudioProcessorParameter.h" | ||||
#include "processors/juce_AudioChannelSet.h" | |||||
#include "processors/juce_AudioProcessor.h" | #include "processors/juce_AudioProcessor.h" | ||||
#include "processors/juce_PluginDescription.h" | #include "processors/juce_PluginDescription.h" | ||||
#include "processors/juce_AudioPluginInstance.h" | #include "processors/juce_AudioPluginInstance.h" | ||||
#include "processors/juce_AudioProcessorGraph.h" | #include "processors/juce_AudioProcessorGraph.h" | ||||
#include "processors/juce_GenericAudioProcessorEditor.h" | #include "processors/juce_GenericAudioProcessorEditor.h" | ||||
#include "processors/AudioProcessorGraphMultiThreaded.h" | |||||
#include "format/juce_AudioPluginFormat.h" | #include "format/juce_AudioPluginFormat.h" | ||||
#include "format/juce_AudioPluginFormatManager.h" | #include "format/juce_AudioPluginFormatManager.h" | ||||
#include "scanning/juce_KnownPluginList.h" | #include "scanning/juce_KnownPluginList.h" | ||||
@@ -78,6 +78,9 @@ public: | |||||
protected: | protected: | ||||
//============================================================================== | //============================================================================== | ||||
AudioPluginInstance() {} | AudioPluginInstance() {} | ||||
AudioPluginInstance (const BusesProperties& ioLayouts) : AudioProcessor (ioLayouts) {} | |||||
template <int numLayouts> | |||||
AudioPluginInstance (const short channelLayoutList[numLayouts][2]) : AudioProcessor (channelLayoutList) {} | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) | ||||
}; | }; | ||||
@@ -25,6 +25,7 @@ | |||||
#ifndef JUCE_AUDIOPROCESSOR_H_INCLUDED | #ifndef JUCE_AUDIOPROCESSOR_H_INCLUDED | ||||
#define JUCE_AUDIOPROCESSOR_H_INCLUDED | #define JUCE_AUDIOPROCESSOR_H_INCLUDED | ||||
struct PluginBusUtilities; | |||||
//============================================================================== | //============================================================================== | ||||
/** | /** | ||||
@@ -43,10 +44,41 @@ | |||||
class JUCE_API AudioProcessor | class JUCE_API AudioProcessor | ||||
{ | { | ||||
protected: | protected: | ||||
struct BusesProperties; | |||||
//============================================================================== | //============================================================================== | ||||
/** Constructor. */ | |||||
/** Constructor. | |||||
This constructor will create a main input and output bus which are diabled | |||||
by default. If you need more fine grain control then use the other | |||||
constructors. | |||||
*/ | |||||
AudioProcessor(); | AudioProcessor(); | ||||
/** Constructor for multibus AudioProcessors | |||||
If your AudioProcessor supports multiple buses than use this constructor | |||||
to initialise the bus layouts and bus names of your plug-in. | |||||
*/ | |||||
AudioProcessor (const BusesProperties& ioLayouts); | |||||
/** Constructor for AudioProcessors which use layout maps | |||||
If your AudioProcessor uses layout maps then use this constructor. | |||||
*/ | |||||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||||
AudioProcessor (const std::initializer_list<const short[2]>& channelLayoutList) | |||||
{ | |||||
initialise (busesPropertiesFromLayoutArray (layoutListToArray (channelLayoutList))); | |||||
} | |||||
#else | |||||
template <int numLayouts> | |||||
AudioProcessor (const short channelLayoutList[numLayouts][2]) | |||||
{ | |||||
initialise (busesPropertiesFromLayoutArray (layoutListToArray (channelLayoutList))); | |||||
} | |||||
#endif | |||||
public: | public: | ||||
//============================================================================== | //============================================================================== | ||||
enum ProcessingPrecision | enum ProcessingPrecision | ||||
@@ -70,18 +102,20 @@ public: | |||||
playback stops. | playback stops. | ||||
You can call getTotalNumInputChannels and getTotalNumOutputChannels | You can call getTotalNumInputChannels and getTotalNumOutputChannels | ||||
or query the busArrangement member variable to find out the number of | |||||
or query the busLayout member variable to find out the number of | |||||
channels your processBlock callback must process. | channels your processBlock callback must process. | ||||
The estimatedSamplesPerBlock value is a HINT about the typical number of | |||||
samples that will be processed for each callback, but isn't any kind | |||||
of guarantee. The actual block sizes that the host uses may be different | |||||
each time the callback happens, and may be more or less than this value. | |||||
The maximumExpectedSamplesPerBlock value is a strong hint about the maximum | |||||
number of samples that will be provided in each block. You may want to use | |||||
this value to resize internal buffers. You should program defensively in | |||||
case a buggy host exceeds this value. The actual block sizes that the host | |||||
uses may be different each time the callback happens: completely variable | |||||
block sizes can be expected from some hosts. | |||||
@see busArrangement, getTotalNumInputChannels, getTotalNumOutputChannels | |||||
@see busLayout, getTotalNumInputChannels, getTotalNumOutputChannels | |||||
*/ | */ | ||||
virtual void prepareToPlay (double sampleRate, | virtual void prepareToPlay (double sampleRate, | ||||
int estimatedSamplesPerBlock) = 0; | |||||
int maximumExpectedSamplesPerBlock) = 0; | |||||
/** Called after playback has stopped, to let the filter free up any resources it | /** Called after playback has stopped, to let the filter free up any resources it | ||||
no longer needs. | no longer needs. | ||||
@@ -107,7 +141,7 @@ public: | |||||
If your plug-in has more than one input or output buses then the buffer passed | If your plug-in has more than one input or output buses then the buffer passed | ||||
to the processBlock methods will contain a bundle of all channels of each bus. | to the processBlock methods will contain a bundle of all channels of each bus. | ||||
Use AudioBusArrangement::getBusBuffer to obtain an audio buffer for a | |||||
Use AudiobusLayout::getBusBuffer to obtain an audio buffer for a | |||||
particular bus. | particular bus. | ||||
Note that if you have more outputs than inputs, then only those channels that | Note that if you have more outputs than inputs, then only those channels that | ||||
@@ -144,7 +178,7 @@ public: | |||||
processBlock() method to send out an asynchronous message. You could also use | processBlock() method to send out an asynchronous message. You could also use | ||||
the AsyncUpdater class in a similar way. | the AsyncUpdater class in a similar way. | ||||
@see AudioBusArrangement::getBusBuffer | |||||
@see AudiobusLayout::getBusBuffer | |||||
*/ | */ | ||||
virtual void processBlock (AudioBuffer<float>& buffer, | virtual void processBlock (AudioBuffer<float>& buffer, | ||||
@@ -169,7 +203,7 @@ public: | |||||
If your plug-in has more than one input or output buses then the buffer passed | If your plug-in has more than one input or output buses then the buffer passed | ||||
to the processBlock methods will contain a bundle of all channels of | to the processBlock methods will contain a bundle of all channels of | ||||
each bus. Use AudioBusArrangement::getBusBuffer to obtain a audio buffer | |||||
each bus. Use AudiobusLayout::getBusBuffer to obtain a audio buffer | |||||
for a particular bus. | for a particular bus. | ||||
Note that if you have more outputs than inputs, then only those channels that | Note that if you have more outputs than inputs, then only those channels that | ||||
@@ -182,8 +216,8 @@ public: | |||||
but you should only read/write from the ones that your filter is supposed to | but you should only read/write from the ones that your filter is supposed to | ||||
be using. | be using. | ||||
If your plugin uses buses, then you should use AudioBusArrangement::getBusBuffer() | |||||
or AudioBusArrangement::getChannelIndexInProcessBlockBuffer() to find out which | |||||
If your plugin uses buses, then you should use AudiobusLayout::getBusBuffer() | |||||
or AudiobusLayout::getChannelIndexInProcessBlockBuffer() to find out which | |||||
of the input and output channels correspond to which of the buses. | of the input and output channels correspond to which of the buses. | ||||
The number of samples in these buffers is NOT guaranteed to be the same for every | The number of samples in these buffers is NOT guaranteed to be the same for every | ||||
@@ -210,12 +244,13 @@ public: | |||||
processBlock() method to send out an asynchronous message. You could also use | processBlock() method to send out an asynchronous message. You could also use | ||||
the AsyncUpdater class in a similar way. | the AsyncUpdater class in a similar way. | ||||
@see AudioBusArrangement::getBusBuffer | |||||
@see AudiobusLayout::getBusBuffer | |||||
*/ | */ | ||||
virtual void processBlock (AudioBuffer<double>& buffer, | virtual void processBlock (AudioBuffer<double>& buffer, | ||||
MidiBuffer& midiMessages); | MidiBuffer& midiMessages); | ||||
/** Renders the next block when the processor is being bypassed. | /** Renders the next block when the processor is being bypassed. | ||||
The default implementation of this method will pass-through any incoming audio, but | The default implementation of this method will pass-through any incoming audio, but | ||||
you may override this method e.g. to add latency compensation to the data to match | you may override this method e.g. to add latency compensation to the data to match | ||||
the processor's latency characteristics. This will avoid situations where bypassing | the processor's latency characteristics. This will avoid situations where bypassing | ||||
@@ -227,6 +262,7 @@ public: | |||||
MidiBuffer& midiMessages); | MidiBuffer& midiMessages); | ||||
/** Renders the next block when the processor is being bypassed. | /** Renders the next block when the processor is being bypassed. | ||||
The default implementation of this method will pass-through any incoming audio, but | The default implementation of this method will pass-through any incoming audio, but | ||||
you may override this method e.g. to add latency compensation to the data to match | you may override this method e.g. to add latency compensation to the data to match | ||||
the processor's latency characteristics. This will avoid situations where bypassing | the processor's latency characteristics. This will avoid situations where bypassing | ||||
@@ -237,103 +273,372 @@ public: | |||||
virtual void processBlockBypassed (AudioBuffer<double>& buffer, | virtual void processBlockBypassed (AudioBuffer<double>& buffer, | ||||
MidiBuffer& midiMessages); | MidiBuffer& midiMessages); | ||||
//============================================================================== | //============================================================================== | ||||
/** Describes the layout and properties of an audio bus. | |||||
Effectively a bus description is a named set of channel types. | |||||
@see AudioChannelSet | |||||
/** | |||||
Represents the bus layout state of a plug-in | |||||
*/ | */ | ||||
struct AudioProcessorBus | |||||
struct BusesLayout | |||||
{ | { | ||||
/** Creates a bus from a name and set of channel types. */ | |||||
AudioProcessorBus (const String& busName, const AudioChannelSet& channelTypes); | |||||
/** An array containing the list of input buses that this processor supports. */ | |||||
Array<AudioChannelSet> inputBuses; | |||||
/** The bus's name. */ | |||||
String name; | |||||
/** An array containing the list of output buses that this processor supports. */ | |||||
Array<AudioChannelSet> outputBuses; | |||||
/** Get the number of channels of a particular bus */ | |||||
int getNumChannels (bool isInput, int busIndex) const noexcept | |||||
{ | |||||
const Array<AudioChannelSet>& bus = (isInput ? inputBuses : outputBuses); | |||||
return isPositiveAndBelow(busIndex, bus.size()) ? bus.getReference (busIndex).size() : 0; | |||||
} | |||||
/** Get the channel set of a particular bus */ | |||||
AudioChannelSet& getChannelSet (bool isInput, int busIndex) | |||||
{ | |||||
Array<AudioChannelSet>& sets = isInput ? inputBuses : outputBuses; | |||||
jassert (isPositiveAndBelow (busIndex, sets.size())); | |||||
return sets.getReference (busIndex); | |||||
} | |||||
/** The set of channel types that the bus contains. */ | |||||
AudioChannelSet channels; | |||||
/** Get the channel set of a particular bus */ | |||||
AudioChannelSet getChannelSet (bool isInput, int busIndex) const noexcept | |||||
{ | |||||
const Array<AudioChannelSet>& sets = isInput ? inputBuses : outputBuses; | |||||
if (isPositiveAndBelow (busIndex, sets.size())) | |||||
return sets.getReference (busIndex); | |||||
else | |||||
return AudioChannelSet(); | |||||
} | |||||
/** Get the input channel layout on the main bus. */ | |||||
AudioChannelSet getMainInputChannelSet() const noexcept { return getChannelSet (true, 0); } | |||||
/** Get the output channel layout on the main bus. */ | |||||
AudioChannelSet getMainOutputChannelSet() const noexcept { return getChannelSet (false, 0); } | |||||
/** Get the number of input channels on the main bus. */ | |||||
int getMainInputChannels() const noexcept { return getNumChannels (true, 0); } | |||||
/** Get the number of output channels on the main bus. */ | |||||
int getMainOutputChannels() const noexcept { return getNumChannels (false, 0); } | |||||
bool operator== (const BusesLayout& other) const noexcept { return inputBuses == other.inputBuses && outputBuses == other.outputBuses; } | |||||
bool operator!= (const BusesLayout& other) const noexcept { return inputBuses != other.inputBuses || outputBuses != other.outputBuses; } | |||||
}; | }; | ||||
//============================================================================== | //============================================================================== | ||||
/** | /** | ||||
Represents a set of input and output buses for an AudioProcessor. | |||||
*/ | |||||
struct AudioBusArrangement | |||||
Describes the layout and properties of an audio bus. | |||||
Effectively a bus description is a named set of channel types. | |||||
@see AudioChannelSet, AudioProcessor::addBus | |||||
*/ | |||||
class Bus | |||||
{ | { | ||||
/** An array containing the list of input buses that this processor supports. */ | |||||
Array<AudioProcessorBus> inputBuses; | |||||
public: | |||||
/** Returns true if this bus is an input bus. */ | |||||
bool isInput() const; | |||||
/** An array containing the list of output buses that this processor supports. */ | |||||
Array<AudioProcessorBus> outputBuses; | |||||
/** Returns the index of this bus. */ | |||||
int getBusIndex() const; | |||||
/** Returns true if the current bus is the main input or output bus. */ | |||||
bool isMain() const { return getBusIndex() == 0; } | |||||
//============================================================================== | |||||
/** The bus's name. */ | |||||
const String &getName() const noexcept { return name; } | |||||
/** Get the default layout of this bus. | |||||
@see AudioChannelSet | |||||
*/ | |||||
const AudioChannelSet& getDefaultLayout() const noexcept { return dfltLayout; } | |||||
//============================================================================== | |||||
/** The bus's current layout. This will be AudioChannelSet::disabled() if the current | |||||
layout is dfisabled. | |||||
@see AudioChannelSet | |||||
*/ | |||||
const AudioChannelSet& getCurrentLayout() const noexcept { return layout; } | |||||
/** Return the bus's last active channel layout. | |||||
If the bus is currently enabled then the result will be identical to getCurrentLayout | |||||
otherwise it will return the last enabled layout. | |||||
@see AudioChannelSet | |||||
*/ | |||||
const AudioChannelSet& getLastEnabledLayout() const noexcept { return lastLayout; } | |||||
/** Sets the bus's current layout. | |||||
If the AudioProcessor does not support this layout then this will return false. | |||||
@see AudioChannelSet | |||||
*/ | |||||
bool setCurrentLayout (const AudioChannelSet& layout); | |||||
/** Sets the bus's current layout without changing the enabled state. | |||||
If the AudioProcessor does not support this layout then this will return false. | |||||
@see AudioChannelSet | |||||
*/ | |||||
bool setCurrentLayoutWithoutEnabling (const AudioChannelSet& layout); | |||||
/** Return the number of channels of the current bus. */ | |||||
inline int getNumberOfChannels() const noexcept { return cachedChannelCount; } | |||||
/** Set the number of channles of this bus. This will return false if the AudioProcessor | |||||
does not support this layout. */ | |||||
bool setNumberOfChannels (int channels); | |||||
//============================================================================== | |||||
/** Checks if a particular layout is supported. | |||||
@param set The AudioChannelSet which is to be probed. | |||||
@see AudioChannelSet | |||||
*/ | |||||
bool isLayoutSupported (const AudioChannelSet& set) const; | |||||
/** Checks if this bus can support a given number of channels. */ | |||||
bool isNumberOfChannelsSupported (int channels) const; | |||||
/** Returns a ChannelSet that the bus supports with a given number of channels. */ | |||||
AudioChannelSet supportedLayoutWithChannels (int channels) const; | |||||
/** Returns the maximum number of channels that this bus can support. | |||||
@param limit The maximum value to return. | |||||
*/ | |||||
int getMaxSupportedChannels (int limit = AudioChannelSet::maxChannelsOfNamedLayout) const; | |||||
/** Returns the resulting layouts of all buses after changing the layout of this bus. | |||||
Changing an individual layout of a bus may also change the layout of all the other | |||||
buses. This method returns what the layouts of all the buses of the audio processor | |||||
would be, if you were to change the layout of this bus to the given layout. If there | |||||
is no way to support the given layout then this method will return the next best | |||||
layout. | |||||
*/ | |||||
BusesLayout getBusesLayoutForLayoutChangeOfBus (const AudioChannelSet& set) const; | |||||
//============================================================================== | |||||
/** Returns true if the current bus is enabled. */ | |||||
bool isEnabled() const noexcept { return ! layout.isDisabled(); } | |||||
/** Enable or disable this bus. This will return false if the AudioProcessor | |||||
does not support disabling this bus. */ | |||||
bool enable (bool shouldEnable = true); | |||||
/** Returns if this bus is enabled by default. */ | |||||
bool isEnabledByDefault() const noexcept { return enabledByDefault; } | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns the position of a bus's channels within the processBlock buffer. | /** Returns the position of a bus's channels within the processBlock buffer. | ||||
This can be called in processBlock to figure out which channel of the master AudioSampleBuffer | This can be called in processBlock to figure out which channel of the master AudioSampleBuffer | ||||
maps onto a specific bus's channel. | maps onto a specific bus's channel. | ||||
*/ | |||||
int getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept; | |||||
*/ | |||||
int getChannelIndexInProcessBlockBuffer (int channelIndex) const noexcept; | |||||
/** Returns an AudioBuffer containing a set of channel pointers for a specific bus. | /** Returns an AudioBuffer containing a set of channel pointers for a specific bus. | ||||
This can be called in processBlock to get a buffer containing a sub-group of the master | This can be called in processBlock to get a buffer containing a sub-group of the master | ||||
AudioSampleBuffer which contains all the plugin channels. | AudioSampleBuffer which contains all the plugin channels. | ||||
*/ | |||||
*/ | |||||
template <typename FloatType> | template <typename FloatType> | ||||
AudioBuffer<FloatType> getBusBuffer (AudioBuffer<FloatType>& processBlockBuffer, bool isInput, int busIndex) const | |||||
AudioBuffer<FloatType> getBusBuffer (AudioBuffer<FloatType>& processBlockBuffer) const | |||||
{ | { | ||||
const int busNumChannels = (isInput ? inputBuses : outputBuses).getReference (busIndex).channels.size(); | |||||
const int channelOffset = getChannelIndexInProcessBlockBuffer (isInput, busIndex, 0); | |||||
return AudioBuffer<FloatType> (processBlockBuffer.getArrayOfWritePointers() + channelOffset, | |||||
busNumChannels, processBlockBuffer.getNumSamples()); | |||||
bool isIn; | |||||
int busIdx; | |||||
busDirAndIndex (isIn, busIdx); | |||||
return owner.getBusBuffer (processBlockBuffer, isIn, busIdx); | |||||
} | } | ||||
private: | |||||
friend class AudioProcessor; | |||||
Bus (AudioProcessor&, const String&, const AudioChannelSet&, bool); | |||||
void busDirAndIndex (bool&, int&) const noexcept; | |||||
void updateChannelCount() noexcept; | |||||
/** Returns the total number of channels in all the input buses. */ | |||||
int getTotalNumInputChannels() const noexcept; | |||||
AudioProcessor& owner; | |||||
String name; | |||||
AudioChannelSet layout, dfltLayout, lastLayout; | |||||
bool enabledByDefault; | |||||
int cachedChannelCount; | |||||
/** Returns the total number of channels in all the output buses. */ | |||||
int getTotalNumOutputChannels() const noexcept; | |||||
JUCE_DECLARE_NON_COPYABLE (Bus); | |||||
}; | }; | ||||
/** The processor's bus arrangement. | |||||
//============================================================================== | |||||
/** Returns the number of buses on the input or output side */ | |||||
int getBusCount (bool isInput) const noexcept { return (isInput ? inputBuses : outputBuses).size(); } | |||||
/** Returns the audio bus with a given index and direction. | |||||
If busIdx is invalid then this method will return a nullptr. | |||||
*/ | |||||
Bus* getBus (bool isInput, int busIdx) noexcept { return (isInput ? inputBuses : outputBuses)[busIdx]; } | |||||
/** Returns the audio bus with a given index and direction. | |||||
If busIdx is invalid then this method will return a nullptr. | |||||
*/ | |||||
const Bus* getBus (bool isInput, int busIdx) const noexcept { return const_cast<AudioProcessor*> (this)->getBus (isInput, busIdx); } | |||||
//============================================================================== | |||||
/** Callback to query if a bus can currently be added. | |||||
This callback probes if a bus can currently be added. You should override | |||||
this callback if you want to support dynamically adding/removing buses by | |||||
the host. This is useful for mixer audio processors. | |||||
The default implementation will always return false. | |||||
@see addBus | |||||
*/ | |||||
virtual bool canAddBus (bool /*inputBus*/) const { return false; } | |||||
/** Callback to query if the last bus can currently be removed. | |||||
This callback probes if the last bus can currently be removed. You should | |||||
override this callback if you want to support dynamically adding/removing | |||||
buses by the host. This is useful for mixer audio processors. | |||||
If you return true in this callback then the AudioProcessor will go ahead | |||||
and delete the bus. | |||||
The default implementation will always return false. | |||||
*/ | |||||
virtual bool canRemoveBus (bool /*inputBus*/) const { return false; } | |||||
/** Dynamically request an additional bus. | |||||
Request an additional bus from the audio processor. If the audio processor | |||||
does not support adding additional buses then this method will return false. | |||||
Most audio processors will not allow you to dynamically add/remove | |||||
audio buses and will return false. | |||||
This method will invoke the canApplyBusCountChange callback to probe | |||||
if a bus can be added and, if yes, will use the supplied bus properties | |||||
of the canApplyBusCountChange callback to create a new bus. | |||||
@see canApplyBusCountChange, removeBus | |||||
*/ | |||||
bool addBus (bool isInput); | |||||
/** Dynamically remove the latest added bus. | |||||
Request the removal of the last bus from the audio processor. If the | |||||
audio processor does not support removing buses then this method will | |||||
return false. | |||||
Most audio processors will not allow you to dynamically add/remove | |||||
audio buses and will return false. | |||||
The default implementation will return false. | |||||
Your plugin can modify this either | |||||
- in the plugin's constructor | |||||
- in the setPreferredBusArrangement() callback | |||||
Changing it at other times can result in undefined behaviour. | |||||
This method will invoke the canApplyBusCountChange callback to probe if | |||||
a bus can currently be removed and, if yes, will go ahead and remove it. | |||||
The host will negotiate with the plugin over its bus configuration by making calls | |||||
to setPreferredBusArrangement(). | |||||
@see addBus, canRemoveBus | |||||
*/ | |||||
bool removeBus (bool isInput); | |||||
//============================================================================== | |||||
/** Set the channel layouts of this audio processor. | |||||
@see setPreferredBusArrangement | |||||
If the layout is not supported by this audio processor then | |||||
this method will return false. You can use the checkBusesLayoutSupported | |||||
and getNextBestLayout methods to probe which layouts this audio | |||||
processor supports. | |||||
*/ | |||||
bool setBusesLayout (const BusesLayout& arr); | |||||
/** Set the channel layouts of this audio processor without changing the | |||||
enablement state of the buses. | |||||
If the layout is not supported by this audio processor then | |||||
this method will return false. You can use the checkBusesLayoutSupported | |||||
and getNextBestLayout methods to probe which layouts this audio | |||||
processor supports. | |||||
*/ | */ | ||||
AudioBusArrangement busArrangement; | |||||
bool setBusesLayoutWithoutEnabling (const BusesLayout& arr); | |||||
/** Provides the current channel layouts of this audio processor. */ | |||||
BusesLayout getBusesLayout() const; | |||||
/** Provides the channel layout of the bus with a given index and direction. | |||||
If the index, direction combination is invalid then this will return an | |||||
AudioChannelSet with no channels. | |||||
*/ | |||||
AudioChannelSet getChannelLayoutOfBus (bool isInput, int busIdx) const noexcept; | |||||
/** Set the channel layout of the bus with a given index and direction. | |||||
If the index, direction combination is invalid or the layout is not | |||||
supported by the audio processor then this method will return false. | |||||
*/ | |||||
bool setChannelLayoutOfBus (bool isInput, int busIdx, const AudioChannelSet& layout); | |||||
/** Provides the number of channels of the bus with a given index and direction. | |||||
If the index, direction combination is invalid then this will return zero. | |||||
*/ | |||||
inline int getChannelCountOfBus (bool isInput, int busIdx) const noexcept | |||||
{ | |||||
if (const Bus* bus = getBus (isInput, busIdx)) | |||||
return bus->getNumberOfChannels(); | |||||
return 0; | |||||
} | |||||
/** Enables all buses */ | |||||
bool enableAllBuses(); | |||||
/** Disables all non-main buses (aux and sidechains). */ | |||||
bool disableNonMainBuses(); | |||||
//============================================================================== | //============================================================================== | ||||
/** Called by the host, this attempts to change the plugin's channel layout on a particular bus. | |||||
The base class implementation will perform some basic sanity-checking and then apply the | |||||
changes to the processor's busArrangement value. | |||||
You may override it and return false if you want to make your plugin smarter about refusing | |||||
certain layouts that you don't want to support. Your plug-in may also respond to this call by | |||||
changing the channel layout of other buses, for example, if your plug-in requires the same | |||||
number of input and output channels. | |||||
/** Returns the position of a bus's channels within the processBlock buffer. | |||||
This can be called in processBlock to figure out which channel of the master AudioSampleBuffer | |||||
maps onto a specific bus's channel. | |||||
*/ | |||||
int getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept; | |||||
For most basic plug-ins, which do not require side-chains, aux buses or detailed audio | |||||
channel layout information, it is easier to specify the acceptable channel configurations | |||||
via the "PlugIn Channel Configurations" field in the Introjucer. In this case, you should | |||||
not override this method. | |||||
/** Returns the offset in a bus's buffer from an absolute channel indes. | |||||
If, on the other hand, you decide to override this method then you need to make sure that | |||||
"PlugIn Channel Configurations" field in the Introjucer is empty. | |||||
This method returns the offset in a bus's buffer given an absolute channel index. | |||||
It also provides the bus index. For example, this method would return one | |||||
for a processor with two stereo buses when given the absolute channel index. | |||||
*/ | |||||
int getOffsetInBusBufferForAbsoluteChannelIndex (bool isInput, int absoluteChannelIndex, /*out*/ int& busIdx) const noexcept; | |||||
Note, that you must not do any heavy allocations or calculations in this callback as it may | |||||
be called several hundred times during initialization. If you require any layout specific | |||||
allocations then defer these to prepareToPlay callback. | |||||
/** Returns an AudioBuffer containing a set of channel pointers for a specific bus. | |||||
This can be called in processBlock to get a buffer containing a sub-group of the master | |||||
AudioSampleBuffer which contains all the plugin channels. | |||||
*/ | |||||
template <typename FloatType> | |||||
AudioBuffer<FloatType> getBusBuffer (AudioBuffer<FloatType>& processBlockBuffer, bool isInput, int busIndex) const | |||||
{ | |||||
const int busNumChannels = getChannelCountOfBus (isInput, busIndex); | |||||
const int channelOffset = getChannelIndexInProcessBlockBuffer (isInput, busIndex, 0); | |||||
@returns false if there is no way for the processor to support the given format on the specified bus. | |||||
return AudioBuffer<FloatType> (processBlockBuffer.getArrayOfWritePointers() + channelOffset, | |||||
busNumChannels, processBlockBuffer.getNumSamples()); | |||||
} | |||||
@see prepareToPlay, busArrangement, AudioBusArrangement::getBusBuffer, getTotalNumInputChannels, getTotalNumOutputChannels | |||||
//============================================================================== | |||||
/** Returns true if the Audio processor is likely to support a given layout. | |||||
This can be called regardless if the processor is currently running. | |||||
*/ | */ | ||||
virtual bool setPreferredBusArrangement (bool isInputBus, int busIndex, const AudioChannelSet& preferredSet); | |||||
bool checkBusesLayoutSupported (const BusesLayout& layouts) const; | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns true if the Audio processor supports double precision floating point processing. | /** Returns true if the Audio processor supports double precision floating point processing. | ||||
@@ -403,7 +708,7 @@ public: | |||||
getMainBusNumInputChannels if your processor does not have any sidechains | getMainBusNumInputChannels if your processor does not have any sidechains | ||||
or aux buses. | or aux buses. | ||||
*/ | */ | ||||
int getTotalNumInputChannels() const noexcept { return busArrangement.getTotalNumInputChannels(); } | |||||
int getTotalNumInputChannels() const noexcept { return cachedTotalIns; } | |||||
/** Returns the total number of output channels. | /** Returns the total number of output channels. | ||||
@@ -417,13 +722,56 @@ public: | |||||
getMainBusNumOutputChannels if your processor does not have any sidechains | getMainBusNumOutputChannels if your processor does not have any sidechains | ||||
or aux buses. | or aux buses. | ||||
*/ | */ | ||||
int getTotalNumOutputChannels() const noexcept { return busArrangement.getTotalNumOutputChannels(); } | |||||
int getTotalNumOutputChannels() const noexcept { return cachedTotalOuts; } | |||||
/** Returns the number of input channels on the main bus. */ | /** Returns the number of input channels on the main bus. */ | ||||
int getMainBusNumInputChannels() const noexcept; | |||||
inline int getMainBusNumInputChannels() const noexcept { return getChannelCountOfBus (true, 0); } | |||||
/** Returns the number of output channels on the main bus. */ | /** Returns the number of output channels on the main bus. */ | ||||
int getMainBusNumOutputChannels() const noexcept; | |||||
inline int getMainBusNumOutputChannels() const noexcept { return getChannelCountOfBus (false, 0); } | |||||
//============================================================================== | |||||
/** Returns true if the channel layout map contains a certain layout. | |||||
You can use this method to help you implement the checkBusesLayoutSupported | |||||
method. For example | |||||
@code | |||||
bool checkBusesLayoutSupported (const BusesLayout& layouts) override | |||||
{ | |||||
return containsLayout (layouts, {{1,1},{2,2}}); | |||||
} | |||||
@endcode | |||||
*/ | |||||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||||
static bool containsLayout (const BusesLayout& layouts, const std::initializer_list<const short[2]>& channelLayoutList) | |||||
{ | |||||
return containsLayout (layouts, layoutListToArray (channelLayoutList)); | |||||
} | |||||
#endif | |||||
template <int numLayouts> | |||||
static bool containsLayout (const BusesLayout& layouts, const short (&channelLayoutList) [numLayouts][2]) | |||||
{ | |||||
return containsLayout (layouts, layoutListToArray (channelLayoutList)); | |||||
} | |||||
/** Returns the next best layout which is contained in a channel layout map. | |||||
You can use this mehtod to help you implement getNextBestLayout. For example: | |||||
@code | |||||
BusesLayout getNextBestLayout (const BusesLayout& layouts) override | |||||
{ | |||||
return getNextBestLayoutInLayoutList (layouts, {{1,1},{2,2}}); | |||||
} | |||||
@endcode | |||||
*/ | |||||
template <int numLayouts> | |||||
BusesLayout getNextBestLayoutInLayoutList (const BusesLayout& layouts, | |||||
const short (&channelLayoutList) [numLayouts][2]) | |||||
{ | |||||
return getNextBestLayoutInList (layouts, layoutListToArray (channelLayoutList)); | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns the current sample rate. | /** Returns the current sample rate. | ||||
@@ -473,6 +821,9 @@ public: | |||||
/** Returns true if the processor supports MPE. */ | /** Returns true if the processor supports MPE. */ | ||||
virtual bool supportsMPE() const { return false; } | virtual bool supportsMPE() const { return false; } | ||||
/** Returns true if this is a midi effect plug-in and does no audio processing. */ | |||||
virtual bool isMidiEffect() const { return false; } | |||||
//============================================================================== | //============================================================================== | ||||
/** This returns a critical section that will automatically be locked while the host | /** This returns a critical section that will automatically be locked while the host | ||||
is calling the processBlock() method. | is calling the processBlock() method. | ||||
@@ -611,6 +962,17 @@ public: | |||||
*/ | */ | ||||
virtual const String getParameterName (int parameterIndex); | virtual const String getParameterName (int parameterIndex); | ||||
/** Returns the ID of a particular parameter. | |||||
The ID is used to communicate the value or mapping of a particular parameter with | |||||
the host. By default this method will simply return a string representation of | |||||
index. | |||||
NOTE! This method will eventually be deprecated! It's recommended that you use the | |||||
AudioProcessorParameterWithID class instead to manage your parameters. | |||||
*/ | |||||
virtual String getParameterID (int index); | |||||
/** Called by the host to find out the value of one of the filter's parameters. | /** Called by the host to find out the value of one of the filter's parameters. | ||||
The host will expect the value returned to be between 0 and 1.0. | The host will expect the value returned to be between 0 and 1.0. | ||||
@@ -628,7 +990,7 @@ public: | |||||
If you want to provide customised short versions of your parameter names that | If you want to provide customised short versions of your parameter names that | ||||
will look better in constrained spaces (e.g. the displays on hardware controller | will look better in constrained spaces (e.g. the displays on hardware controller | ||||
devices or mixing desks) then you should implement this method. | devices or mixing desks) then you should implement this method. | ||||
If you don't override it, the default implementation will call getParameterText(int), | |||||
If you don't override it, the default implementation will call getParameterName(int), | |||||
and truncate the result. | and truncate the result. | ||||
NOTE! This method will eventually be deprecated! It's recommended that you use | NOTE! This method will eventually be deprecated! It's recommended that you use | ||||
@@ -864,9 +1226,15 @@ public: | |||||
*/ | */ | ||||
virtual void setCurrentProgramStateInformation (const void* data, int sizeInBytes); | virtual void setCurrentProgramStateInformation (const void* data, int sizeInBytes); | ||||
/** This method is called when the number of input or output channels is changed. */ | |||||
/** This method is called when the total number of input or output channels is changed. */ | |||||
virtual void numChannelsChanged(); | virtual void numChannelsChanged(); | ||||
/** This method is called when the number of buses is changed. */ | |||||
virtual void numBusesChanged(); | |||||
/** This method is called when the layout of the audio processor changes. */ | |||||
virtual void processorLayoutsChanged(); | |||||
//============================================================================== | //============================================================================== | ||||
/** LV2 specific calls, saving/restore as string. */ | /** LV2 specific calls, saving/restore as string. */ | ||||
virtual String getStateInformationString () { return String::empty; } | virtual String getStateInformationString () { return String::empty; } | ||||
@@ -894,9 +1262,9 @@ public: | |||||
/** This is called by the processor to specify its details before being played. You | /** This is called by the processor to specify its details before being played. You | ||||
should call this function after having informed the processor about the channel | should call this function after having informed the processor about the channel | ||||
and bus layouts via setPreferredBusArrangement. | |||||
and bus layouts via setBusesLayout. | |||||
@see setPreferredBusArrangement | |||||
@see setBusesLayout | |||||
*/ | */ | ||||
void setRateAndBufferSizeDetails (double sampleRate, int blockSize) noexcept; | void setRateAndBufferSizeDetails (double sampleRate, int blockSize) noexcept; | ||||
@@ -913,6 +1281,7 @@ public: | |||||
wrapperType_VST, | wrapperType_VST, | ||||
wrapperType_VST3, | wrapperType_VST3, | ||||
wrapperType_AudioUnit, | wrapperType_AudioUnit, | ||||
wrapperType_AudioUnitv3, | |||||
wrapperType_RTAS, | wrapperType_RTAS, | ||||
wrapperType_AAX, | wrapperType_AAX, | ||||
wrapperType_Standalone | wrapperType_Standalone | ||||
@@ -939,7 +1308,7 @@ public: | |||||
/** Returns the name of one of the processor's input channels. | /** Returns the name of one of the processor's input channels. | ||||
These functions are deprecated: your audio processor can inform the host | These functions are deprecated: your audio processor can inform the host | ||||
on channel layouts and names via the methods in the AudioBusArrangement class. | |||||
on channel layouts and names via the methods in the AudiobusLayout class. | |||||
*/ | */ | ||||
JUCE_DEPRECATED (virtual const String getInputChannelName (int channelIndex) const); | JUCE_DEPRECATED (virtual const String getInputChannelName (int channelIndex) const); | ||||
JUCE_DEPRECATED (virtual const String getOutputChannelName (int channelIndex) const); | JUCE_DEPRECATED (virtual const String getOutputChannelName (int channelIndex) const); | ||||
@@ -947,7 +1316,7 @@ public: | |||||
/** Returns true if the specified channel is part of a stereo pair with its neighbour. | /** Returns true if the specified channel is part of a stereo pair with its neighbour. | ||||
These functions are deprecated: your audio processor should specify the audio | These functions are deprecated: your audio processor should specify the audio | ||||
channel pairing information by modifying the busArrangement member variable in | |||||
channel pairing information by modifying the busLayout member variable in | |||||
the constructor. */ | the constructor. */ | ||||
JUCE_DEPRECATED (virtual bool isInputChannelStereoPair (int index) const); | JUCE_DEPRECATED (virtual bool isInputChannelStereoPair (int index) const); | ||||
JUCE_DEPRECATED (virtual bool isOutputChannelStereoPair (int index) const); | JUCE_DEPRECATED (virtual bool isOutputChannelStereoPair (int index) const); | ||||
@@ -976,6 +1345,110 @@ public: | |||||
static void JUCE_CALLTYPE setTypeOfNextNewPlugin (WrapperType); | static void JUCE_CALLTYPE setTypeOfNextNewPlugin (WrapperType); | ||||
protected: | protected: | ||||
/** Callback to query if the AudioProcessor supports a specific layout. | |||||
This callback is called when the host probes the supported bus layouts via | |||||
the checkBusesLayoutSupported method. You should override this callback if you | |||||
would like to limit the layouts that your AudioProcessor supports. The default | |||||
implementation will accept any layout. JUCE does basic sanity checks so that | |||||
the provided layouts parameter will have the same number of buses as your | |||||
AudioProcessor. | |||||
@see checkBusesLayoutSupported | |||||
*/ | |||||
virtual bool isBusesLayoutSupported (const BusesLayout& /*layouts*/) const { return true; } | |||||
/** Callback to check if a certain bus layout can now be applied | |||||
Most subclasses will not need to override this method and should instead | |||||
override the isBusesLayoutSupported callback to reject certain layout changes. | |||||
This callback is called when the user requests a layout change. It will only be | |||||
called if processing of the AudioProcessor has been stopped by a previous call to | |||||
releaseResources or after the construction of the AudioProcessor. It will be called | |||||
just before the actual layout change. By returning false you will abort the layout | |||||
change and setBusesLayout will return false indicating that the layout change | |||||
was not successful. | |||||
The default implementation will simply call isBusesLayoutSupported. | |||||
You only need to override this method if there is a chance that your AudioProcessor | |||||
may not accept a layout although you have previously claimed to support it via the | |||||
isBusesLayoutSupported callback. This can occur if your AudioProcessor's supported | |||||
layouts depend on other plug-in parameters which may have changed since the last | |||||
call to isBusesLayoutSupported, such as the format of an audio file which can be | |||||
selected by the user in the AudioProcessor's editor. This callback gives the | |||||
AudioProcessor a last chance to reject a layout if conditions have changed as it | |||||
is always called just before the actual layout change. | |||||
As it is never called while the AudioProcessor is processing audio, it can also | |||||
be used for AudioProcessors which wrap other plug-in formats to apply the current | |||||
layout to the underlying plug-in. This callback gives such AudioProcessors a | |||||
chance to reject the layout change should an error occur with the underlying plug-in | |||||
during the layout change. | |||||
@see isBusesLayoutSupported, setBusesLayout | |||||
*/ | |||||
virtual bool canApplyBusesLayout (const BusesLayout& layouts) const { return isBusesLayoutSupported (layouts); } | |||||
//============================================================================== | |||||
/** Structure used for AudioProcessor Callbacks */ | |||||
struct BusProperties | |||||
{ | |||||
/** The name of the bus */ | |||||
String busName; | |||||
/** The default layout of the bus */ | |||||
AudioChannelSet defaultLayout; | |||||
/** Is this bus activated by default? */ | |||||
bool isActivatedByDefault; | |||||
}; | |||||
struct BusesProperties | |||||
{ | |||||
/** The layouts of the input buses */ | |||||
Array<BusProperties> inputLayouts; | |||||
/** The layouts of the output buses */ | |||||
Array<BusProperties> outputLayouts; | |||||
void addBus (bool isInput, const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true); | |||||
BusesProperties withInput (const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true) const; | |||||
BusesProperties withOutput (const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true) const; | |||||
}; | |||||
/** Callback to query if adding/removing buses currently possible. | |||||
This callback is called when the host calls addBus or removeBus. | |||||
Similar to canApplyBusesLayout, this callback is only called while | |||||
the AudioProcessor is stopped and gives the processor a last | |||||
chance to reject a requested bus change. It can also be used to apply | |||||
the bus count change to an underlying wrapped plug-in. | |||||
When adding a bus, isAddingBuses will be true and the plug-in is | |||||
expected to fill out outNewBusProperties with the properties of the | |||||
bus which will be created just after the succesful return of this callback. | |||||
Implementations of AudioProcessor will rarely need to override this | |||||
method. Only override this method if your processor supports adding | |||||
and removing buses and if it needs more fine grain control over the | |||||
naming of new buses or may reject bus number changes although canAddBus | |||||
or canRemoveBus returned true. | |||||
The default implementation will return false if canAddBus/canRemoveBus | |||||
returns false (the default behavior). Otherwise, this method returns | |||||
"Input #busIdx" for input buses and "Output #busIdx" for output buses | |||||
where busIdx is the index for newly created buses. The default layout | |||||
in this case will be the layout of the previous bus of the same direction. | |||||
*/ | |||||
virtual bool canApplyBusCountChange (bool isInput, bool isAddingBuses, | |||||
BusProperties& outNewBusProperties); | |||||
//============================================================================== | |||||
friend struct PluginBusUtilities; | |||||
/** @internal */ | /** @internal */ | ||||
AudioPlayHead* playHead; | AudioPlayHead* playHead; | ||||
@@ -983,6 +1456,67 @@ protected: | |||||
void sendParamChangeMessageToListeners (int parameterIndex, float newValue); | void sendParamChangeMessageToListeners (int parameterIndex, float newValue); | ||||
private: | private: | ||||
//============================================================================== | |||||
struct InOutChannelPair | |||||
{ | |||||
int16 inChannels, outChannels; | |||||
InOutChannelPair() noexcept : inChannels (0), outChannels (0) {} | |||||
InOutChannelPair (short inCh, short outCh) noexcept : inChannels (inCh), outChannels (outCh) {} | |||||
InOutChannelPair (const InOutChannelPair& o) noexcept : inChannels (o.inChannels), outChannels (o.outChannels) {} | |||||
InOutChannelPair (const short (&config)[2]) noexcept : inChannels (config[0]), outChannels (config[1]) {} | |||||
InOutChannelPair& operator= (const InOutChannelPair& o) { inChannels = o.inChannels; outChannels = o.outChannels; return *this; } | |||||
bool operator== (const InOutChannelPair& other) const noexcept | |||||
{ | |||||
return (other.inChannels == inChannels && other.outChannels == outChannels); | |||||
} | |||||
}; | |||||
template <int numLayouts> | |||||
static Array<InOutChannelPair> layoutListToArray (const short (&configuration) [numLayouts][2]) | |||||
{ | |||||
Array<InOutChannelPair> layouts; | |||||
for (int i = 0; i < numLayouts; ++i) | |||||
{ | |||||
InOutChannelPair pair (configuration [i]); | |||||
layouts.add (pair); | |||||
} | |||||
return layouts; | |||||
} | |||||
#if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||||
static Array<InOutChannelPair> layoutListToArray (const std::initializer_list<const short[2]>& configuration) | |||||
{ | |||||
Array<InOutChannelPair> layouts; | |||||
for (std::initializer_list<const short[2]>::const_iterator it = configuration.begin(); | |||||
it != configuration.end(); ++it) | |||||
{ | |||||
InOutChannelPair pair (*it); | |||||
layouts.add (pair); | |||||
} | |||||
return layouts; | |||||
} | |||||
#endif | |||||
//============================================================================== | |||||
static BusesProperties busesPropertiesFromLayoutArray (const Array<InOutChannelPair>&); | |||||
//============================================================================== | |||||
BusesLayout getNextBestLayoutInList (const BusesLayout& layouts, | |||||
const Array<InOutChannelPair>& channelLayouts) const; | |||||
static bool containsLayout (const BusesLayout& layouts, const Array<InOutChannelPair>& channelLayouts); | |||||
//============================================================================== | |||||
void initialise (const BusesProperties& ioLayouts); | |||||
void createBus (bool inputBus, const BusProperties&); | |||||
//============================================================================== | |||||
Array<AudioProcessorListener*> listeners; | Array<AudioProcessorListener*> listeners; | ||||
#if ! JUCE_AUDIO_PROCESSOR_NO_GUI | #if ! JUCE_AUDIO_PROCESSOR_NO_GUI | ||||
Component::SafePointer<AudioProcessorEditor> activeEditor; | Component::SafePointer<AudioProcessorEditor> activeEditor; | ||||
@@ -996,9 +1530,15 @@ private: | |||||
ProcessingPrecision processingPrecision; | ProcessingPrecision processingPrecision; | ||||
CriticalSection callbackLock, listenerLock; | CriticalSection callbackLock, listenerLock; | ||||
friend class Bus; | |||||
mutable OwnedArray<Bus> inputBuses; | |||||
mutable OwnedArray<Bus> outputBuses; | |||||
String cachedInputSpeakerArrString; | String cachedInputSpeakerArrString; | ||||
String cachedOutputSpeakerArrString; | String cachedOutputSpeakerArrString; | ||||
int cachedTotalIns, cachedTotalOuts; | |||||
OwnedArray<AudioProcessorParameter> managedParameters; | OwnedArray<AudioProcessorParameter> managedParameters; | ||||
AudioProcessorParameter* getParamChecked (int) const noexcept; | AudioProcessorParameter* getParamChecked (int) const noexcept; | ||||
@@ -1007,8 +1547,13 @@ private: | |||||
#endif | #endif | ||||
AudioProcessorListener* getListenerLocked (int) const noexcept; | AudioProcessorListener* getListenerLocked (int) const noexcept; | ||||
void disableNonMainBuses (bool isInput); | |||||
void updateSpeakerFormatStrings(); | void updateSpeakerFormatStrings(); | ||||
bool applyBusLayouts (const BusesLayout& arr); | |||||
void audioIOChanged (bool busNumberChanged, bool channelNumChanged); | |||||
BusesLayout getNextBestLayout (const BusesLayout& layouts) const; | |||||
template <typename floatType> | |||||
void processBypassed (AudioBuffer<floatType>&, MidiBuffer&); | |||||
// This method is no longer used - you can delete it from your AudioProcessor classes. | // This method is no longer used - you can delete it from your AudioProcessor classes. | ||||
JUCE_DEPRECATED_WITH_BODY (virtual bool silenceInProducesSilenceOut() const, { return false; }); | JUCE_DEPRECATED_WITH_BODY (virtual bool silenceInProducesSilenceOut() const, { return false; }); | ||||
@@ -22,14 +22,16 @@ | |||||
============================================================================== | ============================================================================== | ||||
*/ | */ | ||||
AudioProcessorEditor::AudioProcessorEditor (AudioProcessor& p) noexcept : processor (p) | |||||
AudioProcessorEditor::AudioProcessorEditor (AudioProcessor& p) noexcept : processor (p), constrainer (nullptr) | |||||
{ | { | ||||
initialise(); | |||||
} | } | ||||
AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* p) noexcept : processor (*p) | |||||
AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* p) noexcept : processor (*p), constrainer (nullptr) | |||||
{ | { | ||||
// the filter must be valid.. | // the filter must be valid.. | ||||
jassert (p != nullptr); | jassert (p != nullptr); | ||||
initialise(); | |||||
} | } | ||||
AudioProcessorEditor::~AudioProcessorEditor() | AudioProcessorEditor::~AudioProcessorEditor() | ||||
@@ -37,7 +39,132 @@ AudioProcessorEditor::~AudioProcessorEditor() | |||||
// if this fails, then the wrapper hasn't called editorBeingDeleted() on the | // if this fails, then the wrapper hasn't called editorBeingDeleted() on the | ||||
// filter for some reason.. | // filter for some reason.. | ||||
jassert (processor.getActiveEditor() != this); | jassert (processor.getActiveEditor() != this); | ||||
removeComponentListener (resizeListener); | |||||
} | } | ||||
void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} | void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} | ||||
int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } | int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } | ||||
void AudioProcessorEditor::initialise() | |||||
{ | |||||
resizable = false; | |||||
attachConstrainer (&defaultConstrainer); | |||||
addComponentListener (resizeListener = new AudioProcessorEditorListener (this)); | |||||
} | |||||
//============================================================================== | |||||
void AudioProcessorEditor::setResizable (const bool shouldBeResizable, const bool useBottomRightCornerResizer) | |||||
{ | |||||
if (shouldBeResizable != resizable) | |||||
{ | |||||
resizable = shouldBeResizable; | |||||
if (! resizable) | |||||
{ | |||||
setConstrainer (&defaultConstrainer); | |||||
if (getWidth() > 0 && getHeight() > 0) | |||||
{ | |||||
defaultConstrainer.setSizeLimits (getWidth(), getHeight(), getWidth(), getHeight()); | |||||
resized(); | |||||
} | |||||
} | |||||
} | |||||
const bool shouldHaveCornerResizer = (useBottomRightCornerResizer && shouldBeResizable); | |||||
if (shouldHaveCornerResizer != (resizableCorner != nullptr)) | |||||
{ | |||||
if (shouldHaveCornerResizer) | |||||
{ | |||||
Component::addChildComponent (resizableCorner = new ResizableCornerComponent (this, constrainer)); | |||||
resizableCorner->setAlwaysOnTop (true); | |||||
} | |||||
else | |||||
resizableCorner = nullptr; | |||||
} | |||||
} | |||||
void AudioProcessorEditor::setResizeLimits (const int newMinimumWidth, | |||||
const int newMinimumHeight, | |||||
const int newMaximumWidth, | |||||
const int newMaximumHeight) noexcept | |||||
{ | |||||
// if you've set up a custom constrainer then these settings won't have any effect.. | |||||
jassert (constrainer == &defaultConstrainer || constrainer == nullptr); | |||||
const bool shouldEnableResize = (newMinimumWidth != newMaximumWidth || newMinimumHeight != newMaximumHeight); | |||||
const bool shouldHaveCornerResizer = (shouldEnableResize != resizable || resizableCorner != nullptr); | |||||
setResizable (shouldEnableResize, shouldHaveCornerResizer); | |||||
if (constrainer == nullptr) | |||||
setConstrainer (&defaultConstrainer); | |||||
defaultConstrainer.setSizeLimits (newMinimumWidth, newMinimumHeight, | |||||
newMaximumWidth, newMaximumHeight); | |||||
setBoundsConstrained (getBounds()); | |||||
} | |||||
void AudioProcessorEditor::setConstrainer (ComponentBoundsConstrainer* newConstrainer) | |||||
{ | |||||
if (constrainer != newConstrainer) | |||||
{ | |||||
resizable = true; | |||||
attachConstrainer (newConstrainer); | |||||
} | |||||
} | |||||
void AudioProcessorEditor::attachConstrainer (ComponentBoundsConstrainer* newConstrainer) | |||||
{ | |||||
if (constrainer != newConstrainer) | |||||
{ | |||||
constrainer = newConstrainer; | |||||
updatePeer(); | |||||
} | |||||
} | |||||
void AudioProcessorEditor::setBoundsConstrained (Rectangle<int> newBounds) | |||||
{ | |||||
if (constrainer != nullptr) | |||||
constrainer->setBoundsForComponent (this, newBounds, false, false, false, false); | |||||
else | |||||
setBounds (newBounds); | |||||
} | |||||
void AudioProcessorEditor::editorResized (bool wasResized) | |||||
{ | |||||
if (wasResized) | |||||
{ | |||||
bool resizerHidden = false; | |||||
if (ComponentPeer* peer = getPeer()) | |||||
resizerHidden = peer->isFullScreen() || peer->isKioskMode(); | |||||
if (resizableCorner != nullptr) | |||||
{ | |||||
resizableCorner->setVisible (! resizerHidden); | |||||
const int resizerSize = 18; | |||||
resizableCorner->setBounds (getWidth() - resizerSize, | |||||
getHeight() - resizerSize, | |||||
resizerSize, resizerSize); | |||||
} | |||||
if (! resizable) | |||||
{ | |||||
if (getWidth() > 0 && getHeight() > 0) | |||||
defaultConstrainer.setSizeLimits (getWidth(), getHeight(), | |||||
getWidth(), getHeight()); | |||||
} | |||||
} | |||||
} | |||||
void AudioProcessorEditor::updatePeer() | |||||
{ | |||||
if (isOnDesktop()) | |||||
if (ComponentPeer* const peer = getPeer()) | |||||
peer->setConstrainer (constrainer); | |||||
} |
@@ -25,7 +25,7 @@ | |||||
#ifndef JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED | #ifndef JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED | ||||
#define JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED | #define JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED | ||||
class AudioProcessorEditorListener; | |||||
//============================================================================== | //============================================================================== | ||||
/** | /** | ||||
Base class for the component that acts as the GUI for an AudioProcessor. | Base class for the component that acts as the GUI for an AudioProcessor. | ||||
@@ -84,7 +84,91 @@ public: | |||||
*/ | */ | ||||
virtual int getControlParameterIndex (Component&); | virtual int getControlParameterIndex (Component&); | ||||
//============================================================================== | |||||
/** Marks the host's editor window as resizable | |||||
@param allowHostToResize whether the editor's parent window can be resized | |||||
by the user or the host. Even if this is false, you | |||||
can still resize your window yourself by calling | |||||
setBounds (for example, when a user clicks on a button | |||||
in your editor to drop out a panel) which will bypass any | |||||
resizable/constraints checks. If you are using | |||||
your own corner resizer than this will also bypass | |||||
any checks. | |||||
@param useBottomRightCornerResizer | |||||
@see setResizeLimits, isResizable | |||||
*/ | |||||
void setResizable (bool allowHostToResize, bool useBottomRightCornerResizer); | |||||
/** Returns true if the host is allowed to resize editor's parent window | |||||
@see setResizable | |||||
*/ | |||||
bool isResizable() const noexcept { return resizable; } | |||||
/** This sets the maximum and minimum sizes for the window. | |||||
If the window's current size is outside these limits, it will be resized to | |||||
make sure it's within them. | |||||
A direct call to setBounds() will bypass any constraint checks, but when the | |||||
window is dragged by the user or resized by other indirect means, the constrainer | |||||
will limit the numbers involved. | |||||
@see setResizable | |||||
*/ | |||||
void setResizeLimits (int newMinimumWidth, | |||||
int newMinimumHeight, | |||||
int newMaximumWidth, | |||||
int newMaximumHeight) noexcept; | |||||
/** Returns the bounds constrainer object that this window is using. | |||||
You can access this to change its properties. | |||||
*/ | |||||
ComponentBoundsConstrainer* getConstrainer() noexcept { return constrainer; } | |||||
/** Sets the bounds-constrainer object to use for resizing and dragging this window. | |||||
A pointer to the object you pass in will be kept, but it won't be deleted | |||||
by this object, so it's the caller's responsibility to manage it. | |||||
If you pass a nullptr, then no contraints will be placed on the positioning of the window. | |||||
*/ | |||||
void setConstrainer (ComponentBoundsConstrainer* newConstrainer); | |||||
/** Calls the window's setBounds method, after first checking these bounds | |||||
with the current constrainer. | |||||
@see setConstrainer | |||||
*/ | |||||
void setBoundsConstrained (Rectangle<int> newBounds); | |||||
ScopedPointer<ResizableCornerComponent> resizableCorner; | |||||
private: | private: | ||||
//============================================================================== | |||||
struct AudioProcessorEditorListener : ComponentListener | |||||
{ | |||||
AudioProcessorEditorListener (AudioProcessorEditor* audioEditor) : e (audioEditor) {} | |||||
void componentMovedOrResized (Component&, bool, bool wasResized) override { e->editorResized (wasResized); } | |||||
void componentParentHierarchyChanged (Component&) override { e->updatePeer(); } | |||||
AudioProcessorEditor* e; | |||||
}; | |||||
//============================================================================== | |||||
void initialise(); | |||||
void editorResized (bool wasResized); | |||||
void updatePeer(); | |||||
void attachConstrainer (ComponentBoundsConstrainer* newConstrainer); | |||||
//============================================================================== | |||||
ScopedPointer<AudioProcessorEditorListener> resizeListener; | |||||
bool resizable; | |||||
ComponentBoundsConstrainer defaultConstrainer; | |||||
ComponentBoundsConstrainer* constrainer; | |||||
JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditor) | JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditor) | ||||
}; | }; | ||||
@@ -264,7 +264,16 @@ struct ProcessBufferOp : public AudioGraphRenderingOp<ProcessBufferOp> | |||||
AudioBuffer<FloatType> buffer (channels, totalChans, numSamples); | AudioBuffer<FloatType> buffer (channels, totalChans, numSamples); | ||||
callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); | |||||
if (processor->isSuspended()) | |||||
{ | |||||
buffer.clear(); | |||||
} | |||||
else | |||||
{ | |||||
ScopedLock lock (processor->getCallbackLock()); | |||||
callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); | |||||
} | |||||
} | } | ||||
void callProcess (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) | void callProcess (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) | ||||
@@ -960,14 +969,7 @@ void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int n | |||||
processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision | processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision | ||||
: singlePrecision); | : singlePrecision); | ||||
processor->setPlayConfigDetails (processor->getMainBusNumInputChannels(), | |||||
processor->getMainBusNumOutputChannels(), | |||||
newSampleRate, newBlockSize); | |||||
// AudioProcessorGraph currently does not support processors with multiple buses | |||||
jassert (processor->getMainBusNumInputChannels() == processor->getTotalNumInputChannels() | |||||
&& processor->getMainBusNumOutputChannels() == processor->getTotalNumOutputChannels()); | |||||
processor->setRateAndBufferSizeDetails (newSampleRate, newBlockSize); | |||||
processor->prepareToPlay (newSampleRate, newBlockSize); | processor->prepareToPlay (newSampleRate, newBlockSize); | ||||
} | } | ||||
} | } | ||||
@@ -1035,7 +1037,7 @@ struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers | |||||
//============================================================================== | //============================================================================== | ||||
AudioProcessorGraph::AudioProcessorGraph() | AudioProcessorGraph::AudioProcessorGraph() | ||||
: lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), | : lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), | ||||
currentMidiInputBuffer (nullptr) | |||||
currentMidiInputBuffer (nullptr), isPrepared (false) | |||||
{ | { | ||||
} | } | ||||
@@ -1102,7 +1104,9 @@ AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const n | |||||
Node* const n = new Node (nodeId, newProcessor); | Node* const n = new Node (nodeId, newProcessor); | ||||
nodes.add (n); | nodes.add (n); | ||||
triggerAsyncUpdate(); | |||||
if (isPrepared) | |||||
triggerAsyncUpdate(); | |||||
n->setParentGraph (this); | n->setParentGraph (this); | ||||
return n; | return n; | ||||
@@ -1117,7 +1121,9 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) | |||||
if (nodes.getUnchecked(i)->nodeId == nodeId) | if (nodes.getUnchecked(i)->nodeId == nodeId) | ||||
{ | { | ||||
nodes.remove (i); | nodes.remove (i); | ||||
triggerAsyncUpdate(); | |||||
if (isPrepared) | |||||
triggerAsyncUpdate(); | |||||
return true; | return true; | ||||
} | } | ||||
@@ -1126,6 +1132,15 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) | |||||
return false; | return false; | ||||
} | } | ||||
bool AudioProcessorGraph::removeNode (Node* node) | |||||
{ | |||||
if (node != nullptr) | |||||
return removeNode (node->nodeId); | |||||
jassertfalse; | |||||
return false; | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, | const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, | ||||
const int sourceChannelIndex, | const int sourceChannelIndex, | ||||
@@ -1168,14 +1183,14 @@ bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId, | |||||
const Node* const source = getNodeForId (sourceNodeId); | const Node* const source = getNodeForId (sourceNodeId); | ||||
if (source == nullptr | if (source == nullptr | ||||
|| (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getMainBusNumOutputChannels()) | |||||
|| (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getTotalNumOutputChannels()) | |||||
|| (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) | || (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) | ||||
return false; | return false; | ||||
const Node* const dest = getNodeForId (destNodeId); | const Node* const dest = getNodeForId (destNodeId); | ||||
if (dest == nullptr | if (dest == nullptr | ||||
|| (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getMainBusNumInputChannels()) | |||||
|| (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getTotalNumInputChannels()) | |||||
|| (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) | || (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) | ||||
return false; | return false; | ||||
@@ -1194,14 +1209,19 @@ bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId, | |||||
GraphRenderingOps::ConnectionSorter sorter; | GraphRenderingOps::ConnectionSorter sorter; | ||||
connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex, | connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex, | ||||
destNodeId, destChannelIndex)); | destNodeId, destChannelIndex)); | ||||
triggerAsyncUpdate(); | |||||
if (isPrepared) | |||||
triggerAsyncUpdate(); | |||||
return true; | return true; | ||||
} | } | ||||
void AudioProcessorGraph::removeConnection (const int index) | void AudioProcessorGraph::removeConnection (const int index) | ||||
{ | { | ||||
connections.remove (index); | connections.remove (index); | ||||
triggerAsyncUpdate(); | |||||
if (isPrepared) | |||||
triggerAsyncUpdate(); | |||||
} | } | ||||
bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, | bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, | ||||
@@ -1253,9 +1273,9 @@ bool AudioProcessorGraph::isConnectionLegal (const Connection* const c) const | |||||
return source != nullptr | return source != nullptr | ||||
&& dest != nullptr | && dest != nullptr | ||||
&& (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getMainBusNumOutputChannels()) | |||||
&& (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getTotalNumOutputChannels()) | |||||
: source->processor->producesMidi()) | : source->processor->producesMidi()) | ||||
&& (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getMainBusNumInputChannels()) | |||||
&& (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getTotalNumInputChannels()) | |||||
: dest->processor->acceptsMidi()); | : dest->processor->acceptsMidi()); | ||||
} | } | ||||
@@ -1383,6 +1403,8 @@ void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSam | |||||
clearRenderingSequence(); | clearRenderingSequence(); | ||||
buildRenderingSequence(); | buildRenderingSequence(); | ||||
isPrepared = true; | |||||
} | } | ||||
bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | ||||
@@ -1392,6 +1414,8 @@ bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | |||||
void AudioProcessorGraph::releaseResources() | void AudioProcessorGraph::releaseResources() | ||||
{ | { | ||||
isPrepared = false; | |||||
for (int i = 0; i < nodes.size(); ++i) | for (int i = 0; i < nodes.size(); ++i) | ||||
nodes.getUnchecked(i)->unprepare(); | nodes.getUnchecked(i)->unprepare(); | ||||
@@ -1516,13 +1540,13 @@ void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (Plugin | |||||
d.version = "1.0"; | d.version = "1.0"; | ||||
d.isInstrument = false; | d.isInstrument = false; | ||||
d.numInputChannels = getMainBusNumInputChannels(); | |||||
d.numInputChannels = getTotalNumInputChannels(); | |||||
if (type == audioOutputNode && graph != nullptr) | if (type == audioOutputNode && graph != nullptr) | ||||
d.numInputChannels = graph->getMainBusNumInputChannels(); | |||||
d.numInputChannels = graph->getTotalNumInputChannels(); | |||||
d.numOutputChannels = getMainBusNumOutputChannels(); | |||||
d.numOutputChannels = getTotalNumOutputChannels(); | |||||
if (type == audioInputNode && graph != nullptr) | if (type == audioInputNode && graph != nullptr) | ||||
d.numOutputChannels = graph->getMainBusNumOutputChannels(); | |||||
d.numOutputChannels = graph->getTotalNumOutputChannels(); | |||||
} | } | ||||
void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) | void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) | ||||
@@ -1639,8 +1663,8 @@ void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorG | |||||
if (graph != nullptr) | if (graph != nullptr) | ||||
{ | { | ||||
setPlayConfigDetails (type == audioOutputNode ? graph->getMainBusNumOutputChannels() : 0, | |||||
type == audioInputNode ? graph->getMainBusNumInputChannels() : 0, | |||||
setPlayConfigDetails (type == audioOutputNode ? graph->getTotalNumOutputChannels() : 0, | |||||
type == audioInputNode ? graph->getTotalNumInputChannels() : 0, | |||||
getSampleRate(), | getSampleRate(), | ||||
getBlockSize()); | getBlockSize()); | ||||
@@ -183,6 +183,12 @@ public: | |||||
*/ | */ | ||||
bool removeNode (uint32 nodeId); | bool removeNode (uint32 nodeId); | ||||
/** Deletes a node within the graph which has the specified ID. | |||||
This will also delete any connections that are attached to this node. | |||||
*/ | |||||
bool removeNode (Node* node); | |||||
//============================================================================== | //============================================================================== | ||||
/** Returns the number of connections in the graph. */ | /** Returns the number of connections in the graph. */ | ||||
int getNumConnections() const { return connections.size(); } | int getNumConnections() const { return connections.size(); } | ||||
@@ -390,6 +396,8 @@ private: | |||||
MidiBuffer* currentMidiInputBuffer; | MidiBuffer* currentMidiInputBuffer; | ||||
MidiBuffer currentMidiOutputBuffer; | MidiBuffer currentMidiOutputBuffer; | ||||
bool isPrepared; | |||||
void handleAsyncUpdate() override; | void handleAsyncUpdate() override; | ||||
void clearRenderingSequence(); | void clearRenderingSequence(); | ||||
void buildRenderingSequence(); | void buildRenderingSequence(); | ||||
@@ -105,6 +105,16 @@ private: | |||||
} | } | ||||
} | } | ||||
void startedDragging() override | |||||
{ | |||||
owner.beginParameterChangeGesture(index); | |||||
} | |||||
void stoppedDragging() override | |||||
{ | |||||
owner.endParameterChangeGesture(index); | |||||
} | |||||
String getTextFromValue (double /*value*/) override | String getTextFromValue (double /*value*/) override | ||||
{ | { | ||||
return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); | return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); | ||||
@@ -118,7 +118,7 @@ public: | |||||
given identifier string. | given identifier string. | ||||
Note that this isn't quite as simple as them just calling createIdentifierString() | Note that this isn't quite as simple as them just calling createIdentifierString() | ||||
and comparing the strings, because the identifers can differ (thanks to shell plug-ins). | |||||
and comparing the strings, because the identifiers can differ (thanks to shell plug-ins). | |||||
*/ | */ | ||||
bool matchesIdentifierString (const String& identifierString) const; | bool matchesIdentifierString (const String& identifierString) const; | ||||
@@ -27,6 +27,8 @@ KnownPluginList::~KnownPluginList() {} | |||||
void KnownPluginList::clear() | void KnownPluginList::clear() | ||||
{ | { | ||||
ScopedLock lock (typesArrayLock); | |||||
if (types.size() > 0) | if (types.size() > 0) | ||||
{ | { | ||||
types.clear(); | types.clear(); | ||||
@@ -36,6 +38,8 @@ void KnownPluginList::clear() | |||||
PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const | PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const | ||||
{ | { | ||||
ScopedLock lock (typesArrayLock); | |||||
for (int i = 0; i < types.size(); ++i) | for (int i = 0; i < types.size(); ++i) | ||||
if (types.getUnchecked(i)->fileOrIdentifier == fileOrIdentifier) | if (types.getUnchecked(i)->fileOrIdentifier == fileOrIdentifier) | ||||
return types.getUnchecked(i); | return types.getUnchecked(i); | ||||
@@ -45,6 +49,8 @@ PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifi | |||||
PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& identifierString) const | PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& identifierString) const | ||||
{ | { | ||||
ScopedLock lock (typesArrayLock); | |||||
for (int i = 0; i < types.size(); ++i) | for (int i = 0; i < types.size(); ++i) | ||||
if (types.getUnchecked(i)->matchesIdentifierString (identifierString)) | if (types.getUnchecked(i)->matchesIdentifierString (identifierString)) | ||||
return types.getUnchecked(i); | return types.getUnchecked(i); | ||||
@@ -54,27 +60,37 @@ PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& id | |||||
bool KnownPluginList::addType (const PluginDescription& type) | bool KnownPluginList::addType (const PluginDescription& type) | ||||
{ | { | ||||
for (int i = types.size(); --i >= 0;) | |||||
{ | { | ||||
if (types.getUnchecked(i)->isDuplicateOf (type)) | |||||
ScopedLock lock (typesArrayLock); | |||||
for (int i = types.size(); --i >= 0;) | |||||
{ | { | ||||
// strange - found a duplicate plugin with different info.. | |||||
jassert (types.getUnchecked(i)->name == type.name); | |||||
jassert (types.getUnchecked(i)->isInstrument == type.isInstrument); | |||||
if (types.getUnchecked(i)->isDuplicateOf (type)) | |||||
{ | |||||
// strange - found a duplicate plugin with different info.. | |||||
jassert (types.getUnchecked(i)->name == type.name); | |||||
jassert (types.getUnchecked(i)->isInstrument == type.isInstrument); | |||||
*types.getUnchecked(i) = type; | |||||
return false; | |||||
*types.getUnchecked(i) = type; | |||||
return false; | |||||
} | |||||
} | } | ||||
types.insert (0, new PluginDescription (type)); | |||||
} | } | ||||
types.insert (0, new PluginDescription (type)); | |||||
sendChangeMessage(); | sendChangeMessage(); | ||||
return true; | return true; | ||||
} | } | ||||
void KnownPluginList::removeType (const int index) | void KnownPluginList::removeType (const int index) | ||||
{ | { | ||||
types.remove (index); | |||||
{ | |||||
ScopedLock lock (typesArrayLock); | |||||
types.remove (index); | |||||
} | |||||
sendChangeMessage(); | sendChangeMessage(); | ||||
} | } | ||||
@@ -84,6 +100,8 @@ bool KnownPluginList::isListingUpToDate (const String& fileOrIdentifier, | |||||
if (getTypeForFile (fileOrIdentifier) == nullptr) | if (getTypeForFile (fileOrIdentifier) == nullptr) | ||||
return false; | return false; | ||||
ScopedLock lock (typesArrayLock); | |||||
for (int i = types.size(); --i >= 0;) | for (int i = types.size(); --i >= 0;) | ||||
{ | { | ||||
const PluginDescription* const d = types.getUnchecked(i); | const PluginDescription* const d = types.getUnchecked(i); | ||||
@@ -103,7 +121,7 @@ void KnownPluginList::setCustomScanner (CustomScanner* newScanner) | |||||
bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | ||||
const bool dontRescanIfAlreadyInList, | const bool dontRescanIfAlreadyInList, | ||||
OwnedArray <PluginDescription>& typesFound, | |||||
OwnedArray<PluginDescription>& typesFound, | |||||
AudioPluginFormat& format) | AudioPluginFormat& format) | ||||
{ | { | ||||
const ScopedLock sl (scanLock); | const ScopedLock sl (scanLock); | ||||
@@ -113,6 +131,8 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||||
{ | { | ||||
bool needsRescanning = false; | bool needsRescanning = false; | ||||
ScopedLock lock (typesArrayLock); | |||||
for (int i = types.size(); --i >= 0;) | for (int i = types.size(); --i >= 0;) | ||||
{ | { | ||||
const PluginDescription* const d = types.getUnchecked(i); | const PluginDescription* const d = types.getUnchecked(i); | ||||
@@ -133,7 +153,7 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||||
if (blacklist.contains (fileOrIdentifier)) | if (blacklist.contains (fileOrIdentifier)) | ||||
return false; | return false; | ||||
OwnedArray <PluginDescription> found; | |||||
OwnedArray<PluginDescription> found; | |||||
{ | { | ||||
const ScopedUnlock sl2 (scanLock); | const ScopedUnlock sl2 (scanLock); | ||||
@@ -163,7 +183,7 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||||
void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager, | void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager, | ||||
const StringArray& files, | const StringArray& files, | ||||
OwnedArray <PluginDescription>& typesFound) | |||||
OwnedArray<PluginDescription>& typesFound) | |||||
{ | { | ||||
for (int i = 0; i < files.size(); ++i) | for (int i = 0; i < files.size(); ++i) | ||||
{ | { | ||||
@@ -298,12 +318,17 @@ void KnownPluginList::sort (const SortMethod method, bool forwards) | |||||
if (method != defaultOrder) | if (method != defaultOrder) | ||||
{ | { | ||||
Array<PluginDescription*> oldOrder, newOrder; | Array<PluginDescription*> oldOrder, newOrder; | ||||
oldOrder.addArray (types); | |||||
PluginSorter sorter (method, forwards); | |||||
types.sort (sorter, true); | |||||
{ | |||||
ScopedLock lock (typesArrayLock); | |||||
oldOrder.addArray (types); | |||||
newOrder.addArray (types); | |||||
PluginSorter sorter (method, forwards); | |||||
types.sort (sorter, true); | |||||
newOrder.addArray (types); | |||||
} | |||||
if (oldOrder != newOrder) | if (oldOrder != newOrder) | ||||
sendChangeMessage(); | sendChangeMessage(); | ||||
@@ -315,8 +340,12 @@ XmlElement* KnownPluginList::createXml() const | |||||
{ | { | ||||
XmlElement* const e = new XmlElement ("KNOWNPLUGINS"); | XmlElement* const e = new XmlElement ("KNOWNPLUGINS"); | ||||
for (int i = types.size(); --i >= 0;) | |||||
e->prependChildElement (types.getUnchecked(i)->createXml()); | |||||
{ | |||||
ScopedLock lock (typesArrayLock); | |||||
for (int i = types.size(); --i >= 0;) | |||||
e->prependChildElement (types.getUnchecked(i)->createXml()); | |||||
} | |||||
for (int i = 0; i < blacklist.size(); ++i) | for (int i = 0; i < blacklist.size(); ++i) | ||||
e->createNewChildElement ("BLACKLISTED")->setAttribute ("id", blacklist[i]); | e->createNewChildElement ("BLACKLISTED")->setAttribute ("id", blacklist[i]); | ||||
@@ -348,7 +377,7 @@ struct PluginTreeUtils | |||||
{ | { | ||||
enum { menuIdBase = 0x324503f4 }; | enum { menuIdBase = 0x324503f4 }; | ||||
static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array <PluginDescription*>& allPlugins) | |||||
static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array<PluginDescription*>& allPlugins) | |||||
{ | { | ||||
for (int i = 0; i < allPlugins.size(); ++i) | for (int i = 0; i < allPlugins.size(); ++i) | ||||
{ | { | ||||
@@ -392,7 +421,7 @@ struct PluginTreeUtils | |||||
} | } | ||||
static void buildTreeByCategory (KnownPluginList::PluginTree& tree, | static void buildTreeByCategory (KnownPluginList::PluginTree& tree, | ||||
const Array <PluginDescription*>& sorted, | |||||
const Array<PluginDescription*>& sorted, | |||||
const KnownPluginList::SortMethod sortMethod) | const KnownPluginList::SortMethod sortMethod) | ||||
{ | { | ||||
String lastType; | String lastType; | ||||
@@ -475,15 +504,21 @@ struct PluginTreeUtils | |||||
return false; | return false; | ||||
} | } | ||||
static void addToMenu (const KnownPluginList::PluginTree& tree, PopupMenu& m, const OwnedArray <PluginDescription>& allPlugins) | |||||
static bool addToMenu (const KnownPluginList::PluginTree& tree, PopupMenu& m, | |||||
const OwnedArray<PluginDescription>& allPlugins, | |||||
const String& currentlyTickedPluginID) | |||||
{ | { | ||||
bool isTicked = false; | |||||
for (int i = 0; i < tree.subFolders.size(); ++i) | for (int i = 0; i < tree.subFolders.size(); ++i) | ||||
{ | { | ||||
const KnownPluginList::PluginTree& sub = *tree.subFolders.getUnchecked(i); | const KnownPluginList::PluginTree& sub = *tree.subFolders.getUnchecked(i); | ||||
PopupMenu subMenu; | PopupMenu subMenu; | ||||
addToMenu (sub, subMenu, allPlugins); | |||||
m.addSubMenu (sub.folder, subMenu); | |||||
const bool isItemTicked = addToMenu (sub, subMenu, allPlugins, currentlyTickedPluginID); | |||||
isTicked = isTicked || isItemTicked; | |||||
m.addSubMenu (sub.folder, subMenu, true, nullptr, isItemTicked, 0); | |||||
} | } | ||||
for (int i = 0; i < tree.plugins.size(); ++i) | for (int i = 0; i < tree.plugins.size(); ++i) | ||||
@@ -495,16 +530,22 @@ struct PluginTreeUtils | |||||
if (containsDuplicateNames (tree.plugins, name)) | if (containsDuplicateNames (tree.plugins, name)) | ||||
name << " (" << plugin->pluginFormatName << ')'; | name << " (" << plugin->pluginFormatName << ')'; | ||||
m.addItem (allPlugins.indexOf (plugin) + menuIdBase, name, true, false); | |||||
const bool isItemTicked = plugin->matchesIdentifierString (currentlyTickedPluginID); | |||||
isTicked = isTicked || isItemTicked; | |||||
m.addItem (allPlugins.indexOf (plugin) + menuIdBase, name, true, isItemTicked); | |||||
} | } | ||||
return isTicked; | |||||
} | } | ||||
}; | }; | ||||
KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortMethod) const | KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortMethod) const | ||||
{ | { | ||||
Array <PluginDescription*> sorted; | |||||
Array<PluginDescription*> sorted; | |||||
{ | { | ||||
ScopedLock lock (typesArrayLock); | |||||
PluginSorter sorter (sortMethod, true); | PluginSorter sorter (sortMethod, true); | ||||
for (int i = 0; i < types.size(); ++i) | for (int i = 0; i < types.size(); ++i) | ||||
@@ -531,10 +572,11 @@ KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortM | |||||
} | } | ||||
//============================================================================== | //============================================================================== | ||||
void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod) const | |||||
void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod, | |||||
const String& currentlyTickedPluginID) const | |||||
{ | { | ||||
ScopedPointer<PluginTree> tree (createTree (sortMethod)); | ScopedPointer<PluginTree> tree (createTree (sortMethod)); | ||||
PluginTreeUtils::addToMenu (*tree, menu, types); | |||||
PluginTreeUtils::addToMenu (*tree, menu, types, currentlyTickedPluginID); | |||||
} | } | ||||
int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const | int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const | ||||
@@ -148,7 +148,8 @@ public: | |||||
Use getIndexChosenByMenu() to find out the type that was chosen. | Use getIndexChosenByMenu() to find out the type that was chosen. | ||||
*/ | */ | ||||
void addToMenu (PopupMenu& menu, SortMethod sortMethod) const; | |||||
void addToMenu (PopupMenu& menu, SortMethod sortMethod, | |||||
const String& currentlyTickedPluginID = String()) const; | |||||
/** Converts a menu item index that has been chosen into its index in this list. | /** Converts a menu item index that has been chosen into its index in this list. | ||||
Returns -1 if it's not an ID that was used. | Returns -1 if it's not an ID that was used. | ||||
@@ -215,7 +216,7 @@ private: | |||||
OwnedArray<PluginDescription> types; | OwnedArray<PluginDescription> types; | ||||
StringArray blacklist; | StringArray blacklist; | ||||
ScopedPointer<CustomScanner> scanner; | ScopedPointer<CustomScanner> scanner; | ||||
CriticalSection scanLock; | |||||
CriticalSection scanLock, typesArrayLock; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList) | ||||
}; | }; | ||||
@@ -34,15 +34,17 @@ PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, | |||||
AudioPluginFormat& formatToLookFor, | AudioPluginFormat& formatToLookFor, | ||||
FileSearchPath directoriesToSearch, | FileSearchPath directoriesToSearch, | ||||
const bool recursive, | const bool recursive, | ||||
const File& deadMansPedal) | |||||
const File& deadMansPedal, | |||||
bool allowPluginsWhichRequireAsynchronousInstantiation) | |||||
: list (listToAddTo), | : list (listToAddTo), | ||||
format (formatToLookFor), | format (formatToLookFor), | ||||
deadMansPedalFile (deadMansPedal), | deadMansPedalFile (deadMansPedal), | ||||
progress (0) | |||||
progress (0), | |||||
allowAsync (allowPluginsWhichRequireAsynchronousInstantiation) | |||||
{ | { | ||||
directoriesToSearch.removeRedundantPaths(); | directoriesToSearch.removeRedundantPaths(); | ||||
filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive); | |||||
filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive, allowAsync); | |||||
// If any plugins have crashed recently when being loaded, move them to the | // If any plugins have crashed recently when being loaded, move them to the | ||||
// end of the list to give the others a chance to load correctly.. | // end of the list to give the others a chance to load correctly.. | ||||
@@ -44,12 +44,11 @@ public: | |||||
@param formatToLookFor this is the type of format that you want to look for | @param formatToLookFor this is the type of format that you want to look for | ||||
@param directoriesToSearch the path to search | @param directoriesToSearch the path to search | ||||
@param searchRecursively true to search recursively | @param searchRecursively true to search recursively | ||||
@param deadMansPedalFile if this isn't File::nonexistent, then it will | |||||
be used as a file to store the names of any plugins | |||||
that crash during initialisation. If there are | |||||
any plugins listed in it, then these will always | |||||
be scanned after all other possible files have | |||||
been tried - in this way, even if there's a few | |||||
@param deadMansPedalFile if this isn't File(), then it will be used as a file | |||||
to store the names of any plugins that crash during | |||||
initialisation. If there are any plugins listed in it, | |||||
then these will always be scanned after all other possible | |||||
files have been tried - in this way, even if there's a few | |||||
dodgy plugins in your path, then a couple of rescans | dodgy plugins in your path, then a couple of rescans | ||||
will still manage to find all the proper plugins. | will still manage to find all the proper plugins. | ||||
It's probably best to choose a file in the user's | It's probably best to choose a file in the user's | ||||
@@ -57,12 +56,16 @@ public: | |||||
settings file) for this. The file format it uses | settings file) for this. The file format it uses | ||||
is just a list of filenames of the modules that | is just a list of filenames of the modules that | ||||
failed. | failed. | ||||
@param allowPluginsWhichRequireAsynchronousInstantiation | |||||
If this is false then the scanner will exclude plug-ins | |||||
asynchronous creation - such as AUv3 plug-ins. | |||||
*/ | */ | ||||
PluginDirectoryScanner (KnownPluginList& listToAddResultsTo, | PluginDirectoryScanner (KnownPluginList& listToAddResultsTo, | ||||
AudioPluginFormat& formatToLookFor, | AudioPluginFormat& formatToLookFor, | ||||
FileSearchPath directoriesToSearch, | FileSearchPath directoriesToSearch, | ||||
bool searchRecursively, | bool searchRecursively, | ||||
const File& deadMansPedalFile); | |||||
const File& deadMansPedalFile, | |||||
bool allowPluginsWhichRequireAsynchronousInstantiation = false); | |||||
/** Destructor. */ | /** Destructor. */ | ||||
~PluginDirectoryScanner(); | ~PluginDirectoryScanner(); | ||||
@@ -116,6 +119,7 @@ private: | |||||
StringArray failedFiles; | StringArray failedFiles; | ||||
Atomic<int> nextIndex; | Atomic<int> nextIndex; | ||||
float progress; | float progress; | ||||
bool allowAsync; | |||||
void updateProgress(); | void updateProgress(); | ||||
void setDeadMansPedalFile (const StringArray& newContents); | void setDeadMansPedalFile (const StringArray& newContents); | ||||
@@ -123,13 +123,15 @@ public: | |||||
//============================================================================== | //============================================================================== | ||||
PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit, | PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit, | ||||
const File& deadMansPedal, PropertiesFile* const props) | |||||
const File& deadMansPedal, PropertiesFile* const props, | |||||
bool allowPluginsWhichRequireAsynchronousInstantiation) | |||||
: formatManager (manager), | : formatManager (manager), | ||||
list (listToEdit), | list (listToEdit), | ||||
deadMansPedalFile (deadMansPedal), | deadMansPedalFile (deadMansPedal), | ||||
optionsButton ("Options..."), | optionsButton ("Options..."), | ||||
propertiesToUse (props), | propertiesToUse (props), | ||||
numThreads (0) | |||||
allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), | |||||
numThreads (allowAsync ? 1 : 0) | |||||
{ | { | ||||
tableModel = new TableModel (*this, listToEdit); | tableModel = new TableModel (*this, listToEdit); | ||||
@@ -331,18 +333,25 @@ class PluginListComponent::Scanner : private Timer | |||||
{ | { | ||||
public: | public: | ||||
Scanner (PluginListComponent& plc, AudioPluginFormat& format, PropertiesFile* properties, | Scanner (PluginListComponent& plc, AudioPluginFormat& format, PropertiesFile* properties, | ||||
int threads, const String& title, const String& text) | |||||
bool allowPluginsWhichRequireAsynchronousInstantiation, int threads, | |||||
const String& title, const String& text) | |||||
: owner (plc), formatToScan (format), propertiesToUse (properties), | : owner (plc), formatToScan (format), propertiesToUse (properties), | ||||
pathChooserWindow (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon), | |||||
pathChooserWindow (TRANS("Select folders to scan..."), String(), AlertWindow::NoIcon), | |||||
progressWindow (title, text, AlertWindow::NoIcon), | progressWindow (title, text, AlertWindow::NoIcon), | ||||
progress (0.0), numThreads (threads), finished (false) | |||||
progress (0.0), numThreads (threads), allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), | |||||
finished (false) | |||||
{ | { | ||||
FileSearchPath path (formatToScan.getDefaultLocationsToSearch()); | FileSearchPath path (formatToScan.getDefaultLocationsToSearch()); | ||||
// You need to use at least one thread when scanning plug-ins asynchronously | |||||
jassert (! allowAsync || (numThreads > 0)); | |||||
if (path.getNumPaths() > 0) // if the path is empty, then paths aren't used for this format. | if (path.getNumPaths() > 0) // if the path is empty, then paths aren't used for this format. | ||||
{ | { | ||||
#if ! JUCE_IOS | |||||
if (propertiesToUse != nullptr) | if (propertiesToUse != nullptr) | ||||
path = getLastSearchPath (*propertiesToUse, formatToScan); | path = getLastSearchPath (*propertiesToUse, formatToScan); | ||||
#endif | |||||
pathList.setSize (500, 300); | pathList.setSize (500, 300); | ||||
pathList.setPath (path); | pathList.setPath (path); | ||||
@@ -381,7 +390,7 @@ private: | |||||
String pluginBeingScanned; | String pluginBeingScanned; | ||||
double progress; | double progress; | ||||
int numThreads; | int numThreads; | ||||
bool finished; | |||||
bool allowAsync, finished; | |||||
ScopedPointer<ThreadPool> pool; | ScopedPointer<ThreadPool> pool; | ||||
static void startScanCallback (int result, AlertWindow* alert, Scanner* scanner) | static void startScanCallback (int result, AlertWindow* alert, Scanner* scanner) | ||||
@@ -413,7 +422,7 @@ private: | |||||
+ TRANS ("Are you sure you want to scan the folder \"XYZ\"?") | + TRANS ("Are you sure you want to scan the folder \"XYZ\"?") | ||||
.replace ("XYZ", f.getFullPathName()), | .replace ("XYZ", f.getFullPathName()), | ||||
TRANS ("Scan"), | TRANS ("Scan"), | ||||
String::empty, | |||||
String(), | |||||
nullptr, | nullptr, | ||||
ModalCallbackFunction::create (warnAboutStupidPathsCallback, this)); | ModalCallbackFunction::create (warnAboutStupidPathsCallback, this)); | ||||
return; | return; | ||||
@@ -465,7 +474,7 @@ private: | |||||
pathChooserWindow.setVisible (false); | pathChooserWindow.setVisible (false); | ||||
scanner = new PluginDirectoryScanner (owner.list, formatToScan, pathList.getPath(), | scanner = new PluginDirectoryScanner (owner.list, formatToScan, pathList.getPath(), | ||||
true, owner.deadMansPedalFile); | |||||
true, owner.deadMansPedalFile, allowAsync); | |||||
if (propertiesToUse != nullptr) | if (propertiesToUse != nullptr) | ||||
{ | { | ||||
@@ -545,7 +554,7 @@ private: | |||||
void PluginListComponent::scanFor (AudioPluginFormat& format) | void PluginListComponent::scanFor (AudioPluginFormat& format) | ||||
{ | { | ||||
currentScanner = new Scanner (*this, format, propertiesToUse, numThreads, | |||||
currentScanner = new Scanner (*this, format, propertiesToUse, allowAsync, numThreads, | |||||
dialogTitle.isNotEmpty() ? dialogTitle : TRANS("Scanning for plug-ins..."), | dialogTitle.isNotEmpty() ? dialogTitle : TRANS("Scanning for plug-ins..."), | ||||
dialogText.isNotEmpty() ? dialogText : TRANS("Searching for all possible plug-in files...")); | dialogText.isNotEmpty() ? dialogText : TRANS("Searching for all possible plug-in files...")); | ||||
} | } | ||||
@@ -47,7 +47,8 @@ public: | |||||
PluginListComponent (AudioPluginFormatManager& formatManager, | PluginListComponent (AudioPluginFormatManager& formatManager, | ||||
KnownPluginList& listToRepresent, | KnownPluginList& listToRepresent, | ||||
const File& deadMansPedalFile, | const File& deadMansPedalFile, | ||||
PropertiesFile* propertiesToUse); | |||||
PropertiesFile* propertiesToUse, | |||||
bool allowPluginsWhichRequireAsynchronousInstantiation = false); | |||||
/** Destructor. */ | /** Destructor. */ | ||||
~PluginListComponent(); | ~PluginListComponent(); | ||||
@@ -60,8 +61,10 @@ public: | |||||
const String& textForProgressWindowDescription); | const String& textForProgressWindowDescription); | ||||
/** Sets how many threads to simultaneously scan for plugins. | /** Sets how many threads to simultaneously scan for plugins. | ||||
If this is 0, then all scanning happens on the message thread (this is the default) | |||||
*/ | |||||
If this is 0, then all scanning happens on the message thread (this is the default when | |||||
allowPluginsWhichRequireAsynchronousInstantiation is false). If | |||||
allowPluginsWhichRequireAsynchronousInstantiation is true then numThreads must not | |||||
be zero (it is one by default). */ | |||||
void setNumberOfThreadsForScanning (int numThreads); | void setNumberOfThreadsForScanning (int numThreads); | ||||
/** Returns the last search path stored in a given properties file for the specified format. */ | /** Returns the last search path stored in a given properties file for the specified format. */ | ||||
@@ -96,6 +99,7 @@ private: | |||||
TextButton optionsButton; | TextButton optionsButton; | ||||
PropertiesFile* propertiesToUse; | PropertiesFile* propertiesToUse; | ||||
String dialogTitle, dialogText; | String dialogTitle, dialogText; | ||||
bool allowAsync; | |||||
int numThreads; | int numThreads; | ||||
class TableModel; | class TableModel; | ||||