/* * limiter~: limit/compress signals * * (c) 1999-2011 IOhannes m zmölnig, forum::fÌr::umlÀute, institute of electronic music and acoustics (iem) * * 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; either version 2 * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program. If not, see . */ /* --------------------------------- limiter/compressor --------------------------------- for details on how it works watch out for "http://iem.kug.ac.at/~zmoelnig/pd" ...and search for "limiter" mail2me4more!n4m8ion : zmoelnig@iem.kug.ac.at */ /* this is a limiter/compressor-object the limiter is based on Falkner's thesis "Entwicklung eines digitalen Stereo-limiters mit Hilfe des Signalprozessors DSP56001" pp.14 2108:forum::fÌr::umlÀute:1999 all rights reserved and no warranties... see GNU-license for details */ #define LIMIT0 0 #define LIMIT1 1 #define COMPRESS 2 #include "zexy.h" /* log2 */ #define LN2 .69314718056 /* hmm, where do these values come from exactly? we are doing a 3* oversampling, so we need to calculate the samples that are +-1/3 off the current sample sinc([13 10 7 4 1 -2 -5 -8 -11]/3) * window window=cosine window */ /* #define SINC[4] .822462987 #define SINC[3] .404460777 #define SINC[5] -.188874003 #define SINC[2] -.143239449 #define SINC[6] .087796546 #define SINC[1] .06917082 #define SINC[7] -.041349667 #define SINC[0] -.030578954 #define SINC[8] .013226276 */ #define BUFSIZE 128 #define XTRASAMPS 9 #define TABLESIZE 512 /* compressor table */ static t_sample SINC[9]; #define PI 3.1415926535897932384626433832795029L /* pi */ static void init_sinc(void) { /* calculate the sinc (windowed with a cosine) */ int i=0; for(i=0; i<9; i++) { long double t=(3.*i - 11.)/3.; long double v=cos(t*PI/10.)*sin(PI*t)/(PI*t); SINC[i]=v; } } /* ------------------------------------------------------------------------------------ */ /* first define the structs... */ static t_class *limiter_class; typedef struct _limctl { /* variables changed by user */ t_float limit; t_float hold_samples; t_float change_of_amplification; } t_limctl; typedef struct _cmpctl { t_float treshold, ratio; /* uclimit is the very same is the limiter1-limit (decalculated relative to our treshold) */ t_float uclimit, climit_inverse; /* climit == compressed limit (uclimit == uncompressed limit) */ t_float limiter_limit; /* start limiting (stop compressing); == tresh/limit; */ t_float treshdB, oneminusratio; } t_cmpctl; typedef struct _inbuf { t_sample* ringbuf; int buf_position; } t_inbuf; typedef struct _limiter { t_object x_obj; int number_of_inlets, s_n; /* variables changed by process */ t_sample amplification; t_float samples_left, still_left; int mode; t_limctl *val1, *val2; t_cmpctl *cmp; /* note : limit is not the same for val1 & val2 : * at val1 it is the limit of the INPUT_VALUE * at val2 it is the limit for the AMPLIFICATION (in fact it is abs_limit1/abs_limit2) */ t_inbuf* in; int buf_size; } t_limiter; /* ------------------------------------------------------------------------------------ */ /* then do the message - thing */ /* do the user settings */ /* calcs */ static t_float calc_holdsamples(t_float htime, int buf) { /* hold_time must be greater than buffer_time to make sure that any peak_sample is amplified with its own factor */ t_float min_hold = buf / sys_getsr(); return (0.001 * sys_getsr() * ((htime > min_hold)?htime:((min_hold > 50)?min_hold:50))); } static t_float calc_coa(t_float hlife) { return (exp(LN2 * 1000 / (((hlife > 0)?hlife:15) * sys_getsr()))); } static void set_uclimit(t_limiter *x) { t_cmpctl *c = x->cmp; t_float limit = x->val1->limit, limitdB = rmstodb(limit), ratio = c->ratio, tresh = c->treshold, treshdB = rmstodb(tresh); c->climit_inverse = limit / tresh; c->uclimit = tresh / dbtorms(treshdB+(limitdB - treshdB)/ratio); c->treshdB = treshdB; c->oneminusratio = 1. - ratio; } /* settings */ static void set_treshold(t_limiter *x, t_float treshold) { t_cmpctl *c = x->cmp; t_float tresh = dbtorms (treshold); if (tresh > x->val1->limit) tresh = x->val1->limit; c->treshold = tresh; set_uclimit(x); } static void set_ratio(t_limiter *x, t_float ratio) { if (ratio < 0) ratio = 1; x->cmp->ratio = ratio; set_uclimit(x); } static void set_mode(t_limiter *x, t_float mode) { int modus = mode; switch (modus) { case LIMIT0: x->mode = LIMIT0; break; case LIMIT1: x->mode = LIMIT1; break; case COMPRESS: x->mode = COMPRESS; break; default: x->mode = LIMIT0; break; } } static void set_LIMIT(t_limiter *x) { set_mode(x, LIMIT0); } static void set_CRACK(t_limiter *x) { set_mode(x, LIMIT1); } static void set_COMPRESS(t_limiter *x) { set_mode(x, COMPRESS); } static void set_bufsize(t_limiter *x, int size) { /* this is really unneeded...and for historical reasons only */ if (size < BUFSIZE) size = BUFSIZE; x->buf_size = size + XTRASAMPS; } static void set_limit(t_limiter *x, t_floatarg limit) { if (limit < 0.00001) limit = 100; x->val1->limit = dbtorms(limit); if (x->val1->limit < x->cmp->treshold) x->cmp->treshold = x->val1->limit; set_uclimit(x); } static void set_limits(t_limiter *x, t_floatarg limit1, t_floatarg limit2) { t_float lim1, lim2; if (limit1 < 0.00001) limit1 = 100; lim1 = dbtorms(limit1); lim2 = dbtorms(limit2); if (lim2 < lim1) { lim2 = 2*lim1; /* this is to prevent lim2 (which should trigger the FAST regulation) */ x->mode = 0; /* to underrun the SLOW regulation; this would cause distortion */ } x->val1->limit = lim1; x->val2->limit = lim1/lim2; if (lim1 < x->cmp->treshold) x->cmp->treshold = lim1; set_uclimit(x); } static void set1(t_limiter *x, t_floatarg limit, t_floatarg hold, t_floatarg release) { t_float lim = dbtorms(limit); x->val1->limit = (lim > 0)?lim:1; x->val1->hold_samples = calc_holdsamples(hold, x->buf_size); x->val1->change_of_amplification = calc_coa(release); if (lim < x->cmp->treshold) x->cmp->treshold = lim; set_uclimit(x); } static void set2(t_limiter *x, t_floatarg limit, t_floatarg hold, t_floatarg release) { t_float lim = dbtorms(limit); x->val2->limit = (lim > x->val1->limit)?(x->val1->limit/lim):.5; x->val2->hold_samples = calc_holdsamples(hold, x->buf_size); x->val2->change_of_amplification = calc_coa(release); } static void set_compressor(t_limiter *x, t_floatarg limit, t_floatarg treshold, t_floatarg ratio) { t_cmpctl *c = x->cmp; t_float lim = dbtorms(limit); t_float tresh = dbtorms(treshold); if ((limit == 0) && (treshold == 0) && (ratio == 0)) {set_mode(x, COMPRESS); return;} if (tresh > lim) tresh = lim; if (ratio < 0.) ratio = 1.; c->ratio = ratio; x->val1->limit = lim; c->treshold = tresh; set_uclimit(x); set_mode(x, COMPRESS); } static void reset(t_limiter *x) { x->amplification = 1.; } /* verbose */ static void status(t_limiter *x) { t_limctl *v1 = x->val1; t_limctl *v2 = x->val2; t_cmpctl *c = x->cmp; t_float sr = sys_getsr() / 1000.; switch (x->mode) { case LIMIT1: post("%d-channel crack-limiter @ %fkHz\n" "\noutput-limit\t= %fdB\nhold1\t\t= %fms\nrelease1\t= %fms\ncrack-limit\t= %fdB\nhold2\t\t= %fms\nrelease2\t= %fms\n" "\namplify\t\t= %fdB\n", x->number_of_inlets, sr, rmstodb(v1->limit), (v1->hold_samples) / sr, LN2 / (log(v1->change_of_amplification) * sr), rmstodb(v1->limit / v2->limit), (v2->hold_samples) / sr, LN2 / (log(v2->change_of_amplification) * sr), x->amplification); break; case LIMIT0: post("%d-channel limiter @ %fkHz\n" "\noutput-limit\t= %fdB\nhold\t\t= %fms\nrelease\t\t= %fms\n" "\namplify\t\t= %fdB\n", x->number_of_inlets, sr, rmstodb(v1->limit), (v1->hold_samples) / sr, LN2 / (log(v1->change_of_amplification) * sr), rmstodb(x->amplification)); break; case COMPRESS: post("%d-channel compressor @ %fkHz\n" "\noutput-limit\t= %fdB\ntreshold\t= %fdB\ninput-limit\t= %f\nratio\t\t= 1:%f\n" "\nhold\t\t= %fms\nrelease\t\t= %fms\n" "\namplify\t\t= %fdB\n", x->number_of_inlets, sr, rmstodb(c->treshold * c->climit_inverse), rmstodb(c->treshold), rmstodb(c->treshold / c->uclimit), 1./c->ratio, (v1->hold_samples) / sr, LN2 / (log(v1->change_of_amplification) * sr), rmstodb(x->amplification)); } } static void limiter_tilde_helper(t_limiter *x) { post("\n\n"HEARTSYMBOL" %d-channel limiter-object: mode %d", x->number_of_inlets, x->mode); poststring("\n'mode '\t\t\t: (0_limiter, 1_crack-limiter, 2_compressor)"); poststring("\n'LIMIT'\t\t\t\t: set to LIMITer"); poststring("\n'CRACK'\t\t\t\t: set to CRACK-limiter"); poststring("\n'COMPRESS'\t\t\t\t: set to COMPRESSor"); switch (x->mode) { case LIMIT0: poststring("\n'limit '\t\t\t: set limit (in dB)" "\n'set '\t: set limiter"); break; case LIMIT1: poststring("\n'limits '\t: set limits (in dB)" "\n'set '\t: set limiter 1" "\n'set2 '\t: set crack-limiter"); break; case COMPRESS: poststring("\n'ratio '\t\t: set compressratio (Ŝ0.5Ŝ instead of Ŝ1:2Ŝ)" "\n'treshold '\t\t: set treshold of the compressor" "\n'compress '\t: set compressor" "\n..........note that is the same for COMPRESSOR and LIMITER.........."); break; default: break; } poststring("\n'print'\t\t\t\t: view actual settings" "\n'help'\t\t\t\t: view this\n"); poststring("\ncreating arguments are :\n" "\"limiter~ [ [ [ [...]]]]\": may be anything\n"); endpost(); } /* ------------------------------------------------------------------------------------ */ /* now do the dsp - thing */ /* ------------------------------------------------------------------------------------ */ static t_int *oversampling_maxima(t_int *w) { t_limiter *x = (t_limiter *)w[1]; t_inbuf *buf = (t_inbuf *)w[2]; t_sample *in = (t_sample *)w[3]; t_sample *out = (t_sample *)w[4]; int n = x->s_n; int bufsize = x->buf_size; int i = buf->buf_position; t_sample *vp = buf->ringbuf, *ep = vp + bufsize, *bp = vp + XTRASAMPS + i; i += n; while (n--) { t_sample os1, os2, max; t_sample last4, last3, last2, last1, sinccurrent, current, next1, next2, next3, next4; if (bp == ep) { vp[0] = bp[-9]; vp[1] = bp[-8]; vp[2] = bp[-7]; vp[3] = bp[-6]; vp[4] = bp[-5]; vp[5] = bp[-4]; vp[6] = bp[-3]; vp[7] = bp[-2]; vp[8] = bp[-1]; bp = vp + XTRASAMPS; i -= bufsize - XTRASAMPS; } last4 = bp[-8]; last3 = bp[-7]; last2 = bp[-6]; last1 = bp[-5]; current = bp[-4]; next1 = bp[-3]; next2 = bp[-2]; next3 = bp[-1]; next4 = bp[0]; sinccurrent = SINC[4] * current; os1= fabsf(SINC[0] * last4 + SINC[1] * last3 + SINC[2] * last2 + SINC[3] * last1 + sinccurrent + SINC[5] * next1 + SINC[6] * next2 + SINC[7] * next3 + SINC[8] * next4); os2= fabsf(SINC[0] * next4 + SINC[1] * next3 + SINC[2] * next2 + SINC[3] * next1 + sinccurrent + SINC[5] * last1 + SINC[6] * last2 + SINC[7] * last3 + SINC[8] * last4); max = fabsf(current); #if 0 if(max>1. || os1>1. || os2>1.) post("%f %f %f\t%f %f %f %f %f %f %f %f %f", max, os1, os2, last4, last3, last2, last1, current, next1, next2, next3, next4 ); #endif if (max < os1) { max = os1; } if (max < os2) { max = os2; } *bp++ = *in++; if (*out++ < max) *(out-1) = max; } buf->buf_position = i; return (w+5); } static t_int *limiter_perform(t_int *w) { t_limiter *x=(t_limiter *)w[1]; int n = x->s_n; t_sample *in = (t_sample *)w[2]; t_sample *out= (t_sample *)w[3]; t_limctl *v1 = (t_limctl *)(x->val1); t_limctl *v2 = (t_limctl *)(x->val2); t_cmpctl *c = (t_cmpctl *)(x->cmp); /* now let's make things a little bit faster */ /* these MUST NOT be changed by process */ const t_float limit = v1->limit; const t_float holdlong = v1->hold_samples; const t_float coa_long = v1->change_of_amplification; const t_float alimit = v2->limit; const t_float holdshort = v2->hold_samples; const t_float coa_short = v2->change_of_amplification; t_float tresh = c->treshold; t_float uclimit = c->uclimit; t_float climit_inv = c->climit_inverse; t_float oneminusratio = c->oneminusratio; /* these will be changed by process */ t_float amp = x->amplification; t_float samplesleft = x->samples_left; t_float stillleft = x->still_left; /* an intern variable... */ t_float max_val; switch (x->mode) { case LIMIT0: while (n--) { max_val = *in; /* the MAIN routine for the 1-treshold-limiter */ if ((max_val * amp) > limit) { amp = limit / max_val; samplesleft = holdlong; } else { if (samplesleft > 0) { samplesleft--; } else { if ((amp *= coa_long) > 1) amp = 1; } } *out++ = amp; *in++ = 0; } break; case LIMIT1: while (n--) { max_val = *in; /* the main routine 2 */ if ((max_val * amp) > limit) { samplesleft = ((amp = (limit / max_val)) < alimit)?holdshort:holdlong; stillleft = holdlong; } else { if (samplesleft > 0) { samplesleft--; stillleft--; } else { if (amp < alimit) { if ((amp *= coa_short) > 1) amp = 1; } else { if (stillleft > 0) { samplesleft = stillleft; } else { if ((amp *= coa_long) > 1) amp = 1; } } } } *out++ = amp; *in++ = 0; } x->still_left = stillleft; break; case COMPRESS: while (n--) { max_val = *in; /* the MAIN routine for the compressor (very similar to the 1-treshold-limiter) */ if (max_val * amp > tresh) { amp = tresh / max_val; samplesleft = holdlong; } else if (samplesleft > 0) samplesleft--; else if ((amp *= coa_long) > 1) amp = 1; if (amp < 1.) if (amp > uclimit) /* amp is still UnCompressed uclimit==limitIN/tresh; */ *out++ = pow(amp, oneminusratio); else *out++ = amp * climit_inv; /* amp must fit for limiting : amp(new) = limit/maxval; = amp(old)*limitOUT/tresh; */ else *out++ = 1.; *in++ = 0.; } break; default: while (n--) *out++ = *in++ = 0.; break; } /* now return the goodies */ x->amplification = amp; x->samples_left = samplesleft; return (w+4); } static void limiter_dsp(t_limiter *x, t_signal **sp) { int i = 0; t_sample* sig_buf = (t_sample *)getbytes(sizeof(*sig_buf) * sp[0]->s_n); x->s_n = sp[0]->s_n; if (x->amplification == 0) x->amplification = 0.0000001; if (x->val2->limit >= 1) x->mode = 0; while (i < x->number_of_inlets) { dsp_add(oversampling_maxima, 4, x, &(x->in[i]), sp[i]->s_vec, sig_buf); i++; } dsp_add(limiter_perform, 3, x, sig_buf, sp[i]->s_vec); } /* ------------------------------------------------------------------------------------ */ /* finally do the creation - things */ static void *limiter_new(t_symbol *s, int argc, t_atom *argv) { t_limiter *x = (t_limiter *)pd_new(limiter_class); int i = 0; if (argc) set_bufsize(x, atom_getfloat(argv)); else { argc = 1; set_bufsize(x, 0); } if (argc > 64) argc=64; if (argc == 0) argc=1; x->number_of_inlets = argc--; while (argc--) { inlet_new(&x->x_obj, &x->x_obj.ob_pd, gensym("signal"), gensym("signal")); } outlet_new(&x->x_obj, gensym("signal")); x->in = (t_inbuf*)getbytes(sizeof(t_inbuf) * x->number_of_inlets); while (i < x->number_of_inlets) { int n; t_sample* buf = (t_sample *)getbytes(sizeof(*buf) * x->buf_size); x->in[i].ringbuf = buf; x->in[i].buf_position = 0; for (n = 0; n < x->buf_size; n++) x->in[i].ringbuf[n] = 0.; i++; } x->val1 = (t_limctl *)getbytes(sizeof(t_limctl)); x->val2 = (t_limctl *)getbytes(sizeof(t_limctl)); x->cmp = (t_cmpctl *)getbytes(sizeof(t_cmpctl)); x->cmp->ratio = 1.; x->cmp->treshold = 1; set1(x, 100, 30, 139); set2(x, 110, 5, 14.2); x->amplification= 1; x->samples_left = x->still_left = x->mode = 0; return (x); } static void limiter_free(t_limiter *x) { int i=0; freebytes(x->val1, sizeof(t_limctl)); freebytes(x->val2, sizeof(t_limctl)); freebytes(x->cmp , sizeof(t_cmpctl)); while (i < x->number_of_inlets) freebytes(x->in[i++].ringbuf, x->buf_size * sizeof(t_sample)); freebytes(x->in, x->number_of_inlets * sizeof(t_inbuf)); } /* ------------------------------------------------------------------------------------ */ /* ------------------------------------------------------------------------------------ */ void limiter_tilde_setup(void) { init_sinc(); limiter_class = class_new(gensym("limiter~"), (t_newmethod)limiter_new, (t_method)limiter_free, sizeof(t_limiter), 0, A_GIMME, 0); class_addmethod(limiter_class, nullfn, gensym("signal"), 0); class_addmethod(limiter_class, (t_method)limiter_dsp, gensym("dsp"), 0); class_addmethod(limiter_class, (t_method)limiter_tilde_helper, gensym("help"), 0); class_addmethod(limiter_class, (t_method)status, gensym("print"), 0); class_addmethod(limiter_class, (t_method)set_mode, gensym("mode"), A_FLOAT, 0); class_addmethod(limiter_class, (t_method)set_LIMIT, gensym("LIMIT"), 0); class_addmethod(limiter_class, (t_method)set_CRACK, gensym("CRACK"), 0); class_addmethod(limiter_class, (t_method)set_COMPRESS, gensym("COMPRESS"), 0); class_addmethod(limiter_class, (t_method)set_treshold, gensym("tresh"), A_FLOAT, 0); class_addmethod(limiter_class, (t_method)set_treshold, gensym("treshold"), A_FLOAT, 0); class_addmethod(limiter_class, (t_method)set_ratio, gensym("ratio"), A_FLOAT, 0); class_addmethod(limiter_class, (t_method)set1, gensym("set"), A_FLOAT, A_FLOAT, A_FLOAT, 0); class_addmethod(limiter_class, (t_method)set2, gensym("set2"), A_FLOAT, A_FLOAT, A_FLOAT, 0); class_addmethod(limiter_class, (t_method)set_compressor,gensym("compress"), A_FLOAT, A_FLOAT, A_FLOAT, 0); class_addmethod(limiter_class, (t_method)set_limits, gensym("limits"), A_FLOAT, A_FLOAT, 0); class_addmethod(limiter_class, (t_method)set_limit, gensym("limit"), A_FLOAT, 0); class_addfloat (limiter_class, set_limit); class_addmethod(limiter_class, (t_method)reset, gensym("reset"), 0); zexy_register("limiter~"); }