Retrieving stereo levels?

Writing software for systems that use VLSI Solution's devices as slave codecs to a host microcontroller.
Post Reply
Brek
Senior User
Posts: 60
Joined: Sun 2016-09-11 5:51

Retrieving stereo levels?

Post by Brek » Mon 2018-01-08 13:48

Hi Guys :)
Funny that I should be motivated to do more with this.

I was wondering if there’s any register or RAM that could be read from vs1003b
that would indicate (generally, if not with accurate measurement) the audio level of each of the L & R channels?

I know you could take the mean of all of the spectrum channels to come up with a single level for summed mono,
but was wondering particularly with isolated stereo channels? The spectrum analyser for vs1003b is loaded if that helps.
Cheers, Brek.

User avatar
Henrik
VLSI Staff
Posts: 1105
Joined: Tue 2010-06-22 14:10

Re: Retrieving stereo levels?

Post by Henrik » Tue 2018-01-09 11:50

Hello Brek,

so you want to know the signal level for the audio that is being played back?

To my knowledge that data isn't directly available anywhere. You could, however, get a pretty accurate estimate if you'd just read the DAC registers at regular intervals, e.g. 1000 times per second, then calculate the mean power of the samples. DAC_LEFT and DAC_RIGHT are located at addresses 0xC015 and 0xC016, respectively, so something like the following could give you a relatively reliable result (pseudo-code has not been tested, I hope you get the idea):

Code: Select all

#define SAMPLE_BITS 4
#define SAMPLES (1<<SAMPLE_BITS)
s_int16 lrPhase = 0;
u_int32 lrSum[2] = {0,0};
u_int16 lrLevel[2] = {0,0};

/* Call this function at (semi)regular intervals, e.g. 1000 times a second. */
void CalculateLevel(void) {
  u_int16 level, i;
  WriteRegister(SCI_WRAMADDR, 0xC015);
  lrPhase = (lrPhase+1) & (SAMPLES-1);
  for (i=0; i<2; i++) {
    level = abs((s_int16)(ReadRegister(SCI_WRAM)));
    lrSum[i] += ((u_int32)level*level) >> SAMPLE_BITS;
    if (!lrPhase) {
    	lrLevel[i] = (u_int16)sqrt(lrSum[i]); /* If there is a faster, integer version of sqrt(), that could also be used. */
    	lrSum[i] = 0;
    }
  }
}
If multiplying or taking the square root of a 32-bit integer is prohibitively expensive with your microcontroller, you could just look at the highest absolute value for each of the channels. The obvious-looking solution, taking the average sample value without the power calculations as shown in the example code will probably give worse results.

Kind regards,
- Henrik
Good signatures never die. They just fade away.

Brek
Senior User
Posts: 60
Joined: Sun 2016-09-11 5:51

Re: Retrieving stereo levels?

Post by Brek » Tue 2018-01-09 18:19

Hi Henrik, Thanks for the reply :)

Yes, Like a pair of old analogue VU meters.
Maybe a trivial thing, but I am likely to make a go of it, and share any result.
Last night I discovered the DAC registers in the datasheet.

I do think I generally get the idea, thanks!

Code: Select all

level = abs((s_int16)(ReadRegister(SCI_WRAM)));
My understanding is you cast the binary value to signed integer, it would be AC with zero crossings, but once written to “int level”, becomes rectified to an absolute amplitude sample.

Once, you rectify to DC like that, only the spring drives the needle backwards,
so in that case , wouldn’t it be nicer for the CPU to just watch for the peaks,
and only use the data to drive the needle forward?

Code: Select all

int needles[2] = {0,0} // needle positions
const unsigned int spring = 1; // should be up to 90 degrees of a sine for pretend meter springs

…

for (i=0; i<2; i++) {
level = abs((s_int16)(ReadRegister(SCI_WRAM)));
if (level > needles[i]) {
needles[i] = level;
} else {
needles[i] = needles[i] - spring;
}
Basically, I’m making a case that for this, the cheap option is the one I should be going for... or is that wrong?

User avatar
Panu
VLSI Staff
Posts: 2572
Joined: Tue 2010-06-22 13:43

Re: Retrieving stereo levels?

Post by Panu » Tue 2018-01-09 20:45

Hi Brek, your code basically does a peak indicator, Henrik's code does a level indicator. Which one looks better is up to you :D

-Panu
Info: Line In and Line Out, VS1000 User interface, Overlay howto, Latest VSIDE, MCU Howto, Youtube
Panu-Kristian Poiksalo, VLSI Solution Oy

Brek
Senior User
Posts: 60
Joined: Sun 2016-09-11 5:51

Re: Retrieving stereo levels?

Post by Brek » Tue 2018-01-09 23:06

I’m confident I’ll get a convincing pair of analogue meters out of it :)
Needs work, but I think I can get it cheap.

