You are on page 1of 9

DS0011 - Sound to Light III 1

Overview
In this tutorial, we will be adding a software update to improve the LED VU meter display further, incorporating band-
pass Infinite Impulse Response filters in software for low, mid and high frequencies. These will be used to drive the
RGB LED intensities on the NB3000 to realize simple left and right channel, 3-band spectrum analyzers. The colors
generated from the intensity levels are based on the Tempered Steel Color Chart shown in Figure 1.

Figure 1. Tempered Steel Color Chart.

Prerequisites
This tutorial assumes you have already completed Discovery Sessions 9 and 10 successfully and are familiar with
schematic and OpenBus design methodologies. It also assumes basic C programming skills. No additional
information is required.

Design detail
This exercise uses the same hardware design that was used in Discovery Sessions 9 and 10 we will be merely
extending the functionality with modifications and additions to the firmware C code.

Tutorial steps preparing the hardware design
1. Copy the contents of the Session 10 \Project folder to the Session 11 \Project folder.
2. Create a new design workspace in Altium Designer using FileNewDesign Workspace. This will clear out
your existing design documents from the workspace.
3. Open the project copy just created from the Session 11 \Project folder.
4. Switch to Devices View and build and download the existing project by clicking on Program FPGA.
Discovery Session 11
Sound to Light Part III
DS0011 - Sound to Light III 2
Tutorial steps updating the embedded code
The following steps detail the updates required in each section of code:
5. We will need to add a module to perform audio filtering to our project. Create a new source document and
save it as IIR.h, entering the following into it:
#i ncl ude <st di nt . h>
#i f ndef __I I R_H
#def i ne __I I R_H

typedef struct coe2pol
{
i nt 32_t al pha;
i nt 32_t bet a;
i nt 32_t gamma;
} coe2pol _t ;

i nt 16_t do_i i r ( i nt 16_t i nput , i nt 16_t al pha, i nt 16_t bet a,
i nt 16_t gamma, i nt 16_t * buf f _x, i nt 16_t * buf f _y ) ;

i nt 32_t scal e ( i nt 16_t sampl e) ;
i nt 16_t t r uncat e ( i nt 32_t sampl e) ;

#endi f
6. And of course, we will implement this IIR filter module in a new C document saved as IIR.c added to the
project, with the following code:
#i ncl ude " I I R. h"

i nt 16_t do_i i r ( i nt 16_t i nput , i nt 16_t al pha, i nt 16_t bet a,
i nt 16_t gamma, i nt 16_t * buf f _x, i nt 16_t * buf f _y )
{
i nt 32_t y = 0;
y - = bet a * buf f _y[ 1] ;
y += al pha * i nput ;
y - = al pha * buf f _x[ 1] ;
y += gamma * buf f _y[ 0] ;

buf f _x[ 1] = buf f _x[ 0] ;
buf f _x[ 0] = i nput ;
buf f _y[ 1] = buf f _y[ 0] ;
buf f _y[ 0] = ( i nt 16_t ) ( y/ 16384) ;
return buf f _y[ 0] ;
}
7. Now we are ready to update the code in main.c. Delete the previous code from main.c and replace it with the
following:
DS0011 - Sound to Light III 3
#i ncl ude <mat h. h>
#i ncl ude <st dl i b. h>
#i ncl ude <st di nt . h>
#i ncl ude <devi ces. h>
#i ncl ude <audi o. h>
#i ncl ude <dr v_l ed. h>
#i ncl ude <l ed_i nf o. h>
#i ncl ude " I I R. h"

#def i ne PI 3. 1415926535897932384626433832795
#def i ne AUDI O_BUF_SI ZE 512
#def i ne FSAMPLE 44100
#def i ne BASS_Hz 64
#def i ne MI D_Hz 1024
#def i ne TREB_Hz 8192

// Coeffs for band-pass IIR filters, FS = 44100, Q = 1.4, 16-bit Fixed Point
i nt 16_t al pha_hi , bet a_hi , gamma_hi ,
al pha_mi d, bet a_mi d, gamma_mi d,
al pha_l o, bet a_l o, gamma_l o;

//coe2pol_t iir buffers;
// Left Channel
i nt 16_t x_l _l o[ 2] = {0};
i nt 16_t x_l _mi d[ 2] = {0};
i nt 16_t x_l _hi [ 2] = {0};
i nt 16_t y_l _l o[ 2] = {0};
i nt 16_t y_l _mi d[ 2] = {0};
i nt 16_t y_l _hi [ 2] = {0};
// Right Channel
i nt 16_t x_r _l o[ 2] = {0};
i nt 16_t x_r _mi d[ 2] = {0};
i nt 16_t x_r _hi [ 2] = {0};
i nt 16_t y_r _l o[ 2] = {0};
i nt 16_t y_r _mi d[ 2] = {0};
i nt 16_t y_r _hi [ 2] = {0};

