Jak się robi design by Bob_er 2017-01-04 19:55:16

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:




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:



flow_handler equ *
ldy system.scenario_index
lda flow.pattern,y
cmp system.pattern_position
bne flow_handler_1
lda flow.song,y
cmp system.song_position
bne flow_handler_1
ldx flow.slot,y
lda flow.routine_lo,y
sta slot0,x
lda flow.routine_hi,y
sta slot0+1,x
iny
sty system.scenario_index
flow_handler_1 equ *
rts



Samo uruchomienie slota jest już banalne:



call_slot0 equ *
lda slot0+1
beq call_slot0_1
lda #$00
sta current_slot
jmp (slot0)
call_slot0_1 equ *
rts



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

oby wiecej technicznych artkow :-)

xeen 2017-01-05 15:05:00

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ę.