Benutzer-Werkzeuge

Webseiten-Werkzeuge


Seitenleiste

projektesose18:soundprofpublic:start

Dokumentation

Einleitung

Unser Ziel war es einen Roboter zu bauen, der in der Lage ist einem Nutzer die Grundlagen des Klavierspielens zu vermitteln. Dafür wählen wir den Ansatz „learning by doing“. Durch Anzeigen einer virtuellen Tastatur in Processing, an der Tasten grün eingefärbt werden können, wird dem Nutzer angezeigt welche Taste gedrückt werden muss um ein vorher vom Nutzer ausgewähltes Stück zu spielen. Dem Nutzer wird durch ein rotes Aufleuchten des Hintergrundes eine Rückmeldung gegeben, falls seine Eingabe falsch war und erst der nächste Ton vorgegeben, wenn die korrekte Taste betätigt wurde. Das Drücken der Tasten bewirkt dabei auch die Ausgabe des entsprechenden Tons wie auf einem Klavier. Der Roboter bewertet die Performance des Nutzers am Ende mit einer Punktzahl zwischen 0 und 100. Außerdem ist eine Demonstration der Stücke möglich.

Umsetzung

Überblick über das Gesamtsystem

Das Gesamtsystem
Abbildung 1: Das Gesamtsystem

Unser Roboter besteht aus einer zusammenhängenden Baugruppe, die die Stromkreise der Taster und den Piezo mit seinem Stromkreis umfasst. Grob betrachtet mussten fünf Hauptaufgabenbereiche bearbeitet werden: Tonausgabe, Bereitstellen der Informationen, die für ein Stück kennzeichnend sind, Anzeigen des nächsten Tons, Rückmeldung, ob gespielter Ton richtig oder falsch war und Navigation/Bedienung. Wir haben uns bewusst dafür entschieden, die Ausgestaltung des Roboters zu einem klavierähnlichen Design zu streichen und das nur anzudeuten. Ebenso das Spielen von mehreren Tönen gleichzeitig.

Tonausgabe

Wenn eine Taste gedrückt wird, soll der Ton erklingen, dem diese Taste auf einem herkömmlichen Klavier entspricht. Dabei wichtig ist auch, dass sobald keine Taste mehr gedrückt wird auch kein Ton gespielt wird.

Bereitstellung der Informationen, die für das Vorgeben von Songs benötigt werden

Um feststellen zu können, ob ein Ton richtig oder falsch war und ein Stück vorzuspielen, muss der Roboter wissen in welcher Reihenfolge welche Töne gespielt werden sollen und wie lange sie gespielt werden sollen. Dafür haben wir eine Arduino-Library geschrieben, die es ermöglicht „Song“ - Objekte zu erstellen, die diese Informationen in Form von Attributen gespeichert haben und die nötigen Methoden besitzen um sich selbst zu demonstrieren.

Anzeigen des nächsten Tons

Um den nächsten Ton anzeigen zu können, muss zunächst eine blanke Tastatur in Processing angezeigt werden können, davon ausgehend kann dann die Farbe des Rechtecks, das die gefragte Taste repräsentiert geändert werden, um sie zu markieren.

Rückmeldung, ob gespielter Ton richtig oder falsch war

Als Rückmeldung die möglichst intuitiv verstanden werden kann, haben wir uns überlegt für den Fall, dass der Ton richtig war einfach den nächsten Ton zu markieren ohne eine zusätzliche Ausgabe. Sollte der Ton falsch gewesen sein, wird der Hintergrund rot eingefärbt und die selbe Taste bleibt markiert.

Bedienung/Navigation im Menü

Um den Roboter zu bedienen wird in Processing ein Menü angezeigt, welches die zur Auswahl stehenden Stücke enthält und das im jeweiligen Moment ausgewählte in einer anderen Farbe darstellt. Mit drei zusätzlichen Tastern kann man durch dieses Menü hindurch navigieren. Ein Taster wählt den nächsten Song aus, ein zweiter startet die Demo und ein weiterer Taster startet den Spielmodus.

