I’d have no idea how to do that. The language c++ implementation code is scary!
I’m currently making a little framework for static structures, where the synth owns buses or buffers or some resources… syntax looks a little like this…
s.waitForBoot {
~noises = JXGroup(groupName, { |group|
var a = JXImport("/home/jordan/Desktop/test.scd").(name: '/a');
var b = JXImport("/home/jordan/Desktop/test.scd").(name: '/b');
JXConnect(a[\output] -> b[\input]);
group.forwardResource(
a[\input] -> \input,
b[\output] /*assumes name unless clash*/
)
})
}
~noise[\input].bus.set(2);
~noise[\output].bus.scope
where “/home/jordan/Desktop/test.scd” is…
{
|name|
JXTypeCheck(name -> Symbol);
JXGroup(name, {
var divide = JXSynthDef('/divide', func: {
var in = JXIn.kr(\input, 0); // this synth owns this bus
JXOut.kr(\divout, in / 2); // and this one
});
var adder = JXSynthDef('/adder', func: {
var fa = JXIn.kr(\in, 0); // sim.
JXOut.kr(\output, fa + 1);
});
divide.connect(\divout -> adder[\in]); // connect the busses, also supports many2many connections, the JXIn can specify if it wants to take the sum or the mean (or a users provided reduction function)
JXGroup.forwardResources( // lets these be accessible out side the group
divide[\input] -> \input,
adder[\output]
)
})
}
In forces the code to mirror the server structure and avoids all globals with the import. I don’t need the flexibility of live coding some managing all my own resources just irritates.
The purpose is to make managing larger projects with multiple files easier, and giving good errors when something goes wrong, and because it runs once at the beginning, I don’t have to worry about speed…
I’m writing these little type checks are the start of every function and it makes debugging significantly easier. When there have been bugs, they are much nicer to fix as in normal sc you can have a large call stack where the wrong object was passed in at the top somewhere. It throws with an error when you send it the wrong stuff. Worlds like this…
{ |name ,n|
JXTypeCheck(name -> [Symbol, Integer], n -> Integer ...);
...
}
Where name
is either a Symbol or an Integer, or something that inherits from them.
When you call connect between two resources there is a little multiple dispatch system that looks at the types of thing your trying to connect and finds the correct connecting function.
So to make JXIn and JXOut I had to implement a few very small classes:
JXOut : JXResourceUgen {...} // ar and kr
JXOutConstructor : JXResourceConstructor {...} // captures data
JXOutResource : JXResource {...} // owns bus
JXIn : JXResourceUgen {...}
JXInConstructor : JXResourceConstructor {...}
JXInResource : JXResource {...}
and this one which registers the multiple dispatch, its name isn’t important.
JXConnection_pr_Out2In : JXResourceConnectionFunctionBase {
*initClass {
JXResourceConnectionFunctionBase.register(
JXOutResource, JXInResource,
JXConnection_pr_Out2In
);
}
*call { |someJXOutResource, someJXInResource| ... }
Then when the user calles connect( a[\out] -> b[\out] )
it ultimately calls JXConnection_pr_Out2In.call(out, in)
, but if the in and out were different types, say a ownedBuffer, and a borrowedBuffer, it will call a different method.
I’d love an optional type system! Stronger than hints, because I’d always want them to run, but if they were omitted it would default to an any type.