Thursday, December 31, 2009

Experiment 5 - testing electronics for a Trombone Controller

For a long time now I've been thinking of how I could build an electronic wind instrument that would be easy for a trombone player to pick up. Although I've owned a WX-7 and have played around with the Akai EVI and EWI, I've never had the time/patience to get the finger chops needed to enjoy those instruments.

Once I found out about the Arduino and the various types of sensors that are available cheaply, it seemed like the right time to get serious about designing a trombone controller.

My current working plans are to use three sensors:
  • A breath sensor that controls articulation and volume.
  • A position sensor of some kind that can detect the slide position.
  • A set of switches, or a position sensor that controls which overtone is selected. I envision that this will be actuated with the player's left thumb, where the F-attachment lever is usually found.
In other words, pretty much like an Akai EVI/Steinerphone, except that we replace the valve buttons with a slide, and we replace the pitch rollers + drop-a-fourth button with the overtone selector.

There are a lot of different possibilities for what kind of sensors to use for each of these, and I'll need to do some experimentation to find what works best. But I don't need to figure that out before I can start working on the code - the Arduino lets me abstract away those issues. So, really, we have:
  • An analog input for the breath sensor.,
  • an analog input for the slide position, and
  • a set of switches for selecting the overtone.
Regarding the code: there are two main paths I might choose:
  1. Make the whole thing a MIDI controller, and have the Arduino send MIDI to a synthesizer (or computer + softsynth, etc).
  2. Use something like Max/MSP or pd to read the controllers directly and control synths implemented in software on the Mac/PC.

The advantages of #1 are:
  • You can take advantage of any existing synth patch, although you do have the problem every wind controller player has in finding/creating patches that respond well.
  • This approach is faster for me to implement, since I don't know Max/MSP.
The advantages of #2 are:
  • You have more control of how the synth responds to the instrument.
  • You aren't limited by MIDI's coarse-grained values (if those are in fact a problem).
The nice thing is that I'm not limited to one or the other approach. All you need is different firmware on the Arduino (heck, just get another one and plug into whichever one you want to use).

So I decided to start down the MIDI path. Just to get started, I built the following circuit to prove some concepts:



The "slide" is a Sharp Infrared sensor, the "breath controller" is a force sensing resistor, and the overtone selector is 5 separate momentary pushbutton switches.

I wrote a sketch that reads the values of each sensor/switch in a loop. For this first iteration, I decided to focus on making the overtone selector and the slide work well, and not worry so much about when note on/off messages are sent. So this thing is always "on", and a new note is only started when a different register button is pressed.

I also opted in this iteration to use pitch bend messages exclusively for pitch within a given overtone. In other words, the only note on values we ever send are the fundamental, the first overtone, the second overtone, etc. All the notes in between are produced by sending a downward pitch bend message. This requires that the synth be set so that maximum bend is six semitones, corresponding to the seven positions of the trombone slide.

On a breadboard, obviously, you can't twiddle all these controllers at once, so I first verified that the overtone selector worked properly. I then checked that tapping/holding/squeezing the force sensor produced something like a reasonable articulation and volume changes. Then I just set up the firmware to send a constant value instead of reading the force sensor (I only have two hands) and verified that the "slide" did something reasonable.

Here's a short sample:




   
   
   
   
   


In the first part, I'm moving up and down the overtone series with the slide stationary, and in the second part I "glissando" down from three diffrerent overtones.

In summary, this all looks promising. Next steps:

  • Find a pressure sensor to use for a breath controller. The first one I tried required far too much pressure.
  • Figure out how to physically make the slide, and experiment with different sensors. Some other ideas include using nichrome wire as a sensor, and position-sensing resistors (these are expensive in the size I need - about 2 feet long).
I'm mostly a software guy, so actually making the physical instrument will be a big challenge.

Here's the sketch.


/*

Prototype sketch for a trombone-like MIDI controller based on the Arduino hardware.

Gordon Good (velo27 yahoo com)

Hardware:

- An overtone selector. At this point, it's a series of discrete on-off pushbottons.
This replaces the act of blowing overtones an a physical instrument.

- A "slide". Currently, this produces pitch bend information, and is implemented
using an infrared distance sensor.

- A volume controller, intended to be actuated by the player's breath.


*/
#include "Midi.h"

const boolean DEBUG = false;
//const boolean DEBUG = true;

const int FSR_PIN = 0;
const int IR_PIN = 1;
const int OT_0_PIN = 2;
const int OT_1_PIN = 3;
const int OT_2_PIN = 4;
const int OT_3_PIN = 5;
const int OT_4_PIN = 6;

const int FUNDAMENTAL = 36; // MIDI note value of our fundamental
const int OT_1 = 48; // First overtone
const int OT_2 = 55; // Second overtone
const int OT_3 = 60; // Third overtone
const int OT_4 = 64; // Fourth overtone
const int OT_NONE = -1; // No overtone key pressed

