GameOfLife / Unit-Lib

The Unit Library is a system that provides high level abstractions on top of the SuperCollider language.
25 stars 6 forks source link

parameter linking in UChains #22

Closed woutersnoei closed 11 years ago

woutersnoei commented 11 years ago

I've wondered for a time about a feature that I would think make control bus I/O in UChains obsolete, and the UMap system mature; parameter linking. What if we could say for any (mappable) arg in the U; use the value of that argname in that unit earlier in the chain, wether it is a value or an UMap. If it is an umap, map to it, if it is a value, get it. It could work like a kind of URL system. This would allow sharing of values between units in the chain, like we have them now with the wfs_control Udefs in the WFSCollider lib. It would make sense to me to be able to use the UMap system for that as well, and make it possible for all kinds of parameters. And in a way that may be easier to manage. At some point the wfs_control Udefs could become obsolete.

We could have two types of linking; direct and mapped. Direct linking would only work if the linked arg has the same (or a similar) spec. Then it's value or map bus could be used directly. The mapped variant could place a synth in between to convert any value into a usable one.

My first idea on how to implement this is as follows: We create a ULinkSpec, to be used by a (or various) link-UMapDefs. The actual value of the link arg could be a string formatted like this; for example to link to the 'freq' arg of the first unit in the chain:

"0/freq"

Or, if the 'freq' happens to be an lfo-UMap with it's own 'freq' parameter, it could be like this:

"0/freq/freq".

This link would be resolved during UChain:prepare, which builds a growing list of all available link URL's with their specs in a global var. From there the UMap handling the linking could pass on .asControlInput value, and perhaps register a controller to the source Unit in case it changes during playback, and if needed filter for allowed specs (if the spec is not allowed, or the linked argname not found, the umap could return a default value). In code it would look like this:

UChain( \sine, [ \pulse, [ \freq, UMap( \link, [ \url, "0/freq" ] ), 'u_o_ar_0_lvl', 1 ] ], \output );

That would create a mix of a \sine and a \pulse, sharing the same 'freq' value (this is the direct variant, not mapped). Whenever the sine's freq is changed, the pulse's freq changes as well, to the same value. If the sine freq would be an UMap, the pulse freq gets it's output bus. For that last one all UMaps within a chain would need an individual bus (instead of only per Unit). That could perhaps be the first step towards realizing this.

In the GUI we would probably have a popup menu with all the available link url's for the current unit. They could be gathered during UChainGUI:makeView.

miguel-negrao commented 11 years ago

This looks interesting yes. Btw, before yesterday I implemented a first attempt at individual buses for each uchain, which seems to be working. This is in order to use supernova. The buses are created in UChain:prepare and freed when the chain of the group is freed. Right now I've made the UGroups create ParGroups instead of groups and to test I've added all chains to the default UGroup. What I'm planning it that by default we create a ParGroup per computer (each computer, one supernova instance) which would be created on the supplied target to prepare, and then if there were any UGroups they would be also ParGroups created inside this main ParGroup. simple testes showed that it was working and got to run quite a lot of chains with one supernova instance. I'm still dealing with xrun problems but that has to do with my linux config and soundcard.

Now my question is how to make the setup for both scsynth and supernova. I'm assuming that for scsynth we would keep things as they are, so UChain would check what is the program being used, or there would be a switch UChain.program or a config option somewhere, that with scsynth things would be as they now are but with supernova it would create the unique buses. Should we also use individual buses for uchains with scsynth, since UMaps will get also individual buses, or better keep things as they are for scsynth ? In this patch I didn't look at UMaps yet, that is probably broken, I still have to work more on it to get it error free.

https://github.com/GameOfLife/Unit-Lib/commit/1b32baa7fc414ae37732f8aa36ef10c681683fe6

(
x = UScore(
    UChain( ['sine', [ \freq, 400]], [ 'output', [ 'numChannels', 1 ] ]).ugroup_(\default),
    UChain( ['sine', [ \freq, 2000]], [ 'output', [ 'numChannels', 1 ] ]).ugroup_(\default)
)

x.prepareAndStart
)

edit: I cleaned up the patch for the whitespace... btw, are you still using the cocoa app or have you switched to the ide ?

woutersnoei commented 11 years ago

Ok, looks nice indeed. One thing I'm wondering about; you are assigning buses using Bus.audio, but the actually used buses are private buses. So the bus number doesn't correspond with the actually used bus, and it will run out of busses sooner than the allocator knows. Perhaps you need to cook up your own allocator for private buses in this case. I could also imagine that we would work with a single "offset" value, similar to how I just implemented unique bus usage for UMaps in chains.

miguel-negrao commented 11 years ago

This would fix it, right ?

@@ -711,7 +711,7 @@ U : ObjectWithArgs {
                .select({ |item| nonsynthKeys.includes( item[0] ).not })
                .collect{ |xs|
                        if( xs[0].asString.findRegexp("u_[oi]_ar_([0-9]*)_bus").size > 0 ) {
-                               [xs[0], buses.at(xs[1]).at(server).index ]
+                               [xs[0], buses.at(xs[1]).at(server).index - server.options.firstPrivateBus ]
                        } {
                                xs
                        }
woutersnoei commented 11 years ago

Ah, I see, so Bus.audio is already allocating private buses. Yes, I suppose it works then. It still seems a bit hacky though, perhaps there can be a more elegant solution? I could imagine we could deal with this in intelligent args for the u_..._bus args, which would get their parent unit at prepare, which on it's turn know it's parent uchain (if any). They could return the correct bus with .asControlInputFor. They could be called UAudioBus and UControlBus, and hold the index of the intended bus inside. The UChain could hold the dict of which bus points where, like it does in your implementation, but the actual bus assingment would take place in the arg instead of U:prepare. And of course there should indeed be a switch somewhere to fall back to scsynth mode where the audio buses are limited. One problem that could be expected is with the 'map' umapdefs; they won't be able to know which bus exactly to tap into, and it can be different per server (when multiple servers play the same synth simultaneously). But I'm sure we can find some clever way of dealing with that too :-)

woutersnoei commented 11 years ago

parameter linking is basically implemented now. Check the 'link' and 'link_point' UMaps. They are expert-only for now, enter link url's by hand.