Implementation eines Konfigurationsparsers mit Hilfe von boost::spirit (Teil 2a)

14. Januar 2010

Nachdem wir im ersten Teil das Format unserer Konfigurationsdateien definiert hatten, wollen wir heute einen Parser fuer dieses Format schreiben. Es waere prinzipiell moeglich, einen solchen Parser von Hand zu schreiben, allerdings ist die Spezifikation unserer Konfigurationssyntax bereits so komplex, dass wir sicherlich sehr lange brauchen wuerden, um eine funktionsfaehige und fehlerfreie Implementierung zu erhalten (sofern wir nicht ueber langjaehrige Erfahrung in der Entwicklung von Parsern verfuegen). Daher greifen wir in diesem Beispiel auf einen sogenannten Parsergenerator zurueck.

Gewoehnliche Parsergeneratoren — wie zum Beispiel lex/yacc oder ANTLR — erwarten als Input eine Beschreibung der Syntax des zu parsenden Formats, die ueblicherweise in der Backus-Naur-Form (BNF) oder einer Erweiterung davon spezifiziert wird und die wir im Folgenden als Grammatik bezeichnen. Daraus erzeugt der Generator Code in der Zielsprache, der die Syntax analysiert und diese entweder in einen Syntaxbaum transformiert oder aber mit den Symbolen verknuepfte Aktionen ausfuehrt. Diese Separierung von Grammatik und generiertem Code hat den Vorteil, dass fuer verschiedene Zielsprachen ein und dieselbe Grammatik verwendet werden kann (sofern der Generator alle gewuenschten Zielsprachen unterstuetzt). Der Vorteil schmilzt jedoch dahin, sobald man semantische Aktionen verwenden will, da diese normalerweise bereits in der Grammatik angegeben werden und in der Zielsprache verfasst sein muessen.

Die boost Library bietet fuer C++ eine wesentlich bessere und elegantere Alternative: Den spirit Parser. Dieser verwendet Paradigmen des Template Metaprogramming, um eine Domain Specific Language auf dem C++ Syntaxraum zu definieren, die der BNF erstaunlich aehnlich ist. Was bedeutet das nun? Betrachten wir als Beispiel die Definition eines Symbols fuer ein if Konstrukt in der Sprache Pascal. In BNF wuerde man schreiben:

<if statement> ::= if <expression> then <statement> |
        if <expression> then <statement> else <statement>

Die verwendeten Symbole “expression” und “statement” werden als bereits definiert vorausgesetzt. Uebertragen wir diese Regel in boost::spirit, so erhalten wir

if_statement = "if" >> expression >> "then" >> statement
             | "if" >> expression >> "then" >> statement >> "else" >> statement;

Wir sehen, dass die Umsetzung der Grammatik in spirit fast eins zu eins der Backus Naur Form entspricht. Um den Syntaxregeln von C++ zu entsprechen, muessen wir allerdings die String Literale in Anfuehrungszeichen einschliessen und die einzelnen Elemente der Regel durch den Operator “>>” trennen (C++ erlaubt nicht die einfache Aneinanderreihung von Variablen ohne Verwendung von Operatoren zwischen den Variablen, weshalb in spirit der right shift Operator zu diesem Zweck ueberladen wurde). Regeln in spirit muessen zudem mit einem Semikolon abgeschlossen werden, da diese nichts anderes als gewoehnliche Variablenzuweisungen sind. Die Regel laesst sich mit Hilfe zusaetzlicher Operatoren noch vereinfachen zu

if_statement = "if" >> expression >> "then" >> statement >> -( "else" >> statement );

Der Operator “-” macht den folgenden, in Klammern eingeschlossenen Ausdruck optional und erspart uns damit die in der vorherigen Version noch vorhandene zweite Regelvariante. Ein ausfuehrliches Benutzerhandbuch und ein Tutorial zu boost::spirit finden Sie unter http://www.boost.org/doc/libs/1_41_0/libs/spirit/doc/html/index.html.

Kommen wir zurueck zu unserem Konfigurationsformat. Wir werden nun eine Grammatik dafuer definieren, ueberspringen dabei aber den Zwischenschritt BNF und schreiben direkt gueltige spirit Regeln. Zunaechst benoetigen wir eine Regel, die die gesamte Datei repraesentiert. Eine Konfigurationsdatei besteht aus beliebig vielen Variablenzuweisungen, also definieren wir eine Regel

file = *( variableAssignment ) >> eoi;

Der Asterisk vor dem geklammerten Ausdruck steht fuer “beliebig viele”. Das abschliessende “eoi” steht fuer “end of input” und sorgt dafuer, dass sowohl Leerzeichen am Ende der Datei erlaubt sind und gleichzeitig der gesamte String gematcht wird (ansonsten koennte man in der Datei auch ungueltige Syntax verwenden, solange davor ein Teil steht, auf den das file Symbol passt).

Als naechstes muessen wir das Symbol “variableAssignment” definieren. Eine solche Variablenzuweisung besteht gemaess unserer Syntaxvorgaben aus einem Variablennamen gefolgt von einem Gleichheitszeichen und einer Variablendefinition:

variableAssignment =
      literal
   >> '='
   >> ( groupDefinition
      | listDefinition
      | arrayDefinition
      | atom
      )
   >  ';'
   ;

Wir haben also festgelegt, dass eine Variablendefinition entweder eine “groupDefinition”, “listDefinition”, “arrayDefinition” oder ein “atom” sein kann. Um den Ausdruck abzuschliessen, fordern wir noch ein Semikolon am Ende, dass in diesem Fall nicht durch “>>” sondern durch “>” angehaengt wird, was dafuer sorgt, dass der Parser sofort einen Fehler wirft, wenn die vorherigen Symbole erfolgreich erkannt wurden, das Semikolon aber fehlt. Das letzte Semikolon dient wieder dazu, das eigentliche C++ Statement abzuschliessen und gehoert nicht zur Grammatik!

Das Symbol “literal” repraesentiert einen Variablennamen. Um gaengigen Programmiersprachenkonventionen zu folgen, erlauben wir hierfuer nur alphanumerische Zeichen und den Unterstrich “_”, und definieren zusaetzlich, dass das erste Zeichen keine Ziffer sein darf. Dies fuehrt uns auf folgende Regel:

literal = (alpha | '_') >> *(alnum | '_');

Ein “literal” ist also ein Buchstabe oder ein Unterstrich gefolgt von beliebig vielen Buchstaben, Ziffern oder Unterstrichen. Beachten Sie bitte, dass nur die zweite Teilregel optional ist, der Variablenname muss also mindestens ein Zeichen lang sein (was Sinn macht).

Betrachten wir nun noch die Definition des Symbols “atom”, das wir wie folgt festlegen:

atom = (strict_double | long_ | bool_ | string);

“long_” und “bool_” sind in spirit bereits eingebaute Parser, die, wie der Name schon suggeriert, long und bool Werte parsen. Das Symbol “string” wollen wir selbst definieren. Es sieht folgendermassen aus:

string = lexeme[ lit('"') >> *(char_ - '"') > '"' ];

Ein String in unserer Konfigurationsdatei muss also mit einem Anfuehrungszeichen beginnen, gefolgt von beliebig vielen Zeichen ausser dem Anfuehrungszeichen (char_ ist wieder ein eingebauter Parser und matcht jedes beliebige Zeichen), abgeschlossen von einem weiteren Anfuehrungszeichen.

Die Regel “strict_double” muessen wir noch definieren. Ein einfaches “double_” wuerde hier nicht wie gewuenscht funktionieren, da es auch Zahlen ohne Dezimalpunkt parst und damit der long_ Parser nie aufgerufen wuerde. Die Reihenfolge der Regeln umzukehren ist jedoch auch keine Loesung, da Floating Point Zahlen in der Regel mit einer Ziffer beginnen und daher die long_ Regel saemtliche Ziffern bis zum Dezimalpunkt konsumieren wuerde, waehrend die uebrigen Zeichen bis zum abschliessenden Semikolon nicht mehr in die Grammatik passen und somit einen Parser Fehler ausloesen wuerden. Wir koennen jedoch mit nur einer Zeile Code einen modifizierten double Parser definieren, der zwingend einen Dezimalpunkt voraussetzt:

namespace qi = boost::spirit::qi;
qi::real_parser<double, qi::strict_real_policies<double> > strict_double;

Fehlen noch die Symbole “groupDefinition”, “listDefinition” und “arrayDefinition”. Diese sind umfangreicher, weshalb wir ihnen einen separaten Teil widmen wollen (2b).

admin Programmierung

Implementation eines Konfigurationsparsers mit Hilfe von boost::spirit (Teil 1)

30. November 2009

In der folgenden Serie von Artikeln beschaeftigen wir uns mit einer immer wiederkehrenden Aufgabenstellung der meisten C++ Programmierer: Der Entwicklung eines Parsers fuer Konfigurationsdateien. In meinen knapp 15 Jahren Programmierpraxis habe ich mindestens 10 verschiedene Parser fuer beinahe genauso viele verschiedene Konfigurationsformate geschrieben — vom simplen INI-Reader bis zum validierenden XML-Parser war alles dabei. Bezueglich des Aufwands sind die beiden genannten Beispiele entgegengesetzte Extreme. INI Dateien lassen sich mit ein paar Zeilen Code einlesen und schreiben und mit geringem Mehraufwand auch in einen Objektbaum uebersetzen. XML hingegen verlangt dem Programmierer einiges an Disziplin und Schreibarbeit ab, um die umfangreichen DOM- bzw. SAX-Schnittstellen zu implementieren, insbesondere wenn man die komplette Spezifikation erfuellen will und gleichzeitig noch eine Validierung benoetigt.

Beide Formate — INI wie XML — sind ausserdem meines Erachtens fuer die Verwendung als Konfigurationsdateien denkbar ungeeignet. Waehrend XML dem Entwickler zwar alle Moeglichkeiten bietet, die Konfigurationen hierarchisch zu gliedern und beliebige Metadaten dazu abzuspeichern, ist die Verwendung von Begin- und Endtags doch in hoechstem Masse speicherineffizient und redundant. Weiterhin gibt das Format an sich keinerlei Typstruktur vor, weshalb diese entweder zusaetzlich auf das Format aufgesetzt oder vom Benutzer gehandhabt werden muss (dies hat natuerlich Vor- und Nachteile!). Wer nun glaubt, mit INI Dateien besser zu fahren, der wird schnell an Grenzen stossen. Lediglich eine einzige Hierarchieebene ist fuer die meisten Anwendungen zu wenig. Und auch hier gibt es ausser Strings keine vorgegebenen Typen.

Welche Anforderungen muss eine Konfigurationsdatei erfuellen, um moeglichst viele Anwendungsbereiche abzudecken? Fassen wir diese in einem kurzen Brainstorming zusammen:

  • Unterstuetzung der wichtigsten atomaren Typen (int, float, bool, string)
  • Bereitstellung von Listen und Arrays als zusammengesetzte Datentypen (Listen koennen dabei beliebige Subelemente enthalten, Arrays hingegen sind homogene Felder atomarer Typen)
  • Einfuehrung unbegrenzter Hierarchieebenen durch Verwendung von assoziativen Arraystrukturen, die mehrere Variablendeklarationen zusammenfassen und rekursiv definiert werden koennen

Damit haben wir Listen-, Array- und hierarchische Semantik gleichermassen abgedeckt und kommen damit schon sehr nahe an das XML Format heran. Bezueglich der Syntax des Formats habe ich mich an C++ bzw. Python angelehnt und fasse Listen in runden, Arrays in eckigen und assoziative Arrays in geschweiften Klammern zusammen. Ein Beispiel fuer eine solche Konfigurationsdatei ist im folgenden dargestellt:

a = 5;
b = 6.5;
c = {
  var1 = "Hallo";
  pi = 3.14159;
};
d = (
  { eins = 1; zwei = 2; drei = 3; },
  [1, 2, 3, 4, 5]
);

Im naechsten Teil der Serie schreiben wir unter Zuhilfenahme der neuesten Version von boost::spirit (v2.1) einen Parser fuer das soeben definierte Format. Im dritten Teil werden wir eine Objektstruktur fuer die C++ API definieren und diese im letzten Teil mit dem Parser verknuepfen.

admin Allgemeines

Rezension: Neue Jazz-Harmonielehre (Frank Sikora)

25. Oktober 2009

“Warum (noch) eine Harmonielehre?” fragt Frank Sikora in der Einleitung zu seinem Buch zu Recht. Eine Vielzahl von Büchern beschäftigt sich bereits ausführlich mit diesem Thema, und der interessierte Musiker mag der Meinung sein, dass sämtliche offenen Fragen in der bisherigen Literatur zufriedenstellend beantwortet werden. Leider deckt jedoch die intensive Beschäftigung mit Jazz, Improvisation und der Komposition im Allgemeinen Lücken im persönlichen Bild der Harmonielehre auf, die mit den üblichen Regeln und Tabellen, die viele Autoren offenbar als äußerst informativ und nachvollziehbar erachten, nicht gefüllt werden können. Ebenso fehlt in so gut wie allen Büchern ein adäquater Bezug zur musikalischen Praxis, der eigentlich im Mittelpunkt einer guten Harmonielehre stehen sollte. Was nützt das beste theoretische Wissen, wenn dieses nicht am Instrument oder Notenpapier umgesetzt werden kann?

Nach einem ersten Überfliegen des Buches steht fest: Dies ist nicht die durchschnittliche, anfängerkompatible Abhandlung über Harmonielehre. Hier werden auf den ersten 150 Seiten schon Themen behandelt, die in anderen Büchern nicht einmal im Anhang angesprochen werden. Wer nun glaubt, dass Sikora dafür weniger ins Detail geht als andere Autoren, der irrt: Jede noch so scheinbar unbedeutende Kleinigkeit wird genau erklärt und hervorragend motiviert. Selbst erfahrene Musiker werden erst beim Lesen dieses Buches verstehen, dass sie ganze Teilbereiche der Harmonielehre bisher schlichtweg nicht kannten.

Die Zielgruppe dieses Buches sind tatsächlich nur gut vorgebildete Musiker – Anfänger werden bereits nach den ersten 20 Seiten kapitulieren. Sikora macht es mit seinem hervorragenden Schreibstil und seinem zweifelsohne hervorragenden pädagogischen Talent dem interessierten Leser jedoch leicht, seinen Ausführungen zu folgen. Zahlreiche qualitativ hochwertige Notenbeispiele helfen, die harmonischen Zusammenhänge zu erfassen und zu verstehen.

