miSCellaneous_lib
miSCellaneous_lib copied to clipboard
multichannel expansion on ODE systems?
I saw that the ODE UGens do not do multichannel expansion (they call *basicNew
directly instead of expanding first). I think it would be straightforward to implement. Is there a reason why this is not the case?
Hello Julian,
arguments of a single Fb1_ODE can already be arrays, so they shouldn't trigger expansion into several Fb1_ODEs.
E.g., see the last example of the video
https://www.youtube.com/watch?v=AcuKXxw4vrE&t=380s
// extension: multiplication with a second sine with modulated time
// for decorrelated stereo we expand to two independent ODEs
// mod should be of size 2
(
Fb1_ODEdef(\exp_decay_2, { arg t, y, mod, dampen = 1;
[
y[0].neg * sin(t) * sin(mod[0] * t) * dampen,
y[1].neg * sin(t) * sin(mod[1] * t) * dampen
]
}, 0, [1, 1]);
)
// this also works because of the initial array [1, 1]
(
Fb1_ODEdef(\exp_decay_2, { arg t, y, mod, dampen = 1;
y.neg * sin(t) * sin(mod * t) * dampen
}, 0, [1, 1]);
)
(
x = {
Fb1_ODE.ar(\exp_decay_2,
argList: [
SinOsc.ar(150).range(-0.01, [0.02, 0.03]), // mod
0.5 // dampen
],
tMul: 200 * 2pi,
// for experimentation use constraining option
// compose: \softclip
) * 0.5
}.play
)
x.release
Sure, this could be done with Refs as with Klang etc., but I suppose that would make things clumsier here. Do you have a certain use case at hand ?
- uniformity in the UGen system
- easy comparison between any number of slightly differen systems
(
Fb1_ODEdef(\exp_decay_2, { arg t, y, mod, dampen = 1;
[
y[0].neg * sin(t) * sin(mod[0] * t) * dampen,
y[1].neg * sin(t) * sin(mod[1] * t) * dampen
]
}, 0, [1, 1]);
)
would then just be one line and could expand to any number of channels, instead of having to write a separate one for each case.
Sure, this could be done with Refs as with Klang etc., but I suppose that would make things clumsier here.
No, you don't need refs. Unless you also want to multichannel-expand on the arrayed args, of course. just wrap them into another level of brackets internally, before you call flop:
[[[1, 2]], [10, 20, 30]].flop // -> [ [ [ 1, 2 ], 10 ], [ [ 1, 2 ], 20 ], [ [ 1, 2 ], 30 ] ]
would then just be one line and could expand to any number of channels, instead of having to write a separate one for each case.
As written in my post, it is already the case that you can write it in one line, see the version below !
(
Fb1_ODEdef(\exp_decay_2, { arg t, y, mod, dampen = 1;
y.neg * sin(t) * sin(mod * t) * dampen
}, 0, [1, 1]);
)
True, this is stereo because of the init values, but you could easily write 1 ! n for the initial state.
But my main point was that using n instances of Fb1_ODE in parallel, from my practice in experiments, is a rather exceptional case. Instead, often you would have one system with several equations and a multichannel input. And for this main case you would have to circumvent multichannel expansion, which makes things overall more complicated.
As written in my post, it is already the case that you can write it in one line, see the version below !
Ah now I understand, that is good.
Although, I would have expected you could then also do:
(
x = {
Fb1_ODE.ar(\exp_decay_2,
argList: [
SinOsc.ar(150).range(-0.01, [0.02, 0.03, 0.04]), // 3 channel mod
0.5 // dampen
],
tMul: 200 * 2pi,
) * 0.5
}.play
)
Couldn't you make the Fb1_ODE
decide how many parallel channels it will generate? Then the defaults in Fb1_ODEdef
would really be defaults, or may not be necessary at all.
A possible behaviour would be that in the definition, you specify how many channels the ODE itself will have (say N
), and then the UGen that uses it would either use it with single inputs, which would result in N
channel audio, or would pass in more channels, say M
, and then you'd get a nested array of dimensions (M, N)
.
This would be what one would expect.
For example PanAz.ar(8, PinkNoise.ar([1, 1]))
would return an array of shape (2, 8)
.
A possible behaviour would be that in the definition, you specify how many channels the ODE itself will have (say N), and then the UGen that uses it would either use it with single inputs, which would result in N channel audio, or would pass in more channels, say M, and then you'd get a nested array of dimensions (M, N).
It sounds indeed possible to base the kind of expansion on the Fb1_ODEdef. But I would have to look into it – not only would it require a change in Fb1_ODE but possibly also in Fb1_ODEdef. In the forseeable future I don't have the time to do that, sorry.
But again, the possible benefit seems small to me as what you describe can already be done with rather minimal effort, I see at least 4 options now:
.) probably easiest, use an array input in the Fb1_ODEdef .) expand the Fb1_ODE explicitely, like { |i| ... Fb1_ODE( ... ) } ! n, also quite straight .) a dedicated expander Function using above operation .) a Function ("Fb1_ODEdef factory") that generates Fb1_ODEdefs for the required channel nums
BTW, several UGens do not multichannel expand, BufWr being a prominent example. Maybe there could be options for expansions here and there but, depending on special circumstances, the tradeoff – syntactic purity vs. practicability – finally resulted in not implementing them.
Yes, of course, you can also always do a collect on the set of arguments if a UGen doesn't expand. It is mainly a uniformity argument.
It sounds indeed possible to base the kind of expansion on the Fb1_ODEdef. But I would have to look into it – not only would it require a change in Fb1_ODE but possibly also in Fb1_ODEdef. In the forseeable future I don't have the time to do that, sorry.
Yes, I completely understand.
It would only need a change in Fb1_ODE
, as long as it is correct that many of them can use a single Fb1_ODEdef
.
BTW, several UGens do not multichannel expand, BufWr being a prominent example.
No, BufWr
does call multiNewList
. It expands fine, it just expects an array in the first argument. So it is a good example for our case, just that Fb1_ODE
is a little complicated.
~bufs = { Buffer.alloc(s, s.sampleRate) } ! 3;
(
{
var input = Decay.ar(Impulse.ar([2, 3, 5]), 0.2) * SinOsc.ar([600, 800, 1200]);
var phase = Phasor.ar(0, BufRateScale.kr(~bufs), 0, BufFrames.kr(~bufs));
BufWr.ar([input], ~bufs, phase);
0.0
}.play
)
~bufs.do(_.plot)
It expands fine, it just expects an array in the first argument.
I wasn't aware of that option, I remembered the helpfile which says
BufWr (in difference to BufRd ) does not do multichannel expansion, because input is an array.
This is somehow misleading, in fact, BufWr can handle multichannel buffers and buffer arrays
~buf = Buffer.alloc(s, s.sampleRate, 3);
(
{
var input = Decay.ar(Impulse.ar([2, 3, 5]), 0.2) * SinOsc.ar([600, 800, 1200]);
var phase = Phasor.ar(0, BufRateScale.kr(~buf), 0, BufFrames.kr(~buf));
BufWr.ar(input, ~buf, phase);
0.0
}.play
)
~buf.do(_.plot)
Maybe the helpfile should include two examples of this kind ?
Oh yes, most definitely. I'll fix this. Are you aware of any other such case?
Oh yes, most definitely. I'll fix this.
Thanks!
Are you aware of any other such case?
Not at the moment.