The byte value can be cast to float, then halved, and I think that result can translate to
degrees angle of rotation, since a meter doesn’t cover a whole semicircle.
And the spring part should be a nicer 90 degrees sine rather than constant.

Code: Select all

void stereopeaklevels() { // update for version 1.0.189
unsigned int vulevel;
unsigned int ivu;
unsigned int vubuffer;
unsigned char vsvala;
unsigned char vsvalb;
while (VSDREQ == 0) {delay_gen(0);}
VSXCS = 0; // select vs1003 instruction
WriteSPIManual(0x02); // write
WriteSPIManual(0x07); //
WriteSPIManual(0xC0); // dac output address
WriteSPIManual(0x15); //
VSXCS = 1; // deselect vs1003 instruction
while (VSDREQ == 0) {delay_gen(0);}
for (ivu=0; ivu<2; ivu++) { // for each channel
VSXCS = 0; // select vs1003 instruction    
WriteSPIManual(0x03); // read
WriteSPIManual(0x06); //
vsvala = ReadSPIManual(); // read dac value ram
vsvalb = ReadSPIManual(); //
VSXCS = 1; // deselect vs1003 instruction
vubuffer = vsvala;
vubuffer = vubuffer << 8;
vubuffer = vubuffer + vsvalb;
vulevel = abs((int)(vubuffer));
vulevel = vulevel / 256; // scale value

//vuneedles[ivu] = vulevel; // using the direct value did not look nice at all

if (vulevel > vuneedles[ivu]) { // but is only one dirty trick away from looking nice
vuneedles[ivu] = vulevel;
} else {
if (vuneedles[ivu] > 5) { // spring constant value
vuneedles[ivu] = vuneedles[ivu] - 5; // spring constant value
} else {
if (vuneedles[ivu] > 0) {vuneedles[ivu]--;}
} // vuneedles
} // vulevel
} // ivu
while (VSDREQ == 0) {delay_gen(0);}
}
//
It’s a pair of bargraphs for the moment

Code: Select all

drawline(69,7,70+vuneedles[0],7);   // draw stereo bargraph peak meters
drawline(69,8,70+vuneedles[0],8);   //
drawline(69,9,70+vuneedles[0],9);   //
drawline(69,10,70+vuneedles[0],10); //
drawline(69,11,70+vuneedles[0],11); //
drawline(69,16,70+vuneedles[1],16); //
drawline(69,17,70+vuneedles[1],17); //
drawline(69,18,70+vuneedles[1],18); //
drawline(69,19,70+vuneedles[1],19); //
drawline(69,20,70+vuneedles[1],20); //

Brek
Senior User
Posts: 60
Joined: Sun 2016-09-11 5:51

Re: Retrieving stereo levels?

Post by Brek » Fri 2018-01-12 12:49

Well that was interesting :)
Due to inherent LCD lag, anything remotely accurate with speed leaves ghosting on the screen, so you can see more than one needle.
In the second example, after I push the button to change mode, I’m trying some hack physics for the meter spring, but then loses accuracy.
I’m not sampling very fast in either of these.

On the plus side, due to the same LCD lag, any bar-graph level meter looks great :D

https://www.youtube.com/watch?v=6rBYfH9nVpQ

User avatar
Henrik
VLSI Staff
Posts: 1105
Joined: Tue 2010-06-22 14:10

Re: Retrieving stereo levels?

Post by Henrik » Thu 2018-01-18 13:27

Hello Brek!

Yes, I have also found that bar graphs generally look nicer on LCDs than needles, except of course if the LCD is a really fast active one. Something like this seems to work quite well:

Code: Select all

level = abs((s_int16)ReadFromVS10XX());       /* Get initial value, interpret as signed, rectify */
dB = LinToDB(level);                          /* In this case, LinToDB() return 0 for full scale, negative for lower signals */
pixels = 2*dB+MAX_PIXELS;                     /* Scaling */
drawPixels -= 2;                              /* "Spring" constant to go down slower */
if (pixels > drawPixels) drawPixels = pixels; /* Fast up for peak indication */
if (drawPixels < 0) drawPixels = 0;
DrawBar(drawPixels);
Kind regards,
- Henrik
Good signatures never die. They just fade away.

Post Reply