Thursday, February 11, 2010

Switch-Based Overtone Selector

My latest experiment with overtone selection on the trombone controller is to use four momentary switches, played with the left hand, to select overtones. I used some Radio Shack lever switches and epoxied them to a 1/2" by 1/2" piece of scrap wood I had, then tie-wrapped it to the handle of the instrument (hey, I'm just prototyping).

The four switches are wired to pull Arduino digital pins 2, 3, 4, and 5 to ground when pressed, and I coded up my sketch to give the following overtones for the given switch selections:

Switch
3210 Overtone
0000 OT_1 (B flat)
0001 OT_2 (F)
0011 OT_3 (B flat)
0111 OT_4 (D)
1111 OT_5 (F)
1110 OT_7 (A flat*)
1100 OT_8 (B flat)
1000 OT_9 (C)

Switch 0 is under the index finger, and switch 3 is under the pinky. Here's a short video showing how it is played:




In terms of playability, it feels pretty good. I can more or less play a scale and the fingers of the left hand will generally do the right thing.

Here's the sketch:



/*

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

Hardware:

- An set of four switches used to select an overtone. We use "chording" to allow
the 4 switches to select overtones. I'm not sure what the most natural method
of chording is, but let's try the following:

Switch
3210 Overtone
0000 OT_1
0001 OT_2
0011 OT_3
0111 OT_4
1111 OT_5
1110 OT_7
1100 OT_8
1000 OT_8

Switches 0-3 are wired to pull Arduino digital input pins 2-5 low when
pressed.

- A "slide". Currently, this produces pitch bend information, and is implemented
with a 500mm SpectraSymbol SoftPot linear resistance strip.

- A volume controller, implemented with a FreeScale pressure sensor. The player
blows into a tube that goes to a "T" - one leg goes to the pressure sensor, and
the other is open (a "dump tube") so that the player can put air through the
instrument.

Feb 9, 2010
Gordon Good (velo27 yahoo com)

*/
#include <MidiUart.h>
#include <Midi.h>

MidiClass Midi;

// If DEBUG == true, then the sketch will print to the serial port what
// it would send on the MIDI bus.
const boolean DEBUG = false;
//const boolean DEBUG = true;

const int BREATH_PIN = 0; // Breath sensor on analog pin 0
const int SLIDE_LPOT_PIN = 1; // Slide sensor on analog pin 1

const int OT_SW_0_PIN = 2; // Overtone switch 0
const int OT_SW_1_PIN = 3; // Overtone switch 1
const int OT_SW_2_PIN = 4; // Overtone switch 2
const int OT_SW_3_PIN = 5; // Overtone switch 3

const int PANIC_PIN = 6; // MIDI all notes off momentary switch on digital I/O 6

// The overtone series this instrument will produce
const int FUNDAMENTAL = 36; // MIDI note value of our fundamental
const int OT_1 = 48; // First overtone (B flat)
const int OT_2 = 55; // Second overtone (F)
const int OT_3 = 60; // Third overtone (B flat)
const int OT_4 = 64; // Fourth overtone (D)
const int OT_5 = 67; // Fifth overtone (F)
const int OT_6 = 70; // Sixth overtone (A flat - not in tune - need to tweak pitch bend)
const int OT_7 = 72; // Seventh overtone (B flat)
const int OT_8 = 74; // Eighth overtone (C)
const int OT_9 = 76; // Ninth overtone (D)
const int OT_NONE = -1; // No overtone key pressed (not possible with ribbon)

// All overtones for this instrument
const int overtones[10] = {FUNDAMENTAL, OT_1, OT_2, OT_3, OT_4, OT_5, OT_6, OT_7, OT_8, OT_9};
// Switch values for given overtones. 0xff means that overtone can't be selected.
const int overtone_sw_values[10] = {0xff, 0x00, 0x01, 0x03, 0x07, 0x0f, 0x0e, 0x0c, 0x08, 0xff};

const int MIDI_VOLUME_CC = 7; // The controller number for MIDI volume data
const int MIDI_BREATH_CC = 2; // The controller number for MIDI breath controller data

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);
const int PB_SEND_THRESHOLD = 10; // Only send pitch bend if it's this much different than the current value
const int VOLUME_SEND_THRESHOLD = 1; // Only send volume change if it's this much differnt that the current value
const int NOTE_ON_VOLUME_THRESHOLD = 50; // Raw sensor value required to turn on a note

