Niedokładny pomiar czasu (MIDI)

0

Opis - wersja skrócona ;-)

Muszę określić częstotliwość odbieranych komunikatów. Konkretnie to wygląda tak, że dopiero 24 odebrane komunikaty stanowią 1 jakąś tam "wartość". Muszę policzyć ilość tych "wartości" w ciągu minuty ;-)

Opis - wersja dłuższa ;)
Tempo utworu w MIDI określa ilość ćwierćnut na minutę, potocznie zwana BPM. Jednak przez MIDI nie przesyła się konkretnej wartości tego tempa w postaci liczby, a generuje się komunikaty zegarowe. Odbiornik zlicza je i na tej podstawie oblicza wspomniane BPM. Na jedną "B" z tego BPM przypada 24 komunikaty. Czyli dla tempa 60 BPM tych komunikatów w ciągu sekundy musi być 24, dla 120 BPM 48 itd.

Odbieram te komunikaty i chcę wyliczyć tempo. Teoretycznie wydawało się to proste, praktycznie mam problem z dokładnością tych wyliczeń. Z braku mądrzejszego pomysłu robię tak:

  1. Tworzę zmienną TStrings Lista, wpisuję do niej 24 po kolei pobrane wartości GetTickCount i przekonwertowane na String
  2. Zaczynam pomiar - odbieram te komunikaty zegarowe - przy każdym wrzucam jak wyżej - przekonwertowany GetTickCount na koniec listy i usuwam pierwszy element - więc zawsze mam 24 elementy na liście.
  3. TTimerem co 1 sek obliczam (po przekonwertowaniu na Int64) różnicę czasu między piewszym a ostatnim elementem na liście. No i 60000 (ilość ms w minucie) podzielone przez tę różnice daje mi wartość BPM.

Problem leży w dokładności tego pomiaru. Wiem na pewno, że urządzenie nadawcze generuje ten zegar np. 120 BPM, a mnie w programie wychodzi 122 - 123 BPM. I z reguły zawsze jest to zawyżone.

Gdzie jest haczyk? Jak to powinno wyglądać "po ludzku"? (taka procedura pomiarowa...)

Pozdrawiam,
Jacek

0

nie wiem jaki ty sposob wykorzystujesz dokladnie ale pod systemem operacyjnym jakim jest windows lepij skorzystac po prostu z winapi patrz: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_using_the_mci_midi_sequencer.asp tam masz opisane jak odczytac tempo aktualnie odtwarzanego midi z urzadzenia

0
  1. z tego co zaobserwowałem to GetTickCount nie jest zbyt dokładna (z resztą timeGetTime też).

  2. zamiast (o zgrozo :>) TStrings użyj circular buffer o rozmiarze 24 (typu DWORD). Chociaż po co bufforować? nie wystarczy zmierzyć czas tych 24 tick'ów (czyli ćwierć-nuty) zamiast robić to dla każdego z osobna???

PS. Czy zawsze jest 24 na ćwierć-nutę??? Tak się tylko pytam ;)

0
0x666 napisał(a)
  1. zamiast (o zgrozo :>) TStrings użyj circular buffer o rozmiarze 24 (typu DWORD).

Czy to jest mechanizm Delphi czy WinAPI? W google'ach nie znalazłem wiele mądrego, poza tym, żę to często jest realizaowane przez sprzęt...

Chociaż po co bufforować? nie wystarczy zmierzyć czas tych 24 tick'ów (czyli ćwierć-nuty) zamiast robić to dla każdego z osobna???

To zmienia się cały czas i aktualna wartość jest średnią z ostatnich 24 ticków.

PS. Czy zawsze jest 24 na ćwierć-nutę??? Tak się tylko pytam ;)

Tak. W plikach MIDI (SMF) podaje się rozdzielczość ppqn - czyli ile jednostek na ćwierćnutę, co potem decyduje o odtwarzaniu wszystkich komunikatów i zależnościach czasowych między nimi. Samo tempo wysyłane przez urządzenia posługuję się owym tykaniem 24 razy na ćwierćnutę.

Jacek

0

Czy to jest mechanizm Delphi czy WinAPI? W google'ach nie znalazłem wiele mądrego, poza tym, żę to często jest realizaowane przez sprzęt...

To jest metoda/sposób buforowania - często używana w delay'ach. Inaczej mówiąc jest to ustawiona na sztywno kolejka FIFO. Implementacja takiej kolejki jest banalnie prosta - jest to zwykła tablica (w tym przypadku DWORD) i wskaźnik zapisu (index). Przy dodawaniu wartości do kolejki wpisujesz ją do elementu tablicy wskazywanego przez ów wskaźnik, a następnie zwiększasz go o jeden. Jeżeli wskaźnik przekroczy zakres tablicy - ustawiasz go na początek. Zaletą tego rozwiązania jest to, że nie wymaga żadnych allokacji/zwalniania pamięci i nie ma konwersji liczb na text (absurd :P) - w aplikacjach audio liczy się szybkość!!!

Tak. W plikach MIDI (SMF) podaje się rozdzielczość ppqn - czyli ile jednostek na ćwierćnutę, co potem decyduje o odtwarzaniu wszystkich komunikatów i zależnościach czasowych między nimi. Samo tempo wysyłane przez urządzenia posługuję się owym tykaniem 24 razy na ćwierćnutę.

No dobra, a jak w takim razie jest to realizowane w Cubase gdzie tick'ów na ćwierć-nutę jest (chyba) 480??? Bo te 24 per 1/4 to mała dokładność. (raczej chodzi mi o komunikację między urządzeniami MIDI, a nie format pliku *.mid).

0
0x666 napisał(a)

Czy to jest mechanizm Delphi czy WinAPI? W google'ach nie znalazłem wiele mądrego, poza tym, żę to często jest realizaowane przez sprzęt...

To jest metoda/sposób buforowania [...]

OK. Popróbuję ;-)

No dobra, a jak w takim razie jest to realizowane w Cubase gdzie tick'ów na ćwierć-nutę jest (chyba) 480??? Bo te 24 per 1/4 to mała dokładność. (raczej chodzi mi o komunikację między urządzeniami MIDI, a nie format pliku *.mid).

Jedno nie przeczy drugiemu. Tempo to nadal 24 komunikaty na ćwierćnutę. Zauważ, że tylko od dokładności tego nadawania/odbierania zależy czy tempo to będzie 120 BPM czy 120.001 BPM. Natomiast rozdzielczość - czyli ppqn - określa pozycje dla zdarzeń MIDI, czyli najmniejszą podziałkę, z jaką dane zdarzenie może być zapisane/odtworzone w stosunku do najbliższej ćwierćnuty. Stare 96 ppqn w wolnym tempie nie nadawało się prawie do użytku, bo to nic innego jak kwantyzacja wg 1/96. Stąd zamiast w 60BPM robioną kawałki w 120BPM z nutkami dwa razy dłuższymi. Obecne 384, 480 czy 768 pozwalają wierniej zarejestrować i odtworzyć zdarzenia. Ale i tak nie przekroczymy tych 31kb/s. Dlatego na jeden port MIDI to czasem za mało przy bogatej instrumentacji. Stąd pomysł na wieloportowe interfejsy MIDI. No i przede wszystkim mLAN, który powinien co nieco poprawić, choć do standardu, jakim jest MIDI, to mu daleko...

Jacek

0

Aha czyli te 24 komunikaty to nie są te właściwe tick'i??? Służa jedynie do synchronizacji i określenia BPM??? Zabawne, napisałem engine automatu perkusyjnego, a w MIDI jak dziecko - niekumaty :P

0
0x666 napisał(a)

Aha czyli te 24 komunikaty to nie są te właściwe tick'i??? Służa jedynie do synchronizacji i określenia BPM??? Zabawne, napisałem engine automatu perkusyjnego, a w MIDI jak dziecko - niekumaty :P

No nie są :-) Ich częstość określa jedynie tempo. I one są niezależne od rozdzielczości. A co do tego automatu -chętnie zerknę ;-) Masz gdzieś wersję skompilowaną? Jak rozwiązałeś kwestie dokładnego tempa przy odtwarzaniu?

