Introduction: Sonifying Capacitive Data Over Serial Port With Teensy and SuperCollider (OSX and Linux)

Picture of Sonifying Capacitive Data Over Serial Port With Teensy and SuperCollider (OSX and Linux)

If you are unable to part with the $600 dollars needed to buy MaxMSP (and your free 30 days have expired) there aren't many free alternatives that can compete with the same level of sound quality as MaxMSP. Fortunately, this is an area where SuperCollider shines, and it's OpenSource!

This Instructable will walk you through how to "sonify" capacitive sensor that is generated over a serial connection from the Teensy 3.1 using SuperCollider. Much of this Instructable can be applied more generally to understand to to sonify any incoming serial data stream with SuperCollider as well. This Instructable will also work with the Teensy LC with no change in pins required.

Step 1: Stuff You'll Need

  1. A Teensy3.1 microcontroller.
  2. Optional but advisable: Some largeish gauge copper wire to fully fill the teensy pin holes (~10 gauge).
  3. A computer with a USB Port.
  4. A male-male USB to microUSB serial cable.
  5. An installation of the SuperCollider audio Engine - https://supercollider.github.io/
  6. Optional: ribbon cable.

Step 2: Setting Up the Teensy

Picture of Setting Up the Teensy

Using the attached sketch and photo as an example, I am reading Capacitive sense data from pins A1-A5 of the available 12 capacitive sense ports, plus the ground pin at the bottom of the board.

See: http://www.pjrc.com/teensy/pinout.html for which touchRead pins are available to use for capacitive sensing.

I don't like the hassle of soldered and de-soldering connections until I have decided on a finished design, so I prefer to prototype by securing my pin connections with large gauge solid conductor copper wire and then secure the other side of the wire with the outer red sleeve as shown in the picture. I chose to connect my copper connections to an old ribbon cable that has EEG sensors at the other end for the sake of experimentation, but you can connect the copper wires from the pins on the board to whatever electrical thing you want to capacitively experiment with.

It is recommended that you also use 10k resistors inbetween your chip and sensor connections, especially if you live in a dry climate. Since I live in an area with high relative humidity (~70%), my risk of accidental electrostatic discharge to the chip is relatively low, so I chose to go without. See: http://electronics.stackexchange.com/questions/3511/best-humidity-level-for-electronic-shops for more practical advice on whether this risk is relevant for you.



Step 3: Running the Teensy Sketch and Sending Serial Data

Picture of Running the Teensy Sketch and Sending Serial Data

Once you are up and running with the Teensy by referencing: http://www.pjrc.com/teensy/td_download.html and running some example programs, you should be ready to install and run the attached example sketch provided.

<p>//calibration<br>int avg_reading1,avg_reading2,avg_reading3,avg_reading4,avg_reading5;
int count_cal_readings = 0;
long total1, total2, total3, total4, total5;
int cap_read1, cap_read2, cap_read3, cap_read4, cap_read5;
boolean done_cal = false;
//use
int touch_threshold;
//debug
boolean debug;
int tval1,tval2,tval3,tval4,tval5;
int t_val1,t_val2,t_val3,t_val4,t_val5;</p><p>void setup(){
  
 debug = false;
 
 Serial.begin(9600);
 done_cal=false;
 touch_threshold = 0;
 if(debug==true){
   Serial.println("STARTING...");
   delay(1000);
 }</p><p>}</p><p>void loop(){
  cap_read1 = touchRead(A1);
  cap_read2 = touchRead(A2);
  cap_read3 = touchRead(A3);
  cap_read4 = touchRead(A4);
  cap_read5 = touchRead(A5);
 
  if(done_cal==true){ //RUNNING
    delay(10); 
    t_val1 = abs( cap_read1 - avg_reading1 );
    //delay(1);
    t_val2 = abs( cap_read2 - avg_reading2 );
    //delay(1);
    t_val3 = abs( cap_read3 - avg_reading3 );
    //delay(1);
    t_val4 = abs( cap_read4 - avg_reading4 );
    //delay(1);
    t_val5 = abs( cap_read5 - avg_reading5 );
   
//   if(t_val1 > touch_threshold){
      if(debug==true){
       //Serial.print("Raw touchRead data [ ");
       Serial.print(cap_read1); Serial.print(" "); 
       Serial.print(cap_read2); Serial.print(" "); 
       Serial.print(cap_read3); Serial.print(" "); 
       Serial.print(cap_read4); Serial.print(" "); 
       Serial.print(cap_read5);
       Serial.println();
       
      // Serial.print(" ]\n");
      // Serial.print("Touch calibrated value [");
      // Serial.print(t_val1); Serial.print("\t"); Serial.print(t_val2);
      // Serial.print("]\n");  
     }else{
       int tval1 = map(t_val1, 1, 65536, 200, 1200);
       int tval2 = map(t_val2, 1, 65536, 200, 1200);
       int tval3 = map(t_val3, 1, 65536, 200, 1200);
       int tval4 = map(t_val4, 1, 65536, 200, 1200);
       int tval5 = map(t_val5, 1, 65536, 200, 1200);
       Serial.print(tval1); Serial.print(" "); 
       Serial.print(tval2); Serial.print(" "); 
       Serial.print(tval3); Serial.print(" "); 
       Serial.print(tval4); Serial.print(" ");
       Serial.print(tval5); 
       Serial.println(); 
     }
 }else{ //CALIBRATION</p><p>   if(millis()<5000 && done_cal==false){ //first five seconds, calibrate
     total1 += cap_read1;
     total2 += cap_read2;
     total3 += cap_read3;
     total4 += cap_read4;
     total5 += cap_read5;
      count_cal_readings++;
      if(debug==true){
        Serial.print(cap_read1);Serial.print(" ");
        Serial.print(cap_read2);Serial.print(" ");
        Serial.print(cap_read3);Serial.print(" ");
        Serial.print(cap_read4);Serial.print(" ");
        Serial.print(cap_read5);
        Serial.println();
      }
   }else{
      avg_reading1 = int(total1/count_cal_readings);
      avg_reading2 = int(total2/count_cal_readings); 
      avg_reading3 = int(total3/count_cal_readings); 
      avg_reading4 = int(total4/count_cal_readings); 
      avg_reading5 = int(total5/count_cal_readings); 
      done_cal = true;
      if(debug==true){
        Serial.println("~~");
        Serial.println("AVG");
        Serial.print(avg_reading1 + " ");
        Serial.print(avg_reading2 + " ");
        Serial.print(avg_reading3 + " ");
        Serial.print(avg_reading4 + " ");
        Serial.println(avg_reading5);
        Serial.println("~~");
        delay(1000);
      }
   } 
 
 }
 
 delay(25);</p><p>}</p>

