Last time, in part 1 we introduced you to the LMP, a simple, lost-cost part that connects directly to the Arduino. This time, we will continue with a simple program I call the Musicator.

It is a simple Spectrum Analyser, completely done in software, which reads the output from an earphone plug (on A01), and generates a 5-channel pattern on the LMP, based on the loudness and frequency of the signal.

Here is a sample video:

All you need, beside the LMP and, of course, the Arduino, is a cable with a jack that fits into an earphone socket. It doesn;t matter whether you use the Left or Right channel.

Step 1: The Sketch (LMP_Musicator01)

  • VUmeter / Light organ (Full db presentation)
  • Horizontal presentation - center-out linear display
  • (c) Copyright 2009 QS@quantsuff.com
  • All Rights Reserved
  • LED Matrix row:1-5; col:1-6
  • Map PORTB == D8:D12 - pin[row+7] : +v
  • PORTD == D2:D7 - pin[8-col] ; Gnd
  • Our output: col::D2:D7 -ve (LOW) while row::D8:D13 +ve

char msg[] = {
0x11, 0x0a, 0x0e, 0x0e, 0x0a, 0x11, // Starburst
0x00, 0x00, 0x00, 0x00, 0x00, 0x0e,
0x11, 0x19, 0x1e, 0x00, 0x1f, 0x10, // Q U
0x1f, 0x00, 0x1e, 0x05, 0x1e, 0x00, // A
0x1f, 0x06, 0x0c, 0x1f, 0x00, 0x03, // N T
0x1f, 0x03, 0x00, 0x12, 0x15, 0x09, // S
0x00, 0x1f, 0x10, 0x1f, 0x00, 0x1f, // U F
0x05, 0x05, 0x00, 0x1f, 0x05, 0x05, // F
0,0,0,0,0,0 }; //Plus an overrun buffer

// bit pattern, 1 byte per column. Top = lsb

char display[6], hdisp[6] ;

int w10kbuf[2], w1kbuf[20], w100buf[100]; // Assume all zeroed (:P)
int w10kidx, w1kidx, w100idx;
// int w10kmax, w5kmax, w1kmax, w500max, w100max;
long w, w10ksum=0, w1ksum=0, w100sum=0;
int www, w10k, w1k, w100, meter[5] ;

int scale=3; // log scale (start at 23)
int recal=0; //AGC rate
int inPin=1 ; // analogRead on A01

void setup() // run once, when the sketch starts
for (int ledPin=2; ledPin<=12; ledPin++) {
pinMode(ledPin, OUTPUT); // sets the digital pin as output
digitalWrite(ledPin,(ledPin<=7)) ; //Disable all cols
lmpWrite(msg,0,15); //Cycle delay is 1/10 secs
// (it's really to initialize the counters)
analogReference(INTERNAL) ; // Do not use DEFAULT or EXTERNAL!
int i= digitalRead(inPin); // remove old data

void loop() // run over and over again
w100 = (w100sum / 23) >> scale; // A cheap filter @ app 100Hz
w1k = abs((w1ksum / 3)-w100) >> scale; // 1kHz
w10k = (www+w10ksum) >> scale; // 10kHz
w= (w100 + w1k + w10k) ; // Total power (VU function)

if (w<13) {
if (recal>=15) { //350mS counts before recalibration of meter
scale-= (scale>3);
} }// increase res only if less than 1/2 scale

// if ((w100>33) || (w1k>30) || (w10k>33)) { // log2
if ((w100+w1k+w10k)>83) { // Peaked: make it less sensitive
w100= w100>>1;
w1k = w1k >>1;
w10k = w10k >>1;
meter[0]= min(w100>>1,31);
meter[1]= min(abs(w100+w1k-w10k)>>2,31);
meter[2]= min(w1k>>2,31);
meter[3]= min(abs(w1k+w10k-w100)>>2,31);
meter[4]= min((w10k*5)>>3,31);

for (int col=0;col<6;col++) {
int c=0;
for (int mm=0; mm<=4; mm++) {
c= c<<1;
c+= (meter[mm] > col);
hdisp[col]= c;
display[0]= hdisp[4];
display[1]= hdisp[2];
display[2]= hdisp[0];
display[3]= hdisp[0];
display[4]= hdisp[2];
display[5]= hdisp[4];


display[0]= hdisp[5];
display[1]= hdisp[3];
display[2]= hdisp[1];
display[3]= hdisp[1];
display[4]= hdisp[3];
display[5]= hdisp[5];

/* Vertical display routine
//fillVBar (display,5,scale+1);
int i=0;
int s=scale-1;
for (int ii=0; ii<5; ii++) {
i= i | (s & 1);
i= i<<1;
s= s>>1;
display[5]= i;


void fillVBar(char disp[],int o, int val) {
int j= B100000; // digit pattern
val= constrain(val,2,127) ;
while (val>2) {
j|= (j>>1) ;
val= val>>1 ;

void lmpWrite(char disp[],int stchar,int steps)
/* Usage: Loads bit pattern in disp[], starting at stchar,
  • 1 byte per column, left-to-right, top-to-bottom
  • Map PORTB == D8:D12 - pin[row+7] : +v
  • PORTD == D2:D7 - pin[8-col] ; Gnd
int col, row, cval, lit=0, ww=0;
long delayStart=millis() + 90*(constrain(steps,1,100)) ;
// Display / loop time
www= 0 ;
do {
for (col=0; col<6; col++) {
cval = disp[col+stchar];
for (row=1; row<=5; row++) { //only 5 lower bits
if (cval & 1) { // light this LED
if (lit==0)
digitalWrite(7-col,LOW); //col needs activating
lit++ ; // in case we need to know how many LEDs are lit
// Cycle stealing here, to read VU input
ww=max(ww,analogRead(inPin)) ; //Raw volume data (rectified)
www=max(ww,www) ; //Peak retect
delayMicroseconds(80); //This makes sure the panel is
// lit the same period of time.
digitalWrite(row+7,LOW); //Turn LED off
cval = cval>>1 ; // check the next bit
digitalWrite(7-col,HIGH); // turn col OFF
w10ksum+= www-w10kbuf[w10kidx]; // This is our 'capacitor'
w10kidx= ((w10kidx<1)); // 0 or 1
//if (w10kidx>3) w10kidx=0;
//w10ksum=ww ; // Increase the high-end response

w1ksum+= ww-w1kbuf[w1kidx];
if (w1kidx>19) w1kidx=0;
//w1kidx= ((w1kidx<19) && (w1kidx++)); // 0 - 19

w100sum+= ww-w100buf[w100idx];
if (w100idx>99) w100idx=0;
//w100idx= ((w100idx<1) && (w100idx++)); // 0 - 199

lit = 0;
while (delayStart > millis()) ;

Step 2: Program Notes

The major consideration for this program is time.

There are three primary tasks involved in this program:

1) Get the input voltage. The 16MHz Arduino takes 100uS to read the input, which means that even if it does nothing else, the maximum frequency it can detect reliably is 3kHz, so we use something called signal aliasing, beginning with taking a number of readings as quickly as possible (5 in our program) and summing them in separate locations. By taking averages of different ranges of samples, we can approximate the low and mid range frequencies, the difference is assumed to be the high reading. This is by no means exact science but here, we just need rough proportions, so exactness is not critical.

2) We will be doing statistical analysis of a large set of numbers, so everything is reduced to integers to save storage space and to speed up calculations. There will be no conversion from the basic 10-bit input of the Analog Input port.

3) Translation and display on the LMP. Each LED is switched on for about the time it takes to read the Analog pin, and each 'frame' (the 6 x 5 matrix) is shown twice, so that we can display 1.5db steps. This takes about 100mS. The time here is critical - any slower, the eye will start to see flicker, too fast, the 'patterns' will look dim and badly formed.

AGC is incuded, so that the program can work with a wide range of volume and music. A minimum of 150mV is required, but it should be no problem with regular listening levels. The "scale' variable can be lowered for more sensitivity, but will make the program more susceptible to electrical noise - in which casee, bypass the input with a 0.1uF capacitor to ground..

To maximize the 'dynamic' nature of the music, the most significant 2bits are dropped, and the remaining 4 are 'stretched' to fit the 6 horizontal LEDs.

Step 3: What's Next?

Send me your suggestions...

But here's a sneak peek at the next generation of the LMP... COLOR!

Electrically identical to (and interchangeable with) the mono-LMP, except for the addition of current limiting to allow us to use any color LED and still have the same current run through it.

Here's a quick look:

Another video of a soft ballad to show the Musicator's dynamic response:

<p>Hi, I like the idea, but could you add a minimal schemtics? Even hand-drawn or ASCII-art is fine. I'm not very sure about how to wire the input (Direct, over an capacitor, over an voltage-divider+capacitor, ...). Thanks a lot</p>
i have build the lmp and constructed as in the instruc, but now all the musicator does is keep the leds high when i input an audio signal :(
If the lmp works with the other sketches then make sure the input is tied to A01 through an isolating cap (0.01uF or so). Some MP3 players use direct (bridge) output and there's a DC component which will 'pin' the circuit. It's also possible (but not likely) the input signal itself is too high - all you need is one channel from an earphone output.
what is the a01 ???? The place you put the earphone ???
thanx a lot for your quick response...It's my first project so i'm a little bit...nervous. Can we have more details about building the board on the project 1.... (solder etc....) <br> <br> <br>Thanx Ben
A01 is a General Purpose input pin in the Arduino - check the datasheet of your model for more details. <br> <br>I connected the White wire from a salvaged earphone plug to A01 and the black to Ground.
i want to start the project but I don't understand where you put your earphone socket !!!!?????
To <em>fruitkid101</em> and others who'd been waiting for the scrolling write-up.<br /> <br /> Sorry, but I'm on a really tight schedule at work and have not had time to get to it, but the code above has EVERYTHING&nbsp;you need, specifically the lmpwrite routine which does the actual scrolling message. Put your pattern in msg[] and it'll work - there is nothing else you need. The code does not depend on any external libraries or code (apart from the Arduino's built-in binary loader).&nbsp;<br /> <br /> I will get to a more detailed Instrutable when I get the chance.<br /> <br /> qs
Is it possible to expand the amount of leds and would you have to change the code to make the different patterns? If it is how would you?
The maximum number of <em>controlled</em> LEDs in this project is limited by the number of ports on the microprocessor, as I mentioned at the top. Beyond that, we will need to resort to multiplexing, which will further complicate the circuit and/or code.<br/><br/>Having said that, the software is written to have each vertical LED controlled by a single bit of a word, so we have room to go to 16 LEDs vertically. The number of horizontal LEDs is limited by the processing time of the uP so that we can both SEE the LEDs (the eyes need 1/10 of a sec to register each change) and to perceive motion and groups.<br/><br/>What is the application you have in mind?<br/>
Ha! I built one for the arduino mega (tons of pins) It's way bigger and can do simple animations, Great!<br />
Ok, thanks for the info; i was just wondering if it could be done.
&nbsp;I have zero experience with arduinos so please excuse my ignorance,<br /> <br /> is it possible to operate this without it being connect to the computer, or does the arduino&nbsp;receive&nbsp;the code from the computer during operation?<br />
Yes, you can use it without the computer. When you upload the code, the arduino stores it and will use it whenever powered up. If you want to use the arduino without a computer, you just have to supply 5v and Gnd to it either by making a cable that can do that, or otherwise<br />
Love the project.&nbsp; I am actually thinking of trying to do the same thing using a FFT transform.&nbsp; If it is not too much trouble, could you provide a more commented version of your code. &nbsp;I do not just want to use it but understand it and adapt it. &nbsp;Thanks.<br />
He's implemented it very cleverly as a series of discrete digital filters, rather than an FFT. Would an FFT&nbsp;work better ?&nbsp;<br />
Not quite sure if an FFT would be easier but the problem with the FFT is the memory involved in the computations. &nbsp;It would be cool to implement it thought.&nbsp; Yeah I see the digital filters now.&nbsp; Thanks.<br />
Oh my god... it actually works!
If I paid you to build me one of these, what would it cost?
Phenomenal project! Software is so important in a project like this - very well executed!
Thanks! I'm quite pleased with the results. Plus it gives me something to watch while I work on the REAL project! LOL.
I'm looking to build an uuber-pov project - and with experimenting with 'more efficient' shifting, want to use generic shift registers to have a 48-bit arduino register :D it will be excellent.
I'll be looking forward to your results. But wouldn't using shift registers (and latches) sort of negate POV?
essentially, I need to something-plex the output to get enough pixels. One word description- &quot;Minivan-pov&quot; :D To someone driving next to you it would look like a glowing bar. Stationary objects would see a picture/message<br/><br/>Use 6 registers @ 8 bits each, latched in parallel, so thats 48 bits in 8 shifts - common clock, latch - and parallel data = 6 + 1+1...easy data output. Just need to tweak the shiftout library to output a matrix instead of an array. There was a lot of buzz on the forum using more advanced techniques for the backend code for shiftout to not use digitalwrite, but rather some fancy assembly that saves a few clock cycles per shift.<br/><br/>The registers I'm using have an active (I forget) low or high latch for data input mode (output off) and data output mode (output on)...basically you can achieve good thruput with this parallel buffer in that you draw frame n+1 while you display frame n.<br/><br/>GHASP! I've spilled my secrets!<br/>
While I was a coop student, I worked on a similar project. The idea was to place individual frames of a moving ad on the walls of the subway tunnel. Then each successive image was supposed to be lit by the light from the carriage, lighting it up just for the passengers at the window. Of course we had to use a 99% reflective glass substrate as a base, surrounded by a dark matt to tell the eyes where to look. Ended up a million $$ failure - the eyes tracked too fast and the pupils reacted much too quickly to get the 'page-flip' effect. It's one of those ideas that sounds so very plausible on paper. Because 2 months after MY job, they had another buyer interested in doing exactly the same thing. But I've seen your idea (kinda) strapped on the wheel of a cab in Boston - a LED string across it mapped a logo/image as the wheel spun.
yeah, like a wheelpov, but horizontal motion and vertical bar. For the subway Idea if it were powered, and had a little radar to tell the speed of the train, it would work to make a moving image for the riders, just not animated.

About This Instructable




More by qs:Joule Thief LED circuits A Triple Channel Musicator - the TriM... Musicator Jr - Mk 2 
Add instructable to: