Sonar Tonebank

 

The Sonar Tonebank is a simple musical instrument that uses a range sensor to trigger sound samples. Players can create musical soundscapes by moving in front of the range sensor, which maps pitch to distance. This project was created using an Arduino, a PING sonar sensor and the Minim sound library in Processing.

 

Processing Code

The Processing code is actually very simple. The program reads range data from the Arduino via the serial port, then triggers samples and visualizer elements according to the distances reported. Any set of samples could be used (for this version, I’m using 16 different short sound textures generated with Reason), and it’s easy to imagine how the code could be modified to send MIDI signals instead of triggering samples.

 

// SONAR TONEBANK

// 1.0
// Jeff Watson
// 27 April 2009

import processing.serial.*;
import ddf.minim.*;

// arduino variables

Serial myPort; // The serial port

// video variables

// minim variables

Minim minim;

AudioSample bell;
AudioSample bell2;
AudioSample bell3;
AudioSample bell4;
AudioSample bell5;
AudioSample bell6;
AudioSample bell7;
AudioSample bell8;
AudioSample bell9;
AudioSample bell10;
AudioSample bell11;
AudioSample bell12;
AudioSample bell13;
AudioSample bell14;
AudioSample bell15;
AudioSample bell16;

// visualizer variables

void setup () {

size(640, 480, P2D);
background(0);
noStroke();

minim = new Minim(this);
bell = minim.loadSample("F#3.wav", 2048);
bell2 = minim.loadSample("G#3.wav", 2048);
bell3 = minim.loadSample("A#3.wav", 2048);
bell4 = minim.loadSample("C#4.wav", 2048);
bell5 = minim.loadSample("D#4.wav", 2048);
bell6 = minim.loadSample("F#4.wav", 2048);
bell7 = minim.loadSample("G#4.wav", 2048);
bell8 = minim.loadSample("A#4.wav", 2048);
bell9 = minim.loadSample("C#5.wav", 2048);
bell10 = minim.loadSample("D#5.wav", 2048);
bell11 = minim.loadSample("F#5.wav", 2048);
bell12 = minim.loadSample("G#5.wav", 2048);
bell13 = minim.loadSample("A#5.wav", 2048);
bell14 = minim.loadSample("C#6.wav", 2048);
bell15 = minim.loadSample("D#6.wav", 2048);
bell16 = minim.loadSample("F#6.wav", 2048);

// List all the available serial ports
println(Serial.list());
// Open whatever port is the one you're using.
myPort = new Serial(this, Serial.list()[1], 9600);
// don't generate a serialEvent() unless you get a newline character:
myPort.bufferUntil('n');
}

void draw () {
// everything happens in the serialEvent()
}

