Waveshaped FM kick sounds great when run as Synth, but sounds dull in a Pdef

Dear Supercollider community,

I recently picked up Supercollider and I’m working on the following kick synth. It sounds the way I want it to sound when played using Synth.new, but sounds dull whenever I loop it in a Pdef. I have no clue why it sounds different. I mean, for as far as I know I’m not overriding default arguments.

Can anybody explain why it does this? Thank you in advance.

(
var transientFunction, bodyFunction;

transientFunction = Env(
  levels: [-0.8, 0.0, 0.8],
  times: [1, 1],
  curve: [8, -8]
).asSignal(1025).asWavetableNoWrap;

~transientBuffer = Buffer.loadCollection(s, transientFunction);

bodyFunction = Env(
  levels: [-0.8, 0.0, 0.8],
  times: [1, 1],
  curve: [1.2, -1.2]
).asSignal(1025).asWavetableNoWrap;

~bodyBuffer = Buffer.loadCollection(s, bodyFunction);

SynthDef.new(\fmkick, {
  |
  bodyBuffer,                      // Body buffer
  transientBuffer,                 // Transient buffer
  f1=500.0, f2=50.0, f3=10.0,      // Body sweep frequencies
  fd1=0.01, fd2=0.2,               // Body sweep frequency durations
  fc1=1, fc2=(-1),                 // Body sweep frequency curves
  tf1=1600.0, tf2=50.0, tf3=10.0,  // Transient sweep frequencies
  tfd1=0.01, tfd2=0.1,             // Transient sweep frequency durations
  tfc1=1, tfc2=(-24),              // Transient sweep frequency curves
  atk=0.01,                        // Amplitude envelope attack
  rel=2,                           // Amplitude envelope release
  c1=1, c2=(-12),                  // Amplitude envelope curves
  amp=0.8,                         // Output amplitude
  pan=0,                           // Output panning
  out=0                            // Output bus
  |
  var signal, envelope, sweep, transientSweep, body, transient;

  sweep = Env(
    levels: [f1, f2, f3],
    times: [fd1, fd2],
    curve: [fc1, fc2]
  ).ar;

  transientSweep = Env(
    levels: [tf1, tf2, tf3],
    times: [tfd1, tfd2],
    curve: [tfc1, tfc2]
  ).ar;

  envelope = Env(
    levels: [0, 1, 0],
    times: [atk, rel],
    curve: [c1, c2]
  ).kr(doneAction: 2);

  body = SinOsc.ar(sweep, 0.5pi, 0.8);
  body = Shaper.ar(~bodyBuffer, body);

  transient = SinOsc.ar(transientSweep, 0.5pi, 0.8);
  transient = Shaper.ar(~transientBuffer, transient * envelope);

  signal = body + transient;
  signal = signal * envelope;
  signal = Pan2.ar(signal, pan, amp);
  Out.ar(out, signal);
}).add;
)

Synth.new(\fmkick, [\transientBuffer, ~transientBuffer, \bodyBuffer, ~bodyBuffer]);

(
Pdef(
  \fmkickpat,
  Pbind(
    \instrument, \fmkick,
    \transientBuffer, ~transientBuffer,
    \bodyBuffer, ~bodyBuffer,
    \dur, Pseq([1/4], inf),
    \stretch, 60 / 130 * 4,
  );
).play;
)

Events / patterns have a few default arguments that are defined independently of the SynthDef you’re playing. At a glance, my guess is that the \amp in the Pdef case is different then in the pure Synth case - easy to check this, of course. Enabling OSC dumping (Server.default.dumpOSC) should let you see exactly what’s being sent in the Pattern case.

1 Like

You’re right, I changed arg amp, to arg amplitude and now it picks up the default value. Thanks!

fwiw here’s a one-liner to fill in default values from your SynthDef in a Pdef:

Pdef(\foo).envir = \default.asSynthDesc.controlDict.collect(_.defaultValue).asEvent;

I use this often when I have a lot of params and want to use the SynthDef default values without manually setting them.

7 Likes

This is brilliant! And some more characters!