Alga: interpolating live coding environment

Thanks a lot for your quick answer.
Why do you want to move the project out of SC ? Do you want to use another language ? I love the fact that it uses SC :slight_smile:

The idea is to have Alga as its own language with its own TUI / GUI paradigms, syntax, interface, etc…

This is not going to happen anytime soon, and I am already very satisfied with SC as the “backend” for this project. One other option to consider is to ship custom scsynth / sclang builds with an alga language frontend. This might be the next step for the project, we’ll see how it evolves! :slight_smile:

1 Like

Good luck for this awesole project ! I really find it easier to grasp than JITLib and my colleagues are starting to love it too !

1 Like

New release: version 1.2.

This one comes up with the much anticipated feature of time interpolation! It is now in fact possible to interpolate the dur parameter of an AlgaPattern, or the entire tempo of Alga with the global Alga.interpTempo method.

Also, lots of other features. Here is the full CHANGELOG:

1.2

New features

  • AlgaPattern: it is now possible to interpolate the 'dur' parameter!

    (
    Alga.boot({
        //Pattern to modify
        a = AP((
            def: { SinOsc.ar * EnvPerc.ar(release: 0.1) * 0.5 },
            dur: 0.5
        )).play(chans:2);
    
        //Metronome
        b = AP((
            def: { EnvPerc.ar(release: SampleDur.ir); Impulse.ar(0) },
            dur: 1
        )).play(chans:2);
    })
    )
    
    //Using Env and resync
    a.interpDur(0.1, time: 5, shape: Env([0, 1, 0.5, 1], [2, 3, 4]))
    
    //No resync
    a.interpDur(0.5, time: 3, resync: false)
    
    //With resync + reset
    a.interpDur(Pseq([0.25, 0.25, 0.125], inf), time: 5, reset: true)
    
    //Other time params work too!
    a.interpStretch(0.5, time: 3, shape: Env([0, 1, 0.5, 1], [2, 3, 4]))
    
  • AlgaPattern now supports scalar parameters. This is now the preferred way of interfacing with Buffer parameters (check Examples/AlgaPattern/03_Buffers.scd).
    Also, scalar parameters can be used for optimization reasons for parameters that need to be set only once at the trigger of the Synth instance, without the overhead of the interpolator.

    (
    Alga.boot({
        a = AP((
            def: { SinOsc.ar(\f.ir) * EnvPerc.ar },
            f: Pseq([440, 880], inf)
        )).play(chans: 2)
    })
    )
    
    //Change at next step
    a.from(Pseq([330, 660], inf), \f, sched: AlgaStep())
    
    //Change at next beat
    a.from(Pseq([220, 440, 880], inf), \f, sched: 1)
    
  • AlgaPattern and AlgaPatternPlayer now support Pfunc and Pkey.
    All scalar and non-SynthDef parameters are now retrievable from any parameter. These are ordered alphabetically:

    (
    Alga.boot({
        a = AP((
            def: { SinOsc.ar(\f.kr) * EnvPerc.ar },
            _f: 440, 
            f: Pfunc { | e | e[\_f] }
        )).play(chans: 2)
    })
    )
    
    //Interpolation still works
    a.from(Pfunc { | e | e[\_f] * 2 }, \f, time: 3)
    
    //These can be changed anytime
    a.from(880, \_f, sched: AlgaStep())
    
  • AlgaPattern: added the lf annotator for functions in order for them to be declared as LiteralFunctions.
    These are used for keys that would interpret all Functions as UGen Functions, while they however could represent a Pfunc or Pif entry:

    (
    Alga.boot({
        a = AP((
            //.lf is needed or it's interpreted as a UGen func
            def: Pif(Pfunc( { 0.5.coin }.lf ), 
                { SinOsc.ar(\freq.kr(440)) * EnvPerc.ar },
                { Saw.ar(\freq.kr(440)) * EnvPerc.ar * 0.5 }
            ),
    
            //No need to .lf here as 'freq' does not expect UGen functions like 'def' does
            freq: Pif( Pfunc { 0.5.coin },
                AT({ LFNoise0.kr(10) }, scale: [440, 880]),
                Pseq([ AT { DC.kr(220) }, AT { DC.kr(440) }], inf)
            )
        )).play(chans:2)
    })
    )
    
  • AlgaSynthDef: can now correctly store and retrieve AlgaSynthDefs with the write / load /
    store calls. By default, these are saved in the AlgaSynthDefs folder in the AlgaLib folder,
    but it can be changed to wherever. The definitions in AlgaSynthDefs are automatically read at
    the booting of Alga. Other definitions can be read with the Alga.readAll / Alga.readDef
    calls.

Bug fixes

  • Major parser rewrite that leads to more predictable results and a larger amount of patterns supported.
8 Likes

Wow ! Another major release ! Congrats !

1 Like

hi and thx @vitreo12.

I’ve got some questions about AlgaPattern inside AlgaProxySpace.

  1. I’m still not fully understand of what’s going on under the hood of AP since it’s using an event instead of a habitual Pbind. Is it possible to use something like Plambda/Ppar/Plet/Pget combo or .collect({ |event| … } chain to share data between patterns in Alga?

  2. I found out that simple value sharing can be achieved with AlgaPatternPlayer, but I still can’t figure out how to setup it correctly inside of an AlgaProxySpace. A .from method in the example below not working as it supposed to (according to Alga examples):

Server.killAll;
t = TempoClock(1).permanent_(true);
p = AlgaProxySpace.boot(clock: t);

(
AlgaSynthDef(\fb, {
	var sig;
	sig = SinOscFB.ar(\freq.kr(100), \fb.kr(0.5))!2;
	sig = sig * Env.perc(\atk.kr(0.01), \rel.kr(0.5), 1.0, \crv.kr(-5.0)).kr(2);
}, sampleAccurate: true).add;
)

(
~app = AlgaPatternPlayer((
	dur: Pwhite(0.1, 0.5, inf),
	freq: Pseq([100, 300, 400, 500], inf),
	fb: Pseq([0.0, 0.5, 1.0], inf)
)).play;
)

(
~p = AP((
	def: \fb,
	freq: ~app[\freq],
	fb: ~app[\fb]
), player: ~app).play;
)

~app.from(0.5, \dur);

~app.stop;
~p.stop;
  1. Can you give an advice or share some examples of key/value sharing techniques between patterns in AlgaProxySpace?

Hello @wehrk!

Each AlgaPattern has its own internal state, which cannot be shared as easily as you would with standard Pbinds. AlgaPatternPlayer is th only current way of sharing values across patterns.

I believe you are using an old version of Alga, cause your code actually works on the latest master. It is in fact possible to assign any class to environment variables of an AlgaProxySpace, while it previously only allowed classes that were used as arguments for an AlgaNode (Symbol, Function) or AlgaPattern (Event). If you pull from master, you’ll be fine with that code.

Regarding the AlgaPatternPlayer, as said, is the only class that can be used to share values across AlgaPatterns. Regardless, you can use Pfuncs to retrieve values from anywhere in SC, so you can have your own “outside of Alga” way of dealing with values sharing.

thx @vitreo12 for the detailed response

Some points have become much clearer. Some not. The thing I’m trying to do is to integrate AlgaPatternPlayer into AlgaProxySpace and make it possible to easily update any keys and re-evaluate the whole Pattern-Pbind/AlgaPattern-Event to instantly see/hear results. But when I re-evaluate the related AlgaPattern/AlgaPatternPlayer everything gets stuck. So it seems like the only option is to use .from messages if you’re working with AlgaPatternPlayer in AlgaProxySpace? Like that:

(
t = TempoClock(1).permanent_(true);
p = AlgaProxySpace.boot(server: s, clock: t);
)

(
AlgaSynthDef(\fb, {
	var sig;
	sig = SinOscFB.ar(\freq.kr(100), \fb.kr(0.5))!2;
	sig = sig * Env.perc(\atk.kr(0.01), \rel.kr(0.5), 1.0, \crv.kr(-5.0)).kr(2);
}, sampleAccurate: true).add;
)

(
~app = AlgaPatternPlayer((
	dur: 1
));
~app.play;
)

// update AlgaPatternPlayer keys here ↴
( 
~app.time = 5; 
~app <<.dur 0.1; 
~app <<.freq Pseq([100, 200, 300, 400, 500], inf);
)

(
~p = AP((
	def: \fb
), sched: AlgaQuant(4), player: ~app);
~p.play;
)

// update AlgaPattern keys here ↴
(
~p.it = 5;
~p <<.freq ~app.read({ | freq, fb | freq.postln; fb.postln; freq }, inf); // test to see what values i can extract from ~app
~p <<.amp 0.1;
)

What do you mean by “outside of Alga”? I can try this, but it only returns “default” key/value pairs → ( ‘legato’: 0, ‘dur’: 0.2, ‘def’: fb ). I can assume that you mean something more complex and non-trivial.

(
~p = (
	def: \fb,
	dur: 0.2,
	freq: Pseq([100, 200, 300, 400, 500], inf),
	amp: 0.1,
	share: Pfunc({ | ev | ev.postln }) // returns only ( 'legato': 0, 'dur': 0.2, 'def': fb )
);
)

There is a misconception here about AlgaPatternPlayer. It currently cannot be assigned new parameters if they were not declared in its body, this is why the code won’t work. You’d have to provide an initial freq entry for it:

(
~app = AlgaPatternPlayer((
	dur: 1,
	freq: 440
));
~app.play;
)

What I mean is that with a Pfunc you can retrieve any value from SC:

(
~freqStream = Pseq([220, 440, 880], inf).asStream;
~p = AP((
	def: \fb,
	freq: Pfunc { ~freqStream.next }
));
~p.play;
)

I have actually pushed a change on develop that will now allow you to define new entries even if they were not declared in the original Event body. Your code now works without changes

2 Likes

Actually, this code works on my machine even if I don’t provide an initial freq. But it will only act like that if I specify .time for the whole AlgaPatternPlayer before providing new entries.

Got it. At first I thought of something else. Thank you!

1 Like

New release: 1.3.0.

This one comes up with the new objects AlgaMonoPattern and AlgaSequencer. These allows to create monophonic sequencers to be used as modulation sources for AlgaNodes and AlgaPatterns. Check the help files for more information!

Here’s the CHANGELOG:

1.3.0

New features

  • AlgaMonoPattern: a new class that implements a monophonic interpolated sequencer:

    (
    Alga.boot({
        //Mono sequencer featuring AlgaTemps modulating at audio rate
        ~freqSeq = AlgaMonoPattern((
            rate: \audio,
            chans: 2,
            in: Pseq([
                AlgaTemp({ SinOsc.ar([Rand(1, 100), Rand(1, 100)]) }, scale: [Pwhite(220, 440), Pwhite(440, 880)]),
                [440, 660],
                880
            ], inf),
            time: Pwhite(0.01, 1),
            dur: Prand([0.125, 0.25, 0.5, 1], inf)
        ));
    
        //Mono sequencer featuring AlgaTemps modulating at audio rate
        ~ampSeq = AlgaMonoPattern((
            rate: \audio,
            in: AlgaTemp({ SinOsc.ar(Rand(1, 1000)) }),
            time: Pwhite(0.01, 1),
            dur: Prand([0.5, 1, 2], inf)
        ));
    
        //The AlgaNode to modulate using both FM and AM
        ~sine = AlgaNode({ SinOsc.ar(\freq.ar(440!2)) }, [\freq, ~freqSeq, \amp, ~ampSeq]).play;
    });
    )
    
  • AlgaSequencer: a new class that implements a sequence of AlgaMonoPatterns:

    (
    Alga.boot({
        //Poly sequencer featuring AlgaTemps modulating at audio rate
        ~seq = AlgaSequencer((
            freq: (
                rate: \audio,
                chans: 2,
                in: Pseq([
                    AlgaTemp({ SinOsc.ar([Rand(1, 100), Rand(1, 100)]) }, scale: [Pwhite(220, 440), Pwhite(440, 880)]),
                    [440, 660],
                    880
                ], inf),
                time: Pwhite(0.01, 1)
            ),
            amp: (
                rate: \audio,
                in: AlgaTemp({ SinOsc.ar(Rand(1, 1000)) }),
                time: Pwhite(0.01, 1),
            ),
            dur: Prand([0.5, 1, 2], inf)
        ));
        
        //The AlgaNode to modulate using both FM and AM
        ~sine = AlgaNode({ SinOsc.ar(\freq.ar(440!2)) }, [\freq, ~seq.freq, \amp, ~seq.amp]).play;
    });
    )
    
  • AlgaPattern: 'def' now supports Array entries to create stacked voices:

    (
    Alga.boot({
        a = AlgaPattern((
            def: [{ SinOsc.ar(\freq.kr) }, { Saw.ar(\freq.kr * 0.25) }],
            amp: AlgaTemp({ EnvPerc.ar }),
            freq: Pseq([220, 440, 880], inf), 
            dur: 0.5
        )).play(2)
    })
    )
    
  • AlgaNode: trigger rate parameters are now supported:

    (
    Alga.boot({
        z = AlgaNode({
            var env = Env.perc(0.01, 1).kr(0, \trig.tr(1));
            SinOsc.ar(440) * env;
        }).play(2)
    })
    )
    
    z <<.trig 1
    
4 Likes

hi, @vitreo12

Since the last update I cannot boot any of Alga environments via
Alga.boot and AlgaProxySpace.boot. Right after generating definitions I’m getting these messages:

(...)
-> Done!
-> an AlgaProxySpace
Booting server 'localhost' on address 127.0.0.1:57110.
NetAddr-Connect failed with exception: connect: Connection refused 
NetAddr-Connect failed with exception: connect: Connection refused 
NetAddr-Connect failed with exception: connect: Connection refused
(...)
WARNING: Couldn't connect to TCP address 127.0.0.1:57110

After the warning sign the Server seems to be stuck in the booting phase. I tried to restart the machine, killing all servers, re-installing AlgaLib, switching modes between UDP/TCP, but the error does not go away. While everything else works.

I’m also noted that this behaviour happened before in the 1.2 versions, but evaluating Server.killAll before the booting helped to resolve this behaviour. In the 1.3 versions killing the servers before booting won’t work.

I think this might be a problem with my system and not Alga, but it happens only with AlgaLib. I will be grateful for any help tips!

Hey! That hanging bug often happens when rebuilding the AlgaSynthDefs on boot. I have pushed a fix on master that should take care of not rebuilding the definitions on every boot. Could you try it out?

Also, Alga provides a forceBoot function that will execute the Server.killAll command for you before booting.

1 Like

Thanks! The problem is gone with and without .forceBoot.

1 Like