Showing posts with label Arduino Due. Show all posts
Showing posts with label Arduino Due. Show all posts

Thursday, April 25, 2013

Reading RC Channels with Arduino Due

Update 27/04/2013

The following code was originally presented as a test sketch to demonstrate a glitch in the Arduino Due micros function which is being resolved. See the following post for details of the glitch and resolution.

http://arduino.cc/forum/index.php/topic,162787.0/topicseen.html

With this resolution in place the code presented below can be used to read 8 RC Channels and output them to a combination of upto 8 RC Servos or ESCs.

The code can be operated in two configurations -

1) Loop Back Test - Here 8 servo outputs are created and given fixed values from 1100 to 1800, these pulse values are output on pins 10 to 17 and can be read back in through the interrupts attached to pins 2 to 9. This is intended to give the user confidence that the code is able to read multiple RC Channels before moving to configuration 2 - Pass Through

2) Pass Through -  This is similar to 1) above however the input is now connected to the output such that a change in an incoming signal will cause a corresponding change in the servo output signal. To implement pass  through on any channel, simply remove the comment form the start of the servo.writeMicrosecond command for that channel for example

Change this -

  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel1.writeMicroseconds(unChannel1In);
      Serial.print(unChannel1In);
      Serial.print(",");
  }
to this -

  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      servoChannel1.writeMicroseconds(unChannel1In);
      Serial.print(unChannel1In);
      Serial.print(",");
  }




The code has been ported from an original project based on the Arduino UNO, follow the links in the comments for the background and detailed explanation.

// MultiChannels
//
// rcarduino.blogspot.com
//
// A simple approach for reading and writing eight RC Channels using Arduino Due interrupts
//
// See related posts -
// http://rcarduino.blogspot.co.uk/2012/01/how-to-read-rc-receiver-with.html
// http://rcarduino.blogspot.co.uk/2012/03/need-more-interrupts-to-read-more.html
// http://rcarduino.blogspot.co.uk/2012/01/can-i-control-more-than-x-servos-with.html
//
// rcarduino.blogspot.com
//

#include "Servo.h"

// Assign your channel in pins
#define CHANNEL1_IN_PIN 2
#define CHANNEL2_IN_PIN 3
#define CHANNEL3_IN_PIN 4
#define CHANNEL4_IN_PIN 5
#define CHANNEL5_IN_PIN 6
#define CHANNEL6_IN_PIN 7
#define CHANNEL7_IN_PIN 8
#define CHANNEL8_IN_PIN 9

// Assign your channel out pins
#define CHANNEL1_OUT_PIN 10
#define CHANNEL2_OUT_PIN 11
#define CHANNEL3_OUT_PIN 12
#define CHANNEL4_OUT_PIN 13
#define CHANNEL5_OUT_PIN 14
#define CHANNEL6_OUT_PIN 15
#define CHANNEL7_OUT_PIN 16
#define CHANNEL8_OUT_PIN 17

// Servo objects generate the signals expected by Electronic Speed Controllers and Servos
// We will use the objects to output the signals we read in
// this example code provides a straight pass through of the signal with no custom processing
Servo servoChannel1;
Servo servoChannel2;
Servo servoChannel3;
Servo servoChannel4;
Servo servoChannel5;
Servo servoChannel6;
Servo servoChannel7;
Servo servoChannel8;

// These bit flags are set in bUpdateFlagsShared to indicate which
// channels have new signals
#define CHANNEL1_FLAG 1
#define CHANNEL2_FLAG 2
#define CHANNEL3_FLAG 4
#define CHANNEL4_FLAG 8
#define CHANNEL5_FLAG 16
#define CHANNEL6_FLAG 32
#define CHANNEL7_FLAG 64
#define CHANNEL8_FLAG 128

// holds the update flags defined above
volatile uint32_t bUpdateFlagsShared;

// shared variables are updated by the ISR and read by loop.
// In loop we immediatley take local copies so that the ISR can keep ownership of the
// shared ones. To access these in loop
// we first turn interrupts off with noInterrupts
// we take a copy to use in loop and the turn interrupts back on
// as quickly as possible, this ensures that we are always able to receive new signals
volatile uint32_t unChannel1InShared;
volatile uint32_t unChannel2InShared;
volatile uint32_t unChannel3InShared;
volatile uint32_t unChannel4InShared;
volatile uint32_t unChannel5InShared;
volatile uint32_t unChannel6InShared;
volatile uint32_t unChannel7InShared;
volatile uint32_t unChannel8InShared;

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

  Serial.println("multiChannels");

  // attach servo objects, these will generate the correct
  // pulses for driving Electronic speed controllers, servos or other devices
  // designed to interface directly with RC Receivers
  servoChannel1.attach(CHANNEL1_OUT_PIN);
  servoChannel2.attach(CHANNEL2_OUT_PIN);
  servoChannel3.attach(CHANNEL3_OUT_PIN);
  servoChannel4.attach(CHANNEL4_OUT_PIN);
  servoChannel5.attach(CHANNEL5_OUT_PIN);
  servoChannel6.attach(CHANNEL6_OUT_PIN);
  servoChannel7.attach(CHANNEL7_OUT_PIN);
  servoChannel8.attach(CHANNEL8_OUT_PIN);

  // attach the interrupts used to read the channels
  attachInterrupt(CHANNEL1_IN_PIN, calcChannel1,CHANGE);
  attachInterrupt(CHANNEL2_IN_PIN, calcChannel2,CHANGE);
  attachInterrupt(CHANNEL3_IN_PIN, calcChannel3,CHANGE);
  attachInterrupt(CHANNEL4_IN_PIN, calcChannel4,CHANGE);
  attachInterrupt(CHANNEL5_IN_PIN, calcChannel5,CHANGE);
  attachInterrupt(CHANNEL6_IN_PIN, calcChannel6,CHANGE);
  attachInterrupt(CHANNEL7_IN_PIN, calcChannel7,CHANGE);
  attachInterrupt(CHANNEL8_IN_PIN, calcChannel8,CHANGE);

  // for loop back test only, lets set each channel to a known value
  servoChannel1.writeMicroseconds(1100);
  servoChannel2.writeMicroseconds(1200);
  servoChannel3.writeMicroseconds(1300);
  servoChannel4.writeMicroseconds(1400);
  servoChannel5.writeMicroseconds(1500);
  servoChannel6.writeMicroseconds(1600);
  servoChannel7.writeMicroseconds(1700);
  servoChannel8.writeMicroseconds(1800);
}

