Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 81 additions & 5 deletions Source/Synthesis/oscillator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ float Oscillator::Process()
case WAVE_POLYBLEP_TRI:
t = phase_;
out = phase_ < 0.5f ? 1.0f : -1.0f;
out += Polyblep(phase_inc_, t);
out -= Polyblep(phase_inc_, fastmod1f(t + 0.5f));
if(!skip_builtin_polyblep_)
{
out += Polyblep(phase_inc_, t);
out -= Polyblep(phase_inc_, fastmod1f(t + 0.5f));
}
// Leaky Integrator:
// y[n] = A + x[n] + (1 - A) * y[n-1]
out = phase_inc_ * out + (1.0f - phase_inc_) * last_out_;
Expand All @@ -31,23 +34,39 @@ float Oscillator::Process()
case WAVE_POLYBLEP_SAW:
t = phase_;
out = (2.0f * t) - 1.0f;
out -= Polyblep(phase_inc_, t);
if(!skip_builtin_polyblep_)
out -= Polyblep(phase_inc_, t);
out *= -1.0f;
break;
case WAVE_POLYBLEP_SQUARE:
t = phase_;
out = phase_ < pw_ ? 1.0f : -1.0f;
out += Polyblep(phase_inc_, t);
out -= Polyblep(phase_inc_, fastmod1f(t + (1.0f - pw_)));
if(!skip_builtin_polyblep_)
{
out += Polyblep(phase_inc_, t);
out -= Polyblep(phase_inc_, fastmod1f(t + (1.0f - pw_)));
}
out *= 0.707f; // ?
break;
default: out = 0.0f; break;
}

// Apply any pending sync-BLEP residual (set by SyncReset(frac) on the
// previous sample). Consumed in one shot.
out += sync_corr_;
sync_corr_ = 0.0f;
skip_builtin_polyblep_ = false;

phase_ += phase_inc_;
if(phase_ > 1.0f)
{
phase_ -= 1.0f;
eoc_ = true;
// Fractional position within this sample at which the wrap occurred.
// Derivation: before advance, phase was p_before = phase_ + 1 - inc.
// Wrap time f satisfies p_before + f*inc = 1, so
// f = (1 - p_before) / inc = 1 - phase_/inc.
eoc_frac_ = (phase_inc_ > 0.0f) ? (1.0f - phase_ / phase_inc_) : 0.0f;
}
else
{
Expand All @@ -58,6 +77,63 @@ float Oscillator::Process()
return out * amp_;
}

float Oscillator::NaiveWaveformValue(float phase) const
{
float t;
switch(waveform_)
{
case WAVE_SIN: return sinf(phase * TWOPI_F);
case WAVE_TRI:
t = -1.0f + (2.0f * phase);
return 2.0f * (fabsf(t) - 0.5f);
case WAVE_SAW: return -1.0f * ((phase * 2.0f) - 1.0f);
case WAVE_RAMP: return (phase * 2.0f) - 1.0f;
case WAVE_SQUARE: return phase < pw_ ? 1.0f : -1.0f;
// For the polyblep variants we return the *pre-correction* naive value
// in the same sign/scale convention as Process()'s output (minus the
// polyblep residual and, for TRI, minus the leaky integrator).
case WAVE_POLYBLEP_TRI: return phase < 0.5f ? 1.0f : -1.0f;
case WAVE_POLYBLEP_SAW: return -1.0f * ((2.0f * phase) - 1.0f);
case WAVE_POLYBLEP_SQUARE:
return (phase < pw_ ? 1.0f : -1.0f) * 0.707f;
default: return 0.0f;
}
}

