SuperDirt icon indicating copy to clipboard operation
SuperDirt copied to clipboard

Controlling parameters between events via parameter buses

Open jarmitage opened this issue 4 years ago • 128 comments

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.

jarmitage avatar Apr 06 '20 15:04 jarmitage

good idea.

telephon avatar Apr 06 '20 16:04 telephon

thinking again about this: what would smooth mean here?

telephon avatar Jun 08 '20 07:06 telephon

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.

jarmitage avatar Jun 08 '20 07:06 jarmitage

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.

telephon avatar Jun 08 '20 15:06 telephon

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?

jarmitage avatar Jun 08 '20 16:06 jarmitage

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>"

yaxu avatar Jun 08 '20 19:06 yaxu

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?

jarmitage avatar Jun 08 '20 20:06 jarmitage

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.

yaxu avatar Jun 08 '20 21:06 yaxu

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

yaxu avatar Jun 08 '20 21:06 yaxu

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 by grp 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!

jarmitage avatar Jun 08 '20 21:06 jarmitage

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

yaxu avatar Jun 08 '20 22:06 yaxu

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.

jarmitage avatar Jun 08 '20 22:06 jarmitage

https://github.com/tidalcycles/Tidal/issues/397 related..

yaxu avatar Jun 08 '20 23:06 yaxu

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.

telephon avatar Jun 09 '20 06:06 telephon

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?

telephon avatar Jun 09 '20 06:06 telephon

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.

yaxu avatar Jun 09 '20 09:06 yaxu

I had a look at the OSCv1.1 spec and couldn't see support for lists

yaxu avatar Jun 09 '20 09:06 yaxu

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.

telephon avatar Jun 09 '20 15:06 telephon

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"

yaxu avatar Jun 09 '20 22:06 yaxu

I think it should be:

~dirt.soundLibrary.addSynth(\set, (play: { ~synthGroup.set(\freq, ~freq.value) }))

telephon avatar Jun 10 '20 05:06 telephon

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.

yaxu avatar Jun 10 '20 14:06 yaxu

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.

telephon avatar Jun 10 '20 19:06 telephon

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

telephon avatar Jun 10 '20 20:06 telephon

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.

telephon avatar Jun 10 '20 20:06 telephon

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"

telephon avatar Jun 11 '20 10:06 telephon

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?

telephon avatar Jun 11 '20 10:06 telephon

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

yaxu avatar Jun 20 '20 12:06 yaxu

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.

telephon avatar Jun 20 '20 15:06 telephon

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).

telephon avatar Jun 20 '20 16:06 telephon

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

yaxu avatar Aug 08 '20 21:08 yaxu