#pragma once #include #include #include #include namespace monostep { enum class Waveform : int { sine = 0, triangle = 1, saw = 2, square = 3, pulse = 4, noise = 5, sub = 6, pluck = 7, super = 8, sah = 9, formant = 10, pwm = 11 }; static constexpr int numWaveforms = 12; inline float frac (float x) { return x - std::floor (x); } inline float polyBlep (float t, float dt) { if (t < dt) { t /= dt; return t + t - t * t - 1.0f; } if (t > 1.0f - dt) { t = (t - 1.0f) / dt; return t * t + t + t + 1.0f; } return 0.0f; } inline float renderWave (Waveform w, float phase, float inc) { switch (w) { case Waveform::sine: return std::sin (phase * 6.283185307179586f); case Waveform::triangle: { const float f = frac (phase); return 4.0f * std::min (f, 1.0f - f) - 1.0f; } case Waveform::saw: { const float f = frac (phase); return (2.0f * f - 1.0f) - polyBlep (f, inc); } case Waveform::square: { const float f = frac (phase); float v = (f < 0.5f) ? 1.0f : -1.0f; v += polyBlep (f, inc); v -= polyBlep (frac (f + 0.5f), inc); return v; } case Waveform::pulse: { const float f = frac (phase); return (f < 0.3f) ? 1.0f : -1.0f; } case Waveform::sub: { const float f = frac (phase * 0.25f); return 4.0f * std::min (f, 1.0f - f) - 1.0f; } case Waveform::noise: { thread_local static std::mt19937 rng { std::random_device {}() }; thread_local static std::uniform_real_distribution dist (-1.0f, 1.0f); return dist (rng); } case Waveform::pluck: { const float f = frac (phase); return 2.0f * std::sin (f * 6.283185307179586f * 2.0f) * std::exp (-10.0f * f); } case Waveform::super: { // A small detuned stack of band-limited saws -> fat, chorus-like lead. float sum = 0.0f; const float detunes[3] = { -0.01f, 0.0f, 0.01f }; for (const float d : detunes) { const float f = frac (phase + d); sum += (2.0f * f - 1.0f) - polyBlep (f, inc); } return sum / 3.0f; } case Waveform::sah: { // Sample & hold: a stepped random level per fraction of the cycle. // Deterministic (no shared state) so each oscillator is independent. const int steps = 16; const float f = frac (phase); int s = static_cast (std::floor (f * (float) steps)); if (s >= steps) s = steps - 1; if (s < 0) s = 0; std::uint32_t h = static_cast (s); h ^= h << 13; h ^= h >> 17; h ^= h << 5; const float r = static_cast (h & 0xffffff) / 16777215.0f; return r * 2.0f - 1.0f; } case Waveform::formant: { // Two formant-like bands via slow AM sidebands (fixed ratios). const float w = phase * 6.283185307179586f; const float env = 0.5f + 0.3f * std::cos (w * 0.5f) + 0.2f * std::cos (w * 0.25f); return std::sin (w) * env; } case Waveform::pwm: { // Fixed 20 % pulse, band-limited at both edges. const float width = 0.2f; const float f = frac (phase); float v = (f < width) ? 1.0f : -1.0f; v += polyBlep (f, inc); v -= polyBlep (frac (f + 1.0f - width), inc); return v; } } return 0.0f; } } // namespace monostep