| @@ -2,8 +2,8 @@ | |||||
| set -e | set -e | ||||
| JUCE_MODULES_DIR="/home/falktx/Projects/FOSS/GIT-mine/DISTRHO-Ports/libs/juce/source/modules/" | |||||
| CARLA_MODULES_DIR="/home/falktx/Projects/FOSS/GIT-mine/Carla/source/modules/" | |||||
| JUCE_MODULES_DIR="/Shared/Personal/FOSS/GIT/DISTRHO/DISTRHO-Ports/libs/juce/source/modules/" | |||||
| CARLA_MODULES_DIR="/home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/" | |||||
| MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") | MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") | ||||
| @@ -60,6 +60,7 @@ | |||||
| // misc | // misc | ||||
| #define JUCE_DISABLE_JUCE_VERSION_PRINTING 1 | #define JUCE_DISABLE_JUCE_VERSION_PRINTING 1 | ||||
| #define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1 | |||||
| #define JUCE_STANDALONE_APPLICATION 0 | #define JUCE_STANDALONE_APPLICATION 0 | ||||
| #define JUCE_STRING_UTF_TYPE 8 | #define JUCE_STRING_UTF_TYPE 8 | ||||
| #define JUCE_USE_VFORK 1 | #define JUCE_USE_VFORK 1 | ||||
| @@ -0,0 +1,336 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2015 - ROLI Ltd. | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ------------------------------------------------------------------------------ | |||||
| To release a closed-source product which uses JUCE, commercial licenses are | |||||
| available: visit www.juce.com for more information. | |||||
| ============================================================================== | |||||
| */ | |||||
| AudioChannelSet::AudioChannelSet (uint32 c) : channels (c) {} | |||||
| bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept { return channels == other.channels; } | |||||
| bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; } | |||||
| bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; } | |||||
| String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) | |||||
| { | |||||
| if (type >= discreteChannel0) | |||||
| return String ("Discrete ") + String (type - discreteChannel0 + 1); | |||||
| switch (type) | |||||
| { | |||||
| case left: return NEEDS_TRANS("Left"); | |||||
| case right: return NEEDS_TRANS("Right"); | |||||
| case centre: return NEEDS_TRANS("Centre"); | |||||
| case LFE: return NEEDS_TRANS("LFE"); | |||||
| case leftSurround: return NEEDS_TRANS("Left Surround"); | |||||
| case rightSurround: return NEEDS_TRANS("Right Surround"); | |||||
| case leftCentre: return NEEDS_TRANS("Left Centre"); | |||||
| case rightCentre: return NEEDS_TRANS("Right Centre"); | |||||
| case centreSurround: return NEEDS_TRANS("Centre Surround"); | |||||
| case leftSurroundRear: return NEEDS_TRANS("Left Surround Rear"); | |||||
| case rightSurroundRear: return NEEDS_TRANS("Right Surround Rear"); | |||||
| case topMiddle: return NEEDS_TRANS("Top Middle"); | |||||
| case topFrontLeft: return NEEDS_TRANS("Top Front Left"); | |||||
| case topFrontCentre: return NEEDS_TRANS("Top Front Centre"); | |||||
| case topFrontRight: return NEEDS_TRANS("Top Front Right"); | |||||
| case topRearLeft: return NEEDS_TRANS("Top Rear Left"); | |||||
| case topRearCentre: return NEEDS_TRANS("Top Rear Centre"); | |||||
| case topRearRight: return NEEDS_TRANS("Top Rear Right"); | |||||
| case wideLeft: return NEEDS_TRANS("Wide Left"); | |||||
| case wideRight: return NEEDS_TRANS("Wide Right"); | |||||
| case LFE2: return NEEDS_TRANS("LFE 2"); | |||||
| case leftSurroundSide: return NEEDS_TRANS ("Left Surround Side"); | |||||
| case rightSurroundSide: return NEEDS_TRANS ("Right Surround Side"); | |||||
| case ambisonicW: return NEEDS_TRANS("Ambisonic W"); | |||||
| case ambisonicX: return NEEDS_TRANS("Ambisonic X"); | |||||
| case ambisonicY: return NEEDS_TRANS("Ambisonic Y"); | |||||
| case ambisonicZ: return NEEDS_TRANS("Ambisonic Z"); | |||||
| default: break; | |||||
| } | |||||
| return "Unknown"; | |||||
| } | |||||
| String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) | |||||
| { | |||||
| if (type >= discreteChannel0) | |||||
| return String (type - discreteChannel0 + 1); | |||||
| switch (type) | |||||
| { | |||||
| case left: return "L"; | |||||
| case right: return "R"; | |||||
| case centre: return "C"; | |||||
| case LFE: return "Lfe"; | |||||
| case leftSurround: return "Ls"; | |||||
| case rightSurround: return "Rs"; | |||||
| case leftCentre: return "Lc"; | |||||
| case rightCentre: return "Rc"; | |||||
| case centreSurround: return "Cs"; | |||||
| case leftSurroundRear: return "Lrs"; | |||||
| case rightSurroundRear: return "Rrs"; | |||||
| case topMiddle: return "Tm"; | |||||
| case topFrontLeft: return "Tfl"; | |||||
| case topFrontCentre: return "Tfc"; | |||||
| case topFrontRight: return "Tfr"; | |||||
| case topRearLeft: return "Trl"; | |||||
| case topRearCentre: return "Trc"; | |||||
| case topRearRight: return "Trr"; | |||||
| case wideLeft: return "Wl"; | |||||
| case wideRight: return "Wr"; | |||||
| case LFE2: return "Lfe2"; | |||||
| case leftSurroundSide: return "Lss"; | |||||
| case rightSurroundSide: return "Rss"; | |||||
| case ambisonicW: return "W"; | |||||
| case ambisonicX: return "X"; | |||||
| case ambisonicY: return "Y"; | |||||
| case ambisonicZ: return "Z"; | |||||
| default: break; | |||||
| } | |||||
| return ""; | |||||
| } | |||||
| String AudioChannelSet::getSpeakerArrangementAsString() const | |||||
| { | |||||
| StringArray speakerTypes; | |||||
| Array<AudioChannelSet::ChannelType> speakers = getChannelTypes(); | |||||
| for (int i = 0; i < speakers.size(); ++i) | |||||
| { | |||||
| String name = getAbbreviatedChannelTypeName (speakers.getReference (i)); | |||||
| if (name.isNotEmpty()) | |||||
| speakerTypes.add (name); | |||||
| } | |||||
| return speakerTypes.joinIntoString (" "); | |||||
| } | |||||
| String AudioChannelSet::getDescription() const | |||||
| { | |||||
| if (isDiscreteLayout()) return String ("Discrete #") + String (size()); | |||||
| if (*this == disabled()) return "Disabled"; | |||||
| if (*this == mono()) return "Mono"; | |||||
| if (*this == stereo()) return "Stereo"; | |||||
| if (*this == createLCR()) return "LCR"; | |||||
| if (*this == createLRS()) return "LRS"; | |||||
| if (*this == createLCRS()) return "LCRS"; | |||||
| if (*this == create5point0()) return "5.1 Surround"; | |||||
| if (*this == create5point1()) return "5.1 Surround (+Lfe)"; | |||||
| if (*this == create6point0()) return "6.1 Surround"; | |||||
| if (*this == create6point1()) return "6.1 Surround (+Lfe)"; | |||||
| if (*this == create6point0Music()) return "6.1 (Music) Surround"; | |||||
| if (*this == create6point1Music()) return "6.1 (Music) Surround (+Lfe)"; | |||||
| if (*this == create7point0()) return "7.1 Surround"; | |||||
| if (*this == create7point1()) return "7.1 Surround (Lfe)"; | |||||
| if (*this == create7point0SDDS()) return "7.1 Surround SDDS"; | |||||
| if (*this == create7point1SDDS()) return "7.1 Surround SDDS (+Lfe)"; | |||||
| if (*this == quadraphonic()) return "Quadraphonic"; | |||||
| if (*this == pentagonal()) return "Pentagonal"; | |||||
| if (*this == hexagonal()) return "Hexagonal"; | |||||
| if (*this == octagonal()) return "Octagonal"; | |||||
| if (*this == ambisonic()) return "Ambisonic"; | |||||
| return "Unknown"; | |||||
| } | |||||
| bool AudioChannelSet::isDiscreteLayout() const noexcept | |||||
| { | |||||
| Array<AudioChannelSet::ChannelType> speakers = getChannelTypes(); | |||||
| for (int i = 0; i < speakers.size(); ++i) | |||||
| if (speakers.getReference (i) > ambisonicZ) | |||||
| return true; | |||||
| return false; | |||||
| } | |||||
| int AudioChannelSet::size() const noexcept | |||||
| { | |||||
| return channels.countNumberOfSetBits(); | |||||
| } | |||||
| AudioChannelSet::ChannelType AudioChannelSet::getTypeOfChannel (int index) const noexcept | |||||
| { | |||||
| int bit = channels.findNextSetBit(0); | |||||
| for (int i = 0; i < index && bit >= 0; ++i) | |||||
| bit = channels.findNextSetBit (bit + 1); | |||||
| return static_cast<ChannelType> (bit); | |||||
| } | |||||
| int AudioChannelSet::getChannelIndexForType (AudioChannelSet::ChannelType type) const noexcept | |||||
| { | |||||
| int idx = 0; | |||||
| for (int bit = channels.findNextSetBit (0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) | |||||
| { | |||||
| if (static_cast<ChannelType> (bit) == type) | |||||
| return idx; | |||||
| idx++; | |||||
| } | |||||
| return -1; | |||||
| } | |||||
| Array<AudioChannelSet::ChannelType> AudioChannelSet::getChannelTypes() const | |||||
| { | |||||
| Array<ChannelType> result; | |||||
| for (int bit = channels.findNextSetBit(0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) | |||||
| result.add (static_cast<ChannelType> (bit)); | |||||
| return result; | |||||
| } | |||||
| void AudioChannelSet::addChannel (ChannelType newChannel) | |||||
| { | |||||
| const int bit = static_cast<int> (newChannel); | |||||
| jassert (bit >= 0 && bit < 1024); | |||||
| channels.setBit (bit); | |||||
| } | |||||
| void AudioChannelSet::removeChannel (ChannelType newChannel) | |||||
| { | |||||
| const int bit = static_cast<int> (newChannel); | |||||
| jassert (bit >= 0 && bit < 1024); | |||||
| channels.clearBit (bit); | |||||
| } | |||||
| AudioChannelSet AudioChannelSet::disabled() { return AudioChannelSet(); } | |||||
| AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet (1u << centre); } | |||||
| AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); } | |||||
| AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre)); } | |||||
| AudioChannelSet AudioChannelSet::createLRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surround)); } | |||||
| AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surround)); } | |||||
| AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround)); } | |||||
| AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << LFE)); } | |||||
| AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); } | |||||
| AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround) | (1u << LFE)); } | |||||
| AudioChannelSet AudioChannelSet::create6point0Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); } | |||||
| AudioChannelSet AudioChannelSet::create6point1Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << LFE)); } | |||||
| AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } | |||||
| AudioChannelSet AudioChannelSet::create7point0SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); } | |||||
| AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << LFE)); } | |||||
| AudioChannelSet AudioChannelSet::create7point1SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre) | (1u << LFE)); } | |||||
| AudioChannelSet AudioChannelSet::ambisonic() { return AudioChannelSet ((1u << ambisonicW) | (1u << ambisonicX) | (1u << ambisonicY) | (1u << ambisonicZ)); } | |||||
| AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround)); } | |||||
| AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } | |||||
| AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << centre) | (1u << centreSurround)); } | |||||
| AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << centre) | (1u << centreSurround) | (1u << wideLeft) | (1u << wideRight)); } | |||||
| AudioChannelSet AudioChannelSet::discreteChannels (int numChannels) | |||||
| { | |||||
| AudioChannelSet s; | |||||
| s.channels.setRange (discreteChannel0, numChannels, true); | |||||
| return s; | |||||
| } | |||||
| AudioChannelSet AudioChannelSet::canonicalChannelSet (int numChannels) | |||||
| { | |||||
| if (numChannels == 1) return AudioChannelSet::mono(); | |||||
| if (numChannels == 2) return AudioChannelSet::stereo(); | |||||
| if (numChannels == 3) return AudioChannelSet::createLCR(); | |||||
| if (numChannels == 4) return AudioChannelSet::quadraphonic(); | |||||
| if (numChannels == 5) return AudioChannelSet::create5point0(); | |||||
| if (numChannels == 6) return AudioChannelSet::create5point1(); | |||||
| if (numChannels == 7) return AudioChannelSet::create7point0(); | |||||
| if (numChannels == 8) return AudioChannelSet::create7point1(); | |||||
| return discreteChannels (numChannels); | |||||
| } | |||||
| AudioChannelSet AudioChannelSet::namedChannelSet (int numChannels) | |||||
| { | |||||
| if (numChannels == 1) return AudioChannelSet::mono(); | |||||
| if (numChannels == 2) return AudioChannelSet::stereo(); | |||||
| if (numChannels == 3) return AudioChannelSet::createLCR(); | |||||
| if (numChannels == 4) return AudioChannelSet::quadraphonic(); | |||||
| if (numChannels == 5) return AudioChannelSet::create5point0(); | |||||
| if (numChannels == 6) return AudioChannelSet::create5point1(); | |||||
| if (numChannels == 7) return AudioChannelSet::create7point0(); | |||||
| if (numChannels == 8) return AudioChannelSet::create7point1(); | |||||
| return AudioChannelSet(); | |||||
| } | |||||
| Array<AudioChannelSet> AudioChannelSet::channelSetsWithNumberOfChannels (int numChannels) | |||||
| { | |||||
| Array<AudioChannelSet> retval; | |||||
| if (numChannels != 0) | |||||
| { | |||||
| retval.add (AudioChannelSet::discreteChannels (numChannels)); | |||||
| if (numChannels == 1) | |||||
| { | |||||
| retval.add (AudioChannelSet::mono()); | |||||
| } | |||||
| else if (numChannels == 2) | |||||
| { | |||||
| retval.add (AudioChannelSet::stereo()); | |||||
| } | |||||
| else if (numChannels == 3) | |||||
| { | |||||
| retval.add (AudioChannelSet::createLCR()); | |||||
| retval.add (AudioChannelSet::createLRS()); | |||||
| } | |||||
| else if (numChannels == 4) | |||||
| { | |||||
| retval.add (AudioChannelSet::quadraphonic()); | |||||
| retval.add (AudioChannelSet::createLCRS()); | |||||
| retval.add (AudioChannelSet::ambisonic()); | |||||
| } | |||||
| else if (numChannels == 5) | |||||
| { | |||||
| retval.add (AudioChannelSet::create5point0()); | |||||
| retval.add (AudioChannelSet::pentagonal()); | |||||
| } | |||||
| else if (numChannels == 6) | |||||
| { | |||||
| retval.add (AudioChannelSet::create5point1()); | |||||
| retval.add (AudioChannelSet::create6point0()); | |||||
| retval.add (AudioChannelSet::create6point0Music()); | |||||
| retval.add (AudioChannelSet::hexagonal()); | |||||
| } | |||||
| else if (numChannels == 7) | |||||
| { | |||||
| retval.add (AudioChannelSet::create7point0()); | |||||
| retval.add (AudioChannelSet::create7point0SDDS()); | |||||
| retval.add (AudioChannelSet::create6point1()); | |||||
| retval.add (AudioChannelSet::create6point1Music()); | |||||
| } | |||||
| else if (numChannels == 8) | |||||
| { | |||||
| retval.add (AudioChannelSet::create7point1()); | |||||
| retval.add (AudioChannelSet::create7point1SDDS()); | |||||
| retval.add (AudioChannelSet::octagonal()); | |||||
| } | |||||
| } | |||||
| return retval; | |||||
| } | |||||
| @@ -0,0 +1,361 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2015 - ROLI Ltd. | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ------------------------------------------------------------------------------ | |||||
| To release a closed-source product which uses JUCE, commercial licenses are | |||||
| available: visit www.juce.com for more information. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef JUCE_AUDIOCHANNELSET_H_INCLUDED | |||||
| #define JUCE_AUDIOCHANNELSET_H_INCLUDED | |||||
| //============================================================================== | |||||
| /** | |||||
| Represents a set of audio channel types. | |||||
| For example, you might have a set of left + right channels, which is a stereo | |||||
| channel set. It is a collection of values from the AudioChannelSet::ChannelType | |||||
| enum, where each type may only occur once within the set. | |||||
| The documentation below lists which AudioChannelSet corresponds to which native | |||||
| layouts used by AAX, VST2/VST3 and CoreAudio/AU. The layout tags in CoreAudio | |||||
| are particularly confusing. For example, the layout which is labeled as "7.1 SDDS" | |||||
| in Logic Pro, corresponds to CoreAudio/AU's kAudioChannelLayoutTag_DTS_7_0 tag, whereas | |||||
| AAX's DTS 7.1 Layout corresponds to CoreAudio/AU's | |||||
| kAudioChannelLayoutTag_MPEG_7_1_A format, etc. Please do not use the CoreAudio tag | |||||
| as an indication to the actual layout of the speakers. | |||||
| @see Bus | |||||
| */ | |||||
| class JUCE_API AudioChannelSet | |||||
| { | |||||
| public: | |||||
| /** Creates an empty channel set. | |||||
| You can call addChannel to add channels to the set. | |||||
| */ | |||||
| AudioChannelSet() noexcept {} | |||||
| /** Creates a zero-channel set which can be used to indicate that a | |||||
| bus is disabled. */ | |||||
| static AudioChannelSet disabled(); | |||||
| //============================================================================== | |||||
| /** Creates a one-channel mono set (centre). | |||||
| Is equivalent to: kMonoAAX (VST), AAX_eStemFormat_Mono (AAX), kAudioChannelLayoutTag_Mono (CoreAudio) | |||||
| */ | |||||
| static AudioChannelSet mono(); | |||||
| /** Creates a set containing a stereo set (left, right). | |||||
| Is equivalent to: kStereo (VST), AAX_eStemFormat_Stereo (AAX), kAudioChannelLayoutTag_Stereo (CoreAudio) | |||||
| */ | |||||
| static AudioChannelSet stereo(); | |||||
| //============================================================================== | |||||
| /** Creates a set containing an LCR set (left, right, centre). | |||||
| Is equivalent to: k30Cine (VST), AAX_eStemFormat_LCR (AAX), kAudioChannelLayoutTag_MPEG_3_0_A (CoreAudio) | |||||
| This format is referred to as "LRC" in Cubase. | |||||
| This format is referred to as "LCR" in Pro Tools. | |||||
| */ | |||||
| static AudioChannelSet createLCR(); | |||||
| /** Creates a set containing an LRS set (left, right, surround). | |||||
| Is equivalent to: k30Music (VST), n/a (AAX), kAudioChannelLayoutTag_ITU_2_1 (CoreAudio) | |||||
| This format is referred to as "LRS" in Cubase. | |||||
| */ | |||||
| static AudioChannelSet createLRS(); | |||||
| /** Creates a set containing an LCRS set (left, right, centre, surround). | |||||
| Is equivalent to: k40Cine (VST), AAX_eStemFormat_LCRS (AAX), kAudioChannelLayoutTag_MPEG_4_0_A (CoreAudio) | |||||
| This format is referred to as "LCRS (Pro Logic)" in Logic Pro. | |||||
| This format is referred to as "LRCS" in Cubase. | |||||
| This format is referred to as "LCRS" in Pro Tools. | |||||
| */ | |||||
| static AudioChannelSet createLCRS(); | |||||
| //============================================================================== | |||||
| /** Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround). | |||||
| Is equivalent to: k50 (VST), AAX_eStemFormat_5_0 (AAX), kAudioChannelLayoutTag_MPEG_5_0_A (CoreAudio) | |||||
| This format is referred to as "5.0" in Cubase. | |||||
| This format is referred to as "5.0" in Pro Tools. | |||||
| */ | |||||
| static AudioChannelSet create5point0(); | |||||
| /** Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE). | |||||
| Is equivalent to: k51 (VST), AAX_eStemFormat_5_1 (AAX), kAudioChannelLayoutTag_MPEG_5_1_A (CoreAudio) | |||||
| This format is referred to as "5.1 (ITU 775)" in Logic Pro. | |||||
| This format is referred to as "5.1" in Cubase. | |||||
| This format is referred to as "5.1" in Pro Tools. | |||||
| */ | |||||
| static AudioChannelSet create5point1(); | |||||
| /** Creates a set for a 6.0 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround). | |||||
| Is equivalent to: k60Cine (VST), AAX_eStemFormat_6_0 (AAX), kAudioChannelLayoutTag_AudioUnit_6_0 (CoreAudio) | |||||
| Logic Pro incorrectly uses this for the surround format labeled "6.1 (ES/EX)". | |||||
| This format is referred to as "6.0 Cine" in Cubase. | |||||
| This format is referred to as "6.0" in Pro Tools. | |||||
| */ | |||||
| static AudioChannelSet create6point0(); | |||||
| /** Creates a set for a 6.1 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround, LFE). | |||||
| Is equivalent to: k61Cine (VST), AAX_eStemFormat_6_1 (AAX), kAudioChannelLayoutTag_MPEG_6_1_A (CoreAudio) | |||||
| This format is referred to as "6.1" in Pro Tools. | |||||
| */ | |||||
| static AudioChannelSet create6point1(); | |||||
| /** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide). | |||||
| Is equivalent to: k60Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_0_A (CoreAudio) | |||||
| This format is referred to as "6.0 Music" in Cubase. | |||||
| */ | |||||
| static AudioChannelSet create6point0Music(); | |||||
| /** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, LFE). | |||||
| Is equivalent to: k61Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_1_A (CoreAudio) | |||||
| */ | |||||
| static AudioChannelSet create6point1Music(); | |||||
| /** Creates a set for a DTS 7.0 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear). | |||||
| Is equivalent to: k70Music (VST), AAX_eStemFormat_7_0_DTS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0 (CoreAudio) | |||||
| This format is referred to as "7.0" in Pro Tools. | |||||
| */ | |||||
| static AudioChannelSet create7point0(); | |||||
| /** Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre). | |||||
| Is equivalent to: k70Cine (VST), AAX_eStemFormat_7_0_SDDS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0_Front (CoreAudio) | |||||
| This format is referred to as "7.0 SDDS" in Pro Tools. | |||||
| */ | |||||
| static AudioChannelSet create7point0SDDS(); | |||||
| /** Creates a set for a DTS 7.1 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE). | |||||
| Is equivalent to: k71CineSideFill (VST), AAX_eStemFormat_7_1_DTS (AAX), kAudioChannelLayoutTag_MPEG_7_1_C/kAudioChannelLayoutTag_ITU_3_4_1 (CoreAudio) | |||||
| This format is referred to as "7.1 (3/4.1)" in Logic Pro. | |||||
| This format is referred to as "7.1" in Pro Tools. | |||||
| */ | |||||
| static AudioChannelSet create7point1(); | |||||
| /** Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE). | |||||
| Is equivalent to: k71Cine (VST), AAX_eStemFormat_7_1_SDDS (AAX), kAudioChannelLayoutTag_MPEG_7_1_A (CoreAudio) | |||||
| This format is referred to as "7.1 (SDDS)" in Logic Pro. | |||||
| This format is referred to as "7.1 SDDS" in Pro Tools. | |||||
| */ | |||||
| static AudioChannelSet create7point1SDDS(); | |||||
| //============================================================================== | |||||
| /** Creates a set for ambisonic surround setups (ambisonicW, ambisonicX, ambisonicY, ambisonicZ). | |||||
| Is equivalent to: kBFormat (VST), n/a (AAX), kAudioChannelLayoutTag_Ambisonic_B_Format (CoreAudio) | |||||
| */ | |||||
| static AudioChannelSet ambisonic(); | |||||
| /** Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround) | |||||
| Is equivalent to: k40Music (VST), AAX_eStemFormat_Quad (AAX), kAudioChannelLayoutTag_Quadraphonic (CoreAudio) | |||||
| This format is referred to as "Quadraphonic" in Logic Pro. | |||||
| This format is referred to as "Quadro" in Cubase. | |||||
| This format is referred to as "Quad" in Pro Tools. | |||||
| */ | |||||
| static AudioChannelSet quadraphonic(); | |||||
| /** Creates a set for pentagonal surround setup (left, right, centre, leftSurroundRear, rightSurroundRear). | |||||
| Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Pentagonal (CoreAudio) | |||||
| */ | |||||
| static AudioChannelSet pentagonal(); | |||||
| /** Creates a set for hexagonal surround setup (left, right, leftSurroundRear, rightSurroundRear, centre, surroundCentre). | |||||
| Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Hexagonal (CoreAudio) | |||||
| */ | |||||
| static AudioChannelSet hexagonal(); | |||||
| /** Creates a set for octagonal surround setup (left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight). | |||||
| Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Octagonal (CoreAudio) | |||||
| */ | |||||
| static AudioChannelSet octagonal(); | |||||
| //============================================================================== | |||||
| /** Creates a set of untyped discrete channels. */ | |||||
| static AudioChannelSet discreteChannels (int numChannels); | |||||
| /** Create a canonical channel set for a given number of channels. | |||||
| For example, numChannels = 1 will return mono, numChannels = 2 will return stereo, etc. */ | |||||
| static AudioChannelSet canonicalChannelSet (int numChannels); | |||||
| /** Create a channel set for a given number of channels which is non-discrete. | |||||
| If numChannels is larger than the number of channels of the surround format | |||||
| with the maximum amount of channels (currently 7.1 Surround), then this | |||||
| function returns an empty set.*/ | |||||
| static AudioChannelSet namedChannelSet (int numChannels); | |||||
| /** Return an array of channel sets which have a given number of channels */ | |||||
| static Array<AudioChannelSet> channelSetsWithNumberOfChannels (int numChannels); | |||||
| //============================================================================== | |||||
| /** Represents different audio channel types. */ | |||||
| enum ChannelType | |||||
| { | |||||
| unknown = 0, | |||||
| left = 1, // L | |||||
| right = 2, // R | |||||
| centre = 3, // C (sometimes M for mono) | |||||
| LFE = 4, | |||||
| leftSurround = 5, // Ls | |||||
| rightSurround = 6, // Rs | |||||
| leftCentre = 7, // Lc (AAX/VST), Lc used as Lss in AU for most layouts | |||||
| rightCentre = 8, // Rc (AAX/VST), Rc used as Rss in AU for most layouts | |||||
| centreSurround = 9, // Cs/S | |||||
| surround = centreSurround, // Cs/S | |||||
| leftSurroundSide = 10, // Lss (AXX), Side Left "Sl" (VST), Left Centre "LC" (AU) | |||||
| rightSurroundSide = 11, // Rss (AXX), Side right "Sr" (VST), Right Centre "Rc" (AU) | |||||
| topMiddle = 12, | |||||
| topFrontLeft = 13, | |||||
| topFrontCentre = 14, | |||||
| topFrontRight = 15, | |||||
| topRearLeft = 16, | |||||
| topRearCentre = 17, | |||||
| topRearRight = 18, | |||||
| LFE2 = 19, | |||||
| leftSurroundRear = 20, // Lsr (AAX), Lcs (VST), Rls (AU) | |||||
| rightSurroundRear = 21, // Rsr (AAX), Rcs (VST), Rrs (AU) | |||||
| wideLeft = 22, | |||||
| wideRight = 23, | |||||
| ambisonicW = 24, | |||||
| ambisonicX = 25, | |||||
| ambisonicY = 26, | |||||
| ambisonicZ = 27, | |||||
| discreteChannel0 = 64 /**< Non-typed individual channels are indexed upwards from this value. */ | |||||
| }; | |||||
| /** Returns the name of a given channel type. For example, this method may return "Surround Left". */ | |||||
| static String getChannelTypeName (ChannelType); | |||||
| /** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */ | |||||
| static String getAbbreviatedChannelTypeName (ChannelType); | |||||
| //============================================================================== | |||||
| enum | |||||
| { | |||||
| maxChannelsOfNamedLayout = 8 | |||||
| }; | |||||
| /** Adds a channel to the set. */ | |||||
| void addChannel (ChannelType newChannelType); | |||||
| /** Removes a channel from the set. */ | |||||
| void removeChannel (ChannelType newChannelType); | |||||
| /** Returns the number of channels in the set. */ | |||||
| int size() const noexcept; | |||||
| /** Returns true if there are no channels in the set. */ | |||||
| bool isDisabled() const noexcept { return size() == 0; } | |||||
| /** Returns an array of all the types in this channel set. */ | |||||
| Array<ChannelType> getChannelTypes() const; | |||||
| /** Returns the type of one of the channels in the set, by index. */ | |||||
| ChannelType getTypeOfChannel (int channelIndex) const noexcept; | |||||
| /** Returns the index for a particular channel-type. | |||||
| Will return -1 if the this set does not contain a channel of this type. */ | |||||
| int getChannelIndexForType (ChannelType type) const noexcept; | |||||
| /** Returns a string containing a whitespace-separated list of speaker types | |||||
| corresponding to each channel. For example in a 5.1 arrangement, | |||||
| the string may be "L R C Lfe Ls Rs". If the speaker arrangement is unknown, | |||||
| the returned string will be empty.*/ | |||||
| String getSpeakerArrangementAsString() const; | |||||
| /** Returns the description of the current layout. For example, this method may return | |||||
| "Quadraphonic". Note that the returned string may not be unique. */ | |||||
| String getDescription() const; | |||||
| /** Returns if this is a channel layout made-up of discrete channels. */ | |||||
| bool isDiscreteLayout() const noexcept; | |||||
| /** Intersect two channel layouts. */ | |||||
| void intersect (const AudioChannelSet& other) { channels &= other.channels; } | |||||
| //============================================================================== | |||||
| bool operator== (const AudioChannelSet&) const noexcept; | |||||
| bool operator!= (const AudioChannelSet&) const noexcept; | |||||
| bool operator< (const AudioChannelSet&) const noexcept; | |||||
| private: | |||||
| BigInteger channels; | |||||
| explicit AudioChannelSet (uint32); | |||||
| }; | |||||
| #endif // JUCE_AUDIOCHANNELSET_H_INCLUDED | |||||
| @@ -397,9 +397,9 @@ public: | |||||
| convert between 32 and 64 bit float buffer types. | convert between 32 and 64 bit float buffer types. | ||||
| */ | */ | ||||
| template <typename OtherType> | template <typename OtherType> | ||||
| void makeCopyOf (const AudioBuffer<OtherType>& other) | |||||
| void makeCopyOf (const AudioBuffer<OtherType>& other, bool avoidReallocating = false) | |||||
| { | { | ||||
| setSize (other.getNumChannels(), other.getNumSamples()); | |||||
| setSize (other.getNumChannels(), other.getNumSamples(), false, false, avoidReallocating); | |||||
| if (other.hasBeenCleared()) | if (other.hasBeenCleared()) | ||||
| { | { | ||||
| @@ -204,10 +204,11 @@ namespace FloatVectorHelpers | |||||
| typedef float Type; | typedef float Type; | ||||
| typedef float32x4_t ParallelType; | typedef float32x4_t ParallelType; | ||||
| typedef uint32x4_t IntegerType; | typedef uint32x4_t IntegerType; | ||||
| union signMaskUnion { ParallelType f; IntegerType i; }; | |||||
| enum { numParallel = 4 }; | enum { numParallel = 4 }; | ||||
| static forcedinline IntegerType toint (ParallelType v) noexcept { union { ParallelType f; IntegerType i; } u; u.f = v; return u.i; } | |||||
| static forcedinline ParallelType toflt (IntegerType v) noexcept { union { ParallelType f; IntegerType i; } u; u.i = v; return u.f; } | |||||
| static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } | |||||
| static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } | |||||
| static forcedinline ParallelType load1 (Type v) noexcept { return vld1q_dup_f32 (&v); } | static forcedinline ParallelType load1 (Type v) noexcept { return vld1q_dup_f32 (&v); } | ||||
| static forcedinline ParallelType loadA (const Type* v) noexcept { return vld1q_f32 (v); } | static forcedinline ParallelType loadA (const Type* v) noexcept { return vld1q_f32 (v); } | ||||
| @@ -235,10 +236,11 @@ namespace FloatVectorHelpers | |||||
| typedef double Type; | typedef double Type; | ||||
| typedef double ParallelType; | typedef double ParallelType; | ||||
| typedef uint64 IntegerType; | typedef uint64 IntegerType; | ||||
| union signMaskUnion { ParallelType f; IntegerType i; }; | |||||
| enum { numParallel = 1 }; | enum { numParallel = 1 }; | ||||
| static forcedinline IntegerType toint (ParallelType v) noexcept { union { ParallelType f; IntegerType i; } u; u.f = v; return u.i; } | |||||
| static forcedinline ParallelType toflt (IntegerType v) noexcept { union { ParallelType f; IntegerType i; } u; u.i = v; return u.f; } | |||||
| static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } | |||||
| static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } | |||||
| static forcedinline ParallelType load1 (Type v) noexcept { return v; } | static forcedinline ParallelType load1 (Type v) noexcept { return v; } | ||||
| static forcedinline ParallelType loadA (const Type* v) noexcept { return *v; } | static forcedinline ParallelType loadA (const Type* v) noexcept { return *v; } | ||||
| @@ -346,6 +348,9 @@ namespace FloatVectorHelpers | |||||
| #define JUCE_LOAD_SRC1_SRC2_DEST(src1Load, src2Load, dstLoad) const Mode::ParallelType d = dstLoad (dest), s1 = src1Load (src1), s2 = src2Load (src2); | #define JUCE_LOAD_SRC1_SRC2_DEST(src1Load, src2Load, dstLoad) const Mode::ParallelType d = dstLoad (dest), s1 = src1Load (src1), s2 = src2Load (src2); | ||||
| #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src); | #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src); | ||||
| union signMask32 { float f; uint32 i; }; | |||||
| union signMask64 { double d; uint64 i; }; | |||||
| #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | ||||
| template<int typeSize> struct ModeType { typedef BasicOps32 Mode; }; | template<int typeSize> struct ModeType { typedef BasicOps32 Mode; }; | ||||
| template<> struct ModeType<8> { typedef BasicOps64 Mode; }; | template<> struct ModeType<8> { typedef BasicOps64 Mode; }; | ||||
| @@ -481,6 +486,17 @@ namespace FloatVectorHelpers | |||||
| #endif | #endif | ||||
| } | } | ||||
| //============================================================================== | |||||
| namespace | |||||
| { | |||||
| #if JUCE_USE_VDSP_FRAMEWORK | |||||
| // This casts away constness to account for slightly different vDSP function signatures | |||||
| // in OSX 10.8 SDK and below. Can be safely removed once those SDKs are obsolete. | |||||
| template <typename ValueType> | |||||
| ValueType* osx108sdkCompatibilityCast (const ValueType* arg) noexcept { return const_cast<ValueType*> (arg); } | |||||
| #endif | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept | void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept | ||||
| { | { | ||||
| @@ -568,10 +584,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double amount, int | |||||
| const Mode::ParallelType amountToAdd = Mode::load1 (amount);) | const Mode::ParallelType amountToAdd = Mode::load1 (amount);) | ||||
| } | } | ||||
| void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float amount, int num) noexcept | |||||
| void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, float amount, int num) noexcept | |||||
| { | { | ||||
| #if JUCE_USE_VDSP_FRAMEWORK | #if JUCE_USE_VDSP_FRAMEWORK | ||||
| vDSP_vsadd (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||||
| vDSP_vsadd (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); | |||||
| #else | #else | ||||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | ||||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | ||||
| @@ -579,10 +595,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float am | |||||
| #endif | #endif | ||||
| } | } | ||||
| void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double* src, double amount, int num) noexcept | |||||
| void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src, double amount, int num) noexcept | |||||
| { | { | ||||
| #if JUCE_USE_VDSP_FRAMEWORK | #if JUCE_USE_VDSP_FRAMEWORK | ||||
| vDSP_vsaddD (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||||
| vDSP_vsaddD (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); | |||||
| #else | #else | ||||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | ||||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | ||||
| @@ -795,7 +811,7 @@ void FloatVectorOperations::abs (float* dest, const float* src, int num) noexcep | |||||
| #if JUCE_USE_VDSP_FRAMEWORK | #if JUCE_USE_VDSP_FRAMEWORK | ||||
| vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num); | vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num); | ||||
| #else | #else | ||||
| union { float f; uint32 i; } signMask; | |||||
| FloatVectorHelpers::signMask32 signMask; | |||||
| signMask.i = 0x7fffffffUL; | signMask.i = 0x7fffffffUL; | ||||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabsf (src[i]), Mode::bit_and (s, mask), | JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabsf (src[i]), Mode::bit_and (s, mask), | ||||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | ||||
| @@ -810,7 +826,7 @@ void FloatVectorOperations::abs (double* dest, const double* src, int num) noexc | |||||
| #if JUCE_USE_VDSP_FRAMEWORK | #if JUCE_USE_VDSP_FRAMEWORK | ||||
| vDSP_vabsD ((double*) src, 1, dest, 1, (vDSP_Length) num); | vDSP_vabsD ((double*) src, 1, dest, 1, (vDSP_Length) num); | ||||
| #else | #else | ||||
| union {double d; uint64 i;} signMask; | |||||
| FloatVectorHelpers::signMask64 signMask; | |||||
| signMask.i = 0x7fffffffffffffffULL; | signMask.i = 0x7fffffffffffffffULL; | ||||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabs (src[i]), Mode::bit_and (s, mask), | JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabs (src[i]), Mode::bit_and (s, mask), | ||||
| @@ -991,7 +1007,7 @@ void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnab | |||||
| void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport() noexcept | void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport() noexcept | ||||
| { | { | ||||
| #if JUCE_USE_SSE_INTRINSICS | #if JUCE_USE_SSE_INTRINSICS | ||||
| const int mxcsr = _mm_getcsr(); | |||||
| const unsigned int mxcsr = _mm_getcsr(); | |||||
| _mm_setcsr (mxcsr | 0x8040); // add the DAZ and FZ bits | _mm_setcsr (mxcsr | 0x8040); // add the DAZ and FZ bits | ||||
| #endif | #endif | ||||
| } | } | ||||
| @@ -66,10 +66,10 @@ public: | |||||
| static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; | static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; | ||||
| /** Adds a fixed value to each source value and stores it in the destination array. */ | /** Adds a fixed value to each source value and stores it in the destination array. */ | ||||
| static void JUCE_CALLTYPE add (float* dest, float* src, float amount, int numValues) noexcept; | |||||
| static void JUCE_CALLTYPE add (float* dest, const float* src, float amount, int numValues) noexcept; | |||||
| /** Adds a fixed value to each source value and stores it in the destination array. */ | /** Adds a fixed value to each source value and stores it in the destination array. */ | ||||
| static void JUCE_CALLTYPE add (double* dest, double* src, double amount, int numValues) noexcept; | |||||
| static void JUCE_CALLTYPE add (double* dest, const double* src, double amount, int numValues) noexcept; | |||||
| /** Adds the source values to the destination values. */ | /** Adds the source values to the destination values. */ | ||||
| static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | ||||
| @@ -22,7 +22,6 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| struct CatmullRomAlgorithm | struct CatmullRomAlgorithm | ||||
| { | { | ||||
| static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept | static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept | ||||
| @@ -22,7 +22,6 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| /** | /** | ||||
| Interpolator for resampling a stream of floats using Catmull-Rom interpolation. | Interpolator for resampling a stream of floats using Catmull-Rom interpolation. | ||||
| @@ -62,33 +62,131 @@ IIRCoefficients::IIRCoefficients (double c1, double c2, double c3, | |||||
| IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | ||||
| const double frequency) noexcept | const double frequency) noexcept | ||||
| { | { | ||||
| jassert (sampleRate > 0); | |||||
| return makeLowPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | |||||
| const double frequency, | |||||
| const double Q) noexcept | |||||
| { | |||||
| jassert (sampleRate > 0.0); | |||||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
| jassert (Q > 0.0); | |||||
| const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | ||||
| const double nSquared = n * n; | const double nSquared = n * n; | ||||
| const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||||
| const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
| return IIRCoefficients (c1, | return IIRCoefficients (c1, | ||||
| c1 * 2.0, | c1 * 2.0, | ||||
| c1, | c1, | ||||
| 1.0, | 1.0, | ||||
| c1 * 2.0 * (1.0 - nSquared), | c1 * 2.0 * (1.0 - nSquared), | ||||
| c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
| c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||||
| } | } | ||||
| IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, | IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, | ||||
| const double frequency) noexcept | const double frequency) noexcept | ||||
| { | { | ||||
| return makeHighPass (sampleRate, frequency, 1.0 / std::sqrt(2.0)); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, | |||||
| const double frequency, | |||||
| const double Q) noexcept | |||||
| { | |||||
| jassert (sampleRate > 0.0); | |||||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
| jassert (Q > 0.0); | |||||
| const double n = std::tan (double_Pi * frequency / sampleRate); | const double n = std::tan (double_Pi * frequency / sampleRate); | ||||
| const double nSquared = n * n; | const double nSquared = n * n; | ||||
| const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||||
| const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
| return IIRCoefficients (c1, | return IIRCoefficients (c1, | ||||
| c1 * -2.0, | c1 * -2.0, | ||||
| c1, | c1, | ||||
| 1.0, | 1.0, | ||||
| c1 * 2.0 * (nSquared - 1.0), | c1 * 2.0 * (nSquared - 1.0), | ||||
| c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||||
| c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeBandPass (const double sampleRate, | |||||
| const double frequency) noexcept | |||||
| { | |||||
| return makeBandPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeBandPass (const double sampleRate, | |||||
| const double frequency, | |||||
| const double Q) noexcept | |||||
| { | |||||
| jassert (sampleRate > 0.0); | |||||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
| jassert (Q > 0.0); | |||||
| const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||||
| const double nSquared = n * n; | |||||
| const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
| return IIRCoefficients (c1 * n / Q, | |||||
| 0.0, | |||||
| -c1 * n / Q, | |||||
| 1.0, | |||||
| c1 * 2.0 * (1.0 - nSquared), | |||||
| c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeNotchFilter (const double sampleRate, | |||||
| const double frequency) noexcept | |||||
| { | |||||
| return makeNotchFilter (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeNotchFilter (const double sampleRate, | |||||
| const double frequency, | |||||
| const double Q) noexcept | |||||
| { | |||||
| jassert (sampleRate > 0.0); | |||||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
| jassert (Q > 0.0); | |||||
| const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||||
| const double nSquared = n * n; | |||||
| const double c1 = 1.0 / (1.0 + n / Q + nSquared); | |||||
| return IIRCoefficients (c1 * (1.0 + nSquared), | |||||
| 2.0 * c1 * (1.0 - nSquared), | |||||
| c1 * (1.0 + nSquared), | |||||
| 1.0, | |||||
| c1 * 2.0 * (1.0 - nSquared), | |||||
| c1 * (1.0 - n / Q + nSquared)); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeAllPass (const double sampleRate, | |||||
| const double frequency) noexcept | |||||
| { | |||||
| return makeAllPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||||
| } | |||||
| IIRCoefficients IIRCoefficients::makeAllPass (const double sampleRate, | |||||
| const double frequency, | |||||
| const double Q) noexcept | |||||
| { | |||||
| jassert (sampleRate > 0.0); | |||||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
| jassert (Q > 0.0); | |||||
| const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||||
| const double nSquared = n * n; | |||||
| const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||||
| return IIRCoefficients (c1 * (1.0 - n / Q + nSquared), | |||||
| c1 * 2.0 * (1.0 - nSquared), | |||||
| 1.0, | |||||
| 1.0, | |||||
| c1 * 2.0 * (1.0 - nSquared), | |||||
| c1 * (1.0 - n / Q + nSquared)); | |||||
| } | } | ||||
| IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, | IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, | ||||
| @@ -96,8 +194,9 @@ IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, | |||||
| const double Q, | const double Q, | ||||
| const float gainFactor) noexcept | const float gainFactor) noexcept | ||||
| { | { | ||||
| jassert (sampleRate > 0); | |||||
| jassert (Q > 0); | |||||
| jassert (sampleRate > 0.0); | |||||
| jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); | |||||
| jassert (Q > 0.0); | |||||
| const double A = jmax (0.0f, std::sqrt (gainFactor)); | const double A = jmax (0.0f, std::sqrt (gainFactor)); | ||||
| const double aminus1 = A - 1.0; | const double aminus1 = A - 1.0; | ||||
| @@ -120,8 +219,9 @@ IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate, | |||||
| const double Q, | const double Q, | ||||
| const float gainFactor) noexcept | const float gainFactor) noexcept | ||||
| { | { | ||||
| jassert (sampleRate > 0); | |||||
| jassert (Q > 0); | |||||
| jassert (sampleRate > 0.0); | |||||
| jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); | |||||
| jassert (Q > 0.0); | |||||
| const double A = jmax (0.0f, std::sqrt (gainFactor)); | const double A = jmax (0.0f, std::sqrt (gainFactor)); | ||||
| const double aminus1 = A - 1.0; | const double aminus1 = A - 1.0; | ||||
| @@ -140,15 +240,16 @@ IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate, | |||||
| } | } | ||||
| IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, | IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, | ||||
| const double centreFrequency, | |||||
| const double frequency, | |||||
| const double Q, | const double Q, | ||||
| const float gainFactor) noexcept | const float gainFactor) noexcept | ||||
| { | { | ||||
| jassert (sampleRate > 0); | |||||
| jassert (Q > 0); | |||||
| jassert (sampleRate > 0.0); | |||||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||||
| jassert (Q > 0.0); | |||||
| const double A = jmax (0.0f, std::sqrt (gainFactor)); | const double A = jmax (0.0f, std::sqrt (gainFactor)); | ||||
| const double omega = (double_Pi * 2.0 * jmax (centreFrequency, 2.0)) / sampleRate; | |||||
| const double omega = (double_Pi * 2.0 * jmax (frequency, 2.0)) / sampleRate; | |||||
| const double alpha = 0.5 * std::sin (omega) / Q; | const double alpha = 0.5 * std::sin (omega) / Q; | ||||
| const double c2 = -2.0 * std::cos (omega); | const double c2 = -2.0 * std::cos (omega); | ||||
| const double alphaTimesA = alpha * A; | const double alphaTimesA = alpha * A; | ||||
| @@ -164,12 +265,12 @@ IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, | |||||
| //============================================================================== | //============================================================================== | ||||
| IIRFilter::IIRFilter() noexcept | IIRFilter::IIRFilter() noexcept | ||||
| : v1 (0), v2 (0), active (false) | |||||
| : v1 (0.0), v2 (0.0), active (false) | |||||
| { | { | ||||
| } | } | ||||
| IIRFilter::IIRFilter (const IIRFilter& other) noexcept | IIRFilter::IIRFilter (const IIRFilter& other) noexcept | ||||
| : v1 (0), v2 (0), active (other.active) | |||||
| : v1 (0.0), v2 (0.0), active (other.active) | |||||
| { | { | ||||
| const SpinLock::ScopedLockType sl (other.processLock); | const SpinLock::ScopedLockType sl (other.processLock); | ||||
| coefficients = other.coefficients; | coefficients = other.coefficients; | ||||
| @@ -198,7 +299,7 @@ void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcep | |||||
| void IIRFilter::reset() noexcept | void IIRFilter::reset() noexcept | ||||
| { | { | ||||
| const SpinLock::ScopedLockType sl (processLock); | const SpinLock::ScopedLockType sl (processLock); | ||||
| v1 = v2 = 0; | |||||
| v1 = v2 = 0.0; | |||||
| } | } | ||||
| float IIRFilter::processSingleSampleRaw (const float in) noexcept | float IIRFilter::processSingleSampleRaw (const float in) noexcept | ||||
| @@ -55,14 +55,53 @@ public: | |||||
| /** Destructor. */ | /** Destructor. */ | ||||
| ~IIRCoefficients() noexcept; | ~IIRCoefficients() noexcept; | ||||
| //============================================================================== | |||||
| /** Returns the coefficients for a low-pass filter. */ | /** Returns the coefficients for a low-pass filter. */ | ||||
| static IIRCoefficients makeLowPass (double sampleRate, | static IIRCoefficients makeLowPass (double sampleRate, | ||||
| double frequency) noexcept; | double frequency) noexcept; | ||||
| /** Returns the coefficients for a low-pass filter with variable Q. */ | |||||
| static IIRCoefficients makeLowPass (double sampleRate, | |||||
| double frequency, | |||||
| double Q) noexcept; | |||||
| //============================================================================== | |||||
| /** Returns the coefficients for a high-pass filter. */ | /** Returns the coefficients for a high-pass filter. */ | ||||
| static IIRCoefficients makeHighPass (double sampleRate, | static IIRCoefficients makeHighPass (double sampleRate, | ||||
| double frequency) noexcept; | double frequency) noexcept; | ||||
| /** Returns the coefficients for a high-pass filter with variable Q. */ | |||||
| static IIRCoefficients makeHighPass (double sampleRate, | |||||
| double frequency, | |||||
| double Q) noexcept; | |||||
| //============================================================================== | |||||
| /** Returns the coefficients for a band-pass filter. */ | |||||
| static IIRCoefficients makeBandPass (double sampleRate, double frequency) noexcept; | |||||
| /** Returns the coefficients for a band-pass filter with variable Q. */ | |||||
| static IIRCoefficients makeBandPass (double sampleRate, | |||||
| double frequency, | |||||
| double Q) noexcept; | |||||
| //============================================================================== | |||||
| /** Returns the coefficients for a notch filter. */ | |||||
| static IIRCoefficients makeNotchFilter (double sampleRate, double frequency) noexcept; | |||||
| /** Returns the coefficients for a notch filter with variable Q. */ | |||||
| static IIRCoefficients makeNotchFilter (double sampleRate, | |||||
| double frequency, | |||||
| double Q) noexcept; | |||||
| //============================================================================== | |||||
| /** Returns the coefficients for an all-pass filter. */ | |||||
| static IIRCoefficients makeAllPass (double sampleRate, double frequency) noexcept; | |||||
| /** Returns the coefficients for an all-pass filter with variable Q. */ | |||||
| static IIRCoefficients makeAllPass (double sampleRate, | |||||
| double frequency, | |||||
| double Q) noexcept; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Returns the coefficients for a low-pass shelf filter with variable Q and gain. | /** Returns the coefficients for a low-pass shelf filter with variable Q and gain. | ||||
| @@ -22,7 +22,6 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| /** | /** | ||||
| Interpolator for resampling a stream of floats using 4-point lagrange interpolation. | Interpolator for resampling a stream of floats using 4-point lagrange interpolation. | ||||
| @@ -33,8 +33,8 @@ | |||||
| */ | */ | ||||
| //============================================================================== | //============================================================================== | ||||
| template<typename FloatType> | |||||
| class JUCE_API LinearSmoothedValue | |||||
| template <typename FloatType> | |||||
| class LinearSmoothedValue | |||||
| { | { | ||||
| public: | public: | ||||
| /** Constructor. */ | /** Constructor. */ | ||||
| @@ -59,7 +59,6 @@ public: | |||||
| countdown = 0; | countdown = 0; | ||||
| } | } | ||||
| //============================================================================== | |||||
| /** Set a new target value. */ | /** Set a new target value. */ | ||||
| void setValue (FloatType newValue) noexcept | void setValue (FloatType newValue) noexcept | ||||
| { | { | ||||
| @@ -75,7 +74,6 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| //============================================================================== | |||||
| /** Compute the next value. */ | /** Compute the next value. */ | ||||
| FloatType getNextValue() noexcept | FloatType getNextValue() noexcept | ||||
| { | { | ||||
| @@ -87,6 +85,18 @@ public: | |||||
| return currentValue; | return currentValue; | ||||
| } | } | ||||
| /** Returns true if the current value is currently being interpolated. */ | |||||
| bool isSmoothing() const noexcept | |||||
| { | |||||
| return countdown > 0; | |||||
| } | |||||
| /** Returns the target value towards which the smoothed value is currently moving. */ | |||||
| FloatType getTargetValue() const noexcept | |||||
| { | |||||
| return target; | |||||
| } | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| FloatType currentValue, target, step; | FloatType currentValue, target, step; | ||||
| @@ -31,6 +31,8 @@ | |||||
| #error "Incorrect use of JUCE cpp file" | #error "Incorrect use of JUCE cpp file" | ||||
| #endif | #endif | ||||
| #include "AppConfig.h" | |||||
| #include "juce_audio_basics.h" | #include "juce_audio_basics.h" | ||||
| #if JUCE_MINGW && ! defined (__SSE2__) | #if JUCE_MINGW && ! defined (__SSE2__) | ||||
| @@ -67,6 +69,13 @@ | |||||
| #define JUCE_USE_ARM_NEON 1 | #define JUCE_USE_ARM_NEON 1 | ||||
| #endif | #endif | ||||
| #if TARGET_IPHONE_SIMULATOR | |||||
| #ifdef JUCE_USE_ARM_NEON | |||||
| #undef JUCE_USE_ARM_NEON | |||||
| #endif | |||||
| #define JUCE_USE_ARM_NEON 0 | |||||
| #endif | |||||
| #if JUCE_USE_ARM_NEON | #if JUCE_USE_ARM_NEON | ||||
| #include <arm_neon.h> | #include <arm_neon.h> | ||||
| #endif | #endif | ||||
| @@ -76,6 +85,7 @@ namespace juce | |||||
| #include "buffers/juce_AudioDataConverters.cpp" | #include "buffers/juce_AudioDataConverters.cpp" | ||||
| #include "buffers/juce_FloatVectorOperations.cpp" | #include "buffers/juce_FloatVectorOperations.cpp" | ||||
| #include "buffers/juce_AudioChannelSet.cpp" | |||||
| #include "effects/juce_IIRFilter.cpp" | #include "effects/juce_IIRFilter.cpp" | ||||
| #include "effects/juce_IIRFilterOld.cpp" | #include "effects/juce_IIRFilterOld.cpp" | ||||
| #include "effects/juce_LagrangeInterpolator.cpp" | #include "effects/juce_LagrangeInterpolator.cpp" | ||||
| @@ -22,12 +22,37 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| /******************************************************************************* | |||||
| The block below describes the properties of this module, and is read by | |||||
| the Projucer to automatically generate project code that uses it. | |||||
| For details about the syntax and how to create or use a module, see the | |||||
| JUCE Module Format.txt file. | |||||
| BEGIN_JUCE_MODULE_DECLARATION | |||||
| ID: juce_audio_basics | |||||
| vendor: juce | |||||
| version: 4.3.0 | |||||
| name: JUCE audio and MIDI data classes | |||||
| description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | |||||
| website: http://www.juce.com/juce | |||||
| license: GPL/Commercial | |||||
| dependencies: juce_core | |||||
| OSXFrameworks: Accelerate | |||||
| iOSFrameworks: Accelerate | |||||
| END_JUCE_MODULE_DECLARATION | |||||
| *******************************************************************************/ | |||||
| #ifndef JUCE_AUDIO_BASICS_H_INCLUDED | #ifndef JUCE_AUDIO_BASICS_H_INCLUDED | ||||
| #define JUCE_AUDIO_BASICS_H_INCLUDED | #define JUCE_AUDIO_BASICS_H_INCLUDED | ||||
| #include "../juce_core/juce_core.h" | |||||
| #include "juce_core/juce_core.h" | |||||
| //============================================================================== | |||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| @@ -37,6 +62,7 @@ namespace juce | |||||
| #include "buffers/juce_AudioDataConverters.h" | #include "buffers/juce_AudioDataConverters.h" | ||||
| #include "buffers/juce_FloatVectorOperations.h" | #include "buffers/juce_FloatVectorOperations.h" | ||||
| #include "buffers/juce_AudioSampleBuffer.h" | #include "buffers/juce_AudioSampleBuffer.h" | ||||
| #include "buffers/juce_AudioChannelSet.h" | |||||
| #include "effects/juce_Decibels.h" | #include "effects/juce_Decibels.h" | ||||
| #include "effects/juce_IIRFilter.h" | #include "effects/juce_IIRFilter.h" | ||||
| #include "effects/juce_IIRFilterOld.h" | #include "effects/juce_IIRFilterOld.h" | ||||
| @@ -248,7 +248,7 @@ bool MidiFile::readFrom (InputStream& sourceStream) | |||||
| clear(); | clear(); | ||||
| MemoryBlock data; | MemoryBlock data; | ||||
| const int maxSensibleMidiFileSize = 2 * 1024 * 1024; | |||||
| const int maxSensibleMidiFileSize = 200 * 1024 * 1024; | |||||
| // (put a sanity-check on the file size, as midi files are generally small) | // (put a sanity-check on the file size, as midi files are generally small) | ||||
| if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | ||||
| @@ -33,11 +33,23 @@ namespace MidiHelpers | |||||
| { | { | ||||
| return (uint8) jlimit (0, 127, v); | return (uint8) jlimit (0, 127, v); | ||||
| } | } | ||||
| } | |||||
| inline uint8 floatVelocityToByte (const float v) noexcept | |||||
| { | |||||
| return validVelocity (roundToInt (v * 127.0f)); | |||||
| } | |||||
| //============================================================================== | |||||
| uint8 MidiMessage::floatValueToMidiByte (const float v) noexcept | |||||
| { | |||||
| return MidiHelpers::validVelocity (roundToInt (v * 127.0f)); | |||||
| } | |||||
| uint16 MidiMessage::pitchbendToPitchwheelPos (const float pitchbend, | |||||
| const float pitchbendRange) noexcept | |||||
| { | |||||
| // can't translate a pitchbend value that is outside of the given range! | |||||
| jassert (std::abs (pitchbend) <= pitchbendRange); | |||||
| return static_cast<uint16> (pitchbend > 0.0f | |||||
| ? jmap (pitchbend, 0.0f, pitchbendRange, 8192.0f, 16383.0f) | |||||
| : jmap (pitchbend, -pitchbendRange, 0.0f, 0.0f, 8192.0f)); | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -84,25 +96,24 @@ int MidiMessage::getMessageLengthFromFirstByte (const uint8 firstByte) noexcept | |||||
| MidiMessage::MidiMessage() noexcept | MidiMessage::MidiMessage() noexcept | ||||
| : timeStamp (0), size (2) | : timeStamp (0), size (2) | ||||
| { | { | ||||
| preallocatedData.asBytes[0] = 0xf0; | |||||
| preallocatedData.asBytes[1] = 0xf7; | |||||
| packedData.asBytes[0] = 0xf0; | |||||
| packedData.asBytes[1] = 0xf7; | |||||
| } | } | ||||
| MidiMessage::MidiMessage (const void* const d, const int dataSize, const double t) | MidiMessage::MidiMessage (const void* const d, const int dataSize, const double t) | ||||
| : timeStamp (t), | |||||
| size (dataSize) | |||||
| : timeStamp (t), size (dataSize) | |||||
| { | { | ||||
| jassert (dataSize > 0); | jassert (dataSize > 0); | ||||
| memcpy (allocateSpace (dataSize), d, (size_t) dataSize); | |||||
| // this checks that the length matches the data.. | |||||
| jassert (dataSize > 3 || *(uint8*)d >= 0xf0 || getMessageLengthFromFirstByte (*(uint8*)d) == size); | |||||
| // check that the length matches the data.. | |||||
| jassert (size > 3 || *(uint8*)d >= 0xf0 || getMessageLengthFromFirstByte (*(uint8*)d) == size); | |||||
| memcpy (allocateSpace (dataSize), d, (size_t) dataSize); | |||||
| } | } | ||||
| MidiMessage::MidiMessage (const int byte1, const double t) noexcept | MidiMessage::MidiMessage (const int byte1, const double t) noexcept | ||||
| : timeStamp (t), size (1) | : timeStamp (t), size (1) | ||||
| { | { | ||||
| preallocatedData.asBytes[0] = (uint8) byte1; | |||||
| packedData.asBytes[0] = (uint8) byte1; | |||||
| // check that the length matches the data.. | // check that the length matches the data.. | ||||
| jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 1); | jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 1); | ||||
| @@ -111,8 +122,8 @@ MidiMessage::MidiMessage (const int byte1, const double t) noexcept | |||||
| MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noexcept | MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noexcept | ||||
| : timeStamp (t), size (2) | : timeStamp (t), size (2) | ||||
| { | { | ||||
| preallocatedData.asBytes[0] = (uint8) byte1; | |||||
| preallocatedData.asBytes[1] = (uint8) byte2; | |||||
| packedData.asBytes[0] = (uint8) byte1; | |||||
| packedData.asBytes[1] = (uint8) byte2; | |||||
| // check that the length matches the data.. | // check that the length matches the data.. | ||||
| jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 2); | jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 2); | ||||
| @@ -121,9 +132,9 @@ MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noex | |||||
| MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, const double t) noexcept | MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, const double t) noexcept | ||||
| : timeStamp (t), size (3) | : timeStamp (t), size (3) | ||||
| { | { | ||||
| preallocatedData.asBytes[0] = (uint8) byte1; | |||||
| preallocatedData.asBytes[1] = (uint8) byte2; | |||||
| preallocatedData.asBytes[2] = (uint8) byte3; | |||||
| packedData.asBytes[0] = (uint8) byte1; | |||||
| packedData.asBytes[1] = (uint8) byte2; | |||||
| packedData.asBytes[2] = (uint8) byte3; | |||||
| // check that the length matches the data.. | // check that the length matches the data.. | ||||
| jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 3); | jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 3); | ||||
| @@ -132,29 +143,19 @@ MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, con | |||||
| MidiMessage::MidiMessage (const MidiMessage& other) | MidiMessage::MidiMessage (const MidiMessage& other) | ||||
| : timeStamp (other.timeStamp), size (other.size) | : timeStamp (other.timeStamp), size (other.size) | ||||
| { | { | ||||
| if (other.allocatedData != nullptr) | |||||
| { | |||||
| allocatedData.malloc ((size_t) size); | |||||
| memcpy (allocatedData, other.allocatedData, (size_t) size); | |||||
| } | |||||
| if (isHeapAllocated()) | |||||
| memcpy (allocateSpace (size), other.getData(), (size_t) size); | |||||
| else | else | ||||
| { | |||||
| preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||||
| } | |||||
| packedData.allocatedData = other.packedData.allocatedData; | |||||
| } | } | ||||
| MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) | MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) | ||||
| : timeStamp (newTimeStamp), size (other.size) | : timeStamp (newTimeStamp), size (other.size) | ||||
| { | { | ||||
| if (other.allocatedData != nullptr) | |||||
| { | |||||
| allocatedData.malloc ((size_t) size); | |||||
| memcpy (allocatedData, other.allocatedData, (size_t) size); | |||||
| } | |||||
| if (isHeapAllocated()) | |||||
| memcpy (allocateSpace (size), other.getData(), (size_t) size); | |||||
| else | else | ||||
| { | |||||
| preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||||
| } | |||||
| packedData.allocatedData = other.packedData.allocatedData; | |||||
| } | } | ||||
| MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, | MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, | ||||
| @@ -229,16 +230,15 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| preallocatedData.asInt32 = 0; | |||||
| size = getMessageLengthFromFirstByte ((uint8) byte); | size = getMessageLengthFromFirstByte ((uint8) byte); | ||||
| preallocatedData.asBytes[0] = (uint8) byte; | |||||
| packedData.asBytes[0] = (uint8) byte; | |||||
| if (size > 1) | if (size > 1) | ||||
| { | { | ||||
| preallocatedData.asBytes[1] = src[0]; | |||||
| packedData.asBytes[1] = src[0]; | |||||
| if (size > 2) | if (size > 2) | ||||
| preallocatedData.asBytes[2] = src[1]; | |||||
| packedData.asBytes[2] = src[1]; | |||||
| } | } | ||||
| } | } | ||||
| @@ -246,7 +246,7 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| preallocatedData.asInt32 = 0; | |||||
| packedData.allocatedData = nullptr; | |||||
| size = 0; | size = 0; | ||||
| } | } | ||||
| } | } | ||||
| @@ -255,19 +255,25 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& other) | |||||
| { | { | ||||
| if (this != &other) | if (this != &other) | ||||
| { | { | ||||
| timeStamp = other.timeStamp; | |||||
| size = other.size; | |||||
| if (other.allocatedData != nullptr) | |||||
| if (other.isHeapAllocated()) | |||||
| { | { | ||||
| allocatedData.malloc ((size_t) size); | |||||
| memcpy (allocatedData, other.allocatedData, (size_t) size); | |||||
| if (isHeapAllocated()) | |||||
| packedData.allocatedData = static_cast<uint8*> (std::realloc (packedData.allocatedData, (size_t) other.size)); | |||||
| else | |||||
| packedData.allocatedData = static_cast<uint8*> (std::malloc ((size_t) other.size)); | |||||
| memcpy (packedData.allocatedData, other.packedData.allocatedData, (size_t) other.size); | |||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| allocatedData.free(); | |||||
| preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||||
| if (isHeapAllocated()) | |||||
| std::free (packedData.allocatedData); | |||||
| packedData.allocatedData = other.packedData.allocatedData; | |||||
| } | } | ||||
| timeStamp = other.timeStamp; | |||||
| size = other.size; | |||||
| } | } | ||||
| return *this; | return *this; | ||||
| @@ -277,36 +283,61 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& other) | |||||
| MidiMessage::MidiMessage (MidiMessage&& other) noexcept | MidiMessage::MidiMessage (MidiMessage&& other) noexcept | ||||
| : timeStamp (other.timeStamp), size (other.size) | : timeStamp (other.timeStamp), size (other.size) | ||||
| { | { | ||||
| if (other.allocatedData != nullptr) | |||||
| allocatedData.swapWith (other.allocatedData); | |||||
| else | |||||
| preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||||
| packedData.allocatedData = other.packedData.allocatedData; | |||||
| other.size = 0; | |||||
| } | } | ||||
| MidiMessage& MidiMessage::operator= (MidiMessage&& other) noexcept | MidiMessage& MidiMessage::operator= (MidiMessage&& other) noexcept | ||||
| { | { | ||||
| jassert (this != &other); // shouldn't be possible | |||||
| packedData.allocatedData = other.packedData.allocatedData; | |||||
| timeStamp = other.timeStamp; | timeStamp = other.timeStamp; | ||||
| size = other.size; | size = other.size; | ||||
| allocatedData.swapWith (other.allocatedData); | |||||
| preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||||
| other.size = 0; | |||||
| return *this; | return *this; | ||||
| } | } | ||||
| #endif | #endif | ||||
| MidiMessage::~MidiMessage() {} | |||||
| MidiMessage::~MidiMessage() noexcept | |||||
| { | |||||
| if (isHeapAllocated()) | |||||
| std::free (packedData.allocatedData); | |||||
| } | |||||
| uint8* MidiMessage::allocateSpace (int bytes) | uint8* MidiMessage::allocateSpace (int bytes) | ||||
| { | { | ||||
| if (bytes > 4) | |||||
| if (bytes > (int) sizeof (packedData)) | |||||
| { | |||||
| uint8* d = static_cast<uint8*> (std::malloc ((size_t) bytes)); | |||||
| packedData.allocatedData = d; | |||||
| return d; | |||||
| } | |||||
| return packedData.asBytes; | |||||
| } | |||||
| String MidiMessage::getDescription() const | |||||
| { | |||||
| if (isNoteOn()) return "Note on " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); | |||||
| if (isNoteOff()) return "Note off " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); | |||||
| if (isProgramChange()) return "Program change " + String (getProgramChangeNumber()) + " Channel " + String (getChannel()); | |||||
| if (isPitchWheel()) return "Pitch wheel " + String (getPitchWheelValue()) + " Channel " + String (getChannel()); | |||||
| if (isAftertouch()) return "Aftertouch " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + ": " + String (getAfterTouchValue()) + " Channel " + String (getChannel()); | |||||
| if (isChannelPressure()) return "Channel pressure " + String (getChannelPressureValue()) + " Channel " + String (getChannel()); | |||||
| if (isAllNotesOff()) return "All notes off Channel " + String (getChannel()); | |||||
| if (isAllSoundOff()) return "All sound off Channel " + String (getChannel()); | |||||
| if (isMetaEvent()) return "Meta event"; | |||||
| if (isController()) | |||||
| { | { | ||||
| allocatedData.malloc ((size_t) bytes); | |||||
| return allocatedData; | |||||
| String name (MidiMessage::getControllerName (getControllerNumber())); | |||||
| if (name.isEmpty()) | |||||
| name = String (getControllerNumber()); | |||||
| return "Controller " + name + ": " + String (getControllerValue()) + " Channel " + String (getChannel()); | |||||
| } | } | ||||
| return preallocatedData.asBytes; | |||||
| return String::toHexString (getRawData(), getRawDataSize()); | |||||
| } | } | ||||
| int MidiMessage::getChannel() const noexcept | int MidiMessage::getChannel() const noexcept | ||||
| @@ -391,7 +422,7 @@ float MidiMessage::getFloatVelocity() const noexcept | |||||
| void MidiMessage::setVelocity (const float newVelocity) noexcept | void MidiMessage::setVelocity (const float newVelocity) noexcept | ||||
| { | { | ||||
| if (isNoteOnOrOff()) | if (isNoteOnOrOff()) | ||||
| getData()[2] = MidiHelpers::floatVelocityToByte (newVelocity); | |||||
| getData()[2] = floatValueToMidiByte (newVelocity); | |||||
| } | } | ||||
| void MidiMessage::multiplyVelocity (const float scaleFactor) noexcept | void MidiMessage::multiplyVelocity (const float scaleFactor) noexcept | ||||
| @@ -538,7 +569,7 @@ MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const | |||||
| MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) noexcept | MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) noexcept | ||||
| { | { | ||||
| return noteOn (channel, noteNumber, MidiHelpers::floatVelocityToByte (velocity)); | |||||
| return noteOn (channel, noteNumber, floatValueToMidiByte (velocity)); | |||||
| } | } | ||||
| MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 velocity) noexcept | MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 velocity) noexcept | ||||
| @@ -552,7 +583,7 @@ MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 | |||||
| MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, float velocity) noexcept | MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, float velocity) noexcept | ||||
| { | { | ||||
| return noteOff (channel, noteNumber, MidiHelpers::floatVelocityToByte (velocity)); | |||||
| return noteOff (channel, noteNumber, floatValueToMidiByte (velocity)); | |||||
| } | } | ||||
| MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) noexcept | MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) noexcept | ||||
| @@ -697,9 +728,10 @@ MidiMessage MidiMessage::textMetaEvent (int type, StringRef text) | |||||
| header[--n] = 0xff; | header[--n] = 0xff; | ||||
| const size_t headerLen = sizeof (header) - n; | const size_t headerLen = sizeof (header) - n; | ||||
| const int totalSize = (int) (headerLen + textSize); | |||||
| uint8* const dest = result.allocateSpace ((int) (headerLen + textSize)); | |||||
| result.size = (int) (headerLen + textSize); | |||||
| uint8* const dest = result.allocateSpace (totalSize); | |||||
| result.size = totalSize; | |||||
| memcpy (dest, header + n, headerLen); | memcpy (dest, header + n, headerLen); | ||||
| memcpy (dest + headerLen, text.text.getAddress(), textSize); | memcpy (dest + headerLen, text.text.getAddress(), textSize); | ||||
| @@ -816,7 +848,7 @@ bool MidiMessage::isKeySignatureMetaEvent() const noexcept | |||||
| int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const noexcept | int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const noexcept | ||||
| { | { | ||||
| return (int) getMetaEventData()[0]; | |||||
| return (int) (int8) getMetaEventData()[0]; | |||||
| } | } | ||||
| bool MidiMessage::isKeySignatureMajorKey() const noexcept | bool MidiMessage::isKeySignatureMajorKey() const noexcept | ||||
| @@ -985,7 +1017,7 @@ String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctav | |||||
| return String(); | return String(); | ||||
| } | } | ||||
| double MidiMessage::getMidiNoteInHertz (int noteNumber, const double frequencyOfA) noexcept | |||||
| double MidiMessage::getMidiNoteInHertz (const int noteNumber, const double frequencyOfA) noexcept | |||||
| { | { | ||||
| return frequencyOfA * pow (2.0, (noteNumber - 69) / 12.0); | return frequencyOfA * pow (2.0, (noteNumber - 69) / 12.0); | ||||
| } | } | ||||
| @@ -104,7 +104,7 @@ public: | |||||
| MidiMessage (const MidiMessage&, double newTimeStamp); | MidiMessage (const MidiMessage&, double newTimeStamp); | ||||
| /** Destructor. */ | /** Destructor. */ | ||||
| ~MidiMessage(); | |||||
| ~MidiMessage() noexcept; | |||||
| /** Copies this message from another one. */ | /** Copies this message from another one. */ | ||||
| MidiMessage& operator= (const MidiMessage& other); | MidiMessage& operator= (const MidiMessage& other); | ||||
| @@ -118,13 +118,19 @@ public: | |||||
| /** Returns a pointer to the raw midi data. | /** Returns a pointer to the raw midi data. | ||||
| @see getRawDataSize | @see getRawDataSize | ||||
| */ | */ | ||||
| const uint8* getRawData() const noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; } | |||||
| const uint8* getRawData() const noexcept { return getData(); } | |||||
| /** Returns the number of bytes of data in the message. | /** Returns the number of bytes of data in the message. | ||||
| @see getRawData | @see getRawData | ||||
| */ | */ | ||||
| int getRawDataSize() const noexcept { return size; } | int getRawDataSize() const noexcept { return size; } | ||||
| //============================================================================== | |||||
| /** Returns a human-readable description of the midi message as a string, | |||||
| for example "Note On C#3 Velocity 120 Channel 1". | |||||
| */ | |||||
| String getDescription() const; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Returns the timestamp associated with this message. | /** Returns the timestamp associated with this message. | ||||
| @@ -845,7 +851,7 @@ public: | |||||
| The value passed in must be 0x80 or higher. | The value passed in must be 0x80 or higher. | ||||
| */ | */ | ||||
| static int getMessageLengthFromFirstByte (const uint8 firstByte) noexcept; | |||||
| static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Returns the name of a midi note number. | /** Returns the name of a midi note number. | ||||
| @@ -872,7 +878,7 @@ public: | |||||
| The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. | The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. | ||||
| @see getMidiNoteName | @see getMidiNoteName | ||||
| */ | */ | ||||
| static double getMidiNoteInHertz (int noteNumber, const double frequencyOfA = 440.0) noexcept; | |||||
| static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept; | |||||
| /** Returns true if the given midi note number is a black key. */ | /** Returns true if the given midi note number is a black key. */ | ||||
| static bool isMidiNoteBlack (int noteNumber) noexcept; | static bool isMidiNoteBlack (int noteNumber) noexcept; | ||||
| @@ -899,21 +905,29 @@ public: | |||||
| */ | */ | ||||
| static const char* getControllerName (int controllerNumber); | static const char* getControllerName (int controllerNumber); | ||||
| /** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */ | |||||
| static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept; | |||||
| /** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */ | |||||
| static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones, | |||||
| float pitchbendRangeInSemitones) noexcept; | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| double timeStamp; | |||||
| HeapBlock<uint8> allocatedData; | |||||
| int size; | |||||
| #ifndef DOXYGEN | #ifndef DOXYGEN | ||||
| union | |||||
| union PackedData | |||||
| { | { | ||||
| uint8 asBytes[4]; | |||||
| uint32 asInt32; | |||||
| } preallocatedData; | |||||
| uint8* allocatedData; | |||||
| uint8 asBytes[sizeof (uint8*)]; | |||||
| }; | |||||
| PackedData packedData; | |||||
| double timeStamp; | |||||
| int size; | |||||
| #endif | #endif | ||||
| inline uint8* getData() noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; } | |||||
| inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } | |||||
| inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; } | |||||
| uint8* allocateSpace (int); | uint8* allocateSpace (int); | ||||
| }; | }; | ||||
| @@ -80,7 +80,7 @@ int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const noexcep | |||||
| return -1; | return -1; | ||||
| } | } | ||||
| int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const noexcept | |||||
| int MidiMessageSequence::getIndexOf (const MidiEventHolder* const event) const noexcept | |||||
| { | { | ||||
| return list.indexOf (event); | return list.indexOf (event); | ||||
| } | } | ||||
| @@ -48,6 +48,18 @@ public: | |||||
| /** Replaces this sequence with another one. */ | /** Replaces this sequence with another one. */ | ||||
| MidiMessageSequence& operator= (const MidiMessageSequence&); | MidiMessageSequence& operator= (const MidiMessageSequence&); | ||||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||||
| MidiMessageSequence (MidiMessageSequence&& other) noexcept | |||||
| : list (static_cast<OwnedArray<MidiEventHolder>&&> (other.list)) | |||||
| {} | |||||
| MidiMessageSequence& operator= (MidiMessageSequence&& other) noexcept | |||||
| { | |||||
| list = static_cast<OwnedArray<MidiEventHolder>&&> (other.list); | |||||
| return *this; | |||||
| } | |||||
| #endif | |||||
| /** Destructor. */ | /** Destructor. */ | ||||
| ~MidiMessageSequence(); | ~MidiMessageSequence(); | ||||
| @@ -109,7 +121,7 @@ public: | |||||
| int getIndexOfMatchingKeyUp (int index) const noexcept; | int getIndexOfMatchingKeyUp (int index) const noexcept; | ||||
| /** Returns the index of an event. */ | /** Returns the index of an event. */ | ||||
| int getIndexOf (MidiEventHolder* event) const noexcept; | |||||
| int getIndexOf (const MidiEventHolder* event) const noexcept; | |||||
| /** Returns the index of the first event on or after the given timestamp. | /** Returns the index of the first event on or after the given timestamp. | ||||
| If the time is beyond the end of the sequence, this will return the | If the time is beyond the end of the sequence, this will return the | ||||
| @@ -22,7 +22,6 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| MidiRPNDetector::MidiRPNDetector() noexcept | MidiRPNDetector::MidiRPNDetector() noexcept | ||||
| { | { | ||||
| } | } | ||||
| @@ -55,7 +54,7 @@ void MidiRPNDetector::reset() noexcept | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| MidiRPNDetector::ChannelState::ChannelState () noexcept | |||||
| MidiRPNDetector::ChannelState::ChannelState() noexcept | |||||
| : parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false) | : parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false) | ||||
| { | { | ||||
| } | } | ||||
| @@ -39,6 +39,9 @@ MPEInstrument::MPEInstrument() noexcept | |||||
| pressureDimension.value = &MPENote::pressure; | pressureDimension.value = &MPENote::pressure; | ||||
| timbreDimension.value = &MPENote::timbre; | timbreDimension.value = &MPENote::timbre; | ||||
| // the default value for pressure is 0, for all other dimension it is centre (= default MPEValue) | |||||
| std::fill_n (pressureDimension.lastValueReceivedOnChannel, 16, MPEValue::minValue()); | |||||
| legacyMode.isEnabled = false; | legacyMode.isEnabled = false; | ||||
| legacyMode.pitchbendRange = 2; | legacyMode.pitchbendRange = 2; | ||||
| legacyMode.channelRange = Range<int> (1, 17); | legacyMode.channelRange = Range<int> (1, 17); | ||||
| @@ -271,22 +274,6 @@ void MPEInstrument::handleTimbreLSB (int midiChannel, int value) noexcept | |||||
| lastTimbreLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value); | lastTimbreLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value); | ||||
| } | } | ||||
| //============================================================================== | |||||
| MPEValue MPEInstrument::getInitialPitchbendForNoteOn (int midiChannel, int /*midiNoteNumber*/, MPEValue /*midiNoteOnVelocity*/) const | |||||
| { | |||||
| return pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1]; | |||||
| } | |||||
| MPEValue MPEInstrument::getInitialPressureForNoteOn (int /*midiChannel*/, int /*midiNoteNumber*/, MPEValue midiNoteOnVelocity) const | |||||
| { | |||||
| return midiNoteOnVelocity; | |||||
| } | |||||
| MPEValue MPEInstrument::getInitialTimbreForNoteOn (int midiChannel, int /*midiNoteNumber*/, MPEValue /*midiNoteOnVelocity*/) const | |||||
| { | |||||
| return timbreDimension.lastValueReceivedOnChannel[midiChannel - 1]; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| void MPEInstrument::noteOn (int midiChannel, | void MPEInstrument::noteOn (int midiChannel, | ||||
| int midiNoteNumber, | int midiNoteNumber, | ||||
| @@ -298,9 +285,9 @@ void MPEInstrument::noteOn (int midiChannel, | |||||
| MPENote newNote (midiChannel, | MPENote newNote (midiChannel, | ||||
| midiNoteNumber, | midiNoteNumber, | ||||
| midiNoteOnVelocity, | midiNoteOnVelocity, | ||||
| getInitialPitchbendForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), | |||||
| getInitialPressureForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), | |||||
| getInitialTimbreForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), | |||||
| getInitialValueForNewNote (midiChannel, pitchbendDimension), | |||||
| getInitialValueForNewNote (midiChannel, pressureDimension), | |||||
| getInitialValueForNewNote (midiChannel, timbreDimension), | |||||
| isNoteChannelSustained[midiChannel - 1] ? MPENote::keyDownAndSustained : MPENote::keyDown); | isNoteChannelSustained[midiChannel - 1] ? MPENote::keyDownAndSustained : MPENote::keyDown); | ||||
| const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
| @@ -324,7 +311,7 @@ void MPEInstrument::noteOff (int midiChannel, | |||||
| int midiNoteNumber, | int midiNoteNumber, | ||||
| MPEValue midiNoteOffVelocity) | MPEValue midiNoteOffVelocity) | ||||
| { | { | ||||
| if (notes.empty() || ! isNoteChannel (midiChannel)) | |||||
| if (notes.isEmpty() || ! isNoteChannel (midiChannel)) | |||||
| return; | return; | ||||
| const ScopedLock sl (lock); | const ScopedLock sl (lock); | ||||
| @@ -334,10 +321,11 @@ void MPEInstrument::noteOff (int midiChannel, | |||||
| note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off; | note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off; | ||||
| note->noteOffVelocity = midiNoteOffVelocity; | note->noteOffVelocity = midiNoteOffVelocity; | ||||
| // last pitchbend and timbre values received for this note should not be re-used for | |||||
| // last dimension values received for this note should not be re-used for | |||||
| // any new notes, so reset them: | // any new notes, so reset them: | ||||
| pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue(); | |||||
| timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue(); | |||||
| pressureDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::minValue(); | |||||
| pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); | |||||
| timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); | |||||
| if (note->keyState == MPENote::off) | if (note->keyState == MPENote::off) | ||||
| { | { | ||||
| @@ -370,12 +358,20 @@ void MPEInstrument::timbre (int midiChannel, MPEValue value) | |||||
| updateDimension (midiChannel, timbreDimension, value); | updateDimension (midiChannel, timbreDimension, value); | ||||
| } | } | ||||
| MPEValue MPEInstrument::getInitialValueForNewNote (int midiChannel, MPEDimension& dimension) const | |||||
| { | |||||
| if (getLastNotePlayedPtr (midiChannel) != nullptr) | |||||
| return &dimension == &pressureDimension ? MPEValue::minValue() : MPEValue::centreValue(); | |||||
| return dimension.lastValueReceivedOnChannel[midiChannel - 1]; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, MPEValue value) | void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, MPEValue value) | ||||
| { | { | ||||
| dimension.lastValueReceivedOnChannel[midiChannel - 1] = value; | dimension.lastValueReceivedOnChannel[midiChannel - 1] = value; | ||||
| if (notes.empty()) | |||||
| if (notes.isEmpty()) | |||||
| return; | return; | ||||
| if (MPEZone* zone = zoneLayout.getZoneByMasterChannel (midiChannel)) | if (MPEZone* zone = zoneLayout.getZoneByMasterChannel (midiChannel)) | ||||
| @@ -758,7 +754,7 @@ public: | |||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
| expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
| expectEquals (test.noteAddedCallCounter, 1); | expectEquals (test.noteAddedCallCounter, 1); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| // note-off | // note-off | ||||
| test.noteOff (3, 60, MPEValue::from7BitInt (33)); | test.noteOff (3, 60, MPEValue::from7BitInt (33)); | ||||
| @@ -774,13 +770,13 @@ public: | |||||
| // note off with non-matching note number shouldn't do anything | // note off with non-matching note number shouldn't do anything | ||||
| test.noteOff (3, 61, MPEValue::from7BitInt (33)); | test.noteOff (3, 61, MPEValue::from7BitInt (33)); | ||||
| expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteReleasedCallCounter, 0); | expectEquals (test.noteReleasedCallCounter, 0); | ||||
| // note off with non-matching midi channel shouldn't do anything | // note off with non-matching midi channel shouldn't do anything | ||||
| test.noteOff (2, 60, MPEValue::from7BitInt (33)); | test.noteOff (2, 60, MPEValue::from7BitInt (33)); | ||||
| expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteReleasedCallCounter, 0); | expectEquals (test.noteReleasedCallCounter, 0); | ||||
| } | } | ||||
| { | { | ||||
| @@ -791,9 +787,9 @@ public: | |||||
| test.noteOn (3, 1, MPEValue::from7BitInt (100)); | test.noteOn (3, 1, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 2, MPEValue::from7BitInt (100)); | test.noteOn (3, 2, MPEValue::from7BitInt (100)); | ||||
| expectEquals (test.getNumPlayingNotes(), 3); | expectEquals (test.getNumPlayingNotes(), 3); | ||||
| expectNote (test.getNote (3, 0), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 1), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 2), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 0), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 1), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 2), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| } | } | ||||
| { | { | ||||
| // pathological case: second note-on for same note should retrigger it. | // pathological case: second note-on for same note should retrigger it. | ||||
| @@ -802,7 +798,7 @@ public: | |||||
| test.noteOn (3, 0, MPEValue::from7BitInt (100)); | test.noteOn (3, 0, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 0, MPEValue::from7BitInt (60)); | test.noteOn (3, 0, MPEValue::from7BitInt (60)); | ||||
| expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
| expectNote (test.getNote (3, 0), 60, 60, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 0), 60, 0, 8192, 64, MPENote::keyDown); | |||||
| } | } | ||||
| } | } | ||||
| @@ -844,42 +840,42 @@ public: | |||||
| // sustain pedal on per-note channel shouldn't do anything. | // sustain pedal on per-note channel shouldn't do anything. | ||||
| test.sustainPedal (3, true); | test.sustainPedal (3, true); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 0); | expectEquals (test.noteKeyStateChangedCallCounter, 0); | ||||
| // sustain pedal on non-zone channel shouldn't do anything either. | // sustain pedal on non-zone channel shouldn't do anything either. | ||||
| test.sustainPedal (1, true); | test.sustainPedal (1, true); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 0); | expectEquals (test.noteKeyStateChangedCallCounter, 0); | ||||
| // sustain pedal on master channel should sustain notes on *that* zone. | // sustain pedal on master channel should sustain notes on *that* zone. | ||||
| test.sustainPedal (2, true); | test.sustainPedal (2, true); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 1); | expectEquals (test.noteKeyStateChangedCallCounter, 1); | ||||
| // release | // release | ||||
| test.sustainPedal (2, false); | test.sustainPedal (2, false); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 2); | expectEquals (test.noteKeyStateChangedCallCounter, 2); | ||||
| // should also sustain new notes added after the press | // should also sustain new notes added after the press | ||||
| test.sustainPedal (2, true); | test.sustainPedal (2, true); | ||||
| expectEquals (test.noteKeyStateChangedCallCounter, 3); | expectEquals (test.noteKeyStateChangedCallCounter, 3); | ||||
| test.noteOn (4, 51, MPEValue::from7BitInt (100)); | test.noteOn (4, 51, MPEValue::from7BitInt (100)); | ||||
| expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||||
| expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 3); | expectEquals (test.noteKeyStateChangedCallCounter, 3); | ||||
| // ...but only if that sustain came on the master channel of that zone! | // ...but only if that sustain came on the master channel of that zone! | ||||
| test.sustainPedal (11, true); | test.sustainPedal (11, true); | ||||
| test.noteOn (11, 52, MPEValue::from7BitInt (100)); | test.noteOn (11, 52, MPEValue::from7BitInt (100)); | ||||
| expectNote (test.getNote (11, 52), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (11, 52), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| test.noteOff (11, 52, MPEValue::from7BitInt (100)); | test.noteOff (11, 52, MPEValue::from7BitInt (100)); | ||||
| expectEquals (test.noteReleasedCallCounter, 1); | expectEquals (test.noteReleasedCallCounter, 1); | ||||
| @@ -890,8 +886,8 @@ public: | |||||
| expectEquals (test.getNumPlayingNotes(), 2); | expectEquals (test.getNumPlayingNotes(), 2); | ||||
| expectEquals (test.noteReleasedCallCounter, 2); | expectEquals (test.noteReleasedCallCounter, 2); | ||||
| expectEquals (test.noteKeyStateChangedCallCounter, 5); | expectEquals (test.noteKeyStateChangedCallCounter, 5); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
| expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::sustained); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
| expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::sustained); | |||||
| // notes should be turned off when pedal is released | // notes should be turned off when pedal is released | ||||
| test.sustainPedal (2, false); | test.sustainPedal (2, false); | ||||
| @@ -908,26 +904,26 @@ public: | |||||
| // sostenuto pedal on per-note channel shouldn't do anything. | // sostenuto pedal on per-note channel shouldn't do anything. | ||||
| test.sostenutoPedal (3, true); | test.sostenutoPedal (3, true); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 0); | expectEquals (test.noteKeyStateChangedCallCounter, 0); | ||||
| // sostenuto pedal on non-zone channel shouldn't do anything either. | // sostenuto pedal on non-zone channel shouldn't do anything either. | ||||
| test.sostenutoPedal (1, true); | test.sostenutoPedal (1, true); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 0); | expectEquals (test.noteKeyStateChangedCallCounter, 0); | ||||
| // sostenuto pedal on master channel should sustain notes on *that* zone. | // sostenuto pedal on master channel should sustain notes on *that* zone. | ||||
| test.sostenutoPedal (2, true); | test.sostenutoPedal (2, true); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 1); | expectEquals (test.noteKeyStateChangedCallCounter, 1); | ||||
| // release | // release | ||||
| test.sostenutoPedal (2, false); | test.sostenutoPedal (2, false); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 2); | expectEquals (test.noteKeyStateChangedCallCounter, 2); | ||||
| // should only sustain notes turned on *before* the press (difference to sustain pedal) | // should only sustain notes turned on *before* the press (difference to sustain pedal) | ||||
| @@ -935,9 +931,9 @@ public: | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 3); | expectEquals (test.noteKeyStateChangedCallCounter, 3); | ||||
| test.noteOn (4, 51, MPEValue::from7BitInt (100)); | test.noteOn (4, 51, MPEValue::from7BitInt (100)); | ||||
| expectEquals (test.getNumPlayingNotes(), 3); | expectEquals (test.getNumPlayingNotes(), 3); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||||
| expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||||
| expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 3); | expectEquals (test.noteKeyStateChangedCallCounter, 3); | ||||
| // note-off should not turn off sustained notes inside the same zone, | // note-off should not turn off sustained notes inside the same zone, | ||||
| @@ -946,7 +942,7 @@ public: | |||||
| test.noteOff (4, 51, MPEValue::from7BitInt (100)); | test.noteOff (4, 51, MPEValue::from7BitInt (100)); | ||||
| test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected! | test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected! | ||||
| expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
| expectEquals (test.noteReleasedCallCounter, 2); | expectEquals (test.noteReleasedCallCounter, 2); | ||||
| expectEquals (test.noteKeyStateChangedCallCounter, 4); | expectEquals (test.noteKeyStateChangedCallCounter, 4); | ||||
| @@ -1047,22 +1043,22 @@ public: | |||||
| // applying pressure on a per-note channel should modulate one note | // applying pressure on a per-note channel should modulate one note | ||||
| test.pressure (3, MPEValue::from7BitInt (33)); | test.pressure (3, MPEValue::from7BitInt (33)); | ||||
| expectNote (test.getNote (3, 60), 100, 33, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 60), 100, 33, 8192, 64, MPENote::keyDown); | ||||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
| // applying pressure on a master channel should modulate all notes in this zone | // applying pressure on a master channel should modulate all notes in this zone | ||||
| test.pressure (2, MPEValue::from7BitInt (44)); | test.pressure (2, MPEValue::from7BitInt (44)); | ||||
| expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); | ||||
| expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); | ||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePressureChangedCallCounter, 3); | expectEquals (test.notePressureChangedCallCounter, 3); | ||||
| // applying pressure on an unrelated channel should be ignored | // applying pressure on an unrelated channel should be ignored | ||||
| test.pressure (1, MPEValue::from7BitInt (55)); | test.pressure (1, MPEValue::from7BitInt (55)); | ||||
| expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); | ||||
| expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); | ||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePressureChangedCallCounter, 3); | expectEquals (test.notePressureChangedCallCounter, 3); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1073,7 +1069,7 @@ public: | |||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.pressure (3, MPEValue::from7BitInt (66)); | test.pressure (3, MPEValue::from7BitInt (66)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 66, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 61), 100, 66, 8192, 64, MPENote::keyDown); | ||||
| expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
| } | } | ||||
| @@ -1091,6 +1087,49 @@ public: | |||||
| expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); | ||||
| expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
| } | } | ||||
| { | |||||
| UnitTestInstrument test; | |||||
| test.setZoneLayout (testLayout); | |||||
| // if no pressure is sent before note-on, default = 0 should be used | |||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| } | |||||
| { | |||||
| UnitTestInstrument test; | |||||
| test.setZoneLayout (testLayout); | |||||
| // if pressure is sent before note-on, use that | |||||
| test.pressure (3, MPEValue::from7BitInt (77)); | |||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||||
| expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); | |||||
| } | |||||
| { | |||||
| UnitTestInstrument test; | |||||
| test.setZoneLayout (testLayout); | |||||
| // if pressure is sent before note-on, but it belonged to another note | |||||
| // on the same channel that has since been turned off, use default = 0 | |||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||||
| test.pressure (3, MPEValue::from7BitInt (77)); | |||||
| test.noteOff (3, 61, MPEValue::from7BitInt (100)); | |||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| } | |||||
| { | |||||
| UnitTestInstrument test; | |||||
| test.setZoneLayout (testLayout); | |||||
| // edge case: two notes on the same channel simultaneously. the second one should use | |||||
| // pressure = 0 initially but then react to additional pressure messages | |||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||||
| test.pressure (3, MPEValue::from7BitInt (77)); | |||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| test.pressure (3, MPEValue::from7BitInt (78)); | |||||
| expectNote (test.getNote (3, 60), 100, 78, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 77, 8192, 64, MPENote::keyDown); | |||||
| } | |||||
| } | } | ||||
| beginTest ("pitchbend"); | beginTest ("pitchbend"); | ||||
| @@ -1105,9 +1144,9 @@ public: | |||||
| // applying pitchbend on a per-note channel should modulate one note | // applying pitchbend on a per-note channel should modulate one note | ||||
| test.pitchbend (3, MPEValue::from14BitInt (1111)); | test.pitchbend (3, MPEValue::from14BitInt (1111)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
| // applying pitchbend on a master channel should be ignored for the | // applying pitchbend on a master channel should be ignored for the | ||||
| @@ -1115,16 +1154,16 @@ public: | |||||
| // Note: noteChanged will be called anyway for notes in that zone | // Note: noteChanged will be called anyway for notes in that zone | ||||
| // because the total pitchbend for those notes has changed | // because the total pitchbend for those notes has changed | ||||
| test.pitchbend (2, MPEValue::from14BitInt (2222)); | test.pitchbend (2, MPEValue::from14BitInt (2222)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePitchbendChangedCallCounter, 3); | expectEquals (test.notePitchbendChangedCallCounter, 3); | ||||
| // applying pitchbend on an unrelated channel should do nothing. | // applying pitchbend on an unrelated channel should do nothing. | ||||
| test.pitchbend (1, MPEValue::from14BitInt (3333)); | test.pitchbend (1, MPEValue::from14BitInt (3333)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePitchbendChangedCallCounter, 3); | expectEquals (test.notePitchbendChangedCallCounter, 3); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1135,8 +1174,8 @@ public: | |||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (3, MPEValue::from14BitInt (4444)); | test.pitchbend (3, MPEValue::from14BitInt (4444)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 4444, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 4444, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1150,7 +1189,7 @@ public: | |||||
| test.noteOff (3, 61, MPEValue::from7BitInt (100)); | test.noteOff (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (3, MPEValue::from14BitInt (5555)); | test.pitchbend (3, MPEValue::from14BitInt (5555)); | ||||
| expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 5555, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1169,14 +1208,14 @@ public: | |||||
| test.sustainPedal (2, true); | test.sustainPedal (2, true); | ||||
| test.noteOff (3, 60, MPEValue::from7BitInt (64)); | test.noteOff (3, 60, MPEValue::from7BitInt (64)); | ||||
| expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
| expectEquals (test.noteKeyStateChangedCallCounter, 2); | expectEquals (test.noteKeyStateChangedCallCounter, 2); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (3, MPEValue::from14BitInt (6666)); | test.pitchbend (3, MPEValue::from14BitInt (6666)); | ||||
| expectEquals (test.getNumPlayingNotes(), 2); | expectEquals (test.getNumPlayingNotes(), 2); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 6666, 64, MPENote::keyDownAndSustained); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 6666, 64, MPENote::keyDownAndSustained); | |||||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1193,11 +1232,11 @@ public: | |||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (3, MPEValue::from14BitInt (5555)); | test.pitchbend (3, MPEValue::from14BitInt (5555)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 5555, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); | |||||
| test.noteOff (3, 60, MPEValue::from7BitInt (100)); | test.noteOff (3, 60, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| } | } | ||||
| { | { | ||||
| // applying per-note pitchbend should set the note's totalPitchbendInSemitones | // applying per-note pitchbend should set the note's totalPitchbendInSemitones | ||||
| @@ -1289,23 +1328,23 @@ public: | |||||
| // modulating timbre on a per-note channel should modulate one note | // modulating timbre on a per-note channel should modulate one note | ||||
| test.timbre (3, MPEValue::from7BitInt (33)); | test.timbre (3, MPEValue::from7BitInt (33)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 33, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 33, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
| // modulating timbre on a master channel should modulate all notes in this zone | // modulating timbre on a master channel should modulate all notes in this zone | ||||
| test.timbre (2, MPEValue::from7BitInt (44)); | test.timbre (2, MPEValue::from7BitInt (44)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteTimbreChangedCallCounter, 3); | expectEquals (test.noteTimbreChangedCallCounter, 3); | ||||
| // modulating timbre on an unrelated channel should be ignored | // modulating timbre on an unrelated channel should be ignored | ||||
| test.timbre (1, MPEValue::from7BitInt (55)); | test.timbre (1, MPEValue::from7BitInt (55)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteTimbreChangedCallCounter, 3); | expectEquals (test.noteTimbreChangedCallCounter, 3); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1316,8 +1355,8 @@ public: | |||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.timbre (3, MPEValue::from7BitInt (66)); | test.timbre (3, MPEValue::from7BitInt (66)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 66, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 66, MPENote::keyDown); | |||||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1331,7 +1370,7 @@ public: | |||||
| test.noteOff (3, 61, MPEValue::from7BitInt (100)); | test.noteOff (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.timbre (3, MPEValue::from7BitInt (77)); | test.timbre (3, MPEValue::from7BitInt (77)); | ||||
| expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 77, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 77, MPENote::keyDown); | |||||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1341,11 +1380,11 @@ public: | |||||
| // Zsolt's edge case for timbre | // Zsolt's edge case for timbre | ||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
| test.timbre (3, MPEValue::from7BitInt (42)); | test.timbre (3, MPEValue::from7BitInt (42)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 42, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 42, MPENote::keyDown); | |||||
| test.noteOff (3, 60, MPEValue::from7BitInt (100)); | test.noteOff (3, 60, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| } | } | ||||
| } | } | ||||
| @@ -1361,8 +1400,8 @@ public: | |||||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.pressure (3, MPEValue::from7BitInt (99)); | test.pressure (3, MPEValue::from7BitInt (99)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown); | ||||
| expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
| } | } | ||||
| @@ -1377,8 +1416,8 @@ public: | |||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.pressure (3, MPEValue::from7BitInt (99)); | test.pressure (3, MPEValue::from7BitInt (99)); | ||||
| expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown); | ||||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1391,9 +1430,9 @@ public: | |||||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.pressure (3, MPEValue::from7BitInt (99)); | test.pressure (3, MPEValue::from7BitInt (99)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown); | ||||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePressureChangedCallCounter, 1); | expectEquals (test.notePressureChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1425,9 +1464,9 @@ public: | |||||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (3, MPEValue::from14BitInt (9999)); | test.pitchbend (3, MPEValue::from14BitInt (9999)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1440,9 +1479,9 @@ public: | |||||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (3, MPEValue::from14BitInt (9999)); | test.pitchbend (3, MPEValue::from14BitInt (9999)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1455,9 +1494,9 @@ public: | |||||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (3, MPEValue::from14BitInt (9999)); | test.pitchbend (3, MPEValue::from14BitInt (9999)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | expectEquals (test.notePitchbendChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1470,9 +1509,9 @@ public: | |||||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (3, MPEValue::from14BitInt (9999)); | test.pitchbend (3, MPEValue::from14BitInt (9999)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| expectEquals (test.notePitchbendChangedCallCounter, 3); | expectEquals (test.notePitchbendChangedCallCounter, 3); | ||||
| } | } | ||||
| } | } | ||||
| @@ -1489,9 +1528,9 @@ public: | |||||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.timbre (3, MPEValue::from7BitInt (99)); | test.timbre (3, MPEValue::from7BitInt (99)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 99, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); | |||||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1504,9 +1543,9 @@ public: | |||||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.timbre (3, MPEValue::from7BitInt (99)); | test.timbre (3, MPEValue::from7BitInt (99)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 99, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1519,9 +1558,9 @@ public: | |||||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.timbre (3, MPEValue::from7BitInt (99)); | test.timbre (3, MPEValue::from7BitInt (99)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 99, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | expectEquals (test.noteTimbreChangedCallCounter, 1); | ||||
| } | } | ||||
| { | { | ||||
| @@ -1534,9 +1573,9 @@ public: | |||||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | test.noteOn (3, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | test.noteOn (3, 61, MPEValue::from7BitInt (100)); | ||||
| test.timbre (3, MPEValue::from7BitInt (99)); | test.timbre (3, MPEValue::from7BitInt (99)); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 99, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 99, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 99, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); | |||||
| expectEquals (test.noteTimbreChangedCallCounter, 3); | expectEquals (test.noteTimbreChangedCallCounter, 3); | ||||
| } | } | ||||
| } | } | ||||
| @@ -1724,7 +1763,7 @@ public: | |||||
| expectEquals (test.getNumPlayingNotes(), 0); | expectEquals (test.getNumPlayingNotes(), 0); | ||||
| } | } | ||||
| beginTest ("default getInitial...ForNoteOn"); | |||||
| beginTest ("default initial values for pitchbend and timbre"); | |||||
| { | { | ||||
| MPEInstrument test; | MPEInstrument test; | ||||
| test.setZoneLayout (testLayout); | test.setZoneLayout (testLayout); | ||||
| @@ -1739,16 +1778,7 @@ public: | |||||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | test.noteOn (3, 60, MPEValue::from7BitInt (100)); | ||||
| expectNote (test.getMostRecentNote (3), 100, 100, 3333, 66, MPENote::keyDown); | |||||
| } | |||||
| beginTest ("overriding getInitial...ForNoteOn"); | |||||
| { | |||||
| CustomInitialValuesTest<33, 4444, 55> test; | |||||
| test.setZoneLayout (testLayout); | |||||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||||
| expectNote (test.getMostRecentNote (3), 100, 33, 4444, 55, MPENote::keyDown); | |||||
| expectNote (test.getMostRecentNote (3), 100, 0, 3333, 66, MPENote::keyDown); | |||||
| } | } | ||||
| beginTest ("Legacy mode"); | beginTest ("Legacy mode"); | ||||
| @@ -1806,10 +1836,10 @@ public: | |||||
| test.pressure (2, MPEValue::from7BitInt (88)); | test.pressure (2, MPEValue::from7BitInt (88)); | ||||
| test.timbre (15, MPEValue::from7BitInt (77)); | test.timbre (15, MPEValue::from7BitInt (77)); | ||||
| expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (2, 60), 100, 88, 8192, 64, MPENote::keyDown); | expectNote (test.getNote (2, 60), 100, 88, 8192, 64, MPENote::keyDown); | ||||
| expectNote (test.getNote (15, 60), 100, 100, 8192, 77, MPENote::keyDown); | |||||
| expectNote (test.getNote (16, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (15, 60), 100, 0, 8192, 77, MPENote::keyDown); | |||||
| expectNote (test.getNote (16, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| // note off should work in legacy mode | // note off should work in legacy mode | ||||
| @@ -1835,10 +1865,10 @@ public: | |||||
| test.noteOn (16, 60, MPEValue::from7BitInt (100)); | test.noteOn (16, 60, MPEValue::from7BitInt (100)); | ||||
| expectEquals (test.getNumPlayingNotes(), 4); | expectEquals (test.getNumPlayingNotes(), 4); | ||||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (6, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (7, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (6, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (7, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| } | } | ||||
| { | { | ||||
| // tracking mode in legacy mode | // tracking mode in legacy mode | ||||
| @@ -1851,9 +1881,9 @@ public: | |||||
| test.noteOn (1, 62, MPEValue::from7BitInt (100)); | test.noteOn (1, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (1, 61, MPEValue::from7BitInt (100)); | test.noteOn (1, 61, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (1, MPEValue::from14BitInt (9999)); | test.pitchbend (1, MPEValue::from14BitInt (9999)); | ||||
| expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| } | } | ||||
| { | { | ||||
| UnitTestInstrument test; | UnitTestInstrument test; | ||||
| @@ -1864,9 +1894,9 @@ public: | |||||
| test.noteOn (1, 62, MPEValue::from7BitInt (100)); | test.noteOn (1, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (1, 61, MPEValue::from7BitInt (100)); | test.noteOn (1, 61, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (1, MPEValue::from14BitInt (9999)); | test.pitchbend (1, MPEValue::from14BitInt (9999)); | ||||
| expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| } | } | ||||
| { | { | ||||
| UnitTestInstrument test; | UnitTestInstrument test; | ||||
| @@ -1877,9 +1907,9 @@ public: | |||||
| test.noteOn (1, 62, MPEValue::from7BitInt (100)); | test.noteOn (1, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (1, 61, MPEValue::from7BitInt (100)); | test.noteOn (1, 61, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (1, MPEValue::from14BitInt (9999)); | test.pitchbend (1, MPEValue::from14BitInt (9999)); | ||||
| expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| } | } | ||||
| { | { | ||||
| UnitTestInstrument test; | UnitTestInstrument test; | ||||
| @@ -1890,9 +1920,9 @@ public: | |||||
| test.noteOn (1, 62, MPEValue::from7BitInt (100)); | test.noteOn (1, 62, MPEValue::from7BitInt (100)); | ||||
| test.noteOn (1, 61, MPEValue::from7BitInt (100)); | test.noteOn (1, 61, MPEValue::from7BitInt (100)); | ||||
| test.pitchbend (1, MPEValue::from14BitInt (9999)); | test.pitchbend (1, MPEValue::from14BitInt (9999)); | ||||
| expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||||
| } | } | ||||
| } | } | ||||
| { | { | ||||
| @@ -1916,7 +1946,7 @@ public: | |||||
| test.noteOff (1, 60, MPEValue::from7BitInt (100)); | test.noteOff (1, 60, MPEValue::from7BitInt (100)); | ||||
| expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
| expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
| expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
| test.sustainPedal (1, false); | test.sustainPedal (1, false); | ||||
| expectEquals (test.getNumPlayingNotes(), 0); | expectEquals (test.getNumPlayingNotes(), 0); | ||||
| @@ -1939,7 +1969,7 @@ public: | |||||
| test.noteOff (2, 61, MPEValue::from7BitInt (100)); | test.noteOff (2, 61, MPEValue::from7BitInt (100)); | ||||
| expectEquals (test.getNumPlayingNotes(), 1); | expectEquals (test.getNumPlayingNotes(), 1); | ||||
| expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::sustained); | |||||
| expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); | |||||
| test.sostenutoPedal (1, false); | test.sostenutoPedal (1, false); | ||||
| expectEquals (test.getNumPlayingNotes(), 0); | expectEquals (test.getNumPlayingNotes(), 0); | ||||
| @@ -2081,26 +2111,6 @@ private: | |||||
| } | } | ||||
| }; | }; | ||||
| //============================================================================== | |||||
| template <int initial7BitPressure, int initial14BitPitchbend, int initial7BitTimbre> | |||||
| class CustomInitialValuesTest : public MPEInstrument | |||||
| { | |||||
| MPEValue getInitialPitchbendForNoteOn (int, int, MPEValue) const override | |||||
| { | |||||
| return MPEValue::from14BitInt (initial14BitPitchbend); | |||||
| } | |||||
| MPEValue getInitialPressureForNoteOn (int, int, MPEValue) const override | |||||
| { | |||||
| return MPEValue::from7BitInt (initial7BitPressure); | |||||
| } | |||||
| MPEValue getInitialTimbreForNoteOn (int, int, MPEValue) const override | |||||
| { | |||||
| return MPEValue::from7BitInt (initial7BitTimbre); | |||||
| } | |||||
| }; | |||||
| //============================================================================== | //============================================================================== | ||||
| void expectNote (MPENote noteToTest, | void expectNote (MPENote noteToTest, | ||||
| int noteOnVelocity7Bit, | int noteOnVelocity7Bit, | ||||
| @@ -320,36 +320,6 @@ public: | |||||
| /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | ||||
| void setLegacyModePitchbendRange (int pitchbendRange); | void setLegacyModePitchbendRange (int pitchbendRange); | ||||
| protected: | |||||
| //============================================================================== | |||||
| /** This method defines what initial pitchbend value should be used for newly | |||||
| triggered notes. The default is to use the last pitchbend value | |||||
| that has been received on the same MIDI channel (or no pitchbend | |||||
| if no pitchbend messages have been received so far). | |||||
| Override this method if you need different behaviour. | |||||
| */ | |||||
| virtual MPEValue getInitialPitchbendForNoteOn (int midiChannel, | |||||
| int midiNoteNumber, | |||||
| MPEValue midiNoteOnVelocity) const; | |||||
| /** This method defines what initial pressure value should be used for newly | |||||
| triggered notes. The default is to re-use the note-on velocity value. | |||||
| Override this method if you need different behaviour. | |||||
| */ | |||||
| virtual MPEValue getInitialPressureForNoteOn (int midiChannel, | |||||
| int midiNoteNumber, | |||||
| MPEValue midiNoteOnVelocity) const; | |||||
| /** This method defines what initial timbre value should be used for newly | |||||
| triggered notes. The default is to use the last timbre value that has | |||||
| that has been received on the same MIDI channel (or a neutral centred value | |||||
| if no pitchbend messages have been received so far). | |||||
| Override this method if you need different behaviour. | |||||
| */ | |||||
| virtual MPEValue getInitialTimbreForNoteOn (int midiChannel, | |||||
| int midiNoteNumber, | |||||
| MPEValue midiNoteOnVelocity) const; | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| CriticalSection lock; | CriticalSection lock; | ||||
| @@ -384,6 +354,7 @@ private: | |||||
| void updateDimensionMaster (MPEZone&, MPEDimension&, MPEValue); | void updateDimensionMaster (MPEZone&, MPEDimension&, MPEValue); | ||||
| void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | ||||
| void callListenersDimensionChanged (MPENote&, MPEDimension&); | void callListenersDimensionChanged (MPENote&, MPEDimension&); | ||||
| MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const; | |||||
| void processMidiNoteOnMessage (const MidiMessage&); | void processMidiNoteOnMessage (const MidiMessage&); | ||||
| void processMidiNoteOffMessage (const MidiMessage&); | void processMidiNoteOffMessage (const MidiMessage&); | ||||
| @@ -22,7 +22,6 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| MidiBuffer MPEMessages::addZone (MPEZone zone) | MidiBuffer MPEMessages::addZone (MPEZone zone) | ||||
| { | { | ||||
| MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(), | MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(), | ||||
| @@ -25,7 +25,8 @@ | |||||
| MPESynthesiserBase::MPESynthesiserBase() | MPESynthesiserBase::MPESynthesiserBase() | ||||
| : instrument (new MPEInstrument), | : instrument (new MPEInstrument), | ||||
| sampleRate (0), | sampleRate (0), | ||||
| minimumSubBlockSize (32) | |||||
| minimumSubBlockSize (32), | |||||
| subBlockSubdivisionIsStrict (false) | |||||
| { | { | ||||
| instrument->addListener (this); | instrument->addListener (this); | ||||
| } | } | ||||
| @@ -100,10 +101,11 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
| MidiBuffer::Iterator midiIterator (inputMidi); | MidiBuffer::Iterator midiIterator (inputMidi); | ||||
| midiIterator.setNextSamplePosition (startSample); | midiIterator.setNextSamplePosition (startSample); | ||||
| bool firstEvent = true; | |||||
| int midiEventPos; | int midiEventPos; | ||||
| MidiMessage m; | MidiMessage m; | ||||
| const ScopedLock sl (renderAudioLock); | |||||
| const ScopedLock sl (noteStateLock); | |||||
| while (numSamples > 0) | while (numSamples > 0) | ||||
| { | { | ||||
| @@ -122,12 +124,14 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
| break; | break; | ||||
| } | } | ||||
| if (samplesToNextMidiMessage < minimumSubBlockSize) | |||||
| if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||||
| { | { | ||||
| handleMidiEvent (m); | handleMidiEvent (m); | ||||
| continue; | continue; | ||||
| } | } | ||||
| firstEvent = false; | |||||
| renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage); | renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage); | ||||
| handleMidiEvent (m); | handleMidiEvent (m); | ||||
| startSample += samplesToNextMidiMessage; | startSample += samplesToNextMidiMessage; | ||||
| @@ -147,15 +151,16 @@ void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) | |||||
| { | { | ||||
| if (sampleRate != newRate) | if (sampleRate != newRate) | ||||
| { | { | ||||
| const ScopedLock sl (renderAudioLock); | |||||
| const ScopedLock sl (noteStateLock); | |||||
| instrument->releaseAllNotes(); | instrument->releaseAllNotes(); | ||||
| sampleRate = newRate; | sampleRate = newRate; | ||||
| } | } | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples) noexcept | |||||
| void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept | |||||
| { | { | ||||
| jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | ||||
| minimumSubBlockSize = numSamples; | minimumSubBlockSize = numSamples; | ||||
| subBlockSubdivisionIsStrict = shouldBeStrict; | |||||
| } | } | ||||
| @@ -127,8 +127,14 @@ public: | |||||
| The default setting is 32, which means that midi messages are accurate to about < 1ms | The default setting is 32, which means that midi messages are accurate to about < 1ms | ||||
| accuracy, which is probably fine for most purposes, but you may want to increase or | accuracy, which is probably fine for most purposes, but you may want to increase or | ||||
| decrease this value for your synth. | decrease this value for your synth. | ||||
| If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||||
| If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||||
| to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||||
| (this can sometimes help to avoid quantisation or phasing issues). | |||||
| */ | */ | ||||
| void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; | |||||
| void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Puts the synthesiser into legacy mode. | /** Puts the synthesiser into legacy mode. | ||||
| @@ -179,13 +185,13 @@ protected: | |||||
| //============================================================================== | //============================================================================== | ||||
| /** @internal */ | /** @internal */ | ||||
| ScopedPointer<MPEInstrument> instrument; | ScopedPointer<MPEInstrument> instrument; | ||||
| /** @internal */ | |||||
| CriticalSection renderAudioLock; | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| CriticalSection noteStateLock; | |||||
| double sampleRate; | double sampleRate; | ||||
| int minimumSubBlockSize; | int minimumSubBlockSize; | ||||
| bool subBlockSubdivisionIsStrict; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) | ||||
| }; | }; | ||||
| @@ -69,27 +69,35 @@ struct JUCE_API MPEZone | |||||
| int perNotePitchbendRange = 48, | int perNotePitchbendRange = 48, | ||||
| int masterPitchbendRange = 2) noexcept; | int masterPitchbendRange = 2) noexcept; | ||||
| /* Returns the MIDI master channel of this zone. */ | |||||
| /* Returns the MIDI master channel number (in the range 1-16) of this zone. */ | |||||
| int getMasterChannel() const noexcept; | int getMasterChannel() const noexcept; | ||||
| /** Returns the number of note channels occupied by this zone. */ | /** Returns the number of note channels occupied by this zone. */ | ||||
| int getNumNoteChannels() const noexcept; | int getNumNoteChannels() const noexcept; | ||||
| /* Returns the MIDI channel number of the lowest-numbered note channel of this zone. */ | |||||
| /* Returns the MIDI channel number (in the range 1-16) of the | |||||
| lowest-numbered note channel of this zone. | |||||
| */ | |||||
| int getFirstNoteChannel() const noexcept; | int getFirstNoteChannel() const noexcept; | ||||
| /* Returns the MIDI channel number of the highest-numbered note channel of this zone. */ | |||||
| /* Returns the MIDI channel number (in the range 1-16) of the | |||||
| highest-numbered note channel of this zone. | |||||
| */ | |||||
| int getLastNoteChannel() const noexcept; | int getLastNoteChannel() const noexcept; | ||||
| /** Returns the MIDI channel numbers of the note channels of this zone as a Range. */ | |||||
| /** Returns the MIDI channel numbers (in the range 1-16) of the | |||||
| note channels of this zone as a Range. | |||||
| */ | |||||
| Range<int> getNoteChannelRange() const noexcept; | Range<int> getNoteChannelRange() const noexcept; | ||||
| /** Returns true if the MIDI channel (in the range 1-16) is used by this zone | /** Returns true if the MIDI channel (in the range 1-16) is used by this zone | ||||
| either as a note channel or as the master channel; false otherwise. */ | |||||
| either as a note channel or as the master channel; false otherwise. | |||||
| */ | |||||
| bool isUsingChannel (int channel) const noexcept; | bool isUsingChannel (int channel) const noexcept; | ||||
| /** Returns true if the MIDI channel (in the range 1-16) is used by this zone | /** Returns true if the MIDI channel (in the range 1-16) is used by this zone | ||||
| as a note channel; false otherwise. */ | |||||
| as a note channel; false otherwise. | |||||
| */ | |||||
| bool isUsingChannelAsNoteChannel (int channel) const noexcept; | bool isUsingChannelAsNoteChannel (int channel) const noexcept; | ||||
| /** Returns the per-note pitchbend range in semitones set for this zone. */ | /** Returns the per-note pitchbend range in semitones set for this zone. */ | ||||
| @@ -34,6 +34,7 @@ MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other) | |||||
| MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) | MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) | ||||
| { | { | ||||
| zones = other.zones; | zones = other.zones; | ||||
| listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); | |||||
| return *this; | return *this; | ||||
| } | } | ||||
| @@ -100,8 +100,9 @@ public: | |||||
| /** Returns the current number of MPE zones. */ | /** Returns the current number of MPE zones. */ | ||||
| int getNumZones() const noexcept; | int getNumZones() const noexcept; | ||||
| /** Returns a pointer to the MPE zone at the given index, | |||||
| or nullptr if there is no such zone. | |||||
| /** Returns a pointer to the MPE zone at the given index, or nullptr if there | |||||
| is no such zone. Zones are sorted by insertion order (most recently added | |||||
| zone last). | |||||
| */ | */ | ||||
| MPEZone* getZoneByIndex (int index) const noexcept; | MPEZone* getZoneByIndex (int index) const noexcept; | ||||
| @@ -26,7 +26,8 @@ BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, | |||||
| TimeSliceThread& thread, | TimeSliceThread& thread, | ||||
| const bool deleteSourceWhenDeleted, | const bool deleteSourceWhenDeleted, | ||||
| const int bufferSizeSamples, | const int bufferSizeSamples, | ||||
| const int numChannels) | |||||
| const int numChannels, | |||||
| bool prefillBufferOnPrepareToPlay) | |||||
| : source (s, deleteSourceWhenDeleted), | : source (s, deleteSourceWhenDeleted), | ||||
| backgroundThread (thread), | backgroundThread (thread), | ||||
| numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)), | numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)), | ||||
| @@ -36,7 +37,8 @@ BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, | |||||
| nextPlayPos (0), | nextPlayPos (0), | ||||
| sampleRate (0), | sampleRate (0), | ||||
| wasSourceLooping (false), | wasSourceLooping (false), | ||||
| isPrepared (false) | |||||
| isPrepared (false), | |||||
| prefillBuffer (prefillBufferOnPrepareToPlay) | |||||
| { | { | ||||
| jassert (source != nullptr); | jassert (source != nullptr); | ||||
| @@ -73,12 +75,13 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne | |||||
| backgroundThread.addTimeSliceClient (this); | backgroundThread.addTimeSliceClient (this); | ||||
| while (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, | |||||
| buffer.getNumSamples() / 2)) | |||||
| do | |||||
| { | { | ||||
| backgroundThread.moveToFrontOfQueue (this); | backgroundThread.moveToFrontOfQueue (this); | ||||
| Thread::sleep (5); | Thread::sleep (5); | ||||
| } | } | ||||
| while (prefillBuffer | |||||
| && (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2))); | |||||
| } | } | ||||
| } | } | ||||
| @@ -88,7 +91,12 @@ void BufferingAudioSource::releaseResources() | |||||
| backgroundThread.removeTimeSliceClient (this); | backgroundThread.removeTimeSliceClient (this); | ||||
| buffer.setSize (numberOfChannels, 0); | buffer.setSize (numberOfChannels, 0); | ||||
| source->releaseResources(); | |||||
| // MSVC2015 seems to need this if statement to not generate a warning during linking. | |||||
| // As source is set in the constructor, there is no way that source could | |||||
| // ever equal this, but it seems to make MSVC2015 happy. | |||||
| if (source != this) | |||||
| source->releaseResources(); | |||||
| } | } | ||||
| void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | ||||
| @@ -148,6 +156,41 @@ void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info | |||||
| } | } | ||||
| } | } | ||||
| bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout) | |||||
| { | |||||
| if (!source || source->getTotalLength() <= 0) | |||||
| return false; | |||||
| if (nextPlayPos + info.numSamples < 0) | |||||
| return true; | |||||
| if (! isLooping() && nextPlayPos > getTotalLength()) | |||||
| return true; | |||||
| const uint32 endTime = Time::getMillisecondCounter() + timeout; | |||||
| uint32 now = Time::getMillisecondCounter(); | |||||
| while (now < endTime) | |||||
| { | |||||
| { | |||||
| const ScopedLock sl (bufferStartPosLock); | |||||
| const int validStart = static_cast<int> (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos); | |||||
| const int validEnd = static_cast<int> (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos); | |||||
| if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples) | |||||
| return true; | |||||
| } | |||||
| if (! bufferReadyEvent.wait (static_cast<int> (endTime - now))) | |||||
| return false; | |||||
| now = Time::getMillisecondCounter(); | |||||
| } | |||||
| return false; | |||||
| } | |||||
| int64 BufferingAudioSource::getNextReadPosition() const | int64 BufferingAudioSource::getNextReadPosition() const | ||||
| { | { | ||||
| jassert (source->getTotalLength() > 0); | jassert (source->getTotalLength() > 0); | ||||
| @@ -241,6 +284,8 @@ bool BufferingAudioSource::readNextBufferChunk() | |||||
| bufferValidEnd = newBVE; | bufferValidEnd = newBVE; | ||||
| } | } | ||||
| bufferReadyEvent.signal(); | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -43,21 +43,24 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Creates a BufferingAudioSource. | /** Creates a BufferingAudioSource. | ||||
| @param source the input source to read from | |||||
| @param backgroundThread a background thread that will be used for the | |||||
| background read-ahead. This object must not be deleted | |||||
| until after any BufferingAudioSources that are using it | |||||
| have been deleted! | |||||
| @param deleteSourceWhenDeleted if true, then the input source object will | |||||
| be deleted when this object is deleted | |||||
| @param numberOfSamplesToBuffer the size of buffer to use for reading ahead | |||||
| @param numberOfChannels the number of channels that will be played | |||||
| @param source the input source to read from | |||||
| @param backgroundThread a background thread that will be used for the | |||||
| background read-ahead. This object must not be deleted | |||||
| until after any BufferingAudioSources that are using it | |||||
| have been deleted! | |||||
| @param deleteSourceWhenDeleted if true, then the input source object will | |||||
| be deleted when this object is deleted | |||||
| @param numberOfSamplesToBuffer the size of buffer to use for reading ahead | |||||
| @param numberOfChannels the number of channels that will be played | |||||
| @param prefillBufferOnPrepareToPlay if true, then calling prepareToPlay on this object will | |||||
| block until the buffer has been filled | |||||
| */ | */ | ||||
| BufferingAudioSource (PositionableAudioSource* source, | BufferingAudioSource (PositionableAudioSource* source, | ||||
| TimeSliceThread& backgroundThread, | TimeSliceThread& backgroundThread, | ||||
| bool deleteSourceWhenDeleted, | bool deleteSourceWhenDeleted, | ||||
| int numberOfSamplesToBuffer, | int numberOfSamplesToBuffer, | ||||
| int numberOfChannels = 2); | |||||
| int numberOfChannels = 2, | |||||
| bool prefillBufferOnPrepareToPlay = true); | |||||
| /** Destructor. | /** Destructor. | ||||
| @@ -89,6 +92,12 @@ public: | |||||
| /** Implements the PositionableAudioSource method. */ | /** Implements the PositionableAudioSource method. */ | ||||
| bool isLooping() const override { return source->isLooping(); } | bool isLooping() const override { return source->isLooping(); } | ||||
| /** A useful function to block until the next the buffer info can be filled. | |||||
| This is useful for offline rendering. | |||||
| */ | |||||
| bool waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout); | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| OptionalScopedPointer<PositionableAudioSource> source; | OptionalScopedPointer<PositionableAudioSource> source; | ||||
| @@ -96,9 +105,10 @@ private: | |||||
| int numberOfSamplesToBuffer, numberOfChannels; | int numberOfSamplesToBuffer, numberOfChannels; | ||||
| AudioSampleBuffer buffer; | AudioSampleBuffer buffer; | ||||
| CriticalSection bufferStartPosLock; | CriticalSection bufferStartPosLock; | ||||
| WaitableEvent bufferReadyEvent; | |||||
| int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; | int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; | ||||
| double volatile sampleRate; | double volatile sampleRate; | ||||
| bool wasSourceLooping, isPrepared; | |||||
| bool wasSourceLooping, isPrepared, prefillBuffer; | |||||
| bool readNextBufferChunk(); | bool readNextBufferChunk(); | ||||
| void readBufferSection (int64 start, int length, int bufferOffset); | void readBufferSection (int64 start, int length, int bufferOffset); | ||||
| @@ -30,7 +30,7 @@ | |||||
| /** | /** | ||||
| A type of AudioSource that takes an input source and changes its sample rate. | A type of AudioSource that takes an input source and changes its sample rate. | ||||
| @see AudioSource | |||||
| @see AudioSource, LagrangeInterpolator, CatmullRomInterpolator | |||||
| */ | */ | ||||
| class JUCE_API ResamplingAudioSource : public AudioSource | class JUCE_API ResamplingAudioSource : public AudioSource | ||||
| { | { | ||||
| @@ -88,6 +88,7 @@ Synthesiser::Synthesiser() | |||||
| : sampleRate (0), | : sampleRate (0), | ||||
| lastNoteOnCounter (0), | lastNoteOnCounter (0), | ||||
| minimumSubBlockSize (32), | minimumSubBlockSize (32), | ||||
| subBlockSubdivisionIsStrict (false), | |||||
| shouldStealNotes (true) | shouldStealNotes (true) | ||||
| { | { | ||||
| for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | ||||
| @@ -147,10 +148,11 @@ void Synthesiser::setNoteStealingEnabled (const bool shouldSteal) | |||||
| shouldStealNotes = shouldSteal; | shouldStealNotes = shouldSteal; | ||||
| } | } | ||||
| void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples) noexcept | |||||
| void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept | |||||
| { | { | ||||
| jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | ||||
| minimumSubBlockSize = numSamples; | minimumSubBlockSize = numSamples; | ||||
| subBlockSubdivisionIsStrict = shouldBeStrict; | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -177,10 +179,12 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
| { | { | ||||
| // must set the sample rate before using this! | // must set the sample rate before using this! | ||||
| jassert (sampleRate != 0); | jassert (sampleRate != 0); | ||||
| const int targetChannels = outputAudio.getNumChannels(); | |||||
| MidiBuffer::Iterator midiIterator (midiData); | MidiBuffer::Iterator midiIterator (midiData); | ||||
| midiIterator.setNextSamplePosition (startSample); | midiIterator.setNextSamplePosition (startSample); | ||||
| bool firstEvent = true; | |||||
| int midiEventPos; | int midiEventPos; | ||||
| MidiMessage m; | MidiMessage m; | ||||
| @@ -190,7 +194,9 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
| { | { | ||||
| if (! midiIterator.getNextEvent (m, midiEventPos)) | if (! midiIterator.getNextEvent (m, midiEventPos)) | ||||
| { | { | ||||
| renderVoices (outputAudio, startSample, numSamples); | |||||
| if (targetChannels > 0) | |||||
| renderVoices (outputAudio, startSample, numSamples); | |||||
| return; | return; | ||||
| } | } | ||||
| @@ -198,18 +204,24 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||||
| if (samplesToNextMidiMessage >= numSamples) | if (samplesToNextMidiMessage >= numSamples) | ||||
| { | { | ||||
| renderVoices (outputAudio, startSample, numSamples); | |||||
| if (targetChannels > 0) | |||||
| renderVoices (outputAudio, startSample, numSamples); | |||||
| handleMidiEvent (m); | handleMidiEvent (m); | ||||
| break; | break; | ||||
| } | } | ||||
| if (samplesToNextMidiMessage < minimumSubBlockSize) | |||||
| if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||||
| { | { | ||||
| handleMidiEvent (m); | handleMidiEvent (m); | ||||
| continue; | continue; | ||||
| } | } | ||||
| renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||||
| firstEvent = false; | |||||
| if (targetChannels > 0) | |||||
| renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||||
| handleMidiEvent (m); | handleMidiEvent (m); | ||||
| startSample += samplesToNextMidiMessage; | startSample += samplesToNextMidiMessage; | ||||
| numSamples -= samplesToNextMidiMessage; | numSamples -= samplesToNextMidiMessage; | ||||
| @@ -539,8 +539,14 @@ public: | |||||
| The default setting is 32, which means that midi messages are accurate to about < 1ms | The default setting is 32, which means that midi messages are accurate to about < 1ms | ||||
| accuracy, which is probably fine for most purposes, but you may want to increase or | accuracy, which is probably fine for most purposes, but you may want to increase or | ||||
| decrease this value for your synth. | decrease this value for your synth. | ||||
| If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||||
| If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||||
| to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||||
| (this can sometimes help to avoid quantisation or phasing issues). | |||||
| */ | */ | ||||
| void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; | |||||
| void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||||
| protected: | protected: | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -615,6 +621,7 @@ private: | |||||
| double sampleRate; | double sampleRate; | ||||
| uint32 lastNoteOnCounter; | uint32 lastNoteOnCounter; | ||||
| int minimumSubBlockSize; | int minimumSubBlockSize; | ||||
| bool subBlockSubdivisionIsStrict; | |||||
| bool shouldStealNotes; | bool shouldStealNotes; | ||||
| BigInteger sustainPedalsDown; | BigInteger sustainPedalsDown; | ||||
| @@ -86,72 +86,12 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) | ||||
| }; | }; | ||||
| //============================================================================== | |||||
| // This is an AudioTransportSource which will own it's assigned source | |||||
| struct AudioSourceOwningTransportSource : public AudioTransportSource | |||||
| { | |||||
| AudioSourceOwningTransportSource (PositionableAudioSource* s) : source (s) | |||||
| { | |||||
| AudioTransportSource::setSource (s); | |||||
| } | |||||
| ~AudioSourceOwningTransportSource() | |||||
| { | |||||
| setSource (nullptr); | |||||
| } | |||||
| private: | |||||
| ScopedPointer<PositionableAudioSource> source; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource) | |||||
| }; | |||||
| //============================================================================== | |||||
| // An AudioSourcePlayer which will remove itself from the AudioDeviceManager's | |||||
| // callback list once it finishes playing its source | |||||
| struct AutoRemovingSourcePlayer : public AudioSourcePlayer, | |||||
| private Timer | |||||
| { | |||||
| AutoRemovingSourcePlayer (AudioDeviceManager& dm, AudioTransportSource* ts, bool ownSource) | |||||
| : manager (dm), transportSource (ts, ownSource) | |||||
| { | |||||
| jassert (ts != nullptr); | |||||
| manager.addAudioCallback (this); | |||||
| AudioSourcePlayer::setSource (transportSource); | |||||
| startTimerHz (10); | |||||
| } | |||||
| ~AutoRemovingSourcePlayer() | |||||
| { | |||||
| setSource (nullptr); | |||||
| manager.removeAudioCallback (this); | |||||
| } | |||||
| void timerCallback() override | |||||
| { | |||||
| if (getCurrentSource() == nullptr || ! transportSource->isPlaying()) | |||||
| delete this; | |||||
| } | |||||
| void audioDeviceStopped() override | |||||
| { | |||||
| AudioSourcePlayer::audioDeviceStopped(); | |||||
| setSource (nullptr); | |||||
| } | |||||
| private: | |||||
| AudioDeviceManager& manager; | |||||
| OptionalScopedPointer<AudioTransportSource> transportSource; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer) | |||||
| }; | |||||
| //============================================================================== | //============================================================================== | ||||
| AudioDeviceManager::AudioDeviceManager() | AudioDeviceManager::AudioDeviceManager() | ||||
| : numInputChansNeeded (0), | : numInputChansNeeded (0), | ||||
| numOutputChansNeeded (2), | numOutputChansNeeded (2), | ||||
| listNeedsScanning (true), | listNeedsScanning (true), | ||||
| inputLevel (0), | |||||
| testSoundPosition (0), | |||||
| cpuUsageMs (0), | cpuUsageMs (0), | ||||
| timeToCpuScale (0) | timeToCpuScale (0) | ||||
| { | { | ||||
| @@ -162,10 +102,6 @@ AudioDeviceManager::~AudioDeviceManager() | |||||
| { | { | ||||
| currentAudioDevice = nullptr; | currentAudioDevice = nullptr; | ||||
| defaultMidiOutput = nullptr; | defaultMidiOutput = nullptr; | ||||
| for (int i = 0; i < callbacks.size(); ++i) | |||||
| if (AutoRemovingSourcePlayer* p = dynamic_cast<AutoRemovingSourcePlayer*> (callbacks.getUnchecked(i))) | |||||
| delete p; | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -346,8 +282,8 @@ String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, | |||||
| currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | ||||
| } | } | ||||
| setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize"); | |||||
| setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate"); | |||||
| setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize", setup.bufferSize); | |||||
| setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate", setup.sampleRate); | |||||
| setup.inputChannels .parseString (xml.getStringAttribute ("audioDeviceInChans", "11"), 2); | setup.inputChannels .parseString (xml.getStringAttribute ("audioDeviceInChans", "11"), 2); | ||||
| setup.outputChannels.parseString (xml.getStringAttribute ("audioDeviceOutChans", "11"), 2); | setup.outputChannels.parseString (xml.getStringAttribute ("audioDeviceOutChans", "11"), 2); | ||||
| @@ -650,6 +586,8 @@ void AudioDeviceManager::stopDevice() | |||||
| { | { | ||||
| if (currentAudioDevice != nullptr) | if (currentAudioDevice != nullptr) | ||||
| currentAudioDevice->stop(); | currentAudioDevice->stop(); | ||||
| testSound = nullptr; | |||||
| } | } | ||||
| void AudioDeviceManager::closeAudioDevice() | void AudioDeviceManager::closeAudioDevice() | ||||
| @@ -761,31 +699,8 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||||
| { | { | ||||
| const ScopedLock sl (audioCallbackLock); | const ScopedLock sl (audioCallbackLock); | ||||
| if (inputLevelMeasurementEnabledCount.get() > 0 && numInputChannels > 0) | |||||
| { | |||||
| for (int j = 0; j < numSamples; ++j) | |||||
| { | |||||
| float s = 0; | |||||
| for (int i = 0; i < numInputChannels; ++i) | |||||
| s += std::abs (inputChannelData[i][j]); | |||||
| s /= numInputChannels; | |||||
| const double decayFactor = 0.99992; | |||||
| if (s > inputLevel) | |||||
| inputLevel = s; | |||||
| else if (inputLevel > 0.001f) | |||||
| inputLevel *= decayFactor; | |||||
| else | |||||
| inputLevel = 0; | |||||
| } | |||||
| } | |||||
| else | |||||
| { | |||||
| inputLevel = 0; | |||||
| } | |||||
| inputLevelMeter.updateLevel (inputChannelData, numInputChannels, numSamples); | |||||
| outputLevelMeter.updateLevel (const_cast<const float**> (outputChannelData), numOutputChannels, numSamples); | |||||
| if (callbacks.size() > 0) | if (callbacks.size() > 0) | ||||
| { | { | ||||
| @@ -821,6 +736,20 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||||
| for (int i = 0; i < numOutputChannels; ++i) | for (int i = 0; i < numOutputChannels; ++i) | ||||
| zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); | zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); | ||||
| } | } | ||||
| if (testSound != nullptr) | |||||
| { | |||||
| const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); | |||||
| const float* const src = testSound->getReadPointer (0, testSoundPosition); | |||||
| for (int i = 0; i < numOutputChannels; ++i) | |||||
| for (int j = 0; j < numSamps; ++j) | |||||
| outputChannelData [i][j] += src[j]; | |||||
| testSoundPosition += numSamps; | |||||
| if (testSoundPosition >= testSound->getNumSamples()) | |||||
| testSound = nullptr; | |||||
| } | |||||
| } | } | ||||
| void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) | void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) | ||||
| @@ -989,170 +918,87 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| // An AudioSource which simply outputs a buffer | |||||
| class AudioSampleBufferSource : public PositionableAudioSource | |||||
| { | |||||
| public: | |||||
| AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer, bool playOnAllChannels) | |||||
| : buffer (audioBuffer, ownBuffer), | |||||
| position (0), looping (false), playAcrossAllChannels (playOnAllChannels) | |||||
| {} | |||||
| AudioDeviceManager::LevelMeter::LevelMeter() noexcept : level() {} | |||||
| //============================================================================== | |||||
| void setNextReadPosition (int64 newPosition) override | |||||
| { | |||||
| jassert (newPosition >= 0); | |||||
| if (looping) | |||||
| newPosition = newPosition % static_cast<int64> (buffer->getNumSamples()); | |||||
| position = jmin (buffer->getNumSamples(), static_cast<int> (newPosition)); | |||||
| } | |||||
| int64 getNextReadPosition() const override { return static_cast<int64> (position); } | |||||
| int64 getTotalLength() const override { return static_cast<int64> (buffer->getNumSamples()); } | |||||
| bool isLooping() const override { return looping; } | |||||
| void setLooping (bool shouldLoop) override { looping = shouldLoop; } | |||||
| //============================================================================== | |||||
| void prepareToPlay (int, double) override {} | |||||
| void releaseResources() override {} | |||||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override | |||||
| void AudioDeviceManager::LevelMeter::updateLevel (const float* const* channelData, int numChannels, int numSamples) noexcept | |||||
| { | |||||
| if (enabled.get() != 0 && numChannels > 0) | |||||
| { | { | ||||
| bufferToFill.clearActiveBufferRegion(); | |||||
| const int bufferSize = buffer->getNumSamples(); | |||||
| const int samplesNeeded = bufferToFill.numSamples; | |||||
| const int samplesToCopy = jmin (bufferSize - position, samplesNeeded); | |||||
| if (samplesToCopy > 0) | |||||
| for (int j = 0; j < numSamples; ++j) | |||||
| { | { | ||||
| int maxInChannels = buffer->getNumChannels(); | |||||
| int maxOutChannels = bufferToFill.buffer->getNumChannels(); | |||||
| if (! playAcrossAllChannels) | |||||
| maxOutChannels = jmin (maxOutChannels, maxInChannels); | |||||
| for (int i = 0; i < maxOutChannels; ++i) | |||||
| bufferToFill.buffer->copyFrom (i, bufferToFill.startSample, *buffer, | |||||
| i % maxInChannels, position, samplesToCopy); | |||||
| } | |||||
| position += samplesNeeded; | |||||
| float s = 0; | |||||
| if (looping) | |||||
| position %= bufferSize; | |||||
| } | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| s += std::abs (channelData[i][j]); | |||||
| private: | |||||
| //============================================================================== | |||||
| OptionalScopedPointer<AudioSampleBuffer> buffer; | |||||
| int position; | |||||
| bool looping, playAcrossAllChannels; | |||||
| s /= numChannels; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource) | |||||
| }; | |||||
| void AudioDeviceManager::playSound (const File& file) | |||||
| { | |||||
| if (file.existsAsFile()) | |||||
| { | |||||
| AudioFormatManager formatManager; | |||||
| const double decayFactor = 0.99992; | |||||
| formatManager.registerBasicFormats(); | |||||
| playSound (formatManager.createReaderFor (file), true); | |||||
| if (s > level) | |||||
| level = s; | |||||
| else if (level > 0.001f) | |||||
| level *= decayFactor; | |||||
| else | |||||
| level = 0; | |||||
| } | |||||
| } | } | ||||
| } | |||||
| void AudioDeviceManager::playSound (const void* resourceData, size_t resourceSize) | |||||
| { | |||||
| if (resourceData != nullptr && resourceSize > 0) | |||||
| else | |||||
| { | { | ||||
| AudioFormatManager formatManager; | |||||
| formatManager.registerBasicFormats(); | |||||
| MemoryInputStream* mem = new MemoryInputStream (resourceData, resourceSize, false); | |||||
| playSound (formatManager.createReaderFor (mem), true); | |||||
| level = 0; | |||||
| } | } | ||||
| } | } | ||||
| void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFinished) | |||||
| void AudioDeviceManager::LevelMeter::setEnabled (bool shouldBeEnabled) noexcept | |||||
| { | { | ||||
| if (reader != nullptr) | |||||
| playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); | |||||
| enabled.set (shouldBeEnabled ? 1 : 0); | |||||
| level = 0; | |||||
| } | } | ||||
| void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels) | |||||
| double AudioDeviceManager::LevelMeter::getCurrentLevel() const noexcept | |||||
| { | { | ||||
| if (buffer != nullptr) | |||||
| playSound (new AudioSampleBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true); | |||||
| jassert (enabled.get() != 0); // you need to call setEnabled (true) before using this! | |||||
| return level; | |||||
| } | } | ||||
| void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished) | |||||
| void AudioDeviceManager::playTestSound() | |||||
| { | { | ||||
| if (audioSource != nullptr && currentAudioDevice != nullptr) | |||||
| { | |||||
| AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource); | |||||
| { // cunningly nested to swap, unlock and delete in that order. | |||||
| ScopedPointer<AudioSampleBuffer> oldSound; | |||||
| if (transport == nullptr) | |||||
| { | { | ||||
| if (deleteWhenFinished) | |||||
| { | |||||
| transport = new AudioSourceOwningTransportSource (audioSource); | |||||
| } | |||||
| else | |||||
| { | |||||
| transport = new AudioTransportSource(); | |||||
| transport->setSource (audioSource); | |||||
| deleteWhenFinished = true; | |||||
| } | |||||
| const ScopedLock sl (audioCallbackLock); | |||||
| oldSound = testSound; | |||||
| } | } | ||||
| transport->start(); | |||||
| new AutoRemovingSourcePlayer (*this, transport, deleteWhenFinished); | |||||
| } | } | ||||
| else | |||||
| { | |||||
| if (deleteWhenFinished) | |||||
| delete audioSource; | |||||
| } | |||||
| } | |||||
| void AudioDeviceManager::playTestSound() | |||||
| { | |||||
| const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||||
| const int soundLength = (int) sampleRate; | |||||
| testSoundPosition = 0; | |||||
| const double frequency = 440.0; | |||||
| const float amplitude = 0.5f; | |||||
| const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||||
| if (currentAudioDevice != nullptr) | |||||
| { | |||||
| const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||||
| const int soundLength = (int) sampleRate; | |||||
| AudioSampleBuffer* newSound = new AudioSampleBuffer (1, soundLength); | |||||
| const double frequency = 440.0; | |||||
| const float amplitude = 0.5f; | |||||
| for (int i = 0; i < soundLength; ++i) | |||||
| newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); | |||||
| const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||||
| newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); | |||||
| newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); | |||||
| AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); | |||||
| playSound (newSound, true, true); | |||||
| } | |||||
| for (int i = 0; i < soundLength; ++i) | |||||
| newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); | |||||
| //============================================================================== | |||||
| void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement) | |||||
| { | |||||
| if (enableMeasurement) | |||||
| ++inputLevelMeasurementEnabledCount; | |||||
| else | |||||
| --inputLevelMeasurementEnabledCount; | |||||
| newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); | |||||
| newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); | |||||
| inputLevel = 0; | |||||
| const ScopedLock sl (audioCallbackLock); | |||||
| testSound = newSound; | |||||
| } | |||||
| } | } | ||||
| double AudioDeviceManager::getCurrentInputLevel() const | |||||
| { | |||||
| jassert (inputLevelMeasurementEnabledCount.get() > 0); // you need to call enableInputLevelMeasurement() before using this! | |||||
| return inputLevel; | |||||
| } | |||||
| double AudioDeviceManager::getCurrentInputLevel() const noexcept { return inputLevelMeter.getCurrentLevel(); } | |||||
| double AudioDeviceManager::getCurrentOutputLevel() const noexcept { return outputLevelMeter.getCurrentLevel(); } | |||||
| void AudioDeviceManager::enableInputLevelMeasurement (bool enable) noexcept { inputLevelMeter.setEnabled (enable); } | |||||
| void AudioDeviceManager::enableOutputLevelMeasurement (bool enable) noexcept { outputLevelMeter.setEnabled (enable); } | |||||
| @@ -404,78 +404,28 @@ public: | |||||
| */ | */ | ||||
| void playTestSound(); | void playTestSound(); | ||||
| /** Plays a sound from a file. */ | |||||
| void playSound (const File& file); | |||||
| /** Convenient method to play sound from a JUCE resource. */ | |||||
| void playSound (const void* resourceData, size_t resourceSize); | |||||
| /** Plays the sound from an audio format reader. | |||||
| If deleteWhenFinished is true then the format reader will be | |||||
| automatically deleted once the sound has finished playing. | |||||
| */ | |||||
| void playSound (AudioFormatReader* buffer, bool deleteWhenFinished = false); | |||||
| /** Plays the sound from a positionable audio source. | |||||
| This will output the sound coming from a positionable audio source. | |||||
| This gives you slightly more control over the sound playback compared | |||||
| to the other playSound methods. For example, if you would like to | |||||
| stop the sound prematurely you can call this method with a | |||||
| TransportAudioSource and then call audioSource->stop. Note that, | |||||
| you must call audioSource->start to start the playback, if your | |||||
| audioSource is a TransportAudioSource. | |||||
| The audio device manager will not hold any references to this audio | |||||
| source once the audio source has stopped playing for any reason, | |||||
| for example when the sound has finished playing or when you have | |||||
| called audioSource->stop. Therefore, calling audioSource->start() on | |||||
| a finished audioSource will not restart the sound again. If this is | |||||
| desired simply call playSound with the same audioSource again. | |||||
| @param audioSource the audio source to play | |||||
| @param deleteWhenFinished If this is true then the audio source will | |||||
| be deleted once the device manager has finished playing. | |||||
| */ | |||||
| void playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished = false); | |||||
| /** Plays the sound from an audio sample buffer. | |||||
| This will output the sound contained in an audio sample buffer. If | |||||
| deleteWhenFinished is true then the audio sample buffer will be | |||||
| automatically deleted once the sound has finished playing. | |||||
| If playOnAllOutputChannels is true, then if there are more output channels | |||||
| than buffer channels, then the ones that are available will be re-used on | |||||
| multiple outputs so that something is sent to all output channels. If it | |||||
| is false, then the buffer will just be played on the first output channels. | |||||
| */ | |||||
| void playSound (AudioSampleBuffer* buffer, | |||||
| bool deleteWhenFinished = false, | |||||
| bool playOnAllOutputChannels = false); | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Turns on level-measuring. | |||||
| When enabled, the device manager will measure the peak input level | |||||
| across all channels, and you can get this level by calling getCurrentInputLevel(). | |||||
| This is mainly intended for audio setup UI panels to use to create a mic | |||||
| level display, so that the user can check that they've selected the right | |||||
| device. | |||||
| /** Turns on level-measuring for input channels. | |||||
| @see getCurrentInputLevel() | |||||
| */ | |||||
| void enableInputLevelMeasurement (bool enableMeasurement) noexcept; | |||||
| A simple filter is used to make the level decay smoothly, but this is | |||||
| only intended for giving rough feedback, and not for any kind of accurate | |||||
| measurement. | |||||
| /** Turns on level-measuring for output channels. | |||||
| @see getCurrentOutputLevel() | |||||
| */ | */ | ||||
| void enableInputLevelMeasurement (bool enableMeasurement); | |||||
| void enableOutputLevelMeasurement (bool enableMeasurement) noexcept; | |||||
| /** Returns the current input level. | /** Returns the current input level. | ||||
| To use this, you must first enable it by calling enableInputLevelMeasurement(). | To use this, you must first enable it by calling enableInputLevelMeasurement(). | ||||
| See enableInputLevelMeasurement() for more info. | |||||
| @see enableInputLevelMeasurement() | |||||
| */ | */ | ||||
| double getCurrentInputLevel() const; | |||||
| double getCurrentInputLevel() const noexcept; | |||||
| /** Returns the current output level. | |||||
| To use this, you must first enable it by calling enableOutputLevelMeasurement(). | |||||
| @see enableOutputLevelMeasurement() | |||||
| */ | |||||
| double getCurrentOutputLevel() const noexcept; | |||||
| /** Returns the a lock that can be used to synchronise access to the audio callback. | /** Returns the a lock that can be used to synchronise access to the audio callback. | ||||
| Obviously while this is locked, you're blocking the audio thread from running, so | Obviously while this is locked, you're blocking the audio thread from running, so | ||||
| @@ -502,8 +452,6 @@ private: | |||||
| BigInteger inputChannels, outputChannels; | BigInteger inputChannels, outputChannels; | ||||
| ScopedPointer<XmlElement> lastExplicitSettings; | ScopedPointer<XmlElement> lastExplicitSettings; | ||||
| mutable bool listNeedsScanning; | mutable bool listNeedsScanning; | ||||
| Atomic<int> inputLevelMeasurementEnabledCount; | |||||
| double inputLevel; | |||||
| AudioSampleBuffer tempBuffer; | AudioSampleBuffer tempBuffer; | ||||
| struct MidiCallbackInfo | struct MidiCallbackInfo | ||||
| @@ -520,8 +468,24 @@ private: | |||||
| ScopedPointer<MidiOutput> defaultMidiOutput; | ScopedPointer<MidiOutput> defaultMidiOutput; | ||||
| CriticalSection audioCallbackLock, midiCallbackLock; | CriticalSection audioCallbackLock, midiCallbackLock; | ||||
| ScopedPointer<AudioSampleBuffer> testSound; | |||||
| int testSoundPosition; | |||||
| double cpuUsageMs, timeToCpuScale; | double cpuUsageMs, timeToCpuScale; | ||||
| struct LevelMeter | |||||
| { | |||||
| LevelMeter() noexcept; | |||||
| void updateLevel (const float* const*, int numChannels, int numSamples) noexcept; | |||||
| void setEnabled (bool) noexcept; | |||||
| double getCurrentLevel() const noexcept; | |||||
| Atomic<int> enabled; | |||||
| double level; | |||||
| }; | |||||
| LevelMeter inputLevelMeter, outputLevelMeter; | |||||
| //============================================================================== | //============================================================================== | ||||
| class CallbackHandler; | class CallbackHandler; | ||||
| friend class CallbackHandler; | friend class CallbackHandler; | ||||
| @@ -31,6 +31,8 @@ | |||||
| #error "Incorrect use of JUCE cpp file" | #error "Incorrect use of JUCE cpp file" | ||||
| #endif | #endif | ||||
| #include "AppConfig.h" | |||||
| #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | ||||
| #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | ||||
| #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | ||||
| @@ -45,7 +47,6 @@ | |||||
| #define Component CarbonDummyCompName | #define Component CarbonDummyCompName | ||||
| #import <CoreAudio/AudioHardware.h> | #import <CoreAudio/AudioHardware.h> | ||||
| #import <CoreMIDI/MIDIServices.h> | #import <CoreMIDI/MIDIServices.h> | ||||
| #import <DiscRecording/DiscRecording.h> | |||||
| #import <AudioToolbox/AudioServices.h> | #import <AudioToolbox/AudioServices.h> | ||||
| #undef Point | #undef Point | ||||
| #undef Component | #undef Component | ||||
| @@ -55,10 +56,14 @@ | |||||
| #import <AVFoundation/AVFoundation.h> | #import <AVFoundation/AVFoundation.h> | ||||
| #import <CoreMIDI/MIDIServices.h> | #import <CoreMIDI/MIDIServices.h> | ||||
| #if TARGET_OS_SIMULATOR | |||||
| #import <CoreMIDI/MIDINetworkSession.h> | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| #elif JUCE_WINDOWS | #elif JUCE_WINDOWS | ||||
| #if JUCE_WASAPI | #if JUCE_WASAPI | ||||
| #include <MMReg.h> | |||||
| #include <mmreg.h> | |||||
| #endif | #endif | ||||
| #if JUCE_ASIO | #if JUCE_ASIO | ||||
| @@ -84,15 +89,6 @@ | |||||
| #include <iasiodrv.h> | #include <iasiodrv.h> | ||||
| #endif | #endif | ||||
| #if JUCE_USE_CDBURNER | |||||
| /* You'll need the Platform SDK for these headers - if you don't have it and don't | |||||
| need to use CD-burning, then you might just want to set the JUCE_USE_CDBURNER flag | |||||
| to 0, to avoid these includes. | |||||
| */ | |||||
| #include <imapi.h> | |||||
| #include <imapierror.h> | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| #elif JUCE_LINUX | #elif JUCE_LINUX | ||||
| #if JUCE_ALSA | #if JUCE_ALSA | ||||
| @@ -139,7 +135,6 @@ namespace juce | |||||
| #include "audio_io/juce_AudioIODeviceType.cpp" | #include "audio_io/juce_AudioIODeviceType.cpp" | ||||
| #include "midi_io/juce_MidiMessageCollector.cpp" | #include "midi_io/juce_MidiMessageCollector.cpp" | ||||
| #include "midi_io/juce_MidiOutput.cpp" | #include "midi_io/juce_MidiOutput.cpp" | ||||
| #include "audio_cd/juce_AudioCDReader.cpp" | |||||
| #include "sources/juce_AudioSourcePlayer.cpp" | #include "sources/juce_AudioSourcePlayer.cpp" | ||||
| #include "sources/juce_AudioTransportSource.cpp" | #include "sources/juce_AudioTransportSource.cpp" | ||||
| #include "native/juce_MidiDataConcatenator.h" | #include "native/juce_MidiDataConcatenator.h" | ||||
| @@ -149,14 +144,6 @@ namespace juce | |||||
| #include "native/juce_mac_CoreAudio.cpp" | #include "native/juce_mac_CoreAudio.cpp" | ||||
| #include "native/juce_mac_CoreMidi.cpp" | #include "native/juce_mac_CoreMidi.cpp" | ||||
| #if JUCE_USE_CDREADER | |||||
| #include "native/juce_mac_AudioCDReader.mm" | |||||
| #endif | |||||
| #if JUCE_USE_CDBURNER | |||||
| #include "native/juce_mac_AudioCDBurner.mm" | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| #elif JUCE_IOS | #elif JUCE_IOS | ||||
| #include "native/juce_ios_Audio.cpp" | #include "native/juce_ios_Audio.cpp" | ||||
| @@ -179,14 +166,6 @@ namespace juce | |||||
| #include "native/juce_win32_ASIO.cpp" | #include "native/juce_win32_ASIO.cpp" | ||||
| #endif | #endif | ||||
| #if JUCE_USE_CDREADER | |||||
| #include "native/juce_win32_AudioCDReader.cpp" | |||||
| #endif | |||||
| #if JUCE_USE_CDBURNER | |||||
| #include "native/juce_win32_AudioCDBurner.cpp" | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| #elif JUCE_LINUX | #elif JUCE_LINUX | ||||
| #if JUCE_ALSA | #if JUCE_ALSA | ||||
| @@ -199,10 +178,6 @@ namespace juce | |||||
| #include "native/juce_linux_JackAudio.cpp" | #include "native/juce_linux_JackAudio.cpp" | ||||
| #endif | #endif | ||||
| #if JUCE_USE_CDREADER | |||||
| #include "native/juce_linux_AudioCDReader.cpp" | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| #elif JUCE_ANDROID | #elif JUCE_ANDROID | ||||
| #include "native/juce_android_Audio.cpp" | #include "native/juce_android_Audio.cpp" | ||||
| @@ -22,12 +22,39 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| /******************************************************************************* | |||||
| The block below describes the properties of this module, and is read by | |||||
| the Projucer to automatically generate project code that uses it. | |||||
| For details about the syntax and how to create or use a module, see the | |||||
| JUCE Module Format.txt file. | |||||
| BEGIN_JUCE_MODULE_DECLARATION | |||||
| ID: juce_audio_devices | |||||
| vendor: juce | |||||
| version: 4.3.0 | |||||
| name: JUCE audio and MIDI I/O device classes | |||||
| description: Classes to play and record from audio and MIDI I/O devices | |||||
| website: http://www.juce.com/juce | |||||
| license: GPL/Commercial | |||||
| dependencies: juce_audio_basics, juce_events | |||||
| OSXFrameworks: CoreAudio CoreMIDI AudioToolbox | |||||
| iOSFrameworks: CoreAudio CoreMIDI AudioToolbox AVFoundation | |||||
| linuxPackages: alsa | |||||
| mingwLibs: winmm | |||||
| END_JUCE_MODULE_DECLARATION | |||||
| *******************************************************************************/ | |||||
| #ifndef JUCE_AUDIO_DEVICES_H_INCLUDED | #ifndef JUCE_AUDIO_DEVICES_H_INCLUDED | ||||
| #define JUCE_AUDIO_DEVICES_H_INCLUDED | #define JUCE_AUDIO_DEVICES_H_INCLUDED | ||||
| #include "../juce_events/juce_events.h" | |||||
| #include "../juce_audio_basics/juce_audio_basics.h" | |||||
| #include "../juce_audio_formats/juce_audio_formats.h" | |||||
| #include <juce_events/juce_events.h> | |||||
| #include <juce_audio_basics/juce_audio_basics.h> | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Config: JUCE_ASIO | /** Config: JUCE_ASIO | ||||
| @@ -90,21 +117,6 @@ | |||||
| #endif | #endif | ||||
| #endif | #endif | ||||
| //============================================================================== | |||||
| /** Config: JUCE_USE_CDREADER | |||||
| Enables the AudioCDReader class (on supported platforms). | |||||
| */ | |||||
| #ifndef JUCE_USE_CDREADER | |||||
| #define JUCE_USE_CDREADER 0 | |||||
| #endif | |||||
| /** Config: JUCE_USE_CDBURNER | |||||
| Enables the AudioCDBurner class (on supported platforms). | |||||
| */ | |||||
| #ifndef JUCE_USE_CDBURNER | |||||
| #define JUCE_USE_CDBURNER 0 | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| namespace juce | namespace juce | ||||
| { | { | ||||
| @@ -117,8 +129,6 @@ namespace juce | |||||
| #include "midi_io/juce_MidiOutput.h" | #include "midi_io/juce_MidiOutput.h" | ||||
| #include "sources/juce_AudioSourcePlayer.h" | #include "sources/juce_AudioSourcePlayer.h" | ||||
| #include "sources/juce_AudioTransportSource.h" | #include "sources/juce_AudioTransportSource.h" | ||||
| #include "audio_cd/juce_AudioCDBurner.h" | |||||
| #include "audio_cd/juce_AudioCDReader.h" | |||||
| #include "audio_io/juce_AudioDeviceManager.h" | #include "audio_io/juce_AudioDeviceManager.h" | ||||
| } | } | ||||
| @@ -71,8 +71,7 @@ public: | |||||
| // the normal message, handle it now.. | // the normal message, handle it now.. | ||||
| if (*d >= 0xf8 && *d <= 0xfe) | if (*d >= 0xf8 && *d <= 0xfe) | ||||
| { | { | ||||
| const MidiMessage m (*d++, time); | |||||
| callback.handleIncomingMidiMessage (input, m); | |||||
| callback.handleIncomingMidiMessage (input, MidiMessage (*d++, time)); | |||||
| --numBytes; | --numBytes; | ||||
| } | } | ||||
| else | else | ||||
| @@ -83,7 +82,15 @@ public: | |||||
| data[len++] = *d++; | data[len++] = *d++; | ||||
| --numBytes; | --numBytes; | ||||
| if (len >= MidiMessage::getMessageLengthFromFirstByte (data[0])) | |||||
| const uint8 firstByte = data[0]; | |||||
| if (firstByte < 0x80 || firstByte == 0xf7) | |||||
| { | |||||
| len = 0; | |||||
| break; // ignore this malformed MIDI message.. | |||||
| } | |||||
| if (len >= MidiMessage::getMessageLengthFromFirstByte (firstByte)) | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| @@ -153,7 +153,7 @@ JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceM | |||||
| class AndroidMidiDeviceManager | class AndroidMidiDeviceManager | ||||
| { | { | ||||
| public: | public: | ||||
| AndroidMidiDeviceManager () | |||||
| AndroidMidiDeviceManager() | |||||
| : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager)) | : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager)) | ||||
| { | { | ||||
| } | } | ||||
| @@ -332,7 +332,7 @@ private: | |||||
| void run() override | void run() override | ||||
| { | { | ||||
| setThreadToAudioPriority (); | |||||
| setThreadToAudioPriority(); | |||||
| if (recorder != nullptr) recorder->start(); | if (recorder != nullptr) recorder->start(); | ||||
| if (player != nullptr) player->start(); | if (player != nullptr) player->start(); | ||||
| @@ -341,7 +341,7 @@ private: | |||||
| processBuffers(); | processBuffers(); | ||||
| } | } | ||||
| void setThreadToAudioPriority () | |||||
| void setThreadToAudioPriority() | |||||
| { | { | ||||
| // see android.os.Process.THREAD_PRIORITY_AUDIO | // see android.os.Process.THREAD_PRIORITY_AUDIO | ||||
| const int THREAD_PRIORITY_AUDIO = -16; | const int THREAD_PRIORITY_AUDIO = -16; | ||||
| @@ -287,7 +287,14 @@ public: | |||||
| return r; | return r; | ||||
| } | } | ||||
| int getDefaultBufferSize() override { return 256; } | |||||
| int getDefaultBufferSize() override | |||||
| { | |||||
| #if TARGET_IPHONE_SIMULATOR | |||||
| return 512; | |||||
| #else | |||||
| return 256; | |||||
| #endif | |||||
| } | |||||
| String open (const BigInteger& inputChannelsWanted, | String open (const BigInteger& inputChannelsWanted, | ||||
| const BigInteger& outputChannelsWanted, | const BigInteger& outputChannelsWanted, | ||||
| @@ -431,6 +438,8 @@ public: | |||||
| void handleStatusChange (bool enabled, const char* reason) | void handleStatusChange (bool enabled, const char* reason) | ||||
| { | { | ||||
| const ScopedLock myScopedLock (callbackLock); | |||||
| JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason); | JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason); | ||||
| isRunning = enabled; | isRunning = enabled; | ||||
| @@ -447,6 +456,8 @@ public: | |||||
| void handleRouteChange (const char* reason) | void handleRouteChange (const char* reason) | ||||
| { | { | ||||
| const ScopedLock myScopedLock (callbackLock); | |||||
| JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason); | JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason); | ||||
| fixAudioRouteIfSetToReceiver(); | fixAudioRouteIfSetToReceiver(); | ||||
| @@ -518,9 +529,9 @@ private: | |||||
| if (audioInputIsAvailable && numInputChannels > 0) | if (audioInputIsAvailable && numInputChannels > 0) | ||||
| err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data); | err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data); | ||||
| const ScopedLock sl (callbackLock); | |||||
| const ScopedTryLock stl (callbackLock); | |||||
| if (callback != nullptr) | |||||
| if (stl.isLocked() && callback != nullptr) | |||||
| { | { | ||||
| if ((int) numFrames > floatData.getNumSamples()) | if ((int) numFrames > floatData.getNumSamples()) | ||||
| prepareFloatBuffers ((int) numFrames); | prepareFloatBuffers ((int) numFrames); | ||||
| @@ -679,7 +690,23 @@ private: | |||||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format)); | AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format)); | ||||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format)); | AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format)); | ||||
| UInt32 framesPerSlice; | |||||
| UInt32 dataSize = sizeof (framesPerSlice); | |||||
| AudioUnitInitialize (audioUnit); | AudioUnitInitialize (audioUnit); | ||||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, | |||||
| kAudioUnitScope_Global, 0, &actualBufferSize, sizeof (actualBufferSize)); | |||||
| if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, | |||||
| kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr | |||||
| && dataSize == sizeof (framesPerSlice) && static_cast<int> (framesPerSlice) != actualBufferSize) | |||||
| { | |||||
| actualBufferSize = static_cast<int> (framesPerSlice); | |||||
| prepareFloatBuffers (actualBufferSize); | |||||
| } | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -755,8 +782,24 @@ void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) c | |||||
| void AudioSessionHolder::handleRouteChange (const char* reason) const | void AudioSessionHolder::handleRouteChange (const char* reason) const | ||||
| { | { | ||||
| for (auto device: activeDevices) | |||||
| device->handleRouteChange (reason); | |||||
| struct RouteChangeMessage : public CallbackMessage | |||||
| { | |||||
| RouteChangeMessage (Array<iOSAudioIODevice*> devs, const char* r) | |||||
| : devices (devs), changeReason (r) | |||||
| { | |||||
| } | |||||
| void messageCallback() override | |||||
| { | |||||
| for (auto device: devices) | |||||
| device->handleRouteChange (changeReason); | |||||
| } | |||||
| Array<iOSAudioIODevice*> devices; | |||||
| const char* changeReason; | |||||
| }; | |||||
| (new RouteChangeMessage (activeDevices, reason))->post(); | |||||
| } | } | ||||
| #undef JUCE_NSERROR_CHECK | #undef JUCE_NSERROR_CHECK | ||||
| @@ -183,7 +183,7 @@ public: | |||||
| // open output ports | // open output ports | ||||
| const StringArray outputChannels (getOutputChannelNames()); | const StringArray outputChannels (getOutputChannelNames()); | ||||
| for (int i = 0; i < outputChannels.size (); ++i) | |||||
| for (int i = 0; i < outputChannels.size(); ++i) | |||||
| { | { | ||||
| String outputName; | String outputName; | ||||
| outputName << "out_" << ++totalNumberOfOutputChannels; | outputName << "out_" << ++totalNumberOfOutputChannels; | ||||
| @@ -290,6 +290,8 @@ public: | |||||
| } | } | ||||
| } | } | ||||
| updateActivePorts(); | |||||
| return lastError; | return lastError; | ||||
| } | } | ||||
| @@ -45,37 +45,17 @@ class AlsaClient : public ReferenceCountedObject | |||||
| public: | public: | ||||
| typedef ReferenceCountedObjectPtr<AlsaClient> Ptr; | typedef ReferenceCountedObjectPtr<AlsaClient> Ptr; | ||||
| AlsaClient (bool forInput) | |||||
| : input (forInput), handle (nullptr) | |||||
| static Ptr getInstance (bool forInput) | |||||
| { | { | ||||
| snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||||
| : SND_SEQ_OPEN_OUTPUT, 0); | |||||
| } | |||||
| ~AlsaClient() | |||||
| { | |||||
| if (handle != nullptr) | |||||
| { | |||||
| snd_seq_close (handle); | |||||
| handle = nullptr; | |||||
| } | |||||
| jassert (activeCallbacks.size() == 0); | |||||
| AlsaClient*& instance = (forInput ? inInstance : outInstance); | |||||
| if (instance == nullptr) | |||||
| instance = new AlsaClient (forInput); | |||||
| if (inputThread) | |||||
| { | |||||
| inputThread->stopThread (3000); | |||||
| inputThread = nullptr; | |||||
| } | |||||
| return instance; | |||||
| } | } | ||||
| bool isInput() const noexcept { return input; } | bool isInput() const noexcept { return input; } | ||||
| void setName (const String& name) | |||||
| { | |||||
| snd_seq_set_client_name (handle, name.toUTF8()); | |||||
| } | |||||
| void registerCallback (AlsaPortAndCallback* cb) | void registerCallback (AlsaPortAndCallback* cb) | ||||
| { | { | ||||
| if (cb != nullptr) | if (cb != nullptr) | ||||
| @@ -103,7 +83,8 @@ public: | |||||
| inputThread->signalThreadShouldExit(); | inputThread->signalThreadShouldExit(); | ||||
| } | } | ||||
| void handleIncomingMidiMessage (const MidiMessage& message, int port); | |||||
| void handleIncomingMidiMessage (snd_seq_event*, const MidiMessage&); | |||||
| void handlePartialSysexMessage (snd_seq_event*, const uint8*, int, double); | |||||
| snd_seq_t* get() const noexcept { return handle; } | snd_seq_t* get() const noexcept { return handle; } | ||||
| @@ -114,12 +95,56 @@ private: | |||||
| Array<AlsaPortAndCallback*> activeCallbacks; | Array<AlsaPortAndCallback*> activeCallbacks; | ||||
| CriticalSection callbackLock; | CriticalSection callbackLock; | ||||
| static AlsaClient* inInstance; | |||||
| static AlsaClient* outInstance; | |||||
| //============================================================================== | |||||
| friend class ReferenceCountedObjectPtr<AlsaClient>; | |||||
| friend struct ContainerDeletePolicy<AlsaClient>; | |||||
| AlsaClient (bool forInput) | |||||
| : input (forInput), handle (nullptr) | |||||
| { | |||||
| AlsaClient*& instance = (input ? inInstance : outInstance); | |||||
| jassert (instance == nullptr); | |||||
| instance = this; | |||||
| snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||||
| : SND_SEQ_OPEN_OUTPUT, 0); | |||||
| snd_seq_set_client_name (handle, forInput ? JUCE_ALSA_MIDI_INPUT_NAME | |||||
| : JUCE_ALSA_MIDI_OUTPUT_NAME); | |||||
| } | |||||
| ~AlsaClient() | |||||
| { | |||||
| AlsaClient*& instance = (input ? inInstance : outInstance); | |||||
| jassert (instance != nullptr); | |||||
| instance = nullptr; | |||||
| if (handle != nullptr) | |||||
| { | |||||
| snd_seq_close (handle); | |||||
| handle = nullptr; | |||||
| } | |||||
| jassert (activeCallbacks.size() == 0); | |||||
| if (inputThread) | |||||
| { | |||||
| inputThread->stopThread (3000); | |||||
| inputThread = nullptr; | |||||
| } | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| class MidiInputThread : public Thread | class MidiInputThread : public Thread | ||||
| { | { | ||||
| public: | public: | ||||
| MidiInputThread (AlsaClient& c) | MidiInputThread (AlsaClient& c) | ||||
| : Thread ("Juce MIDI Input"), client (c) | |||||
| : Thread ("Juce MIDI Input"), client (c), concatenator (2048) | |||||
| { | { | ||||
| jassert (client.input && client.get() != nullptr); | jassert (client.input && client.get() != nullptr); | ||||
| } | } | ||||
| @@ -159,13 +184,9 @@ private: | |||||
| snd_midi_event_reset_decode (midiParser); | snd_midi_event_reset_decode (midiParser); | ||||
| if (numBytes > 0) | |||||
| { | |||||
| const MidiMessage message ((const uint8*) buffer, (int) numBytes, | |||||
| Time::getMillisecondCounter() * 0.001); | |||||
| client.handleIncomingMidiMessage (message, inputEvent->dest.port); | |||||
| } | |||||
| concatenator.pushMidiData (buffer, (int) numBytes, | |||||
| Time::getMillisecondCounter() * 0.001, | |||||
| inputEvent, client); | |||||
| snd_seq_free_event (inputEvent); | snd_seq_free_event (inputEvent); | ||||
| } | } | ||||
| @@ -180,29 +201,14 @@ private: | |||||
| private: | private: | ||||
| AlsaClient& client; | AlsaClient& client; | ||||
| MidiDataConcatenator concatenator; | |||||
| }; | }; | ||||
| ScopedPointer<MidiInputThread> inputThread; | ScopedPointer<MidiInputThread> inputThread; | ||||
| }; | }; | ||||
| static AlsaClient::Ptr globalAlsaSequencerIn() | |||||
| { | |||||
| static AlsaClient::Ptr global (new AlsaClient (true)); | |||||
| return global; | |||||
| } | |||||
| static AlsaClient::Ptr globalAlsaSequencerOut() | |||||
| { | |||||
| static AlsaClient::Ptr global (new AlsaClient (false)); | |||||
| return global; | |||||
| } | |||||
| static AlsaClient::Ptr globalAlsaSequencer (bool input) | |||||
| { | |||||
| return input ? globalAlsaSequencerIn() | |||||
| : globalAlsaSequencerOut(); | |||||
| } | |||||
| AlsaClient* AlsaClient::inInstance = nullptr; | |||||
| AlsaClient* AlsaClient::outInstance = nullptr; | |||||
| //============================================================================== | //============================================================================== | ||||
| // represents an input or output port of the supplied AlsaClient | // represents an input or output port of the supplied AlsaClient | ||||
| @@ -282,6 +288,11 @@ public: | |||||
| callback->handleIncomingMidiMessage (midiInput, message); | callback->handleIncomingMidiMessage (midiInput, message); | ||||
| } | } | ||||
| void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp) | |||||
| { | |||||
| callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp); | |||||
| } | |||||
| private: | private: | ||||
| AlsaPort port; | AlsaPort port; | ||||
| MidiInput* midiInput; | MidiInput* midiInput; | ||||
| @@ -291,14 +302,22 @@ private: | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlsaPortAndCallback) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlsaPortAndCallback) | ||||
| }; | }; | ||||
| void AlsaClient::handleIncomingMidiMessage (const MidiMessage& message, int port) | |||||
| void AlsaClient::handleIncomingMidiMessage (snd_seq_event_t* event, const MidiMessage& message) | |||||
| { | { | ||||
| const ScopedLock sl (callbackLock); | const ScopedLock sl (callbackLock); | ||||
| if (AlsaPortAndCallback* const cb = activeCallbacks[port]) | |||||
| if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port]) | |||||
| cb->handleIncomingMidiMessage (message); | cb->handleIncomingMidiMessage (message); | ||||
| } | } | ||||
| void AlsaClient::handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp) | |||||
| { | |||||
| const ScopedLock sl (callbackLock); | |||||
| if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port]) | |||||
| cb->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, | static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, | ||||
| snd_seq_client_info_t* clientInfo, | snd_seq_client_info_t* clientInfo, | ||||
| @@ -325,19 +344,23 @@ static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, | |||||
| && (snd_seq_port_info_get_capability (portInfo) & (forInput ? SND_SEQ_PORT_CAP_READ | && (snd_seq_port_info_get_capability (portInfo) & (forInput ? SND_SEQ_PORT_CAP_READ | ||||
| : SND_SEQ_PORT_CAP_WRITE)) != 0) | : SND_SEQ_PORT_CAP_WRITE)) != 0) | ||||
| { | { | ||||
| deviceNamesFound.add (snd_seq_client_info_get_name (clientInfo)); | |||||
| const String clientName = snd_seq_client_info_get_name (clientInfo); | |||||
| const String portName = snd_seq_port_info_get_name(portInfo); | |||||
| if (clientName == portName) | |||||
| deviceNamesFound.add (clientName); | |||||
| else | |||||
| deviceNamesFound.add (clientName + ": " + portName); | |||||
| if (deviceNamesFound.size() == deviceIndexToOpen + 1) | if (deviceNamesFound.size() == deviceIndexToOpen + 1) | ||||
| { | { | ||||
| const int sourcePort = snd_seq_port_info_get_port (portInfo); | const int sourcePort = snd_seq_port_info_get_port (portInfo); | ||||
| const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||||
| if (sourcePort != -1) | if (sourcePort != -1) | ||||
| { | { | ||||
| const String name (forInput ? JUCE_ALSA_MIDI_INPUT_NAME | |||||
| : JUCE_ALSA_MIDI_OUTPUT_NAME); | |||||
| seq->setName (name); | |||||
| port.createPort (seq, name, forInput); | |||||
| const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||||
| port.createPort (seq, portName, forInput); | |||||
| port.connectWith (sourceClient, sourcePort); | port.connectWith (sourceClient, sourcePort); | ||||
| } | } | ||||
| } | } | ||||
| @@ -355,7 +378,7 @@ static AlsaPort iterateMidiDevices (const bool forInput, | |||||
| const int deviceIndexToOpen) | const int deviceIndexToOpen) | ||||
| { | { | ||||
| AlsaPort port; | AlsaPort port; | ||||
| const AlsaClient::Ptr client (globalAlsaSequencer (forInput)); | |||||
| const AlsaClient::Ptr client (AlsaClient::getInstance (forInput)); | |||||
| if (snd_seq_t* const seqHandle = client->get()) | if (snd_seq_t* const seqHandle = client->get()) | ||||
| { | { | ||||
| @@ -387,19 +410,6 @@ static AlsaPort iterateMidiDevices (const bool forInput, | |||||
| return port; | return port; | ||||
| } | } | ||||
| AlsaPort createMidiDevice (const bool forInput, const String& deviceNameToOpen) | |||||
| { | |||||
| AlsaPort port; | |||||
| AlsaClient::Ptr client (new AlsaClient (forInput)); | |||||
| if (client->get()) | |||||
| { | |||||
| client->setName (deviceNameToOpen + (forInput ? " Input" : " Output")); | |||||
| port.createPort (client, forInput ? "in" : "out", forInput); | |||||
| } | |||||
| return port; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| class MidiOutputDevice | class MidiOutputDevice | ||||
| @@ -450,7 +460,7 @@ public: | |||||
| numBytes -= numSent; | numBytes -= numSent; | ||||
| data += numSent; | data += numSent; | ||||
| snd_seq_ev_set_source (&event, 0); | |||||
| snd_seq_ev_set_source (&event, port.portId); | |||||
| snd_seq_ev_set_subs (&event); | snd_seq_ev_set_subs (&event); | ||||
| snd_seq_ev_set_direct (&event); | snd_seq_ev_set_direct (&event); | ||||
| @@ -507,8 +517,11 @@ MidiOutput* MidiOutput::openDevice (int deviceIndex) | |||||
| MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | ||||
| { | { | ||||
| MidiOutput* newDevice = nullptr; | MidiOutput* newDevice = nullptr; | ||||
| AlsaPort port; | |||||
| const AlsaClient::Ptr client (AlsaClient::getInstance (false)); | |||||
| AlsaPort port (createMidiDevice (false, deviceName)); | |||||
| port.createPort (client, deviceName, false); | |||||
| if (port.isValid()) | if (port.isValid()) | ||||
| { | { | ||||
| @@ -584,8 +597,11 @@ MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | |||||
| MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | ||||
| { | { | ||||
| MidiInput* newDevice = nullptr; | MidiInput* newDevice = nullptr; | ||||
| AlsaPort port; | |||||
| const AlsaClient::Ptr client (AlsaClient::getInstance (true)); | |||||
| AlsaPort port (createMidiDevice (true, deviceName)); | |||||
| port.createPort (client, deviceName, true); | |||||
| if (port.isValid()) | if (port.isValid()) | ||||
| { | { | ||||
| @@ -149,6 +149,7 @@ bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool mute) { return SystemVol | |||||
| struct CoreAudioClasses | struct CoreAudioClasses | ||||
| { | { | ||||
| class CoreAudioIODeviceType; | |||||
| class CoreAudioIODevice; | class CoreAudioIODevice; | ||||
| //============================================================================== | //============================================================================== | ||||
| @@ -691,7 +692,7 @@ public: | |||||
| { const ScopedLock sl (callbackLock); } | { const ScopedLock sl (callbackLock); } | ||||
| // wait until it's definately stopped calling back.. | |||||
| // wait until it's definitely stopped calling back.. | |||||
| for (int i = 40; --i >= 0;) | for (int i = 40; --i >= 0;) | ||||
| { | { | ||||
| Thread::sleep (50); | Thread::sleep (50); | ||||
| @@ -847,6 +848,11 @@ private: | |||||
| intern->deviceDetailsChanged(); | intern->deviceDetailsChanged(); | ||||
| break; | break; | ||||
| case kAudioObjectPropertyOwnedObjects: | |||||
| intern->stop (false); | |||||
| intern->owner.deviceType.triggerAsyncAudioDeviceListChange(); | |||||
| break; | |||||
| case kAudioDevicePropertyBufferSizeRange: | case kAudioDevicePropertyBufferSizeRange: | ||||
| case kAudioDevicePropertyVolumeScalar: | case kAudioDevicePropertyVolumeScalar: | ||||
| case kAudioDevicePropertyMute: | case kAudioDevicePropertyMute: | ||||
| @@ -902,10 +908,12 @@ private: | |||||
| class CoreAudioIODevice : public AudioIODevice | class CoreAudioIODevice : public AudioIODevice | ||||
| { | { | ||||
| public: | public: | ||||
| CoreAudioIODevice (const String& deviceName, | |||||
| CoreAudioIODevice (CoreAudioIODeviceType& dt, | |||||
| const String& deviceName, | |||||
| AudioDeviceID inputDeviceId, const int inputIndex_, | AudioDeviceID inputDeviceId, const int inputIndex_, | ||||
| AudioDeviceID outputDeviceId, const int outputIndex_) | AudioDeviceID outputDeviceId, const int outputIndex_) | ||||
| : AudioIODevice (deviceName, "CoreAudio"), | : AudioIODevice (deviceName, "CoreAudio"), | ||||
| deviceType (dt), | |||||
| inputIndex (inputIndex_), | inputIndex (inputIndex_), | ||||
| outputIndex (outputIndex_), | outputIndex (outputIndex_), | ||||
| isOpen_ (false), | isOpen_ (false), | ||||
| @@ -1060,6 +1068,12 @@ public: | |||||
| return lastError; | return lastError; | ||||
| } | } | ||||
| void audioDeviceListChanged() | |||||
| { | |||||
| deviceType.audioDeviceListChanged(); | |||||
| } | |||||
| CoreAudioIODeviceType& deviceType; | |||||
| int inputIndex, outputIndex; | int inputIndex, outputIndex; | ||||
| private: | private: | ||||
| @@ -1745,7 +1759,8 @@ private: | |||||
| //============================================================================== | //============================================================================== | ||||
| class CoreAudioIODeviceType : public AudioIODeviceType | |||||
| class CoreAudioIODeviceType : public AudioIODeviceType, | |||||
| private AsyncUpdater | |||||
| { | { | ||||
| public: | public: | ||||
| CoreAudioIODeviceType() | CoreAudioIODeviceType() | ||||
| @@ -1771,7 +1786,7 @@ public: | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void scanForDevices() | |||||
| void scanForDevices() override | |||||
| { | { | ||||
| hasScanned = true; | hasScanned = true; | ||||
| @@ -1827,7 +1842,7 @@ public: | |||||
| outputDeviceNames.appendNumbersToDuplicates (false, true); | outputDeviceNames.appendNumbersToDuplicates (false, true); | ||||
| } | } | ||||
| StringArray getDeviceNames (bool wantInputNames) const | |||||
| StringArray getDeviceNames (bool wantInputNames) const override | |||||
| { | { | ||||
| jassert (hasScanned); // need to call scanForDevices() before doing this | jassert (hasScanned); // need to call scanForDevices() before doing this | ||||
| @@ -1835,7 +1850,7 @@ public: | |||||
| : outputDeviceNames; | : outputDeviceNames; | ||||
| } | } | ||||
| int getDefaultDeviceIndex (bool forInput) const | |||||
| int getDefaultDeviceIndex (bool forInput) const override | |||||
| { | { | ||||
| jassert (hasScanned); // need to call scanForDevices() before doing this | jassert (hasScanned); // need to call scanForDevices() before doing this | ||||
| @@ -1870,7 +1885,7 @@ public: | |||||
| return 0; | return 0; | ||||
| } | } | ||||
| int getIndexOfDevice (AudioIODevice* device, bool asInput) const | |||||
| int getIndexOfDevice (AudioIODevice* device, bool asInput) const override | |||||
| { | { | ||||
| jassert (hasScanned); // need to call scanForDevices() before doing this | jassert (hasScanned); // need to call scanForDevices() before doing this | ||||
| @@ -1894,10 +1909,10 @@ public: | |||||
| return -1; | return -1; | ||||
| } | } | ||||
| bool hasSeparateInputsAndOutputs() const { return true; } | |||||
| bool hasSeparateInputsAndOutputs() const override { return true; } | |||||
| AudioIODevice* createDevice (const String& outputDeviceName, | AudioIODevice* createDevice (const String& outputDeviceName, | ||||
| const String& inputDeviceName) | |||||
| const String& inputDeviceName) override | |||||
| { | { | ||||
| jassert (hasScanned); // need to call scanForDevices() before doing this | jassert (hasScanned); // need to call scanForDevices() before doing this | ||||
| @@ -1913,15 +1928,15 @@ public: | |||||
| String combinedName (outputDeviceName.isEmpty() ? inputDeviceName : outputDeviceName); | String combinedName (outputDeviceName.isEmpty() ? inputDeviceName : outputDeviceName); | ||||
| if (inputDeviceID == outputDeviceID) | if (inputDeviceID == outputDeviceID) | ||||
| return new CoreAudioIODevice (combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); | |||||
| return new CoreAudioIODevice (*this, combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); | |||||
| ScopedPointer<CoreAudioIODevice> in, out; | ScopedPointer<CoreAudioIODevice> in, out; | ||||
| if (inputDeviceID != 0) | if (inputDeviceID != 0) | ||||
| in = new CoreAudioIODevice (inputDeviceName, inputDeviceID, inputIndex, 0, -1); | |||||
| in = new CoreAudioIODevice (*this, inputDeviceName, inputDeviceID, inputIndex, 0, -1); | |||||
| if (outputDeviceID != 0) | if (outputDeviceID != 0) | ||||
| out = new CoreAudioIODevice (outputDeviceName, 0, -1, outputDeviceID, outputIndex); | |||||
| out = new CoreAudioIODevice (*this, outputDeviceName, 0, -1, outputDeviceID, outputIndex); | |||||
| if (in == nullptr) return out.release(); | if (in == nullptr) return out.release(); | ||||
| if (out == nullptr) return in.release(); | if (out == nullptr) return in.release(); | ||||
| @@ -1932,6 +1947,17 @@ public: | |||||
| return combo.release(); | return combo.release(); | ||||
| } | } | ||||
| void audioDeviceListChanged() | |||||
| { | |||||
| scanForDevices(); | |||||
| callDeviceChangeListeners(); | |||||
| } | |||||
| void triggerAsyncAudioDeviceListChange() | |||||
| { | |||||
| triggerAsyncUpdate(); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| private: | private: | ||||
| StringArray inputDeviceNames, outputDeviceNames; | StringArray inputDeviceNames, outputDeviceNames; | ||||
| @@ -1969,18 +1995,17 @@ private: | |||||
| return total; | return total; | ||||
| } | } | ||||
| void audioDeviceListChanged() | |||||
| { | |||||
| scanForDevices(); | |||||
| callDeviceChangeListeners(); | |||||
| } | |||||
| static OSStatus hardwareListenerProc (AudioDeviceID, UInt32, const AudioObjectPropertyAddress*, void* clientData) | static OSStatus hardwareListenerProc (AudioDeviceID, UInt32, const AudioObjectPropertyAddress*, void* clientData) | ||||
| { | { | ||||
| static_cast<CoreAudioIODeviceType*> (clientData)->audioDeviceListChanged(); | static_cast<CoreAudioIODeviceType*> (clientData)->audioDeviceListChanged(); | ||||
| return noErr; | return noErr; | ||||
| } | } | ||||
| void handleAsyncUpdate() override | |||||
| { | |||||
| audioDeviceListChanged(); | |||||
| } | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODeviceType) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODeviceType) | ||||
| }; | }; | ||||
| @@ -171,6 +171,10 @@ namespace CoreMidiHelpers | |||||
| static StringArray findDevices (const bool forInput) | static StringArray findDevices (const bool forInput) | ||||
| { | { | ||||
| // It seems that OSX can be a bit picky about the thread that's first used to | |||||
| // search for devices. It's safest to use the message thread for calling this. | |||||
| jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||||
| const ItemCount num = forInput ? MIDIGetNumberOfSources() | const ItemCount num = forInput ? MIDIGetNumberOfSources() | ||||
| : MIDIGetNumberOfDestinations(); | : MIDIGetNumberOfDestinations(); | ||||
| StringArray s; | StringArray s; | ||||
| @@ -216,6 +220,14 @@ namespace CoreMidiHelpers | |||||
| // correctly when called from the message thread! | // correctly when called from the message thread! | ||||
| jassert (MessageManager::getInstance()->isThisTheMessageThread()); | jassert (MessageManager::getInstance()->isThisTheMessageThread()); | ||||
| #if TARGET_OS_SIMULATOR | |||||
| // Enable MIDI for iOS simulator | |||||
| MIDINetworkSession* session = [MIDINetworkSession defaultSession]; | |||||
| session.enabled = YES; | |||||
| session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; | |||||
| #endif | |||||
| CoreMidiHelpers::ScopedCFString name; | CoreMidiHelpers::ScopedCFString name; | ||||
| name.cfString = getGlobalMidiClientName().toCFString(); | name.cfString = getGlobalMidiClientName().toCFString(); | ||||
| CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | ||||
| @@ -365,17 +365,18 @@ public: | |||||
| void updateSampleRates() | void updateSampleRates() | ||||
| { | { | ||||
| // find a list of sample rates.. | // find a list of sample rates.. | ||||
| const int possibleSampleRates[] = { 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; | |||||
| Array<double> newRates; | Array<double> newRates; | ||||
| if (asioObject != nullptr) | if (asioObject != nullptr) | ||||
| { | { | ||||
| const int possibleSampleRates[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; | |||||
| for (int index = 0; index < numElementsInArray (possibleSampleRates); ++index) | for (int index = 0; index < numElementsInArray (possibleSampleRates); ++index) | ||||
| if (asioObject->canSampleRate ((double) possibleSampleRates[index]) == 0) | if (asioObject->canSampleRate ((double) possibleSampleRates[index]) == 0) | ||||
| newRates.add ((double) possibleSampleRates[index]); | newRates.add ((double) possibleSampleRates[index]); | ||||
| } | } | ||||
| if (newRates.size() == 0) | |||||
| if (newRates.isEmpty()) | |||||
| { | { | ||||
| double cr = getSampleRate(); | double cr = getSampleRate(); | ||||
| JUCE_ASIO_LOG ("No sample rates supported - current rate: " + String ((int) cr)); | JUCE_ASIO_LOG ("No sample rates supported - current rate: " + String ((int) cr)); | ||||
| @@ -1571,7 +1572,7 @@ private: | |||||
| DWORD dsize = sizeof (pathName); | DWORD dsize = sizeof (pathName); | ||||
| if (RegQueryValueEx (pathKey, 0, 0, &dtype, (LPBYTE) pathName, &dsize) == ERROR_SUCCESS) | if (RegQueryValueEx (pathKey, 0, 0, &dtype, (LPBYTE) pathName, &dsize) == ERROR_SUCCESS) | ||||
| // In older code, this used to check for the existance of the file, but there are situations | |||||
| // In older code, this used to check for the existence of the file, but there are situations | |||||
| // where our process doesn't have access to it, but where the driver still loads ok.. | // where our process doesn't have access to it, but where the driver still loads ok.. | ||||
| ok = (pathName[0] != 0); | ok = (pathName[0] != 0); | ||||
| @@ -96,11 +96,18 @@ bool check (HRESULT hr) | |||||
| } | } | ||||
| #if JUCE_MINGW | #if JUCE_MINGW | ||||
| #define JUCE_COMCLASS(name, guid) \ | #define JUCE_COMCLASS(name, guid) \ | ||||
| struct name; \ | struct name; \ | ||||
| template<> struct UUIDGetter<name> { static CLSID get() { return uuidFromString (guid); } }; \ | template<> struct UUIDGetter<name> { static CLSID get() { return uuidFromString (guid); } }; \ | ||||
| struct name | struct name | ||||
| #ifdef __uuidof | |||||
| #undef __uuidof | |||||
| #endif | |||||
| #define __uuidof(cls) UUIDGetter<cls>::get() | |||||
| struct PROPERTYKEY | struct PROPERTYKEY | ||||
| { | { | ||||
| GUID fmtid; | GUID fmtid; | ||||
| @@ -60,7 +60,7 @@ public: | |||||
| The source passed in will not be deleted by this object, so must be managed by | The source passed in will not be deleted by this object, so must be managed by | ||||
| the caller. | the caller. | ||||
| @param newSource the new input source to use. This may be zero | |||||
| @param newSource the new input source to use. This may be a nullptr | |||||
| @param readAheadBufferSize a size of buffer to use for reading ahead. If this | @param readAheadBufferSize a size of buffer to use for reading ahead. If this | ||||
| is zero, no reading ahead will be done; if it's | is zero, no reading ahead will be done; if it's | ||||
| greater than zero, a BufferingAudioSource will be used | greater than zero, a BufferingAudioSource will be used | ||||
| @@ -41,11 +41,14 @@ | |||||
| * before #including this file, otherwise SIZE_MAX might not be defined | * before #including this file, otherwise SIZE_MAX might not be defined | ||||
| */ | */ | ||||
| #include <limits.h> /* for SIZE_MAX */ | |||||
| #if HAVE_STDINT_H | |||||
| #include <stdint.h> /* for SIZE_MAX in case limits.h didn't get it */ | |||||
| #endif | |||||
| #include <stdlib.h> /* for size_t, malloc(), etc */ | |||||
| // JUCE: removed as JUCE already includes standard headers and including | |||||
| // these in FlacNamespace will cause problems | |||||
| //#include <limits.h> /* for SIZE_MAX */ | |||||
| //#if HAVE_STDINT_H | |||||
| //#include <stdint.h> /* for SIZE_MAX in case limits.h didn't get it */ | |||||
| //#endif | |||||
| //#include <stdlib.h> /* for size_t, malloc(), etc */ | |||||
| #include "compat.h" | #include "compat.h" | ||||
| #ifndef SIZE_MAX | #ifndef SIZE_MAX | ||||
| @@ -35,7 +35,10 @@ | |||||
| /* we need this since some compilers (like MSVC) leave assert()s on release code (and we don't want to use their ASSERT) */ | /* we need this since some compilers (like MSVC) leave assert()s on release code (and we don't want to use their ASSERT) */ | ||||
| #ifdef DEBUG | #ifdef DEBUG | ||||
| #include <assert.h> | |||||
| // JUCE: removed as JUCE already includes standard headers and including | |||||
| // these in FlacNamespace will cause problems | |||||
| //#include <assert.h> | |||||
| #define FLAC__ASSERT(x) assert(x) | #define FLAC__ASSERT(x) assert(x) | ||||
| #define FLAC__ASSERT_DECLARATION(x) x | #define FLAC__ASSERT_DECLARATION(x) x | ||||
| #else | #else | ||||
| @@ -34,7 +34,10 @@ | |||||
| #define FLAC__CALLBACK_H | #define FLAC__CALLBACK_H | ||||
| #include "ordinals.h" | #include "ordinals.h" | ||||
| #include <stdlib.h> /* for size_t */ | |||||
| // JUCE: removed as JUCE already includes this and including stdlib | |||||
| // in FlacNamespace will cause problems | |||||
| //#include <stdlib.h> | |||||
| /** \file include/FLAC/callback.h | /** \file include/FLAC/callback.h | ||||
| * | * | ||||
| @@ -39,15 +39,7 @@ | |||||
| #ifndef FLAC__SHARE__COMPAT_H | #ifndef FLAC__SHARE__COMPAT_H | ||||
| #define FLAC__SHARE__COMPAT_H | #define FLAC__SHARE__COMPAT_H | ||||
| #if defined _WIN32 && !defined __CYGWIN__ | |||||
| /* where MSVC puts unlink() */ | |||||
| # include <io.h> | |||||
| #else | |||||
| # include <unistd.h> | |||||
| #endif | |||||
| #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | ||||
| #include <sys/types.h> /* for off_t */ | |||||
| #define FLAC__off_t __int64 /* use this instead of off_t to fix the 2 GB limit */ | #define FLAC__off_t __int64 /* use this instead of off_t to fix the 2 GB limit */ | ||||
| #if !defined __MINGW32__ | #if !defined __MINGW32__ | ||||
| #define fseeko _fseeki64 | #define fseeko _fseeki64 | ||||
| @@ -62,11 +54,6 @@ | |||||
| #define FLAC__off_t off_t | #define FLAC__off_t off_t | ||||
| #endif | #endif | ||||
| #if HAVE_INTTYPES_H | |||||
| #define __STDC_FORMAT_MACROS | |||||
| #include <inttypes.h> | |||||
| #endif | |||||
| #if defined(_MSC_VER) | #if defined(_MSC_VER) | ||||
| #define strtoll _strtoi64 | #define strtoll _strtoi64 | ||||
| #define strtoull _strtoui64 | #define strtoull _strtoui64 | ||||
| @@ -95,33 +82,13 @@ | |||||
| #define FLAC__STRNCASECMP strncasecmp | #define FLAC__STRNCASECMP strncasecmp | ||||
| #endif | #endif | ||||
| #if defined _MSC_VER || defined __MINGW32__ || defined __CYGWIN__ || defined __EMX__ | |||||
| #include <io.h> /* for _setmode(), chmod() */ | |||||
| #include <fcntl.h> /* for _O_BINARY */ | |||||
| #else | |||||
| #include <unistd.h> /* for chown(), unlink() */ | |||||
| #endif | |||||
| #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | |||||
| #if defined __BORLANDC__ | |||||
| #include <utime.h> /* for utime() */ | |||||
| #else | |||||
| #include <sys/utime.h> /* for utime() */ | |||||
| #endif | |||||
| #else | |||||
| #include <sys/types.h> /* some flavors of BSD (like OS X) require this to get time_t */ | |||||
| #include <utime.h> /* for utime() */ | |||||
| #endif | |||||
| #if defined _MSC_VER | #if defined _MSC_VER | ||||
| # if _MSC_VER >= 1600 | # if _MSC_VER >= 1600 | ||||
| /* Visual Studio 2010 has decent C99 support */ | /* Visual Studio 2010 has decent C99 support */ | ||||
| # include <stdint.h> | |||||
| # define PRIu64 "llu" | # define PRIu64 "llu" | ||||
| # define PRId64 "lld" | # define PRId64 "lld" | ||||
| # define PRIx64 "llx" | # define PRIx64 "llx" | ||||
| # else | # else | ||||
| # include <limits.h> | |||||
| # ifndef UINT32_MAX | # ifndef UINT32_MAX | ||||
| # define UINT32_MAX _UI32_MAX | # define UINT32_MAX _UI32_MAX | ||||
| # endif | # endif | ||||
| @@ -51,7 +51,9 @@ static inline unsigned short __builtin_bswap16(unsigned short a) | |||||
| #elif defined HAVE_BYTESWAP_H /* Linux */ | #elif defined HAVE_BYTESWAP_H /* Linux */ | ||||
| #include <byteswap.h> | |||||
| // JUCE: removed as JUCE already includes standard headers and including | |||||
| // these in FlacNamespace will cause problems | |||||
| //#include <byteswap.h> | |||||
| #define ENDSWAP_16(x) (bswap_16 (x)) | #define ENDSWAP_16(x) (bswap_16 (x)) | ||||
| #define ENDSWAP_32(x) (bswap_32 (x)) | #define ENDSWAP_32(x) (bswap_32 (x)) | ||||
| @@ -33,7 +33,6 @@ | |||||
| #ifndef FLAC__METADATA_H | #ifndef FLAC__METADATA_H | ||||
| #define FLAC__METADATA_H | #define FLAC__METADATA_H | ||||
| #include <sys/types.h> /* for off_t */ | |||||
| #include "export.h" | #include "export.h" | ||||
| #include "callback.h" | #include "callback.h" | ||||
| #include "format.h" | #include "format.h" | ||||
| @@ -33,7 +33,6 @@ | |||||
| #ifndef FLAC__STREAM_DECODER_H | #ifndef FLAC__STREAM_DECODER_H | ||||
| #define FLAC__STREAM_DECODER_H | #define FLAC__STREAM_DECODER_H | ||||
| #include <stdio.h> /* for FILE */ | |||||
| #include "export.h" | #include "export.h" | ||||
| #include "format.h" | #include "format.h" | ||||
| @@ -33,7 +33,6 @@ | |||||
| #ifndef FLAC__STREAM_ENCODER_H | #ifndef FLAC__STREAM_ENCODER_H | ||||
| #define FLAC__STREAM_ENCODER_H | #define FLAC__STREAM_ENCODER_H | ||||
| #include <stdio.h> /* for FILE */ | |||||
| #include "export.h" | #include "export.h" | ||||
| #include "format.h" | #include "format.h" | ||||
| #include "stream_decoder.h" | #include "stream_decoder.h" | ||||
| @@ -38,11 +38,6 @@ | |||||
| extern "C" { | extern "C" { | ||||
| #endif | #endif | ||||
| #include <stdio.h> | |||||
| #include <sys/stat.h> | |||||
| #include <stdarg.h> | |||||
| #include <windows.h> | |||||
| int get_utf8_argv(int *argc, char ***argv); | int get_utf8_argv(int *argc, char ***argv); | ||||
| int printf_utf8(const char *format, ...); | int printf_utf8(const char *format, ...); | ||||
| @@ -668,13 +668,14 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| bool write (const int** data, int numSamples) override | bool write (const int** data, int numSamples) override | ||||
| { | { | ||||
| jassert (numSamples >= 0); | |||||
| jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel! | jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel! | ||||
| if (writeFailed) | if (writeFailed) | ||||
| return false; | return false; | ||||
| const size_t bytes = (size_t) numSamples * numChannels * bitsPerSample / 8; | |||||
| tempBlock.ensureSize ((size_t) bytes, false); | |||||
| const size_t bytes = numChannels * (size_t) numSamples * bitsPerSample / 8; | |||||
| tempBlock.ensureSize (bytes, false); | |||||
| switch (bitsPerSample) | switch (bitsPerSample) | ||||
| { | { | ||||
| @@ -695,13 +696,10 @@ public: | |||||
| writeFailed = true; | writeFailed = true; | ||||
| return false; | return false; | ||||
| } | } | ||||
| else | |||||
| { | |||||
| bytesWritten += bytes; | |||||
| lengthInSamples += (uint64) numSamples; | |||||
| return true; | |||||
| } | |||||
| bytesWritten += bytes; | |||||
| lengthInSamples += (uint64) numSamples; | |||||
| return true; | |||||
| } | } | ||||
| private: | private: | ||||
| @@ -24,17 +24,79 @@ | |||||
| #if JUCE_USE_FLAC | #if JUCE_USE_FLAC | ||||
| } | |||||
| #if defined _WIN32 && !defined __CYGWIN__ | |||||
| #include <io.h> | |||||
| #else | |||||
| #include <unistd.h> | |||||
| #endif | |||||
| #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | |||||
| #include <sys/types.h> /* for off_t */ | |||||
| #endif | |||||
| #if HAVE_INTTYPES_H | |||||
| #define __STDC_FORMAT_MACROS | |||||
| #include <inttypes.h> | |||||
| #endif | |||||
| #if defined _MSC_VER || defined __MINGW32__ || defined __CYGWIN__ || defined __EMX__ | |||||
| #include <io.h> /* for _setmode(), chmod() */ | |||||
| #include <fcntl.h> /* for _O_BINARY */ | |||||
| #else | |||||
| #include <unistd.h> /* for chown(), unlink() */ | |||||
| #endif | |||||
| #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | |||||
| #if defined __BORLANDC__ | |||||
| #include <utime.h> /* for utime() */ | |||||
| #else | |||||
| #include <sys/utime.h> /* for utime() */ | |||||
| #endif | |||||
| #else | |||||
| #include <sys/types.h> /* some flavors of BSD (like OS X) require this to get time_t */ | |||||
| #include <utime.h> /* for utime() */ | |||||
| #endif | |||||
| #if defined _MSC_VER | |||||
| #if _MSC_VER >= 1600 | |||||
| #include <stdint.h> | |||||
| #else | |||||
| #include <limits.h> | |||||
| #endif | |||||
| #endif | |||||
| #ifdef _WIN32 | |||||
| #include <stdio.h> | |||||
| #include <sys/stat.h> | |||||
| #include <stdarg.h> | |||||
| #include <windows.h> | |||||
| #endif | |||||
| #ifdef DEBUG | |||||
| #include <assert.h> | |||||
| #endif | |||||
| #include <stdlib.h> | |||||
| #include <stdio.h> | |||||
| namespace juce | |||||
| { | |||||
| namespace FlacNamespace | namespace FlacNamespace | ||||
| { | { | ||||
| #if JUCE_INCLUDE_FLAC_CODE || ! defined (JUCE_INCLUDE_FLAC_CODE) | #if JUCE_INCLUDE_FLAC_CODE || ! defined (JUCE_INCLUDE_FLAC_CODE) | ||||
| #undef VERSION | #undef VERSION | ||||
| #define VERSION "1.2.1" | |||||
| #define VERSION "1.3.1" | |||||
| #define FLAC__NO_DLL 1 | #define FLAC__NO_DLL 1 | ||||
| #if JUCE_MSVC | #if JUCE_MSVC | ||||
| #pragma warning (disable: 4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4312 4505 4365 4005 4334 181 111) | #pragma warning (disable: 4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4312 4505 4365 4005 4334 181 111) | ||||
| #else | |||||
| #define HAVE_LROUND 1 | |||||
| #endif | #endif | ||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| @@ -66,6 +128,7 @@ namespace FlacNamespace | |||||
| #define __STDC_LIMIT_MACROS 1 | #define __STDC_LIMIT_MACROS 1 | ||||
| #define flac_max jmax | #define flac_max jmax | ||||
| #define flac_min jmin | #define flac_min jmin | ||||
| #undef DEBUG // (some flac code dumps debug trace if the app defines this macro) | |||||
| #include "flac/all.h" | #include "flac/all.h" | ||||
| #include "flac/libFLAC/bitmath.c" | #include "flac/libFLAC/bitmath.c" | ||||
| #include "flac/libFLAC/bitreader.c" | #include "flac/libFLAC/bitreader.c" | ||||
| @@ -324,7 +387,8 @@ class FlacWriter : public AudioFormatWriter | |||||
| { | { | ||||
| public: | public: | ||||
| FlacWriter (OutputStream* const out, double rate, uint32 numChans, uint32 bits, int qualityOptionIndex) | FlacWriter (OutputStream* const out, double rate, uint32 numChans, uint32 bits, int qualityOptionIndex) | ||||
| : AudioFormatWriter (out, flacFormatName, rate, numChans, bits) | |||||
| : AudioFormatWriter (out, flacFormatName, rate, numChans, bits), | |||||
| streamStartPos (output != nullptr ? jmax (output->getPosition(), 0ll) : 0ll) | |||||
| { | { | ||||
| using namespace FlacNamespace; | using namespace FlacNamespace; | ||||
| encoder = FLAC__stream_encoder_new(); | encoder = FLAC__stream_encoder_new(); | ||||
| @@ -432,7 +496,7 @@ public: | |||||
| packUint32 ((FLAC__uint32) info.total_samples, buffer + 14, 4); | packUint32 ((FLAC__uint32) info.total_samples, buffer + 14, 4); | ||||
| memcpy (buffer + 18, info.md5sum, 16); | memcpy (buffer + 18, info.md5sum, 16); | ||||
| const bool seekOk = output->setPosition (4); | |||||
| const bool seekOk = output->setPosition (streamStartPos + 4); | |||||
| ignoreUnused (seekOk); | ignoreUnused (seekOk); | ||||
| // if this fails, you've given it an output stream that can't seek! It needs | // if this fails, you've given it an output stream that can't seek! It needs | ||||
| @@ -482,6 +546,7 @@ public: | |||||
| private: | private: | ||||
| FlacNamespace::FLAC__StreamEncoder* encoder; | FlacNamespace::FLAC__StreamEncoder* encoder; | ||||
| int64 streamStartPos; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacWriter) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacWriter) | ||||
| }; | }; | ||||
| @@ -222,7 +222,7 @@ public: | |||||
| DisposeMovie (movie); | DisposeMovie (movie); | ||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| ExitMoviesOnThread (); | |||||
| ExitMoviesOnThread(); | |||||
| #endif | #endif | ||||
| } | } | ||||
| } | } | ||||
| @@ -656,7 +656,10 @@ namespace WavFileHelpers | |||||
| if (infoLength > 0) | if (infoLength > 0) | ||||
| { | { | ||||
| infoLength = jlimit ((int64) 0, infoLength, (int64) input.readInt()); | |||||
| infoLength = jmin (infoLength, (int64) input.readInt()); | |||||
| if (infoLength <= 0) | |||||
| return; | |||||
| for (int i = 0; i < numElementsInArray (types); ++i) | for (int i = 0; i < numElementsInArray (types); ++i) | ||||
| { | { | ||||
| @@ -664,7 +667,8 @@ namespace WavFileHelpers | |||||
| { | { | ||||
| MemoryBlock mb; | MemoryBlock mb; | ||||
| input.readIntoMemoryBlock (mb, (ssize_t) infoLength); | input.readIntoMemoryBlock (mb, (ssize_t) infoLength); | ||||
| values.set (types[i], mb.toString()); | |||||
| values.set (types[i], String::createStringFromData ((const char*) mb.getData(), | |||||
| (int) mb.getSize())); | |||||
| break; | break; | ||||
| } | } | ||||
| } | } | ||||
| @@ -1258,7 +1262,7 @@ public: | |||||
| if (writeFailed) | if (writeFailed) | ||||
| return false; | return false; | ||||
| const size_t bytes = numChannels * (unsigned int) numSamples * bitsPerSample / 8; | |||||
| const size_t bytes = numChannels * (size_t) numSamples * bitsPerSample / 8; | |||||
| tempBlock.ensureSize (bytes, false); | tempBlock.ensureSize (bytes, false); | ||||
| switch (bitsPerSample) | switch (bitsPerSample) | ||||
| @@ -166,6 +166,9 @@ public: | |||||
| checkCoInitialiseCalled(); | checkCoInitialiseCalled(); | ||||
| clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, | |||||
| startSampleInFile, numSamples, lengthInSamples); | |||||
| const int stride = numChannels * sizeof (int16); | const int stride = numChannels * sizeof (int16); | ||||
| while (numSamples > 0) | while (numSamples > 0) | ||||
| @@ -297,7 +300,7 @@ private: | |||||
| sampleRate = inputFormat->nSamplesPerSec; | sampleRate = inputFormat->nSamplesPerSec; | ||||
| numChannels = inputFormat->nChannels; | numChannels = inputFormat->nChannels; | ||||
| bitsPerSample = inputFormat->wBitsPerSample; | |||||
| bitsPerSample = inputFormat->wBitsPerSample != 0 ? inputFormat->wBitsPerSample : 16; | |||||
| lengthInSamples = (lengthInNanoseconds * (int) sampleRate) / 10000000; | lengthInSamples = (lengthInNanoseconds * (int) sampleRate) / 10000000; | ||||
| } | } | ||||
| } | } | ||||
| @@ -31,6 +31,8 @@ | |||||
| #error "Incorrect use of JUCE cpp file" | #error "Incorrect use of JUCE cpp file" | ||||
| #endif | #endif | ||||
| #include "AppConfig.h" | |||||
| #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | ||||
| #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | ||||
| #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | ||||
| @@ -22,10 +22,36 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| /******************************************************************************* | |||||
| The block below describes the properties of this module, and is read by | |||||
| the Projucer to automatically generate project code that uses it. | |||||
| For details about the syntax and how to create or use a module, see the | |||||
| JUCE Module Format.txt file. | |||||
| BEGIN_JUCE_MODULE_DECLARATION | |||||
| ID: juce_audio_formats | |||||
| vendor: juce | |||||
| version: 4.3.0 | |||||
| name: JUCE audio file format codecs | |||||
| description: Classes for reading and writing various audio file formats. | |||||
| website: http://www.juce.com/juce | |||||
| license: GPL/Commercial | |||||
| dependencies: juce_audio_basics | |||||
| OSXFrameworks: CoreAudio CoreMIDI QuartzCore AudioToolbox | |||||
| iOSFrameworks: AudioToolbox QuartzCore | |||||
| END_JUCE_MODULE_DECLARATION | |||||
| *******************************************************************************/ | |||||
| #ifndef JUCE_AUDIO_FORMATS_H_INCLUDED | #ifndef JUCE_AUDIO_FORMATS_H_INCLUDED | ||||
| #define JUCE_AUDIO_FORMATS_H_INCLUDED | #define JUCE_AUDIO_FORMATS_H_INCLUDED | ||||
| #include "../juce_audio_basics/juce_audio_basics.h" | |||||
| #include <juce_audio_basics/juce_audio_basics.h> | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Config: JUCE_USE_FLAC | /** Config: JUCE_USE_FLAC | ||||
| @@ -22,5 +22,188 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| namespace AudioPluginFormatHelpers | |||||
| { | |||||
| struct CallbackInvoker | |||||
| { | |||||
| struct InvokeOnMessageThread : public CallbackMessage | |||||
| { | |||||
| InvokeOnMessageThread (AudioPluginInstance* inInstance, const String& inError, | |||||
| AudioPluginFormat::InstantiationCompletionCallback* inCompletion, | |||||
| CallbackInvoker* invoker) | |||||
| : instance (inInstance), error (inError), compCallback (inCompletion), owner (invoker) | |||||
| {} | |||||
| void messageCallback() override { compCallback->completionCallback (instance, error); } | |||||
| //============================================================================== | |||||
| AudioPluginInstance* instance; | |||||
| String error; | |||||
| ScopedPointer<AudioPluginFormat::InstantiationCompletionCallback> compCallback; | |||||
| ScopedPointer<CallbackInvoker> owner; | |||||
| }; | |||||
| //============================================================================== | |||||
| CallbackInvoker (AudioPluginFormat::InstantiationCompletionCallback* cc) : completion (cc) | |||||
| {} | |||||
| void completionCallback (AudioPluginInstance* instance, const String& error) | |||||
| { | |||||
| (new InvokeOnMessageThread (instance, error, completion, this))->post(); | |||||
| } | |||||
| static void staticCompletionCallback (void* userData, AudioPluginInstance* instance, const String& error) | |||||
| { | |||||
| reinterpret_cast<CallbackInvoker*> (userData)->completionCallback (instance, error); | |||||
| } | |||||
| //============================================================================== | |||||
| AudioPluginFormat::InstantiationCompletionCallback* completion; | |||||
| }; | |||||
| } | |||||
| AudioPluginFormat::AudioPluginFormat() noexcept {} | AudioPluginFormat::AudioPluginFormat() noexcept {} | ||||
| AudioPluginFormat::~AudioPluginFormat() {} | AudioPluginFormat::~AudioPluginFormat() {} | ||||
| AudioPluginInstance* AudioPluginFormat::createInstanceFromDescription (const PluginDescription& desc, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize) | |||||
| { | |||||
| String errorMessage; | |||||
| return createInstanceFromDescription (desc, initialSampleRate, initialBufferSize, errorMessage); | |||||
| } | |||||
| //============================================================================== | |||||
| struct EventSignaler : public AudioPluginFormat::InstantiationCompletionCallback | |||||
| { | |||||
| EventSignaler (WaitableEvent& inEvent, AudioPluginInstance*& inInstance, String& inErrorMessage) | |||||
| : event (inEvent), outInstance (inInstance), outErrorMessage (inErrorMessage) | |||||
| {} | |||||
| void completionCallback (AudioPluginInstance* newInstance, const String& result) override | |||||
| { | |||||
| outInstance = newInstance; | |||||
| outErrorMessage = result; | |||||
| event.signal(); | |||||
| } | |||||
| static void staticCompletionCallback (void* userData, AudioPluginInstance* pluginInstance, const String& error) | |||||
| { | |||||
| reinterpret_cast<EventSignaler*> (userData)->completionCallback (pluginInstance, error); | |||||
| } | |||||
| WaitableEvent& event; | |||||
| AudioPluginInstance*& outInstance; | |||||
| String& outErrorMessage; | |||||
| JUCE_DECLARE_NON_COPYABLE (EventSignaler) | |||||
| }; | |||||
| AudioPluginInstance* AudioPluginFormat::createInstanceFromDescription (const PluginDescription& desc, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| String& errorMessage) | |||||
| { | |||||
| if (MessageManager::getInstance()->isThisTheMessageThread() | |||||
| && requiresUnblockedMessageThreadDuringCreation(desc)) | |||||
| { | |||||
| errorMessage = NEEDS_TRANS ("This plug-in cannot be instantiated synchronously"); | |||||
| return nullptr; | |||||
| } | |||||
| WaitableEvent waitForCreation; | |||||
| AudioPluginInstance* instance = nullptr; | |||||
| ScopedPointer<EventSignaler> eventSignaler (new EventSignaler (waitForCreation, instance, errorMessage)); | |||||
| if (! MessageManager::getInstance()->isThisTheMessageThread()) | |||||
| createPluginInstanceAsync (desc, initialSampleRate, initialBufferSize, eventSignaler.release()); | |||||
| else | |||||
| createPluginInstance (desc, initialSampleRate, initialBufferSize, | |||||
| eventSignaler, EventSignaler::staticCompletionCallback); | |||||
| waitForCreation.wait(); | |||||
| return instance; | |||||
| } | |||||
| void AudioPluginFormat::createPluginInstanceAsync (const PluginDescription& description, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| AudioPluginFormat::InstantiationCompletionCallback* callback) | |||||
| { | |||||
| if (MessageManager::getInstance()->isThisTheMessageThread()) | |||||
| { | |||||
| createPluginInstanceOnMessageThread (description, initialSampleRate, initialBufferSize, callback); | |||||
| return; | |||||
| } | |||||
| //============================================================================== | |||||
| struct InvokeOnMessageThread : public CallbackMessage | |||||
| { | |||||
| InvokeOnMessageThread (AudioPluginFormat* myself, | |||||
| const PluginDescription& descriptionParam, | |||||
| double initialSampleRateParam, | |||||
| int initialBufferSizeParam, | |||||
| AudioPluginFormat::InstantiationCompletionCallback* callbackParam) | |||||
| : owner (myself), descr (descriptionParam), sampleRate (initialSampleRateParam), | |||||
| bufferSize (initialBufferSizeParam), call (callbackParam) | |||||
| {} | |||||
| void messageCallback() override | |||||
| { | |||||
| owner->createPluginInstanceOnMessageThread (descr, sampleRate, bufferSize, call); | |||||
| } | |||||
| AudioPluginFormat* owner; | |||||
| PluginDescription descr; | |||||
| double sampleRate; | |||||
| int bufferSize; | |||||
| AudioPluginFormat::InstantiationCompletionCallback* call; | |||||
| }; | |||||
| (new InvokeOnMessageThread (this, description, initialSampleRate, initialBufferSize, callback))->post(); | |||||
| } | |||||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||||
| void AudioPluginFormat::createPluginInstanceAsync (const PluginDescription& description, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| std::function<void (AudioPluginInstance*, const String&)> f) | |||||
| { | |||||
| struct CallbackInvoker : public AudioPluginFormat::InstantiationCompletionCallback | |||||
| { | |||||
| CallbackInvoker (std::function<void (AudioPluginInstance*, const String&)> inCompletion) | |||||
| : completion (inCompletion) | |||||
| {} | |||||
| void completionCallback (AudioPluginInstance* instance, const String& error) override | |||||
| { | |||||
| completion (instance, error); | |||||
| } | |||||
| std::function<void (AudioPluginInstance*, const String&)> completion; | |||||
| }; | |||||
| createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, new CallbackInvoker (f)); | |||||
| } | |||||
| #endif | |||||
| void AudioPluginFormat::createPluginInstanceOnMessageThread (const PluginDescription& description, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| AudioPluginFormat::InstantiationCompletionCallback* callback) | |||||
| { | |||||
| jassert (callback != nullptr); | |||||
| jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||||
| //============================================================================== | |||||
| //============================================================================== | |||||
| AudioPluginFormatHelpers::CallbackInvoker* completion = new AudioPluginFormatHelpers::CallbackInvoker (callback); | |||||
| createPluginInstance (description, initialSampleRate, initialBufferSize, completion, | |||||
| AudioPluginFormatHelpers::CallbackInvoker::staticCompletionCallback); | |||||
| } | |||||
| @@ -30,11 +30,20 @@ | |||||
| /** | /** | ||||
| The base class for a type of plugin format, such as VST, AudioUnit, LADSPA, etc. | The base class for a type of plugin format, such as VST, AudioUnit, LADSPA, etc. | ||||
| @see AudioFormatManager | |||||
| @see AudioPluginFormatManager | |||||
| */ | */ | ||||
| class JUCE_API AudioPluginFormat | class JUCE_API AudioPluginFormat | ||||
| { | { | ||||
| public: | public: | ||||
| //============================================================================== | |||||
| struct JUCE_API InstantiationCompletionCallback | |||||
| { | |||||
| virtual ~InstantiationCompletionCallback() {} | |||||
| virtual void completionCallback (AudioPluginInstance* instance, const String& error) = 0; | |||||
| JUCE_LEAK_DETECTOR (InstantiationCompletionCallback) | |||||
| }; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Destructor. */ | /** Destructor. */ | ||||
| virtual ~AudioPluginFormat(); | virtual ~AudioPluginFormat(); | ||||
| @@ -58,11 +67,36 @@ public: | |||||
| const String& fileOrIdentifier) = 0; | const String& fileOrIdentifier) = 0; | ||||
| /** Tries to recreate a type from a previously generated PluginDescription. | /** Tries to recreate a type from a previously generated PluginDescription. | ||||
| @see PluginDescription::createInstance | |||||
| @see AudioPluginFormatManager::createInstance | |||||
| */ | |||||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize); | |||||
| /** Same as above but with the possibility of returning an error message. | |||||
| @see AudioPluginFormatManager::createInstance | |||||
| */ | */ | ||||
| virtual AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize) = 0; | |||||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| String& errorMessage); | |||||
| /** Tries to recreate a type from a previously generated PluginDescription. | |||||
| @see AudioPluginFormatManager::createInstanceAsync | |||||
| */ | |||||
| void createPluginInstanceAsync (const PluginDescription& description, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| InstantiationCompletionCallback* completionCallback); | |||||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||||
| void createPluginInstanceAsync (const PluginDescription& description, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| std::function<void (AudioPluginInstance*, const String&)> completionCallback); | |||||
| #endif | |||||
| /** Should do a quick check to see if this file or directory might be a plugin of | /** Should do a quick check to see if this file or directory might be a plugin of | ||||
| this format. | this format. | ||||
| @@ -82,7 +116,7 @@ public: | |||||
| It doesn't actually need to load it, just to check whether the file or component | It doesn't actually need to load it, just to check whether the file or component | ||||
| still exists. | still exists. | ||||
| */ | */ | ||||
| virtual bool doesPluginStillExist (const PluginDescription& desc) = 0; | |||||
| virtual bool doesPluginStillExist (const PluginDescription&) = 0; | |||||
| /** Returns true if this format needs to run a scan to find its list of plugins. */ | /** Returns true if this format needs to run a scan to find its list of plugins. */ | ||||
| virtual bool canScanForPlugins() const = 0; | virtual bool canScanForPlugins() const = 0; | ||||
| @@ -90,9 +124,17 @@ public: | |||||
| /** Searches a suggested set of directories for any plugins in this format. | /** Searches a suggested set of directories for any plugins in this format. | ||||
| The path might be ignored, e.g. by AUs, which are found by the OS rather | The path might be ignored, e.g. by AUs, which are found by the OS rather | ||||
| than manually. | than manually. | ||||
| @param directoriesToSearch This specifies which directories shall be | |||||
| searched for plug-ins. | |||||
| @param recursive Should the search recursively traverse folders. | |||||
| @param allowPluginsWhichRequireAsynchronousInstantiation | |||||
| If this is false then plug-ins which require | |||||
| asynchronous creation will be excluded. | |||||
| */ | */ | ||||
| virtual StringArray searchPathsForPlugins (const FileSearchPath& directoriesToSearch, | virtual StringArray searchPathsForPlugins (const FileSearchPath& directoriesToSearch, | ||||
| bool recursive) = 0; | |||||
| bool recursive, | |||||
| bool allowPluginsWhichRequireAsynchronousInstantiation = false) = 0; | |||||
| /** Returns the typical places to look for this kind of plugin. | /** Returns the typical places to look for this kind of plugin. | ||||
| @@ -103,8 +145,24 @@ public: | |||||
| protected: | protected: | ||||
| //============================================================================== | //============================================================================== | ||||
| friend class AudioPluginFormatManager; | |||||
| AudioPluginFormat() noexcept; | AudioPluginFormat() noexcept; | ||||
| /** Implementors must override this function. This is guaranteed to be called on | |||||
| the message thread. You may call the callback on any thread. | |||||
| */ | |||||
| virtual void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||||
| int initialBufferSize, void* userData, | |||||
| void (*callback) (void*, AudioPluginInstance*, const String&)) = 0; | |||||
| virtual bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept = 0; | |||||
| private: | |||||
| /** @internal */ | |||||
| void createPluginInstanceOnMessageThread (const PluginDescription&, double rate, int size, | |||||
| AudioPluginFormat::InstantiationCompletionCallback*); | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormat) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormat) | ||||
| }; | }; | ||||
| @@ -22,6 +22,39 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| namespace PluginFormatManagerHelpers | |||||
| { | |||||
| struct ErrorCallbackOnMessageThread : public CallbackMessage | |||||
| { | |||||
| ErrorCallbackOnMessageThread (const String& inError, | |||||
| AudioPluginFormat::InstantiationCompletionCallback* inCallback) | |||||
| : error (inError), callback (inCallback) | |||||
| { | |||||
| } | |||||
| void messageCallback() override { callback->completionCallback (nullptr, error); } | |||||
| String error; | |||||
| ScopedPointer<AudioPluginFormat::InstantiationCompletionCallback> callback; | |||||
| }; | |||||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||||
| struct ErrorLambdaOnMessageThread : public CallbackMessage | |||||
| { | |||||
| ErrorLambdaOnMessageThread (const String& inError, | |||||
| std::function<void (AudioPluginInstance*, const String&)> f) | |||||
| : error (inError), lambda (f) | |||||
| { | |||||
| } | |||||
| void messageCallback() override { lambda (nullptr, error); } | |||||
| String error; | |||||
| std::function<void (AudioPluginInstance*, const String&)> lambda; | |||||
| }; | |||||
| #endif | |||||
| } | |||||
| AudioPluginFormatManager::AudioPluginFormatManager() {} | AudioPluginFormatManager::AudioPluginFormatManager() {} | ||||
| AudioPluginFormatManager::~AudioPluginFormatManager() {} | AudioPluginFormatManager::~AudioPluginFormatManager() {} | ||||
| @@ -32,15 +65,15 @@ void AudioPluginFormatManager::addDefaultFormats() | |||||
| // you should only call this method once! | // you should only call this method once! | ||||
| for (int i = formats.size(); --i >= 0;) | for (int i = formats.size(); --i >= 0;) | ||||
| { | { | ||||
| #if JUCE_PLUGINHOST_VST | |||||
| #if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) | |||||
| jassert (dynamic_cast<VSTPluginFormat*> (formats[i]) == nullptr); | jassert (dynamic_cast<VSTPluginFormat*> (formats[i]) == nullptr); | ||||
| #endif | #endif | ||||
| #if JUCE_PLUGINHOST_VST3 | |||||
| #if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) | |||||
| jassert (dynamic_cast<VST3PluginFormat*> (formats[i]) == nullptr); | jassert (dynamic_cast<VST3PluginFormat*> (formats[i]) == nullptr); | ||||
| #endif | #endif | ||||
| #if JUCE_PLUGINHOST_AU && JUCE_MAC | |||||
| #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | |||||
| jassert (dynamic_cast<AudioUnitPluginFormat*> (formats[i]) == nullptr); | jassert (dynamic_cast<AudioUnitPluginFormat*> (formats[i]) == nullptr); | ||||
| #endif | #endif | ||||
| @@ -50,15 +83,15 @@ void AudioPluginFormatManager::addDefaultFormats() | |||||
| } | } | ||||
| #endif | #endif | ||||
| #if JUCE_PLUGINHOST_AU && JUCE_MAC | |||||
| #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | |||||
| formats.add (new AudioUnitPluginFormat()); | formats.add (new AudioUnitPluginFormat()); | ||||
| #endif | #endif | ||||
| #if JUCE_PLUGINHOST_VST | |||||
| #if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) | |||||
| formats.add (new VSTPluginFormat()); | formats.add (new VSTPluginFormat()); | ||||
| #endif | #endif | ||||
| #if JUCE_PLUGINHOST_VST3 | |||||
| #if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) | |||||
| formats.add (new VST3PluginFormat()); | formats.add (new VST3PluginFormat()); | ||||
| #endif | #endif | ||||
| @@ -85,12 +118,55 @@ void AudioPluginFormatManager::addFormat (AudioPluginFormat* const format) | |||||
| AudioPluginInstance* AudioPluginFormatManager::createPluginInstance (const PluginDescription& description, double rate, | AudioPluginInstance* AudioPluginFormatManager::createPluginInstance (const PluginDescription& description, double rate, | ||||
| int blockSize, String& errorMessage) const | int blockSize, String& errorMessage) const | ||||
| { | { | ||||
| if (AudioPluginFormat* format = findFormatForDescription (description, errorMessage)) | |||||
| return format->createInstanceFromDescription (description, rate, blockSize, errorMessage); | |||||
| return nullptr; | |||||
| } | |||||
| void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| AudioPluginFormat::InstantiationCompletionCallback* callback) | |||||
| { | |||||
| String error; | |||||
| if (AudioPluginFormat* format = findFormatForDescription (description, error)) | |||||
| return format->createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, callback); | |||||
| (new PluginFormatManagerHelpers::ErrorCallbackOnMessageThread (error, callback))->post(); | |||||
| } | |||||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||||
| void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| std::function<void (AudioPluginInstance*, const String&)> f) | |||||
| { | |||||
| String error; | |||||
| if (AudioPluginFormat* format = findFormatForDescription (description, error)) | |||||
| return format->createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, f); | |||||
| (new PluginFormatManagerHelpers::ErrorLambdaOnMessageThread (error, f))->post(); | |||||
| } | |||||
| #endif | |||||
| AudioPluginFormat* AudioPluginFormatManager::findFormatForDescription (const PluginDescription& description, String& errorMessage) const | |||||
| { | |||||
| errorMessage = String(); | |||||
| for (int i = 0; i < formats.size(); ++i) | for (int i = 0; i < formats.size(); ++i) | ||||
| if (AudioPluginInstance* result = formats.getUnchecked(i)->createInstanceFromDescription (description, rate, blockSize)) | |||||
| return result; | |||||
| { | |||||
| AudioPluginFormat* format; | |||||
| if ((format = formats.getUnchecked (i))->getName() == description.pluginFormatName | |||||
| && format->fileMightContainThisPluginType (description.fileOrIdentifier)) | |||||
| return format; | |||||
| } | |||||
| errorMessage = NEEDS_TRANS ("No compatible plug-in format exists for this plug-in"); | |||||
| errorMessage = doesPluginStillExist (description) ? TRANS ("This plug-in failed to load correctly") | |||||
| : TRANS ("This plug-in file no longer exists"); | |||||
| return nullptr; | return nullptr; | ||||
| } | } | ||||
| @@ -75,12 +75,51 @@ public: | |||||
| If it can't load the plugin, it returns nullptr and leaves a message in the | If it can't load the plugin, it returns nullptr and leaves a message in the | ||||
| errorMessage string. | errorMessage string. | ||||
| If you intend to instantiate a AudioUnit v3 plug-in then you must either | |||||
| use the non-blocking asynchrous version below - or call this method from a | |||||
| thread other than the message thread and without blocking the message | |||||
| thread. | |||||
| */ | */ | ||||
| AudioPluginInstance* createPluginInstance (const PluginDescription& description, | AudioPluginInstance* createPluginInstance (const PluginDescription& description, | ||||
| double initialSampleRate, | double initialSampleRate, | ||||
| int initialBufferSize, | int initialBufferSize, | ||||
| String& errorMessage) const; | String& errorMessage) const; | ||||
| /** Tries to asynchronously load the type for this description, by trying | |||||
| all the formats that this manager knows about. | |||||
| The caller must supply a callback object which will be called when | |||||
| the instantantiation has completed. | |||||
| If it can't load the plugin then the callback function will be called | |||||
| passing a nullptr as the instance argument along with an error message. | |||||
| The callback function will be called on the message thread so the caller | |||||
| must not block the message thread. | |||||
| The callback object will be deleted automatically after it has been | |||||
| invoked. | |||||
| The caller is responsible for deleting the instance that is passed to | |||||
| the callback function. | |||||
| If you intend to instantiate a AudioUnit v3 plug-in then you must use | |||||
| this non-blocking asynchrous version - or call the synchrous method | |||||
| from an auxiliary thread. | |||||
| */ | |||||
| void createPluginInstanceAsync (const PluginDescription& description, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| AudioPluginFormat::InstantiationCompletionCallback* callback); | |||||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||||
| void createPluginInstanceAsync (const PluginDescription& description, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| std::function<void (AudioPluginInstance*, const String&)> completionCallback); | |||||
| #endif | |||||
| /** Checks that the file or component for this plugin actually still exists. | /** Checks that the file or component for this plugin actually still exists. | ||||
| (This won't try to load the plugin) | (This won't try to load the plugin) | ||||
| @@ -89,6 +128,9 @@ public: | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| //@internal | |||||
| AudioPluginFormat* findFormatForDescription (const PluginDescription&, String& errorMessage) const; | |||||
| OwnedArray<AudioPluginFormat> formats; | OwnedArray<AudioPluginFormat> formats; | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormatManager) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormatManager) | ||||
| @@ -0,0 +1,778 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2015 - ROLI Ltd. | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ------------------------------------------------------------------------------ | |||||
| To release a closed-source product which uses JUCE, commercial licenses are | |||||
| available: visit www.juce.com for more information. | |||||
| ============================================================================== | |||||
| */ | |||||
| // This macro can be set if you need to override this internal name for some reason.. | |||||
| #ifndef JUCE_STATE_DICTIONARY_KEY | |||||
| #define JUCE_STATE_DICTIONARY_KEY "jucePluginState" | |||||
| #endif | |||||
| struct AudioUnitHelpers | |||||
| { | |||||
| // maps a channel index into an AU format to an index of a juce format | |||||
| struct AUChannelStreamOrder | |||||
| { | |||||
| AudioChannelLayoutTag auLayoutTag; | |||||
| AudioChannelSet::ChannelType speakerOrder[8]; | |||||
| }; | |||||
| struct StreamOrder : public AudioChannelSet | |||||
| { | |||||
| static AUChannelStreamOrder auChannelStreamOrder[]; | |||||
| }; | |||||
| static AudioChannelSet::ChannelType CoreAudioChannelLabelToJuceType (AudioChannelLabel label) noexcept | |||||
| { | |||||
| if (label >= kAudioChannelLabel_Discrete_0 && label <= kAudioChannelLabel_Discrete_65535) | |||||
| { | |||||
| const unsigned int discreteChannelNum = label - kAudioChannelLabel_Discrete_0; | |||||
| return static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + discreteChannelNum); | |||||
| } | |||||
| switch (label) | |||||
| { | |||||
| case kAudioChannelLabel_Center: | |||||
| case kAudioChannelLabel_Mono: return AudioChannelSet::centre; | |||||
| case kAudioChannelLabel_Left: | |||||
| case kAudioChannelLabel_HeadphonesLeft: return AudioChannelSet::left; | |||||
| case kAudioChannelLabel_Right: | |||||
| case kAudioChannelLabel_HeadphonesRight: return AudioChannelSet::right; | |||||
| case kAudioChannelLabel_LFEScreen: return AudioChannelSet::LFE; | |||||
| case kAudioChannelLabel_LeftSurround: return AudioChannelSet::leftSurround; | |||||
| case kAudioChannelLabel_RightSurround: return AudioChannelSet::rightSurround; | |||||
| case kAudioChannelLabel_LeftCenter: return AudioChannelSet::leftCentre; | |||||
| case kAudioChannelLabel_RightCenter: return AudioChannelSet::rightCentre; | |||||
| case kAudioChannelLabel_CenterSurround: return AudioChannelSet::surround; | |||||
| case kAudioChannelLabel_LeftSurroundDirect: return AudioChannelSet::leftSurroundSide; | |||||
| case kAudioChannelLabel_RightSurroundDirect: return AudioChannelSet::rightSurroundSide; | |||||
| case kAudioChannelLabel_TopCenterSurround: return AudioChannelSet::topMiddle; | |||||
| case kAudioChannelLabel_VerticalHeightLeft: return AudioChannelSet::topFrontLeft; | |||||
| case kAudioChannelLabel_VerticalHeightRight: return AudioChannelSet::topFrontRight; | |||||
| case kAudioChannelLabel_VerticalHeightCenter: return AudioChannelSet::topFrontCentre; | |||||
| case kAudioChannelLabel_TopBackLeft: return AudioChannelSet::topRearLeft; | |||||
| case kAudioChannelLabel_RearSurroundLeft: return AudioChannelSet::leftSurroundRear; | |||||
| case kAudioChannelLabel_TopBackRight: return AudioChannelSet::topRearRight; | |||||
| case kAudioChannelLabel_RearSurroundRight: return AudioChannelSet::rightSurroundRear; | |||||
| case kAudioChannelLabel_TopBackCenter: return AudioChannelSet::topRearCentre; | |||||
| case kAudioChannelLabel_LFE2: return AudioChannelSet::LFE2; | |||||
| case kAudioChannelLabel_LeftWide: return AudioChannelSet::wideLeft; | |||||
| case kAudioChannelLabel_RightWide: return AudioChannelSet::wideRight; | |||||
| case kAudioChannelLabel_Ambisonic_W: return AudioChannelSet::ambisonicW; | |||||
| case kAudioChannelLabel_Ambisonic_X: return AudioChannelSet::ambisonicX; | |||||
| case kAudioChannelLabel_Ambisonic_Y: return AudioChannelSet::ambisonicY; | |||||
| case kAudioChannelLabel_Ambisonic_Z: return AudioChannelSet::ambisonicZ; | |||||
| default: return AudioChannelSet::unknown; | |||||
| } | |||||
| } | |||||
| static AudioChannelLabel JuceChannelTypeToCoreAudioLabel (const AudioChannelSet::ChannelType& label) noexcept | |||||
| { | |||||
| if (label >= AudioChannelSet::discreteChannel0) | |||||
| { | |||||
| const unsigned int discreteChannelNum = label - AudioChannelSet::discreteChannel0;; | |||||
| return static_cast<AudioChannelLabel> (kAudioChannelLabel_Discrete_0 + discreteChannelNum); | |||||
| } | |||||
| switch (label) | |||||
| { | |||||
| case AudioChannelSet::centre: return kAudioChannelLabel_Center; | |||||
| case AudioChannelSet::left: return kAudioChannelLabel_Left; | |||||
| case AudioChannelSet::right: return kAudioChannelLabel_Right; | |||||
| case AudioChannelSet::LFE: return kAudioChannelLabel_LFEScreen; | |||||
| case AudioChannelSet::leftSurroundRear: return kAudioChannelLabel_RearSurroundLeft; | |||||
| case AudioChannelSet::rightSurroundRear: return kAudioChannelLabel_RearSurroundRight; | |||||
| case AudioChannelSet::leftCentre: return kAudioChannelLabel_LeftCenter; | |||||
| case AudioChannelSet::rightCentre: return kAudioChannelLabel_RightCenter; | |||||
| case AudioChannelSet::surround: return kAudioChannelLabel_CenterSurround; | |||||
| case AudioChannelSet::leftSurround: return kAudioChannelLabel_LeftSurround; | |||||
| case AudioChannelSet::rightSurround: return kAudioChannelLabel_RightSurround; | |||||
| case AudioChannelSet::topMiddle: return kAudioChannelLabel_TopCenterSurround; | |||||
| case AudioChannelSet::topFrontLeft: return kAudioChannelLabel_VerticalHeightLeft; | |||||
| case AudioChannelSet::topFrontRight: return kAudioChannelLabel_VerticalHeightRight; | |||||
| case AudioChannelSet::topFrontCentre: return kAudioChannelLabel_VerticalHeightCenter; | |||||
| case AudioChannelSet::topRearLeft: return kAudioChannelLabel_TopBackLeft; | |||||
| case AudioChannelSet::topRearRight: return kAudioChannelLabel_TopBackRight; | |||||
| case AudioChannelSet::topRearCentre: return kAudioChannelLabel_TopBackCenter; | |||||
| case AudioChannelSet::LFE2: return kAudioChannelLabel_LFE2; | |||||
| case AudioChannelSet::wideLeft: return kAudioChannelLabel_LeftWide; | |||||
| case AudioChannelSet::wideRight: return kAudioChannelLabel_RightWide; | |||||
| case AudioChannelSet::ambisonicW: return kAudioChannelLabel_Ambisonic_W; | |||||
| case AudioChannelSet::ambisonicX: return kAudioChannelLabel_Ambisonic_X; | |||||
| case AudioChannelSet::ambisonicY: return kAudioChannelLabel_Ambisonic_Y; | |||||
| case AudioChannelSet::ambisonicZ: return kAudioChannelLabel_Ambisonic_Z; | |||||
| case AudioChannelSet::leftSurroundSide: return kAudioChannelLabel_LeftSurroundDirect; | |||||
| case AudioChannelSet::rightSurroundSide: return kAudioChannelLabel_RightSurroundDirect; | |||||
| case AudioChannelSet::unknown: return kAudioChannelLabel_Unknown; | |||||
| case AudioChannelSet::discreteChannel0: return kAudioChannelLabel_Discrete_0; | |||||
| } | |||||
| return kAudioChannelLabel_Unknown; | |||||
| } | |||||
| static AudioChannelSet CoreAudioChannelBitmapToJuceType (UInt32 bitmap) noexcept | |||||
| { | |||||
| AudioChannelSet set; | |||||
| if ((bitmap & kAudioChannelBit_Left) != 0) set.addChannel (AudioChannelSet::left); | |||||
| if ((bitmap & kAudioChannelBit_Right) != 0) set.addChannel (AudioChannelSet::right); | |||||
| if ((bitmap & kAudioChannelBit_Center) != 0) set.addChannel (AudioChannelSet::centre); | |||||
| if ((bitmap & kAudioChannelBit_LFEScreen) != 0) set.addChannel (AudioChannelSet::LFE); | |||||
| if ((bitmap & kAudioChannelBit_LeftSurroundDirect) != 0) set.addChannel (AudioChannelSet::leftSurroundSide); | |||||
| if ((bitmap & kAudioChannelBit_RightSurroundDirect) != 0) set.addChannel (AudioChannelSet::rightSurroundSide); | |||||
| if ((bitmap & kAudioChannelBit_LeftCenter) != 0) set.addChannel (AudioChannelSet::leftCentre); | |||||
| if ((bitmap & kAudioChannelBit_RightCenter) != 0) set.addChannel (AudioChannelSet::rightCentre); | |||||
| if ((bitmap & kAudioChannelBit_CenterSurround) != 0) set.addChannel (AudioChannelSet::surround); | |||||
| if ((bitmap & kAudioChannelBit_LeftSurround) != 0) set.addChannel (AudioChannelSet::leftSurround); | |||||
| if ((bitmap & kAudioChannelBit_RightSurround) != 0) set.addChannel (AudioChannelSet::rightSurround); | |||||
| if ((bitmap & kAudioChannelBit_TopCenterSurround) != 0) set.addChannel (AudioChannelSet::topMiddle); | |||||
| if ((bitmap & kAudioChannelBit_VerticalHeightLeft) != 0) set.addChannel (AudioChannelSet::topFrontLeft); | |||||
| if ((bitmap & kAudioChannelBit_VerticalHeightCenter) != 0) set.addChannel (AudioChannelSet::topFrontCentre); | |||||
| if ((bitmap & kAudioChannelBit_VerticalHeightRight) != 0) set.addChannel (AudioChannelSet::topFrontRight); | |||||
| if ((bitmap & kAudioChannelBit_TopBackLeft) != 0) set.addChannel (AudioChannelSet::topRearLeft); | |||||
| if ((bitmap & kAudioChannelBit_TopBackCenter) != 0) set.addChannel (AudioChannelSet::topRearCentre); | |||||
| if ((bitmap & kAudioChannelBit_TopBackRight) != 0) set.addChannel (AudioChannelSet::topRearRight); | |||||
| return set; | |||||
| } | |||||
| static AudioChannelSet CoreAudioChannelLayoutToJuceType (const AudioChannelLayout& layout) noexcept | |||||
| { | |||||
| const AudioChannelLayoutTag tag = layout.mChannelLayoutTag; | |||||
| if (tag == kAudioChannelLayoutTag_UseChannelBitmap) return CoreAudioChannelBitmapToJuceType (layout.mChannelBitmap); | |||||
| if (tag == kAudioChannelLayoutTag_UseChannelDescriptions) | |||||
| { | |||||
| if (layout.mNumberChannelDescriptions <= 8) | |||||
| { | |||||
| // first try to convert the layout via the auChannelStreamOrder array | |||||
| int layoutIndex; | |||||
| for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != 0; ++layoutIndex) | |||||
| { | |||||
| const AUChannelStreamOrder& streamOrder = StreamOrder::auChannelStreamOrder[layoutIndex]; | |||||
| int numChannels; | |||||
| for (numChannels = 0; numChannels < 8 && streamOrder.speakerOrder[numChannels] != 0;) | |||||
| ++numChannels; | |||||
| if (numChannels != (int) layout.mNumberChannelDescriptions) | |||||
| continue; | |||||
| int ch; | |||||
| for (ch = 0; ch < numChannels; ++ch) | |||||
| if (JuceChannelTypeToCoreAudioLabel (streamOrder.speakerOrder[ch]) != layout.mChannelDescriptions[ch].mChannelLabel) | |||||
| break; | |||||
| // match! | |||||
| if (ch == numChannels) | |||||
| break; | |||||
| } | |||||
| if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != 0) | |||||
| return CALayoutTagToChannelSet (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag); | |||||
| } | |||||
| AudioChannelSet set; | |||||
| for (unsigned int i = 0; i < layout.mNumberChannelDescriptions; ++i) | |||||
| set.addChannel (CoreAudioChannelLabelToJuceType (layout.mChannelDescriptions[i].mChannelLabel)); | |||||
| return set; | |||||
| } | |||||
| return CALayoutTagToChannelSet (tag); | |||||
| } | |||||
| static AudioChannelSet CALayoutTagToChannelSet (AudioChannelLayoutTag tag) noexcept | |||||
| { | |||||
| switch (tag) | |||||
| { | |||||
| case kAudioChannelLayoutTag_Unknown: return AudioChannelSet::disabled(); | |||||
| case kAudioChannelLayoutTag_Mono: return AudioChannelSet::mono(); | |||||
| case kAudioChannelLayoutTag_Stereo: | |||||
| case kAudioChannelLayoutTag_StereoHeadphones: | |||||
| case kAudioChannelLayoutTag_Binaural: return AudioChannelSet::stereo(); | |||||
| case kAudioChannelLayoutTag_Quadraphonic: return AudioChannelSet::quadraphonic(); | |||||
| case kAudioChannelLayoutTag_Pentagonal: return AudioChannelSet::pentagonal(); | |||||
| case kAudioChannelLayoutTag_Hexagonal: return AudioChannelSet::hexagonal(); | |||||
| case kAudioChannelLayoutTag_Octagonal: return AudioChannelSet::octagonal(); | |||||
| case kAudioChannelLayoutTag_Ambisonic_B_Format: return AudioChannelSet::ambisonic(); | |||||
| case kAudioChannelLayoutTag_AudioUnit_6_0: return AudioChannelSet::create6point0(); | |||||
| case kAudioChannelLayoutTag_DTS_6_0_A: return AudioChannelSet::create6point0Music(); | |||||
| case kAudioChannelLayoutTag_MPEG_6_1_A: return AudioChannelSet::create6point1(); | |||||
| case kAudioChannelLayoutTag_DTS_6_1_A: return AudioChannelSet::create6point1Music(); | |||||
| case kAudioChannelLayoutTag_MPEG_5_0_B: | |||||
| case kAudioChannelLayoutTag_MPEG_5_0_A: | |||||
| return AudioChannelSet::create5point0(); | |||||
| case kAudioChannelLayoutTag_MPEG_5_1_A: return AudioChannelSet::create5point1(); | |||||
| case kAudioChannelLayoutTag_DTS_7_1: | |||||
| case kAudioChannelLayoutTag_AudioUnit_7_0: return AudioChannelSet::create7point0(); | |||||
| case kAudioChannelLayoutTag_AudioUnit_7_0_Front: return AudioChannelSet::create7point0SDDS(); | |||||
| case kAudioChannelLayoutTag_MPEG_7_1_A: return AudioChannelSet::create7point1SDDS(); | |||||
| case kAudioChannelLayoutTag_MPEG_3_0_A: | |||||
| case kAudioChannelLayoutTag_MPEG_3_0_B: return AudioChannelSet::createLCR(); | |||||
| case kAudioChannelLayoutTag_MPEG_4_0_A: | |||||
| case kAudioChannelLayoutTag_MPEG_4_0_B: return AudioChannelSet::createLCRS(); | |||||
| case kAudioChannelLayoutTag_ITU_2_1: return AudioChannelSet::createLRS(); | |||||
| case kAudioChannelLayoutTag_MPEG_7_1_C: return AudioChannelSet::create7point1(); | |||||
| } | |||||
| if (int numChannels = static_cast<int> (tag) & 0xffff) | |||||
| return AudioChannelSet::discreteChannels (numChannels); | |||||
| // Bitmap and channel description array layout tags are currently unsupported :-( | |||||
| jassertfalse; | |||||
| return AudioChannelSet(); | |||||
| } | |||||
| static AudioChannelLayoutTag ChannelSetToCALayoutTag (const AudioChannelSet& set) noexcept | |||||
| { | |||||
| if (set == AudioChannelSet::mono()) return kAudioChannelLayoutTag_Mono; | |||||
| if (set == AudioChannelSet::stereo()) return kAudioChannelLayoutTag_Stereo; | |||||
| if (set == AudioChannelSet::createLCR()) return kAudioChannelLayoutTag_MPEG_3_0_A; | |||||
| if (set == AudioChannelSet::createLRS()) return kAudioChannelLayoutTag_ITU_2_1; | |||||
| if (set == AudioChannelSet::createLCRS()) return kAudioChannelLayoutTag_MPEG_4_0_A; | |||||
| if (set == AudioChannelSet::quadraphonic()) return kAudioChannelLayoutTag_Quadraphonic; | |||||
| if (set == AudioChannelSet::pentagonal()) return kAudioChannelLayoutTag_Pentagonal; | |||||
| if (set == AudioChannelSet::hexagonal()) return kAudioChannelLayoutTag_Hexagonal; | |||||
| if (set == AudioChannelSet::octagonal()) return kAudioChannelLayoutTag_Octagonal; | |||||
| if (set == AudioChannelSet::ambisonic()) return kAudioChannelLayoutTag_Ambisonic_B_Format; | |||||
| if (set == AudioChannelSet::create5point0()) return kAudioChannelLayoutTag_MPEG_5_0_A; | |||||
| if (set == AudioChannelSet::create5point1()) return kAudioChannelLayoutTag_MPEG_5_1_A; | |||||
| if (set == AudioChannelSet::create6point0()) return kAudioChannelLayoutTag_AudioUnit_6_0; | |||||
| if (set == AudioChannelSet::create6point0Music()) return kAudioChannelLayoutTag_DTS_6_0_A; | |||||
| if (set == AudioChannelSet::create6point1Music()) return kAudioChannelLayoutTag_DTS_6_1_A; | |||||
| if (set == AudioChannelSet::create6point1()) return kAudioChannelLayoutTag_MPEG_6_1_A; | |||||
| if (set == AudioChannelSet::create7point0()) return kAudioChannelLayoutTag_AudioUnit_7_0; | |||||
| if (set == AudioChannelSet::create7point1()) return kAudioChannelLayoutTag_MPEG_7_1_C; | |||||
| if (set == AudioChannelSet::create7point0SDDS()) return kAudioChannelLayoutTag_AudioUnit_7_0_Front; | |||||
| if (set == AudioChannelSet::create7point1SDDS()) return kAudioChannelLayoutTag_MPEG_7_1_A; | |||||
| if (set == AudioChannelSet::disabled()) return kAudioChannelLayoutTag_Unknown; | |||||
| return static_cast<AudioChannelLayoutTag> ((int) kAudioChannelLayoutTag_DiscreteInOrder | set.size()); | |||||
| } | |||||
| static int auChannelIndexToJuce (int auIndex, const AudioChannelSet& channelSet) | |||||
| { | |||||
| if (auIndex >= 8) return auIndex; | |||||
| AudioChannelLayoutTag currentLayout = ChannelSetToCALayoutTag (channelSet); | |||||
| int layoutIndex; | |||||
| for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != currentLayout; ++layoutIndex) | |||||
| if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag == 0) return auIndex; | |||||
| AudioChannelSet::ChannelType channelType | |||||
| = StreamOrder::auChannelStreamOrder[layoutIndex].speakerOrder[auIndex]; | |||||
| const int juceIndex = channelSet.getChannelTypes().indexOf (channelType); | |||||
| jassert (juceIndex >= 0); | |||||
| return juceIndex >= 0 ? juceIndex : auIndex; | |||||
| } | |||||
| static int juceChannelIndexToAu (int juceIndex, const AudioChannelSet& channelSet) | |||||
| { | |||||
| if (channelSet.isDiscreteLayout()) | |||||
| return juceIndex; | |||||
| AudioChannelLayoutTag currentLayout = ChannelSetToCALayoutTag (channelSet); | |||||
| int layoutIndex; | |||||
| for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != currentLayout; ++layoutIndex) | |||||
| { | |||||
| if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag == 0) | |||||
| { | |||||
| jassertfalse; | |||||
| return juceIndex; | |||||
| } | |||||
| } | |||||
| const AUChannelStreamOrder& channelOrder = StreamOrder::auChannelStreamOrder[layoutIndex]; | |||||
| AudioChannelSet::ChannelType channelType = channelSet.getTypeOfChannel (juceIndex); | |||||
| for (int i = 0; i < 8 && channelOrder.speakerOrder[i] != 0; ++i) | |||||
| if (channelOrder.speakerOrder[i] == channelType) | |||||
| return i; | |||||
| jassertfalse; | |||||
| return juceIndex; | |||||
| } | |||||
| class ChannelRemapper | |||||
| { | |||||
| public: | |||||
| ChannelRemapper (AudioProcessor& p) : processor (p), inputLayoutMap (nullptr), outputLayoutMap (nullptr) {} | |||||
| ~ChannelRemapper() {} | |||||
| void alloc() | |||||
| { | |||||
| const int numInputBuses = processor.getBusCount (true); | |||||
| const int numOutputBuses = processor.getBusCount (false); | |||||
| initializeChannelMapArray (true, numInputBuses); | |||||
| initializeChannelMapArray (false, numOutputBuses); | |||||
| for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) | |||||
| fillLayoutChannelMaps (true, busIdx); | |||||
| for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) | |||||
| fillLayoutChannelMaps (false, busIdx); | |||||
| } | |||||
| void release() | |||||
| { | |||||
| inputLayoutMap = outputLayoutMap = nullptr; | |||||
| inputLayoutMapPtrStorage.free(); | |||||
| outputLayoutMapPtrStorage.free(); | |||||
| inputLayoutMapStorage.free(); | |||||
| outputLayoutMapStorage.free(); | |||||
| } | |||||
| inline const int* get (bool input, int bus) const noexcept { return (input ? inputLayoutMap : outputLayoutMap) [bus]; } | |||||
| private: | |||||
| //============================================================================== | |||||
| AudioProcessor& processor; | |||||
| HeapBlock<int*> inputLayoutMapPtrStorage, outputLayoutMapPtrStorage; | |||||
| HeapBlock<int> inputLayoutMapStorage, outputLayoutMapStorage; | |||||
| int** inputLayoutMap; | |||||
| int** outputLayoutMap; | |||||
| //============================================================================== | |||||
| void initializeChannelMapArray (bool isInput, const int numBuses) | |||||
| { | |||||
| HeapBlock<int*>& layoutMapPtrStorage = isInput ? inputLayoutMapPtrStorage : outputLayoutMapPtrStorage; | |||||
| HeapBlock<int>& layoutMapStorage = isInput ? inputLayoutMapStorage : outputLayoutMapStorage; | |||||
| int**& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; | |||||
| const int totalInChannels = processor.getTotalNumInputChannels(); | |||||
| const int totalOutChannels = processor.getTotalNumOutputChannels(); | |||||
| layoutMapPtrStorage.calloc (static_cast<size_t> (numBuses)); | |||||
| layoutMapStorage.calloc (static_cast<size_t> (isInput ? totalInChannels : totalOutChannels)); | |||||
| layoutMap = layoutMapPtrStorage. getData(); | |||||
| int ch = 0; | |||||
| for (int busIdx = 0; busIdx < numBuses; ++busIdx) | |||||
| { | |||||
| layoutMap[busIdx] = layoutMapStorage.getData() + ch; | |||||
| ch += processor.getChannelCountOfBus (isInput, busIdx); | |||||
| } | |||||
| } | |||||
| void fillLayoutChannelMaps (bool isInput, int busNr) | |||||
| { | |||||
| int* layoutMap = (isInput ? inputLayoutMap : outputLayoutMap)[busNr]; | |||||
| const AudioChannelSet& channelFormat = processor.getChannelLayoutOfBus (isInput, busNr); | |||||
| const int numChannels = channelFormat.size(); | |||||
| for (int i = 0; i < numChannels; ++i) | |||||
| layoutMap[i] = AudioUnitHelpers::juceChannelIndexToAu (i, channelFormat); | |||||
| } | |||||
| }; | |||||
| //============================================================================== | |||||
| class CoreAudioBufferList | |||||
| { | |||||
| public: | |||||
| CoreAudioBufferList() { reset(); } | |||||
| //============================================================================== | |||||
| void prepare (int inChannels, int outChannels, int maxFrames) | |||||
| { | |||||
| const int numChannels = jmax (inChannels, outChannels); | |||||
| scratch.setSize (numChannels, maxFrames); | |||||
| channels.calloc (static_cast<size_t> (numChannels)); | |||||
| reset(); | |||||
| } | |||||
| void release() | |||||
| { | |||||
| scratch.setSize (0, 0); | |||||
| channels.free(); | |||||
| } | |||||
| void reset() noexcept | |||||
| { | |||||
| pushIdx = 0; | |||||
| popIdx = 0; | |||||
| zeromem (channels.getData(), sizeof(float*) * static_cast<size_t> (scratch.getNumChannels())); | |||||
| } | |||||
| //============================================================================== | |||||
| float* setBuffer (const int idx, float* ptr = nullptr) noexcept | |||||
| { | |||||
| jassert (idx < scratch.getNumChannels()); | |||||
| return (channels [idx] = uniqueBuffer (idx, ptr)); | |||||
| } | |||||
| //============================================================================== | |||||
| float* push() noexcept | |||||
| { | |||||
| jassert (pushIdx < scratch.getNumChannels()); | |||||
| return channels [pushIdx++]; | |||||
| } | |||||
| void push (AudioBufferList& bufferList, const int* channelMap) noexcept | |||||
| { | |||||
| jassert (pushIdx < scratch.getNumChannels()); | |||||
| if (bufferList.mNumberBuffers > 0) | |||||
| { | |||||
| const UInt32 n = bufferList.mBuffers [0].mDataByteSize / | |||||
| (bufferList.mBuffers [0].mNumberChannels * sizeof (float)); | |||||
| const bool isInterleaved = isAudioBufferInterleaved (bufferList); | |||||
| const int numChannels = static_cast<int> (isInterleaved ? bufferList.mBuffers [0].mNumberChannels | |||||
| : bufferList.mNumberBuffers); | |||||
| for (int ch = 0; ch < numChannels; ++ch) | |||||
| { | |||||
| float* data = push(); | |||||
| int mappedChannel = channelMap [ch]; | |||||
| if (isInterleaved || static_cast<float*> (bufferList.mBuffers [mappedChannel].mData) != data) | |||||
| copyAudioBuffer (bufferList, mappedChannel, n, data); | |||||
| } | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| float* pop() noexcept | |||||
| { | |||||
| jassert (popIdx < scratch.getNumChannels()); | |||||
| return channels[popIdx++]; | |||||
| } | |||||
| void pop (AudioBufferList& buffer, const int* channelMap) noexcept | |||||
| { | |||||
| if (buffer.mNumberBuffers > 0) | |||||
| { | |||||
| const UInt32 n = buffer.mBuffers [0].mDataByteSize / (buffer.mBuffers [0].mNumberChannels * sizeof (float)); | |||||
| const bool isInterleaved = isAudioBufferInterleaved (buffer); | |||||
| const int numChannels = static_cast<int> (isInterleaved ? buffer.mBuffers [0].mNumberChannels : buffer.mNumberBuffers); | |||||
| for (int ch = 0; ch < numChannels; ++ch) | |||||
| { | |||||
| int mappedChannel = channelMap [ch]; | |||||
| float* nextBuffer = pop(); | |||||
| if (nextBuffer == buffer.mBuffers [mappedChannel].mData && ! isInterleaved) | |||||
| continue; // no copying necessary | |||||
| if (buffer.mBuffers [mappedChannel].mData == nullptr && ! isInterleaved) | |||||
| buffer.mBuffers [mappedChannel].mData = nextBuffer; | |||||
| else | |||||
| copyAudioBuffer (nextBuffer, mappedChannel, n, buffer); | |||||
| } | |||||
| } | |||||
| } | |||||
| //============================================================================== | |||||
| AudioSampleBuffer& getBuffer (UInt32 frames) noexcept | |||||
| { | |||||
| jassert (pushIdx == scratch.getNumChannels()); | |||||
| #if JUCE_DEBUG | |||||
| for (int i = 0; i < pushIdx; ++i) | |||||
| jassert (channels [i] != nullptr); | |||||
| #endif | |||||
| mutableBuffer.setDataToReferTo (channels, pushIdx, static_cast<int> (frames)); | |||||
| return mutableBuffer; | |||||
| } | |||||
| private: | |||||
| float* uniqueBuffer (int idx, float* buffer) noexcept | |||||
| { | |||||
| if (buffer == nullptr) | |||||
| return scratch.getWritePointer (idx); | |||||
| for (int ch = 0; ch < idx; ++ch) | |||||
| if (buffer == channels[ch]) | |||||
| return scratch.getWritePointer (idx); | |||||
| return buffer; | |||||
| } | |||||
| //============================================================================== | |||||
| AudioSampleBuffer scratch; | |||||
| AudioSampleBuffer mutableBuffer; | |||||
| HeapBlock<float*> channels; | |||||
| int pushIdx, popIdx; | |||||
| }; | |||||
| static bool isAudioBufferInterleaved (const AudioBufferList& audioBuffer) noexcept | |||||
| { | |||||
| return (audioBuffer.mNumberBuffers == 1 && audioBuffer.mBuffers[0].mNumberChannels > 1); | |||||
| } | |||||
| static void clearAudioBuffer (const AudioBufferList& audioBuffer) noexcept | |||||
| { | |||||
| for (unsigned int ch = 0; ch < audioBuffer.mNumberBuffers; ++ch) | |||||
| zeromem (audioBuffer.mBuffers[ch].mData, audioBuffer.mBuffers[ch].mDataByteSize); | |||||
| } | |||||
| static void copyAudioBuffer (const AudioBufferList& audioBuffer, const int channel, const UInt32 size, float* dst) noexcept | |||||
| { | |||||
| if (! isAudioBufferInterleaved (audioBuffer)) | |||||
| { | |||||
| jassert (channel < static_cast<int> (audioBuffer.mNumberBuffers)); | |||||
| jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); | |||||
| memcpy (dst, audioBuffer.mBuffers[channel].mData, size * sizeof (float)); | |||||
| } | |||||
| else | |||||
| { | |||||
| const int numChannels = static_cast<int> (audioBuffer.mBuffers[0].mNumberChannels); | |||||
| const UInt32 n = static_cast<UInt32> (numChannels) * size; | |||||
| const float* src = static_cast<const float*> (audioBuffer.mBuffers[0].mData); | |||||
| jassert (channel < numChannels); | |||||
| jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); | |||||
| for (const float* inData = src; inData < (src + n); inData += numChannels) | |||||
| *dst++ = inData[channel]; | |||||
| } | |||||
| } | |||||
| static void copyAudioBuffer (const float *src, const int channel, const UInt32 size, AudioBufferList& audioBuffer) noexcept | |||||
| { | |||||
| if (! isAudioBufferInterleaved (audioBuffer)) | |||||
| { | |||||
| jassert (channel < static_cast<int> (audioBuffer.mNumberBuffers)); | |||||
| jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); | |||||
| memcpy (audioBuffer.mBuffers[channel].mData, src, size * sizeof (float)); | |||||
| } | |||||
| else | |||||
| { | |||||
| const int numChannels = static_cast<int> (audioBuffer.mBuffers[0].mNumberChannels); | |||||
| const UInt32 n = static_cast<UInt32> (numChannels) * size; | |||||
| float* dst = static_cast<float*> (audioBuffer.mBuffers[0].mData); | |||||
| jassert (channel < numChannels); | |||||
| jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); | |||||
| for (float* outData = dst; outData < (dst + n); outData += numChannels) | |||||
| outData[channel] = *src++; | |||||
| } | |||||
| } | |||||
| template <int numLayouts> | |||||
| static bool isLayoutSupported (const AudioProcessor& processor, | |||||
| bool isInput, int busIdx, | |||||
| int numChannels, | |||||
| const short (&channelLayoutList) [numLayouts][2], | |||||
| bool hasLayoutMap = true) | |||||
| { | |||||
| if (const AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) | |||||
| { | |||||
| if (! bus->isNumberOfChannelsSupported (numChannels)) | |||||
| return false; | |||||
| if (! hasLayoutMap) | |||||
| return true; | |||||
| const int numConfigs = sizeof (channelLayoutList) / sizeof (short[2]); | |||||
| for (int i = 0; i < numConfigs; ++i) | |||||
| { | |||||
| if (channelLayoutList[i][isInput ? 0 : 1] == numChannels) | |||||
| return true; | |||||
| } | |||||
| } | |||||
| return false; | |||||
| } | |||||
| static Array<AUChannelInfo> getAUChannelInfo (const AudioProcessor& processor) | |||||
| { | |||||
| Array<AUChannelInfo> channelInfo; | |||||
| const bool hasMainInputBus = (processor.getBusCount (true) > 0); | |||||
| const bool hasMainOutputBus = (processor.getBusCount (false) > 0); | |||||
| if ((! hasMainInputBus) && (! hasMainOutputBus)) | |||||
| { | |||||
| // midi effect plug-in: no audio | |||||
| AUChannelInfo info; | |||||
| info.inChannels = 0; | |||||
| info.outChannels = 0; | |||||
| channelInfo.add (info); | |||||
| return channelInfo; | |||||
| } | |||||
| else | |||||
| { | |||||
| const uint32_t maxNumChanToCheckFor = 9; | |||||
| uint32_t defaultInputs = static_cast<uint32_t> (processor.getChannelCountOfBus (true, 0)); | |||||
| uint32_t defaultOutputs = static_cast<uint32_t> (processor.getChannelCountOfBus (false, 0)); | |||||
| uint32_t lastInputs = defaultInputs; | |||||
| uint32_t lastOutputs = defaultOutputs; | |||||
| SortedSet<uint32_t> supportedChannels; | |||||
| // add the current configuration | |||||
| if (lastInputs != 0 || lastOutputs != 0) | |||||
| supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||||
| for (uint32_t inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | |||||
| { | |||||
| const AudioProcessor::Bus* inBus = processor.getBus (true, 0); | |||||
| if (inBus != nullptr && (! inBus->isNumberOfChannelsSupported ((int) inChanNum))) | |||||
| continue; | |||||
| for (uint32_t outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) | |||||
| { | |||||
| const AudioProcessor::Bus* outBus = processor.getBus (false, 0); | |||||
| if (outBus != nullptr && (! outBus->isNumberOfChannelsSupported ((int) outChanNum))) | |||||
| continue; | |||||
| uint32_t channelConfiguration = (inChanNum << 16) | outChanNum; | |||||
| // did we already try this configuration? | |||||
| if (supportedChannels.contains (channelConfiguration)) continue; | |||||
| if (lastInputs != inChanNum && (inChanNum > 0 && inBus != nullptr)) | |||||
| { | |||||
| AudioChannelSet set = inBus->supportedLayoutWithChannels ((int) inChanNum); | |||||
| AudioProcessor::BusesLayout layouts = inBus->getBusesLayoutForLayoutChangeOfBus (set); | |||||
| lastInputs = inChanNum; | |||||
| lastOutputs = hasMainOutputBus ? static_cast<uint32_t> (layouts.outputBuses.getReference (0).size()) : 0; | |||||
| supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||||
| } | |||||
| if (lastOutputs != outChanNum && (outChanNum > 0 && outBus != nullptr)) | |||||
| { | |||||
| AudioChannelSet set = outBus->supportedLayoutWithChannels ((int) outChanNum); | |||||
| AudioProcessor::BusesLayout layouts = outBus->getBusesLayoutForLayoutChangeOfBus (set); | |||||
| lastOutputs = outChanNum; | |||||
| lastInputs = hasMainInputBus ? static_cast<uint32_t> (layouts.inputBuses.getReference (0).size()) : 0; | |||||
| supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||||
| } | |||||
| } | |||||
| } | |||||
| bool hasInOutMismatch = false; | |||||
| for (int i = 0; i < supportedChannels.size(); ++i) | |||||
| { | |||||
| const uint32_t numInputs = (supportedChannels[i] >> 16) & 0xffff; | |||||
| const uint32_t numOutputs = (supportedChannels[i] >> 0) & 0xffff; | |||||
| if (numInputs != numOutputs) | |||||
| { | |||||
| hasInOutMismatch = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| bool hasUnsupportedInput = ! hasMainOutputBus, hasUnsupportedOutput = ! hasMainInputBus; | |||||
| for (uint32_t inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | |||||
| { | |||||
| uint32_t channelConfiguration = (inChanNum << 16) | (hasInOutMismatch ? defaultOutputs : inChanNum); | |||||
| if (! supportedChannels.contains (channelConfiguration)) | |||||
| { | |||||
| hasUnsupportedInput = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| for (uint32_t outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) | |||||
| { | |||||
| uint32_t channelConfiguration = ((hasInOutMismatch ? defaultInputs : outChanNum) << 16) | outChanNum; | |||||
| if (! supportedChannels.contains (channelConfiguration)) | |||||
| { | |||||
| hasUnsupportedOutput = true; | |||||
| break; | |||||
| } | |||||
| } | |||||
| for (int i = 0; i < supportedChannels.size(); ++i) | |||||
| { | |||||
| const int numInputs = (supportedChannels[i] >> 16) & 0xffff; | |||||
| const int numOutputs = (supportedChannels[i] >> 0) & 0xffff; | |||||
| AUChannelInfo info; | |||||
| // see here: https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html | |||||
| info.inChannels = static_cast<SInt16> (hasMainInputBus ? (hasUnsupportedInput ? numInputs : (hasInOutMismatch && (! hasUnsupportedOutput) ? -2 : -1)) : 0); | |||||
| info.outChannels = static_cast<SInt16> (hasMainOutputBus ? (hasUnsupportedOutput ? numOutputs : (hasInOutMismatch && (! hasUnsupportedInput) ? -2 : -1)) : 0); | |||||
| if (info.inChannels == -2 && info.outChannels == -2) | |||||
| info.inChannels = -1; | |||||
| int j; | |||||
| for (j = 0; j < channelInfo.size(); ++j) | |||||
| if (channelInfo[j].inChannels == info.inChannels && channelInfo[j].outChannels == info.outChannels) | |||||
| break; | |||||
| if (j >= channelInfo.size()) | |||||
| channelInfo.add (info); | |||||
| } | |||||
| } | |||||
| return channelInfo; | |||||
| } | |||||
| }; | |||||
| AudioUnitHelpers::AUChannelStreamOrder AudioUnitHelpers::StreamOrder::auChannelStreamOrder[] = | |||||
| { | |||||
| {kAudioChannelLayoutTag_Mono, {centre, unknown, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_Stereo, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_StereoHeadphones, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_Binaural, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_Quadraphonic, {left, right, leftSurround, rightSurround, unknown, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_Pentagonal, {left, right, leftSurroundRear, rightSurroundRear, centre, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_Hexagonal, {left, right, leftSurroundRear, rightSurroundRear, centre, centreSurround, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_Octagonal, {left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight}}, | |||||
| {kAudioChannelLayoutTag_Ambisonic_B_Format, {ambisonicW, ambisonicX, ambisonicY, ambisonicZ, unknown, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_MPEG_5_0_A, {left, right, centre, leftSurround, rightSurround, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_MPEG_5_0_B, {left, right, leftSurround, rightSurround, centre, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_MPEG_5_1_A, {left, right, centre, LFE, leftSurround, rightSurround, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_AudioUnit_6_0, {left, right, leftSurround, rightSurround, centre, centreSurround, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_DTS_6_0_A, {left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, unknown, unknown}}, // TODO check this one | |||||
| {kAudioChannelLayoutTag_MPEG_6_1_A, {left, right, centre, LFE, leftSurround, rightSurround, centre, unknown}}, | |||||
| {kAudioChannelLayoutTag_DTS_6_1_A, {leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround, LFE, unknown}}, | |||||
| {kAudioChannelLayoutTag_AudioUnit_7_0, {left, right, leftSurroundSide, rightSurroundSide, centre, leftSurroundRear, rightSurroundRear, unknown}}, | |||||
| {kAudioChannelLayoutTag_MPEG_7_1_C, {left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear}}, | |||||
| {kAudioChannelLayoutTag_AudioUnit_7_0_Front,{left, right, leftSurround, rightSurround, centre, leftCentre, rightCentre, unknown}}, | |||||
| {kAudioChannelLayoutTag_MPEG_7_1_A, {left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre}}, | |||||
| {kAudioChannelLayoutTag_DTS_7_1, {leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround, LFE}}, | |||||
| {kAudioChannelLayoutTag_MPEG_3_0_A, {left, right, centre, unknown, unknown, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_MPEG_3_0_B, {centre, left, right, unknown, unknown, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_MPEG_4_0_A, {left, right, centre, centreSurround, unknown, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_MPEG_4_0_B, {centre, left, right, centreSurround, unknown, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_ITU_2_1, {left, right, centreSurround, unknown, unknown, unknown, unknown, unknown}}, | |||||
| {kAudioChannelLayoutTag_EAC3_7_1_C, {left, centre, right, leftSurround, rightSurround, LFE, leftSurroundSide, rightSurroundSide}}, | |||||
| {unknown, {unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown}} | |||||
| }; | |||||
| @@ -22,7 +22,7 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| #if (JUCE_PLUGINHOST_AU && JUCE_MAC) || DOXYGEN | |||||
| #if (JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS)) || DOXYGEN | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| @@ -38,16 +38,24 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| String getName() const override { return "AudioUnit"; } | String getName() const override { return "AudioUnit"; } | ||||
| void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | ||||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc, double, int) override; | |||||
| bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | ||||
| String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | ||||
| bool pluginNeedsRescanning (const PluginDescription&) override; | bool pluginNeedsRescanning (const PluginDescription&) override; | ||||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; | |||||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||||
| bool doesPluginStillExist (const PluginDescription&) override; | bool doesPluginStillExist (const PluginDescription&) override; | ||||
| FileSearchPath getDefaultLocationsToSearch() override; | FileSearchPath getDefaultLocationsToSearch() override; | ||||
| bool canScanForPlugins() const override { return true; } | bool canScanForPlugins() const override { return true; } | ||||
| private: | private: | ||||
| //============================================================================== | |||||
| void createPluginInstance (const PluginDescription&, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize, | |||||
| void* userData, | |||||
| void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||||
| bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||||
| //============================================================================== | //============================================================================== | ||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginFormat) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginFormat) | ||||
| }; | }; | ||||
| @@ -55,8 +63,10 @@ private: | |||||
| #endif | #endif | ||||
| //============================================================================== | //============================================================================== | ||||
| #if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 | |||||
| enum | enum | ||||
| { | { | ||||
| /** Custom AudioUnit property used to indicate MPE support */ | /** Custom AudioUnit property used to indicate MPE support */ | ||||
| kAudioUnitProperty_SupportsMPE = 75001 | |||||
| kAudioUnitProperty_SupportsMPE = 58 | |||||
| }; | }; | ||||
| #endif | |||||
| @@ -610,11 +610,14 @@ void LADSPAPluginFormat::findAllTypesForFile (OwnedArray <PluginDescription>& re | |||||
| } | } | ||||
| } | } | ||||
| AudioPluginInstance* LADSPAPluginFormat::createInstanceFromDescription (const PluginDescription& desc, | |||||
| double sampleRate, int blockSize) | |||||
| void LADSPAPluginFormat::createPluginInstance (const PluginDescription& desc, | |||||
| double sampleRate, int blockSize, | |||||
| void* userData, | |||||
| void (*callback) (void*, AudioPluginInstance*, const String&)) | |||||
| { | { | ||||
| ScopedPointer<LADSPAPluginInstance> result; | ScopedPointer<LADSPAPluginInstance> result; | ||||
| if (fileMightContainThisPluginType (desc.fileOrIdentifier)) | if (fileMightContainThisPluginType (desc.fileOrIdentifier)) | ||||
| { | { | ||||
| File file (desc.fileOrIdentifier); | File file (desc.fileOrIdentifier); | ||||
| @@ -639,7 +642,17 @@ AudioPluginInstance* LADSPAPluginFormat::createInstanceFromDescription (const Pl | |||||
| previousWorkingDirectory.setAsCurrentWorkingDirectory(); | previousWorkingDirectory.setAsCurrentWorkingDirectory(); | ||||
| } | } | ||||
| return result.release(); | |||||
| String errorMsg; | |||||
| if (result == nullptr) | |||||
| errorMsg = String (NEEDS_TRANS ("Unable to load XXX plug-in file")).replace ("XXX", "LADSPA"); | |||||
| callback (userData, result.release(), errorMsg); | |||||
| } | |||||
| bool LADSPAPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept | |||||
| { | |||||
| return false; | |||||
| } | } | ||||
| bool LADSPAPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) | bool LADSPAPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) | ||||
| @@ -663,7 +676,7 @@ bool LADSPAPluginFormat::doesPluginStillExist (const PluginDescription& desc) | |||||
| return File::createFileWithoutCheckingPath (desc.fileOrIdentifier).exists(); | return File::createFileWithoutCheckingPath (desc.fileOrIdentifier).exists(); | ||||
| } | } | ||||
| StringArray LADSPAPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive) | |||||
| StringArray LADSPAPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive, bool) | |||||
| { | { | ||||
| StringArray results; | StringArray results; | ||||
| @@ -31,22 +31,28 @@ | |||||
| class JUCE_API LADSPAPluginFormat : public AudioPluginFormat | class JUCE_API LADSPAPluginFormat : public AudioPluginFormat | ||||
| { | { | ||||
| public: | public: | ||||
| //============================================================================== | |||||
| LADSPAPluginFormat(); | LADSPAPluginFormat(); | ||||
| ~LADSPAPluginFormat(); | ~LADSPAPluginFormat(); | ||||
| //============================================================================== | //============================================================================== | ||||
| String getName() const override { return "LADSPA"; } | String getName() const override { return "LADSPA"; } | ||||
| void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | ||||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, double, int) override; | |||||
| bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | ||||
| String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | ||||
| bool pluginNeedsRescanning (const PluginDescription&) override; | bool pluginNeedsRescanning (const PluginDescription&) override; | ||||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; | |||||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||||
| bool doesPluginStillExist (const PluginDescription&) override; | bool doesPluginStillExist (const PluginDescription&) override; | ||||
| FileSearchPath getDefaultLocationsToSearch() override; | FileSearchPath getDefaultLocationsToSearch() override; | ||||
| bool canScanForPlugins() const override { return true; } | bool canScanForPlugins() const override { return true; } | ||||
| private: | |||||
| //============================================================================== | |||||
| void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||||
| int initialBufferSize, void* userData, | |||||
| void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||||
| bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||||
| private: | private: | ||||
| void recursiveFileSearch (StringArray&, const File&, bool recursive); | void recursiveFileSearch (StringArray&, const File&, bool recursive); | ||||
| @@ -67,17 +67,17 @@ inline juce::String toString (const Steinberg::char16* string) noexcept { re | |||||
| inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | ||||
| inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | ||||
| static void toString128 (Steinberg::Vst::String128 result, const char* source) | |||||
| inline void toString128 (Steinberg::Vst::String128 result, const char* source) | |||||
| { | { | ||||
| Steinberg::UString (result, 128).fromAscii (source); | Steinberg::UString (result, 128).fromAscii (source); | ||||
| } | } | ||||
| static void toString128 (Steinberg::Vst::String128 result, const juce::String& source) | |||||
| inline void toString128 (Steinberg::Vst::String128 result, const juce::String& source) | |||||
| { | { | ||||
| Steinberg::UString (result, 128).fromAscii (source.toUTF8()); | Steinberg::UString (result, 128).fromAscii (source.toUTF8()); | ||||
| } | } | ||||
| static Steinberg::Vst::TChar* toString (const juce::String& source) noexcept | |||||
| inline Steinberg::Vst::TChar* toString (const juce::String& source) noexcept | |||||
| { | { | ||||
| return reinterpret_cast<Steinberg::Vst::TChar*> (source.toUTF16().getAddress()); | return reinterpret_cast<Steinberg::Vst::TChar*> (source.toUTF16().getAddress()); | ||||
| } | } | ||||
| @@ -146,22 +146,22 @@ static inline Steinberg::Vst::Speaker getSpeakerType (AudioChannelSet::ChannelTy | |||||
| case AudioChannelSet::left: return kSpeakerL; | case AudioChannelSet::left: return kSpeakerL; | ||||
| case AudioChannelSet::right: return kSpeakerR; | case AudioChannelSet::right: return kSpeakerR; | ||||
| case AudioChannelSet::centre: return kSpeakerC; | case AudioChannelSet::centre: return kSpeakerC; | ||||
| case AudioChannelSet::subbass: return kSpeakerLfe; | |||||
| case AudioChannelSet::surroundLeft: return kSpeakerLs; | |||||
| case AudioChannelSet::surroundRight: return kSpeakerRs; | |||||
| case AudioChannelSet::centreLeft: return kSpeakerLc; | |||||
| case AudioChannelSet::centreRight: return kSpeakerRc; | |||||
| case AudioChannelSet::surround: return kSpeakerS; | |||||
| case AudioChannelSet::sideLeft: return kSpeakerSl; | |||||
| case AudioChannelSet::sideRight: return kSpeakerSr; | |||||
| case AudioChannelSet::topMiddle: return kSpeakerTm; | |||||
| case AudioChannelSet::LFE: return kSpeakerLfe; | |||||
| case AudioChannelSet::leftSurround: return kSpeakerLs; | |||||
| case AudioChannelSet::rightSurround: return kSpeakerRs; | |||||
| case AudioChannelSet::leftCentre: return kSpeakerLc; | |||||
| case AudioChannelSet::rightCentre: return kSpeakerRc; | |||||
| case AudioChannelSet::centreSurround: return kSpeakerCs; | |||||
| case AudioChannelSet::leftSurroundRear: return kSpeakerSl; | |||||
| case AudioChannelSet::rightSurroundRear: return kSpeakerSr; | |||||
| case AudioChannelSet::topMiddle: return (1 << 11); /* kSpeakerTm */ | |||||
| case AudioChannelSet::topFrontLeft: return kSpeakerTfl; | case AudioChannelSet::topFrontLeft: return kSpeakerTfl; | ||||
| case AudioChannelSet::topFrontCentre: return kSpeakerTfc; | case AudioChannelSet::topFrontCentre: return kSpeakerTfc; | ||||
| case AudioChannelSet::topFrontRight: return kSpeakerTfr; | case AudioChannelSet::topFrontRight: return kSpeakerTfr; | ||||
| case AudioChannelSet::topRearLeft: return kSpeakerTrl; | case AudioChannelSet::topRearLeft: return kSpeakerTrl; | ||||
| case AudioChannelSet::topRearCentre: return kSpeakerTrc; | case AudioChannelSet::topRearCentre: return kSpeakerTrc; | ||||
| case AudioChannelSet::topRearRight: return kSpeakerTrr; | case AudioChannelSet::topRearRight: return kSpeakerTrr; | ||||
| case AudioChannelSet::subbass2: return kSpeakerLfe2; | |||||
| case AudioChannelSet::LFE2: return kSpeakerLfe2; | |||||
| default: break; | default: break; | ||||
| } | } | ||||
| @@ -177,30 +177,49 @@ static inline AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::Speak | |||||
| case kSpeakerL: return AudioChannelSet::left; | case kSpeakerL: return AudioChannelSet::left; | ||||
| case kSpeakerR: return AudioChannelSet::right; | case kSpeakerR: return AudioChannelSet::right; | ||||
| case kSpeakerC: return AudioChannelSet::centre; | case kSpeakerC: return AudioChannelSet::centre; | ||||
| case kSpeakerLfe: return AudioChannelSet::subbass; | |||||
| case kSpeakerLs: return AudioChannelSet::surroundLeft; | |||||
| case kSpeakerRs: return AudioChannelSet::surroundRight; | |||||
| case kSpeakerLc: return AudioChannelSet::centreLeft; | |||||
| case kSpeakerRc: return AudioChannelSet::centreRight; | |||||
| case kSpeakerS: return AudioChannelSet::surround; | |||||
| case kSpeakerSl: return AudioChannelSet::sideLeft; | |||||
| case kSpeakerSr: return AudioChannelSet::sideRight; | |||||
| case kSpeakerTm: return AudioChannelSet::topMiddle; | |||||
| case kSpeakerLfe: return AudioChannelSet::LFE; | |||||
| case kSpeakerLs: return AudioChannelSet::leftSurround; | |||||
| case kSpeakerRs: return AudioChannelSet::rightSurround; | |||||
| case kSpeakerLc: return AudioChannelSet::leftCentre; | |||||
| case kSpeakerRc: return AudioChannelSet::rightCentre; | |||||
| case kSpeakerCs: return AudioChannelSet::centreSurround; | |||||
| case kSpeakerSl: return AudioChannelSet::leftSurroundRear; | |||||
| case kSpeakerSr: return AudioChannelSet::rightSurroundRear; | |||||
| case (1 << 11): return AudioChannelSet::topMiddle; /* kSpeakerTm */ | |||||
| case kSpeakerTfl: return AudioChannelSet::topFrontLeft; | case kSpeakerTfl: return AudioChannelSet::topFrontLeft; | ||||
| case kSpeakerTfc: return AudioChannelSet::topFrontCentre; | case kSpeakerTfc: return AudioChannelSet::topFrontCentre; | ||||
| case kSpeakerTfr: return AudioChannelSet::topFrontRight; | case kSpeakerTfr: return AudioChannelSet::topFrontRight; | ||||
| case kSpeakerTrl: return AudioChannelSet::topRearLeft; | case kSpeakerTrl: return AudioChannelSet::topRearLeft; | ||||
| case kSpeakerTrc: return AudioChannelSet::topRearCentre; | case kSpeakerTrc: return AudioChannelSet::topRearCentre; | ||||
| case kSpeakerTrr: return AudioChannelSet::topRearRight; | case kSpeakerTrr: return AudioChannelSet::topRearRight; | ||||
| case kSpeakerLfe2: return AudioChannelSet::subbass2; | |||||
| case kSpeakerLfe2: return AudioChannelSet::LFE2; | |||||
| default: break; | default: break; | ||||
| } | } | ||||
| return AudioChannelSet::unknown; | return AudioChannelSet::unknown; | ||||
| } | } | ||||
| static inline Steinberg::Vst::SpeakerArrangement getSpeakerArrangement (const AudioChannelSet& channels) noexcept | |||||
| static inline Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept | |||||
| { | { | ||||
| if (channels == AudioChannelSet::disabled()) return Steinberg::Vst::SpeakerArr::kEmpty; | |||||
| else if (channels == AudioChannelSet::mono()) return Steinberg::Vst::SpeakerArr::kMono; | |||||
| else if (channels == AudioChannelSet::stereo()) return Steinberg::Vst::SpeakerArr::kStereo; | |||||
| else if (channels == AudioChannelSet::createLCR()) return Steinberg::Vst::SpeakerArr::k30Cine; | |||||
| else if (channels == AudioChannelSet::createLRS()) return Steinberg::Vst::SpeakerArr::k30Music; | |||||
| else if (channels == AudioChannelSet::createLCRS()) return Steinberg::Vst::SpeakerArr::k40Cine; | |||||
| else if (channels == AudioChannelSet::create5point0()) return Steinberg::Vst::SpeakerArr::k50; | |||||
| else if (channels == AudioChannelSet::create5point1()) return Steinberg::Vst::SpeakerArr::k51; | |||||
| else if (channels == AudioChannelSet::create6point0()) return Steinberg::Vst::SpeakerArr::k60Cine; | |||||
| else if (channels == AudioChannelSet::create6point1()) return Steinberg::Vst::SpeakerArr::k61Cine; | |||||
| else if (channels == AudioChannelSet::create6point0Music()) return Steinberg::Vst::SpeakerArr::k60Music; | |||||
| else if (channels == AudioChannelSet::create6point1Music()) return Steinberg::Vst::SpeakerArr::k61Music; | |||||
| else if (channels == AudioChannelSet::create7point0()) return Steinberg::Vst::SpeakerArr::k70Music; | |||||
| else if (channels == AudioChannelSet::create7point0SDDS()) return Steinberg::Vst::SpeakerArr::k70Cine; | |||||
| else if (channels == AudioChannelSet::create7point1()) return Steinberg::Vst::SpeakerArr::k71CineSideFill; | |||||
| else if (channels == AudioChannelSet::create7point1SDDS()) return Steinberg::Vst::SpeakerArr::k71Cine; | |||||
| else if (channels == AudioChannelSet::ambisonic()) return Steinberg::Vst::SpeakerArr::kBFormat; | |||||
| else if (channels == AudioChannelSet::quadraphonic()) return Steinberg::Vst::SpeakerArr::k40Music; | |||||
| Steinberg::Vst::SpeakerArrangement result = 0; | Steinberg::Vst::SpeakerArrangement result = 0; | ||||
| Array<AudioChannelSet::ChannelType> types (channels.getChannelTypes()); | Array<AudioChannelSet::ChannelType> types (channels.getChannelTypes()); | ||||
| @@ -213,6 +232,25 @@ static inline Steinberg::Vst::SpeakerArrangement getSpeakerArrangement (const Au | |||||
| static inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept | static inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept | ||||
| { | { | ||||
| if (arr == Steinberg::Vst::SpeakerArr::kEmpty) return AudioChannelSet::disabled(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::kMono) return AudioChannelSet::mono(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::kStereo) return AudioChannelSet::stereo(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k30Cine) return AudioChannelSet::createLCR(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k30Music) return AudioChannelSet::createLRS(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k40Cine) return AudioChannelSet::createLCRS(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k50) return AudioChannelSet::create5point0(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k51) return AudioChannelSet::create5point1(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k60Cine) return AudioChannelSet::create6point0(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k61Cine) return AudioChannelSet::create6point1(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k60Music) return AudioChannelSet::create6point0Music(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k61Music) return AudioChannelSet::create6point1Music(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k70Music) return AudioChannelSet::create7point0(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k70Cine) return AudioChannelSet::create7point0SDDS(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k71CineSideFill) return AudioChannelSet::create7point1(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k71Cine) return AudioChannelSet::create7point1SDDS(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::kBFormat) return AudioChannelSet::ambisonic(); | |||||
| else if (arr == Steinberg::Vst::SpeakerArr::k40Music) return AudioChannelSet::quadraphonic(); | |||||
| AudioChannelSet result; | AudioChannelSet result; | ||||
| for (Steinberg::Vst::Speaker speaker = 1; speaker <= Steinberg::Vst::kSpeakerRcs; speaker <<= 1) | for (Steinberg::Vst::Speaker speaker = 1; speaker <= Steinberg::Vst::kSpeakerRcs; speaker <<= 1) | ||||
| @@ -462,12 +500,12 @@ struct VST3BufferExchange | |||||
| vstBuffers.silenceFlags = 0; | vstBuffers.silenceFlags = 0; | ||||
| } | } | ||||
| static void mapArrangementToBusses (int& channelIndexOffset, int index, | |||||
| static void mapArrangementToBuses (int& channelIndexOffset, int index, | |||||
| Array<Steinberg::Vst::AudioBusBuffers>& result, | Array<Steinberg::Vst::AudioBusBuffers>& result, | ||||
| BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, | |||||
| BusMap& busMapToUse, const AudioChannelSet& arrangement, | |||||
| AudioBuffer<FloatType>& source) | AudioBuffer<FloatType>& source) | ||||
| { | { | ||||
| const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); | |||||
| const int numChansForBus = arrangement.size(); | |||||
| if (index >= result.size()) | if (index >= result.size()) | ||||
| result.add (Steinberg::Vst::AudioBusBuffers()); | result.add (Steinberg::Vst::AudioBusBuffers()); | ||||
| @@ -483,26 +521,26 @@ struct VST3BufferExchange | |||||
| channelIndexOffset += numChansForBus; | channelIndexOffset += numChansForBus; | ||||
| } | } | ||||
| static inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse, | |||||
| const Array<Steinberg::Vst::SpeakerArrangement>& arrangements, | |||||
| static inline void mapBufferToBuses (Array<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse, | |||||
| const Array<AudioChannelSet>& arrangements, | |||||
| AudioBuffer<FloatType>& source) | AudioBuffer<FloatType>& source) | ||||
| { | { | ||||
| int channelIndexOffset = 0; | int channelIndexOffset = 0; | ||||
| for (int i = 0; i < arrangements.size(); ++i) | for (int i = 0; i < arrangements.size(); ++i) | ||||
| mapArrangementToBusses (channelIndexOffset, i, result, busMapToUse, | |||||
| mapArrangementToBuses (channelIndexOffset, i, result, busMapToUse, | |||||
| arrangements.getUnchecked (i), source); | arrangements.getUnchecked (i), source); | ||||
| } | } | ||||
| static inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, | |||||
| static inline void mapBufferToBuses (Array<Steinberg::Vst::AudioBusBuffers>& result, | |||||
| Steinberg::Vst::IAudioProcessor& processor, | Steinberg::Vst::IAudioProcessor& processor, | ||||
| BusMap& busMapToUse, bool isInput, int numBusses, | |||||
| BusMap& busMapToUse, bool isInput, int numBuses, | |||||
| AudioBuffer<FloatType>& source) | AudioBuffer<FloatType>& source) | ||||
| { | { | ||||
| int channelIndexOffset = 0; | int channelIndexOffset = 0; | ||||
| for (int i = 0; i < numBusses; ++i) | |||||
| mapArrangementToBusses (channelIndexOffset, i, | |||||
| for (int i = 0; i < numBuses; ++i) | |||||
| mapArrangementToBuses (channelIndexOffset, i, | |||||
| result, busMapToUse, | result, busMapToUse, | ||||
| getArrangementForBus (&processor, isInput, i), | getArrangementForBus (&processor, isInput, i), | ||||
| source); | source); | ||||
| @@ -47,12 +47,15 @@ | |||||
| #pragma clang diagnostic ignored "-Wdelete-non-virtual-dtor" | #pragma clang diagnostic ignored "-Wdelete-non-virtual-dtor" | ||||
| #endif | #endif | ||||
| #undef DEVELOPMENT | |||||
| #define DEVELOPMENT 0 // This avoids a Clang warning in Steinberg code about unused values | |||||
| /* These files come with the Steinberg VST3 SDK - to get them, you'll need to | /* These files come with the Steinberg VST3 SDK - to get them, you'll need to | ||||
| visit the Steinberg website and agree to whatever is currently required to | visit the Steinberg website and agree to whatever is currently required to | ||||
| get them. | get them. | ||||
| Then, you'll need to make sure your include path contains your "VST3 SDK" | Then, you'll need to make sure your include path contains your "VST3 SDK" | ||||
| directory (or whatever you've named it on your machine). The Introjucer has | |||||
| directory (or whatever you've named it on your machine). The Projucer has | |||||
| a special box for setting this path. | a special box for setting this path. | ||||
| */ | */ | ||||
| #if JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY | #if JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY | ||||
| @@ -78,6 +81,7 @@ | |||||
| #include <pluginterfaces/vst/ivstunits.h> | #include <pluginterfaces/vst/ivstunits.h> | ||||
| #include <pluginterfaces/vst/ivstmidicontrollers.h> | #include <pluginterfaces/vst/ivstmidicontrollers.h> | ||||
| #include <public.sdk/source/common/memorystream.h> | #include <public.sdk/source/common/memorystream.h> | ||||
| #include <public.sdk/source/vst/vsteditcontroller.h> | |||||
| #else | #else | ||||
| #if JUCE_MINGW | #if JUCE_MINGW | ||||
| #define _set_abort_behavior(...) | #define _set_abort_behavior(...) | ||||
| @@ -128,6 +132,10 @@ namespace Steinberg | |||||
| #pragma clang diagnostic pop | #pragma clang diagnostic pop | ||||
| #endif | #endif | ||||
| #if JUCE_WINDOWS | |||||
| #include <windows.h> | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| #undef ASSERT | #undef ASSERT | ||||
| #undef WARNING | #undef WARNING | ||||
| @@ -25,11 +25,12 @@ | |||||
| #ifndef JUCE_VST3PLUGINFORMAT_H_INCLUDED | #ifndef JUCE_VST3PLUGINFORMAT_H_INCLUDED | ||||
| #define JUCE_VST3PLUGINFORMAT_H_INCLUDED | #define JUCE_VST3PLUGINFORMAT_H_INCLUDED | ||||
| #if JUCE_PLUGINHOST_VST3 | |||||
| #if (JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS)) || DOXYGEN | |||||
| /** | /** | ||||
| Implements a plugin format for VST3s. | Implements a plugin format for VST3s. | ||||
| */ | */ | ||||
| class JUCE_API VST3PluginFormat : public AudioPluginFormat | |||||
| class JUCE_API VST3PluginFormat : public AudioPluginFormat | |||||
| { | { | ||||
| public: | public: | ||||
| /** Constructor */ | /** Constructor */ | ||||
| @@ -39,32 +40,27 @@ public: | |||||
| ~VST3PluginFormat(); | ~VST3PluginFormat(); | ||||
| //============================================================================== | //============================================================================== | ||||
| /** @internal */ | |||||
| String getName() const override { return "VST3"; } | |||||
| /** @internal */ | |||||
| void findAllTypesForFile (OwnedArray<PluginDescription>& results, const String& fileOrIdentifier) override; | |||||
| /** @internal */ | |||||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription& description, double, int) override; | |||||
| /** @internal */ | |||||
| String getName() const override { return "VST3"; } | |||||
| void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | |||||
| bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | ||||
| /** @internal */ | |||||
| String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | ||||
| /** @internal */ | |||||
| bool pluginNeedsRescanning (const PluginDescription& description) override; | |||||
| /** @internal */ | |||||
| StringArray searchPathsForPlugins (const FileSearchPath& searchPath, bool recursive) override; | |||||
| /** @internal */ | |||||
| bool doesPluginStillExist (const PluginDescription& description) override; | |||||
| /** @internal */ | |||||
| bool pluginNeedsRescanning (const PluginDescription&) override; | |||||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||||
| bool doesPluginStillExist (const PluginDescription&) override; | |||||
| FileSearchPath getDefaultLocationsToSearch() override; | FileSearchPath getDefaultLocationsToSearch() override; | ||||
| /** @internal */ | |||||
| bool canScanForPlugins() const override { return true; } | |||||
| bool canScanForPlugins() const override { return true; } | |||||
| private: | |||||
| void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||||
| int initialBufferSize, void* userData, | |||||
| void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||||
| bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||||
| private: | private: | ||||
| //============================================================================== | //============================================================================== | ||||
| void recursiveFileSearch (StringArray&, const File&, bool recursive); | void recursiveFileSearch (StringArray&, const File&, bool recursive); | ||||
| //============================================================================== | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginFormat) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginFormat) | ||||
| }; | }; | ||||
| @@ -0,0 +1,238 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2015 - ROLI Ltd. | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ------------------------------------------------------------------------------ | |||||
| To release a closed-source product which uses JUCE, commercial licenses are | |||||
| available: visit www.juce.com for more information. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef JUCE_VSTCOMMON_H_INCLUDED | |||||
| #define JUCE_VSTCOMMON_H_INCLUDED | |||||
| //============================================================================== | |||||
| struct SpeakerMappings : private AudioChannelSet // (inheritance only to give easier access to items in the namespace) | |||||
| { | |||||
| struct Mapping | |||||
| { | |||||
| int32 vst2; | |||||
| ChannelType channels[13]; | |||||
| bool matches (const Array<ChannelType>& chans) const noexcept | |||||
| { | |||||
| const int n = sizeof (channels) / sizeof (ChannelType); | |||||
| for (int i = 0; i < n; ++i) | |||||
| { | |||||
| if (channels[i] == unknown) return (i == chans.size()); | |||||
| if (i == chans.size()) return (channels[i] == unknown); | |||||
| if (channels[i] != chans.getUnchecked(i)) | |||||
| return false; | |||||
| } | |||||
| return true; | |||||
| } | |||||
| }; | |||||
| static AudioChannelSet vstArrangementTypeToChannelSet (int32 arr, int fallbackNumChannels) | |||||
| { | |||||
| if (arr == vstSpeakerConfigTypeEmpty) return AudioChannelSet::disabled(); | |||||
| else if (arr == vstSpeakerConfigTypeMono) return AudioChannelSet::mono(); | |||||
| else if (arr == vstSpeakerConfigTypeLR) return AudioChannelSet::stereo(); | |||||
| else if (arr == vstSpeakerConfigTypeLRC) return AudioChannelSet::createLCR(); | |||||
| else if (arr == vstSpeakerConfigTypeLRS) return AudioChannelSet::createLRS(); | |||||
| else if (arr == vstSpeakerConfigTypeLRCS) return AudioChannelSet::createLCRS(); | |||||
| else if (arr == vstSpeakerConfigTypeLRCLsRs) return AudioChannelSet::create5point0(); | |||||
| else if (arr == vstSpeakerConfigTypeLRCLfeLsRs) return AudioChannelSet::create5point1(); | |||||
| else if (arr == vstSpeakerConfigTypeLRCLsRsCs) return AudioChannelSet::create6point0(); | |||||
| else if (arr == vstSpeakerConfigTypeLRCLfeLsRsCs) return AudioChannelSet::create6point1(); | |||||
| else if (arr == vstSpeakerConfigTypeLRLsRsSlSr) return AudioChannelSet::create6point0Music(); | |||||
| else if (arr == vstSpeakerConfigTypeLRLfeLsRsSlSr) return AudioChannelSet::create6point1Music(); | |||||
| else if (arr == vstSpeakerConfigTypeLRCLsRsSlSr) return AudioChannelSet::create7point0(); | |||||
| else if (arr == vstSpeakerConfigTypeLRCLsRsLcRc) return AudioChannelSet::create7point0SDDS(); | |||||
| else if (arr == vstSpeakerConfigTypeLRCLfeLsRsSlSr) return AudioChannelSet::create7point1(); | |||||
| else if (arr == vstSpeakerConfigTypeLRCLfeLsRsLcRc) return AudioChannelSet::create7point1SDDS(); | |||||
| else if (arr == vstSpeakerConfigTypeLRLsRs) return AudioChannelSet::quadraphonic(); | |||||
| for (const Mapping* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m) | |||||
| { | |||||
| if (m->vst2 == arr) | |||||
| { | |||||
| AudioChannelSet s; | |||||
| for (int i = 0; m->channels[i] != 0; ++i) | |||||
| s.addChannel (m->channels[i]); | |||||
| return s; | |||||
| } | |||||
| } | |||||
| return AudioChannelSet::discreteChannels (fallbackNumChannels); | |||||
| } | |||||
| static AudioChannelSet vstArrangementTypeToChannelSet (const VstSpeakerConfiguration& arr) | |||||
| { | |||||
| return vstArrangementTypeToChannelSet (arr.type, arr.numberOfChannels); | |||||
| } | |||||
| static int32 channelSetToVstArrangementType (AudioChannelSet channels) | |||||
| { | |||||
| if (channels == AudioChannelSet::disabled()) return vstSpeakerConfigTypeEmpty; | |||||
| else if (channels == AudioChannelSet::mono()) return vstSpeakerConfigTypeMono; | |||||
| else if (channels == AudioChannelSet::stereo()) return vstSpeakerConfigTypeLR; | |||||
| else if (channels == AudioChannelSet::createLCR()) return vstSpeakerConfigTypeLRC; | |||||
| else if (channels == AudioChannelSet::createLRS()) return vstSpeakerConfigTypeLRS; | |||||
| else if (channels == AudioChannelSet::createLCRS()) return vstSpeakerConfigTypeLRCS; | |||||
| else if (channels == AudioChannelSet::create5point0()) return vstSpeakerConfigTypeLRCLsRs; | |||||
| else if (channels == AudioChannelSet::create5point1()) return vstSpeakerConfigTypeLRCLfeLsRs; | |||||
| else if (channels == AudioChannelSet::create6point0()) return vstSpeakerConfigTypeLRCLsRsCs; | |||||
| else if (channels == AudioChannelSet::create6point1()) return vstSpeakerConfigTypeLRCLfeLsRsCs; | |||||
| else if (channels == AudioChannelSet::create6point0Music()) return vstSpeakerConfigTypeLRLsRsSlSr; | |||||
| else if (channels == AudioChannelSet::create6point1Music()) return vstSpeakerConfigTypeLRLfeLsRsSlSr; | |||||
| else if (channels == AudioChannelSet::create7point0()) return vstSpeakerConfigTypeLRCLsRsSlSr; | |||||
| else if (channels == AudioChannelSet::create7point0SDDS()) return vstSpeakerConfigTypeLRCLsRsLcRc; | |||||
| else if (channels == AudioChannelSet::create7point1()) return vstSpeakerConfigTypeLRCLfeLsRsSlSr; | |||||
| else if (channels == AudioChannelSet::create7point1SDDS()) return vstSpeakerConfigTypeLRCLfeLsRsLcRc; | |||||
| else if (channels == AudioChannelSet::quadraphonic()) return vstSpeakerConfigTypeLRLsRs; | |||||
| Array<AudioChannelSet::ChannelType> chans (channels.getChannelTypes()); | |||||
| if (channels == AudioChannelSet::disabled()) | |||||
| return vstSpeakerConfigTypeEmpty; | |||||
| for (const Mapping* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m) | |||||
| if (m->matches (chans)) | |||||
| return m->vst2; | |||||
| return vstSpeakerConfigTypeUser; | |||||
| } | |||||
| static void channelSetToVstArrangement (const AudioChannelSet& channels, VstSpeakerConfiguration& result) | |||||
| { | |||||
| result.type = channelSetToVstArrangementType (channels); | |||||
| result.numberOfChannels = channels.size(); | |||||
| for (int i = 0; i < result.numberOfChannels; ++i) | |||||
| { | |||||
| VstIndividualSpeakerInfo& speaker = result.speakers[i]; | |||||
| zeromem (&speaker, sizeof (VstIndividualSpeakerInfo)); | |||||
| speaker.type = getSpeakerType (channels.getTypeOfChannel (i)); | |||||
| } | |||||
| } | |||||
| static const Mapping* getMappings() noexcept | |||||
| { | |||||
| static const Mapping mappings[] = | |||||
| { | |||||
| { vstSpeakerConfigTypeMono, { centre, unknown } }, | |||||
| { vstSpeakerConfigTypeLR, { left, right, unknown } }, | |||||
| { vstSpeakerConfigTypeLsRs, { leftSurround, rightSurround, unknown } }, | |||||
| { vstSpeakerConfigTypeLcRc, { leftCentre, rightCentre, unknown } }, | |||||
| { vstSpeakerConfigTypeSlSr, { leftSurroundRear, rightSurroundRear, unknown } }, | |||||
| { vstSpeakerConfigTypeCLfe, { centre, LFE, unknown } }, | |||||
| { vstSpeakerConfigTypeLRC, { left, right, centre, unknown } }, | |||||
| { vstSpeakerConfigTypeLRS, { left, right, surround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLfe, { left, right, centre, LFE, unknown } }, | |||||
| { vstSpeakerConfigTypeLRLfeS, { left, right, LFE, surround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCS, { left, right, centre, surround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRLsRs, { left, right, leftSurround, rightSurround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLfeS, { left, right, centre, LFE, surround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRLfeLsRs, { left, right, LFE, leftSurround, rightSurround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLsRs, { left, right, centre, leftSurround, rightSurround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLfeLsRs, { left, right, centre, LFE, leftSurround, rightSurround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLsRsCs, { left, right, centre, leftSurround, rightSurround, surround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRLsRsSlSr, { left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLfeLsRsCs, { left, right, centre, LFE, leftSurround, rightSurround, surround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRLfeLsRsSlSr, { left, right, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLsRsLcRc, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLsRsSlSr, { left, right, centre, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLfeLsRsLcRc, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLfeLsRsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLsRsLcRcCs, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLsRsCsSlSr, { left, right, centre, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } }, | |||||
| { vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontCentre, topFrontRight, topRearLeft, topRearRight, LFE2, unknown } }, | |||||
| { vstSpeakerConfigTypeEmpty, { unknown } } | |||||
| }; | |||||
| return mappings; | |||||
| } | |||||
| static inline int32 getSpeakerType (AudioChannelSet::ChannelType type) noexcept | |||||
| { | |||||
| switch (type) | |||||
| { | |||||
| case AudioChannelSet::left: return vstIndividualSpeakerTypeLeft; | |||||
| case AudioChannelSet::right: return vstIndividualSpeakerTypeRight; | |||||
| case AudioChannelSet::centre: return vstIndividualSpeakerTypeCentre; | |||||
| case AudioChannelSet::LFE: return vstIndividualSpeakerTypeLFE; | |||||
| case AudioChannelSet::leftSurround: return vstIndividualSpeakerTypeLeftSurround; | |||||
| case AudioChannelSet::rightSurround: return vstIndividualSpeakerTypeRightSurround; | |||||
| case AudioChannelSet::leftCentre: return vstIndividualSpeakerTypeLeftCentre; | |||||
| case AudioChannelSet::rightCentre: return vstIndividualSpeakerTypeRightCentre; | |||||
| case AudioChannelSet::surround: return vstIndividualSpeakerTypeSurround; | |||||
| case AudioChannelSet::leftSurroundRear: return vstIndividualSpeakerTypeLeftRearSurround; | |||||
| case AudioChannelSet::rightSurroundRear: return vstIndividualSpeakerTypeRightRearSurround; | |||||
| case AudioChannelSet::topMiddle: return vstIndividualSpeakerTypeTopMiddle; | |||||
| case AudioChannelSet::topFrontLeft: return vstIndividualSpeakerTypeTopFrontLeft; | |||||
| case AudioChannelSet::topFrontCentre: return vstIndividualSpeakerTypeTopFrontCentre; | |||||
| case AudioChannelSet::topFrontRight: return vstIndividualSpeakerTypeTopFrontRight; | |||||
| case AudioChannelSet::topRearLeft: return vstIndividualSpeakerTypeTopRearLeft; | |||||
| case AudioChannelSet::topRearCentre: return vstIndividualSpeakerTypeTopRearCentre; | |||||
| case AudioChannelSet::topRearRight: return vstIndividualSpeakerTypeTopRearRight; | |||||
| case AudioChannelSet::LFE2: return vstIndividualSpeakerTypeLFE2; | |||||
| default: break; | |||||
| } | |||||
| return 0; | |||||
| } | |||||
| static inline AudioChannelSet::ChannelType getChannelType (int32 type) noexcept | |||||
| { | |||||
| switch (type) | |||||
| { | |||||
| case vstIndividualSpeakerTypeLeft: return AudioChannelSet::left; | |||||
| case vstIndividualSpeakerTypeRight: return AudioChannelSet::right; | |||||
| case vstIndividualSpeakerTypeCentre: return AudioChannelSet::centre; | |||||
| case vstIndividualSpeakerTypeLFE: return AudioChannelSet::LFE; | |||||
| case vstIndividualSpeakerTypeLeftSurround: return AudioChannelSet::leftSurround; | |||||
| case vstIndividualSpeakerTypeRightSurround: return AudioChannelSet::rightSurround; | |||||
| case vstIndividualSpeakerTypeLeftCentre: return AudioChannelSet::leftCentre; | |||||
| case vstIndividualSpeakerTypeRightCentre: return AudioChannelSet::rightCentre; | |||||
| case vstIndividualSpeakerTypeSurround: return AudioChannelSet::surround; | |||||
| case vstIndividualSpeakerTypeLeftRearSurround: return AudioChannelSet::leftSurroundRear; | |||||
| case vstIndividualSpeakerTypeRightRearSurround: return AudioChannelSet::rightSurroundRear; | |||||
| case vstIndividualSpeakerTypeTopMiddle: return AudioChannelSet::topMiddle; | |||||
| case vstIndividualSpeakerTypeTopFrontLeft: return AudioChannelSet::topFrontLeft; | |||||
| case vstIndividualSpeakerTypeTopFrontCentre: return AudioChannelSet::topFrontCentre; | |||||
| case vstIndividualSpeakerTypeTopFrontRight: return AudioChannelSet::topFrontRight; | |||||
| case vstIndividualSpeakerTypeTopRearLeft: return AudioChannelSet::topRearLeft; | |||||
| case vstIndividualSpeakerTypeTopRearCentre: return AudioChannelSet::topRearCentre; | |||||
| case vstIndividualSpeakerTypeTopRearRight: return AudioChannelSet::topRearRight; | |||||
| case vstIndividualSpeakerTypeLFE2: return AudioChannelSet::LFE2; | |||||
| default: break; | |||||
| } | |||||
| return AudioChannelSet::unknown; | |||||
| } | |||||
| }; | |||||
| #endif // JUCE_VSTCOMMON_H_INCLUDED | |||||
| @@ -0,0 +1,458 @@ | |||||
| /* | |||||
| ============================================================================== | |||||
| This file is part of the JUCE library. | |||||
| Copyright (c) 2016 - ROLI Ltd. | |||||
| Permission is granted to use this software under the terms of either: | |||||
| a) the GPL v2 (or any later version) | |||||
| b) the Affero GPL v3 | |||||
| Details of these licenses can be found at: www.gnu.org/licenses | |||||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
| ----------------------------------------------------------------------------- | |||||
| To release a closed-source product which uses JUCE, commercial licenses are | |||||
| available: visit www.juce.com for more information. | |||||
| ============================================================================== | |||||
| */ | |||||
| #ifndef JUCE_VSTINTERFACE_H_INCLUDED | |||||
| #define JUCE_VSTINTERFACE_H_INCLUDED | |||||
| #include "../../juce_core/juce_core.h" | |||||
| using namespace juce; | |||||
| #if JUCE_MSVC | |||||
| #define VSTINTERFACECALL __cdecl | |||||
| #pragma pack(push) | |||||
| #pragma pack(8) | |||||
| #elif JUCE_MAC || JUCE_IOS | |||||
| #define VSTINTERFACECALL | |||||
| #if JUCE_64BIT | |||||
| #pragma options align=power | |||||
| #else | |||||
| #pragma options align=mac68k | |||||
| #endif | |||||
| #else | |||||
| #define VSTINTERFACECALL | |||||
| #pragma pack(push, 8) | |||||
| #endif | |||||
| const int32 juceVstInterfaceVersion = 2400; | |||||
| const int32 juceVstInterfaceIdentifier = 0x56737450; // The "magic" identifier in the SDK is 'VstP'. | |||||
| //============================================================================== | |||||
| struct VstEffectInterface | |||||
| { | |||||
| int32 interfaceIdentifier; | |||||
| pointer_sized_int (VSTINTERFACECALL* dispatchFunction) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); | |||||
| void (VSTINTERFACECALL* processAudioFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); | |||||
| void (VSTINTERFACECALL* setParameterValueFunction) (VstEffectInterface*, int32 parameterIndex, float value); | |||||
| float (VSTINTERFACECALL* getParameterValueFunction) (VstEffectInterface*, int32 parameterIndex); | |||||
| int32 numPrograms; | |||||
| int32 numParameters; | |||||
| int32 numInputChannels; | |||||
| int32 numOutputChannels; | |||||
| int32 flags; | |||||
| pointer_sized_int hostSpace1; | |||||
| pointer_sized_int hostSpace2; | |||||
| int32 latency; | |||||
| int32 deprecated1; | |||||
| int32 deprecated2; | |||||
| float deprecated3; | |||||
| void* effectPointer; | |||||
| void* userPointer; | |||||
| int32 plugInIdentifier; | |||||
| int32 plugInVersion; | |||||
| void (VSTINTERFACECALL* processAudioInplaceFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); | |||||
| void (VSTINTERFACECALL* processDoubleAudioInplaceFunction) (VstEffectInterface*, double** inputs, double** outputs, int32 numSamples); | |||||
| char emptySpace[56]; | |||||
| }; | |||||
| typedef pointer_sized_int (VSTINTERFACECALL* VstHostCallback) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); | |||||
| enum VstEffectInterfaceFlags | |||||
| { | |||||
| vstEffectFlagHasEditor = 1, | |||||
| vstEffectFlagInplaceAudio = 16, | |||||
| vstEffectFlagDataInChunks = 32, | |||||
| vstEffectFlagIsSynth = 256, | |||||
| vstEffectFlagInplaceDoubleAudio = 4096 | |||||
| }; | |||||
| //============================================================================== | |||||
| enum VstHostToPlugInOpcodes | |||||
| { | |||||
| plugInOpcodeOpen, | |||||
| plugInOpcodeClose, | |||||
| plugInOpcodeSetCurrentProgram, | |||||
| plugInOpcodeGetCurrentProgram, | |||||
| plugInOpcodeSetCurrentProgramName, | |||||
| plugInOpcodeGetCurrentProgramName, | |||||
| plugInOpcodeGetParameterLabel, | |||||
| plugInOpcodeGetParameterText, | |||||
| plugInOpcodeGetParameterName, | |||||
| plugInOpcodeSetSampleRate = plugInOpcodeGetParameterName + 2, | |||||
| plugInOpcodeSetBlockSize, | |||||
| plugInOpcodeResumeSuspend, | |||||
| plugInOpcodeGetEditorBounds, | |||||
| plugInOpcodeOpenEditor, | |||||
| plugInOpcodeCloseEditor, | |||||
| plugInOpcodeDrawEditor, | |||||
| plugInOpcodeGetMouse, | |||||
| plugInOpcodeEditorIdle = plugInOpcodeGetMouse + 2, | |||||
| plugInOpcodeeffEditorTop, | |||||
| plugInOpcodeSleepEditor, | |||||
| plugInOpcodeIdentify, | |||||
| plugInOpcodeGetData, | |||||
| plugInOpcodeSetData, | |||||
| plugInOpcodePreAudioProcessingEvents, | |||||
| plugInOpcodeIsParameterAutomatable, | |||||
| plugInOpcodeParameterValueForText, | |||||
| plugInOpcodeGetProgramName = plugInOpcodeParameterValueForText + 2, | |||||
| plugInOpcodeConnectInput = plugInOpcodeGetProgramName + 2, | |||||
| plugInOpcodeConnectOutput, | |||||
| plugInOpcodeGetInputPinProperties, | |||||
| plugInOpcodeGetOutputPinProperties, | |||||
| plugInOpcodeGetPlugInCategory, | |||||
| plugInOpcodeSetSpeakerConfiguration = plugInOpcodeGetPlugInCategory + 7, | |||||
| plugInOpcodeSetBypass = plugInOpcodeSetSpeakerConfiguration + 2, | |||||
| plugInOpcodeGetPlugInName, | |||||
| plugInOpcodeGetManufacturerName = plugInOpcodeGetPlugInName + 2, | |||||
| plugInOpcodeGetManufacturerProductName, | |||||
| plugInOpcodeGetManufacturerVersion, | |||||
| plugInOpcodeManufacturerSpecific, | |||||
| plugInOpcodeCanPlugInDo, | |||||
| plugInOpcodeGetTailSize, | |||||
| plugInOpcodeIdle, | |||||
| plugInOpcodeKeyboardFocusRequired = plugInOpcodeIdle + 4, | |||||
| plugInOpcodeGetVstInterfaceVersion, | |||||
| plugInOpcodeGetCurrentMidiProgram = plugInOpcodeGetVstInterfaceVersion + 5, | |||||
| plugInOpcodeGetSpeakerArrangement = plugInOpcodeGetCurrentMidiProgram + 6, | |||||
| plugInOpcodeNextPlugInUniqueID, | |||||
| plugInOpcodeStartProcess, | |||||
| plugInOpcodeStopProcess, | |||||
| plugInOpcodeSetNumberOfSamplesToProcess, | |||||
| plugInOpcodeSetSampleFloatType = plugInOpcodeSetNumberOfSamplesToProcess + 4, | |||||
| plugInOpcodeMaximum = plugInOpcodeSetSampleFloatType | |||||
| }; | |||||
| enum VstPlugInToHostOpcodes | |||||
| { | |||||
| hostOpcodeParameterChanged, | |||||
| hostOpcodeVstVersion, | |||||
| hostOpcodeCurrentId, | |||||
| hostOpcodeIdle, | |||||
| hostOpcodePinConnected, | |||||
| hostOpcodePlugInWantsMidi = hostOpcodePinConnected + 2, | |||||
| hostOpcodeGetTimingInfo, | |||||
| hostOpcodePreAudioProcessingEvents, | |||||
| hostOpcodeSetTime, | |||||
| hostOpcodeTempoAt, | |||||
| hostOpcodeGetNumberOfAutomatableParameters, | |||||
| hostOpcodeGetParameterInterval, | |||||
| hostOpcodeIOModified, | |||||
| hostOpcodeNeedsIdle, | |||||
| hostOpcodeWindowSize, | |||||
| hostOpcodeGetSampleRate, | |||||
| hostOpcodeGetBlockSize, | |||||
| hostOpcodeGetInputLatency, | |||||
| hostOpcodeGetOutputLatency, | |||||
| hostOpcodeGetPreviousPlugIn, | |||||
| hostOpcodeGetNextPlugIn, | |||||
| hostOpcodeWillReplace, | |||||
| hostOpcodeGetCurrentAudioProcessingLevel, | |||||
| hostOpcodeGetAutomationState, | |||||
| hostOpcodeOfflineStart, | |||||
| hostOpcodeOfflineReadSource, | |||||
| hostOpcodeOfflineWrite, | |||||
| hostOpcodeOfflineGetCurrentPass, | |||||
| hostOpcodeOfflineGetCurrentMetaPass, | |||||
| hostOpcodeSetOutputSampleRate, | |||||
| hostOpcodeGetOutputSpeakerConfiguration, | |||||
| hostOpcodeGetManufacturerName, | |||||
| hostOpcodeGetProductName, | |||||
| hostOpcodeGetManufacturerVersion, | |||||
| hostOpcodeManufacturerSpecific, | |||||
| hostOpcodeSetIcon, | |||||
| hostOpcodeCanHostDo, | |||||
| hostOpcodeGetLanguage, | |||||
| hostOpcodeOpenEditorWindow, | |||||
| hostOpcodeCloseEditorWindow, | |||||
| hostOpcodeGetDirectory, | |||||
| hostOpcodeUpdateView, | |||||
| hostOpcodeParameterChangeGestureBegin, | |||||
| hostOpcodeParameterChangeGestureEnd, | |||||
| }; | |||||
| //============================================================================== | |||||
| enum VstProcessingSampleType | |||||
| { | |||||
| vstProcessingSampleTypeFloat, | |||||
| vstProcessingSampleTypeDouble | |||||
| }; | |||||
| //============================================================================== | |||||
| // These names must be identical to the Steinberg SDK so JUCE users can set | |||||
| // exactly what they want. | |||||
| enum VstPlugInCategory | |||||
| { | |||||
| kPlugCategUnknown, | |||||
| kPlugCategEffect, | |||||
| kPlugCategSynth, | |||||
| kPlugCategAnalysis, | |||||
| kPlugCategMastering, | |||||
| kPlugCategSpacializer, | |||||
| kPlugCategRoomFx, | |||||
| kPlugSurroundFx, | |||||
| kPlugCategRestoration, | |||||
| kPlugCategOfflineProcess, | |||||
| kPlugCategShell, | |||||
| kPlugCategGenerator | |||||
| }; | |||||
| //============================================================================== | |||||
| struct VstEditorBounds | |||||
| { | |||||
| int16 upper; | |||||
| int16 leftmost; | |||||
| int16 lower; | |||||
| int16 rightmost; | |||||
| }; | |||||
| //============================================================================== | |||||
| enum VstMaxStringLengths | |||||
| { | |||||
| vstMaxNameLength = 64, | |||||
| vstMaxParameterOrPinLabelLength = 64, | |||||
| vstMaxParameterOrPinShortLabelLength = 8, | |||||
| vstMaxCategoryLength = 24, | |||||
| vstMaxManufacturerStringLength = 64, | |||||
| vstMaxPlugInNameStringLength = 64 | |||||
| }; | |||||
| //============================================================================== | |||||
| struct VstPinInfo | |||||
| { | |||||
| char text[vstMaxParameterOrPinLabelLength]; | |||||
| int32 flags; | |||||
| int32 configurationType; | |||||
| char shortText[vstMaxParameterOrPinShortLabelLength]; | |||||
| char unused[48]; | |||||
| }; | |||||
| enum VstPinInfoFlags | |||||
| { | |||||
| vstPinInfoFlagIsActive = 1, | |||||
| vstPinInfoFlagIsStereo = 2, | |||||
| vstPinInfoFlagValid = 4 | |||||
| }; | |||||
| //============================================================================== | |||||
| struct VstEvent | |||||
| { | |||||
| int32 type; | |||||
| int32 size; | |||||
| int32 sampleOffset; | |||||
| int32 flags; | |||||
| char content[16]; | |||||
| }; | |||||
| enum VstEventTypes | |||||
| { | |||||
| vstMidiEventType = 1, | |||||
| vstSysExEventType = 6 | |||||
| }; | |||||
| struct VstEventBlock | |||||
| { | |||||
| int32 numberOfEvents; | |||||
| pointer_sized_int future; | |||||
| VstEvent* events[2]; | |||||
| }; | |||||
| struct VstMidiEvent | |||||
| { | |||||
| int32 type; | |||||
| int32 size; | |||||
| int32 sampleOffset; | |||||
| int32 flags; | |||||
| int32 noteSampleLength; | |||||
| int32 noteSampleOffset; | |||||
| char midiData[4]; | |||||
| char tuning; | |||||
| char noteVelocityOff; | |||||
| char future1; | |||||
| char future2; | |||||
| }; | |||||
| enum VstMidiEventFlags | |||||
| { | |||||
| vstMidiEventIsRealtime = 1 | |||||
| }; | |||||
| struct VstSysExEvent | |||||
| { | |||||
| int32 type; | |||||
| int32 size; | |||||
| int32 offsetSamples; | |||||
| int32 flags; | |||||
| int32 sysExDumpSize; | |||||
| pointer_sized_int future1; | |||||
| char* sysExDump; | |||||
| pointer_sized_int future2; | |||||
| }; | |||||
| //============================================================================== | |||||
| struct VstTimingInformation | |||||
| { | |||||
| double samplePosition; | |||||
| double sampleRate; | |||||
| double systemTimeNanoseconds; | |||||
| double musicalPosition; | |||||
| double tempoBPM; | |||||
| double lastBarPosition; | |||||
| double loopStartPosition; | |||||
| double loopEndPosition; | |||||
| int32 timeSignatureNumerator; | |||||
| int32 timeSignatureDenominator; | |||||
| int32 smpteOffset; | |||||
| int32 smpteRate; | |||||
| int32 samplesToNearestClock; | |||||
| int32 flags; | |||||
| }; | |||||
| enum VstTimingInformationFlags | |||||
| { | |||||
| vstTimingInfoFlagTransportChanged = 1, | |||||
| vstTimingInfoFlagCurrentlyPlaying = 2, | |||||
| vstTimingInfoFlagLoopActive = 4, | |||||
| vstTimingInfoFlagCurrentlyRecording = 8, | |||||
| vstTimingInfoFlagAutomationWriteModeActive = 64, | |||||
| vstTimingInfoFlagAutomationReadModeActive = 128, | |||||
| vstTimingInfoFlagNanosecondsValid = 256, | |||||
| vstTimingInfoFlagMusicalPositionValid = 512, | |||||
| vstTimingInfoFlagTempoValid = 1024, | |||||
| vstTimingInfoFlagLastBarPositionValid = 2056, | |||||
| vstTimingInfoFlagLoopPositionValid = 4096, | |||||
| vstTimingInfoFlagTimeSignatureValid = 8192, | |||||
| vstTimingInfoFlagSmpteValid = 16384, | |||||
| vstTimingInfoFlagNearestClockValid = 32768 | |||||
| }; | |||||
| //============================================================================== | |||||
| enum VstSmpteRates | |||||
| { | |||||
| vstSmpteRateFps24, | |||||
| vstSmpteRateFps25, | |||||
| vstSmpteRateFps2997, | |||||
| vstSmpteRateFps30, | |||||
| vstSmpteRateFps2997drop, | |||||
| vstSmpteRateFps30drop, | |||||
| vstSmpteRate16mmFilm, | |||||
| vstSmpteRate35mmFilm, | |||||
| vstSmpteRateFps239 = vstSmpteRate35mmFilm + 3, | |||||
| vstSmpteRateFps249 , | |||||
| vstSmpteRateFps599, | |||||
| vstSmpteRateFps60 | |||||
| }; | |||||
| //============================================================================== | |||||
| struct VstIndividualSpeakerInfo | |||||
| { | |||||
| float azimuthalAngle; | |||||
| float elevationAngle; | |||||
| float radius; | |||||
| float reserved; | |||||
| char label[vstMaxNameLength]; | |||||
| int32 type; | |||||
| char unused[28]; | |||||
| }; | |||||
| enum VstIndividualSpeakerType | |||||
| { | |||||
| vstIndividualSpeakerTypeUndefined = 0x7fffffff, | |||||
| vstIndividualSpeakerTypeMono = 0, | |||||
| vstIndividualSpeakerTypeLeft, | |||||
| vstIndividualSpeakerTypeRight, | |||||
| vstIndividualSpeakerTypeCentre, | |||||
| vstIndividualSpeakerTypeLFE, | |||||
| vstIndividualSpeakerTypeLeftSurround, | |||||
| vstIndividualSpeakerTypeRightSurround, | |||||
| vstIndividualSpeakerTypeLeftCentre, | |||||
| vstIndividualSpeakerTypeRightCentre, | |||||
| vstIndividualSpeakerTypeSurround, | |||||
| vstIndividualSpeakerTypeCentreSurround = vstIndividualSpeakerTypeSurround, | |||||
| vstIndividualSpeakerTypeLeftRearSurround, | |||||
| vstIndividualSpeakerTypeRightRearSurround, | |||||
| vstIndividualSpeakerTypeTopMiddle, | |||||
| vstIndividualSpeakerTypeTopFrontLeft, | |||||
| vstIndividualSpeakerTypeTopFrontCentre, | |||||
| vstIndividualSpeakerTypeTopFrontRight, | |||||
| vstIndividualSpeakerTypeTopRearLeft, | |||||
| vstIndividualSpeakerTypeTopRearCentre, | |||||
| vstIndividualSpeakerTypeTopRearRight, | |||||
| vstIndividualSpeakerTypeLFE2 | |||||
| }; | |||||
| struct VstSpeakerConfiguration | |||||
| { | |||||
| int32 type; | |||||
| int32 numberOfChannels; | |||||
| VstIndividualSpeakerInfo speakers[8]; | |||||
| }; | |||||
| enum VstSpeakerConfigurationType | |||||
| { | |||||
| vstSpeakerConfigTypeUser = -2, | |||||
| vstSpeakerConfigTypeEmpty = -1, | |||||
| vstSpeakerConfigTypeMono = 0, | |||||
| vstSpeakerConfigTypeLR, | |||||
| vstSpeakerConfigTypeLsRs, | |||||
| vstSpeakerConfigTypeLcRc, | |||||
| vstSpeakerConfigTypeSlSr, | |||||
| vstSpeakerConfigTypeCLfe, | |||||
| vstSpeakerConfigTypeLRC, | |||||
| vstSpeakerConfigTypeLRS, | |||||
| vstSpeakerConfigTypeLRCLfe, | |||||
| vstSpeakerConfigTypeLRLfeS, | |||||
| vstSpeakerConfigTypeLRCS, | |||||
| vstSpeakerConfigTypeLRLsRs, | |||||
| vstSpeakerConfigTypeLRCLfeS, | |||||
| vstSpeakerConfigTypeLRLfeLsRs, | |||||
| vstSpeakerConfigTypeLRCLsRs, | |||||
| vstSpeakerConfigTypeLRCLfeLsRs, | |||||
| vstSpeakerConfigTypeLRCLsRsCs, | |||||
| vstSpeakerConfigTypeLRLsRsSlSr, | |||||
| vstSpeakerConfigTypeLRCLfeLsRsCs, | |||||
| vstSpeakerConfigTypeLRLfeLsRsSlSr, | |||||
| vstSpeakerConfigTypeLRCLsRsLcRc, | |||||
| vstSpeakerConfigTypeLRCLsRsSlSr, | |||||
| vstSpeakerConfigTypeLRCLfeLsRsLcRc, | |||||
| vstSpeakerConfigTypeLRCLfeLsRsSlSr, | |||||
| vstSpeakerConfigTypeLRCLsRsLcRcCs, | |||||
| vstSpeakerConfigTypeLRCLsRsCsSlSr, | |||||
| vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, | |||||
| vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, | |||||
| vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2 | |||||
| }; | |||||
| #if JUCE_MSVC | |||||
| #pragma pack(pop) | |||||
| #elif JUCE_MAC || JUCE_IOS | |||||
| #pragma options align=reset | |||||
| #else | |||||
| #pragma pack(pop) | |||||
| #endif | |||||
| #endif // JUCE_VSTINTERFACE_H_INCLUDED | |||||
| @@ -22,7 +22,8 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| #ifdef __aeffect__ // NB: this must come first, *before* the header-guard. | |||||
| // NB: this must come first, *before* the header-guard. | |||||
| #ifdef JUCE_VSTINTERFACE_H_INCLUDED | |||||
| #ifndef JUCE_VSTMIDIEVENTLIST_H_INCLUDED | #ifndef JUCE_VSTMIDIEVENTLIST_H_INCLUDED | ||||
| #define JUCE_VSTMIDIEVENTLIST_H_INCLUDED | #define JUCE_VSTMIDIEVENTLIST_H_INCLUDED | ||||
| @@ -53,7 +54,7 @@ public: | |||||
| numEventsUsed = 0; | numEventsUsed = 0; | ||||
| if (events != nullptr) | if (events != nullptr) | ||||
| events->numEvents = 0; | |||||
| events->numberOfEvents = 0; | |||||
| } | } | ||||
| void addEvent (const void* const midiData, const int numBytes, const int frameOffset) | void addEvent (const void* const midiData, const int numBytes, const int frameOffset) | ||||
| @@ -61,65 +62,65 @@ public: | |||||
| ensureSize (numEventsUsed + 1); | ensureSize (numEventsUsed + 1); | ||||
| VstMidiEvent* const e = (VstMidiEvent*) (events->events [numEventsUsed]); | VstMidiEvent* const e = (VstMidiEvent*) (events->events [numEventsUsed]); | ||||
| events->numEvents = ++numEventsUsed; | |||||
| events->numberOfEvents = ++numEventsUsed; | |||||
| if (numBytes <= 4) | if (numBytes <= 4) | ||||
| { | { | ||||
| if (e->type == kVstSysExType) | |||||
| if (e->type == vstSysExEventType) | |||||
| { | { | ||||
| delete[] (((VstMidiSysexEvent*) e)->sysexDump); | |||||
| e->type = kVstMidiType; | |||||
| e->byteSize = sizeof (VstMidiEvent); | |||||
| e->noteLength = 0; | |||||
| e->noteOffset = 0; | |||||
| e->detune = 0; | |||||
| e->noteOffVelocity = 0; | |||||
| delete[] (((VstSysExEvent*) e)->sysExDump); | |||||
| e->type = vstMidiEventType; | |||||
| e->size = sizeof (VstMidiEvent); | |||||
| e->noteSampleLength = 0; | |||||
| e->noteSampleOffset = 0; | |||||
| e->tuning = 0; | |||||
| e->noteVelocityOff = 0; | |||||
| } | } | ||||
| e->deltaFrames = frameOffset; | |||||
| e->sampleOffset = frameOffset; | |||||
| memcpy (e->midiData, midiData, (size_t) numBytes); | memcpy (e->midiData, midiData, (size_t) numBytes); | ||||
| } | } | ||||
| else | else | ||||
| { | { | ||||
| VstMidiSysexEvent* const se = (VstMidiSysexEvent*) e; | |||||
| VstSysExEvent* const se = (VstSysExEvent*) e; | |||||
| if (se->type == kVstSysExType) | |||||
| delete[] se->sysexDump; | |||||
| if (se->type == vstSysExEventType) | |||||
| delete[] se->sysExDump; | |||||
| se->sysexDump = new char [numBytes]; | |||||
| memcpy (se->sysexDump, midiData, (size_t) numBytes); | |||||
| se->sysExDump = new char [(size_t) numBytes]; | |||||
| memcpy (se->sysExDump, midiData, (size_t) numBytes); | |||||
| se->type = kVstSysExType; | |||||
| se->byteSize = sizeof (VstMidiSysexEvent); | |||||
| se->deltaFrames = frameOffset; | |||||
| se->type = vstSysExEventType; | |||||
| se->size = sizeof (VstSysExEvent); | |||||
| se->offsetSamples = frameOffset; | |||||
| se->flags = 0; | se->flags = 0; | ||||
| se->dumpBytes = numBytes; | |||||
| se->resvd1 = 0; | |||||
| se->resvd2 = 0; | |||||
| se->sysExDumpSize = numBytes; | |||||
| se->future1 = 0; | |||||
| se->future2 = 0; | |||||
| } | } | ||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| // Handy method to pull the events out of an event buffer supplied by the host | // Handy method to pull the events out of an event buffer supplied by the host | ||||
| // or plugin. | // or plugin. | ||||
| static void addEventsToMidiBuffer (const VstEvents* events, MidiBuffer& dest) | |||||
| static void addEventsToMidiBuffer (const VstEventBlock* events, MidiBuffer& dest) | |||||
| { | { | ||||
| for (int i = 0; i < events->numEvents; ++i) | |||||
| for (int i = 0; i < events->numberOfEvents; ++i) | |||||
| { | { | ||||
| const VstEvent* const e = events->events[i]; | const VstEvent* const e = events->events[i]; | ||||
| if (e != nullptr) | if (e != nullptr) | ||||
| { | { | ||||
| if (e->type == kVstMidiType) | |||||
| if (e->type == vstMidiEventType) | |||||
| { | { | ||||
| dest.addEvent ((const juce::uint8*) ((const VstMidiEvent*) e)->midiData, | dest.addEvent ((const juce::uint8*) ((const VstMidiEvent*) e)->midiData, | ||||
| 4, e->deltaFrames); | |||||
| 4, e->sampleOffset); | |||||
| } | } | ||||
| else if (e->type == kVstSysExType) | |||||
| else if (e->type == vstSysExEventType) | |||||
| { | { | ||||
| dest.addEvent ((const juce::uint8*) ((const VstMidiSysexEvent*) e)->sysexDump, | |||||
| (int) ((const VstMidiSysexEvent*) e)->dumpBytes, | |||||
| e->deltaFrames); | |||||
| dest.addEvent ((const juce::uint8*) ((const VstSysExEvent*) e)->sysExDump, | |||||
| (int) ((const VstSysExEvent*) e)->sysExDumpSize, | |||||
| e->sampleOffset); | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -160,24 +161,24 @@ public: | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| HeapBlock<VstEvents> events; | |||||
| HeapBlock<VstEventBlock> events; | |||||
| private: | private: | ||||
| int numEventsUsed, numEventsAllocated; | int numEventsUsed, numEventsAllocated; | ||||
| static VstEvent* allocateVSTEvent() | static VstEvent* allocateVSTEvent() | ||||
| { | { | ||||
| VstEvent* const e = (VstEvent*) std::calloc (1, sizeof (VstMidiEvent) > sizeof (VstMidiSysexEvent) ? sizeof (VstMidiEvent) | |||||
| : sizeof (VstMidiSysexEvent)); | |||||
| e->type = kVstMidiType; | |||||
| e->byteSize = sizeof (VstMidiEvent); | |||||
| VstEvent* const e = (VstEvent*) std::calloc (1, sizeof (VstMidiEvent) > sizeof (VstSysExEvent) ? sizeof (VstMidiEvent) | |||||
| : sizeof (VstSysExEvent)); | |||||
| e->type = vstMidiEventType; | |||||
| e->size = sizeof (VstMidiEvent); | |||||
| return e; | return e; | ||||
| } | } | ||||
| static void freeVSTEvent (VstEvent* e) | static void freeVSTEvent (VstEvent* e) | ||||
| { | { | ||||
| if (e->type == kVstSysExType) | |||||
| delete[] (((VstMidiSysexEvent*) e)->sysexDump); | |||||
| if (e->type == vstSysExEventType) | |||||
| delete[] (((VstSysExEvent*) e)->sysExDump); | |||||
| std::free (e); | std::free (e); | ||||
| } | } | ||||
| @@ -22,7 +22,7 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| #if JUCE_PLUGINHOST_VST || DOXYGEN | |||||
| #if (JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS)) || DOXYGEN | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| @@ -36,7 +36,7 @@ public: | |||||
| ~VSTPluginFormat(); | ~VSTPluginFormat(); | ||||
| //============================================================================== | //============================================================================== | ||||
| /** Attempts to retreive the VSTXML data from a plugin. | |||||
| /** Attempts to retrieve the VSTXML data from a plugin. | |||||
| Will return nullptr if the plugin isn't a VST, or if it doesn't have any VSTXML. | Will return nullptr if the plugin isn't a VST, or if it doesn't have any VSTXML. | ||||
| */ | */ | ||||
| static const XmlElement* getVSTXML (AudioPluginInstance* plugin); | static const XmlElement* getVSTXML (AudioPluginInstance* plugin); | ||||
| @@ -53,6 +53,13 @@ public: | |||||
| /** Attempts to set a VST's state from a chunk of memory. */ | /** Attempts to set a VST's state from a chunk of memory. */ | ||||
| static bool setChunkData (AudioPluginInstance* plugin, const void* data, int size, bool isPreset); | static bool setChunkData (AudioPluginInstance* plugin, const void* data, int size, bool isPreset); | ||||
| /** Given a suitable function pointer to a VSTPluginMain function, this will attempt to | |||||
| instantiate and return a plugin for it. | |||||
| */ | |||||
| static AudioPluginInstance* createCustomVSTFromMainCall (void* entryPointFunction, | |||||
| double initialSampleRate, | |||||
| int initialBufferSize); | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Base class for some extra functions that can be attached to a VST plugin instance. */ | /** Base class for some extra functions that can be attached to a VST plugin instance. */ | ||||
| class ExtraFunctions | class ExtraFunctions | ||||
| @@ -75,23 +82,21 @@ public: | |||||
| static void setExtraFunctions (AudioPluginInstance* plugin, ExtraFunctions* functions); | static void setExtraFunctions (AudioPluginInstance* plugin, ExtraFunctions* functions); | ||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_64BIT | |||||
| typedef int64 VstIntPtr; | |||||
| #else | |||||
| typedef int32 VstIntPtr; | |||||
| #endif | |||||
| /** This simply calls directly to the VST's AEffect::dispatcher() function. */ | /** This simply calls directly to the VST's AEffect::dispatcher() function. */ | ||||
| static VstIntPtr JUCE_CALLTYPE dispatcher (AudioPluginInstance*, int32, int32, VstIntPtr, void*, float); | |||||
| static pointer_sized_int JUCE_CALLTYPE dispatcher (AudioPluginInstance*, int32, int32, pointer_sized_int, void*, float); | |||||
| /** Given a VstEffectInterface* (aka vst::AEffect*), this will return the juce AudioPluginInstance | |||||
| that is being used to wrap it | |||||
| */ | |||||
| static AudioPluginInstance* getPluginInstanceFromVstEffectInterface (void* aEffect); | |||||
| //============================================================================== | //============================================================================== | ||||
| String getName() const override { return "VST"; } | String getName() const override { return "VST"; } | ||||
| void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | ||||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, double, int) override; | |||||
| bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | ||||
| String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | ||||
| bool pluginNeedsRescanning (const PluginDescription&) override; | bool pluginNeedsRescanning (const PluginDescription&) override; | ||||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; | |||||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||||
| bool doesPluginStillExist (const PluginDescription&) override; | bool doesPluginStillExist (const PluginDescription&) override; | ||||
| FileSearchPath getDefaultLocationsToSearch() override; | FileSearchPath getDefaultLocationsToSearch() override; | ||||
| bool canScanForPlugins() const override { return true; } | bool canScanForPlugins() const override { return true; } | ||||
| @@ -103,6 +108,14 @@ public: | |||||
| */ | */ | ||||
| virtual void aboutToScanVSTShellPlugin (const PluginDescription&); | virtual void aboutToScanVSTShellPlugin (const PluginDescription&); | ||||
| private: | |||||
| //============================================================================== | |||||
| void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||||
| int initialBufferSize, void* userData, | |||||
| void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||||
| bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||||
| private: | private: | ||||
| void recursiveFileSearch (StringArray&, const File&, bool recursive); | void recursiveFileSearch (StringArray&, const File&, bool recursive); | ||||
| @@ -31,10 +31,15 @@ | |||||
| #error "Incorrect use of JUCE cpp file" | #error "Incorrect use of JUCE cpp file" | ||||
| #endif | #endif | ||||
| #include "AppConfig.h" | |||||
| #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | ||||
| #include "juce_audio_processors.h" | #include "juce_audio_processors.h" | ||||
| #include "../juce_gui_extra/juce_gui_extra.h" | |||||
| #if ! JUCE_AUDIO_PROCESSOR_NO_GUI | |||||
| #include <juce_gui_extra/juce_gui_extra.h> | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| #if JUCE_MAC | #if JUCE_MAC | ||||
| @@ -70,9 +75,18 @@ static inline bool arrayContainsPlugin (const OwnedArray<PluginDescription>& lis | |||||
| return false; | return false; | ||||
| } | } | ||||
| #if JUCE_MAC | |||||
| #if JUCE_MAC || JUCE_IOS | |||||
| #if JUCE_IOS | |||||
| #define JUCE_IOS_MAC_VIEW UIView | |||||
| typedef UIViewComponent ViewComponentBaseClass; | |||||
| #else | |||||
| #define JUCE_IOS_MAC_VIEW NSView | |||||
| typedef NSViewComponent ViewComponentBaseClass; | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| struct AutoResizingNSViewComponent : public NSViewComponent, | struct AutoResizingNSViewComponent : public NSViewComponent, | ||||
| private AsyncUpdater { | private AsyncUpdater { | ||||
| AutoResizingNSViewComponent(); | AutoResizingNSViewComponent(); | ||||
| @@ -84,7 +98,7 @@ struct AutoResizingNSViewComponent : public NSViewComponent, | |||||
| struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewComponent, | struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewComponent, | ||||
| private Timer { | private Timer { | ||||
| AutoResizingNSViewComponentWithParent(); | AutoResizingNSViewComponentWithParent(); | ||||
| NSView* getChildView() const; | |||||
| JUCE_IOS_MAC_VIEW* getChildView() const; | |||||
| void timerCallback() override; | void timerCallback() override; | ||||
| }; | }; | ||||
| @@ -112,27 +126,29 @@ void AutoResizingNSViewComponent::handleAsyncUpdate() | |||||
| resizeToFitView(); | resizeToFitView(); | ||||
| } | } | ||||
| //============================================================================== | |||||
| AutoResizingNSViewComponentWithParent::AutoResizingNSViewComponentWithParent() | AutoResizingNSViewComponentWithParent::AutoResizingNSViewComponentWithParent() | ||||
| { | { | ||||
| NSView* v = [[NSView alloc] init]; | |||||
| JUCE_IOS_MAC_VIEW* v = [[JUCE_IOS_MAC_VIEW alloc] init]; | |||||
| setView (v); | setView (v); | ||||
| [v release]; | [v release]; | ||||
| startTimer(500); | |||||
| startTimer(30); | |||||
| } | } | ||||
| NSView* AutoResizingNSViewComponentWithParent::getChildView() const | |||||
| JUCE_IOS_MAC_VIEW* AutoResizingNSViewComponentWithParent::getChildView() const | |||||
| { | { | ||||
| if (NSView* parent = (NSView*)getView()) | |||||
| if (JUCE_IOS_MAC_VIEW* parent = (JUCE_IOS_MAC_VIEW*)getView()) | |||||
| if ([[parent subviews] count] > 0) | if ([[parent subviews] count] > 0) | ||||
| return [[parent subviews] objectAtIndex: 0]; | return [[parent subviews] objectAtIndex: 0]; | ||||
| return nil; | return nil; | ||||
| } | } | ||||
| void AutoResizingNSViewComponentWithParent::timerCallback() | void AutoResizingNSViewComponentWithParent::timerCallback() | ||||
| { | { | ||||
| if (NSView* child = getChildView()) | |||||
| if (JUCE_IOS_MAC_VIEW* child = getChildView()) | |||||
| { | { | ||||
| stopTimer(); | stopTimer(); | ||||
| setView(child); | setView(child); | ||||
| @@ -148,12 +164,10 @@ void AutoResizingNSViewComponentWithParent::timerCallback() | |||||
| #include "format/juce_AudioPluginFormat.cpp" | #include "format/juce_AudioPluginFormat.cpp" | ||||
| #include "format/juce_AudioPluginFormatManager.cpp" | #include "format/juce_AudioPluginFormatManager.cpp" | ||||
| #include "processors/juce_AudioProcessor.cpp" | #include "processors/juce_AudioProcessor.cpp" | ||||
| #include "processors/juce_AudioChannelSet.cpp" | |||||
| #include "processors/juce_AudioProcessorEditor.cpp" | #include "processors/juce_AudioProcessorEditor.cpp" | ||||
| #include "processors/juce_AudioProcessorGraph.cpp" | #include "processors/juce_AudioProcessorGraph.cpp" | ||||
| #include "processors/juce_GenericAudioProcessorEditor.cpp" | #include "processors/juce_GenericAudioProcessorEditor.cpp" | ||||
| #include "processors/juce_PluginDescription.cpp" | #include "processors/juce_PluginDescription.cpp" | ||||
| #include "processors/AudioProcessorGraphMultiThreaded.cpp" | |||||
| #include "format_types/juce_LADSPAPluginFormat.cpp" | #include "format_types/juce_LADSPAPluginFormat.cpp" | ||||
| #include "format_types/juce_VSTPluginFormat.cpp" | #include "format_types/juce_VSTPluginFormat.cpp" | ||||
| #include "format_types/juce_VST3PluginFormat.cpp" | #include "format_types/juce_VST3PluginFormat.cpp" | ||||
| @@ -22,17 +22,42 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| /******************************************************************************* | |||||
| The block below describes the properties of this module, and is read by | |||||
| the Projucer to automatically generate project code that uses it. | |||||
| For details about the syntax and how to create or use a module, see the | |||||
| JUCE Module Format.txt file. | |||||
| BEGIN_JUCE_MODULE_DECLARATION | |||||
| ID: juce_audio_processors | |||||
| vendor: juce | |||||
| version: 4.3.0 | |||||
| name: JUCE audio processor classes | |||||
| description: Classes for loading and playing VST, AU, or internally-generated audio processors. | |||||
| website: http://www.juce.com/juce | |||||
| license: GPL/Commercial | |||||
| dependencies: juce_gui_extra, juce_audio_basics | |||||
| OSXFrameworks: CoreAudio CoreMIDI AudioToolbox | |||||
| iOSFrameworks: AudioToolbox | |||||
| END_JUCE_MODULE_DECLARATION | |||||
| *******************************************************************************/ | |||||
| #ifndef JUCE_AUDIO_PROCESSORS_H_INCLUDED | #ifndef JUCE_AUDIO_PROCESSORS_H_INCLUDED | ||||
| #define JUCE_AUDIO_PROCESSORS_H_INCLUDED | #define JUCE_AUDIO_PROCESSORS_H_INCLUDED | ||||
| #include "../juce_gui_basics/juce_gui_basics.h" | |||||
| #include "../juce_audio_basics/juce_audio_basics.h" | |||||
| #include <juce_gui_basics/juce_gui_basics.h> | |||||
| #include <juce_audio_basics/juce_audio_basics.h> | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Config: JUCE_PLUGINHOST_VST | /** Config: JUCE_PLUGINHOST_VST | ||||
| Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be | |||||
| installed on your machine. | |||||
| Enables the VST audio plugin hosting classes. | |||||
| @see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU, JUCE_PLUGINHOST_VST3 | @see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU, JUCE_PLUGINHOST_VST3 | ||||
| */ | */ | ||||
| @@ -63,10 +88,14 @@ | |||||
| // #error "You need to set either the JUCE_PLUGINHOST_AU and/or JUCE_PLUGINHOST_VST and/or JUCE_PLUGINHOST_VST3 flags if you're using this module!" | // #error "You need to set either the JUCE_PLUGINHOST_AU and/or JUCE_PLUGINHOST_VST and/or JUCE_PLUGINHOST_VST3 flags if you're using this module!" | ||||
| #endif | #endif | ||||
| #if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT) | |||||
| #if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT || JUCE_IOS) | |||||
| #define JUCE_SUPPORT_CARBON 1 | #define JUCE_SUPPORT_CARBON 1 | ||||
| #endif | #endif | ||||
| #ifndef JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR | |||||
| #define JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR 1 | |||||
| #endif | |||||
| //============================================================================== | //============================================================================== | ||||
| //============================================================================== | //============================================================================== | ||||
| namespace juce | namespace juce | ||||
| @@ -77,13 +106,11 @@ class AudioProcessor; | |||||
| #include "processors/juce_AudioProcessorEditor.h" | #include "processors/juce_AudioProcessorEditor.h" | ||||
| #include "processors/juce_AudioProcessorListener.h" | #include "processors/juce_AudioProcessorListener.h" | ||||
| #include "processors/juce_AudioProcessorParameter.h" | #include "processors/juce_AudioProcessorParameter.h" | ||||
| #include "processors/juce_AudioChannelSet.h" | |||||
| #include "processors/juce_AudioProcessor.h" | #include "processors/juce_AudioProcessor.h" | ||||
| #include "processors/juce_PluginDescription.h" | #include "processors/juce_PluginDescription.h" | ||||
| #include "processors/juce_AudioPluginInstance.h" | #include "processors/juce_AudioPluginInstance.h" | ||||
| #include "processors/juce_AudioProcessorGraph.h" | #include "processors/juce_AudioProcessorGraph.h" | ||||
| #include "processors/juce_GenericAudioProcessorEditor.h" | #include "processors/juce_GenericAudioProcessorEditor.h" | ||||
| #include "processors/AudioProcessorGraphMultiThreaded.h" | |||||
| #include "format/juce_AudioPluginFormat.h" | #include "format/juce_AudioPluginFormat.h" | ||||
| #include "format/juce_AudioPluginFormatManager.h" | #include "format/juce_AudioPluginFormatManager.h" | ||||
| #include "scanning/juce_KnownPluginList.h" | #include "scanning/juce_KnownPluginList.h" | ||||
| @@ -78,6 +78,9 @@ public: | |||||
| protected: | protected: | ||||
| //============================================================================== | //============================================================================== | ||||
| AudioPluginInstance() {} | AudioPluginInstance() {} | ||||
| AudioPluginInstance (const BusesProperties& ioLayouts) : AudioProcessor (ioLayouts) {} | |||||
| template <int numLayouts> | |||||
| AudioPluginInstance (const short channelLayoutList[numLayouts][2]) : AudioProcessor (channelLayoutList) {} | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) | ||||
| }; | }; | ||||
| @@ -25,6 +25,7 @@ | |||||
| #ifndef JUCE_AUDIOPROCESSOR_H_INCLUDED | #ifndef JUCE_AUDIOPROCESSOR_H_INCLUDED | ||||
| #define JUCE_AUDIOPROCESSOR_H_INCLUDED | #define JUCE_AUDIOPROCESSOR_H_INCLUDED | ||||
| struct PluginBusUtilities; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| @@ -43,10 +44,41 @@ | |||||
| class JUCE_API AudioProcessor | class JUCE_API AudioProcessor | ||||
| { | { | ||||
| protected: | protected: | ||||
| struct BusesProperties; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Constructor. */ | |||||
| /** Constructor. | |||||
| This constructor will create a main input and output bus which are diabled | |||||
| by default. If you need more fine grain control then use the other | |||||
| constructors. | |||||
| */ | |||||
| AudioProcessor(); | AudioProcessor(); | ||||
| /** Constructor for multibus AudioProcessors | |||||
| If your AudioProcessor supports multiple buses than use this constructor | |||||
| to initialise the bus layouts and bus names of your plug-in. | |||||
| */ | |||||
| AudioProcessor (const BusesProperties& ioLayouts); | |||||
| /** Constructor for AudioProcessors which use layout maps | |||||
| If your AudioProcessor uses layout maps then use this constructor. | |||||
| */ | |||||
| #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||||
| AudioProcessor (const std::initializer_list<const short[2]>& channelLayoutList) | |||||
| { | |||||
| initialise (busesPropertiesFromLayoutArray (layoutListToArray (channelLayoutList))); | |||||
| } | |||||
| #else | |||||
| template <int numLayouts> | |||||
| AudioProcessor (const short channelLayoutList[numLayouts][2]) | |||||
| { | |||||
| initialise (busesPropertiesFromLayoutArray (layoutListToArray (channelLayoutList))); | |||||
| } | |||||
| #endif | |||||
| public: | public: | ||||
| //============================================================================== | //============================================================================== | ||||
| enum ProcessingPrecision | enum ProcessingPrecision | ||||
| @@ -70,18 +102,20 @@ public: | |||||
| playback stops. | playback stops. | ||||
| You can call getTotalNumInputChannels and getTotalNumOutputChannels | You can call getTotalNumInputChannels and getTotalNumOutputChannels | ||||
| or query the busArrangement member variable to find out the number of | |||||
| or query the busLayout member variable to find out the number of | |||||
| channels your processBlock callback must process. | channels your processBlock callback must process. | ||||
| The estimatedSamplesPerBlock value is a HINT about the typical number of | |||||
| samples that will be processed for each callback, but isn't any kind | |||||
| of guarantee. The actual block sizes that the host uses may be different | |||||
| each time the callback happens, and may be more or less than this value. | |||||
| The maximumExpectedSamplesPerBlock value is a strong hint about the maximum | |||||
| number of samples that will be provided in each block. You may want to use | |||||
| this value to resize internal buffers. You should program defensively in | |||||
| case a buggy host exceeds this value. The actual block sizes that the host | |||||
| uses may be different each time the callback happens: completely variable | |||||
| block sizes can be expected from some hosts. | |||||
| @see busArrangement, getTotalNumInputChannels, getTotalNumOutputChannels | |||||
| @see busLayout, getTotalNumInputChannels, getTotalNumOutputChannels | |||||
| */ | */ | ||||
| virtual void prepareToPlay (double sampleRate, | virtual void prepareToPlay (double sampleRate, | ||||
| int estimatedSamplesPerBlock) = 0; | |||||
| int maximumExpectedSamplesPerBlock) = 0; | |||||
| /** Called after playback has stopped, to let the filter free up any resources it | /** Called after playback has stopped, to let the filter free up any resources it | ||||
| no longer needs. | no longer needs. | ||||
| @@ -107,7 +141,7 @@ public: | |||||
| If your plug-in has more than one input or output buses then the buffer passed | If your plug-in has more than one input or output buses then the buffer passed | ||||
| to the processBlock methods will contain a bundle of all channels of each bus. | to the processBlock methods will contain a bundle of all channels of each bus. | ||||
| Use AudioBusArrangement::getBusBuffer to obtain an audio buffer for a | |||||
| Use AudiobusLayout::getBusBuffer to obtain an audio buffer for a | |||||
| particular bus. | particular bus. | ||||
| Note that if you have more outputs than inputs, then only those channels that | Note that if you have more outputs than inputs, then only those channels that | ||||
| @@ -144,7 +178,7 @@ public: | |||||
| processBlock() method to send out an asynchronous message. You could also use | processBlock() method to send out an asynchronous message. You could also use | ||||
| the AsyncUpdater class in a similar way. | the AsyncUpdater class in a similar way. | ||||
| @see AudioBusArrangement::getBusBuffer | |||||
| @see AudiobusLayout::getBusBuffer | |||||
| */ | */ | ||||
| virtual void processBlock (AudioBuffer<float>& buffer, | virtual void processBlock (AudioBuffer<float>& buffer, | ||||
| @@ -169,7 +203,7 @@ public: | |||||
| If your plug-in has more than one input or output buses then the buffer passed | If your plug-in has more than one input or output buses then the buffer passed | ||||
| to the processBlock methods will contain a bundle of all channels of | to the processBlock methods will contain a bundle of all channels of | ||||
| each bus. Use AudioBusArrangement::getBusBuffer to obtain a audio buffer | |||||
| each bus. Use AudiobusLayout::getBusBuffer to obtain a audio buffer | |||||
| for a particular bus. | for a particular bus. | ||||
| Note that if you have more outputs than inputs, then only those channels that | Note that if you have more outputs than inputs, then only those channels that | ||||
| @@ -182,8 +216,8 @@ public: | |||||
| but you should only read/write from the ones that your filter is supposed to | but you should only read/write from the ones that your filter is supposed to | ||||
| be using. | be using. | ||||
| If your plugin uses buses, then you should use AudioBusArrangement::getBusBuffer() | |||||
| or AudioBusArrangement::getChannelIndexInProcessBlockBuffer() to find out which | |||||
| If your plugin uses buses, then you should use AudiobusLayout::getBusBuffer() | |||||
| or AudiobusLayout::getChannelIndexInProcessBlockBuffer() to find out which | |||||
| of the input and output channels correspond to which of the buses. | of the input and output channels correspond to which of the buses. | ||||
| The number of samples in these buffers is NOT guaranteed to be the same for every | The number of samples in these buffers is NOT guaranteed to be the same for every | ||||
| @@ -210,12 +244,13 @@ public: | |||||
| processBlock() method to send out an asynchronous message. You could also use | processBlock() method to send out an asynchronous message. You could also use | ||||
| the AsyncUpdater class in a similar way. | the AsyncUpdater class in a similar way. | ||||
| @see AudioBusArrangement::getBusBuffer | |||||
| @see AudiobusLayout::getBusBuffer | |||||
| */ | */ | ||||
| virtual void processBlock (AudioBuffer<double>& buffer, | virtual void processBlock (AudioBuffer<double>& buffer, | ||||
| MidiBuffer& midiMessages); | MidiBuffer& midiMessages); | ||||
| /** Renders the next block when the processor is being bypassed. | /** Renders the next block when the processor is being bypassed. | ||||
| The default implementation of this method will pass-through any incoming audio, but | The default implementation of this method will pass-through any incoming audio, but | ||||
| you may override this method e.g. to add latency compensation to the data to match | you may override this method e.g. to add latency compensation to the data to match | ||||
| the processor's latency characteristics. This will avoid situations where bypassing | the processor's latency characteristics. This will avoid situations where bypassing | ||||
| @@ -227,6 +262,7 @@ public: | |||||
| MidiBuffer& midiMessages); | MidiBuffer& midiMessages); | ||||
| /** Renders the next block when the processor is being bypassed. | /** Renders the next block when the processor is being bypassed. | ||||
| The default implementation of this method will pass-through any incoming audio, but | The default implementation of this method will pass-through any incoming audio, but | ||||
| you may override this method e.g. to add latency compensation to the data to match | you may override this method e.g. to add latency compensation to the data to match | ||||
| the processor's latency characteristics. This will avoid situations where bypassing | the processor's latency characteristics. This will avoid situations where bypassing | ||||
| @@ -237,103 +273,372 @@ public: | |||||
| virtual void processBlockBypassed (AudioBuffer<double>& buffer, | virtual void processBlockBypassed (AudioBuffer<double>& buffer, | ||||
| MidiBuffer& midiMessages); | MidiBuffer& midiMessages); | ||||
| //============================================================================== | //============================================================================== | ||||
| /** Describes the layout and properties of an audio bus. | |||||
| Effectively a bus description is a named set of channel types. | |||||
| @see AudioChannelSet | |||||
| /** | |||||
| Represents the bus layout state of a plug-in | |||||
| */ | */ | ||||
| struct AudioProcessorBus | |||||
| struct BusesLayout | |||||
| { | { | ||||
| /** Creates a bus from a name and set of channel types. */ | |||||
| AudioProcessorBus (const String& busName, const AudioChannelSet& channelTypes); | |||||
| /** An array containing the list of input buses that this processor supports. */ | |||||
| Array<AudioChannelSet> inputBuses; | |||||
| /** The bus's name. */ | |||||
| String name; | |||||
| /** An array containing the list of output buses that this processor supports. */ | |||||
| Array<AudioChannelSet> outputBuses; | |||||
| /** Get the number of channels of a particular bus */ | |||||
| int getNumChannels (bool isInput, int busIndex) const noexcept | |||||
| { | |||||
| const Array<AudioChannelSet>& bus = (isInput ? inputBuses : outputBuses); | |||||
| return isPositiveAndBelow(busIndex, bus.size()) ? bus.getReference (busIndex).size() : 0; | |||||
| } | |||||
| /** Get the channel set of a particular bus */ | |||||
| AudioChannelSet& getChannelSet (bool isInput, int busIndex) | |||||
| { | |||||
| Array<AudioChannelSet>& sets = isInput ? inputBuses : outputBuses; | |||||
| jassert (isPositiveAndBelow (busIndex, sets.size())); | |||||
| return sets.getReference (busIndex); | |||||
| } | |||||
| /** The set of channel types that the bus contains. */ | |||||
| AudioChannelSet channels; | |||||
| /** Get the channel set of a particular bus */ | |||||
| AudioChannelSet getChannelSet (bool isInput, int busIndex) const noexcept | |||||
| { | |||||
| const Array<AudioChannelSet>& sets = isInput ? inputBuses : outputBuses; | |||||
| if (isPositiveAndBelow (busIndex, sets.size())) | |||||
| return sets.getReference (busIndex); | |||||
| else | |||||
| return AudioChannelSet(); | |||||
| } | |||||
| /** Get the input channel layout on the main bus. */ | |||||
| AudioChannelSet getMainInputChannelSet() const noexcept { return getChannelSet (true, 0); } | |||||
| /** Get the output channel layout on the main bus. */ | |||||
| AudioChannelSet getMainOutputChannelSet() const noexcept { return getChannelSet (false, 0); } | |||||
| /** Get the number of input channels on the main bus. */ | |||||
| int getMainInputChannels() const noexcept { return getNumChannels (true, 0); } | |||||
| /** Get the number of output channels on the main bus. */ | |||||
| int getMainOutputChannels() const noexcept { return getNumChannels (false, 0); } | |||||
| bool operator== (const BusesLayout& other) const noexcept { return inputBuses == other.inputBuses && outputBuses == other.outputBuses; } | |||||
| bool operator!= (const BusesLayout& other) const noexcept { return inputBuses != other.inputBuses || outputBuses != other.outputBuses; } | |||||
| }; | }; | ||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| Represents a set of input and output buses for an AudioProcessor. | |||||
| */ | |||||
| struct AudioBusArrangement | |||||
| Describes the layout and properties of an audio bus. | |||||
| Effectively a bus description is a named set of channel types. | |||||
| @see AudioChannelSet, AudioProcessor::addBus | |||||
| */ | |||||
| class Bus | |||||
| { | { | ||||
| /** An array containing the list of input buses that this processor supports. */ | |||||
| Array<AudioProcessorBus> inputBuses; | |||||
| public: | |||||
| /** Returns true if this bus is an input bus. */ | |||||
| bool isInput() const; | |||||
| /** An array containing the list of output buses that this processor supports. */ | |||||
| Array<AudioProcessorBus> outputBuses; | |||||
| /** Returns the index of this bus. */ | |||||
| int getBusIndex() const; | |||||
| /** Returns true if the current bus is the main input or output bus. */ | |||||
| bool isMain() const { return getBusIndex() == 0; } | |||||
| //============================================================================== | |||||
| /** The bus's name. */ | |||||
| const String &getName() const noexcept { return name; } | |||||
| /** Get the default layout of this bus. | |||||
| @see AudioChannelSet | |||||
| */ | |||||
| const AudioChannelSet& getDefaultLayout() const noexcept { return dfltLayout; } | |||||
| //============================================================================== | |||||
| /** The bus's current layout. This will be AudioChannelSet::disabled() if the current | |||||
| layout is dfisabled. | |||||
| @see AudioChannelSet | |||||
| */ | |||||
| const AudioChannelSet& getCurrentLayout() const noexcept { return layout; } | |||||
| /** Return the bus's last active channel layout. | |||||
| If the bus is currently enabled then the result will be identical to getCurrentLayout | |||||
| otherwise it will return the last enabled layout. | |||||
| @see AudioChannelSet | |||||
| */ | |||||
| const AudioChannelSet& getLastEnabledLayout() const noexcept { return lastLayout; } | |||||
| /** Sets the bus's current layout. | |||||
| If the AudioProcessor does not support this layout then this will return false. | |||||
| @see AudioChannelSet | |||||
| */ | |||||
| bool setCurrentLayout (const AudioChannelSet& layout); | |||||
| /** Sets the bus's current layout without changing the enabled state. | |||||
| If the AudioProcessor does not support this layout then this will return false. | |||||
| @see AudioChannelSet | |||||
| */ | |||||
| bool setCurrentLayoutWithoutEnabling (const AudioChannelSet& layout); | |||||
| /** Return the number of channels of the current bus. */ | |||||
| inline int getNumberOfChannels() const noexcept { return cachedChannelCount; } | |||||
| /** Set the number of channles of this bus. This will return false if the AudioProcessor | |||||
| does not support this layout. */ | |||||
| bool setNumberOfChannels (int channels); | |||||
| //============================================================================== | |||||
| /** Checks if a particular layout is supported. | |||||
| @param set The AudioChannelSet which is to be probed. | |||||
| @see AudioChannelSet | |||||
| */ | |||||
| bool isLayoutSupported (const AudioChannelSet& set) const; | |||||
| /** Checks if this bus can support a given number of channels. */ | |||||
| bool isNumberOfChannelsSupported (int channels) const; | |||||
| /** Returns a ChannelSet that the bus supports with a given number of channels. */ | |||||
| AudioChannelSet supportedLayoutWithChannels (int channels) const; | |||||
| /** Returns the maximum number of channels that this bus can support. | |||||
| @param limit The maximum value to return. | |||||
| */ | |||||
| int getMaxSupportedChannels (int limit = AudioChannelSet::maxChannelsOfNamedLayout) const; | |||||
| /** Returns the resulting layouts of all buses after changing the layout of this bus. | |||||
| Changing an individual layout of a bus may also change the layout of all the other | |||||
| buses. This method returns what the layouts of all the buses of the audio processor | |||||
| would be, if you were to change the layout of this bus to the given layout. If there | |||||
| is no way to support the given layout then this method will return the next best | |||||
| layout. | |||||
| */ | |||||
| BusesLayout getBusesLayoutForLayoutChangeOfBus (const AudioChannelSet& set) const; | |||||
| //============================================================================== | |||||
| /** Returns true if the current bus is enabled. */ | |||||
| bool isEnabled() const noexcept { return ! layout.isDisabled(); } | |||||
| /** Enable or disable this bus. This will return false if the AudioProcessor | |||||
| does not support disabling this bus. */ | |||||
| bool enable (bool shouldEnable = true); | |||||
| /** Returns if this bus is enabled by default. */ | |||||
| bool isEnabledByDefault() const noexcept { return enabledByDefault; } | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Returns the position of a bus's channels within the processBlock buffer. | /** Returns the position of a bus's channels within the processBlock buffer. | ||||
| This can be called in processBlock to figure out which channel of the master AudioSampleBuffer | This can be called in processBlock to figure out which channel of the master AudioSampleBuffer | ||||
| maps onto a specific bus's channel. | maps onto a specific bus's channel. | ||||
| */ | |||||
| int getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept; | |||||
| */ | |||||
| int getChannelIndexInProcessBlockBuffer (int channelIndex) const noexcept; | |||||
| /** Returns an AudioBuffer containing a set of channel pointers for a specific bus. | /** Returns an AudioBuffer containing a set of channel pointers for a specific bus. | ||||
| This can be called in processBlock to get a buffer containing a sub-group of the master | This can be called in processBlock to get a buffer containing a sub-group of the master | ||||
| AudioSampleBuffer which contains all the plugin channels. | AudioSampleBuffer which contains all the plugin channels. | ||||
| */ | |||||
| */ | |||||
| template <typename FloatType> | template <typename FloatType> | ||||
| AudioBuffer<FloatType> getBusBuffer (AudioBuffer<FloatType>& processBlockBuffer, bool isInput, int busIndex) const | |||||
| AudioBuffer<FloatType> getBusBuffer (AudioBuffer<FloatType>& processBlockBuffer) const | |||||
| { | { | ||||
| const int busNumChannels = (isInput ? inputBuses : outputBuses).getReference (busIndex).channels.size(); | |||||
| const int channelOffset = getChannelIndexInProcessBlockBuffer (isInput, busIndex, 0); | |||||
| return AudioBuffer<FloatType> (processBlockBuffer.getArrayOfWritePointers() + channelOffset, | |||||
| busNumChannels, processBlockBuffer.getNumSamples()); | |||||
| bool isIn; | |||||
| int busIdx; | |||||
| busDirAndIndex (isIn, busIdx); | |||||
| return owner.getBusBuffer (processBlockBuffer, isIn, busIdx); | |||||
| } | } | ||||
| private: | |||||
| friend class AudioProcessor; | |||||
| Bus (AudioProcessor&, const String&, const AudioChannelSet&, bool); | |||||
| void busDirAndIndex (bool&, int&) const noexcept; | |||||
| void updateChannelCount() noexcept; | |||||
| /** Returns the total number of channels in all the input buses. */ | |||||
| int getTotalNumInputChannels() const noexcept; | |||||
| AudioProcessor& owner; | |||||
| String name; | |||||
| AudioChannelSet layout, dfltLayout, lastLayout; | |||||
| bool enabledByDefault; | |||||
| int cachedChannelCount; | |||||
| /** Returns the total number of channels in all the output buses. */ | |||||
| int getTotalNumOutputChannels() const noexcept; | |||||
| JUCE_DECLARE_NON_COPYABLE (Bus); | |||||
| }; | }; | ||||
| /** The processor's bus arrangement. | |||||
| //============================================================================== | |||||
| /** Returns the number of buses on the input or output side */ | |||||
| int getBusCount (bool isInput) const noexcept { return (isInput ? inputBuses : outputBuses).size(); } | |||||
| /** Returns the audio bus with a given index and direction. | |||||
| If busIdx is invalid then this method will return a nullptr. | |||||
| */ | |||||
| Bus* getBus (bool isInput, int busIdx) noexcept { return (isInput ? inputBuses : outputBuses)[busIdx]; } | |||||
| /** Returns the audio bus with a given index and direction. | |||||
| If busIdx is invalid then this method will return a nullptr. | |||||
| */ | |||||
| const Bus* getBus (bool isInput, int busIdx) const noexcept { return const_cast<AudioProcessor*> (this)->getBus (isInput, busIdx); } | |||||
| //============================================================================== | |||||
| /** Callback to query if a bus can currently be added. | |||||
| This callback probes if a bus can currently be added. You should override | |||||
| this callback if you want to support dynamically adding/removing buses by | |||||
| the host. This is useful for mixer audio processors. | |||||
| The default implementation will always return false. | |||||
| @see addBus | |||||
| */ | |||||
| virtual bool canAddBus (bool /*inputBus*/) const { return false; } | |||||
| /** Callback to query if the last bus can currently be removed. | |||||
| This callback probes if the last bus can currently be removed. You should | |||||
| override this callback if you want to support dynamically adding/removing | |||||
| buses by the host. This is useful for mixer audio processors. | |||||
| If you return true in this callback then the AudioProcessor will go ahead | |||||
| and delete the bus. | |||||
| The default implementation will always return false. | |||||
| */ | |||||
| virtual bool canRemoveBus (bool /*inputBus*/) const { return false; } | |||||
| /** Dynamically request an additional bus. | |||||
| Request an additional bus from the audio processor. If the audio processor | |||||
| does not support adding additional buses then this method will return false. | |||||
| Most audio processors will not allow you to dynamically add/remove | |||||
| audio buses and will return false. | |||||
| This method will invoke the canApplyBusCountChange callback to probe | |||||
| if a bus can be added and, if yes, will use the supplied bus properties | |||||
| of the canApplyBusCountChange callback to create a new bus. | |||||
| @see canApplyBusCountChange, removeBus | |||||
| */ | |||||
| bool addBus (bool isInput); | |||||
| /** Dynamically remove the latest added bus. | |||||
| Request the removal of the last bus from the audio processor. If the | |||||
| audio processor does not support removing buses then this method will | |||||
| return false. | |||||
| Most audio processors will not allow you to dynamically add/remove | |||||
| audio buses and will return false. | |||||
| The default implementation will return false. | |||||
| Your plugin can modify this either | |||||
| - in the plugin's constructor | |||||
| - in the setPreferredBusArrangement() callback | |||||
| Changing it at other times can result in undefined behaviour. | |||||
| This method will invoke the canApplyBusCountChange callback to probe if | |||||
| a bus can currently be removed and, if yes, will go ahead and remove it. | |||||
| The host will negotiate with the plugin over its bus configuration by making calls | |||||
| to setPreferredBusArrangement(). | |||||
| @see addBus, canRemoveBus | |||||
| */ | |||||
| bool removeBus (bool isInput); | |||||
| //============================================================================== | |||||
| /** Set the channel layouts of this audio processor. | |||||
| @see setPreferredBusArrangement | |||||
| If the layout is not supported by this audio processor then | |||||
| this method will return false. You can use the checkBusesLayoutSupported | |||||
| and getNextBestLayout methods to probe which layouts this audio | |||||
| processor supports. | |||||
| */ | |||||
| bool setBusesLayout (const BusesLayout& arr); | |||||
| /** Set the channel layouts of this audio processor without changing the | |||||
| enablement state of the buses. | |||||
| If the layout is not supported by this audio processor then | |||||
| this method will return false. You can use the checkBusesLayoutSupported | |||||
| and getNextBestLayout methods to probe which layouts this audio | |||||
| processor supports. | |||||
| */ | */ | ||||
| AudioBusArrangement busArrangement; | |||||
| bool setBusesLayoutWithoutEnabling (const BusesLayout& arr); | |||||
| /** Provides the current channel layouts of this audio processor. */ | |||||
| BusesLayout getBusesLayout() const; | |||||
| /** Provides the channel layout of the bus with a given index and direction. | |||||
| If the index, direction combination is invalid then this will return an | |||||
| AudioChannelSet with no channels. | |||||
| */ | |||||
| AudioChannelSet getChannelLayoutOfBus (bool isInput, int busIdx) const noexcept; | |||||
| /** Set the channel layout of the bus with a given index and direction. | |||||
| If the index, direction combination is invalid or the layout is not | |||||
| supported by the audio processor then this method will return false. | |||||
| */ | |||||
| bool setChannelLayoutOfBus (bool isInput, int busIdx, const AudioChannelSet& layout); | |||||
| /** Provides the number of channels of the bus with a given index and direction. | |||||
| If the index, direction combination is invalid then this will return zero. | |||||
| */ | |||||
| inline int getChannelCountOfBus (bool isInput, int busIdx) const noexcept | |||||
| { | |||||
| if (const Bus* bus = getBus (isInput, busIdx)) | |||||
| return bus->getNumberOfChannels(); | |||||
| return 0; | |||||
| } | |||||
| /** Enables all buses */ | |||||
| bool enableAllBuses(); | |||||
| /** Disables all non-main buses (aux and sidechains). */ | |||||
| bool disableNonMainBuses(); | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Called by the host, this attempts to change the plugin's channel layout on a particular bus. | |||||
| The base class implementation will perform some basic sanity-checking and then apply the | |||||
| changes to the processor's busArrangement value. | |||||
| You may override it and return false if you want to make your plugin smarter about refusing | |||||
| certain layouts that you don't want to support. Your plug-in may also respond to this call by | |||||
| changing the channel layout of other buses, for example, if your plug-in requires the same | |||||
| number of input and output channels. | |||||
| /** Returns the position of a bus's channels within the processBlock buffer. | |||||
| This can be called in processBlock to figure out which channel of the master AudioSampleBuffer | |||||
| maps onto a specific bus's channel. | |||||
| */ | |||||
| int getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept; | |||||
| For most basic plug-ins, which do not require side-chains, aux buses or detailed audio | |||||
| channel layout information, it is easier to specify the acceptable channel configurations | |||||
| via the "PlugIn Channel Configurations" field in the Introjucer. In this case, you should | |||||
| not override this method. | |||||
| /** Returns the offset in a bus's buffer from an absolute channel indes. | |||||
| If, on the other hand, you decide to override this method then you need to make sure that | |||||
| "PlugIn Channel Configurations" field in the Introjucer is empty. | |||||
| This method returns the offset in a bus's buffer given an absolute channel index. | |||||
| It also provides the bus index. For example, this method would return one | |||||
| for a processor with two stereo buses when given the absolute channel index. | |||||
| */ | |||||
| int getOffsetInBusBufferForAbsoluteChannelIndex (bool isInput, int absoluteChannelIndex, /*out*/ int& busIdx) const noexcept; | |||||
| Note, that you must not do any heavy allocations or calculations in this callback as it may | |||||
| be called several hundred times during initialization. If you require any layout specific | |||||
| allocations then defer these to prepareToPlay callback. | |||||
| /** Returns an AudioBuffer containing a set of channel pointers for a specific bus. | |||||
| This can be called in processBlock to get a buffer containing a sub-group of the master | |||||
| AudioSampleBuffer which contains all the plugin channels. | |||||
| */ | |||||
| template <typename FloatType> | |||||
| AudioBuffer<FloatType> getBusBuffer (AudioBuffer<FloatType>& processBlockBuffer, bool isInput, int busIndex) const | |||||
| { | |||||
| const int busNumChannels = getChannelCountOfBus (isInput, busIndex); | |||||
| const int channelOffset = getChannelIndexInProcessBlockBuffer (isInput, busIndex, 0); | |||||
| @returns false if there is no way for the processor to support the given format on the specified bus. | |||||
| return AudioBuffer<FloatType> (processBlockBuffer.getArrayOfWritePointers() + channelOffset, | |||||
| busNumChannels, processBlockBuffer.getNumSamples()); | |||||
| } | |||||
| @see prepareToPlay, busArrangement, AudioBusArrangement::getBusBuffer, getTotalNumInputChannels, getTotalNumOutputChannels | |||||
| //============================================================================== | |||||
| /** Returns true if the Audio processor is likely to support a given layout. | |||||
| This can be called regardless if the processor is currently running. | |||||
| */ | */ | ||||
| virtual bool setPreferredBusArrangement (bool isInputBus, int busIndex, const AudioChannelSet& preferredSet); | |||||
| bool checkBusesLayoutSupported (const BusesLayout& layouts) const; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Returns true if the Audio processor supports double precision floating point processing. | /** Returns true if the Audio processor supports double precision floating point processing. | ||||
| @@ -403,7 +708,7 @@ public: | |||||
| getMainBusNumInputChannels if your processor does not have any sidechains | getMainBusNumInputChannels if your processor does not have any sidechains | ||||
| or aux buses. | or aux buses. | ||||
| */ | */ | ||||
| int getTotalNumInputChannels() const noexcept { return busArrangement.getTotalNumInputChannels(); } | |||||
| int getTotalNumInputChannels() const noexcept { return cachedTotalIns; } | |||||
| /** Returns the total number of output channels. | /** Returns the total number of output channels. | ||||
| @@ -417,13 +722,56 @@ public: | |||||
| getMainBusNumOutputChannels if your processor does not have any sidechains | getMainBusNumOutputChannels if your processor does not have any sidechains | ||||
| or aux buses. | or aux buses. | ||||
| */ | */ | ||||
| int getTotalNumOutputChannels() const noexcept { return busArrangement.getTotalNumOutputChannels(); } | |||||
| int getTotalNumOutputChannels() const noexcept { return cachedTotalOuts; } | |||||
| /** Returns the number of input channels on the main bus. */ | /** Returns the number of input channels on the main bus. */ | ||||
| int getMainBusNumInputChannels() const noexcept; | |||||
| inline int getMainBusNumInputChannels() const noexcept { return getChannelCountOfBus (true, 0); } | |||||
| /** Returns the number of output channels on the main bus. */ | /** Returns the number of output channels on the main bus. */ | ||||
| int getMainBusNumOutputChannels() const noexcept; | |||||
| inline int getMainBusNumOutputChannels() const noexcept { return getChannelCountOfBus (false, 0); } | |||||
| //============================================================================== | |||||
| /** Returns true if the channel layout map contains a certain layout. | |||||
| You can use this method to help you implement the checkBusesLayoutSupported | |||||
| method. For example | |||||
| @code | |||||
| bool checkBusesLayoutSupported (const BusesLayout& layouts) override | |||||
| { | |||||
| return containsLayout (layouts, {{1,1},{2,2}}); | |||||
| } | |||||
| @endcode | |||||
| */ | |||||
| #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||||
| static bool containsLayout (const BusesLayout& layouts, const std::initializer_list<const short[2]>& channelLayoutList) | |||||
| { | |||||
| return containsLayout (layouts, layoutListToArray (channelLayoutList)); | |||||
| } | |||||
| #endif | |||||
| template <int numLayouts> | |||||
| static bool containsLayout (const BusesLayout& layouts, const short (&channelLayoutList) [numLayouts][2]) | |||||
| { | |||||
| return containsLayout (layouts, layoutListToArray (channelLayoutList)); | |||||
| } | |||||
| /** Returns the next best layout which is contained in a channel layout map. | |||||
| You can use this mehtod to help you implement getNextBestLayout. For example: | |||||
| @code | |||||
| BusesLayout getNextBestLayout (const BusesLayout& layouts) override | |||||
| { | |||||
| return getNextBestLayoutInLayoutList (layouts, {{1,1},{2,2}}); | |||||
| } | |||||
| @endcode | |||||
| */ | |||||
| template <int numLayouts> | |||||
| BusesLayout getNextBestLayoutInLayoutList (const BusesLayout& layouts, | |||||
| const short (&channelLayoutList) [numLayouts][2]) | |||||
| { | |||||
| return getNextBestLayoutInList (layouts, layoutListToArray (channelLayoutList)); | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Returns the current sample rate. | /** Returns the current sample rate. | ||||
| @@ -473,6 +821,9 @@ public: | |||||
| /** Returns true if the processor supports MPE. */ | /** Returns true if the processor supports MPE. */ | ||||
| virtual bool supportsMPE() const { return false; } | virtual bool supportsMPE() const { return false; } | ||||
| /** Returns true if this is a midi effect plug-in and does no audio processing. */ | |||||
| virtual bool isMidiEffect() const { return false; } | |||||
| //============================================================================== | //============================================================================== | ||||
| /** This returns a critical section that will automatically be locked while the host | /** This returns a critical section that will automatically be locked while the host | ||||
| is calling the processBlock() method. | is calling the processBlock() method. | ||||
| @@ -611,6 +962,17 @@ public: | |||||
| */ | */ | ||||
| virtual const String getParameterName (int parameterIndex); | virtual const String getParameterName (int parameterIndex); | ||||
| /** Returns the ID of a particular parameter. | |||||
| The ID is used to communicate the value or mapping of a particular parameter with | |||||
| the host. By default this method will simply return a string representation of | |||||
| index. | |||||
| NOTE! This method will eventually be deprecated! It's recommended that you use the | |||||
| AudioProcessorParameterWithID class instead to manage your parameters. | |||||
| */ | |||||
| virtual String getParameterID (int index); | |||||
| /** Called by the host to find out the value of one of the filter's parameters. | /** Called by the host to find out the value of one of the filter's parameters. | ||||
| The host will expect the value returned to be between 0 and 1.0. | The host will expect the value returned to be between 0 and 1.0. | ||||
| @@ -628,7 +990,7 @@ public: | |||||
| If you want to provide customised short versions of your parameter names that | If you want to provide customised short versions of your parameter names that | ||||
| will look better in constrained spaces (e.g. the displays on hardware controller | will look better in constrained spaces (e.g. the displays on hardware controller | ||||
| devices or mixing desks) then you should implement this method. | devices or mixing desks) then you should implement this method. | ||||
| If you don't override it, the default implementation will call getParameterText(int), | |||||
| If you don't override it, the default implementation will call getParameterName(int), | |||||
| and truncate the result. | and truncate the result. | ||||
| NOTE! This method will eventually be deprecated! It's recommended that you use | NOTE! This method will eventually be deprecated! It's recommended that you use | ||||
| @@ -864,9 +1226,15 @@ public: | |||||
| */ | */ | ||||
| virtual void setCurrentProgramStateInformation (const void* data, int sizeInBytes); | virtual void setCurrentProgramStateInformation (const void* data, int sizeInBytes); | ||||
| /** This method is called when the number of input or output channels is changed. */ | |||||
| /** This method is called when the total number of input or output channels is changed. */ | |||||
| virtual void numChannelsChanged(); | virtual void numChannelsChanged(); | ||||
| /** This method is called when the number of buses is changed. */ | |||||
| virtual void numBusesChanged(); | |||||
| /** This method is called when the layout of the audio processor changes. */ | |||||
| virtual void processorLayoutsChanged(); | |||||
| //============================================================================== | //============================================================================== | ||||
| /** LV2 specific calls, saving/restore as string. */ | /** LV2 specific calls, saving/restore as string. */ | ||||
| virtual String getStateInformationString () { return String::empty; } | virtual String getStateInformationString () { return String::empty; } | ||||
| @@ -894,9 +1262,9 @@ public: | |||||
| /** This is called by the processor to specify its details before being played. You | /** This is called by the processor to specify its details before being played. You | ||||
| should call this function after having informed the processor about the channel | should call this function after having informed the processor about the channel | ||||
| and bus layouts via setPreferredBusArrangement. | |||||
| and bus layouts via setBusesLayout. | |||||
| @see setPreferredBusArrangement | |||||
| @see setBusesLayout | |||||
| */ | */ | ||||
| void setRateAndBufferSizeDetails (double sampleRate, int blockSize) noexcept; | void setRateAndBufferSizeDetails (double sampleRate, int blockSize) noexcept; | ||||
| @@ -913,6 +1281,7 @@ public: | |||||
| wrapperType_VST, | wrapperType_VST, | ||||
| wrapperType_VST3, | wrapperType_VST3, | ||||
| wrapperType_AudioUnit, | wrapperType_AudioUnit, | ||||
| wrapperType_AudioUnitv3, | |||||
| wrapperType_RTAS, | wrapperType_RTAS, | ||||
| wrapperType_AAX, | wrapperType_AAX, | ||||
| wrapperType_Standalone | wrapperType_Standalone | ||||
| @@ -939,7 +1308,7 @@ public: | |||||
| /** Returns the name of one of the processor's input channels. | /** Returns the name of one of the processor's input channels. | ||||
| These functions are deprecated: your audio processor can inform the host | These functions are deprecated: your audio processor can inform the host | ||||
| on channel layouts and names via the methods in the AudioBusArrangement class. | |||||
| on channel layouts and names via the methods in the AudiobusLayout class. | |||||
| */ | */ | ||||
| JUCE_DEPRECATED (virtual const String getInputChannelName (int channelIndex) const); | JUCE_DEPRECATED (virtual const String getInputChannelName (int channelIndex) const); | ||||
| JUCE_DEPRECATED (virtual const String getOutputChannelName (int channelIndex) const); | JUCE_DEPRECATED (virtual const String getOutputChannelName (int channelIndex) const); | ||||
| @@ -947,7 +1316,7 @@ public: | |||||
| /** Returns true if the specified channel is part of a stereo pair with its neighbour. | /** Returns true if the specified channel is part of a stereo pair with its neighbour. | ||||
| These functions are deprecated: your audio processor should specify the audio | These functions are deprecated: your audio processor should specify the audio | ||||
| channel pairing information by modifying the busArrangement member variable in | |||||
| channel pairing information by modifying the busLayout member variable in | |||||
| the constructor. */ | the constructor. */ | ||||
| JUCE_DEPRECATED (virtual bool isInputChannelStereoPair (int index) const); | JUCE_DEPRECATED (virtual bool isInputChannelStereoPair (int index) const); | ||||
| JUCE_DEPRECATED (virtual bool isOutputChannelStereoPair (int index) const); | JUCE_DEPRECATED (virtual bool isOutputChannelStereoPair (int index) const); | ||||
| @@ -976,6 +1345,110 @@ public: | |||||
| static void JUCE_CALLTYPE setTypeOfNextNewPlugin (WrapperType); | static void JUCE_CALLTYPE setTypeOfNextNewPlugin (WrapperType); | ||||
| protected: | protected: | ||||
| /** Callback to query if the AudioProcessor supports a specific layout. | |||||
| This callback is called when the host probes the supported bus layouts via | |||||
| the checkBusesLayoutSupported method. You should override this callback if you | |||||
| would like to limit the layouts that your AudioProcessor supports. The default | |||||
| implementation will accept any layout. JUCE does basic sanity checks so that | |||||
| the provided layouts parameter will have the same number of buses as your | |||||
| AudioProcessor. | |||||
| @see checkBusesLayoutSupported | |||||
| */ | |||||
| virtual bool isBusesLayoutSupported (const BusesLayout& /*layouts*/) const { return true; } | |||||
| /** Callback to check if a certain bus layout can now be applied | |||||
| Most subclasses will not need to override this method and should instead | |||||
| override the isBusesLayoutSupported callback to reject certain layout changes. | |||||
| This callback is called when the user requests a layout change. It will only be | |||||
| called if processing of the AudioProcessor has been stopped by a previous call to | |||||
| releaseResources or after the construction of the AudioProcessor. It will be called | |||||
| just before the actual layout change. By returning false you will abort the layout | |||||
| change and setBusesLayout will return false indicating that the layout change | |||||
| was not successful. | |||||
| The default implementation will simply call isBusesLayoutSupported. | |||||
| You only need to override this method if there is a chance that your AudioProcessor | |||||
| may not accept a layout although you have previously claimed to support it via the | |||||
| isBusesLayoutSupported callback. This can occur if your AudioProcessor's supported | |||||
| layouts depend on other plug-in parameters which may have changed since the last | |||||
| call to isBusesLayoutSupported, such as the format of an audio file which can be | |||||
| selected by the user in the AudioProcessor's editor. This callback gives the | |||||
| AudioProcessor a last chance to reject a layout if conditions have changed as it | |||||
| is always called just before the actual layout change. | |||||
| As it is never called while the AudioProcessor is processing audio, it can also | |||||
| be used for AudioProcessors which wrap other plug-in formats to apply the current | |||||
| layout to the underlying plug-in. This callback gives such AudioProcessors a | |||||
| chance to reject the layout change should an error occur with the underlying plug-in | |||||
| during the layout change. | |||||
| @see isBusesLayoutSupported, setBusesLayout | |||||
| */ | |||||
| virtual bool canApplyBusesLayout (const BusesLayout& layouts) const { return isBusesLayoutSupported (layouts); } | |||||
| //============================================================================== | |||||
| /** Structure used for AudioProcessor Callbacks */ | |||||
| struct BusProperties | |||||
| { | |||||
| /** The name of the bus */ | |||||
| String busName; | |||||
| /** The default layout of the bus */ | |||||
| AudioChannelSet defaultLayout; | |||||
| /** Is this bus activated by default? */ | |||||
| bool isActivatedByDefault; | |||||
| }; | |||||
| struct BusesProperties | |||||
| { | |||||
| /** The layouts of the input buses */ | |||||
| Array<BusProperties> inputLayouts; | |||||
| /** The layouts of the output buses */ | |||||
| Array<BusProperties> outputLayouts; | |||||
| void addBus (bool isInput, const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true); | |||||
| BusesProperties withInput (const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true) const; | |||||
| BusesProperties withOutput (const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true) const; | |||||
| }; | |||||
| /** Callback to query if adding/removing buses currently possible. | |||||
| This callback is called when the host calls addBus or removeBus. | |||||
| Similar to canApplyBusesLayout, this callback is only called while | |||||
| the AudioProcessor is stopped and gives the processor a last | |||||
| chance to reject a requested bus change. It can also be used to apply | |||||
| the bus count change to an underlying wrapped plug-in. | |||||
| When adding a bus, isAddingBuses will be true and the plug-in is | |||||
| expected to fill out outNewBusProperties with the properties of the | |||||
| bus which will be created just after the succesful return of this callback. | |||||
| Implementations of AudioProcessor will rarely need to override this | |||||
| method. Only override this method if your processor supports adding | |||||
| and removing buses and if it needs more fine grain control over the | |||||
| naming of new buses or may reject bus number changes although canAddBus | |||||
| or canRemoveBus returned true. | |||||
| The default implementation will return false if canAddBus/canRemoveBus | |||||
| returns false (the default behavior). Otherwise, this method returns | |||||
| "Input #busIdx" for input buses and "Output #busIdx" for output buses | |||||
| where busIdx is the index for newly created buses. The default layout | |||||
| in this case will be the layout of the previous bus of the same direction. | |||||
| */ | |||||
| virtual bool canApplyBusCountChange (bool isInput, bool isAddingBuses, | |||||
| BusProperties& outNewBusProperties); | |||||
| //============================================================================== | |||||
| friend struct PluginBusUtilities; | |||||
| /** @internal */ | /** @internal */ | ||||
| AudioPlayHead* playHead; | AudioPlayHead* playHead; | ||||
| @@ -983,6 +1456,67 @@ protected: | |||||
| void sendParamChangeMessageToListeners (int parameterIndex, float newValue); | void sendParamChangeMessageToListeners (int parameterIndex, float newValue); | ||||
| private: | private: | ||||
| //============================================================================== | |||||
| struct InOutChannelPair | |||||
| { | |||||
| int16 inChannels, outChannels; | |||||
| InOutChannelPair() noexcept : inChannels (0), outChannels (0) {} | |||||
| InOutChannelPair (short inCh, short outCh) noexcept : inChannels (inCh), outChannels (outCh) {} | |||||
| InOutChannelPair (const InOutChannelPair& o) noexcept : inChannels (o.inChannels), outChannels (o.outChannels) {} | |||||
| InOutChannelPair (const short (&config)[2]) noexcept : inChannels (config[0]), outChannels (config[1]) {} | |||||
| InOutChannelPair& operator= (const InOutChannelPair& o) { inChannels = o.inChannels; outChannels = o.outChannels; return *this; } | |||||
| bool operator== (const InOutChannelPair& other) const noexcept | |||||
| { | |||||
| return (other.inChannels == inChannels && other.outChannels == outChannels); | |||||
| } | |||||
| }; | |||||
| template <int numLayouts> | |||||
| static Array<InOutChannelPair> layoutListToArray (const short (&configuration) [numLayouts][2]) | |||||
| { | |||||
| Array<InOutChannelPair> layouts; | |||||
| for (int i = 0; i < numLayouts; ++i) | |||||
| { | |||||
| InOutChannelPair pair (configuration [i]); | |||||
| layouts.add (pair); | |||||
| } | |||||
| return layouts; | |||||
| } | |||||
| #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||||
| static Array<InOutChannelPair> layoutListToArray (const std::initializer_list<const short[2]>& configuration) | |||||
| { | |||||
| Array<InOutChannelPair> layouts; | |||||
| for (std::initializer_list<const short[2]>::const_iterator it = configuration.begin(); | |||||
| it != configuration.end(); ++it) | |||||
| { | |||||
| InOutChannelPair pair (*it); | |||||
| layouts.add (pair); | |||||
| } | |||||
| return layouts; | |||||
| } | |||||
| #endif | |||||
| //============================================================================== | |||||
| static BusesProperties busesPropertiesFromLayoutArray (const Array<InOutChannelPair>&); | |||||
| //============================================================================== | |||||
| BusesLayout getNextBestLayoutInList (const BusesLayout& layouts, | |||||
| const Array<InOutChannelPair>& channelLayouts) const; | |||||
| static bool containsLayout (const BusesLayout& layouts, const Array<InOutChannelPair>& channelLayouts); | |||||
| //============================================================================== | |||||
| void initialise (const BusesProperties& ioLayouts); | |||||
| void createBus (bool inputBus, const BusProperties&); | |||||
| //============================================================================== | |||||
| Array<AudioProcessorListener*> listeners; | Array<AudioProcessorListener*> listeners; | ||||
| #if ! JUCE_AUDIO_PROCESSOR_NO_GUI | #if ! JUCE_AUDIO_PROCESSOR_NO_GUI | ||||
| Component::SafePointer<AudioProcessorEditor> activeEditor; | Component::SafePointer<AudioProcessorEditor> activeEditor; | ||||
| @@ -996,9 +1530,15 @@ private: | |||||
| ProcessingPrecision processingPrecision; | ProcessingPrecision processingPrecision; | ||||
| CriticalSection callbackLock, listenerLock; | CriticalSection callbackLock, listenerLock; | ||||
| friend class Bus; | |||||
| mutable OwnedArray<Bus> inputBuses; | |||||
| mutable OwnedArray<Bus> outputBuses; | |||||
| String cachedInputSpeakerArrString; | String cachedInputSpeakerArrString; | ||||
| String cachedOutputSpeakerArrString; | String cachedOutputSpeakerArrString; | ||||
| int cachedTotalIns, cachedTotalOuts; | |||||
| OwnedArray<AudioProcessorParameter> managedParameters; | OwnedArray<AudioProcessorParameter> managedParameters; | ||||
| AudioProcessorParameter* getParamChecked (int) const noexcept; | AudioProcessorParameter* getParamChecked (int) const noexcept; | ||||
| @@ -1007,8 +1547,13 @@ private: | |||||
| #endif | #endif | ||||
| AudioProcessorListener* getListenerLocked (int) const noexcept; | AudioProcessorListener* getListenerLocked (int) const noexcept; | ||||
| void disableNonMainBuses (bool isInput); | |||||
| void updateSpeakerFormatStrings(); | void updateSpeakerFormatStrings(); | ||||
| bool applyBusLayouts (const BusesLayout& arr); | |||||
| void audioIOChanged (bool busNumberChanged, bool channelNumChanged); | |||||
| BusesLayout getNextBestLayout (const BusesLayout& layouts) const; | |||||
| template <typename floatType> | |||||
| void processBypassed (AudioBuffer<floatType>&, MidiBuffer&); | |||||
| // This method is no longer used - you can delete it from your AudioProcessor classes. | // This method is no longer used - you can delete it from your AudioProcessor classes. | ||||
| JUCE_DEPRECATED_WITH_BODY (virtual bool silenceInProducesSilenceOut() const, { return false; }); | JUCE_DEPRECATED_WITH_BODY (virtual bool silenceInProducesSilenceOut() const, { return false; }); | ||||
| @@ -22,14 +22,16 @@ | |||||
| ============================================================================== | ============================================================================== | ||||
| */ | */ | ||||
| AudioProcessorEditor::AudioProcessorEditor (AudioProcessor& p) noexcept : processor (p) | |||||
| AudioProcessorEditor::AudioProcessorEditor (AudioProcessor& p) noexcept : processor (p), constrainer (nullptr) | |||||
| { | { | ||||
| initialise(); | |||||
| } | } | ||||
| AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* p) noexcept : processor (*p) | |||||
| AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* p) noexcept : processor (*p), constrainer (nullptr) | |||||
| { | { | ||||
| // the filter must be valid.. | // the filter must be valid.. | ||||
| jassert (p != nullptr); | jassert (p != nullptr); | ||||
| initialise(); | |||||
| } | } | ||||
| AudioProcessorEditor::~AudioProcessorEditor() | AudioProcessorEditor::~AudioProcessorEditor() | ||||
| @@ -37,7 +39,132 @@ AudioProcessorEditor::~AudioProcessorEditor() | |||||
| // if this fails, then the wrapper hasn't called editorBeingDeleted() on the | // if this fails, then the wrapper hasn't called editorBeingDeleted() on the | ||||
| // filter for some reason.. | // filter for some reason.. | ||||
| jassert (processor.getActiveEditor() != this); | jassert (processor.getActiveEditor() != this); | ||||
| removeComponentListener (resizeListener); | |||||
| } | } | ||||
| void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} | void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} | ||||
| int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } | int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } | ||||
| void AudioProcessorEditor::initialise() | |||||
| { | |||||
| resizable = false; | |||||
| attachConstrainer (&defaultConstrainer); | |||||
| addComponentListener (resizeListener = new AudioProcessorEditorListener (this)); | |||||
| } | |||||
| //============================================================================== | |||||
| void AudioProcessorEditor::setResizable (const bool shouldBeResizable, const bool useBottomRightCornerResizer) | |||||
| { | |||||
| if (shouldBeResizable != resizable) | |||||
| { | |||||
| resizable = shouldBeResizable; | |||||
| if (! resizable) | |||||
| { | |||||
| setConstrainer (&defaultConstrainer); | |||||
| if (getWidth() > 0 && getHeight() > 0) | |||||
| { | |||||
| defaultConstrainer.setSizeLimits (getWidth(), getHeight(), getWidth(), getHeight()); | |||||
| resized(); | |||||
| } | |||||
| } | |||||
| } | |||||
| const bool shouldHaveCornerResizer = (useBottomRightCornerResizer && shouldBeResizable); | |||||
| if (shouldHaveCornerResizer != (resizableCorner != nullptr)) | |||||
| { | |||||
| if (shouldHaveCornerResizer) | |||||
| { | |||||
| Component::addChildComponent (resizableCorner = new ResizableCornerComponent (this, constrainer)); | |||||
| resizableCorner->setAlwaysOnTop (true); | |||||
| } | |||||
| else | |||||
| resizableCorner = nullptr; | |||||
| } | |||||
| } | |||||
| void AudioProcessorEditor::setResizeLimits (const int newMinimumWidth, | |||||
| const int newMinimumHeight, | |||||
| const int newMaximumWidth, | |||||
| const int newMaximumHeight) noexcept | |||||
| { | |||||
| // if you've set up a custom constrainer then these settings won't have any effect.. | |||||
| jassert (constrainer == &defaultConstrainer || constrainer == nullptr); | |||||
| const bool shouldEnableResize = (newMinimumWidth != newMaximumWidth || newMinimumHeight != newMaximumHeight); | |||||
| const bool shouldHaveCornerResizer = (shouldEnableResize != resizable || resizableCorner != nullptr); | |||||
| setResizable (shouldEnableResize, shouldHaveCornerResizer); | |||||
| if (constrainer == nullptr) | |||||
| setConstrainer (&defaultConstrainer); | |||||
| defaultConstrainer.setSizeLimits (newMinimumWidth, newMinimumHeight, | |||||
| newMaximumWidth, newMaximumHeight); | |||||
| setBoundsConstrained (getBounds()); | |||||
| } | |||||
| void AudioProcessorEditor::setConstrainer (ComponentBoundsConstrainer* newConstrainer) | |||||
| { | |||||
| if (constrainer != newConstrainer) | |||||
| { | |||||
| resizable = true; | |||||
| attachConstrainer (newConstrainer); | |||||
| } | |||||
| } | |||||
| void AudioProcessorEditor::attachConstrainer (ComponentBoundsConstrainer* newConstrainer) | |||||
| { | |||||
| if (constrainer != newConstrainer) | |||||
| { | |||||
| constrainer = newConstrainer; | |||||
| updatePeer(); | |||||
| } | |||||
| } | |||||
| void AudioProcessorEditor::setBoundsConstrained (Rectangle<int> newBounds) | |||||
| { | |||||
| if (constrainer != nullptr) | |||||
| constrainer->setBoundsForComponent (this, newBounds, false, false, false, false); | |||||
| else | |||||
| setBounds (newBounds); | |||||
| } | |||||
| void AudioProcessorEditor::editorResized (bool wasResized) | |||||
| { | |||||
| if (wasResized) | |||||
| { | |||||
| bool resizerHidden = false; | |||||
| if (ComponentPeer* peer = getPeer()) | |||||
| resizerHidden = peer->isFullScreen() || peer->isKioskMode(); | |||||
| if (resizableCorner != nullptr) | |||||
| { | |||||
| resizableCorner->setVisible (! resizerHidden); | |||||
| const int resizerSize = 18; | |||||
| resizableCorner->setBounds (getWidth() - resizerSize, | |||||
| getHeight() - resizerSize, | |||||
| resizerSize, resizerSize); | |||||
| } | |||||
| if (! resizable) | |||||
| { | |||||
| if (getWidth() > 0 && getHeight() > 0) | |||||
| defaultConstrainer.setSizeLimits (getWidth(), getHeight(), | |||||
| getWidth(), getHeight()); | |||||
| } | |||||
| } | |||||
| } | |||||
| void AudioProcessorEditor::updatePeer() | |||||
| { | |||||
| if (isOnDesktop()) | |||||
| if (ComponentPeer* const peer = getPeer()) | |||||
| peer->setConstrainer (constrainer); | |||||
| } | |||||
| @@ -25,7 +25,7 @@ | |||||
| #ifndef JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED | #ifndef JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED | ||||
| #define JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED | #define JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED | ||||
| class AudioProcessorEditorListener; | |||||
| //============================================================================== | //============================================================================== | ||||
| /** | /** | ||||
| Base class for the component that acts as the GUI for an AudioProcessor. | Base class for the component that acts as the GUI for an AudioProcessor. | ||||
| @@ -84,7 +84,91 @@ public: | |||||
| */ | */ | ||||
| virtual int getControlParameterIndex (Component&); | virtual int getControlParameterIndex (Component&); | ||||
| //============================================================================== | |||||
| /** Marks the host's editor window as resizable | |||||
| @param allowHostToResize whether the editor's parent window can be resized | |||||
| by the user or the host. Even if this is false, you | |||||
| can still resize your window yourself by calling | |||||
| setBounds (for example, when a user clicks on a button | |||||
| in your editor to drop out a panel) which will bypass any | |||||
| resizable/constraints checks. If you are using | |||||
| your own corner resizer than this will also bypass | |||||
| any checks. | |||||
| @param useBottomRightCornerResizer | |||||
| @see setResizeLimits, isResizable | |||||
| */ | |||||
| void setResizable (bool allowHostToResize, bool useBottomRightCornerResizer); | |||||
| /** Returns true if the host is allowed to resize editor's parent window | |||||
| @see setResizable | |||||
| */ | |||||
| bool isResizable() const noexcept { return resizable; } | |||||
| /** This sets the maximum and minimum sizes for the window. | |||||
| If the window's current size is outside these limits, it will be resized to | |||||
| make sure it's within them. | |||||
| A direct call to setBounds() will bypass any constraint checks, but when the | |||||
| window is dragged by the user or resized by other indirect means, the constrainer | |||||
| will limit the numbers involved. | |||||
| @see setResizable | |||||
| */ | |||||
| void setResizeLimits (int newMinimumWidth, | |||||
| int newMinimumHeight, | |||||
| int newMaximumWidth, | |||||
| int newMaximumHeight) noexcept; | |||||
| /** Returns the bounds constrainer object that this window is using. | |||||
| You can access this to change its properties. | |||||
| */ | |||||
| ComponentBoundsConstrainer* getConstrainer() noexcept { return constrainer; } | |||||
| /** Sets the bounds-constrainer object to use for resizing and dragging this window. | |||||
| A pointer to the object you pass in will be kept, but it won't be deleted | |||||
| by this object, so it's the caller's responsibility to manage it. | |||||
| If you pass a nullptr, then no contraints will be placed on the positioning of the window. | |||||
| */ | |||||
| void setConstrainer (ComponentBoundsConstrainer* newConstrainer); | |||||
| /** Calls the window's setBounds method, after first checking these bounds | |||||
| with the current constrainer. | |||||
| @see setConstrainer | |||||
| */ | |||||
| void setBoundsConstrained (Rectangle<int> newBounds); | |||||
| ScopedPointer<ResizableCornerComponent> resizableCorner; | |||||
| private: | private: | ||||
| //============================================================================== | |||||
| struct AudioProcessorEditorListener : ComponentListener | |||||
| { | |||||
| AudioProcessorEditorListener (AudioProcessorEditor* audioEditor) : e (audioEditor) {} | |||||
| void componentMovedOrResized (Component&, bool, bool wasResized) override { e->editorResized (wasResized); } | |||||
| void componentParentHierarchyChanged (Component&) override { e->updatePeer(); } | |||||
| AudioProcessorEditor* e; | |||||
| }; | |||||
| //============================================================================== | |||||
| void initialise(); | |||||
| void editorResized (bool wasResized); | |||||
| void updatePeer(); | |||||
| void attachConstrainer (ComponentBoundsConstrainer* newConstrainer); | |||||
| //============================================================================== | |||||
| ScopedPointer<AudioProcessorEditorListener> resizeListener; | |||||
| bool resizable; | |||||
| ComponentBoundsConstrainer defaultConstrainer; | |||||
| ComponentBoundsConstrainer* constrainer; | |||||
| JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditor) | JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditor) | ||||
| }; | }; | ||||
| @@ -264,7 +264,16 @@ struct ProcessBufferOp : public AudioGraphRenderingOp<ProcessBufferOp> | |||||
| AudioBuffer<FloatType> buffer (channels, totalChans, numSamples); | AudioBuffer<FloatType> buffer (channels, totalChans, numSamples); | ||||
| callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); | |||||
| if (processor->isSuspended()) | |||||
| { | |||||
| buffer.clear(); | |||||
| } | |||||
| else | |||||
| { | |||||
| ScopedLock lock (processor->getCallbackLock()); | |||||
| callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); | |||||
| } | |||||
| } | } | ||||
| void callProcess (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) | void callProcess (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) | ||||
| @@ -960,14 +969,7 @@ void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int n | |||||
| processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision | processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision | ||||
| : singlePrecision); | : singlePrecision); | ||||
| processor->setPlayConfigDetails (processor->getMainBusNumInputChannels(), | |||||
| processor->getMainBusNumOutputChannels(), | |||||
| newSampleRate, newBlockSize); | |||||
| // AudioProcessorGraph currently does not support processors with multiple buses | |||||
| jassert (processor->getMainBusNumInputChannels() == processor->getTotalNumInputChannels() | |||||
| && processor->getMainBusNumOutputChannels() == processor->getTotalNumOutputChannels()); | |||||
| processor->setRateAndBufferSizeDetails (newSampleRate, newBlockSize); | |||||
| processor->prepareToPlay (newSampleRate, newBlockSize); | processor->prepareToPlay (newSampleRate, newBlockSize); | ||||
| } | } | ||||
| } | } | ||||
| @@ -1035,7 +1037,7 @@ struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers | |||||
| //============================================================================== | //============================================================================== | ||||
| AudioProcessorGraph::AudioProcessorGraph() | AudioProcessorGraph::AudioProcessorGraph() | ||||
| : lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), | : lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), | ||||
| currentMidiInputBuffer (nullptr) | |||||
| currentMidiInputBuffer (nullptr), isPrepared (false) | |||||
| { | { | ||||
| } | } | ||||
| @@ -1102,7 +1104,9 @@ AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const n | |||||
| Node* const n = new Node (nodeId, newProcessor); | Node* const n = new Node (nodeId, newProcessor); | ||||
| nodes.add (n); | nodes.add (n); | ||||
| triggerAsyncUpdate(); | |||||
| if (isPrepared) | |||||
| triggerAsyncUpdate(); | |||||
| n->setParentGraph (this); | n->setParentGraph (this); | ||||
| return n; | return n; | ||||
| @@ -1117,7 +1121,9 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) | |||||
| if (nodes.getUnchecked(i)->nodeId == nodeId) | if (nodes.getUnchecked(i)->nodeId == nodeId) | ||||
| { | { | ||||
| nodes.remove (i); | nodes.remove (i); | ||||
| triggerAsyncUpdate(); | |||||
| if (isPrepared) | |||||
| triggerAsyncUpdate(); | |||||
| return true; | return true; | ||||
| } | } | ||||
| @@ -1126,6 +1132,15 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) | |||||
| return false; | return false; | ||||
| } | } | ||||
| bool AudioProcessorGraph::removeNode (Node* node) | |||||
| { | |||||
| if (node != nullptr) | |||||
| return removeNode (node->nodeId); | |||||
| jassertfalse; | |||||
| return false; | |||||
| } | |||||
| //============================================================================== | //============================================================================== | ||||
| const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, | const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, | ||||
| const int sourceChannelIndex, | const int sourceChannelIndex, | ||||
| @@ -1168,14 +1183,14 @@ bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId, | |||||
| const Node* const source = getNodeForId (sourceNodeId); | const Node* const source = getNodeForId (sourceNodeId); | ||||
| if (source == nullptr | if (source == nullptr | ||||
| || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getMainBusNumOutputChannels()) | |||||
| || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getTotalNumOutputChannels()) | |||||
| || (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) | || (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) | ||||
| return false; | return false; | ||||
| const Node* const dest = getNodeForId (destNodeId); | const Node* const dest = getNodeForId (destNodeId); | ||||
| if (dest == nullptr | if (dest == nullptr | ||||
| || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getMainBusNumInputChannels()) | |||||
| || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getTotalNumInputChannels()) | |||||
| || (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) | || (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) | ||||
| return false; | return false; | ||||
| @@ -1194,14 +1209,19 @@ bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId, | |||||
| GraphRenderingOps::ConnectionSorter sorter; | GraphRenderingOps::ConnectionSorter sorter; | ||||
| connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex, | connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex, | ||||
| destNodeId, destChannelIndex)); | destNodeId, destChannelIndex)); | ||||
| triggerAsyncUpdate(); | |||||
| if (isPrepared) | |||||
| triggerAsyncUpdate(); | |||||
| return true; | return true; | ||||
| } | } | ||||
| void AudioProcessorGraph::removeConnection (const int index) | void AudioProcessorGraph::removeConnection (const int index) | ||||
| { | { | ||||
| connections.remove (index); | connections.remove (index); | ||||
| triggerAsyncUpdate(); | |||||
| if (isPrepared) | |||||
| triggerAsyncUpdate(); | |||||
| } | } | ||||
| bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, | bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, | ||||
| @@ -1253,9 +1273,9 @@ bool AudioProcessorGraph::isConnectionLegal (const Connection* const c) const | |||||
| return source != nullptr | return source != nullptr | ||||
| && dest != nullptr | && dest != nullptr | ||||
| && (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getMainBusNumOutputChannels()) | |||||
| && (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getTotalNumOutputChannels()) | |||||
| : source->processor->producesMidi()) | : source->processor->producesMidi()) | ||||
| && (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getMainBusNumInputChannels()) | |||||
| && (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getTotalNumInputChannels()) | |||||
| : dest->processor->acceptsMidi()); | : dest->processor->acceptsMidi()); | ||||
| } | } | ||||
| @@ -1383,6 +1403,8 @@ void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSam | |||||
| clearRenderingSequence(); | clearRenderingSequence(); | ||||
| buildRenderingSequence(); | buildRenderingSequence(); | ||||
| isPrepared = true; | |||||
| } | } | ||||
| bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | ||||
| @@ -1392,6 +1414,8 @@ bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | |||||
| void AudioProcessorGraph::releaseResources() | void AudioProcessorGraph::releaseResources() | ||||
| { | { | ||||
| isPrepared = false; | |||||
| for (int i = 0; i < nodes.size(); ++i) | for (int i = 0; i < nodes.size(); ++i) | ||||
| nodes.getUnchecked(i)->unprepare(); | nodes.getUnchecked(i)->unprepare(); | ||||
| @@ -1516,13 +1540,13 @@ void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (Plugin | |||||
| d.version = "1.0"; | d.version = "1.0"; | ||||
| d.isInstrument = false; | d.isInstrument = false; | ||||
| d.numInputChannels = getMainBusNumInputChannels(); | |||||
| d.numInputChannels = getTotalNumInputChannels(); | |||||
| if (type == audioOutputNode && graph != nullptr) | if (type == audioOutputNode && graph != nullptr) | ||||
| d.numInputChannels = graph->getMainBusNumInputChannels(); | |||||
| d.numInputChannels = graph->getTotalNumInputChannels(); | |||||
| d.numOutputChannels = getMainBusNumOutputChannels(); | |||||
| d.numOutputChannels = getTotalNumOutputChannels(); | |||||
| if (type == audioInputNode && graph != nullptr) | if (type == audioInputNode && graph != nullptr) | ||||
| d.numOutputChannels = graph->getMainBusNumOutputChannels(); | |||||
| d.numOutputChannels = graph->getTotalNumOutputChannels(); | |||||
| } | } | ||||
| void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) | void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) | ||||
| @@ -1639,8 +1663,8 @@ void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorG | |||||
| if (graph != nullptr) | if (graph != nullptr) | ||||
| { | { | ||||
| setPlayConfigDetails (type == audioOutputNode ? graph->getMainBusNumOutputChannels() : 0, | |||||
| type == audioInputNode ? graph->getMainBusNumInputChannels() : 0, | |||||
| setPlayConfigDetails (type == audioOutputNode ? graph->getTotalNumOutputChannels() : 0, | |||||
| type == audioInputNode ? graph->getTotalNumInputChannels() : 0, | |||||
| getSampleRate(), | getSampleRate(), | ||||
| getBlockSize()); | getBlockSize()); | ||||
| @@ -183,6 +183,12 @@ public: | |||||
| */ | */ | ||||
| bool removeNode (uint32 nodeId); | bool removeNode (uint32 nodeId); | ||||
| /** Deletes a node within the graph which has the specified ID. | |||||
| This will also delete any connections that are attached to this node. | |||||
| */ | |||||
| bool removeNode (Node* node); | |||||
| //============================================================================== | //============================================================================== | ||||
| /** Returns the number of connections in the graph. */ | /** Returns the number of connections in the graph. */ | ||||
| int getNumConnections() const { return connections.size(); } | int getNumConnections() const { return connections.size(); } | ||||
| @@ -390,6 +396,8 @@ private: | |||||
| MidiBuffer* currentMidiInputBuffer; | MidiBuffer* currentMidiInputBuffer; | ||||
| MidiBuffer currentMidiOutputBuffer; | MidiBuffer currentMidiOutputBuffer; | ||||
| bool isPrepared; | |||||
| void handleAsyncUpdate() override; | void handleAsyncUpdate() override; | ||||
| void clearRenderingSequence(); | void clearRenderingSequence(); | ||||
| void buildRenderingSequence(); | void buildRenderingSequence(); | ||||
| @@ -105,6 +105,16 @@ private: | |||||
| } | } | ||||
| } | } | ||||
| void startedDragging() override | |||||
| { | |||||
| owner.beginParameterChangeGesture(index); | |||||
| } | |||||
| void stoppedDragging() override | |||||
| { | |||||
| owner.endParameterChangeGesture(index); | |||||
| } | |||||
| String getTextFromValue (double /*value*/) override | String getTextFromValue (double /*value*/) override | ||||
| { | { | ||||
| return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); | return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); | ||||
| @@ -118,7 +118,7 @@ public: | |||||
| given identifier string. | given identifier string. | ||||
| Note that this isn't quite as simple as them just calling createIdentifierString() | Note that this isn't quite as simple as them just calling createIdentifierString() | ||||
| and comparing the strings, because the identifers can differ (thanks to shell plug-ins). | |||||
| and comparing the strings, because the identifiers can differ (thanks to shell plug-ins). | |||||
| */ | */ | ||||
| bool matchesIdentifierString (const String& identifierString) const; | bool matchesIdentifierString (const String& identifierString) const; | ||||
| @@ -27,6 +27,8 @@ KnownPluginList::~KnownPluginList() {} | |||||
| void KnownPluginList::clear() | void KnownPluginList::clear() | ||||
| { | { | ||||
| ScopedLock lock (typesArrayLock); | |||||
| if (types.size() > 0) | if (types.size() > 0) | ||||
| { | { | ||||
| types.clear(); | types.clear(); | ||||
| @@ -36,6 +38,8 @@ void KnownPluginList::clear() | |||||
| PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const | PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const | ||||
| { | { | ||||
| ScopedLock lock (typesArrayLock); | |||||
| for (int i = 0; i < types.size(); ++i) | for (int i = 0; i < types.size(); ++i) | ||||
| if (types.getUnchecked(i)->fileOrIdentifier == fileOrIdentifier) | if (types.getUnchecked(i)->fileOrIdentifier == fileOrIdentifier) | ||||
| return types.getUnchecked(i); | return types.getUnchecked(i); | ||||
| @@ -45,6 +49,8 @@ PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifi | |||||
| PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& identifierString) const | PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& identifierString) const | ||||
| { | { | ||||
| ScopedLock lock (typesArrayLock); | |||||
| for (int i = 0; i < types.size(); ++i) | for (int i = 0; i < types.size(); ++i) | ||||
| if (types.getUnchecked(i)->matchesIdentifierString (identifierString)) | if (types.getUnchecked(i)->matchesIdentifierString (identifierString)) | ||||
| return types.getUnchecked(i); | return types.getUnchecked(i); | ||||
| @@ -54,27 +60,37 @@ PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& id | |||||
| bool KnownPluginList::addType (const PluginDescription& type) | bool KnownPluginList::addType (const PluginDescription& type) | ||||
| { | { | ||||
| for (int i = types.size(); --i >= 0;) | |||||
| { | { | ||||
| if (types.getUnchecked(i)->isDuplicateOf (type)) | |||||
| ScopedLock lock (typesArrayLock); | |||||
| for (int i = types.size(); --i >= 0;) | |||||
| { | { | ||||
| // strange - found a duplicate plugin with different info.. | |||||
| jassert (types.getUnchecked(i)->name == type.name); | |||||
| jassert (types.getUnchecked(i)->isInstrument == type.isInstrument); | |||||
| if (types.getUnchecked(i)->isDuplicateOf (type)) | |||||
| { | |||||
| // strange - found a duplicate plugin with different info.. | |||||
| jassert (types.getUnchecked(i)->name == type.name); | |||||
| jassert (types.getUnchecked(i)->isInstrument == type.isInstrument); | |||||
| *types.getUnchecked(i) = type; | |||||
| return false; | |||||
| *types.getUnchecked(i) = type; | |||||
| return false; | |||||
| } | |||||
| } | } | ||||
| types.insert (0, new PluginDescription (type)); | |||||
| } | } | ||||
| types.insert (0, new PluginDescription (type)); | |||||
| sendChangeMessage(); | sendChangeMessage(); | ||||
| return true; | return true; | ||||
| } | } | ||||
| void KnownPluginList::removeType (const int index) | void KnownPluginList::removeType (const int index) | ||||
| { | { | ||||
| types.remove (index); | |||||
| { | |||||
| ScopedLock lock (typesArrayLock); | |||||
| types.remove (index); | |||||
| } | |||||
| sendChangeMessage(); | sendChangeMessage(); | ||||
| } | } | ||||
| @@ -84,6 +100,8 @@ bool KnownPluginList::isListingUpToDate (const String& fileOrIdentifier, | |||||
| if (getTypeForFile (fileOrIdentifier) == nullptr) | if (getTypeForFile (fileOrIdentifier) == nullptr) | ||||
| return false; | return false; | ||||
| ScopedLock lock (typesArrayLock); | |||||
| for (int i = types.size(); --i >= 0;) | for (int i = types.size(); --i >= 0;) | ||||
| { | { | ||||
| const PluginDescription* const d = types.getUnchecked(i); | const PluginDescription* const d = types.getUnchecked(i); | ||||
| @@ -103,7 +121,7 @@ void KnownPluginList::setCustomScanner (CustomScanner* newScanner) | |||||
| bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | ||||
| const bool dontRescanIfAlreadyInList, | const bool dontRescanIfAlreadyInList, | ||||
| OwnedArray <PluginDescription>& typesFound, | |||||
| OwnedArray<PluginDescription>& typesFound, | |||||
| AudioPluginFormat& format) | AudioPluginFormat& format) | ||||
| { | { | ||||
| const ScopedLock sl (scanLock); | const ScopedLock sl (scanLock); | ||||
| @@ -113,6 +131,8 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||||
| { | { | ||||
| bool needsRescanning = false; | bool needsRescanning = false; | ||||
| ScopedLock lock (typesArrayLock); | |||||
| for (int i = types.size(); --i >= 0;) | for (int i = types.size(); --i >= 0;) | ||||
| { | { | ||||
| const PluginDescription* const d = types.getUnchecked(i); | const PluginDescription* const d = types.getUnchecked(i); | ||||
| @@ -133,7 +153,7 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||||
| if (blacklist.contains (fileOrIdentifier)) | if (blacklist.contains (fileOrIdentifier)) | ||||
| return false; | return false; | ||||
| OwnedArray <PluginDescription> found; | |||||
| OwnedArray<PluginDescription> found; | |||||
| { | { | ||||
| const ScopedUnlock sl2 (scanLock); | const ScopedUnlock sl2 (scanLock); | ||||
| @@ -163,7 +183,7 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||||
| void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager, | void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager, | ||||
| const StringArray& files, | const StringArray& files, | ||||
| OwnedArray <PluginDescription>& typesFound) | |||||
| OwnedArray<PluginDescription>& typesFound) | |||||
| { | { | ||||
| for (int i = 0; i < files.size(); ++i) | for (int i = 0; i < files.size(); ++i) | ||||
| { | { | ||||
| @@ -298,12 +318,17 @@ void KnownPluginList::sort (const SortMethod method, bool forwards) | |||||
| if (method != defaultOrder) | if (method != defaultOrder) | ||||
| { | { | ||||
| Array<PluginDescription*> oldOrder, newOrder; | Array<PluginDescription*> oldOrder, newOrder; | ||||
| oldOrder.addArray (types); | |||||
| PluginSorter sorter (method, forwards); | |||||
| types.sort (sorter, true); | |||||
| { | |||||
| ScopedLock lock (typesArrayLock); | |||||
| oldOrder.addArray (types); | |||||
| newOrder.addArray (types); | |||||
| PluginSorter sorter (method, forwards); | |||||
| types.sort (sorter, true); | |||||
| newOrder.addArray (types); | |||||
| } | |||||
| if (oldOrder != newOrder) | if (oldOrder != newOrder) | ||||
| sendChangeMessage(); | sendChangeMessage(); | ||||
| @@ -315,8 +340,12 @@ XmlElement* KnownPluginList::createXml() const | |||||
| { | { | ||||
| XmlElement* const e = new XmlElement ("KNOWNPLUGINS"); | XmlElement* const e = new XmlElement ("KNOWNPLUGINS"); | ||||
| for (int i = types.size(); --i >= 0;) | |||||
| e->prependChildElement (types.getUnchecked(i)->createXml()); | |||||
| { | |||||
| ScopedLock lock (typesArrayLock); | |||||
| for (int i = types.size(); --i >= 0;) | |||||
| e->prependChildElement (types.getUnchecked(i)->createXml()); | |||||
| } | |||||
| for (int i = 0; i < blacklist.size(); ++i) | for (int i = 0; i < blacklist.size(); ++i) | ||||
| e->createNewChildElement ("BLACKLISTED")->setAttribute ("id", blacklist[i]); | e->createNewChildElement ("BLACKLISTED")->setAttribute ("id", blacklist[i]); | ||||
| @@ -348,7 +377,7 @@ struct PluginTreeUtils | |||||
| { | { | ||||
| enum { menuIdBase = 0x324503f4 }; | enum { menuIdBase = 0x324503f4 }; | ||||
| static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array <PluginDescription*>& allPlugins) | |||||
| static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array<PluginDescription*>& allPlugins) | |||||
| { | { | ||||
| for (int i = 0; i < allPlugins.size(); ++i) | for (int i = 0; i < allPlugins.size(); ++i) | ||||
| { | { | ||||
| @@ -392,7 +421,7 @@ struct PluginTreeUtils | |||||
| } | } | ||||
| static void buildTreeByCategory (KnownPluginList::PluginTree& tree, | static void buildTreeByCategory (KnownPluginList::PluginTree& tree, | ||||
| const Array <PluginDescription*>& sorted, | |||||
| const Array<PluginDescription*>& sorted, | |||||
| const KnownPluginList::SortMethod sortMethod) | const KnownPluginList::SortMethod sortMethod) | ||||
| { | { | ||||
| String lastType; | String lastType; | ||||
| @@ -475,15 +504,21 @@ struct PluginTreeUtils | |||||
| return false; | return false; | ||||
| } | } | ||||
| static void addToMenu (const KnownPluginList::PluginTree& tree, PopupMenu& m, const OwnedArray <PluginDescription>& allPlugins) | |||||
| static bool addToMenu (const KnownPluginList::PluginTree& tree, PopupMenu& m, | |||||
| const OwnedArray<PluginDescription>& allPlugins, | |||||
| const String& currentlyTickedPluginID) | |||||
| { | { | ||||
| bool isTicked = false; | |||||
| for (int i = 0; i < tree.subFolders.size(); ++i) | for (int i = 0; i < tree.subFolders.size(); ++i) | ||||
| { | { | ||||
| const KnownPluginList::PluginTree& sub = *tree.subFolders.getUnchecked(i); | const KnownPluginList::PluginTree& sub = *tree.subFolders.getUnchecked(i); | ||||
| PopupMenu subMenu; | PopupMenu subMenu; | ||||
| addToMenu (sub, subMenu, allPlugins); | |||||
| m.addSubMenu (sub.folder, subMenu); | |||||
| const bool isItemTicked = addToMenu (sub, subMenu, allPlugins, currentlyTickedPluginID); | |||||
| isTicked = isTicked || isItemTicked; | |||||
| m.addSubMenu (sub.folder, subMenu, true, nullptr, isItemTicked, 0); | |||||
| } | } | ||||
| for (int i = 0; i < tree.plugins.size(); ++i) | for (int i = 0; i < tree.plugins.size(); ++i) | ||||
| @@ -495,16 +530,22 @@ struct PluginTreeUtils | |||||
| if (containsDuplicateNames (tree.plugins, name)) | if (containsDuplicateNames (tree.plugins, name)) | ||||
| name << " (" << plugin->pluginFormatName << ')'; | name << " (" << plugin->pluginFormatName << ')'; | ||||
| m.addItem (allPlugins.indexOf (plugin) + menuIdBase, name, true, false); | |||||
| const bool isItemTicked = plugin->matchesIdentifierString (currentlyTickedPluginID); | |||||
| isTicked = isTicked || isItemTicked; | |||||
| m.addItem (allPlugins.indexOf (plugin) + menuIdBase, name, true, isItemTicked); | |||||
| } | } | ||||
| return isTicked; | |||||
| } | } | ||||
| }; | }; | ||||
| KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortMethod) const | KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortMethod) const | ||||
| { | { | ||||
| Array <PluginDescription*> sorted; | |||||
| Array<PluginDescription*> sorted; | |||||
| { | { | ||||
| ScopedLock lock (typesArrayLock); | |||||
| PluginSorter sorter (sortMethod, true); | PluginSorter sorter (sortMethod, true); | ||||
| for (int i = 0; i < types.size(); ++i) | for (int i = 0; i < types.size(); ++i) | ||||
| @@ -531,10 +572,11 @@ KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortM | |||||
| } | } | ||||
| //============================================================================== | //============================================================================== | ||||
| void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod) const | |||||
| void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod, | |||||
| const String& currentlyTickedPluginID) const | |||||
| { | { | ||||
| ScopedPointer<PluginTree> tree (createTree (sortMethod)); | ScopedPointer<PluginTree> tree (createTree (sortMethod)); | ||||
| PluginTreeUtils::addToMenu (*tree, menu, types); | |||||
| PluginTreeUtils::addToMenu (*tree, menu, types, currentlyTickedPluginID); | |||||
| } | } | ||||
| int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const | int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const | ||||
| @@ -148,7 +148,8 @@ public: | |||||
| Use getIndexChosenByMenu() to find out the type that was chosen. | Use getIndexChosenByMenu() to find out the type that was chosen. | ||||
| */ | */ | ||||
| void addToMenu (PopupMenu& menu, SortMethod sortMethod) const; | |||||
| void addToMenu (PopupMenu& menu, SortMethod sortMethod, | |||||
| const String& currentlyTickedPluginID = String()) const; | |||||
| /** Converts a menu item index that has been chosen into its index in this list. | /** Converts a menu item index that has been chosen into its index in this list. | ||||
| Returns -1 if it's not an ID that was used. | Returns -1 if it's not an ID that was used. | ||||
| @@ -215,7 +216,7 @@ private: | |||||
| OwnedArray<PluginDescription> types; | OwnedArray<PluginDescription> types; | ||||
| StringArray blacklist; | StringArray blacklist; | ||||
| ScopedPointer<CustomScanner> scanner; | ScopedPointer<CustomScanner> scanner; | ||||
| CriticalSection scanLock; | |||||
| CriticalSection scanLock, typesArrayLock; | |||||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList) | JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList) | ||||
| }; | }; | ||||
| @@ -34,15 +34,17 @@ PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, | |||||
| AudioPluginFormat& formatToLookFor, | AudioPluginFormat& formatToLookFor, | ||||
| FileSearchPath directoriesToSearch, | FileSearchPath directoriesToSearch, | ||||
| const bool recursive, | const bool recursive, | ||||
| const File& deadMansPedal) | |||||
| const File& deadMansPedal, | |||||
| bool allowPluginsWhichRequireAsynchronousInstantiation) | |||||
| : list (listToAddTo), | : list (listToAddTo), | ||||
| format (formatToLookFor), | format (formatToLookFor), | ||||
| deadMansPedalFile (deadMansPedal), | deadMansPedalFile (deadMansPedal), | ||||
| progress (0) | |||||
| progress (0), | |||||
| allowAsync (allowPluginsWhichRequireAsynchronousInstantiation) | |||||
| { | { | ||||
| directoriesToSearch.removeRedundantPaths(); | directoriesToSearch.removeRedundantPaths(); | ||||
| filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive); | |||||
| filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive, allowAsync); | |||||
| // If any plugins have crashed recently when being loaded, move them to the | // If any plugins have crashed recently when being loaded, move them to the | ||||
| // end of the list to give the others a chance to load correctly.. | // end of the list to give the others a chance to load correctly.. | ||||
| @@ -44,12 +44,11 @@ public: | |||||
| @param formatToLookFor this is the type of format that you want to look for | @param formatToLookFor this is the type of format that you want to look for | ||||
| @param directoriesToSearch the path to search | @param directoriesToSearch the path to search | ||||
| @param searchRecursively true to search recursively | @param searchRecursively true to search recursively | ||||
| @param deadMansPedalFile if this isn't File::nonexistent, then it will | |||||
| be used as a file to store the names of any plugins | |||||
| that crash during initialisation. If there are | |||||
| any plugins listed in it, then these will always | |||||
| be scanned after all other possible files have | |||||
| been tried - in this way, even if there's a few | |||||
| @param deadMansPedalFile if this isn't File(), then it will be used as a file | |||||
| to store the names of any plugins that crash during | |||||
| initialisation. If there are any plugins listed in it, | |||||
| then these will always be scanned after all other possible | |||||
| files have been tried - in this way, even if there's a few | |||||
| dodgy plugins in your path, then a couple of rescans | dodgy plugins in your path, then a couple of rescans | ||||
| will still manage to find all the proper plugins. | will still manage to find all the proper plugins. | ||||
| It's probably best to choose a file in the user's | It's probably best to choose a file in the user's | ||||
| @@ -57,12 +56,16 @@ public: | |||||
| settings file) for this. The file format it uses | settings file) for this. The file format it uses | ||||
| is just a list of filenames of the modules that | is just a list of filenames of the modules that | ||||
| failed. | failed. | ||||
| @param allowPluginsWhichRequireAsynchronousInstantiation | |||||
| If this is false then the scanner will exclude plug-ins | |||||
| asynchronous creation - such as AUv3 plug-ins. | |||||
| */ | */ | ||||
| PluginDirectoryScanner (KnownPluginList& listToAddResultsTo, | PluginDirectoryScanner (KnownPluginList& listToAddResultsTo, | ||||
| AudioPluginFormat& formatToLookFor, | AudioPluginFormat& formatToLookFor, | ||||
| FileSearchPath directoriesToSearch, | FileSearchPath directoriesToSearch, | ||||
| bool searchRecursively, | bool searchRecursively, | ||||
| const File& deadMansPedalFile); | |||||
| const File& deadMansPedalFile, | |||||
| bool allowPluginsWhichRequireAsynchronousInstantiation = false); | |||||
| /** Destructor. */ | /** Destructor. */ | ||||
| ~PluginDirectoryScanner(); | ~PluginDirectoryScanner(); | ||||
| @@ -116,6 +119,7 @@ private: | |||||
| StringArray failedFiles; | StringArray failedFiles; | ||||
| Atomic<int> nextIndex; | Atomic<int> nextIndex; | ||||
| float progress; | float progress; | ||||
| bool allowAsync; | |||||
| void updateProgress(); | void updateProgress(); | ||||
| void setDeadMansPedalFile (const StringArray& newContents); | void setDeadMansPedalFile (const StringArray& newContents); | ||||
| @@ -123,13 +123,15 @@ public: | |||||
| //============================================================================== | //============================================================================== | ||||
| PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit, | PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit, | ||||
| const File& deadMansPedal, PropertiesFile* const props) | |||||
| const File& deadMansPedal, PropertiesFile* const props, | |||||
| bool allowPluginsWhichRequireAsynchronousInstantiation) | |||||
| : formatManager (manager), | : formatManager (manager), | ||||
| list (listToEdit), | list (listToEdit), | ||||
| deadMansPedalFile (deadMansPedal), | deadMansPedalFile (deadMansPedal), | ||||
| optionsButton ("Options..."), | optionsButton ("Options..."), | ||||
| propertiesToUse (props), | propertiesToUse (props), | ||||
| numThreads (0) | |||||
| allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), | |||||
| numThreads (allowAsync ? 1 : 0) | |||||
| { | { | ||||
| tableModel = new TableModel (*this, listToEdit); | tableModel = new TableModel (*this, listToEdit); | ||||
| @@ -331,18 +333,25 @@ class PluginListComponent::Scanner : private Timer | |||||
| { | { | ||||
| public: | public: | ||||
| Scanner (PluginListComponent& plc, AudioPluginFormat& format, PropertiesFile* properties, | Scanner (PluginListComponent& plc, AudioPluginFormat& format, PropertiesFile* properties, | ||||
| int threads, const String& title, const String& text) | |||||
| bool allowPluginsWhichRequireAsynchronousInstantiation, int threads, | |||||
| const String& title, const String& text) | |||||
| : owner (plc), formatToScan (format), propertiesToUse (properties), | : owner (plc), formatToScan (format), propertiesToUse (properties), | ||||
| pathChooserWindow (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon), | |||||
| pathChooserWindow (TRANS("Select folders to scan..."), String(), AlertWindow::NoIcon), | |||||
| progressWindow (title, text, AlertWindow::NoIcon), | progressWindow (title, text, AlertWindow::NoIcon), | ||||
| progress (0.0), numThreads (threads), finished (false) | |||||
| progress (0.0), numThreads (threads), allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), | |||||
| finished (false) | |||||
| { | { | ||||
| FileSearchPath path (formatToScan.getDefaultLocationsToSearch()); | FileSearchPath path (formatToScan.getDefaultLocationsToSearch()); | ||||
| // You need to use at least one thread when scanning plug-ins asynchronously | |||||
| jassert (! allowAsync || (numThreads > 0)); | |||||
| if (path.getNumPaths() > 0) // if the path is empty, then paths aren't used for this format. | if (path.getNumPaths() > 0) // if the path is empty, then paths aren't used for this format. | ||||
| { | { | ||||
| #if ! JUCE_IOS | |||||
| if (propertiesToUse != nullptr) | if (propertiesToUse != nullptr) | ||||
| path = getLastSearchPath (*propertiesToUse, formatToScan); | path = getLastSearchPath (*propertiesToUse, formatToScan); | ||||
| #endif | |||||
| pathList.setSize (500, 300); | pathList.setSize (500, 300); | ||||
| pathList.setPath (path); | pathList.setPath (path); | ||||
| @@ -381,7 +390,7 @@ private: | |||||
| String pluginBeingScanned; | String pluginBeingScanned; | ||||
| double progress; | double progress; | ||||
| int numThreads; | int numThreads; | ||||
| bool finished; | |||||
| bool allowAsync, finished; | |||||
| ScopedPointer<ThreadPool> pool; | ScopedPointer<ThreadPool> pool; | ||||
| static void startScanCallback (int result, AlertWindow* alert, Scanner* scanner) | static void startScanCallback (int result, AlertWindow* alert, Scanner* scanner) | ||||
| @@ -413,7 +422,7 @@ private: | |||||
| + TRANS ("Are you sure you want to scan the folder \"XYZ\"?") | + TRANS ("Are you sure you want to scan the folder \"XYZ\"?") | ||||
| .replace ("XYZ", f.getFullPathName()), | .replace ("XYZ", f.getFullPathName()), | ||||
| TRANS ("Scan"), | TRANS ("Scan"), | ||||
| String::empty, | |||||
| String(), | |||||
| nullptr, | nullptr, | ||||
| ModalCallbackFunction::create (warnAboutStupidPathsCallback, this)); | ModalCallbackFunction::create (warnAboutStupidPathsCallback, this)); | ||||
| return; | return; | ||||
| @@ -465,7 +474,7 @@ private: | |||||
| pathChooserWindow.setVisible (false); | pathChooserWindow.setVisible (false); | ||||
| scanner = new PluginDirectoryScanner (owner.list, formatToScan, pathList.getPath(), | scanner = new PluginDirectoryScanner (owner.list, formatToScan, pathList.getPath(), | ||||
| true, owner.deadMansPedalFile); | |||||
| true, owner.deadMansPedalFile, allowAsync); | |||||
| if (propertiesToUse != nullptr) | if (propertiesToUse != nullptr) | ||||
| { | { | ||||
| @@ -545,7 +554,7 @@ private: | |||||
| void PluginListComponent::scanFor (AudioPluginFormat& format) | void PluginListComponent::scanFor (AudioPluginFormat& format) | ||||
| { | { | ||||
| currentScanner = new Scanner (*this, format, propertiesToUse, numThreads, | |||||
| currentScanner = new Scanner (*this, format, propertiesToUse, allowAsync, numThreads, | |||||
| dialogTitle.isNotEmpty() ? dialogTitle : TRANS("Scanning for plug-ins..."), | dialogTitle.isNotEmpty() ? dialogTitle : TRANS("Scanning for plug-ins..."), | ||||
| dialogText.isNotEmpty() ? dialogText : TRANS("Searching for all possible plug-in files...")); | dialogText.isNotEmpty() ? dialogText : TRANS("Searching for all possible plug-in files...")); | ||||
| } | } | ||||
| @@ -47,7 +47,8 @@ public: | |||||
| PluginListComponent (AudioPluginFormatManager& formatManager, | PluginListComponent (AudioPluginFormatManager& formatManager, | ||||
| KnownPluginList& listToRepresent, | KnownPluginList& listToRepresent, | ||||
| const File& deadMansPedalFile, | const File& deadMansPedalFile, | ||||
| PropertiesFile* propertiesToUse); | |||||
| PropertiesFile* propertiesToUse, | |||||
| bool allowPluginsWhichRequireAsynchronousInstantiation = false); | |||||
| /** Destructor. */ | /** Destructor. */ | ||||
| ~PluginListComponent(); | ~PluginListComponent(); | ||||
| @@ -60,8 +61,10 @@ public: | |||||
| const String& textForProgressWindowDescription); | const String& textForProgressWindowDescription); | ||||
| /** Sets how many threads to simultaneously scan for plugins. | /** Sets how many threads to simultaneously scan for plugins. | ||||
| If this is 0, then all scanning happens on the message thread (this is the default) | |||||
| */ | |||||
| If this is 0, then all scanning happens on the message thread (this is the default when | |||||
| allowPluginsWhichRequireAsynchronousInstantiation is false). If | |||||
| allowPluginsWhichRequireAsynchronousInstantiation is true then numThreads must not | |||||
| be zero (it is one by default). */ | |||||
| void setNumberOfThreadsForScanning (int numThreads); | void setNumberOfThreadsForScanning (int numThreads); | ||||
| /** Returns the last search path stored in a given properties file for the specified format. */ | /** Returns the last search path stored in a given properties file for the specified format. */ | ||||
| @@ -96,6 +99,7 @@ private: | |||||
| TextButton optionsButton; | TextButton optionsButton; | ||||
| PropertiesFile* propertiesToUse; | PropertiesFile* propertiesToUse; | ||||
| String dialogTitle, dialogText; | String dialogTitle, dialogText; | ||||
| bool allowAsync; | |||||
| int numThreads; | int numThreads; | ||||
| class TableModel; | class TableModel; | ||||