hey, would someone know how i can get this pulsedivider attempt to output an initial trigger and also distribute the phases correctly accross the channels:
(
var multiChannelTrigger = { |numChannels, trig|
var count = Demand.ar(trig, 0, Dseries(0, 1, inf));
numChannels.collect{ |chan|
Trig1.ar(BinaryOpUGen('==', (count + (numChannels - 1 - chan) + 1) % numChannels, 0), SampleDur.ir);
};
};
var multiChannelPhase = { |triggers, windowRate|
triggers.collect{ |localTrig|
var hasTriggered = PulseCount.ar(localTrig) > 0;
var localPhase = Sweep.ar(localTrig, windowRate * hasTriggered);
localPhase * (localPhase < 1);
};
};
{
var numChannels = 4;
var tFreq = \tFreq.kr(400);
var trig = Impulse.ar(tFreq);
var triggers = multiChannelTrigger.(numChannels, trig);
var windowRate = tFreq / min(\overlap.kr(1), numChannels);
var phase = multiChannelPhase.(triggers, windowRate);
triggers;
}.plot(0.01)
)
no initial trigger:
correct distribution of phases across the channels:
pulsedivider seems not to work with an initial trigger of Impulse.ar(0). When i use a phasor and start it one sample frame earlier and then creating triggers with rampToTrig it seems to work fine:
(
var multiChannelTrigger = { |numChannels, trig|
numChannels.collect{ |chan|
PulseDivider.ar(trig, numChannels, numChannels - 1 - chan);
};
};
var rampToTrig = { |phase|
var history = Delay1.ar(phase);
var delta = (phase - history);
var sum = (phase + history);
var trig = (delta / sum).abs > 0.5;
Trig1.ar(trig, SampleDur.ir);
};
{
var numChannels = 4;
var rate = 400;
var phase = (Phasor.ar(DC.ar(0), rate * SampleDur.ir) - SampleDur.ir).wrap(0, 1);
var trig = rampToTrig.(phase);
multiChannelTrigger.(numChannels, trig);
}.plot(0.01)
)
thanks for clarifying. I guess this i then also true for Trig1 which i have tried to use instead of Pulsedivider because of this issue.
The Phasor / rampToTrig approach is nice, however tricky when beeing used together with Demand Ugens, which need the initial trigger. If you then additionaly use the slope of the phasor with Delay1 and create a trigger from it, once more delayed one sample. You have three different unsynced triggers Impulse.ar(0), rampToTrig half a sample later and the trigger from the slope one sample later. This is currently messing up my multichannel sequencer.
The code you posted in the first post of this thread works with SuperCollider 3.12.0 on Raspberry OS and produces the following result:
However, in
SuperCollider 3.13.0 on macOS 13.6.4 and
SuperCollider 3.14.0_dev on macOS 13.6.4, Ubuntu 22.04 and Windows,
the same code returns the following error:
ERROR: Message 'addKr' not understood.
RECEIVER:
nil
ARGS:
Symbol 'tFreq'
Integer 400
PROTECTED CALL STACK:
Meta_MethodError:new 0x14071f240
arg this = DoesNotUnderstandError
arg what = nil
arg receiver = nil
Meta_DoesNotUnderstandError:new 0x140721580
arg this = DoesNotUnderstandError
arg receiver = nil
arg selector = addKr
arg args = [ tFreq, 400 ]
Object:doesNotUnderstand 0x140543300
arg this = nil
arg selector = addKr
arg args = nil
NamedControl:init 0x1600bb940
arg this = a NamedControl
var prefix = nil
var str = tFreq
Meta_NamedControl:new 0x1600bab00
arg this = NamedControl
arg name = tFreq
arg values = [ 400 ]
arg rate = control
arg lags = nil
arg fixedLag = false
arg spec = nil
var res = nil
a FunctionDef 0x1504254a8
sourceCode = "<an open Function>"
var numChannels = 4
var tFreq = nil
var trig = nil
var triggers = nil
var windowRate = nil
var phase = nil
a FunctionDef 0x140ba3a00
sourceCode = "<an open Function>"
a FunctionDef 0x1409f9b40
sourceCode = "<an open Function>"
a FunctionDef 0x1601f5e00
sourceCode = "<an open Function>"
Function:prTry 0x1409fcf40
arg this = a Function
var result = nil
var thread = a Thread
var next = nil
var wasInProtectedFunc = false
CALL STACK:
DoesNotUnderstandError:reportError
arg this = <instance of DoesNotUnderstandError>
< closed FunctionDef >
arg error = <instance of DoesNotUnderstandError>
Integer:forBy
arg this = 0
arg endval = 0
arg stepval = 2
arg function = <instance of Function>
var i = 0
var j = 0
SequenceableCollection:pairsDo
arg this = [*2]
arg function = <instance of Function>
Scheduler:seconds_
arg this = <instance of Scheduler>
arg newSeconds = 19.533960583
Meta_AppClock:tick
arg this = <instance of Meta_AppClock>
var saveClock = <instance of Meta_SystemClock>
Process:tick
arg this = <instance of Main>
^^ ERROR: Message 'addKr' not understood.
RECEIVER: nil
Only SC3.12.0 on Raspberry Pi seems to show the first pulse: the thick vertical line.
Plotter’ in SC3.12.x gives a different result, at least for this code, depending on the OS.
Plotter’ in SC3.13.0 and above results in the following error:
Actually BinaryOpUGen can’t know it’s previous state (without making assumptions), so it won’t ever work as an initial trigger (important caveat!).
If you don’t mind a 1-sample delay in your system, this works (3.13, 3.12.2):
(
var multiChannelTrigger = { |numChannels, trig|
var count = Demand.ar(trig, 0, Dseries(0, 1, inf));
numChannels.collect{ |chan|
Trig1.ar(
TDelay.ar( // <<<<<<<<<<<< ADDED
BinaryOpUGen('==', (count + (numChannels - 1 - chan) + 1) % numChannels, 0),
SampleDur.ir),
SampleDur.ir);
};
};
var multiChannelPhase = { |triggers, windowRate|
triggers.collect{ |localTrig|
var hasTriggered = PulseCount.ar(localTrig) > 0;
var localPhase = Sweep.ar(localTrig, windowRate * hasTriggered);
localPhase * (localPhase < 1);
};
};
{
var numChannels = 4;
var tFreq = \tFreq.kr(400);
// var trig = TDelay.ar(Impulse.ar(tFreq), 1/s.sampleRate);
var trig = Impulse.ar(tFreq);
var triggers = multiChannelTrigger.(numChannels, trig);
var windowRate = tFreq / min(\overlap.kr(1), numChannels);
var phase = multiChannelPhase.(triggers, windowRate);
triggers;
}.plot(0.01)
)
Note, you might normally more simply use Delay1 instead of TDelay with a 1-sample delay (although TDelay is likely slightly more efficient), but you would need unmerged fixes to Delay1.
My suggestion on this, years ago, was that the only places in the UGens codebase that know whether the incoming data are to be used as a trigger are the trigger inputs themselves – therefore the UGen receiving the trigger is responsible for initializing mPrevTrig to 0 and nowhere else in the code should be responsible for this.
If trigger-receiving UGens actually did this consistently, then BinaryOpUGen would work as a trigger.
The problem with PulseDivider is that the Ctor can leave mPrevTrig nonzero, when the only valid value for mPrevTrig at the end of the Ctor is 0.
Sorry I misspoke about binop as a trigger. I got turned around thinking it was reading in the trigger, but it is generating it. And prevA/B are used for k-to-a interpolation, not triggering. So it works as a trigger in the original example.
The problem is with the initialization of Trig1. I tested it on 3.14-dev with the pending fix to Trig1 I linked to above and it works as @dietcv originally posted.
In any case the workaround I posted should still work in the meantime (assuming 1-sample delay is OK).
Trig1 and Delay1/2 should both be in 3.14, PulseDivider is part of TriggerUGens, which isn’t currently in scope of the init sample fix “project”, so that one is TBD…