Freigeben über


Zeitstempelereignisse

Das Gesamtbild des Synthesizer-Timings besteht darin, dass jede Note mit einem Zeitstempel versehen und in einen Puffer gelegt wird, anstatt sie genau in dem Moment zu senden, in dem sie gespielt werden muss. Dieser Puffer wird dann verarbeitet und innerhalb von zwei Millisekunden nach der vom Zeitstempel angegebenen Zeit wiedergegeben. (Obwohl die Zeitliche Auflösung in Hunderten von Nanosekunden liegt, werden wir in Bezug auf Millisekunden sprechen, die für diese Diskussion komfortablere Zeiteinheiten sind.)

Da die Latenz dem System über die Latenzuhr bekannt ist, können zeitgestempelte Ereignisse in einem Puffer warten, um zu dem vorgesehenen Zeitpunkt abgespielt zu werden, anstatt einfach Ereignisse in eine Warteschlange einzufügen in der Hoffnung, dass die Latenz niedrig ist.

Eine Masteruhr implementiert eine COM-IReferenceClock-Schnittstelle (in der Microsoft Windows SDK-Dokumentation beschrieben). Alle Geräte auf dem System verwenden diese Referenzzeit.

Die Implementierung der Wellenspüle von Microsoft erstellt einen Thread, der alle 20 Millisekunden aufwacht. Der Auftrag des Threads besteht darin, einen anderen Puffer zu erstellen und an DirectSound zu übergeben. Um diesen Puffer zu erstellen, ruft er den Synthesizer auf und fordert ihn auf, eine bestimmte Menge von Musikdaten zu rendern. Die Anforderung wird durch die tatsächliche Zeit bestimmt, zu der der Thread aufwacht, was höchstwahrscheinlich nicht genau 20 Millisekunden sind.

Was tatsächlich an den Synthesizer übergeben wird, ist einfach ein Zeiger auf die Position im Arbeitsspeicher, an der mit dem Schreiben von Daten in den PCM-Puffer begonnen werden soll, und ein Längenparameter, der angibt, wie viel Daten geschrieben werden sollen. Der Synth kann dann PCM-Daten in diesen Puffer schreiben und bis zur angegebenen Menge ausfüllen. Das bedeutet, dass von der Startadresse aus gerendert wird, bis die angegebene Länge erreicht ist. Dieser Speicherblock kann ein DirectSoundBuffer sein (was der Standardfall ist), aber es kann auch ein DirectShow-Graph oder ein anderes Ziel sein, das durch die Wave-Sink definiert wird.

Der PCM-Puffer ist konzeptionell zyklischer (d. a. er wird ständig durchlaufen). Der Synthesizer rendert die 16-Bit-Zahlen, die den Sound in aufeinander folgende Segmente des Puffers beschreiben. Die Segmentgröße ist jedes Mal etwas anders, wenn der Thread erwacht, da die Spüle nicht genau alle 20 Millisekunden aufwachen kann. Jedes Mal, wenn der Thread aufwacht, wird also nachholen, um zu bestimmen, wie weit er durch den Puffer voranschreiten sollte, bevor er wieder in den Ruhezustand wechselt.

Aus Sicht der Anwendung verfügt der Synth-Porttreiber selbst über die Funktion IDirectMusicSynth::GetLatencyClock, welche den Clock vom Wave-Sink abruft. Es gibt also zwei Uhren:

  • Die Meisteruhr, die jeder, einschließlich der Wellenspüle, hört.

  • Die Latenzuhr, die von der Wellenspüle implementiert wird, die von der Anwendung als DirectMusic-Port betrachtet wird, der die Latenzuhr bereitstellt.

Mit anderen Worten: Die Anwendung fragt nach der Latenzuhr, nimmt die Uhr jedoch als von der DirectMusic-Portabstraktion kommend wahr und nicht von der Wave-Senke.

Die von dieser Latenzuhr zurückgegebene Zeit ist die früheste Zeit, in der der Puffer gerendert werden kann, da der Synth bereits bis zu diesem Punkt im Puffer gerendert wurde. Wenn der Synth einen kleineren Puffer für den letzten Schreibvorgang gerendert hat, wäre die Latenz ebenfalls kleiner.

Daher ruft die Wellenspüle IDirectMusicSynth::Render auf dem Synth auf, stellt den Puffer dar und fordert an, dass er mit gerenderten Daten gefüllt werden soll. Wie in der folgenden Abbildung dargestellt, nimmt der Synth alle zeitgestempelten Ereignisse auf, die als Ergebnis von IDirectMusicSynth::PlayBuffer-Funktionsaufrufen hereinkommen.

Diagramm zur Veranschaulichung des Warteschlangenprozesses von mit Zeitstempel versehenen Nachrichten in einem Synthesizer.

Jeder Eingabepuffer enthält zeitstempelte Nachrichten. Jede dieser Nachrichten wird in eine Warteschlange eingefügt, die zu dem durch den Zeitstempel angegebenen Zeitpunkt in einen Puffer gerendert werden soll.

Eines der wichtigen Aspekte dieses Modells ist, dass es keine bestimmte Reihenfolge außer dem Zeitstempel gibt. Diese Ereignisse werden gestreamt, sodass sie jederzeit vor dem Rendern in die Warteschlange eingefügt werden können. Alles ist ereignisbasiert in Bezug auf die Zeit. Wenn die Referenzzeit z. B. derzeit 400 Zeiteinheiten beträgt, geschieht jetzt alles, was zum Zeitpunkt 400 geschehen soll. Ereignisse, die ab sofort mit einem Zeitstempel von 10 Einheiten versehen sind, werden zu Zeit 410 stattfinden.