@@ -2,8 +2,8 @@ | |||
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") | |||
@@ -60,6 +60,7 @@ | |||
// misc | |||
#define JUCE_DISABLE_JUCE_VERSION_PRINTING 1 | |||
#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1 | |||
#define JUCE_STANDALONE_APPLICATION 0 | |||
#define JUCE_STRING_UTF_TYPE 8 | |||
#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. | |||
*/ | |||
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()) | |||
{ | |||
@@ -204,10 +204,11 @@ namespace FloatVectorHelpers | |||
typedef float Type; | |||
typedef float32x4_t ParallelType; | |||
typedef uint32x4_t IntegerType; | |||
union signMaskUnion { ParallelType f; IntegerType i; }; | |||
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 loadA (const Type* v) noexcept { return vld1q_f32 (v); } | |||
@@ -235,10 +236,11 @@ namespace FloatVectorHelpers | |||
typedef double Type; | |||
typedef double ParallelType; | |||
typedef uint64 IntegerType; | |||
union signMaskUnion { ParallelType f; IntegerType i; }; | |||
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 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_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 | |||
template<int typeSize> struct ModeType { typedef BasicOps32 Mode; }; | |||
template<> struct ModeType<8> { typedef BasicOps64 Mode; }; | |||
@@ -481,6 +486,17 @@ namespace FloatVectorHelpers | |||
#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 | |||
{ | |||
@@ -568,10 +584,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double amount, int | |||
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 | |||
vDSP_vsadd (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||
vDSP_vsadd (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); | |||
#else | |||
JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | |||
JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
@@ -579,10 +595,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float am | |||
#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 | |||
vDSP_vsaddD (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||
vDSP_vsaddD (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); | |||
#else | |||
JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | |||
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 | |||
vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num); | |||
#else | |||
union { float f; uint32 i; } signMask; | |||
FloatVectorHelpers::signMask32 signMask; | |||
signMask.i = 0x7fffffffUL; | |||
JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabsf (src[i]), Mode::bit_and (s, mask), | |||
JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
@@ -810,7 +826,7 @@ void FloatVectorOperations::abs (double* dest, const double* src, int num) noexc | |||
#if JUCE_USE_VDSP_FRAMEWORK | |||
vDSP_vabsD ((double*) src, 1, dest, 1, (vDSP_Length) num); | |||
#else | |||
union {double d; uint64 i;} signMask; | |||
FloatVectorHelpers::signMask64 signMask; | |||
signMask.i = 0x7fffffffffffffffULL; | |||
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 | |||
{ | |||
#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 | |||
#endif | |||
} | |||
@@ -66,10 +66,10 @@ public: | |||
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. */ | |||
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. */ | |||
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. */ | |||
static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | |||
@@ -22,7 +22,6 @@ | |||
============================================================================== | |||
*/ | |||
struct CatmullRomAlgorithm | |||
{ | |||
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. | |||
@@ -62,33 +62,131 @@ IIRCoefficients::IIRCoefficients (double c1, double c2, double c3, | |||
IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | |||
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 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, | |||
c1 * 2.0, | |||
c1, | |||
1.0, | |||
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, | |||
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 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, | |||
c1 * -2.0, | |||
c1, | |||
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, | |||
@@ -96,8 +194,9 @@ IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, | |||
const double Q, | |||
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 aminus1 = A - 1.0; | |||
@@ -120,8 +219,9 @@ IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate, | |||
const double Q, | |||
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 aminus1 = A - 1.0; | |||
@@ -140,15 +240,16 @@ IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate, | |||
} | |||
IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, | |||
const double centreFrequency, | |||
const double frequency, | |||
const double Q, | |||
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 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 c2 = -2.0 * std::cos (omega); | |||
const double alphaTimesA = alpha * A; | |||
@@ -164,12 +265,12 @@ IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, | |||
//============================================================================== | |||
IIRFilter::IIRFilter() noexcept | |||
: v1 (0), v2 (0), active (false) | |||
: v1 (0.0), v2 (0.0), active (false) | |||
{ | |||
} | |||
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); | |||
coefficients = other.coefficients; | |||
@@ -198,7 +299,7 @@ void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcep | |||
void IIRFilter::reset() noexcept | |||
{ | |||
const SpinLock::ScopedLockType sl (processLock); | |||
v1 = v2 = 0; | |||
v1 = v2 = 0.0; | |||
} | |||
float IIRFilter::processSingleSampleRaw (const float in) noexcept | |||
@@ -55,14 +55,53 @@ public: | |||
/** Destructor. */ | |||
~IIRCoefficients() noexcept; | |||
//============================================================================== | |||
/** Returns the coefficients for a low-pass filter. */ | |||
static IIRCoefficients makeLowPass (double sampleRate, | |||
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. */ | |||
static IIRCoefficients makeHighPass (double sampleRate, | |||
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. | |||
@@ -22,7 +22,6 @@ | |||
============================================================================== | |||
*/ | |||
/** | |||
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: | |||
/** Constructor. */ | |||
@@ -59,7 +59,6 @@ public: | |||
countdown = 0; | |||
} | |||
//============================================================================== | |||
/** Set a new target value. */ | |||
void setValue (FloatType newValue) noexcept | |||
{ | |||
@@ -75,7 +74,6 @@ public: | |||
} | |||
} | |||
//============================================================================== | |||
/** Compute the next value. */ | |||
FloatType getNextValue() noexcept | |||
{ | |||
@@ -87,6 +85,18 @@ public: | |||
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: | |||
//============================================================================== | |||
FloatType currentValue, target, step; | |||
@@ -31,6 +31,8 @@ | |||
#error "Incorrect use of JUCE cpp file" | |||
#endif | |||
#include "AppConfig.h" | |||
#include "juce_audio_basics.h" | |||
#if JUCE_MINGW && ! defined (__SSE2__) | |||
@@ -67,6 +69,13 @@ | |||
#define JUCE_USE_ARM_NEON 1 | |||
#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 | |||
#include <arm_neon.h> | |||
#endif | |||
@@ -76,6 +85,7 @@ namespace juce | |||
#include "buffers/juce_AudioDataConverters.cpp" | |||
#include "buffers/juce_FloatVectorOperations.cpp" | |||
#include "buffers/juce_AudioChannelSet.cpp" | |||
#include "effects/juce_IIRFilter.cpp" | |||
#include "effects/juce_IIRFilterOld.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 | |||
#define JUCE_AUDIO_BASICS_H_INCLUDED | |||
#include "../juce_core/juce_core.h" | |||
#include "juce_core/juce_core.h" | |||
//============================================================================== | |||
namespace juce | |||
{ | |||
@@ -37,6 +62,7 @@ namespace juce | |||
#include "buffers/juce_AudioDataConverters.h" | |||
#include "buffers/juce_FloatVectorOperations.h" | |||
#include "buffers/juce_AudioSampleBuffer.h" | |||
#include "buffers/juce_AudioChannelSet.h" | |||
#include "effects/juce_Decibels.h" | |||
#include "effects/juce_IIRFilter.h" | |||
#include "effects/juce_IIRFilterOld.h" | |||
@@ -248,7 +248,7 @@ bool MidiFile::readFrom (InputStream& sourceStream) | |||
clear(); | |||
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) | |||
if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | |||
@@ -33,11 +33,23 @@ namespace MidiHelpers | |||
{ | |||
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 | |||
: 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) | |||
: timeStamp (t), | |||
size (dataSize) | |||
: timeStamp (t), size (dataSize) | |||
{ | |||
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 | |||
: timeStamp (t), size (1) | |||
{ | |||
preallocatedData.asBytes[0] = (uint8) byte1; | |||
packedData.asBytes[0] = (uint8) byte1; | |||
// check that the length matches the data.. | |||
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 | |||
: 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.. | |||
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 | |||
: 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.. | |||
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) | |||
: 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 | |||
{ | |||
preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||
} | |||
packedData.allocatedData = other.packedData.allocatedData; | |||
} | |||
MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) | |||
: 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 | |||
{ | |||
preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||
} | |||
packedData.allocatedData = other.packedData.allocatedData; | |||
} | |||
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 | |||
{ | |||
preallocatedData.asInt32 = 0; | |||
size = getMessageLengthFromFirstByte ((uint8) byte); | |||
preallocatedData.asBytes[0] = (uint8) byte; | |||
packedData.asBytes[0] = (uint8) byte; | |||
if (size > 1) | |||
{ | |||
preallocatedData.asBytes[1] = src[0]; | |||
packedData.asBytes[1] = src[0]; | |||
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 | |||
{ | |||
preallocatedData.asInt32 = 0; | |||
packedData.allocatedData = nullptr; | |||
size = 0; | |||
} | |||
} | |||
@@ -255,19 +255,25 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& 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 | |||
{ | |||
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; | |||
@@ -277,36 +283,61 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& other) | |||
MidiMessage::MidiMessage (MidiMessage&& other) noexcept | |||
: 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 | |||
{ | |||
jassert (this != &other); // shouldn't be possible | |||
packedData.allocatedData = other.packedData.allocatedData; | |||
timeStamp = other.timeStamp; | |||
size = other.size; | |||
allocatedData.swapWith (other.allocatedData); | |||
preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||
other.size = 0; | |||
return *this; | |||
} | |||
#endif | |||
MidiMessage::~MidiMessage() {} | |||
MidiMessage::~MidiMessage() noexcept | |||
{ | |||
if (isHeapAllocated()) | |||
std::free (packedData.allocatedData); | |||
} | |||
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 | |||
@@ -391,7 +422,7 @@ float MidiMessage::getFloatVelocity() const noexcept | |||
void MidiMessage::setVelocity (const float newVelocity) noexcept | |||
{ | |||
if (isNoteOnOrOff()) | |||
getData()[2] = MidiHelpers::floatVelocityToByte (newVelocity); | |||
getData()[2] = floatValueToMidiByte (newVelocity); | |||
} | |||
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 | |||
{ | |||
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 | |||
@@ -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 | |||
{ | |||
return noteOff (channel, noteNumber, MidiHelpers::floatVelocityToByte (velocity)); | |||
return noteOff (channel, noteNumber, floatValueToMidiByte (velocity)); | |||
} | |||
MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) noexcept | |||
@@ -697,9 +728,10 @@ MidiMessage MidiMessage::textMetaEvent (int type, StringRef text) | |||
header[--n] = 0xff; | |||
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 + headerLen, text.text.getAddress(), textSize); | |||
@@ -816,7 +848,7 @@ bool MidiMessage::isKeySignatureMetaEvent() const noexcept | |||
int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const noexcept | |||
{ | |||
return (int) getMetaEventData()[0]; | |||
return (int) (int8) getMetaEventData()[0]; | |||
} | |||
bool MidiMessage::isKeySignatureMajorKey() const noexcept | |||
@@ -985,7 +1017,7 @@ String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctav | |||
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); | |||
} | |||
@@ -104,7 +104,7 @@ public: | |||
MidiMessage (const MidiMessage&, double newTimeStamp); | |||
/** Destructor. */ | |||
~MidiMessage(); | |||
~MidiMessage() noexcept; | |||
/** Copies this message from another one. */ | |||
MidiMessage& operator= (const MidiMessage& other); | |||
@@ -118,13 +118,19 @@ public: | |||
/** Returns a pointer to the raw midi data. | |||
@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. | |||
@see getRawData | |||
*/ | |||
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. | |||
@@ -845,7 +851,7 @@ public: | |||
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. | |||
@@ -872,7 +878,7 @@ public: | |||
The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. | |||
@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. */ | |||
static bool isMidiNoteBlack (int noteNumber) noexcept; | |||
@@ -899,21 +905,29 @@ public: | |||
*/ | |||
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: | |||
//============================================================================== | |||
double timeStamp; | |||
HeapBlock<uint8> allocatedData; | |||
int size; | |||
#ifndef DOXYGEN | |||
union | |||
union PackedData | |||
{ | |||
uint8 asBytes[4]; | |||
uint32 asInt32; | |||
} preallocatedData; | |||
uint8* allocatedData; | |||
uint8 asBytes[sizeof (uint8*)]; | |||
}; | |||
PackedData packedData; | |||
double timeStamp; | |||
int size; | |||
#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); | |||
}; | |||
@@ -80,7 +80,7 @@ int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const noexcep | |||
return -1; | |||
} | |||
int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const noexcept | |||
int MidiMessageSequence::getIndexOf (const MidiEventHolder* const event) const noexcept | |||
{ | |||
return list.indexOf (event); | |||
} | |||
@@ -48,6 +48,18 @@ public: | |||
/** Replaces this sequence with another one. */ | |||
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. */ | |||
~MidiMessageSequence(); | |||
@@ -109,7 +121,7 @@ public: | |||
int getIndexOfMatchingKeyUp (int index) const noexcept; | |||
/** 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. | |||
If the time is beyond the end of the sequence, this will return the | |||
@@ -22,7 +22,6 @@ | |||
============================================================================== | |||
*/ | |||
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) | |||
{ | |||
} | |||
@@ -39,6 +39,9 @@ MPEInstrument::MPEInstrument() noexcept | |||
pressureDimension.value = &MPENote::pressure; | |||
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.pitchbendRange = 2; | |||
legacyMode.channelRange = Range<int> (1, 17); | |||
@@ -271,22 +274,6 @@ void MPEInstrument::handleTimbreLSB (int midiChannel, int value) noexcept | |||
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, | |||
int midiNoteNumber, | |||
@@ -298,9 +285,9 @@ void MPEInstrument::noteOn (int midiChannel, | |||
MPENote newNote (midiChannel, | |||
midiNoteNumber, | |||
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); | |||
const ScopedLock sl (lock); | |||
@@ -324,7 +311,7 @@ void MPEInstrument::noteOff (int midiChannel, | |||
int midiNoteNumber, | |||
MPEValue midiNoteOffVelocity) | |||
{ | |||
if (notes.empty() || ! isNoteChannel (midiChannel)) | |||
if (notes.isEmpty() || ! isNoteChannel (midiChannel)) | |||
return; | |||
const ScopedLock sl (lock); | |||
@@ -334,10 +321,11 @@ void MPEInstrument::noteOff (int midiChannel, | |||
note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off; | |||
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: | |||
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) | |||
{ | |||
@@ -370,12 +358,20 @@ void MPEInstrument::timbre (int midiChannel, MPEValue 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) | |||
{ | |||
dimension.lastValueReceivedOnChannel[midiChannel - 1] = value; | |||
if (notes.empty()) | |||
if (notes.isEmpty()) | |||
return; | |||
if (MPEZone* zone = zoneLayout.getZoneByMasterChannel (midiChannel)) | |||
@@ -758,7 +754,7 @@ public: | |||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
expectEquals (test.getNumPlayingNotes(), 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 | |||
test.noteOff (3, 60, MPEValue::from7BitInt (33)); | |||
@@ -774,13 +770,13 @@ public: | |||
// note off with non-matching note number shouldn't do anything | |||
test.noteOff (3, 61, MPEValue::from7BitInt (33)); | |||
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); | |||
// note off with non-matching midi channel shouldn't do anything | |||
test.noteOff (2, 60, MPEValue::from7BitInt (33)); | |||
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); | |||
} | |||
{ | |||
@@ -791,9 +787,9 @@ public: | |||
test.noteOn (3, 1, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 2, MPEValue::from7BitInt (100)); | |||
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. | |||
@@ -802,7 +798,7 @@ public: | |||
test.noteOn (3, 0, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 0, MPEValue::from7BitInt (60)); | |||
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. | |||
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); | |||
// sustain pedal on non-zone channel shouldn't do anything either. | |||
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); | |||
// sustain pedal on master channel should sustain notes on *that* zone. | |||
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); | |||
// release | |||
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); | |||
// should also sustain new notes added after the press | |||
test.sustainPedal (2, true); | |||
expectEquals (test.noteKeyStateChangedCallCounter, 3); | |||
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); | |||
// ...but only if that sustain came on the master channel of that zone! | |||
test.sustainPedal (11, true); | |||
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)); | |||
expectEquals (test.noteReleasedCallCounter, 1); | |||
@@ -890,8 +886,8 @@ public: | |||
expectEquals (test.getNumPlayingNotes(), 2); | |||
expectEquals (test.noteReleasedCallCounter, 2); | |||
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 | |||
test.sustainPedal (2, false); | |||
@@ -908,26 +904,26 @@ public: | |||
// sostenuto pedal on per-note channel shouldn't do anything. | |||
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); | |||
// sostenuto pedal on non-zone channel shouldn't do anything either. | |||
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); | |||
// sostenuto pedal on master channel should sustain notes on *that* zone. | |||
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); | |||
// release | |||
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); | |||
// should only sustain notes turned on *before* the press (difference to sustain pedal) | |||
@@ -935,9 +931,9 @@ public: | |||
expectEquals (test.noteKeyStateChangedCallCounter, 3); | |||
test.noteOn (4, 51, MPEValue::from7BitInt (100)); | |||
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); | |||
// 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 (10, 60, MPEValue::from7BitInt (100)); // not affected! | |||
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.noteKeyStateChangedCallCounter, 4); | |||
@@ -1047,22 +1043,22 @@ public: | |||
// applying pressure on a per-note channel should modulate one note | |||
test.pressure (3, MPEValue::from7BitInt (33)); | |||
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); | |||
// applying pressure on a master channel should modulate all notes in this zone | |||
test.pressure (2, MPEValue::from7BitInt (44)); | |||
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 (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
expectEquals (test.notePressureChangedCallCounter, 3); | |||
// applying pressure on an unrelated channel should be ignored | |||
test.pressure (1, MPEValue::from7BitInt (55)); | |||
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 (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
expectEquals (test.notePressureChangedCallCounter, 3); | |||
} | |||
{ | |||
@@ -1073,7 +1069,7 @@ public: | |||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
expectEquals (test.notePressureChangedCallCounter, 1); | |||
} | |||
@@ -1091,6 +1087,49 @@ public: | |||
expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); | |||
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"); | |||
@@ -1105,9 +1144,9 @@ public: | |||
// applying pitchbend on a per-note channel should modulate one note | |||
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); | |||
// 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 | |||
// because the total pitchbend for those notes has changed | |||
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); | |||
// applying pitchbend on an unrelated channel should do nothing. | |||
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); | |||
} | |||
{ | |||
@@ -1135,8 +1174,8 @@ public: | |||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
} | |||
{ | |||
@@ -1150,7 +1189,7 @@ public: | |||
test.noteOff (3, 61, MPEValue::from7BitInt (100)); | |||
test.pitchbend (3, MPEValue::from14BitInt (5555)); | |||
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); | |||
} | |||
{ | |||
@@ -1169,14 +1208,14 @@ public: | |||
test.sustainPedal (2, true); | |||
test.noteOff (3, 60, MPEValue::from7BitInt (64)); | |||
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); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
test.pitchbend (3, MPEValue::from14BitInt (6666)); | |||
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); | |||
} | |||
{ | |||
@@ -1193,11 +1232,11 @@ public: | |||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
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.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 | |||
@@ -1289,23 +1328,23 @@ public: | |||
// modulating timbre on a per-note channel should modulate one note | |||
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); | |||
// modulating timbre on a master channel should modulate all notes in this zone | |||
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); | |||
// modulating timbre on an unrelated channel should be ignored | |||
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); | |||
} | |||
{ | |||
@@ -1316,8 +1355,8 @@ public: | |||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
} | |||
{ | |||
@@ -1331,7 +1370,7 @@ public: | |||
test.noteOff (3, 61, MPEValue::from7BitInt (100)); | |||
test.timbre (3, MPEValue::from7BitInt (77)); | |||
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); | |||
} | |||
{ | |||
@@ -1341,11 +1380,11 @@ public: | |||
// Zsolt's edge case for timbre | |||
test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
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.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, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
expectEquals (test.notePressureChangedCallCounter, 1); | |||
} | |||
@@ -1377,8 +1416,8 @@ public: | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
test.pressure (3, MPEValue::from7BitInt (99)); | |||
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); | |||
} | |||
{ | |||
@@ -1391,9 +1430,9 @@ public: | |||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||
expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||
expectEquals (test.notePressureChangedCallCounter, 1); | |||
} | |||
{ | |||
@@ -1425,9 +1464,9 @@ public: | |||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
} | |||
{ | |||
@@ -1440,9 +1479,9 @@ public: | |||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
} | |||
{ | |||
@@ -1455,9 +1494,9 @@ public: | |||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
} | |||
{ | |||
@@ -1470,9 +1509,9 @@ public: | |||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
} | |||
} | |||
@@ -1489,9 +1528,9 @@ public: | |||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
} | |||
{ | |||
@@ -1504,9 +1543,9 @@ public: | |||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
} | |||
{ | |||
@@ -1519,9 +1558,9 @@ public: | |||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
} | |||
{ | |||
@@ -1534,9 +1573,9 @@ public: | |||
test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
} | |||
} | |||
@@ -1724,7 +1763,7 @@ public: | |||
expectEquals (test.getNumPlayingNotes(), 0); | |||
} | |||
beginTest ("default getInitial...ForNoteOn"); | |||
beginTest ("default initial values for pitchbend and timbre"); | |||
{ | |||
MPEInstrument test; | |||
test.setZoneLayout (testLayout); | |||
@@ -1739,16 +1778,7 @@ public: | |||
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"); | |||
@@ -1806,10 +1836,10 @@ public: | |||
test.pressure (2, MPEValue::from7BitInt (88)); | |||
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 (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 | |||
@@ -1835,10 +1865,10 @@ public: | |||
test.noteOn (16, 60, MPEValue::from7BitInt (100)); | |||
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 | |||
@@ -1851,9 +1881,9 @@ public: | |||
test.noteOn (1, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (1, 61, MPEValue::from7BitInt (100)); | |||
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; | |||
@@ -1864,9 +1894,9 @@ public: | |||
test.noteOn (1, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (1, 61, MPEValue::from7BitInt (100)); | |||
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; | |||
@@ -1877,9 +1907,9 @@ public: | |||
test.noteOn (1, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (1, 61, MPEValue::from7BitInt (100)); | |||
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; | |||
@@ -1890,9 +1920,9 @@ public: | |||
test.noteOn (1, 62, MPEValue::from7BitInt (100)); | |||
test.noteOn (1, 61, MPEValue::from7BitInt (100)); | |||
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)); | |||
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); | |||
expectEquals (test.getNumPlayingNotes(), 0); | |||
@@ -1939,7 +1969,7 @@ public: | |||
test.noteOff (2, 61, MPEValue::from7BitInt (100)); | |||
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); | |||
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, | |||
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. */ | |||
void setLegacyModePitchbendRange (int pitchbendRange); | |||
protected: | |||
//============================================================================== | |||
/** This method defines what initial pitchbend value should be used for newly | |||
triggered notes. The default is to use the last pitchbend value | |||
that has been received on the same MIDI channel (or no pitchbend | |||
if no pitchbend messages have been received so far). | |||
Override this method if you need different behaviour. | |||
*/ | |||
virtual MPEValue getInitialPitchbendForNoteOn (int midiChannel, | |||
int midiNoteNumber, | |||
MPEValue midiNoteOnVelocity) const; | |||
/** This method defines what initial pressure value should be used for newly | |||
triggered notes. The default is to re-use the note-on velocity value. | |||
Override this method if you need different behaviour. | |||
*/ | |||
virtual MPEValue getInitialPressureForNoteOn (int midiChannel, | |||
int midiNoteNumber, | |||
MPEValue midiNoteOnVelocity) const; | |||
/** This method defines what initial timbre value should be used for newly | |||
triggered notes. The default is to use the last timbre value that has | |||
that has been received on the same MIDI channel (or a neutral centred value | |||
if no pitchbend messages have been received so far). | |||
Override this method if you need different behaviour. | |||
*/ | |||
virtual MPEValue getInitialTimbreForNoteOn (int midiChannel, | |||
int midiNoteNumber, | |||
MPEValue midiNoteOnVelocity) const; | |||
private: | |||
//============================================================================== | |||
CriticalSection lock; | |||
@@ -384,6 +354,7 @@ private: | |||
void updateDimensionMaster (MPEZone&, MPEDimension&, MPEValue); | |||
void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | |||
void callListenersDimensionChanged (MPENote&, MPEDimension&); | |||
MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const; | |||
void processMidiNoteOnMessage (const MidiMessage&); | |||
void processMidiNoteOffMessage (const MidiMessage&); | |||
@@ -22,7 +22,6 @@ | |||
============================================================================== | |||
*/ | |||
MidiBuffer MPEMessages::addZone (MPEZone zone) | |||
{ | |||
MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(), | |||
@@ -25,7 +25,8 @@ | |||
MPESynthesiserBase::MPESynthesiserBase() | |||
: instrument (new MPEInstrument), | |||
sampleRate (0), | |||
minimumSubBlockSize (32) | |||
minimumSubBlockSize (32), | |||
subBlockSubdivisionIsStrict (false) | |||
{ | |||
instrument->addListener (this); | |||
} | |||
@@ -100,10 +101,11 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||
MidiBuffer::Iterator midiIterator (inputMidi); | |||
midiIterator.setNextSamplePosition (startSample); | |||
bool firstEvent = true; | |||
int midiEventPos; | |||
MidiMessage m; | |||
const ScopedLock sl (renderAudioLock); | |||
const ScopedLock sl (noteStateLock); | |||
while (numSamples > 0) | |||
{ | |||
@@ -122,12 +124,14 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||
break; | |||
} | |||
if (samplesToNextMidiMessage < minimumSubBlockSize) | |||
if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||
{ | |||
handleMidiEvent (m); | |||
continue; | |||
} | |||
firstEvent = false; | |||
renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage); | |||
handleMidiEvent (m); | |||
startSample += samplesToNextMidiMessage; | |||
@@ -147,15 +151,16 @@ void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) | |||
{ | |||
if (sampleRate != newRate) | |||
{ | |||
const ScopedLock sl (renderAudioLock); | |||
const ScopedLock sl (noteStateLock); | |||
instrument->releaseAllNotes(); | |||
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 | |||
minimumSubBlockSize = numSamples; | |||
subBlockSubdivisionIsStrict = shouldBeStrict; | |||
} |
@@ -127,8 +127,14 @@ public: | |||
The default setting is 32, which means that midi messages are accurate to about < 1ms | |||
accuracy, which is probably fine for most purposes, but you may want to increase or | |||
decrease this value for your synth. | |||
If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||
If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||
to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||
(this can sometimes help to avoid quantisation or phasing issues). | |||
*/ | |||
void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; | |||
void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||
//============================================================================== | |||
/** Puts the synthesiser into legacy mode. | |||
@@ -179,13 +185,13 @@ protected: | |||
//============================================================================== | |||
/** @internal */ | |||
ScopedPointer<MPEInstrument> instrument; | |||
/** @internal */ | |||
CriticalSection renderAudioLock; | |||
private: | |||
//============================================================================== | |||
CriticalSection noteStateLock; | |||
double sampleRate; | |||
int minimumSubBlockSize; | |||
bool subBlockSubdivisionIsStrict; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) | |||
}; | |||
@@ -69,27 +69,35 @@ struct JUCE_API MPEZone | |||
int perNotePitchbendRange = 48, | |||
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; | |||
/** Returns the number of note channels occupied by this zone. */ | |||
int getNumNoteChannels() const noexcept; | |||
/* Returns the MIDI channel number of the lowest-numbered note channel of this zone. */ | |||
/* Returns the MIDI channel number (in the range 1-16) of the | |||
lowest-numbered note channel of this zone. | |||
*/ | |||
int getFirstNoteChannel() const noexcept; | |||
/* Returns the MIDI channel number 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; | |||
/** 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; | |||
/** 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; | |||
/** 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; | |||
/** 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) | |||
{ | |||
zones = other.zones; | |||
listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); | |||
return *this; | |||
} | |||
@@ -100,8 +100,9 @@ public: | |||
/** Returns the current number of MPE zones. */ | |||
int getNumZones() const noexcept; | |||
/** Returns a pointer to the MPE zone at the given index, | |||
or nullptr if there is no such zone. | |||
/** 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; | |||
@@ -26,7 +26,8 @@ BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, | |||
TimeSliceThread& thread, | |||
const bool deleteSourceWhenDeleted, | |||
const int bufferSizeSamples, | |||
const int numChannels) | |||
const int numChannels, | |||
bool prefillBufferOnPrepareToPlay) | |||
: source (s, deleteSourceWhenDeleted), | |||
backgroundThread (thread), | |||
numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)), | |||
@@ -36,7 +37,8 @@ BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, | |||
nextPlayPos (0), | |||
sampleRate (0), | |||
wasSourceLooping (false), | |||
isPrepared (false) | |||
isPrepared (false), | |||
prefillBuffer (prefillBufferOnPrepareToPlay) | |||
{ | |||
jassert (source != nullptr); | |||
@@ -73,12 +75,13 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne | |||
backgroundThread.addTimeSliceClient (this); | |||
while (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, | |||
buffer.getNumSamples() / 2)) | |||
do | |||
{ | |||
backgroundThread.moveToFrontOfQueue (this); | |||
Thread::sleep (5); | |||
} | |||
while (prefillBuffer | |||
&& (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2))); | |||
} | |||
} | |||
@@ -88,7 +91,12 @@ void BufferingAudioSource::releaseResources() | |||
backgroundThread.removeTimeSliceClient (this); | |||
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) | |||
@@ -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 | |||
{ | |||
jassert (source->getTotalLength() > 0); | |||
@@ -241,6 +284,8 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
bufferValidEnd = newBVE; | |||
} | |||
bufferReadyEvent.signal(); | |||
return true; | |||
} | |||
@@ -43,21 +43,24 @@ public: | |||
//============================================================================== | |||
/** 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, | |||
TimeSliceThread& backgroundThread, | |||
bool deleteSourceWhenDeleted, | |||
int numberOfSamplesToBuffer, | |||
int numberOfChannels = 2); | |||
int numberOfChannels = 2, | |||
bool prefillBufferOnPrepareToPlay = true); | |||
/** Destructor. | |||
@@ -89,6 +92,12 @@ public: | |||
/** Implements the PositionableAudioSource method. */ | |||
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: | |||
//============================================================================== | |||
OptionalScopedPointer<PositionableAudioSource> source; | |||
@@ -96,9 +105,10 @@ private: | |||
int numberOfSamplesToBuffer, numberOfChannels; | |||
AudioSampleBuffer buffer; | |||
CriticalSection bufferStartPosLock; | |||
WaitableEvent bufferReadyEvent; | |||
int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; | |||
double volatile sampleRate; | |||
bool wasSourceLooping, isPrepared; | |||
bool wasSourceLooping, isPrepared, prefillBuffer; | |||
bool readNextBufferChunk(); | |||
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. | |||
@see AudioSource | |||
@see AudioSource, LagrangeInterpolator, CatmullRomInterpolator | |||
*/ | |||
class JUCE_API ResamplingAudioSource : public AudioSource | |||
{ | |||
@@ -88,6 +88,7 @@ Synthesiser::Synthesiser() | |||
: sampleRate (0), | |||
lastNoteOnCounter (0), | |||
minimumSubBlockSize (32), | |||
subBlockSubdivisionIsStrict (false), | |||
shouldStealNotes (true) | |||
{ | |||
for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | |||
@@ -147,10 +148,11 @@ void Synthesiser::setNoteStealingEnabled (const bool 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 | |||
minimumSubBlockSize = numSamples; | |||
subBlockSubdivisionIsStrict = shouldBeStrict; | |||
} | |||
//============================================================================== | |||
@@ -177,10 +179,12 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||
{ | |||
// must set the sample rate before using this! | |||
jassert (sampleRate != 0); | |||
const int targetChannels = outputAudio.getNumChannels(); | |||
MidiBuffer::Iterator midiIterator (midiData); | |||
midiIterator.setNextSamplePosition (startSample); | |||
bool firstEvent = true; | |||
int midiEventPos; | |||
MidiMessage m; | |||
@@ -190,7 +194,9 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||
{ | |||
if (! midiIterator.getNextEvent (m, midiEventPos)) | |||
{ | |||
renderVoices (outputAudio, startSample, numSamples); | |||
if (targetChannels > 0) | |||
renderVoices (outputAudio, startSample, numSamples); | |||
return; | |||
} | |||
@@ -198,18 +204,24 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||
if (samplesToNextMidiMessage >= numSamples) | |||
{ | |||
renderVoices (outputAudio, startSample, numSamples); | |||
if (targetChannels > 0) | |||
renderVoices (outputAudio, startSample, numSamples); | |||
handleMidiEvent (m); | |||
break; | |||
} | |||
if (samplesToNextMidiMessage < minimumSubBlockSize) | |||
if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||
{ | |||
handleMidiEvent (m); | |||
continue; | |||
} | |||
renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||
firstEvent = false; | |||
if (targetChannels > 0) | |||
renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||
handleMidiEvent (m); | |||
startSample += samplesToNextMidiMessage; | |||
numSamples -= samplesToNextMidiMessage; | |||
@@ -539,8 +539,14 @@ public: | |||
The default setting is 32, which means that midi messages are accurate to about < 1ms | |||
accuracy, which is probably fine for most purposes, but you may want to increase or | |||
decrease this value for your synth. | |||
If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||
If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||
to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||
(this can sometimes help to avoid quantisation or phasing issues). | |||
*/ | |||
void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; | |||
void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||
protected: | |||
//============================================================================== | |||
@@ -615,6 +621,7 @@ private: | |||
double sampleRate; | |||
uint32 lastNoteOnCounter; | |||
int minimumSubBlockSize; | |||
bool subBlockSubdivisionIsStrict; | |||
bool shouldStealNotes; | |||
BigInteger sustainPedalsDown; | |||
@@ -86,72 +86,12 @@ private: | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) | |||
}; | |||
//============================================================================== | |||
// This is an AudioTransportSource which will own it's assigned source | |||
struct AudioSourceOwningTransportSource : public AudioTransportSource | |||
{ | |||
AudioSourceOwningTransportSource (PositionableAudioSource* s) : source (s) | |||
{ | |||
AudioTransportSource::setSource (s); | |||
} | |||
~AudioSourceOwningTransportSource() | |||
{ | |||
setSource (nullptr); | |||
} | |||
private: | |||
ScopedPointer<PositionableAudioSource> source; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource) | |||
}; | |||
//============================================================================== | |||
// An AudioSourcePlayer which will remove itself from the AudioDeviceManager's | |||
// callback list once it finishes playing its source | |||
struct AutoRemovingSourcePlayer : public AudioSourcePlayer, | |||
private Timer | |||
{ | |||
AutoRemovingSourcePlayer (AudioDeviceManager& dm, AudioTransportSource* ts, bool ownSource) | |||
: manager (dm), transportSource (ts, ownSource) | |||
{ | |||
jassert (ts != nullptr); | |||
manager.addAudioCallback (this); | |||
AudioSourcePlayer::setSource (transportSource); | |||
startTimerHz (10); | |||
} | |||
~AutoRemovingSourcePlayer() | |||
{ | |||
setSource (nullptr); | |||
manager.removeAudioCallback (this); | |||
} | |||
void timerCallback() override | |||
{ | |||
if (getCurrentSource() == nullptr || ! transportSource->isPlaying()) | |||
delete this; | |||
} | |||
void audioDeviceStopped() override | |||
{ | |||
AudioSourcePlayer::audioDeviceStopped(); | |||
setSource (nullptr); | |||
} | |||
private: | |||
AudioDeviceManager& manager; | |||
OptionalScopedPointer<AudioTransportSource> transportSource; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer) | |||
}; | |||
//============================================================================== | |||
AudioDeviceManager::AudioDeviceManager() | |||
: numInputChansNeeded (0), | |||
numOutputChansNeeded (2), | |||
listNeedsScanning (true), | |||
inputLevel (0), | |||
testSoundPosition (0), | |||
cpuUsageMs (0), | |||
timeToCpuScale (0) | |||
{ | |||
@@ -162,10 +102,6 @@ AudioDeviceManager::~AudioDeviceManager() | |||
{ | |||
currentAudioDevice = nullptr; | |||
defaultMidiOutput = nullptr; | |||
for (int i = 0; i < callbacks.size(); ++i) | |||
if (AutoRemovingSourcePlayer* p = dynamic_cast<AutoRemovingSourcePlayer*> (callbacks.getUnchecked(i))) | |||
delete p; | |||
} | |||
//============================================================================== | |||
@@ -346,8 +282,8 @@ String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, | |||
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.outputChannels.parseString (xml.getStringAttribute ("audioDeviceOutChans", "11"), 2); | |||
@@ -650,6 +586,8 @@ void AudioDeviceManager::stopDevice() | |||
{ | |||
if (currentAudioDevice != nullptr) | |||
currentAudioDevice->stop(); | |||
testSound = nullptr; | |||
} | |||
void AudioDeviceManager::closeAudioDevice() | |||
@@ -761,31 +699,8 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||
{ | |||
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) | |||
{ | |||
@@ -821,6 +736,20 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||
for (int i = 0; i < numOutputChannels; ++i) | |||
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) | |||
@@ -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(); | |||
/** 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. | |||
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. | |||
Obviously while this is locked, you're blocking the audio thread from running, so | |||
@@ -502,8 +452,6 @@ private: | |||
BigInteger inputChannels, outputChannels; | |||
ScopedPointer<XmlElement> lastExplicitSettings; | |||
mutable bool listNeedsScanning; | |||
Atomic<int> inputLevelMeasurementEnabledCount; | |||
double inputLevel; | |||
AudioSampleBuffer tempBuffer; | |||
struct MidiCallbackInfo | |||
@@ -520,8 +468,24 @@ private: | |||
ScopedPointer<MidiOutput> defaultMidiOutput; | |||
CriticalSection audioCallbackLock, midiCallbackLock; | |||
ScopedPointer<AudioSampleBuffer> testSound; | |||
int testSoundPosition; | |||
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; | |||
friend class CallbackHandler; | |||
@@ -31,6 +31,8 @@ | |||
#error "Incorrect use of JUCE cpp file" | |||
#endif | |||
#include "AppConfig.h" | |||
#define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | |||
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | |||
#define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | |||
@@ -45,7 +47,6 @@ | |||
#define Component CarbonDummyCompName | |||
#import <CoreAudio/AudioHardware.h> | |||
#import <CoreMIDI/MIDIServices.h> | |||
#import <DiscRecording/DiscRecording.h> | |||
#import <AudioToolbox/AudioServices.h> | |||
#undef Point | |||
#undef Component | |||
@@ -55,10 +56,14 @@ | |||
#import <AVFoundation/AVFoundation.h> | |||
#import <CoreMIDI/MIDIServices.h> | |||
#if TARGET_OS_SIMULATOR | |||
#import <CoreMIDI/MIDINetworkSession.h> | |||
#endif | |||
//============================================================================== | |||
#elif JUCE_WINDOWS | |||
#if JUCE_WASAPI | |||
#include <MMReg.h> | |||
#include <mmreg.h> | |||
#endif | |||
#if JUCE_ASIO | |||
@@ -84,15 +89,6 @@ | |||
#include <iasiodrv.h> | |||
#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 | |||
#if JUCE_ALSA | |||
@@ -139,7 +135,6 @@ namespace juce | |||
#include "audio_io/juce_AudioIODeviceType.cpp" | |||
#include "midi_io/juce_MidiMessageCollector.cpp" | |||
#include "midi_io/juce_MidiOutput.cpp" | |||
#include "audio_cd/juce_AudioCDReader.cpp" | |||
#include "sources/juce_AudioSourcePlayer.cpp" | |||
#include "sources/juce_AudioTransportSource.cpp" | |||
#include "native/juce_MidiDataConcatenator.h" | |||
@@ -149,14 +144,6 @@ namespace juce | |||
#include "native/juce_mac_CoreAudio.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 | |||
#include "native/juce_ios_Audio.cpp" | |||
@@ -179,14 +166,6 @@ namespace juce | |||
#include "native/juce_win32_ASIO.cpp" | |||
#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 | |||
#if JUCE_ALSA | |||
@@ -199,10 +178,6 @@ namespace juce | |||
#include "native/juce_linux_JackAudio.cpp" | |||
#endif | |||
#if JUCE_USE_CDREADER | |||
#include "native/juce_linux_AudioCDReader.cpp" | |||
#endif | |||
//============================================================================== | |||
#elif JUCE_ANDROID | |||
#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 | |||
#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 | |||
@@ -90,21 +117,6 @@ | |||
#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 | |||
{ | |||
@@ -117,8 +129,6 @@ namespace juce | |||
#include "midi_io/juce_MidiOutput.h" | |||
#include "sources/juce_AudioSourcePlayer.h" | |||
#include "sources/juce_AudioTransportSource.h" | |||
#include "audio_cd/juce_AudioCDBurner.h" | |||
#include "audio_cd/juce_AudioCDReader.h" | |||
#include "audio_io/juce_AudioDeviceManager.h" | |||
} | |||
@@ -71,8 +71,7 @@ public: | |||
// the normal message, handle it now.. | |||
if (*d >= 0xf8 && *d <= 0xfe) | |||
{ | |||
const MidiMessage m (*d++, time); | |||
callback.handleIncomingMidiMessage (input, m); | |||
callback.handleIncomingMidiMessage (input, MidiMessage (*d++, time)); | |||
--numBytes; | |||
} | |||
else | |||
@@ -83,7 +82,15 @@ public: | |||
data[len++] = *d++; | |||
--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; | |||
} | |||
} | |||
@@ -153,7 +153,7 @@ JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceM | |||
class AndroidMidiDeviceManager | |||
{ | |||
public: | |||
AndroidMidiDeviceManager () | |||
AndroidMidiDeviceManager() | |||
: deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager)) | |||
{ | |||
} | |||
@@ -332,7 +332,7 @@ private: | |||
void run() override | |||
{ | |||
setThreadToAudioPriority (); | |||
setThreadToAudioPriority(); | |||
if (recorder != nullptr) recorder->start(); | |||
if (player != nullptr) player->start(); | |||
@@ -341,7 +341,7 @@ private: | |||
processBuffers(); | |||
} | |||
void setThreadToAudioPriority () | |||
void setThreadToAudioPriority() | |||
{ | |||
// see android.os.Process.THREAD_PRIORITY_AUDIO | |||
const int THREAD_PRIORITY_AUDIO = -16; | |||
@@ -287,7 +287,14 @@ public: | |||
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, | |||
const BigInteger& outputChannelsWanted, | |||
@@ -431,6 +438,8 @@ public: | |||
void handleStatusChange (bool enabled, const char* reason) | |||
{ | |||
const ScopedLock myScopedLock (callbackLock); | |||
JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason); | |||
isRunning = enabled; | |||
@@ -447,6 +456,8 @@ public: | |||
void handleRouteChange (const char* reason) | |||
{ | |||
const ScopedLock myScopedLock (callbackLock); | |||
JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason); | |||
fixAudioRouteIfSetToReceiver(); | |||
@@ -518,9 +529,9 @@ private: | |||
if (audioInputIsAvailable && numInputChannels > 0) | |||
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()) | |||
prepareFloatBuffers ((int) numFrames); | |||
@@ -679,7 +690,23 @@ private: | |||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format)); | |||
AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format)); | |||
UInt32 framesPerSlice; | |||
UInt32 dataSize = sizeof (framesPerSlice); | |||
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; | |||
} | |||
@@ -755,8 +782,24 @@ void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) c | |||
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 |
@@ -183,7 +183,7 @@ public: | |||
// open output ports | |||
const StringArray outputChannels (getOutputChannelNames()); | |||
for (int i = 0; i < outputChannels.size (); ++i) | |||
for (int i = 0; i < outputChannels.size(); ++i) | |||
{ | |||
String outputName; | |||
outputName << "out_" << ++totalNumberOfOutputChannels; | |||
@@ -290,6 +290,8 @@ public: | |||
} | |||
} | |||
updateActivePorts(); | |||
return lastError; | |||
} | |||
@@ -45,37 +45,17 @@ class AlsaClient : public ReferenceCountedObject | |||
public: | |||
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; } | |||
void setName (const String& name) | |||
{ | |||
snd_seq_set_client_name (handle, name.toUTF8()); | |||
} | |||
void registerCallback (AlsaPortAndCallback* cb) | |||
{ | |||
if (cb != nullptr) | |||
@@ -103,7 +83,8 @@ public: | |||
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; } | |||
@@ -114,12 +95,56 @@ private: | |||
Array<AlsaPortAndCallback*> activeCallbacks; | |||
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 | |||
{ | |||
public: | |||
MidiInputThread (AlsaClient& c) | |||
: Thread ("Juce MIDI Input"), client (c) | |||
: Thread ("Juce MIDI Input"), client (c), concatenator (2048) | |||
{ | |||
jassert (client.input && client.get() != nullptr); | |||
} | |||
@@ -159,13 +184,9 @@ private: | |||
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); | |||
} | |||
@@ -180,29 +201,14 @@ private: | |||
private: | |||
AlsaClient& client; | |||
MidiDataConcatenator concatenator; | |||
}; | |||
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 | |||
@@ -282,6 +288,11 @@ public: | |||
callback->handleIncomingMidiMessage (midiInput, message); | |||
} | |||
void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp) | |||
{ | |||
callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp); | |||
} | |||
private: | |||
AlsaPort port; | |||
MidiInput* midiInput; | |||
@@ -291,14 +302,22 @@ private: | |||
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); | |||
if (AlsaPortAndCallback* const cb = activeCallbacks[port]) | |||
if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port]) | |||
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, | |||
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_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) | |||
{ | |||
const int sourcePort = snd_seq_port_info_get_port (portInfo); | |||
const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||
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); | |||
} | |||
} | |||
@@ -355,7 +378,7 @@ static AlsaPort iterateMidiDevices (const bool forInput, | |||
const int deviceIndexToOpen) | |||
{ | |||
AlsaPort port; | |||
const AlsaClient::Ptr client (globalAlsaSequencer (forInput)); | |||
const AlsaClient::Ptr client (AlsaClient::getInstance (forInput)); | |||
if (snd_seq_t* const seqHandle = client->get()) | |||
{ | |||
@@ -387,19 +410,6 @@ static AlsaPort iterateMidiDevices (const bool forInput, | |||
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 | |||
@@ -450,7 +460,7 @@ public: | |||
numBytes -= 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_direct (&event); | |||
@@ -507,8 +517,11 @@ MidiOutput* MidiOutput::openDevice (int deviceIndex) | |||
MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||
{ | |||
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()) | |||
{ | |||
@@ -584,8 +597,11 @@ MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | |||
MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||
{ | |||
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()) | |||
{ | |||
@@ -149,6 +149,7 @@ bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool mute) { return SystemVol | |||
struct CoreAudioClasses | |||
{ | |||
class CoreAudioIODeviceType; | |||
class CoreAudioIODevice; | |||
//============================================================================== | |||
@@ -691,7 +692,7 @@ public: | |||
{ 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;) | |||
{ | |||
Thread::sleep (50); | |||
@@ -847,6 +848,11 @@ private: | |||
intern->deviceDetailsChanged(); | |||
break; | |||
case kAudioObjectPropertyOwnedObjects: | |||
intern->stop (false); | |||
intern->owner.deviceType.triggerAsyncAudioDeviceListChange(); | |||
break; | |||
case kAudioDevicePropertyBufferSizeRange: | |||
case kAudioDevicePropertyVolumeScalar: | |||
case kAudioDevicePropertyMute: | |||
@@ -902,10 +908,12 @@ private: | |||
class CoreAudioIODevice : public AudioIODevice | |||
{ | |||
public: | |||
CoreAudioIODevice (const String& deviceName, | |||
CoreAudioIODevice (CoreAudioIODeviceType& dt, | |||
const String& deviceName, | |||
AudioDeviceID inputDeviceId, const int inputIndex_, | |||
AudioDeviceID outputDeviceId, const int outputIndex_) | |||
: AudioIODevice (deviceName, "CoreAudio"), | |||
deviceType (dt), | |||
inputIndex (inputIndex_), | |||
outputIndex (outputIndex_), | |||
isOpen_ (false), | |||
@@ -1060,6 +1068,12 @@ public: | |||
return lastError; | |||
} | |||
void audioDeviceListChanged() | |||
{ | |||
deviceType.audioDeviceListChanged(); | |||
} | |||
CoreAudioIODeviceType& deviceType; | |||
int inputIndex, outputIndex; | |||
private: | |||
@@ -1745,7 +1759,8 @@ private: | |||
//============================================================================== | |||
class CoreAudioIODeviceType : public AudioIODeviceType | |||
class CoreAudioIODeviceType : public AudioIODeviceType, | |||
private AsyncUpdater | |||
{ | |||
public: | |||
CoreAudioIODeviceType() | |||
@@ -1771,7 +1786,7 @@ public: | |||
} | |||
//============================================================================== | |||
void scanForDevices() | |||
void scanForDevices() override | |||
{ | |||
hasScanned = true; | |||
@@ -1827,7 +1842,7 @@ public: | |||
outputDeviceNames.appendNumbersToDuplicates (false, true); | |||
} | |||
StringArray getDeviceNames (bool wantInputNames) const | |||
StringArray getDeviceNames (bool wantInputNames) const override | |||
{ | |||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||
@@ -1835,7 +1850,7 @@ public: | |||
: outputDeviceNames; | |||
} | |||
int getDefaultDeviceIndex (bool forInput) const | |||
int getDefaultDeviceIndex (bool forInput) const override | |||
{ | |||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||
@@ -1870,7 +1885,7 @@ public: | |||
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 | |||
@@ -1894,10 +1909,10 @@ public: | |||
return -1; | |||
} | |||
bool hasSeparateInputsAndOutputs() const { return true; } | |||
bool hasSeparateInputsAndOutputs() const override { return true; } | |||
AudioIODevice* createDevice (const String& outputDeviceName, | |||
const String& inputDeviceName) | |||
const String& inputDeviceName) override | |||
{ | |||
jassert (hasScanned); // need to call scanForDevices() before doing this | |||
@@ -1913,15 +1928,15 @@ public: | |||
String combinedName (outputDeviceName.isEmpty() ? inputDeviceName : outputDeviceName); | |||
if (inputDeviceID == outputDeviceID) | |||
return new CoreAudioIODevice (combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); | |||
return new CoreAudioIODevice (*this, combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); | |||
ScopedPointer<CoreAudioIODevice> in, out; | |||
if (inputDeviceID != 0) | |||
in = new CoreAudioIODevice (inputDeviceName, inputDeviceID, inputIndex, 0, -1); | |||
in = new CoreAudioIODevice (*this, inputDeviceName, inputDeviceID, inputIndex, 0, -1); | |||
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 (out == nullptr) return in.release(); | |||
@@ -1932,6 +1947,17 @@ public: | |||
return combo.release(); | |||
} | |||
void audioDeviceListChanged() | |||
{ | |||
scanForDevices(); | |||
callDeviceChangeListeners(); | |||
} | |||
void triggerAsyncAudioDeviceListChange() | |||
{ | |||
triggerAsyncUpdate(); | |||
} | |||
//============================================================================== | |||
private: | |||
StringArray inputDeviceNames, outputDeviceNames; | |||
@@ -1969,18 +1995,17 @@ private: | |||
return total; | |||
} | |||
void audioDeviceListChanged() | |||
{ | |||
scanForDevices(); | |||
callDeviceChangeListeners(); | |||
} | |||
static OSStatus hardwareListenerProc (AudioDeviceID, UInt32, const AudioObjectPropertyAddress*, void* clientData) | |||
{ | |||
static_cast<CoreAudioIODeviceType*> (clientData)->audioDeviceListChanged(); | |||
return noErr; | |||
} | |||
void handleAsyncUpdate() override | |||
{ | |||
audioDeviceListChanged(); | |||
} | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODeviceType) | |||
}; | |||
@@ -171,6 +171,10 @@ namespace CoreMidiHelpers | |||
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() | |||
: MIDIGetNumberOfDestinations(); | |||
StringArray s; | |||
@@ -216,6 +220,14 @@ namespace CoreMidiHelpers | |||
// correctly when called from the message thread! | |||
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; | |||
name.cfString = getGlobalMidiClientName().toCFString(); | |||
CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | |||
@@ -365,17 +365,18 @@ public: | |||
void updateSampleRates() | |||
{ | |||
// find a list of sample rates.. | |||
const int possibleSampleRates[] = { 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; | |||
Array<double> newRates; | |||
if (asioObject != nullptr) | |||
{ | |||
const int possibleSampleRates[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; | |||
for (int index = 0; index < numElementsInArray (possibleSampleRates); ++index) | |||
if (asioObject->canSampleRate ((double) possibleSampleRates[index]) == 0) | |||
newRates.add ((double) possibleSampleRates[index]); | |||
} | |||
if (newRates.size() == 0) | |||
if (newRates.isEmpty()) | |||
{ | |||
double cr = getSampleRate(); | |||
JUCE_ASIO_LOG ("No sample rates supported - current rate: " + String ((int) cr)); | |||
@@ -1571,7 +1572,7 @@ private: | |||
DWORD dsize = sizeof (pathName); | |||
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.. | |||
ok = (pathName[0] != 0); | |||
@@ -96,11 +96,18 @@ bool check (HRESULT hr) | |||
} | |||
#if JUCE_MINGW | |||
#define JUCE_COMCLASS(name, guid) \ | |||
struct name; \ | |||
template<> struct UUIDGetter<name> { static CLSID get() { return uuidFromString (guid); } }; \ | |||
struct name | |||
#ifdef __uuidof | |||
#undef __uuidof | |||
#endif | |||
#define __uuidof(cls) UUIDGetter<cls>::get() | |||
struct PROPERTYKEY | |||
{ | |||
GUID fmtid; | |||
@@ -60,7 +60,7 @@ public: | |||
The source passed in will not be deleted by this object, so must be managed by | |||
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 | |||
is zero, no reading ahead will be done; if it's | |||
greater than zero, a BufferingAudioSource will be used | |||
@@ -41,11 +41,14 @@ | |||
* 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" | |||
#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) */ | |||
#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_DECLARATION(x) x | |||
#else | |||
@@ -34,7 +34,10 @@ | |||
#define FLAC__CALLBACK_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 | |||
* | |||
@@ -39,15 +39,7 @@ | |||
#ifndef 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__ | |||
#include <sys/types.h> /* for off_t */ | |||
#define FLAC__off_t __int64 /* use this instead of off_t to fix the 2 GB limit */ | |||
#if !defined __MINGW32__ | |||
#define fseeko _fseeki64 | |||
@@ -62,11 +54,6 @@ | |||
#define FLAC__off_t off_t | |||
#endif | |||
#if HAVE_INTTYPES_H | |||
#define __STDC_FORMAT_MACROS | |||
#include <inttypes.h> | |||
#endif | |||
#if defined(_MSC_VER) | |||
#define strtoll _strtoi64 | |||
#define strtoull _strtoui64 | |||
@@ -95,33 +82,13 @@ | |||
#define FLAC__STRNCASECMP strncasecmp | |||
#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 | |||
/* Visual Studio 2010 has decent C99 support */ | |||
# include <stdint.h> | |||
# define PRIu64 "llu" | |||
# define PRId64 "lld" | |||
# define PRIx64 "llx" | |||
# else | |||
# include <limits.h> | |||
# ifndef UINT32_MAX | |||
# define UINT32_MAX _UI32_MAX | |||
# endif | |||
@@ -51,7 +51,9 @@ static inline unsigned short __builtin_bswap16(unsigned short a) | |||
#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_32(x) (bswap_32 (x)) | |||
@@ -33,7 +33,6 @@ | |||
#ifndef FLAC__METADATA_H | |||
#define FLAC__METADATA_H | |||
#include <sys/types.h> /* for off_t */ | |||
#include "export.h" | |||
#include "callback.h" | |||
#include "format.h" | |||
@@ -33,7 +33,6 @@ | |||
#ifndef FLAC__STREAM_DECODER_H | |||
#define FLAC__STREAM_DECODER_H | |||
#include <stdio.h> /* for FILE */ | |||
#include "export.h" | |||
#include "format.h" | |||
@@ -33,7 +33,6 @@ | |||
#ifndef FLAC__STREAM_ENCODER_H | |||
#define FLAC__STREAM_ENCODER_H | |||
#include <stdio.h> /* for FILE */ | |||
#include "export.h" | |||
#include "format.h" | |||
#include "stream_decoder.h" | |||
@@ -38,11 +38,6 @@ | |||
extern "C" { | |||
#endif | |||
#include <stdio.h> | |||
#include <sys/stat.h> | |||
#include <stdarg.h> | |||
#include <windows.h> | |||
int get_utf8_argv(int *argc, char ***argv); | |||
int printf_utf8(const char *format, ...); | |||
@@ -668,13 +668,14 @@ public: | |||
//============================================================================== | |||
bool write (const int** data, int numSamples) override | |||
{ | |||
jassert (numSamples >= 0); | |||
jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel! | |||
if (writeFailed) | |||
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) | |||
{ | |||
@@ -695,13 +696,10 @@ public: | |||
writeFailed = true; | |||
return false; | |||
} | |||
else | |||
{ | |||
bytesWritten += bytes; | |||
lengthInSamples += (uint64) numSamples; | |||
return true; | |||
} | |||
bytesWritten += bytes; | |||
lengthInSamples += (uint64) numSamples; | |||
return true; | |||
} | |||
private: | |||
@@ -24,17 +24,79 @@ | |||
#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 | |||
{ | |||
#if JUCE_INCLUDE_FLAC_CODE || ! defined (JUCE_INCLUDE_FLAC_CODE) | |||
#undef VERSION | |||
#define VERSION "1.2.1" | |||
#define VERSION "1.3.1" | |||
#define FLAC__NO_DLL 1 | |||
#if JUCE_MSVC | |||
#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 | |||
#if JUCE_MAC | |||
@@ -66,6 +128,7 @@ namespace FlacNamespace | |||
#define __STDC_LIMIT_MACROS 1 | |||
#define flac_max jmax | |||
#define flac_min jmin | |||
#undef DEBUG // (some flac code dumps debug trace if the app defines this macro) | |||
#include "flac/all.h" | |||
#include "flac/libFLAC/bitmath.c" | |||
#include "flac/libFLAC/bitreader.c" | |||
@@ -324,7 +387,8 @@ class FlacWriter : public AudioFormatWriter | |||
{ | |||
public: | |||
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; | |||
encoder = FLAC__stream_encoder_new(); | |||
@@ -432,7 +496,7 @@ public: | |||
packUint32 ((FLAC__uint32) info.total_samples, buffer + 14, 4); | |||
memcpy (buffer + 18, info.md5sum, 16); | |||
const bool seekOk = output->setPosition (4); | |||
const bool seekOk = output->setPosition (streamStartPos + 4); | |||
ignoreUnused (seekOk); | |||
// if this fails, you've given it an output stream that can't seek! It needs | |||
@@ -482,6 +546,7 @@ public: | |||
private: | |||
FlacNamespace::FLAC__StreamEncoder* encoder; | |||
int64 streamStartPos; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacWriter) | |||
}; | |||
@@ -222,7 +222,7 @@ public: | |||
DisposeMovie (movie); | |||
#if JUCE_MAC | |||
ExitMoviesOnThread (); | |||
ExitMoviesOnThread(); | |||
#endif | |||
} | |||
} | |||
@@ -656,7 +656,10 @@ namespace WavFileHelpers | |||
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) | |||
{ | |||
@@ -664,7 +667,8 @@ namespace WavFileHelpers | |||
{ | |||
MemoryBlock mb; | |||
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; | |||
} | |||
} | |||
@@ -1258,7 +1262,7 @@ public: | |||
if (writeFailed) | |||
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); | |||
switch (bitsPerSample) | |||
@@ -166,6 +166,9 @@ public: | |||
checkCoInitialiseCalled(); | |||
clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, | |||
startSampleInFile, numSamples, lengthInSamples); | |||
const int stride = numChannels * sizeof (int16); | |||
while (numSamples > 0) | |||
@@ -297,7 +300,7 @@ private: | |||
sampleRate = inputFormat->nSamplesPerSec; | |||
numChannels = inputFormat->nChannels; | |||
bitsPerSample = inputFormat->wBitsPerSample; | |||
bitsPerSample = inputFormat->wBitsPerSample != 0 ? inputFormat->wBitsPerSample : 16; | |||
lengthInSamples = (lengthInNanoseconds * (int) sampleRate) / 10000000; | |||
} | |||
} | |||
@@ -31,6 +31,8 @@ | |||
#error "Incorrect use of JUCE cpp file" | |||
#endif | |||
#include "AppConfig.h" | |||
#define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | |||
#define JUCE_CORE_INCLUDE_JNI_HELPERS 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 | |||
#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 | |||
@@ -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() {} | |||
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. | |||
@see AudioFormatManager | |||
@see AudioPluginFormatManager | |||
*/ | |||
class JUCE_API AudioPluginFormat | |||
{ | |||
public: | |||
//============================================================================== | |||
struct JUCE_API InstantiationCompletionCallback | |||
{ | |||
virtual ~InstantiationCompletionCallback() {} | |||
virtual void completionCallback (AudioPluginInstance* instance, const String& error) = 0; | |||
JUCE_LEAK_DETECTOR (InstantiationCompletionCallback) | |||
}; | |||
//============================================================================== | |||
/** Destructor. */ | |||
virtual ~AudioPluginFormat(); | |||
@@ -58,11 +67,36 @@ public: | |||
const String& fileOrIdentifier) = 0; | |||
/** 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 | |||
this format. | |||
@@ -82,7 +116,7 @@ public: | |||
It doesn't actually need to load it, just to check whether the file or component | |||
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. */ | |||
virtual bool canScanForPlugins() const = 0; | |||
@@ -90,9 +124,17 @@ public: | |||
/** 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 | |||
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, | |||
bool recursive) = 0; | |||
bool recursive, | |||
bool allowPluginsWhichRequireAsynchronousInstantiation = false) = 0; | |||
/** Returns the typical places to look for this kind of plugin. | |||
@@ -103,8 +145,24 @@ public: | |||
protected: | |||
//============================================================================== | |||
friend class AudioPluginFormatManager; | |||
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) | |||
}; | |||
@@ -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() {} | |||
@@ -32,15 +65,15 @@ void AudioPluginFormatManager::addDefaultFormats() | |||
// you should only call this method once! | |||
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); | |||
#endif | |||
#if JUCE_PLUGINHOST_VST3 | |||
#if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) | |||
jassert (dynamic_cast<VST3PluginFormat*> (formats[i]) == nullptr); | |||
#endif | |||
#if JUCE_PLUGINHOST_AU && JUCE_MAC | |||
#if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | |||
jassert (dynamic_cast<AudioUnitPluginFormat*> (formats[i]) == nullptr); | |||
#endif | |||
@@ -50,15 +83,15 @@ void AudioPluginFormatManager::addDefaultFormats() | |||
} | |||
#endif | |||
#if JUCE_PLUGINHOST_AU && JUCE_MAC | |||
#if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | |||
formats.add (new AudioUnitPluginFormat()); | |||
#endif | |||
#if JUCE_PLUGINHOST_VST | |||
#if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) | |||
formats.add (new VSTPluginFormat()); | |||
#endif | |||
#if JUCE_PLUGINHOST_VST3 | |||
#if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) | |||
formats.add (new VST3PluginFormat()); | |||
#endif | |||
@@ -85,12 +118,55 @@ void AudioPluginFormatManager::addFormat (AudioPluginFormat* const format) | |||
AudioPluginInstance* AudioPluginFormatManager::createPluginInstance (const PluginDescription& description, double rate, | |||
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) | |||
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; | |||
} | |||
@@ -75,12 +75,51 @@ public: | |||
If it can't load the plugin, it returns nullptr and leaves a message in the | |||
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, | |||
double initialSampleRate, | |||
int initialBufferSize, | |||
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. | |||
(This won't try to load the plugin) | |||
@@ -89,6 +128,9 @@ public: | |||
private: | |||
//============================================================================== | |||
//@internal | |||
AudioPluginFormat* findFormatForDescription (const PluginDescription&, String& errorMessage) const; | |||
OwnedArray<AudioPluginFormat> formats; | |||
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"; } | |||
void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | |||
AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc, double, int) override; | |||
bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | |||
String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) 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; | |||
FileSearchPath getDefaultLocationsToSearch() override; | |||
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; | |||
//============================================================================== | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginFormat) | |||
}; | |||
@@ -55,8 +63,10 @@ private: | |||
#endif | |||
//============================================================================== | |||
#if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 | |||
enum | |||
{ | |||
/** 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; | |||
if (fileMightContainThisPluginType (desc.fileOrIdentifier)) | |||
{ | |||
File file (desc.fileOrIdentifier); | |||
@@ -639,7 +642,17 @@ AudioPluginInstance* LADSPAPluginFormat::createInstanceFromDescription (const Pl | |||
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) | |||
@@ -663,7 +676,7 @@ bool LADSPAPluginFormat::doesPluginStillExist (const PluginDescription& desc) | |||
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; | |||
@@ -31,22 +31,28 @@ | |||
class JUCE_API LADSPAPluginFormat : public AudioPluginFormat | |||
{ | |||
public: | |||
//============================================================================== | |||
LADSPAPluginFormat(); | |||
~LADSPAPluginFormat(); | |||
//============================================================================== | |||
String getName() const override { return "LADSPA"; } | |||
void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | |||
AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, double, int) override; | |||
bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | |||
String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) 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; | |||
FileSearchPath getDefaultLocationsToSearch() override; | |||
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: | |||
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::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); | |||
} | |||
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()); | |||
} | |||
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()); | |||
} | |||
@@ -146,22 +146,22 @@ static inline Steinberg::Vst::Speaker getSpeakerType (AudioChannelSet::ChannelTy | |||
case AudioChannelSet::left: return kSpeakerL; | |||
case AudioChannelSet::right: return kSpeakerR; | |||
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::topFrontCentre: return kSpeakerTfc; | |||
case AudioChannelSet::topFrontRight: return kSpeakerTfr; | |||
case AudioChannelSet::topRearLeft: return kSpeakerTrl; | |||
case AudioChannelSet::topRearCentre: return kSpeakerTrc; | |||
case AudioChannelSet::topRearRight: return kSpeakerTrr; | |||
case AudioChannelSet::subbass2: return kSpeakerLfe2; | |||
case AudioChannelSet::LFE2: return kSpeakerLfe2; | |||
default: break; | |||
} | |||
@@ -177,30 +177,49 @@ static inline AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::Speak | |||
case kSpeakerL: return AudioChannelSet::left; | |||
case kSpeakerR: return AudioChannelSet::right; | |||
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 kSpeakerTfc: return AudioChannelSet::topFrontCentre; | |||
case kSpeakerTfr: return AudioChannelSet::topFrontRight; | |||
case kSpeakerTrl: return AudioChannelSet::topRearLeft; | |||
case kSpeakerTrc: return AudioChannelSet::topRearCentre; | |||
case kSpeakerTrr: return AudioChannelSet::topRearRight; | |||
case kSpeakerLfe2: return AudioChannelSet::subbass2; | |||
case kSpeakerLfe2: return AudioChannelSet::LFE2; | |||
default: break; | |||
} | |||
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; | |||
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 | |||
{ | |||
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; | |||
for (Steinberg::Vst::Speaker speaker = 1; speaker <= Steinberg::Vst::kSpeakerRcs; speaker <<= 1) | |||
@@ -462,12 +500,12 @@ struct VST3BufferExchange | |||
vstBuffers.silenceFlags = 0; | |||
} | |||
static void mapArrangementToBusses (int& channelIndexOffset, int index, | |||
static void mapArrangementToBuses (int& channelIndexOffset, int index, | |||
Array<Steinberg::Vst::AudioBusBuffers>& result, | |||
BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, | |||
BusMap& busMapToUse, const AudioChannelSet& arrangement, | |||
AudioBuffer<FloatType>& source) | |||
{ | |||
const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); | |||
const int numChansForBus = arrangement.size(); | |||
if (index >= result.size()) | |||
result.add (Steinberg::Vst::AudioBusBuffers()); | |||
@@ -483,26 +521,26 @@ struct VST3BufferExchange | |||
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) | |||
{ | |||
int channelIndexOffset = 0; | |||
for (int i = 0; i < arrangements.size(); ++i) | |||
mapArrangementToBusses (channelIndexOffset, i, result, busMapToUse, | |||
mapArrangementToBuses (channelIndexOffset, i, result, busMapToUse, | |||
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, | |||
BusMap& busMapToUse, bool isInput, int numBusses, | |||
BusMap& busMapToUse, bool isInput, int numBuses, | |||
AudioBuffer<FloatType>& source) | |||
{ | |||
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, | |||
getArrangementForBus (&processor, isInput, i), | |||
source); | |||
@@ -47,12 +47,15 @@ | |||
#pragma clang diagnostic ignored "-Wdelete-non-virtual-dtor" | |||
#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 | |||
visit the Steinberg website and agree to whatever is currently required to | |||
get them. | |||
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. | |||
*/ | |||
#if JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY | |||
@@ -78,6 +81,7 @@ | |||
#include <pluginterfaces/vst/ivstunits.h> | |||
#include <pluginterfaces/vst/ivstmidicontrollers.h> | |||
#include <public.sdk/source/common/memorystream.h> | |||
#include <public.sdk/source/vst/vsteditcontroller.h> | |||
#else | |||
#if JUCE_MINGW | |||
#define _set_abort_behavior(...) | |||
@@ -128,6 +132,10 @@ namespace Steinberg | |||
#pragma clang diagnostic pop | |||
#endif | |||
#if JUCE_WINDOWS | |||
#include <windows.h> | |||
#endif | |||
//============================================================================== | |||
#undef ASSERT | |||
#undef WARNING | |||
@@ -25,11 +25,12 @@ | |||
#ifndef 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. | |||
*/ | |||
class JUCE_API VST3PluginFormat : public AudioPluginFormat | |||
class JUCE_API VST3PluginFormat : public AudioPluginFormat | |||
{ | |||
public: | |||
/** Constructor */ | |||
@@ -39,32 +40,27 @@ public: | |||
~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; | |||
/** @internal */ | |||
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; | |||
/** @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: | |||
//============================================================================== | |||
void recursiveFileSearch (StringArray&, const File&, bool recursive); | |||
//============================================================================== | |||
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 | |||
#define JUCE_VSTMIDIEVENTLIST_H_INCLUDED | |||
@@ -53,7 +54,7 @@ public: | |||
numEventsUsed = 0; | |||
if (events != nullptr) | |||
events->numEvents = 0; | |||
events->numberOfEvents = 0; | |||
} | |||
void addEvent (const void* const midiData, const int numBytes, const int frameOffset) | |||
@@ -61,65 +62,65 @@ public: | |||
ensureSize (numEventsUsed + 1); | |||
VstMidiEvent* const e = (VstMidiEvent*) (events->events [numEventsUsed]); | |||
events->numEvents = ++numEventsUsed; | |||
events->numberOfEvents = ++numEventsUsed; | |||
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); | |||
} | |||
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->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 | |||
// 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]; | |||
if (e != nullptr) | |||
{ | |||
if (e->type == kVstMidiType) | |||
if (e->type == vstMidiEventType) | |||
{ | |||
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: | |||
int numEventsUsed, numEventsAllocated; | |||
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; | |||
} | |||
static void freeVSTEvent (VstEvent* e) | |||
{ | |||
if (e->type == kVstSysExType) | |||
delete[] (((VstMidiSysexEvent*) e)->sysexDump); | |||
if (e->type == vstSysExEventType) | |||
delete[] (((VstSysExEvent*) e)->sysExDump); | |||
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(); | |||
//============================================================================== | |||
/** 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. | |||
*/ | |||
static const XmlElement* getVSTXML (AudioPluginInstance* plugin); | |||
@@ -53,6 +53,13 @@ public: | |||
/** Attempts to set a VST's state from a chunk of memory. */ | |||
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. */ | |||
class ExtraFunctions | |||
@@ -75,23 +82,21 @@ public: | |||
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. */ | |||
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"; } | |||
void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | |||
AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, double, int) override; | |||
bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | |||
String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) 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; | |||
FileSearchPath getDefaultLocationsToSearch() override; | |||
bool canScanForPlugins() const override { return true; } | |||
@@ -103,6 +108,14 @@ public: | |||
*/ | |||
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: | |||
void recursiveFileSearch (StringArray&, const File&, bool recursive); | |||
@@ -31,10 +31,15 @@ | |||
#error "Incorrect use of JUCE cpp file" | |||
#endif | |||
#include "AppConfig.h" | |||
#define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
#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 | |||
@@ -70,9 +75,18 @@ static inline bool arrayContainsPlugin (const OwnedArray<PluginDescription>& lis | |||
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, | |||
private AsyncUpdater { | |||
AutoResizingNSViewComponent(); | |||
@@ -84,7 +98,7 @@ struct AutoResizingNSViewComponent : public NSViewComponent, | |||
struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewComponent, | |||
private Timer { | |||
AutoResizingNSViewComponentWithParent(); | |||
NSView* getChildView() const; | |||
JUCE_IOS_MAC_VIEW* getChildView() const; | |||
void timerCallback() override; | |||
}; | |||
@@ -112,27 +126,29 @@ void AutoResizingNSViewComponent::handleAsyncUpdate() | |||
resizeToFitView(); | |||
} | |||
//============================================================================== | |||
AutoResizingNSViewComponentWithParent::AutoResizingNSViewComponentWithParent() | |||
{ | |||
NSView* v = [[NSView alloc] init]; | |||
JUCE_IOS_MAC_VIEW* v = [[JUCE_IOS_MAC_VIEW alloc] init]; | |||
setView (v); | |||
[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) | |||
return [[parent subviews] objectAtIndex: 0]; | |||
return nil; | |||
} | |||
void AutoResizingNSViewComponentWithParent::timerCallback() | |||
{ | |||
if (NSView* child = getChildView()) | |||
if (JUCE_IOS_MAC_VIEW* child = getChildView()) | |||
{ | |||
stopTimer(); | |||
setView(child); | |||
@@ -148,12 +164,10 @@ void AutoResizingNSViewComponentWithParent::timerCallback() | |||
#include "format/juce_AudioPluginFormat.cpp" | |||
#include "format/juce_AudioPluginFormatManager.cpp" | |||
#include "processors/juce_AudioProcessor.cpp" | |||
#include "processors/juce_AudioChannelSet.cpp" | |||
#include "processors/juce_AudioProcessorEditor.cpp" | |||
#include "processors/juce_AudioProcessorGraph.cpp" | |||
#include "processors/juce_GenericAudioProcessorEditor.cpp" | |||
#include "processors/juce_PluginDescription.cpp" | |||
#include "processors/AudioProcessorGraphMultiThreaded.cpp" | |||
#include "format_types/juce_LADSPAPluginFormat.cpp" | |||
#include "format_types/juce_VSTPluginFormat.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 | |||
#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 | |||
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 | |||
*/ | |||
@@ -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!" | |||
#endif | |||
#if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT) | |||
#if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT || JUCE_IOS) | |||
#define JUCE_SUPPORT_CARBON 1 | |||
#endif | |||
#ifndef JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR | |||
#define JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR 1 | |||
#endif | |||
//============================================================================== | |||
//============================================================================== | |||
namespace juce | |||
@@ -77,13 +106,11 @@ class AudioProcessor; | |||
#include "processors/juce_AudioProcessorEditor.h" | |||
#include "processors/juce_AudioProcessorListener.h" | |||
#include "processors/juce_AudioProcessorParameter.h" | |||
#include "processors/juce_AudioChannelSet.h" | |||
#include "processors/juce_AudioProcessor.h" | |||
#include "processors/juce_PluginDescription.h" | |||
#include "processors/juce_AudioPluginInstance.h" | |||
#include "processors/juce_AudioProcessorGraph.h" | |||
#include "processors/juce_GenericAudioProcessorEditor.h" | |||
#include "processors/AudioProcessorGraphMultiThreaded.h" | |||
#include "format/juce_AudioPluginFormat.h" | |||
#include "format/juce_AudioPluginFormatManager.h" | |||
#include "scanning/juce_KnownPluginList.h" | |||
@@ -78,6 +78,9 @@ public: | |||
protected: | |||
//============================================================================== | |||
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) | |||
}; | |||
@@ -25,6 +25,7 @@ | |||
#ifndef JUCE_AUDIOPROCESSOR_H_INCLUDED | |||
#define JUCE_AUDIOPROCESSOR_H_INCLUDED | |||
struct PluginBusUtilities; | |||
//============================================================================== | |||
/** | |||
@@ -43,10 +44,41 @@ | |||
class JUCE_API AudioProcessor | |||
{ | |||
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(); | |||
/** 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: | |||
//============================================================================== | |||
enum ProcessingPrecision | |||
@@ -70,18 +102,20 @@ public: | |||
playback stops. | |||
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. | |||
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, | |||
int estimatedSamplesPerBlock) = 0; | |||
int maximumExpectedSamplesPerBlock) = 0; | |||
/** Called after playback has stopped, to let the filter free up any resources it | |||
no longer needs. | |||
@@ -107,7 +141,7 @@ public: | |||
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. | |||
Use AudioBusArrangement::getBusBuffer to obtain an audio buffer for a | |||
Use AudiobusLayout::getBusBuffer to obtain an audio buffer for a | |||
particular bus. | |||
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 | |||
the AsyncUpdater class in a similar way. | |||
@see AudioBusArrangement::getBusBuffer | |||
@see AudiobusLayout::getBusBuffer | |||
*/ | |||
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 | |||
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. | |||
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 | |||
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. | |||
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 | |||
the AsyncUpdater class in a similar way. | |||
@see AudioBusArrangement::getBusBuffer | |||
@see AudiobusLayout::getBusBuffer | |||
*/ | |||
virtual void processBlock (AudioBuffer<double>& buffer, | |||
MidiBuffer& midiMessages); | |||
/** Renders the next block when the processor is being bypassed. | |||
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 | |||
the processor's latency characteristics. This will avoid situations where bypassing | |||
@@ -227,6 +262,7 @@ public: | |||
MidiBuffer& midiMessages); | |||
/** Renders the next block when the processor is being bypassed. | |||
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 | |||
the processor's latency characteristics. This will avoid situations where bypassing | |||
@@ -237,103 +273,372 @@ public: | |||
virtual void processBlockBypassed (AudioBuffer<double>& buffer, | |||
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. | |||
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; | |||
*/ | |||
int getChannelIndexInProcessBlockBuffer (int channelIndex) const noexcept; | |||
/** 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 | |||
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. | |||
@@ -403,7 +708,7 @@ public: | |||
getMainBusNumInputChannels if your processor does not have any sidechains | |||
or aux buses. | |||
*/ | |||
int getTotalNumInputChannels() const noexcept { return busArrangement.getTotalNumInputChannels(); } | |||
int getTotalNumInputChannels() const noexcept { return cachedTotalIns; } | |||
/** Returns the total number of output channels. | |||
@@ -417,13 +722,56 @@ public: | |||
getMainBusNumOutputChannels if your processor does not have any sidechains | |||
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. */ | |||
int getMainBusNumInputChannels() const noexcept; | |||
inline int getMainBusNumInputChannels() const noexcept { return getChannelCountOfBus (true, 0); } | |||
/** 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. | |||
@@ -473,6 +821,9 @@ public: | |||
/** Returns true if the processor supports MPE. */ | |||
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 | |||
is calling the processBlock() method. | |||
@@ -611,6 +962,17 @@ public: | |||
*/ | |||
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. | |||
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 | |||
will look better in constrained spaces (e.g. the displays on hardware controller | |||
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. | |||
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); | |||
/** 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(); | |||
/** 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. */ | |||
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 | |||
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; | |||
@@ -913,6 +1281,7 @@ public: | |||
wrapperType_VST, | |||
wrapperType_VST3, | |||
wrapperType_AudioUnit, | |||
wrapperType_AudioUnitv3, | |||
wrapperType_RTAS, | |||
wrapperType_AAX, | |||
wrapperType_Standalone | |||
@@ -939,7 +1308,7 @@ public: | |||
/** Returns the name of one of the processor's input channels. | |||
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 getOutputChannelName (int channelIndex) const); | |||
@@ -947,7 +1316,7 @@ public: | |||
/** 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 | |||
channel pairing information by modifying the busArrangement member variable in | |||
channel pairing information by modifying the busLayout member variable in | |||
the constructor. */ | |||
JUCE_DEPRECATED (virtual bool isInputChannelStereoPair (int index) const); | |||
JUCE_DEPRECATED (virtual bool isOutputChannelStereoPair (int index) const); | |||
@@ -976,6 +1345,110 @@ public: | |||
static void JUCE_CALLTYPE setTypeOfNextNewPlugin (WrapperType); | |||
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 */ | |||
AudioPlayHead* playHead; | |||
@@ -983,6 +1456,67 @@ protected: | |||
void sendParamChangeMessageToListeners (int parameterIndex, float newValue); | |||
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; | |||
#if ! JUCE_AUDIO_PROCESSOR_NO_GUI | |||
Component::SafePointer<AudioProcessorEditor> activeEditor; | |||
@@ -996,9 +1530,15 @@ private: | |||
ProcessingPrecision processingPrecision; | |||
CriticalSection callbackLock, listenerLock; | |||
friend class Bus; | |||
mutable OwnedArray<Bus> inputBuses; | |||
mutable OwnedArray<Bus> outputBuses; | |||
String cachedInputSpeakerArrString; | |||
String cachedOutputSpeakerArrString; | |||
int cachedTotalIns, cachedTotalOuts; | |||
OwnedArray<AudioProcessorParameter> managedParameters; | |||
AudioProcessorParameter* getParamChecked (int) const noexcept; | |||
@@ -1007,8 +1547,13 @@ private: | |||
#endif | |||
AudioProcessorListener* getListenerLocked (int) const noexcept; | |||
void disableNonMainBuses (bool isInput); | |||
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. | |||
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.. | |||
jassert (p != nullptr); | |||
initialise(); | |||
} | |||
AudioProcessorEditor::~AudioProcessorEditor() | |||
@@ -37,7 +39,132 @@ AudioProcessorEditor::~AudioProcessorEditor() | |||
// if this fails, then the wrapper hasn't called editorBeingDeleted() on the | |||
// filter for some reason.. | |||
jassert (processor.getActiveEditor() != this); | |||
removeComponentListener (resizeListener); | |||
} | |||
void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} | |||
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 | |||
#define JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED | |||
class AudioProcessorEditorListener; | |||
//============================================================================== | |||
/** | |||
Base class for the component that acts as the GUI for an AudioProcessor. | |||
@@ -84,7 +84,91 @@ public: | |||
*/ | |||
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: | |||
//============================================================================== | |||
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) | |||
}; | |||
@@ -264,7 +264,16 @@ struct ProcessBufferOp : public AudioGraphRenderingOp<ProcessBufferOp> | |||
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) | |||
@@ -960,14 +969,7 @@ void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int n | |||
processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision | |||
: 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); | |||
} | |||
} | |||
@@ -1035,7 +1037,7 @@ struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers | |||
//============================================================================== | |||
AudioProcessorGraph::AudioProcessorGraph() | |||
: 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); | |||
nodes.add (n); | |||
triggerAsyncUpdate(); | |||
if (isPrepared) | |||
triggerAsyncUpdate(); | |||
n->setParentGraph (this); | |||
return n; | |||
@@ -1117,7 +1121,9 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) | |||
if (nodes.getUnchecked(i)->nodeId == nodeId) | |||
{ | |||
nodes.remove (i); | |||
triggerAsyncUpdate(); | |||
if (isPrepared) | |||
triggerAsyncUpdate(); | |||
return true; | |||
} | |||
@@ -1126,6 +1132,15 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) | |||
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 int sourceChannelIndex, | |||
@@ -1168,14 +1183,14 @@ bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId, | |||
const Node* const source = getNodeForId (sourceNodeId); | |||
if (source == nullptr | |||
|| (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getMainBusNumOutputChannels()) | |||
|| (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getTotalNumOutputChannels()) | |||
|| (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) | |||
return false; | |||
const Node* const dest = getNodeForId (destNodeId); | |||
if (dest == nullptr | |||
|| (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getMainBusNumInputChannels()) | |||
|| (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getTotalNumInputChannels()) | |||
|| (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) | |||
return false; | |||
@@ -1194,14 +1209,19 @@ bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId, | |||
GraphRenderingOps::ConnectionSorter sorter; | |||
connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex, | |||
destNodeId, destChannelIndex)); | |||
triggerAsyncUpdate(); | |||
if (isPrepared) | |||
triggerAsyncUpdate(); | |||
return true; | |||
} | |||
void AudioProcessorGraph::removeConnection (const int index) | |||
{ | |||
connections.remove (index); | |||
triggerAsyncUpdate(); | |||
if (isPrepared) | |||
triggerAsyncUpdate(); | |||
} | |||
bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, | |||
@@ -1253,9 +1273,9 @@ bool AudioProcessorGraph::isConnectionLegal (const Connection* const c) const | |||
return source != nullptr | |||
&& dest != nullptr | |||
&& (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getMainBusNumOutputChannels()) | |||
&& (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getTotalNumOutputChannels()) | |||
: source->processor->producesMidi()) | |||
&& (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getMainBusNumInputChannels()) | |||
&& (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getTotalNumInputChannels()) | |||
: dest->processor->acceptsMidi()); | |||
} | |||
@@ -1383,6 +1403,8 @@ void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSam | |||
clearRenderingSequence(); | |||
buildRenderingSequence(); | |||
isPrepared = true; | |||
} | |||
bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | |||
@@ -1392,6 +1414,8 @@ bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | |||
void AudioProcessorGraph::releaseResources() | |||
{ | |||
isPrepared = false; | |||
for (int i = 0; i < nodes.size(); ++i) | |||
nodes.getUnchecked(i)->unprepare(); | |||
@@ -1516,13 +1540,13 @@ void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (Plugin | |||
d.version = "1.0"; | |||
d.isInstrument = false; | |||
d.numInputChannels = getMainBusNumInputChannels(); | |||
d.numInputChannels = getTotalNumInputChannels(); | |||
if (type == audioOutputNode && graph != nullptr) | |||
d.numInputChannels = graph->getMainBusNumInputChannels(); | |||
d.numInputChannels = graph->getTotalNumInputChannels(); | |||
d.numOutputChannels = getMainBusNumOutputChannels(); | |||
d.numOutputChannels = getTotalNumOutputChannels(); | |||
if (type == audioInputNode && graph != nullptr) | |||
d.numOutputChannels = graph->getMainBusNumOutputChannels(); | |||
d.numOutputChannels = graph->getTotalNumOutputChannels(); | |||
} | |||
void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) | |||
@@ -1639,8 +1663,8 @@ void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorG | |||
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(), | |||
getBlockSize()); | |||
@@ -183,6 +183,12 @@ public: | |||
*/ | |||
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. */ | |||
int getNumConnections() const { return connections.size(); } | |||
@@ -390,6 +396,8 @@ private: | |||
MidiBuffer* currentMidiInputBuffer; | |||
MidiBuffer currentMidiOutputBuffer; | |||
bool isPrepared; | |||
void handleAsyncUpdate() override; | |||
void clearRenderingSequence(); | |||
void buildRenderingSequence(); | |||
@@ -105,6 +105,16 @@ private: | |||
} | |||
} | |||
void startedDragging() override | |||
{ | |||
owner.beginParameterChangeGesture(index); | |||
} | |||
void stoppedDragging() override | |||
{ | |||
owner.endParameterChangeGesture(index); | |||
} | |||
String getTextFromValue (double /*value*/) override | |||
{ | |||
return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); | |||
@@ -118,7 +118,7 @@ public: | |||
given identifier string. | |||
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; | |||
@@ -27,6 +27,8 @@ KnownPluginList::~KnownPluginList() {} | |||
void KnownPluginList::clear() | |||
{ | |||
ScopedLock lock (typesArrayLock); | |||
if (types.size() > 0) | |||
{ | |||
types.clear(); | |||
@@ -36,6 +38,8 @@ void KnownPluginList::clear() | |||
PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const | |||
{ | |||
ScopedLock lock (typesArrayLock); | |||
for (int i = 0; i < types.size(); ++i) | |||
if (types.getUnchecked(i)->fileOrIdentifier == fileOrIdentifier) | |||
return types.getUnchecked(i); | |||
@@ -45,6 +49,8 @@ PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifi | |||
PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& identifierString) const | |||
{ | |||
ScopedLock lock (typesArrayLock); | |||
for (int i = 0; i < types.size(); ++i) | |||
if (types.getUnchecked(i)->matchesIdentifierString (identifierString)) | |||
return types.getUnchecked(i); | |||
@@ -54,27 +60,37 @@ PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& id | |||
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(); | |||
return true; | |||
} | |||
void KnownPluginList::removeType (const int index) | |||
{ | |||
types.remove (index); | |||
{ | |||
ScopedLock lock (typesArrayLock); | |||
types.remove (index); | |||
} | |||
sendChangeMessage(); | |||
} | |||
@@ -84,6 +100,8 @@ bool KnownPluginList::isListingUpToDate (const String& fileOrIdentifier, | |||
if (getTypeForFile (fileOrIdentifier) == nullptr) | |||
return false; | |||
ScopedLock lock (typesArrayLock); | |||
for (int i = types.size(); --i >= 0;) | |||
{ | |||
const PluginDescription* const d = types.getUnchecked(i); | |||
@@ -103,7 +121,7 @@ void KnownPluginList::setCustomScanner (CustomScanner* newScanner) | |||
bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||
const bool dontRescanIfAlreadyInList, | |||
OwnedArray <PluginDescription>& typesFound, | |||
OwnedArray<PluginDescription>& typesFound, | |||
AudioPluginFormat& format) | |||
{ | |||
const ScopedLock sl (scanLock); | |||
@@ -113,6 +131,8 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||
{ | |||
bool needsRescanning = false; | |||
ScopedLock lock (typesArrayLock); | |||
for (int i = types.size(); --i >= 0;) | |||
{ | |||
const PluginDescription* const d = types.getUnchecked(i); | |||
@@ -133,7 +153,7 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||
if (blacklist.contains (fileOrIdentifier)) | |||
return false; | |||
OwnedArray <PluginDescription> found; | |||
OwnedArray<PluginDescription> found; | |||
{ | |||
const ScopedUnlock sl2 (scanLock); | |||
@@ -163,7 +183,7 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||
void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager, | |||
const StringArray& files, | |||
OwnedArray <PluginDescription>& typesFound) | |||
OwnedArray<PluginDescription>& typesFound) | |||
{ | |||
for (int i = 0; i < files.size(); ++i) | |||
{ | |||
@@ -298,12 +318,17 @@ void KnownPluginList::sort (const SortMethod method, bool forwards) | |||
if (method != defaultOrder) | |||
{ | |||
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) | |||
sendChangeMessage(); | |||
@@ -315,8 +340,12 @@ XmlElement* KnownPluginList::createXml() const | |||
{ | |||
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) | |||
e->createNewChildElement ("BLACKLISTED")->setAttribute ("id", blacklist[i]); | |||
@@ -348,7 +377,7 @@ struct PluginTreeUtils | |||
{ | |||
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) | |||
{ | |||
@@ -392,7 +421,7 @@ struct PluginTreeUtils | |||
} | |||
static void buildTreeByCategory (KnownPluginList::PluginTree& tree, | |||
const Array <PluginDescription*>& sorted, | |||
const Array<PluginDescription*>& sorted, | |||
const KnownPluginList::SortMethod sortMethod) | |||
{ | |||
String lastType; | |||
@@ -475,15 +504,21 @@ struct PluginTreeUtils | |||
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) | |||
{ | |||
const KnownPluginList::PluginTree& sub = *tree.subFolders.getUnchecked(i); | |||
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) | |||
@@ -495,16 +530,22 @@ struct PluginTreeUtils | |||
if (containsDuplicateNames (tree.plugins, name)) | |||
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 | |||
{ | |||
Array <PluginDescription*> sorted; | |||
Array<PluginDescription*> sorted; | |||
{ | |||
ScopedLock lock (typesArrayLock); | |||
PluginSorter sorter (sortMethod, true); | |||
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)); | |||
PluginTreeUtils::addToMenu (*tree, menu, types); | |||
PluginTreeUtils::addToMenu (*tree, menu, types, currentlyTickedPluginID); | |||
} | |||
int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const | |||
@@ -148,7 +148,8 @@ public: | |||
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. | |||
Returns -1 if it's not an ID that was used. | |||
@@ -215,7 +216,7 @@ private: | |||
OwnedArray<PluginDescription> types; | |||
StringArray blacklist; | |||
ScopedPointer<CustomScanner> scanner; | |||
CriticalSection scanLock; | |||
CriticalSection scanLock, typesArrayLock; | |||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList) | |||
}; | |||
@@ -34,15 +34,17 @@ PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, | |||
AudioPluginFormat& formatToLookFor, | |||
FileSearchPath directoriesToSearch, | |||
const bool recursive, | |||
const File& deadMansPedal) | |||
const File& deadMansPedal, | |||
bool allowPluginsWhichRequireAsynchronousInstantiation) | |||
: list (listToAddTo), | |||
format (formatToLookFor), | |||
deadMansPedalFile (deadMansPedal), | |||
progress (0) | |||
progress (0), | |||
allowAsync (allowPluginsWhichRequireAsynchronousInstantiation) | |||
{ | |||
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 | |||
// 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 directoriesToSearch the path to search | |||
@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 | |||
will still manage to find all the proper plugins. | |||
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 | |||
is just a list of filenames of the modules that | |||
failed. | |||
@param allowPluginsWhichRequireAsynchronousInstantiation | |||
If this is false then the scanner will exclude plug-ins | |||
asynchronous creation - such as AUv3 plug-ins. | |||
*/ | |||
PluginDirectoryScanner (KnownPluginList& listToAddResultsTo, | |||
AudioPluginFormat& formatToLookFor, | |||
FileSearchPath directoriesToSearch, | |||
bool searchRecursively, | |||
const File& deadMansPedalFile); | |||
const File& deadMansPedalFile, | |||
bool allowPluginsWhichRequireAsynchronousInstantiation = false); | |||
/** Destructor. */ | |||
~PluginDirectoryScanner(); | |||
@@ -116,6 +119,7 @@ private: | |||
StringArray failedFiles; | |||
Atomic<int> nextIndex; | |||
float progress; | |||
bool allowAsync; | |||
void updateProgress(); | |||
void setDeadMansPedalFile (const StringArray& newContents); | |||
@@ -123,13 +123,15 @@ public: | |||
//============================================================================== | |||
PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit, | |||
const File& deadMansPedal, PropertiesFile* const props) | |||
const File& deadMansPedal, PropertiesFile* const props, | |||
bool allowPluginsWhichRequireAsynchronousInstantiation) | |||
: formatManager (manager), | |||
list (listToEdit), | |||
deadMansPedalFile (deadMansPedal), | |||
optionsButton ("Options..."), | |||
propertiesToUse (props), | |||
numThreads (0) | |||
allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), | |||
numThreads (allowAsync ? 1 : 0) | |||
{ | |||
tableModel = new TableModel (*this, listToEdit); | |||
@@ -331,18 +333,25 @@ class PluginListComponent::Scanner : private Timer | |||
{ | |||
public: | |||
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), | |||
pathChooserWindow (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon), | |||
pathChooserWindow (TRANS("Select folders to scan..."), String(), 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()); | |||
// 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 ! JUCE_IOS | |||
if (propertiesToUse != nullptr) | |||
path = getLastSearchPath (*propertiesToUse, formatToScan); | |||
#endif | |||
pathList.setSize (500, 300); | |||
pathList.setPath (path); | |||
@@ -381,7 +390,7 @@ private: | |||
String pluginBeingScanned; | |||
double progress; | |||
int numThreads; | |||
bool finished; | |||
bool allowAsync, finished; | |||
ScopedPointer<ThreadPool> pool; | |||
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\"?") | |||
.replace ("XYZ", f.getFullPathName()), | |||
TRANS ("Scan"), | |||
String::empty, | |||
String(), | |||
nullptr, | |||
ModalCallbackFunction::create (warnAboutStupidPathsCallback, this)); | |||
return; | |||
@@ -465,7 +474,7 @@ private: | |||
pathChooserWindow.setVisible (false); | |||
scanner = new PluginDirectoryScanner (owner.list, formatToScan, pathList.getPath(), | |||
true, owner.deadMansPedalFile); | |||
true, owner.deadMansPedalFile, allowAsync); | |||
if (propertiesToUse != nullptr) | |||
{ | |||
@@ -545,7 +554,7 @@ private: | |||
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..."), | |||
dialogText.isNotEmpty() ? dialogText : TRANS("Searching for all possible plug-in files...")); | |||
} | |||
@@ -47,7 +47,8 @@ public: | |||
PluginListComponent (AudioPluginFormatManager& formatManager, | |||
KnownPluginList& listToRepresent, | |||
const File& deadMansPedalFile, | |||
PropertiesFile* propertiesToUse); | |||
PropertiesFile* propertiesToUse, | |||
bool allowPluginsWhichRequireAsynchronousInstantiation = false); | |||
/** Destructor. */ | |||
~PluginListComponent(); | |||
@@ -60,8 +61,10 @@ public: | |||
const String& textForProgressWindowDescription); | |||
/** 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); | |||
/** Returns the last search path stored in a given properties file for the specified format. */ | |||
@@ -96,6 +99,7 @@ private: | |||
TextButton optionsButton; | |||
PropertiesFile* propertiesToUse; | |||
String dialogTitle, dialogText; | |||
bool allowAsync; | |||
int numThreads; | |||
class TableModel; | |||