diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h index 2f43b2d9a..e17fb372f 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.h @@ -14,15 +14,46 @@ - (void)audioQueueDidCompleteBuffer:(nonnull CSAudioQueue *)audioQueue; @end +/*! + CSAudioQueue provides an audio queue to which packets of arbitrary size may be appended; + it can notify a delegate each time a buffer is completed and offer advice as to the preferred + output sampling rate and a manageable buffer size for this machine. +*/ @interface CSAudioQueue : NSObject +/*! + Creates an instance of CSAudioQueue. + + @param samplingRate The output audio rate. + + @returns An instance of CSAudioQueue if successful; @c nil otherwise. +*/ - (nonnull instancetype)initWithSamplingRate:(Float64)samplingRate; + +/*! + Enqueues a buffer for playback. + + @param buffer A pointer to the data that comprises the buffer. + @param lengthInSamples The length of the buffer, in samples. +*/ - (void)enqueueAudioBuffer:(nonnull const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples; +/// @returns The sampling rate at which this queue is playing audio. @property (nonatomic, readonly) Float64 samplingRate; + +/// A delegate, if set, will receive notification upon the completion of each enqueue buffer. @property (nonatomic, weak, nullable) id delegate; +/*! + @returns The ideal output sampling rate for this computer; likely to be 44.1Khz or + 48Khz or 96Khz or one of the other comon numbers but not guaranteed to be. +*/ + (Float64)preferredSamplingRate; -@property (nonatomic, readonly) NSUInteger bufferSize; + +/*! + @returns A selected preferred buffer size (in samples). If an owner cannot otherwise + decide in what size to enqueue audio, this is a helpful suggestion. +*/ +@property (nonatomic, readonly) NSUInteger preferredBufferSize; @end diff --git a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m index 47507b332..d7fd0b598 100644 --- a/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m +++ b/OSBindings/Mac/Clock Signal/Audio/CSAudioQueue.m @@ -10,77 +10,18 @@ @import AudioToolbox; #define AudioQueueBufferMaxLength 8192 -#define AudioQueueNumAudioBuffers 3 -#define AudioQueueMaxStreamLength (AudioQueueBufferMaxLength*AudioQueueNumAudioBuffers) - -enum { - AudioQueueCanProceed, - AudioQueueWait, - AudioQueueIsInvalidated -}; @implementation CSAudioQueue { - NSUInteger _bufferLength; - NSUInteger _streamLength; - AudioQueueRef _audioQueue; - AudioQueueBufferRef _audioBuffers[AudioQueueNumAudioBuffers]; - - unsigned int _audioStreamReadPosition, _audioStreamWritePosition; - int16_t _audioStream[AudioQueueMaxStreamLength]; - - NSConditionLock *_writeLock; - BOOL _isInvalidated; - int _dequeuedCount; } -#pragma mark - -#pragma mark AudioQueue callbacks and setup; for pushing audio out +#pragma mark - AudioQueue callbacks - (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer { [self.delegate audioQueueDidCompleteBuffer:self]; - - [_writeLock lock]; - - const unsigned int writeLead = _audioStreamWritePosition - _audioStreamReadPosition; - const size_t audioDataSampleSize = buffer->mAudioDataByteSize / sizeof(int16_t); - - // TODO: if write lead is too great, skip some audio - if(writeLead >= audioDataSampleSize) - { - size_t samplesBeforeOverflow = _streamLength - (_audioStreamReadPosition % _streamLength); - if(audioDataSampleSize <= samplesBeforeOverflow) - { - memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % _streamLength], buffer->mAudioDataByteSize); - } - else - { - const size_t bytesRemaining = samplesBeforeOverflow * sizeof(int16_t); - memcpy(buffer->mAudioData, &_audioStream[_audioStreamReadPosition % _streamLength], bytesRemaining); - memcpy(buffer->mAudioData, &_audioStream[0], buffer->mAudioDataByteSize - bytesRemaining); - } - _audioStreamReadPosition += audioDataSampleSize; - } - else - { - memset(buffer->mAudioData, 0, buffer->mAudioDataByteSize); - } - - if(!_isInvalidated) - { - [_writeLock unlockWithCondition:AudioQueueCanProceed]; - AudioQueueEnqueueBuffer(theAudioQueue, buffer, 0, NULL); - } - else - { - _dequeuedCount++; - if(_dequeuedCount == AudioQueueNumAudioBuffers) - [_writeLock unlockWithCondition:AudioQueueIsInvalidated]; - else - [_writeLock unlockWithCondition:AudioQueueCanProceed]; - } + AudioQueueFreeBuffer(_audioQueue, buffer); } static void audioOutputCallback( @@ -91,19 +32,19 @@ static void audioOutputCallback( [(__bridge CSAudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer]; } +#pragma mark - Standard object lifecycle + - (instancetype)initWithSamplingRate:(Float64)samplingRate { self = [super init]; if(self) { - _writeLock = [[NSConditionLock alloc] initWithCondition:AudioQueueCanProceed]; _samplingRate = samplingRate; - // determine buffer sizes - _bufferLength = AudioQueueBufferMaxLength; - while((Float64)_bufferLength*50.0 > samplingRate) _bufferLength >>= 1; - _streamLength = _bufferLength * AudioQueueNumAudioBuffers; + // determine preferred buffer sizes + _preferredBufferSize = AudioQueueBufferMaxLength; + while((Float64)_preferredBufferSize*100.0 > samplingRate) _preferredBufferSize >>= 1; /* Describe a mono 16bit stream of the requested sampling rate @@ -133,17 +74,6 @@ static void audioOutputCallback( 0, &_audioQueue)) { - UInt32 bufferBytes = (UInt32)(_bufferLength * sizeof(int16_t)); - - int c = AudioQueueNumAudioBuffers; - while(c--) - { - AudioQueueAllocateBuffer(_audioQueue, bufferBytes, &_audioBuffers[c]); - memset(_audioBuffers[c]->mAudioData, 0, bufferBytes); - _audioBuffers[c]->mAudioDataByteSize = bufferBytes; - AudioQueueEnqueueBuffer(_audioQueue, _audioBuffers[c], 0, NULL); - } - AudioQueueStart(_audioQueue, NULL); } } @@ -158,51 +88,21 @@ static void audioOutputCallback( - (void)dealloc { - [_writeLock lock]; - _isInvalidated = YES; - [_writeLock unlock]; - - [_writeLock lockWhenCondition:AudioQueueIsInvalidated]; - [_writeLock unlock]; - - int c = AudioQueueNumAudioBuffers; - while(c--) - AudioQueueFreeBuffer(_audioQueue, _audioBuffers[c]); - if(_audioQueue) AudioQueueDispose(_audioQueue, NO); } +#pragma mark - Audio enqueuer + - (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples { - if([_writeLock tryLockWhenCondition:AudioQueueCanProceed]) - { - if((_audioStreamReadPosition + _streamLength) - _audioStreamWritePosition >= lengthInSamples) - { - size_t samplesBeforeOverflow = _streamLength - (_audioStreamWritePosition % _streamLength); + AudioQueueBufferRef newBuffer; + size_t bufferBytes = lengthInSamples * sizeof(int16_t); - if(samplesBeforeOverflow < lengthInSamples) - { - memcpy(&_audioStream[_audioStreamWritePosition % _streamLength], buffer, samplesBeforeOverflow * sizeof(int16_t)); - memcpy(&_audioStream[0], &buffer[samplesBeforeOverflow], (lengthInSamples - samplesBeforeOverflow) * sizeof(int16_t)); - } - else - { - memcpy(&_audioStream[_audioStreamWritePosition % _streamLength], buffer, lengthInSamples * sizeof(int16_t)); - } + AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes, &newBuffer); + memcpy(newBuffer->mAudioData, buffer, bufferBytes); + newBuffer->mAudioDataByteSize = (UInt32)bufferBytes; - _audioStreamWritePosition += lengthInSamples; - [_writeLock unlockWithCondition:[self writeLockCondition]]; - } - else - { - [_writeLock unlockWithCondition:AudioQueueWait]; - } - } -} - -- (NSInteger)writeLockCondition -{ - return ((_audioStreamWritePosition - _audioStreamReadPosition) < (_streamLength - _bufferLength)) ? AudioQueueCanProceed : AudioQueueWait; + AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL); } #pragma mark - Sampling Rate getters @@ -231,9 +131,4 @@ static void audioOutputCallback( return AudioObjectGetPropertyData([self defaultOutputDevice], &address, sizeof(AudioObjectPropertyAddress), NULL, &size, &samplingRate) ? 0.0 : samplingRate; } -- (NSUInteger)bufferSize -{ - return _bufferLength; -} - @end diff --git a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift index 5ef302a53..d709fef5b 100644 --- a/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift +++ b/OSBindings/Mac/Clock Signal/Documents/MachineDocument.swift @@ -85,7 +85,7 @@ class MachineDocument: audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate)) audioQueue.delegate = self self.machine.audioQueue = self.audioQueue - self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.bufferSize / 2) + self.machine.setAudioSamplingRate(selectedSamplingRate, bufferSize:audioQueue.preferredBufferSize) } self.bestEffortUpdater.clockRate = self.machine.clockRate