const int MIDI_VOLUME_CC = 7;

long ccSendTime = 0; // Last time we sent continuous data (volume, pb);
const int MIN_CC_INTERVAL = 10; // Send CC data no more often than this (in milliseconds);

Midi midi(Serial);

int playingNote = -1;

void setup() {
if (DEBUG) {
Serial.begin(9600);
} else {
midi.begin(0); // Initialize MIDI
}
}

int getPitchBendFromForceSensor() {
// Get the raw value from the force sensor
int pbRawVal = analogRead(FSR_PIN);
// Constrain it to values we're willing to handle
int pbConstrainedVal = constrain(pbRawVal, 0, 1023);
// Then map those values to the MIDI pitch bend range values.
return map(pbConstrainedVal, 0, 1023, 0, 16383);
}

int getPitchBendFromIRSensor() {
// Get the raw value from the IR sensor
int pbRawVal = analogRead(IR_PIN);
// Constrain it to values we're willing to handle
int pbConstrainedVal = constrain(pbRawVal, 50, 640);
// Then map those values to the MIDI pitch bend range values.
// NOTE: We stick our head in a hole here and assume the IR
// sensor response is linear. In fact, it's not, so we need to
// come back and improve this. The current implementation will
// result in a trombone slide that is non-linear.
return map(pbConstrainedVal, 640, 50, 16383 / 2, 0);
}

int getNeutralPitchBend() {
return 8192;
}

int getPitchBend() {
return getPitchBendFromIRSensor();
}

int getMIDINote() {
// middle C = 60, so let's use two octaves below middle c as the fundamental of this instrument for now
if (digitalRead(OT_4_PIN) == 1) {
return OT_4;
} else if (digitalRead(OT_3_PIN) == 1) {
return OT_3;
} else if (digitalRead(OT_2_PIN) == 1) {
return OT_2;
} else if (digitalRead(OT_1_PIN) == 1) {
return OT_1;
} else if (digitalRead(OT_0_PIN) == 1) {
return FUNDAMENTAL;
} else {
// No overtone key pressed - return -1 so caller can know
return OT_NONE;
}
}

int getFixedVolume() {
return 127;
}

int getVolumeFromFSR() {
// Temporary code, since I don't have a good breath
// sensor yet. However, initial tests with a force sensing resistor
// indicate that it's possible to do decent articulation with the
// following code.
int volRawVal = analogRead(FSR_PIN);
return map(volRawVal, 0, 970, 0, 127);
}

int getVolume() {
return getFixedVolume();
}

void sendNoteOn(int note, int vel, byte chan, boolean debug) {
if (debug) {
Serial.print("ON ");
Serial.println(note);
} else {
midi.sendNoteOn(chan, note, vel);
}
}

void sendNoteOff(int note, int vel, byte chan, boolean debug) {
if (debug) {
Serial.print("OFF ");
Serial.println(note);
} else {
midi.sendNoteOff(chan, note, vel);
}
}

void sendPitchBend(int val, boolean debug) {
if (debug) {
Serial.print("BEND ");
Serial.println(val);
} else {
midi.sendPitchChange(val);
}
}

void sendVolume(int volume, byte chan, boolean debug) {
if (debug) {
Serial.print("VOL ");
Serial.println(volume);
} else {
midi.sendControlChange(chan, MIDI_VOLUME_CC, volume);
}
}


void loop() {
int pb = getPitchBend();
int note = getMIDINote();
int volume = getVolume();
if (-1 != note && note != playingNote) {
sendNoteOff(playingNote, 0, 1, DEBUG);
sendPitchBend(pb, DEBUG);
sendVolume(volume, 1, DEBUG);
sendNoteOn(note, 127, 1, DEBUG);
playingNote = note;
delay(50);
} else {
if (millis() > ccSendTime + MIN_CC_INTERVAL) {
sendPitchBend(pb, DEBUG);
sendVolume(volume, 1, DEBUG);
ccSendTime = millis();
}
}
}

Monday, December 28, 2009

Experiment 4 - pd and arduino

I've been thinking about a platform for building musical instruments made of out physical controllers, and one plausible option is to use the Arduino as a data gathering device feeding into PureData (pd). There is a firmware project (Firmata) that turns the Arduino into a data gathering device for pd, and a pduino project for pd that provides pd objects that can read from and write to the Arduino.

I haven't played with pd very much, so I'm only scratching the surface here, but I assembled a little instrument that responds to the ambient light in the room by altering the pitch of a sine wave oscillator. Of course, I couldn't see fit to leave well enough alone, so I added a bit of FM synthesis (based on the pd tutorials), whose parameters are also controlled by the ambient light, so this is sort of a twisted theremin.

I can't figure out how to record the audio output of pd (in the 30 minutes I spent trying to figure it out), so there's no recording of this experiment. The circuit is very simple - a voltage divider to measure the voltage drop across a photoresistor, connected to analog input 0 on the Arduino. Here's a screen capture of the pd patch.



I got a little tricky where the sampled output of the photoresistor feeds the oscillator input frequency, but also feeds the amplitude and frequency of the FM component of the sound (for more information on FM synthesis, see the "A09.frequency.mod.pd" section of the PD help files. It sounds like a theremin with a "send-back-to-factory" problem.

Thursday, December 24, 2009

Experiment 3 - Distance Sensor Pentatonic Wash

In Radio Shack this afternoon, I found that they had an ultrasonic distance sensor (actually a Parallax ping))) sensor) for sale. I bought one, and put together a simple instrument.

To use the Parallax, you send a pulse by asserting its input high for a few microseconds, then measure the duration of the pulse it returns (which corresponds to the distance to the target). While that may sound complicated, the Arduino library provides a simple function that will measure the duration of a pulse, so this only requires a few lines of Arduino code.

This instrument measures the distance from the sensor to the object (typically the performer's hand) and plays a note (where the pitch corresponds to the distance) for two seconds. It repeats this cycle 3 times per second, so three new notes are started every second, and three notes are stopped each second. The notes overlap, and this produces a pleasing "wash" of notes. The pitches are selected from a pentatonic scale (the black keys on a piano). Here's what it sounds like:









And here's a video of how it's played:
video

And the code:


/*
Produce a pentatonic wash with pitches based on how close the performer's
hand is to a Parallax ping))) ultrasonic distance sensor.

The basic idea is to sample the distance sensor every 1/3 of a second, and
transmit a note on event corresponding to where the performer's hand is
relative to the sensor. At the same time, add to an event queue a note off
event 2 seconds in the future to turn off the note. This causes a "wash"
of sound as the new notes overlap with those already sounding.

The Parallax sensor should be connected to pin 7 on the Arduino.

Gordon Good (velo27 yahooo com) 12/24/2009.

*/

#include "MIDI.h"

class Event {
public:
int type;
int value;
long time;
};

// Pitches of a pentatonic scale, in 7 different octaves.
int notes[] = {
40, 42, 45, 47, 49,
52, 54, 57, 59, 61,
64, 66, 69, 71, 73,
76, 78, 81, 83, 85,
88, 90, 93, 95, 97,
100, 102, 105, 107, 109,
112, 114, 117, 119, 121,
};

const int PING_PIN = 7;

const int QUEUE_LENGTH = 100;
const int NOTE_ON = 1;
const int NOTE_OFF = 2;

Event currentEvent;
Event queue[QUEUE_LENGTH];
int queue_head = 0;
int queue_tail = 0;

int enqueue(int type, int value, long time) {
queue[queue_tail].type = type;
queue[queue_tail].value = value;
queue[queue_tail].time = time;;
queue_tail = (queue_tail + 1) % QUEUE_LENGTH;
}

void dequeueMIDIMessage() {
while (queue[queue_head].time != -1 && queue[queue_head].time < millis()) {
if (queue[queue_head].type == NOTE_ON) {
// Not used in this sketch
MIDI.sendNoteOn(queue[queue_head].value, 127, 1);
} else if (queue[queue_head].type == NOTE_OFF) {
MIDI.sendNoteOn(110, 127, 1);
MIDI.sendNoteOff(110, 0, 1);
MIDI.sendNoteOff(queue[queue_head].value, 0, 1);
}
queue[queue_head].time = -1;
queue_head = (queue_head + 1) % QUEUE_LENGTH;
}
}

int ping_getEchoDuration() {
// To measure dstance with the PING))), send a pulse of 5 uSec, then
// read the duration of the pulse it sends. To send a clean pulse,
// set the output low for 2 uSec, then send the pulse.
pinMode(PING_PIN, OUTPUT);
digitalWrite(PING_PIN, LOW);
delayMicroseconds(2);
digitalWrite(PING_PIN, HIGH);
delayMicroseconds(5);
digitalWrite(PING_PIN, LOW);

pinMode(PING_PIN, INPUT);
return pulseIn(PING_PIN, HIGH);
}

int durationToNote(int duration) {
duration = constrain(duration, 200, 2000);
int index = map(duration, 200, 2000, 0, sizeof(notes));
return notes[index];
}

void setup() {
MIDI.begin(1);
}

void loop() {
int echoDuration = ping_getEchoDuration();
int note = durationToNote(echoDuration);
MIDI.sendNoteOn(note, 127, 1);
enqueue(NOTE_OFF, note, millis() + 2000);
dequeueMIDIMessage();
delay(333);
}

Wednesday, December 23, 2009

Experiment 2 - Reading/Sending Expression Data

For my next experiment, I wanted to try reading some sort of analog device and using that value to control a MIDI expression paramter. None of my more interesting sensors have arrived yet (I ordered an Adafruit Sensor Pack), so I opted to just read the value of a potentiometer and use that to send MIDI volume controller information. This is a common way to get a standard synth patch to work with a wind controller like the Yamaha WX-7.

The circuit builds on the one in my previous post in which a pushbutton causes a MIDI note on value to be sent. To this I added a potentiometer. One side goes to +5V, the other side to ground, and the wiper to analog input #2 on the Arduino. In the photo, the pot is on the small breadboard to the right.

The code is below. There are two slightly interesting things:
  1. The analogRead() method will report values in the range 0-1023, but MIDI continuous controller values must be in the range 0 to 127. The Arduino library has a convenient map() method which handles this.
  2. If you simply read the pot and send a volume change message every time through the loop() method, you'll produce a lot of data. The code includes a check so that we only send a volume control message every VOLUME_SEND_INTERVAL milliseconds. I experimented a bit with the value, and 100 milliseconds is too coarse - on fast volume control changes you can hear "graininess."
Code:

#include "MIDI.h"

/*
Demonstrate how to read an analog input (a potentiometer in this case) and
use that data to send MIDI volume controller information.

Every time the pushbotton is pressed, a MIDI note on will be sent with
a noteOn velocity of 127, Then, as long as the button is held, volume
controller information will be sent - the value depends on the value
read from the pot.

As a visual aid, an LED connected to digital port 13 will light whenever
the pushbutton is pressed.

Circuit:

MIDI OUT to Arduino Serial pin 1
MIDI IN from Arduino Serial pin 0
Potentiometer: Vin, gnd to each side, wiper to analog pin 2

Digital pin 2 to a pushbutton switch, and to 5v through a 10k resistor
other leg of switch to 5v

*/

#define LED 13
#define BUTTON 2
#define VOLUME_POT 2 // Analog input 2 for volume pot
int MIDI_VOLUME_CONTROLLER = 7; // MIDI controller 7 = Volume
int VOLUME_SEND_INTERVAL = 10; // Only send volume data this often (in ms).

int buttonState = LOW;
int prevButtonState = LOW;
int note = 55;
int potVal = 0;

int lastVolumeSendTime = 0; // Last time we sent volume data

void setup() {
MIDI.begin(1);
pinMode(LED, OUTPUT);
pinMode(BUTTON, INPUT);
}

void loop() {
buttonState = digitalRead(BUTTON);
if (buttonState == HIGH) {
if (prevButtonState == LOW) {
prevButtonState = HIGH;
digitalWrite(LED, HIGH);
MIDI.sendNoteOn(note, 127, 1);
} else {
// Button still pressed - send volume controller info, but
// only sample it every VOLUME_SEND_INTERVAL milliseconds so
// we don't flood the MIDI bus.
if (millis() - lastVolumeSendTime > VOLUME_SEND_INTERVAL) {
lastVolumeSendTime = millis();
potVal = map(analogRead(VOLUME_POT), 0, 1023, 0, 127);
MIDI.sendControlChange(MIDI_VOLUME_CONTROLLER, potVal, 1);
}
}
} else if (buttonState == LOW) {
if (prevButtonState == HIGH) {
prevButtonState = LOW;
MIDI.sendNoteOff(note, 0, 1);
}
digitalWrite(LED, LOW);
}
}

Monday, December 21, 2009

My First Instrument


My Arduino arrived today, and after making sure everything worked properly, I built my first musical instrument from the thing.

It's just a little example to make sure I understand how to do MIDI with it. There's a button. You press it, and it plays a random note until you release it. Press it again, and it plays a different random note.

And here'smy first performance.



   
   
   
   
   


Elliot Carter, watch out.

Code:



#include "MIDI.h"

/*
Circuit:

MIDI OUT to Arduino Serial pin 1
MIDI IN from Arduino Serial pin 0

Digital pin 2 to a pushbutton switch, and to 5v through a 10k resistor
other leg of switch to 5v

*/

#define LED 13
#define BUTTON 2

int buttonState = LOW;
int prevButtonState = LOW;
int note = 43;

void setup() {
MIDI.begin(1);
pinMode(LED, OUTPUT);
pinMode(BUTTON, INPUT);
}

void loop() {
buttonState = digitalRead(BUTTON);
if (buttonState == HIGH && prevButtonState == LOW) {
prevButtonState = HIGH;
digitalWrite(LED, HIGH);
MIDI.sendNoteOn(note, 127, 1);
} else if (buttonState == LOW) {
if (prevButtonState == HIGH) {
prevButtonState = LOW;
MIDI.sendNoteOff(note, 0, 1);
note = random(30, 70);
}
digitalWrite(LED, LOW);
}
}