title:: UGen Changelog
summary:: A summary of UGen changes across SuperCollider versions
categories:: News, Server>Nodes

This file documents changes affecting UGens in each SuperCollider version,
starting from version 3.14. 

section:: Version 3.14

Summary of changes:
list::
## Delay1 and Delay2 accept new additional arguments: predelay sample values can be set explicitly
## Added LevelComp pseudo-UGen, providing level compensation logic for
multichannel mixing and panning
## GVerb teletype::roomsize:: modulation was fixed;
## Sweep teletype::rate:: modulation now affects next sample instead of current;
## ToggleFF doesn't toggle randomly anymore when running at audio rate with a
control-rate input;
## EnvGen rounding error when calculating segment duration was fixed;
## PanAz fixed server crash and numeric blow-up with negative teletype::width::

## Most of the other UGens listed here had their initialization sample
calculation fixed, affecting their first samples of output;
::

subsection:: AmpComp
Fixed initialization sample. No changes in output.
Previously, calling AmpComp_next when arg rates were anything but all scalar rate caused state vars to be uninitialized and init sample would always be zero.
Now init sample is always equal to first output sample.

subsection:: Decay
Fixed initialization sample. First sample of input was incorrectly added twice, fixing it causes a change in the first samples of output:
code::
{ Decay.ar(Impulse.ar(0)) }.loadToFloatArray(1e-2, action:_.postcs)
// 3.13: [1.9998, 1.9995, 1.9992]; 3.14: [1.0, 0.9998, 0.9996]
// note that in 3.13 values are higher of exactly 1.0, other than being one sample too early

// if input is delayed of at least one sample, no change
{ Decay.ar(DelayN.ar(Impulse.ar(0), delaytime:1/SampleRate.ir)) }.loadToFloatArray(1e-4, action:_.postln)
// 3.13: [0.0, 1.0, 0.9998, 0.9996]; 3.14: [0.0, 1.0, 0.9998, 0.9996]
::

subsection:: Decay2
Fixed initialization sample. First sample of input was incorrectly added twice, fixing it causes a change in the first samples of output:
code::
{ Decay2.ar(Impulse.ar(0), attackTime: 0) }.loadToFloatArray(1e-4, action:_.postln)
// 3.13: [0.9998, 1.9995, 1.9992]; 3.14: [0.0, 0.9998, 0.9996]
// note that in 3.13 values are higher of exactly 1.0, other than being one sample too early

// if input is delayed of at least one sample, no change
{ Decay2.ar(DelayN.ar(Impulse.ar(0), delaytime:1/SampleRate.ir), attackTime: 0) }.loadToFloatArray(1e-4, action:_.postln)
// 3.13: [0.0, 0.0, 0.9998, 0.9996]; 3.14: [0.0, 0.0, 0.9998, 0.9996]
::

subsection:: Delay1
list::
## Added new argument teletype::x1:: for pre-delay (the value of first output sample). New behavior:
when running at audio rate, x1 defaults to 0.0, i.e. "silence" precedes the delayed input. At control rate, the default value is set to the first sample of in, i.e. the first input sample is held during the delay.
## Fixed initialization samples, changes first sample of output: a trigger on first sample wasn't delayed correctly, now it is.
## Initialization sample used to be the input's value, now is fixed to reflect first sample of output (0)
::
code::
// output changes:
{ Delay1.ar(Impulse.ar(0)) }.loadToFloatArray(1e-4, action:_.postln)
// 3.13: [1.0, 1.0, 0.0,]; 3.14: [0.0, 1.0, 0.0]

// init sample changes:
{ IEnvGen.ar(Env([Delay1.ar(Impulse.ar(0))]), 0) }.loadToFloatArray(1e-4, action:_.postln)
// 3.13: initSample is 1. 3.14: initSample is 0.
::
Note: first sample can now be set by user
code::
{ Delay1.ar(Impulse.ar(0), x1:10) }.loadToFloatArray(1e-4, action:_.postln)
// 3.14: [10.0, 1.0, 0.0]
{ IEnvGen.ar(Env([Delay1.ar(Impulse.ar(0), x1: 10)]), 0) }.loadToFloatArray(1e-4, action:_.postln)
// init sample: 10
::

