Alga: interpolating live coding environment

Hello everyone!

I have just decided to finally publicly share a project that I have been working on for a while, and I thought this would be a good place to share it.

Alga is a new language for live coding that focuses on the creation and connection of sonic modules. Unlike other audio software environments, the act of connecting Alga modules together is viewed as an essential component of music composing and improvising, and not just as a mean towards static audio patches. In Alga , the definition of a new connection between the output of a module and the input of another does not happen instantaneously, but it triggers a process of parameter interpolation over a specified window of time.

As of now, Alga only exists in the form of AlgaLib, an extension for the SuperCollider environment. While AlgaLib contains all the core features of Alga, I also plan to build a custom syntax on top of the audio implementation (similarly to what TidalCycles does with SuperDirt).

To install AlgaLib, you can either:

  1. Use the Quarks system: Quarks.install("https://github.com/vitreo12/AlgaLib")
  2. git clone this repository to your Platform.userExtensionDir.

These are a couple of very basic examples that should show some of the features of Alga:

Check the Help files and the Examples folder for usage and examples!

Let me know what you think and if you have any questions!

Have a great day :slight_smile:

12 Likes

Hi!

I guess you’re planning to organize algaraves… :roll_eyes:

I’m a noob, so I don’t understand clearly “Unlike other audio software environments, the act of connecting Alga modules together is viewed as an essential component of music composing and improvising, and not just as a mean towards static audio patches.” Looking at compositions in SC, connecting synths is generally essential.

Triggering interpolations “as of now” is a relevant need for me. I had the idea of doing an overlay for GLSL shaders that just does that, because the only uniform we get is the time since startup, so it’s difficult to make transitions from a unknown point in time during performances. TidalCycles already has some transition functions.

My two cents for your potential, own live coding env: do not do a custom syntax, because nowadays programming is not only about writing text, but also about having extra tools to make you write more comfortably. TidalCycles is a Haskell library, thus you can use any editor with Haskell addon to have syntax coloration, autocompletion, etc. for free, and then one can make a custom grammar which inherits the Haskell grammar to add coloration of TidalCycles patterns. Yaxu is making a custom editor with fancy visualizations, but you can use it for writing plain Haskell as well. In the same way, FoxDot is a Python library with a custom editor, SonicPi is a Ruby library with a custom editor, etc. and they all rely on SC for synthesis. Besides, you also get for free all the features of the host language: loops, conditionals, subprocedures, maybe async processing, libraries, etc. In brief, I encourage you to find a language that you like and extend it, rather that doing your own.

1 Like

Thank you for your answer!

I guess you’re planning to organize algaraves… :roll_eyes:

That’s a good pun, I might have to start using it :slight_smile:

Looking at compositions in SC, connecting synths is generally essential.

It surely is, and that is what Alga leverages. However, I would define the default connection of synths in SuperCollider as static, as it is with Max, PD, or any other environment I know of. What I mean by static is that there is a discontinuity between the sound before a connection (e.g. the value 440) and the one after the connection has been made (e.g. the value 220, after calling the set method). JITLib solves the issue with the xset methods by creating a new Synth and fading its volume in, while fading the old one out. On the other hand, Alga keeps the Synth running, but interpolates the values of the connections themselves, resulting in a smooth transition (however long).

In any case, I see where your confusion might come from. In its current state Alga is best described as a live coding environment within SuperCollider (I have modified the title of the post to reflect this). The language around Alga has not been developed yet, but it will be the next step for me.

I have experience in creating programming languages for audio programming (see Omni), so I am well aware of the pitfalls that creating new syntaxes might bring. However, I also believe that specific features need specific languages to reflect the uniqueness of that feature, as I think Alga’s interpolation is.

TidalCycles already has some transition functions.

