// (C) 2008, Jan Krueger . All rights reserved. /* * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * Neither the name of the author nor the names of any contributors may be * used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* Here's some sort-of documentation. * I was inspired to write this effect after looking at Michael Gruhn's * "Saturation" filter but I wanted more control over the curve properties. * Other soft clipping filters I had seen sometimes sounded weird without making * it even somewhat obvious why. * * This completely time-independent filter performs what is called range * compression, i.e. it maps input sample values onto different output sample * values according to the curve you can see in the filter's window (X axis = * input, Y axis = output). * * The idea is, roughly, to make clipping "less noticeable" by softening the * impact with the ceiling. We do this by making the curve approach the ceiling * more slowly (the part where we do this slowing down is called the knee of the * curve). As a fun side effect, if you pull a signal through this effect it * tends to sound "fuller". * * The sliders work like this: * * Pre-boost: * Boost the levels by the given amount before doing anything else. This * will increase clipping. See how much you can get away with. * Soft ceiling: * This controls the maximum level that comes out of the filter. This is * basically just a negative gain filter applied after everything else. * Knee size: * Here you define where the knee begins. The non-knee part of the curve * has a dark green background while the knee part is two shades lighter * (see below for details about the shade between them). * Knee form: * The leftmost position of this slider means no knee at all. The more you * pump it up, the more slowly the curve approaches the ceiling. For some * values of "knee size" above, high values on this slider cause a knee that * kicks in rather drastically. To reduce that, the knee onset is * automatically smoothened. The smoothened part of the curve has the third * shade of green mentioned above. * * A note about boosting: a large and very soft knee tends to increase perceived * loudness quite a bit more than simply boosting the pre-level to achieve the * same average boost (as displayed in the graph). Oh, and average boost doesn't * mean what you think it means. Remember that this filter is time-independent, * i.e. it has no memory. As such, average definitely doesn't mean "average * boost across your signal". */ desc:Soft clipper with customizable knee slider1:0<0,10,0.1>Pre-boost (dB) slider2:0<-10,0,0.1>Soft ceiling (dB) slider3:2<0,6,0.1>Knee size (dB) slider4:1<0,2,0.02>Knee form // Now automatically calculated //slider5:0.15<0,0.15,0.01>Smoothen knee onset @init gfx_clear = 256*48; sm = 0; tm = 0; hp = $pi/2; @slider preFac = 2^(slider1/6); ceilFac = 2^(slider2/6); kneeSz = 1/2^(slider3/6); dblSine = (slider4 > 1); // Determine weight for outer sine function sineAmount = dblSine ? slider4-1 : slider4; (sineAmount == 0) ? sineAmount = 0.001; // Parameters for making the knee part straighter or curvier sineFac = sineAmount/2*$pi; sineDiv = sin(sineFac); // Calculate base gain so the curve has no jumps dblSine ? kneeSzOut = sin(kneeSz*hp) : kneeSzOut = kneeSz; kneeSzOut = sin(kneeSzOut*sineFac)/sineDiv; kneeStretch = kneeSzOut/kneeSz; // Knee smoothening (linear) /* We automatically calculate a reasonable value now // First make sure we don't smoothen too much slider5 = max(0,min(min(slider5, 0.99-kneeSz), kneeSz)); // For very straight knees the mixing doesn't work too well slider5 = min(slider5, slider4/9); smoothSz = slider5 * 2; */ smoothSz = min(min(0.99-kneeSz, kneeSz), slider4/9)*2; smoothB = kneeSz - smoothSz/2; smoothE = kneeSz + smoothSz/2; @sample // Pre-boost and clipping spl0 = min(1,max(-1, spl0 * preFac)); spl1 = min(1,max(-1, spl1 * preFac)); // Calculate knee part lvl = abs(spl0); (lvl >= smoothB) ? ( dblSine ? s = sin(spl0*hp) : s = spl0; s = sin(s*sineFac)/sineDiv; sm = min(1, (lvl - smoothB)/smoothSz); ) : s = 0; // Calculate base part (lvl <= smoothE) ? ( t = spl0 * kneeStretch; tm = min(1, (smoothE - lvl)/smoothSz); ) : t = 0; // This mixing formula is the result of hours of experimentation. Touch at your own risk. slider4 ? spl0 = min(1,max(-1, (s*sm + t*tm^(1+smoothSz/4.5)) * ceilFac)); // Exactly the same thing again for the other channel lvl = abs(spl1); (lvl >= smoothB) ? ( dblSine ? s = sin(spl1*hp) : s = spl1; s = sin(s*sineFac)/sineDiv; sm = min(1, (lvl - smoothB)/smoothSz); ) : s = 0; (lvl <= smoothE) ? ( t = spl1 * kneeStretch; tm = min(1, (smoothE - lvl)/smoothSz); ) : t = 0; slider4 ? spl1 = min(1,max(-1, (s*sm + t*tm^(1+smoothSz/4.5)) * ceilFac)); @gfx gs = min(gfx_w,gfx_h); gfx_a = 1; // Different BG for base and mix gfx_r = gfx_b = 0; (smoothB != smoothE) ? ( x = smoothE*gs/2 / preFac; gfx_g = 0.15; gfx_x = gs/2-x; gfx_y = 0; gfx_rectto(gs/2+x, gs-1); ); x = smoothB*gs/2 / preFac; gfx_g = 0.125; gfx_x = gs/2-x; gfx_y = 0; gfx_rectto(gs/2+x, gs-1); // Axes gfx_r = gfx_g = gfx_b = 1; gfx_x = 0; gfx_y = gs/2; gfx_lineto(gs,gs/2,0); gfx_x = gs/2; gfx_y = 0; gfx_lineto(gs/2,gs,0); // Curve gfx_r = gfx_g = 1; gfx_b = 0; gfx_x = 0; gfx_y = gs; avgLvl = 0; avgLvlDiv = 0; x = -1; while( xp = min(1,max(-1, x*preFac)); cg_lvl = abs(xp); // Calculate knee part (cg_lvl >= smoothB) ? ( dblSine ? cg_s = sin(xp*hp) : cg_s = xp; cg_s = sin(cg_s*sineFac)/sineDiv; cg_sm = min(1, (cg_lvl - smoothB)/smoothSz); ) : cg_s = 0; // Calculate base part (cg_lvl <= smoothE) ? ( cg_t = xp * kneeStretch; cg_tm = min(1, (smoothE - cg_lvl)/smoothSz); ) : cg_t = 0; y = (cg_s*cg_sm + cg_t*cg_tm^(1+smoothSz/4.5)) * ceilFac; (x != 0) ? (avgLvl += y/x; avgLvlDiv += 1;); gfx_lineto(x*gs/2+gs/2,-y*gs/2+gs/2,1); (x += 0.01) <= 1; ); gfx_lineto(gs,(gs/2-ceilFac*gs/2),0); // And finally... gfx_x = gfx_y = 5; gfx_drawchar($'A'); gfx_drawchar($'v'); gfx_drawchar($'e'); gfx_drawchar($'r'); gfx_drawchar($'a'); gfx_drawchar($'g'); gfx_drawchar($'e'); gfx_x += 8; gfx_drawchar($'b'); gfx_drawchar($'o'); gfx_drawchar($'o'); gfx_drawchar($'s'); gfx_drawchar($'t'); gfx_drawchar($':'); gfx_x += 8; gfx_drawnumber(10*log10(avgLvl/avgLvlDiv*ceilFac),2); gfx_x += 8; gfx_drawchar($'d'); gfx_drawchar($'B');