<ECE 476 Final Project Report>


The Guitar Effects Synthesizer

by
Tatiana Lamela-Rabell

and
Gabriel Rivera


<Introduction>


In the last few decades technology has constantly pushed music further and further into the digital realm. Digital technology has infiltrated all aspects of music-making, from its creation to its recording, editing and production. We have decided to join this technology movement by fitting it to our taste.

Since both of us enjoy playing the guitar, we have decided to use the Mega 32 AVR microcontroller to make a Guitar Effects Synthesizer, which allows us to use one of the following effects to make our guitars sound better: reverb, echo, distortion or tremolo.


<High Level Design>


Our initial goals were to make an echo for the guitar. However, we instantly fell into problems. To make an echo effect we would need enough memory to store the previous past audio samples. So we went to the drawing board to consider the feasibility of our soon to exist cool echo machine. We figured that we wanted to sample at 44.1kHz or cd-quality (why would we reduce ourselves to anything less). So it turns out that sampling at 44.1kHz or 44,100 samples per second using a 1 byte resolution we would need 44,100 bytes per second! Ok, so how would we accomplish this? It turns out that none of the AVR micro controllers in lab have this much memory. One possible solution at the time was to use the 8515 micro controller which had 32Kb of memory. Hence if we would satisfy ourselves by sampling at 32kHz we were set! Ok, this didn’t work out either. The 8515 does have 32kBytes of external addressable SRAM but it does not have an ADC (analog to digital converter) and even worse the external memory uses 2 ports and 2 pins for addressing and memory IO. This meant a couple of things. It meant that we would use an external ADC and it had to be serially connected to the MCU. Of course the fact that we did not have serial ADC in the lab available and that if indeed we would get it in on time and pull everything off we would not have any more ports available to do a cool LCD display couldn’t make things worse. We took a step back and reanalyzed the problem. What was so cool about an echo anyway?

We sat down and thought about other ideas and came up with the guitar effects synthesizer. If we could build a circuit that could make more than one effect and the user had the freedom to switch among a list of effects and change their parameters we would be blessed. And this is what we set out to do.

We quickly figured that our main constraint was memory. We had to pick guitar effects that were computationally simple and memory efficient. We initially set out to create the reverb effect. Later on we ended with a total of four effects. The list of effects we decided to implement where the following: Reverb, Distortion, Tremolo, and Echo (more on this later).

We used the Mega32 micro controller because it had 2Kb of memory and internal ADC. We decided to sample the sound signal at a rate of 6kHz. (Ok, so we lowered our expectations a tad from the latter sampling rate of 44.1kHz). At this sampling rate and with 2kb of SRAM we would be able to hold about a third of a second in our buffer. Would this be enough? It was. We were capable of building a circuit which had two user inputs (button0 and button1) and a LCD display. When the user presses button0 the LCD displays a guitar effect. The user can keep pressing button0 and cycling through all the effects including a "No effect" effect which basically just passes the guitar signal untouched. Once the user has selected a effect he can press button1 and change the effect properties to a list of pre-set values. That is the user can select the Distortion effect and then select by pressing button1 the amount of Distortion he or she desires. Below a is a list of all the following effects with their pre-set values and a brief description of them.

For definitions of the effects please refer to the Software Design page.

 

EFFECT: Reverb

PRE-SET PROPERTIES: Church, Auditorium, Large Room, Reverse*

* The property names refer to the sound the guitar makes when played. That is, when playing in Reverb/Church mode the sound (if recorded) of the guitar sounds like if it was recorded in a huge Church as oppose to a small studio recording room. Refer to the Results page to listen to the effects!

 

EFFECT: Distortion

PRE-SET PROPERTIES: High, Medium, Low, None

 

EFFECT: Tremolo

PRE-SET PROPERTIES: Fast, Medium, Slow, Very Slow*

*These property names refer to the frequency of the Tremolo. Refer to the Results page to listen to the effects! These frequency variations are clearly heard in the sound sample.

 

EFFECT: Echo

PRE-SET PROPERTIES: None

 

EFFECT: No Effect

PRE-SET PROPERTIES: None


<Software Design>


