Another question about VU

Magnus Danielson cfmd at swipnet.se
Sat Feb 26 00:57:07 CET 2000


From: "Batz Goodfortune" <batzman at all-electric.com>
Subject: Another question about VU
Date: Thu, 24 Feb 2000 12:36:02 +1000

> Y-ellow Y'all.

B-lue B'atz!

WARNING Fellas! This is WWAAAAAAAAAAAAAAAAAAAAAAAYYYYYYYY Off Topic!!!!

Migth be interesting thougth!

> 	While we're on the subject, and as embarrassing as it is for me to ask. I
> need to know how to render a VU scale using an 8 bit A-D. In the simplest
> possible terms. I was away the day they did math at school. I know it's log
> 20 something but I can't rap my brains round a ratio right now.

It's

                 U
L = 20 * log   (----)
            10  Uref

Batz!

> I'll explain the problem.
> 
> I need to build multiple bargraphs. The simplest way to do that is to use a
> multi-channel A-D chip and a micro. The micros can drive LEDs directly and
> multiplex them. So it would be a 2 or 3 chip solution. Small in size (very
> important) and able to do all the nice bits like peak/average in software.
> 
> The way I imagined it would work is that there might be a look up table Or
> at least a scale table, Where if a value was higher that a certain point it
> would turn a LED on. Etc.
> 
> Now the problem is that In some cases this might be an array of the usual
> 10 LEDs. In other cases there are some 36 LED displays I can use. In yet
> more cases I could use an LCD. On the LCD it could be showing the two main
> channel outputs across the display in detail. In the other modes it could
> be showing multiple channels in less detail vertically. So I'd like to have
> some universal scale that divides the 256 step linear scale into the log 20
> VU scale. Then I could apply this to any given set of bargraphs.

Ah. I think I see the problem here...

> I just can't wrap my brain around it at the moment. And if you tell me,
> you'd have to tell me in the simplest possible forms. "Do this, do that
> then you get this..."

Right, very basic that is.

> I know the proper VU (dB) scale is a ratio devised by nWatts into 600 ohms.
> 1mW thanks Tim. But how do you map that against a scale which could be
> totally arbitrary and is actually measuring voltage?

OK. We will do that to.

> I've been asking round without trying to embarrass myself in public like
> this but I just had to bite the bullet in the end. <he says grimacing>

Well, we all have our weak moments ;)

OK... the problem is really not a simple linear-to-dB scale problem, it's
worse ;)

First, the whole dB scale really relates some power level to some reference
power level. This is really fundamental, we are talking about power levels and
not voltages or amperes or anything else, it's power! Now, we know that we
can convert many other forms of measures into power, the voltage can be
converted into power by using

     2
    U
P = -
    R

The level (in Bel, the fundamental unit here) is

           P
L = log  (----)
       10 Pref

but by popular demand I will use the decibel scale, so we need to multiply
with 10, thus (in decibel):

                P
L = 10 * log  (----)
            10 Pref

Now, together with the mail I just sent you should have all that math clear.

The trouble is, what you measure with a A/D sample is just a amplitude (or
current) sample at some point in time, not the power. The power is also just
a momentary measure, but the hard point here is if it represent the power of
the frequency components involved. In order to see that you will need to take
measures over the full cycle of each involved frequency (or multiple of) and
average out that value. You can do this on all frequencies at once by averaging
out the momentary power of the voltage. This is really what RMS is all about,
it is short for Root Mean Square which is basically how you would read the
formula

         _________________
        / 1  / T     2    |
U = \  /  -  |    u(t)  dt
     \/   T / 0

this is the continous time formula (for us analog freaks) where as the discrete
time equalent (for digital guys and thus doing stuff using CPUs) would be

               _____________
              /  T - 1      |
             /   ----
            / 1  \        2
U = \      /  -   >   u(t)
     \    /   T  /
      \  /       ----
       \/        t = 0

Now, this would give you the RMS value not peak and not average. Or actually,
it will give you the average power and not peak voltage or average voltage.
Also, it is not the peak power.

Here we run into an issue of discussion, there have been many discussions about
the virtue of this or that detection method of level, especially in the
compression/limiter/expander and similar dynamic processing equipment.

