Project 03 |
Goals
- Use registers to setup pin outs and a timer.
- Use a timer to create an oscillator.
- Implement code that runs quickly using bitwise shifting instead of division.
- Choose variable types based on memory efficiency.
- Implement Additive Synethesis signal generation techniques.
|
Setup |
Setup
Start with your code from Lab 3 that determines which button is pressed using an analog input and plays a note using the tone library.
Save it as a new Arduino project titled colbyUsername_P03_Part1 (with your colby username, mine would be hewolfe_P03_Part1).
Notes on Variable Types
- Chars are 1 byte, so we can use them to represent values while taking up less space than an int.
- Unsigned ints are 2 bytes, representing values from 0 to 216.
Waveforms.h
- Create a file
Waveforms.h and put it in the same folder as your arduino project.
- Add a header guard to the top of
Waveforms.h
- Hardcode a constant sine wave table with 256 chars, ranging from 127 to -127. Use __attribute__ to allocate the array on a 256-byte boundary.
- Create a struct
OscillatorState
- It should have a field volume, which is the volume multiplied for the oscillator. The max value for the volume variable should be 127, so we can use a char to store this value.
- With a field, keep track of the phase step, which is the frequency of the oscillator. This value will be between 0-216.
- With a field, keep track of the accumulated phase, which steps through this oscillator. This value will be an
unsigned int (2 bytes).
- Add a
OscillatorState constructor header, with a parameter float frequency.
- Add a function
set_phaseStep header, with a parameter float frequency. This method coverts from frequency to phase step.
- Add a function
update header which returns an int. This gets the current value of the oscillator multiplied by the oscillator’s volume.
|
Timers |
Timers
You will be creating a phase correct PWM at the nominal frequency of 31372.549. Updating the compare match at each interrupt will be used to create a sinusodial signal.
Videos and Datasheet Reading
Introduction to Registers
Introduction to External Interrupts
Introduction to Timer Interrupts
Review 16.9-16.11.8 of the ATmega328P Datasheet
Create your C++ file Waveforms.cpp
- Create a file
Waveforms.cpp and put it in the same folder as your arduino project.
- Include Waveforms.h
- Create a global volatile variable
curWave which is a pointer to sine256.
- Implement
OscillatorState, set_phaseStep and update
OscillatorState initializes the the phase step using set_phaseStep, volume and phase accumulation.
set_phaseStep sets the phase step to be the frequency multiplied by the max value phase step can be (as an unsigned int), divided by the timer’s frequency. (Reference: Arduino Datasheet 16.9.4)
update adds the phase step to the phase accumulator and returns the current value of the oscillator multiplied by the oscillator’s volume. To find the current value of the oscillator, we can use the “high byte” of the phase accumulator as the current sample index, which is the equivalent of dividing the value by 256. This can be done using a bitwise shift.
Notes on Bitwise Shifting
Set Registers in Setup()
- Begin a Serial connection at 9600 bps.
- SWITCH SPEAKER TO DIGITAL PIN 9.
- Using a bitwise shift to set pin 9 to be an output. Port Registers & Pin Mapping
- Set Timer1 to a phase correct PWM at the nominal frequency of 31372.549 Arduino Reference
- Set the Timer/Counter1 Control Register A to Clear OC1A on Compare Match when upcounting and set OC1A on Compare Match when downcounting. (See Arduino datasheet, 16.11.1, Table 16-3)
- Set the Timer/Counter1 Control Register A to PWM, Phase Correct, 8-bit (See Arduino datasheet, 16.11.1, Table 16-4)
- Set the Timer/Counter1 Interrupt Mask Register to have Overflow Interrupt Enabled (See Arduino datasheet, 16.11.8)
Timer Overflow Interrupt
- In your .ino file, create a function called getByteLevel with an int as a parameter which returns an inline byte.
- This is expected to receive a value from your wavetable (-127 to 127) multiplied by volume (max 128)
- Use a bitwise shift to divide the input value by 128
- Add 128 so the value is between 0 and 255
- Return the value as an inline byte
- Create a global variable in your .ino file which is of type
OscillatorState.
- In your .ino file create an interrupt that will be called when timer1 overflows
ISR(TIMER1_OVF_vect){ … }
- Get current value of the oscillator multiplied by its volume.
- Set
OCR1A to the byte level of the value.
Replace Tone() with an Oscillator
- In your .h file, create a frequency array with the 7 note frequencies (1 per button A-G) using frequencies from the arduino tone tutorial. Higher notes (greater than octave 4) will sound better.
- In your .h file, create a enum with the note for each frequency.
- Inside your loop, update your oscillator's volume and phase step dependent on the button pressed instead of using tone().
|
Parallel Piano |
Additive Synthesis
You will use additive synthesis to play two notes at once.
Save part 1 as a new Arduino project titled colbyUsername_P03_Part2 (with your colby username, mine would be hewolfe_P03_Part2).
Suggested Readings from Physical Audio Signal Processing
For Virtual Musical Instruments and Audio Effects by Julius O. Smith III
Detect 2 Keys Pressed
Modify your code so if two buttons are pressed at the same time, it prints the name of the note associated with both buttons. Use the equation you derived for Vout when two buttons are pressed at the same time to calculate the expected A0 readings.
Parallel Piano
Modify your code to be able to play two notes at once.
- Create a second oscillator
- Inside
ISR(TIMER1_OVF_vect) update both oscillators. Calculate the byte level of the two values added together. (The volume of the two oscillators combined should never be greater that 128).
Inside your loop, update your oscillator's volume and phase step dependent on the button or buttons pressed.
You can use your rolling average struct for smoothing the signal with a short window if needed, though if it is too long it may create a glissando between notes.
|
Extension |
Minor Extensions
Minor extensions build on the pre-existing circuit for this project.
Here are some ideas:
- Use a different audio synthesis technique.
- Use the keys to control different parameters of the audio synthesis.
- Modify the code to use timer2 to control the oscillator instead.
- Detect other combinations of key presses.
- An indepth comparison of your implementation to the Tone Library. Review the source code, how is it different both structurally and algorithmically?
Document your minor extension:
- Submit your code on filer in a folder inside your project folder.
- A 1-3 minute video documenting your extension working. (If different from project)
- A description of the extension.
- A section on implementation.
- A section on results & reflection.
- Include any relevant charts or graphs.
Major Extension
Design and implement an idea for a new project which involves adding new components and modifying the circuitry.
- It must incorporate the materials from this project; the piezo element (as an input or output device) and buttons.
- It must include atleast one of the following components: a second piezo element (your partners), the IR emitters, IR receivers, and/or the vibrating motors.
- It can include a potentiometer or red LED.
Document your major extension:
- Submit your code on filer in a folder inside your project folder.
- A 1-3 minute video of the system working.
- A description of the extension.
- A circuit diagram.
- A photograph of the circuit board.
- A section on implementation.
- A section on results & reflection.
|
Submission |
Submit your code on Filer
Submit your arduino code in a folder titled colbyUsername_P03 (with your colby username, mine would be hewolfe_P03).
Submit your report and video on Google Classroom
Upload a 1-3 min Documentation Video of your keyboard, showing that each note works and each pair of notes work. Describe what is happening.
Attach a Google Doc Report which includes:
- Photo of circuit. Clear photo of the circuit in which the wiring is clearly visible.
- Implementation Section
- Explain how you implemented the project.
- Explain what algorithms or techniques you used.
- Compare your implementation to the Tone Library. Explain the differences in implementation.
- Results & Reflection Section
- How well does your implementation work?
- How does the output of your implementation compare to the tone() library?
- Acknowledgements Section
- What resources did you use? Include websites, books and anyone you received help from or discussed the project with.
|