I'm upgrading an old app that was written in Objective C and relied upon openAL (which has been deprecated) to manage the audio. The app is a musical interval ear training app. What it does is play two pitches (sound files) either simultaneously or with a small delay (1.2 seconds). Each sound files last from 4 to 8 seconds so there is always a period where they are heard simultaneously.
To work out how to use AVAudioEngine I created an Objective C iOS app in Xcode 12.5.1 with iOS 14.5 as the deployment target. I added a single button (Play Interval) to the storyboard that fires on touch down the playSound action. My viewController.m file looks like this:
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <AVFAudio/AVFAudio.h>
@interface ViewController ()
{
AVAudioEngine *_engine;
AVAudioPlayerNode *_player1;
AVAudioPlayerNode *_player2;
AVAudioMixerNode *_mainMixer;
}
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
_engine = [[AVAudioEngine alloc]init];
_player1 = [[AVAudioPlayerNode alloc]init];
_player2 = [[AVAudioPlayerNode alloc]init];
[_engine attachNode:_player1];
[_engine attachNode:_player2];
_mainMixer = [_engine mainMixerNode];
}
- (IBAction)playSound:(id)sender {
NSString *path1 = [[NSBundle mainBundle] pathForResource:@"PianoC4" ofType:@"mp3"];
NSURL *fileURL1 = [NSURL URLWithString:path1];
NSString *path2 = [[NSBundle mainBundle] pathForResource:@"PianoE4" ofType:@"mp3"];
NSURL *fileURL2 = [NSURL URLWithString:path2];
AVAudioFile *file1 = [[AVAudioFile alloc]initForReading:fileURL1 error:nil];
AVAudioFile *file2 = [[AVAudioFile alloc]initForReading:fileURL2 error:nil];
const float kStartDelayTime = 0.025;
AVAudioFormat *outputFormat = [_player1 outputFormatForBus:0];
AVAudioFramePosition startSampleTime = _player1.lastRenderTime.sampleTime;
AVAudioTime *startTime1 = [AVAudioTime timeWithSampleTime:(startSampleTime + ((kStartDelayTime + 0.0) * outputFormat.sampleRate)) atRate:outputFormat.sampleRate];
AVAudioTime *startTime2 = [AVAudioTime timeWithSampleTime:(startSampleTime + ((kStartDelayTime + 1.2) * outputFormat.sampleRate)) atRate:outputFormat.sampleRate];
[_engine connect:_player1 to:_mainMixer format:file1.processingFormat];
[_player1 scheduleFile:file1 atTime: nil completionHandler:nil];
[_engine connect:_player2 to:_mainMixer format:file2.processingFormat];
[_player2 scheduleFile:file2 atTime: nil completionHandler:nil];
NSError *error;
[_engine startAndReturnError:&error];
if(error) {
NSLog(@"engine ERROR!");
}
[_player1 playAtTime:startTime1];
[_player2 playAtTime:startTime2];
}
@end
The first time the playSound button is tapped the 2 sounds are played simultaneously (no delay between the two sound files). Whereas with all subsequent button taps the second sound file is played with the expected 1.2 seconds delay. I can't figure out why the delay does not occur the first time but does occur all other subsequent times!??
Any help will be greatly appreciated and sorry about the Objective C