gilfuser / liebesbrunnen

The technical details and resources to make the Liebesbrunnen

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Liebesbrunnen_inside

Liebesbrunnen

####Technical details and resources of the Liebesbrunnen

The artistic concept and a video of the Liebesbrunnen in action can be seen in the link https://vimeo.com/122400494.

The Liebesbrunnen is essentially a plastic (PVC maybe) tube with one meter high and ten centimeters of diameter. It have wires LEDs and rubbish inside. It produces sound and video while it is manipulated.

There's a web-cam attached in one of the extremities to record what goes on inside it. The video is processed by Processing (https://processing.org/), which adds delays and layers in a similar way as the sound does. The sound is captured by a contact microphone made from a piezo disc and processed with Supercollider (http://supercollider.github.io/). Delays with altered pitches are added to the original sound. Every time the Liebesbrunnen changes from the horizontal position to vertical or vice-versa the tilt sensor re-starts some synths with random parameters in order to the Liebesbrunnen to sounds differently, in a more or less unpredictable way. The tilt sensor state also changes the LEDs controlled by an Arduino program. The sound amplitude controls the brightness of the white LED.

Here it goes the Arduino Programm:

//  For more informations about this stetch, see Arduino/File/Examples/Basics/Digital/Debounce
//  and Arduino/File/Examples/Basics/Communication/Serial... one of those. I need to check wich one
// give names to the pins, regarding the LEDs colors
const int tiltPin = 2;
const int blue2 = 3;
const int yellow = 5;
const int red3 = 6;
const int red = 9;
const int white = 10;
const int green = 11;
const int red2 = 12;
const int ledPin = 13;

//initial values for some LEDs brightness
int briG = 0;
int briW = 255;
int briR = 127;
int briR3 = 100;
int value;
int valueR = 127;
int valueR3 = 100;

long time=0;  //for the timer. In order not to use 'wait'
// for fading exponentially - much better than linearly
int periode = 3000;
int displace = 1515;

int tiltState = 0;  // current state of the tilt sensor
int lastTiltState = LOW;  // previous state of the tilt sensor
int ledState = HIGH;  // visual feedback of the tilt sensor state

//to debouncing - discard fast changings on the tilt sensor state
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay = 50;    // the debounce time; increase if the output flickers

