Synchronize the loud phase of a sound with a slow attack on the beat


#1

Hello,

In a pattern, for example a pseq, how do you manage the samples with a slow attack, so that it is the beginning of the decay and not the beginning of the sound, which is on the beat. The beginning of the sound being thus a little before the beat…

Is there a simple way to deal with this kind of problem?


#2

In the Event help file there is mention of both “lag” and “timingOffset” parameters for durEvent’s. I’m not sure if these can take negative values though (which you might need to use in order to start your samples ahead of the beat so that the decay phase lands on the beat)…


#3

Yes, negative values are possible. It works so!
Thank you :slight_smile:


#4

Care to share an example?

I just gave it a try, and can’t get it working. I’d like to change the attack time for each event, and still have the envelope peak land on the beat. Edit: It is working now, I corrected the code below:

(
SynthDef(\slowAttack, {
	var att, dec, freq, env, src;
	att = \att.ir(1);
	dec = \dec.ir(0);
	freq = \freq.kr(440);
	env = EnvGen.ar(Env.perc(att, dec), doneAction:2);
	src = SinOsc.ar(freq) * env;
	Out.ar(0, src!2);
}).add;

SynthDef(\hat, {
	var thud, env;
	env = EnvGen.ar(Env.perc(1e-6, 1e-1), doneAction:2);
	Out.ar(0, WhiteNoise.ar!2 * env);

}).add;

)

// vary the attack time:
(
~dur = 4;
Ppar(
[
	Pbind(\instrument, \slowAttack,
		\dur, ~dur,
		\att, Pwhite(0.1,~dur),
		\degree, Prand((0..16), inf),
		\lag, ~dur-Pkey(\att)
	),
	Pbind(\instrument, \hat,
		\dur, ~dur
	)
], inf).play;
)

#5

Thank you for the example. I modified your slowAttack Pbind so that the sound with slow attack starts from the first beat rather than the second. But it seems that in this case, the negative numbers do not work well:

Pbind(\instrument, \slowAttack,
		\dur, ~dur,
		\att, Pwhite(0.1,~dur).trace,
		\degree, Prand((0..16), inf),
		\lag, Pkey(\att).neg.trace
	)

On the other hand, it works in negative values in this example with samples:

// 2 soundfiles. One with a slow attack, the other with a quick attack:
(
~slowAtk = Buffer.read(s, "~/son/sons/cuisine/boisson/bouchon/bouchon_H_LtAtk01.wav");
~quickAtk = Buffer.read(s, "~/son/sons/cuisine/boisson/bouchon/bouchon_H_Se01.aiff");
)
// a SynthDef to play the sounds:
(
SynthDef(\bufplay, {
	arg buf=0, amp=1, pos=0, out;
	var sig;
	sig = PlayBuf.ar(numChannels:1, bufnum:buf, rate:BufRateScale.ir(buf), doneAction:2);
	sig = sig*amp;
	Out.ar(out, sig!2);
}).add;
)

// a Pbind that sequences both sounds
(TempoClock.default.tempo = 200/60;
Pbind( \instrument, \bufplay,
	\dur, Pseq([1, 1, 0.5, 0.5, 1], inf),
	\buf, Pseq([~slowAtk, ~quickAtk],inf),
	// \lag: offset in seconds — \timingOffset: offset in beats
	\lag, Pfunc({|r| switch(r.buf,
		~slowAtk, -0.1, //0.1 seconds in negative (-0.1) to compensate for the slow attack of the sound
		~quickAtk, 0
	)}),
).play;
)

The question is: why does it work in one case and not in the other …


#6

It’s quite difficult for anyone to guess what’s going wrong if you don’t describe it and the soundfile is unknown …
The code that you posted looks ok to me and I checked with a synthesised buffer:

// suppose sample rate 44100
    
(
x = (Signal.sineFill(15, [1]) ! 30).flat * Signal.hanningWindow(450) * 0.1;
y = 0 ! 4410 ++ x;        // delay 0.1 with supposed sr 44100
~slowAtk = Buffer.loadCollection(s, y);
~quickAtk = Buffer.loadCollection(s, x);
)


(
TempoClock.default.tempo = 200/60;
Pbind( \instrument, \bufplay,
    \dur, Pseq([1, 1, 0.5, 0.5, 1], inf),
    \buf, Pseq([~slowAtk, ~quickAtk],inf),
    // \lag: offset in seconds — \timingOffset: offset in beats
    \lag, Pfunc({|r| switch(r.buf,
        ~slowAtk, -0.1, //0.1 seconds in negative (-0.1) to compensate for the slow attack of the sound
        ~quickAtk, 0
    )}),
).play;
)

Also the absolute value of negative lagtime has to be smaller than latency, have you reset latency maybe ?


#7

