Skip to content
Permalink
master
Go to file
 
 
Cannot retrieve contributors at this time
executable file 1754 lines (1400 sloc) 50.3 KB
#ifndef GAMMA_OSCILLATOR_H_INC
#define GAMMA_OSCILLATOR_H_INC
/* Gamma - Generic processing library
See COPYRIGHT file for authors and license information */
#include <functional> // std::function
#include "Gamma/gen.h"
#include "Gamma/scl.h"
#include "Gamma/tbl.h"
#include "Gamma/Strategy.h"
#include "Gamma/Domain.h"
#include "Gamma/Types.h"
namespace gam{
/// Periodic waveforms to be used as sound or modulation sources
/// \defgroup Oscillator
/// Fixed-point phase accumulator
/// This is a linear phase accumulator that uses integer (fixed-point)
/// arithmetic. The advantage of using fixed-point versus floating-point is that
/// the phase is wrapped automatically when the integer overflows. As long as
/// we used unsigned integers, this wrapping behavior is well-defined--all
/// results of addition are taken modulo the maximum size of the integer.
/// The frequency resolution is SR/2^32. For SR=44100 Hz, this equates to
/// a frequency resolution of ~10^-5 Hz which has a period of ~27 hours.
///
/// \tparam Sp Phase increment strategy (e.g., phsInc::Loop, phsInc::Oneshot)
/// \tparam Td Domain type
/// \ingroup Oscillator
template <class Sp = phsInc::Loop, class Td = GAM_DEFAULT_DOMAIN>
class Accum : public Td {
public:
/// \param[in] frq Frequency
/// \param[in] phs Phase in [0, 1)
Accum(float frq=440, float phs=0);
void freq(float v); ///< Set frequency
void freqI(uint32_t v); ///< Set fixed-point frequency
void freqAdd(float v); ///< Add value to frequency for 1 sample
void freqMul(float v); ///< Multiply frequency by value for 1 sample
void phase(float v); ///< Set phase from [0, 1) of one period
void phaseMax(); ///< Set phase to maximum value
void phaseAdd(float v); ///< Add value to phase [0, 1)
void period(float v); ///< Set period length
void reset(){ mPhaseI=0; mSp.reset(); } ///< Reset phase accumulator
void finish(){ phaseMax(); } ///< Set phase to end (maximum value)
Sp& phsInc(){ return mSp; } ///< Get phase increment strategy
bool done() const; ///< Returns true if done cycling
bool cycled() const; ///< Returns whether phase cycled on last iteration
float freq() const; ///< Get frequency
uint32_t freqI() const; ///< Get fixed-point frequency
float freqUnit() const; ///< Get frequency in [0, 1)
float period() const; ///< Get period
float phase() const; ///< Get phase in [0, 1)
uint32_t phaseI() const; ///< Get fixed-point phase
/// Iterates accumulator; \returns true on phase wrap, false otherwise
bool operator()();
uint32_t nextPhase(); ///< Increment phase and return updated phase
uint32_t nextPhase(float freqOffset);
uint32_t cycles(); ///< Get 1 to 0 transitions of all accumulator bits
bool cycle();
bool once();
/// Returns sequence of 32 triggers based on a pattern of bits
/// The 5 MSBs of the phase are used to determine how much to right
/// bit-shift the pattern value to check its bit.
/// This means we get a pattern of 32 triggers over one oscillation period.
/// If the oscillation frequency is positive then the pattern bits are
/// scanned from the LSB (bit 0) to the MSB (bit 31). If the frequency is
/// negative, then the bits are scanned (perhaps more intuitively) from the
/// MSB to the LSB.
/// The following shows all 4-bit patterns made from hex values:
///\verbatim
/// hex pattern hex pattern hex pattern hex pattern
/// 0 . . . . 4 . / . . 8 / . . . c / / . .
/// 1 . . . / 5 . / . / 9 / . . / d / / . /
/// 2 . . / . 6 . / / . a / . / . e / / / .
/// 3 . . / / 7 . / / / b / . / / f / / / / \endverbatim
bool seq(uint32_t pattern);
void onDomainChange(double r);
//protected:
private:
float mFreq; // Current frequency
double mFreqToInt;
uint32_t mPhaseI; // Current fixed-point phase in [0, 2^32)
uint32_t mFreqI; // Current fixed-point frequency
Sp mSp;
uint32_t mapFreq(float v) const;
};
#define ACCUM_INHERIT\
using Accum<Sp,Td>::phaseI;\
using Accum<Sp,Td>::freqI;\
using Accum<Sp,Td>::nextPhase;
/// Linear sweep in interval [0,1)
/// \tparam Sp Phase increment strategy (e.g., phsInc::Loop, phsInc::Oneshot)
/// \tparam Td Domain type
///\ingroup Oscillator
template <class Sp = phsInc::Loop, class Td = GAM_DEFAULT_DOMAIN>
class Sweep : public Accum<Sp, Td> {
public:
/// \param[in] frq Frequency
/// \param[in] phs Phase in [0,1)
Sweep(float frq=440, float phs=0)
: Accum<Sp, Td>(frq, phs){}
float operator()(){
float r = this->phase();
this->nextPhase();
return r;
}
};
/// Floating-point phase accumulator with output in [-A, A)
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
///\ingroup Oscillator
template <class Tv = gam::real, class Td = GAM_DEFAULT_DOMAIN>
class AccumPhase : public Td{
public:
/// \param[in] frq Frequency
/// \param[in] phs Phase in [0, 1)
/// \param[in] radius Output range in [-radius, radius)
AccumPhase(Tv frq=440, Tv phs=0, Tv rad=M_PI);
/// Generate next sample. Stored phase is post-incremented.
Tv nextPhase();
/// Generate next sample with a frequency offset
Tv nextPhase(Tv frqOffset);
void freq(Tv v); ///< Set frequency
void freqAdd(Tv v); ///< Add value to frequency for 1 sample
void freqMul(Tv v); ///< Multiply frequency by value for 1 sample
void period(Tv v); ///< Set period length
void phase(Tv v); ///< Set phase from [0, 1) of one period
void phaseAdd(Tv v); ///< Add value to unit phase
void radius(Tv v); ///< Set output range to be in [-radius, radius)
Tv freq() const; ///< Get frequency
Tv freqUnit() const; ///< Get frequency in [0, 1)
Tv period() const; ///< Get period
Tv phase() const ; ///< Get normalized phase in [0, 1)
Tv radius() const; ///< Get radius of output
void onDomainChange(double r);
void print(FILE * fp = stdout, const char * append = "\n");
protected:
Tv mInc, mPhase, mRadius;
Tv mFreqToInc;
void recache();
Tv mapFreq(Tv v) const;
Tv mapPhase(Tv v) const;
Tv nextPhaseUsing(Tv frq);
};
/// Tabulated function oscillator
/// This generator produces a periodic signal by reading values from a table.
/// Its advantage over other types of waveform generators is that it can
/// produce arbitrary periodic waveforms with a fixed computational cost. Its
/// main weakness is lack of parametric control over the waveform timbre.
/// This generator is named after the generator of the same name in the MUSIC
/// series of compilers. [Mathews, M. (1969). The Technology of Computer Music.
/// The M.I.T. Press, Boston.]
///
/// \tparam Tv Table element type
/// \tparam Si Interpolation strategy
/// \tparam Sp Phase increment strategy
/// \tparam Td Domain type
/// \ingroup Oscillator Envelope
/// \sa Other ways to synthesize sine waves: TableSine, CSine, LFO, Sine, SineR
/// \sa Functions for building waveforms in tables with additive synthesis:
/// addSine, addSines, addSinesPow, addWave
template<
class Tv = gam::real,
template<class> class Si = ipl::Linear,
class Sp = phsInc::Loop,
class Td = GAM_DEFAULT_DOMAIN
>
class Osc : public Accum<Sp,Td>, public ArrayPow2<Tv>{
public:
/// Default constructor does not allocate table memory
Osc()
: ArrayPow2<Tv>(defaultArray<Tv>(), 1)
{}
/// Constructor that allocates an internal table
/// \param[in] frq Frequency
/// \param[in] phs Phase in [0, 1)
/// \param[in] size Size of table (actual number is power of 2 ceiling)
Osc(float frq, float phs=0, uint32_t size=512)
: Accum<Sp,Td>(frq, phs), ArrayPow2<Tv>(size, Tv())
{}
/// Constructor that references an external table
/// \param[in] frq Frequency
/// \param[in] phs Phase in [0, 1)
/// \param[in] src A table to use as a reference
Osc(float frq, float phs, ArrayPow2<Tv>& src)
: Accum<Sp,Td>(frq, phs), ArrayPow2<Tv>(src.elems(), src.size())
{}
/// Generate next sample
Tv operator()(){
Tv r = val();
this->nextPhase();
return r;
}
/// Get current value
Tv val() const { return atPhaseI(this->phaseI()); }
/// Get table value at fixed-point phase
Tv atPhaseI(uint32_t v) const { return mIpol(table(), v); }
/// Add sine to table
/// \param[in] cycles number of cycles
/// \param[in] amp amplitude
/// \param[in] phs unit phase, [0, 1)
Osc& addSine(double cycles, double amp=1, double phs=0){
double f = cycles/this->size();
for(unsigned i=0; i<this->size(); ++i){
double p = (f*i + phs)*M_2PI;
(*this)[i] += sin(p) * amp;
}
return *this;
}
/// Get reference to table
ArrayPow2<Tv>& table(){ return *this; }
const ArrayPow2<Tv>& table() const { return *this; }
protected:
Si<Tv> mIpol;
};
/// Complex sinusoid oscillator
/// This oscillator outputs a (decaying) complex sinusoid whose real and
/// imaginary components correspond to a cosine and sine wave, respectively.
/// The complex sinusoid is generated by recursively multiplying two complex
/// numbers and thus requires only four multiplications and two additions per
/// iteration. One disadvantage of this oscillator is that it is expensive to
/// change its frequency. Another is that the amplitude may drift over time.
/// This, however, can be mostly resolved by using double precision.
///
/// Reference: Mathews, M., Smith, J. 2003. "Methods for synthesizing very high
/// Q parametrically well behaved two pole filters."
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
/// \sa Osc, TableSine, CSine, LFO, Sine, SineR
template<class Tv = gam::real, class Td = GAM_DEFAULT_DOMAIN>
class CSine : public Td{
public:
typedef Complex<Tv> complex;
/// \param[in] frq Frequency
/// \param[in] amp Amplitude
/// \param[in] dcy -60 dB decay length (negative for no decay)
/// \param[in] phs Phase in [0, 1)
CSine(Tv frq=440, Tv amp=1, Tv dcy=-1, Tv phs=0);
complex val; ///< Current value
complex operator()(); ///< Generate next sample
void amp(Tv v); ///< Set amplitude
void decay(Tv v); ///< Set -60 dB decay length (negative for no decay)
void freq(Tv v); ///< Set frequency
void freq(complex v){ mInc=v; }
void phase(Tv v); ///< Set unit phase, in [0,1]
void reset(); ///< Reset amplitude and set phase to 0
void set(Tv frq, Tv phs, Tv amp, Tv dcy);
Tv amp() const {return mAmp;} ///< Get amplitude
Tv decay() const {return mDcy60;} ///< Get decay length
Tv freq() const {return mFreq;} ///< Get frequency
void onDomainChange(double r);
protected:
Tv mAmp, mFreq, mDcy60;
Tv mDcy; // phasor amp
complex mInc; // rotation increment
};
/// Computed sine wave oscillator.
/// This oscillator uses a polynomial approximation to compute sine values.
/// Computation time is about as much as a linearly-interpolating table lookup.
/// In addition, polynomial approximations are often more spectrally pure than
/// table lookup methods since the distortion arises as harmonics.
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
/// \sa Osc, TableSine, CSine, LFO, SineR
template<class Tv = gam::real, class Td = GAM_DEFAULT_DOMAIN>
class Sine : public AccumPhase<Tv,Td> {
public:
/// \param[in] frq Frequency
/// \param[in] phs Phase in [0, 1)
Sine(Tv frq=440, Tv phs=0) : AccumPhase<Tv,Td>(frq, phs, Tv(1)){}
/// Generate next sample with a frequency offset
Tv operator()(Tv frqOffset = Tv(0)){
//return ::sin(this->nextPhase(frqOffset)/*M_PI*/); // 260%
//return scl::sinT7(this->nextPhase(frqOffset)/*M_PI*/); // 140%
return scl::sinP9(this->nextPhase(frqOffset)); // 110%
//return scl::sinP7(this->nextPhase(frqOffset)); // 100%
//return scl::sinFast(this->nextPhase(frqOffset)/*M_PI*/); // 95%
}
};
/// Sine oscillator based on an efficient recursion equation.
/// This oscillator is based on a recursion equation requiring only one
/// multiply and add per sample computation. While computation time and quality
/// are near ideal, parameter updates are expensive and 64-bit precision is
/// required to prevent growing or decaying in amplitude over time. This
/// generator is ideal in situations where a stationary sinusoid is all that is
/// required, e.g. a grain or modulator.
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
/// \sa Osc, TableSine, CSine, LFO, Sine, SineRs (Synthesizes multiple sines)
template <class Tv = double, class Td = GAM_DEFAULT_DOMAIN>
class SineR : public gen::RSin<Tv>, Td{
public:
/// \param[in] frq Frequency
/// \param[in] amp Amplitude
/// \param[in] phs Phase in [0, 1)
SineR(Tv frq=440, Tv amp=1, Tv phs=0){ set(frq, amp, phs); }
/// Get frequency
Tv freq() const { return Base::freq() * Td::spu(); }
/// Set amplitude and phase
void ampPhase(Tv a=1, Tv p=0){ set(freq(), a, p); }
/// Set frequency
void freq(Tv v){ Base::freq(v*Td::ups()); }
/// Set period
void period(Tv v){ Base::freq(Td::ups()/v); }
/// Set all control parameters
void set(Tv frq, Tv amp, Tv phs=0){ Base::set(frq*Td::ups(), phs, amp); }
void onDomainChange(double ratio){ Base::freq(Base::freq()/ratio); }
private:
typedef gen::RSin<Tv> Base;
};
/// Multiple SineRs
/// This is a dynamically-sized bank of SineR objects.
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
/// \sa SineR (synthesizes a single sine)
template <class Tv = double, class Td = GAM_DEFAULT_DOMAIN>
class SineRs : public Array<SineR<Tv, Domain1> >, Td{
public:
SineRs(){}
/// \param[in] num Number of resonators
SineRs(unsigned num): Base(num){ onDomainChange(1); }
/// Generate next sum of all oscillators
Tv operator()(){
Tv r = Tv(0);
for(unsigned i=0; i<this->size(); ++i) r += (*this)[i]();
return r;
}
/// Get last output of oscillator i
Tv last(unsigned i) const { return (*this)[i].val; }
/// Set all control parameters of oscillator i
void set(unsigned i, Tv frq, Tv amp=1, Tv phs=0){
(*this)[i].set(frq*Td::ups(), amp, phs);
}
void onDomainChange(double ratio){
for(unsigned i=0; i<this->size(); ++i){
(*this)[i].onDomainChange(ratio);
}
}
private:
typedef Array<SineR<Tv, Domain1> > Base;
};
/// Damped sine oscillator based on an efficient recursion equation.
/// This oscillator is similar to SineR, however, it has an extra multiply
/// in its sample generation to allow the oscillator to decay.
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
/// \sa SineR, SineDs
template <class Tv = double, class Td = GAM_DEFAULT_DOMAIN>
class SineD : public gen::RSin2<Tv>, Td{
public:
/// \param[in] frq Frequency
/// \param[in] amp Amplitude
/// \param[in] dcy T60 decay length (negative == no decay)
/// \param[in] phs Phase in [0, 1)
SineD(Tv frq=440, Tv amp=1, Tv dcy=-1, Tv phs=0)
{ set(frq, amp, dcy, phs); }
/// Get frequency
Tv freq() const { return Base::freq() * Td::spu(); }
/// Set amplitude and phase
void ampPhase(Tv a=1, Tv p=0){ set(freq(), a, Base::decay(), p); }
/// Set decay length
void decay(Tv v){ Base::decay(decayFactor(v)); }
/// Set frequency
void freq(Tv v){ Base::freq(v*Td::ups()); }
/// Set period
void period(Tv v){ Base::freq(Td::ups()/v); }
/// Set all control parameters
/// \param[in] frq Frequency
/// \param[in] amp Amplitude
/// \param[in] dcy T60 decay length (negative == no decay)
/// \param[in] phs Phase in [0, 1)
void set(Tv frq, Tv amp, Tv dcy, Tv phs=0){
Base::set(
frq * Td::ups(),
phs,
decayFactor(dcy),
amp
);
}
void onDomainChange(double ratio){
Base::freq(Base::freq()/ratio);
//printf("%g\n", Base::decay());
//printf("%g %g %g\n", Base::decay(), ratio, ::pow(Base::decay(), 1./ratio));
// double radius60(double dcy, double ups){ return ::exp(M_LN001/dcy * ups); }
// same as (0.001)^(ups/dcy)
Base::decay(::pow(Base::decay(), 1./ratio));
}
private:
typedef gen::RSin2<Tv> Base;
Tv decayFactor(Tv length){
return length > Tv(0) ? Tv(scl::radius60(length, Td::ups())) : Tv(1);
}
};
/// Multiple SineDs
/// This is a dynamically-sized bank of SineD objects.
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
/// \sa SineD
template <class Tv = double, class Td = GAM_DEFAULT_DOMAIN>
class SineDs : public Array<SineD<Tv, Domain1> >, Td{
public:
SineDs(){}
/// \param[in] num Number of resonators
SineDs(unsigned num): Base(num){
onDomainChange(1);
for(unsigned i=0; i<num; ++i) set(i, 0,0,0);
}
/// Generate next sum of all oscillators
Tv operator()(){
Tv r=Tv(0);
for(unsigned j=0; j<this->size(); ++j) r+=(*this)[j]();
return r;
}
/// Get last output of oscillator i
Tv last(unsigned i) const { return (*this)[i].val; }
/// Set all control parameters of oscillator i
/// \param[in] i index of oscillator
/// \param[in] frq Frequency
/// \param[in] amp Amplitude
/// \param[in] dcy T60 decay length (negative == no decay)
/// \param[in] phs Phase in [0, 1)
void set(unsigned i, Tv frq, Tv amp, Tv dcy, Tv phs=0){
(*this)[i].set(frq*Td::ups(), amp, dcy*Td::spu(), phs);
}
void onDomainChange(double ratio){
for(unsigned i=0; i<this->size(); ++i){
(*this)[i].onDomainChange(ratio);
}
}
private:
typedef Array<SineD<Tv, Domain1> > Base;
};
/// Swept sinusoid with Gaussian envelope
/// This generates a sinusoid with a linear frequency sweep and Gaussian
/// envelope. Only two complex multiplies are required per sample.
/// Single-precision should be used only for very short envelopes due to
/// accumulation error.
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
template<class Tv = double, class Td = GAM_DEFAULT_DOMAIN>
class Chirplet : public Td{
public:
typedef Complex<Tv> complex;
/// \param[in] frq1 Start frequency
/// \param[in] frq2 End frequency
/// \param[in] amp Amplitude
/// \param[in] len Length
/// \param[in] phs Phase in [0, 1)
Chirplet(Tv frq1=440, Tv frq2=880, Tv amp=1, Tv len=1, Tv phs=0){
freq(frq1, frq2);
this->amp(amp);
length(len);
}
/// Set start and end frequencies
Chirplet& freq(double start, double end){
mFreq1 = start;
mFreq2 = end;
mRGauss.mul1.arg(freqToRad(start));
mRGauss.mul2.arg(freqToRad(end-start)/mRGauss.length());
//printf("(%g, %g), (%g, %g)\n", mRGauss.mul2.mag(), mRGauss.mul2.arg()/2/M_PI, mRGauss.mul1.mag(), mRGauss.mul1.arg()/2/M_PI);
return *this;
}
/// Set frequency
Chirplet& freq(double v){
mFreq1 = v;
mFreq2 = v;
mRGauss.mul1.arg(freqToRad(v));
mRGauss.mul2.arg(0);
return *this;
}
/// Set envelope length
Chirplet& length(double v, const complex& offset=complex(0.01)){
mLength = v;
mRGauss.set(v*Td::spu(), offset);
return *this;
}
/// Set amplitude
Chirplet& amp(double v){
mRGauss.mul1.mag(v);
return *this;
}
/// Get next sample
complex operator()(){
return mRGauss();
}
/// Reset envelope
Chirplet& reset(){ return length(mLength); }
/// Returns true if envelope done
bool done(float thresh=0.001) const { return mRGauss.done(); }
/// Get length
Tv length() const { return mLength; }
void onDomainChange(double r){
//Tv A = mRGauss.mul1.mag();
length(mLength);
freq(mFreq1, mFreq2);
//amp(A);
}
protected:
gen::RGauss<complex> mRGauss;
Tv mFreq1, mFreq2, mLength;
double freqToRad(double v){ return v * 2*M_PI*Td::ups(); }
};
/// Low-frequency oscillator
/// This object generates various waveform types by mapping the output of a
/// an accumulator through mathematical functions. The resulting waveforms are
/// non-band-limited.
///
/// \tparam Sp Phase increment strategy (e.g., phsInc::Loop, phsInc::Oneshot)
/// \tparam Td Domain type
/// \ingroup Oscillator
/// \sa Osc, TableSine, CSine, Sine, SineR
template <class Sp = phsInc::Loop, class Td = GAM_DEFAULT_DOMAIN>
class LFO : public Accum<Sp,Td>{
public:
LFO();
/// \param[in] frq Frequency
/// \param[in] phase Phase in [0, 1)
/// \param[in] mod Modifier amount in [0, 1)
LFO(double frq, double phase=0, double mod=0.5);
/// Set frequency, phase and modifier amount
LFO& set(float f, float p, float m);
LFO& mod(double n); ///< Set modifier from unit value
LFO& modI(uint32_t v); ///< Set modifier from integer
/// Get modifier value
uint32_t modI() const { return mMod; }
double mod() const { return mMod / 4294967296.; }
float cos(); ///< Cosine-like wave based on 3rd order polynomial
float down(); ///< Downward ramp (1 to -1); S1 Clausen function
float even3(); ///< Even harmonic sine-like wave (3rd order); S3 Clausen function
float even5(); ///< Even harmonic sine-like wave (5th order)
float imp(); ///< Impulse train with aliasing reduction
float line2(); ///< 2-segment line; 'mod' changes wave from down to tri to up
float para(); ///< Parabolic wave; C2 Clausen function
float pulse(); ///< Pulse (up + down). 'mod' controls pulse width
float pulseRange(); ///< Pulse (up + down). 'mod' controls pulse width. amplitude doesn't change with mod.
float saw(){ return down(); } ///< Saw wave (downward ramp)
float sinPara(); ///< Sine-like wave constructed from parabolas; integral of triangle
float stair(); ///< Stair (square + square). 'mod' controls pulse width
float sqr(); ///< Square (-1 to 1)
float tri(); ///< Triangle (starts at 1 goes down to -1 then up to 1)
float up(); ///< Upward ramp
float up2(); ///< Dual upward ramp (up + up). 'mod' controls pulse width.
float S1(); ///< S1 Clausen function; sum_k sin(kt)/k^1
float C2(); ///< C2 Clausen function; sum_k cos(kt)/k^2
float S3(); ///< S3 Clausen function; sum_k sin(kt)/k^3
float C4(); ///< C4 Clausen function; sum_k cos(kt)/k^4
float S5(); ///< S5 Clausen function; sum_k sin(kt)/k^5
float cosU(); ///< Unipolar cosine-like wave based on 3rd order polynomial
float downU(); ///< Unipolar downward ramp
float hann(); ///< Hann-like window based on 3rd order polynomial
float impU(); ///< Unipolar impulse train
float line2U(); ///< Unipolar line2
float paraU(); ///< Unipolar parabolic wave
float pulseU(); ///< Unipolar pulse
float stairU(); ///< Unipolar stair
float sqrU(); ///< Unipolar square
float triU(); ///< Unipolar triangle (starts at 1 going down then up)
float upU(); ///< Unipolar upward ramp
float up2U(); ///< Unipolar upward ramp2
float patU();
float patU(uint32_t mul);
float sineT9();
float sineP9();
ACCUM_INHERIT
private:
typedef Accum<Sp,Td> Base;
uint32_t mMod; // Modifier parameter
};
/// Differenced wave oscillator
/// This oscillator uses a difference between two waveforms to reduce aliasing.
/// Computation time is higher than that of an LFO unit generator, however,
/// the output exhibits far less aliasing at high frequencies.
template <class Sp = phsInc::Loop, class Td = GAM_DEFAULT_DOMAIN>
class DWO : public Accum<Sp,Td>{
public:
DWO();
/// \param[in] frq Frequency
/// \param[in] phase Phase in [0, 1)
/// \param[in] mod Modifier amount in [0, 1)
DWO(float frq, float phase=0, float mod=0.5);
DWO& mod(double n); ///< Set modifier from unit value
DWO& modI(uint32_t v); ///< Set modifier from integer
/// Get modifier value
uint32_t modI() const { return mMod; }
double mod() const { return mMod / 4294967296.; }
/// Set frequency
void freq(float v);
void period(float v){ freq(1.f/v); }
float up(); ///< Upward saw
float down(); ///< Downward saw
float saw(){ return down(); } ///< Saw
float sqr(); ///< Square
float para(); ///< Parabolic
float tri(); ///< Triangle
float pulse(); ///< Pulse
float imp(); ///< Impulse train
void onDomainChange(double r);
private:
typedef Accum<Sp,Td> Base;
uint32_t mMod; // Modifier parameter
float mGain;
//float mPrev;
//float diff(float v);
};
/// Sum of cosine waves
/// This produces a finite sum of harmonic, equi-amplitude cosine waves that
/// approach the shape of a periodic impulse train. The algorithm uses a
/// closed-form solution to the sum thus its computational complexity is always
/// O(1) (constant) regardless of the number of harmonics.
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
template<class Tv = gam::real, class Td = GAM_DEFAULT_DOMAIN>
class Buzz : public AccumPhase<Tv,Td> {
public:
/// \param[in] frq Frequency
/// \param[in] phase Phase in [0, 1)
/// \param[in] harmonics Number of harmonics
Buzz(Tv frq=440, Tv phase=0, Tv harmonics=8);
virtual ~Buzz(){}
void antialias(); ///< Adjust number of harmonics to prevent aliasing
void harmonics(Tv num); ///< Set number of harmonics
void harmonicsMax(); ///< Set number of harmonics to fill Nyquist range
void normalize(bool v); ///< Whether to normalize amplitude
Tv operator()(); ///< Returns next sample of all harmonic impulse
Tv odd(); ///< Returns next sample of odd harmonic impulse
Tv saw(Tv intg=0.999); ///< Returns next sample of saw waveform
Tv square(Tv intg=0.999); ///< Returns next sample of square waveform
Tv maxHarmonics() const; ///< Get number of harmonics below Nyquist based on current settings
void onDomainChange(double r);
protected:
Tv mAmp; // amplitude normalization factor
Tv mN; // actual number of harmonics
Tv mNDesired; // desired number of harmonics
Tv mNFrac;
Tv mSPU_2; // cached local
Tv mPrev; // previous output for integration
bool mNormalize;
void setAmp();
private: typedef AccumPhase<Tv,Td> Base;
};
/// Band-limited impulse train
/// This produces a Fourier representation of an impulse train where the number
/// of harmonics is adjusted automatically to prevent aliasing.
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
/// \sa Buzz
template <class Tv = gam::real, class Td = GAM_DEFAULT_DOMAIN>
struct Impulse : public Buzz<Tv,Td>{
public:
/// \param[in] frq Frequency
/// \param[in] phs Phase, in [0, 1)
Impulse(Tv frq=440, Tv phs=0): Base(frq, phs){ onDomainChange(1); }
/// Set frequency
void freq(Tv v){ Base::freq(v); Base::harmonicsMax(); }
void onDomainChange(double r){
Base::onDomainChange(r);
freq(AccumPhase<Tv,Td>::freq());
}
using Buzz<Tv,Td>::freq; // needed for getter
private: typedef Buzz<Tv,Td> Base;
};
/// Band-limited saw wave
/// This produces a Fourier representation of a saw wave where the number of
/// harmonics is adjusted automatically to prevent aliasing.
/// Due to numerical issues, this generator should not be used for producing
/// very low frequency modulation signals. For that purpose, it is better to use
/// the LFO class.
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
template <class Tv = gam::real, class Td = GAM_DEFAULT_DOMAIN>
struct Saw : public Impulse<Tv,Td> {
/// \param[in] frq Frequency
/// \param[in] phs Phase, in [0, 1)
Saw(Tv frq=440, Tv phs=0): Impulse<Tv, Td>(frq, phs){}
/// Generate next sample
/// \param[in] itg Leaky integration factor
///
Tv operator()(Tv itg=0.999){ return Impulse<Tv,Td>::saw(itg); }
};
/// Band-limited square wave
/// This produces a Fourier representation of a square wave where the number of
/// harmonics is adjusted automatically to prevent aliasing.
/// Due to numerical issues, this generator should not be used for producing
/// very low frequency modulation signals. For that purpose, it is better to use
/// the LFO class.
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
template <class Tv = gam::real, class Td = GAM_DEFAULT_DOMAIN>
struct Square : public Impulse<Tv,Td> {
/// \param[in] frq Frequency
/// \param[in] phs Phase, in [0, 1)
Square(Tv frq=440, Tv phs=0) : Impulse<Tv,Td>(frq, phs){}
/// Generate next sample
/// \param[in] itg Leaky integration factor
///
Tv operator()(Tv itg=0.999){ return Impulse<Tv,Td>::square(itg); }
};
/// Discrete summation formula (DSF) oscillator
/// This produces a finite sum of harmonics whose amplitudes follow a geometric
/// series. The amplitude of harmonic i is ar^i where 'ar' is called the
/// amplitude ratio. The frequency of harmonic i is (i * fr + 1) where 'fr' is
/// called the frequency ratio. Harmonics run from i=0 (the fundamental) to
/// the maximum specified harmonic.
///
/// \tparam Tv Value (sample) type
/// \tparam Td Domain type
/// \ingroup Oscillator
template<class Tv = gam::real, class Td = GAM_DEFAULT_DOMAIN>
class DSF : public AccumPhase<Tv,Td> {
public:
/// \param[in] frq Frequency
/// \param[in] freqRatio Frequency ratio of partials
/// \param[in] ampRatio Amplitude ratio of partials
/// \param[in] harmonics Number of harmonics
DSF(Tv frq=440, Tv freqRatio=1, Tv ampRatio=0.5, Tv harmonics=8);
Tv operator()(); ///< Generate next sample
void ampRatio(Tv v); ///< Set amplitude ratio of partials
void antialias(); ///< Adjust harmonics so partials do not alias
void freq(Tv v); ///< Set frequency of fundamental
void freqRatio(Tv v); ///< Set frequency ratio of partials
void harmonics(Tv v); ///< Set number of harmonics
void harmonicsMax(); ///< Set number of harmonics to fill Nyquist range
Tv ampRatio() const; ///< Get amplitude ratio
Tv freqRatio() const; ///< Get frequency ratio
Tv harmonics() const; ///< Get current number of harmonics
Tv maxHarmonics() const; ///< Get maximum number of harmonics for current settings
void onDomainChange(double r);
protected:
typedef AccumPhase<Tv,Td> Base;
Tv mN, mNDesired; // actual and desired # harmonics
Tv mFreqRatio; // frequency ratio
Tv mA; // Partial amplitude ratio
Tv mBeta, mBetaInc; // "detune" accumulator
Tv mAPow, mASqP1; // cached vars
void updateAPow();
void updateBetaInc();
};
/// Upsamples and interpolates a signal
/// This interpolates between the samples of a lower-rate signal. Use cases
/// include a sample-and-hold and a low-frequency noise generator.
///
/// \tparam Gen Signal generator
/// \tparam Si Sequence interpolation strategy
/// \tparam Sp Phase increment strategy
/// \tparam Td Domain type
/// \ingroup Oscillator
template <
class Gen = gen::Default<>,
template <typename> class Si = iplSeq::Linear,
class Sp = phsInc::Loop,
class Td = GAM_DEFAULT_DOMAIN
>
class Upsample : public Accum<Sp,Td>{
public:
typedef typename Gen::value_type value_type;
/// \param[in] frq Frequency of lower-rate signal
Upsample(float frq=440)
: Accum<Sp,Td>(frq)
{
this->finish();
mIpl.push(0);
}
// Upsample an external generator
template <class SampleGen>
value_type operator()(SampleGen& gen){
if(this->cycle()) mIpl.push(gen());
return mIpl(this->phase());
}
// Upsample an external function
value_type operator()(const std::function<value_type(void)>& onCycle){
if(this->cycle()) mIpl.push(onCycle());
return mIpl(this->phase());
}
// Sample and interpolate a signal
value_type operator()(value_type in){
return (*this)(gen::Val<value_type>(in));
}
// Upsample internal generator
value_type operator()(){
return (*this)(mGen);
}
/// Get internal generator
Gen& gen(){ return mGen; }
private:
Gen mGen;
Si<value_type> mIpl;
};
// Implementation_______________________________________________________________
namespace{
// Convert unit floating-point to fixed-point integer.
// 32-bit float is good enough here since [0.f, 1.f) uses 29 bits.
inline uint32_t mapFI(float v){
//return scl::unitToUInt(v);
//return (uint32_t)(v * 4294967296.);
return castIntRound(v * 4294967296.);
}
// Convert fixed-point integer to unit floating-point.
inline double mapIF(uint32_t v){
return v/4294967296.;
//return uintToUnit<float>(v); // not enough precision
}
};
//---- Accum
template<class Sp, class Td>
Accum<Sp,Td>::Accum(float f, float p)
: mFreq(f), mFreqToInt(4294967296.), mFreqI(0)
{
onDomainChange(1);
phase(p);
}
template<class Sp, class Td>
inline uint32_t Accum<Sp,Td>::mapFreq(float v) const {
//return mapFI(v * Td::ups());
return castIntRound(v * mFreqToInt);
}
template<class Sp, class Td>
void Accum<Sp,Td>::onDomainChange(double /*r*/){ //printf("Accum: onDomainChange (%p)\n", this);
mFreqToInt = 4294967296. / Td::spu();
freq(mFreq);
}
template<class Sp, class Td>
inline void Accum<Sp,Td>::freq(float v){
mFreq = v;
mFreqI= mapFreq(v);
}
template<class Sp, class Td>
inline void Accum<Sp,Td>::freqI(uint32_t v){
mFreqI= v;
mFreq = mapIF(v) * Td::spu();
}
template<class Sp, class Td> inline void Accum<Sp,Td>::freqAdd(float v){ phaseAdd(v*Td::ups()); }
template<class Sp, class Td> inline void Accum<Sp,Td>::freqMul(float v){ freqAdd((v-1.f)*freq()); }
template<class Sp, class Td> inline void Accum<Sp,Td>::period(float v){ freq(1.f/v); }
template<class Sp, class Td> inline void Accum<Sp,Td>::phase(float v){ mPhaseI = mapFI(v); }
template<class Sp, class Td> inline void Accum<Sp,Td>::phaseAdd(float v){ mSp(mPhaseI, mapFI(v)); }
template<class Sp, class Td> void Accum<Sp,Td>::phaseMax(){ mPhaseI = 0xffffffff; }
template<class Sp, class Td>
inline bool Accum<Sp,Td>::done() const { return mSp.done(phaseI()); }
template<class Sp, class Td>
inline bool Accum<Sp,Td>::cycled() const {
uint32_t prev = phaseI();
Sp temp = mSp;
temp(prev, ~freqI());
return ((~phaseI() & prev) & 0x80000000) != 0;
}
template<class Sp, class Td> inline float Accum<Sp,Td>::freq() const { return mFreq; }
template<class Sp, class Td> inline uint32_t Accum<Sp,Td>::freqI() const { return mFreqI; }
template<class Sp, class Td> inline float Accum<Sp,Td>::freqUnit() const { return float(mapIF(mFreqI)); }
template<class Sp, class Td> inline float Accum<Sp,Td>::period() const { return 1.f/freq(); }
template<class Sp, class Td> inline float Accum<Sp,Td>::phase() const { return float(mapIF(mPhaseI)); }
template<class Sp, class Td> inline uint32_t Accum<Sp,Td>::phaseI() const { return mPhaseI; }
template<class Sp, class Td> inline uint32_t Accum<Sp,Td>::nextPhase(float frqOffset){
uint32_t p = mPhaseI;
mSp(mPhaseI, mFreqI + mapFreq(frqOffset)); // apply phase inc strategy
return p;
}
template<class Sp, class Td> inline uint32_t Accum<Sp,Td>::nextPhase(){
uint32_t p = mPhaseI;
mSp(mPhaseI, mFreqI); // apply phase inc strategy
return p;
}
template<class Sp, class Td> inline bool Accum<Sp,Td>::operator()(){ return cycle(); }
template<class Sp, class Td> inline bool Accum<Sp,Td>::cycle(){ return (cycles() & 0x80000000) != 0; }
//template<class Sp, class Td> inline uint32_t Accum<Sp,Td>::cycle(uint32_t mask){
// return cycles() & mask;
//}
template<class Sp, class Td> inline uint32_t Accum<Sp,Td>::cycles(){
uint32_t prev = phaseI();
nextPhase();
return ~phaseI() & prev;
}
template<class Sp, class Td> inline bool Accum<Sp,Td>::once(){
uint32_t prev = phaseI();
bool c = cycle();
if(c) mPhaseI = prev;
return c;
}
template<class Sp, class Td> inline bool Accum<Sp,Td>::seq(uint32_t pat){
uint32_t prev = phaseI();
nextPhase();
// Did any of the 5 MSBs change?
if((phaseI() ^ prev) & 0xf8000000){
return (pat >> (phaseI()>>27)) & 0x1;
}
return false;
}
//---- AccumPhase
template<class Tv, class Td>
AccumPhase<Tv, Td>::AccumPhase(Tv f, Tv p, Tv r)
: mInc(f), mRadius(r), mFreqToInc(1)
{
onDomainChange(1);
this->phase(p);
}
template<class Tv, class Td> inline Tv AccumPhase<Tv, Td>::mapFreq(Tv v) const {
return v*mFreqToInc;
}
template<class Tv, class Td> inline Tv AccumPhase<Tv, Td>::mapPhase(Tv v) const {
return v*Tv(2)*mRadius;
}
template<class Tv, class Td>
inline Tv AccumPhase<Tv, Td>::nextPhaseUsing(Tv inc){
mPhase = scl::wrap(mPhase, mRadius,-mRadius); // guarantees that result is in [-A, A)
Tv r = mPhase;
mPhase += inc;
return r;
}
template<class Tv, class Td> inline Tv AccumPhase<Tv, Td>::nextPhase(){
return nextPhaseUsing(mInc);
}
template<class Tv, class Td> inline Tv AccumPhase<Tv, Td>::nextPhase(Tv frqMod){
return nextPhaseUsing(mInc + mapFreq(frqMod));
}
template<class Tv, class Td> inline void AccumPhase<Tv, Td>::freq(Tv v){ mInc = mapFreq(v); }
template<class Tv, class Td> inline void AccumPhase<Tv, Td>::freqAdd(Tv v){
nextPhaseUsing(mapFreq(v));
}
template<class Tv, class Td> inline void AccumPhase<Tv, Td>::freqMul(Tv v){
nextPhaseUsing((v-Tv(1))*mInc);
}
template<class Tv, class Td> inline void AccumPhase<Tv, Td>::period(Tv v){ freq(Tv(1)/v); }
template<class Tv, class Td> inline void AccumPhase<Tv, Td>::phase(Tv v){ mPhase = mapPhase(v); }
template<class Tv, class Td> inline void AccumPhase<Tv, Td>::phaseAdd(Tv v){ mPhase += mapPhase(v); }
template<class Tv, class Td> inline void AccumPhase<Tv, Td>::radius(Tv v){
Tv f = freq();
mRadius=v;
recache();
freq(f);
}
template<class Tv, class Td> inline Tv AccumPhase<Tv, Td>::freq() const { return mInc/mFreqToInc; }
template<class Tv, class Td> inline Tv AccumPhase<Tv, Td>::freqUnit() const { return freq()*this->ups(); }
template<class Tv, class Td> inline Tv AccumPhase<Tv, Td>::period() const { return Tv(1)/freq(); }
template<class Tv, class Td> inline Tv AccumPhase<Tv, Td>::phase() const { return mPhase/(Tv(2)*mRadius); }
template<class Tv, class Td> inline Tv AccumPhase<Tv, Td>::radius() const { return mRadius; }
template<class Tv, class Td> void AccumPhase<Tv, Td>::onDomainChange(double /*r*/){
Tv f=freq();
recache();
freq(f);
}
template<class Tv, class Td> void AccumPhase<Tv, Td>::recache(){
mFreqToInc = Tv(Td::ups() * Tv(2)*mRadius);
}
template<class Tv, class Td> void AccumPhase<Tv, Td>::print(FILE * fp, const char * append){
fprintf(fp, "%f %f %f%s", freq(), phase(), mInc, append);
}
//---- CSine
template<class Tv, class Td> CSine<Tv, Td>::CSine(Tv f, Tv a, Tv dcy60, Tv p)
: val(a, 0), mAmp(a), mFreq(f), mDcy60(dcy60)
{
onDomainChange(1);
this->phase(p);
}
template<class Tv, class Td> void CSine<Tv, Td>::amp(Tv v){
if(scl::abs(mAmp) > Tv(0.000001)){
Tv factor = v / mAmp;
val *= factor;
} else {
val(v, Tv(0));
}
mAmp = v;
}
template<class Tv, class Td> void CSine<Tv, Td>::decay(Tv v){
mDcy60 = v;
mDcy = v > Tv(0) ? Tv(scl::t60(v * Td::spu())) : Tv(1);
freq(mFreq);
}
template<class Tv, class Td> void CSine<Tv, Td>::freq(Tv v){
mFreq = v;
Tv phaseInc = v * Td::ups() * Tv(M_2PI);
mInc.fromPolar(mDcy, phaseInc);
//printf("%f %f %f %f\n", phaseInc, mDcy, c1, s1);
}
template<class Tv, class Td> void CSine<Tv, Td>::phase(Tv v){
// set phase without changing current magnitude
val.fromPolar(val.norm(), v*Tv(M_2PI));
}
template<class Tv, class Td> void CSine<Tv, Td>::reset(){ val(mAmp, Tv(0)); }
template<class Tv, class Td> void CSine<Tv, Td>::set(Tv frq, Tv phase, Tv amp, Tv dcy60){
mFreq = frq;
decay(dcy60);
this->amp(amp);
this->phase(phase);
}
template<class Tv, class Td> inline Complex<Tv> CSine<Tv, Td>::operator()(){
complex c = val;
val *= mInc;
return c;
}
template<class Tv, class Td> void CSine<Tv, Td>::onDomainChange(double /*r*/){
decay(mDcy60); // this sets frequency as well
}
//---- LFO
#define TLFO LFO<Sp,Td>
template<class Sp, class Td> TLFO::LFO(): Base(){ mod(0.5); }
template<class Sp, class Td> TLFO::LFO(double f, double p, double m): Base(f, p){ mod(m); }
template<class Sp, class Td> inline TLFO& TLFO::set(float f, float p, float m){
this->freq(f);
this->phase(p);
return mod(m);
}
template<class Sp, class Td> inline TLFO& TLFO::mod(double v){
return modI(castIntRound(v*4294967296.));
}
template<class Sp, class Td> inline TLFO& TLFO::modI(uint32_t v){
mMod=v;
return *this;
}
template<class Sp, class Td> inline float TLFO::line2(){
uint32_t m = scl::clip<uint32_t>(mMod, 0xffefffff, 512); // avoid div by zero
/* Starts at 1
float r1 = scl::rampDown(phaseI());
float r2 = scl::rampDown(phaseI() + m); //*/
//* Starts at -1 (better for creating attack/decay like envelopes)
float r1 = scl::rampDown(phaseI() - m);
float r2 = scl::rampDown(phaseI()); //*/
float p = punUF(Expo2<float>() | (m >> 9)) - 2.f; // [0, 2);
float r = (r1*r1 - r2*r2)/(p*(2.f - p));
nextPhase();
return r;
}
template<class Sp, class Td> inline float TLFO::line2U(){ return line2()*0.5f+0.5f; }
#define DEF(name, exp) template<class Sp, class Td> inline float TLFO::name{ float r = exp; return r; }
//DEF(cos(), tri(); r *= 0.5f * r*r - 1.5f)
//DEF(cos(), up(); r=scl::abs(r*r) )//r = -1.f - scl::pow2(2.f*r)*(scl::abs(r)-1.5f) )
DEF(cos(), up(); r = -1.f - r*r*(4.f*scl::abs(r)-6.f) )
DEF(down(), scl::rampDown(nextPhase()))
DEF(even3(), up(); float c=-2.598076211353315;/*-1.50*pow(3,0.50)*/ r *= (1.f-r*r)*c;)
DEF(even5(), up(); float c=-1.869185976526527;/*-1.25*pow(5,0.25)*/ r *= (1.f-scl::pow4(r))*c;)
DEF(imp(), scl::pulse(nextPhase(), this->freqI()) )
DEF(para(), paraU()*1.5f - 0.5f)
DEF(pulse(), scl::pulse(nextPhase(), mMod))
DEF(sinPara(), scl::sinPara(nextPhase()))
DEF(stair(), scl::stair(nextPhase(), mMod))
DEF(sqr(), scl::square(nextPhase()))
DEF(tri(), scl::triangle(nextPhase()))
DEF(up(), scl::rampUp(nextPhase()))
DEF(up2(), scl::rampUp2(nextPhase(), mMod))
DEF(S1(), up(); float c= -M_PI / 2; r = c*r)
DEF(C2(), up(); float c= scl::pow2(M_PI)/ 4; r = c*(r*r - 1.f/3))
DEF(S3(), up(); float c= scl::pow3(M_PI)/ 12; r = c*(r*r*r - r))
DEF(C4(), up(); float c=-scl::pow4(M_PI)/ 48; float rr=r*r; r = c*(rr*(rr - 2.f) + 7.f/15))
DEF(S5(), up(); float c=-scl::pow5(M_PI)/240; float rr=r*r; r = c*r*(rr*(rr - 10.f/3) + 7.f/3))
DEF(cosU(), up(); r = r*r*(-2.f*scl::abs(r)+3.f))
DEF(downU(), scl::rampDownU(nextPhase()))
//DEF(hann(), tri(); r = r * (0.25f * r*r - 0.75f) + 0.5f)
DEF(hann(), up(); r = 1.f + r*r*(2.f*scl::abs(r)-3.f))
DEF(impU(), scl::pulseU(nextPhase(), this->freqI()) )
DEF(paraU(), up(); r*=r;)
DEF(pulseU(), scl::pulseU(nextPhase(), mMod))
DEF(pulseRange(), scl::pulseRange(nextPhase(), mMod))
DEF(sqrU(), scl::squareU(nextPhase()))
DEF(stairU(), scl::stairU(nextPhase(), mMod))
DEF(triU(), scl::triangleU(nextPhase()))
DEF(upU(), scl::rampUpU(nextPhase()))
DEF(up2U(), scl::rampUp2U(nextPhase(), mMod))
DEF(patU(), scl::rampUpU(nextPhase() & mMod))
DEF(patU(uint32_t mul), scl::rampUpU((nextPhase() & mMod) * mul))
/* The input domain for these is [-1,1] corresponding to [-pi,pi], but that will
give us an upside-down sine. We therefore use a downward ramp to flip the wave
on the time axis.*/
DEF(sineT9(), down(); r = scl::sinT9(r * M_PI))
DEF(sineP9(), down(); r = scl::sinP9(r))
#undef DEF
#undef TLFO
template <class Sp, class Td>
DWO<Sp,Td>::DWO()
//: mPrev(0)
{
mod(0.5);
}
template<class Sp, class Td>
DWO<Sp,Td>::DWO(float f, float p, float m)
//: mPrev(0)
{
freq(f);
this->phase(p);
mod(m);
}
template<class Sp, class Td>
inline DWO<Sp,Td>& DWO<Sp,Td>::mod(double v){
return modI(castIntRound(v*4294967296.));
}
template<class Sp, class Td>
inline DWO<Sp,Td>& DWO<Sp,Td>::modI(uint32_t v){
mMod=v;
return *this;
}
template <class Sp, class Td>
inline void DWO<Sp,Td>::freq(float v){
Base::freq(v);
float freq1 = this->freqUnit();
// Very low freq will produce quantization noise
if(freq1 < 1e-5) freq1 = 1e-5;
mGain = -0.25/freq1;
}
/* Ideally we would use a differencing filter, however, it has a serious shortcoming. Any sudden changes in phase (or frequency) will lead a large amplitude impulse in the output which becomes worse the lower the frequency. A related problem is what to initialize the filter's previous input sample to. Instead of a filter, we use an analytic approach which subtracts two phase-shifted waveforms.
*/
namespace{
inline float para01(uint32_t p){
float s = scl::rampUp(p);
return s*s;
}
inline float triangle02(uint32_t p){
p = Expo4<float>() | (p >> 9); // [4, 8)
return scl::abs(punUF(p) - 6.f);
}
}
template <class Sp, class Td>
inline float DWO<Sp,Td>::up(){
/*
float s = para01(this->nextPhase());
return diff(s);//*/
//*
uint32_t p = this->nextPhase();
float s = para01(p);
float t = para01(p + this->freqI());
return (s - t)*mGain;//*/
}
template <class Sp, class Td>
inline float DWO<Sp,Td>::down(){
/*float s = para01(this->nextPhase());
return diff(-s);*/
uint32_t p = this->nextPhase();
float s = para01(p);
float t = para01(p + this->freqI());
return (t - s)*mGain;
}
template <class Sp, class Td>
inline float DWO<Sp,Td>::sqr(){
/*
float s = triangle02(this->nextPhase());
return diff(s);//*/
//*
uint32_t p = this->nextPhase();
float s = triangle02(p);
float t = triangle02(p + this->freqI());
return (t - s)*mGain;//*/
}
template <class Sp, class Td>
inline float DWO<Sp,Td>::para(){
static const float c = (-M_PI*M_PI*M_PI/12.)*0.5;
uint32_t p = this->nextPhase();
float s = scl::rampUp(p);
s = s*s*s - s;
float t = scl::rampUp(p + this->freqI());
t = t*t*t - t;
return (t - s)*c*mGain;
}
template <class Sp, class Td>
inline float DWO<Sp,Td>::tri(){
/*
float s = scl::sinPara(this->nextPhase())*0.5f;
return diff(s);//*/
//*
uint32_t p = this->nextPhase();
float s = scl::sinPara(p);
float t = scl::sinPara(p + this->freqI());
return (t - s)*-0.5f*mGain;//*/
}
template <class Sp, class Td>
inline float DWO<Sp,Td>::pulse(){
/*
uint32_t p = this->nextPhase();
float s1 = para01(p);
float s2 = para01(p + mMod);
return diff((s1 - s2)*0.5f);//*/
//*
uint32_t p = this->nextPhase();
float s1 = para01(p);
float s2 = para01(p - mMod);
float s = s1 - s2;
uint32_t q = p + this->freqI();
float t1 = para01(q);
float t2 = para01(q - mMod);
float t = t1 - t2;
return (t - s)*0.5f*mGain;//*/
}
template <class Sp, class Td>
inline float DWO<Sp,Td>::imp(){
return scl::pulse(this->nextPhase(), this->freqI());
}
/*template <class Sp, class Td>
inline float DWO<Sp,Td>::diff(float v){
float res = (v - mPrev)*mGain;
mPrev = v;
return res;
}*/
template<class Sp, class Td>
void DWO<Sp,Td>::onDomainChange(double r){
Base::onDomainChange(r);
freq(Base::freq());
}
//---- Buzz
template<class Tv, class Td> Buzz<Tv,Td>::Buzz(Tv f, Tv p, Tv harmonics)
: Base(f, p), mAmp(0), mPrev(Tv(0)), mNormalize(true)
{
onDomainChange(1);
this->harmonics(harmonics);
}
template<class Tv, class Td> inline void Buzz<Tv,Td>::harmonics(Tv v){
mNDesired = v;
mN = scl::floor(v);
mNFrac = v - mN;
setAmp();
}
template<class Tv, class Td> inline void Buzz<Tv,Td>::harmonicsMax(){
harmonics(maxHarmonics());
}
template<class Tv, class Td> inline void Buzz<Tv,Td>::antialias(){
Tv newN = maxHarmonics();
newN = mNDesired > newN ? newN : mNDesired;
mN = scl::floor(newN);
mNFrac = newN - mN;
setAmp();
}
template<class Tv, class Td> void Buzz<Tv,Td>::normalize(bool v){
mNormalize = v;
setAmp();
}
template<class Tv, class Td> inline Tv Buzz<Tv,Td>::maxHarmonics() const {
return mSPU_2 / this->freq();
}
template<class Tv, class Td> inline void Buzz<Tv,Td>::setAmp(){
if(mNormalize){
// Normally, the amplitude is 1/(2N), but we will linearly interpolate
// based on fractional harmonics to avoid sudden changes in amplitude to
// the lower harmonics which is very noticeable.
mAmp = (mN != Tv(0)) ? (Tv(0.5) / (mN+mNFrac)) : 0;
}
else{
mAmp = Tv(0.5);
}
}
#define EPS 0.000001
template<class Tv, class Td> inline Tv Buzz<Tv, Td>::operator()(){
/* 1 / sin((N+0.5)x) \
f(x) = -- | ------------- - 1 |
2N \ sin(0.5x) /
*/
Tv theta = this->nextPhase();
Tv result;
Tv denom = scl::sinT7(theta * Tv(0.5));
// denominator goes to zero when theta is an integer multiple of 2 pi
if(scl::abs(denom) < Tv(EPS)){
result = Tv(2) * mN * mAmp;
//printf("Buzz::operator(): oops\n");
}
else{
Tv nphase = scl::wrapPhase(theta * (mN + Tv(0.5)));
//result = (scl::sinT7(nphase) / denom - Tv(1)) * mAmp;
result = ((scl::sinT7(nphase) - denom) / denom) * mAmp;
}
//Tv fund = ((denom*denom)-0.5)*-4*mAmp;
//result -= fund; // drop first harmonic
// Mitigate pops when number of harmonics is changed by a small amount
//result -= 2.f * mAmp * cos(theta * mN) * (1.f - scl::pow2(mNFrac));
return result;
}
template<class Tv, class Td> inline Tv Buzz<Tv,Td>::odd(){
/* 1 / sin(2N x) \
f(x) = -- | --------- |
2N \ sin(x) /
*/
Tv theta = this->nextPhase();
Tv result;
Tv n2 = scl::roundAway(mN*0.5) * 2;
Tv n2frac = ((mN + mNFrac) - (n2-1)); // fraction, in [0,2), btw odd harmonics
// cos is more precise near zero-crossings
Tv denom = scl::cosT8(scl::wrapPhaseOnce(theta - M_PI_2));
//Tv denom = scl::sinT9(theta);
if( scl::abs(denom) < Tv(EPS) ){
if( theta > M_PI ) theta -= M_2PI;
Tv A = n2 / (n2 + n2frac);
result = (theta > -M_PI_2 && theta < M_PI_2) ? A : -A;
//printf("Buzz::odd(): oops\n");
}
else result = scl::sinT7(scl::wrapPhase(n2 * theta)) / (denom * (n2 + n2frac));
return result;
}
#undef EPS
template<class Tv, class Td>
inline Tv Buzz<Tv,Td>::saw(Tv b){ return mPrev=(*this)() + b*mPrev; }
template<class Tv, class Td>
inline Tv Buzz<Tv,Td>::square(Tv b){ return mPrev=odd() + b*mPrev; }
template<class Tv, class Td> void Buzz<Tv,Td>::onDomainChange(double r){
Base::onDomainChange(r);
mSPU_2 = Tv(Td::spu() * 0.5);
}
//---- DSF
template<class Tv, class Td> DSF<Tv,Td>::DSF(Tv frq, Tv freqRatioA, Tv ampRatioA, Tv harmonicsA)
: Base(frq)
{
freq(frq);
freqRatio(freqRatioA);
harmonics(harmonicsA);
ampRatio(ampRatioA);
mBeta = 0.f;
}
template<class Tv, class Td> inline void DSF<Tv,Td>::freq(Tv v){
Base::freq(v);
updateBetaInc();
}
template<class Tv, class Td> inline void DSF<Tv,Td>::freqRatio(Tv v){
mFreqRatio = v;
updateBetaInc();
}
template<class Tv, class Td> inline void DSF<Tv,Td>::ampRatio(Tv v){
if(v != mA){
// if near 1, nudge away
static const Tv epslt = 0.9997;
static const Tv epsgt = 1/epslt;
if(v >= 1 && v < epsgt) v = epsgt;
else if(v <= 1 && v > epslt) v = epslt;
mA = v;
mASqP1 = mA * mA + 1.f;
updateAPow();
}
}
template<class Tv, class Td> inline void DSF<Tv,Td>::harmonics(Tv v){
if(v != mN){
mN = mNDesired = v;
updateAPow();
}
}
template<class Tv, class Td> inline void DSF<Tv,Td>::harmonicsMax(){
harmonics(maxHarmonics());
}
template<class Tv, class Td> inline void DSF<Tv,Td>::antialias(){
Tv maxN = maxHarmonics();
if(mNDesired > maxN) mN = maxN;
else mN = mNDesired;
updateAPow();
}
template<class Tv, class Td> inline Tv DSF<Tv,Td>::ampRatio() const {
return mA;
}
template<class Tv, class Td> inline Tv DSF<Tv,Td>::freqRatio() const {
return mFreqRatio;
}
template<class Tv, class Td> inline Tv DSF<Tv,Td>::harmonics() const {
return mN;
}
template<class Tv, class Td> inline Tv DSF<Tv,Td>::maxHarmonics() const {
return scl::floor((Tv(this->spu()) * Tv(0.5)/this->freq() - Tv(1))/freqRatio() + Tv(1));
}
template<class Tv, class Td> inline void DSF<Tv,Td>::updateAPow(){
mAPow = ::pow(mA, mN);
}
template<class Tv, class Td> inline void DSF<Tv,Td>::updateBetaInc(){
mBetaInc = this->mInc * mFreqRatio;
}
// Generalized DSF formula:
// sum{k=0, N}( a^k sin(T + k B) )
// = sin(T) - a sin(T - B) - a^(N+1) [sin(T + (N+1) B) - a sin(T + N B))]
// / 1 + a^2 - 2a cos(B)
#define SIN scl::sinT7
#define COS scl::cosT8
//#define SIN sin
//#define COS cos
template<class Tv, class Td> inline Tv DSF<Tv,Td>::operator()(){
Tv theta = Base::nextPhase();
mBeta = scl::wrapPhase(mBeta);
Tv phs2 = scl::wrapPhaseOnce(theta - mBeta);
Tv phs3 = scl::wrapPhase(theta + mN * mBeta);
Tv phs4 = scl::wrapPhaseOnce(phs3 - mBeta);
Tv result = SIN(theta) - mA * SIN(phs2) - mAPow * (SIN(phs3) - mA * SIN(phs4));
result /= mASqP1 - Tv(2) * mA * COS(mBeta);
//result /= mA * (mA - Tv(2) * COS(mBeta)) + Tv(1);
mBeta += mBetaInc;
return result;
}
#undef SIN
#undef COS
template<class Tv, class Td> void DSF<Tv,Td>::onDomainChange(double r){
Base::onDomainChange(r);
freq(Base::freq());
harmonics(mNDesired);
}
} // gam::
#endif