Das Gesamtsystem
Abbildung 2: Das Menü

Tonausgabe

Um zu überprüfen welcher Taster vom Nutzer gedrückt wurde und dann den entsprechenden Ton auszugeben existieren im loop if-Abfragen für jeden Taster nach folgendem Muster (jeder Note wird zu Beginn eine Frequenz zugeordnet):

int speakerPin = 3;  //Beispielhaft gewählt
int g2 = 196;    //Dem Ton g2 ist auf der üblichen Klaviertastatur die Frequenz 196 Hz zugeordnet
 
if (digitalRead(2) == HIGH) {  //Wenn der Stromkreis an Pin 2 geschlossen ist (also der entsprechende Taster gedrückt wurde)
      tone(speakerPin, g2);    //soll der zuvor festgelegte Ton gespielt werden
      played = "g2";
    }
 
else {
  played = "null";
}
 
if (played.equals("null")) {
   noTone(speakerPin);      //Wenn kein Taster gedrückt wird, soll kein Ton gespielt werden (noTone beendet alle tone-Aufrufe auf dem übergegebenen Pin)
}

Der Aufruf der noTone-Funktion ist erforderlich, da wir das Spielen so gestalten wollten, dass es möglichst viel Ähnlichkeit mit klassischem Klavierspielen hat, der Ton muss also genau dann ertönen, wenn der Taster gedrückt wird und nicht für eine vorher festgelegte Dauer, wie es mit der tone-Funktion alleine möglich gewesen wäre.

Die Arduino-Song-Library

Da der Nutzer erst zur Laufzeit entscheidet welches Stück gespielt werden soll, haben wir uns für die Bereitstellung der songspezifischen Informationen überlegt, dass dies am sinnvollsten realisiert werden kann mittels einer Song-Klasse, die für jeden Song die Tonreihenfolge, Länge des Songs, Länge der Töne und das Tempo in Attributen speichert und Methoden zum Vorspielen des Songs bereitstellt. Dadurch sind diese Informationen unabhängig, flexibel und gekapselt, da sie für die Funktionalität des Haupt-Codes nicht relevant sind.

Aus der Header-Datei der Klasse:

class Song {
  public:
    Song(int laenge, String* notes, int* beats, int tempo); //Konstruktor
    void play();                                            //spielt den Song ab
    void playTone(int sound, int duration);                 //spielt genau spezifizierten Ton
    void playNote(String note, int duration);               //spielt den Ton, der dem Notennamen entspricht
 
    int laenge;       //Anzahl der Noten
    String* notes;     //Tonhöhe
    int* beats;        //Länge der Töne
    int tempo;         //Geschwindigkeit des Liedes
    static const int speakerPin = 3;    //Variable für den speakerPin
};

Kommunikation zwischen Arduino und Processing

Arduino und Processing kommunizieren ständig miteinander, dabei gibt es einmal das regelmäßige Verschicken der aktuell zu markierenden Taste sowie andere Nachrichten, deren Verschicken von Aktionen des Nutzers ausgelöst wird. Um zu gewährleisten, dass keine der Nachrichten verloren geht, weil der Serial Port gerade mit einer anderen Nachricht beschäftigt ist, haben wir ein wenig mit der Baud-Rate für die Kommunikation herumprobiert und durch Ausprobieren die beste Häufigkeit für das Verschicken der aktuellen Taste gefunden. Unsere ausgewählte Baud-Rate beträgt 1200 und die zu markierende Taste wird bei jedem 2000. Durchlauf des loops gesendet. Mit diesen Einstellungen konnten wir die besten Ergebnisse erzielen (nach unserem subjektiven Empfinden haben wir bewertet, wie schnell die Reaktionen des User-Interface zu sehen sind).

