mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-23 20:29:42 +00:00
Pulled away my crazy rationalisation of an audio queue into a circular buffer and decided just to trust the OS. This should reduce latency.
This commit is contained in:
parent
c284818af5
commit
de397799ed
@ -23,6 +23,6 @@
|
||||
@property (nonatomic, weak, nullable) id<CSAudioQueueDelegate> delegate;
|
||||
|
||||
+ (Float64)preferredSamplingRate;
|
||||
@property (nonatomic, readonly) NSUInteger bufferSize;
|
||||
@property (nonatomic, readonly) NSUInteger preferredBufferSize;
|
||||
|
||||
@end
|
||||
|
@ -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*50.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
|
||||
|
@ -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 / 2)
|
||||
}
|
||||
|
||||
self.bestEffortUpdater.clockRate = self.machine.clockRate
|
||||
|
Loading…
Reference in New Issue
Block a user