Register FAQ / Rules Forum Spy Search Today's Posts Mark Forums Read
Go Back   MacRumors Forums > Apple Systems and Services > Programming > iPhone/iPad Programming

Reply
 
Thread Tools Search this Thread Display Modes
Old Jan 18, 2009, 09:47 PM   #1
mightyPhone
macrumors newbie
 
Join Date: Aug 2008
change frequency

How can I change the playback frequency (pitch) of a .wav file programmatically?
mightyPhone is offline   0 Reply With Quote
Old Jan 19, 2009, 04:10 AM   #2
neil.b
macrumors member
 
Join Date: Nov 2008
I figured out a way to do this using AVFoundation and AIFF files.

You need to load the sample into an NSData object and you can then use NSData methods to modify the sample rate stored in the AIFF header. The reason you need to pull the samples into NSData objects is because you can't modify the contents of the NIB/XIB files. For that reason it's a bit wasteful with memory (you've effectively got two copies of the sample loaded up - one in the XIB and the other in the NSData object).

Then initialise an instance of an AVAudioPlayer object using the initWithData method (instead of initWithURL). Then you can play the sound. The only downside is you need to re-initialise the AVAudioPlayer object every time you change the pitch so it increases latency - not massively but it's noticable. It depends on what you're trying to do with the sound as to how effective this method is for you.

I posted about this in the Apple Dev Forum. I'm still playing around with it but it works.

(rest of post copied from my Dev Forum posts)

The basic steps are;

1) Load the AIF sample into a NSData object, allocate & initialise the AVAudioPlayer with the NSData
2) Modify the sample rate via the NSData object
3) When you want to play the sound, initialise the AVAudioPlayer using the initWithData method (you have to do this or the sample rate change has no effect)


1) Load the AIF sample into a NSData object, allocate & initialise the AVAudioPlayer with the NSData

Code:
AVAudioPlayer *myAudioPlayer;
NSMutableData *soundFileData;
soundFileData = [NSMutableData dataWithContentsOfURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"sample.aif" ofType:NULL]]];
myPlayer = [[AVAudioPlayer alloc] initWithData:soundFileData error:NULL];
2) Modify the sample rate via the NSData object
I've not done any proper decoding of the AIFF chunks, just worked on the assumption that with a "normal" AIF sample, the sample rate is at offset 0x1C and, being in IEEE 80-bit extended format, is 10 bytes long. I found a handy C function to convert to/from IEEE 80-bit. So, my quick rough-and-ready sample rate modifier;

Code:
# define FloatToUnsigned(f)      ((unsigned long)(((long)(f - 2147483648.0)) + 2147483647L) + 1)
 
void ConvertToIeeeExtended(double num, char* bytes)
{
    int    sign;
    int expon;
    double fMant, fsMant;
    unsigned long hiMant, loMant;
 
    if (num < 0) {
        sign = 0x8000;
        num *= -1;
    } else {
        sign = 0;
    }
 
    if (num == 0) {
        expon = 0; hiMant = 0; loMant = 0;
    }
    else {
        fMant = frexp(num, &expon);
        if ((expon > 16384) || !(fMant < 1)) {    /* Infinity or NaN */
            expon = sign|0x7FFF; hiMant = 0; loMant = 0; /* infinity */
        }
        else {    /* Finite */
            expon += 16382;
            if (expon < 0) {    /* denormalized */
                fMant = ldexp(fMant, expon);
                expon = 0;
            }
            expon |= sign;
            fMant = ldexp(fMant, 32);         
            fsMant = floor(fMant);
            hiMant = FloatToUnsigned(fsMant);
            fMant = ldexp(fMant - fsMant, 32);
            fsMant = floor(fMant);
            loMant = FloatToUnsigned(fsMant);
        }
    }
   
    bytes[0] = expon >> 8;
    bytes[1] = expon;
    bytes[2] = hiMant >> 24;
    bytes[3] = hiMant >> 16;
    bytes[4] = hiMant >> 8;
    bytes[5] = hiMant;
    bytes[6] = loMant >> 24;
    bytes[7] = loMant >> 16;
    bytes[8] = loMant >> 8;
    bytes[9] = loMant;
}
And the method to modify the sample rate;

Code:
-(void) changeSampleRate:(NSMutableData *)sampleData :(double)newRate
{
     char *sampleRateByteBuffer = malloc(10);
     NSRange sampleRateRange = NSMakeRange(0x1C, 10);
     ConvertToIeeeExtended(newRate, sampleRateByteBuffer);

 
     [sampleData replaceBytesInRange:sampleRateRange withBytes:sampleRateByteBuffer];
 
     //free the temp buffers
     free(sampleRateByteBuffer);
 
}
3) When you want to play the sound, initialise the AVAudioPlayer using the initWithData method (you have to do this or the sample rate change has no effect), then play the sound