void loop()
{
  // create local variables to hold a local copies of the channel inputs
  // these are declared static so that thier values will be retained
  // between calls to loop.
  static uint32_t unChannel1In;
  static uint32_t unChannel2In;
  static uint32_t unChannel3In;
  static uint32_t unChannel4In;
  static uint32_t unChannel5In;
  static uint32_t unChannel6In;
  static uint32_t unChannel7In;
  static uint32_t unChannel8In;
 
  // local copy of update flags
  static uint32_t bUpdateFlags;

  // check shared update flags to see if any channels have a new signal
  if(bUpdateFlagsShared)
  {
    noInterrupts(); // turn interrupts off quickly while we take local copies of the shared variables

    // take a local copy of which channels were updated in case we need to use this in the rest of loop
    bUpdateFlags = bUpdateFlagsShared;
  
    // in the current code, the shared values are always populated
    // so we could copy them without testing the flags
    // however in the future this could change, so lets
    // only copy when the flags tell us we can.
  
    if(bUpdateFlags & CHANNEL1_FLAG)
    {
      unChannel1In = unChannel1InShared;
    }
  
    if(bUpdateFlags & CHANNEL2_FLAG)
    {
      unChannel2In = unChannel2InShared;
    }
  
    if(bUpdateFlags & CHANNEL3_FLAG)
    {
      unChannel3In = unChannel3InShared;
    }
   
    if(bUpdateFlags & CHANNEL4_FLAG)
    {
      unChannel4In = unChannel4InShared;
    }
  
    if(bUpdateFlags & CHANNEL5_FLAG)
    {
      unChannel5In = unChannel5InShared;
    }
  
    if(bUpdateFlags & CHANNEL6_FLAG)
    {
      unChannel6In = unChannel6InShared;
    }
   
    if(bUpdateFlags & CHANNEL7_FLAG)
    {
      unChannel7In = unChannel7InShared;
    }
  
    if(bUpdateFlags & CHANNEL8_FLAG)
    {
      unChannel8In = unChannel8InShared;
    }
    // clear shared copy of updated flags as we have already taken the updates
    // we still have a local copy if we need to use it in bUpdateFlags
    bUpdateFlagsShared = 0;
  
    interrupts(); // we have local copies of the inputs, so now we can turn interrupts back on
    // as soon as interrupts are back on, we can no longer use the shared copies, the interrupt
    // service routines own these and could update them at any time. During the update, the
    // shared copies may contain junk. Luckily we have our local copies to work with :-)
  }
 
  // do any processing from here onwards
  // only use the local values unChannel1, unChannel2, unChannel3, unChannel4, unChannel5, unChannel6, unChannel7, unChannel8
  // variables unChannel1InShared, unChannel2InShared, etc are always owned by the
  // the interrupt routines and should not be used in loop
 
  if(bUpdateFlags & CHANNEL1_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel1.writeMicroseconds(unChannel1In);
      Serial.println();
      Serial.print(unChannel1In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL2_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel2.writeMicroseconds(unChannel2In);
      Serial.print(unChannel2In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL3_FLAG)
  {
      // remove the // from the line below to implement pass through updates to the servo on this channel -
      //servoChannel3.writeMicroseconds(unChannel3In);
      Serial.print(unChannel3In);
      Serial.print(",");
  }

  if(bUpdateFlags & CHANNEL4_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel4.writeMicroseconds(unChannel4In);
    Serial.print(unChannel4In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL5_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel5.writeMicroseconds(unChannel5In);
    Serial.print(unChannel5In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL6_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel6.writeMicroseconds(unChannel6In);
    Serial.print(unChannel6In);
  }
 
  if(bUpdateFlags & CHANNEL7_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel7.writeMicroseconds(unChannel7In);
    Serial.print(unChannel7In);
    Serial.print(",");
  }
 
  if(bUpdateFlags & CHANNEL8_FLAG)
  {
    // remove the // from the line below to implement pass through updates to the servo on this channel -
    // servoChannel8.writeMicroseconds(unChannel8In);
    Serial.print(unChannel8In);
  }
 
  bUpdateFlags = 0;
}

void calcChannel1()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL1_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel1InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL1_FLAG;
  }
}

void calcChannel2()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL2_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel2InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL2_FLAG;
  }
}

void calcChannel3()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL3_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel3InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL3_FLAG;
  }
}

void calcChannel4()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL4_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel4InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL4_FLAG;
  }
}

void calcChannel5()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL5_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel5InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL5_FLAG;
  }
}

void calcChannel6()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL6_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel6InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL6_FLAG;
  }
}

void calcChannel7()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL7_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel7InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL7_FLAG;
  }
}

void calcChannel8()
{
  static uint32_t ulStart;
 
  if(digitalRead(CHANNEL8_IN_PIN))
  {
    ulStart = micros();
  }
  else
  {
    unChannel8InShared = (uint32_t)(micros() - ulStart);
    bUpdateFlagsShared |= CHANNEL8_FLAG;
  }
}


Saturday, December 1, 2012

Arduino Due DDS - Part 1 - Sinewaves and Fixed Point Maths

This post provides a quick introduction to Direct Digital Synthesis (DDS) on the Arduino Due. The sample sketch outputs a sinewave at a 44.1Khz sample rate with 12 bit resolution using the built in Digital To Analog Converter (DAC).

A related sketch which provides more interesting audio output without the explanation can be found here -
http://rcarduino.blogspot.com/2012/11/quick-and-dirty-synth-for-arduino-due.html

All of the Audio projects on RCArduino are based on this same DDS technique described below, see the following links for DDS Projects -
http://rcarduino.blogspot.com/2012/10/five-dollar-synthesiser.html
http://rcarduino.blogspot.com/2012/08/the-must-build-arduino-project-illutron.html

http://rcarduino.blogspot.com/2012/11/auduino-with-delay.html
http://rcarduino.blogspot.com/2012/08/adding-audio-to-arduino-projects.html
http://rcarduino.blogspot.com/2012/10/arduino-modular-synthesizer-part-one.html

Direct Digital Synthesis - Introduction
DDS is a common approach to generating sinewave output from digital systems. The approach is based around outputting samples from a description of the required waveform held in memory. The description is an array of samples which can be traversed to plot the waveform onscreen or by using DDS can generate a sinewave output at a selected frequency.

The 600 sample 12 Bit sine wave table used in the sketch below plotted on screen using processing -

The 128 sample 8 Bit wave tables used in the Illutron B synth -


Direct Digital Synthesis - Sample Rate
To generate an output waveform we need a way to regularly update the output value, we do this by using a timer interrupt. The frequency of this interrupt is termed the sample rate, in our case we are using a 44.1Khz sample rate meaning that we have an interrupt triggering 44,100 times per second.

As this interrupt is triggering at such a high rate it is very important that we keep it efficient, this is the reason for using a pre computed wavetable rather than computing the sine samples on the fly.

Direct Digital Synthesis - Frequency Generation
We can generate different frequencies by changing the rate at which we traverse through the wave table. One pass through the wavetable provides one complete cycle of the selected waveform (sine in our case). If we pass through the table once in 44,100 interrupts we generate a frequency of 1Hz, if we pass through 100 times faster, we get a sinewave output at a frequency of 100Hz.

Direct Digital Synthesis - Implementation and Terminology
The following terms are commonly used in reference to DDS and variations are used throughout the RC Arduino audio projects.

1) Phase Accumulator - this is an array index which points to the current sample in the selected wavetable.
2) Phase Increment - this value is used to advance the Phase Accumulator each time the interrupt is called.

Example usage of the phase accumulator used inside the timer interrupt to select the next sample in the wave table array -

// Update the phase accumulator by adding the phase increment
ulPhaseAccumulator += ulPhaseIncrement;

// get the current sample from the sine table using the phase accumulator as the index  
uint32_t ulOutput = nSineTable[ulPhaseAccumulator>>20];

Direct Digital Synthesis - Fixed Point Maths
To generate a full range of frequencies we will often need to use fractional values for our phase accumulator.


The usual method of working with fractional values is to use the float data type however in DDS we need our interrupts to run as fast as possible. With very few exceptions microcontrollers are not able to process the float data type at high speed, the solution is to use fixed point integer maths. 

Fixed Points For Higher Integer Precision
There are many applications of fixed point arithmetic but in DDS the main use is to provide increased precision - equivalent to having many decimal points while still using the high speed integer (whole number) data type.

To get extra precision we use more bits than we need to represent our data range. For example the sample code below has a wave table size of 600 samples so the range of we need for our phase accumulator is 0 to 599.

We can represent this range with 10 bits but if we use 16 bits we have 6 additional bits of precession.


16 Bit Integer














Bit  15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Value 32768 16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1

















Fixed Point 10.6 using 16 Bit Integer












Bit  15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0
Value 1024 512 256 128 64 32 16 8 4 2 1 0.5 0.25 0.125 0.0625 0.0313

We use this additional precision to perform fractional addition to the phase accumulator, the 10.6 format gives 1/32 precision as the least significant bit.

Fixed point addition - we know its fixed point, the compiler doesn't

// Update the phase accumulator by adding the phase increment
ulPhaseAccumulator += ulPhaseIncrement;  

This line of code is actually adding two fixed point numbers, part of the trick is that the compiler does not know this, it sees them simply as two integers which it processes using very fast hardware integer addition.

When we want to use the phase accumulator as the array index, we perform an arithmetic shift six places to the right, this drops off the six bits of additional precession and leaves us with a 10 bit integer value for the array index.

// drop the six bits of additional precision and access the remaining 10 bits as an integer array index in the range 0 - 599

uint16_t unOutputValue = sineTable[ulPhaseAccumulator>>6];

The only trick to fixed point maths is to figure out a scheme which will work for your project and stick to it. Remember the compiler does not know that you are treating part of the number as fractions so the responsibility is on you the programmer to keep consistency.

Fixed Point In The RCArduino DDS Example Sketch
The standard data type for the 32 bit Arduino Due is a 32 bit integer, this gives us scope for very high levels of precession in integer maths. The scheme used for the phase increment and phase accumulator variables in the sample sketch is 12.20 (12+20 = 32 Bits) this can be seen in the following line of code where the precision is shifted out to access the pure 12 bit integer value -

uint32_t ulOutput = nSineTable[ulPhaseAccumulator>>20];

Where ever you see code that makes extensive use of '>>' operations you might be looking at fixed point arithmetic, in a follow up post we will look at how we perform fast fixed point multiplication. This is another key to fast and flexible synthesizer engines.

Fixed Point Multiplication is used in Audio synthesis to apply envelopes and amplitude modulation.

Envelope Applied to a DDS sine wave using Fixed Point Maths in the Illutron B Project -

DDS Sinewave for Arduino Due - The Sketch
Use a potentiometer connected to analog pin 0 to control the pitch. To hear the output you can use an amplifier circuit such as this one used throughout RCArduino - 

http://rcarduino.blogspot.com/2012/08/adding-audio-to-arduino-projects.html

The sinewave is output through DAC0 - Note that the DAC does not provide a full 0-3.3volt swing, this is a hardware limitation of the SAM3X8E chip.


Caution : The SAM3X8E microcontroller at the heart of the Arduino Due is less able to sink and source current than the AVR family of chips used in the 8-Bit Arduinos. See the update below for the most recent recommendations - 

Update 09/06/2013 - Gaétan Ro has taken things much further in the latest build of his Groovuino project. The Project uses the technique described here combined with some sensors, a sequencer and filter to produce a really nice sounding groovebox. I plan to build one of these for myself and hope to be able to help Gaétan with its further development.

http://www.youtube.com/watch?v=EqI2hEVbMPI

Gaéta's blog -  http://groovuino.blogspot.com/

Update 28/03/2013 : There is little information on the SAM3X8E DAC in the datasheet however Arduino forum used 'stimmer' has found recommendations on a closely related chip which suggest that a 2K Ohm resistor should be placed as a series current limiting resistor between the DAC Output and the load. This assumes that the load has no resistance of its own which will be the case if you accidentally short something, if you know the resistance of your load, you can reduce this value, if not, its a reasonable starting point and will protect against accidents. For more information see the original thread here - http://arduino.cc/forum/index.php/topic,139733.15.html

Update 28/03/2013 : Gaétan Ro has been building on the techniques here to produce an Arduino Due based groove box he is calling the 'Groovuino'. You can hear a clip of the project in action here -

https://soundcloud.com/gaetino/groovuino

And follow the future development on the blog Gaétan has started here -

http://groovuino.blogspot.ae/

// RCArduino DDS Sinewave for Arduino Due
// RCArduino DDS Sinewave by RCArduino is licensed under a Creative Commons Attribution 3.0 Unported License.
// Based on a work at rcarduino.blogspot.com.

// For helpful background information on Arduino Due Timer Configuration, refer to the following link
// thanks to Sebastian Vik
// http://arduino.cc/forum/index.php?action=post;topic=130423.15;num_replies=20

// For background information on the DDS Technique see
// http://interface.khm.de/index.php/lab/experiments/arduino-dds-sinewave-generator/

// For audio sketches making extensive use of DDS Techniques, search the RCArduino Blog
// for the tags Audio or synth

// 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

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

// 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];

// fill the note table with the phase increment values we require to generate the note
void createNoteTable(float fSampleRate)
{
  for(uint32_t unMidiNote = 0;unMidiNote < MIDI_NOTES;unMidiNote++)
  {
    // Correct calculation for frequency
    Serial.print(unMidiNote);
    Serial.print(" ");
    float fFrequency = ((pow(2.0,(unMidiNote-69.0)/12.0)) * 440.0);
    Serial.print(fFrequency);
    Serial.print(" ");
   
    nMidiPhaseIncrement[unMidiNote] = fFrequency*TICKS_PER_CYCLE;
    Serial.println(nMidiPhaseIncrement[unMidiNote]);
  }
}