A peak voltage measurement is in theory just doing a peak-hold curcuit, that
is a (ideal) diod into a (ideal) capacitor. The bad thing is that a strict
peak hold will hide the local peaks occuring later, so in order to adapt to
the dynamic we need to trow away the charge at some time. Doing a reset of the
cap (or hold level) is one way, another is to have a leakage resistor so that
the peak value lowers until it gets overrun by a new peak value.

A real peak voltage measure would need to have a absolute voltage value, so
that you only have positive voltages. Thus

u   (t) = |u(t)|
 abs

or

	if (u < 0)
	   uabs = -u;
	else
	   uabs = u;

In analog curcuits you acheive this with an precision full-wave rectifier.

The average voltage detection you do by averaging the Uabs value, it's just
a RC filter, possibly with some leakage to have a usefull release.

Now, for a RMS value you would need to average the square of the voltage, this
we can do by taking the square of the momentary voltage (that is, the voltage
coming down the line):

              2
u   (t) = u(t)
 sqr

Note here that I use a small u to denote that it is a momentary voltage, and
the (t) to further point out that it is a function of time. The large U is
reserved for effective values, that is the power or RMS value.

In analog curcuits you can build a squarer by a few transistors (using the
logarithmic functions that transistors provide us with, just as with our
expo curcuits, basically the same thing happening). For digital calculation,
just multiply with itself.

Then, given the square value, we do the averaging on that value (just as with
the average voltage). However, the value we now have is now linear with the
power, we need to get back the voltage, so we run it trougth a square root
operation (yeat again this can be done in analog too). The resulting value is
now the U (or sometimes written as Urms). This value we can then convert into
logarithmic scale for the dB readings of suitable form.

You could consider the idea of doing a peak power detection. However, this is
actually the same as doing the peak voltage detection.

So... what you so far got is a basic idea of how some pieces stick together,
right?

How the heck does one implement such a beast in a CPU?

Well, here is a untested sketch of what I would do:

First, I realize I need to spend some time on the averaging function.

If we take a suitable analog filter:

       R
O----/\/\/\---o----O
              |
|             |    |
| V1       C ===   | V2
V             |    V
              |
O-------------o----O

That is, a capacitor with load resistor R1 and discharge resistor R2 we get
the transfer function:



V2      Zc          1              1
-- = ------ = ------------- = ----------
V1   Zc + R   1 + s * R * C   1 + s * t1

The time constant is

tc = R * C

which is the attack and decay times. The corner frequency is

         1         1
fc = -------- = -------
     2*PI*R*C   2*PI*tc

                   1
wc = 2 * PI * fc = --
                   tc

Now, to make a suitable digital filter out of this we will take the filter
thru the bilinear transformation. This require us to tweak the corner frequency
prior to transforming the curcuit, this is done using the formula

     2     w * T
wp = - tan -----
     T       2

or in it's more (for us) usefull form:

            T
tc' = --------------
                T
      2 * tan ------
              2 * tc

Where T is the time between each sample.

migth look complex... but for relatively long tc will T/tc be a small value
and we can approximate tan x = x without hurting ourselfs too much, then we
end up with:

           T         T * 2 * tc
tc' = ------------ = ---------- = tc
            T          2 * T
      2 * ------
          2 * tc

so that was not so complex in real life, was it? ;)

Now, the actual bilinear transformation can now be done. We replace s according
to

           -1
    2 1 - z
s = - -------
    T      -1
      1 + z

in our prewraped transfer function

            1
H(s) = -----------
       1 + s * tc'

and get

                                              -1
                1                        1 + z
H(z) = ------------------- = -----------------------------
                        -1        -1         2         -1
                 2 1 - z     1 + z   + tc' * - * (1 - z  )
       1 + tc' * - ------                    T
                 T      -1
                   1 + z

                         -1
                    1 + z
H(z) = -----------------------------------
                  2                2    -1
       (1 + tc' * - ) + (1 - tc' * - ) z
                  T                T

At this point many would stay, but I have a little nice trick up my sleve here.
Lets make K to be

       T
K = -------
    2 * tc'

We then get the very exiting

                     -1                     -1
                1 + z                  1 + z
H(z) = ----------------------- = ------------------
            1          1    -1   K + 1   K - 1  -1
       (1 + - ) + (1 - - ) z     ----- + ----- z
            K          K           K       K

So, this is even more math just playing around with us... besides, we must
remove the (K+1)/K for a 1 term instead... so we get

                       -1
         K        1 + z
