I’ve been interested in experimenting with electronic music for a while now and also recently started doing some work with the Arduino. So I thought, ‘why not try both?’ I began with a great article I found on Make Magazine (one of my absolute favorite sites) to create the basic script to generate an audio signal with an Arduino. A Digital to Analog Converter (DAC) converts the binary outputs from the Arduino into a relatively fluid scale of voltages which make up the sound wave

On the electronics side, my setup is quite similar to my reference, with the addition of a small amplifier using an LM386 op amp chip and a couple resistors and capacitors for some basic filtering. On the code side I’ve created a much more substantial instrument. Using Processing I built an interface to create a 32 sample waveform and a melody. The data is sent live to the Arduino which places the data into it’s waveform array and then using a timer writes each value sequentially to the DAC to create the sound.

arduino_synthesizer_dac

The interface contains two sets of sliders. One represents the shape of the sound wave. Changing the shape alters the timbre of the sound. The second set controls a series of pitches. The currently playing note is lit and a light bar indicates the current position of the playhead. The waveform sliders can be adjusted individually or as a group by clicking and dragging across the set. The sequence bars control both the pitch and the frequency of the notes.

arduino_synthesizer_interface

A Video

Technical Stuff

On the Arduino side an array of characters (buff[]) stores incoming serial data from Processing through the USB cable. Two more arrays store the current wave form and sequence data. Using the avr timer the script writes the next sample in the waveform to pins 1-8 thirty-two times for one period of the current frequency. All variables from Processing are stored in the buffer. If the Arduino receives and indicator character ( ‘A’ or ‘B’ ) it will then process the previously received data accordingly and write it into the appropriate array.

Rather than continually sending the full pitch sequence, Processing sends only the current frequency which is passed into a variable used by the timer. The frequency is derived directly from the position of the current pitch slider, as an integer between 0 and 255. To improve the sound this should be mapped the set of standardized musical notes either using an array of predetermined pitches or, more elegantly, with some fancy math. The pitches are currently quite arbitrary.

For more details on building the Digital to analog converter look at this documentation. The setup is fairly simple and just involves a bunch of resistors, some wire, and a speaker. They include a potentiometer to change the pitch. If your controlling it with Processing this isn’t necessary. I also left out the jack and plugged an old speaker directly into my breadboard.

arduino code
code formatter
Arduino Synthesizer

// arduino synthesizer code
// requires waveform and frequency data from processing

#include <avr/interrupt.h>
#include <stdlib.h> 

int  i ;
int potval;

char buff[]= "000000000000000000000000000000000000";
char Mel[]= "000000";
char wave [32]; 

void ioinit (void) {
   //Initialize output ports
   PORTD = B11111111;
   DDRD  = B11111111;
} 

void timer_setup(){
  TCCR2A = 0;
  TCNT2=455;    //455 outputs 1.007khz
  TCCR2B = B00000010;
  //Timer2 Overflow Interrupt Enable
  TIMSK2 = 1<<TOIE2;
}

void setup(){
  ioinit();
  for (int i=0; i<32; i++) {
    wave[i]=0;
  }
  cli();
  timer_setup();
  i = 0;
  sei();

  Serial.begin(9600);
}

ISR(TIMER2_OVF_vect) {
  PORTD=(wave[i++]);
  TCNT2=potval;
  if(i==32){
    i=0;
  }
} 

void loop() {
  // gathers data sent from processing
  while(Serial.available()>0) {
    for(int i=0; i<36; i++) {
      buff[i]=buff[i+1];
    }
    buff[36]=Serial.read();
    // if an indicator char is found
    // the data is processed
    if (buff[36]=='A') {
      for(int i=0; i<32; i++) {
        wave[i]=buff[35-i];
      }
    } else
    if (buff[36]=='B') {
      potval=buff[35];
    }
  }
}

Processing stuff

The Processing code is quite a bit longer, but is, I think, a little easier to digest. I commented key lines with their purpose but will also give a quick overview. If you would like to play with the interface without an Arduino plugged it it will give you an error. To remedy this comment out the lines regarding the serial functions. These code chunks are marked with “//**.” You will also need to include your own font file.

The sliders are created with the same class (more or less) I’ve used in a few other projects. These are stored in an array and handled in the draw loop. The sequencer sliders use an extended slider class which adds new graphics a few variables to handle the duration of each note. They are handled by the ‘set’ class which includes the functions to change the length of notes on right click. The set stores the clicked slider in a variable, in order to handle any adjustments, until the mouse is released. The set also includes the playhead which slides across the sequence. The note it is currently over is stored to be sent to the Arduino.

A few key improvements could be made. The data sent to the Arduino only really needs to be sent at the start of the program and then when changes are made or when the playhead reaches the next note in the sequence. The Arduino script should already handle this change.

processing code
code formatter
Arduino Synth Interface (processing)

// arduino synth interface
// anthony mattox www.anthonymattox.com

import processing.serial.*;
Serial port;

slider[] Wave=new slider[32];
Set melBars;
void setup() {
  size(1200,800);
  noStroke();
  noFill();
  smooth();
  // create waveform sliders
  for(int i=0; i<Wave.length; i++) {
    Wave[i]=new slider(i*31+125, 100, 255, color(0));
  }
  // create instance of sequencer set, parameter is # of notes
  melBars=new Set(17);

  PFont font;
  // hooray Gotham, you'll need to add your own font file
  //   tools>create font, add your file name below
  font = loadFont("Gotham-Light-30.vlw");
  textFont(font); 

  //** set up serial port
  println("Available serial ports:");
  println(Serial.list());
  port = new Serial(this, Serial.list()[0], 9600);
}

void draw() {
  background(255);
  // start with some graphics
  pushStyle();
  fill(229);
  noStroke();
  rect(0,0,1200,55);
  rect(0,790,1200,10);

  noFill();
  stroke(150);
  strokeWeight(1);
  line(25,228,1089,228);
  line(25,710,1089,710);

  fill(0);
  textSize(14);
  text("waveform", 25, 225);
  textSize(14);
  text("sequence", 25, 707);
  textSize(22);
  text("arduino sythesizer", 25, 35);
  textAlign(RIGHT);
  textSize(14);
  text("anthony mattox", 1185, 25);
  text("www.anthonymattox.com", 1185, 42);
  popStyle();

  // update+render waveform sliders
  // this could all be put into a slider set class
  for(int i=0; i<Wave.length; i++) {
    Wave[i].update();
    Wave[i].render();
    if (i>0) {
      pushStyle();
      noFill();
      stroke(0);
      strokeWeight(2);
      line(Wave[i].xpos, Wave[i].thesize-Wave[i].p+Wave[i].ypos,
           Wave[i-1].xpos, Wave[i-1].thesize-Wave[i-1].p+Wave[i-1].ypos);
      popStyle();
    }
  }
  // update+render sequencer set
  melBars.update();
  melBars.render();
  //** write data to serial
  Write();
}

//**
void Write() {
  // writes all slider values followed by indicator
  for(int i=0; i<Wave.length; i++) {
    port.write(Wave[i].p);
  }
  port.write('A');
  port.write(melBars.current.p);
  port.write('B');
}

// Slider Class
class slider {
  int xpos, ypos, thesize, p;
  boolean slide;
  color c, cb;
  slider (int x, int y, int s, color col) {
    xpos=x;
    ypos=y;
    thesize=s;
    p=127;
    slide=false;
    c=col;
    //cb=color(red(c),green(c),blue(c),150);
    cb=color(0,211,216,150);
  }

  void render() {
    stroke(0,35);
    strokeWeight(10);
    noFill();
    line(xpos,ypos,xpos,ypos+thesize);

    stroke(80);
    strokeWeight(1.5);
    noFill();
    line(xpos,ypos,xpos,ypos+thesize);

    noStroke();
    fill(cb);
    ellipse(xpos, thesize-p+ypos, 12, 12);
    fill(c);
    ellipse(xpos, thesize-p+ypos, 8, 8);
  }

  void update() {
    if (slide=true && mousePressed==true && mouseButton==LEFT
         && mouseX<xpos+13 && mouseX>xpos-13){
      if ((mouseY<=ypos+thesize+10) && (mouseY>=ypos-10)) {
        p=thesize-(mouseY-ypos);
        if (p<0) {
          p=0;
        } else if (p>thesize) {
          p=thesize;
        }
      }
    }
  }
}

// sequencer bar class
// similar to normal slider class but with different rendering
// must be within a 'set' for all functions

class bar extends slider{
  int w;
  bar (int x, int y, int s, color col) {
    super(x, y, s, col);
    w=25;
    p=0;
  }

  void render() {
    if (melBars.current==this){
      noStroke();
      fill(0,211,216);
      rect(xpos,ypos-5, w, thesize+10);
    }

    noFill();
    stroke(0);
    line(xpos,ypos-5,xpos,ypos+thesize+5);

    fill(0);
    rect(xpos,thesize-p+ypos-5,w,10);
  }

  void update() {
    if (melBars.Target==null && melBars.wTarget==null) {
      if (mousePressed==true && mouseX<xpos+w && mouseX>xpos
          && (mouseY<=ypos+thesize+10) && (mouseY>=ypos-10)){
        if (mouseButton==LEFT) {
          melBars.Target=this;
        } else
        if (mouseButton==RIGHT) {
          melBars.wTarget=this;
        }
      }
    }
  }
}

// sequencer class

class Set{
  bar[] Mel;
  bar wTarget;
  bar Target;
  int ind;
  int theHeight;
  int ypos;
  bar current;

  Set(int N) {
    Mel=new bar[N];
    theHeight=255;
    ypos=450;
    // create sliders
    for(int i=0; i<Mel.length; i++) {
      Mel[i]=new bar(i*25+125, ypos, theHeight, color(i*(255/Mel.length),0,200));
    }
    // variables for active sliders
    wTarget=null;
    Target=null;
    current=Mel[0];
    ind=100;
  }

  void render() {
    // W stores width of the set
    int W=tWidth();
    // some graphics
    noFill();
    stroke(0);
    line(W+125,Mel[0].ypos-5,W+125,Mel[0].ypos+Mel[0].thesize+5);

    pushStyle();
    stroke(150);
    strokeWeight(1);
    line(125,ypos-6,125+W,ypos-6);
    popStyle();

    // playhead to light and send current frequency to arduino
    ind+=2;
    if (ind>W+125) {
      ind=125;
    }

    pushStyle();
    strokeCap(SQUARE);
    for(int i=0; i<Mel.length; i++) {
      Mel[i].render();
      if (ind>Mel[i].xpos && ind<Mel[i].xpos+Mel[i].w){
        current=Mel[i];
      }
    }
    popStyle();

    noStroke();
    fill(255,200);
    rect(ind, ypos-5, 3, theHeight+10);
  }

  void update() {
    // some interaction stuff
    if (mousePressed){
      if (Target!=null) {
        Target.p=Target.thesize-(mouseY-Target.ypos);
        if (Target.p<0) {
          Target.p=0;
        } else if (Target.p>Target.thesize) {
          Target.p=Target.thesize;
        }
      } else
      if (wTarget!=null) {
        wTarget.w=mouseX-wTarget.xpos;
        if (wTarget.w<25){
          wTarget.w=25;
        }
      }

      for(int i=0; i<Mel.length; i++) {
        Mel[i].update();
        if (i>0) {
          Mel[i].xpos=Mel[i-1].xpos + Mel[i-1].w;
        }
      }

    }
  }

  int tWidth() {
    int q=0;
    for(int i=0; i<Mel.length; i++) {
      q+=Mel[i].w;
    }
    return q;
  }

}

// some mousey things

void mouseReleased() {
  melBars.wTarget=null;
  melBars.Target=null;
}

void mousePressed() {
  if (mouseX>980 && mouseY<55) {
    link("http://www.anthonymattox.com");
  }
}

That was a little long, but I hope you enjoyed it. I know I did. If your trying to run this without an Arduino connected be sure to check out my note above about removing the communication code.

If you have any questions, comments, or resolutions leave a comment. Other people may have the same issues.


6 comments

  • noman
    05.07.09

    I think audio to visuals is more interesting than vice versa, due to the fact that visuals have to be extremely simplified to produce a pleasant sound.
    And then there is the fact that -correct me if I’m wrong- there already are a ton of plugins for this functionality.


  • noman
    05.07.09

    Nonetheless a cool setup, though.


  • Pingback: Arduino synth/sequencer with Processing + R2R DAC - machine quotidien

  • tony
    05.07.09

    That’s true. There are a lot of plugins that do similar things but the audio quality isn’t good enough for anything serious. This was just a fun experiment. I also tried to create a more intuitive interface that some other programs.


  • noman
    05.07.09

    Kind of hateful to see lines of code about audio, when I want to learn about your particle system, haha.
    It’s all good, though.


  • Pingback: Arduino synth/sequencer with Processing + R2R DAC | SquareCows