home *** CD-ROM | disk | FTP | other *** search
- Path: sparky!uunet!usc!cs.utexas.edu!sun-barr!ames!sgi!fido!prophet.esd.sgi.com!gints
- From: gints@prophet.esd.sgi.com (Gints Klimanis)
- Newsgroups: comp.sys.sgi
- Subject: FOR paul@comback
- Date: 17 Dec 1992 00:46:46 GMT
- Organization: Silicon Graphics, Inc.
- Lines: 304
- Distribution: world
- Message-ID: <1goilmINNa9l@fido.asd.sgi.com>
- NNTP-Posting-Host: prophet.esd.sgi.com
-
- 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;
- }
- }
- }
-
-
-
-