change frequency

Discussion in 'iPhone/iPad Programming' started by mightyPhone, Jan 18, 2009.

  1. macrumors newbie

    Joined:
    Aug 13, 2008
    #1
    How can I change the playback frequency (pitch) of a .wav file programmatically?
     
  2. macrumors member

    neil.b

    Joined:
    Nov 20, 2008
    #2
    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];
    
     
  3. thread starter macrumors newbie

    Joined:
    Aug 13, 2008
    #3
    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.
     
  4. macrumors member

    neil.b

    Joined:
    Nov 20, 2008
    #4
    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).
     
  5. thread starter macrumors newbie

    Joined:
    Aug 13, 2008
    #5
    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.
     
  6. macrumors member

    neil.b

    Joined:
    Nov 20, 2008
    #6
    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.
     
  7. thread starter macrumors newbie

    Joined:
    Aug 13, 2008
    #7
    .
     
  8. thread starter macrumors newbie

    Joined:
    Aug 13, 2008
    #8
  9. macrumors member

    neil.b

    Joined:
    Nov 20, 2008
    #9
    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 :)
     
  10. macrumors member

    neil.b

    Joined:
    Nov 20, 2008
    #10
    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... :)
     
  11. thread starter macrumors newbie

    Joined:
    Aug 13, 2008
    #11
    <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?
     
  12. macrumors member

    neil.b

    Joined:
    Nov 20, 2008
    #12
    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 :D
     
  13. thread starter macrumors newbie

    Joined:
    Aug 13, 2008
    #13
    I looked into Audio Queue Service, seems like there is a good posibility. :)
     
  14. macrumors newbie

    Joined:
    Dec 30, 2009
    #14
    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];
    
     
  15. macrumors 603

    Joined:
    Jul 29, 2003
    Location:
    Silicon Valley
    #15
    Do you want to change just the pitch (duration stays constant)? Or the pitch and speed of playback together?
     
  16. macrumors newbie

    Joined:
    Dec 30, 2009
    #16
    Changing the pitch and keeping the same duration would be ideal, but I would be happy with just a sample rate change.
     
  17. macQM, Mar 9, 2011
    Last edited by a moderator: Mar 9, 2011

    macrumors newbie

    Joined:
    Mar 9, 2011
    #17
    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];
     
  18. macrumors regular

    nickculbertson

    Joined:
    Nov 19, 2010
    Location:
    Nashville, TN
    #18
    Google "finch openal". It doesn't use AVAudio but it will show you how to change pitch using OpenAL (which is better anyway).
    Nick
     
  19. macrumors newbie

    Joined:
    Mar 9, 2011
    #19
    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!
     
  20. macrumors 603

    Joined:
    Jul 29, 2003
    Location:
    Silicon Valley
    #20
    There exists no hint. You need to use something other than AVFoundation.
     

Share This Page