aboutsummaryrefslogtreecommitdiff
path: root/src/z_limiter.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/z_limiter.c')
-rw-r--r--src/z_limiter.c707
1 files changed, 707 insertions, 0 deletions
diff --git a/src/z_limiter.c b/src/z_limiter.c
new file mode 100644
index 0000000..9f18efe
--- /dev/null
+++ b/src/z_limiter.c
@@ -0,0 +1,707 @@
+/*
+ --------------------------------- 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 (shipped with pd)
+*/
+
+#define LIMIT0 0
+#define LIMIT1 1
+#define COMPRESS 2
+
+#include "zexy.h"
+#include <math.h>
+
+#ifdef NT
+#define fabsf fabs
+#pragma warning( disable : 4244 )
+#pragma warning( disable : 4305 )
+#endif
+
+#define LN2 .69314718056
+#define SINC1 .822462987
+#define SINC2 .404460777
+#define SINC3 -.188874003
+#define SINC4 -.143239449
+#define SINC5 .087796546
+#define SINC6 .06917082
+#define SINC7 -.041349667
+#define SINC8 -.030578954
+#define SINC9 .013226276
+
+#define BUFSIZE 128
+#define XTRASAMPS 9
+#define TABLESIZE 512 /* compressor table */
+
+/* ------------------------------------------------------------------------------------ */
+// first define the structs...
+
+static t_class *limiter_class;
+
+typedef struct _limctl
+{ // variables changed by user
+ float limit, hold_samples, change_of_amplification;
+} t_limctl;
+
+typedef struct _cmpctl
+{
+ float treshold, ratio; // uclimit is the very same is the limiter1-limit (decalculated relative to our treshold)
+ float uclimit, climit_inverse; // climit == compressed limit (uclimit == uncompressed limit)
+
+ float limiter_limit; // start limiting (stop compressing); == tresh/limit;
+
+ float treshdB, oneminusratio;
+} t_cmpctl;
+
+typedef struct _inbuf
+{
+ float* ringbuf;
+ int buf_position;
+} t_inbuf;
+
+typedef struct _limiter
+{
+ t_object x_obj;
+
+ int number_of_inlets, s_n;
+
+ // variables changed by process
+
+ float amplification;
+ 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
+ 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, float treshold)
+{
+ t_cmpctl *c = x->cmp;
+ 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, float ratio)
+{
+ if (ratio < 0) ratio = 1;
+ x->cmp->ratio = ratio;
+
+ set_uclimit(x);
+}
+
+static void set_mode(t_limiter *x, 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;
+ }
+ post("mode set to %d", x->mode);
+}
+
+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, float 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 helper(t_limiter *x)
+{
+ post("\n\n%c %d-channel limiter-object: mode %d", HEARTSYMBOL, x->number_of_inlets, x->mode);
+ poststring("\n'mode <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 <limit>'\t\t\t: set limit (in dB)"
+ "\n'set <limit><htime><rtime>'\t: set limiter");
+ break;
+ case LIMIT1:
+ poststring("\n'limits <limit1><limit2>'\t: set limits (in dB)"
+ "\n'set <limit1><htime1><rtime1>'\t: set limiter 1"
+ "\n'set2 <limit2><htime2><rtime2>'\t: set crack-limiter");
+ break;
+ case COMPRESS:
+ poststring("\n'ratio <compressratio>'\t\t: set compressratio (´0.5´ instead of ´1:2´)"
+ "\n'treshold <treshold>'\t\t: set treshold of the compressor"
+ "\n'compress <limit><treshold><ratio>'\t: set compressor"
+ "\n..........note that <limit> 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~ [<in1> [<in2> [<in3> [...]]]]\": <in*> 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_float *in = (t_float *)w[3];
+ t_float *out = (t_float *)w[4];
+
+ int n = x->s_n;
+ int bufsize = x->buf_size;
+
+ int i = buf->buf_position;
+
+ t_float *vp = buf->ringbuf, *ep = vp + bufsize, *bp = vp + XTRASAMPS + i;
+
+ i += n;
+
+ while (n--)
+ {
+ t_float os1, os2, max;
+ t_float 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;
+ }
+
+ os1= fabsf(SINC8 * (last4 = bp[-8]) +
+ SINC6 * (last3 = bp[-7]) +
+ SINC4 * (last2 = bp[-6]) +
+ SINC2 * (last1 = bp[-5]) +
+ (sinccurrent = SINC1 * (current = bp[-4])) +
+ SINC3 * (next1 = bp[-3]) +
+ SINC5 * (next2 = bp[-2]) +
+ SINC7 * (next3 = bp[-1]) +
+ SINC9 * (next4 = bp[0]));
+
+ os2= fabsf(SINC8 * next4 +
+ SINC4 * next3 +
+ SINC6 * next2 +
+ SINC2 * next1 +
+ sinccurrent +
+ SINC3 * last1 +
+ SINC5 * last2 +
+ SINC7 * last3 +
+ SINC9 * last4);
+
+ max = fabsf(current);
+
+ 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_float *in = (t_float *)w[2];
+ t_float *out= (t_float *)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);
+}
+
+
+#if 0
+static t_int *route_through(t_int *w)
+{
+ t_float *in = (t_float *)w[1];
+ t_float *out = (t_float *)w[2];
+ int n = (int)w[3];
+
+ while(n--)
+ {
+ *out++ = *in;
+ *in++ = 0;
+ }
+
+ return (w+4);
+}
+#endif
+
+void limiter_dsp(t_limiter *x, t_signal **sp)
+{
+ int i = 0;
+ t_float* sig_buf = (t_float *)getbytes(sizeof(t_float) * 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
+
+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, &s_signal, &s_signal);
+ }
+
+ outlet_new(&x->x_obj, &s_signal);
+
+ x->in = (t_inbuf*)getbytes(sizeof(t_inbuf) * x->number_of_inlets);
+ while (i < x->number_of_inlets)
+ {
+ int n;
+ t_float* buf = (float *)getbytes(sizeof(float) * 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);
+}
+
+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_float));
+
+ freebytes(x->in, x->number_of_inlets * sizeof(t_inbuf));
+}
+
+
+
+/* ------------------------------------------------------------------------------------ */
+/* ------------------------------------------------------------------------------------ */
+
+
+
+void z_limiter_setup(void)
+{
+ 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)helper, gensym("help"), 0);
+ class_addmethod(limiter_class, (t_method)status, gensym("print"), 0);
+ class_sethelpsymbol(limiter_class, gensym("zexy/limiter~"));
+
+ 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);
+
+}