2006-01-20 12:34:02 -03:00
|
|
|
//
|
2006-04-02 11:44:08 -04:00
|
|
|
// OutputNode.m
|
2006-01-20 12:34:02 -03:00
|
|
|
// Cog
|
|
|
|
//
|
2006-09-04 14:46:18 -04:00
|
|
|
// Created by Vincent Spader on 8/2/05.
|
|
|
|
// Copyright 2005 Vincent Spader. All rights reserved.
|
2006-01-20 12:34:02 -03:00
|
|
|
//
|
|
|
|
|
|
|
|
#import "OutputNode.h"
|
2007-02-24 17:36:27 -03:00
|
|
|
#import "AudioPlayer.h"
|
|
|
|
#import "BufferChain.h"
|
2023-10-02 11:04:26 -03:00
|
|
|
#import "OutputCoreAudio.h"
|
2006-01-20 12:34:02 -03:00
|
|
|
|
2013-10-11 09:03:55 -03:00
|
|
|
#import "Logging.h"
|
|
|
|
|
2006-01-20 12:34:02 -03:00
|
|
|
@implementation OutputNode
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)setup {
|
2025-02-28 22:56:10 -03:00
|
|
|
[self setupWithInterval:NO];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setupWithInterval:(BOOL)resumeInterval {
|
|
|
|
if(!resumeInterval) {
|
|
|
|
amountPlayed = 0.0;
|
|
|
|
amountPlayedInterval = 0.0;
|
|
|
|
intervalReported = NO;
|
|
|
|
}
|
2022-02-07 02:49:27 -03:00
|
|
|
|
|
|
|
paused = YES;
|
|
|
|
started = NO;
|
2006-04-02 11:44:08 -04:00
|
|
|
|
2023-10-02 11:04:26 -03:00
|
|
|
output = [[OutputCoreAudio alloc] initWithController:self];
|
2022-02-07 02:49:27 -03:00
|
|
|
|
2006-01-20 12:34:02 -03:00
|
|
|
[output setup];
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)seek:(double)time {
|
|
|
|
// [output pause];
|
|
|
|
[self resetBuffer];
|
2007-05-26 18:13:11 -04:00
|
|
|
|
2022-01-13 04:17:07 -03:00
|
|
|
amountPlayed = time;
|
2006-04-02 16:03:12 -04:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)process {
|
|
|
|
paused = NO;
|
|
|
|
[output start];
|
2006-01-20 12:34:02 -03:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)pause {
|
|
|
|
paused = YES;
|
2006-01-29 11:57:48 -03:00
|
|
|
[output pause];
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)resume {
|
|
|
|
paused = NO;
|
2006-01-29 11:57:48 -03:00
|
|
|
[output resume];
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)incrementAmountPlayed:(double)seconds {
|
|
|
|
amountPlayed += seconds;
|
2022-06-19 02:00:08 -04:00
|
|
|
amountPlayedInterval += seconds;
|
|
|
|
if(!intervalReported && amountPlayedInterval >= 60.0) {
|
|
|
|
intervalReported = YES;
|
|
|
|
[controller reportPlayCount];
|
|
|
|
}
|
2022-01-15 03:46:41 -03:00
|
|
|
}
|
|
|
|
|
2025-02-12 09:41:11 -03:00
|
|
|
- (void)setAmountPlayed:(double)seconds {
|
|
|
|
double delta = seconds - amountPlayed;
|
|
|
|
if(delta > 0.0 && delta < 5.0) {
|
|
|
|
[self incrementAmountPlayed:delta];
|
2025-02-13 08:26:59 -03:00
|
|
|
} else if(delta) {
|
2025-02-12 09:41:11 -03:00
|
|
|
amountPlayed = seconds;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)resetAmountPlayed {
|
|
|
|
amountPlayed = 0;
|
2022-01-15 03:46:41 -03:00
|
|
|
}
|
|
|
|
|
2022-06-19 02:00:08 -04:00
|
|
|
- (void)resetAmountPlayedInterval {
|
|
|
|
amountPlayedInterval = 0;
|
|
|
|
intervalReported = NO;
|
|
|
|
}
|
|
|
|
|
2022-06-24 02:17:31 -04:00
|
|
|
- (BOOL)selectNextBuffer {
|
|
|
|
return [controller selectNextBuffer];
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)endOfInputPlayed {
|
2022-06-19 02:00:08 -04:00
|
|
|
if(!intervalReported) {
|
|
|
|
intervalReported = YES;
|
|
|
|
[controller reportPlayCount];
|
|
|
|
}
|
2022-02-07 02:49:27 -03:00
|
|
|
[controller endOfInputPlayed];
|
2022-06-19 02:00:08 -04:00
|
|
|
[self resetAmountPlayedInterval];
|
2022-01-15 03:46:41 -03:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (BOOL)chainQueueHasTracks {
|
|
|
|
return [controller chainQueueHasTracks];
|
2022-01-15 03:46:41 -03:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (double)secondsBuffered {
|
|
|
|
return [buffer listDuration];
|
2022-01-15 03:46:41 -03:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (AudioChunk *)readChunk:(size_t)amount {
|
|
|
|
@autoreleasepool {
|
2025-02-16 00:53:34 -03:00
|
|
|
Node *finalNode = [[controller bufferChain] finalNode];
|
|
|
|
[self setPreviousNode:finalNode];
|
|
|
|
|
|
|
|
if(finalNode) {
|
|
|
|
AudioChunk *ret = [super readChunk:amount];
|
|
|
|
|
2025-03-04 05:15:47 -03:00
|
|
|
if((!ret || ![ret frameCount]) && [previousNode endOfStream]) {
|
|
|
|
endOfStream = YES;
|
|
|
|
}
|
|
|
|
|
2025-02-16 00:53:34 -03:00
|
|
|
return ret;
|
|
|
|
} else {
|
|
|
|
return [[AudioChunk alloc] init];
|
|
|
|
}
|
2007-05-26 08:44:01 -04:00
|
|
|
}
|
2006-04-02 11:44:08 -04:00
|
|
|
}
|
2006-01-20 12:34:02 -03:00
|
|
|
|
2022-07-15 06:34:10 -04:00
|
|
|
- (BOOL)peekFormat:(nonnull AudioStreamBasicDescription *)format channelConfig:(nonnull uint32_t *)config {
|
|
|
|
@autoreleasepool {
|
|
|
|
[self setPreviousNode:[[controller bufferChain] finalNode]];
|
|
|
|
|
2025-03-04 05:15:47 -03:00
|
|
|
BOOL ret = [super peekFormat:format channelConfig:config];
|
|
|
|
if(!ret && [previousNode endOfStream]) {
|
|
|
|
endOfStream = YES;
|
|
|
|
}
|
|
|
|
return ret;
|
2022-07-15 06:34:10 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (double)amountPlayed {
|
|
|
|
return amountPlayed;
|
2006-01-20 12:34:02 -03:00
|
|
|
}
|
|
|
|
|
2022-06-19 02:00:08 -04:00
|
|
|
- (double)amountPlayedInterval {
|
|
|
|
return amountPlayedInterval;
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (AudioStreamBasicDescription)format {
|
2006-01-20 12:34:02 -03:00
|
|
|
return format;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:56:05 -03:00
|
|
|
- (uint32_t)config {
|
|
|
|
return config;
|
|
|
|
}
|
|
|
|
|
2025-02-13 19:56:18 -03:00
|
|
|
- (AudioStreamBasicDescription)deviceFormat {
|
|
|
|
return [output deviceFormat];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (uint32_t)deviceChannelConfig {
|
|
|
|
return [output deviceChannelConfig];
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:56:05 -03:00
|
|
|
- (void)setFormat:(AudioStreamBasicDescription *)f channelConfig:(uint32_t)channelConfig {
|
2025-02-15 06:33:06 -03:00
|
|
|
if(!shouldContinue) return;
|
|
|
|
|
2006-01-20 12:34:02 -03:00
|
|
|
format = *f;
|
2022-02-07 05:56:05 -03:00
|
|
|
config = channelConfig;
|
2022-02-07 02:49:27 -03:00
|
|
|
// Calculate a ratio and add to double(seconds) instead, as format may change
|
|
|
|
// double oldSampleRatio = sampleRatio;
|
2025-02-24 00:58:56 -03:00
|
|
|
AudioPlayer *audioPlayer = controller;
|
|
|
|
BufferChain *bufferChain = [audioPlayer bufferChain];
|
2022-02-07 02:49:27 -03:00
|
|
|
if(bufferChain) {
|
|
|
|
ConverterNode *converter = [bufferChain converter];
|
2025-02-14 23:50:50 -03:00
|
|
|
DSPDownmixNode *downmix = [bufferChain downmix];
|
|
|
|
AudioStreamBasicDescription outputFormat;
|
|
|
|
uint32_t outputChannelConfig;
|
|
|
|
BOOL formatChanged = NO;
|
2022-02-07 02:49:27 -03:00
|
|
|
if(converter) {
|
2025-02-14 23:50:50 -03:00
|
|
|
AudioStreamBasicDescription converterFormat = [converter nodeFormat];
|
|
|
|
if(memcmp(&converterFormat, &format, sizeof(converterFormat)) != 0) {
|
|
|
|
formatChanged = YES;
|
|
|
|
}
|
2022-02-07 02:49:27 -03:00
|
|
|
}
|
2025-02-14 23:50:50 -03:00
|
|
|
if(downmix && output && !formatChanged) {
|
|
|
|
outputFormat = [output deviceFormat];
|
|
|
|
outputChannelConfig = [output deviceChannelConfig];
|
|
|
|
AudioStreamBasicDescription currentOutputFormat = [downmix nodeFormat];
|
|
|
|
uint32_t currentOutputChannelConfig = [downmix nodeChannelConfig];
|
|
|
|
if(memcmp(¤tOutputFormat, &outputFormat, sizeof(currentOutputFormat)) != 0 ||
|
|
|
|
currentOutputChannelConfig != outputChannelConfig) {
|
|
|
|
formatChanged = YES;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(formatChanged) {
|
|
|
|
InputNode *inputNode = [bufferChain inputNode];
|
|
|
|
if(converter) {
|
|
|
|
[converter setOutputFormat:format];
|
|
|
|
}
|
|
|
|
if(downmix && output) {
|
|
|
|
[downmix setOutputFormat:[output deviceFormat] withChannelConfig:[output deviceChannelConfig]];
|
|
|
|
}
|
|
|
|
if(inputNode) {
|
|
|
|
AudioStreamBasicDescription inputFormat = [inputNode nodeFormat];
|
|
|
|
[inputNode seek:(long)(amountPlayed * inputFormat.mSampleRate)];
|
|
|
|
}
|
2025-02-13 19:56:18 -03:00
|
|
|
}
|
2022-02-07 02:49:27 -03:00
|
|
|
}
|
2006-01-20 12:34:02 -03:00
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)close {
|
2006-04-02 11:44:08 -04:00
|
|
|
[output stop];
|
2022-02-07 02:49:27 -03:00
|
|
|
output = nil;
|
2006-04-02 11:44:08 -04:00
|
|
|
}
|
|
|
|
|
2025-02-14 23:50:50 -03:00
|
|
|
- (double)volume {
|
|
|
|
return [output volume];
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)setVolume:(double)v {
|
2006-01-20 12:34:02 -03:00
|
|
|
[output setVolume:v];
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)setShouldContinue:(BOOL)s {
|
2006-01-20 12:34:02 -03:00
|
|
|
[super setShouldContinue:s];
|
2022-02-07 02:49:27 -03:00
|
|
|
|
|
|
|
// if (s == NO)
|
|
|
|
// [output stop];
|
2006-01-20 12:34:02 -03:00
|
|
|
}
|
2013-10-21 02:04:09 -03:00
|
|
|
|
2022-12-10 02:18:47 -03:00
|
|
|
- (void)setShouldPlayOutBuffer:(BOOL)s {
|
|
|
|
[output setShouldPlayOutBuffer:s];
|
|
|
|
}
|
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (BOOL)isPaused {
|
|
|
|
return paused;
|
2013-10-21 02:04:09 -03:00
|
|
|
}
|
2022-01-16 12:32:47 -03:00
|
|
|
|
2022-02-07 02:49:27 -03:00
|
|
|
- (void)sustainHDCD {
|
|
|
|
[output sustainHDCD];
|
2022-01-19 07:08:57 -03:00
|
|
|
}
|
2022-01-22 19:37:37 -03:00
|
|
|
|
2022-02-08 03:44:56 -03:00
|
|
|
- (void)restartPlaybackAtCurrentPosition {
|
|
|
|
[controller restartPlaybackAtCurrentPosition];
|
|
|
|
}
|
|
|
|
|
2022-06-25 09:42:56 -04:00
|
|
|
- (double)latency {
|
|
|
|
return [output latency];
|
|
|
|
}
|
|
|
|
|
2025-02-13 06:12:53 -03:00
|
|
|
- (double)getTotalLatency {
|
|
|
|
return [[controller bufferChain] secondsBuffered] + [output latency];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (double)getPostVisLatency {
|
|
|
|
return [[controller bufferChain] getPostVisLatency] + [output latency];
|
|
|
|
}
|
|
|
|
|
2006-01-20 12:34:02 -03:00
|
|
|
@end
|