dual arduino :: voice generator & manager

Looking for an original solution for a voice generator (instead of the Juno 82c54 solution), I decided to dedicate an entire Arduino to voice generation.

All voices start with a PWM wave and that’s just a switch from +5V to 0V for a precise number of times per second. This voice generator Arduino runs in 1 big counter loop, flipping four PWM outputs between HIGH and LOW. Each pin has its own switch value, determined by the desired PWM frequency for that pin. All values are subject to the speed of the counter loop. The faster the counter loop, the higher the switch values and the more precise the PWM frequencies.

The manager Arduino controls all other aspects of the synthesizer like filters etc. This Arduino is also responsible for MIDI control. MIDI-IN is interpreted and send to the voice generator via SPI (74hc595). The voice generator has a 8 bit parallel in for MIDI and voice data.

To archieve the voice generation, I combined this article http://jeremyblum.com/2010/09/05/driving-5-speakers-simultaneously-with-an-arduino/ [jeremy] with this article http://billgrundmann.wordpress.com/2009/03/03/to-use-or-not-use-writedigital/ [bill]. Using Bill’s article, I build a very fast counter loop. In pseudo code, the program is like this:

setup -> enable output pins and disable everything, except mainloop counter

setup -> enable parallel in on pins 6 – 13

loop -> for each voice -> check if this voice pin should be flipped

loop -> check if there’s a new note value for one of the voices -> yes? read -> no? continue

Here’s the code for the voice generator Arduino:

// arduino 4 voice square wave generator
// all code resides in main loop
// all delays are generated by main loop events

// globals
// toggle_steps -> after n steps, toggle voice output from HIGH to LOW, thus creating a squarewave
int toggle_after_nrof_steps[] = { 132, 210, 220, 230, 240, 250, 260, 270, 280, 290,
                300, 310, 320, 330, 340, 350, 360, 370, 380, 390,
                400, 410, 420, 430, 440, 450, 460, 470, 480, 490,
                500, 510, 520, 530, 540, 550, 560, 570, 580, 590,
                600, 610, 620, 630, 640, 650, 660, 670, 680, 690,
                700, 710, 720, 730, 740, 750, 760, 770, 780, 790,
                800, 810, 820, 830, 840, 850, 860, 870, 880, 890,
                900, 910, 920, 930, 940, 950, 960, 970, 980, 990,
                1000, 1010, 1020, 1030, 1040, 1050, 1060, 1070, 1080, 1090,
                1100, 1110, 1120, 1130, 1140, 1150, 1160, 1170, 1180, 1190 //100
              };

long stepcounter1 = 0L;
long stepcounter2 = 0L;
long stepcounter3 = 0L;
long stepcounter4 = 0L;
int steps1 = 0;
int steps2 = 1;
int steps3 = 2;
int steps4 = 3;
int pinb_value= 0;
int pind_value= 0;
int pinb_value_old= 0;
int pind_value_old= 0;
boolean newnote = false;

void setup() {
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  pinMode(5, OUTPUT);
  //pinMode(1, OUTPUT);   // dummy / buffer output

  // 6 - 13 input high
 int i;
 for(i=6; i<14;i++)
 {
   pinMode(i, INPUT);
   digitalWrite(i, HIGH);
 }

  bitWrite(ADCSRA, ADEN, LOW); // turn ADC off

  bitWrite(WDTCSR, WDE, LOW);     // turn watchdog off
  bitWrite(WDTCSR, WDIE, LOW);    // turn watchdog off

  bitWrite(TCCR0B, CS02, LOW);     // turn timer0 off
  bitWrite(TCCR0B, CS01, LOW);     // turn timer0 off
  bitWrite(TCCR0B, CS00, LOW);     // turn timer0 off

  bitWrite(TCCR1B, CS12, LOW);     // turn timer1 off
  bitWrite(TCCR1B, CS11, LOW);     // turn timer1 off
  bitWrite(TCCR1B, CS10, LOW);     // turn timer1 off

  bitWrite(TCCR2B, CS22, LOW);     // turn timer2 off
  bitWrite(TCCR2B, CS21, LOW);     // turn timer2 off
  bitWrite(TCCR2B, CS20, LOW);     // turn timer2 off

  Serial.begin(9600);
  Serial.println("start");
}

void loop() {
  while(1) {

    // for optimal processing (no sound hickups etc), the logic is divided over all voices

    // each voice has the same logic
    // if stepcounter reaches steps (note) value
    //   switch digital pin
    //   reset stepcounter
    // increase stepcounter
    // newnote?
    //   check voice value
    //    if this voice --> change steps value, reset stepscounter

    // voice 1-----------------------------------------------------------------
    if(stepcounter1 == toggle_after_nrof_steps[steps1]) {
       PIND |= _BV(2);      // 250ns
       stepcounter1 = 0;
    }
    stepcounter1++;
    if(newnote) {
      if(pind_value == 0) {steps1=pinb_value;stepcounter1=0;}
      steps1=pinb_value;stepcounter1=0;
    }

    // voice 2-----------------------------------------------------------------
    if(stepcounter2 == toggle_after_nrof_steps[steps2]) {
      PIND |= _BV(3);
      stepcounter2 = 0;
    }
    stepcounter2++;
    if(newnote) {
      if(pind_value == 1) {steps2=pinb_value;stepcounter2=0;}
    }

    // voice 3-----------------------------------------------------------------
    if(stepcounter3 == toggle_after_nrof_steps[steps3]) {
      PIND |= _BV(4);
      stepcounter3 = 0;
    }
    stepcounter3++;
    if(newnote) {
      if(pind_value == 2) {steps3=pinb_value;stepcounter3=0;}
    }

    // voice 4-----------------------------------------------------------------
    if(stepcounter4 == toggle_after_nrof_steps[steps4]) {
      PIND |= _BV(5);
      stepcounter4 = 0;
    }
    stepcounter4++;
    if(newnote) {
      if(pind_value == 3) {steps4=pinb_value;stepcounter4=0;}
    }

    // reset newnote
    if (newnote) {
      pinb_value_old = pinb_value;
      newnote = false;
    }

    // if digital pins changed
    // raise new value flag

    // read digitalpin 6,7 (voice id) and digital pin 8,9,10,11,12,13 (data)
    pinb_value = PINB & B00111111; // dont read 14,15
    pind_value = PIND & B11000000; // only read 6 and 7

    if(pinb_value!=pinb_value_old) {
      newnote = true;
    }
  }
}