Template State Machine auf der Basis von Typelists

Boost bietet eine sehr elegante und leistungsfaehige State Machine Implementierung (Meta State Machine), die es erlaubt mit Hilfe von Templates eine statische Definition von States und State Transitions anzugeben. Leider haengt die Meta State Machine von zahlreichen anderen Boost Komponenten ab, was es schwierig macht, diese in einem Umfeld einzusetzen, das die Verwendung von Boost nicht erlaubt. Um dieses Problem zu loesen habe ich eine eigene State Machine implementiert, die sich zwar an die Meta State Machine von Boost anlehnt, aber zum einen keine Abhaengigkeiten von anderen Boost Komponenten hat und zum anderen Typelists fuer die Definition von States und Transitions benutzt. Letzteres ermoeglicht eine etwas elegantere und leichtgewichtigere Umsetzung. Es werden sowohl Guards und Actions unterstuetzt als auch Enter und Exit Methoden fuer die States. Weitere Features werden eventuell zu einem spaeteren Zeitpunkt hinzugefuegt. Im Folgenden moechte ich einen kurzen Ueberblick ueber die Konzepte und Fallstricke dieser Implementierung geben.

Typelists

Die Definition von States und Transition Table soll, wie bereits erwaehnt, mittels Template Konstrukten erfolgen. Wir benoetigen daher eine Moeglichkeit, Listen von Typen darzustellen. Dazu benutzen wir (anders als boost::msm) Typelists, die folgendermassen definiert sind:

Der Typ TTail darf wiederum eine Typelist sein, was es erlaubt, prinzipiell beliebig tief verschachtelte Template Konstrukte zu erzeugen mit denen uns wiederum eine Abbildung von Listen gelingt. Eine Liste mit drei Typen int, float und char koennte zum Beispiel folgendermassen aussehen:

Der null_ Typ wird benoetigt, um das Ende der Liste zu kennzeichnen. Eine leere Klassendefinition (bzw. ein struct) ist dafuer ausreichend, da er keine weitere Funktionalitaet benoetigt. Um eine solche Typelist komfortabel erzeugen zu koennen, schreiben wir uns eine Metafunktion:

Wir koennen die obige Definition unserer Liste aus drei Typen nun einfacher schreiben als

Auf diesen Typelists koennen nun zahlreiche (Compile-Time-)Algorithmen implementiert werden, die ich hier nicht alle beschreiben moechte. Als Beispiel sei die RemoveDuplicates-Metafunktion genannt, die eine Typelist als Parameter nimmt und diese ohne Duplikate zurueckgibt.

Eine weitere Metafunktion wird spaeter auch sehr wichtig sein:

Diese ermoeglicht uns, fuer jeden Typ in einer Typelist die statische Methode Execute in einem Funktor Typ aufzurufen. Wozu wir das brauchen, sehen wir bei der Implementierung der Transition Table.

State Machine Definition

Fuer die Definition der State Machine Behaviour (States, Transitions, Actions, Guards) nutzen wir das Curiously Recurring Template Pattern. Dieses besteht aus einem Vererbungsmechanismus der es erlaubt, aus der Basisklasse heraus auf Typen zuzugreifen, die erst in der vererbten Klasse definiert werden. Dazu muss die Basisklasse einen Template Parameter DERIVED haben, der in der vererbten Klasse mit der Subklasse selbst initialisiert wird:

Eine konkrete “Instanziierung” dieser StateMachineDefinition wuerde dann so aussehen:

Man kann auch sagen, dieses Pattern simuliert Dynamic Binding in einem statischen Kontext. Dadurch koennen wir jetzt konkrete Typen in der StateMachineDefinition Klasse verwenden, die erst in der abgeleiteten Klasse per typedef definiert werden muessen. Diesen Umstand nutzen wir, um die Transition Table, den Initial State, Actions und Guards zu konstruieren.

Die Transition Table benoetigt dazu spezielle getemplatete Structs, die jeweils fuer einen Eintrag in der Table stehen. Im folgenden ist als Beispiel nur die Definition von RowA (Eintrag mit zugeordneter Action) gezeigt:

