Teensy 3.1 + Audio Board. Possible to calculate dbu values?

Status
Not open for further replies.
I got my Teensy 3.1 with Audio Board working, however one thing I would like to accomplish is determining dbu values from the line in. (Accurate VU meters)

Is this even possible with the library at all?

I was able to do this with an Arduino Uno and voltage divider (5v -> 2.5v) on the ADC to get good results there. Unfortunately the Arduino Uno is a bit slow with the ST7735 LCD. AFAIK the Teensy 3.1 cannot take in 5V for analog reference can it? (Would blow it up?).

I think I killed a 2nd Teensy I have as well :( It is no longer recognized by the PC at all.
 
I think you have to program it yourself. AFAIK there is only a peak detect.
It all depends on which specification you take as a reference and what you call accurate.
It is very easy to implement, it will only involve taking the absolute value and a 2.44 Hz lowpass filter.
As for the hardware, a 10k resistor accross the input should set the proper impedance.

I do not see the connection with the reference of the teensy, the audio board uses its own reference.
 
I was sampling the inputs with a small buffer and calculating RMS voltage with the Arduino. It was surprisingly accurate (0.775v ~= 0dbu) and was hoping to do the same with the Teensy.

The show stopper there is that 0dbu is about 2.2v peak to peak, and with a DC bias this would exceed the Teensy 3.3v limit on analog in (Arduino is fine with this at 5v max).

I suppose I will have to wait till I get another teensy and try an alternate method.
 
In the first post you mention you have an audio board. Are you using the audio board or the ADC of the Teensy?
The audio board has DC blocking capacitors, if you are using the Teensy, you might also add a blocking capacitor (plus some resistors to set the DC level)
Furthermore, the ADC of both the Teensy and the audio board is more then enough for a VU meter, so you can easily add a voltage divider to scale the input so that it is within the proper range.
Finally, although you can relate RMS voltage to dBu for a continuous sine wave, the dynamic response of both will be different.
 
Yes, I had blocking capacitors on my Arduino circuit. I was using the Teensy Audio Board.

I imagine I could sample the input using the audio board still? Is it still possible to use it as an ADC?

I could try using a voltage divider on the input for the regular Teensy ADC, however my setup I have the Audio Board stacked and soldered to the Teensy. For this reason it would be nice to go through the Audio Board if possible.

Thanks for your input and replies :)
 
The audio board can handle 2.83 Vpp according to the datasheet.
The IC on this board is designed to be able to handle normal line-in signals.
You mention you have a working setup. I am sorry I do not understand the real problem. You should be able to sample the input, so you can use it as an ADC.
Or is your problem that you want a higher dynamic range?
 
I'm sorry if I didn't make it more clear to begin with :(

I do have a working setup with the Arduino Uno. However I desire for a faster display response (Arduino is a bit slow with the ST7735 LCD) as well as the ability for a proper FFT and graphic EQ, which the Teensy 3.1 has the speed for.

This is why I want to try and do the same with the Teensy 3.1... which was my ultimate goal to begin with.

I'll give a whirl at sampling the inputs of the Audio Board next time I get a chance to play with it.
 
My effort at an object to analyze levels and frequency.

View attachment analyze_freq.cpp View attachment analyze_freq.h


Code:
<Audio.h>
#include <analyze_freq.h>
#include <Wire.h>
#include <SD.h>
#include <SPI.h>


//const int myInput = AUDIO_INPUT_LINEIN;
 const int myInput = AUDIO_INPUT_MIC;
//AudioInputAnalog        audioInput(A0); 
AudioInputI2S        audioInput;
AudioFreq            Freq_L;
AudioOutputI2S       audioOutput;        // audio shield: headphones & line-out

AudioConnection c1(audioInput,0,Freq_L,0);
AudioConnection c4(audioInput,1,audioOutput,1);
AudioControlSGTL5000 audioShield;

void setup() {
 
 
 AudioMemory(6);
 audioShield.enable();
 audioShield.inputSelect(myInput);
 Freq_L.begin();
 
}


void loop() {
  
 
  Serial.println(Freq_L.dbm()-93.2);// 93.2 to calibrate to my test equipment
                                    // tracks very well after calibration 
    
  }
 
As stated earlier, dBu as used for VU meters is not only measuring power in dB's, but it also specifies the dynamics of measuring. The following code gives an idea of how to implement this. Rectifying the signal, and then low-passfiltering it. It calculates the linear value. The log value should be taken in the read function.

Untested and uncompiled code
Code:
// time constant based on 99% in 300ms (actually 98.8% due to truncation)
// tau = -.3 / log(1 - .99)
#define TAU 0.065144
const uint16_t alpha = 65536 / (1 + TAU * AUDIO_SAMPLE_RATE_EXACT);
const uint16_t beta = 65536 - alpha;

void VUmeter::update(void)
{
        audio_block_t *block;
        const int16_t *p, *end;

        if (!(block = receiveReadOnly()))
                return;
        p = block->data;
        end = p + AUDIO_BLOCK_SAMPLES;
        do {
                int16_t sample = *p++;
                // rectifying
                if (sample < 0)
                        sample = -sample;
                // simulating ballistics
                vu_lin = (alpha * (uint16_t) sample + beta * vu_lin) >> 16;
        } while (p < end);
        release(block);
}
 
Never mind, move along, nothing to see here. Problem appears to be in my output routine ( progress bar using Gameduino2).
Added 50 msec delay to the output loop and everything settled down has the look and feel of a VU display now :)



