38#ifndef SBL_DSP_MODULES_MORPH_OSC_HPP_
39#define SBL_DSP_MODULES_MORPH_OSC_HPP_
52struct MorphOscTestAccess;
58template <u
int16_t MaxFrames = 48>
61 static constexpr float kMinNote = 24.0f;
62 static constexpr float kMaxNote = 120.0f;
63 static constexpr float kNoteRange = kMaxNote - kMinNote;
64 static constexpr float kMaxDriftSemitones = 0.10f;
65 static constexpr float kMinPW = 0.05f;
66 static constexpr float kMaxPW = 0.95f;
75 su = (su < 0.0f) ? 0.0f : (su > 1.0f) ? 1.0f : su;
87 su = (su < 0.0f) ? 0.0f : (su > 1.0f) ? 1.0f : su;
88 set_pulse_width(kMinPW + su * (kMaxPW - kMinPW));
93 su = (su < 0.0f) ? 0.0f : (su > 1.0f) ? 1.0f : su;
94 set_drift_amount(su * kMaxDriftSemitones);
99 su = (su < 0.0f) ? 0.0f : (su > 1.0f) ? 1.0f : su;
118 if (drift_amount_ > 0.0f) {
120 drift_pulse_.
process(drift_amount_));
122 drift_tri_.
process(drift_amount_));
124 drift_saw_.
process(drift_amount_));
126 drift_ratio_pulse_ = 1.0f;
127 drift_ratio_tri_ = 1.0f;
128 drift_ratio_saw_ = 1.0f;
137 drift_pulse_.
reset();
140 drift_ratio_pulse_ = 1.0f;
141 drift_ratio_tri_ = 1.0f;
142 drift_ratio_saw_ = 1.0f;
156 const float* fm_mod =
nullptr,
float fm_depth = 0.0f) {
157 uint16_t n = (frames > MaxFrames) ? MaxFrames : frames;
159 if (fm_mod !=
nullptr) {
161 float base = apply_detune(freq_hz_);
162 float g_tri, g_pulse, g_saw;
163 compute_gains(g_tri, g_pulse, g_saw);
164 float comp = compute_morph_compensation();
166 for (uint16_t i = 0; i < n; ++i) {
168 if (freq < 0.1f) freq = 0.1f;
171 tri_osc_.set_frequency(freq * drift_ratio_tri_);
172 tri_osc_.
process(buf_tri_ + i, 1);
174 if (g_pulse > 0.0f) {
175 pulse_osc_.set_frequency(freq * drift_ratio_pulse_);
176 pulse_osc_.
process(buf_pulse_ + i, 1);
179 saw_osc_.set_frequency(freq * drift_ratio_saw_);
180 saw_osc_.
process(buf_saw_ + i, 1);
185 for (uint16_t i = 0; i < n; ++i) {
187 if (g_tri > 0.0f) s += buf_tri_[i] * g_tri;
188 if (g_pulse > 0.0f) s += buf_pulse_[i] * g_pulse;
189 if (g_saw > 0.0f) s += buf_saw_[i] * g_saw;
191 if (s > 1.0f) s = 1.0f;
192 else if (s < -1.0f) s = -1.0f;
197 float freq = apply_detune(freq_hz_);
198 pulse_osc_.set_frequency(freq * drift_ratio_pulse_);
199 tri_osc_.set_frequency(freq * drift_ratio_tri_);
200 saw_osc_.set_frequency(freq * drift_ratio_saw_);
202 render_morphed(out, n);
210 void set_frequency(
float hz) { freq_hz_ = hz; }
213 void set_morph(
float morph) {
214 morph_ = (morph < 0.0f) ? 0.0f : (morph > 1.0f) ? 1.0f : morph;
218 void set_pulse_width(
float pw) {
219 pulse_osc_.set_pulse_width(pw);
223 void set_drift_amount(
float semitones) { drift_amount_ = semitones; }
226 void set_amplitude(
float amp) {
227 pulse_osc_.set_amplitude(amp);
228 tri_osc_.set_amplitude(amp);
229 saw_osc_.set_amplitude(amp);
239 float freq_hz_ = 220.0f;
241 float detune_cents_ = 0.0f;
245 comp::AnalogDrift drift_pulse_{0x12345678u};
246 comp::AnalogDrift drift_tri_{0x9ABCDEF0u};
247 comp::AnalogDrift drift_saw_{0xDEADBEEFu};
248 float drift_amount_ = 0.0f;
249 float drift_ratio_pulse_ = 1.0f;
250 float drift_ratio_tri_ = 1.0f;
251 float drift_ratio_saw_ = 1.0f;
254 float buf_pulse_[MaxFrames];
255 float buf_tri_[MaxFrames];
256 float buf_saw_[MaxFrames];
259 float apply_detune(
float hz)
const {
260 if (detune_cents_ == 0.0f)
return hz;
266 void compute_gains(
float& g_tri,
float& g_pulse,
float& g_saw) {
267 if (morph_ <= 0.5f) {
268 float t = morph_ * 2.0f;
269 g_tri = sqrtf(1.0f - t);
273 float t = (morph_ - 0.5f) * 2.0f;
275 g_pulse = sqrtf(1.0f - t);
284 float compute_morph_compensation()
const {
285 static constexpr float kTriSawRms2 = 1.0f / 3.0f;
286 static constexpr float kPulseRms2 = 1.0f;
288 if (morph_ <= 0.5f) {
289 float t = morph_ * 2.0f;
290 rms2 = (1.0f - t) * kTriSawRms2 + t * kPulseRms2;
292 float t = (morph_ - 0.5f) * 2.0f;
293 rms2 = (1.0f - t) * kPulseRms2 + t * kTriSawRms2;
295 return sqrtf(kTriSawRms2 / rms2);
299 void render_morphed(
float* out, uint16_t n) {
300 float g_tri, g_pulse, g_saw;
301 compute_gains(g_tri, g_pulse, g_saw);
302 float comp = compute_morph_compensation();
304 if (g_tri > 0.0f) tri_osc_.
process(buf_tri_, n);
305 if (g_pulse > 0.0f) pulse_osc_.
process(buf_pulse_, n);
306 if (g_saw > 0.0f) saw_osc_.
process(buf_saw_, n);
308 for (uint16_t i = 0; i < n; ++i) {
310 if (g_tri > 0.0f) s += buf_tri_[i] * g_tri;
311 if (g_pulse > 0.0f) s += buf_pulse_[i] * g_pulse;
312 if (g_saw > 0.0f) s += buf_saw_[i] * g_saw;
314 if (s > 1.0f) s = 1.0f;
315 else if (s < -1.0f) s = -1.0f;
Analog oscillator drift simulation.
void set_rate(float control_rate_hz)
void reset()
Reset drift state to zero.
float process(float amount)
friend struct MorphOscTestAccess
void set_detune_cents(float cents)
Set detune offset in cents (±1200). Applied as a frequency ratio.
void set_drift_amount_su(float su)
Set analog drift amount from normalized signal. [0,1] → [0, 0.10] semitones.
void set_pitch_su(float su)
Set pitch from normalized signal. [0,1] → C1–C9 (MIDI 24–120).
void set_pulse_width_su(float su)
Set pulse width from normalized signal. [0,1] → [0.05, 0.95].
void process(float *out, uint16_t frames, const float *fm_mod=nullptr, float fm_depth=0.0f)
void set_amplitude_su(float su)
Set output amplitude from normalized signal. [0,1] → [0, 1].
void set_drift_rate(float hz)
Set drift control rate in Hz. Call once at init.
void reset()
Reset all oscillator phases and drift state to zero.
void set_morph_su(float su)
Set morph position from normalized signal. [0,1] → triangle→pulse→saw.
Fixed-point constants and audio sample types.
Interpolation and blending utilities.
constexpr float note_of_su(float su)
float fast_exp2f(float x)
float semitones_to_ratio(float semitones)
float note_to_frequency(float midi_note)
MIDI note to frequency in Hz. A4 = 440 Hz.
Semitone-to-frequency-ratio conversion.
PolyBLEP oscillator (Audio Stack — Widgets)