/* sound rainbow

   By: Andrew Tuline

   Date: Jan, 2022

   Intercept the output of a non-sound reactive animation to use sound reactivity on an Arduino Nano. As a result,
   you may be able to use almost any animation with this technique for use with sound reactivity without modifying
   the original animation.

  Instructions: 

  * Comment out the soundAdjust(); routine in the loop to see the original animation.
  * Remove the comment and see how it works with sound reactivity.


   In this case, we blended the results of a rainbow march animation combined with black based on the result of sound reactive values.

   My physical version of this was based on an Arduino Nano (old bootloader), with a MAX9814 microphone @40db gain. In that case,
   I'd connected the AREF pin to 3.3V and connected VCC of the microphone to 3.3V as well.

   Oh, and I have an unpublished/unfinished version that uses a proportional integral AGC loop along with Mark Kriegsman's demoreel100
   as well as the arduinoFFT() library for the ESP32.

   Am also considering modifying CRGB::Black with something more dynamic.

   Edit: Here's a pastebin link to a modified version of Mark Kriegsman's demoreel100.ino.

   https://pastebin.com/iA6NNUBV

*/

#define MIC_PIN A0                  // Analog port for microphone
uint8_t squelch = 1;                // Anything below this is background noise. Use a higher value with a physical microphone.
int sample;                         // Current sample.
float sampleAvg = 0;                // Smoothed Average.
float micLev = 0;                   // Used to convert returned value to have '0' as minimum.

int sampleAgc;                      // Automatic Gain Controlled value.
int multAgc;                        // Multiplier for the AGC.
uint8_t targetAgc = 60;             // This is our setPoint at 20% of max for the adjusted output.


#include <FastLED.h>

#define LED_PIN     5
#define NUM_LEDS    50
#define BRIGHTNESS  128
#define LED_TYPE    WS2811
#define COLOR_ORDER GRB
CRGB leds[NUM_LEDS];

uint8_t vals[NUM_LEDS];             // Used for sound reactivity blending.


void setup() {
  Serial.begin(115200);
  FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS);
} // setup()



void loop() {

  getSample();
  agcAvg();

  rainbow();
//  soundAdjust();             // Comment this out to view the original animation.

  FastLED.show();
}

void rainbow() {
  fill_rainbow(leds, NUM_LEDS, millis() / 10, 5);     // A real basic FastLED rainbow march.
} // rainbow()



void soundAdjust() {

  vals[millis() % NUM_LEDS] = sampleAgc;              // This requires a high refresh rate.

  for (int i = 0; i < NUM_LEDS; i++) {                // Modifying the array prior to FastLED.show().
    leds[i] = blend(CRGB::Black, leds[i], vals[i]);   // vals[i] is a uint8_t, which determines the blending between the two colours for the resultant value.
  }

} // soundAdjust()



void getSample() {

  int16_t micIn;                                              // Current sample starts with negative values and large values, which is why it's 16 bit signed.

  micIn = analogRead(MIC_PIN);                                // Poor man's analog read.
  micLev = ((micLev * 31) + micIn) / 32;                      // Use exponential smoothing over the last 32 samples for automatic centering.
  micIn -= micLev;                                            // Let's center it to 0. This allows us to use ANY microphone without having to worry about offset calculations.
  micIn = abs(micIn);                                         // And get the absolute value of each sample.

  sample = (micIn <= squelch) ? 0 : (sample + micIn) / 2;     // Let's filter out the background noise with a ternary operator and more exponential smoothing.
  sampleAvg = ((sampleAvg * 31) + sample) / 32;               // Smooth it out over the last 32 samples. This is a non AGC average.

} // getSample()



void agcAvg() {                                                   // A simple averaging multiplier to automatically adjust sound sensitivity.
  multAgc = (sampleAvg < 1) ? targetAgc : targetAgc / sampleAvg;  // Make the multiplier so that sampleAvg * multiplier = setpoint
  sampleAgc = sample * multAgc;
  if (sampleAgc > 255) sampleAgc = 255;
} // agcAvg()