// по материалам: 
//https://www.instructables.com/Simple-LCD-MENU-Using-Arduino/
// https://alexgyver.ru/lessons/
// https://www.hackster.io/mircemk/diy-arduino-vfd-display-20x2-vu-volume-unit-meter-37898f
// https://github.com/AlexGyver/FHTSpectrumAnalyzer/blob/master/Firmware/spertrum1602/spertrum1602.ino

#define GAIN 5 // усиление 0...50
#define STEP 20  // плавность полос 0...20
#define RL 1    // RL - горизонт, вертикаль 0...1
// Выходы для управления реле (вкл.вкл  ЦАП/Блютуз)
#define RELAY_POWER_USB 7
#define RELAY_POWER_BT 6
//#define RELAY_POWER_MP3 10
#define RELAY_OUT_USB 8
#define RELAY_OUT_BT 9

// Индикаторы включения (подключения) входов
#define OPTOCOUPLE_USB 10
#define OPTOCOUPLE_COAX 11
#define OPTOCOUPLE_BT 12
// Входы звукового сигнала для анализатора спектра
#define AUDIO_IN_L 0
#define AUDIO_IN_R 1


// Кнопки
#define BTN_MENU 2       // Кнопка меню
#define BTN_EXECUTE 3       // Кнопка подтверждения
#define INIT_ADDR 1023  // номер резервной ячейки
#define INIT_KEY 1     // ключ первого запуска

// ---------------- НАСТРОЙКИ ----------------
#define DRIVER_VERSION 0   // 0 - маркировка драйвера кончается на 4АТ, 1 - на 4Т
#define GAIN_CONTROL 0     // ручная настройка потенциометром на громкость (1 - вкл, 0 - выкл)

#define AUTO_GAIN 0       // автонастройка по громкости (экспериментальная функция)
#define VOL_THR 30        // порог тишины (ниже него отображения на матрице не будет)

#define LOW_PASS 30        // нижний порог чувствительности шумов (нет скачков при отсутствии звука)
#define DEF_GAIN 80       // максимальный порог по умолчанию (при GAIN_CONTROL игнорируется)

#define FHT_N 256          // ширина спектра х2
// вручную забитый массив тонов, сначала плавно, потом круче
byte posOffset[16] = {2, 3, 4, 6, 8, 10, 12, 14, 16, 20, 25, 30, 35, 60, 80, 100};
// ---------------- НАСТРОЙКИ ----------------

// --------------- БИБЛИОТЕКИ ---------------
#define LOG_OUT 1
#include <FHT.h>          // преобразование Хартли
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define printByte(args) write(args);
double prevVolts = 100.0;
// --------------- БИБЛИОТЕКИ ---------------
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

#include <EEPROM.h>

//  LiquidCrystal lcdVuMeter(2, 3, 4, 5, 6, 7);// RS,E,D4,D5,D6,D7
LiquidCrystal_I2C lcdVuMeter(0x27, 16, 2);
LiquidCrystal_I2C lcdService(0x26, 16, 2);

bool usb, bluetooth, mp3;
int menu = 1;
bool menuShow = false;

