[vlc-devel] Dynamic range compressor patch
Ron Wright
logiconcepts819 at gmail.com
Sun Jun 27 08:58:34 CEST 2010
Hello again,
I have updated the patches again as usual, and added a third patch that
includes the compressor module to be linked against the math library.
Moreover, I have modified the compressor code so as to add latency
compensation for RMS compression, also known as "lookahead." However, this
comes at a cost. The cost is a delay at the output that is inversely
proportional to the sample rate. If any of you folks can think of a better
way of implementing this lookahead mechanism, let me know.
Regards,
Ronald Wright
On Sat, Jun 26, 2010 at 2:51 PM, Laurent Aimar <fenrir at elivagar.org> wrote:
> On Fri, Jun 25, 2010 at 03:39:30PM -0500, Ron Wright wrote:
> > Hello,
> >
> > I have made many drastic changes in the compressor.c file and some minor
> > changes in the UI code. Here are some of the changes:
> >
> > 1. Added VLC copyright and $id$ tag to the banner.
> > 2. Made the filter structure, RMS envelope structure, and attack lookup
> table
> > contiguous in memory so that only one memory allocation and one memory
> > deallocation is needed.
> > 3. Reorganized the functions so that they don't appear out of place.
> > 4. Removed two wasteful members (amplitude and gain reduction) from the
> filter
> > structure (this is probably used to implement output meters in certain
> LADSPA
> > frontends).
> > 5. The release of the mutex lock in the DoWork function now occurs
> immediately
> > after retrieving the shared values.
> > 6. Replaced the logic in the callback functions with clipping functions.
> > 7. Removed useless casts.
> > 8. Changed default values.
> >
> > Here are some notes to take into consideration:
> >
> > 1. The f_max and f_clamp functions work as expected. Proof:
> >
> > f_max (with arguments x and a):
> >
> > If x < a, then 0.5 * ((x - a) + fabs(x - a)) + a = 0.5 * ((x - a) - (x -
> a)) +
> > a = a
> > If x >= a, then 0.5 * ((x - a) + fabs(x - a)) + a = 0.5 * ((x - a) + (x -
> a)) +
> > a = 0.5*2*(x - a) + a = x
> >
> > f_clamp (with arguments x, a, and b):
> >
> > If x < a, then 0.5 * (fabs(x - a) + a + b - fabs(x - b)) = 0.5 * (-(x -
> a) + a
> > + b + (x - b)) = 0.5 * (2*a - x + b + x - b) = a
> > If a <= x < b, then 0.5 * (fabs(x - a) + a + b - fabs(x - b)) = 0.5 * ((x
> - a)
> > + a + b + (x - b)) = 0.5 * (x + b + x - b) = x
> > If x >= b, then 0.5 * (fabs(x - a) + a + b - fabs(x - b)) = 0.5 * ((x -
> a) + a
> > + b - (x - b)) = 0.5 * (x + b - x + b) = b
> >
> > These are branchless clipping functions from Laurent de Soras.
> >
> > 2. A buffer overflow will not occur when either attack or release
> converted to
> > seconds is multiplied by (A_TBL - 1), with the resulting value used as an
> index
> > to retrieve the attack (or release) value from the lookup table. The
> maximum
> > of the maximum values of attack and release is 800, and A_TBL is 256, so
> 800 *
> > 0.001 * 255 = 204. This means that the index will neither go beyond 255
> nor
> > even reach 255. The minimum of the minimum values of attack and release
> is
> > 1.5. However, the real minimum value used in the code is 2.
> Nevertheless,
> > there will also be no buffer underflow, since this value is positive.
> Thanks,
>
>
> > 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 \
> I am not sure, but seeing that you use mathematics functions, I think you
> also
> need to modify configure.ac to link with -lm.
>
> > diff --git a/modules/audio_filter/compressor.c
> b/modules/audio_filter/compressor.c
> > new file mode 100644
> > index 0000000..daa260a
> > --- /dev/null
> > +++ b/modules/audio_filter/compressor.c
> > @@ -0,0 +1,683 @@
> >
> +/*****************************************************************************
> > + * compressor.c: dynamic range compressor, ported from SC4 plugin
> > +
> *****************************************************************************
> > + * Copyright (C) 2010 the VideoLAN team
> Please use your name instead of 'the VideoLAN team' (as the code is
> ported, dunno
> if the original copyright shouldn't be used too).
>
> > +#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 (64)
> > +
> > +#define LIN_INTERP(f,a,b) ((a) + (f) * ( (b) - (a) ))
> > +
> > +typedef struct
> > +{
> > + float buffer[RMS_BUF_SIZE];
> > + unsigned int pos;
> > + float sum;
> > +
> > +} rms_env;
> > +
> > +struct filter_sys_t
> > +{
> > + float rms_peak;
> > + float attack;
> > + float release;
> > + float threshold;
> > + float ratio;
> > + float knee;
> > + float makeup_gain;
> > +
> > + float amp;
> > + float *as;
> > + unsigned int count;
> > + float env;
> > + float env_peak;
> > + float env_rms;
> > + float gain;
> > + float gain_thres;
> > + rms_env* rms;
> > + float sum;
> > +
> > + float db_data[DB_TABLE_SIZE];
> > + float lin_data[LIN_TABLE_SIZE];
> > +
> > + vlc_mutex_t lock;
> > +};
> I meant to use directly
>
> rms_env rms;
> float as[A_TBL].
>
> to simplify the allocation.
>
> Also, if you could move the variables protected by the lock below the
> declaration of the lock, it would make it easier to maintain.
>
> >
> +/*****************************************************************************
> > + * 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 sample_rate = p_filter->fmt_in.audio.i_rate;
> > + struct filter_sys_t *p_sys;
> > + unsigned int i;
> > +
> > + 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 ) )
> > + {
> > + memcpy( &p_filter->fmt_out.audio, &p_filter->fmt_in.audio,
> > + sizeof(audio_sample_format_t) );
> p_filter->fmt_out.audio = p_filter->fmt_in.audio;
> is simpler.
>
> > + msg_Warn( p_filter, "input and output formats are not similar"
> );
> > + return VLC_EGENERIC;
> > + }
> > +
> > + p_sys = p_filter->p_sys = calloc( 1, sizeof(struct filter_sys_t)
> > + + sizeof(rms_env)
> > + + A_TBL * sizeof(float) );
> > + if( !p_sys )
> > + {
> > + return VLC_ENOMEM;
> > + }
> > + p_sys->rms = (rms_env*)( p_sys + 1 );
> > + p_sys->as = (float *)( p_sys->rms + 1 );
> See remarks above at filter_sys_t.
>
> >
> +/*****************************************************************************
> > + * 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;
> > +
> > + 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 );
> > +
> > + vlc_mutex_destroy( &p_sys->lock );
> > +
> > + 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 *p_buf = (float*)p_in_buf->p_buffer;
> > +
> > + float rms_peak, attack, release, threshold, ratio, knee,
> makeup_gain;
> > + float amp, *as, env, env_peak, env_rms, gain, gain_thres, sum;
> > + unsigned int count;
> > + rms_env *rms;
> > +
> > + float ga, gr, rs, mug, knee_min, knee_max, ef_a, ef_ai;
> > +
> > + int pos, pos_chan;
> > +
> > + /* Current configuration */
> > + struct filter_sys_t *p_sys = p_filter->p_sys;
> > +
> > + vlc_mutex_lock( &p_sys->lock );
> > +
> > + /* RMS/peak (float value) */
> > + rms_peak = p_sys->rms_peak;
> > +
> > + /* Attack time (ms) (float value) */
> > + attack = p_sys->attack;
> > +
> > + /* Release time (ms) (float value) */
> > + release = p_sys->release;
> > +
> > + /* Threshold level (dB) (float value) */
> > + threshold = p_sys->threshold;
> > +
> > + /* Ratio (n:1) (float value) */
> > + ratio = p_sys->ratio;
> > +
> > + /* Knee radius (dB) (float value) */
> > + knee = p_sys->knee;
> > +
> > + /* Makeup gain (dB) (float value) */
> > + makeup_gain = p_sys->makeup_gain;
> > +
> > + vlc_mutex_unlock( &p_sys->lock );
> > +
> > + amp = p_sys->amp;
> > + as = p_sys->as;
> > + count = p_sys->count;
> > + env = p_sys->env;
> > + env_peak = p_sys->env_peak;
> > + env_rms = p_sys->env_rms;
> > + gain = p_sys->gain;
> > + gain_thres = p_sys->gain_thres;
> > + rms = p_sys->rms;
> > + sum = p_sys->sum;
> > +
> > + ga = attack < 2.0f ? 0.0f
> > + : as[f_round( attack * 0.001f * ( A_TBL - 1 ) )];
> > + gr = as[f_round( release * 0.001f * ( A_TBL - 1 ) )];
> > + rs = ( ratio - 1.0f ) / ratio;
> > + mug = db2lin( makeup_gain, p_sys );
> > + knee_min = db2lin( threshold - knee, p_sys );
> > + knee_max = db2lin( threshold + knee, p_sys );
> > + ef_a = ga * 0.25f;
> > + ef_ai = 1.0f - ef_a;
> > + for( pos = 0; pos < i_samples; pos++ )
> If you want, you can also declare a variable inside code (we accept c99)
> or in for()
> statement.
>
> Regards,
>
> --
> fenrir
>
> _______________________________________________
> vlc-devel mailing list
> To unsubscribe or modify your subscription options:
> http://mailman.videolan.org/listinfo/vlc-devel
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20100627/7fd3e5ae/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: vlc-compressor-configure.patch
Type: text/x-patch
Size: 690 bytes
Desc: not available
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20100627/7fd3e5ae/attachment.bin>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: vlc-compressor-core.patch
Type: text/x-patch
Size: 25003 bytes
Desc: not available
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20100627/7fd3e5ae/attachment-0001.bin>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: vlc-compressor-ui.patch
Type: text/x-patch
Size: 7903 bytes
Desc: not available
URL: <http://mailman.videolan.org/pipermail/vlc-devel/attachments/20100627/7fd3e5ae/attachment-0002.bin>
More information about the vlc-devel
mailing list