Thursday, February 10, 2011

Color Organ / Spectrum Analyzer on Arduino


                                                              
 The basic idea was to create color organ / spectrum analyzer on arduino board, trying to minimize quantity of external components, like analog filters, LED display drivers, etc.
Spend a lot of time in search on internet I was able to find only two ! project ,  which implemented FFT in order to solve a problem. One of them is: http://elm-chan.org/works/akilcd/report_e.html    in a few days it will celebrate 6-th anniversary.  The obstacle, at least for me, was to compile / adapt his software for Arduino IDE platform, as it written in assembly and C.  So, I've moved on, and was lucky to discover an excellent chunk of code dated back to 1989! They didn't have floating point co-processor or "blue deep" around at that time, so mathematical skills were in high demand.
Video on youtube.
FFT algorithm could find application in wide variety of projects, for example, musical note recognition, voice recognition, sound localization etc. It could be done in Arduino, or in combination with PC. To use the the data provided by Arduino board in different application on PC (like interactivity / processing),  you can just pull data over serial link, as it was done for debugging purposes with "f" command.
 After FFT-processing input data array x, generated output array fx with  32 elements, "bins", each representing a range of frequencies. The width of a bin equals: D = 1 / T, where T is input array sampling timing, in our case T = 14.6 millisecond.
D = 1 / (14.6 * 10^-3) = ~ 70 Hz.
So, the value of fx[0] is amplitude DC offset up to 35 Hz;
fx[1] is amplitude in a range   35 <---> 105 Hz;
fx[2] is amplitude in a range 105 <---> 175 Hz;
fx[3] is amplitude in a range 175 <---> 245 Hz;
fx[4] is amplitude in a range 245 <---> 315 Hz;
.................................................................
fx[31] is amplitude in a range 2135 <---> 2170 Hz;
  Upper limits could be extended up to 76 KHz.(*)

Sum up first 10 fx bins, I'm getting 35 <---> 735 Hz frequency range for red LED's, from 11-th to 20-th consequently provides me 735 <---> 1435 Hz  for green LED's, and from 21-st to 31-st 1435 <---> 2170 Hz range for blue LED's.

 Now hardware part.

I was considering two way to build display, using PWM or BarGraph. In my opinion, PWM is not quite suited with LED, due nonlinearity in their current-brightness response. Probably, PWM approach would be O'K with incandescent lights.
  BarGraph design gives better impression, when higher sound volume   highlighted bigger area. Just imagine, how beautiful Fireworks are!
I've used Christmas LEDs, that left over since last holiday  :) This is why I put 3 of them in each string, and have to use ULN2003 with 12V power source. Basically, all you need is 12 Leds ( 4 - red, 4 - green and 4 blue ) and 12 resistors, connect led+resistor between output of arduino board  and ground.


