Random looping audio in SpriteKit Objective-C - objective-c

Im trying to play a random music file that loops in SpriteKit. I have achieved this but theres a delay in-between the loop using SKAction like this. Regardless of the simulator or device.
self.backgroundMusicArray = [[NSMutableArray alloc] initWithObjects:#"BackgroundMusic1.mp3",#"BackgroundMusic2.mp3",#"BackgroundMusic3.mp3",#"BackgroundMusic4.mp3",#"BackgroundMusic5.mp3",#"BackgroundMusic6.mp3", nil];
int randomNumber = arc4random() % self.backgroundMusicArray.count;
SKAction* play = [SKAction playSoundFileNamed:[self.backgroundMusicArray objectAtIndex:randomNumber] waitForCompletion:YES];
SKAction *loop = [SKAction repeatActionForever:play];
[self runAction:loop];
I have also achieved this with NSURL & AVAudioPlayer without the delay problem, but I'm lost on how to make it random. So I guess my question is "how can I do this randomly with AVAuidoPlayer". Im fairly new to objc so any help is greatly appreciated.
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle]
pathForResource:#"BackGroundMusic1"
ofType:#"mp3"]];
self.backGroundplayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:nil];
self.backGroundplayer.numberOfLoops = -1;
[self.backGroundplayer play];

Related

ios8 AVAudioEngine: proper way to play multiple (long) audio files

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!

NSString set from a url doesn't read?