// If a value larger than this is read from a SoftPot, treat it as if the player is not touching it.
// Note: for some reason, the two SoftPots interact, e.g. just actuating the slide pot gives me
// no-touch values all above 1000, but when also touching the overtone pot, the values can go
// as low as 999. I suspect I may be taxing the 5v supply line.
const int LPOT_NO_TOUCH_VALUE = 1010;

int currentNote = -1; // The MIDI note currently sounding
int currentPitchBend = 8192; // The current pitch bend
int currentVolume = 0; // The current volume

void setup() {
enableDigitalInput(OT_SW_0_PIN, true);
enableDigitalInput(OT_SW_1_PIN, true);
enableDigitalInput(OT_SW_2_PIN, true);
enableDigitalInput(OT_SW_3_PIN, true);
enableDigitalInput(PANIC_PIN, true);
enableAnalogInput(BREATH_PIN, false);
enableAnalogInput(SLIDE_LPOT_PIN, true);

if (DEBUG) {
Serial.begin(9600);
} else {
MidiUart.init(); // Initialize MIDI
}
}

/**
* Enable a pin for analog input, and set its internal pullup.
*/
void enableAnalogInput(int pin, boolean enablePullup) {
pinMode(pin, INPUT);
digitalWrite(pin + 14, enablePullup ? HIGH : LOW);
}

/**
* Enable a pin for digital input, and set its internal pullup.
*/
void enableDigitalInput(int pin, boolean enablePullup) {
pinMode(pin, INPUT);
digitalWrite(pin, enablePullup ? HIGH : LOW);
}


/**
* Read the slide pot and return a pitch bend value. The values
* returned are all bends down from the base pitch being played,
* and are in the range 8192 (no bend) to 0 (maximum bend down).
* This means that the synth patch needs to be adjusted to provide
* a maximum pitch bend of seven semitones, if you want it to
* behave like a trombone.
*
* Return -1 if the player is not touching the sensor.
*/
int getPitchBendFromLinearPot() {
// Get the raw value from the linear pot
int pbRawVal = analogRead(SLIDE_LPOT_PIN);
if (pbRawVal > LPOT_NO_TOUCH_VALUE) {
return -1;
} else {
return map(pbRawVal, 0, LPOT_NO_TOUCH_VALUE, 0, 16383 / 2);
}
}

int getPitchBend() {
return getPitchBendFromLinearPot();
}

/**
* Read the overtone switches and return the appropriate overtone.
* If an invalid key combination is found, return -1. Note that
* we invert the values from digitalRead, since these switches
* pull to ground, so switch enabled = digital 0.
*/
int getOvertoneFromOvertoneSwitches() {
unsigned char val = !digitalRead(OT_SW_3_PIN);
val = val << 1 | !digitalRead(OT_SW_2_PIN);
val = val << 1 | !digitalRead(OT_SW_1_PIN);
val = val << 1 | !digitalRead(OT_SW_0_PIN);
// now select the appropriate overtone
for (int i = 0; i < sizeof(overtone_sw_values); i++) {
if (val == overtone_sw_values[i]) {
return i;
}
}
return -1;
}

int getMIDINote() {
int ot = getOvertoneFromOvertoneSwitches();
if (-1 == ot) {
return currentNote;
} else {
return overtones[ot];
}
}

/**
* Read the breath sensor and map it to a volume level. For now,
* this maps to the range 0 - 127 so we can generate MIDI
* continuous controller information.
*/
int getVolumeFromBreathSensor() {
int volRawVal = analogRead(BREATH_PIN);
if (volRawVal < NOTE_ON_VOLUME_THRESHOLD) {
return 0;
} else {
return map(constrain(volRawVal, 30, 500), 30, 500, 0, 127);
}
}

int getVolume() {
return getVolumeFromBreathSensor();
}

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

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

void sendPitchBend(int pitchBend, boolean debug) {
if (-1 != pitchBend) {
if (abs(currentPitchBend - pitchBend) > PB_SEND_THRESHOLD) {
currentPitchBend = pitchBend;
if (debug) {
Serial.print("BEND ");
Serial.println(pitchBend);
} else {
MidiUart.sendPitchBend(pitchBend);
}
}
}
}

void sendVolume(int volume, byte chan, boolean debug) {
if (abs(currentVolume - volume) > VOLUME_SEND_THRESHOLD) {
currentVolume = volume;
if (debug) {
Serial.print("VOL ");
Serial.println(volume);
} else {
//midi.sendControlChange(chan, MIDI_VOLUME_CC, volume);
MidiUart.sendCC(chan, MIDI_VOLUME_CC, 100 );
}
}
}

void sendBreathController(int volume, byte chan, boolean debug) {
if (abs(currentVolume - volume) > VOLUME_SEND_THRESHOLD) {
if (debug) {
Serial.print("BC ");
Serial.println(volume);
} else {
MidiUart.sendCC(chan, MIDI_BREATH_CC, volume );
}
}
}

void allNotesOff() {
for (int i = 0; i < 128; i++) {
sendNoteOff(i, 0, 1, DEBUG);
}
}

void loop() {

if (digitalRead(PANIC_PIN) == 0) {
allNotesOff();
}

int pb = getPitchBend();
int note = getMIDINote();
int volume = getVolume();

if ((-1 != currentNote) && (0 == volume)) {
// Breath stopped, so send a note off
sendNoteOff(currentNote, 0, 1, DEBUG);
currentNote = -1;
} else if ((-1 == currentNote) && (0 != volume) && (-1 != note)) {
// No note was playing, and we have breath and a valid overtone, so send a note on
sendNoteOn(note, 127, 1, DEBUG);
currentNote = note;
} else if ((-1 != currentNote) && (note != currentNote)) {
// A note was playing, but the player has moved to a different note.
// Turn off the old note and turn on the new one.
sendNoteOff(currentNote, 0, 1, DEBUG);
sendPitchBend(pb, DEBUG);
sendBreathController(volume, 1, DEBUG);
sendNoteOn(note, 127, 1, DEBUG);
currentNote = note;
} else if (-1 != currentNote) {
// Send updated breath controller and pitch bend values.
if (millis() > ccSendTime + MIN_CC_INTERVAL) {
sendPitchBend(pb, DEBUG);
sendBreathController(volume, 1, DEBUG);
ccSendTime = millis();
}
}
delay(50);
}

Saturday, February 6, 2010

Using Wesen's MIDIDuino library on a Mac

I ran into a few problems using Ruin & Wesen's excellent MIDIDuino library on my Mac - here are three things to know:

Thing 1 - Incompatibility with Recent Arduino IDEs

Because of some changes in the gcc bundled with recent Arduino IDEs, you need to use an older IDE (verson 0013 is known to work) with the MIDIDuino library. I'm sure that Wesen will eventually fix that, but for now, get 0013 and install it on your Mac (this advice applies if you're running it on a PC as well).

Thing 2 - Arduino 0013 and 64-bit Snow Leopard

If you're running Snow Leopard, depending on which model of Mac you have, you may be running in 64-bit mode, and if so, you'll get the following error when you try to launch Arduino 0013:



So search engines can find this, the text in the dialog is:

Cannot launch Java application

