Pulsating Osc on off without clicks

Hello,
I am trying to create tone pulse on-off but nice smooth and to controll the speed

Is this an optimum solutiion?

pulseEnv = EnvGen.ar( Env.perc(pulseAtk, pulseRel, curve: \lin),Impulse.ar(pulseRate) );

You could also use LFPulse, maybe with .lag to smooth it:

(
{
	var pulseRate = 10;
	var pulseEnv = LFPulse.ar(pulseRate, width: 0.2 );
	var pulseLagged = pulseEnv.lag(0.01);
	var pulseLagged2 = pulseEnv.lag(0.03);
	[pulseEnv, pulseLagged, pulseLagged2];
}.plot(0.3);
)

Best,
Paul

Thanx Paul,
I will your take asap…
Basically I can give now my whole synth as i am about to finish it.

(
SynthDef(\Tone_send, {
    arg
        freq = 440,
        pulseRate = 10,
        amp = 0.5,
        gate = 1,
        attack = 2,
        release = 3,
        pulseAtk = 0.01,
        pulseRel = 0.01,
        out = 0,
        send = -1,       // The level of the signal sent to reverb (-inf = no send)
        sendBus = 0;     // The bus to send to

    var carrier, pulseEnv, env, sig;

    carrier = SinOsc.ar(freq);
    pulseEnv = EnvGen.ar(Env.perc(pulseAtk, pulseRel, curve: \lin), Impulse.ar(pulseRate));
    env = EnvGen.kr(Env.asr(attack, 1, release), gate, doneAction: 2);

    sig = (carrier * pulseEnv) * env * amp;

    // --- Main Output ---
    Out.ar(out, sig ! 2);

    // --- Send to Reverb Bus ---
    Out.ar(sendBus, sig * send.dbamp); // Use .dbamp for decibel-level control
}).add;
)

I tried the lag solution but it is not as smooth as with Impulse, at least i could make it as smooth.

You could also try Lag2.ar and Lag3.ar for extra smoothness.

For more smoothness it can help to implement a phase reset for your carrier oscillator and apply a smoother window function then the rectangular pulse to smooth out the discontinuity on phase reset. Thats called “windowed sync”.

1 Like

If the zero crossings always coincide (varying a few samples between -1 and 1), will the “click” persist or disappear, even with almost the same (sharp) waveform? There are more obscure things happening, I believe )

This is one of those threads that got a bit overcomplicated.

“Pulse on-off” suggests that the envelope rises to a level, holds there for some period of time, and then falls back to 0. “Smooth” means that the up- and down-ramps should not be instantaneous. This is a trapezoidal envelope, which in SC is implemented by Env.linen (or by the Linen UGen).

Env.perc with curve = \lin is a triangular envelope, not quite the same thing as “pulse on/off.”

So I think you need a synth arg for pulseHold, then:

pulseEnv = EnvGen.ar(Env.linen(pulseAtk, pulseHold, pulseRel, curve: \lin), Impulse.ar(pulseRate));

Lag implements a one pole filter. When it receives stairstep signals (such as an LFPulse), the filter’s output curve moves quickly toward the new value and then decelerates toward the target. This may be why it didn’t sound smooth enough.

I never believed the “edit at zero crossings” folklore so I don’t see that this case requires phase reset, windowed sync or such. Using a correct trapezoidal envelope should be enough.

hjh

1 Like

That’s it, Maestro.

Env.linen creates a trapezoidal envelope, which is what our ears “expect”.

But phase reset and windowed sync (as dietcv mentioned) can help with more complex scenarios. I don’t think it’s an urban legend hehe ))

Sure, and I’m not saying that windowed sync is wrong. It’s rather that the question reads to me like “how do I get a pulse style, but smoothed, envelope onto a waveform (and I didn’t like the sound of Lag)?” – which should be straightforward (and it is!) so I thought it’s worth giving the Occam’s razor answer.

I don’t think it’s an urban legend hehe

Stopping a wave cold, even at a zero crossing, is effectively to add an equivalent negative wave, instantaneously (as in, at a loud finish of an organ work in a cathedral, you can hear the moment of release reverberating away). It might not click but it isn’t exactly smooth. (Also, starting or stopping a sine at 0 introduces the maximum change in slope, instantaneously, which may or may not be the sound that’s desired in this or that scenario. A 2ms envelope spreads that out over 80 samples or so, and it’s usually the sound I prefer.)

