Opiszę tutaj pokrótce, w jaki sposób w słynnych un-demach MECów,
został zakodowany design. Nie będę wchodził w subiektywne niuanse w
stylu - czemu muzyka jest taka dziwna, albo brakuje sensownej
grafiki. Opiszę, jak został zaprojektowany i zaimplementowany cały
silnik dema, by móc takie wygibasy obsługiwać. Dla niektórych może
nie będzie to nic odkrywczego (wszak nie jest to niewiadomo jaki
wynalazek). Dla tych osób może to będzie jako ciekawostka
przyrodnicza lub potwierdzenie słuszności koncepcji, jeśli sami coś
podobnego stosują. Inni, może faktycznie się zainspirują.
Próby zsynchronizowania ze sobą obrazu i dźwięku są tak samo stare,
jak dema. W przypadku naszych dem, na dobre temat się pojawił w
pierwszym demie z serii UN - unplugged. Wówczas to, podczas
kombinowania, w jaki wygodny sposób przełączać efekty wpadłem na
pomysł, by rozdzielić logikę efektu od logiki przełączania. W
unplugged mechanizm co prawda ułomny jest bardzo, ale jednak
kierunek już został wytyczony. Z każdą kolejną produkcją mechanizm
był rozwijany, aż do finalnej postaci w Undesigned.
By móc coś napisać, najpierw trzeba wiedzieć co - czyli
koncepcja.
Sam pomysł, jak już na niego wpadłem wydawał się prosty. Każdy
efekt ma swój kod, swoją logikę, która sprawia, że w skrócie pisząc
- efekt działa. Czy to będzie zwykły scroll, czy ciężkiego kalibru
3D - zasada działania jest zawsze taka sama. Efekt ma główną pętlę,
podczas której (w dużym skrócie) najpierw efekt jest renderowany,
potem następuje zmiana parametrów. I tak w kółko.
Jednocześnie gra muzyka, patterny przemijają. Wypadało by wiedzieć,
kiedy bieżący efekt ma się zakończyć. Gdy efekt w całym demie
pojawia się raz, sprawa jest dosyć prosta. Wystarczy na VBlanku
liczyć czas (w ten czy inny sposób), i w efekcie w odpowiednim
czasie z efektu wyjść. Jednak, gdy kod uruchamiany jest kilka razy,
zaczynają się pojawiać nieprzyjemne if-y. W tym momencie do głowy
przyszedł mi pomysł banalny - w kodzie efektu trzeba po prostu
sprawdzać flagę - wychodzimy już, czy nie? Flaga jest ustawiana
przez kod realizujący logikę dema. Na podstawie bieżącego stanu
muzyki ustawią ją odpowiednio. W tym momencie efekt uwolnił się od
ciągłego sprawdzania muzyki, a sprawdza tylko flagę. Jednocześnie
kod designu sprawdza muzykę. Oba kawałki są proste do napisania.
Zmiana niby niewielka, ale jak napisałem - umożliwia dwie
rzeczy:
oddzielenie logiki efektu od logiki designu
efekt można łatwo pokazywać i kończyć wiele razy.
Czyli jest prawie dobrze. Prawie, bo wciąż prawie dwa razy
sprawdzać trzeba co robić w temacie designu. Raz - podczas
śledzenia muzyki ustawić trzeba odpowiednie flagi, a drugi raz - w
samym efekcie flagi trzeba sprawdzać. A co jeśli w samym efekcie by
nie sprawdzać flag w ogóle, a cała robota by była robiona przez kod
logiki? W tym miejscu właśnie pojawia się koncepcja rodem z
Undesigned - 'sloty' (jak je nazwałem). Z technicznego punktu
widzenia są to adresy (wektory) umieszczone na stronie zerowej, w
których umieszczone są bieżące adresy do kodu renderującego. Co
ważne - mogą być puste, i wtedy są nieaktywne. Z logicznego punktu
widzenia są to kolejne,
równolegle uruchamiane efekty. I to jest właśnie ta koncepcja.
Jakie są konsekwencje takiego podejścia?
1. Efekt nigdy nie wie, jak długo będzie wykonywany. Czy to, co
właśnie jest renderowane, to będzie ostatnia ramka, czy jeszcze
nie.
2. Efekt nie może być uciążliwy w (re)inicjalizacji. Seria
kilku(nastu) LDA/STA jest dopuszczalna, ale przeliczanie tablic
zajmie dużo czasu i flow dema może zakłócić. Zatem jest tylko jedna
duża inicjalizacja - na samym początku dema.
3. Wszystkie efekty muszą się w pamięci zmieścić z całym swoim
kodem i danymi jednocześnie. W skrócie - wszystkie w pamięci leżą i
czekają na uruchomienie.
4. Każdy efekt ma swój kawałek strony zerowej tylko dla siebie.
5. Efekt zastaje ekran w pewnym stanie, i nie może go zniszczyć.
Musi dodać tylko swoje rzeczy nie nadpisując tego, co zastał.
6. Efekt od silnika dema dostaje informację, na którym buforze
renderować trzeba (poprzez numer i/lub adres bufora).
Sport tylko dla twardych - czyli implementacja.
W demach MECów używany jest player do TMC. Dostępny player
udostępnia bieżącą pozycję w patternie*. Pośrednio jest też
dostępna pozycja w songu. Jest więc dostępna pełna informacja o
bieżącym stanie muzyki. Mając możliwość indeksowania bieżącego
stanu dema (a znając bieżącą pozycję w muzyce można mieć możliwość
indeksowania), całe flow dema można zdefiniować poprzez odpowiednie
tablice - na jakiej pozycji w muzyce co trzeba do slotów wpisać, by
engine dema zaczął uruchamiać co trzeba, i całość gotowa :).
Slotów w Undesigned są 4 (i czasem wszystkie zajęte) W
system.scenario_index trzymana jest bieżąca pozycja designu.
Wektory slotów są umieszczone bezpośrednio jeden za drugim, a sam
numer slotu w tablicy flow.slot jest pomnożony przez 2. Reszta
tablic jest chyba wystarczająco czytelna. Sama zmiana efektu
następuje poprzez
zmianę adresu w slocie:
W current_slot jest bieżący numer slota. Czasem się przy pewnych
machinacjach, by efekt wiedział, na którym slocie siedzi.
*Na ST jest jeszcze fajniej, bo player Maxymisera ma komendę Z,
której parametr (8 bitów bez znaku) jest przez player udostępniana
programowi. Nie trzeba zatem ręcznie czekać na pozycję w songu,
tylko bezpośrednio z muzyki wydawać komendy, co ma właśnie się
wydarzyć. Jeżeli player jest do d..., to trzeba posiłkować się
licznikami na vblanku lub jakimiś innymi protezami.
Parę słów mądrych na koniec.
Nasz świat tak jest ułożony, że proste rzeczy mają swoje naukowe,
bardzo mądre czasem nazwy. Nie inaczej jest i tutaj. W temacie
koncepcji i terminologii, mamy tutaj następujące rzeczy:
1. Programowanie obiektowe. Dokładniej - polimorfizm oraz
hermetyzacja. Każdy efekt ma dokładnie taki sam interface (w ten
sam sposób dostaje informację, gdzie ma renderować) oraz przed
silnikiem dema chowa swoje szczegóły implementacji. Silnik
potrzebuje tylko dwóch informacji - adres kodu inicjalizującego (na
samym początku, jeszcze przed uruchomieniem muzyki) oraz adres kodu
renderującego. Budowa dema jest więc fajnym przykładem, że
programowania obiektowego nie używamy pisząc w Javie/C++/... tylko
pisząc w odpowiedni sposób niezależnie od języka.
2. Wielozadaniowość. Silnik dema uruchamia kilka efektów na ramkę,
więc jest tutaj używana wielozadaniowość. Ze względu na pewne
założenia kodu (i jego pełną kontrolę) i architektury jest to
wersja bez wywłaszczenia.
I to w zasadzie tyle, co chciałem o designie napisać. Na XE już
wiecie jak się to robi :). Na ST - pomysł jest analogiczny, jednak
całość jest sterowana Z kodami, a adres jest przekazywany poprzez
rejestr procesora.
pin 2017-01-04 22:55:05
ogólnie dzięki Panowie za demka spod znaku "UN". Ciekawe "inne" pomysły, dobry design, coś się dzieje. Oby tak dalej! xeen 2017-01-05 09:25:14
Ogólnie mam bardzo podobny schemat postępowania tyle, że mam dwa sloty i zawsze w nich zakładam efekt główny i efekt wspomagający. Czasem efekt wspomagający służy do wyjścia/wejścia (np. najprostszy fade out) lub do przeczekania np. na rozpakowanie jako efekt który jest relatywnie lekki. Mam wrażenie, że podobnie robi scena c64 z efektami wspomagającymi / rysunkami bo tam dochodzi zawsze aspekt dogrywania ze stacji (nie ma bajki w postaci 128 czy tam 320 KB).
To co muszę dopracować bo zawsze robię to na piechotę to synchro z muzą - teraz jechałem własnym licznikiem i było to słabo utrzymywalne jak muzyk zmienił co nieco. Zresztą nawet zapinając się na konkret jak player taki udostępnia i tak trzeba to utrzymywać przy zmianach muzy (często jakieś błyski są uzależnione od perkusji i chyba wtedy lepiej zapinać się na jakieś qasi shadow'y dla pokeya).
Punkt 5 mnie mocno zastanawia bo to jednak spore ograniczenie - zwłaszcza jak się jedzie na więcej niż jeden koder. Z drugiej strony takie założenie mocno trzyma ryzy dema i samo z siebie utrzymuje pewną jego spójność.
Dzięki za artka. tebe 2017-01-05 12:10:54
dobry art :)
W kwestii playerów Numen poszedł o krok dalej, niezależnie od efektów dźwiękowych player z Numena zabiera co najwyżej linię skaningową obrazu, ma to znaczenie gdy zakodujemy efekty które są na granicy wytrzymałości ramki obrazu.
Ktoś zakoduje jakiś efekt, działa płynnie, ślicznie, doda muzykę i zaczyna rwać, decyduje się na jakiś szybszy player, rezygnuje z pewnych efektów dźwiękowych bo zajmują za dużo czasu CPU, musi iść na kompromis. Nie raz już tak miałem że kilkaset cykli CPU więcej i potrzebna jest dodatkowa ramka na synchronizację, albo zmieniam sposób synchronizacji zamiast VBL korzystam z VCOUNT ($D40B) i synchro ustawiam gdzieś od środka ramki, będzie mniej widać że "szarpie", coś za coś.
A gdyby tak móc korzystać z każdego playera msx, móc korzystać z każdych efektów dźwiękowych i cały player zawsze mógł zajmować tyle samo, minimalnie czasu CPU :)
Numen tak ma bo korzysta z bufora na muzykę, są tam zapisane wartości dla POKEY-a (np. 9 bajtów), player jest wywoływany w konkretnych miejscach dema aby uzupełnić bufor, czyli w takich miejscach gdzie efekt nie obciąża w pełni CPU, gdzie wyświetlane są różne przerywniki.
Kilkanaście ramek obrazu pozwala wypełnić bufor, tak aby odegrać muzykę przez kilkanaście kolejnych sekund.
Player nie jest autonomiczny, jest sterowany przez "skrypt" który zaprogramował koder składający demo. xxl 2017-01-05 12:52:42
no to rozumiem, że muzyka zajmuje ok 28KB na minutę w takim Numenie prze jej prekalkulowaniu? To jest dosyć kuszące wieczór 2017-01-05 15:38:39
@xeen: nie oznacza, że cała jest przekalkulowana już na starcie. Kalkulowanie następuje w miarę potrzeb, z tego co zrozumiałem - do określonego bufora. Natomiast samo przekalkulowanie wówczas nie jest krtyczne czasowo i może być wykonywane w wolnych chwilach - przy klasycznym graniu, całe kalkulowanie następnych wartości rejestrów musi zmieścić się w ramce (wraz z innymi rzeczami) i trochę zajmuje. O dwóch replayach na ramkę już nawet nie mówiąc xxl 2017-01-05 15:47:12
prekalkulacja wcale nie musi miescic sie w ramce. tebe 2017-01-05 15:54:06
"player" zapisuje wartości do bufora POKEY-a z pełną mocą CPU w danej chwili, więc wywołań takiego playera w 1 ramce jest do oporu, w zasadzie tyle na ile pozwala aktualnie wyświetlany efekt
kolejny player, już ten właściwy pobiera z bufora i zapisuje do POKEY-a, on jest już z kolei wywoływany raz na ramkę
muzykę w formacie MPT można tutaj traktować jak archiwum, spakowane dane, przerobiony player MPT który zamiast do POKEY-a wysyła dane do bufora należy nazwać dekompresorem ;) tebe 2017-01-05 15:57:05
jeśli bufor liczy np. 10KB, 10240 / 9 = 1137 ramek = ~22 sekundy (wartość 9 -> AUDCTL, AUDC1..4, AUDF1..4) Polak mały 2017-01-05 16:15:06
Brawo!!! Wiecej mądrych i edukacyjnych artykułów to krok w kierunku roszerzania wielu manii. Jednocześnie pozbywacie się ELitOmanii. Tak trzymać , jego mać!!! xeen 2017-01-05 17:12:14
wieczór - dałem skrót myślowy. Ja rozmarzyłem się aby sobie zrobić taki prekalk na pełen czas trwania utworu w prodce niezależnej od Numena (licząc of coz raz na ramkę). Zrozumiałem jak to jest robione w Numenie (i przyznam nie wiedziałem o tym - bardzo fajny pomysł). Hospes 2017-01-05 19:33:05
wieczór: ooo taki precalc w wersje ST i YM, gdzie można sobie poszaleć na timerach. Zero ograniczeń, że mi Timer włazi czasowo na ramke;) króger 2017-01-05 20:32:23
bardzo pouczająca dyskusja w komentarzach pin 2017-01-05 21:07:48
... dość wyjątkowa to sytuacja, faktycznie ;)
hi Króger!!! wieczór 2017-01-05 22:03:59
@Hospes: nie rozumiesz :) I tak wejdzie i tak wejdzie - timer jest na samym końcu tego procesu :) wieczór 2017-01-05 22:11:21
Przy takim precalcu to dwa czy nawet 3 razy na ramkę to nie problem, bo zajmuje tylko wpisy do rejestrów. Zajmuje chwilkę. Największym problemem jest zawsze odpalenie całej procedury analizującej song, pattern, obwiednię i obliczająca wartości do tych rejestrów. Ile to zajmuje to widać po paskach w xexowym playerze RMT (a on naprawdę jest optymalizowany przy kompilacji). Sam wpis do rejestrów na końcu to jest pikuś. Jak wartości są przygotowane to można szaleć i 4 razy na ramkę. I wszystko się w czasie zmieści (oby tylko zmieściło się w pamięci ;) ) dhor 2017-01-05 23:18:03
Ooo, to o to chodzi w demkach UN... A to się człowiek na starość dowiedział :D
Buforowanie wartości dla pokeya - klawa sprawa. Niemniej, w przypadku chęci grania co ileś przerwań iRQ.. Czy to się da stablicować? (teoretycznie - czemu nie)...
Ale kto gra na irq :) the fender 2017-01-06 13:31:15
Coder's talk :) To tylko pokazuje jak ogromna jest przepaść ludzi pasjonujących się Atari od ludzi pasjonujących się Atari i rozumiejących jak to działa i jak coś zrobić :)
Sama dysputa wielce interesująca, oby takich więcej tutaj. bob_er 2017-01-07 11:30:32
Dzięki za miłe słowa. Widać jest popyt na takie rzeczy, to pewnie jeszcze coś napiszę. Co do pomysłów w komentarzach: 1. Tablicowanie pokeya - pomysł fajny, ale IMHO wymaga już przynajmniej 128k RAMu. Efekt (jeśli to nie jest przejście) trwa przynajmniej kilkadziesiąt sekund. Z drugiej strony - nie każde wywołanie playera zmienia wszystkie rejestry pokeya, więc pewnie można coś pokombinować. Pomysł do rozważenia na przyszłość :). Tym bardziej, że chodzi mi po głowie demo, gdzie użyję separacji Antic/CPU dla rozszerzonej pamięci. 2. Można też łatwiej grać x razy na ramkę. Nie trzeba tutaj IRQ stosować, można DLI użyć. Czasami demo ma stałą DL, więc było by to wtedy łatwe do utrzymania. 3. Odporność na zmianę muzyki - tego to żadna metoda nie gwarantuje. Choć najbliższe jest rozwiązanie z Maxymisera na ST - Z komenda do sychronizacji. Ból jest wtedy taki, że moduł musi latać w te i wewte pomiędzy muzykiem a koderem - żeby nie stracić już wpisanych Z komend. Normalnie moduł leci od muzyka do kodera. Używanie na XE pozycji w songu i patternie jest dla mnie o tyle wygodne, że design w głowie układam słuchając utworu, czasem nawet kilkadziesiąt razy. Potem w trackerze ustalam, na jakiej pozycji muzyka jest i po prostu te wartości zapisuję w tablicach. 4. Na ST pomysł z tablicowaniem muzyki nie jest banalny. Muzyka zwykle gra na Timerze C, czasem na VBLANKu. Do tego dochodzą kolejne timery, by obsługiwać buzzery i inne sidy. Naprawdę byś musiał długo grafikę wyświetlać, by wyliczyć wartości dla czasu trwania kolejnego efektu. MKM/Lamers 2017-01-07 11:42:02
bob_er bardzo fajny artek;)
Ad 4. Na ST/STE podobna technike stosuja niektore grupy ale wymaga to rezygnacji z efektow dzwiekowych na timerach. dlaczego wiec taki zwykly dump rejestrow ym sie czasem oplaca? szczegolnie w efektach fullscreen gdzie caly kod jest docyklowany (hardsync) i tak nie wolno uzywac timerow (bo kod sie sypnie) a kazdy cykl cpu zanim electron beam wejdzie w widoczny na ekranie obszar jest na wage zlota. z tego co wiem taka technike stosowali np dhs czasem.
PS. az strach mi pisac jak my musielismy walczyc z synchronizacja efekty/muzyka w Polygon Discount gdzie takie Falcon gra MP2 w innym tempie niz orginal na PC... a na emulatorze za to wlaczenie emualcji DSP powoduje ze dzwiek pierdzi i wszystko zwalnia;) ufff to byl koszmar hehe;) mrroman 2017-01-07 13:37:09
bob_er: jeszcze nie czytałe czałego artykułu ale wygląda super :)
Co do buforowania danych do Pokeya i zapisywania za każdym razym wszystkich rejestrów, to mam taki pomysł. Może po prostu do bufora pisać od razu kod, który pisze do rejestrów (jakieś STA AUDF0, STA AUDC0, RTS) i wykonywać go w danym momencie. Wtedy ten kod piszący do bufora wpisywałby tylko te rozkazy które potrzebuje. xxl 2017-01-07 13:45:39
no ale probujemy zaoszczedzic na buforze a tu nagle oprocz samych danych chcesz tam wsadzic adresy i jeszcze rozkazy... z 9 bajtow zrobi sie 35 bob_er 2017-01-07 14:21:21
I taki kod nie będzie wiele szybszy. Wersja max stablicowana zajmie 72-81 cykli (w zależności od układu tablic na stronach) vs 54 cykli (z kodem w buforach). Wychodzi niecałe 20 cykli różnicy. 0xF 2017-01-07 17:58:56
Eleganckie rozwiązanie sterowania czterema jednoczesnymi efektami na podstawie pozycji w songu i paternie. Dzięki! Konop 2017-01-07 18:55:33
Można rozważyć kodek RLE w celu ograniczenia ilości pamięci. Całość będzie wolniejsza, ale da znaczny zysk na pamięci. bob_er 2017-01-07 21:42:05
RLE by najwięcej korzyści przyniosło, gdyby kodować osobno każdy kanał oraz AUDCTL. Ewentualnie, system flag bitowych (czy była zmiana czy nie) i strumień bitowy (tzn - z wejścia odczytujemy bitami (np. 4 bity albo 8 bitów), a nie bajtami). To też spowolni odczyt, ale zapis będzie szybszy niż w RLE. Vasco/Tristesse 2017-01-08 19:14:22
O, ja też korzystałem z buforowania muzy - najszybsze trackmo na Atari ;) 10 GOSUB 10 2017-01-26 14:56:37
Dema MECów są brzydkie, brudne i szorstkie i dlatego je lubię.