// Modified code by Christian Suryanto, from (c) 2019 Shajeeb TM
// HAZI TECH
// Updated by Christian Suryanto
// 

#include <arduinoFFT.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
#include <EEPROM.h>

#define HARDWARE_TYPE MD_MAX72XX::FC16_HW                     // Set display type  so that  MD_MAX72xx library treets it properly
#define CLK_PIN   13                                          // Clock pin to communicate with display
#define DATA_PIN  11                                          // Data pin to communicate with display
#define CS_PIN    10                                          // Control pin to communicate with display

#define SAMPLES 64                                           // Must be a power of 2
#define MAX_DEVICES  4                                        // Total number display modules
#define  xres 32                                              // Total number of  columns in the display, must be <= SAMPLES/2
#define  yres 8                                               // Total number of  rows in the display

#define PREV 0xFF02FD // address is FFA25D but 0x is added because this is how the arduino is told that it is HEXADECIMAL.
#define NEXT 0xFFC23D // control stop code
#define PWR 0xFFA25D // control Power

int audio_response = 15;                                      // put a value between 10 and 80. Smaller the number, higher the audio response

double vReal[SAMPLES];
//double vReal2[SAMPLES];
double vImag[SAMPLES];
char data_avgs[xres];

int yvalue;
int displaycolumn , displayvalue;
int peaks[xres];
const int buttonPin = 6;                                      // the number of the pushbutton pin
int state = HIGH;                                             // the current reading from the input pin
int previousState = LOW;                                      // the previous reading from the input pin
int displaymode; 
unsigned long lastDebounceTime = 0;                           // the last time the output pin was toggled
unsigned long debounceDelay = 50;                             // the debounce time; increase if the output flickers

//int MY_ARRAY[]={0, 128, 192, 224, 240, 248, 252, 254, 255};   // default = standard pattern
int MY_MODE_1[]={0, 128, 192, 224, 240, 248, 252, 254, 255};  // standard pattern
//int MY_MODE_2[]={0, 128, 64, 32, 16, 8, 4, 2, 1};             // only peak pattern
//int MY_MODE_3[]={0, 128, 192, 160, 144, 136, 132, 130, 129};  // only peak +  bottom point
//int MY_MODE_4[]={0, 128, 192, 160, 208, 232, 244, 250, 253};  // one gap in the top , 3rd light onwards

bool EQ_ON = true; // set to false to disable eq

byte eq1[32] = {40, 45, 50, 60, 65, 70, 75, 95,
               110, 110, 110, 110, 110, 110, 110, 110,
               130, 130, 130, 130, 130, 130, 130, 130,
               145, 155, 170, 180, 215, 220, 245, 255
              };


byte eq2[11] = {40, 70, 75, 110, 110, 140, 145, 220, 220, 230, 250};


MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);   // display object
arduinoFFT FFT = arduinoFFT();                                    // FFT object
 

void setup() {
    EEPROM.update(1,1);                                           //(memory address, value), RUN THIS FOR THE FIRST TIME
    displaymode = EEPROM.read(1);
    //displaymode = 1;
    ADCSRA = 0b11100101;                                          // set ADC to free running mode and set pre-scalar to 32 (0xe5)
    ADMUX = 0b00000000;                                           // use pin A0 and external voltage reference
    pinMode(buttonPin, INPUT);
    mx.begin();                                                   // initialize display
    mx.control(MD_MAX72XX::INTENSITY, 0);                         // set LED intensity
    delay(50);                                                    // wait to get reference voltage stabilized
}
 
void loop() {
   // ++ Sampling

   int numData;
   double rSum;

   for(int i=0; i<SAMPLES; i++)
    {
      while(!(ADCSRA & 0x10));                                    // wait for ADC to complete current conversion ie ADIF bit set
      ADCSRA = 0b11110101 ;                                       // clear ADIF bit so that ADC can do next operation (0xf5)
      int value = ADC - 512 ;                                     // Read from ADC and subtract DC offset caused value
      value = value / 8;
      vReal[i]= value;                                          // Copy to bins after compressing
      vImag[i] = 0;                         
    }
    // -- Sampling

 
     //++ FFT
    FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
    FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
    FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);
    // -- FFT

    int step = (SAMPLES)/xres; 

// re-mapping data - Customize by Christian Suryanto ///
    switch (displaymode)
    {
    case 1 :
      {
        numData = 32;
        data_avgs[0] = (vReal[0] + vReal[0])/2;
        data_avgs[1] = (vReal[1] + vReal[63])/2;
        data_avgs[2] = (vReal[2] + vReal[62])/2;
        data_avgs[3] = (vReal[3] + vReal[61])/2;
        data_avgs[4] = (vReal[4] + vReal[60])/2;
        data_avgs[5] = (vReal[5] + vReal[59])/2;
        data_avgs[6] = (vReal[6] + vReal[58])/2;
        data_avgs[7] = (vReal[7] + vReal[57])/2;
        data_avgs[8] = (vReal[8] + vReal[56])/2;
        data_avgs[9] = (vReal[9] + vReal[55])/2;
        data_avgs[10] = (vReal[10] + vReal[54])/2;
        data_avgs[11] = (vReal[11] + vReal[53])/2;
        data_avgs[12] = (vReal[12] + vReal[52])/2;
        data_avgs[13] = (vReal[13] + vReal[51])/2;
        data_avgs[14] = (vReal[14] + vReal[50])/2;
        data_avgs[15] = (vReal[15] + vReal[49])/2;
        data_avgs[16] = (vReal[16] + vReal[48])/2;
        data_avgs[17] = (vReal[17] + vReal[47])/2;
        data_avgs[18] = (vReal[18] + vReal[46])/2;
        data_avgs[19] = (vReal[19] + vReal[45])/2;
        data_avgs[20] = (vReal[20] + vReal[44])/2;
        data_avgs[21] = (vReal[21] + vReal[43])/2;
        data_avgs[22] = (vReal[22] + vReal[42])/2;
        data_avgs[23] = (vReal[23] + vReal[41])/2;
        data_avgs[24] = (vReal[24] + vReal[40])/2;
        data_avgs[25] = (vReal[25] + vReal[39])/2;
        data_avgs[26] = (vReal[26] + vReal[38])/2;
        data_avgs[27] = (vReal[27] + vReal[37])/2;
        data_avgs[28] = (vReal[28] + vReal[36])/2;
        data_avgs[29] = (vReal[29] + vReal[35])/2;
        data_avgs[30] = (vReal[30] + vReal[34])/2;
        data_avgs[31] = (vReal[31] + vReal[33])/2;
      }
      break;

    case 2 :
      {
        numData = 11;
        data_avgs[0] = (vReal[0] + vReal[0])/2;
        data_avgs[1] = (vReal[0] + vReal[0] + vReal[1] + vReal[63]) / 4;
        data_avgs[2] = ( vReal[1] + vReal[63] + vReal[2] + vReal[62] + vReal[3] + vReal[61])/6;
        data_avgs[3] = (vReal[2] + vReal[62] + vReal[3] + vReal[61] + vReal[4] + vReal[60])/6;
        data_avgs[4] = (vReal[5] + vReal[59] + vReal[6] + vReal[58] + vReal[7] + vReal[57])/6;
        data_avgs[5] = (vReal[8] + vReal[56] + vReal[9] + vReal[55] + vReal[10] + vReal[54] + vReal[11] + vReal[53])/8;
        data_avgs[6] = (vReal[12] + vReal[52] + vReal[13] + vReal[51] + vReal[14] + vReal[50] + vReal[15] + vReal[49])/8;
        data_avgs[7] = (vReal[16] + vReal[48] + vReal[17] + vReal[47] + vReal[18] + vReal[46])/6;
        data_avgs[8] = (vReal[19] + vReal[45] + vReal[20] + vReal[44] + vReal[21] + vReal[43] + vReal[22] + vReal[42])/8;
        data_avgs[9] = (vReal[23] + vReal[41] + vReal[24] + vReal[40] + vReal[25] + vReal[39] + vReal[26] + vReal[38] + vReal[27] + vReal[37])/10;
        data_avgs[10] = (vReal[28] + vReal[36] + vReal[29] + vReal[35] + vReal[30] + vReal[34] + vReal[31] + vReal[33])/8;
      }
      break;
    }
// re-mapping data - Customize by Christian Suryanto ///

    for(int i=0; i<numData; i++)
    {
      data_avgs[i] = data_avgs[i] / 2;
      if (EQ_ON)
        switch (displaymode)
        {
          case 1 : data_avgs[i] = (data_avgs[i]) * (float)(eq1[i]) / 100; //apply eq filter
          break;
          
          case 2 : data_avgs[i] = (data_avgs[i]) * (float)(eq2[i]) / 100; //apply eq filter
          break;
        }

      data_avgs[i] = constrain(data_avgs[i],0,audio_response);              // set max & min values for buckets
      data_avgs[i] = map(data_avgs[i], 0, audio_response, 0, yres);         // remap averaged values to yres
      
      yvalue=data_avgs[i];

      peaks[i] = peaks[i]-1;    // decay by one light
      if (yvalue > peaks[i]) 
          peaks[i] = yvalue ;
      yvalue = peaks[i];    
      displayvalue=MY_MODE_1[yvalue];
      
      switch (displaymode)
      {
        case 1: 
        {
          displaycolumn=31-i;
          mx.setColumn(displaycolumn, displayvalue);                // for left to right
        }
        break;
        case 2: 
        {
          displaycolumn=31-(3*i);
          mx.setColumn(displaycolumn-1, displayvalue);                // for left to right
          mx.setColumn(displaycolumn, displayvalue);                // for left to right
        }
        break;
      }
     }
     // -- send to display according measured value 
     
    displayModeChange ();                                       // check if button pressed to change display mode
}

void displayModeChange() {
  int reading = digitalRead(buttonPin);
  if (reading == HIGH && previousState == LOW && millis() - lastDebounceTime > debounceDelay) // works only when pressed
  {
    switch (displaymode) 
     {
      case 1:    //       move from mode 1 to 2
        displaymode = 2;
        mx.clear();
        delay(200);
        EEPROM.update(1,2); 
        break;
      case 2:    //       move from mode 2 to 3
        displaymode = 1;
        mx.clear();
        delay(200);
        EEPROM.update(1,1); 
        break;
    }
    lastDebounceTime = millis();
  }
  previousState = reading;
}