/* 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()