... and yes, that's yours truly spinning that fantastic black plastic for your dancing pleasure .
So time has moved on , and so has technology a bit, and rather than slamming many amps into incandescent halogen headlamp bulbs, we can now get some very bright LED lamps, I'm also going to drive one of those RGB strips that are so popular (and inexpensive) these days.
The original controller had a microphone amplifier, which fed a low pass filter centered on 70Hz ish. This was buffered and fed to a rectifier, and buffered again. This fed two time constants, one short and one long. The long time constant would drift up and down with the level of our bass line, and the short time constant would pick out the peaks. The outputs from the two time constants were fed into a comparator, and when the peak (short time constant) output exceeded the long time constant, a "beat" was deemed detected and the output pulsed to "high". This pulse was sent to a PIC micro, which switched on some meaty FETS in varying patterns to switch the lamps on.
So, how can we improve on this? I wish to do away with the analogue beat detection and attempt it in software. I spent many hours tweaking and adjusting the time constants in the last thing to get it to run right, but this could be done in minutes in software.
I've also been searching the internet for other people's solutions. There are many threads on boards, many circuits and many ideas. The only one which would apparently work has now had it's code removed as it's become a commercial project (boo hiss). So we'll build the thing up from scratch.
Filtration. Now we need to filter out those bass notes. I'd really like to perform an FFT here, but, having tried it (and made it work up to a point) there's not enough clock cycles left to do anything else.
There are plenty of designs of sound to light (or light organ) using the MSGEQ7 IC from mixed-signal. Looks ideal. Sparkfun have provided us with a lovely datasheet here. Now, whilst perusing the datasheet, take good note of pin 6. Marked up GND. Now whatever you do , don't connect it to ground. It's an internal voltage reference. It's half of the supply rail (so usually about ~2.5V). Grounding it buggers up the IC. Don't ask me how I know....
So we send this little guy a reset pulse, and then we send him a strobe pulse. After each strobe pulse he dutifully outputs a voltage corresponding to the amplitude of the appropriate frequency band. There are 7 bands.
This output voltage is then sent to the arduino. To detect beats, I'm only interested in the lowest frequencies, which is the 63Hz channel. So I pick off this value and compare it to a default value of 126. This is our AGC value. Id it's greater than our AGC , we call it a beat, and increment our AGC value. If it's less we decrement our AGC... and repeat....
Now the beats are filtered we can flash our lights on and off in time to the music. (you could also implement the same code to measure the BPM of tracks.)
Now, we've also got stored the other values from our MSGEQ7, so we can use those to drive our RGB strip. I had originally averaged the low frequencies for red, mids for green and highs for blue, but it averages out too well to give off shades of white most of the time , so I now only pick individual frequency bands. I've also allowed those bands to be weighted a bit, to prevent it being primarily blue.
Here's the diagram. (Note IC 2 pin 6 isn't grounded!!)
The arduino outputs are used to drive some large FET's. These are capable of many amps of drive should you get really carried away (obviously your power supply and wiring will need to be man enough for the job). Connect the anode of your LED lamp here.
There's also a speed pot and stobe switch. These strobe all the outputs (and also pin 13) on and off together for a stoboscopic effect. This is time limited in the software, so if the DJ gets heavy handed, all of the clientelle won't have a seizure.
All assembled on perfboard and mounted in a beer-proof die cast box from Hammond.
Completed, with remote control for Strobe and speed.
Did I mention it was important not to ground pin 6? ... I did? Oh good. Don't ground it.
The MSGEQ7 is a fragile little IC. Check, check and double check your wiring before powering up.
I'll post a video up in the very near future of it all set up and working in situ.
Here's the code:-
/* Prom Vaults Ligting Controller Version 1.0 Written by Andy Doswell 2015 License: The MIT License (See full license at the bottom of this file) Schematic can be found at www.andydoz.blogspot.com/ A sound to light controller using the MSGEQ7 filter IC to analyse audio. Software extracts the beat, and uses this to trigger lighting effects. Also has "light organ" and Strobe effects. */ int analogPin = 0; // MSGEQ7 OUT 3 int strobePin = 2; // MSGEQ7 STROBE 4 int resetPin = 4; // MSGEQ7 RESET 7 int channel_1 = 12; // Light output channel 1 int channel_2 = 11; // light output channel 2 int channel_3 = 10; // light output channel 3 int channel_4 = 9; // light output channel 4 int channel_red = 6; // Red output channel int channel_green = 5; // Green output channel int channel_blue = 3; // Blue output channel int channel_strobe = 13; // stoboscope trigger on pin 13 // RGB outputs are on pins 3,5 and 6 respectively int stroboPin = 7; // Not a stobe as in data , but stobe as in rapidly flashing lights. Active low. int stroboOutPin = 13; // Output trigger for stroboscope light int speedPotPin = A1; // Strobe speed pot connected here, and to +5V and GND int strobeDelay; // delay for strobe effect int strobeTime = 30000; // maximum time allowable for strobe operartion unsigned long strobeClock = 0; int strobeLocked = 0; int spectrumValue[7]; // sets the array for the output from the MSGEQ7 int filterValue = 80; // cuts out the noise from the input, like a squelch control. int beatAGC = 126; //set Beat detect AGC to halfway int AGCInc = 150; //Sets the rate at which the AGC is incremented (attack) int AGCDec = 1; //Sets the rate at which the AGC is decremented (decay) int AGCLowLimit = 40; // Sets the lower limit of the beat detector AGC. Has a dramatic effect on beat detection. int beatDebounce = 200; // bit of a debounce delay for the beat detector int beatTimer = 100; // counter for the beat debounce int beatFlag = 0; // set high when the level from the low frequency channel exceeds the AGC level int pattern = 0; // set default pattern int beat = 0; // global beat counter for the changing RGB mode int RGBmode = 0; // default RGB mode int beatcount = 0; // beat counter for change of RGB output if in RGB mode. // Gains of each channel, used to alter the "grey scale" or colour balance of the strip int redGain = 10; int greenGain = 20; int blueGain = 10; void setup() { // Read from MSGEQ7 OUT pinMode(analogPin, INPUT); // Write to MSGEQ7 STROBE and RESET pinMode(strobePin, OUTPUT); pinMode(resetPin, OUTPUT); //pinMode(beatPin, OUTPUT); // beat detector output pin pinMode(channel_1 , OUTPUT); // light channel 1 pinMode(channel_2 , OUTPUT); // light channel 2 pinMode(channel_3 , OUTPUT); // light channel 3 pinMode(channel_4 , OUTPUT); // light channel 4 pinMode(channel_strobe, OUTPUT); // Strobe trigger channel pinMode(stroboPin, INPUT_PULLUP); // Strobe switch connected between here and gnd. pinMode (speedPotPin, INPUT); // Set analogPin's reference voltage analogReference(DEFAULT); // 5V //check all channels digitalWrite(channel_1,HIGH); delay (500); digitalWrite(channel_1,LOW); delay (500); digitalWrite(channel_2,HIGH); delay (500); digitalWrite(channel_2,LOW); delay (500); digitalWrite(channel_3,HIGH); delay (500); digitalWrite(channel_3,LOW); delay (500); digitalWrite(channel_4,HIGH); delay (500); digitalWrite(channel_4,LOW); delay (500); analogWrite (channel_red,255); delay (500); analogWrite (channel_red,0); delay (500); analogWrite (channel_green,255); delay (500); analogWrite (channel_green,0); delay (500); analogWrite (channel_blue,255); delay (500); analogWrite (channel_blue,0); delay (500); // Set startup values for pins digitalWrite(resetPin, LOW); digitalWrite(strobePin, HIGH); } // following are the patterns for the 4 main channels void display_pattern1() { digitalWrite (channel_1, HIGH); digitalWrite (channel_2, HIGH); digitalWrite (channel_3, HIGH); digitalWrite (channel_4, HIGH); delay (30); digitalWrite (channel_1, LOW); digitalWrite (channel_2, LOW); digitalWrite (channel_3, LOW); digitalWrite (channel_4, LOW); } void display_pattern2() { digitalWrite (channel_1,HIGH); delay (30); digitalWrite (channel_1, LOW); } void display_pattern3() { digitalWrite(channel_2,HIGH); delay (30); digitalWrite(channel_2, LOW); } void display_pattern4() { digitalWrite(channel_3,HIGH); delay (30); digitalWrite(channel_3, LOW); } void display_pattern5() { digitalWrite(channel_4,HIGH); delay (30); digitalWrite(channel_4, LOW); } void display_pattern6() { digitalWrite (channel_1,HIGH); digitalWrite(channel_2,HIGH); delay (30); digitalWrite (channel_1,LOW); digitalWrite(channel_2,LOW); } void display_pattern7() { digitalWrite(channel_3,HIGH); digitalWrite(channel_4,HIGH); delay (30); digitalWrite(channel_3,LOW); digitalWrite(channel_4,LOW); } void display_pattern8() { digitalWrite(channel_3,HIGH); digitalWrite(channel_2,HIGH); delay (30); digitalWrite(channel_3,LOW); digitalWrite(channel_2,LOW); } void display_pattern9() { digitalWrite(channel_4,HIGH); digitalWrite(channel_2,HIGH); delay (30); digitalWrite(channel_4,LOW); digitalWrite(channel_2,LOW); } void display_pattern10() { digitalWrite(channel_4,HIGH); digitalWrite(channel_1,HIGH); delay (30); digitalWrite(channel_4,LOW); digitalWrite(channel_1,LOW); } void display_pattern11() { digitalWrite(channel_1,HIGH); delay (30); } void display_pattern12() { digitalWrite(channel_2,HIGH); delay (30); } void display_pattern13() { digitalWrite(channel_3,HIGH); delay (30); } void display_pattern14() { digitalWrite(channel_4,HIGH); delay (30); } void display_pattern15() { digitalWrite(channel_1,HIGH); digitalWrite(channel_2,HIGH); delay (30); } void display_pattern16() { digitalWrite(channel_3,HIGH); digitalWrite(channel_4,HIGH); delay (30); } void display_pattern17() { digitalWrite(channel_3,HIGH); digitalWrite(channel_2,HIGH); delay (30); } void display_pattern18() { digitalWrite(channel_4,HIGH); digitalWrite(channel_2,HIGH); delay (30); } void display_pattern19() { digitalWrite(channel_4,HIGH); digitalWrite(channel_1,HIGH); delay (30); } void display_pattern20() { digitalWrite(channel_4,HIGH); digitalWrite(channel_3,HIGH); digitalWrite(channel_2,HIGH); digitalWrite(channel_1,HIGH); } void display_pattern21() { digitalWrite(channel_4,LOW); digitalWrite(channel_3,LOW); digitalWrite(channel_2,LOW); digitalWrite(channel_1,LOW); } void strobe() { if (millis() > (strobeClock+30000)) { // if the strobe has been on for 30 seconds then inhibit strobeLocked = 1; } if (strobeLocked == 0) { // if the strobe isn't inhibited // everything on digitalWrite(channel_4,HIGH); digitalWrite(channel_3,HIGH); digitalWrite(channel_2,HIGH); digitalWrite(channel_1,HIGH); digitalWrite (channel_strobe,HIGH); analogWrite (channel_red,255); analogWrite (channel_green,255); analogWrite (channel_blue,255); strobeDelay = analogRead(speedPotPin); strobeDelay = map(strobeDelay, 0, 1023, 15, 255); delay (strobeDelay); // read the pot and delay for the returned value. // everything off digitalWrite(channel_4,LOW); digitalWrite(channel_3,LOW); digitalWrite(channel_2,LOW); digitalWrite(channel_1,LOW); digitalWrite (channel_strobe,LOW); analogWrite (channel_red,0); analogWrite (channel_green,0); analogWrite (channel_blue,0); delay (strobeDelay); if (digitalRead (stroboPin) == LOW ) { strobe (); } } } void getSpectrum () { // enables the MSQEQ7 and gets the data // Set reset pin low to enable strobe digitalWrite(resetPin, HIGH); digitalWrite(resetPin, LOW); // Get all 7 spectrum values from the MSGEQ7 for (int i = 0; i < 7; i++) { digitalWrite(strobePin, LOW); delayMicroseconds(30); // Allow output to settle spectrumValue[i] = analogRead(analogPin); // Constrain any value above 1023 or below filterValue spectrumValue[i] = constrain(spectrumValue[i], filterValue, 1023); // Remap the value to a number between 0 and 255 spectrumValue[i] = map(spectrumValue[i], filterValue, 1023, 0, 255); digitalWrite(strobePin, HIGH); } } void loop() { if ((digitalRead(stroboPin) == LOW) && strobeLocked ==0) { // if the strobo is set, and isn't inhibited strobeClock = millis (); strobe(); } if ((strobeLocked == 1 && (millis() >= (strobeClock+90000)))) { // if the strobo is inhibited and 90 seconds have passed since it was last activated then unlock strobeLocked = 0; } getSpectrum (); //gets the current values from the MSGEQ7 beatAGC = constrain (beatAGC,AGCLowLimit,255); // ensures the AGC is between the filter setting and the maximum if (spectrumValue[0] > beatAGC) { beatFlag = 1; beatAGC = beatAGC+AGCInc; //increase the AGC if it's been set. Changing the values of the AGCInc & AGCDec (attack and decay) has a quite marked effect on the performance of the beat detector } else { //decrease the ACG if it hasn't beatAGC = beatAGC-AGCDec; beatFlag = 0; } if (beatFlag == 1 && beatTimer <= 0) { beatFlag=0;// reset the beat flag beatTimer = beatDebounce; // dont allow a trigger again for a number of program cycles pattern = random(24); // select a random pattern and call it if (pattern == 0) { display_pattern1(); } if (pattern == 1 ) { display_pattern2(); } if (pattern == 2 ) { display_pattern3(); } if (pattern == 3 ) { display_pattern4(); } if (pattern ==4 ) { display_pattern5(); } if (pattern ==5 ) { display_pattern6(); } if (pattern ==6) { display_pattern7(); } if (pattern ==7) { display_pattern8(); } if (pattern ==8 ) { display_pattern9(); } if (pattern ==9 ) { display_pattern10(); } if (pattern == 10) { display_pattern11(); } if (pattern == 11 ) { display_pattern12(); } if (pattern == 12 ) { display_pattern13(); } if (pattern == 13 ) { display_pattern14(); } if (pattern ==14 ) { display_pattern15(); } if (pattern ==15 ) { display_pattern16(); } if (pattern ==16) { display_pattern17(); } if (pattern ==17) { display_pattern18(); } if (pattern ==18 ) { display_pattern19(); } if (pattern ==19 ) { display_pattern20(); } if (pattern ==20 ) { display_pattern1(); } if (pattern ==21 ) { display_pattern21(); } if (pattern ==22 ) { display_pattern20(); } if (pattern ==23 ) { display_pattern20(); } beat ++; // increament the beat counter if (beat % 16 ==0) { // every 16 beats change the RGB mode beatcount = 5; RGBmode = random (3); } // cycles through primary / tertiary colours every beat if (RGBmode == 1 && beatcount == 0) { analogWrite (channel_red,255); analogWrite (channel_green,0); analogWrite (channel_blue,0); beatcount = 5; } if (RGBmode == 1 &&beatcount == 1) { analogWrite (channel_green,255); analogWrite (channel_red,0); analogWrite (channel_blue,0); beatcount --; } if (RGBmode == 1 &&beatcount == 2) { analogWrite (channel_blue,255); analogWrite (channel_green,0); analogWrite (channel_red,0); beatcount --; } if (RGBmode == 1 &&beatcount == 3) { analogWrite (channel_red,255); analogWrite (channel_green,255); analogWrite (channel_blue,0); beatcount --; } if (RGBmode == 1 &&beatcount == 4) { analogWrite (channel_red,255); analogWrite (channel_green,0); analogWrite (channel_blue,255); beatcount --; } if (RGBmode == 1 &&beatcount == 5) { analogWrite (channel_red,0); analogWrite (channel_green,255); analogWrite (channel_blue,255); beatcount --; } // cycles through random colours every beat if (RGBmode == 2) { analogWrite (channel_red,(random (256))); analogWrite (channel_green,(random (256))); analogWrite (channel_blue,(random (256))); } } beatTimer --; // provides some debounce to the beat detector beatTimer = constrain(beatTimer,-1,beatDebounce); if (beatTimer == 0){ display_pattern21(); } if (RGBmode == 0) { // spectral RGB output (multiplication factor changes the scaling of each colour) analogWrite (channel_red,((spectrumValue[1])/10)*redGain); analogWrite (channel_green,((spectrumValue[3])/10)*greenGain); analogWrite (channel_blue,((spectrumValue[6])/10)*blueGain); } } /* * Copyright (c) 2015 Andrew Doswell * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHOR(S) OR COPYRIGHT HOLDER(S) BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */
Hola.
ReplyDeleteTe consulto, no tendrás a mano el circuito detector de beats analógico que usabas anteriormente al hecho con arduino?
DeleteTengo el circuito, pero no puedo publicarlo aquí, ¿y me proporciona su correo electrónico?
You are the best! Thank you very much!
ReplyDeleteThis is the best beat detection software I've come across so far! Thank you very much!
ReplyDeletethank you
ReplyDeletei just want code to find beat per min of a song is that possible?
ReplyDeleteIt is possible to have code just to count beats per min of music?
ReplyDeleteYou will have to re-write the code to set up a timer to run on
Deleteif (beatFlag == 1 && beatTimer <= 0)
and average that over , say 10 seconds and multiply by 60 to provide a BPM.
Can I just take the timer part of your code out or the whole code has to be re written. I just want to detect BPM for a project
ReplyDeleteTake whatever you need. Certainly the outputs to drive the lights are redundant.
DeleteThis is the code i stripped from your original for the BPM.... i debugged it and it checked out... Do you think this will work?
ReplyDeleteint analogPin = 0; // MSGEQ7 OUT 3
int spectrumValue[7]; // sets the array for the output from the MSGEQ7
int filterValue = 80; // cuts out the noise from the input, like a squelch control.
int beatAGC = 126; //set Beat detect AGC to halfway
int AGCInc = 150; //Sets the rate at which the AGC is incremented (attack)
int AGCDec = 1; //Sets the rate at which the AGC is decremented (decay)
int AGCLowLimit = 40; // Sets the lower limit of the beat detector AGC. Has a dramatic effect on beat detection.
int beatDebounce = 200; // bit of a debounce delay for the beat detector
int beatTimer = 100; // counter for the beat debounce
int beatFlag = 0; // set high when the level from the low frequency channel exceeds the AGC level
int pattern = 0; // set default pattern
int beat = 0; // global beat counter for the changing RGB mode
int RGBmode = 0; // default RGB mode
int beatcount = 0; // beat counter for change of RGB output if in RGB mode.
// Gains of each channel, used to alter the "grey scale" or colour balance of the strip
int redGain = 10;
int greenGain = 20;
int blueGain = 10;
void setup() {
// Read from MSGEQ7 OUT
pinMode(analogPin, INPUT);
//pinMode(beatPin, OUTPUT); // beat detector output pin
// Set analogPin's reference voltage
analogReference(DEFAULT); // 5V
}
void getSpectrum () { // enables the MSQEQ7 and gets the data
// Get all 7 spectrum values from the MSGEQ7
for (int i = 0; i < 7; i++)
{
delayMicroseconds(30); // Allow output to settle
spectrumValue[i] = analogRead(analogPin);
// Constrain any value above 1023 or below filterValue
spectrumValue[i] = constrain(spectrumValue[i], filterValue, 1023);
// Remap the value to a number between 0 and 255
spectrumValue[i] = map(spectrumValue[i], filterValue, 1023, 0, 255);
}
}
void loop() {
getSpectrum (); //gets the current values from the MSGEQ7
beatAGC = constrain (beatAGC,AGCLowLimit,255); // ensures the AGC is between the filter setting and the maximum
if (spectrumValue[0] > beatAGC) {
beatFlag = 1;
beatAGC = beatAGC+AGCInc; //increase the AGC if it's been set. Changing the values of the AGCInc & AGCDec (attack and decay) has a quite marked effect on the performance of the beat detector
}
else { //decrease the ACG if it hasn't
beatAGC = beatAGC-AGCDec;
beatFlag = 0;
}
if (beatFlag == 1 && beatTimer <= 0 / 10 *60) {
}
}
It's a start , you can lose those red green and blue values. Leave an email here (I won't publish it), and I'll drop you a line. Two caveats, I WILL NOT WRITE THE CODE FOR YOU! You won't learn anything that way! and I'm a busy guy, there may be a delay in replying!
DeleteI got the code to work but it displays the BPM in the 500s. Can you take a look at the code for assistance... I just need to see what im doing wrong... I have no problem fixing the code by myself. I think I just have the equation wrong.
DeleteJust wondering. I am going for a more vintage vibe, would this also work for regular incandescent light bulbs? Thanks!
ReplyDeleteYep. In fact it was used to drive some car headlamp bulbs. Might be worth switching the FETs out for logic drive variants to cut down on dissipation, but I never had an issue.
Delete