Jacek

0
JacekH napisał(a)

A co do tego automatu -chętnie zerknę ;-) Masz gdzieś wersję skompilowaną?

No niestety jeszcze piszę. Wprawdzie engine jest zrobiony ale reszta jeszcze nie jest skończona.

Jak rozwiązałeś kwestie dokładnego tempa przy odtwarzaniu?

Bardzo prosto ;) W założeniu automat miał mieć swój własny software'owy syntezator więc mogłem sobie pozwolić na własny format zapisu modułów dźwiękowych. Taki moduł składa się z patternów, które mają swoje tempo (ustalane przez użytkownika) i długość. W patternie znajdują się event'y (czyli uderzenia), które to mogą być przypisanie do jednego z 128 kanałów (nie mylić z tymi z MIDI :P). Ich pozycja określona jest także w tick'ach tyle, że względem początku patternu. Tu podział jest taki jak w Cubase czyli 480 ticków na ćwierćnutę. W czasie gdy zaczyna się playback, pozycje wszystkich zdarzeń (z całego modułu) przeliczane są na globalne pozycje w samplach. No i dalej zaczyna się synteza, przeliczanie offsetów itd. itd. Aha, całość jest wysyłana na wyjście audio funkcjami waveOutxxxx - czyli jak zwykły materiał audio ;)

0

I grało to równo? Czym odmierzałeś czas? Wątek dla każdego paternu? Nie bardzo mam pomysł jak to zrealizować...

Żeby nie drażnić innych - może skrobniesz coś na maila ;-)

Pozdrawiam,
Jacek

0

I grało to równo?

A dlaczego ma nie grać równo??? Dokładność co do jednej sampli - czyli dla próbkowania 44.1kHz jest to 22.67us ;)

Czym odmierzałeś czas?

Buforami audio - każdy buffor ma swój czas trwania zależny od jego rozmiaru i częstotliwości próbkowania.

Wątek dla każdego paternu?

A po co??? Patterny są po to, żeby zorganizować jakoś budowę całego modułu. One są bardziej istotne dla użytkownika niż dla syntezatora - ten dostaje oddzielną tablicę z odpowiednio przeliczonymi zdarzeniami z całego modułu. Z resztą, tą tablicę można potraktować jako wirtualny plik wav, gdzie każdy event wskazuje gdzie należy "wkleić" odpowiednią sample (np. werbel). Co do wątku, jest tylko jeden (oczywiście poza wątkiem głównym :)).

Nie bardzo mam pomysł jak to zrealizować...

A co dokładnie chcesz zrealizować???

Żeby nie drażnić innych - może skrobniesz coś na maila

Wolałbym na forum ;)

0

Nie bardzo mam pomysł jak to zrealizować...

A co dokładnie chcesz zrealizować???</quote>

OK. Po kolei ;-) Skrobałem już różne programiki z MIDI, ale wszystkie pracowały offline - nie miały związku z czasem. Z odbiorem zegara poradziłem sobie. Teraz gdybam nad odtwarzaniem. CHciałbym zrobić mniej więcej coś jak u Ciebie - z tymi paternami, ale bez audio, samo MIDI. Zastanawiam się w jakiej strukturze przechowywać zarejestrowane komunikaty - te paterny i jak to zrobić podczas odtwarzania - timer wysyłający kolejne zdarzenia, które mają zakodowany czas... ? Jeżli możesz mi coś podsunąć, będę wdzięczny i będę pytał dalej :-)

Jacek

0

CHciałbym zrobić mniej więcej coś jak u Ciebie - z tymi paternami...

W MIDI patterny??? Jeszcze się z czymś takim nie spotkałem ;) Chociaż...

Z odbiorem zegara poradziłem sobie [...]

A czego używasz do odbioru zegara??? Funkcji z rodziny midiInXXX???

0

W MIDI patterny??? Jeszcze się z czymś takim nie spotkałem ;) Chociaż...

