GrainUtils - sub-sample accurate EventScheduler and dynamic VoiceAllocator

Hello @dietcv,
I have this error when I try to build grainutils on Linux

[ 45%] Building CXX object CMakeFiles/Oscs_scsynth.dir/plugins/Oscs/Oscs.cpp.o
In file included from /home/fabien/SuperCollider_source/grainutils/plugins/Oscs/Oscs.hpp:3,
                 from /home/fabien/SuperCollider_source/grainutils/plugins/Oscs/Oscs.cpp:1:
/home/fabien/SuperCollider_source/grainutils/plugins/Utils/OscUtils.hpp:4:10: fatal error: utils.hpp: Aucun fichier ou dossier de ce nom
    4 | #include "utils.hpp"
      |          ^~~~~~~~~~~
compilation terminated.
gmake[2]: *** [CMakeFiles/Oscs_scsynth.dir/build.make:76 : CMakeFiles/Oscs_scsynth.dir/plugins/Oscs/Oscs.cpp.o] Erreur 1
gmake[1]: *** [CMakeFiles/Makefile2:205 : CMakeFiles/Oscs_scsynth.dir/all] Erreur 2
gmake: *** [Makefile:136 : all] Erreur 2

I just replaced

#include “utils.hpp”

by

#include “Utils.hpp”

in grainutils/plugins/Utils/OscUtils.hpp and it seems to work fine.

hey, thanks. I have corrected the typo, should all be fine now :slight_smile:

1 Like

Just felt like it, i have renamed UnitRand to UnitStep i guess that makes a lovely pair with UnitWalk and added an interp argument to UnitStep and UnitWalk for 0 = no interpolation or 1 = cosine interpolation. I miss the days not so long ago, when i was just resetting the whole repository instead of adding and making typos and testing after having created a new release and spamming the history haha.

Now for UnitStep you get:


// Random values with no interpolation (stepped)

(
{
    var phase, stepped;
    
    RandSeed.kr(\seedtrg.kr(1), \seed.kr(1000));
    
    phase = Phasor.ar(DC.ar(0), 1000 * SampleDur.ir);
    stepped = UnitStep.ar(phase, \interp.kr(0));
    
    [phase, stepped];
}.plot(0.041);
)

// Random values with cosine interpolation (smooth)

(
{
    var phase, smooth;
    
    RandSeed.kr(\seedtrg.kr(1), \seed.kr(1000));
    
    phase = Phasor.ar(DC.ar(0), 1000 * SampleDur.ir);
    smooth = UnitStep.ar(phase, \interp.kr(1));
    
    [phase, smooth];
}.plot(0.041);
)

and for UnitWalk:


// Random walk with no interpolation (stepped)

(
{
    var phase, stepped;

    RandSeed.kr(\seedtrg.kr(1), \seed.kr(1000));

    phase = Phasor.ar(DC.ar(0), 1000 * SampleDur.ir);
    stepped = UnitWalk.ar(phase, \step.kr(0.2), \interp.kr(0));

    [phase, stepped];
}.plot(0.041);
)

// Random walk with cosine interpolation (smooth)

(
{
    var phase, smooth;
    
    RandSeed.kr(\seedtrg.kr(1), \seed.kr(1000));
    
    phase = Phasor.ar(DC.ar(0), 1000 * SampleDur.ir);
    smooth = UnitWalk.ar(phase, \step.kr(0.2), \interp.kr(1));
    
    [phase, smooth];
}.plot(0.041);
)

Will probably add the interp argument for the ShiftRegister as well :slight_smile:

7 Likes

I have created a new release with some internal improvements :slight_smile:

GrainDelay is now using NEXTPOWEROFTWO() for m_bufSize with fast bitwise wrapping for the cubic interpolated buffer lookup
replaced sc_wrap between 0 and 1 by sc_frac in all the plugins
replaced std:floor and std::ceil with sc_floor and sc_ceil in all the plugins (these seem to have some internal optimisations)

3 Likes

right after i have made the update i have found the sc_CalcFeedback function which calculates the time for the feedback signal to decay by 60dB based on delayTime and decayTime. I have now replaced our feedback param in the GrainDelay with decayTime between 0.01 - 10 secs and reduced the maxDelayTime from 5.0 to 2.0 secs (i guess thats a more sensible value), will probably make another update soon. Im currently also looking at the MI Clouds which does have some nice param bindings like density which combines trigger frequency and overlap. Lets see what makes sense here to potentially reduce the current parameters to some macro controls :slight_smile:

2 Likes

Hello @dietcv ,

I’m using your gainutils tools for a few weeks now, I’m a big fan but I have a problem that prevents me from using it live and that’s very sad.
I built it on Linux (Ubuntu studio 22.04) with SuperCollider 3.14-dev and I experienced interpreter crash from time to time.
I have this message when it’s happening:

Interpreter has crashed or stopped forcefully. [Exit code: 11]

The interpreter crash is the worst scenario because it forces me to restart everything.
Next to that, when the interpreter doesn’t crash, I often have many xruns that appear in Jack’s logs:

