Mit einem Analysemodul für Standard-MIDI-Files dechiffriert unser Exkurs wichtige Geheimnisse der MIDI-Norm und klärt deren Bedeutung
Das Standard-MIDI-File enthält Daten im Hex-Format, die ohne Lücke aneinandergefügt sind. Um nun dennoch eine Struktur in die Daten zu bekommen, sind sie in Blöcken organisiert und in einer ganz bestimmten Reihenfolge angeordnet. Diese Blöcke sind bezeichnet. Das Programm muß die Namen kennen, um den nachfolgenden Datenblock zu analysieren. Der erste Datenblock ist der Kopf, er heißt immer »MThd« Header-Block.
0x4D 0x54 0x68 0x64 ASCII: MThd
Der Header-Block enthält allgemeine Angaben über das gesamte Standard-MIDI-File. Den zweiten und ggfs. alle weiteren Datenblöcke nennt man »MTrk«.
0x4D 0x54 0x72 0x68 ASCII: MTrk
Track-Blöcke enthalten die Musikdaten. Dabei folgt stets auf eine »Delta-Time« ein MIDI- oder Meta-Event. Die Delta-Time ist der zeitliche Abstand zwischen zwei Ereignissen. Sie wird im »Format variabler Länge« dargestellt. Dies hat den Vorteil, daß nicht immer alle 8 Byte nötigt sind, sondern bei Zeiten bis zu 0x7F 1 Byte genügt. Somit besteht bis zu diesem Wert kein Unterschied zur üblichen Darstellungsform.
Zur Kennzeichnung der Hex-Daten verwenden wir dieselbe Kennzeichnung wie in C-Quelltexten, also »0x«.
Der ASCII-Code ist eine Teilmenge aller Hex-Codes. Die 4 Byte, die den Header-Block kennzeichnen, sind ASCII-Code und werden demzufolge auch von jedem Texteditor korrekt dargestellt. Der Turbo-C-Editor zeigt auch alle anderen Hex-Codes korrekt an, allerdings ermöglicht er keine Eingaben, da die Tastatur nur ASCII-Zeichen produziert. Hier hilft der Turbo-C-Debugger oder ein Diskettenmonitor.
Nun zur »Standard-MIDI-File«-Analyse in Listing B: Das Modul enthält neben der Funktion suchen(i) includierte Bibliotheks-Header, Definitionen, Externdeklarationen und Funktionsprototypen. Sie stellt im wesentlichen eine umfangreiche Fallunterscheidung dar, die mit switch-case und if-else-Ketten realisiert wurde.
Zuerst erscheint eine if-else-Kette zur Erkennung der Blockbezeichnungen:
MThd für Header-Block, MTrk für Track-Block und 0xFF für Meta-Events. Sie dienen dem Umstellen auf verschiedene Datenarten. Zu unterscheiden sind sie lediglich durch ihre Reihenfolge. Die Überprüfung kann bei ASCII-Daten, wie das bei MThd und MTrk der Fall ist, über einen String-Vergleich erfolgen. Das erledigt die Bibliotheksfunktion strcmp(). In allen anderen Fällen muß der Pufferinhalt Stelle für Stelle verglichen werden. Ist einer der drei Ausdrücke erkannt, wird erst einmal der Pufferinhalt durch Überschreiben mit Leerstellen gelöscht. Danach wird mit i = 0 der Pufferindex auf Anfang gesetzt und für das Erkennen eines neuen Ausdrucks vorbereitet. Da die Reihenfolge festliegt, steht also nach jedem erkannten Ausdruck fest, was danach folgt:
MThd Die Länge des Header-Blocks in nachfolgenden Bytes, deren Auswertung durch Setzen der Datenflag d_fl auf HEADERLEN vorbereitet wird.
MTrk: Die Länge des Track-Blocks in nachfolgenden Bytes, deren Auswertung durch Setzen der Datenflag d_fl auf TRACKLEN vorbereitet wird.
0xFF: Meta-Event, dessen Auswertung durch Setzen der Datenflag d_fl auf METAEVENT vorbereitet wird.
Darauf folgt die Bildschirmausgabe des erkannten Ausdrucks mit den Bibliotheksroutinen puts() oder printf(). Die Reihenfolge nach dem Erkennen eines Ausdrucks bleibt stets gleich. Was sich ändert ist die Datenart, die im Flag d_fl bestimmt wird und der Inhalt der Bildschirmausgabe. Ist also das Flag d__fl auf eine neue Datenart eingestellt, wird die Fallunterscheidung switch(d_fl) nur in dem Bereich untersucht, der zum jeweiligen Flag gehört.
Listing A:
Ist also MThd erkannt, wird d_fl = HEADERLEN gesetzt und switch(d_fl) nur noch bei case HEADERLEN durchsucht. Sind die passenden 4 Byte gefunden, wird d_fl = FORMAT gesetzt. Ist von den drei möglichen das richtige identifiziert, wird d_fl = TRACKBL gesetzt und switch(d_fl) bei case TRACKBL durchsucht. Ist die vorhandene Zahl der Track-Blöcke gefunden, wird d_fl = CLICKSPQU und switch(d_fl) bei case CLICKSPQU durchsucht. Ist die vorhandene Anzahl von Schlägen pro Viertelnote gefunden, wird ausnahmsweise d_fl nicht neu gesetzt, da ja nun MTrk folgen muß, was auch ohne Hilfe von d_fl gefunden wird.
Ist MTrk erkannt, wird wiederum das Flag gesetzt und zwar auf d_fl = TRACKLEN. Ist in switch (d_fl) die Länge des Track-Blocks gefunden, wird d_fl = DELTATIME gesetzt. Ist in switch(i) die Delta-Time gefunden, wird anschließend d_fl = MIDIEVENT gesetzt. So kann es nun im Wechsel zwischen DeltaTime und MIDI-Events beliebig lange weitergehen. An die Stelle von MIDI-Events können allerdings jederzeit Meta-Events treten. Deshalb wird ihr Statusbyte auch gleich zu Beginn jedes Suchvorgangs abgefragt, damit d_fl = METAEVENT gesetzt werden kann. Das Meta-Event ist 0x2F am Ende eines Tracks.
Auf die MIDI-Events »PROGRAM-CHANGE« folgt immer unmittelbar die Programm-Nummer. Auch hier wird die Datensteuerung in der besprochenen Art durch Setzen von d_fl auf die nachfolgende Datenart realisiert. Auf PROGRAM-CHANGE muß die Programm-Nummer und auf diese wieder eine Delta-Time folgen. Entsprechend wird mit den MIDI-Events NOTE-ON und NOTE-OFF verfahren. Sie sind allerdings dreiteilig. Ihnen folgen noch Ton-Nummer und Velocity. Erst nach der Velocity folgt die Delta-Time. Jede Spur wird mit dem MetaEvent »Track-End« abgeschlossen. Je nach Format des Standard-MIDI-Files folgen nun noch weitere Track-Blöcke. Format 0 hat immer nur einen Track-Block.
Anmerkung d. Red.: Wer etwas noch nicht ganz verstanden hat, darf sich ruhig mal eine Vorlesungen unseres Autors Prof. Walz an der FH-München Fachbereich 06 zu dem Kapitel Musik & Computer zu Gemüte führen. Die Vorlesungen stehen auch Gasthörern offen.
(mn)
Listing A
HEX-DATEN: BEDEUTUNG: 0x4D 0x54 0x68 0x64 MThd 0x00 0x00 0x00 0x06 Länge des Header-Blocks 6 Bytes nachf. 0x00 0x00 Format 0 0x00 0x01 Anzahl Track-Blöcke 0x00 0xA0 160 Schläge/Viertelnote 0x4D 0x54 0x72 0x68 MTrk 0x00 0x00 0x00 0x24 Länge des Track-Blocks 36 Bytes nachf. 0x00 Delta-Time 0 0xCO 0X0B PROGRAM-CHANGE auf Kanal 1 Progr. 11 0x00 Delta-Time 0 0xC1 0x3C PROGRAM-CHANGE auf Kanal 2 Progr. 60 0x00 Delta-Time 0 0xC2 0x33 PROGRAM-CHANGE auf Kanal 3 Progr. 51 0x00 Delta-Time 0 0x90 0x3E 0x50 NOTE-ON auf Kanal 1 d1 mf 0x64 Delta-Time 100 0x91 0x42 0x40 NOTE-ON auf Kanal 2 fis1 mp 0x64 Delta-Time 100 0x92 0x45 0x60 NOTE-ON auf Kanal 3 a1 f 0x64 Delta-Time 100 0x80 0x3E 0x50 NOTE-OFF auf Kanal 1 d1 mf 0x00 Delta-Time 0 0x81 0x42 0x40 NOTE-OFF auf Kanal 2 fis1 mp 0x00 Delta-Time 0 0x82 0x45 0x60 NOTE-OFF auf Kanal 3 al f 0x00 Delta-Time 0 0xFF 0x2F Meta-Event Track-End