Do you need to dynamically reconfigure the graph at runtime? If so, would it work for your use case to re-gen csound code and have csound recompile it? (i.e., what we do with live coding). If that is the case, you can always represent code as nodes in JS, gen the code in any order as a single instrument, then recompile. It will potentially cause a glitch in this case as state isn’t preserved when moving between one instance of an instrument and a new instance of an updated instrument. It would give flexibility on order of operations however. (I think the Anton’s Haskell csound-expression library works this way: csound-expression: library to make electronic music ).
Yes, I need to be able to change nodes ordering. That would work, and this is the exact solution what I was asking about. I’d like to find a better way instead of recompiling the instruments, though. I’ll provide a detailed example of my use case below.
Rather than zak, you could use the channel system to communicate between instrument instances. This would give some flexibility to name values and you can dynamically create channel names to assist here so that instances of an instrument can read/write to different channels.
Actually, indexing by numbers would be more suitable for my use case as with strings I need to generate UIDs and then there may be situations when I need to reuse them. I could use stringed indexes though (“0”, “1”, “2”, etc.), still converting numbers from/into strings is a little overhead.
Some I’ve done with live coding is have instruments numbered all write output to a bus (via channels), and have one mixer instrument at the end handle mixing and effects. If that matches up with your use case, you can compile/recompile instruments with lower numbers and have their outputs write to the bus for the mixer. Any changes in mixer graph (i.e., inserting new effects, adding sidechaining, etc.) would require a recompile of the mixer however. Effects instruments with numbers between the sources and mixer could be an option to modify at run-time.
This describes partly my use case, but if it would be possible without recompiling, I’d rather to go that way. I was also thinking about changing instrument numbers instead of recompilation, but that would be hardly to manage as there could be a lot of nodes and when you needed to reconnect one part of the graph to another, you’d need to change all the indexes, still keeping in mind the relation to the others…
I’ll try to provide a more detailed design by examples. This is very unstable for now and I’m open to your suggestions. As I’m new to Csound, please consider below more like a pseudocode.
// you are able to define your nodes (ugens) using JS this way
ndef SomeNode(amp, freq) {
let sinosc = ~oscili.a(amp.k, freq.k);
~xout.a(sinosc);
}
// the AST of above code then translated into Csound AST,
// which is an equivalent that you have in the result of
opcode SomeNode, a, kk
kAmp, kFreq xin
aSig oscili kAmp, kFreq
xout aSig
endop
// the tree then compiled by Csound
// now I can init a node, with something like
let node = Node("SomeNode");
// and suppose we have an opcode (= ndef) named "reverb"
let reverb = Node("reverb")
// now I'm able to connect nodes
node.out(reverb);
// and to connect it to output
let out = Node("out");
reverb.out(out);
// the Node(...) is initializer for Node object. Which will be
// responsible for compiling a new instrument-wrapper for
// the opcode, assigning it a number and input/output channels,
// managing connections and parameter changes, etc.
// as I see it now, the node instantiation, in Csound could be
// something like
instr 42 ; automatically generated number on JS side
iOutChNum p4 ; the index of output channel
aSig SomeNode p5, p6
outlet aSig, iOutChNum ; don't know what goes here for now
endin
// so this way I'll be able to change the output channel index,
// without recompiling the instrument. So if, for example, I
// wanted to connect it's the other way
reverb.out(node); // yeah, that's a bad example...
// suppose `node` automatically disconnected and all we
// need to do now, is changing the output/input channel for
// instrument. We can keep the state of the node in the
// Node object on the JS side
// next I want to be able to control any parameter of a node by
// either: a static value, a pattern or another node. But it
// seems like I already figured out this part (except
// modulating by another node, but anyway)
node.set("freq", 440);
// or with a pattern (generates an event each 0.5 of a beat,
// changing "freq" parameter)
node.set("freq", pbind(0.5, pseq([220, 440, 880], inf)));
// or with a node/opcode
node.set("freq", Node("SomeNode", 200, 10));
// or node.set("freq", ~SomeNode.k(200, 10));
// - it depends on implementation
// and it's important that you can define many nodes of
// the same type and use them in different or the same
// graphs
let node2 = Node("SomeNode");
node2.out(node); // another bad example...
I’m developing this library for a DAW-like application, where you can control anything using code. Kinda live coding DAW. So there will be a mixer also, as we have usually in a DAW. The mixer may have a lot of channels and a lot of effects could be inserted in the channels, so I wouldn’t like to recompile all this stuff when an effect ordering changed on a channel, for example. And I’d like to make the library usable on its own, so it’s better to view it separately from the app (there may not be the mixer in the library), but keeping in mind scalability. I still don’t feel like I explained it completely, but at least I tried, and I hope now you better understand my use case
The main question now is node instantiation and connections between nodes. Keep in mind, that I’ll manage the state of instruments/nodes on the JS side. That means, I can keep (= have access to) the index of output/input channels, the index of the instrument, etc.
EDIT:
It looks like both channels and zak good for this. I tested zak with thousands of channels, and it performed quite well. But I’ll go with channels, because if I use zak the users couldn’t use it for their nodes/opcodes. Thanks everyone for your help!