ProKontrol

Aus toolbox_interaktion
Wechseln zu: Navigation, Suche

ProKontrol logo.png


Aufbau verschiedener VST-Synthesizer

In unserem Interaktionsprojekt "ProKontrol" haben wir ein OSC-Interface entwickelt, für Steuerung eines digitalen VST-Synthesizers. Die Grundidee lag darin, dass bei der Vielzahl von VST-Synthesizern auf dem heutigen Markt, es doch immer wieder aufs Neue eine Herausforderung ist, sich an die vielen verschiedenen User-Interface zu gewöhnen. Daher wollten wir eine universelle Oberfläche entwickeln, welche die grundlegenden Funktionen eines jeden Software-Synthesizers ansteuern kann.


Programmaufbau

Für den Programmaufbau, haben wir folgende Geräte und Software verwendet:

  • Ein MacBook Pro A (OS X) worauf wir die Software Xcode verwendet haben in Kombination mit openFrameworks und folgenden Addons: ofxGUI, ofxUI, ofxTUIO, ofxOSC.
  • Ein zweites MacBook Pro B (OS X) auf dem wir die Musik-Software Logic Pro X verwendet haben, um den VST-Synthesizer "Monark" laufen zu lassen und anzusteuern.
  • Ein Multitouch Table oder Device um das Interface über OSC zu steuern.


Overview

ProKontrol - Overview


Interface

Das Interface haben wir mit Hilfe der openFrameworks Bibliotheken ofxUI und ofxGUI programmiert. Wir haben ProKontrol dabei, auf die wichtigsten 11 Funktionen des VST-Synthesizers ausgelegt.

ProKontrol - Interface-Design

Controller

  • Cutoff
  • Contour

Fader/Slider

  • Resonance
  • Feedback
  • Attack 1 + 2
  • Decay 1 + 2
  • Glide Time
  • Filter Load
  • Master Volume

Dieses ProKontrol-Interface wird über HDMI auf einen Multitouch Table projiziert oder auf ein Device gestreamt.

Source-Code: Interface Design

// main.cpp

int main( ){
    // set the correct window size of your multitouch device
    ofSetupOpenGL(1920,1080,OF_WINDOW);

    ofRunApp(new ofApp());
    
}

//--------------------------------------------------------------

// ofApp.h

#include "ofxUI.h"
#include "ofxGUI.h"

    ofxUISuperCanvas *gui0;
    ofxUISuperCanvas *gui1;
    
    void guiEvent(ofxUIEventArgs &e);
    
    bool drawPadding;
    float red, green, blue;

//--------------------------------------------------------------

//  ofApp.cpp

void ofApp::setup(){
    
    ofEnableSmoothing();
    ofSetCircleResolution(60);
    
    red = 100; blue = 100; green = 100;
    drawPadding = false;

    gui0 = new ofxUISuperCanvas("PRO KONTROL");
    
    gui0->addSpacer();
    gui0->addLabel("KONTROL 1");
    gui0->addRotarySlider("Cutoff", 0.0, 1, 0.0, 300, 300);
    gui0->setWidgetPosition(OFX_UI_WIDGET_POSITION_RIGHT);
    gui0->addSpacer(200, 0);
    gui0->addLabel("KONTROL 2");
    gui0->addRotarySlider("Contour", 0.0, 1, 0.0, 300, 300);
    
    gui0->setWidgetPosition(OFX_UI_WIDGET_POSITION_DOWN);
    gui0->addSpacer();
    gui0->addLabel("MOAR KONTROLS");
    gui0->addSlider("Resonance", 0.0, 1, 0.0, 100, 400);
    gui0->setWidgetPosition(OFX_UI_WIDGET_POSITION_RIGHT);
    gui0->addSlider("Feedback", 0.0, 1, 0.0, 100, 400);
    gui0->addSlider("Attack", 0.0, 1, 0.0, 100, 400);
    gui0->addSlider("Attack2", 0.0, 1, 0.0, 100, 400);
    gui0->addSlider("Decay", 0.0, 1, 0.0, 100, 400);
    gui0->addSlider("Decay2", 0.0, 1, 0.0, 100, 400);
    gui0->addSlider("GlideTime", 0.0, 1, 0.0, 100, 400);
    gui0->addSlider("FilterLoad", 0.0, 1, 0.0, 100, 400);
    gui0->addSlider("MasterVolume", 0.0, 1, 0.0, 100, 400);
    gui0->setWidgetPosition(OFX_UI_WIDGET_POSITION_DOWN);

    gui0->autoSizeToFitWidgets();
    ofAddListener(gui0->newGUIEvent,this,&ofApp::guiEvent);   
}

