One thing that is probably obvious is that the ATTINY is not really up to the task of synthesising waveforms at the required rate (ideally, I’d like to run a sample rate of over 40kHz so that something approaching full-frequency audio can be generated), so wavetable-synthesis is required. This is a pretty straightforward way of generating arbitrary waveforms, but requires them to be calculated in advance. The ATTINY85 has 8k of flash memory, so that puts a limit on the size of wavetable – further still if we want to output other waveshapes.
All of the experiments I’ve done so far have two main sources for the Arduino code:
- The main program (usually named after the project itself) containing code to perform setup and processing
- A generated part (often called
calc.ino) containing the precalculated wavetables etc.
I have used Ruby to create the generated part (it’s preinstalled on my macbook, and I use it at work so I know a bit about it). Actually, two files are generated: the source file and a header file (so that the main program has some idea about what’s in the generated part).
Experiment 1 (basic – code here) was intended to get the fundamentals in place:
- Configuring the Pulse-Width Modulation (PWM) output to the highest rate
- Sample-rate interrupt routine for outputting audio data (as PWM)
- Configuring Analogue-to-Digital Converters (ADCs)
(Something I have learned from my day-job, is that straight-line code is to be preferred over conditional code; that way the code is less prone to jitter. Especially in the output of audio, this stuff is audible and can give rise to nasty audio artefacts. Because of this, I concentrated on making the code (especially the audio-output stuff) as streamlined as possible, but this is something which is a process of continual improvement — it ain’t perfect yet!)
The first idea was to try and perform all processing inside the audio-output Interrupt Service Routine (ISR), which is what this does. Two oscillator output seemed like a reasonable goal (sine wave only). Using a control block to hold information about the current state of each oscillator meant that the timer ISR runs at twice the speed of the required sample rate, but flips between oscillators. The ISR reads the oscillator’s ADC channel, updates the phase pointer of the wavetable oscillator, outputs the sample and flips to the other oscillator.
Although this looked like it was working, I later discovered (around the third experiment – coming soon!) that I had misconfigured the PWM (it wasn’t running at the correct speed), and that the ADC was fundamentally not fast enough (the code actually stops the conversion short, so I wasn’t getting the full 10-bit resolution). Still, I had fun with audio-rate FM synthesis (routing the output of one oscillator to the frequency control of the other), and I was at least making some semi-interesting noises…
Here’s what the FM output looks like (it turned out the apparent AM is due to badly-chosen output filter!):
The benefit of doing this stuff in software is that, from an electronics point of view, the schematic is very simple:
Ok for a start, but I wanted MORE!