Theremin 1

 

Now it’s finally time to start building a theremin, at first we will build a manual theremin, there is not going to be a training mode. One control for frequency and one control for volume.

There is not a lot of new material in this section, we are just going to combine the DDS tone generator and servo tutorial. As before, we need to use an amplifier to power the speaker, and use it to provide a ‘master-volume’ control. It is exactly the same as the amplifier we used to make the tone generators. We are just combining the tone generator and servo tutorial with a few software changes.

Here is the instrument wired up. The back of the breadboard is attached with mounting tape, the servos with wood screws, and the Arduino Due is loosely held down with wood screws.

manual_theremin

 

 

 

 

 

 

 

 

 

I have push buttons that trigger the interrupts, and the LCD wired up and ready to go, but the sketch that we will be writing in this section does not use either one. I just decided to go ahead and build the circuit because I was far enough along at this point that I knew it was going to look like but still wanted to take small steps and not combine everything together at once, so just ignore the LCD and push buttons for now, we’ll get to those in the next tutorial.

manual_theremin_3

 

 

 

 

 


 

 

 


 

Only two things are somewhat new here, the first is implementing volume control through software, and we are using two servos as input potentiometers. First lets discuss the software volume control. If you take some measurements you’ll find the DAC port on the Due can output a voltage level from about .6V to 2.8V, giving a total voltage swing of approximately 2.2 V. If we want 10 possible volume levels, we need to divide by 10. This means at a volume level of 1, our output varies from .6V to .82V, at volume = 5, it varies from .6V to 1.7V, and of course at volume = 10, it will cover the whole range of .6V to 2.8V. To implement this, we’ll make a MAX_VOL variable, and divide by it when we create our sinTable[], then multiply by the volume variable when we write the output to the DAC port. The volume level is controlled by using one the servos as an input.

// normalised to 12 bit range 0-4095
    nSineTable[nIndex] = (uint32_t)  (((1+sin(((2.0*PI)/WAVE_SAMPLES)*nIndex))*4095.0)/2) / MAX_VOL;
 // get the current sample  
  uint32_t ulOutput = volume * nSineTable[ulPhaseAccumulator>>20];

Another consideration to make is how large of a motion do we want to cover all 10 volume levels. After playing with it a little, I thought it felt best if we just move the servo through 30° or so to cover all 10 values, which is 3° per volume level. I also centered the range of movement with respect to the values passed to servo.write(), so the minimum volume will correspond to 75° and the maximum volume will correspond to 105°. It is kind of arbitrary since you can remove the little wood handle from the servo and position it however you want, but centering it allows for adjustment in both directions if you want a different range of movement.

We need to introduce 4 new variables to make this happen.

#define MAX_VOL 10
uint32_t volume = MAX_VOL; // var we'll actually use to control the volume

int vMinInput = 350;
int vMaxInput = 435;
int vMinAngle = 75;
int vMaxAngle = 105;

In the loop section, we read the servo

  // get volume from analog input, then scale it to range vMinInput to vMaxInput
  volume = min( max( analogRead(1), vMinInput ), vMaxInput);
  volume = ((volume - vMinInput) / 8);

To ensure our value does not go below the smallest input we will accept, we call the max() function with analogRead(1) and vMinInput as arguments. To ensure that we don’t exceed the largest value we want to accept, we call the min() function with the call to max() and vMaxInput as arguments. At this point we have a valid input between vMinInput and vMaxInput. You can change these settings so that it is comfortable for whatever you want to play, here is a video with 10 volume levels over 30 degrees. Notice how the volume does not change when you move the paddle outside the 30 deg range. (The audio is not great, it sounds better in real life).

when building the sineTable[], we divide by MAX_VOL

// create the individual samples for our sinewave table
void createSineTable()
{
  for(uint32_t nIndex = 0;nIndex < WAVE_SAMPLES;nIndex++)
  {
    // normalised to 12 bit range 0-4095
    nSineTable[nIndex] = (uint32_t)  (((1+sin(((2.0*PI)/WAVE_SAMPLES)*nIndex))*4095.0)/2) / MAX_VOL;
  }
}

That is it for the volume control, now we need to discuss the frequency control. This is done almost exactly like the DDS tone generator. The difference is that we will use a servo for input here. Since our A-to-D inputs will return a value from 0 to 1024, but we only have 128 possible frequencies or notes, we need to take the input value and divide by 8 (i.e. bit shift right 3 places).

// look up the phaseIncrement required to generate the note in our nMidiPhaseIncrement table
  uint32_t ulInput = analogRead(0);
  ulPhaseIncrement = nMidiPhaseIncrement[ulInput>>3];

Then we need to use that number as an index into the phaseIncrement array. The way this works is explained in the DDS tone generator section.

ulPhaseIncrement = nMidiPhaseIncrement[ulInput>>3];

Here is a video of the Theremin in action and the complete sketch to upload.

//  this code reuses code from several places, for the DDS part, look here 
//
// http://rcarduino.blogspot.com/2012/12/arduino-due-dds-part-1-sinewaves-and.html
// http://interface.khm.de/index.php/lab/experiments/arduino-dds-sinewave-generator/
// http://arduino.cc/forum/index.php?action=post;topic=130423.15;num_replies=20
//
// These are the clock frequencies available to the timers /2,/8,/32,/128
// 84Mhz/2 = 42.000 MHz
// 84Mhz/8 = 10.500 MHz
// 84Mhz/32 = 2.625 MHz
// 84Mhz/128 = 656.250 KHz
//
// 44.1Khz = CD Sample Rate
// Lets aim for as close to the CD Sample Rate as we can get -
//
// 42Mhz/44.1Khz = 952.38
// 10.5Mhz/44.1Khz = 238.09 // best fit divide by 8 = TIMER_CLOCK2 and 238 ticks per sample
// 2.625Hmz/44.1Khz = 59.5
// 656Khz/44.1Khz = 14.88 // use for servo control 13120 / 65600 = .02 ( update servos every 20 milliseconds )

// 84Mhz/44.1Khz = 1904 instructions per tick

#include <Servo.h>

#define MAX_VOL 10
uint32_t volume = MAX_VOL; // var we'll actually use to control the volume

int vMinInput = 350;
int vMaxInput = 435;
int vMinAngle = 75;
int vMaxAngle = 105;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// DDS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// the phase accumulator points to the current sample in our wavetable
uint32_t ulPhaseAccumulator = 0;
// the phase increment controls the rate at which we move through the wave table
// higher values = higher frequencies
volatile uint32_t ulPhaseIncrement = 0;   // 32 bit phase increment, see below

// full waveform = 0 to SAMPLES_PER_CYCLE
// Phase Increment for 1 Hz =(SAMPLES_PER_CYCLE_FIXEDPOINT/SAMPLE_RATE) = 1Hz
// Phase Increment for frequency F = (SAMPLES_PER_CYCLE/SAMPLE_RATE)*F
#define SAMPLE_RATE 44100.0
#define SAMPLES_PER_CYCLE 600
#define SAMPLES_PER_CYCLE_FIXEDPOINT (SAMPLES_PER_CYCLE<<20)
#define TICKS_PER_CYCLE (float)((float)SAMPLES_PER_CYCLE_FIXEDPOINT/(float)SAMPLE_RATE)

// to represent 600 we need 10 bits
// Our fixed point format will be 10P22 = 32 bits

// We have 521K flash and 96K ram to play with
// Create a table to hold the phase increments we need to generate midi note frequencies at our 44.1Khz sample rate
#define MIDI_NOTES 128
uint32_t nMidiPhaseIncrement[MIDI_NOTES];

// Create a table to hold pre computed sinewave, the table has a resolution of 600 samples
#define WAVE_SAMPLES 600
// default int is 32 bit, in most cases its best to use uint32_t but for large arrays its better to use smaller
// data types if possible, here we are storing 12 bit samples in 16 bit ints
uint16_t nSineTable[WAVE_SAMPLES];

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Song Data
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int iNote = 0; // index to indicate what note of particular song is playing
uint32_t noteDurationCtr = 0; // ctr used to control how long a given song note should play