Auf den ersten 40 Seiten werden die Grundlagen wie Tonsystem, Obertonreihe, Intervalle, Quintenzirkel und die Akkordsymbolschrift wiederholt. Weiter geht es mit detaillierten Ausführungen zu Modalität, Dur-Diatonik, Kadenzen, Skalen und ihre Beziehung zur Akkordsymbolik bis hin zu Modaler Harmonik und Modal Interchange. Bemerkenswert sind auch die mehr kompositorisch motivierten Kapitel zum musikalischen Motiv, Melodiebogen und harmonischen sowie melodischen Rhythmus.

Die verbleibenden 200 Seiten des Buches sind den Themen “Hören” und “Spielen” gewidmet und versuchen – soweit das mit einem Buch möglich ist – eine Beziehung zwischen dem inneren Ohr und dem Instrument herzustellen und sind nicht nur für Pianisten interessant. Auch das Kapitel zur Transkription ist hervorragend geschrieben und so ausführlich, dass keine Fragen offen bleiben dürften. Wer genug Eigenmotivation mitbringt, den werden Sikoras Ausführungen sicherlich einen großen Schritt weiterbringen.

Die Neue Jazz-Harmonielehre ist ein bemerkenswertes Buch – meines Erachtens sogar die beste Wahl zu diesem Thema – und sollte in keinem Bücherregal fehlen. Erfreulich ist auch der geringe Preis des Buches, das kaum mehr kostet als beispielsweise der hochgelobte Haunschild, der auf 150 Seiten jedoch sehr viele wichtige Themen auslässt und meiner Meinung nach vergleichsweise schlecht motiviert ist.

admin Musik

Python Embedding mit boost::python

27. März 2009

In vielen Fällen ist es wünschenswert, dem Benutzer einer Software die Möglichkeit zu geben, mittels einer Skriptsprache Erweiterungen zur Anwendung hinzuzufügen oder deren Laufzeitverhalten zu steuern oder zu ändern. Das Einbetten einer Skriptsprache in die Applikation ist allerdings kein einfaches Vorhaben, da Daten und Datenstrukturen zwischen den Sprachkontexten ausgetauscht werden müssen um der Skriptsprache Zugriff auf Objekte der Applikation zu ermöglichen und umgekehrt.

Dieser Artikel soll eine kurze Einführung in die Einbettung von Python in C++ Applikationen geben. Dabei wird die Library boost::python benutzt, die das Marshalling der Objekte erheblich vereinfacht (z.B. Kapselung der ansonsten notwendigen manuellen Verwaltung der Garbage Collection).

Betrachten wir ein sehr einfaches Python Modul mymodule.py mit nur einer einzigen Funktion:

def myfunction():
  c = 1234 * 5678
  print 'Result is', c

Um diese Funktion aus einem C++ Kontext heraus aufzurufen, schreiben wir folgendes minimales Programm:

#include <boost/python/import.hpp>
#include <boost/python/call.hpp>
using namespace boost::python;
 
int main(int argc, char* argv[])
{
	Py_Initialize();
 
	object mymodule = import("mymodule");
	object dict = mymodule.attr("__dict__");
	object func = dict["myfunction"];
	func();
 
	Py_Finalize();
	return 0;
}

Die Aufrufe von Py_Initialize() und Py_Finalize() sind notwendig, um den eingebetteten Python Interpreter zu initialisieren bzw. sicher herunterzufahren. Im nächsten Schritt wird zunächst das Modul importiert, das Dictionary des Moduls geholt und schließlich eine Referenz auf die gewünschte Funktion erzeugt. Mit func() wird diese anschließend aufgerufen.

Durch die Verwendung von boost::python werden die zurückgelieferten PyObject Pointer in object Objekten gekapselt, die sich um die Garbage Collection kümmern. Dadurch spart man sich gegenüber der nativen Python API das manuelle Freigeben der Pointer mittels Py_DECREF().

Auf manchen Systemen (z.B. Linux) funktioniert der Aufruf jedoch nicht wie gewünscht, da der eingebettete Python Interpreter das Modul nicht findet. Der Grund dafür ist, dass der Pfad, in dem das C++ Programm ausgeführt wird, nicht automatisch als Suchpfad für Python Module hinzugefügt wird. Die Lösung für dieses Problem ist meines Wissens nicht dokumentiert und besteht einfach darin, mittels PySys_SetArgv(1, progName) einen beliebigen Namen für das Programm zu setzen (oder einfach die argc und argv Parameter der main Methode zu benutzen). Das komplette Programm sieht dann beispielsweise folgendermaßen aus:

#include <boost/python/import.hpp>
#include <boost/python/call.hpp>
using namespace boost::python;
 
int main(int argc, char* argv[])
{
	Py_Initialize();
	char* progName[] = { const_cast<char*>("Embedded") };
	PySys_SetArgv(1, progName);
 
	object mymodule = import("mymodule");
	object dict = mymodule.attr("__dict__");
	object func = dict["myfunction"];
	func();
 
	Py_Finalize();
	return 0;
}