The Software design was mainly comprised of the actual guitar effects synthesis (which had to be computed in real-time, the user interface via the LCD, and the MCU backbone which used the two timers and the ADC which where in charge of accurately sampling at 6kHz, debouncing buttons once every 50ms and updating the LCD display every 100ms.

Given that we were sampling at a rate of 6kHz we were sampling every 168µs (in actuality the period of 6kHz is 166.66µs however our timer1 gave us 168µs evenly when setting the prescaler to 64 and using the compare match interrupt on OCR1A equals 42. Thus our real sample rate was 5952Hz but we will still use the 6kHz sampling rate in the report for clarity). Given that our micro controller was running at 16MHz this translates that we had a total of 2688 cycles to get the data, put it into the buffer, process it and output it. This meant that although we had enough time to process the data we did not have the leeway to “mess around”. We had to use our time discretely and every cycle had to be accounted for.

We also ideally wanted to use up all the 2kb of memory present in the Mega 32 for the data buffer but of course we had to settle for 1.6kb since we needed to use up some space for intermediate variables and for the hardware stack. As an aside, one problem we encountered was that suddenly the LCD display was displaying garbage. The program compiled perfectly so we had no idea of what was happening. Thinking back in time we figured that the error occurred only after we had created one more char variable. We hit ourselves against the computer monitor for sometime and then understood that adding one more global variable (with the data stack held constant at a maximum of 200 bytes-already lowered from 500bytes) meant that the hardware stack had one less byte and it was overwriting other parts of memory not allocated for the system/hardware stack. We expanded the hardware stack by minimizing the data stack by 100 more bytes (down to 100bytes) and everything worked again. As you can see we were tight in time and now in memory. So how did we pull it off?


First, everything that could go in flash went in flash memory. Huge dual dimensional string arrays for LCD displaying were stored here with their corresponding pointers living in SRAM. To decrease computational burden and hence save some cycles we restricted multiplication operations and limited ourselves to multiplying and dividing by two or shifting by 1. This greatly reduces a possible 100 cycle instruction (for numbers like (NUM*.5) to a simple one cycle instruction. But this means we reduced ourselves to only multiplying and dividing by two? No. It is pretty easy to multiply by numbers like 0.125, 0.25 and 0.375 by simple shift operations which can be seen in the code in the appendix inside the “mult” function. The mult function was originally coded in C and not in assembly for simplicity however if serious timing issues would arise in the future we would have gone to assembly. Luckily we did not have this problem.

 

As far as program flow and organization we used time-scheduled tasks. These time scheduling tasks were achieved with the aid of Timer0 and Timer1. Timer0 was set to interrupt every half of a millisecond. Once we had this half of a millisecond timer it was easy to call functions in multiples of halves of millisecond and more practically in multiples of milliseconds. Two tasks executed every 50 milliseconds and one every 100 milliseconds or a tenth of a second. The tasks which were executed every 50 milliseconds were the button0 and button1 debouncer. The LCD display operated every tenth of a second. The debounce function identically as the debounce function we have been using all year in class. This function mainly assures when a button is pressed, it is really pressed. In code, this means that when the variable pushFlag0 is a 1, button0 has been pressed. The LCD display function basically checks if any button has been pushed (if any flags have been raised) and if so, updates the variable effectIndex, a variable holding the current effect(if button0 was pushed). If button1 was pressed the LCD display function updates the property for the effect. After these changes have been made the function proceeds to update de LCD display with the new effects or effect property.

Another thing that is occurring behind the scenes is the analog to digital conversion (ADC). The ADC is occurring constantly and when it is done with a conversion it goes into a ISR (interrupt service routine) and stores the new converted value into a temporary variable Ain. Then the ISR proceeds to reactivate the AD converter for conversion and the process repeats infinitely. Retrieving converted values via interrupts is wise since it does not stall the processor. The ADC takes about 13 cpu cycles or 0.8 µseconds to make a conversion. This means that many samples (around 208 for each sample used) are really thrown away. This apparent waste of cycles are due to the fact that conversion initiation in another function (where it should have gone) was problematic. Because we did not encounter timing problems we did not see why to fuss with the ADC if the whole circuit was behaving properly.

The heart of the program is an ISR performed every 168µs via the use of a Timer1 output compare interrupt. This ISR takes the sample from the ADC stores it into the buffer and proceeds to processes it. Once this is done, the processed sample is sent to the DAC or digital to analog converter so that it could be then go through some analog processing including low pass filtering. To do the actual coding of the guitar effects we had to study closely how the effects where defined mathematically and then try to implement it in our micro controller. For two of our four effects (mainly Reverb and Echo) we had to use past data values of our samples. Hence we had to always store our samples in our “circular” buffer. A circular buffer is nothing more than a long array of memory which when the index reaches its maximum it starts over again and it overwrites the old data. The circular buffer hence gives the impression of having infinite memory. However, this is clearly a false statement since your data is constantly being overwritten. In terms of time, a “longer” circular buffer means more time for samples to be overwritten. In our case as we mentioned earlier our circular buffer gave us at most a third of a second before our data was rewritten. In terms of effects implementation, this meant that at its best an echo arrived a third of a second after its original sound and not a millisecond later.

To implement our effects we had to first discover what they were and how were they defined mathematically. Most of these resources were obtained from the article “Implementing Professional Audio Effects with DSP” by Micea et al. (see Appendices page)

Reverb:

Reverb or reverberation is the acoustical effect of rooms and enclosed buildings on the sound waves. On a large auditorium for instance sound waves are reflected off walls, floors, carpets, people etc. before the sound actually gets to the listener. As a result, the sound heard at any given time is the sum of the sound from the source as well as the reflected sound. Hence the equation for reverberation would look something like this.

y[n]= x[n] + gain1*x[n-d1] + gain2*x[n-d2] + gain3*x[n-d3]…

Where y[n] is the output and x[n] is the input sample and gainN are constants and dN are also time(index) constants. Luckily it turned out that the largest delay dN corresponds to about a third of a second this was good since it amounts to almost exactly the amount of memory we had for the Mega32.


Tremolo:

The tremolo effect is a variation in amplitude gain in the original sound wave. The equation would look something like this.

y[n]=v[t]*x[n]

Where v (volume) stands for a time varying function of time and x and y are as above.
This function is almost always a sinusoid. However using the sine function would have taken too many cpu cycles and multiplying by any number not a divisor of two was too costly. Hence we created v[t] to be a triangle wave whose numbers where .125,.250,.375,.5,.625,.75,.875 and 1 and back down again in a triangular fashion. Using this triangle wave to multiply against x[n] was easy and cheap(in cpu cycles) since at most any multiplication would take a total of three shifts and 2 adds.


Distortion:

Distortion was first seen in old vacuum tube amplifiers when the signal gain was just too high for the amp to withstand. At first distortion was seen as a fault in the amplification of sound, however as time passed distortion became innate in music and even amplifiers started bringing their own distortion effects with them. Hence the musician was able to purposely distort his music and moreover, control the overall distortion he desired.

In old amplifiers distortion translated into a chopping off of the sound wave. It turns out that “nice” distortion occurs when only one side of the sound wave is chopped off and the other is left to pass uncorrupted. This gives the effect of clarity and at the same time of distortion in the overall sound. In our distortion effect we decided to cut off the top side of the wave as opposed to the bottom.

In terms of implementation, distortion was the easiest effect to implement. Because sound was digitized we could set all values above some parameter “highVal” to highVal.

Echo:

Earlier in the report I started saying that we were not able to implement and echo in the 8515 and that is why we switched to the Mega32 and to other effects. The truth is that the echo came to us by surprise. Although it is true that most echo processors have parameters where you can change the total time delay (delay between echoes) and the gain (how fast the echo decays exponentially) we did not have this functionality since we did not have memory to make an echo time delay longer than a third of a second. We discovered the echo by testing our reverb effect. The reverb is in fact many echoes of shorter time delay and varying gain clustered together. Once we discovered a short echo was possible to implement we went for it. Of course we wouldn’t have all of the parameters we would have had in the 8515 but we were still thrilled that we were going for our fourth guitar effect and everything was behaving properly.

The basic echo is done simply by adding a delayed past sample to the current sound sample. The equation would look like this.


y[n]=x[n] + gain*x[n-delay]

Where delay in our case should be as long as possible or 1/3 of a second. What we would hear is hence a single echo 1/3 of a second after the original sound (not very interesting). The figure below shows a schematic of this implementation.

 

The other type of echo is a more realistic one and is the one we decided to implement. This echo effect is described by a first order difference equation and is seen in the schematic below. The feedback gain "loop" feeds the current output again back into the delay as seen below. This echo sounds more realistic because it has lots of echoes depending on the gain. That is, once after the original sound, we hear echoes which decay in time exponentially. It is possible thus to have no attenuation and have infinite echoes. Of course in our circuit because we had so little time between echoes if we chose an gain too big we would get too much feedback and all we’d get out is noise. Hence to be able to have a looping effect (zero gain) we must have at least enough time for the initial sound to die out before the echo is received. Below is a schematic of the echo effect implementation and we invite you to look at the code for it’s implementation.



<Hardware Design>


The following diagram represents the circuit we used for our project:

In the above diagram, R1 = 100k, R2 = 900k, R3 = 1000k, R4 = 48Ohms, C1 = .06uF, C2 = 22uF, and C3 = 1uF. The op-amps we used were LMC7111's, and the D/A converted was a DAC0808 (Look at the datasheet for circuit additions according to the DAC's specification). All the op-amps were driven with 5V and -5V, as well as the DAC. To do this we had to use an external power supply, which gave us no trouble at all when done correctly (connecting both grounds and Vdd's to the MCU).

This circuit takes the analog guitar signal, which varies between 0 and 500mV peak to peak centered at zero, amplifies it, converts it to digital for the MCU to process it, converts it back to analog, and attenuates it back to the original range for the speakers (guitar amp) to output it. The first capacitor combined with the voltage divider give the signal an offset of 2.5V, and afterwards, the first op-amp amplifies the signal to cover the range 0-5V. The signal is then converted to digital with the ATmega32's ADC, and is then processed by the MCU with the function chosen by the user through the buttons and the LCD. The signal is then output to the DAC to get an analog signal. Capacitor C2 gets rid of the 2.5V offset, and feeds the signal to the second op-amp. This op-amp was intended to be an inverting adder since initially we wanted to add the guitar to the processed signal for the effects of reverb and echo. Unfortunately, we couldn't get a switch to work so that it passed the guitar in the cases of echo and reverb but passing ground for the other effects (a transistor would not pass the negative voltages and the CMOS switch chip we could get was not working as it should, which could not be explained even by Professor Land). Therefore, at the end, to not change the circuit, we just added the guitar signal in the MCU whenever we wanted and hardwired ground to one of the inputs to our adder. The third op-amp is simply an inerter since the adder is an inverting adder. The signal should be in the original range at this point and is passed through an RC LPF to get rid of higher frequency noises. It is then ready to be fed to the speakers.

Initially, when we wanted to do simply an echo and didn't have enough ports for buttons or the LCD, the circuit would have done most of the project since it would be the one adding the echo to the signal. However, after we changed our mind about the 8515, as our project progressed and we added more effects, the project ended up depending mostly on the software, even though without the appropriate circuit (amplifying and biasing) it would have been impossible to process the signal digitally.


<Results>


The results of our project were very satisfying. I even dare to say that we impressed ourselves and some around us.

Our device was very interactive and responsive to our commands. The LCD and the choice of multiple effects, which were great results of us giving up on the 8515 and the long echo, allowed us to make a user-friendly and fun system. The machine's reactions to us switching between effects were very quick and steady, especially between the different speeds of the same effect (when changing from effect to effect sometimes the noise in between can be annoying).

As far as accuracy goes, it was very hard to get a real idea of what our signal was doing since the guitar signal changes so much over time. Because of this we always used a sine wave to verify that our effects were working properly, which always gave us surprisingly accurate results for the analog processing of the signal (the hardware part of the process). On the other hand, for effects such as reverb and echo, it was almost impossible to rely on theory, since when we add many unpredictable signals together (past signals), the magnitude of the result as well is unpredictable and can cause overflow for the MCU, which comes out as noise. The solution to this problem was mostly trial and error with the code to get the best sound we could.

Our design, I believe, is very safe, since we are using only a +-5V supply with simple devices (+-5V is under our devices' maximum capacity). This means that our circuit should never blow up or over heat after being used for long periods of time. Also, our signal is always going through short and stable protected wires. On the other hand, since we didn't have time to make a suitable box in which to bound our circuit, outside effects such as radiation and transmission of RF signals could if big enough affect our circuit's safety.

The only thing we would have liked to have been safe from is signals in the lab due to either light or other projects. We attribute most of the noise in our signal (since the noise from the MCU was very low although notable) to have come from these elements, since in different days we got different noise levels.

Above all, we consider our design to be very user-friendly and entertaining, despite the undesired noise. The LCD and the use of only two buttons made it easy to go through our machine. If we had been able to make it portable, we would have definitely used it at home and shared it (shown it off) until it stopped working.


<Conclusions>



This project met and exceeded our expectations. Initially, we thought we would only get to make one effect, but later realized how easy it was to keep adding effects and to make them work as desired. Had we had more time to work on it, we would have liked to make it portable by making our own prototype board for the ATmega32 and a more compact circuit, and a suitable box with holes for the LCD and plugs. We would have also liked to be able to make a switch work so we could add the guitar signal directly when needed. Other than that, we wouldn't change a thing.

Reverse Engineering: "the process of analyzing a subject system to identify the system's components and their interrelationships and create representations of the system in another form or at a higher level of abstraction."

Since we are definitely not the creators of sound effects synthesizing I would say that we have definitely reverse-engineered this concept. We researched on the design and implemented it to our own preferences. On the other hand, if we could have accounted for the noise and the un-portability, I definitely think that this precise design could be patented. Our design is very affordable and flexible to personal taste. If any user asked us to modify it to add any specific effect, it could be done on no cost other than programming time.

We were lucky enough to have all the parts available in the lab and to not need to deal with corporations to build our circuit. On the other hand, I ordered an ADC from Maxim-IC and obtained it (even though a little late) without any trouble at all. Also, our code was completely original, so we technically didn't need any assistance other than that provided by ECE 476.

It is very important to know and follow the IEEE Code of Ethics, since it ensured the correct practice of our field. Below are some of the points in the code that affected our project and how.

2. To avoid real or perceived conflicts of interest whenever possible, and to disclose them to affected parties when they do exist;

This point was met since the beginning of our work. I (Tatiana) wanted to use the 8515 for its memory and Gaby wanted to use the Mega 32 for its ADC and port availability. We dealt with this in a very open matter, since we've known each other for a long time, and came to a decision together that worked out for both our convenience.

3. To be honest and realistic in stating claims or estimates based on available data;

Gaby did the best out of both on this point. While I kept trying to make an external ADC and SRAM work with the 8515, he stayed realistic about our time limitation, and thanks to his influence I realized that the Mega 32 was a much better choice and that probably we wouldn't have been able to finish had we used the 8515, since it involved aspects we had never dealt with.

5. To improve the understanding of technology, its appropriate application, and potential consequences;

I dare say for both of us that in this project we finally understood in practice a great part of the theory we have learned in our undergraduate years. Before this project many aspects of DSP and op-amp circuits were really ideal in our minds without us knowing it. Through this project we saw many of the actual limitations of technology design and how to overcome and balance them.

6. To maintain and improve our technical competence and to undertake technological tasks for others only if qualified by training or experience, or after full disclosure of pertinent limitations;

I think we did pretty well on this point, and this can mainly be seen in our division of labor. While Gaby usually freaks out at the sight of hardware, I usually think I can program better than I really can. Therefore we divided the project according to our fortes and only interfered with the other's ideas when we really thought we were right in doing so. At the same time, in observing each other work, we saw how things can be much simpler than they seemed for each.

7. To seek, accept, and offer honest criticism of technical work, to acknowledge and correct errors, and to credit properly the contributions of others;

I would say as suggested above, that we carried out this point very well. Since we have worked together on many occasions, we have no problem on offering or taking criticism. Even though we each had an area of the project on which to focus, as it is both of our natures, we always looked into each other's work to see if there was anything that could be made better. As it can be seen from this report, I dare say for both of us that the credit has been fairly attributed.


<Appendix A: Code>


Final.c

/***********************************************************************
Final Project ECE 476
Gabriel Rivera gjr8
Tatiana Lamela-Rabell tl74
April 16,2003
***********************************************************************/


#include <Mega32.h>
#include <stdio.h>

#include <lcd.h>
#define bufferLength 1700
#define delayLengthArray 7
#define totalNumEffects 5


#define Reverb 0
#define Distortion 1
#define Tremolo 2
#define Echo 3
#define NoEffect 4

#define NoPush 1
#define MaybePush 2
#define Pushed 3
#define MaybeNoPush 4
#define LCDwidth 16 //characters
#define t0 100 //Debounce Button0
#define t1 100 //Debounce Button1
#define t2 200 //lCD_Display
#define sampleTime 42//this is for 42*4us=168us=5952.38Hz approx 6KHz
#define Out PORTB
#define LCD PORTC
#define MuxSelect PORTA.1
#define Buttons PORTD

#define maxVol 8
#define minVol 0
#asm
.equ __lcd_port=0x15
#endasm


//Function Declarations\
void initialize(void);
char mult(char Num, char multVal);
int multi(int Num, char multVal);
void Button1_Debouncer(void);
void Button0_Debouncer(void);
void LCD_Display(void);

//Variable Declarations
unsigned char Ain,tmpAin;
unsigned char dataBuffer[bufferLength];
unsigned int delay[delayLengthArray]={0,500,700,1000,1300,1500,1594}; //each 100 in delay correspond to actualtime of 17ms
unsigned int i;//delay[delayLengthArray-2]+100;
unsigned int j,k=0,BigOutSignal;
unsigned char OutSignal,direction=1,vol=0;
unsigned char HighVal[]={128,135,145,65000};//Value at which to cut off audiowave for distortion
unsigned char freq[]={30,50,70,90};
unsigned char time0, time1, time2; //timeout counters
unsigned char PushFlag0,PushFlag1; //message indicating a button push
unsigned char PushState0,PushState1;
unsigned char effectIndex=3,propertyIndex[totalNumEffects]={0,0,0,0,0};
unsigned char lcd_buffer[16];
unsigned char SignalGain=0;


char flash *effects[totalNumEffects]={"Reverb","Distort","Tremolo","Echo","NoEffect"};

char flash *properties[totalNumEffects][4]={
{"Church","Auditorium","LargeRoom","Reverse"},
{"High", "Medium","Low","None"},
{"Fast","Medium","Slow","VerySlow"},
{"NoEffect","NoEffect","NoEffect","NoEffect"},
{"NoEffect","NoEffect","NoEffect","NoEffect"}

};


/*****************************void Main(void)****************************
Calls initialize function and then waits for timeN to be zero and calls three more functions:Button0_Debouncer(),Button1_Debouncer(), LCD_Display().
***********************************************************************/


void main(void)
{

initialize();
while(1)
{
if(time0==0)
Button0_Debouncer(); //occur every 50ms

if(time1==0)
Button1_Debouncer();//occur every 50ms

if(time2==0)
LCD_Display();//occur every 100ms

}
}


/****************************void LCD_Display(void)***********************
Updates the
DISPLAY every tenth of a second...t2=200 and checks for button pushes. if Button 0 is pushed then we cycle through
the different effects if we push button 1 we cycle through the effects properties...by this I mean the effect can be Distortion
but what type? High,medium or low distortion
***********************************************************************/

void LCD_Display(void)
{

if(PushFlag0|PushFlag1) //dont want to spend cycles here if not needed(no buttons has been pressed/most of the time)
{
SignalGain=0; //everytime any button is pushed the AGC(Automatic Gain Control) clears again. The logic behind this is that
//every effect has it's own DIFFERENT sound level so we set the gain to 0 or normal each time any button is pressed. THis
//takes about 168us*7=1millisecond

if(PushFlag0) //if button0 push cycle through effects.effectsIndex holds the current effect
{
PushFlag0=0; //clear flag
if(effectIndex!=totalNumEffects-1)//if we reach the end start over "cycle through"
effectIndex++;
else
effectIndex=0;
}


else if(PushFlag1) //same thing as button0 but now for button1
{
PushFlag1=0;//clear flag

switch(effectIndex)
{
case Reverb:
if(propertyIndex[Reverb]!=3)//for all effects the array propertyIndex[effectIndex] holds the current property
propertyIndex[Reverb]++;
else
propertyIndex[Reverb]=0;
break;

case Distortion:
if(propertyIndex[Distortion]!=3)
propertyIndex[Distortion]++;
else
propertyIndex[Distortion]=0;
break;


case Tremolo:
if(propertyIndex[Tremolo]!=3)
propertyIndex[Tremolo]++;
else
propertyIndex[Tremolo]=0;

break;


case Echo:
if(propertyIndex[Echo]!=3)
propertyIndex[Echo]++;
else
propertyIndex[Echo]=0;

break;

case NoEffect:
if(propertyIndex[NoEffect]!=3)
propertyIndex[NoEffect]++;
else
propertyIndex[NoEffect]=0;

break;
}//end switch
}//end elseif


//writing to LCD every tenth of a second
lcd_clear();

lcd_gotoxy(0,0);
lcd_putsf("Effect:");

lcd_gotoxy(8,0); //effectIndex
lcd_putsf(effects[effectIndex]);

lcd_gotoxy(0,1);
lcd_putsf("Sel:");

lcd_gotoxy(5,1);
lcd_putsf(properties[effectIndex][propertyIndex[effectIndex]]);



}//end outer if

}//end LCD_Display


/*****************************void initialize(void)**************************
Starts A/D conversion starts Timer0 and Timer1
***********************************************************************/

void initialize(void)
{
// clearing buffer
lcd_init(LCDwidth); //initialize the display
lcd_clear(); //clear the display

lcd_gotoxy(0,4); //welcome screen
lcd_putsf("Welcome!");
lcd_gotoxy(0,1);
lcd_putsf("Select Effects");

for(j=0;j<bufferLength;j++)//clearing the buffer initially
dataBuffer[j]=0;


//SETTING UP TIMER 1 setting values for registers
OCR1A = sampleTime; //time between samples 168 microseconds =5952samples/second
TCCR1B = 11; //divide by 64, clear-on-match (see page 110 textbook)OK
TCCR1A = 0x00; //turn off pwm and oc lines
TIMSK = 0x10; //enable interrupt T1 cmp

//SETTING UP TIMER 0 value for 1/2 Msec at 8 MHz
TCNT0= 131;// reload=256-125;
TIMSK=TIMSK|1;//turn on timer 0 overflow ISR see page 80 in data sheet
TCCR0=3; //divide by 64


ADCSR=0b11001111; //turn on ADC,allow interrupt at } of conversion,prescaler set to divide by 128
ADMUX=0b00100000; //channel zero/ left adj ADLRA /External Aref
DDRC=0xff;//output LCD
DDRD=0x00; //input buttons
DDRB=0xff; //output to D/A
DDRA.1=1;//output MuxSelect
//Buttons=0xff;

i=delay[delayLengthArray-2]+100; //this is the index for the buffer it needs to start after the delay indexes
time0=100;
time1=100;
time2=200;

PushState0=NoPush; //initializing Debounce StateMachines
PushState1=NoPush;
SignalGain=0;

#asm ("sei");
}

/****************interrupt [TIM0_OVF] void timer0_overflow(void)**************
this interrupt occurs every half millisecond and is used to call tasks.
***********************************************************************/
interrupt [TIM0_OVF] void timer0_overflow(void)
{

if(time0>0)
time0--;
if(time1>0)
time1--;
if(time2>0)
time2--;

TCNT0=131; //reload to force 1/2 mSec overflow

}


/*****************interrupt [TIM1_COMPA] void t1_cmpA(void)****************
This is the IMPORTANT CODE! This occurs every 168us so we have to be in and out FAST!
Here we alter the digital audio signal and output it.
***********************************************************************/

interrupt [TIM1_COMPA] void t1_cmpA(void)
{

tmpAin=Ain; //taking the digital value from the ADC
OutSignal=0;//this is the tmp variable where we hold the sample while we do stuff on it
BigOutSignal=0;//same as above but and integer... we didn't need this initially but then we figured out we did.
//many of our signal processing is adding signals to past signals so do the math if we have a wave
//of 128 or 2.5 volts and add it to another on of 128 or 2.5 volts what do we get 5 volts right?
// wrong 128+128=256 and if our variable is a char then it rolls over and it is zero volts
// so we use an int and then scale the signal w/ the AGC and turn it back into a char when outputting...

//moving circular buffer indexing
if (i!=bufferLength-1)
i++;
else
i=0;

//if the effect is an ECHO we need a feedback buffer
if(effectIndex!=Echo)
dataBuffer[i]=tmpAin;

else
{
BigOutSignal= ( ((int)tmpAin) + ((int) (dataBuffer[delay[0]]*.30)) );//delete tmpAin when we have switch.
dataBuffer[i]=( ((char)(BigOutSignal)) ) ;
}

switch(effectIndex)//what effect are we in now?
{


case Reverb:

switch(propertyIndex[effectIndex])//what property in Reverb are we in now?
{

case 0: //Church-more additions of past samples ==bigger room
BigOutSignal= (( ((int)tmpAin)+ (multi(dataBuffer[delay[0]],2))+ ((int)multi(dataBuffer[delay[1]],1))+ ((int)multi(dataBuffer[delay[2]],2)) + ((int)multi(dataBuffer[delay[3]],1))+ ((int)multi(dataBuffer[delay[4]],0)) + ((int)dataBuffer[delay[5]])) -400) ;
break;

case 1: //Auditorium
BigOutSignal= ( ((int)tmpAin)+ (int)multi(dataBuffer[delay[2]],2)) + ((int)multi(dataBuffer[delay[3]],1))+ ((int)multi(dataBuffer[delay[4]],0)) + ((int)dataBuffer[delay[5]]) -300 ;

break;

case 2: //Large Room
BigOutSignal= ( ((int)tmpAin)+ (int)multi(dataBuffer[delay[3]],1))+ ((int)multi(dataBuffer[delay[4]],0)) + ((int)dataBuffer[delay[5]]) -250 ;

break;

case 3://Reverse Rev-like church but inversed scaling
BigOutSignal= (( ((int)tmpAin)+ (multi(dataBuffer[delay[0]],0))+ ((int)multi(dataBuffer[delay[1]],1))+ ((int)multi(dataBuffer[delay[2]],2)) + ((int)multi(dataBuffer[delay[3]],3))+ ((int)multi(dataBuffer[delay[4]],4)) + ((int)multi(dataBuffer[delay[5]],5))) -400) ;
break;


}//end switch
break;


case Distortion: //pretty self explanatory...just clipping the wave
if(tmpAin>HighVal[propertyIndex[effectIndex]])
BigOutSignal=HighVal[propertyIndex[effectIndex]];
else
BigOutSignal=tmpAin;
break;



case Tremolo:

k++;

//all this ugly code below just choses a volume or a gain for the signal
//so the volume function is a simple triangle wave of height 1 and amplitude one
if(k==freq[propertyIndex[effectIndex]])
{

k=0;
if(direction ==1)//countingup
vol++;
else
vol--;
if(vol==maxVol)
direction=0;//1 is to count up 0 counts down
else if (vol==minVol)
direction=1;
}

BigOutSignal= ((int)mult(tmpAin,vol));//+OutSignal;sinArr[time]
break;

case Echo://most of the echo processing happens above..see above
SignalGain=0;//don't want to mess around w/ AGC since the echo is afeedback loop we can easily distort everything!
break;

case NoEffect: //out=in basically
BigOutSignal=tmpAin;
break;


}//end switch


//here we move the delays foward and when they reach the end we start over again.
for(j=0;j<delayLengthArray;j++)
{
if (delay[j]!=bufferLength-1)
delay[j]++;
else
delay[j]=0;
}



//this is the AGC we simply increase the signal gain until we are happy
if( ((BigOutSignal>255) & SignalGain<7) ) //Automatic Gain Control.
SignalGain++;

//here we convert the int to a char and scale the int so that it can fit in the char.
OutSignal=( ( (char) ( multi(BigOutSignal,SignalGain)) ) );

Out=OutSignal;//Out to DAC


}// interrupt

/*****************char mult(char Num, char multVal)***********************
This function basically just multiply a value using shifts
the +16/32/48 is needed because if we have something like our input like x[n]= 2.5sin(f*n)+2.5 volts and we divide by 2 or shift by 1
to the left we get x[n]/2= 1.25sin(f*n)+1.25 hence we need to bring it back up to a 2.5 offset or when adding our waves we
get garbage. so we add 1.25V or 64 see case 4 which demonstrates the operation
***********************************************************************/

char mult(char Num, char multVal)
{

switch (multVal)
{
case 0: //'1'
return Num;
break;
case 1: //'.875':
return ( ( ((Num>>1)+(Num>>2)+(Num>>3))+16) );
break;
case 2://'.75':
return ( ( ((Num>>1)+(Num>>2))+32));
break;
case 3: //.675
return (( ((Num>>1)+(Num>>3))+48));
break;
case 4://'.5':
return (((Num>>1) + 64));
break;
case 5://'.375':
return ((((Num>>2)+ (Num>>3))+80));
break;
case 6://'.25':
return (((Num>>2)+96));
break;
case 7://'.125':
return ( ((Num>>3)+112) );
break;
case 8://'0'
return 128;
break;
}


}

/*********************char multi(int Num, char multVal)**********************
this is the same as the mult function but for ints
***********************************************************************/

int multi(int Num, char multVal)
{

switch (multVal)
{
case 0: //'1'
return Num;
break;
case 1: //'.875':
return ( (((Num>>1)+(Num>>2)+(Num>>3))+16) );
break;
case 2://'.75':
return ( ( ((Num>>1)+(Num>>2))+32));
break;
case 3: //.675
return ((((Num>>1)+(Num>>3)+48)));
break;
case 4://'.5':
return (((Num>>1) + 64));
break;
case 5://'.375':
return ((((Num>>2)+ (Num>>3))+80));
break;
case 6://'.25':
return (((Num>>2)+96));
break;
case 7://'.125':
return ( ((Num>>3)+112) );
break;
case 8://'0'
return 128;
break;
}


}


/**********************void Button1_Debouncer(void)* **********************
Debounces button1 every 50ms..t1=100. Button1 is the property select for each effect.
***********************************************************************/


void Button1_Debouncer(void)
{
/*lcd_clear();
lcd_gotoxy(0,0);
sprintf(lcd_buffer,"%i",PushState1);
lcd_puts(lcd_buffer);
*/
time1=t1; //reset the task timer
switch (PushState1)
{
case NoPush:
if (~PIND == 0x02) PushState1=MaybePush;
else PushState1=NoPush;
break;
case MaybePush:
if (~PIND == 0x02)
{
PushState1=Pushed;
PushFlag1=1;
}
else PushState1=NoPush;
break;
case Pushed:
if (~PIND == 0x02) PushState1=Pushed;
else PushState1=MaybeNoPush;
break;
case MaybeNoPush:
if (~PIND == 0x02) PushState1=Pushed;
else
{
PushState1=NoPush;
PushFlag1=0;
}
break;
}
}

/*********************void Button0_Debouncer(void)*************************
Debounces button0 every 50ms..t0=100. Button0 is the effect select button
***********************************************************************/


void Button0_Debouncer(void)
{
time0=t0; //reset the task timer
switch (PushState0)
{
case NoPush:
if (~PIND == 0x01) PushState0=MaybePush;
else PushState0=NoPush;
break;
case MaybePush:
if (~PIND == 0x01)
{
PushState0=Pushed;
PushFlag0=1;
}
else PushState0=NoPush;
break;
case Pushed:
if (~PIND == 0x01) PushState0=Pushed;
else PushState0=MaybeNoPush;
break;
case MaybeNoPush:
if (~PIND == 0x01) PushState0=Pushed;
else
{
PushState0=NoPush;
PushFlag0=0;
}
break;
}
}


/********************interrupt [ADC_INT] void adc_done(void)*****************
gets value from A/D and exits...Interrupt constantly executes.
***********************************************************************/
interrupt [ADC_INT] void adc_done(void)
{
Ain = ADCH;
ADCSR= ADCSR|0x40;
}


<Appendix B: Schematics>


Hardware:

Effects:

 
Echo

 


<Appendix C: Cost and Equipment>


Even though our project did not cost us anything since everything we needed was already available in the lab or through Professor Land, I will now make an estimate of what the cost of our project would be according to the major devices it uses (excluding resistors or capacitors).

LMC7111: 0.50*3 =$1.50

DAC0808: $0.50

ATmega32: $15

This adds up to $20, which is a much less than products in the market like these. Of course, you could argue that the ones on the market don't have an STK500 hooked up to them but nonetheless the whole point is OURS IS BETTER! As a matter a fact, with a little more time our project could have been mounted on a prototype board and encased in some stainless steel box for a couple of bucks more.


<Appendix D: Division of Labor>


The division of labor mainly went as follows:

Gaby suggested the project, and then we both developed together the high-level design. Gaby did all the programming and I took care of designing and building the circuit, but we both debugged together both software and hardware. This worked out very well since we each concentrated on what is easier for us (and what we do better).


<References>


1. Implementing Professional Audio Effects Using DSP. Micea et al.

2. http://www.harmony-central.com/Computer/Programming/ for reference on algorithms for effects.

3. http://www.harmony-central.com/Effects/effects-explained.html for reference on the effects.

4. http://www.jwcs.net/developers/dap/algorithms/reverb.htm for reference on reverb algorithms.

5. http://dsplabs.utt.ro/~micha/publications/pdfs/Audio%20Processor.pdf: Paper on effects and their math.

6. http://cache.national.com/ds/LM/LMC7111.pdf: Datasheet for the op-amps.

7. http://cache.national.com/ds/DA/DAC0808.pdf: Datasheet for the DAC.

8. http://www.atmel.com/dyn/resources/prod_documents/2503s.pdf: Datasheet for ATmega32.

9. http://www.maxim-ic.com for ordering parts we didn't have in lab (such as the ADC when we wanted to use the 8515).

10. http://www.cc.gatech.edu/reverse/news/nasa-jpl/0015.html for reference on the meaning of reverse engineering.