home *** CD-ROM | disk | FTP | other *** search
- Path: sparky!uunet!zaphod.mps.ohio-state.edu!saimiri.primate.wisc.edu!ames!sgi!fido!prophet.esd.sgi.com!gints
- 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.
- Lines: 357
- Distribution: world
- Message-ID: <1goj2jINNbd2@fido.asd.sgi.com>
- References: <1goilmINNa9l@fido.asd.sgi.com>
- 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;
- |> }
- |> }
- |> }
- |>
- |>
- |>
-