Code:
//if you don't stop the sound first, some corruption occurs - needs more investigation
     [myPlayer stop];
     //change the sample rate
     [self changeSampleRate:soundFileData :22050];         //just using 22050 as an example
     //(re)init the player object
     [myPlayer initWithData:soundFileData error:NULL];
     //if repeatedly playing the same sound, reset the playback pointer
     if ([myPlayer currentTime] != 0) [myPlayer setCurrentTime:0];
     [myPlayer play];

Last edited by neil.b; Jan 19, 2009 at 06:41 AM. Reason: Couple of missing spaces
neil.b is offline   0 Reply With Quote
Old Jan 19, 2009, 05:20 AM   #3
mightyPhone
Thread Starter
macrumors newbie
 
Join Date: Aug 2008
Oh whoa, thank you so much neil.b.

I was thinking of something on the line like what you have using AVAudioPlayer but wasn't sure if the key is in change the sample rate. This code looks very helpful. Thanks.

Too bad that the iPhone doesn't have a audio systhesizer build in.
mightyPhone is offline   0 Reply With Quote
Old Jan 19, 2009, 06:35 AM   #4
neil.b
macrumors member
 
Join Date: Nov 2008
Quote:
Originally Posted by mightyPhone View Post
Oh whoa, thank you so much neil.b.

I was thinking of something on the line like what you have using AVAudioPlayer but wasn't sure if the key is in change the sample rate. This code looks very helpful. Thanks.

Too bad that the iPhone doesn't have a audio systhesizer build in.
Hope it helps.

I have a little Xcode project with it working if you get stuck though there's nothing remotely useful apart from proving it works.

I think (hope) the next iterration of the AVFoundation Framework will be much improved. It only appear recently and is a lot better than the SystemSound stuff.

You can also use OpenAL supposedly and that can play samples at different pitches. I say supposedly only because I've yet to get it working (I've been trying out all sorts of different audio methods).
neil.b is offline   0 Reply With Quote
Old Jan 19, 2009, 07:45 AM   #5
mightyPhone
Thread Starter
macrumors newbie
 
Join Date: Aug 2008
Yea, I hope the AVFoundation will improve as well, add Sound synthesizer will be a blessing. I do like the AVFoundation tho, it makes play a simple sound hell lot easier without the need to muddle with CA.

Do you think OpenAL can achieve a more real time approach as far a pitch change? I briefly read through the headers but havn't seen anything obvious, time to take a deeper look again. The whole problem seem to lie in the data buffering. Since data have to be rebuffered, there will always be a delay.
mightyPhone is offline   0 Reply With Quote
Old Jan 19, 2009, 08:11 AM   #6
neil.b
macrumors member
 
Join Date: Nov 2008
I'm not sure. I think it's strengths are in the simple(ish) API rather than out-and-out performance but I've yet to test it out.

The lowest latency method is using RemoteIO but it's shockingly under-documented by Apple. It gives you access to audio IO at a much lower level but what it gives in speed, it takes away in complexity and obscurity.

There's a good thread by a guy over on the Developer Forums. It's in the Media/Audio section, search for "RemoteIO". According to the guy that wrote the post he's achieving a very low latency, like a couple of milliseconds, compared to Audio Queue Services which has a latency of 0.5 seconds (or so).

I got his example stuff half-working but because the example he gave was only a skeleton, I got stuck at the point in my code where the callback function has to fill the buffers. I made a mental note to go back to it at some point and try to crowbar stuff from Audio Queue example code into the callbacks for the RemoteIO stuff to see if I can make it work.

Give me a shout if you can't find the thread on the Dev Forums.
neil.b is offline   0 Reply With Quote
Old Jan 19, 2009, 05:07 PM   #7
mightyPhone
Thread Starter
macrumors newbie
 
Join Date: Aug 2008
.

Last edited by mightyPhone; Jan 19, 2009 at 05:18 PM.
mightyPhone is offline   0 Reply With Quote
Old Jan 19, 2009, 05:11 PM   #8
mightyPhone
Thread Starter
macrumors newbie
 
Join Date: Aug 2008
HA! Found it. For those who are also interested in this topic, here is another link:
http://michael.tyson.id.au/2008/11/0...io-audio-unit/

Last edited by mightyPhone; Jan 20, 2009 at 04:40 AM.
mightyPhone is offline   0 Reply With Quote
Old Jan 20, 2009, 03:06 AM   #9
neil.b
macrumors member
 
Join Date: Nov 2008
Yep, that's the one. I didn't realise he'd posted it on his own website too.

Let me know how you get on. Like I said, I got it up and running (as far as no errors and the play callback was getting hit) but couldn't see where/how to create my own playback buffers and service them in the callback. At that point I got the idea of modifying the AIFF files (as above) so I lost interest in RemoteIO for a while
neil.b is offline   0 Reply With Quote
Old Jan 20, 2009, 03:09 AM   #10
neil.b
macrumors member
 
Join Date: Nov 2008
Oh, meant to add: the RemoteIO method, as far as I can tell, won't solve your frequency-changing problem.

I managed to playback sounds at different pitches using AudioQueue but you have to stop, change frequency and refill the buffers every time you play a sound. Which, as you can imagine, was horribly laggy...
neil.b is offline   0 Reply With Quote
Old Jan 20, 2009, 04:49 AM   #11
mightyPhone
Thread Starter
macrumors newbie
 
Join Date: Aug 2008
<was horribly laggy>
Yeah, I found that's the case as well. There's GOT to be a better way to deal with this.

Just a thought, in the good ol days, the Moog systhesizers, all it takes is a attenuator to modulate the pitch, wonder if, somehow, there is a way to manipulate the iPhone audio hardware to do that?

Last edited by mightyPhone; Jan 20, 2009 at 05:09 AM.
mightyPhone is offline   0 Reply With Quote
Old Jan 20, 2009, 05:56 AM   #12
neil.b
macrumors member
 
Join Date: Nov 2008
Quote:
Originally Posted by mightyPhone View Post
<was horribly laggy>
Yeah, I found that's the case as well. There's GOT to be a better way to deal with this.

Just a thought, in the good ol days, the Moog systhesizers, all it takes is a attenuator to modulate the pitch, wonder if, somehow, there is a way to manipulate the iPhone audio hardware to do that?
I'm sure there is if you could get down low enough.

Apple SDK is not going to expose that to you though.

By all accounts the DSP is pretty powerful - look at stuff like Noise.io Pro and see what can be achieved. I've no idea how they achieved it though
neil.b is offline   0 Reply With Quote
Old Jan 21, 2009, 05:36 AM   #13
mightyPhone
Thread Starter
macrumors newbie
 
Join Date: Aug 2008
I looked into Audio Queue Service, seems like there is a good posibility.
mightyPhone is offline   0 Reply With Quote
Old Dec 30, 2009, 02:22 AM   #14
ezone
macrumors newbie
 
Join Date: Dec 2009
Change sampleRate of a CAF file

Any ideas on how to change the sampleRate for a CAF? I'm using AVAudioRecorder to record a sound sample from the iPhone mic, and I wanted to change the pitch of the recorded sample and play it back. Here are the settings I'm using for AVAudioRecorder:

Code:
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryRecord error: &error];
NSMutableDictionary* recordSetting = [[NSMutableDictionary alloc] init];
[recordSetting setValue :[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];
[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey]; 
[recordSetting setValue:[NSNumber numberWithInt: 1] forKey:AVNumberOfChannelsKey];	
[recordSetting setValue :[NSNumber numberWithInt:16] forKey:AVLinearPCMBitDepthKey];
[recordSetting setValue :[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsBigEndianKey];
[recordSetting setValue :[NSNumber numberWithBool:NO] forKey:AVLinearPCMIsFloatKey];
	
// Create a new dated file
NSDate *now = [NSDate dateWithTimeIntervalSinceNow:0];
NSString *caldate = [now description];
recorderFilePath = [[NSString stringWithFormat:@"%@/%@.caf", DOCUMENTS_FOLDER, caldate] retain];
NSURL *url = [NSURL fileURLWithPath:recorderFilePath];
recorder = [[ AVAudioRecorder alloc] initWithURL:url settings:recordSetting error:&error];
ezone is offline   0 Reply With Quote
Old Dec 30, 2009, 10:01 PM   #15
firewood
macrumors 603
 
Join Date: Jul 2003
Location: Silicon Valley
Do you want to change just the pitch (duration stays constant)? Or the pitch and speed of playback together?
firewood is offline   0 Reply With Quote
Old Dec 31, 2009, 12:54 AM   #16
ezone
macrumors newbie
 
Join Date: Dec 2009
Quote:
Originally Posted by firewood View Post
Do you want to change just the pitch (duration stays constant)? Or the pitch and speed of playback together?
Changing the pitch and keeping the same duration would be ideal, but I would be happy with just a sample rate change.
ezone is offline   0 Reply With Quote
Old Mar 9, 2011, 04:36 PM   #17
macQM
macrumors newbie
 
Join Date: Mar 2011
Got this working, except pitch does not seem to change...

I'm using an aiff recorded file from a url that was stored in NSMutableData. The following is the only way I could get it to play with the change rate code. I tell the player to play in another method. This one sets it up with the sampleData. However, the audio sounds normal. I changed malloc(10) to malloc(5) and malloc(1), but that did not do anything. Does anyone know what needs to be altered to change the pitch? Is it something in the C+ code? I'm not very well-versed in buffers and/or advanced audio. Thanks for any pointers/help.

Code:
-(void) changeSampleRate:(NSMutableData*)sampleData :(double)newRate
{
	char *sampleRateByteBuffer = malloc(1);
	NSRange sampleRateRange = NSMakeRange(0x1C, 10);
	ConvertToIeeeExtended(newRate, sampleRateByteBuffer);
	[sampleData replaceBytesInRange:sampleRateRange withBytes:sampleRateByteBuffer];
	
	AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithData:sampleData error:NULL];
	[audioPlayer prepareToPlay] ;
	
	//free the temp buffers
	free(sampleRateByteBuffer);
	
	currentPlayer = audioPlayer;
Should I have different recordSettings?
Code:
NSMutableDictionary* recordSetting = [[NSMutableDictionary alloc] init];
	[recordSetting setValue:[NSNumber numberWithInt:kAudioFormatLinearPCM] forKey:AVFormatIDKey];[recordSetting setValue:[NSNumber numberWithFloat:44100.0] forKey:AVSampleRateKey]; 
	[recordSetting setValue:[NSNumber numberWithInt: 2] forKey:AVNumberOfChannelsKey];
	[recordSetting setValue:[NSNumber numberWithInt: 2] forKey:AVNumberOfChannelsKey];

Last edited by dejo; Mar 9, 2011 at 05:17 PM. Reason: Please use [code] tags.
macQM is offline   0 Reply With Quote
Old Mar 9, 2011, 09:32 PM   #18
nickculbertson
macrumors regular
 
Join Date: Nov 2010
Location: Nashville, TN
Google "finch openal". It doesn't use AVAudio but it will show you how to change pitch using OpenAL (which is better anyway).
Nick
__________________
My App Blog --->
nickculbertson is offline   0 Reply With Quote
Old Mar 9, 2011, 11:14 PM   #19
macQM
macrumors newbie
 
Join Date: Mar 2011
Finch

I guess I will try Finch. I had already downloaded but was hoping I could change pitch using AVFoundation, because my app is already done with that framework. I had already downloaded Finch but it looks really complicated.

Back to the drawing board - trying Finch. Unless anyone knows of a way to do it with AVFoundation. Is it not possible to do this with a file recorded with AVAudioRecorder and then played back with AVAudioPlayer? I noticed the MacOSX code has player.rate = 0.5 but of course, iPhone only has the change volume code (player.volume = x).

I've researched this for a few days now and found nothing except this forum thread that remotely has a chance of using AVFoundation to change pitch. If it is possible, please give me a hint of what to change in the code. Much thanks!
macQM is offline   0 Reply With Quote
Old Mar 9, 2011, 11:58 PM   #20
firewood
macrumors 603
 
Join Date: Jul 2003
Location: Silicon Valley
There exists no hint. You need to use something other than AVFoundation.
firewood is offline   0 Reply With Quote

Reply
MacRumors Forums > Apple Systems and Services > Programming > iPhone/iPad Programming

Thread Tools Search this Thread
Search this Thread:

Advanced Search
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

Similar Threads
thread Thread Starter Forum Replies Last Post
Change location of iPhone/iPod backups? Nihaochan OS X 20 Mar 20, 2014 05:34 AM
Change Frequency of Alerts - missing in ios5 Dino F iOS 5 and earlier 2 Oct 29, 2011 08:49 AM
Cydia's Manage Account Feature: How to change email address? johnnyxhuynh Jailbreaks and iOS Hacks 1 Mar 24, 2011 04:51 AM
name one thing you would like to change on your primary OS iPhoneCollector OS X 9 Feb 22, 2011 10:41 AM
Help with remoteIO audio. Changing frequency dynamically? wyager iPhone/iPad Programming 0 Nov 26, 2010 08:54 PM


All times are GMT -5. The time now is 09:53 PM.

Mac Rumors | Mac | iPhone | iPhone Game Reviews | iPhone Apps

Mobile Version | Fixed | Fluid | Fluid HD
Copyright 2002-2013, MacRumors.com, LLC