[vlc-devel] Dynamic range compressor patch
Laurent Aimar
fenrir at elivagar.org
Thu Jun 24 21:05:42 CEST 2010
On Mon, Jun 21, 2010 at 11:28:55PM -0500, Ron Wright wrote:
> +/*****************************************************************************
> + * 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;
I see what you need it but I find it really really ugly. Dunno how to
avoid that. Anyone with an idea ?
> + struct filter_sys_t *p_sys;
> + float amp;
> + float *as = NULL;
Useless init.
> + unsigned int count;
> + float env;
> + float env_peak;
> + float env_rms;
> + float gain;
> + float gain_t;
> + rms_env *rms = NULL;
Useless init.
> + float sum;
> +
> + sample_rate = (float)p_filter->fmt_in.audio.i_rate;
Useless cast.
> + rms = rms_env_new();
> + if ( !rms )
> + {
> + free( p_sys );
> + return VLC_ENOMEM;
> + }
> +
> + sum = 0.0f;
> + amp = 0.0f;
> + gain = 0.0f;
> + gain_t = 0.0f;
> + env = 0.0f;
> + env_rms = 0.0f;
> + env_peak = 0.0f;
> + count = 0;
> +
> + as = malloc( A_TBL * sizeof(float) );
calloc( A_TBL, sizeof(*as) )
> + if ( !as )
> + {
> + rms_env_free( rms );
> + free( p_sys );
> + return VLC_ENOMEM;
> + }
But moving as and rms into the context (instead of allocating them
independantly) would avoid thoses 2 mallocs and so simplify the code
(only one error path).
> + as[0] = 1.0f;
> + for ( i = 1; i < A_TBL; i++ ) {
> + as[i] = expf( -1.0f / ( sample_rate * (float)i / (float)A_TBL ) );
Useless float casts.
> + p_sys->amp = amp;
> + p_sys->as = as;
> + p_sys->count = count;
> + p_sys->env = env;
> + p_sys->env_peak = env_peak;
> + p_sys->env_rms = env_rms;
> + p_sys->gain = gain;
> + p_sys->gain_t = gain_t;
> + p_sys->rms = rms;
> + p_sys->sum = sum;
I think you call remove most of the local variables and directly init p_sys.
> + vlc_mutex_init( &p_sys->lock );
> +
> + p_sys->rms_peak = var_CreateGetFloat( p_aout, "compressor-rms-peak" );
> + p_sys->attack = var_CreateGetFloat( p_aout, "compressor-attack" );
> + p_sys->release = var_CreateGetFloat( p_aout, "compressor-release" );
> + p_sys->threshold = var_CreateGetFloat( p_aout, "compressor-threshold" );
> + p_sys->ratio = var_CreateGetFloat( p_aout, "compressor-ratio" );
> + p_sys->knee = var_CreateGetFloat( p_aout, "compressor-knee" );
> + p_sys->makeup_gain = var_CreateGetFloat( p_aout, "compressor-makeup-gain" );
> + p_sys->amplitude = 0;
> + p_sys->gain_red = 0;
> +
> + /* Add our own callbacks */
> + 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 );
> +
> + msg_Dbg( p_filter, "compressor successfully initialized" );
> + return VLC_SUCCESS;
> +}
> +
> +static float f_db2lin_cube( float db, filter_sys_t * p_sys )
> +{
> + float scale = ( db - DB_MIN ) * (float)LIN_TABLE_SIZE / ( DB_MAX - DB_MIN );
> + int base = f_round( scale - 0.5f );
> + float ofs = scale - base;
> + float *lin_data = p_sys->lin_data;
> +
> + if ( base < 1 )
> + {
> + return 0.0f;
> + }
> + else if ( base > LIN_TABLE_SIZE - 3 )
> + {
> + return lin_data[LIN_TABLE_SIZE - 2];
> + }
> +
> + return cube_interp( ofs, lin_data[base - 1],
> + lin_data[base],
> + lin_data[base + 1],
> + lin_data[base + 2] );
> +}
> +
> +static float f_db2lin_lerp( float db, filter_sys_t * p_sys )
> +{
> + float scale = ( db - DB_MIN ) * (float)LIN_TABLE_SIZE / ( DB_MAX - DB_MIN );
> + int base = f_round( scale - 0.5f );
> + float ofs = scale - base;
> + float *lin_data = p_sys->lin_data;
> +
> + if ( base < 1 )
> + {
> + return 0.0f;
> + }
> + else if ( base > LIN_TABLE_SIZE - 3 )
> + {
> + return lin_data[LIN_TABLE_SIZE - 2];
> + }
> +
> + return ( 1.0f - ofs ) * lin_data[base] + ofs * lin_data[base + 1];
> +}
f_db2lin_cube and f_db2lin_lerp could be merged and simply use the define for
the only specific line.
> +static float f_lin2db_cube( float lin, filter_sys_t * p_sys )
> +{
> + float scale = ( lin - LIN_MIN ) * (float)DB_TABLE_SIZE /
> + ( LIN_MAX - LIN_MIN );
> + int base = f_round( scale - 0.5f );
> + float ofs = scale - base;
> + float *db_data = p_sys->db_data;
> +
> + if ( base < 2 )
> + {
> + return db_data[2] * scale * 0.5f - 23.0f * ( 2.0f - scale );
> + }
> + else if ( base > DB_TABLE_SIZE - 3 )
> + {
> + return db_data[DB_TABLE_SIZE - 2];
> + }
> +
> + return cube_interp( ofs, db_data[base - 1],
> + db_data[base],
> + db_data[base + 1],
> + db_data[base + 2] );
> +}
> +
> +static float f_lin2db_lerp( float lin, filter_sys_t * p_sys )
> +{
> + float scale = ( lin - LIN_MIN ) * (float)DB_TABLE_SIZE /
> + ( LIN_MAX - LIN_MIN );
> + int base = f_round( scale - 0.5f );
> + float ofs = scale - base;
> + float *db_data = p_sys->db_data;
> +
> + if ( base < 2 )
> + {
> + return db_data[2] * scale * 0.5f - 23.0f * ( 2.0f - scale );
> + }
> + else if ( base > DB_TABLE_SIZE - 2 )
> + {
> + return db_data[DB_TABLE_SIZE - 1];
> + }
> +
> + return ( 1.0f - ofs ) * db_data[base] + ofs * db_data[base + 1];
> +}
Same here (dunno if the -2 vs -3 is a bug or not, it doesn't match
f_db2lin_cube vs f_db2lin_lerp).
> +static void round_to_zero( volatile float *f )
> +{
> + *f += 1e-18;
> + *f -= 1e-18;
> +}
I don't think volatile is needed here.
Btw, what do you want to do with it ? Is it to remove not normalized number ?
> +static float f_max( float x, float a )
> +{
> + x -= a;
> + x += fabs( x );
> + x *= 0.5;
> + x += a;
> +
> + return x;
> +}
Is it really a max like function ?
> +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_out = (float*)p_in_buf->p_buffer;
> + float *p_in = (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_t, 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;
As you made a copy of all the variable protected by the lock, you could
release it here (instead of later). It is always good to minimized the time
you hold a lock when possible.
> +
> + 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_t = p_sys->gain_t;
I would prefer if you don't use _t for a variable.
> + rms = p_sys->rms;
> + sum = p_sys->sum;
> +
> + ga = attack < 2.0f ? 0.0f
> + : as[f_round( attack * 0.001f * (float)( A_TBL - 1 ) )];
> + gr = as[f_round( release * 0.001f * (float)( A_TBL - 1 ) )];
Are thoses as[] accesses guaranted to be insided bound ? (besides, with float
I am a bit paranoiac).
> + 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++ )
> + {
> + float lev_in = fabs( p_in[0] );
> + for( pos_chan = 1; pos_chan < i_channels; pos_chan++ )
> + {
> + lev_in = f_max( lev_in, fabs( p_in[pos_chan] ) );
> + }
> + sum += lev_in * lev_in;
> +
> + if ( amp > env_rms )
> + {
> + env_rms = env_rms * ga + amp * ( 1.0f - ga );
> + }
> + else
> + {
> + env_rms = env_rms * gr + amp * ( 1.0f - gr );
> + }
> + round_to_zero( &env_rms );
> + if ( lev_in > env_peak )
> + {
> + env_peak = env_peak * ga + lev_in * ( 1.0f - ga );
> + }
> + else
> + {
> + env_peak = env_peak * gr + lev_in * ( 1.0f - gr );
> + }
> + round_to_zero( &env_peak );
> + if ( ( count++ & 3 ) == 3 )
> + {
> + amp = rms_env_process( rms, sum * 0.25f );
> + sum = 0.0f;
> + if ( isnan( env_rms ) )
> + {
> + /* This can happen sometimes, but I don't know why */
> + env_rms = 0.0f;
Maybe because your env_rms equation diverges (easy with float) or one
of the other numbers used is a NaN.
> + }
> +
> + env = LIN_INTERP( rms_peak, env_rms, env_peak );
> +
> + if ( env <= knee_min )
> + {
> + gain_t = 1.0f;
> + }
> + else if ( env < knee_max )
> + {
> + const float x = -( threshold - knee
> + - lin2db( env, p_sys ) ) / knee;
> + gain_t = db2lin( -knee * rs * x * x * 0.25f, p_sys );
> + }
> + else
> + {
> + gain_t = db2lin( ( threshold - lin2db( env, p_sys ) ) * rs,
> + p_sys );
> + }
> + }
> +
> + gain = gain * ef_a + gain_t * ef_ai;
> +
> + for ( pos_chan = 0; pos_chan < i_channels; pos_chan++ )
> + {
> + p_out[pos_chan] = p_in[pos_chan] * gain * mug;
> + }
> +
> + p_in += i_channels;
> + p_out += i_channels;
You may also one only one pointer (but not needed).
> + }
> +
> + p_sys->sum = sum;
> + p_sys->amp = amp;
> + p_sys->gain = gain;
> + p_sys->gain_t = gain_t;
> + p_sys->env = env;
> + p_sys->env_rms = env_rms;
> + p_sys->env_peak = env_peak;
> + p_sys->count = count;
> +
> + p_sys->amplitude = lin2db( env, p_sys );
> + p_sys->gain_red = lin2db( gain, p_sys );
> +
> + vlc_mutex_unlock( &p_sys->lock );
> +
> + return p_in_buf;
> +}
> +
> +/*****************************************************************************
> + * Callback functions
> + *****************************************************************************/
> +
> +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;
> +
> + if ( newval.f_float < 0.0 )
> + {
> + newval.f_float = 0.0;
> + }
> + else if ( newval.f_float > 1.0 )
> + {
> + newval.f_float = 1.0;
> + }
> +
> + vlc_mutex_lock( &p_sys->lock );
> + p_sys->rms_peak = newval.f_float;
> + 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;
> +
> + if ( newval.f_float < 1.5 )
> + {
> + newval.f_float = 1.5;
> + }
> + else if ( newval.f_float > 400.0 )
> + {
> + newval.f_float = 400.0;
> + }
> +
> + vlc_mutex_lock( &p_sys->lock );
> + p_sys->attack = newval.f_float;
> + 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;
> +
> + if ( newval.f_float < 2.0 )
> + {
> + newval.f_float = 2.0;
> + }
> + else if ( newval.f_float > 800.0 )
> + {
> + newval.f_float = 800.0;
> + }
> +
> + vlc_mutex_lock( &p_sys->lock );
> + p_sys->release = newval.f_float;
> + 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;
> +
> + if ( newval.f_float < -30.0 )
> + {
> + newval.f_float = -30.0;
> + }
> + else if ( newval.f_float > 0.0 )
> + {
> + newval.f_float = 0.0;
> + }
> +
> + vlc_mutex_lock( &p_sys->lock );
> + p_sys->threshold = newval.f_float;
> + 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;
> +
> + if ( newval.f_float < 1.0 )
> + {
> + newval.f_float = 1.0;
> + }
> + else if ( newval.f_float > 20.0 )
> + {
> + newval.f_float = 20.0;
> + }
> +
> + vlc_mutex_lock( &p_sys->lock );
> + p_sys->ratio = newval.f_float;
> + 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;
> +
> + if ( newval.f_float < 1.0 )
> + {
> + newval.f_float = 1.0;
> + }
> + else if ( newval.f_float > 10.0 )
> + {
> + newval.f_float = 10.0;
> + }
> +
> + vlc_mutex_lock( &p_sys->lock );
> + p_sys->knee = newval.f_float;
> + 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;
> +
> + if ( newval.f_float < 0.0 )
> + {
> + newval.f_float = 0.0;
> + }
> + else if ( newval.f_float > 24.0 )
> + {
> + newval.f_float = 24.0;
> + }
> +
> + vlc_mutex_lock( &p_sys->lock );
> + p_sys->makeup_gain = newval.f_float;
> + vlc_mutex_unlock( &p_sys->lock );
> +
> + return VLC_SUCCESS;
> +}
Doing a small float cliping function would reduce the code (and so improve
readability) IMHO.
You may also create only one callback and check the value of psz_cmd inside
but that depends on one preferences.
Regards,
--
fenrir
More information about the vlc-devel
mailing list