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 :slight_smile: .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,

  1. 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.)

shaper_alaw shaper_bram shaper_bram2 shaper_cubic shaper_expo shaper_reciprocal shaper_sine shaper_tanh shaper_tsq shaper_ulaw

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)

  1. 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.)

  2. 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…)

6 Likes

FWIW, I’ve ported a few of the mutable modules to SuperCollider (among those is Plaits).
Sources are here:

UGens comiled for macos (scroll down all the way):
https://vboehm.net/downloads/

Sound demos:

11 Likes

That sounds good!

I’ve not really attempted making a plugin myself, having been sucked in to other stuff, since starting this discussion, but when I do get around to it, some kind of basic “insert your audio-rate algorithm here” template for custom SuperCollider UGens would be super useful, I think.

1 Like

Wow, cool!!

How feasible would it be to build these for Norns’ Arm processor?

hm, I guess it shouldn’t be too hard, but I haven’t tired it - so have no idea really.

If I have some free time later I will share the steps needed to get stuff building on Norns, major life event stuff happening today so more than likely I will get around to it tomorrow. It’s pretty straight forward if you’ve use make before.

3 Likes

I compiled some other ugens a long while back. I might give these a go later today if I have time.

3 Likes

oh man, a clouds ugen would be awesome!

1 Like

so I just complied MiClouds but not entirely sure what to do next to test it

I forget where I need to install these (and do I include the cpp files, or just the .so and .sc files?)

@Justmat do you wanna try?

EDIT (a few hours later): I have a very basic MiRings engine working.

4 Likes

Volker – these are awesome TY!

Clouds engine WIP

Thanks geplanteobsoleszenz!!

Edit: FWIW - I couldn’t get Plaits to compile on my pi, so would love to chat with someone to help figure that out.

(and I totally stole this cloud from @Justmat’s Showers :laughing: Thx Matt! )

6 Likes

@shreeswifty thanks, Patrick

@okyeron great you got it working!
Can’t really help with the pi, but what’s the compile error you’re getting?

here’s where it barfed on make

[ 96%] Building CXX object CMakeFiles/MiPlaits.dir/MiPlaits.cpp.o
[100%] Linking CXX shared module MiPlaits.so
c++: error: Accelerate: No such file or directory
c++: error: unrecognized command line option ‘-framework’
make[2]: *** [CMakeFiles/MiPlaits.dir/build.make:549: MiPlaits.so] Error 1
make[1]: *** [CMakeFiles/Makefile2:73: CMakeFiles/MiPlaits.dir/all] Error 2
make: *** [Makefile:84: all] Error 2

gcc (Raspbian 8.3.0-6+rpi1) 8.3.0
pulled supercollider source today but not sure what version that source is. (3.11.0?)

EDIT: Accelerate is MacOS only I’m reading??

EDIT 2: can I just delete target_link_libraries(${PROJECT_NAME} PUBLIC "-framework Accelerate") from CMakeLists.txt?

EDIT 3: removing that seems to have done the trick and it finished compiling without errors

EDIT 4: MiVerb hits this error:

[ 50%] Building CXX object CMakeFiles/MiVerb.dir/MiVerb.cpp.o
/home/we/mi-UGens/MiVerb/MiVerb.cpp:31:10: fatal error: SC_PlugIn.h: No such file or directory
 #include "SC_PlugIn.h"
          ^~~~~~~~~~~~~
compilation terminated.

which is odd since the others have that and compiled ok?

1 Like

ah, yes, I used apple’s vDSP_sve for trigger detection to speed up the vector summing. You can comment it out in the cmakeLists file. In the source you should probably delete the accelerate include at the beginning and replace the vDSP_sve call with a simple for loop which sums all values from the trigger input.

1 Like

EDIT 4: MiVerb hits this error:

sorry, that’s a leftover - comment out the “set(SC_PATH /Users/vb…” in cmakeLists.txt and provide your own path to sc sources.

2 Likes

Nice. Got the other compiled now. Thanks a bunch.

I’ll package these up somehow tomorrow to share with the class :slight_smile:

4 Likes

Have you managed to compile the whole set for Pi, then?

Yep. Will put everything together and post tomorrow (way past my bedtime already)

Have basic Norns engines + demo scripts working with rings and clouds so far.
:grin:

8 Likes

20 characters of That’s great!!