// for internal loop.
int i , j ;

/* bufffers */
// Hi/Mid/Low filter output buffers
// Left Channel
i nt 16_t bass_buf _l [ AUDI O_BUF_SI ZE/ 2] = {0};
i nt 16_t mi d_buf _l [ AUDI O_BUF_SI ZE/ 2] = {0};
i nt 16_t hi _buf _l [ AUDI O_BUF_SI ZE/ 2] = {0};
/ / Ri ght Channel
i nt 16_t bass_buf _r [ AUDI O_BUF_SI ZE/ 2] = {0};
i nt 16_t mi d_buf _r [ AUDI O_BUF_SI ZE/ 2] = {0};
i nt 16_t hi _buf _r [ AUDI O_BUF_SI ZE/ 2] = {0};

// Audio pass-through buffer
i nt 16_t st er eo_buf [ AUDI O_BUF_SI ZE] = {0};

// contexts for drivers
audi o_t *audi o;
l ed_t *l eds;
ui nt 8_t r gb_v[ 3] = {0};

/* function prototypes */
int get _audi o( i nt 16_t *buf f er , int si ze) ;
int put _audi o( i nt 16_t *buf f er , int n) ;
ui nt 8_t abs_ave( i nt 16_t *buf f er , i nt n) ;
void updat e_coef f s ( double f r equency, double qf ,
DS0011 - Sound to Light III 4
i nt 16_t * i al pha, i nt 16_t * i bet a, i nt 16_t * i gamma ) ;
void updat e_i nt ensi t y( ui nt 8_t i nt ensi t y, ui nt 8_t * r gb) ;

/**********************************************************************
|*
|* FUNCTION : main
|*
|* PARAMETERS : None
|*
|* RETURNS : None
|*
|* DESCRIPTION : Start here
*/

void mai n( void)
{
audi o = audi o_open( AUDI O_1) ;
l eds = l ed_open( LEDS) ;
updat e_coef f s( BASS_Hz, 1. 4, &al pha_l o, &bet a_l o, &gamma_l o ) ;
updat e_coef f s( MI D_Hz, 1. 4, &al pha_mi d, &bet a_mi d, &gamma_mi d) ;
updat e_coef f s( TREB_Hz, 1. 4, &al pha_hi , &bet a_hi , &gamma_hi ) ;

while ( 1)
{
// get audio to left and right buffers for processing.
get _audi o( st er eo_buf , AUDI O_BUF_SI ZE) ;

// Loop left and right channels through.
put _audi o( st er eo_buf , AUDI O_BUF_SI ZE) ;

for ( i = 0, j = 0; i < AUDI O_BUF_SI ZE; i ++, j ++)
{
/ / Spl i t l ef t and r i ght channel s, pass t hr ough f i l t er s
bass_buf _r [ j ] = do_i i r ( st er eo_buf [ i ] , al pha_l o, bet a_l o,
gamma_l o, x_l _l o, y_l _l o ) ;
mi d_buf _r [ j ] = do_i i r ( st er eo_buf [ i ] , al pha_mi d, bet a_mi d,
gamma_mi d, x_l _mi d, y_l _mi d) ;
hi _buf _r [ j ] = do_i i r ( st er eo_buf [ i ] , al pha_hi , bet a_hi ,
gamma_hi , x_l _hi , y_l _hi ) ;
i ++; / / Next Channel
bass_buf _l [ j ] = do_i i r ( st er eo_buf [ i ] , al pha_l o, bet a_l o,
gamma_l o, x_r _l o, y_r _l o ) ;
mi d_buf _l [ j ] = do_i i r ( st er eo_buf [ i ] , al pha_mi d, bet a_mi d,
gamma_mi d, x_r _mi d, y_r _mi d) ;
hi _buf _l [ j ] = do_i i r ( st er eo_buf [ i ] , al pha_hi , bet a_hi ,
gamma_hi , x_r _hi , y_r _hi ) ;
}

// Put the D.C. average value of the wavelet on LEDs
updat e_i nt ensi t y( abs_ave( bass_buf _r , AUDI O_BUF_SI ZE/ 8) , r gb_v) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED0_R, r gb_v[ 0] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED0_G, r gb_v[ 1] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED0_B, r gb_v[ 2] ) ;

updat e_i nt ensi t y( abs_ave( mi d_buf _r , AUDI O_BUF_SI ZE/ 8) , r gb_v) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED1_R, r gb_v[ 0] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED1_G, r gb_v[ 1] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED1_B, r gb_v[ 2] ) ;

