SuperDirt
SuperDirt copied to clipboard
Controlling parameters between events via parameter buses
E.g.:
d1 $ pan "0 1" $ s "bev"
# panmode "smooth" 1
When # pan "1"
is sent, # panmode
sets interpolation to smooth
over 1
cycle duration.
good idea.
thinking again about this: what would smooth mean here?
One way this could work is that the panning runs one event or one cycle behind so that it knows where to interpolate to. Thoughts?
And, maybe smooth isn't a useful descriptor and linear
ease
etc would be better.
ah I see, so you want a pan that moves within each event. It would be possible to add a panFrom
parameter.
In principle, this would be a possible desire to have for every existing parameter, so I'm not sure if it would be better to introduce some architecture for such a thing.
ah I see, so you want a pan that moves within each event. It would be possible to add a panFrom parameter.
Yes, and that's a better suggestion.
In principle, this would be a possible desire to have for every existing parameter, so I'm not sure if it would be better to introduce some architecture for such a thing.
I completely agree, and I think this would overcome one of the main limitations of the Tidal-SuperDirt relationship cc @yaxu. Should we create a separate issue for that?
We could rename this one. What would we call it. "Parameter envelopes?"
A simple case would be a list of values, equally spaced over the duration of an event (one at start one at end) that supercollider moves between linearly.
We already use :
as a kind of list separator in the mini-notation (although this needs work to make the elements patternable).
The interface for that simple case could then be d1 $ sound "sax" # speed "0.5:1:<0.25 0.5>"
I was thinking slightly more generally "parameter interpolation".
It's going to give parameters more independence from the main pattern structure, so there should be a clear way to use the regular approach still.
A notation for this would be great, but maybe we also want a way of doing this with an explicit parameter as well? It's always great having multiple ways of doing things, and sometimes the pattern notation can get quite dense.
What would # speed "0.5:1:<0.25 0.5>"
do exactly?
Yes I was just trying to start with the simplest case of linear interpolation,
# speed "0.5:1:<0.25 0.5>
would make events during the first cycle have a speed starting at 0.5, going up linearly to 1, then down to 0.25. The second cycle it would go down to 0.5 instead. This would probably happen over the duration of the sound.
A helper function could turn 'monophonic' patterns into this kind of envelope e.g. # pan (slide "0 1")
would be the same as pan "0:1 1:0"
We should probably start with finding out how this kind of thing is usually done with supercollider, then looking for a minimal and expressive Tidal UI to support that.
By the way I just checked and dirt used to have a pan_to parameter a couple of rewrites ago, back in 2011 :) http://slab.org/datadirt/datadirt_1.0.0.tar.gz
I see, although I'm not sure :
would be very scalable for this use case:
- Writing/reading long patterns with many
:
would be hard - Trouble combining
:
with[]
,,
and{}
(e.g."[0.5:1]:0.25 0.5"
) -
:
is already used bygrp
to mean separate lists for separate parameters, whereas this proposal suggests separating events within a single parameter pattern
All that being said, I'm afraid I don't have any better ideas off the top of my head.
Until we know a bit more how it's looking on SuperDirt side it might be easier to prototype with additional explicit parameters, and then come back to the UI/notation side later.
And that is a great archeological find - no new ideas under the sun!
My thinking is that both are lists, so should maybe make use of the same syntax, with wider context giving meaning to what the list is of. Additional parameters will likely be needed too, for giving the type of envelope
Ok, so in this case the "lists" would be lists of patterns that describe interpolation points for the same parameter, with the first list being the "from" pattern, and subsequent lists being "to" patterns?
Wouldn't your example though need to be "0.5:~ 1:~ ~ <0.25 0.5>"
? I'm struggling to grasp the temporal relations a bit here.
https://github.com/tidalcycles/Tidal/issues/397 related..
isn't interpolation just a special case? What you want, in a way, is to be able to change parameters on a running synth, which does the intrpolation internally, e.g. by lagging the parameter.
Let's assume we have a synth event called "set", defined like this (we'll need a more general solution):
~dirt.soundLibrary.addSynth(\set, (play: { ~group.set(\freq, ~freq.value) }))
Then in tidal, we could make a synth that has a long legato, and while it runs, set it:
sound "supergong set set set" # freq "200 700 100 500" # legato 4
(I realise that it would be nice to have a legato that is relative to the cycle length)
The synth supergong has no interpolation in its values, but this could work. Still need to test.
Would this lead to a solution in your direction?
That would only work for monophonic synths, unless we made progress on #133.
My suggestion is to send a parameter as an array at trigger time, that sets an envelope on that parameter.
I had a look at the OSCv1.1 spec and couldn't see support for lists
That would only work for monophonic synths, unless we made progress on #133.
My suggestion is to send a parameter as an array at trigger time, that sets an envelope on that parameter.
The suggestion above is independent of all this. You can set the orbit's group, and this will set the parameters of all synths in that orbit.
So (I thinkt at least) independently of this: http://opensoundcontrol.org/spec-1_0 down below:
[ | Indicates the beginning of an array. The tags following are for data in the Array until a close brace tag is reached.
etc.
I see. You might still want different sounds in the same orbit with different frequencies.
I couldn't get the example to work, I get "Instrument isn't found" for "set"
I think it should be:
~dirt.soundLibrary.addSynth(\set, (play: { ~synthGroup.set(\freq, ~freq.value) }))
Hm I'm still getting the same:
-> a DirtSoundLibrary
module 'sound': instrument not found: set
module 'sound': instrument not found: set
module 'sound': instrument not found: set
If I run the addSynth line again I get
replacing 'set' (1)
-> a DirtSoundLibrary
So it seems it's definitely adding it.. Strange.
yes, I had the same. I need to investigate why this happens. ~~Also I need to refactor the DirtEvent a little, need to first get the group id so that the synths can grab it.~~
This will be a good first step. If we want to separate the synths, we can think how to do this. There is still the possibility to allocate groups and put synths in groups. Alll this adds complexity though.
Here is a first example that actually works:
(
~dirt.soundLibrary.addSynth(\set,
(play: { |dirtEvent|
var group = dirtEvent.orbit.group.asGroup;
group.set(\freq, ~freq.value)
})
);
SynthDef(\supertest, {|out, freq=440, sustain=1, pan |
var sound = RLPF.ar(Pulse.ar(freq.lag(1)), (freq * 3).lag(2), 0.05);
Out.ar(out, DirtPan.ar(sound, ~dirt.numChannels, pan))
}).add
);
Then you can write:
sound "supertest set set set" # freq "200 700 100 500" # legato 4
What we could do is add control routing busses to superdirt (we already have audio routing busses):
(
~dirt.addModule(\get, { |orbit|
var bus = orbit.dirt.controlBusses.wrapAt(~fromBus);
orbit.server.sendMsg("/n_map", ~setParam, bus.index);
}, { ~fromBus.notNil })
)
(
~dirt.addModule(\set, { |orbit|
var bus = orbit.dirt.controlBusses.wrapAt(~setBus);
bus.set(~toValue);
}, { ~setBus.notNil })
)
And then we could write in tidal, after having defined the right parameter functions:
sound "supertest" # setParam "freq" # fromBus "7" # setBus "7" # toValue "403 508 201 703"
Both parts, the setParam
and the setBus
could happen in completely different orbits.
Currently, we can do this already with audio signal routing, but not with setting controls.
P.S. I've added controlBusses now, so the above code should almost work. In tidal:
let fromBus = pI "fromBus"
setBus = pI "setBus"
toValue = pF "toValue"
setParam = pS "setParam"
OK, here we go:
let fromBus = pI "fromBus"
setBus = pI "setBus"
toValue = pF "toValue"
setParam = pS "setParam"
fadeTime = pF "fadeTime"
d1 $ stack [
sound "supertest" # sustain 4 # fadeTime 1 # setParam "freq" # fromBus "7" ,
sound "supersilent*4" # setBus "7" # toValue "403 508 201 703"
]
SuperDirt:
(
SynthDef(\supertest, {|out, freq=440, sustain=1, pan |
var sound;
sound = RLPF.ar(Pulse.ar(freq.lag(1)), (freq * 3).lag(2), 0.05);
Out.ar(out, DirtPan.ar(sound, ~dirt.numChannels, pan))
}).add;
SynthDef(\supersilent, {|out, freq=440, sustain=1, pan |
var sound;
sound = Silent.ar(~dirt.numChannels);
Out.ar(out, DirtPan.ar(sound, ~dirt.numChannels, pan))
}).add;
~dirt.addModule(\get, { |event|
var bus = event.orbit.dirt.controlBusses.wrapAt(~fromBus);
event.orbit.server.sendMsg("/n_map", ~synthGroup, ~setParam, bus.index);
}, { ~fromBus.notNil });
~dirt.addModule(\set, { |event|
var bus = event.orbit.dirt.controlBusses.wrapAt(~setBus);
bus.set(~toValue);
}, { ~setBus.notNil })
)
Does this go in the right direction?
Instead of sending
/play2 "sound" "supersaw" "setParam" "freq" "fromBus" "7"
... could tidal instead send the bus value with the name prefixed e.g. by '_'?
/play2 "sound" "supersaw" "_freq" 7
Then there could be multiple things set in one message
/play2 "sound" "supersaw" "_freq" 7 "_squiz" 8
Instead of using '/play2' for setting a bus value, tidal could use a different OSC path, e.g.
/set 7 0.4
This would work, but would incur a bit of calculation cost on all messages (we need to check every argument name for the prefix, but there is a primitive that is quite efficient).
If this is ok, the nicest way would be:
// getting a bus value from a bus named "theFreq"
"_freq" "theFreq"
// and setting the bus value
"theFreq_" 567
But maybe numbers are just fine as well.
Assuming that we do /set 7 0.4
(which would work much better!).
A very direct way would be to send:
/play2 "sound" "supersaw" "freq" "c7"
We don't have to change anything on the superdirt side for that really. (Note that this won't work for all parameters, e.g. n
doesn't work).
But: Then we need to guarantee that superdirt has the relevant bus numbers. That probably means that tidal needs to know the busses that superdirt has, and also how many. Superdirt could send them to tidal.
The possibility of having synths playing out into audio busses which can then be mapped to arguments is already implemented, but this works differently (there, you mostly don't want to have the same pattern write and read).
Would it be possible to assign a bus number to every possible parameter available, or would that be too many buses?
I'm wondering about a nice interface, maybe this could work:
d1 $ sound "supersaw"
# (bus 7 $ freq "500 600 200)"
Then I think that the tidal scheduler could take care of sending the "freq" "c7"
in the play2
message, and then separate messages with /set 7 500