Sunday, November 14, 2010

Arduino Realtime Audio Spectrum Analyzer with Video out!


Once again, I decided to put the old travel DVD player's screen to good use by using as an output device for the Arduino.  Though the DVD mechanism is broken, the screen allows for standard NTSC composite video input.. runs on an attached rechargeable battery pack (can also power the Arduino.) 

I make no guarantees, and you do so AT YOUR OWN RISK, but it should work with any TV or device that allows NTSC Video In.  I am in no way responsible if you damage yourself or your equipment.  This is a prototype built by an amateur, keep that in mind.

A brilliant bit of code, the TVout http://www.arduino.cc/playground/Main/TVout library for Arduino, allows you to generate composite NTSC monochrome video with only two pins and two resistors.  I generally leave the resolution at the default 128x96, which translates to 16x12 text with the default 8x8 font.  Running under the defaults seems to give the least amount of trouble with this library, which is a work in progress.  Note that due to this library being actively worked on, there's no guarantee the code I am using will work with other IDE or Library versions.  This has been developed using version 0019, though I will be testing shortly on the most recent releases.  In addition, though it should not matter, I am using a 5v Adafruit Boarduino.  There should be no differences, as long as your Arduino is a 5v device.  Also note that old versions of Arduino which use an Atmel ATmega168 won't be able to run this, they don't have enough memory.

The other piece of the puzzle is collecting and processing audio, so we have something to display on our little display.

The first piece- data collection- is fairly standard.  I use an electret microphone (which alone only produces a few mV output, far too low for our Arduino to use directly) with a transistor amplifier as the signal source, which is then sampled via the ADC on the Analog 0 pin of the Arduino. 

To do spectrum analysis however, you need to capture signal over time, then process that data with what is known as a Fourier Transformation.  This magical process takes a signal and breaks it down into buckets based upon frequencies found within the sample.  This produces a remarkably good picture of the signal.. and if displayed, functions as a visual spectrum analyzer if looped over and over.

In this project, I've used code posted by a user to the Arduino forums:
http://www.arduino.cc/cgi-bin/yabb2/YaBB.pl?num=1286718155.

This post contains a library which performs both the sampling and the Fast Fourier Transformation completely in C in 8 bits, amazing fast considering that fact, and uses a few tricks to be really stingy on memory, which is at a premium on Arduino- especially with the TVout data space eating up quite a bit.  Since the Atmega 328 only has 2k of RAM, every byte counts.  Matrix math done like this is nothing short of awesome.  Best of all, it's usable as a library.  Cut and paste the .cpp and .h into a new folder named "FFT" in the Libraries directory.  My Arduino project code is adapted from the original code from the forum-posted Arduino program.

So, to produce our desired outcome, we just need to get to get the whole show together and hope it can perform..  With only a handful of cheap components (a few dollars at most), it produces a perfectly usable and quite entertaining realtime 64-band video spectrum analyzer.  Though not really "useful" for any real purposes, it makes an entertaining party display out of any television with a video input...


There are a lot of improvements which can be made- the first being the amplifier gain to make it a bit more responsive, and optimization of the FFT code.  In reality, it's the drawing of the bars which takes the most time per sample/display cycle..not the matrix math!

Arduino Code:

#include <TVout.h>
#include <fix_fft.h>

TVout TV;
char im[128], data[128], lastpass[64];
char x=32, ylim=90;
int i=0,val;

void setup()
    {                                         
    TV.begin(_NTSC,128,96);                              //  Initialize TV output, 128x96.
    TV.print_str(2,2,"  Realtime Arduino");             //  TVout lib uses x,y for print
    TV.print_str(2,11,"  Spectrum Analyzer");         //  statements.  8x8 default font.
    analogReference(DEFAULT);                          //  Use default (5v) aref voltage.
    for (int z=0; z<64; z++) {lastpass[z]=80;};       //  fill the lastpass[] array with dummy data
    };

void loop()
    {
    for (i=0; i < 128; i++){                                     // We don't go for clean timing here, it's
      val = analogRead(0);                                      // better to get somewhat dirty data fast
      data[i] = val/4 -128;                                       // than to get data that's lab-accurate
      im[i] = 0;                                                       // but too slow, for this application.
      };

    fix_fft(data,im,7,0);
   
    for (i=1; i< 64;i++){                                          // In the current design, 60Hz and noise
      data[i] = sqrt(data[i] * data[i] + im[i] * im[i]);  // in general are a problem.  Future designs
      TV.draw_line(i+x,lastpass[i],i+x,ylim,0);          // and code may fix this, but for now, I
      TV.draw_line(i+x,ylim,i+x,ylim-data[i],1);        // skip displaying the 0-500hz band completely.
      lastpass[i]=ylim-data[i];                                   // if you insist, initialize the loop with 0
      };                                                                    // rather than 1.
    };

 The circuit required is a simple microphone and transistor amplifier, as well as two resistors connected to D8 and D9 to provide video signal.  See the next blog post for the schematic.. drawing is a much bigger pain than you'd think!