// 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];

// 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] = (uint16_t)  (((1+sin(((2.0*PI)/WAVE_SAMPLES)*nIndex))*4095.0)/2);
    Serial.println(nSineTable[nIndex]);
  }
}

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

  createNoteTable(SAMPLE_RATE);
  createSineTable();
 
  /* turn on the timer clock in the power management controller */
  pmc_set_writeprotect(false);
  pmc_enable_periph_clk(ID_TC4);

  /* we want wavesel 01 with RC */
  TC_Configure(/* clock */TC1,/* channel */1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK2);
  TC_SetRC(TC1, 1, 238); // sets <> 44.1 Khz interrupt rate
  TC_Start(TC1, 1);
 
  // enable timer interrupts on the timer
  TC1->TC_CHANNEL[1].TC_IER=TC_IER_CPCS;
  TC1->TC_CHANNEL[1].TC_IDR=~TC_IER_CPCS;
 
  /* 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(TC4_IRQn);

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

void loop()
{
  // read analog input 0 drop the range from 0-1024 to 0-127 with a right shift 3 places,
  // then look up the phaseIncrement required to generate the note in our nMidiPhaseIncrement table
  uint32_t ulInput = analogRead(0);
  ulPhaseIncrement = nMidiPhaseIncrement[ulInput>>3];
}

void TC4_Handler()
{
  // We need to get the status to clear it and allow the interrupt to fire again
  TC_GetStatus(TC1, 1);
 
  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 = 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);
}

Friday, November 30, 2012

Quick And Dirty Synth For Arduino Due

The first RCArduino project for the Arduino Due is the Quick And Dirty Synth. The synth is a simple showcase for audio output through the DAC running at the Audio CD sample rate of 44.1Khz.


Creative Commons License
RCArduino Quick And Dirty Synth by RCArduino is licensed under a
Creative Commons Attribution 3.0 Unported License.
Based on a work at rcarduino.blogspot.com.


The showcase is based on the simplest synth engine I could create - 3 counters counting up at a rate controlled by three analog inputs.

It might sound simple but its a surprisingly rich sounding synth engine, here is how it works - 

Two of the counters generate ramp waveforms - think about it, they are counting up from 0 to 4095, when they overflow, they go back to 0 and start the count again.


Example Ramp Output -  




Example Ramp At A Higher Frequency -



These ramp waveforms are summed together at the output to generate a more complex waveform - two ramp waves of independent frequency superimposed on each other.

Example - Two triangle at frequency F and 3F mixed together to create a new output waveform.

The third ramp waveform is used to control the pitch. It is not mixed with the output waveforms instead it achieves pitch control by resetting the first two waveforms.


Output waveform reset at frequency determined by third counter - notice that the counter is not directly present in the output but controls the repetition/synchronization of the output which in turn creates the pitch. 


To hear the technique in action connect three potentiometers to your Arduino Due on analog inputs 0,1,2 and an audio amplifier to DAC0.

All of the Audio projects on RCArduino use the following simple amplifier circuit -
http://rcarduino.blogspot.com/2012/08/adding-audio-to-arduino-projects.html

To learn more about the synthesis technique used in the quick and dirty synthesizer see the following link -

http://rcarduino.blogspot.com/2012/08/adding-audio-to-arduino-projects.html

The code 

Caution : The SAM3X8E microcontroller at the heart of the Arduino Due is less able to sink and source current than the AVR family of chips used in the 8-Bit Arduinos. I would suggest using a series resistor of around 500 Ohms when connecting the Arduino Due DAC to an external device. See this thread on the Arduino forum for more information - http://arduino.cc/forum/index.php/topic,139733.0.html
 
// RCArduino Quick And Dirty Synth for Arduino Due// RCArduino Quick And Dirty Synth by RCArduino is licensed under a Creative Commons Attribution 3.0 Unported License.
// Based on a work at rcarduino.blogspot.com.

// For helpful background information on Arduino Due Timer Configuration, refer to the following link
// thanks to Sebastian Vik
// http://arduino.cc/forum/index.php?action=post;topic=130423.15;num_replies=20

// The following folders within the arduino install provide access to source code and documentation
// its very low level, the documentation is next to useless, but it might help someone
// C:\arduino-1.5.1r2\hardware\arduino\sam\system\libsam\source
// C:/arduino-1.5.1r2/hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/html/tc1.html
// C:/arduino-1.5.1r2/hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/html/SAM3XA.html
// C:\arduino-1.5.1r2\hardware\arduino\sam\system\CMSIS\CMSIS\Documentation

// 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

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

// These variables represent our synth engine, its similar to a vastly simplified Auduino and has a similar sound.
// Phase Accumulator controls the main pitch, grain1 and grain two phase accumulator control the pitch for the two grains
// The grainPhaseAccumulators are essentially simple counters, when you count up and overflow back to 0, you generate a ramp waveform
// we generate two and mix them together.
uint32_t ulPhaseAccumulator = 0; // 32 bit phase accumulator, if we shift >> 20 bits we get a 12 bit value for our output ADC
volatile uint32_t ulPhaseIncrement = 0;   // 32 bit phase increment, see below
uint32_t ulGrain1PhaseAccumulator = 0; // 32 bit phase accumulator, if we shift >> 20 bits we get a 12 bit value for our output ADC
volatile uint32_t ulGrain1PhaseIncrement = 0;   // 32 bit phase increment, see below
uint32_t ulGrain2PhaseAccumulator = 0; // 32 bit phase accumulator, if we shift >> 20 bits we get a 12 bit value for our output ADC
volatile uint32_t ulGrain2PhaseIncrement = 0;   // 32 bit phase increment, see below

// full waveform = 0 to 4294967296
// Phase Increment for 1 Hz =(4294967296/44100) = 1Hz
// Phase Increment for frequency F = (4294967296/44100)*F
#define SAMPLE_RATE 44100.0
#define TICKS_PER_CYCLE (4294967296.0/SAMPLE_RATE)

// 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];

void createNoteTable(float fSampleRate)
{
  for(uint32_t unMidiNote = 0;unMidiNote < MIDI_NOTES;unMidiNote++)
  {
    // Correct calculation for frequency
    Serial.print(unMidiNote);
    Serial.print(" ");
    float fFrequency = ((pow(2.0,(unMidiNote-69.0)/12.0)) * 440.0);
    Serial.print(fFrequency);
    Serial.print(" ");
    nMidiPhaseIncrement[unMidiNote] = fFrequency*TICKS_PER_CYCLE;
    Serial.println(nMidiPhaseIncrement[unMidiNote]);
  }
}

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

  createNoteTable(SAMPLE_RATE);
 
  /* turn on the timer clock in the power management controller */
  pmc_set_writeprotect(false);
  pmc_enable_periph_clk(ID_TC4);

  /* we want wavesel 01 with RC */
  TC_Configure(/* clock */TC1,/* channel */1, TC_CMR_WAVE | TC_CMR_WAVSEL_UP_RC | TC_CMR_TCCLKS_TIMER_CLOCK2);
  TC_SetRC(TC1, 1, 238); // sets <> 44.1 Khz interrupt rate
  TC_Start(TC1, 1);
 
  // enable timer interrupts on the timer
  TC1->TC_CHANNEL[1].TC_IER=TC_IER_CPCS;
  TC1->TC_CHANNEL[1].TC_IDR=~TC_IER_CPCS;
 
  /* 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(TC4_IRQn);

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

void loop()
{
  // read analog input 0 drop the range from 0-1024 to 0-127 with a right shift 3 places,
  // then look up the phaseIncrement required to generate the note in our nMidiPhaseIncrement table
  uint32_t ulInput = analogRead(0);
  ulPhaseIncrement = nMidiPhaseIncrement[ulInput>>3];
 
  // set the phase increment for grains 1 and 2, we do not want a precise frequency for these,
  // they set the character of the note rather than pitch
  ulGrain1PhaseIncrement = analogRead(1)<<18;
  ulGrain2PhaseIncrement = analogRead(2)<<18;
}

void TC4_Handler()
{
  // We need to get the status to clear it and allow the interrupt to fire again
  TC_GetStatus(TC1, 1);
 
  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 < ulPhaseIncrement)
  {
   ulGrain1PhaseAccumulator = 0;
   ulGrain2PhaseAccumulator = 0;
  }
  else
  {
   // increment the grains
   ulGrain1PhaseAccumulator += ulGrain1PhaseIncrement;
   ulGrain2PhaseAccumulator += ulGrain2PhaseIncrement;
  }
 
  // mix the grains by adding them together - to the result of adding these two 32bit values into the 12 bit ADC Output
  // we shift both values right 21 places to sum two 11 bit values giving a 12 bit result.
  uint32_t ulOutput = (ulGrain1PhaseAccumulator>>21)+(ulGrain2PhaseAccumulator>>21);
 
  // 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);
}

Wednesday, October 10, 2012

New Arduino Products and Video Tutorials

UK Based electronics supplier RS-Online have added a new Arduino section to the home page of thier site.

Clicking on the links will take you to this section with an overview of the new starter kit, some video demonstrations and datasheets.

These are the 15 projects included in the new official Arduino starter kit -

01 GET TO KNOW YOUR OWN TOOL an introduction to the concepts you'll need to use this kit
02 SPACESHIP INTERFACE design to control panel for your starship
03 LOVE-O-METER measure how hot-blooded you are
04 COLOR MIXING LAMP produce any color with a lamp that uses light as an input
05 MOOD CUE clue people in to how you're doing
06 LIGHT THERIM create a musical instrument you play by waving your hands
07 KEYBOARD INSTRUMENT play music and make some noise with this keyboard
08 DIGITAL HOURGLASS a light-up hourglass that can stop you from working too much
09 MOTORIZED PINWHEEL a color wheel that will have your head spinning
10 ZOETROPE create a mechanical animation you can play forward or reverse
11 CRYSTAL BALL a mystical tour to answer all your tough question
12 KNOCK LOCK tap out the secret code to open the door
13 TOUCHY-FEEL LAMP a lamp that responds to your touch
14 TWEAK THE ARDUINO LOGO control your personal computer from your Arduino
15 HACKING BUTTONS create a master control for all your devices!

More information including the video tutorials can be found here -

http://uk.rs-online.com/web/generalDisplay.html?id=arduino

Arduino Due

While the site does not mention the upcoming Arduino Due, there is a 'stay tuned for Arduino' email notification which you can subscribe to in the link above. If RS have any marketting sense I am sure they will use this mailing list to announce the availability of Due or pre orders.

So if you are UK based Stay Tuned.

UPDATE: The Arduino team have started a new thread on the Arduino forum for the newly released started kit - http://arduino.cc/forum/index.php/topic,125444.0/topicseen.html


Duane B

Sunday, October 7, 2012

Arduino Modular Synthesizer - Part One

The RC Arduino site has featured a number of Audio projects recently, and while they all produce very different sounds they all use the same basic technique to generate their sound.

With this in mind an obvious development of these projects would be a modular synthesizer which allows the user to easily combine the techniques used by individual projects to produce new instruments.

Modular Synthesizer
A modular synthesizer is traditionally a large analogue device often including a mix and match of vintage components. Early models were often enormous DIY machines that could easily take up a whole wall of a room.

Current models are still large in comparison to digital synthesizers this is partly due to the physical user interface which is also what gives these machines their character.

A typical modular synthesizer user interface - Analog inputs to control the module oscillators and a patchwork of wires to direct the signals between different modules.
Original photo by Nina Richards


Arduino as a Digital Modular Synth ?

While the Arduino is a digital device, the Auduino and Illutron B projects both demonstrate the appeal of using analog inputs to control an Arduino based digital synthesizer.

Auduino Illutron B

Auduino Features - 
A grain synthesizer which uses two triangle waveforms with independently controlled frequency and decay to create a more complex waveform. A fifth control is then used to set the pitch by controlling the repetition rate of the complex waveform.

The tone of the waveform can be dramatically altered by adjusting the frequencies of the two component waveforms.


Illutron B Features
A wavetable synthesizer which uses pre calculated waveforms stored within the program memory. These waveforms are played back at different speeds to produce sound at the required pitch. To further increase the musical quality of the waveforms, they are combined with an envelope, this describes how the sound developes over time and can provide an approximation of many musical instruments.




More information and examples of both of these projects can be found here -

http://rcarduino.blogspot.com/2012/08/the-must-build-arduino-project-illutron.html
http://rcarduino.blogspot.com/2012/08/adding-audio-to-arduino-projects.html

Taking it further
 
The goal of this series of posts is to extend these ideas by -

1) Building a set of software components that will provide digital versions of the most common modules found in a modular synthesizer.


2) Building a simple, low cost simulated patch panel to direct how the digital sound will be built and processed by the software running on the Arduino.

Progress so far

Below are two examples of vastly different Arduino synthesisers built for this project with the same four oscillators connected in different ways - in effect this is a demonstration of the concept of an Arduino modular synthesiser.

XLT2 - eXtremely Limited Techno Toy


 
The eXtremeley Limited Techno Toy or XLT2

As an initial experiment I wanted to build something that could be built from a few common components and that would be able to generate a reasonable range of new sounds.

The first result is the XLT2 - eXtemeley Limited Techno Toy.

An added bonus is that this synth and the Gated Grain Synth below are both directly compatible with the Auduino synth circuit, so if you have never built an Auduino before, now you have two more reasons to build one.

The XLT2 features two audio oscillators that are able to generate square, ramp or sine waveforms at audio frequencies.

These audio waveforms are then combined with individual low frequency oscillators ( LFOs ) which again generate square, ramp or sine waves but at much lower frequencies. These low frequency waveforms may not be audible on their own, but when we direct them to control the amplitude (volume) of an audio signal they can add a rythm, pulse or vibrato effect.

Each LFO also has an associated LED which reflects the LFO output both in frequency and profile - i.e. a squarewave will switch the LED hard on, then hard off whereas a sinewave will fade on and fade off reflecting the effect the LFO has on the audio signal.

A final control is provided to mix the two channels together according to a user selected ratio.

The Gated Grain Synthesizer

Once the XLT2 was up and running I wanted to try creating a similar sound to the Auduino using the same four oscillators as the XLT2, the grain synthesizer is the result.

This synth can produce a sound similar to the Auduino or even a passable violin if a sinewave is used as the output gate.

The main feature of the Gated Grain synth is that it sounds very different from the XLT2 but is built from the same four oscillators driving different aspects of the audio signal.

Proof of concept

The difference in sound between these two devices demonstrates the potential for an Arduino based synthesiser which allows the end user to configure the sound generation path using a simulated patch pannel. A follow up post will explore some of the options for implementing the patch pannel and how the relevant routing can be implemented at run time in software.

Getting Serious and moving to the Arduino Due

While this project has lots of potential for development the limited processing power of the 8-bit 16Mhz Arduino UNO will only get us so far.

The soon to be release Arduino Due platform however will allow the idea to be taken much further.

The Due has a built in analog to digital converter giving a true analog output rather than the UNOs PWM output, this together with higher resolution should provide far better sound quality.

The higher processing speeds of the Arduino Due will also allow for more oscillators and more importantly - complex relationships between the oscillators. It should also be possible to implement some software filters.

Perhaps the most exciting possibility is the high sampling rate offered by the Due, it may be possible to capture samples directly with the due and use them there and then as part of the synthesizer.




As the project stands, the user must select the waveforms at compile time however an obvious development would be to add a button for each oscillator allowing the user to cycle through the available waveforms. As my intention is to provide this function and others through a simulated patch panel, I will leave it to the user to add these buttons.


Note : The Audio output in the video is through a PC Speaker driven by the simple amplifier outlined in this post -

http://rcarduino.blogspot.com/2012/08/adding-audio-to-arduino-projects.html



#define SAMPLE_MAX (65535.0)
#define SAMPLE_FREQUENCY (8000.0)
#define TIMER1_FREQUENCY 2000000
#define UPDATE_RATE 8000

#define MIDI_NOTE_MAX 127

#define MIX_MIDDLE 127

#define GRAIN1_FREQUENCY_INPUT A0
#define GRAIN1_AMPLITUDE_INPUT A1

#define GRAIN2_FREQUENCY_INPUT A2
#define GRAIN2_AMPLITUDE_INPUT A3

#define LFO_FREQUENCY_INPUT A4
#define LFO_AMPLITUDE_INPUT A5

#define GRAIN1_SHAPE_INPUT 4
#define GRAIN2_SHAPE_INPUT 7
#define LFO_SHAPE_INPUT 8

#define PWM_OUT OCR0A

// Will put this in progmem - eventually
/*PROGMEM*/
unsigned char sineWave[256] =
{
127    ,    //0
130    ,    //1
133    ,    //2
136    ,    //3
139    ,    //4
142    ,    //5
145    ,    //6
148    ,    //7
151    ,    //8
154    ,    //9
157    ,    //10
160    ,    //11
163    ,    //12
166    ,    //13
169    ,    //14
172    ,    //15
175    ,    //16
178    ,    //17
181    ,    //18
184    ,    //19
186    ,    //20
189    ,    //21
192    ,    //22
194    ,    //23
197    ,    //24
200    ,    //25
202    ,    //26
205    ,    //27
207    ,    //28
209    ,    //29
212    ,    //30
214    ,    //31
216    ,    //32
218    ,    //33
221    ,    //34
223    ,    //35
225    ,    //36
227    ,    //37
229    ,    //38
230    ,    //39
232    ,    //40
234    ,    //41
235    ,    //42
237    ,    //43
239    ,    //44
240    ,    //45
241    ,    //46
243    ,    //47
244    ,    //48
245    ,    //49
246    ,    //50
247    ,    //51
248    ,    //52
249    ,    //53
250    ,    //54
250    ,    //55
251    ,    //56
252    ,    //57
252    ,    //58
253    ,    //59
253    ,    //60
253    ,    //61
253    ,    //62
253    ,    //63
254    ,    //64
253    ,    //65
253    ,    //66
253    ,    //67
253    ,    //68
253    ,    //69
252    ,    //70
252    ,    //71
251    ,    //72
250    ,    //73
250    ,    //74
249    ,    //75
248    ,    //76
247    ,    //77
246    ,    //78
245    ,    //79
244    ,    //80
243    ,    //81
241    ,    //82
240    ,    //83
239    ,    //84
237    ,    //85
235    ,    //86
234    ,    //87
232    ,    //88
230    ,    //89
229    ,    //90
227    ,    //91
225    ,    //92
223    ,    //93
221    ,    //94
218    ,    //95
216    ,    //96
214    ,    //97
212    ,    //98
209    ,    //99
207    ,    //100
205    ,    //101
202    ,    //102
200    ,    //103
197    ,    //104
194    ,    //105
192    ,    //106
189    ,    //107
186    ,    //108
184    ,    //109
181    ,    //110
178    ,    //111
175    ,    //112
172    ,    //113
169    ,    //114
166    ,    //115
163    ,    //116
160    ,    //117
157    ,    //118
154    ,    //119
151    ,    //120
148    ,    //121
145    ,    //122
142    ,    //123
139    ,    //124
136    ,    //125
133    ,    //126
130    ,    //127
127    ,    //128
124    ,    //129
121    ,    //130
118    ,    //131
115    ,    //132
112    ,    //133
109    ,    //134
106    ,    //135
103    ,    //136
100    ,    //137
97    ,    //138
94    ,    //139
91    ,    //140
88    ,    //141
85    ,    //142
82    ,    //143
79    ,    //144
76    ,    //145
73    ,    //146
70    ,    //147
68    ,    //148
65    ,    //149
62    ,    //150
60    ,    //151
57    ,    //152
54    ,    //153
52    ,    //154
49    ,    //155
47    ,    //156
45    ,    //157
42    ,    //158
40    ,    //159
38    ,    //160
36    ,    //161
33    ,    //162
31    ,    //163
29    ,    //164
27    ,    //165
25    ,    //166
24    ,    //167
22    ,    //168
20    ,    //169
19    ,    //170
17    ,    //171
15    ,    //172
14    ,    //173
13    ,    //174
11    ,    //175
10    ,    //176
9    ,    //177
8    ,    //178
7    ,    //179
6    ,    //180
5    ,    //181
4    ,    //182
4    ,    //183
3    ,    //184
2    ,    //185
2    ,    //186
1    ,    //187
1    ,    //188
1    ,    //189
1    ,    //190
1    ,    //191
0    ,    //192
1    ,    //193
1    ,    //194
1    ,    //195
1    ,    //196
1    ,    //197
2    ,    //198
2    ,    //199
3    ,    //200
4    ,    //201
4    ,    //202
5    ,    //203
6    ,    //204
7    ,    //205
8    ,    //206
9    ,    //207
10    ,    //208
11    ,    //209
13    ,    //210
14    ,    //211
15    ,    //212
17    ,    //213
19    ,    //214
20    ,    //215
22    ,    //216
24    ,    //217
25    ,    //218
27    ,    //219
29    ,    //220
31    ,    //221
33    ,    //222
36    ,    //223
38    ,    //224
40    ,    //225
42    ,    //226
45    ,    //227
47    ,    //228
49    ,    //229
52    ,    //230
54    ,    //231
57    ,    //232
60    ,    //233
62    ,    //234
65    ,    //235
68    ,    //236
70    ,    //237
73    ,    //238
76    ,    //239
79    ,    //240
82    ,    //241
85    ,    //242
88    ,    //243
91    ,    //244
94    ,    //245
97    ,    //246
100    ,    //247
103    ,    //248
106    ,    //249
109    ,    //250
112    ,    //251
115    ,    //252
118    ,    //253
121    ,    //254
123        //255
};

// should pre calculate these and store them in progmem to save memory
// for prototyping, lets calculate them and use ram
unsigned char rampWave[256];
unsigned char squareWave[256];

// used to convert midi note numbers into the increments required to generate the note in the ISR
PROGMEM unsigned int midiNoteToWavePhaseIncrement[128] =
{
 66 // 0,8.18,66.98,66
,70 // 1,8.66,70.96,70
,75 // 2,9.18,75.18,75
,79 // 3,9.72,79.65,79
,84 // 4,10.30,84.38,84
,89 // 5,10.91,89.40,89
,94 // 6,11.56,94.72,94
,100 // 7,12.25,100.35,100
,106 // 8,12.98,106.32,106
,112 // 9,13.75,112.64,112
,119 // 10,14.57,119.34,119
,126 // 11,15.43,126.43,126
,133 // 12,16.35,133.95,133
,141 // 13,17.32,141.92,141
,150 // 14,18.35,150.35,150
,159 // 15,19.45,159.29,159
,168 // 16,20.60,168.77,168
,178 // 17,21.83,178.80,178
,189 // 18,23.12,189.43,189
,200 // 19,24.50,200.70,200
,212 // 20,25.96,212.63,212
,225 // 21,27.50,225.28,225
,238 // 22,29.14,238.67,238
,252 // 23,30.87,252.86,252
,267 // 24,32.70,267.90,267
,283 // 25,34.65,283.83,283
,300 // 26,36.71,300.71,300
,318 // 27,38.89,318.59,318
,337 // 28,41.20,337.53,337
,357 // 29,43.65,357.60,357
,378 // 30,46.25,378.87,378
,401 // 31,49.00,401.40,401
,425 // 32,51.91,425.27,425
,450 // 33,55.00,450.55,450
,477 // 34,58.27,477.34,477
,505 // 35,61.74,505.73,505
,535 // 36,65.41,535.80,535
,567 // 37,69.30,567.66,567
,601 // 38,73.42,601.42,601
,637 // 39,77.78,637.18,637
,675 // 40,82.41,675.07,675
,715 // 41,87.31,715.21,715
,757 // 42,92.50,757.74,757
,802 // 43,98.00,802.79,802
,850 // 44,103.83,850.53,850
,901 // 45,110.00,901.11,901
,954 // 46,116.54,954.69,954
,1011 // 47,123.47,1011.46,1011
,1071 // 48,130.81,1071.60,1071
,1135 // 49,138.59,1135.32,1135
,1202 // 50,146.83,1202.83,1202
,1274 // 51,155.56,1274.36,1274
,1350 // 52,164.81,1350.13,1350
,1430 // 53,174.61,1430.42,1430
,1515 // 54,185.00,1515.47,1515
,1605 // 55,196.00,1605.59,1605
,1701 // 56,207.65,1701.06,1701
,1802 // 57,220.00,1802.21,1802
,1909 // 58,233.08,1909.38,1909
,2022 // 59,246.94,2022.92,2022
,2143 // 60,261.63,2143.20,2143
,2270 // 61,277.18,2270.64,2270
,2405 // 62,293.66,2405.66,2405
,2548 // 63,311.13,2548.71,2548
,2700 // 64,329.63,2700.27,2700
,2860 // 65,349.23,2860.83,2860
,3030 // 66,369.99,3030.95,3030
,3211 // 67,392.00,3211.18,3211
,3402 // 68,415.30,3402.12,3402
,3604 // 69,440.00,3604.42,3604
,3818 // 70,466.16,3818.75,3818
,4045 // 71,493.88,4045.83,4045
,4286 // 72,523.25,4286.41,4286
,4541 // 73,554.37,4541.29,4541
,4811 // 74,587.33,4811.33,4811
,5097 // 75,622.25,5097.42,5097
,5400 // 76,659.26,5400.53,5400
,5721 // 77,698.46,5721.67,5721
,6061 // 78,739.99,6061.89,6061
,6422 // 79,783.99,6422.36,6422
,6804 // 80,830.61,6804.25,6804
,7208 // 81,880.00,7208.85,7208
,7637 // 82,932.33,7637.51,7637
,8091 // 83,987.77,8091.66,8091
,8572 // 84,1046.50,8572.82,8572
,9082 // 85,1108.73,9082.58,9082
,9622 // 86,1174.66,9622.66,9622
,10194 // 87,1244.51,10194.85,10194
,10801 // 88,1318.51,10801.07,10801
,11443 // 89,1396.91,11443.33,11443
,12123 // 90,1479.98,12123.79,12123
,12844 // 91,1567.98,12844.71,12844
,13608 // 92,1661.22,13608.50,13608
,14417 // 93,1760.00,14417.70,14417
,15275 // 94,1864.65,15275.02,15275
,16183 // 95,1975.53,16183.31,16183
,17145 // 96,2093.00,17145.63,17145
,18165 // 97,2217.46,18165.16,18165
,19245 // 98,2349.32,19245.31,19245
,20389 // 99,2489.01,20389.70,20389
,21602 // 100,2637.02,21602.14,21602
,22886 // 101,2793.83,22886.67,22886
,24247 // 102,2959.95,24247.58,24247
,25689 // 103,3135.96,25689.42,25689
,27216 // 104,3322.44,27216.99,27216
,28835 // 105,3520.00,28835.39,28835
,30550 // 106,3729.31,30550.04,30550
,32366 // 107,3951.06,32366.63,32366
,34291 // 108,4186.01,34291.26,34291
,36330 // 109,4434.92,36330.32,36330
,38490 // 110,4698.64,38490.65,38490
,40779 // 111,4978.03,40779.41,40779
,43204 // 112,5274.04,43204.25,43204
,45773 // 113,5587.65,45773.32,45773
,48495 // 114,5919.91,48495.14,48495
,51378 // 115,6271.92,51378.79,51378
,54433 // 116,6644.87,54433.96,54433
,57670 // 117,7040.00,57670.76,57670
,61100 // 118,7458.62,61100.07,61100
,64733 // 119,7902.13,64733.26,64733
,3046 // 120,8372.02,68582.53,3046
,7124 // 121,8869.84,72660.64,7124
,11445 // 122,9397.27,76981.30,11445
,16022 // 123,9956.06,81558.77,16022
,20872 // 124,10548.07,86408.50,20872
,26010 // 125,11175.30,91546.65,26010
,31454 // 126,11839.81,96990.28,31454
};