admin Programmierung

Das ROOT Framework aus softwarearchitektonischer Sicht

6. Februar 2009

Das Fachgebiet der experimentellen Teilchenphysik beschäftigt sich mit der Suche und dem Nachweis neuer Teilchen und der Vermessung ihrer Eigenschaften. In großen Beschleunigern werden stabile Teilchen mit hohen Energien zur Kollision gebracht. Aufgrund der Äquivalenz von Energie und Masse können dabei neue Partikel erzeugt werden, deren Spuren und Energiedepositionen mit geeigneten Detektoren gemessen werden. Aus diesen Messungen lassen sich Parameter wie Impuls, Ladung, Masse etc. ableiten. Die dabei aufgezeichneten Daten müssen von Physikern verknüpft, analysiert und visualisiert werden.

Das Softwareframework ROOT, das dabei zum Einsatz kommt, ist ein Musterbeispiel für misslungene Softwarearchitektur. Die Behauptung, es handle sich um ein System, das objektorientierte Paradigmen konsequent umsetzt, ist nicht haltbar. Betrachten wir als Beispiel die Vererbungshierarchie der Histogrammklassen.

th2_inh

Die erste Frage, die sich stellt, ist, warum das zweidimensionale Histogramm TH2 vom eindimensionalen TH1 erbt. Dies wäre durchaus verständlich, wenn TH2 lediglich gemeinsame Funktionalität von TH1 übernehmen würde. In Wirklichkeit besitzt jedoch schon TH1 eine virtuelle Funktion GetYAxis() und sogar ein GetZAxis(), da in der weiteren Hierarchie auch TH3 von TH2 und damit von TH1 erbt.

Viel interessanter als dieser (schon recht verwunderliche) Umstand ist jedoch die Tatsache, dass alle Histogrammklassen direkt oder indirekt von den “Attributklassen” TAttLine, TAttFill und TAttMarker erben. Diese beschreiben Eigenschaften von Linien, Flächen oder Symbolen wie Strichstärke, Farbe, Muster o.Ä. Hier wird die eigentlich einer Vererbung zugrunde liegende is-a-Relation ad absurdum geführt. Ist ein Histogramm eine Linie? Ist es eine Füllfläche? Oder ist es vielleicht ein Marker? Nein, es besitzt vielmehr einen Satz von Linien-, Flächen- und Markereigenschaften. Die korrekte Umsetzung dieser Relation würde darin bestehen, den Histogrammklassen Membervariablen vom Typ TAttLine, TAttFill und TAttMarker zu geben. Um Schreibarbeit bei der späteren Verwendung der Objekte zu sparen, wurde jedoch kurzerhand die Vererbung gewählt. Was die Konsequenzen davon sind, lässt sich leicht an dem folgenden Vererbungsdiagramm aus der ROOT Dokumentation erkennen:

tattline_inh

Man beachte insbesondere die Anmerkung “…and more”. Dieser Vererbungswahnsinn ist nicht nur unpraktisch, da er das Einbinden der entsprechenden Header in jeder der erbenden Klassendefinitionen erfordert (und im Übrigen auch die Verwendung von Pimpl unmöglich macht), sondern sogar gefährlich, da er die starke Typisierung von C++ durch übertriebenen Polymorphismus aufweicht. Dies ist jedoch nur ein willkürlich herausgegriffenes Beispiel. Im gesamten Framework wird konsequent vererbt, wo Vererbung nicht notwendig und teilweise sogar unerwünscht ist.

Ein weiterer sträflich vernachlässigter Punkt ist die Trennung von Daten und Darstellung. Warum muss TH1 überhaupt Informationen über Strichstärke und Füllfarbe speichern, wenn seine eigentliche Aufgabe die Speicherung der Histogrammdaten ist? Diese beiden disjunkten Aufgabenbereiche in einer Klasse zu kombinieren mag vielleicht den Vorteil haben, nur mit einem Objekt interagieren zu müssen. Schon bei der Serialisierung stellt sich aber die Frage, ob man wirklich zu jedem Histogramm vorgegebene Darstellungsattribute abspeichern will. Sinnvoller wären Painter Objekte, die die Darstellungsinformationen enthalten und beliebige Histogrammdaten zeichnen können. Auch diese Objekte könnte man für eine spätere Wiederverwendung serialisieren und nötigenfalls Histogramme mit zugehörigen Paintern verknüpfen.

Es gibt jedoch auch positive Aspekte des ROOT Frameworks, die hervorgehoben werden sollten. Für die Serialisierung von Objekten bietet ROOT einen relativ komplexen Mechanismus, der auf der Generierung von Klassen-Metadaten mit Hilfe eines speziellen Codeprozessors basiert. Die generierten Klassen beschreiben Membervariablen und Methoden der zu serialisierenden Klassen und erlauben so eine automatische Persistierung bzw. Erstellung von Objekten aus persistierten Daten. Gleichzeitig erlaubt dieser Mechanismus auch den Zugriff auf die Klasseninformationen, um Benutzerinteraktion über einen speziellen C++ Interpreter zu ermöglichen. Alle Klassen, für die Metadaten generiert wurden, können so im Interpreter instanziiert und verwendet werden.