void setup()
{
  Serial.begin(9600);

  createNoteTable(SAMPLE_RATE);
  createSineTable();

  /* turn on the timer clock in the power management controller */
  pmc_set_writeprotect(false);		 // disable write protection for pmc registers
  pmc_enable_periph_clk(ID_TC6);	 // enable peripheral clock TC6
  pmc_enable_periph_clk(ID_TC7);	 // enable peripheral clock TC7

  /* we want wavesel 01 with RC */
  TC_Configure(/* clock */TC2,/* channel */0, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK2);
  TC_SetRC(TC2, 0, 238); // sets <> 44.1 Khz interrupt rate 
  TC_Start(TC2, 0);

  // enable timer interrupts on the timer
  TC2->TC_CHANNEL[0].TC_IER=TC_IER_CPCS;   // IER = interrupt enable register
  TC2->TC_CHANNEL[0].TC_IDR=~TC_IER_CPCS;  // IDR = interrupt disable register

  /* Enable the interrupt in the nested vector interrupt controller */
  /* TC4_IRQn where 4 is the timer number * timer channels (3) + the channel number (=(1*3)+1) for timer1 channel1 */
  NVIC_EnableIRQ(TC6_IRQn);

  // this is a cheat - enable the DAC
  analogWrite(DAC1,0);
}

void loop()
{
  uint32_t ulInput = analogRead(0);
  ulPhaseIncrement = nMidiPhaseIncrement[ulInput>>3];

  // get volume from analog input, then scale it to range vMinInput to vMaxInput
  volume = min( max( analogRead(1), vMinInput ), vMaxInput);
  volume = ((volume - vMinInput) / 8);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// INTERRUPT HANDLERS
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void TC6_Handler()
{
  // We need to get the status to clear it and allow the interrupt to fire again
  TC_GetStatus(TC2, 0);

  ulPhaseAccumulator += ulPhaseIncrement;   // 32 bit phase increment, see below

  // if the phase accumulator over flows - we have been through one cycle at the current pitch,
  // now we need to reset the grains ready for our next cycle
  if(ulPhaseAccumulator > SAMPLES_PER_CYCLE_FIXEDPOINT)
  {
   // DB 02/Jan/2012 - carry the remainder of the phase accumulator
   ulPhaseAccumulator -= SAMPLES_PER_CYCLE_FIXEDPOINT;
  }

   // get the current sample  
  uint32_t ulOutput = volume * nSineTable[ulPhaseAccumulator>>20]; 

  // we cheated and user analogWrite to enable the dac, but here we want to be fast so
  // write directly 
  dacc_write_conversion_data(DACC_INTERFACE, ulOutput);
}

// create the notes we want to be abale to play, i.e. a list of frequencies
void createNoteTable(float fSampleRate)
{
  for(uint32_t unMidiNote = 0; unMidiNote < 128; unMidiNote++)
  {
    float fFrequency = ((pow(2.0,(unMidiNote-69.0)/12.0)) * 440.0);
    nMidiPhaseIncrement[unMidiNote] = fFrequency*TICKS_PER_CYCLE;
  }
}

// create the individual samples for our sinewave table
void createSineTable()
{
  for(uint32_t nIndex = 0;nIndex < WAVE_SAMPLES;nIndex++)
  {
    // normalised to 12 bit range 0-4095
    nSineTable[nIndex] = (uint32_t)  (((1+sin(((2.0*PI)/WAVE_SAMPLES)*nIndex))*4095.0)/2) / MAX_VOL;
  }
}

Intro->SOS->LCD Tutorial->Servo Tutorial->Timer Interrupts->Hacking Servos->Tone generator->DDS Tone Generator->Theremin 1->Theremin 2->RTTL Songs

 Posted by at 3:55 pm

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)


Time limit is exhausted. Please reload CAPTCHA.