class CSynth
{
public:
  volatile static unsigned char* m_pWaveForm1;
  volatile static unsigned char* m_pWaveForm2;
  volatile static unsigned char* m_pLFOShape1;
  volatile static unsigned char* m_pLFOShape2;
  volatile static unsigned int m_nWaveForm1Accumulator;
  volatile static unsigned int m_nWaveForm1Increment;
  volatile static unsigned int m_nWaveForm2Accumulator;
  volatile static unsigned int m_nWaveForm2Increment;
  volatile static unsigned int m_nLFOAccumulator1;
  volatile static unsigned int m_nLFOIncrement1;
  volatile static unsigned int m_nLFOAccumulator2;
  volatile static unsigned int m_nLFOIncrement2;
  volatile static unsigned char m_sLFOType;
  volatile static unsigned char m_sMix;
  static void startAudio()
  {
     TCCR1A=0x0;          // set the timer prescaler to 8 = 16/8 = 2MHz
     TCCR1B=0x02;          // set the timer prescaler to 8 = 16/8 = 2MHz
     TIMSK1 |= (1<<OCIE1A);   // Enable output compare match interrupt on OCR1A
    
     TCCR0A=0B10110011;                                    //-8 bit audio PWM
    //TCCR0A=0x83;          // Set timer waveform generation mode to FAST PWM, clear OC0A On match, set at bottom - OC0A = digital pin 6.
    TCCR0B=0x01;          // Set to clock frequency, no prescaler
    OCR0A=127;            // set in the middle - do we need this ? probably not.
    DDRD|=1<<6;          // Set digital pin 6 to output - channels 2 and 3
  }
  static void triggerMidiNote(unsigned char sNote)
  {
    if(sNote < MIDI_NOTE_MAX)
    {
      m_nWaveForm1Increment = (pgm_read_word(midiNoteToWavePhaseIncrement + (sNote)));
    }
  }
};

