Musical Patterns in SC Lang

Throughout this tutorial we have been creating synthesizers, effects, routing them through busses, putting them into groups and more, but for many the question is how to make musical patterns or arrange events in time. For this we need some kind of a representation of the events, for example stored in an array, or generated algorithmically on the fly. Chapter 3 introduced some basic ways of controlling synths, but in this section we will explore in a bit more detail how to arrange musical events in time.

The SynthDefs

For now we'll use two synth definitions.

SynthDef(\sine, {arg out=0, amp=0.1, freq=440, envdur=1, pan=0.0;
    var signal;
    signal = Pan2.ar(SinOsc.ar(freq, 0, amp**amp).cubed, pan); // note the pan
    signal = signal * EnvGen.ar(Env.perc(0.01, envdur), doneAction:2);
    Out.ar(out, signal);
}).add;

SynthDef(\synth1, {arg out=0, freq=440, envdur=1, amp=0.4, pan=0;
    var x, env;
    env = EnvGen.kr(Env.perc(0.001, envdur, amp), doneAction:2);
    x = Mix.ar([FSinOsc.ar(freq, pi/2, 0.5), Pulse.ar(freq,Rand(0.3,0.7))]);
    x = RLPF.ar(x,freq*4,Rand(0.04,1));
    x = Pan2.ar(x,pan);
    Out.ar(out, x*env);
}).add; 

Routines and Tasks

We have already explored how to play a melody using a Task and a Routine (check the documentation for each, but in short a Task is a Routine that can be paused).

Function has a method called "fork" which will turn the function into a Routine (co-routine, and some could think of it as a "thread" - although technically it's not), but this allows for a process to run independently of what is happening elsewhere in the program.

Routine({
    1.postln; 
    Synth(\sine, [\freq, 220]);
    0.5.wait;
    2.postln;
    Synth(\sine, [\freq, 220*2]);
    0.5.wait;
    3.postln;
    Synth(\sine, [\freq, 220*3]);
    0.5.wait;
}).play

This could also be written as:

{ 3.do({arg i; (i+1).postln; Synth(\sine, [\freq, 220*(i+1)]); 0.5.wait }) }.fork

Or unpacked:

{ 
    3.do({arg i; 
        (i+1).postln; 
        Synth(\sine, [\freq, 220*(i+1)]); 
        0.5.wait;
    }) 
}.fork

So with a little melody stored in an array we could play it repeatedly:

m = [60, 63, 64, 61];

{ inf.do({arg i; Synth(\sine, [\freq, m.wrapAt(i).midicps]); 0.5.wait }) }.fork

The "fork" is running a routine and the routine is played by SuperCollider's default TempoClock.

If you keep that code running and then evaluate:

TempoClock.default.tempo = 2

You will see how the tempo changes, as the 0.5.wait in the Routine is half a beat of the tempo clock that has now changed its tempo.

Clocks in SuperCollider

All temporal tasks in SuperCollider run from one of the language's clocks. There are 3 clocks in SuperCollider:

- SystemClock (the main clock that starts when you launch SC)
- TemploClock (same as SystemClock but counts in beats as opposed seconds)
- AppClock (musically unreliable, but good for communicating with GUIs or external hardware)

Routines, Tasks and Patterns can all run by these 3 different clocks. You pass the clocks as arguments to them.

SystemClock

Let's have a quick look at the SystemClock:

(
SystemClock.sched(2.0,{ arg time;  
    time.postln; 
    0.5 // wait between next scheduled event
});
)

(
SystemClock.sched(2.0,{ arg time;  
    "HI THERE! Long wait".postln; 
    nil // no wait - no next scheduled event
});
)

You can also schedule an event for an absolute time:

(
SystemClock.schedAbs( (thisThread.seconds + 4.0).round(1.0),{ arg time;
    ("the time is exactly " ++ time.asString 
        ++ " seconds since starting SuperCollider").postln;
});
)

AppClock

The AppClock works pretty much the same but uses different source clocks (Apples NSTimers).

You could try to create a GUI which is updated by a clock.

w = Window.new("oo", Rect(100, 100, 240, 100)).front;
x = Slider.new(w, Rect(20, 20, 200, 40));

// This works
{inf.do({x.value_(1.0.rand); 0.4.wait})}.fork(AppClock)

// However this won't work (as it's using the TempoClock by default)
{inf.do({x.value_(1.0.rand); 0.4.wait})}.fork

You will get an error message that could become familiar:

"Operation cannot be called from this Process. Try using AppClock instead of SystemClock."

You can also get this done by "deferring" the command to the AppClock using .defer.

{inf.do({ {x.value_(1.0.rand)}.defer; Synth(\sine); 0.4.wait})}.fork

So here we are using the SystemClock to play the \sine synth, but deferring the updating of the GUI to the AppClock.

TempoClock

TempoClocks are typically used for musical tasks. You can run many tempo clocks at the same time, at different tempi or in different meters. TempoClocks are ideal for high priority scheduling of musical events, and if there is a need for external communication, such as MIDI, GUI or Serial communication, the trick is to defer that message with a "{}.defer".

Let's explore the tempo clock:

t = TempoClock(2); // tempo is 2 beats per second (120 bpm);

Many people who think in BPM (beats per minute) typically set the argument to the tempo clock as "120/60" (which equals to 2 beats per second), or "60/60" (which is 1 bps, and SuperCollider's "default" tempo).

The clock above is now in a variable "t" and we can use it to schedule events (at a particular beat in the future):

t.schedAbs(t.beats.ceil, { arg beat, sec; [beat, sec].postln; 1});
t.schedAbs(t.beats.ceil, { arg beat, sec; "ho ho --".post; [beat, sec].postln; 1 });

And we can change the tempo:

t.tempo_(4)

t.beatDur // we can ask the clock the duration of the beats
t.beats // the beat time of the clock

t.clear

Polyrhythm of 3/4 against 4/4

(
t = TempoClock(4);
t.schedAbs(t.beats.ceil, { arg beat, sec;
    beat.postln;
    if (beat % 2==0, {Synth(\sine, [\freq, 444])});
    if (beat % 4==0, {Synth(\sine, [\freq, 333])});
    if (beat % 3==0, {Synth(\sine, [\freq, 888])});
    1; // repeat
});
)
t.tempo_(6)

Polyrhythm of 5/4 against 4/4

(
t = TempoClock(4);
t.schedAbs(t.beats.ceil, { arg beat, sec;
    if (beat % 2==0, {Synth(\sine, [\freq, 444])});
    if (beat % 4==0, {Synth(\sine, [\freq, 333])});
    if (beat % 5==0, {Synth(\sine, [\freq, 888])});
    1; // repeat
});

)

Or perhaps a polyrhythm of 5/4 against 4/4 where the bass line is in 4/4 and the high synth in 5/4.

(
t = TempoClock(4);

t.schedAbs(t.beats.ceil, { arg beat, sec;
    if (beat % 2==0, {Synth(\sine, [\freq, 60.midicps])});
    if (beat % 4==0, {Synth(\sine, [\freq, 64.midicps])});
    if (beat % 5==0, {Synth(\synth1, [\freq, 72.midicps])});
    if (beat % 5==3, {Synth(\synth1, [\freq, 77.midicps])});
    1; // repeat
});
)

Another version

(
t = TempoClock(4);

t.schedAbs(t.beats.ceil, { arg beat, sec;
    if (beat % 4==0, {"one".postln; Synth(\sine, [\freq, 60.midicps])});
    if (beat % 4==2, {"two".postln; Synth(\sine, [\freq, 72.midicps])});
    if ((beat % 4==1) || (beat % 4==3), {Synth(\sine, [\freq, 84.midicps])});

    if (beat % 5==0, {Synth(\synth1, [\freq, 89.midicps, \amp, 0.2])});
    if (beat % 5==2, {Synth(\synth1, [\freq, 96.midicps, \amp, 0.2])});
    1; // repeat
});
)

We can try to make this a bit more interesting by creating another synth:

(
SynthDef( \klanks, { arg freqScale = 1.0, amp = 0.1;
    var trig, klan;
    var  p, exc, x, s;
    trig = Impulse.ar( 0 );
    klan = Klank.ar(`[ Array.fill( 16, { linrand(8000.0 ) + 60 }), nil, Array.fill( 16, { rrand( 0.1, 2.0)})], trig, freqScale );
    klan = (klan * amp).softclip;
    DetectSilence.ar( klan, doneAction: 2 );
    Out.ar( 0, Pan2.ar( klan ));
}).store;
)

And play the same polyrhythm.

(
t = TempoClock(4);

t.schedAbs(t.beats.ceil, { arg beat, sec;
    if (beat % 4==0, {"one".postln; Synth(\klanks, [\freqScale, 40.midicps])});
    if (beat % 4==2, {"two".postln; Synth(\klanks, [\freqScale, 52.midicps])});
    if ((beat % 4==1) || (beat % 4==3), {Synth(\klanks, [\freqScale, 43.midicps])});

    if (beat % 7==0, {Synth(\synth1, [\freq, 88.midicps, \amp, 0.2])});
    if (beat % 7==3, {Synth(\synth1, [\freq, 96.midicps, \amp, 0.2])});
    if (beat % 7==5, {Synth(\synth1, [\freq, 86.midicps, \amp, 0.2])});

    1; // repeat
});
)

t.tempo_(8)

an example showing tempo changes

(
t = TempoClock(80/60); // 80 bpm
// schedule an event at next whole beat
t.schedAbs(t.beats.ceil, { arg beat, sec; 
    "beat : ".post; beat.postln;
    if (beat % 4==0, { Synth(\sine, [\freq, 60.midicps]) });
    if (beat % 4==2, { Synth(\sine, [\freq, 67.midicps]) });
    if (beat % 0==0, { Synth(\sine, [\freq, 72.midicps]) });
    1 // 1 here means that we are repeating/looping this
});
t.schedAbs(16, { arg beat, sec; 
    " ****  tempochange on beat : ".post; beat.postln; 
    t.tempo_(150/60); // 150 bpm
});
5.do({ |i| // on beats 32, 36, 40, 44, 48 
    t.schedAbs(32+(i*4), { arg beat, sec;
        " ****  tempo is now : ".post; (150-(10*(i+1))).post; " BPM".postln; 
        t.tempo_((150-(10*(i+1)))/60); // going down by 10 bpm each time
    });
});
t.schedAbs(60, { arg beat; t.tempo_(200/60) }); // 200 bpm
t.schedAbs(76, { arg beat;
    t.clear;
    t.schedAbs(t.beats.ceil, { arg beat, sec; 
        "beat : ".post; beat.postln;
        if (beat % 4==0, { Synth(\sine, [\freq, 67.midicps]) });
        if (beat % 4==2, { Synth(\sine, [\freq, 74.midicps]) });
        if (beat % 0==0, { Synth(\sine, [\freq, 79.midicps]) });
        1 // 1 here means that we are repeating/looping this
    });
    t.schedAbs(92, { arg beat; t.stop }); // stop it!
}); // 200 bpm
t.schedAbs(92, { arg beat; t.stop }); // if we tried to stop it here, it would have been "cleared"
)

A survey of Patterns

We can try to play the above synth definitions with Patterns and it will play using the default arguments of patterns (see the Event source file). Let's start by exploring the Pbind pattern. As we saw in chapter 3, if you run the code below:

().play // "()" is an empty Event dictionary
Pbind().play // Pbind plays an empty Event

You can hear that there are default arguments, like a note played every second, an instrument is used (SuperCollider's \default) and a frequency (440Hz).

In the example below, we use Pbind (Pattern that binds keys (synth def arguments) and their arguments). Here we pass the \sine synth def as the argument for the \instrument (again as defined in the Event class).

Pbind(\instrument, \sine).play // it plays our synth definition

Pbind(\instrument, \sine, \freq, Pseq([60, 65, 57, 62].midicps)).play // it plays our synth definition

Our \sine synth has a frequency argument, and we are sending the frequency directly. However, if we wanted we could also send 'note' or 'midinote' arguments, but here the values are converted internally to the \freq argument of \sine.

Pbind(\instrument, \sine, \note, Pseq([0, 5, 7, 2])).play // it plays our synth definition

Pbind(\instrument, \sine, \midinote, Pseq([60, 65, 57, 62])).play // it plays our synth definition

Pattern definitions (Pdef) are a handy way to define and play patterns. They are a bit like Synth definitions in that they have a unique name and can be recompiled on the fly.

(
Pdef(\scale, Pbind(    \instrument, \sine,
                    \freq, Pseq([62, 64, 67, 69, 71, 74], inf).midicps,
                    \dur,  Pseq([0.25, 0.5, 0.25, 0.25, 0.5, 0.5], inf)
)); 
)

a = Pdef(\scale).play;
a.pause     // pause. the stream
a.resume     // resume it
a.stop     // stop it (resets it)
a.play     // start again

Then we can set variables in our instrument using .set

Pdef(\scale).set(\out, 20); // outbus 20 
Pdef(\scale).set(\out, 0); // outbus 0 

// here we set the duration of the envelope in our instrument
Pdef(\scale).set(\envdur, 0.2);

Patterns use default keywords defined in the Event class, so take care not to use those keywords in your synth definitions. If we had used dur instead of envdur for the envelope in our instrument, this would happen:

Pdef(\scale).set(\dur, 0.1);

because dur is a keyword of Patterns (the main ones are \dur, \freq, \amp, \out, \midi)

resetting the freq info is not possible however :

Pdef(\scale).set(\freq, Pseq([72,74,72,69,71,74], inf).midicps);

one solution would be to resubmit the Pattern Definition:

(
Pdef(\scale, Pbind( \instrument, \sine,
                \freq, Pseq([72,74,72,69,71,74], inf).midicps, // different sequence
                \dur,  Pseq([0.25, 0.5, 0.25, 0.25, 0.5, 0.5], inf)
)); 
)

and it's still in our variable "a", it's just the definition that's different

a.pause
a.resume

Patterns and environmental variables

Or we could use Pdefn (read the helpfiles to compare Pdef and Pdefn) (here we are using envrionment variables to refer to patterns)

We use a Pdefn to hold the scale

Pdefn(\scaleholder, { |arr| Pseq(arr.freqarr) });
// and we add an array to it
Pdefn(\scaleholder).set(\freqarr, Array.fill(6, {440 +(300.rand)} ));

then we play a Pdef with the Pdefn

Pdef(\scale, 
        Pbind(     \instrument, \synth1,
                \freq, Pn(Pdefn(\scaleholder), inf), // loop
                \dur, 0.4
            )

); 
a = Pdef(\scale).play;

// and we can reset our scale 
Pdefn(\scaleholder).set(\freqarr, Array.fill(3, {440 +(300.rand)} ));

Another example

(
Pdefn(\deg, Pseq([0, 3, 2],inf));

Pset(\instrument, \synth1, 
    Ppar([
        Pbind(\degree, Pdefn(\deg)),
        Pbind(\degree, Pdefn(\deg), \dur, 1/3)
])
).play;
)

Pdefn(\deg, Prand([0, 3, [1s, 4]],inf));
Pdefn(\deg, Pn(Pshuf([4, 3, 2, 7],2),inf));
Pdefn(\deg, Pn(Pshuf([0, 3],2),inf));

(
Pdefn(\deg, Plazy { var pat;
                pat = [Pshuf([0, 3, 2, 7, 6],2), Pshuf([3, 2, 6],2), Pseries(11, -1, 11)].choose;
                Pn(pat, inf)
        });
)

/////////////// p

(
Pdef(\player).set(\instrument, \synth1);

Pdef(\player,
    Pbind(
        \instrument,     Pfunc({ |e| e.instrument }),
        \midinote,     Pseq([45,59,59,43,61,43,61,61,45,33,31], inf),
        \dur,         Pseq ([0.25,1,0.25,0.5,0.5,0.5,0.125,0.125,0.5,0.25,0.25], inf),
        \amp,         Pseq ([1,0.1,0.2,1,0.1125,0.1125,1,0.1125,0.125,0.25,1,0.5], inf)
    )
);
)

Pdef(\player).play;

Pdef(\player).set(\instrument, \synth1);
Pdef(\player).set(\envdur, 0.1);
Pdef(\player).set(\envdur, 0.25);
Pdef(\player).set(\envdur, 1);
Pdef(\player).set(\instrument, \sine);

///////////////////////////////////////////////////////

(
~scale = [62,67,69, 77];

c = Pdef(\p04b, 
        Pbind(\instrument, \synth1, 
                    \freq, (Pseq.new(~scale, inf)).midicps, // freq arg
                    \dur, Pseq.new([1, 1, 1, 1], inf);  // dur arg
        )
);

c = Pdef(\p04c, 
        Pbind(\instrument, \synth1,
                    \freq, (Pseq.new(~scale, inf)).midicps, // freq arg
                    \dur, Pseq.new([1, 1, 1, 1], inf);  // dur arg
        )
);
)

Pdef(\p04b).quant_([2, 0, 0]);
Pdef(\p04c).quant_([2, 0.5, 0]); // offset by half a beat
Pdef(\p04b).play;
Pdef(\p04c).play;

(quant can't be reset in real-time, so we use align to align patterns). align takes the same arguments as quant (see helpfile of Pdef)

Pdef(\p04c).align([4, 0, 0]);
Pdef(\p04c).align([4, 0.75, 0]); // offset by 3/4 a beat

Another useful pattern is Tdef (Task patterns)

Tdef(\x, { loop({ Synth(\sine, [\freq, 200+(440.rand)]); 0.25.wait; }) });

TempoClock.default.tempo = 2; // it runs on the default tempo clock

Tdef(\x).play(quant:1);
Tdef(\x).stop;

and we can redefine the definition "x" in realtime whilst playing

Tdef(\x, { loop({ Synth(\synth1, [\freq, 200+(440.rand)]); 1.wait; }) });

Tdef(\y, { loop({ Synth(\synth1, [\freq, 1200+(440.rand)]); 1.wait; }) });
Tdef(\y).play(quant:1);

Tdef(\y).stop;

to change the values in a pattern in realtime, use List instead of Array:

~notes = List[63, 61, 64, 65];

Pbind(
    \midinote, Pseq(~notes, inf),
    \dur, Pseq([0.4, 0.2, 0.1, 0.2], inf)
).play;

~notes[1] = 80

yet another (known?) melody

(
Pbind(
     \midinote, Pseq([72, 76, 79, 71, 72, 74, 72, 81, 79, 84, 79, 77, 76, 77, 76], 1),
     \dur, Pseq([4, 2, 2, 3, 0.5, 0.5, 4, 4, 2, 2, 2, 1, 0.5, 0.5, 2]/4, 1)
 ).play
)

USING Pfx (effects patterns)

Make the synthdef and add it

SynthDef(\testenv2, { arg in=0, dur=2;
    var env;
    env = EnvGen.kr(Env.sine(dur), doneAction:2).poll;
    XOut.ar(0, 1, (In.ar(in, 1)+WhiteNoise.ar(0.1)) * env); // add noise for clarity
}).add;


p = Pbind(\degree, Pseq([0, 4, 4, 2, 8, 3, 2, 0]), \dur, 0.5);
p.play
q = Pfx(p, \testenv, \dur, 4); // play it... all working (sine env is 4 secs)
q.play

Now write the def to disk

SynthDef(\testenv2, { arg in=0, dur=2;
    var env;
    env = EnvGen.kr(Env.sine(dur), doneAction:2).poll;
    XOut.ar(0, 1, (In.ar(in, 1)+WhiteNoise.ar(0.1)) * env); // add noise for clarity
}).writeDefFile;


// quit SuperCollider, open it again and now try this
p = Pbind(\degree, Pseq([0, 4, 4, 2, 8, 3, 2, 0]), \dur, 0.5);
q = Pfx(p, \testenv, \dur, 4); // not working (sine env is 2 secs, the synthdef default)
q.play

but here is the trick, read the SynthDescLib and try again!

SynthDescLib.global.read;
q = Pfx(p, \testenv, \dur, 4); // not working (sine env is 2 secs, the synthdef default)
q.play

rendering the pattern as soundfile to disk (it will be written to your SuperCollider folder)

q.render(
    "ixi_tutorial_render_test.aif", 
    4, 
    sampleFormat: "int16", 
    options: Server.default.options.numOutputBusChannels_(2)
);

TempoClock and Patterns

To chage the tempo of the above Patterns, you can use the default TempoClock (as you didn't register a TempoClock for the pattern)

TempoClock.default.tempo = 1.2

But if you want to have each pattern playing different TempoClocks, you need to create 2 clocks and use them to drive each pattern. (this way one can do some nice phasing/polyrhytmic stuff)

(
t = TempoClock.new;
u = TempoClock.new;
Pdef(\p04b).play(t);
Pdef(\p04c).play(u);
u.tempo = 1.5
)

it's hard to get this clear as they are running the same pitch patterns so let's redefine one of the patterns:

(
Pdef(\p04c, 
        Pbind(\instrument, \synth1,
                    \freq, (Pseq.new(~scale.scramble, inf)).midicps*2, // freq arg
                    \dur, Pseq.new([1, 1, 1, 1], inf);  // dur arg
        )
)
)
// and try to change the tempo 
u.tempo = 1;
u.tempo = 1.2;
u.tempo = 1.8;
u.tempo = 3.2;

Popcorn

An example of making a tune using patterns. For an excellent example take a look at spacelab, in examples/pieces/spacelab.scd

SynthDescLib.global.read;

// the poppcorn

(
~s1 = [72, 70, 72, 67, 64, 67, 60];
~s2 = [72, 74, 75, 74, 75, 74, 72, 74, 72, 74, 72, 70, 72, 67, 64, 67, 72];

~t1 = [0.25, 0.25, 0.25, 0.25, 0.125, 0.25, 0.625];
~t2 = [0.25, 0.25, 0.25, 0.125, 0.25, 0.125, 0.25, 0.25, 0.125, 0.25, 0.125, 0.25, 0.25, 0.25, 0.125, 0.25, 0.5 ];

c = Pdef(\moogy, 
        Pbind(\instrument, \synth1, // using our synth1 synthdef
                    \freq, 
                        Pseq.new([
                            Pseq.new([
                                Pseq.new(~s1.midicps, 2),
                                Pseq.new(~s2.midicps, 1)
                                ], 2),
                            Pseq.new([
                                Pseq.new((~s1+7).midicps, 2),
                                Pseq.new((~s2+7).midicps, 1)
                                ], 2)    
                            ], inf),
                    \dur, Pseq.new([ 
                            Pseq.new(~t1, 2),
                            Pseq.new(~t2, 1)
                            ], inf)
        )
);
Pdef(\moogy).play
)

Mozart

A little transcription of Mozart’s Piano Sonata No 16 in C major. Here the instrument has been put into a variable called "instr" so it's easier to quickly change the instrument.

(
var instr = \default;
Ppar([
// right hand - using the Event-style notation
Pseq([
        (\instrument: instr, \midinote: 72, \dur: 1),
        (\instrument: instr, \midinote: 76, \dur: 0.5),
        (\instrument: instr, \midinote: 79, \dur: 0.5),
        (\instrument: instr, \midinote: 71, \dur: 0.75),
        (\instrument: instr, \midinote: 72, \dur: 0.125),
        (\instrument: instr, \midinote: 74, \dur: 0.125),
        (\instrument: instr, \midinote: 72, \dur: 1),
        (\instrument: instr, \midinote: 81, \dur: 1),
        (\instrument: instr, \midinote: 79, \dur: 0.5),
        (\instrument: instr, \midinote: 84, \dur: 0.5),
        (\instrument: instr, \midinote: 79, \dur: 0.5),
        (\instrument: instr, \midinote: 77, \dur: 0.25),
        (\instrument: instr, \midinote: 76, \dur: 0.125),
        (\instrument: instr, \midinote: 77, \dur: 0.125),
        (\instrument: instr, \midinote: 76, \dur: 1)
], 1),

// left hand - array notation
Pbind(\instrument, instr, 
        \midinote, Pseq([60, 67, 64, 67, 60, 67, 64, 67, 62, 67, 65, 67, 60, 67, 64, 67,
                     60, 69, 65, 69, 60, 67, 64, 67, 59, 67, 62, 67, 60, 67, 64, 67 ], 1),
        \dur, 0.25
        )], 1).play
)

Syncing Patterns and TempoClocks

SynthDef(\string, {arg out=0, freq=440, pan=0, sustain=0.5, amp=0.3;
    var pluck, period, string;
    pluck = PinkNoise.ar(Decay.kr(Impulse.kr(0.005), 0.05));
    period = freq.reciprocal;
    string = CombL.ar(pluck, period, period, sustain*6);
    string = LeakDC.ar(LPF.ar(Pan2.ar(string, pan), 12000)) * amp;
    DetectSilence.ar(string, doneAction:2);
    Out.ar(out, string)
}).add;


SynthDef(\impulse, {
    Out.ar(0, Impulse.ar(0)!2);    
}).add

Synth(\impulse)

Pbind(
    \instrument, \impulse,
    \dur, 1
).play(TempoClock.default, quant:1)

// not working
TempoClock.default.play({
    Synth(\impulse, [\amp, 2]); // this is the problem
    1.0
    }, quant:[1, Server.default.latency] );

// working
TempoClock.default.play({
    s.sendBundle(0.2, ["/s_new", \impulse, s.nextNodeID, 0, 1]);
    1.0
    }, quant:[1, 0] );

TempoClock.default.tempo = 2.5

Pbind(
    \instrument, \string,
    \freq, Pseq([440, 880], inf),
    \dur, 1
).play(TempoClock.default, quant:1);

TempoClock.default.play({arg i;
    s.sendBundle(0.2, ["/s_new", \string, s.nextNodeID, 0, 1, \freq, if(i.asInteger.even, {660}, {770}), \amp, 0.3]);
    1.0
    }, quant:[1, 0] );