0

I want to synchronized start multiple AKPlayer after a given short delay using Objective-C in an iOS app.

I found the following swift code doing that, in the source code of AudioKit, file AKTiming.swift:

let bufferDuration = AKSettings.ioBufferDuration
let referenceTime = AudioKit.engine.outputNode.lastRenderTime ?? AVAudioTime.now()
let startTime = referenceTime + bufferDuration
for node in nodes {
   node.start(at: startTime)
}

How can I do something similar in objective c with a given bufferduration in a NSTimeInterval parameter.

Unfortunately an addition like referenceTime + bufferDuration with AVAudioTime variable is not possible in objective c, and a now() method also doesn't exist.

Apples documentation of the AVAudioTime class is very short and wasn't very helpful for me.

May I use the static method hostTimeForSeconds to convert NSTimeInterval to a hostTime and then timeWithHostTime to create an AVAudioTime instance?

Thank you for your help!

Matthias

2 Answers2

0

May I use the static method hostTimeForSeconds to convert NSTimeInterval to a hostTime and then timeWithHostTime to create an AVAudioTime instance?

Yes!

You'll also need to #import <mach/mach_time.h> and use mach_absolute_time if you want to handle the case where lastRenderTime is NULL.

double bufferDuration = AKSettings.ioBufferDuration;
AVAudioTime *referenceTime = AudioKit.engine.outputNode.lastRenderTime ?: [[AVAudioTime alloc] initWithHostTime:mach_absolute_time()];
uint64_t startHostTime = referenceTime.hostTime + [AVAudioTime hostTimeForSeconds:bufferDuration];
AVAudioTime *startTime = [[AVAudioTime alloc] initWithHostTime:startHostTime];

for (AKPlayer *node in nodes) {
    [node startAt:startTime];
}
dave234
  • 4,793
  • 1
  • 14
  • 29
0

Unfortunately an addition like referenceTime + bufferDuration with AVAudioTime variable is not possible in objective c, and a now() method also doesn't exist.

They don't exist because it is not part of AVAudioTime, it is an extension of AudioKit.

If you look at their source code, you will find:

// An AVAudioTime with a valid hostTime representing now.
public static func now() -> AVAudioTime {
    return AVAudioTime(hostTime: mach_absolute_time())
}

/// Returns an AVAudioTime offset by seconds.
open func offset(seconds: Double) -> AVAudioTime {

    if isSampleTimeValid && isHostTimeValid {
        return AVAudioTime(hostTime: hostTime + seconds / ticksToSeconds,
                           sampleTime: sampleTime + AVAudioFramePosition(seconds * sampleRate),
                           atRate: sampleRate)
    } else if isHostTimeValid {
        return AVAudioTime(hostTime: hostTime + seconds / ticksToSeconds)
    } else if isSampleTimeValid {
        return AVAudioTime(sampleTime: sampleTime + AVAudioFramePosition(seconds * sampleRate),
                           atRate: sampleRate)
    }
    return self
}

public func + (left: AVAudioTime, right: Double) -> AVAudioTime {
    return left.offset(seconds: right)
}

You can implement these extension yourself as well. I don't think you can implement + operator in Objective C, so you'll just have to use the offset method. (NOTE: I did not check the following)

double ticksToSeconds() {
    struct mach_timebase_info tinfo;
    kern_return_t err = mach_timebase_info(&tinfo);
    double timecon = (double)(tinfo.numer) / (double)(tinfo.denom);
    return timecon * 0.000000001;
}

@interface AVAudioTime (Extensions)

+ (AVAudioTime *)now;
- (AVAudioTime *)offsetWithSeconds:(double)seconds;

@end

@implementation AVAudioTime (Extensions)

+ (AVAudioTime *)now {
    return [[AVAudioTime alloc] initWithHostTime:mach_absolute_time()];
}

- (AVAudioTime *)offsetWithSeconds:(double)seconds {
    if ([self isSampleTimeValid] && [self isHostTimeValid]) {
        return [[AVAudioTime alloc] initWithHostTime:self.hostTime + (seconds / ticksToSeconds())
                                          sampleTime:self.sampleTime + (seconds * self.sampleRate)
                                              atRate:self.sampleRate];
    }
    else if ([self isHostTimeValid]) {
        return [[AVAudioTime alloc] initWithHostTime:self.hostTime + (seconds / ticksToSeconds())];
    }
    else if ([self isSampleTimeValid]) {
        return [[AVAudioTime alloc] initWithSampleTime:self.sampleTime + (seconds * self.sampleRate)
                                                atRate:self.sampleRate];
    }
    return self;
}

@end
Bill
  • 469
  • 2
  • 4
  • Thank you very much for your help and the background information. – Matthias Bauer Dec 29 '18 at 18:12
  • When I use this extension with following code, the players dont't start: `double bufferDuration = AKSettings.ioBufferDuration; AVAudioTime *referenceTime = AudioKit.engine.outputNode.lastRenderTime ?: [AVAudioTime now]; AVAudioTime *startTime = [referenceTime offsetWithSeconds: bufferDuration];` When changing the offset method to use the isHostTimeValid variant even if isSampleTime is true, then it works fine. – Matthias Bauer Dec 29 '18 at 18:22