volatile unsigned char* CSynth::m_pWaveForm1;
volatile unsigned char* CSynth::m_pWaveForm2;
volatile unsigned char* CSynth::m_pLFOShape1;
volatile unsigned char* CSynth::m_pLFOShape2;
volatile unsigned int CSynth::m_nWaveForm1Accumulator;
volatile unsigned int CSynth::m_nWaveForm1Increment;
volatile unsigned int CSynth::m_nWaveForm2Accumulator;
volatile unsigned int CSynth::m_nWaveForm2Increment;
volatile unsigned int CSynth::m_nLFOAccumulator1;
volatile unsigned int CSynth::m_nLFOIncrement1;
volatile unsigned int CSynth::m_nLFOAccumulator2;
volatile unsigned int CSynth::m_nLFOIncrement2;
volatile unsigned char CSynth::m_sLFOType;
volatile unsigned char CSynth::m_sMix;
 
CSynth synth;

// iterate the grains and LFO
SIGNAL (TIMER1_COMPA_vect)
{
  OCR1A += (TIMER1_FREQUENCY/UPDATE_RATE);

   // cross fade between the two channels
  unsigned char sAmplitude1 = 0;
  unsigned char sAmplitude2 = 0;
 
  if(synth.m_sMix < 127)
  {
    sAmplitude1 = synth.m_sMix << 1;
    sAmplitude2 = 255;
  }
  else if(synth.m_sMix > 127)
  {
    sAmplitude2 = (255-synth.m_sMix) << 1;
    sAmplitude1 = 255;
  }
 
  OCR0B = OCR0A =
  (((((
    (*(synth.m_pWaveForm1 + ((synth.m_nWaveForm1Accumulator+=synth.m_nWaveForm1Increment)>>8))) *
    (*(synth.m_pLFOShape1 + ((synth.m_nLFOAccumulator1+=synth.m_nLFOIncrement1) >> 8)))) >> 8) *
    sAmplitude1) >> 8) +
   ((((
    (*(synth.m_pWaveForm2 + ((synth.m_nWaveForm2Accumulator+=synth.m_nWaveForm2Increment)>>8))) *
    (*(synth.m_pLFOShape2 + ((synth.m_nLFOAccumulator2+=synth.m_nLFOIncrement2) >> 8)))) >> 8) *
    sAmplitude2) >> 8)) >> 1;   

}

