AnTrack
My friend Kenneth expressed some interest in giving Processing a spin. He is an engineering student and usually deals in C++.
The learing curve in Processing is extremely flat and if you have prior programming experience you will go from having an idea to an actual prototype in minutes. So we thought up a small project: A Processing sketch that could detect a wavy black line on a piece of paper and subsequently play a tone according to the lines y position.
The result was anTrack, it uses the Capture Library to read the bitmaps of a web cam and the Minim sound library to produce a sine wave. It works just fine and my friend was hooked on the simplicity of Processing, he actually wrote most of the implementations.
What to use this for? I have no idea, it resembles a 1979 sci-fi horror film sound effect or a Theremin. We thought about building some sort of a rack in lego mindstorm that would continously scroll a piece of paper infront of the camera. A seismographic reading turned into sound, I guess, but the main goal was to have a little fun with processing for a couple of hours and we achieved that just fine.
anTrack.pde
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 | /* * author: Kenneth Knudsen & Ricki Gregersen * description: reads a line from a piece of paper using a webcam * and outputs a tone between 440 and 880 Hz * according to the lines y coordinate */ //use the minim sound library, included in processing 1.0 import ddf.minim.signals.*; import ddf.minim.*; import ddf.minim.analysis.*; import ddf.minim.effects.*; //CamModule instance for grapping a frame from the webcam CamModule cam; //SoundModule instance for outputting a sine wave at a specific frequence SoundModule sound; //TrackModule instance for translating a y coordinate to a frequency TrackModule track; void setup() { size(640, 480); frameRate(30); //CamModule constructor takes an input of (PApplet, width to grap, height to grap and offset) //this means: we will grap 30x480 pixels from the webcam starting at 320 in the x direction cam = new CamModule(this, 30, height, 320); sound = new SoundModule(this); track = new TrackModule(); } void draw() { //get the bitmap from the webcam PImage frame = cam.bitmap(); //process it and return the position of the line as a frequency float freq = track.processBitmap(frame); //feed the frequency to the soundModule sound.freq(freq); //present the frame grabbed from the webcam to the screen image(frame, 320, 0); } //remember to close the connection made to the soundcard void stop() { super.stop(); sound.stop(); } |
The CamModule:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | import processing.video.*; class CamModule { private Capture cam; private PImage frame; private int w, h, offSet; CamModule(PApplet applet, int _w, int _h, int _o) { w = _w; h = _h; offSet = _o; String[] devices = Capture.list(); //println(devices); //comment in the above line if you don't know which port your webcam is plugged into cam = new Capture(applet, width, height, devices[1]); frame = createImage(w, h, RGB); } public PImage bitmap() { //read the full image from the webcam and copy out the pixels needed if (cam.available()) { cam.read(); frame.copy(cam, offSet, 0, w, h, 0, 0, w, h); } return frame; } } |
The SoundModule.pde
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | class SoundModule { Minim m; AudioOutput dac; SineWave sine; SoundModule(PApplet a) { m = new Minim(a); dac = m.getLineOut(Minim.STEREO); sine = new SineWave(0, 0.5, dac.sampleRate()); //it is all in the minim documentation which is quite good, but portamento means that there //will be no pause when changing from one frequency to another, instead there is a 200 ms slide from //the original tone to the new one. sine.portamento(200); dac.addSignal(sine); } void freq(float f) { //if a frequency is present, map it to a sine with Hz between 440 and 880 (an octave) if(f > 0) { f = map(f, 0.1, height, 440, 880); } else{ f = 0; } sine.setFreq(f); } //again remenber to close the connection to the soundcard void stop() { m.stop(); dac.close(); } } |
The TrackModule.pde
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | class TrackModule { private float th = 80; //brightness 0-255 private int hh = 80; //percent 0-100 TrackModule() { } float processBitmap(PImage bm) { int index; int hits; int successive_hits = bm.width * (hh / 100); /* this is a sort of filter. we run through the pixels one horizontal line at the time if we find a pixel with a brightness below the threshold (th) we add a hit. So if 80 percent of a line is dark enought to trigger the filter we count that as a dark line spotted and we retun the y value were it was spotted. */ for(int y = 0; y < bm.height; y++) { hits = 0; index = y * bm.width; for(int x = 0; x < bm.width; x++) { color c = bm.pixels[index + x]; if( brightness(c) < th) { hits++; if(hits > successive_hits) { return y; } } } } return 0.0; } } |
There is an Applet here, I haven’t gone through the steps to make it work in a browser so the visitor
can use their own web cam, it’s mostly done so you can get the source code.