| @@ -2,8 +2,8 @@ | |||
| set -e | |||
| JUCE_MODULES_DIR="/home/falktx/Projects/FOSS/GIT-mine/DISTRHO-Ports/libs/juce/source/modules/" | |||
| CARLA_MODULES_DIR="/home/falktx/Projects/FOSS/GIT-mine/Carla/source/modules/" | |||
| JUCE_MODULES_DIR="/Shared/Personal/FOSS/GIT/DISTRHO/DISTRHO-Ports/libs/juce/source/modules/" | |||
| CARLA_MODULES_DIR="/home/falktx/FOSS/GIT-mine/falkTX/Carla/source/modules/" | |||
| MODULES=("juce_audio_basics juce_audio_devices juce_audio_formats juce_audio_processors juce_core juce_data_structures juce_events juce_graphics juce_gui_basics juce_gui_extra") | |||
| @@ -60,6 +60,7 @@ | |||
| // misc | |||
| #define JUCE_DISABLE_JUCE_VERSION_PRINTING 1 | |||
| #define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1 | |||
| #define JUCE_STANDALONE_APPLICATION 0 | |||
| #define JUCE_STRING_UTF_TYPE 8 | |||
| #define JUCE_USE_VFORK 1 | |||
| @@ -0,0 +1,336 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| AudioChannelSet::AudioChannelSet (uint32 c) : channels (c) {} | |||
| bool AudioChannelSet::operator== (const AudioChannelSet& other) const noexcept { return channels == other.channels; } | |||
| bool AudioChannelSet::operator!= (const AudioChannelSet& other) const noexcept { return channels != other.channels; } | |||
| bool AudioChannelSet::operator< (const AudioChannelSet& other) const noexcept { return channels < other.channels; } | |||
| String AudioChannelSet::getChannelTypeName (AudioChannelSet::ChannelType type) | |||
| { | |||
| if (type >= discreteChannel0) | |||
| return String ("Discrete ") + String (type - discreteChannel0 + 1); | |||
| switch (type) | |||
| { | |||
| case left: return NEEDS_TRANS("Left"); | |||
| case right: return NEEDS_TRANS("Right"); | |||
| case centre: return NEEDS_TRANS("Centre"); | |||
| case LFE: return NEEDS_TRANS("LFE"); | |||
| case leftSurround: return NEEDS_TRANS("Left Surround"); | |||
| case rightSurround: return NEEDS_TRANS("Right Surround"); | |||
| case leftCentre: return NEEDS_TRANS("Left Centre"); | |||
| case rightCentre: return NEEDS_TRANS("Right Centre"); | |||
| case centreSurround: return NEEDS_TRANS("Centre Surround"); | |||
| case leftSurroundRear: return NEEDS_TRANS("Left Surround Rear"); | |||
| case rightSurroundRear: return NEEDS_TRANS("Right Surround Rear"); | |||
| case topMiddle: return NEEDS_TRANS("Top Middle"); | |||
| case topFrontLeft: return NEEDS_TRANS("Top Front Left"); | |||
| case topFrontCentre: return NEEDS_TRANS("Top Front Centre"); | |||
| case topFrontRight: return NEEDS_TRANS("Top Front Right"); | |||
| case topRearLeft: return NEEDS_TRANS("Top Rear Left"); | |||
| case topRearCentre: return NEEDS_TRANS("Top Rear Centre"); | |||
| case topRearRight: return NEEDS_TRANS("Top Rear Right"); | |||
| case wideLeft: return NEEDS_TRANS("Wide Left"); | |||
| case wideRight: return NEEDS_TRANS("Wide Right"); | |||
| case LFE2: return NEEDS_TRANS("LFE 2"); | |||
| case leftSurroundSide: return NEEDS_TRANS ("Left Surround Side"); | |||
| case rightSurroundSide: return NEEDS_TRANS ("Right Surround Side"); | |||
| case ambisonicW: return NEEDS_TRANS("Ambisonic W"); | |||
| case ambisonicX: return NEEDS_TRANS("Ambisonic X"); | |||
| case ambisonicY: return NEEDS_TRANS("Ambisonic Y"); | |||
| case ambisonicZ: return NEEDS_TRANS("Ambisonic Z"); | |||
| default: break; | |||
| } | |||
| return "Unknown"; | |||
| } | |||
| String AudioChannelSet::getAbbreviatedChannelTypeName (AudioChannelSet::ChannelType type) | |||
| { | |||
| if (type >= discreteChannel0) | |||
| return String (type - discreteChannel0 + 1); | |||
| switch (type) | |||
| { | |||
| case left: return "L"; | |||
| case right: return "R"; | |||
| case centre: return "C"; | |||
| case LFE: return "Lfe"; | |||
| case leftSurround: return "Ls"; | |||
| case rightSurround: return "Rs"; | |||
| case leftCentre: return "Lc"; | |||
| case rightCentre: return "Rc"; | |||
| case centreSurround: return "Cs"; | |||
| case leftSurroundRear: return "Lrs"; | |||
| case rightSurroundRear: return "Rrs"; | |||
| case topMiddle: return "Tm"; | |||
| case topFrontLeft: return "Tfl"; | |||
| case topFrontCentre: return "Tfc"; | |||
| case topFrontRight: return "Tfr"; | |||
| case topRearLeft: return "Trl"; | |||
| case topRearCentre: return "Trc"; | |||
| case topRearRight: return "Trr"; | |||
| case wideLeft: return "Wl"; | |||
| case wideRight: return "Wr"; | |||
| case LFE2: return "Lfe2"; | |||
| case leftSurroundSide: return "Lss"; | |||
| case rightSurroundSide: return "Rss"; | |||
| case ambisonicW: return "W"; | |||
| case ambisonicX: return "X"; | |||
| case ambisonicY: return "Y"; | |||
| case ambisonicZ: return "Z"; | |||
| default: break; | |||
| } | |||
| return ""; | |||
| } | |||
| String AudioChannelSet::getSpeakerArrangementAsString() const | |||
| { | |||
| StringArray speakerTypes; | |||
| Array<AudioChannelSet::ChannelType> speakers = getChannelTypes(); | |||
| for (int i = 0; i < speakers.size(); ++i) | |||
| { | |||
| String name = getAbbreviatedChannelTypeName (speakers.getReference (i)); | |||
| if (name.isNotEmpty()) | |||
| speakerTypes.add (name); | |||
| } | |||
| return speakerTypes.joinIntoString (" "); | |||
| } | |||
| String AudioChannelSet::getDescription() const | |||
| { | |||
| if (isDiscreteLayout()) return String ("Discrete #") + String (size()); | |||
| if (*this == disabled()) return "Disabled"; | |||
| if (*this == mono()) return "Mono"; | |||
| if (*this == stereo()) return "Stereo"; | |||
| if (*this == createLCR()) return "LCR"; | |||
| if (*this == createLRS()) return "LRS"; | |||
| if (*this == createLCRS()) return "LCRS"; | |||
| if (*this == create5point0()) return "5.1 Surround"; | |||
| if (*this == create5point1()) return "5.1 Surround (+Lfe)"; | |||
| if (*this == create6point0()) return "6.1 Surround"; | |||
| if (*this == create6point1()) return "6.1 Surround (+Lfe)"; | |||
| if (*this == create6point0Music()) return "6.1 (Music) Surround"; | |||
| if (*this == create6point1Music()) return "6.1 (Music) Surround (+Lfe)"; | |||
| if (*this == create7point0()) return "7.1 Surround"; | |||
| if (*this == create7point1()) return "7.1 Surround (Lfe)"; | |||
| if (*this == create7point0SDDS()) return "7.1 Surround SDDS"; | |||
| if (*this == create7point1SDDS()) return "7.1 Surround SDDS (+Lfe)"; | |||
| if (*this == quadraphonic()) return "Quadraphonic"; | |||
| if (*this == pentagonal()) return "Pentagonal"; | |||
| if (*this == hexagonal()) return "Hexagonal"; | |||
| if (*this == octagonal()) return "Octagonal"; | |||
| if (*this == ambisonic()) return "Ambisonic"; | |||
| return "Unknown"; | |||
| } | |||
| bool AudioChannelSet::isDiscreteLayout() const noexcept | |||
| { | |||
| Array<AudioChannelSet::ChannelType> speakers = getChannelTypes(); | |||
| for (int i = 0; i < speakers.size(); ++i) | |||
| if (speakers.getReference (i) > ambisonicZ) | |||
| return true; | |||
| return false; | |||
| } | |||
| int AudioChannelSet::size() const noexcept | |||
| { | |||
| return channels.countNumberOfSetBits(); | |||
| } | |||
| AudioChannelSet::ChannelType AudioChannelSet::getTypeOfChannel (int index) const noexcept | |||
| { | |||
| int bit = channels.findNextSetBit(0); | |||
| for (int i = 0; i < index && bit >= 0; ++i) | |||
| bit = channels.findNextSetBit (bit + 1); | |||
| return static_cast<ChannelType> (bit); | |||
| } | |||
| int AudioChannelSet::getChannelIndexForType (AudioChannelSet::ChannelType type) const noexcept | |||
| { | |||
| int idx = 0; | |||
| for (int bit = channels.findNextSetBit (0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) | |||
| { | |||
| if (static_cast<ChannelType> (bit) == type) | |||
| return idx; | |||
| idx++; | |||
| } | |||
| return -1; | |||
| } | |||
| Array<AudioChannelSet::ChannelType> AudioChannelSet::getChannelTypes() const | |||
| { | |||
| Array<ChannelType> result; | |||
| for (int bit = channels.findNextSetBit(0); bit >= 0; bit = channels.findNextSetBit (bit + 1)) | |||
| result.add (static_cast<ChannelType> (bit)); | |||
| return result; | |||
| } | |||
| void AudioChannelSet::addChannel (ChannelType newChannel) | |||
| { | |||
| const int bit = static_cast<int> (newChannel); | |||
| jassert (bit >= 0 && bit < 1024); | |||
| channels.setBit (bit); | |||
| } | |||
| void AudioChannelSet::removeChannel (ChannelType newChannel) | |||
| { | |||
| const int bit = static_cast<int> (newChannel); | |||
| jassert (bit >= 0 && bit < 1024); | |||
| channels.clearBit (bit); | |||
| } | |||
| AudioChannelSet AudioChannelSet::disabled() { return AudioChannelSet(); } | |||
| AudioChannelSet AudioChannelSet::mono() { return AudioChannelSet (1u << centre); } | |||
| AudioChannelSet AudioChannelSet::stereo() { return AudioChannelSet ((1u << left) | (1u << right)); } | |||
| AudioChannelSet AudioChannelSet::createLCR() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre)); } | |||
| AudioChannelSet AudioChannelSet::createLRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << surround)); } | |||
| AudioChannelSet AudioChannelSet::createLCRS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << surround)); } | |||
| AudioChannelSet AudioChannelSet::create5point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround)); } | |||
| AudioChannelSet AudioChannelSet::create5point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << LFE)); } | |||
| AudioChannelSet AudioChannelSet::create6point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround)); } | |||
| AudioChannelSet AudioChannelSet::create6point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << centreSurround) | (1u << LFE)); } | |||
| AudioChannelSet AudioChannelSet::create6point0Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide)); } | |||
| AudioChannelSet AudioChannelSet::create6point1Music() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << LFE)); } | |||
| AudioChannelSet AudioChannelSet::create7point0() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } | |||
| AudioChannelSet AudioChannelSet::create7point0SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre)); } | |||
| AudioChannelSet AudioChannelSet::create7point1() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundSide) | (1u << rightSurroundSide) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << LFE)); } | |||
| AudioChannelSet AudioChannelSet::create7point1SDDS() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurround) | (1u << rightSurround) | (1u << leftCentre) | (1u << rightCentre) | (1u << LFE)); } | |||
| AudioChannelSet AudioChannelSet::ambisonic() { return AudioChannelSet ((1u << ambisonicW) | (1u << ambisonicX) | (1u << ambisonicY) | (1u << ambisonicZ)); } | |||
| AudioChannelSet AudioChannelSet::quadraphonic() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround)); } | |||
| AudioChannelSet AudioChannelSet::pentagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << centre) | (1u << leftSurroundRear) | (1u << rightSurroundRear)); } | |||
| AudioChannelSet AudioChannelSet::hexagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurroundRear) | (1u << rightSurroundRear) | (1u << centre) | (1u << centreSurround)); } | |||
| AudioChannelSet AudioChannelSet::octagonal() { return AudioChannelSet ((1u << left) | (1u << right) | (1u << leftSurround) | (1u << rightSurround) | (1u << centre) | (1u << centreSurround) | (1u << wideLeft) | (1u << wideRight)); } | |||
| AudioChannelSet AudioChannelSet::discreteChannels (int numChannels) | |||
| { | |||
| AudioChannelSet s; | |||
| s.channels.setRange (discreteChannel0, numChannels, true); | |||
| return s; | |||
| } | |||
| AudioChannelSet AudioChannelSet::canonicalChannelSet (int numChannels) | |||
| { | |||
| if (numChannels == 1) return AudioChannelSet::mono(); | |||
| if (numChannels == 2) return AudioChannelSet::stereo(); | |||
| if (numChannels == 3) return AudioChannelSet::createLCR(); | |||
| if (numChannels == 4) return AudioChannelSet::quadraphonic(); | |||
| if (numChannels == 5) return AudioChannelSet::create5point0(); | |||
| if (numChannels == 6) return AudioChannelSet::create5point1(); | |||
| if (numChannels == 7) return AudioChannelSet::create7point0(); | |||
| if (numChannels == 8) return AudioChannelSet::create7point1(); | |||
| return discreteChannels (numChannels); | |||
| } | |||
| AudioChannelSet AudioChannelSet::namedChannelSet (int numChannels) | |||
| { | |||
| if (numChannels == 1) return AudioChannelSet::mono(); | |||
| if (numChannels == 2) return AudioChannelSet::stereo(); | |||
| if (numChannels == 3) return AudioChannelSet::createLCR(); | |||
| if (numChannels == 4) return AudioChannelSet::quadraphonic(); | |||
| if (numChannels == 5) return AudioChannelSet::create5point0(); | |||
| if (numChannels == 6) return AudioChannelSet::create5point1(); | |||
| if (numChannels == 7) return AudioChannelSet::create7point0(); | |||
| if (numChannels == 8) return AudioChannelSet::create7point1(); | |||
| return AudioChannelSet(); | |||
| } | |||
| Array<AudioChannelSet> AudioChannelSet::channelSetsWithNumberOfChannels (int numChannels) | |||
| { | |||
| Array<AudioChannelSet> retval; | |||
| if (numChannels != 0) | |||
| { | |||
| retval.add (AudioChannelSet::discreteChannels (numChannels)); | |||
| if (numChannels == 1) | |||
| { | |||
| retval.add (AudioChannelSet::mono()); | |||
| } | |||
| else if (numChannels == 2) | |||
| { | |||
| retval.add (AudioChannelSet::stereo()); | |||
| } | |||
| else if (numChannels == 3) | |||
| { | |||
| retval.add (AudioChannelSet::createLCR()); | |||
| retval.add (AudioChannelSet::createLRS()); | |||
| } | |||
| else if (numChannels == 4) | |||
| { | |||
| retval.add (AudioChannelSet::quadraphonic()); | |||
| retval.add (AudioChannelSet::createLCRS()); | |||
| retval.add (AudioChannelSet::ambisonic()); | |||
| } | |||
| else if (numChannels == 5) | |||
| { | |||
| retval.add (AudioChannelSet::create5point0()); | |||
| retval.add (AudioChannelSet::pentagonal()); | |||
| } | |||
| else if (numChannels == 6) | |||
| { | |||
| retval.add (AudioChannelSet::create5point1()); | |||
| retval.add (AudioChannelSet::create6point0()); | |||
| retval.add (AudioChannelSet::create6point0Music()); | |||
| retval.add (AudioChannelSet::hexagonal()); | |||
| } | |||
| else if (numChannels == 7) | |||
| { | |||
| retval.add (AudioChannelSet::create7point0()); | |||
| retval.add (AudioChannelSet::create7point0SDDS()); | |||
| retval.add (AudioChannelSet::create6point1()); | |||
| retval.add (AudioChannelSet::create6point1Music()); | |||
| } | |||
| else if (numChannels == 8) | |||
| { | |||
| retval.add (AudioChannelSet::create7point1()); | |||
| retval.add (AudioChannelSet::create7point1SDDS()); | |||
| retval.add (AudioChannelSet::octagonal()); | |||
| } | |||
| } | |||
| return retval; | |||
| } | |||
| @@ -0,0 +1,361 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_AUDIOCHANNELSET_H_INCLUDED | |||
| #define JUCE_AUDIOCHANNELSET_H_INCLUDED | |||
| //============================================================================== | |||
| /** | |||
| Represents a set of audio channel types. | |||
| For example, you might have a set of left + right channels, which is a stereo | |||
| channel set. It is a collection of values from the AudioChannelSet::ChannelType | |||
| enum, where each type may only occur once within the set. | |||
| The documentation below lists which AudioChannelSet corresponds to which native | |||
| layouts used by AAX, VST2/VST3 and CoreAudio/AU. The layout tags in CoreAudio | |||
| are particularly confusing. For example, the layout which is labeled as "7.1 SDDS" | |||
| in Logic Pro, corresponds to CoreAudio/AU's kAudioChannelLayoutTag_DTS_7_0 tag, whereas | |||
| AAX's DTS 7.1 Layout corresponds to CoreAudio/AU's | |||
| kAudioChannelLayoutTag_MPEG_7_1_A format, etc. Please do not use the CoreAudio tag | |||
| as an indication to the actual layout of the speakers. | |||
| @see Bus | |||
| */ | |||
| class JUCE_API AudioChannelSet | |||
| { | |||
| public: | |||
| /** Creates an empty channel set. | |||
| You can call addChannel to add channels to the set. | |||
| */ | |||
| AudioChannelSet() noexcept {} | |||
| /** Creates a zero-channel set which can be used to indicate that a | |||
| bus is disabled. */ | |||
| static AudioChannelSet disabled(); | |||
| //============================================================================== | |||
| /** Creates a one-channel mono set (centre). | |||
| Is equivalent to: kMonoAAX (VST), AAX_eStemFormat_Mono (AAX), kAudioChannelLayoutTag_Mono (CoreAudio) | |||
| */ | |||
| static AudioChannelSet mono(); | |||
| /** Creates a set containing a stereo set (left, right). | |||
| Is equivalent to: kStereo (VST), AAX_eStemFormat_Stereo (AAX), kAudioChannelLayoutTag_Stereo (CoreAudio) | |||
| */ | |||
| static AudioChannelSet stereo(); | |||
| //============================================================================== | |||
| /** Creates a set containing an LCR set (left, right, centre). | |||
| Is equivalent to: k30Cine (VST), AAX_eStemFormat_LCR (AAX), kAudioChannelLayoutTag_MPEG_3_0_A (CoreAudio) | |||
| This format is referred to as "LRC" in Cubase. | |||
| This format is referred to as "LCR" in Pro Tools. | |||
| */ | |||
| static AudioChannelSet createLCR(); | |||
| /** Creates a set containing an LRS set (left, right, surround). | |||
| Is equivalent to: k30Music (VST), n/a (AAX), kAudioChannelLayoutTag_ITU_2_1 (CoreAudio) | |||
| This format is referred to as "LRS" in Cubase. | |||
| */ | |||
| static AudioChannelSet createLRS(); | |||
| /** Creates a set containing an LCRS set (left, right, centre, surround). | |||
| Is equivalent to: k40Cine (VST), AAX_eStemFormat_LCRS (AAX), kAudioChannelLayoutTag_MPEG_4_0_A (CoreAudio) | |||
| This format is referred to as "LCRS (Pro Logic)" in Logic Pro. | |||
| This format is referred to as "LRCS" in Cubase. | |||
| This format is referred to as "LCRS" in Pro Tools. | |||
| */ | |||
| static AudioChannelSet createLCRS(); | |||
| //============================================================================== | |||
| /** Creates a set for a 5.0 surround setup (left, right, centre, leftSurround, rightSurround). | |||
| Is equivalent to: k50 (VST), AAX_eStemFormat_5_0 (AAX), kAudioChannelLayoutTag_MPEG_5_0_A (CoreAudio) | |||
| This format is referred to as "5.0" in Cubase. | |||
| This format is referred to as "5.0" in Pro Tools. | |||
| */ | |||
| static AudioChannelSet create5point0(); | |||
| /** Creates a set for a 5.1 surround setup (left, right, centre, leftSurround, rightSurround, LFE). | |||
| Is equivalent to: k51 (VST), AAX_eStemFormat_5_1 (AAX), kAudioChannelLayoutTag_MPEG_5_1_A (CoreAudio) | |||
| This format is referred to as "5.1 (ITU 775)" in Logic Pro. | |||
| This format is referred to as "5.1" in Cubase. | |||
| This format is referred to as "5.1" in Pro Tools. | |||
| */ | |||
| static AudioChannelSet create5point1(); | |||
| /** Creates a set for a 6.0 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround). | |||
| Is equivalent to: k60Cine (VST), AAX_eStemFormat_6_0 (AAX), kAudioChannelLayoutTag_AudioUnit_6_0 (CoreAudio) | |||
| Logic Pro incorrectly uses this for the surround format labeled "6.1 (ES/EX)". | |||
| This format is referred to as "6.0 Cine" in Cubase. | |||
| This format is referred to as "6.0" in Pro Tools. | |||
| */ | |||
| static AudioChannelSet create6point0(); | |||
| /** Creates a set for a 6.1 Cine surround setup (left, right, centre, leftSurround, rightSurround, centreSurround, LFE). | |||
| Is equivalent to: k61Cine (VST), AAX_eStemFormat_6_1 (AAX), kAudioChannelLayoutTag_MPEG_6_1_A (CoreAudio) | |||
| This format is referred to as "6.1" in Pro Tools. | |||
| */ | |||
| static AudioChannelSet create6point1(); | |||
| /** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide). | |||
| Is equivalent to: k60Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_0_A (CoreAudio) | |||
| This format is referred to as "6.0 Music" in Cubase. | |||
| */ | |||
| static AudioChannelSet create6point0Music(); | |||
| /** Creates a set for a 6.0 Music surround setup (left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, LFE). | |||
| Is equivalent to: k61Music (VST), n/a (AAX), kAudioChannelLayoutTag_DTS_6_1_A (CoreAudio) | |||
| */ | |||
| static AudioChannelSet create6point1Music(); | |||
| /** Creates a set for a DTS 7.0 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear). | |||
| Is equivalent to: k70Music (VST), AAX_eStemFormat_7_0_DTS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0 (CoreAudio) | |||
| This format is referred to as "7.0" in Pro Tools. | |||
| */ | |||
| static AudioChannelSet create7point0(); | |||
| /** Creates a set for a SDDS 7.0 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre). | |||
| Is equivalent to: k70Cine (VST), AAX_eStemFormat_7_0_SDDS (AAX), kAudioChannelLayoutTag_AudioUnit_7_0_Front (CoreAudio) | |||
| This format is referred to as "7.0 SDDS" in Pro Tools. | |||
| */ | |||
| static AudioChannelSet create7point0SDDS(); | |||
| /** Creates a set for a DTS 7.1 surround setup (left, right, centre, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear, LFE). | |||
| Is equivalent to: k71CineSideFill (VST), AAX_eStemFormat_7_1_DTS (AAX), kAudioChannelLayoutTag_MPEG_7_1_C/kAudioChannelLayoutTag_ITU_3_4_1 (CoreAudio) | |||
| This format is referred to as "7.1 (3/4.1)" in Logic Pro. | |||
| This format is referred to as "7.1" in Pro Tools. | |||
| */ | |||
| static AudioChannelSet create7point1(); | |||
| /** Creates a set for a 7.1 surround setup (left, right, centre, leftSurround, rightSurround, leftCentre, rightCentre, LFE). | |||
| Is equivalent to: k71Cine (VST), AAX_eStemFormat_7_1_SDDS (AAX), kAudioChannelLayoutTag_MPEG_7_1_A (CoreAudio) | |||
| This format is referred to as "7.1 (SDDS)" in Logic Pro. | |||
| This format is referred to as "7.1 SDDS" in Pro Tools. | |||
| */ | |||
| static AudioChannelSet create7point1SDDS(); | |||
| //============================================================================== | |||
| /** Creates a set for ambisonic surround setups (ambisonicW, ambisonicX, ambisonicY, ambisonicZ). | |||
| Is equivalent to: kBFormat (VST), n/a (AAX), kAudioChannelLayoutTag_Ambisonic_B_Format (CoreAudio) | |||
| */ | |||
| static AudioChannelSet ambisonic(); | |||
| /** Creates a set for quadraphonic surround setup (left, right, leftSurround, rightSurround) | |||
| Is equivalent to: k40Music (VST), AAX_eStemFormat_Quad (AAX), kAudioChannelLayoutTag_Quadraphonic (CoreAudio) | |||
| This format is referred to as "Quadraphonic" in Logic Pro. | |||
| This format is referred to as "Quadro" in Cubase. | |||
| This format is referred to as "Quad" in Pro Tools. | |||
| */ | |||
| static AudioChannelSet quadraphonic(); | |||
| /** Creates a set for pentagonal surround setup (left, right, centre, leftSurroundRear, rightSurroundRear). | |||
| Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Pentagonal (CoreAudio) | |||
| */ | |||
| static AudioChannelSet pentagonal(); | |||
| /** Creates a set for hexagonal surround setup (left, right, leftSurroundRear, rightSurroundRear, centre, surroundCentre). | |||
| Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Hexagonal (CoreAudio) | |||
| */ | |||
| static AudioChannelSet hexagonal(); | |||
| /** Creates a set for octagonal surround setup (left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight). | |||
| Is equivalent to: n/a (VST), n/a (AAX), kAudioChannelLayoutTag_Octagonal (CoreAudio) | |||
| */ | |||
| static AudioChannelSet octagonal(); | |||
| //============================================================================== | |||
| /** Creates a set of untyped discrete channels. */ | |||
| static AudioChannelSet discreteChannels (int numChannels); | |||
| /** Create a canonical channel set for a given number of channels. | |||
| For example, numChannels = 1 will return mono, numChannels = 2 will return stereo, etc. */ | |||
| static AudioChannelSet canonicalChannelSet (int numChannels); | |||
| /** Create a channel set for a given number of channels which is non-discrete. | |||
| If numChannels is larger than the number of channels of the surround format | |||
| with the maximum amount of channels (currently 7.1 Surround), then this | |||
| function returns an empty set.*/ | |||
| static AudioChannelSet namedChannelSet (int numChannels); | |||
| /** Return an array of channel sets which have a given number of channels */ | |||
| static Array<AudioChannelSet> channelSetsWithNumberOfChannels (int numChannels); | |||
| //============================================================================== | |||
| /** Represents different audio channel types. */ | |||
| enum ChannelType | |||
| { | |||
| unknown = 0, | |||
| left = 1, // L | |||
| right = 2, // R | |||
| centre = 3, // C (sometimes M for mono) | |||
| LFE = 4, | |||
| leftSurround = 5, // Ls | |||
| rightSurround = 6, // Rs | |||
| leftCentre = 7, // Lc (AAX/VST), Lc used as Lss in AU for most layouts | |||
| rightCentre = 8, // Rc (AAX/VST), Rc used as Rss in AU for most layouts | |||
| centreSurround = 9, // Cs/S | |||
| surround = centreSurround, // Cs/S | |||
| leftSurroundSide = 10, // Lss (AXX), Side Left "Sl" (VST), Left Centre "LC" (AU) | |||
| rightSurroundSide = 11, // Rss (AXX), Side right "Sr" (VST), Right Centre "Rc" (AU) | |||
| topMiddle = 12, | |||
| topFrontLeft = 13, | |||
| topFrontCentre = 14, | |||
| topFrontRight = 15, | |||
| topRearLeft = 16, | |||
| topRearCentre = 17, | |||
| topRearRight = 18, | |||
| LFE2 = 19, | |||
| leftSurroundRear = 20, // Lsr (AAX), Lcs (VST), Rls (AU) | |||
| rightSurroundRear = 21, // Rsr (AAX), Rcs (VST), Rrs (AU) | |||
| wideLeft = 22, | |||
| wideRight = 23, | |||
| ambisonicW = 24, | |||
| ambisonicX = 25, | |||
| ambisonicY = 26, | |||
| ambisonicZ = 27, | |||
| discreteChannel0 = 64 /**< Non-typed individual channels are indexed upwards from this value. */ | |||
| }; | |||
| /** Returns the name of a given channel type. For example, this method may return "Surround Left". */ | |||
| static String getChannelTypeName (ChannelType); | |||
| /** Returns the abbreviated name of a channel type. For example, this method may return "Ls". */ | |||
| static String getAbbreviatedChannelTypeName (ChannelType); | |||
| //============================================================================== | |||
| enum | |||
| { | |||
| maxChannelsOfNamedLayout = 8 | |||
| }; | |||
| /** Adds a channel to the set. */ | |||
| void addChannel (ChannelType newChannelType); | |||
| /** Removes a channel from the set. */ | |||
| void removeChannel (ChannelType newChannelType); | |||
| /** Returns the number of channels in the set. */ | |||
| int size() const noexcept; | |||
| /** Returns true if there are no channels in the set. */ | |||
| bool isDisabled() const noexcept { return size() == 0; } | |||
| /** Returns an array of all the types in this channel set. */ | |||
| Array<ChannelType> getChannelTypes() const; | |||
| /** Returns the type of one of the channels in the set, by index. */ | |||
| ChannelType getTypeOfChannel (int channelIndex) const noexcept; | |||
| /** Returns the index for a particular channel-type. | |||
| Will return -1 if the this set does not contain a channel of this type. */ | |||
| int getChannelIndexForType (ChannelType type) const noexcept; | |||
| /** Returns a string containing a whitespace-separated list of speaker types | |||
| corresponding to each channel. For example in a 5.1 arrangement, | |||
| the string may be "L R C Lfe Ls Rs". If the speaker arrangement is unknown, | |||
| the returned string will be empty.*/ | |||
| String getSpeakerArrangementAsString() const; | |||
| /** Returns the description of the current layout. For example, this method may return | |||
| "Quadraphonic". Note that the returned string may not be unique. */ | |||
| String getDescription() const; | |||
| /** Returns if this is a channel layout made-up of discrete channels. */ | |||
| bool isDiscreteLayout() const noexcept; | |||
| /** Intersect two channel layouts. */ | |||
| void intersect (const AudioChannelSet& other) { channels &= other.channels; } | |||
| //============================================================================== | |||
| bool operator== (const AudioChannelSet&) const noexcept; | |||
| bool operator!= (const AudioChannelSet&) const noexcept; | |||
| bool operator< (const AudioChannelSet&) const noexcept; | |||
| private: | |||
| BigInteger channels; | |||
| explicit AudioChannelSet (uint32); | |||
| }; | |||
| #endif // JUCE_AUDIOCHANNELSET_H_INCLUDED | |||
| @@ -397,9 +397,9 @@ public: | |||
| convert between 32 and 64 bit float buffer types. | |||
| */ | |||
| template <typename OtherType> | |||
| void makeCopyOf (const AudioBuffer<OtherType>& other) | |||
| void makeCopyOf (const AudioBuffer<OtherType>& other, bool avoidReallocating = false) | |||
| { | |||
| setSize (other.getNumChannels(), other.getNumSamples()); | |||
| setSize (other.getNumChannels(), other.getNumSamples(), false, false, avoidReallocating); | |||
| if (other.hasBeenCleared()) | |||
| { | |||
| @@ -204,10 +204,11 @@ namespace FloatVectorHelpers | |||
| typedef float Type; | |||
| typedef float32x4_t ParallelType; | |||
| typedef uint32x4_t IntegerType; | |||
| union signMaskUnion { ParallelType f; IntegerType i; }; | |||
| enum { numParallel = 4 }; | |||
| static forcedinline IntegerType toint (ParallelType v) noexcept { union { ParallelType f; IntegerType i; } u; u.f = v; return u.i; } | |||
| static forcedinline ParallelType toflt (IntegerType v) noexcept { union { ParallelType f; IntegerType i; } u; u.i = v; return u.f; } | |||
| static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } | |||
| static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } | |||
| static forcedinline ParallelType load1 (Type v) noexcept { return vld1q_dup_f32 (&v); } | |||
| static forcedinline ParallelType loadA (const Type* v) noexcept { return vld1q_f32 (v); } | |||
| @@ -235,10 +236,11 @@ namespace FloatVectorHelpers | |||
| typedef double Type; | |||
| typedef double ParallelType; | |||
| typedef uint64 IntegerType; | |||
| union signMaskUnion { ParallelType f; IntegerType i; }; | |||
| enum { numParallel = 1 }; | |||
| static forcedinline IntegerType toint (ParallelType v) noexcept { union { ParallelType f; IntegerType i; } u; u.f = v; return u.i; } | |||
| static forcedinline ParallelType toflt (IntegerType v) noexcept { union { ParallelType f; IntegerType i; } u; u.i = v; return u.f; } | |||
| static forcedinline IntegerType toint (ParallelType v) noexcept { signMaskUnion u; u.f = v; return u.i; } | |||
| static forcedinline ParallelType toflt (IntegerType v) noexcept { signMaskUnion u; u.i = v; return u.f; } | |||
| static forcedinline ParallelType load1 (Type v) noexcept { return v; } | |||
| static forcedinline ParallelType loadA (const Type* v) noexcept { return *v; } | |||
| @@ -346,6 +348,9 @@ namespace FloatVectorHelpers | |||
| #define JUCE_LOAD_SRC1_SRC2_DEST(src1Load, src2Load, dstLoad) const Mode::ParallelType d = dstLoad (dest), s1 = src1Load (src1), s2 = src2Load (src2); | |||
| #define JUCE_LOAD_SRC_DEST(srcLoad, dstLoad) const Mode::ParallelType d = dstLoad (dest), s = srcLoad (src); | |||
| union signMask32 { float f; uint32 i; }; | |||
| union signMask64 { double d; uint64 i; }; | |||
| #if JUCE_USE_SSE_INTRINSICS || JUCE_USE_ARM_NEON | |||
| template<int typeSize> struct ModeType { typedef BasicOps32 Mode; }; | |||
| template<> struct ModeType<8> { typedef BasicOps64 Mode; }; | |||
| @@ -481,6 +486,17 @@ namespace FloatVectorHelpers | |||
| #endif | |||
| } | |||
| //============================================================================== | |||
| namespace | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| // This casts away constness to account for slightly different vDSP function signatures | |||
| // in OSX 10.8 SDK and below. Can be safely removed once those SDKs are obsolete. | |||
| template <typename ValueType> | |||
| ValueType* osx108sdkCompatibilityCast (const ValueType* arg) noexcept { return const_cast<ValueType*> (arg); } | |||
| #endif | |||
| } | |||
| //============================================================================== | |||
| void JUCE_CALLTYPE FloatVectorOperations::clear (float* dest, int num) noexcept | |||
| { | |||
| @@ -568,10 +584,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double amount, int | |||
| const Mode::ParallelType amountToAdd = Mode::load1 (amount);) | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float amount, int num) noexcept | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, const float* src, float amount, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsadd (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||
| vDSP_vsadd (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| @@ -579,10 +595,10 @@ void JUCE_CALLTYPE FloatVectorOperations::add (float* dest, float* src, float am | |||
| #endif | |||
| } | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, double* src, double amount, int num) noexcept | |||
| void JUCE_CALLTYPE FloatVectorOperations::add (double* dest, const double* src, double amount, int num) noexcept | |||
| { | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vsaddD (src, 1, &amount, dest, 1, (vDSP_Length) num); | |||
| vDSP_vsaddD (osx108sdkCompatibilityCast (src), 1, &amount, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = src[i] + amount, Mode::add (am, s), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| @@ -795,7 +811,7 @@ void FloatVectorOperations::abs (float* dest, const float* src, int num) noexcep | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vabs ((float*) src, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| union { float f; uint32 i; } signMask; | |||
| FloatVectorHelpers::signMask32 signMask; | |||
| signMask.i = 0x7fffffffUL; | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabsf (src[i]), Mode::bit_and (s, mask), | |||
| JUCE_LOAD_SRC, JUCE_INCREMENT_SRC_DEST, | |||
| @@ -810,7 +826,7 @@ void FloatVectorOperations::abs (double* dest, const double* src, int num) noexc | |||
| #if JUCE_USE_VDSP_FRAMEWORK | |||
| vDSP_vabsD ((double*) src, 1, dest, 1, (vDSP_Length) num); | |||
| #else | |||
| union {double d; uint64 i;} signMask; | |||
| FloatVectorHelpers::signMask64 signMask; | |||
| signMask.i = 0x7fffffffffffffffULL; | |||
| JUCE_PERFORM_VEC_OP_SRC_DEST (dest[i] = fabs (src[i]), Mode::bit_and (s, mask), | |||
| @@ -991,7 +1007,7 @@ void JUCE_CALLTYPE FloatVectorOperations::enableFlushToZeroMode (bool shouldEnab | |||
| void JUCE_CALLTYPE FloatVectorOperations::disableDenormalisedNumberSupport() noexcept | |||
| { | |||
| #if JUCE_USE_SSE_INTRINSICS | |||
| const int mxcsr = _mm_getcsr(); | |||
| const unsigned int mxcsr = _mm_getcsr(); | |||
| _mm_setcsr (mxcsr | 0x8040); // add the DAZ and FZ bits | |||
| #endif | |||
| } | |||
| @@ -66,10 +66,10 @@ public: | |||
| static void JUCE_CALLTYPE add (double* dest, double amountToAdd, int numValues) noexcept; | |||
| /** Adds a fixed value to each source value and stores it in the destination array. */ | |||
| static void JUCE_CALLTYPE add (float* dest, float* src, float amount, int numValues) noexcept; | |||
| static void JUCE_CALLTYPE add (float* dest, const float* src, float amount, int numValues) noexcept; | |||
| /** Adds a fixed value to each source value and stores it in the destination array. */ | |||
| static void JUCE_CALLTYPE add (double* dest, double* src, double amount, int numValues) noexcept; | |||
| static void JUCE_CALLTYPE add (double* dest, const double* src, double amount, int numValues) noexcept; | |||
| /** Adds the source values to the destination values. */ | |||
| static void JUCE_CALLTYPE add (float* dest, const float* src, int numValues) noexcept; | |||
| @@ -22,7 +22,6 @@ | |||
| ============================================================================== | |||
| */ | |||
| struct CatmullRomAlgorithm | |||
| { | |||
| static forcedinline float valueAtOffset (const float* const inputs, const float offset) noexcept | |||
| @@ -22,7 +22,6 @@ | |||
| ============================================================================== | |||
| */ | |||
| /** | |||
| Interpolator for resampling a stream of floats using Catmull-Rom interpolation. | |||
| @@ -62,33 +62,131 @@ IIRCoefficients::IIRCoefficients (double c1, double c2, double c3, | |||
| IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | |||
| const double frequency) noexcept | |||
| { | |||
| jassert (sampleRate > 0); | |||
| return makeLowPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||
| } | |||
| IIRCoefficients IIRCoefficients::makeLowPass (const double sampleRate, | |||
| const double frequency, | |||
| const double Q) noexcept | |||
| { | |||
| jassert (sampleRate > 0.0); | |||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||
| jassert (Q > 0.0); | |||
| const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||
| const double nSquared = n * n; | |||
| const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||
| const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||
| return IIRCoefficients (c1, | |||
| c1 * 2.0, | |||
| c1, | |||
| 1.0, | |||
| c1 * 2.0 * (1.0 - nSquared), | |||
| c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||
| c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||
| } | |||
| IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, | |||
| const double frequency) noexcept | |||
| { | |||
| return makeHighPass (sampleRate, frequency, 1.0 / std::sqrt(2.0)); | |||
| } | |||
| IIRCoefficients IIRCoefficients::makeHighPass (const double sampleRate, | |||
| const double frequency, | |||
| const double Q) noexcept | |||
| { | |||
| jassert (sampleRate > 0.0); | |||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||
| jassert (Q > 0.0); | |||
| const double n = std::tan (double_Pi * frequency / sampleRate); | |||
| const double nSquared = n * n; | |||
| const double c1 = 1.0 / (1.0 + std::sqrt (2.0) * n + nSquared); | |||
| const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||
| return IIRCoefficients (c1, | |||
| c1 * -2.0, | |||
| c1, | |||
| 1.0, | |||
| c1 * 2.0 * (nSquared - 1.0), | |||
| c1 * (1.0 - std::sqrt (2.0) * n + nSquared)); | |||
| c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||
| } | |||
| IIRCoefficients IIRCoefficients::makeBandPass (const double sampleRate, | |||
| const double frequency) noexcept | |||
| { | |||
| return makeBandPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||
| } | |||
| IIRCoefficients IIRCoefficients::makeBandPass (const double sampleRate, | |||
| const double frequency, | |||
| const double Q) noexcept | |||
| { | |||
| jassert (sampleRate > 0.0); | |||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||
| jassert (Q > 0.0); | |||
| const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||
| const double nSquared = n * n; | |||
| const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||
| return IIRCoefficients (c1 * n / Q, | |||
| 0.0, | |||
| -c1 * n / Q, | |||
| 1.0, | |||
| c1 * 2.0 * (1.0 - nSquared), | |||
| c1 * (1.0 - 1.0 / Q * n + nSquared)); | |||
| } | |||
| IIRCoefficients IIRCoefficients::makeNotchFilter (const double sampleRate, | |||
| const double frequency) noexcept | |||
| { | |||
| return makeNotchFilter (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||
| } | |||
| IIRCoefficients IIRCoefficients::makeNotchFilter (const double sampleRate, | |||
| const double frequency, | |||
| const double Q) noexcept | |||
| { | |||
| jassert (sampleRate > 0.0); | |||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||
| jassert (Q > 0.0); | |||
| const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||
| const double nSquared = n * n; | |||
| const double c1 = 1.0 / (1.0 + n / Q + nSquared); | |||
| return IIRCoefficients (c1 * (1.0 + nSquared), | |||
| 2.0 * c1 * (1.0 - nSquared), | |||
| c1 * (1.0 + nSquared), | |||
| 1.0, | |||
| c1 * 2.0 * (1.0 - nSquared), | |||
| c1 * (1.0 - n / Q + nSquared)); | |||
| } | |||
| IIRCoefficients IIRCoefficients::makeAllPass (const double sampleRate, | |||
| const double frequency) noexcept | |||
| { | |||
| return makeAllPass (sampleRate, frequency, 1.0 / std::sqrt (2.0)); | |||
| } | |||
| IIRCoefficients IIRCoefficients::makeAllPass (const double sampleRate, | |||
| const double frequency, | |||
| const double Q) noexcept | |||
| { | |||
| jassert (sampleRate > 0.0); | |||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||
| jassert (Q > 0.0); | |||
| const double n = 1.0 / std::tan (double_Pi * frequency / sampleRate); | |||
| const double nSquared = n * n; | |||
| const double c1 = 1.0 / (1.0 + 1.0 / Q * n + nSquared); | |||
| return IIRCoefficients (c1 * (1.0 - n / Q + nSquared), | |||
| c1 * 2.0 * (1.0 - nSquared), | |||
| 1.0, | |||
| 1.0, | |||
| c1 * 2.0 * (1.0 - nSquared), | |||
| c1 * (1.0 - n / Q + nSquared)); | |||
| } | |||
| IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, | |||
| @@ -96,8 +194,9 @@ IIRCoefficients IIRCoefficients::makeLowShelf (const double sampleRate, | |||
| const double Q, | |||
| const float gainFactor) noexcept | |||
| { | |||
| jassert (sampleRate > 0); | |||
| jassert (Q > 0); | |||
| jassert (sampleRate > 0.0); | |||
| jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); | |||
| jassert (Q > 0.0); | |||
| const double A = jmax (0.0f, std::sqrt (gainFactor)); | |||
| const double aminus1 = A - 1.0; | |||
| @@ -120,8 +219,9 @@ IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate, | |||
| const double Q, | |||
| const float gainFactor) noexcept | |||
| { | |||
| jassert (sampleRate > 0); | |||
| jassert (Q > 0); | |||
| jassert (sampleRate > 0.0); | |||
| jassert (cutOffFrequency > 0.0 && cutOffFrequency <= sampleRate * 0.5); | |||
| jassert (Q > 0.0); | |||
| const double A = jmax (0.0f, std::sqrt (gainFactor)); | |||
| const double aminus1 = A - 1.0; | |||
| @@ -140,15 +240,16 @@ IIRCoefficients IIRCoefficients::makeHighShelf (const double sampleRate, | |||
| } | |||
| IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, | |||
| const double centreFrequency, | |||
| const double frequency, | |||
| const double Q, | |||
| const float gainFactor) noexcept | |||
| { | |||
| jassert (sampleRate > 0); | |||
| jassert (Q > 0); | |||
| jassert (sampleRate > 0.0); | |||
| jassert (frequency > 0.0 && frequency <= sampleRate * 0.5); | |||
| jassert (Q > 0.0); | |||
| const double A = jmax (0.0f, std::sqrt (gainFactor)); | |||
| const double omega = (double_Pi * 2.0 * jmax (centreFrequency, 2.0)) / sampleRate; | |||
| const double omega = (double_Pi * 2.0 * jmax (frequency, 2.0)) / sampleRate; | |||
| const double alpha = 0.5 * std::sin (omega) / Q; | |||
| const double c2 = -2.0 * std::cos (omega); | |||
| const double alphaTimesA = alpha * A; | |||
| @@ -164,12 +265,12 @@ IIRCoefficients IIRCoefficients::makePeakFilter (const double sampleRate, | |||
| //============================================================================== | |||
| IIRFilter::IIRFilter() noexcept | |||
| : v1 (0), v2 (0), active (false) | |||
| : v1 (0.0), v2 (0.0), active (false) | |||
| { | |||
| } | |||
| IIRFilter::IIRFilter (const IIRFilter& other) noexcept | |||
| : v1 (0), v2 (0), active (other.active) | |||
| : v1 (0.0), v2 (0.0), active (other.active) | |||
| { | |||
| const SpinLock::ScopedLockType sl (other.processLock); | |||
| coefficients = other.coefficients; | |||
| @@ -198,7 +299,7 @@ void IIRFilter::setCoefficients (const IIRCoefficients& newCoefficients) noexcep | |||
| void IIRFilter::reset() noexcept | |||
| { | |||
| const SpinLock::ScopedLockType sl (processLock); | |||
| v1 = v2 = 0; | |||
| v1 = v2 = 0.0; | |||
| } | |||
| float IIRFilter::processSingleSampleRaw (const float in) noexcept | |||
| @@ -55,14 +55,53 @@ public: | |||
| /** Destructor. */ | |||
| ~IIRCoefficients() noexcept; | |||
| //============================================================================== | |||
| /** Returns the coefficients for a low-pass filter. */ | |||
| static IIRCoefficients makeLowPass (double sampleRate, | |||
| double frequency) noexcept; | |||
| /** Returns the coefficients for a low-pass filter with variable Q. */ | |||
| static IIRCoefficients makeLowPass (double sampleRate, | |||
| double frequency, | |||
| double Q) noexcept; | |||
| //============================================================================== | |||
| /** Returns the coefficients for a high-pass filter. */ | |||
| static IIRCoefficients makeHighPass (double sampleRate, | |||
| double frequency) noexcept; | |||
| /** Returns the coefficients for a high-pass filter with variable Q. */ | |||
| static IIRCoefficients makeHighPass (double sampleRate, | |||
| double frequency, | |||
| double Q) noexcept; | |||
| //============================================================================== | |||
| /** Returns the coefficients for a band-pass filter. */ | |||
| static IIRCoefficients makeBandPass (double sampleRate, double frequency) noexcept; | |||
| /** Returns the coefficients for a band-pass filter with variable Q. */ | |||
| static IIRCoefficients makeBandPass (double sampleRate, | |||
| double frequency, | |||
| double Q) noexcept; | |||
| //============================================================================== | |||
| /** Returns the coefficients for a notch filter. */ | |||
| static IIRCoefficients makeNotchFilter (double sampleRate, double frequency) noexcept; | |||
| /** Returns the coefficients for a notch filter with variable Q. */ | |||
| static IIRCoefficients makeNotchFilter (double sampleRate, | |||
| double frequency, | |||
| double Q) noexcept; | |||
| //============================================================================== | |||
| /** Returns the coefficients for an all-pass filter. */ | |||
| static IIRCoefficients makeAllPass (double sampleRate, double frequency) noexcept; | |||
| /** Returns the coefficients for an all-pass filter with variable Q. */ | |||
| static IIRCoefficients makeAllPass (double sampleRate, | |||
| double frequency, | |||
| double Q) noexcept; | |||
| //============================================================================== | |||
| /** Returns the coefficients for a low-pass shelf filter with variable Q and gain. | |||
| @@ -22,7 +22,6 @@ | |||
| ============================================================================== | |||
| */ | |||
| /** | |||
| Interpolator for resampling a stream of floats using 4-point lagrange interpolation. | |||
| @@ -33,8 +33,8 @@ | |||
| */ | |||
| //============================================================================== | |||
| template<typename FloatType> | |||
| class JUCE_API LinearSmoothedValue | |||
| template <typename FloatType> | |||
| class LinearSmoothedValue | |||
| { | |||
| public: | |||
| /** Constructor. */ | |||
| @@ -59,7 +59,6 @@ public: | |||
| countdown = 0; | |||
| } | |||
| //============================================================================== | |||
| /** Set a new target value. */ | |||
| void setValue (FloatType newValue) noexcept | |||
| { | |||
| @@ -75,7 +74,6 @@ public: | |||
| } | |||
| } | |||
| //============================================================================== | |||
| /** Compute the next value. */ | |||
| FloatType getNextValue() noexcept | |||
| { | |||
| @@ -87,6 +85,18 @@ public: | |||
| return currentValue; | |||
| } | |||
| /** Returns true if the current value is currently being interpolated. */ | |||
| bool isSmoothing() const noexcept | |||
| { | |||
| return countdown > 0; | |||
| } | |||
| /** Returns the target value towards which the smoothed value is currently moving. */ | |||
| FloatType getTargetValue() const noexcept | |||
| { | |||
| return target; | |||
| } | |||
| private: | |||
| //============================================================================== | |||
| FloatType currentValue, target, step; | |||
| @@ -31,6 +31,8 @@ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| #include "AppConfig.h" | |||
| #include "juce_audio_basics.h" | |||
| #if JUCE_MINGW && ! defined (__SSE2__) | |||
| @@ -67,6 +69,13 @@ | |||
| #define JUCE_USE_ARM_NEON 1 | |||
| #endif | |||
| #if TARGET_IPHONE_SIMULATOR | |||
| #ifdef JUCE_USE_ARM_NEON | |||
| #undef JUCE_USE_ARM_NEON | |||
| #endif | |||
| #define JUCE_USE_ARM_NEON 0 | |||
| #endif | |||
| #if JUCE_USE_ARM_NEON | |||
| #include <arm_neon.h> | |||
| #endif | |||
| @@ -76,6 +85,7 @@ namespace juce | |||
| #include "buffers/juce_AudioDataConverters.cpp" | |||
| #include "buffers/juce_FloatVectorOperations.cpp" | |||
| #include "buffers/juce_AudioChannelSet.cpp" | |||
| #include "effects/juce_IIRFilter.cpp" | |||
| #include "effects/juce_IIRFilterOld.cpp" | |||
| #include "effects/juce_LagrangeInterpolator.cpp" | |||
| @@ -22,12 +22,37 @@ | |||
| ============================================================================== | |||
| */ | |||
| /******************************************************************************* | |||
| The block below describes the properties of this module, and is read by | |||
| the Projucer to automatically generate project code that uses it. | |||
| For details about the syntax and how to create or use a module, see the | |||
| JUCE Module Format.txt file. | |||
| BEGIN_JUCE_MODULE_DECLARATION | |||
| ID: juce_audio_basics | |||
| vendor: juce | |||
| version: 4.3.0 | |||
| name: JUCE audio and MIDI data classes | |||
| description: Classes for audio buffer manipulation, midi message handling, synthesis, etc. | |||
| website: http://www.juce.com/juce | |||
| license: GPL/Commercial | |||
| dependencies: juce_core | |||
| OSXFrameworks: Accelerate | |||
| iOSFrameworks: Accelerate | |||
| END_JUCE_MODULE_DECLARATION | |||
| *******************************************************************************/ | |||
| #ifndef JUCE_AUDIO_BASICS_H_INCLUDED | |||
| #define JUCE_AUDIO_BASICS_H_INCLUDED | |||
| #include "../juce_core/juce_core.h" | |||
| #include "juce_core/juce_core.h" | |||
| //============================================================================== | |||
| namespace juce | |||
| { | |||
| @@ -37,6 +62,7 @@ namespace juce | |||
| #include "buffers/juce_AudioDataConverters.h" | |||
| #include "buffers/juce_FloatVectorOperations.h" | |||
| #include "buffers/juce_AudioSampleBuffer.h" | |||
| #include "buffers/juce_AudioChannelSet.h" | |||
| #include "effects/juce_Decibels.h" | |||
| #include "effects/juce_IIRFilter.h" | |||
| #include "effects/juce_IIRFilterOld.h" | |||
| @@ -248,7 +248,7 @@ bool MidiFile::readFrom (InputStream& sourceStream) | |||
| clear(); | |||
| MemoryBlock data; | |||
| const int maxSensibleMidiFileSize = 2 * 1024 * 1024; | |||
| const int maxSensibleMidiFileSize = 200 * 1024 * 1024; | |||
| // (put a sanity-check on the file size, as midi files are generally small) | |||
| if (sourceStream.readIntoMemoryBlock (data, maxSensibleMidiFileSize)) | |||
| @@ -33,11 +33,23 @@ namespace MidiHelpers | |||
| { | |||
| return (uint8) jlimit (0, 127, v); | |||
| } | |||
| } | |||
| inline uint8 floatVelocityToByte (const float v) noexcept | |||
| { | |||
| return validVelocity (roundToInt (v * 127.0f)); | |||
| } | |||
| //============================================================================== | |||
| uint8 MidiMessage::floatValueToMidiByte (const float v) noexcept | |||
| { | |||
| return MidiHelpers::validVelocity (roundToInt (v * 127.0f)); | |||
| } | |||
| uint16 MidiMessage::pitchbendToPitchwheelPos (const float pitchbend, | |||
| const float pitchbendRange) noexcept | |||
| { | |||
| // can't translate a pitchbend value that is outside of the given range! | |||
| jassert (std::abs (pitchbend) <= pitchbendRange); | |||
| return static_cast<uint16> (pitchbend > 0.0f | |||
| ? jmap (pitchbend, 0.0f, pitchbendRange, 8192.0f, 16383.0f) | |||
| : jmap (pitchbend, -pitchbendRange, 0.0f, 0.0f, 8192.0f)); | |||
| } | |||
| //============================================================================== | |||
| @@ -84,25 +96,24 @@ int MidiMessage::getMessageLengthFromFirstByte (const uint8 firstByte) noexcept | |||
| MidiMessage::MidiMessage() noexcept | |||
| : timeStamp (0), size (2) | |||
| { | |||
| preallocatedData.asBytes[0] = 0xf0; | |||
| preallocatedData.asBytes[1] = 0xf7; | |||
| packedData.asBytes[0] = 0xf0; | |||
| packedData.asBytes[1] = 0xf7; | |||
| } | |||
| MidiMessage::MidiMessage (const void* const d, const int dataSize, const double t) | |||
| : timeStamp (t), | |||
| size (dataSize) | |||
| : timeStamp (t), size (dataSize) | |||
| { | |||
| jassert (dataSize > 0); | |||
| memcpy (allocateSpace (dataSize), d, (size_t) dataSize); | |||
| // this checks that the length matches the data.. | |||
| jassert (dataSize > 3 || *(uint8*)d >= 0xf0 || getMessageLengthFromFirstByte (*(uint8*)d) == size); | |||
| // check that the length matches the data.. | |||
| jassert (size > 3 || *(uint8*)d >= 0xf0 || getMessageLengthFromFirstByte (*(uint8*)d) == size); | |||
| memcpy (allocateSpace (dataSize), d, (size_t) dataSize); | |||
| } | |||
| MidiMessage::MidiMessage (const int byte1, const double t) noexcept | |||
| : timeStamp (t), size (1) | |||
| { | |||
| preallocatedData.asBytes[0] = (uint8) byte1; | |||
| packedData.asBytes[0] = (uint8) byte1; | |||
| // check that the length matches the data.. | |||
| jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 1); | |||
| @@ -111,8 +122,8 @@ MidiMessage::MidiMessage (const int byte1, const double t) noexcept | |||
| MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noexcept | |||
| : timeStamp (t), size (2) | |||
| { | |||
| preallocatedData.asBytes[0] = (uint8) byte1; | |||
| preallocatedData.asBytes[1] = (uint8) byte2; | |||
| packedData.asBytes[0] = (uint8) byte1; | |||
| packedData.asBytes[1] = (uint8) byte2; | |||
| // check that the length matches the data.. | |||
| jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 2); | |||
| @@ -121,9 +132,9 @@ MidiMessage::MidiMessage (const int byte1, const int byte2, const double t) noex | |||
| MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, const double t) noexcept | |||
| : timeStamp (t), size (3) | |||
| { | |||
| preallocatedData.asBytes[0] = (uint8) byte1; | |||
| preallocatedData.asBytes[1] = (uint8) byte2; | |||
| preallocatedData.asBytes[2] = (uint8) byte3; | |||
| packedData.asBytes[0] = (uint8) byte1; | |||
| packedData.asBytes[1] = (uint8) byte2; | |||
| packedData.asBytes[2] = (uint8) byte3; | |||
| // check that the length matches the data.. | |||
| jassert (byte1 >= 0xf0 || getMessageLengthFromFirstByte ((uint8) byte1) == 3); | |||
| @@ -132,29 +143,19 @@ MidiMessage::MidiMessage (const int byte1, const int byte2, const int byte3, con | |||
| MidiMessage::MidiMessage (const MidiMessage& other) | |||
| : timeStamp (other.timeStamp), size (other.size) | |||
| { | |||
| if (other.allocatedData != nullptr) | |||
| { | |||
| allocatedData.malloc ((size_t) size); | |||
| memcpy (allocatedData, other.allocatedData, (size_t) size); | |||
| } | |||
| if (isHeapAllocated()) | |||
| memcpy (allocateSpace (size), other.getData(), (size_t) size); | |||
| else | |||
| { | |||
| preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||
| } | |||
| packedData.allocatedData = other.packedData.allocatedData; | |||
| } | |||
| MidiMessage::MidiMessage (const MidiMessage& other, const double newTimeStamp) | |||
| : timeStamp (newTimeStamp), size (other.size) | |||
| { | |||
| if (other.allocatedData != nullptr) | |||
| { | |||
| allocatedData.malloc ((size_t) size); | |||
| memcpy (allocatedData, other.allocatedData, (size_t) size); | |||
| } | |||
| if (isHeapAllocated()) | |||
| memcpy (allocateSpace (size), other.getData(), (size_t) size); | |||
| else | |||
| { | |||
| preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||
| } | |||
| packedData.allocatedData = other.packedData.allocatedData; | |||
| } | |||
| MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const uint8 lastStatusByte, | |||
| @@ -229,16 +230,15 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const | |||
| } | |||
| else | |||
| { | |||
| preallocatedData.asInt32 = 0; | |||
| size = getMessageLengthFromFirstByte ((uint8) byte); | |||
| preallocatedData.asBytes[0] = (uint8) byte; | |||
| packedData.asBytes[0] = (uint8) byte; | |||
| if (size > 1) | |||
| { | |||
| preallocatedData.asBytes[1] = src[0]; | |||
| packedData.asBytes[1] = src[0]; | |||
| if (size > 2) | |||
| preallocatedData.asBytes[2] = src[1]; | |||
| packedData.asBytes[2] = src[1]; | |||
| } | |||
| } | |||
| @@ -246,7 +246,7 @@ MidiMessage::MidiMessage (const void* srcData, int sz, int& numBytesUsed, const | |||
| } | |||
| else | |||
| { | |||
| preallocatedData.asInt32 = 0; | |||
| packedData.allocatedData = nullptr; | |||
| size = 0; | |||
| } | |||
| } | |||
| @@ -255,19 +255,25 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& other) | |||
| { | |||
| if (this != &other) | |||
| { | |||
| timeStamp = other.timeStamp; | |||
| size = other.size; | |||
| if (other.allocatedData != nullptr) | |||
| if (other.isHeapAllocated()) | |||
| { | |||
| allocatedData.malloc ((size_t) size); | |||
| memcpy (allocatedData, other.allocatedData, (size_t) size); | |||
| if (isHeapAllocated()) | |||
| packedData.allocatedData = static_cast<uint8*> (std::realloc (packedData.allocatedData, (size_t) other.size)); | |||
| else | |||
| packedData.allocatedData = static_cast<uint8*> (std::malloc ((size_t) other.size)); | |||
| memcpy (packedData.allocatedData, other.packedData.allocatedData, (size_t) other.size); | |||
| } | |||
| else | |||
| { | |||
| allocatedData.free(); | |||
| preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||
| if (isHeapAllocated()) | |||
| std::free (packedData.allocatedData); | |||
| packedData.allocatedData = other.packedData.allocatedData; | |||
| } | |||
| timeStamp = other.timeStamp; | |||
| size = other.size; | |||
| } | |||
| return *this; | |||
| @@ -277,36 +283,61 @@ MidiMessage& MidiMessage::operator= (const MidiMessage& other) | |||
| MidiMessage::MidiMessage (MidiMessage&& other) noexcept | |||
| : timeStamp (other.timeStamp), size (other.size) | |||
| { | |||
| if (other.allocatedData != nullptr) | |||
| allocatedData.swapWith (other.allocatedData); | |||
| else | |||
| preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||
| packedData.allocatedData = other.packedData.allocatedData; | |||
| other.size = 0; | |||
| } | |||
| MidiMessage& MidiMessage::operator= (MidiMessage&& other) noexcept | |||
| { | |||
| jassert (this != &other); // shouldn't be possible | |||
| packedData.allocatedData = other.packedData.allocatedData; | |||
| timeStamp = other.timeStamp; | |||
| size = other.size; | |||
| allocatedData.swapWith (other.allocatedData); | |||
| preallocatedData.asInt32 = other.preallocatedData.asInt32; | |||
| other.size = 0; | |||
| return *this; | |||
| } | |||
| #endif | |||
| MidiMessage::~MidiMessage() {} | |||
| MidiMessage::~MidiMessage() noexcept | |||
| { | |||
| if (isHeapAllocated()) | |||
| std::free (packedData.allocatedData); | |||
| } | |||
| uint8* MidiMessage::allocateSpace (int bytes) | |||
| { | |||
| if (bytes > 4) | |||
| if (bytes > (int) sizeof (packedData)) | |||
| { | |||
| uint8* d = static_cast<uint8*> (std::malloc ((size_t) bytes)); | |||
| packedData.allocatedData = d; | |||
| return d; | |||
| } | |||
| return packedData.asBytes; | |||
| } | |||
| String MidiMessage::getDescription() const | |||
| { | |||
| if (isNoteOn()) return "Note on " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); | |||
| if (isNoteOff()) return "Note off " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + " Velocity " + String (getVelocity()) + " Channel " + String (getChannel()); | |||
| if (isProgramChange()) return "Program change " + String (getProgramChangeNumber()) + " Channel " + String (getChannel()); | |||
| if (isPitchWheel()) return "Pitch wheel " + String (getPitchWheelValue()) + " Channel " + String (getChannel()); | |||
| if (isAftertouch()) return "Aftertouch " + MidiMessage::getMidiNoteName (getNoteNumber(), true, true, 3) + ": " + String (getAfterTouchValue()) + " Channel " + String (getChannel()); | |||
| if (isChannelPressure()) return "Channel pressure " + String (getChannelPressureValue()) + " Channel " + String (getChannel()); | |||
| if (isAllNotesOff()) return "All notes off Channel " + String (getChannel()); | |||
| if (isAllSoundOff()) return "All sound off Channel " + String (getChannel()); | |||
| if (isMetaEvent()) return "Meta event"; | |||
| if (isController()) | |||
| { | |||
| allocatedData.malloc ((size_t) bytes); | |||
| return allocatedData; | |||
| String name (MidiMessage::getControllerName (getControllerNumber())); | |||
| if (name.isEmpty()) | |||
| name = String (getControllerNumber()); | |||
| return "Controller " + name + ": " + String (getControllerValue()) + " Channel " + String (getChannel()); | |||
| } | |||
| return preallocatedData.asBytes; | |||
| return String::toHexString (getRawData(), getRawDataSize()); | |||
| } | |||
| int MidiMessage::getChannel() const noexcept | |||
| @@ -391,7 +422,7 @@ float MidiMessage::getFloatVelocity() const noexcept | |||
| void MidiMessage::setVelocity (const float newVelocity) noexcept | |||
| { | |||
| if (isNoteOnOrOff()) | |||
| getData()[2] = MidiHelpers::floatVelocityToByte (newVelocity); | |||
| getData()[2] = floatValueToMidiByte (newVelocity); | |||
| } | |||
| void MidiMessage::multiplyVelocity (const float scaleFactor) noexcept | |||
| @@ -538,7 +569,7 @@ MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const | |||
| MidiMessage MidiMessage::noteOn (const int channel, const int noteNumber, const float velocity) noexcept | |||
| { | |||
| return noteOn (channel, noteNumber, MidiHelpers::floatVelocityToByte (velocity)); | |||
| return noteOn (channel, noteNumber, floatValueToMidiByte (velocity)); | |||
| } | |||
| MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 velocity) noexcept | |||
| @@ -552,7 +583,7 @@ MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, uint8 | |||
| MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber, float velocity) noexcept | |||
| { | |||
| return noteOff (channel, noteNumber, MidiHelpers::floatVelocityToByte (velocity)); | |||
| return noteOff (channel, noteNumber, floatValueToMidiByte (velocity)); | |||
| } | |||
| MidiMessage MidiMessage::noteOff (const int channel, const int noteNumber) noexcept | |||
| @@ -697,9 +728,10 @@ MidiMessage MidiMessage::textMetaEvent (int type, StringRef text) | |||
| header[--n] = 0xff; | |||
| const size_t headerLen = sizeof (header) - n; | |||
| const int totalSize = (int) (headerLen + textSize); | |||
| uint8* const dest = result.allocateSpace ((int) (headerLen + textSize)); | |||
| result.size = (int) (headerLen + textSize); | |||
| uint8* const dest = result.allocateSpace (totalSize); | |||
| result.size = totalSize; | |||
| memcpy (dest, header + n, headerLen); | |||
| memcpy (dest + headerLen, text.text.getAddress(), textSize); | |||
| @@ -816,7 +848,7 @@ bool MidiMessage::isKeySignatureMetaEvent() const noexcept | |||
| int MidiMessage::getKeySignatureNumberOfSharpsOrFlats() const noexcept | |||
| { | |||
| return (int) getMetaEventData()[0]; | |||
| return (int) (int8) getMetaEventData()[0]; | |||
| } | |||
| bool MidiMessage::isKeySignatureMajorKey() const noexcept | |||
| @@ -985,7 +1017,7 @@ String MidiMessage::getMidiNoteName (int note, bool useSharps, bool includeOctav | |||
| return String(); | |||
| } | |||
| double MidiMessage::getMidiNoteInHertz (int noteNumber, const double frequencyOfA) noexcept | |||
| double MidiMessage::getMidiNoteInHertz (const int noteNumber, const double frequencyOfA) noexcept | |||
| { | |||
| return frequencyOfA * pow (2.0, (noteNumber - 69) / 12.0); | |||
| } | |||
| @@ -104,7 +104,7 @@ public: | |||
| MidiMessage (const MidiMessage&, double newTimeStamp); | |||
| /** Destructor. */ | |||
| ~MidiMessage(); | |||
| ~MidiMessage() noexcept; | |||
| /** Copies this message from another one. */ | |||
| MidiMessage& operator= (const MidiMessage& other); | |||
| @@ -118,13 +118,19 @@ public: | |||
| /** Returns a pointer to the raw midi data. | |||
| @see getRawDataSize | |||
| */ | |||
| const uint8* getRawData() const noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; } | |||
| const uint8* getRawData() const noexcept { return getData(); } | |||
| /** Returns the number of bytes of data in the message. | |||
| @see getRawData | |||
| */ | |||
| int getRawDataSize() const noexcept { return size; } | |||
| //============================================================================== | |||
| /** Returns a human-readable description of the midi message as a string, | |||
| for example "Note On C#3 Velocity 120 Channel 1". | |||
| */ | |||
| String getDescription() const; | |||
| //============================================================================== | |||
| /** Returns the timestamp associated with this message. | |||
| @@ -845,7 +851,7 @@ public: | |||
| The value passed in must be 0x80 or higher. | |||
| */ | |||
| static int getMessageLengthFromFirstByte (const uint8 firstByte) noexcept; | |||
| static int getMessageLengthFromFirstByte (uint8 firstByte) noexcept; | |||
| //============================================================================== | |||
| /** Returns the name of a midi note number. | |||
| @@ -872,7 +878,7 @@ public: | |||
| The frequencyOfA parameter is an optional frequency for 'A', normally 440-444Hz for concert pitch. | |||
| @see getMidiNoteName | |||
| */ | |||
| static double getMidiNoteInHertz (int noteNumber, const double frequencyOfA = 440.0) noexcept; | |||
| static double getMidiNoteInHertz (int noteNumber, double frequencyOfA = 440.0) noexcept; | |||
| /** Returns true if the given midi note number is a black key. */ | |||
| static bool isMidiNoteBlack (int noteNumber) noexcept; | |||
| @@ -899,21 +905,29 @@ public: | |||
| */ | |||
| static const char* getControllerName (int controllerNumber); | |||
| /** Converts a floating-point value between 0 and 1 to a MIDI 7-bit value between 0 and 127. */ | |||
| static uint8 floatValueToMidiByte (float valueBetween0and1) noexcept; | |||
| /** Converts a pitchbend value in semitones to a MIDI 14-bit pitchwheel position value. */ | |||
| static uint16 pitchbendToPitchwheelPos (float pitchbendInSemitones, | |||
| float pitchbendRangeInSemitones) noexcept; | |||
| private: | |||
| //============================================================================== | |||
| double timeStamp; | |||
| HeapBlock<uint8> allocatedData; | |||
| int size; | |||
| #ifndef DOXYGEN | |||
| union | |||
| union PackedData | |||
| { | |||
| uint8 asBytes[4]; | |||
| uint32 asInt32; | |||
| } preallocatedData; | |||
| uint8* allocatedData; | |||
| uint8 asBytes[sizeof (uint8*)]; | |||
| }; | |||
| PackedData packedData; | |||
| double timeStamp; | |||
| int size; | |||
| #endif | |||
| inline uint8* getData() noexcept { return allocatedData != nullptr ? allocatedData.getData() : preallocatedData.asBytes; } | |||
| inline bool isHeapAllocated() const noexcept { return size > (int) sizeof (packedData); } | |||
| inline uint8* getData() const noexcept { return isHeapAllocated() ? packedData.allocatedData : (uint8*) packedData.asBytes; } | |||
| uint8* allocateSpace (int); | |||
| }; | |||
| @@ -80,7 +80,7 @@ int MidiMessageSequence::getIndexOfMatchingKeyUp (const int index) const noexcep | |||
| return -1; | |||
| } | |||
| int MidiMessageSequence::getIndexOf (MidiEventHolder* const event) const noexcept | |||
| int MidiMessageSequence::getIndexOf (const MidiEventHolder* const event) const noexcept | |||
| { | |||
| return list.indexOf (event); | |||
| } | |||
| @@ -48,6 +48,18 @@ public: | |||
| /** Replaces this sequence with another one. */ | |||
| MidiMessageSequence& operator= (const MidiMessageSequence&); | |||
| #if JUCE_COMPILER_SUPPORTS_MOVE_SEMANTICS | |||
| MidiMessageSequence (MidiMessageSequence&& other) noexcept | |||
| : list (static_cast<OwnedArray<MidiEventHolder>&&> (other.list)) | |||
| {} | |||
| MidiMessageSequence& operator= (MidiMessageSequence&& other) noexcept | |||
| { | |||
| list = static_cast<OwnedArray<MidiEventHolder>&&> (other.list); | |||
| return *this; | |||
| } | |||
| #endif | |||
| /** Destructor. */ | |||
| ~MidiMessageSequence(); | |||
| @@ -109,7 +121,7 @@ public: | |||
| int getIndexOfMatchingKeyUp (int index) const noexcept; | |||
| /** Returns the index of an event. */ | |||
| int getIndexOf (MidiEventHolder* event) const noexcept; | |||
| int getIndexOf (const MidiEventHolder* event) const noexcept; | |||
| /** Returns the index of the first event on or after the given timestamp. | |||
| If the time is beyond the end of the sequence, this will return the | |||
| @@ -22,7 +22,6 @@ | |||
| ============================================================================== | |||
| */ | |||
| MidiRPNDetector::MidiRPNDetector() noexcept | |||
| { | |||
| } | |||
| @@ -55,7 +54,7 @@ void MidiRPNDetector::reset() noexcept | |||
| } | |||
| //============================================================================== | |||
| MidiRPNDetector::ChannelState::ChannelState () noexcept | |||
| MidiRPNDetector::ChannelState::ChannelState() noexcept | |||
| : parameterMSB (0xff), parameterLSB (0xff), valueMSB (0xff), valueLSB (0xff), isNRPN (false) | |||
| { | |||
| } | |||
| @@ -39,6 +39,9 @@ MPEInstrument::MPEInstrument() noexcept | |||
| pressureDimension.value = &MPENote::pressure; | |||
| timbreDimension.value = &MPENote::timbre; | |||
| // the default value for pressure is 0, for all other dimension it is centre (= default MPEValue) | |||
| std::fill_n (pressureDimension.lastValueReceivedOnChannel, 16, MPEValue::minValue()); | |||
| legacyMode.isEnabled = false; | |||
| legacyMode.pitchbendRange = 2; | |||
| legacyMode.channelRange = Range<int> (1, 17); | |||
| @@ -271,22 +274,6 @@ void MPEInstrument::handleTimbreLSB (int midiChannel, int value) noexcept | |||
| lastTimbreLowerBitReceivedOnChannel[midiChannel - 1] = uint8 (value); | |||
| } | |||
| //============================================================================== | |||
| MPEValue MPEInstrument::getInitialPitchbendForNoteOn (int midiChannel, int /*midiNoteNumber*/, MPEValue /*midiNoteOnVelocity*/) const | |||
| { | |||
| return pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1]; | |||
| } | |||
| MPEValue MPEInstrument::getInitialPressureForNoteOn (int /*midiChannel*/, int /*midiNoteNumber*/, MPEValue midiNoteOnVelocity) const | |||
| { | |||
| return midiNoteOnVelocity; | |||
| } | |||
| MPEValue MPEInstrument::getInitialTimbreForNoteOn (int midiChannel, int /*midiNoteNumber*/, MPEValue /*midiNoteOnVelocity*/) const | |||
| { | |||
| return timbreDimension.lastValueReceivedOnChannel[midiChannel - 1]; | |||
| } | |||
| //============================================================================== | |||
| void MPEInstrument::noteOn (int midiChannel, | |||
| int midiNoteNumber, | |||
| @@ -298,9 +285,9 @@ void MPEInstrument::noteOn (int midiChannel, | |||
| MPENote newNote (midiChannel, | |||
| midiNoteNumber, | |||
| midiNoteOnVelocity, | |||
| getInitialPitchbendForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), | |||
| getInitialPressureForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), | |||
| getInitialTimbreForNoteOn (midiChannel, midiNoteNumber, midiNoteOnVelocity), | |||
| getInitialValueForNewNote (midiChannel, pitchbendDimension), | |||
| getInitialValueForNewNote (midiChannel, pressureDimension), | |||
| getInitialValueForNewNote (midiChannel, timbreDimension), | |||
| isNoteChannelSustained[midiChannel - 1] ? MPENote::keyDownAndSustained : MPENote::keyDown); | |||
| const ScopedLock sl (lock); | |||
| @@ -324,7 +311,7 @@ void MPEInstrument::noteOff (int midiChannel, | |||
| int midiNoteNumber, | |||
| MPEValue midiNoteOffVelocity) | |||
| { | |||
| if (notes.empty() || ! isNoteChannel (midiChannel)) | |||
| if (notes.isEmpty() || ! isNoteChannel (midiChannel)) | |||
| return; | |||
| const ScopedLock sl (lock); | |||
| @@ -334,10 +321,11 @@ void MPEInstrument::noteOff (int midiChannel, | |||
| note->keyState = (note->keyState == MPENote::keyDownAndSustained) ? MPENote::sustained : MPENote::off; | |||
| note->noteOffVelocity = midiNoteOffVelocity; | |||
| // last pitchbend and timbre values received for this note should not be re-used for | |||
| // last dimension values received for this note should not be re-used for | |||
| // any new notes, so reset them: | |||
| pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue(); | |||
| timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue(); | |||
| pressureDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::minValue(); | |||
| pitchbendDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); | |||
| timbreDimension.lastValueReceivedOnChannel[midiChannel - 1] = MPEValue::centreValue(); | |||
| if (note->keyState == MPENote::off) | |||
| { | |||
| @@ -370,12 +358,20 @@ void MPEInstrument::timbre (int midiChannel, MPEValue value) | |||
| updateDimension (midiChannel, timbreDimension, value); | |||
| } | |||
| MPEValue MPEInstrument::getInitialValueForNewNote (int midiChannel, MPEDimension& dimension) const | |||
| { | |||
| if (getLastNotePlayedPtr (midiChannel) != nullptr) | |||
| return &dimension == &pressureDimension ? MPEValue::minValue() : MPEValue::centreValue(); | |||
| return dimension.lastValueReceivedOnChannel[midiChannel - 1]; | |||
| } | |||
| //============================================================================== | |||
| void MPEInstrument::updateDimension (int midiChannel, MPEDimension& dimension, MPEValue value) | |||
| { | |||
| dimension.lastValueReceivedOnChannel[midiChannel - 1] = value; | |||
| if (notes.empty()) | |||
| if (notes.isEmpty()) | |||
| return; | |||
| if (MPEZone* zone = zoneLayout.getZoneByMasterChannel (midiChannel)) | |||
| @@ -758,7 +754,7 @@ public: | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| expectEquals (test.getNumPlayingNotes(), 1); | |||
| expectEquals (test.noteAddedCallCounter, 1); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| // note-off | |||
| test.noteOff (3, 60, MPEValue::from7BitInt (33)); | |||
| @@ -774,13 +770,13 @@ public: | |||
| // note off with non-matching note number shouldn't do anything | |||
| test.noteOff (3, 61, MPEValue::from7BitInt (33)); | |||
| expectEquals (test.getNumPlayingNotes(), 1); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteReleasedCallCounter, 0); | |||
| // note off with non-matching midi channel shouldn't do anything | |||
| test.noteOff (2, 60, MPEValue::from7BitInt (33)); | |||
| expectEquals (test.getNumPlayingNotes(), 1); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteReleasedCallCounter, 0); | |||
| } | |||
| { | |||
| @@ -791,9 +787,9 @@ public: | |||
| test.noteOn (3, 1, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 2, MPEValue::from7BitInt (100)); | |||
| expectEquals (test.getNumPlayingNotes(), 3); | |||
| expectNote (test.getNote (3, 0), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 1), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 2), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 0), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 1), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 2), 100, 0, 8192, 64, MPENote::keyDown); | |||
| } | |||
| { | |||
| // pathological case: second note-on for same note should retrigger it. | |||
| @@ -802,7 +798,7 @@ public: | |||
| test.noteOn (3, 0, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 0, MPEValue::from7BitInt (60)); | |||
| expectEquals (test.getNumPlayingNotes(), 1); | |||
| expectNote (test.getNote (3, 0), 60, 60, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 0), 60, 0, 8192, 64, MPENote::keyDown); | |||
| } | |||
| } | |||
| @@ -844,42 +840,42 @@ public: | |||
| // sustain pedal on per-note channel shouldn't do anything. | |||
| test.sustainPedal (3, true); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 0); | |||
| // sustain pedal on non-zone channel shouldn't do anything either. | |||
| test.sustainPedal (1, true); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 0); | |||
| // sustain pedal on master channel should sustain notes on *that* zone. | |||
| test.sustainPedal (2, true); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 1); | |||
| // release | |||
| test.sustainPedal (2, false); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 2); | |||
| // should also sustain new notes added after the press | |||
| test.sustainPedal (2, true); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 3); | |||
| test.noteOn (4, 51, MPEValue::from7BitInt (100)); | |||
| expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||
| expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 3); | |||
| // ...but only if that sustain came on the master channel of that zone! | |||
| test.sustainPedal (11, true); | |||
| test.noteOn (11, 52, MPEValue::from7BitInt (100)); | |||
| expectNote (test.getNote (11, 52), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (11, 52), 100, 0, 8192, 64, MPENote::keyDown); | |||
| test.noteOff (11, 52, MPEValue::from7BitInt (100)); | |||
| expectEquals (test.noteReleasedCallCounter, 1); | |||
| @@ -890,8 +886,8 @@ public: | |||
| expectEquals (test.getNumPlayingNotes(), 2); | |||
| expectEquals (test.noteReleasedCallCounter, 2); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 5); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||
| expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::sustained); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||
| expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::sustained); | |||
| // notes should be turned off when pedal is released | |||
| test.sustainPedal (2, false); | |||
| @@ -908,26 +904,26 @@ public: | |||
| // sostenuto pedal on per-note channel shouldn't do anything. | |||
| test.sostenutoPedal (3, true); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 0); | |||
| // sostenuto pedal on non-zone channel shouldn't do anything either. | |||
| test.sostenutoPedal (1, true); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 0); | |||
| // sostenuto pedal on master channel should sustain notes on *that* zone. | |||
| test.sostenutoPedal (2, true); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 1); | |||
| // release | |||
| test.sostenutoPedal (2, false); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 2); | |||
| // should only sustain notes turned on *before* the press (difference to sustain pedal) | |||
| @@ -935,9 +931,9 @@ public: | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 3); | |||
| test.noteOn (4, 51, MPEValue::from7BitInt (100)); | |||
| expectEquals (test.getNumPlayingNotes(), 3); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDownAndSustained); | |||
| expectNote (test.getNote (4, 51), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDownAndSustained); | |||
| expectNote (test.getNote (4, 51), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 3); | |||
| // note-off should not turn off sustained notes inside the same zone, | |||
| @@ -946,7 +942,7 @@ public: | |||
| test.noteOff (4, 51, MPEValue::from7BitInt (100)); | |||
| test.noteOff (10, 60, MPEValue::from7BitInt (100)); // not affected! | |||
| expectEquals (test.getNumPlayingNotes(), 1); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||
| expectEquals (test.noteReleasedCallCounter, 2); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 4); | |||
| @@ -1047,22 +1043,22 @@ public: | |||
| // applying pressure on a per-note channel should modulate one note | |||
| test.pressure (3, MPEValue::from7BitInt (33)); | |||
| expectNote (test.getNote (3, 60), 100, 33, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePressureChangedCallCounter, 1); | |||
| // applying pressure on a master channel should modulate all notes in this zone | |||
| test.pressure (2, MPEValue::from7BitInt (44)); | |||
| expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePressureChangedCallCounter, 3); | |||
| // applying pressure on an unrelated channel should be ignored | |||
| test.pressure (1, MPEValue::from7BitInt (55)); | |||
| expectNote (test.getNote (3, 60), 100, 44, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 44, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePressureChangedCallCounter, 3); | |||
| } | |||
| { | |||
| @@ -1073,7 +1069,7 @@ public: | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pressure (3, MPEValue::from7BitInt (66)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 66, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePressureChangedCallCounter, 1); | |||
| } | |||
| @@ -1091,6 +1087,49 @@ public: | |||
| expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePressureChangedCallCounter, 1); | |||
| } | |||
| { | |||
| UnitTestInstrument test; | |||
| test.setZoneLayout (testLayout); | |||
| // if no pressure is sent before note-on, default = 0 should be used | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| } | |||
| { | |||
| UnitTestInstrument test; | |||
| test.setZoneLayout (testLayout); | |||
| // if pressure is sent before note-on, use that | |||
| test.pressure (3, MPEValue::from7BitInt (77)); | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| expectNote (test.getNote (3, 60), 100, 77, 8192, 64, MPENote::keyDown); | |||
| } | |||
| { | |||
| UnitTestInstrument test; | |||
| test.setZoneLayout (testLayout); | |||
| // if pressure is sent before note-on, but it belonged to another note | |||
| // on the same channel that has since been turned off, use default = 0 | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pressure (3, MPEValue::from7BitInt (77)); | |||
| test.noteOff (3, 61, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| } | |||
| { | |||
| UnitTestInstrument test; | |||
| test.setZoneLayout (testLayout); | |||
| // edge case: two notes on the same channel simultaneously. the second one should use | |||
| // pressure = 0 initially but then react to additional pressure messages | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pressure (3, MPEValue::from7BitInt (77)); | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| test.pressure (3, MPEValue::from7BitInt (78)); | |||
| expectNote (test.getNote (3, 60), 100, 78, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 77, 8192, 64, MPENote::keyDown); | |||
| } | |||
| } | |||
| beginTest ("pitchbend"); | |||
| @@ -1105,9 +1144,9 @@ public: | |||
| // applying pitchbend on a per-note channel should modulate one note | |||
| test.pitchbend (3, MPEValue::from14BitInt (1111)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | |||
| // applying pitchbend on a master channel should be ignored for the | |||
| @@ -1115,16 +1154,16 @@ public: | |||
| // Note: noteChanged will be called anyway for notes in that zone | |||
| // because the total pitchbend for those notes has changed | |||
| test.pitchbend (2, MPEValue::from14BitInt (2222)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePitchbendChangedCallCounter, 3); | |||
| // applying pitchbend on an unrelated channel should do nothing. | |||
| test.pitchbend (1, MPEValue::from14BitInt (3333)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 1111, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 1111, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePitchbendChangedCallCounter, 3); | |||
| } | |||
| { | |||
| @@ -1135,8 +1174,8 @@ public: | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (3, MPEValue::from14BitInt (4444)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 4444, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 4444, 64, MPENote::keyDown); | |||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1150,7 +1189,7 @@ public: | |||
| test.noteOff (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (3, MPEValue::from14BitInt (5555)); | |||
| expectEquals (test.getNumPlayingNotes(), 1); | |||
| expectNote (test.getNote (3, 60), 100, 100, 5555, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); | |||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1169,14 +1208,14 @@ public: | |||
| test.sustainPedal (2, true); | |||
| test.noteOff (3, 60, MPEValue::from7BitInt (64)); | |||
| expectEquals (test.getNumPlayingNotes(), 1); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||
| expectEquals (test.noteKeyStateChangedCallCounter, 2); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (3, MPEValue::from14BitInt (6666)); | |||
| expectEquals (test.getNumPlayingNotes(), 2); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::sustained); | |||
| expectNote (test.getNote (3, 61), 100, 100, 6666, 64, MPENote::keyDownAndSustained); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::sustained); | |||
| expectNote (test.getNote (3, 61), 100, 0, 6666, 64, MPENote::keyDownAndSustained); | |||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1193,11 +1232,11 @@ public: | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (3, MPEValue::from14BitInt (5555)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 5555, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 5555, 64, MPENote::keyDown); | |||
| test.noteOff (3, 60, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| } | |||
| { | |||
| // applying per-note pitchbend should set the note's totalPitchbendInSemitones | |||
| @@ -1289,23 +1328,23 @@ public: | |||
| // modulating timbre on a per-note channel should modulate one note | |||
| test.timbre (3, MPEValue::from7BitInt (33)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 33, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 33, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | |||
| // modulating timbre on a master channel should modulate all notes in this zone | |||
| test.timbre (2, MPEValue::from7BitInt (44)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteTimbreChangedCallCounter, 3); | |||
| // modulating timbre on an unrelated channel should be ignored | |||
| test.timbre (1, MPEValue::from7BitInt (55)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 44, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 44, MPENote::keyDown); | |||
| expectNote (test.getNote (10, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteTimbreChangedCallCounter, 3); | |||
| } | |||
| { | |||
| @@ -1316,8 +1355,8 @@ public: | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.timbre (3, MPEValue::from7BitInt (66)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 66, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 66, MPENote::keyDown); | |||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1331,7 +1370,7 @@ public: | |||
| test.noteOff (3, 61, MPEValue::from7BitInt (100)); | |||
| test.timbre (3, MPEValue::from7BitInt (77)); | |||
| expectEquals (test.getNumPlayingNotes(), 1); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 77, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 77, MPENote::keyDown); | |||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1341,11 +1380,11 @@ public: | |||
| // Zsolt's edge case for timbre | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| test.timbre (3, MPEValue::from7BitInt (42)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 42, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 42, MPENote::keyDown); | |||
| test.noteOff (3, 60, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| } | |||
| } | |||
| @@ -1361,8 +1400,8 @@ public: | |||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pressure (3, MPEValue::from7BitInt (99)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 99, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePressureChangedCallCounter, 1); | |||
| } | |||
| @@ -1377,8 +1416,8 @@ public: | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pressure (3, MPEValue::from7BitInt (99)); | |||
| expectNote (test.getNote (3, 60), 100, 99, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePressureChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1391,9 +1430,9 @@ public: | |||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pressure (3, MPEValue::from7BitInt (99)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 99, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePressureChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1425,9 +1464,9 @@ public: | |||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (3, MPEValue::from14BitInt (9999)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1440,9 +1479,9 @@ public: | |||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (3, MPEValue::from14BitInt (9999)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1455,9 +1494,9 @@ public: | |||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (3, MPEValue::from14BitInt (9999)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.notePitchbendChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1470,9 +1509,9 @@ public: | |||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (3, MPEValue::from14BitInt (9999)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||
| expectEquals (test.notePitchbendChangedCallCounter, 3); | |||
| } | |||
| } | |||
| @@ -1489,9 +1528,9 @@ public: | |||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.timbre (3, MPEValue::from7BitInt (99)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 99, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); | |||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1504,9 +1543,9 @@ public: | |||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.timbre (3, MPEValue::from7BitInt (99)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 99, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1519,9 +1558,9 @@ public: | |||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.timbre (3, MPEValue::from7BitInt (99)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 99, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectEquals (test.noteTimbreChangedCallCounter, 1); | |||
| } | |||
| { | |||
| @@ -1534,9 +1573,9 @@ public: | |||
| test.noteOn (3, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| test.timbre (3, MPEValue::from7BitInt (99)); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 99, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 100, 8192, 99, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 100, 8192, 99, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 99, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 62), 100, 0, 8192, 99, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 61), 100, 0, 8192, 99, MPENote::keyDown); | |||
| expectEquals (test.noteTimbreChangedCallCounter, 3); | |||
| } | |||
| } | |||
| @@ -1724,7 +1763,7 @@ public: | |||
| expectEquals (test.getNumPlayingNotes(), 0); | |||
| } | |||
| beginTest ("default getInitial...ForNoteOn"); | |||
| beginTest ("default initial values for pitchbend and timbre"); | |||
| { | |||
| MPEInstrument test; | |||
| test.setZoneLayout (testLayout); | |||
| @@ -1739,16 +1778,7 @@ public: | |||
| test.noteOn (3, 60, MPEValue::from7BitInt (100)); | |||
| expectNote (test.getMostRecentNote (3), 100, 100, 3333, 66, MPENote::keyDown); | |||
| } | |||
| beginTest ("overriding getInitial...ForNoteOn"); | |||
| { | |||
| CustomInitialValuesTest<33, 4444, 55> test; | |||
| test.setZoneLayout (testLayout); | |||
| test.noteOn (3, 61, MPEValue::from7BitInt (100)); | |||
| expectNote (test.getMostRecentNote (3), 100, 33, 4444, 55, MPENote::keyDown); | |||
| expectNote (test.getMostRecentNote (3), 100, 0, 3333, 66, MPENote::keyDown); | |||
| } | |||
| beginTest ("Legacy mode"); | |||
| @@ -1806,10 +1836,10 @@ public: | |||
| test.pressure (2, MPEValue::from7BitInt (88)); | |||
| test.timbre (15, MPEValue::from7BitInt (77)); | |||
| expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (2, 60), 100, 88, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (15, 60), 100, 100, 8192, 77, MPENote::keyDown); | |||
| expectNote (test.getNote (16, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (15, 60), 100, 0, 8192, 77, MPENote::keyDown); | |||
| expectNote (test.getNote (16, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| // note off should work in legacy mode | |||
| @@ -1835,10 +1865,10 @@ public: | |||
| test.noteOn (16, 60, MPEValue::from7BitInt (100)); | |||
| expectEquals (test.getNumPlayingNotes(), 4); | |||
| expectNote (test.getNote (3, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (6, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (7, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (3, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (4, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (6, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (7, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| } | |||
| { | |||
| // tracking mode in legacy mode | |||
| @@ -1851,9 +1881,9 @@ public: | |||
| test.noteOn (1, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (1, 61, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (1, MPEValue::from14BitInt (9999)); | |||
| expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||
| } | |||
| { | |||
| UnitTestInstrument test; | |||
| @@ -1864,9 +1894,9 @@ public: | |||
| test.noteOn (1, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (1, 61, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (1, MPEValue::from14BitInt (9999)); | |||
| expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 62), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 62), 100, 0, 8192, 64, MPENote::keyDown); | |||
| } | |||
| { | |||
| UnitTestInstrument test; | |||
| @@ -1877,9 +1907,9 @@ public: | |||
| test.noteOn (1, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (1, 61, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (1, MPEValue::from14BitInt (9999)); | |||
| expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 61), 100, 100, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 61), 100, 0, 8192, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||
| } | |||
| { | |||
| UnitTestInstrument test; | |||
| @@ -1890,9 +1920,9 @@ public: | |||
| test.noteOn (1, 62, MPEValue::from7BitInt (100)); | |||
| test.noteOn (1, 61, MPEValue::from7BitInt (100)); | |||
| test.pitchbend (1, MPEValue::from14BitInt (9999)); | |||
| expectNote (test.getNote (1, 60), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 61), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 62), 100, 100, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 60), 100, 0, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 61), 100, 0, 9999, 64, MPENote::keyDown); | |||
| expectNote (test.getNote (1, 62), 100, 0, 9999, 64, MPENote::keyDown); | |||
| } | |||
| } | |||
| { | |||
| @@ -1916,7 +1946,7 @@ public: | |||
| test.noteOff (1, 60, MPEValue::from7BitInt (100)); | |||
| expectEquals (test.getNumPlayingNotes(), 1); | |||
| expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::sustained); | |||
| expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); | |||
| test.sustainPedal (1, false); | |||
| expectEquals (test.getNumPlayingNotes(), 0); | |||
| @@ -1939,7 +1969,7 @@ public: | |||
| test.noteOff (2, 61, MPEValue::from7BitInt (100)); | |||
| expectEquals (test.getNumPlayingNotes(), 1); | |||
| expectNote (test.getNote (1, 60), 100, 100, 8192, 64, MPENote::sustained); | |||
| expectNote (test.getNote (1, 60), 100, 0, 8192, 64, MPENote::sustained); | |||
| test.sostenutoPedal (1, false); | |||
| expectEquals (test.getNumPlayingNotes(), 0); | |||
| @@ -2081,26 +2111,6 @@ private: | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| template <int initial7BitPressure, int initial14BitPitchbend, int initial7BitTimbre> | |||
| class CustomInitialValuesTest : public MPEInstrument | |||
| { | |||
| MPEValue getInitialPitchbendForNoteOn (int, int, MPEValue) const override | |||
| { | |||
| return MPEValue::from14BitInt (initial14BitPitchbend); | |||
| } | |||
| MPEValue getInitialPressureForNoteOn (int, int, MPEValue) const override | |||
| { | |||
| return MPEValue::from7BitInt (initial7BitPressure); | |||
| } | |||
| MPEValue getInitialTimbreForNoteOn (int, int, MPEValue) const override | |||
| { | |||
| return MPEValue::from7BitInt (initial7BitTimbre); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| void expectNote (MPENote noteToTest, | |||
| int noteOnVelocity7Bit, | |||
| @@ -320,36 +320,6 @@ public: | |||
| /** Re-sets the pitchbend range in semitones (0-96) to be used for notes when in legacy mode. */ | |||
| void setLegacyModePitchbendRange (int pitchbendRange); | |||
| protected: | |||
| //============================================================================== | |||
| /** This method defines what initial pitchbend value should be used for newly | |||
| triggered notes. The default is to use the last pitchbend value | |||
| that has been received on the same MIDI channel (or no pitchbend | |||
| if no pitchbend messages have been received so far). | |||
| Override this method if you need different behaviour. | |||
| */ | |||
| virtual MPEValue getInitialPitchbendForNoteOn (int midiChannel, | |||
| int midiNoteNumber, | |||
| MPEValue midiNoteOnVelocity) const; | |||
| /** This method defines what initial pressure value should be used for newly | |||
| triggered notes. The default is to re-use the note-on velocity value. | |||
| Override this method if you need different behaviour. | |||
| */ | |||
| virtual MPEValue getInitialPressureForNoteOn (int midiChannel, | |||
| int midiNoteNumber, | |||
| MPEValue midiNoteOnVelocity) const; | |||
| /** This method defines what initial timbre value should be used for newly | |||
| triggered notes. The default is to use the last timbre value that has | |||
| that has been received on the same MIDI channel (or a neutral centred value | |||
| if no pitchbend messages have been received so far). | |||
| Override this method if you need different behaviour. | |||
| */ | |||
| virtual MPEValue getInitialTimbreForNoteOn (int midiChannel, | |||
| int midiNoteNumber, | |||
| MPEValue midiNoteOnVelocity) const; | |||
| private: | |||
| //============================================================================== | |||
| CriticalSection lock; | |||
| @@ -384,6 +354,7 @@ private: | |||
| void updateDimensionMaster (MPEZone&, MPEDimension&, MPEValue); | |||
| void updateDimensionForNote (MPENote&, MPEDimension&, MPEValue); | |||
| void callListenersDimensionChanged (MPENote&, MPEDimension&); | |||
| MPEValue getInitialValueForNewNote (int midiChannel, MPEDimension&) const; | |||
| void processMidiNoteOnMessage (const MidiMessage&); | |||
| void processMidiNoteOffMessage (const MidiMessage&); | |||
| @@ -22,7 +22,6 @@ | |||
| ============================================================================== | |||
| */ | |||
| MidiBuffer MPEMessages::addZone (MPEZone zone) | |||
| { | |||
| MidiBuffer buffer (MidiRPNGenerator::generate (zone.getFirstNoteChannel(), | |||
| @@ -25,7 +25,8 @@ | |||
| MPESynthesiserBase::MPESynthesiserBase() | |||
| : instrument (new MPEInstrument), | |||
| sampleRate (0), | |||
| minimumSubBlockSize (32) | |||
| minimumSubBlockSize (32), | |||
| subBlockSubdivisionIsStrict (false) | |||
| { | |||
| instrument->addListener (this); | |||
| } | |||
| @@ -100,10 +101,11 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||
| MidiBuffer::Iterator midiIterator (inputMidi); | |||
| midiIterator.setNextSamplePosition (startSample); | |||
| bool firstEvent = true; | |||
| int midiEventPos; | |||
| MidiMessage m; | |||
| const ScopedLock sl (renderAudioLock); | |||
| const ScopedLock sl (noteStateLock); | |||
| while (numSamples > 0) | |||
| { | |||
| @@ -122,12 +124,14 @@ void MPESynthesiserBase::renderNextBlock (AudioBuffer<floatType>& outputAudio, | |||
| break; | |||
| } | |||
| if (samplesToNextMidiMessage < minimumSubBlockSize) | |||
| if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||
| { | |||
| handleMidiEvent (m); | |||
| continue; | |||
| } | |||
| firstEvent = false; | |||
| renderNextSubBlock (outputAudio, startSample, samplesToNextMidiMessage); | |||
| handleMidiEvent (m); | |||
| startSample += samplesToNextMidiMessage; | |||
| @@ -147,15 +151,16 @@ void MPESynthesiserBase::setCurrentPlaybackSampleRate (const double newRate) | |||
| { | |||
| if (sampleRate != newRate) | |||
| { | |||
| const ScopedLock sl (renderAudioLock); | |||
| const ScopedLock sl (noteStateLock); | |||
| instrument->releaseAllNotes(); | |||
| sampleRate = newRate; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples) noexcept | |||
| void MPESynthesiserBase::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept | |||
| { | |||
| jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | |||
| minimumSubBlockSize = numSamples; | |||
| subBlockSubdivisionIsStrict = shouldBeStrict; | |||
| } | |||
| @@ -127,8 +127,14 @@ public: | |||
| The default setting is 32, which means that midi messages are accurate to about < 1ms | |||
| accuracy, which is probably fine for most purposes, but you may want to increase or | |||
| decrease this value for your synth. | |||
| If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||
| If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||
| to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||
| (this can sometimes help to avoid quantisation or phasing issues). | |||
| */ | |||
| void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; | |||
| void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||
| //============================================================================== | |||
| /** Puts the synthesiser into legacy mode. | |||
| @@ -179,13 +185,13 @@ protected: | |||
| //============================================================================== | |||
| /** @internal */ | |||
| ScopedPointer<MPEInstrument> instrument; | |||
| /** @internal */ | |||
| CriticalSection renderAudioLock; | |||
| private: | |||
| //============================================================================== | |||
| CriticalSection noteStateLock; | |||
| double sampleRate; | |||
| int minimumSubBlockSize; | |||
| bool subBlockSubdivisionIsStrict; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (MPESynthesiserBase) | |||
| }; | |||
| @@ -69,27 +69,35 @@ struct JUCE_API MPEZone | |||
| int perNotePitchbendRange = 48, | |||
| int masterPitchbendRange = 2) noexcept; | |||
| /* Returns the MIDI master channel of this zone. */ | |||
| /* Returns the MIDI master channel number (in the range 1-16) of this zone. */ | |||
| int getMasterChannel() const noexcept; | |||
| /** Returns the number of note channels occupied by this zone. */ | |||
| int getNumNoteChannels() const noexcept; | |||
| /* Returns the MIDI channel number of the lowest-numbered note channel of this zone. */ | |||
| /* Returns the MIDI channel number (in the range 1-16) of the | |||
| lowest-numbered note channel of this zone. | |||
| */ | |||
| int getFirstNoteChannel() const noexcept; | |||
| /* Returns the MIDI channel number of the highest-numbered note channel of this zone. */ | |||
| /* Returns the MIDI channel number (in the range 1-16) of the | |||
| highest-numbered note channel of this zone. | |||
| */ | |||
| int getLastNoteChannel() const noexcept; | |||
| /** Returns the MIDI channel numbers of the note channels of this zone as a Range. */ | |||
| /** Returns the MIDI channel numbers (in the range 1-16) of the | |||
| note channels of this zone as a Range. | |||
| */ | |||
| Range<int> getNoteChannelRange() const noexcept; | |||
| /** Returns true if the MIDI channel (in the range 1-16) is used by this zone | |||
| either as a note channel or as the master channel; false otherwise. */ | |||
| either as a note channel or as the master channel; false otherwise. | |||
| */ | |||
| bool isUsingChannel (int channel) const noexcept; | |||
| /** Returns true if the MIDI channel (in the range 1-16) is used by this zone | |||
| as a note channel; false otherwise. */ | |||
| as a note channel; false otherwise. | |||
| */ | |||
| bool isUsingChannelAsNoteChannel (int channel) const noexcept; | |||
| /** Returns the per-note pitchbend range in semitones set for this zone. */ | |||
| @@ -34,6 +34,7 @@ MPEZoneLayout::MPEZoneLayout (const MPEZoneLayout& other) | |||
| MPEZoneLayout& MPEZoneLayout::operator= (const MPEZoneLayout& other) | |||
| { | |||
| zones = other.zones; | |||
| listeners.call (&MPEZoneLayout::Listener::zoneLayoutChanged, *this); | |||
| return *this; | |||
| } | |||
| @@ -100,8 +100,9 @@ public: | |||
| /** Returns the current number of MPE zones. */ | |||
| int getNumZones() const noexcept; | |||
| /** Returns a pointer to the MPE zone at the given index, | |||
| or nullptr if there is no such zone. | |||
| /** Returns a pointer to the MPE zone at the given index, or nullptr if there | |||
| is no such zone. Zones are sorted by insertion order (most recently added | |||
| zone last). | |||
| */ | |||
| MPEZone* getZoneByIndex (int index) const noexcept; | |||
| @@ -26,7 +26,8 @@ BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, | |||
| TimeSliceThread& thread, | |||
| const bool deleteSourceWhenDeleted, | |||
| const int bufferSizeSamples, | |||
| const int numChannels) | |||
| const int numChannels, | |||
| bool prefillBufferOnPrepareToPlay) | |||
| : source (s, deleteSourceWhenDeleted), | |||
| backgroundThread (thread), | |||
| numberOfSamplesToBuffer (jmax (1024, bufferSizeSamples)), | |||
| @@ -36,7 +37,8 @@ BufferingAudioSource::BufferingAudioSource (PositionableAudioSource* s, | |||
| nextPlayPos (0), | |||
| sampleRate (0), | |||
| wasSourceLooping (false), | |||
| isPrepared (false) | |||
| isPrepared (false), | |||
| prefillBuffer (prefillBufferOnPrepareToPlay) | |||
| { | |||
| jassert (source != nullptr); | |||
| @@ -73,12 +75,13 @@ void BufferingAudioSource::prepareToPlay (int samplesPerBlockExpected, double ne | |||
| backgroundThread.addTimeSliceClient (this); | |||
| while (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, | |||
| buffer.getNumSamples() / 2)) | |||
| do | |||
| { | |||
| backgroundThread.moveToFrontOfQueue (this); | |||
| Thread::sleep (5); | |||
| } | |||
| while (prefillBuffer | |||
| && (bufferValidEnd - bufferValidStart < jmin (((int) newSampleRate) / 4, buffer.getNumSamples() / 2))); | |||
| } | |||
| } | |||
| @@ -88,7 +91,12 @@ void BufferingAudioSource::releaseResources() | |||
| backgroundThread.removeTimeSliceClient (this); | |||
| buffer.setSize (numberOfChannels, 0); | |||
| source->releaseResources(); | |||
| // MSVC2015 seems to need this if statement to not generate a warning during linking. | |||
| // As source is set in the constructor, there is no way that source could | |||
| // ever equal this, but it seems to make MSVC2015 happy. | |||
| if (source != this) | |||
| source->releaseResources(); | |||
| } | |||
| void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info) | |||
| @@ -148,6 +156,41 @@ void BufferingAudioSource::getNextAudioBlock (const AudioSourceChannelInfo& info | |||
| } | |||
| } | |||
| bool BufferingAudioSource::waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout) | |||
| { | |||
| if (!source || source->getTotalLength() <= 0) | |||
| return false; | |||
| if (nextPlayPos + info.numSamples < 0) | |||
| return true; | |||
| if (! isLooping() && nextPlayPos > getTotalLength()) | |||
| return true; | |||
| const uint32 endTime = Time::getMillisecondCounter() + timeout; | |||
| uint32 now = Time::getMillisecondCounter(); | |||
| while (now < endTime) | |||
| { | |||
| { | |||
| const ScopedLock sl (bufferStartPosLock); | |||
| const int validStart = static_cast<int> (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos) - nextPlayPos); | |||
| const int validEnd = static_cast<int> (jlimit (bufferValidStart, bufferValidEnd, nextPlayPos + info.numSamples) - nextPlayPos); | |||
| if (validStart <= 0 && validStart < validEnd && validEnd >= info.numSamples) | |||
| return true; | |||
| } | |||
| if (! bufferReadyEvent.wait (static_cast<int> (endTime - now))) | |||
| return false; | |||
| now = Time::getMillisecondCounter(); | |||
| } | |||
| return false; | |||
| } | |||
| int64 BufferingAudioSource::getNextReadPosition() const | |||
| { | |||
| jassert (source->getTotalLength() > 0); | |||
| @@ -241,6 +284,8 @@ bool BufferingAudioSource::readNextBufferChunk() | |||
| bufferValidEnd = newBVE; | |||
| } | |||
| bufferReadyEvent.signal(); | |||
| return true; | |||
| } | |||
| @@ -43,21 +43,24 @@ public: | |||
| //============================================================================== | |||
| /** Creates a BufferingAudioSource. | |||
| @param source the input source to read from | |||
| @param backgroundThread a background thread that will be used for the | |||
| background read-ahead. This object must not be deleted | |||
| until after any BufferingAudioSources that are using it | |||
| have been deleted! | |||
| @param deleteSourceWhenDeleted if true, then the input source object will | |||
| be deleted when this object is deleted | |||
| @param numberOfSamplesToBuffer the size of buffer to use for reading ahead | |||
| @param numberOfChannels the number of channels that will be played | |||
| @param source the input source to read from | |||
| @param backgroundThread a background thread that will be used for the | |||
| background read-ahead. This object must not be deleted | |||
| until after any BufferingAudioSources that are using it | |||
| have been deleted! | |||
| @param deleteSourceWhenDeleted if true, then the input source object will | |||
| be deleted when this object is deleted | |||
| @param numberOfSamplesToBuffer the size of buffer to use for reading ahead | |||
| @param numberOfChannels the number of channels that will be played | |||
| @param prefillBufferOnPrepareToPlay if true, then calling prepareToPlay on this object will | |||
| block until the buffer has been filled | |||
| */ | |||
| BufferingAudioSource (PositionableAudioSource* source, | |||
| TimeSliceThread& backgroundThread, | |||
| bool deleteSourceWhenDeleted, | |||
| int numberOfSamplesToBuffer, | |||
| int numberOfChannels = 2); | |||
| int numberOfChannels = 2, | |||
| bool prefillBufferOnPrepareToPlay = true); | |||
| /** Destructor. | |||
| @@ -89,6 +92,12 @@ public: | |||
| /** Implements the PositionableAudioSource method. */ | |||
| bool isLooping() const override { return source->isLooping(); } | |||
| /** A useful function to block until the next the buffer info can be filled. | |||
| This is useful for offline rendering. | |||
| */ | |||
| bool waitForNextAudioBlockReady (const AudioSourceChannelInfo& info, const uint32 timeout); | |||
| private: | |||
| //============================================================================== | |||
| OptionalScopedPointer<PositionableAudioSource> source; | |||
| @@ -96,9 +105,10 @@ private: | |||
| int numberOfSamplesToBuffer, numberOfChannels; | |||
| AudioSampleBuffer buffer; | |||
| CriticalSection bufferStartPosLock; | |||
| WaitableEvent bufferReadyEvent; | |||
| int64 volatile bufferValidStart, bufferValidEnd, nextPlayPos; | |||
| double volatile sampleRate; | |||
| bool wasSourceLooping, isPrepared; | |||
| bool wasSourceLooping, isPrepared, prefillBuffer; | |||
| bool readNextBufferChunk(); | |||
| void readBufferSection (int64 start, int length, int bufferOffset); | |||
| @@ -30,7 +30,7 @@ | |||
| /** | |||
| A type of AudioSource that takes an input source and changes its sample rate. | |||
| @see AudioSource | |||
| @see AudioSource, LagrangeInterpolator, CatmullRomInterpolator | |||
| */ | |||
| class JUCE_API ResamplingAudioSource : public AudioSource | |||
| { | |||
| @@ -88,6 +88,7 @@ Synthesiser::Synthesiser() | |||
| : sampleRate (0), | |||
| lastNoteOnCounter (0), | |||
| minimumSubBlockSize (32), | |||
| subBlockSubdivisionIsStrict (false), | |||
| shouldStealNotes (true) | |||
| { | |||
| for (int i = 0; i < numElementsInArray (lastPitchWheelValues); ++i) | |||
| @@ -147,10 +148,11 @@ void Synthesiser::setNoteStealingEnabled (const bool shouldSteal) | |||
| shouldStealNotes = shouldSteal; | |||
| } | |||
| void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples) noexcept | |||
| void Synthesiser::setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict) noexcept | |||
| { | |||
| jassert (numSamples > 0); // it wouldn't make much sense for this to be less than 1 | |||
| minimumSubBlockSize = numSamples; | |||
| subBlockSubdivisionIsStrict = shouldBeStrict; | |||
| } | |||
| //============================================================================== | |||
| @@ -177,10 +179,12 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||
| { | |||
| // must set the sample rate before using this! | |||
| jassert (sampleRate != 0); | |||
| const int targetChannels = outputAudio.getNumChannels(); | |||
| MidiBuffer::Iterator midiIterator (midiData); | |||
| midiIterator.setNextSamplePosition (startSample); | |||
| bool firstEvent = true; | |||
| int midiEventPos; | |||
| MidiMessage m; | |||
| @@ -190,7 +194,9 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||
| { | |||
| if (! midiIterator.getNextEvent (m, midiEventPos)) | |||
| { | |||
| renderVoices (outputAudio, startSample, numSamples); | |||
| if (targetChannels > 0) | |||
| renderVoices (outputAudio, startSample, numSamples); | |||
| return; | |||
| } | |||
| @@ -198,18 +204,24 @@ void Synthesiser::processNextBlock (AudioBuffer<floatType>& outputAudio, | |||
| if (samplesToNextMidiMessage >= numSamples) | |||
| { | |||
| renderVoices (outputAudio, startSample, numSamples); | |||
| if (targetChannels > 0) | |||
| renderVoices (outputAudio, startSample, numSamples); | |||
| handleMidiEvent (m); | |||
| break; | |||
| } | |||
| if (samplesToNextMidiMessage < minimumSubBlockSize) | |||
| if (samplesToNextMidiMessage < ((firstEvent && ! subBlockSubdivisionIsStrict) ? 1 : minimumSubBlockSize)) | |||
| { | |||
| handleMidiEvent (m); | |||
| continue; | |||
| } | |||
| renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||
| firstEvent = false; | |||
| if (targetChannels > 0) | |||
| renderVoices (outputAudio, startSample, samplesToNextMidiMessage); | |||
| handleMidiEvent (m); | |||
| startSample += samplesToNextMidiMessage; | |||
| numSamples -= samplesToNextMidiMessage; | |||
| @@ -539,8 +539,14 @@ public: | |||
| The default setting is 32, which means that midi messages are accurate to about < 1ms | |||
| accuracy, which is probably fine for most purposes, but you may want to increase or | |||
| decrease this value for your synth. | |||
| If shouldBeStrict is true, the audio sub-blocks will strictly never be smaller than numSamples. | |||
| If shouldBeStrict is false (default), the first audio sub-block in the buffer is allowed | |||
| to be smaller, to make sure that the first MIDI event in a buffer will always be sample-accurate | |||
| (this can sometimes help to avoid quantisation or phasing issues). | |||
| */ | |||
| void setMinimumRenderingSubdivisionSize (int numSamples) noexcept; | |||
| void setMinimumRenderingSubdivisionSize (int numSamples, bool shouldBeStrict = false) noexcept; | |||
| protected: | |||
| //============================================================================== | |||
| @@ -615,6 +621,7 @@ private: | |||
| double sampleRate; | |||
| uint32 lastNoteOnCounter; | |||
| int minimumSubBlockSize; | |||
| bool subBlockSubdivisionIsStrict; | |||
| bool shouldStealNotes; | |||
| BigInteger sustainPedalsDown; | |||
| @@ -86,72 +86,12 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CallbackHandler) | |||
| }; | |||
| //============================================================================== | |||
| // This is an AudioTransportSource which will own it's assigned source | |||
| struct AudioSourceOwningTransportSource : public AudioTransportSource | |||
| { | |||
| AudioSourceOwningTransportSource (PositionableAudioSource* s) : source (s) | |||
| { | |||
| AudioTransportSource::setSource (s); | |||
| } | |||
| ~AudioSourceOwningTransportSource() | |||
| { | |||
| setSource (nullptr); | |||
| } | |||
| private: | |||
| ScopedPointer<PositionableAudioSource> source; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSourceOwningTransportSource) | |||
| }; | |||
| //============================================================================== | |||
| // An AudioSourcePlayer which will remove itself from the AudioDeviceManager's | |||
| // callback list once it finishes playing its source | |||
| struct AutoRemovingSourcePlayer : public AudioSourcePlayer, | |||
| private Timer | |||
| { | |||
| AutoRemovingSourcePlayer (AudioDeviceManager& dm, AudioTransportSource* ts, bool ownSource) | |||
| : manager (dm), transportSource (ts, ownSource) | |||
| { | |||
| jassert (ts != nullptr); | |||
| manager.addAudioCallback (this); | |||
| AudioSourcePlayer::setSource (transportSource); | |||
| startTimerHz (10); | |||
| } | |||
| ~AutoRemovingSourcePlayer() | |||
| { | |||
| setSource (nullptr); | |||
| manager.removeAudioCallback (this); | |||
| } | |||
| void timerCallback() override | |||
| { | |||
| if (getCurrentSource() == nullptr || ! transportSource->isPlaying()) | |||
| delete this; | |||
| } | |||
| void audioDeviceStopped() override | |||
| { | |||
| AudioSourcePlayer::audioDeviceStopped(); | |||
| setSource (nullptr); | |||
| } | |||
| private: | |||
| AudioDeviceManager& manager; | |||
| OptionalScopedPointer<AudioTransportSource> transportSource; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AutoRemovingSourcePlayer) | |||
| }; | |||
| //============================================================================== | |||
| AudioDeviceManager::AudioDeviceManager() | |||
| : numInputChansNeeded (0), | |||
| numOutputChansNeeded (2), | |||
| listNeedsScanning (true), | |||
| inputLevel (0), | |||
| testSoundPosition (0), | |||
| cpuUsageMs (0), | |||
| timeToCpuScale (0) | |||
| { | |||
| @@ -162,10 +102,6 @@ AudioDeviceManager::~AudioDeviceManager() | |||
| { | |||
| currentAudioDevice = nullptr; | |||
| defaultMidiOutput = nullptr; | |||
| for (int i = 0; i < callbacks.size(); ++i) | |||
| if (AutoRemovingSourcePlayer* p = dynamic_cast<AutoRemovingSourcePlayer*> (callbacks.getUnchecked(i))) | |||
| delete p; | |||
| } | |||
| //============================================================================== | |||
| @@ -346,8 +282,8 @@ String AudioDeviceManager::initialiseFromXML (const XmlElement& xml, | |||
| currentDeviceType = availableDeviceTypes.getUnchecked(0)->getTypeName(); | |||
| } | |||
| setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize"); | |||
| setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate"); | |||
| setup.bufferSize = xml.getIntAttribute ("audioDeviceBufferSize", setup.bufferSize); | |||
| setup.sampleRate = xml.getDoubleAttribute ("audioDeviceRate", setup.sampleRate); | |||
| setup.inputChannels .parseString (xml.getStringAttribute ("audioDeviceInChans", "11"), 2); | |||
| setup.outputChannels.parseString (xml.getStringAttribute ("audioDeviceOutChans", "11"), 2); | |||
| @@ -650,6 +586,8 @@ void AudioDeviceManager::stopDevice() | |||
| { | |||
| if (currentAudioDevice != nullptr) | |||
| currentAudioDevice->stop(); | |||
| testSound = nullptr; | |||
| } | |||
| void AudioDeviceManager::closeAudioDevice() | |||
| @@ -761,31 +699,8 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||
| { | |||
| const ScopedLock sl (audioCallbackLock); | |||
| if (inputLevelMeasurementEnabledCount.get() > 0 && numInputChannels > 0) | |||
| { | |||
| for (int j = 0; j < numSamples; ++j) | |||
| { | |||
| float s = 0; | |||
| for (int i = 0; i < numInputChannels; ++i) | |||
| s += std::abs (inputChannelData[i][j]); | |||
| s /= numInputChannels; | |||
| const double decayFactor = 0.99992; | |||
| if (s > inputLevel) | |||
| inputLevel = s; | |||
| else if (inputLevel > 0.001f) | |||
| inputLevel *= decayFactor; | |||
| else | |||
| inputLevel = 0; | |||
| } | |||
| } | |||
| else | |||
| { | |||
| inputLevel = 0; | |||
| } | |||
| inputLevelMeter.updateLevel (inputChannelData, numInputChannels, numSamples); | |||
| outputLevelMeter.updateLevel (const_cast<const float**> (outputChannelData), numOutputChannels, numSamples); | |||
| if (callbacks.size() > 0) | |||
| { | |||
| @@ -821,6 +736,20 @@ void AudioDeviceManager::audioDeviceIOCallbackInt (const float** inputChannelDat | |||
| for (int i = 0; i < numOutputChannels; ++i) | |||
| zeromem (outputChannelData[i], sizeof (float) * (size_t) numSamples); | |||
| } | |||
| if (testSound != nullptr) | |||
| { | |||
| const int numSamps = jmin (numSamples, testSound->getNumSamples() - testSoundPosition); | |||
| const float* const src = testSound->getReadPointer (0, testSoundPosition); | |||
| for (int i = 0; i < numOutputChannels; ++i) | |||
| for (int j = 0; j < numSamps; ++j) | |||
| outputChannelData [i][j] += src[j]; | |||
| testSoundPosition += numSamps; | |||
| if (testSoundPosition >= testSound->getNumSamples()) | |||
| testSound = nullptr; | |||
| } | |||
| } | |||
| void AudioDeviceManager::audioDeviceAboutToStartInt (AudioIODevice* const device) | |||
| @@ -989,170 +918,87 @@ void AudioDeviceManager::setDefaultMidiOutput (const String& deviceName) | |||
| } | |||
| //============================================================================== | |||
| // An AudioSource which simply outputs a buffer | |||
| class AudioSampleBufferSource : public PositionableAudioSource | |||
| { | |||
| public: | |||
| AudioSampleBufferSource (AudioSampleBuffer* audioBuffer, bool ownBuffer, bool playOnAllChannels) | |||
| : buffer (audioBuffer, ownBuffer), | |||
| position (0), looping (false), playAcrossAllChannels (playOnAllChannels) | |||
| {} | |||
| AudioDeviceManager::LevelMeter::LevelMeter() noexcept : level() {} | |||
| //============================================================================== | |||
| void setNextReadPosition (int64 newPosition) override | |||
| { | |||
| jassert (newPosition >= 0); | |||
| if (looping) | |||
| newPosition = newPosition % static_cast<int64> (buffer->getNumSamples()); | |||
| position = jmin (buffer->getNumSamples(), static_cast<int> (newPosition)); | |||
| } | |||
| int64 getNextReadPosition() const override { return static_cast<int64> (position); } | |||
| int64 getTotalLength() const override { return static_cast<int64> (buffer->getNumSamples()); } | |||
| bool isLooping() const override { return looping; } | |||
| void setLooping (bool shouldLoop) override { looping = shouldLoop; } | |||
| //============================================================================== | |||
| void prepareToPlay (int, double) override {} | |||
| void releaseResources() override {} | |||
| void getNextAudioBlock (const AudioSourceChannelInfo& bufferToFill) override | |||
| void AudioDeviceManager::LevelMeter::updateLevel (const float* const* channelData, int numChannels, int numSamples) noexcept | |||
| { | |||
| if (enabled.get() != 0 && numChannels > 0) | |||
| { | |||
| bufferToFill.clearActiveBufferRegion(); | |||
| const int bufferSize = buffer->getNumSamples(); | |||
| const int samplesNeeded = bufferToFill.numSamples; | |||
| const int samplesToCopy = jmin (bufferSize - position, samplesNeeded); | |||
| if (samplesToCopy > 0) | |||
| for (int j = 0; j < numSamples; ++j) | |||
| { | |||
| int maxInChannels = buffer->getNumChannels(); | |||
| int maxOutChannels = bufferToFill.buffer->getNumChannels(); | |||
| if (! playAcrossAllChannels) | |||
| maxOutChannels = jmin (maxOutChannels, maxInChannels); | |||
| for (int i = 0; i < maxOutChannels; ++i) | |||
| bufferToFill.buffer->copyFrom (i, bufferToFill.startSample, *buffer, | |||
| i % maxInChannels, position, samplesToCopy); | |||
| } | |||
| position += samplesNeeded; | |||
| float s = 0; | |||
| if (looping) | |||
| position %= bufferSize; | |||
| } | |||
| for (int i = 0; i < numChannels; ++i) | |||
| s += std::abs (channelData[i][j]); | |||
| private: | |||
| //============================================================================== | |||
| OptionalScopedPointer<AudioSampleBuffer> buffer; | |||
| int position; | |||
| bool looping, playAcrossAllChannels; | |||
| s /= numChannels; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioSampleBufferSource) | |||
| }; | |||
| void AudioDeviceManager::playSound (const File& file) | |||
| { | |||
| if (file.existsAsFile()) | |||
| { | |||
| AudioFormatManager formatManager; | |||
| const double decayFactor = 0.99992; | |||
| formatManager.registerBasicFormats(); | |||
| playSound (formatManager.createReaderFor (file), true); | |||
| if (s > level) | |||
| level = s; | |||
| else if (level > 0.001f) | |||
| level *= decayFactor; | |||
| else | |||
| level = 0; | |||
| } | |||
| } | |||
| } | |||
| void AudioDeviceManager::playSound (const void* resourceData, size_t resourceSize) | |||
| { | |||
| if (resourceData != nullptr && resourceSize > 0) | |||
| else | |||
| { | |||
| AudioFormatManager formatManager; | |||
| formatManager.registerBasicFormats(); | |||
| MemoryInputStream* mem = new MemoryInputStream (resourceData, resourceSize, false); | |||
| playSound (formatManager.createReaderFor (mem), true); | |||
| level = 0; | |||
| } | |||
| } | |||
| void AudioDeviceManager::playSound (AudioFormatReader* reader, bool deleteWhenFinished) | |||
| void AudioDeviceManager::LevelMeter::setEnabled (bool shouldBeEnabled) noexcept | |||
| { | |||
| if (reader != nullptr) | |||
| playSound (new AudioFormatReaderSource (reader, deleteWhenFinished), true); | |||
| enabled.set (shouldBeEnabled ? 1 : 0); | |||
| level = 0; | |||
| } | |||
| void AudioDeviceManager::playSound (AudioSampleBuffer* buffer, bool deleteWhenFinished, bool playOnAllOutputChannels) | |||
| double AudioDeviceManager::LevelMeter::getCurrentLevel() const noexcept | |||
| { | |||
| if (buffer != nullptr) | |||
| playSound (new AudioSampleBufferSource (buffer, deleteWhenFinished, playOnAllOutputChannels), true); | |||
| jassert (enabled.get() != 0); // you need to call setEnabled (true) before using this! | |||
| return level; | |||
| } | |||
| void AudioDeviceManager::playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished) | |||
| void AudioDeviceManager::playTestSound() | |||
| { | |||
| if (audioSource != nullptr && currentAudioDevice != nullptr) | |||
| { | |||
| AudioTransportSource* transport = dynamic_cast<AudioTransportSource*> (audioSource); | |||
| { // cunningly nested to swap, unlock and delete in that order. | |||
| ScopedPointer<AudioSampleBuffer> oldSound; | |||
| if (transport == nullptr) | |||
| { | |||
| if (deleteWhenFinished) | |||
| { | |||
| transport = new AudioSourceOwningTransportSource (audioSource); | |||
| } | |||
| else | |||
| { | |||
| transport = new AudioTransportSource(); | |||
| transport->setSource (audioSource); | |||
| deleteWhenFinished = true; | |||
| } | |||
| const ScopedLock sl (audioCallbackLock); | |||
| oldSound = testSound; | |||
| } | |||
| transport->start(); | |||
| new AutoRemovingSourcePlayer (*this, transport, deleteWhenFinished); | |||
| } | |||
| else | |||
| { | |||
| if (deleteWhenFinished) | |||
| delete audioSource; | |||
| } | |||
| } | |||
| void AudioDeviceManager::playTestSound() | |||
| { | |||
| const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||
| const int soundLength = (int) sampleRate; | |||
| testSoundPosition = 0; | |||
| const double frequency = 440.0; | |||
| const float amplitude = 0.5f; | |||
| const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||
| if (currentAudioDevice != nullptr) | |||
| { | |||
| const double sampleRate = currentAudioDevice->getCurrentSampleRate(); | |||
| const int soundLength = (int) sampleRate; | |||
| AudioSampleBuffer* newSound = new AudioSampleBuffer (1, soundLength); | |||
| const double frequency = 440.0; | |||
| const float amplitude = 0.5f; | |||
| for (int i = 0; i < soundLength; ++i) | |||
| newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); | |||
| const double phasePerSample = double_Pi * 2.0 / (sampleRate / frequency); | |||
| newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); | |||
| newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); | |||
| AudioSampleBuffer* const newSound = new AudioSampleBuffer (1, soundLength); | |||
| playSound (newSound, true, true); | |||
| } | |||
| for (int i = 0; i < soundLength; ++i) | |||
| newSound->setSample (0, i, amplitude * (float) std::sin (i * phasePerSample)); | |||
| //============================================================================== | |||
| void AudioDeviceManager::enableInputLevelMeasurement (const bool enableMeasurement) | |||
| { | |||
| if (enableMeasurement) | |||
| ++inputLevelMeasurementEnabledCount; | |||
| else | |||
| --inputLevelMeasurementEnabledCount; | |||
| newSound->applyGainRamp (0, 0, soundLength / 10, 0.0f, 1.0f); | |||
| newSound->applyGainRamp (0, soundLength - soundLength / 4, soundLength / 4, 1.0f, 0.0f); | |||
| inputLevel = 0; | |||
| const ScopedLock sl (audioCallbackLock); | |||
| testSound = newSound; | |||
| } | |||
| } | |||
| double AudioDeviceManager::getCurrentInputLevel() const | |||
| { | |||
| jassert (inputLevelMeasurementEnabledCount.get() > 0); // you need to call enableInputLevelMeasurement() before using this! | |||
| return inputLevel; | |||
| } | |||
| double AudioDeviceManager::getCurrentInputLevel() const noexcept { return inputLevelMeter.getCurrentLevel(); } | |||
| double AudioDeviceManager::getCurrentOutputLevel() const noexcept { return outputLevelMeter.getCurrentLevel(); } | |||
| void AudioDeviceManager::enableInputLevelMeasurement (bool enable) noexcept { inputLevelMeter.setEnabled (enable); } | |||
| void AudioDeviceManager::enableOutputLevelMeasurement (bool enable) noexcept { outputLevelMeter.setEnabled (enable); } | |||
| @@ -404,78 +404,28 @@ public: | |||
| */ | |||
| void playTestSound(); | |||
| /** Plays a sound from a file. */ | |||
| void playSound (const File& file); | |||
| /** Convenient method to play sound from a JUCE resource. */ | |||
| void playSound (const void* resourceData, size_t resourceSize); | |||
| /** Plays the sound from an audio format reader. | |||
| If deleteWhenFinished is true then the format reader will be | |||
| automatically deleted once the sound has finished playing. | |||
| */ | |||
| void playSound (AudioFormatReader* buffer, bool deleteWhenFinished = false); | |||
| /** Plays the sound from a positionable audio source. | |||
| This will output the sound coming from a positionable audio source. | |||
| This gives you slightly more control over the sound playback compared | |||
| to the other playSound methods. For example, if you would like to | |||
| stop the sound prematurely you can call this method with a | |||
| TransportAudioSource and then call audioSource->stop. Note that, | |||
| you must call audioSource->start to start the playback, if your | |||
| audioSource is a TransportAudioSource. | |||
| The audio device manager will not hold any references to this audio | |||
| source once the audio source has stopped playing for any reason, | |||
| for example when the sound has finished playing or when you have | |||
| called audioSource->stop. Therefore, calling audioSource->start() on | |||
| a finished audioSource will not restart the sound again. If this is | |||
| desired simply call playSound with the same audioSource again. | |||
| @param audioSource the audio source to play | |||
| @param deleteWhenFinished If this is true then the audio source will | |||
| be deleted once the device manager has finished playing. | |||
| */ | |||
| void playSound (PositionableAudioSource* audioSource, bool deleteWhenFinished = false); | |||
| /** Plays the sound from an audio sample buffer. | |||
| This will output the sound contained in an audio sample buffer. If | |||
| deleteWhenFinished is true then the audio sample buffer will be | |||
| automatically deleted once the sound has finished playing. | |||
| If playOnAllOutputChannels is true, then if there are more output channels | |||
| than buffer channels, then the ones that are available will be re-used on | |||
| multiple outputs so that something is sent to all output channels. If it | |||
| is false, then the buffer will just be played on the first output channels. | |||
| */ | |||
| void playSound (AudioSampleBuffer* buffer, | |||
| bool deleteWhenFinished = false, | |||
| bool playOnAllOutputChannels = false); | |||
| //============================================================================== | |||
| /** Turns on level-measuring. | |||
| When enabled, the device manager will measure the peak input level | |||
| across all channels, and you can get this level by calling getCurrentInputLevel(). | |||
| This is mainly intended for audio setup UI panels to use to create a mic | |||
| level display, so that the user can check that they've selected the right | |||
| device. | |||
| /** Turns on level-measuring for input channels. | |||
| @see getCurrentInputLevel() | |||
| */ | |||
| void enableInputLevelMeasurement (bool enableMeasurement) noexcept; | |||
| A simple filter is used to make the level decay smoothly, but this is | |||
| only intended for giving rough feedback, and not for any kind of accurate | |||
| measurement. | |||
| /** Turns on level-measuring for output channels. | |||
| @see getCurrentOutputLevel() | |||
| */ | |||
| void enableInputLevelMeasurement (bool enableMeasurement); | |||
| void enableOutputLevelMeasurement (bool enableMeasurement) noexcept; | |||
| /** Returns the current input level. | |||
| To use this, you must first enable it by calling enableInputLevelMeasurement(). | |||
| See enableInputLevelMeasurement() for more info. | |||
| @see enableInputLevelMeasurement() | |||
| */ | |||
| double getCurrentInputLevel() const; | |||
| double getCurrentInputLevel() const noexcept; | |||
| /** Returns the current output level. | |||
| To use this, you must first enable it by calling enableOutputLevelMeasurement(). | |||
| @see enableOutputLevelMeasurement() | |||
| */ | |||
| double getCurrentOutputLevel() const noexcept; | |||
| /** Returns the a lock that can be used to synchronise access to the audio callback. | |||
| Obviously while this is locked, you're blocking the audio thread from running, so | |||
| @@ -502,8 +452,6 @@ private: | |||
| BigInteger inputChannels, outputChannels; | |||
| ScopedPointer<XmlElement> lastExplicitSettings; | |||
| mutable bool listNeedsScanning; | |||
| Atomic<int> inputLevelMeasurementEnabledCount; | |||
| double inputLevel; | |||
| AudioSampleBuffer tempBuffer; | |||
| struct MidiCallbackInfo | |||
| @@ -520,8 +468,24 @@ private: | |||
| ScopedPointer<MidiOutput> defaultMidiOutput; | |||
| CriticalSection audioCallbackLock, midiCallbackLock; | |||
| ScopedPointer<AudioSampleBuffer> testSound; | |||
| int testSoundPosition; | |||
| double cpuUsageMs, timeToCpuScale; | |||
| struct LevelMeter | |||
| { | |||
| LevelMeter() noexcept; | |||
| void updateLevel (const float* const*, int numChannels, int numSamples) noexcept; | |||
| void setEnabled (bool) noexcept; | |||
| double getCurrentLevel() const noexcept; | |||
| Atomic<int> enabled; | |||
| double level; | |||
| }; | |||
| LevelMeter inputLevelMeter, outputLevelMeter; | |||
| //============================================================================== | |||
| class CallbackHandler; | |||
| friend class CallbackHandler; | |||
| @@ -31,6 +31,8 @@ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| #include "AppConfig.h" | |||
| #define JUCE_CORE_INCLUDE_OBJC_HELPERS 1 | |||
| #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | |||
| #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | |||
| @@ -45,7 +47,6 @@ | |||
| #define Component CarbonDummyCompName | |||
| #import <CoreAudio/AudioHardware.h> | |||
| #import <CoreMIDI/MIDIServices.h> | |||
| #import <DiscRecording/DiscRecording.h> | |||
| #import <AudioToolbox/AudioServices.h> | |||
| #undef Point | |||
| #undef Component | |||
| @@ -55,10 +56,14 @@ | |||
| #import <AVFoundation/AVFoundation.h> | |||
| #import <CoreMIDI/MIDIServices.h> | |||
| #if TARGET_OS_SIMULATOR | |||
| #import <CoreMIDI/MIDINetworkSession.h> | |||
| #endif | |||
| //============================================================================== | |||
| #elif JUCE_WINDOWS | |||
| #if JUCE_WASAPI | |||
| #include <MMReg.h> | |||
| #include <mmreg.h> | |||
| #endif | |||
| #if JUCE_ASIO | |||
| @@ -84,15 +89,6 @@ | |||
| #include <iasiodrv.h> | |||
| #endif | |||
| #if JUCE_USE_CDBURNER | |||
| /* You'll need the Platform SDK for these headers - if you don't have it and don't | |||
| need to use CD-burning, then you might just want to set the JUCE_USE_CDBURNER flag | |||
| to 0, to avoid these includes. | |||
| */ | |||
| #include <imapi.h> | |||
| #include <imapierror.h> | |||
| #endif | |||
| //============================================================================== | |||
| #elif JUCE_LINUX | |||
| #if JUCE_ALSA | |||
| @@ -139,7 +135,6 @@ namespace juce | |||
| #include "audio_io/juce_AudioIODeviceType.cpp" | |||
| #include "midi_io/juce_MidiMessageCollector.cpp" | |||
| #include "midi_io/juce_MidiOutput.cpp" | |||
| #include "audio_cd/juce_AudioCDReader.cpp" | |||
| #include "sources/juce_AudioSourcePlayer.cpp" | |||
| #include "sources/juce_AudioTransportSource.cpp" | |||
| #include "native/juce_MidiDataConcatenator.h" | |||
| @@ -149,14 +144,6 @@ namespace juce | |||
| #include "native/juce_mac_CoreAudio.cpp" | |||
| #include "native/juce_mac_CoreMidi.cpp" | |||
| #if JUCE_USE_CDREADER | |||
| #include "native/juce_mac_AudioCDReader.mm" | |||
| #endif | |||
| #if JUCE_USE_CDBURNER | |||
| #include "native/juce_mac_AudioCDBurner.mm" | |||
| #endif | |||
| //============================================================================== | |||
| #elif JUCE_IOS | |||
| #include "native/juce_ios_Audio.cpp" | |||
| @@ -179,14 +166,6 @@ namespace juce | |||
| #include "native/juce_win32_ASIO.cpp" | |||
| #endif | |||
| #if JUCE_USE_CDREADER | |||
| #include "native/juce_win32_AudioCDReader.cpp" | |||
| #endif | |||
| #if JUCE_USE_CDBURNER | |||
| #include "native/juce_win32_AudioCDBurner.cpp" | |||
| #endif | |||
| //============================================================================== | |||
| #elif JUCE_LINUX | |||
| #if JUCE_ALSA | |||
| @@ -199,10 +178,6 @@ namespace juce | |||
| #include "native/juce_linux_JackAudio.cpp" | |||
| #endif | |||
| #if JUCE_USE_CDREADER | |||
| #include "native/juce_linux_AudioCDReader.cpp" | |||
| #endif | |||
| //============================================================================== | |||
| #elif JUCE_ANDROID | |||
| #include "native/juce_android_Audio.cpp" | |||
| @@ -22,12 +22,39 @@ | |||
| ============================================================================== | |||
| */ | |||
| /******************************************************************************* | |||
| The block below describes the properties of this module, and is read by | |||
| the Projucer to automatically generate project code that uses it. | |||
| For details about the syntax and how to create or use a module, see the | |||
| JUCE Module Format.txt file. | |||
| BEGIN_JUCE_MODULE_DECLARATION | |||
| ID: juce_audio_devices | |||
| vendor: juce | |||
| version: 4.3.0 | |||
| name: JUCE audio and MIDI I/O device classes | |||
| description: Classes to play and record from audio and MIDI I/O devices | |||
| website: http://www.juce.com/juce | |||
| license: GPL/Commercial | |||
| dependencies: juce_audio_basics, juce_events | |||
| OSXFrameworks: CoreAudio CoreMIDI AudioToolbox | |||
| iOSFrameworks: CoreAudio CoreMIDI AudioToolbox AVFoundation | |||
| linuxPackages: alsa | |||
| mingwLibs: winmm | |||
| END_JUCE_MODULE_DECLARATION | |||
| *******************************************************************************/ | |||
| #ifndef JUCE_AUDIO_DEVICES_H_INCLUDED | |||
| #define JUCE_AUDIO_DEVICES_H_INCLUDED | |||
| #include "../juce_events/juce_events.h" | |||
| #include "../juce_audio_basics/juce_audio_basics.h" | |||
| #include "../juce_audio_formats/juce_audio_formats.h" | |||
| #include <juce_events/juce_events.h> | |||
| #include <juce_audio_basics/juce_audio_basics.h> | |||
| //============================================================================== | |||
| /** Config: JUCE_ASIO | |||
| @@ -90,21 +117,6 @@ | |||
| #endif | |||
| #endif | |||
| //============================================================================== | |||
| /** Config: JUCE_USE_CDREADER | |||
| Enables the AudioCDReader class (on supported platforms). | |||
| */ | |||
| #ifndef JUCE_USE_CDREADER | |||
| #define JUCE_USE_CDREADER 0 | |||
| #endif | |||
| /** Config: JUCE_USE_CDBURNER | |||
| Enables the AudioCDBurner class (on supported platforms). | |||
| */ | |||
| #ifndef JUCE_USE_CDBURNER | |||
| #define JUCE_USE_CDBURNER 0 | |||
| #endif | |||
| //============================================================================== | |||
| namespace juce | |||
| { | |||
| @@ -117,8 +129,6 @@ namespace juce | |||
| #include "midi_io/juce_MidiOutput.h" | |||
| #include "sources/juce_AudioSourcePlayer.h" | |||
| #include "sources/juce_AudioTransportSource.h" | |||
| #include "audio_cd/juce_AudioCDBurner.h" | |||
| #include "audio_cd/juce_AudioCDReader.h" | |||
| #include "audio_io/juce_AudioDeviceManager.h" | |||
| } | |||
| @@ -71,8 +71,7 @@ public: | |||
| // the normal message, handle it now.. | |||
| if (*d >= 0xf8 && *d <= 0xfe) | |||
| { | |||
| const MidiMessage m (*d++, time); | |||
| callback.handleIncomingMidiMessage (input, m); | |||
| callback.handleIncomingMidiMessage (input, MidiMessage (*d++, time)); | |||
| --numBytes; | |||
| } | |||
| else | |||
| @@ -83,7 +82,15 @@ public: | |||
| data[len++] = *d++; | |||
| --numBytes; | |||
| if (len >= MidiMessage::getMessageLengthFromFirstByte (data[0])) | |||
| const uint8 firstByte = data[0]; | |||
| if (firstByte < 0x80 || firstByte == 0xf7) | |||
| { | |||
| len = 0; | |||
| break; // ignore this malformed MIDI message.. | |||
| } | |||
| if (len >= MidiMessage::getMessageLengthFromFirstByte (firstByte)) | |||
| break; | |||
| } | |||
| } | |||
| @@ -153,7 +153,7 @@ JUCE_JNI_CALLBACK (JUCE_JOIN_MACRO (JUCE_ANDROID_ACTIVITY_CLASSNAME, _00024JuceM | |||
| class AndroidMidiDeviceManager | |||
| { | |||
| public: | |||
| AndroidMidiDeviceManager () | |||
| AndroidMidiDeviceManager() | |||
| : deviceManager (android.activity.callObjectMethod (JuceAppActivity.getAndroidMidiDeviceManager)) | |||
| { | |||
| } | |||
| @@ -332,7 +332,7 @@ private: | |||
| void run() override | |||
| { | |||
| setThreadToAudioPriority (); | |||
| setThreadToAudioPriority(); | |||
| if (recorder != nullptr) recorder->start(); | |||
| if (player != nullptr) player->start(); | |||
| @@ -341,7 +341,7 @@ private: | |||
| processBuffers(); | |||
| } | |||
| void setThreadToAudioPriority () | |||
| void setThreadToAudioPriority() | |||
| { | |||
| // see android.os.Process.THREAD_PRIORITY_AUDIO | |||
| const int THREAD_PRIORITY_AUDIO = -16; | |||
| @@ -287,7 +287,14 @@ public: | |||
| return r; | |||
| } | |||
| int getDefaultBufferSize() override { return 256; } | |||
| int getDefaultBufferSize() override | |||
| { | |||
| #if TARGET_IPHONE_SIMULATOR | |||
| return 512; | |||
| #else | |||
| return 256; | |||
| #endif | |||
| } | |||
| String open (const BigInteger& inputChannelsWanted, | |||
| const BigInteger& outputChannelsWanted, | |||
| @@ -431,6 +438,8 @@ public: | |||
| void handleStatusChange (bool enabled, const char* reason) | |||
| { | |||
| const ScopedLock myScopedLock (callbackLock); | |||
| JUCE_IOS_AUDIO_LOG ("handleStatusChange: enabled: " << (int) enabled << ", reason: " << reason); | |||
| isRunning = enabled; | |||
| @@ -447,6 +456,8 @@ public: | |||
| void handleRouteChange (const char* reason) | |||
| { | |||
| const ScopedLock myScopedLock (callbackLock); | |||
| JUCE_IOS_AUDIO_LOG ("handleRouteChange: reason: " << reason); | |||
| fixAudioRouteIfSetToReceiver(); | |||
| @@ -518,9 +529,9 @@ private: | |||
| if (audioInputIsAvailable && numInputChannels > 0) | |||
| err = AudioUnitRender (audioUnit, flags, time, 1, numFrames, data); | |||
| const ScopedLock sl (callbackLock); | |||
| const ScopedTryLock stl (callbackLock); | |||
| if (callback != nullptr) | |||
| if (stl.isLocked() && callback != nullptr) | |||
| { | |||
| if ((int) numFrames > floatData.getNumSamples()) | |||
| prepareFloatBuffers ((int) numFrames); | |||
| @@ -679,7 +690,23 @@ private: | |||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof (format)); | |||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof (format)); | |||
| UInt32 framesPerSlice; | |||
| UInt32 dataSize = sizeof (framesPerSlice); | |||
| AudioUnitInitialize (audioUnit); | |||
| AudioUnitSetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, | |||
| kAudioUnitScope_Global, 0, &actualBufferSize, sizeof (actualBufferSize)); | |||
| if (AudioUnitGetProperty (audioUnit, kAudioUnitProperty_MaximumFramesPerSlice, | |||
| kAudioUnitScope_Global, 0, &framesPerSlice, &dataSize) == noErr | |||
| && dataSize == sizeof (framesPerSlice) && static_cast<int> (framesPerSlice) != actualBufferSize) | |||
| { | |||
| actualBufferSize = static_cast<int> (framesPerSlice); | |||
| prepareFloatBuffers (actualBufferSize); | |||
| } | |||
| return true; | |||
| } | |||
| @@ -755,8 +782,24 @@ void AudioSessionHolder::handleStatusChange (bool enabled, const char* reason) c | |||
| void AudioSessionHolder::handleRouteChange (const char* reason) const | |||
| { | |||
| for (auto device: activeDevices) | |||
| device->handleRouteChange (reason); | |||
| struct RouteChangeMessage : public CallbackMessage | |||
| { | |||
| RouteChangeMessage (Array<iOSAudioIODevice*> devs, const char* r) | |||
| : devices (devs), changeReason (r) | |||
| { | |||
| } | |||
| void messageCallback() override | |||
| { | |||
| for (auto device: devices) | |||
| device->handleRouteChange (changeReason); | |||
| } | |||
| Array<iOSAudioIODevice*> devices; | |||
| const char* changeReason; | |||
| }; | |||
| (new RouteChangeMessage (activeDevices, reason))->post(); | |||
| } | |||
| #undef JUCE_NSERROR_CHECK | |||
| @@ -183,7 +183,7 @@ public: | |||
| // open output ports | |||
| const StringArray outputChannels (getOutputChannelNames()); | |||
| for (int i = 0; i < outputChannels.size (); ++i) | |||
| for (int i = 0; i < outputChannels.size(); ++i) | |||
| { | |||
| String outputName; | |||
| outputName << "out_" << ++totalNumberOfOutputChannels; | |||
| @@ -290,6 +290,8 @@ public: | |||
| } | |||
| } | |||
| updateActivePorts(); | |||
| return lastError; | |||
| } | |||
| @@ -45,37 +45,17 @@ class AlsaClient : public ReferenceCountedObject | |||
| public: | |||
| typedef ReferenceCountedObjectPtr<AlsaClient> Ptr; | |||
| AlsaClient (bool forInput) | |||
| : input (forInput), handle (nullptr) | |||
| static Ptr getInstance (bool forInput) | |||
| { | |||
| snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||
| : SND_SEQ_OPEN_OUTPUT, 0); | |||
| } | |||
| ~AlsaClient() | |||
| { | |||
| if (handle != nullptr) | |||
| { | |||
| snd_seq_close (handle); | |||
| handle = nullptr; | |||
| } | |||
| jassert (activeCallbacks.size() == 0); | |||
| AlsaClient*& instance = (forInput ? inInstance : outInstance); | |||
| if (instance == nullptr) | |||
| instance = new AlsaClient (forInput); | |||
| if (inputThread) | |||
| { | |||
| inputThread->stopThread (3000); | |||
| inputThread = nullptr; | |||
| } | |||
| return instance; | |||
| } | |||
| bool isInput() const noexcept { return input; } | |||
| void setName (const String& name) | |||
| { | |||
| snd_seq_set_client_name (handle, name.toUTF8()); | |||
| } | |||
| void registerCallback (AlsaPortAndCallback* cb) | |||
| { | |||
| if (cb != nullptr) | |||
| @@ -103,7 +83,8 @@ public: | |||
| inputThread->signalThreadShouldExit(); | |||
| } | |||
| void handleIncomingMidiMessage (const MidiMessage& message, int port); | |||
| void handleIncomingMidiMessage (snd_seq_event*, const MidiMessage&); | |||
| void handlePartialSysexMessage (snd_seq_event*, const uint8*, int, double); | |||
| snd_seq_t* get() const noexcept { return handle; } | |||
| @@ -114,12 +95,56 @@ private: | |||
| Array<AlsaPortAndCallback*> activeCallbacks; | |||
| CriticalSection callbackLock; | |||
| static AlsaClient* inInstance; | |||
| static AlsaClient* outInstance; | |||
| //============================================================================== | |||
| friend class ReferenceCountedObjectPtr<AlsaClient>; | |||
| friend struct ContainerDeletePolicy<AlsaClient>; | |||
| AlsaClient (bool forInput) | |||
| : input (forInput), handle (nullptr) | |||
| { | |||
| AlsaClient*& instance = (input ? inInstance : outInstance); | |||
| jassert (instance == nullptr); | |||
| instance = this; | |||
| snd_seq_open (&handle, "default", forInput ? SND_SEQ_OPEN_INPUT | |||
| : SND_SEQ_OPEN_OUTPUT, 0); | |||
| snd_seq_set_client_name (handle, forInput ? JUCE_ALSA_MIDI_INPUT_NAME | |||
| : JUCE_ALSA_MIDI_OUTPUT_NAME); | |||
| } | |||
| ~AlsaClient() | |||
| { | |||
| AlsaClient*& instance = (input ? inInstance : outInstance); | |||
| jassert (instance != nullptr); | |||
| instance = nullptr; | |||
| if (handle != nullptr) | |||
| { | |||
| snd_seq_close (handle); | |||
| handle = nullptr; | |||
| } | |||
| jassert (activeCallbacks.size() == 0); | |||
| if (inputThread) | |||
| { | |||
| inputThread->stopThread (3000); | |||
| inputThread = nullptr; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| class MidiInputThread : public Thread | |||
| { | |||
| public: | |||
| MidiInputThread (AlsaClient& c) | |||
| : Thread ("Juce MIDI Input"), client (c) | |||
| : Thread ("Juce MIDI Input"), client (c), concatenator (2048) | |||
| { | |||
| jassert (client.input && client.get() != nullptr); | |||
| } | |||
| @@ -159,13 +184,9 @@ private: | |||
| snd_midi_event_reset_decode (midiParser); | |||
| if (numBytes > 0) | |||
| { | |||
| const MidiMessage message ((const uint8*) buffer, (int) numBytes, | |||
| Time::getMillisecondCounter() * 0.001); | |||
| client.handleIncomingMidiMessage (message, inputEvent->dest.port); | |||
| } | |||
| concatenator.pushMidiData (buffer, (int) numBytes, | |||
| Time::getMillisecondCounter() * 0.001, | |||
| inputEvent, client); | |||
| snd_seq_free_event (inputEvent); | |||
| } | |||
| @@ -180,29 +201,14 @@ private: | |||
| private: | |||
| AlsaClient& client; | |||
| MidiDataConcatenator concatenator; | |||
| }; | |||
| ScopedPointer<MidiInputThread> inputThread; | |||
| }; | |||
| static AlsaClient::Ptr globalAlsaSequencerIn() | |||
| { | |||
| static AlsaClient::Ptr global (new AlsaClient (true)); | |||
| return global; | |||
| } | |||
| static AlsaClient::Ptr globalAlsaSequencerOut() | |||
| { | |||
| static AlsaClient::Ptr global (new AlsaClient (false)); | |||
| return global; | |||
| } | |||
| static AlsaClient::Ptr globalAlsaSequencer (bool input) | |||
| { | |||
| return input ? globalAlsaSequencerIn() | |||
| : globalAlsaSequencerOut(); | |||
| } | |||
| AlsaClient* AlsaClient::inInstance = nullptr; | |||
| AlsaClient* AlsaClient::outInstance = nullptr; | |||
| //============================================================================== | |||
| // represents an input or output port of the supplied AlsaClient | |||
| @@ -282,6 +288,11 @@ public: | |||
| callback->handleIncomingMidiMessage (midiInput, message); | |||
| } | |||
| void handlePartialSysexMessage (const uint8* messageData, int numBytesSoFar, double timeStamp) | |||
| { | |||
| callback->handlePartialSysexMessage (midiInput, messageData, numBytesSoFar, timeStamp); | |||
| } | |||
| private: | |||
| AlsaPort port; | |||
| MidiInput* midiInput; | |||
| @@ -291,14 +302,22 @@ private: | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AlsaPortAndCallback) | |||
| }; | |||
| void AlsaClient::handleIncomingMidiMessage (const MidiMessage& message, int port) | |||
| void AlsaClient::handleIncomingMidiMessage (snd_seq_event_t* event, const MidiMessage& message) | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| if (AlsaPortAndCallback* const cb = activeCallbacks[port]) | |||
| if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port]) | |||
| cb->handleIncomingMidiMessage (message); | |||
| } | |||
| void AlsaClient::handlePartialSysexMessage (snd_seq_event* event, const uint8* messageData, int numBytesSoFar, double timeStamp) | |||
| { | |||
| const ScopedLock sl (callbackLock); | |||
| if (AlsaPortAndCallback* const cb = activeCallbacks[event->dest.port]) | |||
| cb->handlePartialSysexMessage (messageData, numBytesSoFar, timeStamp); | |||
| } | |||
| //============================================================================== | |||
| static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, | |||
| snd_seq_client_info_t* clientInfo, | |||
| @@ -325,19 +344,23 @@ static AlsaPort iterateMidiClient (const AlsaClient::Ptr& seq, | |||
| && (snd_seq_port_info_get_capability (portInfo) & (forInput ? SND_SEQ_PORT_CAP_READ | |||
| : SND_SEQ_PORT_CAP_WRITE)) != 0) | |||
| { | |||
| deviceNamesFound.add (snd_seq_client_info_get_name (clientInfo)); | |||
| const String clientName = snd_seq_client_info_get_name (clientInfo); | |||
| const String portName = snd_seq_port_info_get_name(portInfo); | |||
| if (clientName == portName) | |||
| deviceNamesFound.add (clientName); | |||
| else | |||
| deviceNamesFound.add (clientName + ": " + portName); | |||
| if (deviceNamesFound.size() == deviceIndexToOpen + 1) | |||
| { | |||
| const int sourcePort = snd_seq_port_info_get_port (portInfo); | |||
| const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||
| if (sourcePort != -1) | |||
| { | |||
| const String name (forInput ? JUCE_ALSA_MIDI_INPUT_NAME | |||
| : JUCE_ALSA_MIDI_OUTPUT_NAME); | |||
| seq->setName (name); | |||
| port.createPort (seq, name, forInput); | |||
| const int sourceClient = snd_seq_client_info_get_client (clientInfo); | |||
| port.createPort (seq, portName, forInput); | |||
| port.connectWith (sourceClient, sourcePort); | |||
| } | |||
| } | |||
| @@ -355,7 +378,7 @@ static AlsaPort iterateMidiDevices (const bool forInput, | |||
| const int deviceIndexToOpen) | |||
| { | |||
| AlsaPort port; | |||
| const AlsaClient::Ptr client (globalAlsaSequencer (forInput)); | |||
| const AlsaClient::Ptr client (AlsaClient::getInstance (forInput)); | |||
| if (snd_seq_t* const seqHandle = client->get()) | |||
| { | |||
| @@ -387,19 +410,6 @@ static AlsaPort iterateMidiDevices (const bool forInput, | |||
| return port; | |||
| } | |||
| AlsaPort createMidiDevice (const bool forInput, const String& deviceNameToOpen) | |||
| { | |||
| AlsaPort port; | |||
| AlsaClient::Ptr client (new AlsaClient (forInput)); | |||
| if (client->get()) | |||
| { | |||
| client->setName (deviceNameToOpen + (forInput ? " Input" : " Output")); | |||
| port.createPort (client, forInput ? "in" : "out", forInput); | |||
| } | |||
| return port; | |||
| } | |||
| //============================================================================== | |||
| class MidiOutputDevice | |||
| @@ -450,7 +460,7 @@ public: | |||
| numBytes -= numSent; | |||
| data += numSent; | |||
| snd_seq_ev_set_source (&event, 0); | |||
| snd_seq_ev_set_source (&event, port.portId); | |||
| snd_seq_ev_set_subs (&event); | |||
| snd_seq_ev_set_direct (&event); | |||
| @@ -507,8 +517,11 @@ MidiOutput* MidiOutput::openDevice (int deviceIndex) | |||
| MidiOutput* MidiOutput::createNewDevice (const String& deviceName) | |||
| { | |||
| MidiOutput* newDevice = nullptr; | |||
| AlsaPort port; | |||
| const AlsaClient::Ptr client (AlsaClient::getInstance (false)); | |||
| AlsaPort port (createMidiDevice (false, deviceName)); | |||
| port.createPort (client, deviceName, false); | |||
| if (port.isValid()) | |||
| { | |||
| @@ -584,8 +597,11 @@ MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback) | |||
| MidiInput* MidiInput::createNewDevice (const String& deviceName, MidiInputCallback* callback) | |||
| { | |||
| MidiInput* newDevice = nullptr; | |||
| AlsaPort port; | |||
| const AlsaClient::Ptr client (AlsaClient::getInstance (true)); | |||
| AlsaPort port (createMidiDevice (true, deviceName)); | |||
| port.createPort (client, deviceName, true); | |||
| if (port.isValid()) | |||
| { | |||
| @@ -149,6 +149,7 @@ bool JUCE_CALLTYPE SystemAudioVolume::setMuted (bool mute) { return SystemVol | |||
| struct CoreAudioClasses | |||
| { | |||
| class CoreAudioIODeviceType; | |||
| class CoreAudioIODevice; | |||
| //============================================================================== | |||
| @@ -691,7 +692,7 @@ public: | |||
| { const ScopedLock sl (callbackLock); } | |||
| // wait until it's definately stopped calling back.. | |||
| // wait until it's definitely stopped calling back.. | |||
| for (int i = 40; --i >= 0;) | |||
| { | |||
| Thread::sleep (50); | |||
| @@ -847,6 +848,11 @@ private: | |||
| intern->deviceDetailsChanged(); | |||
| break; | |||
| case kAudioObjectPropertyOwnedObjects: | |||
| intern->stop (false); | |||
| intern->owner.deviceType.triggerAsyncAudioDeviceListChange(); | |||
| break; | |||
| case kAudioDevicePropertyBufferSizeRange: | |||
| case kAudioDevicePropertyVolumeScalar: | |||
| case kAudioDevicePropertyMute: | |||
| @@ -902,10 +908,12 @@ private: | |||
| class CoreAudioIODevice : public AudioIODevice | |||
| { | |||
| public: | |||
| CoreAudioIODevice (const String& deviceName, | |||
| CoreAudioIODevice (CoreAudioIODeviceType& dt, | |||
| const String& deviceName, | |||
| AudioDeviceID inputDeviceId, const int inputIndex_, | |||
| AudioDeviceID outputDeviceId, const int outputIndex_) | |||
| : AudioIODevice (deviceName, "CoreAudio"), | |||
| deviceType (dt), | |||
| inputIndex (inputIndex_), | |||
| outputIndex (outputIndex_), | |||
| isOpen_ (false), | |||
| @@ -1060,6 +1068,12 @@ public: | |||
| return lastError; | |||
| } | |||
| void audioDeviceListChanged() | |||
| { | |||
| deviceType.audioDeviceListChanged(); | |||
| } | |||
| CoreAudioIODeviceType& deviceType; | |||
| int inputIndex, outputIndex; | |||
| private: | |||
| @@ -1745,7 +1759,8 @@ private: | |||
| //============================================================================== | |||
| class CoreAudioIODeviceType : public AudioIODeviceType | |||
| class CoreAudioIODeviceType : public AudioIODeviceType, | |||
| private AsyncUpdater | |||
| { | |||
| public: | |||
| CoreAudioIODeviceType() | |||
| @@ -1771,7 +1786,7 @@ public: | |||
| } | |||
| //============================================================================== | |||
| void scanForDevices() | |||
| void scanForDevices() override | |||
| { | |||
| hasScanned = true; | |||
| @@ -1827,7 +1842,7 @@ public: | |||
| outputDeviceNames.appendNumbersToDuplicates (false, true); | |||
| } | |||
| StringArray getDeviceNames (bool wantInputNames) const | |||
| StringArray getDeviceNames (bool wantInputNames) const override | |||
| { | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| @@ -1835,7 +1850,7 @@ public: | |||
| : outputDeviceNames; | |||
| } | |||
| int getDefaultDeviceIndex (bool forInput) const | |||
| int getDefaultDeviceIndex (bool forInput) const override | |||
| { | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| @@ -1870,7 +1885,7 @@ public: | |||
| return 0; | |||
| } | |||
| int getIndexOfDevice (AudioIODevice* device, bool asInput) const | |||
| int getIndexOfDevice (AudioIODevice* device, bool asInput) const override | |||
| { | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| @@ -1894,10 +1909,10 @@ public: | |||
| return -1; | |||
| } | |||
| bool hasSeparateInputsAndOutputs() const { return true; } | |||
| bool hasSeparateInputsAndOutputs() const override { return true; } | |||
| AudioIODevice* createDevice (const String& outputDeviceName, | |||
| const String& inputDeviceName) | |||
| const String& inputDeviceName) override | |||
| { | |||
| jassert (hasScanned); // need to call scanForDevices() before doing this | |||
| @@ -1913,15 +1928,15 @@ public: | |||
| String combinedName (outputDeviceName.isEmpty() ? inputDeviceName : outputDeviceName); | |||
| if (inputDeviceID == outputDeviceID) | |||
| return new CoreAudioIODevice (combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); | |||
| return new CoreAudioIODevice (*this, combinedName, inputDeviceID, inputIndex, outputDeviceID, outputIndex); | |||
| ScopedPointer<CoreAudioIODevice> in, out; | |||
| if (inputDeviceID != 0) | |||
| in = new CoreAudioIODevice (inputDeviceName, inputDeviceID, inputIndex, 0, -1); | |||
| in = new CoreAudioIODevice (*this, inputDeviceName, inputDeviceID, inputIndex, 0, -1); | |||
| if (outputDeviceID != 0) | |||
| out = new CoreAudioIODevice (outputDeviceName, 0, -1, outputDeviceID, outputIndex); | |||
| out = new CoreAudioIODevice (*this, outputDeviceName, 0, -1, outputDeviceID, outputIndex); | |||
| if (in == nullptr) return out.release(); | |||
| if (out == nullptr) return in.release(); | |||
| @@ -1932,6 +1947,17 @@ public: | |||
| return combo.release(); | |||
| } | |||
| void audioDeviceListChanged() | |||
| { | |||
| scanForDevices(); | |||
| callDeviceChangeListeners(); | |||
| } | |||
| void triggerAsyncAudioDeviceListChange() | |||
| { | |||
| triggerAsyncUpdate(); | |||
| } | |||
| //============================================================================== | |||
| private: | |||
| StringArray inputDeviceNames, outputDeviceNames; | |||
| @@ -1969,18 +1995,17 @@ private: | |||
| return total; | |||
| } | |||
| void audioDeviceListChanged() | |||
| { | |||
| scanForDevices(); | |||
| callDeviceChangeListeners(); | |||
| } | |||
| static OSStatus hardwareListenerProc (AudioDeviceID, UInt32, const AudioObjectPropertyAddress*, void* clientData) | |||
| { | |||
| static_cast<CoreAudioIODeviceType*> (clientData)->audioDeviceListChanged(); | |||
| return noErr; | |||
| } | |||
| void handleAsyncUpdate() override | |||
| { | |||
| audioDeviceListChanged(); | |||
| } | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (CoreAudioIODeviceType) | |||
| }; | |||
| @@ -171,6 +171,10 @@ namespace CoreMidiHelpers | |||
| static StringArray findDevices (const bool forInput) | |||
| { | |||
| // It seems that OSX can be a bit picky about the thread that's first used to | |||
| // search for devices. It's safest to use the message thread for calling this. | |||
| jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||
| const ItemCount num = forInput ? MIDIGetNumberOfSources() | |||
| : MIDIGetNumberOfDestinations(); | |||
| StringArray s; | |||
| @@ -216,6 +220,14 @@ namespace CoreMidiHelpers | |||
| // correctly when called from the message thread! | |||
| jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||
| #if TARGET_OS_SIMULATOR | |||
| // Enable MIDI for iOS simulator | |||
| MIDINetworkSession* session = [MIDINetworkSession defaultSession]; | |||
| session.enabled = YES; | |||
| session.connectionPolicy = MIDINetworkConnectionPolicy_Anyone; | |||
| #endif | |||
| CoreMidiHelpers::ScopedCFString name; | |||
| name.cfString = getGlobalMidiClientName().toCFString(); | |||
| CHECK_ERROR (MIDIClientCreate (name.cfString, &globalSystemChangeCallback, nullptr, &globalMidiClient)); | |||
| @@ -365,17 +365,18 @@ public: | |||
| void updateSampleRates() | |||
| { | |||
| // find a list of sample rates.. | |||
| const int possibleSampleRates[] = { 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; | |||
| Array<double> newRates; | |||
| if (asioObject != nullptr) | |||
| { | |||
| const int possibleSampleRates[] = { 32000, 44100, 48000, 88200, 96000, 176400, 192000, 352800, 384000 }; | |||
| for (int index = 0; index < numElementsInArray (possibleSampleRates); ++index) | |||
| if (asioObject->canSampleRate ((double) possibleSampleRates[index]) == 0) | |||
| newRates.add ((double) possibleSampleRates[index]); | |||
| } | |||
| if (newRates.size() == 0) | |||
| if (newRates.isEmpty()) | |||
| { | |||
| double cr = getSampleRate(); | |||
| JUCE_ASIO_LOG ("No sample rates supported - current rate: " + String ((int) cr)); | |||
| @@ -1571,7 +1572,7 @@ private: | |||
| DWORD dsize = sizeof (pathName); | |||
| if (RegQueryValueEx (pathKey, 0, 0, &dtype, (LPBYTE) pathName, &dsize) == ERROR_SUCCESS) | |||
| // In older code, this used to check for the existance of the file, but there are situations | |||
| // In older code, this used to check for the existence of the file, but there are situations | |||
| // where our process doesn't have access to it, but where the driver still loads ok.. | |||
| ok = (pathName[0] != 0); | |||
| @@ -96,11 +96,18 @@ bool check (HRESULT hr) | |||
| } | |||
| #if JUCE_MINGW | |||
| #define JUCE_COMCLASS(name, guid) \ | |||
| struct name; \ | |||
| template<> struct UUIDGetter<name> { static CLSID get() { return uuidFromString (guid); } }; \ | |||
| struct name | |||
| #ifdef __uuidof | |||
| #undef __uuidof | |||
| #endif | |||
| #define __uuidof(cls) UUIDGetter<cls>::get() | |||
| struct PROPERTYKEY | |||
| { | |||
| GUID fmtid; | |||
| @@ -60,7 +60,7 @@ public: | |||
| The source passed in will not be deleted by this object, so must be managed by | |||
| the caller. | |||
| @param newSource the new input source to use. This may be zero | |||
| @param newSource the new input source to use. This may be a nullptr | |||
| @param readAheadBufferSize a size of buffer to use for reading ahead. If this | |||
| is zero, no reading ahead will be done; if it's | |||
| greater than zero, a BufferingAudioSource will be used | |||
| @@ -41,11 +41,14 @@ | |||
| * before #including this file, otherwise SIZE_MAX might not be defined | |||
| */ | |||
| #include <limits.h> /* for SIZE_MAX */ | |||
| #if HAVE_STDINT_H | |||
| #include <stdint.h> /* for SIZE_MAX in case limits.h didn't get it */ | |||
| #endif | |||
| #include <stdlib.h> /* for size_t, malloc(), etc */ | |||
| // JUCE: removed as JUCE already includes standard headers and including | |||
| // these in FlacNamespace will cause problems | |||
| //#include <limits.h> /* for SIZE_MAX */ | |||
| //#if HAVE_STDINT_H | |||
| //#include <stdint.h> /* for SIZE_MAX in case limits.h didn't get it */ | |||
| //#endif | |||
| //#include <stdlib.h> /* for size_t, malloc(), etc */ | |||
| #include "compat.h" | |||
| #ifndef SIZE_MAX | |||
| @@ -35,7 +35,10 @@ | |||
| /* we need this since some compilers (like MSVC) leave assert()s on release code (and we don't want to use their ASSERT) */ | |||
| #ifdef DEBUG | |||
| #include <assert.h> | |||
| // JUCE: removed as JUCE already includes standard headers and including | |||
| // these in FlacNamespace will cause problems | |||
| //#include <assert.h> | |||
| #define FLAC__ASSERT(x) assert(x) | |||
| #define FLAC__ASSERT_DECLARATION(x) x | |||
| #else | |||
| @@ -34,7 +34,10 @@ | |||
| #define FLAC__CALLBACK_H | |||
| #include "ordinals.h" | |||
| #include <stdlib.h> /* for size_t */ | |||
| // JUCE: removed as JUCE already includes this and including stdlib | |||
| // in FlacNamespace will cause problems | |||
| //#include <stdlib.h> | |||
| /** \file include/FLAC/callback.h | |||
| * | |||
| @@ -39,15 +39,7 @@ | |||
| #ifndef FLAC__SHARE__COMPAT_H | |||
| #define FLAC__SHARE__COMPAT_H | |||
| #if defined _WIN32 && !defined __CYGWIN__ | |||
| /* where MSVC puts unlink() */ | |||
| # include <io.h> | |||
| #else | |||
| # include <unistd.h> | |||
| #endif | |||
| #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | |||
| #include <sys/types.h> /* for off_t */ | |||
| #define FLAC__off_t __int64 /* use this instead of off_t to fix the 2 GB limit */ | |||
| #if !defined __MINGW32__ | |||
| #define fseeko _fseeki64 | |||
| @@ -62,11 +54,6 @@ | |||
| #define FLAC__off_t off_t | |||
| #endif | |||
| #if HAVE_INTTYPES_H | |||
| #define __STDC_FORMAT_MACROS | |||
| #include <inttypes.h> | |||
| #endif | |||
| #if defined(_MSC_VER) | |||
| #define strtoll _strtoi64 | |||
| #define strtoull _strtoui64 | |||
| @@ -95,33 +82,13 @@ | |||
| #define FLAC__STRNCASECMP strncasecmp | |||
| #endif | |||
| #if defined _MSC_VER || defined __MINGW32__ || defined __CYGWIN__ || defined __EMX__ | |||
| #include <io.h> /* for _setmode(), chmod() */ | |||
| #include <fcntl.h> /* for _O_BINARY */ | |||
| #else | |||
| #include <unistd.h> /* for chown(), unlink() */ | |||
| #endif | |||
| #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | |||
| #if defined __BORLANDC__ | |||
| #include <utime.h> /* for utime() */ | |||
| #else | |||
| #include <sys/utime.h> /* for utime() */ | |||
| #endif | |||
| #else | |||
| #include <sys/types.h> /* some flavors of BSD (like OS X) require this to get time_t */ | |||
| #include <utime.h> /* for utime() */ | |||
| #endif | |||
| #if defined _MSC_VER | |||
| # if _MSC_VER >= 1600 | |||
| /* Visual Studio 2010 has decent C99 support */ | |||
| # include <stdint.h> | |||
| # define PRIu64 "llu" | |||
| # define PRId64 "lld" | |||
| # define PRIx64 "llx" | |||
| # else | |||
| # include <limits.h> | |||
| # ifndef UINT32_MAX | |||
| # define UINT32_MAX _UI32_MAX | |||
| # endif | |||
| @@ -51,7 +51,9 @@ static inline unsigned short __builtin_bswap16(unsigned short a) | |||
| #elif defined HAVE_BYTESWAP_H /* Linux */ | |||
| #include <byteswap.h> | |||
| // JUCE: removed as JUCE already includes standard headers and including | |||
| // these in FlacNamespace will cause problems | |||
| //#include <byteswap.h> | |||
| #define ENDSWAP_16(x) (bswap_16 (x)) | |||
| #define ENDSWAP_32(x) (bswap_32 (x)) | |||
| @@ -33,7 +33,6 @@ | |||
| #ifndef FLAC__METADATA_H | |||
| #define FLAC__METADATA_H | |||
| #include <sys/types.h> /* for off_t */ | |||
| #include "export.h" | |||
| #include "callback.h" | |||
| #include "format.h" | |||
| @@ -33,7 +33,6 @@ | |||
| #ifndef FLAC__STREAM_DECODER_H | |||
| #define FLAC__STREAM_DECODER_H | |||
| #include <stdio.h> /* for FILE */ | |||
| #include "export.h" | |||
| #include "format.h" | |||
| @@ -33,7 +33,6 @@ | |||
| #ifndef FLAC__STREAM_ENCODER_H | |||
| #define FLAC__STREAM_ENCODER_H | |||
| #include <stdio.h> /* for FILE */ | |||
| #include "export.h" | |||
| #include "format.h" | |||
| #include "stream_decoder.h" | |||
| @@ -38,11 +38,6 @@ | |||
| extern "C" { | |||
| #endif | |||
| #include <stdio.h> | |||
| #include <sys/stat.h> | |||
| #include <stdarg.h> | |||
| #include <windows.h> | |||
| int get_utf8_argv(int *argc, char ***argv); | |||
| int printf_utf8(const char *format, ...); | |||
| @@ -668,13 +668,14 @@ public: | |||
| //============================================================================== | |||
| bool write (const int** data, int numSamples) override | |||
| { | |||
| jassert (numSamples >= 0); | |||
| jassert (data != nullptr && *data != nullptr); // the input must contain at least one channel! | |||
| if (writeFailed) | |||
| return false; | |||
| const size_t bytes = (size_t) numSamples * numChannels * bitsPerSample / 8; | |||
| tempBlock.ensureSize ((size_t) bytes, false); | |||
| const size_t bytes = numChannels * (size_t) numSamples * bitsPerSample / 8; | |||
| tempBlock.ensureSize (bytes, false); | |||
| switch (bitsPerSample) | |||
| { | |||
| @@ -695,13 +696,10 @@ public: | |||
| writeFailed = true; | |||
| return false; | |||
| } | |||
| else | |||
| { | |||
| bytesWritten += bytes; | |||
| lengthInSamples += (uint64) numSamples; | |||
| return true; | |||
| } | |||
| bytesWritten += bytes; | |||
| lengthInSamples += (uint64) numSamples; | |||
| return true; | |||
| } | |||
| private: | |||
| @@ -24,17 +24,79 @@ | |||
| #if JUCE_USE_FLAC | |||
| } | |||
| #if defined _WIN32 && !defined __CYGWIN__ | |||
| #include <io.h> | |||
| #else | |||
| #include <unistd.h> | |||
| #endif | |||
| #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | |||
| #include <sys/types.h> /* for off_t */ | |||
| #endif | |||
| #if HAVE_INTTYPES_H | |||
| #define __STDC_FORMAT_MACROS | |||
| #include <inttypes.h> | |||
| #endif | |||
| #if defined _MSC_VER || defined __MINGW32__ || defined __CYGWIN__ || defined __EMX__ | |||
| #include <io.h> /* for _setmode(), chmod() */ | |||
| #include <fcntl.h> /* for _O_BINARY */ | |||
| #else | |||
| #include <unistd.h> /* for chown(), unlink() */ | |||
| #endif | |||
| #if defined _MSC_VER || defined __BORLANDC__ || defined __MINGW32__ | |||
| #if defined __BORLANDC__ | |||
| #include <utime.h> /* for utime() */ | |||
| #else | |||
| #include <sys/utime.h> /* for utime() */ | |||
| #endif | |||
| #else | |||
| #include <sys/types.h> /* some flavors of BSD (like OS X) require this to get time_t */ | |||
| #include <utime.h> /* for utime() */ | |||
| #endif | |||
| #if defined _MSC_VER | |||
| #if _MSC_VER >= 1600 | |||
| #include <stdint.h> | |||
| #else | |||
| #include <limits.h> | |||
| #endif | |||
| #endif | |||
| #ifdef _WIN32 | |||
| #include <stdio.h> | |||
| #include <sys/stat.h> | |||
| #include <stdarg.h> | |||
| #include <windows.h> | |||
| #endif | |||
| #ifdef DEBUG | |||
| #include <assert.h> | |||
| #endif | |||
| #include <stdlib.h> | |||
| #include <stdio.h> | |||
| namespace juce | |||
| { | |||
| namespace FlacNamespace | |||
| { | |||
| #if JUCE_INCLUDE_FLAC_CODE || ! defined (JUCE_INCLUDE_FLAC_CODE) | |||
| #undef VERSION | |||
| #define VERSION "1.2.1" | |||
| #define VERSION "1.3.1" | |||
| #define FLAC__NO_DLL 1 | |||
| #if JUCE_MSVC | |||
| #pragma warning (disable: 4267 4127 4244 4996 4100 4701 4702 4013 4133 4206 4312 4505 4365 4005 4334 181 111) | |||
| #else | |||
| #define HAVE_LROUND 1 | |||
| #endif | |||
| #if JUCE_MAC | |||
| @@ -66,6 +128,7 @@ namespace FlacNamespace | |||
| #define __STDC_LIMIT_MACROS 1 | |||
| #define flac_max jmax | |||
| #define flac_min jmin | |||
| #undef DEBUG // (some flac code dumps debug trace if the app defines this macro) | |||
| #include "flac/all.h" | |||
| #include "flac/libFLAC/bitmath.c" | |||
| #include "flac/libFLAC/bitreader.c" | |||
| @@ -324,7 +387,8 @@ class FlacWriter : public AudioFormatWriter | |||
| { | |||
| public: | |||
| FlacWriter (OutputStream* const out, double rate, uint32 numChans, uint32 bits, int qualityOptionIndex) | |||
| : AudioFormatWriter (out, flacFormatName, rate, numChans, bits) | |||
| : AudioFormatWriter (out, flacFormatName, rate, numChans, bits), | |||
| streamStartPos (output != nullptr ? jmax (output->getPosition(), 0ll) : 0ll) | |||
| { | |||
| using namespace FlacNamespace; | |||
| encoder = FLAC__stream_encoder_new(); | |||
| @@ -432,7 +496,7 @@ public: | |||
| packUint32 ((FLAC__uint32) info.total_samples, buffer + 14, 4); | |||
| memcpy (buffer + 18, info.md5sum, 16); | |||
| const bool seekOk = output->setPosition (4); | |||
| const bool seekOk = output->setPosition (streamStartPos + 4); | |||
| ignoreUnused (seekOk); | |||
| // if this fails, you've given it an output stream that can't seek! It needs | |||
| @@ -482,6 +546,7 @@ public: | |||
| private: | |||
| FlacNamespace::FLAC__StreamEncoder* encoder; | |||
| int64 streamStartPos; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (FlacWriter) | |||
| }; | |||
| @@ -222,7 +222,7 @@ public: | |||
| DisposeMovie (movie); | |||
| #if JUCE_MAC | |||
| ExitMoviesOnThread (); | |||
| ExitMoviesOnThread(); | |||
| #endif | |||
| } | |||
| } | |||
| @@ -656,7 +656,10 @@ namespace WavFileHelpers | |||
| if (infoLength > 0) | |||
| { | |||
| infoLength = jlimit ((int64) 0, infoLength, (int64) input.readInt()); | |||
| infoLength = jmin (infoLength, (int64) input.readInt()); | |||
| if (infoLength <= 0) | |||
| return; | |||
| for (int i = 0; i < numElementsInArray (types); ++i) | |||
| { | |||
| @@ -664,7 +667,8 @@ namespace WavFileHelpers | |||
| { | |||
| MemoryBlock mb; | |||
| input.readIntoMemoryBlock (mb, (ssize_t) infoLength); | |||
| values.set (types[i], mb.toString()); | |||
| values.set (types[i], String::createStringFromData ((const char*) mb.getData(), | |||
| (int) mb.getSize())); | |||
| break; | |||
| } | |||
| } | |||
| @@ -1258,7 +1262,7 @@ public: | |||
| if (writeFailed) | |||
| return false; | |||
| const size_t bytes = numChannels * (unsigned int) numSamples * bitsPerSample / 8; | |||
| const size_t bytes = numChannels * (size_t) numSamples * bitsPerSample / 8; | |||
| tempBlock.ensureSize (bytes, false); | |||
| switch (bitsPerSample) | |||
| @@ -166,6 +166,9 @@ public: | |||
| checkCoInitialiseCalled(); | |||
| clearSamplesBeyondAvailableLength (destSamples, numDestChannels, startOffsetInDestBuffer, | |||
| startSampleInFile, numSamples, lengthInSamples); | |||
| const int stride = numChannels * sizeof (int16); | |||
| while (numSamples > 0) | |||
| @@ -297,7 +300,7 @@ private: | |||
| sampleRate = inputFormat->nSamplesPerSec; | |||
| numChannels = inputFormat->nChannels; | |||
| bitsPerSample = inputFormat->wBitsPerSample; | |||
| bitsPerSample = inputFormat->wBitsPerSample != 0 ? inputFormat->wBitsPerSample : 16; | |||
| lengthInSamples = (lengthInNanoseconds * (int) sampleRate) / 10000000; | |||
| } | |||
| } | |||
| @@ -31,6 +31,8 @@ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| #include "AppConfig.h" | |||
| #define JUCE_CORE_INCLUDE_COM_SMART_PTR 1 | |||
| #define JUCE_CORE_INCLUDE_JNI_HELPERS 1 | |||
| #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
| @@ -22,10 +22,36 @@ | |||
| ============================================================================== | |||
| */ | |||
| /******************************************************************************* | |||
| The block below describes the properties of this module, and is read by | |||
| the Projucer to automatically generate project code that uses it. | |||
| For details about the syntax and how to create or use a module, see the | |||
| JUCE Module Format.txt file. | |||
| BEGIN_JUCE_MODULE_DECLARATION | |||
| ID: juce_audio_formats | |||
| vendor: juce | |||
| version: 4.3.0 | |||
| name: JUCE audio file format codecs | |||
| description: Classes for reading and writing various audio file formats. | |||
| website: http://www.juce.com/juce | |||
| license: GPL/Commercial | |||
| dependencies: juce_audio_basics | |||
| OSXFrameworks: CoreAudio CoreMIDI QuartzCore AudioToolbox | |||
| iOSFrameworks: AudioToolbox QuartzCore | |||
| END_JUCE_MODULE_DECLARATION | |||
| *******************************************************************************/ | |||
| #ifndef JUCE_AUDIO_FORMATS_H_INCLUDED | |||
| #define JUCE_AUDIO_FORMATS_H_INCLUDED | |||
| #include "../juce_audio_basics/juce_audio_basics.h" | |||
| #include <juce_audio_basics/juce_audio_basics.h> | |||
| //============================================================================== | |||
| /** Config: JUCE_USE_FLAC | |||
| @@ -22,5 +22,188 @@ | |||
| ============================================================================== | |||
| */ | |||
| namespace AudioPluginFormatHelpers | |||
| { | |||
| struct CallbackInvoker | |||
| { | |||
| struct InvokeOnMessageThread : public CallbackMessage | |||
| { | |||
| InvokeOnMessageThread (AudioPluginInstance* inInstance, const String& inError, | |||
| AudioPluginFormat::InstantiationCompletionCallback* inCompletion, | |||
| CallbackInvoker* invoker) | |||
| : instance (inInstance), error (inError), compCallback (inCompletion), owner (invoker) | |||
| {} | |||
| void messageCallback() override { compCallback->completionCallback (instance, error); } | |||
| //============================================================================== | |||
| AudioPluginInstance* instance; | |||
| String error; | |||
| ScopedPointer<AudioPluginFormat::InstantiationCompletionCallback> compCallback; | |||
| ScopedPointer<CallbackInvoker> owner; | |||
| }; | |||
| //============================================================================== | |||
| CallbackInvoker (AudioPluginFormat::InstantiationCompletionCallback* cc) : completion (cc) | |||
| {} | |||
| void completionCallback (AudioPluginInstance* instance, const String& error) | |||
| { | |||
| (new InvokeOnMessageThread (instance, error, completion, this))->post(); | |||
| } | |||
| static void staticCompletionCallback (void* userData, AudioPluginInstance* instance, const String& error) | |||
| { | |||
| reinterpret_cast<CallbackInvoker*> (userData)->completionCallback (instance, error); | |||
| } | |||
| //============================================================================== | |||
| AudioPluginFormat::InstantiationCompletionCallback* completion; | |||
| }; | |||
| } | |||
| AudioPluginFormat::AudioPluginFormat() noexcept {} | |||
| AudioPluginFormat::~AudioPluginFormat() {} | |||
| AudioPluginInstance* AudioPluginFormat::createInstanceFromDescription (const PluginDescription& desc, | |||
| double initialSampleRate, | |||
| int initialBufferSize) | |||
| { | |||
| String errorMessage; | |||
| return createInstanceFromDescription (desc, initialSampleRate, initialBufferSize, errorMessage); | |||
| } | |||
| //============================================================================== | |||
| struct EventSignaler : public AudioPluginFormat::InstantiationCompletionCallback | |||
| { | |||
| EventSignaler (WaitableEvent& inEvent, AudioPluginInstance*& inInstance, String& inErrorMessage) | |||
| : event (inEvent), outInstance (inInstance), outErrorMessage (inErrorMessage) | |||
| {} | |||
| void completionCallback (AudioPluginInstance* newInstance, const String& result) override | |||
| { | |||
| outInstance = newInstance; | |||
| outErrorMessage = result; | |||
| event.signal(); | |||
| } | |||
| static void staticCompletionCallback (void* userData, AudioPluginInstance* pluginInstance, const String& error) | |||
| { | |||
| reinterpret_cast<EventSignaler*> (userData)->completionCallback (pluginInstance, error); | |||
| } | |||
| WaitableEvent& event; | |||
| AudioPluginInstance*& outInstance; | |||
| String& outErrorMessage; | |||
| JUCE_DECLARE_NON_COPYABLE (EventSignaler) | |||
| }; | |||
| AudioPluginInstance* AudioPluginFormat::createInstanceFromDescription (const PluginDescription& desc, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| String& errorMessage) | |||
| { | |||
| if (MessageManager::getInstance()->isThisTheMessageThread() | |||
| && requiresUnblockedMessageThreadDuringCreation(desc)) | |||
| { | |||
| errorMessage = NEEDS_TRANS ("This plug-in cannot be instantiated synchronously"); | |||
| return nullptr; | |||
| } | |||
| WaitableEvent waitForCreation; | |||
| AudioPluginInstance* instance = nullptr; | |||
| ScopedPointer<EventSignaler> eventSignaler (new EventSignaler (waitForCreation, instance, errorMessage)); | |||
| if (! MessageManager::getInstance()->isThisTheMessageThread()) | |||
| createPluginInstanceAsync (desc, initialSampleRate, initialBufferSize, eventSignaler.release()); | |||
| else | |||
| createPluginInstance (desc, initialSampleRate, initialBufferSize, | |||
| eventSignaler, EventSignaler::staticCompletionCallback); | |||
| waitForCreation.wait(); | |||
| return instance; | |||
| } | |||
| void AudioPluginFormat::createPluginInstanceAsync (const PluginDescription& description, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| AudioPluginFormat::InstantiationCompletionCallback* callback) | |||
| { | |||
| if (MessageManager::getInstance()->isThisTheMessageThread()) | |||
| { | |||
| createPluginInstanceOnMessageThread (description, initialSampleRate, initialBufferSize, callback); | |||
| return; | |||
| } | |||
| //============================================================================== | |||
| struct InvokeOnMessageThread : public CallbackMessage | |||
| { | |||
| InvokeOnMessageThread (AudioPluginFormat* myself, | |||
| const PluginDescription& descriptionParam, | |||
| double initialSampleRateParam, | |||
| int initialBufferSizeParam, | |||
| AudioPluginFormat::InstantiationCompletionCallback* callbackParam) | |||
| : owner (myself), descr (descriptionParam), sampleRate (initialSampleRateParam), | |||
| bufferSize (initialBufferSizeParam), call (callbackParam) | |||
| {} | |||
| void messageCallback() override | |||
| { | |||
| owner->createPluginInstanceOnMessageThread (descr, sampleRate, bufferSize, call); | |||
| } | |||
| AudioPluginFormat* owner; | |||
| PluginDescription descr; | |||
| double sampleRate; | |||
| int bufferSize; | |||
| AudioPluginFormat::InstantiationCompletionCallback* call; | |||
| }; | |||
| (new InvokeOnMessageThread (this, description, initialSampleRate, initialBufferSize, callback))->post(); | |||
| } | |||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||
| void AudioPluginFormat::createPluginInstanceAsync (const PluginDescription& description, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| std::function<void (AudioPluginInstance*, const String&)> f) | |||
| { | |||
| struct CallbackInvoker : public AudioPluginFormat::InstantiationCompletionCallback | |||
| { | |||
| CallbackInvoker (std::function<void (AudioPluginInstance*, const String&)> inCompletion) | |||
| : completion (inCompletion) | |||
| {} | |||
| void completionCallback (AudioPluginInstance* instance, const String& error) override | |||
| { | |||
| completion (instance, error); | |||
| } | |||
| std::function<void (AudioPluginInstance*, const String&)> completion; | |||
| }; | |||
| createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, new CallbackInvoker (f)); | |||
| } | |||
| #endif | |||
| void AudioPluginFormat::createPluginInstanceOnMessageThread (const PluginDescription& description, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| AudioPluginFormat::InstantiationCompletionCallback* callback) | |||
| { | |||
| jassert (callback != nullptr); | |||
| jassert (MessageManager::getInstance()->isThisTheMessageThread()); | |||
| //============================================================================== | |||
| //============================================================================== | |||
| AudioPluginFormatHelpers::CallbackInvoker* completion = new AudioPluginFormatHelpers::CallbackInvoker (callback); | |||
| createPluginInstance (description, initialSampleRate, initialBufferSize, completion, | |||
| AudioPluginFormatHelpers::CallbackInvoker::staticCompletionCallback); | |||
| } | |||
| @@ -30,11 +30,20 @@ | |||
| /** | |||
| The base class for a type of plugin format, such as VST, AudioUnit, LADSPA, etc. | |||
| @see AudioFormatManager | |||
| @see AudioPluginFormatManager | |||
| */ | |||
| class JUCE_API AudioPluginFormat | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| struct JUCE_API InstantiationCompletionCallback | |||
| { | |||
| virtual ~InstantiationCompletionCallback() {} | |||
| virtual void completionCallback (AudioPluginInstance* instance, const String& error) = 0; | |||
| JUCE_LEAK_DETECTOR (InstantiationCompletionCallback) | |||
| }; | |||
| //============================================================================== | |||
| /** Destructor. */ | |||
| virtual ~AudioPluginFormat(); | |||
| @@ -58,11 +67,36 @@ public: | |||
| const String& fileOrIdentifier) = 0; | |||
| /** Tries to recreate a type from a previously generated PluginDescription. | |||
| @see PluginDescription::createInstance | |||
| @see AudioPluginFormatManager::createInstance | |||
| */ | |||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, | |||
| double initialSampleRate, | |||
| int initialBufferSize); | |||
| /** Same as above but with the possibility of returning an error message. | |||
| @see AudioPluginFormatManager::createInstance | |||
| */ | |||
| virtual AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc, | |||
| double initialSampleRate, | |||
| int initialBufferSize) = 0; | |||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| String& errorMessage); | |||
| /** Tries to recreate a type from a previously generated PluginDescription. | |||
| @see AudioPluginFormatManager::createInstanceAsync | |||
| */ | |||
| void createPluginInstanceAsync (const PluginDescription& description, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| InstantiationCompletionCallback* completionCallback); | |||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||
| void createPluginInstanceAsync (const PluginDescription& description, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| std::function<void (AudioPluginInstance*, const String&)> completionCallback); | |||
| #endif | |||
| /** Should do a quick check to see if this file or directory might be a plugin of | |||
| this format. | |||
| @@ -82,7 +116,7 @@ public: | |||
| It doesn't actually need to load it, just to check whether the file or component | |||
| still exists. | |||
| */ | |||
| virtual bool doesPluginStillExist (const PluginDescription& desc) = 0; | |||
| virtual bool doesPluginStillExist (const PluginDescription&) = 0; | |||
| /** Returns true if this format needs to run a scan to find its list of plugins. */ | |||
| virtual bool canScanForPlugins() const = 0; | |||
| @@ -90,9 +124,17 @@ public: | |||
| /** Searches a suggested set of directories for any plugins in this format. | |||
| The path might be ignored, e.g. by AUs, which are found by the OS rather | |||
| than manually. | |||
| @param directoriesToSearch This specifies which directories shall be | |||
| searched for plug-ins. | |||
| @param recursive Should the search recursively traverse folders. | |||
| @param allowPluginsWhichRequireAsynchronousInstantiation | |||
| If this is false then plug-ins which require | |||
| asynchronous creation will be excluded. | |||
| */ | |||
| virtual StringArray searchPathsForPlugins (const FileSearchPath& directoriesToSearch, | |||
| bool recursive) = 0; | |||
| bool recursive, | |||
| bool allowPluginsWhichRequireAsynchronousInstantiation = false) = 0; | |||
| /** Returns the typical places to look for this kind of plugin. | |||
| @@ -103,8 +145,24 @@ public: | |||
| protected: | |||
| //============================================================================== | |||
| friend class AudioPluginFormatManager; | |||
| AudioPluginFormat() noexcept; | |||
| /** Implementors must override this function. This is guaranteed to be called on | |||
| the message thread. You may call the callback on any thread. | |||
| */ | |||
| virtual void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||
| int initialBufferSize, void* userData, | |||
| void (*callback) (void*, AudioPluginInstance*, const String&)) = 0; | |||
| virtual bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept = 0; | |||
| private: | |||
| /** @internal */ | |||
| void createPluginInstanceOnMessageThread (const PluginDescription&, double rate, int size, | |||
| AudioPluginFormat::InstantiationCompletionCallback*); | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormat) | |||
| }; | |||
| @@ -22,6 +22,39 @@ | |||
| ============================================================================== | |||
| */ | |||
| namespace PluginFormatManagerHelpers | |||
| { | |||
| struct ErrorCallbackOnMessageThread : public CallbackMessage | |||
| { | |||
| ErrorCallbackOnMessageThread (const String& inError, | |||
| AudioPluginFormat::InstantiationCompletionCallback* inCallback) | |||
| : error (inError), callback (inCallback) | |||
| { | |||
| } | |||
| void messageCallback() override { callback->completionCallback (nullptr, error); } | |||
| String error; | |||
| ScopedPointer<AudioPluginFormat::InstantiationCompletionCallback> callback; | |||
| }; | |||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||
| struct ErrorLambdaOnMessageThread : public CallbackMessage | |||
| { | |||
| ErrorLambdaOnMessageThread (const String& inError, | |||
| std::function<void (AudioPluginInstance*, const String&)> f) | |||
| : error (inError), lambda (f) | |||
| { | |||
| } | |||
| void messageCallback() override { lambda (nullptr, error); } | |||
| String error; | |||
| std::function<void (AudioPluginInstance*, const String&)> lambda; | |||
| }; | |||
| #endif | |||
| } | |||
| AudioPluginFormatManager::AudioPluginFormatManager() {} | |||
| AudioPluginFormatManager::~AudioPluginFormatManager() {} | |||
| @@ -32,15 +65,15 @@ void AudioPluginFormatManager::addDefaultFormats() | |||
| // you should only call this method once! | |||
| for (int i = formats.size(); --i >= 0;) | |||
| { | |||
| #if JUCE_PLUGINHOST_VST | |||
| #if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) | |||
| jassert (dynamic_cast<VSTPluginFormat*> (formats[i]) == nullptr); | |||
| #endif | |||
| #if JUCE_PLUGINHOST_VST3 | |||
| #if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) | |||
| jassert (dynamic_cast<VST3PluginFormat*> (formats[i]) == nullptr); | |||
| #endif | |||
| #if JUCE_PLUGINHOST_AU && JUCE_MAC | |||
| #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | |||
| jassert (dynamic_cast<AudioUnitPluginFormat*> (formats[i]) == nullptr); | |||
| #endif | |||
| @@ -50,15 +83,15 @@ void AudioPluginFormatManager::addDefaultFormats() | |||
| } | |||
| #endif | |||
| #if JUCE_PLUGINHOST_AU && JUCE_MAC | |||
| #if JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS) | |||
| formats.add (new AudioUnitPluginFormat()); | |||
| #endif | |||
| #if JUCE_PLUGINHOST_VST | |||
| #if JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS) | |||
| formats.add (new VSTPluginFormat()); | |||
| #endif | |||
| #if JUCE_PLUGINHOST_VST3 | |||
| #if JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS) | |||
| formats.add (new VST3PluginFormat()); | |||
| #endif | |||
| @@ -85,12 +118,55 @@ void AudioPluginFormatManager::addFormat (AudioPluginFormat* const format) | |||
| AudioPluginInstance* AudioPluginFormatManager::createPluginInstance (const PluginDescription& description, double rate, | |||
| int blockSize, String& errorMessage) const | |||
| { | |||
| if (AudioPluginFormat* format = findFormatForDescription (description, errorMessage)) | |||
| return format->createInstanceFromDescription (description, rate, blockSize, errorMessage); | |||
| return nullptr; | |||
| } | |||
| void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| AudioPluginFormat::InstantiationCompletionCallback* callback) | |||
| { | |||
| String error; | |||
| if (AudioPluginFormat* format = findFormatForDescription (description, error)) | |||
| return format->createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, callback); | |||
| (new PluginFormatManagerHelpers::ErrorCallbackOnMessageThread (error, callback))->post(); | |||
| } | |||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||
| void AudioPluginFormatManager::createPluginInstanceAsync (const PluginDescription& description, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| std::function<void (AudioPluginInstance*, const String&)> f) | |||
| { | |||
| String error; | |||
| if (AudioPluginFormat* format = findFormatForDescription (description, error)) | |||
| return format->createPluginInstanceAsync (description, initialSampleRate, initialBufferSize, f); | |||
| (new PluginFormatManagerHelpers::ErrorLambdaOnMessageThread (error, f))->post(); | |||
| } | |||
| #endif | |||
| AudioPluginFormat* AudioPluginFormatManager::findFormatForDescription (const PluginDescription& description, String& errorMessage) const | |||
| { | |||
| errorMessage = String(); | |||
| for (int i = 0; i < formats.size(); ++i) | |||
| if (AudioPluginInstance* result = formats.getUnchecked(i)->createInstanceFromDescription (description, rate, blockSize)) | |||
| return result; | |||
| { | |||
| AudioPluginFormat* format; | |||
| if ((format = formats.getUnchecked (i))->getName() == description.pluginFormatName | |||
| && format->fileMightContainThisPluginType (description.fileOrIdentifier)) | |||
| return format; | |||
| } | |||
| errorMessage = NEEDS_TRANS ("No compatible plug-in format exists for this plug-in"); | |||
| errorMessage = doesPluginStillExist (description) ? TRANS ("This plug-in failed to load correctly") | |||
| : TRANS ("This plug-in file no longer exists"); | |||
| return nullptr; | |||
| } | |||
| @@ -75,12 +75,51 @@ public: | |||
| If it can't load the plugin, it returns nullptr and leaves a message in the | |||
| errorMessage string. | |||
| If you intend to instantiate a AudioUnit v3 plug-in then you must either | |||
| use the non-blocking asynchrous version below - or call this method from a | |||
| thread other than the message thread and without blocking the message | |||
| thread. | |||
| */ | |||
| AudioPluginInstance* createPluginInstance (const PluginDescription& description, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| String& errorMessage) const; | |||
| /** Tries to asynchronously load the type for this description, by trying | |||
| all the formats that this manager knows about. | |||
| The caller must supply a callback object which will be called when | |||
| the instantantiation has completed. | |||
| If it can't load the plugin then the callback function will be called | |||
| passing a nullptr as the instance argument along with an error message. | |||
| The callback function will be called on the message thread so the caller | |||
| must not block the message thread. | |||
| The callback object will be deleted automatically after it has been | |||
| invoked. | |||
| The caller is responsible for deleting the instance that is passed to | |||
| the callback function. | |||
| If you intend to instantiate a AudioUnit v3 plug-in then you must use | |||
| this non-blocking asynchrous version - or call the synchrous method | |||
| from an auxiliary thread. | |||
| */ | |||
| void createPluginInstanceAsync (const PluginDescription& description, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| AudioPluginFormat::InstantiationCompletionCallback* callback); | |||
| #if JUCE_COMPILER_SUPPORTS_LAMBDAS | |||
| void createPluginInstanceAsync (const PluginDescription& description, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| std::function<void (AudioPluginInstance*, const String&)> completionCallback); | |||
| #endif | |||
| /** Checks that the file or component for this plugin actually still exists. | |||
| (This won't try to load the plugin) | |||
| @@ -89,6 +128,9 @@ public: | |||
| private: | |||
| //============================================================================== | |||
| //@internal | |||
| AudioPluginFormat* findFormatForDescription (const PluginDescription&, String& errorMessage) const; | |||
| OwnedArray<AudioPluginFormat> formats; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginFormatManager) | |||
| @@ -0,0 +1,778 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| // This macro can be set if you need to override this internal name for some reason.. | |||
| #ifndef JUCE_STATE_DICTIONARY_KEY | |||
| #define JUCE_STATE_DICTIONARY_KEY "jucePluginState" | |||
| #endif | |||
| struct AudioUnitHelpers | |||
| { | |||
| // maps a channel index into an AU format to an index of a juce format | |||
| struct AUChannelStreamOrder | |||
| { | |||
| AudioChannelLayoutTag auLayoutTag; | |||
| AudioChannelSet::ChannelType speakerOrder[8]; | |||
| }; | |||
| struct StreamOrder : public AudioChannelSet | |||
| { | |||
| static AUChannelStreamOrder auChannelStreamOrder[]; | |||
| }; | |||
| static AudioChannelSet::ChannelType CoreAudioChannelLabelToJuceType (AudioChannelLabel label) noexcept | |||
| { | |||
| if (label >= kAudioChannelLabel_Discrete_0 && label <= kAudioChannelLabel_Discrete_65535) | |||
| { | |||
| const unsigned int discreteChannelNum = label - kAudioChannelLabel_Discrete_0; | |||
| return static_cast<AudioChannelSet::ChannelType> (AudioChannelSet::discreteChannel0 + discreteChannelNum); | |||
| } | |||
| switch (label) | |||
| { | |||
| case kAudioChannelLabel_Center: | |||
| case kAudioChannelLabel_Mono: return AudioChannelSet::centre; | |||
| case kAudioChannelLabel_Left: | |||
| case kAudioChannelLabel_HeadphonesLeft: return AudioChannelSet::left; | |||
| case kAudioChannelLabel_Right: | |||
| case kAudioChannelLabel_HeadphonesRight: return AudioChannelSet::right; | |||
| case kAudioChannelLabel_LFEScreen: return AudioChannelSet::LFE; | |||
| case kAudioChannelLabel_LeftSurround: return AudioChannelSet::leftSurround; | |||
| case kAudioChannelLabel_RightSurround: return AudioChannelSet::rightSurround; | |||
| case kAudioChannelLabel_LeftCenter: return AudioChannelSet::leftCentre; | |||
| case kAudioChannelLabel_RightCenter: return AudioChannelSet::rightCentre; | |||
| case kAudioChannelLabel_CenterSurround: return AudioChannelSet::surround; | |||
| case kAudioChannelLabel_LeftSurroundDirect: return AudioChannelSet::leftSurroundSide; | |||
| case kAudioChannelLabel_RightSurroundDirect: return AudioChannelSet::rightSurroundSide; | |||
| case kAudioChannelLabel_TopCenterSurround: return AudioChannelSet::topMiddle; | |||
| case kAudioChannelLabel_VerticalHeightLeft: return AudioChannelSet::topFrontLeft; | |||
| case kAudioChannelLabel_VerticalHeightRight: return AudioChannelSet::topFrontRight; | |||
| case kAudioChannelLabel_VerticalHeightCenter: return AudioChannelSet::topFrontCentre; | |||
| case kAudioChannelLabel_TopBackLeft: return AudioChannelSet::topRearLeft; | |||
| case kAudioChannelLabel_RearSurroundLeft: return AudioChannelSet::leftSurroundRear; | |||
| case kAudioChannelLabel_TopBackRight: return AudioChannelSet::topRearRight; | |||
| case kAudioChannelLabel_RearSurroundRight: return AudioChannelSet::rightSurroundRear; | |||
| case kAudioChannelLabel_TopBackCenter: return AudioChannelSet::topRearCentre; | |||
| case kAudioChannelLabel_LFE2: return AudioChannelSet::LFE2; | |||
| case kAudioChannelLabel_LeftWide: return AudioChannelSet::wideLeft; | |||
| case kAudioChannelLabel_RightWide: return AudioChannelSet::wideRight; | |||
| case kAudioChannelLabel_Ambisonic_W: return AudioChannelSet::ambisonicW; | |||
| case kAudioChannelLabel_Ambisonic_X: return AudioChannelSet::ambisonicX; | |||
| case kAudioChannelLabel_Ambisonic_Y: return AudioChannelSet::ambisonicY; | |||
| case kAudioChannelLabel_Ambisonic_Z: return AudioChannelSet::ambisonicZ; | |||
| default: return AudioChannelSet::unknown; | |||
| } | |||
| } | |||
| static AudioChannelLabel JuceChannelTypeToCoreAudioLabel (const AudioChannelSet::ChannelType& label) noexcept | |||
| { | |||
| if (label >= AudioChannelSet::discreteChannel0) | |||
| { | |||
| const unsigned int discreteChannelNum = label - AudioChannelSet::discreteChannel0;; | |||
| return static_cast<AudioChannelLabel> (kAudioChannelLabel_Discrete_0 + discreteChannelNum); | |||
| } | |||
| switch (label) | |||
| { | |||
| case AudioChannelSet::centre: return kAudioChannelLabel_Center; | |||
| case AudioChannelSet::left: return kAudioChannelLabel_Left; | |||
| case AudioChannelSet::right: return kAudioChannelLabel_Right; | |||
| case AudioChannelSet::LFE: return kAudioChannelLabel_LFEScreen; | |||
| case AudioChannelSet::leftSurroundRear: return kAudioChannelLabel_RearSurroundLeft; | |||
| case AudioChannelSet::rightSurroundRear: return kAudioChannelLabel_RearSurroundRight; | |||
| case AudioChannelSet::leftCentre: return kAudioChannelLabel_LeftCenter; | |||
| case AudioChannelSet::rightCentre: return kAudioChannelLabel_RightCenter; | |||
| case AudioChannelSet::surround: return kAudioChannelLabel_CenterSurround; | |||
| case AudioChannelSet::leftSurround: return kAudioChannelLabel_LeftSurround; | |||
| case AudioChannelSet::rightSurround: return kAudioChannelLabel_RightSurround; | |||
| case AudioChannelSet::topMiddle: return kAudioChannelLabel_TopCenterSurround; | |||
| case AudioChannelSet::topFrontLeft: return kAudioChannelLabel_VerticalHeightLeft; | |||
| case AudioChannelSet::topFrontRight: return kAudioChannelLabel_VerticalHeightRight; | |||
| case AudioChannelSet::topFrontCentre: return kAudioChannelLabel_VerticalHeightCenter; | |||
| case AudioChannelSet::topRearLeft: return kAudioChannelLabel_TopBackLeft; | |||
| case AudioChannelSet::topRearRight: return kAudioChannelLabel_TopBackRight; | |||
| case AudioChannelSet::topRearCentre: return kAudioChannelLabel_TopBackCenter; | |||
| case AudioChannelSet::LFE2: return kAudioChannelLabel_LFE2; | |||
| case AudioChannelSet::wideLeft: return kAudioChannelLabel_LeftWide; | |||
| case AudioChannelSet::wideRight: return kAudioChannelLabel_RightWide; | |||
| case AudioChannelSet::ambisonicW: return kAudioChannelLabel_Ambisonic_W; | |||
| case AudioChannelSet::ambisonicX: return kAudioChannelLabel_Ambisonic_X; | |||
| case AudioChannelSet::ambisonicY: return kAudioChannelLabel_Ambisonic_Y; | |||
| case AudioChannelSet::ambisonicZ: return kAudioChannelLabel_Ambisonic_Z; | |||
| case AudioChannelSet::leftSurroundSide: return kAudioChannelLabel_LeftSurroundDirect; | |||
| case AudioChannelSet::rightSurroundSide: return kAudioChannelLabel_RightSurroundDirect; | |||
| case AudioChannelSet::unknown: return kAudioChannelLabel_Unknown; | |||
| case AudioChannelSet::discreteChannel0: return kAudioChannelLabel_Discrete_0; | |||
| } | |||
| return kAudioChannelLabel_Unknown; | |||
| } | |||
| static AudioChannelSet CoreAudioChannelBitmapToJuceType (UInt32 bitmap) noexcept | |||
| { | |||
| AudioChannelSet set; | |||
| if ((bitmap & kAudioChannelBit_Left) != 0) set.addChannel (AudioChannelSet::left); | |||
| if ((bitmap & kAudioChannelBit_Right) != 0) set.addChannel (AudioChannelSet::right); | |||
| if ((bitmap & kAudioChannelBit_Center) != 0) set.addChannel (AudioChannelSet::centre); | |||
| if ((bitmap & kAudioChannelBit_LFEScreen) != 0) set.addChannel (AudioChannelSet::LFE); | |||
| if ((bitmap & kAudioChannelBit_LeftSurroundDirect) != 0) set.addChannel (AudioChannelSet::leftSurroundSide); | |||
| if ((bitmap & kAudioChannelBit_RightSurroundDirect) != 0) set.addChannel (AudioChannelSet::rightSurroundSide); | |||
| if ((bitmap & kAudioChannelBit_LeftCenter) != 0) set.addChannel (AudioChannelSet::leftCentre); | |||
| if ((bitmap & kAudioChannelBit_RightCenter) != 0) set.addChannel (AudioChannelSet::rightCentre); | |||
| if ((bitmap & kAudioChannelBit_CenterSurround) != 0) set.addChannel (AudioChannelSet::surround); | |||
| if ((bitmap & kAudioChannelBit_LeftSurround) != 0) set.addChannel (AudioChannelSet::leftSurround); | |||
| if ((bitmap & kAudioChannelBit_RightSurround) != 0) set.addChannel (AudioChannelSet::rightSurround); | |||
| if ((bitmap & kAudioChannelBit_TopCenterSurround) != 0) set.addChannel (AudioChannelSet::topMiddle); | |||
| if ((bitmap & kAudioChannelBit_VerticalHeightLeft) != 0) set.addChannel (AudioChannelSet::topFrontLeft); | |||
| if ((bitmap & kAudioChannelBit_VerticalHeightCenter) != 0) set.addChannel (AudioChannelSet::topFrontCentre); | |||
| if ((bitmap & kAudioChannelBit_VerticalHeightRight) != 0) set.addChannel (AudioChannelSet::topFrontRight); | |||
| if ((bitmap & kAudioChannelBit_TopBackLeft) != 0) set.addChannel (AudioChannelSet::topRearLeft); | |||
| if ((bitmap & kAudioChannelBit_TopBackCenter) != 0) set.addChannel (AudioChannelSet::topRearCentre); | |||
| if ((bitmap & kAudioChannelBit_TopBackRight) != 0) set.addChannel (AudioChannelSet::topRearRight); | |||
| return set; | |||
| } | |||
| static AudioChannelSet CoreAudioChannelLayoutToJuceType (const AudioChannelLayout& layout) noexcept | |||
| { | |||
| const AudioChannelLayoutTag tag = layout.mChannelLayoutTag; | |||
| if (tag == kAudioChannelLayoutTag_UseChannelBitmap) return CoreAudioChannelBitmapToJuceType (layout.mChannelBitmap); | |||
| if (tag == kAudioChannelLayoutTag_UseChannelDescriptions) | |||
| { | |||
| if (layout.mNumberChannelDescriptions <= 8) | |||
| { | |||
| // first try to convert the layout via the auChannelStreamOrder array | |||
| int layoutIndex; | |||
| for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != 0; ++layoutIndex) | |||
| { | |||
| const AUChannelStreamOrder& streamOrder = StreamOrder::auChannelStreamOrder[layoutIndex]; | |||
| int numChannels; | |||
| for (numChannels = 0; numChannels < 8 && streamOrder.speakerOrder[numChannels] != 0;) | |||
| ++numChannels; | |||
| if (numChannels != (int) layout.mNumberChannelDescriptions) | |||
| continue; | |||
| int ch; | |||
| for (ch = 0; ch < numChannels; ++ch) | |||
| if (JuceChannelTypeToCoreAudioLabel (streamOrder.speakerOrder[ch]) != layout.mChannelDescriptions[ch].mChannelLabel) | |||
| break; | |||
| // match! | |||
| if (ch == numChannels) | |||
| break; | |||
| } | |||
| if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != 0) | |||
| return CALayoutTagToChannelSet (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag); | |||
| } | |||
| AudioChannelSet set; | |||
| for (unsigned int i = 0; i < layout.mNumberChannelDescriptions; ++i) | |||
| set.addChannel (CoreAudioChannelLabelToJuceType (layout.mChannelDescriptions[i].mChannelLabel)); | |||
| return set; | |||
| } | |||
| return CALayoutTagToChannelSet (tag); | |||
| } | |||
| static AudioChannelSet CALayoutTagToChannelSet (AudioChannelLayoutTag tag) noexcept | |||
| { | |||
| switch (tag) | |||
| { | |||
| case kAudioChannelLayoutTag_Unknown: return AudioChannelSet::disabled(); | |||
| case kAudioChannelLayoutTag_Mono: return AudioChannelSet::mono(); | |||
| case kAudioChannelLayoutTag_Stereo: | |||
| case kAudioChannelLayoutTag_StereoHeadphones: | |||
| case kAudioChannelLayoutTag_Binaural: return AudioChannelSet::stereo(); | |||
| case kAudioChannelLayoutTag_Quadraphonic: return AudioChannelSet::quadraphonic(); | |||
| case kAudioChannelLayoutTag_Pentagonal: return AudioChannelSet::pentagonal(); | |||
| case kAudioChannelLayoutTag_Hexagonal: return AudioChannelSet::hexagonal(); | |||
| case kAudioChannelLayoutTag_Octagonal: return AudioChannelSet::octagonal(); | |||
| case kAudioChannelLayoutTag_Ambisonic_B_Format: return AudioChannelSet::ambisonic(); | |||
| case kAudioChannelLayoutTag_AudioUnit_6_0: return AudioChannelSet::create6point0(); | |||
| case kAudioChannelLayoutTag_DTS_6_0_A: return AudioChannelSet::create6point0Music(); | |||
| case kAudioChannelLayoutTag_MPEG_6_1_A: return AudioChannelSet::create6point1(); | |||
| case kAudioChannelLayoutTag_DTS_6_1_A: return AudioChannelSet::create6point1Music(); | |||
| case kAudioChannelLayoutTag_MPEG_5_0_B: | |||
| case kAudioChannelLayoutTag_MPEG_5_0_A: | |||
| return AudioChannelSet::create5point0(); | |||
| case kAudioChannelLayoutTag_MPEG_5_1_A: return AudioChannelSet::create5point1(); | |||
| case kAudioChannelLayoutTag_DTS_7_1: | |||
| case kAudioChannelLayoutTag_AudioUnit_7_0: return AudioChannelSet::create7point0(); | |||
| case kAudioChannelLayoutTag_AudioUnit_7_0_Front: return AudioChannelSet::create7point0SDDS(); | |||
| case kAudioChannelLayoutTag_MPEG_7_1_A: return AudioChannelSet::create7point1SDDS(); | |||
| case kAudioChannelLayoutTag_MPEG_3_0_A: | |||
| case kAudioChannelLayoutTag_MPEG_3_0_B: return AudioChannelSet::createLCR(); | |||
| case kAudioChannelLayoutTag_MPEG_4_0_A: | |||
| case kAudioChannelLayoutTag_MPEG_4_0_B: return AudioChannelSet::createLCRS(); | |||
| case kAudioChannelLayoutTag_ITU_2_1: return AudioChannelSet::createLRS(); | |||
| case kAudioChannelLayoutTag_MPEG_7_1_C: return AudioChannelSet::create7point1(); | |||
| } | |||
| if (int numChannels = static_cast<int> (tag) & 0xffff) | |||
| return AudioChannelSet::discreteChannels (numChannels); | |||
| // Bitmap and channel description array layout tags are currently unsupported :-( | |||
| jassertfalse; | |||
| return AudioChannelSet(); | |||
| } | |||
| static AudioChannelLayoutTag ChannelSetToCALayoutTag (const AudioChannelSet& set) noexcept | |||
| { | |||
| if (set == AudioChannelSet::mono()) return kAudioChannelLayoutTag_Mono; | |||
| if (set == AudioChannelSet::stereo()) return kAudioChannelLayoutTag_Stereo; | |||
| if (set == AudioChannelSet::createLCR()) return kAudioChannelLayoutTag_MPEG_3_0_A; | |||
| if (set == AudioChannelSet::createLRS()) return kAudioChannelLayoutTag_ITU_2_1; | |||
| if (set == AudioChannelSet::createLCRS()) return kAudioChannelLayoutTag_MPEG_4_0_A; | |||
| if (set == AudioChannelSet::quadraphonic()) return kAudioChannelLayoutTag_Quadraphonic; | |||
| if (set == AudioChannelSet::pentagonal()) return kAudioChannelLayoutTag_Pentagonal; | |||
| if (set == AudioChannelSet::hexagonal()) return kAudioChannelLayoutTag_Hexagonal; | |||
| if (set == AudioChannelSet::octagonal()) return kAudioChannelLayoutTag_Octagonal; | |||
| if (set == AudioChannelSet::ambisonic()) return kAudioChannelLayoutTag_Ambisonic_B_Format; | |||
| if (set == AudioChannelSet::create5point0()) return kAudioChannelLayoutTag_MPEG_5_0_A; | |||
| if (set == AudioChannelSet::create5point1()) return kAudioChannelLayoutTag_MPEG_5_1_A; | |||
| if (set == AudioChannelSet::create6point0()) return kAudioChannelLayoutTag_AudioUnit_6_0; | |||
| if (set == AudioChannelSet::create6point0Music()) return kAudioChannelLayoutTag_DTS_6_0_A; | |||
| if (set == AudioChannelSet::create6point1Music()) return kAudioChannelLayoutTag_DTS_6_1_A; | |||
| if (set == AudioChannelSet::create6point1()) return kAudioChannelLayoutTag_MPEG_6_1_A; | |||
| if (set == AudioChannelSet::create7point0()) return kAudioChannelLayoutTag_AudioUnit_7_0; | |||
| if (set == AudioChannelSet::create7point1()) return kAudioChannelLayoutTag_MPEG_7_1_C; | |||
| if (set == AudioChannelSet::create7point0SDDS()) return kAudioChannelLayoutTag_AudioUnit_7_0_Front; | |||
| if (set == AudioChannelSet::create7point1SDDS()) return kAudioChannelLayoutTag_MPEG_7_1_A; | |||
| if (set == AudioChannelSet::disabled()) return kAudioChannelLayoutTag_Unknown; | |||
| return static_cast<AudioChannelLayoutTag> ((int) kAudioChannelLayoutTag_DiscreteInOrder | set.size()); | |||
| } | |||
| static int auChannelIndexToJuce (int auIndex, const AudioChannelSet& channelSet) | |||
| { | |||
| if (auIndex >= 8) return auIndex; | |||
| AudioChannelLayoutTag currentLayout = ChannelSetToCALayoutTag (channelSet); | |||
| int layoutIndex; | |||
| for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != currentLayout; ++layoutIndex) | |||
| if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag == 0) return auIndex; | |||
| AudioChannelSet::ChannelType channelType | |||
| = StreamOrder::auChannelStreamOrder[layoutIndex].speakerOrder[auIndex]; | |||
| const int juceIndex = channelSet.getChannelTypes().indexOf (channelType); | |||
| jassert (juceIndex >= 0); | |||
| return juceIndex >= 0 ? juceIndex : auIndex; | |||
| } | |||
| static int juceChannelIndexToAu (int juceIndex, const AudioChannelSet& channelSet) | |||
| { | |||
| if (channelSet.isDiscreteLayout()) | |||
| return juceIndex; | |||
| AudioChannelLayoutTag currentLayout = ChannelSetToCALayoutTag (channelSet); | |||
| int layoutIndex; | |||
| for (layoutIndex = 0; StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag != currentLayout; ++layoutIndex) | |||
| { | |||
| if (StreamOrder::auChannelStreamOrder[layoutIndex].auLayoutTag == 0) | |||
| { | |||
| jassertfalse; | |||
| return juceIndex; | |||
| } | |||
| } | |||
| const AUChannelStreamOrder& channelOrder = StreamOrder::auChannelStreamOrder[layoutIndex]; | |||
| AudioChannelSet::ChannelType channelType = channelSet.getTypeOfChannel (juceIndex); | |||
| for (int i = 0; i < 8 && channelOrder.speakerOrder[i] != 0; ++i) | |||
| if (channelOrder.speakerOrder[i] == channelType) | |||
| return i; | |||
| jassertfalse; | |||
| return juceIndex; | |||
| } | |||
| class ChannelRemapper | |||
| { | |||
| public: | |||
| ChannelRemapper (AudioProcessor& p) : processor (p), inputLayoutMap (nullptr), outputLayoutMap (nullptr) {} | |||
| ~ChannelRemapper() {} | |||
| void alloc() | |||
| { | |||
| const int numInputBuses = processor.getBusCount (true); | |||
| const int numOutputBuses = processor.getBusCount (false); | |||
| initializeChannelMapArray (true, numInputBuses); | |||
| initializeChannelMapArray (false, numOutputBuses); | |||
| for (int busIdx = 0; busIdx < numInputBuses; ++busIdx) | |||
| fillLayoutChannelMaps (true, busIdx); | |||
| for (int busIdx = 0; busIdx < numOutputBuses; ++busIdx) | |||
| fillLayoutChannelMaps (false, busIdx); | |||
| } | |||
| void release() | |||
| { | |||
| inputLayoutMap = outputLayoutMap = nullptr; | |||
| inputLayoutMapPtrStorage.free(); | |||
| outputLayoutMapPtrStorage.free(); | |||
| inputLayoutMapStorage.free(); | |||
| outputLayoutMapStorage.free(); | |||
| } | |||
| inline const int* get (bool input, int bus) const noexcept { return (input ? inputLayoutMap : outputLayoutMap) [bus]; } | |||
| private: | |||
| //============================================================================== | |||
| AudioProcessor& processor; | |||
| HeapBlock<int*> inputLayoutMapPtrStorage, outputLayoutMapPtrStorage; | |||
| HeapBlock<int> inputLayoutMapStorage, outputLayoutMapStorage; | |||
| int** inputLayoutMap; | |||
| int** outputLayoutMap; | |||
| //============================================================================== | |||
| void initializeChannelMapArray (bool isInput, const int numBuses) | |||
| { | |||
| HeapBlock<int*>& layoutMapPtrStorage = isInput ? inputLayoutMapPtrStorage : outputLayoutMapPtrStorage; | |||
| HeapBlock<int>& layoutMapStorage = isInput ? inputLayoutMapStorage : outputLayoutMapStorage; | |||
| int**& layoutMap = isInput ? inputLayoutMap : outputLayoutMap; | |||
| const int totalInChannels = processor.getTotalNumInputChannels(); | |||
| const int totalOutChannels = processor.getTotalNumOutputChannels(); | |||
| layoutMapPtrStorage.calloc (static_cast<size_t> (numBuses)); | |||
| layoutMapStorage.calloc (static_cast<size_t> (isInput ? totalInChannels : totalOutChannels)); | |||
| layoutMap = layoutMapPtrStorage. getData(); | |||
| int ch = 0; | |||
| for (int busIdx = 0; busIdx < numBuses; ++busIdx) | |||
| { | |||
| layoutMap[busIdx] = layoutMapStorage.getData() + ch; | |||
| ch += processor.getChannelCountOfBus (isInput, busIdx); | |||
| } | |||
| } | |||
| void fillLayoutChannelMaps (bool isInput, int busNr) | |||
| { | |||
| int* layoutMap = (isInput ? inputLayoutMap : outputLayoutMap)[busNr]; | |||
| const AudioChannelSet& channelFormat = processor.getChannelLayoutOfBus (isInput, busNr); | |||
| const int numChannels = channelFormat.size(); | |||
| for (int i = 0; i < numChannels; ++i) | |||
| layoutMap[i] = AudioUnitHelpers::juceChannelIndexToAu (i, channelFormat); | |||
| } | |||
| }; | |||
| //============================================================================== | |||
| class CoreAudioBufferList | |||
| { | |||
| public: | |||
| CoreAudioBufferList() { reset(); } | |||
| //============================================================================== | |||
| void prepare (int inChannels, int outChannels, int maxFrames) | |||
| { | |||
| const int numChannels = jmax (inChannels, outChannels); | |||
| scratch.setSize (numChannels, maxFrames); | |||
| channels.calloc (static_cast<size_t> (numChannels)); | |||
| reset(); | |||
| } | |||
| void release() | |||
| { | |||
| scratch.setSize (0, 0); | |||
| channels.free(); | |||
| } | |||
| void reset() noexcept | |||
| { | |||
| pushIdx = 0; | |||
| popIdx = 0; | |||
| zeromem (channels.getData(), sizeof(float*) * static_cast<size_t> (scratch.getNumChannels())); | |||
| } | |||
| //============================================================================== | |||
| float* setBuffer (const int idx, float* ptr = nullptr) noexcept | |||
| { | |||
| jassert (idx < scratch.getNumChannels()); | |||
| return (channels [idx] = uniqueBuffer (idx, ptr)); | |||
| } | |||
| //============================================================================== | |||
| float* push() noexcept | |||
| { | |||
| jassert (pushIdx < scratch.getNumChannels()); | |||
| return channels [pushIdx++]; | |||
| } | |||
| void push (AudioBufferList& bufferList, const int* channelMap) noexcept | |||
| { | |||
| jassert (pushIdx < scratch.getNumChannels()); | |||
| if (bufferList.mNumberBuffers > 0) | |||
| { | |||
| const UInt32 n = bufferList.mBuffers [0].mDataByteSize / | |||
| (bufferList.mBuffers [0].mNumberChannels * sizeof (float)); | |||
| const bool isInterleaved = isAudioBufferInterleaved (bufferList); | |||
| const int numChannels = static_cast<int> (isInterleaved ? bufferList.mBuffers [0].mNumberChannels | |||
| : bufferList.mNumberBuffers); | |||
| for (int ch = 0; ch < numChannels; ++ch) | |||
| { | |||
| float* data = push(); | |||
| int mappedChannel = channelMap [ch]; | |||
| if (isInterleaved || static_cast<float*> (bufferList.mBuffers [mappedChannel].mData) != data) | |||
| copyAudioBuffer (bufferList, mappedChannel, n, data); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| float* pop() noexcept | |||
| { | |||
| jassert (popIdx < scratch.getNumChannels()); | |||
| return channels[popIdx++]; | |||
| } | |||
| void pop (AudioBufferList& buffer, const int* channelMap) noexcept | |||
| { | |||
| if (buffer.mNumberBuffers > 0) | |||
| { | |||
| const UInt32 n = buffer.mBuffers [0].mDataByteSize / (buffer.mBuffers [0].mNumberChannels * sizeof (float)); | |||
| const bool isInterleaved = isAudioBufferInterleaved (buffer); | |||
| const int numChannels = static_cast<int> (isInterleaved ? buffer.mBuffers [0].mNumberChannels : buffer.mNumberBuffers); | |||
| for (int ch = 0; ch < numChannels; ++ch) | |||
| { | |||
| int mappedChannel = channelMap [ch]; | |||
| float* nextBuffer = pop(); | |||
| if (nextBuffer == buffer.mBuffers [mappedChannel].mData && ! isInterleaved) | |||
| continue; // no copying necessary | |||
| if (buffer.mBuffers [mappedChannel].mData == nullptr && ! isInterleaved) | |||
| buffer.mBuffers [mappedChannel].mData = nextBuffer; | |||
| else | |||
| copyAudioBuffer (nextBuffer, mappedChannel, n, buffer); | |||
| } | |||
| } | |||
| } | |||
| //============================================================================== | |||
| AudioSampleBuffer& getBuffer (UInt32 frames) noexcept | |||
| { | |||
| jassert (pushIdx == scratch.getNumChannels()); | |||
| #if JUCE_DEBUG | |||
| for (int i = 0; i < pushIdx; ++i) | |||
| jassert (channels [i] != nullptr); | |||
| #endif | |||
| mutableBuffer.setDataToReferTo (channels, pushIdx, static_cast<int> (frames)); | |||
| return mutableBuffer; | |||
| } | |||
| private: | |||
| float* uniqueBuffer (int idx, float* buffer) noexcept | |||
| { | |||
| if (buffer == nullptr) | |||
| return scratch.getWritePointer (idx); | |||
| for (int ch = 0; ch < idx; ++ch) | |||
| if (buffer == channels[ch]) | |||
| return scratch.getWritePointer (idx); | |||
| return buffer; | |||
| } | |||
| //============================================================================== | |||
| AudioSampleBuffer scratch; | |||
| AudioSampleBuffer mutableBuffer; | |||
| HeapBlock<float*> channels; | |||
| int pushIdx, popIdx; | |||
| }; | |||
| static bool isAudioBufferInterleaved (const AudioBufferList& audioBuffer) noexcept | |||
| { | |||
| return (audioBuffer.mNumberBuffers == 1 && audioBuffer.mBuffers[0].mNumberChannels > 1); | |||
| } | |||
| static void clearAudioBuffer (const AudioBufferList& audioBuffer) noexcept | |||
| { | |||
| for (unsigned int ch = 0; ch < audioBuffer.mNumberBuffers; ++ch) | |||
| zeromem (audioBuffer.mBuffers[ch].mData, audioBuffer.mBuffers[ch].mDataByteSize); | |||
| } | |||
| static void copyAudioBuffer (const AudioBufferList& audioBuffer, const int channel, const UInt32 size, float* dst) noexcept | |||
| { | |||
| if (! isAudioBufferInterleaved (audioBuffer)) | |||
| { | |||
| jassert (channel < static_cast<int> (audioBuffer.mNumberBuffers)); | |||
| jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); | |||
| memcpy (dst, audioBuffer.mBuffers[channel].mData, size * sizeof (float)); | |||
| } | |||
| else | |||
| { | |||
| const int numChannels = static_cast<int> (audioBuffer.mBuffers[0].mNumberChannels); | |||
| const UInt32 n = static_cast<UInt32> (numChannels) * size; | |||
| const float* src = static_cast<const float*> (audioBuffer.mBuffers[0].mData); | |||
| jassert (channel < numChannels); | |||
| jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); | |||
| for (const float* inData = src; inData < (src + n); inData += numChannels) | |||
| *dst++ = inData[channel]; | |||
| } | |||
| } | |||
| static void copyAudioBuffer (const float *src, const int channel, const UInt32 size, AudioBufferList& audioBuffer) noexcept | |||
| { | |||
| if (! isAudioBufferInterleaved (audioBuffer)) | |||
| { | |||
| jassert (channel < static_cast<int> (audioBuffer.mNumberBuffers)); | |||
| jassert (audioBuffer.mBuffers[channel].mDataByteSize == (size * sizeof (float))); | |||
| memcpy (audioBuffer.mBuffers[channel].mData, src, size * sizeof (float)); | |||
| } | |||
| else | |||
| { | |||
| const int numChannels = static_cast<int> (audioBuffer.mBuffers[0].mNumberChannels); | |||
| const UInt32 n = static_cast<UInt32> (numChannels) * size; | |||
| float* dst = static_cast<float*> (audioBuffer.mBuffers[0].mData); | |||
| jassert (channel < numChannels); | |||
| jassert (audioBuffer.mBuffers[0].mDataByteSize == (n * sizeof (float))); | |||
| for (float* outData = dst; outData < (dst + n); outData += numChannels) | |||
| outData[channel] = *src++; | |||
| } | |||
| } | |||
| template <int numLayouts> | |||
| static bool isLayoutSupported (const AudioProcessor& processor, | |||
| bool isInput, int busIdx, | |||
| int numChannels, | |||
| const short (&channelLayoutList) [numLayouts][2], | |||
| bool hasLayoutMap = true) | |||
| { | |||
| if (const AudioProcessor::Bus* bus = processor.getBus (isInput, busIdx)) | |||
| { | |||
| if (! bus->isNumberOfChannelsSupported (numChannels)) | |||
| return false; | |||
| if (! hasLayoutMap) | |||
| return true; | |||
| const int numConfigs = sizeof (channelLayoutList) / sizeof (short[2]); | |||
| for (int i = 0; i < numConfigs; ++i) | |||
| { | |||
| if (channelLayoutList[i][isInput ? 0 : 1] == numChannels) | |||
| return true; | |||
| } | |||
| } | |||
| return false; | |||
| } | |||
| static Array<AUChannelInfo> getAUChannelInfo (const AudioProcessor& processor) | |||
| { | |||
| Array<AUChannelInfo> channelInfo; | |||
| const bool hasMainInputBus = (processor.getBusCount (true) > 0); | |||
| const bool hasMainOutputBus = (processor.getBusCount (false) > 0); | |||
| if ((! hasMainInputBus) && (! hasMainOutputBus)) | |||
| { | |||
| // midi effect plug-in: no audio | |||
| AUChannelInfo info; | |||
| info.inChannels = 0; | |||
| info.outChannels = 0; | |||
| channelInfo.add (info); | |||
| return channelInfo; | |||
| } | |||
| else | |||
| { | |||
| const uint32_t maxNumChanToCheckFor = 9; | |||
| uint32_t defaultInputs = static_cast<uint32_t> (processor.getChannelCountOfBus (true, 0)); | |||
| uint32_t defaultOutputs = static_cast<uint32_t> (processor.getChannelCountOfBus (false, 0)); | |||
| uint32_t lastInputs = defaultInputs; | |||
| uint32_t lastOutputs = defaultOutputs; | |||
| SortedSet<uint32_t> supportedChannels; | |||
| // add the current configuration | |||
| if (lastInputs != 0 || lastOutputs != 0) | |||
| supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||
| for (uint32_t inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | |||
| { | |||
| const AudioProcessor::Bus* inBus = processor.getBus (true, 0); | |||
| if (inBus != nullptr && (! inBus->isNumberOfChannelsSupported ((int) inChanNum))) | |||
| continue; | |||
| for (uint32_t outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) | |||
| { | |||
| const AudioProcessor::Bus* outBus = processor.getBus (false, 0); | |||
| if (outBus != nullptr && (! outBus->isNumberOfChannelsSupported ((int) outChanNum))) | |||
| continue; | |||
| uint32_t channelConfiguration = (inChanNum << 16) | outChanNum; | |||
| // did we already try this configuration? | |||
| if (supportedChannels.contains (channelConfiguration)) continue; | |||
| if (lastInputs != inChanNum && (inChanNum > 0 && inBus != nullptr)) | |||
| { | |||
| AudioChannelSet set = inBus->supportedLayoutWithChannels ((int) inChanNum); | |||
| AudioProcessor::BusesLayout layouts = inBus->getBusesLayoutForLayoutChangeOfBus (set); | |||
| lastInputs = inChanNum; | |||
| lastOutputs = hasMainOutputBus ? static_cast<uint32_t> (layouts.outputBuses.getReference (0).size()) : 0; | |||
| supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||
| } | |||
| if (lastOutputs != outChanNum && (outChanNum > 0 && outBus != nullptr)) | |||
| { | |||
| AudioChannelSet set = outBus->supportedLayoutWithChannels ((int) outChanNum); | |||
| AudioProcessor::BusesLayout layouts = outBus->getBusesLayoutForLayoutChangeOfBus (set); | |||
| lastOutputs = outChanNum; | |||
| lastInputs = hasMainInputBus ? static_cast<uint32_t> (layouts.inputBuses.getReference (0).size()) : 0; | |||
| supportedChannels.add ((lastInputs << 16) | lastOutputs); | |||
| } | |||
| } | |||
| } | |||
| bool hasInOutMismatch = false; | |||
| for (int i = 0; i < supportedChannels.size(); ++i) | |||
| { | |||
| const uint32_t numInputs = (supportedChannels[i] >> 16) & 0xffff; | |||
| const uint32_t numOutputs = (supportedChannels[i] >> 0) & 0xffff; | |||
| if (numInputs != numOutputs) | |||
| { | |||
| hasInOutMismatch = true; | |||
| break; | |||
| } | |||
| } | |||
| bool hasUnsupportedInput = ! hasMainOutputBus, hasUnsupportedOutput = ! hasMainInputBus; | |||
| for (uint32_t inChanNum = hasMainInputBus ? 1 : 0; inChanNum <= (hasMainInputBus ? maxNumChanToCheckFor : 0); ++inChanNum) | |||
| { | |||
| uint32_t channelConfiguration = (inChanNum << 16) | (hasInOutMismatch ? defaultOutputs : inChanNum); | |||
| if (! supportedChannels.contains (channelConfiguration)) | |||
| { | |||
| hasUnsupportedInput = true; | |||
| break; | |||
| } | |||
| } | |||
| for (uint32_t outChanNum = hasMainOutputBus ? 1 : 0; outChanNum <= (hasMainOutputBus ? maxNumChanToCheckFor : 0); ++outChanNum) | |||
| { | |||
| uint32_t channelConfiguration = ((hasInOutMismatch ? defaultInputs : outChanNum) << 16) | outChanNum; | |||
| if (! supportedChannels.contains (channelConfiguration)) | |||
| { | |||
| hasUnsupportedOutput = true; | |||
| break; | |||
| } | |||
| } | |||
| for (int i = 0; i < supportedChannels.size(); ++i) | |||
| { | |||
| const int numInputs = (supportedChannels[i] >> 16) & 0xffff; | |||
| const int numOutputs = (supportedChannels[i] >> 0) & 0xffff; | |||
| AUChannelInfo info; | |||
| // see here: https://developer.apple.com/library/mac/documentation/MusicAudio/Conceptual/AudioUnitProgrammingGuide/TheAudioUnit/TheAudioUnit.html | |||
| info.inChannels = static_cast<SInt16> (hasMainInputBus ? (hasUnsupportedInput ? numInputs : (hasInOutMismatch && (! hasUnsupportedOutput) ? -2 : -1)) : 0); | |||
| info.outChannels = static_cast<SInt16> (hasMainOutputBus ? (hasUnsupportedOutput ? numOutputs : (hasInOutMismatch && (! hasUnsupportedInput) ? -2 : -1)) : 0); | |||
| if (info.inChannels == -2 && info.outChannels == -2) | |||
| info.inChannels = -1; | |||
| int j; | |||
| for (j = 0; j < channelInfo.size(); ++j) | |||
| if (channelInfo[j].inChannels == info.inChannels && channelInfo[j].outChannels == info.outChannels) | |||
| break; | |||
| if (j >= channelInfo.size()) | |||
| channelInfo.add (info); | |||
| } | |||
| } | |||
| return channelInfo; | |||
| } | |||
| }; | |||
| AudioUnitHelpers::AUChannelStreamOrder AudioUnitHelpers::StreamOrder::auChannelStreamOrder[] = | |||
| { | |||
| {kAudioChannelLayoutTag_Mono, {centre, unknown, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_Stereo, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_StereoHeadphones, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_Binaural, {left, right, unknown, unknown, unknown, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_Quadraphonic, {left, right, leftSurround, rightSurround, unknown, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_Pentagonal, {left, right, leftSurroundRear, rightSurroundRear, centre, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_Hexagonal, {left, right, leftSurroundRear, rightSurroundRear, centre, centreSurround, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_Octagonal, {left, right, leftSurround, rightSurround, centre, centreSurround, wideLeft, wideRight}}, | |||
| {kAudioChannelLayoutTag_Ambisonic_B_Format, {ambisonicW, ambisonicX, ambisonicY, ambisonicZ, unknown, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_MPEG_5_0_A, {left, right, centre, leftSurround, rightSurround, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_MPEG_5_0_B, {left, right, leftSurround, rightSurround, centre, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_MPEG_5_1_A, {left, right, centre, LFE, leftSurround, rightSurround, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_AudioUnit_6_0, {left, right, leftSurround, rightSurround, centre, centreSurround, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_DTS_6_0_A, {left, right, leftSurround, rightSurround, leftSurroundSide, rightSurroundSide, unknown, unknown}}, // TODO check this one | |||
| {kAudioChannelLayoutTag_MPEG_6_1_A, {left, right, centre, LFE, leftSurround, rightSurround, centre, unknown}}, | |||
| {kAudioChannelLayoutTag_DTS_6_1_A, {leftSurroundSide, rightSurroundSide, left, right, leftSurround, rightSurround, LFE, unknown}}, | |||
| {kAudioChannelLayoutTag_AudioUnit_7_0, {left, right, leftSurroundSide, rightSurroundSide, centre, leftSurroundRear, rightSurroundRear, unknown}}, | |||
| {kAudioChannelLayoutTag_MPEG_7_1_C, {left, right, centre, LFE, leftSurroundSide, rightSurroundSide, leftSurroundRear, rightSurroundRear}}, | |||
| {kAudioChannelLayoutTag_AudioUnit_7_0_Front,{left, right, leftSurround, rightSurround, centre, leftCentre, rightCentre, unknown}}, | |||
| {kAudioChannelLayoutTag_MPEG_7_1_A, {left, right, centre, LFE, leftSurround, rightSurround, leftCentre, rightCentre}}, | |||
| {kAudioChannelLayoutTag_DTS_7_1, {leftCentre, centre, rightCentre, left, right, leftSurround, rightSurround, LFE}}, | |||
| {kAudioChannelLayoutTag_MPEG_3_0_A, {left, right, centre, unknown, unknown, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_MPEG_3_0_B, {centre, left, right, unknown, unknown, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_MPEG_4_0_A, {left, right, centre, centreSurround, unknown, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_MPEG_4_0_B, {centre, left, right, centreSurround, unknown, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_ITU_2_1, {left, right, centreSurround, unknown, unknown, unknown, unknown, unknown}}, | |||
| {kAudioChannelLayoutTag_EAC3_7_1_C, {left, centre, right, leftSurround, rightSurround, LFE, leftSurroundSide, rightSurroundSide}}, | |||
| {unknown, {unknown,unknown,unknown,unknown,unknown,unknown,unknown,unknown}} | |||
| }; | |||
| @@ -22,7 +22,7 @@ | |||
| ============================================================================== | |||
| */ | |||
| #if (JUCE_PLUGINHOST_AU && JUCE_MAC) || DOXYGEN | |||
| #if (JUCE_PLUGINHOST_AU && (JUCE_MAC || JUCE_IOS)) || DOXYGEN | |||
| //============================================================================== | |||
| /** | |||
| @@ -38,16 +38,24 @@ public: | |||
| //============================================================================== | |||
| String getName() const override { return "AudioUnit"; } | |||
| void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | |||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription& desc, double, int) override; | |||
| bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | |||
| String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | |||
| bool pluginNeedsRescanning (const PluginDescription&) override; | |||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; | |||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||
| bool doesPluginStillExist (const PluginDescription&) override; | |||
| FileSearchPath getDefaultLocationsToSearch() override; | |||
| bool canScanForPlugins() const override { return true; } | |||
| private: | |||
| //============================================================================== | |||
| void createPluginInstance (const PluginDescription&, | |||
| double initialSampleRate, | |||
| int initialBufferSize, | |||
| void* userData, | |||
| void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||
| bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioUnitPluginFormat) | |||
| }; | |||
| @@ -55,8 +63,10 @@ private: | |||
| #endif | |||
| //============================================================================== | |||
| #if MAC_OS_X_VERSION_MAX_ALLOWED < 101200 | |||
| enum | |||
| { | |||
| /** Custom AudioUnit property used to indicate MPE support */ | |||
| kAudioUnitProperty_SupportsMPE = 75001 | |||
| kAudioUnitProperty_SupportsMPE = 58 | |||
| }; | |||
| #endif | |||
| @@ -610,11 +610,14 @@ void LADSPAPluginFormat::findAllTypesForFile (OwnedArray <PluginDescription>& re | |||
| } | |||
| } | |||
| AudioPluginInstance* LADSPAPluginFormat::createInstanceFromDescription (const PluginDescription& desc, | |||
| double sampleRate, int blockSize) | |||
| void LADSPAPluginFormat::createPluginInstance (const PluginDescription& desc, | |||
| double sampleRate, int blockSize, | |||
| void* userData, | |||
| void (*callback) (void*, AudioPluginInstance*, const String&)) | |||
| { | |||
| ScopedPointer<LADSPAPluginInstance> result; | |||
| if (fileMightContainThisPluginType (desc.fileOrIdentifier)) | |||
| { | |||
| File file (desc.fileOrIdentifier); | |||
| @@ -639,7 +642,17 @@ AudioPluginInstance* LADSPAPluginFormat::createInstanceFromDescription (const Pl | |||
| previousWorkingDirectory.setAsCurrentWorkingDirectory(); | |||
| } | |||
| return result.release(); | |||
| String errorMsg; | |||
| if (result == nullptr) | |||
| errorMsg = String (NEEDS_TRANS ("Unable to load XXX plug-in file")).replace ("XXX", "LADSPA"); | |||
| callback (userData, result.release(), errorMsg); | |||
| } | |||
| bool LADSPAPluginFormat::requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept | |||
| { | |||
| return false; | |||
| } | |||
| bool LADSPAPluginFormat::fileMightContainThisPluginType (const String& fileOrIdentifier) | |||
| @@ -663,7 +676,7 @@ bool LADSPAPluginFormat::doesPluginStillExist (const PluginDescription& desc) | |||
| return File::createFileWithoutCheckingPath (desc.fileOrIdentifier).exists(); | |||
| } | |||
| StringArray LADSPAPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive) | |||
| StringArray LADSPAPluginFormat::searchPathsForPlugins (const FileSearchPath& directoriesToSearch, const bool recursive, bool) | |||
| { | |||
| StringArray results; | |||
| @@ -31,22 +31,28 @@ | |||
| class JUCE_API LADSPAPluginFormat : public AudioPluginFormat | |||
| { | |||
| public: | |||
| //============================================================================== | |||
| LADSPAPluginFormat(); | |||
| ~LADSPAPluginFormat(); | |||
| //============================================================================== | |||
| String getName() const override { return "LADSPA"; } | |||
| void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | |||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, double, int) override; | |||
| bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | |||
| String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | |||
| bool pluginNeedsRescanning (const PluginDescription&) override; | |||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; | |||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||
| bool doesPluginStillExist (const PluginDescription&) override; | |||
| FileSearchPath getDefaultLocationsToSearch() override; | |||
| bool canScanForPlugins() const override { return true; } | |||
| private: | |||
| //============================================================================== | |||
| void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||
| int initialBufferSize, void* userData, | |||
| void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||
| bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||
| private: | |||
| void recursiveFileSearch (StringArray&, const File&, bool recursive); | |||
| @@ -67,17 +67,17 @@ inline juce::String toString (const Steinberg::char16* string) noexcept { re | |||
| inline juce::String toString (const Steinberg::UString128& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | |||
| inline juce::String toString (const Steinberg::UString256& string) noexcept { return toString (static_cast<const Steinberg::char16*> (string)); } | |||
| static void toString128 (Steinberg::Vst::String128 result, const char* source) | |||
| inline void toString128 (Steinberg::Vst::String128 result, const char* source) | |||
| { | |||
| Steinberg::UString (result, 128).fromAscii (source); | |||
| } | |||
| static void toString128 (Steinberg::Vst::String128 result, const juce::String& source) | |||
| inline void toString128 (Steinberg::Vst::String128 result, const juce::String& source) | |||
| { | |||
| Steinberg::UString (result, 128).fromAscii (source.toUTF8()); | |||
| } | |||
| static Steinberg::Vst::TChar* toString (const juce::String& source) noexcept | |||
| inline Steinberg::Vst::TChar* toString (const juce::String& source) noexcept | |||
| { | |||
| return reinterpret_cast<Steinberg::Vst::TChar*> (source.toUTF16().getAddress()); | |||
| } | |||
| @@ -146,22 +146,22 @@ static inline Steinberg::Vst::Speaker getSpeakerType (AudioChannelSet::ChannelTy | |||
| case AudioChannelSet::left: return kSpeakerL; | |||
| case AudioChannelSet::right: return kSpeakerR; | |||
| case AudioChannelSet::centre: return kSpeakerC; | |||
| case AudioChannelSet::subbass: return kSpeakerLfe; | |||
| case AudioChannelSet::surroundLeft: return kSpeakerLs; | |||
| case AudioChannelSet::surroundRight: return kSpeakerRs; | |||
| case AudioChannelSet::centreLeft: return kSpeakerLc; | |||
| case AudioChannelSet::centreRight: return kSpeakerRc; | |||
| case AudioChannelSet::surround: return kSpeakerS; | |||
| case AudioChannelSet::sideLeft: return kSpeakerSl; | |||
| case AudioChannelSet::sideRight: return kSpeakerSr; | |||
| case AudioChannelSet::topMiddle: return kSpeakerTm; | |||
| case AudioChannelSet::LFE: return kSpeakerLfe; | |||
| case AudioChannelSet::leftSurround: return kSpeakerLs; | |||
| case AudioChannelSet::rightSurround: return kSpeakerRs; | |||
| case AudioChannelSet::leftCentre: return kSpeakerLc; | |||
| case AudioChannelSet::rightCentre: return kSpeakerRc; | |||
| case AudioChannelSet::centreSurround: return kSpeakerCs; | |||
| case AudioChannelSet::leftSurroundRear: return kSpeakerSl; | |||
| case AudioChannelSet::rightSurroundRear: return kSpeakerSr; | |||
| case AudioChannelSet::topMiddle: return (1 << 11); /* kSpeakerTm */ | |||
| case AudioChannelSet::topFrontLeft: return kSpeakerTfl; | |||
| case AudioChannelSet::topFrontCentre: return kSpeakerTfc; | |||
| case AudioChannelSet::topFrontRight: return kSpeakerTfr; | |||
| case AudioChannelSet::topRearLeft: return kSpeakerTrl; | |||
| case AudioChannelSet::topRearCentre: return kSpeakerTrc; | |||
| case AudioChannelSet::topRearRight: return kSpeakerTrr; | |||
| case AudioChannelSet::subbass2: return kSpeakerLfe2; | |||
| case AudioChannelSet::LFE2: return kSpeakerLfe2; | |||
| default: break; | |||
| } | |||
| @@ -177,30 +177,49 @@ static inline AudioChannelSet::ChannelType getChannelType (Steinberg::Vst::Speak | |||
| case kSpeakerL: return AudioChannelSet::left; | |||
| case kSpeakerR: return AudioChannelSet::right; | |||
| case kSpeakerC: return AudioChannelSet::centre; | |||
| case kSpeakerLfe: return AudioChannelSet::subbass; | |||
| case kSpeakerLs: return AudioChannelSet::surroundLeft; | |||
| case kSpeakerRs: return AudioChannelSet::surroundRight; | |||
| case kSpeakerLc: return AudioChannelSet::centreLeft; | |||
| case kSpeakerRc: return AudioChannelSet::centreRight; | |||
| case kSpeakerS: return AudioChannelSet::surround; | |||
| case kSpeakerSl: return AudioChannelSet::sideLeft; | |||
| case kSpeakerSr: return AudioChannelSet::sideRight; | |||
| case kSpeakerTm: return AudioChannelSet::topMiddle; | |||
| case kSpeakerLfe: return AudioChannelSet::LFE; | |||
| case kSpeakerLs: return AudioChannelSet::leftSurround; | |||
| case kSpeakerRs: return AudioChannelSet::rightSurround; | |||
| case kSpeakerLc: return AudioChannelSet::leftCentre; | |||
| case kSpeakerRc: return AudioChannelSet::rightCentre; | |||
| case kSpeakerCs: return AudioChannelSet::centreSurround; | |||
| case kSpeakerSl: return AudioChannelSet::leftSurroundRear; | |||
| case kSpeakerSr: return AudioChannelSet::rightSurroundRear; | |||
| case (1 << 11): return AudioChannelSet::topMiddle; /* kSpeakerTm */ | |||
| case kSpeakerTfl: return AudioChannelSet::topFrontLeft; | |||
| case kSpeakerTfc: return AudioChannelSet::topFrontCentre; | |||
| case kSpeakerTfr: return AudioChannelSet::topFrontRight; | |||
| case kSpeakerTrl: return AudioChannelSet::topRearLeft; | |||
| case kSpeakerTrc: return AudioChannelSet::topRearCentre; | |||
| case kSpeakerTrr: return AudioChannelSet::topRearRight; | |||
| case kSpeakerLfe2: return AudioChannelSet::subbass2; | |||
| case kSpeakerLfe2: return AudioChannelSet::LFE2; | |||
| default: break; | |||
| } | |||
| return AudioChannelSet::unknown; | |||
| } | |||
| static inline Steinberg::Vst::SpeakerArrangement getSpeakerArrangement (const AudioChannelSet& channels) noexcept | |||
| static inline Steinberg::Vst::SpeakerArrangement getVst3SpeakerArrangement (const AudioChannelSet& channels) noexcept | |||
| { | |||
| if (channels == AudioChannelSet::disabled()) return Steinberg::Vst::SpeakerArr::kEmpty; | |||
| else if (channels == AudioChannelSet::mono()) return Steinberg::Vst::SpeakerArr::kMono; | |||
| else if (channels == AudioChannelSet::stereo()) return Steinberg::Vst::SpeakerArr::kStereo; | |||
| else if (channels == AudioChannelSet::createLCR()) return Steinberg::Vst::SpeakerArr::k30Cine; | |||
| else if (channels == AudioChannelSet::createLRS()) return Steinberg::Vst::SpeakerArr::k30Music; | |||
| else if (channels == AudioChannelSet::createLCRS()) return Steinberg::Vst::SpeakerArr::k40Cine; | |||
| else if (channels == AudioChannelSet::create5point0()) return Steinberg::Vst::SpeakerArr::k50; | |||
| else if (channels == AudioChannelSet::create5point1()) return Steinberg::Vst::SpeakerArr::k51; | |||
| else if (channels == AudioChannelSet::create6point0()) return Steinberg::Vst::SpeakerArr::k60Cine; | |||
| else if (channels == AudioChannelSet::create6point1()) return Steinberg::Vst::SpeakerArr::k61Cine; | |||
| else if (channels == AudioChannelSet::create6point0Music()) return Steinberg::Vst::SpeakerArr::k60Music; | |||
| else if (channels == AudioChannelSet::create6point1Music()) return Steinberg::Vst::SpeakerArr::k61Music; | |||
| else if (channels == AudioChannelSet::create7point0()) return Steinberg::Vst::SpeakerArr::k70Music; | |||
| else if (channels == AudioChannelSet::create7point0SDDS()) return Steinberg::Vst::SpeakerArr::k70Cine; | |||
| else if (channels == AudioChannelSet::create7point1()) return Steinberg::Vst::SpeakerArr::k71CineSideFill; | |||
| else if (channels == AudioChannelSet::create7point1SDDS()) return Steinberg::Vst::SpeakerArr::k71Cine; | |||
| else if (channels == AudioChannelSet::ambisonic()) return Steinberg::Vst::SpeakerArr::kBFormat; | |||
| else if (channels == AudioChannelSet::quadraphonic()) return Steinberg::Vst::SpeakerArr::k40Music; | |||
| Steinberg::Vst::SpeakerArrangement result = 0; | |||
| Array<AudioChannelSet::ChannelType> types (channels.getChannelTypes()); | |||
| @@ -213,6 +232,25 @@ static inline Steinberg::Vst::SpeakerArrangement getSpeakerArrangement (const Au | |||
| static inline AudioChannelSet getChannelSetForSpeakerArrangement (Steinberg::Vst::SpeakerArrangement arr) noexcept | |||
| { | |||
| if (arr == Steinberg::Vst::SpeakerArr::kEmpty) return AudioChannelSet::disabled(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::kMono) return AudioChannelSet::mono(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::kStereo) return AudioChannelSet::stereo(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k30Cine) return AudioChannelSet::createLCR(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k30Music) return AudioChannelSet::createLRS(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k40Cine) return AudioChannelSet::createLCRS(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k50) return AudioChannelSet::create5point0(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k51) return AudioChannelSet::create5point1(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k60Cine) return AudioChannelSet::create6point0(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k61Cine) return AudioChannelSet::create6point1(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k60Music) return AudioChannelSet::create6point0Music(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k61Music) return AudioChannelSet::create6point1Music(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k70Music) return AudioChannelSet::create7point0(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k70Cine) return AudioChannelSet::create7point0SDDS(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k71CineSideFill) return AudioChannelSet::create7point1(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k71Cine) return AudioChannelSet::create7point1SDDS(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::kBFormat) return AudioChannelSet::ambisonic(); | |||
| else if (arr == Steinberg::Vst::SpeakerArr::k40Music) return AudioChannelSet::quadraphonic(); | |||
| AudioChannelSet result; | |||
| for (Steinberg::Vst::Speaker speaker = 1; speaker <= Steinberg::Vst::kSpeakerRcs; speaker <<= 1) | |||
| @@ -462,12 +500,12 @@ struct VST3BufferExchange | |||
| vstBuffers.silenceFlags = 0; | |||
| } | |||
| static void mapArrangementToBusses (int& channelIndexOffset, int index, | |||
| static void mapArrangementToBuses (int& channelIndexOffset, int index, | |||
| Array<Steinberg::Vst::AudioBusBuffers>& result, | |||
| BusMap& busMapToUse, Steinberg::Vst::SpeakerArrangement arrangement, | |||
| BusMap& busMapToUse, const AudioChannelSet& arrangement, | |||
| AudioBuffer<FloatType>& source) | |||
| { | |||
| const int numChansForBus = BigInteger ((juce::int64) arrangement).countNumberOfSetBits(); | |||
| const int numChansForBus = arrangement.size(); | |||
| if (index >= result.size()) | |||
| result.add (Steinberg::Vst::AudioBusBuffers()); | |||
| @@ -483,26 +521,26 @@ struct VST3BufferExchange | |||
| channelIndexOffset += numChansForBus; | |||
| } | |||
| static inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse, | |||
| const Array<Steinberg::Vst::SpeakerArrangement>& arrangements, | |||
| static inline void mapBufferToBuses (Array<Steinberg::Vst::AudioBusBuffers>& result, BusMap& busMapToUse, | |||
| const Array<AudioChannelSet>& arrangements, | |||
| AudioBuffer<FloatType>& source) | |||
| { | |||
| int channelIndexOffset = 0; | |||
| for (int i = 0; i < arrangements.size(); ++i) | |||
| mapArrangementToBusses (channelIndexOffset, i, result, busMapToUse, | |||
| mapArrangementToBuses (channelIndexOffset, i, result, busMapToUse, | |||
| arrangements.getUnchecked (i), source); | |||
| } | |||
| static inline void mapBufferToBusses (Array<Steinberg::Vst::AudioBusBuffers>& result, | |||
| static inline void mapBufferToBuses (Array<Steinberg::Vst::AudioBusBuffers>& result, | |||
| Steinberg::Vst::IAudioProcessor& processor, | |||
| BusMap& busMapToUse, bool isInput, int numBusses, | |||
| BusMap& busMapToUse, bool isInput, int numBuses, | |||
| AudioBuffer<FloatType>& source) | |||
| { | |||
| int channelIndexOffset = 0; | |||
| for (int i = 0; i < numBusses; ++i) | |||
| mapArrangementToBusses (channelIndexOffset, i, | |||
| for (int i = 0; i < numBuses; ++i) | |||
| mapArrangementToBuses (channelIndexOffset, i, | |||
| result, busMapToUse, | |||
| getArrangementForBus (&processor, isInput, i), | |||
| source); | |||
| @@ -47,12 +47,15 @@ | |||
| #pragma clang diagnostic ignored "-Wdelete-non-virtual-dtor" | |||
| #endif | |||
| #undef DEVELOPMENT | |||
| #define DEVELOPMENT 0 // This avoids a Clang warning in Steinberg code about unused values | |||
| /* These files come with the Steinberg VST3 SDK - to get them, you'll need to | |||
| visit the Steinberg website and agree to whatever is currently required to | |||
| get them. | |||
| Then, you'll need to make sure your include path contains your "VST3 SDK" | |||
| directory (or whatever you've named it on your machine). The Introjucer has | |||
| directory (or whatever you've named it on your machine). The Projucer has | |||
| a special box for setting this path. | |||
| */ | |||
| #if JUCE_VST3HEADERS_INCLUDE_HEADERS_ONLY | |||
| @@ -78,6 +81,7 @@ | |||
| #include <pluginterfaces/vst/ivstunits.h> | |||
| #include <pluginterfaces/vst/ivstmidicontrollers.h> | |||
| #include <public.sdk/source/common/memorystream.h> | |||
| #include <public.sdk/source/vst/vsteditcontroller.h> | |||
| #else | |||
| #if JUCE_MINGW | |||
| #define _set_abort_behavior(...) | |||
| @@ -128,6 +132,10 @@ namespace Steinberg | |||
| #pragma clang diagnostic pop | |||
| #endif | |||
| #if JUCE_WINDOWS | |||
| #include <windows.h> | |||
| #endif | |||
| //============================================================================== | |||
| #undef ASSERT | |||
| #undef WARNING | |||
| @@ -25,11 +25,12 @@ | |||
| #ifndef JUCE_VST3PLUGINFORMAT_H_INCLUDED | |||
| #define JUCE_VST3PLUGINFORMAT_H_INCLUDED | |||
| #if JUCE_PLUGINHOST_VST3 | |||
| #if (JUCE_PLUGINHOST_VST3 && (JUCE_MAC || JUCE_WINDOWS)) || DOXYGEN | |||
| /** | |||
| Implements a plugin format for VST3s. | |||
| */ | |||
| class JUCE_API VST3PluginFormat : public AudioPluginFormat | |||
| class JUCE_API VST3PluginFormat : public AudioPluginFormat | |||
| { | |||
| public: | |||
| /** Constructor */ | |||
| @@ -39,32 +40,27 @@ public: | |||
| ~VST3PluginFormat(); | |||
| //============================================================================== | |||
| /** @internal */ | |||
| String getName() const override { return "VST3"; } | |||
| /** @internal */ | |||
| void findAllTypesForFile (OwnedArray<PluginDescription>& results, const String& fileOrIdentifier) override; | |||
| /** @internal */ | |||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription& description, double, int) override; | |||
| /** @internal */ | |||
| String getName() const override { return "VST3"; } | |||
| void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | |||
| bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | |||
| /** @internal */ | |||
| String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | |||
| /** @internal */ | |||
| bool pluginNeedsRescanning (const PluginDescription& description) override; | |||
| /** @internal */ | |||
| StringArray searchPathsForPlugins (const FileSearchPath& searchPath, bool recursive) override; | |||
| /** @internal */ | |||
| bool doesPluginStillExist (const PluginDescription& description) override; | |||
| /** @internal */ | |||
| bool pluginNeedsRescanning (const PluginDescription&) override; | |||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||
| bool doesPluginStillExist (const PluginDescription&) override; | |||
| FileSearchPath getDefaultLocationsToSearch() override; | |||
| /** @internal */ | |||
| bool canScanForPlugins() const override { return true; } | |||
| bool canScanForPlugins() const override { return true; } | |||
| private: | |||
| void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||
| int initialBufferSize, void* userData, | |||
| void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||
| bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||
| private: | |||
| //============================================================================== | |||
| void recursiveFileSearch (StringArray&, const File&, bool recursive); | |||
| //============================================================================== | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (VST3PluginFormat) | |||
| }; | |||
| @@ -0,0 +1,238 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2015 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ------------------------------------------------------------------------------ | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_VSTCOMMON_H_INCLUDED | |||
| #define JUCE_VSTCOMMON_H_INCLUDED | |||
| //============================================================================== | |||
| struct SpeakerMappings : private AudioChannelSet // (inheritance only to give easier access to items in the namespace) | |||
| { | |||
| struct Mapping | |||
| { | |||
| int32 vst2; | |||
| ChannelType channels[13]; | |||
| bool matches (const Array<ChannelType>& chans) const noexcept | |||
| { | |||
| const int n = sizeof (channels) / sizeof (ChannelType); | |||
| for (int i = 0; i < n; ++i) | |||
| { | |||
| if (channels[i] == unknown) return (i == chans.size()); | |||
| if (i == chans.size()) return (channels[i] == unknown); | |||
| if (channels[i] != chans.getUnchecked(i)) | |||
| return false; | |||
| } | |||
| return true; | |||
| } | |||
| }; | |||
| static AudioChannelSet vstArrangementTypeToChannelSet (int32 arr, int fallbackNumChannels) | |||
| { | |||
| if (arr == vstSpeakerConfigTypeEmpty) return AudioChannelSet::disabled(); | |||
| else if (arr == vstSpeakerConfigTypeMono) return AudioChannelSet::mono(); | |||
| else if (arr == vstSpeakerConfigTypeLR) return AudioChannelSet::stereo(); | |||
| else if (arr == vstSpeakerConfigTypeLRC) return AudioChannelSet::createLCR(); | |||
| else if (arr == vstSpeakerConfigTypeLRS) return AudioChannelSet::createLRS(); | |||
| else if (arr == vstSpeakerConfigTypeLRCS) return AudioChannelSet::createLCRS(); | |||
| else if (arr == vstSpeakerConfigTypeLRCLsRs) return AudioChannelSet::create5point0(); | |||
| else if (arr == vstSpeakerConfigTypeLRCLfeLsRs) return AudioChannelSet::create5point1(); | |||
| else if (arr == vstSpeakerConfigTypeLRCLsRsCs) return AudioChannelSet::create6point0(); | |||
| else if (arr == vstSpeakerConfigTypeLRCLfeLsRsCs) return AudioChannelSet::create6point1(); | |||
| else if (arr == vstSpeakerConfigTypeLRLsRsSlSr) return AudioChannelSet::create6point0Music(); | |||
| else if (arr == vstSpeakerConfigTypeLRLfeLsRsSlSr) return AudioChannelSet::create6point1Music(); | |||
| else if (arr == vstSpeakerConfigTypeLRCLsRsSlSr) return AudioChannelSet::create7point0(); | |||
| else if (arr == vstSpeakerConfigTypeLRCLsRsLcRc) return AudioChannelSet::create7point0SDDS(); | |||
| else if (arr == vstSpeakerConfigTypeLRCLfeLsRsSlSr) return AudioChannelSet::create7point1(); | |||
| else if (arr == vstSpeakerConfigTypeLRCLfeLsRsLcRc) return AudioChannelSet::create7point1SDDS(); | |||
| else if (arr == vstSpeakerConfigTypeLRLsRs) return AudioChannelSet::quadraphonic(); | |||
| for (const Mapping* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m) | |||
| { | |||
| if (m->vst2 == arr) | |||
| { | |||
| AudioChannelSet s; | |||
| for (int i = 0; m->channels[i] != 0; ++i) | |||
| s.addChannel (m->channels[i]); | |||
| return s; | |||
| } | |||
| } | |||
| return AudioChannelSet::discreteChannels (fallbackNumChannels); | |||
| } | |||
| static AudioChannelSet vstArrangementTypeToChannelSet (const VstSpeakerConfiguration& arr) | |||
| { | |||
| return vstArrangementTypeToChannelSet (arr.type, arr.numberOfChannels); | |||
| } | |||
| static int32 channelSetToVstArrangementType (AudioChannelSet channels) | |||
| { | |||
| if (channels == AudioChannelSet::disabled()) return vstSpeakerConfigTypeEmpty; | |||
| else if (channels == AudioChannelSet::mono()) return vstSpeakerConfigTypeMono; | |||
| else if (channels == AudioChannelSet::stereo()) return vstSpeakerConfigTypeLR; | |||
| else if (channels == AudioChannelSet::createLCR()) return vstSpeakerConfigTypeLRC; | |||
| else if (channels == AudioChannelSet::createLRS()) return vstSpeakerConfigTypeLRS; | |||
| else if (channels == AudioChannelSet::createLCRS()) return vstSpeakerConfigTypeLRCS; | |||
| else if (channels == AudioChannelSet::create5point0()) return vstSpeakerConfigTypeLRCLsRs; | |||
| else if (channels == AudioChannelSet::create5point1()) return vstSpeakerConfigTypeLRCLfeLsRs; | |||
| else if (channels == AudioChannelSet::create6point0()) return vstSpeakerConfigTypeLRCLsRsCs; | |||
| else if (channels == AudioChannelSet::create6point1()) return vstSpeakerConfigTypeLRCLfeLsRsCs; | |||
| else if (channels == AudioChannelSet::create6point0Music()) return vstSpeakerConfigTypeLRLsRsSlSr; | |||
| else if (channels == AudioChannelSet::create6point1Music()) return vstSpeakerConfigTypeLRLfeLsRsSlSr; | |||
| else if (channels == AudioChannelSet::create7point0()) return vstSpeakerConfigTypeLRCLsRsSlSr; | |||
| else if (channels == AudioChannelSet::create7point0SDDS()) return vstSpeakerConfigTypeLRCLsRsLcRc; | |||
| else if (channels == AudioChannelSet::create7point1()) return vstSpeakerConfigTypeLRCLfeLsRsSlSr; | |||
| else if (channels == AudioChannelSet::create7point1SDDS()) return vstSpeakerConfigTypeLRCLfeLsRsLcRc; | |||
| else if (channels == AudioChannelSet::quadraphonic()) return vstSpeakerConfigTypeLRLsRs; | |||
| Array<AudioChannelSet::ChannelType> chans (channels.getChannelTypes()); | |||
| if (channels == AudioChannelSet::disabled()) | |||
| return vstSpeakerConfigTypeEmpty; | |||
| for (const Mapping* m = getMappings(); m->vst2 != vstSpeakerConfigTypeEmpty; ++m) | |||
| if (m->matches (chans)) | |||
| return m->vst2; | |||
| return vstSpeakerConfigTypeUser; | |||
| } | |||
| static void channelSetToVstArrangement (const AudioChannelSet& channels, VstSpeakerConfiguration& result) | |||
| { | |||
| result.type = channelSetToVstArrangementType (channels); | |||
| result.numberOfChannels = channels.size(); | |||
| for (int i = 0; i < result.numberOfChannels; ++i) | |||
| { | |||
| VstIndividualSpeakerInfo& speaker = result.speakers[i]; | |||
| zeromem (&speaker, sizeof (VstIndividualSpeakerInfo)); | |||
| speaker.type = getSpeakerType (channels.getTypeOfChannel (i)); | |||
| } | |||
| } | |||
| static const Mapping* getMappings() noexcept | |||
| { | |||
| static const Mapping mappings[] = | |||
| { | |||
| { vstSpeakerConfigTypeMono, { centre, unknown } }, | |||
| { vstSpeakerConfigTypeLR, { left, right, unknown } }, | |||
| { vstSpeakerConfigTypeLsRs, { leftSurround, rightSurround, unknown } }, | |||
| { vstSpeakerConfigTypeLcRc, { leftCentre, rightCentre, unknown } }, | |||
| { vstSpeakerConfigTypeSlSr, { leftSurroundRear, rightSurroundRear, unknown } }, | |||
| { vstSpeakerConfigTypeCLfe, { centre, LFE, unknown } }, | |||
| { vstSpeakerConfigTypeLRC, { left, right, centre, unknown } }, | |||
| { vstSpeakerConfigTypeLRS, { left, right, surround, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLfe, { left, right, centre, LFE, unknown } }, | |||
| { vstSpeakerConfigTypeLRLfeS, { left, right, LFE, surround, unknown } }, | |||
| { vstSpeakerConfigTypeLRCS, { left, right, centre, surround, unknown } }, | |||
| { vstSpeakerConfigTypeLRLsRs, { left, right, leftSurround, rightSurround, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLfeS, { left, right, centre, LFE, surround, unknown } }, | |||
| { vstSpeakerConfigTypeLRLfeLsRs, { left, right, LFE, leftSurround, rightSurround, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLsRs, { left, right, centre, leftSurround, rightSurround, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLfeLsRs, { left, right, centre, LFE, leftSurround, rightSurround, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLsRsCs, { left, right, centre, leftSurround, rightSurround, surround, unknown } }, | |||
| { vstSpeakerConfigTypeLRLsRsSlSr, { left, right, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLfeLsRsCs, { left, right, centre, LFE, leftSurround, rightSurround, surround, unknown } }, | |||
| { vstSpeakerConfigTypeLRLfeLsRsSlSr, { left, right, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLsRsLcRc, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLsRsSlSr, { left, right, centre, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLfeLsRsLcRc, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLfeLsRsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, leftSurroundRear, rightSurroundRear, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLsRsLcRcCs, { left, right, centre, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLsRsCsSlSr, { left, right, centre, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontRight, surround, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, { left, right, centre, LFE, leftSurround, rightSurround, surround, leftSurroundRear, rightSurroundRear, unknown } }, | |||
| { vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2, { left, right, centre, LFE, leftSurround, rightSurround, topFrontLeft, topFrontCentre, topFrontRight, topRearLeft, topRearRight, LFE2, unknown } }, | |||
| { vstSpeakerConfigTypeEmpty, { unknown } } | |||
| }; | |||
| return mappings; | |||
| } | |||
| static inline int32 getSpeakerType (AudioChannelSet::ChannelType type) noexcept | |||
| { | |||
| switch (type) | |||
| { | |||
| case AudioChannelSet::left: return vstIndividualSpeakerTypeLeft; | |||
| case AudioChannelSet::right: return vstIndividualSpeakerTypeRight; | |||
| case AudioChannelSet::centre: return vstIndividualSpeakerTypeCentre; | |||
| case AudioChannelSet::LFE: return vstIndividualSpeakerTypeLFE; | |||
| case AudioChannelSet::leftSurround: return vstIndividualSpeakerTypeLeftSurround; | |||
| case AudioChannelSet::rightSurround: return vstIndividualSpeakerTypeRightSurround; | |||
| case AudioChannelSet::leftCentre: return vstIndividualSpeakerTypeLeftCentre; | |||
| case AudioChannelSet::rightCentre: return vstIndividualSpeakerTypeRightCentre; | |||
| case AudioChannelSet::surround: return vstIndividualSpeakerTypeSurround; | |||
| case AudioChannelSet::leftSurroundRear: return vstIndividualSpeakerTypeLeftRearSurround; | |||
| case AudioChannelSet::rightSurroundRear: return vstIndividualSpeakerTypeRightRearSurround; | |||
| case AudioChannelSet::topMiddle: return vstIndividualSpeakerTypeTopMiddle; | |||
| case AudioChannelSet::topFrontLeft: return vstIndividualSpeakerTypeTopFrontLeft; | |||
| case AudioChannelSet::topFrontCentre: return vstIndividualSpeakerTypeTopFrontCentre; | |||
| case AudioChannelSet::topFrontRight: return vstIndividualSpeakerTypeTopFrontRight; | |||
| case AudioChannelSet::topRearLeft: return vstIndividualSpeakerTypeTopRearLeft; | |||
| case AudioChannelSet::topRearCentre: return vstIndividualSpeakerTypeTopRearCentre; | |||
| case AudioChannelSet::topRearRight: return vstIndividualSpeakerTypeTopRearRight; | |||
| case AudioChannelSet::LFE2: return vstIndividualSpeakerTypeLFE2; | |||
| default: break; | |||
| } | |||
| return 0; | |||
| } | |||
| static inline AudioChannelSet::ChannelType getChannelType (int32 type) noexcept | |||
| { | |||
| switch (type) | |||
| { | |||
| case vstIndividualSpeakerTypeLeft: return AudioChannelSet::left; | |||
| case vstIndividualSpeakerTypeRight: return AudioChannelSet::right; | |||
| case vstIndividualSpeakerTypeCentre: return AudioChannelSet::centre; | |||
| case vstIndividualSpeakerTypeLFE: return AudioChannelSet::LFE; | |||
| case vstIndividualSpeakerTypeLeftSurround: return AudioChannelSet::leftSurround; | |||
| case vstIndividualSpeakerTypeRightSurround: return AudioChannelSet::rightSurround; | |||
| case vstIndividualSpeakerTypeLeftCentre: return AudioChannelSet::leftCentre; | |||
| case vstIndividualSpeakerTypeRightCentre: return AudioChannelSet::rightCentre; | |||
| case vstIndividualSpeakerTypeSurround: return AudioChannelSet::surround; | |||
| case vstIndividualSpeakerTypeLeftRearSurround: return AudioChannelSet::leftSurroundRear; | |||
| case vstIndividualSpeakerTypeRightRearSurround: return AudioChannelSet::rightSurroundRear; | |||
| case vstIndividualSpeakerTypeTopMiddle: return AudioChannelSet::topMiddle; | |||
| case vstIndividualSpeakerTypeTopFrontLeft: return AudioChannelSet::topFrontLeft; | |||
| case vstIndividualSpeakerTypeTopFrontCentre: return AudioChannelSet::topFrontCentre; | |||
| case vstIndividualSpeakerTypeTopFrontRight: return AudioChannelSet::topFrontRight; | |||
| case vstIndividualSpeakerTypeTopRearLeft: return AudioChannelSet::topRearLeft; | |||
| case vstIndividualSpeakerTypeTopRearCentre: return AudioChannelSet::topRearCentre; | |||
| case vstIndividualSpeakerTypeTopRearRight: return AudioChannelSet::topRearRight; | |||
| case vstIndividualSpeakerTypeLFE2: return AudioChannelSet::LFE2; | |||
| default: break; | |||
| } | |||
| return AudioChannelSet::unknown; | |||
| } | |||
| }; | |||
| #endif // JUCE_VSTCOMMON_H_INCLUDED | |||
| @@ -0,0 +1,458 @@ | |||
| /* | |||
| ============================================================================== | |||
| This file is part of the JUCE library. | |||
| Copyright (c) 2016 - ROLI Ltd. | |||
| Permission is granted to use this software under the terms of either: | |||
| a) the GPL v2 (or any later version) | |||
| b) the Affero GPL v3 | |||
| Details of these licenses can be found at: www.gnu.org/licenses | |||
| JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||
| WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||
| A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||
| ----------------------------------------------------------------------------- | |||
| To release a closed-source product which uses JUCE, commercial licenses are | |||
| available: visit www.juce.com for more information. | |||
| ============================================================================== | |||
| */ | |||
| #ifndef JUCE_VSTINTERFACE_H_INCLUDED | |||
| #define JUCE_VSTINTERFACE_H_INCLUDED | |||
| #include "../../juce_core/juce_core.h" | |||
| using namespace juce; | |||
| #if JUCE_MSVC | |||
| #define VSTINTERFACECALL __cdecl | |||
| #pragma pack(push) | |||
| #pragma pack(8) | |||
| #elif JUCE_MAC || JUCE_IOS | |||
| #define VSTINTERFACECALL | |||
| #if JUCE_64BIT | |||
| #pragma options align=power | |||
| #else | |||
| #pragma options align=mac68k | |||
| #endif | |||
| #else | |||
| #define VSTINTERFACECALL | |||
| #pragma pack(push, 8) | |||
| #endif | |||
| const int32 juceVstInterfaceVersion = 2400; | |||
| const int32 juceVstInterfaceIdentifier = 0x56737450; // The "magic" identifier in the SDK is 'VstP'. | |||
| //============================================================================== | |||
| struct VstEffectInterface | |||
| { | |||
| int32 interfaceIdentifier; | |||
| pointer_sized_int (VSTINTERFACECALL* dispatchFunction) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); | |||
| void (VSTINTERFACECALL* processAudioFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); | |||
| void (VSTINTERFACECALL* setParameterValueFunction) (VstEffectInterface*, int32 parameterIndex, float value); | |||
| float (VSTINTERFACECALL* getParameterValueFunction) (VstEffectInterface*, int32 parameterIndex); | |||
| int32 numPrograms; | |||
| int32 numParameters; | |||
| int32 numInputChannels; | |||
| int32 numOutputChannels; | |||
| int32 flags; | |||
| pointer_sized_int hostSpace1; | |||
| pointer_sized_int hostSpace2; | |||
| int32 latency; | |||
| int32 deprecated1; | |||
| int32 deprecated2; | |||
| float deprecated3; | |||
| void* effectPointer; | |||
| void* userPointer; | |||
| int32 plugInIdentifier; | |||
| int32 plugInVersion; | |||
| void (VSTINTERFACECALL* processAudioInplaceFunction) (VstEffectInterface*, float** inputs, float** outputs, int32 numSamples); | |||
| void (VSTINTERFACECALL* processDoubleAudioInplaceFunction) (VstEffectInterface*, double** inputs, double** outputs, int32 numSamples); | |||
| char emptySpace[56]; | |||
| }; | |||
| typedef pointer_sized_int (VSTINTERFACECALL* VstHostCallback) (VstEffectInterface*, int32 op, int32 index, pointer_sized_int value, void* ptr, float opt); | |||
| enum VstEffectInterfaceFlags | |||
| { | |||
| vstEffectFlagHasEditor = 1, | |||
| vstEffectFlagInplaceAudio = 16, | |||
| vstEffectFlagDataInChunks = 32, | |||
| vstEffectFlagIsSynth = 256, | |||
| vstEffectFlagInplaceDoubleAudio = 4096 | |||
| }; | |||
| //============================================================================== | |||
| enum VstHostToPlugInOpcodes | |||
| { | |||
| plugInOpcodeOpen, | |||
| plugInOpcodeClose, | |||
| plugInOpcodeSetCurrentProgram, | |||
| plugInOpcodeGetCurrentProgram, | |||
| plugInOpcodeSetCurrentProgramName, | |||
| plugInOpcodeGetCurrentProgramName, | |||
| plugInOpcodeGetParameterLabel, | |||
| plugInOpcodeGetParameterText, | |||
| plugInOpcodeGetParameterName, | |||
| plugInOpcodeSetSampleRate = plugInOpcodeGetParameterName + 2, | |||
| plugInOpcodeSetBlockSize, | |||
| plugInOpcodeResumeSuspend, | |||
| plugInOpcodeGetEditorBounds, | |||
| plugInOpcodeOpenEditor, | |||
| plugInOpcodeCloseEditor, | |||
| plugInOpcodeDrawEditor, | |||
| plugInOpcodeGetMouse, | |||
| plugInOpcodeEditorIdle = plugInOpcodeGetMouse + 2, | |||
| plugInOpcodeeffEditorTop, | |||
| plugInOpcodeSleepEditor, | |||
| plugInOpcodeIdentify, | |||
| plugInOpcodeGetData, | |||
| plugInOpcodeSetData, | |||
| plugInOpcodePreAudioProcessingEvents, | |||
| plugInOpcodeIsParameterAutomatable, | |||
| plugInOpcodeParameterValueForText, | |||
| plugInOpcodeGetProgramName = plugInOpcodeParameterValueForText + 2, | |||
| plugInOpcodeConnectInput = plugInOpcodeGetProgramName + 2, | |||
| plugInOpcodeConnectOutput, | |||
| plugInOpcodeGetInputPinProperties, | |||
| plugInOpcodeGetOutputPinProperties, | |||
| plugInOpcodeGetPlugInCategory, | |||
| plugInOpcodeSetSpeakerConfiguration = plugInOpcodeGetPlugInCategory + 7, | |||
| plugInOpcodeSetBypass = plugInOpcodeSetSpeakerConfiguration + 2, | |||
| plugInOpcodeGetPlugInName, | |||
| plugInOpcodeGetManufacturerName = plugInOpcodeGetPlugInName + 2, | |||
| plugInOpcodeGetManufacturerProductName, | |||
| plugInOpcodeGetManufacturerVersion, | |||
| plugInOpcodeManufacturerSpecific, | |||
| plugInOpcodeCanPlugInDo, | |||
| plugInOpcodeGetTailSize, | |||
| plugInOpcodeIdle, | |||
| plugInOpcodeKeyboardFocusRequired = plugInOpcodeIdle + 4, | |||
| plugInOpcodeGetVstInterfaceVersion, | |||
| plugInOpcodeGetCurrentMidiProgram = plugInOpcodeGetVstInterfaceVersion + 5, | |||
| plugInOpcodeGetSpeakerArrangement = plugInOpcodeGetCurrentMidiProgram + 6, | |||
| plugInOpcodeNextPlugInUniqueID, | |||
| plugInOpcodeStartProcess, | |||
| plugInOpcodeStopProcess, | |||
| plugInOpcodeSetNumberOfSamplesToProcess, | |||
| plugInOpcodeSetSampleFloatType = plugInOpcodeSetNumberOfSamplesToProcess + 4, | |||
| plugInOpcodeMaximum = plugInOpcodeSetSampleFloatType | |||
| }; | |||
| enum VstPlugInToHostOpcodes | |||
| { | |||
| hostOpcodeParameterChanged, | |||
| hostOpcodeVstVersion, | |||
| hostOpcodeCurrentId, | |||
| hostOpcodeIdle, | |||
| hostOpcodePinConnected, | |||
| hostOpcodePlugInWantsMidi = hostOpcodePinConnected + 2, | |||
| hostOpcodeGetTimingInfo, | |||
| hostOpcodePreAudioProcessingEvents, | |||
| hostOpcodeSetTime, | |||
| hostOpcodeTempoAt, | |||
| hostOpcodeGetNumberOfAutomatableParameters, | |||
| hostOpcodeGetParameterInterval, | |||
| hostOpcodeIOModified, | |||
| hostOpcodeNeedsIdle, | |||
| hostOpcodeWindowSize, | |||
| hostOpcodeGetSampleRate, | |||
| hostOpcodeGetBlockSize, | |||
| hostOpcodeGetInputLatency, | |||
| hostOpcodeGetOutputLatency, | |||
| hostOpcodeGetPreviousPlugIn, | |||
| hostOpcodeGetNextPlugIn, | |||
| hostOpcodeWillReplace, | |||
| hostOpcodeGetCurrentAudioProcessingLevel, | |||
| hostOpcodeGetAutomationState, | |||
| hostOpcodeOfflineStart, | |||
| hostOpcodeOfflineReadSource, | |||
| hostOpcodeOfflineWrite, | |||
| hostOpcodeOfflineGetCurrentPass, | |||
| hostOpcodeOfflineGetCurrentMetaPass, | |||
| hostOpcodeSetOutputSampleRate, | |||
| hostOpcodeGetOutputSpeakerConfiguration, | |||
| hostOpcodeGetManufacturerName, | |||
| hostOpcodeGetProductName, | |||
| hostOpcodeGetManufacturerVersion, | |||
| hostOpcodeManufacturerSpecific, | |||
| hostOpcodeSetIcon, | |||
| hostOpcodeCanHostDo, | |||
| hostOpcodeGetLanguage, | |||
| hostOpcodeOpenEditorWindow, | |||
| hostOpcodeCloseEditorWindow, | |||
| hostOpcodeGetDirectory, | |||
| hostOpcodeUpdateView, | |||
| hostOpcodeParameterChangeGestureBegin, | |||
| hostOpcodeParameterChangeGestureEnd, | |||
| }; | |||
| //============================================================================== | |||
| enum VstProcessingSampleType | |||
| { | |||
| vstProcessingSampleTypeFloat, | |||
| vstProcessingSampleTypeDouble | |||
| }; | |||
| //============================================================================== | |||
| // These names must be identical to the Steinberg SDK so JUCE users can set | |||
| // exactly what they want. | |||
| enum VstPlugInCategory | |||
| { | |||
| kPlugCategUnknown, | |||
| kPlugCategEffect, | |||
| kPlugCategSynth, | |||
| kPlugCategAnalysis, | |||
| kPlugCategMastering, | |||
| kPlugCategSpacializer, | |||
| kPlugCategRoomFx, | |||
| kPlugSurroundFx, | |||
| kPlugCategRestoration, | |||
| kPlugCategOfflineProcess, | |||
| kPlugCategShell, | |||
| kPlugCategGenerator | |||
| }; | |||
| //============================================================================== | |||
| struct VstEditorBounds | |||
| { | |||
| int16 upper; | |||
| int16 leftmost; | |||
| int16 lower; | |||
| int16 rightmost; | |||
| }; | |||
| //============================================================================== | |||
| enum VstMaxStringLengths | |||
| { | |||
| vstMaxNameLength = 64, | |||
| vstMaxParameterOrPinLabelLength = 64, | |||
| vstMaxParameterOrPinShortLabelLength = 8, | |||
| vstMaxCategoryLength = 24, | |||
| vstMaxManufacturerStringLength = 64, | |||
| vstMaxPlugInNameStringLength = 64 | |||
| }; | |||
| //============================================================================== | |||
| struct VstPinInfo | |||
| { | |||
| char text[vstMaxParameterOrPinLabelLength]; | |||
| int32 flags; | |||
| int32 configurationType; | |||
| char shortText[vstMaxParameterOrPinShortLabelLength]; | |||
| char unused[48]; | |||
| }; | |||
| enum VstPinInfoFlags | |||
| { | |||
| vstPinInfoFlagIsActive = 1, | |||
| vstPinInfoFlagIsStereo = 2, | |||
| vstPinInfoFlagValid = 4 | |||
| }; | |||
| //============================================================================== | |||
| struct VstEvent | |||
| { | |||
| int32 type; | |||
| int32 size; | |||
| int32 sampleOffset; | |||
| int32 flags; | |||
| char content[16]; | |||
| }; | |||
| enum VstEventTypes | |||
| { | |||
| vstMidiEventType = 1, | |||
| vstSysExEventType = 6 | |||
| }; | |||
| struct VstEventBlock | |||
| { | |||
| int32 numberOfEvents; | |||
| pointer_sized_int future; | |||
| VstEvent* events[2]; | |||
| }; | |||
| struct VstMidiEvent | |||
| { | |||
| int32 type; | |||
| int32 size; | |||
| int32 sampleOffset; | |||
| int32 flags; | |||
| int32 noteSampleLength; | |||
| int32 noteSampleOffset; | |||
| char midiData[4]; | |||
| char tuning; | |||
| char noteVelocityOff; | |||
| char future1; | |||
| char future2; | |||
| }; | |||
| enum VstMidiEventFlags | |||
| { | |||
| vstMidiEventIsRealtime = 1 | |||
| }; | |||
| struct VstSysExEvent | |||
| { | |||
| int32 type; | |||
| int32 size; | |||
| int32 offsetSamples; | |||
| int32 flags; | |||
| int32 sysExDumpSize; | |||
| pointer_sized_int future1; | |||
| char* sysExDump; | |||
| pointer_sized_int future2; | |||
| }; | |||
| //============================================================================== | |||
| struct VstTimingInformation | |||
| { | |||
| double samplePosition; | |||
| double sampleRate; | |||
| double systemTimeNanoseconds; | |||
| double musicalPosition; | |||
| double tempoBPM; | |||
| double lastBarPosition; | |||
| double loopStartPosition; | |||
| double loopEndPosition; | |||
| int32 timeSignatureNumerator; | |||
| int32 timeSignatureDenominator; | |||
| int32 smpteOffset; | |||
| int32 smpteRate; | |||
| int32 samplesToNearestClock; | |||
| int32 flags; | |||
| }; | |||
| enum VstTimingInformationFlags | |||
| { | |||
| vstTimingInfoFlagTransportChanged = 1, | |||
| vstTimingInfoFlagCurrentlyPlaying = 2, | |||
| vstTimingInfoFlagLoopActive = 4, | |||
| vstTimingInfoFlagCurrentlyRecording = 8, | |||
| vstTimingInfoFlagAutomationWriteModeActive = 64, | |||
| vstTimingInfoFlagAutomationReadModeActive = 128, | |||
| vstTimingInfoFlagNanosecondsValid = 256, | |||
| vstTimingInfoFlagMusicalPositionValid = 512, | |||
| vstTimingInfoFlagTempoValid = 1024, | |||
| vstTimingInfoFlagLastBarPositionValid = 2056, | |||
| vstTimingInfoFlagLoopPositionValid = 4096, | |||
| vstTimingInfoFlagTimeSignatureValid = 8192, | |||
| vstTimingInfoFlagSmpteValid = 16384, | |||
| vstTimingInfoFlagNearestClockValid = 32768 | |||
| }; | |||
| //============================================================================== | |||
| enum VstSmpteRates | |||
| { | |||
| vstSmpteRateFps24, | |||
| vstSmpteRateFps25, | |||
| vstSmpteRateFps2997, | |||
| vstSmpteRateFps30, | |||
| vstSmpteRateFps2997drop, | |||
| vstSmpteRateFps30drop, | |||
| vstSmpteRate16mmFilm, | |||
| vstSmpteRate35mmFilm, | |||
| vstSmpteRateFps239 = vstSmpteRate35mmFilm + 3, | |||
| vstSmpteRateFps249 , | |||
| vstSmpteRateFps599, | |||
| vstSmpteRateFps60 | |||
| }; | |||
| //============================================================================== | |||
| struct VstIndividualSpeakerInfo | |||
| { | |||
| float azimuthalAngle; | |||
| float elevationAngle; | |||
| float radius; | |||
| float reserved; | |||
| char label[vstMaxNameLength]; | |||
| int32 type; | |||
| char unused[28]; | |||
| }; | |||
| enum VstIndividualSpeakerType | |||
| { | |||
| vstIndividualSpeakerTypeUndefined = 0x7fffffff, | |||
| vstIndividualSpeakerTypeMono = 0, | |||
| vstIndividualSpeakerTypeLeft, | |||
| vstIndividualSpeakerTypeRight, | |||
| vstIndividualSpeakerTypeCentre, | |||
| vstIndividualSpeakerTypeLFE, | |||
| vstIndividualSpeakerTypeLeftSurround, | |||
| vstIndividualSpeakerTypeRightSurround, | |||
| vstIndividualSpeakerTypeLeftCentre, | |||
| vstIndividualSpeakerTypeRightCentre, | |||
| vstIndividualSpeakerTypeSurround, | |||
| vstIndividualSpeakerTypeCentreSurround = vstIndividualSpeakerTypeSurround, | |||
| vstIndividualSpeakerTypeLeftRearSurround, | |||
| vstIndividualSpeakerTypeRightRearSurround, | |||
| vstIndividualSpeakerTypeTopMiddle, | |||
| vstIndividualSpeakerTypeTopFrontLeft, | |||
| vstIndividualSpeakerTypeTopFrontCentre, | |||
| vstIndividualSpeakerTypeTopFrontRight, | |||
| vstIndividualSpeakerTypeTopRearLeft, | |||
| vstIndividualSpeakerTypeTopRearCentre, | |||
| vstIndividualSpeakerTypeTopRearRight, | |||
| vstIndividualSpeakerTypeLFE2 | |||
| }; | |||
| struct VstSpeakerConfiguration | |||
| { | |||
| int32 type; | |||
| int32 numberOfChannels; | |||
| VstIndividualSpeakerInfo speakers[8]; | |||
| }; | |||
| enum VstSpeakerConfigurationType | |||
| { | |||
| vstSpeakerConfigTypeUser = -2, | |||
| vstSpeakerConfigTypeEmpty = -1, | |||
| vstSpeakerConfigTypeMono = 0, | |||
| vstSpeakerConfigTypeLR, | |||
| vstSpeakerConfigTypeLsRs, | |||
| vstSpeakerConfigTypeLcRc, | |||
| vstSpeakerConfigTypeSlSr, | |||
| vstSpeakerConfigTypeCLfe, | |||
| vstSpeakerConfigTypeLRC, | |||
| vstSpeakerConfigTypeLRS, | |||
| vstSpeakerConfigTypeLRCLfe, | |||
| vstSpeakerConfigTypeLRLfeS, | |||
| vstSpeakerConfigTypeLRCS, | |||
| vstSpeakerConfigTypeLRLsRs, | |||
| vstSpeakerConfigTypeLRCLfeS, | |||
| vstSpeakerConfigTypeLRLfeLsRs, | |||
| vstSpeakerConfigTypeLRCLsRs, | |||
| vstSpeakerConfigTypeLRCLfeLsRs, | |||
| vstSpeakerConfigTypeLRCLsRsCs, | |||
| vstSpeakerConfigTypeLRLsRsSlSr, | |||
| vstSpeakerConfigTypeLRCLfeLsRsCs, | |||
| vstSpeakerConfigTypeLRLfeLsRsSlSr, | |||
| vstSpeakerConfigTypeLRCLsRsLcRc, | |||
| vstSpeakerConfigTypeLRCLsRsSlSr, | |||
| vstSpeakerConfigTypeLRCLfeLsRsLcRc, | |||
| vstSpeakerConfigTypeLRCLfeLsRsSlSr, | |||
| vstSpeakerConfigTypeLRCLsRsLcRcCs, | |||
| vstSpeakerConfigTypeLRCLsRsCsSlSr, | |||
| vstSpeakerConfigTypeLRCLfeLsRsLcRcCs, | |||
| vstSpeakerConfigTypeLRCLfeLsRsCsSlSr, | |||
| vstSpeakerConfigTypeLRCLfeLsRsTflTfcTfrTrlTrrLfe2 | |||
| }; | |||
| #if JUCE_MSVC | |||
| #pragma pack(pop) | |||
| #elif JUCE_MAC || JUCE_IOS | |||
| #pragma options align=reset | |||
| #else | |||
| #pragma pack(pop) | |||
| #endif | |||
| #endif // JUCE_VSTINTERFACE_H_INCLUDED | |||
| @@ -22,7 +22,8 @@ | |||
| ============================================================================== | |||
| */ | |||
| #ifdef __aeffect__ // NB: this must come first, *before* the header-guard. | |||
| // NB: this must come first, *before* the header-guard. | |||
| #ifdef JUCE_VSTINTERFACE_H_INCLUDED | |||
| #ifndef JUCE_VSTMIDIEVENTLIST_H_INCLUDED | |||
| #define JUCE_VSTMIDIEVENTLIST_H_INCLUDED | |||
| @@ -53,7 +54,7 @@ public: | |||
| numEventsUsed = 0; | |||
| if (events != nullptr) | |||
| events->numEvents = 0; | |||
| events->numberOfEvents = 0; | |||
| } | |||
| void addEvent (const void* const midiData, const int numBytes, const int frameOffset) | |||
| @@ -61,65 +62,65 @@ public: | |||
| ensureSize (numEventsUsed + 1); | |||
| VstMidiEvent* const e = (VstMidiEvent*) (events->events [numEventsUsed]); | |||
| events->numEvents = ++numEventsUsed; | |||
| events->numberOfEvents = ++numEventsUsed; | |||
| if (numBytes <= 4) | |||
| { | |||
| if (e->type == kVstSysExType) | |||
| if (e->type == vstSysExEventType) | |||
| { | |||
| delete[] (((VstMidiSysexEvent*) e)->sysexDump); | |||
| e->type = kVstMidiType; | |||
| e->byteSize = sizeof (VstMidiEvent); | |||
| e->noteLength = 0; | |||
| e->noteOffset = 0; | |||
| e->detune = 0; | |||
| e->noteOffVelocity = 0; | |||
| delete[] (((VstSysExEvent*) e)->sysExDump); | |||
| e->type = vstMidiEventType; | |||
| e->size = sizeof (VstMidiEvent); | |||
| e->noteSampleLength = 0; | |||
| e->noteSampleOffset = 0; | |||
| e->tuning = 0; | |||
| e->noteVelocityOff = 0; | |||
| } | |||
| e->deltaFrames = frameOffset; | |||
| e->sampleOffset = frameOffset; | |||
| memcpy (e->midiData, midiData, (size_t) numBytes); | |||
| } | |||
| else | |||
| { | |||
| VstMidiSysexEvent* const se = (VstMidiSysexEvent*) e; | |||
| VstSysExEvent* const se = (VstSysExEvent*) e; | |||
| if (se->type == kVstSysExType) | |||
| delete[] se->sysexDump; | |||
| if (se->type == vstSysExEventType) | |||
| delete[] se->sysExDump; | |||
| se->sysexDump = new char [numBytes]; | |||
| memcpy (se->sysexDump, midiData, (size_t) numBytes); | |||
| se->sysExDump = new char [(size_t) numBytes]; | |||
| memcpy (se->sysExDump, midiData, (size_t) numBytes); | |||
| se->type = kVstSysExType; | |||
| se->byteSize = sizeof (VstMidiSysexEvent); | |||
| se->deltaFrames = frameOffset; | |||
| se->type = vstSysExEventType; | |||
| se->size = sizeof (VstSysExEvent); | |||
| se->offsetSamples = frameOffset; | |||
| se->flags = 0; | |||
| se->dumpBytes = numBytes; | |||
| se->resvd1 = 0; | |||
| se->resvd2 = 0; | |||
| se->sysExDumpSize = numBytes; | |||
| se->future1 = 0; | |||
| se->future2 = 0; | |||
| } | |||
| } | |||
| //============================================================================== | |||
| // Handy method to pull the events out of an event buffer supplied by the host | |||
| // or plugin. | |||
| static void addEventsToMidiBuffer (const VstEvents* events, MidiBuffer& dest) | |||
| static void addEventsToMidiBuffer (const VstEventBlock* events, MidiBuffer& dest) | |||
| { | |||
| for (int i = 0; i < events->numEvents; ++i) | |||
| for (int i = 0; i < events->numberOfEvents; ++i) | |||
| { | |||
| const VstEvent* const e = events->events[i]; | |||
| if (e != nullptr) | |||
| { | |||
| if (e->type == kVstMidiType) | |||
| if (e->type == vstMidiEventType) | |||
| { | |||
| dest.addEvent ((const juce::uint8*) ((const VstMidiEvent*) e)->midiData, | |||
| 4, e->deltaFrames); | |||
| 4, e->sampleOffset); | |||
| } | |||
| else if (e->type == kVstSysExType) | |||
| else if (e->type == vstSysExEventType) | |||
| { | |||
| dest.addEvent ((const juce::uint8*) ((const VstMidiSysexEvent*) e)->sysexDump, | |||
| (int) ((const VstMidiSysexEvent*) e)->dumpBytes, | |||
| e->deltaFrames); | |||
| dest.addEvent ((const juce::uint8*) ((const VstSysExEvent*) e)->sysExDump, | |||
| (int) ((const VstSysExEvent*) e)->sysExDumpSize, | |||
| e->sampleOffset); | |||
| } | |||
| } | |||
| } | |||
| @@ -160,24 +161,24 @@ public: | |||
| } | |||
| //============================================================================== | |||
| HeapBlock<VstEvents> events; | |||
| HeapBlock<VstEventBlock> events; | |||
| private: | |||
| int numEventsUsed, numEventsAllocated; | |||
| static VstEvent* allocateVSTEvent() | |||
| { | |||
| VstEvent* const e = (VstEvent*) std::calloc (1, sizeof (VstMidiEvent) > sizeof (VstMidiSysexEvent) ? sizeof (VstMidiEvent) | |||
| : sizeof (VstMidiSysexEvent)); | |||
| e->type = kVstMidiType; | |||
| e->byteSize = sizeof (VstMidiEvent); | |||
| VstEvent* const e = (VstEvent*) std::calloc (1, sizeof (VstMidiEvent) > sizeof (VstSysExEvent) ? sizeof (VstMidiEvent) | |||
| : sizeof (VstSysExEvent)); | |||
| e->type = vstMidiEventType; | |||
| e->size = sizeof (VstMidiEvent); | |||
| return e; | |||
| } | |||
| static void freeVSTEvent (VstEvent* e) | |||
| { | |||
| if (e->type == kVstSysExType) | |||
| delete[] (((VstMidiSysexEvent*) e)->sysexDump); | |||
| if (e->type == vstSysExEventType) | |||
| delete[] (((VstSysExEvent*) e)->sysExDump); | |||
| std::free (e); | |||
| } | |||
| @@ -22,7 +22,7 @@ | |||
| ============================================================================== | |||
| */ | |||
| #if JUCE_PLUGINHOST_VST || DOXYGEN | |||
| #if (JUCE_PLUGINHOST_VST && (JUCE_MAC || JUCE_WINDOWS || JUCE_LINUX || JUCE_IOS)) || DOXYGEN | |||
| //============================================================================== | |||
| /** | |||
| @@ -36,7 +36,7 @@ public: | |||
| ~VSTPluginFormat(); | |||
| //============================================================================== | |||
| /** Attempts to retreive the VSTXML data from a plugin. | |||
| /** Attempts to retrieve the VSTXML data from a plugin. | |||
| Will return nullptr if the plugin isn't a VST, or if it doesn't have any VSTXML. | |||
| */ | |||
| static const XmlElement* getVSTXML (AudioPluginInstance* plugin); | |||
| @@ -53,6 +53,13 @@ public: | |||
| /** Attempts to set a VST's state from a chunk of memory. */ | |||
| static bool setChunkData (AudioPluginInstance* plugin, const void* data, int size, bool isPreset); | |||
| /** Given a suitable function pointer to a VSTPluginMain function, this will attempt to | |||
| instantiate and return a plugin for it. | |||
| */ | |||
| static AudioPluginInstance* createCustomVSTFromMainCall (void* entryPointFunction, | |||
| double initialSampleRate, | |||
| int initialBufferSize); | |||
| //============================================================================== | |||
| /** Base class for some extra functions that can be attached to a VST plugin instance. */ | |||
| class ExtraFunctions | |||
| @@ -75,23 +82,21 @@ public: | |||
| static void setExtraFunctions (AudioPluginInstance* plugin, ExtraFunctions* functions); | |||
| //============================================================================== | |||
| #if JUCE_64BIT | |||
| typedef int64 VstIntPtr; | |||
| #else | |||
| typedef int32 VstIntPtr; | |||
| #endif | |||
| /** This simply calls directly to the VST's AEffect::dispatcher() function. */ | |||
| static VstIntPtr JUCE_CALLTYPE dispatcher (AudioPluginInstance*, int32, int32, VstIntPtr, void*, float); | |||
| static pointer_sized_int JUCE_CALLTYPE dispatcher (AudioPluginInstance*, int32, int32, pointer_sized_int, void*, float); | |||
| /** Given a VstEffectInterface* (aka vst::AEffect*), this will return the juce AudioPluginInstance | |||
| that is being used to wrap it | |||
| */ | |||
| static AudioPluginInstance* getPluginInstanceFromVstEffectInterface (void* aEffect); | |||
| //============================================================================== | |||
| String getName() const override { return "VST"; } | |||
| void findAllTypesForFile (OwnedArray<PluginDescription>&, const String& fileOrIdentifier) override; | |||
| AudioPluginInstance* createInstanceFromDescription (const PluginDescription&, double, int) override; | |||
| bool fileMightContainThisPluginType (const String& fileOrIdentifier) override; | |||
| String getNameOfPluginFromIdentifier (const String& fileOrIdentifier) override; | |||
| bool pluginNeedsRescanning (const PluginDescription&) override; | |||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive) override; | |||
| StringArray searchPathsForPlugins (const FileSearchPath&, bool recursive, bool) override; | |||
| bool doesPluginStillExist (const PluginDescription&) override; | |||
| FileSearchPath getDefaultLocationsToSearch() override; | |||
| bool canScanForPlugins() const override { return true; } | |||
| @@ -103,6 +108,14 @@ public: | |||
| */ | |||
| virtual void aboutToScanVSTShellPlugin (const PluginDescription&); | |||
| private: | |||
| //============================================================================== | |||
| void createPluginInstance (const PluginDescription&, double initialSampleRate, | |||
| int initialBufferSize, void* userData, | |||
| void (*callback) (void*, AudioPluginInstance*, const String&)) override; | |||
| bool requiresUnblockedMessageThreadDuringCreation (const PluginDescription&) const noexcept override; | |||
| private: | |||
| void recursiveFileSearch (StringArray&, const File&, bool recursive); | |||
| @@ -31,10 +31,15 @@ | |||
| #error "Incorrect use of JUCE cpp file" | |||
| #endif | |||
| #include "AppConfig.h" | |||
| #define JUCE_CORE_INCLUDE_NATIVE_HEADERS 1 | |||
| #include "juce_audio_processors.h" | |||
| #include "../juce_gui_extra/juce_gui_extra.h" | |||
| #if ! JUCE_AUDIO_PROCESSOR_NO_GUI | |||
| #include <juce_gui_extra/juce_gui_extra.h> | |||
| #endif | |||
| //============================================================================== | |||
| #if JUCE_MAC | |||
| @@ -70,9 +75,18 @@ static inline bool arrayContainsPlugin (const OwnedArray<PluginDescription>& lis | |||
| return false; | |||
| } | |||
| #if JUCE_MAC | |||
| #if JUCE_MAC || JUCE_IOS | |||
| #if JUCE_IOS | |||
| #define JUCE_IOS_MAC_VIEW UIView | |||
| typedef UIViewComponent ViewComponentBaseClass; | |||
| #else | |||
| #define JUCE_IOS_MAC_VIEW NSView | |||
| typedef NSViewComponent ViewComponentBaseClass; | |||
| #endif | |||
| //============================================================================== | |||
| struct AutoResizingNSViewComponent : public NSViewComponent, | |||
| private AsyncUpdater { | |||
| AutoResizingNSViewComponent(); | |||
| @@ -84,7 +98,7 @@ struct AutoResizingNSViewComponent : public NSViewComponent, | |||
| struct AutoResizingNSViewComponentWithParent : public AutoResizingNSViewComponent, | |||
| private Timer { | |||
| AutoResizingNSViewComponentWithParent(); | |||
| NSView* getChildView() const; | |||
| JUCE_IOS_MAC_VIEW* getChildView() const; | |||
| void timerCallback() override; | |||
| }; | |||
| @@ -112,27 +126,29 @@ void AutoResizingNSViewComponent::handleAsyncUpdate() | |||
| resizeToFitView(); | |||
| } | |||
| //============================================================================== | |||
| AutoResizingNSViewComponentWithParent::AutoResizingNSViewComponentWithParent() | |||
| { | |||
| NSView* v = [[NSView alloc] init]; | |||
| JUCE_IOS_MAC_VIEW* v = [[JUCE_IOS_MAC_VIEW alloc] init]; | |||
| setView (v); | |||
| [v release]; | |||
| startTimer(500); | |||
| startTimer(30); | |||
| } | |||
| NSView* AutoResizingNSViewComponentWithParent::getChildView() const | |||
| JUCE_IOS_MAC_VIEW* AutoResizingNSViewComponentWithParent::getChildView() const | |||
| { | |||
| if (NSView* parent = (NSView*)getView()) | |||
| if (JUCE_IOS_MAC_VIEW* parent = (JUCE_IOS_MAC_VIEW*)getView()) | |||
| if ([[parent subviews] count] > 0) | |||
| return [[parent subviews] objectAtIndex: 0]; | |||
| return nil; | |||
| } | |||
| void AutoResizingNSViewComponentWithParent::timerCallback() | |||
| { | |||
| if (NSView* child = getChildView()) | |||
| if (JUCE_IOS_MAC_VIEW* child = getChildView()) | |||
| { | |||
| stopTimer(); | |||
| setView(child); | |||
| @@ -148,12 +164,10 @@ void AutoResizingNSViewComponentWithParent::timerCallback() | |||
| #include "format/juce_AudioPluginFormat.cpp" | |||
| #include "format/juce_AudioPluginFormatManager.cpp" | |||
| #include "processors/juce_AudioProcessor.cpp" | |||
| #include "processors/juce_AudioChannelSet.cpp" | |||
| #include "processors/juce_AudioProcessorEditor.cpp" | |||
| #include "processors/juce_AudioProcessorGraph.cpp" | |||
| #include "processors/juce_GenericAudioProcessorEditor.cpp" | |||
| #include "processors/juce_PluginDescription.cpp" | |||
| #include "processors/AudioProcessorGraphMultiThreaded.cpp" | |||
| #include "format_types/juce_LADSPAPluginFormat.cpp" | |||
| #include "format_types/juce_VSTPluginFormat.cpp" | |||
| #include "format_types/juce_VST3PluginFormat.cpp" | |||
| @@ -22,17 +22,42 @@ | |||
| ============================================================================== | |||
| */ | |||
| /******************************************************************************* | |||
| The block below describes the properties of this module, and is read by | |||
| the Projucer to automatically generate project code that uses it. | |||
| For details about the syntax and how to create or use a module, see the | |||
| JUCE Module Format.txt file. | |||
| BEGIN_JUCE_MODULE_DECLARATION | |||
| ID: juce_audio_processors | |||
| vendor: juce | |||
| version: 4.3.0 | |||
| name: JUCE audio processor classes | |||
| description: Classes for loading and playing VST, AU, or internally-generated audio processors. | |||
| website: http://www.juce.com/juce | |||
| license: GPL/Commercial | |||
| dependencies: juce_gui_extra, juce_audio_basics | |||
| OSXFrameworks: CoreAudio CoreMIDI AudioToolbox | |||
| iOSFrameworks: AudioToolbox | |||
| END_JUCE_MODULE_DECLARATION | |||
| *******************************************************************************/ | |||
| #ifndef JUCE_AUDIO_PROCESSORS_H_INCLUDED | |||
| #define JUCE_AUDIO_PROCESSORS_H_INCLUDED | |||
| #include "../juce_gui_basics/juce_gui_basics.h" | |||
| #include "../juce_audio_basics/juce_audio_basics.h" | |||
| #include <juce_gui_basics/juce_gui_basics.h> | |||
| #include <juce_audio_basics/juce_audio_basics.h> | |||
| //============================================================================== | |||
| /** Config: JUCE_PLUGINHOST_VST | |||
| Enables the VST audio plugin hosting classes. This requires the Steinberg VST SDK to be | |||
| installed on your machine. | |||
| Enables the VST audio plugin hosting classes. | |||
| @see VSTPluginFormat, VST3PluginFormat, AudioPluginFormat, AudioPluginFormatManager, JUCE_PLUGINHOST_AU, JUCE_PLUGINHOST_VST3 | |||
| */ | |||
| @@ -63,10 +88,14 @@ | |||
| // #error "You need to set either the JUCE_PLUGINHOST_AU and/or JUCE_PLUGINHOST_VST and/or JUCE_PLUGINHOST_VST3 flags if you're using this module!" | |||
| #endif | |||
| #if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT) | |||
| #if ! (defined (JUCE_SUPPORT_CARBON) || JUCE_64BIT || JUCE_IOS) | |||
| #define JUCE_SUPPORT_CARBON 1 | |||
| #endif | |||
| #ifndef JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR | |||
| #define JUCE_SUPPORT_LEGACY_AUDIOPROCESSOR 1 | |||
| #endif | |||
| //============================================================================== | |||
| //============================================================================== | |||
| namespace juce | |||
| @@ -77,13 +106,11 @@ class AudioProcessor; | |||
| #include "processors/juce_AudioProcessorEditor.h" | |||
| #include "processors/juce_AudioProcessorListener.h" | |||
| #include "processors/juce_AudioProcessorParameter.h" | |||
| #include "processors/juce_AudioChannelSet.h" | |||
| #include "processors/juce_AudioProcessor.h" | |||
| #include "processors/juce_PluginDescription.h" | |||
| #include "processors/juce_AudioPluginInstance.h" | |||
| #include "processors/juce_AudioProcessorGraph.h" | |||
| #include "processors/juce_GenericAudioProcessorEditor.h" | |||
| #include "processors/AudioProcessorGraphMultiThreaded.h" | |||
| #include "format/juce_AudioPluginFormat.h" | |||
| #include "format/juce_AudioPluginFormatManager.h" | |||
| #include "scanning/juce_KnownPluginList.h" | |||
| @@ -78,6 +78,9 @@ public: | |||
| protected: | |||
| //============================================================================== | |||
| AudioPluginInstance() {} | |||
| AudioPluginInstance (const BusesProperties& ioLayouts) : AudioProcessor (ioLayouts) {} | |||
| template <int numLayouts> | |||
| AudioPluginInstance (const short channelLayoutList[numLayouts][2]) : AudioProcessor (channelLayoutList) {} | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AudioPluginInstance) | |||
| }; | |||
| @@ -25,6 +25,7 @@ | |||
| #ifndef JUCE_AUDIOPROCESSOR_H_INCLUDED | |||
| #define JUCE_AUDIOPROCESSOR_H_INCLUDED | |||
| struct PluginBusUtilities; | |||
| //============================================================================== | |||
| /** | |||
| @@ -43,10 +44,41 @@ | |||
| class JUCE_API AudioProcessor | |||
| { | |||
| protected: | |||
| struct BusesProperties; | |||
| //============================================================================== | |||
| /** Constructor. */ | |||
| /** Constructor. | |||
| This constructor will create a main input and output bus which are diabled | |||
| by default. If you need more fine grain control then use the other | |||
| constructors. | |||
| */ | |||
| AudioProcessor(); | |||
| /** Constructor for multibus AudioProcessors | |||
| If your AudioProcessor supports multiple buses than use this constructor | |||
| to initialise the bus layouts and bus names of your plug-in. | |||
| */ | |||
| AudioProcessor (const BusesProperties& ioLayouts); | |||
| /** Constructor for AudioProcessors which use layout maps | |||
| If your AudioProcessor uses layout maps then use this constructor. | |||
| */ | |||
| #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||
| AudioProcessor (const std::initializer_list<const short[2]>& channelLayoutList) | |||
| { | |||
| initialise (busesPropertiesFromLayoutArray (layoutListToArray (channelLayoutList))); | |||
| } | |||
| #else | |||
| template <int numLayouts> | |||
| AudioProcessor (const short channelLayoutList[numLayouts][2]) | |||
| { | |||
| initialise (busesPropertiesFromLayoutArray (layoutListToArray (channelLayoutList))); | |||
| } | |||
| #endif | |||
| public: | |||
| //============================================================================== | |||
| enum ProcessingPrecision | |||
| @@ -70,18 +102,20 @@ public: | |||
| playback stops. | |||
| You can call getTotalNumInputChannels and getTotalNumOutputChannels | |||
| or query the busArrangement member variable to find out the number of | |||
| or query the busLayout member variable to find out the number of | |||
| channels your processBlock callback must process. | |||
| The estimatedSamplesPerBlock value is a HINT about the typical number of | |||
| samples that will be processed for each callback, but isn't any kind | |||
| of guarantee. The actual block sizes that the host uses may be different | |||
| each time the callback happens, and may be more or less than this value. | |||
| The maximumExpectedSamplesPerBlock value is a strong hint about the maximum | |||
| number of samples that will be provided in each block. You may want to use | |||
| this value to resize internal buffers. You should program defensively in | |||
| case a buggy host exceeds this value. The actual block sizes that the host | |||
| uses may be different each time the callback happens: completely variable | |||
| block sizes can be expected from some hosts. | |||
| @see busArrangement, getTotalNumInputChannels, getTotalNumOutputChannels | |||
| @see busLayout, getTotalNumInputChannels, getTotalNumOutputChannels | |||
| */ | |||
| virtual void prepareToPlay (double sampleRate, | |||
| int estimatedSamplesPerBlock) = 0; | |||
| int maximumExpectedSamplesPerBlock) = 0; | |||
| /** Called after playback has stopped, to let the filter free up any resources it | |||
| no longer needs. | |||
| @@ -107,7 +141,7 @@ public: | |||
| If your plug-in has more than one input or output buses then the buffer passed | |||
| to the processBlock methods will contain a bundle of all channels of each bus. | |||
| Use AudioBusArrangement::getBusBuffer to obtain an audio buffer for a | |||
| Use AudiobusLayout::getBusBuffer to obtain an audio buffer for a | |||
| particular bus. | |||
| Note that if you have more outputs than inputs, then only those channels that | |||
| @@ -144,7 +178,7 @@ public: | |||
| processBlock() method to send out an asynchronous message. You could also use | |||
| the AsyncUpdater class in a similar way. | |||
| @see AudioBusArrangement::getBusBuffer | |||
| @see AudiobusLayout::getBusBuffer | |||
| */ | |||
| virtual void processBlock (AudioBuffer<float>& buffer, | |||
| @@ -169,7 +203,7 @@ public: | |||
| If your plug-in has more than one input or output buses then the buffer passed | |||
| to the processBlock methods will contain a bundle of all channels of | |||
| each bus. Use AudioBusArrangement::getBusBuffer to obtain a audio buffer | |||
| each bus. Use AudiobusLayout::getBusBuffer to obtain a audio buffer | |||
| for a particular bus. | |||
| Note that if you have more outputs than inputs, then only those channels that | |||
| @@ -182,8 +216,8 @@ public: | |||
| but you should only read/write from the ones that your filter is supposed to | |||
| be using. | |||
| If your plugin uses buses, then you should use AudioBusArrangement::getBusBuffer() | |||
| or AudioBusArrangement::getChannelIndexInProcessBlockBuffer() to find out which | |||
| If your plugin uses buses, then you should use AudiobusLayout::getBusBuffer() | |||
| or AudiobusLayout::getChannelIndexInProcessBlockBuffer() to find out which | |||
| of the input and output channels correspond to which of the buses. | |||
| The number of samples in these buffers is NOT guaranteed to be the same for every | |||
| @@ -210,12 +244,13 @@ public: | |||
| processBlock() method to send out an asynchronous message. You could also use | |||
| the AsyncUpdater class in a similar way. | |||
| @see AudioBusArrangement::getBusBuffer | |||
| @see AudiobusLayout::getBusBuffer | |||
| */ | |||
| virtual void processBlock (AudioBuffer<double>& buffer, | |||
| MidiBuffer& midiMessages); | |||
| /** Renders the next block when the processor is being bypassed. | |||
| The default implementation of this method will pass-through any incoming audio, but | |||
| you may override this method e.g. to add latency compensation to the data to match | |||
| the processor's latency characteristics. This will avoid situations where bypassing | |||
| @@ -227,6 +262,7 @@ public: | |||
| MidiBuffer& midiMessages); | |||
| /** Renders the next block when the processor is being bypassed. | |||
| The default implementation of this method will pass-through any incoming audio, but | |||
| you may override this method e.g. to add latency compensation to the data to match | |||
| the processor's latency characteristics. This will avoid situations where bypassing | |||
| @@ -237,103 +273,372 @@ public: | |||
| virtual void processBlockBypassed (AudioBuffer<double>& buffer, | |||
| MidiBuffer& midiMessages); | |||
| //============================================================================== | |||
| /** Describes the layout and properties of an audio bus. | |||
| Effectively a bus description is a named set of channel types. | |||
| @see AudioChannelSet | |||
| /** | |||
| Represents the bus layout state of a plug-in | |||
| */ | |||
| struct AudioProcessorBus | |||
| struct BusesLayout | |||
| { | |||
| /** Creates a bus from a name and set of channel types. */ | |||
| AudioProcessorBus (const String& busName, const AudioChannelSet& channelTypes); | |||
| /** An array containing the list of input buses that this processor supports. */ | |||
| Array<AudioChannelSet> inputBuses; | |||
| /** The bus's name. */ | |||
| String name; | |||
| /** An array containing the list of output buses that this processor supports. */ | |||
| Array<AudioChannelSet> outputBuses; | |||
| /** Get the number of channels of a particular bus */ | |||
| int getNumChannels (bool isInput, int busIndex) const noexcept | |||
| { | |||
| const Array<AudioChannelSet>& bus = (isInput ? inputBuses : outputBuses); | |||
| return isPositiveAndBelow(busIndex, bus.size()) ? bus.getReference (busIndex).size() : 0; | |||
| } | |||
| /** Get the channel set of a particular bus */ | |||
| AudioChannelSet& getChannelSet (bool isInput, int busIndex) | |||
| { | |||
| Array<AudioChannelSet>& sets = isInput ? inputBuses : outputBuses; | |||
| jassert (isPositiveAndBelow (busIndex, sets.size())); | |||
| return sets.getReference (busIndex); | |||
| } | |||
| /** The set of channel types that the bus contains. */ | |||
| AudioChannelSet channels; | |||
| /** Get the channel set of a particular bus */ | |||
| AudioChannelSet getChannelSet (bool isInput, int busIndex) const noexcept | |||
| { | |||
| const Array<AudioChannelSet>& sets = isInput ? inputBuses : outputBuses; | |||
| if (isPositiveAndBelow (busIndex, sets.size())) | |||
| return sets.getReference (busIndex); | |||
| else | |||
| return AudioChannelSet(); | |||
| } | |||
| /** Get the input channel layout on the main bus. */ | |||
| AudioChannelSet getMainInputChannelSet() const noexcept { return getChannelSet (true, 0); } | |||
| /** Get the output channel layout on the main bus. */ | |||
| AudioChannelSet getMainOutputChannelSet() const noexcept { return getChannelSet (false, 0); } | |||
| /** Get the number of input channels on the main bus. */ | |||
| int getMainInputChannels() const noexcept { return getNumChannels (true, 0); } | |||
| /** Get the number of output channels on the main bus. */ | |||
| int getMainOutputChannels() const noexcept { return getNumChannels (false, 0); } | |||
| bool operator== (const BusesLayout& other) const noexcept { return inputBuses == other.inputBuses && outputBuses == other.outputBuses; } | |||
| bool operator!= (const BusesLayout& other) const noexcept { return inputBuses != other.inputBuses || outputBuses != other.outputBuses; } | |||
| }; | |||
| //============================================================================== | |||
| /** | |||
| Represents a set of input and output buses for an AudioProcessor. | |||
| */ | |||
| struct AudioBusArrangement | |||
| Describes the layout and properties of an audio bus. | |||
| Effectively a bus description is a named set of channel types. | |||
| @see AudioChannelSet, AudioProcessor::addBus | |||
| */ | |||
| class Bus | |||
| { | |||
| /** An array containing the list of input buses that this processor supports. */ | |||
| Array<AudioProcessorBus> inputBuses; | |||
| public: | |||
| /** Returns true if this bus is an input bus. */ | |||
| bool isInput() const; | |||
| /** An array containing the list of output buses that this processor supports. */ | |||
| Array<AudioProcessorBus> outputBuses; | |||
| /** Returns the index of this bus. */ | |||
| int getBusIndex() const; | |||
| /** Returns true if the current bus is the main input or output bus. */ | |||
| bool isMain() const { return getBusIndex() == 0; } | |||
| //============================================================================== | |||
| /** The bus's name. */ | |||
| const String &getName() const noexcept { return name; } | |||
| /** Get the default layout of this bus. | |||
| @see AudioChannelSet | |||
| */ | |||
| const AudioChannelSet& getDefaultLayout() const noexcept { return dfltLayout; } | |||
| //============================================================================== | |||
| /** The bus's current layout. This will be AudioChannelSet::disabled() if the current | |||
| layout is dfisabled. | |||
| @see AudioChannelSet | |||
| */ | |||
| const AudioChannelSet& getCurrentLayout() const noexcept { return layout; } | |||
| /** Return the bus's last active channel layout. | |||
| If the bus is currently enabled then the result will be identical to getCurrentLayout | |||
| otherwise it will return the last enabled layout. | |||
| @see AudioChannelSet | |||
| */ | |||
| const AudioChannelSet& getLastEnabledLayout() const noexcept { return lastLayout; } | |||
| /** Sets the bus's current layout. | |||
| If the AudioProcessor does not support this layout then this will return false. | |||
| @see AudioChannelSet | |||
| */ | |||
| bool setCurrentLayout (const AudioChannelSet& layout); | |||
| /** Sets the bus's current layout without changing the enabled state. | |||
| If the AudioProcessor does not support this layout then this will return false. | |||
| @see AudioChannelSet | |||
| */ | |||
| bool setCurrentLayoutWithoutEnabling (const AudioChannelSet& layout); | |||
| /** Return the number of channels of the current bus. */ | |||
| inline int getNumberOfChannels() const noexcept { return cachedChannelCount; } | |||
| /** Set the number of channles of this bus. This will return false if the AudioProcessor | |||
| does not support this layout. */ | |||
| bool setNumberOfChannels (int channels); | |||
| //============================================================================== | |||
| /** Checks if a particular layout is supported. | |||
| @param set The AudioChannelSet which is to be probed. | |||
| @see AudioChannelSet | |||
| */ | |||
| bool isLayoutSupported (const AudioChannelSet& set) const; | |||
| /** Checks if this bus can support a given number of channels. */ | |||
| bool isNumberOfChannelsSupported (int channels) const; | |||
| /** Returns a ChannelSet that the bus supports with a given number of channels. */ | |||
| AudioChannelSet supportedLayoutWithChannels (int channels) const; | |||
| /** Returns the maximum number of channels that this bus can support. | |||
| @param limit The maximum value to return. | |||
| */ | |||
| int getMaxSupportedChannels (int limit = AudioChannelSet::maxChannelsOfNamedLayout) const; | |||
| /** Returns the resulting layouts of all buses after changing the layout of this bus. | |||
| Changing an individual layout of a bus may also change the layout of all the other | |||
| buses. This method returns what the layouts of all the buses of the audio processor | |||
| would be, if you were to change the layout of this bus to the given layout. If there | |||
| is no way to support the given layout then this method will return the next best | |||
| layout. | |||
| */ | |||
| BusesLayout getBusesLayoutForLayoutChangeOfBus (const AudioChannelSet& set) const; | |||
| //============================================================================== | |||
| /** Returns true if the current bus is enabled. */ | |||
| bool isEnabled() const noexcept { return ! layout.isDisabled(); } | |||
| /** Enable or disable this bus. This will return false if the AudioProcessor | |||
| does not support disabling this bus. */ | |||
| bool enable (bool shouldEnable = true); | |||
| /** Returns if this bus is enabled by default. */ | |||
| bool isEnabledByDefault() const noexcept { return enabledByDefault; } | |||
| //============================================================================== | |||
| /** Returns the position of a bus's channels within the processBlock buffer. | |||
| This can be called in processBlock to figure out which channel of the master AudioSampleBuffer | |||
| maps onto a specific bus's channel. | |||
| */ | |||
| int getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept; | |||
| */ | |||
| int getChannelIndexInProcessBlockBuffer (int channelIndex) const noexcept; | |||
| /** Returns an AudioBuffer containing a set of channel pointers for a specific bus. | |||
| This can be called in processBlock to get a buffer containing a sub-group of the master | |||
| AudioSampleBuffer which contains all the plugin channels. | |||
| */ | |||
| */ | |||
| template <typename FloatType> | |||
| AudioBuffer<FloatType> getBusBuffer (AudioBuffer<FloatType>& processBlockBuffer, bool isInput, int busIndex) const | |||
| AudioBuffer<FloatType> getBusBuffer (AudioBuffer<FloatType>& processBlockBuffer) const | |||
| { | |||
| const int busNumChannels = (isInput ? inputBuses : outputBuses).getReference (busIndex).channels.size(); | |||
| const int channelOffset = getChannelIndexInProcessBlockBuffer (isInput, busIndex, 0); | |||
| return AudioBuffer<FloatType> (processBlockBuffer.getArrayOfWritePointers() + channelOffset, | |||
| busNumChannels, processBlockBuffer.getNumSamples()); | |||
| bool isIn; | |||
| int busIdx; | |||
| busDirAndIndex (isIn, busIdx); | |||
| return owner.getBusBuffer (processBlockBuffer, isIn, busIdx); | |||
| } | |||
| private: | |||
| friend class AudioProcessor; | |||
| Bus (AudioProcessor&, const String&, const AudioChannelSet&, bool); | |||
| void busDirAndIndex (bool&, int&) const noexcept; | |||
| void updateChannelCount() noexcept; | |||
| /** Returns the total number of channels in all the input buses. */ | |||
| int getTotalNumInputChannels() const noexcept; | |||
| AudioProcessor& owner; | |||
| String name; | |||
| AudioChannelSet layout, dfltLayout, lastLayout; | |||
| bool enabledByDefault; | |||
| int cachedChannelCount; | |||
| /** Returns the total number of channels in all the output buses. */ | |||
| int getTotalNumOutputChannels() const noexcept; | |||
| JUCE_DECLARE_NON_COPYABLE (Bus); | |||
| }; | |||
| /** The processor's bus arrangement. | |||
| //============================================================================== | |||
| /** Returns the number of buses on the input or output side */ | |||
| int getBusCount (bool isInput) const noexcept { return (isInput ? inputBuses : outputBuses).size(); } | |||
| /** Returns the audio bus with a given index and direction. | |||
| If busIdx is invalid then this method will return a nullptr. | |||
| */ | |||
| Bus* getBus (bool isInput, int busIdx) noexcept { return (isInput ? inputBuses : outputBuses)[busIdx]; } | |||
| /** Returns the audio bus with a given index and direction. | |||
| If busIdx is invalid then this method will return a nullptr. | |||
| */ | |||
| const Bus* getBus (bool isInput, int busIdx) const noexcept { return const_cast<AudioProcessor*> (this)->getBus (isInput, busIdx); } | |||
| //============================================================================== | |||
| /** Callback to query if a bus can currently be added. | |||
| This callback probes if a bus can currently be added. You should override | |||
| this callback if you want to support dynamically adding/removing buses by | |||
| the host. This is useful for mixer audio processors. | |||
| The default implementation will always return false. | |||
| @see addBus | |||
| */ | |||
| virtual bool canAddBus (bool /*inputBus*/) const { return false; } | |||
| /** Callback to query if the last bus can currently be removed. | |||
| This callback probes if the last bus can currently be removed. You should | |||
| override this callback if you want to support dynamically adding/removing | |||
| buses by the host. This is useful for mixer audio processors. | |||
| If you return true in this callback then the AudioProcessor will go ahead | |||
| and delete the bus. | |||
| The default implementation will always return false. | |||
| */ | |||
| virtual bool canRemoveBus (bool /*inputBus*/) const { return false; } | |||
| /** Dynamically request an additional bus. | |||
| Request an additional bus from the audio processor. If the audio processor | |||
| does not support adding additional buses then this method will return false. | |||
| Most audio processors will not allow you to dynamically add/remove | |||
| audio buses and will return false. | |||
| This method will invoke the canApplyBusCountChange callback to probe | |||
| if a bus can be added and, if yes, will use the supplied bus properties | |||
| of the canApplyBusCountChange callback to create a new bus. | |||
| @see canApplyBusCountChange, removeBus | |||
| */ | |||
| bool addBus (bool isInput); | |||
| /** Dynamically remove the latest added bus. | |||
| Request the removal of the last bus from the audio processor. If the | |||
| audio processor does not support removing buses then this method will | |||
| return false. | |||
| Most audio processors will not allow you to dynamically add/remove | |||
| audio buses and will return false. | |||
| The default implementation will return false. | |||
| Your plugin can modify this either | |||
| - in the plugin's constructor | |||
| - in the setPreferredBusArrangement() callback | |||
| Changing it at other times can result in undefined behaviour. | |||
| This method will invoke the canApplyBusCountChange callback to probe if | |||
| a bus can currently be removed and, if yes, will go ahead and remove it. | |||
| The host will negotiate with the plugin over its bus configuration by making calls | |||
| to setPreferredBusArrangement(). | |||
| @see addBus, canRemoveBus | |||
| */ | |||
| bool removeBus (bool isInput); | |||
| //============================================================================== | |||
| /** Set the channel layouts of this audio processor. | |||
| @see setPreferredBusArrangement | |||
| If the layout is not supported by this audio processor then | |||
| this method will return false. You can use the checkBusesLayoutSupported | |||
| and getNextBestLayout methods to probe which layouts this audio | |||
| processor supports. | |||
| */ | |||
| bool setBusesLayout (const BusesLayout& arr); | |||
| /** Set the channel layouts of this audio processor without changing the | |||
| enablement state of the buses. | |||
| If the layout is not supported by this audio processor then | |||
| this method will return false. You can use the checkBusesLayoutSupported | |||
| and getNextBestLayout methods to probe which layouts this audio | |||
| processor supports. | |||
| */ | |||
| AudioBusArrangement busArrangement; | |||
| bool setBusesLayoutWithoutEnabling (const BusesLayout& arr); | |||
| /** Provides the current channel layouts of this audio processor. */ | |||
| BusesLayout getBusesLayout() const; | |||
| /** Provides the channel layout of the bus with a given index and direction. | |||
| If the index, direction combination is invalid then this will return an | |||
| AudioChannelSet with no channels. | |||
| */ | |||
| AudioChannelSet getChannelLayoutOfBus (bool isInput, int busIdx) const noexcept; | |||
| /** Set the channel layout of the bus with a given index and direction. | |||
| If the index, direction combination is invalid or the layout is not | |||
| supported by the audio processor then this method will return false. | |||
| */ | |||
| bool setChannelLayoutOfBus (bool isInput, int busIdx, const AudioChannelSet& layout); | |||
| /** Provides the number of channels of the bus with a given index and direction. | |||
| If the index, direction combination is invalid then this will return zero. | |||
| */ | |||
| inline int getChannelCountOfBus (bool isInput, int busIdx) const noexcept | |||
| { | |||
| if (const Bus* bus = getBus (isInput, busIdx)) | |||
| return bus->getNumberOfChannels(); | |||
| return 0; | |||
| } | |||
| /** Enables all buses */ | |||
| bool enableAllBuses(); | |||
| /** Disables all non-main buses (aux and sidechains). */ | |||
| bool disableNonMainBuses(); | |||
| //============================================================================== | |||
| /** Called by the host, this attempts to change the plugin's channel layout on a particular bus. | |||
| The base class implementation will perform some basic sanity-checking and then apply the | |||
| changes to the processor's busArrangement value. | |||
| You may override it and return false if you want to make your plugin smarter about refusing | |||
| certain layouts that you don't want to support. Your plug-in may also respond to this call by | |||
| changing the channel layout of other buses, for example, if your plug-in requires the same | |||
| number of input and output channels. | |||
| /** Returns the position of a bus's channels within the processBlock buffer. | |||
| This can be called in processBlock to figure out which channel of the master AudioSampleBuffer | |||
| maps onto a specific bus's channel. | |||
| */ | |||
| int getChannelIndexInProcessBlockBuffer (bool isInput, int busIndex, int channelIndex) const noexcept; | |||
| For most basic plug-ins, which do not require side-chains, aux buses or detailed audio | |||
| channel layout information, it is easier to specify the acceptable channel configurations | |||
| via the "PlugIn Channel Configurations" field in the Introjucer. In this case, you should | |||
| not override this method. | |||
| /** Returns the offset in a bus's buffer from an absolute channel indes. | |||
| If, on the other hand, you decide to override this method then you need to make sure that | |||
| "PlugIn Channel Configurations" field in the Introjucer is empty. | |||
| This method returns the offset in a bus's buffer given an absolute channel index. | |||
| It also provides the bus index. For example, this method would return one | |||
| for a processor with two stereo buses when given the absolute channel index. | |||
| */ | |||
| int getOffsetInBusBufferForAbsoluteChannelIndex (bool isInput, int absoluteChannelIndex, /*out*/ int& busIdx) const noexcept; | |||
| Note, that you must not do any heavy allocations or calculations in this callback as it may | |||
| be called several hundred times during initialization. If you require any layout specific | |||
| allocations then defer these to prepareToPlay callback. | |||
| /** Returns an AudioBuffer containing a set of channel pointers for a specific bus. | |||
| This can be called in processBlock to get a buffer containing a sub-group of the master | |||
| AudioSampleBuffer which contains all the plugin channels. | |||
| */ | |||
| template <typename FloatType> | |||
| AudioBuffer<FloatType> getBusBuffer (AudioBuffer<FloatType>& processBlockBuffer, bool isInput, int busIndex) const | |||
| { | |||
| const int busNumChannels = getChannelCountOfBus (isInput, busIndex); | |||
| const int channelOffset = getChannelIndexInProcessBlockBuffer (isInput, busIndex, 0); | |||
| @returns false if there is no way for the processor to support the given format on the specified bus. | |||
| return AudioBuffer<FloatType> (processBlockBuffer.getArrayOfWritePointers() + channelOffset, | |||
| busNumChannels, processBlockBuffer.getNumSamples()); | |||
| } | |||
| @see prepareToPlay, busArrangement, AudioBusArrangement::getBusBuffer, getTotalNumInputChannels, getTotalNumOutputChannels | |||
| //============================================================================== | |||
| /** Returns true if the Audio processor is likely to support a given layout. | |||
| This can be called regardless if the processor is currently running. | |||
| */ | |||
| virtual bool setPreferredBusArrangement (bool isInputBus, int busIndex, const AudioChannelSet& preferredSet); | |||
| bool checkBusesLayoutSupported (const BusesLayout& layouts) const; | |||
| //============================================================================== | |||
| /** Returns true if the Audio processor supports double precision floating point processing. | |||
| @@ -403,7 +708,7 @@ public: | |||
| getMainBusNumInputChannels if your processor does not have any sidechains | |||
| or aux buses. | |||
| */ | |||
| int getTotalNumInputChannels() const noexcept { return busArrangement.getTotalNumInputChannels(); } | |||
| int getTotalNumInputChannels() const noexcept { return cachedTotalIns; } | |||
| /** Returns the total number of output channels. | |||
| @@ -417,13 +722,56 @@ public: | |||
| getMainBusNumOutputChannels if your processor does not have any sidechains | |||
| or aux buses. | |||
| */ | |||
| int getTotalNumOutputChannels() const noexcept { return busArrangement.getTotalNumOutputChannels(); } | |||
| int getTotalNumOutputChannels() const noexcept { return cachedTotalOuts; } | |||
| /** Returns the number of input channels on the main bus. */ | |||
| int getMainBusNumInputChannels() const noexcept; | |||
| inline int getMainBusNumInputChannels() const noexcept { return getChannelCountOfBus (true, 0); } | |||
| /** Returns the number of output channels on the main bus. */ | |||
| int getMainBusNumOutputChannels() const noexcept; | |||
| inline int getMainBusNumOutputChannels() const noexcept { return getChannelCountOfBus (false, 0); } | |||
| //============================================================================== | |||
| /** Returns true if the channel layout map contains a certain layout. | |||
| You can use this method to help you implement the checkBusesLayoutSupported | |||
| method. For example | |||
| @code | |||
| bool checkBusesLayoutSupported (const BusesLayout& layouts) override | |||
| { | |||
| return containsLayout (layouts, {{1,1},{2,2}}); | |||
| } | |||
| @endcode | |||
| */ | |||
| #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||
| static bool containsLayout (const BusesLayout& layouts, const std::initializer_list<const short[2]>& channelLayoutList) | |||
| { | |||
| return containsLayout (layouts, layoutListToArray (channelLayoutList)); | |||
| } | |||
| #endif | |||
| template <int numLayouts> | |||
| static bool containsLayout (const BusesLayout& layouts, const short (&channelLayoutList) [numLayouts][2]) | |||
| { | |||
| return containsLayout (layouts, layoutListToArray (channelLayoutList)); | |||
| } | |||
| /** Returns the next best layout which is contained in a channel layout map. | |||
| You can use this mehtod to help you implement getNextBestLayout. For example: | |||
| @code | |||
| BusesLayout getNextBestLayout (const BusesLayout& layouts) override | |||
| { | |||
| return getNextBestLayoutInLayoutList (layouts, {{1,1},{2,2}}); | |||
| } | |||
| @endcode | |||
| */ | |||
| template <int numLayouts> | |||
| BusesLayout getNextBestLayoutInLayoutList (const BusesLayout& layouts, | |||
| const short (&channelLayoutList) [numLayouts][2]) | |||
| { | |||
| return getNextBestLayoutInList (layouts, layoutListToArray (channelLayoutList)); | |||
| } | |||
| //============================================================================== | |||
| /** Returns the current sample rate. | |||
| @@ -473,6 +821,9 @@ public: | |||
| /** Returns true if the processor supports MPE. */ | |||
| virtual bool supportsMPE() const { return false; } | |||
| /** Returns true if this is a midi effect plug-in and does no audio processing. */ | |||
| virtual bool isMidiEffect() const { return false; } | |||
| //============================================================================== | |||
| /** This returns a critical section that will automatically be locked while the host | |||
| is calling the processBlock() method. | |||
| @@ -611,6 +962,17 @@ public: | |||
| */ | |||
| virtual const String getParameterName (int parameterIndex); | |||
| /** Returns the ID of a particular parameter. | |||
| The ID is used to communicate the value or mapping of a particular parameter with | |||
| the host. By default this method will simply return a string representation of | |||
| index. | |||
| NOTE! This method will eventually be deprecated! It's recommended that you use the | |||
| AudioProcessorParameterWithID class instead to manage your parameters. | |||
| */ | |||
| virtual String getParameterID (int index); | |||
| /** Called by the host to find out the value of one of the filter's parameters. | |||
| The host will expect the value returned to be between 0 and 1.0. | |||
| @@ -628,7 +990,7 @@ public: | |||
| If you want to provide customised short versions of your parameter names that | |||
| will look better in constrained spaces (e.g. the displays on hardware controller | |||
| devices or mixing desks) then you should implement this method. | |||
| If you don't override it, the default implementation will call getParameterText(int), | |||
| If you don't override it, the default implementation will call getParameterName(int), | |||
| and truncate the result. | |||
| NOTE! This method will eventually be deprecated! It's recommended that you use | |||
| @@ -864,9 +1226,15 @@ public: | |||
| */ | |||
| virtual void setCurrentProgramStateInformation (const void* data, int sizeInBytes); | |||
| /** This method is called when the number of input or output channels is changed. */ | |||
| /** This method is called when the total number of input or output channels is changed. */ | |||
| virtual void numChannelsChanged(); | |||
| /** This method is called when the number of buses is changed. */ | |||
| virtual void numBusesChanged(); | |||
| /** This method is called when the layout of the audio processor changes. */ | |||
| virtual void processorLayoutsChanged(); | |||
| //============================================================================== | |||
| /** LV2 specific calls, saving/restore as string. */ | |||
| virtual String getStateInformationString () { return String::empty; } | |||
| @@ -894,9 +1262,9 @@ public: | |||
| /** This is called by the processor to specify its details before being played. You | |||
| should call this function after having informed the processor about the channel | |||
| and bus layouts via setPreferredBusArrangement. | |||
| and bus layouts via setBusesLayout. | |||
| @see setPreferredBusArrangement | |||
| @see setBusesLayout | |||
| */ | |||
| void setRateAndBufferSizeDetails (double sampleRate, int blockSize) noexcept; | |||
| @@ -913,6 +1281,7 @@ public: | |||
| wrapperType_VST, | |||
| wrapperType_VST3, | |||
| wrapperType_AudioUnit, | |||
| wrapperType_AudioUnitv3, | |||
| wrapperType_RTAS, | |||
| wrapperType_AAX, | |||
| wrapperType_Standalone | |||
| @@ -939,7 +1308,7 @@ public: | |||
| /** Returns the name of one of the processor's input channels. | |||
| These functions are deprecated: your audio processor can inform the host | |||
| on channel layouts and names via the methods in the AudioBusArrangement class. | |||
| on channel layouts and names via the methods in the AudiobusLayout class. | |||
| */ | |||
| JUCE_DEPRECATED (virtual const String getInputChannelName (int channelIndex) const); | |||
| JUCE_DEPRECATED (virtual const String getOutputChannelName (int channelIndex) const); | |||
| @@ -947,7 +1316,7 @@ public: | |||
| /** Returns true if the specified channel is part of a stereo pair with its neighbour. | |||
| These functions are deprecated: your audio processor should specify the audio | |||
| channel pairing information by modifying the busArrangement member variable in | |||
| channel pairing information by modifying the busLayout member variable in | |||
| the constructor. */ | |||
| JUCE_DEPRECATED (virtual bool isInputChannelStereoPair (int index) const); | |||
| JUCE_DEPRECATED (virtual bool isOutputChannelStereoPair (int index) const); | |||
| @@ -976,6 +1345,110 @@ public: | |||
| static void JUCE_CALLTYPE setTypeOfNextNewPlugin (WrapperType); | |||
| protected: | |||
| /** Callback to query if the AudioProcessor supports a specific layout. | |||
| This callback is called when the host probes the supported bus layouts via | |||
| the checkBusesLayoutSupported method. You should override this callback if you | |||
| would like to limit the layouts that your AudioProcessor supports. The default | |||
| implementation will accept any layout. JUCE does basic sanity checks so that | |||
| the provided layouts parameter will have the same number of buses as your | |||
| AudioProcessor. | |||
| @see checkBusesLayoutSupported | |||
| */ | |||
| virtual bool isBusesLayoutSupported (const BusesLayout& /*layouts*/) const { return true; } | |||
| /** Callback to check if a certain bus layout can now be applied | |||
| Most subclasses will not need to override this method and should instead | |||
| override the isBusesLayoutSupported callback to reject certain layout changes. | |||
| This callback is called when the user requests a layout change. It will only be | |||
| called if processing of the AudioProcessor has been stopped by a previous call to | |||
| releaseResources or after the construction of the AudioProcessor. It will be called | |||
| just before the actual layout change. By returning false you will abort the layout | |||
| change and setBusesLayout will return false indicating that the layout change | |||
| was not successful. | |||
| The default implementation will simply call isBusesLayoutSupported. | |||
| You only need to override this method if there is a chance that your AudioProcessor | |||
| may not accept a layout although you have previously claimed to support it via the | |||
| isBusesLayoutSupported callback. This can occur if your AudioProcessor's supported | |||
| layouts depend on other plug-in parameters which may have changed since the last | |||
| call to isBusesLayoutSupported, such as the format of an audio file which can be | |||
| selected by the user in the AudioProcessor's editor. This callback gives the | |||
| AudioProcessor a last chance to reject a layout if conditions have changed as it | |||
| is always called just before the actual layout change. | |||
| As it is never called while the AudioProcessor is processing audio, it can also | |||
| be used for AudioProcessors which wrap other plug-in formats to apply the current | |||
| layout to the underlying plug-in. This callback gives such AudioProcessors a | |||
| chance to reject the layout change should an error occur with the underlying plug-in | |||
| during the layout change. | |||
| @see isBusesLayoutSupported, setBusesLayout | |||
| */ | |||
| virtual bool canApplyBusesLayout (const BusesLayout& layouts) const { return isBusesLayoutSupported (layouts); } | |||
| //============================================================================== | |||
| /** Structure used for AudioProcessor Callbacks */ | |||
| struct BusProperties | |||
| { | |||
| /** The name of the bus */ | |||
| String busName; | |||
| /** The default layout of the bus */ | |||
| AudioChannelSet defaultLayout; | |||
| /** Is this bus activated by default? */ | |||
| bool isActivatedByDefault; | |||
| }; | |||
| struct BusesProperties | |||
| { | |||
| /** The layouts of the input buses */ | |||
| Array<BusProperties> inputLayouts; | |||
| /** The layouts of the output buses */ | |||
| Array<BusProperties> outputLayouts; | |||
| void addBus (bool isInput, const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true); | |||
| BusesProperties withInput (const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true) const; | |||
| BusesProperties withOutput (const String& name, const AudioChannelSet& dfltLayout, bool isActivatedByDefault = true) const; | |||
| }; | |||
| /** Callback to query if adding/removing buses currently possible. | |||
| This callback is called when the host calls addBus or removeBus. | |||
| Similar to canApplyBusesLayout, this callback is only called while | |||
| the AudioProcessor is stopped and gives the processor a last | |||
| chance to reject a requested bus change. It can also be used to apply | |||
| the bus count change to an underlying wrapped plug-in. | |||
| When adding a bus, isAddingBuses will be true and the plug-in is | |||
| expected to fill out outNewBusProperties with the properties of the | |||
| bus which will be created just after the succesful return of this callback. | |||
| Implementations of AudioProcessor will rarely need to override this | |||
| method. Only override this method if your processor supports adding | |||
| and removing buses and if it needs more fine grain control over the | |||
| naming of new buses or may reject bus number changes although canAddBus | |||
| or canRemoveBus returned true. | |||
| The default implementation will return false if canAddBus/canRemoveBus | |||
| returns false (the default behavior). Otherwise, this method returns | |||
| "Input #busIdx" for input buses and "Output #busIdx" for output buses | |||
| where busIdx is the index for newly created buses. The default layout | |||
| in this case will be the layout of the previous bus of the same direction. | |||
| */ | |||
| virtual bool canApplyBusCountChange (bool isInput, bool isAddingBuses, | |||
| BusProperties& outNewBusProperties); | |||
| //============================================================================== | |||
| friend struct PluginBusUtilities; | |||
| /** @internal */ | |||
| AudioPlayHead* playHead; | |||
| @@ -983,6 +1456,67 @@ protected: | |||
| void sendParamChangeMessageToListeners (int parameterIndex, float newValue); | |||
| private: | |||
| //============================================================================== | |||
| struct InOutChannelPair | |||
| { | |||
| int16 inChannels, outChannels; | |||
| InOutChannelPair() noexcept : inChannels (0), outChannels (0) {} | |||
| InOutChannelPair (short inCh, short outCh) noexcept : inChannels (inCh), outChannels (outCh) {} | |||
| InOutChannelPair (const InOutChannelPair& o) noexcept : inChannels (o.inChannels), outChannels (o.outChannels) {} | |||
| InOutChannelPair (const short (&config)[2]) noexcept : inChannels (config[0]), outChannels (config[1]) {} | |||
| InOutChannelPair& operator= (const InOutChannelPair& o) { inChannels = o.inChannels; outChannels = o.outChannels; return *this; } | |||
| bool operator== (const InOutChannelPair& other) const noexcept | |||
| { | |||
| return (other.inChannels == inChannels && other.outChannels == outChannels); | |||
| } | |||
| }; | |||
| template <int numLayouts> | |||
| static Array<InOutChannelPair> layoutListToArray (const short (&configuration) [numLayouts][2]) | |||
| { | |||
| Array<InOutChannelPair> layouts; | |||
| for (int i = 0; i < numLayouts; ++i) | |||
| { | |||
| InOutChannelPair pair (configuration [i]); | |||
| layouts.add (pair); | |||
| } | |||
| return layouts; | |||
| } | |||
| #if JUCE_COMPILER_SUPPORTS_INITIALIZER_LISTS | |||
| static Array<InOutChannelPair> layoutListToArray (const std::initializer_list<const short[2]>& configuration) | |||
| { | |||
| Array<InOutChannelPair> layouts; | |||
| for (std::initializer_list<const short[2]>::const_iterator it = configuration.begin(); | |||
| it != configuration.end(); ++it) | |||
| { | |||
| InOutChannelPair pair (*it); | |||
| layouts.add (pair); | |||
| } | |||
| return layouts; | |||
| } | |||
| #endif | |||
| //============================================================================== | |||
| static BusesProperties busesPropertiesFromLayoutArray (const Array<InOutChannelPair>&); | |||
| //============================================================================== | |||
| BusesLayout getNextBestLayoutInList (const BusesLayout& layouts, | |||
| const Array<InOutChannelPair>& channelLayouts) const; | |||
| static bool containsLayout (const BusesLayout& layouts, const Array<InOutChannelPair>& channelLayouts); | |||
| //============================================================================== | |||
| void initialise (const BusesProperties& ioLayouts); | |||
| void createBus (bool inputBus, const BusProperties&); | |||
| //============================================================================== | |||
| Array<AudioProcessorListener*> listeners; | |||
| #if ! JUCE_AUDIO_PROCESSOR_NO_GUI | |||
| Component::SafePointer<AudioProcessorEditor> activeEditor; | |||
| @@ -996,9 +1530,15 @@ private: | |||
| ProcessingPrecision processingPrecision; | |||
| CriticalSection callbackLock, listenerLock; | |||
| friend class Bus; | |||
| mutable OwnedArray<Bus> inputBuses; | |||
| mutable OwnedArray<Bus> outputBuses; | |||
| String cachedInputSpeakerArrString; | |||
| String cachedOutputSpeakerArrString; | |||
| int cachedTotalIns, cachedTotalOuts; | |||
| OwnedArray<AudioProcessorParameter> managedParameters; | |||
| AudioProcessorParameter* getParamChecked (int) const noexcept; | |||
| @@ -1007,8 +1547,13 @@ private: | |||
| #endif | |||
| AudioProcessorListener* getListenerLocked (int) const noexcept; | |||
| void disableNonMainBuses (bool isInput); | |||
| void updateSpeakerFormatStrings(); | |||
| bool applyBusLayouts (const BusesLayout& arr); | |||
| void audioIOChanged (bool busNumberChanged, bool channelNumChanged); | |||
| BusesLayout getNextBestLayout (const BusesLayout& layouts) const; | |||
| template <typename floatType> | |||
| void processBypassed (AudioBuffer<floatType>&, MidiBuffer&); | |||
| // This method is no longer used - you can delete it from your AudioProcessor classes. | |||
| JUCE_DEPRECATED_WITH_BODY (virtual bool silenceInProducesSilenceOut() const, { return false; }); | |||
| @@ -22,14 +22,16 @@ | |||
| ============================================================================== | |||
| */ | |||
| AudioProcessorEditor::AudioProcessorEditor (AudioProcessor& p) noexcept : processor (p) | |||
| AudioProcessorEditor::AudioProcessorEditor (AudioProcessor& p) noexcept : processor (p), constrainer (nullptr) | |||
| { | |||
| initialise(); | |||
| } | |||
| AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* p) noexcept : processor (*p) | |||
| AudioProcessorEditor::AudioProcessorEditor (AudioProcessor* p) noexcept : processor (*p), constrainer (nullptr) | |||
| { | |||
| // the filter must be valid.. | |||
| jassert (p != nullptr); | |||
| initialise(); | |||
| } | |||
| AudioProcessorEditor::~AudioProcessorEditor() | |||
| @@ -37,7 +39,132 @@ AudioProcessorEditor::~AudioProcessorEditor() | |||
| // if this fails, then the wrapper hasn't called editorBeingDeleted() on the | |||
| // filter for some reason.. | |||
| jassert (processor.getActiveEditor() != this); | |||
| removeComponentListener (resizeListener); | |||
| } | |||
| void AudioProcessorEditor::setControlHighlight (ParameterControlHighlightInfo) {} | |||
| int AudioProcessorEditor::getControlParameterIndex (Component&) { return -1; } | |||
| void AudioProcessorEditor::initialise() | |||
| { | |||
| resizable = false; | |||
| attachConstrainer (&defaultConstrainer); | |||
| addComponentListener (resizeListener = new AudioProcessorEditorListener (this)); | |||
| } | |||
| //============================================================================== | |||
| void AudioProcessorEditor::setResizable (const bool shouldBeResizable, const bool useBottomRightCornerResizer) | |||
| { | |||
| if (shouldBeResizable != resizable) | |||
| { | |||
| resizable = shouldBeResizable; | |||
| if (! resizable) | |||
| { | |||
| setConstrainer (&defaultConstrainer); | |||
| if (getWidth() > 0 && getHeight() > 0) | |||
| { | |||
| defaultConstrainer.setSizeLimits (getWidth(), getHeight(), getWidth(), getHeight()); | |||
| resized(); | |||
| } | |||
| } | |||
| } | |||
| const bool shouldHaveCornerResizer = (useBottomRightCornerResizer && shouldBeResizable); | |||
| if (shouldHaveCornerResizer != (resizableCorner != nullptr)) | |||
| { | |||
| if (shouldHaveCornerResizer) | |||
| { | |||
| Component::addChildComponent (resizableCorner = new ResizableCornerComponent (this, constrainer)); | |||
| resizableCorner->setAlwaysOnTop (true); | |||
| } | |||
| else | |||
| resizableCorner = nullptr; | |||
| } | |||
| } | |||
| void AudioProcessorEditor::setResizeLimits (const int newMinimumWidth, | |||
| const int newMinimumHeight, | |||
| const int newMaximumWidth, | |||
| const int newMaximumHeight) noexcept | |||
| { | |||
| // if you've set up a custom constrainer then these settings won't have any effect.. | |||
| jassert (constrainer == &defaultConstrainer || constrainer == nullptr); | |||
| const bool shouldEnableResize = (newMinimumWidth != newMaximumWidth || newMinimumHeight != newMaximumHeight); | |||
| const bool shouldHaveCornerResizer = (shouldEnableResize != resizable || resizableCorner != nullptr); | |||
| setResizable (shouldEnableResize, shouldHaveCornerResizer); | |||
| if (constrainer == nullptr) | |||
| setConstrainer (&defaultConstrainer); | |||
| defaultConstrainer.setSizeLimits (newMinimumWidth, newMinimumHeight, | |||
| newMaximumWidth, newMaximumHeight); | |||
| setBoundsConstrained (getBounds()); | |||
| } | |||
| void AudioProcessorEditor::setConstrainer (ComponentBoundsConstrainer* newConstrainer) | |||
| { | |||
| if (constrainer != newConstrainer) | |||
| { | |||
| resizable = true; | |||
| attachConstrainer (newConstrainer); | |||
| } | |||
| } | |||
| void AudioProcessorEditor::attachConstrainer (ComponentBoundsConstrainer* newConstrainer) | |||
| { | |||
| if (constrainer != newConstrainer) | |||
| { | |||
| constrainer = newConstrainer; | |||
| updatePeer(); | |||
| } | |||
| } | |||
| void AudioProcessorEditor::setBoundsConstrained (Rectangle<int> newBounds) | |||
| { | |||
| if (constrainer != nullptr) | |||
| constrainer->setBoundsForComponent (this, newBounds, false, false, false, false); | |||
| else | |||
| setBounds (newBounds); | |||
| } | |||
| void AudioProcessorEditor::editorResized (bool wasResized) | |||
| { | |||
| if (wasResized) | |||
| { | |||
| bool resizerHidden = false; | |||
| if (ComponentPeer* peer = getPeer()) | |||
| resizerHidden = peer->isFullScreen() || peer->isKioskMode(); | |||
| if (resizableCorner != nullptr) | |||
| { | |||
| resizableCorner->setVisible (! resizerHidden); | |||
| const int resizerSize = 18; | |||
| resizableCorner->setBounds (getWidth() - resizerSize, | |||
| getHeight() - resizerSize, | |||
| resizerSize, resizerSize); | |||
| } | |||
| if (! resizable) | |||
| { | |||
| if (getWidth() > 0 && getHeight() > 0) | |||
| defaultConstrainer.setSizeLimits (getWidth(), getHeight(), | |||
| getWidth(), getHeight()); | |||
| } | |||
| } | |||
| } | |||
| void AudioProcessorEditor::updatePeer() | |||
| { | |||
| if (isOnDesktop()) | |||
| if (ComponentPeer* const peer = getPeer()) | |||
| peer->setConstrainer (constrainer); | |||
| } | |||
| @@ -25,7 +25,7 @@ | |||
| #ifndef JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED | |||
| #define JUCE_AUDIOPROCESSOREDITOR_H_INCLUDED | |||
| class AudioProcessorEditorListener; | |||
| //============================================================================== | |||
| /** | |||
| Base class for the component that acts as the GUI for an AudioProcessor. | |||
| @@ -84,7 +84,91 @@ public: | |||
| */ | |||
| virtual int getControlParameterIndex (Component&); | |||
| //============================================================================== | |||
| /** Marks the host's editor window as resizable | |||
| @param allowHostToResize whether the editor's parent window can be resized | |||
| by the user or the host. Even if this is false, you | |||
| can still resize your window yourself by calling | |||
| setBounds (for example, when a user clicks on a button | |||
| in your editor to drop out a panel) which will bypass any | |||
| resizable/constraints checks. If you are using | |||
| your own corner resizer than this will also bypass | |||
| any checks. | |||
| @param useBottomRightCornerResizer | |||
| @see setResizeLimits, isResizable | |||
| */ | |||
| void setResizable (bool allowHostToResize, bool useBottomRightCornerResizer); | |||
| /** Returns true if the host is allowed to resize editor's parent window | |||
| @see setResizable | |||
| */ | |||
| bool isResizable() const noexcept { return resizable; } | |||
| /** This sets the maximum and minimum sizes for the window. | |||
| If the window's current size is outside these limits, it will be resized to | |||
| make sure it's within them. | |||
| A direct call to setBounds() will bypass any constraint checks, but when the | |||
| window is dragged by the user or resized by other indirect means, the constrainer | |||
| will limit the numbers involved. | |||
| @see setResizable | |||
| */ | |||
| void setResizeLimits (int newMinimumWidth, | |||
| int newMinimumHeight, | |||
| int newMaximumWidth, | |||
| int newMaximumHeight) noexcept; | |||
| /** Returns the bounds constrainer object that this window is using. | |||
| You can access this to change its properties. | |||
| */ | |||
| ComponentBoundsConstrainer* getConstrainer() noexcept { return constrainer; } | |||
| /** Sets the bounds-constrainer object to use for resizing and dragging this window. | |||
| A pointer to the object you pass in will be kept, but it won't be deleted | |||
| by this object, so it's the caller's responsibility to manage it. | |||
| If you pass a nullptr, then no contraints will be placed on the positioning of the window. | |||
| */ | |||
| void setConstrainer (ComponentBoundsConstrainer* newConstrainer); | |||
| /** Calls the window's setBounds method, after first checking these bounds | |||
| with the current constrainer. | |||
| @see setConstrainer | |||
| */ | |||
| void setBoundsConstrained (Rectangle<int> newBounds); | |||
| ScopedPointer<ResizableCornerComponent> resizableCorner; | |||
| private: | |||
| //============================================================================== | |||
| struct AudioProcessorEditorListener : ComponentListener | |||
| { | |||
| AudioProcessorEditorListener (AudioProcessorEditor* audioEditor) : e (audioEditor) {} | |||
| void componentMovedOrResized (Component&, bool, bool wasResized) override { e->editorResized (wasResized); } | |||
| void componentParentHierarchyChanged (Component&) override { e->updatePeer(); } | |||
| AudioProcessorEditor* e; | |||
| }; | |||
| //============================================================================== | |||
| void initialise(); | |||
| void editorResized (bool wasResized); | |||
| void updatePeer(); | |||
| void attachConstrainer (ComponentBoundsConstrainer* newConstrainer); | |||
| //============================================================================== | |||
| ScopedPointer<AudioProcessorEditorListener> resizeListener; | |||
| bool resizable; | |||
| ComponentBoundsConstrainer defaultConstrainer; | |||
| ComponentBoundsConstrainer* constrainer; | |||
| JUCE_DECLARE_NON_COPYABLE (AudioProcessorEditor) | |||
| }; | |||
| @@ -264,7 +264,16 @@ struct ProcessBufferOp : public AudioGraphRenderingOp<ProcessBufferOp> | |||
| AudioBuffer<FloatType> buffer (channels, totalChans, numSamples); | |||
| callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); | |||
| if (processor->isSuspended()) | |||
| { | |||
| buffer.clear(); | |||
| } | |||
| else | |||
| { | |||
| ScopedLock lock (processor->getCallbackLock()); | |||
| callProcess (buffer, *sharedMidiBuffers.getUnchecked (midiBufferToUse)); | |||
| } | |||
| } | |||
| void callProcess (AudioBuffer<float>& buffer, MidiBuffer& midiMessages) | |||
| @@ -960,14 +969,7 @@ void AudioProcessorGraph::Node::prepare (const double newSampleRate, const int n | |||
| processor->setProcessingPrecision (processor->supportsDoublePrecisionProcessing() ? precision | |||
| : singlePrecision); | |||
| processor->setPlayConfigDetails (processor->getMainBusNumInputChannels(), | |||
| processor->getMainBusNumOutputChannels(), | |||
| newSampleRate, newBlockSize); | |||
| // AudioProcessorGraph currently does not support processors with multiple buses | |||
| jassert (processor->getMainBusNumInputChannels() == processor->getTotalNumInputChannels() | |||
| && processor->getMainBusNumOutputChannels() == processor->getTotalNumOutputChannels()); | |||
| processor->setRateAndBufferSizeDetails (newSampleRate, newBlockSize); | |||
| processor->prepareToPlay (newSampleRate, newBlockSize); | |||
| } | |||
| } | |||
| @@ -1035,7 +1037,7 @@ struct AudioProcessorGraph::AudioProcessorGraphBufferHelpers | |||
| //============================================================================== | |||
| AudioProcessorGraph::AudioProcessorGraph() | |||
| : lastNodeId (0), audioBuffers (new AudioProcessorGraphBufferHelpers), | |||
| currentMidiInputBuffer (nullptr) | |||
| currentMidiInputBuffer (nullptr), isPrepared (false) | |||
| { | |||
| } | |||
| @@ -1102,7 +1104,9 @@ AudioProcessorGraph::Node* AudioProcessorGraph::addNode (AudioProcessor* const n | |||
| Node* const n = new Node (nodeId, newProcessor); | |||
| nodes.add (n); | |||
| triggerAsyncUpdate(); | |||
| if (isPrepared) | |||
| triggerAsyncUpdate(); | |||
| n->setParentGraph (this); | |||
| return n; | |||
| @@ -1117,7 +1121,9 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) | |||
| if (nodes.getUnchecked(i)->nodeId == nodeId) | |||
| { | |||
| nodes.remove (i); | |||
| triggerAsyncUpdate(); | |||
| if (isPrepared) | |||
| triggerAsyncUpdate(); | |||
| return true; | |||
| } | |||
| @@ -1126,6 +1132,15 @@ bool AudioProcessorGraph::removeNode (const uint32 nodeId) | |||
| return false; | |||
| } | |||
| bool AudioProcessorGraph::removeNode (Node* node) | |||
| { | |||
| if (node != nullptr) | |||
| return removeNode (node->nodeId); | |||
| jassertfalse; | |||
| return false; | |||
| } | |||
| //============================================================================== | |||
| const AudioProcessorGraph::Connection* AudioProcessorGraph::getConnectionBetween (const uint32 sourceNodeId, | |||
| const int sourceChannelIndex, | |||
| @@ -1168,14 +1183,14 @@ bool AudioProcessorGraph::canConnect (const uint32 sourceNodeId, | |||
| const Node* const source = getNodeForId (sourceNodeId); | |||
| if (source == nullptr | |||
| || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getMainBusNumOutputChannels()) | |||
| || (sourceChannelIndex != midiChannelIndex && sourceChannelIndex >= source->processor->getTotalNumOutputChannels()) | |||
| || (sourceChannelIndex == midiChannelIndex && ! source->processor->producesMidi())) | |||
| return false; | |||
| const Node* const dest = getNodeForId (destNodeId); | |||
| if (dest == nullptr | |||
| || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getMainBusNumInputChannels()) | |||
| || (destChannelIndex != midiChannelIndex && destChannelIndex >= dest->processor->getTotalNumInputChannels()) | |||
| || (destChannelIndex == midiChannelIndex && ! dest->processor->acceptsMidi())) | |||
| return false; | |||
| @@ -1194,14 +1209,19 @@ bool AudioProcessorGraph::addConnection (const uint32 sourceNodeId, | |||
| GraphRenderingOps::ConnectionSorter sorter; | |||
| connections.addSorted (sorter, new Connection (sourceNodeId, sourceChannelIndex, | |||
| destNodeId, destChannelIndex)); | |||
| triggerAsyncUpdate(); | |||
| if (isPrepared) | |||
| triggerAsyncUpdate(); | |||
| return true; | |||
| } | |||
| void AudioProcessorGraph::removeConnection (const int index) | |||
| { | |||
| connections.remove (index); | |||
| triggerAsyncUpdate(); | |||
| if (isPrepared) | |||
| triggerAsyncUpdate(); | |||
| } | |||
| bool AudioProcessorGraph::removeConnection (const uint32 sourceNodeId, const int sourceChannelIndex, | |||
| @@ -1253,9 +1273,9 @@ bool AudioProcessorGraph::isConnectionLegal (const Connection* const c) const | |||
| return source != nullptr | |||
| && dest != nullptr | |||
| && (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getMainBusNumOutputChannels()) | |||
| && (c->sourceChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->sourceChannelIndex, source->processor->getTotalNumOutputChannels()) | |||
| : source->processor->producesMidi()) | |||
| && (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getMainBusNumInputChannels()) | |||
| && (c->destChannelIndex != midiChannelIndex ? isPositiveAndBelow (c->destChannelIndex, dest->processor->getTotalNumInputChannels()) | |||
| : dest->processor->acceptsMidi()); | |||
| } | |||
| @@ -1383,6 +1403,8 @@ void AudioProcessorGraph::prepareToPlay (double /*sampleRate*/, int estimatedSam | |||
| clearRenderingSequence(); | |||
| buildRenderingSequence(); | |||
| isPrepared = true; | |||
| } | |||
| bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | |||
| @@ -1392,6 +1414,8 @@ bool AudioProcessorGraph::supportsDoublePrecisionProcessing() const | |||
| void AudioProcessorGraph::releaseResources() | |||
| { | |||
| isPrepared = false; | |||
| for (int i = 0; i < nodes.size(); ++i) | |||
| nodes.getUnchecked(i)->unprepare(); | |||
| @@ -1516,13 +1540,13 @@ void AudioProcessorGraph::AudioGraphIOProcessor::fillInPluginDescription (Plugin | |||
| d.version = "1.0"; | |||
| d.isInstrument = false; | |||
| d.numInputChannels = getMainBusNumInputChannels(); | |||
| d.numInputChannels = getTotalNumInputChannels(); | |||
| if (type == audioOutputNode && graph != nullptr) | |||
| d.numInputChannels = graph->getMainBusNumInputChannels(); | |||
| d.numInputChannels = graph->getTotalNumInputChannels(); | |||
| d.numOutputChannels = getMainBusNumOutputChannels(); | |||
| d.numOutputChannels = getTotalNumOutputChannels(); | |||
| if (type == audioInputNode && graph != nullptr) | |||
| d.numOutputChannels = graph->getMainBusNumOutputChannels(); | |||
| d.numOutputChannels = graph->getTotalNumOutputChannels(); | |||
| } | |||
| void AudioProcessorGraph::AudioGraphIOProcessor::prepareToPlay (double, int) | |||
| @@ -1639,8 +1663,8 @@ void AudioProcessorGraph::AudioGraphIOProcessor::setParentGraph (AudioProcessorG | |||
| if (graph != nullptr) | |||
| { | |||
| setPlayConfigDetails (type == audioOutputNode ? graph->getMainBusNumOutputChannels() : 0, | |||
| type == audioInputNode ? graph->getMainBusNumInputChannels() : 0, | |||
| setPlayConfigDetails (type == audioOutputNode ? graph->getTotalNumOutputChannels() : 0, | |||
| type == audioInputNode ? graph->getTotalNumInputChannels() : 0, | |||
| getSampleRate(), | |||
| getBlockSize()); | |||
| @@ -183,6 +183,12 @@ public: | |||
| */ | |||
| bool removeNode (uint32 nodeId); | |||
| /** Deletes a node within the graph which has the specified ID. | |||
| This will also delete any connections that are attached to this node. | |||
| */ | |||
| bool removeNode (Node* node); | |||
| //============================================================================== | |||
| /** Returns the number of connections in the graph. */ | |||
| int getNumConnections() const { return connections.size(); } | |||
| @@ -390,6 +396,8 @@ private: | |||
| MidiBuffer* currentMidiInputBuffer; | |||
| MidiBuffer currentMidiOutputBuffer; | |||
| bool isPrepared; | |||
| void handleAsyncUpdate() override; | |||
| void clearRenderingSequence(); | |||
| void buildRenderingSequence(); | |||
| @@ -105,6 +105,16 @@ private: | |||
| } | |||
| } | |||
| void startedDragging() override | |||
| { | |||
| owner.beginParameterChangeGesture(index); | |||
| } | |||
| void stoppedDragging() override | |||
| { | |||
| owner.endParameterChangeGesture(index); | |||
| } | |||
| String getTextFromValue (double /*value*/) override | |||
| { | |||
| return owner.getParameterText (index) + " " + owner.getParameterLabel (index).trimEnd(); | |||
| @@ -118,7 +118,7 @@ public: | |||
| given identifier string. | |||
| Note that this isn't quite as simple as them just calling createIdentifierString() | |||
| and comparing the strings, because the identifers can differ (thanks to shell plug-ins). | |||
| and comparing the strings, because the identifiers can differ (thanks to shell plug-ins). | |||
| */ | |||
| bool matchesIdentifierString (const String& identifierString) const; | |||
| @@ -27,6 +27,8 @@ KnownPluginList::~KnownPluginList() {} | |||
| void KnownPluginList::clear() | |||
| { | |||
| ScopedLock lock (typesArrayLock); | |||
| if (types.size() > 0) | |||
| { | |||
| types.clear(); | |||
| @@ -36,6 +38,8 @@ void KnownPluginList::clear() | |||
| PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifier) const | |||
| { | |||
| ScopedLock lock (typesArrayLock); | |||
| for (int i = 0; i < types.size(); ++i) | |||
| if (types.getUnchecked(i)->fileOrIdentifier == fileOrIdentifier) | |||
| return types.getUnchecked(i); | |||
| @@ -45,6 +49,8 @@ PluginDescription* KnownPluginList::getTypeForFile (const String& fileOrIdentifi | |||
| PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& identifierString) const | |||
| { | |||
| ScopedLock lock (typesArrayLock); | |||
| for (int i = 0; i < types.size(); ++i) | |||
| if (types.getUnchecked(i)->matchesIdentifierString (identifierString)) | |||
| return types.getUnchecked(i); | |||
| @@ -54,27 +60,37 @@ PluginDescription* KnownPluginList::getTypeForIdentifierString (const String& id | |||
| bool KnownPluginList::addType (const PluginDescription& type) | |||
| { | |||
| for (int i = types.size(); --i >= 0;) | |||
| { | |||
| if (types.getUnchecked(i)->isDuplicateOf (type)) | |||
| ScopedLock lock (typesArrayLock); | |||
| for (int i = types.size(); --i >= 0;) | |||
| { | |||
| // strange - found a duplicate plugin with different info.. | |||
| jassert (types.getUnchecked(i)->name == type.name); | |||
| jassert (types.getUnchecked(i)->isInstrument == type.isInstrument); | |||
| if (types.getUnchecked(i)->isDuplicateOf (type)) | |||
| { | |||
| // strange - found a duplicate plugin with different info.. | |||
| jassert (types.getUnchecked(i)->name == type.name); | |||
| jassert (types.getUnchecked(i)->isInstrument == type.isInstrument); | |||
| *types.getUnchecked(i) = type; | |||
| return false; | |||
| *types.getUnchecked(i) = type; | |||
| return false; | |||
| } | |||
| } | |||
| types.insert (0, new PluginDescription (type)); | |||
| } | |||
| types.insert (0, new PluginDescription (type)); | |||
| sendChangeMessage(); | |||
| return true; | |||
| } | |||
| void KnownPluginList::removeType (const int index) | |||
| { | |||
| types.remove (index); | |||
| { | |||
| ScopedLock lock (typesArrayLock); | |||
| types.remove (index); | |||
| } | |||
| sendChangeMessage(); | |||
| } | |||
| @@ -84,6 +100,8 @@ bool KnownPluginList::isListingUpToDate (const String& fileOrIdentifier, | |||
| if (getTypeForFile (fileOrIdentifier) == nullptr) | |||
| return false; | |||
| ScopedLock lock (typesArrayLock); | |||
| for (int i = types.size(); --i >= 0;) | |||
| { | |||
| const PluginDescription* const d = types.getUnchecked(i); | |||
| @@ -103,7 +121,7 @@ void KnownPluginList::setCustomScanner (CustomScanner* newScanner) | |||
| bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||
| const bool dontRescanIfAlreadyInList, | |||
| OwnedArray <PluginDescription>& typesFound, | |||
| OwnedArray<PluginDescription>& typesFound, | |||
| AudioPluginFormat& format) | |||
| { | |||
| const ScopedLock sl (scanLock); | |||
| @@ -113,6 +131,8 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||
| { | |||
| bool needsRescanning = false; | |||
| ScopedLock lock (typesArrayLock); | |||
| for (int i = types.size(); --i >= 0;) | |||
| { | |||
| const PluginDescription* const d = types.getUnchecked(i); | |||
| @@ -133,7 +153,7 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||
| if (blacklist.contains (fileOrIdentifier)) | |||
| return false; | |||
| OwnedArray <PluginDescription> found; | |||
| OwnedArray<PluginDescription> found; | |||
| { | |||
| const ScopedUnlock sl2 (scanLock); | |||
| @@ -163,7 +183,7 @@ bool KnownPluginList::scanAndAddFile (const String& fileOrIdentifier, | |||
| void KnownPluginList::scanAndAddDragAndDroppedFiles (AudioPluginFormatManager& formatManager, | |||
| const StringArray& files, | |||
| OwnedArray <PluginDescription>& typesFound) | |||
| OwnedArray<PluginDescription>& typesFound) | |||
| { | |||
| for (int i = 0; i < files.size(); ++i) | |||
| { | |||
| @@ -298,12 +318,17 @@ void KnownPluginList::sort (const SortMethod method, bool forwards) | |||
| if (method != defaultOrder) | |||
| { | |||
| Array<PluginDescription*> oldOrder, newOrder; | |||
| oldOrder.addArray (types); | |||
| PluginSorter sorter (method, forwards); | |||
| types.sort (sorter, true); | |||
| { | |||
| ScopedLock lock (typesArrayLock); | |||
| oldOrder.addArray (types); | |||
| newOrder.addArray (types); | |||
| PluginSorter sorter (method, forwards); | |||
| types.sort (sorter, true); | |||
| newOrder.addArray (types); | |||
| } | |||
| if (oldOrder != newOrder) | |||
| sendChangeMessage(); | |||
| @@ -315,8 +340,12 @@ XmlElement* KnownPluginList::createXml() const | |||
| { | |||
| XmlElement* const e = new XmlElement ("KNOWNPLUGINS"); | |||
| for (int i = types.size(); --i >= 0;) | |||
| e->prependChildElement (types.getUnchecked(i)->createXml()); | |||
| { | |||
| ScopedLock lock (typesArrayLock); | |||
| for (int i = types.size(); --i >= 0;) | |||
| e->prependChildElement (types.getUnchecked(i)->createXml()); | |||
| } | |||
| for (int i = 0; i < blacklist.size(); ++i) | |||
| e->createNewChildElement ("BLACKLISTED")->setAttribute ("id", blacklist[i]); | |||
| @@ -348,7 +377,7 @@ struct PluginTreeUtils | |||
| { | |||
| enum { menuIdBase = 0x324503f4 }; | |||
| static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array <PluginDescription*>& allPlugins) | |||
| static void buildTreeByFolder (KnownPluginList::PluginTree& tree, const Array<PluginDescription*>& allPlugins) | |||
| { | |||
| for (int i = 0; i < allPlugins.size(); ++i) | |||
| { | |||
| @@ -392,7 +421,7 @@ struct PluginTreeUtils | |||
| } | |||
| static void buildTreeByCategory (KnownPluginList::PluginTree& tree, | |||
| const Array <PluginDescription*>& sorted, | |||
| const Array<PluginDescription*>& sorted, | |||
| const KnownPluginList::SortMethod sortMethod) | |||
| { | |||
| String lastType; | |||
| @@ -475,15 +504,21 @@ struct PluginTreeUtils | |||
| return false; | |||
| } | |||
| static void addToMenu (const KnownPluginList::PluginTree& tree, PopupMenu& m, const OwnedArray <PluginDescription>& allPlugins) | |||
| static bool addToMenu (const KnownPluginList::PluginTree& tree, PopupMenu& m, | |||
| const OwnedArray<PluginDescription>& allPlugins, | |||
| const String& currentlyTickedPluginID) | |||
| { | |||
| bool isTicked = false; | |||
| for (int i = 0; i < tree.subFolders.size(); ++i) | |||
| { | |||
| const KnownPluginList::PluginTree& sub = *tree.subFolders.getUnchecked(i); | |||
| PopupMenu subMenu; | |||
| addToMenu (sub, subMenu, allPlugins); | |||
| m.addSubMenu (sub.folder, subMenu); | |||
| const bool isItemTicked = addToMenu (sub, subMenu, allPlugins, currentlyTickedPluginID); | |||
| isTicked = isTicked || isItemTicked; | |||
| m.addSubMenu (sub.folder, subMenu, true, nullptr, isItemTicked, 0); | |||
| } | |||
| for (int i = 0; i < tree.plugins.size(); ++i) | |||
| @@ -495,16 +530,22 @@ struct PluginTreeUtils | |||
| if (containsDuplicateNames (tree.plugins, name)) | |||
| name << " (" << plugin->pluginFormatName << ')'; | |||
| m.addItem (allPlugins.indexOf (plugin) + menuIdBase, name, true, false); | |||
| const bool isItemTicked = plugin->matchesIdentifierString (currentlyTickedPluginID); | |||
| isTicked = isTicked || isItemTicked; | |||
| m.addItem (allPlugins.indexOf (plugin) + menuIdBase, name, true, isItemTicked); | |||
| } | |||
| return isTicked; | |||
| } | |||
| }; | |||
| KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortMethod) const | |||
| { | |||
| Array <PluginDescription*> sorted; | |||
| Array<PluginDescription*> sorted; | |||
| { | |||
| ScopedLock lock (typesArrayLock); | |||
| PluginSorter sorter (sortMethod, true); | |||
| for (int i = 0; i < types.size(); ++i) | |||
| @@ -531,10 +572,11 @@ KnownPluginList::PluginTree* KnownPluginList::createTree (const SortMethod sortM | |||
| } | |||
| //============================================================================== | |||
| void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod) const | |||
| void KnownPluginList::addToMenu (PopupMenu& menu, const SortMethod sortMethod, | |||
| const String& currentlyTickedPluginID) const | |||
| { | |||
| ScopedPointer<PluginTree> tree (createTree (sortMethod)); | |||
| PluginTreeUtils::addToMenu (*tree, menu, types); | |||
| PluginTreeUtils::addToMenu (*tree, menu, types, currentlyTickedPluginID); | |||
| } | |||
| int KnownPluginList::getIndexChosenByMenu (const int menuResultCode) const | |||
| @@ -148,7 +148,8 @@ public: | |||
| Use getIndexChosenByMenu() to find out the type that was chosen. | |||
| */ | |||
| void addToMenu (PopupMenu& menu, SortMethod sortMethod) const; | |||
| void addToMenu (PopupMenu& menu, SortMethod sortMethod, | |||
| const String& currentlyTickedPluginID = String()) const; | |||
| /** Converts a menu item index that has been chosen into its index in this list. | |||
| Returns -1 if it's not an ID that was used. | |||
| @@ -215,7 +216,7 @@ private: | |||
| OwnedArray<PluginDescription> types; | |||
| StringArray blacklist; | |||
| ScopedPointer<CustomScanner> scanner; | |||
| CriticalSection scanLock; | |||
| CriticalSection scanLock, typesArrayLock; | |||
| JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (KnownPluginList) | |||
| }; | |||
| @@ -34,15 +34,17 @@ PluginDirectoryScanner::PluginDirectoryScanner (KnownPluginList& listToAddTo, | |||
| AudioPluginFormat& formatToLookFor, | |||
| FileSearchPath directoriesToSearch, | |||
| const bool recursive, | |||
| const File& deadMansPedal) | |||
| const File& deadMansPedal, | |||
| bool allowPluginsWhichRequireAsynchronousInstantiation) | |||
| : list (listToAddTo), | |||
| format (formatToLookFor), | |||
| deadMansPedalFile (deadMansPedal), | |||
| progress (0) | |||
| progress (0), | |||
| allowAsync (allowPluginsWhichRequireAsynchronousInstantiation) | |||
| { | |||
| directoriesToSearch.removeRedundantPaths(); | |||
| filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive); | |||
| filesOrIdentifiersToScan = format.searchPathsForPlugins (directoriesToSearch, recursive, allowAsync); | |||
| // If any plugins have crashed recently when being loaded, move them to the | |||
| // end of the list to give the others a chance to load correctly.. | |||
| @@ -44,12 +44,11 @@ public: | |||
| @param formatToLookFor this is the type of format that you want to look for | |||
| @param directoriesToSearch the path to search | |||
| @param searchRecursively true to search recursively | |||
| @param deadMansPedalFile if this isn't File::nonexistent, then it will | |||
| be used as a file to store the names of any plugins | |||
| that crash during initialisation. If there are | |||
| any plugins listed in it, then these will always | |||
| be scanned after all other possible files have | |||
| been tried - in this way, even if there's a few | |||
| @param deadMansPedalFile if this isn't File(), then it will be used as a file | |||
| to store the names of any plugins that crash during | |||
| initialisation. If there are any plugins listed in it, | |||
| then these will always be scanned after all other possible | |||
| files have been tried - in this way, even if there's a few | |||
| dodgy plugins in your path, then a couple of rescans | |||
| will still manage to find all the proper plugins. | |||
| It's probably best to choose a file in the user's | |||
| @@ -57,12 +56,16 @@ public: | |||
| settings file) for this. The file format it uses | |||
| is just a list of filenames of the modules that | |||
| failed. | |||
| @param allowPluginsWhichRequireAsynchronousInstantiation | |||
| If this is false then the scanner will exclude plug-ins | |||
| asynchronous creation - such as AUv3 plug-ins. | |||
| */ | |||
| PluginDirectoryScanner (KnownPluginList& listToAddResultsTo, | |||
| AudioPluginFormat& formatToLookFor, | |||
| FileSearchPath directoriesToSearch, | |||
| bool searchRecursively, | |||
| const File& deadMansPedalFile); | |||
| const File& deadMansPedalFile, | |||
| bool allowPluginsWhichRequireAsynchronousInstantiation = false); | |||
| /** Destructor. */ | |||
| ~PluginDirectoryScanner(); | |||
| @@ -116,6 +119,7 @@ private: | |||
| StringArray failedFiles; | |||
| Atomic<int> nextIndex; | |||
| float progress; | |||
| bool allowAsync; | |||
| void updateProgress(); | |||
| void setDeadMansPedalFile (const StringArray& newContents); | |||
| @@ -123,13 +123,15 @@ public: | |||
| //============================================================================== | |||
| PluginListComponent::PluginListComponent (AudioPluginFormatManager& manager, KnownPluginList& listToEdit, | |||
| const File& deadMansPedal, PropertiesFile* const props) | |||
| const File& deadMansPedal, PropertiesFile* const props, | |||
| bool allowPluginsWhichRequireAsynchronousInstantiation) | |||
| : formatManager (manager), | |||
| list (listToEdit), | |||
| deadMansPedalFile (deadMansPedal), | |||
| optionsButton ("Options..."), | |||
| propertiesToUse (props), | |||
| numThreads (0) | |||
| allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), | |||
| numThreads (allowAsync ? 1 : 0) | |||
| { | |||
| tableModel = new TableModel (*this, listToEdit); | |||
| @@ -331,18 +333,25 @@ class PluginListComponent::Scanner : private Timer | |||
| { | |||
| public: | |||
| Scanner (PluginListComponent& plc, AudioPluginFormat& format, PropertiesFile* properties, | |||
| int threads, const String& title, const String& text) | |||
| bool allowPluginsWhichRequireAsynchronousInstantiation, int threads, | |||
| const String& title, const String& text) | |||
| : owner (plc), formatToScan (format), propertiesToUse (properties), | |||
| pathChooserWindow (TRANS("Select folders to scan..."), String::empty, AlertWindow::NoIcon), | |||
| pathChooserWindow (TRANS("Select folders to scan..."), String(), AlertWindow::NoIcon), | |||
| progressWindow (title, text, AlertWindow::NoIcon), | |||
| progress (0.0), numThreads (threads), finished (false) | |||
| progress (0.0), numThreads (threads), allowAsync (allowPluginsWhichRequireAsynchronousInstantiation), | |||
| finished (false) | |||
| { | |||
| FileSearchPath path (formatToScan.getDefaultLocationsToSearch()); | |||
| // You need to use at least one thread when scanning plug-ins asynchronously | |||
| jassert (! allowAsync || (numThreads > 0)); | |||
| if (path.getNumPaths() > 0) // if the path is empty, then paths aren't used for this format. | |||
| { | |||
| #if ! JUCE_IOS | |||
| if (propertiesToUse != nullptr) | |||
| path = getLastSearchPath (*propertiesToUse, formatToScan); | |||
| #endif | |||
| pathList.setSize (500, 300); | |||
| pathList.setPath (path); | |||
| @@ -381,7 +390,7 @@ private: | |||
| String pluginBeingScanned; | |||
| double progress; | |||
| int numThreads; | |||
| bool finished; | |||
| bool allowAsync, finished; | |||
| ScopedPointer<ThreadPool> pool; | |||
| static void startScanCallback (int result, AlertWindow* alert, Scanner* scanner) | |||
| @@ -413,7 +422,7 @@ private: | |||
| + TRANS ("Are you sure you want to scan the folder \"XYZ\"?") | |||
| .replace ("XYZ", f.getFullPathName()), | |||
| TRANS ("Scan"), | |||
| String::empty, | |||
| String(), | |||
| nullptr, | |||
| ModalCallbackFunction::create (warnAboutStupidPathsCallback, this)); | |||
| return; | |||
| @@ -465,7 +474,7 @@ private: | |||
| pathChooserWindow.setVisible (false); | |||
| scanner = new PluginDirectoryScanner (owner.list, formatToScan, pathList.getPath(), | |||
| true, owner.deadMansPedalFile); | |||
| true, owner.deadMansPedalFile, allowAsync); | |||
| if (propertiesToUse != nullptr) | |||
| { | |||
| @@ -545,7 +554,7 @@ private: | |||
| void PluginListComponent::scanFor (AudioPluginFormat& format) | |||
| { | |||
| currentScanner = new Scanner (*this, format, propertiesToUse, numThreads, | |||
| currentScanner = new Scanner (*this, format, propertiesToUse, allowAsync, numThreads, | |||
| dialogTitle.isNotEmpty() ? dialogTitle : TRANS("Scanning for plug-ins..."), | |||
| dialogText.isNotEmpty() ? dialogText : TRANS("Searching for all possible plug-in files...")); | |||
| } | |||
| @@ -47,7 +47,8 @@ public: | |||
| PluginListComponent (AudioPluginFormatManager& formatManager, | |||
| KnownPluginList& listToRepresent, | |||
| const File& deadMansPedalFile, | |||
| PropertiesFile* propertiesToUse); | |||
| PropertiesFile* propertiesToUse, | |||
| bool allowPluginsWhichRequireAsynchronousInstantiation = false); | |||
| /** Destructor. */ | |||
| ~PluginListComponent(); | |||
| @@ -60,8 +61,10 @@ public: | |||
| const String& textForProgressWindowDescription); | |||
| /** Sets how many threads to simultaneously scan for plugins. | |||
| If this is 0, then all scanning happens on the message thread (this is the default) | |||
| */ | |||
| If this is 0, then all scanning happens on the message thread (this is the default when | |||
| allowPluginsWhichRequireAsynchronousInstantiation is false). If | |||
| allowPluginsWhichRequireAsynchronousInstantiation is true then numThreads must not | |||
| be zero (it is one by default). */ | |||
| void setNumberOfThreadsForScanning (int numThreads); | |||
| /** Returns the last search path stored in a given properties file for the specified format. */ | |||
| @@ -96,6 +99,7 @@ private: | |||
| TextButton optionsButton; | |||
| PropertiesFile* propertiesToUse; | |||
| String dialogTitle, dialogText; | |||
| bool allowAsync; | |||
| int numThreads; | |||
| class TableModel; | |||