Notice that this sketch code has a brief calibration phase that it goes through in the initialization of every new run. So to get the most accurate readings, its' always a good idea to have your hands off the Teensy and cables, and not have the cables touching ground or metal when you first plug it in. You can see the calibration phase in the Arduino console if you set the DEBUG flag to 'true' in the sketch.

Once the calibration is done, you will then see ambient the numeric values in scroll on the monitor. The actual touchRead values coming from Teensy are 16 bit in size and range from 1-65536 but I have mapped the output values down into a more narrow (less annoying) audible frequency range of 200-1200 in the .ino sketch.

Step 4: Reading Data Into SuperCollider and Creating Sound!

** Use reference tutorial video for supplemental help with remaining steps. **

After installing SuperCollider from the https://supercollider.github.io/ site and starting it up, the first thing you will want to do is install the necessary package needed to read in Arduino serial data.

1) Run the following command on the command screen in the Supercollider IDE:

Quarks.install("Arduino"); //use the command-return key sequence when cursor in on the command line to execute code

2) Select "Recompile Class Library" from the Language menu.

Followed by:

3) Selecting "Boot Server" from the Language menu.

4) Open the attached example sketch, CapSenseSCExampleForInstructibles.scd file in SuperCollider and adjust the serial port device name ("/dev/cu.usbmodem880691") on line 21 to match the computers serial port name that is attached to the Teensy microcontroller.

<br><p>(<br>  SynthDef(\simpleSin, { | freq=440, myDur = 2.0, myTimeScale=0.2 |
	// For info:
	// EnvGen.kr(envelope, gate, levelScale, levelBias, timeScale, doneAction)
	// Env.perc(attackTime, releaseTime, peakLevel, curve)
	//var env = Env(levels:[0.0, 0.5], times:[0.1, 0.01], curve:-4);
	var env1 = Env.linen(sustainTime:0.001, releaseTime:0.01, curve:[\sine,\welch]);
	var envgen = EnvGen.kr(env1, myTimeScale:1/freq**2, doneAction:2);</p><p>	//var env1 = Env.xyc([[midfreq/100, 0.2, -1]!2]), gate:1, levelScale:1, levelBias:0, timeScale:myTimeScale,  doneAction:2)
		Out.ar(0,SinOsc.ar(freq )*envgen*0.3) }
  ).add;
)</p><p>(
s=Server.default;
s.serverRunning.not.if({ s.boot });
(s.volume).setVolumeRange(argMax:-12);
CmdPeriod.doOnce{SerialPort.closeAll};
p = ArduinoSMS("/dev/cu.usbmodem880691", 9600);  // Adjust serial port name as needed!!
p.action = {
	|... msg|
	msg.postln;
	//~mesg=msg;
	Synth(\simpleSin,[\freq: msg[0] ]);
	Synth(\simpleSin,[\freq: msg[1] ]);
	Synth(\simpleSin,[\freq: msg[2] ]);
	Synth(\simpleSin,[\freq: msg[3] ]);
	Synth(\simpleSin,[\freq: msg[4] ]);</p><p>};
)</p>

5) Execute first the upper, then lower, code SC block sections with cursor position and cmd-return in code block sections as shown in the video.

And you're done! You should now be hearing variable pitched sound from your computer as you touch (or get near) your capacitive sensors!

Comments

DIY Hacks and How Tos (author)2015-06-28

Cool project. Thanks for sharing.

Thanks!

About This Instructable

886views

12favorites

License:

Bio: Cere Davis is a cross-disciplinary Science Artist, Kinetic Sculptor, Engineer, Musician and Dancer based in Berkeley, Ca.
More by ceremona:Magnetic "Fire Ants" Moving SculptureCreating Long Acoustic-Kinetic "Reverb" SpringsSonifying Capacitive Data Over Serial Port with Teensy and SuperCollider (OSX and Linux)
Add instructable to: