# Wavetable and discovery

hi everyone,

little topic about wavetable and maybe .sineFill from Signal Class.

I am trying to reproduce a signal that imitates this shape

({EnvGen.kr(Env([0,0,0,0,1,1,1,0],[0,0,0,t.beatDur/2,0, t.beatDur/2,0,0],6,6,0),Trig.kr(1,490))}.plot(duration:2))

This loops is to add shaping to a bass but does not time right (goes out of sync when used as control rate bus) I cannot use an envelope as my sequence is Pseq([1/4],4) and this is to shape 1/1 beat around the [1/4] *4

haha thanks for the lights

Unfortunately you’ll have to take a different approach.

1. The server’s sample rate doesn’t match up exactly with the language side clock (see s.actualSampleRate). It’s very close but we can’t guarantee that a synth control signal will stay in time with the language for long periods of time.

2. If you are running at 44.1 kHz with default server settings, then the control block size does not evenly divide 1.0 sec: 44100 / 64 = 689.0625. So times or rates expressed in terms of seconds are always inexact at kr.

(
a = {
[\ar, \kr].do { |rate|
var pulse = Impulse.perform(rate, 1);
var time = Sweep.perform(rate, 1);
Poll.perform(rate, pulse, time, rate);
};
Silent.ar(1);
}.play;
)

a.free;


Possible solutions are:

• Use ar.
• Use a block size that evenly divides the sample rate. (This may not be practical for 44100 because the block size must be a power of 2 and 44100 = 2×2×3×3×5×5×7×7 so the block size could be at most 4.)
• Use a different sample rate with more factors of 2, e.g. 48000 is divisible by 128 so a 64-sample block size would be fine at that sample rate.

But probably the best solution is to make a synth that produces one cycle of your shape, and trigger it from the language. Then it will stay in sync with language scheduling.

hjh

1 Like

thanks heaps.

I have changed my sameple rate to 48 000 but not sure what would need to be changed next to synchronize

it worked after i made it ar

cannot see the checkbox to tick answered but thank you so much !!

I would still recommend against trying to run a long LFO completely in the server, when you want it to stay tempo-locked with patterns in the language. (If not tempo-locked, then the long LFO is no problem – but this thread is about a tempo-locked control signal.)

Switching to ar means that it won’t drift as quickly, but it will still drift.

Driving the envelope from the language means that it will definitely stay in sync.

If you’re happy with the solution that sort of works for a while (but eventually won’t be working that well), that’s your choice. For myself, I prefer the one that always works.

s.boot;

(
SynthDef(\kick, { |out, freq = 55, aFreq = 300, aTime = 0.08,
dec = 0.3, sus = 0.7, rel = 0.08, amp = 0.1, preamp = 3|
var eg = EnvGen.ar(Env([0, 1, sus, 0], [0.005, dec, rel], -4), doneAction: 2);
var feg = EnvGen.ar(Env([aFreq, freq], [aTime], \exp));
var sig = amp * tanh(SinOsc.ar(feg) * preamp);
Out.ar(out, (sig * eg).dup);

SynthDef(\shape, { |out, beatDur = 1, gate = 1|
var halfBeat = beatDur * 0.5;
var innerGate = Trig1.kr(Impulse.kr(beatDur.reciprocal), beatDur - 0.01);
var sig = EnvGen.kr(
Env([0, 0, 0, 0, 1, 1, 1, 0], [0, 0, 0, halfBeat, 0, halfBeat, 0, 0], 6, 6, 0),
innerGate
);
FreeSelf.kr(gate <= 0);
Out.kr(out, sig);

SynthDef(\pad, { |out, gate = 1, freq = 440, detun = 1.008, amp = 0.1,
ffreq = 8000, ffreqMod = 1, ffreqModMul = 1,
atk = 0.1, dec = 0.3, sus = 0.5, rel = 0.3|
var n = 7;
var freqs = freq * Array.fill(n, { detun ** Rand(-1, 1) });
var sig = Splay.ar(Saw.ar(freqs));
var eg = EnvGen.ar(Env.adsr(atk, dec, sus, rel), gate, doneAction: 2);
ffreq = (ffreq * ((ffreqMod * ffreqModMul) + 1)).clip(20, 20000);
sig = LPF.ar(LPF.ar(sig, ffreq), ffreq);
Out.ar(out, sig * (amp * eg));
)

TempoClock.tempo = 104/60;

Pdef(\kick).quant = -1;
Pdef(\kick, Pbind(\instrument, \kick, \dur, 1, \amp, 0.2, \freq, 50, \aFreq, 200, \aTime, 0.05, \dec, 0.15)).play;

b = Bus.control(s, 1);
b.set(1);

a = Synth(\pad, [amp: 0.2, ffreq: 1000, ffreqMod: b.asMap, ffreqModMul: 3]);

// not recommended: use one 'shape' synth running forever
// after some minutes this will probably have drifted
TempoClock.schedAbs(TempoClock.nextTimeOnGrid(-1), {
s.makeBundle(0.2, {
c = Synth(\shape, [out: b, beatDur: TempoClock.beatDur]);
});
});

c.free;  // before the next bit

// I prefer: one synth per shape trigger
// this is always 100% in tempo because it's running on the same clock
Pdef(\shape).quant = -1;
Pdef(\shape, Pbind(\instrument, \shape, \out, b, \beatDur, Pfunc { TempoClock.beatDur }, \dur, 1, \legato, 1)).play;


Also, the “one synth” approach is likely to break if you change tempo, but the Pdef(\shape) approach will be more robust.

hjh

Here’s a technique I’ve used in the past for this, maybe it point in a helpful direction (sorry, for some reason my server isn’t launching rn, so this is untested and a bit rough )

SynthDef(\clock, {
var setTime, tempo, time, sig, rateMax;

setTime = \setTime.tr(0);
tempo = \tempo.kr(1);
rateMax = 1.05 * tempo;

time = Phasor.ar(
setTime,
SampleDur.ir * tempo,
0,
inf,
setTime
).poll;

time = Slew.ar(
time,
up:rateMax, dn:rateMax
);

sig = SinOsc.ar(0, time.mod(2pi));
//sig = IEnvGen.ar(Env.adsr, time * 3)

Out.ar(\out.kr, sig);

Pdef(\lfoSync, Pmono(
\clock,
\out, ~someBus,
\dur, 1,
\tempo, 1,
\setTime, Ptime() * Pfunc({ ~tempo })
));


The idea here:

1. Use a Phasor to drive your LFO or other periodic thing - example has either a SinOsc or an envelope.
2. Provide a trigger input, \setTime, which resets the phasor to a new time. This re-syncs your synth’s time to your pattern time if they drift.
3. Use Slew to rate-limit the phasor to a LITTLE BIT faster than your desired speed.
4. Drive the whole thing with a Pmono - so, every \dur beats you’re sending a setTime which tells the Synth what time it SHOULD be at.

The synth should be roughly in-sync with your pattern / sclang tempo - but of course it will not stay perfectly in sync forever (the language + server, as James has pointed out, are two slightly different clocks). \setTime will periodically re-sync the clock to the correct time - but probably you don’t want this re-sync to jump discontinuously to the new value, because it would cause a click etc. So, the Slew limits the rate-of-change of time to a little faster than the speed you expect, so it will catch up or slow down slightly when it’s out of sync.

Of course, if rateMax is low and your clocks are drifting a lot, it’s possible time will never catch up. And, if rateMax is high, then it might speed up quickly enough that it’s discontinuous in an audible way - this simply has to be tuned by ear.

The \dur of your pattern here is simply controlling how often you resync your clock - this can also simply be tuned to the context. It might be that you only need to re-sync once every 10 seconds and everything works fine - or maybe need very tight sync and 0.1 sounds better - again, probably tune by ear.

As an overall architectural concept, I’ll have a \clock synth output the time straight to a bus, and then have individual other synths read that time and drive their own LFO’s / envelopes / etc with it. Once you’ve got something that works, wrap it up as a Pdef and drop it in any patch or setup where you need a clock to drive other synths.

One final caveat: this example as it is MAY fun afoul of server latency - the language sends messages to the server expecting that they will be executed slightly in the future, so that timing is consistent. If this works but you’re hearing that “everything is slightly out of sync”, this is the likely culprit: just add a fixed offset to your time at the end, e.g. time = time + \latency.kr(0) - probably this will just need to be the server latency, but again something that should really just be done by ear in the end.

2 Likes

This is, incidentally, a cool way to implement a sort of “drifting in and out of sync” sound. For example, if you have a bank of LFO’s that you want to be perfectly synced but occasionally drift out: you can drive them all with one clock, and then offset the time to de-sync them. As an example:

desync = 0.4 * Trig.ar(Dust.ar(0.1), 10).lag(4);
lfo = SinOsc.ar(0, (time + desync).mod(2pi))


When the Dust fires, you get a slow ramp up to 0.4 and then back to 0, over 10 seconds - adding this to your time means de-syncing by 1/4 beat and then re-syncing again.

1 Like

damn i ll have to give another read to this, thanks a lot

hey hey, I tried building another synth for shaping and poof noise artefact since SC doesnt like low frequency on filters is there a way to go around this ?

I studied the counter drifting from scztt its pretty advanced and I m still hoping the extra synth can solve

thanks ^^


(s.waitForBoot({

t = TempoClock.new(151.703592/60).permanent_(true);
~bassShapB = Bus.audio(s,2);

s.sync;

SynthDef(\bass,{
arg freq=41.2, amp=0.5,
atkcrv=1, relcrv=5,
atk=0, rel=0.1, lpf=100, out = 0;

var sig, env;

env = EnvGen.ar(
Env(
[0,1,0],
[atk,rel],
[atkcrv,relcrv]
),
doneAction:2
);

sig = Saw.ar(freq,-1,-0.4);
sig = sig.lincurve(-1,1,-1,1,-7);
sig = BLowPass.ar(sig, lpf);

sig = sig * amp.dbamp!2;

sig = sig * env;

Out.ar(out, sig);

SynthDef(\shap,{|in, out=0|
var sig, halfBeat, env;

halfBeat = t.beatDur * 0.5;

env = EnvGen.kr(Env([0,81.41,81.41],[halfBeat,halfBeat,0],9),doneAction:2);

sig = LPF.ar(In.ar(in, 2), env);

Out.ar(out, sig);

s.sync;
~bass = Pbind(
\instrument, \bass,
\type, Pseq([\rest, \note, \note, \note], inf),
\dur, Pseq([1/4],4),\lpf,81.41,
\midinote, 28,
\atk, t.beatDur/4 * 0.01,
\rel, t.beatDur/4 * 0.99,
\atkcrv, -2,
\relcrv, -1,
\amp, (6),\out,0
);

~bassShap = Pbind(\instrument, \shap, \dur,Pseq([1/1],1));

s.sync;

~bassPr = Ppar([ Pbindf(~bass, \out, ~bassShapB), Pbindf(~bassShap, \in, ~bassShapB)],7).play(t);

});)

//bass wihtout shaping

~bass.play(t)



oh i might try to run shap independently forever as Pseq([1/1],inf) and subtract its value to the bass synth filter envelope !!


... paste the code in here



hjh

this does not work either maybe I need to make 2 different groups to be sure the shap is set before bass…

ok mb the synth was faulty i think left overs again from previous session thanks a lot it worked with the extra synth

EnvGen.kr(Env([0,81.41,81.41],[halfBeat,halfBeat,0],9),doneAction:2);


Part of the SC philosophy is to allow users to make mistakes – where Ableton’s Autofilter doesn’t allow the filter frequency to be too low, SC leaves it to the user to clip the range.

If you start the filter envelope at 0, then yes, the filter will blow up. Starting in envelope at zero is appropriate for a volume envelope. But you’re not using the envelope for volume – so it’s not necessarily the case that you should start at 0 out of habit.

Probably the most intuitive way to do filter frequency modulation is like this (though I admit that I didn’t think of this specific formula until just now – but it solves some problems I had in the past with frequency envelopes, I think I will do it this way in the future):

arg ffreq = 1000,
ffreqMod = 3;  // a factor: modulation can range /3 to *3

var filtEg = EnvGen.kr(........);  // range 0 to 1 or -1 to +1

ffreq = (ffreq * (ffreqMod ** filtEg)).clip(20, 20000);


With this, an envelope value of zero would output the original ffreq. Env = +1 would output ffreq * ffreqMod.

i think left overs again from previous session thanks a lot it worked with the extra synth

Maybe just recompile the class library between sessions. Then it guarantees that all the old stuff is gone.

Managing these resources is one of the tricky things in SC, admittedly.

hjh

you are 100% correct that ableton filter doesnt go to zero i forgot about that, SC is the best but some part of it are rough haha takes practise