Examples

Create your own sound

Steps

  • Install and start Processing
  • Copy code of next section into processing
  • Modify file name and – via that – the input file (ATTENTION: exact file name format is important!)
  • Run program and enjoy!

Code

// Recommended to use Processing 3.5.4 (V4 and above have no mididbus support)
// Save file in format "sketch_yymmdd_abcd" with "abcd" being a four letter rcsb code to specify molecule (e.g. "1HOC"), save, and run

import themidibus.*;
import java.io.File;
MidiBus myBus;
Table table;
float[] xVal, yVal, zVal,x,y;
int[] xscaled, yscaled, zscaled;
float[] xoriginal, yoriginal, zoriginal;

float xmin, xmax, ymin, ymax, zmin, zmax, wert1, wert2, wert3;
int len;
int[] tone;
int i = 0;
int j = 0;
float border = 0.1;
float dj;
float[] p1a, p1b, p2a, p2b, p3a, p3b;

void setup() {
  size(1000, 1000);
  background(0);
  String scriptPath = System.getProperty("user.dir");
  File currentScript = new File(scriptPath + File.separator + this.getClass().getSimpleName() + ".pde");
  String myString = (currentScript.getName());
  String protein = myString.substring(14,18);
  println(protein);

  myBus = new MidiBus(this, "Real Time Sequencer", "Microsoft MIDI Mapper");
  
  saveData(protein);
  scaleData(protein);
}

void saveData(String protein) {
  String filename = "https://files.rcsb.org/view/" + protein + ".cif";
  String[] lines = loadStrings(filename);

  ArrayList<String> xvalList = new ArrayList<String>();
  ArrayList<String> yvalList = new ArrayList<String>();
  ArrayList<String> zvalList = new ArrayList<String>();

  for (String mainString : lines) {
    String[] words = mainString.split("\\s+");
    if (words.length > 0 && words[0].equals("ATOM") && words[3].equals("CA")) {
      xvalList.add(words[10]);
      yvalList.add(words[11]);
      zvalList.add(words[12]);
    }
  }

  ArrayList<String> csvContentList = new ArrayList<String>();
  csvContentList.add(join(new String[]{"x", "y", "z"}, ","));

  for (int k = 0; k < xvalList.size(); k++) {
    csvContentList.add(join(new String[]{xvalList.get(k), yvalList.get(k), zvalList.get(k)}, ","));
  }

  String outputFilename = "output_" + protein + ".csv";
  saveStrings(outputFilename, csvContentList.toArray(new String[0]));
}

void scaleData(String protein) {
  table = loadTable("output_" + protein + ".csv", "header");
  len = table.getRowCount();

  // Initialize arrays
  xVal = new float[len];
  yVal = new float[len];
  zVal = new float[len];
  p1a = new float[len];
  p1b = new float[len];
  p2a = new float[len];
  p2b = new float[len];
  p3a = new float[len];
  p3b = new float[len];

  for (int row = 0; row < len; row++) {
    xVal[row] = table.getFloat(row, "x");
    yVal[row] = table.getFloat(row, "y");
    zVal[row] = table.getFloat(row, "z");
  }

  xmin = min(xVal);
  xmax = max(xVal);
  ymin = min(yVal);
  ymax = max(yVal);
  zmin = min(zVal);
  zmax = max(zVal);

  for (int row = 0; row < len; row++) {
    wert1 = map(table.getFloat(row, "x"), xmin, xmax, 0,height/2);
    wert2 = map(table.getFloat(row, "y"), ymin, ymax, 0,height/2);
    wert3 = map(table.getFloat(row, "z"), zmin, zmax, 0,height/2);

    xVal[row] = wert1;
    yVal[row] = wert2;
    zVal[row] = wert3;
  }

  dj = TWO_PI / len;
  initializePoints(xVal, p1a, p1b);
  initializePoints(yVal, p2a, p2b);
  initializePoints(zVal, p3a, p3b);
}

void draw() {
  if (i < len - 1) {
    drawSequence(xVal, p1a, p1b, color(255,255,255));
    drawSequence(yVal, p2a, p2b, color(255,127,36));
    drawSequence(zVal, p3a, p3b, color(139,90,43));
    i++;
 
   } else if (i == len - 1) {
    // Draw a closing line back to zero or to a specified point
    drawClosingLines(xVal, p1a, p1b, color(255,255,255));
    drawClosingLines(yVal, p2a, p2b, color(255,127,36));
    drawClosingLines(zVal, p3a, p3b, color(139,90,43));
    i++; // Increment to avoid re-entering this block
  }
  
if (mousePressed == true) { saveFrame("pic.png");};
saveFrame("picfinal.png");
}

void initializePoints(float[] sequence, float[] x, float[] y) {
  for (int j = 0; j < len; j++) {
    x[j] = width/2 + sequence[j] * cos(j * dj);
    y[j] = width/2 + sequence[j] * sin(j * dj);
  }
}

void drawClosingLines(float[] sequence, float[] x, float[] y, color strokeColor) {
  stroke(strokeColor);
  line(x[len - 1], y[len - 1], x[0], y[0]); // Connect the last point back to center or origin
}

void drawSequence(float[] sequence, float[] x, float[] y, color strokeColor) {
  int tone = (int) sequence[i];
  
  myBus.sendNoteOn(1, tone*127/width*2 , tone);
  
  float r = random(1, 3);
  delay(int(r) * 80);
 
  stroke(strokeColor);
  x[i] = width/2 + sequence[i] * cos(i * dj);
  y[i] = width/2 + sequence[i] * sin(i * dj);
  line(x[i], y[i], x[i+1], y[i+1]);
}

How the sounds were made

During an artist residency at the Springer Lab at Constructor University Bremen – exploring biomolecules such as proteins and viruses in various contexts – a computer program was developed to transform atomic positions of biomolecules into MIDI sounds while simultaneously visualizing the conversion through flower-like polar coordinate shapes.

The input data consisted of the x, y, and z coordinates of all atoms of a specified molecule, retrieved from the molecular database RCSB PDB. These coordinates generated both a three-voice sound and a visualization looking like circular canons.

That way, each biomolecule produces a unique sound and shape, inviting unconventional perspectives to compare, celebrate, and appreciate nature’s endlessly varied yet intrinsically related structures.