14 comments:

  1. This is fantastic - I spent ages trying to get something like this to work last year.

    Thank you!

    ReplyDelete
  2. Impressive :)
    I know that most challenging code is written by some other people, but this power of open source :) I have to say, that idea is brilliant, I wouldn't expect that it was possible at all with Arduino :)

    ReplyDelete
  3. This really was a project I did on a whim.. I saw the lib code, and knew roughly what a Fourier Transform was.. I'm also a huge fan of the TVout lib, as it allows you to use something you already have as a very sophisticated output device with only two resistors.

    Open source makes it possible for a tinkerer to stand on the shoulders of giants (like the poster that worked out the transform code, and the TVout lib author), this really was a matter of gluing together a couple of great ideas to make something. I'm a tinkerer from way back, Arduino in some ways takes me back to those days way back in the 80's as a teenager, trying to work out a way to hook some LED's to an Atari 400.. (Thank you, 'Compute!' magazine, among others)

    ReplyDelete
  4. How can this be changed to display just 3 bands (Bass, Mid, Treble)?

    ReplyDelete
  5. Is it possible for you to post the circuit diagram information? Thanks!

    ReplyDelete
  6. Can you provide the ckt diagram for this project?

    ReplyDelete
  7. You'll find the circuit on the next blog post... I've redone it a little and will be reposting, but it remains a simple single-transistor amplifier. Really, just about any amplifier circuit would work, an OpAmp (like a LM741) might be a higher-gain solution for really low input levels.

    As for posting the code, you've got the original version of my sketch (which is a tweaked version of the sketch (program, for non arduino folks)) above, and I'll be posting a newer version shortly.

    The library (fft) is the work of a poster, and must be cut and pasted into the .h and .cpp files by yourself, and tossed in a directory under "libraries". I've got a message out to the original author of the library code, as I want to post it as a zipped lib ready to import into the Arduino IDE.. but I want to get permission from the author first. I'm making a few changes to the code myself, so once I do that, I guess I *could* call it a new version, credit the original, and put the library out there.. but I don't feel right doing that without the original author's blessing.

    The TVout Library is due for a new release within the next few days according to the author, with dramatic changes which may break old version functionality.

    Since I've got these two things on the burner at the moment, I'm waiting a couple of days to update the project code online, so that I don't get anyone caught in a versioning issue. The "new" revision of the project will use the "new" library, so as not to have compatability issues with the legacy library.

    ReplyDelete
  8. @cde: Pretty simply, actually. Each array element (0-63) is approximately 500Hz wide.. giving roughly 0-32kHz, much more than human hearing. To make three equal bands, you would average the value of the array elements. To make it easy, let's say we only go to 30kHz, which would be array elements 0-59. Your first band is simply the average (or total, scaled, your choice on the math) of array elements 0-19, the first 1/3 of the range.

    It's that easy..

    ReplyDelete
  9. Hi! I'm trying to make this work, but all i'm getting is this:

    SpectrumTVout.cpp.o: In function `setup':
    SpectrumTVout.cpp:23: undefined reference to `fix_fft(char*, char*, int, int)'

    I'm using the arduino 22 version under ubuntu 64 bits

    Thanks!

    ReplyDelete
  10. Well... I spend several hours yesterday trying to get this working until I realized that I had named the FFT library fix_fft.c instead of fix_fft.cpp !!
    Now it works wonderfully, thanks for sharing this!

    ReplyDelete
  11. Glad to hear you got it working. I'm going to wrapper the fft code and make sure attrib it, so I an post a couple more things I've based off the code. The code was taken from a forum post; I want to be sure that origination of that code is clearly given to the poster that worked out that nightmare piece of code!

    ReplyDelete
  12. It looks like you're only using up to data[64] to generate the output. If this is the case why are data[] and im[] 128 byte arrays?

    ReplyDelete
  13. The output array is N/2 frequency bins for any N samples in the array. For example, if we used 256 samples, we'd get 128 bands of data output, and so on. To save on memory, the code in the library uses the same array to provide output as was used to provide the sampled data to the routine. With only 2k of SRAM, the little ATMEGA328 is pushing every byte runnng both the arrays and variables as well as the TV signal generation..

    I've now stored the library on Google Code at:

    http://code.google.com/p/arduino-integer-fft/

    Enjoy!

    ReplyDelete
  14. I keep getting this message when I try to run the code:

    "CLASS TV OUT" has no member named Begin

    TV.begin(_NTSC,128,96); // Initialize TV output, 128x96.

    ReplyDelete