diff --git a/build/linux/platform_specific_code/juce_linux_Audio.cpp b/build/linux/platform_specific_code/juce_linux_Audio.cpp index 8967d2451a..68991865c1 100644 --- a/build/linux/platform_specific_code/juce_linux_Audio.cpp +++ b/build/linux/platform_specific_code/juce_linux_Audio.cpp @@ -932,7 +932,9 @@ public: return namesCopy; } - const String getDefaultDeviceName (const bool /*preferInputNames*/) const + const String getDefaultDeviceName (const bool /*preferInputNames*/, + const int /*numInputChannelsNeeded*/, + const int /*numOutputChannelsNeeded*/) const { jassert (hasScanned); // need to call scanForDevices() before doing this diff --git a/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp b/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp index 385ca08c56..efae3e074e 100644 --- a/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp +++ b/build/macosx/platform_specific_code/juce_mac_CoreAudio.cpp @@ -1,1185 +1,1193 @@ -/* - ============================================================================== - - 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 (((unsigned int) 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 +/* + ============================================================================== + + 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 (((unsigned int) 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; + + // this seems like a good guess at getting the latency right - comparing + // this with a round-trip measurement, it gets it to within a few millisecs + // for the built-in mac soundcard + return internal->outputLatency + internal->getBufferSize() * 2; + } + + int getInputLatencyInSamples() + { + if (internal == 0) + return 0; + + return internal->inputLatency + internal->getBufferSize() * 2; + } + + 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 int numInputChannelsNeeded, + const int numOutputChannelsNeeded) const + { + jassert (hasScanned); // need to call scanForDevices() before doing this + + String result (names[0]); + + AudioDeviceID deviceID; + UInt32 size = sizeof (deviceID); + + // if they're asking for any input channels at all, use the default input, so we + // get the built-in mic rather than the built-in output with no inputs.. + if (AudioHardwareGetProperty (numInputChannelsNeeded > 0 + ? 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 f1192c3628..5947869aaf 100644 --- a/build/win32/platform_specific_code/juce_win32_ASIO.cpp +++ b/build/win32/platform_specific_code/juce_win32_ASIO.cpp @@ -751,12 +751,12 @@ public: int getOutputLatencyInSamples() { - return outputLatency; + return outputLatency + currentBlockSizeSamples / 4; } int getInputLatencyInSamples() { - return inputLatency; + return inputLatency + currentBlockSizeSamples / 4; } void start (AudioIODeviceCallback* callback) @@ -1809,7 +1809,9 @@ public: return deviceNames; } - const String getDefaultDeviceName (const bool /*preferInputNames*/) const + const String getDefaultDeviceName (const bool /*preferInputNames*/, + const int /*numInputChannelsNeeded*/, + const int /*numOutputChannelsNeeded*/) const { jassert (hasScanned); // need to call scanForDevices() before doing this diff --git a/build/win32/platform_specific_code/juce_win32_DirectSound.cpp b/build/win32/platform_specific_code/juce_win32_DirectSound.cpp index 00a7b5f86b..adc03d73bd 100644 --- a/build/win32/platform_specific_code/juce_win32_DirectSound.cpp +++ b/build/win32/platform_specific_code/juce_win32_DirectSound.cpp @@ -1477,7 +1477,9 @@ public: : outputDeviceNames; } - const String getDefaultDeviceName (const bool preferInputNames) const + const String getDefaultDeviceName (const bool preferInputNames, + const int /*numInputChannelsNeeded*/, + const int /*numOutputChannelsNeeded*/) const { jassert (hasScanned); // need to call scanForDevices() before doing this diff --git a/docs/JUCE changelist.txt b/docs/JUCE changelist.txt index 63cba4bfed..ec96bbad6f 100644 --- a/docs/JUCE changelist.txt +++ b/docs/JUCE changelist.txt @@ -11,6 +11,7 @@ Changelist for version 1.46 - added AudioUnit support to the audio hosting code - any top-level components will now have their parentSizeChanged() method called when the screen res is changed (not on linux yet though..) - jucer: added support for ImageButtons +- audio devices - a few tweaks to the various audio drivers to try to make the best possible guess at the input and output latencies that they introduce ============================================================================== Changelist for version 1.45 diff --git a/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp b/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp index edef0dd6d6..bfb92e50bb 100644 --- a/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp +++ b/src/juce_appframework/audio/devices/juce_AudioDeviceManager.cpp @@ -118,7 +118,9 @@ const String AudioDeviceManager::initialise (const int numInputChannelsNeeded, String defaultDevice; if (availableDeviceTypes [0] != 0) - defaultDevice = availableDeviceTypes[0]->getDefaultDeviceName (numOutputChannelsNeeded == 0); + defaultDevice = availableDeviceTypes[0]->getDefaultDeviceName (numOutputChannelsNeeded == 0, + numInputChannelsNeeded, + numOutputChannelsNeeded); return setAudioDevice (defaultDevice, 0, 0, 0, 0, false); } diff --git a/src/juce_appframework/audio/devices/juce_AudioIODeviceType.cpp b/src/juce_appframework/audio/devices/juce_AudioIODeviceType.cpp index 4d8cf7c0c4..8575617643 100644 --- a/src/juce_appframework/audio/devices/juce_AudioIODeviceType.cpp +++ b/src/juce_appframework/audio/devices/juce_AudioIODeviceType.cpp @@ -49,10 +49,15 @@ AudioIODeviceType::~AudioIODeviceType() //============================================================================== extern AudioIODeviceType* juce_createDefaultAudioIODeviceType(); -#if JUCE_ASIO && JUCE_WIN32 +#if JUCE_WIN32 && JUCE_ASIO extern AudioIODeviceType* juce_createASIOAudioIODeviceType(); #endif +#if JUCE_WIN32 && JUCE_WDM_AUDIO + extern AudioIODeviceType* juce_createWDMAudioIODeviceType(); +#endif + +//============================================================================== void AudioIODeviceType::createDeviceTypes (OwnedArray & list) { AudioIODeviceType* const defaultDeviceType = juce_createDefaultAudioIODeviceType(); @@ -60,9 +65,13 @@ void AudioIODeviceType::createDeviceTypes (OwnedArray & list) if (defaultDeviceType != 0) list.add (defaultDeviceType); -#if JUCE_ASIO && JUCE_WIN32 +#if JUCE_WIN32 && JUCE_ASIO list.add (juce_createASIOAudioIODeviceType()); #endif + +#if JUCE_WIN32 && JUCE_WDM_AUDIO + list.add (juce_createWDMAudioIODeviceType()); +#endif } diff --git a/src/juce_appframework/audio/devices/juce_AudioIODeviceType.h b/src/juce_appframework/audio/devices/juce_AudioIODeviceType.h index d6cfcd831e..1b689e20ba 100644 --- a/src/juce_appframework/audio/devices/juce_AudioIODeviceType.h +++ b/src/juce_appframework/audio/devices/juce_AudioIODeviceType.h @@ -117,8 +117,14 @@ public: @param preferInputNames only really used by DirectSound where devices are split up into inputs and outputs, this indicates whether to use the input or output name to refer to a pair of devices. + @param numInputChannelsNeeded the number of input channels the user is expecting to need - this + may be used to help decide which device would be most suitable + @param numOutputChannelsNeeded the number of output channels the user is expecting to need - this + may be used to help decide which device would be most suitable */ - virtual const String getDefaultDeviceName (const bool preferInputNames = false) const = 0; + virtual const String getDefaultDeviceName (const bool preferInputNames, + const int numInputChannelsNeeded, + const int numOutputChannelsNeeded) const = 0; /** Creates one of the devices of this type. diff --git a/src/juce_appframework/audio/dsp/juce_AudioDataConverters.cpp b/src/juce_appframework/audio/dsp/juce_AudioDataConverters.cpp index b96b38f1f2..d4ec6cdc82 100644 --- a/src/juce_appframework/audio/dsp/juce_AudioDataConverters.cpp +++ b/src/juce_appframework/audio/dsp/juce_AudioDataConverters.cpp @@ -37,25 +37,31 @@ BEGIN_JUCE_NAMESPACE //============================================================================== -void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples) +void AudioDataConverters::convertFloatToInt16LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fff; - uint16* const intData = (uint16*) dest; + char* intData = (char*) dest; for (int i = 0; i < numSamples; ++i) - intData[i] = swapIfBigEndian ((uint16) (short) roundDoubleToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + { + *(uint16*)intData = swapIfBigEndian ((uint16) (short) roundDoubleToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + intData += destBytesPerSample; + } } -void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples) +void AudioDataConverters::convertFloatToInt16BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fff; - uint16* const intData = (uint16*) dest; + char* intData = (char*) dest; for (int i = 0; i < numSamples; ++i) - intData[i] = swapIfLittleEndian ((uint16) (short) roundDoubleToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + { + *(uint16*)intData = swapIfLittleEndian ((uint16) (short) roundDoubleToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + intData += destBytesPerSample; + } } -void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples) +void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffff; char* intData = (char*) dest; @@ -63,11 +69,11 @@ void AudioDataConverters::convertFloatToInt24LE (const float* source, void* dest for (int i = 0; i < numSamples; ++i) { littleEndian24BitToChars ((uint32) roundDoubleToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); - intData += 3; + intData += destBytesPerSample; } } -void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples) +void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffff; char* intData = (char*) dest; @@ -75,119 +81,174 @@ void AudioDataConverters::convertFloatToInt24BE (const float* source, void* dest for (int i = 0; i < numSamples; ++i) { bigEndian24BitToChars ((uint32) roundDoubleToInt (jlimit (-maxVal, maxVal, maxVal * source[i])), intData); - intData += 3; + intData += destBytesPerSample; } } -void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples) +void AudioDataConverters::convertFloatToInt32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffffff; - uint32* const intData = (uint32*) dest; + char* intData = (char*) dest; for (int i = 0; i < numSamples; ++i) - intData[i] = swapIfBigEndian ((uint32) roundDoubleToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + { + *(uint32*)intData = swapIfBigEndian ((uint32) roundDoubleToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + intData += destBytesPerSample; + } } -void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples) +void AudioDataConverters::convertFloatToInt32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { const double maxVal = (double) 0x7fffffff; - uint32* const intData = (uint32*) dest; + char* intData = (char*) dest; for (int i = 0; i < numSamples; ++i) - intData[i] = swapIfLittleEndian ((uint32) roundDoubleToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + { + *(uint32*)intData = swapIfLittleEndian ((uint32) roundDoubleToInt (jlimit (-maxVal, maxVal, maxVal * source[i]))); + intData += destBytesPerSample; + } } -void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples) +void AudioDataConverters::convertFloatToFloat32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { - if (source != (const float*) dest) - memcpy (dest, source, numSamples * sizeof (float)); - -#if JUCE_BIG_ENDIAN - uint32* const data = (uint32*) dest; + char* d = (char*) dest; for (int i = 0; i < numSamples; ++i) - data[i] = swapByteOrder (data [i]); + { + *(float*)d = source[i]; + +#if JUCE_BIG_ENDIAN + *(uint32*)d = swapByteOrder (*(uint32*)d); #endif + + d += destBytesPerSample; + } } -void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples) +void AudioDataConverters::convertFloatToFloat32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample) { - if (source != (const float*) dest) - memcpy (dest, source, numSamples * sizeof (float)); - -#if JUCE_LITTLE_ENDIAN - uint32* const data = (uint32*) dest; + char* d = (char*) dest; for (int i = 0; i < numSamples; ++i) - data[i] = swapByteOrder (data [i]); + { + *(float*)d = source[i]; + +#if JUCE_LITTLE_ENDIAN + *(uint32*)d = swapByteOrder (*(uint32*)d); #endif + + d += destBytesPerSample; + } } //============================================================================== -void AudioDataConverters::convertInt16LEToFloat (const void* const source, float* const dest, int numSamples) +void AudioDataConverters::convertInt16LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fff; - uint16* const intData = (uint16*) source; + const char* intData = (const char*) source; - while (--numSamples >= 0) - dest [numSamples] = scale * (short) swapIfBigEndian (intData [numSamples]); + for (int i = 0; i < numSamples; ++i) + { + dest[i] = scale * (short) swapIfBigEndian (*(uint16*)intData); + intData += srcBytesPerSample; + } } -void AudioDataConverters::convertInt16BEToFloat (const void* const source, float* const dest, int numSamples) +void AudioDataConverters::convertInt16BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fff; - uint16* const intData = (uint16*) source; + const char* intData = (const char*) source; - while (--numSamples >= 0) - dest [numSamples] = scale * (short) swapIfLittleEndian (intData [numSamples]); + for (int i = 0; i < numSamples; ++i) + { + dest[i] = scale * (short) swapIfLittleEndian (*(uint16*)intData); + intData += srcBytesPerSample; + } } -void AudioDataConverters::convertInt24LEToFloat (const void* const source, float* const dest, int numSamples) +void AudioDataConverters::convertInt24LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffff; - char* const intData = (char*) source; + const char* intData = (const char*) source; - while (--numSamples >= 0) - dest [numSamples] = scale * littleEndian24Bit (intData + (numSamples + numSamples + numSamples)); + for (int i = 0; i < numSamples; ++i) + { + dest[i] = scale * (short) littleEndian24Bit (intData); + intData += srcBytesPerSample; + } } -void AudioDataConverters::convertInt24BEToFloat (const void* const source, float* const dest, int numSamples) +void AudioDataConverters::convertInt24BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffff; - char* const intData = (char*) source; + const char* intData = (const char*) source; - while (--numSamples >= 0) - dest [numSamples] = scale * bigEndian24Bit (intData + (numSamples + numSamples + numSamples)); + for (int i = 0; i < numSamples; ++i) + { + dest[i] = scale * (short) bigEndian24Bit (intData); + intData += srcBytesPerSample; + } } -void AudioDataConverters::convertInt32LEToFloat (const void* const source, float* const dest, int numSamples) +void AudioDataConverters::convertInt32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffffff; - uint32* const intData = (uint32*) source; + const char* intData = (const char*) source; for (int i = 0; i < numSamples; ++i) - dest [numSamples] = scale * (int) swapIfBigEndian (intData [numSamples]); + { + dest[i] = scale * (int) swapIfBigEndian (*(uint32*) intData); + intData += srcBytesPerSample; + } } -void AudioDataConverters::convertInt32BEToFloat (const void* const source, float* const dest, int numSamples) +void AudioDataConverters::convertInt32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { const float scale = 1.0f / 0x7fffffff; - uint32* const intData = (uint32*) source; + const char* intData = (const char*) source; for (int i = 0; i < numSamples; ++i) - dest [numSamples] = scale * (int) swapIfLittleEndian (intData [numSamples]); + { + dest[i] = scale * (int) (swapIfLittleEndian (*(uint32*) intData)); + intData += srcBytesPerSample; + } } -void AudioDataConverters::convertFloat32LEToFloat (const void* const source, float* const dest, int numSamples) +void AudioDataConverters::convertFloat32LEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { - convertFloatToFloat32LE ((float*) source, dest, numSamples); + const char* s = (const char*) source; + + for (int i = 0; i < numSamples; ++i) + { + dest[i] = *(float*)s; + +#if JUCE_BIG_ENDIAN + uint32* const d = (uint32*) (dest + i); + *d = swapByteOrder (*d); +#endif + + s += srcBytesPerSample; + } } -void AudioDataConverters::convertFloat32BEToFloat (const void* const source, float* const dest, int numSamples) +void AudioDataConverters::convertFloat32BEToFloat (const void* const source, float* const dest, int numSamples, const int srcBytesPerSample) { - convertFloatToFloat32BE ((float*) source, dest, numSamples); + const char* s = (const char*) source; + + for (int i = 0; i < numSamples; ++i) + { + dest[i] = *(float*)s; + +#if JUCE_LITTLE_ENDIAN + uint32* const d = (uint32*) (dest + i); + *d = swapByteOrder (*d); +#endif + + s += srcBytesPerSample; + } } + //============================================================================== void AudioDataConverters::convertFloatToFormat (const DataFormat destFormat, const float* const source, diff --git a/src/juce_appframework/audio/dsp/juce_AudioDataConverters.h b/src/juce_appframework/audio/dsp/juce_AudioDataConverters.h index 6a8ecdaaf1..462c5be51e 100644 --- a/src/juce_appframework/audio/dsp/juce_AudioDataConverters.h +++ b/src/juce_appframework/audio/dsp/juce_AudioDataConverters.h @@ -43,30 +43,30 @@ class JUCE_API AudioDataConverters { public: //============================================================================== - static void convertFloatToInt16LE (const float* source, void* dest, int numSamples); - static void convertFloatToInt16BE (const float* source, void* dest, int numSamples); + static void convertFloatToInt16LE (const float* source, void* dest, int numSamples, const int destBytesPerSample = 2); + static void convertFloatToInt16BE (const float* source, void* dest, int numSamples, const int destBytesPerSample = 2); - static void convertFloatToInt24LE (const float* source, void* dest, int numSamples); - static void convertFloatToInt24BE (const float* source, void* dest, int numSamples); + static void convertFloatToInt24LE (const float* source, void* dest, int numSamples, const int destBytesPerSample = 3); + static void convertFloatToInt24BE (const float* source, void* dest, int numSamples, const int destBytesPerSample = 3); - static void convertFloatToInt32LE (const float* source, void* dest, int numSamples); - static void convertFloatToInt32BE (const float* source, void* dest, int numSamples); + static void convertFloatToInt32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample = 4); + static void convertFloatToInt32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample = 4); - static void convertFloatToFloat32LE (const float* source, void* dest, int numSamples); - static void convertFloatToFloat32BE (const float* source, void* dest, int numSamples); + static void convertFloatToFloat32LE (const float* source, void* dest, int numSamples, const int destBytesPerSample = 4); + static void convertFloatToFloat32BE (const float* source, void* dest, int numSamples, const int destBytesPerSample = 4); //============================================================================== - static void convertInt16LEToFloat (const void* source, float* dest, int numSamples); - static void convertInt16BEToFloat (const void* source, float* dest, int numSamples); + static void convertInt16LEToFloat (const void* source, float* dest, int numSamples, const int srcBytesPerSample = 2); + static void convertInt16BEToFloat (const void* source, float* dest, int numSamples, const int srcBytesPerSample = 2); - static void convertInt24LEToFloat (const void* source, float* dest, int numSamples); - static void convertInt24BEToFloat (const void* source, float* dest, int numSamples); + static void convertInt24LEToFloat (const void* source, float* dest, int numSamples, const int srcBytesPerSample = 3); + static void convertInt24BEToFloat (const void* source, float* dest, int numSamples, const int srcBytesPerSample = 3); - static void convertInt32LEToFloat (const void* source, float* dest, int numSamples); - static void convertInt32BEToFloat (const void* source, float* dest, int numSamples); + static void convertInt32LEToFloat (const void* source, float* dest, int numSamples, const int srcBytesPerSample = 4); + static void convertInt32BEToFloat (const void* source, float* dest, int numSamples, const int srcBytesPerSample = 4); - static void convertFloat32LEToFloat (const void* source, float* dest, int numSamples); - static void convertFloat32BEToFloat (const void* source, float* dest, int numSamples); + static void convertFloat32LEToFloat (const void* source, float* dest, int numSamples, const int srcBytesPerSample = 4); + static void convertFloat32BEToFloat (const void* source, float* dest, int numSamples, const int srcBytesPerSample = 4); //============================================================================== enum DataFormat diff --git a/src/juce_appframework/gui/components/filebrowser/juce_FileBrowserComponent.cpp b/src/juce_appframework/gui/components/filebrowser/juce_FileBrowserComponent.cpp index ae48ad54dd..36e8584583 100644 --- a/src/juce_appframework/gui/components/filebrowser/juce_FileBrowserComponent.cpp +++ b/src/juce_appframework/gui/components/filebrowser/juce_FileBrowserComponent.cpp @@ -132,14 +132,8 @@ FileBrowserComponent::FileBrowserComponent (FileChooserMode mode_, addAndMakeVisible (label); label->attachToComponent (filenameBox, true); - addAndMakeVisible (goUpButton = new DrawableButton ("up", DrawableButton::ImageOnButtonBackground)); - Path arrowPath; - arrowPath.addArrow (50.0f, 100.0f, 50.0f, 0.0, 40.0f, 100.0f, 50.0f); - DrawablePath arrowImage; - arrowImage.setSolidFill (Colours::black.withAlpha (0.4f)); - arrowImage.setPath (arrowPath); - - goUpButton->setImages (&arrowImage); + addAndMakeVisible (goUpButton = getLookAndFeel().createFileBrowserGoUpButton()); + goUpButton->addButtonListener (this); goUpButton->setTooltip (TRANS ("go up to parent directory")); @@ -275,33 +269,10 @@ FilePreviewComponent* FileBrowserComponent::getPreviewComponent() const throw() //============================================================================== void FileBrowserComponent::resized() { - const int x = 8; - int w = getWidth() - x - x; - - if (previewComp != 0) - { - const int previewWidth = w / 3; - previewComp->setBounds (x + w - previewWidth, 0, previewWidth, getHeight()); - - w -= previewWidth + 4; - } - - int y = 4; - - const int controlsHeight = 22; - const int bottomSectionHeight = controlsHeight + 8; - const int upButtonWidth = 50; - - currentPathBox->setBounds (x, y, w - upButtonWidth - 6, controlsHeight); - goUpButton->setBounds (x + w - upButtonWidth, y, upButtonWidth, controlsHeight); - - y += controlsHeight + 4; - - Component* const listAsComp = dynamic_cast (fileListComponent); - listAsComp->setBounds (x, y, w, getHeight() - y - bottomSectionHeight); - - y = listAsComp->getBottom() + 4; - filenameBox->setBounds (x + 50, y, w - 50, controlsHeight); + getLookAndFeel() + .layoutFileBrowserComponent (*this, fileListComponent, + previewComp, currentPathBox, + filenameBox, goUpButton); } //============================================================================== diff --git a/src/juce_appframework/gui/components/filebrowser/juce_FileBrowserComponent.h b/src/juce_appframework/gui/components/filebrowser/juce_FileBrowserComponent.h index 6ba1671be2..af8ceda2a3 100644 --- a/src/juce_appframework/gui/components/filebrowser/juce_FileBrowserComponent.h +++ b/src/juce_appframework/gui/components/filebrowser/juce_FileBrowserComponent.h @@ -200,7 +200,7 @@ private: FilePreviewComponent* previewComp; ComboBox* currentPathBox; TextEditor* filenameBox; - DrawableButton* goUpButton; + Button* goUpButton; TimeSliceThread thread; diff --git a/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp b/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp index 93199541b9..af86dea25e 100644 --- a/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp +++ b/src/juce_appframework/gui/components/layout/juce_TabbedButtonBar.cpp @@ -39,8 +39,6 @@ BEGIN_JUCE_NAMESPACE //============================================================================== -const int spaceAroundImage = 4; - TabBarButton::TabBarButton (const String& name, TabbedButtonBar* const owner_, const int index) @@ -128,6 +126,8 @@ void TabBarButton::getActiveArea (int& x, int& y, int& w, int& h) int r = getWidth(); int b = getHeight(); + const int spaceAroundImage = getLookAndFeel().getTabButtonSpaceAroundImage(); + if (owner->getOrientation() != TabbedButtonBar::TabsAtLeft) r -= spaceAroundImage; @@ -377,7 +377,8 @@ void TabbedButtonBar::resized() if (orientation == TabsAtTop || orientation == TabsAtBottom) swapVariables (depth, length); - const int overlap = getLookAndFeel().getTabButtonOverlap (depth) + spaceAroundImage * 2; + const int overlap = getLookAndFeel().getTabButtonOverlap (depth) + + getLookAndFeel().getTabButtonSpaceAroundImage() * 2; int i, totalLength = overlap; int numVisibleButtons = tabs.size(); diff --git a/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp b/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp index cfc8eddd99..64f7ac1878 100644 --- a/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp +++ b/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.cpp @@ -57,6 +57,7 @@ BEGIN_JUCE_NAMESPACE #include "../filebrowser/juce_FilenameComponent.h" #include "../filebrowser/juce_DirectoryContentsDisplayComponent.h" #include "../filebrowser/juce_FileSearchPathListComponent.h" +#include "../filebrowser/juce_FileBrowserComponent.h" #include "../layout/juce_GroupComponent.h" #include "../properties/juce_PropertyComponent.h" #include "../juce_Desktop.h" @@ -1790,6 +1791,11 @@ int LookAndFeel::getTabButtonOverlap (int tabDepth) return 1 + tabDepth / 3; } +int LookAndFeel::getTabButtonSpaceAroundImage() +{ + return 4; +} + void LookAndFeel::createTabButtonShape (Path& p, int width, int height, int /*tabIndex*/, @@ -2323,6 +2329,58 @@ void LookAndFeel::drawFileBrowserRow (Graphics& g, int width, int height, } } +Button* LookAndFeel::createFileBrowserGoUpButton() +{ + DrawableButton* goUpButton = new DrawableButton ("up", DrawableButton::ImageOnButtonBackground); + + Path arrowPath; + arrowPath.addArrow (50.0f, 100.0f, 50.0f, 0.0, 40.0f, 100.0f, 50.0f); + + DrawablePath arrowImage; + arrowImage.setSolidFill (Colours::black.withAlpha (0.4f)); + arrowImage.setPath (arrowPath); + + goUpButton->setImages (&arrowImage); + + return goUpButton; +} + +void LookAndFeel::layoutFileBrowserComponent (FileBrowserComponent& browserComp, + DirectoryContentsDisplayComponent* fileListComponent, + FilePreviewComponent* previewComp, + ComboBox* currentPathBox, + TextEditor* filenameBox, + Button* goUpButton) +{ + const int x = 8; + int w = browserComp.getWidth() - x - x; + + if (previewComp != 0) + { + const int previewWidth = w / 3; + previewComp->setBounds (x + w - previewWidth, 0, previewWidth, browserComp.getHeight()); + + w -= previewWidth + 4; + } + + int y = 4; + + const int controlsHeight = 22; + const int bottomSectionHeight = controlsHeight + 8; + const int upButtonWidth = 50; + + currentPathBox->setBounds (x, y, w - upButtonWidth - 6, controlsHeight); + goUpButton->setBounds (x + w - upButtonWidth, y, upButtonWidth, controlsHeight); + + y += controlsHeight + 4; + + Component* const listAsComp = dynamic_cast (fileListComponent); + listAsComp->setBounds (x, y, w, browserComp.getHeight() - y - bottomSectionHeight); + + y = listAsComp->getBottom() + 4; + filenameBox->setBounds (x + 50, y, w - 50, controlsHeight); +} + Image* LookAndFeel::getDefaultFolderImage() { static const unsigned char foldericon_png[] = { 137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,32,0,0,0,28,8,6,0,0,0,0,194,189,34,0,0,0,4,103,65,77,65,0,0,175,200,55,5, diff --git a/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.h b/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.h index afda0cbbc9..1a1d41c36e 100644 --- a/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.h +++ b/src/juce_appframework/gui/components/lookandfeel/juce_LookAndFeel.h @@ -58,6 +58,9 @@ class Toolbar; class ToolbarItemComponent; class PopupMenu; class ProgressBar; +class FileBrowserComponent; +class DirectoryContentsDisplayComponent; +class FilePreviewComponent; //============================================================================== @@ -262,6 +265,15 @@ public: const bool isDirectory, const bool isItemSelected); + virtual Button* createFileBrowserGoUpButton(); + + virtual void layoutFileBrowserComponent (FileBrowserComponent& browserComp, + DirectoryContentsDisplayComponent* fileListComponent, + FilePreviewComponent* previewComp, + ComboBox* currentPathBox, + TextEditor* filenameBox, + Button* goUpButton); + //============================================================================== virtual void drawBubble (Graphics& g, float tipX, float tipY, @@ -449,6 +461,7 @@ public: const bool isFrontTab); virtual int getTabButtonOverlap (int tabDepth); + virtual int getTabButtonSpaceAroundImage(); virtual int getTabButtonBestWidth (int tabIndex, const String& text, diff --git a/src/juce_appframework/gui/components/menus/juce_PopupMenu.cpp b/src/juce_appframework/gui/components/menus/juce_PopupMenu.cpp index f57a4251cf..d0ee4746da 100644 --- a/src/juce_appframework/gui/components/menus/juce_PopupMenu.cpp +++ b/src/juce_appframework/gui/components/menus/juce_PopupMenu.cpp @@ -361,7 +361,6 @@ public: if (menu.items.size() > 0) { int totalItems = 0; - bool lastItemWasSeparator = true; PopupMenuWindow* const mw = new PopupMenuWindow(); mw->setLookAndFeel (menu.lookAndFeel); @@ -375,30 +374,8 @@ public: { MenuItemInfo* const item = (MenuItemInfo*) menu.items.getUnchecked(i); - if (item->isSeparator) - { - if (! lastItemWasSeparator) - { - // check it's not one of the last separators.. - for (int j = i + 1; j < menu.items.size(); ++j) - { - if (! ((MenuItemInfo*) menu.items.getUnchecked (j))->isSeparator) - { - mw->addItem (*item); - ++totalItems; - - break; - } - } - } - } - else - { - mw->addItem (*item); - ++totalItems; - } - - lastItemWasSeparator = item->isSeparator; + mw->addItem (*item); + ++totalItems; } if (totalItems == 0) @@ -1321,13 +1298,15 @@ private: //============================================================================== PopupMenu::PopupMenu() throw() : items (8), - lookAndFeel (0) + lookAndFeel (0), + separatorPending (false) { } PopupMenu::PopupMenu (const PopupMenu& other) throw() : items (8), - lookAndFeel (other.lookAndFeel) + lookAndFeel (other.lookAndFeel), + separatorPending (false) { items.ensureStorageAllocated (other.items.size()); @@ -1365,6 +1344,18 @@ void PopupMenu::clear() throw() } items.clear(); + separatorPending = false; +} + +void PopupMenu::addSeparatorIfPending() +{ + if (separatorPending) + { + separatorPending = false; + + if (items.size() > 0) + items.add (new MenuItemInfo()); + } } void PopupMenu::addItem (const int itemResultId, @@ -1377,6 +1368,8 @@ void PopupMenu::addItem (const int itemResultId, // didn't pick anything, so you shouldn't use it as the id // for an item.. + addSeparatorIfPending(); + items.add (new MenuItemInfo (itemResultId, itemText, isActive, @@ -1400,6 +1393,8 @@ void PopupMenu::addCommandItem (ApplicationCommandManager* commandManager, ApplicationCommandInfo info (*registeredInfo); ApplicationCommandTarget* const target = commandManager->getTargetForCommand (commandID, info); + addSeparatorIfPending(); + items.add (new MenuItemInfo (commandID, displayName.isNotEmpty() ? displayName : info.shortName, @@ -1424,6 +1419,8 @@ void PopupMenu::addColouredItem (const int itemResultId, // didn't pick anything, so you shouldn't use it as the id // for an item.. + addSeparatorIfPending(); + items.add (new MenuItemInfo (itemResultId, itemText, isActive, @@ -1442,6 +1439,8 @@ void PopupMenu::addCustomItem (const int itemResultId, // didn't pick anything, so you shouldn't use it as the id // for an item.. + addSeparatorIfPending(); + items.add (new MenuItemInfo (itemResultId, String::empty, true, @@ -1506,6 +1505,8 @@ void PopupMenu::addSubMenu (const String& subMenuName, const bool isActive, Image* const iconToUse) throw() { + addSeparatorIfPending(); + items.add (new MenuItemInfo (0, subMenuName, isActive && (subMenu.getNumItems() > 0), @@ -1520,7 +1521,7 @@ void PopupMenu::addSubMenu (const String& subMenuName, void PopupMenu::addSeparator() throw() { - items.add (new MenuItemInfo()); + separatorPending = true; } diff --git a/src/juce_appframework/gui/components/menus/juce_PopupMenu.h b/src/juce_appframework/gui/components/menus/juce_PopupMenu.h index 3de7982b0a..e0bb91fc19 100644 --- a/src/juce_appframework/gui/components/menus/juce_PopupMenu.h +++ b/src/juce_appframework/gui/components/menus/juce_PopupMenu.h @@ -381,6 +381,9 @@ private: friend class MenuItemIterator; VoidArray items; LookAndFeel* lookAndFeel; + bool separatorPending; + + void addSeparatorIfPending(); int showMenu (const int x, const int y, const int w, const int h, const int itemIdThatMustBeVisible, diff --git a/src/juce_appframework/gui/components/special/juce_QuickTimeMovieComponent.cpp b/src/juce_appframework/gui/components/special/juce_QuickTimeMovieComponent.cpp index cb85e093b8..c3028e2f6e 100644 --- a/src/juce_appframework/gui/components/special/juce_QuickTimeMovieComponent.cpp +++ b/src/juce_appframework/gui/components/special/juce_QuickTimeMovieComponent.cpp @@ -1,856 +1,870 @@ -/* - ============================================================================== - - 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_Config.h" - -#if JUCE_QUICKTIME - -#ifdef _MSC_VER - #pragma warning (disable: 4514) -#endif - -#ifdef _WIN32 - #include - - // If you've got QuickTime 7 installed, then these COM objects should be found in - // the "\Program Files\Quicktime" directory. You'll need to add this directory to - // your include search path to make these import statements work. - #import - #import - - using namespace QTOLibrary; - using namespace QTOControlLib; -#else - #include - #include -#endif - -#include "../../../../juce_core/basics/juce_StandardHeader.h" - -BEGIN_JUCE_NAMESPACE - -#include "juce_QuickTimeMovieComponent.h" - - -//============================================================================== -#if JUCE_WIN32 - -struct QTMovieCompInternal -{ - IQTControlPtr qtControlInternal; - IQTMoviePtr qtMovieInternal; -}; - -#define qtControl (((QTMovieCompInternal*) internal)->qtControlInternal) -#define qtMovie (((QTMovieCompInternal*) internal)->qtMovieInternal) - -//============================================================================== -QuickTimeMovieComponent::QuickTimeMovieComponent() - : movieLoaded (false), - controllerVisible (true) -{ - internal = new QTMovieCompInternal(); -} - -QuickTimeMovieComponent::~QuickTimeMovieComponent() -{ - closeMovie(); - qtControl = 0; - - deleteControl(); - - delete internal; - internal = 0; -} - -//============================================================================== -void QuickTimeMovieComponent::createControlIfNeeded() -{ - if (isShowing() && ! isControlCreated()) - { - const IID qtIID = __uuidof (QTControl); - - if (createControl (&qtIID)) - { - const IID qtInterfaceIID = __uuidof (IQTControl); - qtControl = (IQTControl*) queryInterface (&qtInterfaceIID); - - if (qtControl != 0) - { - qtControl->Release(); // it has one ref too many at this point - - qtControl->QuickTimeInitialize(); - qtControl->PutSizing (qtMovieFitsControl); - - if (movieFile != File::nonexistent) - loadMovie (movieFile, controllerVisible); - } - } - } -} - -bool QuickTimeMovieComponent::isControlCreated() const -{ - return isControlOpen(); -} - -bool QuickTimeMovieComponent::loadMovie (const File& movieFile_, - const bool isControllerVisible) -{ - movieFile = movieFile_; - movieLoaded = false; - qtMovie = 0; - controllerVisible = isControllerVisible; - createControlIfNeeded(); - - if (isControlCreated()) - { - if (qtControl != 0) - { - qtControl->PutURL ((const WCHAR*) movieFile_.getFullPathName()); - qtMovie = qtControl->GetMovie(); - - if (qtMovie != 0) - qtMovie->PutMovieControllerType (isControllerVisible ? qtMovieControllerTypeStandard - : qtMovieControllerTypeNone); - } - - movieLoaded = (qtMovie != 0); - return movieLoaded; - } - else - { - // You're trying to open a movie when the control hasn't yet been created, probably because - // you've not yet added this component to a Window and made the whole component hierarchy visible. - jassertfalse - return false; - } -} - -void QuickTimeMovieComponent::closeMovie() -{ - stop(); - movieFile = File::nonexistent; - movieLoaded = false; - qtMovie = 0; - - if (qtControl != 0) - qtControl->PutURL (L""); -} - -const File QuickTimeMovieComponent::getCurrentMovieFile() const -{ - return movieFile; -} - -bool QuickTimeMovieComponent::isMovieOpen() const -{ - return movieLoaded; -} - -double QuickTimeMovieComponent::getMovieDuration() const -{ - if (qtMovie != 0) - return qtMovie->GetDuration() / (double) qtMovie->GetTimeScale(); - - return 0.0; -} - -void QuickTimeMovieComponent::getMovieNormalSize (int& width, int& height) const -{ - if (qtMovie != 0) - { - struct QTRECT r = qtMovie->GetNaturalRect(); - - width = r.right - r.left; - height = r.bottom - r.top; - } - else - { - width = height = 0; - } -} - -void QuickTimeMovieComponent::play() -{ - if (qtMovie != 0) - qtMovie->Play(); -} - -void QuickTimeMovieComponent::stop() -{ - if (qtMovie != 0) - qtMovie->Stop(); -} - -bool QuickTimeMovieComponent::isPlaying() const -{ - return qtMovie != 0 && qtMovie->GetRate() != 0.0f; -} - -void QuickTimeMovieComponent::setPosition (const double seconds) -{ - if (qtMovie != 0) - qtMovie->PutTime ((long) (seconds * qtMovie->GetTimeScale())); -} - -double QuickTimeMovieComponent::getPosition() const -{ - if (qtMovie != 0) - return qtMovie->GetTime() / (double) qtMovie->GetTimeScale(); - - return 0.0; -} - -void QuickTimeMovieComponent::setMovieVolume (const float newVolume) -{ - if (qtMovie != 0) - qtMovie->PutAudioVolume (newVolume); -} - -float QuickTimeMovieComponent::getMovieVolume() const -{ - if (qtMovie != 0) - return qtMovie->GetAudioVolume(); - - return 0.0f; -} - -void QuickTimeMovieComponent::setLooping (const bool shouldLoop) -{ - if (qtMovie != 0) - qtMovie->PutLoop (shouldLoop); -} - -bool QuickTimeMovieComponent::isLooping() const -{ - return qtMovie != 0 && qtMovie->GetLoop(); -} - -bool QuickTimeMovieComponent::isControllerVisible() const -{ - return controllerVisible; -} - -void QuickTimeMovieComponent::parentHierarchyChanged() -{ - createControlIfNeeded(); - QTWinBaseClass::parentHierarchyChanged(); -} - -void QuickTimeMovieComponent::visibilityChanged() -{ - createControlIfNeeded(); - QTWinBaseClass::visibilityChanged(); -} - -void QuickTimeMovieComponent::paint (Graphics& g) -{ - if (! isControlCreated()) - g.fillAll (Colours::black); -} - -#endif - -//============================================================================== -#if JUCE_MAC - -#include "../../../../juce_core/misc/juce_PlatformUtilities.h" -#include "../../../events/juce_MessageManager.h" -#include "../../graphics/geometry/juce_RectangleList.h" - -static bool isQTAvailable = false; -static bool hasLoadedQT = false; -static VoidArray activeQTWindows (2); - -struct MacClickEventData -{ - ::Point where; - long when; - long modifiers; -}; - -void OfferMouseClickToQuickTime (WindowRef window, - ::Point where, long when, long modifiers, - Component* topLevelComp) -{ - if (hasLoadedQT) - { - for (int i = activeQTWindows.size(); --i >= 0;) - { - QuickTimeMovieComponent* const qtw = (QuickTimeMovieComponent*) activeQTWindows[i]; - - if (qtw->isVisible() && topLevelComp->isParentOf (qtw)) - { - MacClickEventData data; - data.where = where; - data.when = when; - data.modifiers = modifiers; - - qtw->handleMCEvent (&data); - } - } - } -} - -//============================================================================== -struct InternalData -{ - Movie movie; - MovieController controller; -}; - -//============================================================================== -QuickTimeMovieComponent::QuickTimeMovieComponent() - : internal (new InternalData()), - associatedWindow (0), - controllerVisible (false), - controllerAssignedToWindow (false), - reentrant (false) -{ - InternalData* const id = (InternalData*) internal; - id->movie = 0; - id->controller = 0; - - if (! hasLoadedQT) - { - hasLoadedQT = true; - isQTAvailable = EnterMovies() == noErr; - } - - setOpaque (true); - setVisible (true); - - activeQTWindows.add (this); -} - -QuickTimeMovieComponent::~QuickTimeMovieComponent() -{ - closeMovie(); - - activeQTWindows.removeValue ((void*) this); - - InternalData* const id = (InternalData*) internal; - delete id; - - if (activeQTWindows.size() == 0 && isQTAvailable) - { - isQTAvailable = false; - hasLoadedQT = false; - - ExitMovies(); - } -} - -bool QuickTimeMovieComponent::loadMovie (const File& f, - const bool controllerVisible_) -{ - if (! (isQTAvailable && f.existsAsFile())) - return false; - - closeMovie(); - - if (getPeer() == 0) - { - // To open a movie, this component must be visible inside a functioning window, so that - // the QT control can be assigned to the window. - jassertfalse - return false; - } - - controllerVisible = controllerVisible_; - - InternalData* const id = (InternalData*) internal; - - GrafPtr savedPort; - GetPort (&savedPort); - bool result = false; - - FSSpec fsSpec; - - PlatformUtilities::makeFSSpecFromPath (&fsSpec, f.getFullPathName()); - - short refNum = -1; - OSErr err; - - if ((err = OpenMovieFile (&fsSpec, &refNum, fsRdWrPerm)) == noErr - || (err = OpenMovieFile (&fsSpec, &refNum, fsRdPerm)) == noErr) - { - id->controller = 0; - - short resID = 0; - - if (NewMovieFromFile (&id->movie, refNum, &resID, 0, newMovieActive, 0) == noErr - && id->movie != 0) - { - void* window = getWindowHandle(); - - if (window != associatedWindow && window != 0) - associatedWindow = window; - - assignMovieToWindow(); - - SetMovieActive (id->movie, true); - SetMovieProgressProc (id->movie, (MovieProgressUPP) -1, 0); - - movieFile = f; - startTimer (1000 / 50); // this needs to be quite a high frequency for smooth playback - result = true; - - repaint(); - } - } - - MacSetPort (savedPort); - - return result; -} - -void QuickTimeMovieComponent::closeMovie() -{ - stop(); - - InternalData* const id = (InternalData*) internal; - - if (id->controller != 0) - { - DisposeMovieController (id->controller); - id->controller = 0; - } - - if (id->movie != 0) - { - DisposeMovie (id->movie); - id->movie = 0; - } - - stopTimer(); - movieFile = File::nonexistent; -} - -bool QuickTimeMovieComponent::isMovieOpen() const -{ - InternalData* const id = (InternalData*) internal; - return id->movie != 0 && id->controller != 0; -} - -const File QuickTimeMovieComponent::getCurrentMovieFile() const -{ - return movieFile; -} - -static GrafPtr getPortForWindow (void* window) -{ - if (window == 0) - return 0; - - return (GrafPtr) GetWindowPort ((WindowRef) window); -} - -void QuickTimeMovieComponent::assignMovieToWindow() -{ - if (reentrant) - return; - - reentrant = true; - - InternalData* const id = (InternalData*) internal; - if (id->controller != 0) - { - DisposeMovieController (id->controller); - id->controller = 0; - } - - controllerAssignedToWindow = false; - - void* window = getWindowHandle(); - GrafPtr port = getPortForWindow (window); - - if (port != 0) - { - GrafPtr savedPort; - GetPort (&savedPort); - - SetMovieGWorld (id->movie, (CGrafPtr) port, 0); - MacSetPort (port); - - Rect r; - r.top = 0; - r.left = 0; - r.right = (short) jmax (1, getWidth()); - r.bottom = (short) jmax (1, getHeight()); - SetMovieBox (id->movie, &r); - - // create the movie controller - id->controller = NewMovieController (id->movie, &r, - controllerVisible ? mcTopLeftMovie - : mcNotVisible); - - if (id->controller != 0) - { - MCEnableEditing (id->controller, true); - - MCDoAction (id->controller, mcActionSetUseBadge, (void*) false); - MCDoAction (id->controller, mcActionSetLoopIsPalindrome, (void*) false); - setLooping (looping); - - MCDoAction (id->controller, mcActionSetFlags, - (void*) (pointer_sized_int) (mcFlagSuppressMovieFrame | (controllerVisible ? 0 : (mcFlagSuppressStepButtons | mcFlagSuppressSpeakerButton)))); - - MCSetControllerBoundsRect (id->controller, &r); - - controllerAssignedToWindow = true; - - resized(); - } - - MacSetPort (savedPort); - } - else - { - SetMovieGWorld (id->movie, 0, 0); - } - - reentrant = false; -} - -void QuickTimeMovieComponent::play() -{ - InternalData* const id = (InternalData*) internal; - - if (id->movie != 0) - StartMovie (id->movie); -} - -void QuickTimeMovieComponent::stop() -{ - InternalData* const id = (InternalData*) internal; - - if (id->movie != 0) - StopMovie (id->movie); -} - -bool QuickTimeMovieComponent::isPlaying() const -{ - InternalData* const id = (InternalData*) internal; - - return id->movie != 0 && GetMovieRate (id->movie) != 0; -} - -void QuickTimeMovieComponent::setPosition (const double seconds) -{ - InternalData* const id = (InternalData*) internal; - - if (id->controller != 0) - { - TimeRecord time; - time.base = GetMovieTimeBase (id->movie); - time.scale = 100000; - const uint64 t = (uint64) (100000.0 * seconds); - time.value.lo = (UInt32) (t & 0xffffffff); - time.value.hi = (UInt32) (t >> 32); - - SetMovieTime (id->movie, &time); - timerCallback(); // to call MCIdle - } - else - { - jassertfalse // no movie is open, so can't set the position. - } -} - -double QuickTimeMovieComponent::getPosition() const -{ - InternalData* const id = (InternalData*) internal; - - if (id->movie != 0) - { - TimeRecord time; - GetMovieTime (id->movie, &time); - - return ((int64) (((uint64) time.value.hi << 32) | (uint64) time.value.lo)) - / (double) time.scale; - } - - return 0.0; -} - -double QuickTimeMovieComponent::getMovieDuration() const -{ - InternalData* const id = (InternalData*) internal; - - if (id->movie != 0) - return GetMovieDuration (id->movie) / (double) GetMovieTimeScale (id->movie); - - return 0.0; -} - -void QuickTimeMovieComponent::setLooping (const bool shouldLoop) -{ - InternalData* const id = (InternalData*) internal; - looping = shouldLoop; - - if (id->controller != 0) - MCDoAction (id->controller, mcActionSetLooping, (void*) shouldLoop); -} - -bool QuickTimeMovieComponent::isLooping() const -{ - return looping; -} - -void QuickTimeMovieComponent::setMovieVolume (const float newVolume) -{ - InternalData* const id = (InternalData*) internal; - - if (id->movie != 0) - SetMovieVolume (id->movie, jlimit ((short) 0, (short) 0x100, (short) (newVolume * 0x0100))); -} - -float QuickTimeMovieComponent::getMovieVolume() const -{ - InternalData* const id = (InternalData*) internal; - - if (id->movie != 0) - return jmax (0.0f, GetMovieVolume (id->movie) / (float) 0x0100); - - return 0.0f; -} - -void QuickTimeMovieComponent::getMovieNormalSize (int& width, int& height) const -{ - width = 0; - height = 0; - - InternalData* const id = (InternalData*) internal; - - if (id->movie != 0) - { - Rect r; - GetMovieNaturalBoundsRect (id->movie, &r); - width = r.right - r.left; - height = r.bottom - r.top; - } -} - -void QuickTimeMovieComponent::paint (Graphics& g) -{ - InternalData* const id = (InternalData*) internal; - - if (id->movie == 0 || id->controller == 0) - { - g.fillAll (Colours::black); - return; - } - - GrafPtr savedPort; - GetPort (&savedPort); - - MacSetPort (getPortForWindow (getWindowHandle())); - MCDraw (id->controller, (WindowRef) getWindowHandle()); - - MacSetPort (savedPort); - - ComponentPeer* const peer = getPeer(); - - if (peer != 0) - { - peer->addMaskedRegion (getScreenX() - peer->getScreenX(), - getScreenY() - peer->getScreenY(), - getWidth(), getHeight()); - } - - timerCallback(); -} - -static const Rectangle getMoviePos (Component* const c) -{ - return Rectangle (c->getScreenX() - c->getTopLevelComponent()->getScreenX(), - c->getScreenY() - c->getTopLevelComponent()->getScreenY(), - jmax (1, c->getWidth()), - jmax (1, c->getHeight())); -} - -void QuickTimeMovieComponent::moved() -{ - resized(); -} - -void QuickTimeMovieComponent::resized() -{ - InternalData* const id = (InternalData*) internal; - - if (id->controller != 0 && isShowing()) - { - checkWindowAssociation(); - - GrafPtr port = getPortForWindow (getWindowHandle()); - - if (port != 0) - { - GrafPtr savedPort; - GetPort (&savedPort); - - SetMovieGWorld (id->movie, (CGrafPtr) port, 0); - MacSetPort (port); - - lastPositionApplied = getMoviePos (this); - - Rect r; - r.left = (short) lastPositionApplied.getX(); - r.top = (short) lastPositionApplied.getY(); - r.right = (short) lastPositionApplied.getRight(); - r.bottom = (short) lastPositionApplied.getBottom(); - - if (MCGetVisible (id->controller)) - MCSetControllerBoundsRect (id->controller, &r); - else - SetMovieBox (id->movie, &r); - - if (! isPlaying()) - timerCallback(); - - MacSetPort (savedPort); - - repaint(); - } - } -} - -void QuickTimeMovieComponent::visibilityChanged() -{ - checkWindowAssociation(); - QTWinBaseClass::visibilityChanged(); -} - -void QuickTimeMovieComponent::timerCallback() -{ - InternalData* const id = (InternalData*) internal; - - if (id->controller != 0) - { - if (isTimerRunning()) - startTimer (getTimerInterval()); - - MCIdle (id->controller); - - if (lastPositionApplied != getMoviePos (this)) - resized(); - } -} - -void QuickTimeMovieComponent::checkWindowAssociation() -{ - void* const window = getWindowHandle(); - - if (window != associatedWindow - || (window != 0 && ! controllerAssignedToWindow)) - { - associatedWindow = window; - assignMovieToWindow(); - } -} - -void QuickTimeMovieComponent::parentHierarchyChanged() -{ - checkWindowAssociation(); -} - -void QuickTimeMovieComponent::handleMCEvent (void* ev) -{ - InternalData* const id = (InternalData*) internal; - - if (id->controller != 0 && isShowing()) - { - MacClickEventData* data = (MacClickEventData*) ev; - - data->where.h -= getTopLevelComponent()->getScreenX(); - data->where.v -= getTopLevelComponent()->getScreenY(); - - Boolean b = false; - MCPtInController (id->controller, data->where, &b); - - if (b) - { - const int oldTimeBeforeWaitCursor = MessageManager::getInstance()->getTimeBeforeShowingWaitCursor(); - MessageManager::getInstance()->setTimeBeforeShowingWaitCursor (0); - - MCClick (id->controller, - (WindowRef) getWindowHandle(), - data->where, - data->when, - data->modifiers); - - MessageManager::getInstance()->setTimeBeforeShowingWaitCursor (oldTimeBeforeWaitCursor); - } - } -} - -#endif - -//============================================================================== -// (methods common to both platforms..) - -void QuickTimeMovieComponent::goToStart() -{ - setPosition (0.0); -} - -void QuickTimeMovieComponent::setBoundsWithCorrectAspectRatio (const Rectangle& spaceToFitWithin, - const RectanglePlacement& placement) -{ - int normalWidth, normalHeight; - getMovieNormalSize (normalWidth, normalHeight); - - if (normalWidth > 0 && normalHeight > 0 && ! spaceToFitWithin.isEmpty()) - { - double x = 0.0, y = 0.0, w = normalWidth, h = normalHeight; - - placement.applyTo (x, y, w, h, - spaceToFitWithin.getX(), spaceToFitWithin.getY(), - spaceToFitWithin.getWidth(), spaceToFitWithin.getHeight()); - - if (w > 0 && h > 0) - { - setBounds (roundDoubleToInt (x), roundDoubleToInt (y), - roundDoubleToInt (w), roundDoubleToInt (h)); - } - } - else - { - setBounds (spaceToFitWithin); - } -} - - -END_JUCE_NAMESPACE - -#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. + + ============================================================================== +*/ + +#include "../../../../../juce_Config.h" + +#if JUCE_QUICKTIME + +#ifdef _MSC_VER + #pragma warning (disable: 4514) +#endif + +#ifdef _WIN32 + #include + + // If you've got QuickTime 7 installed, then these COM objects should be found in + // the "\Program Files\Quicktime" directory. You'll need to add this directory to + // your include search path to make these import statements work. + #import + #import + + using namespace QTOLibrary; + using namespace QTOControlLib; +#else + #include + #include +#endif + +#include "../../../../juce_core/basics/juce_StandardHeader.h" + +BEGIN_JUCE_NAMESPACE + +#include "juce_QuickTimeMovieComponent.h" + + +//============================================================================== +#if JUCE_WIN32 + +struct QTMovieCompInternal +{ + IQTControlPtr qtControlInternal; + IQTMoviePtr qtMovieInternal; +}; + +#define qtControl (((QTMovieCompInternal*) internal)->qtControlInternal) +#define qtMovie (((QTMovieCompInternal*) internal)->qtMovieInternal) + +//============================================================================== +QuickTimeMovieComponent::QuickTimeMovieComponent() + : movieLoaded (false), + controllerVisible (true) +{ + internal = new QTMovieCompInternal(); +} + +QuickTimeMovieComponent::~QuickTimeMovieComponent() +{ + closeMovie(); + qtControl = 0; + + deleteControl(); + + delete internal; + internal = 0; +} + +//============================================================================== +void QuickTimeMovieComponent::createControlIfNeeded() +{ + if (isShowing() && ! isControlCreated()) + { + const IID qtIID = __uuidof (QTControl); + + if (createControl (&qtIID)) + { + const IID qtInterfaceIID = __uuidof (IQTControl); + qtControl = (IQTControl*) queryInterface (&qtInterfaceIID); + + if (qtControl != 0) + { + qtControl->Release(); // it has one ref too many at this point + + qtControl->QuickTimeInitialize(); + qtControl->PutSizing (qtMovieFitsControl); + + if (movieFile != File::nonexistent) + loadMovie (movieFile, controllerVisible); + } + } + } +} + +bool QuickTimeMovieComponent::isControlCreated() const +{ + return isControlOpen(); +} + +bool QuickTimeMovieComponent::loadMovie (const File& movieFile_, + const bool isControllerVisible) +{ + movieFile = movieFile_; + movieLoaded = false; + qtMovie = 0; + controllerVisible = isControllerVisible; + createControlIfNeeded(); + + if (isControlCreated()) + { + if (qtControl != 0) + { + qtControl->PutURL ((const WCHAR*) movieFile_.getFullPathName()); + qtMovie = qtControl->GetMovie(); + + if (qtMovie != 0) + qtMovie->PutMovieControllerType (isControllerVisible ? qtMovieControllerTypeStandard + : qtMovieControllerTypeNone); + } + + movieLoaded = (qtMovie != 0); + return movieLoaded; + } + else + { + // You're trying to open a movie when the control hasn't yet been created, probably because + // you've not yet added this component to a Window and made the whole component hierarchy visible. + jassertfalse + return false; + } +} + +void QuickTimeMovieComponent::closeMovie() +{ + stop(); + movieFile = File::nonexistent; + movieLoaded = false; + qtMovie = 0; + + if (qtControl != 0) + qtControl->PutURL (L""); +} + +const File QuickTimeMovieComponent::getCurrentMovieFile() const +{ + return movieFile; +} + +bool QuickTimeMovieComponent::isMovieOpen() const +{ + return movieLoaded; +} + +double QuickTimeMovieComponent::getMovieDuration() const +{ + if (qtMovie != 0) + return qtMovie->GetDuration() / (double) qtMovie->GetTimeScale(); + + return 0.0; +} + +void QuickTimeMovieComponent::getMovieNormalSize (int& width, int& height) const +{ + if (qtMovie != 0) + { + struct QTRECT r = qtMovie->GetNaturalRect(); + + width = r.right - r.left; + height = r.bottom - r.top; + } + else + { + width = height = 0; + } +} + +void QuickTimeMovieComponent::play() +{ + if (qtMovie != 0) + qtMovie->Play(); +} + +void QuickTimeMovieComponent::stop() +{ + if (qtMovie != 0) + qtMovie->Stop(); +} + +bool QuickTimeMovieComponent::isPlaying() const +{ + return qtMovie != 0 && qtMovie->GetRate() != 0.0f; +} + +void QuickTimeMovieComponent::setPosition (const double seconds) +{ + if (qtMovie != 0) + qtMovie->PutTime ((long) (seconds * qtMovie->GetTimeScale())); +} + +double QuickTimeMovieComponent::getPosition() const +{ + if (qtMovie != 0) + return qtMovie->GetTime() / (double) qtMovie->GetTimeScale(); + + return 0.0; +} + +void QuickTimeMovieComponent::setSpeed (const float newSpeed) +{ + if (qtMovie != 0) + qtMovie->PutRate (newSpeed); +} + +void QuickTimeMovieComponent::setMovieVolume (const float newVolume) +{ + if (qtMovie != 0) + qtMovie->PutAudioVolume (newVolume); +} + +float QuickTimeMovieComponent::getMovieVolume() const +{ + if (qtMovie != 0) + return qtMovie->GetAudioVolume(); + + return 0.0f; +} + +void QuickTimeMovieComponent::setLooping (const bool shouldLoop) +{ + if (qtMovie != 0) + qtMovie->PutLoop (shouldLoop); +} + +bool QuickTimeMovieComponent::isLooping() const +{ + return qtMovie != 0 && qtMovie->GetLoop(); +} + +bool QuickTimeMovieComponent::isControllerVisible() const +{ + return controllerVisible; +} + +void QuickTimeMovieComponent::parentHierarchyChanged() +{ + createControlIfNeeded(); + QTWinBaseClass::parentHierarchyChanged(); +} + +void QuickTimeMovieComponent::visibilityChanged() +{ + createControlIfNeeded(); + QTWinBaseClass::visibilityChanged(); +} + +void QuickTimeMovieComponent::paint (Graphics& g) +{ + if (! isControlCreated()) + g.fillAll (Colours::black); +} + +#endif + +//============================================================================== +#if JUCE_MAC + +#include "../../../../juce_core/misc/juce_PlatformUtilities.h" +#include "../../../events/juce_MessageManager.h" +#include "../../graphics/geometry/juce_RectangleList.h" + +static bool isQTAvailable = false; +static bool hasLoadedQT = false; +static VoidArray activeQTWindows (2); + +struct MacClickEventData +{ + ::Point where; + long when; + long modifiers; +}; + +void OfferMouseClickToQuickTime (WindowRef window, + ::Point where, long when, long modifiers, + Component* topLevelComp) +{ + if (hasLoadedQT) + { + for (int i = activeQTWindows.size(); --i >= 0;) + { + QuickTimeMovieComponent* const qtw = (QuickTimeMovieComponent*) activeQTWindows[i]; + + if (qtw->isVisible() && topLevelComp->isParentOf (qtw)) + { + MacClickEventData data; + data.where = where; + data.when = when; + data.modifiers = modifiers; + + qtw->handleMCEvent (&data); + } + } + } +} + +//============================================================================== +struct InternalData +{ + Movie movie; + MovieController controller; +}; + +//============================================================================== +QuickTimeMovieComponent::QuickTimeMovieComponent() + : internal (new InternalData()), + associatedWindow (0), + controllerVisible (false), + controllerAssignedToWindow (false), + reentrant (false) +{ + InternalData* const id = (InternalData*) internal; + id->movie = 0; + id->controller = 0; + + if (! hasLoadedQT) + { + hasLoadedQT = true; + isQTAvailable = EnterMovies() == noErr; + } + + setOpaque (true); + setVisible (true); + + activeQTWindows.add (this); +} + +QuickTimeMovieComponent::~QuickTimeMovieComponent() +{ + closeMovie(); + + activeQTWindows.removeValue ((void*) this); + + InternalData* const id = (InternalData*) internal; + delete id; + + if (activeQTWindows.size() == 0 && isQTAvailable) + { + isQTAvailable = false; + hasLoadedQT = false; + + ExitMovies(); + } +} + +bool QuickTimeMovieComponent::loadMovie (const File& f, + const bool controllerVisible_) +{ + if (! (isQTAvailable && f.existsAsFile())) + return false; + + closeMovie(); + + if (getPeer() == 0) + { + // To open a movie, this component must be visible inside a functioning window, so that + // the QT control can be assigned to the window. + jassertfalse + return false; + } + + controllerVisible = controllerVisible_; + + InternalData* const id = (InternalData*) internal; + + GrafPtr savedPort; + GetPort (&savedPort); + bool result = false; + + FSSpec fsSpec; + + PlatformUtilities::makeFSSpecFromPath (&fsSpec, f.getFullPathName()); + + short refNum = -1; + OSErr err; + + if ((err = OpenMovieFile (&fsSpec, &refNum, fsRdWrPerm)) == noErr + || (err = OpenMovieFile (&fsSpec, &refNum, fsRdPerm)) == noErr) + { + id->controller = 0; + + short resID = 0; + + if (NewMovieFromFile (&id->movie, refNum, &resID, 0, newMovieActive, 0) == noErr + && id->movie != 0) + { + void* window = getWindowHandle(); + + if (window != associatedWindow && window != 0) + associatedWindow = window; + + assignMovieToWindow(); + + SetMovieActive (id->movie, true); + SetMovieProgressProc (id->movie, (MovieProgressUPP) -1, 0); + + movieFile = f; + startTimer (1000 / 50); // this needs to be quite a high frequency for smooth playback + result = true; + + repaint(); + } + } + + MacSetPort (savedPort); + + return result; +} + +void QuickTimeMovieComponent::closeMovie() +{ + stop(); + + InternalData* const id = (InternalData*) internal; + + if (id->controller != 0) + { + DisposeMovieController (id->controller); + id->controller = 0; + } + + if (id->movie != 0) + { + DisposeMovie (id->movie); + id->movie = 0; + } + + stopTimer(); + movieFile = File::nonexistent; +} + +bool QuickTimeMovieComponent::isMovieOpen() const +{ + InternalData* const id = (InternalData*) internal; + return id->movie != 0 && id->controller != 0; +} + +const File QuickTimeMovieComponent::getCurrentMovieFile() const +{ + return movieFile; +} + +static GrafPtr getPortForWindow (void* window) +{ + if (window == 0) + return 0; + + return (GrafPtr) GetWindowPort ((WindowRef) window); +} + +void QuickTimeMovieComponent::assignMovieToWindow() +{ + if (reentrant) + return; + + reentrant = true; + + InternalData* const id = (InternalData*) internal; + if (id->controller != 0) + { + DisposeMovieController (id->controller); + id->controller = 0; + } + + controllerAssignedToWindow = false; + + void* window = getWindowHandle(); + GrafPtr port = getPortForWindow (window); + + if (port != 0) + { + GrafPtr savedPort; + GetPort (&savedPort); + + SetMovieGWorld (id->movie, (CGrafPtr) port, 0); + MacSetPort (port); + + Rect r; + r.top = 0; + r.left = 0; + r.right = (short) jmax (1, getWidth()); + r.bottom = (short) jmax (1, getHeight()); + SetMovieBox (id->movie, &r); + + // create the movie controller + id->controller = NewMovieController (id->movie, &r, + controllerVisible ? mcTopLeftMovie + : mcNotVisible); + + if (id->controller != 0) + { + MCEnableEditing (id->controller, true); + + MCDoAction (id->controller, mcActionSetUseBadge, (void*) false); + MCDoAction (id->controller, mcActionSetLoopIsPalindrome, (void*) false); + setLooping (looping); + + MCDoAction (id->controller, mcActionSetFlags, + (void*) (pointer_sized_int) (mcFlagSuppressMovieFrame | (controllerVisible ? 0 : (mcFlagSuppressStepButtons | mcFlagSuppressSpeakerButton)))); + + MCSetControllerBoundsRect (id->controller, &r); + + controllerAssignedToWindow = true; + + resized(); + } + + MacSetPort (savedPort); + } + else + { + SetMovieGWorld (id->movie, 0, 0); + } + + reentrant = false; +} + +void QuickTimeMovieComponent::play() +{ + InternalData* const id = (InternalData*) internal; + + if (id->movie != 0) + StartMovie (id->movie); +} + +void QuickTimeMovieComponent::stop() +{ + InternalData* const id = (InternalData*) internal; + + if (id->movie != 0) + StopMovie (id->movie); +} + +bool QuickTimeMovieComponent::isPlaying() const +{ + InternalData* const id = (InternalData*) internal; + + return id->movie != 0 && GetMovieRate (id->movie) != 0; +} + +void QuickTimeMovieComponent::setPosition (const double seconds) +{ + InternalData* const id = (InternalData*) internal; + + if (id->controller != 0) + { + TimeRecord time; + time.base = GetMovieTimeBase (id->movie); + time.scale = 100000; + const uint64 t = (uint64) (100000.0 * seconds); + time.value.lo = (UInt32) (t & 0xffffffff); + time.value.hi = (UInt32) (t >> 32); + + SetMovieTime (id->movie, &time); + timerCallback(); // to call MCIdle + } + else + { + jassertfalse // no movie is open, so can't set the position. + } +} + +double QuickTimeMovieComponent::getPosition() const +{ + InternalData* const id = (InternalData*) internal; + + if (id->movie != 0) + { + TimeRecord time; + GetMovieTime (id->movie, &time); + + return ((int64) (((uint64) time.value.hi << 32) | (uint64) time.value.lo)) + / (double) time.scale; + } + + return 0.0; +} + +void QuickTimeMovieComponent::setSpeed (const float newSpeed) +{ + InternalData* const id = (InternalData*) internal; + + if (id->movie != 0) + SetMovieRate (id->movie, (Fixed) (newSpeed * (Fixed) 0x00010000L)); +} + +double QuickTimeMovieComponent::getMovieDuration() const +{ + InternalData* const id = (InternalData*) internal; + + if (id->movie != 0) + return GetMovieDuration (id->movie) / (double) GetMovieTimeScale (id->movie); + + return 0.0; +} + +void QuickTimeMovieComponent::setLooping (const bool shouldLoop) +{ + InternalData* const id = (InternalData*) internal; + looping = shouldLoop; + + if (id->controller != 0) + MCDoAction (id->controller, mcActionSetLooping, (void*) shouldLoop); +} + +bool QuickTimeMovieComponent::isLooping() const +{ + return looping; +} + +void QuickTimeMovieComponent::setMovieVolume (const float newVolume) +{ + InternalData* const id = (InternalData*) internal; + + if (id->movie != 0) + SetMovieVolume (id->movie, jlimit ((short) 0, (short) 0x100, (short) (newVolume * 0x0100))); +} + +float QuickTimeMovieComponent::getMovieVolume() const +{ + InternalData* const id = (InternalData*) internal; + + if (id->movie != 0) + return jmax (0.0f, GetMovieVolume (id->movie) / (float) 0x0100); + + return 0.0f; +} + +void QuickTimeMovieComponent::getMovieNormalSize (int& width, int& height) const +{ + width = 0; + height = 0; + + InternalData* const id = (InternalData*) internal; + + if (id->movie != 0) + { + Rect r; + GetMovieNaturalBoundsRect (id->movie, &r); + width = r.right - r.left; + height = r.bottom - r.top; + } +} + +void QuickTimeMovieComponent::paint (Graphics& g) +{ + InternalData* const id = (InternalData*) internal; + + if (id->movie == 0 || id->controller == 0) + { + g.fillAll (Colours::black); + return; + } + + GrafPtr savedPort; + GetPort (&savedPort); + + MacSetPort (getPortForWindow (getWindowHandle())); + MCDraw (id->controller, (WindowRef) getWindowHandle()); + + MacSetPort (savedPort); + + ComponentPeer* const peer = getPeer(); + + if (peer != 0) + { + peer->addMaskedRegion (getScreenX() - peer->getScreenX(), + getScreenY() - peer->getScreenY(), + getWidth(), getHeight()); + } + + timerCallback(); +} + +static const Rectangle getMoviePos (Component* const c) +{ + return Rectangle (c->getScreenX() - c->getTopLevelComponent()->getScreenX(), + c->getScreenY() - c->getTopLevelComponent()->getScreenY(), + jmax (1, c->getWidth()), + jmax (1, c->getHeight())); +} + +void QuickTimeMovieComponent::moved() +{ + resized(); +} + +void QuickTimeMovieComponent::resized() +{ + InternalData* const id = (InternalData*) internal; + + if (id->controller != 0 && isShowing()) + { + checkWindowAssociation(); + + GrafPtr port = getPortForWindow (getWindowHandle()); + + if (port != 0) + { + GrafPtr savedPort; + GetPort (&savedPort); + + SetMovieGWorld (id->movie, (CGrafPtr) port, 0); + MacSetPort (port); + + lastPositionApplied = getMoviePos (this); + + Rect r; + r.left = (short) lastPositionApplied.getX(); + r.top = (short) lastPositionApplied.getY(); + r.right = (short) lastPositionApplied.getRight(); + r.bottom = (short) lastPositionApplied.getBottom(); + + if (MCGetVisible (id->controller)) + MCSetControllerBoundsRect (id->controller, &r); + else + SetMovieBox (id->movie, &r); + + if (! isPlaying()) + timerCallback(); + + MacSetPort (savedPort); + + repaint(); + } + } +} + +void QuickTimeMovieComponent::visibilityChanged() +{ + checkWindowAssociation(); + QTWinBaseClass::visibilityChanged(); +} + +void QuickTimeMovieComponent::timerCallback() +{ + InternalData* const id = (InternalData*) internal; + + if (id->controller != 0) + { + if (isTimerRunning()) + startTimer (getTimerInterval()); + + MCIdle (id->controller); + + if (lastPositionApplied != getMoviePos (this)) + resized(); + } +} + +void QuickTimeMovieComponent::checkWindowAssociation() +{ + void* const window = getWindowHandle(); + + if (window != associatedWindow + || (window != 0 && ! controllerAssignedToWindow)) + { + associatedWindow = window; + assignMovieToWindow(); + } +} + +void QuickTimeMovieComponent::parentHierarchyChanged() +{ + checkWindowAssociation(); +} + +void QuickTimeMovieComponent::handleMCEvent (void* ev) +{ + InternalData* const id = (InternalData*) internal; + + if (id->controller != 0 && isShowing()) + { + MacClickEventData* data = (MacClickEventData*) ev; + + data->where.h -= getTopLevelComponent()->getScreenX(); + data->where.v -= getTopLevelComponent()->getScreenY(); + + Boolean b = false; + MCPtInController (id->controller, data->where, &b); + + if (b) + { + const int oldTimeBeforeWaitCursor = MessageManager::getInstance()->getTimeBeforeShowingWaitCursor(); + MessageManager::getInstance()->setTimeBeforeShowingWaitCursor (0); + + MCClick (id->controller, + (WindowRef) getWindowHandle(), + data->where, + data->when, + data->modifiers); + + MessageManager::getInstance()->setTimeBeforeShowingWaitCursor (oldTimeBeforeWaitCursor); + } + } +} + +#endif + +//============================================================================== +// (methods common to both platforms..) + +void QuickTimeMovieComponent::goToStart() +{ + setPosition (0.0); +} + +void QuickTimeMovieComponent::setBoundsWithCorrectAspectRatio (const Rectangle& spaceToFitWithin, + const RectanglePlacement& placement) +{ + int normalWidth, normalHeight; + getMovieNormalSize (normalWidth, normalHeight); + + if (normalWidth > 0 && normalHeight > 0 && ! spaceToFitWithin.isEmpty()) + { + double x = 0.0, y = 0.0, w = normalWidth, h = normalHeight; + + placement.applyTo (x, y, w, h, + spaceToFitWithin.getX(), spaceToFitWithin.getY(), + spaceToFitWithin.getWidth(), spaceToFitWithin.getHeight()); + + if (w > 0 && h > 0) + { + setBounds (roundDoubleToInt (x), roundDoubleToInt (y), + roundDoubleToInt (w), roundDoubleToInt (h)); + } + } + else + { + setBounds (spaceToFitWithin); + } +} + + +END_JUCE_NAMESPACE + +#endif diff --git a/src/juce_appframework/gui/components/special/juce_QuickTimeMovieComponent.h b/src/juce_appframework/gui/components/special/juce_QuickTimeMovieComponent.h index ee96ffe35b..34d7ea706a 100644 --- a/src/juce_appframework/gui/components/special/juce_QuickTimeMovieComponent.h +++ b/src/juce_appframework/gui/components/special/juce_QuickTimeMovieComponent.h @@ -137,6 +137,13 @@ public: /** Returns the current play position of the movie. */ double getPosition() const; + /** Changes the movie playback rate. + + A value of 1 is normal speed, greater values play it proportionately faster, + smaller values play it slower. + */ + void setSpeed (const float newSpeed); + /** Changes the movie's playback volume. @param newVolume the volume in the range 0 (silent) to 1.0 (full)