// ------------------------------------- ПОЛОСОЧКИ -------------------------------------
byte v1[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111};
byte v2[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111};
byte v3[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111};
byte v4[8] = {0b00000, 0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111};
byte v5[8] = {0b00000, 0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
byte v6[8] = {0b00000, 0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
byte v7[8] = {0b00000, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
byte v8[8] = {0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111, 0b11111};
// ------------------------------------- ПОЛОСОЧКИ -------------------------------------
byte gain = DEF_GAIN;   // усиление по умолчанию
unsigned long gainTimer;
byte maxValue, maxValue_f;
float k = 0.1;

void setup() {
  lcdVuMeter.init();
  lcdVuMeter.backlight();
  lcdVuMeter.clear();
  lcdService.init();
  lcdService.backlight();
  lcdService.clear();
 // lcdService.setCursor(0, 0);
//  lcdService.print("Menu");

// Кнопка управления (переключения)
  pinMode(BTN_MENU, INPUT_PULLUP);
  pinMode(BTN_EXECUTE, INPUT_PULLUP);
// Выходы для управления реле (вкл.вкл  ЦАП/Блютуз)
  pinMode(RELAY_POWER_USB, OUTPUT);
  pinMode(RELAY_POWER_BT, OUTPUT);
//  pinMode(RELAY_POWER_MP3, OUTPUT);
  pinMode(RELAY_OUT_USB, OUTPUT);
  pinMode(RELAY_OUT_BT, OUTPUT);
//  pinMode(RELAY_OUT_MP3_LEFT, OUTPUT);
//  pinMode(RELAY_OUT_MP3_RIGHT, OUTPUT);

// Входы индикаторов с USB ЦАП
  pinMode(OPTOCOUPLE_USB, INPUT_PULLUP);
  pinMode(OPTOCOUPLE_COAX, INPUT_PULLUP);
// Вход индикатора с BlueTooth адаптера
  pinMode(OPTOCOUPLE_BT, INPUT_PULLUP);
  
  //  lcdVuMeter.createChar(5,znak_l);
  //  lcdVuMeter.begin(16, 2);// lcdVuMeter 16X2
 // analogReference(INTERNAL);  // если очень маленький уровень сигнала
  pinMode(A0,INPUT);// A0 - аналоговый вход R
  pinMode(A1,INPUT);// A1 - аналоговый вход L

  // первый запуск и установка занчений переменных в память
  if (EEPROM.read(INIT_ADDR) != INIT_KEY) {
    EEPROM.write(INIT_ADDR, INIT_KEY);
    usb = true;
    bluetooth = false;
    mp3 = false;
    
    EEPROM.put(10, usb);
    EEPROM.put(12, bluetooth);
    EEPROM.put(14, mp3);
  } else {
    usb = EEPROM.read(10);
    bluetooth = EEPROM.read(12);
//    mp3 = EEPROM.read(14);  
  }

  // поднимаем частоту опроса аналогового порта до 38.4 кГц, по теореме
  // Котельникова (Найквиста) частота дискретизации будет 19 кГц
  // http://yaab-arduino.blogspot.ru/2015/02/fast-sampling-from-analog-input.html
  sbi(ADCSRA, ADPS2);
  cbi(ADCSRA, ADPS1);
  sbi(ADCSRA, ADPS0);

  // для увеличения точности уменьшаем опорное напряжение,
  // выставив EXTERNAL и подключив Aref к выходу 3.3V на плате через делитель
  // GND ---[2х10 кОм] --- REF --- [10 кОм] --- 3V3
  analogReference(EXTERNAL);

 
  if (usb) {
    digitalWrite(RELAY_POWER_BT,HIGH);
//    digitalWrite(RELAY_POWER_MP3,HIGH);
    digitalWrite(RELAY_POWER_USB,HIGH);
    delay(500);
    digitalWrite(RELAY_OUT_USB,HIGH);
    digitalWrite(RELAY_OUT_BT,LOW);
//    digitalWrite(RELAY_OUT_MP3_LEFT,HIGH);
//    digitalWrite(RELAY_OUT_MP3_RIGHT,HIGH);
  }

  if (bluetooth) {
    digitalWrite(RELAY_POWER_USB,HIGH);
//    digitalWrite(RELAY_POWER_MP3,HIGH);
    delay(500);
//    digitalWrite(RELAY_OUT_MP3_LEFT,HIGH);
//    digitalWrite(RELAY_OUT_MP3_RIGHT,HIGH);
    delay(1000);
    digitalWrite(RELAY_OUT_USB,LOW);
    digitalWrite(RELAY_OUT_BT,HIGH);
    digitalWrite(RELAY_POWER_BT,LOW);
  }
  if (mp3) {
     }
  lcdChars();   // подхватить коды полосочек 
  updateMenu();
}

void lcdChars() {
  lcdVuMeter.createChar(0, v1);
  lcdVuMeter.createChar(1, v2);
  lcdVuMeter.createChar(2, v3);
  lcdVuMeter.createChar(3, v4);
  lcdVuMeter.createChar(4, v5);
  lcdVuMeter.createChar(5, v6);
  lcdVuMeter.createChar(6, v7);
  lcdVuMeter.createChar(7, v8);
}

// Получаем тип подключенного входа к USB ЦАПу
String setUSBinput() {
  if (!digitalRead(OPTOCOUPLE_USB) && usb) {
    return "input: USB    ";
  } else if (!digitalRead(OPTOCOUPLE_COAX) && usb) {
    return "input: Coaxial";
  } else {
    return "input: Off    ";
  }
}
// Получаем статус подключения устройства к bluetooth
String setBTstatus() {
  if (!digitalRead(OPTOCOUPLE_BT) && bluetooth) {
    return " connected";
  } else {
    return "           ";
  }
}


void updateMenu() {
  switch (menu) {
    case 0:
      menu = 1;
      break;
    case 1:
      lcdService.clear();
      lcdService.print("> USB DAC");
      lcdService.setCursor(2, 1);
      if(usb){
        lcdService.print("On");
      }else{
        lcdService.print("Off");
      }
      break;
    case 2:
      lcdService.clear();
      lcdService.print("> Bluetooth");
      lcdService.setCursor(2, 1);
      if(bluetooth){
        lcdService.print("On");
      }else{
        lcdService.print("Off");
      }
      break;
//    case 3:
//      lcdService.clear();
//      lcdService.print(">MP3 module");
//      lcdService.setCursor(2, 1);
//      if(mp3){
//        lcdService.print("On");
//      }else{
//        lcdService.print("Off");
//      }
//      break;
    case 3:
      menu = 0;
      menuShow = false;
      lcdService.clear();
      break;
  }
}

void executeMenuAction() {
  switch (menu) {
    case 1:
      actionUSB();
      break;
    case 2:
      actionBT();
      break;
//    case 3:
//      actionMP3();
//      break;
  }
  EEPROM.put(10, usb);
  EEPROM.put(12, bluetooth);
//  EEPROM.put(14, mp3);
}

void actionUSB() {
//  lcdService.clear();
  lcdService.setCursor(2,1);
  lcdService.print("On ");
  digitalWrite(RELAY_POWER_BT,HIGH);
//  digitalWrite(RELAY_POWER_MP3,HIGH);
  digitalWrite(RELAY_POWER_USB,HIGH);
  delay(500);
  digitalWrite(RELAY_OUT_USB,HIGH);
  digitalWrite(RELAY_OUT_BT,LOW);
//  digitalWrite(RELAY_OUT_MP3_LEFT,HIGH);
//  digitalWrite(RELAY_OUT_MP3_RIGHT,HIGH);
  usb = true;
  bluetooth = false;
  mp3 = false;

//  delay(1500);
}

void actionBT() {
//  lcdService.clear();
  lcdService.setCursor(2,1);
  lcdService.print("On ");
  digitalWrite(RELAY_POWER_USB,HIGH);
//  digitalWrite(RELAY_POWER_MP3,HIGH);
  delay(500);
//  digitalWrite(RELAY_OUT_MP3_LEFT,HIGH);
//  digitalWrite(RELAY_OUT_MP3_RIGHT,HIGH);
  delay(1000);
  digitalWrite(RELAY_OUT_USB,LOW);
  digitalWrite(RELAY_OUT_BT,HIGH);
  digitalWrite(RELAY_POWER_BT,LOW);
  usb = false;
  bluetooth = true;
  mp3 = false;

//  delay(1500);
}

//void actionMP3() {
////  lcdService.clear();
//  lcdService.setCursor(2,1);
//  lcdService.print("On ");
//  digitalWrite(RELAY_POWER_USB,HIGH);
//  digitalWrite(RELAY_POWER_BT,HIGH);
//  delay(500);
//  digitalWrite(RELAY_OUT_USB,HIGH);
//  digitalWrite(RELAY_OUT_BT,HIGH);
//  delay(1000);
//  digitalWrite(RELAY_OUT_MP3_LEFT,LOW);  
//  digitalWrite(RELAY_OUT_MP3_RIGHT,LOW);
//  digitalWrite(RELAY_POWER_MP3,LOW);
//  usb = false;
//  bluetooth = false;
//  mp3 = true;
//  delay(1500);
//}


uint32_t btnTimer = 0;

void loop() {
  lcdService.setCursor(0,0);
  // Выводим на экран информацию какой модуль включен

  if(!menuShow){
    if(usb){
      lcdService.print("USB | S/PDIF");
      lcdService.setCursor(0,1);
      lcdService.print(setUSBinput());
//      actionUSB();
    }
    if(bluetooth){
      lcdService.print("Bluetooth");
      lcdService.setCursor(0,1);
      lcdService.print(setBTstatus());
//      actionBT();
    }
//    if(mp3){
//      lcdService.print("MP3 module");
////      actionMP3();
//    }
  }
//  bool btnMenuState = digitalRead(BTN_MENU);
//  bool btnExecuteState = digitalRead(BTN_EXECUTE);

  if (!digitalRead(BTN_MENU)){
    menuShow = true;
    menu ++;
    updateMenu();
    delay(100);
    while (!digitalRead(BTN_MENU));
  }
  if (!digitalRead(BTN_EXECUTE)){
    executeMenuAction();
    updateMenu();
    delay(100);
    while (!digitalRead(BTN_EXECUTE));
  }

//  if (GAIN_CONTROL) gain = map(analogRead(POT_PIN), 0, 1023, 0, 150);

  analyzeAudio();   // функция FHT, забивает массив fht_log_out[] величинами по спектру

  for (int pos = 0; pos < 16; pos++) {   // для окошек дисплея с 0 по 15
    // найти максимум из пачки тонов
    if (fht_log_out[posOffset[pos]] > maxValue) maxValue = fht_log_out[posOffset[pos]];

    lcdVuMeter.setCursor(pos, 0);

    // преобразовать значение величины спектра в диапазон 0..15 с учётом настроек
    int posLevel = map(fht_log_out[posOffset[pos]], LOW_PASS, gain, 0, 15);
    posLevel = constrain(posLevel, 0, 15);

    if (posLevel > 7) {               // если значение больше 7 (значит нижний квадратик будет полный)
      lcdVuMeter.printByte(posLevel - 8);    // верхний квадратик залить тем что осталось
      lcdVuMeter.setCursor(pos, 1);          // перейти на нижний квадратик
      lcdVuMeter.printByte(7);               // залить его полностью
    } else {                          // если значение меньше 8
      lcdVuMeter.print(" ");                 // верхний квадратик пустой
      lcdVuMeter.setCursor(pos, 1);          // нижний квадратик
      lcdVuMeter.printByte(posLevel);        // залить полосками
    }
  }

  if (AUTO_GAIN) {
     maxValue_f = maxValue * k + maxValue_f * (1 - k);
    if (millis() - gainTimer > 1500) {      // каждые 1500 мс
      // если максимальное значение больше порога, взять его как максимум для отображения
      if (maxValue_f > VOL_THR) gain = maxValue_f;

      // если нет, то взять порог побольше, чтобы шумы вообще не проходили
      else gain = 100;
      gainTimer = millis();
    }
  }
}
void analyzeAudio() {
  for (int i = 0 ; i < FHT_N ; i++) {
    int sample = (analogRead(AUDIO_IN_L) + analogRead(AUDIO_IN_R)) / 2;
    fht_input[i] = sample; // put real data into bins
  }
  fht_window();  // window the data for better frequency response
  fht_reorder(); // reorder the data before doing the fht
  fht_run();     // process the data in the fht
  fht_mag_log(); // take the output of the fht
}