@dkmayer:
There is a misunderstanding (maybe due to my bad english): My code with the buffer works. This is the frankchannel code that I modified by putting a negative value that works badly. I did that for the sound with slow attack starts from the first beat rather than the second. But it does not work: the end of the attack does not synchronize on the beat:

(
SynthDef(\slowAttack, {
	var att, dec, freq, env, src;
	att = \att.ir(1);
	dec = \dec.ir(0);
	freq = \freq.kr(440);
	env = EnvGen.ar(Env.perc(att, dec), doneAction:2);
	src = SinOsc.ar(freq) * env;
	Out.ar(0, src!2);
}).add;

SynthDef(\hat, {
	var thud, env;
	env = EnvGen.ar(Env.perc(1e-6, 1e-1), doneAction:2);
	Out.ar(0, WhiteNoise.ar!2 * env);

}).add;

)

// vary the attack time:

(
~dur = 4;
Ppar(
[
	Pbind(\instrument, \slowAttack,
		\dur, ~dur,
		\att, Pwhite(0.1,~dur),
		\degree, Prand((0..16), inf),
		//I changed this line to the frankchannel code (negative value):
		\lag, Pkey(\att).neg //instead of : \lag, ~dur-Pkey(\att)
	),
	Pbind(\instrument, \hat,
		\dur, ~dur
	)
], inf).play;
)

Thank you for your attention :slight_smile:


#8

If you look at the post window output the late message show what I adressed in the last line of the last post:

A negative lag means that you reduce latency: a negative latency is impossible, this event will always be late!
And even very small latencies (< 0.035 sec or so) might not be possible.
If you are unsure how the latency mechanism works see this help file:

http://doc.sccode.org/Guides/ServerTiming.html

// this version of the example works, note that the minimum latency here cannot be smaller than 0.1 sec
// this will (pretty sure) always be possible

s.latency = 1.1;

(
~dur = 1;
Ppar(
[
    Pbind(\instrument, \slowAttack,
        \dur, ~dur,
        \att, Pwhite(0.1,~dur),
        \degree, Prand((0..16), inf),
        //I changed this line to the frankchannel code (negative value):
        \lag, Pkey(\att).neg //instead of : \lag, ~dur-Pkey(\att)
    ),
    Pbind(\instrument, \hat,
        \dur, ~dur
    )
], inf).play;
)

#9

Yes, I understand. Perhaps it is possible to solve this problem also this way:

(
~dur = 4;
a = Pbind(\instrument, \slowAttack,
		\dur, ~dur,
		\att, Pwhite(0.1,~dur),
		\degree, Prand((0..16), inf),
		\lag, ~dur-Pkey(\att)
);

b = Pbind(\instrument, \hat,
		\dur, ~dur
);
)
(
a.play(quant:[4]);
b.play(quant:[4,4]);
)

… But in this way, there is always a little silence at the beginning, which is not necessarily a problem.
@dkmayer, @frankchannel, thank you for help :slight_smile:


#10

I believe these examples are correct, apart from one detail…

Suppose the pattern system has just played an event at time=10, with a \dur of 1. This means the player will wake up and generate the next event at time=11. If this event has lag = -1 (as in your examples), then the correct start time for that event is time=10… which is in the past! Under normal circumstances, negative \lag or \timingOffset (which works similarly to lag) will not work because by the time you have generated the \lag value (e.g. generated the next event), it’s already too late!

Fortunately, there’s a solution - the \latency key causes the pattern system to generate the next event before it is actually time for it to be played. By default this is a small value like 0.2, just enough to ensure that the event is ready in time for it to be played (e.g. an event at time=11 would be prepared at time=10.8 and then scheduled for playback on the server at time=11). If you want to use negative \lag or \timingOffset values, you simply have to increase the \latency value so that events are generated far enough in advance to account for your negative time value.

The \latency value needs to be the maximum amount of time you’ll ever need to pre-generate events in order to account for \lag… You can’t simply set the right latency every time because the next event has not yet been generated, and thus you don’t yet know the \lag or the latency you would need. In the case of your examples, I think something like this would work:

a = Pbind(\instrument, \slowAttack,
		\dur, ~dur,
		\att, Pwhite(0.1,~dur),
		\degree, Prand((0..16), inf),
		\lag, Pkey(\att).neg,
		\latency, ~dur
);

Note that the latency will delay the time it takes to actually get sounds from your Pbind - and, this can be a problem if you’re syncing across Pbinds (I’m not totally sure whether Ppar solves this, but it’s probably safer to give all Pbinds in a Ppar the same latency). Using longer latencies should have no other observable effects once playback has started, unless your pattern values are pulling from a real-time source like a MIDI controller.


#11

On my machine, it seems that we have to add the latency of the server (s.latency):

a = Pbind(\instrument, \slowAttack,
		\dur, ~dur,
		\att, Pwhite(0.1,~dur),
		\degree, Prand((0..16), inf),
		\lag, Pkey(\att).neg,
		\latency, ~dur+s.latency
);

And here it is good.