I am having trouble getting my NSString to read my string that I have set. The play action is hooked to a UIButton OnTouchDown if it matters.
- (IBAction)play {
if (audioPlayer.playing == NO) {
urlSong = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/k.mp3", [[NSBundle mainBundle] resourcePath]]];
[audioPlayer release];
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:urlSong error:&error];
audioPlayer.numberOfLoops = 0;
//if i set this line to stringSong = #"k"; then comment out the next line everything works fine
stringSong = [[urlSong lastPathComponent] stringByDeletingPathExtension];
label.text = stringSong;
AVAudioSession *audiosession = [AVAudioSession sharedInstance];
[audiosession setCategory:AVAudioSessionCategoryAmbient error:nil];
[audioPlayer play];
[start setTitle:#"Stop" forState:UIControlStateNormal];
}
else
if ([stringSong isEqualToString:#"k"]){
label.text = #"audio stopped";
[audioPlayer stop];
}
}
When I run the code on my iPhone Xcode returns
EXC_BAD_ACCESS
on this line
if ([stringSong isEqualToString:#"k"]){
I know this is due to me trying to set the string from the url because when I plainly set my string to 'k' the code executes just fine.
If anybody is wondering the overall point of this, I am trying to have my audio buttons set to stop all other audio when this button is pressed and start the audio assigned to this button which in this case is 'k.mp3'. But if this audio is already playing just to stop playing all audio. Thank you.
You aren't retaining stringSong.
This line:
stringSong = [[urlSong lastPathComponent] stringByDeletingPathExtension];
Should be:
stringSong = [[[urlSong lastPathComponent] stringByDeletingPathExtension] retain];
At some point this needs to be released.

Play NSSound two times

I want to play a NSSound two times except its shorter than 10sec then i want to wait until the 10sec are done and then start the second one.
But I already have a problem playing the same NSSound twice.
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"MessageArrived" ofType:#"wav"];
NSSound *sound = [[NSSound alloc] initWithContentsOfFile:resourcePath byReference:YES];
[sound play] //It plays the fist time
[sound play] //I get the following Error and it doesn't play a secont time
/*malloc: *** auto malloc[498]: error: GC operation on unregistered thread. Thread registered implicitly. Break on auto_zone_thread_registration_error() to debug.*/
Can someone tell me whats the reason for this error?
How can I handle this?
Is there another way i have to do this?
So after a loot of searching and trying to fix it I got the solution for my problem...
NSSound *sound = [NSSound soundNamed:#"MessageArrived"];
BOOL res = [sound play];
NSLog(#"%d", res);
[NSThread detachNewThreadSelector:#selector(playSecondSound:) toTarget:self withObject:sound];
-(void)playSecondSound:(NSSound*)sound
{
[NSThread sleepForTimeInterval:10-[sound duration]];
[sound stop];
BOOL res = [sound play];
NSLog(#"%d", res);
}
I found out that even when the sound has finished I have to call [sound stop] before starting the second.
The sound is already playing when you call the second play method, obviously a bug in garbage collection as the second play should be ignored, but it will not be an issue with what you want to achieve.
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:#"MessageArrived" ofType:#"wav"];
NSSound *sound = [[NSSound alloc] initWithContentsOfFile:resourcePath byReference:YES];
[sound play];
//Will play the second time after 10.0 seconds have passed.
NSInteger wait = ([sound duration] < 10.0) ? 10.0 - [sound duration] : 0.0;
[sound performSelector:#selector(play) withObject:nil afterDelay:wait];

iOS AVAudioPlayer double sound playing issue with Xcode 4.2 and ARC

Ive been beating my head up against the all trying to figure out a weird sound issue in my program. I will post the code below. As a brief description I have a function that gets passed a variable and it takes that and decides which category of sound it should play, within that function I have multiple sounds of reach category so I use an arc4random statement then run that through a switch statement. What keeps happening is that it will play two sounds from the case statement instead of just one, most times if I call it twice with my button push the second time it will play the sound from the first time and a new sound, other times it plays the same same over the top of each other but with a slight delay. I put in a breakpoint in the switch and when it double plays it only goes through the switch once which is really confusing. One thing to note is right before this I do play another sound but it uses a separate AVAudioplayer and path variable so that shouldn't be an issue as it never plays the second sound as the other sound I'm playing. I'm only calling the function once when I press the button so I'm not sure why it does this. I have tried putting the *path and *avaudioplayer variables inside the function but it won't play at all. Searching here it seems as though arc deallocs it before it gets a chance to play it. I ended up trying to put it at the top of my .m file as a global variable then just set the actual path and play the sound within the switch. The sound will play but it plays twice.. Hopefully someone can help me out. I tried putting the avaudioplayer definition as a property as well and it does the same thing. Here is my code snippet. And thanks in advance ...
in the .m file
// Just below my synthesize statements
NSString *path;
AVAudioPlayer *theSound
// My code that I call when the button is pressed
[self playVoice:#"buyVoice"];
// The playVoice function
- (void)playVoice:(NSString*)voiceNum;
if ([voiceNum isEqualToString:#"buyVoice"]) // Bought an Item Time for a voice
{
// play coin sound
[self coinSound];
// play random buy phrase and coin sound
int phraseNumber = 0;
phraseNumber = arc4random() %(3);
switch (phraseNumber)
{
case 0:
{
path = [[NSBundle mainBundle] pathForResource:#"sndBuy1" ofType:#"m4a"];
theSound =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: path] error:NULL];
[theSound setNumberOfLoops:0];
[theSound play];
break;
}
case 1:
{
path = [[NSBundle mainBundle] pathForResource:#"sndBuy2" ofType:#"m4a"];
theSound =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: path] error:NULL];
[theSound setNumberOfLoops:0];
[theSound play];
break;
}
case 2:
{
path = [[NSBundle mainBundle] pathForResource:#"sndBuy3" ofType:#"m4a"];
theSound =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: path] error:NULL];
[theSound setNumberOfLoops:0];
[theSound play];
break;
}
case 3:
{
path = [[NSBundle mainBundle] pathForResource:#"sndBuy4" ofType:#"m4a"];
theSound =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath: path] error:NULL];
[theSound setNumberOfLoops:0];
[theSound play];
break;
}
}
}
set this code before u play the Audio sound
This will reset the player to the beginning:
[theSound setCurrentTime:0.0];
[theSound play];
try this

Help on Objective C? My app keeps crashing?

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.