diff --git a/build/linux/platform_specific_code/juce_linux_Audio.cpp b/build/linux/platform_specific_code/juce_linux_Audio.cpp index b01f943fc1..c5555359f5 100644 --- a/build/linux/platform_specific_code/juce_linux_Audio.cpp +++ b/build/linux/platform_specific_code/juce_linux_Audio.cpp @@ -399,6 +399,8 @@ public: error = String::empty; sampleRate = sampleRate_; bufferSize = bufferSize_; + currentInputChans.clear(); + currentOutputChans.clear(); numChannelsRunning = jmax (inputChannels.getHighestBit(), outputChannels.getHighestBit()) + 1; @@ -414,7 +416,10 @@ public: inputChannelData [i] = (float*) juce_calloc (sizeof (float) * bufferSize); if (inputChannels[i]) + { inputChannelDataForCallback [totalNumInputChannels++] = inputChannelData [i]; + currentInputChans.setBit (i); + } } } @@ -425,7 +430,10 @@ public: outputChannelData [i] = (float*) juce_calloc (sizeof (float) * bufferSize); if (outputChannels[i]) + { outputChannelDataForCallback [totalNumOutputChannels++] = outputChannelData [i]; + currentOutputChans.setBit (i); + } } } @@ -603,6 +611,7 @@ public: String error; double sampleRate; int bufferSize; + BitArray currentInputChans, currentOutputChans; Array sampleRates; StringArray channelNamesOut, channelNamesIn; @@ -777,6 +786,16 @@ public: return internal->getBitDepth(); } + const BitArray getActiveOutputChannels() const + { + return internal->activeOutputChans; + } + + const BitArray getActiveInputChannels() const + { + return internal->activeInputChans; + } + int getOutputLatencyInSamples() { return 0; diff --git a/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp b/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp index c46556e324..92e973a74b 100644 --- a/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp +++ b/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp @@ -1,1161 +1,1185 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../src/juce_core/basics/juce_StandardHeader.h" -#include - -BEGIN_JUCE_NAMESPACE - - -#include "../../../src/juce_appframework/audio/devices/juce_AudioIODeviceType.h" -#include "../../../src/juce_appframework/events/juce_Timer.h" -#include "../../../src/juce_core/threads/juce_ScopedLock.h" -#include "../../../src/juce_core/threads/juce_Thread.h" -#include "../../../src/juce_core/text/juce_LocalisedStrings.h" - - -//============================================================================== -#ifndef JUCE_COREAUDIO_ERROR_LOGGING_ENABLED - #define JUCE_COREAUDIO_ERROR_LOGGING_ENABLED 1 -#endif - -//============================================================================== -#if JUCE_COREAUDIO_LOGGING_ENABLED - #define log(a) Logger::writeToLog (a) -#else - #define log(a) -#endif - -#if JUCE_COREAUDIO_ERROR_LOGGING_ENABLED - static bool logAnyErrors (const OSStatus err, const int lineNum) - { - if (err == noErr) - return true; - - Logger::writeToLog (T("CoreAudio error: ") + String (lineNum) + T(" - ") + String::toHexString ((int)err)); - jassertfalse - return false; - } - - #define OK(a) logAnyErrors (a, __LINE__) -#else - #define OK(a) (a == noErr) -#endif - -//============================================================================== -static const int maxNumChans = 96; - - -//============================================================================== -class CoreAudioInternal : public Timer -{ -public: - //============================================================================== - CoreAudioInternal (AudioDeviceID id) - : deviceID (id), - started (false), - audioBuffer (0), - numInputChans (0), - numOutputChans (0), - callbacksAllowed (true), - inputLatency (0), - outputLatency (0), - callback (0), - inputDevice (0), - isSlaveDevice (false) - { - sampleRate = 0; - bufferSize = 512; - - if (deviceID == 0) - { - error = TRANS("can't open device"); - } - else - { - updateDetailsFromDevice(); - - AudioDeviceAddPropertyListener (deviceID, - kAudioPropertyWildcardChannel, - kAudioPropertyWildcardSection, - kAudioPropertyWildcardPropertyID, - deviceListenerProc, this); - } - } - - ~CoreAudioInternal() - { - AudioDeviceRemovePropertyListener (deviceID, - kAudioPropertyWildcardChannel, - kAudioPropertyWildcardSection, - kAudioPropertyWildcardPropertyID, - deviceListenerProc); - - stop (false); - - juce_free (audioBuffer); - delete inputDevice; - } - - void setTempBufferSize (const int numChannels, const int numSamples) - { - juce_free (audioBuffer); - - audioBuffer = (float*) juce_calloc (32 + numChannels * numSamples * sizeof (float)); - - zeromem (tempInputBuffers, sizeof (tempInputBuffers)); - zeromem (tempOutputBuffers, sizeof (tempOutputBuffers)); - - int count = 0; - int i; - for (i = maxNumChans; --i >= 0;) - if (activeInputChans[i]) - tempInputBuffers[i] = audioBuffer + count++ * numSamples; - - for (i = maxNumChans; --i >= 0;) - if (activeOutputChans[i]) - tempOutputBuffers[i] = audioBuffer + count++ * numSamples; - } - - // returns the number of actual available channels - void fillInChannelInfo (bool input) - { - int chanNum = 0, activeChans = 0; - UInt32 size; - - if (OK (AudioDeviceGetPropertyInfo (deviceID, 0, input, kAudioDevicePropertyStreamConfiguration, &size, 0))) - { - AudioBufferList* const bufList = (AudioBufferList*) juce_calloc (size); - - if (OK (AudioDeviceGetProperty (deviceID, 0, input, kAudioDevicePropertyStreamConfiguration, &size, bufList))) - { - const int numStreams = bufList->mNumberBuffers; - - for (int i = 0; i < numStreams; ++i) - { - const AudioBuffer& b = bufList->mBuffers[i]; - - for (unsigned int j = 0; j < b.mNumberChannels; ++j) - { - if (input) - { - if (activeInputChans[chanNum]) - { - inputChannelInfo [activeChans].sourceChannelNum = chanNum; - inputChannelInfo [activeChans].streamNum = i; - inputChannelInfo [activeChans].dataOffsetSamples = j; - inputChannelInfo [activeChans].dataStrideSamples = b.mNumberChannels; - ++activeChans; - numInputChannelInfos = activeChans; - } - - inChanNames.add (T("input ") + String (chanNum + 1)); - } - else - { - if (activeOutputChans[chanNum]) - { - outputChannelInfo [activeChans].sourceChannelNum = chanNum; - outputChannelInfo [activeChans].streamNum = i; - outputChannelInfo [activeChans].dataOffsetSamples = j; - outputChannelInfo [activeChans].dataStrideSamples = b.mNumberChannels; - ++activeChans; - numOutputChannelInfos = activeChans; - } - - outChanNames.add (T("output ") + String (chanNum + 1)); - } - - ++chanNum; - } - } - } - - juce_free (bufList); - } - } - - void updateDetailsFromDevice() - { - stopTimer(); - - if (deviceID == 0) - return; - - const ScopedLock sl (callbackLock); - - Float64 sr; - UInt32 size = sizeof (Float64); - if (OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyNominalSampleRate, &size, &sr))) - sampleRate = sr; - - UInt32 framesPerBuf; - size = sizeof (framesPerBuf); - - if (OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyBufferFrameSize, &size, &framesPerBuf))) - { - bufferSize = framesPerBuf; - - if (bufferSize > 0) - setTempBufferSize (numInputChans + numOutputChans, bufferSize); - } - - bufferSizes.clear(); - - if (OK (AudioDeviceGetPropertyInfo (deviceID, 0, false, kAudioDevicePropertyBufferFrameSizeRange, &size, 0))) - { - AudioValueRange* ranges = (AudioValueRange*) juce_calloc (size); - - if (OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyBufferFrameSizeRange, &size, ranges))) - { - bufferSizes.add ((int) ranges[0].mMinimum); - - for (int i = 32; i < 8192; i += 32) - { - for (int j = size / sizeof (AudioValueRange); --j >= 0;) - { - if (i >= ranges[j].mMinimum && i <= ranges[j].mMaximum) - { - bufferSizes.addIfNotAlreadyThere (i); - break; - } - } - } - - if (bufferSize > 0) - bufferSizes.addIfNotAlreadyThere (bufferSize); - } - - juce_free (ranges); - } - - if (bufferSizes.size() == 0 && bufferSize > 0) - bufferSizes.add (bufferSize); - - sampleRates.clear(); - const double possibleRates[] = { 44100.0, 48000.0, 88200.0, 96000.0, 176400.0, 192000.0 }; - String rates; - - if (OK (AudioDeviceGetPropertyInfo (deviceID, 0, false, kAudioDevicePropertyAvailableNominalSampleRates, &size, 0))) - { - AudioValueRange* ranges = (AudioValueRange*) juce_calloc (size); - - if (OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyAvailableNominalSampleRates, &size, ranges))) - { - for (int i = 0; i < numElementsInArray (possibleRates); ++i) - { - bool ok = false; - - for (int j = size / sizeof (AudioValueRange); --j >= 0;) - if (possibleRates[i] >= ranges[j].mMinimum - 2 && possibleRates[i] <= ranges[j].mMaximum + 2) - ok = true; - - if (ok) - { - sampleRates.add (possibleRates[i]); - rates << possibleRates[i] << T(" "); - } - } - } - - juce_free (ranges); - } - - if (sampleRates.size() == 0 && sampleRate > 0) - { - sampleRates.add (sampleRate); - rates << sampleRate; - } - - log (T("sr: ") + rates); - - inputLatency = 0; - outputLatency = 0; - UInt32 lat; - size = sizeof (UInt32); - if (AudioDeviceGetProperty (deviceID, 0, true, kAudioDevicePropertyLatency, &size, &lat) == noErr) - inputLatency = (int) lat; - - if (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyLatency, &size, &lat) == noErr) - outputLatency = (int) lat; - - log (T("lat: ") + String (inputLatency) + T(" ") + String (outputLatency)); - - inChanNames.clear(); - outChanNames.clear(); - - zeromem (inputChannelInfo, sizeof (inputChannelInfo)); - zeromem (outputChannelInfo, sizeof (outputChannelInfo)); - - fillInChannelInfo (true); - fillInChannelInfo (false); - } - - //============================================================================== - const StringArray getSources (bool input) - { - StringArray s; - int num = 0; - OSType* types = getAllDataSourcesForDevice (deviceID, input, num); - - if (types != 0) - { - for (int i = 0; i < num; ++i) - { - AudioValueTranslation avt; - char buffer[256]; - - avt.mInputData = (void*) &(types[i]); - avt.mInputDataSize = sizeof (UInt32); - avt.mOutputData = buffer; - avt.mOutputDataSize = 256; - - UInt32 transSize = sizeof (avt); - if (OK (AudioDeviceGetProperty (deviceID, 0, input, kAudioDevicePropertyDataSourceNameForID, &transSize, &avt))) - { - DBG (buffer); - s.add (buffer); - } - } - - juce_free (types); - } - - return s; - } - - int getCurrentSourceIndex (bool input) const - { - OSType currentSourceID = 0; - UInt32 size = 0; - int result = -1; - - if (deviceID != 0 - && OK (AudioDeviceGetPropertyInfo (deviceID, 0, input, kAudioDevicePropertyDataSource, &size, 0))) - { - if (OK (AudioDeviceGetProperty (deviceID, 0, input, kAudioDevicePropertyDataSource, &size, ¤tSourceID))) - { - int num = 0; - OSType* const types = getAllDataSourcesForDevice (deviceID, input, num); - - if (types != 0) - { - for (int i = 0; i < num; ++i) - { - if (types[num] == currentSourceID) - { - result = i; - break; - } - } - - juce_free (types); - } - } - } - - return result; - } - - void setCurrentSourceIndex (int index, bool input) - { - if (deviceID != 0) - { - int num = 0; - OSType* types = getAllDataSourcesForDevice (deviceID, input, num); - - if (types != 0) - { - if (index >= 0 && index < num) - { - OSType id = types[index]; - AudioDeviceSetProperty (deviceID, 0, 0, input, kAudioDevicePropertyDataSource, sizeof (id), &id); - } - - juce_free (types); - } - } - } - - //============================================================================== - const String reopen (const BitArray& inputChannels, - const BitArray& outputChannels, - double newSampleRate, - int bufferSizeSamples) - { - error = String::empty; - log ("CoreAudio reopen"); - callbacksAllowed = false; - stopTimer(); - - stop (false); - - activeInputChans = inputChannels; - activeOutputChans = outputChannels; - numInputChans = inputChannels.countNumberOfSetBits(); - numOutputChans = outputChannels.countNumberOfSetBits(); - - // set sample rate - Float64 sr = newSampleRate; - UInt32 size = sizeof (sr); - OK (AudioDeviceSetProperty (deviceID, 0, 0, false, kAudioDevicePropertyNominalSampleRate, size, &sr)); - OK (AudioDeviceSetProperty (deviceID, 0, 0, true, kAudioDevicePropertyNominalSampleRate, size, &sr)); - - // change buffer size - UInt32 framesPerBuf = bufferSizeSamples; - size = sizeof (framesPerBuf); - - OK (AudioDeviceSetProperty (deviceID, 0, 0, false, kAudioDevicePropertyBufferFrameSize, size, &framesPerBuf)); - OK (AudioDeviceSetProperty (deviceID, 0, 0, true, kAudioDevicePropertyBufferFrameSize, size, &framesPerBuf)); - - // wait for the changes to happen (on some devices) - int i = 30; - while (--i >= 0) - { - updateDetailsFromDevice(); - - if (sampleRate == newSampleRate && bufferSizeSamples == bufferSize) - break; - - Thread::sleep (100); - } - - if (i < 0) - error = "Couldn't change sample rate/buffer size"; - - if (sampleRates.size() == 0) - error = "Device has no available sample-rates"; - - if (bufferSizes.size() == 0) - error = "Device has no available buffer-sizes"; - - numInputChans = jmin (numInputChans, numInputChannelInfos); - numOutputChans = jmin (numOutputChans, numOutputChannelInfos); - - if (inputDevice != 0 && error.isEmpty()) - error = inputDevice->reopen (inputChannels, - outputChannels, - newSampleRate, - bufferSizeSamples); - - callbacksAllowed = true; - - return error; - } - - bool start (AudioIODeviceCallback* cb) - { - if (! started) - { - callback = 0; - - if (deviceID != 0) - { - if (OK (AudioDeviceAddIOProc (deviceID, audioIOProc, (void*) this))) - { - if (OK (AudioDeviceStart (deviceID, audioIOProc))) - { - started = true; - } - else - { - OK (AudioDeviceRemoveIOProc (deviceID, audioIOProc)); - } - } - } - } - - if (started) - { - const ScopedLock sl (callbackLock); - callback = cb; - } - - if (inputDevice != 0) - return started && inputDevice->start (cb); - else - return started; - } - - void stop (bool leaveInterruptRunning) - { - callbackLock.enter(); - callback = 0; - callbackLock.exit(); - - if (started - && (deviceID != 0) - && ! leaveInterruptRunning) - { - OK (AudioDeviceStop (deviceID, audioIOProc)); - OK (AudioDeviceRemoveIOProc (deviceID, audioIOProc)); - started = false; - - callbackLock.enter(); - callbackLock.exit(); - - // wait until it's definately stopped calling back.. - for (int i = 40; --i >= 0;) - { - Thread::sleep (50); - - UInt32 running = 0; - UInt32 size = sizeof (running); - OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyDeviceIsRunning, &size, &running)); - if (running == 0) - break; - } - - callbackLock.enter(); - callbackLock.exit(); - } - - if (inputDevice != 0) - inputDevice->stop (leaveInterruptRunning); - } - - double getSampleRate() const - { - return sampleRate; - } - - int getBufferSize() const - { - return bufferSize; - } - - void audioCallback (const AudioBufferList* inInputData, - AudioBufferList* outOutputData) - { - int i; - const ScopedLock sl (callbackLock); - - if (callback != 0) - { - if (inputDevice == 0) - { - for (i = numInputChans; --i >= 0;) - { - const CallbackDetailsForChannel& info = inputChannelInfo[i]; - float* dest = tempInputBuffers [info.sourceChannelNum]; - const float* src = ((const float*) inInputData->mBuffers[info.streamNum].mData) - + info.dataOffsetSamples; - const int stride = info.dataStrideSamples; - - if (stride != 0) // if this is zero, info is invalid - { - for (int j = bufferSize; --j >= 0;) - { - *dest++ = *src; - src += stride; - } - } - } - } - - if (! isSlaveDevice) - { - if (inputDevice == 0) - { - callback->audioDeviceIOCallback ((const float**) tempInputBuffers, - numInputChans, - tempOutputBuffers, - numOutputChans, - bufferSize); - } - else - { - jassert (inputDevice->bufferSize == bufferSize); - - callback->audioDeviceIOCallback ((const float**) inputDevice->tempInputBuffers, - inputDevice->numInputChans, - tempOutputBuffers, - numOutputChans, - bufferSize); - } - - for (i = numOutputChans; --i >= 0;) - { - const CallbackDetailsForChannel& info = outputChannelInfo[i]; - const float* src = tempOutputBuffers [info.sourceChannelNum]; - float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) - + info.dataOffsetSamples; - const int stride = info.dataStrideSamples; - - if (stride != 0) // if this is zero, info is invalid - { - for (int j = bufferSize; --j >= 0;) - { - *dest = *src++; - dest += stride; - } - } - } - } - } - else - { - for (i = jmin (numOutputChans, numOutputChannelInfos); --i >= 0;) - { - const CallbackDetailsForChannel& info = outputChannelInfo[i]; - float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) - + info.dataOffsetSamples; - const int stride = info.dataStrideSamples; - - if (stride != 0) // if this is zero, info is invalid - { - for (int j = bufferSize; --j >= 0;) - { - *dest = 0.0f; - dest += stride; - } - } - } - } - } - - // called by callbacks - void deviceDetailsChanged() - { - if (callbacksAllowed) - startTimer (100); - } - - void timerCallback() - { - stopTimer(); - log ("CoreAudio device changed callback"); - - const double oldSampleRate = sampleRate; - const int oldBufferSize = bufferSize; - updateDetailsFromDevice(); - - if (oldBufferSize != bufferSize || oldSampleRate != sampleRate) - { - callbacksAllowed = false; - stop (false); - updateDetailsFromDevice(); - callbacksAllowed = true; - } - } - - CoreAudioInternal* getRelatedDevice() const - { - UInt32 size = 0; - CoreAudioInternal* result = 0; - - if (deviceID != 0 - && AudioDeviceGetPropertyInfo (deviceID, 0, false, kAudioDevicePropertyRelatedDevices, &size, 0) == noErr - && size > 0) - { - AudioDeviceID* devs = (AudioDeviceID*) juce_calloc (size); - - if (OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyRelatedDevices, &size, devs))) - { - for (unsigned int i = 0; i < size / sizeof (AudioDeviceID); ++i) - { - if (devs[i] != deviceID && devs[i] != 0) - { - result = new CoreAudioInternal (devs[i]); - - if (result->error.isEmpty()) - { - const bool thisIsInput = inChanNames.size() > 0 && outChanNames.size() == 0; - const bool otherIsInput = result->inChanNames.size() > 0 && result->outChanNames.size() == 0; - - if (thisIsInput != otherIsInput) - break; - } - - deleteAndZero (result); - } - } - } - - juce_free (devs); - } - - return result; - } - - //============================================================================== - juce_UseDebuggingNewOperator - - String error; - int inputLatency, outputLatency; - StringArray inChanNames, outChanNames; - Array sampleRates; - Array bufferSizes; - AudioIODeviceCallback* callback; - - CoreAudioInternal* inputDevice; - bool isSlaveDevice; - -private: - CriticalSection callbackLock; - AudioDeviceID deviceID; - bool started; - double sampleRate; - int bufferSize; - float* audioBuffer; - BitArray activeInputChans, activeOutputChans; - int numInputChans, numOutputChans; - bool callbacksAllowed; - - struct CallbackDetailsForChannel - { - int sourceChannelNum; - int streamNum; - int dataOffsetSamples; - int dataStrideSamples; - }; - - int numInputChannelInfos, numOutputChannelInfos; - CallbackDetailsForChannel inputChannelInfo [maxNumChans]; - CallbackDetailsForChannel outputChannelInfo [maxNumChans]; - float* tempInputBuffers [maxNumChans]; - float* tempOutputBuffers [maxNumChans]; - - CoreAudioInternal (const CoreAudioInternal&); - const CoreAudioInternal& operator= (const CoreAudioInternal&); - - //============================================================================== - static OSStatus audioIOProc (AudioDeviceID inDevice, - const AudioTimeStamp* inNow, - const AudioBufferList* inInputData, - const AudioTimeStamp* inInputTime, - AudioBufferList* outOutputData, - const AudioTimeStamp* inOutputTime, - void* device) - { - ((CoreAudioInternal*) device)->audioCallback (inInputData, outOutputData); - return noErr; - } - - static OSStatus deviceListenerProc (AudioDeviceID inDevice, - UInt32 inLine, - Boolean isInput, - AudioDevicePropertyID inPropertyID, - void* inClientData) - { - CoreAudioInternal* const intern = (CoreAudioInternal*) inClientData; - - switch (inPropertyID) - { - case kAudioDevicePropertyBufferSize: - case kAudioDevicePropertyBufferFrameSize: - case kAudioDevicePropertyNominalSampleRate: - case kAudioDevicePropertyStreamFormat: - case kAudioDevicePropertyDeviceIsAlive: - intern->deviceDetailsChanged(); - break; - - case kAudioDevicePropertyBufferSizeRange: - case kAudioDevicePropertyVolumeScalar: - case kAudioDevicePropertyMute: - case kAudioDevicePropertyPlayThru: - case kAudioDevicePropertyDataSource: - case kAudioDevicePropertyDeviceIsRunning: - break; - } - - return noErr; - } - - //============================================================================== - static OSType* getAllDataSourcesForDevice (AudioDeviceID deviceID, const bool input, int& num) - { - OSType* types = 0; - UInt32 size = 0; - num = 0; - - if (deviceID != 0 - && OK (AudioDeviceGetPropertyInfo (deviceID, 0, input, kAudioDevicePropertyDataSources, &size, 0))) - { - types = (OSType*) juce_calloc (size); - - if (OK (AudioDeviceGetProperty (deviceID, 0, input, kAudioDevicePropertyDataSources, &size, types))) - { - num = size / sizeof (OSType); - } - else - { - juce_free (types); - types = 0; - } - } - - return types; - } -}; - - -//============================================================================== -class CoreAudioIODevice : public AudioIODevice -{ -public: - CoreAudioIODevice (const String& deviceName, - AudioDeviceID deviceId1) - : AudioIODevice (deviceName, "CoreAudio"), - isOpen_ (false), - isStarted (false) - { - internal = 0; - - CoreAudioInternal* device = new CoreAudioInternal (deviceId1); - lastError = device->error; - - if (lastError.isNotEmpty()) - { - deleteAndZero (device); - } - else - { - CoreAudioInternal* secondDevice = device->getRelatedDevice(); - - if (secondDevice != 0) - { - if (device->inChanNames.size() > secondDevice->inChanNames.size()) - swapVariables (device, secondDevice); - - device->inputDevice = secondDevice; - secondDevice->isSlaveDevice = true; - } - } - - internal = device; - - AudioHardwareAddPropertyListener (kAudioPropertyWildcardPropertyID, - hardwareListenerProc, internal); - } - - ~CoreAudioIODevice() - { - AudioHardwareRemovePropertyListener (kAudioPropertyWildcardPropertyID, - hardwareListenerProc); - - delete internal; - } - - const StringArray getOutputChannelNames() - { - return internal->outChanNames; - } - - const StringArray getInputChannelNames() - { - if (internal->inputDevice != 0) - return internal->inputDevice->inChanNames; - else - return internal->inChanNames; - } - - int getNumSampleRates() - { - return internal->sampleRates.size(); - } - - double getSampleRate (int index) - { - return internal->sampleRates [index]; - } - - int getNumBufferSizesAvailable() - { - return internal->bufferSizes.size(); - } - - int getBufferSizeSamples (int index) - { - return internal->bufferSizes [index]; - } - - int getDefaultBufferSize() - { - for (int i = 0; i < getNumBufferSizesAvailable(); ++i) - if (getBufferSizeSamples(i) >= 512) - return getBufferSizeSamples(i); - - return 512; - } - - const String open (const BitArray& inputChannels, - const BitArray& outputChannels, - double sampleRate, - int bufferSizeSamples) - { - isOpen_ = true; - - if (bufferSizeSamples <= 0) - bufferSizeSamples = getDefaultBufferSize(); - - internal->reopen (inputChannels, outputChannels, sampleRate, bufferSizeSamples); - lastError = internal->error; - return lastError; - } - - void close() - { - isOpen_ = false; - } - - bool isOpen() - { - return isOpen_; - } - - int getCurrentBufferSizeSamples() - { - if (internal == 0) - return 512; - - return internal->getBufferSize(); - } - - double getCurrentSampleRate() - { - if (internal == 0) - return 0; - - return internal->getSampleRate(); - } - - int getCurrentBitDepth() - { - return 32; // no way to find out, so just assume it's high.. - } - - int getOutputLatencyInSamples() - { - if (internal == 0) - return 0; - - return internal->outputLatency; - } - - int getInputLatencyInSamples() - { - if (internal == 0) - return 0; - - return internal->inputLatency; - } - - void start (AudioIODeviceCallback* callback) - { - if (internal != 0 && ! isStarted) - { - if (callback != 0) - callback->audioDeviceAboutToStart (getCurrentSampleRate(), - getCurrentBufferSizeSamples()); - - isStarted = true; - internal->start (callback); - } - } - - void stop() - { - if (isStarted && internal != 0) - { - AudioIODeviceCallback* const lastCallback = internal->callback; - - isStarted = false; - internal->stop (true); - - if (lastCallback != 0) - lastCallback->audioDeviceStopped(); - } - } - - bool isPlaying() - { - if (internal->callback == 0) - isStarted = false; - - return isStarted; - } - - const String getLastError() - { - return lastError; - } - - juce_UseDebuggingNewOperator - -private: - CoreAudioInternal* internal; - bool isOpen_, isStarted; - String lastError; - - static OSStatus hardwareListenerProc (AudioHardwarePropertyID inPropertyID, void* inClientData) - { - CoreAudioInternal* const intern = (CoreAudioInternal*) inClientData; - - switch (inPropertyID) - { - case kAudioHardwarePropertyDevices: - intern->deviceDetailsChanged(); - break; - - case kAudioHardwarePropertyDefaultOutputDevice: - case kAudioHardwarePropertyDefaultInputDevice: - case kAudioHardwarePropertyDefaultSystemOutputDevice: - break; - } - - return noErr; - } - - CoreAudioIODevice (const CoreAudioIODevice&); - const CoreAudioIODevice& operator= (const CoreAudioIODevice&); -}; - - -//============================================================================== -class CoreAudioIODeviceType : public AudioIODeviceType -{ -public: - //============================================================================== - CoreAudioIODeviceType() - : AudioIODeviceType (T("CoreAudio")), - hasScanned (false) - { - } - - ~CoreAudioIODeviceType() - { - } - - //============================================================================== - void scanForDevices() - { - hasScanned = true; - - names.clear(); - ids.clear(); - - UInt32 size; - if (OK (AudioHardwareGetPropertyInfo (kAudioHardwarePropertyDevices, &size, 0))) - { - AudioDeviceID* const devs = (AudioDeviceID*) juce_calloc (size); - - if (OK (AudioHardwareGetProperty (kAudioHardwarePropertyDevices, &size, devs))) - { - static bool alreadyLogged = false; - const int num = size / sizeof (AudioDeviceID); - for (int i = 0; i < num; ++i) - { - char name[1024]; - size = sizeof (name); - if (OK (AudioDeviceGetProperty (devs[i], 0, false, kAudioDevicePropertyDeviceName, &size, name))) - { - const String nameString (String::fromUTF8 ((const uint8*) name, strlen (name))); - - if (! alreadyLogged) - log (T("CoreAudio device: ") + nameString); - - names.add (nameString); - ids.add (devs[i]); - } - } - - alreadyLogged = true; - } - - juce_free (devs); - } - } - - const StringArray getDeviceNames (const bool /*preferInputNames*/) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - StringArray namesCopy (names); - namesCopy.removeDuplicates (true); - - return namesCopy; - } - - const String getDefaultDeviceName (const bool preferInputNames) const - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - String result (names[0]); - - AudioDeviceID deviceID; - UInt32 size = sizeof (deviceID); - - if (AudioHardwareGetProperty (preferInputNames ? kAudioHardwarePropertyDefaultInputDevice - : kAudioHardwarePropertyDefaultOutputDevice, - &size, &deviceID) == noErr) - { - for (int i = ids.size(); --i >= 0;) - if (ids[i] == deviceID) - result = names[i]; - } - - return result; - } - - AudioIODevice* createDevice (const String& deviceName) - { - jassert (hasScanned); // need to call scanForDevices() before doing this - - const int index = names.indexOf (deviceName); - - if (index >= 0) - return new CoreAudioIODevice (deviceName, ids [index]); - - return 0; - } - - //============================================================================== - juce_UseDebuggingNewOperator - -private: - StringArray names; - Array ids; - - bool hasScanned; - - CoreAudioIODeviceType (const CoreAudioIODeviceType&); - const CoreAudioIODeviceType& operator= (const CoreAudioIODeviceType&); -}; - -//============================================================================== -AudioIODeviceType* juce_createDefaultAudioIODeviceType() -{ - return new CoreAudioIODeviceType(); -} - - -END_JUCE_NAMESPACE +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../src/juce_core/basics/juce_StandardHeader.h" +#include + +BEGIN_JUCE_NAMESPACE + + +#include "../../../src/juce_appframework/audio/devices/juce_AudioIODeviceType.h" +#include "../../../src/juce_appframework/events/juce_Timer.h" +#include "../../../src/juce_core/threads/juce_ScopedLock.h" +#include "../../../src/juce_core/threads/juce_Thread.h" +#include "../../../src/juce_core/text/juce_LocalisedStrings.h" + + +//============================================================================== +#ifndef JUCE_COREAUDIO_ERROR_LOGGING_ENABLED + #define JUCE_COREAUDIO_ERROR_LOGGING_ENABLED 1 +#endif + +//============================================================================== +#if JUCE_COREAUDIO_LOGGING_ENABLED + #define log(a) Logger::writeToLog (a) +#else + #define log(a) +#endif + +#if JUCE_COREAUDIO_ERROR_LOGGING_ENABLED + static bool logAnyErrors (const OSStatus err, const int lineNum) + { + if (err == noErr) + return true; + + Logger::writeToLog (T("CoreAudio error: ") + String (lineNum) + T(" - ") + String::toHexString ((int)err)); + jassertfalse + return false; + } + + #define OK(a) logAnyErrors (a, __LINE__) +#else + #define OK(a) (a == noErr) +#endif + +//============================================================================== +static const int maxNumChans = 96; + + +//============================================================================== +class CoreAudioInternal : public Timer +{ +public: + //============================================================================== + CoreAudioInternal (AudioDeviceID id) + : deviceID (id), + started (false), + audioBuffer (0), + numInputChans (0), + numOutputChans (0), + callbacksAllowed (true), + numInputChannelInfos (0), + numOutputChannelInfos (0), + inputLatency (0), + outputLatency (0), + callback (0), + inputDevice (0), + isSlaveDevice (false) + { + sampleRate = 0; + bufferSize = 512; + + if (deviceID == 0) + { + error = TRANS("can't open device"); + } + else + { + updateDetailsFromDevice(); + + AudioDeviceAddPropertyListener (deviceID, + kAudioPropertyWildcardChannel, + kAudioPropertyWildcardSection, + kAudioPropertyWildcardPropertyID, + deviceListenerProc, this); + } + } + + ~CoreAudioInternal() + { + AudioDeviceRemovePropertyListener (deviceID, + kAudioPropertyWildcardChannel, + kAudioPropertyWildcardSection, + kAudioPropertyWildcardPropertyID, + deviceListenerProc); + + stop (false); + + juce_free (audioBuffer); + delete inputDevice; + } + + void setTempBufferSize (const int numChannels, const int numSamples) + { + juce_free (audioBuffer); + + audioBuffer = (float*) juce_calloc (32 + numChannels * numSamples * sizeof (float)); + + zeromem (tempInputBuffers, sizeof (tempInputBuffers)); + zeromem (tempOutputBuffers, sizeof (tempOutputBuffers)); + + int count = 0; + int i; + for (i = maxNumChans; --i >= 0;) + if (activeInputChans[i]) + tempInputBuffers[i] = audioBuffer + count++ * numSamples; + + for (i = maxNumChans; --i >= 0;) + if (activeOutputChans[i]) + tempOutputBuffers[i] = audioBuffer + count++ * numSamples; + } + + // returns the number of actual available channels + void fillInChannelInfo (bool input) + { + int chanNum = 0, activeChans = 0; + UInt32 size; + + if (OK (AudioDeviceGetPropertyInfo (deviceID, 0, input, kAudioDevicePropertyStreamConfiguration, &size, 0))) + { + AudioBufferList* const bufList = (AudioBufferList*) juce_calloc (size); + + if (OK (AudioDeviceGetProperty (deviceID, 0, input, kAudioDevicePropertyStreamConfiguration, &size, bufList))) + { + const int numStreams = bufList->mNumberBuffers; + + for (int i = 0; i < numStreams; ++i) + { + const AudioBuffer& b = bufList->mBuffers[i]; + + for (unsigned int j = 0; j < b.mNumberChannels; ++j) + { + if (input) + { + if (activeInputChans[chanNum]) + { + inputChannelInfo [activeChans].sourceChannelNum = chanNum; + inputChannelInfo [activeChans].streamNum = i; + inputChannelInfo [activeChans].dataOffsetSamples = j; + inputChannelInfo [activeChans].dataStrideSamples = b.mNumberChannels; + ++activeChans; + numInputChannelInfos = activeChans; + } + + inChanNames.add (T("input ") + String (chanNum + 1)); + } + else + { + if (activeOutputChans[chanNum]) + { + outputChannelInfo [activeChans].sourceChannelNum = chanNum; + outputChannelInfo [activeChans].streamNum = i; + outputChannelInfo [activeChans].dataOffsetSamples = j; + outputChannelInfo [activeChans].dataStrideSamples = b.mNumberChannels; + ++activeChans; + numOutputChannelInfos = activeChans; + } + + outChanNames.add (T("output ") + String (chanNum + 1)); + } + + ++chanNum; + } + } + } + + juce_free (bufList); + } + } + + void updateDetailsFromDevice() + { + stopTimer(); + + if (deviceID == 0) + return; + + const ScopedLock sl (callbackLock); + + Float64 sr; + UInt32 size = sizeof (Float64); + if (OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyNominalSampleRate, &size, &sr))) + sampleRate = sr; + + UInt32 framesPerBuf; + size = sizeof (framesPerBuf); + + if (OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyBufferFrameSize, &size, &framesPerBuf))) + { + bufferSize = framesPerBuf; + + if (bufferSize > 0) + setTempBufferSize (numInputChans + numOutputChans, bufferSize); + } + + bufferSizes.clear(); + + if (OK (AudioDeviceGetPropertyInfo (deviceID, 0, false, kAudioDevicePropertyBufferFrameSizeRange, &size, 0))) + { + AudioValueRange* ranges = (AudioValueRange*) juce_calloc (size); + + if (OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyBufferFrameSizeRange, &size, ranges))) + { + bufferSizes.add ((int) ranges[0].mMinimum); + + for (int i = 32; i < 8192; i += 32) + { + for (int j = size / sizeof (AudioValueRange); --j >= 0;) + { + if (i >= ranges[j].mMinimum && i <= ranges[j].mMaximum) + { + bufferSizes.addIfNotAlreadyThere (i); + break; + } + } + } + + if (bufferSize > 0) + bufferSizes.addIfNotAlreadyThere (bufferSize); + } + + juce_free (ranges); + } + + if (bufferSizes.size() == 0 && bufferSize > 0) + bufferSizes.add (bufferSize); + + sampleRates.clear(); + const double possibleRates[] = { 44100.0, 48000.0, 88200.0, 96000.0, 176400.0, 192000.0 }; + String rates; + + if (OK (AudioDeviceGetPropertyInfo (deviceID, 0, false, kAudioDevicePropertyAvailableNominalSampleRates, &size, 0))) + { + AudioValueRange* ranges = (AudioValueRange*) juce_calloc (size); + + if (OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyAvailableNominalSampleRates, &size, ranges))) + { + for (int i = 0; i < numElementsInArray (possibleRates); ++i) + { + bool ok = false; + + for (int j = size / sizeof (AudioValueRange); --j >= 0;) + if (possibleRates[i] >= ranges[j].mMinimum - 2 && possibleRates[i] <= ranges[j].mMaximum + 2) + ok = true; + + if (ok) + { + sampleRates.add (possibleRates[i]); + rates << possibleRates[i] << T(" "); + } + } + } + + juce_free (ranges); + } + + if (sampleRates.size() == 0 && sampleRate > 0) + { + sampleRates.add (sampleRate); + rates << sampleRate; + } + + log (T("sr: ") + rates); + + inputLatency = 0; + outputLatency = 0; + UInt32 lat; + size = sizeof (UInt32); + if (AudioDeviceGetProperty (deviceID, 0, true, kAudioDevicePropertyLatency, &size, &lat) == noErr) + inputLatency = (int) lat; + + if (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyLatency, &size, &lat) == noErr) + outputLatency = (int) lat; + + log (T("lat: ") + String (inputLatency) + T(" ") + String (outputLatency)); + + inChanNames.clear(); + outChanNames.clear(); + + zeromem (inputChannelInfo, sizeof (inputChannelInfo)); + zeromem (outputChannelInfo, sizeof (outputChannelInfo)); + + fillInChannelInfo (true); + fillInChannelInfo (false); + } + + //============================================================================== + const StringArray getSources (bool input) + { + StringArray s; + int num = 0; + OSType* types = getAllDataSourcesForDevice (deviceID, input, num); + + if (types != 0) + { + for (int i = 0; i < num; ++i) + { + AudioValueTranslation avt; + char buffer[256]; + + avt.mInputData = (void*) &(types[i]); + avt.mInputDataSize = sizeof (UInt32); + avt.mOutputData = buffer; + avt.mOutputDataSize = 256; + + UInt32 transSize = sizeof (avt); + if (OK (AudioDeviceGetProperty (deviceID, 0, input, kAudioDevicePropertyDataSourceNameForID, &transSize, &avt))) + { + DBG (buffer); + s.add (buffer); + } + } + + juce_free (types); + } + + return s; + } + + int getCurrentSourceIndex (bool input) const + { + OSType currentSourceID = 0; + UInt32 size = 0; + int result = -1; + + if (deviceID != 0 + && OK (AudioDeviceGetPropertyInfo (deviceID, 0, input, kAudioDevicePropertyDataSource, &size, 0))) + { + if (OK (AudioDeviceGetProperty (deviceID, 0, input, kAudioDevicePropertyDataSource, &size, ¤tSourceID))) + { + int num = 0; + OSType* const types = getAllDataSourcesForDevice (deviceID, input, num); + + if (types != 0) + { + for (int i = 0; i < num; ++i) + { + if (types[num] == currentSourceID) + { + result = i; + break; + } + } + + juce_free (types); + } + } + } + + return result; + } + + void setCurrentSourceIndex (int index, bool input) + { + if (deviceID != 0) + { + int num = 0; + OSType* types = getAllDataSourcesForDevice (deviceID, input, num); + + if (types != 0) + { + if (index >= 0 && index < num) + { + OSType id = types[index]; + AudioDeviceSetProperty (deviceID, 0, 0, input, kAudioDevicePropertyDataSource, sizeof (id), &id); + } + + juce_free (types); + } + } + } + + //============================================================================== + const String reopen (const BitArray& inputChannels, + const BitArray& outputChannels, + double newSampleRate, + int bufferSizeSamples) + { + error = String::empty; + log ("CoreAudio reopen"); + callbacksAllowed = false; + stopTimer(); + + stop (false); + + activeInputChans = inputChannels; + activeOutputChans = outputChannels; + numInputChans = inputChannels.countNumberOfSetBits(); + numOutputChans = outputChannels.countNumberOfSetBits(); + + // set sample rate + Float64 sr = newSampleRate; + UInt32 size = sizeof (sr); + OK (AudioDeviceSetProperty (deviceID, 0, 0, false, kAudioDevicePropertyNominalSampleRate, size, &sr)); + OK (AudioDeviceSetProperty (deviceID, 0, 0, true, kAudioDevicePropertyNominalSampleRate, size, &sr)); + + // change buffer size + UInt32 framesPerBuf = bufferSizeSamples; + size = sizeof (framesPerBuf); + + OK (AudioDeviceSetProperty (deviceID, 0, 0, false, kAudioDevicePropertyBufferFrameSize, size, &framesPerBuf)); + OK (AudioDeviceSetProperty (deviceID, 0, 0, true, kAudioDevicePropertyBufferFrameSize, size, &framesPerBuf)); + + // wait for the changes to happen (on some devices) + int i = 30; + while (--i >= 0) + { + updateDetailsFromDevice(); + + if (sampleRate == newSampleRate && bufferSizeSamples == bufferSize) + break; + + Thread::sleep (100); + } + + if (i < 0) + error = "Couldn't change sample rate/buffer size"; + + if (sampleRates.size() == 0) + error = "Device has no available sample-rates"; + + if (bufferSizes.size() == 0) + error = "Device has no available buffer-sizes"; + + numInputChans = jmin (numInputChans, numInputChannelInfos); + numOutputChans = jmin (numOutputChans, numOutputChannelInfos); + + activeInputChans.setRange (inChanNames.size(), + activeInputChans.getHighestBit() + 1 - inChanNames.size(), + false); + + activeOutputChans.setRange (outChanNames.size(), + activeOutputChans.getHighestBit() + 1 - outChanNames.size(), + false); + + if (inputDevice != 0 && error.isEmpty()) + error = inputDevice->reopen (inputChannels, + outputChannels, + newSampleRate, + bufferSizeSamples); + + callbacksAllowed = true; + + return error; + } + + bool start (AudioIODeviceCallback* cb) + { + if (! started) + { + callback = 0; + + if (deviceID != 0) + { + if (OK (AudioDeviceAddIOProc (deviceID, audioIOProc, (void*) this))) + { + if (OK (AudioDeviceStart (deviceID, audioIOProc))) + { + started = true; + } + else + { + OK (AudioDeviceRemoveIOProc (deviceID, audioIOProc)); + } + } + } + } + + if (started) + { + const ScopedLock sl (callbackLock); + callback = cb; + } + + if (inputDevice != 0) + return started && inputDevice->start (cb); + else + return started; + } + + void stop (bool leaveInterruptRunning) + { + callbackLock.enter(); + callback = 0; + callbackLock.exit(); + + if (started + && (deviceID != 0) + && ! leaveInterruptRunning) + { + OK (AudioDeviceStop (deviceID, audioIOProc)); + OK (AudioDeviceRemoveIOProc (deviceID, audioIOProc)); + started = false; + + callbackLock.enter(); + callbackLock.exit(); + + // wait until it's definately stopped calling back.. + for (int i = 40; --i >= 0;) + { + Thread::sleep (50); + + UInt32 running = 0; + UInt32 size = sizeof (running); + OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyDeviceIsRunning, &size, &running)); + if (running == 0) + break; + } + + callbackLock.enter(); + callbackLock.exit(); + } + + if (inputDevice != 0) + inputDevice->stop (leaveInterruptRunning); + } + + double getSampleRate() const + { + return sampleRate; + } + + int getBufferSize() const + { + return bufferSize; + } + + void audioCallback (const AudioBufferList* inInputData, + AudioBufferList* outOutputData) + { + int i; + const ScopedLock sl (callbackLock); + + if (callback != 0) + { + if (inputDevice == 0) + { + for (i = numInputChans; --i >= 0;) + { + const CallbackDetailsForChannel& info = inputChannelInfo[i]; + float* dest = tempInputBuffers [info.sourceChannelNum]; + const float* src = ((const float*) inInputData->mBuffers[info.streamNum].mData) + + info.dataOffsetSamples; + const int stride = info.dataStrideSamples; + + if (stride != 0) // if this is zero, info is invalid + { + for (int j = bufferSize; --j >= 0;) + { + *dest++ = *src; + src += stride; + } + } + } + } + + if (! isSlaveDevice) + { + if (inputDevice == 0) + { + callback->audioDeviceIOCallback ((const float**) tempInputBuffers, + numInputChans, + tempOutputBuffers, + numOutputChans, + bufferSize); + } + else + { + jassert (inputDevice->bufferSize == bufferSize); + + callback->audioDeviceIOCallback ((const float**) inputDevice->tempInputBuffers, + inputDevice->numInputChans, + tempOutputBuffers, + numOutputChans, + bufferSize); + } + + for (i = numOutputChans; --i >= 0;) + { + const CallbackDetailsForChannel& info = outputChannelInfo[i]; + const float* src = tempOutputBuffers [info.sourceChannelNum]; + float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) + + info.dataOffsetSamples; + const int stride = info.dataStrideSamples; + + if (stride != 0) // if this is zero, info is invalid + { + for (int j = bufferSize; --j >= 0;) + { + *dest = *src++; + dest += stride; + } + } + } + } + } + else + { + for (i = jmin (numOutputChans, numOutputChannelInfos); --i >= 0;) + { + const CallbackDetailsForChannel& info = outputChannelInfo[i]; + float* dest = ((float*) outOutputData->mBuffers[info.streamNum].mData) + + info.dataOffsetSamples; + const int stride = info.dataStrideSamples; + + if (stride != 0) // if this is zero, info is invalid + { + for (int j = bufferSize; --j >= 0;) + { + *dest = 0.0f; + dest += stride; + } + } + } + } + } + + // called by callbacks + void deviceDetailsChanged() + { + if (callbacksAllowed) + startTimer (100); + } + + void timerCallback() + { + stopTimer(); + log ("CoreAudio device changed callback"); + + const double oldSampleRate = sampleRate; + const int oldBufferSize = bufferSize; + updateDetailsFromDevice(); + + if (oldBufferSize != bufferSize || oldSampleRate != sampleRate) + { + callbacksAllowed = false; + stop (false); + updateDetailsFromDevice(); + callbacksAllowed = true; + } + } + + CoreAudioInternal* getRelatedDevice() const + { + UInt32 size = 0; + CoreAudioInternal* result = 0; + + if (deviceID != 0 + && AudioDeviceGetPropertyInfo (deviceID, 0, false, kAudioDevicePropertyRelatedDevices, &size, 0) == noErr + && size > 0) + { + AudioDeviceID* devs = (AudioDeviceID*) juce_calloc (size); + + if (OK (AudioDeviceGetProperty (deviceID, 0, false, kAudioDevicePropertyRelatedDevices, &size, devs))) + { + for (unsigned int i = 0; i < size / sizeof (AudioDeviceID); ++i) + { + if (devs[i] != deviceID && devs[i] != 0) + { + result = new CoreAudioInternal (devs[i]); + + if (result->error.isEmpty()) + { + const bool thisIsInput = inChanNames.size() > 0 && outChanNames.size() == 0; + const bool otherIsInput = result->inChanNames.size() > 0 && result->outChanNames.size() == 0; + + if (thisIsInput != otherIsInput) + break; + } + + deleteAndZero (result); + } + } + } + + juce_free (devs); + } + + return result; + } + + //============================================================================== + juce_UseDebuggingNewOperator + + String error; + int inputLatency, outputLatency; + BitArray activeInputChans, activeOutputChans; + StringArray inChanNames, outChanNames; + Array sampleRates; + Array bufferSizes; + AudioIODeviceCallback* callback; + + CoreAudioInternal* inputDevice; + bool isSlaveDevice; + +private: + CriticalSection callbackLock; + AudioDeviceID deviceID; + bool started; + double sampleRate; + int bufferSize; + float* audioBuffer; + int numInputChans, numOutputChans; + bool callbacksAllowed; + + struct CallbackDetailsForChannel + { + int sourceChannelNum; + int streamNum; + int dataOffsetSamples; + int dataStrideSamples; + }; + + int numInputChannelInfos, numOutputChannelInfos; + CallbackDetailsForChannel inputChannelInfo [maxNumChans]; + CallbackDetailsForChannel outputChannelInfo [maxNumChans]; + float* tempInputBuffers [maxNumChans]; + float* tempOutputBuffers [maxNumChans]; + + CoreAudioInternal (const CoreAudioInternal&); + const CoreAudioInternal& operator= (const CoreAudioInternal&); + + //============================================================================== + static OSStatus audioIOProc (AudioDeviceID inDevice, + const AudioTimeStamp* inNow, + const AudioBufferList* inInputData, + const AudioTimeStamp* inInputTime, + AudioBufferList* outOutputData, + const AudioTimeStamp* inOutputTime, + void* device) + { + ((CoreAudioInternal*) device)->audioCallback (inInputData, outOutputData); + return noErr; + } + + static OSStatus deviceListenerProc (AudioDeviceID inDevice, + UInt32 inLine, + Boolean isInput, + AudioDevicePropertyID inPropertyID, + void* inClientData) + { + CoreAudioInternal* const intern = (CoreAudioInternal*) inClientData; + + switch (inPropertyID) + { + case kAudioDevicePropertyBufferSize: + case kAudioDevicePropertyBufferFrameSize: + case kAudioDevicePropertyNominalSampleRate: + case kAudioDevicePropertyStreamFormat: + case kAudioDevicePropertyDeviceIsAlive: + intern->deviceDetailsChanged(); + break; + + case kAudioDevicePropertyBufferSizeRange: + case kAudioDevicePropertyVolumeScalar: + case kAudioDevicePropertyMute: + case kAudioDevicePropertyPlayThru: + case kAudioDevicePropertyDataSource: + case kAudioDevicePropertyDeviceIsRunning: + break; + } + + return noErr; + } + + //============================================================================== + static OSType* getAllDataSourcesForDevice (AudioDeviceID deviceID, const bool input, int& num) + { + OSType* types = 0; + UInt32 size = 0; + num = 0; + + if (deviceID != 0 + && OK (AudioDeviceGetPropertyInfo (deviceID, 0, input, kAudioDevicePropertyDataSources, &size, 0))) + { + types = (OSType*) juce_calloc (size); + + if (OK (AudioDeviceGetProperty (deviceID, 0, input, kAudioDevicePropertyDataSources, &size, types))) + { + num = size / sizeof (OSType); + } + else + { + juce_free (types); + types = 0; + } + } + + return types; + } +}; + + +//============================================================================== +class CoreAudioIODevice : public AudioIODevice +{ +public: + CoreAudioIODevice (const String& deviceName, + AudioDeviceID deviceId1) + : AudioIODevice (deviceName, "CoreAudio"), + isOpen_ (false), + isStarted (false) + { + internal = 0; + + CoreAudioInternal* device = new CoreAudioInternal (deviceId1); + lastError = device->error; + + if (lastError.isNotEmpty()) + { + deleteAndZero (device); + } + else + { + CoreAudioInternal* secondDevice = device->getRelatedDevice(); + + if (secondDevice != 0) + { + if (device->inChanNames.size() > secondDevice->inChanNames.size()) + swapVariables (device, secondDevice); + + device->inputDevice = secondDevice; + secondDevice->isSlaveDevice = true; + } + } + + internal = device; + + AudioHardwareAddPropertyListener (kAudioPropertyWildcardPropertyID, + hardwareListenerProc, internal); + } + + ~CoreAudioIODevice() + { + AudioHardwareRemovePropertyListener (kAudioPropertyWildcardPropertyID, + hardwareListenerProc); + + delete internal; + } + + const StringArray getOutputChannelNames() + { + return internal->outChanNames; + } + + const StringArray getInputChannelNames() + { + if (internal->inputDevice != 0) + return internal->inputDevice->inChanNames; + else + return internal->inChanNames; + } + + int getNumSampleRates() + { + return internal->sampleRates.size(); + } + + double getSampleRate (int index) + { + return internal->sampleRates [index]; + } + + int getNumBufferSizesAvailable() + { + return internal->bufferSizes.size(); + } + + int getBufferSizeSamples (int index) + { + return internal->bufferSizes [index]; + } + + int getDefaultBufferSize() + { + for (int i = 0; i < getNumBufferSizesAvailable(); ++i) + if (getBufferSizeSamples(i) >= 512) + return getBufferSizeSamples(i); + + return 512; + } + + const String open (const BitArray& inputChannels, + const BitArray& outputChannels, + double sampleRate, + int bufferSizeSamples) + { + isOpen_ = true; + + if (bufferSizeSamples <= 0) + bufferSizeSamples = getDefaultBufferSize(); + + internal->reopen (inputChannels, outputChannels, sampleRate, bufferSizeSamples); + lastError = internal->error; + return lastError; + } + + void close() + { + isOpen_ = false; + } + + bool isOpen() + { + return isOpen_; + } + + int getCurrentBufferSizeSamples() + { + return internal != 0 ? internal->getBufferSize() : 512; + } + + double getCurrentSampleRate() + { + return internal != 0 ? internal->getSampleRate() : 0; + } + + int getCurrentBitDepth() + { + return 32; // no way to find out, so just assume it's high.. + } + + const BitArray getActiveOutputChannels() const + { + return internal != 0 ? internal->activeOutputChans : BitArray(); + } + + const BitArray getActiveInputChannels() const + { + BitArray chans; + + if (internal != 0) + { + chans = internal->activeInputChans; + + if (internal->inputDevice != 0) + chans.orWith (internal->inputDevice->activeInputChans); + } + + return chans; + } + + int getOutputLatencyInSamples() + { + if (internal == 0) + return 0; + + return internal->outputLatency; + } + + int getInputLatencyInSamples() + { + if (internal == 0) + return 0; + + return internal->inputLatency; + } + + void start (AudioIODeviceCallback* callback) + { + if (internal != 0 && ! isStarted) + { + if (callback != 0) + callback->audioDeviceAboutToStart (getCurrentSampleRate(), + getCurrentBufferSizeSamples()); + + isStarted = true; + internal->start (callback); + } + } + + void stop() + { + if (isStarted && internal != 0) + { + AudioIODeviceCallback* const lastCallback = internal->callback; + + isStarted = false; + internal->stop (true); + + if (lastCallback != 0) + lastCallback->audioDeviceStopped(); + } + } + + bool isPlaying() + { + if (internal->callback == 0) + isStarted = false; + + return isStarted; + } + + const String getLastError() + { + return lastError; + } + + juce_UseDebuggingNewOperator + +private: + CoreAudioInternal* internal; + bool isOpen_, isStarted; + String lastError; + + static OSStatus hardwareListenerProc (AudioHardwarePropertyID inPropertyID, void* inClientData) + { + CoreAudioInternal* const intern = (CoreAudioInternal*) inClientData; + + switch (inPropertyID) + { + case kAudioHardwarePropertyDevices: + intern->deviceDetailsChanged(); + break; + + case kAudioHardwarePropertyDefaultOutputDevice: + case kAudioHardwarePropertyDefaultInputDevice: + case kAudioHardwarePropertyDefaultSystemOutputDevice: + break; + } + + return noErr; + } + + CoreAudioIODevice (const CoreAudioIODevice&); + const CoreAudioIODevice& operator= (const CoreAudioIODevice&); +}; + + +//============================================================================== +class CoreAudioIODeviceType : public AudioIODeviceType +{ +public: + //============================================================================== + CoreAudioIODeviceType() + : AudioIODeviceType (T("CoreAudio")), + hasScanned (false) + { + } + + ~CoreAudioIODeviceType() + { + } + + //============================================================================== + void scanForDevices() + { + hasScanned = true; + + names.clear(); + ids.clear(); + + UInt32 size; + if (OK (AudioHardwareGetPropertyInfo (kAudioHardwarePropertyDevices, &size, 0))) + { + AudioDeviceID* const devs = (AudioDeviceID*) juce_calloc (size); + + if (OK (AudioHardwareGetProperty (kAudioHardwarePropertyDevices, &size, devs))) + { + static bool alreadyLogged = false; + const int num = size / sizeof (AudioDeviceID); + for (int i = 0; i < num; ++i) + { + char name[1024]; + size = sizeof (name); + if (OK (AudioDeviceGetProperty (devs[i], 0, false, kAudioDevicePropertyDeviceName, &size, name))) + { + const String nameString (String::fromUTF8 ((const uint8*) name, strlen (name))); + + if (! alreadyLogged) + log (T("CoreAudio device: ") + nameString); + + names.add (nameString); + ids.add (devs[i]); + } + } + + alreadyLogged = true; + } + + juce_free (devs); + } + } + + const StringArray getDeviceNames (const bool /*preferInputNames*/) const + { + jassert (hasScanned); // need to call scanForDevices() before doing this + + StringArray namesCopy (names); + namesCopy.removeDuplicates (true); + + return namesCopy; + } + + const String getDefaultDeviceName (const bool preferInputNames) const + { + jassert (hasScanned); // need to call scanForDevices() before doing this + + String result (names[0]); + + AudioDeviceID deviceID; + UInt32 size = sizeof (deviceID); + + if (AudioHardwareGetProperty (preferInputNames ? kAudioHardwarePropertyDefaultInputDevice + : kAudioHardwarePropertyDefaultOutputDevice, + &size, &deviceID) == noErr) + { + for (int i = ids.size(); --i >= 0;) + if (ids[i] == deviceID) + result = names[i]; + } + + return result; + } + + AudioIODevice* createDevice (const String& deviceName) + { + jassert (hasScanned); // need to call scanForDevices() before doing this + + const int index = names.indexOf (deviceName); + + if (index >= 0) + return new CoreAudioIODevice (deviceName, ids [index]); + + return 0; + } + + //============================================================================== + juce_UseDebuggingNewOperator + +private: + StringArray names; + Array ids; + + bool hasScanned; + + CoreAudioIODeviceType (const CoreAudioIODeviceType&); + const CoreAudioIODeviceType& operator= (const CoreAudioIODeviceType&); +}; + +//============================================================================== +AudioIODeviceType* juce_createDefaultAudioIODeviceType() +{ + return new CoreAudioIODeviceType(); +} + + +END_JUCE_NAMESPACE diff --git a/build/win32/platform_specific_code/juce_win32_ASIO.cpp b/build/win32/platform_specific_code/juce_win32_ASIO.cpp index 38a55f4848..f1192c3628 100644 --- a/build/win32/platform_specific_code/juce_win32_ASIO.cpp +++ b/build/win32/platform_specific_code/juce_win32_ASIO.cpp @@ -311,8 +311,8 @@ public: int sampleRate = roundDoubleToInt (sr); currentSampleRate = sampleRate; currentBlockSizeSamples = bufferSizeSamples; - currentChansOut = outputChannels; - currentChansIn = inputChannels; + currentChansOut.clear(); + currentChansIn.clear(); updateSampleRates(); @@ -432,6 +432,7 @@ public: { if (inputChannels[i]) { + currentChansIn.setBit (i); info->isInput = 1; info->channelNum = i; info->buffers[0] = info->buffers[1] = 0; @@ -444,6 +445,7 @@ public: { if (outputChannels[i]) { + currentChansOut.setBit (i); info->isInput = 0; info->channelNum = i; info->buffers[0] = info->buffers[1] = 0; @@ -732,6 +734,16 @@ public: return currentSampleRate; } + const BitArray getActiveOutputChannels() const + { + return currentChansOut; + } + + const BitArray getActiveInputChannels() const + { + return currentChansIn; + } + int getCurrentBitDepth() { return currentBitDepth; diff --git a/build/win32/platform_specific_code/juce_win32_DirectSound.cpp b/build/win32/platform_specific_code/juce_win32_DirectSound.cpp index fe7d8f995b..0f0123303d 100644 --- a/build/win32/platform_specific_code/juce_win32_DirectSound.cpp +++ b/build/win32/platform_specific_code/juce_win32_DirectSound.cpp @@ -1131,6 +1131,16 @@ public: return bits; } + const BitArray getActiveOutputChannels() const + { + return enabledOutputs; + } + + const BitArray getActiveInputChannels() const + { + return enabledInputs; + } + int getOutputLatencyInSamples() { return (int) (getCurrentBufferSizeSamples() * 1.5); @@ -1205,6 +1215,7 @@ private: int volatile totalSamplesOut; int64 volatile lastBlockTime; double sampleRate; + BitArray enabledInputs, enabledOutputs; float** inputBuffers; float** outputBuffers; @@ -1629,6 +1640,8 @@ const String DSoundAudioIODevice::openDevice (const BitArray& inputChannels, { closeDevice(); totalSamplesOut = 0; + enabledInputs.clear(); + enabledOutputs.clear(); sampleRate = sampleRate_; @@ -1649,16 +1662,28 @@ const String DSoundAudioIODevice::openDevice (const BitArray& inputChannels, int i; for (i = 0; i < numInputBuffers + 2; ++i) { - inputBuffers[i] = (inputChannels[i] && i < numInputBuffers) - ? (float*) juce_calloc ((bufferSizeSamples + 16) * sizeof (float)) - : 0; + if (inputChannels[i] && i < numInputBuffers) + { + inputBuffers[i] = (float*) juce_calloc ((bufferSizeSamples + 16) * sizeof (float)); + enabledInputs.setBit (i); + } + else + { + inputBuffers[i] = 0; + } } for (i = 0; i < numOutputBuffers + 2; ++i) { - outputBuffers[i] = (outputChannels[i] && i < numOutputBuffers) - ? (float*) juce_calloc ((bufferSizeSamples + 16) * sizeof (float)) - : 0; + if (outputChannels[i] && i < numOutputBuffers) + { + outputBuffers[i] = (float*) juce_calloc ((bufferSizeSamples + 16) * sizeof (float)); + enabledOutputs.setBit (i); + } + else + { + outputBuffers[i] = 0; + } } for (i = 0; i < numInputBuffers; ++i) diff --git a/docs/JUCE changelist.txt b/docs/JUCE changelist.txt index 57cbed9195..28fcf09b9d 100644 --- a/docs/JUCE changelist.txt +++ b/docs/JUCE changelist.txt @@ -7,6 +7,7 @@ Changelist for version 1.45 - big new project in the "extras" folder - a basic audio plugin host! Currently it loads VSTs on PC/Mac, and lets you put them together in a filter graph, which it plays. Hosting functionality is very basic at the moment, but I'm laying down a good architecture to hopefully build out into cross-platform plugin loading. +- audio plugins: I've simplified the processBlock() call in AudioFilterBase. It now just takes a single buffer for all input and output channels, and the accumulate parameter has gone. This will mean tweaking your plugin code, but the result will probably make it much less complex and less messy. - audio plugins: AudioFilterBase now requires a few extra methods to be implemented by your plugin: getInputChannelName, getOutputChannelName, isInputChannelStereoPair, isOutputChannelStereoPair. - new class: FileSearchPathListComponent, for letting the user edit a FileSearchPath. diff --git a/extras/audio plugin host/build/mac/PluginHost.xcodeproj/project.pbxproj b/extras/audio plugin host/build/mac/PluginHost.xcodeproj/project.pbxproj index f316a15b15..a9a4d6ff35 100644 --- a/extras/audio plugin host/build/mac/PluginHost.xcodeproj/project.pbxproj +++ b/extras/audio plugin host/build/mac/PluginHost.xcodeproj/project.pbxproj @@ -116,7 +116,9 @@ 20286C2AFDCF999611CA2CEA /* Sources */ = { isa = PBXGroup; children = ( - 84FFAE900C6C8A6F009F6E72 /* src */, + 84FFAE910C6C8A6F009F6E72 /* host */, + 84FFAE9B0C6C8A6F009F6E72 /* plugins */, + 84FFAE9A0C6C8A6F009F6E72 /* HostStartup.cpp */, ); name = Sources; sourceTree = ""; @@ -145,17 +147,6 @@ name = "External Frameworks and Libraries"; sourceTree = ""; }; - 84FFAE900C6C8A6F009F6E72 /* src */ = { - isa = PBXGroup; - children = ( - 84FFAE910C6C8A6F009F6E72 /* host */, - 84FFAE9A0C6C8A6F009F6E72 /* HostStartup.cpp */, - 84FFAE9B0C6C8A6F009F6E72 /* plugins */, - ); - name = src; - path = ../../src; - sourceTree = SOURCE_ROOT; - }; 84FFAE910C6C8A6F009F6E72 /* host */ = { isa = PBXGroup; children = ( @@ -175,6 +166,7 @@ 84FFAE9B0C6C8A6F009F6E72 /* plugins */ = { isa = PBXGroup; children = ( + 84FFAEA80C6C8A6F009F6E72 /* vst */, 84FFAE9C0C6C8A6F009F6E72 /* juce_AudioPluginFormat.cpp */, 84FFAE9D0C6C8A6F009F6E72 /* juce_AudioPluginFormat.h */, 84FFAE9E0C6C8A6F009F6E72 /* juce_AudioPluginInstance.cpp */, @@ -187,7 +179,6 @@ 84FFAEA50C6C8A6F009F6E72 /* juce_PluginDirectoryScanner.h */, 84FFAEA60C6C8A6F009F6E72 /* juce_PluginListComponent.cpp */, 84FFAEA70C6C8A6F009F6E72 /* juce_PluginListComponent.h */, - 84FFAEA80C6C8A6F009F6E72 /* vst */, ); name = plugins; path = ../../src/plugins; @@ -300,7 +291,6 @@ C0E91AC608A95435008D54AB /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = ppc; COPY_PHASE_STRIP = NO; }; name = Debug; @@ -308,7 +298,6 @@ C0E91AC708A95435008D54AB /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ARCHS = ppc; DEAD_CODE_STRIPPING = YES; INSTALL_PATH = "$(HOME)/Applications"; PRESERVE_DEAD_CODE_INITS_AND_TERMS = YES; diff --git a/extras/audio plugin host/src/HostStartup.cpp b/extras/audio plugin host/src/HostStartup.cpp index 2be3a8ed7b..9f589375dd 100644 --- a/extras/audio plugin host/src/HostStartup.cpp +++ b/extras/audio plugin host/src/HostStartup.cpp @@ -1,110 +1,110 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include "../../../juce.h" -#include "host/MainHostWindow.h" -#include "host/InternalFilters.h" - - -ApplicationCommandManager* commandManager = 0; - - -//============================================================================== -class PluginHostApp : public JUCEApplication -{ - MainHostWindow* mainWindow; - -public: - //============================================================================== - PluginHostApp() - : mainWindow (0) - { - } - - ~PluginHostApp() - { - } - - void initialise (const String& /*commandLine*/) - { - // initialise our settings file.. - ApplicationProperties::getInstance() - ->setStorageParameters (T("Juce Audio Plugin Host"), - T("settings"), String::empty, 1000, - PropertiesFile::storeAsXML); - - commandManager = new ApplicationCommandManager(); - - AudioPluginFormatManager::getInstance()->addDefaultFormats(); - AudioPluginFormatManager::getInstance()->addFormat (new InternalPluginFormat()); - - mainWindow = new MainHostWindow(); - - commandManager->registerAllCommandsForTarget (this); - commandManager->registerAllCommandsForTarget (mainWindow); - } - - void shutdown() - { - deleteAndZero (mainWindow); - - ApplicationProperties::getInstance()->closeFiles(); - - deleteAndZero (commandManager); - } - - const String getApplicationName() - { - return T("Juce Plug-In Host"); - } - - const String getApplicationVersion() - { - return T("0.9"); - } - - void systemRequestedQuit() - { - if (mainWindow->isValidComponent()) - mainWindow->tryToQuitApplication(); - else - JUCEApplication::quit(); - } - - bool moreThanOneInstanceAllowed() - { - return true; - } -}; - - -// This kicks the whole thing off.. -START_JUCE_APPLICATION (PluginHostApp) +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include "../../../juce.h" +#include "host/MainHostWindow.h" +#include "host/InternalFilters.h" + + +ApplicationCommandManager* commandManager = 0; + + +//============================================================================== +class PluginHostApp : public JUCEApplication +{ + MainHostWindow* mainWindow; + +public: + //============================================================================== + PluginHostApp() + : mainWindow (0) + { + } + + ~PluginHostApp() + { + } + + void initialise (const String& /*commandLine*/) + { + // initialise our settings file.. + ApplicationProperties::getInstance() + ->setStorageParameters (T("Juce Audio Plugin Host"), + T("settings"), String::empty, 1000, + PropertiesFile::storeAsXML); + + commandManager = new ApplicationCommandManager(); + + AudioPluginFormatManager::getInstance()->addDefaultFormats(); + AudioPluginFormatManager::getInstance()->addFormat (new InternalPluginFormat()); + + mainWindow = new MainHostWindow(); + + commandManager->registerAllCommandsForTarget (this); + commandManager->registerAllCommandsForTarget (mainWindow); + } + + void shutdown() + { + deleteAndZero (mainWindow); + + ApplicationProperties::getInstance()->closeFiles(); + + deleteAndZero (commandManager); + } + + const String getApplicationName() + { + return T("Juce Plug-In Host"); + } + + const String getApplicationVersion() + { + return T("0.9"); + } + + void systemRequestedQuit() + { + if (mainWindow->isValidComponent()) + mainWindow->tryToQuitApplication(); + else + JUCEApplication::quit(); + } + + bool moreThanOneInstanceAllowed() + { + return true; + } +}; + + +// This kicks the whole thing off.. +START_JUCE_APPLICATION (PluginHostApp) diff --git a/extras/audio plugin host/src/host/FilterGraph.cpp b/extras/audio plugin host/src/host/FilterGraph.cpp index 430643a517..9ba3e5dcd3 100644 --- a/extras/audio plugin host/src/host/FilterGraph.cpp +++ b/extras/audio plugin host/src/host/FilterGraph.cpp @@ -99,7 +99,6 @@ FilterInGraph::FilterInGraph (FilterGraph& owner_, AudioPluginInstance* const fi filter (filter_), uid (0), processedAudio (1, 1), - inputAudio (1, 1), activeUI (0) { lastX = 100 + Random::getSystemRandom().nextInt (400); @@ -137,8 +136,7 @@ void FilterInGraph::showUI() void FilterInGraph::prepareBuffers (int blockSize) { - processedAudio.setSize (jmax (1, filter->getNumOutputChannels()), blockSize); - inputAudio.setSize (jmax (1, filter->getNumInputChannels()), blockSize); + processedAudio.setSize (jmax (1, filter->getNumInputChannels(), filter->getNumOutputChannels()), blockSize); processedAudio.clear(); processedMidi.clear(); @@ -148,11 +146,9 @@ void FilterInGraph::renderBlock (int numSamples, const ReferenceCountedArray & filters, const OwnedArray & connections) { - processedAudio.setSize (jmax (1, filter->getNumOutputChannels()), numSamples); - inputAudio.setSize (jmax (1, filter->getNumInputChannels()), numSamples); + processedAudio.setSize (jmax (1, filter->getNumInputChannels(), filter->getNumOutputChannels()), numSamples); // this isn't particularly efficient - could do with some optimising here - inputAudio.clear(); processedAudio.clear(); processedMidi.clear(); @@ -174,8 +170,12 @@ void FilterInGraph::renderBlock (int numSamples, } else { - inputAudio.addFrom (fc->destChannel, 0, input->processedAudio, - fc->sourceChannel, 0, numSamples); + if (fc->destChannel < filter->getNumInputChannels() + && fc->sourceChannel < input->filter->getNumOutputChannels()) + { + processedAudio.addFrom (fc->destChannel, 0, input->processedAudio, + fc->sourceChannel, 0, numSamples); + } } break; @@ -184,7 +184,7 @@ void FilterInGraph::renderBlock (int numSamples, } } - filter->processBlock (inputAudio, processedAudio, true, processedMidi); + filter->processBlock (processedAudio, processedMidi); } XmlElement* FilterInGraph::createXml() const @@ -481,7 +481,7 @@ bool FilterGraph::canConnect (uint32 sourceFilterUID, int sourceFilterChannel, return true; } -void FilterGraph::addConnection (uint32 sourceFilterUID, int sourceChannel, +bool FilterGraph::addConnection (uint32 sourceFilterUID, int sourceChannel, uint32 destFilterUID, int destChannel) { if (canConnect (sourceFilterUID, sourceChannel, destFilterUID, destChannel)) @@ -495,7 +495,11 @@ void FilterGraph::addConnection (uint32 sourceFilterUID, int sourceChannel, connections.add (conn); changed(); + + return true; } + + return false; } void FilterGraph::removeConnection (const int index) @@ -507,6 +511,33 @@ void FilterGraph::removeConnection (const int index) } } +void FilterGraph::removeIllegalConnections() +{ + for (int i = connections.size(); --i >= 0;) + { + const FilterConnection* const fc = connections.getUnchecked(i); + + bool ok = true; + const FilterInGraph* const source = getFilterForUID (fc->sourceFilterID); + + if (source == 0 + || (fc->sourceChannel != midiChannelNumber && (fc->sourceChannel < 0 || fc->sourceChannel >= source->filter->getNumOutputChannels())) + || (fc->sourceChannel == midiChannelNumber && ! source->filter->producesMidi())) + ok = false; + + const FilterInGraph* const dest = getFilterForUID (fc->destFilterID); + + if (dest == 0 + || (fc->destChannel != midiChannelNumber && (fc->destChannel < 0 || fc->destChannel >= dest->filter->getNumInputChannels())) + || (fc->destChannel == midiChannelNumber && ! dest->filter->acceptsMidi())) + ok = false; + + + if (! ok) + removeConnection (i); + } +} + void FilterGraph::removeConnection (uint32 sourceFilterUID, int sourceFilterChannel, uint32 destFilterUID, int destFilterChannel) { @@ -575,11 +606,14 @@ void FilterGraph::restoreFromXml (const XmlElement& xml) (uint32) e->getIntAttribute (T("dstFilter")), e->getIntAttribute (T("dstChannel"))); } + + removeIllegalConnections(); } //============================================================================== -FilterGraphPlayer::FilterGraphPlayer() - : sampleRate (44100.0), +FilterGraphPlayer::FilterGraphPlayer (FilterGraph& graph_) + : graph (graph_), + sampleRate (44100.0), blockSize (512), deviceManager (0), inputChannelData (0), @@ -589,10 +623,12 @@ FilterGraphPlayer::FilterGraphPlayer() { setAudioDeviceManager (0); keyState.addListener (&messageCollector); + graph.addChangeListener (this); } FilterGraphPlayer::~FilterGraphPlayer() { + graph.removeChangeListener (this); keyState.removeListener (&messageCollector); } @@ -624,53 +660,43 @@ int FilterGraphPlayer::compareElements (FilterInGraph* const first, FilterInGrap return firstIsInputToSecond ? -1 : 1; } -void FilterGraphPlayer::updateFrom (FilterGraph* graphToUse) +void FilterGraphPlayer::update() { ReferenceCountedArray filtersBeingRemoved (filters); - if (graphToUse != 0) - { - ReferenceCountedArray newFilters (graphToUse->filters); - int i; - for (i = newFilters.size(); --i >= 0;) - if (filters.contains (newFilters.getUnchecked(i))) - newFilters.remove (i); - - for (i = filtersBeingRemoved.size(); --i >= 0;) - if (graphToUse->filters.contains (filtersBeingRemoved.getUnchecked(i))) - filtersBeingRemoved.remove (i); + ReferenceCountedArray newFilters (graph.filters); + int i; + for (i = newFilters.size(); --i >= 0;) + if (filters.contains (newFilters.getUnchecked(i))) + newFilters.remove (i); - // prepare any new filters for use.. - for (i = 0; i < newFilters.size(); ++i) - newFilters.getUnchecked(i)->filter->prepareToPlay (sampleRate, blockSize); + for (i = filtersBeingRemoved.size(); --i >= 0;) + if (graph.filters.contains (filtersBeingRemoved.getUnchecked(i))) + filtersBeingRemoved.remove (i); - ReferenceCountedArray sortedFilters (graphToUse->filters); - sortedFilters.sort (*this, true); + // prepare any new filters for use.. + for (i = 0; i < newFilters.size(); ++i) + newFilters.getUnchecked(i)->filter->prepareToPlay (sampleRate, blockSize); - for (i = sortedFilters.size(); --i >= 0;) - { - PlayerAwareFilter* const specialFilter = dynamic_cast (sortedFilters.getUnchecked(i)->filter); - - if (specialFilter != 0) - specialFilter->setPlayer (this); - } + ReferenceCountedArray sortedFilters (graph.filters); + sortedFilters.sort (*this, true); - { - const ScopedLock sl (processLock); - - filters = sortedFilters; - connections.clear(); + for (i = sortedFilters.size(); --i >= 0;) + { + PlayerAwareFilter* const specialFilter = dynamic_cast (sortedFilters.getUnchecked(i)->filter); - for (int i = 0; i < graphToUse->connections.size(); ++i) - connections.add (new FilterConnection (*graphToUse->connections.getUnchecked(i))); - } + if (specialFilter != 0) + specialFilter->setPlayer (this); } - else + { const ScopedLock sl (processLock); - filters.clear(); + filters = sortedFilters; connections.clear(); + + for (int i = 0; i < graph.connections.size(); ++i) + connections.add (new FilterConnection (*graph.connections.getUnchecked(i))); } // release any old ones.. @@ -685,6 +711,11 @@ void FilterGraphPlayer::updateFrom (FilterGraph* graphToUse) } } +void FilterGraphPlayer::changeListenerCallback (void*) +{ + update(); +} + void FilterGraphPlayer::audioDeviceIOCallback (const float** inputChannelData_, int totalNumInputChannels_, float** outputChannelData_, @@ -724,9 +755,11 @@ void FilterGraphPlayer::audioDeviceAboutToStart (double sampleRate_, int numSamp for (int i = 0; i < filters.size(); ++i) { - filters.getUnchecked(i)->prepareBuffers (blockSize); filters.getUnchecked(i)->filter->prepareToPlay (sampleRate, blockSize); + filters.getUnchecked(i)->prepareBuffers (blockSize); } + + graph.sendChangeMessage (&graph); } void FilterGraphPlayer::audioDeviceStopped() diff --git a/extras/audio plugin host/src/host/FilterGraph.h b/extras/audio plugin host/src/host/FilterGraph.h index fdc0b63af9..c732f8cfe8 100644 --- a/extras/audio plugin host/src/host/FilterGraph.h +++ b/extras/audio plugin host/src/host/FilterGraph.h @@ -114,7 +114,7 @@ private: int lastX, lastY; MidiBuffer outputMidi; - AudioSampleBuffer inputAudio, processedAudio; + AudioSampleBuffer processedAudio; MidiBuffer processedMidi; void prepareBuffers (int blockSize); @@ -149,6 +149,8 @@ public: void removeFilter (const uint32 filterUID); void disconnectFilter (const uint32 filterUID); + void removeIllegalConnections(); + //============================================================================== int getNumConnections() const throw() { return connections.size(); } FilterConnection* getConnection (const int index) const throw() { return connections [index]; } @@ -161,7 +163,7 @@ public: bool canConnect (uint32 sourceFilterUID, int sourceFilterChannel, uint32 destFilterUID, int destFilterChannel) const throw(); - void addConnection (uint32 sourceFilterUID, int sourceFilterChannel, + bool addConnection (uint32 sourceFilterUID, int sourceFilterChannel, uint32 destFilterUID, int destFilterChannel); void removeConnection (const int index); @@ -208,17 +210,16 @@ private: An object the */ class FilterGraphPlayer : public AudioIODeviceCallback, - public MidiInputCallback + public MidiInputCallback, + public ChangeListener { public: //============================================================================== - FilterGraphPlayer(); + FilterGraphPlayer (FilterGraph& graph); ~FilterGraphPlayer(); //============================================================================== - void updateFrom (FilterGraph* graphToUse); - void setAudioDeviceManager (AudioDeviceManager* dm); AudioDeviceManager* getAudioDeviceManager() const throw() { return deviceManager; } @@ -233,6 +234,8 @@ public: void handleIncomingMidiMessage (MidiInput* source, const MidiMessage& message); + void changeListenerCallback (void*); + //============================================================================== static int compareElements (FilterInGraph* const first, FilterInGraph* const second) throw(); @@ -253,6 +256,7 @@ public: }; private: + FilterGraph& graph; CriticalSection processLock; double sampleRate; int blockSize; @@ -261,6 +265,8 @@ private: ReferenceCountedArray filters; OwnedArray connections; + void update(); + FilterGraphPlayer (const FilterGraphPlayer&); const FilterGraphPlayer& operator= (const FilterGraphPlayer&); }; diff --git a/extras/audio plugin host/src/host/GraphEditorPanel.cpp b/extras/audio plugin host/src/host/GraphEditorPanel.cpp index 394d4e0ef2..210fc97e60 100644 --- a/extras/audio plugin host/src/host/GraphEditorPanel.cpp +++ b/extras/audio plugin host/src/host/GraphEditorPanel.cpp @@ -858,27 +858,22 @@ GraphDocumentComponent::GraphDocumentComponent (AudioDeviceManager* deviceManage { addAndMakeVisible (graphPanel = new GraphEditorPanel (graph)); - addAndMakeVisible (keyboardComp = new MidiKeyboardComponent (graphRenderer.keyState, - MidiKeyboardComponent::horizontalKeyboard)); + graphPlayer = new FilterGraphPlayer (graph); - graphRenderer.setAudioDeviceManager (deviceManager); - graphRenderer.updateFrom (&graph); + addAndMakeVisible (keyboardComp = new MidiKeyboardComponent (graphPlayer->keyState, + MidiKeyboardComponent::horizontalKeyboard)); - graph.addChangeListener (this); - deviceManager->addChangeListener (this); + graphPlayer->setAudioDeviceManager (deviceManager); graphPanel->updateComponents(); } GraphDocumentComponent::~GraphDocumentComponent() { - graph.removeChangeListener (this); - deviceManager->removeChangeListener (this); - deleteAllChildren(); - graphRenderer.updateFrom (0); - graphRenderer.setAudioDeviceManager (0); + graphPlayer->setAudioDeviceManager (0); + deleteAndZero (graphPlayer); graph.clear(); } @@ -894,8 +889,3 @@ void GraphDocumentComponent::createNewPlugin (const PluginDescription* desc, int { graphPanel->createNewPlugin (desc, x, y); } - -void GraphDocumentComponent::changeListenerCallback (void*) -{ - graphRenderer.updateFrom (&graph); -} diff --git a/extras/audio plugin host/src/host/GraphEditorPanel.h b/extras/audio plugin host/src/host/GraphEditorPanel.h index 098d163747..cd96b62a3d 100644 --- a/extras/audio plugin host/src/host/GraphEditorPanel.h +++ b/extras/audio plugin host/src/host/GraphEditorPanel.h @@ -89,8 +89,7 @@ private: It also manages the graph itself, and plays it. */ -class GraphDocumentComponent : public Component, - public ChangeListener +class GraphDocumentComponent : public Component { public: //============================================================================== @@ -105,14 +104,13 @@ public: //============================================================================== void resized(); - void changeListenerCallback (void*); //============================================================================== juce_UseDebuggingNewOperator private: AudioDeviceManager* deviceManager; - FilterGraphPlayer graphRenderer; + FilterGraphPlayer* graphPlayer; GraphEditorPanel* graphPanel; Component* keyboardComp; diff --git a/extras/audio plugin host/src/host/InternalFilters.cpp b/extras/audio plugin host/src/host/InternalFilters.cpp index 2fceb4d452..097e5debe1 100644 --- a/extras/audio plugin host/src/host/InternalFilters.cpp +++ b/extras/audio plugin host/src/host/InternalFilters.cpp @@ -49,10 +49,10 @@ public: void setPlayer (FilterGraphPlayer* p) { player = p; - rendererChanged(); + playerChanged(); } - virtual void rendererChanged() {} + virtual void playerChanged() {} const String getManufacturer() const { return "Raw Material Software"; } const String getFormatName() const { return "Internal"; } @@ -110,42 +110,36 @@ public: bool acceptsMidi() const { return false; } bool producesMidi() const { return false; } - void rendererChanged() + void playerChanged() { AudioIODevice* const dev = getAudioDevice(); if (dev != 0) - numOutputChannels = dev->getInputChannelNames().size(); + numOutputChannels = dev->getActiveInputChannels().countNumberOfSetBits(); } void JUCE_CALLTYPE prepareToPlay (double /*sampleRate*/, int /*estimatedSamplesPerBlock*/) { - rendererChanged(); + playerChanged(); } void JUCE_CALLTYPE releaseResources() { } - void JUCE_CALLTYPE processBlock (const AudioSampleBuffer& /*input*/, - AudioSampleBuffer& output, - const bool accumulateOutput, + void JUCE_CALLTYPE processBlock (AudioSampleBuffer& buffer, MidiBuffer&) { int n = 0; for (int i = 0; i < player->totalNumInputChannels; ++i) { - if (n >= output.getNumChannels()) + if (n >= getNumOutputChannels()) break; if (player->inputChannelData [i] != 0) { - if (accumulateOutput) - output.addFrom (n, 0, player->inputChannelData [i], output.getNumSamples()); - else - output.copyFrom (n, 0, player->inputChannelData [i], output.getNumSamples()); - + buffer.copyFrom (n, 0, player->inputChannelData [i], buffer.getNumSamples()); ++n; } } @@ -196,49 +190,42 @@ public: bool acceptsMidi() const { return false; } bool producesMidi() const { return false; } - void rendererChanged() + void playerChanged() { AudioIODevice* const dev = getAudioDevice(); if (dev != 0) - numInputChannels = dev->getOutputChannelNames().size(); + numInputChannels = dev->getActiveOutputChannels().countNumberOfSetBits(); } void JUCE_CALLTYPE prepareToPlay (double /*sampleRate*/, int /*estimatedSamplesPerBlock*/) { - rendererChanged(); + playerChanged(); } void JUCE_CALLTYPE releaseResources() { } - void JUCE_CALLTYPE processBlock (const AudioSampleBuffer& input, - AudioSampleBuffer&, - const bool accumulateOutput, + void JUCE_CALLTYPE processBlock (AudioSampleBuffer& buffer, MidiBuffer&) { int n = 0; for (int i = 0; i < player->totalNumOutputChannels; ++i) { - if (n >= input.getNumChannels()) - break; - - float* dst = player->outputChannelData [i]; + float* const dst = player->outputChannelData [i]; if (dst != 0) { - const float* src = input.getSampleData (n); - - if (accumulateOutput) + if (n >= getNumInputChannels()) { - for (int i = input.getNumSamples(); --i >= 0;) - *dst++ += *src++; + zeromem (dst, sizeof (float) * buffer.getNumSamples()); } else { - memcpy (dst, src, sizeof (float) * input.getNumSamples()); + memcpy (dst, buffer.getSampleData (n), + sizeof (float) * buffer.getNumSamples()); } ++n; @@ -290,7 +277,7 @@ public: bool acceptsMidi() const { return false; } bool producesMidi() const { return true; } - void rendererChanged() + void playerChanged() { if (player != 0) { @@ -302,22 +289,18 @@ public: void JUCE_CALLTYPE prepareToPlay (double /*sampleRate*/, int /*estimatedSamplesPerBlock*/) { - rendererChanged(); + playerChanged(); } void JUCE_CALLTYPE releaseResources() { } - void JUCE_CALLTYPE processBlock (const AudioSampleBuffer& input, - AudioSampleBuffer&, - const bool accumulateOutput, + void JUCE_CALLTYPE processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiBuffer) { - if (! accumulateOutput) - midiBuffer.clear(); - - midiBuffer.addEvents (player->incomingMidi, 0, input.getNumSamples(), 0); + midiBuffer.clear(); + midiBuffer.addEvents (player->incomingMidi, 0, buffer.getNumSamples(), 0); } const String JUCE_CALLTYPE getInputChannelName (const int channelIndex) const diff --git a/extras/audio plugin host/src/host/MainHostWindow.cpp b/extras/audio plugin host/src/host/MainHostWindow.cpp index 5e81ef9bd3..892676de3c 100644 --- a/extras/audio plugin host/src/host/MainHostWindow.cpp +++ b/extras/audio plugin host/src/host/MainHostWindow.cpp @@ -434,9 +434,9 @@ bool MainHostWindow::perform (const InvocationInfo& info) void MainHostWindow::showAudioSettings() { AudioDeviceSelectorComponent audioSettingsComp (deviceManager, - 0, 2, - 0, 2, - false); + 0, 256, + 0, 256, + true); audioSettingsComp.setSize (500, 300); @@ -449,11 +449,16 @@ void MainHostWindow::showAudioSettings() XmlElement* const audioState = deviceManager.createStateXml(); ApplicationProperties::getInstance()->getUserSettings() - ->setValue (T("audioSettings"), audioState); + ->setValue (T("audioDeviceState"), audioState); + + delete audioState; ApplicationProperties::getInstance()->getUserSettings()->saveIfNeeded(); - delete audioState; + GraphDocumentComponent* const graphEditor = getGraphEditor(); + + if (graphEditor != 0) + graphEditor->graph.removeIllegalConnections(); } bool MainHostWindow::filesDropped (const StringArray& files, int x, int y) diff --git a/extras/audio plugin host/src/plugins/juce_KnownPluginList.cpp b/extras/audio plugin host/src/plugins/juce_KnownPluginList.cpp index 69261db10a..b374d9e291 100644 --- a/extras/audio plugin host/src/plugins/juce_KnownPluginList.cpp +++ b/extras/audio plugin host/src/plugins/juce_KnownPluginList.cpp @@ -135,12 +135,11 @@ bool KnownPluginList::scanAndAddFile (const File& possiblePluginFile, return false; } - OwnedArray found; - for (int i = 0; i < AudioPluginFormatManager::getInstance()->getNumFormats(); ++i) { AudioPluginFormat* const format = AudioPluginFormatManager::getInstance()->getFormat (i); + OwnedArray found; format->findAllTypesForFile (found, possiblePluginFile); for (int i = 0; i < found.size(); ++i) @@ -165,7 +164,7 @@ void KnownPluginList::scanAndAddDragAndDroppedFiles (const StringArray& files, { const File f (files [i]); - if (! scanAndAddFile (f, false, typesFound)) + if (! scanAndAddFile (f, true, typesFound)) { if (f.isDirectory()) { diff --git a/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.cpp b/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.cpp index e805bce25a..0713ac0059 100644 --- a/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.cpp +++ b/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.cpp @@ -1,2540 +1,2533 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#ifdef _WIN32 - #define _WIN32_WINNT 0x500 - #define STRICT - #include - #include - #pragma warning (disable : 4312) -#else - #include -#endif - -#include "../../../../../juce.h" -#include "juce_VSTPluginInstance.h" -#include "../juce_PluginDescription.h" - -#if ! JUCE_WIN32 - #define _fpreset() - #define _clearfp() -#endif - -//============================================================================== -const int fxbVersionNum = 1; - -//============================================================================== -struct fxProgram -{ - long chunkMagic; // 'CcnK' - long byteSize; // of this chunk, excl. magic + byteSize - long fxMagic; // 'FxCk' - long version; - long fxID; // fx unique id - long fxVersion; - long numParams; - char prgName[28]; - float params[1]; // variable no. of parameters -}; - -struct fxSet -{ - long chunkMagic; // 'CcnK' - long byteSize; // of this chunk, excl. magic + byteSize - long fxMagic; // 'FxBk' - long version; - long fxID; // fx unique id - long fxVersion; - long numPrograms; - char future[128]; - fxProgram programs[1]; // variable no. of programs -}; - -struct fxChunkSet -{ - long chunkMagic; // 'CcnK' - long byteSize; // of this chunk, excl. magic + byteSize - long fxMagic; // 'FxCh', 'FPCh', or 'FBCh' - long version; - long fxID; // fx unique id - long fxVersion; - long numPrograms; - char future[128]; - long chunkSize; - char chunk[8]; // variable -}; - -struct fxProgramSet -{ - long chunkMagic; // 'CcnK' - long byteSize; // of this chunk, excl. magic + byteSize - long fxMagic; // 'FxCh', 'FPCh', or 'FBCh' - long version; - long fxID; // fx unique id - long fxVersion; - long numPrograms; - char name[28]; - long chunkSize; - char chunk[8]; // variable -}; - - -#ifdef JUCE_LITTLE_ENDIAN - static long swap (const long x) throw() { return (long) swapByteOrder ((uint32) x); } - - static float swapFloat (const float x) throw() - { - union { uint32 asInt; float asFloat; } n; - n.asFloat = x; - n.asInt = swapByteOrder (n.asInt); - return n.asFloat; - } -#else - #define swap(x) (x) - #define swapFloat(x) (x) -#endif - -//============================================================================== -typedef AEffect* (*MainCall) (audioMasterCallback); - -static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt); - -#if JUCE_PPC - static void* audioMasterCoerced = 0; -#endif - -static int shellUIDToCreate = 0; -static int insideVSTCallback = 0; - -static Array activeWindows; - -//============================================================================== -// Change this to disable logging of various VST activities -#ifndef VST_LOGGING - #define VST_LOGGING 1 -#endif - -#if VST_LOGGING - #define log(a) Logger::writeToLog(a); -#else - #define log(a) -#endif - -//============================================================================== -#if JUCE_MAC -namespace JUCE_NAMESPACE -{ - extern bool juce_isHIViewCreatedByJuce (HIViewRef view); - extern bool juce_isWindowCreatedByJuce (WindowRef window); -} - -#if JUCE_PPC -static void* NewCFMFromMachO (void* const machofp) throw() -{ - void* result = juce_malloc (8); - - ((void**) result)[0] = machofp; - ((void**) result)[1] = result; - - return result; -} -#endif -#endif - -//============================================================================== -static VoidArray activeModules; - -//============================================================================== -class ModuleHandle : public ReferenceCountedObject -{ -public: - //============================================================================== - File file; - MainCall moduleMain; - String pluginName; - - //============================================================================== - static ModuleHandle* findOrCreateModule (const File& file) - { - for (int i = activeModules.size(); --i >= 0;) - { - ModuleHandle* const module = (ModuleHandle*) activeModules.getUnchecked(i); - - if (module->file == file) - return module; - } - - _fpreset(); // (doesn't do any harm) - ++insideVSTCallback; - shellUIDToCreate = 0; - - log ("Attempting to load VST: " + file.getFullPathName()); - - ModuleHandle* m = new ModuleHandle (file); - - if (! m->open()) - deleteAndZero (m); - - --insideVSTCallback; - _fpreset(); // (doesn't do any harm) - - return m; - } - - //============================================================================== - ModuleHandle (const File& file_) - : file (file_), - moduleMain (0), -#if JUCE_WIN32 - hModule (0) -#else - fragId (0), - resHandle (0), - bundleRef (0), - resFileId (0) -#endif - { - activeModules.add (this); - -#if JUCE_WIN32 - fullParentDirectoryPathName = file_.getParentDirectory().getFullPathName(); -#else - PlatformUtilities::makeFSSpecFromPath (&parentDirFSSpec, file_.getParentDirectory().getFullPathName()); -#endif - } - - ~ModuleHandle() - { - activeModules.removeValue (this); - - close(); - } - - //============================================================================== - juce_UseDebuggingNewOperator - - //============================================================================== -#if JUCE_WIN32 - HMODULE hModule; - String fullParentDirectoryPathName; - - static HMODULE loadDLL (const TCHAR* filename) throw() - { - HMODULE h = 0; - - __try - { - h = LoadLibrary (filename); - } - __finally - { - } - - return h; - } - - bool open() - { - static bool timePeriodSet = false; - - if (! timePeriodSet) - { - timePeriodSet = true; - timeBeginPeriod (2); - } - - pluginName = file.getFileNameWithoutExtension(); - - hModule = loadDLL (file.getFullPathName()); - - if (hModule == 0) - return false; - - moduleMain = (MainCall) GetProcAddress (hModule, "VSTPluginMain"); - - if (moduleMain == 0) - moduleMain = (MainCall) GetProcAddress (hModule, "main"); - - return moduleMain != 0; - } - - void close() - { - if (hModule != 0) - { - try - { - _fpreset(); // (doesn't do any harm) - FreeLibrary (hModule); - } - catch (...) - {} - } - } - - void closeEffect (AEffect* eff) - { - eff->dispatcher (eff, effClose, 0, 0, 0, 0); - } - -#else - CFragConnectionID fragId; - Handle resHandle; - CFBundleRef bundleRef; - FSSpec parentDirFSSpec; - short resFileId; - - bool open() - { - bool ok = false; - const String filename (file.getFullPathName()); - - if (file.hasFileExtension (T(".vst"))) - { - CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) (const char*) filename, - filename.length(), file.isDirectory()); - - if (url != 0) - { - bundleRef = CFBundleCreate (kCFAllocatorDefault, url); - CFRelease (url); - - if (bundleRef != 0) - { - if (CFBundleLoadExecutable (bundleRef)) - { - moduleMain = (MainCall) CFBundleGetFunctionPointerForName (bundleRef, CFSTR("main_macho")); - - if (moduleMain == 0) - moduleMain = (MainCall) CFBundleGetFunctionPointerForName (bundleRef, CFSTR("VSTPluginMain")); - - if (moduleMain != 0) - { - CFTypeRef name = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleName")); - - if (name != 0) - { - if (CFGetTypeID (name) == CFStringGetTypeID()) - { - char buffer[1024]; - - if (CFStringGetCString ((CFStringRef) name, buffer, sizeof (buffer), CFStringGetSystemEncoding())) - pluginName = buffer; - } - } - - if (pluginName.isEmpty()) - pluginName = file.getFileNameWithoutExtension(); - - resFileId = CFBundleOpenBundleResourceMap (bundleRef); - - ok = true; - } - } - - if (! ok) - { - CFBundleUnloadExecutable (bundleRef); - CFRelease (bundleRef); - bundleRef = 0; - } - } - } - } -#if JUCE_PPC - else - { - FSRef fn; - - if (FSPathMakeRef ((UInt8*) (const char*) filename, &fn, 0) == noErr) - { - resFileId = FSOpenResFile (&fn, fsRdPerm); - - if (resFileId != -1) - { - const int numEffs = Count1Resources ('aEff'); - - for (int i = 0; i < numEffs; ++i) - { - resHandle = Get1IndResource ('aEff', i + 1); - - if (resHandle != 0) - { - OSType type; - Str255 name; - SInt16 id; - GetResInfo (resHandle, &id, &type, name); - pluginName = String ((const char*) name + 1, name[0]); - DetachResource (resHandle); - HLock (resHandle); - - Ptr ptr; - Str255 errorText; - - OSErr err = GetMemFragment (*resHandle, GetHandleSize (resHandle), - name, kPrivateCFragCopy, - &fragId, &ptr, errorText); - - if (err == noErr) - { - moduleMain = (MainCall) newMachOFromCFM (ptr); - ok = true; - } - else - { - HUnlock (resHandle); - } - - break; - } - } - - if (! ok) - CloseResFile (resFileId); - } - } - } -#endif - - return ok; - } - - void close() - { -#if JUCE_PPC - if (fragId != 0) - { - if (moduleMain != 0) - disposeMachOFromCFM ((void*) moduleMain); - - CloseConnection (&fragId); - HUnlock (resHandle); - - if (resFileId != 0) - CloseResFile (resFileId); - } - else -#endif - if (bundleRef != 0) - { - CFBundleCloseBundleResourceMap (bundleRef, resFileId); - - if (CFGetRetainCount (bundleRef) == 1) - CFBundleUnloadExecutable (bundleRef); - - if (CFGetRetainCount (bundleRef) > 0) - CFRelease (bundleRef); - } - } - - void closeEffect (AEffect* eff) - { -#if JUCE_PPC - if (fragId != 0) - { - VoidArray thingsToDelete; - thingsToDelete.add ((void*) eff->dispatcher); - thingsToDelete.add ((void*) eff->process); - thingsToDelete.add ((void*) eff->setParameter); - thingsToDelete.add ((void*) eff->getParameter); - thingsToDelete.add ((void*) eff->processReplacing); - - eff->dispatcher (eff, effClose, 0, 0, 0, 0); - - for (int i = thingsToDelete.size(); --i >= 0;) - disposeMachOFromCFM (thingsToDelete[i]); - } - else -#endif - { - eff->dispatcher (eff, effClose, 0, 0, 0, 0); - } - } - -#if JUCE_PPC - static void* newMachOFromCFM (void* cfmfp) - { - if (cfmfp == 0) - return 0; - - UInt32* const mfp = (UInt32*) juce_malloc (sizeof (UInt32) * 6); - - mfp[0] = 0x3d800000 | ((UInt32) cfmfp >> 16); - mfp[1] = 0x618c0000 | ((UInt32) cfmfp & 0xffff); - mfp[2] = 0x800c0000; - mfp[3] = 0x804c0004; - mfp[4] = 0x7c0903a6; - mfp[5] = 0x4e800420; - - MakeDataExecutable (mfp, sizeof (UInt32) * 6); - return mfp; - } - - static void disposeMachOFromCFM (void* ptr) - { - juce_free (ptr); - } - - void coerceAEffectFunctionCalls (AEffect* eff) - { - if (fragId != 0) - { - eff->dispatcher = (AEffectDispatcherProc) newMachOFromCFM ((void*) eff->dispatcher); - eff->process = (AEffectProcessProc) newMachOFromCFM ((void*) eff->process); - eff->setParameter = (AEffectSetParameterProc) newMachOFromCFM ((void*) eff->setParameter); - eff->getParameter = (AEffectGetParameterProc) newMachOFromCFM ((void*) eff->getParameter); - eff->processReplacing = (AEffectProcessProc) newMachOFromCFM ((void*) eff->processReplacing); - } - } -#endif - -#endif -}; - - -//============================================================================== -VSTPluginInstance::VSTPluginInstance (const ReferenceCountedObjectPtr & module_) - : effect (0), - wantsMidiMessages (false), - initialised (false), - isPowerOn (false), - numAllocatedMidiEvents (0), - midiEventsToSend (0), - tempBuffer (2, 8), - channelsIn (0), - channelsOut (0), - module (module_) -{ - try - { - _fpreset(); - - ++insideVSTCallback; - - name = module->pluginName; - log (T("Creating VST instance: ") + name); - -#if JUCE_MAC - if (module->resFileId != 0) - UseResFile (module->resFileId); - -#if JUCE_PPC - if (module->fragId != 0) - { - static void* audioMasterCoerced = 0; - if (audioMasterCoerced == 0) - audioMasterCoerced = NewCFMFromMachO ((void*) &audioMaster); - - effect = module->moduleMain ((audioMasterCallback) audioMasterCoerced); - } - else -#endif -#endif - { - effect = module->moduleMain (&audioMaster); - } - - --insideVSTCallback; - - if (effect != 0 && effect->magic == kEffectMagic) - { -#if JUCE_PPC - module->coerceAEffectFunctionCalls (effect); -#endif - - jassert (effect->resvd2 == 0); - jassert (effect->object != 0); - - _fpreset(); // some dodgy plugs fuck around with this - } - else - { - effect = 0; - } - } - catch (...) - { - --insideVSTCallback; - } -} - -VSTPluginInstance::~VSTPluginInstance() -{ - { - const ScopedLock sl (lock); - - jassert (insideVSTCallback == 0); - - if (effect != 0 && effect->magic == kEffectMagic) - { - try - { -#if JUCE_MAC - if (module->resFileId != 0) - UseResFile (module->resFileId); -#endif - - // Must delete any editors before deleting the plugin instance! - jassert (getActiveEditor() == 0); - - _fpreset(); // some dodgy plugs fuck around with this - - module->closeEffect (effect); - } - catch (...) - {} - } - - effect = 0; - } - - freeMidiEvents(); - - juce_free (channelsIn); - channelsIn = 0; - juce_free (channelsOut); - channelsOut = 0; -} - -//============================================================================== -void VSTPluginInstance::initialise() -{ - if (initialised || effect == 0) - return; - - log (T("Initialising VST: ") + module->pluginName); - initialised = true; - - dispatch (effIdentify, 0, 0, 0, 0); - - { - char buffer [kVstMaxEffectNameLen + 8]; - zerostruct (buffer); - dispatch (effGetEffectName, 0, 0, buffer, 0); - - name = String (buffer); - if (name.trim().isEmpty()) - name = module->pluginName; - } - - dispatch (effSetSampleRate, 0, 0, 0, (float) sampleRate); - dispatch (effSetBlockSize, 0, jmax (16, blockSize), 0, 0); - - dispatch (effOpen, 0, 0, 0, 0); - - numOutputChannels = effect->numOutputs; - numInputChannels = effect->numInputs; - - if (getNumPrograms() > 1) - setCurrentProgram (0); - else - dispatch (effSetProgram, 0, 0, 0, 0); - - int i; - for (i = effect->numInputs; --i >= 0;) - dispatch (effConnectInput, i, 1, 0, 0); - - for (i = effect->numOutputs; --i >= 0;) - dispatch (effConnectOutput, i, 1, 0, 0); - - updateStoredProgramNames(); - - wantsMidiMessages = dispatch (effCanDo, 0, 0, (void*) "receiveVstMidiEvent", 0) != 0; -} - - -//============================================================================== -void JUCE_CALLTYPE VSTPluginInstance::prepareToPlay (double sampleRate_, int samplesPerBlockExpected) -{ - sampleRate = sampleRate_; - blockSize = samplesPerBlockExpected; - midiCollector.reset (sampleRate); - - juce_free (channelsIn); - channelsIn = (float**) juce_calloc (sizeof (float*) * jmax (16, getNumInputChannels() + 4)); - juce_free (channelsOut); - channelsOut = (float**) juce_calloc (sizeof (float*) * jmax (16, getNumOutputChannels() + 4)); - - vstHostTime.tempo = 120.0; - vstHostTime.timeSigNumerator = 4; - vstHostTime.timeSigDenominator = 4; - vstHostTime.sampleRate = sampleRate; - vstHostTime.samplePos = 0; - vstHostTime.flags = kVstNanosValid; /*| kVstTransportPlaying | kVstTempoValid | kVstTimeSigValid*/; - - initialise(); - - if (initialised) - { - wantsMidiMessages = wantsMidiMessages - || (dispatch (effCanDo, 0, 0, (void*) "receiveVstMidiEvent", 0) != 0); - - if (wantsMidiMessages) - ensureMidiEventSize (256); - else - freeMidiEvents(); - - incomingMidi.clear(); - - dispatch (effSetSampleRate, 0, 0, 0, (float) sampleRate); - dispatch (effSetBlockSize, 0, jmax (16, blockSize), 0, 0); - - tempBuffer.setSize (jmax (effect->numOutputs, effect->numInputs), blockSize); - - if (! isPowerOn) - setPower (true); - - // dodgy hack to force some plugins to initialise the sample rate.. - if ((! hasEditor()) && getNumParameters() > 0) - { - const float old = getParameter (0); - setParameter (0, (old < 0.5f) ? 1.0f : 0.0f); - setParameter (0, old); - } - - dispatch (effStartProcess, 0, 0, 0, 0); - } -} - -void JUCE_CALLTYPE VSTPluginInstance::releaseResources() -{ - if (initialised) - { - dispatch (effStopProcess, 0, 0, 0, 0); - setPower (false); - } - - midiCollector.reset (sampleRate); - tempBuffer.setSize (1, 1); - incomingMidi.clear(); - - freeMidiEvents(); - juce_free (channelsIn); - channelsIn = 0; - juce_free (channelsOut); - channelsOut = 0; -} - -void JUCE_CALLTYPE VSTPluginInstance::processBlock (const AudioSampleBuffer& input, - AudioSampleBuffer& output, - const bool accumulateOutput, - MidiBuffer& midiMessages) -{ - const int numSamples = output.getNumSamples(); - - if (initialised) - { -#if JUCE_WIN32 - vstHostTime.nanoSeconds = timeGetTime() * 1000000.0; -#else - UnsignedWide micro; - Microseconds (µ); - vstHostTime.nanoSeconds = micro.lo * 1000.0; -#endif - - if (wantsMidiMessages) - { - MidiBuffer::Iterator iter (midiMessages); - - int eventIndex = 0; - const uint8* midiData; - int numBytesOfMidiData, samplePosition; - - while (iter.getNextEvent (midiData, numBytesOfMidiData, samplePosition)) - { - if (numBytesOfMidiData < 4) - { - ensureMidiEventSize (eventIndex); - VstMidiEvent* const e - = (VstMidiEvent*) ((VstEvents*) midiEventsToSend)->events [eventIndex++]; - - // check that some plugin hasn't messed up our objects - jassert (e->type == kVstMidiType); - jassert (e->byteSize == 24); - - e->deltaFrames = jlimit (0, numSamples - 1, samplePosition); - e->noteLength = 0; - e->noteOffset = 0; - e->midiData[0] = midiData[0]; - e->midiData[1] = midiData[1]; - e->midiData[2] = midiData[2]; - e->detune = 0; - e->noteOffVelocity = 0; - } - } - - if (midiEventsToSend == 0) - ensureMidiEventSize (1); - - ((VstEvents*) midiEventsToSend)->numEvents = eventIndex; - - try - { - effect->dispatcher (effect, effProcessEvents, 0, 0, midiEventsToSend, 0); - } - catch (...) - {} - } - - { - int i; - for (i = effect->numInputs; --i >= 0;) - channelsIn[i] = input.getSampleData (jmin (i, input.getNumChannels() - 1)); // xxx should use empty temp buffers if it runs out - - for (i = effect->numOutputs; --i >= 0;) - channelsOut[i] = output.getSampleData (jmin (i, output.getNumChannels() - 1)); - } - - _clearfp(); - - try - { - if (accumulateOutput) - { - effect->process (effect, channelsIn, channelsOut, numSamples); - } - else - { - if ((effect->flags & effFlagsCanReplacing) != 0) - { - effect->processReplacing (effect, channelsIn, channelsOut, numSamples); - } - else - { - output.clear(); - effect->process (effect, channelsIn, channelsOut, numSamples); - } - } - } - catch (...) - {} - } - else - { - // Not initialised, so just bypass.. - if (! accumulateOutput) - { - for (int i = output.getNumChannels(); --i >= 0;) - output.copyFrom (i, 0, - input, - jmin (i, input.getNumChannels() - 1), - 0, - numSamples); - } - } - - { - // copy any incoming midi.. - const ScopedLock sl (midiInLock); - - midiMessages = incomingMidi; - incomingMidi.clear(); - } -} - -//============================================================================== -void VSTPluginInstance::ensureMidiEventSize (int numEventsNeeded) -{ - if (numEventsNeeded > numAllocatedMidiEvents) - { - numEventsNeeded = (numEventsNeeded + 32) & ~31; - - const int size = 20 + sizeof (VstEvent*) * numEventsNeeded; - - if (midiEventsToSend == 0) - midiEventsToSend = juce_calloc (size); - else - midiEventsToSend = juce_realloc (midiEventsToSend, size); - - for (int i = numAllocatedMidiEvents; i < numEventsNeeded; ++i) - { - VstMidiEvent* const e = (VstMidiEvent*) juce_calloc (sizeof (VstMidiEvent)); - e->type = kVstMidiType; - e->byteSize = 24; - - ((VstEvents*) midiEventsToSend)->events[i] = (VstEvent*) e; - } - - numAllocatedMidiEvents = numEventsNeeded; - } -} - -void VSTPluginInstance::freeMidiEvents() -{ - if (midiEventsToSend != 0) - { - for (int i = numAllocatedMidiEvents; --i >= 0;) - juce_free (((VstEvents*) midiEventsToSend)->events[i]); - - juce_free (midiEventsToSend); - midiEventsToSend = 0; - numAllocatedMidiEvents = 0; - } -} - -void VSTPluginInstance::handleMidiFromPlugin (const VstEvents* const events) -{ - if (events != 0) - { - const ScopedLock sl (midiInLock); - - for (int i = 0; i < events->numEvents; ++i) - { - const VstEvent* const e = events->events[i]; - - if (e->type == kVstMidiType) - { - incomingMidi.addEvent ((const uint8*) ((const VstMidiEvent*) e)->midiData, - 3, e->deltaFrames); - } - } - } -} - -//============================================================================== -class VSTPluginWindow : public AudioFilterEditor, - public Timer -{ -public: - //============================================================================== - VSTPluginWindow (VSTPluginInstance& plugin_) - : AudioFilterEditor (&plugin_), - plugin (plugin_), - isOpen (false), - wasShowing (false), - pluginRefusesToResize (false), - pluginWantsKeys (false), - alreadyInside (false), - recursiveResize (false) - { -#if JUCE_WIN32 - sizeCheckCount = 0; - pluginHWND = 0; -#else - pluginViewRef = 0; -#endif - - movementWatcher = new CompMovementWatcher (this); - - activeWindows.add (this); - - setOpaque (true); - setVisible (true); - } - - ~VSTPluginWindow() - { - deleteAndZero (movementWatcher); - - closePluginWindow(); - - activeWindows.removeValue (this); - plugin.editorBeingDeleted (this); - } - - //============================================================================== - void componentMovedOrResized() - { - if (recursiveResize) - return; - - Component* const topComp = getTopLevelComponent(); - - if (topComp->getPeer() != 0) - { - int x = 0, y = 0; - relativePositionToOtherComponent (topComp, x, y); - - recursiveResize = true; - -#if JUCE_MAC - if (pluginViewRef != 0) - { - HIRect r; - r.origin.x = (float) x; - r.origin.y = (float) y; - r.size.width = (float) getWidth(); - r.size.height = (float) getHeight(); - HIViewSetFrame (pluginViewRef, &r); - } - else if (pluginWindowRef != 0) - { - Rect r; - r.left = getScreenX(); - r.top = getScreenY(); - r.right = r.left + getWidth(); - r.bottom = r.top + getHeight(); - - WindowGroupRef group = GetWindowGroup (pluginWindowRef); - WindowGroupAttributes atts; - GetWindowGroupAttributes (group, &atts); - ChangeWindowGroupAttributes (group, 0, kWindowGroupAttrMoveTogether); - - SetWindowBounds (pluginWindowRef, kWindowContentRgn, &r); - - if ((atts & kWindowGroupAttrMoveTogether) != 0) - ChangeWindowGroupAttributes (group, kWindowGroupAttrMoveTogether, 0); - } - else - { - repaint(); - } -#else - if (pluginHWND != 0) - MoveWindow (pluginHWND, x, y, getWidth(), getHeight(), TRUE); -#endif - - recursiveResize = false; - } - } - - void componentVisibilityChanged() - { - const bool isShowingNow = isShowing(); - - if (wasShowing != isShowingNow) - { - wasShowing = isShowingNow; - - if (isShowingNow) - openPluginWindow(); - else - closePluginWindow(); - } - - componentMovedOrResized(); - } - - void componentPeerChanged() - { - closePluginWindow(); - openPluginWindow(); - } - - //============================================================================== - bool keyStateChanged() - { - return pluginWantsKeys; - } - - bool keyPressed (const KeyPress&) - { - return pluginWantsKeys; - } - - //============================================================================== - void paint (Graphics& g) - { - if (isOpen) - { - ComponentPeer* const peer = getPeer(); - - if (peer != 0) - { - peer->addMaskedRegion (getScreenX() - peer->getScreenX(), - getScreenY() - peer->getScreenY(), - getWidth(), getHeight()); - -#if JUCE_MAC - dispatch (effEditDraw, 0, 0, 0, 0); -#endif - } - } - else - { - g.fillAll (Colours::black); - } - } - - //============================================================================== - void timerCallback() - { -#if JUCE_WIN32 - if (--sizeCheckCount <= 0) - { - sizeCheckCount = 10; - - checkPluginWindowSize(); - } -#endif - - try - { - static bool reentrant = false; - - if (! reentrant) - { - reentrant = true; - plugin.dispatch (effEditIdle, 0, 0, 0, 0); - reentrant = false; - } - } - catch (...) - {} - } - - //============================================================================== - void mouseDown (const MouseEvent& e) - { -#if JUCE_MAC - if (! alreadyInside) - { - alreadyInside = true; - toFront (true); - dispatch (effEditMouse, e.x, e.y, 0, 0); - alreadyInside = false; - } - else - { - PostEvent (::mouseDown, 0); - } -#else - (void) e; - - toFront (true); -#endif - } - - void broughtToFront() - { - activeWindows.removeValue (this); - activeWindows.add (this); - -#if JUCE_MAC - dispatch (effEditTop, 0, 0, 0, 0); -#endif - } - - //============================================================================== - juce_UseDebuggingNewOperator - -private: - VSTPluginInstance& plugin; - bool isOpen, wasShowing, recursiveResize; - bool pluginWantsKeys, pluginRefusesToResize, alreadyInside; - -#if JUCE_WIN32 - HWND pluginHWND; - void* originalWndProc; - int sizeCheckCount; -#else - HIViewRef pluginViewRef; - WindowRef pluginWindowRef; -#endif - - //============================================================================== - void openPluginWindow() - { - if (isOpen || getWindowHandle() == 0) - return; - - log (T("Opening VST UI: ") + plugin.getName()); - isOpen = true; - - ERect* rect = 0; - dispatch (effEditGetRect, 0, 0, &rect, 0); - dispatch (effEditOpen, 0, 0, getWindowHandle(), 0); - - // do this before and after like in the steinberg example - dispatch (effEditGetRect, 0, 0, &rect, 0); - dispatch (effGetProgram, 0, 0, 0, 0); // also in steinberg code - - // Install keyboard hooks - pluginWantsKeys = (dispatch (effKeysRequired, 0, 0, 0, 0) == 0); - -#if JUCE_WIN32 - originalWndProc = 0; - pluginHWND = GetWindow ((HWND) getWindowHandle(), GW_CHILD); - - if (pluginHWND == 0) - { - isOpen = false; - setSize (300, 150); - return; - } - - #pragma warning (push) - #pragma warning (disable: 4244) - - originalWndProc = (void*) GetWindowLongPtr (pluginHWND, GWL_WNDPROC); - - if (! pluginWantsKeys) - SetWindowLongPtr (pluginHWND, GWL_WNDPROC, (LONG_PTR) vstHookWndProc); - - #pragma warning (pop) - - int w, h; - RECT r; - GetWindowRect (pluginHWND, &r); - w = r.right - r.left; - h = r.bottom - r.top; - - if (rect != 0) - { - const int rw = rect->right - rect->left; - const int rh = rect->bottom - rect->top; - - if ((rw > 50 && rh > 50 && rw < 2000 && rh < 2000 && rw != w && rh != h) - || ((w == 0 && rw > 0) || (h == 0 && rh > 0))) - { - // very dodgy logic to decide which size is right. - if (abs (rw - w) > 350 || abs (rh - h) > 350) - { - SetWindowPos (pluginHWND, 0, - 0, 0, rw, rh, - SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); - - GetWindowRect (pluginHWND, &r); - - w = r.right - r.left; - h = r.bottom - r.top; - - pluginRefusesToResize = (w != rw) || (h != rh); - - w = rw; - h = rh; - } - } - } -#else - HIViewRef root = HIViewGetRoot ((WindowRef) getWindowHandle()); - HIViewFindByID (root, kHIViewWindowContentID, &root); - pluginViewRef = HIViewGetFirstSubview (root); - - while (pluginViewRef != 0 && juce_isHIViewCreatedByJuce (pluginViewRef)) - pluginViewRef = HIViewGetNextView (pluginViewRef); - - pluginWindowRef = 0; - - if (pluginViewRef == 0) - { - WindowGroupRef ourGroup = GetWindowGroup ((WindowRef) getWindowHandle()); - //DebugPrintWindowGroup (ourGroup); - //DebugPrintAllWindowGroups(); - - GetIndexedWindow (ourGroup, 1, - kWindowGroupContentsVisible, - &pluginWindowRef); - - if (pluginWindowRef == (WindowRef) getWindowHandle() - || juce_isWindowCreatedByJuce (pluginWindowRef)) - pluginWindowRef = 0; - } - - int w = 250, h = 150; - - if (rect != 0) - { - w = rect->right - rect->left; - h = rect->bottom - rect->top; - - if (w == 0 || h == 0) - { - w = 250; - h = 150; - } - } -#endif - - // double-check it's not too tiny - w = jmax (w, 32); - h = jmax (h, 32); - - setSize (w, h); - -#if JUCE_WIN32 - checkPluginWindowSize(); -#endif - - startTimer (18 + juce::Random::getSystemRandom().nextInt (5)); - repaint(); - } - - //============================================================================== - void closePluginWindow() - { - if (isOpen) - { - log (T("Closing VST UI: ") + plugin.getName()); - isOpen = false; - - dispatch (effEditClose, 0, 0, 0, 0); - -#if JUCE_WIN32 - #pragma warning (push) - #pragma warning (disable: 4244) - - if (pluginHWND != 0 && IsWindow (pluginHWND)) - SetWindowLongPtr (pluginHWND, GWL_WNDPROC, (LONG_PTR) originalWndProc); - - #pragma warning (pop) - - stopTimer(); - - if (pluginHWND != 0 && IsWindow (pluginHWND)) - DestroyWindow (pluginHWND); - - pluginHWND = 0; -#else - dispatch (effEditSleep, 0, 0, 0, 0); - pluginViewRef = 0; - stopTimer(); -#endif - } - } - - //============================================================================== -#if JUCE_WIN32 - void checkPluginWindowSize() throw() - { - RECT r; - GetWindowRect (pluginHWND, &r); - const int w = r.right - r.left; - const int h = r.bottom - r.top; - - if (isShowing() && w > 0 && h > 0 - && (w != getWidth() || h != getHeight()) - && ! pluginRefusesToResize) - { - setSize (w, h); - sizeCheckCount = 0; - } - } -#endif - - //============================================================================== - class CompMovementWatcher : public ComponentMovementWatcher - { - public: - CompMovementWatcher (VSTPluginWindow* const owner_) - : ComponentMovementWatcher (owner_), - owner (owner_) - { - } - - //============================================================================== - void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) - { - owner->componentMovedOrResized(); - } - - void componentPeerChanged() - { - owner->componentPeerChanged(); - } - - void componentVisibilityChanged (Component&) - { - owner->componentVisibilityChanged(); - } - - private: - VSTPluginWindow* const owner; - }; - - CompMovementWatcher* movementWatcher; - - //============================================================================== - int dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) - { - return plugin.dispatch (opcode, index, value, ptr, opt); - } - - //============================================================================== - // hooks to get keyboard events from VST windows.. -#if JUCE_WIN32 - static LRESULT CALLBACK vstHookWndProc (HWND hW, UINT message, WPARAM wParam, LPARAM lParam) - { - for (int i = activeWindows.size(); --i >= 0;) - { - const VSTPluginWindow* const w = (const VSTPluginWindow*) activeWindows.getUnchecked (i); - - if (w->pluginHWND == hW) - { - if (message == WM_CHAR - || message == WM_KEYDOWN - || message == WM_SYSKEYDOWN - || message == WM_KEYUP - || message == WM_SYSKEYUP - || message == WM_APPCOMMAND) - { - SendMessage ((HWND) w->getTopLevelComponent()->getWindowHandle(), - message, wParam, lParam); - } - - return CallWindowProc ((WNDPROC) (w->originalWndProc), - (HWND) w->pluginHWND, - message, - wParam, - lParam); - } - } - - return DefWindowProc (hW, message, wParam, lParam); - } -#endif -}; - -//============================================================================== -AudioFilterEditor* JUCE_CALLTYPE VSTPluginInstance::createEditor() -{ - return new VSTPluginWindow (*this); -} - - -//============================================================================== -void VSTPluginInstance::handleAsyncUpdate() -{ - // called asynchronously to indicate that the plugin's parameters have changed.. -} - -//============================================================================== -bool VSTPluginInstance::restoreProgramSettings (const fxProgram* const prog) -{ - if (swap (prog->chunkMagic) == 'CcnK' && swap (prog->fxMagic) == 'FxCk') - { - changeProgramName (getCurrentProgram(), prog->prgName); - - for (int i = 0; i < swap (prog->numParams); ++i) - setParameter (i, swapFloat (prog->params[i])); - - return true; - } - - return false; -} - -bool VSTPluginInstance::loadFromFXBFile (const void* const data, - const int dataSize) -{ - if (dataSize < 28) - return false; - - const fxSet* const set = (const fxSet*) data; - - if ((swap (set->chunkMagic) != 'CcnK' && swap (set->chunkMagic) != 'KncC') - || swap (set->version) > fxbVersionNum) - return false; - - if (swap (set->fxMagic) == 'FxBk') - { - // bank of programs - if (swap (set->numPrograms) >= 0) - { - const int oldProg = getCurrentProgram(); - const int numParams = swap (((const fxProgram*) (set->programs))->numParams); - const int progLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); - - for (int i = 0; i < swap (set->numPrograms); ++i) - { - if (i != oldProg) - { - const fxProgram* const prog = (const fxProgram*) (((const char*) (set->programs)) + i * progLen); - if (((const char*) prog) - ((const char*) set) >= dataSize) - return false; - - if (swap (set->numPrograms) > 0) - setCurrentProgram (i); - - if (! restoreProgramSettings (prog)) - return false; - } - } - - if (swap (set->numPrograms) > 0) - setCurrentProgram (oldProg); - - const fxProgram* const prog = (const fxProgram*) (((const char*) (set->programs)) + oldProg * progLen); - if (((const char*) prog) - ((const char*) set) >= dataSize) - return false; - - if (! restoreProgramSettings (prog)) - return false; - } - } - else if (swap (set->fxMagic) == 'FxCk') - { - // single program - const fxProgram* const prog = (const fxProgram*) data; - - if (swap (prog->chunkMagic) != 'CcnK') - return false; - - changeProgramName (getCurrentProgram(), prog->prgName); - - for (int i = 0; i < swap (prog->numParams); ++i) - setParameter (i, swapFloat (prog->params[i])); - } - else if (swap (set->fxMagic) == 'FBCh' || swap (set->fxMagic) == 'hCBF') - { - // non-preset chunk - const fxChunkSet* const cset = (const fxChunkSet*) data; - - if (swap (cset->chunkSize) + sizeof (fxChunkSet) - 8 > (unsigned int) dataSize) - return false; - - setChunkData (cset->chunk, swap (cset->chunkSize), false); - } - else if (swap (set->fxMagic) == 'FPCh' || swap (set->fxMagic) == 'hCPF') - { - // preset chunk - const fxProgramSet* const cset = (const fxProgramSet*) data; - - if (swap (cset->chunkSize) + sizeof (fxProgramSet) - 8 > (unsigned int) dataSize) - return false; - - setChunkData (cset->chunk, swap (cset->chunkSize), true); - - changeProgramName (getCurrentProgram(), cset->name); - } - else - { - return false; - } - - return true; -} - -//============================================================================== -void VSTPluginInstance::setParamsInProgramBlock (fxProgram* const prog) throw() -{ - const int numParams = getNumParameters(); - - prog->chunkMagic = swap ('CcnK'); - prog->byteSize = 0; - prog->fxMagic = swap ('FxCk'); - prog->version = swap (fxbVersionNum); - prog->fxID = swap (getUID()); - prog->fxVersion = swap (getVersionNumber()); - prog->numParams = swap (numParams); - - getCurrentProgramName().copyToBuffer (prog->prgName, sizeof (prog->prgName) - 1); - - for (int i = 0; i < numParams; ++i) - prog->params[i] = swapFloat (getParameter (i)); -} - -bool VSTPluginInstance::saveToFXBFile (juce::MemoryBlock& dest, bool isFXB, int maxSizeMB) -{ - const int numPrograms = getNumPrograms(); - const int numParams = getNumParameters(); - - if (usesChunks()) - { - if (isFXB) - { - juce::MemoryBlock chunk; - getChunkData (chunk, false, maxSizeMB); - - const int totalLen = sizeof (fxChunkSet) + chunk.getSize() - 8; - dest.setSize (totalLen, true); - - fxChunkSet* const set = (fxChunkSet*) dest.getData(); - set->chunkMagic = swap ('CcnK'); - set->byteSize = 0; - set->fxMagic = swap ('FBCh'); - set->version = swap (fxbVersionNum); - set->fxID = swap (getUID()); - set->fxVersion = swap (getVersionNumber()); - set->numPrograms = swap (numPrograms); - set->chunkSize = swap (chunk.getSize()); - - chunk.copyTo (set->chunk, 0, chunk.getSize()); - } - else - { - juce::MemoryBlock chunk; - getChunkData (chunk, true, maxSizeMB); - - const int totalLen = sizeof (fxProgramSet) + chunk.getSize() - 8; - dest.setSize (totalLen, true); - - fxProgramSet* const set = (fxProgramSet*) dest.getData(); - set->chunkMagic = swap ('CcnK'); - set->byteSize = 0; - set->fxMagic = swap ('FPCh'); - set->version = swap (fxbVersionNum); - set->fxID = swap (getUID()); - set->fxVersion = swap (getVersionNumber()); - set->numPrograms = swap (numPrograms); - set->chunkSize = swap (chunk.getSize()); - - getCurrentProgramName().copyToBuffer (set->name, sizeof (set->name) - 1); - chunk.copyTo (set->chunk, 0, chunk.getSize()); - } - } - else - { - if (isFXB) - { - const int progLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); - const int len = (sizeof (fxSet) - sizeof (fxProgram)) + progLen * jmax (1, numPrograms); - dest.setSize (len, true); - - fxSet* const set = (fxSet*) dest.getData(); - set->chunkMagic = swap ('CcnK'); - set->byteSize = 0; - set->fxMagic = swap ('FxBk'); - set->version = swap (fxbVersionNum); - set->fxID = swap (getUID()); - set->fxVersion = swap (getVersionNumber()); - set->numPrograms = swap (numPrograms); - - const int oldProgram = getCurrentProgram(); - juce::MemoryBlock oldSettings; - createTempParameterStore (oldSettings); - - setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + oldProgram * progLen)); - - for (int i = 0; i < numPrograms; ++i) - { - if (i != oldProgram) - { - setCurrentProgram (i); - setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + i * progLen)); - } - } - - setCurrentProgram (oldProgram); - restoreFromTempParameterStore (oldSettings); - } - else - { - const int totalLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); - dest.setSize (totalLen, true); - - setParamsInProgramBlock ((fxProgram*) dest.getData()); - } - } - - return true; -} - -void VSTPluginInstance::getChunkData (juce::MemoryBlock& mb, bool isPreset, int maxSizeMB) const -{ - if (usesChunks()) - { - void* data = 0; - const int bytes = dispatch (effGetChunk, isPreset ? 1 : 0, 0, &data, 0.0f); - - if (data != 0 && bytes <= maxSizeMB * 1024 * 1024) - { - mb.setSize (bytes); - mb.copyFrom (data, 0, bytes); - } - } -} - -void VSTPluginInstance::setChunkData (const char* data, int size, bool isPreset) -{ - if (size > 0 && usesChunks()) - { - dispatch (effSetChunk, isPreset ? 1 : 0, size, (void*) data, 0.0f); - - if (! isPreset) - updateStoredProgramNames(); - } -} - -//============================================================================== -void VSTPluginInstance::timerCallback() -{ - if (dispatch (effIdle, 0, 0, 0, 0) == 0) - stopTimer(); -} - -int VSTPluginInstance::dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) const -{ - const ScopedLock sl (lock); - - ++insideVSTCallback; - int result = 0; - - try - { - if (effect != 0) - { -#if JUCE_MAC - if (module->resFileId != 0) - UseResFile (module->resFileId); - - CGrafPtr oldPort; - - if (getActiveEditor() != 0) - { - int x = 0, y = 0; - getActiveEditor()->relativePositionToOtherComponent (getActiveEditor()->getTopLevelComponent(), x, y); - - GetPort (&oldPort); - SetPortWindowPort ((WindowRef) getActiveEditor()->getWindowHandle()); - SetOrigin (-x, -y); - } -#endif - - result = effect->dispatcher (effect, opcode, index, value, ptr, opt); - -#if JUCE_MAC - if (getActiveEditor() != 0) - SetPort (oldPort); - - module->resFileId = CurResFile(); -#endif - - --insideVSTCallback; - return result; - } - } - catch (...) - { - //char s[512]; - //sprintf (s, "dispatcher (%d, %d, %d, %x, %f)", opcode, index, value, (int)ptr, opt); - } - - --insideVSTCallback; - return result; -} - -//============================================================================== -// handles non plugin-specific callbacks.. -static VstIntPtr handleGeneralCallback (VstInt32 opcode, VstInt32 index, VstInt32 value, void *ptr, float opt) -{ - (void) index; - (void) value; - (void) opt; - - switch (opcode) - { - case audioMasterCanDo: - { - static const char* canDos[] = { "supplyIdle", - "sendVstEvents", - "sendVstMidiEvent", - "sendVstTimeInfo", - "receiveVstEvents", - "receiveVstMidiEvent", - "supportShell", - "shellCategory" }; - - for (int i = 0; i < numElementsInArray (canDos); ++i) - if (strcmp (canDos[i], (const char*) ptr) == 0) - return 1; - - return 0; - } - - case audioMasterVersion: - return 0x2400; - case audioMasterCurrentId: - return shellUIDToCreate; - case audioMasterGetNumAutomatableParameters: - return 0; - case audioMasterGetAutomationState: - return 1; - - case audioMasterGetVendorVersion: - return 1; - case audioMasterGetVendorString: - case audioMasterGetProductString: - JUCEApplication::getInstance() - ->getApplicationName().copyToBuffer ((char*) ptr, jmin (kVstMaxVendorStrLen, kVstMaxProductStrLen) - 1); - break; - - case audioMasterGetSampleRate: - return 44100; - - case audioMasterGetBlockSize: - return 512; - - case audioMasterSetOutputSampleRate: - return 0; - - default: - DBG ("*** Unhandled VST Callback: " + String ((int) opcode)); - break; - } - - return 0; -} - -// handles callbacks for a specific plugin -VstIntPtr VSTPluginInstance::handleCallback (VstInt32 opcode, VstInt32 index, VstInt32 value, void *ptr, float opt) -{ - switch (opcode) - { - case audioMasterAutomate: - // index = param num, opt = value - break; - - case audioMasterProcessEvents: - handleMidiFromPlugin ((const VstEvents*) ptr); - break; - - case audioMasterGetTime: - #ifdef _MSC_VER - #pragma warning (push) - #pragma warning (disable: 4311) - #endif - - return (VstIntPtr) &vstHostTime; - - #ifdef _MSC_VER - #pragma warning (pop) - #endif - break; - - case audioMasterIdle: - Thread::sleep (1); - if (! isTimerRunning()) - startTimer (50); - -#if JUCE_MAC - if (getActiveEditor() != 0) - dispatch (effEditIdle, 0, 0, 0, 0); -#endif - break; - - case audioMasterUpdateDisplay: - triggerAsyncUpdate(); - break; - - case audioMasterTempoAt: - // returns (10000 * bpm) - break; - - case audioMasterNeedIdle: - startTimer (50); - break; - - case audioMasterSizeWindow: - if (getActiveEditor() != 0) - getActiveEditor()->setSize (index, value); - - return 1; - - case audioMasterGetSampleRate: - return (VstIntPtr) sampleRate; - - case audioMasterGetBlockSize: - return (VstIntPtr) blockSize; - - case audioMasterWantMidi: - wantsMidiMessages = true; - break; - - case audioMasterGetDirectory: - #if JUCE_MAC - return (VstIntPtr) (void*) &module->parentDirFSSpec; - #else - return (VstIntPtr) (pointer_sized_uint) (const char*) module->fullParentDirectoryPathName; - #endif - - case audioMasterGetAutomationState: - // returns 0: not supported, 1: off, 2:read, 3:write, 4:read/write - break; - - // none of these are handled (yet).. - case audioMasterBeginEdit: - case audioMasterEndEdit: - case audioMasterSetTime: - case audioMasterPinConnected: - case audioMasterGetParameterQuantization: - case audioMasterIOChanged: - case audioMasterGetInputLatency: - case audioMasterGetOutputLatency: - case audioMasterGetPreviousPlug: - case audioMasterGetNextPlug: - case audioMasterWillReplaceOrAccumulate: - case audioMasterGetCurrentProcessLevel: - case audioMasterOfflineStart: - case audioMasterOfflineRead: - case audioMasterOfflineWrite: - case audioMasterOfflineGetCurrentPass: - case audioMasterOfflineGetCurrentMetaPass: - case audioMasterVendorSpecific: - case audioMasterSetIcon: - case audioMasterGetLanguage: - case audioMasterOpenWindow: - case audioMasterCloseWindow: - break; - - default: - return handleGeneralCallback (opcode, index, value, ptr, opt); - } - - return 0; -} - -// entry point for all callbacks from the plugin -static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) -{ - try - { - if (effect != 0 && effect->resvd2 != 0) - { - return ((VSTPluginInstance*)(effect->resvd2)) - ->handleCallback (opcode, index, value, ptr, opt); - } - - return handleGeneralCallback (opcode, index, value, ptr, opt); - } - catch (...) - { - return 0; - } -} - -//============================================================================== -const String VSTPluginInstance::getName() const -{ - return name; -} - -const String VSTPluginInstance::getManufacturer() const -{ - char buffer [kVstMaxVendorStrLen + 8]; - zerostruct (buffer); - dispatch (effGetVendorString, 0, 0, buffer, 0); - return buffer; -} - -const String VSTPluginInstance::getVersion() const -{ - int v = dispatch (effGetVendorVersion, 0, 0, 0, 0); - - String s; - - if (v != 0) - { - int versionBits[4]; - int n = 0; - - while (v != 0) - { - versionBits [n++] = (v & 0xff); - v >>= 8; - } - - s << 'V'; - - while (n > 0) - { - s << versionBits [--n]; - - if (n > 0) - s << '.'; - } - } - - return s; -} - -int VSTPluginInstance::getVersionNumber() const throw() -{ - return effect != 0 ? effect->version : 0; -} - -const String VSTPluginInstance::getFormatName() const -{ - return "VST"; -} - -const File VSTPluginInstance::getFile() const -{ - return module->file; -} - -int VSTPluginInstance::getUID() const -{ - int uid = effect != 0 ? effect->uniqueID : 0; - - if (uid == 0) - uid = getFile().hashCode(); - - return uid; -} - -const String VSTPluginInstance::getCategory() const -{ - const char* result = 0; - - switch (dispatch (effGetPlugCategory, 0, 0, 0, 0)) - { - case kPlugCategEffect: - result = "Effect"; - break; - - case kPlugCategSynth: - result = "Synth"; - break; - - case kPlugCategAnalysis: - result = "Anaylsis"; - break; - - case kPlugCategMastering: - result = "Mastering"; - break; - - case kPlugCategSpacializer: - result = "Spacial"; - break; - - case kPlugCategRoomFx: - result = "Reverb"; - break; - - case kPlugSurroundFx: - result = "Surround"; - break; - - case kPlugCategRestoration: - result = "Restoration"; - break; - - case kPlugCategGenerator: - result = "Tone generation"; - break; - - default: - break; - } - - return result; -} - -//============================================================================== -int JUCE_CALLTYPE VSTPluginInstance::getNumParameters() -{ - return effect != 0 ? effect->numParams : 0; -} - -float JUCE_CALLTYPE VSTPluginInstance::getParameter (int index) -{ - if (effect != 0 && index >= 0 && index < effect->numParams) - { - try - { - const ScopedLock sl (lock); - return effect->getParameter (effect, index); - } - catch (...) - { - } - } - - return 0.0f; -} - -void JUCE_CALLTYPE VSTPluginInstance::setParameter (int index, float newValue) -{ - if (effect != 0 && index >= 0 && index < effect->numParams) - { - try - { - const ScopedLock sl (lock); - - if (effect->getParameter (effect, index) != newValue) - effect->setParameter (effect, index, newValue); - } - catch (...) - { - } - } -} - -const String JUCE_CALLTYPE VSTPluginInstance::getParameterName (int index) -{ - if (effect != 0) - { - jassert (index >= 0 && index < effect->numParams); - - char nm [256]; - zerostruct (nm); - dispatch (effGetParamName, index, 0, nm, 0); - return String (nm).trim(); - } - - return String::empty; -} - -const String VSTPluginInstance::getParameterLabel (int index) const -{ - if (effect != 0) - { - jassert (index >= 0 && index < effect->numParams); - - char nm [256]; - zerostruct (nm); - dispatch (effGetParamLabel, index, 0, nm, 0); - return String (nm).trim(); - } - - return String::empty; -} - -const String JUCE_CALLTYPE VSTPluginInstance::getParameterText (int index) -{ - if (effect != 0) - { - jassert (index >= 0 && index < effect->numParams); - - char nm [256]; - zerostruct (nm); - dispatch (effGetParamDisplay, index, 0, nm, 0); - return String (nm).trim(); - } - - return String::empty; -} - -bool VSTPluginInstance::isParameterAutomatable (int index) const -{ - if (effect != 0) - { - jassert (index >= 0 && index < effect->numParams); - return dispatch (effCanBeAutomated, index, 0, 0, 0) != 0; - } - - return false; -} - -void VSTPluginInstance::createTempParameterStore (juce::MemoryBlock& dest) -{ - dest.setSize (64 + 4 * getNumParameters()); - dest.fillWith (0); - - getCurrentProgramName().copyToBuffer ((char*) dest.getData(), 63); - - float* const p = (float*) (((char*) dest.getData()) + 64); - for (int i = 0; i < getNumParameters(); ++i) - p[i] = getParameter(i); -} - -void VSTPluginInstance::restoreFromTempParameterStore (const juce::MemoryBlock& m) -{ - changeProgramName (getCurrentProgram(), (const char*) m); - - float* p = (float*) (((char*) m.getData()) + 64); - for (int i = 0; i < getNumParameters(); ++i) - setParameter (i, p[i]); -} - -//============================================================================== -int JUCE_CALLTYPE VSTPluginInstance::getNumPrograms() -{ - return effect != 0 ? effect->numPrograms : 0; -} - -int JUCE_CALLTYPE VSTPluginInstance::getCurrentProgram() -{ - return dispatch (effGetProgram, 0, 0, 0, 0); -} - -void JUCE_CALLTYPE VSTPluginInstance::setCurrentProgram (int newIndex) -{ - if (getNumPrograms() > 0 && newIndex != getCurrentProgram()) - dispatch (effSetProgram, 0, jlimit (0, getNumPrograms() - 1, newIndex), 0, 0); -} - -const String JUCE_CALLTYPE VSTPluginInstance::getProgramName (int index) -{ - if (index == getCurrentProgram()) - { - return getCurrentProgramName(); - } - else if (effect != 0) - { - char nm [256]; - zerostruct (nm); - - if (dispatch (effGetProgramNameIndexed, - jlimit (0, getNumPrograms(), index), - -1, nm, 0) != 0) - { - return String (nm).trim(); - } - } - - return programNames [index]; -} - -void JUCE_CALLTYPE VSTPluginInstance::changeProgramName (int index, const String& newName) -{ - if (index == getCurrentProgram()) - { - if (getNumPrograms() > 0 && newName != getCurrentProgramName()) - dispatch (effSetProgramName, 0, 0, (void*) (const char*) newName.substring (0, 24), 0.0f); - } - else - { - jassertfalse // xxx not implemented! - } -} - -void VSTPluginInstance::updateStoredProgramNames() -{ - if (effect != 0 && getNumPrograms() > 0) - { - char nm [256]; - zerostruct (nm); - - // only do this if the plugin can't use indexed names.. - if (dispatch (effGetProgramNameIndexed, 0, -1, nm, 0) == 0) - { - const int oldProgram = getCurrentProgram(); - juce::MemoryBlock oldSettings; - createTempParameterStore (oldSettings); - - for (int i = 0; i < getNumPrograms(); ++i) - { - setCurrentProgram (i); - getCurrentProgramName(); // (this updates the list) - } - - setCurrentProgram (oldProgram); - restoreFromTempParameterStore (oldSettings); - } - } -} - -const String VSTPluginInstance::getCurrentProgramName() -{ - if (effect != 0) - { - char nm [256]; - zerostruct (nm); - dispatch (effGetProgramName, 0, 0, nm, 0); - - const int index = getCurrentProgram(); - if (programNames[index].isEmpty()) - { - while (programNames.size() < index) - programNames.add (String::empty); - - programNames.set (index, String (nm).trim()); - } - - return String (nm).trim(); - } - - return String::empty; -} - -//============================================================================== -const String VSTPluginInstance::getInputChannelName (const int index) const -{ - if (index >= 0 && index < getNumInputChannels()) - { - VstPinProperties pinProps; - if (dispatch (effGetInputProperties, index, 0, &pinProps, 0.0f) != 0) - return String (pinProps.label, sizeof (pinProps.label)); - } - - return String::empty; -} - -bool VSTPluginInstance::isInputChannelStereoPair (int index) const -{ - if (index < 0 || index >= getNumInputChannels()) - return false; - - VstPinProperties pinProps; - if (dispatch (effGetInputProperties, index, 0, &pinProps, 0.0f) != 0) - return (pinProps.flags & kVstPinIsStereo) != 0; - - return true; -} - -const String VSTPluginInstance::getOutputChannelName (const int index) const -{ - if (index >= 0 && index < getNumOutputChannels()) - { - VstPinProperties pinProps; - if (dispatch (effGetOutputProperties, index, 0, &pinProps, 0.0f) != 0) - return String (pinProps.label, sizeof (pinProps.label)); - } - - return String::empty; -} - -bool VSTPluginInstance::isOutputChannelStereoPair (int index) const -{ - if (index < 0 || index >= getNumOutputChannels()) - return false; - - VstPinProperties pinProps; - if (dispatch (effGetOutputProperties, index, 0, &pinProps, 0.0f) != 0) - return (pinProps.flags & kVstPinIsStereo) != 0; - - return true; -} - -//============================================================================== -bool VSTPluginInstance::acceptsMidi() const -{ - return wantsMidiMessages; -} - -bool VSTPluginInstance::producesMidi() const -{ - return dispatch (effCanDo, 0, 0, (void*) "sendVstMidiEvent", 0) > 0; -} - -int VSTPluginInstance::getSamplesLatency() const -{ - return effect != 0 ? effect->initialDelay : 0; -} - -void VSTPluginInstance::setPower (const bool on) -{ - dispatch (effMainsChanged, 0, on ? 1 : 0, 0, 0); - isPowerOn = on; -} - -bool VSTPluginInstance::hasEditor() const throw() -{ - return effect != 0 && (effect->flags & effFlagsHasEditor) != 0; -} - -bool VSTPluginInstance::canMono() const throw() -{ - return effect != 0 && (effect->flags & effFlagsCanMono) != 0; -} - -bool VSTPluginInstance::canReplace() const throw() -{ - return effect != 0 && (effect->flags & effFlagsCanReplacing) != 0; -} - -bool VSTPluginInstance::isOffline() const throw() -{ - return dispatch (effCanDo, 0, 0, (void*) "offline", 0) > 0; -} - -bool VSTPluginInstance::isInstrument() const -{ - return effect != 0 && (effect->flags & effFlagsIsSynth) != 0; -} - -bool VSTPluginInstance::usesChunks() const throw() -{ - return effect != 0 && (effect->flags & effFlagsProgramChunks) != 0; -} - - -//============================================================================== -const int defaultMaxSizeMB = 64; - -void JUCE_CALLTYPE VSTPluginInstance::getStateInformation (JUCE_NAMESPACE::MemoryBlock& destData) -{ - saveToFXBFile (destData, true, defaultMaxSizeMB); -} - -void JUCE_CALLTYPE VSTPluginInstance::getCurrentProgramStateInformation (JUCE_NAMESPACE::MemoryBlock& destData) -{ - saveToFXBFile (destData, false, defaultMaxSizeMB); -} - -void JUCE_CALLTYPE VSTPluginInstance::setStateInformation (const void* data, int sizeInBytes) -{ - loadFromFXBFile (data, sizeInBytes); -} - -void JUCE_CALLTYPE VSTPluginInstance::setCurrentProgramStateInformation (const void* data, int sizeInBytes) -{ - loadFromFXBFile (data, sizeInBytes); -} - -//============================================================================== -//============================================================================== -VSTPluginFormat::VSTPluginFormat() -{ -} - -VSTPluginFormat::~VSTPluginFormat() -{ -} - -void VSTPluginFormat::findAllTypesForFile (OwnedArray & results, - const File& file) -{ - if (! fileMightContainThisPluginType (file)) - return; - - PluginDescription desc; - desc.file = file; - desc.uid = 0; - - VSTPluginInstance* instance = dynamic_cast (createInstanceFromDescription (desc)); - - if (instance == 0) - return; - - try - { -#if JUCE_MAC - if (instance->module->resFileId != 0) - UseResFile (instance->module->resFileId); -#endif - - desc.fillInFromInstance (*instance); - - VstPlugCategory category = (VstPlugCategory) instance->dispatch (effGetPlugCategory, 0, 0, 0, 0); - - if (category != kPlugCategShell) - { - // Normal plugin... - results.add (new PluginDescription (desc)); - - ++insideVSTCallback; - instance->dispatch (effOpen, 0, 0, 0, 0); - --insideVSTCallback; - } - else - { - // It's a shell plugin, so iterate all the subtypes... - char shellEffectName [64]; - - for (;;) - { - zerostruct (shellEffectName); - const int uid = instance->dispatch (effShellGetNextPlugin, 0, 0, shellEffectName, 0); - - if (uid == 0) - { - break; - } - else - { - desc.uid = uid; - desc.name = shellEffectName; - - bool alreadyThere = false; - - for (int i = results.size(); --i >= 0;) - { - PluginDescription* const d = results.getUnchecked(i); - - if (d->isDuplicateOf (desc)) - { - alreadyThere = true; - break; - } - } - - if (! alreadyThere) - results.add (new PluginDescription (desc)); - } - } - } - } - catch (...) - { - // crashed while loading... - } - - deleteAndZero (instance); -} - -AudioPluginInstance* VSTPluginFormat::createInstanceFromDescription (const PluginDescription& desc) -{ - VSTPluginInstance* result = 0; - - if (fileMightContainThisPluginType (desc.file)) - { - const File previousWorkingDirectory (File::getCurrentWorkingDirectory()); - desc.file.getParentDirectory().setAsCurrentWorkingDirectory(); - - const ReferenceCountedObjectPtr module (ModuleHandle::findOrCreateModule (desc.file)); - - if (module != 0) - { - shellUIDToCreate = desc.uid; - - result = new VSTPluginInstance (module); - - if (result->effect != 0) - { - result->effect->resvd2 = (VstIntPtr) (pointer_sized_int) result; - result->initialise(); - } - else - { - deleteAndZero (result); - } - } - - previousWorkingDirectory.setAsCurrentWorkingDirectory(); - } - - return result; -} - -bool VSTPluginFormat::fileMightContainThisPluginType (const File& f) -{ -#if JUCE_MAC - if (f.isDirectory() && f.hasFileExtension (T(".vst"))) - return true; - -#if JUCE_PPC - FSRef fileRef; - if (PlatformUtilities::makeFSRefFromPath (&fileRef, f.getFullPathName())) - { - const short resFileId = FSOpenResFile (&fileRef, fsRdPerm); - - if (resFileId != -1) - { - const int numEffects = Count1Resources ('aEff'); - CloseResFile (resFileId); - - if (numEffects > 0) - return true; - } - } -#endif - - return false; -#else - return f.existsAsFile() - && f.hasFileExtension (T(".dll")); -#endif -} - -const FileSearchPath VSTPluginFormat::getDefaultLocationsToSearch() -{ -#if JUCE_MAC - return FileSearchPath ("~/Library/Audio/Plug-Ins/VST;/Library/Audio/Plug-Ins/VST"); -#else - const String programFiles (File::getSpecialLocation (File::globalApplicationsDirectory).getFullPathName()); - - return FileSearchPath (programFiles + "\\Steinberg\\VstPlugins"); -#endif -} +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#ifdef _WIN32 + #define _WIN32_WINNT 0x500 + #define STRICT + #include + #include + #pragma warning (disable : 4312) +#else + #include +#endif + +#include "../../../../../juce.h" +#include "juce_VSTPluginInstance.h" +#include "../juce_PluginDescription.h" + +#if ! JUCE_WIN32 + #define _fpreset() + #define _clearfp() +#endif + +//============================================================================== +const int fxbVersionNum = 1; + +//============================================================================== +struct fxProgram +{ + long chunkMagic; // 'CcnK' + long byteSize; // of this chunk, excl. magic + byteSize + long fxMagic; // 'FxCk' + long version; + long fxID; // fx unique id + long fxVersion; + long numParams; + char prgName[28]; + float params[1]; // variable no. of parameters +}; + +struct fxSet +{ + long chunkMagic; // 'CcnK' + long byteSize; // of this chunk, excl. magic + byteSize + long fxMagic; // 'FxBk' + long version; + long fxID; // fx unique id + long fxVersion; + long numPrograms; + char future[128]; + fxProgram programs[1]; // variable no. of programs +}; + +struct fxChunkSet +{ + long chunkMagic; // 'CcnK' + long byteSize; // of this chunk, excl. magic + byteSize + long fxMagic; // 'FxCh', 'FPCh', or 'FBCh' + long version; + long fxID; // fx unique id + long fxVersion; + long numPrograms; + char future[128]; + long chunkSize; + char chunk[8]; // variable +}; + +struct fxProgramSet +{ + long chunkMagic; // 'CcnK' + long byteSize; // of this chunk, excl. magic + byteSize + long fxMagic; // 'FxCh', 'FPCh', or 'FBCh' + long version; + long fxID; // fx unique id + long fxVersion; + long numPrograms; + char name[28]; + long chunkSize; + char chunk[8]; // variable +}; + + +#ifdef JUCE_LITTLE_ENDIAN + static long swap (const long x) throw() { return (long) swapByteOrder ((uint32) x); } + + static float swapFloat (const float x) throw() + { + union { uint32 asInt; float asFloat; } n; + n.asFloat = x; + n.asInt = swapByteOrder (n.asInt); + return n.asFloat; + } +#else + #define swap(x) (x) + #define swapFloat(x) (x) +#endif + +//============================================================================== +typedef AEffect* (*MainCall) (audioMasterCallback); + +static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt); + +#if JUCE_PPC + static void* audioMasterCoerced = 0; +#endif + +static int shellUIDToCreate = 0; +static int insideVSTCallback = 0; + +static Array activeWindows; + +//============================================================================== +// Change this to disable logging of various VST activities +#ifndef VST_LOGGING + #define VST_LOGGING 1 +#endif + +#if VST_LOGGING + #define log(a) Logger::writeToLog(a); +#else + #define log(a) +#endif + +//============================================================================== +#if JUCE_MAC +namespace JUCE_NAMESPACE +{ + extern bool juce_isHIViewCreatedByJuce (HIViewRef view); + extern bool juce_isWindowCreatedByJuce (WindowRef window); +} + +#if JUCE_PPC +static void* NewCFMFromMachO (void* const machofp) throw() +{ + void* result = juce_malloc (8); + + ((void**) result)[0] = machofp; + ((void**) result)[1] = result; + + return result; +} +#endif +#endif + +//============================================================================== +static VoidArray activeModules; + +//============================================================================== +class ModuleHandle : public ReferenceCountedObject +{ +public: + //============================================================================== + File file; + MainCall moduleMain; + String pluginName; + + //============================================================================== + static ModuleHandle* findOrCreateModule (const File& file) + { + for (int i = activeModules.size(); --i >= 0;) + { + ModuleHandle* const module = (ModuleHandle*) activeModules.getUnchecked(i); + + if (module->file == file) + return module; + } + + _fpreset(); // (doesn't do any harm) + ++insideVSTCallback; + shellUIDToCreate = 0; + + log ("Attempting to load VST: " + file.getFullPathName()); + + ModuleHandle* m = new ModuleHandle (file); + + if (! m->open()) + deleteAndZero (m); + + --insideVSTCallback; + _fpreset(); // (doesn't do any harm) + + return m; + } + + //============================================================================== + ModuleHandle (const File& file_) + : file (file_), + moduleMain (0), +#if JUCE_WIN32 + hModule (0) +#else + fragId (0), + resHandle (0), + bundleRef (0), + resFileId (0) +#endif + { + activeModules.add (this); + +#if JUCE_WIN32 + fullParentDirectoryPathName = file_.getParentDirectory().getFullPathName(); +#else + PlatformUtilities::makeFSSpecFromPath (&parentDirFSSpec, file_.getParentDirectory().getFullPathName()); +#endif + } + + ~ModuleHandle() + { + activeModules.removeValue (this); + + close(); + } + + //============================================================================== + juce_UseDebuggingNewOperator + + //============================================================================== +#if JUCE_WIN32 + HMODULE hModule; + String fullParentDirectoryPathName; + + static HMODULE loadDLL (const TCHAR* filename) throw() + { + HMODULE h = 0; + + __try + { + h = LoadLibrary (filename); + } + __finally + { + } + + return h; + } + + bool open() + { + static bool timePeriodSet = false; + + if (! timePeriodSet) + { + timePeriodSet = true; + timeBeginPeriod (2); + } + + pluginName = file.getFileNameWithoutExtension(); + + hModule = loadDLL (file.getFullPathName()); + + if (hModule == 0) + return false; + + moduleMain = (MainCall) GetProcAddress (hModule, "VSTPluginMain"); + + if (moduleMain == 0) + moduleMain = (MainCall) GetProcAddress (hModule, "main"); + + return moduleMain != 0; + } + + void close() + { + if (hModule != 0) + { + try + { + _fpreset(); // (doesn't do any harm) + FreeLibrary (hModule); + } + catch (...) + {} + } + } + + void closeEffect (AEffect* eff) + { + eff->dispatcher (eff, effClose, 0, 0, 0, 0); + } + +#else + CFragConnectionID fragId; + Handle resHandle; + CFBundleRef bundleRef; + FSSpec parentDirFSSpec; + short resFileId; + + bool open() + { + bool ok = false; + const String filename (file.getFullPathName()); + + if (file.hasFileExtension (T(".vst"))) + { + CFURLRef url = CFURLCreateFromFileSystemRepresentation (0, (const UInt8*) (const char*) filename, + filename.length(), file.isDirectory()); + + if (url != 0) + { + bundleRef = CFBundleCreate (kCFAllocatorDefault, url); + CFRelease (url); + + if (bundleRef != 0) + { + if (CFBundleLoadExecutable (bundleRef)) + { + moduleMain = (MainCall) CFBundleGetFunctionPointerForName (bundleRef, CFSTR("main_macho")); + + if (moduleMain == 0) + moduleMain = (MainCall) CFBundleGetFunctionPointerForName (bundleRef, CFSTR("VSTPluginMain")); + + if (moduleMain != 0) + { + CFTypeRef name = CFBundleGetValueForInfoDictionaryKey (bundleRef, CFSTR("CFBundleName")); + + if (name != 0) + { + if (CFGetTypeID (name) == CFStringGetTypeID()) + { + char buffer[1024]; + + if (CFStringGetCString ((CFStringRef) name, buffer, sizeof (buffer), CFStringGetSystemEncoding())) + pluginName = buffer; + } + } + + if (pluginName.isEmpty()) + pluginName = file.getFileNameWithoutExtension(); + + resFileId = CFBundleOpenBundleResourceMap (bundleRef); + + ok = true; + } + } + + if (! ok) + { + CFBundleUnloadExecutable (bundleRef); + CFRelease (bundleRef); + bundleRef = 0; + } + } + } + } +#if JUCE_PPC + else + { + FSRef fn; + + if (FSPathMakeRef ((UInt8*) (const char*) filename, &fn, 0) == noErr) + { + resFileId = FSOpenResFile (&fn, fsRdPerm); + + if (resFileId != -1) + { + const int numEffs = Count1Resources ('aEff'); + + for (int i = 0; i < numEffs; ++i) + { + resHandle = Get1IndResource ('aEff', i + 1); + + if (resHandle != 0) + { + OSType type; + Str255 name; + SInt16 id; + GetResInfo (resHandle, &id, &type, name); + pluginName = String ((const char*) name + 1, name[0]); + DetachResource (resHandle); + HLock (resHandle); + + Ptr ptr; + Str255 errorText; + + OSErr err = GetMemFragment (*resHandle, GetHandleSize (resHandle), + name, kPrivateCFragCopy, + &fragId, &ptr, errorText); + + if (err == noErr) + { + moduleMain = (MainCall) newMachOFromCFM (ptr); + ok = true; + } + else + { + HUnlock (resHandle); + } + + break; + } + } + + if (! ok) + CloseResFile (resFileId); + } + } + } +#endif + + return ok; + } + + void close() + { +#if JUCE_PPC + if (fragId != 0) + { + if (moduleMain != 0) + disposeMachOFromCFM ((void*) moduleMain); + + CloseConnection (&fragId); + HUnlock (resHandle); + + if (resFileId != 0) + CloseResFile (resFileId); + } + else +#endif + if (bundleRef != 0) + { + CFBundleCloseBundleResourceMap (bundleRef, resFileId); + + if (CFGetRetainCount (bundleRef) == 1) + CFBundleUnloadExecutable (bundleRef); + + if (CFGetRetainCount (bundleRef) > 0) + CFRelease (bundleRef); + } + } + + void closeEffect (AEffect* eff) + { +#if JUCE_PPC + if (fragId != 0) + { + VoidArray thingsToDelete; + thingsToDelete.add ((void*) eff->dispatcher); + thingsToDelete.add ((void*) eff->process); + thingsToDelete.add ((void*) eff->setParameter); + thingsToDelete.add ((void*) eff->getParameter); + thingsToDelete.add ((void*) eff->processReplacing); + + eff->dispatcher (eff, effClose, 0, 0, 0, 0); + + for (int i = thingsToDelete.size(); --i >= 0;) + disposeMachOFromCFM (thingsToDelete[i]); + } + else +#endif + { + eff->dispatcher (eff, effClose, 0, 0, 0, 0); + } + } + +#if JUCE_PPC + static void* newMachOFromCFM (void* cfmfp) + { + if (cfmfp == 0) + return 0; + + UInt32* const mfp = (UInt32*) juce_malloc (sizeof (UInt32) * 6); + + mfp[0] = 0x3d800000 | ((UInt32) cfmfp >> 16); + mfp[1] = 0x618c0000 | ((UInt32) cfmfp & 0xffff); + mfp[2] = 0x800c0000; + mfp[3] = 0x804c0004; + mfp[4] = 0x7c0903a6; + mfp[5] = 0x4e800420; + + MakeDataExecutable (mfp, sizeof (UInt32) * 6); + return mfp; + } + + static void disposeMachOFromCFM (void* ptr) + { + juce_free (ptr); + } + + void coerceAEffectFunctionCalls (AEffect* eff) + { + if (fragId != 0) + { + eff->dispatcher = (AEffectDispatcherProc) newMachOFromCFM ((void*) eff->dispatcher); + eff->process = (AEffectProcessProc) newMachOFromCFM ((void*) eff->process); + eff->setParameter = (AEffectSetParameterProc) newMachOFromCFM ((void*) eff->setParameter); + eff->getParameter = (AEffectGetParameterProc) newMachOFromCFM ((void*) eff->getParameter); + eff->processReplacing = (AEffectProcessProc) newMachOFromCFM ((void*) eff->processReplacing); + } + } +#endif + +#endif +}; + + +//============================================================================== +VSTPluginInstance::VSTPluginInstance (const ReferenceCountedObjectPtr & module_) + : effect (0), + wantsMidiMessages (false), + initialised (false), + isPowerOn (false), + numAllocatedMidiEvents (0), + midiEventsToSend (0), + tempBuffer (1, 1), + channels (0), + module (module_) +{ + try + { + _fpreset(); + + ++insideVSTCallback; + + name = module->pluginName; + log (T("Creating VST instance: ") + name); + +#if JUCE_MAC + if (module->resFileId != 0) + UseResFile (module->resFileId); + +#if JUCE_PPC + if (module->fragId != 0) + { + static void* audioMasterCoerced = 0; + if (audioMasterCoerced == 0) + audioMasterCoerced = NewCFMFromMachO ((void*) &audioMaster); + + effect = module->moduleMain ((audioMasterCallback) audioMasterCoerced); + } + else +#endif +#endif + { + effect = module->moduleMain (&audioMaster); + } + + --insideVSTCallback; + + if (effect != 0 && effect->magic == kEffectMagic) + { +#if JUCE_PPC + module->coerceAEffectFunctionCalls (effect); +#endif + + jassert (effect->resvd2 == 0); + jassert (effect->object != 0); + + _fpreset(); // some dodgy plugs fuck around with this + } + else + { + effect = 0; + } + } + catch (...) + { + --insideVSTCallback; + } +} + +VSTPluginInstance::~VSTPluginInstance() +{ + { + const ScopedLock sl (lock); + + jassert (insideVSTCallback == 0); + + if (effect != 0 && effect->magic == kEffectMagic) + { + try + { +#if JUCE_MAC + if (module->resFileId != 0) + UseResFile (module->resFileId); +#endif + + // Must delete any editors before deleting the plugin instance! + jassert (getActiveEditor() == 0); + + _fpreset(); // some dodgy plugs fuck around with this + + module->closeEffect (effect); + } + catch (...) + {} + } + + effect = 0; + } + + freeMidiEvents(); + + juce_free (channels); + channels = 0; +} + +//============================================================================== +void VSTPluginInstance::initialise() +{ + if (initialised || effect == 0) + return; + + log (T("Initialising VST: ") + module->pluginName); + initialised = true; + + dispatch (effIdentify, 0, 0, 0, 0); + + { + char buffer [kVstMaxEffectNameLen + 8]; + zerostruct (buffer); + dispatch (effGetEffectName, 0, 0, buffer, 0); + + name = String (buffer); + if (name.trim().isEmpty()) + name = module->pluginName; + } + + dispatch (effSetSampleRate, 0, 0, 0, (float) sampleRate); + dispatch (effSetBlockSize, 0, jmax (16, blockSize), 0, 0); + + dispatch (effOpen, 0, 0, 0, 0); + + numOutputChannels = effect->numOutputs; + numInputChannels = effect->numInputs; + + if (getNumPrograms() > 1) + setCurrentProgram (0); + else + dispatch (effSetProgram, 0, 0, 0, 0); + + int i; + for (i = effect->numInputs; --i >= 0;) + dispatch (effConnectInput, i, 1, 0, 0); + + for (i = effect->numOutputs; --i >= 0;) + dispatch (effConnectOutput, i, 1, 0, 0); + + updateStoredProgramNames(); + + wantsMidiMessages = dispatch (effCanDo, 0, 0, (void*) "receiveVstMidiEvent", 0) != 0; +} + + +//============================================================================== +void JUCE_CALLTYPE VSTPluginInstance::prepareToPlay (double sampleRate_, int samplesPerBlockExpected) +{ + sampleRate = sampleRate_; + blockSize = samplesPerBlockExpected; + midiCollector.reset (sampleRate); + + juce_free (channels); + channels = (float**) juce_calloc (sizeof (float*) * jmax (16, getNumOutputChannels() + 2, getNumInputChannels() + 2)); + + vstHostTime.tempo = 120.0; + vstHostTime.timeSigNumerator = 4; + vstHostTime.timeSigDenominator = 4; + vstHostTime.sampleRate = sampleRate; + vstHostTime.samplePos = 0; + vstHostTime.flags = kVstNanosValid; /*| kVstTransportPlaying | kVstTempoValid | kVstTimeSigValid*/; + + initialise(); + + if (initialised) + { + wantsMidiMessages = wantsMidiMessages + || (dispatch (effCanDo, 0, 0, (void*) "receiveVstMidiEvent", 0) != 0); + + if (wantsMidiMessages) + ensureMidiEventSize (256); + else + freeMidiEvents(); + + incomingMidi.clear(); + + dispatch (effSetSampleRate, 0, 0, 0, (float) sampleRate); + dispatch (effSetBlockSize, 0, jmax (16, blockSize), 0, 0); + + tempBuffer.setSize (effect->numOutputs, blockSize); + + if (! isPowerOn) + setPower (true); + + // dodgy hack to force some plugins to initialise the sample rate.. + if ((! hasEditor()) && getNumParameters() > 0) + { + const float old = getParameter (0); + setParameter (0, (old < 0.5f) ? 1.0f : 0.0f); + setParameter (0, old); + } + + dispatch (effStartProcess, 0, 0, 0, 0); + } +} + +void JUCE_CALLTYPE VSTPluginInstance::releaseResources() +{ + if (initialised) + { + dispatch (effStopProcess, 0, 0, 0, 0); + setPower (false); + } + + midiCollector.reset (sampleRate); + tempBuffer.setSize (1, 1); + incomingMidi.clear(); + + freeMidiEvents(); + juce_free (channels); + channels = 0; +} + +void JUCE_CALLTYPE VSTPluginInstance::processBlock (AudioSampleBuffer& buffer, + MidiBuffer& midiMessages) +{ + const int numSamples = buffer.getNumSamples(); + + if (initialised) + { +#if JUCE_WIN32 + vstHostTime.nanoSeconds = timeGetTime() * 1000000.0; +#else + UnsignedWide micro; + Microseconds (µ); + vstHostTime.nanoSeconds = micro.lo * 1000.0; +#endif + + if (wantsMidiMessages) + { + MidiBuffer::Iterator iter (midiMessages); + + int eventIndex = 0; + const uint8* midiData; + int numBytesOfMidiData, samplePosition; + + while (iter.getNextEvent (midiData, numBytesOfMidiData, samplePosition)) + { + if (numBytesOfMidiData < 4) + { + ensureMidiEventSize (eventIndex); + VstMidiEvent* const e + = (VstMidiEvent*) ((VstEvents*) midiEventsToSend)->events [eventIndex++]; + + // check that some plugin hasn't messed up our objects + jassert (e->type == kVstMidiType); + jassert (e->byteSize == 24); + + e->deltaFrames = jlimit (0, numSamples - 1, samplePosition); + e->noteLength = 0; + e->noteOffset = 0; + e->midiData[0] = midiData[0]; + e->midiData[1] = midiData[1]; + e->midiData[2] = midiData[2]; + e->detune = 0; + e->noteOffVelocity = 0; + } + } + + if (midiEventsToSend == 0) + ensureMidiEventSize (1); + + ((VstEvents*) midiEventsToSend)->numEvents = eventIndex; + + try + { + effect->dispatcher (effect, effProcessEvents, 0, 0, midiEventsToSend, 0); + } + catch (...) + {} + } + + int i; + const int maxChans = jmax (effect->numInputs, effect->numOutputs); + + for (i = 0; i < maxChans; ++i) + channels[i] = buffer.getSampleData (i); + + channels [maxChans] = 0; + + _clearfp(); + + if ((effect->flags & effFlagsCanReplacing) != 0) + { + try + { + effect->processReplacing (effect, channels, channels, numSamples); + } + catch (...) + {} + } + else + { + tempBuffer.setSize (effect->numOutputs, numSamples); + tempBuffer.clear(); + + float* outs [64]; + + for (i = effect->numOutputs; --i >= 0;) + outs[i] = tempBuffer.getSampleData (i); + + outs [effect->numOutputs] = 0; + + try + { + effect->process (effect, channels, outs, numSamples); + } + catch (...) + {} + + for (i = effect->numOutputs; --i >= 0;) + buffer.copyFrom (i, 0, outs[i], numSamples); + } + } + else + { + // Not initialised, so just bypass.. + for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i) + buffer.clear (i, 0, buffer.getNumSamples()); + } + + { + // copy any incoming midi.. + const ScopedLock sl (midiInLock); + + midiMessages = incomingMidi; + incomingMidi.clear(); + } +} + +//============================================================================== +void VSTPluginInstance::ensureMidiEventSize (int numEventsNeeded) +{ + if (numEventsNeeded > numAllocatedMidiEvents) + { + numEventsNeeded = (numEventsNeeded + 32) & ~31; + + const int size = 20 + sizeof (VstEvent*) * numEventsNeeded; + + if (midiEventsToSend == 0) + midiEventsToSend = juce_calloc (size); + else + midiEventsToSend = juce_realloc (midiEventsToSend, size); + + for (int i = numAllocatedMidiEvents; i < numEventsNeeded; ++i) + { + VstMidiEvent* const e = (VstMidiEvent*) juce_calloc (sizeof (VstMidiEvent)); + e->type = kVstMidiType; + e->byteSize = 24; + + ((VstEvents*) midiEventsToSend)->events[i] = (VstEvent*) e; + } + + numAllocatedMidiEvents = numEventsNeeded; + } +} + +void VSTPluginInstance::freeMidiEvents() +{ + if (midiEventsToSend != 0) + { + for (int i = numAllocatedMidiEvents; --i >= 0;) + juce_free (((VstEvents*) midiEventsToSend)->events[i]); + + juce_free (midiEventsToSend); + midiEventsToSend = 0; + numAllocatedMidiEvents = 0; + } +} + +void VSTPluginInstance::handleMidiFromPlugin (const VstEvents* const events) +{ + if (events != 0) + { + const ScopedLock sl (midiInLock); + + for (int i = 0; i < events->numEvents; ++i) + { + const VstEvent* const e = events->events[i]; + + if (e->type == kVstMidiType) + { + incomingMidi.addEvent ((const uint8*) ((const VstMidiEvent*) e)->midiData, + 3, e->deltaFrames); + } + } + } +} + +//============================================================================== +class VSTPluginWindow : public AudioFilterEditor, + public Timer +{ +public: + //============================================================================== + VSTPluginWindow (VSTPluginInstance& plugin_) + : AudioFilterEditor (&plugin_), + plugin (plugin_), + isOpen (false), + wasShowing (false), + pluginRefusesToResize (false), + pluginWantsKeys (false), + alreadyInside (false), + recursiveResize (false) + { +#if JUCE_WIN32 + sizeCheckCount = 0; + pluginHWND = 0; +#else + pluginViewRef = 0; +#endif + + movementWatcher = new CompMovementWatcher (this); + + activeWindows.add (this); + + setOpaque (true); + setVisible (true); + } + + ~VSTPluginWindow() + { + deleteAndZero (movementWatcher); + + closePluginWindow(); + + activeWindows.removeValue (this); + plugin.editorBeingDeleted (this); + } + + //============================================================================== + void componentMovedOrResized() + { + if (recursiveResize) + return; + + Component* const topComp = getTopLevelComponent(); + + if (topComp->getPeer() != 0) + { + int x = 0, y = 0; + relativePositionToOtherComponent (topComp, x, y); + + recursiveResize = true; + +#if JUCE_MAC + if (pluginViewRef != 0) + { + HIRect r; + r.origin.x = (float) x; + r.origin.y = (float) y; + r.size.width = (float) getWidth(); + r.size.height = (float) getHeight(); + HIViewSetFrame (pluginViewRef, &r); + } + else if (pluginWindowRef != 0) + { + Rect r; + r.left = getScreenX(); + r.top = getScreenY(); + r.right = r.left + getWidth(); + r.bottom = r.top + getHeight(); + + WindowGroupRef group = GetWindowGroup (pluginWindowRef); + WindowGroupAttributes atts; + GetWindowGroupAttributes (group, &atts); + ChangeWindowGroupAttributes (group, 0, kWindowGroupAttrMoveTogether); + + SetWindowBounds (pluginWindowRef, kWindowContentRgn, &r); + + if ((atts & kWindowGroupAttrMoveTogether) != 0) + ChangeWindowGroupAttributes (group, kWindowGroupAttrMoveTogether, 0); + } + else + { + repaint(); + } +#else + if (pluginHWND != 0) + MoveWindow (pluginHWND, x, y, getWidth(), getHeight(), TRUE); +#endif + + recursiveResize = false; + } + } + + void componentVisibilityChanged() + { + const bool isShowingNow = isShowing(); + + if (wasShowing != isShowingNow) + { + wasShowing = isShowingNow; + + if (isShowingNow) + openPluginWindow(); + else + closePluginWindow(); + } + + componentMovedOrResized(); + } + + void componentPeerChanged() + { + closePluginWindow(); + openPluginWindow(); + } + + //============================================================================== + bool keyStateChanged() + { + return pluginWantsKeys; + } + + bool keyPressed (const KeyPress&) + { + return pluginWantsKeys; + } + + //============================================================================== + void paint (Graphics& g) + { + if (isOpen) + { + ComponentPeer* const peer = getPeer(); + + if (peer != 0) + { + peer->addMaskedRegion (getScreenX() - peer->getScreenX(), + getScreenY() - peer->getScreenY(), + getWidth(), getHeight()); + +#if JUCE_MAC + dispatch (effEditDraw, 0, 0, 0, 0); +#endif + } + } + else + { + g.fillAll (Colours::black); + } + } + + //============================================================================== + void timerCallback() + { +#if JUCE_WIN32 + if (--sizeCheckCount <= 0) + { + sizeCheckCount = 10; + + checkPluginWindowSize(); + } +#endif + + try + { + static bool reentrant = false; + + if (! reentrant) + { + reentrant = true; + plugin.dispatch (effEditIdle, 0, 0, 0, 0); + reentrant = false; + } + } + catch (...) + {} + } + + //============================================================================== + void mouseDown (const MouseEvent& e) + { +#if JUCE_MAC + if (! alreadyInside) + { + alreadyInside = true; + toFront (true); + dispatch (effEditMouse, e.x, e.y, 0, 0); + alreadyInside = false; + } + else + { + PostEvent (::mouseDown, 0); + } +#else + (void) e; + + toFront (true); +#endif + } + + void broughtToFront() + { + activeWindows.removeValue (this); + activeWindows.add (this); + +#if JUCE_MAC + dispatch (effEditTop, 0, 0, 0, 0); +#endif + } + + //============================================================================== + juce_UseDebuggingNewOperator + +private: + VSTPluginInstance& plugin; + bool isOpen, wasShowing, recursiveResize; + bool pluginWantsKeys, pluginRefusesToResize, alreadyInside; + +#if JUCE_WIN32 + HWND pluginHWND; + void* originalWndProc; + int sizeCheckCount; +#else + HIViewRef pluginViewRef; + WindowRef pluginWindowRef; +#endif + + //============================================================================== + void openPluginWindow() + { + if (isOpen || getWindowHandle() == 0) + return; + + log (T("Opening VST UI: ") + plugin.getName()); + isOpen = true; + + ERect* rect = 0; + dispatch (effEditGetRect, 0, 0, &rect, 0); + dispatch (effEditOpen, 0, 0, getWindowHandle(), 0); + + // do this before and after like in the steinberg example + dispatch (effEditGetRect, 0, 0, &rect, 0); + dispatch (effGetProgram, 0, 0, 0, 0); // also in steinberg code + + // Install keyboard hooks + pluginWantsKeys = (dispatch (effKeysRequired, 0, 0, 0, 0) == 0); + +#if JUCE_WIN32 + originalWndProc = 0; + pluginHWND = GetWindow ((HWND) getWindowHandle(), GW_CHILD); + + if (pluginHWND == 0) + { + isOpen = false; + setSize (300, 150); + return; + } + + #pragma warning (push) + #pragma warning (disable: 4244) + + originalWndProc = (void*) GetWindowLongPtr (pluginHWND, GWL_WNDPROC); + + if (! pluginWantsKeys) + SetWindowLongPtr (pluginHWND, GWL_WNDPROC, (LONG_PTR) vstHookWndProc); + + #pragma warning (pop) + + int w, h; + RECT r; + GetWindowRect (pluginHWND, &r); + w = r.right - r.left; + h = r.bottom - r.top; + + if (rect != 0) + { + const int rw = rect->right - rect->left; + const int rh = rect->bottom - rect->top; + + if ((rw > 50 && rh > 50 && rw < 2000 && rh < 2000 && rw != w && rh != h) + || ((w == 0 && rw > 0) || (h == 0 && rh > 0))) + { + // very dodgy logic to decide which size is right. + if (abs (rw - w) > 350 || abs (rh - h) > 350) + { + SetWindowPos (pluginHWND, 0, + 0, 0, rw, rh, + SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER); + + GetWindowRect (pluginHWND, &r); + + w = r.right - r.left; + h = r.bottom - r.top; + + pluginRefusesToResize = (w != rw) || (h != rh); + + w = rw; + h = rh; + } + } + } +#else + HIViewRef root = HIViewGetRoot ((WindowRef) getWindowHandle()); + HIViewFindByID (root, kHIViewWindowContentID, &root); + pluginViewRef = HIViewGetFirstSubview (root); + + while (pluginViewRef != 0 && juce_isHIViewCreatedByJuce (pluginViewRef)) + pluginViewRef = HIViewGetNextView (pluginViewRef); + + pluginWindowRef = 0; + + if (pluginViewRef == 0) + { + WindowGroupRef ourGroup = GetWindowGroup ((WindowRef) getWindowHandle()); + //DebugPrintWindowGroup (ourGroup); + //DebugPrintAllWindowGroups(); + + GetIndexedWindow (ourGroup, 1, + kWindowGroupContentsVisible, + &pluginWindowRef); + + if (pluginWindowRef == (WindowRef) getWindowHandle() + || juce_isWindowCreatedByJuce (pluginWindowRef)) + pluginWindowRef = 0; + } + + int w = 250, h = 150; + + if (rect != 0) + { + w = rect->right - rect->left; + h = rect->bottom - rect->top; + + if (w == 0 || h == 0) + { + w = 250; + h = 150; + } + } +#endif + + // double-check it's not too tiny + w = jmax (w, 32); + h = jmax (h, 32); + + setSize (w, h); + +#if JUCE_WIN32 + checkPluginWindowSize(); +#endif + + startTimer (18 + juce::Random::getSystemRandom().nextInt (5)); + repaint(); + } + + //============================================================================== + void closePluginWindow() + { + if (isOpen) + { + log (T("Closing VST UI: ") + plugin.getName()); + isOpen = false; + + dispatch (effEditClose, 0, 0, 0, 0); + +#if JUCE_WIN32 + #pragma warning (push) + #pragma warning (disable: 4244) + + if (pluginHWND != 0 && IsWindow (pluginHWND)) + SetWindowLongPtr (pluginHWND, GWL_WNDPROC, (LONG_PTR) originalWndProc); + + #pragma warning (pop) + + stopTimer(); + + if (pluginHWND != 0 && IsWindow (pluginHWND)) + DestroyWindow (pluginHWND); + + pluginHWND = 0; +#else + dispatch (effEditSleep, 0, 0, 0, 0); + pluginViewRef = 0; + stopTimer(); +#endif + } + } + + //============================================================================== +#if JUCE_WIN32 + void checkPluginWindowSize() throw() + { + RECT r; + GetWindowRect (pluginHWND, &r); + const int w = r.right - r.left; + const int h = r.bottom - r.top; + + if (isShowing() && w > 0 && h > 0 + && (w != getWidth() || h != getHeight()) + && ! pluginRefusesToResize) + { + setSize (w, h); + sizeCheckCount = 0; + } + } +#endif + + //============================================================================== + class CompMovementWatcher : public ComponentMovementWatcher + { + public: + CompMovementWatcher (VSTPluginWindow* const owner_) + : ComponentMovementWatcher (owner_), + owner (owner_) + { + } + + //============================================================================== + void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) + { + owner->componentMovedOrResized(); + } + + void componentPeerChanged() + { + owner->componentPeerChanged(); + } + + void componentVisibilityChanged (Component&) + { + owner->componentVisibilityChanged(); + } + + private: + VSTPluginWindow* const owner; + }; + + CompMovementWatcher* movementWatcher; + + //============================================================================== + int dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) + { + return plugin.dispatch (opcode, index, value, ptr, opt); + } + + //============================================================================== + // hooks to get keyboard events from VST windows.. +#if JUCE_WIN32 + static LRESULT CALLBACK vstHookWndProc (HWND hW, UINT message, WPARAM wParam, LPARAM lParam) + { + for (int i = activeWindows.size(); --i >= 0;) + { + const VSTPluginWindow* const w = (const VSTPluginWindow*) activeWindows.getUnchecked (i); + + if (w->pluginHWND == hW) + { + if (message == WM_CHAR + || message == WM_KEYDOWN + || message == WM_SYSKEYDOWN + || message == WM_KEYUP + || message == WM_SYSKEYUP + || message == WM_APPCOMMAND) + { + SendMessage ((HWND) w->getTopLevelComponent()->getWindowHandle(), + message, wParam, lParam); + } + + return CallWindowProc ((WNDPROC) (w->originalWndProc), + (HWND) w->pluginHWND, + message, + wParam, + lParam); + } + } + + return DefWindowProc (hW, message, wParam, lParam); + } +#endif +}; + +//============================================================================== +AudioFilterEditor* JUCE_CALLTYPE VSTPluginInstance::createEditor() +{ + return new VSTPluginWindow (*this); +} + + +//============================================================================== +void VSTPluginInstance::handleAsyncUpdate() +{ + // called asynchronously to indicate that the plugin's parameters have changed.. +} + +//============================================================================== +bool VSTPluginInstance::restoreProgramSettings (const fxProgram* const prog) +{ + if (swap (prog->chunkMagic) == 'CcnK' && swap (prog->fxMagic) == 'FxCk') + { + changeProgramName (getCurrentProgram(), prog->prgName); + + for (int i = 0; i < swap (prog->numParams); ++i) + setParameter (i, swapFloat (prog->params[i])); + + return true; + } + + return false; +} + +bool VSTPluginInstance::loadFromFXBFile (const void* const data, + const int dataSize) +{ + if (dataSize < 28) + return false; + + const fxSet* const set = (const fxSet*) data; + + if ((swap (set->chunkMagic) != 'CcnK' && swap (set->chunkMagic) != 'KncC') + || swap (set->version) > fxbVersionNum) + return false; + + if (swap (set->fxMagic) == 'FxBk') + { + // bank of programs + if (swap (set->numPrograms) >= 0) + { + const int oldProg = getCurrentProgram(); + const int numParams = swap (((const fxProgram*) (set->programs))->numParams); + const int progLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); + + for (int i = 0; i < swap (set->numPrograms); ++i) + { + if (i != oldProg) + { + const fxProgram* const prog = (const fxProgram*) (((const char*) (set->programs)) + i * progLen); + if (((const char*) prog) - ((const char*) set) >= dataSize) + return false; + + if (swap (set->numPrograms) > 0) + setCurrentProgram (i); + + if (! restoreProgramSettings (prog)) + return false; + } + } + + if (swap (set->numPrograms) > 0) + setCurrentProgram (oldProg); + + const fxProgram* const prog = (const fxProgram*) (((const char*) (set->programs)) + oldProg * progLen); + if (((const char*) prog) - ((const char*) set) >= dataSize) + return false; + + if (! restoreProgramSettings (prog)) + return false; + } + } + else if (swap (set->fxMagic) == 'FxCk') + { + // single program + const fxProgram* const prog = (const fxProgram*) data; + + if (swap (prog->chunkMagic) != 'CcnK') + return false; + + changeProgramName (getCurrentProgram(), prog->prgName); + + for (int i = 0; i < swap (prog->numParams); ++i) + setParameter (i, swapFloat (prog->params[i])); + } + else if (swap (set->fxMagic) == 'FBCh' || swap (set->fxMagic) == 'hCBF') + { + // non-preset chunk + const fxChunkSet* const cset = (const fxChunkSet*) data; + + if (swap (cset->chunkSize) + sizeof (fxChunkSet) - 8 > (unsigned int) dataSize) + return false; + + setChunkData (cset->chunk, swap (cset->chunkSize), false); + } + else if (swap (set->fxMagic) == 'FPCh' || swap (set->fxMagic) == 'hCPF') + { + // preset chunk + const fxProgramSet* const cset = (const fxProgramSet*) data; + + if (swap (cset->chunkSize) + sizeof (fxProgramSet) - 8 > (unsigned int) dataSize) + return false; + + setChunkData (cset->chunk, swap (cset->chunkSize), true); + + changeProgramName (getCurrentProgram(), cset->name); + } + else + { + return false; + } + + return true; +} + +//============================================================================== +void VSTPluginInstance::setParamsInProgramBlock (fxProgram* const prog) throw() +{ + const int numParams = getNumParameters(); + + prog->chunkMagic = swap ('CcnK'); + prog->byteSize = 0; + prog->fxMagic = swap ('FxCk'); + prog->version = swap (fxbVersionNum); + prog->fxID = swap (getUID()); + prog->fxVersion = swap (getVersionNumber()); + prog->numParams = swap (numParams); + + getCurrentProgramName().copyToBuffer (prog->prgName, sizeof (prog->prgName) - 1); + + for (int i = 0; i < numParams; ++i) + prog->params[i] = swapFloat (getParameter (i)); +} + +bool VSTPluginInstance::saveToFXBFile (juce::MemoryBlock& dest, bool isFXB, int maxSizeMB) +{ + const int numPrograms = getNumPrograms(); + const int numParams = getNumParameters(); + + if (usesChunks()) + { + if (isFXB) + { + juce::MemoryBlock chunk; + getChunkData (chunk, false, maxSizeMB); + + const int totalLen = sizeof (fxChunkSet) + chunk.getSize() - 8; + dest.setSize (totalLen, true); + + fxChunkSet* const set = (fxChunkSet*) dest.getData(); + set->chunkMagic = swap ('CcnK'); + set->byteSize = 0; + set->fxMagic = swap ('FBCh'); + set->version = swap (fxbVersionNum); + set->fxID = swap (getUID()); + set->fxVersion = swap (getVersionNumber()); + set->numPrograms = swap (numPrograms); + set->chunkSize = swap (chunk.getSize()); + + chunk.copyTo (set->chunk, 0, chunk.getSize()); + } + else + { + juce::MemoryBlock chunk; + getChunkData (chunk, true, maxSizeMB); + + const int totalLen = sizeof (fxProgramSet) + chunk.getSize() - 8; + dest.setSize (totalLen, true); + + fxProgramSet* const set = (fxProgramSet*) dest.getData(); + set->chunkMagic = swap ('CcnK'); + set->byteSize = 0; + set->fxMagic = swap ('FPCh'); + set->version = swap (fxbVersionNum); + set->fxID = swap (getUID()); + set->fxVersion = swap (getVersionNumber()); + set->numPrograms = swap (numPrograms); + set->chunkSize = swap (chunk.getSize()); + + getCurrentProgramName().copyToBuffer (set->name, sizeof (set->name) - 1); + chunk.copyTo (set->chunk, 0, chunk.getSize()); + } + } + else + { + if (isFXB) + { + const int progLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); + const int len = (sizeof (fxSet) - sizeof (fxProgram)) + progLen * jmax (1, numPrograms); + dest.setSize (len, true); + + fxSet* const set = (fxSet*) dest.getData(); + set->chunkMagic = swap ('CcnK'); + set->byteSize = 0; + set->fxMagic = swap ('FxBk'); + set->version = swap (fxbVersionNum); + set->fxID = swap (getUID()); + set->fxVersion = swap (getVersionNumber()); + set->numPrograms = swap (numPrograms); + + const int oldProgram = getCurrentProgram(); + juce::MemoryBlock oldSettings; + createTempParameterStore (oldSettings); + + setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + oldProgram * progLen)); + + for (int i = 0; i < numPrograms; ++i) + { + if (i != oldProgram) + { + setCurrentProgram (i); + setParamsInProgramBlock ((fxProgram*) (((char*) (set->programs)) + i * progLen)); + } + } + + setCurrentProgram (oldProgram); + restoreFromTempParameterStore (oldSettings); + } + else + { + const int totalLen = sizeof (fxProgram) + (numParams - 1) * sizeof (float); + dest.setSize (totalLen, true); + + setParamsInProgramBlock ((fxProgram*) dest.getData()); + } + } + + return true; +} + +void VSTPluginInstance::getChunkData (juce::MemoryBlock& mb, bool isPreset, int maxSizeMB) const +{ + if (usesChunks()) + { + void* data = 0; + const int bytes = dispatch (effGetChunk, isPreset ? 1 : 0, 0, &data, 0.0f); + + if (data != 0 && bytes <= maxSizeMB * 1024 * 1024) + { + mb.setSize (bytes); + mb.copyFrom (data, 0, bytes); + } + } +} + +void VSTPluginInstance::setChunkData (const char* data, int size, bool isPreset) +{ + if (size > 0 && usesChunks()) + { + dispatch (effSetChunk, isPreset ? 1 : 0, size, (void*) data, 0.0f); + + if (! isPreset) + updateStoredProgramNames(); + } +} + +//============================================================================== +void VSTPluginInstance::timerCallback() +{ + if (dispatch (effIdle, 0, 0, 0, 0) == 0) + stopTimer(); +} + +int VSTPluginInstance::dispatch (const int opcode, const int index, const int value, void* const ptr, float opt) const +{ + const ScopedLock sl (lock); + + ++insideVSTCallback; + int result = 0; + + try + { + if (effect != 0) + { +#if JUCE_MAC + if (module->resFileId != 0) + UseResFile (module->resFileId); + + CGrafPtr oldPort; + + if (getActiveEditor() != 0) + { + int x = 0, y = 0; + getActiveEditor()->relativePositionToOtherComponent (getActiveEditor()->getTopLevelComponent(), x, y); + + GetPort (&oldPort); + SetPortWindowPort ((WindowRef) getActiveEditor()->getWindowHandle()); + SetOrigin (-x, -y); + } +#endif + + result = effect->dispatcher (effect, opcode, index, value, ptr, opt); + +#if JUCE_MAC + if (getActiveEditor() != 0) + SetPort (oldPort); + + module->resFileId = CurResFile(); +#endif + + --insideVSTCallback; + return result; + } + } + catch (...) + { + //char s[512]; + //sprintf (s, "dispatcher (%d, %d, %d, %x, %f)", opcode, index, value, (int)ptr, opt); + } + + --insideVSTCallback; + return result; +} + +//============================================================================== +// handles non plugin-specific callbacks.. +static VstIntPtr handleGeneralCallback (VstInt32 opcode, VstInt32 index, VstInt32 value, void *ptr, float opt) +{ + (void) index; + (void) value; + (void) opt; + + switch (opcode) + { + case audioMasterCanDo: + { + static const char* canDos[] = { "supplyIdle", + "sendVstEvents", + "sendVstMidiEvent", + "sendVstTimeInfo", + "receiveVstEvents", + "receiveVstMidiEvent", + "supportShell", + "shellCategory" }; + + for (int i = 0; i < numElementsInArray (canDos); ++i) + if (strcmp (canDos[i], (const char*) ptr) == 0) + return 1; + + return 0; + } + + case audioMasterVersion: + return 0x2400; + case audioMasterCurrentId: + return shellUIDToCreate; + case audioMasterGetNumAutomatableParameters: + return 0; + case audioMasterGetAutomationState: + return 1; + + case audioMasterGetVendorVersion: + return 1; + case audioMasterGetVendorString: + case audioMasterGetProductString: + JUCEApplication::getInstance() + ->getApplicationName().copyToBuffer ((char*) ptr, jmin (kVstMaxVendorStrLen, kVstMaxProductStrLen) - 1); + break; + + case audioMasterGetSampleRate: + return 44100; + + case audioMasterGetBlockSize: + return 512; + + case audioMasterSetOutputSampleRate: + return 0; + + default: + DBG ("*** Unhandled VST Callback: " + String ((int) opcode)); + break; + } + + return 0; +} + +// handles callbacks for a specific plugin +VstIntPtr VSTPluginInstance::handleCallback (VstInt32 opcode, VstInt32 index, VstInt32 value, void *ptr, float opt) +{ + switch (opcode) + { + case audioMasterAutomate: + // index = param num, opt = value + break; + + case audioMasterProcessEvents: + handleMidiFromPlugin ((const VstEvents*) ptr); + break; + + case audioMasterGetTime: + #ifdef _MSC_VER + #pragma warning (push) + #pragma warning (disable: 4311) + #endif + + return (VstIntPtr) &vstHostTime; + + #ifdef _MSC_VER + #pragma warning (pop) + #endif + break; + + case audioMasterIdle: + Thread::sleep (1); + if (! isTimerRunning()) + startTimer (50); + +#if JUCE_MAC + if (getActiveEditor() != 0) + dispatch (effEditIdle, 0, 0, 0, 0); +#endif + break; + + case audioMasterUpdateDisplay: + triggerAsyncUpdate(); + break; + + case audioMasterTempoAt: + // returns (10000 * bpm) + break; + + case audioMasterNeedIdle: + startTimer (50); + break; + + case audioMasterSizeWindow: + if (getActiveEditor() != 0) + getActiveEditor()->setSize (index, value); + + return 1; + + case audioMasterGetSampleRate: + return (VstIntPtr) sampleRate; + + case audioMasterGetBlockSize: + return (VstIntPtr) blockSize; + + case audioMasterWantMidi: + wantsMidiMessages = true; + break; + + case audioMasterGetDirectory: + #if JUCE_MAC + return (VstIntPtr) (void*) &module->parentDirFSSpec; + #else + return (VstIntPtr) (pointer_sized_uint) (const char*) module->fullParentDirectoryPathName; + #endif + + case audioMasterGetAutomationState: + // returns 0: not supported, 1: off, 2:read, 3:write, 4:read/write + break; + + // none of these are handled (yet).. + case audioMasterBeginEdit: + case audioMasterEndEdit: + case audioMasterSetTime: + case audioMasterPinConnected: + case audioMasterGetParameterQuantization: + case audioMasterIOChanged: + case audioMasterGetInputLatency: + case audioMasterGetOutputLatency: + case audioMasterGetPreviousPlug: + case audioMasterGetNextPlug: + case audioMasterWillReplaceOrAccumulate: + case audioMasterGetCurrentProcessLevel: + case audioMasterOfflineStart: + case audioMasterOfflineRead: + case audioMasterOfflineWrite: + case audioMasterOfflineGetCurrentPass: + case audioMasterOfflineGetCurrentMetaPass: + case audioMasterVendorSpecific: + case audioMasterSetIcon: + case audioMasterGetLanguage: + case audioMasterOpenWindow: + case audioMasterCloseWindow: + break; + + default: + return handleGeneralCallback (opcode, index, value, ptr, opt); + } + + return 0; +} + +// entry point for all callbacks from the plugin +static VstIntPtr VSTCALLBACK audioMaster (AEffect* effect, VstInt32 opcode, VstInt32 index, VstIntPtr value, void* ptr, float opt) +{ + try + { + if (effect != 0 && effect->resvd2 != 0) + { + return ((VSTPluginInstance*)(effect->resvd2)) + ->handleCallback (opcode, index, value, ptr, opt); + } + + return handleGeneralCallback (opcode, index, value, ptr, opt); + } + catch (...) + { + return 0; + } +} + +//============================================================================== +const String VSTPluginInstance::getName() const +{ + return name; +} + +const String VSTPluginInstance::getManufacturer() const +{ + char buffer [kVstMaxVendorStrLen + 8]; + zerostruct (buffer); + dispatch (effGetVendorString, 0, 0, buffer, 0); + return buffer; +} + +const String VSTPluginInstance::getVersion() const +{ + int v = dispatch (effGetVendorVersion, 0, 0, 0, 0); + + String s; + + if (v != 0) + { + int versionBits[4]; + int n = 0; + + while (v != 0) + { + versionBits [n++] = (v & 0xff); + v >>= 8; + } + + s << 'V'; + + while (n > 0) + { + s << versionBits [--n]; + + if (n > 0) + s << '.'; + } + } + + return s; +} + +int VSTPluginInstance::getVersionNumber() const throw() +{ + return effect != 0 ? effect->version : 0; +} + +const String VSTPluginInstance::getFormatName() const +{ + return "VST"; +} + +const File VSTPluginInstance::getFile() const +{ + return module->file; +} + +int VSTPluginInstance::getUID() const +{ + int uid = effect != 0 ? effect->uniqueID : 0; + + if (uid == 0) + uid = getFile().hashCode(); + + return uid; +} + +const String VSTPluginInstance::getCategory() const +{ + const char* result = 0; + + switch (dispatch (effGetPlugCategory, 0, 0, 0, 0)) + { + case kPlugCategEffect: + result = "Effect"; + break; + + case kPlugCategSynth: + result = "Synth"; + break; + + case kPlugCategAnalysis: + result = "Anaylsis"; + break; + + case kPlugCategMastering: + result = "Mastering"; + break; + + case kPlugCategSpacializer: + result = "Spacial"; + break; + + case kPlugCategRoomFx: + result = "Reverb"; + break; + + case kPlugSurroundFx: + result = "Surround"; + break; + + case kPlugCategRestoration: + result = "Restoration"; + break; + + case kPlugCategGenerator: + result = "Tone generation"; + break; + + default: + break; + } + + return result; +} + +//============================================================================== +int JUCE_CALLTYPE VSTPluginInstance::getNumParameters() +{ + return effect != 0 ? effect->numParams : 0; +} + +float JUCE_CALLTYPE VSTPluginInstance::getParameter (int index) +{ + if (effect != 0 && index >= 0 && index < effect->numParams) + { + try + { + const ScopedLock sl (lock); + return effect->getParameter (effect, index); + } + catch (...) + { + } + } + + return 0.0f; +} + +void JUCE_CALLTYPE VSTPluginInstance::setParameter (int index, float newValue) +{ + if (effect != 0 && index >= 0 && index < effect->numParams) + { + try + { + const ScopedLock sl (lock); + + if (effect->getParameter (effect, index) != newValue) + effect->setParameter (effect, index, newValue); + } + catch (...) + { + } + } +} + +const String JUCE_CALLTYPE VSTPluginInstance::getParameterName (int index) +{ + if (effect != 0) + { + jassert (index >= 0 && index < effect->numParams); + + char nm [256]; + zerostruct (nm); + dispatch (effGetParamName, index, 0, nm, 0); + return String (nm).trim(); + } + + return String::empty; +} + +const String VSTPluginInstance::getParameterLabel (int index) const +{ + if (effect != 0) + { + jassert (index >= 0 && index < effect->numParams); + + char nm [256]; + zerostruct (nm); + dispatch (effGetParamLabel, index, 0, nm, 0); + return String (nm).trim(); + } + + return String::empty; +} + +const String JUCE_CALLTYPE VSTPluginInstance::getParameterText (int index) +{ + if (effect != 0) + { + jassert (index >= 0 && index < effect->numParams); + + char nm [256]; + zerostruct (nm); + dispatch (effGetParamDisplay, index, 0, nm, 0); + return String (nm).trim(); + } + + return String::empty; +} + +bool VSTPluginInstance::isParameterAutomatable (int index) const +{ + if (effect != 0) + { + jassert (index >= 0 && index < effect->numParams); + return dispatch (effCanBeAutomated, index, 0, 0, 0) != 0; + } + + return false; +} + +void VSTPluginInstance::createTempParameterStore (juce::MemoryBlock& dest) +{ + dest.setSize (64 + 4 * getNumParameters()); + dest.fillWith (0); + + getCurrentProgramName().copyToBuffer ((char*) dest.getData(), 63); + + float* const p = (float*) (((char*) dest.getData()) + 64); + for (int i = 0; i < getNumParameters(); ++i) + p[i] = getParameter(i); +} + +void VSTPluginInstance::restoreFromTempParameterStore (const juce::MemoryBlock& m) +{ + changeProgramName (getCurrentProgram(), (const char*) m); + + float* p = (float*) (((char*) m.getData()) + 64); + for (int i = 0; i < getNumParameters(); ++i) + setParameter (i, p[i]); +} + +//============================================================================== +int JUCE_CALLTYPE VSTPluginInstance::getNumPrograms() +{ + return effect != 0 ? effect->numPrograms : 0; +} + +int JUCE_CALLTYPE VSTPluginInstance::getCurrentProgram() +{ + return dispatch (effGetProgram, 0, 0, 0, 0); +} + +void JUCE_CALLTYPE VSTPluginInstance::setCurrentProgram (int newIndex) +{ + if (getNumPrograms() > 0 && newIndex != getCurrentProgram()) + dispatch (effSetProgram, 0, jlimit (0, getNumPrograms() - 1, newIndex), 0, 0); +} + +const String JUCE_CALLTYPE VSTPluginInstance::getProgramName (int index) +{ + if (index == getCurrentProgram()) + { + return getCurrentProgramName(); + } + else if (effect != 0) + { + char nm [256]; + zerostruct (nm); + + if (dispatch (effGetProgramNameIndexed, + jlimit (0, getNumPrograms(), index), + -1, nm, 0) != 0) + { + return String (nm).trim(); + } + } + + return programNames [index]; +} + +void JUCE_CALLTYPE VSTPluginInstance::changeProgramName (int index, const String& newName) +{ + if (index == getCurrentProgram()) + { + if (getNumPrograms() > 0 && newName != getCurrentProgramName()) + dispatch (effSetProgramName, 0, 0, (void*) (const char*) newName.substring (0, 24), 0.0f); + } + else + { + jassertfalse // xxx not implemented! + } +} + +void VSTPluginInstance::updateStoredProgramNames() +{ + if (effect != 0 && getNumPrograms() > 0) + { + char nm [256]; + zerostruct (nm); + + // only do this if the plugin can't use indexed names.. + if (dispatch (effGetProgramNameIndexed, 0, -1, nm, 0) == 0) + { + const int oldProgram = getCurrentProgram(); + juce::MemoryBlock oldSettings; + createTempParameterStore (oldSettings); + + for (int i = 0; i < getNumPrograms(); ++i) + { + setCurrentProgram (i); + getCurrentProgramName(); // (this updates the list) + } + + setCurrentProgram (oldProgram); + restoreFromTempParameterStore (oldSettings); + } + } +} + +const String VSTPluginInstance::getCurrentProgramName() +{ + if (effect != 0) + { + char nm [256]; + zerostruct (nm); + dispatch (effGetProgramName, 0, 0, nm, 0); + + const int index = getCurrentProgram(); + if (programNames[index].isEmpty()) + { + while (programNames.size() < index) + programNames.add (String::empty); + + programNames.set (index, String (nm).trim()); + } + + return String (nm).trim(); + } + + return String::empty; +} + +//============================================================================== +const String VSTPluginInstance::getInputChannelName (const int index) const +{ + if (index >= 0 && index < getNumInputChannels()) + { + VstPinProperties pinProps; + if (dispatch (effGetInputProperties, index, 0, &pinProps, 0.0f) != 0) + return String (pinProps.label, sizeof (pinProps.label)); + } + + return String::empty; +} + +bool VSTPluginInstance::isInputChannelStereoPair (int index) const +{ + if (index < 0 || index >= getNumInputChannels()) + return false; + + VstPinProperties pinProps; + if (dispatch (effGetInputProperties, index, 0, &pinProps, 0.0f) != 0) + return (pinProps.flags & kVstPinIsStereo) != 0; + + return true; +} + +const String VSTPluginInstance::getOutputChannelName (const int index) const +{ + if (index >= 0 && index < getNumOutputChannels()) + { + VstPinProperties pinProps; + if (dispatch (effGetOutputProperties, index, 0, &pinProps, 0.0f) != 0) + return String (pinProps.label, sizeof (pinProps.label)); + } + + return String::empty; +} + +bool VSTPluginInstance::isOutputChannelStereoPair (int index) const +{ + if (index < 0 || index >= getNumOutputChannels()) + return false; + + VstPinProperties pinProps; + if (dispatch (effGetOutputProperties, index, 0, &pinProps, 0.0f) != 0) + return (pinProps.flags & kVstPinIsStereo) != 0; + + return true; +} + +//============================================================================== +bool VSTPluginInstance::acceptsMidi() const +{ + return wantsMidiMessages; +} + +bool VSTPluginInstance::producesMidi() const +{ + return dispatch (effCanDo, 0, 0, (void*) "sendVstMidiEvent", 0) > 0; +} + +int VSTPluginInstance::getSamplesLatency() const +{ + return effect != 0 ? effect->initialDelay : 0; +} + +void VSTPluginInstance::setPower (const bool on) +{ + dispatch (effMainsChanged, 0, on ? 1 : 0, 0, 0); + isPowerOn = on; +} + +bool VSTPluginInstance::hasEditor() const throw() +{ + return effect != 0 && (effect->flags & effFlagsHasEditor) != 0; +} + +bool VSTPluginInstance::canMono() const throw() +{ + return effect != 0 && (effect->flags & effFlagsCanMono) != 0; +} + +bool VSTPluginInstance::canReplace() const throw() +{ + return effect != 0 && (effect->flags & effFlagsCanReplacing) != 0; +} + +bool VSTPluginInstance::isOffline() const throw() +{ + return dispatch (effCanDo, 0, 0, (void*) "offline", 0) > 0; +} + +bool VSTPluginInstance::isInstrument() const +{ + return effect != 0 && (effect->flags & effFlagsIsSynth) != 0; +} + +bool VSTPluginInstance::usesChunks() const throw() +{ + return effect != 0 && (effect->flags & effFlagsProgramChunks) != 0; +} + + +//============================================================================== +const int defaultMaxSizeMB = 64; + +void JUCE_CALLTYPE VSTPluginInstance::getStateInformation (JUCE_NAMESPACE::MemoryBlock& destData) +{ + saveToFXBFile (destData, true, defaultMaxSizeMB); +} + +void JUCE_CALLTYPE VSTPluginInstance::getCurrentProgramStateInformation (JUCE_NAMESPACE::MemoryBlock& destData) +{ + saveToFXBFile (destData, false, defaultMaxSizeMB); +} + +void JUCE_CALLTYPE VSTPluginInstance::setStateInformation (const void* data, int sizeInBytes) +{ + loadFromFXBFile (data, sizeInBytes); +} + +void JUCE_CALLTYPE VSTPluginInstance::setCurrentProgramStateInformation (const void* data, int sizeInBytes) +{ + loadFromFXBFile (data, sizeInBytes); +} + +//============================================================================== +//============================================================================== +VSTPluginFormat::VSTPluginFormat() +{ +} + +VSTPluginFormat::~VSTPluginFormat() +{ +} + +void VSTPluginFormat::findAllTypesForFile (OwnedArray & results, + const File& file) +{ + if (! fileMightContainThisPluginType (file)) + return; + + PluginDescription desc; + desc.file = file; + desc.uid = 0; + + VSTPluginInstance* instance = dynamic_cast (createInstanceFromDescription (desc)); + + if (instance == 0) + return; + + try + { +#if JUCE_MAC + if (instance->module->resFileId != 0) + UseResFile (instance->module->resFileId); +#endif + + desc.fillInFromInstance (*instance); + + VstPlugCategory category = (VstPlugCategory) instance->dispatch (effGetPlugCategory, 0, 0, 0, 0); + + if (category != kPlugCategShell) + { + // Normal plugin... + results.add (new PluginDescription (desc)); + + ++insideVSTCallback; + instance->dispatch (effOpen, 0, 0, 0, 0); + --insideVSTCallback; + } + else + { + // It's a shell plugin, so iterate all the subtypes... + char shellEffectName [64]; + + for (;;) + { + zerostruct (shellEffectName); + const int uid = instance->dispatch (effShellGetNextPlugin, 0, 0, shellEffectName, 0); + + if (uid == 0) + { + break; + } + else + { + desc.uid = uid; + desc.name = shellEffectName; + + bool alreadyThere = false; + + for (int i = results.size(); --i >= 0;) + { + PluginDescription* const d = results.getUnchecked(i); + + if (d->isDuplicateOf (desc)) + { + alreadyThere = true; + break; + } + } + + if (! alreadyThere) + results.add (new PluginDescription (desc)); + } + } + } + } + catch (...) + { + // crashed while loading... + } + + deleteAndZero (instance); +} + +AudioPluginInstance* VSTPluginFormat::createInstanceFromDescription (const PluginDescription& desc) +{ + VSTPluginInstance* result = 0; + + if (fileMightContainThisPluginType (desc.file)) + { + const File previousWorkingDirectory (File::getCurrentWorkingDirectory()); + desc.file.getParentDirectory().setAsCurrentWorkingDirectory(); + + const ReferenceCountedObjectPtr module (ModuleHandle::findOrCreateModule (desc.file)); + + if (module != 0) + { + shellUIDToCreate = desc.uid; + + result = new VSTPluginInstance (module); + + if (result->effect != 0) + { + result->effect->resvd2 = (VstIntPtr) (pointer_sized_int) result; + result->initialise(); + } + else + { + deleteAndZero (result); + } + } + + previousWorkingDirectory.setAsCurrentWorkingDirectory(); + } + + return result; +} + +bool VSTPluginFormat::fileMightContainThisPluginType (const File& f) +{ +#if JUCE_MAC + if (f.isDirectory() && f.hasFileExtension (T(".vst"))) + return true; + +#if JUCE_PPC + FSRef fileRef; + if (PlatformUtilities::makeFSRefFromPath (&fileRef, f.getFullPathName())) + { + const short resFileId = FSOpenResFile (&fileRef, fsRdPerm); + + if (resFileId != -1) + { + const int numEffects = Count1Resources ('aEff'); + CloseResFile (resFileId); + + if (numEffects > 0) + return true; + } + } +#endif + + return false; +#else + return f.existsAsFile() + && f.hasFileExtension (T(".dll")); +#endif +} + +const FileSearchPath VSTPluginFormat::getDefaultLocationsToSearch() +{ +#if JUCE_MAC + return FileSearchPath ("~/Library/Audio/Plug-Ins/VST;/Library/Audio/Plug-Ins/VST"); +#else + const String programFiles (File::getSpecialLocation (File::globalApplicationsDirectory).getFullPathName()); + + return FileSearchPath (programFiles + "\\Steinberg\\VstPlugins"); +#endif +} diff --git a/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.h b/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.h index d2ff680c01..c3f2d17280 100644 --- a/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.h +++ b/extras/audio plugin host/src/plugins/vst/juce_VSTPluginInstance.h @@ -88,9 +88,7 @@ public: void JUCE_CALLTYPE prepareToPlay (double sampleRate, int estimatedSamplesPerBlock); void JUCE_CALLTYPE releaseResources(); - void JUCE_CALLTYPE processBlock (const AudioSampleBuffer& input, - AudioSampleBuffer& output, - const bool accumulateOutput, + void JUCE_CALLTYPE processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages); AudioFilterEditor* JUCE_CALLTYPE createEditor(); @@ -147,8 +145,7 @@ private: void* midiEventsToSend; int numAllocatedMidiEvents; VstTimeInfo vstHostTime; - float** channelsIn; - float** channelsOut; + float** channels; ReferenceCountedObjectPtr module; diff --git a/extras/audio plugins/demo/build/AudioUnit/JuceDemoAU.xcodeproj/project.pbxproj b/extras/audio plugins/demo/build/AudioUnit/JuceDemoAU.xcodeproj/project.pbxproj index 37a4c41bd1..11d6ac4f63 100644 --- a/extras/audio plugins/demo/build/AudioUnit/JuceDemoAU.xcodeproj/project.pbxproj +++ b/extras/audio plugins/demo/build/AudioUnit/JuceDemoAU.xcodeproj/project.pbxproj @@ -31,6 +31,7 @@ 3E8BF120079CA60300021B09 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5AA9A300281AAB901C34293 /* CoreServices.framework */; }; 3E8BF121079CA60300021B09 /* Carbon.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5DA37E702821D04014500A0 /* Carbon.framework */; }; 3E8BF122079CA60300021B09 /* AudioUnit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5D19ABE0317606901CA2136 /* AudioUnit.framework */; }; + 844C50290C71B6E300D0082E /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844C50280C71B6E300D0082E /* IOKit.framework */; }; 845FAE5F0A5C0A6A008C94D8 /* juce.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 845FAE5E0A5C0A6A008C94D8 /* juce.xcconfig */; }; 845FAEE10A5C2696008C94D8 /* QuickTime.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 845FAEE00A5C2696008C94D8 /* QuickTime.framework */; }; 84CFAEFB090964560053C22C /* AUCarbonViewDispatch.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5D19AAB03175F3201CA2136 /* AUCarbonViewDispatch.cpp */; }; @@ -96,6 +97,7 @@ 7967EF8B04D70E7C00C625F7 /* CAAudioChannelLayout.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CAAudioChannelLayout.cpp; sourceTree = ""; }; 7967EF8C04D70E7C00C625F7 /* CAAudioChannelLayout.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = CAAudioChannelLayout.h; sourceTree = ""; }; 7967EF8D04D70E7C00C625F7 /* CAStreamBasicDescription.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; path = CAStreamBasicDescription.cpp; sourceTree = ""; }; + 844C50280C71B6E300D0082E /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = IOKit.framework; sourceTree = ""; }; 845FAE5E0A5C0A6A008C94D8 /* juce.xcconfig */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = text.xcconfig; name = juce.xcconfig; path = ../../../../../build/macosx/juce.xcconfig; sourceTree = SOURCE_ROOT; }; 845FAEE00A5C2696008C94D8 /* QuickTime.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = QuickTime.framework; sourceTree = ""; }; 84EB4009090A4A2C008FAC1B /* juce_AudioUnitWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = juce_AudioUnitWrapper.cpp; path = ../../../wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp; sourceTree = SOURCE_ROOT; }; @@ -170,6 +172,7 @@ 84F0548F090687F600AEC8DB /* CoreAudio.framework in Frameworks */, 84F87963093B1EDC00225D65 /* AGL.framework in Frameworks */, 845FAEE10A5C2696008C94D8 /* QuickTime.framework in Frameworks */, + 844C50290C71B6E300D0082E /* IOKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -271,6 +274,7 @@ F5AA9A2F0281AAB901C34293 /* CoreFoundation.framework */, F5AA9A300281AAB901C34293 /* CoreServices.framework */, 84F87962093B1EDC00225D65 /* AGL.framework */, + 844C50280C71B6E300D0082E /* IOKit.framework */, 845FAEE00A5C2696008C94D8 /* QuickTime.framework */, ); name = Frameworks; diff --git a/extras/audio plugins/demo/src/DemoJuceFilter.cpp b/extras/audio plugins/demo/src/DemoJuceFilter.cpp index 5542564314..7c78b044ca 100644 --- a/extras/audio plugins/demo/src/DemoJuceFilter.cpp +++ b/extras/audio plugins/demo/src/DemoJuceFilter.cpp @@ -103,6 +103,26 @@ const String DemoJuceFilter::getParameterText (int index) return String::empty; } +const String JUCE_CALLTYPE DemoJuceFilter::getInputChannelName (const int channelIndex) const +{ + return String (channelIndex + 1); +} + +const String JUCE_CALLTYPE DemoJuceFilter::getOutputChannelName (const int channelIndex) const +{ + return String (channelIndex + 1); +} + +bool JUCE_CALLTYPE DemoJuceFilter::isInputChannelStereoPair (int index) const +{ + return false; +} + +bool JUCE_CALLTYPE DemoJuceFilter::isOutputChannelStereoPair (int index) const +{ + return false; +} + //============================================================================== void DemoJuceFilter::prepareToPlay (double sampleRate, int samplesPerBlock) { @@ -116,67 +136,28 @@ void DemoJuceFilter::releaseResources() // spare memory, etc. } -void DemoJuceFilter::processBlock (const AudioSampleBuffer& input, - AudioSampleBuffer& output, - const bool accumulateOutput, +void DemoJuceFilter::processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) { - if (accumulateOutput) + // for each of our input channels, we'll attenuate its level by the + // amount that our volume parameter is set to. + for (int channel = 0; channel < getNumInputChannels(); ++channel) { - // if we're accumulating, we should add our results to the existing contents of the - // output buffer.. - - if (input.getNumChannels() > 0) - { - for (int channel = 0; channel < output.getNumChannels(); ++channel) - { - // for each output channel, add the contents of the corresponding - // input channel (or if there are more outputs than inputs, just - // keep using the last input channel) - output.addFrom (channel, - 0, - input, - jmin (channel, input.getNumChannels() - 1), - 0, - input.getNumSamples(), - gain); - } - } + buffer.applyGain (channel, 0, buffer.getNumSamples(), gain); } - else - { - // if we're not accumulating, the output buffer's contents are undefined - // (don't assume they're zero!) and we should overwrite it. - if (input.getNumChannels() > 0) - { - for (int channel = 0; channel < output.getNumChannels(); ++channel) - { - // for each output channel, copy the contents of the corresponding - // input channel (or if there are more outputs than inputs, just - // keep using the last input channel) - output.copyFrom (channel, - 0, - input, - jmin (channel, input.getNumChannels() - 1), - 0, - input.getNumSamples()); - } - - output.applyGain (0, output.getNumSamples(), gain); - } - else - { - // when not accumulating, you always have to put something into - // the output buffer, even if in this case we have no inputs to copy. - output.clear(); - } + // in case we have more outputs than inputs, we'll clear any output + // channels that didn't contain input data, (because these aren't + // guaranteed to be empty - they may contain garbage). + for (int i = getNumInputChannels(); i < getNumOutputChannels(); ++i) + { + buffer.clear (i, 0, buffer.getNumSamples()); } // if any midi messages come in, use them to update the keyboard state object. This // object sends notification to the UI component about key up/down changes keyboardState.processNextMidiBuffer (midiMessages, - 0, output.getNumSamples(), + 0, buffer.getNumSamples(), true); // have a go at getting the current time from the host, and if it's changed, tell diff --git a/extras/audio plugins/demo/src/DemoJuceFilter.h b/extras/audio plugins/demo/src/DemoJuceFilter.h index 9bcdbdae87..854dc6fc6e 100644 --- a/extras/audio plugins/demo/src/DemoJuceFilter.h +++ b/extras/audio plugins/demo/src/DemoJuceFilter.h @@ -53,9 +53,7 @@ public: void JUCE_CALLTYPE prepareToPlay (double sampleRate, int samplesPerBlock); void JUCE_CALLTYPE releaseResources(); - void JUCE_CALLTYPE processBlock (const AudioSampleBuffer& input, - AudioSampleBuffer& output, - const bool accumulateOutput, + void JUCE_CALLTYPE processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages); //============================================================================== @@ -70,6 +68,11 @@ public: const String JUCE_CALLTYPE getParameterName (int index); const String JUCE_CALLTYPE getParameterText (int index); + const String JUCE_CALLTYPE getInputChannelName (const int channelIndex) const; + const String JUCE_CALLTYPE getOutputChannelName (const int channelIndex) const; + bool JUCE_CALLTYPE isInputChannelStereoPair (int index) const; + bool JUCE_CALLTYPE isOutputChannelStereoPair (int index) const; + //============================================================================== int JUCE_CALLTYPE getNumPrograms() { return 0; } int JUCE_CALLTYPE getCurrentProgram() { return 0; } diff --git a/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp b/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp index 7ae92ca67e..c2679a36f9 100644 --- a/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp +++ b/extras/audio plugins/wrapper/formats/AudioUnit/juce_AudioUnitWrapper.cpp @@ -1,827 +1,846 @@ -/* - ============================================================================== - - This file is part of the JUCE library - "Jules' Utility Class Extensions" - Copyright 2004-7 by Raw Material Software ltd. - - ------------------------------------------------------------------------------ - - JUCE can be redistributed and/or modified under the terms of the - GNU General Public License, as published by the Free Software Foundation; - either version 2 of the License, or (at your option) any later version. - - 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. - - You should have received a copy of the GNU General Public License - along with JUCE; if not, visit www.gnu.org/licenses or write to the - Free Software Foundation, Inc., 59 Temple Place, Suite 330, - Boston, MA 02111-1307 USA - - ------------------------------------------------------------------------------ - - If you'd like to release a closed-source product which uses JUCE, commercial - licenses are also available: visit www.rawmaterialsoftware.com/juce for - more information. - - ============================================================================== -*/ - -#include -#include "AUMIDIEffectBase.h" -#include "AUCarbonViewBase.h" -#include "../../juce_AudioFilterBase.h" -#include "../../juce_IncludeCharacteristics.h" - - -//============================================================================== -#define juceFilterObjectPropertyID 0x1a45ffe9 -static VoidArray activePlugins; - -static const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; -static const int numChannelConfigs = numElementsInArray (channelConfigs); - -BEGIN_JUCE_NAMESPACE -extern void juce_setCurrentExecutableFileNameFromBundleId (const String& bundleId) throw(); -END_JUCE_NAMESPACE - - -//============================================================================== -class JuceAU : public AUMIDIEffectBase, - public AudioFilterBase::FilterNativeCallbacks -{ -public: - //============================================================================== - JuceAU (AudioUnit component) - : AUMIDIEffectBase (component), - juceFilter (0), - bufferSpace (2, 16), - prepared (false) - { - CreateElements(); - - if (activePlugins.size() == 0) - { - initialiseJuce_GUI(); - -#ifdef JucePlugin_CFBundleIdentifier - juce_setCurrentExecutableFileNameFromBundleId (JucePlugin_CFBundleIdentifier); -#endif - - MessageManager::getInstance()->setTimeBeforeShowingWaitCursor (0); - } - - juceFilter = createPluginFilter(); - juceFilter->initialiseInternal (this); - - jassert (juceFilter != 0); - Globals()->UseIndexedParameters (juceFilter->getNumParameters()); - - activePlugins.add (this); - } - - ~JuceAU() - { - delete juceFilter; - juceFilter = 0; - - jassert (activePlugins.contains (this)); - activePlugins.removeValue (this); - - if (activePlugins.size() == 0) - shutdownJuce_GUI(); - } - - //============================================================================== - ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - UInt32& outDataSize, - Boolean& outWritable) - { - if (inScope == kAudioUnitScope_Global) - { - if (inID == juceFilterObjectPropertyID) - { - outWritable = false; - outDataSize = sizeof (void*); - return noErr; - } - } - - return AUMIDIEffectBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); - } - - ComponentResult GetProperty (AudioUnitPropertyID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - void* outData) - { - if (inScope == kAudioUnitScope_Global) - { - if (inID == juceFilterObjectPropertyID) - { - *((void**) outData) = (void*) juceFilter; - return noErr; - } - } - - return AUMIDIEffectBase::GetProperty (inID, inScope, inElement, outData); - } - - ComponentResult SaveState (CFPropertyListRef* outData) - { - ComponentResult err = AUMIDIEffectBase::SaveState (outData); - - if (err != noErr) - return err; - - jassert (CFGetTypeID (*outData) == CFDictionaryGetTypeID()); - - CFMutableDictionaryRef dict = (CFMutableDictionaryRef) *outData; - - if (juceFilter != 0) - { - JUCE_NAMESPACE::MemoryBlock state; - juceFilter->getStateInformation (state); - - if (state.getSize() > 0) - { - CFDataRef ourState = CFDataCreate (kCFAllocatorDefault, (const uint8*) state, state.getSize()); - CFDictionarySetValue (dict, CFSTR("jucePluginState"), ourState); - CFRelease (ourState); - } - } - - return noErr; - } - - ComponentResult RestoreState (CFPropertyListRef inData) - { - ComponentResult err = AUMIDIEffectBase::RestoreState (inData); - - if (err != noErr) - return err; - - if (juceFilter != 0) - { - CFDictionaryRef dict = (CFDictionaryRef) inData; - CFDataRef data = 0; - - if (CFDictionaryGetValueIfPresent (dict, CFSTR("jucePluginState"), - (const void**) &data)) - { - if (data != 0) - { - const int numBytes = (int) CFDataGetLength (data); - const uint8* const rawBytes = CFDataGetBytePtr (data); - - if (numBytes > 0) - juceFilter->setStateInformation (rawBytes, numBytes); - } - } - } - - return noErr; - } - - UInt32 SupportedNumChannels (const AUChannelInfo** outInfo) - { - if (juceFilter == 0) - return 0; - - // You need to actually add some configurations to the JucePlugin_PreferredChannelConfigurations - // value in your JucePluginCharacteristics.h file.. - jassert (numChannelConfigs > 0); - - if (outInfo != 0) - { - for (int i = 0; i < numChannelConfigs; ++i) - { - channelInfo[i].inChannels = channelConfigs[i][0]; - channelInfo[i].outChannels = channelConfigs[i][1]; - - outInfo[i] = channelInfo + i; - } - } - - return numChannelConfigs; - } - - //============================================================================== - ComponentResult GetParameterInfo (AudioUnitScope inScope, - AudioUnitParameterID inParameterID, - AudioUnitParameterInfo& outParameterInfo) - { - if (inScope == kAudioUnitScope_Global && juceFilter != 0) - { - outParameterInfo.flags = kAudioUnitParameterFlag_IsWritable - | kAudioUnitParameterFlag_IsReadable - | kAudioUnitParameterFlag_HasCFNameString; - - outParameterInfo.name[0] = 0; - outParameterInfo.cfNameString = PlatformUtilities::juceStringToCFString (juceFilter->getParameterName ((int) inParameterID)); - outParameterInfo.minValue = 0.0f; - outParameterInfo.maxValue = 1.0f; - outParameterInfo.defaultValue = 0.0f; - outParameterInfo.unit = kAudioUnitParameterUnit_Generic; - - return noErr; - } - else - { - return kAudioUnitErr_InvalidParameter; - } - } - - ComponentResult GetParameter (AudioUnitParameterID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - Float32& outValue) - { - if (inScope == kAudioUnitScope_Global && juceFilter != 0) - { - outValue = juceFilter->getParameter ((int) inID); - return noErr; - } - - return AUBase::GetParameter (inID, inScope, inElement, outValue); - } - - ComponentResult SetParameter (AudioUnitParameterID inID, - AudioUnitScope inScope, - AudioUnitElement inElement, - Float32 inValue, - UInt32 inBufferOffsetInFrames) - { - if (inScope == kAudioUnitScope_Global && juceFilter != 0) - { - juceFilter->setParameter ((int) inID, inValue); - return noErr; - } - - return AUBase::SetParameter (inID, inScope, inElement, inValue, inBufferOffsetInFrames); - } - - //============================================================================== - ComponentResult Version() { return JucePlugin_VersionCode; } - - bool SupportsTail() { return true; } - Float64 GetTailTime() { return 0; } - - Float64 GetLatency() - { - jassert (GetSampleRate() > 0); - - if (GetSampleRate() <= 0) - return 0.0; - - return (JucePlugin_Latency) / GetSampleRate(); - } - - //============================================================================== - int GetNumCustomUIComponents() { return 1; } - - void GetUIComponentDescs (ComponentDescription* inDescArray) - { - inDescArray[0].componentType = kAudioUnitCarbonViewComponentType; - inDescArray[0].componentSubType = JucePlugin_AUSubType; - inDescArray[0].componentManufacturer = JucePlugin_AUManufacturerCode; - inDescArray[0].componentFlags = 0; - inDescArray[0].componentFlagsMask = 0; - } - - //============================================================================== - bool getCurrentPositionInfo (AudioFilterBase::CurrentPositionInfo& info) - { - info.timeSigNumerator = 0; - info.timeSigDenominator = 0; - info.timeInSeconds = 0; - info.editOriginTime = 0; - info.ppqPositionOfLastBarStart = 0; - info.isPlaying = false; - info.isRecording = false; - - switch (lastSMPTETime.mType) - { - case kSMPTETimeType24: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps24; - break; - - case kSMPTETimeType25: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps25; - break; - - case kSMPTETimeType30Drop: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps30drop; - break; - - case kSMPTETimeType30: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps30; - break; - - case kSMPTETimeType2997: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps2997; - break; - - case kSMPTETimeType2997Drop: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fps2997drop; - break; - - //case kSMPTETimeType60: - //case kSMPTETimeType5994: - default: - info.frameRate = AudioFilterBase::CurrentPositionInfo::fpsUnknown; - break; - } - - if (CallHostBeatAndTempo (&info.ppqPosition, &info.bpm) != noErr) - { - info.ppqPosition = 0; - info.bpm = 0; - } - - UInt32 outDeltaSampleOffsetToNextBeat; - double outCurrentMeasureDownBeat; - float num; - UInt32 den; - - if (CallHostMusicalTimeLocation (&outDeltaSampleOffsetToNextBeat, &num, &den, - &outCurrentMeasureDownBeat) == noErr) - { - info.timeSigNumerator = (int) num; - info.timeSigDenominator = den; - info.ppqPositionOfLastBarStart = outCurrentMeasureDownBeat; - } - - double outCurrentSampleInTimeLine, outCycleStartBeat, outCycleEndBeat; - Boolean playing, playchanged, looping; - - if (CallHostTransportState (&playing, - &playchanged, - &outCurrentSampleInTimeLine, - &looping, - &outCycleStartBeat, - &outCycleEndBeat) == noErr) - { - info.isPlaying = playing; - info.timeInSeconds = outCurrentSampleInTimeLine / GetSampleRate(); - } - - return true; - } - - void informHostOfParameterChange (int index, float newValue) - { - if (juceFilter != 0) - { - juceFilter->setParameter (index, newValue); - - if (AUEventListenerNotify != 0) - { - AudioUnitEvent e; - e.mEventType = kAudioUnitEvent_ParameterValueChange; - e.mArgument.mParameter.mAudioUnit = GetComponentInstance(); - e.mArgument.mParameter.mParameterID = (AudioUnitParameterID) index; - e.mArgument.mParameter.mScope = kAudioUnitScope_Global; - e.mArgument.mParameter.mElement = 0; - AUEventListenerNotify (0, 0, &e); - } - } - } - - //============================================================================== - ComponentResult Initialize() - { - AUMIDIEffectBase::Initialize(); - prepareToPlay(); - return noErr; - } - - void Cleanup() - { - AUMIDIEffectBase::Cleanup(); - - if (juceFilter != 0) - juceFilter->releaseResources(); - - bufferSpace.setSize (2, 16); - midiEvents.clear(); - prepared = false; - } - - ComponentResult Reset (AudioUnitScope inScope, AudioUnitElement inElement) - { - if (! prepared) - prepareToPlay(); - - return AUMIDIEffectBase::Reset (inScope, inElement); - } - - void prepareToPlay() - { - if (juceFilter != 0) - { - juceFilter->numInputChannels = GetInput(0)->GetStreamFormat().mChannelsPerFrame; - juceFilter->numOutputChannels = GetOutput(0)->GetStreamFormat().mChannelsPerFrame; - - bufferSpace.setSize (juceFilter->numInputChannels + juceFilter->numOutputChannels, - GetMaxFramesPerSlice() + 32); - - juceFilter->prepareToPlay (GetSampleRate(), - GetMaxFramesPerSlice()); - - midiEvents.clear(); - - prepared = true; - } - } - - ComponentResult Render (AudioUnitRenderActionFlags &ioActionFlags, - const AudioTimeStamp& inTimeStamp, - UInt32 nFrames) - { - lastSMPTETime = inTimeStamp.mSMPTETime; - - return AUMIDIEffectBase::Render (ioActionFlags, inTimeStamp, nFrames); - } - - - OSStatus ProcessBufferLists (AudioUnitRenderActionFlags& ioActionFlags, - const AudioBufferList& inBuffer, - AudioBufferList& outBuffer, - UInt32 inFramesToProcess) - { - if (juceFilter != 0) - { - jassert (prepared); - - float* inChans [64]; - int numInChans = 0; - float* outChans [64]; - int numOutChans = 0; - int nextSpareBufferChan = 0; - bool needToReinterleave = false; - - unsigned int i; - for (i = 0; i < inBuffer.mNumberBuffers; ++i) - { - const AudioBuffer& buf = inBuffer.mBuffers[i]; - - if (buf.mNumberChannels == 1) - { - inChans [numInChans++] = (float*) buf.mData; - } - else - { - // need to de-interleave.. - for (unsigned int subChan = 0; subChan < buf.mNumberChannels; ++subChan) - { - float* dest = bufferSpace.getSampleData (nextSpareBufferChan++); - inChans [numInChans++] = dest; - - const float* src = ((const float*) buf.mData) + subChan; - - for (int j = inFramesToProcess; --j >= 0;) - { - *dest++ = *src; - src += buf.mNumberChannels; - } - } - } - - if (numInChans >= juceFilter->numInputChannels) - break; - } - - const int firstBufferedOutChan = nextSpareBufferChan; - - for (i = 0; i < outBuffer.mNumberBuffers; ++i) - { - AudioBuffer& buf = outBuffer.mBuffers[i]; - - if (buf.mNumberChannels == 1) - { - outChans [numOutChans++] = (float*) buf.mData; - } - else - { - needToReinterleave = true; - - for (unsigned int subChan = 0; subChan < buf.mNumberChannels; ++subChan) - { - float* dest = bufferSpace.getSampleData (nextSpareBufferChan++); - outChans [numOutChans++] = dest; - } - } - - if (numOutChans >= juceFilter->numOutputChannels) - break; - } - - const bool accumulate = false; - - { - const AudioSampleBuffer input (inChans, numInChans, inFramesToProcess); - AudioSampleBuffer output (outChans, numOutChans, inFramesToProcess); - - const ScopedLock sl (juceFilter->getCallbackLock()); - - if (juceFilter->suspended) - output.clear(); - else - juceFilter->processBlock (input, output, accumulate, midiEvents); - } - - if (! midiEvents.isEmpty()) - { -#if JucePlugin_ProducesMidiOutput - const uint8* midiEventData; - int midiEventSize, midiEventPosition; - MidiBuffer::Iterator i (midiEvents); - - while (i.getNextEvent (midiEventData, midiEventSize, midiEventPosition)) - { - jassert (midiEventPosition >= 0 && midiEventPosition < (int) inFramesToProcess); - - - - //xxx - } -#else - // if your plugin creates midi messages, you'll need to set - // the JucePlugin_ProducesMidiOutput macro to 1 in your - // JucePluginCharacteristics.h file - //jassert (midiEvents.getNumEvents() <= numMidiEventsComingIn); -#endif - midiEvents.clear(); - } - - if (needToReinterleave) - { - nextSpareBufferChan = firstBufferedOutChan; - - for (i = 0; i < outBuffer.mNumberBuffers; ++i) - { - AudioBuffer& buf = outBuffer.mBuffers[i]; - - if (buf.mNumberChannels > 1) - { - for (unsigned int subChan = 0; subChan < buf.mNumberChannels; ++subChan) - { - const float* src = bufferSpace.getSampleData (nextSpareBufferChan++); - float* dest = ((float*) buf.mData) + subChan; - - for (int j = inFramesToProcess; --j >= 0;) - { - *dest = *src++; - dest += buf.mNumberChannels; - } - } - } - } - } - -#if ! JucePlugin_SilenceInProducesSilenceOut - ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; -#endif - } - - return noErr; - } - -protected: - OSStatus HandleMidiEvent (UInt8 nStatus, - UInt8 inChannel, - UInt8 inData1, - UInt8 inData2, - long inStartFrame) - { -#if JucePlugin_WantsMidiInput - uint8 data [4]; - data[0] = nStatus | inChannel; - data[1] = inData1; - data[2] = inData2; - - midiEvents.addEvent (data, 3, inStartFrame); -#endif - - return noErr; - } - - //============================================================================== -private: - AudioFilterBase* juceFilter; - AudioSampleBuffer bufferSpace; - MidiBuffer midiEvents; - bool prepared; - SMPTETime lastSMPTETime; - AUChannelInfo channelInfo [numChannelConfigs]; -}; - - -//============================================================================== -class JuceAUComponentHolder : public Component -{ -public: - JuceAUComponentHolder (Component* const editorComp) - { - addAndMakeVisible (editorComp); - setOpaque (true); - setVisible (true); - setBroughtToFrontOnMouseClick (true); - -#if ! JucePlugin_EditorRequiresKeyboardFocus - setWantsKeyboardFocus (false); -#endif - } - - ~JuceAUComponentHolder() - { - } - - void resized() - { - if (getNumChildComponents() > 0) - getChildComponent (0)->setBounds (0, 0, getWidth(), getHeight()); - } - - void paint (Graphics& g) - { - } -}; - -//============================================================================== -class JuceAUView : public AUCarbonViewBase, - public ComponentListener, - public MouseListener, - public Timer -{ - AudioFilterBase* juceFilter; - AudioFilterEditor* editorComp; - Component* windowComp; - bool recursive; - int mx, my; - -public: - JuceAUView (AudioUnitCarbonView auview) - : AUCarbonViewBase (auview), - juceFilter (0), - editorComp (0), - windowComp (0), - recursive (false), - mx (0), - my (0) - { - } - - ~JuceAUView() - { - deleteUI(); - } - - ComponentResult CreateUI (Float32 inXOffset, Float32 inYOffset) - { - if (juceFilter == 0) - { - UInt32 propertySize = sizeof (&juceFilter); - - AudioUnitGetProperty (GetEditAudioUnit(), - juceFilterObjectPropertyID, - kAudioUnitScope_Global, - 0, - &juceFilter, - &propertySize); - } - - if (juceFilter != 0) - { - deleteUI(); - - editorComp = juceFilter->createEditorIfNeeded(); - - const int w = editorComp->getWidth(); - const int h = editorComp->getHeight(); - - editorComp->setOpaque (true); - editorComp->setVisible (true); - - windowComp = new JuceAUComponentHolder (editorComp); - windowComp->setBounds ((int) inXOffset, (int) inYOffset, w, h); - - windowComp->addToDesktop (0, (void*) mCarbonPane); - SizeControl (mCarbonPane, w, h); - - editorComp->addComponentListener (this); - windowComp->addMouseListener (this, true); - - startTimer (20); - } - else - { - jassertfalse // can't get a pointer to our effect - } - - return noErr; - } - - void componentMovedOrResized (Component& component, - bool wasMoved, - bool wasResized) - { - if (! recursive) - { - recursive = true; - - if (editorComp != 0 && wasResized) - { - const int w = jmax (32, editorComp->getWidth()); - const int h = jmax (32, editorComp->getHeight()); - - SizeControl (mCarbonPane, w, h); - - if (windowComp->getWidth() != w - || windowComp->getHeight() != h) - { - windowComp->setSize (w, h); - } - - editorComp->repaint(); - } - - recursive = false; - } - } - - void timerCallback() - { - // for some stupid Apple-related reason, mouse move events just don't seem to get sent - // to the windows in an AU, so we have to bodge it here and simulate them with a - // timer.. - if (editorComp != 0) - { - int x, y; - Desktop::getInstance().getMousePosition (x, y); - - if (x != mx || y != my) - { - mx = x; - my = y; - - if (! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) - { - for (int i = ComponentPeer::getNumPeers(); --i >= 0;) - { - ComponentPeer* const peer = ComponentPeer::getPeer (i); - - const int rx = x - peer->getComponent()->getX(); - const int ry = y - peer->getComponent()->getY(); - - if (peer->contains (rx, ry, false) && peer->getComponent()->isShowing()) - { - peer->handleMouseMove (rx, ry, Time::currentTimeMillis()); - break; - } - } - } - } - } - } - - void mouseMove (const MouseEvent& e) - { - Desktop::getInstance().getMousePosition (mx, my); - startTimer (20); - } - -private: - void deleteUI() - { - PopupMenu::dismissAllActiveMenus(); - - // there's some kind of component currently modal, but the host - // is trying to delete our plugin.. - jassert (Component::getCurrentlyModalComponent() == 0); - - if (editorComp != 0) - juceFilter->editorBeingDeleted (editorComp); - - deleteAndZero (editorComp); - deleteAndZero (windowComp); - } -}; - -//============================================================================== -#define JUCE_COMPONENT_ENTRYX(Class, Name, Suffix) \ -extern "C" __attribute__((visibility("default"))) ComponentResult Name ## Suffix (ComponentParameters* params, Class* obj); \ -extern "C" __attribute__((visibility("default"))) ComponentResult Name ## Suffix (ComponentParameters* params, Class* obj) \ -{ \ - return ComponentEntryPoint::Dispatch(params, obj); \ -} - -#define JUCE_COMPONENT_ENTRY(Class, Name, Suffix) JUCE_COMPONENT_ENTRYX(Class, Name, Suffix) - -JUCE_COMPONENT_ENTRY (JuceAU, JucePlugin_AUExportPrefix, Entry) -JUCE_COMPONENT_ENTRY (JuceAUView, JucePlugin_AUExportPrefix, ViewEntry) +/* + ============================================================================== + + This file is part of the JUCE library - "Jules' Utility Class Extensions" + Copyright 2004-7 by Raw Material Software ltd. + + ------------------------------------------------------------------------------ + + JUCE can be redistributed and/or modified under the terms of the + GNU General Public License, as published by the Free Software Foundation; + either version 2 of the License, or (at your option) any later version. + + 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. + + You should have received a copy of the GNU General Public License + along with JUCE; if not, visit www.gnu.org/licenses or write to the + Free Software Foundation, Inc., 59 Temple Place, Suite 330, + Boston, MA 02111-1307 USA + + ------------------------------------------------------------------------------ + + If you'd like to release a closed-source product which uses JUCE, commercial + licenses are also available: visit www.rawmaterialsoftware.com/juce for + more information. + + ============================================================================== +*/ + +#include +#include "AUMIDIEffectBase.h" +#include "AUCarbonViewBase.h" +#include "../../juce_AudioFilterBase.h" +#include "../../juce_IncludeCharacteristics.h" + + +//============================================================================== +#define juceFilterObjectPropertyID 0x1a45ffe9 +static VoidArray activePlugins; + +static const short channelConfigs[][2] = { JucePlugin_PreferredChannelConfigurations }; +static const int numChannelConfigs = numElementsInArray (channelConfigs); + +BEGIN_JUCE_NAMESPACE +extern void juce_setCurrentExecutableFileNameFromBundleId (const String& bundleId) throw(); +END_JUCE_NAMESPACE + + +//============================================================================== +class JuceAU : public AUMIDIEffectBase, + public AudioFilterBase::FilterNativeCallbacks +{ +public: + //============================================================================== + JuceAU (AudioUnit component) + : AUMIDIEffectBase (component), + juceFilter (0), + bufferSpace (2, 16), + channels (0), + prepared (false) + { + CreateElements(); + + if (activePlugins.size() == 0) + { + initialiseJuce_GUI(); + +#ifdef JucePlugin_CFBundleIdentifier + juce_setCurrentExecutableFileNameFromBundleId (JucePlugin_CFBundleIdentifier); +#endif + + MessageManager::getInstance()->setTimeBeforeShowingWaitCursor (0); + } + + juceFilter = createPluginFilter(); + juceFilter->initialiseInternal (this); + + jassert (juceFilter != 0); + Globals()->UseIndexedParameters (juceFilter->getNumParameters()); + + activePlugins.add (this); + } + + ~JuceAU() + { + delete juceFilter; + juceFilter = 0; + + juce_free (channels); + channels = 0; + + jassert (activePlugins.contains (this)); + activePlugins.removeValue (this); + + if (activePlugins.size() == 0) + shutdownJuce_GUI(); + } + + //============================================================================== + ComponentResult GetPropertyInfo (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + UInt32& outDataSize, + Boolean& outWritable) + { + if (inScope == kAudioUnitScope_Global) + { + if (inID == juceFilterObjectPropertyID) + { + outWritable = false; + outDataSize = sizeof (void*); + return noErr; + } + } + + return AUMIDIEffectBase::GetPropertyInfo (inID, inScope, inElement, outDataSize, outWritable); + } + + ComponentResult GetProperty (AudioUnitPropertyID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + void* outData) + { + if (inScope == kAudioUnitScope_Global) + { + if (inID == juceFilterObjectPropertyID) + { + *((void**) outData) = (void*) juceFilter; + return noErr; + } + } + + return AUMIDIEffectBase::GetProperty (inID, inScope, inElement, outData); + } + + ComponentResult SaveState (CFPropertyListRef* outData) + { + ComponentResult err = AUMIDIEffectBase::SaveState (outData); + + if (err != noErr) + return err; + + jassert (CFGetTypeID (*outData) == CFDictionaryGetTypeID()); + + CFMutableDictionaryRef dict = (CFMutableDictionaryRef) *outData; + + if (juceFilter != 0) + { + JUCE_NAMESPACE::MemoryBlock state; + juceFilter->getStateInformation (state); + + if (state.getSize() > 0) + { + CFDataRef ourState = CFDataCreate (kCFAllocatorDefault, (const uint8*) state, state.getSize()); + CFDictionarySetValue (dict, CFSTR("jucePluginState"), ourState); + CFRelease (ourState); + } + } + + return noErr; + } + + ComponentResult RestoreState (CFPropertyListRef inData) + { + ComponentResult err = AUMIDIEffectBase::RestoreState (inData); + + if (err != noErr) + return err; + + if (juceFilter != 0) + { + CFDictionaryRef dict = (CFDictionaryRef) inData; + CFDataRef data = 0; + + if (CFDictionaryGetValueIfPresent (dict, CFSTR("jucePluginState"), + (const void**) &data)) + { + if (data != 0) + { + const int numBytes = (int) CFDataGetLength (data); + const uint8* const rawBytes = CFDataGetBytePtr (data); + + if (numBytes > 0) + juceFilter->setStateInformation (rawBytes, numBytes); + } + } + } + + return noErr; + } + + UInt32 SupportedNumChannels (const AUChannelInfo** outInfo) + { + if (juceFilter == 0) + return 0; + + // You need to actually add some configurations to the JucePlugin_PreferredChannelConfigurations + // value in your JucePluginCharacteristics.h file.. + jassert (numChannelConfigs > 0); + + if (outInfo != 0) + { + for (int i = 0; i < numChannelConfigs; ++i) + { + channelInfo[i].inChannels = channelConfigs[i][0]; + channelInfo[i].outChannels = channelConfigs[i][1]; + + outInfo[i] = channelInfo + i; + } + } + + return numChannelConfigs; + } + + //============================================================================== + ComponentResult GetParameterInfo (AudioUnitScope inScope, + AudioUnitParameterID inParameterID, + AudioUnitParameterInfo& outParameterInfo) + { + if (inScope == kAudioUnitScope_Global && juceFilter != 0) + { + outParameterInfo.flags = kAudioUnitParameterFlag_IsWritable + | kAudioUnitParameterFlag_IsReadable + | kAudioUnitParameterFlag_HasCFNameString; + + outParameterInfo.name[0] = 0; + outParameterInfo.cfNameString = PlatformUtilities::juceStringToCFString (juceFilter->getParameterName ((int) inParameterID)); + outParameterInfo.minValue = 0.0f; + outParameterInfo.maxValue = 1.0f; + outParameterInfo.defaultValue = 0.0f; + outParameterInfo.unit = kAudioUnitParameterUnit_Generic; + + return noErr; + } + else + { + return kAudioUnitErr_InvalidParameter; + } + } + + ComponentResult GetParameter (AudioUnitParameterID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + Float32& outValue) + { + if (inScope == kAudioUnitScope_Global && juceFilter != 0) + { + outValue = juceFilter->getParameter ((int) inID); + return noErr; + } + + return AUBase::GetParameter (inID, inScope, inElement, outValue); + } + + ComponentResult SetParameter (AudioUnitParameterID inID, + AudioUnitScope inScope, + AudioUnitElement inElement, + Float32 inValue, + UInt32 inBufferOffsetInFrames) + { + if (inScope == kAudioUnitScope_Global && juceFilter != 0) + { + juceFilter->setParameter ((int) inID, inValue); + return noErr; + } + + return AUBase::SetParameter (inID, inScope, inElement, inValue, inBufferOffsetInFrames); + } + + //============================================================================== + ComponentResult Version() { return JucePlugin_VersionCode; } + + bool SupportsTail() { return true; } + Float64 GetTailTime() { return 0; } + + Float64 GetLatency() + { + jassert (GetSampleRate() > 0); + + if (GetSampleRate() <= 0) + return 0.0; + + return (JucePlugin_Latency) / GetSampleRate(); + } + + //============================================================================== + int GetNumCustomUIComponents() { return 1; } + + void GetUIComponentDescs (ComponentDescription* inDescArray) + { + inDescArray[0].componentType = kAudioUnitCarbonViewComponentType; + inDescArray[0].componentSubType = JucePlugin_AUSubType; + inDescArray[0].componentManufacturer = JucePlugin_AUManufacturerCode; + inDescArray[0].componentFlags = 0; + inDescArray[0].componentFlagsMask = 0; + } + + //============================================================================== + bool getCurrentPositionInfo (AudioFilterBase::CurrentPositionInfo& info) + { + info.timeSigNumerator = 0; + info.timeSigDenominator = 0; + info.timeInSeconds = 0; + info.editOriginTime = 0; + info.ppqPositionOfLastBarStart = 0; + info.isPlaying = false; + info.isRecording = false; + + switch (lastSMPTETime.mType) + { + case kSMPTETimeType24: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps24; + break; + + case kSMPTETimeType25: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps25; + break; + + case kSMPTETimeType30Drop: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps30drop; + break; + + case kSMPTETimeType30: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps30; + break; + + case kSMPTETimeType2997: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps2997; + break; + + case kSMPTETimeType2997Drop: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fps2997drop; + break; + + //case kSMPTETimeType60: + //case kSMPTETimeType5994: + default: + info.frameRate = AudioFilterBase::CurrentPositionInfo::fpsUnknown; + break; + } + + if (CallHostBeatAndTempo (&info.ppqPosition, &info.bpm) != noErr) + { + info.ppqPosition = 0; + info.bpm = 0; + } + + UInt32 outDeltaSampleOffsetToNextBeat; + double outCurrentMeasureDownBeat; + float num; + UInt32 den; + + if (CallHostMusicalTimeLocation (&outDeltaSampleOffsetToNextBeat, &num, &den, + &outCurrentMeasureDownBeat) == noErr) + { + info.timeSigNumerator = (int) num; + info.timeSigDenominator = den; + info.ppqPositionOfLastBarStart = outCurrentMeasureDownBeat; + } + + double outCurrentSampleInTimeLine, outCycleStartBeat, outCycleEndBeat; + Boolean playing, playchanged, looping; + + if (CallHostTransportState (&playing, + &playchanged, + &outCurrentSampleInTimeLine, + &looping, + &outCycleStartBeat, + &outCycleEndBeat) == noErr) + { + info.isPlaying = playing; + info.timeInSeconds = outCurrentSampleInTimeLine / GetSampleRate(); + } + + return true; + } + + void informHostOfParameterChange (int index, float newValue) + { + if (juceFilter != 0) + { + juceFilter->setParameter (index, newValue); + + if (AUEventListenerNotify != 0) + { + AudioUnitEvent e; + e.mEventType = kAudioUnitEvent_ParameterValueChange; + e.mArgument.mParameter.mAudioUnit = GetComponentInstance(); + e.mArgument.mParameter.mParameterID = (AudioUnitParameterID) index; + e.mArgument.mParameter.mScope = kAudioUnitScope_Global; + e.mArgument.mParameter.mElement = 0; + AUEventListenerNotify (0, 0, &e); + } + } + } + + //============================================================================== + ComponentResult Initialize() + { + AUMIDIEffectBase::Initialize(); + prepareToPlay(); + return noErr; + } + + void Cleanup() + { + AUMIDIEffectBase::Cleanup(); + + if (juceFilter != 0) + juceFilter->releaseResources(); + + bufferSpace.setSize (2, 16); + midiEvents.clear(); + prepared = false; + } + + ComponentResult Reset (AudioUnitScope inScope, AudioUnitElement inElement) + { + if (! prepared) + prepareToPlay(); + + return AUMIDIEffectBase::Reset (inScope, inElement); + } + + void prepareToPlay() + { + if (juceFilter != 0) + { + juceFilter->numInputChannels = GetInput(0)->GetStreamFormat().mChannelsPerFrame; + juceFilter->numOutputChannels = GetOutput(0)->GetStreamFormat().mChannelsPerFrame; + + bufferSpace.setSize (juceFilter->numInputChannels + juceFilter->numOutputChannels, + GetMaxFramesPerSlice() + 32); + + juceFilter->prepareToPlay (GetSampleRate(), + GetMaxFramesPerSlice()); + + midiEvents.clear(); + + juce_free (channels); + channels = (float**) juce_calloc (sizeof (float*) * jmax (juceFilter->numInputChannels, + juceFilter->numOutputChannels) + 4); + + prepared = true; + } + } + + ComponentResult Render (AudioUnitRenderActionFlags &ioActionFlags, + const AudioTimeStamp& inTimeStamp, + UInt32 nFrames) + { + lastSMPTETime = inTimeStamp.mSMPTETime; + + return AUMIDIEffectBase::Render (ioActionFlags, inTimeStamp, nFrames); + } + + + OSStatus ProcessBufferLists (AudioUnitRenderActionFlags& ioActionFlags, + const AudioBufferList& inBuffer, + AudioBufferList& outBuffer, + UInt32 numSamples) + { + if (juceFilter != 0) + { + jassert (prepared); + + int numOutChans = 0; + int nextSpareBufferChan = 0; + bool needToReinterleave = false; + const int numIn = juceFilter->numInputChannels; + const int numOut = juceFilter->numOutputChannels; + + unsigned int i; + for (i = 0; i < outBuffer.mNumberBuffers; ++i) + { + AudioBuffer& buf = outBuffer.mBuffers[i]; + + if (buf.mNumberChannels == 1) + { + channels [numOutChans++] = (float*) buf.mData; + } + else + { + needToReinterleave = true; + + for (unsigned int subChan = 0; subChan < buf.mNumberChannels && numOutChans < numOut; ++subChan) + channels [numOutChans++] = bufferSpace.getSampleData (nextSpareBufferChan++); + } + + if (numOutChans >= numOut) + break; + } + + int numInChans = 0; + + for (i = 0; i < inBuffer.mNumberBuffers; ++i) + { + const AudioBuffer& buf = inBuffer.mBuffers[i]; + + if (buf.mNumberChannels == 1) + { + if (numInChans < numOut) + memcpy (channels [numInChans], (const float*) buf.mData, sizeof (float) * numSamples); + else + channels [numInChans] = (float*) buf.mData; + } + else + { + // need to de-interleave.. + for (unsigned int subChan = 0; subChan < buf.mNumberChannels && numInChans < numIn; ++subChan) + { + float* dest; + + if (numInChans >= numOut) + { + dest = bufferSpace.getSampleData (nextSpareBufferChan++); + channels [numInChans++] = dest; + } + else + { + dest = channels [numInChans++]; + } + + const float* src = ((const float*) buf.mData) + subChan; + + for (int j = numSamples; --j >= 0;) + { + *dest++ = *src; + src += buf.mNumberChannels; + } + } + } + + if (numInChans >= numIn) + break; + } + + { + AudioSampleBuffer buffer (channels, jmax (numIn, numOut), numSamples); + + const ScopedLock sl (juceFilter->getCallbackLock()); + + if (juceFilter->suspended) + { + for (int i = 0; i < numOut; ++i) + zeromem (channels [i], sizeof (float) * numSamples); + } + else + { + juceFilter->processBlock (buffer, midiEvents); + } + } + + if (! midiEvents.isEmpty()) + { +#if JucePlugin_ProducesMidiOutput + const uint8* midiEventData; + int midiEventSize, midiEventPosition; + MidiBuffer::Iterator i (midiEvents); + + while (i.getNextEvent (midiEventData, midiEventSize, midiEventPosition)) + { + jassert (midiEventPosition >= 0 && midiEventPosition < (int) numSamples); + + + + //xxx + } +#else + // if your plugin creates midi messages, you'll need to set + // the JucePlugin_ProducesMidiOutput macro to 1 in your + // JucePluginCharacteristics.h file + //jassert (midiEvents.getNumEvents() <= numMidiEventsComingIn); +#endif + midiEvents.clear(); + } + + if (needToReinterleave) + { + nextSpareBufferChan = 0; + + for (i = 0; i < outBuffer.mNumberBuffers; ++i) + { + AudioBuffer& buf = outBuffer.mBuffers[i]; + + if (buf.mNumberChannels > 1) + { + for (unsigned int subChan = 0; subChan < buf.mNumberChannels; ++subChan) + { + const float* src = bufferSpace.getSampleData (nextSpareBufferChan++); + float* dest = ((float*) buf.mData) + subChan; + + for (int j = numSamples; --j >= 0;) + { + *dest = *src++; + dest += buf.mNumberChannels; + } + } + } + } + } + +#if ! JucePlugin_SilenceInProducesSilenceOut + ioActionFlags &= ~kAudioUnitRenderAction_OutputIsSilence; +#endif + } + + return noErr; + } + +protected: + OSStatus HandleMidiEvent (UInt8 nStatus, + UInt8 inChannel, + UInt8 inData1, + UInt8 inData2, + long inStartFrame) + { +#if JucePlugin_WantsMidiInput + uint8 data [4]; + data[0] = nStatus | inChannel; + data[1] = inData1; + data[2] = inData2; + + midiEvents.addEvent (data, 3, inStartFrame); +#endif + + return noErr; + } + + //============================================================================== +private: + AudioFilterBase* juceFilter; + AudioSampleBuffer bufferSpace; + float** channels; + MidiBuffer midiEvents; + bool prepared; + SMPTETime lastSMPTETime; + AUChannelInfo channelInfo [numChannelConfigs]; +}; + + +//============================================================================== +class JuceAUComponentHolder : public Component +{ +public: + JuceAUComponentHolder (Component* const editorComp) + { + addAndMakeVisible (editorComp); + setOpaque (true); + setVisible (true); + setBroughtToFrontOnMouseClick (true); + +#if ! JucePlugin_EditorRequiresKeyboardFocus + setWantsKeyboardFocus (false); +#endif + } + + ~JuceAUComponentHolder() + { + } + + void resized() + { + if (getNumChildComponents() > 0) + getChildComponent (0)->setBounds (0, 0, getWidth(), getHeight()); + } + + void paint (Graphics& g) + { + } +}; + +//============================================================================== +class JuceAUView : public AUCarbonViewBase, + public ComponentListener, + public MouseListener, + public Timer +{ + AudioFilterBase* juceFilter; + AudioFilterEditor* editorComp; + Component* windowComp; + bool recursive; + int mx, my; + +public: + JuceAUView (AudioUnitCarbonView auview) + : AUCarbonViewBase (auview), + juceFilter (0), + editorComp (0), + windowComp (0), + recursive (false), + mx (0), + my (0) + { + } + + ~JuceAUView() + { + deleteUI(); + } + + ComponentResult CreateUI (Float32 inXOffset, Float32 inYOffset) + { + if (juceFilter == 0) + { + UInt32 propertySize = sizeof (&juceFilter); + + AudioUnitGetProperty (GetEditAudioUnit(), + juceFilterObjectPropertyID, + kAudioUnitScope_Global, + 0, + &juceFilter, + &propertySize); + } + + if (juceFilter != 0) + { + deleteUI(); + + editorComp = juceFilter->createEditorIfNeeded(); + + const int w = editorComp->getWidth(); + const int h = editorComp->getHeight(); + + editorComp->setOpaque (true); + editorComp->setVisible (true); + + windowComp = new JuceAUComponentHolder (editorComp); + windowComp->setBounds ((int) inXOffset, (int) inYOffset, w, h); + + windowComp->addToDesktop (0, (void*) mCarbonPane); + SizeControl (mCarbonPane, w, h); + + editorComp->addComponentListener (this); + windowComp->addMouseListener (this, true); + + startTimer (20); + } + else + { + jassertfalse // can't get a pointer to our effect + } + + return noErr; + } + + void componentMovedOrResized (Component& component, + bool wasMoved, + bool wasResized) + { + if (! recursive) + { + recursive = true; + + if (editorComp != 0 && wasResized) + { + const int w = jmax (32, editorComp->getWidth()); + const int h = jmax (32, editorComp->getHeight()); + + SizeControl (mCarbonPane, w, h); + + if (windowComp->getWidth() != w + || windowComp->getHeight() != h) + { + windowComp->setSize (w, h); + } + + editorComp->repaint(); + } + + recursive = false; + } + } + + void timerCallback() + { + // for some stupid Apple-related reason, mouse move events just don't seem to get sent + // to the windows in an AU, so we have to bodge it here and simulate them with a + // timer.. + if (editorComp != 0) + { + int x, y; + Desktop::getInstance().getMousePosition (x, y); + + if (x != mx || y != my) + { + mx = x; + my = y; + + if (! ModifierKeys::getCurrentModifiers().isAnyMouseButtonDown()) + { + for (int i = ComponentPeer::getNumPeers(); --i >= 0;) + { + ComponentPeer* const peer = ComponentPeer::getPeer (i); + + const int rx = x - peer->getComponent()->getX(); + const int ry = y - peer->getComponent()->getY(); + + if (peer->contains (rx, ry, false) && peer->getComponent()->isShowing()) + { + peer->handleMouseMove (rx, ry, Time::currentTimeMillis()); + break; + } + } + } + } + } + } + + void mouseMove (const MouseEvent& e) + { + Desktop::getInstance().getMousePosition (mx, my); + startTimer (20); + } + +private: + void deleteUI() + { + PopupMenu::dismissAllActiveMenus(); + + // there's some kind of component currently modal, but the host + // is trying to delete our plugin.. + jassert (Component::getCurrentlyModalComponent() == 0); + + if (editorComp != 0) + juceFilter->editorBeingDeleted (editorComp); + + deleteAndZero (editorComp); + deleteAndZero (windowComp); + } +}; + +//============================================================================== +#define JUCE_COMPONENT_ENTRYX(Class, Name, Suffix) \ +extern "C" __attribute__((visibility("default"))) ComponentResult Name ## Suffix (ComponentParameters* params, Class* obj); \ +extern "C" __attribute__((visibility("default"))) ComponentResult Name ## Suffix (ComponentParameters* params, Class* obj) \ +{ \ + return ComponentEntryPoint::Dispatch(params, obj); \ +} + +#define JUCE_COMPONENT_ENTRY(Class, Name, Suffix) JUCE_COMPONENT_ENTRYX(Class, Name, Suffix) + +JUCE_COMPONENT_ENTRY (JuceAU, JucePlugin_AUExportPrefix, Entry) +JUCE_COMPONENT_ENTRY (JuceAUView, JucePlugin_AUExportPrefix, ViewEntry) diff --git a/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp b/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp index 19ab3b961d..650682fd63 100644 --- a/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp +++ b/extras/audio plugins/wrapper/formats/RTAS/juce_RTASWrapper.cpp @@ -120,7 +120,8 @@ class JucePlugInProcess : public CEffectProcessMIDI, public: //============================================================================== JucePlugInProcess() - : prepared (false), + : channels (0), + prepared (false), midiBufferNode (0), midiTransport (0), sampleRate (44100.0) @@ -143,6 +144,7 @@ public: juceFilter->releaseResources(); delete juceFilter; + juce_free (channels); } //============================================================================== @@ -580,6 +582,10 @@ protected: sampleRate = gProcessGroup->GetSampleRate(); jassert (sampleRate > 0); + juce_free (channels); + channels = (float**) juce_calloc (sizeof (float*) * jmax (juceFilter->getNumInputChannels(), + juceFilter->getNumOutputChannels())); + juceFilter->prepareToPlay (sampleRate, mRTGlobals->mHWBufferSizeInSamples); @@ -632,15 +638,37 @@ protected: #endif { - const AudioSampleBuffer input (inputs, juceFilter->numInputChannels, numSamples); - AudioSampleBuffer output (outputs, juceFilter->numOutputChannels, numSamples); - const ScopedLock sl (juceFilter->getCallbackLock()); + const int numIn = juceFilter->numInputChannels; + const int numOut = juceFilter->numOutputChannels; + const int totalChans = jmax (numIn, numOut); + if (juceFilter->suspended) - bypassBuffers (inputs, outputs, numSamples); + { + for (int i = 0; i < numOut; ++i) + zeromem (outputs [i], sizeof (float) * numSamples); + } else - juceFilter->processBlock (input, output, false, midiEvents); + { + { + int i; + for (i = 0; i < numOut; ++i) + { + channels[i] = outputs [i]; + + if (i < numIn && inputs != outputs) + memcpy (outputs [i], inputs[i], sizeof (float) * numSamples); + } + + for (; i < numIn; ++i) + channels [i] = inputs [i]; + } + + AudioSampleBuffer chans (channels, totalChans, numSamples); + + juceFilter->processBlock (chans, midiEvents); + } } if (! midiEvents.isEmpty()) @@ -826,6 +854,7 @@ private: DirectMidiPacket midiBuffer [midiBufferSize]; juce::MemoryBlock tempFilterData; + float** channels; bool prepared; double sampleRate; diff --git a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp index 03f34ce5b5..c1469d6e5f 100644 --- a/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp +++ b/extras/audio plugins/wrapper/formats/Standalone/juce_AudioFilterStreamer.cpp @@ -86,9 +86,16 @@ void AudioFilterStreamer::audioDeviceIOCallback (const float** inputChannelData, const ScopedLock sl (filter.getCallbackLock()); if (filter.suspended) + { output.clear(); + } else - filter.processBlock (input, output, false, midiBuffer); + { + for (int i = jmin (output.getNumChannels(), input.getNumChannels()); --i >= 0;) + output.copyFrom (i, 0, input, i, 0, numSamples); + + filter.processBlock (output, midiBuffer); + } } while (numOutsWanted < numActiveOutChans) diff --git a/extras/audio plugins/wrapper/formats/VST/juce_VstWrapper.cpp b/extras/audio plugins/wrapper/formats/VST/juce_VstWrapper.cpp index 9e3267bc36..0a89239ec7 100644 --- a/extras/audio plugins/wrapper/formats/VST/juce_VstWrapper.cpp +++ b/extras/audio plugins/wrapper/formats/VST/juce_VstWrapper.cpp @@ -323,6 +323,7 @@ public: chunkMemoryTime = 0; isProcessing = false; firstResize = true; + channels = 0; #if JUCE_MAC || JUCE_LINUX hostWindow = 0; @@ -377,6 +378,8 @@ public: jassert (activePlugins.contains (this)); activePlugins.removeValue (this); + juce_free (channels); + #if JUCE_MAC || JUCE_LINUX if (activePlugins.size() == 0) { @@ -528,9 +531,24 @@ public: #endif } - void process (float** inputs, float** outputs, - const VstInt32 numSamples, const bool accumulate) + void process (float** inputs, float** outputs, VstInt32 numSamples) { + AudioSampleBuffer temp (filter->numInputChannels, numSamples); + int i; + for (i = filter->numInputChannels; --i >= 0;) + memcpy (temp.getSampleData (i), outputs[i], sizeof (float) * numSamples); + + processReplacing (inputs, outputs, numSamples); + + AudioSampleBuffer dest (outputs, filter->numOutputChannels, numSamples); + + for (i = jmin (filter->numOutputChannels, filter->numInputChannels); --i >= 0;) + dest.addFrom (i, 0, temp, i, 0, numSamples); + } + + void processReplacing (float** inputs, float** outputs, VstInt32 numSamples) + { + //process (inputs, outputs, numSamples, false); // if this fails, the host hasn't called resume() before processing jassert (isProcessing); @@ -546,19 +564,36 @@ public: jassert (activePlugins.contains (this)); { - const AudioSampleBuffer input (inputs, filter->numInputChannels, numSamples); - AudioSampleBuffer output (outputs, filter->numOutputChannels, numSamples); - const ScopedLock sl (filter->getCallbackLock()); + const int numIn = filter->numInputChannels; + const int numOut = filter->numOutputChannels; + const int totalChans = jmax (numIn, numOut); + if (filter->suspended) { - if (! accumulate) - output.clear(); + for (int i = 0; i < numOut; ++i) + zeromem (outputs [i], sizeof (float) * numSamples); } else { - filter->processBlock (input, output, accumulate, midiEvents); + { + int i; + for (i = 0; i < numOut; ++i) + { + channels[i] = outputs [i]; + + if (i < numIn && inputs != outputs) + memcpy (outputs [i], inputs[i], sizeof (float) * numSamples); + } + + for (; i < numIn; ++i) + channels [i] = inputs [i]; + } + + AudioSampleBuffer chans (channels, totalChans, numSamples); + + filter->processBlock (chans, midiEvents); } } @@ -610,20 +645,12 @@ public: } } - void process (float** inputs, float** outputs, VstInt32 numSamples) - { - process (inputs, outputs, numSamples, true); - } - - void processReplacing (float** inputs, float** outputs, VstInt32 numSamples) - { - process (inputs, outputs, numSamples, false); - } - //============================================================================== void resume() { isProcessing = true; + juce_free (channels); + channels = (float**) juce_calloc (sizeof (float*) * jmax (filter->getNumInputChannels(), filter->getNumOutputChannels())); filter->sampleRate = getSampleRate(); @@ -656,6 +683,8 @@ public: midiEvents.clear(); isProcessing = false; + juce_free (channels); + channels = 0; } bool JUCE_CALLTYPE getCurrentPositionInfo (AudioFilterBase::CurrentPositionInfo& info) @@ -766,13 +795,13 @@ public: void getParameterDisplay (VstInt32 index, char* text) { jassert (index >= 0 && index < filter->getNumParameters()); - filter->getParameterText (index).copyToBuffer (text, 64); + filter->getParameterText (index).copyToBuffer (text, 24); // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. } void getParameterName (VstInt32 index, char* text) { jassert (index >= 0 && index < filter->getNumParameters()); - filter->getParameterName (index).copyToBuffer (text, 8); + filter->getParameterName (index).copyToBuffer (text, 16); // length should technically be kVstMaxParamStrLen, which is 8, but hosts will normally allow a bit more. } void JUCE_CALLTYPE informHostOfParameterChange (int index, float newValue) @@ -1149,6 +1178,7 @@ private: bool isProcessing; bool firstResize; int diffW, diffH; + float** channels; void ensureOutgoingEventSize (int numEvents) { diff --git a/extras/audio plugins/wrapper/juce_AudioFilterBase.h b/extras/audio plugins/wrapper/juce_AudioFilterBase.h index 1560a944ba..058add0782 100644 --- a/extras/audio plugins/wrapper/juce_AudioFilterBase.h +++ b/extras/audio plugins/wrapper/juce_AudioFilterBase.h @@ -42,14 +42,17 @@ //============================================================================== /** - Base class for plugins written using JUCE. + Base class for audio filters or plugins written using JUCE. - This is intended to act as a base class of plugin that is general enough to - be wrapped as a VST, AU, RTAS, etc. + This is intended to act as a base class of audio filter that is general enough to + be wrapped as a VST, AU, RTAS, etc, or used internally. - Derive your filter class from this base class, and implement a global function - called initialiseFilterInfo() which creates and returns a new instance of - your subclass. + It is also used by the plugin hosting code as the wrapper around an instance + of a loaded plugin. + + Derive your filter class from this base class, and if you're building a plugin, + you should implement a global function called initialiseFilterInfo() which creates + and returns a new instance of your subclass. */ class AudioFilterBase { @@ -67,7 +70,7 @@ public: virtual ~AudioFilterBase(); //============================================================================== - /** Called before playback starts, to let the plugin prepare itself. + /** Called before playback starts, to let the filter prepare itself. The sample rate is the target sample rate, and will remain constant until playback stops. @@ -80,47 +83,50 @@ public: virtual void JUCE_CALLTYPE prepareToPlay (double sampleRate, int estimatedSamplesPerBlock) = 0; - /** Called after playback has stopped, to let the plugin free up any resources it + /** Called after playback has stopped, to let the filter free up any resources it no longer needs. */ virtual void JUCE_CALLTYPE releaseResources() = 0; /** Renders the next block. - The input and output buffers will have been prepared with the number of samples - and channels required, and mustn't be resized. Note that these may both point to - the same block of memory if accumulateOutput is true. There will always be the - same number of samples in the input and output buffers, but the number of channels - may not be the same. - - If accumulateOutput is true, then the output buffer will contain a copy of the - input buffer (or may be physically the same memory - be careful!), and your filter's - output should be added to (or may replace) whatever samples are already in the - output buffer. - - If accumulateOutput is false then the contents of the output buffer are undefined - and MUST ALL be overwritten with your plugin's output. - - Note that the number of samples in these buffers is NOT guaranteed to be - the same for every callback, and may be more or less than the estimated value - given to prepareToPlay(). Your code must be able to cope with variable-sized - blocks, or you're going to get clicks and crashes! - - Your plugin must also not make any assumptions about the number of channels - supplied in the input and output buffers - there could be any number of channels - here, up to the maximum values specified in your JucePluginCharacteristics.h file. - However, the number of channels will remain constant between the prepareToPlay() - and releaseResources() calls. - - If the plugin has indicated that it needs midi input, then the midiMessages - array will be filled with midi messages for this block. Each message's timestamp - will indicate the message's time, as a number of samples from the start of the - block. - - If the plugin has indicated that it produces midi output, then any messages remaining - in the midiMessages array will be sent to the host after the processBlock method - returns. This means that the plugin must be careful to clear any incoming messages - out of the array if it doesn't want them to be passed-on. + When this method is called, the buffer contains a number of channels which is + at least as great as the maximum number of input and output channels that + this filter is using. It will be filled with the filter's input data and + should be replaced with the filter's output. + + So for example if your filter has 2 input channels and 4 output channels, then + the buffer will contain 4 channels, the first two being filled with the + input data. Your filter should read these, do its processing, and replace + the contents of all 4 channels with its output. + + Or if your filter has 5 inputs and 2 outputs, the buffer will have 5 channels, + all filled with data, and your filter should overwrite the first 2 of these + with its output. But be VERY careful not to write anything to the last 3 + channels, as these might be mapped to memory that the host assumes is read-only! + + Note that if you have more outputs than inputs, then only those channels that + correspond to an input channel are guaranteed to contain sensible data - e.g. + in the case of 2 inputs and 4 outputs, the first two channels contain the input, + but the last two channels may contain garbage, so you should be careful not to + let this pass through without being overwritten or cleared. + + Also note that the buffer may have more channels than are strictly necessary, + but your should only read/write from the ones that your filter is supposed to + be using. + + The number of samples in these buffers is NOT guaranteed to be the same for every + callback, and may be more or less than the estimated value given to prepareToPlay(). + Your code must be able to cope with variable-sized blocks, or you're going to get + clicks and crashes! + + If the filter is receiving a midi input, then the midiMessages array will be filled + with the midi messages for this block. Each message's timestamp will indicate the + message's time, as a number of samples from the start of the block. + + Any messages left in the midi buffer when this method has finished are assumed to + be the filter's midi output. This means that your filter should be careful to + clear any incoming messages from the array if it doesn't want them to be passed-on. Be very careful about what you do in this callback - it's going to be called by the audio thread, so any kind of interaction with the UI is absolutely @@ -130,9 +136,7 @@ public: processBlock() method to send out an asynchronous message. You could also use the AsyncUpdater class in a similar way. */ - virtual void JUCE_CALLTYPE processBlock (const AudioSampleBuffer& input, - AudioSampleBuffer& output, - const bool accumulateOutput, + virtual void JUCE_CALLTYPE processBlock (AudioSampleBuffer& buffer, MidiBuffer& midiMessages) = 0; //============================================================================== @@ -230,7 +234,7 @@ public: /** Returns the number of input channels that the host will be sending the filter. In your JucePluginCharacteristics.h file, you specify the number of channels - that your plugin would prefer to get, and this method lets you know how + that your filter would prefer to get, and this method lets you know how many the host is actually going to send. Note that this method is only valid during or after the prepareToPlay() @@ -241,7 +245,7 @@ public: /** Returns the number of output channels that the host will be sending the filter. In your JucePluginCharacteristics.h file, you specify the number of channels - that your plugin would prefer to get, and this method lets you know how + that your filter would prefer to get, and this method lets you know how many the host is actually going to send. Note that this method is only valid during or after the prepareToPlay() @@ -310,24 +314,27 @@ public: void JUCE_CALLTYPE suspendProcessing (const bool shouldBeSuspended); //============================================================================== - /** Creates the plugin's UI. + /** Creates the filter's UI. - This can return 0 if you want a UI-less plugin. Otherwise, the component should - be created and set to the size you want it to be before returning it. + This can return 0 if you want a UI-less filter, in which case the host may create + a generic UI that lets the user twiddle the parameters directly. + + If you do want to pass back a component, the component should be created and set to + the correct size before returning it. - Remember not to do anything silly like allowing your plugin to keep a pointer to - the component that gets created - this may be deleted later without any warning, so - that pointer could become a dangler. Use the getActiveEditor() method instead. + Remember not to do anything silly like allowing your filter to keep a pointer to + the component that gets created - it could be deleted later without any warning, which + would make your pointer into a dangler. Use the getActiveEditor() method instead. The correct way to handle the connection between an editor component and its - plugin is to use something like a ChangeBroadcaster so that the editor can + filter is to use something like a ChangeBroadcaster so that the editor can register itself as a listener, and be told when a change occurs. This lets them safely unregister themselves when they are deleted. - Here are a few assumptions to bear in mind when writing an editor: + Here are a few things to bear in mind when writing an editor: - Initially there won't be an editor, until the user opens one, or they might - not open one at all. Your plugin mustn't rely on it being there. + not open one at all. Your filter mustn't rely on it being there. - An editor object may be deleted and a replacement one created again at any time. - It's safe to assume that an editor will be deleted before its filter. */ @@ -356,24 +363,26 @@ public: /** Returns the name of a particular parameter. */ virtual const String JUCE_CALLTYPE getParameterName (int parameterIndex) = 0; - /** Called by the host to find out the value of one of the plugin's parameters. + /** Called by the host to find out the value of one of the filter's parameters. The host will expect the value returned to be between 0 and 1.0. This could be called quite frequently, so try to make your code efficient. + It's also likely to be called by non-UI threads, so the code in here should + be thread-aware. */ virtual float JUCE_CALLTYPE getParameter (int parameterIndex) = 0; /** Returns the value of a parameter as a text string. */ virtual const String JUCE_CALLTYPE getParameterText (int parameterIndex) = 0; - /** The host will call this method to change the value of one of the plugin's parameters. + /** The host will call this method to change the value of one of the filter's parameters. The host may call this at any time, including during the audio processing - callback, so the plugin has to process this very fast and avoid blocking. + callback, so the filter has to process this very fast and avoid blocking. If you want to set the value of a parameter internally, e.g. from your - plugin editor, then don't call this directly - instead, use the + editor component, then don't call this directly - instead, use the setParameterNotifyingHost() method, which will also send a message to the host telling it about the change. If the message isn't sent, the host won't be able to automate your parameters properly. @@ -383,7 +392,7 @@ public: virtual void JUCE_CALLTYPE setParameter (int parameterIndex, float newValue) = 0; - /** Your plugin can call this when it needs to change one of its parameters. + /** Your filter can call this when it needs to change one of its parameters. This could happen when the editor or some other internal operation changes a parameter. This method will call the setParameter() method to change the @@ -399,7 +408,7 @@ public: virtual bool isParameterAutomatable (int index) const; //============================================================================== - /** Returns the number of preset programs the plugin supports. + /** Returns the number of preset programs the filter supports. The value returned must be valid as soon as this object is created, and must not change over its lifetime. @@ -424,9 +433,9 @@ public: virtual void JUCE_CALLTYPE changeProgramName (int index, const String& newName) = 0; //============================================================================== - /** The host will call this method when it wants to save the plugin's internal state. + /** The host will call this method when it wants to save the filter's internal state. - This must copy any info about the plugin's state into the block of memory provided, + This must copy any info about the filter's state into the block of memory provided, so that the host can store this and later restore it using setStateInformation(). Note that there's also a getCurrentProgramStateInformation() method, which only @@ -438,7 +447,7 @@ public: */ virtual void JUCE_CALLTYPE getStateInformation (JUCE_NAMESPACE::MemoryBlock& destData) = 0; - /** The host will call this method if it wants to save the state of just the plugin's + /** The host will call this method if it wants to save the state of just the filter's current program. Unlike getStateInformation, this should only return the current program's state. @@ -451,7 +460,7 @@ public: */ virtual void JUCE_CALLTYPE getCurrentProgramStateInformation (JUCE_NAMESPACE::MemoryBlock& destData); - /** This must restore the plugin's state from a block of data previously created + /** This must restore the filter's state from a block of data previously created using getStateInformation(). Note that there's also a setCurrentProgramStateInformation() method, which tries @@ -463,7 +472,7 @@ public: */ virtual void JUCE_CALLTYPE setStateInformation (const void* data, int sizeInBytes) = 0; - /** The host will call this method if it wants to restore the state of just the plugin's + /** The host will call this method if it wants to restore the state of just the filter's current program. Not all hosts support this, and if you don't implement it, the base class @@ -504,7 +513,7 @@ protected: //============================================================================== /** Helper function that just converts an xml element into a binary blob. - Use this in your plugin's getStateInformation() method if you want to + Use this in your filter's getStateInformation() method if you want to store its state as xml. Then use getXmlFromBinary() to reverse this operation and retrieve the XML @@ -524,7 +533,7 @@ protected: /** @internal */ double sampleRate; /** @internal */ - int numInputChannels, numOutputChannels, blockSize; + int blockSize, numInputChannels, numOutputChannels; private: friend class JuceVSTWrapper; @@ -544,7 +553,7 @@ private: //============================================================================== /** Somewhere in the code for an actual plugin, you need to implement this function - and make it create an instance of the plugin subclass that you're building. + and make it create an instance of the filter subclass that you're building. */ extern AudioFilterBase* JUCE_CALLTYPE createPluginFilter(); diff --git a/extras/audio plugins/wrapper/juce_AudioFilterEditor.h b/extras/audio plugins/wrapper/juce_AudioFilterEditor.h index bbfca96095..843cc280b6 100644 --- a/extras/audio plugins/wrapper/juce_AudioFilterEditor.h +++ b/extras/audio plugins/wrapper/juce_AudioFilterEditor.h @@ -42,7 +42,7 @@ class AudioFilterBase; //============================================================================== /** - Base class for the component that forms a plugin's GUI. + Base class for the component that forms a filter's GUI. Derive your editor component from this class, and create an instance of it by overriding the AudioFilterBase::createEditor() method. diff --git a/src/juce_appframework/audio/devices/juce_AudioIODevice.h b/src/juce_appframework/audio/devices/juce_AudioIODevice.h index fd4164de8c..8372a980c8 100644 --- a/src/juce_appframework/audio/devices/juce_AudioIODevice.h +++ b/src/juce_appframework/audio/devices/juce_AudioIODevice.h @@ -148,12 +148,18 @@ public: */ const String& getTypeName() const throw() { return typeName; } - /** Returns the names of the available output channels on this device. */ + //============================================================================== + /** Returns the names of all the available output channels on this device. + To find out which of these are currently in use, call getActiveOutputChannels(). + */ virtual const StringArray getOutputChannelNames() = 0; - /** Returns the names of the available input channels on this device. */ + /** Returns the names of all the available input channels on this device. + To find out which of these are currently in use, call getActiveInputChannels(). + */ virtual const StringArray getInputChannelNames() = 0; + //============================================================================== /** Returns the number of sample-rates this device supports. To find out which rates are available on this device, use this method to @@ -273,6 +279,18 @@ public: */ virtual int getCurrentBitDepth() = 0; + /** Returns a mask showing which of the available output channels are currently + enabled. + @see getOutputChannelNames + */ + virtual const BitArray getActiveOutputChannels() const = 0; + + /** Returns a mask showing which of the available input channels are currently + enabled. + @see getInputChannelNames + */ + virtual const BitArray getActiveInputChannels() const = 0; + /** Returns the device's output latency. This is the delay in samples between a callback getting a block of data, and diff --git a/src/juce_appframework/gui/components/windows/juce_TopLevelWindow.cpp b/src/juce_appframework/gui/components/windows/juce_TopLevelWindow.cpp index d06a23633e..e8c4a94a67 100644 --- a/src/juce_appframework/gui/components/windows/juce_TopLevelWindow.cpp +++ b/src/juce_appframework/gui/components/windows/juce_TopLevelWindow.cpp @@ -289,22 +289,7 @@ void TopLevelWindow::addToDesktop (int windowStyleFlags, void* nativeWindowToAtt void TopLevelWindow::centreAroundComponent (Component* c, const int width, const int height) { if (c == 0) - { - if (c == 0) - c = TopLevelWindow::getActiveTopLevelWindow(); - - if (c == 0) - { - c = Component::getCurrentlyFocusedComponent(); - - if (c != 0) - c = c->getTopLevelComponent(); - } - } - else - { - c = c->getTopLevelComponent(); - } + c = TopLevelWindow::getActiveTopLevelWindow(); if (c == 0) { @@ -312,39 +297,21 @@ void TopLevelWindow::centreAroundComponent (Component* c, const int width, const } else { - int cx = c->getWidth() / 2; - int cy = c->getHeight() / 2; - c->relativePositionToGlobal (cx, cy); - - int x = cx - width / 2; - int y = cy - height / 2; + int x = (c->getWidth() - width) / 2; + int y = (c->getHeight() - height) / 2; + c->relativePositionToGlobal (x, y); - if (x <= cx - && y <= cy - && x + width >= cx + c->getWidth() - && y + height >= cy + c->getHeight()) - { - cx = 20; - cy = 20; - c->relativePositionToGlobal (cx, cy); - } + Rectangle parentArea (c->getParentMonitorArea()); if (getParentComponent() != 0) { getParentComponent()->globalPositionToRelative (x, y); - - setBounds (jlimit (0, jmax (0, getParentWidth() - width), x), - jlimit (0, jmax (0, getParentHeight() - height), y), - width, height); + parentArea.setBounds (0, 0, getParentWidth(), getParentHeight()); } - else - { - const Rectangle screenArea (getParentMonitorArea()); - setBounds (jlimit (screenArea.getX(), jmax (screenArea.getX(), screenArea.getWidth() - width), x), - jlimit (screenArea.getY(), jmax (screenArea.getY(), screenArea.getHeight() - height), y), - width, height); - } + setBounds (parentArea.getX() + jlimit (0, jmax (0, parentArea.getWidth() - width), x), + parentArea.getY() + jlimit (0, jmax (0, parentArea.getHeight() - height), y), + width, height); } }