From: gints@prophet.esd.sgi.com (Gints Klimanis)
Newsgroups: comp.sys.sgi
Subject: For Tom Benoist
Date: 17 Dec 1992 00:53:39 GMT
Organization: Silicon Graphics, Inc.
Distribution: world
NNTP-Posting-Host: prophet.esd.sgi.com

Your mail bounces.


In article <1goilmINNa9l@fido.asd.sgi.com>, gints@prophet.esd.sgi.com
(Gints Klimanis) writes:
|> Hi !
|> 
|> Your mail bounces.
|> /* 
|> NOTE: this is not actual apanel code.  Send questions to:
|> 
|> 	    Gints Klimanis
|> 	    gints@sgi.com
|> 
|> apanel method is a dual peak-metering scheme.  VU meter simulation
|> (signal average and signal peak) didn't "look" right, 
|> because the average values never "pushed" up the peak meters.
|> 
|> Most meters on DAT machines are dual peak meters.  The short and
|> long
|> meter intervals are a fraction of a second and one second,
|> respectively.  
|> An meter update rate (and short time interval) in excess of 15
|> renders/sec.
|> looks crisp.  If the meter intervals are measured in signal samples 
|> rather than by a timer, the interval lengths must track the sampling
|> rate so signals in lower sampling rates are not monitored with lower
|> meter update rates.
|> 
|> The apanel meters are driven by a lookup into a logarithmic table.  A
|> |> data cache miss to index this table 20 times/second is acceptable
|> in 
|> comparison to the CPU required to render the meters.
|> 
|> Apanel meters go red when the absolute value of the signal equals or
|> |> exceeds 32767, the 0dB headroom level for a sixteen-bit resolution
|> signal.
|> Since a logarithmic scale exaggerates the lower values, apanel draws
|> from -60dB to 0dB.  This prevents quiescent noise (also future system
|> dither) 
|> from  external sources from flickering the lower segments.
|> 
|> Logarithmic metering mandates DC removal  Apanel uses a simple run
|> length 
|> complementary low pass filter which computes the signal average
|> value.  
|> Since this filter  is linear phase, the filter output
|> may be subtracted from the meter peak values to determine the actual
|> meter
|> level.  Subtracting the low pass filter from the input is the
|> complement
|> of a low-pass filter operation:  a high pass filter.  Worry not
|> about
|> time alignment during the subtraction, since we can assume that the
|> DC
|> level does not change over the short time interval.  The DC level
|> must
|> be computed in real-time rather than at system login/power up since
|> the level 
|> varies from machine to machine, with temperature and the line/mic
|> input
|> attenuation.   Thus, each of the stereo input requires a separate
|> filter.
|> The mic and line inputs have different DC offsets.
|> 
|> The length of the filter should be long enough to determine the
|> average
|> signal level but short enough to minimize the settling time.  You can
|> observe
|> a meter flicker caused by this filter settling by selecting line or 
|> mic input, input attenution faders at lowest screen position and
|> switching 
|> between the two input sources.
|> 
|> The filter accumulators and the run length buffer must be cleared
|> when
|> the signal breaks.  We do this for changes in input sources 
|> and sampling rates.  An additional opportunity is when meters are
|> toggled
|> on and off. This provides recovery when different devices (with
|> potentially
|> different DC offests) are connected to the IRIS audio inputs.
|> */
|> #include "audio.h"
|> 
|> /* linear to log scale for input level meters */
|> float		levelToDecibelTable[32768];	/* can be reduced in size */
|> 
|> /* run length buffers used to compute DC offset in meter compute
|> routines */
|> /* important to keep length of run buffer small.  Need to keep it
|> large enough for an accurate measurement at the high sampling rates.
|> However, large values take a long time to settle when changing input
|> levels (thus changing the offset) and sampling rates.
|> Since it currently does not scale with sampling rate, the settling
|> time
|> is longer at the lower sampling rates. Seems to be ok.
|> */
|> short		runLengthBufferL[RUN_LENGTH_BUFFER_LENGTH];	
|> short		runLengthBufferR[RUN_LENGTH_BUFFER_LENGTH];	
|> #define RUN_LENGTH_BUFFER_INDEX_MODULO	RUN_LENGTH_BUFFER_LENGTH-1
|> unsigned long	runLengthBufferIndex;
|> 
|> long		dcOffsetL, dcOffsetR;	/* DC filter filter accumulators */
|> 
|> float		meterUpdateRate = 18.0;	/* # short time meter updates / sec
|> unsigned int	shortBlockLength;	/* length of block used for
|> 							short time peak meter */
|> 
|> #define REFERENCE_LEVEL 32767	/* largest value considered from 
|> 					2's complement 16-bit data */
|> 
|> /* stuff for meter short and long interval storage */
|> long shortBlockMinL = shortBlockMinR = 0;
|> long shortBlockMaxL = shortBlockMaxR = 0;
|> long shortBlockCounter = 0;
|> long longBlockPeakL = longBlockPeakR = 0;	
|> 					
|> /*
|>
**********************************************************************
|>  *
|>  * ComputeShortTimeMeterInterval:	compute new short time interval 
|>  *					(in samples) and clear filter
|>  *
|>
**********************************************************************
*/
|>     void 
|> ComputeShortTimeMeterInterval()
|> {
|> long	pvBuffer[2];
|> 
|> /* clear meter registers */
|>     shortBlockMinL = shortBlockMinR = 0;
|>     shortBlockMaxL = shortBlockMaxR = 0;
|>     longBlockPeakL = longBlockPeakR = 0;
|>     shortBlockCounter = 0;
|> 
|> /* shortBlockLength is inversely proportional to sampling rate */
|> /* this code asks audio hardware for sampling rate.  Otherwise,
|>     substitute your known sampling rate */
|>     pvBuffer[0] = AL_INPUT_RATE;
|>     pvBuffer[1] = 0;
|>     ALgetparams(AL_DEFAULT_DEVICE, pvBuffer, 2);
|> /* compute short block length meter update rate is 50 Hz (WHAT !!
|> BUT
|> SEEMS TO BE RIGHT) */
|>     shortBlockLength = (unsigned int) ((((float)
|> pvBuffer[1])/meterUpdateRate) + 0.5);
|> 
|> /* 
|>  * initialize some filter variables.  This code should be used
|>  *  to reset the run length filters when the signal continuity is
|> violated.
|>  *  Apanel runs similar stuff at start up and every time metering is
|> enabled
|>  */
|> /* clear run length buffer and set ptr to start at beginning */
|>     for (i = 0; i < RUN_LENGTH_BUFFER_LENGTH; i++)
|> 	{
|> 	runLengthBufferL[i] = 0;
|> 	runLengthBufferR[i] = 0;
|> 	}
|>     runLengthBufferIndex = 0;
|> 
|> /* clear filter accumulators */
|> dcOffsetL = 0;
|> dcOffsetR = 0;
|> }	/* ------------------ end ComputeShortTimeMeterInterval()
|> --------------- */
|> 
|> /*
|>
**********************************************************************
|>  *
|>  * ComputeLevelToMeterLookUpTable:	fill meter logarithmic lookup
|>  *					table.  These values range between
|>  *					0.0 .. 1.0.  
|>  *
|>
**********************************************************************
*/
|>     void 
|> ComputeLevelToMeterLookUpTable()
|> {
|>     int	    i;
|>     int	    minimumLinearLevel = 33;
|>     int	    maximumLinearLevel = 32767;
|>     float   topdBLevel, bottomdBLevel;
|> 
|> /* Any values below -60 dB (value 33of32767 linear) are displayed as
|> 0.0 */
|> /* -60dB = 20*log10(maximumLinearLevel/maximumLinearLevel) */
|>     /* nuke out minimumLinearLevel positions */
|>     for (i = 0; i <= minimumLinearLevel; i++)
|> 	{
|> 	levelToDecibelTable[i] = 0.0;
|> 	}
|> 
|>     /* comppute values only between the bottom and top decibel values
|> */
|>     topdBLevel = 20.0*log10(2.0*((float) maximumLinearLevel));
|>     bottomdBLevel = 20.0*log10(2.0*((float) minimumLinearLevel));
|>     for (; i < maximumLinearLevel; i++)
|> 	{
|> 	levelToDecibelTable[i] = topdBLevel - bottomdBLevel - (-20.0 *
|> log10(float(i) / ((float) REFERENCE_LEVEL)));
|> 	levelToDecibelTable[i] /= topdBLevel - bottomdBLevel;
|> 	}
|> 	levelToDecibelTable[maximumLinearLevel] = 1.0;
|> }	/* ------------------ end ComputeLevelToMeterLookUpTable()
|> --------------- */
|> 
|> /*
|>
**********************************************************************
|>  *
|>  * ComputeDualPeakMeters:	compute positions of short and long
|>  *				interval peak meters
|>  *
|>
**********************************************************************
*/
|> void 
|> ComputeDualPeakMeters()
|> {
|>     register long h;
|> 
|>     register short *inBufferPtr;	/* make it point to stereo, time
|> 					interleaved buffer of your choice */
|> 
|>  	for (numStereoSamplesLeft = INPUT_BUFFER_LENGTH;
|> numStereoSamplesLeft;
|> h += 2, inBufferPtr += 2) 
|> 	    {
|> 	    /* 
|> 	     * compute run length filters.  This will give us a
|> 	     *	majorly low pass filtered version of input signals.
|> 	     *	This value is also the DC offset.  The DC offset is then
|> 	     *	    subtracted from the input signal.  This could very
|> 	     *	    well be a high pass filter if the signal and filter
|> 	     *	    output were time aligned.  However, they are not. 
|> 	     *	This operation works on the assumption that the low pass
|> 	     *	filter output value changes VERY slowly over time. 
|> 	     *	
|> 	     *	Also need to compute for each channel.  DC offset also
|> 	     *	    seems to vary a little with the input attenuation.
|> 	     */
|> 	    /* subtract off value at end of circular runlength buffer */
|> 	    /* and write new input into buffers */
|> 	    dcOffsetL -= (long) runLengthBufferL[runLengthBufferIndex];
|> 	    runLengthBufferL[runLengthBufferIndex] = inBufferPtr[0];
|> 
|> 	    dcOffsetR -= (long) runLengthBufferR[runLengthBufferIndex];
|> 	    runLengthBufferR[runLengthBufferIndex] = inBufferPtr[1];
|> 
|> 	    /* read input values from input buffer */
|> 	    long inputLeft = (long) inBufferPtr[0];
|> 	    long inputRight = (long) inBufferPtr[1];
|> 
|> 	    /* advance and bound buffer index */
|> 	    runLengthBufferIndex++;
|> 	    runLengthBufferIndex &= RUN_LENGTH_BUFFER_INDEX_MODULO;
|> 	    /* accumulate input to signed 32bits:  CAREFUL.  short interval
|> 	    should be short enough to avoid overflow */
|> 	    dcOffsetL += inputLeft;
|> 	    dcOffsetR += inputRight;
|> 
|> 	    /* collect maximum negative and positive values of 
|> 		    left/right input samples */
|> /* should work to subtract DC inside slower update loop.  Do not
|> subtract
|> 	off absolute value */
|> 	    if (inputLeft > shortBlockMaxL) 
|> 		{
|> 		shortBlockMaxL = inputLeft;
|> 		}
|> 	    else if (inputLeft < shortBlockMinL)
|> 		{
|> 		shortBlockMinL = inputLeft;
|> 		}
|> 	    if (inputRight > shortBlockMaxR) 
|> 		{
|> 		shortBlockMaxR = inputRight;
|> 		}
|> 	    else if (inputRight < shortBlockMinR)
|> 		{
|> 		shortBlockMinR = inputRight;
|> 		}
|> 	
|> 	    /* 
|> 	     * update meters every shortBlockLength using dual
|> 	     * peak metering scheme.  Short timepeak interval is 
|> 	     * 1/20 second.  Long time peak interval is 1 second.
|> 	     * The long time peak values are maintained in
|> 	     *	the meter drawing routine 
|> 	     */
|> 		/* IMPORTANT:  >= operation is safeguard for dynamic changes in
|> 		    shortBlockLength.  Such changes occur when the sampling
|> 		    rate is changed. */
|> 	    if ((shortBlockCounter++ >= shortBlockLength))
|> 		{
|> 	    /* only need to remove DC offset at rate which short block
|> 		peak is updated */
|> 	    /* choose greatest short block absolute value */
|> 		if (shortBlockMaxL < -shortBlockMinL)
|> 		    shortBlockMaxL = -shortBlockMinL;
|> 
|> 		if (shortBlockMaxR < -shortBlockMinR)
|> 		    shortBlockMaxR = -shortBlockMinR;
|> 
|> 	    /* saturate short block max values */
|> 		if (shortBlockMaxL > REFERENCE_LEVEL)
|> 			{
|> 			shortBlockMaxL = REFERENCE_LEVEL;
|> 			}
|> 		if (shortBlockMaxR > REFERENCE_LEVEL)
|> 			{
|> 			shortBlockMaxR = REFERENCE_LEVEL;
|> 			}
|> 
|> 	    /* use short block max values to update long block max values
|> */
|> 		if (shortBlockMaxL > longBlockPeakL) 
|> 		    {
|> 		    longBlockPeakL = shortBlockMaxL;
|> 		    }
|> 		if (shortBlockMaxR > longBlockPeakR) 
|> 		    {
|> 		    longBlockPeakR = shortBlockMaxR;
|> 		    }
|> 
|> 	    /* look up meter value in decibel table and render on screen */
|> 
|> 	    /* AT THIS POINT, WE HAVE THE METER VALUE TO BE DISPLAYED */
|> 
|> 	    /* the long block is actually
|> 		lMeter->newLevel(levelToDecibelTable[shortBlockMaxL], 
|> 			levelToDecibelTable[longBlockPeakL]);
|> 		rMeter->newLevel(levelToDecibelTable[shortBlockMaxR], 
|> 			levelToDecibelTable[longBlockPeakR]);
|> 		/* clear long block register every second.  Add your code to
|> 		 ensure they are not cleared every loop iteration. Simple
|> 		to make it a multiple of the short block interval. */
|> 		longBlockPeakL = longBlockPeakR = 0;	
|> 
|> 	    /* clear short peak registers and short block counter */
|> 		shortBlockMinL = shortBlockMinR = 0;
|> 		shortBlockMaxL = shortBlockMaxR = 0;
|> 		shortBlockCounter = 0;
|> 		}
|> 	}
|>     }
|> 
|> 
|>