That is surely true, but Alga is definitely not aiming at being a substitute of TidalCycles: they have completely different approaches to live coding. Alga promotes interpolation between connected modules running at any rate (both audio and control), as well as pattern interpolation, where each pattern element can also be any other AlgaNode (Alga’s modules). On the other hand, TidalCycles is a pattern language, whose focus is only on patterns. There is no concept of audio modules within it.

In a way, as mentioned, Alga is much similar to JITLib, with 4 main differences:

  1. Alga takes care of converting the rate of any parameter accordingly, giving the option of “patching anything to anything” that modular synthesizers have.

  2. Alga takes care of not only mapping inputs of a module (via the NamedControl syntax), but also its outputs (see AlgaNode's and AlgaSynthDef's help files to see what I mean here, togehter with the Examples folder).

  3. Alga takes care of ordering all the nodes in the server so that no audio delay is introduced when patching modules together (unless feedback connections are introduced, as shown in the Examples folder).

  4. Alga does not fades audio in and out when making new connections, but effectively interpolates the contents of the connections over specified windows of time.

4 Likes

aaah the delay issue is one the things that has kept me from being able to use JITlib for certain things!

excited to try this out!

also: wow the pattern interpolation is fascinating

1 Like

I would not fully agree. “Programming” has one set of goals; live coding has a different set of goals. They overlap, but it isn’t reasonable to assume that what is good for “programming” is also necessarily good for a specific live coding project’s goals.

Most(?)… or at least many… programming languages prioritize stability and future maintenance over immediacy and convenience. Onstage, a tool that’s oriented toward writing things in a way that is legible/maintainable 5 years from now may be a hindrance.

Live coding is partly about imagining mechanisms for data representation that are convenient to manipulate experimentally in real time. If a language’s existing syntax discourages such mechanisms, then new syntax may be appropriate.

For example, in vanilla SC Pbind, inserting a note at a specific metric position in the bar requires multiple disparate edits to separate lines in a way that a/ is easy to mess up and b/ doesn’t communicate the intent to a live audience. In my system, I can start with a kick drum pattern "o| o| o|" and insert an unaccented note on the “e” of beat 3 by adding two characters "o| o| _o |" but oh dear, I’ve gone and created new syntax.

The trade-off is indeed access to editing tools. But I wouldn’t accept that the gains brought by new syntax are always trivial. It’s up to the designer of the project to decide the right balance for that project, not for someone to make a global assertion.

hjh

2 Likes

I played with Alga this afternoon and I really, really like the syntax.
I did not encounter any bug, it’s stable.
I’m impressed !
Small FR (for @Spacechild1 or you, I don’t know), it would be cool to be able to use VSTPlugin in an AlgaNode.
Thanks again for making it public !

2 Likes

Thanks a lot for checking it out! :slight_smile:

Regarding using VSTPlugin within Alga, it would be great to make it feasible! If it’s working for JITLib, it should definitely work for Alga. I might take a look at it myself when I have some spare time.

1 Like

Yes, VSTPlugin works with JITLib
I tried using the NdefVstController with an AlgaNode, just in case, but it did not work.
Thanks again, alga is definitely worth it !

Hello!

I have what’s likely a fairly simple question regarding routing. When using a NamedControl arg for the purposes of taking in external values (or signals) with multichannel expansion, is it possible to route values or signals to specific index slots in the array?

For example:

l = AN({ SinOsc.ar(\freq.kr(440)) * \amp.ar([1, 1]) }).play

m = AN({ LFTri.ar(\freq.kr(0.1)) })

l <<. amp m

The behavior I’m experiencing is that the single channel triangle LFO gets sent to both channels of the oscillator. This is super handy 99% of the time, but occasionally I’d like to use the LFO to control just one of those channels, sort of like l <<. amp[1] m, which doesn’t work.

Is there a workaround that’s short of using two separate NamedControls to access just one of the busses?

Thank you!

