Und weiter geht es mit den Einblicken in den Programmcode der die Würfel steuert. Hier habe ich schon die Grundlagen und Verbindung mit unserem Netzwerk erläutert. Im Folgenden soll es darum gehen wie die Sensordaten ausgelesen, verarbeitet, und übers Netzwerk geschickt werden.
Natürlich ist der Code nach wie vor auf unserem Github Repository verfügbar.
Konfiguration
Ähnlich wie auch schon im Abschnitt zum Netzwerk erklärt, muss zunächst die entsprechende Library eingebunden werden, die es uns erlaubt mit den integrierten Sensoren zu kommunizieren.

Als nächstes werden einige Variablen deklariert, welche im späteren Verlauf noch benötigt werden.

Mit „ax“, „ay“, und „az“ werden Variablen deklariert in welchen später die Werte des Beschleunigungssensors ausgelesen werden können. Die Variablen „s1“ bis „s6“ repräsentieren die Seiten des Würfels für die wir jeweils eigene Daten benötigen. „currentSide“ und „currentSideThreshold“ sind weitere Variablen die im Programmverlauf benötigt werden um die gewünschte Ausgabe zu erreichen, aber dazu später mehr.
Prinzipiell ist es empfehlenswert Variablen, gerade solche die oft eingelesen bzw. überschrieben werden vor dem eigentlichen Programmstart zu deklarieren. Hier wird direkt zu Beginn ein Bereich im Speicher für diese Variable reserviert, sodass dies nicht zur Laufzeit mit jeder Wiederholung geschehen muss, was etwas Performance einspart.
In der Funktion „void setup()“ wird nun der Sensor registriert.

In Zeile 55 wird der Sensor registriert. Sollte dies fehlschlagen, weil z.B. der Sensor nicht verfügbar ist, gibt der „IMU.begin()“ false zurück, und der Code innerhalb der if-Bedingung wird ausgeführt. Hier werden die LEDS auf rot geschaltet, um einen Fehler zu visualisieren und das Programm durch die Schleife „while (1)“ in Zeile 65 effektiv gestoppt. Ist die Registrierung erfolgreich ist die Konfiguration abgeschlossen und wir können zur eigentlichen Programmschleife fortschreiten.

Zunächst wird überprüft ob der Accelerometer verfügbar ist. Ist dies der Fall, werden die Daten von diesem entgegen genommen und in die vorher deklarierten Variablen gespeichert.
Accelerometer
Ein Accelerometer, auch Beschleunigungssensor genannt, misst die eigene Beschleunigung im Bezug auf drei Achsen. Die Werte werden dabei in der Regel als mehrfache der Erdbeschleunigung g angegeben. Im Ruhezustand ist dies z.B. 1g für die Seite, die direkt in Richtung Boden gerichtet ist. Durch vergleichen der drei Werte kann so die Ausrichtung des Accelerometers bestimmen.

Im nächsten Schritt werden die Sensorenwerte auf für uns nützliche Werte für jede Seite abgebildet. Unsere Zielskala reicht dabei von 0, wenn die Seite nach unten zeigt, bis 127, wenn die Seite ganz oben ist. Die Skala 0 – 127 wurde gewählt da Ableton intern mit MIDI-Tönen arbeitet und diese Werte zwischen 0 und 127 annehmen können. Hierfür wird die folgende Funktion „mapValue“ verwendet.

Die Funktion nimmt als Parameter den abzubildenden Wert, den minimalen und maximalen Wert der Ausgangsskala, sowie minimalen und maximalen Wert der Zielskala entgegen. Hierbei handelt es sich um eine einfache lineare Funktion nach dem Schema y = mx + b, wobei die Steigung „m“ der Quotient zwischen den Wertebereichen der Skalen ist.
Zu bemerken ist, dass die Ausgangsskalen der jeweils gegenüberliegenden Seiten (1 und 6, 2 und 5, sowie 3 und 4) invertiert sind. Dies ist der Fall, da die jeweiligen seiten zwar auf der selben Achse liegen, aber unterschiedliche Ausrichtungen (Vorzeichen) haben.
Sind die Werte aller Seiten bestimmt, wird die momentan oben liegende Seite bestimmt. Dies erfolgt in einer einfachen Reihe an if-Bedingungen in denen der jeweilige Seitenwert mit dem anfangs definierten „currentSideThreshold“ verglichen wird:

Zu guter Letzt werden die die so erzeugten Daten zu einem OSC Bundle gepackt und per UDP gesendet.

Ein OSC Bundle (hier „bndlOut“) besteht aus mehreren einzelnen OSC Nachrichten. Diese werden hier in den Zeilen 167 bis 173 erzeugt. Hierbei wird zunächst die OSC Adresse festgelegt. Diese wird in diesem Fall aus einer externen Datei eingelesen, was das ändern der Adresse für die einzelnen Würfel leichter macht. Die Adresse folgt dem Muster „/cubeX/sideY“, wobei X und Y die jeweiligen Würfel bzw. Seiten identifizieren. Durch die Adresse können die einzelnen Nachrichten später in Max identifiziert und richtig zugeordnet werden. danach wird der entsprechende Wert an die Nachricht gehängt.
In den Zeilen 176 bis 179 wird nun ein UDP Datagram erstellt, mit dem OSC Bundle gefüllt, und an die Ziel-IP Adresse geschickt. Danach wird das OSC Bundle geleert, sodass es beim nächsten Programmdurchlauf wieder befüllt werden kann.
OSC vs MIDI
In anfänglichen Prototypen haben wir als Nachrichtenprotokoll noch MIDI benutzt, was nach wie vor weit verbreitet für digitale Musikinstrumente und Interfaces ist. Wir haben uns am Ende aber dennoch für OSC entschieden, da wir so in der Genauigkeit nicht durch die MIDI „beat clock“ beschränkt sind. Außerdem biete OSC eine größere Flexibilität. Es können neben ganzzahligen „Integer“ Werten auch eine Reihe anderer Datentypen wie z.B. Fließkommazahlen oder Zeichenketten übergeben werden.
Gerade bei der Steuerung der LEDs ist diese Flexibilität besonders nützlich. Und wie das funktioniert wird im nächsten Teil „Part 3: LED Steuerung“ erklärt.