134 lines
4.2 KiB
C++
134 lines
4.2 KiB
C++
#include "Oscillator.h"
|
|
|
|
namespace serum
|
|
{
|
|
|
|
void Oscillator::reset()
|
|
{
|
|
for (auto& v : voices)
|
|
{
|
|
v.phase = 0.0;
|
|
v.detuneRatio = 1.0;
|
|
v.pan = 0.0f;
|
|
v.level = 1.0f;
|
|
}
|
|
activeUnison = 1;
|
|
}
|
|
|
|
void Oscillator::noteOn (double freqHz, const OscParams& p, juce::uint32 seed)
|
|
{
|
|
jassert (freqHz > 0.0);
|
|
rng.setSeed (seed);
|
|
|
|
const int uni = juce::jlimit (1, kMaxUnison, p.unison);
|
|
activeUnison = uni;
|
|
|
|
const double basePhase = (double) p.phase * kTwoPi;
|
|
|
|
for (int v = 0; v < uni; ++v)
|
|
{
|
|
// Even phase spacing prevents cancellation across unison voices.
|
|
double offset = (uni > 1) ? ((double) v / (double) uni) * kTwoPi : 0.0;
|
|
double random = rng.nextFloat() * (double) p.randPhase * kTwoPi;
|
|
voices[(size_t) v].phase = basePhase + offset + random;
|
|
|
|
// Detune: linear spread in cents, 0..50 cents at full depth.
|
|
double detuneCents = 0.0;
|
|
if (uni > 1)
|
|
detuneCents = (double) p.detune * 50.0 * ((double) (v - (uni - 1) / 2.0) / (double) ((uni - 1) / 2.0));
|
|
voices[(size_t) v].detuneRatio = std::pow (2.0, detuneCents / 1200.0);
|
|
|
|
// Stereo spread.
|
|
float panPos = (uni > 1) ? ((float) v / (float) (uni - 1) - 0.5f) * 2.0f * p.spread : 0.0f;
|
|
voices[(size_t) v].pan = panPos;
|
|
|
|
// Gain scaling with a centre emphasis for odd unison counts.
|
|
float lvl = 1.0f / std::sqrt ((float) uni);
|
|
if ((uni & 1) && v == uni / 2)
|
|
lvl *= 1.3f;
|
|
voices[(size_t) v].level = lvl;
|
|
}
|
|
}
|
|
|
|
float Oscillator::warpPhase (float phase, const OscParams& p) const noexcept
|
|
{
|
|
const float amt = p.warpAmt;
|
|
switch ((WarpMode) p.warp)
|
|
{
|
|
case WarpMode::None: return phase;
|
|
case WarpMode::BendPlus: return phase + amt * 0.5f * std::sin (phase * (float) kTwoPi);
|
|
case WarpMode::BendMinus: return phase - amt * 0.5f * std::sin (phase * (float) kTwoPi);
|
|
case WarpMode::Sync: return std::fmod (phase * (1.0f + amt * 7.0f), 1.0f);
|
|
case WarpMode::Asym:
|
|
if (amt < 0.001f) return phase;
|
|
return std::pow (phase, 1.0f + amt * 3.0f);
|
|
case WarpMode::Mirror:
|
|
{
|
|
const float mirror = 2.0f * std::abs (phase - 0.5f);
|
|
return phase + (mirror - phase) * amt;
|
|
}
|
|
case WarpMode::PWM: return phase;
|
|
case WarpMode::Fold: return phase;
|
|
default: return phase;
|
|
}
|
|
}
|
|
|
|
float Oscillator::warpSample (float sample, const OscParams& p) const noexcept
|
|
{
|
|
const float amt = p.warpAmt;
|
|
switch ((WarpMode) p.warp)
|
|
{
|
|
case WarpMode::PWM:
|
|
{
|
|
const float threshold = (2.0f * amt - 1.0f) * 0.9f;
|
|
return std::tanh ((sample - threshold) * 4.0f);
|
|
}
|
|
case WarpMode::Fold:
|
|
return std::sin (sample * (1.0f + amt * 5.0f) * 1.5707963267948966f);
|
|
default:
|
|
return sample;
|
|
}
|
|
}
|
|
|
|
void Oscillator::processAdd (const Wavetable& wt, const OscParams& p, double freqHz,
|
|
float& outL, float& outR) noexcept
|
|
{
|
|
if (! p.enabled || p.level <= 0.0f || freqHz <= 0.0)
|
|
return;
|
|
|
|
const int uni = juce::jlimit (1, kMaxUnison, p.unison);
|
|
const float framePos = p.wtPos * 255.0f;
|
|
const double phaseInc = kTwoPi * freqHz / sr;
|
|
|
|
float accL = 0.0f;
|
|
float accR = 0.0f;
|
|
|
|
for (int v = 0; v < uni; ++v)
|
|
{
|
|
auto& sv = voices[(size_t) v];
|
|
|
|
sv.phase += phaseInc * sv.detuneRatio;
|
|
sv.phase -= std::floor (sv.phase * (1.0 / kTwoPi)) * kTwoPi;
|
|
if (sv.phase >= kTwoPi) sv.phase -= kTwoPi;
|
|
if (sv.phase < 0.0) sv.phase += kTwoPi;
|
|
|
|
float phase01 = (float) (sv.phase * (1.0 / kTwoPi));
|
|
float sample = wt.readSafe (framePos, warpPhase (phase01, p));
|
|
sample = warpSample (sample, p);
|
|
|
|
// Constant-power pan.
|
|
const float panAngle = (sv.pan + 1.0f) * 0.5f * 1.5707963267948966f;
|
|
const float panL = std::cos (panAngle);
|
|
const float panR = std::sin (panAngle);
|
|
|
|
const float gain = sv.level * p.level;
|
|
accL += sample * gain * panL;
|
|
accR += sample * gain * panR;
|
|
}
|
|
|
|
outL += accL;
|
|
outR += accR;
|
|
}
|
|
|
|
} // namespace serum
|