For sound input I used a kit MK136 ( 2 mic's + amplifier IC NE5532 ), powered up from arduino board +5V. Why kit? It provides a board and components. Next, you can easily reconfigure input circuitry for different sound sources as:
 1. signal from 3.5 mm jack connector
 2. pick up sound using microphone.
 3. configure one channel with mic and another with 3.5 jack, that is what I did.
 Plus it has a pot, to adjust sensitivity on the fly. Don't install DC blocking capacitors at the outputs.
Sparkfun's breakout board for electret microphone will works too:       http://www.sparkfun.com/products/9964
 (with some limits, from 3 options listed above only number 2 would be avaiable.)



Here how to create DC offset with just couple resistors + cap. Circuit requires line level of the input signal (~1 V). You can use headphones output.

Link to download sketch:                Music_color
 *Please, be advised, code written for AtMega328p and should be corrected to work with other microprocessors. 


 Sine Tables for someone who 'd like to experiment, but don't know how to create them:         Sine_Tables 

* More on how define higher frequency range/ sampling rate you can get from other my blog:   http://oscilloscopeexpress.blogspot.com/


Thats it for now, will come back to answer your questions if you have any.

31 comments:

  1. I'm very new to Arduino. Your color organ looks awesome, I am just a little confused by the code. I don't know how/what to include as a library/sketch, etc. My email is mikehill100@gmail.com. I'm really interested and any help would be great.

    ReplyDelete
  2. Code intentionally was done as "all in one". You don't have to configure additional library or headers files.

    There is only one scetch, copy paste from first to last line in your IDE and it would compile without any error.

    ReplyDelete
  3. So this only picks up 0-2.1Khz? Can this code be adapted to pick up the entire human hearing range 0-20kHz? I have no previous experience with FFT so got it to work but am lost in tweaking it :(

    ReplyDelete
    Replies
    1. http://coolarduino.wordpress.com/2012/03/24/radix-4-fft-integer-math/#
      O'k, in a link posted above you could find a picture, that show a spectrum with upper limits close to 18 kHz.

      Delete
  4. I used this code on a Arduino mega and it works great but i have to put a jumper into digital 4 and hold it with out touching the bare wire before it will work. also how can i use the left and right channels out of digital 22-53

    ReplyDelete
  5. No-no, digital pin 3 configured as level-3 in red led-bar, and shouldn't be connected to ground
    or power rail!!! You can permanently damaged a board, be careful. The "problem", as you described it, could be related to slow responsiveness of the system on start-up, due high value of the "kary" variable. This variable multiply by "maxim" in every cycle (40 millisec), and set threshold for all three channel (RGB). It's take time in the beginning, to settle maxim to normal or real level of the sound signal on the input, as it adjusted only by 0.1%. The solution is assign kary to lower value, 0.99 or even 0.95.
    Now, second question. Yes, it will works for two channel, left and right. And performance
    27 * 2 = 54 millisecond is expected to be quite good . All you have to do is double the code and change a names in every section, except PROGMEM, and couple functions on top: fix_fft and fix_fftr. In the begining, for example:
    int xL[N], fxL[N]; <- you define left variables/array.
    int incomingByte;
    int i, count, scale;
    int xR[N], fxR[N]; <- you define right variables/array.


    int ledPinsKrL[] = { <--------------- Left pins
    2, 3, 4, 5 }; // an array of pin numbers to which red LEDs are attached
    int ledPinsZlL[] = {
    6, 7, 8, 9 }; // an array of pin numbers to which green LEDs are attached
    int ledPinsSnL[] = {
    10, 11, 12, 13 };// an array of pin numbers to which blue LEDs are attached

    nt ledPinsKrR[] = { <------------------Right pins
    22, 23, 24, 25 }; // an array of pin numbers to which red LEDs are attached
    int ledPinsZlR[] = {
    26, 27, 28, 29 }; // an array of pin numbers to which green LEDs are attached
    int ledPinsSnR[] = {
    30, 31, 32, 33 };// an array of pin numbers to which blue LEDs are attached

    The same in the Setup section and the main Loop. Almost everything goes double.
    There is only one things will be different, and this is the Input analog pin for left chanel and for right channel. Pin is defined by ADMUX = 0x60 (analog 0), in the section of the code for right chanel change it to ADMUX = 0x61 (analog 1).

    ReplyDelete
  6. Very cool! I have a (I think) pretty silly question: where exactly is the sound being input here? I assume its in the bit starting with "ADCSRA = 0x87;" but I'm not quite sure how... like, if I wanted to use one of those sparkfun microphones, I'm not sure where I input the AUD analog-in pin? Thanks for any help!

    ReplyDelete
  7. To rretter2: please, send your sketch on my e-mail,
    I will check on it and respond you back
    anatolyk69@gmail.com

    To jn: Pin is defined by ADMUX = 0x60 (analog 0).

    ReplyDelete
  8. It took me a few minutes to figure out how to connect the headphone jack but it's working. This gets me started on the stuff I want to do. Thanks.

    ReplyDelete
  9. Hello Magician, I couldn't seem to download your sketch.
    Without seeing your code, I assume you are using fix_fft as your library? Can you tell me how I can make it to for 0 to ~ 200kHz? I think the sample code I found has the operating range from 0 to 500Hz and I have been puzzling to figure out how to get to work at a wider spectrum... Thanks!
    xray

    ReplyDelete
  10. I'm using fix_fft as a function/subroutine, there is only one sketch "all-in-one". Arduino ADC can't sample faster than ~150 kHz, so maximum freq. analysis up to 75 kHz. If your microprocessor is different (mbed, Maple, PIC, cortex- whatever) and capable take samples 400 kS/s (to get 200 kHz), you will have to write other code and call fix_fft function when array of samples ready.

    ReplyDelete
  11. Thanks Magician, sorry I am very new with Arduino, let alone using FFT. Now comparing with the fix_fft I found at other source, I noticed you have increased the length of the sinetable considerably. And would you mind telling me the difference between fix_fft() vs. fixfftr()? Say if I want to analyze spectrum bandwidth of 20kHz, where should I modify your code to do so?

    Thanks,
    xray

    ReplyDelete
  12. Don't know where you get other fix_fft, I download a code from this link: http://www.jjj.de/fft/fftpage.html
    Sinetable size should be no less than FFT_SIZE, 64 in my code would be sufficient,I just left it as it was in original source code, in case someone increase number of bins (up to 256 on Uno or even more on Mega). fix_fftr doesn't do much in my code as it "intermediate" stage to change direction - make reverse FFT in original version.

    ReplyDelete
  13. I got the code from here:
    http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1286718155

    still not so sure how to adjust the sinetable size, fft_size to fit within the operating range...

    ReplyDelete
  14. I posted sine tables file above, size 32, 64, 128 and 256.

    ReplyDelete
  15. So, by adjusting the since of the since table will change the maximum range of FFT? If not too much to ask, would u share the math behind it?

    ReplyDelete
  16. Adjusting size of the sinetable, you make your sketch smaller, convenience to navigate and edit. It has only "cosmetic" effect. I was not going to explain how FFT works, but looks like I have to. Just be aware, I'm a Magician, not really Mathematician -);
    If you have study mathematics in school, probably you know what is "polynomial regression" is? http://en.wikipedia.org/wiki/Polynomial_regression
    It's some kind of math procedure, that trying to fit a polynom (linear or higher order) to set of data, and reports if it's fit and how well. FFT is no difference, only instead of polynom it trying to "fit" sine/cos wave, and report how well it goes for sine 1x, sine2x, sine3x and so on up to sineNx, where N is number of bins. And cos1x, cos2x, cosNx .... It's pure math, and has no clue where this data comes from. Data could be anything, audio, video, temperature log, humidity or sun emission around the year.It's you, who should know how data were gathered, in order to interpret output results. Based on freq. range you need, you define sampling period according to Nyquist theorem:
    Fsaml. = 2 x Fmax. For example, for 0 - 20 kHz freq., samples must be taken with 40 kHz freq. No less, but could be more, to relax antialising filter requirements. There are two way: using ADC control register of Arduino, or using a Timer (what people usually do to schedule periodic action). First way easier, and you could look into my other blog how to set ADC for faster readings. Disadvantage is sampling freq. has to be multiple of the Fosc. You can't set it to 40 kHz sharp, but ~36 kHz, 72 kHz, or default ~9 kHz.
    When you wrap around "periodic sampling" function in the code, next question: how many samples do I need?
    It depends on what freq resolution do you need,( and what memory size of your chip). Quantity of samples not necessarily to be exactly power of 2 ( 64, 128 ....), could be less, missing samples replaced by "0". I did it in musical note recognition blog, where only 256 samples gathered instead of 512. In this example, 20 kHz with array of 64 samples would provide : 20.000 / 32 = 625 Hz/bin. Pay attention, I reduce 64 in half, to 32 as output of the FFT always "mirrored" in other half. Hope , this helps.

    ReplyDelete
  17. thanks for your explanation. Math is never my strong subject, let me take some time to digest..

    ReplyDelete
  18. Bear with me Magician, I am a slow learner....

    I found reference about ADC, so sampling rate = cpu clock / prescaling factor. And default value is set to 128, so as your code ("ADCSRA = 0x87;").With that sampling rate would be 9.6KHz (13 ADC clock to convert 8 bit I assume?).. How does 9.6Khz translate to 2170Hz as the last bin Fx(31) in your code?
    It is funny that the 8-bit fft version I started with seems to have a upper freq of 500Hz even without changing any of the ADC prescaling. But with TOSC set to 128 as default, I don't understand how the upper frequency can be so low?

    ReplyDelete
  19. ADC is 10-bits, it'd be 9.6 kHz in free running mode, but there is some codes overhead like "for/while" loop and shifting/stuffing value in the array x[]. So it boils down to 8.8 kHz. You could modify code depends on your "precision" requirements, and "free running mode ADC" is definitely path to follow, only color organ is not so peeky application on this matter, and it was my first project on Arduino by the way -); Next conversion happens at this stage:
    [code] for (i=0; i>1] = x[i] ;
    else
    fx[i>>1] = x[i] ;
    } [/code] data rate reduced by 2. Again, this part is specific for LED light illumination, as I need lower freq. sampling and ADC doesn't allow it below 9.6 kHz. Sure, you should omit this stage, as you heed higher rate. After that, I have 4.4 kHz and according to Nyquist - maximum 2.2 kHz freq. analysis. There is no antialising filter at the input, and probably measurements not very accurate, because audio content is spread up to 15/20 kHz, the same time, IMHO, it makes visual performance looks even better.

    ReplyDelete
  20. Hi,

    Many thanks for a great project; can I ask a couple of questions please:

    In the 1st photo there are two wires going from what looks like GND and AREF on the Arduino towards the LED's. What are they for and where do the terminate ?

    I'm assuming that as you've left out the DC blocking caps on the 'Super Ears' board that there is no requirement for the DC-Offset circuitry ? Would I need this DC Offset if, for example, I was to use the headphone output from a PC ?

    many thanks for you time,

    Jeff

    ReplyDelete
  21. Couple wires are GND and Vin(+12V) powering arduino when usb is not connected anywhere.
    Yes, you don't need to create off-set, as OPA output already carry 2.5V (kit must be powered no more than +5V).
    For any other music source, like mp3/iPod/PC I'd recommend install 3.5 mm jack and drive a sound via second channel. Picture in the blog is "first version", look on updates here:
    https://docs.google.com/open?id=0Bw4tXXvyWtFVMDI4YTYyYmYtNGQ4MC00NzhhLTg2MWYtOTlmNzc3NmMxODFh

    ReplyDelete
  22. Hi,

    I do have to say you've built that color organ quite nicely. I've got a couple questions as i'm trying to do the same:

    1. I saw on the schematic that you had everything connected from a 12V power supply and was wondering if that's what's powering the whole arduino and the LEDs as I've never seen a setup like that (I'm new to arduino), as well as that placement of the ground being a bit confusing.

    2. Will it work with stereo input directly from a 3.5mm headphone wire?

    ReplyDelete
  23. 1. You can power arduino board by Vin-pin, 7-12V optimal range. It could work up to 20V with lower current, when outputs are "buffered" with external chips or drivers. It's o'k to have only 1 power source for whole project (sensors, arduino, displays, etc) as long as they share common ground.
    2. I'm not sure what do you mean "directly"? You need to create "off-set" voltage, as audio signal carry positive and negative half-wave (-1V / +1V), but AtMega doesn't accept negative ( 0 / +5 only).
    Use circuitry with 2 resistor and cap if you are not confident with transistors or OPA's.
    There is also a topic on arduino forum: http://arduino.cc/forum/index.php/topic,51828.0.html , other questions (not close related to color-music, rather "general electronics" questions) feel free to ask on a forum.

    ReplyDelete
  24. Magician,

    Thanks for an amazing project! I'm working on something similar, but I was going to use an msgeq7 instead of doing the FFT. I have it working from a laptop headphone jack, but I wanted to make it portable, and so I liked your idea of modifying as MK136 super ear to accept a line in or use a microphone, but I can't quite tell what you've done to it from the photo.

    Do you have a higher res photo, or can you explain more clearly what I need to do with the MK136 kit?

    Thanks!

    ReplyDelete
    Replies
    1. There is an assembly manual with electrical drawings:
      http://www.vellemanusa.com/products/view/?id=351278
      You DON'T install:
      MICR, R1, C1, C5, C7, R7, R8, R11, R13.
      Putting a piece of wire between phonejack (now it becomes line-in input) to upper side RV1B pot. Left channel will operate as it supposed to, (I only recommend increase R15 to 220 k, increasing gain). Right channel becomes line-in, better to sum-up left-right soldering in parallel both pins of the phonejack.

      Delete
    2. Magician,

      Thank you this is really helpful! I'm setting to work soldering this right now. A few clarifications when you say I should put a piece of wire between the phone jack and the "upper side RV1B", RV1 is the the pot, but I'm not clear which is the upper side, or what the B refers to? I'm guessing B means the middle pin?

      And when you say "better to sum-up left-right soldering in parallel both pins of the phonejack." you mean I should bridge R to L and R' to L' so I get both left and right channels from line in? Assuming that is right, is it the RL or RL' I want to run to the pot?

      Thanks!

      Delete
    3. I've soldered everything on the kit except for the omissions you suggested, and the pot. In looking at your photo and re-reading your instructions, I'm inclined to put a wire connecting the empty sockets on the right (near the phones jack) of R7 & R8 together and then wire that to the "upper side RV1B" as soon as you clarify what that means. Thanks!

      Delete
    4. Have you download an assembly instruction from the link posted above? It's one page pdf document. If you open it, at step 10 there is a double potentiometer RV1 50 k to install. Because it's double, it has two pots driven simultaneously, marked as RV1A and RV1B at the drawings. If you turn documents 90 degree clock-wise, electrical drawings at right side of the doc would be easier to navigate, and upper / lower pins of the components, including potentiometer, would be lay-out correctly.

      Delete
    5. Thanks I missed the RV1A & B on the circuit, I only saw the RV1 on the step by step portion. I'm off to solder again, thanks for the really quick and helpful responses. I really appreciate it!

      Delete
  25. Is there any chance i could convince you to edit the arduino code and have it just return each frequency range detected as a string? I would love to adapt your code for a project of mine, but with all the led stuff I'm kind of confused as to what I actually need to keep and what it returns when it detects a certain frequency.

    ReplyDelete