Letzte Änderungen: 12.03.2009

Zur Hauptseite kurs9


Interner Aufbau eines MIDI/Audio-Sequencers und das OS

Das Betriebssystem/die CPU

Grundsätzlich ist natürlich jedes Betriebssystem und jede CPU für MIDI geeignet. Man sollte aber ein Multitasking-OS und ein OS mit möglichst geringer Latenzzeit verwenden. Das gute, alte Amiga OS war dafür eigentlich recht gut geeignet-auch wenn dieses keinen Speicherschutz besitzt, was die Programmierung etwas schwieriger macht. Mac OS X soll laut Apple ziemlich latenzarm sein, BeOS sowieso, Linux sollte das auch schaffen-bei Microsoft weiss man ja sowieso nie genau :-).

Ob man nun aber eine 5Ghz 64Bit CPU besitzt oder einen Amiga500 mit 7MHz ist für MIDI ziemlich unwichtig. Die hohen MHz der CPU werden dann aber bei Realtime-Audio Anwendungen interessant. MBytes spielen heutzutage auch keine Rolle mehr.

Die Latenzzeit

Intern haben die Multitasking Betriebssysteme eine Art Zeituhr (interruptgesteuert) , mit der die Tasks/Processe je nach Priorität/Bedarf angesprungen und abgearbeitet werden. Diese "Auflösung" ist wichtig um eine möglichst zeitgenaue Aufnahme/Wiedergabe/Echtzeitbearbeitung von MIDI-Events zu gewährleisten. Die Latenzzeit sollte bei MIDI nicht über 2ms (millisekunde) liegen. Grundsätzlich bringt MIDI an sich schon eine Latenzzeit von ca. 1 ms pro Note mit, da bei 31250 Baud der Transfer einer MIDI Note vom Keyboard zum Computer (ingesamt 3 Bytes) schon ca. 1 ms dauert. Das menschliche Ohr nimmt wohl Latenzzeiten ab 5 ms war, das hängt natürlich auch von der Schulung des Ohres ab. Die Latenzzeit wird natürlich weiter durch die Trägheit der MIDI-Hardware (Synthesizer, MIDI-Interface, USB-Port usw...) noch erhöht. Die CPU dürfte dabei eigentlich keine Rolle spielen - die CPU eines Amiga3000 (also 68030/25Mhz) konnte schon 1992 ca. 3000 MIDI Noten/Sekunde berarbeiten - die Latenzzeit des Multitaskings ist schon wichtiger. Also je geringer desto besser.

Die Kern-Processe/Tasks eine Sequencers

Beim Camouflage-Sequencer z.B. laufen:

a.) 3 (bei Audio-Wiedergabe 4) Processe

b.) 1 Der Clock-Interrupt

c.) Das Hauptprogramm


Das absolute Timing mit Timing-Baustein (Interrupt) - die systemspezifische Lösung

Der Clock-Interrupt ist eigentlich nur eine kleine Routine, die ständig per Interrupt angesprungen wird. Die Häufigkeit dieses Anspringens wird durch die PPQ-Rate (PPQ: Pulses per Quarter/also die interne Auflösung innerhalbs des Sequencers selbst) festgelegt. Der Clock Interrupt erhöht abhängig von der momentanen BPM (Beats per minutes) eigentlich nur einen 64-Bit-Zähler (Sequenceruhr), der ständig erhöht (beim Abspielen/Aufnehmen/FastForward), bzw. ständig verkleinert wird (Rückwartsabspielen). Diese 64-Bit-Zähler läuft eben von 0 bis x, wobei 0 dann den Sequencer an den Anfang des Songs setzt. x stellt im Prinzip die maximale Länge des Songs dar, was bei 64-Bit aber nie erreicht werden sollte - es sei denn der Song ist 2 Monate lang.

Bei 768 PPQ (das besitzt z.B. der Camouflage Sequencer) wird dieser Clock-Interrupt exakt 1536 mal in der Sekunde angesprungen. Da diese Clock-Routine aber sehr klein sein sollte, dürfte diese problemlos im Cache der CPU untergebracht sein, um dort schnell abgearbeitet zu werden, ohne das Multitasking unnötig zu blockieren.

Der Clock-Interrupt besitzt neben der Sequenceruhr noch mehrere 64-Bit Alarm-Counter, die entweder bis auf NULL runtergezählt (Sub-Counter Alarm) werden und dann ein Alarm-Signal an den entsprechenden Process schicken, oder dann ein Alarm-Signal senden, falls die Sequenceruhr diese Wert erreicht oder überschritten hat (Clock-Counter Alarm).

Beim Camouflage Sequencer hatte jeder Process je einen dieser beiden Alarm-Counter.

Das relative Timing (über das Betriebssystem) - die bessere Lösung

Falls das Betriebssystem/die Hardware nicht die Möglichkeit bereitstellt, einen konstanten Interrupt-Timer in der gewünschten Auflösung (PPQ) zur Verfügung zu stellen, gibt es noch die Möglichkeit des relativen Timings per Betriebssystem. Dabei wird eben nur ein einziger Alarm-Wert - aus allen anstehenden Alarmwerten, der nächste eben - berechnet, der relativ zum alten (bzw. Startposition des Songs) Alarmwert berechnet wird. Das komplette Timing wird somit vom Betriebssystem übernommen.

Diese Methode hat den Vorteil dass keine Interrupts benötigt werden, und dass keine Belastung des Systems durch viele Interrupts (bei 768ppq immerhin 1536 Int/sec) entsteht. Der Nachteil könnte in einer nicht so hohen Präzision des Timings liegen, da das Timing (die Alarmmeldung) eben nicht wirklich exakt erfolgen kann (wenn z.B. das Betriebssystem durch Festplattenzugriffe stark beschäftigt ist). Das relative Timing macht natürlich Sinn wenn man systemunabhängig programmieren will, was sicherlich ratsam ist. Man muss sich eben keine Gedanken über Aufbau der Hardware und der Timingchips machen. - das ist Aufgabe des Betriebssystems und der Hardware-Treiber. Man muss einfach mit der Latenz des Betriebssystems leben


Das Hauptprogramm (niedrige Priorität)

Das Hauptprogramm installiert Speicherbereiche/Daten, stellt die Oberfläche eine Sequencers zu Verfügung, und führt Dateizugriffe durch.Der Benutzer kommunziert eigentlich nur mit dem Hauptprogramm, von den Hintergrundprocessen nimmt er eigentlich nichts zur Kenntnis.

Alle Processe werden von dem Hauptprogramm (das Hauptprogramm ist zu 60% die Oberfläche/UI und Datenfunktionen) gestartet/beendet. Steuerungsbefehle werden zwischen Hauptprogramm und Processen entweder per Signal (das geht schnell, da praktisch nur ein bestimmtes Bit gesetzt werden muss) oder per Message (etwas langsamer) übermittelt. Grundsätzlich sollte man latenzwichtige Steuerungsbefehle (z.B. das Senden einer MIDI-Note) immer per Signal mitteilen.

Das Hauptprogramm selbst ist jedoch komplett von den Realtimefunktionen (also MIDI und Audio) abgekoppelt und hat keinen Einfluss auf den MIDI-Datenfluss/bzw. Audio-Datentransfer. So ist es z.B. möglich dass das Hauptprogramm 5 sekunden lang auf die Festplatte zugreift, ohne dass es irgendwelche Auswirkungen auf die Aufnahme/Wiedergabe von Audio/MIDI-Events gibt - im Hintergrund arbeiten eben die entsprechenden Processe einfach still und leise weiter.

Eigentlich könnte das Hauptprogramm deshalb auch in Basic geschrieben werden.


Der Aufnahme-Process (hohe Priorität)

Der Aufnahme Process wartet eigentlich ständig auch eintreffenden MIDI-Events um diese entweder gleich im Speicher abzulegen (falls sich der Sequencer im Aufnahme-Modus befindet) oder aber die eintreffenden MIDI-Events gleich an den ebenfalls ständig wartenden Wiedergabe Process zu schicken (falls der Thru-Modus des Sequencers aktiviert ist).

Das erste was der Aufnahme Process mit einem MIDI Event macht : Er verpasst ihm einen Zeitstempel (Timestamp), d.h. dem MIDI-Event wird gleich die aktuelle Sequenceruhr eingefügt. Somit hat man neben dem eigentlich MIDI-Event auf den entsprechenden Aufnahmezeitpunkt, schliesslich will man dieses Event später ja auch zum richtigen Zeitpunkt wiedergeben.


Der Wiedergabe Process (hohe Priorität)

Der Wiedergabe Process ist eigentlich der wichtigste Realtime-Prozess, der die MIDI-Events in Echtzeit wieder an die Synthesizer schickt. Dieser Process wartet also, das ihm vom Clock-Interrupt oder vom Aufnahme-Process ein Signal gesendet wird. Sobald er ein Alarm-Signal (d.h. ein MIDI-Event soll gesendet werden) erhält, fängt er an MIDI-Events (die sich im Speicher sortiert nach Timestamp auf den Tracks des Sequencers befinden) deren Timestamp kleiner oder gleich der aktuellen Sequenceruhr ist zu senden. Danach werden diese Events als gesendet markiert und nicht noch einmal gesendet.

Der MIDI-Ausgabe Buffer:

Beim Camouflage Sequencer hab ich es aus Gründen der Latenzzeit so gemacht, dass der Wiedergabe Process vor und während der Aufnahme immer möglichst einen MIDI-Event Buffer von 40 Events gefüllt hat. Somit konnte z.B. viele Noten die gleichzeitig oder kurz nacheinander gesendet werden sollten, schnell an die Synthesizer geschickt werden. Danach wurde dieser Buffer immer wieder aufgefüllt. Bei den schnellen CPU's von heute ist das wohl nicht mehr nötig, kann aber nicht schaden.


Der grafische Darstellungsprozess (niedrige Priorität)
Dieser Prozess hat keinen Einfluss auf die Realtime MIDI-Daten, und wartet nur das ihm ein Signal geschickt wird. Dann überprüft dieser ob in den geöffneten Fenstern irgendwo eine grafische Änderung (z.B. das Ändern der MIDI-Noten Velocity Anzeige im Trackeditor) vergenommen werden soll. Ähnlichen einem Computerspiel wird dieser grafische Prozess noch 50x Sekunde (also mit 50 Hz/sec) direkt vom Betriebssystem angesprungen, und kann somit 50 grafische Änderungen pro Sekunden vornehmen ohne irgendwelche Belastungen für den MIDI oder Audio-Datenstrom darzustellen.