Different RandSeed for the same SynthDef

Hello, if I have a SynthDef with a 30 randomized params, how can I decide to control the first 15 params by a specific RandSeed and the 15 other by another one ? So far, I am just able to use one RandSeed for all the synth.
Thanks a lot

Anyone please ? Impossible de find any examples on the documentation or on the forums. So like:

(
SynthDef(\Rand, {

/// one RandSeed for thoses :
var aaa =  rrand(1, 10);    
var aaa2 = rrand(10, 20);

/// another RandSeed for thoses :
var bbb  = rrand(100, 1000); 
var bbb2 = rrand(1000, 2000);

/// but both staying in the same SynthDef

}).add;

)

Why would you want to do this? Randomness is supposed to be uniform, so it makes no difference to the distribution if you change the seed, or get the next value.

The reason why you would want to set a random seed in the first place is so that many synths can have the same random value for each subsequent call to the random number generator. If many synths are sharing the same parameters, then it would be better to generate all the values at once, store them, then initialise the synths with the same values.

You’re slightly mistaken about how this is working: rrand is called when you BUILD the SynthDef - this is controlled by thisThread.randSeed - setting this seed will control the seed value for future calls to rrand, so you can do something like this:

thisThread.randSeed = 10;
var aaa =  rrand(1, 10);    
var aaa2 = rrand(10, 20);

thisThread.randSeed = 11;
var bbb  = rrand(100, 1000); 
var bbb2 = rrand(1000, 2000);

These random values are now hard-coded into your SynthDef structure - not sure if this is desired or not.

RandSeed resets the random seed used for UGen’s for a Synth while it’s running. This would apply to things like TRand and WhiteNoise. You can’t straightforwardly use two RandSeed UGens in your SynthDef, since UGens aren’t necessarily run in the order that you specify them in code, so placing a RandSeed in between your two sets of parameters may not mean much - and in any case, random values are random values, changing the seed in between will just result in more random values :). If you want reproducible “random” values for a group of parameters, I’ve used a pattern like this:

SynthDef(\random, {
   var baseSeed, makeRand, seed1, seed1, a, b, c,d;
   baseSeed = rrand(0, 1000); // this can be deterministic also....
   makeRand  = {
        |min, max, seed|
        baseSeed = baseSeed  + 1;
        Hasher.ar(baseSeed + (seed * 1000)).linlin(-1, 1, min, max)
   };
   seed1 = \seed1.kr(10);
   seed2 = \seed2.kr(11);

   a = makeRand.(0, 100, seed1);
   b = makeRand.(0, 400, seed1);
   c = makeRand.(0, 10, seed2);
   d = makeRand.(-0.3, 0.3, seed2);
});

Hasher produces a (deterministic) random number based on an input value. Your input value is a combination of (1) a synth-global value you increment each time you run the function (so that each makeRand call produces a different value), and (2) a seed value you pass in. With this setup, you can bump a seed to re-randomize ALL the values that use that seed. Your values will be random, but depend on baseSeed - this can be hard-coded (so the “random” values are the same ever time), or also randomized in whatever controlled way you want.

5 Likes

That’s clear, thank you taking your time to clarify this.

Sorry to reopen this, thanks a lot for the explanation.

How would it be possible to run inside a SynthDef two LFNoise0.ar(1) and LFNoise0.ar(2) which have different frequencies but outputs the same list of values?

With two SynthDefs I can make this, but I have no idea how to make it within a single SynthDef:

(
SynthDef(\test, {arg seed, freq;
	RandSeed.ir(1, seed);
	Out.ar(0, SinOsc.ar(LFNoise0.kr(freq).range(440, 880), 0, 0.4));
}).add;
)
a = Synth(\test, [\freq, 3, \seed, 1000]);
b = Synth(\test, [\freq, 2, \seed, 1000]);

But setting the RandSeed only makes each instance static, but not all of them equal:

{RandSeed.ir(1, 500);[LFNoise0.ar(500),LFNoise0.ar(1000), LFNoise0.ar(2000), LFNoise0.ar(4000)]}.plot

