Skip to content

Commit

Permalink
Merge pull request #4150 from mtmccrea/topic/impulse-rewrite
Browse files Browse the repository at this point in the history
Topic/impulse rewrite
  • Loading branch information
joshpar authored Aug 23, 2022
2 parents 0f66e76 + fcd97da commit 8a5513e
Show file tree
Hide file tree
Showing 4 changed files with 333 additions and 82 deletions.
47 changes: 37 additions & 10 deletions HelpSource/Classes/Impulse.schelp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ categories:: UGens>Generators>Deterministic


Description::

Outputs non-bandlimited single sample impulses.


Expand All @@ -14,26 +13,54 @@ classmethods::
method::ar, kr

argument::freq

Frequency in Hertz.
Frequency in Hertz. strong::freq:: may be negative.


argument::phase

Phase offset in cycles (0..1).

Phase offset in cycles (0..1). Staying in this range offers a slight efficiency
advantage, though phase offsets outside this range are supported and wrapped
internally.

argument::mul

The output will be multiplied by this value.


argument::add

This value will be added to the output.

discussion::
An Impulse with frequency 0 returns a single impulse.
Discussion::
code::Impulse:: will output an impulse on the first sample (assuming no phase
offset).

When the initial code::freq = 0::, a single impulse is output on first sample,
followed by silence until the frequency changes.

Discussion::
code::Impulse:: will output a code::1.0:: on the first sample (assuming no
phase offset).

If the initial code::freq = 0::, a single impulse is output on first sample,
followed by silence until the frequency changes.

Supported rate combinations for code::(freq, phase):: are
code::(a,a)::, code::(a,k)::, code::(a,i)::,
code::(k,k)::, code::(k,i)::,
code::(i,k)::, code::(i,i)::.


Internally, code::Impulse:: is based on a wrapping phasor: when the phase wraps,
an impulse is output. Any strong::phase:: offset is added and wrapped before
the phase increment (determined by strong::freq::) is applied. Therefore, it is
the phase increment (freq) that triggers an impulse, not the phase offset. For
example, if you wanted to drive and impulse train directly by the phase,
code::Impulse:: would not support that. However, a small UGen network could
achieve this result:
code::
({ var f = 1000;
HPZ1.ar(HPZ1.ar(Phasor.ar(rate: f * SampleDur.ir))) > 1e-5
}.plot(0.005)
);
::

Examples::

Expand Down
264 changes: 195 additions & 69 deletions server/plugins/LFUGens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ struct LFGauss : public Unit {
};

struct Impulse : public Unit {
double mPhase, mPhaseOffset;
double mPhase, mPhaseOffset, mPhaseIncrement;
float mFreqMul;
};

Expand Down Expand Up @@ -202,9 +202,13 @@ void VarSaw_next_a(VarSaw* unit, int inNumSamples);
void VarSaw_next_k(VarSaw* unit, int inNumSamples);
void VarSaw_Ctor(VarSaw* unit);

void Impulse_next_a(Impulse* unit, int inNumSamples);
void Impulse_next_aa(Impulse* unit, int inNumSamples);
void Impulse_next_ak(Impulse* unit, int inNumSamples);
void Impulse_next_ai(Impulse* unit, int inNumSamples);
void Impulse_next_kk(Impulse* unit, int inNumSamples);
void Impulse_next_k(Impulse* unit, int inNumSamples);
void Impulse_next_ki(Impulse* unit, int inNumSamples);
void Impulse_next_ik(Impulse* unit, int inNumSamples);
void Impulse_next_ii(Impulse* unit, int inNumSamples);
void Impulse_Ctor(Impulse* unit);

void SyncSaw_next_aa(SyncSaw* unit, int inNumSamples);
Expand Down Expand Up @@ -793,109 +797,231 @@ void LFGauss_Ctor(LFGauss* unit) {

//////////////////////////////////////////////////////////////////////////////////////////////////

void Impulse_next_a(Impulse* unit, int inNumSamples) {
// detect if phasor is out-of-bounds, trigger and wrap [0, 1]
static inline float Impulse_testWrapPhase(double prev_inc, double& phase) {
if (prev_inc < 0.f) { // negative freqs
if (phase <= 0.f) {
phase += 1.f;
if (phase <= 0.f) { // catch large phase jumps
phase -= sc_ceil(phase);
}
return 1.f;
} else {
return 0.f;
}
} else { // positive freqs
if (phase >= 1.f) {
phase -= 1.f;
if (phase >= 1.f) {
phase -= sc_floor(phase);
}
return 1.f;
} else {
return 0.f;
}
}
}

void Impulse_next_ii(Impulse* unit, int inNumSamples) {
float* out = ZOUT(0);
float* freq = ZIN(0);
double phase = unit->mPhase;
double inc = unit->mPhaseIncrement;

float freqmul = unit->mFreqMul;
LOOP1(inNumSamples, ZXP(out) = Impulse_testWrapPhase(inc, phase); phase += inc;);

unit->mPhase = phase;
}

void Impulse_next_ik(Impulse* unit, int inNumSamples) {
float* out = ZOUT(0);
double phase = unit->mPhase;

double inc = unit->mPhaseIncrement;

double prev_off = unit->mPhaseOffset;
double off = ZIN0(1);
double phaseSlope = CALCSLOPE(off, prev_off);
bool phOffChanged = phaseSlope != 0.f;

LOOP1(
inNumSamples, float z; if (phase >= 1.f) {
phase -= 1.f;
z = 1.f;
} else { z = 0.f; } phase += ZXP(freq) * freqmul;
ZXP(out) = z;);
inNumSamples, ZXP(out) = Impulse_testWrapPhase(inc, phase);

if (phOffChanged) {
phase += phaseSlope;
Impulse_testWrapPhase(inc, phase);
} phase += inc;);

unit->mPhase = phase;
unit->mPhaseOffset = off;
}

/* phase mod - jrh 03 */

void Impulse_next_ak(Impulse* unit, int inNumSamples) {
void Impulse_next_ki(Impulse* unit, int inNumSamples) {
float* out = ZOUT(0);
float* freq = ZIN(0);
double phaseOffset = ZIN0(1);

float freqmul = unit->mFreqMul;
double phase = unit->mPhase;
double prev_phaseOffset = unit->mPhaseOffset;
double phaseSlope = CALCSLOPE(phaseOffset, prev_phaseOffset);
phase += prev_phaseOffset;

LOOP1(
inNumSamples, float z; phase += phaseSlope; if (phase >= 1.f) {
phase -= 1.f;
z = 1.f;
} else { z = 0.f; } phase += ZXP(freq) * freqmul;
ZXP(out) = z;);
double prev_inc = unit->mPhaseIncrement;
double inc = ZIN0(0) * unit->mFreqMul;
double incSlope = CALCSLOPE(inc, prev_inc);

LOOP1(inNumSamples, ZXP(out) = Impulse_testWrapPhase(prev_inc, phase);

prev_inc += incSlope; phase += prev_inc;);

unit->mPhase = phase - phaseOffset;
unit->mPhaseOffset = phaseOffset;
unit->mPhase = phase;
unit->mPhaseIncrement = inc;
}

void Impulse_next_kk(Impulse* unit, int inNumSamples) {
float* out = ZOUT(0);
float freq = ZIN0(0) * unit->mFreqMul;
double phaseOffset = ZIN0(1);

double phase = unit->mPhase;
double prev_phaseOffset = unit->mPhaseOffset;
double phaseSlope = CALCSLOPE(phaseOffset, prev_phaseOffset);
phase += prev_phaseOffset;

double prev_inc = unit->mPhaseIncrement;
double inc = ZIN0(0) * unit->mFreqMul;
double incSlope = CALCSLOPE(inc, prev_inc);

double prev_off = unit->mPhaseOffset;
double off = ZIN0(1);
double phaseSlope = CALCSLOPE(off, prev_off);
bool phOffChanged = phaseSlope != 0.f;

LOOP1(
inNumSamples, float z; phase += phaseSlope; if (phase >= 1.f) {
phase -= 1.f;
z = 1.f;
} else { z = 0.f; } phase += freq;
ZXP(out) = z;);
inNumSamples, ZXP(out) = Impulse_testWrapPhase(prev_inc, phase);

unit->mPhase = phase - phaseOffset;
unit->mPhaseOffset = phaseOffset;
}
if (phOffChanged) {
phase += phaseSlope;
Impulse_testWrapPhase(prev_inc, phase);
} prev_inc += incSlope;
phase += prev_inc;);

unit->mPhase = phase;
unit->mPhaseOffset = off;
unit->mPhaseIncrement = inc;
}

void Impulse_next_k(Impulse* unit, int inNumSamples) {
void Impulse_next_ak(Impulse* unit, int inNumSamples) {
float* out = ZOUT(0);
float freq = ZIN0(0) * unit->mFreqMul;

double phase = unit->mPhase;

double inc = unit->mPhaseIncrement;
float* freqIn = ZIN(0);
float freqMul = unit->mFreqMul;

double prev_off = unit->mPhaseOffset;
double off = ZIN0(1);
double offSlope = CALCSLOPE(off, prev_off);
bool offChanged = offSlope != 0.f;

LOOP1(
inNumSamples, float z; if (phase >= 1.f) {
phase -= 1.f;
z = 1.f;
} else { z = 0.f; } phase += freq;
ZXP(out) = z;);
inNumSamples, float z = Impulse_testWrapPhase(inc, phase); if (offChanged) {
phase += offSlope;
Impulse_testWrapPhase(inc, phase);
} inc = ZXP(freqIn) * freqMul;
ZXP(out) = z; phase += inc;);

unit->mPhase = phase;
unit->mPhaseOffset = off;
unit->mPhaseIncrement = inc;
}

