It turns out that there is a not-quite-fully-solvable problem with this approach.
It’s a good case of isolating components for testing. You would never discover the cause only by listening to the result. The only way to debug this is to look at all four channels – what is happening with the components going into the envelopes.
“.plot” won’t work with Pmono (which is essential to your case). So, we also have to take apart plotting – create a buffer, and RecordBuf instead of Out. Also, it doesn’t make sense to pass envelopes by themselves out to the hardware, so let’s create a bus too.
~recBuf = Buffer.alloc(s, s.sampleRate, 4);
~recBus = Bus.audio(s, 4);
Four channels because, to debug this type of problem, you need to make sure each channel is doing the right thing.
s.boot;
(
// custom granular envelopes
~getCustomEnvs = {
var tri = Env([0, 1, 0], [1, 1], \lin);
var perc = Env.perc(0.01, 1, 1, -4);
var customEnvs = [tri, perc];
customEnvs.collect{|env| Buffer.sendCollection(s, env.discretize(2048), 1) };
};
~customEnvs = ~getCustomEnvs.();
)
~recBuf = Buffer.alloc(s, s.sampleRate, 4);
~recBus = Bus.audio(s, 4);
(
SynthDef(\pulseDividerRec, { |envBuf|
var tFreq = \tFreq.kr(1);
var trig = Impulse.ar(tFreq);
var maxOverlap = 4;
var grainRate = tFreq / \overlap.kr(1);
var trigs = PulseDivider.ar(trig, maxOverlap, (0 .. maxOverlap-1));
var hasTriggered = PulseCount.ar(trigs) > 0;
var phase = Sweep.ar(trigs, Latch.kr(grainRate, trigs) * hasTriggered);
var grainEnv = BufRd.ar(1, Latch.kr(envBuf, trigs), phase * BufFrames.kr(envBuf));
RecordBuf.ar(grainEnv, ~recBuf, loop: 0);
}).add;
)
// run the Pmono for at least 1 sec
// save time and trouble: use Pfindur to stop it automatically
(
y = Pfindur(1, Pmono(\pulseDividerRec,
\freq, 440,
\bufIndex, Pseq([1, 0], inf),
\envBuf, Pfunc{ |ev| ~customEnvs[ev[\bufIndex]] },
\dur, 0.1,
\tFreq, Pfunc{ |ev| ev[\dur].reciprocal },
\overlap, 1.00,
\amp, 0.1,
\out, ~recBus,
)).play;
)
~recBuf.plot;
Oddly, at this point, only the fourth channel is active.
That’s definitely not right. (So, this part of troubleshooting depends on knowing what is the expected result – which is, that the triggers are distributed, round robin, among the four channels, and each trigger produces an envelope within its channel. We aren’t seeing that, hence, there’s a bug, hence, dig deeper.)
So, back up a layer, to phase – change to RecordBuf.ar(phase, ~recBuf, loop: 0);
and run the test again.
… and the phase is active only in the fourth channel, and only one every four triggers. (So the repeated envelopes are coming from BufRd loop = 1 – actually that’s another bug – you don’t want BufRd to loop!)
But if you check trigs
and hasTriggered
this way, they are behaving correctly.
Pull out the Sweep rates, and run it again:
(
SynthDef(\pulseDividerRec, { |envBuf|
var tFreq = \tFreq.kr(1);
var trig = Impulse.ar(tFreq);
var maxOverlap = 4;
var grainRate = tFreq / \overlap.kr(1);
var trigs = PulseDivider.ar(trig, maxOverlap, (0 .. maxOverlap-1));
var hasTriggered = PulseCount.ar(trigs) > 0;
var rates = Latch.kr(grainRate, trigs) * hasTriggered;
var phase = Sweep.ar(trigs, rates);
var grainEnv = BufRd.ar(1, Latch.kr(envBuf, trigs), phase * BufFrames.kr(envBuf));
RecordBuf.ar(rates, ~recBuf, loop: 0);
}).add;
)
Only the 4th channel rises to 10.
At this point, I was stumped for a while, until realizing that you have audio rate triggers and kr Latches. If a trigger occurs in the middle of a control block (there’s a 63/64 probability of this!) then Latch.kr won’t respond.
So when using ar inputs, Latch needs to be fully ar, all the way (including every input).
But – and here’s the bad news – BufRd grabs its buffer number only at control rate. To sequence envelope buffers, the buffer number needs to switch exactly at the moment of the trigger, which might occur in the middle of a control block.
So, with this approach, you would be limited to control rate triggers. This might be acceptable if you reduce the block size: 64/44100 is about 1.45 ms, but 16/44100 is about 0.36 ms, where jitter would be undetectable to the ear.
(
SynthDef(\pulseDividerRec, { |envBuf|
var tFreq = \tFreq.kr(1);
var trig = Impulse.kr(tFreq);
var maxOverlap = 4;
var grainRate = tFreq / \overlap.kr(1);
var trigs = PulseDivider.kr(trig, maxOverlap, (0 .. maxOverlap-1));
var hasTriggered = PulseCount.kr(trigs) > 0;
var rates = Latch.kr(grainRate, trigs) * hasTriggered;
var phase = Sweep.ar(trigs, rates);
var envBufNums = Latch.kr(envBuf, trigs);
var grainEnv = BufRd.ar(1, envBufNums, phase * BufFrames.kr(envBuf), loop: 0);
RecordBuf.ar(grainEnv, ~recBuf, loop: 0);
// envBufNums.poll(trigs);
}).add;
)
Syncing Impulse.kr with the language clock is likely to be a problem, so I’d suggest passing in the trigger from the pattern.
(
SynthDef(\pulseDivider, { |envBuf|
var trig = \trig.tr(0);
var tFreq = \tFreq.kr(1);
var maxOverlap = 4;
var grainRate = tFreq / \overlap.kr(1);
var trigs = PulseDivider.kr(trig, maxOverlap, (0 .. maxOverlap-1));
var hasTriggered = PulseCount.kr(trigs) > 0;
var rates = Latch.kr(grainRate, trigs) * hasTriggered;
var phase = Sweep.ar(trigs, rates);
var envBufNums = Latch.kr(envBuf, trigs);
var grainEnv = BufRd.ar(1, envBufNums, phase * BufFrames.kr(envBuf), loop: 0);
var sig = SinOsc.ar(Latch.kr(\freq.kr(440), trigs));
sig = (sig * grainEnv).sum;
sig = Pan2.ar(sig, \pan.kr(0), \amp.kr(0.10));
Out.ar(\out.kr(0), sig);
}).add;
)
// sequencing of two shapes with pulsedivider
(
y = Pmono(\pulseDivider,
\freq, 440,
\trig, 1, // not optional
\bufIndex, Pseq([1, 0], inf),
\envBuf, Pfunc{ |ev| ~customEnvs[ev[\bufIndex]] },
\dur, 0.4,
\tFreq, Pfunc{ |ev| ev[\dur].reciprocal },
\overlap, 0.5,
\amp, 0.1,
\out, 0,
).play;
)
y.stop;
Indeed, this was a hard one, crashing up against a hidden assumption in the buffer UGens (that you won’t switch buffers very often). I assumed you could switch buffers freely
But with a small block size, you might get acceptable results. That is, there’s a trade-off: sample accurate triggers or more control over the signal being granulated, but at present, there’s no way to have both at the same time. (Edit: After stepping away from the computer, I realized you could also have one BufRd per envelope shape times max overlaps, and multiplex the triggers to the right BufRd for this cycle. Then there is no need to change the bufnum for any of the BufRd units = kr problem gone. But I’m out of time for today, won’t be able to write that code just now.)
hjh