mirror of
https://github.com/TomHarte/CLK.git
synced 2025-01-11 08:30:55 +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;
|
||||
@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<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;
|
||||
@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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user