3.5 MIDI

Spectral Canon for Conlon Nancarrow (James Tenney, 1974) Digitale Reproduktion

Midi ist ein Standard zum Austausch von Daten zwischen musikalischen Klangerzeugern, Keyboards und Sequenzern. Midi wurde 1982 eingeführt und später von DAWs (Digital Audio Workstations) wie Cubase, Logic, Fruity Loops, Ableton Live, etc. übernommen. Es lassen sich damit sowohl digitale Instrumente im Laptop-Sequenzer (VST-Plugins) sowie nach wie vor auch externe Geräte ansteuern und automatisieren.

1. MIDI-Geräte aus Processing ansteuern

Derzeit ist in den Core-Libraries von Processing keine Midi-Funktionalität implementiert. Möchte man Midi-Signale an externe Klangerzeuger senden, so sollte man daher die Library The MidiBus über den Contribution Manager von Processing hinzufügen.

Beispiel 3-8


import themidibus.*; 

MidiBus gaiaBus; 

int channel = 0;
int pitch = 64;
int velocity = 64;

void setup() {
  size(800, 600);
  gaiaBus = new MidiBus(this, -1, 1);
}

void draw() {
}

void mouseMoved(){  
  pitch = int(map(mouseX, 0, width, 50, 60));   
  float c = map(mouseX, 0, width, 0, 255);   
  background(c);   
  gaiaBus.sendNoteOn(channel, pitch, velocity);    
  gaiaBus.sendNoteOff(channel, pitch, velocity);   
}

2. MIDI-Controller zur Eingabe verwenden

MIDI-Controller können umgekehrt als Interface dienen um Processing anzusteuern. So könnte man beispielsweise Parameter von Live-Visuals nicht nur durch den Sound, sondern auch durch manuelle Eingriffe steuern.

Der Controller AKAI MIDIMIX bietet beispielsweise ein Interface aus Schiebereglern und Drehknöpfen, das an die Haptik eines Mischpultes angelehnt ist. Indem man seinen Sketch entsprechend programmiert können die Regler und Knöpfe jedoch ganz nach Belieben für andere Funktionen verwendet werden.

Der folgende Sketch verwendet die drei Drehregler von Kanalzug 1 des AKAI MIDIMIX, um die RGB-Werte des Hintergrunds einzustellen. Mit den Schiebereglern der ersten drei Kanalzüge kann die Größe und Position des Objekts gesteuert werden.

Beispiel 3-9


import themidibus.*; 

MidiBus midiMixBus; 

final byte hueKnob = 16;
final byte satKnob = 17;
final byte brightKnob = 18;
final byte xSlider = 19;
final byte ySlider = 23;
final byte zSlider = 27;
final byte xRotSlider = 31;
final byte yRotSlider = 49;
final byte zRotSlider = 53;

float hue = 0;
float sat = 100;
float bright = 255;
float boxX = width/2;
float boxY = height/2;
float boxZ = 0;
float rotX = 0;
float rotY = 0;
float rotZ = 0;

void setup() {
  size(800, 600, P3D);
  midiMixBus = new MidiBus(this, "MIDI Mix", 1);
  colorMode(HSB);
}

void draw() {
  background(hue, sat, bright);
  translate(boxX, boxY, boxZ);
  rotateX(rotX);
  rotateY(rotY);
  rotateZ(rotZ);
  box(200);
}

void rawMidi(byte[] data) { 
  switch(data[1]){
     case hueKnob:
       hue = map(data[2], 0, 127, 0, 255);
       break;
     case satKnob:
       sat = map(data[2], 0, 127, 0, 255);
       break;
     case brightKnob:
       bright = map(data[2], 0, 127, 0, 255);
       break;
     case xSlider:
       boxX = map(data[2], 0, 127, 0, width);
       break;
     case ySlider:
       boxY = map(data[2], 0, 127, 0, height);
       break;
     case zSlider:
       boxZ = map(data[2], 0, 127, -400, 0);
       break;
     case xRotSlider:
       rotX = map(data[2], 0, 127, 0, 2);
       break;
     case yRotSlider:
       rotY = map(data[2], 0, 127, 0, 2);
       break;
     case zRotSlider:
       rotZ = map(data[2], 0, 127, 0, 2);
       break;
  }
}

