Tones! ( & A Synth!)

This is my process for building a synth & midi controller based around musical modes. See final shots & the enclosure here.
End result (pre enclosure)


After the servo lab, I got to work on trying tone. I’ve wanted to create a synth/music controller that was based around musical modes so I figured this week was a perfect time to try it out.

8 buttons would control notes, and 7 buttons would select the scale. The microcontroller would generate tones, monophonically with the highest note taking priority, and output polyphonic midi data.

First, I wrestled with getting my speaker set up. It was a simple issue of not properly supplying power to the breadboard, but I struggled for far too long….

Now that I had the speaker, I hooked up a button and had it play a C.

Now it was time to up the ante and add a second note while beginning to create the structure for my project.


and heres the code


#include "pitches.h"

int buttons[] = {12, 13};
int buttonCount = 2;

int speakerPin = 8;

int majorScale[] = {NOTE_C3, NOTE_D3};

bool noteOn = false;

void setup() {
for (int x = 0; x < buttonCount; x++) { pinMode(buttons[x], INPUT); } pinMode(speakerPin, OUTPUT); Serial.begin(9600); } void loop() { for (int z = buttonCount - 1; z >= 0; z--) {
if (digitalRead(buttons[z]) == HIGH) {
tone(speakerPin, majorScale[z]);
noteOn = true;
break;
}
else
noteOn = false;
}
if(!noteOn) noTone(speakerPin);
delay(10);

}

Now I got to work on wiring a full array of 8 buttons for notes. I also began setting up my scales.

I tried getting fancy but this did not work….

int ionian[] = {NOTE_C3, NOTE_D3, NOTE_E3, NOTE_F3, NOTE_G3, NOTE_A3, NOTE_B3, NOTE_C3};
int dorian[] = {NOTE_C3, NOTE_D3, NOTE_DS3, NOTE_F3, NOTE_G3, NOTE_A3, NOTE_AS3, NOTE_C3};
int scales[][8] = {{ionian}, {dorian}};
...
tone(speakerPin, scales[currentScale][z]);

I probably could have set it up to dump the scales array from my other arrays in a for loop, but that seemed inefficient and less flexible. So instead I decided to define my scales as something relative to a root note. Then I could create a notes array and simply grab the corresponding parts of a scale.

I kind of “over coded” by defining each scale as “whole” and “half” notes, and from there, calculating each notes deviation from the root. I could have defined all these more procedurally, but I stuck with this method, because I had already done it and it sort of makes it more readable. I tested this version out with a potentiometer to control what mode was selected.

#include "pitches.h"
#define whole 2
#define half 1
#define root 0

int buttons[] = {2,3,4,5,6,7,8,9};
int notes[] = {NOTE_B0, NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1, NOTE_C2, NOTE_CS2, NOTE_D2, NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, NOTE_B2, NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3, NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4, NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6};
int buttonCount = 8;

int rootNote = 26; //C3
int speakerPin = 12;

int pot = A0; //testing pot being tied to the scale

int scales[7][8] = {
{root, whole, whole, half, whole, whole, whole, half},
{root, whole, half, whole, whole, whole, half, whole},
{root, half, whole, whole, whole, half, whole, whole},
{root, whole, whole, whole, half, whole, whole, half},
{root, whole, whole, half, whole, whole, half, whole},
{root, whole, half, whole, whole, half, whole, whole},
{root, half, whole, whole, half, whole, whole, whole}
};

bool noteOn = false;

int currentScale = 0;

void setup() {
// put your setup code here, to run once:
for (int x = 0; x < buttonCount; x++) { pinMode(buttons[x], INPUT); } pinMode(speakerPin, OUTPUT); Serial.begin(9600); //convert our scales from human readable notation to the deviation from the root note. for(int x = 0; x < 7; x++){ for(int y=1; y < 8; y++){ scales[x][y] = scales[x][y]+scales[x][y-1]; } } } void loop() { // put your main code here, to run repeatedly: int potReading = analogRead(pot); int scaleMode = map(potReading,0,1023,0,7); Serial.println(String(String(potReading) + "," + String(scaleMode))); for (int z = buttonCount - 1; z >= 0; z--) {
if (digitalRead(buttons[z]) == HIGH) {
tone(speakerPin, notes[(scales[scaleMode][z]+rootNote)]);
noteOn = true;
break;
}
else
noteOn = false;
}
if (!noteOn) noTone(speakerPin);
delay(10);

}

