visuals

The visuals at the under the concrete gazebo nights are generated by a Windows laptop running the software Processing. This outputs to the projector in the vemue via HDMI.

 

https://processing.org/

 

There's a wacom pen tablet connected to the laptop via USB - this tablet is on a table on which there's also a microphone and a USB audio interface. These are connected to the laptop using a USB hub.

 

The person drawing sits at this table and can see the laptop screen projected on the back wall of the room behind the people playing.

 

The cursor from the tablet pen appears as a cross on the screen and the pen mark they draw when dragging the pen across the tablet is affected by the sound received by the microphone.

 

Roughly speaking, frequency of the sound affects colour, and volume of the sound affects size of the pen mark.

 

The gain, or sensitivity, of the microphone can be adjusted using a knob on the audio interface.

 

There is also a USB MIDI controller on the table with five turnable MIDI CC (control change) knobs which the Processing software reads. These CC values are used to affect various parameters to do with the colour and size of the pen mark being drawn.

 

The software uses a Fast Fourier Transform to divide the sound the microphone receives into three frequency bands - low, mid and high. These bands are mapped to the red, green and blue values which comprise the colour of the pen mark being drawn on the screen.

 

The boundary points between low-mid and mid-high frequencies can be altered with the MIDI CC knobs. This allows the person drawing to have some control over the colour of the pen mark they are drawing with, whilst still allowing the frequency of the sound in the room to affect that colour.

 

In addition, the amount by which the volume of the sound increases the size of the pen mark can be adjusted using a knob on the MIDI controller.

 

Each pixel co-ordinate drawn on the screen is stored in an array by the software. If a new pen mark being drawn is close in proximity to a previously stored co-ordinate, straight lines are drawn linking the old co-ordinates to the new one.

To run the software, download and install Processing:

https://processing.org/

 

The code is below:

FFT fft;
AudioIn in;
Amplitude amp;
int bands = 2048;
float[] spectrum = new float[bands];
boolean drawFFT = false;

int MidiCC0 = 0;
int MidiCC1 = 0;
int MidiCC2 = 0;
int MidiCC3 = 0;
float specLowStart = 0.02; // 3%
float specMidStart = 0.12;  // 12.5%
float specHiStart = 0.25;   // 25%
float specLow = specLowStart;
float specMid = specMidStart;  
float specHi = specHiStart;  
float scoreLow = 0;
float scoreMid = 0;
float scoreHi = 0;
import themidibus.*;
MidiBus myBus;
int pointBufferSize = 30000;
PVector[] pointHistory = new PVector[pointBufferSize];

int pointHistoryIndex = 0;
int distthresh = 56;
float myStrokeWeight = 0;
float currentAmp;

 

void setup() {
 
  fullScreen(P2D, 2);

  background(0);
  strokeWeight(0.4);
  stroke(0, 15);
  smooth();
  cursor(CROSS);
 
  MidiBus.list();  
  // Connect to one of the devices
  myBus = new MidiBus(this, 1, 1);
 
  // Create an Input stream which is routed into the Amplitude analyzer
  fft = new FFT(this, bands);
  amp = new Amplitude(this);
  in = new AudioIn(this, 0);  
  // start the Audio Input
  in.start();
  amp.input(in);  
  // patch the AudioIn
  fft.input(in);

     

void draw() {
 
  currentAmp = amp.analyze();
  currentAmp *= currentAmp;
  myStrokeWeight = (currentAmp * 1000) + 1 + MidiCC3;
  if(myStrokeWeight > 200)
    myStrokeWeight = 200;
   
  fft.analyze(spectrum);

  scoreLow = 0;
  scoreMid = 0;
  scoreHi = 0;
 
  strokeWeight(5);
  stroke(100, 100, 255);
  for(int i = 0; i < bands*specLow; i++)
  {
    scoreLow += spectrum[i];
  }
  stroke(100, 255, 100);
  for(int i = (int)(bands*specLow); i < bands*specMid; i++)
  {
    scoreMid += spectrum[i];
  }
  stroke(255, 100, 100);
  for(int i = (int)(bands*specMid); i < bands * specHi; i++)
  {
    scoreHi += spectrum[i];
  }
 
  scoreLow = scoreLow * 255 + MidiCC2;
  scoreMid = scoreMid * 255 + MidiCC2;
  scoreHi = scoreHi * 255 + MidiCC2;
  if(scoreLow > 255)
    scoreLow = 255;
  if(scoreMid > 255)
    scoreMid = 255;
  if(scoreHi > 255)
    scoreHi = 255;
    
  if(drawFFT){
    strokeWeight(50);
    stroke(scoreLow, scoreLow, scoreLow);
    line(0, 25, 50, 25);
    stroke(scoreMid, scoreMid, scoreMid);
    line(100, 25, 150, 25);
    stroke(scoreHi, scoreHi, scoreHi);
    line(200, 25, 250, 25);
    stroke(scoreHi, scoreMid, scoreLow);
    line(300, 25, 600, 25);
  }
}

 

void addPoint(int xp, int yp) {

  PVector vCurrent  = new PVector();
  PVector vPrev;
 
  vCurrent.x = xp;
  vCurrent.y = yp;
 
  if(pointHistoryIndex == pointBufferSize)
    pointHistoryIndex = 0;
  if(pointHistoryIndex > 0)
    vPrev = pointHistory[pointHistoryIndex];
    
  pointHistory[pointHistoryIndex++] = vCurrent;

  stroke(scoreHi, scoreMid, scoreLow, 15);
 
  strokeWeight(myStrokeWeight);
  if(pointHistoryIndex > 1)
    line(xp, yp, pointHistory[pointHistoryIndex-2].x, pointHistory[pointHistoryIndex-2].y);
 
  for (int p=0; p < pointHistoryIndex; p++) {
    vPrev = pointHistory[p];
    if ((vCurrent.dist(vPrev) < distthresh) && (random(1) < 0.5) && (myStrokeWeight < 10)) {
      line(vCurrent.x, vCurrent.y, vPrev.x, vPrev.y);
    }
  }
}

 

void mouseDragged() {
  addPoint(mouseX, mouseY);
}

 

void controllerChange(int channel, int number, int value) {
  // Here we print the controller number.
  println(channel, number, value);
  if(number == 50)
    specLow = ((float)value / 127.0) * specLowStart;
  if(number == 51)
    specMid = ((float)value / 127.0) * specMidStart;
  if(number == 52)
    MidiCC2 = value;
  if(number == 53)
    MidiCC3 = value;
}

 

void keyPressed() {
 
  if (key == ' ') {
    background(0);
    pointHistoryIndex = 0;
  }
  if (key == 's') {
    println("key is " + key);
    saveFrame();
  }
  if (key == 'f'){
    drawFFT = !drawFFT;
    if(!drawFFT){
      strokeWeight(50);
      stroke(0, 0, 0);
      line(0, 25, 650, 25);
    }
  }
    
  if (key == 'x') {
    println("key is " + key);
    saveFrame();
    exit();
  }
}