Moreover, how would it be possible to run inside a SynthDef two LFNoise0.ar(1) and LFNoise0.ar(2) which have different frequencies but outputs the same list of values alongside with other two LFNoise0.ar(3) and LFNoise0.ar(4) which have different frequencies and also different list of values (different RandSeed for each group of oscillators)?

1 Like

as far as I can see, this is currently not possible:


(
{ 
	var a, b;
	RandID.ir(0);
	RandSeed.ir(0,1789);
	a = LFNoise1.ar(2000);
	RandID.ir(1);
	RandSeed.ir(0,1789);
	b = LFNoise1.ar(2000);
	[a, b]
}.plot
)

This doesn’t produce the same form twice, I think because the rand id is set only once and not at each cycle. This was one of my first projects in supercollider, and I didn’t think of that (if I remember correctly).

1 Like

Would it be interesting to have a variant of LFNoise0 that has a seed (maybe as a parameter) that cold be reused/shared?

I also came across other pseudo-random algorithms, that claim to be more efficient and more ‘random’, focused on the opposite, splitting generators. (Not sure any of this would be useful for sc)

1 Like

@hobbes Lazy as i am. I would use something like CuspN or for this. :grinning:

edit: or pick one of these ChaosGen.subclasses and try to find some initial values that sounds interresting.

2 Likes

Perlin noise can be a good candidate for very controllable and deterministic noise.
https://doc.sccode.org/Classes/Perlin3.html

Though iirc there are some settings when generating perlin noise that are not exposed here, so it may have limited flexibility?

1 Like

I second that.

But it also reminded me of colleagues who recorded pink noise from a beautiful analog generator, so they could work with “truly random noise.” I’m not sure it did make any difference, but, as soon as you recorded it, is it still random or just a sequence of numbers in your computer?

1 Like

It would be cool to have this as a new feature, if possible!

Cunning solution, I will definitively try this out!

This implementation of Perlin noise have some bugs, right? I’ve tried to explore it but it shows some really strange behavior like triangular waveforms and impulse trains…

Please elaborate further! If you can post some code example of this it would be nice as well!

I’ve checked the sources, and in principle, it would work if we call the RandID for every block:


(
{ 
	var a, b;
	RandID.kr(0);
	RandSeed.ir(0,1789);
	a = LFNoise1.kr(200);
	RandID.kr(1);
	RandSeed.ir(0,1789);
	b = LFNoise1.kr(200);
	[a, b] // should be the same
}.plot(0.5)
)

The problem seems to be in the setting of the RandID:

But this would have to be checked. I can’t do this myself at the moment.

1 Like

Thanks for the help!! Unfortunately, even when the RandSeed is the same, the results are not equal…

(
{ 
	var a, b;
	RandID.kr(0);
	RandSeed.ir(0,1789);
	a = LFNoise1.kr(200);
	RandID.kr(1);
	RandSeed.ir(0,1789);
	b = LFNoise1.kr(200);
	[a, b] // a!=b
}.plot(0.5)
)

(
{ 
	var a, b;
	RandID.kr(0);
	RandSeed.ir(0,1789);
	a = LFNoise1.kr(200);
	RandID.kr(0);
	RandSeed.ir(0,1789);
	b = LFNoise1.kr(200);
	[a, b] // a!=b
}.plot(0.5)
)

All makes sense to me, I agree. In the case of Gendys, a good splittable generator, providing several parallel deterministic high-quality generators would be be optimal option. We can’t do without it, nathan, someone has to fix it.

Well, you avoid threatening seeds. That’s exactly the point, statistically it’s as good as independent generators (which is not the case with other algorithms)

Yes, exactly this was my point – it doesn’t work, but should.

I think I’ve found the problem.

void RandID_next(RandID* unit, int inNumSamples) {
    float id = ZIN0(0);

    if (id != unit->m_id) {
        unit->m_id = id;
        uint32 iid = (uint32)id;
        if (iid < unit->mWorld->mNumRGens) {
            unit->mParent->mRGen = unit->mWorld->mRGen + iid;
        }
    }
    ZOUT0(0) = 0.f;
}

should be