subsection:: Delay2
list::
## Added new arguments teletype::x1:: and teletype::x2:: for explicitly setting
predelay values. teletype::x2:: is second sample predelay (first
output sample value); teletype::x1:: is first sample predelay (second output
sample value). When running at audio rate, x1 and x2 default to 0.0, i.e. "silence" precedes the delayed input. At control rate, the default values are set to the first sample of in, i.e. the first input sample is held during the delay.
## Initialization samples. Changes first sample of output: a trigger on first sample wasn't delayed correctly, now it is.
::
code::
{ Delay2.ar(Impulse.ar(0)) }.loadToFloatArray(1e-4, action:_.postln)
// 3.13: [0.0, 1.0, 1.0, 0.0]; 3.14: [0.0, 0.0, 1.0, 0.0]
::
Note: first samples can now be set by user
code::
{ Delay2.ar(Impulse.ar(0), x1:10, x2: 20) }.loadToFloatArray(1e-4, action:_.postln)
// 3.14: [20.0, 10.0, 10.0, 0.0]
{ IEnvGen.ar(Env([Delay2.ar(Impulse.ar(0), x1: 10, x2: 20)]), 0) }.loadToFloatArray(1e-4, action:_.postln)
// init sample: 20
::

subsection:: EnvGen
list::
## EnvGen.kr: fixed accumulation of rounding errors causing segment of shorter durations than specified.
## Fixed init sample calculation. Fixes initial phase output: it used to miss the first sample.
::
code::
{ EnvGen.kr(Env([0, 1], [ControlDur.ir*7])) }.loadToFloatArray(0.012, action:_.postln)
// 3.13: [0.16, 0.33, 0.5, 0.66, 0.83, 1.0]
// 3.14: [0.0, 0.14, 0.29, 0.42, 0.57, 0.71, 0.85, 1.0]
// note that in 3.13 the segment didn't start from 0, and lasted 5 samples instead of 7
::

