Search code examples
androidc++audiooboe

How to change audio pitch?


I'm using the Oboe C++ library for playing sounds in my android application. I want to change the pitch of my audio samples. So, I started creating "mPos" float value to hold the current played frame and adding the "mPitch" value every step.

It seems like the audio played correctly with the new Pitch but it's double it's self when the pitch is high(e.g 1.2) and make a weird noise and when the pitch is low (e.g 0.212).

This is my first-time audio programming, I did a lot of research before I post this question. I even send messages directly to "Oboe" supports but no response. Does anyone have any idea how to implement the Pitch correctly?

streamLength always 192

channelCount always 2

Code:

void Player::renderAudio(float *stream, int32_t streamLength){

    const int32_t channelCount = mSound->getChannelCount();

    if (mIsPlaying){

        float framesToRenderFromData = streamLength ;
        float totalSourceFrames = mSound->getTotalFrames()/mPitch;
        const float *data = mSound->getData();

        // Check whether we're about to reach the end of the recording
        if (mPos + streamLength >= totalSourceFrames ){
            framesToRenderFromData = (totalSourceFrames - mPos);
            mIsPlaying = false;
        }
        for (int i = 0; i < framesToRenderFromData; ++i) {
            for (int j = 0; j < channelCount; ++j) {
                if(j % 2 == 0){
                    stream[(i*channelCount)+j] = (data[((size_t)mPos * channelCount)) + j] * mLeftVol) * mVol;
                }else{
                    stream[(i*channelCount)+j] = (data[((size_t)mPos * channelCount)) + j] * mRightVol) * mVol;
                }
            }
            mPos += mPitch;
            if(mPos >= totalSourceFrames){
                mPos = 0;
            }
        }
        if (framesToRenderFromData < streamLength){
            renderSilence(&stream[(size_t)framesToRenderFromData], streamLength * channelCount);
        }
    } else {
        renderSilence(stream, streamLength * channelCount);
    }
}

void Player::renderSilence(float *start, int32_t numSamples){
    for (int i = 0; i < numSamples; ++i) {
        start[i] = 0;
    }
}

void Player::setPitch(float pitchData){
    mPitch = pitchData;
};

Solution

  • When you multiply a float variable (mPos) by an integer-type variable (channelCount), the result is a float. You are, at the least, messing up your channel interleaving. Instead of

    (size_t)(mPos * channelCount)
    

    try

    ((size_t)mPos) * channelCount
    

    EDIT: You are intentionally looping the source when reaching the end, with the if statement that results in mPos = 0;. Instead of doing this, you could calculate the number of source samples independently of the pitch, but break out of the loop when your source samples are exhausted. Also, your comparison of the source and destination samples isn't useful because of the pitch adjustment:

        float framesToRenderFromData = streamLength ;
        float totalSourceFrames = mSound->getTotalFrames();  // Note change here
        const float *data = mSound->getData();
    
        // Note: Only check here is whether mPos has reached the end from
        // a previous call
        if ( mPos >= totalSourceFrames ) {
            framesToRenderFromData = 0.0f;
        }
        for (int i = 0; i < framesToRenderFromData; ++i) {
           for (int j = 0; j < channelCount; ++j) {
               if(j % 2 == 0){
                    stream[(i*channelCount)+j] = (data[((size_t)mPos * channelCount)) + j] * mLeftVol) * mVol;
                }else{
                    stream[(i*channelCount)+j] = (data[((size_t)mPos * channelCount)) + j] * mRightVol) * mVol;
                }
            }
            mPos += mPitch;
            if ( ((size_t)mPos) >= totalSourceFrames ) {   // Replace this 'if' and its contents
                framesToRenderFromData = (size_t)mPos;
                mPos = 0.0f;
                break;
            }
        }
    

    A note, however, for completeness: You really shouldn't be accomplishing pitch change in this way for any serious application -- the sound quality will be terrible. There are free libraries for audio resampling to an arbitrary target rate; these will convert your source sample to a higher or lower number of samples, and provide quality pitch changes when replayed at the same rate as the source.