Skip to content

Instantly share code, notes, and snippets.

@magnetophon
Last active May 21, 2020 12:36
Show Gist options
  • Select an option

  • Save magnetophon/11f6955c5975f6bf177b579dbd16599a to your computer and use it in GitHub Desktop.

Select an option

Save magnetophon/11f6955c5975f6bf177b579dbd16599a to your computer and use it in GitHub Desktop.
/*
* Copyright (C) 2014 Bart Brouns
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
/*some building blocks where taken from or inspired by compressor-basics.dsp by Sampo Savolainen*/
declare name "LazyLimiter";
declare author "Bart Brouns";
declare version "0.3.2";
declare copyright "(C) 2014 Bart Brouns";
//import ("GUI.lib");
//import ("LazyLimiter.lib");
//process = stereoGainComputer;
//process = naiveStereoLimiter;
//process = ( 0:seq(i,maxHoldTime,(currentdown(x)@(i):max(lastdown)),_: min ));
//process(x,y) = (((Lookahead(x):releaseEnv(minRelease)),(Lookahead(y):releaseEnv(minRelease))):min)~(+(inGain@maxHoldTime)):meter:ba.db2linear<:(_*x@maxHoldTime,_*y@maxHoldTime);
//(((Lookahead(x):releaseEnv(minRelease)),(Lookahead(y):releaseEnv(minRelease))):min)~(_<:(_,_))+(inGain@maxHoldTime):meter:ba.db2linear<:(_*x@maxHoldTime,_*y@maxHoldTime);
//simpleStereoLimiter;
//process = slidemax(5,8);
//process = minimalStereoLimiter;
/*process(x) =*/
/*0: seq(i,maxAttackTime,*/
/*(currentdown(x)@(i+1-maxAttackTime+maxHoldTime))*/
/**(((i+1)/maxAttackTime))*/
/*,_: min*/
/*);*/
/*process =*/
/*(0,_):seq(i,maxAttackTime,*/
/*(*/
/*(_,*/
/*(*/
/*((_')<:(_,_)):*/
/*(*/
/*(_ *(((i+1)/maxAttackTime)))*/
/*,_*/
/*)*/
/*)*/
/*)*/
/*:min,_*/
/*)*/
/*) ;*/
/*process(x) = pmin(currentdown(x),0,4)*/
/*with {*/
/*pmin(del,mini,1) =del', ((del *((maxAttackTime/maxAttackTime))) ,mini : min);*/
/*pmin(del,mini,k) = del,(((pmin(del@(1),mini,(k-1))):(_*(((maxAttackTime-k+1)/maxAttackTime))),_) : (min));*/
/*};*/
//process =avgMeter(inGain);
//process(x,y) = (stereoGainComputerHalf(x,y),stereoGainComputerHalf(y,x))~(ro.cross(2));
//(stereoGainComputerHalf(x,y),stereoGainComputerHalf(y,x))~((_,_ <: !,_,_,!),_)
process = stereoLimiter;
// process = naiveStereoLimiter;
// process = minimalStereoLimiter;
// process(x) = block_hold(x);
// process(x) = Yann_hold(x);
// process = ((fixed_hold(maxWinSize)@(5):max(lastdown)),_): min ;
// Lookahead(x,lastdown,avgLevel) =
//(((_,(_,((_,_):Lookahead(y)):min)):linearXfade(link)):releaseEnv(minRelease):rateLimit);
//(((_,(_<:_,_)):(Lookahead(x)<:_,_),(_<:_,_)):ro.interleave(2,2));
//(((_,(_,Lookahead(y,prevy,avgLevely):min)):linearXfade(link)):releaseEnv(minRelease):rateLimit);
//GOOD:
//process(x,y,prevy) =
/*(*/
/*(((_,(_,((prevy,_):Lookahead(y)):min)):linearXfade(link)):releaseEnv(minRelease):(rateLimit))*/
/*~(((_<:(_,_)),_):((ro.cross(2):Lookahead(x)<:_,_),_))*/
/*):(_,!);*/
/*(*/
/*(((_,(_,(prevy:Lookahead(y,_)):min)):linearXfade(link)):releaseEnv(minRelease):rateLimit)*/
/*~((Lookahead(x)<:_,_),_)*/
/*);*/
/*(*/
/*(((_,(_,((prevy:Lookahead(y),_):(_,!)):min)):linearXfade(link)):releaseEnv(minRelease):rateLimit)*/
/*~((Lookahead(x)<:_,_),_):(_,!)*/
/*);*/
//process(x)= rdtable(maxAttackTime, (5) ,int(x*maxAttackTime));
//process(x)= rdtable(int(maxAttackTime), ma.tanh((6/maxAttackTime):pow(1:attackScale)),int(x*maxAttackTime));
/*process(x)= rdtable(maxAttackTime, ( ma.tanh((6/maxAttackTime):pow(attack:attackScale)*(attack*5+.1))/ma.tanh(attack*5+.1)),int(x*maxAttackTime))*/
/*with { attack = 1; };*/
/*attackScale(x) = (x+1):pow(7);*/
//process = rateLimiter;
//process = SMOOTH(3,4);
/*process(x) = 0:seq(i,maxHoldTime,*/
/*(((i+1)>(maxHoldTime-holdTime))*(currentdown(x)@(i):max(lastdown))),_: min */
/*)*/
/*with { maxHoldTime = 1024; holdTime = 4; lastdown = no.noise;};*/
/*
* Copyright (C) 2014 Bart Brouns
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
/*some building blocks where taken from or inspired by compressor-basics.dsp by Sampo Savolainen*/
declare name "LazyLimiter";
declare author "Bart Brouns";
declare version "0.3.2";
declare copyright "(C) 2014 Bart Brouns";
import("stdfaust.lib");
//import("slidingReduce.lib");
//todo: mid-side, auto-release
currentLevel(x) = ((abs(x)):ba.linear2db);
currentdown(x) = 0-(((currentLevel(x))-(threshold-inGain)):max(0));
// smoothing function, 0 attack, release coefficient "r" and signal x
releaseEnv(r, x) = x:(env(r))~_
with {
env (r, prevx, x)= select2( (x > prevx), x, (x*r)+(prevx * (1-r)) );
};
time_ratio_release(t) = exp(1) / ( t * ma.SR);// * time_ratio_target_rel );
// slidemin by Yann Orlarey, with variable_hold by Bart Brouns.
// 2*k : number of windows, w : size of the window in samples
// total window siz in samples: 2*k*w
// example _ : slidemin(16,16) : _
slidemin(k,w,x) = fixed_hold(w,x) : pmin(k,w)
with {
variable_hold(w,x) =
0:seq(i,maxWinSize,
(((i+1)>(maxWinSize-w))*(x@(i))),_: min
);
fixed_hold(w,x) =
0:seq(i,maxWinSize,
((x@(i))),_: min
);
smin(n) = F ~ (_,_) : !,_
with {
F(k,m,x) = (k+1)%n, ba.if(k==0, x, min(x,m));
};
pmin(1,w) = _ <: _ , @(w) : min;
pmin(k,w) = _ <: pmin(1,w), (@(2*w) : pmin(k-1,w)) : min;
};
// by Yann Orlarey
// 2*k : number of windows, w : size of the window in samples
// example _ : slidemax(7,16) : _
slidemax(k,w) = smax(w) : pmax(k,w)
with {
smax(n) = F ~ (_,_) : !,_
with {
F(k,m,x) = (k+1)%n, ba.if(k==0, x, max(x,m));
};
pmax(1,w) = _ <: _ , @(w) : max;
pmax(k,w) = _ <: pmax(1,w), (@(2*w) : pmax(k-1,w)) : max;
};
Lookahead(x,lastdown,avgLevel) =
attackGRYann,
// attackGRrelative,
// attackGR,
// attackGRrelativeLin,
// currentdown(x)@maxHoldTime,
// Yann_hold(x)
// variable_hold(maxHoldTime,winSize,x)
// block_hold_semivar(x)
// block_hold_var(x)
binary_block_hold(x)
// block_hold(x)
// 0
// (fixed_hold(maxHoldTime,currentdown(x)):max(lastdown))
// variable_hold(maxHoldTime,holdTime,x)
:min//:min(currentdown(x)@maxHoldTime)// todo: remove this hack without getting overshoot
// ;
// lastdown = -0.002;
with {
// this one should be more efficient: fixed hold time
// fixed_hold = ( 0:seq(i,maxHoldTime,(currentdown(x)@(i):max(lastdown)),_: min ));
fixed_hold(w,x) =
0:seq(i,w,
((x@(i))),_: min
);
// x:minn(w);
// yet this one is more efficient: variable hold time
variable_hold(maxSize,size,x) =
0:seq(i,maxSize,
(((maxSize-i)<=(size))*(currentdown(x)@(i))),_: min
// (((i+1)>(maxSize-size))*(currentdown(x)@(i))),_: min
// ):max(lastdown):min(currentdown(x)@maxHoldTime);
):max(lastdown);
block_hold(x) = ( 0:seq(i,int(maxHoldTime/maxWinSize),((fixed_hold(maxWinSize,currentdown(x))@(i*maxWinSize))),_:min )):max(lastdown);
binary_block_hold(x) = slidingMinN(winSize,maxHoldTime,currentdown(x)):max(lastdown);
block_hold_semivar(x) =
( (0)
:seq(i,int(maxHoldTime/maxWinSize),((fixed_hold(maxWinSize,currentdown(x))@(i*maxWinSize)*((int(maxHoldTime/maxWinSize)-i)<=ceil(winSize/maxWinSize)))),_:min )
):max(lastdown);
block_hold_var_OLD(x) =
( (variable_hold(maxWinSize,((decimal(winSize/maxWinSize))*maxWinSize),x@(floor(winSize/maxWinSize)*maxWinSize)))
:seq(i,int(maxHoldTime/maxWinSize),((fixed_hold(maxWinSize,currentdown(x))@(i*maxWinSize)*((int(maxHoldTime/maxWinSize)-i)<=floor(winSize/maxWinSize)))),_:min )
):max(lastdown);
block_hold_var(x) =
( 0:seq(i,int(maxHoldTime/maxWinSize),(variable_hold(maxWinSize,((decimal(winSize/maxWinSize))*maxWinSize),x)@(i*maxWinSize)*(((floor(winSize/maxWinSize)==i)))),_:min )
:seq(i,int(maxHoldTime/maxWinSize),((fixed_hold(maxWinSize,currentdown(x))@(i*maxWinSize)*((int(maxHoldTime/maxWinSize)-i)<=floor(winSize/maxWinSize)))),_:min )
):max(lastdown);
// winSize = int((holdTime):max(minHoldTime)):dhMeter;
// winSize = int((holdTime/(1+(((((avgLevel-lastdown):max(0)))*dynHold)))):max(minHoldTime)):dhMeter;
winSize = int((holdTime/(1+(((((avgLevel-lastdown):max(0))/dynHoldDiv):pow(dynHoldPow):max(0)*dynHold*dynHoldDiv)))):max(minHoldTime):dhMeter);
block_hold_va(size,block) = _ <: variable,par(i,int(rmsMaxSize/block), integrate(block)@(int(i*block))*(i<floor(size/block))) :> _ with {
// variable = delaysum(size:min(block),block);
variable = @(floor(size/block)*block):delaysum(int(decimal(size/block)*block),block);
};
Yann_hold(x)= slidemin(nrWin,winSize,currentdown(x)) : max(lastdown)
with {
winSize = maxWinSize;
// winSize = ((holdTime/(1+(((((avgLevel-lastdown):max(0))/dynHoldDiv):pow(dynHoldPow):max(0)*dynHold*dynHoldDiv)))):max(minHoldTime):dhMeter);
};
/*attackGRYann = currentdown(x)<:pmin(maxAttackTime)*/
/*with {*/
/*pmin(del,mini,1) = del <: @(1), (mini ,(@(1)*((1/maxAttackTime):attackShaper)) : min);*/
/*pmin(del,mini,k) = del <: @(1), (del,mini:pmin(k-1) ,(@(1)*((k/maxAttackTime):attackShaper)) : min);*/
/*};*/
attackGRYann =
(0,(currentdown(x))@(maxHoldTime-maxAttackTime)):seq(i,maxAttackTime,
(
(_,
(
((_')<:(_,_)):
(
(_ *(((i+1)/maxAttackTime):attackShaper))
,_
)
)
)
:min,_
)
):(_,!);
attackGR =
0: seq(i,maxAttackTime,
(currentdown(x)@(i+1-maxAttackTime+maxHoldTime))*(((i+1)/maxAttackTime):attackShaper),_: min
);
// in theory, attackGRrelative should give lower distortion, since the gain curve is si.smooth.
// neither my ear nor measurements confirm this.
// the non-relative version tkes much less CPU, so can work with a much longer attack-time,
// which does lower distorion (at least for linear attack)
// oddly enough, with minimalStereoLimiter, the both variants use about the same amount of CPU
attackGRrelativeOLD =
(0: seq(i,maxAttackTime,
// ((((currentdown(x)@(i+1-maxAttackTime+maxHoldTime)-lastRel(i))*(((i+1)/maxAttackTime):attackShaperI(i)))+lastRel(i))),_: min
((((currentdown(x)@(i+1-maxAttackTime+maxHoldTime)-lastRel(i))*(((i+1)/maxAttackTime):attackShaper))+lastRel(i)):(_<:((_<lastRel(i) ),_):*)),_: min
// ((((currentdown(x)@(i+1-maxAttackTime+maxHoldTime)-lastRel(i))*(((i+1)/maxAttackTime):attackShaper))+lastRel(i)):(_<:((_<=lastdown),_):*)),_: min
// ((((currentdown(x)@(i+1-maxAttackTime+maxHoldTime)-lastRel(i) )*(((i+1)/maxAttackTime):attackShaper))+lastRel(i))*(currentdown(x)@(i+1-maxAttackTime+maxHoldTime)<lastdown@(i+1))),_: min
// ((((currentdown(x)@(i+1-maxAttackTime+maxHoldTime)-lastRel(i) )*(((i+1)/maxAttackTime):attackShaper))+lastRel(i))*(currentdown(x)@(i+1-maxAttackTime+maxHoldTime)<lastdown)),_: min
// ));
));
attackGRrelativeLin =
(0: seq(i,maxAttackTime,
((((currentdown(x)@(i+1-maxAttackTime+maxHoldTime)-lastRel(i))*(((i+1)/maxAttackTime):attackShaper))+lastRel(i)):(_<:((_<lastRel(i) ),_):*)),_: min
)) with {
lastRel(i) = select2((lastdown<=lastdown'),lastdown,_)~_;
// lastRel(i) = lastdown@(i);
};
attackGRrelative =
(0: seq(i,maxAttackTime,
// ((lastRel(i)+((currentdown(x)@(i+1-maxAttackTime+maxHoldTime))*(((1)/maxAttackTime)))):(_<:((_<lastdown),_):*)),_: min
// ((((currentdown(x)@(i+1-maxAttackTime+maxHoldTime)-lastRel(i))*(((i+1)/maxAttackTime):attackShaper))+lastRel(i)):(_<:((_<lastdown),_):*)),_: min
((((currentdown(x)@(i+1-maxAttackTime+maxHoldTime)-lastdown)*(((i+1)/maxAttackTime):newshape))+lastdown):(_<:((_<lastdown),_):*)),_: min
// ((((((currentdown(x)@(i+1-maxAttackTime+maxHoldTime)-lastdown)*((i+1)/maxAttackTime)/(currentdown(x)@(i+1-maxAttackTime+maxHoldTime)-lastdown):newshape)*(currentdown(x)@(i+1-maxAttackTime+maxHoldTime)-lastdown)))+lastdown):(_<:((_<lastdown),_):*)),_: min
)):min(currentdown(x)@maxHoldTime);
// newshape(s) = ((sin(((s)*ma.PI)-(0.5*ma.PI))+1)/2):pow(1+(attack*40));
add = hslider("add", 0, 0, 1, 0.001)/10;
newshape(s) = s*(attack:pow(10));
// newshape = ((_+add):pow(1+(attack*40)))-(add:pow(1+(attack*40)));
// newshape = _;
// lastRel = select2((currentdown(x)@maxHoldTime)>(currentdown(x)@maxHoldTime+1),0,lastdown)@(-maxAttackTime+maxHoldTime);
// lastRel(i) = 0;
// lastRel(i) = lastdown;
lastRel(i) = lastdown@(i):max(lastdown);
// lastRel(i) = lastdown@(i):max(lastdown);
// lastRel(i) = lastdown@(i-maxAttackTime+maxHoldTime); // stays down
// lastRel(i) = select2((currentdown(x)@(i+1-maxAttackTime+maxHoldTime)<=lastdown),lastdown,_)~_;
// lastRel(i) = select2((currentdown(x)@(-maxAttackTime+maxHoldTime)>=currentdown(x)@(1-maxAttackTime+maxHoldTime)),lastdown,_)~_;
// lastRel = select2(lastdown>(currentdown(x)@maxHoldTime+1),0,lastdown)@(-maxAttackTime+maxHoldTime);
// lastRel = select2((lastdown@(-maxAttackTime+maxHoldTime))<currentdown(x)@(1-maxAttackTime+maxHoldTime),0,lastdown@(-maxAttackTime+maxHoldTime));
attackShaperHalf(fraction)= attackShaperHalf(fraction);
// attackShaperI(i,fraction)= select2(currentdown(x)@(i+1-maxAttackTime+maxHoldTime)>lastdown,attackShaperHalf(fraction),32/maxAttackTime);
attackShaperI(i,fraction)= select2(currentdown(x)@(i+1-maxAttackTime+maxHoldTime)>lastdown,attackShaperHalf(fraction),((attackShaperHalf((fraction*-1)+1)*-1)+1));
// attackShaper(fraction)= select2(currentdown(x)@maxHoldTime>lastdown,attackShaperHalf(fraction),((attackShaperHalf((fraction*-1)+1)*-1)+1));
attackShaper(fraction)= ma.tanh(fraction:pow(attack:attackScale)*(attack*5+.1))/ma.tanh(attack*5+.1);
// attackShaperLookup takes shorter to compile, but uses slightly more cpu than attackShaper, even though the later has a fixed attack shape.
attackShaperLookup(x)= rdtable(maxAttackTime, ( ma.tanh((time/maxAttackTime):pow(attack:attackScale)*(attack*5+.1))/ma.tanh(attack*5+.1)),int(x*maxAttackTime))
with { attack = 1; };
//attackShaper(x)= ma.tanh(x:pow(attack:attackScale)*mult)/ma.tanh(mult);
//attackShaper(x)= x:pow(attack:attackScale);
attackScale(x) = (x+1):pow(7); //from 0-1 to 1-128, just to make the knob fit the aural experience better
};
//ma.tanh(x^(32)*20)/ma.tanh(20)
LookaheadPar(x,lastdown) =
( par(i,maxHoldTime,(((i+1)>(maxHoldTime-holdTime))*(currentdown(x)@(i):max(lastdown)))): seq(j,(log(maxHoldTime)/log(2)),par(k,maxHoldTime/(2:pow(j+1)),min))),
( par(i,maxAttackTime, currentdown(x)@((i+1-maxAttackTime+maxHoldTime):max(0))*(((i+1)/maxAttackTime):attackShaper)): seq(j,(log(maxAttackTime)/log(2)),par(k,maxAttackTime/(2:pow(j+1)),min)))
:min
with {
//attackShaper(x)= x:pow(attack:attackScale);//atan((gainHS+0.0001)*128*x)/atan((gainHS+0.0001)*128);
attackShaper(x)= ma.tanh(x:pow(attack:attackScale)*(attack*5+.1))/ma.tanh(attack*5+.1);
attackScale(x) = (x+1):pow(7); //from 0-1 to 1-128, just to make the knob fit the aural experience better
}
;
// smoothing function, attack coefficient "a", release coefficient "r" and signal x
SMOOTH(a, r, x) =
((x * select2( (x < _), a, r )) + (_ * (1 - select2( (x < _), a, r)))) ~ (_<:_,_,_);
/*rateLimiter(baserelease,prevx,x) = prevx+newtangent:min(0)*/
/*with {*/
/*newtangent = select2(tangent>0,minus,plus);*/
/*tangent = x- prevx;*/
/*plus = tangent*((abs(avgChange):mymeter*-1):ba.db2linear):min(baserelease);*/
/*minus = tangent;// ba.if you have to go down, you have to go down!*/
/*avgChange = (abs((tangent)-(tangent@1)):integrate(IM_size)*releaseMult:_+1:pow(releasePower)-1)/200:SMOOTH(attackAVG,0.0008):ma.tanh*changeScale;*/
/*//select2(abs(tangent)>maxRate,tangent,maxRate);*/
/*integrate(size,x) = delaysum(size, x)/size;*/
/*delaysum(size) = _ <: par(i,rmsMaxSize, @(i)*(i<size)) :> _;*/
/*};*/
rateLimiter(baserelease,prevx,x) = ((prevx+newtangent):min(0)),avgLevel
with {
newtangent = select2(tangent>0,minus,plus);
tangent = x- prevx;
// this design reacts slower when the AVG is near 0dB GR versus when the AVG is lower, because there is no overshoot then.
//therefore, we have a 24 dB inGain.
// todo: maybe slightly si.smooth the GR curve manually around 0dB GR (probably not needed)
plus = (
(baserelease)*
(1-(((prevx - avgLevel )*((prevx - avgLevel )>0)*(antiPump:pow(2))*4):min(1)))*
(1-(((prevx - avgLevel )*((prevx - avgLevel )<0)*(transientSpeed:pow(2)*100))))
):min(tangent);
//plus = ((tangent*speed):max(baserelease)*(1-(((prevx - avgLevel )*((prevx - avgLevel )>0)*(antiPump:pow(2))):min(1))));
//:min(baserelease)
minus = tangent;// ba.if you have to go down, you have to go down!
speed = abs(select2(prevx<avgLevel,aboveAvg,belowAvg)):min(0.5):mymeter;
avgLevel = prevx:SMOOTH(releaseAVG,attackAVG):avgMeter;
belowAvg = ((abs(x - avgLevel ) )*releaseMult:_+1:pow(releasePower)-1)/200:ma.tanh:pow(transientSpeed);
aboveAvg = 0;
//avgChange = (abs(x-(integrate(IM_size,prevx):SMOOTH(attackAVG,releaseAVG)))*releaseMult:_+1:pow(releasePower)-1)/200:ma.tanh*changeScale;
//select2(abs(tangent)>maxRate,tangent,maxRate);
integrate(size,x) = delaysum(size, x)/size;
delaysum(size) = _ <: par(i,rmsMaxSize, @(i)*(i<size)) :> _;
};
/*rateLimiter(baserelease,prevx,x) = prevx+newtangent:min(0)*/
/*with {*/
/*newtangent = select2(tangent>0,minus,plus);*/
/*tangent = x- prevx;*/
/*plus = tangent*((abs(avgChange):mymeter*-1):ba.db2linear):min(baserelease);*/
/*minus = tangent;// ba.if you have to go down, you have to go down!*/
/*avgChange = (tangent:nrSignChange(IM_size)*releaseMult+1:pow(releasePower)-1)/200:SMOOTH(attackAVG,0.0008):ma.tanh*changeScale;*/
/*//select2(abs(tangent)>maxRate,tangent,maxRate);*/
/*nrSignChange(size) = _ <: par(i,rmsMaxSize, signChange(i,size)) :> _/size;*/
/*//signChange(i,x) = ( ((x@(i)>0) && (x@(i+1)<0)));*/
/*signChange(i,size,x) = ( ((x@(i):max(0)) * (x@(i+1)<=0)) + ((x@(i):min(0)*-1) * (x@(i+1)>0)) ) * (i<size);*/
/*integrate(size,x) = delaysum(size, x)/size;*/
/*delaysum(size) = _ <: par(i,rmsMaxSize, @(i)*(i<size)) :> _;*/
/*};*/
time_ratio_target_atk = 8.0;
time_ratio_attack(t) = exp(1) / ( t * ma.SR * time_ratio_target_atk );
rateLimit = ( rateLimiter(baserelease) ~ (_,!):(_,_));
releaseMult = (hslider("[3]releaseMult[tooltip: ]", 1 , 0, 10 , 0.001)*100);
releasePower = (hslider("[4]releasePower[tooltip: ]", 1, 0, 5 , 0.001));
IM_size = (hslider("[5]IM_size[tooltip: ]",256, 1, rmsMaxSize, 1)*44100/ma.SR); //0.0005 * min(192000.0, max(22050.0, ma.SR));
linearXfade(x,a,b) = a*(1-x),b*x : +;
limiter(x) = (Lookahead(x):releaseEnv(minRelease))~_+(inGain@maxHoldTime):meter:ba.db2linear *x@maxHoldTime;
// gainComputer(x) = (Lookahead(x):releaseEnv(minRelease):rateLimit:(min(currentdown(x)@maxHoldTime),_))~(_,_):(_,!);
// gainComputer(x) = (Lookahead(x):min(currentdown(x)@maxHoldTime))~(_,0);
gainComputer(x) = (Lookahead(x):releaseEnv(minRelease))~(_,0);
// gainComputer(x) = (0:min(currentdown(x)@maxHoldTime));
//:min(currentdown(x)@maxHoldTime)// todo: remove this hack without getting overshoot
//gainComputer(x) = (Lookahead(x):releaseEnv(minRelease)<:(_,rateLimit):linearXfade((link*-1)+1))~_<:(_,rateLimit):linearXfade(link);
//gainComputer(x) = (Lookahead(x)<:(releaseEnv(minRelease),rateLimit):linearXfade(link))~_;
//gainComputer(x) = (Lookahead(x):releaseEnv(minRelease))~_;
stereoGainComputerHalf (x,y,prevy,avgLevely) =
(
((Lookahead(x)<:_,_):((_,(_,((prevy,avgLevely):Lookahead(y)):min)):linearXfade(link)):releaseEnv(minRelease):(rateLimit))
~(si.bus(2))
);
/*(*/
/*(((_,(_,((prevy:Lookahead(y),_):(_,!)):min)):linearXfade(link)):releaseEnv(minRelease):rateLimit)*/
/*~((Lookahead(x)<:_,_),_):(_,!)*/
/*);*/
stereoGainComputer(x,y) = (stereoGainComputerHalf(x,y),stereoGainComputerHalf(y,x))~((ro.cross(2),ro.cross(2)):ro.cross(4)):(_,!,_,!);
//(stereoGainComputerHalf(x,y),stereoGainComputerHalf(y,x))~((_,_ <: !,_,_,!),_);
stereoLimiter(x,y) = (stereoGainComputer(x,y) : (((meter:_+(inGain@maxHoldTime):ba.db2linear)*x@maxHoldTime,((meter:_+(inGain@maxHoldTime):ba.db2linear)*y@maxHoldTime))));
//faust2jaqt -t 999999 -time -sch -vs 4096 -mcd 8192 LazyLimiter.dsp 485.55s
//maxHoldTime = 1024 maxAttackTime = 1024
//jack: 78% CPU
//faust2jaqt -t 999999 -time -sch -vs 2048 -mcd 4096 LazyLimiter.dsp 485.55s
//maxHoldTime = 1024 maxAttackTime = 1024
//jack: 80% CPU
simpleStereoLimiter(x,y) = (((Lookahead(x):releaseEnv(minRelease)),(Lookahead(y):releaseEnv(minRelease))):min)~(_<:(_,_))+(inGain@maxHoldTime):meter:ba.db2linear<:(_*x@maxHoldTime,_*y@maxHoldTime);
//simpleStereoLimiter(x,y) = (LookaheadPar(x),LookaheadPar(y):min:releaseEnv(minRelease))~(_<:(_,_))+(inGain@maxHoldTime):meter:ba.db2linear<:(_*x@maxHoldTime,_*y@maxHoldTime);
//faust2jaqt -t 999999 -time -sch -vs 2048 -mcd 4096 LazyLimiter.dsp
//maxHoldTime = 1024 maxAttackTime = 1024
//jack: 52% CPU 169.49s
//maxHoldTime = 2048 maxAttackTime = 1024
//jack: 52% CPU 485.55s
naiveStereoLimiter(x,y) = ((gainComputer(x),gainComputer(y)):min:meter+(inGain@maxHoldTime):ba.db2linear<:(_*x@maxHoldTime,_*y@maxHoldTime)),((gainComputer(x),gainComputer(y)):par(i, 2, ba.db2linear));
minimalStereoLimiter(x,y) = (gainComputer((abs(x),abs(y)):max):meter+(inGain@maxHoldTime):ba.db2linear<:(_*x@maxHoldTime,_*y@maxHoldTime)),(gainComputer((abs(x),abs(y)):max):ba.db2linear),((((abs(x),abs(y)):max)@maxHoldTime)*ba.db2linear(-18));
/************************************************************************
************************************************************************
FAUST library file
Copyright (C) 2016 Bart Brouns
---------------------------------------------------------------------
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of the
License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; ba.if not, write to the Free
Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
02111-1307 USA.
************************************************************************
************************************************************************/
declare name "Sliding Reduce Library";
declare author "Bart Brouns (bart@magnetophon.nl)";
declare copyright "Bart Brouns";
declare version "0.1";
declare license "LGPL";
import("stdfaust.lib");
//---------------------------------------------------------------
// Provides various operations on the last N samples
// using a high order 'slidingReduce(N,maxN,op,disabledVal,x)' fold-like function :
//
// slidingSumN(n,maxn) : the sliding sum of the last n input samples
// slidingMaxN(n,maxn) : the sliding max of the last n input samples
// slidingMinN(n,maxn) : the sliding min of the last n input samples
// slidingMeanN(n,maxn) : the sliding mean of the last n input samples
// slidingRMSn(n,maxn) : the sliding RMS of the last n input samples
//---------------------------------------------------------------
//---------------------------------------------------------------
// slidingReduce(N,maxN,op,disabledVal,x)
//---------------------------------------------------------------
// Fold-like high order function. Apply a binary operation <op>
// on the last <n> consecutive samples of a signal <x>.
// For example : slidingReduce(128,128,max,-INFINITY) will compute the maximun of
// the last 128 samples.
// The output is updated each sample,
// unlike reduce, where the output is constant for the duration of a block.
// N is the number of values to process
// maxN is the maximum number of values to process
// op is the operator
// disabledVal is the value to use when we want to ignore a value.
// In other words:
// op(x,disabledVal) should equal x
// For example:
// +(x,0) equals x
// min(x,INFINITY) equals x
// So ba.if we want to calculate the sum, we need to give 0 as disabledVal,
// and ba.if we want the minimum, we need to give INFINITY as disabledVal
//---------------------------------------------------------------
// working principle
//---------------------------------------------------------------
// Say we want the sum of the last 100 values.
// I will explain 3 methods to do that, each more complex but less expensive then the last.
// They build on each others ideas, so form a better followable train of thought.
// Method 1
// implemented in:
// https://github.com/magnetophon/faustCompressors/blob/master/compressors.lib#L84
// The simplest is to look up what the value is now, add it to the value of one sample ago,
// add that to the value two samples ago, and so forth.
// This costs one plus and one delayline for each value we want to sum.
// Method 2
// implemented in:
// https://github.com/magnetophon/faustCompressors/blob/master/compressors.lib#L92
// A more efficient method is based on the following idea:
// The sum of the last 10 values, let's call them v0 trough v-9, delayed by 10 samples,
// is equal to the sum of v-10 trough v-19.
// The sum of those two is equal to the sum of v0 trough v-19.
// So now we just need to calculate the sum of only 10 values (let's call it the block), add it to itself delayed by 10 samples,
// add that to the block delayed by 20 samples, etcetera.
// So adding 10 samples more, only costs one plus and one de.delay,
// on top of the operators to calculate the sum of the first ten.
// Method 3
// implemented in:
// https://github.com/magnetophon/faustCompressors/blob/master/compressors.lib#L130
// The next step is to ba.take this idea to the extreme:
// We can calculat the sum of the last four values as the sum of the last two plus the sum of the last two delayed by 2.
// The sum of the last eight is the sum of the last four plus the sum of the last four delayed by four.
// And on until you reach 128.
// So now it only costs one plus and one de.delay to double the number of values we sum.
// Trouble is, we wanted a 100 values, not 128.
// Even better would be to choose at run time how many values to add.
// To explain how that's done, we will again look at each of the above three methods.
// To choose whether a value is used, we can multiply it by 1 or 0 after it has been delayed.
// So in the first method, ba.if we want to have the sum of the last 42 values out of a maximum of 100,
// we multiply v0 through v-41 by 1 and v-42 trough v-99 by 0, and then add them all up.
// This costs one multiply for each extra value we want to switch,
// on top of the operations to do the non-switched version.
// In the second method, we need to multiply the blocks.
// So blocks 0 through -4 representing v0 trough -v39 get multiplied by 1,
// and block -5 trough -10, representing v-40 trough -99, get turned off.
// But thats only 40 values.
// To get the last two, we need to use a variable block:
// a block of up to 10 samples, where each sample can be turned on or of.
// To get the sum of v-40 and v-41, we ba.take the sum of the last 2 (v0 and v1),
// using our variable block, and de.delay it by 40 samples.
// Add that to the sum of the first 40, and we have 42 again.
// This costs one multiply per 10 values, plus the 10 multiplies and single de.delay for the variable block.
// To switch on and off values in the third method is a bit tricky:
// With the second method, the normal (non variable) blocks have a fixed delaytime:
// Block 4 will only ever be used when blocks 1 trough 3 are allready on,
// so it always gets a de.delay of 30 samples.
// The problem with the 3rd method is that the blocks are not always used in the same order,
// So they need a variable de.delay:
// For 42 we need a block of 32, plus a block of 8, plus a block of 2.
// The block of 2 doesn't get delayed, so represents v0 and v-1,
// the block of 8 gets delayed by 2 so it represents v-2 trough v-9
// and the block of 32 gets delayed by 10, so represents v-10 trough v-41.
// Add them all up, and we have the sum of v0 trough v-41 again.
// To illustrate that a certain block does not always get the same de.delay:
// If we wanted to get the sum of the last 32 values,
// we'd also use the 32 block, but *not* delayed, so it would represent
// v0 trough v-31
// So for making the third method switchable,
// we only need one multiply and one de.delay for each doubling of the number of values.
// To generalize from a function that adds the last n values
// to one that does any(* note) two input operation on the last n values,
// we just need to replace the 'times 1' and 'times 0'
// by 'choose input value' and 'choose bypass value' respectively.
// And of course change out the plus operator for a placeholder.
// That is implemented in slidingReduce below.
// If you can not, or do not want to specify a 'bypass value', you can use newSlidingReduce below.
// (* note) Not actually any operation: there are a few assumtions in the algorithm, as explained above.
// todo: can we do it with one switched block less and an op(x) at the end?
// that way the first block represents 2 instead of 1.
slidingReduce(N,maxN,op,disabledVal,x) =
par(i,maxNrBits,fixedDelayOp(1<<i,x)@sumOfPrevDelays(N,maxN,i) :useVal(i)):combine(maxNrBits) with {
// apply <op> to the last <N> values of <x>, where <N> is fixed
fixedDelayOp = case {
(1,x) => x;
(N,x) => op(fixedDelayOp(N/2,x) , fixedDelayOp(N/2,x)@(N/2));
};
// The sum of all the delays that are applied to the previous blocks
sumOfPrevDelays(N,maxN,0) = 0;
sumOfPrevDelays(N,maxN,i) = (ba.subseq((allDelays(N,maxN)),0,i):>_) with {
allDelays(N,maxN) = par(j, maxNrBits, (1<<j) * ba.take(j+1,(int2bin(N,maxN))) );
};
maxNrBits = int2nrOfBits(maxN);
// Apply <op> to <N> parallel inputsignals
combine(2) = op;
combine(N) = op(combine(N-1),_);
// Decide wether or not to use a certain value, based on N
// Basically only the second <select2> is needed,
// but this version also works for N == 0
// 'works' in this case means 'does the same as reduce
useVal(i) =
_<:select2((i==0) & (N==0) ,
select2( ba.take(i+1,(int2bin(N,maxN))) , disabledVal,_),
_
);
// useVal(i) =
// select2( ba.take(i+1,(int2bin(N,maxN))) , disabledVal,_);
};
newSlidingReduce(N,maxN,op,disabledVal,x) =
par(i,maxNrBits,fixedDelayOp(1<<i,x)@sumOfPrevDelays(N,maxN,i) )
:switched_combine(N,maxN)
with {
// apply <op> to the last <N> values of <x>, where <N> is fixed
fixedDelayOp = case {
(1,x) => x;
(N,x) => op(fixedDelayOp(N/2,x) , fixedDelayOp(N/2,x)@(N/2));
};
// The sum of all the delays that are applied to the previous blocks
sumOfPrevDelays(N,maxN,0) = 0;
sumOfPrevDelays(N,maxN,i) = (ba.subseq((allDelays(N,maxN)),0,i):>_) with {
allDelays(N,maxN) = par(j, maxNrBits, (1<<j) * ba.take(j+1,(int2bin(N,maxN))) );
};
// maxNrBits = int2nrOfBits(maxN);
// Apply <op> to <N> parallel inputsignals
combine(0) = 0:!;
combine(1) = _;
combine(2) = op;
combine(N) = op(combine(N-1),_);
isUsed(i,n) = int(floor(i/(1<<n))%2);
nrOfUsedBits(i,n) = par(j,int2nrOfBits(n),isUsed(i,j)):>_;
switched_combine(i,n) =
si.bus(int2nrOfBits(n))<:
par(j,n,
par(k,int2nrOfBits(n),S(isUsed(j+1,k))):combine(nrOfUsedBits(j+1,n))
)
: par(j, n, _*(i==j+1)):>_
with {
S(0) = !;
S(1) = _;
};
};
isSquare(N) =
(sqrt(N) == floor(sqrt(N)));
// N is a Fibonacci number ba.if and only ba.if 5 N^2 + 4 or 5 N^2 - 4 is a os.square number.
// http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibFormula.html#section5
isFibonacci(N) =
isSquare((5*(pow(N,2)))+4)
+
isSquare((5*(pow(N,2)))-4);
fixedFibonacciOps(N,op,disabledVal,x) =
(seq(i, N+1, fixedFibonacciOpPart(i)))
// fixedFibonacciOpPart(1)
// :(!,_)
with {
fixedFibonacciOpPart(0) = disabledVal;
fixedFibonacciOpPart(1) = _, x;
fixedFibonacciOpPart(2) = _,_,x;
fixedFibonacciOpPart(3) = si.bus(3),op(x,x');
fixedFibonacciOpPart(n) =
(si.bus(n-2),((split,split):ro.interleave(2,2)))
:(si.bus(n),op(_@Fibonacci2int(n-1),_));
split = _<:si.bus(2);
};
fixedFibonacciOp(0,op,disabledVal,x) = disabledVal;
fixedFibonacciOp(1,op,disabledVal,x) = x;
fixedFibonacciOp(2,op,disabledVal,x) = x;
fixedFibonacciOp(3,op,disabledVal,x) = op(x,x');
fixedFibonacciOp(N,op,disabledVal,x) =
(x,disabledVal):(seq(i, N-2, fixedFibonacciOpPart(i+3))):(!,_)
with {
fixedFibonacciOpPart(3,valMinOne,val) = x,op(x,x');
fixedFibonacciOpPart(i,valMinOne,val) = val,op(val, valMinOne@Fibonacci2int(i-1));
};
// fixedFibonacciOp(0,op,disabledVal,x) = disabledVal;
// fixedFibonacciOp(1,op,disabledVal,x) = x;
// fixedFibonacciOp(2,op,disabledVal,x) = x;
// fixedFibonacciOp(i,op,disabledVal,x) = op(fixedFibonacciOp((i-1),op,disabledVal,x), fixedFibonacciOp((i-2),op,disabledVal,x)@Fibonacci2int(i-1));
FibonacciOp(0,op,disabledVal,x) = disabledVal;
FibonacciOp(1,op,disabledVal,x) = x;
FibonacciOp(N,op,disabledVal,x) =
op( fixedFibonacciOp( largestIndex ,op,disabledVal,x) , (FibonacciOp (rest,op,disabledVal,x)@largestInt) )
with {
largestIndex = int2FibonacciIndexOfLargestPart(N);
largestInt = Fibonacci2int(largestIndex);
rest = max(0, N-largestInt);
};
Phi = (sqrt(5) +1)/2;
// int2FibonacciFloat(0) = 0;
// int2FibonacciFloat(1) = 1;
int2FibonacciFloat(N) =
select2(N>0,0,
select2(N>1,1,
(log(N) + (log(5)/2))/log(Phi))); // source: http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibFormula.html#section6
int2FibonacciFloored(N) = int(floor(int2FibonacciFloat(N)));
// int2FibonacciIndexOfLargestPart(0) = 0;
int2FibonacciIndexOfLargestPart(N) = // int2fibonacci sometimes gives a sligthly too low value, so we test:
select2((N == Fibonacci2int(int2FibonacciFloored(N+1))), int2FibonacciFloored(N),int2FibonacciFloored(N+1) )
; // more info: http://www.maths.surrey.ac.uk/hosted-sites/R.Knott/Fibonacci/fibFormula.html#section6
// int2FibonacciIndexOfLargestPart(N) = // make a table.
// int2FibonacciIndexOfLargestPartCalc(time)
myBus(0)=0:!; // redefine si.bus so it can have 0 elements
myBus(n) = si.bus(n);
Fibonacci2intSeq(n,maxN) =
// 1:seq(i, maxN-1 , int( _ * Phi ))
0<:(_,_):seq(i, maxN-1 , myBus(i+1) , Fibonacci2intPart(i+1))
: (si.bus(maxN),!)
: (par(i, maxN, * (i==n)))
// : ba.selector(1,maxN)
:>_
with {
// Fibonacci2intPart(0,prevFib)=0,(prevFib:!) <:(_,_);
Fibonacci2intPart(1,prevFib)=1,(prevFib:!) <:(_,_);
Fibonacci2intPart(2,prevFib)=1,(prevFib:!) <:(_,_);
Fibonacci2intPart(n,prevFib) = ( prevFib * Phi ) : (_+0.5:floor) <:(_,_); // for all n > 1
};
Fibonacci2intRecurive(0)=0;
Fibonacci2intRecurive(1)=1;
Fibonacci2intRecurive(N) = Fibonacci2intRecurive(N-1) + Fibonacci2intRecurive(N-2);
maxNrBits = 28;
// maxNrBits = 4;
// maxNrBits = 19;
// works fine up to about 21 maxNrBits, but then eats all RAM trying to compile:
// Fibonacci2int(N) = par(i,maxNrBits+1, Fibonacci2intRecurive(i)*(N==i)):>_;
// so we do:
time = int((+(1)~_ ) - 1); // 0,1,2,3,...
// Fibonacci2int(i) = rdtable(maxNrBits+1, par(j,maxNrBits+1, Fibonacci2intRecurive(j)*(j==(int(time)))):>_, i:min(maxNrBits+1):max(0));
Fibonacci2int(i) = rdtable(maxNrBits+1, Fibonacci2intSeq(time,maxNrBits+1), i:min(maxNrBits+1):max(0));
OLDfibReduce(N,maxN,op,disabledVal,x) =
(N,disabledVal,0):seq(i,maxNrBits+1, fibPart(i) ):(!,_,!)
with {
// i=3;
isUsed(i,n) = (int2FibonacciIndexOfLargestPart(n)) == i;
fibPart(i,rest,intermediateVal,sumOfDelays) =
(max(0, rest-(isUsed((currentBit(i)),rest) * ( Fibonacci2int(largestIndex(Fibonacci2int(currentBit(i)))) )))),
op(intermediateVal, (select2( isUsed((currentBit(i)),rest ), disabledVal, (fixedFibonacciOp( (currentBit(i)) , op,disabledVal,x)@sumOfDelays)))),
(sumOfDelays+ (isUsed(currentBit(i),rest ) * largestInt(rest)));
currentBit(i) = (maxNrBits-i);
// maxNrBits = int2FibonacciIndexOfLargestPart(maxN)+1;
largestIndex(n) = int2FibonacciIndexOfLargestPart(n);
largestInt(n) = Fibonacci2int(largestIndex(n));
};
fibReduce(N,maxN,op,disabledVal,x) =
(
disabledVal,N,0,
(fixedFibonacciOps(maxNrBits,op,disabledVal,x):ro.cross(maxNrBits+1))
)
:seq(i,maxNrBits+1, fibPart(i) ):(_,!,!)
with {
// i=3;
isUsed(i,n) = (int2FibonacciIndexOfLargestPart(n)) == i;
fibPart(i,intermediateVal,rest,sumOfDelays) =
op(intermediateVal, (select2( isUsed((currentBit(i)),rest ), disabledVal, (_@sumOfDelays)))),
(max(0, rest-(isUsed((currentBit(i)),rest) * ( Fibonacci2int(largestIndex(Fibonacci2int(currentBit(i)))) )))),
(sumOfDelays+ (isUsed(currentBit(i),rest ) * largestInt(rest))),
myBus(maxNrBits-i);
currentBit(i) = (maxNrBits-i);
// maxNrBits = int2FibonacciIndexOfLargestPart(maxN)+1;
largestIndex(n) = int2FibonacciIndexOfLargestPart(n);
largestInt(n) = Fibonacci2int(largestIndex(n));
};
FibonacciSlidingReduce(N,maxN,op,disabledVal,x) =
par(i,maxNrBits,fixedFibonacciOp(i)@sumOfPrevDelays(N,maxN,i) :useVal(i)):combine(maxNrBits) with {
// The sum of all the delays that are applied to the previous blocks
sumOfPrevDelays(N,maxN,0) = 0;
sumOfPrevDelays(N,maxN,i) = (ba.subseq((allDelays(N,maxN)),0,i):>_) with {
allDelays(N,maxN) = par(j, maxNrBits, j * ba.take(j+1,(int2bin(N,maxN))) );
};
// maxNrBits = int2FibonacciIndexOfLargestPart(maxN)+1;
// Apply <op> to <N> parallel inputsignals
combine(2) = op;
combine(N) = op(combine(N-1),_);
// Decide wether or not to use a certain value, based on N
// Basically only the second <select2> is needed,
// but this version also works for N == 0
// 'works' in this case means 'does the same as reduce
useVal(i) =
_<:select2((i==0) & (N==0) ,
select2( ba.take(i+1,(int2bin(N,maxN))) , disabledVal,_),
_
);
// useVal(i) =
// select2( ba.take(i+1,(int2bin(N,maxN))) , disabledVal,_);
};
// // convert N into a list of ones and zeros
int2bin(N,maxN) = par(i,int2nrOfBits(maxN),int(floor(N/(1<<i)))%2);
// calculate how many ones and zeros are needed to represent maxN
int2nrOfBits(0) = 0;
int2nrOfBits(maxN) = int(floor(log(maxN)/log(2))+1);
// slidingSumN(n,maxn) : the sliding sum of the last n input samples
slidingSumN(n,maxn) = slidingReduce(n,maxn,+,0);
// slidingMaxN(n,maxn) : the sliding max of the last n input samples
slidingMaxN(n,maxn) = slidingReduce(n,maxn,max,-INFINITY);
// slidingMinN(n,maxn) : the sliding min of the last n input samples
slidingMinN(n,maxn) = slidingReduce(n,maxn,min,INFINITY);
// slidingMeanN(n,maxn) : the sliding mean of the last n input samples
slidingMeanN(n,maxn) = slidingSumN(n,maxn)/n;
// slidingRMSn(n,maxn) : the sliding RMS of the last n input samples
slidingRMSn(n,maxn) = pow(2):slidingMeanN(n,maxn) : sqrt;
// maybe useful for math.lib:
INFINITY = fconstant(float INFINITY, <math.h>);
// bug in ba.subseq?:
// no elements is undefined.
// proposed solution:
// ba.subseq((head, tail), 0, 0) = !;
// ba.subseq((head, tail), 0, 1) = head;
// ba.subseq((head, tail), 0, n) = head, ba.subseq(tail, 0, n-1);
// ba.subseq((head, tail), p, n) = ba.subseq(tail, p-1, n);
// ba.subseq(head, 0, n) = head;
//
/*
* Copyright (C) 2014 Bart Brouns
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
/*some building blocks where taken from or inspired by compressor-basics.dsp by Sampo Savolainen*/
declare name "LazyLimiter";
declare author "Bart Brouns";
declare version "0.3.2";
declare copyright "(C) 2014 Bart Brouns";
import("stdfaust.lib");
SampleRate = 44100;
//Lookahead and LookaheadPar need a power of 2 as a size
// maxHoldTime = 4; // = 0.1ms, for looking at the block diagram
// maxHoldTime = 128; // = 3ms
//maxHoldTime = 256; // = 6ms
// maxHoldTime = 512; // = 12ms, starts to sound OK, 84% cpu
// maxHoldTime = 1024; // = 23ms, good sound, 185% CPU
// maxHoldTime = 2048; // = 46ms, even less distortion, but can be less loud, 300% CPU
// maxHoldTime = maxWinSize*nrWin;//8192 // = 186ms
// maxHoldTime = maxWinSize*nrWin*2;//8192 // = 186ms
// maxHoldTime = 4096; // = 92ms
maxHoldTime = 8192; // = 186ms
nrWin = 64;
// nrWin = 4;
// maxHoldTime = 32; // =
maxWinSize = int(maxHoldTime/nrWin*SampleRate/44100);
// maxWinSize = int(128*SampleRate/44100);
// maxWinSize = int(4*SampleRate/44100);
// nrWin = 128;
// nrWin = 32;
// nrWin = 256;
// nrWin = 512;
//with maxHoldTime = 1024, having maxAttackTime = 512 uses more cpu then maxAttackTime = 1024
// maxAttackTime = int(16*SampleRate/44100):min(maxHoldTime);
maxAttackTime = int(1024*SampleRate/44100):min(maxHoldTime);
//rmsMaxSize = 1024:min(maxHoldTime);
rmsMaxSize = int(512*SampleRate/44100):min(maxHoldTime);
main_group(x) = (hgroup("[1]", x));
minKnobGroup(x) = main_group(vgroup("[0]minimum gain reduction [tooltip: There will never be less gain reduction then what these setting dictate.]", x));
inGain = minKnobGroup(hslider("[0]input gain [unit:dB] [tooltip: input gain in dB ", 0, 0, 30, 0.1)):si.smooth(0.999) ;
threshold = minKnobGroup(hslider("[1]threshold [unit:dB] [tooltip: maximum output level in dB]", -0.5, -60, 0, 0.1));
attack = minKnobGroup(hslider("[2]attack shape[tooltip: 0 gives a linear attack (slow), 1 a strongly exponential one (fast)]", 1 , 0, 1 , 0.001));
// release = minKnobGroup(hslider("[3]lin release[unit:dB/s][tooltip: maximum release rate]", 10, 6, 500 , 1)/SampleRate);
minRelease = minKnobGroup(hslider("[3]minimum release time[unit:ms] [tooltip: minimum time in ms for the GR to go up][scale:log]",30, 1, 100, 0.1)/1000):time_ratio_release;
// time_ratio_target_rel = minKnobGroup(hslider("[4]release shape", 1, 0.5, 5.0, 0.001));
// hardcoding link to 1 leads to much longer compilation times, yet similar cpu-usage, while one would expect less cpu usage and maybe shorter compilation time
link = minKnobGroup(hslider("[5]stereo link[tooltip: 0 means independent, 1 fully linked]", 1, 0, 1 , 0.001));
dynHoldKnobGroup(x) = main_group(vgroup("[1]dynamic hold [tooltip: the GR will not go up ba.if it has to be back here within the hold time]", x));
//hardcoding holdTime to maxHoldTime uses more cpu then having a fader!
maxHoldMs = maxHoldTime*1000/SampleRate;
// holdTime = 0;
holdTime = int(dynHoldKnobGroup(hslider("[0]maximum hold time[unit:ms] [tooltip: maximum hold time in ms][scale:log]", maxHoldMs, 0.1, maxHoldMs ,0.1))/1000*SampleRate);
// minHoldTime = int(dynHoldKnobGroup(hslider("[1]minimum hold time[unit:ms] [tooltip: minimum hold time in ms]", 0, 0, maxHoldTime ,1)));
minHoldTime = int(dynHoldKnobGroup(hslider("[1]minimum hold time[unit:ms] [tooltip: minimum hold time in ms][scale:log]", 30, 0.1, maxHoldMs ,0.1))/1000*SampleRate);
dynHold = dynHoldKnobGroup(hslider("[2]dynHold[tooltip: shorten the hold time when the GR is below AVG]", 0.5, 0, 1 , 0.001))*20;
dynHoldPow = dynHoldKnobGroup(hslider("[3]dynHoldPow[tooltip: shape the curve of the hold time]", 2, 0.1, 10 , 0.1));
dynHoldDiv = dynHoldKnobGroup(hslider("[4]dynHoldDiv[tooltip: scale the curve of the hold time]", 6, 0.1, 24 , 0.1));
musicRelKnobGroup(x) = main_group(vgroup("[2] musical release [tooltip: this section fine tunes the release to sound musical]", x));
baserelease = musicRelKnobGroup(hslider("[0]base release rate[unit:dB/s][tooltip: release rate when the GR is at AVG, in dB/s][scale:log]", 30, 0.1, 6000 , 0.1)/SampleRate);
transientSpeed = musicRelKnobGroup(hslider("[1]transient speed[tooltip: speed up the release when the GR is below AVG ]", 0.5, 0, 1, 0.001));
antiPump = musicRelKnobGroup(hslider("[2]anti pump[tooltip: slow down the release when the GR is above AVG ]", 0.5, 0, 1, 0.001));
attackAVG = musicRelKnobGroup(time_ratio_attack(hslider("[3] AVG attack [unit:ms] [tooltip: time in ms for the AVG to go down][scale:log]", 1400, 50, 5000, 1)/1000)) ;
releaseAVG = musicRelKnobGroup(time_ratio_attack(hslider("[4] AVG release [unit:ms] [tooltip: time in ms for the AVG to go up][scale:log]", 300, 50, 5000, 1)/1000)) ;
GRmeter_group(x) = main_group(hgroup("[3] GR [tooltip: gain reduction in dB]", x));
meter = GRmeter_group( _<:(_,(_:min(0):max(-20):( (vbargraph("[unit:dB]", -20, 0))))):attach);
AVGmeter_group(x) = (main_group(hgroup("[4] AVG [tooltip: average gain reduction in dB]", x)));
avgMeter = AVGmeter_group((_<:(_,(_:min(0):max(-20):( (vbargraph("[unit:dB]", -20, 0))))):attach));
DHmeter_group(x) = (main_group(hgroup("[5] HoldTime [tooltip: hold time in ms]", x)));
dhMeter = DHmeter_group((_<:(_,((_*1000/SampleRate):min(maxHoldMs):max(0):( (vbargraph("[unit: ms]", 0, maxHoldMs))))):attach));
// dhMeter = DHmeter_group((_<:(_,((_*1000/SampleRate*nrWin*2):min(maxHoldMs):max(0):( (vbargraph("[unit: ms]", 0, maxHoldMs))))):attach));
mymeter = meter_group(_<:(_, ( (vbargraph("[2]SD[tooltip: slow down amount]", 0, 0.5)))):attach);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment