mirror of
https://github.com/TomHarte/CLK.git
synced 2024-10-17 17:25:18 +00:00
Merge pull request #56 from TomHarte/NoBuffer
Removes the locking attempt at enforcing a circular audio buffer on the Mac
This commit is contained in:
commit
95afdbe966
@ -14,15 +14,46 @@
|
|||||||
- (void)audioQueueDidCompleteBuffer:(nonnull CSAudioQueue *)audioQueue;
|
- (void)audioQueueDidCompleteBuffer:(nonnull CSAudioQueue *)audioQueue;
|
||||||
@end
|
@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
|
@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;
|
- (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;
|
- (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;
|
@property (nonatomic, readonly) Float64 samplingRate;
|
||||||
|
|
||||||
|
/// A delegate, if set, will receive notification upon the completion of each enqueue buffer.
|
||||||
@property (nonatomic, weak, nullable) id<CSAudioQueueDelegate> delegate;
|
@property (nonatomic, weak, nullable) id<CSAudioQueueDelegate> 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;
|
+ (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
|
@end
|
||||||
|
@ -10,77 +10,18 @@
|
|||||||
@import AudioToolbox;
|
@import AudioToolbox;
|
||||||
|
|
||||||
#define AudioQueueBufferMaxLength 8192
|
#define AudioQueueBufferMaxLength 8192
|
||||||
#define AudioQueueNumAudioBuffers 3
|
|
||||||
#define AudioQueueMaxStreamLength (AudioQueueBufferMaxLength*AudioQueueNumAudioBuffers)
|
|
||||||
|
|
||||||
enum {
|
|
||||||
AudioQueueCanProceed,
|
|
||||||
AudioQueueWait,
|
|
||||||
AudioQueueIsInvalidated
|
|
||||||
};
|
|
||||||
|
|
||||||
@implementation CSAudioQueue
|
@implementation CSAudioQueue
|
||||||
{
|
{
|
||||||
NSUInteger _bufferLength;
|
|
||||||
NSUInteger _streamLength;
|
|
||||||
|
|
||||||
AudioQueueRef _audioQueue;
|
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
|
||||||
#pragma mark AudioQueue callbacks and setup; for pushing audio out
|
|
||||||
|
|
||||||
- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
- (void)audioQueue:(AudioQueueRef)theAudioQueue didCallbackWithBuffer:(AudioQueueBufferRef)buffer
|
||||||
{
|
{
|
||||||
[self.delegate audioQueueDidCompleteBuffer:self];
|
[self.delegate audioQueueDidCompleteBuffer:self];
|
||||||
|
AudioQueueFreeBuffer(_audioQueue, buffer);
|
||||||
[_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];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void audioOutputCallback(
|
static void audioOutputCallback(
|
||||||
@ -91,19 +32,19 @@ static void audioOutputCallback(
|
|||||||
[(__bridge CSAudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer];
|
[(__bridge CSAudioQueue *)inUserData audioQueue:inAQ didCallbackWithBuffer:inBuffer];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - Standard object lifecycle
|
||||||
|
|
||||||
- (instancetype)initWithSamplingRate:(Float64)samplingRate
|
- (instancetype)initWithSamplingRate:(Float64)samplingRate
|
||||||
{
|
{
|
||||||
self = [super init];
|
self = [super init];
|
||||||
|
|
||||||
if(self)
|
if(self)
|
||||||
{
|
{
|
||||||
_writeLock = [[NSConditionLock alloc] initWithCondition:AudioQueueCanProceed];
|
|
||||||
_samplingRate = samplingRate;
|
_samplingRate = samplingRate;
|
||||||
|
|
||||||
// determine buffer sizes
|
// determine preferred buffer sizes
|
||||||
_bufferLength = AudioQueueBufferMaxLength;
|
_preferredBufferSize = AudioQueueBufferMaxLength;
|
||||||
while((Float64)_bufferLength*50.0 > samplingRate) _bufferLength >>= 1;
|
while((Float64)_preferredBufferSize*100.0 > samplingRate) _preferredBufferSize >>= 1;
|
||||||
_streamLength = _bufferLength * AudioQueueNumAudioBuffers;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Describe a mono 16bit stream of the requested sampling rate
|
Describe a mono 16bit stream of the requested sampling rate
|
||||||
@ -133,17 +74,6 @@ static void audioOutputCallback(
|
|||||||
0,
|
0,
|
||||||
&_audioQueue))
|
&_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);
|
AudioQueueStart(_audioQueue, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -158,51 +88,21 @@ static void audioOutputCallback(
|
|||||||
|
|
||||||
- (void)dealloc
|
- (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);
|
if(_audioQueue) AudioQueueDispose(_audioQueue, NO);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#pragma mark - Audio enqueuer
|
||||||
|
|
||||||
- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples
|
- (void)enqueueAudioBuffer:(const int16_t *)buffer numberOfSamples:(size_t)lengthInSamples
|
||||||
{
|
{
|
||||||
if([_writeLock tryLockWhenCondition:AudioQueueCanProceed])
|
AudioQueueBufferRef newBuffer;
|
||||||
{
|
size_t bufferBytes = lengthInSamples * sizeof(int16_t);
|
||||||
if((_audioStreamReadPosition + _streamLength) - _audioStreamWritePosition >= lengthInSamples)
|
|
||||||
{
|
|
||||||
size_t samplesBeforeOverflow = _streamLength - (_audioStreamWritePosition % _streamLength);
|
|
||||||
|
|
||||||
if(samplesBeforeOverflow < lengthInSamples)
|
AudioQueueAllocateBuffer(_audioQueue, (UInt32)bufferBytes, &newBuffer);
|
||||||
{
|
memcpy(newBuffer->mAudioData, buffer, bufferBytes);
|
||||||
memcpy(&_audioStream[_audioStreamWritePosition % _streamLength], buffer, samplesBeforeOverflow * sizeof(int16_t));
|
newBuffer->mAudioDataByteSize = (UInt32)bufferBytes;
|
||||||
memcpy(&_audioStream[0], &buffer[samplesBeforeOverflow], (lengthInSamples - samplesBeforeOverflow) * sizeof(int16_t));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
memcpy(&_audioStream[_audioStreamWritePosition % _streamLength], buffer, lengthInSamples * sizeof(int16_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
_audioStreamWritePosition += lengthInSamples;
|
AudioQueueEnqueueBuffer(_audioQueue, newBuffer, 0, NULL);
|
||||||
[_writeLock unlockWithCondition:[self writeLockCondition]];
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
[_writeLock unlockWithCondition:AudioQueueWait];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
- (NSInteger)writeLockCondition
|
|
||||||
{
|
|
||||||
return ((_audioStreamWritePosition - _audioStreamReadPosition) < (_streamLength - _bufferLength)) ? AudioQueueCanProceed : AudioQueueWait;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma mark - Sampling Rate getters
|
#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;
|
return AudioObjectGetPropertyData([self defaultOutputDevice], &address, sizeof(AudioObjectPropertyAddress), NULL, &size, &samplingRate) ? 0.0 : samplingRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
- (NSUInteger)bufferSize
|
|
||||||
{
|
|
||||||
return _bufferLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
@ -85,7 +85,7 @@ class MachineDocument:
|
|||||||
audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate))
|
audioQueue = CSAudioQueue(samplingRate: Float64(selectedSamplingRate))
|
||||||
audioQueue.delegate = self
|
audioQueue.delegate = self
|
||||||
self.machine.audioQueue = self.audioQueue
|
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
|
self.bestEffortUpdater.clockRate = self.machine.clockRate
|
||||||
|
Loading…
Reference in New Issue
Block a user