Protoplasta Cubase'a, czyli Pro-24 operował właśnie paternami przy budowie utwory. Patern to kilkutaktowy fragment - na jednym lub wielu kanałach MIDI. Podczas odtwarzania można z paternów budować kawałek dowolnie je ze sobą mieszając, ustawiając za sobą itd.

Z odbiorem zegara poradziłem sobie [...]

A czego używasz do odbioru zegara??? Funkcji z rodziny midiInXXX???</quote>
Tak. To akurat to najprostrze. W sumie to po prostu zliczam kolejne ticki zegara, i jak minie określony to wyliczam czas. Zależnie ile tych określonych uwzględniam przy pomiarze to mam mniej lub bardziej "chwiejny" wynik. Im więcej tym dokładniej, ale podczas gwałtownych zmian tempa przy wolnym tempie muszę długo czekać na wynik, stąd zrobiłem możliwość zmiany tego pomiaru. Swoją drogą, nie wiem dlaczego, ale podczas odbierania nutek (MIM_DATA) każda powinna mieć zakodowany timestamp - wg MS SDK. A tu guzik, wszystkie komunikaty mają ten sam czas... Jak Ty radziłeś sobie z nagrywaniem?

Jacek

0

Protoplasta Cubase'a, czyli Pro-24 operował właśnie paternami przy budowie utwory.

A to ciekawe. Zawsze myślałem, że patterny to coś specyficznego dla programów pokroju FastTracker'a ;)

W sumie to po prostu zliczam kolejne ticki zegara, i jak minie określony to wyliczam czas. Zależnie ile tych określonych uwzględniam przy pomiarze to mam mniej lub bardziej "chwiejny" wynik.

No właśnie problem w tym, że w systemie nie zawsze się da bardzo dokładnie zmierzyć czas nie tracąc na wydajności - funkcja QueryPerformanceCounter nieźle zamula, a timeGetTime potrafi zwracać dziwne wartości, szczególnie te małe (na ogół jest to 0):/ Tak się zastanawiam czy aby te low-level'owe rozwiązanie jest dobre. Próbowałeś bufforowanego zapisu (midiInAddBuffer)??? Wiesz, tutaj wiele rzeczy może być robione sprzętowo, a co za tym idzie większa dokładość. Odbierając po jednym zdarzeniu MIDI to jest tak jakby nagrywając dźwięk odbierać po jednej sampli (22.67us dla 44.1kHz ;P), a nie np. po 100ms bufforze.

Jak Ty radziłeś sobie z nagrywaniem?

Jak już wcześniej mówiłem, z MIDI mam niewiele wspólnego ;)

0

Próbowałeś bufforowanego zapisu (midiInAddBuffer)??? Wiesz, tutaj wiele rzeczy może być robione sprzętowo, a co za tym idzie większa dokładość. Odbierając po jednym zdarzeniu MIDI to jest tak jakby nagrywając dźwięk odbierać po jednej sampli (22.67us dla 44.1kHz ;P), a nie np. po 100ms bufforze.

midiInAddBuffer - to służy jednie do odbioru komunikatów systemu exclusive (MIM_LONGDATA) i oczywiście z tego korzystam przy odbiorze danych sysex. W SDK jest coś o Multimedia Timer... Ale chyba to odłożę na później, aż się wyśpię ;)

Jacek

0

midiInAddBuffer - to służy jednie do odbioru komunikatów systemu exclusive (MIM_LONGDATA) i oczywiście z tego korzystam przy odbiorze danych sysex.

To gdzie tu logika??? 8-| Przecież równie dobrze driver mógłby zapisywać do buffora zwykłe zdarzenia. Chyba nic innego ci nie pozostaje jak zaimplementować własne bufforowanie wszystkich zdarzeń z komunikatu MIM_DATA - czyli zapisujesz dwMidiMessage i dwTimestamp w jakiejś strukturze i do buffora. Później sobie poprzeliczasz jednostki, tempa itd.

Co do zerowych dwTimestamp:

Applications that require time-stamped data use a callback function to receive MIDI data.

//EDIT

Z tego co wyczytałem to te zdarzenia system exclusive nie są aż tak istotne. Czy tak???

1 użytkowników online, w tym zalogowanych: 0, gości: 1