mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-06-18 11:48:46 +02:00
263 lines
8.6 KiB
C++
263 lines
8.6 KiB
C++
#ifndef STK_LENTPITSHIFT_H
|
|
#define STK_LENTPITSHIFT_H
|
|
|
|
#include "Effect.h"
|
|
#include "Delay.h"
|
|
|
|
namespace stk {
|
|
|
|
/***************************************************/
|
|
/*! \class LentPitShift
|
|
\brief Pitch shifter effect class based on the Lent algorithm.
|
|
|
|
This class implements a pitch shifter using pitch
|
|
tracking and sample windowing and shifting.
|
|
|
|
by Francois Germain, 2009.
|
|
*/
|
|
/***************************************************/
|
|
|
|
class LentPitShift : public Effect
|
|
{
|
|
public:
|
|
//! Class constructor.
|
|
LentPitShift( StkFloat periodRatio = 1.0, int tMax = RT_BUFFER_SIZE );
|
|
|
|
~LentPitShift( void ) {
|
|
delete window;
|
|
delete dt;
|
|
delete dpt;
|
|
delete cumDt;
|
|
}
|
|
|
|
//! Reset and clear all internal state.
|
|
void clear( void );
|
|
|
|
//! Set the pitch shift factor (1.0 produces no shift).
|
|
void setShift( StkFloat shift );
|
|
|
|
//! Input one sample to the filter and return one output.
|
|
StkFloat tick( StkFloat input );
|
|
|
|
//! Take a channel of the StkFrames object as inputs to the filter and replace with corresponding outputs.
|
|
/*!
|
|
The StkFrames argument reference is returned. The \c channel
|
|
argument must be less than the number of channels in the
|
|
StkFrames argument (the first channel is specified by 0).
|
|
However, range checking is only performed if _STK_DEBUG_ is
|
|
defined during compilation, in which case an out-of-range value
|
|
will trigger an StkError exception.
|
|
*/
|
|
StkFrames& tick( StkFrames& frames, unsigned int channel = 0 );
|
|
|
|
//! Take a channel of the \c iFrames object as inputs to the filter and write outputs to the \c oFrames object.
|
|
/*!
|
|
The \c iFrames object reference is returned. Each channel
|
|
argument must be less than the number of channels in the
|
|
corresponding StkFrames argument (the first channel is specified
|
|
by 0). However, range checking is only performed if _STK_DEBUG_
|
|
is defined during compilation, in which case an out-of-range value
|
|
will trigger an StkError exception.
|
|
*/
|
|
StkFrames& tick( StkFrames& iFrames, StkFrames &oFrames, unsigned int iChannel = 0, unsigned int oChannel = 0 );
|
|
|
|
protected:
|
|
|
|
//! Apply the effect on the input samples and store it.
|
|
/*!
|
|
The samples stored in the input frame vector are processed
|
|
and the delayed result are stored in the output frame vector.
|
|
*/
|
|
void process( );
|
|
|
|
// Frame storage vectors for process function
|
|
StkFrames inputFrames;
|
|
StkFrames outputFrames;
|
|
int ptrFrames; // writing pointer
|
|
|
|
// Input delay line
|
|
Delay inputLine_;
|
|
int inputPtr;
|
|
|
|
// Output delay line
|
|
Delay outputLine_;
|
|
double outputPtr;
|
|
|
|
// Pitch tracker variables
|
|
unsigned long tMax_; // Maximal period measurable by the pitch tracker.
|
|
// It is also the size of the window used by the pitch tracker and
|
|
// the size of the frames that can be computed by the tick function
|
|
|
|
StkFloat threshold_; // Threshold of detection for the pitch tracker
|
|
unsigned long lastPeriod_; // Result of the last pitch tracking loop
|
|
StkFloat* dt; // Array containing the euclidian distance coefficients
|
|
StkFloat* cumDt; // Array containing the cumulative sum of the coefficients in dt
|
|
StkFloat* dpt; // Array containing the pitch tracking function coefficients
|
|
|
|
// Pitch shifter variables
|
|
StkFloat env[2]; // Coefficients for the linear interpolation when modifying the output samples
|
|
StkFloat* window; // Hamming window used for the input portion extraction
|
|
double periodRatio_; // Ratio of modification of the signal period
|
|
StkFrames zeroFrame; // Frame of tMax_ zero samples
|
|
|
|
|
|
// Coefficient delay line that could be used for a dynamic calculation of the pitch
|
|
//Delay* coeffLine_;
|
|
|
|
};
|
|
|
|
inline void LentPitShift::process()
|
|
{
|
|
StkFloat x_t; // input coefficient
|
|
StkFloat x_t_T; // previous input coefficient at T samples
|
|
StkFloat coeff; // new coefficient for the difference function
|
|
|
|
int alternativePitch = tMax_; // Global minimum storage
|
|
lastPeriod_ = tMax_+1; // Storage of the lowest local minimum under the threshold
|
|
|
|
// Loop variables
|
|
unsigned long delay_;
|
|
unsigned int n;
|
|
|
|
// Initialization of the dt coefficients. Since the
|
|
// frames are of tMax_ length, there is no overlapping
|
|
// between the successive windows where pitch tracking
|
|
// is performed.
|
|
for ( delay_=1; delay_<=tMax_; delay_++ )
|
|
dt[delay_] = 0.;
|
|
|
|
// Calculation of the dt coefficients and update of the input delay line.
|
|
for ( n=0; n<inputFrames.size(); n++ ) {
|
|
x_t = inputLine_.tick( inputFrames[ n ] );
|
|
for ( delay_=1; delay_<= tMax_; delay_++ ) {
|
|
x_t_T = inputLine_.tapOut( delay_ );
|
|
coeff = x_t - x_t_T;
|
|
dt[delay_] += coeff * coeff;
|
|
}
|
|
}
|
|
|
|
// Calculation of the pitch tracking function and test for the minima.
|
|
for ( delay_=1; delay_<=tMax_; delay_++ ) {
|
|
cumDt[delay_] = dt[delay_] + cumDt[delay_-1];
|
|
dpt[delay_] = dt[delay_] * delay_ / cumDt[delay_];
|
|
|
|
// Look for a minimum
|
|
if ( dpt[delay_-1]-dpt[delay_-2] < 0 && dpt[delay_]-dpt[delay_-1] > 0 ) {
|
|
// Check if the minimum is under the threshold
|
|
if ( dpt[delay_-1] < threshold_ ){
|
|
lastPeriod_ = delay_-1;
|
|
// If a minimum is found, we can stop the loop
|
|
break;
|
|
}
|
|
else if ( dpt[alternativePitch] > dpt[delay_-1] )
|
|
// Otherwise we store it if it is the current global minimum
|
|
alternativePitch = delay_-1;
|
|
}
|
|
}
|
|
|
|
// Test for the last period length.
|
|
if ( dpt[delay_]-dpt[delay_-1] < 0 ) {
|
|
if ( dpt[delay_] < threshold_ )
|
|
lastPeriod_ = delay_;
|
|
else if ( dpt[alternativePitch] > dpt[delay_] )
|
|
alternativePitch = delay_;
|
|
}
|
|
|
|
if ( lastPeriod_ == tMax_+1 )
|
|
// No period has been under the threshold so we used the global minimum
|
|
lastPeriod_ = alternativePitch;
|
|
|
|
// We put the new zero output coefficients in the output delay line and
|
|
// we get the previous calculated coefficients
|
|
outputLine_.tick( zeroFrame, outputFrames );
|
|
|
|
// Initialization of the Hamming window used in the algorithm
|
|
for ( int n=-(int)lastPeriod_; n<(int)lastPeriod_; n++ )
|
|
window[n+lastPeriod_] = (1 + cos(PI*n/lastPeriod_)) / 2 ;
|
|
|
|
int M; // Index of reading in the input delay line
|
|
int N; // Index of writing in the output delay line
|
|
double sample; // Temporary storage for the new coefficient
|
|
|
|
// We loop for all the frames of length lastPeriod_ presents between inputPtr and tMax_
|
|
for ( ; inputPtr<(int)(tMax_-lastPeriod_); inputPtr+=lastPeriod_ ) {
|
|
// Test for the decision of compression/expansion
|
|
while ( outputPtr < inputPtr ) {
|
|
// Coefficients for the linear interpolation
|
|
env[1] = fmod( outputPtr + tMax_, 1.0 );
|
|
env[0] = 1.0 - env[1];
|
|
M = tMax_ - inputPtr + lastPeriod_ - 1; // New reading pointer
|
|
N = 2*tMax_ - (unsigned long)floor(outputPtr + tMax_) + lastPeriod_ - 1; // New writing pointer
|
|
for ( unsigned int j=0; j<2*lastPeriod_; j++,M--,N-- ) {
|
|
sample = inputLine_.tapOut(M) * window[j] / 2.;
|
|
// Linear interpolation
|
|
outputLine_.addTo(env[0] * sample, N);
|
|
outputLine_.addTo(env[1] * sample, N-1);
|
|
}
|
|
outputPtr = outputPtr + lastPeriod_ * periodRatio_; // new output pointer
|
|
}
|
|
}
|
|
// Shifting of the pointers waiting for the new frame of length tMax_.
|
|
outputPtr -= tMax_;
|
|
inputPtr -= tMax_;
|
|
}
|
|
|
|
|
|
inline StkFloat LentPitShift :: tick( StkFloat input )
|
|
{
|
|
StkFloat sample;
|
|
|
|
inputFrames[ptrFrames] = input;
|
|
|
|
sample = outputFrames[ptrFrames++];
|
|
|
|
// Check for end condition
|
|
if ( ptrFrames == (int) inputFrames.size() ){
|
|
ptrFrames = 0;
|
|
process( );
|
|
}
|
|
|
|
return sample;
|
|
}
|
|
|
|
inline StkFrames& LentPitShift :: tick( StkFrames& frames, unsigned int channel )
|
|
{
|
|
#if defined(_STK_DEBUG_)
|
|
if ( channel >= frames.channels() ) {
|
|
oStream_ << "LentPitShift::tick(): channel and StkFrames arguments are incompatible!";
|
|
handleError( StkError::FUNCTION_ARGUMENT );
|
|
}
|
|
#endif
|
|
|
|
StkFloat *samples = &frames[channel];
|
|
unsigned int hop = frames.channels();
|
|
for ( unsigned int i=0; i<frames.frames(); i++, samples += hop ) {
|
|
*samples = tick( *samples );
|
|
}
|
|
|
|
return frames;
|
|
}
|
|
|
|
inline StkFrames& LentPitShift :: tick( StkFrames& iFrames, StkFrames& oFrames, unsigned int iChannel, unsigned int oChannel )
|
|
{
|
|
#if defined(_STK_DEBUG_)
|
|
if ( iChannel >= iFrames.channels() || oChannel >= oFrames.channels() ) {
|
|
oStream_ << "LentPitShift::tick(): channel and StkFrames arguments are incompatible!";
|
|
handleError( StkError::FUNCTION_ARGUMENT );
|
|
}
|
|
#endif
|
|
|
|
StkFloat *iSamples = &iFrames[iChannel];
|
|
StkFloat *oSamples = &oFrames[oChannel];
|
|
unsigned int iHop = iFrames.channels(), oHop = oFrames.channels();
|
|
for ( unsigned int i=0; i<iFrames.frames(); i++, iSamples += iHop, oSamples += oHop ) {
|
|
*oSamples = tick( *iSamples );
|
|
}
|
|
|
|
return iFrames;
|
|
}
|
|
|
|
} // stk namespace
|
|
|
|
#endif
|
|
|