Udostępnij przez


Time-Stamped zdarzenia

Ogólny obraz synchronizacji syntezatora polega na tym, że zamiast wysyłać nutę dokładnie w momencie, gdy ma być grana, każda nuta jest oznaczona sygnaturą czasową i umieszczana w buforze. Ten bufor jest następnie przetwarzany i odtwarzany w ciągu dwóch milisekund czasu określonego przez znacznik czasu. (Chociaż rozdzielczość czasowa jest mierzona w setkach nanosekund, będziemy mówić w milisekundach, które są wygodniejszymi jednostkami czasu dla naszych rozważań.)

Ponieważ opóźnienie jest znane systemowi za pomocą zegara opóźnienia, zdarzenia z sygnaturą czasową mogą czekać w buforze na odpowiedni moment, zamiast tylko wrzucać zdarzenia do kolejki i liczyć na to, że opóźnienie będzie niskie.

Zegar główny implementuje interfejs COM IReferenceClock opisany w dokumentacji Microsoft Windows SDK. Wszystkie urządzenia w systemie używają tego czasu odniesienia.

Implementacja sinka fal firmy Microsoft tworzy wątek, który budzi się co 20 milisekund. Zadaniem wątku jest utworzenie kolejnego bufora i przekazanie go do DirectSound. Aby utworzyć ten bufor, wywołuje syntetyzator i prosi o renderowanie określonej ilości danych muzycznych. Kwota, o którą prosi, jest określana przez rzeczywisty czas, w którym wątek się budzi, co jest mało prawdopodobne, aby wynosiło dokładnie 20 milisekund.

To, co jest rzeczywiście przekazywane do syntezatora, to po prostu wskaźnik do lokalizacji w pamięci, w której można rozpocząć zapisywanie danych w buforze PCM i parametr długości, który określa ilość danych do zapisania. Następnie syntezator może zapisywać dane PCM w tym buforze i wypełniać do określonej pojemności. Oznacza to, że proces renderowania zaczyna się od adresu początkowego i trwa, dopóki nie osiągnie określonej długości. Ten blok pamięci może być directSoundBuffer (co jest domyślnym przypadkiem), ale może to być również wykres DirectShow lub inny element docelowy zdefiniowany przez ujście fali.

Bufor PCM jest koncepcyjnie cykliczny (to znaczy, że stale działa w pętli). Syntetyzator przetwarza 16-bitowe liczby opisujące dźwięk na kolejne fragmenty buforu. Rozmiar wycinka jest nieco inny za każdym razem, gdy wątek budzi się, ponieważ odbiornik nie może obudzić się dokładnie co 20 milisekund. Więc za każdym razem, gdy wątek się obudzi, gra nadrobić zaległości, aby określić, jak daleko powinno przejść przez bufor przed powrotem do snu.

Z perspektywy aplikacji sam sterownik portu synth ma funkcję IDirectMusicSynth::GetLatencyClock , która pobiera zegar z ujścia fali. Istnieją więc dwa zegary:

  • Zegar główny, którego wszyscy, w tym odbiornik fal, słuchają.

  • Zegar opóźnienia implementowany przez odbiornik fali, który aplikacja widzi jako port DirectMusic dostarczający zegar opóźnienia.

Innymi słowy, aplikacja prosi o zegar opóźnienia, ale widzi zegar pochodzący z abstrakcji portu DirectMusic, a nie z odbiornika fal.

Czas zwracany przez ten zegar opóźnienia to najwcześniejszy czas, w którym można zrenderować bufor, ponieważ syntezator już zrenderował do tego momentu w buforze. Jeśli syntezator wygenerowałby mniejszy bufor podczas ostatniego zapisu, opóźnienie byłoby również mniejsze.

W związku z tym ujście fali wywołuje metodę IDirectMusicSynth::Render na syntezatorze, przedstawiając bufor i żądając jego wypełnienia renderowanymi danymi. Jak pokazano na poniższej ilustracji, syntezator przejmuje wszystkie zdarzenia oznaczone znacznikiem czasowym, które pojawiają się w wyniku wywołań funkcji IDirectMusicSynth::PlayBuffer.

Diagram ilustrujący proces kolejkowania komunikatów opatrzonych znacznikami czasu w syntetyzatorze.

Każdy bufor wejściowy zawiera komunikaty ze znacznikami czasu. Każdy z tych komunikatów umieszcza się w kolejce do przetwarzania w buforze w czasie określonym przez jego znacznik czasu.

Jedną z ważnych kwestii dotyczących tego modelu jest to, że nie ma określonego porządku innego niż sygnatura czasowa. Te zdarzenia są przesyłane strumieniowo, więc można je dodać do kolejki w dowolnym momencie przed renderowaniem. Wszystko jest oparte na zdarzeniach w odniesieniu do czasu. Jeśli na przykład czas odwołania wynosi obecnie 400 jednostek czasu, wszystko, co jest oznaczone jako mające miejsce w czasie 400, dzieje się teraz. Zdarzenia oznaczone sygnaturą czasową wydarzą się za 10 jednostek czasu o godzinie 410.