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.
// SPDX-FileCopyrightText: 2018 John Park for Adafruit Industries // // SPDX-License-Identifier: MIT //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.
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!
Page last edited January 21, 2025
Text editor powered by tinymce.