yes, well, i certainly did not ask for an interrogation of pure data’s block scheduler internals with my pretty basic and (i thought) non-controversial observation. and i’m no expert in PD anyways
.thank you for informing me about block~. fwiw, i agree that always re-allocating upwards seems safest if you absolutely do need to allocate.
strategies for managing buffering layers for algos, definitely seems on topic for any plugin format. it’s a common requirement (any time you want to resample / interpolate / delay / &c) and the general solution is some kind of ring buffer. (i don’t see why the MI oscillators actually need their own output buffer, except that it’s less work than updating each FooRender() function to convert to float… ok right, moving on. )
like, here is an external i just made last week for fun. it is a chaotic oscillator that requires 2 layers of internal buffering: one for signal analysis (it works by weighted-averaging of a chaotic map history), and one for interpolation.
{https://github.com/catfact/rad/blob/master/source/projects/rad.worb_tilde/rad.worb_tilde.cpp}
{https://github.com/catfact/rad-lib/blob/cb95d10cc5bb33ef738eb90531c172591a8a76fe/worb.h}
{https://github.com/catfact/rad-lib/blob/cb95d10cc5bb33ef738eb90531c172591a8a76fe/interpolator.h}
{https://github.com/catfact/rad-lib/blob/cb95d10cc5bb33ef738eb90531c172591a8a76fe/generator.h}
implementation-wise, i’ve found the above structure to be widely useful for a broad class of oscillators and colored-noise generators: specify an update rate (frequency of “highest harmonic”,) a generator function that is called at the update rate, and a signal interpolation method.
i have a bunch more similar weird oscillators using same pattern; when i get a minute, i will wrap them in SuperCollider UGens as well. that would be a good opportunity to work on a good UGen template.
this discussion has made me realize that i was being a bit too glib in simply recommending a look at example-plugins and the official guides. there seems to be room for a clean UGen template that uses only the stuff relevant for a direct implementation of an audio algorithm.
given infinite time it sure would be nice to have a generic C++ wrapper system that targeted the plugin formats of pd/max/sc. (Faust is nice, but it’s not always the most expedient wray to express something.)
ok, these are fun questions:
going backawrds,
- clipping distortion is the simplest here, at base it is as you say:
y = min(1, max(-1, x * gain))
but of course hard clipping introduces infinite bandwidth expansion, and in general one uses some “soft clipping” shape to constrain the generation of harmonics. if the clipping function is a polynomial then the poly order constrains the order of harmonics, which is nice.
whatever function you use, static waveshaping is nice and simple because it requires no signal history at all.
here’s a kind of “bestiary” of waveshaping functions i’ve (mostly) collected or (occasionally) made up over the years, which include old standbys like tanh. they are expressed as python code, sometimes with multiple parameters and sometimes just gain. (in a plugin, some would be much to expensive to compute directly and i would use a LUT.)
some of these produce folding at higher parameter values.
def shaper_tsq(x, t):
# two-stage quadratic
# t is softclip threshold in (0, 1) exclusive
g = 2.0
ax = abs(x)
sx = np.sign(x)
t_1 = t - 1.
a = g / (2. * t_1)
b = g * t - (a * t_1 * t_1)
if ax > t:
q = ax - 1.
y = a * q * q + b
return y * sx / 2
else:
return x * g / 2
def shaper_bram(x, a):
ax = np.abs(x)
return x * (ax + a) / (x * x + (a - 1) * ax + 1)
def shaper_bram2(x, a):
ax = np.abs(x)
sx = np.sign(x)
if ax < a:
return x
if ax > 1:
return sx * (a + 1) / 2
return sx * (a + (ax - a) / (1 + ((ax - a) / (1 - a)) ** 2))
def shaper_cubic(x, a):
g = 2 ** a
x = x * g
y = ((3 * x / 2) - ((x * x * x) / 2))
return y / g
def shaper_expo(x, a):
sx = np.sign(x)
return sx * (1 - (np.abs(x - sx) ** a))
def shaper_sine(x, a):
# param = logarithmic pre-gain
x = x * (2 ** a)
return np.sin(np.pi * x / 2)
def shaper_reciprocal(x, a):
# param = pregain
x = x * (2 ** a)
return np.sign(x) * (1 - (1 / (np.abs(x) + 1)))
def shaper_tanh(x, a):
# param = pregain
x = x * (2 ** a)
return np.tanh(x)
def shaper_ulaw(x, a):
ax = abs(x)
sx = np.sign(x)
return sx * np.log(1 + a * ax) / np.log(1 + a)
def shaper_alaw(x, a):
ax = abs(x)
sx = np.sign(x)
denom = 1 + np.log(a)
if ax < (1 / a):
return sx * a * ax / denom
else:
return sx * (1 + np.log(a * ax)) / denom
# including to show useful ranges
test_shaper(shaper_bram, [1, 2, 3, 5, 7, 8, 9, 10])
test_shaper(shaper_bram2, [0.999, 0.8, 0.7, 0.5, 0.3, 0.15, 0.05, 0.001])
test_shaper(shaper_tsq, [0.9, 0.8, 0.7, 0.5, 0.3, 0.2, 0.1, 0.001], 1)
test_shaper(shaper_cubic, [-1, -0.5, 0, 0.25, 0.5], 1)
test_shaper(shaper_expo, [1, 2, 3, 4, 5, 6], 1)
test_shaper(shaper_sine, [-1, -0.5, -0.25, 0, 0.25, 0.5], 1)
test_shaper(shaper_reciprocal, [1, 2, 3, 4, 6, 8, 9, 10, 11, 12], 1)
test_shaper(shaper_tanh, [0, 0.5, 1, 2, 3, 4], 1)
test_shaper(shaper_alaw, np.exp(np.linspace(0, np.log(100), 10)))
test_shaper(shaper_ulaw, np.exp(np.linspace(0, np.log(300), 10)))
and here are their transfer functions and spectra. (spectra display is a little wonky, oh well.)

ooh and here’s a wonderful trick from robert bristow-johnson: family of polynomials approximating integral of (1 - x^2)^N by binomial expansion… this gives you a really nice progression of odd-order distortion
(matlab code for the moment i’m afraid)
rbjpoly.m (446 Bytes)
-
simple delay is also, well, simple. if delay time is a multiple of 1 sample, it is just a peek backwards into a ringbuffer. (see peek method in worb.h linked above for example.) for fractional delays, interpolate between neighboring samples with whatever interpolation is appropriate. (usually linear, or hermite spline, occasionally allpass if you want specific phase distortion effects, like in reverbs and phasers.)
-
risset glissando is the most complex here. it is basically simple (some sine waves and some rate / amplitude functions) but tuning them can take some time. (i’ll see if i can dig up some old notes on those…)