2006-01-20 15:34:02 +00:00
|
|
|
//
|
2006-04-02 15:44:08 +00:00
|
|
|
// Node.m
|
2006-01-20 15:34:02 +00:00
|
|
|
// CogNew
|
|
|
|
//
|
2006-09-04 18:46:18 +00:00
|
|
|
// Created by Vincent Spader on 1/4/06.
|
|
|
|
// Copyright 2006 Vincent Spader. All rights reserved.
|
2006-01-20 15:34:02 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
#import "Node.h"
|
|
|
|
|
2021-01-27 02:12:07 +00:00
|
|
|
#import "BufferChain.h"
|
2022-02-07 05:49:27 +00:00
|
|
|
#import "Logging.h"
|
2013-10-11 12:03:55 +00:00
|
|
|
|
2022-06-09 07:27:55 +00:00
|
|
|
#import "OutputCoreAudio.h"
|
|
|
|
|
2022-06-09 08:38:30 +00:00
|
|
|
#import <mach/mach_time.h>
|
|
|
|
|
|
|
|
// This workgroup attribute isn't currently used. Set it to NULL.
|
|
|
|
static os_workgroup_attr_t _Nullable attr = nil;
|
|
|
|
|
|
|
|
// One nanosecond in seconds.
|
|
|
|
static const double kOneNanosecond = 1.0e9;
|
|
|
|
|
|
|
|
// The I/O interval time in seconds.
|
|
|
|
static const double kIOIntervalTime = 0.020;
|
|
|
|
|
|
|
|
// The clock identifier that specifies interval timestamps.
|
|
|
|
static const os_clockid_t clockId = OS_CLOCK_MACH_ABSOLUTE_TIME;
|
|
|
|
|
2006-01-20 15:34:02 +00:00
|
|
|
@implementation Node
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (id)initWithController:(id)c previous:(id)p {
|
2006-01-20 15:34:02 +00:00
|
|
|
self = [super init];
|
2022-02-07 05:49:27 +00:00
|
|
|
if(self) {
|
|
|
|
buffer = [[ChunkList alloc] initWithMaximumDuration:3.0];
|
|
|
|
semaphore = [[Semaphore alloc] init];
|
|
|
|
|
|
|
|
accessLock = [[NSRecursiveLock alloc] init];
|
|
|
|
|
2007-03-19 00:19:47 +00:00
|
|
|
initialBufferFilled = NO;
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2008-02-16 02:52:49 +00:00
|
|
|
controller = c;
|
2006-04-02 15:44:08 +00:00
|
|
|
endOfStream = NO;
|
|
|
|
shouldContinue = YES;
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2022-02-07 08:56:05 +00:00
|
|
|
nodeChannelConfig = 0;
|
2022-02-07 05:49:27 +00:00
|
|
|
nodeLossless = NO;
|
2008-02-16 02:46:19 +00:00
|
|
|
|
2022-06-09 08:38:30 +00:00
|
|
|
if(@available(macOS 11, *)) {
|
|
|
|
workgroup = AudioWorkIntervalCreate("Node Work Interval", clockId, attr);
|
|
|
|
|
|
|
|
// Get the mach time info.
|
|
|
|
struct mach_timebase_info timeBaseInfo;
|
|
|
|
mach_timebase_info(&timeBaseInfo);
|
|
|
|
|
|
|
|
// The frequency of the clock is: (timeBaseInfo.denom / timeBaseInfo.numer) * kOneNanosecond
|
|
|
|
const double nanoSecFrequency = (double)(timeBaseInfo.denom) / (double)(timeBaseInfo.numer);
|
|
|
|
const double frequency = nanoSecFrequency * kOneNanosecond;
|
|
|
|
|
|
|
|
// Convert the interval time in seconds to mach time length.
|
|
|
|
intervalMachLength = (int64_t)(kIOIntervalTime * frequency);
|
|
|
|
}
|
|
|
|
|
2008-02-16 02:46:19 +00:00
|
|
|
[self setPreviousNode:p];
|
2006-01-20 15:34:02 +00:00
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
|
2006-01-20 15:34:02 +00:00
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (AudioStreamBasicDescription)nodeFormat {
|
|
|
|
return nodeFormat;
|
|
|
|
}
|
|
|
|
|
2022-02-07 08:56:05 +00:00
|
|
|
- (uint32_t)nodeChannelConfig {
|
|
|
|
return nodeChannelConfig;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (BOOL)nodeLossless {
|
|
|
|
return nodeLossless;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)writeData:(const void *)ptr amount:(size_t)amount {
|
|
|
|
[accessLock lock];
|
|
|
|
|
|
|
|
AudioChunk *chunk = [[AudioChunk alloc] init];
|
|
|
|
[chunk setFormat:nodeFormat];
|
2022-02-07 08:56:05 +00:00
|
|
|
if(nodeChannelConfig) {
|
|
|
|
[chunk setChannelConfig:nodeChannelConfig];
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
[chunk setLossless:nodeLossless];
|
|
|
|
[chunk assignSamples:ptr frameCount:amount / nodeFormat.mBytesPerPacket];
|
|
|
|
|
|
|
|
const double chunkDuration = [chunk duration];
|
|
|
|
double durationLeft = [buffer maxDuration] - [buffer listDuration];
|
|
|
|
|
|
|
|
while(shouldContinue == YES && chunkDuration > durationLeft) {
|
|
|
|
if(durationLeft < chunkDuration) {
|
|
|
|
if(initialBufferFilled == NO) {
|
|
|
|
initialBufferFilled = YES;
|
|
|
|
if([controller respondsToSelector:@selector(initialBufferFilled:)])
|
|
|
|
[controller performSelector:@selector(initialBufferFilled:) withObject:self];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(durationLeft < chunkDuration || shouldReset) {
|
|
|
|
[accessLock unlock];
|
|
|
|
[semaphore wait];
|
|
|
|
[accessLock lock];
|
|
|
|
}
|
|
|
|
|
|
|
|
durationLeft = [buffer maxDuration] - [buffer listDuration];
|
|
|
|
}
|
|
|
|
|
|
|
|
[buffer addChunk:chunk];
|
|
|
|
|
|
|
|
[accessLock unlock];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should be overwriten by subclass.
|
|
|
|
- (void)process {
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)threadEntry:(id)arg {
|
|
|
|
@autoreleasepool {
|
2022-06-09 07:27:55 +00:00
|
|
|
[self followWorkgroup];
|
2022-02-07 05:49:27 +00:00
|
|
|
[self process];
|
2022-06-09 07:27:55 +00:00
|
|
|
[self leaveWorkgroup];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)followWorkgroup {
|
|
|
|
if(@available(macOS 11, *)) {
|
2022-06-09 08:38:30 +00:00
|
|
|
if(!wg) {
|
|
|
|
wg = workgroup;
|
2022-06-09 07:27:55 +00:00
|
|
|
if(wg) {
|
|
|
|
int result = os_workgroup_join(wg, &wgToken);
|
|
|
|
if(result == 0) return;
|
|
|
|
if(result == EALREADY) {
|
|
|
|
DLog(@"Thread already in workgroup");
|
|
|
|
} else {
|
|
|
|
DLog(@"Cannot join workgroup, error %d", result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)leaveWorkgroup {
|
|
|
|
if(@available(macOS 11, *)) {
|
|
|
|
if(wg) {
|
|
|
|
os_workgroup_leave(wg, &wgToken);
|
|
|
|
wg = nil;
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-06-09 08:38:30 +00:00
|
|
|
- (void)startWorkslice {
|
|
|
|
if(@available(macOS 11, *)) {
|
|
|
|
if(wg) {
|
|
|
|
const uint64_t currentTime = mach_absolute_time();
|
|
|
|
const uint64_t deadline = currentTime + intervalMachLength;
|
|
|
|
int result = os_workgroup_interval_start(wg, currentTime, deadline, nil);
|
|
|
|
if(result != 0) {
|
|
|
|
DLog(@"Deadline error = %d", result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)endWorkslice {
|
|
|
|
if(@available(macOS 11, *)) {
|
|
|
|
if(wg) {
|
|
|
|
int result = os_workgroup_interval_finish(wg, nil);
|
|
|
|
if(result != 0) {
|
|
|
|
DLog(@"Deadline end error = %d", result);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-08 03:18:45 +00:00
|
|
|
- (BOOL)peekFormat:(nonnull AudioStreamBasicDescription *)format channelConfig:(nonnull uint32_t *)config {
|
|
|
|
[accessLock lock];
|
|
|
|
|
|
|
|
BOOL ret = [[previousNode buffer] peekFormat:format channelConfig:config];
|
|
|
|
|
|
|
|
[accessLock unlock];
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (AudioChunk *)readChunk:(size_t)maxFrames {
|
|
|
|
[accessLock lock];
|
|
|
|
|
|
|
|
if([[previousNode buffer] isEmpty] && [previousNode endOfStream] == YES) {
|
|
|
|
endOfStream = YES;
|
|
|
|
[accessLock unlock];
|
|
|
|
return [[AudioChunk alloc] init];
|
|
|
|
}
|
|
|
|
|
|
|
|
if([previousNode shouldReset] == YES) {
|
2022-02-07 12:06:36 +00:00
|
|
|
@autoreleasepool {
|
|
|
|
[buffer reset];
|
|
|
|
}
|
2007-10-03 20:23:14 +00:00
|
|
|
|
|
|
|
shouldReset = YES;
|
2022-02-07 05:49:27 +00:00
|
|
|
[previousNode setShouldReset:NO];
|
|
|
|
|
|
|
|
[[previousNode semaphore] signal];
|
2007-10-03 20:23:14 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 12:06:36 +00:00
|
|
|
AudioChunk *ret;
|
|
|
|
|
|
|
|
@autoreleasepool {
|
|
|
|
ret = [[previousNode buffer] removeSamples:maxFrames];
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
|
|
|
|
[accessLock unlock];
|
2022-02-06 11:08:34 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
if([ret frameCount]) {
|
|
|
|
[[previousNode semaphore] signal];
|
|
|
|
}
|
2022-02-06 11:08:34 +00:00
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
return ret;
|
2006-01-20 15:34:02 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)launchThread {
|
2006-01-20 15:34:02 +00:00
|
|
|
[NSThread detachNewThreadSelector:@selector(threadEntry:) toTarget:self withObject:nil];
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setPreviousNode:(id)p {
|
2008-02-16 02:46:19 +00:00
|
|
|
previousNode = p;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (id)previousNode {
|
2006-01-20 15:34:02 +00:00
|
|
|
return previousNode;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (BOOL)shouldContinue {
|
2006-01-20 15:34:02 +00:00
|
|
|
return shouldContinue;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setShouldContinue:(BOOL)s {
|
2006-01-20 15:34:02 +00:00
|
|
|
shouldContinue = s;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (ChunkList *)buffer {
|
2006-01-20 15:34:02 +00:00
|
|
|
return buffer;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)resetBuffer {
|
|
|
|
shouldReset = YES; // Will reset on next write.
|
|
|
|
if(previousNode == nil) {
|
2022-02-07 12:06:36 +00:00
|
|
|
@autoreleasepool {
|
|
|
|
[accessLock lock];
|
|
|
|
[buffer reset];
|
|
|
|
[accessLock unlock];
|
|
|
|
}
|
2007-10-03 20:23:14 +00:00
|
|
|
}
|
2006-04-02 20:03:12 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (Semaphore *)semaphore {
|
2006-01-20 15:34:02 +00:00
|
|
|
return semaphore;
|
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (BOOL)endOfStream {
|
2006-04-02 15:44:08 +00:00
|
|
|
return endOfStream;
|
2006-01-20 15:34:02 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setEndOfStream:(BOOL)e {
|
2006-04-02 15:44:08 +00:00
|
|
|
endOfStream = e;
|
2006-01-20 15:34:02 +00:00
|
|
|
}
|
|
|
|
|
2022-02-07 05:49:27 +00:00
|
|
|
- (void)setShouldReset:(BOOL)s {
|
2007-10-03 20:23:14 +00:00
|
|
|
shouldReset = s;
|
|
|
|
}
|
2022-02-07 05:49:27 +00:00
|
|
|
- (BOOL)shouldReset {
|
2007-10-03 20:23:14 +00:00
|
|
|
return shouldReset;
|
|
|
|
}
|
|
|
|
|
2022-01-14 07:05:32 +00:00
|
|
|
// Buffering nodes should implement this
|
2022-02-07 05:49:27 +00:00
|
|
|
- (double)secondsBuffered {
|
|
|
|
return 0.0;
|
2022-01-14 07:05:32 +00:00
|
|
|
}
|
|
|
|
|
2006-01-20 15:34:02 +00:00
|
|
|
@end
|