Skip to content

Commit

Permalink
Make AEAudioFilePlayerModule take AudioTimeStamp values for playback …
Browse files Browse the repository at this point in the history
…scheduling

This allows scheduling to function in offline mode. Use the new AETimeStamp functions to convert host ticks or samples to AudioTimeStamps.
  • Loading branch information
michaeltyson committed Jul 2, 2016
1 parent 310aeb6 commit afff5e7
Show file tree
Hide file tree
Showing 6 changed files with 72 additions and 31 deletions.
2 changes: 1 addition & 1 deletion TAAESample/Classes/AEAudioController.m
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ - (void)playRecordingWithCompletionBlock:(void (^)())block {

// Go
self.playingRecording = YES;
[player playAtTime:0];
[player playAtTime:AETimeStampNone];
}
}

Expand Down
8 changes: 4 additions & 4 deletions TAAESample/Classes/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class ViewController: UIViewController {

// Start player
player.currentTime = 0
player.playAtTime(syncTime, beginBlock: {
player.playAtTime(AETimeStampWithHostTicks(syncTime), beginBlock: {
// Show playing state
sender.rotateSpeed = self.currentRotateSpeed()
})
Expand Down Expand Up @@ -159,7 +159,7 @@ class ViewController: UIViewController {

// Start player
player.currentTime = 0
player.playAtTime(syncTime, beginBlock: {
player.playAtTime(AETimeStampWithHostTicks(syncTime), beginBlock: {
// Set button state to playing
sender.imageView!.layer.removeAllAnimations()
sender.selected = true
Expand Down Expand Up @@ -311,7 +311,7 @@ class ViewController: UIViewController {
audio.hit.loop = true
if !audio.hit.playing {
audio.hit.currentTime = 0
audio.hit.playAtTime(audio.nextSyncTimeForPlayer(audio.hit))
audio.hit.playAtTime(AETimeStampWithHostTicks(audio.nextSyncTimeForPlayer(audio.hit)))
}
hitButton.selected = true

Expand All @@ -331,7 +331,7 @@ class ViewController: UIViewController {
// Start the drums playing again
if !audio.drums.playing {
audio.drums.currentTime = 0
audio.drums.playAtTime(audio.nextSyncTimeForPlayer(audio.drums))
audio.drums.playAtTime(AETimeStampWithHostTicks(audio.nextSyncTimeForPlayer(audio.drums)))
beatButton.rotateSpeed = currentRotateSpeed()
}

Expand Down
21 changes: 21 additions & 0 deletions TheAmazingAudioEngine/Core Types/AETime.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,13 @@ extern "C" {
#endif

#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>

typedef uint64_t AEHostTicks;
typedef double AESeconds;

extern const AudioTimeStamp AETimeStampNone; //!< An empty timestamp

/*!
* Initialize
*/
Expand Down Expand Up @@ -63,6 +66,24 @@ AEHostTicks AEHostTicksFromSeconds(AESeconds seconds);
* @return The time in seconds
*/
AESeconds AESecondsFromHostTicks(AEHostTicks ticks);

/*!
* Create an AudioTimeStamps with a host ticks value
*
* If a zero value is provided, then AETimeStampNone will be returned.
*
* @param ticks The time in host ticks
* @return The timestamp
*/
AudioTimeStamp AETimeStampWithHostTicks(AEHostTicks ticks);

/*!
* Create an AudioTimeStamps with a sample time value
*
* @param samples The time in samples
* @return The timestamp
*/
AudioTimeStamp AETimeStampWithSamples(Float64 samples);

#ifdef __cplusplus
}
Expand Down
10 changes: 10 additions & 0 deletions TheAmazingAudioEngine/Core Types/AETime.m
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
static double __hostTicksToSeconds = 0.0;
static double __secondsToHostTicks = 0.0;

const AudioTimeStamp AETimeStampNone = {};

void AETimeInit() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Expand Down Expand Up @@ -60,3 +62,11 @@ AESeconds AESecondsFromHostTicks(AEHostTicks ticks) {
return ticks * __hostTicksToSeconds;
}

AudioTimeStamp AETimeStampWithHostTicks(AEHostTicks ticks) {
if ( !ticks ) return AETimeStampNone;
return (AudioTimeStamp) { .mFlags = kAudioTimeStampHostTimeValid, .mHostTime = ticks };
}

AudioTimeStamp AETimeStampWithSamples(Float64 samples) {
return (AudioTimeStamp) { .mFlags = kAudioTimeStampSampleTimeValid, .mSampleTime = samples };
}
Original file line number Diff line number Diff line change
Expand Up @@ -67,23 +67,23 @@ typedef void (^AEAudioFilePlayerModuleBlock)();
* is reached. Use this method to synchronize playback with other audio
* generators.
*
* If you pass 0 as the time, the module will immediately begin outputting
* audio.
* If you pass AETimeStampNone as the time, the module will immediately
* begin outputting audio.
*
* @param time The time, in host ticks, at which to begin playback
* @param time The timestamp at which to begin playback
*/
- (void)playAtTime:(AEHostTicks)time;
- (void)playAtTime:(AudioTimeStamp)time;

/*!
* Begin playback
*
* Begins playback at the given time; this version allows you to provide a
* block which will be called on the main thread shortly after playback starts.
*
* @param time The time, in host ticks, at which to begin playback
* @param time The timestamp at which to begin playback
* @param block Block to call on main thread when the time is reached and playback starts
*/
- (void)playAtTime:(AEHostTicks)time beginBlock:(AEAudioFilePlayerModuleBlock _Nullable)block;
- (void)playAtTime:(AudioTimeStamp)time beginBlock:(AEAudioFilePlayerModuleBlock _Nullable)block;

/*!
* Stop playback
Expand Down
50 changes: 30 additions & 20 deletions TheAmazingAudioEngine/Modules/Generation/AEAudioFilePlayerModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ @interface AEAudioFilePlayerModule () {
AESeconds _regionStartTime;
BOOL _stopEventScheduled;
BOOL _sequenceScheduled;
AEHostTicks _startTime;
AudioTimeStamp _startTime;
AEHostTicks _anchorTime;
double _playhead;
UInt32 _remainingMicrofadeInFrames;
Expand Down Expand Up @@ -97,18 +97,18 @@ - (void)setCompletionBlock:(void (^)())completionBlock {
}
}

- (void)playAtTime:(AEHostTicks)time {
- (void)playAtTime:(AudioTimeStamp)time {
[self playAtTime:time beginBlock:nil];
}

- (void)playAtTime:(AEHostTicks)time beginBlock:(AEAudioFilePlayerModuleBlock)block {
- (void)playAtTime:(AudioTimeStamp)time beginBlock:(AEAudioFilePlayerModuleBlock)block {
#ifdef DEBUG
if ( time ) {
if ( time.mFlags & kAudioTimeStampHostTimeValid ) {
AEHostTicks now = AECurrentTimeInHostTicks();
if ( time < now-AEHostTicksFromSeconds(0.5) ) {
NSLog(@"%@: Start time is %lf seconds in the past", self, AESecondsFromHostTicks(now-time));
} else if ( time > now+AEHostTicksFromSeconds(60*60) ) {
NSLog(@"%@: Start time is %lf seconds in the future", self, AESecondsFromHostTicks(time-now));
if ( time.mHostTime < now-AEHostTicksFromSeconds(0.5) ) {
NSLog(@"%@: Start time is %lf seconds in the past", self, AESecondsFromHostTicks(now-time.mHostTime));
} else if ( time.mHostTime > now+AEHostTicksFromSeconds(60*60) ) {
NSLog(@"%@: Start time is %lf seconds in the future", self, AESecondsFromHostTicks(time.mHostTime-now));
}
}
#endif
Expand All @@ -134,7 +134,7 @@ - (void)playAtTime:(AEHostTicks)time beginBlock:(AEAudioFilePlayerModuleBlock)bl

- (void)stop {
self.beginBlock = nil;
if ( _startTime ) {
if ( _startTime.mFlags != 0 ) {
// Not yet playing - just stop now
AECheckOSStatus(AudioUnitReset(self.audioUnit, kAudioUnitScope_Global, 0), "AudioUnitReset");
_sequenceScheduled = NO;
Expand Down Expand Up @@ -432,26 +432,32 @@ static void AEAudioFilePlayerModuleProcess(__unsafe_unretained AEAudioFilePlayer
}

AudioUnit audioUnit = AEAudioUnitModuleGetAudioUnit(THIS);
AEHostTicks startTime = THIS->_startTime;
AudioTimeStamp startTime = THIS->_startTime;
double playhead = THIS->_playhead;

// Check start time
AEHostTicks hostTimeAtBufferEnd
= context->timestamp->mHostTime + AEHostTicksFromSeconds((double)context->frames / context->sampleRate);
if ( startTime && startTime > hostTimeAtBufferEnd ) {
UInt32 sampleTimeAtBufferEnd = context->timestamp->mSampleTime + context->frames;

if ( (startTime.mFlags & kAudioTimeStampHostTimeValid && startTime.mHostTime > hostTimeAtBufferEnd)
|| (startTime.mFlags & kAudioTimeStampSampleTimeValid && startTime.mSampleTime > sampleTimeAtBufferEnd) ) {
// Start time not yet reached: emit silence
AEAudioBufferListSilence(abl, 0, context->frames);
return;

} else if ( startTime && startTime < context->timestamp->mHostTime ) {
} else if ( (startTime.mFlags & kAudioTimeStampHostTimeValid && startTime.mHostTime < context->timestamp->mHostTime)
|| (startTime.mFlags & kAudioTimeStampSampleTimeValid && startTime.mSampleTime < context->timestamp->mSampleTime) ) {
// Start time is in the past - we need to skip some frames
AEHostTicks skipTime = context->timestamp->mHostTime - startTime;
UInt32 skipFrames = round(AESecondsFromHostTicks(skipTime) * context->sampleRate);
UInt32 skipFrames =
startTime.mFlags & kAudioTimeStampHostTimeValid ?
round(AESecondsFromHostTicks(context->timestamp->mHostTime - startTime.mHostTime) * context->sampleRate)
: context->timestamp->mSampleTime - startTime.mSampleTime;
playhead += (double)skipFrames * (THIS->_fileSampleRate / context->sampleRate);
AudioTimeStamp timestamp = {
.mFlags = kAudioTimeStampSampleTimeValid|kAudioTimeStampHostTimeValid,
.mFlags = startTime.mFlags | kAudioTimeStampSampleTimeValid,
.mSampleTime = context->timestamp->mSampleTime - skipFrames,
.mHostTime = startTime
.mHostTime = startTime.mHostTime
};

AEAudioBufferListCopyOnStack(scratch, abl, 0);
Expand All @@ -471,12 +477,16 @@ static void AEAudioFilePlayerModuleProcess(__unsafe_unretained AEAudioFilePlayer
}
}

THIS->_startTime = 0;
THIS->_startTime = AETimeStampNone;

// Prepare buffer
UInt32 frames = context->frames;
UInt32 silentFrames = startTime && startTime > context->timestamp->mHostTime
? round(AESecondsFromHostTicks(startTime - context->timestamp->mHostTime) * context->sampleRate) : 0;
UInt32 silentFrames =
(startTime.mFlags & kAudioTimeStampHostTimeValid && startTime.mHostTime > context->timestamp->mHostTime)
? round(AESecondsFromHostTicks(startTime.mHostTime - context->timestamp->mHostTime) * context->sampleRate)
: (startTime.mFlags & kAudioTimeStampSampleTimeValid && startTime.mSampleTime > context->timestamp->mSampleTime)
? startTime.mSampleTime - context->timestamp->mSampleTime
: 0;
AEAudioBufferListCopyOnStack(mutableAbl, abl, silentFrames);
AudioTimeStamp adjustedTime = *context->timestamp;

Expand All @@ -486,7 +496,7 @@ static void AEAudioFilePlayerModuleProcess(__unsafe_unretained AEAudioFilePlayer
// Point buffer list to remaining frames
abl = mutableAbl;
frames -= silentFrames;
adjustedTime.mHostTime = startTime;
adjustedTime.mHostTime += AEHostTicksFromSeconds((double)silentFrames / context->sampleRate);
adjustedTime.mSampleTime += silentFrames;
}

Expand Down

0 comments on commit afff5e7

Please sign in to comment.