summaryrefslogtreecommitdiff
path: root/libs/fluidsynth/src/fluid_rev.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/fluidsynth/src/fluid_rev.c')
-rw-r--r--libs/fluidsynth/src/fluid_rev.c544
1 files changed, 544 insertions, 0 deletions
diff --git a/libs/fluidsynth/src/fluid_rev.c b/libs/fluidsynth/src/fluid_rev.c
new file mode 100644
index 0000000000..166007da3f
--- /dev/null
+++ b/libs/fluidsynth/src/fluid_rev.c
@@ -0,0 +1,544 @@
+/*
+
+ Freeverb
+
+ Written by Jezar at Dreampoint, June 2000
+ http://www.dreampoint.co.uk
+ This code is public domain
+
+ Translated to C by Peter Hanappe, Mai 2001
+*/
+
+#include "fluid_rev.h"
+
+/***************************************************************
+ *
+ * REVERB
+ */
+
+/* Denormalising:
+ *
+ * According to music-dsp thread 'Denormalise', Pentium processors
+ * have a hardware 'feature', that is of interest here, related to
+ * numeric underflow. We have a recursive filter. The output decays
+ * exponentially, if the input stops. So the numbers get smaller and
+ * smaller... At some point, they reach 'denormal' level. This will
+ * lead to drastic spikes in the CPU load. The effect was reproduced
+ * with the reverb - sometimes the average load over 10 s doubles!!.
+ *
+ * The 'undenormalise' macro fixes the problem: As soon as the number
+ * is close enough to denormal level, the macro forces the number to
+ * 0.0f. The original macro is:
+ *
+ * #define undenormalise(sample) if(((*(unsigned int*)&sample)&0x7f800000)==0) sample=0.0f
+ *
+ * This will zero out a number when it reaches the denormal level.
+ * Advantage: Maximum dynamic range Disadvantage: We'll have to check
+ * every sample, expensive. The alternative macro comes from a later
+ * mail from Jon Watte. It will zap a number before it reaches
+ * denormal level. Jon suggests to run it once per block instead of
+ * every sample.
+ */
+
+# if defined(WITH_FLOATX)
+# define zap_almost_zero(sample) (((*(unsigned int*)&(sample))&0x7f800000) < 0x08000000)?0.0f:(sample)
+# else
+/* 1e-20 was chosen as an arbitrary (small) threshold. */
+#define zap_almost_zero(sample) fabs(sample)<1e-10 ? 0 : sample;
+#endif
+
+/* Denormalising part II:
+ *
+ * Another method fixes the problem cheaper: Use a small DC-offset in
+ * the filter calculations. Now the signals converge not against 0,
+ * but against the offset. The constant offset is invisible from the
+ * outside world (i.e. it does not appear at the output. There is a
+ * very small turn-on transient response, which should not cause
+ * problems.
+ */
+
+
+//#define DC_OFFSET 0
+#define DC_OFFSET 1e-8
+//#define DC_OFFSET 0.001f
+typedef struct _fluid_allpass fluid_allpass;
+typedef struct _fluid_comb fluid_comb;
+
+struct _fluid_allpass {
+ fluid_real_t feedback;
+ fluid_real_t *buffer;
+ int bufsize;
+ int bufidx;
+};
+
+void fluid_allpass_init(fluid_allpass* allpass);
+void fluid_allpass_setfeedback(fluid_allpass* allpass, fluid_real_t val);
+fluid_real_t fluid_allpass_getfeedback(fluid_allpass* allpass);
+
+static void
+fluid_allpass_setbuffer(fluid_allpass* allpass, int size)
+{
+ allpass->bufidx = 0;
+ allpass->buffer = FLUID_ARRAY(fluid_real_t,size);
+ allpass->bufsize = size;
+}
+
+static void
+fluid_allpass_release(fluid_allpass* allpass)
+{
+ FLUID_FREE(allpass->buffer);
+}
+
+void
+fluid_allpass_init(fluid_allpass* allpass)
+{
+ int i;
+ int len = allpass->bufsize;
+ fluid_real_t* buf = allpass->buffer;
+ for (i = 0; i < len; i++) {
+ buf[i] = DC_OFFSET; /* this is not 100 % correct. */
+ }
+}
+
+void
+fluid_allpass_setfeedback(fluid_allpass* allpass, fluid_real_t val)
+{
+ allpass->feedback = val;
+}
+
+fluid_real_t
+fluid_allpass_getfeedback(fluid_allpass* allpass)
+{
+ return allpass->feedback;
+}
+
+#define fluid_allpass_process(_allpass, _input) \
+{ \
+ fluid_real_t output; \
+ fluid_real_t bufout; \
+ bufout = _allpass.buffer[_allpass.bufidx]; \
+ output = bufout-_input; \
+ _allpass.buffer[_allpass.bufidx] = _input + (bufout * _allpass.feedback); \
+ if (++_allpass.bufidx >= _allpass.bufsize) { \
+ _allpass.bufidx = 0; \
+ } \
+ _input = output; \
+}
+
+/* fluid_real_t fluid_allpass_process(fluid_allpass* allpass, fluid_real_t input) */
+/* { */
+/* fluid_real_t output; */
+/* fluid_real_t bufout; */
+/* bufout = allpass->buffer[allpass->bufidx]; */
+/* undenormalise(bufout); */
+/* output = -input + bufout; */
+/* allpass->buffer[allpass->bufidx] = input + (bufout * allpass->feedback); */
+/* if (++allpass->bufidx >= allpass->bufsize) { */
+/* allpass->bufidx = 0; */
+/* } */
+/* return output; */
+/* } */
+
+struct _fluid_comb {
+ fluid_real_t feedback;
+ fluid_real_t filterstore;
+ fluid_real_t damp1;
+ fluid_real_t damp2;
+ fluid_real_t *buffer;
+ int bufsize;
+ int bufidx;
+};
+
+void fluid_comb_setbuffer(fluid_comb* comb, int size);
+void fluid_comb_release(fluid_comb* comb);
+void fluid_comb_init(fluid_comb* comb);
+void fluid_comb_setdamp(fluid_comb* comb, fluid_real_t val);
+fluid_real_t fluid_comb_getdamp(fluid_comb* comb);
+void fluid_comb_setfeedback(fluid_comb* comb, fluid_real_t val);
+fluid_real_t fluid_comb_getfeedback(fluid_comb* comb);
+
+void
+fluid_comb_setbuffer(fluid_comb* comb, int size)
+{
+ comb->filterstore = 0;
+ comb->bufidx = 0;
+ comb->buffer = FLUID_ARRAY(fluid_real_t,size);
+ comb->bufsize = size;
+}
+
+void
+fluid_comb_release(fluid_comb* comb)
+{
+ FLUID_FREE(comb->buffer);
+}
+
+void
+fluid_comb_init(fluid_comb* comb)
+{
+ int i;
+ fluid_real_t* buf = comb->buffer;
+ int len = comb->bufsize;
+ for (i = 0; i < len; i++) {
+ buf[i] = DC_OFFSET; /* This is not 100 % correct. */
+ }
+}
+
+void
+fluid_comb_setdamp(fluid_comb* comb, fluid_real_t val)
+{
+ comb->damp1 = val;
+ comb->damp2 = 1 - val;
+}
+
+fluid_real_t
+fluid_comb_getdamp(fluid_comb* comb)
+{
+ return comb->damp1;
+}
+
+void
+fluid_comb_setfeedback(fluid_comb* comb, fluid_real_t val)
+{
+ comb->feedback = val;
+}
+
+fluid_real_t
+fluid_comb_getfeedback(fluid_comb* comb)
+{
+ return comb->feedback;
+}
+
+#define fluid_comb_process(_comb, _input, _output) \
+{ \
+ fluid_real_t _tmp = _comb.buffer[_comb.bufidx]; \
+ _comb.filterstore = (_tmp * _comb.damp2) + (_comb.filterstore * _comb.damp1); \
+ _comb.buffer[_comb.bufidx] = _input + (_comb.filterstore * _comb.feedback); \
+ if (++_comb.bufidx >= _comb.bufsize) { \
+ _comb.bufidx = 0; \
+ } \
+ _output += _tmp; \
+}
+
+/* fluid_real_t fluid_comb_process(fluid_comb* comb, fluid_real_t input) */
+/* { */
+/* fluid_real_t output; */
+
+/* output = comb->buffer[comb->bufidx]; */
+/* undenormalise(output); */
+/* comb->filterstore = (output * comb->damp2) + (comb->filterstore * comb->damp1); */
+/* undenormalise(comb->filterstore); */
+/* comb->buffer[comb->bufidx] = input + (comb->filterstore * comb->feedback); */
+/* if (++comb->bufidx >= comb->bufsize) { */
+/* comb->bufidx = 0; */
+/* } */
+
+/* return output; */
+/* } */
+
+#define numcombs 8
+#define numallpasses 4
+#define fixedgain 0.015f
+#define scalewet 3.0f
+#define scaledamp 1.0f
+#define scaleroom 0.28f
+#define offsetroom 0.7f
+#define initialroom 0.5f
+#define initialdamp 0.2f
+#define initialwet 1
+#define initialdry 0
+#define initialwidth 1
+#define stereospread 23
+
+/*
+ These values assume 44.1KHz sample rate
+ they will probably be OK for 48KHz sample rate
+ but would need scaling for 96KHz (or other) sample rates.
+ The values were obtained by listening tests.
+*/
+#define combtuningL1 1116
+#define combtuningR1 (1116 + stereospread)
+#define combtuningL2 1188
+#define combtuningR2 (1188 + stereospread)
+#define combtuningL3 1277
+#define combtuningR3 (1277 + stereospread)
+#define combtuningL4 1356
+#define combtuningR4 (1356 + stereospread)
+#define combtuningL5 1422
+#define combtuningR5 (1422 + stereospread)
+#define combtuningL6 1491
+#define combtuningR6 (1491 + stereospread)
+#define combtuningL7 1557
+#define combtuningR7 (1557 + stereospread)
+#define combtuningL8 1617
+#define combtuningR8 (1617 + stereospread)
+#define allpasstuningL1 556
+#define allpasstuningR1 (556 + stereospread)
+#define allpasstuningL2 441
+#define allpasstuningR2 (441 + stereospread)
+#define allpasstuningL3 341
+#define allpasstuningR3 (341 + stereospread)
+#define allpasstuningL4 225
+#define allpasstuningR4 (225 + stereospread)
+
+struct _fluid_revmodel_t {
+ fluid_real_t roomsize;
+ fluid_real_t damp;
+ fluid_real_t wet, wet1, wet2;
+ fluid_real_t width;
+ fluid_real_t gain;
+ /*
+ The following are all declared inline
+ to remove the need for dynamic allocation
+ with its subsequent error-checking messiness
+ */
+ /* Comb filters */
+ fluid_comb combL[numcombs];
+ fluid_comb combR[numcombs];
+ /* Allpass filters */
+ fluid_allpass allpassL[numallpasses];
+ fluid_allpass allpassR[numallpasses];
+};
+
+static void fluid_revmodel_update(fluid_revmodel_t* rev);
+static void fluid_revmodel_init(fluid_revmodel_t* rev);
+void fluid_set_revmodel_buffers(fluid_revmodel_t* rev, fluid_real_t sample_rate);
+
+fluid_revmodel_t*
+new_fluid_revmodel(fluid_real_t sample_rate)
+{
+ fluid_revmodel_t* rev;
+ rev = FLUID_NEW(fluid_revmodel_t);
+ if (rev == NULL) {
+ return NULL;
+ }
+
+ fluid_set_revmodel_buffers(rev, sample_rate);
+
+ /* Set default values */
+ fluid_allpass_setfeedback(&rev->allpassL[0], 0.5f);
+ fluid_allpass_setfeedback(&rev->allpassR[0], 0.5f);
+ fluid_allpass_setfeedback(&rev->allpassL[1], 0.5f);
+ fluid_allpass_setfeedback(&rev->allpassR[1], 0.5f);
+ fluid_allpass_setfeedback(&rev->allpassL[2], 0.5f);
+ fluid_allpass_setfeedback(&rev->allpassR[2], 0.5f);
+ fluid_allpass_setfeedback(&rev->allpassL[3], 0.5f);
+ fluid_allpass_setfeedback(&rev->allpassR[3], 0.5f);
+
+ rev->gain = fixedgain;
+ fluid_revmodel_set(rev,FLUID_REVMODEL_SET_ALL,initialroom,initialdamp,initialwidth,initialwet);
+
+ return rev;
+}
+
+void
+delete_fluid_revmodel(fluid_revmodel_t* rev)
+{
+ int i;
+ for (i = 0; i < numcombs;i++) {
+ fluid_comb_release(&rev->combL[i]);
+ fluid_comb_release(&rev->combR[i]);
+ }
+ for (i = 0; i < numallpasses; i++) {
+ fluid_allpass_release(&rev->allpassL[i]);
+ fluid_allpass_release(&rev->allpassR[i]);
+ }
+
+ FLUID_FREE(rev);
+}
+
+void
+fluid_set_revmodel_buffers(fluid_revmodel_t* rev, fluid_real_t sample_rate) {
+
+ float srfactor = sample_rate/44100.0f;
+
+ fluid_comb_setbuffer(&rev->combL[0], combtuningL1*srfactor);
+ fluid_comb_setbuffer(&rev->combR[0], combtuningR1*srfactor);
+ fluid_comb_setbuffer(&rev->combL[1], combtuningL2*srfactor);
+ fluid_comb_setbuffer(&rev->combR[1], combtuningR2*srfactor);
+ fluid_comb_setbuffer(&rev->combL[2], combtuningL3*srfactor);
+ fluid_comb_setbuffer(&rev->combR[2], combtuningR3*srfactor);
+ fluid_comb_setbuffer(&rev->combL[3], combtuningL4*srfactor);
+ fluid_comb_setbuffer(&rev->combR[3], combtuningR4*srfactor);
+ fluid_comb_setbuffer(&rev->combL[4], combtuningL5*srfactor);
+ fluid_comb_setbuffer(&rev->combR[4], combtuningR5*srfactor);
+ fluid_comb_setbuffer(&rev->combL[5], combtuningL6*srfactor);
+ fluid_comb_setbuffer(&rev->combR[5], combtuningR6*srfactor);
+ fluid_comb_setbuffer(&rev->combL[6], combtuningL7*srfactor);
+ fluid_comb_setbuffer(&rev->combR[6], combtuningR7*srfactor);
+ fluid_comb_setbuffer(&rev->combL[7], combtuningL8*srfactor);
+ fluid_comb_setbuffer(&rev->combR[7], combtuningR8*srfactor);
+ fluid_allpass_setbuffer(&rev->allpassL[0], allpasstuningL1*srfactor);
+ fluid_allpass_setbuffer(&rev->allpassR[0], allpasstuningR1*srfactor);
+ fluid_allpass_setbuffer(&rev->allpassL[1], allpasstuningL2*srfactor);
+ fluid_allpass_setbuffer(&rev->allpassR[1], allpasstuningR2*srfactor);
+ fluid_allpass_setbuffer(&rev->allpassL[2], allpasstuningL3*srfactor);
+ fluid_allpass_setbuffer(&rev->allpassR[2], allpasstuningR3*srfactor);
+ fluid_allpass_setbuffer(&rev->allpassL[3], allpasstuningL4*srfactor);
+ fluid_allpass_setbuffer(&rev->allpassR[3], allpasstuningR4*srfactor);
+
+ /* Clear all buffers */
+ fluid_revmodel_init(rev);
+}
+
+
+static void
+fluid_revmodel_init(fluid_revmodel_t* rev)
+{
+ int i;
+ for (i = 0; i < numcombs;i++) {
+ fluid_comb_init(&rev->combL[i]);
+ fluid_comb_init(&rev->combR[i]);
+ }
+ for (i = 0; i < numallpasses; i++) {
+ fluid_allpass_init(&rev->allpassL[i]);
+ fluid_allpass_init(&rev->allpassR[i]);
+ }
+}
+
+void
+fluid_revmodel_reset(fluid_revmodel_t* rev)
+{
+ fluid_revmodel_init(rev);
+}
+
+void
+fluid_revmodel_processreplace(fluid_revmodel_t* rev, fluid_real_t *in,
+ fluid_real_t *left_out, fluid_real_t *right_out)
+{
+ int i, k = 0;
+ fluid_real_t outL, outR, input;
+
+ for (k = 0; k < FLUID_BUFSIZE; k++) {
+
+ outL = outR = 0;
+
+ /* The original Freeverb code expects a stereo signal and 'input'
+ * is set to the sum of the left and right input sample. Since
+ * this code works on a mono signal, 'input' is set to twice the
+ * input sample. */
+ input = (2.0f * in[k] + DC_OFFSET) * rev->gain;
+
+ /* Accumulate comb filters in parallel */
+ for (i = 0; i < numcombs; i++) {
+ fluid_comb_process(rev->combL[i], input, outL);
+ fluid_comb_process(rev->combR[i], input, outR);
+ }
+ /* Feed through allpasses in series */
+ for (i = 0; i < numallpasses; i++) {
+ fluid_allpass_process(rev->allpassL[i], outL);
+ fluid_allpass_process(rev->allpassR[i], outR);
+ }
+
+ /* Remove the DC offset */
+ outL -= DC_OFFSET;
+ outR -= DC_OFFSET;
+
+ /* Calculate output REPLACING anything already there */
+ left_out[k] = outL * rev->wet1 + outR * rev->wet2;
+ right_out[k] = outR * rev->wet1 + outL * rev->wet2;
+ }
+}
+
+void
+fluid_revmodel_processmix(fluid_revmodel_t* rev, fluid_real_t *in,
+ fluid_real_t *left_out, fluid_real_t *right_out)
+{
+ int i, k = 0;
+ fluid_real_t outL, outR, input;
+
+ for (k = 0; k < FLUID_BUFSIZE; k++) {
+
+ outL = outR = 0;
+
+ /* The original Freeverb code expects a stereo signal and 'input'
+ * is set to the sum of the left and right input sample. Since
+ * this code works on a mono signal, 'input' is set to twice the
+ * input sample. */
+ input = (2.0f * in[k] + DC_OFFSET) * rev->gain;
+
+ /* Accumulate comb filters in parallel */
+ for (i = 0; i < numcombs; i++) {
+ fluid_comb_process(rev->combL[i], input, outL);
+ fluid_comb_process(rev->combR[i], input, outR);
+ }
+ /* Feed through allpasses in series */
+ for (i = 0; i < numallpasses; i++) {
+ fluid_allpass_process(rev->allpassL[i], outL);
+ fluid_allpass_process(rev->allpassR[i], outR);
+ }
+
+ /* Remove the DC offset */
+ outL -= DC_OFFSET;
+ outR -= DC_OFFSET;
+
+ /* Calculate output MIXING with anything already there */
+ left_out[k] += outL * rev->wet1 + outR * rev->wet2;
+ right_out[k] += outR * rev->wet1 + outL * rev->wet2;
+ }
+}
+
+static void
+fluid_revmodel_update(fluid_revmodel_t* rev)
+{
+ /* Recalculate internal values after parameter change */
+ int i;
+
+ rev->wet1 = rev->wet * (rev->width / 2.0f + 0.5f);
+ rev->wet2 = rev->wet * ((1.0f - rev->width) / 2.0f);
+
+ for (i = 0; i < numcombs; i++) {
+ fluid_comb_setfeedback(&rev->combL[i], rev->roomsize);
+ fluid_comb_setfeedback(&rev->combR[i], rev->roomsize);
+ }
+
+ for (i = 0; i < numcombs; i++) {
+ fluid_comb_setdamp(&rev->combL[i], rev->damp);
+ fluid_comb_setdamp(&rev->combR[i], rev->damp);
+ }
+}
+
+/**
+ * Set one or more reverb parameters.
+ * @param rev Reverb instance
+ * @param set One or more flags from #fluid_revmodel_set_t indicating what
+ * parameters to set (#FLUID_REVMODEL_SET_ALL to set all parameters)
+ * @param roomsize Reverb room size
+ * @param damping Reverb damping
+ * @param width Reverb width
+ * @param level Reverb level
+ */
+void
+fluid_revmodel_set(fluid_revmodel_t* rev, int set, float roomsize,
+ float damping, float width, float level)
+{
+ if (set & FLUID_REVMODEL_SET_ROOMSIZE)
+ rev->roomsize = (roomsize * scaleroom) + offsetroom;
+
+ if (set & FLUID_REVMODEL_SET_DAMPING)
+ rev->damp = damping * scaledamp;
+
+ if (set & FLUID_REVMODEL_SET_WIDTH)
+ rev->width = width;
+
+ if (set & FLUID_REVMODEL_SET_LEVEL)
+ {
+ fluid_clip(level, 0.0f, 1.0f);
+ rev->wet = level * scalewet;
+ }
+
+ fluid_revmodel_update (rev);
+}
+
+void
+fluid_revmodel_samplerate_change(fluid_revmodel_t* rev, fluid_real_t sample_rate) {
+ int i;
+ for (i = 0; i < numcombs;i++) {
+ fluid_comb_release(&rev->combL[i]);
+ fluid_comb_release(&rev->combR[i]);
+ }
+ for (i = 0; i < numallpasses; i++) {
+ fluid_allpass_release(&rev->allpassL[i]);
+ fluid_allpass_release(&rev->allpassR[i]);
+ }
+ fluid_set_revmodel_buffers(rev, sample_rate);
+}