Arduino Theremin/Synth Final Walkthrough
Part II: The HardwarePart III: The Software
The Software: I’ve gone into some detail about the algorithm used in a prior post, so I’ll just cover how the effects are generated. To quickly review, the audio output is generated via pulse width modulation on pin 3. Every time the main loop runs, it reads from the sensors, and transforms the sampled waveform array(s) depending on their input. Pitch control is done by controlling how quickly the timer steps through that array. So we have our sampled arrays:
char sine[] = {0,49,90,117,127,117,90,49,0,-49,-90,-117,-127,-117,-90,-49,0,49,90,117,127,117,90,49,0,-49,-90,-117,-127,-117,-90,-49};
//a simple sine wave with 32 samples
char overtones[] = {0,107,118,127,118,92,52,-8,17,46,-6,-36,-56,-67,-70,-80,0,80,70,67,56,36,6,-46,-17,8,-52,-92,-118,-127,-118,-107};
//the same wave with the overtones (and undertone) baked in
And the transformations:
/******************************* Apply volume and effects **********************************/
if (abs(periodold - period) > 25) {
decay = 255;
}
else {
decay = ((decay * analog4) / 255);
}
//if a new tone is produced, reset decay to full, otherwise increment it downward.
analog3buffer = float(analog3) / 200;
//convert sensor 3 to a floating point number in the range of pi/16 to pi/4
for (byte i = 0; i < SAMPLESIZE ; i++) {
playbufferelement = (analog2*sine[i] + (255-analog2)*overtones[i]) / 255;
/* waveform selection-- as analog2 ranges from 0-255 it changes the tone from
* a pure sine wave to a more complex wavetype. */
playbufferelement = playbufferelement*cos(analog3buffer*i);
/* Wah effect-- creates a variable comb filter. Note that as cos(0) = 1, when the input
* from the sensor is 0, the waveform is unchanged */
playbufferelement = playbufferelement * analog1 * decay / 65025;
//volume and decay.
playbuffer[i] = playbufferelement + 128;
//bias the values back to 0-255 for PWM output.
}
This is a conceptually dense section of code, so let me see if I can unpack it. There’s a for() loop that fills up the playbuffer array, which is asynchronously read from by the audio output routine. I managed to keep expensive floating point math to an absolute minimum by multiplying by a byte value, then dividing by 255. In particular,
Waveform Selection: is faded proportionally from the pure tone in the sine[] array to the fundamental+overtones in the overtones[] array. This is done by multiplying the value given in each array by analog2 (0 < analog2 < 255), or 255-analog2, then adding them together and dividing by 255 to scale it back to its original range.
“Wah” is done by multiplying the incoming (sinewave) values by a cosine function, with the frequency of the cosine function set by the analog3 input. This is my one concession on the floating point math thing, and it’s cost in terms of processor speed is considerable. The end effect is to create a variable comb filter. I think. I programmed by ear after my oscilloscope went tits up, so I don’t know with a certainty why it sounds the way it does.
Volume: is done by simply multiplying the value by analog1, which ranges from 0 to 255, and then dividing by 255. So we get a percentage of the 0-255 range.
Decay is similar to volume, but the decay variable is decremented every time the main loop runs with the same frequency. Since it’s multiplied by the new decay value and then divided by 255, it is a logarithmic rather than linear decay–which is why it sounds so natural. The volume and decay calculations are done together and then divided by 255*255 = 65025 to reduce the number of calculations.
Finally, playbufferelement, which at this point ranges over -128 to 127 is increased by 128 to scale it to 0-255 for PWM output.
The software may be downloaded here.
A Final Note: this was a skunkworks project. I pulled an all-nighter on the day before I went to Chicago, to finish the hardware, and the software was finished in Chicago. So there are a lot of hard-coded values and other hacks that reflect the particular parameters of the hardware I had. What I’m saying is, I would not build it as shown in the schematic, upload the software, and expect it to work the way you want to. But I think with a more incremental approach, there’s a lot of potential here, and I’ve barely scratched the surface.
Go to Section:
- Part I: Demonstration
- Part II: The Hardware
- Part III: The Software
- View All