Now it was time to get midi involved. I had worked with midi input before, but not output. I’ve never figured out how to create a software serial port while simoultaneously using Midi so there was a lot of trial and error throughout the process. Here was my first bit of code using midi:

//read all the buttons
//make sure we have midi note on
//if its the highest note, send a tone value
//if its off, send midi note off
//if no notes are on, ensure we have no tone
//questions: is it bad to repeatedly send note on or note off messages? or do I need to keep track of button states and only send on state change?

toneOn = false;

for (int z = buttonCount - 1; z >= 0; z--) {
if (digitalRead(buttons[z]) == HIGH) {
MIDI.sendNoteOn( (scales[scaleMode][z] + rootNote + midiIndexTransposition), 127, 1);
if (!toneOn) {
tone(speakerPin, notes[(scales[scaleMode][z] + rootNote)]);
toneOn = true;
}
} else {
MIDI.sendNoteOff((scales[scaleMode][z] + rootNote + midiIndexTransposition), 0, 1);
}
}
if (!toneOn) noTone(speakerPin);

At first, this didn’t sound right. I assumed the instrument didn’t like getting repeated note on messages, so I converted my “buttons” array to a 2d array, where I would store the button’s previous state. That way I would only send note on or off when the state changed. This solved my issue immediately. YAY!

Now it was time to add my mode switching buttons.

With my mode switching buttons, I would be at a total of 15 inputs, plus a serial out, and an output for my speaker. I decided that I wanted to use a teensy for my final version since they are tiny and cheap and I wanted to get some experience with them. This is where my misery began.

Moving my project over to my Teensy LC was relatively straight forward. The buttons and tones worked as expected but I quickly ran into an issue with midi. I banged my head against the wall and couldn’t figure out what the heck was wrong. Finally I came across this forum post explaining that teensy outputs its serial over usb by default; you have to explicitly tell it to use the hardware serial port. This was helpful but I was still struggling with getting it working. Finally, I realized you could just set the serial options in arduino settings!!!! AHHHHH!! Well now it worked, but I had eaten up a ton of time.

My code was coming together, but as I added my next set of 7 buttons, I kept hitting problems. Sometimes my notes would come out wobbly. Sometimes nothing was working. Sometimes everything was working. I created a version of my program that excluded midi so I could more easily debug what was happening… And now the tone was working as expected on the teensy

#include "pitches.h"
//#include "midiNotes.h"
#define whole 2
#define half 1
#define root 0

int speakerPin = 12;

int modalButtons[] = {14, 15, 16, 17, 18, 19, 20};

int buttons[][2] =
{{2, 0}, { 3, 0}, { 4, 0}, { 5, 0 }, {6, 0}, {7, 0}, {8, 0}, {9, 0}}; //stores locations of our note buttons (from lowest note to highest note), second position stores the buttons last state

int notes[] = {NOTE_B0, NOTE_C1, NOTE_CS1, NOTE_D1, NOTE_DS1, NOTE_E1, NOTE_F1, NOTE_FS1, NOTE_G1, NOTE_GS1, NOTE_A1, NOTE_AS1, NOTE_B1, NOTE_C2, NOTE_CS2, NOTE_D2, NOTE_DS2, NOTE_E2, NOTE_F2, NOTE_FS2, NOTE_G2, NOTE_GS2, NOTE_A2, NOTE_AS2, NOTE_B2, NOTE_C3, NOTE_CS3, NOTE_D3, NOTE_DS3, NOTE_E3, NOTE_F3, NOTE_FS3, NOTE_G3, NOTE_GS3, NOTE_A3, NOTE_AS3, NOTE_B3, NOTE_C4, NOTE_CS4, NOTE_D4, NOTE_DS4, NOTE_E4, NOTE_F4, NOTE_FS4, NOTE_G4, NOTE_GS4, NOTE_A4, NOTE_AS4, NOTE_B4, NOTE_C5, NOTE_CS5, NOTE_D5, NOTE_DS5, NOTE_E5, NOTE_F5, NOTE_FS5, NOTE_G5, NOTE_GS5, NOTE_A5, NOTE_AS5, NOTE_B5, NOTE_C6, NOTE_CS6, NOTE_D6, NOTE_DS6, NOTE_E6, NOTE_F6, NOTE_FS6, NOTE_G6, NOTE_GS6, NOTE_A6, NOTE_AS6, NOTE_B6};
int midiIndexTransposition = 11; //if you wanted to play notes[i], the corresponding midi value for that note is i+11

int buttonCount = 8; //number of notes we can play in a given scale , aka the size of our buttons array

int rootNote = 37; //the index number of C4 in our notes array
int maxRootIndex = 65; //our notes array is 73 items long; we need to leave room for 7 notes above the root.

//ionian, dorian, phrygian, lydian...
int scales[7][8] = {
{root, whole, whole, half, whole, whole, whole, half},
{root, whole, half, whole, whole, whole, half, whole},
{root, half, whole, whole, whole, half, whole, whole},
{root, whole, whole, whole, half, whole, whole, half},
{root, whole, whole, half, whole, whole, half, whole},
{root, whole, half, whole, whole, half, whole, whole},
{root, half, whole, whole, half, whole, whole, whole}
};

bool toneOn = false; //keeps track of whether or not we play a tone

int currentScale = 0; // starts at ionian, scale[0][x]

void setup() {

pinMode(13, OUTPUT); //debug led
for (int x = 0; x < 7; x++) { pinMode(modalButtons[x], INPUT); } for (int x = 0; x < buttonCount; x++) { pinMode(buttons[x][0], INPUT); } pinMode(speakerPin, OUTPUT); //convert our scales from whole and half steps to the deviation from the root note. for (int x = 0; x < 7; x++) { for (int y = 1; y < 8; y++) { scales[x][y] = scales[x][y] + scales[x][y - 1]; } } Serial.begin(9600); Serial.println("Hello World"); } void loop() { //read all the buttons //if the note is on, and was previously off, send midi note on //if its the highest note, send a tone value //if its off, and the button was previously on, send midi note off //if no notes are on, ensure we have no tone toneOn = false; for (int z = buttonCount - 1; z >= 0; z--) {
int incoming = digitalRead(buttons[z][0]);
int currentNote = scales[currentScale][z] + rootNote + midiIndexTransposition;
//Serial.println(String(String(z) + ":" + String(incoming)));

if (incoming == HIGH) {
if (!toneOn) {
tone(speakerPin, notes[(scales[currentScale][z] + rootNote)]);
toneOn = true;
}
}
buttons[z][1] = incoming;
}
if (!toneOn) {
noTone(speakerPin);
}

//mode buttons are treated like switches
//listen to the mode buttons

for (int x = 0; x < 7; x++) //there are 7 scales { //Serial.println(x); int buttonReading = digitalRead(modalButtons[x]); //Serial.println(String("Button #" + String(x) + " - " + String(buttonReading))); if (buttonReading == HIGH && x != currentScale) //if a new mode is selected { digitalWrite(13, HIGH); currentScale = x; Serial.println(String("Current Scale is " + String(currentScale))); break; // not sure if this is necessary } } digitalWrite(13, LOW); }

But now moving back over to the version with midi and GAAAH nothing was working any more!!!!!

I gave it a day and of course now things are working! I think my breadboard buttons were the issue but who knows!

Here's the code for the version with midi and the version without

One thought on “Tones! ( & A Synth!)

Leave a Reply

Your email address will not be published. Required fields are marked *