From f2b2e5a4af3b687aee51e35098fa05d50a6d2f49 Mon Sep 17 00:00:00 2001 From: James Harkins Date: Sat, 9 Dec 2023 09:08:03 +0800 Subject: [PATCH 1/3] Plugins/classlib: Add 'force' argument to RandID --- HelpSource/Classes/RandID.schelp | 93 +++++++++++++++++++++++++++- SCClassLibrary/Common/Audio/Noise.sc | 8 +-- server/plugins/NoiseUGens.cpp | 4 ++ 3 files changed, 99 insertions(+), 6 deletions(-) diff --git a/HelpSource/Classes/RandID.schelp b/HelpSource/Classes/RandID.schelp index c0a63b3415c..a6c33fd2233 100644 --- a/HelpSource/Classes/RandID.schelp +++ b/HelpSource/Classes/RandID.schelp @@ -18,6 +18,14 @@ method::kr, ir argument::id The random number generator ID. +argument::force +A Boolean flag. A positive value is 'true', and indicates that RandID should set the synth's random number generator ID on every evaluation, even if the input ID has not changed. Negative or zero is 'false', meaning that RandID should set the synth's random number generator ID only when the input ID changes. Default is 0 (false), for compatibility with versions prior to the addition of this feature. + +"Forcing" is useful in the special case where different parts of the same SynthDef graph need to use different random number generators. See examples below. + +note:: code::RandID.ir:: will always evaluate only once, at the start of the synth, so the code::force:: argument has no effect in that case. :: + + Examples:: code:: @@ -35,8 +43,8 @@ SynthDef("help-RandID", { arg out=0, id=1; //reset the seed of my rgen at a variable rate ( SynthDef("help-RandSeed", { arg seed=1910, id=1; - RandID.kr(id); - RandSeed.kr(Impulse.kr(FSinOsc.kr(0.2, 0, 10, 11)), seed); + RandID.kr(id); + RandSeed.kr(Impulse.kr(FSinOsc.kr(0.2, 0, 10, 11)), seed); }).add; ) @@ -50,6 +58,87 @@ x = Synth("help-RandSeed", [\id, 1]); //change the target randgen to 2 (affects right channel) x.set(\id, 2); +:: + +subsection:: Multiple random number generators and 'force' + +In most cases, randomized UGens in a SynthDef may all pull from the same random number generator. In this typical case, it's likely unnecessary to set the ID or re-seed at all. + +If, however, one is merging randomized signals from multiple synths into a single synth, and one wants to retain individual control over these separate streams, then the following sequence of evaluation is needed: + +numberedlist:: +## Set RNG ID (and re-seed). +## Calculate some randomized UGens. +## Set a different RNG ID (and re-seed). +## Calculate some other randomized UGens. +:: + +This entire sequence needs to be performed, in this order, on every control block. + +The original RandID implementation would set the ID only when the input changed. Here, even though the code looks like it should produce two identical pseudo-random sequences, they are different because both LFNoise0 units are using the same RNG (ID = 1). + +code:: +( +{ + var a, b; + RandID.kr(0); + RandSeed.ir(1, 88372); + a = LFDNoise0.ar(440); + RandID.kr(1); + RandSeed.ir(1, 88372); + b = LFDNoise0.ar(440); + [a, b, a - b] +}.plot; +) +:: + +By "forcing" the RNG ID to be updated on every control block, then the random sequences do match. + +code:: +( +{ + var a, b; + RandID.kr(0, 1); + RandSeed.ir(1, 88372); + a = LFDNoise0.ar(440); + RandID.kr(1, 1); + RandSeed.ir(1, 88372); + b = LFDNoise0.ar(440); + [a, b, a - b] +}.plot; +) +:: + +note:: In such cases, use a SynthDef and check the order of UGens carefully. +code:: +( +SynthDef(\test, { + var a, b; + RandID.kr(0, 1); + RandSeed.ir(1, 88372); + a = LFDNoise0.ar(440); + RandID.kr(1, 1); + RandSeed.ir(1, 88372); + b = LFDNoise0.ar(440); + Out.ar(1000, [a, b, a - b]) +}).dumpUGens +) + +// correct order! annotated: + +// one RNG chain, id = 0 +[0_RandID, control, [0, 1]] +[1_RandSeed, scalar, [1, 88372]] +[2_LFDNoise0, audio, [440]] + +// another RNG chain, id = 1 +[3_RandID, control, [1, 1]] +[4_RandSeed, scalar, [1, 88372]] +[5_LFDNoise0, audio, [440]] + +[6_-, audio, [2_LFDNoise0, 5_LFDNoise0]] +[7_Out, audio, [1000, 2_LFDNoise0, 5_LFDNoise0, 6_-]] :: +:: diff --git a/SCClassLibrary/Common/Audio/Noise.sc b/SCClassLibrary/Common/Audio/Noise.sc index ca001d258a5..41f29103f19 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, force = 0; + this.multiNew('control', id, force) ^0.0 // RandID has no output } - *ir { arg id=0; - this.multiNew('scalar', id) + *ir { arg id = 0, force = 0; + this.multiNew('scalar', id, force) ^0.0 // RandID has no output } } diff --git a/server/plugins/NoiseUGens.cpp b/server/plugins/NoiseUGens.cpp index 7bbe8464344..2f15e36f9dc 100644 --- a/server/plugins/NoiseUGens.cpp +++ b/server/plugins/NoiseUGens.cpp @@ -790,9 +790,13 @@ 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; + fire = true; + } + if (fire) { uint32 iid = (uint32)id; if (iid < unit->mWorld->mNumRGens) { unit->mParent->mRGen = unit->mWorld->mRGen + iid; From 83f813a48478698a87e807038883af04e8427ec0 Mon Sep 17 00:00:00 2001 From: James Harkins Date: Fri, 15 Dec 2023 09:33:44 +0800 Subject: [PATCH 2/3] Testsuite: Add TestNoiseUGens with RandID 'force' test --- testsuite/classlibrary/TestNoiseUGens.sc | 109 +++++++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 testsuite/classlibrary/TestNoiseUGens.sc diff --git a/testsuite/classlibrary/TestNoiseUGens.sc b/testsuite/classlibrary/TestNoiseUGens.sc new file mode 100644 index 00000000000..4579c63adb0 --- /dev/null +++ b/testsuite/classlibrary/TestNoiseUGens.sc @@ -0,0 +1,109 @@ +TestNoiseUGens : UnitTest { + var server; + + setUp { + server = Server(this.class.name); + } + + tearDown { + server.quit; + server.remove; + } + + test_RandSeed_sets_seed { + var synth; + var cond = CondVar.new; + var buffer; + var ok = false; + var err; + + var seed = 7777; + var size = 128; + // 'drop' b/c empirically, the server side drops one value + var testValues = Pseed(seed, Pwhite(-1.0, 1.0, inf).drop(1)) + .asStream.nextN(size); + var serverValues; + + server.bootSync; + + buffer = Buffer.alloc(server, size, 1); + server.sync; + + try { + synth = { + RandSeed.ir(1, seed); + RecordBuf.ar(WhiteNoise.ar, buffer, loop: 0, doneAction: 2); + Silent.ar(1) + }.play(server).onFree { + ok = true; + cond.signalAll + }; + cond.waitFor(0.1, { ok }); + if(ok.not) { Exception("synth timed out").throw }; + + ok = false; + buffer.getn(0, size, { |data| + serverValues = data; + ok = true; + cond.signalAll; + }); + cond.waitFor(0.1, { ok }); + if(ok.not) { Exception("b_getn failed").throw }; + } { |error| + if(error.isMemberOf(Exception)) { + err = error // my error + } { + buffer.free; + error.throw // unknown error + } + }; + + buffer.free; + this.assert( + ok and: { testValues == serverValues.as(Array) }, + err.tryPerform(\errorString) ?? { "random values on server were seeded identically to the language" } + ); + } + + test_RandID_forceArgSetsIDForEveryBlock { + var synth; + var cond = CondVar.new; + var ok = false; + var osc; + var countNonMatching; + var err; + var seed = 18624; + + server.bootSync; + + synth = { + var x, y; + var timer = Line.kr(0, 1, ControlDur.ir * 2, doneAction: 2); + var seed = 36163; + + #x, y = Array.fill(2, { |i| + RandID.kr(i, 1); + RandSeed.kr(1, seed); + WhiteNoise.ar + }); + + SendReply.kr(Done.kr(timer), '/count', A2K.kr(Integrator.ar((x absdif: y) > 0))); + Silent.ar(1) + }.play(server); + + osc = OSCFunc({ |msg| + countNonMatching = msg[3]; + ok = true; + cond.signalAll; + }, '/count', server.addr); + cond.waitFor(0.1, { ok }); + + osc.free; + + if(ok) { + this.assertEquals(countNonMatching, 0, "Two RNG IDs, seeded equally, should produce 0 non-matching values"); + } { + this.assert(false, "timed out") + }; + } +} From 49910c1848fb6edbd542da4a517df7ba4491035e Mon Sep 17 00:00:00 2001 From: James Harkins Date: Fri, 12 Jan 2024 10:27:53 +0800 Subject: [PATCH 3/3] plugins: RandID: Guard ZIN0(1) for old clients that don't supply 2 inputs --- server/plugins/NoiseUGens.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/plugins/NoiseUGens.cpp b/server/plugins/NoiseUGens.cpp index 2f15e36f9dc..0acc9764283 100644 --- a/server/plugins/NoiseUGens.cpp +++ b/server/plugins/NoiseUGens.cpp @@ -790,7 +790,7 @@ void RandID_Ctor(RandID* unit) { void RandID_next(RandID* unit, int inNumSamples) { float id = ZIN0(0); - bool fire = ZIN0(1) > 0; + bool fire = unit->mNumInputs >= 2 && (ZIN0(1) > 0.f); if (id != unit->m_id) { unit->m_id = id;