Derived::*Action ist dabei ein Pointer auf eine Methode, die in der abgeleiteten Klasse definiert sein muss. Das Struct RowATag wird benoetigt, um spaeter zwischen den verschiedenen Row Typen differenzieren zu koennen). Hier sehen wir ein Beispiel fuer eine solche State Machine Definition:

Die beiden typedefs TransitionTable und InitialState sind zwingend erforderlich, da sie von der Basisklasse referenziert werden!

State Machine

Um aus dieser Definition nun eine tatsaechliche State Machine zu generieren (die konkrete StateMachineDefinition ist bisher nichts anderes als eine Ansammlung von Typ- und Methodendefinitionen sowie typedefs ohne wirkliche Funktionalitaet) muessen wir die Compiletime / Runtime Barriere durchbrechen. Dazu benoetigen wir zunaechst eine weitere Klasse StateMachine die als Template Argument die konkrete StateMachineDefinition bekommt.

Innerhalb dieser Klasse kann nun die Transition Table aus der Definition extrahiert werden (Curiously Recurring Template Pattern!):

Uns interessiert nun die Liste der States selbst. Diese ist in der Transition Table “versteckt” und muss erst extrahiert werden. Dazu muessen wir sowohl Source als auch Target States (das erste bzw. dritte Template Argument jedes Row Elements) aus der Typelist holen und aus der resultierenden Liste alle Duplikate entfernen. Dazu schreiben wir eine weitere Metafunktion:

Diese nimmt TTable als Argument (die Transition Table) und extrahiert mittels der Transform Metafunktion zunaechst die Source und Target States in zwei separate Typelists (SourceStatesFiltered und TargetStatesFiltered). Diese Filterung geschieht ueber die zwei Metafunktoren GetSourceState und GetTargetState. Danach werden die zwei Typelists gemergt (AllStatesFiltered) und im letzten Schritt von Duplikaten befreit. Dieses letzte typedef Type ist schliesslich der Rueckgabewert der Metafunktion.

Innerhalb der StateMachine Klasse wird StateListFromTransitionTable dann fuer die Extraktion benutzt:

Schon haben wir die Liste der States. Neben einigen weiteren Schritten fehlt uns aber noch der wichtigste und leider auch komplizierteste Teil: Das Generieren der Runtime State Instanzen und der Dispatch Table, die zur Laufzeit fuer die Transitions und den Aufruf der Actions sowie der Enter/Exit-Methoden benoetigt wird. Dazu muessen wir zunaechst die Transition Table aus dem StateMachineDefinition Kontext in eine neue (statische) Tabelle im StateMachine Kontext transformieren. Jeder Row Type aus der Definition braucht einen korrespondierenden Type in der StateMachine. Hier ist als Beispiel das Gegenstueck zu RowA gezeigt:

Dieser InternalRowA Typ enthaelt nun auch eine statische Methode, die den tatsaechlichen State Uebergang vornimmt. Um die StateMachineDefinition Transition Table in die StateMachine Transition Table ueberzufuehren, benoetigen wir weitere Metafunktionen:

Die letzte Metafunktion (MakeInternalTable) koennen wir nun benutzen, um die Tabellen zu transformieren:

Fuer jeden State Uebergang muessen wir eine Dispatch Table generieren, die vom jeweiligen Event abhaengt. Dazu basteln wir uns ein Struct, das im Groben wie folgt aussieht:

FSM ist dabei der Typ der StateMachine und Event der Typ des eingegangenen Events, das eine State Transition ausloesen soll. Mit Hilfe der Filter Metafunktion wird die Transition Table nach dem Event gefiltert (RelevantTransitions). Im Konstruktor von DispatchTable wird dann sowohl ueber die Liste aller States als auch ueber die Liste der relevanten Transitions geloopt. Fuer die relevanten Transitions wird ein Pointer auf eine Funktion (die statische Process Methode in der jeweiligen InternalRow) in die Tabelle (Entries) eingefuegt, der den Guard auswertet (wenn vorhanden), die tatsaechliche Transition vornimmt und ggf. die zugehoerige Action aufruft. Fuer alle uebrigen zeigt dieser Pointer auf eine leere Funktion (keine Aktionen notwendig).

Shure SE215 In Ear Kopfhoerer

se215k-full-cable_10_colsFuer diesen (relativ) guenstigen Preis hatte ich eigentlich keine besonders ueberzeugende Qualitaet erwartet, aber die SE215 schlagen sich ziemlich gut. Schwaechen hat der Hoerer aber definitiv in den Hoehen und den Baessen. Den Baessen fehlt es an Druck und sie klingen ein wenig undefiniert. Das ist aber durchaus noch im Rahmen, wenn man um 80 Hz herum ein bisschen mit dem EQ nachhilft. Bei den Hoehen habe ich auch einen Test mit dem Graphic EQ gemacht und festgestellt, dass ab ca. 10kHz nicht mehr viel geht. Ab 12-14 kHz bringt auch das Boosten am EQ nichts mehr, folglich ist da einfach der Hoerer an seinen Grenzen.

Dazwischen klingt der Shure jedoch erstaunlich ausgewogen. Piano klingt wie Piano und die sehr sensiblen Streicher kommen auch anstaendig raus, vielleicht ein ganz klein wenig topfig, aber ueberhaupt nicht schlimm. Ein bisschen Korrektur bei 1 kHz hat hier geholfen. Glanz und Seidigkeit darf man natuerlich keine erwarten, wenn ab 14 kHz nichts mehr rauskommt. Die Baesse sind – nunja, sie sind zumindest da, aber man hat nicht das Gefuehl, dass es einen gleich wegblaest. Wer sowas braucht, muss schon zu einem Zweiwegesystem oder hoeher greifen. Insgesamt aber eine DEUTLICHE Verbesserung zu jedem Floor Wedge, mit dem ich bisher gespielt habe.

Die Stoepsel halten sehr gut in meinen (durchschnittlichen) Ohren, allerdings kann ich mir vorstellen, dass man mit kleineren Ohren etwas Probleme haben wird. Leider druecken sie nach einer Weile etwas in meinem rechten Ohr, das waere aber sicherlich mit anderen Passstuecken zu beheben und muss ja nicht bei jedem so sein.

Die Aussengeraeuschdaemmung ist fabelhaft. Man hoert wirklich fast ueberhaupt nichts mehr. Unterhaltung ist ab einer Entfernung von > 50cm nicht mehr moeglich. Auch das extrem laute Schlagzeug unseres Drummers ist in unserem Proberaum kaum noch durchzuhoeren, obwohl ich nur knapp zwei Meter davon entfernt sitze. Wer sowas nicht kennt, wird es im ersten Moment wahrscheinlich als unangenehm empfinden, aber man gewoehnt sich dran. Ein Ambience Mikro ist aber in jedem Fall zu empfehlen, da man sonst schon sehr isoliert ist auf der Buehne.

Fazit: Fuer alle, die sonst auch mit einem droehnenden Stage Monitor zurecht kommen, sind die SE215 mit Sicherheit voellig ausreichend und vor allem wesentlich gehoerschonender. Sie klingen in den Mitten recht ausgewogen, haben aber leichte Schwaechen im Bass und Defizite in den Hoehen, die jedoch gar nicht so unangenehm auffallen. Wer besonderen Wert auf einen glasklaren Klang und eine gut definierte Basswiedergabe legt, der sollte zu den teureren Shure Modellen im Zwei- oder Dreiwegesystem greifen.

Vergleichbar gute Alternativen gibt es in diesem Preissegment keine. Man kann die t.bone Hoerer von Thomann vielleicht mal ausprobieren, aber ich habe den EP-3 getestet, und der klingt wirklich ABSOLUT katastrophal! Selbst ein Analogtelefon hat eine linearere Frequenzwiedergabe. Finger weg davon. Wie die groesseren t.bone Modelle klingen, weiss ich nicht, aber das muesste schon eine erhebliche Verbesserung sein, um aus dem indiskutablen Sound des EP-3 noch etwas Brauchbares zu machen. Die guenstigen Fisher Amps Hoerer taugen leider auch nichts.

