Das Tool vvvv ist ein weit verbreitetes Werkzeug, um interaktive Systeme in Verbindung mit Multimedia (2D, 3D und Film) schnell zu entwickeln. Das System läuft ausschließlich unter Windows und ist für nicht-kommerzielle Zwecke kostenlos. Mehr zum Hintergrund von vvvv finden Sie auf Wikipedia.
vvvv kann sowohl zum Prototyping von neuen Interfaces, insbesondere mit aktuellen Sensoren (Kinect, leap, myo) verwendet werden, als auch als Plattform für künstlerische Installationen und Performances. Unter vvvv.org finden Sie eine Galerie mit vielen Beispielen.
vvvv wird "grafisch" über Flow-Diagramme programmiert. Priorität hat der Fluss der Daten. Daher ist vvvv für hochgradig reaktive Systeme geeignet, wo Inputdaten (Sensorik) kontinuierlich auf grafischen oder auditiven Output gemappt wird. Als Neuling sollte man sich Zeit nehmen, die Grundkonstrukte zu verstehen, bevor man komplexere Systeme baut.
Als Beispiel für ein künstlerisches vvvv-Projekt sei das studentische Projekt Reäktor (Hochschule Augsburg) genannt, wo Tanz und interaktive Grafik mit Hilfe von vvv und zwei Kinects kombiniert wurden.
10.1 Flow-based Programming
Das Tool vvvv folgt einem komplett anderem System, als Sie es vom traditionellem Programmieren in z.B. Java oder C kennen. Dieses System nennt sich datenstromorientierte Programmierung (engl. dataflow programming oder flow-based programming).
Traditionelle Programmiersprachen gehen zeilenweise (sequentiell) durch den Code. Der Programmierer kann den Kontrollfluss durch Konstrukte wie Schleifen und bedingte Anweisungen ändern. Daten werden in Variablen und komplexeren Datenstrukturen gespeichert.
In der datenstromorientierten Programmierung steht der Datenfluss im Vordergrund. Der Programmierer schließt vorgefertigte Module mit Datenleitungen zusammen. Die Module können generieren, rechnen, transformieren oder ausgeben und operieren als Black Boxes, d.h. der Programmierer kennt die Funktion des Moduls, aber nicht, wie diese Funktion realisiert wird. Der geschickte Zusammenschluss vieler Module ergibt das neue Programm.
10.1.1 Knoten und Pins in vvvv
Daten fließen zum Beispiel von Modul A (wo die Daten entstehen) zu Modul B (wo die Daten verarbeitet werden) zu Modul C (wo die Daten ausgegeben werden). Ein solches Modul nennen wir im folgenden Knoten (engl. node). Um die Datenleitungen an Knoten anzuschließen, haben Knoten Eingabe-Pins und Ausgabe-Pins, in Analogie zu den Mikrochips in der Computer-Hardware. Mikrochips haben kleine Metallfüße, Pins genannt, die den Kontakt zu den Leiterbahnen auf den Platinen herstellen. In vvvv kann an jeden Eingabe-Pin genau eine Datenleitung angeschlossen werden, aus einem Ausgabe-Pin können mehrere Datenleitungen ausgehen. In vvvv sind die Eingabe-Pins immer oben und die Ausgabe-Pins immer unten.
Es gibt Knoten, die nur Daten erzeugen, sogenannte Quellen. Diese haben nur Ausgabe-Pins. Denken Sie zum Beispiel an einen Knoten, der die Position Ihrer Maus erfasst oder an einen Knoten, der eine Datei einliest.
Es gibt auch Knoten, die nur Daten erhalten, sogenannte Senken. Diese haben nur Eingabe-Pins. Denken Sie Knoten zur grafischen Ausgabe oder zur Wiedergabe von Audio.
10.2 Eingabe, Verarbeitung, Ausgabe
Ein Programm in vvvv heißt Patch.
Die IOBox erschaffen Sie per Rechtsklick. Wenn Sie nichts weiter machen, bekommen Sie eine Value IOBox, die einen Gleitkommawert (float) enthält. Den Wert können Sie per Doppelklick und Tastatureingabe oder per Rechtsklick + Drag (hoch/runter) ändern. Erzeugen Sie insgesamt drei IOBox-Knoten. Zum Beispiel, indem Sie den ersten Knoten markieren und mit STRG-d zweimal duplizieren.
Jetzt erzeugen wir einen Knoten namens + (Value). Dazu führen Sie einen Linksdoppelklick aus (Sie sehen eine umfangreiche Auswahlliste) und tippen + und anschließend Enter.
Jetzt verknüpfen Sie die vier Knoten wie folgt:
Sobald Sie die Werte der oberen IOBox-Knoten ändern, ändert sich der Wert der unteren IOBox. Dies veranschaulicht die grundlegende Arbeitsweise von vvvv. Es gibt Knoten, die Daten produzieren (die oberen zwei Knoten), diese nennt man Quellen. Es gibt Knoten, die Daten empfangen, ohne sie weiterzugeben, das sind die sog. Senken. Daten fließen also von den Quellen zu den Senken. Dabei arbeiten alle Knoten ständig gleichzeitig bzw. nach Bedarf, also sobald sich die Eingabedaten ändern.
Wenn Sie eine IOBox erschaffen, haben Sie nach dem Rechts-Doppelklick mehrere Optionen. Zum Beispiel können Sie eine ganzzahlige (Integer) IOBox erschaffen. Dann fehlen die Nachkommastellen:
Sie können mit IOBox (Toggle) auch einen Schalter, also einen booleschen Wert, einbauen. Das Ein- und Ausschalten erfolgt mit Rechtsklick. Analog zu den arithmetischen Operationen gibt es boolesche Verknüpfungen.
Mit IOBox (String) erzeugen Sie Strings. Beachten Sie, dass Sie alle Knoten größer ziehen können, indem Sie mit der Maus an der linken unteren Ecke ziehen. Hier verwenden wir den Knoten Add (String), denn Sie auch mit dem Pluszeichen suchen können, und der eine String-Konkatenation durchführt. Am dritten Eingabe-Pin (Intersperse) haben wir den Wert Space eingestellt.
Der Knoten IOBox (Bang) ist ein boolescher Wert, der beim Anklicken genau 1x auf true gesetzt wird und dann wieder auf false.
Im Beispiel sehen wir den Knoten LFO (steht für Low Frequency Oscillator). Dies ist ein Zähler, der jede Sekunde um eins hochgezählt wird, wie man am Ausgabeknoten sieht. Dieser Knoten kann auf 0 zurückgesetzt werden, indem man true am entsprechenden Input-Pin anlegt. Hier kommt unser Bang ins Spiel. Wird der Bang als Eingabe verwendet (auch Rechtsklick) und ausgelöst, beginnt der Zähler wieder bei 0.
Wird er als Ausgabe verwendet, kann der Knoten bestimmte Events anzeigen. Wie oben zählt der Ausgabe-Pin von LFO jede Sekunde um eins hoch. Der Knoten Change gibt bei jeder Änderung der Eingabe ein kurzes true-Signal aus, das vom Bang angezeigt wird.
10.2.1 Hilfe-Patches (F1)
Zu den meisten Knoten gibt es eine sehr gute Dokumentation in Form eines Beispiel-Patches. Diesen ruft man ganz einfach auf, indem man einen Knoten markiert (Linksklick) und F1 drückt. Das Betrachten und Experimentieren mit Hilfe-Patches ist einer der besten Wege, vvvv zu lernen.
10.3 Grafik und Animation
10.3.1 Grafikausgabe (Renderer)
Eine besondere Art der Senke ist ein Renderer (EX9). Dieser Knoten empfängt ein Bild. Mit ALT-2 betten Sie das Renderer-Fenster in das Patch ein. So ein Bild kann z.B. von einem Quad (DX9) produziert werden. Um das Quadrat zu bewegen, geben wir dem Quad noch eine Transform (2d) mit. Hier belegen wir die Eingabe-Pins für ScaleX und ScaleY mit demselben Wert (IOBox) und TranslateX und TranslateY mit je eigenen IOBoxen.
Sie sehen, dass ein Ausgabe-Pin auch auf mehrere Pins verteilt werden kann. Umgekehrt kann ein Eingabe-Pin immer nur einen einzigen Input bekommen (sonst gäbe es Unklarheiten, welcher Eingabewert denn nun gelten soll).
Vielleicht noch ein paar Tipps zur GUI:
- Pins verbinden Sie mit Linksklick auf Pin 1, dann Linksklick auf Pin 2
- Ein Patch-Fenster schließen Sie mit STRG-w
- Alle Fenster (und vvvv) beenden Sie mit ALT-F4
- Mit STRG-p erzeugen Sie ein neues Patch
- Mit STRG-s speichern Sie das aktuelle Patch
- Mit STRG-d duplizieren Sie den markierten Knoten
Mit dem Knoten Group können Sie mehrere Knoten an den Renderer anschließen.
Das Koordinatensystem sieht wie folgt aus (von den vvvv Seiten):
Mehr zum Thema Grafik und Rendering: http://vvvv.org/documentation/dx9-rendering
10.3.2 IOBox (Color)
Es gibt eine weitere IOBox (Doppel-Rechtsklick) namens Color. Diese kann als Input für zu zeichnende Objekte wie Quad verwendet werden. Der Color-Knoten arbeitet im HSV-Farbraum (hue, saturation, value) und kann wie folgt per Maus eingestellt werden:
- Rechtsklick + links/rechts: hue (Farbwert)
- Rechtsklick + hoch/runter: value (Helligkeit)
- STRG + Rechtsklick + hoch/runter: saturation (Sättigung)
- SHIFT + Rechtsklick + hoch/runter: alpha (Transparenz)
10.3.3 Mauseingabe
Im folgenden Patch folgt das Rechteck der Maus. Wir sehen Mouse (Devices Window) als Quellknoten, der die Mausposition innerhalb des Renderer-Fensters ausgibt. Der Ausgabe-Pin enthält zwei Werte gleichzeitig, also einen Vektor. Da der Knoten Transform (2d) aber zwei separate Eingaben benötigt (TranslateX und TranslateY), müssen wir diesen Vektor in zwei Werte aufsplitten. Dies leistet der Knoten Vector (2d split), der einen 2d-Vektor auf zwei Leitungen verteilt.
10.3.4 Zeit, Frames und Verzögerung
Die aktuelle Uhrzeit bekommt man mit dem Knoten CurrentTime (Astronomy).
Nützlicher ist oft die Zeit in Sekunden, die seit einem bestimmten Zeitpunkt vergangen ist. Hier hilft der Knoten Stopwatch, den man ein-, ausschalten und zurücksetzen (Reset) kann. Ähnlich funktioniert FrameCounter, mit dem Unterschied, dass hier die Frames statt der Sekunden gezählt werden (ein "Frame" meint hier ein rechnerisches/grafisches Update des Programms; eine vvvv-Anwendung läuft erfahrungsgemäß mit 40-60 Frames pro Sekunde).
Mit dem Knoten Delay (Animation) kann man einen Werte-Input um eine vorgegebene Zeit verzögern.
10.3.5 Animation mit Filtern
Eine Möglichkeit der (interaktiven) Animation ist es, einen Zielwert vorzugeben (z.B. die Mausposition) und über einen Knoten den Weg dorthin berechnen zu lassen, d.h. das Rechteck erreicht den Zielpunkt etwas später und wird dorthin animiert. Zu diesem Zweck hat man eine Reihe von sogenannten Filtern zur Verfügung: LinearFilter, Oscillator, Damper, Decay, DeNiro.
LinearFilter sorgt dafür, dass in einer vorgegebenen Zeit (Filter Time, hier: 0,2 s) der Zielwert (Go To Position) angenommen wird.
Oscillator funktioniert ähnlich wie LinearFilter, jedoch pendelt der Wert sich um den Zielwert ein, es wirkt im Beispiel als wäre das Rechteck mit einer Feder am Mauszeiger fixiert. Eine Filter Time von 4 s ist hier angemessener.
10.3.6 Animation mit LFO
Eine vom Benutzer unabhängige Animation erreicht man mit dem Knoten LFO (Animation). Dieser Knoten durchläuft ständig das Interval 0 bis 1 in einem vorgegebenen Zeit (Default = 1 s). Im Beispiel mappen wir das auf das Interval -1 bis 1.
10.4 Spreads und Queue
Ein Spread ist eines der wichtigsten Grundkonzepte von vvvv. Ein Spread ist eine Art Array, d.h. so etwas wie eine Bündelung von mehreren Datenleitungen.
Der Knoten LinearSpread erzeugt N Werte (Spread Count), die den angegebenen Werteraum (Width) gleichmäßig unterteilen.
Eine wichtige Eigenschaft von Spreads ist, dass sie Knoten vervielfachen. Sie wirken sozusagen ansteckend. Wenn ich an den Eingabe-Pin eines Knotens einen Spread mit fünf Werten anlege, dann verfünffacht sich dieser Knoten und gibt ebenfalls einen Spread der Größe fünf aus. Es ist so, als ob es fünf Datenleitungen gäbe und fünf separate Knoten.
Im folgenden Beispiel wird ein 5-Spread in einen Additionsknoten gegeben, der als zweiten Input eine 10 erhält. Dieser Knten verfünffacht sind und gibt ebenfalls einen 5-Spread aus.
Weitere Spread-Knoten sind I (Spreads), der eine Aufzählung von z.B. 0...5 generiert, und RandomSpread, welcher eine Anzahl von Zufallszahlen herstellt.
Hat ein Knoten eine Bin Size (wie z.B. GetSlice), so bedeutet das, dass das Spread nochmal aufgeteilt wird. Zum Beispiel das Spread 1, 2, 3, 4, 5, 6. Bei Bin Size 3 gäbe es nur noch zwei Indices, nämlich 0 für 1, 2, 3 und 1 für 4, 5, 6.
Strings in einem Spread kann man mit dem + (String Spectral) zu einem String (evtl. mit Trennzeichen wie Kommas) zusammenfassen.
Eine häufige Anwendung ist das Vervielfachen von grafischen Objekten. Im folgenden Beispiel wird dazu aber nicht zuerst das Objekt (Quad) vervielfacht, sondern die Transformation, da diese ja die Eingabe darstellt. Hier wird ein 2-Spread weitergeleitet.
Mit GetSlice (Spreads) kann man auf einzelne Werte eines Spreads zugreifen, sollte das nötig sein.
Ein CircularSpread erzeugt x/y-Werte entlang eines Kreises mit Mittelpunkt (0, 0) und dem Durchmesser Width. Die Phase bestimmt, mit welchem Startwinkel die Punkte angeordnet sind. Im Beispiel werden über ein LFO an Phase die Quads rotiert.
Mit GetSlice (Node) kann man auf einen Spread zugreifen, der nicht aus Werten, sondern z.B. aus Objekten besteht. Im folgenden Beispiel haben wir einen Spread von Transformationen. Wir möchten eine davon herausgreifen und mit einer neuen Farbe belegen:
Mit LinearSpread erzeugen Sie eine Verteilung von Punkten. Wenn man mit zwei LinearSpread-Knoten z.B. ein Raster erzeugen wollte (der eine für die x-Punkte, der andere für die y-Punkte), bräuchte man jede Kombination der beiden Outputs. Der Knoten Cross leistet genau dies: er kombiniert zwei Spreads und gibt jede mögliche Kombination aus (in der Mathematik wäre dies das Kreuzprodukt zweier Mengen, daher der Name).
10.4.1 Queue
Der Knote Queue (String) speichert eine Reihe von Strings und gibt die letzten N Strings als Spread aus.
Bei Zahlen nehmen Sie den allgemeineren Knoten Queue (Spreads).
Wenn der Insert-Pin ständig auf 1 ist, speichert die Queue für jeden Frame (also 50-60 Mal pro Sekunde) einen neuen Wert.
10.5 Kinect
Die Daten der Kinect kommen über den Knoten Kinect (Devices Microsoft) in Ihr Patch. Der Knoten gibt alle wesentlichen Daten über den Pin Kinect Runtime aus. Diese Daten müssen dann nochmal mit spezialisierten Knoten gefiltert werden. Wenn wir zunächst mal nur das Kamerabild sehen möchten, wählen wir den Knoten RGB (Kinect Microsoft). Dieser Knoten gibt eine Textur aus, die wir auf einen FullscreenQuad legen können, der wiederum im Renderer dargestellt wird. Damit das funktioniert, müssen wir den Eingabe-Pin Enable Color und den Pin Enable auf 1 setzen.
Um ein Skelett darzustellen, benötigen Sie den Knoten Skeleton (Kinect Microsoft), der die Positionen der Gelenke (Joints) ausgibt. Diese werden dann verwendet, um Kugeln darzustellen:
Mit dem Knoten Delay kann man leicht einen Doppelgänger herstellen, der z.B. mit 5 Sekunden Verspätung die gleichen Handlungen vollführt.