admin Programmierung

Liliypond: TeX für Noten

5. Februar 2009

Das Satzsystem TeX / LaTeX erfreut sich insbesondere im akademischen Umfeld großer Beliebtheit, da es eine Trennung von Inhalt und Formatierung erlaubt, wie sie mit Textverarbeitungsprogrammen nicht möglich ist. Seit geraumer Zeit existiert ein System, das vergleichbare Funktionalität für den Notensatz anbietet. Der Benutzer verfasst dazu eine Textdatei, die die zu setzenden Noten in Form einer Art Markup Language beschreibt. Lilypond erstellt daraus direkt den fertigen Satz.

Im direkten Vergleich mit den kommerziellen Notensatzprogrammen Finale und Sibelius muss sich Lilyponds Satz nicht verstecken. Im Gegenteil, das Ergebnis der kostenfreien Lösung wirkt eher wie von einem professionellen Notensetzer erstellt als das von Sibelius. Finale bildet hier das Schlusslicht. Seine Resultate wirken sehr künstlich und unnatürlich.

Den großen Funktionsumfang eines Finale kann Lilypond im Hinblick auf den Notensatz nicht erreichen (zumindest ist dieser nicht leicht zugänglich). Es liegt in etwa auf einer Höhe mit Sibelius. Spezielle Notationen wie feathered beams o.Ä. können zwar über Umwege bewerkstelligt werden, richtig integriert scheint die Funktionalität aber weder in Sibelius noch in Lilypond.

Der größte Nachteil von Lilypond ist die fehlende Möglichkeit zur akustischen Wiedergabe. Dies behindert den kompositorischen Prozess erheblich. Das mag weniger problematisch sein, wenn bereits fertiggestellte, handschriftliche Partituren gesetzt werden müssen, ist aber ein gravierender Nachteil für den explorativen Komponisten.

Während bei TeX das Fehlen einer WYSIWYG Ansicht als Vorteil geschätzt wird, ist es für den Notensetzer eher eine Behinderung. Eine Trennung von Inhalt und Formatierung ist in der Notenschrift nicht einfach, da sie in vielen Fällen unmittelbar zusammenhängen.

Lilypond ist durchaus in die engere Auswahl einzubeziehen, wenn eine günstige und qualitativ hochwertige Notensatzlösung benötigt wird. Aufgrund der fehlenden Flexibilität und der mangelnden Unterstützung explorativer oder feedbackbasierender Kompositions- und Satztechniken ist das Einsatzgebiet jedoch sehr eingeschränkt.

Als hilfreich hat sich allerdings die Integration von Lilypond in LaTeX erwiesen (Stichwort lilypond-book), die es ermöglicht, direkt Lilypond Codefragmente in LaTeX Dokumenten zu verwenden. Insbesondere für das Verfassen musikwissenschaftlicher und -theoretischer Dokumente oder Lehrmaterialien ist diese Kombination den bisherigen Vorgehensweisen (Satz in Sibelius oder Finale, Kopieren der Grafik, Einfügen im Textverarbeitungs- oder DTP-Programm) weit überlegen.

admin Musik

Funktionale Programmierung und Template Metaprogramming

5. Februar 2009