Nachtrag zum Kronos Tastaturproblem

Eigentlich war ich vom KORG Kronos sehr begeistert. Die vielen verschiedenen Syntheseformen, Sample Streaming von SSD und das Bedienkonzept mit dem grossen Touchscreen haben den Markt tatsaechlich umgekrempelt. Allerdings waehrte die Freude am Kronos nur kurz, denn wie viele andere hatte auch ich den bekannten Fabrikationsfehler an meiner Tastatur zu beklagen. Beim ersten Austausch des Kronos gegen ein Neugeraet war ich zunaechst hoffnungsvoll, dass das Problem nun behoben ist. Nach wenigen Wochen zeigte jedoch auch das Neugeraet die gleichen Symptome. Ich beschwerte mich bei KORG, der Support verwies mich an meinen Haendler. Das Zusenden einer leeren Umverpackung fuer den Kronos sei zudem auch nicht moeglich, da “wir keine Umverpackungen lagernd haben” (ich hatte meine aus Platzmangel bereits entsorgt). Eine Abwicklung direkt ueber KORG sei zudem gar nicht moeglich. Also ein drittes Mal 50 Kilometer zum Haendler fahren mit dem teuren Geraet im Kofferraum fuer einen weiteren Austausch. Wie nicht anders zu erwarten war, hat aber auch das zweite Austauschgeraet die gleichen Probleme, sogar noch schlimmer als die beiden vorherigen.

Natuerlich haette ich das Keyboard auch reparieren lassen koennen, was das Problem vielleicht schon beim ersten Mal behoben haette, allerdings bin ich in diesem Punkt konsequent. Als Kunde habe ich grundsaetzlich die Wahl zwischen Reparatur und Neugeraet. Ich bestehe immer auf die Lieferung eines Neugeraetes, zumal das keinerlei unverhaeltnismaessigen Aufwand fuer den Distributor bedeutet (ganz im Gegenteil). Wenn KORG keine funktionierenden Neugeraete auf Lager hat, ist das nun mal nicht mein Problem.

Angesichts der Ergebnisse dieser unfreiwillig vorgenommenen Stichprobe stellt sich mir allerdings die Frage, wie KORG behaupten kann, dass nur ein verschwindend geringer Anteil der Kronos den Fehler in der Tastaturdaempfung aufweist. Die Wahrscheinlichkeit, drei Geraete mit dem gleichen Fehler zu bekommen ist bei verschwindend geringen Prozentsaetzen ziemlich nahe bei Null. Ich glaube eher, dass alle Kronos den selben Defekt haben, der nur mehr oder weniger stark ausgepraegt ist, und manche Besitzer den Effekt einfach nicht wahrnehmen. Fuer Pianisten ist das aber leider absolut inakzeptabel und ich habe ehrlich gesagt vorher noch nie eine derartige Reaktion einer Tastaturmechanik erlebt. Das SV-1 Stagepiano von KORG, das angeblich die baugleiche Tastatur besitzt wie der Kronos, hat das Problem nicht.

Meine Enttaeuschung ueber KORG (oder zumindest den deutschen Vertrieb) ist gross. Wenn ich es schon beim ersten Austausch nicht schaffe, dem Kunden ein funktionierendes Geraet zuzusenden, dann versuche ich wenigstens beim zweiten Mal alles, um das Problem aus der Welt zu schaffen. Moeglichkeiten haette es mehrere gegeben. Zum Beispiel das vorherige Testen des Neugeraetes auf das Tastaturproblem (was natuerlich keinen Erfolg haben wird, wenn alle Geraete betroffen sind). Den Austausch der Tastatur gegen die reparierte Variante an einem Neugeraet. Oder ein kostenloses Upgrade auf den Nachfolger KronosX, was in Anbetracht meiner bereits angefallenen Spritkosten fuer die Hin- und Herkutschiererei des Kronos auch nicht ungerechtfertigt waere. Natuerlich habe ich diesen Vorschlag auch an KORG kommuniziert, dort war man zunaechst auch zuversichtlich, dass sich eine einvernehmliche Loesung finden liesse. Als mein Haendler dann mit dem Vertrieb telefonierte, wollte davon allerdings niemand mehr etwas wissen. Selbstverstaendlich koennte ich einen KronosX haben – gegen die ueblichen 300 Euro Aufpreis, die jeder dafuer zahlen muesste, ohne Rabatt, ohne Abzug auf Kulanz. Respekt KORG, einen kulanteren Laden wie euch habe ich selten erlebt.