void setup() {
  // this is very specific related to the layout from the pins in the Arduino FIO  
  pinMode(red, OUTPUT);
  pinMode(green, OUTPUT);
  pinMode(white, OUTPUT);
  pinMode(red2, OUTPUT);
  pinMode(yellow, OUTPUT);
  pinMode(red3, OUTPUT);
  pinMode(blue2, OUTPUT);
  pinMode(tiltPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  digitalWrite(ledPin, ledState);

  Serial.begin(115200);  // this is maybe too fast
}

void loop() {
  time = millis();

  int reading = digitalRead(tiltPin);
  if (reading != lastTiltState) {  // if a changing in the tilt sensor occurs
    lastDebounceTime = millis();  // get the instant of the changing
  }

  if ((millis() - lastDebounceTime) > debounceDelay) {  /* if the period in between two
   changes in the tilt sensor is bigger than the given 'time threshold'*/
    if (reading != tiltState) {  // and if the tilt sensor's state is indeed changing
      tiltState = reading;  // mark the actual state
      Serial.write(reading);  // print the actual state
      if (tiltState == HIGH) {
        ledState = !ledState;
      }
    }
  }
  // set the LED:
  digitalWrite(ledPin, ledState);

  // save the reading.  Next time through the loop,
  // it'll be the lastTiltState:
  lastTiltState = reading;

  // read the numbers that come from Supercollider and use it to modulate the brightness
  if(Serial.available()) {
    byte val= Serial.read();
    analogWrite(white, val);
  }
  //  fades in and outs, in loops. Maybe there are some gambiarras on these pieces of code, 
  //  but it's working ok.
  if (tiltState == LOW) {  // if the Liebesbrunnen is in a more horizontal position:...
    value = 16*sin(PI/periode*(time/24));
    if(value < 0 || value > 20){
      value = value * -1;
    }
    briG = value;

    if (valueR3 > 30) {
      valueR3 = 128+127*cos(2*PI/periode*(displace-time)/6);
    } 
    else if (valueR3 <= 1) { 
      valueR3 = 30; 
    };
    briR3 = valueR3;

    valueR = map(128+127*sin(PI/periode*(time/1.6)), 0, 255, 63, 192);
    if(valueR <= 0){
      value = value * -1;
    };
    briR = valueR;

    analogWrite(red, briR);  
    analogWrite(green, briG);
    digitalWrite(red2, HIGH);  //roxo
    analogWrite(yellow, 255);
    analogWrite(red3, briR3);  // laranja
    analogWrite(blue2, 8);
    ledState = !ledState;
  }
  else {  // if the Liebesbrunnen is positioned more vertically:...
    if (valueR < 122) {
      valueR = 128-127*cos(2*PI/periode*(displace-time)/6);
    }  
    else if (valueR >= 122) {
      valueR = 122;
    };
    briR3 = valueR;
    analogWrite(red3, briR3);

    briG = 16*sin(2*PI/periode*(time)/8);

    if(briG < 0){
      briG = briG * -1;
    }
    // Serial.println(briG);  // this is for debugging and check how the number are behaving, and
    // MUST be commented while running Supercollider, otherwise every time a variable is printed
    // a Synth will start.
    analogWrite(green, briG);
    analogWrite(red, briR);

  }
}

The Processing part:

import processing.video.*;
Capture video;
int capW = 960;  //match camera resolution here
int capH = 720;
float yoff = 58.0;  // 2nd dimension of perlin noise
float delayTime;
int nDelayFrames = 96; // about 3 seconds
int currentFrame = nDelayFrames-1;
int currentFrame2;
int currentFrame3;
int numPixels;
int[] previousFrame;
PImage frames[];
//PImage framesHV[];
PImage framesV[];
//PImage videoFlipH;
PImage framesH[];
void setup() {
  size(capW, capH);  //set monitor size here
  frameRate(200);
  video = new Capture(this, capW, capH, "/dev/video0", 24);
  video.start();
  frames = new PImage [nDelayFrames];
  //framesHV = new PImage[nDelayFrames];
  framesV = new PImage[nDelayFrames];
  //videoFlipH = new PImage(video.width, video.height);
  framesH = new PImage[nDelayFrames];
  for (int i= 0; i<nDelayFrames; i++) {
    frames[i] = createImage(capW, capH, ARGB);
    //framesHV[i] = createImage(capW, capH, ARGB);
    framesV[i] = createImage(capW, capH, ARGB);
    framesH[i] = createImage(capW, capH, ARGB);
    numPixels = video.width * video.height;
    // Create an array to store the previously captured frame
    previousFrame = new int[numPixels];
    loadPixels();
  }
}
void draw() {
  float xoff = yoff; // Option #2: 1D Noise
  float delayTime = constrain(map(noise(yoff)*10, 1, 7, 1, 96), 1, 96);
  yoff = (yoff+0.01) % nDelayFrames;
  nDelayFrames = int(delayTime);
  if (video.available()) {
    video.read();
    video.loadPixels();

    currentFrame = (currentFrame-1 + nDelayFrames) % nDelayFrames;
    // +30= delay time. must be less than nDelayFrames
    currentFrame2 = (currentFrame +30)%nDelayFrames;
    // +60= delay time. must be less than nDelayFrames
    currentFrame3 = (currentFrame +60)%nDelayFrames;
    for (int x = 0; x < video.width; x++) {
      for (int y = 0; y < video.height; y++) {
        // flip the image horizontally
        framesH[currentFrame].pixels[y*video.width + x] = 
        video.pixels[y*video.width+(video.width-(x+1))];
        // flip the image both horizontally and vertically
        framesV[currentFrame].pixels[y*(video.width) + x] = 
        video.pixels[(video.height - 1 - y)*(video.width) + x];
      }
    }
// desaturate the image
    for (int loc = 0; loc < width*height; loc++) {
      color currColor = framesH[currentFrame].pixels[loc];
      int currR = (currColor >> 16) & 0xFF;
      int currG = (currColor >> 8) & 0xFF;
      int currB = currColor & 0xFF;
      int newR = abs(int(currR+(currG+currB)/2)/2);
      int newG = abs(int(currG+(currR+currB)/2)/2);
      int newB = abs(int(currB+(currG+currR)/2)/2);
      framesH[currentFrame].pixels[loc] = 0xff000000 | (newR << 16) | (newG << 8) | newB;
    }
    updatePixels();
    framesH[currentFrame].updatePixels();
    framesV[currentFrame3].updatePixels();
    //framesH[currentFrame2].updatePixels();
    image(framesH[currentFrame], 0, 0, width, height);
    //try with ADD, DARKEST etc here. see blend help
    blend(framesV[currentFrame3], 0, 0, width, height, 0, 0, width, height, OVERLAY);
    // if I use this next, the framerate drops dramatically. I would like to know why!
    //blend(framesH[currentFrame2], 0, 0, width, height, 0, 0, width, height, OVERLAY);

    updatePixels();
  }
    // println(nDelayFrames);
  //println(int(frameRate));
}

...and the Supercollider

// this is outdated. I'm going to upload the new code soon

s.freeAll;
s.quit;
s.options.memSize = 262144;
s = FreqScope.server.boot;
s.meter;
s.plotTree;
p = SerialPort("/dev/ttyUSB0", 115200, crtscts: true);
p.close;

(
~sourceG = Group.before;
~fxG = Group.tail;
~ampG =Group.after;

SynthDef(\dryS,	{ | /*in=[0,1],*/ out, gate = 1, release = 0.3, pitchDry |
	var  dry, pitchRate, grainSize, pitchDisp, timeDisp, env, sig, tilt=0;
	grainSize = 0.5;
	dry = AudioIn.ar([1,2]);
	env = Env.asr(1, 1, 1, \sin);

	pitchDry = PitchShift.ar( Compander.ar(dry, dry, 0.2, 1.5, 0.3, 0.002, 0.06, 0.75), 
	grainSize, ExpRand(5, 40).round*0.1, LFDNoise3.kr(0.054, 0.5, 2), LFNoise2.kr(0.0313, 
	grainSize-0.05, grainSize).lag3(0.24));

	sig = EnvGen.kr(env, gate, doneAction:2) * pitchDry;
	Out.ar(out, sig);
	};
).add;

SynthDef(\wetS,	{ |in, out, gate = 1|
	var wet, wetEnv, sig, env, winSize = {ExpRand(0.3, 3)}, pitchDisp = {ExpRand(0.2, 2.0)};

	env = Env.asr(1, 1, 2, \sin);
	sig = HPF.ar(In.ar(in, 2), 192);

	wet = Splay.ar(PitchShift.ar(LocalIn.ar(2) + sig, ExpRand(0.3, 3), 
	LFNoise2.kr(0.0334, 0.2, 2).lag3(0.321), ExpRand(0.2, 2.0), ExpRand(0.5, 2.0) ));
	wet = OnePole.ar(wet, 0.47);
	wet = OnePole.ar(wet, -0.079);

	LocalOut.ar(wet * LFNoise2.ar(0.0187, 0.1, 1).lag3(0.587));
	wet = BPeakEQ.ar(wet, 20000, 1, 12);
	wetEnv = EnvGen.kr(env, gate, doneAction:2) * wet;
	Out.ar(out, wetEnv);
};).add;

SynthDef(\revS,	{ |in, out|
	var del, sig, delTime, decTime, ampR;
	sig = In.ar(in, 4);
	del = LocalIn.ar(2)+sig;
	del = Splay.ar(del.reverse*0.3);
	4.do {
		del = AllpassC.ar(del, 12, ExpRand(0.2, 6.0), ExpRand(3, 24));
	};


	ampR = Amplitude.kr(Mix.ar(sig));
	del = Compander.ar(del, Pan2.ar(In.ar(in, 4)), 0.25, 1, 1/4, 0.002, 0.6);

	LocalOut.ar(del * LFNoise2.ar(0.0107, 0.2, 1).lag2(0.3));

	Out.ar(out, del);
};).add;

SynthDef(\amp,	{
	arg in, in2, in3, out;
	var inNum=3, att=0.01, rel=0.2, rate=48,sig,trig,amp;
	in = In.ar(in,2);
	in2 = In.ar(in2,2);
	in3 = In.ar(in3,2);
	sig = Median.ar(11, 
		(BHiPass.ar(Mix.ar(in+(in2/2)+(in3*2)), 
		100, 1).linlin(0.0001, 0.08, 5.0, 255.0).lag3(0.48)));
	trig = Impulse.kr(rate);
	amp = Amplitude.kr(sig, att, rel, inNum.reciprocal);
	SendReply.kr(trig, '/amp', [amp]);  // Get ampl. on the server and send to the language
	Out.ar(out, in+(in2/2)+in3);
	};
).add;
//s.prepareForRecord;
)
(
//s.record;
x = Synth(\revS, [\in, [10, 11, 12, 13], \out, 14], ~fxG, \addToTail);
z = Synth(\amp, [\in, [10, 11], \in2, [12, 13],\in3, [14, 15],\out, 0], ~ampG, \addToTail);

p = SerialPort("/dev/ttyUSB0", 115200, crtscts: true);

OSCdef(\receiveAmp, {|msg|
	var amp = (msg[3]).asInteger;
	var tilt = (msg[0]);
	//p.read(msg[0]).postln;
	p.put(msg[3].linlin(0, 1, 0, 3, 255).asInteger);
}, '/amp' );

Routine.run({
	var byte;
	inf.do{
		while({byte= p.read; byte.notNil}, {
			if (byte == 1)
			{
				if(t.notNil){
					t.set(\gate, 0);
				};
				if(w.notNil){
					w.set(\gate, 0);
				};
				u = Synth(\dryS, [\in, [1,2], \out, 10], ~sourceG);
				v = Synth(\wetS, [\in, [10, 11], \out, 12], ~fxG);
			};
			if(byte == 0)
			{
				if(u.notNil){
					u.set(\gate, 0);
				};
				if(v.notNil){
					v.set(\gate, 0);
				};
				t = Synth(\dryS, [\in, [1,2], \out, 10], ~sourceG);
				w = Synth(\wetS, [\in, [10, 11], \out, 12], ~fxG);
			};
		});
		0.01.wait;
	};
});
CmdPeriod.doOnce({p.close});
)

About

The technical details and resources to make the Liebesbrunnen