/*
   Mechanical VU meter
   Author: Marco Croce
   Version: 1.0 - 09/2021
*/

// library used for the LCD display
#include <LiquidCrystal_I2C.h>

/*
   Bit / Pixel Mapping to create the letters
   and the symbols to show on the display
*/

byte c0[8] = {B11111, B00000, B00000, B00000, B00000, B00000, B00000, B11111};
byte c1[8] = {B11111, B00000, B10000, B10000, B10000, B10000, B00000, B11111};
byte c2[8] = {B11111, B00000, B11000, B11000, B11000, B11000, B00000, B11111};
byte c3[8] = {B11111, B00000, B11100, B11100, B11100, B11100, B00000, B11111};
byte c4[8] = {B11111, B00000, B11110, B11110, B11110, B11110, B00000, B11111};
byte c5[8] = {B11111, B00000, B11111, B11111, B11111, B11111, B00000, B11111};
byte C[8]  = {B11100, B11110, B11111, B11111, B11111, B11111, B11110, B11100};
byte R[8]  = {B00000, B11110, B10001, B10001, B11110, B10100, B10010, B10001};
byte L[8]  = {B00000, B10000, B10000, B10000, B10000, B10000, B10000, B11111};

// Set the LCD's pin number through the constructor
LiquidCrystal_I2C lcd(0x27, 16, 2);

// Range of dB values
int valori[] = {-75, -50, -40, -30, -20, 0, 5, 10};

/*
   Variables:
   - valore -> The value of volts read using Arduino
   - i -> A simple counter
   - dbVU -> The value of dbVU obtained by valore
   - livello -> A counter for the rectangles of the VU Meter
*/
int valore = 0, i = 0;
double dbVU;
int livello;

void setup()
{
  // set up the LCD's number of columns and rows
  lcd.init();
  lcd.backlight();

  // set up the pin from which to read the value
  pinMode(A1, INPUT);

  /*
     Creation of the custom characters to be used
     on the display
  */
  lcd.createChar(1, c1);
  lcd.createChar(2, c2);
  lcd.createChar(3, c3);
  lcd.createChar(4, c4);
  lcd.createChar(5, c5);
  lcd.createChar(6, c0);
  lcd.createChar(7, C);
  lcd.createChar(8, L);
  lcd.createChar(9, R);

  // Loading initial screen
  lcd.setCursor(0, 0);
  lcd.print("    VU METER    ");
  delay(500);

  // Loading line
  for ( livello = 0; livello < 16; livello++ )
  {
    lcd.setCursor(livello, 1);
    lcd.write(5);
    delay(100);
  }

  delay(500);

  lcd.clear();

  delay(500);

}

void loop()
{
  // Letter L - LEFT
  lcd.setCursor(0, 0);
  lcd.write(8);
  // Letter R - RIGHT
  lcd.setCursor(0, 1);
  lcd.write(9);

  // Delimitation symbol C
  lcd.setCursor(15, 0);
  lcd.write(7);
  lcd.setCursor(15, 1);
  lcd.write(7);

  delay(100);

  // Voltage reading from the pin A1
  do
  {
    valore = analogRead(A1);
  } while ( valore <= 5 );

  /*
     1) valore*0.0049 -> Arduino reads 4.9 mV to unit, so this is
     the conversion to volt
     More info at: https://www.arduino.cc/reference/it/language/functions/analog-io/analogread/
     2) valore*0.0049/1.2 -> 1.2 represents the value of SOL ( Standard Operative Level ), used
     to indicate the maximum level to avoid the saturation, for VU Meter is 1.2 Volt
     3) 20*log( valore*0.0049/1.2 ) -> This is the formula to converts values from volt to
     db for VU Meter, dbVU
  */
  dbVU = (log10((double)valore * 0.0049 / 1.2)) * 20;

  // Setting the new rectangles
  for (int i = 0, livello = 1; i < 7; i++)
  {
    if (valori[i] < dbVU) {
      lcd.setCursor(livello, 0);
      lcd.write(5);
      lcd.setCursor(livello++, 1);
      lcd.write(5);
      delay(50);
      lcd.setCursor(livello, 0);
      lcd.write(5);
      lcd.setCursor(livello++, 1);
      lcd.write(5);
    }
  }

  // Removing the old rectangles
  for (int i = 7, livello = 14; i > 0; i--)
  {
    if (valori[i] > dbVU)
    {
      lcd.setCursor(livello, 0);
      lcd.write(6);
      lcd.setCursor(livello--, 1);
      lcd.write(6);
      delay(50);
      lcd.setCursor(livello, 0);
      lcd.write(6);
      lcd.setCursor(livello--, 1);
      lcd.write(6);
    }
  }

  delay(100);

}