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:


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);
}

No comments:

Post a Comment