VU-Meter hooked to Teensy (display MidiOut-Messages with variable velocity)

I coded a usb midi-controller (Teensy2++) to receive MidiOut-Messages with variable velocity. I use it with Music-Software (Traktor) and have a LED attached to it. So the louder the music, the brighter the LED (I already posted the Code here).

Now I plan to attach a VU-Meter with a LM3915 to the output instead of a single LED.
The problem is, that the LM3915 is usually used with 12V and audio-signals with very low Voltage (0 - 1V). But my Teensy runs with 5V and the MidiOut-Signal is 0-5V, so i have to use different resistors.

On this LM3915-Datasheet on page 2 is a formula to calculate the values I would need for the resistors R1 and R2. I understood, that I have to put 5V as the value for „Vref“, but did not get any further as my math is too poor :frowning:

So can anybody help me with this?
It would also be good to know, if I got the right approach here, as I am not shure if the LM3915 will play nice with the Teensy.

This sounds like a very convoluted way to get an LED VU meter. You have a microcontroller that can drive LEDs, there's no point in converting the digital signal to PWM signal, filter it to get an analog voltage, and then use a series of comparators to convert it to digital signals to drive LEDs again.

You can use shift registers with LED drivers if you don't have enough Arduino pins, for example.

Pieter

Hi Pieter, thanks for the response. Yes, I could find a way to do everything with the teensy, but as I plan 4 vu-meters with 10 LEDs each it would mean that I would need 40 pins just for the vu-meters.
And I already use several pins for buttons and even have a Multiplexer attached to control 23 analog-ins for pots (Codes already gots several 100 lines). So it looked like the simpler solution to just use 4 PWM-pins and just 4 LM3915s. Why reinvent vu-meters, when LM3915s are build exactly for that purpose?

What you need is a LED driver, not a VU meter.
You already have all the data you need to know which LEDs to turn on or off.

For example, you could use a TLC59025 from Texas Instruments. They have an entire product range of signage LED drivers, which is exactly what you need.
You can drive tens or even hundreds of these chips from only 3 pins on your microcontroller.

It's the same principle as the popular 74HC595 shift registers. The outputs are different though, LED drivers have constant current outputs, while general purpose shift registers have TTL outputs (voltage), so you need current limiting resistors for all outputs.
The 74HC595 is not really suited to drive LEDs (not enough output current), but many people use them for this purpose without any problems.

Ok, it took me a while, but now I understand what you mean:
Convert a digital signal to analog just to reconvert to a digital signal is an unnecessary detour.
You are definitely right about that.

Unfortunately this will not help me very much, because I am not an experienced Arduino-Coder and do not know one who could help me out. It was already quite a struggle for me to get the code running and to understand the LM3915. So I am afraid that I will not be able to rewrite the code above to make the teensy work with a LED driver.

I am also still thinking about using analog VU-Meters, so it would be nice if the code would work with both analog or LED vu-meters.

Apart from being an unnecessary detour - do you think using an analogOut to drive an LED VU-Meter would cause any problems?

Apart from being an unnecessary detour - do you think using an analogOut to drive an LED VU-Meter would cause any problems?

It would depend on the design. A "true VU meter" (analog or digital) would work fine because the response time is defined. A peak meter wouldn't work at all because the PWM peaks are always +5V.

You're probably not going to find a true VU meter because it's "outdated" and not very useful with digital audio.

balduin:
Apart from being an unnecessary detour - do you think using an analogOut to drive an LED VU-Meter would cause any problems?

Yes: analogWrite is not analog, it's a PWM square wave, and the LM3915 has a logarithmic scale.
Decent LM3915 designs will have a peak detector at the input (always look at the design documents from the manufacturer, there are some schematics in the datasheet, most schematics you'll find by googling are garbage), which can be as simple as a rectifying diode and an RC low-pass filter. In your case, just a low-pass filter will probably fine to filter out the PWM.

balduin:
I am not an experienced Arduino-Coder and do not know one who could help me out. It was already quite a struggle for me to get the code running and to understand the LM3915. So I am afraid that I will not be able to rewrite the code above to make the teensy work with a LED driver.

Using these shift registers / LED drivers is really easy.
If you use this Arduino-Helpers library, you can just use the familiar digitalWrite(pin, value) function to turn on / off the LEDs of the driver. Some examples:
https://tttapa.github.io/Arduino-Helpers/Doxygen/df/d9e/2_8RGB-LED-Chaser_8ino-example.html
https://tttapa.github.io/Arduino-Helpers/Doxygen/d9/d5f/1_8SPI-Blink_8ino-example.html

I'm also working on this MIDI Control Surface library. It includes an example that takes the velocity of an incoming MIDI Note message and displays it on an LED "VU" bar graph, it's just 10 lines of actual code:
https://tttapa.github.io/Control-Surface/Doc/Doxygen/d9/dfe/3_8NoteLEDBar_8ino-example.html

Ok, finally I got some help from an Expert, dumped the Idea of an analog VU-Meter and went digital using a LED-Strip and the NeoPixel-Library instead. We came along with this code, using a 15-led-strip attached to pin 12, listening to midi-channel 64:

// AdaFruit NeoPixel library
#include <Adafruit_NeoPixel.h>
#ifdef __AVR__
  #include <avr/power.h>
#endif

// NeoPixel Setup: Number of Pixels, Pin, 3rd Parameter might have to be changed for older neopixel-strips (see strandtest-example for more possible values)
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(15, 12, NEO_GRB + NEO_KHZ800);

void setup(){
  //Serial.begin(115200);
  pixels.begin(); // This initializes the NeoPixel library.
  pixels.clear();//set all leds to off
  usbMIDI.setHandleNoteOn(OnNoteOn) ;
}

void loop(){
  usbMIDI.read(2); // Midi channel to read  
}

//MIDI READ
void OnNoteOn(byte channel, byte note, byte velocity){
  if(velocity>0){
    switch(note){
      case 64:
        //update the led strip
        int nr_of_leds = velocity*15/127+0.5;//calculate the number of leds that should be on, 0.5 is to correct the integer rounding errors
        pixels.clear();//set all leds to off
        for (int current_led = 0; current_led < nr_of_leds; current_led++){//switch on the correct number of leds    
          pixels.setPixelColor(current_led, pixels.Color(0,70,0)); // Moderately bright green color, depending on velocity value.
        }
        pixels.show(); // This sends the updated pixel color to the hardware.        
        break;
    }
  }
}

It is already working, but we stumbled upon an old problem: Audio-Output is linear, but VU-Meters show logarithmic values as it fits better to human hearing.

In this line the velocity-value (0-127) is recalculated to light up the correct amount of leds on the strip (0-15), but the output of the velocity-values is linear:

 int nr_of_leds = velocity*15/127+0.5;

As a reference I use the VU-Meter in Traktor which shows logarithmic values - the led-strip behaves different: Quite sound is shown already in the traktor-vu-meter, but to light up the first LEDs on the strip it as to be louder.

Now I could set specific velocity-values-range for each of the 15 LEDs, but I wonder if there is a better, simpler way.

Is there maybe a formula to calculate logarithmic values which I can apply in the code?

I found a thread which might be helpful:
https://forum.arduino.cc/index.php?topic=208960.0
(code in 3rd post)

The function log() is used there, but I am having a hard time to wrap my head around it.
Can anybody tell me how to apply it to my code?

Now I could set specific velocity-values-range for each of the 15 LEDs, but I wonder if there is a better, simpler way.

Since there are only 15 possibilities (16 if you count all-off) a look-up table might be the simplest, most straightforward solution.

The function log() is used there, but I am having a hard time to wrap my head around it.

If you want decibels the formula is dB = 20 * log(A/Aref) where A is the amplitude (or velocity) and Aref is your 0dB reference.*

For example, assuming 127 is your 0dB reference then 64 (about half the amplitude) is 20 * log (64/127) = -6dB (approximately).

You can play-around with a spreadsheet if that helps you. (I've never tried the log() function with the Arduino).


...I'm wondering how well this is going to work because you'll probably have lots of notes with the same velocity, perhaps with little or no pause/rest in-between. Plus, the actual notes/sounds will decay but that won't show-up and it probably won't look like a VU meter.

  • Just in case you run-across a different dB formula, that's the formula for amplitude (like a voltage or digital level). The formula for power (Watts) is dB = 10 * log(P/Pref). i.e. -6dB is half the amplitude but 1/4th of the power. (-3dB is half power.)

OK, we played around with several numbers/formulars and now the logarithmic output is not perfect, but acceptable.

Using four 8-Pixel-Stick to display the velocity-output of four different decks:

// AdaFruit NeoPixel library
#include <Adafruit_NeoPixel.h>
#ifdef AVR
#include <avr/power.h>
#endif


#define NR_OF_LEDS 8 //Number of leds on each neopixel-strip

// NeoPixel Setup: Number of pixelsA, Pin, 3rd Parameter might have to be changed for older neopixel-strips (see strandtest-example for more possible values)
Adafruit_NeoPixel pixelsA = Adafruit_NeoPixel(NR_OF_LEDS, 25, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel pixelsB = Adafruit_NeoPixel(NR_OF_LEDS, 24, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel pixelsC = Adafruit_NeoPixel(NR_OF_LEDS, 26, NEO_GRB + NEO_KHZ800);
Adafruit_NeoPixel pixelsD = Adafruit_NeoPixel(NR_OF_LEDS, 27, NEO_GRB + NEO_KHZ800);

void setup() {
//Serial.begin(115200);

pixelsA.begin(); // This initializes the NeoPixel library.
pixelsA.setBrightness(10); // set pixel brightness
pixelsA.clear();//set all leds to off

pixelsB.begin();
pixelsB.setBrightness(10);
pixelsB.clear();

pixelsC.begin();
pixelsC.setBrightness(10);
pixelsC.clear();

pixelsD.begin();
pixelsD.setBrightness(10);
pixelsD.clear();

pinMode(4, OUTPUT);
usbMIDI.setHandleNoteOff(OnNoteOff);
usbMIDI.setHandleNoteOn(OnNoteOn) ;
}

void loop() {
usbMIDI.read(2); // Midi channel to read
}

//MIDI READ
void OnNoteOn(byte channel, byte note, byte velocity) {
if (velocity > 0) {
switch (note) {
case 61:
updateVUStrip( &pixelsA, velocity);
break;
case 62:
updateVUStrip( &pixelsB, velocity);
break;
case 60:
updateVUStrip( &pixelsC, velocity);
break;
case 63:
updateVUStrip( &pixelsD, velocity);
break;
}
}
else {
switch (note) {
case 61:
pixelsA.clear(); //set all leds to off
pixelsA.show(); // This sends the updated pixel to the hardware.
break;
case 62:
pixelsB.clear();
pixelsB.show();
break;
case 60:
pixelsC.clear();
pixelsC.show();
break;
case 63:
pixelsD.clear();
pixelsD.show();
break;
}
}
}

void OnNoteOff(byte channel, byte note, byte velocity) {
switch (note) {
case 61:
pixelsA.clear();
pixelsA.show();
break;
case 62:
pixelsB.clear();
pixelsB.show();
break;
case 60:
pixelsC.clear();
pixelsC.show();
break;
case 63:
pixelsD.clear();
pixelsD.show();
break;
}
}

void updateVUStrip(Adafruit_NeoPixel* neopixel_object, byte velocity){
//original log-value: 2.89, better 2.8
int active_pixel = (2.66 log(velocity)) / 15 NR_OF_LEDS + 1.5; //calculate the number of leds that should be on, 0.5 is to correct the integer rounding errors
neopixel_object->clear();//set all leds to off
for (int current_pixel = 0; current_pixel < active_pixel; current_pixel++) { //switch on the correct number of leds
if (current_pixel >= 7)
neopixel_object->setPixelColor(current_pixel, neopixel_object->Color(255, 0, 0)); // leds in red color
else if (current_pixel < 6)
neopixel_object->setPixelColor(current_pixel, neopixel_object->Color(0, 255, 0)); // leds in green color
else
neopixel_object->setPixelColor(current_pixel, neopixel_object->Color(255, 160, 0)); // leds in orange color
}
neopixel_object->show(); // This sends the updated pixel to the hardware.
}

It is working, but now there is a new challenge: due to contruction-layout I have to mount 2 of the 4 Sticks in reverse order.

So I wonder if there is a way to change the direction of the two sticks pixelsB and pixelsD.

As all sticks use the same function updateVUStrip() I first thought about duplicating that function and make some changes to the > and + in the „for“ part like described here:
https://forums.adafruit.com/viewtopic.php?f=47&t=49649

Tried it, worked in a way, but would need much more modification to the duplicated function.

It might be easier, if I could just reverse the pixel-adressing of the sticks pixelsB and pixelsD (1=8, 2=7, 3=6 etc.).
Does anybody know to do do this?