// delete gui on exit
void ofApp::exit()
{
    delete gui0;
    delete gui1;
}

// change gui graphics with values
void ofApp::keyPressed(int key)
{
    switch (key)
    {
        case 'p':
        {
            drawPadding = !drawPadding;
            gui0->setDrawWidgetPadding(drawPadding);
        }
            break;
            
        case 'g':
        {
            gui0->toggleVisible();
        }
            break;
        default:
            break;
    }
}


// color of Background
void ofApp::draw()
{
    ofBackground(115, 204, 255);
    
}

}


VST-Synthesizer

In unserem Projekt haben wir uns den übersichtlichen VST-Synthesizer "Monark" von Native Instruments ausgesucht, damit die Interaktion auch deutlich sichtbar ist.

Monark VST-Synthesizer


OSC

Beim bewegen der Regler auf dem Multitouch Device, werden die Touch-Werte durch die Kamera-Bilderkennung ausgelesen und mit Hilfe von ofxTUIO als TUIO-Daten ausgegeben. Diese Daten leiten wir über unsere HOST IP (WLAN) an unseren ReceiverPort "23456" im Programm weiter. (Das Auslesen der TUIO-Daten konnte wegen technischen Problemen mit dem Tisch nie getestet werden, weshalb der zugehörige Code nicht mehr in der Software enthalten ist.)

Programming with Xcode OSC-Settings in Monark

Auf unserem MacBook A werden dann die Regler-Daten ausgewertet und über unseren SenderPort "12345"b(HOST IP/WLAN) als OSC-Nachrichten an das zweite MacBook B gesendet. Der Monark Synthesizer hat einen eigenen OSC Port, womit er automatisch weiß, dass die eingehenden Daten an ihn adressiert sind. Durch eintragen der OSC-Adressen (Bsp.: /Cutoff -> Cutoff) mit mapping auf die Parameter in Monark ist eine einfache Steuerung möglich.


OSC-Sender

Für das Senden von OSC-Nachrichten an unseren VST-Synthesizer "Monark":

// ofApp.h

#include "ofxOsc.h"

#define HOST "141.75.231.134"
#define PORT 12345
#define ReceiverPort 23456

    ofxOscSender sender;

//--------------------------------------------------------------

//  ofApp.cpp

void ofApp::setup(){

    sender.setup(HOST, PORT);

}


void ofApp::guiEvent(ofxUIEventArgs &e)
{
    string name = e.getName();
    int kind = e.getKind();
    ofxOscMessage m;
    if (name=="Cutoff"){
        ofxUIRotarySlider *Cutoff = (ofxUIRotarySlider *) e.getSlider();
        m.setAddress("/Cutoff");
        m.addFloatArg(Cutoff->getValue());
    }
    
    else if (name=="Contour"){
        ofxUIRotarySlider *Contour = (ofxUIRotarySlider *) e.getSlider();
        m.setAddress("/Contour");
        m.addFloatArg(Contour->getValue());
    }
    
    
    else if (name=="Resonance"){
        ofxUISlider*Resonance = (ofxUISlider *) e.getSlider();
        m.setAddress("/Resonance");
        m.addFloatArg(Resonance->getValue());
    }
    
    else if (name=="Feedback"){
        ofxUISlider*Feedback = (ofxUISlider *) e.getSlider();
        m.setAddress("/Feedback");
        m.addFloatArg(Feedback->getValue());
    }
    
    else if (name=="Attack"){
        ofxUISlider*Attack = (ofxUISlider *) e.getSlider();
        m.setAddress("/Attack");
        m.addFloatArg(Attack->getValue());
    }
    
    else if (name=="Attack2"){
        ofxUISlider*Attack2 = (ofxUISlider *) e.getSlider();
        m.setAddress("/Attack2");
        m.addFloatArg(Attack2->getValue());
    }
    
    else if (name=="Decay"){
        ofxUISlider*Decay = (ofxUISlider *) e.getSlider();
        m.setAddress("/Decay");
        m.addFloatArg(Decay->getValue());
    }
    
    else if (name=="Decay2"){
        ofxUISlider*Decay2 = (ofxUISlider *) e.getSlider();
        m.setAddress("/Decay2");
        m.addFloatArg(Decay2->getValue());
    }
    
    else if (name=="GlideTime"){
        ofxUISlider*GlideTime = (ofxUISlider *) e.getSlider();
        m.setAddress("/GlideTime");
        m.addFloatArg(GlideTime->getValue());
    }
    else if (name=="FilterLoad"){
        ofxUISlider*FilterLoad = (ofxUISlider *) e.getSlider();
        m.setAddress("/FilterLoad");
        m.addFloatArg(FilterLoad->getValue());
    }
    
    else if (name=="MasterVolume"){
        ofxUISlider*MasterVolume = (ofxUISlider *) e.getSlider();
        m.setAddress("/MasterVolume");
        m.addFloatArg(MasterVolume->getValue());
    }
        
    sender.sendMessage(m);

 }