H(z) = ----- * -----------
       K + 1       K - 1  -1
               1 + ----- z
                   K + 1

Now, if we let b be the gain

      K
b = -----
    K + 1

and express the a1 factor

     K - 1
a1 = -----
     K + 1

we would now want to have the a1 factor described as a expression of b and
for this we need K to be described in b, which becomes

                                                         b
b * K + b = K => b = K - b * K => b = K (1 - b) => K = -----
                                                       1 - b

Now, applying this to a1 gives us

       b
     ----- - 1
     1 - b       b - 1 + b   2 * b - 1
a1 = --------- = --------- = --------- = 2 * b - 1
       b         b + 1 - b       1
     ----- + 1
     1 - b

So, someone says... all this math, but was does it really says?

Well, you get a filter of the topology

     b

in --|>--(+)---o---(+)--- out
          | -  |    |
     o---(+)  |d|   |
     |    | -  |    |
     | 2b |    |    |
     o-<|-o----o----o

(sorry for the poor ASCII graphics)

This filter does 1 addition, 2 subtractions and 2 multiplications per sample.
This topology behaves quite well in headroom and precission/roundoff analysis.

Now, for a piece of C code you would write

int filter(int in, int b)
{
	static int d;
	int    t, out;

	t = in * b + d - 2 * b * d;
	out = t + d;
	d = t;
	return out;
}

For you non-C fellows, "static int d" means that the value d is being kept
in the routine between the calls, it is those the memory. This routine needs
mods to be able to handle many channels, it can only do one as it is written.
A little simplification for clarity.

Well, that was simple enougth. However, if we force b to only take on values
of 2^n then we can use shifting. For a negative n value we want to shift by
-n steps to the left, or in C terms

int filter(int in, int n)
{
	static int d;
	int    t, out;

	t = in >> n + d - 2 * d >> n;
	out = t + d;
	d = t;
	return out;
}

or even better:

int filter(int in, int n)
{
	static int d;
	int    t, out;

	t = in >> n + d - d >> (n - 1);
	out = t + d;
	d = t;
	return out;
}

We are now down to 2 additions, 1 subtraction and 2 shifts. The n-1 term is
really a constant just as n. For both real hardware additions, subtracts and
shifts are cheap. Allmost dirt cheap, at least compared to multiplication.

Now then, how does the values of b (or n for that matter) relate to time or
frequency... well if we use the shift variant we have

     -n
b = 2

      b
K = -----
    1 - b

       T
K = -------
    2 * tc'

cooking them all together gives:

        T     T * (1 - b)     T     T
tc' = ----- = ----------- = ----- - -
      2 * K      2 * b      2 * b   2

or the other way around:

      K
b = -----
    K + 1

       T
K = -------
    2 * tc'

         T
b = -----------
    T + 2 * tc'

Thus, scaling is pretty easy...

So, now we have a fairly simple filter, could we please go on with the
detector stuff?

Sure...

Now, let's develop a digital peak (with leakage).

For every sample we get, we will first get the absolute value. Then, if this
value is above the current peak value, then we will directly set the value to
be that of the peak, otherwise we feed the filter nothing (0 that is).

To set the d value from a new peak value p we need to resulve d from

p = p * b + d - 2 * b * d + d

p - p * b = d - 2 * b * d + d = d (1 - 2 * b + 1) = d * 2 * b

      1 - b     p     p   1  p
d = p ----- = ----- - - = - (- - p)
      2 * b   2 * b   2   2  b

which with b = 2^-n becomes

d = ((p << n) - p) >> 1

So, for the peak voltage value we have:

int peakfilter(int in, int n)
{
	static int d, out;
	int    t;

	/* Make absolute value */
	if (in < 0)
	   t = -in;
	else
	   t = in;

	/* Check for peak */
	if (t >= out)
	{
		/* Is peak */
		d = ((p << n) - p) >> 1;
		out = t;
	}
	else
	{
		/* Is not new peak, use decay filter */
		t = d - d >> (n - 1);
		out = t + d;
		d = t;
	}
	return out;
}

For average power (RMS) value we have:

int rmsfilter(int in, int n)
{
	static int d;
	int    t, out;

	t = in * in;
	t = t >> n + d - d >> (n - 1);
	out = t + d;
	d = t;
	return out;
}

int rmsvalue(int in)
{
	return sqrt(in);
}

