Synth def with dynamic buffer amplitude

I want to write a synth def which dynamically changes the amplitude of a “playing” buffer
with a server command like ​ s.sendMsg("/n_set", 101, “ampdyn”, ampNow, 4.5, -0.2, 1);
meaning: decrease the amplitude of buffer 101 with 0.2 over the next 4.5 seconds - linearly (1).
Any suggestion?

I would think about it this way:

  1. A “playing buffer” is really some buffer player playing that buffer. Let’s say that player is a Synth itself.
  2. Our player synth can read amplitude from a bus.
  3. Any other synth can write values on that bus.
SynthDef(\player2) { |out=0, buf=0, amp=1|
    Out.ar(0, PlayBuf.ar(2, buf, loop:1) * amp)
}.add;

SynthDef(\lineCtrl){ |out=0, start=0, end=1, dur=1|
    Out.kr(out, Line.kr(start, end, dur, doneAction:2))
}.add;

// read buffer
b = Buffer.read(s, /* your buffer path here */ );

// reserve a one-channel control bus, and set its initial value to 1
a = Bus.control(s, 1).set(1)

// start player, map \amp to bus a
p = Synth(\player2, [buf: b, amp: a.asMap]);

// now change amplitude, but you first want to get the current bus value (v)
a.get{|v| Synth(\lineCtrl, [out: a, start:v.postln, end: v/10, dur: 2])}

The pattern I often use for this is something like:

SynthDef(\player, {
	var sig, buffer, amplitude;
	
	buffer = \buffer.kr;
	amplitude = \amplitude.kr(1);
	amplitude = VarLag.kr(amplitude, \amplitude_lag.kr);  // <---- a lag, taking the time value as an argument also
	
	sig = PlayBuf.ar(
		numChannels: 1, 
		bufnum: buffer,
		rate: BufRateScale.ir(buffer),
		loop: 1
	);
	sig = amplitude.poll * sig;
	
	FreeSelfWhenDone.kr(sig);
	
	Out.ar(\out.kr(0), sig);
}).add;

~buffer = Buffer.read(s, Platform.resourceDir +/+ "sounds/a11wlk01.wav");

~synth = Synth(\player, args:[\buffer, ~buffer]);

and then to set my amplitude over time:

~synth.set(\amplitude, 0.1, \amplitude_lag, 4); // to 0.1, over 4 seconds
~synth.set(\amplitude, 0.8, \amplitude_lag, 0.5); // to 0.8, half a second

The only trick is really to set both the parameter value and the lag value at the same time - VarLag and the other Lag UGens will all handle this correctly, so you can use these to get different curves.


An additional trick you might consider: interpolating amplitude values will sound much more natural if you do it in decibels rather than raw amplitude values. Consider setting and interpolating in dB, and then converting at the end. The Lag UGens will unfortunately choke on a -inf (silence) - the trick is to use the \db spec to map to and from a 0…1 value. An example:

amplitudeDb = \amplitudeDb.kr(-3); // default of -3db
amplitudeDb = \db.asSpec.unmap(amplitudeDb); // now a (0..1) value, still dB scaled though
amplitudeDb = VarLag.kr(amplitudeDb, \amplitudeDb_lag.kr); // lag my (0..1) value
amplitudeDb = \db.asSpec.map(amplitudeDb); // back to proper dB
amplitudeDb = amplitudeDb.dbamp; // dB to raw amplitude value
sig = sig * amplitudeDb; // finally, multiply by signal
1 Like

Another note - this also works really well with patterns: you can set the lag value directly from the Event \dur, which means you’ll be fading the exact amount of time until you set the next value:

Pdef(\fadePattern, Pbind(
    \type, \set,
    \args, [\amplitude, \amplitude_lag],
    \id, ~synth,
    \amplitude, Pseq([0, 0.2, 1, 0.5, 0.2], inf),   // a sequence of amplitudes
    \dur, Pseq([ 2, 4, 6, 4 ], inf),   // a sequence of durations
    \amplitude_lag, Pkey(\dur),  // just set the lag from the dur, so the fade lasts until you get your next value
)).play;
1 Like

elgiano,
thank you very much, I got it working!

scztt,
I just saw your notes and will look into your solution. Also to you - thank you!

This was my first question to this forum and I really appreciate your quick answer!

Eivind