/*============================================================================= oalSource.cpp al_osx Created by Bob Aron on Mon Oct 13 2003. Copyright (c) 2003 Apple Computer Inc. All rights reserved. $Log: oalSource.cpp,v $ Revision 1.47 2005/03/03 07:05:02 baron Guard against nan values in doppler shift that may result in a nan playbackRate to the 3DMixer. [4025376] Revision 1.46 2005/02/23 22:50:46 baron Extend upward pitch shift to 4 octaves [4021380]. Revision 1.45 2005/02/11 23:23:59 baron Move all buffers into the InactiveQueue upon a call to StopSource. This allows GetProcessedBuffers to return the accurate value which is the size of the inactive Q. [3952762] Revision 1.44 2005/01/18 20:45:51 baron Use OpenAL doppler formula instead of the 3DMixer doppler feature. [3917843] Revision 1.43 2004/09/27 21:40:24 baron Make the OALSource object thread safe by using two bufferQ lists and mutexes. Don't dereference NULL ptr. [3791105, 3791120] Revision 1.42 2004/09/02 22:33:21 baron Clean up some code. Revision 1.41 2004/09/02 20:20:07 baron Add workaround for DistanceParameter problem in the 2.0 3DMixer. Allow Doppler to be turned ON. Allow OAL Distance Model to be AL_NONE. Revision 1.40 2004/04/28 17:54:26 baron Set MIXER_PARAMS_UNDEFINED to false. They exist now. Revision 1.39 2004/04/23 16:54:29 baron Correctly set the DistanceAtten property when using the v1.3 3DMixer. Rolloff curve should now be correct and this change fine tunes the change to scale distance calculations to the reference distance. Revision 1.38 2004/03/29 19:04:45 baron For MaxAttenuation, clamp the value to zero, if db calculation is greater than 0.0. Revision 1.37 2004/03/25 19:25:39 baron Don't call kAudioUnitProperty_3DMixerDistanceParams if the preferred mixer is not available. Place temporary definitions so library will build with pre 6.5.1 (QT) headers. Revision 1.36 2004/03/19 22:53:34 baron Add BSD License and clean up comments before submitting to Open Source (Creative). Revision 1.35 2004/03/17 23:44:37 baron Always reverse the sign of the attenuation result before placing the db value to the MixerDistanceParams when setting the 3DMixer params. Revision 1.34 2004/03/16 23:27:00 baron Update comments. Revision 1.33 2004/03/11 19:42:22 baron Add extension to clamp rendering to stereo (ALC_RENDER_CHANNEL_COUNT). Merge the OAL3DGraph and OALDevice classes. General clean up. Revision 1.32 2004/03/04 01:32:08 baron Support 1.3 version of the 3DMixer with added SRC code. Revision 1.31 2004/02/25 20:51:55 baron Check version of 3DMixer component and fail if pre-Panther version. Use playback rate parameter to adjust sample rates if 3DMixer is not 2.0 or >, else use stream format property to change sample rates on the mixer busses. Revision 1.30 2004/02/24 21:21:35 baron More sample rate changes. Revision 1.29 2004/02/24 21:10:01 baron Fix typo. Revision 1.28 2004/02/24 19:05:16 baron Stop using the playback rate parameter to adjust for sample rate of data. Use the stream format property instead. Revision 1.27 2004/02/13 17:43:24 baron Removed mResetGain member from the OALSource class. Fix the Min/Max gain support. Remove older version of the calculate Azimuth/Distance method. Revision 1.26 2004/02/13 02:35:02 baron Check for NANs. Add 3DMixer Distance Params support. Partial Min/Max Gain changes. Everything builds and works, some commented code needs to be clean up still. Revision 1.25 2004/02/11 18:13:34 baron Remove unnecessary calls to AUGraphUpdate(). No longer call AUGraphStart() for each new play call. Revision 1.24 2004/01/29 22:08:06 baron Misc. cleanup. Revision 1.23 2004/01/27 23:23:29 baron Always reset the gain when Play() is called. Revision 1.22 2004/01/26 18:47:30 baron Separate the FileRenderProc() into a notify and input proc. Revision 1.21 2004/01/23 06:16:43 baron Improve buffer queue looping code. Fix try/catch types. Revision 1.20 2004/01/20 22:31:52 baron Simplify the code that resets the buffer queue. Revision 1.19 2004/01/20 19:45:07 baron Reinstate the should fade code. Set bus stream format in the GetAvailableMono/Stereo methods instead of the Source Play() method. cleanup. Revision 1.18 2004/01/20 02:15:11 baron Many changes: Doppler support, warm buffers support, logging added, buffer queueing changes, reduce calls to AudioUnit SetProperty and SetParameter. Revision 1.17 2004/01/13 18:31:31 josephw added distance min/max clamps depending on min/max volume and max distance Revision 1.16 2004/01/09 18:35:06 baron Remove calloc from Render Proc and other potential blocking code. Add several build flags and logging code. Revision 1.15 2004/01/08 20:17:43 baron Remove unneeded method. Revision 1.14 2003/12/19 22:22:30 baron Only reset specific bus settings (pitch, gain, distance, doppler) when necessary from the source's render proc. Revision 1.13 2003/12/17 18:27:52 baron Add mMinDistance member for distance calculations. Revision 1.12 2003/12/11 23:07:00 baron Add the ability to bypass the AudioConverters when the data is already in the mixer's format. Add stereo pass through support. Revision 1.11 2003/12/10 17:58:22 baron Add some logging code and clamp bus volume. Revision 1.10 2003/12/09 21:07:37 josephw Optimized the azimuth calculation in CalculateDistanceAndAzimuthAndVolume Revision 1.9 2003/12/09 01:26:21 baron more cleanup. Revision 1.8 2003/12/06 00:11:20 baron Clean up RenderProc and ACFillProc. Don't count samples pulled for anymore. Revision 1.7 2003/12/03 23:17:09 baron Add some doppler support and clean up comments Revision 1.6 2003/12/02 18:59:50 baron Add check-in comment logging. $NoKeywords: $ =============================================================================*/ /********************************************************************************************************************************** * * OpenAL cross platform audio library * Copyright © 2004, Apple Computer, Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, are permitted provided * that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following * disclaimer in the documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of its contributors may be used to endorse or promote * products derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * **********************************************************************************************************************************/ /* Each OALSource object maintains a BufferQueue (list) and an ACMap (multimap). The buffer queue is an ordered list of BufferInfo structs. These structs contain an OAL buffer and other pertinent data. The ACMap is a multimap of ACInfo structs. These structs each contain an AudioConverter and the input format of the AudioConverter. The AudioConverters are created as needed each time a buffer with a new format is added to the queue. This allows differently formatted data to be queued seemlessly. The correct AC is used for each buffer as the BufferInfo keeps track of the appropriate AC to use. All public methods that are not called from within the source object are guarded by a state mutex to ensure the object is not disposed from one thread, while running one of it's mthods from another. If the method is also used from within an object method, it takes */ #include "oalSource.h" #define LOG_PLAYBACK 0 #define LOG_VERBOSE 0 #define LOG_BUFFER_QUEUEING 0 #define LOG_DOPPLER 0 #define CALCULATE_POSITION 1 // this should be true except for testing #define USE_MUTEXES 1 // this should be true except for testing #define WARM_THE_BUFFERS 1 // when playing, touch all the audio data in memory once before it is needed in the render proc (RealTime thread) // These are macros for locking around play/stop code: #define TRY_PLAY_MUTEX \ bool wasLocked = false; \ try \ { \ if (CallingInRenderThread()) { \ if (mPlayGuard.Try(wasLocked) == false) { \ goto home; \ } \ } else { \ wasLocked = mPlayGuard.Lock(); \ } #define UNLOCK_PLAY_MUTEX \ home:; \ if (wasLocked) \ mPlayGuard.Unlock(); \ \ } catch (OSStatus stat) { \ if (wasLocked) \ mPlayGuard.Unlock(); \ throw stat; \ } catch (...) { \ if (wasLocked) \ mPlayGuard.Unlock(); \ throw -1; \ } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // TEMPORARY // This build flag should be on if you do not have a copy of AudioUnitProperties.h that // defines the struct MixerDistanceParams and the constant kAudioUnitProperty_3DMixerDistanceParams #define MIXER_PARAMS_UNDEFINED 0 #if MIXER_PARAMS_UNDEFINED typedef struct MixerDistanceParams { Float32 mReferenceDistance; Float32 mMaxDistance; Float32 mMaxAttenuation; } MixerDistanceParams; enum { kAudioUnitProperty_3DMixerDistanceParams = 3010 }; #endif // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // OALSource // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #pragma mark ***** OALSources - PUBLIC ***** OALSource::OALSource (const UInt32 inSelfToken, OALContext *inOwningContext) : mSelfToken (inSelfToken), mOwningContext(inOwningContext), mOwningDevice(NULL), mStateMutex(kStateMutex), mCalculateDistance(true), mResetBusFormat(true), mResetPitch(true), mBufferQueueActive(NULL), mBufferQueueInactive(NULL), mBufferQueueMutex(kBufferQueueMutex), mCurrentBufferIndex(0), mQueueIsProcessed(true), mRenderThreadID (0), mPlayGuard ("OALAudioPlayer::Guard"), mCurrentPlayBus (kSourceNeedsBus), mShouldFade (false), mACMap(NULL), mOutputSilence(false), mLooping(AL_FALSE), mSourceRelative(AL_FALSE), mConeInnerAngle(360.0), mConeOuterAngle(360.0), mConeOuterGain(0.0), mReadIndex(0.0), mTempSourceStorageBufferSize(2048), // only used if preferred mixer is unavailable mState(AL_INITIAL), mGain(1.0), mPitch(1.0), // this is the user pitch setting changed via the OAL APIs mDopplerScaler(1.0), mRollOffFactor(kDefaultRolloff), mReferenceDistance(kDefaultReferenceDistance), mMaxDistance(kDefaultMaximumDistance), // ***** should be MAX_FLOAT mMinDistance(1.0), mMinGain(0.0), mMaxGain(1.0) { mOwningDevice = mOwningContext->GetOwningDevice(); mPosition[0] = 0.0; mPosition[1] = 0.0; mPosition[2] = 0.0; mVelocity[0] = 0.0; mVelocity[1] = 0.0; mVelocity[2] = 0.0; mDirection[0] = 0.0; mDirection[1] = 0.0; mDirection[2] = 0.0; mBufferQueueActive = new BufferQueue(); mBufferQueueInactive = new BufferQueue(); mACMap = new ACMap(); mReferenceDistance = mOwningDevice->GetDefaultReferenceDistance(); mMaxDistance = mOwningDevice->GetDefaultMaxDistance(); if (!mOwningDevice->IsPreferredMixerAvailable()) { // since the preferred mixer is not available, some temporary storgae will be needed for SRC // for now assume that sources will not have more than 2 channels of data mTempSourceStorage = (AudioBufferList *) malloc ((offsetof(AudioBufferList, mBuffers)) + (2 * sizeof(AudioBuffer))); mTempSourceStorage->mBuffers[0].mDataByteSize = mTempSourceStorageBufferSize; mTempSourceStorage->mBuffers[0].mData = malloc(mTempSourceStorageBufferSize); mTempSourceStorage->mBuffers[1].mDataByteSize = mTempSourceStorageBufferSize; mTempSourceStorage->mBuffers[1].mData = malloc(mTempSourceStorageBufferSize); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OALSource::~OALSource() { #if LOG_VERBOSE DebugMessageN1("OALSource::~OALSource() - OALSource = %ld\n", mSelfToken); #endif // make sure the object is not torn if one of it's methods is being executed from another thread CAMutex::Locker stateLock(mStateMutex); Stop(); // stop any playback that is in progress // release the 3DMixer bus if necessary if (mCurrentPlayBus != kSourceNeedsBus) { mOwningDevice->SetBusAsAvailable (mCurrentPlayBus); mCurrentPlayBus = kSourceNeedsBus; } mBufferQueueMutex.Lock(); // empty the two queues UInt32 count = mBufferQueueInactive->Size(); for (UInt32 i = 0; i < count; i++) { mBufferQueueInactive->RemoveQueueEntryByIndex(0); } delete (mBufferQueueInactive); count = mBufferQueueActive->Size(); for (UInt32 i = 0; i < count; i++) { mBufferQueueActive->RemoveQueueEntryByIndex(0); } delete (mBufferQueueActive); mBufferQueueMutex.Unlock(); // remove all the AudioConverters that were created for the buffer queue of this source object if (mACMap) { mACMap->RemoveAllConverters(); delete (mACMap); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // SET METHODS // Any set methods that do not take an isRenderThread argument should not be called from // another method of the source object // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetPitch (float inPitch, bool isRenderThread) { // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); if ((inPitch == mPitch) && (mResetPitch == false)) return; // nothing to do mPitch = inPitch; if (!mOwningDevice->IsPreferredMixerAvailable()) return; // 1.3 3DMixer does not work properly when doing SRC on a mono bus Float32 newPitch = mPitch * mDopplerScaler; if (mCurrentPlayBus != kSourceNeedsBus) { BufferInfo *oalBuffer = mBufferQueueActive->Get(mCurrentBufferIndex); if (oalBuffer != NULL) { #if LOG_GRAPH_AND_MIXER_CHANGES DebugMessageN2("OALSource::SetPitch: k3DMixerParam_PlaybackRate called - OALSource:mPitch = %ld:%f2\n", mSelfToken, mPitch ); #endif OSStatus result = AudioUnitSetParameter ( mOwningDevice->GetMixerUnit(), k3DMixerParam_PlaybackRate, kAudioUnitScope_Input, mCurrentPlayBus, newPitch /*mPitch*/, 0); if (result != noErr) DebugMessageN2("OALSource::SetPitch: k3DMixerParam_PlaybackRate called - OALSource:mPitch = %ld:%f2\n", mSelfToken, mPitch ); } mResetPitch = false; } else mResetPitch = true; // the source is not currently connected to a bus, so make this change when play is called } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetGain (float inGain, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetGain - OALSource:inGain = %ld:%f\n", mSelfToken, inGain); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); if (mGain != inGain) { mGain = inGain; UpdateBusGain(isRenderThread); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetMinGain (Float32 inMinGain, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetMinGain - OALSource:inMinGain = %ld:%f\n", mSelfToken, inMinGain); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); if (mMinGain != inMinGain) { mMinGain = inMinGain; UpdateBusGain(isRenderThread); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetMaxGain (Float32 inMaxGain, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetMaxGain - OALSource:inMaxGain = %ld:%f\n", mSelfToken, inMaxGain); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); if (mMaxGain != inMaxGain) { mMaxGain = inMaxGain; UpdateBusGain(isRenderThread); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetReferenceDistance (Float32 inReferenceDistance, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetReferenceDistance - OALSource:inReferenceDistance = %ld/%f2\n", mSelfToken, inReferenceDistance); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); if (inReferenceDistance == mReferenceDistance) return; // nothing to do mReferenceDistance = inReferenceDistance; if (mCurrentPlayBus != kSourceNeedsBus) { if (!mOwningDevice->IsPreferredMixerAvailable()) { // the pre-2.0 3DMixer does not accept kAudioUnitProperty_3DMixerDistanceParams, it has do some extra work and use the DistanceAtten property instead mOwningDevice->SetDistanceAttenuation(mCurrentPlayBus, mReferenceDistance, mMaxDistance, mRollOffFactor); } else { MixerDistanceParams distanceParams; UInt32 outSize = sizeof(distanceParams); OSStatus result = AudioUnitGetProperty(mOwningDevice->GetMixerUnit(), kAudioUnitProperty_3DMixerDistanceParams, kAudioUnitScope_Input, mCurrentPlayBus, &distanceParams, &outSize); if (result == noErr) { Float32 rollOff = mRollOffFactor; if (mOwningDevice->IsDistanceScalingRequired()) { // scale the reference distance distanceParams.mReferenceDistance = (mReferenceDistance/mMaxDistance) * kDistanceScalar; // limit the max distance distanceParams.mMaxDistance = kDistanceScalar; // scale the rolloff rollOff *= (kDistanceScalar/mMaxDistance); } else distanceParams.mReferenceDistance = mReferenceDistance; distanceParams.mMaxAttenuation = 20 * log10(distanceParams.mReferenceDistance / (distanceParams.mReferenceDistance + (rollOff * (distanceParams.mMaxDistance - distanceParams.mReferenceDistance)))); if (distanceParams.mMaxAttenuation < 0.0) distanceParams.mMaxAttenuation *= -1.0; else distanceParams.mMaxAttenuation = 0.0; // if db result was positive, clamp it to zero result = AudioUnitSetProperty(mOwningDevice->GetMixerUnit(), kAudioUnitProperty_3DMixerDistanceParams, kAudioUnitScope_Input, mCurrentPlayBus, &distanceParams, sizeof(distanceParams)); } } } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetMaxDistance (Float32 inMaxDistance, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetMaxDistance - OALSource:inMaxDistance = %ld:%f2\n", mSelfToken, inMaxDistance); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); if (inMaxDistance == mMaxDistance) return; // nothing to do mMaxDistance = inMaxDistance; if (mCurrentPlayBus != kSourceNeedsBus) { if (!mOwningDevice->IsPreferredMixerAvailable()) { // the pre-2.0 3DMixer does not accept kAudioUnitProperty_3DMixerDistanceParams, it has do some extra work and use the DistanceAtten property instead mOwningDevice->SetDistanceAttenuation(mCurrentPlayBus, mReferenceDistance, mMaxDistance, mRollOffFactor); } else { MixerDistanceParams distanceParams; UInt32 outSize = sizeof(distanceParams); OSStatus result = AudioUnitGetProperty(mOwningDevice->GetMixerUnit(), kAudioUnitProperty_3DMixerDistanceParams, kAudioUnitScope_Input, mCurrentPlayBus, &distanceParams, &outSize); if (result == noErr) { Float32 rollOff = mRollOffFactor; if (mOwningDevice->IsDistanceScalingRequired()) { // scale the reference distance distanceParams.mReferenceDistance = (mReferenceDistance/mMaxDistance) * kDistanceScalar; // limit the max distance distanceParams.mMaxDistance = kDistanceScalar; // scale the rolloff rollOff *= (kDistanceScalar/mMaxDistance); } else distanceParams.mMaxDistance = mMaxDistance; distanceParams.mMaxAttenuation = 20 * log10(distanceParams.mReferenceDistance / (distanceParams.mReferenceDistance + (rollOff * (distanceParams.mMaxDistance - distanceParams.mReferenceDistance)))); if (distanceParams.mMaxAttenuation < 0.0) distanceParams.mMaxAttenuation *= -1.0; else distanceParams.mMaxAttenuation = 0.0; // if db result was positive, clamp it to zero result = AudioUnitSetProperty(mOwningDevice->GetMixerUnit(), kAudioUnitProperty_3DMixerDistanceParams, kAudioUnitScope_Input, mCurrentPlayBus, &distanceParams, sizeof(distanceParams)); } } } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetRollOffFactor (Float32 inRollOffFactor, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetRollOffFactor - OALSource:inRollOffFactor = %ld:%f\n", mSelfToken, inRollOffFactor); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); if (inRollOffFactor == mRollOffFactor) return; // nothing to do mRollOffFactor = inRollOffFactor; if (mCurrentPlayBus != kSourceNeedsBus) { if (!mOwningDevice->IsPreferredMixerAvailable()) { // the pre-2.0 3DMixer does not accept kAudioUnitProperty_3DMixerDistanceParams, it has do some extra work and use the DistanceAtten property instead mOwningDevice->SetDistanceAttenuation(mCurrentPlayBus, mReferenceDistance, mMaxDistance, mRollOffFactor); } else { MixerDistanceParams distanceParams; UInt32 outSize = sizeof(distanceParams); OSStatus result = AudioUnitGetProperty(mOwningDevice->GetMixerUnit(), kAudioUnitProperty_3DMixerDistanceParams, kAudioUnitScope_Input, mCurrentPlayBus, &distanceParams, &outSize); if (result == noErr) { Float32 rollOff = mRollOffFactor; if (mOwningDevice->IsDistanceScalingRequired()) { // scale the reference distance distanceParams.mReferenceDistance = (mReferenceDistance/mMaxDistance) * kDistanceScalar; // limit the max distance distanceParams.mMaxDistance = kDistanceScalar; // scale the rolloff rollOff *= (kDistanceScalar/mMaxDistance); } distanceParams.mMaxAttenuation = 20 * log10(distanceParams.mReferenceDistance / (distanceParams.mReferenceDistance + (rollOff * (distanceParams.mMaxDistance - distanceParams.mReferenceDistance)))); if (distanceParams.mMaxAttenuation < 0.0) distanceParams.mMaxAttenuation *= -1.0; else distanceParams.mMaxAttenuation = 0.0; // if db result was positive, clamp it to zero result = AudioUnitSetProperty(mOwningDevice->GetMixerUnit(), kAudioUnitProperty_3DMixerDistanceParams, kAudioUnitScope_Input, mCurrentPlayBus, &distanceParams, sizeof(distanceParams)); } } } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetLooping (UInt32 inLooping, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetLooping called - OALSource:inLooping = %ld:%ld\n", mSelfToken, inLooping); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); mLooping = inLooping; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetPosition (Float32 inX, Float32 inY, Float32 inZ, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN4("OALSource::SetPosition called - OALSource:X:Y:Z = %ld:%f:%f:%f\n", mSelfToken, inX, inY, inZ); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); mPosition[0] = inX; mPosition[1] = inY; mPosition[2] = inZ; mCalculateDistance = true; // change the distance next time the PreRender proc or a new Play() is called } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetVelocity (Float32 inX, Float32 inY, Float32 inZ, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN1("OALSource::SetVelocity called - OALSource = %ld\n", mSelfToken); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); mVelocity[0] = inX; mVelocity[1] = inY; mVelocity[2] = inZ; mCalculateDistance = true; // change the velocity next time the PreRender proc or a new Play() is called } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetDirection (Float32 inX, Float32 inY, Float32 inZ, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN1("OALSource::SetDirection called - OALSource = %ld\n", mSelfToken); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); mDirection[0] = inX; mDirection[1] = inY; mDirection[2] = inZ; mCalculateDistance = true; // change the direction next time the PreRender proc or a new Play() is called } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetSourceRelative (UInt32 inSourceRelative, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetSourceRelative called - OALSource:inSourceRelative = %ld:%ld\n", mSelfToken, inSourceRelative); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); mSourceRelative = inSourceRelative; mCalculateDistance = true; // change the source relative next time the PreRender proc or a new Play() is called } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetChannelParameters (bool isRenderThread) { #if LOG_VERBOSE DebugMessageN1("OALSource::SetChannelParameters called - OALSource = %ld\n", mSelfToken); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); mCalculateDistance = true; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetConeInnerAngle (Float32 inConeInnerAngle, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetConeInnerAngle called - OALSource:inConeInnerAngle = %ld:%f2\n", mSelfToken, inConeInnerAngle); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); mConeInnerAngle = inConeInnerAngle; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetConeOuterAngle (Float32 inConeOuterAngle, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetConeOuterAngle called - OALSource:inConeOuterAngle = %ld:%f2\n", mSelfToken, inConeOuterAngle); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); mConeOuterAngle = inConeOuterAngle; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::SetConeOuterGain (Float32 inConeOuterGain, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetConeOuterGain called - OALSource:inConeOuterGain = %ld:%f2\n", mSelfToken, inConeOuterGain); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); mConeOuterGain = inConeOuterGain; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // GET METHODS // Any get methods that do not take an isRenderThread argument should not be called from // another method of the source object // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Float32 OALSource::GetPitch () { CAMutex::Locker stateLock(mStateMutex); return mPitch; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Float32 OALSource::GetDopplerScaler () { CAMutex::Locker stateLock(mStateMutex); return mDopplerScaler; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) Float32 OALSource::GetGain () { CAMutex::Locker stateLock(mStateMutex); return mGain; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) Float32 OALSource::GetMinGain () { CAMutex::Locker stateLock(mStateMutex); return mMinGain; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) Float32 OALSource::GetMaxGain () { CAMutex::Locker stateLock(mStateMutex); return mMaxGain; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) Float32 OALSource::GetReferenceDistance () { CAMutex::Locker stateLock(mStateMutex); return mReferenceDistance; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) Float32 OALSource::GetMaxDistance () { CAMutex::Locker stateLock(mStateMutex); return mMaxDistance; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) Float32 OALSource::GetRollOffFactor () { CAMutex::Locker stateLock(mStateMutex); return mRollOffFactor; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) UInt32 OALSource::GetLooping () { CAMutex::Locker stateLock(mStateMutex); return mLooping; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::GetPosition (Float32 &inX, Float32 &inY, Float32 &inZ, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN1("OALSource::GetPosition called - OALSource = %ld\n", mSelfToken); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); inX = mPosition[0]; inY = mPosition[1]; inZ = mPosition[2]; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::GetVelocity (Float32 &inX, Float32 &inY, Float32 &inZ, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN1("OALSource::GetVelocity called - OALSource = %ld\n", mSelfToken); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); inX = mVelocity[0]; inY = mVelocity[1]; inZ = mVelocity[2]; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::GetDirection (Float32 &inX, Float32 &inY, Float32 &inZ, bool isRenderThread) { #if LOG_VERBOSE DebugMessageN1("OALSource::GetDirection called - OALSource = %ld\n", mSelfToken); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); inX = mDirection[0]; inY = mDirection[1]; inZ = mDirection[2]; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) UInt32 OALSource::GetSourceRelative () { CAMutex::Locker stateLock(mStateMutex); return mSourceRelative; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) Float32 OALSource::GetConeInnerAngle () { CAMutex::Locker stateLock(mStateMutex); return mConeInnerAngle; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) Float32 OALSource::GetConeOuterAngle () { CAMutex::Locker stateLock(mStateMutex); return mConeOuterAngle; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) Float32 OALSource::GetConeOuterGain () { CAMutex::Locker stateLock(mStateMutex); return mConeOuterGain; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) UInt32 OALSource::GetState() { CAMutex::Locker stateLock(mStateMutex); return mState; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Should only be called from the API set (which should not be th erender thread) UInt32 OALSource::GetToken() { CAMutex::Locker stateLock(mStateMutex); return mSelfToken; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // BUFFER QUEUE METHODS // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UInt32 OALSource::GetQLength() { // as far as the user is concerned, the Q length is the size of the inactive & active lists UInt32 returnValue = 0; CAMutex::Locker qMutex(mBufferQueueMutex); returnValue = mBufferQueueInactive->Size() + mBufferQueueActive->Size(); #if LOG_BUFFER_QUEUEING DebugMessageN2("OALSource::GetQLength called - OALSource:QLength = %ld:%ld\n", mSelfToken, returnValue); #endif return (returnValue); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UInt32 OALSource::GetBuffersProcessed() { UInt32 returnValue = 0; if (mState == AL_INITIAL) return(0); else { CAMutex::Locker qMutex(mBufferQueueMutex); returnValue = mBufferQueueInactive->Size(); } #if LOG_BUFFER_QUEUEING DebugMessageN2("OALSource::GetBuffersProcessed called - OALSource:ProcessedCount = %ld:%ld\n", mSelfToken, returnValue); #endif return (returnValue); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // This should not be called from the render thread, the caller can wait on the lock until it is safe to // touch both active and inactive lists void OALSource::SetBuffer (UInt32 inBufferToken, OALBuffer *inBuffer) { #if LOG_VERBOSE DebugMessageN2("OALSource::SetBuffer called - OALSource:inBufferToken = %ld:%ld\n", mSelfToken, inBufferToken); #endif CAMutex::Locker stateLock(mBufferQueueMutex); // empty the two queues UInt32 count = mBufferQueueInactive->Size(); for (UInt32 i = 0; i < count; i++) { mBufferQueueInactive->RemoveQueueEntryByIndex(0); } count = mBufferQueueActive->Size(); for (UInt32 i = 0; i < count; i++) { mBufferQueueActive->RemoveQueueEntryByIndex(0); } // if inBufferToken == 0, do nothing, passing NONE to this method is legal and should merely empty the queue if (inBufferToken != 0) { AppendBufferToQueue(inBufferToken, inBuffer); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ UInt32 OALSource::GetBuffer () { #if LOG_VERBOSE DebugMessageN2("OALSource::GetBuffer called - OALSource:currentBuffer = %ld:%ld\n", mSelfToken, mBufferQueueActive->GetBufferTokenByIndex(mCurrentBufferIndex)); #endif // for now, return the currently playing buffer in an active source, or the 1st buffer of the Q in an inactive source return (mBufferQueueActive->GetBufferTokenByIndex(mCurrentBufferIndex)); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // This should NOT be called from the render thread void OALSource::AppendBufferToQueue(UInt32 inBufferToken, OALBuffer *inBuffer) { #if LOG_BUFFER_QUEUEING DebugMessageN2("OALSource::AppendBufferToQueue called - OALSource:inBufferToken = %ld:%ld\n", mSelfToken, inBufferToken); #endif CAMutex::Locker qMutex(mBufferQueueMutex); OSStatus result = noErr; try { if (mBufferQueueActive->Empty()) { mCurrentBufferIndex = 0; // this is the first buffer in the queue mQueueIsProcessed = false; } // do we need an AC for the format of this buffer? if (inBuffer->mDataHasBeenConverted) { // the data was already convertered to the mixer format, no AC is necessary (as indicated by the ACToken setting of zero) mBufferQueueActive->AppendBuffer(inBufferToken, inBuffer, 0); } else { // check the format against the real format of the data, NOT the input format of the converter which may be subtly different // both in SampleRate and Interleaved flags UInt32 outToken = 0; mACMap->GetACForFormat(inBuffer->mDataFormat, outToken); if (outToken == 0) { // create an AudioConverter for this format because there isn't one yet AudioConverterRef converter = 0; CAStreamBasicDescription inFormat; inFormat.SetFrom(inBuffer->mDataFormat); // if the source is mono, set the flags to be non interleaved, so a reinterleaver does not get setup when // completely unnecessary - since the flags on output are always set to non interleaved if (inFormat.NumberChannels() == 1) inFormat.mFormatFlags |= kAudioFormatFlagIsNonInterleaved; // output should have actual number of channels, but frame/packet size is for single channel // this is to make de interleaved data work correctly with > 1 channel CAStreamBasicDescription outFormat; outFormat.mChannelsPerFrame = inFormat.NumberChannels(); outFormat.mSampleRate = inFormat.mSampleRate; outFormat.mFormatID = kAudioFormatLinearPCM; outFormat.mFormatFlags = kAudioFormatFlagsNativeFloatPacked | kAudioFormatFlagIsNonInterleaved; outFormat.mBytesPerPacket = sizeof (Float32); outFormat.mFramesPerPacket = 1; outFormat.mBytesPerFrame = sizeof (Float32); outFormat.mBitsPerChannel = sizeof (Float32) * 8; result = AudioConverterNew(&inFormat, &outFormat, &converter); THROW_RESULT ACInfo info; info.mInputFormat = inBuffer->mDataFormat; info.mConverter = converter; // add this AudioConverter to the source's ACMap UInt32 newACToken = GetNewToken(); mACMap->Add(newACToken, &info); // add the buffer to the queue - each buffer now knows which AC to use when it is converted in the render proc mBufferQueueActive->AppendBuffer(inBufferToken, inBuffer, newACToken); } else { // there is already an AC for this buffer's data format, so just append the buffer to the queue mBufferQueueActive->AppendBuffer(inBufferToken, inBuffer, outToken); } } } catch (OSStatus result) { DebugMessageN1("APPEND BUFFER FAILED %ld\n", mSelfToken); throw (result); } #if LOG_BUFFER_QUEUEING DebugMessageN2("OALSource::AppendBufferToQueue called - OALSource:QLength = %ld:%ld\n", mSelfToken, mBufferQueueInactive->Size() + mBufferQueueActive->Size()); #endif return; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Do NOT call this from the render thread void OALSource::RemoveBuffersFromQueue(UInt32 inCount, UInt32 *outBufferTokens) { if (inCount == 0) return; #if LOG_BUFFER_QUEUEING DebugMessageN2("OALSource::RemoveBuffersFromQueue called - OALSource:inCount = %ld:%ld\n", mSelfToken, inCount); #endif try { // 1) do nothing if queue is playing and in loop mode - to risky that a requested unqueue buffer will be needed // 2) do nothing if any of the requested buffers is in progress or not processed and queue is PLAYING // 3) if source state is not playing or paused, do the request if the buffer count is valid if ((mState == AL_PLAYING) || (mState == AL_PAUSED)) { if (mLooping == true) { // case #1 throw ((OSStatus) AL_INVALID_OPERATION); } } // it is safe to remove the buffers from the queue right now because the source is not rendering CAMutex::Locker qMutex(mBufferQueueMutex); // see if there are enough buffers in the inactive list to satisfy the request if (inCount > mBufferQueueInactive->Size()) { mBufferQueueMutex.Unlock(); throw ((OSStatus) AL_INVALID_OPERATION); } if (!mBufferQueueInactive->Empty()) { for (UInt32 i = 0; i < inCount; i++) { UInt32 outToken = mBufferQueueInactive->RemoveQueueEntryByIndex(0); if (outBufferTokens) outBufferTokens[i] = outToken; } } } catch (OSStatus result) { DebugMessageN1("REMOVE BUFFER FAILED, OALSource = %ld\n", mSelfToken); throw (result); } #if LOG_BUFFER_QUEUEING DebugMessageN2("OALSource:RemoveBuffersFromQueue called - OALSource:QLength = %ld:%ld\n", mSelfToken, mBufferQueueInactive->Size() + mBufferQueueActive->Size()); #endif return; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // PLAYBACK METHODS // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::Play() { UInt32 outSize; if (!mOwningDevice->IsPlayable()) return; try { // reset some of the class members before we start playing Reset(); #if LOG_PLAYBACK DebugMessageN3("OALSource::Play called - OALSource:QSize:Looping = %ld:%ld:%ld\n", mSelfToken, mBufferQueueActive->Size(), mLooping); #endif OSStatus result = noErr; if (mBufferQueueActive->Empty()) return; // there isn't anything to do // ******* Get the format for the first buffer in the queue mCurrentBufferIndex = 0; // find the first real buffer in the queue because it is legal to start the queue with an AL_NONE buffer while (mBufferQueueActive->GetBufferTokenByIndex(mCurrentBufferIndex) == AL_NONE) { // mark buffer as processed mBufferQueueActive->SetBufferAsProcessed(mCurrentBufferIndex); mCurrentBufferIndex++; } BufferInfo *buffer = mBufferQueueActive->Get(mCurrentBufferIndex); if (buffer == NULL) throw AL_INVALID_OPERATION; #if LOG_PLAYBACK DebugMessage("OALSource::Play called - Format of 1st buffer in the Q = \n"); buffer->mBuffer->mDataFormat.Print(); #endif #if WARM_THE_BUFFERS // touch the memory now instead of in the render proc, so audio data will be paged in if it's currently in VM volatile UInt32 X; UInt32 *start = (UInt32 *)buffer->mBuffer->mData; UInt32 *end = (UInt32 *)(buffer->mBuffer->mData + (buffer->mBuffer->mDataSize &0xFFFFFFFC)); while (start < end) { X = *start; start += 1024; } #endif if (buffer->mBuffer->mDataHasBeenConverted == false) AudioConverterReset(mACMap->Get(buffer->mACToken)); if (mCurrentPlayBus == kSourceNeedsBus) { // the bus stream format will get set if necessary while getting the available bus mCurrentPlayBus = (buffer->mBuffer->mDataFormat.NumberChannels() == 1) ? mOwningDevice->GetAvailableMonoBus() : mOwningDevice->GetAvailableStereoBus(); Float32 rollOff = mRollOffFactor; Float32 refDistance = mReferenceDistance; Float32 maxDistance = mMaxDistance; if (mOwningDevice->IsDistanceScalingRequired()) { refDistance = (mReferenceDistance/mMaxDistance) * kDistanceScalar; maxDistance = kDistanceScalar; rollOff *= (kDistanceScalar/mMaxDistance); } Float32 testAttenuation = 20 * log10(mReferenceDistance / (mReferenceDistance + (mRollOffFactor * (mMaxDistance - mReferenceDistance)))); if (testAttenuation < 0.0) testAttenuation *= -1.0; else testAttenuation = 0.0; // if db result was positive, clamp it to zero if (mOwningDevice->IsPreferredMixerAvailable()) { // Set the MixerDistanceParams for the new bus if necessary MixerDistanceParams distanceParams; outSize = sizeof(distanceParams); result = AudioUnitGetProperty(mOwningDevice->GetMixerUnit(), kAudioUnitProperty_3DMixerDistanceParams, kAudioUnitScope_Input, mCurrentPlayBus, &distanceParams, &outSize); if ((result == noErr) && ((distanceParams.mReferenceDistance != refDistance) || (distanceParams.mMaxDistance != maxDistance) || (distanceParams.mMaxAttenuation != testAttenuation))) { distanceParams.mMaxAttenuation = testAttenuation; if (mOwningDevice->IsDistanceScalingRequired()) { distanceParams.mReferenceDistance = (mReferenceDistance/mMaxDistance) * kDistanceScalar; // limit the max distance distanceParams.mMaxDistance = kDistanceScalar; distanceParams.mMaxAttenuation = testAttenuation; } else { distanceParams.mReferenceDistance = mReferenceDistance; distanceParams.mMaxDistance = mMaxDistance; } result = AudioUnitSetProperty(mOwningDevice->GetMixerUnit(), kAudioUnitProperty_3DMixerDistanceParams, kAudioUnitScope_Input, mCurrentPlayBus, &distanceParams, sizeof(distanceParams)); } } else { // the pre-2.0 3DMixer does not accept kAudioUnitProperty_3DMixerDistanceParams, it has do some extra work and use the DistanceAtten property instead mOwningDevice->SetDistanceAttenuation(mCurrentPlayBus, mReferenceDistance, mMaxDistance, mRollOffFactor); } } // get the sample rate of the bus CAStreamBasicDescription desc; outSize = sizeof(desc); result = AudioUnitGetProperty(mOwningDevice->GetMixerUnit(), kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, mCurrentPlayBus, &desc, &outSize); mResetPitch = true; mCalculateDistance = true; if (desc.mSampleRate != buffer->mBuffer->mDataFormat.mSampleRate) mResetBusFormat = true; // only reset the bus stream format if it is different than sample rate of the data // *************************** Set properties for the mixer bus #if USE_MUTEXES TRY_PLAY_MUTEX #endif ChangeChannelSettings(false); UpdateBusGain(false); mShouldFade = false; mState = AL_PLAYING; mQueueIsProcessed = false; // set the render callback for the player AU mPlayCallback.inputProc = SourceInputProc; mPlayCallback.inputProcRefCon = this; result = AudioUnitSetProperty ( mOwningDevice->GetMixerUnit(), kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, mCurrentPlayBus, &mPlayCallback, sizeof(mPlayCallback)); THROW_RESULT result = AudioUnitAddRenderNotify(mOwningDevice->GetMixerUnit(), SourceNotificationProc, this); THROW_RESULT #if USE_MUTEXES UNLOCK_PLAY_MUTEX #endif } catch (OSStatus result) { DebugMessageN2("PLAY FAILED source = %ld, err = %ld\n", mSelfToken, result); throw (result); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::Pause() { #if LOG_PLAYBACK DebugMessageN1("OALSource::Pause called - OALSource = %ld\n", mSelfToken); #endif #if USE_MUTEXES TRY_PLAY_MUTEX #endif if (mCurrentPlayBus != kSourceNeedsBus) { AudioUnitRemoveRenderNotify(mOwningDevice->GetMixerUnit(), SourceNotificationProc,this); mPlayCallback.inputProc = 0; mPlayCallback.inputProcRefCon = 0; AudioUnitSetProperty ( mOwningDevice->GetMixerUnit(), kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, mCurrentPlayBus, &mPlayCallback, sizeof(mPlayCallback)); mState = AL_PAUSED; } #if USE_MUTEXES UNLOCK_PLAY_MUTEX #endif } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::Resume() { #if LOG_PLAYBACK DebugMessageN1("OALSource::Resume called - OALSource = %ld\n", mSelfToken); #endif #if USE_MUTEXES TRY_PLAY_MUTEX #endif if (mState = AL_PAUSED) { OSStatus result = noErr; mPlayCallback.inputProc = SourceInputProc; mPlayCallback.inputProcRefCon = this; // set the render callback for the player AU result = AudioUnitSetProperty ( mOwningDevice->GetMixerUnit(), kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, mCurrentPlayBus, &mPlayCallback, sizeof(mPlayCallback)); THROW_RESULT result = AudioUnitAddRenderNotify(mOwningDevice->GetMixerUnit(), SourceNotificationProc, this); THROW_RESULT mState = AL_PLAYING; } #if USE_MUTEXES UNLOCK_PLAY_MUTEX #endif } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::Stop() { #if LOG_PLAYBACK DebugMessageN1("OALSource::Stop called - OALSource = %ld\n", mSelfToken); #endif #if USE_MUTEXES TRY_PLAY_MUTEX #endif if ((mCurrentPlayBus != kSourceNeedsBus) && (mState != AL_STOPPED)) { AudioUnitRemoveRenderNotify(mOwningDevice->GetMixerUnit(), SourceNotificationProc,this); #if LOG_GRAPH_AND_MIXER_CHANGES DebugMessageN1("OALSource::Stop: set kAudioUnitProperty_SetRenderCallback (removing) called - OALSource = %ld\n", mSelfToken); #endif mPlayCallback.inputProc = 0; mPlayCallback.inputProcRefCon = 0; AudioUnitSetProperty ( mOwningDevice->GetMixerUnit(), kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, mCurrentPlayBus, &mPlayCallback, sizeof(mPlayCallback)); mOwningDevice->SetBusAsAvailable (mCurrentPlayBus); mCurrentPlayBus = kSourceNeedsBus; // move all the remaining unprocessed buffers into the inactive queue so GetProcessedBuffers will // return the correct value in the AL_STOPPED state while (mBufferQueueActive->Size() > 0) { // Get buffer #i from Active List BufferInfo *staleBufferInfo = mBufferQueueActive->Get(0); mBufferQueueActive->SetBufferAsProcessed(0); // Append it to Inactive List mBufferQueueInactive->AppendBuffer(staleBufferInfo->mBufferToken, staleBufferInfo->mBuffer, staleBufferInfo->mACToken); // Remove it from Active List mBufferQueueActive->RemoveQueueEntryByIndex(0); } mState = AL_STOPPED; } #if USE_MUTEXES UNLOCK_PLAY_MUTEX #endif } #pragma mark ***** OALSources - PRIVATE ***** // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // must be called when the source has a mixer bus void OALSource::ChangeChannelSettings(bool isRenderThread) { #if CALCULATE_POSITION if (mCalculateDistance == true) { BufferInfo *bufferInfo = mBufferQueueActive->Get(mCurrentBufferIndex); if (bufferInfo) { #if LOG_GRAPH_AND_MIXER_CHANGES DebugMessageN1("OALSource::ChangeChannelSettings: k3DMixerParam_Azimuth/k3DMixerParam_Distance called - OALSource = %ld\n", mSelfToken); #endif // only calculate position if sound is mono - stereo sounds get no location changes if ( bufferInfo->mBuffer->mDataFormat.NumberChannels() == 1) { Float32 rel_azimuth, rel_distance, dopplerShift; CalculateDistanceAndAzimuth(&rel_distance, &rel_azimuth, &dopplerShift, isRenderThread); if(dopplerShift != mDopplerScaler) { mDopplerScaler = dopplerShift; mResetPitch = true; } if (isnan(rel_azimuth)) { rel_azimuth = 0.0; // DO NOT pass a NAN to the mixer for azimuth DebugMessage("ERROR: OALSOurce::ChangeChannelSettings - CalculateDistanceAndAzimuth returned a NAN for rel_azimuth\n"); } AudioUnitSetParameter(mOwningDevice->GetMixerUnit(), k3DMixerParam_Azimuth, kAudioUnitScope_Input, mCurrentPlayBus, rel_azimuth, 0); // do not change the distance if the user has set the distance model to AL_NONE if (mOwningContext->GetDistanceModel() == AL_NONE) { rel_distance = mReferenceDistance; } else { if (mOwningDevice->IsDistanceScalingRequired()) rel_distance *= (kDistanceScalar/mMaxDistance); } AudioUnitSetParameter(mOwningDevice->GetMixerUnit(), k3DMixerParam_Distance, kAudioUnitScope_Input, mCurrentPlayBus, rel_distance, 0); } } mCalculateDistance = false; } #endif // end CALCULATE_POSITION SetPitch (mPitch, isRenderThread); UpdateBusFormat(isRenderThread); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::Reset() { #if LOG_VERBOSE DebugMessageN1("OALSource::Reset called - OALSource = %ld\n", mSelfToken); #endif JoinBufferLists(); mState = AL_INITIAL; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // this method should only be called when a looping queue reaches it's end and needs to start over (called from render thread) void OALSource::LoopToBeginning() { #if LOG_VERBOSE DebugMessageN1("OALSource::LoopToBeginning called - OALSource = %ld\n", mSelfToken); #endif // swap the list pointers now BufferQueue* tQ = mBufferQueueActive; mBufferQueueActive = mBufferQueueInactive; mBufferQueueInactive = tQ; //mShouldFade = true; bool gotTheLock; mBufferQueueMutex.Try(gotTheLock); mBufferQueueActive->ResetBuffers(); // mark all the buffers as unprocessed mCurrentBufferIndex = 0; // start at the first buffer in the queue mQueueIsProcessed = false; if (gotTheLock) mBufferQueueMutex.Unlock(); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // this method should only be called from a non playing state and is used to rejoin the 2 buffer Q lists void OALSource::JoinBufferLists() { #if LOG_VERBOSE DebugMessageN1("OALSource::JoinBufferLists called - OALSource = %ld\n", mSelfToken); #endif bool gotTheLock; // Try and get the mutex so this buffer can be moved to the inactive list if (mBufferQueueMutex.Try(gotTheLock)) { UInt32 count = mBufferQueueActive->Size(); for (UInt32 i = 0; i < count; i++) { // Get buffer #i from Active List BufferInfo *staleBufferInfo = mBufferQueueActive->Get(mCurrentBufferIndex); mBufferQueueActive->SetBufferAsProcessed(mCurrentBufferIndex); // Append it to Inactive List mBufferQueueInactive->AppendBuffer(staleBufferInfo->mBufferToken, staleBufferInfo->mBuffer, staleBufferInfo->mACToken); // Remove it from Active List mBufferQueueActive->RemoveQueueEntryByIndex(mCurrentBufferIndex); } // swap the list pointers now BufferQueue* tQ = mBufferQueueActive; mBufferQueueActive = mBufferQueueInactive; mBufferQueueInactive = tQ; //mShouldFade = true; mBufferQueueActive->ResetBuffers(); // mark all the buffers as unprocessed mCurrentBufferIndex = 0; // start at the first buffer in the queue mQueueIsProcessed = false; if (gotTheLock) mBufferQueueMutex.Unlock(); } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::UpdateBusGain (bool isRenderThread) { // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); if (mCurrentPlayBus != kSourceNeedsBus) { Float32 busGain; // figure out which gain to actually use if (mMinGain > mGain) busGain = mMinGain; else if (mMaxGain < mGain) busGain = mMaxGain; else busGain = mGain; // clamp the gain used to 0.0-1.0 if (busGain > 1.0) busGain = 1.0; else if (busGain < 0.0) busGain = 0.0; mOutputSilence = busGain > 0.0 ? false : true; if (busGain > 0.0) { Float32 db = 20.0 * log10(busGain); // convert to db if (db < -120.0) db = -120.0; // clamp minimum audible level at -120db #if LOG_GRAPH_AND_MIXER_CHANGES DebugMessageN2("OALSource::UpdateBusGain: k3DMixerParam_Gain called - OALSource:busGain = %ld:%f2\n", mSelfToken, busGain ); #endif OSStatus result = AudioUnitSetParameter ( mOwningDevice->GetMixerUnit(), k3DMixerParam_Gain, kAudioUnitScope_Input, mCurrentPlayBus, db, 0); THROW_RESULT } } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::UpdateBusFormat (bool isRenderThread) { #if LOG_VERBOSE DebugMessageN1("OALSource::UpdateBusFormat - OALSource = %ld\n", mSelfToken); #endif // only lock if this is not being called from the render thread if (!isRenderThread) CAMutex::Locker stateLock(mStateMutex); if (!mOwningDevice->IsPreferredMixerAvailable()) // the pre-2.0 3DMixer cannot change stream formats once initialized return; if (mResetBusFormat) { CAStreamBasicDescription desc; UInt32 outSize = sizeof(desc); OSStatus result = AudioUnitGetProperty(mOwningDevice->GetMixerUnit(), kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, mCurrentPlayBus, &desc, &outSize); if (result == noErr) { BufferInfo *buffer = mBufferQueueActive->Get(mCurrentBufferIndex); if (buffer != NULL) { desc.mSampleRate = buffer->mBuffer->mDataFormat.mSampleRate; AudioUnitSetProperty(mOwningDevice->GetMixerUnit(), kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, mCurrentPlayBus, &desc, sizeof(desc)); mResetBusFormat = false; } } } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::PlayFinished() { #if LOG_PLAYBACK DebugMessageN1("OALSource::PlayFinished called - OALSource = %ld\n", mSelfToken); #endif #if USE_MUTEXES TRY_PLAY_MUTEX #endif if (mCurrentPlayBus != kSourceNeedsBus) { AudioUnitRemoveRenderNotify(mOwningDevice->GetMixerUnit(), SourceNotificationProc,this); mPlayCallback.inputProc = 0; mPlayCallback.inputProcRefCon = 0; #if LOG_GRAPH_AND_MIXER_CHANGES DebugMessageN1("OALSource::Stop: set kAudioUnitProperty_SetRenderCallback (removing) called - OALSource = %ld\n", mSelfToken); #endif AudioUnitSetProperty ( mOwningDevice->GetMixerUnit(), kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, mCurrentPlayBus, &mPlayCallback, sizeof(mPlayCallback)); mOwningDevice->SetBusAsAvailable (mCurrentPlayBus); mCurrentPlayBus = kSourceNeedsBus; } #if USE_MUTEXES UNLOCK_PLAY_MUTEX #endif } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OSStatus OALSource::SourceNotificationProc ( void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { OALSource* THIS = (OALSource*)inRefCon; if (*inActionFlags & kAudioUnitRenderAction_PreRender) return THIS->DoPreRender(); if (*inActionFlags & kAudioUnitRenderAction_PostRender) return THIS->DoPostRender(); return (noErr); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #if USE_AU_TRACER static UInt32 tracerStart = 0xca3d0000; static UInt32 tracerEnd = 0xca3d0004; #include #include #endif OSStatus OALSource::SourceInputProc ( void *inRefCon, AudioUnitRenderActionFlags *inActionFlags, const AudioTimeStamp *inTimeStamp, UInt32 inBusNumber, UInt32 inNumberFrames, AudioBufferList *ioData) { OALSource* THIS = (OALSource*)inRefCon; if (THIS->mOutputSilence) *inActionFlags |= kAudioUnitRenderAction_OutputIsSilence; else *inActionFlags &= 0xEF; // the mask for the kAudioUnitRenderAction_OutputIsSilence bit #if USE_AU_TRACER syscall(180, tracerStart, inBusNumber, ioData->mNumberBuffers, 0, 0); #endif OSStatus result = noErr; if (THIS->mOwningDevice->IsPreferredMixerAvailable()) result = THIS->DoRender (ioData); // normal case else result = THIS->DoSRCRender (ioData); // pre 2.0 mixer case #if USE_AU_TRACER syscall(180, tracerEnd, inBusNumber, ioData->mNumberBuffers, 0, 0); #endif return (result); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OSStatus OALSource::DoPreRender () { if (mCurrentBufferIndex > 0) { #if LOG_BUFFER_QUEUEING DebugMessageN2("OALSource::DoPreRender Qs being updated, OALSource:mCurrentBufferIndex %ld:%ld\n", mSelfToken, mCurrentBufferIndex); #endif // This means there were buffers that could not be moved to the inactive list before, // so try and move them now bool gotTheLock; if (mBufferQueueMutex.Try(gotTheLock)) { for (UInt32 i = 0; i < mCurrentBufferIndex; i++) { // Get buffer #i from Active List BufferInfo *bufferInfo = mBufferQueueActive->Get(0); // Append it to Inactive List mBufferQueueInactive->AppendBuffer(bufferInfo->mBufferToken, bufferInfo->mBuffer, bufferInfo->mACToken); // Remove it from Active List mBufferQueueActive->RemoveQueueEntryByIndex(0); } mCurrentBufferIndex = 0; if (gotTheLock) mBufferQueueMutex.Unlock(); } } BufferInfo *bufferInfo = mBufferQueueActive->Get(mCurrentBufferIndex); if (bufferInfo == NULL) { mQueueIsProcessed = true; PlayFinished(); mState = AL_STOPPED; // stop rendering, there is no more data return -1; // there are no buffers } mRenderThreadID = (int) pthread_self(); // there is a need to accomodate the spec which allows a 'NONE' buffer to be queued. In our case, just move on to the // next buffer in the queue until a non-NONE buffer is found while (bufferInfo->mBufferToken == AL_NONE) { mCurrentBufferIndex++; bufferInfo->mProcessedState = kProcessed; bufferInfo = mBufferQueueActive->Get(mCurrentBufferIndex); if (bufferInfo == NULL) { mQueueIsProcessed = true; PlayFinished(); mState = AL_STOPPED; return -1; // there are no more real buffers } } ChangeChannelSettings(true); if (mShouldFade) return MuteCurrentPlayBus(); return (noErr); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OSStatus OALSource::ACComplexInputDataProc ( AudioConverterRef inAudioConverter, UInt32 *ioNumberDataPackets, AudioBufferList *ioData, AudioStreamPacketDescription **outDataPacketDescription, void* inUserData) { OSStatus err = noErr; OALSource* THIS = (OALSource*)inUserData; BufferInfo *bufferInfo = THIS->mBufferQueueActive->Get(THIS->mCurrentBufferIndex); UInt32 sourcePacketsLeft = 0; if (bufferInfo == NULL) { ioData->mBuffers[0].mData = NULL; // return nothing ioData->mBuffers[0].mDataByteSize = 0; // return nothing *ioNumberDataPackets = 0; return -1; } sourcePacketsLeft = (bufferInfo->mBuffer->mDataSize - bufferInfo->mOffset) / bufferInfo->mBuffer->mDataFormat.mBytesPerPacket; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // BUFFER EMPTY: If the buffer is empty now, decide on returning an error based on what gets played next in the queue if (sourcePacketsLeft == 0) { bufferInfo->mProcessedState = kProcessed; // this buffer is done bufferInfo->mOffset = 0; // will be ready for the next time bool gotTheLock; // Try and get the mutex so this buffer can be moved to the inactive list if (THIS->mBufferQueueMutex.Try(gotTheLock)) { // Get buffer #i from Active List BufferInfo *staleBufferInfo = THIS->mBufferQueueActive->Get(THIS->mCurrentBufferIndex); staleBufferInfo->mOffset = 0; THIS->mBufferQueueActive->SetBufferAsProcessed(THIS->mCurrentBufferIndex); // Append it to Inactive List THIS->mBufferQueueInactive->AppendBuffer(staleBufferInfo->mBufferToken, staleBufferInfo->mBuffer, staleBufferInfo->mACToken); // Remove it from Active List THIS->mBufferQueueActive->RemoveQueueEntryByIndex(THIS->mCurrentBufferIndex); if (gotTheLock) THIS->mBufferQueueMutex.Unlock(); } else THIS->mCurrentBufferIndex++; // see if there is a next buffer or if the queue is looping and should return to the start BufferInfo *nextBufferInfo = THIS->mBufferQueueActive->Get(THIS->mCurrentBufferIndex); if ((nextBufferInfo != NULL) || (THIS->mLooping == true)) { // either we will loop back to the beginning or will use a new buffer if (nextBufferInfo == NULL) { THIS->LoopToBeginning(); // see if the 1st buffer in the queue is the same format, return an error if so, so we get called again nextBufferInfo = THIS->mBufferQueueActive->Get(0); } if (nextBufferInfo->mBuffer->mDataFormat.mFormatID == bufferInfo->mBuffer->mDataFormat.mFormatID) err = OALSourceError_CallConverterAgain; if (nextBufferInfo->mBuffer->mDataFormat.mSampleRate != bufferInfo->mBuffer->mDataFormat.mSampleRate) THIS->mResetBusFormat = true; } else { // looping is false and there are no more buffers so we are really out of data // return what we have and no error, the AC should then be reset in the RenderProc // and what ever data is in the AC should get returned THIS->mBufferQueueActive->SetBufferAsProcessed(THIS->mCurrentBufferIndex); THIS->mQueueIsProcessed = true; // we are done now, the Q is dry } ioData->mBuffers[0].mData = NULL; // return nothing ioData->mBuffers[0].mDataByteSize = 0; // return nothing *ioNumberDataPackets = 0; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // BUFFER HAS DATA else { // return the entire request or the remainder of the buffer if (sourcePacketsLeft < *ioNumberDataPackets) *ioNumberDataPackets = sourcePacketsLeft; ioData->mBuffers[0].mData = bufferInfo->mBuffer->mData + bufferInfo->mOffset; // point to the data we are providing ioData->mBuffers[0].mDataByteSize = *ioNumberDataPackets * bufferInfo->mBuffer->mDataFormat.mBytesPerPacket; bufferInfo->mOffset += ioData->mBuffers[0].mDataByteSize; } return (err); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OSStatus OALSource::DoRender (AudioBufferList *ioData) { OSStatus err = noErr; UInt32 packetsRequestedFromRenderProc = ioData->mBuffers[0].mDataByteSize / sizeof(Float32); UInt32 packetsObtained = 0; UInt32 packetCount; AudioBufferList *tempBufferList = ioData; UInt32 dataByteSize = ioData->mBuffers[0].mDataByteSize; void *dataStarts[2]; // save the data ptrs to restore later for (UInt32 i = 0; i < tempBufferList->mNumberBuffers; i++) { dataStarts[i] = tempBufferList->mBuffers[i].mData; } // walk through as many buffers as needed to satisfy the request AudioConverterFillComplexBuffer will // get called each time the data format changes until enough packets have been obtained or the q is empty. while ((packetsObtained < packetsRequestedFromRenderProc) && (mQueueIsProcessed == false)) { BufferInfo *bufferInfo = mBufferQueueActive->Get(mCurrentBufferIndex); if (bufferInfo == NULL) { // just zero out the remainder of the buffer mQueueIsProcessed = true; goto Finished; } bufferInfo->mProcessedState = kInProgress; for (UInt32 i = 0; i < tempBufferList->mNumberBuffers; i++) { tempBufferList->mBuffers[i].mDataByteSize = dataByteSize - (packetsObtained * sizeof(Float32)); tempBufferList->mBuffers[i].mData = (Byte *) ioData->mBuffers[i].mData + (packetsObtained * sizeof(Float32)); } if (bufferInfo->mBuffer->mDataHasBeenConverted == false) { // CONVERT THE BUFFER DATA AudioConverterRef converter = mACMap->Get(bufferInfo->mACToken); packetCount = packetsRequestedFromRenderProc - packetsObtained; // if OALSourceError_CallConverterAgain is returned, there is nothing to do, just go around again and try and get the data err = AudioConverterFillComplexBuffer(converter, ACComplexInputDataProc, this, &packetCount, tempBufferList, NULL); packetsObtained += packetCount; if ((packetsObtained < packetsRequestedFromRenderProc) && (err == noErr)) { // we didn't get back what we asked for, but no error implies we have used up the data of this format // so reset this converter so it will be ready for the next time AudioConverterReset(converter); #if LOG_VERBOSE DebugMessageN1("OALSource::DoRender: Resetting AudioConverter - OALSource = %ld\n", mSelfToken); #endif } } else { // Data has already been converted to the mixer's format, so just do a copy (should be mono only) UInt32 bytesRemaining = bufferInfo->mBuffer->mDataSize - bufferInfo->mOffset; UInt32 framesRemaining = bytesRemaining / sizeof(Float32); UInt32 bytesToCopy = 0; UInt32 framesToCopy = packetsRequestedFromRenderProc - packetsObtained; if (framesRemaining < framesToCopy) framesToCopy = framesRemaining; bytesToCopy = framesToCopy * sizeof(Float32); memcpy(tempBufferList->mBuffers->mData, bufferInfo->mBuffer->mData + bufferInfo->mOffset, bytesToCopy); bufferInfo->mOffset += bytesToCopy; packetsObtained += framesToCopy; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // this block of code is the same as that found in the fill proc - // it is for determining what to do when a buffer runs out of data if (bufferInfo->mOffset == bufferInfo->mBuffer->mDataSize) { bool gotTheLock; // Try and get the mutex so this buffer can be moved to the inactive list if (mBufferQueueMutex.Try(gotTheLock)) { // Get buffer #i from Active List BufferInfo *staleBufferInfo = mBufferQueueActive->Get(mCurrentBufferIndex); staleBufferInfo->mOffset = 0; mBufferQueueActive->SetBufferAsProcessed(mCurrentBufferIndex); // Append it to Inactive List mBufferQueueInactive->AppendBuffer(staleBufferInfo->mBufferToken, staleBufferInfo->mBuffer, staleBufferInfo->mACToken); // Remove it from Active List mBufferQueueActive->RemoveQueueEntryByIndex(mCurrentBufferIndex); if (gotTheLock) mBufferQueueMutex.Unlock(); } else mCurrentBufferIndex++; // see if there is a next buffer or if the queue is looping and should return to the start BufferInfo *nextBufferInfo = mBufferQueueActive->Get(mCurrentBufferIndex); if ((nextBufferInfo != NULL) || (mLooping == true)) { if (mLooping == true) { LoopToBeginning(); nextBufferInfo = mBufferQueueActive->Get(mCurrentBufferIndex); } // if the format of the next buffer is the same, return an error so we get called again, // else, return what we have and no error, the AC should then be reset in the RenderProc if (nextBufferInfo->mBuffer->mDataFormat.mSampleRate != bufferInfo->mBuffer->mDataFormat.mSampleRate) { // if the next buffer is a different sample rate, the bus format will need to be changed mResetBusFormat = true; } } else { // looping is false and there are no more buffers so we are really out of data // return what we have and no error mQueueIsProcessed = true; // we are done now, the Q is dry // swap the list pointers now BufferQueue* tQ = mBufferQueueActive; mBufferQueueActive = mBufferQueueInactive; mBufferQueueInactive = tQ; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ } } } Finished: // if there wasn't enough data left, be sure to silence the end of the buffer if (packetsObtained < packetsRequestedFromRenderProc) { for (UInt32 i = 0; i < tempBufferList->mNumberBuffers; i++) { tempBufferList->mBuffers[i].mDataByteSize = dataByteSize - (packetsObtained * sizeof(Float32)); tempBufferList->mBuffers[i].mData = (Byte *) ioData->mBuffers[i].mData + (packetsObtained * sizeof(Float32)); memset(tempBufferList->mBuffers[i].mData, 0, tempBufferList->mBuffers[i].mDataByteSize); } } for (UInt32 i = 0; i < tempBufferList->mNumberBuffers; i++) { tempBufferList->mBuffers[i].mData = dataStarts[i]; tempBufferList->mBuffers[i].mDataByteSize = dataByteSize; } return (noErr); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OSStatus OALSource::DoPostRender () { if (mCurrentBufferIndex > 0) { #if LOG_BUFFER_QUEUEING DebugMessageN2("OALSource::DoPostRender, Qs being updated, OALSource:mCurrentBufferIndex %ld:%ld\n", mSelfToken, mCurrentBufferIndex); #endif // This means there were buffers that could not be moved to the inactive list before, // so try and move them now bool gotTheLock; if (mBufferQueueMutex.Try(gotTheLock)) { for (UInt32 i = 0; i < mCurrentBufferIndex; i++) { // Get buffer #i from Active List BufferInfo *bufferInfo = mBufferQueueActive->Get(0); // Append it to Inactive List mBufferQueueInactive->AppendBuffer(bufferInfo->mBufferToken, bufferInfo->mBuffer, bufferInfo->mACToken); // Remove it from Active List mBufferQueueActive->RemoveQueueEntryByIndex(0); } mCurrentBufferIndex = 0; if (gotTheLock) mBufferQueueMutex.Unlock(); } } if ((mQueueIsProcessed == false) && (mShouldFade == false)) return (noErr); #if USE_MUTEXES TRY_PLAY_MUTEX #endif if (mShouldFade) { UpdateBusGain (true); // it was turned down, so reset it now mShouldFade = false; } if (mQueueIsProcessed == true) { PlayFinished(); mState = AL_STOPPED; } #if USE_MUTEXES UNLOCK_PLAY_MUTEX #endif return noErr; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // CalculateDistanceAndAzimuth() support // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void aluCrossproduct(ALfloat *inVector1,ALfloat *inVector2,ALfloat *outVector) { outVector[0]=(inVector1[1]*inVector2[2]-inVector1[2]*inVector2[1]); outVector[1]=(inVector1[2]*inVector2[0]-inVector1[0]*inVector2[2]); outVector[2]=(inVector1[0]*inVector2[1]-inVector1[1]*inVector2[0]); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Float32 aluDotproduct(ALfloat *inVector1,ALfloat *inVector2) { return (inVector1[0]*inVector2[0]+inVector1[1]*inVector2[1]+inVector1[2]*inVector2[2]); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void aluNormalize(ALfloat *inVector) { ALfloat length,inverse_length; length=(ALfloat)sqrt(aluDotproduct(inVector,inVector)); if (length != 0) { inverse_length=(1.0f/length); inVector[0]*=inverse_length; inVector[1]*=inverse_length; inVector[2]*=inverse_length; } } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void aluMatrixVector(ALfloat *vector,ALfloat matrix[3][3]) { ALfloat result[3]; result[0]=vector[0]*matrix[0][0]+vector[1]*matrix[1][0]+vector[2]*matrix[2][0]; result[1]=vector[0]*matrix[0][1]+vector[1]*matrix[1][1]+vector[2]*matrix[2][1]; result[2]=vector[0]*matrix[0][2]+vector[1]*matrix[1][2]+vector[2]*matrix[2][2]; memcpy(vector,result,sizeof(result)); } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void OALSource::CalculateDistanceAndAzimuth(Float32 *outDistance, Float32 *outAzimuth, Float32 *outDopplerShift, bool isRenderThread) { Float32 ListenerOrientation[6], ListenerPosition[3], ListenerVelocity[3], Angle = 0.0, Distance = 2.0, Distance_squared = 4.0, front_back, SourceToListener[3], Look_Norm[3], U[3], tPosition[3], dopplerShift = 1.0; // default at No shift *outDopplerShift = dopplerShift; // initialize SourceToListener[0]=0; // initialize SourceToListener[1]=0; // initialize SourceToListener[2]=0; // initialize tPosition[0] = mPosition[0]; tPosition[1] = mPosition[1]; tPosition[2] = mPosition[2]; #if LOG_VERBOSE DebugMessageN3("CalculateDistanceAndAzimuth source position = %f2/%f2/%f2\n", mPosition[0], mPosition[1], mPosition[2]); #endif //Get listener properties mOwningContext->GetListenerPosition(&ListenerPosition[0], &ListenerPosition[1], &ListenerPosition[2]); mOwningContext->GetListenerVelocity(&ListenerVelocity[0], &ListenerVelocity[1], &ListenerVelocity[2]); mOwningContext->GetListenerOrientation(&ListenerOrientation[0], &ListenerOrientation[1], &ListenerOrientation[2], &ListenerOrientation[3], &ListenerOrientation[4], &ListenerOrientation[5]); #if LOG_VERBOSE DebugMessageN3("CalculateDistanceAndAzimuth listener position = %f2/%f2/%f2\n", ListenerPosition[0], ListenerPosition[1], ListenerPosition[2]); #endif // Get buffer properties BufferInfo *bufferInfo = mBufferQueueActive->Get(mCurrentBufferIndex); if (bufferInfo == NULL) { // Not sure if this should be the error case *outDistance = 0.0; *outAzimuth = 0.0; return; // there are no buffers } // Only apply 3D calculations for mono buffers if (bufferInfo->mBuffer->mDataFormat.NumberChannels() == 1) { //1. Translate Listener to origin (convert to head relative) if (mSourceRelative == AL_FALSE) { tPosition[0] -= ListenerPosition[0]; tPosition[1] -= ListenerPosition[1]; tPosition[2] -= ListenerPosition[2]; } //2. Align coordinate system axes aluCrossproduct(&ListenerOrientation[0],&ListenerOrientation[3],U); // Right-ear-vector aluNormalize(U); // Normalized Right-ear-vector Look_Norm[0] = ListenerOrientation[0]; Look_Norm[1] = ListenerOrientation[1]; Look_Norm[2] = ListenerOrientation[2]; aluNormalize(Look_Norm); //3. Calculate distance attenuation Distance_squared = aluDotproduct(tPosition,tPosition); Distance = sqrt(Distance_squared); Angle = 0.0f; //4. Determine Angle of source relative to listener if(Distance>0.0f){ SourceToListener[0]=tPosition[0]; SourceToListener[1]=tPosition[1]; SourceToListener[2]=tPosition[2]; aluNormalize(SourceToListener); Angle = 180.0 * acos (aluDotproduct(SourceToListener,U))/3.141592654f; //is the source infront of the listener or behind? front_back = aluDotproduct(SourceToListener,Look_Norm); if(front_back<0.0f) Angle = 360.0f - Angle; //translate from cartesian angle to 3d mixer angle if((Angle>=0.0f)&&(Angle<=270.0f)) Angle = 90.0f - Angle; else Angle = 450.0f - Angle; } //5. Calculate doppler Float32 DopplerFactor = mOwningContext->GetDopplerFactor(); if (DopplerFactor > 0.0) { Float32 SourceVelocity[3]; GetVelocity (SourceVelocity[0], SourceVelocity[1], SourceVelocity[2], isRenderThread); Float32 ProjectedSourceVelocity = aluDotproduct(SourceVelocity, SourceToListener); Float32 ProjectedListenerVelocity = aluDotproduct(ListenerVelocity, SourceToListener); if (fabsf(DopplerFactor) < 1.0e-6) { ProjectedSourceVelocity *= DopplerFactor; ProjectedListenerVelocity *= DopplerFactor; } #if LOG_DOPPLER DebugMessageN2("CalculateDistanceAndAzimuth: ListenerVelocity/SourceVelocity %f2/%f2", ProjectedListenerVelocity, ProjectedSourceVelocity); #endif if (fabsf(ProjectedSourceVelocity) > mOwningContext->GetDopplerVelocity()) { ProjectedSourceVelocity = ProjectedSourceVelocity > 0.0 ? mOwningContext->GetDopplerVelocity() : -mOwningContext->GetDopplerVelocity(); #if LOG_DOPPLER DebugMessageN1("CalculateDistanceAndAzimuth: Pinning ProjectedSourceVelocity at = %f2", -mOwningContext->GetDopplerVelocity()); #endif } if (fabsf(ProjectedListenerVelocity) > mOwningContext->GetDopplerVelocity()) { ProjectedListenerVelocity = ProjectedListenerVelocity > 0.0 ? mOwningContext->GetDopplerVelocity() : -mOwningContext->GetDopplerVelocity(); #if LOG_DOPPLER DebugMessageN1("CalculateDistanceAndAzimuth: Pinning ProjectedListenerVelocity at = %f2", mOwningContext->GetDopplerVelocity()); #endif } dopplerShift = ( (mOwningContext->GetDopplerVelocity() - ProjectedListenerVelocity) / (mOwningContext->GetDopplerVelocity() + ProjectedSourceVelocity) ); if (isnan(dopplerShift)) dopplerShift = 1.0; // limit the pitch shifting to 4 octaves up and 3 octaves down if (dopplerShift > 1.0) { dopplerShift *= DopplerFactor; if (dopplerShift > 16.0) dopplerShift = 16.0; } else { dopplerShift /= DopplerFactor; if(dopplerShift < 0.125) dopplerShift = 0.125; } #if LOG_DOPPLER DebugMessageN1("CalculateDistanceAndAzimuth: dopplerShift after scaling = %f2\n", dopplerShift); #endif *outDopplerShift = dopplerShift; } } else { Angle=0.0; Distance=0.0; } if (!mOwningDevice->IsPreferredMixerAvailable() && (mReferenceDistance > 1.0)) { // the pre 2.0 mixer does not have the DistanceParam property so to compensate, // set the DistanceAtten property correctly for refDist, maxDist, and rolloff, // and then scale our calculated distance to a reference distance of 1.0 before passing to the mixer Distance = Distance/mReferenceDistance; if (Distance > mMaxDistance/mReferenceDistance) Distance = mMaxDistance/mReferenceDistance; // clamp the distance to the max distance } *outDistance = Distance; *outAzimuth = Angle; } // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Support for Pre 2.0 3DMixer // // Pull the audio data by using DoRender(), and then Sample Rate Convert it to the mixer's // output sample rate so the 1.3 mixer doesn't have to do any SRC. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ OSStatus OALSource::DoSRCRender( AudioBufferList *ioData ) { BufferInfo *bufferInfo = mBufferQueueActive->Get(mCurrentBufferIndex); if (bufferInfo == NULL) return -1; double srcSampleRate = bufferInfo->mBuffer->mDataFormat.mSampleRate; double dstSampleRate = mOwningDevice->GetMixerRate(); double ratio = (srcSampleRate / dstSampleRate) * mPitch * mDopplerScaler; int nchannels = ioData->mNumberBuffers; if (ratio == 1.0) { // no SRC necessary so just call the normal render proc and let it fill out the buffers return (DoRender(ioData)); } // otherwise continue on to do dirty linear interpolation UInt32 inFramesToProcess = ioData->mBuffers[0].mDataByteSize / sizeof(Float32); float readIndex = mReadIndex; int readUntilIndex = (int) (2.0 + readIndex + inFramesToProcess * ratio ); int framesToPull = readUntilIndex - 2; if (framesToPull == 0) return -1; // set the buffer size so DoRender will get the correct amount of frames mTempSourceStorage->mBuffers[0].mDataByteSize = framesToPull * sizeof(UInt32); mTempSourceStorage->mBuffers[1].mDataByteSize = framesToPull * sizeof(UInt32); // if the size of the buffers are too small, reallocate them now if (mTempSourceStorageBufferSize < (framesToPull * sizeof(UInt32))) { if (mTempSourceStorage->mBuffers[0].mData != NULL) free(mTempSourceStorage->mBuffers[0].mData); if (mTempSourceStorage->mBuffers[1].mData != NULL) free(mTempSourceStorage->mBuffers[1].mData); mTempSourceStorageBufferSize = (framesToPull * sizeof(UInt32)); mTempSourceStorage->mBuffers[0].mData = malloc(mTempSourceStorageBufferSize); mTempSourceStorage->mBuffers[1].mData = malloc(mTempSourceStorageBufferSize); } // get input source audio mTempSourceStorage->mNumberBuffers = ioData->mNumberBuffers; for (UInt32 i = 0; i < mTempSourceStorage->mNumberBuffers; i++) { mTempSourceStorage->mBuffers[i].mDataByteSize = framesToPull * sizeof(UInt32); } OSStatus result = DoRender(mTempSourceStorage); if (result != noErr ) return result; // !!@ something bad happened (could return error code) float *pullL = (float *) mTempSourceStorage->mBuffers[0].mData; float *pullR = nchannels > 1 ? (float *) mTempSourceStorage->mBuffers[1].mData: NULL; // setup a small array of the previous two cached values, plus the first new input frame float tempL[4]; float tempR[4]; tempL[0] = mCachedInputL1; tempL[1] = mCachedInputL2; tempL[2] = pullL[0]; if (pullR) { tempR[0] = mCachedInputR1; tempR[1] = mCachedInputR2; tempR[2] = pullR[0]; } // in first loop start out getting source from this small array, then change sourceL/sourceR to point // to the buffers containing the new pulled input for the main loop float *sourceL = tempL; float *sourceR = tempR; if(!pullR) sourceR = NULL; // keep around for next time mCachedInputL1 = pullL[framesToPull - 2]; mCachedInputL2 = pullL[framesToPull - 1]; if(pullR) { mCachedInputR1 = pullR[framesToPull - 2]; mCachedInputR2 = pullR[framesToPull - 1]; } // quick-and-dirty linear interpolation int n = inFramesToProcess; float *destL = (float *) ioData->mBuffers[0].mData; float *destR = (float *) ioData->mBuffers[1].mData; if (!sourceR) { // mono input // generate output based on previous cached values while (readIndex < 2.0 && n > 0) { int iReadIndex = (int)readIndex; int iReadIndex2 = iReadIndex + 1; float frac = readIndex - float(iReadIndex); float s1 = sourceL[iReadIndex]; float s2 = sourceL[iReadIndex2]; float left = s1 + frac * (s2-s1); *destL++ = left; readIndex += ratio; n--; } // generate output based on new pulled input readIndex -= 2.0; sourceL = pullL; while (n--) { int iReadIndex = (int)readIndex; int iReadIndex2 = iReadIndex + 1; float frac = readIndex - float(iReadIndex); float s1 = sourceL[iReadIndex]; float s2 = sourceL[iReadIndex2]; float left = s1 + frac * (s2-s1); *destL++ = left; readIndex += ratio; } readIndex += 2.0; } else { // stereo input // generate output based on previous cached values while(readIndex < 2.0 && n > 0) { int iReadIndex = (int)readIndex; int iReadIndex2 = iReadIndex + 1; float frac = readIndex - float(iReadIndex); float s1 = sourceL[iReadIndex]; float s2 = sourceL[iReadIndex2]; float left = s1 + frac * (s2-s1); float s3 = sourceR[iReadIndex]; float s4 = sourceR[iReadIndex2]; float right = s3 + frac * (s4-s3); *destL++ = left; *destR++ = right; readIndex += ratio; n--; } // generate output based on new pulled input readIndex -= 2.0; sourceL = pullL; sourceR = pullR; while (n--) { int iReadIndex = (int)readIndex; int iReadIndex2 = iReadIndex + 1; float frac = readIndex - float(iReadIndex); float s1 = sourceL[iReadIndex]; float s2 = sourceL[iReadIndex2]; float left = s1 + frac * (s2-s1); float s3 = sourceR[iReadIndex]; float s4 = sourceR[iReadIndex2]; float right = s3 + frac * (s4-s3); *destL++ = left; *destR++ = right; readIndex += ratio; } readIndex += 2.0; } // normalize read index back to start of buffer for next time around... readIndex -= float(framesToPull); mReadIndex = readIndex; return noErr; }