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.
166 lines
4.5 KiB
166 lines
4.5 KiB
4 years ago
|
#pragma once
|
||
|
#ifndef DSY_HARMONIC_H
|
||
|
#define DSY_HARMONIC_H
|
||
|
|
||
|
#include <stdint.h>
|
||
|
#include "Utility/dsp.h"
|
||
|
#ifdef __cplusplus
|
||
|
|
||
|
|
||
|
/** @file harmonic_osc.h */
|
||
|
namespace daisysp
|
||
|
{
|
||
|
/**
|
||
|
@brief Harmonic Oscillator Module based on Chebyshev polynomials.
|
||
|
@author Ben Sergentanis
|
||
|
@date Dec 2020
|
||
|
Harmonic Oscillator Module based on Chebyshev polynomials \n
|
||
|
Works well for a small number of harmonics. For the higher order harmonics. \n
|
||
|
We need to reinitialize the recurrence by computing two high harmonics. \n \n
|
||
|
Ported from pichenettes/eurorack/plaits/dsp/oscillator/harmonic_oscillator.h \n
|
||
|
to an independent module. \n
|
||
|
Original code written by Emilie Gillet in 2016. \n
|
||
|
*/
|
||
|
template <int num_harmonics = 16>
|
||
|
class HarmonicOscillator
|
||
|
{
|
||
|
public:
|
||
|
HarmonicOscillator() {}
|
||
|
~HarmonicOscillator() {}
|
||
|
|
||
|
/** Initialize harmonic oscillator
|
||
|
\param sample_rate Audio engine samplerate
|
||
|
*/
|
||
|
void Init(float sample_rate)
|
||
|
{
|
||
|
sample_rate_ = sample_rate;
|
||
|
phase_ = 0.0f;
|
||
|
|
||
|
for(int i = 0; i < num_harmonics; ++i)
|
||
|
{
|
||
|
amplitude_[i] = 0.0f;
|
||
|
newamplitude_[i] = 0.f;
|
||
|
}
|
||
|
amplitude_[0] = 1.f;
|
||
|
newamplitude_[0] = 1.f;
|
||
|
|
||
|
SetFirstHarmIdx(1);
|
||
|
SetFreq(440.f);
|
||
|
|
||
|
recalc_ = false;
|
||
|
}
|
||
|
|
||
|
/** Get the next floating point sample */
|
||
|
float Process()
|
||
|
{
|
||
|
if(recalc_)
|
||
|
{
|
||
|
recalc_ = false;
|
||
|
for(int i = 0; i < num_harmonics; ++i)
|
||
|
{
|
||
|
float f = frequency_
|
||
|
* static_cast<float>(first_harmonic_index_ + i);
|
||
|
if(f >= 0.5f)
|
||
|
{
|
||
|
f = 0.5f;
|
||
|
}
|
||
|
amplitude_[i] = newamplitude_[i] * (1.0f - f * 2.0f);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
phase_ += frequency_;
|
||
|
if(phase_ >= 1.0f)
|
||
|
{
|
||
|
phase_ -= 1.0f;
|
||
|
}
|
||
|
const float two_x = 2.0f * sinf(phase_ * TWOPI_F);
|
||
|
float previous, current;
|
||
|
if(first_harmonic_index_ == 1)
|
||
|
{
|
||
|
previous = 1.0f;
|
||
|
current = two_x * 0.5f;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const float k = first_harmonic_index_;
|
||
|
previous = sinf((phase_ * (k - 1.0f) + 0.25f) * TWOPI_F);
|
||
|
current = sinf((phase_ * k) * TWOPI_F);
|
||
|
}
|
||
|
|
||
|
float sum = 0.0f;
|
||
|
for(int i = 0; i < num_harmonics; ++i)
|
||
|
{
|
||
|
sum += amplitude_[i] * current;
|
||
|
float temp = current;
|
||
|
current = two_x * current - previous;
|
||
|
previous = temp;
|
||
|
}
|
||
|
|
||
|
return sum;
|
||
|
}
|
||
|
|
||
|
/** Set the main frequency
|
||
|
\param freq Freq to be set in Hz.
|
||
|
*/
|
||
|
void SetFreq(float freq)
|
||
|
{
|
||
|
//convert from Hz to phase_inc / sample
|
||
|
freq = freq / sample_rate_;
|
||
|
freq = freq >= .5f ? .5f : freq;
|
||
|
freq = freq <= -.5f ? -.5f : freq;
|
||
|
recalc_ = cmp(freq, frequency_) || recalc_;
|
||
|
frequency_ = freq;
|
||
|
}
|
||
|
|
||
|
/** Offset the set of harmonics. Passing in 3 means "harmonic 0" is the 3rd harm., 1 is the 4th, etc.
|
||
|
\param idx Default behavior is 1. Values < 0 default to 1.
|
||
|
*/
|
||
|
void SetFirstHarmIdx(int idx)
|
||
|
{
|
||
|
idx = idx < 1 ? 1 : idx;
|
||
|
recalc_ = cmp(idx, first_harmonic_index_) || recalc_;
|
||
|
first_harmonic_index_ = idx;
|
||
|
}
|
||
|
|
||
|
/** Set the amplitudes of each harmonic of the root.
|
||
|
\param amplitudes Amplitudes to set. Sum of all amplitudes must be < 1. The array referenced must be at least as large as num_harmonics.
|
||
|
*/
|
||
|
void SetAmplitudes(const float* amplitudes)
|
||
|
{
|
||
|
for(int i = 0; i < num_harmonics; i++)
|
||
|
{
|
||
|
recalc_ = cmp(newamplitude_[i], amplitudes[i]) || recalc_;
|
||
|
newamplitude_[i] = amplitudes[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/** Sets one amplitude. Does nothing if idx out of range.
|
||
|
\param amp Amplitude to set
|
||
|
\param idx Which harmonic to set.
|
||
|
*/
|
||
|
void SetSingleAmp(const float amp, int idx)
|
||
|
{
|
||
|
if(idx < 0 || idx >= num_harmonics)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
recalc_ = cmp(amplitude_[idx], amp) || recalc_;
|
||
|
newamplitude_[idx] = amp;
|
||
|
}
|
||
|
|
||
|
|
||
|
private:
|
||
|
bool cmp(float a, float b) { return fabsf(a - b) > .000001f; }
|
||
|
|
||
|
float sample_rate_;
|
||
|
float phase_;
|
||
|
float frequency_;
|
||
|
float amplitude_[num_harmonics];
|
||
|
float newamplitude_[num_harmonics];
|
||
|
bool recalc_;
|
||
|
|
||
|
int first_harmonic_index_;
|
||
|
};
|
||
|
} // namespace daisysp
|
||
|
#endif
|
||
|
#endif
|