[vlc-devel] [PATCH 2/4] Adds the compressor module to VLC

Ronald Wright logiconcepts819 at gmail.com
Thu Jul 1 10:33:00 CEST 2010


---
 modules/LIST                      |    1 +
 modules/audio_filter/Modules.am   |    2 +
 modules/audio_filter/compressor.c |  975 +++++++++++++++++++++++++++++++++++++
 3 files changed, 978 insertions(+), 0 deletions(-)
 create mode 100644 modules/audio_filter/compressor.c

diff --git a/modules/LIST b/modules/LIST
index 820da0a..8f77c57 100644
--- a/modules/LIST
+++ b/modules/LIST
@@ -72,6 +72,7 @@ $Id$
  * chorus_flanger: variable delay audio filter
  * clone: Clone video filter
  * colorthres:  Theshold color based on similarity to reference color Video filter
+ * compressor: Dynamic range compressor
  * converter_fixed: Fixed-point audio format conversions
  * crop: Crop video filter
  * croppadd: Crop/Padd image filter
diff --git a/modules/audio_filter/Modules.am b/modules/audio_filter/Modules.am
index eae73fc..7767cbe 100644
--- a/modules/audio_filter/Modules.am
+++ b/modules/audio_filter/Modules.am
@@ -1,5 +1,6 @@
 SUBDIRS = channel_mixer converter resampler spatializer
 SOURCES_equalizer = equalizer.c equalizer_presets.h
+SOURCES_compressor = compressor.c
 SOURCES_normvol = normvol.c
 SOURCES_audiobargraph_a = audiobargraph_a.c
 SOURCES_param_eq = param_eq.c
@@ -9,6 +10,7 @@ SOURCES_chorus_flanger = chorus_flanger.c
 libvlc_LTLIBRARIES += \
 	libaudiobargraph_a_plugin.la \
 	libchorus_flanger_plugin.la \
+	libcompressor_plugin.la \
 	libequalizer_plugin.la \
 	libnormvol_plugin.la \
 	libparam_eq_plugin.la \
diff --git a/modules/audio_filter/compressor.c b/modules/audio_filter/compressor.c
new file mode 100644
index 0000000..0c60745
--- /dev/null
+++ b/modules/audio_filter/compressor.c
@@ -0,0 +1,975 @@
+/*****************************************************************************
+ * compressor.c: dynamic range compressor, ported from plugins from LADSPA SWH
+ *****************************************************************************
+ * Copyright (C) 2010 Ronald Wright
+ * $Id$
+ *
+ * Author: Ronald Wright <logiconcepts819 at gmail.com>
+ * Original author: Steve Harris <steve at plugin.org.uk>
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+/*****************************************************************************
+ * Preamble
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#include <math.h>
+#include <stdint.h>
+
+#include <vlc_common.h>
+#include <vlc_plugin.h>
+
+#include <vlc_aout.h>
+#include <vlc_filter.h>
+
+/*****************************************************************************
+* Local prototypes.
+*****************************************************************************/
+
+#define A_TBL (256)
+
+#define DB_TABLE_SIZE   (1024)
+#define DB_MIN          (-60.0f)
+#define DB_MAX          (24.0f)
+#define LIN_TABLE_SIZE  (1024)
+#define LIN_MIN         (0.0000000002f)
+#define LIN_MAX         (9.0f)
+#define DB_DEFAULT_CUBE
+#define RMS_BUF_SIZE    (960)
+#define LOOKAHEAD_SIZE  ((RMS_BUF_SIZE)<<1)
+
+#define LN_2_2          (0.34657359f)  /* ln(2)/2 */
+#define PEAK_BW         (0.3f)         /* Peak EQ bandwidth (octaves) */
+#define SHELF_SLOPE     (1.5f)         /* Shelf EQ slope (arb. units) */
+
+#define LIN_INTERP(f,a,b) ((a) + (f) * ( (b) - (a) ))
+#define LIMIT(v,l,u)      (v < l ? l : ( v > u ? u : v ))
+
+typedef struct
+{
+    float        pf_buf[RMS_BUF_SIZE];
+    unsigned int i_pos;
+    unsigned int i_count;
+    float        f_sum;
+
+} rms_env;
+
+typedef struct
+{
+    struct
+    {
+        float pf_vals[AOUT_CHAN_MAX];
+        float f_lev_in;
+
+    } p_buf[LOOKAHEAD_SIZE];
+    unsigned int i_pos;
+    unsigned int i_count;
+
+} lookahead;
+
+typedef struct
+{
+    float f_a1;
+    float f_a2;
+    float f_b0;
+    float f_b1;
+    float f_b2;
+    float f_x1;
+    float f_x2;
+    float f_y1;
+    float f_y2;
+
+} biquad;
+
+struct filter_sys_t
+{
+    float f_amp;
+    float pf_as[A_TBL];
+    unsigned int i_count;
+    float f_env;
+    float f_env_peak;
+    float f_env_rms;
+    float f_gain;
+    float f_gain_out;
+    rms_env rms;
+    float f_sum;
+    lookahead la;
+
+    float pf_db_data[DB_TABLE_SIZE];
+    float pf_lin_data[LIN_TABLE_SIZE];
+
+    biquad p_filters[3 * AOUT_CHAN_MAX];
+
+    vlc_mutex_t lock;
+
+    float f_lo_pregain;
+    float f_mid_pregain;
+    float f_hi_pregain;
+    float f_rms_peak;
+    float f_attack;
+    float f_release;
+    float f_threshold;
+    float f_ratio;
+    float f_knee;
+    float f_makeup_gain;
+};
+
+typedef union
+{
+    float f;
+    int32_t i;
+
+} ls_pcast32;
+
+static int      Open            ( vlc_object_t * );
+static void     Close           ( vlc_object_t * );
+static block_t *DoWork          ( filter_t *, block_t * );
+
+static void     DbInit          ( filter_sys_t * );
+static float    Db2Lin          ( float, filter_sys_t * );
+static float    Lin2Db          ( float, filter_sys_t * );
+#ifdef DB_DEFAULT_CUBE
+static float    CubeInterp      ( const float, const float, const float,
+                                  const float, const float );
+#endif
+static float    FlushToZero     ( float );
+static void     RoundToZero     ( float * );
+static float    Max             ( float, float );
+static float    Clamp           ( float, float, float );
+static int      Round           ( float );
+static float    RmsEnvProcess   ( rms_env *, const float );
+static void     BufferProcess   ( float *, int, float, float, lookahead * );
+static void     EqSetParams     ( biquad *, float, float, float, float );
+static void     HsSetParams     ( biquad *, float, float, float, float );
+static float    BiquadRun       ( biquad *, const float );
+
+static int LoPregainCallback    ( vlc_object_t *, char const *, vlc_value_t,
+                                  vlc_value_t, void * );
+static int MidPregainCallback   ( vlc_object_t *, char const *, vlc_value_t,
+                                  vlc_value_t, void * );
+static int HiPregainCallback    ( vlc_object_t *, char const *, vlc_value_t,
+                                  vlc_value_t, void * );
+static int RMSPeakCallback      ( vlc_object_t *, char const *, vlc_value_t,
+                                  vlc_value_t, void * );
+static int AttackCallback       ( vlc_object_t *, char const *, vlc_value_t,
+                                  vlc_value_t, void * );
+static int ReleaseCallback      ( vlc_object_t *, char const *, vlc_value_t,
+                                  vlc_value_t, void * );
+static int ThresholdCallback    ( vlc_object_t *, char const *, vlc_value_t,
+                                  vlc_value_t, void * );
+static int RatioCallback        ( vlc_object_t *, char const *, vlc_value_t,
+                                  vlc_value_t, void * );
+static int KneeCallback         ( vlc_object_t *, char const *, vlc_value_t,
+                                  vlc_value_t, void * );
+static int MakeupGainCallback   ( vlc_object_t *, char const *, vlc_value_t,
+                                  vlc_value_t, void * );
+
+/*****************************************************************************
+ * Module descriptor
+ *****************************************************************************/
+
+#define LO_TEXT N_( "Lo pre-gain" )
+#define LO_LONGTEXT N_( "Set the low pre-gain in dB (-70 ... 6)." )
+
+#define MID_TEXT N_( "Mid pre-gain" )
+#define MID_LONGTEXT N_( "Set the mid pre-gain in dB (-70 ... 6)." )
+
+#define HI_TEXT N_( "Hi pre-gain" )
+#define HI_LONGTEXT N_( "Set the high pre-gain in dB (-70 ... 6)." )
+
+#define RMS_PEAK_TEXT N_( "RMS/peak" )
+#define RMS_PEAK_LONGTEXT N_( "Set the RMS/peak (0 ... 1)." )
+
+#define ATTACK_TEXT N_( "Attack time" )
+#define ATTACK_LONGTEXT N_( \
+        "Set the attack time in milliseconds (1.5 ... 400)." )
+
+#define RELEASE_TEXT N_( "Release time" )
+#define RELEASE_LONGTEXT N_( \
+        "Set the release time in milliseconds (2 ... 800)." )
+
+#define THRESHOLD_TEXT N_( "Threshold level" )
+#define THRESHOLD_LONGTEXT N_( "Set the threshold level in dB (-30 ... 0)." )
+
+#define RATIO_TEXT N_( "Ratio" )
+#define RATIO_LONGTEXT N_( "Set the ratio (n:1) (1 ... 20)." )
+
+#define KNEE_TEXT N_( "Knee radius" )
+#define KNEE_LONGTEXT N_( "Set the knee radius in dB (1 ... 10)." )
+
+#define MAKEUP_GAIN_TEXT N_( "Makeup gain" )
+#define MAKEUP_GAIN_LONGTEXT N_( "Set the makeup gain in dB (0 ... 24)." )
+
+vlc_module_begin()
+    set_shortname( _("Compressor") )
+    set_description( _("Dynamic range compressor") )
+    set_capability( "audio filter", 1 )
+    set_category( CAT_AUDIO )
+    set_subcategory( SUBCAT_AUDIO_AFILTER )
+
+    add_float( "compressor-lo-pregain", 0.0, NULL, LO_TEXT,
+               LO_LONGTEXT, false )
+    add_float( "compressor-mid-pregain", 0.0, NULL, MID_TEXT,
+               MID_LONGTEXT, false )
+    add_float( "compressor-hi-pregain", 0.0, NULL, HI_TEXT,
+               HI_LONGTEXT, false )
+    add_float( "compressor-rms-peak", 0.0, NULL, RMS_PEAK_TEXT,
+               RMS_PEAK_LONGTEXT, false )
+    add_float( "compressor-attack", 25.0, NULL, ATTACK_TEXT,
+               ATTACK_LONGTEXT, false )
+    add_float( "compressor-release", 100.0, NULL, RELEASE_TEXT,
+               RELEASE_LONGTEXT, false )
+    add_float( "compressor-threshold", -11.0, NULL, THRESHOLD_TEXT,
+               THRESHOLD_LONGTEXT, false )
+    add_float( "compressor-ratio", 8.0, NULL, RATIO_TEXT,
+               RATIO_LONGTEXT, false )
+    add_float( "compressor-knee", 2.5, NULL, KNEE_TEXT,
+               KNEE_LONGTEXT, false )
+    add_float( "compressor-makeup-gain", 7.0, NULL, MAKEUP_GAIN_TEXT,
+               MAKEUP_GAIN_LONGTEXT, false )
+    set_callbacks( Open, Close )
+    add_shortcut( "compressor" )
+vlc_module_end ()
+
+/*****************************************************************************
+ * Open: initialize interface
+ *****************************************************************************/
+
+static int Open( vlc_object_t *p_this )
+{
+    filter_t *p_filter = (filter_t*)p_this;
+    vlc_object_t *p_aout = p_filter->p_parent;
+    float f_sample_rate = p_filter->fmt_in.audio.i_rate;
+    struct filter_sys_t *p_sys;
+    float f_num;
+
+    if( p_filter->fmt_in.audio.i_format != VLC_CODEC_FL32 ||
+        p_filter->fmt_out.audio.i_format != VLC_CODEC_FL32 )
+    {
+        p_filter->fmt_in.audio.i_format = VLC_CODEC_FL32;
+        p_filter->fmt_out.audio.i_format = VLC_CODEC_FL32;
+        msg_Warn( p_filter, "bad input or output format" );
+        return VLC_EGENERIC;
+    }
+    if( !AOUT_FMTS_SIMILAR( &p_filter->fmt_in.audio,
+                            &p_filter->fmt_out.audio ) )
+    {
+        p_filter->fmt_out.audio = p_filter->fmt_in.audio;
+        msg_Warn( p_filter, "input and output formats are not similar" );
+        return VLC_EGENERIC;
+    }
+
+    /* Initialize the filter parameter structure */
+    p_sys = p_filter->p_sys = calloc( 1, sizeof(*p_sys) );
+    if( !p_sys )
+    {
+        return VLC_ENOMEM;
+    }
+
+    /* Initialize the attack lookup table */
+    p_sys->pf_as[0] = 1.0f;
+    for( int i = 1; i < A_TBL; i++ )
+    {
+        p_sys->pf_as[i] = expf( -1.0f / ( f_sample_rate * i / A_TBL ) );
+    }
+
+    /* Calculate the RMS and lookahead sizes from the sample rate */
+    f_num = 0.01f * f_sample_rate;
+    p_sys->rms.i_count = Round( Clamp( 0.5f * f_num, 1.0f, RMS_BUF_SIZE ) );
+    p_sys->la.i_count = Round( Clamp( f_num, 1.0f, LOOKAHEAD_SIZE ) );
+
+    /* Initialize decibel lookup tables */
+    DbInit( p_sys );
+
+    /* Restore the last saved settings */
+    p_sys->f_lo_pregain  =
+           var_CreateGetFloat( p_aout, "compressor-lo-pregain" );
+    p_sys->f_mid_pregain =
+           var_CreateGetFloat( p_aout, "compressor-mid-pregain" );
+    p_sys->f_hi_pregain  =
+           var_CreateGetFloat( p_aout, "compressor-hi-pregain" );
+    p_sys->f_rms_peak    = var_CreateGetFloat( p_aout, "compressor-rms-peak" );
+    p_sys->f_attack      = var_CreateGetFloat( p_aout, "compressor-attack" );
+    p_sys->f_release     = var_CreateGetFloat( p_aout, "compressor-release" );
+    p_sys->f_threshold   = var_CreateGetFloat( p_aout, "compressor-threshold" );
+    p_sys->f_ratio       = var_CreateGetFloat( p_aout, "compressor-ratio" );
+    p_sys->f_knee        = var_CreateGetFloat( p_aout, "compressor-knee" );
+    p_sys->f_makeup_gain =
+           var_CreateGetFloat( p_aout, "compressor-makeup-gain" );
+
+    /* Initialize the mutex */
+    vlc_mutex_init( &p_sys->lock );
+
+    /* Add our own callbacks */
+    var_AddCallback( p_aout, "compressor-lo-pregain", LoPregainCallback,
+                     p_sys );
+    var_AddCallback( p_aout, "compressor-mid-pregain", MidPregainCallback,
+                     p_sys );
+    var_AddCallback( p_aout, "compressor-hi-pregain", HiPregainCallback,
+                     p_sys );
+    var_AddCallback( p_aout, "compressor-rms-peak", RMSPeakCallback, p_sys );
+    var_AddCallback( p_aout, "compressor-attack", AttackCallback, p_sys );
+    var_AddCallback( p_aout, "compressor-release", ReleaseCallback, p_sys );
+    var_AddCallback( p_aout, "compressor-threshold", ThresholdCallback, p_sys );
+    var_AddCallback( p_aout, "compressor-ratio", RatioCallback, p_sys );
+    var_AddCallback( p_aout, "compressor-knee", KneeCallback, p_sys );
+    var_AddCallback( p_aout, "compressor-makeup-gain", MakeupGainCallback,
+                     p_sys );
+
+    /* Set the filter function */
+    p_filter->pf_audio_filter = DoWork;
+
+    /* At this stage, we are ready! */
+    msg_Dbg( p_filter, "compressor successfully initialized" );
+    return VLC_SUCCESS;
+}
+
+/*****************************************************************************
+ * Close: destroy interface
+ *****************************************************************************/
+
+static void Close( vlc_object_t *p_this )
+{
+    filter_t *p_filter = (filter_t*)p_this;
+    vlc_object_t *p_aout = p_filter->p_parent;
+    struct filter_sys_t *p_sys = p_filter->p_sys;
+
+    /* Remove our callbacks */
+    var_DelCallback( p_aout, "compressor-rms-peak", RMSPeakCallback, p_sys );
+    var_DelCallback( p_aout, "compressor-attack", AttackCallback, p_sys );
+    var_DelCallback( p_aout, "compressor-release", ReleaseCallback, p_sys );
+    var_DelCallback( p_aout, "compressor-threshold", ThresholdCallback, p_sys );
+    var_DelCallback( p_aout, "compressor-ratio", RatioCallback, p_sys );
+    var_DelCallback( p_aout, "compressor-knee", KneeCallback, p_sys );
+    var_DelCallback( p_aout, "compressor-makeup-gain", MakeupGainCallback,
+                     p_sys );
+
+    /* Destroy the mutex */
+    vlc_mutex_destroy( &p_sys->lock );
+
+    /* Destroy the filter parameter structure */
+    free( p_sys );
+}
+
+/*****************************************************************************
+ * DoWork: process samples buffer
+ *****************************************************************************/
+
+static block_t * DoWork( filter_t * p_filter, block_t * p_in_buf )
+{
+    int i_samples = p_in_buf->i_nb_samples;
+    int i_channels = aout_FormatNbChannels( &p_filter->fmt_in.audio );
+    float *pf_buf = (float*)p_in_buf->p_buffer;
+    float f_sample_rate = p_filter->fmt_in.audio.i_rate;
+
+    float f_lo_pregain;
+    float f_mid_pregain;
+    float f_hi_pregain;
+    float f_rms_peak;
+    float f_attack;
+    float f_release;
+    float f_threshold;
+    float f_ratio;
+    float f_knee;
+    float f_makeup_gain;
+
+    float f_amp;
+    float *pf_as;
+    float f_env;
+    float f_env_peak;
+    float f_env_rms;
+    float f_gain;
+    float f_gain_out;
+    rms_env *p_rms;
+    float f_sum;
+    lookahead *p_la;
+    biquad *p_filters;
+
+    float f_ga;
+    float f_gr;
+    float f_rs;
+    float f_mug;
+    float f_knee_min;
+    float f_knee_max;
+    float f_ef_a;
+    float f_ef_ai;
+
+    /* Current parameters */
+    struct filter_sys_t *p_sys = p_filter->p_sys;
+
+    /* Fetch the configurable parameters */
+    vlc_mutex_lock( &p_sys->lock );
+
+    f_lo_pregain  = p_sys->f_lo_pregain;   /* Lo pre-gain (dB) */
+    f_mid_pregain = p_sys->f_mid_pregain;  /* Mid pre-gain (dB) */
+    f_hi_pregain  = p_sys->f_hi_pregain;   /* High pre-gain (dB) */
+    f_rms_peak    = p_sys->f_rms_peak;     /* RMS/peak */
+    f_attack      = p_sys->f_attack;       /* Attack time (ms) */
+    f_release     = p_sys->f_release;      /* Release time (ms) */
+    f_threshold   = p_sys->f_threshold;    /* Threshold level (dB) */
+    f_ratio       = p_sys->f_ratio;        /* Ratio (n:1) */
+    f_knee        = p_sys->f_knee;         /* Knee radius (dB) */
+    f_makeup_gain = p_sys->f_makeup_gain;  /* Makeup gain (dB) */
+
+    vlc_mutex_unlock( &p_sys->lock );
+
+    /* Fetch the internal parameters */
+    f_amp      =  p_sys->f_amp;
+    pf_as      =  p_sys->pf_as;
+    f_env      =  p_sys->f_env;
+    f_env_peak =  p_sys->f_env_peak;
+    f_env_rms  =  p_sys->f_env_rms;
+    f_gain     =  p_sys->f_gain;
+    f_gain_out =  p_sys->f_gain_out;
+    p_rms      = &p_sys->rms;
+    f_sum      =  p_sys->f_sum;
+    p_la       = &p_sys->la;
+    p_filters  =  p_sys->p_filters;
+
+    /* Prepare the equalizer filter info */
+    for( int i_chan = 0; i_chan < i_channels; i_chan++ )
+    {
+        EqSetParams( &p_filters[3 * i_chan + 0], 100.0f, f_lo_pregain,
+                     PEAK_BW, f_sample_rate );
+        EqSetParams( &p_filters[3 * i_chan + 1], 1000.0f, f_mid_pregain,
+                     PEAK_BW, f_sample_rate );
+        HsSetParams( &p_filters[3 * i_chan + 2], 10000.0f, f_hi_pregain,
+                     SHELF_SLOPE, f_sample_rate );
+    }
+
+    /* Prepare other compressor parameters */
+    f_ga       = f_attack < 2.0f ? 0.0f :
+                 pf_as[Round( f_attack  * 0.001f * ( A_TBL - 1 ) )];
+    f_gr       = pf_as[Round( f_release * 0.001f * ( A_TBL - 1 ) )];
+    f_rs       = ( f_ratio - 1.0f ) / f_ratio;
+    f_mug      = Db2Lin( f_makeup_gain, p_sys );
+    f_knee_min = Db2Lin( f_threshold - f_knee, p_sys );
+    f_knee_max = Db2Lin( f_threshold + f_knee, p_sys );
+    f_ef_a     = f_ga * 0.25f;
+    f_ef_ai    = 1.0f - f_ef_a;
+
+    /* Process the current buffer */
+    for( int i = 0; i < i_samples; i++ )
+    {
+        float f_lev_in_old, f_lev_in_new;
+
+        /* First, pass the audio through the equalizer (ported from dj_eq_1901
+         * plugin) */
+        for( int i_chan = 0; i_chan < i_channels; i_chan++ )
+        {
+            pf_buf[i_chan] = BiquadRun( &p_filters[3 * i_chan + 0],
+                                         pf_buf[i_chan] );
+            pf_buf[i_chan] = BiquadRun( &p_filters[3 * i_chan + 1],
+                                         pf_buf[i_chan] );
+            pf_buf[i_chan] = BiquadRun( &p_filters[3 * i_chan + 2],
+                                         pf_buf[i_chan] );
+        }
+
+        /* Now, compress the pre-equalized audio (ported from sc4_1882
+         * plugin with a few modifications) */
+
+        /* Fetch the old delayed buffer value */
+        f_lev_in_old = p_la->p_buf[p_la->i_pos].f_lev_in;
+
+        /* Find the peak value of current sample.  This becomes the new delayed
+         * buffer value that replaces the old one in the lookahead array */
+        f_lev_in_new = fabs( pf_buf[0] );
+        for( int i_chan = 1; i_chan < i_channels; i_chan++ )
+        {
+            f_lev_in_new = Max( f_lev_in_new, fabs( pf_buf[i_chan] ) );
+        }
+        p_la->p_buf[p_la->i_pos].f_lev_in = f_lev_in_new;
+
+        /* Add the square of the peak value to a running sum */
+        f_sum += f_lev_in_new * f_lev_in_new;
+
+        /* Update the RMS envelope */
+        if( f_amp > f_env_rms )
+        {
+            f_env_rms = f_env_rms * f_ga + f_amp * ( 1.0f - f_ga );
+        }
+        else
+        {
+            f_env_rms = f_env_rms * f_gr + f_amp * ( 1.0f - f_gr );
+        }
+        RoundToZero( &f_env_rms );
+
+        /* Update the peak envelope */
+        if( f_lev_in_old > f_env_peak )
+        {
+            f_env_peak = f_env_peak * f_ga + f_lev_in_old * ( 1.0f - f_ga );
+        }
+        else
+        {
+            f_env_peak = f_env_peak * f_gr + f_lev_in_old * ( 1.0f - f_gr );
+        }
+        RoundToZero( &f_env_peak );
+
+        /* Process the RMS value and update the output gain every 4 samples */
+        if( ( p_sys->i_count++ & 3 ) == 3 )
+        {
+            /* Process the RMS value by placing in the mean square value, and
+             * reset the running sum */
+            f_amp = RmsEnvProcess( p_rms, f_sum * 0.25f );
+            f_sum = 0.0f;
+            if( isnan( f_env_rms ) )
+            {
+                /* This can happen sometimes, but I don't know why. */
+                f_env_rms = 0.0f;
+            }
+
+            /* Find the superposition of the RMS and peak envelopes */
+            f_env = LIN_INTERP( f_rms_peak, f_env_rms, f_env_peak );
+
+            /* Update the output gain */
+            if( f_env <= f_knee_min )
+            {
+                /* Gain below the knee (and below the threshold) */
+                f_gain_out = 1.0f;
+            }
+            else if( f_env < f_knee_max )
+            {
+                /* Gain within the knee */
+                const float f_x = -( f_threshold
+                                   - f_knee - Lin2Db( f_env, p_sys ) ) / f_knee;
+                f_gain_out = Db2Lin( -f_knee * f_rs * f_x * f_x * 0.25f,
+                                      p_sys );
+            }
+            else
+            {
+                /* Gain above the knee (and above the threshold) */
+                f_gain_out = Db2Lin( ( f_threshold - Lin2Db( f_env, p_sys ) )
+                                     * f_rs, p_sys );
+            }
+        }
+
+        /* Find the total gain */
+        f_gain = f_gain * f_ef_a + f_gain_out * f_ef_ai;
+
+        /* Write the resulting buffer to the output */
+        BufferProcess( pf_buf, i_channels, f_gain, f_mug, p_la );
+        pf_buf += i_channels;
+    }
+
+    /* Update the internal parameters */
+    p_sys->f_sum      = f_sum;
+    p_sys->f_amp      = f_amp;
+    p_sys->f_gain     = f_gain;
+    p_sys->f_gain_out = f_gain_out;
+    p_sys->f_env      = f_env;
+    p_sys->f_env_rms  = f_env_rms;
+    p_sys->f_env_peak = f_env_peak;
+
+    return p_in_buf;
+}
+
+/*****************************************************************************
+ * Helper functions for compressor
+ *****************************************************************************/
+
+static void DbInit( filter_sys_t * p_sys )
+{
+    float *pf_lin_data = p_sys->pf_lin_data;
+    float *pf_db_data = p_sys->pf_db_data;
+
+    /* Fill linear lookup table */
+    for( int i = 0; i < LIN_TABLE_SIZE; i++ )
+    {
+        pf_lin_data[i] = powf( 10.0f, ( ( DB_MAX - DB_MIN ) *
+                   (float)i / LIN_TABLE_SIZE + DB_MIN ) / 20.0f );
+    }
+
+    /* Fill logarithmic lookup table */
+    for( int i = 0; i < DB_TABLE_SIZE; i++ )
+    {
+        pf_db_data[i] = 20.0f * log10f( ( LIN_MAX - LIN_MIN ) *
+                   (float)i / DB_TABLE_SIZE + LIN_MIN );
+    }
+}
+
+static float Db2Lin( float f_db, filter_sys_t * p_sys )
+{
+    float f_scale = ( f_db - DB_MIN ) * LIN_TABLE_SIZE / ( DB_MAX - DB_MIN );
+    int i_base = Round( f_scale - 0.5f );
+    float f_ofs = f_scale - i_base;
+    float *pf_lin_data = p_sys->pf_lin_data;
+
+    if( i_base < 1 )
+    {
+        return 0.0f;
+    }
+    else if( i_base > LIN_TABLE_SIZE - 3 )
+    {
+        return pf_lin_data[LIN_TABLE_SIZE - 2];
+    }
+
+#ifdef DB_DEFAULT_CUBE
+    return CubeInterp( f_ofs, pf_lin_data[i_base - 1],
+                              pf_lin_data[i_base],
+                              pf_lin_data[i_base + 1],
+                              pf_lin_data[i_base + 2] );
+#else
+    return ( 1.0f - f_ofs ) * pf_lin_data[i_base]
+                  + f_ofs   * pf_lin_data[i_base + 1];
+#endif
+}
+
+static float Lin2Db( float f_lin, filter_sys_t * p_sys )
+{
+    float f_scale = ( f_lin - LIN_MIN ) * DB_TABLE_SIZE / ( LIN_MAX - LIN_MIN );
+    int i_base = Round( f_scale - 0.5f );
+    float f_ofs = f_scale - i_base;
+    float *pf_db_data = p_sys->pf_db_data;
+
+    if( i_base < 2 )
+    {
+        return pf_db_data[2] * f_scale * 0.5f - 23.0f * ( 2.0f - f_scale );
+    }
+    else if( i_base > DB_TABLE_SIZE - 3 )
+    {
+        return pf_db_data[DB_TABLE_SIZE - 2];
+    }
+
+#ifdef DB_DEFAULT_CUBE
+    return CubeInterp( f_ofs, pf_db_data[i_base - 1],
+                              pf_db_data[i_base],
+                              pf_db_data[i_base + 1],
+                              pf_db_data[i_base + 2] );
+#else
+    return ( 1.0f - f_ofs ) * pf_db_data[i_base]
+                  + f_ofs   * pf_db_data[i_base + 1];
+#endif
+}
+
+#ifdef DB_DEFAULT_CUBE
+/* Cubic interpolation function */
+static float CubeInterp( const float f_fr, const float f_inm1,
+                                           const float f_in,
+                                           const float f_inp1,
+                                           const float f_inp2 )
+{
+    return f_in + 0.5f * f_fr * ( f_inp1 - f_inm1 +
+         f_fr * ( 4.0f * f_inp1 + 2.0f * f_inm1 - 5.0f * f_in - f_inp2 +
+         f_fr * ( 3.0f * ( f_in - f_inp1 ) - f_inm1 + f_inp2 ) ) );
+}
+#endif
+
+static float FlushToZero( float f_x )
+{
+    ls_pcast32 v;
+
+    v.f = f_x;
+
+    /* original: return ( v.i & 0x7f800000 ) == 0 ? 0.0f : f_x;
+     * version from Tim Blechmann */
+    return ( v.i & 0x7f800000 ) < 0x08000000 ? 0.0f : f_x;
+}
+
+/* Zero out denormals by adding and subtracting a small number, from Laurent
+ * de Soras */
+static void RoundToZero( float *pf_x )
+{
+    static const float f_anti_denormal = 1e-18;
+
+    *pf_x += f_anti_denormal;
+    *pf_x -= f_anti_denormal;
+}
+
+/* A set of branchless clipping operations from Laurent de Soras */
+
+static float Max( float f_x, float f_a )
+{
+    f_x -= f_a;
+    f_x += fabs( f_x );
+    f_x *= 0.5;
+    f_x += f_a;
+
+    return f_x;
+}
+
+static float Clamp( float f_x, float f_a, float f_b )
+{
+    const float f_x1 = fabs( f_x - f_a );
+    const float f_x2 = fabs( f_x - f_b );
+
+    f_x = f_x1 + f_a + f_b;
+    f_x -= f_x2;
+    f_x *= 0.5;
+
+    return f_x;
+}
+
+/* Round float to int using IEEE int* hack */
+static int Round( float f_x )
+{
+    ls_pcast32 p;
+
+    p.f = f_x;
+    p.f += ( 3 << 22 );
+
+    return p.i - 0x4b400000;
+}
+
+/* Calculate current level from root-mean-squared of circular buffer ("RMS") */
+static float RmsEnvProcess( rms_env * p_r, const float f_x )
+{
+    /* Remove the old term from the sum */
+    p_r->f_sum -= p_r->pf_buf[p_r->i_pos];
+
+    /* Add the new term to the sum */
+    p_r->f_sum += f_x;
+
+    /* If the sum is small enough, make it zero */
+    if( p_r->f_sum < 1.0e-6 )
+    {
+        p_r->f_sum = 0.0f;
+    }
+
+    /* Replace the old term in the array with the new one */
+    p_r->pf_buf[p_r->i_pos] = f_x;
+
+    /* Go to the next position for the next RMS calculation */
+    p_r->i_pos = ( p_r->i_pos + 1 ) % ( p_r->i_count );
+
+    /* Return the RMS value */
+    return sqrt( p_r->f_sum / p_r->i_count );
+}
+
+/* Output the compressed delayed buffer and store the current buffer.  Uses a
+ * circular array, just like the one used in calculating the RMS of the buffer
+ */
+static void BufferProcess( float * pf_buf, int i_channels, float f_gain,
+                           float f_mug, lookahead * p_la )
+{
+    /* Loop through every channel */
+    for( int i_chan = 0; i_chan < i_channels; i_chan++ )
+    {
+        float f_x = pf_buf[i_chan]; /* Current buffer value */
+
+        /* Output the compressed delayed buffer value */
+        pf_buf[i_chan] = p_la->p_buf[p_la->i_pos].pf_vals[i_chan]
+                       * f_gain * f_mug;
+
+        /* Update the delayed buffer value */
+        p_la->p_buf[p_la->i_pos].pf_vals[i_chan] = f_x;
+    }
+
+    /* Go to the next delayed buffer value for the next run */
+    p_la->i_pos = ( p_la->i_pos + 1 ) % ( p_la->i_count );
+}
+
+static void EqSetParams( biquad *p_f, float f_fc, float f_gain, float f_bw,
+                         float f_sr )
+{
+    float f_w = 2.0f * M_PI * LIMIT( f_fc, 1.0f, f_sr / 2.0f ) / f_sr;
+    float f_cw = cosf( f_w );
+    float f_sw = sinf( f_w );
+    float f_J = pow( 10.0f, f_gain * 0.025f );
+    float f_g = f_sw * sinhf( LN_2_2 * LIMIT( f_bw, 0.0001f, 4.0f )
+                                     * f_w / f_sw );
+    float f_a0r = 1.0f / ( 1.0f + ( f_g / f_J ) );
+
+    p_f->f_b0 = ( 1.0f + ( f_g * f_J ) ) * f_a0r;
+    p_f->f_b1 = ( -2.0f * f_cw ) * f_a0r;
+    p_f->f_b2 = ( 1.0f - ( f_g * f_J ) ) * f_a0r;
+    p_f->f_a1 = -( p_f->f_b1 );
+    p_f->f_a2 = ( ( f_g / f_J ) - 1.0f ) * f_a0r;
+}
+
+static void HsSetParams( biquad *p_f, float f_fc, float f_gain, float f_slope,
+                         float f_sr )
+{
+    float f_w = 2.0f * M_PI * LIMIT( f_fc, 1.0f, f_sr / 2.0f ) / f_sr;
+    float f_cw = cosf( f_w );
+    float f_sw = sinf( f_w );
+    float f_A = powf( 10.0f, f_gain * 0.025f );
+    float f_b = sqrt( ( ( 1.0f + f_A * f_A ) / LIMIT( f_slope, 0.0001f, 1.0f ) )
+                           - ( ( f_A - 1.0f ) * ( f_A - 1.0f ) ) );
+
+    float f_apc = f_cw * ( f_A + 1.0f );
+    float f_amc = f_cw * ( f_A - 1.0f );
+    float f_bs = f_b * f_sw;
+    float f_a0r = 1.0f / ( f_A + 1.0f - f_amc + f_bs );
+
+    p_f->f_b0 = f_a0r * f_A * ( f_A + 1.0f + f_amc + f_bs );
+    p_f->f_b1 = f_a0r * -2.0f * f_A * ( f_A - 1.0f + f_apc );
+    p_f->f_b2 = f_a0r * f_A * ( f_A + 1.0f + f_amc - f_bs );
+    p_f->f_a1 = f_a0r * -2.0f * ( f_A - 1.0f - f_apc );
+    p_f->f_a2 = f_a0r * ( -f_A - 1.0f + f_amc + f_bs );
+}
+
+static float BiquadRun( biquad *p_f, const float f_x )
+{
+    float f_y;
+
+    f_y = p_f->f_b0 * f_x + p_f->f_b1 * p_f->f_x1 + p_f->f_b2 * p_f->f_x2
+                          + p_f->f_a1 * p_f->f_y1 + p_f->f_a2 * p_f->f_y2;
+    f_y = FlushToZero( f_y );
+    p_f->f_x2 = p_f->f_x1;
+    p_f->f_x1 = f_x;
+    p_f->f_y2 = p_f->f_y1;
+    p_f->f_y1 = f_y;
+
+    return f_y;
+}
+
+/*****************************************************************************
+ * Callback functions
+ *****************************************************************************/
+
+static int LoPregainCallback( vlc_object_t *p_this, char const *psz_cmd,
+                              vlc_value_t oldval, vlc_value_t newval,
+                              void * p_data )
+{
+    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
+    filter_sys_t *p_sys = p_data;
+
+    vlc_mutex_lock( &p_sys->lock );
+    p_sys->f_lo_pregain = Clamp( newval.f_float, -70.0f, 6.0f );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return VLC_SUCCESS;
+}
+
+static int MidPregainCallback( vlc_object_t *p_this, char const *psz_cmd,
+                               vlc_value_t oldval, vlc_value_t newval,
+                               void * p_data )
+{
+    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
+    filter_sys_t *p_sys = p_data;
+
+    vlc_mutex_lock( &p_sys->lock );
+    p_sys->f_mid_pregain = Clamp( newval.f_float, -70.0f, 6.0f );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return VLC_SUCCESS;
+}
+
+static int HiPregainCallback( vlc_object_t *p_this, char const *psz_cmd,
+                              vlc_value_t oldval, vlc_value_t newval,
+                              void * p_data )
+{
+    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
+    filter_sys_t *p_sys = p_data;
+
+    vlc_mutex_lock( &p_sys->lock );
+    p_sys->f_hi_pregain = Clamp( newval.f_float, -70.0f, 6.0f );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return VLC_SUCCESS;
+}
+
+static int RMSPeakCallback( vlc_object_t *p_this, char const *psz_cmd,
+                            vlc_value_t oldval, vlc_value_t newval,
+                            void * p_data )
+{
+    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
+    filter_sys_t *p_sys = p_data;
+
+    vlc_mutex_lock( &p_sys->lock );
+    p_sys->f_rms_peak = Clamp( newval.f_float, 0.0f, 1.0f );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return VLC_SUCCESS;
+}
+
+static int AttackCallback( vlc_object_t *p_this, char const *psz_cmd,
+                           vlc_value_t oldval, vlc_value_t newval,
+                           void * p_data )
+{
+    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
+    filter_sys_t *p_sys = p_data;
+
+    vlc_mutex_lock( &p_sys->lock );
+    p_sys->f_attack = Clamp( newval.f_float, 1.5f, 400.0f );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return VLC_SUCCESS;
+}
+
+static int ReleaseCallback( vlc_object_t *p_this, char const *psz_cmd,
+                            vlc_value_t oldval, vlc_value_t newval,
+                            void * p_data )
+{
+    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
+    filter_sys_t *p_sys = p_data;
+
+    vlc_mutex_lock( &p_sys->lock );
+    p_sys->f_release = Clamp( newval.f_float, 2.0f, 800.0f );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return VLC_SUCCESS;
+}
+
+static int ThresholdCallback( vlc_object_t *p_this, char const *psz_cmd,
+                              vlc_value_t oldval, vlc_value_t newval,
+                              void * p_data )
+{
+    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
+    filter_sys_t *p_sys = p_data;
+
+    vlc_mutex_lock( &p_sys->lock );
+    p_sys->f_threshold = Clamp( newval.f_float, -30.0f, 0.0f );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return VLC_SUCCESS;
+}
+
+static int RatioCallback( vlc_object_t *p_this, char const *psz_cmd,
+                          vlc_value_t oldval, vlc_value_t newval,
+                          void * p_data )
+{
+    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
+    filter_sys_t *p_sys = p_data;
+
+    vlc_mutex_lock( &p_sys->lock );
+    p_sys->f_ratio = Clamp( newval.f_float, 1.0f, 20.0f );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return VLC_SUCCESS;
+}
+
+static int KneeCallback( vlc_object_t *p_this, char const *psz_cmd,
+                         vlc_value_t oldval, vlc_value_t newval,
+                         void * p_data )
+{
+    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
+    filter_sys_t *p_sys = p_data;
+
+    vlc_mutex_lock( &p_sys->lock );
+    p_sys->f_knee = Clamp( newval.f_float, 1.0f, 10.0f );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return VLC_SUCCESS;
+}
+
+static int MakeupGainCallback( vlc_object_t *p_this, char const *psz_cmd,
+                               vlc_value_t oldval, vlc_value_t newval,
+                               void * p_data )
+{
+    VLC_UNUSED(p_this); VLC_UNUSED(psz_cmd); VLC_UNUSED(oldval);
+    filter_sys_t *p_sys = p_data;
+
+    vlc_mutex_lock( &p_sys->lock );
+    p_sys->f_makeup_gain = Clamp( newval.f_float, 0.0f, 24.0f );
+    vlc_mutex_unlock( &p_sys->lock );
+
+    return VLC_SUCCESS;
+}
-- 
1.7.0.4




More information about the vlc-devel mailing list