Skip to content

Commit

Permalink
Added "pause" functionality to file player unit
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnthe committed Apr 9, 2012
1 parent 280cc77 commit 44c2d43
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 34 deletions.
20 changes: 18 additions & 2 deletions YBAudioUnit/YBAudioFilePlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,32 @@
@property (nonatomic, readwrite, strong) NSURL *fileURL;
- (void)setFileURL:(NSURL *)fileURL typeHint:(AudioFileTypeID)typeHint;

/**
Read-only property containing the player's current playback time,
offset from the beginning of the file.
*/
@property (nonatomic, readonly) AudioTimeStamp currentPlayTime;

/**
Convenience method to set the entire file as scheduled region,
prime the buffers of the player and set the start time stamp as `now`.
*/
- (void)scheduleEntireFilePrimeAndStartImmediately;

/**
Convenience method to unschedule previously scheduled regions.
Convenience method to unschedule the current region and schedule from the specified
time up to the end of the file. This can be used to `pause` the player unit by giving
the current playback time. To `continue` playback after `pausing`, send -setStartTimeStampImmediately.
*/
- (void)rescheduleEntireFileBeginningAtPlaybackTime:(AudioTimeStamp)timestamp;

/**
Convenience method to unschedule and reschedule the entire file beginning at the current
playback time. This can be used to `pause` the player unit at the current position.
To `continue` playback after `pausing`, send -setStartTimeStampImmediately.
@see -rescheduleEntireFileBeginningAtPlaybackTime:
*/
- (void)unschedule;
- (void)rescheduleEntireFileBeginningAtCurrentPlaybackTime;

/**
Methods to set and get the region of the file that is scheduled for playback.
Expand Down
75 changes: 52 additions & 23 deletions YBAudioUnit/YBAudioFilePlayer.m
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,26 @@ - (id)initWithAUNode:(AUNode)auNode audioUnit:(AudioUnit)auAudioUnit inGraph:(YB
@implementation YBAudioFilePlayer {
AudioFileID _audioFileID;
ScheduledAudioFileRegion _region;
AudioStreamBasicDescription _fileASBD;
UInt64 _filePacketsCount;
}

/**
Overriden because kAudioUnitProperty_CurrentPlayTime is the playTime relative to the mStartFrame,
while it often makes more sense to have the time from the beginning of the file.
In case the player is stopped (currentPlayTime == -1.) the mStartFrame of the current region is
reported back, which often makes sense as this is often used as the cue point at which the player is `paused`.
*/
- (AudioTimeStamp)currentPlayTime {
AudioTimeStamp currentPlayTime;
UInt32 dataSize = sizeof(currentPlayTime);
YBAudioThrowIfErr(AudioUnitGetProperty(_auAudioUnit, kAudioUnitProperty_CurrentPlayTime, kAudioUnitScope_Global, 0, &currentPlayTime, &dataSize));
if (currentPlayTime.mSampleTime == -1.) {
currentPlayTime.mSampleTime = 0;
}
currentPlayTime.mFlags = kAudioTimeStampSampleTimeValid;
currentPlayTime.mSampleTime += _region.mStartFrame;
return currentPlayTime;
}