hjh

Yeah, I understood it, you used a pinch of salt hehe.

Or, well, something like this?

smoothing_filter::sample_rate ( nframes_t n )
{
    const float FS = n;
    // a one-pole filter with a cutoff frequency
    w = 2 * PI * _cutoff / (FS + 2 * PI * _cutoff);
    // Or for time-constant
    // w = 1.0f / (1.0f + FS / (2 * PI * _cutoff));
}

Instead of: value = new_value; the filter does: value += w * (new_value - value); which is gradual

I believe it creates a mini-envelope on every parameter modification, controlling the artifacts

I believe that already exists as Lag / OnePole (though not expressed in terms of frequency).

(
{
	var pulse = LFPulse.ar(10, 0, 0.3);
	var pulseEnv = EnvGen.ar(Env.linen(0.01, 0.02, 0.01), pulse);
	var pulseLag = Lag.ar(pulse, 0.01);
	var pulseOnePole = OnePole.ar(pulse, 0.001 ** (1 / (0.01 * s.sampleRate)));
	SinOsc.ar(440) * 0.1 * [pulseEnv, pulseLag, pulseOnePole];
}.plot(duration: 0.2);
)

#2 and #3 match, as expected. #1, however – EnvGen never fully reaches 0 :confused: so there may be a bug in there.

2 ms fade time may not be enough, though, contrary to what I said before. 5-10 ms is probably better.

hjh

I have to admit that im not 100% sure what the desired outcome was @Agathodemonas had in mind. I have just made a suggestion and im not sure if my suggestion was of the type “over-engineering”. The audio rate trigger of the initial example made me think what range of trigger rate they might want to use. Im always dwelling on this haha, but here it is again: As soon as you are using server side triggers for scheduling you run into problems when your trigger frequency is faster then your envelope duration and there is not a really a good way of handling this on the server especially with trigger based scheduling and envelopes. This might just be a personal preference but in my opinion especially audio rate triggers scheduled on the server doesnt work very well with envelopes.

I hear what you are saying but since I am not exprerienced with SC I cannot evaluate your answer.
How would you go in that case? Is it possible to do it on the client side really?

Maybe best if I take your SynthDef and change it to a trapezoidal envelope:

(
SynthDef(\Tone_send, {
    arg
        freq = 440,
        pulseRate = 10,
        amp = 0.5,
        gate = 1,
        attack = 2,
        release = 3,
        pulseAtk = 0.01,
        pulseHold = 0.05,
        pulseRel = 0.01,
        out = 0,
        send = -1,       // The level of the signal sent to reverb (-inf = no send)
        sendBus = 0;     // The bus to send to

    var carrier, pulseEnv, env, sig;

    carrier = SinOsc.ar(freq);
    pulseEnv = EnvGen.ar(Env.linen(pulseAtk, pulseHold, pulseRel, curve: \lin), Impulse.ar(pulseRate));
    env = EnvGen.kr(Env.asr(attack, 1, release), gate, doneAction: 2);

    sig = (carrier * pulseEnv) * env * amp;

    // --- Main Output ---
    Out.ar(out, sig ! 2);

    // --- Send to Reverb Bus ---
    Out.ar(sendBus, sig * send.dbamp); // Use .dbamp for decibel-level control
}).add;
)

It might be a bit smoother to use curve: \sin instead of \lin.

The impulse has a period of 100 ms; I chose the pulseHold default value so that 0.01 + 0.05 + 0.01 < 0.1 sec. If you want a pulse on/off sound, it’s important to make sure there’s space between the envelope windows.

If you are a relative beginner in SC, try this first – there are several suggestions in this thread that are valid but perhaps not beginner-friendly. I’d suggest to dig into the different Env options first.

I think the relevant point there is to be sure that the envelope’s open duration is less than the trigger period. EnvGen.ar is not inherently problematic for most use cases, though it’s not ideal for some more advanced cases that you don’t need to concern yourself with yet.

hjh

Thanks,
I have to mention that in SC, especially, there are tons of ways to use envelopes.
It does take a bit of time to grasp the sequence of connections and events.