void Oscillator::SyncReset(float frac)
{
// Clamp frac into [0, 1) defensively.
if(frac < 0.0f) frac = 0.0f;
if(frac > 1.0f) frac = 1.0f;

// Slave's pre-sync continuous phase at the instant sync fired.
float pre_phase = phase_ + frac * phase_inc_;
if(pre_phase >= 1.0f) pre_phase -= 1.0f;
float pre_val = NaiveWaveformValue(pre_phase);

// Slave's phase at the next sample boundary: it reset to 0 at the sync
// instant and has been running for (1 - frac) * phase_inc_ since.
float post_phase = (1.0f - frac) * phase_inc_;
float post_val = NaiveWaveformValue(post_phase);

// Step magnitude at the discontinuity.
float step = post_val - pre_val;

// Polyblep residual for a unit step at fractional offset `frac` into the
// period [k-1, k], evaluated at sample k (the sample right after the
// step). Standard 2-sample polyblep shape gives correction = -frac^2/2
// per unit of step magnitude at sample k. The pre-step sample k-1 has
// already been emitted, so its correction is lost (causality limit of
// an online implementation).
sync_corr_ = step * (-0.5f * frac * frac);
skip_builtin_polyblep_ = true;

phase_ = post_phase;
last_out_ = 0.0f;
eoc_ = true;
eoc_frac_ = 0.0f;
}

float Oscillator::CalcPhaseInc(float f)
{
return f * sr_recip_;
Expand Down
49 changes: 39 additions & 10 deletions Source/Synthesis/oscillator.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,19 @@ class Oscillator
*/
void Init(float sample_rate)
{
sr_ = sample_rate;
sr_recip_ = 1.0f / sample_rate;
freq_ = 100.0f;
amp_ = 0.5f;
pw_ = 0.5f;
phase_ = 0.0f;
phase_inc_ = CalcPhaseInc(freq_);
waveform_ = WAVE_SIN;
eoc_ = true;
eor_ = true;
sr_ = sample_rate;
sr_recip_ = 1.0f / sample_rate;
freq_ = 100.0f;
amp_ = 0.5f;
pw_ = 0.5f;
phase_ = 0.0f;
phase_inc_ = CalcPhaseInc(freq_);
waveform_ = WAVE_SIN;
eoc_ = true;
eor_ = true;
eoc_frac_ = 0.0f;
sync_corr_ = 0.0f;
skip_builtin_polyblep_ = false;
}


Expand Down Expand Up @@ -92,6 +95,14 @@ class Oscillator
*/
inline bool IsEOC() { return eoc_; }

/** When IsEOC() is true, returns the fractional position within the
just-processed sample at which the phase wrap occurred.
0.0 = at the very start of the sample period, 1.0 = at the end.
Used by a slave oscillator's SyncReset(frac) for sub-sample-accurate
(BLEP-corrected) hard sync.
*/
inline float GetEocFraction() const { return eoc_frac_; }

/** Returns true if cycle rising.
*/
inline bool IsRising() { return phase_ < 0.5f; }
Expand All @@ -112,13 +123,31 @@ class Oscillator
*/
void Reset(float _phase = 0.0f) { phase_ = _phase; }

/** BLEP-corrected hard sync. `frac` is the sub-sample position (0..1)
at which the master wrapped, typically obtained from
`master.GetEocFraction()`. Produces a band-limited reset: the slave's
phase is advanced as if it had been reset at the correct sub-sample
instant, and a polyblep residual is stored to be applied to the next
Process() output. Suppresses the built-in polyblep on that sample
since its assumed step magnitude would be incorrect for a sync event.

Intended usage:
float m = master.Process();
if(master.IsEOC()) slave.SyncReset(master.GetEocFraction());
float s = slave.Process();
*/
void SyncReset(float frac);

private:
float CalcPhaseInc(float f);
float NaiveWaveformValue(float phase) const;
uint8_t waveform_;
float amp_, freq_, pw_;
float sr_, sr_recip_, phase_, phase_inc_;
float last_out_, last_freq_;
float eoc_frac_, sync_corr_;
bool eor_, eoc_;
bool skip_builtin_polyblep_;
};
} // namespace daisysp
#endif
Expand Down