void serialEvent (Serial myPort) {
// get the ASCII string:
String inString = myPort.readStringUntil('n');

if (inString != null) {
// trim off any whitespace:
inString = trim(inString);
// convert to an int and map to the screen height:
float inByte = float(inString);
int VideoMod = int(inString);

println(VideoMod);

// 1st Octave

if ( VideoMod < 5 )
{
bell.trigger();
delay(33);
fill(0,0,255);
rect(1,0,128,480);
}

else if ( VideoMod < 8 )
{
bell2.trigger();
delay(33);
fill(13,82,122);
rect(129,0,128,480);
}

else if ( VideoMod < 11 )
{
bell3.trigger();
delay(33);
fill(0,0,255);
rect(257,0,128,480);
}

else if ( VideoMod < 14 )
{
bell4.trigger();
delay(33);
fill(13,82,122);
rect(385,0,128,480);
}

else if ( VideoMod < 17 )
{
bell5.trigger();
delay(33);
fill(0,0,255);
rect(513,0,128,480);
}

// 2nd Octave

else if ( VideoMod < 20 )
{
bell6.trigger();
delay(33);
fill(0,0,0);
rect(0,0,640,480);
fill(0,0,255,191);
rect(1,0,128,480);
}

else if ( VideoMod < 23 )
{
bell7.trigger();
delay(33);
fill(0,0,0);
rect(0,0,640,480);
fill(13,82,122,191);
rect(129,0,128,480);
}

else if ( VideoMod < 26 )
{
bell8.trigger();
delay(33);
fill(0,0,0);
rect(0,0,640,480);
fill(0,0,255,191);
rect(257,0,128,480);
}

else if ( VideoMod < 29 )
{
bell9.trigger();
delay(33);
fill(0,0,0);
rect(0,0,640,480);
fill(13,82,122,191);
rect(385,0,128,480);
}

else if ( VideoMod < 32 )
{
bell10.trigger();
delay(33);
fill(0,0,0);
rect(0,0,640,480);
fill(0,0,255,191);
rect(513,0,128,480);
}

// 3rd Octave

else if ( VideoMod < 35 )
{
bell11.trigger();
delay(33);
fill(0,0,0);
rect(0,100,640,380);
fill(0,0,255,127);
rect(1,0,128,480);
}

else if ( VideoMod < 38 )
{
bell12.trigger();
delay(33);
fill(0,0,0);
rect(0,100,640,380);
fill(13,82,122,191);
rect(129,0,128,480);
}

else if ( VideoMod < 41 )
{
bell13.trigger();
delay(33);
fill(0,0,0);
rect(0,100,640,380);
fill(0,0,255,191);
rect(257,0,128,480);
}

else if ( VideoMod < 44 )
{
bell14.trigger();
delay(33);
fill(0,0,0);
rect(0,100,640,380);
fill(13,82,122,191);
rect(385,0,128,480);
}

else if ( VideoMod < 47 )
{
bell15.trigger();
delay(33);
fill(0,0,0);
rect(0,100,640,380);
fill(0,0,255,191);
rect(513,0,128,480);
}

// Extra note clears screen

else if ( VideoMod < 50 )
{
bell16.trigger();
delay(33);
background(0);
}

else if ( VideoMod > 50 )
{
}

}
}

See also: Arduino-Graph

Arduino Code

The Arduino code is an adaptation of the PING sensor tutorial. It gets values from the sensor and sends them back to Processing.
int pingPin = 7;

void setup()
{
Serial.begin(9600);
}

void loop()
{
long duration, inches, cm;

// The PING))) is triggered by a HIGH pulse of 2 or more microseconds.
// We give a short LOW pulse beforehand to ensure a clean HIGH pulse.
pinMode(pingPin, OUTPUT);
digitalWrite(pingPin, LOW);
delayMicroseconds(2);
digitalWrite(pingPin, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin, LOW);

// The same pin is used to read the signal from the PING))): a HIGH
// pulse whose duration is the time (in microseconds) from the sending
// of the ping to the reception of its echo off of an object.
pinMode(pingPin, INPUT);
duration = pulseIn(pingPin, HIGH);

// convert the time into a distance
inches = microsecondsToInches(duration);
cm = microsecondsToCentimeters(duration);

Serial.println(inches);

delay(100);
}

long microsecondsToInches(long microseconds)
{
// According to Parallax's datasheet for the PING))), there are
// 73.746 microseconds per inch (i.e. sound travels at 1130 feet per
// second). This gives the distance travelled by the ping, outbound
// and return, so we divide by 2 to get the distance of the obstacle.
// See: http://www.parallax.com/dl/docs/prod/acc/28015-PING-v1.3.pdf
return microseconds / 74 / 2;
}

long microsecondsToCentimeters(long microseconds)
{
// The speed of sound is 340 m/s or 29 microseconds per centimeter.
// The ping travels out and back, so to find the distance of the
// object we take half of the distance travelled.
return microseconds / 29 / 2;
}


See also:
Arduino-Ping

During the early prototyping phase, I also made a simple Theremin using the PING sensor, based on Alberto Bietti’s excellent documentation. The piezo speaker and button on the current version of the project are remnants of this prototype; I’ve left them on there for now for demoing purposes.