Search code examples
objective-cavfoundationavaudioplayercore-audio

Changing Sine Wave frequencies in the same AVAudioPCMBuffer


I've been working on getting a clean sine wave sound that can change frequencies when different notes are played. From what I've understood, I need to resize the buffer's frameLength relative to the frequency to avoid those popping sounds caused when the frame ends on a sine's peak.

So on every iteration, I set the frameLength and then populate buffer with the signal.

AVAudioPlayerNode *audioPlayer = [[AVAudioPlayerNode alloc] init];
AVAudioPCMBuffer *buffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat:[audioPlayer outputFormatForBus:0] frameCapacity:44100*10];`

while(YES){
    AVAudioFrameCount frameCount = ceil(44100.0/osc.frequency);
    [buffer setFrameLength:frameCount];
    [audioPlayer scheduleBuffer:buffer atTime:0 options:AVAudioPlayerNodeBufferLoops completionHandler:nil];
    for(int i = 0; i < [buffer frameLength]; i++){
        for (int channelNumber = 0; channelNumber < channelCount ; channelNumber++) {
            float * const channelBuffer = floatChannelData[channelNumber];
            channelBuffer[i] = [self getSignalOnFrame:i];
        }
    }
}

where the signal is generated from:

(float)getSignalOnFrame:(int)i {
     float sampleRate = 44100.0;
     return [osc amplitude] * sinf([osc frequency] * i * 2.0 * M_PI / sampleRate);
}

The starting tone sounds fine and there are no popping sounds when notes change but the notes themselves sound like they're being turned into sawtooth waves or something.

Any ideas on what I might be missing here? Or should I just create a whole new audioPlayer with a fresh buffer for each note played?

Thanks for any advice!


Solution

  • If the buffers are contiguous, then a better method to not have discontinuities in sine wave generation is to remember the phase of the sinewave at the end of one buffer, and use that phase as the starting point (angle) to generate the next buffer.

    If the buffers are not contiguous, then a common way to avoid clicks is to gradually taper the first and last few milliseconds of each buffer from full gain to zero. A linear gain taper will do, but a raised cosine taper is a slightly smoother taper.