Weiterhin haben wir uns dazu entschieden nur Daten eines Typs, des Datentyps String zu verschicken. Um in einer ersten if-Abfrage grob zu unterscheiden was dort versendet wurde, betrachten wir die Länge des erhaltenen Strings und erst dann den Inhalt des Strings. Die Noten werden mit den üblichen Bezeichnungen von g2 bis c5 gekennzeichnet, wenn der Name einer weißen Taste verschickt wurde hat der erhaltene String also eine Länge von 4, da immer noch ein „\n“ mitgeschickt wird. Entsprechend ist der String 6 Zeichen lang, wenn der Name einer schwarzen Taste enthalten ist. Die anderen Möglichkeiten für den gesendeten String sind „correct“, wenn die richtige Taste gedrückt wurde, „false“, falls eine falsche Taste gedrückt wurde und „gameover“, wenn das Ende des ausgewählten Stückes erreicht wurde, jeweils mit einem Zeilenumbruch angehängt.

value = myPort.readStringUntil('\n');
 
if (value != null) {
 
if (value.length() == 4) {
   val = value.substring(0, 2);    //wenn der Name einer weißen Taste gesendet wurde
} else if (value.length() == 6) {
   val = value.substring(0, 4);    //wenn der Name einer schwarzen Taste gesendet wurde
} else if (value.length() == 7) {
   wasRight = false;               //wenn "false" gesendet wurde
   bg = wrong;
} else if (value.length() == 9) {
   wasRight = true;                //wenn "correct" gesendet wurde
   bg = norm;
} else if (value.length() == 10) {    //wenn gameover gesendet wurde
   gameMode = false;
   endScreen = true;
      }

Die virtuelle Tastatur

Die virtuelle Tastatur stellt das User Interface dar. Um die Processing-Datei möglichst übersichtlich zu gestalten haben wir die Tasten mittels zweier Klassen modelliert, eine Klasse „Key“ welche eine (weiße) Taste darstellt sowie eine Klasse „BlackKey“ welche eine schwarze Taste repräsentiert. Die BlackKey-Klasse erbt dabei von der Superklasse „Key“. Dies ist aus mehreren Gründen vorteilhaft. Zum einen müssen dadurch nur die wenigen Attribute und Methoden überschrieben/neu geschrieben werden, die für weiße und schwarze Tasten unterschiedlich sind, zum anderen können nun in Variablen des Typs „Key“ sowohl Instanzen der Klasse „Key“ als auch der Klasse „BlackKey“ gespeichert werden. Diese Tatsache haben wir ausgenutzt indem wir sämtliche Tasten-Objekte in einer Hash-Map gespeichert haben, die als Datentypen Key-Objekte speichert und diesen String-Schlüssel zuweist. Diese Schlüssel sind die Notennamen, wie sie für die Töne der Klaviertastatur genutzt werden.

Die Tasten-Objekte werden erzeugt und dabei direkt der put-Methode der Hash-Map übergeben. Die Konstruktoren der beiden Klassen führen mittels Klassenattributen Buch wie viele schwarze bzw. weiße Tasten schon existieren und berechnen daraus die x-Position der neuen Taste, damit sie nahtlos an der Letzten anliegt. Die Tasten werden als einfache Rechtecke gezeichnet, die durch ihre Höhe, Breite, Farbe und ihre Position gekennzeichnet sind. Bei jedem Aufruf der draw-Methode in Processing werden zuerst die weißen Tasten gezeichnet, dann ermittelt welche Taste markiert werden muss, dann alle Markierungen entfernt und dann die neue Taste markiert. Erst danach werden die schwarzen Tasten gezeichnet, damit sie nicht von den benachbarten weißen Tasten überdeckt werden.

Die virtuelle Tastatur
Abbildung 3: Die virtuelle Tastatur

In sowohl dem Arduino als auch dem Processing Code existiert jeweils eine boolean-Variable, die angibt, ob sich der Nutzer noch im Menü Modus befindet oder schon im Spielmodus. Je nachdem ob true oder false wird im loop bzw. der draw()-Funktion unterschiedlicher Code ausgeführt, im Falle, dass sich der Nutzer noch im Menü befindet wird vom Arduino gezählt wie häufig der Taster gedrückt wurde, der für die Auswahl des Songs zuständig ist. Diese Anzahl wird an Processing gesendet, was immerzu alle Namen der verfügbaren Songs anzeigt, wobei der gerade ausgewählte in einer anderen Farbe angezeigt wird als die Restlichen.

Im Arduino-Code wird zu Beginn eine Integer-Variable „score“ deklariert, die für die Bewertung steht, und mit dem Wert 100 initialisiert. Für jeden Durchlauf des loops bei dem eine falsche Taste gedrückt ist wird eins davon abgezogen, sodass je mehr Punkte abgezogen werden, je länger ein falscher Ton gespielt wird. Wenn 0 erreicht wird, werden keine weiteren Punkte mehr abgezogen. Wenn der Nutzer ein Stück zu Ende gespielt hat, wird der Wert der score-Variable an Processing gesendet und während der Arduino ein 30 sekündiges Delay ablaufen lässt zeigt Processing einen Endbildschirm mit der erreichten Punktzahl.


Materialliste

Material Anzahl
Arduino Mega1
Steckplatine3
Taster32
Widerstände (1000 Ohm)32
Piezo-Lautsprecher1

Tabelle 1: Materialliste

Pinbelegungstabelle

Arduino-Pin Funktion
3Piezo
42Taster zum Starten des Spielmodus
44Taster zum Abspielen der Demo
46Taster Auswählen des Songs
2, 4-19, 22-32, 52, 53Taster für Klaviertasten

Tabelle 2: Pinbelegungstablle

Abbildung 4: Schaltplan
Materialliste des Modells
Material Anzahl
Arduino Nano1
Steckplatine1
Taster5
Widerstände (1000 Ohm)5
Piezo-Lautsprecher1

Tabelle 3: Materialliste des Modells

prototyp.jpg
Abbildung 5: Klavierähnlicher kleinerer Prototyp

Ergebnis und Diskussion

Der Roboter ist in der Lage den Nutzer durch ein Menü zu führen, in welchem vier Stücke zur Auswahl stehen, die alle auch per Knopfdruck vorgespielt werden können. Es kann eines dieser vier Stücke zum Nachspielen ausgewählt werden und dann die virtuelle Tastatur angezeigt werden, die die zu drückende Taste grün markiert und rot aufleuchtet, wenn ein falscher Ton gespielt wurde. Auch die Tonausgabe und Vergabe einer Punktzahl am Ende funktionieren so wie geplant und der Roboter kehrt nach Beenden eines Stückes zurück in das Menü und der Ablauf beginnt von neuem. Jedoch konnten wir ein klavierähnliches Aussehen nur in einem kleinen Modell umsetzen und mussten uns für die große Ausführung mit den Tastern und einer Markierung von schwarzen und weißen Tasten durch eine angeklebte Leiste begnügen. Außerdem konnten wir keine unserer weiteren Ideen umsetzen, man könnte zum Beispiel noch den Piezo durch einen besseren Lautsprecher ersetzen und dann auch eine Lösung für das Spielen mehrerer Töne gleichzeitig erarbeiten. Auch hilfreich wäre die Möglichkeit MIDI-Files nutzen zu können um sich das manuelle Zusammenstellen der Stücke aus Tonreihenfolge und einzelnen Tonlängen zu sparen.

Wir haben einen kleinen klavierähnlichen Prototypen mit einem Tonumfang von fünf Tönen gebaut (Abbildung 5). Dieser hat Tasten aus Holz, die dem klaviertypischen Layout nachempfunden sind und das Spielen auf dem SoundProf erleichtern sollen und mittels kleiner Scharniere an einer Art Wand aus Holz befestigt sind. Die Taster samt Kabeln und Widerständen befinden sich darunter, so dass durch Herunterdrücken einer der Holztasten der darunter liegende Taster heruntergedrückt wird. Diesen Aufbau könnte man noch vergrößern, so dass alle 30 Taster des SoundProfs sich darüber betätigen ließen (Version SoundProf 2.0).

Gesamter Code

projektesose18/soundprofpublic/start.txt · Zuletzt geändert: 2018/11/09 10:52 von d.golovko