/* Normalizer plugin * * Limitations: * - only AFMT_S16_LE supported * - no parameters yet => tweak the values by editing the #defines * * License: GPLv2 * Author: pl (c) 2002 and beyond... * * Sources: some ideas from volnorm plugin for xmms * * */ #define PLUGIN /* Values for AVG: * 1: uses a 1 value memory and coefficients new=a*old+b*cur (with a+b=1) * * 2: uses several samples to smooth the variations (standard weighted mean * on past samples) * * */ #define AVG 1 #include #include #include #include // for sqrt() #include "audio_out.h" #include "audio_plugin.h" #include "audio_plugin_internal.h" #include "afmt.h" static ao_info_t info = { "Volume normalizer", "volnorm", "pl ", "" }; LIBAO_PLUGIN_EXTERN(volnorm) // mul is the value by which the samples are scaled // and has to be in [MUL_MIN, MUL_MAX] #define MUL_INIT 1.0 #define MUL_MIN 0.1 #define MUL_MAX 5.0 static float mul; #if AVG==1 // "history" value of the filter static float lastavg; // SMOOTH_* must be in ]0.0, 1.0[ // The new value accounts for SMOOTH_MUL in the value and history #define SMOOTH_MUL 0.06 #define SMOOTH_LASTAVG 0.06 #elif AVG==2 // Size of the memory array // FIXME: should depend on the frequency of the data (should be a few seconds) #define NSAMPLES 128 // Indicates where to write (in 0..NSAMPLES-1) static int idx; // The array static struct { float avg; // average level of the sample int32_t len; // sample size (weight) } mem[NSAMPLES]; // If summing all the mem[].len is lower than MIN_SAMPLE_SIZE bytes, then we // choose to ignore the computed value as it's not significant enough // FIXME: should depend on the frequency of the data (0.5s maybe) #define MIN_SAMPLE_SIZE 32000 #else // Kab00m ! #error "Unknown AVG" #endif // Some limits #define MIN_S16 -32768 #define MAX_S16 32767 // "Ideal" level #define MID_S16 (MAX_S16 * 0.25) // Silence level // FIXME: should be relative to the level of the samples #define SIL_S16 (MAX_S16 * 0.01) // Local data static struct { int inuse; // This plugin is in use TRUE, FALSE int format; // sample fomat } pl_volnorm = {0, 0}; // minimal interface static int control(int cmd,void *arg){ switch(cmd){ case AOCONTROL_PLUGIN_SET_LEN: return CONTROL_OK; } return CONTROL_UNKNOWN; } // minimal interface // open & setup audio device // return: 1=success 0=fail static int init(){ switch(ao_plugin_data.format){ case(AFMT_S16_NE): break; default: fprintf(stderr,"[pl_volnorm] Audio format not yet supported.\n"); return 0; } pl_volnorm.format = ao_plugin_data.format; pl_volnorm.inuse = 1; reset(); printf("[pl_volnorm] Normalizer plugin in use.\n"); return 1; } // close plugin static void uninit(){ pl_volnorm.inuse=0; } // empty buffers static void reset(){ int i; mul = MUL_INIT; switch(ao_plugin_data.format) { case(AFMT_S16_NE): #if AVG==1 lastavg = MID_S16; #elif AVG==2 for(i=0; i < NSAMPLES; ++i) { mem[i].len = 0; mem[i].avg = 0; } idx = 0; #endif break; default: fprintf(stderr,"[pl_volnorm] internal inconsistency - bugreport !\n"); *(char *) 0 = 0; } } // processes 'ao_plugin_data.len' bytes of 'data' // called for every block of data static int play(){ switch(pl_volnorm.format){ case(AFMT_S16_NE): { #define CLAMP(x,m,M) do { if ((x)<(m)) (x) = (m); else if ((x)>(M)) (x) = (M); } while(0) int16_t* data=(int16_t*)ao_plugin_data.data; int len=ao_plugin_data.len / 2; // 16 bits samples int32_t i, tmp; float curavg, newavg; #if AVG==1 float neededmul; #elif AVG==2 float avg; int32_t totallen; #endif // Evaluate current samples average level curavg = 0.0; for (i = 0; i < len ; ++i) { tmp = data[i]; curavg += tmp * tmp; } curavg = sqrt(curavg / (float) len); // Evaluate an adequate 'mul' coefficient based on previous state, current // samples level, etc #if AVG==1 if (curavg > SIL_S16) { neededmul = MID_S16 / ( curavg * mul); mul = (1.0 - SMOOTH_MUL) * mul + SMOOTH_MUL * neededmul; // Clamp the mul coefficient CLAMP(mul, MUL_MIN, MUL_MAX); } #elif AVG==2 avg = 0.0; totallen = 0; for (i = 0; i < NSAMPLES; ++i) { avg += mem[i].avg * (float) mem[i].len; totallen += mem[i].len; } if (totallen > MIN_SAMPLE_SIZE) { avg /= (float) totallen; if (avg >= SIL_S16) { mul = (float) MID_S16 / avg; CLAMP(mul, MUL_MIN, MUL_MAX); } } #endif // Scale & clamp the samples for (i = 0; i < len ; ++i) { tmp = mul * data[i]; CLAMP(tmp, MIN_S16, MAX_S16); data[i] = tmp; } // Evaluation of newavg (not 100% accurate because of values clamping) newavg = mul * curavg; // Stores computed values for future smoothing #if AVG==1 lastavg = (1.0 - SMOOTH_LASTAVG) * lastavg + SMOOTH_LASTAVG * newavg; //printf("\rmul=%02.1f ", mul); #elif AVG==2 mem[idx].len = len; mem[idx].avg = newavg; idx = (idx + 1) % NSAMPLES; //printf("\rmul=%02.1f (%04dKiB) ", mul, totallen/1024); #endif //fflush(stdout); break; } default: return 0; } return 1; }