void Impulse_next_aa(Impulse* unit, int inNumSamples) {
float* out = ZOUT(0);
double phase = unit->mPhase;

double inc = unit->mPhaseIncrement;
float* freqin = ZIN(0);
float freqmul = unit->mFreqMul;

double prev_off = unit->mPhaseOffset;
float* offIn = ZIN(1);

LOOP1(inNumSamples, float z = Impulse_testWrapPhase(inc, phase); float off = ZXP(offIn);
float offInc = off - prev_off; phase += offInc; Impulse_testWrapPhase(inc, phase);
inc = ZXP(freqin) * freqmul; ZXP(out) = z;

phase += inc; prev_off = off;);

unit->mPhase = phase;
unit->mPhaseOffset = prev_off;
unit->mPhaseIncrement = inc;
}

void Impulse_next_ai(Impulse* unit, int inNumSamples) {
float* out = ZOUT(0);
double phase = unit->mPhase;

double inc = unit->mPhaseIncrement;
float* freqin = ZIN(0);
float freqmul = unit->mFreqMul;

LOOP1(inNumSamples, float z = Impulse_testWrapPhase(inc, phase); inc = ZXP(freqin) * freqmul; ZXP(out) = z;
phase += inc;);

unit->mPhase = phase;
unit->mPhaseIncrement = inc;
}

// Impulse is based on a wrapping phasor. When the phase wraps, an impulse is
// output. Phase _increments_ according to its frequency and an additional phase
// _offset_ is applied.
// Order of operations:
// 1. Phase _offset_ is applied to the current phase (if offset has changed).
// 2. Phase is wrapped into range.
// 3. Phase _increment_ is added (according to the frequency).
// 4. Phase is checked for being out of range, in which case a trigger is fired
// and the phase is again wrapped.
// Therefore, phase increment (freq) triggers an impulse, but not phase offset.
void Impulse_Ctor(Impulse* unit) {
unit->mPhase = ZIN0(1);
unit->mPhaseOffset = ZIN0(1);
unit->mFreqMul = unit->mRate->mSampleDur;
unit->mPhaseIncrement = ZIN0(0) * unit->mFreqMul;

if (INRATE(0) == calc_FullRate) {
if (INRATE(1) != calc_ScalarRate) {
SETCALC(Impulse_next_ak);
unit->mPhase = 1.f;
double initOff = unit->mPhaseOffset;
double initInc = unit->mPhaseIncrement;
double initPhase = sc_wrap(initOff, 0.0, 1.0);

// Initial phase offset of 0 means output of 1 on first sample.
// Set phase to wrap point to trigger impulse on first sample
if (initPhase == 0.0 && initInc >= 0.0) {
initPhase = 1.0; // positive frequency trigger/wrap position
}
unit->mPhase = initPhase;

UnitCalcFunc func;
switch (INRATE(0)) {
case calc_FullRate:
switch (INRATE(1)) {
case calc_ScalarRate:
func = (UnitCalcFunc)Impulse_next_ai;
break;
case calc_BufRate:
func = (UnitCalcFunc)Impulse_next_ak;
break;
case calc_FullRate:
func = (UnitCalcFunc)Impulse_next_aa;
break;
}
break;
case calc_BufRate:
if (INRATE(1) == calc_ScalarRate) {
func = (UnitCalcFunc)Impulse_next_ki;
} else {
SETCALC(Impulse_next_a);
func = (UnitCalcFunc)Impulse_next_kk;
}
} else {
if (INRATE(1) != calc_ScalarRate) {
SETCALC(Impulse_next_kk);
unit->mPhase = 1.f;
break;
case calc_ScalarRate:
if (INRATE(1) == calc_ScalarRate) {
func = (UnitCalcFunc)Impulse_next_ii;
} else {
SETCALC(Impulse_next_k);
func = (UnitCalcFunc)Impulse_next_ik;
}
break;
}
unit->mCalcFunc = func;
func(unit, 1);


unit->mPhaseOffset = 0.f;
unit->mFreqMul = unit->mRate->mSampleDur;
if (unit->mPhase == 0.f)
unit->mPhase = 1.f;

ZOUT0(0) = 0.f;
unit->mPhase = initPhase;
unit->mPhaseOffset = initOff;
unit->mPhaseIncrement = initInc;
}

//////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down
Loading

0 comments on commit 8a5513e

Please sign in to comment.