Uncaught exception in main method:
java.lang.UnsatisfiedLinkError:/Applications/
Resources/Java/librxtxSerial.jnilib:no suitable image found.
Did find: /Applications/arduino-0013/Arduino/
13.app/Contents/Resource/Java/librxtxSerial.jnilib:
no matching architecture in universal wrapper

To fix this, set Arduino-0013 to run in 32-bit mode. First, find the Arduino app (if it's in your dock, you can Crtl-click, then choose Options->Show in Finder. Single-click the app, then choose Get Info. In the inspector that appears, click "Open in 32-bit Mode" and dismiss the insepctor.




Thing 3 - Arduino preferences.txt problems

If you've run more recent versions of the Arduino IDE, you may have an Arduino preferences file that the Arduino 0013 can't read. You'll get this error:




Cannot launch Java application

Uncaught exception in main method:
java.ang.NumberFormatException: null

The simplest thing is to delete the file, or rename it. Of course, if you need to also run the newer Arduino IDE from time to time, you'll need to perform this every time you switch back. This is left as an exercise for the reader. :-)

To find the preferences file, choose "Preferences" from the File menu, and look at the bottom for "More preferences can be edited directly in the file".

Oh, and this advice about the preferences file also applies to PC users.

Friday, February 5, 2010

More Progress

Tonight I experimented with placing a 100 mm linear pot on the handle of the "trombone" instrument, where the player holds it with the left hand. By touching the pot with one of the four fingers of the left hand, the player is able to select one of five partials (no fingers, one finger, ... four fingers). With this arrangement, I was able to play a decent taps:










I also found that playing trombonistically was a lot more natural with this arrangement. For example, if you're playing F (2nd overtone) and want to go up to G, on a trombone, you'd go from first to fourth position, and blow up to the next partial. On my instrument, you would go from first to fourth position, and put the next highest finger down. When I tried this, my body just sort of did it naturally, probably because the physical orientation of the overtone selection was the same in my brain (up).

Now, since there are only 4 fingers to work with (and possibly the thumb, if I can free it up from its job of keeping me from dropping the instrument), to cover the typical 8 partial + range of the instrument, to make this work, we may need to figure out some sort of "chording" for the fingers of the left hand. One thing that occurs to me right away is to use the one-finger-per-overtone approach for the lower partials, then bring the other fingers back into the picture, e.g. (fingers are numbered 1 = index, 4 - pinky)

0th partial (fundamental) - no fingers (B flat)
1st partial - 4 (B flat)
2nd partial - 3 (F)
3rd partial - 2 (B flat)
4th partial - 1 (D)
5th partial - 1 + 2 (F)
6th partial - 1 + 2 + 3 ("A flat")
7th partial - 1 + 2 + 3 + 4 (B flat)

If you're a trombonist, you see that this is missing a few more playable partials (probably another 4-6 semitones is required of the physical instrument).

I don't have any good answers for how to solve these problems yet, but I'm revisiting my assumption that left-hand control of the overtone series is a dead-end. If I follow up on this approach, I think a set of momentary switches would work out a lot better than the linear pot.

On that left-hand-is-a dead-end front, I built a prototype mouthpiece with a baffle that splits the airflow into two vertically separated streams. After the epoxy hardens, I plan to use this to investigate the feasibility of using embouchure "gestures" as overtone selectors. It may be a total bust, but if I can make it work, I think it might make the instrument a lot more playable.

Wednesday, February 3, 2010

Force Sensitive Resistor (FSR) as an overtone selector

Tonight, I wired up a Force Sensitive Resistor (FSR) so that it would select an overtone on my MIDI trombone. I put it where the performer grips the instrument so that it could be actuated by the performer's left thumb. Then, I tried to play it myself (by trying to move the slide and actuating the overtone selector). The results were disappointing. Switching between partials required a far more subtle gesture than I was able to produce.

Back to the drawing board!