OSC-Receiver

Für das Empfangen von TUIO-Daten und OSC-Nachrichten. Leider haben wir diese Interaktion aus technischen gründen nicht mehr testen können, da der Multitouch-Table, welcher bereits die TUIO-Daten auslesen konnte, nicht Einsatzfähig war und wir unsere Schnittstelle nicht mit unserem Programm testen konnten, daher haben wir in unserer Präsentation direkt über ein iPad die Mouse-Event-Daten unseres MacBook A angesteuert. Theoretisch gesehen müsste dennoch das Empfangen von OSC-Nachrichten folgender Maßen aussehen:


// ofApp.h

#include "TuioListener.h"

ofxOscReceiver receiver;
    
    int current_msg_string;
    string msg_strings[NUM_MSG_STRINGS];
    float timers[NUM_MSG_STRINGS];

//--------------------------------------------------------------

// ofApp.cpp

void ofApp::setup(){

    receiver.setup(ReceiverPort);
    current_msg_string = 0;

}


void ofApp::guiEvent(ofxUIEventArgs &e)
{
     for(int i = 0; i < NUM_MSG_STRINGS; i++){
     if(timers[i] < ofGetElapsedTimef()){
     msg_strings[i] = "";
     }
     }
     
     // check for waiting messages
     while(receiver.hasWaitingMessages()){
     // get the next message
     ofxOscMessage r;
     receiver.getNextMessage(r);
     
     // check for mouse moved message
     if(r.getAddress() == "/Cutoff"){
         ofxUISlider*Cutoff = (ofxUISlider *) e.getSlider();
         Cutoff->setValue(r.getArgAsFloat(0.0));
     }
     
     else if(r.getAddress() == "/Contour"){
     r.getArgAsDouble(contourvalue);
     }
     
     else if(r.getAddress() == "/Feedback"){
     r.getArgAsDouble(feedbackvalue);
     }
     
     else if(r.getAddress() == "/Resonance"){
     r.getArgAsDouble(resonancevalue);
     }

     else if(r.getAddress() == "/Attack"){
     r.getArgAsDouble(attackvalue);
     }

     else if(r.getAddress() == "/Attack2"){
     r.getArgAsDouble(attack2value);
     }

     else if(r.getAddress() == "/Decay"){
     r.getArgAsDouble(decayvalue);
     }

     else if(r.getAddress() == "/Decay2"){
     r.getArgAsDouble(decay2value);
     }

     else if(r.getAddress() == "/FilterLoad"){
     r.getArgAsDouble(filterloadvalue);
     }

     else if(r.getAddress() == "/MasterVolume"){
     r.getArgAsDouble(mastervolumevalue);
     }

     else if(r.getAddress() == "/GlideTime"){
     r.getArgAsDouble(glidetimevalue);
     }

     else{

     // unrecognized message: display on the bottom of the screen
     string msg_string;
     msg_string = r.getAddress();
     msg_string += ": ";
     for(int i = 0; i < r.getNumArgs(); i++){

     // get the argument type
     msg_string += r.getArgTypeName(i);
     msg_string += ":";

     // display the argument - make sure we get the right type
     if(r.getArgType(i) == OFXOSC_TYPE_INT32){
     msg_string += ofToString(r.getArgAsInt32(i));
     }
     else if(r.getArgType(i) == OFXOSC_TYPE_FLOAT){
     msg_string += ofToString(r.getArgAsFloat(i));
     }
     else if(r.getArgType(i) == OFXOSC_TYPE_STRING){
     msg_string += r.getArgAsString(i);
     }
     else{
     msg_string += "unknown";
     }
     }

     // add to the list of strings to display
     msg_strings[current_msg_string] = msg_string;
     timers[current_msg_string] = ofGetElapsedTimef() + 5.0f;
     current_msg_string = (current_msg_string + 1) % NUM_MSG_STRINGS;
     // clear the next line
     msg_strings[current_msg_string] = "";
     }
     
}

Kontakt

Andreas Kocevski: Mail
Julian Anders: Mail.

Software