void RandID_next(RandID* unit, int inNumSamples) {
    float id = ZIN0(0);

    if (id != unit->m_id) {
        unit->m_id = id;
   }
   if (iid < unit->mWorld->mNumRGens) {
        uint32 iid = (uint32)id;
        unit->mParent->mRGen = unit->mWorld->mRGen + iid;
   }
    ZOUT0(0) = 0.f;
}

I just can’t test it right now.

It would be nice to have methods for Hasher that impement these different behaviours.

Indeed, it’s necessary to move the uint32 iid part outside of the if (id != unit->m_id) – although you’d have to reverse the order of if (iid < unit->mWorld->mNumRGens) and uint32 iid = (uint32)id; because the posted version performs a < check on an uninitialized variable.

void RandID_next(RandID* unit, int inNumSamples) {
    float id = ZIN0(0);

    if (id != unit->m_id) {
        unit->m_id = id;
    };
    uint32 iid = (uint32)id;
    if (iid < unit->mWorld->mNumRGens) {
        unit->mParent->mRGen = unit->mWorld->mRGen + iid;
    }
    ZOUT0(0) = 0.f;
}

This would raise CPU usage for every synth using RandID.kr – currently, unit->mParent->mRGen is set only when the ID changes, but after a change such as this, it will be set in every control block, whether changing or not. So it might be nice to add a second input, a flag, determining whether to fire every time or not, such as bool fire = ZIN0(1) > 0 and, in the id != block, set fire = true. Then if(fire && (iid < unit->mWorld->mNumRGens)). A positive input would be true in every block; a nonpositive input would initially be false, but a change in ID would override that. Default 0 for compatibility with current behavior.

It’s also necessary to send a nonzero trigger to RandSeed. The examples are using trig = 0, so RandSeed never fires, and the seeds are ignored.

With some debugging output, then I get:

init rand id 0 with seed 1789
got 0.758146 from rand id 0
init rand id 1 with seed 1789
got 0.758146 from rand id 1  -- same
got 0.524698 from rand id 0
got 0.524698 from rand id 1  -- same

Diffs:

diff --git a/server/plugins/NoiseUGens.cpp b/server/plugins/NoiseUGens.cpp
index 7bbe84643..50c581d44 100644
--- a/server/plugins/NoiseUGens.cpp
+++ b/server/plugins/NoiseUGens.cpp
@@ -790,13 +790,15 @@ void RandID_Ctor(RandID* unit) {
 
 void RandID_next(RandID* unit, int inNumSamples) {
     float id = ZIN0(0);
+    bool fire = ZIN0(1) > 0;
 
     if (id != unit->m_id) {
         unit->m_id = id;
-        uint32 iid = (uint32)id;
-        if (iid < unit->mWorld->mNumRGens) {
-            unit->mParent->mRGen = unit->mWorld->mRGen + iid;
-        }
+       fire = true;
+    };
+    uint32 iid = (uint32)id;
+    if (fire && (iid < unit->mWorld->mNumRGens)) {
+        unit->mParent->mRGen = unit->mWorld->mRGen + iid;
     }
     ZOUT0(0) = 0.f;
 }

diff --git a/SCClassLibrary/Common/Audio/Noise.sc b/SCClassLibrary/Common/Audio/Noise.sc
index ca001d258..ab37e62bf 100644
--- a/SCClassLibrary/Common/Audio/Noise.sc
+++ b/SCClassLibrary/Common/Audio/Noise.sc
@@ -33,12 +33,12 @@ RandSeed : WidthFirstUGen {
 
 RandID : WidthFirstUGen {
        // choose which random number generator to use for this synth .
-       *kr { arg id=0;
-               this.multiNew('control', id)
+       *kr { arg id = 0, alwaysUpdate = 0;
+               this.multiNew('control', id, alwaysUpdate)
                ^0.0            // RandID has no output
        }
-       *ir { arg id=0;
-               this.multiNew('scalar', id)
+       *ir { arg id = 0, alwaysUpdate = 0;
+               this.multiNew('scalar', id, alwaysUpdate)
                ^0.0            // RandID has no output
        }
 }

hjh

Very good! Can you make a pull request for it?

1 Like