3. SimpleSynth & Midi

Unser einfacher Synthesizer aus Kapitel 3.2 kann nun so erweitert werden, dass er von einem externen Midi-Keyboard gespielt werden kann. Hierzu müssen die eingehenden Midi-Noten (dabei handelt es sich um Werte zwischen 0 und 127) in Frequenzen für die Oszillatoren umgerechnet werden:

Midi-Note in Hertz:
frequenz = pow((midinote-69)/12) * 440;

Beispiel 3-10: simpleSynth mit MIDI-Steuerung


import controlP5.*;
import processing.sound.*;
import themidibus.*;

ControlP5 cp5;
MidiBus midi; 

int count; 
int Duration = 1000;

boolean sinoPlay = false;
boolean sawoPlay = false; 
boolean squaroPlay = false;
boolean delayDo = false;
boolean envelopeDo = false;
float FreqSin, FreqSaw, FreqRect;
float attack, sustain, susLevel, release;
float Sinus, Saegezahn, Rechteck;

SinOsc sino;
SawOsc sawo;
SqrOsc squaro;
WhiteNoise noise;
BandPass bandPass;
Env envelope;
Reverb reverb;

void setup() {
  size(500,200);
  background(0);

  sino = new SinOsc(this);
  sawo = new SawOsc(this);
  squaro = new SqrOsc(this);
  noise = new WhiteNoise(this);
  bandPass = new BandPass(this);
  envelope  = new Env(this);
  reverb = new Reverb(this);
  cp5 = new ControlP5(this);
  midi = new MidiBus(this, "Plug 1", -1);

  cp5.addBang("bang")
     .setPosition(10, 10)
     .setSize(40, 20)
     .setTriggerEvent(Bang.RELEASE)
     .setLabel("Play")
     ;

  cp5.addSlider("Duration")
     .setPosition(100,10)
     .setSize(100, 20)
     .setRange(0, 2000) // values can range from big to small as well
     .setValue(1000)
     .setNumberOfTickMarks(5)
     .setSliderMode(Slider.FLEXIBLE)
     ;

   // Oszillatoren   
   cp5.addToggle("Sinus")
     .setPosition(310,10)
     .setSize(40,20)
     .setValue(true)
     ;
   cp5.addToggle("Saegezahn")
     .setPosition(360,10)
     .setSize(40,20)
     .setValue(false)
     ;
   cp5.addToggle("Rechteck")
     .setPosition(410,10)
     .setSize(40,20)
     .setValue(false)
     ;
   cp5.addSlider("FreqSin")
     .setPosition(310,50)
     .setSize(40, 20)
     .setRange(16, 2000) 
     .setValue(440)
     //.setSliderMode(Slider.FLEXIBLE)
     ;
   cp5.getController("FreqSin").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
   cp5.addSlider("FreqSaw")
     .setPosition(360,50)
     .setSize(40, 20)
     .setRange(16, 2000) 
     .setValue(440)
     .setSliderMode(Slider.FLEXIBLE)
     ;
   cp5.getController("FreqSaw").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
   cp5.addSlider("FreqRect")
     .setPosition(410,50)
     .setSize(40, 20)
     .setRange(16, 2000) 
     .setValue(440)
     .setSliderMode(Slider.FLEXIBLE)
     ;
    cp5.getController("FreqRect").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);

   //Envelope
   cp5.addToggle("envelopeOn")
     .setPosition(10,160)
     .setSize(40,20)
     .setValue(false)
     .setLabel("Env on");
     ;

   cp5.addSlider("attack")
     .setPosition(60,160)
     .setSize(40, 20)
     .setRange(0.01, 0.5) 
     .setValue(0.1)
     .setSliderMode(Slider.FLEXIBLE)
     ;
     cp5.getController("attack").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);

   cp5.addSlider("sustain")
     .setPosition(110,160)
     .setSize(40, 20)
     .setRange(0.01, 0.5) // values can range from big to small as well
     .setValue(0.2)
     .setSliderMode(Slider.FLEXIBLE)
     ;
     cp5.getController("sustain").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);

   cp5.addSlider("susLevel")
     .setPosition(160,160)
     .setSize(40, 20)
     .setRange(0, 1) // values can range from big to small as well
     .setValue(0.5)
     .setSliderMode(Slider.FLEXIBLE)
     ;
     cp5.getController("susLevel").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);

   cp5.addSlider("release")
     .setPosition(210,160)
     .setSize(40, 20)
     .setRange(0.01, 0.5) // values can range from big to small as well
     .setValue(0.1)
     .setSliderMode(Slider.FLEXIBLE)
     ;
     cp5.getController("release").getCaptionLabel().align(ControlP5.LEFT, ControlP5.BOTTOM_OUTSIDE).setPaddingX(0);
}      