Funktionale Programmiersprachen sind in den letzten Jahren immer beliebter geworden, denn viele Abläufe lassen sich funktional einfacher und in weniger Zeilen formulieren als in imperativen Sprachen (eine Kombination dieser Paradigmen bietet übrigens die Programmiersprache F#, die von Microsoft entwickelt wurde).

Betrachten wir zum Vergleich die Berechnung der Exponentialfunktion (für ganzzahlige Exponenten). Eine sehr naive Implementierung in C++ würde wie folgt aussehen:

long exp(long base, long exponent)
{
	long result = 1;
	for(long i=0; i<exponent; ++i)
	{
		result *= base;
	}
 
	return result;
}

Diese Formulierung mag zwar einfach sein, ist aber nicht besonders intuitiv. Die rekursive Definition erscheint eingängiger:

long exp(long base, long exponent)
{
	if(exponent == 1)
		return base;
	else
		return exp(base, exponent-1) * base;
}

Die Schleife wurde ersetzt durch einen rekursiven Aufruf der Funktion. Lediglich das verbliebene if verunreinigt die funktionale Definition. Betrachten wir das selbe Beispiel in der funktionalen Programmiersprache Erlang:

exp(B, 1) ->
	B;
exp(B, E) ->
	exp(B, E-1) * B.

Hier wird ebenfalls eine Funktion exp definiert, allerdings wird diese für den zweiten Parameter “überladen”. Die in C++ notwendige if Abfrage wird dadurch in die Funktionsdefinition absorbiert (streng genommen ist die Abfrage E == 1 immer noch vorhanden, sie wird aber implizit vom Erlang Interpreter vorgenommen).

Weitgehend unbekannt unter den meisten C++ Programmierern ist die Tatsache, dass sich mit Hilfe von Templates funktionale Paradigmen abbilden lassen, die statische Code-Generierung und/oder Berechnungen zur Compile-Zeit zulassen. Die obige rekursive Definition der Exponentialfunktion lässt sich beispielsweise folgendermaßen implementieren:

template<int B, int E>
struct Exp
{
	enum { value = Exp<B, E-1>::value * B };
};
 
template<int B>
struct Exp<B, 1>
{
	enum { value = B };
};

Durch Verwendung von Exp<2, 4>::value im Code berechnet der Compiler rekursiv den Wert der Exponentialfunktion. Wichtig anzumerken ist allerdings, dass dieser Wert lediglich als Konstante verwendet werden kann, denn aufgrund des statischen Charakters von Templates ist eine dynamische Berechnung so nicht möglich.

Man mag sich nun fragen, welchen Mehrwert Template Metaprogramming bietet, wenn keine dynamischen Berechnungen damit möglich sind. In unserem Beispiel mag das auf den ersten Blick nicht deutlich werden, aber betrachten wir dazu das folgende Codefragment:

#define THREETOTHEPOWEROFSIX	729
#define FIVETOTHEPOWEROFSEVENTEEN	762939453125
 
long stupid_function(int a)
{
	return a * pow(3, 6) * pow(5, 17);
}
 
long normal_function(int a)
{
	return a * THREETOTHEPOWEROFSIX * FIVETOTHEPOWEROFSEVENTEEN;
}
 
long nice_function(int a)
{
	return a * Exp<3, 6>::value * Exp<5, 17>::value;
}

Welche Funktion ist performanter und leichter verständlich? Weitere interessante Betrachtungen des Template Metaprogramming folgen in einem der nächsten Artikel.

admin Programmierung

Akkumulierende Mean Berechnung in C++

5. Februar 2009

Für die Berechnung einer Threadpool Idle Statistik muss jeder Worker-Thread den Zeitpunkt des Arbeitsbeginns und den Zeitpunkt des Arbeitsendes (= Beginn der Idle Phase) loggen. Die Differenz dieser Timestamps ergibt die Verarbeitungsdauer eines einzelnen Work-Items bzw. komplementär das jeweilige Idle Intervall. Für die Berechnung des Mittelwerts der Idle-Intervalle müssen die einzelnen Samples akkumuliert werden. In einer Server-Applikation kann eine permanente Akkumulation durch Aufsummierung problematisch werden, da je nach Größe der einzelnen Samplewerte und Laufzeit des Systems die Wertebereiche von 64 bit Variablen überschritten werden können. Dieses Problem lässt sich durch Mittelung über eine fixe Anzahl von Samplewerten umgehen.

Dazu wird zunächst eine fixed_size_queue Datenstruktur mit fester Maximalgröße eingeführt. Diese erbt sämtliche Funktionalität von std::queue und überschreibt lediglich die push() Funktion, um beim pushen in eine volle Queue ein altes Element zu droppen.

template<class T, class Sequence = std::deque<T> >
class fixed_size_queue : public std::queue<T, Sequence>
{
public:
	typedef T value_type;
	typedef typename Sequence::size_type size_type;
 
	typedef typename Sequence::const_iterator const_iterator;
 
	explicit fixed_size_queue(size_type maxSize)
		: maxSize_(maxSize)
	{}
 
	void push(const value_type& value)
	{
		if(this->size() == maxSize_)
			this->pop();
 
		std::queue<T, Sequence>::push(value);
	}
 
	const_iterator begin() const { return this->c.begin(); }
	const_iterator end() const { return this->c.end(); }
 
protected:
	size_type maxSize_;
};

Unter Verwendung dieser Klasse lässt sich nun ein Akkumulator definieren. Dieser wird im Konstruktor auf ein wählbares Sample-Integrationsintervall festgelegt. Nachdem Samples in die interne fixed_size_queue eingefügt wurden, kann über die Funktion value() der aktuelle Mittelwert abgerufen werden.

/**
 * @brief Sliding window sample accumulator for calculating the mean value
 *
 * Please note that SampleType can be any type or class that is convertible
 * to double.
 */
template
<
	typename SampleType,
	template <class> class MeanCalculator = Average
>
class MeanAccumulator
{
public:
	typedef SampleType sample_type;
	typedef typename MeanCalculator<sample_type>::mean_type mean_type;
 
	MeanCalculator<sample_type> mean_calc_;
 
public:
	explicit MeanAccumulator(
			typename fixed_size_queue<sample_type>::size_type maxSize)
		: samples_(maxSize)
	{}
 
	void operator()(sample_type sample)
	{
		samples_.push(sample);
	}
 
	const mean_type value() const { return mean_calc_(samples_); }
 
protected:
	fixed_size_queue<sample_type> samples_;
};

Um die Berechnung des Mittelwerts generisch zu halten, wurde eine Policy für dessen Berechnung eingeführt. Die Standard Policy wurde dabei auf Average festgelegt, das wie folgt definiert ist:

template<typename SampleType>
class Average
{
public:
	typedef SampleType sample_type;
	typedef double mean_type;
	typedef fixed_size_queue<sample_type> queue_type;
 
	const mean_type operator()(
			const queue_type& samples) const
	{
		mean_type sum = 0.0;
 
		typename queue_type::const_iterator it = samples.begin();
		for(; it != samples.end(); ++it)
		{
			// Divide each summand by size to reduce chances of overflow
			sum += static_cast<mean_type>(*it) / samples.size();
		}
 
		return sum;
	}
};

Bei der Berechnung des Mittelwerts wird jedes Sample auf den mean_type (double) konvertiert. Damit ist auch die Verwendung von Klassentypen als Sampletyp möglich, sofern diese einen operator double implementieren. Die Division durch die Anzahl der Samples erfolgt in jedem Summanden. Dies reduziert weiter die Wahrscheinlichkeit eines Overflows.

admin Programmierung

Nokia E63 Testbericht

15. Januar 2009

Mit dem E63 stellte Nokia im November 2008 das Budget Modell des erfolreichen E71 vor. Seit Anfang diesen Jahres ist das E63 nun endlich in den meisten Shops verfügbar, und vor zwei Wochen erhielt auch ich mein lange erwartetes Exemplar.

Der Funktionsumfang des E63 unterscheidet sich nur in wenigen Punkten von dem des E71. Verzichten muss man zum einen auf das Metallgehäuse; das E63 besteht komplett aus Plastik, macht jedoch trotzdem einen hochwertigen Eindruck und ist gut verarbeitet. Zum anderen wird kein HSDPA unterstützt. Da das Handy jedoch ohnehin primär für die Email Kommunikation ausgelegt ist, kann man das durchaus verschmerzen. Weiterhin besitzt das E63 keinen GPS Empfänger und lediglich eine 2 Megapixel Kamera. Eine zweite Kamera für Videotelefonie fehlt ebenso.

Beim Auspacken fiel zunächst das kompakte Design des Geräts auf. Für ein Handy mit Volltastatur ist das E63 angenehm klein ausgefallen. Alle Tasten haben einen knackigen Druckpunkt. Die QWERTZ Tastatur ist vielleicht ein bisschen zu klein ausgefallen, lässt sich aber trotzdem auch mit größeren Fingern noch recht gut bedienen. Die Positionierung der Umlaute ist gewöhnungsbedürftig.

Das E63 bietet alle Organizer Funktionen, die man im beruflichen Alltag benötigt. Termine und Kontakte lassen sich schnell und komfortabel anlegen. Der Kalender des Symbian Geräts ist meines Erachtens sehr gut gelungen und wird trotz des relativ kleinen Displays übersichtlich dargestellt.

Beim Verfassen von Kurzmitteilungen und Emails schlägt sich das Nokia Handy wie erwartet sehr gut. Die Eingabe über die Tastatur ist schnell und präzise. Eine optionale, unaufdringliche Worterkennung bügelt kleinere Schreibfehler zuverlässig aus.

Neben Email Push Services unterstützt das E63 auch den Zugriff auf Standard Email Accounts wie GMX, Google Mail o.ä. Dazu kann auch eine eventuell vorhandene WLAN Verbindung genutzt werden. Beim Verlassen des Email Menüs wird diese allerdings automatisch wieder getrennt. Bisher habe ich noch keine Möglichkeit gefunden, die Verbindung offen zu halten, um über eingehende Emails auf einem IMAP Account informiert zu werden.

Natürlich lässt sich mit dem E63 auch telefonieren. Die Sprachverständlichkeit ist gut und die Lautstärke der Klingeltöne auch für laute Umgebungen ausreichend. Über entgangene Anrufe oder eingegangene Mitteilungen informiert das Handy mit einem dezenten Blinken um die Bestätigungstaste. Diese visuelle Benachrichtigung ist jedoch leider nicht besonders auffällig und kann leicht übersehen werden.

Die Akkulaufzeit des Handys beträgt im Standby bei deaktiviertem UMTS und Bluetooth etwa 7 Tage. Für die gebotene Funktionalität ein durchaus akzeptabler Wert.

Im Großen und Ganzen ist das E63 ein würdiger Gegner für die überteuerten Blackberry Smartphones. Zu dem geringen Preis von etwa 230 € kann man eine uneingeschränkte Kaufempfehlung aussprechen.

admin Technologie

Neue Website

13. Januar 2009

Die Website ist auf einen neuen Server umgezogen und wird zurzeit aktualisiert. Bitte schauen Sie in ein paar Tagen nochmal vorbei.

admin Allgemeines