Am liebsten waere mir, ich wuerde mein Geld zurueckbekommen und muesste den Kronos nie wieder sehen. Mein 300 Euro teures, massgefertigtes Flightcase muesste ich dann zwar anderweitig entsorgen, aber immer noch besser als staendig dran erinnert zu werden, dass manche Firmen sich gelinde gesagt wenig fuer ihre Kunden interessieren. Der Kronos war mein zweites und letztes Geraet von KORG. Ich bleibe lieber bei Yamaha und Kawai, die stets verlaessliche und hochwertig verarbeitete Geraete abgeliefert haben, mit denen ich NIEMALS auch nur ein einziges Problem hatte.

cconfig v1.0

Wie bereits angekuendigt moechte ich heute meine neue Konfigurations Bibliothek cconfig v1.0 fuer C++ vorstellen. Sie ist eine Weiterentwicklung von cfg_file und bietet die folgenden neuen Features:

  • Parser Backend wird jetzt mit ANTLR statt boost::spirit generiert um die Portierung auf andere Sprachen zu erleichtern
  • Konfigurationsdateien koennen gegen ein Schema validiert werden
  • C++ Code Generator fuer das Erzeugen von nativen Objektstrukturen und einem In-Memory-Validator aus der Schema-Definition
  • Config Stub Generator fuer das Erzeugen von validen Konfigurationsvorlagen aus dem Schema

Ausserdem wurde die Syntax von Gruppen geaendert, um die Eingabe zu erleichtern:

  • Gruppendefinitionen werden ohne Gleichheitszeichen angegeben (group {…} statt group = {…})
  • Gruppendefinitionen muessen nicht mehr mit einem Semikolon terminiert werden

Hier ein Beispiel fuer eine Konfigurationsdatei in cconfig:

Um Konfigurationsdateien validieren und C++ Code fuer den Zugriff generieren zu koennen, ist ein Schema erforderlich, das in diesem Fall wie folgt aussehen koennte:

Im Schema wird sowohl eine Hierarchie fuer die Konfigurationsdateien vorgegeben als auch die erwarteten Typen der einzelnen Settings definiert. Weiterhin koennen Settings als ‘required’ oder ‘optional’ markiert und mit Default Werten versehen werden.

Wie bereits beim Vorgaenger cfg_file ist ein programmatischer Zugriff auf die Settings ueber Index Operatoren oder die lookup() Funktion moeglich. Im folgenden Beispiel laden wir die Konfigurationsdatei, validieren sie gegen das Schema und geben einige der Settings aus:

Noch einfacher wird der Zugriff jedoch unter Zuhilfenahme generierter In-Memory Repraesentationen der Konfigurationsdaten. Dazu kann das Programm ‘cconfig_code_gen’ benutzt werden, das aus einer beliebigen Schema Datei solchen C++ Code generiert. Um aus unserem test.schema die zwei benoetigten Dateien ‘test_schema_wrapper.hpp’ und ‘test_schema_wrapper.cpp’ zu generieren, rufen wir den Generator folgendermassen auf:

Die beiden generierten Dateien enthalten sowohl die komplette Schema Information und den Objektbaum (verschachtelte structs und std::vector) als auch Code um das Config File zu lesen, und den Objektbaum zu befuellen. Es ist daher ausreichend, nur die generierte cpp Datei und die cconfig Library in Projekte hineinzukompilieren. Das Schema selbst muss nicht mit ausgeliefert werden.

Der Zugriff auf die Settings sieht jetzt wie folgt aus:

Die Sources von cconfig v1.0 finden Sie unter diesem Link. Getestet wurde es nur unter Linux, sollte aber auch auf anderen Plattformen problemlos laufen. Wer Verbesserungen zur Bibliothek beitragen oder an der Weiterentwicklung aktiv teilhaben moechte, kann sich gerne bei mir melden.