void draw() {
    fill(20,20,50);
    noStroke();
  // Zeichnen des Hüllkurven-Diagramms
    rect(10,50,280,100);
    stroke(255);
    float sp = 150-susLevel*100;
    line(10,150, attack*200+10, sp);
    line(attack*200+10,sp, attack*200+sustain*200+10,sp);
    line(attack*200+sustain*200+10, sp, attack*200+sustain*200+release*200+10,149);

  // Ton beenden
    if (millis() > count+Duration && envelopeDo == false){
      if (envelopeDo == false){
        if (sinoPlay){ sino.stop(); }
        if (sawoPlay){ sawo.stop(); }
        if (squaroPlay){ squaro.stop(); }
      }
    }
}

void bang() {
  if (sinoPlay){
       if (envelopeDo){
         sino.play(FreqSin, 0.5);
         envelope.play(sino, attack, sustain, susLevel, release);
       }else{
         sino.stop();
         sino.play(FreqSin, 0.5);
       }
  }
  if(sawoPlay){
       if (envelopeDo){
         sawo.play(FreqSaw, 0.5);
         envelope.play(sawo, attack, sustain, susLevel, release);
       }else{
         sawo.stop();
         sawo.play(FreqSaw, 0.5);
       }

  }
  if(squaroPlay){   
       if (envelopeDo){
         squaro.play(FreqRect, 0.5);
         envelope.play(squaro, attack, sustain, susLevel, release);
       } else{
         squaro.stop();
         squaro.play(FreqRect, 0.5);
       } 
  }

  count = millis();
}

void rawMidi(byte[] data) {
  int midinote = data[1];
  //text(midinote, 300, 100);
  float envDuration = (attack+sustain+release)*1000;

  if(data[2] == 127){       // nur spielen, wenn NoteON (127) nicht bei NoteOff (64)
    float frequenz = 440 * pow(2.0,(midinote-69.0)/12.0);
    cp5.getController("FreqSin").setValue(frequenz);  
    cp5.getController("FreqSaw").setValue(frequenz); 
    cp5.getController("FreqRect").setValue(frequenz); 
    Sinus = frequenz;
    Saegezahn = frequenz;
    Rechteck = frequenz;
    bang();
  }
}

void envelopeOn(boolean flag){
  if(flag){
     envelopeDo = true;
  }else{
     envelopeDo = false;
  }
}

void delayOn(boolean flag){
  if(flag){
     delayDo = true;
  }else{
     delayDo = false;
  }
}

void Sinus(boolean flag){
  if(flag){
     sinoPlay = true;
  }else{
     sinoPlay = false;
  }
}

void Saegezahn(boolean flag){
  if(flag){
     sawoPlay = true;
  }else{
     sawoPlay = false;
  }
}

void Rechteck(boolean flag){
  if(flag){
     squaroPlay = true;
  }else{
     squaroPlay = false;
  }
}