Aiming towards 16 bit.
I’ve done a video to demonstrate.
I’m still learning about what can be done with Arduinos, the main target being data acquisition for the ELFQuake project (the material in this post is all about getting stuff out). But as it involves breadboarding, I’ve got another fun target in mind – a hybrid music synth.
I’ve been reading around what other people have done with the things. For analog output, especially when considering music synthesis, there’s a problem that needs solving.
(Note I’ve been playing with an Arduino Uno – some of the other models have improved features).
The Quality Issue
Unless you’re after bitcrushed, lo-fi, glitch sounds, you need a decent sample rate and resolution. For ballpark, CD audio has a 44.1kHz sample rate, offering something under 22kHz bandwidth, see Nyquist frequency. It’s 16-bit, which means in its basic form, it has a Signal/Noise Ratio of about 96dB – though there are tricks to improve this.
A typical digital synth would use a high sample rate through a dedicated Digital-to-Analog (DAC) chip, with associated circuitry to get things into the analog domain. But as they stand though, Arduinos are very much 8 bit-based. You can get 8 bit analog signals out of a digital output pin using Pulse Wave Modulation (PWM), followed by a very simple analog filter. The easiest way is analogWrite(pin, value). But then you pretty much immediately run into the sample rate problem – I can’t remember offhand, but it’s slow. But the Arduino has 3 built-in PWM timers which can be used to get a much better rate (into the 10s of kHz, so tolerable quality should be possible).
When using the interrupts, the code starts getting obfuscated, but the principle is the same as analogWrite().
(TCCRxx refers to control registers, OC1A (Arduino pin 9) and OC1B (Arduino pin 10). For more info check the ATmega328 Datasheet.)
For the resolution, it’s possible to combine the analog from more than one PWM output. There’s some excellent material on the Open Music Labs site about this, but the basic idea is to scale the (analog) values from the PWM outputs, so eg. one is 256x the other, corresponding to the low and high 8 bits of a 16 bit signal. My basic circuit looks like this:
D9 ---- 1k --------------| D10 --- 200k --- 56k ----|---> OUT | 10n === | | GND
However, it can pretty much be guaranteed it won’t be 16 bits coming out of here.
The Mozzi synth project uses the same kind of configuration, but they, more realistically, only aim for max 14 bits (and kinda amusingly, their example of a 14 bit output actually only receives 8 bit values, left-shifted 6 bits, and their circuit uses a 499k/3.9k ratio, specified at 0.5% tolerance…). More generally, I doubt very much if the actual output using the kind of circuit above will get anywhere close to 14 bits.
However (2), my gut feeling is that by paying a little more attention to the analog side, it will be possible to get something like 14+ bits. I plan to experiment on this, using a few little tricks:
- send the digital outputs through voltage-referenced comparators, so they are as close to each other in ‘raw’ state as possible
- buffer the voltage division
- use considerably more sophisticated integration/filtering of the PWM (perhaps independently for each output)
I must admit to have been gobsmacked to have seen some folks using wavetables to generate noise. The shift register-based generators only need a handful of very low-level processor operations, they should compile down to very fast machine code. Depending where the table is stored, I wouldn’t be surprised to find out the computation is faster than the lookup. On a memory-limited device like the Arduino, I’d say it’s worth the trade-off whatever.
To be continued…
Here’s the code (I’ve left in lines that refer to the ADC, might want to use a pot to control freq or level):
oops – I forgot about all the << and >> in there, so rather than figuring out the markup escaping here, I’ve popped it on github.