Despite my pidgin-level recollection of c++, have hammered out some base code to get things running in a crude approximation of the way I want.
The Basics:
Attached to my Arduino are:
- A mini sound sensor (5v, outputs an analog signal from 0-1024 dependent on the amplitude of the sound sensed.)
- Gnd and Power to their respective rails
- Out to A0 on the Duino
- A standard LED
- GND to rail (via 110k ohm resistor)
- Anode to pin 8 on the Duino
- An RGB LED
- GND to rail
- Red to pin 3 on the Duino (via 110k ohm resistor)
- Green to pin 5 on the Duino (via 110k ohm resistor)
- Red to pin 7 on the Duino (via 110k ohm resistor)
Please see the somewhat dark and fuzzy picture below.
Did an initial test by doing a Serial.print(analogRead(0)) and firing it off. Sure enough, I saw a stream of values from 1 to around 300 flying in on the serial reader. Great! Fired off another test, using the map function to convert the analog signal range of 0-1024 to the RGB's range of 0-255 and fired the mapped value off to the RGB LED's red pin. Uploaded the code, fired up some Lindsey Stirling on iTunes and let there be light!
One minor problem, though... while the peak signal range of the sound sensor was supposedly 1024, I couldn't get it to read anything above 400, even by tapping on the mic. As such, I would only get a dim red. Decided to:
- Track the average sound level in the room (this would help it only pick up beats, and compensate for low-level conversation etc.)
- Only fire off IF the sound detected was above the average level AND multiply the value by how many times louder than the average detected sound value (trimming it if it went above the peak level (in this case 255))
Code for the calculation function follows:
int getRelativeValue(int val, int avg, int peak)
{
// Map the sensitivity level (controlled by the pin
int sensitivity=map(analogRead(sensePin),0,1023,0,128);
int output=val;
if (val>avg)
{
double mult=2;
if (avg>0)
{
// Set the multiplier by how many times greater the value is.
mult=val/avg;
}
// multiply the value by the weighed multiplier
output=val*mult+sensitivity;
if (output>peak)
{
output=peak;
}
}
return output;
}
Significant improvement. After a few seconds of acclimating to the University of Texas' Danserye, the red pin showed a much wider spectrum, going bright on the drum beats and trumpet blasts and soft on the bells and flutes.
One thing I didn't like, though. This was an RGB LED, and I was only firing on red. There were two other lamps I wasn't even touching. The idea of firing a random color appealed to me. The idea of periodically changing that color (based on both a randomly set number of ms and the strength of the sound wave,) rocked my world. Created a simple case switch routine to first randomly determine the dominant color, then the strengths of the other two (I created a 5-way switch that stepped, by .25, from 0 to 1.) Code follows:
double calcRandomPerc(int i)
{
double output=0;
switch(i)
{
case 1:
break;
case 2:
output=0.25;
break;
case 3:
output=0.5;
break;
case 4:
output=0.75;
break;
default:
output=1;
break;
}
return output;
}
This double would be multiplied against the value of the detected sound... so if the detected sound were 255, Red was 1, Green was 0 and Blue was .5, Red would be 255, Green would be 0 and Blue would be 128... a faint purple. Threw in a counter, which I incremented by the delay value plus the strength of the beat; once this counter exceeded a randomized ceiling between 3,000 and 5,000 the color would be reset. This, again, worked pretty well.
Added the static (in this case yellow) LED to the mix next, but after many tries couldn't get it to light. After about half an hour of troubleshooting, discovered that this was because of the way I was passing my LED's as an array of a
custom LED class to my music pulsing function. I'm so used to manipulating enumerations of objects using C#'s List, I forgot that if you want to pass arrays as arguments, you have to pass the number of items contained within the arrays as a constant, and ended up never changing this constant from what I had initially set it to: 1. As such, it was only looping over the first item in the array (the RGB LED.) Once this rookie mistake was resolved, the yellow LED flashed with the RGB.
After watching it for a while, I discovered that this wasn't precisely what I wanted it to do. The digital yellow almost always blazed over the more undestated analog RGB. Experimented around with values until I ended up only having the LED fire if the mapped sound range was over 128. Voila, the yellow would only flash on the stronger beats.
The code:
if (soundNum>128)
{
fireLed(ledList[thisPin], false);
}
After a night's rest, I decided to add two potentiometers to the sketch, allowing my wife to fine-tune the sensitivity to the environment (this could also potentially be employed to change sparkle/chase speed etc.) One potentiometer would create a modifier that would be weight the sound value, the other could be adjusted to make the static LED's more sensitive. Drew up (and compiled) the code for this, but haven't tested it yet.
#include <LED.h>
Led ledList[]={Led(3,5,6), Led(8)};
const int soundPin=0;
void setup()
{
Serial.begin(9600);
}
void loop()
{
musicPulse(ledList,2,1000);
}
int getRelativeValue(int val, int avg, int peak)
{
int output=val;
if (val>avg)
{
double mult=2;
if (avg>0)
{
// Set the multiplier by how many times greater the value is.
mult=val/avg;
}
do {
// multiply the value by the weighed multiplier
output=val*mult;
//If too high, step multiplier down by 10%
mult=mult*.9;
} while (output>peak);
}
return output;
}
double calcRandomPerc(int i)
{
double output=0;
switch(i)
{
case 1:
break;
case 2:
output=0.25;
break;
case 3:
output=0.5;
break;
case 4:
output=0.75;
break;
default:
output=1;
break;
}
return output;
}
void musicPulse(Led ledList[],int arraySize,long seconds)
{
int valCount=0;
long valTot=0;
int newPeak=0;
int countPeak=30000;
// initialize divisors
double redDiv=1;
double blueDiv=1;
double greenDiv=1;
int count=0;
// read from the sound sensor
do {
int val=analogRead(soundPin);
// add this to the total (to calculate avg)
valTot=valTot+val;
valCount=valCount+1;
// If the reading is above 100, proceed. Else wait
if (val>0)
{
if (count==0)
{
// Determine the color ratio
redDiv=calcRandomPerc(random(1,5));
blueDiv=calcRandomPerc(random(1,5));
greenDiv=calcRandomPerc(random(1,5));
// set the focal color (the one set to 1.)
switch (random(1,3))
{
case 1:
redDiv=1;
break;
case 2:
blueDiv=1;
break;
case 3:
greenDiv=1;
break;
}
// randomly set the next count time
countPeak=random(5000,60000);
}
if (count%100==0)
{
newPeak=newPeak/2;
}
int soundNum=map(val,0,1023,0,255);
if (val>newPeak)
{
newPeak=val;
}
// Calculate the average.
double valAvg=(valTot/valCount);
soundNum=getRelativeValue(soundNum,valAvg,254);
for (int thisPin = 0; thisPin < arraySize; thisPin++) {
int i=ledList[thisPin].RPin();
if(ledList[thisPin].IsRGB())
{
// Set the color divisors to create a random color.
// The color will last 30 seconds. If the count is zero, set the colors.
// Pass the value to the effected pins
ledList[thisPin].SetRed(soundNum*redDiv);
ledList[thisPin].SetBlue(soundNum*blueDiv);
ledList[thisPin].SetGreen(soundNum*greenDiv);
// Light the bulb
fireLed(ledList[thisPin], false);
}
// If it's a standard LED...
else {
// if the latest reading is higher than average, light the LED.
if (val>valAvg+(newPeak/2)&&(soundNum>128))
{
Serial.println("should've fired.");
fireLed(ledList[thisPin], false);
}
else {
Serial.println("didnt fired.");
}
}
// Step up the count by the delay value (30 ms)
count=count+30+soundNum*2;
// if the count has exceeded the peak count time, set the count to 0 (the color will randomize on the next loop.)
if (count>countPeak)
{
count=0;
}
}
delay(30);
// Turn off the pin
for (int thisPin = 0; thisPin < arraySize; thisPin++) {
turnOffLed(ledList[thisPin]);
}
// if the potentiometer is set below 800, switch.f
}
} while(true); // Puts us in a loop until the potentiometer is changed.
}
void fireLed(Led led, bool rand)
{
// Randomize the color
if (rand&&(led.IsRGB()))
{
led.SetRandomColor();
led.SetRed(255);
}
if (led.IsRGB())
{
// Fire red, green, then blue
analogWrite(led.RPin(),led.Red());
analogWrite(led.GPin(),led.Green());
analogWrite(led.BPin(),led.Blue());
}
else
{
digitalWrite(led.RPin(),HIGH);
}
}
void turnOffLed(Led led)
{
if (led.IsRGB())
{
// If it's an RGB, turn off red, green, then blue
analogWrite(led.RPin(),0);
analogWrite(led.GPin(),0);
analogWrite(led.BPin(),0);
}
else
{
digitalWrite(led.RPin(),LOW);
}
}