void setup()
{
 pinMode(GRAIN1_SHAPE_INPUT,INPUT);
 pinMode(GRAIN2_SHAPE_INPUT,INPUT);
 pinMode(LFO_SHAPE_INPUT,INPUT);

 digitalWrite(GRAIN1_SHAPE_INPUT,HIGH);
 digitalWrite(GRAIN2_SHAPE_INPUT,HIGH);
 digitalWrite(LFO_SHAPE_INPUT,HIGH);

 Serial.begin(9600);

  for(int nIndex = 0;nIndex < 256;nIndex ++)
 {
   rampWave[nIndex] = 255 - nIndex;
   squareWave[nIndex] = (nIndex < 127) ? 0 : 255;
 }

 synth.m_pWaveForm1 = sineWave;
 synth.m_pWaveForm2 = sineWave;
 synth.m_pLFOShape1 = rampWave;
 synth.m_pLFOShape2 = squareWave;
 synth.m_sMix = MIX_MIDDLE;
 synth.startAudio();
}
void loop()
{
 synth.triggerMidiNote(analogRead(1)>>3);
 synth.m_nWaveForm2Increment = analogRead(2)<<5;

 // the LFO Oscilators control the volume of the two waveforms, a low value
 synth.m_nLFOIncrement1 = analogRead(3) >> 2;
 synth.m_nLFOIncrement2 = analogRead(4) >> 2;

 // this mixes the two channels a low value will shut off one channel, a high value will shut off the other
 // anywhere in between will generate a proportional mix of the two channels
 synth.m_sMix = analogRead(5) >> 2;
}