How can I change non-realtime pitch/samplerate of a sound? - objective-c

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.

Related

freeing memory in a simple avaudioengine program?

I'm wondering how to free memory in this simple program that plays a file through a buffer and then stops it.
-(void)setupAudioOne
{
NSError *error;
BOOL success = NO;
_player = [[AVAudioPlayerNode alloc] init];
NSURL *hiphopOneURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"Hip Hop 1" ofType:#"caf"]];
AVAudioFile *hiphopOneFile = [[AVAudioFile alloc] initForReading:hiphopOneURL error:&error];
_playerLoopBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:[hiphopOneFile processingFormat] frameCapacity:(AVAudioFrameCount)[hiphopOneFile length]];
success = [hiphopOneFile readIntoBuffer:_playerLoopBuffer error:&error];
_engine = [[AVAudioEngine alloc] init];
[_engine attachNode:_player];
AVAudioMixerNode *mainMixer = [_engine mainMixerNode];
AVAudioFormat *stereoFormat = [[AVAudioFormat alloc] initStandardFormatWithSampleRate:44100 channels:2];
[_engine connect:_player to:mainMixer fromBus:0 toBus:0 format:stereoFormat];
[self startEngine];
}
Above is the general setup of the engine and the player node.
Then we implement the player with a play button:
- (IBAction)play:(id)sender {
if (!self.playerIsPlaying)
{
[self setupAudioOne];
[_player scheduleBuffer:_playerLoopBuffer atTime:nil options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
[_player play];
}
}
And finally we stop the player with a stop button:
- (IBAction)stopHipHopOne:(id)sender {
if (self.playerIsPlaying) {
[_player stop];
}
playerIsPlaying is just a simple BOOL that determines if the _player is playing.
So basically my question is, when you hit the stop button as this program is written now, no memory will be freed.
Surely there is a simple line of code that I can add to the stop button that frees up the memory the engine and player is using?
Any thoughts?
Yes, there is. After stopping the player node, you can call:
[_engine disconnectNodeInput:_player];
[_engine detachNode:_player];
I saw you're also keeping a reference to the audio buffer, so you might want to nil that one as well. Let me know if that doesn't work for you. Something else could be leaking.

Random looping audio in SpriteKit 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];

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!

playing music by url

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.

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.