subsection:: GVerb
list::
## GVerb seems more glitchy now than before! Are there any other reproducers?
## maxroomsize has now a minimum of 1.
::
Example by @nhthn (https://github.com/supercollider/supercollider/issues/2302)
code::
(
// CAUTION: THE FOLLOWING CAN BE EXTREMELY LOUD
// set the following variable to true if you want to run this code
var run = false;
if (run) {
	{ GVerb.ar(Dust.ar(5), SinOsc.kr(0.3).range(10, 14), drylevel:0) }.play
}
// 3.13 super loud glitch (crackling)! 3.14: super loud glitch (cleaner pitch shifting)!
)
::

subsection:: InRect
sclang now only accepts audio-rate xy inputs for teletype::InRect.ar::
code::
// accepted in 3.13, error in 3.14: x and y needs to be audio-rate
{ InRect.ar(0, 0, Rect(0,1,0,1)) }
// accepted in both
{ InRect.ar(DC.ar(0), DC.ar(0), Rect(0,1,0,1)) }
::

subsection:: LFUGens
Fixed init sample calculation. Fixes initial phase output: it used to miss the first sample.
code::
{ LFPulse.ar(SampleRate.ir/4) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [1, 0, 0, 1, 1, 0, 0, 1]
// 3.14: [1, 1, 0, 0, 1, 1, 0, 0]
{ LFSaw.ar(SampleRate.ir/4) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [0.5, -1.0, -0.5, 0.0, -1.0, -0.5, 0.0]
// 3.14: [0.0, 0.5, -1.0, -0.5, 0.0, -1.0, -0.5]
{ LFPar.ar(SampleRate.ir/5) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [0.35, -0.84, -0.83, 0.36, 1.0, 0.35, -0.84]
// 3.14: [1.0, 0.35, -0.84, -0.83, 0.36, 1.0, 0.35,]
{ LFCub.ar(SampleRate.ir/5) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [0.94, 0.56, -0.56, -0.94, 0.0, 0.94, 0.56, -0.56]
// 3.14: [0.0, 0.94, 0.56, -0.56, -0.94, 0.0, 0.94, 0.56]
{ LFTri.ar(SampleRate.ir/5) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [0.80, 0.39, -0.40, -0.79, 5.96e-08, 0.80, 0.39, -0.40]
// 3.14: [0.0, 0.80, 0.39, -0.40, -0.79, 5.96e-08, 0.80, 0.39]
{ Line.ar(0, 1, 10/SampleRate.ir) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8]
// 3.14: [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7]
{ XLine.ar(1, 2, 7/SampleRate.ir) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [1.10, 1.21, 1.34, 1.48, 1.64, 1.81, 2.0, 2.0]
// 3.14: [1.0, 1.10, 1.21, 1.34, 1.48, 1.64, 1.81, 2.0]
{ Vibrato.kr(1, ControlRate.ir/4, 1,0,0,0,0) }.loadToFloatArray(0.01, action:_.postln)
// 3.13: [2, 1, 0, 1, 2, 1]
// 3.14: [1, 2, 1, 0, 1, 2]
::

subsection:: PanAz
Fixed server crash and numeric blow-up with negative teletype::width:: (now
negative teletype::width:: behaves exactly like positive).

code::
{ PanAz.ar(2, DC.ar(1), width: -0.01) }.loadToFloatArray(1e-4, action: _.postln)
// 3.13: server crash; 3.14: [0, 0]
{ PanAz.ar(2, DC.ar(1), width: -0.1) }.loadToFloatArray(1e-4, action: _.postln)
// 3.13: [-0.7, 7e28]; 3.14: [0, 0]
{ PanAz.ar(2, DC.ar(1), width: -1) }.loadToFloatArray(1e-4, action: _.postln)
// 3.13: [0.0, -524284.71875]; 3.14: [0, 0]
{ PanAz.ar(2, DC.ar(1), width: -1.1) }.loadToFloatArray(1e-3, action: _.postln)
// 3.13: [56354660.0, -35998664.0]; 3.14: [0.1429, 0.1429]
::

subsection:: Peak, PeakFollower
Fixed init sample when input is negative

subsection:: Phasor
Fixed ignoring trigger on first sample, now Phasor starts from resetPos if triggered on first input sample.
code::
{ Phasor.ar(1, 1, start: 0, end: 10, resetPos: 5) }.loadToFloatArray(1e-4, action: _.postln)
// 3.13: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5]
// 3.14: [5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
::

subsection:: PulseDivider
Fixed init sample calculation. First sample of output changed for trigger on
first sample and teletype::div=1::
code::
{ PulseDivider.ar(DC.ar(1), 1) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [0,0,0,0,0,0,0,0]; 3.14: [1,0,0,0,0,0,0,0]
{ PulseDivider.ar(Impulse.ar(SampleRate.ir/2), 1) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: 0010101010101010...
// 3.14: 1010101010101010...

// no change if div > 1
{ PulseDivider.ar(Impulse.ar(SampleRate.ir/2), div: 2) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13/3.14: 0010001000100010...
::

subsection:: SpecPcile
In case of a memory allocation fail, now the UGen outputs all zeros, instead of potentially garbage values.

subsection:: Sweep
Fixed outputting 0 when triggered on first sample (it used to immediately increment to 1)
code::
// starts on 0
{ Sweep.ar(Impulse.ar(SampleRate.ir/4)) * SampleRate.ir }.loadToFloatArray(1e-4, action:_.postln)
// 3.13: [1, 2, 3, 4, 0, 1, 2, 3, 0]
// 3.14: [0, 1, 2, 3, 0, 1, 2, 3, 0]
::
Fixed increment behavior: now modulating teletype::rate:: affects the next
sample calculation (instead of the current, for which the previous value of
teletype::rate:: is used).
code::
// note that when rate changes to 1, Sweep doesn't increment anymore on the same sample, but on the next
{
	var rate = Impulse.ar(SampleRate.ir/2);
	Sweep.ar(0, rate) * SampleRate.ir
}.loadToFloatArray(2e-4, action: _.postln)
// rate: [1, 0, 1, 0, 1, 0, 1, 0]
// 3.13: [1, 1, 2, 2, 3, 3, 4, 4]
// 3.14: [0, 1, 1, 2, 2, 3, 3, 4]
::

subsection:: T2A
Fixed init sample calculation, affecting first sample of output. Used to ignore triggers on first sample, now it outputs them correctly

code::
{ T2A.ar(Impulse.kr(0)) }.loadToFloatArray(1e-4, action:_.postln)
// 3.13: [0.0, 0.0, 0.0, 0.0]; 3.14: [1.0, 0.0, 0.0, 0.0]
::

subsection:: TDelay
Fix delay of trigger on first sample. Output changes only if TDelay has a trigger on its first sample of input. If delayTime > dt (where dt is the time between first and second trigger), the output re-aligns with the previous version at the second trigger.
code::
{ var sr = SampleRate.ir; TDelay.ar(Impulse.ar(sr/2), 1/sr) }.loadToFloatArray(1e-4, action:_.postln)
// 3.13: [1, 0, 0, 1, 0, 1, 0, 1]
// 3.14: [0, 1, 0, 1, 0, 1, 0 ,1]

// example of outputs non re-aligning after first trig:
{ var sr = SampleRate.ir; TDelay.ar(Impulse.ar(sr/3), 4/sr)}.loadToFloatArray(1e-3, action:_.postln)
// 3.13: 0001000010000010000010000010000010000010000010000010000010000010
// 3.14: 0000100000010000010000010000010000010000010000010000010000010000
::

subsection:: Timer
Fix init sample calculation, fixes the first measured duration.
code::
{ Timer.ar(Impulse.ar(SampleRate.ir/2)) * SampleRate.ir }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [0, 0, 3, 3, 2, 2, 2, 2]; 3.14: [0, 0, 2, 2, 2, 2, 2, 2]

// also affects delayed triggers:
{
	var dt = SampleDur.ir * 4;
	Timer.ar(DelayN.ar(Impulse.ar(SampleRate.ir/2), dt, dt)) * SampleRate.ir
}.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [0, 0, 0, 0, 0.5, 0.5, 2, 2, 2, 2]
// 3.14: [0, 0, 0, 0, 4, 4, 2, 2, 2, 2]
::

subsection:: ToggleFF
list::
## Fixed init sample to be the same as first output sample
## Fixed random toggling when running at audio-rate with a control-rate input trigger
::
code::
{ ToggleFF.ar(Impulse.kr(0)) }.loadToFloatArray(3e-4, action: _.postln)
// trig: [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
// 3.13: [1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 1, 1, 1] (random)
// 3.14: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
::

subsection:: Trig, Trig1
Fixed init sample calculation, fixes initial phase output: it used to miss the first sample.
code::
{ Trig.ar(Impulse.ar(0), 4/SampleRate.ir) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [1, 1, 1, 0, 0, 0, 0]
// 3.14: [1, 1, 1, 1, 0, 0, 0, 0]
{ Trig1.ar(Impulse.ar(0), 4/SampleRate.ir) }.loadToFloatArray(2e-4, action:_.postln)
// 3.13: [1, 1, 1, 0, 0, 0, 0]
// 3.14: [1, 1, 1, 1, 0, 0, 0, 0]
::

subsection:: VarSaw
Fixed behavior for out-of-range initial phase: changes output if iphase is not
within 0 and 1.
code::
{ VarSaw.ar(SampleRate.ir/4, 10) }.loadToFloatArray(1e-4, action:_.postln)
// 3.13: [-33.0, -30.0, -27.00, -24.0]; 3.14: [-1.0, 0.0, 1.0, 0.0]

// no change if iphase is in range (0..1)
{ VarSaw.ar(SampleRate.ir/4) }.loadToFloatArray(1e-4, action:_.postln)
// 3.13/3.14: [-1.0, 0.0, 1.0, 0.0]
::
Fixed init sample calculation: was hardcoded to 0, now it's equal to first output sample.
code::
{ IEnvGen.ar(Env([VarSaw.ar(SampleRate.ir/4)]), 0) }.loadToFloatArray(0.0001, action:_.postln)
// 3.13: init sample is 0.0, 3.14: init sample is -1.0
::