- (void)setFileURL:(NSURL *)fileURL {
Expand All @@ -39,6 +59,14 @@ - (void)setFileURL:(NSURL *)fileURL typeHint:(AudioFileTypeID)typeHint {
if (_fileURL) {
YBAudioThrowIfErr(AudioFileOpenURL((__bridge CFURLRef)fileURL, kAudioFileReadPermission, typeHint, &_audioFileID));
YBAudioThrowIfErr(AudioUnitSetProperty(_auAudioUnit, kAudioUnitProperty_ScheduledFileIDs, kAudioUnitScope_Global, 0, &_audioFileID, sizeof(AudioFileID)));

// Get number of audio packets in the file:
UInt32 propsize = sizeof(_filePacketsCount);
YBAudioThrowIfErr(AudioFileGetProperty(_audioFileID, kAudioFilePropertyAudioDataPacketCount, &propsize, &_filePacketsCount));

// Get file's asbd:
propsize = sizeof(_fileASBD);
YBAudioThrowIfErr(AudioFileGetProperty(_audioFileID, kAudioFilePropertyDataFormat, &propsize, &_fileASBD));
}
}

Expand All @@ -48,35 +76,36 @@ - (void)scheduleEntireFilePrimeAndStartImmediately {
[self setStartTimeStampSampleTime:-1.];
}

- (void)unschedule {
[self reset];
}

- (void)setRegionEntireFile {
// Get number of audio packets in the file:
UInt64 nPackets = 0;
UInt32 propsize = sizeof(UInt64);
YBAudioThrowIfErr(AudioFileGetProperty(_audioFileID, kAudioFilePropertyAudioDataPacketCount, &propsize, &nPackets));

// Get file's asbd:
AudioStreamBasicDescription fileASBD = {0};
propsize = sizeof(fileASBD);
YBAudioThrowIfErr(AudioFileGetProperty(_audioFileID, kAudioFilePropertyDataFormat, &propsize, &fileASBD));

// Tell the file player AU to play the entire file and with which point in the graph's time it should start (at time 0)
// This makes sure the players start at the same time and stay perfectly sync.
- (void)resetRegionToEntireFileWithStartFrame:(SInt64)startFrame {
_region.mTimeStamp.mFlags = kAudioTimeStampSampleTimeValid;
_region.mTimeStamp.mSampleTime = 0;
_region.mTimeStamp.mSampleTime = 0; /* Relative to graph's time line */
_region.mAudioFile = _audioFileID;
_region.mLoopCount = 0;
_region.mStartFrame = 0;
_region.mFramesToPlay = nPackets * fileASBD.mFramesPerPacket;

_region.mStartFrame = startFrame;
_region.mFramesToPlay = (_filePacketsCount * _fileASBD.mFramesPerPacket) - startFrame;
}

- (void)rescheduleEntireFileBeginningAtPlaybackTime:(AudioTimeStamp)timestamp {
[self unschedule];
NSAssert((timestamp.mFlags & kAudioTimeStampSampleTimeValid), nil);
[self resetRegionToEntireFileWithStartFrame:timestamp.mSampleTime];
YBAudioThrowIfErr(AudioUnitSetProperty(_auAudioUnit, kAudioUnitProperty_ScheduledFileRegion, kAudioUnitScope_Global, 0, &_region, sizeof(_region)));
[self primeBuffers];
}

- (void)rescheduleEntireFileBeginningAtCurrentPlaybackTime {
[self rescheduleEntireFileBeginningAtPlaybackTime:self.currentPlayTime];
}

- (void)setRegionEntireFile {
[self resetRegionToEntireFileWithStartFrame:0];
YBAudioThrowIfErr(AudioUnitSetProperty(_auAudioUnit, kAudioUnitProperty_ScheduledFileRegion, kAudioUnitScope_Global, 0, &_region, sizeof(_region)));
}

- (void)setRegion:(ScheduledAudioFileRegion*)region {
memcpy(&_region, region, sizeof(_region));
if (region != &_region) {
memcpy(&_region, region, sizeof(_region));
}
YBAudioThrowIfErr(AudioUnitSetProperty(_auAudioUnit, kAudioUnitProperty_ScheduledFileRegion, kAudioUnitScope_Global, 0, &_region, sizeof(_region)));
}

Expand All @@ -96,7 +125,7 @@ - (void)primeBuffersWithFrames:(UInt32)numberOfFrames {
- (id)initWithAUNode:(AUNode)auNode audioUnit:(AudioUnit)auAudioUnit inGraph:(YBAudioUnitGraph *)graph {
self = [super initWithAUNode:auNode audioUnit:auAudioUnit inGraph:graph];
if (self) {

}
return self;
}
Expand Down
20 changes: 19 additions & 1 deletion YBAudioUnit/YBScheduledSoundPlayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,22 @@
@interface YBScheduledSoundPlayer : YBAudioUnitNode

/**
Read-only property containing the player's current playback time.
Read-only property containing the player's current playback time,
offset from its start time.
*/
@property (nonatomic, readonly) AudioTimeStamp currentPlayTime;

/**
Read-only property indicating whether playback has started.
*/
@property (nonatomic, readonly) BOOL isPlaying;

/**
Read-only property indicating whether the startTimeStamp has been set,
i.e. if it is scheduled and possibly playing.
*/
@property (nonatomic, readonly) BOOL hasStartTimeStamp;

/**
Methods to set the ScheduleStartTimeStamp property.
@see kAudioUnitProperty_ScheduleStartTimeStamp
Expand All @@ -29,4 +41,10 @@
- (void)setStartTimeStampSampleTime:(Float64)startSampleTime;
- (void)setStartTimeStamp:(AudioTimeStamp*)startTime;

/**
Convenience method to unschedule previously scheduled regions.
If the player unit has already started playback, it will stop.
*/
- (void)unschedule;

@end
24 changes: 23 additions & 1 deletion YBAudioUnit/YBScheduledSoundPlayer.m
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@
#import "YBScheduledSoundPlayer.h"
#import "YBAudioException.h"

@implementation YBScheduledSoundPlayer
@implementation YBScheduledSoundPlayer {
BOOL _hasStartTimeStamp;
}

- (AudioTimeStamp)currentPlayTime {
AudioTimeStamp currentPlayTime;
Expand All @@ -27,10 +29,30 @@ - (void)setStartTimeStampSampleTime:(Float64)startSampleTime {
startTime.mFlags = kAudioTimeStampSampleTimeValid;
startTime.mSampleTime = startSampleTime;
YBAudioThrowIfErr(AudioUnitSetProperty(_auAudioUnit, kAudioUnitProperty_ScheduleStartTimeStamp, kAudioUnitScope_Global, 0, &startTime, sizeof(startTime)));
_hasStartTimeStamp = YES;
}

- (void)setStartTimeStamp:(AudioTimeStamp*)startTime {
YBAudioThrowIfErr(AudioUnitSetProperty(_auAudioUnit, kAudioUnitProperty_ScheduleStartTimeStamp, kAudioUnitScope_Global, 0, &startTime, sizeof(startTime)));
_hasStartTimeStamp = YES;
}

- (BOOL)isPlaying {
AudioTimeStamp currentPlayTime;
UInt32 dataSize = sizeof(currentPlayTime);
YBAudioThrowIfErr(AudioUnitGetProperty(_auAudioUnit, kAudioUnitProperty_CurrentPlayTime, kAudioUnitScope_Global, 0, &currentPlayTime, &dataSize));
return (currentPlayTime.mSampleTime != -1.);
}

- (void)unschedule {
[self reset];
}

- (void)reset {
_hasStartTimeStamp = NO;
[super reset];
}


@synthesize hasStartTimeStamp = _hasStartTimeStamp;
@end
16 changes: 16 additions & 0 deletions YBAudioUnitExample/YBViewController.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ @interface YBViewController ()
@implementation YBViewController {
__weak IBOutlet UIButton *startStopButton;
__weak IBOutlet UILabel *timeLabel;
__weak IBOutlet UIButton *playPauseButton;
NSTimer *timer;

YBAudioUnitGraph *graph;
Expand Down Expand Up @@ -80,6 +81,7 @@ - (void)viewDidLoad {

- (void)viewDidUnload {
timeLabel = nil;
playPauseButton = nil;
[super viewDidUnload];
startStopButton = nil;
graph = nil;
Expand Down Expand Up @@ -133,10 +135,24 @@ - (IBAction)mixerPanAction:(UISlider*)sender {
- (IBAction)rescheduleAction:(id)sender {
[guitarPlayerNode unschedule];
[guitarPlayerNode scheduleEntireFilePrimeAndStartImmediately];

[bandPlayerNode unschedule];
[bandPlayerNode scheduleEntireFilePrimeAndStartImmediately];
}

- (IBAction)playPauseAction:(id)sender {
AudioTimeStamp currentPlayTime = [guitarPlayerNode currentPlayTime];
if ([guitarPlayerNode isPlaying]) {
[guitarPlayerNode rescheduleEntireFileBeginningAtPlaybackTime:currentPlayTime];
[bandPlayerNode rescheduleEntireFileBeginningAtPlaybackTime:currentPlayTime];
[playPauseButton setTitle:@"Play" forState:UIControlStateNormal];
} else {
[guitarPlayerNode setStartTimeStampImmediately];
[bandPlayerNode setStartTimeStampImmediately];
[playPauseButton setTitle:@"Pause" forState:UIControlStateNormal];
}
}

- (IBAction)distortionAction:(id)sender {
YBParameterViewController *filterViewController = [[YBParameterViewController alloc] init];
filterViewController.filter = distortionFilter;
Expand Down
Loading

0 comments on commit 44c2d43

Please sign in to comment.