First hit always sounds weak when using Ndef with pattern source

When playing a pattern through an Ndef, the first hit always sounds noticeably weaker than subsequent hits. This is most apparent with percussive sounds but also happens with \default:

(
  Ndef(\test).fadeTime_(0).play;
  Ndef(\test, Pdef(\test, Pbind(\freq, 100, \dur, 1, \amp, 1)));
)

The waveform clearly shows the first hit has lower amplitude compared to all subsequent hits:

Running this separately works fine:

Ndef(\test).fadeTime_(0).play;

Then evaluate this one:

Ndef(\test, Pdef(\test, Pbind(\freq, 100, \dur, 1, \amp, 1)));

So I have been trying a programmatic delay (fork, defer, AppClock.sched) to replicate the behavior of two separate manual evaluations but didn’t work.

Do you know a way to ensure the Ndef monitor is fully initialized before the first pattern event fires, all within a single evaluation block?

can-t reproduce the problem on my system. maybe it goes away when using a ugen-function as object for the ndef and node-proxy-role \set?

(
Ndef.clear;

t = TempoClock.new;
t.tempo = 120/60;

Ndef(\kick, { arg t_trig;
	var snd;
	snd = SinOsc.ar(Env.perc(0, 0.1).ar(gate: t_trig).linexp(0, 1, 60, 600));
	snd = snd * Env.perc(0.001, 0.2).ar(gate: t_trig);
	snd = snd ! 2;
	snd * -6.dbamp;
});

Ndef(\kick).clock = t;
Ndef(\kick).play;

Ndef(\kick)[1] = \set -> Pbind(
		\dur, Pseq((1 ! 7) ++ [0.75, 0.25], inf),
		\t_trig, 1
);
)

@mstep Thanks for the suggestion! The \set role approach works because the synth is already running, but looking at the waveform closely, even that shows a slight difference to me on the first hit.

In my case I need pattern-spawned synths (for sample playback, polyphony, different instruments per event), so the \set role isn’t applicable. The issue seems fundamental to how Ndef initializes its monitoring chain when a pattern source is assigned, the only workaround I’ve found is evaluating .play and the source assignment as two separate evaluations.

Maybe there is a way to force the Ndef monitor to be fully initialized before the first pattern event fires, all within a single code block?

did you try setting the fadeTime for play?

Ndef(\kick).play(fadeTime: 0);

Thanks, @LFSaw ! Using play(fadeTime: 0) seems to fix the issue for a single Ndef:

(
  var pdef = Pdef(\test, Pbind(\freq, 100, \dur, 1, \amp, 1)).quant_(4);
  Ndef(\test, pdef).quant_(4).play(fadeTime: 0);
)

However, my setup uses two Ndef layers, an inner Ndef per pattern feeding into an outer mixer Ndef via Mix, and there the issue persists:

(
  var pdef = Pdef(\test, Pbind(\instrument, \playbuf, \buf, Px.buf("ki", 0), \dur, Pseq([1, 0.5, 1, 0.5, 1], inf), \amp, 1)).quant_(4);
  var ndef = Ndef(\test, pdef).quant_(4);
  Ndef(\px, { Mix.new([ndef]) }).quant_(4).play(fadeTime: 0);
)

The outer Ndef serves both as a mixer and as a master effects bus, so I can’t easily remove it. I will to make fadeTime: 0 work across multiple Ndef layers, or refactor it to use a shared bus instead.

not tested but you can also set the fadeTime of an Ndef:

Ndef(\test, {SinOsc.ar}).fadeTime_(0)

This affects the crossfade between reinstantiations of Ndef source definitions.

Also, you might want to create your master fx Ndef beforehand…

(
// create/prepare chain beforehand
Ndef(\mixermaster, { Mix.new([Ndef(\test).ar(2)]) }).quant_(4).play(fadeTime: 0);
Ndef(\test, Pdef(\test)).quant_(4).fadeTime_(0);
)
// run this to actually make some sound 
Pdef(\test, Pbind(\instrument, \default, \dur, Pseq([1, 0.5, 1, 0.5, 1], inf), \amp, 1)).quant_(4);

@LFSaw That sounds good too :slightly_smiling_face:

Finally I opted for this solution:

~mixBus = Bus.audio(s, 2);
Ndef(\master, { InFeedback.ar(~mixBus.index, 2) }).play(fadeTime: 0);
Ndef(\kick, Pbind(\instrument, \default, \dur, 1, \freq, 60)).play(out: ~mixBus.index, fadeTime: 0);

InFeedback.ar on the bus is already listening before any pattern writes to it, so the first sample hits at full amplitude.

Thank you!