You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
432 lines
13 KiB
432 lines
13 KiB
/*
|
|
* 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 <stdio.h> //debug only
|
|
#include <arts/fft.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
|
|
#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<float *>(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);
|