The main structure for the software is divided up into several parts, first we have an overlaying state machine that keeps track of all the settings(ADSR, wavetype for both oscillators, operator type, demos, tone shifters, and filtering). From there each function is asynchronous running off of the flags set by the overall machine. In addition we have two interrupts running, one to collect input from the midi controller and another to control the output of the PWM. The overall structure can be seen in appendix and the following sections describe the specifics for each part of the software. A diagram of this structure can be viewed in the Appendix
Main State Machine
The main state machine to control the setting was pretty generic. The first level(using button0) cycled between the main functions(ADSR, OSC1, OSC2, OPERATOR, SPECIAL FUNC). From there we have a second level(using button1) that scrolls through the different wavetypes(sine,square,triangle,saw tooth) for OSC1 and OSC2, the different operators(add, subtract, multiply) for OPERATOR, and the different functions(filter, demo, tone shift) for SPECIAL FUNC. Each state sets a flag or act like a flag to let the rest of the software know what the current settings are.
Midi Receive Interrupt
To recieve the input from the MIDI we used the serial receive pin(PIND.1). By setting UCSRB = 0b10011000 and UBRRL = 31 we set the receive interrupt to turn on when a signal is received and and a baud rate of 31.25 KBaud. Within the interrupt we decoded the midi signal which is represented in 3 bytes(refer to Theory for more details). Our decoding algorithms looked for a NOTEON(0x92) status byte and then stored the following byte as the note to be played, and the third bite as the velocity(how hard the button was pressed) for that note. From there, we turned on an oscillator(by setting the increment) to play that specific note at the given velocity and the PWM/sound generation code takes over. When the velocity for a note is zero, that signal the note is released and the oscillator is signaled to stop sustaining the note.
DDS, PWM Interrupt and Sound Generation
The sound generation is performed through the PWM and DDS. For a single channel we used an accumulator and an incrementer to scroll through the values of a wave at different speeds to represent different frequencies. This value is then amplitude modified by using the amp variables provided by the ADSR code and the key velocity code. The final value is then outputted through the PWM to create the sound the desired sound.
We made two main changes from the PWM we used in the cricket generator lab. First we decreased the number of bits used for the accumulator from 32 to 16, and the second we decreased the speed of the PWM by a factor of 8. However, we didn't want to slow down the PWM itself, because that would create a high frequency buzz. To solve this problem, we set the PWM at full speed while running an interrupt on a second timer(timer1) with a prescalar of 8 and used that to update the PWM. The main reason we did these two changes was because we need more time to run our code. This process allowed us to fit everything in. Since:
increment = (freq) * 2 X *N *256/ (16 Mhz)
then the new changes to the code modifed the multiplier for our increment to 8.3886. Hence to produce a music note A(440Hz) we would set increment = (440)*8.3886. Using PORTB.3 as the output we would be able to produce a sound with an A pitch.
To create a polyphony of 8, we simply copied this code 8 times, running it on 8 accumulators and incrementers, and then summing the 8 waveforms at the end and output that combination.
To formulate the ADSR times, we used an amplitude shift on the output wave. Essentially for each variable the total time is divided by a set amount and then at each increment of that time, the amplitude is dropped by 10%. The number of division is Dependant on the variable. For attack the raise is always to max, hence it raises 10% for 10 division(attack time/10). However, the decay and release time are dependent on the sustain level, hence decay will drop to that level and the release will drop from that level. Hence, for example if sustain is at 70%, then decay will drop 10% for 3 divisions(decay time/3) and release will drop 10% for 7 divisions(release time/3). This amplitude is shared by the wave generation code in the PWM interrupt. Hence over the course of the note(tracked by a timer for), the note will raise and decrease in amplitude creating the ADSR and the sound associated with it. Here are some examples of these envelope waveforms.
To create the Tone shifter we simply took the current note and then used another channel to run that note shifted by the desired halftone. Since each tone can be represented a multiple of the twelths root of two, we just multiple or divide by that amount to move up and down the tone.
Low Pass Filter
The low pass filter was just the IIR onepolelow() filter we used for the video generation lab(lab 4). The code for it can be found on the ece476 webpage for that lab. The filter is turned on when the main state machine throws the flag.
Waveform types and operation
The waveform type is create by setting two wave buffers (one for each of the two oscillators) to the predefined tables(i.e. sine, square, triangle, sawtooth) of those wave. Once the two waves have been determined, then they are multiplied, added or subtracted to create a final wave for the PWM to use. The type of the wave copied into the wave buffers and the operation performed on them is determined by the flags set by the main state machine. Here are some of the waveforms we were able to produce:
Saw Tooth Wave: