Kurs Assemblera cz. 6 1/2
- TeBe / MadTeam
Uzupełnienie do kursu "InertiaPlayer & ProTracker"
Pewnie to wszyscy wiedzieli, tylko nie Ja, że pomysłodawcą i autorem programu do odtwarzania plików MOD (MD8) jest Pecuś. Dzięki jego uprzejmości można teraz lepiej wyjaśnic zasadę działania playera.
Dodatkowo jest jeszcze sprawa Epiego i jego NeoTrackera ;), który chwali się że jest szybszy, ciekawe jak? Sample w Neo zajmują zawsze bank pamięci (16kb), ale wątpie żeby to przyspieszyło samego playera, co najwyżej procedurę dekodującą pattern, czyli pare cykli, które nijak bądą się miały do całego grania. Epi pochwal się swoimi osiągnięciami ! :)
Jak ładować do pamięci dane i znajdować odpowiednio dużo miejsca ?
Dostępna pamięć reprezentowana będzie przez tablicę z bitami, każdy bit to strona pamięci. Bit 1 - strona pamięci zajęta, bit 0 - strona wolna. Oczywiście każdy bit w tablicy to odpowiedni adres w pamięci. Teraz obliczamy ile stron zajmują dane, np. $150 = 2strony, szukamy w tablicy najmniejszego wolnego obszaru >= 2 i tam załadujemy dane. Dzięki temu załadujemy więcej sampli i oszczędzimy pamięć. Proste, prawda ?:)
Teraz wróćmy do fragmentu playera, mało zrozumiałego, czyli etykiety "ist_0" itd.
Pecuś rozjaśni obraz:
pmain equ * bank0 lda #$fe ;bank w którym znajduje się sampl sta $d301 ist_0 lda #0 ;tego nie rozumiem, bo brak w playerze odwolan do ist_0 itp. iad0_m adc #0 ;ale sa odwołania do "iad0_m" i "iad0_s" sta ist_0+1 lda p_0c+1 ;modyfikacja adresu sampla, dodajemy wartosc z tablicy czestotliwosci ... ...
Sprawa jest prosta, jedno słowo wystarczy "ułamek" :)
Jeśli chcesz odtworzyć dźwiek o częstotliwości np. o połowę mniejszej niż wzorcowa, a petla licząca pozycje w samplu tylko dodaje i nie można sterować częstotliwością jej wywołania, to ile musisz dodawać za każdym obrotem pętli by osiągnąć o połowę mniejszą częstotliwość (czyli każda próbka odtwarzana jest dwa razy)?
To oczywiste musisz dodać 0,5 - czyli dodajesz ułamek a i tak część po przecinku jest Ci potem niepotrzebna. No ale dodawać tak trzeba. W przypadku mojego playera $80 zapisane w tej "nieużywanej" komórce, a zera w pozostałych oznacza właśnie 0,5 tak jak $33 oznacza około 0,2, a dodawanie tej wartości spowoduje pięciokrotnie wolniejsze odtwarzanie sampla. Poprostu traktujemy 2 bajty jako adres sampla i dodatkowo jeszcze jeden bajt jako ułamkową część adresu.
Być może ta komórka jest w innym obszarze strony zerowej i nie jest tak że liczba całkowita i ułamek są zapisane kolejno - ale to wynikało z prób i dodawania pewnych rzeczy po napisaniu wstępnej wersji.
A teraz największe zaskoczenie i ciekawostka. Przy pisaniu tych procedur trzeba było stworzyć tablicę dodawanych wartości - nazwaną potem niezbyt poprawnie tablicą częstotliwości dźwieku. Zacząłem to liczyć za pomocą programu w basicu i tabeli częstotliwości kolejnych dźwięków z gamy. Wyliczyłem pierwszą oktawę i rzuciłem okiem na część kodu playera z Amigi. Tam jest inaczej bo 4 przetworniki mogą grać z niezależnie ustawianymi szybkościami... No ale do rzeczy - jakież było moje zdziwienie kiedy zauważyłem że tablica, którą zacząłem wyliczać odpowiada DOKŁADNIE tablicy częstotliwości przetworników w playerze Amigowym przepisanej tylko od końca :) - tak więc dałem sobie spokój z liczeniem i przewaliłem te tablice z Amigi.
A co do odtwarzania, to player nie gra na JEDNYM generatorze - gra na TRZECH. Poprostu nieliniowość POKEYa powoduje, że odtwarzanie sampla na jednym generatorze powoduje zniekształcenia sampla granego na drugim - nie sumuje sie to zwyczajnie - a przy 3 już się nie da słuchać nie mówiąc o 4. Podobno niezły efekt uzyskuje sie przez ustawienie dźwieku o najwższej możliwej częstotliwości na każdym kanale i sterowanie głośnością (a nie bezpośrednie sterowanie głośnikiem). Ja poszedłem inną drogą, pomierzyłem napięcia uzyskiwane na wyjściu POKEYa przy wpisywaniu różnych wartości do generatorów, ułożyłem wyniki liniowo i stworzyłem tablice wpisów dla trzech generatorów - poczatkowo było na czterech, ale niestety mamy tylko 3 rejestry, a rozkaz ładowania rejestru po drodze zakłócał dźwiek.
Uff... Na AIM wrzuciłem kiedyś źrodła playera i konwertera, ale ja nie komentowałem ich zbyt dobrze (słynna etykieta SPRDAL - osnaczająca oczywiście "Sprawdzaj Dalej" :) - poza tym to nie jest ostatnia wersja, a chyba nawet jedna z pierwszych - polecam zdisasemblowanie wersji z Intel Oudside. A to ładowanie na DOSa to nie zabezpieczenie - poprostu miało chodzić na 64kb, więc potrzebna była maksymalna ciągła pamięć, stąd loader z CHAOSa w środku i tak niskie ładowanie. A zabezpieczenienie było dlatego, że jak dałem do testowania w Bajtku wersje niezabezpieczoną to za tydzień krążyła już po giełdzie (a nie miała jeszcze niezależnej głośności kanałów i zapętleń końcówek sampli).
Pozdro. Pecuś.
pmain equ * bank0 lda #$fe ;bank w którym znajduje się sampl sta $d301 ist_0 lda #0 ;to już rozumiem bo Pecuś wytłumaczył :) iad0_m adc #0 ;modyfikacja wartości w "iad0_m" i "iad0_s" sta ist_0+1 lda p_0c+1 ;modyfikacja adresu sampla, dodajemy wartosc z tablicy czestotliwosci iad0_s adc #0 ;mlodszy bajt adresu sta p_0c+1 bcc p_0c inc p_0c+2 ;starszy bajt adresu lda p_0c+2 ;sprawdzenie czy to już koniec sampla ien0_s cmp #0 bcc p_0c ire0_m lda #0 ;ustawienie nowego adresu poczatkowego sampla sta p_0c+1 ;czyli petla sampla ire0_s lda #0 sta p_0c+2 jmp bank1 p_0c ldx $ffff ;wartość sampla ivol10 lda $d800,x ;tablica głośności ch0 sta $d600 ;graj, jeśli urzadzeniem jest POKEY to będzie adres $d200 bank1 ........... nastepny kanal itd.
Sprawa głośności sampla. Nie ma szybszego sposobu niż:
ldx $ffff ;wartość sampla (bajt) lda $d800,x ;tablica głośności - modyfikowany adres sta $d600 ;graj, jeśli urzadzeniem jest POKEY to będzie adres $d200
MOD ma 64-o stopniową skalę głośności, ale na potrzeby Atari wystarczająca jest 32 stopniowa skala, zajmuje o połowe mniej miejsca :), jak wyliczyć tablice? Wartości w samplu są z zakresu <0,255>, więc te wartości będą skalowane przez tablice głośności. 256 wartości przez 32 głośności = 8, czyli co 8 jednostek będzie zmieniała się głośność.
Czyli mamy głośności tak naprawdę 0,8,16,32 ... 256, np. glośność =8 oznacza że w 256 wartościach naszej tablicy głośności musimy zmieścić wartości z zakresu 0..8, głośność =48 oznacza, że w 256 wartościach są liczby z zakresu 0..48 itd. Dla głośności =0, wszystkie wartości w tablicy będą =0, czyż nie jest to oczywiste :D Chyba już rozumiecie że skalujemy w ten sposób wartości sampla :)
Oto programik w stylu Turbo Pascala, wyliczający tablice głośności:
uses crt; var z,i,skala:integer; {deklaracje zmiennych INTEGER-całkowite} co_ile_stopni:integer; x:real; {zmienna typu rzeczywistego, dla ułamków} v:byte; {byte} begin clrscr; {czyścimy ekran} co_ile_stopni:=8; {czyli 256wartości przez 32 stopniowa} {skale głośności = 8} for skala:=1 to 32 do begin z:=skala*co_ile_stopni; {obliczamy zakres} x:=z/256; {obliczamy wartość kroku, aby wszystkie} {elementy zmieściły się w 256 bajtach} for i:=0 to 255 do begin v:=round(i*x); {liczymy zaokrąglając wartości} write(v,','); {wypisujemy na ekran, albo do pliku} end; end; end. {koniec przykładu}
Dla każdej głośności będzie więc 256 bajtowa tablica. 32*256 = 8192bajtów zajmą wszystkie tablice. Normalnie w Inertii taka tablica siedzi sobie w dodatkowym banku, i dopiero gdy jest potrzebna przepisywana jest pod ROM, $d800...
Pytanie za 100punktów
Czy da się przyspieszyć player? Jakieś sugestie co do jego przyspieszenia? Pamiętajcie tylko, że cały kod mieści się na stronie zerowej.
A może tak przydałby się player grający 256 bajtowymi samplami, do tego wibracje i inne efekty z MOD-a, tak żeby można było stworzyć chiptunes. Takiego 256bajtowego sampla możnaby było sobie samemu edytować, nadać mu kształt trójkąta, fali czy czegoś innego, tak aby wydobyć dźwięki np. podobne do SIDa. Jak myślicie, można na takim 256 bajtowym sampelku coś zrobić ??
Player dla 256bajtowych próbek byłby szybszy, a mógłby wyglądać tak:
pmain equ * ist_0 lda #0 ;to już rozumiem bo Pecuś wytłumaczył :) iad0_m adc #0 ;modyfikacja wartości w "iad0_m" i "iad0_s" sta ist_0+1 lda p_0c+1 ;modyfikacja adresu sampla, dodajemy wartosc z tablicy czestotliwosci iad0_s adc #0 ;mlodszy bajt adresu sta p_0c+1 bcc p_0c ien0_s cmp #0 ;test dlugosci bcc p_0c ire0_m lda #0 ;ustawienie nowego adresu (mlodszy) poczatkowego sampla sta p_0c+1 ;czyli petla sampla jmp kanal2 p_0c ldx $ffff ;wartość sampla ivol10 lda $d800,x ;tablica głośności ch0 sta $d600 ;graj, jeśli urzadzeniem jest POKEY to będzie adres $d200 kanal2 ........... nastepny kanal itd.
Sample nie musiałyby znajdować się w dodatkowych bankach, zawsze miałyby 256bajtów, sprawdzany byłby tylko młodszy bajt adresu. Dzięki takiej oszczędności cykli możnaby do playera dorzucić procedurki odpowiedzialne za inne fx-y niż Volume. Chyba w ten sposób działa SoftSynth, jeśli ktoś zna działanie SoftSytnha od "kuchni" chętnie posłucham. Przydałby się jeszcze dokładny opis z implementacją efektów dźwiękowych z MOD-a, ktoś chętny??