Cog/Audio/Chain/OutputNode.m
Christopher Snowhill 637ea4efe1 Core Audio output: Rewrote major portions
After all this rewriting, down or upmixing the audio is now handled with
the lowest latency possible, meaning that toggling the HRIR option now
takes effect immediately.

Signed-off-by: Christopher Snowhill <kode54@gmail.com>
2022-02-05 03:45:02 -08:00

209 lines
4 KiB
Objective-C

//
// OutputNode.m
// Cog
//
// Created by Vincent Spader on 8/2/05.
// Copyright 2005 Vincent Spader. All rights reserved.
//
#import "OutputNode.h"
#import "OutputCoreAudio.h"
#import "AudioPlayer.h"
#import "BufferChain.h"
#import "Logging.h"
@implementation OutputNode
- (void)setup
{
amountPlayed = 0.0;
sampleRatio = 0.0;
paused = YES;
started = NO;
formatSetup = NO;
formatChanged = NO;
output = [[OutputCoreAudio alloc] initWithController:self];
[output setup];
}
- (void)seek:(double)time
{
// [output pause];
[self resetBuffer];
amountPlayed = time;
}
- (void)process
{
paused = NO;
[output start];
}
- (void)pause
{
paused = YES;
[output pause];
}
- (void)resume
{
paused = NO;
[output resume];
}
- (void)incrementAmountPlayed:(long)count
{
amountPlayed += (double)count * sampleRatio;
}
- (void)resetAmountPlayed
{
amountPlayed = 0;
}
- (void)endOfInputPlayed
{
[controller endOfInputPlayed];
}
- (BOOL)chainQueueHasTracks
{
return [controller chainQueueHasTracks];
}
- (double)secondsBuffered
{
return (double)([buffer bufferedLength]) / (format.mSampleRate * format.mBytesPerPacket);
}
- (int)readData:(void *)ptr amount:(int)amount
{
@autoreleasepool {
int n;
[self setPreviousNode:[[controller bufferChain] finalNode]];
n = [super readData:ptr amount:amount];
/* if (n == 0) {
DLog(@"Output Buffer dry!");
}
*/
return n;
}
}
- (double)amountPlayed
{
return amountPlayed;
}
- (AudioStreamBasicDescription) format
{
return format;
}
- (void)setFormat:(AudioStreamBasicDescription *)f
{
format = *f;
// Calculate a ratio and add to double(seconds) instead, as format may change
// double oldSampleRatio = sampleRatio;
sampleRatio = 1.0 / (format.mSampleRate * format.mBytesPerPacket);
BufferChain *bufferChain = [controller bufferChain];
if (bufferChain)
{
ConverterNode *converter = [bufferChain converter];
if (converter)
{
// This clears the resampler buffer, but not the input buffer
// We also have to jump the play position ahead accounting for
// the data we are flushing
#if 0
// We no longer need to do this, because outputchanged converter
// now uses the RefillNode to slap the previous samples into
// itself
if (oldSampleRatio)
amountPlayed += oldSampleRatio * [[converter buffer] bufferedLength];
#endif
AudioStreamBasicDescription inf = [bufferChain inputFormat];
format.mChannelsPerFrame = inf.mChannelsPerFrame;
format.mBytesPerFrame = ((format.mBitsPerChannel + 7) / 8) * format.mChannelsPerFrame;
format.mBytesPerPacket = format.mBytesPerFrame * format.mFramesPerPacket;
sampleRatio = 1.0 / (format.mSampleRate * format.mBytesPerPacket);
[converter setOutputFormat:format];
[converter inputFormatDidChange:[bufferChain inputFormat]];
}
}
}
- (void)close
{
[output stop];
output = nil;
}
- (void)setVolume:(double) v
{
[output setVolume:v];
}
- (void)setShouldContinue:(BOOL)s
{
[super setShouldContinue:s];
// if (s == NO)
// [output stop];
}
- (BOOL)isPaused
{
return paused;
}
- (void)beginEqualizer:(AudioUnit)eq
{
[controller beginEqualizer:eq];
}
- (void)refreshEqualizer:(AudioUnit)eq
{
[controller refreshEqualizer:eq];
}
- (void)endEqualizer:(AudioUnit)eq
{
[controller endEqualizer:eq];
}
- (void)sustainHDCD
{
[output sustainHDCD];
}
- (BOOL)formatChanged
{
[self setPreviousNode:[[controller bufferChain] finalNode]];
AudioStreamBasicDescription inf = [[self previousNode] nodeFormat];
if (!formatSetup || memcmp(&nodeFormat, &inf, sizeof(nodeFormat)) != 0) {
nodeFormat = inf;
formatSetup = YES;
formatChanged = YES;
}
BOOL copyFormatChanged = formatChanged;
formatChanged = NO;
return copyFormatChanged;
}
@end