#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