Hello.
I’m thinking about a way where a Impulse.ar would stay within the initial timing even when changing the rate. Obviously when taking the Impulse.ar() and then changing the frequency, it would get out of time. So I came up with one solution to fix this but first let me explain the problem a bit further:
// move mouse on X-axis, metronom and signal not in time once the mouse was moved
(
{
//metronom
var met = Env.perc(0.01,0.1).ar(gate:Impulse.ar(1)) * SinOsc.ar()!2;
//signal
var sig_gate = Impulse.ar(2**MouseX.kr(4,0).round);
var sig = Env.perc(0.01,0.05).ar(gate:sig_gate) * WhiteNoise.ar()!2;
sig + met;
}.play;
)
One can see that the signal and the Metronom are not in the same timing, once the mouse is moved.
I came up with an solution for this, but I’m not satisfied with it.
I thought of replacing the Impulse.ar() with a Sweep.ar() and taking a modulo, and when ever the Sweep jumps back to zero, I will send a trigger. I do this by comparing the Sweep with itself on a Delay1.ar(). And i need to add a Impulse.ar(0), because this would skip the first hit. This solution works, but whenever I move the mouse, it will send an extra trigger, because the math I’ve implemented also recognizes a change in the Modulo as a trigger. So whenever the Mouse moves, a trigger is (most of the time) also sent. First here is an improved solution:
(
{
//metronom
var met = Env.perc(0.01,0.1).ar(gate:Impulse.ar(1)) * SinOsc.ar()!2;
//signal
var swp = Sweep.ar() % (2 ** MouseX.kr(-4,0).round);
var sig_gate = swp - Delay1.ar(swp) < 0 + Impulse.ar(0);
var sig = Env.perc(0.01,0.05).ar(gate:sig_gate) * WhiteNoise.ar()!2;
sig + met;
}.play;
)
I was curious, if somebody thought of this problem as well or would like to figure out a solution with me. To basically find a solution to Implement a Impulse that will stay in time with itself, even when the “frequency” or something analogous to it changes.
I hope the problem became clear and thanks for reading and any answers that might come.
Kind Regards!
For these kind of problems I would highly encourage you to start with a linear phase signal between 0 and 1 as your source of time instead of Impulse, which you then subdivide and can derive slopes and triggers from. In my GrainUtils library is a RampDivider Ugen which could be useful for your purpose.
Thanks for your response! And also for the GrainUtils!
I mean its what i thought to be my solution, the second block of code, I’ve shown to subdivide a linear slope as my source.
And my solution works but only when I change the subdivision dynamically, it creates an extra hit.
With the RampDivider, I don’t see exactly how it would help, since similiarily to a Impulse.ar it also just adapts a new rate.
({
//metronom
var met = Env.perc(0.01,0.1).ar(gate:Impulse.ar(1)) * SinOsc.ar()!2;
//signal
var swp = Phasor.ar(1,1/SampleRate.ir,0,1);
var sig_gate = RampDivider.ar(swp, (2**(MouseX.kr(-4,0).round)))-0.001;
//works a bit better with a reset synced to the swp
// var sig_gate = RampDivider.ar(swp, (2**(MouseX.kr(-4,0).round)), swp-0.001)-0.001;
var sig = Env.perc(0.01,0.05).ar(gate:sig_gate) * WhiteNoise.ar()!2;
sig + met;
}.play;
)
Autosync also doesn’t seem to help with getting out of the tempo. I’m not sure why it doesnt, it seems in the helpfile-example, that it could work like this.
One way would be to sync the RampDivider with the reset. (one can hear by uncommenting the sig_gate).
But the issue persists that between the moment of a change in the ratio, and the reset, the timing will be messed up.
Sometimes in SC-land, we have a bias in the direction of avoiding the generation of triggers (or events in the language) that aren’t producing an audible result. In this case, that’s leading to the problem being expressed in terms of synchronizing two Impulse units, which is proving difficult.
Modular synth folks would express the same problem in terms of generating a fast “leader” clock signal (pulse train), filtering out the ones that aren’t needed using a clock divider. SC has a PulseDivider unit, but changing its div input doesn’t guarantee sync with the metronome either.
But you can roll your own looping pulse count, and use “modulo == 0” to detect subdivision boundaries. |==| is “lazy equals” (documented under Object, IIRC) – you need “lazy equals” here to perform the comparison on the signals, rather than the objects in the language.
(
a = { |tempo = 1, beatsPerBar = 4, ppq = 32|
var pulseTrain = Impulse.ar(tempo * ppq);
var count = Demand.ar(pulseTrain, 0,
Dseq([Dseries(0, 1, ppq * beatsPerBar)], inf)
);
// not single-sample impulses but should still work as a trigger
// these are for the metronome
var barTrig = count |==| 0;
var beatTrig = (count % ppq) |==| 0;
// the variable one
// half notes at left, 64th notes at right
// half note = ppq*2 or ppq * (2 ** 1), 64th = ppq/16 or ppq * (2 ** -4)
// explicit K2A is required!
// without it, subdiv will be K2A'ed implicitly for subdivTrig
// and this linear interpolation causes extraneous triggers
var subdiv = ppq * (2 ** K2A.ar(MouseX.kr(1, -4, 0)).round);
var subdivTrig = (count % subdiv) |==| 0;
var env = Env.perc(0.01, 0.1);
var metro = SinOsc.ar(
440 * Latch.ar(barTrig + beatTrig, beatTrig)
) * EnvGen.ar(env, beatTrig);
var pulses = SinOsc.ar(440 * 2.5)
* EnvGen.ar(env, subdivTrig);
((metro + pulses) * 0.1).dup
}.play;
)
I think this is doing what you want? Anyway this struck me as one of those cases where turning the problem on its head leads to an easier solution.
Oh, this is very much true. I was thinking only towards the same direction!
Thanks for the Response and yes this is a good solution for me!
And good to know about the lazy equals. I’ve been looking for them for a while for inside a SynthDef.
I’ve reduced the code a bit so it’s in the same style as me previous code for anyone who is reading in the future:
(
{
//metronom
var met = Env.perc(0.01,0.1).ar(gate:Impulse.ar(1)) * SinOsc.ar()!2;
//signal
var cnt = PulseCount.ar(Impulse.ar(64))-1;
var sig_gate_subdiv = 4*(2**K2A.ar(MouseX.kr(0,4)).round);
var sig_gate = (cnt%sig_gate_subdiv) |==| 0;
var sig = Env.perc(0.01,0.02).ar(gate:sig_gate) * WhiteNoise.ar()!2;
(sig + met) * 0.2;
}.play;
)