21:21:32.291 Récupération désynchronisation (XRUN) (1).
Fri Oct 31 21:21:32 2025: ERROR: JackEngine::XRun: client = PulseAudio JACK Sink was not finished, state = Triggered
Fri Oct 31 21:21:32 2025: ERROR: JackAudioDriver::ProcessGraphAsyncMaster: Process error
Fri Oct 31 21:21:32 2025: ERROR: JackEngine::XRun: client = PulseAudio JACK Sink was not finished, state = Triggered
Fri Oct 31 21:21:32 2025: ERROR: JackAudioDriver::ProcessGraphAsyncMaster: Process error
Fri Oct 31 21:21:32 2025: ERROR: JackEngine::XRun: client = PulseAudio JACK Sink was not finished, state = Triggered
Fri Oct 31 21:21:32 2025: ERROR: JackAudioDriver::ProcessGraphAsyncMaster: Process error
21:21:33.770 Récupération de désynchronisation (XRUN) (6 sauté).
Fri Oct 31 21:21:33 2025: ERROR: JackEngine::XRun: client = PulseAudio JACK Sink was not finished, state = Triggered
Fri Oct 31 21:21:33 2025: ERROR: JackAudioDriver::ProcessGraphAsyncMaster: Process error
Fri Oct 31 21:21:33 2025: ERROR: JackEngine::XRun: client = PulseAudio JACK Sink was not finished, state = Triggered
Fri Oct 31 21:21:33 2025: ERROR: JackAudioDriver::ProcessGraphAsyncMaster: Process error
21:21:34.970 Récupération désynchronisation (XRUN) (8).
21:25:13.054 Récupération désynchronisation (XRUN) (9).
Fri Oct 31 21:25:13 2025: ERROR: JackEngine::XRun: client = SuperCollider was not finished, state = Triggered
Fri Oct 31 21:25:13 2025: ERROR: JackAudioDriver::ProcessGraphAsyncMaster: Process error
Fri Oct 31 21:25:13 2025: ERROR: JackEngine::XRun: client = SuperCollider was not finished, state = Triggered
Fri Oct 31 21:25:13 2025: ERROR: JackAudioDriver::ProcessGraphAsyncMaster: Process error
21:25:14.228 Récupération de désynchronisation (XRUN) (1 sauté).
...

Sometimes, I can see this warning in the post window (I assumed that’s from C++):
exception in GraphDef_Load: ios_base:failbit set: iostream stream error
I also got this message with another machine running on windows 10 (SuperCollider 3.13).

Unfortunately all these happens from time to time (5 times last week) and I can’t manage to isolate a chunk of code to reproduce these behaviors systematically.
So I can’t affirmate that it’s comming from grainutils but all I can tell is that I never encounter these problems before, it started after installing and using grainutils and it doesn’t happen when I don’t use it.

hey, thanks for reporting. unfortunately i have no idea what this is about. I think it would help to have a reproducible example.

sorry for pinging, but do you have an idea @Spacechild1 ?

I have reconsidered this, i think delayTime based on T60 makes sense for a conventional feedback delay effect, but in our case the overlap param already controls the sustain and i therefore think having the direct feedback control between 0 and 1 is the better choice here.

I have done a lengthy refactor of the library and added UnitUSR and Disperser.
You can grab the latest release here:

The UnitUSR (Universal Shift Register) replaces the former ShiftRegister Ugen.
Now the shift register is clocked by a ramp signal as the input instead of a trigger and you have additional interpolation available. The Universal Shift Register could also be transformed into a Rungler by feeding back into itself, will experiment more with that and maybe release UnitRungler then.

(
{
	var phase, register;

	RandSeed.kr(\seedtrg.kr(1), \seed.kr(500));

	phase = Phasor.ar(DC.ar(0), 1000 * SampleDur.ir);
	register = UnitUSR.ar(
		phase: phase,
		chance: 0.5,
		length: 8,
		rotate: 1,
		interp: 0
	);

	[
		register[\bit3],
		register[\bit8]
	];

}.plot(0.02);
)

(
{
	var phase, register;

	RandSeed.kr(\seedtrg.kr(1), \seed.kr(500));

	phase = Phasor.ar(DC.ar(0), 1000 * SampleDur.ir);
	register = UnitUSR.ar(
		phase: phase,
		chance: 0.5,
		length: 8,
		rotate: 1,
		interp: 1
	);

	[
		register[\bit3],
		register[\bit8]
	];

}.plot(0.02);
)

The Disperser Ugen is based on an Allpass Cascade of 2nd order biquad allpass filters with control over filter q to change the steepness of the phase response and carefully calculated coefficients (often times phaser plugins are based on 1st order allpass filters where this option is not available, if you setup your cascade with Allpass2 from SC Plugins modulating the resonance will blow up the filter because of the coefficient calculation, not the case here!).
For some configurations it is similiar to a phaser with feedback for others its more similiar to the kilohertz disperser plugin which decorates your input with a chirp by a frequency dependent delay (spectral delay filter). Currently only possible to use control rate to modulate the params, will change that soon.

(
{
	var sig = Saw.ar(32.midicps * (2 ** TIRand.ar(-1, 1, Dust.ar(2))));
	Disperser.ar(
		input: sig,
		freq: 500,
		resonance: SinOsc.kr(0.3).linlin(-1, 1, 0, 1),
		mix: 1.0,
		feedback: 0.85,
	)!2 * 0.1
}.play;
)
3 Likes