Minimale .vimrc fuer C++ Entwicklung

Neue C++ Configuration File Library (cconfig) in Kuerze verfuegbar

Die Weiterentwicklung meines bisherigen Konfigurationsparsers (config_file v1.2) steht in Kuerze zum Download zur Verfuegung. Die Neuerungen sind:

  • Kleinere Anpassungen an der Config Syntax
  • Parser Backend wird jetzt mit ANTLR statt boost::spirit generiert um die Portierung auf andere Sprachen zu erleichtern
  • Konfigurationsdateien koennen gegen ein Schema validiert werden
  • C++ Code Generator fuer das Erzeugen von nativen Objektstrukturen und einem In-Memory-Validator aus der Schema-Definition

Insbesondere der generierte C++ Wrapper erleichtert den Zugriff auf die Config Settings enorm und ermoeglicht ausserdem eine Validierung der verwendeten Konfigurationszugriffe zur Compilezeit. Hier ein Beispiel fuer ein Config File, das zugehoerige Schema und die Verwendung des generierten Wrappers:

Fuer die technisch Interessierten: Der Generator erzeugt eine Hierarchie von struct und std::vector Instanzen, die von der load_config() Methode mit Werten befuellt werden. Im Schema koennen einzelne Settings auch optional gemacht und mit einem Default Wert versehen werden.

Kabelbinder richtig befestigen

Wer kennt sie nicht, die guten Klett-Kabelbinder von Thomann, Musicstore oder woher auch immer. Was jedoch die wenigsten wissen ist, wie man die Dinger richtig an den Kabeln befestigt. Es gibt eine Technik, die die Kabelbinder so bombenfest am Kabel fixiert, dass sie garantiert nie mehr abfallen und (wenn das Kabel nicht allzu glatt ist) auch nicht mehr verrutschen koennen.

Leider ist das nicht ganz so einfach und am Anfang eine ziemliche Fummelei, aber nach ein paar Uebungslaeufen geht es eigentlich recht gut von der Hand. Es funktioniert folgendermassen:

  1. Kabelbinder ganz normal um das Kabel herumfuehren (die flauschige Seite weist vom Kabel weg)
  2. Das Ende des Kabelbinders von oben (!) durch den Metallring stecken
  3. Nochmal unter dem Kabel durchfuehren
  4. Ein zweites Mal (diesmal in die andere Richtung) durch den Metallring faedeln

Jetzt muss der Kabelbinder nur noch festgezogen werden. Dazu reicht es aber nicht, einfach am einen Ende zu ziehen, denn dabei zieht sich nur die innere Schlaufe zu. Der Trick ist, das Kabel festzuhalten und mit dem Ende des Kabelbinders rechtwinklig zum Kabelverlauf abwechselnd leicht hin und her zu ziehen. Dabei wird die auessere Schleife immer kleiner und irgendwann liegen beide Schlaufen eng am Kabel an. Jetzt sitzt der Kabelbinder bombenfest.


Durchbruch in der Gestensteuerung?

Die Firma LEAP Motion hat ein neues Eingabegerät vorgestellt, dass die Bedienung von Computern revolutionieren könnte. Wie Microsofts Kinect erkennt ‘The Leap’ Handbewegungen und Gesten, nur mit sehr viel höherer Präzision. Das folgende Video verschafft einen Eindruck von den Möglichkeiten:

Die Geräte sind bereits heute vorbestellbar, zu einem sensationellen Preis von knapp 70 Dollar. Mit der Auslieferung ist in den nächsten Monaten zu rechnen.

Unterrichtsmaterial

Ab sofort stelle ich kostenloses Unterrichtsmaterial für den Musikunterricht auf meiner Website zur Verfügung. Bereits heute können Sie sich einen Theorietest für Anfänger als PDF herunterladen, weitere Inhalte folgen in den nächsten Wochen, sobald ich diese aufbereitet habe. Bitte folgen Sie dem Link Downloads > Unterrichtsmaterial im Menü oben oder klicken Sie hier.