Open.Theremin V3 Software

Description of the software to run the Open.Theremin

To download go to the Download section.

The following description of the code was written and provided by community member Andrew Mackenzie. He has independently succeeded what I was never able to, to describe the software simply and clearly for everyone. Thanks a lot.


The system consists of a continuously running timer (16MHz) that is used to measure the period of the pitch and volume inputs, 2 external interrupts and an ICP (input capture pin).

One external interrupt (INT1) is fed by a 31250Hz clock (derived by dividing the 8MHz pitch clock by 256); this forms the basis of the timing system used for button debouncing and automatic calibration. The second interrupt (INT0) is used for the volume logic, whilst the ICP is used for the pitch logic.

Upon start up of the system, automatic calibration of the volume and pitch circuits takes place and calibration values are stored.


The volume circuitry generates pulses, which trigger INT0 on a rising edge. When a pulse is detected, the interrupt routine stores the current Timer 1 value and sets a flag. The main program loop detects the flag and then calculates the period between the current pulse and the previous pulse.

The period is then averaged and filtered with the previous period. The averaged value is then adjusted using the volume calibration value and lastly scaled between 0 and 255.

Lastly, the flag is cleared.


The pitch circuitry generates pulses, which trigger ICP1 to set the capture flag (ICF1). The main loop checks ICF1, and when activated, stores the current value of Timer 1. This value is used to calculate the period between the current pulse and the previous pulse.

The pitch's period is then averaged and filtered with the previous period, before being adjusted by the pitch calibration value and scaled.

Lastly the ICF1 flag is cleared, ready for the next pulse.


The 31250Hz clock applied to INT1, triggers an interrupt every 32us. When this interrupt is triggered, INT1 is masked off from interrupting again. The AVR hardware automatically disables global interrupts when the ISR (interrupt sub routine) fires, however the code requires that interrupts continue to be serviced, so global interrupts are manually re-enabled.

A value from the wave table is retrieved (the value retrieved is based on an offset from the previous execution of this routine). This value is then multiplied by the scaled volume value (calculated in the main routine); this has the result of changing the amplitude, and hence, adjusts the volume of the waveform.

The adjusted value is then converted to a 12-bit value and an offset is applied; this is done because the DAC is 12-bit and only operates on the positive rail.

The 12-bit value is sent to the DAC for output. The DAC's output signal passes through a filter, a buffer and then an AC coupling capacitor (C17) to remove the offset.

The scaled pitch value calculated in the main loop is then used to determine the offset for the next time INT1 is triggered. A larger scaled pitch value will give a bigger offset, thus effectively fast-forwarding the waveform and hence decreasing the waveform's frequency resulting in a higher pitch. A smaller scaled pitch value will give a smaller offset, thus effectively slowing down the waveform, resulting in a lower frequency and hence a lower pitch.

Lastly, the timing system is incremented by 1 tick before once again disabling global interrupts and unmasking the INT1 interrupt.

The AVR automatically re-enables global interrupts before resuming where it left off.