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.
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!
PRE-SET PROPERTIES: High, Medium, Low, None
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.
PRE-SET PROPERTIES: None
EFFECT: No Effect
PRE-SET PROPERTIES: None
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 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.
The tremolo effect is a variation in amplitude gain in the original sound wave. The equation would look something like this.
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 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.
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.
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
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.
For more detail: Guitar Special Effects