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];