Sunday, July 19, 2009

The Arduino as a Sequencer

There are a number of simple projects that use the Arduino (or another ATmega168/328 platform) as a tone generator and/or sequencer. In a fit of activity one night, I realized that with a broken pair of headphones and some spare wire, I had everything I needed to make my Arduino into a fun noise generator as well.

Thus began work on the sequencer_1 code.

Sequencer_1

The first iteration of the sequencer software that I put together for my Arduino was pretty basic, re-using a lot of existing example code (such as the frequency-output function freqout). It was very simple, with all of the interesting stuff happening in the loop function:
  • check for the "record" button
  • check the status of the scale and note knobs
  • put any recorded note/scale combinations into the sequence at the current index
  • read the tempo knob
  • play the current note/scale for however long the tempo requires it to be played
  • set the speaker pin HIGH for a little while
  • then set the speaker pin LOW for a little while
  • advance the index of the sequence
It's a simple square wave emitter, taking very little code. It has a mildly annoying halt in-between steps of the sequence. This is because it has to stop the playback on the speaker pin in order to do everything else (advance the sequence, check the record button, etc). This halt manifested as audible noise, almost like a "pop." Even if you played the same tone for multiple steps in the sequence, it would be broken up by that artifact, rather than sounding like a single held note.

Sequencer_2

The second iteration of the sequencer software started to make use of the internal interrupt handlers. It attached Timer1 at a very high frequency and used it to flip the speaker pin between HIGH and LOW based on a dividing counter. The Timer2 interrupt was used at a lower frequency to service the tempo. The loop method was still used, but now it only performed the recording of the sequence. The input and output were nearly identical to the sequencer_1, except now there was no gap between the steps of the sequence-- a note could be held and would sound like a single solid tone.

Sequencer_3

Both of the previous sequencers had used a minimal number of parts, but had only been capable of outputting a square wave. I wanted to get into some analog action, so it was time to try and output a wave.

First I needed to figure out how to programmatically determine what voltage I should be attempting to produce at any given moment. I would commit an interrupt to updating the output voltage at a sampling frequency of around 10kHz. A second interrupt would act as the tempo handler. Whenever the tempo would update, it would set a global multiplier and divider pair. The divider becomes a counter which decrements each time the sampling interrupt fires. When it reaches zero, an output level is selected from a pre-defined table representing the sine wave. The index into this table is incremented by the multiplier each time the divider counter reaches zero. This has the net result of simulating a sampled wave at the appropriate frequency.

To actually convert the given calculated value into a voltage, a serial-to-parallel chip is fed the byte. When it latches, the eight-bit output is fed into a digital-to-analog converter. In my initial build, a simple R/2R ladder DAC worked out fine, but required a voltage divider on the output (to drop the signal to a normal 1.5V line-level range) as well as a passive low-pass filter to quiet down some component noise.

Code

The code so far is over at my Google Code page