Setup

Let’s make some music to test things out! First, we’ll need to set up our coding environment — we’ll use Arduino and some specific libraries, but in the future you’ll be able to use CircuitPython for this project as well.

First, make sure you’re set up with Arduino IDE and able to upload a sketch to the board. You can follow this guide to get that going. Be sure to follow the directions on adding the Adafruit Feather M0 Express board definition to your Arduino IDE. Then, upload the blink sketch as a test using these instructions. When you’re ready, return here!

MIDI Library

Next, we'll install the MIDI library from Fourty Seven Effects. In the Arduino IDE click Sketch > Include Library > Manage Libraries...

Then, in the library manager, click in the search box and type MIDI synth this will narrow down the library list to just the one we want. 

Click on the MIDI Library selection and then click the Install button. (It is greyed out here because I already have it installed.)

MIDI Messages

To tell the DSP-G1 what to do, we need to have the Feather send MIDI commands to the synth chip. MIDI (Musical Instrument Digital Interface) is a standard used to send messages between music controllers, computers, sequencers, synthesizers, and other devices. 
For a great introduction to MIDI, check out this guide Collin’s Lab: MIDI.

The DSP-G1’s firmware is written to receive messages on MIDI channel 1 (the MIDI standard allows 16 different channels to be used in order to accommodate multiple devices in a single, interconnected system). We can send it two types of commands: Notes and Control Changes (CC) messages. 

Note commands can be either a Note On or Note Off message, along with a specification for which musical note to play. The DSP-G1 is a 5-voice paraphonic synthesizer, which means we can play up to five notes at once. The range for notes is 0-127, with 64 being a middle C.

CC messages are used to control all other parameters on the DSP-G1, such as oscillator waveform range, filter cutoff frequency, and low frequency oscillator (LFO) rate. The range for these CC messages is 0-127. Later we’ll take a closer look at all of the available CC parameters on the synth.
Let’s look at some sample code that will play a few notes and sweep through some CC values.

Code Demo

Copy this code, paste it into a new Arduino sketch, and then upload it to your Feather M0 Express.

//DSP-G1_Synth_Parameters_Demo
//Feather M0 Express connected to DSP-G1 voice chip over TX pin

#include <MIDI.h>
MIDI_CREATE_DEFAULT_INSTANCE();
static const unsigned LED = 13;      // LED pin on the Feather

void setup() {
  Serial.begin(115200);
  pinMode(LED, OUTPUT);
  MIDI.begin(1);

/////////////DSP-G1 CC Parameter Settings
//arguments are CC number, followed by value, and MIDI out channel
  MIDI.sendControlChange( 7, 120, 1);   //volume
  MIDI.sendControlChange( 1,   0, 1);   //LFO mod
  MIDI.sendControlChange(16,   6, 1);   //LFO rate
  MIDI.sendControlChange(20,   0, 1);   //LFO waveform 0-63 sine, 64-127 S/H
  MIDI.sendControlChange(74,  80, 1);   //DC Filter cutoff - higher number lets more harmonics through
  MIDI.sendControlChange(71,   0, 1);   //DC Filter resonance
  MIDI.sendControlChange(82,  32, 1);   //DC Filter envelope Attack
  MIDI.sendControlChange(83,  38, 1);   //DC Filter envelope Decay
  MIDI.sendControlChange(28,  64, 1);   //DC Filter envelope Sustain
  MIDI.sendControlChange(29,  32, 1);   //DC Filter envelope Release
  MIDI.sendControlChange(81,  57, 1);   //DC Filter envelope modulation
  MIDI.sendControlChange(76, 100, 1);   //DC Oscillator waveform* 100
  MIDI.sendControlChange( 4,   0, 1);   //DC Oscillator wrap
  MIDI.sendControlChange(21,   0, 1);   //DC Oscillator range
  MIDI.sendControlChange(93,   4, 1);   //DC Oscillator detune
  MIDI.sendControlChange(73,   5, 1);   //DC Oscillator envelope Attack
  MIDI.sendControlChange(75,  12, 1);   //DC Oscillator envelope Decay
  MIDI.sendControlChange(31,  60, 1);   //DC Oscillator envelope Sustain
  MIDI.sendControlChange(72,  80, 1);   //DC Oscillator envelope Release
// Wavforms: 0 tri, 25 squarish, 50 pulse, 75 other squarish, 100 saw
}

void loop() {
  digitalWrite(LED, HIGH);
  Serial.println("Playing notes");
  //Play a C
  MIDI.sendNoteOn(24,127,1); //note 24 is C1, velocity 127, channel 1)
  //Velocity isn't implemented, but it's a good habit to specify it
  delay(1000);

  //Play an E
  MIDI.sendNoteOff(24,0,1); // note 24, velocity 0, channel 1
  delay(250);
  MIDI.sendNoteOn(28,127,1); //note E1
  delay(1000);

  //Play a G
  MIDI.sendNoteOff(28,0,1);
  delay(250);
  MIDI.sendNoteOn(31,127,1); //note G1
  delay(1000);

  //Play an A#
  MIDI.sendNoteOff(31,0,1);
  delay(250);
  MIDI.sendNoteOn(34,127,1); //note G1
  delay(1000);
  MIDI.sendNoteOff(34,0,1);

  //rest
  delay(500);
  //chord
  MIDI.sendNoteOn(12,127,1); //C0
  MIDI.sendNoteOn(28,127,1); //E2
  MIDI.sendNoteOn(31,127,1); //G2
  MIDI.sendNoteOn(34,127,1); //A#2
  MIDI.sendNoteOn(48,127,1); //C3
  //hold
  delay(4000);

  //filter cutoff frequency sweep
  sweepFilterCutoff(1,15); //turn the filter cutoff knob to the right
  sweepFilterCutoff(0,15); //turn the filter cutoff knob to the left

  //filter cutoff and resonance sweeps
  sweepFilterCutoff(1,15); //turn the filter cutoff knob to the right
  sweepFilterResonance(1,15); //turn the filter resonance peak knob to the right
  sweepFilterCutoff(0,15); //turn down the cutoff
  sweepFilterResonance(0,15); //and turn down the resonance
  //hold here
  delay(2000);

  //oscillator detune
  sweepDetune(1,15); //turn up the detune
  //hold to listen to that detune
  delay(3000);
  sweepDetune(0, 15); //turn it back down
  delay(2000);

  digitalWrite(LED, LOW);
  Serial.println("Notes off");
  MIDI.sendNoteOff(12,0,1);
  MIDI.sendNoteOff(28,0,1);
  MIDI.sendNoteOff(31,0,1);
  MIDI.sendNoteOff(34,0,1);
  MIDI.sendNoteOff(48,0,1);
  delay(200);
}

void sweepFilterCutoff(int dir,int rate){ //dir 0 down, dir 1 up. rate in ms, e.g. 30
  if(dir==1){ //sweep up
    for(int i = 0; i < 128; i++){
      MIDI.sendControlChange(74,i,1);
      delay(rate);
      Serial.print("Filter cutoff: "); Serial.println(i);
    }
  }
  else{ //sweep down
    for(int i = 127; i >=0 ; i--){
      MIDI.sendControlChange(74,i,1);
      delay(rate);
      Serial.print("Filter cutoff: "); Serial.println(i);
    }
  }
}

void sweepDetune(int dir,int rate){ //dir 0 down, dir 1 up. rate in ms, e.g. 30
  if(dir==1){ //sweep up
    for(int i = 0; i < 128; i++){
      MIDI.sendControlChange(93,i,1);
      delay(rate);
      Serial.print("Detune: "); Serial.println(i);
    }
  }
  else{ //sweep down
    for(int i = 127; i >=0 ; i--){
      MIDI.sendControlChange(93,i,1);
      delay(rate);
      Serial.print("Detune: "); Serial.println(i);
    }
  }
}

void sweepFilterResonance(int dir,int rate){ //dir 0 down, dir 1 up. rate in ms, e.g. 30
  if(dir==1){ //sweep up
    for(int i = 0; i < 100; i++){ //127 has loads of feedback
      MIDI.sendControlChange(71,i,1);
      delay(rate);
      Serial.print("Filter resonance: "); Serial.println(i);
    }
  }
  else{ //sweep down
    for(int i = 127; i >=0 ; i--){
      MIDI.sendControlChange(71,i,1);
      delay(rate);
      Serial.print("Filter resonance: "); Serial.println(i);
    }
  }
}

Plug in some headphones or an external powered speaker and you’ll hear some music! The code plays a few notes individually, then holds a chord of five notes, and while those are held it sweeps through CC values on a few parameters: Filter cutoff frequency, filter resonance, and oscillator detune. It then stops the chord and starts it all over again.

Take a look at the code and the comments to see how this works. The most important commands are:

MIDI.sendNoteOn(note number, velocity, MIDI Out channel)

MIDI.sendNoteOff(note number, velocity, MIDI Out channel)

MIDI.sendControlChange(CC number, value, MIDI Out channel)

With the MIDI library in place, instructing the Feather to do a MIDI.sendNoteOn(64, 127, 1); will send the MIDI message over its TX pin to the DSP-G1's MIDI In leg that instructs the synth chip to play a middle C (note 64) at full key velocity (velocity is not implemented on the DSP-G1, so this number doesn’t matter in this case) on MIDI channel 1.

Sweeps

I've also written some simple procedures you can see at the bottom of the code that can be used to simulate turning a knob up and down. Until we plug in potentiometers, this is a good way to simulate knob twisting and hear the effects they have on the sound. 

In this code snippet you can see how the sweepFilterCutoff() procedure works. It takes two arguments, dir and rate, and uses those to increment or de-increment the value of the MIDI CC 74, which is assigned to filter cutoff frequency on the DSP-G1. The rate parameter determines how quickly it sweeps through the values. 

Download: file
void sweepFilterCutoff(int dir,int rate){ //dir 0 down, dir 1 up. rate in ms, e.g. 30
  if(dir==1){ //sweep up
    for(int i = 0; i < 128; i++){
      MIDI.sendControlChange(74,i,1);
      delay(rate);
      Serial.print("Filter cutoff: "); Serial.println(i);
    }
  }
  else{ //sweep down
    for(int i = 127; i >=0 ; i--){
      MIDI.sendControlChange(74,i,1);
      delay(rate);
      Serial.print("Filter cutoff: "); Serial.println(i);
    }
  }
}

You can look at the full list of the DSP-G1 parameters where they are initialized at the top of the code and create your own sweep procedures to test those out as well!

Now, let's start add real input controls to the synthesizer!

This guide was first published on Apr 30, 2018. It was last updated on Apr 30, 2018.

This page (Code Test) was last updated on Nov 06, 2020.