Here, only when the actual RMS value is needed, the square root function is
being exercised. This is since it is so expensive and it's usage is much rarer
than the update of the value. The function of rmsvalue could be baked into
a later stage, so we do not have to worry about it. The RMS filter is really
the interesting part here.

OK, so now we have the generic peak and RMS filters to work with. They don't
seem too scary. Note that you can implement them in hardware/FPGA without too
much headache.

Now comes a quite diffrent head-ache, namely converting these values into
a number telling us how many LEDs, segments or whatever to lit. The really
interesing issue here is set the limits. 

Let's start with defining the scale we want
>L
  0 dB 7 leds
 -3 dB 6 leds
 -6 dB 5 leds
-10 dB 4 leds
-20 dB 3 leds
-30 dB 2 leds
-40 dB 1 leds
-OO dB 0 leds

We can convert our values into the level L and then compare them with these
limits and from that decide how many leds to lit. This means doing a log
function. Hmm... seems a bit wasty in my mind. No, let's instead take these
limits thru the log function and thus convert the limits to the native values
that pop out of our filters instead. Then we will still make the same
comparision but with diffrent limits, I can live with that. We had

                 U
L = 20 * log   (----)
            10  Uref

which means that we can get
          L
 U       (--)
---- = 10 20
Uref

Now, our limits would be converted 
>L     leds U/Uref
  0 dB  7   1
 -3 dB  6   0.7079
 -6 dB  5   0.5011
-10 dB  4   0.3162
-20 dB  3   0.1000
-30 dB  2   0.0316
-40 dB  1   0.0100
-OO dB  0   0

Let's assume that full scale of a 10-bit A/D means +3 dB, then we have the
value 1023 for the level +3 dB. This means that our 0 dB reference level is
at

         1023
Uref' = ------ = 724.2285
          3/20
        10

The comparator level U' would then be
                                         L                        L-3
                       U      1023      (--)    1023             (---)
U' = U/Uref * Uref' = ---- * ------ = 10 20  * ------ = 1023 * 10 20
                      Uref     3/20              3/20
                             10                10

and this would render the values

>L     leds U/Uref  U'
  0 dB  7   1	    724
 -3 dB  6   0.7079  513
 -6 dB  5   0.5011  363
-10 dB  4   0.3162  229
-20 dB  3   0.1000   72
-30 dB  2   0.0316   23
-40 dB  1   0.0100    7
-OO dB  0   0         0

So, for our peak values, we only need to compare the filtered peak value with
the U' limits in order to deside exactly how many leds we need to lit. The
real U' formula is

                   L - Lpeak
       b          (---------)
U' = (2  - 1) * 10    20

where b is the number of bits in the A/D and Lpeak is the signal level which
results in full reading.

Now, if we RMS value we where pushing this square root function ahead of us.
However, isn't it high time we start to deal with it?

Rigth. But we notice that we are now comparing to values being the square of
of U', so...
                     L - Lpeak                     L - Lpeak
         b          (---------) 2     b     2     (---------)2
U' = ( (2  - 1) * 10     20    )  = (2  - 1)  * 10     20

                    L - Lpeak
       b     2     (---------)
U' = (2  - 1)  * 10     10

and now we get for our 10-bit and +3 dB peak ref the limit values of


>L     leds U'
  0 dB  7   524507
 -3 dB  6   262876
 -6 dB  5   131750
-10 dB  4    52451
-20 dB  3     5245
-30 dB  2      525
-40 dB  1       52
-OO dB  0        0

It becomes obvious that the RMS value require twice as many bits as the A/D
converter has.

The comparision to limits can be implemented in many forms, so I will not
dwelve into that. To trim the scale, just trim the A/D converter reference
voltage.

If you want diffrent limit scales, diffrent number of leds etc. the means to
reach those values should be pretty straigth forward from the above examples.

Adjust the timescale of the filters for suitable speed.

Note that the normal Nyquist sampling frequency problem exists and there is
nothing magic about it.

I have not explicitly tested this, I know that the core filter works, I have
been useing it in real life. However, there migth be some minor bugs here and
there.

I hope I have provided enougth hints on how to acheive a practical
implementation of a peak and RMS detection system in a CPU with a stable
enougth support in theory for people to be able to verify it.

Batz, did this help you with your problem?

Cheers,
Magnus



More information about the Synth-diy mailing list