Here is my implementation:

Looks like its working with fast attack and slower decay, the problem is about a two second latency from the start of the audio.
Code:
#include "analyze_freq.h"
#define TAU 0.065144
const uint16_t alpha = 65536 / (1 + TAU * AUDIO_SAMPLE_RATE_EXACT);
const uint16_t beta = 65536 - alpha;
void AudioFreq::update(void)
{
	audio_block_t *block;

	const int16_t *p, *end;
	block = receiveReadOnly();
	if (!block) {
		return;
	}
	if (!m_enabled) {
		release(block);
		return;
	}
	p = block->data;
	end = p + AUDIO_BLOCK_SAMPLES;

	do {
		int16_t d=*p++;
		numSamples=numSamples+1.0;
		if((oldSample<0)& (d>=0)) {
         if(cycleKnt>15){
          delta=float(oldSample)/(float(d)-float(oldSample));
          Samples=numSamples-delta;
          numSamples=delta;

          cycleKnt=0;
          rmsSamplesSave = rmsSamples;
          rmsSamples = 0.0;
          valueSquaredSave = valueSquared;
          valueSquared=0;
		 }
		cycleKnt++;

		}
		oldSample=d;
        valueSquared+= d*d;
        rmsSamples++;
        if (d>0){
        d= -d;
        }
        vu_lin=(alpha * d+ beta * vu_lin)>>16;
	} while (p < end);
	release(block);
}

void AudioFreq::begin(bool noReset)
{

	m_enabled=true;
}
double AudioFreq::period(void)
{

   return Samples;

}

double AudioFreq::dbm(void)
{
    return 10.0*log10(valueSquaredSave/(rmsSamplesSave/2));
}

double AudioFreq::rms(void)
{
    return sqrt(valueSquaredSave/rmsSamplesSave);
}
 uint16_t AudioFreq::VU(void)
 {
     return vu_lin;
 }
 
Last edited:
The fast attack/slow decay is a side effect of the log scale, on a linear scale both will be equal.
Anyway this is how it is supposed to be.

Dont know about the latency, did you initialize vu_lin to 0?

I also notice that you removed the uint16_t from
Code:
vu_lin = (alpha * (uint16_t) sample + beta * vu_lin) >> 16;
This was used to remove redundant sign extension instructions and prevent overflow.
The latter should be important. The few cycles saved due to sign extension is not really important for you since you are using floating point in the rest.

If you look at the code I posted in https://forum.pjrc.com/threads/27905-1024-point-FFT-30-more-efficient-(experimental)?p=67030&viewfull=1#post67030 You can see how I did essentially the same timing as you, but using fixed point, with a resolution of 1/65536 of a sample.
Code:
uint32_t startidx = (idx << 16) | (((lastsample << 16) / (lastsample - sample)) & 0xffff);
This will save a reasonable amount of cycles in your interrupt function.
 
Last edited:
Whoa... easy there ^^; I did not expect all of these replies.