updat e_i nt ensi t y( abs_ave( hi _buf _r , AUDI O_BUF_SI ZE/ 8) , r gb_v) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED2_R, r gb_v[ 0] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED2_G, r gb_v[ 1] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED2_B, r gb_v[ 2] ) ;

updat e_i nt ensi t y( abs_ave( bass_buf _l , AUDI O_BUF_SI ZE/ 8) , r gb_v) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED7_R, r gb_v[ 0] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED7_G, r gb_v[ 1] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED7_B, r gb_v[ 2] ) ;

DS0011 - Sound to Light III 5
updat e_i nt ensi t y( abs_ave( mi d_buf _l , AUDI O_BUF_SI ZE/ 8) , r gb_v) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED6_R, r gb_v[ 0] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED6_G, r gb_v[ 1] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED6_B, r gb_v[ 2] ) ;

updat e_i nt ensi t y( abs_ave( hi _buf _l , AUDI O_BUF_SI ZE/ 8) , r gb_v) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED5_R, r gb_v[ 0] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED5_G, r gb_v[ 1] ) ;
l ed_set _i nt ensi t y( l eds, LEDS_LED5_B, r gb_v[ 2] ) ;
}
}

/**********************************************************************
|*
|* FUNCTION : get_audio
|*
|* PARAMETERS : buffer = pointer to audio buffer
|* n = number of samples to read
|*
|* RETURNS : Number of samples actually received in buffer
|*
|* DESCRIPTION : Receive buffer from audio-input
*/

int get _audi o( i nt 16_t *buf f er , int n)
{
int s;

do
{
s = audi o_r ecor d( audi o, buf f er , n) ;
n - = s;
buf f er += s;
}while ( n ! = 0) ;

return s;
}

/**********************************************************************
|*
|* FUNCTION : put_audio
|*
|* PARAMETERS : buffer = pointer to audio stream
|* n = Number of samples to write
|*
|* RETURNS : Number of samples actually transmitted
|*
|* DESCRIPTION : Send buffer to audio-output
*/

int put _audi o( i nt 16_t *buf f er , i nt n)
{
int s;

do
{
s = audi o_pl ay( audi o, buf f er , n) ;
n - = s;
buf f er += s;
} while ( n ! = 0) ;

return 0;
}

/**********************************************************************
|*
|* FUNCTION : abs_ave
|*
DS0011 - Sound to Light III 6
|* PARAMETERS : buffer = pointer to audio wavelet
|* n = number of samples to use for calculation
|*
|* RETURNS : 8-bit value representing audio average volume
|*
|* DESCRIPTION : Finds the average of the absolute values of a sample
|* buffer and scales down to unsigned 8-bit for VU meter.
*/

ui nt 8_t abs_ave( i nt 16_t *buf f er , int n)
{
i nt 16_t cusum= 0;
for ( int i = 0; i < n; i ++)
{
cusum+= ( buf f er [ i ] < 0) ? - ( buf f er [ i ] / n) : buf f er [ i ] / n;
}
return ( ui nt 8_t ) ( cusum>>2) ;
}

/**********************************************************************
|*
|* FUNCTION : update_coeffs
|*
|* DESCRIPTION : updates alpha, beta and gamma IIR coefficients
|* for left and right channel filters.
|*
*/

void updat e_coef f s ( double f r equency, double qf ,
i nt 16_t * i al pha, i nt 16_t * i bet a, i nt 16_t * i gamma )
{
double al pha;
double bet a;
double gamma;
double t het a;
t het a = 2 * ( double) PI * ( f r equency/ FSAMPLE) ;
bet a = 0. 5 * ( ( 1 - t an( t het a/ 2*qf ) ) / ( 1 + t an( t het a/ 2*qf ) ) ) ;
gamma = ( 0. 5 + bet a) *cos( t het a) ;
al pha = ( 0. 5 - bet a) / 2;
*i al pha = ( i nt 16_t ) ( al pha * 32767) ;
*i bet a = ( i nt 16_t ) ( bet a * 32767) ;
*i gamma = ( i nt 16_t ) ( gamma * 32767) ;
}

/**********************************************************************
|*
|* FUNCTION : update_intensity
|*
|* PARAMETERS : intensity = 8-bit average wavelet intensity value
|* rgb = UINT8_T * RGB value
|* (pointer to red, green and blue).
|*
|* DESCRIPTION : Updates LED intensity registers based on average
|* sound value
*/

void updat e_i nt ensi t y( ui nt 8_t i nt ensi t y, ui nt 8_t * r gb)
{
if ( i nt ensi t y < 0x08)
{
r gb[ 0] = 0;
r gb[ 1] = 0;
r gb[ 2] = 0;
} else
{
r gb[ 0] = 0x80 - i nt ensi t y < 0 ? 0 : 0x80 - i nt ensi t y;
r gb[ 1] = 0x40 - i nt ensi t y < 0 ? 0 : 0x40 - i nt ensi t y/ 2;
r gb[ 2] = i nt ensi t y < 0x80 ? 0 : i nt ensi t y/ 2 - 0x40;
}
}
DS0011 - Sound to Light III 7
Tutorial steps running your design
Now its time to build and download the project:
8. Save your work using FileSave All. Re-compile your embedded
project using ProjectCompile Embedded Project
Snd2Light.PrjEmb.
9. Switch to Device View and click the Up to Date Download
button to build and download the design to the NB3000.
10. Once you have built and downloaded the design, you need to plug in a
sound source (such as your PC or an MP3 Player) to the NB3000s
LINE IN on the front edge of the board (see Figure 2).
11. Optionally, plug some stereo headphones into the HEADPHONES
output on the front edge of the board, or listen to the sound play
through on the NB3000s speakers (the speakers are disabled if headphones are plugged in).
12. Play some sounds and observe the LEDs brightness on the left and right sides of the LED array, with respect
to the music thats playing.

Code Explanation
We added RGB Spectrum Analysis capability to our design, with the RGB LEDs configured as shown in . To
get this result we needed to add some code that essentially performs the function depicted in Figure 4.

Figure 3. RGB LED output assignments.
To achieve this, we created six sample buffers, for Bass, Midrange and Treble filters in Left and Right audio
channels respectively. These were added in lines 44 through 50 of main.c.
We added a function update_coeffs which is defined in lines 233 to 247 of main.c. This function is a handy
one that takes our desired center frequency and Q-factor for the band pass filter and generates the fixed-point
(integer math) representation of the filter coefficients i al pha, i bet a and i gamma. From this function (called
at the beginning of main) we generate the coefficients for the Bass, Mid and Treble filters.
Lines 99 through 111 of main.c call the IIR filter function, using four-sample buffers for each of the six filters
required. These four-sample buffers are defined as simple variables in lines 22 through 36 of main.c. This
simplified the IIR implementation but still allowed a single IIR function to be called for all filters.
The output wavelets from the filters bass_buf _l , mi d_buf _l , hi _buf _l etc. are fed to the abs_ave and
update_intensity functions, just as done in previous sessions, with extra modifications that allow
intensities to map to full RGB colors of the LEDs.

Figure 2. NB3000 Line Input.
DS0011 - Sound to Light III 8



Figure 4. VU Meter Block Diagram (one channel).

Some additional items for you to try
Modify the project to pass through filtered audio instead of using the filters only for the LED displays.
Adjust filter center frequencies (defined by macros in lines 13 to 15) and Q-factors to see the affect on the
display try to more clearly separate Bass, Midrange and Treble.
Modify the update_intensity function to change colors based on something other than the chart in Figure
1. For example allowing more discrete color transitions and less blending.
Add audio delay buffers and some feedback to create an echo effect on the audio passing through to the
output.
DS0011 - Sound to Light III 9
Revision History
Date Revision No. Changes
29-J ul-2009 1.0 New document release
Software, hardware, documentation and related materials:
Copyright 2009 Altium Limited.
All rights reserved. You are permitted to print this document provided that (1) the use of such is for personal use only and will not be copied or
posted on any network computer or broadcast in any media, and (2) no modifications of the document is made. Unauthorized duplication, in whole
or part, of this document by any means, mechanical or electronic, including translation into another language, except for brief excerpts in published
reviews, is prohibited without the express written permission of Altium Limited. Unauthorized duplication of this work may also be prohibited by local
statute. Violators may be subject to both criminal and civil penalties, including fines and/or imprisonment. Altium, Altium Designer, Board Insight,
Design Explorer, DXP, LiveDesign, NanoBoard, NanoTalk, P-CAD, SimCode, Situs, TASKING, and Topological Autorouting and their respective
logos are trademarks or registered trademarks of Altium Limited or its subsidiaries. All other registered or unregistered trademarks referenced
herein are the property of their respective owners and no trademark rights to the same are claimed.

You might also like