Node:set can also take numeric indexes of controls. It’s up to you to figure out the numeric index for a specific control from SynthDesc:controlNames - this can be a little tricky for multichannel controls, but all the required information should be in the SynthDesc. I’m guessing in the syntax of Alga, it would be something like this?

l <<. 2 m

It’s worth saying that this way of setting can be a little fragile, since it depends on SynthDesc information that may not match the SynthDef that is visible to the server (this is uncommon but possible, e.g. in the time between when you create a SynthDef and when it’s loaded by the server, or in cases where the server directly loads a cached def). If it’s relatively easy to factor this as two NamedControls, it might be a little more robust.

Hey!

That’s a use case I hadn’t think of yet, but it would definitely be nice to have.
Alga already provides a way of specifying specific channels mapping with the chans argument to the functions, but it does not include the case where one of the mapped channels should be ignored.

For such a case, I propose something like this (it won’t work now as it’s not been implemented yet):

l = AN({ SinOsc.ar(\freq.kr(440)) * \amp.ar([1, 1]) }).play

m = AN({ LFTri.ar(\freq.kr(0.1)) })

l.from(m, \amp, chans: [\out1, nil]) //nil == don't map it

What do you think?

Also note that you can still do something like this:


l = AN({ SinOsc.ar(\freq.kr(440)) * \amp.ar([1, 1]) }).play

m = AN({ [LFTri.ar(\freq.kr(0.1)), Silent.ar] })

l <<.amp m

Or if you want to keep your original one channel modulator, you can plug an “in between” node:


l = AN({ SinOsc.ar(\freq.kr(440)) * \amp.ar([1, 1]) }).play

m = AN({ LFTri.ar(\freq.kr(0.01)) })

n = AN({ [\in.ar(0), Silent.ar] })

(
n << m;
l <<.amp n
)

This is also a very solid solution.

Thanks @vitreo12 and @scztt!

I’ll need to dig around the from method’s chans argument – I might have gotten it mixed up with AlgaNode.new's outsMapping.

Is there a way I can inspect the input and output UGens created either on the node or for an AlgaSynthDef?

I’ll investigate this as well, it might be a simple solution. I suppose one thing I’m doing on my end though is actually mostly making AlgaSynthDefs, and then looking it up when instantiating an AlgaNode. I suppose a workaround for that may be to use variables to save functions as opposed to declaring the SynthDef…

Though maybe this is another way to work around it. It’s also not the biggest deal to use two NamedControls.

Ahh nevermind, I am able to inspect via SynthDescLib.global.browse.

Basically, the chans arguments are used to actually use the output mappings that you define in AlgaSynthDef's outsMapping. It allows Alga to not only specify connections with inputs mapping (a.k.a. NamedControl), but also via output mappings, like so:

(
Alga.boot({
	~sines = AlgaNode(
		{ SinOsc.ar(\freq.kr([440, 880])) },
		outsMapping: [\sine1, \sine2]
	);
	
	~bypass = AlgaNode({ \in.ar([0, 0]) }).play
})
)

//Normal connection
~sines.to(~bypass)

//Inverting the channels using the output names declared as outMappings
~sines.to(~bypass, chans: [\sine2, \sine1])

//Note that by default Alga addresses outMappings as \out1, \out2, etc...
~sines.to(~bypass, chans: [\out1, \out2])

//Or by indices: 0, 1, 2, etc...
~sines.to(~bypass, chans: [1, 0])

I realize that there are many things that should be documented way better. I am planning to do so for the next release, together with two main things:

  1. automatic parallelization of nodes using the supernova server, so that the CPU load is spread across cores without any changes to user code.
  2. allowing the user to specify any kind of interpolation shape via an Env interface.

If you feel adventurous, these things are already up and running on the develop branch (check the Examples/AlgaNode/10_Multithreading.scd and Examples/AlgaNode/11_InterpShape.scd for usage). Note that to get the new interpolation features to work, it is required to install the latest AlgaUGens from https://github.com/vitreo12/AlgaUGens/releases/tag/v1.0.0

2 Likes

Your explanation makes a ton of sense, and I’m starting to get it more now. Very interesting. One of my compositional tactics deals heavily with routing and signal flow, so I’ll need to think about how to make use this feature.

In snooping around the SynthDesc, it appears that no true input UGens are being created, and so as you said, it is all NamedControls and bus mapping that allows signal to flow in? There isn’t a parallel for the inputs for what you’ve done with the outputs.

And your next release sounds very exciting! I’ll check out the develop branch. And I think that there is a lot of very good documentation that you’ve included already. Between the examples and the help files and messing around I’m getting there. Part of this is me getting my bearings. I’m so used to setting up In UGens and Busses directly that suddenly having them invisible threw me for a loop. The example you posted above makes everything much more clear though. I think it’d be great to include it as well in the examples.

Oh, last question while I’ve got you- there’s no ReplaceOut option right? For routing through fully-wet effects I’ll need to stop the source Node at the same time as routing it to the effect, right?

That is correct. Well, I honestly see NamedControls as an already established feature of SC to deal with input sounds, and I did not want to break conventions by introducing a different way of doing so.

I feel you, Alga manages all of that for you automatically (and much more, like automatic conversions of channels / rates, automatic ordering of nodes in the server, etc…). This was done in an effort of providing a full-featured modular environment with little friction for the user to interact with, in order to be used in a live context. Basically, the goal is to only define what makes sound and how any sound is connected to one another, without worrying about any scaffolding to make it work.

Yes, this is only true if the node was playing. In such a case, you can also just fade out its playing to the speakers by calling stop(time: 2), in case you want it to fade out over 2 seconds.

1 Like

Ah got it. I didn’t know it was possible to route signals like this via NamedControls. Very cool.

Quick bump to announce that I just released a new version with quite a number of improvements and new features. Here is the full CHANGELOG:

