I'm attempting to play several fairly long files at once in sync using AVAudioEngine in iOS 8.
I can do this with the following:
- (void)startAudio {
AVAudioMixerNode *mainMixer = [self.engine mainMixerNode];
AVAudioPlayerNode *player = [[AVAudioPlayerNode alloc] init];
AVAudioPlayerNode *player2 = [[AVAudioPlayerNode alloc] init];
AVAudioPlayerNode *player3 = [[AVAudioPlayerNode alloc] init];
[self.engine attachNode:player];
[self.engine attachNode:player2];
[self.engine attachNode:player3];
NSString *fileName = [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] bundlePath], #"file1.caf"];
NSURL *fileUrl = [NSURL fileURLWithPath:fileName];
AVAudioFile *file = [[AVAudioFile alloc] initForReading:fileUrl error:nil];
NSString *fileName2 = [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] bundlePath], #"file2.caf"];
NSURL *fileUrl2 = [NSURL fileURLWithPath:fileName2];
AVAudioFile *file2 = [[AVAudioFile alloc] initForReading:fileUrl2 error:nil];
NSString *fileName3 = [NSString stringWithFormat:#"%#/%#", [[NSBundle mainBundle] bundlePath], #"file3.caf"];
NSURL *fileUrl3 = [NSURL fileURLWithPath:fileName3];
AVAudioFile *file3 = [[AVAudioFile alloc] initForReading:fileUrl3 error:nil];
[self.engine connect:player to:mainMixer format:file.processingFormat];
[self.engine connect:player2 to:mainMixer format:file2.processingFormat];
[self.engine connect:player3 to:mainMixer format:file3.processingFormat];
[player scheduleFile:file atTime:nil completionHandler:nil];
[player2 scheduleFile:file2 atTime:nil completionHandler:nil];
[player3 scheduleFile:file3 atTime:nil completionHandler:nil];
NSError *error;
[self.engine startAndReturnError:&error];
[player play];
[player2 play];
[player3 play];
}
But that seems cumbersome. I thought there is a way to chain nodes together, but I can't find any examples and wasn't quite clear how to do this from watching the WWDC session on AVAudioEngine.
Any ideas?
You should not atTime:nil, but choose a time in the near future. Like now + 0.1 seconds and schedule it all players to play at that time.
Look at my question an answer at: Scheduling an audio file for playback in the future with AVAudioTime
to find out how.
scheduleSegment or scheduleFile work about the same way, but with scheduleSegment you can start playing at any point in the file.
ATTENTION - Note the difference between synchronized STARTING and synchronized PLAYING!!!
If you only want to start several player ONCE and at the same start time you definitely have to use the playAtTime: method with all of them referring to the same now time that you can freely choose. You can choose nil as a value and the system will schedule them on the next render frame 1024 samples later. Just in case you don't have your playAtTime: calls in a row (which you do have with play: in your code above) but rather interrupted by some other calls or NSLog-s it is generally safer to assign a start time instead of just putting nil. But mind you this only gives you a sync'ed FIRST start but no further guaranty for synching additional new players to the block of players recently started.
There are some design flaws on Apple's side (I would call them bugs) that make sync'ed playing with AVAudioEngine almost impossible. e.g.
if your start time is playAtTime:playerLastRender you are already screwed. Your player DO start in-sync to each other but you are already out-of-sync in regards to your engine's node time because...
in some cases your submitted start time gets overruled and changed by the system, so all subsequent calculations - you have to do for e.g. synching an additional player - that are based on your own requested (but silently changed under the hood) start time will be out-of-sync...
the system even goofs itself up!!! playerTimeForNodeTime: and nodeTimeForPlayerTime: do report wrong values - so you can't make any decisions based on these values either etc.
For the subtleties have a look at my answer in
AVAudioEngine multiple AVAudioInputNodes do not play in perfect sync
I've covered a lot of stuff of AVAudioEngine that one should know!
Have fun!
Related
I have an mp3 sound I'd like to play with pan, pitch/samplerate, and volume controls set once (not changing in realtime).
I am using AVAudioPlayer at the moment, which works, but the rate setting performs time stretching instead of performing the samplerate change where the lower values cause a sound to get slower and lower-pitched, and the higher ones cause a sound to get faster and higher-pitched (kind of like tape speed).
E.g., setting samplerate to 88200 HZ when your sound is actually 44100 will result in it playing at 200% speed/pitch.
Is something like this possible with AVAudioPlayer, or is there another way to accomplish this?
Here's what I have so far:
player=[[AVAudioPlayer alloc] initWithContentsOfURL:
[NSURL fileURLWithPath: #"sound.mp3"] error: nil];
player.volume=0.4f;
player.pan=-1f;
player.enableRate=YES;
player.rate=2.0f;
[player play];
Note: I'm not referring to the pitch method where time stretching is used in combination to keep the length of the sound aproximetly the same, or any "advanced" algorithms.
AVAudioEngine can give you pitch, rate, pan and volume control:
self.engine = [[AVAudioEngine alloc] init];
NSError *error;
AVAudioPlayerNode *playerNode = [[AVAudioPlayerNode alloc] init];
AVAudioMixerNode *mixer = [[AVAudioMixerNode alloc] init];
AVAudioUnitVarispeed *varispeed = [[AVAudioUnitVarispeed alloc] init];
[self.engine attachNode:playerNode];
[self.engine attachNode:varispeed];
[self.engine attachNode:mixer];
[self.engine connect:playerNode to:varispeed format:nil];
[self.engine connect:varispeed to:mixer format:nil];
[self.engine connect:mixer to:self.engine.mainMixerNode format:nil];
BOOL result = [self.engine startAndReturnError: &error];
assert(result);
AVAudioFile *audioFile = [[AVAudioFile alloc] initForReading:url error:&error];
assert(audioFile);
// rate & pitch (fused), pan and volume controls
varispeed.rate = 0.5; // half pitch & rate
mixer.pan = -1; // left speaker
mixer.volume = 0.5; // half volume
[playerNode scheduleFile:audioFile atTime:nil completionHandler:nil];
[playerNode play];
If you want separate rate & pitch control, replace the AVAudioUnitVarispeed node with an AVAudioUnitTimePitch node.
I'm sure this has been asked before, but I've had no luck finding it. In my app data is loading synchronously, which locks up the app. I've tried asynch loading, but that doesn't work with the JSON parser.
To denote that the app isn't frozen, just working on downloading data, I was hoping to present the user with a small transparent overlay with the loading icon. I was wondering how to go about this - do I need to put it on another thread?
To clarify, I want to do something very similar to the Netflix iPad app - their loading overlay is perfect for the projet I'm working on.
Edit: I've added some async code below
I first call this function:
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSURLConnection *c = [[NSURLConnection alloc] init];
[self connectionWorks:c didReceiveData:data];
connectionworks
-(void)connectionWorks:(NSURLConnection *)connection didReceiveData:(NSData *)data{
OLWork *newWork;
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSDictionary *results = [jsonString JSONValue];
NSArray *rawBooks = [results objectForKey:#"works"];
for (NSDictionary *work in rawBooks) {
newWork = [[OLWork alloc] init];
newWork.title = [work objectForKey:#"title"];
newWork.author = [[[work objectForKey:#"authors"] objectAtIndex:0] objectForKey:#"name"];
newWork.key = [work objectForKey:#"key"];
[self.works setValue:newWork forKey:newWork.title];
}
}
This will do the job for you, it's well documented and easy to use
https://github.com/jdg/MBProgressHUD
Out of intrest which JSON parser are you using? Getting asynchronous requests working would be a much better solution.
I want to play the music from internet by url.
I create a simply project that has one button with the following code:
NSURL *url = [[NSURL alloc] initWithString:#"http://someURL.mp3"];
NSError **err;
QTMovie *movie = [[QTMovie alloc] initWithURL:url error:err];
[movie play];
It works but with some delay (I think because it waits while file has been fully downloaded).
So what I need to do that the music starts to play immediately (when, for example, 10% of file has been downloaded)?
Use -[QTMovie autoplay] to automatically play the music once enough data has been downloaded.
In your case:
NSURL *url = [[NSURL alloc] initWithString:#"http://someURL.mp3"];
NSError **err;
QTMovie *movie = [[QTMovie alloc] initWithURL:url error:err];
[movie autoplay];
From the QTMovie class reference:
The autoplay method configures a QTMovie object to begin playing as soon as enough data is available that the playback can continue uninterrupted to the end of the movie.
If you can consider displaying a Quicktime window over your app, you can use this code :
NSString *url = #"http://someURL.mp3";
UIWebView* tempAudioPlayer = [[UIWebView alloc] initWithFrame:CGRectMake(0, 0, 0, 0)];
[tempAudioPlayer loadHTMLString:[NSString stringWithFormat:#"<iframe frameborder=\"0\" width=\"0\" height=\"0\" src=\"%#\"></iframe>", url] baseURL:nil];
[self addSubview:tempAudioPlayer];
I first create a UIWebView which will not be displayed. Then I loadHTMLString a <iframe> in it, with the URL of my file as the src value. And I add it to my view. Quicktime appears immediately.
I have a relatively simple app that plays a sound using AVAudioPlayer, like this:
NSURL *file3 = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/ball_bounce.aiff", [[NSBundle mainBundle] resourcePath]]];
player3 = [[AVAudioPlayer alloc] initWithContentsOfURL:file3 error:nil];
[player3 prepareToPlay];
to later be called to play, like this:
[player3 play];
But this sound is called in multiple places (collisions with a bouncing ball), many times. Sometimes, the ball hits two things in quick enough succession where the sound from the first bounce is still playing when the ball collides a second time. This second bounce then does not produce a sound. How can I make it play a sound every time it collides, even if it involves paying a sound that overlaps an already playing sound?
Any help is greatly appreciated.
One way to solve this problem would to be create a new instance of AVAudioPlayer if the previous one is already playing:
if([player3 isPlaying]) {
AVAudioPlayer *newPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[player3 url]];
[newPlayer setDelegate:self];
[newPlayer play];
}
Then, in the delegate method audioPlayerDidFinishPlaying:successfully:, you should send the player the -release message:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag {
if(player != player3) {
[player release];
}
}
This is just the way it has to be, as you do need a new source buffer for each concurrent sound playing. :(
It is an old thread but maybe someone can benefit from this answer. Creating AVAudioPlayer repeatedly can be a burden for the app and can creates lags. In this case you can use SystemSound.
NSString *pathToMySound = [[NSBundle mainBundle] pathForResource:#"yourMusic" ofType:#"mp3"];
SystemSoundID soundID;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)([NSURL fileURLWithPath: pathToMySound]), &soundID);
AudioServicesPlaySystemSound(soundID);
Downside of this solution, you can't adjust the volume.
I am creating an app for the Iphone/Itouch, but I keep on running into a couple of major leaks that just crash the app. In my game, right when I press play (from the home screen)It goes to another page that has the game on it. But, right after it appears, ind the console I get a Warning: Memory level=1. What could be happening? Here is my ViewDidLoad Method:
-(void)viewDidLoad {
[super viewDidLoad];
array = [[NSMutableArray alloc] initWithCapacity:100];
bulletArray = [[NSMutableArray alloc] initWithCapacity:100];
pos = CGPointMake(0.0,-5.0);
NSString *path = [[NSBundle mainBundle] pathForResource:#"GloriousMorning" ofType:#"mp3"];
AVAudioPlayer *theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
//float effects_Volume = [[NSUserDefaults standardUserDefaults] floatForKey:#"effectsVolume"];
//theAudio.volume = effects_Volume;
[theAudio play];
}
And also, a second question, since my game is a shooting game, the user presses a button titled "Fire". But, every time I test my app on a device, It crashes when I press the fire button. Here is my code for the fire button.
-(IBAction)Fire {
NSString *path = [[NSBundle mainBundle] pathForResource:#"gunShot" ofType:#"mp3"];
AVAudioPlayer *theAudio=[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
//float effects_Volume = [[NSUserDefaults standardUserDefaults] floatForKey:#"effectsVolume"];
//theAudio.volume = effects_Volume;
[theAudio play];
//IBOutlet UIImageView *newBullet = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Bullet.png"]];
UIImageView *newBullet = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"Bullet.png"]];
newBullet.frame = CGRectMake(0, 0, 10.0, 10.0);
newBullet.center = CGPointMake(239.0, 236.0);
[bulletArray addObject:newBullet];
[self.view addSubview:newBullet];
}
First, I create a sound. Then, I place a bullet right where the gun is currently located, and add it to an array so that every .01 of a second, in another bit of code, I can run through the array and check every bullet to detect collision.
Please tell me what I am doing wrong. Thanks!!!
The error when I click the Fire Button that makes the app crash is this:
GDB: Data Formatters temporarily unavailable, will retry after a 'continue'(unknown error loading shared library "
And Also I think I am making a huge leak when I try to play the audio, at least that's what someone told me. (If that is the case, please tell me how to fix it)
You alloc'd theAudio so you need to release it when you are done. Might be better to make that a ivar so you don't have to set up and tear down the audio every time they press the fire button.