0
$\begingroup$

I'm working on a phase vocoder pitch shifter running in C++ on an embedded microcontroller platform. I've successfully written the phase vocoder using the optimized FFT library, and it appears to be working well for analysis and resynthesis. With no processing on the phase vocoder bins audio throughput sounds clean.

I'm now writing a pitch shifter algorithm based on shifting the bins up or down by a given ratio and adjusting phase by the same ratio. This appears to be working as well, but the result is extremely artifacted. It sounds like there is amplitude loss and loss of frequency resolution (like the new frequencies have been quantized to an incorrect value).

Does my method for pitch shifting look correct? Is there anything I should change in the processing function, or in my code in general?

Here is my pitch shift function:

void pvproc( void ){

    memset(pv_out, 0, fft_size * sizeof(float32_t));
    uint32_t i, newchan;
    float32_t pscal = 1;
    float32_t chan = 1;

    for (i = 2, chan = 1; i < fft_size; chan++, i += 2) {
        newchan  = (int32_t)((chan * pscal)+0.5) << 1;
        if (newchan < fft_size && newchan > 0) {
          pv_out[newchan] = pv_in[i];
          pv_out[newchan + 1] = pv_in[i+1] * pscal;
        }
    }
}

Here is my phase vocoder pitch shifter class as a whole:

template <uint32_t fft_size = AUDIO_BUFFER_SIZE,
uint32_t hop_size = PROC_BUFFER_SIZE>
class pvoc_shift : public dsp_object {
public:

    pvoc_shift(T shift = 0){ //use internal buffer memory
        for(uint32_t i = 0; i < fft_size; ++i){
            window[i] = .5 * (1 - cos(2.0 * M_PI * float32_t(i) / float32_t(fft_size - 1)));
        }
        arm_rfft_fast_init_f32(&fftHandler, fft_size);
    };

    void init( void ){

    }

    void read_n(std::initializer_list<T*> inputs, std::initializer_list<T*> outputs, int len){
        read_impl(inputs, outputs, len);
    };

    float32_t window[fft_size] = {};

    float32_t input_ring[fft_size];
    float32_t output_ring[fft_size];


    float32_t fft_in[fft_size] = {};
    float32_t fft_out[fft_size] = {};

    float32_t pv_in[fft_size] = {};
    float32_t pv_out[fft_size] = {};

    float32_t last_ph_in[fft_size] = {};
    float32_t last_ph_out[fft_size] = {};

    int index = 0;

    arm_rfft_fast_instance_f32 fftHandler;

    float32_t fftBufIn[PROC_BUFFER_SIZE];
    float32_t fftBufOut[PROC_BUFFER_SIZE];


private:

    void pvanal( void ){
            uint32_t i, k;
            float32_t phi, mag, delta;

            const float32_t fac = AUDIO_SAMPLING_FREQUENCY_HZ / (float32_t(hop_size) * 2.0 * M_PI);
            const float32_t scal = 2.0 * M_PI * float32_t(hop_size) / float32_t(fft_size);

            for(i = 0; i < fft_size; i++){
                fft_in[i] = input_ring[(i+index) % fft_size] * window[i];
            }

            arm_rfft_fast_f32(&fftHandler, fft_in, fft_out, 0);

            for(i = 0, k = 0; i < fft_size; i += 2, k++){
                mag = sqrtf((fft_out[i] * fft_out[i]) + (fft_out[i+1] * fft_out[i+1]));
                phi = atan2f(fft_out[i+1], fft_out[i]);
                delta = phi - last_ph_in[k];
                last_ph_in[k] = phi;

                while(delta > M_PI) delta -= (2.0 * M_PI);
                while(delta < -M_PI) delta += (2.0 * M_PI);

                pv_in[i] = mag;
                pv_in[i+1] = (delta + (k * scal)) * fac;
            }
    };

    void pvsynth( void ){
        uint32_t i, k;
        float32_t phi, mag, delta;

        const float32_t fac = float32_t(hop_size) * 2.0 * M_PI / AUDIO_SAMPLING_FREQUENCY_HZ;
        const float32_t scal = AUDIO_SAMPLING_FREQUENCY_HZ / float32_t(fft_size);

        for(i = 0, k = 0; i < fft_size; i += 2, k++){
            delta = (pv_out[i+1] - (k * scal)) * fac;
            phi = last_ph_out[k] + delta;
            last_ph_out[k] = phi;
            mag = pv_out[i];
            fft_in[i] = mag * cosf(phi);
            fft_in[i+1] = mag * sinf(phi);
        }

        arm_rfft_fast_f32(&fftHandler, fft_in, fft_out, 1);

        for(i = 0; i < fft_size; i++){
            output_ring[(i+index)%fft_size] += fft_out[i] * window[i];
        }
    };

    void pvproc( void ){

        memset(pv_out, 0, fft_size * sizeof(float32_t));
        uint32_t i, newchan;
        float32_t pscal = 1;
        float32_t chan = 1;

        for (i = 2, chan = 1; i < fft_size; chan++, i += 2) {
            newchan  = (int32_t)((chan * pscal)+0.5) << 1;
            if (newchan < fft_size && newchan > 0) {
              pv_out[newchan] = pv_in[i];
              pv_out[newchan + 1] = pv_in[i+1] * pscal;
            }
      }
    }

    void read_impl(std::initializer_list<T*> inputs, std::initializer_list<T*> outputs, int len) {

        auto inputIter = inputs.begin();
        auto outputIter = outputs.begin();

        T* input = *inputIter;
        T* output = *outputIter;

        memcpy(&input_ring[index], input, hop_size * sizeof(float32_t));
        memset(&output_ring[index], 0, hop_size * sizeof(float32_t));

        index = (index + hop_size) % fft_size;

        pvanal();
        pvproc();
        pvsynth();

        memcpy(output, &output_ring[index], hop_size * sizeof(float32_t));

    };

};

This is set up with a circular input and output buffer, hop size is the same as the size of the incoming processing blocks, FFT size is the processing block size * 4, such that one phase vocoder block is analyzed and processed per callback.

$\endgroup$

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.