1.1.0

  • Added the interpShape option. This allows to specify an interpolation shape in the form of an Env. All connection methods have also been updated to receive a shape argument to set the Env for the specific connection. Check the Examples/AlgaNode/10_InterpShape.scd below:

    (
    Alga.boot({
        a = AlgaNode(
            { SinOsc.ar(\freq.kr(440)) },
            interpTime: 3,
            interpShape: Env([0, 1, 0.5, 1], [1, 0.5, 1])
        ).play
    })
    )
    
    //The connection will use the Env declared in interpShape
    a <<.freq 220;
    
    //Temporary Env (standard ramp)
    a.from(880, \freq, shape: Env([0, 1]))
    
    //Using the original interpShape
    a <<.freq 440;
    
  • Added multithreading support. Now, if booting Alga with supernova, it will automatically parallelize the arrangement of the nodes over the CPU cores. Check the Examples/Extras/Multithreading.scd below:

    //The code to test
    (
    c = {
        var reverb;
    
        //Definition of a bank of 50 sines
        AlgaSynthDef(\sines, {
            Mix.ar(Array.fill(50, { SinOsc.ar(Rand(200, 1000)) * 0.002 }))
        }).add;
    
        s.sync;
    
        //A reverb effect
        reverb = AlgaNode({ FreeVerb1.ar(\in.ar) }).play(chans: 2);
    
        //50 separated banks of 50 sine oscillators.
        //Their load will be spread across multiple CPU cores.
        50.do({
            var bank = AlgaNode(\sines);
            reverb <<+ bank;
        });
    
        //Print CPU usage
        fork {
            loop {
                ("CPU: " ++ s.avgCPU.asStringPrec(4) ++ " %").postln;
                1.wait;
            }
        }
    }
    )
    
    //Boot Alga with the supernova server
    //Alga will automatically spread the load across multiple CPU cores.
    Alga.boot(c, algaServerOptions: AlgaServerOptions(supernova: true, latency: 1));
    
    //Boot Alga with the standard scsynth server.
    //Note the higher CPU usage as the load is only on one CPU core.
    Alga.boot(c, algaServerOptions: AlgaServerOptions(latency: 1))
    
  • Added support for using the same AlgaSynthDefs for both AlgaNodes and AlgaPatterns:

    //1
    (
    Alga.boot({
        AlgaSynthDef(\test, {
            SinOsc.ar(\freq.kr(440))
        }, sampleAccurate: true).add;
    
        s.sync;
    
        //a = AN(\test).play;
    
        b = AP((
            def: \test,
            amp: AlgaTemp({
                EnvPerc.ar(\atk.kr(0.01), \rel.kr(0.25), \curve.kr(-2), 0)
            }, sampleAccurate: true),
            dur: 0.5,
        )).play(chans:2)
    });
    )
    
    //2
    (
    Alga.boot({
        y = AP((
            def: { SinOsc.ar(\freq.kr(440)) * \amp.kr(1) },
            amp: AlgaTemp({
                EnvPerc.kr(\atk.kr(0.01), \rel.kr(0.25), \curve.kr(-2), 0)
            }),
            dur: 0.5,
        )).play(chans:2)
    })
    )
    
    //3
    (
    Alga.boot({
        y = AP((
            def: { SinOsc.ar(\freq.kr(440)) },
            amp: Pseq([
                AlgaTemp({
                    EnvPerc.ar(\atk.kr(0.01), \rel.kr(0.25), \curve.kr(-2), 0) * 0.25
                }, sampleAccurate: true),
                AlgaTemp({
                    EnvPerc.ar(\atk.kr(0.001), \rel.kr(0.1), \curve.kr(-2), 0) * 0.25
                }, sampleAccurate: true),
            ], inf),
            freq: Pwhite(440, 880),
            dur: Pwhite(0.01, 0.2),
        )).play(chans:2)
    })
    )
    
  • Added support for sustain / stretch / legato to AlgaPatterns:

    //1
    (
    Alga.boot({
        a = AP((
            def: { SinOsc.ar * EnvGen.ar(Env.adsr, \gate.kr) }, //user can use \gate
            sustain: Pseq([1, 2], inf), //reserved keyword
            dur: 3
        )).play
    })
    )
    
    //2
    (
    Alga.boot({
        a = AP((
            def: { SinOsc.ar },
            amp: AlgaTemp({ EnvGen.ar(Env.adsr, \gate.kr) }, sampleAccurate: true),
            sustain: Pseq([1, 2], inf),
            dur: 3
        )).play
    })
    )
    
    //3
    (
    Alga.boot({
        a = AP((
            def: { SinOsc.ar(\freq.kr(440)) *
                EnvGen.kr(Env.adsr(\atk.kr(0.01), \del.kr(0.3), \sus.kr(0.5), \rel.kr(1.0)), \gate.kr, doneAction: 2) *
                \amp.kr(0.5)
            },
            dur: 4,
            freq: Pseed(1, Pexprand(100.0, 800.0).round(27.3)),
            amp: Pseq([0.3, 0.2], inf),
            //sustain: 4,
            legato: Pseq([1, 0.5], inf),
            rel: 0.1,
            callback: { |ev| ev.postln },
        )).sustainToDur_(true).play
    })
    )
    
    //Same as:
    (
    Alga.boot({
        a = AP((
            def: { SinOsc.ar(\freq.kr(440)) *
                EnvGen.kr(Env.adsr(\atk.kr(0.01), \del.kr(0.3), \sus.kr(0.5), \rel.kr(1.0)), \gate.kr, doneAction: 2) *
                \amp.kr(0.5)
            },
            dur: 4,
            freq: Pseed(1, Pexprand(100.0, 800.0).round(27.3)),
            amp: Pseq([0.3, 0.2], inf),
            sustain: 4,
            legato: Pseq([1, 0.5], inf),
            rel: 0.1,
            callback: { |ev| ev.postln },
        )).play
    })
    )
    
  • Added the AlgaStep class. This class allows to schedule actions not on a specific beat, but at a specific “pattern trigger” that will happen in the future:

    (
    Alga.boot({
        a = AlgaPattern((
            def: { SinOsc.ar(\freq.ar(440)) * EnvPerc.ar * 0.5 },
            dur: 1
        )).play
    })
    )
    
    //Schedule 2 triggers from now
    a.from(Pseq([220, 880], inf), \freq, time: 1, sched: AlgaStep(2))
    
    //Schedule at the next trigger
    a.from(Pseq([0.5, 0.25], inf), \dur, sched: AlgaStep(0))
    
  • Added the AlgaProxySpace class. This allows to quickly define AlgaNodes and AlgaPatterns in a fashion that is similar to SC’s ProxySpace. Check the help files and the Examples folder for a deeper look at all of its features.

    p = AlgaProxySpace.boot;
    
    //A simple node
    ~a = { SinOsc.ar(100) };
    
    //Use it as FM input for another node
    ~b.play(chans:2);
    ~b.interpTime = 2;
    ~b.playTime = 0.5;
    ~b = { SinOsc.ar(\freq.ar(~a).range(200, 400)) };
    
    //Replace
    ~a = { SinOsc.ar(440) };
    
    //New connection as usual
    ~b.from({ LFNoise1.ar(100) }, \freq, time:3)
    
  • Introducing AlgaPatternPlayer, a new way to trigger and dispatch AlgaPatterns:

    (
    Alga.boot({
        //Define and start an AlgaPatternPlayer
        ~player = AlgaPatternPlayer((
            dur: Pwhite(0.2, 0.7),
            freq: Pseq([440, 880], inf)
        )).play;
    
        //Use ~player for both indexing values and triggering the pattern
        ~pattern = AP((
            def: { SinOsc.ar(\freq.kr + \freq2.kr) * EnvPerc.ar },
            freq: ~player[\freq],
            freq2: ~player.read({ | freq |
                if(freq == 440, { freq * 2 }, { 0 })
            }),
        ), player: ~player).play;
    })
    )
    
    //Interpolation still works
    ~pattern.from(~player.({ | freq | freq * 0.5 }), \freq, time: 5) //.value == .read
    ~pattern.from(~player.({ | freq | freq * 2 }), \freq2, time: 5)
    
    //Modify dur
    ~player.from(0.5, \dur, sched: AlgaStep(3))
    
    //If modifying player, the interpolation is still triggered on the children
    ~player.from(Pseq([330, 660], inf), \freq, time: 5)
    
    //Removing a player stops the pattern triggering
    ~pattern.removePlayer;
    
3 Likes

Hello !

Still investigating Alga, it’s great.
Is there any mean to scope / plot / trace the output of an AlgaNode ?
Also, is it possible to show the running nodes ? There’s SC node display but there are lots of nodes created with a single AlgaNode.

All the best

Geoffroy

Hi Geoffroy,

I am glad you are enjoying Alga :slight_smile:

There currently are no native ways to plot or scope an AlgaNode, but I definitely agree that it should be a feature! In fact, I opened a GH issue to track this.

One way to get around this limitation, for now, is to do something like this:

(
Alga.boot({
	a = AN { SinOsc.ar(1) };
	1.wait;
	a.synthBus.bus.plot(1);
})
)

However, if you call replace, the synthBus will change, so you need to keep that in mind.

Not really, unless you use the AlgaProxySpace class, which stores the nodes:

(
p = AlgaProxySpace.boot({
	~a = { SinOsc.ar(1) };
	~b = { SinOsc.ar(2) };
	~c = { SinOsc.ar(3) };
})
)

p.nodes.postln

In the future, I would love to build a GUI that shows the whole node graph with the correct connections, but it will be probably be achieved when I move this project out of SC entirely.

1 Like