I just got back to working on my project and will try some various methods provided here and see what my results are.

Thanks for all of the helpful hints to all who replied :eek:
 
My effort at an object to analyze levels and frequency.

View attachment 3900 View attachment 3901

Unfortunately this would not compile for me. It looks like a lot of variables are not declared? Perhaps the wrong header file linked?

Either way this is a good starting point to try fiddling with it myself.

EDIT:

I got it working now, I tried to clean up carteres code a bit so I can understand it better. I don't understand how it works but it does now for me.

Its not terribly important, but the audio output skips when drawing to the TFT LCD (taking too long? ST7735 with the Teensy specific library). Am I correct to assume that the audio routines in the backround even with interrupts cannot interrupt the LCD refresh?
 
Last edited:
kpc, I have not tried your code yet... apologies but I will see if I can get it to work as well for a comparison.

cartere, I took out the period measurement and tried to speed it up a bit here.

I noticed one math operation in particular was taking up a lot of cpu time. Measuring from the processorUsage() method was showing about 10% (per channel) mainly from accumulating the valSquared as float.

Changing it to an unsigned 64-bit long (were assuming unsigned because your zero crossings only works on the positive points?) and doing the math on integers instead of floats for squaring brought the cpu down to 1%, with the max showing 3.98%. A lot better!

I hope you dont mind me stealing your bits and trying to tidy it up a little :eek:

Code:
#ifndef analyze_dbu_h_
#define analyze_dbu_h_

#include "AudioStream.h"

class AudioDB : public AudioStream
{
public:
	AudioDB(void) : AudioStream(1, inputQueueArray) { }

	virtual void update(void);

	void begin(bool noReset);
	void begin(void) { begin(false); }
	void stop(void) { m_enabled=false; }
	float rms(void);
	float dbm(void);

	// double period(void);
	// vlong num_samples(void);
private:
	audio_block_t *inputQueueArray[1];
	bool m_enabled;
	
	int16_t cycleCount;
	int16_t rmsSamples, rmsSamplesLast;
	
	// Massive speedup using 64-bit unsigned long for the squared values
	unsigned long long valSquared, valSquaredLast;
	
	float delta, numSamples;
	float periodSamples;
	
	int16_t old_sample;
	int16_t current_sample;
};

#endif

Code:
#include "analyze_dbu.h"

#define DB_CALIBRATION     87.45

void AudioDB::update(void)
{
	audio_block_t *block;
	const int16_t *block_pointer, *block_end;

	block = receiveReadOnly();
	if (!block) {
		return;
	}
	if (!m_enabled) {
		release(block);
		return;
	}
	
	block_pointer = block->data;
	block_end = block_pointer + AUDIO_BLOCK_SAMPLES;

  
	do {
		current_sample = *block_pointer++;
		//numSamples += 1.0;
		
		if ((old_sample < 0) && (current_sample >= 0)) {
      if (cycleCount > 15) {
        // We dont care about the period measuring to speed things up
        
        //delta = float(old_sample) / (float(current_sample) - float(old_sample));
        //periodSamples = numSamples - delta;
        //numSamples = delta;
        
        cycleCount = 0;
        rmsSamplesLast = rmsSamples;
        rmsSamples = 0;
        valSquaredLast = valSquared;
        valSquared = 0;
      }
      cycleCount++;
    }
    
    old_sample = current_sample;
    //fastSquared = current_sample * current_sample;
    //valSquared += current_sample * current_sample;
    valSquared += current_sample * current_sample;
    rmsSamples++;
    		
	} while (block_pointer < block_end);
	
	release(block);
}

void AudioDB::begin(bool noReset)
{
	m_enabled=true;
	
	cycleCount = 0;
	
}

/*
double AudioDB::period(void)
{
   return periodSamples;
}

long AudioDB::num_samples(void)
{
   return numSamples;
}
*/

float AudioDB::dbm(void)
{
    return (10.0 * log10((float)valSquaredLast / ((float)rmsSamplesLast / 2)) - DB_CALIBRATION);
}

float AudioDB::rms(void)
{
    return sqrt(valSquaredLast / rmsSamplesLast);
}

The results of all of this...

Thanks to you both for your help :3
 
@Croccydle - No problem, all help and input is good. I found another spot of win7 hell where Codeworks refused to load the version of the file I wanted and was pulling one in from where I have no idea, ended up having to reload everything from backup and lost most of a day. Thought I had cut and pasted working code, sorry for the mix up :(
 
@Croccydle - No problem, all help and input is good. I found another spot of win7 hell where Codeworks refused to load the version of the file I wanted and was pulling one in from where I have no idea, ended up having to reload everything from backup and lost most of a day. Thought I had cut and pasted working code, sorry for the mix up :(

It's all right, I managed to figure it out looking through the other posts what might be going. I imagine that it detects positive slope zero crossings from the code and only calculates RMS from those. I've noticed that as such it will register down to about 20hz here. Overall its been pretty accurate after calibrating to 0db @ 0.775v!

This is todays progress.Waiting for more 74hc595s to arrive to finish the led bar graphs and ill have most of the hardware ready to go.
 
I have noticed with more use that the measured peaks with your algorithm cartere disagrees slightly with regular use (what my sound card or mixer shows ) vs. test tones.

With the test tones (1khz) its spot on for dbm measurement, although I'm curious if its just the way that my sound card/mixer is showing levels vs. what were calculating?

Thoughts? Or am I just being paranoid?

0.775v @ 1khz = 0dbm measured at the device, so I guess that is all that really matters right?

(I may be missing something obvious)
 
Probably a matter of some visual sustain implemented in their meter, or perhaps they sample longer and update less frequently than you do (which equates to a form of visual sustain imho anyway.)

If it reports same for sustained tones as the comparison unit does then you can try putting the displays of the meters as near side by side as possible and watch them as you kill the tone (do not ramp the tone down, this will defeat purpose of test) and then watch them as the tone is started again (again, do not ramp it) then this should confirm or deny the chances that I am right - your meter will immediately reflect the change in audio power level; theirs will (I expect) visually decay the peak after tone is killed but practically immediately display it when tone is restored.

Of course I like being right but hearing I am wrong can still (hopefully) educate me (eventually, perhaps), please tell me how you feel result goes if you try this :)
 
Probably a matter of some visual sustain implemented in their meter, or perhaps they sample longer and update less frequently than you do (which equates to a form of visual sustain imho anyway.)

If it reports same for sustained tones as the comparison unit does then you can try putting the displays of the meters as near side by side as possible and watch them as you kill the tone (do not ramp the tone down, this will defeat purpose of test) and then watch them as the tone is started again (again, do not ramp it) then this should confirm or deny the chances that I am right - your meter will immediately reflect the change in audio power level; theirs will (I expect) visually decay the peak after tone is killed but practically immediately display it when tone is restored.

Of course I like being right but hearing I am wrong can still (hopefully) educate me (eventually, perhaps), please tell me how you feel result goes if you try this :)

I think I've managed to track down and get a better grasp of the culprit. In terms of meter decay, both the PC (kx audio) and the mixer have instantaneous meters with no decay. I do get different results with analog vu meters but that is to be expected given their nature.

I had been using board.lineInLevel(0); as it describes the setting being "0: 3.12 Volts p-p" in order to get maximum dynamic range. However going back to the default "5: 1.33 Volts p-p" improved the meter response in "real world" scenarios, the results are very close to what my mixer and PC are showing. This limits the clipping to around +0.5 to +1db on the metering, but "feels" more accurate.

So I'm guessing I'll have to stick to the default gain level, since its working pretty good now. I still have to figure out FFT bins for frequencies (I'm not an expert on this subject) on the graphic EQ.

I understand why someone would ask "Why do this when you have other equipment that can do this?" but the purpose of this exercise is so I can both learn and create something more interactive.
 
Per SGTL5000 doc:

The ADC has its own analog gain stage that provides 0 to +22.5 dB of gain in 1.5 dB steps. A bit is available that shifts this range down by 6.0 dB to effectively provide -6.0 dB to +16.5 dB of gain. The ADC gain is controlled in the CHIP_ANA_ADC_CTRL register.

It does not look like Paul is using this bit to drop the gain by 6db in the library if you need to go up to +7db it could be set.
 
Last edited:
Status
Not open for further replies.
Back
Top