/* * Copyright (C) 2002 Michael Zuercher * mzuerche@iastate.edu * * Based on an algorithm by Stephan M. Sprenger, http://www.dspdimension.com * * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, * are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, this list * of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this * list of conditions and the following disclaimer in the documentation and/or * other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ #include "artsmodulessynth.h" #include "stdsynthmodule.h" #include //debug only #include #include #include #define MAX(a,b) (((a) > (b) ? (a) : (b))) #define MIN(a,b) (((a) < (b) ? (a) : (b))) using namespace Arts; class Synth_PITCH_SHIFT_FFT_impl : virtual public Synth_PITCH_SHIFT_FFT_skel, virtual public StdSynthModule { private: struct fftBin { float magnitude; float frequency; float phase; }; bool addPi; /* the attributes (gui changeable) */ /* these can happen on the fly */ float _scaleFactor, _speed; /* these require calling setStreamOpts() */ unsigned int _frameSize, _oversample; /* the buffers */ float *inBuffer, *outBuffer; /* circular buffers (float) */ float *windowedData; /* windowed and unrolled buffer (float) */ fftBin *analysisBuf, *synthesisBuf; /* workspaces (fftBin) */ float *real, *imag; /* place for the FFT to output */ float *windowCoeffient; float *scratch; /* used to store imag IFFT results that we don't need */ float *phaseDiff; /* variables to keep us in the right place of the buffers */ unsigned long bufferOffset; /* stream not yet ready to go until we have prerolled this many windows */ unsigned int initStepsRemaining; /* some commonly used variables */ unsigned long stepSize; double expectedPhaseDiff; double freqPerBin; /* Helper functions */ void inWindow(float windowedData[], const float *inBuffer, const unsigned int basePopPoint); void analysis(fftBin analysisBuf[], const float real[]); void pitchScale(fftBin synthesisBuf[], const fftBin analysisBuf[]); void synthesis(float windowedData[], fftBin synthesisBuf[]); void outWindow(float *outBuffer, const unsigned int basePushPoint, const float windowedData[]); public: /* functions for the plugin interface */ float speed() { return _speed; } void speed(float newSpeed) { _speed = newSpeed; } float scaleFactor() { return _scaleFactor; } void scaleFactor(float newScaleFactor) { _scaleFactor = newScaleFactor; } long frameSize() { return (long)_frameSize; } void frameSize(long newFrameSize) { setStreamOpts(newFrameSize, _oversample); } long oversample() { return (long)_oversample; } void oversample(long newOversample) { setStreamOpts(_frameSize, newOversample); } /* gets called by arts when it needs more data */ void calculateBlock(unsigned long samples); void streamInit() { inBuffer = outBuffer = NULL; analysisBuf = synthesisBuf = NULL; real = imag = NULL; windowedData = NULL; windowCoeffient = NULL; scratch = NULL; phaseDiff = NULL; /* setup default stream parameters */ _speed = 1.0; _scaleFactor = 0.9; setStreamOpts(4096,2); addPi = false; } void streamEnd() { /* clean up buffers */ delete [] inBuffer; delete [] outBuffer; delete [] windowedData; delete [] analysisBuf; delete [] synthesisBuf; delete [] real; delete [] imag; delete [] windowCoeffient; delete [] scratch; delete [] phaseDiff; } void setStreamOpts(unsigned int frameSize, unsigned int oversample) { /* clear any buffers left around */ delete [] inBuffer; delete [] outBuffer; delete [] windowedData; delete [] analysisBuf; delete [] synthesisBuf; delete [] real; delete [] imag; delete [] windowCoeffient; delete [] scratch; delete [] phaseDiff; _frameSize = frameSize; _oversample = oversample; /* create the buffers */ inBuffer = new float[_frameSize]; outBuffer = new float[_frameSize]; windowedData = new float[_frameSize]; analysisBuf = new fftBin[_frameSize]; synthesisBuf = new fftBin[_frameSize]; real = new float[_frameSize]; imag = new float[_frameSize]; windowCoeffient = new float[_frameSize]; scratch = new float[_frameSize]; phaseDiff = new float[_oversample]; /* set up the windowing coeffients */ for(unsigned int sample=0; sample < _frameSize; sample++) { windowCoeffient[sample] = -0.5*cos(2.0*M_PI*(double)sample/(double)_frameSize)+0.5; } /* we should start at the beginning of the buffers */ bufferOffset = 0; /* stream not yet ready to go until we have prerolled this many windows */ initStepsRemaining = _oversample; /* calculate some commonly used variables */ stepSize = _frameSize / _oversample; expectedPhaseDiff = 2*M_PI*(double)stepSize/(double)_frameSize; freqPerBin = samplingRate/(double)_frameSize; for(unsigned int bin=0; bin < _oversample; bin++) { phaseDiff[bin] = bin*expectedPhaseDiff; } memset(outBuffer, 0 ,stepSize * sizeof(float)); /* clear the first part of the output accumulator */ memset(analysisBuf, 0 ,_frameSize * sizeof(fftBin)); memset(synthesisBuf, 0 ,_frameSize * sizeof(fftBin)); } }; void Synth_PITCH_SHIFT_FFT_impl::calculateBlock(unsigned long samples) { unsigned long samplesRemaining = samples; /* pointers to the arts streams */ float *inData = inStream; float *outData = outStream; while(samplesRemaining > 0) { /* either fill the next window, or take all we have */ int samplesThisPass = MIN(samplesRemaining,stepSize - (bufferOffset % stepSize)); /* copy the incoming data into the buffer */ memcpy(inBuffer + bufferOffset, inData, samplesThisPass * sizeof(float)); /* set inData to data we haven't already taken */ inData += samplesThisPass; if((bufferOffset+samplesThisPass) % stepSize) { /* if we still have only a partial window (input is still in the * middle of a window), we can't run it yet, but we have leftover * output we can use */ } else { /* round down the the nearest stepSize, and this window is full */ if(initStepsRemaining > 0) /* we need to have enough old data for a full block too */ { initStepsRemaining--; /* one less step to fill before we can start for real */ } else { unsigned int stepOffset = (bufferOffset + samplesThisPass) - stepSize; /* now we have a complete block (not still filling at init) to add the * new complete window on to */ /* ############################ prepare one stepSize ########################### */ inWindow(windowedData,inBuffer,stepOffset); analysis(analysisBuf,windowedData); pitchScale(synthesisBuf,analysisBuf); synthesis(windowedData,synthesisBuf); outWindow(outBuffer,bufferOffset,windowedData); /* ############################################################################# */ } } memcpy(outData, outBuffer + bufferOffset, samplesThisPass * sizeof(float)); outData += samplesThisPass; memset(outBuffer + bufferOffset, 0 ,samplesThisPass * sizeof(float)); /* clear the output space that we have used */ bufferOffset += samplesThisPass; bufferOffset %= _frameSize; /* wrap if needed before the next frame starts */ samplesRemaining -= samplesThisPass; } } void Synth_PITCH_SHIFT_FFT_impl::inWindow(float windowedData[], const float *inBuffer, const unsigned int basePopPoint) { unsigned int sample; for(sample=0; sample < _frameSize-basePopPoint; sample++) { /* window the data and unroll the buffers */ windowedData[sample] = inBuffer[basePopPoint + sample] * windowCoeffient[sample]; } for(; sample < _frameSize; sample++) { /* window the data and unroll the buffers */ windowedData[sample] = inBuffer[(basePopPoint + sample) - _frameSize] * windowCoeffient[sample]; } } void Synth_PITCH_SHIFT_FFT_impl::analysis(fftBin analysisBuf[], const float windowedData[]) { float lastPhase; float phaseDrift; /* do forward FFT */ /* const_cast because arts_fft_float is silly */ arts_fft_float(_frameSize, 0, const_cast(windowedData), NULL, real, imag); /* the actual analysis loop */ for(unsigned int bin=0; bin < _frameSize/2; bin++) { lastPhase = analysisBuf[bin].phase; /* compute magnitude and phase */ analysisBuf[bin].magnitude = 2.0 * sqrt(real[bin]*real[bin] + imag[bin]*imag[bin]); analysisBuf[bin].phase = atan2(imag[bin],real[bin]); /* compute phase difference and subtract expected phase difference */ phaseDrift = (analysisBuf[bin].phase - lastPhase) - float(phaseDiff[bin % _oversample]); /* we now need to map it into the +/- Pi interval */ while(phaseDrift < -M_PI) phaseDrift += 2*M_PI; while(phaseDrift > M_PI) phaseDrift -= 2*M_PI; /* compute true frequency */ analysisBuf[bin].frequency = (bin + ((phaseDrift * _oversample) / (2*M_PI)))*freqPerBin; //analysisBuf[bin].frequency = (bin + (phaseDrift/(2*M_PI)))*freqPerBin; } } void Synth_PITCH_SHIFT_FFT_impl::pitchScale(fftBin synthesisBuf[], const fftBin analysisBuf[]) { unsigned int sourceBin; for(unsigned int destBin=0; destBin < _frameSize/2; destBin++) { sourceBin = (unsigned int)floor(destBin/_scaleFactor); if(sourceBin < _frameSize/2) { /* new bin overrides existing if magnitude is higher */ //if(analysisBuf[sourceBin].magnitude > synthesisBuf[destBin].magnitude) //{ synthesisBuf[destBin].magnitude = analysisBuf[sourceBin].magnitude; synthesisBuf[destBin].frequency = analysisBuf[sourceBin].frequency * _scaleFactor; //} #if 0 /* fill empty bins with nearest neighbor */ if((synthesisBuf[destBin].frequency == 0.0) && (destBin > 0)) { cerr << "Empty bins\n"; synthesisBuf[destBin].frequency = synthesisBuf[destBin-1].frequency; synthesisBuf[destBin].magnitude = synthesisBuf[destBin-1].magnitude; } #endif } else { synthesisBuf[destBin].magnitude = 0; } } #if 0 for(unsigned int destBin=0; destBin < _frameSize/2; destBin++) { synthesisBuf[destBin].magnitude = analysisBuf[destBin].magnitude; synthesisBuf[destBin].frequency = analysisBuf[destBin].frequency; } #endif } void Synth_PITCH_SHIFT_FFT_impl::synthesis(float windowedData[], fftBin synthesisBuf[]) { double phaseDrift; #if 0 double test; if(addPi == true) test = -M_PI; else test = 0; #endif for(unsigned int bin=0;bin < _frameSize/2; bin++) { /* deviation of this bin's phase from one exactly at the true bin frequency */ //phaseDrift = (((synthesisBuf[bin].frequency - bin*freqPerBin)/ freqPerBin)*(2*M_PI))/_oversample; phaseDrift = (synthesisBuf[bin].frequency / freqPerBin - bin)*(2*M_PI)/_oversample; //phaseDrift = 0; /* calculate the real and imag data */ real[bin] = synthesisBuf[bin].magnitude * cos(synthesisBuf[bin].phase); imag[bin] = synthesisBuf[bin].magnitude * sin(synthesisBuf[bin].phase); /* accumulate current phase for this wave */ synthesisBuf[bin].phase += (phaseDrift + phaseDiff[bin % _oversample]); //synthesisBuf[bin].phase += (phaseDrift + phaseDiff[bin % _oversample] + test); /* keep it so that -M_PI < phase < M_PI */ while(synthesisBuf[bin].phase > M_PI) synthesisBuf[bin].phase -= 2*M_PI; while(synthesisBuf[bin].phase <= -M_PI) synthesisBuf[bin].phase += 2*M_PI; #if 0 //this needs to happen so that that 'strongest wave' picking in pitchScale works //but this isn't really the right place to do it synthesisBuf[bin].magnitude = 0; synthesisBuf[bin].frequency = 0; #endif } /* zero the conjugate numbers */ for(unsigned int i = _frameSize/2; i < _frameSize; i++) { real[i] = 0.0; imag[i] = 0.0; } #if 0 if(addPi == false) addPi = true; else addPi = false; #endif /* do the inverse transform */ arts_fft_float(_frameSize, 1, real, imag, windowedData, scratch); } void Synth_PITCH_SHIFT_FFT_impl::outWindow(float *outBuffer, const unsigned int basePushPoint, const float windowedData[]) { unsigned int sample; for(sample=0; sample < _frameSize - basePushPoint; sample++) { /* window the data and accumulate it back into the circular buffer */ outBuffer[sample+basePushPoint] += 2.0 * windowCoeffient[sample] * windowedData[sample]/(_oversample); } for(; sample < _frameSize; sample++) { /* window the data and accumulate it back into the circular buffer */ outBuffer[(sample+basePushPoint) - _frameSize] += 2.0 * windowCoeffient[sample] * windowedData[sample]/(_oversample); } } REGISTER_IMPLEMENTATION(Synth_PITCH_SHIFT_FFT_impl);