Wyzwanie dla elektronika


"O co mu znowu chodzi?"
Hmm... niby proste... a jednak.

       Sprawa przedstawia się następująco: Pisząc NeoTracker-a, mówiąc wprost i bez fałszywej skromności, doszedłem do granic możliwości Atarki. Czy to źle, czy dobrze - pozostawiam waszej indywidualnej ocenie. Według mnie to bardzo źle, bo... Słuch mój nie jest na pewno genialny, ale na tyle czuły, że mogę bez żadnych problemów stwierdzić, że 11.7 kHz to zdecydowanie za mało i po prostu razi to odczucia estetyczne każdego człowieka obdarzonego jakimkolwiek słuchem. Gorzej, że programowo więcej wycisnąć się nie da. Jeżeli mi nie wierzycie, to spytajcie bardziej doświadczonych i zasłużonych koderów, choćby Fox-a - mistrza w liczeniu cykli. `ródłówka procki odtwarzającej sample z NeoTracker-a jest rozebrana na czynniki pierwsze w którymś z artykułów w tym numerze (mam nadzieję...), więc będzie miał podstawę do osądu.

       Dlatego po całym tygodniu męki nad wyciskaniem wody (a właściwie cykli) z procedurki odtwarzającej sample w wyżej wymienionym programie, już dokładnie wiedziałem, czego w Atarce brakuje.

       Ale najpierw mała dygresja: Drodzy Koderzy! Czy nie wkurza was to, że wszystkim musicie zajmować się Wy, tylko dlatego, że projektanci układów specjalizowanych w Atarce byli zbyt leniwi i większość zadań zrzucili na 6502?! Mnie też to wkurzyło, dlatego przedstawiam pomysł układu, który odciąży CPU przy niezwykle pracochłonnym odgrywaniu sampli. Nazywać się to może POKEY8, COVOX-2 albo POVOX czy jak tam kto woli. W opisie dla porządku będę używał tej ostatniej nazwy, bo jest najkrótsza. Tak więc określenie "COVOX" oznacza część układu odpowiedzialną za samo generowanie dźwięku, czyli cztery 8-bitowe przetworniki D/A, a "POVOX" to cały hipotetyczny układ, o którym jest tu mowa, łącznie z samym COVOX-em. A ponieważ układ ten ma zastępować procedurkę wykonywaną cyklicznie, więc działanie odpowiadające jednokrotnemu wykonaniu takiej procki nazwiemy już na wstępie "cyklem POVOX-a", żeby potem uniknąć niedomówień, przydługich opisów, itd.

       Ponieważ sam na warsztatach z elektroniki zamiast lutować cyfrówkę maluję stoły, to jestem zmuszony pomęczyć elektroników, których na naszej scenie wcale nie jest mało i w dodatku trzymają niewiarygodnie wysoki poziom. Jak widzę, co tworzą np. Pasiu albo Zenon, to własnym oczom nie wierzę, więc postanowiłem puścić wodze fantazji i dać Wam, mistrzowie lutownicy i "odcycacza" ;) zajęcie na długie jesienne (może nawet zimowe) wieczory.

       Na tyle, na ile przez te kilka lat posiadania kompa spod znaku góry Fuji dowiedziałem się co nieco o jego budowie oraz o działaniu różnych układów elektronicznych, postaram się mniej więcej naprowadzić potencjalnego projektanta takiego układu na takie rozwiązania, jakie moim zdaniem sprawdziłyby się najlepiej. Ale, że elektronik ze mnie marny, więc to wykonawca będzie miał ostatnie słowo. Również w sprawie ochrzczenia interface'u jakąś dźwięczną nazwą. Byle efekty działania były takie same.

       Więc... do rzeczy!

       Pomysł jest taki:

       Układzik, który sam będzie pobierał z pamięci odpowiednie dane i wpisywał je do COVOXa. eby było trudniej, to powinien jeszcze obsługiwać całą dodatkową pamięć (podstawową zostawmy w spokoju) z pominięciem CPU i o ile to możliwe także PIA, a także sam przeliczać adresy (24-bitowe) kolejnych bajtów sampli. eby było jeszcze trudniej, to niech ma własny zegar, żeby granie było dobrze zsynchronizowane w czasie, nawet przy włączonym ekranie i aktywnych przerwaniach. No i oczywiście dobrze by było, gdyby był kompatybilny ze starym COVOXem.

       Ale to nie wszystko. :> Maksymalny czas trwania jednego cyklu to pół linii ekranu (57 taktów CPU), a CPU ma być odłączany TYLKO NA CZAS POBIERANIA DANYCH z pamięci przez POVOX. Jak łatwo zgadnąć, wskazane jest również odłączanie ANTICa na tą chwilkę, bo a nuż widelec ktoś puści sample przy włączonym ekranie i może pokazać się sieczka. POVOX ma działać zupełnie niezależnie od reszty układów, wstrzymując ich działanie, jeśli korzystają one z pamięci.

       Czyli ogólnie ma być to układ do odtwarzania sampli z dowolną częstotliwością, z pamięci "wypożyczanej" od reszty układów jako z własnego bufora adresowanego samodzielnie na szynie adresowej o szerokości zależnej od ilości pamięci rozszerzonej. Taki POKEY, tylko że zamiast na syntetykach 4-bitowych gra na 8-bitowych samplach. I wcale do tego nie potrzebuje zaawansowanych dzielników częstotliwości! Dlaczego? O tym nieco niżej. A teraz...

       Najpierw przedstawię swoją propozycję rozkładu rejestrów POVOX-a (jest ich od groma i trochę...):

adresnazwaznaczenie
$D600 COVOX1 sygnał na ścieżce pierwszej (położenie membrany)
$D601 COVOX2 sygnał na ścieżce 2
$D602 COVOX3 sygnał na ścieżce 3
$D603 COVOX4 sygnał na ścieżce 4

do tej pory jest tak samo jak w starym COVOXie. A wyimaginowany układ ma wpisywać do tych rejestrów to, co trzeba, korzystając z kolejnych rejestrów:

adresnazwaznaczenie
$D604 C1IND ułamkowa część adresu w C1IADR ($D605)
$D605 C1ADR adres próbki - ścieżka 1
(3 bajty, ale 20 bitów chyba wystarczy, pod $D605 - najmłodszy bajt,$D607 - najstarszy)
$D608 C1END adres końca próbki
$D60B C1REP adres początku pętli
$D60E C1FRQ częstotliwość na ścieżce 1
$D610 C1VOL głośność na ścieżce 1
i dalej w tej samej kolejności to samo dla kolejnych ścieżek. Na koniec potrzebny jest jeszcze jeden rejestr, nazwijmy go CVCTL, do kontroli układu, której temat rozwinę jeszcze nieco później, a tymczasem wytłumaczę, co znaczą niezrozumiałe skróty myślowe w powyższej tabelce.

       W dalszych opisach nazwy rejestrów będę zapisywał z x-em oznaczającym ścieżkę, którą rejestr ten obsługuje. Np. CxVOL może oznaczać C1VOL, C2VOL, C3VOL jak i C4VOL, ale ponieważ nie ma to wpływu na sam algorytm, więc, żeby go nie zaciemniać, po prostu numerek ukryłem.

       Co do rejestrów, to mam małą prośbę: byłoby niezmiernie miło, gdyby przynajmiej rejestry COVOXx i CxADR dały się odczytywać. Po co? Bo przy pisaniu programu obsługującego takie cudo, można się pokusić o bajerki. Pobierając dane z COVOXx robi się oscyloskop, a z CxADR - wskaźnik aktualnie odgrywanej próbki.

       A teraz "kilka" ;) słów o adresowaniu pamięci przez POVOXa. Jest to z punktu widzenia kodera szczególnie ważna rzecz, żeby było tak, a nie inaczej (chyba, że ktoś znajdzie lepszy sposób), dlatego się tak okrutnie rozpisałem. Jak na mój gust, to ta metoda jest też chyba najłatwiejsza do praktycznego zrealizowania.

       Adres aktualnie odtwarzanego bajtu sampla należy rozumieć następująco:

bajt3 bajt2 bajt 1
CxADR+2 CxADR+1 CxADR
....xxxx xxyyyyyy yyyyyyyy

Spacje są tylko po to, żeby rozdzielić pojedyncze bajty od siebie. Bity ukryte pod igrekami tworzą adres odtwarzanego bajtu względem początku adresu banku dodatkowej pamięci dla CPU ($4000), a owe banki są numerowane za pomocą bitów ukrytych pod x-ami.

       Bitów tych w przykładzie jest sześć, co daje 64 banki (1 MB) dodatkowej pamięci, ale ilość wykorzystanych bitów powinna być dobrana w zależności od ilości pamięci w danym kompie.

       Banki powinny być numerowane zgodnie z bitami portu B, to znaczy: jeżeli w porcie B znaczące są cztery bity (+256kB), np. 6,5,3 i 2, to powinny one odpowiadać bitom CxADR w taki sposób:

               76543210                 PORTB
                || ||
          ......xx xxyyyyyy yyyyyyyy    CxADR
           bajt 3   bajt 2   bajt 1

Bity ukryte pod kropkami nie mają wpływu na pracę układu.

       Jeżeli w kompie jest 1MB (64 banki, czyli 6 bitów przełączających Portu B, zazwyczaj: 7,6,5,3,2 i 1), to przyporządkowanie wygląda tak:

               76543210                 PORTB
               ||| /||
           ....xxxx xxyyyyyy yyyyyyyy   CxADR
            bajt 3   bajt 2   bajt 1

Czyli wykorzystywane jest tylko tyle bitów x, ile jest aktywnych bitów przełączających PORT B.

Na tej samej zasadzie podaje się adresy w CxADR, CxREP i CxEND.

       Proste, prawda?

       Dlaczego tak namotałem przy tych x-ach? Bo jeżeli omijamy PIA, to nie podajemy już numeru banku jak do PORTu B przy normalnym adresowaniu jak CPU, tylko traktujemy dodatkową pamięć jakby była ciągłym, pojedynczym buforem karty dźwiękowej, gdzie przechowywane są sample i adresujemy całą przestrzeń tej pamięci od razu, a nie bankami. Podział jest zasadniczo wyimaginowany i służy tylko temu, żeby koder w symbiozie z procesorem nie pogubili się przy adresowaniu. Dlatego kolejność banków powinna być logicznie powiązana z kolejnością obszarów po $4000 bajtów w pamięci POVOXa. Najwłaściwsze rozwiązanie, to właśnie tablica, w której numery banków ułożone będą w kolejności rosnącej. Jeżeli chcemy wiedzieć, do którego banku musimy się odwołać, aby uzyskać dostęp do określonego adresu pamięci POVOXa, bierzemy część tego adresu opisaną x-ami i traktujemy ją jako indeks w tej tablicy. Wtedy procesor ZAWSZE będzie mógł uzyskać dostęp do właściwego miejsca w pamięci.

       Czyli adres dla tego interface'u może wyglądać np. tak: $02E1F5 (binarnie: %00101110000111110101), ale CPU, żeby odpowiedni bajt zaadresować, będzie musiał z tego adresu wydzielić numer banku (%001011), na jego podstawie pobrać z tablicy kod banku dla PIA, pozostałą część (%10000111110101) dodać do $4000 (bitowe OR), aby otrzymać adres wewnątrz banku, czyli %0110000111110101 -po ludzku $61F5.

       Przy takim adresowaniu nie trzeba się bawić w przełączanie banków, próbki mogą mieć więc dowolną długość, ograniczoną tylko ilością dodatkowej pamięci. POVOX adresuje całą pamięć nie zwracając uwagi na podział na banki, CPU dalej korzysta z niej przez PORT B i obszar $4000-$7FFF, więc w zasadzie wszystko działa tak, jak do tej pory.

       Po takim łopatologicznym opisie wszystko powinno być jasne. Jeżeli nie jest - idź na piwo, wytrzeźwiej, idź na piwo, wyśpij się, wytrzeźwiej, wypij dwie szklanki wody mineralnej niegazowanej, zjedz coś sycącego, odprowadź z organizmu niepotrzebne substancje i w pełni skupiony i wypoczęty zacznij czytanie od linii numer jeden. Jeżeli dalej nie rozumiesz, to wróć się do podstawówki. Po kilku (-nastu) latach może zrozumiesz. ;>

       A Ci, którzy zrozumieli, mogą z dumą i rozkoszą rozpocząć dalsze czytanie.

       Ponieważ POVOX ma zastępować całą procedurkę odtwarzania sampli, łącznie z ich zapętlaniem, więc po dojściu do adresu określonego w CxEND, powinien wstawiać adres początku pętli CxREP do CxADR.

       Jak wiadomo, aby odtwarzać sample z różną częstotliwością, należy po przepisaniu odpowiedniej danej do rejestru COVOXa, należy zwiększyć adres o pewną stałą wartość, od której zależy wysokość dźwięku. Wartość ta bardzo często ma "coś" po przecinku, stąd rejestr CxIND określający ułamkową część adresu, której wartość nie ma wpływu na to, skąd pobierane są dane. Rejestr CxFRQ również zawiera liczbę w takim zakamuflowanym zmienno (a właściwie stało- bo przecinek stoi zawsze między siódmym a ósmym bitem) przecinkowym formacie, gdzie młodszy bajt oznacza część ułamkową, dodawaną do CxIND w każdym przebiegu, a starszy jest dodawany do najmłodszego bajtu CxADR. Proste, prawda?

       Symbolicznie można zapisać działania przy dodawaniu adresów w następujący sposób:

                x3x2x1.y1
               +    z2.z1
          ----------------
                a3a2a1.c1

x3x2x1 to zawartość rejestru CxADR, gdzie x3 to bajt najstarszy, a x1 -najmłodszy. y1 to CxIND, czyli ułamek w adresie, z1 to młodszy bajt CxFRQ, z2 to bajt starszy.

       Teraz chyba jest to zrozumiałe. Wynikiem jest liczba a3a2a1, wpisywana do CxADR (a3 bajtem najstarszym,a1 - najmłodszym) zawierająca część ułamkową c1 wpisywaną do CxIND. Czyli innymi słowy układ dodaje fizycznie liczbę dwubajtową do czterobajtowej, ale najmłodszy bajt adresu to część ułamkowa, bo właściwy adres znajduje się w starszych 3 bajtach, zawierających część całkowitą Tak więc dla uproszczenia budowy układu CxIND powinien znajdować się tuż przed CxADR, stanowiąc z nim jedną całość 32-bitowego adresu.

       Bardziej łopatologicznie już nie potrafię. Jeżeli masz problemy z tymi ułamkami, to zajrzyj do Seriousa o ile dobrze pamiętam numer #10, gdzie Króger w artku pt. "Siedemnastobitowa arytmetyka" bardzo rzeczowo opisał ten temat.

       Przykładowo odtwarzanie sampla tak, jak jest zapisany w pamięci, czyli bajt po bajcie, musi rozpocząć się wpisaniem 0 do CxIND, adresu początku sampla do CxADR, adresu końca sampla do CxEND, adresu początku pętli do CxREP, i w końcu $0100 do CxFRQ.

       Dlaczego aqrat tyle? Bo wtedy ułamkowa część adresu nie jest w ogóle ruszana (dodawanie zera), w każdym cyklu POVOXa do całkowitej części adresu dodawane jest 1 (starszy bajt CxFRQ), więc próbki są puszczane po kolei.

       Odtwarzanie sampla oktawę niżej realizuje się przez wpisanie $0080 do CxFRQ, a oktawę wyżej - CxFRQ=$0200.

       Jasne?

       I wszystko byłoby pięknie, gdyby nie... moja dzika fanaberia ze zmianą głośności. Domyślam się, że o ile dodawanie można zrealizować w dość prosty, działający szybko sposób, o tyle układ wykonujący odpowiednie dzielenia w wystarczająco krótkim czasie byłby chyba zbyt skomplikowany, a nawet jeśli nie, to pewnie zbyt drogi. A w ogóle - po co robić układ o możliwościach arytmetycznych przewyższających CPU, tylko do puszczania sampli?
No? Dlatego mając typowo koderski nawyk tablicowania wszystkiego, co się da, proponuję dołożenie EPROMki zawierającej tablice głośności. Jeżeli ustalić 32 poziomy głośności oraz poziom "0" - ciszę (jak w Neo), to tablice zajmowałyby równo 8kB, przy czym w tablicach pomijamy ową ciszę, bo układ może łatwo wykryć, że jest wpisana głośność 0 i po prostu bez żadnej zabawy wpisać 128 do właściwego rejestru COVOXa, ewentualnie rozpoznać maksymalną głośność i po prostu przepisać sampla, z pominięciem tablicy.

       No i fajnie by było, gdyby te tablice były numerowane tak jak w Neo- i ProTrackerze, czyli parzystymi numerkami z zakresu 0-64.

       Myślę, że jest to jasne. No a jeśli nie... to wiesz, co masz robić! ;>

       To tyle opisu szczegółowego, a teraz rozpiska algorytmu, który miałby realizować opisywany interface w każdym swoim cyklu. Jest taki język, który nazywa się BASIC. Moim zdaniem jego najlepszym zastosowaniem jest przedstawianie algorytmów w formie krótkiej, zwięzłej i zrozumiałej. Więc... jak nie znasz BASICa, to... nie wracaj się do podstawówki, tylko czytaj Zientarę! hehe... ;>

       Jest małe ale... Otóż tym razem zastosuję nieco zmodyfikowany TBXL, bo mamy coś takiego jak słowa 3-bajtowe (24 bity), więc odpowiednie dla nich POKE i PEEK to TPOKE i TPEEK, analogicznie jak dla 2-bajtowych są DPOKE i DPEEK. A że są też słowa 4-bajtowe (PEEK(CxIND)+TPEEK(CxADR)*256), to dla uproszczenia zastosujemy do nich instrukcję QPOKE i funkcję QPEEK. Jak przebiega adresacja, to już wiemy.

    . pobranie danych z pamięci
 10 CPUHALT=1
 15 A=PEEK(TPEEK(CxADR))
 20 CPUHALT=0

    . wpisanie do COVOXa
 25 IF PEEK(CxVOL)=$40
 30   POKE COVOXx,A
 35 ELSE
 40   POKE COVOXx,VTAB(PEEK(CxVOL) DIV 2,A)

ewentualnie:
 40   POKE COVOXx,EPEEK(PEEK(CxVOL) DIV 2*128+A)
 45 ENDIF

    . aktualizacja adresów
 50 QPOKE CxIND,QPEEK(CxIND)+DPEEK(CxFRQ)
 55 IF TPEEK(CxADR)>=TPEEK(CxEND) THEN TPOKE CxADR,TPEEK(CxREP)

Takie "co" kopiujemy cztery razy, za "x" podstawiając kolejno 1, 2, 3 i 4.

       No i parę słów komentarza:

       Do pierwszej części - umówmy się, że stan zmiennej CPUHALT odzwierciedla stan blokady kompa. 1 oznacza, że praca 6502 jest wstrzymana. Oczywiście ANTICa też. O ile mi wiadomo, 6502 ma możliwość zablokowania przez podanie odpowiedniego sygnału na pin HALT (tylko w ATARI), ale z ANTICiem będziecie się chyba musieli trochę pomęczyć.

       Do części drugiej - sprawdzamy, czy głośność jest maksymalna, w praktyce wystarczy sprawdzić stan bitu 6 (licząc od 0) - jeżeli jest ustawiony, to głośność jest maksymalna, więc przepisujemy próbkę prosto do COVOXa.

       W przeciwnym razie pobieramy wartość z tablicy w EPROMce, gdzie młodszy bajt adresu w EPROM to bajt pobrany z pamięci, a starszy składa się z bitów 5-1 CxVOL. Funkcja EPEEK odpowiada właśnie za pobranie bajtu z tablicy w EPROM.

       Część trzecia jest chyba zrozumiała. "Dodawaczkę" (element ALU) na upartego można złożyć nawet z "paru" ;) bramek logicznych.

       Aha. No i jeszcze rejestr CVCTL. Pierwsze, co przychodzi logicznie myślącemu człowiekowi do głowy, jeśli chodzi o kontrolę układu, to (co jest chyba jasne) jego włączanie i wyłączanie. Do tego wystarczy jeden dowolny bit tego rejestru. Jeżeli ma własny zegar, o częstotliwości powiedzmy 31kHz, bo taka jest potrzebna, jeśli odtwarzamy co pół linii (dla zaokrąglenia można przyjąć 32kHz), to każdy impuls takiego zegara powoduje wykonanie algorytmu, nad którego opisaniem tyle się już napociłem, że mam już dość... Ale ktoś może zechcieć wrócić do 15.5 kHz, czyli co 1 linię, i przydałaby się możliwość ustawienia takiego trybu, że odtwarzania próbki powodowałby tylko co drugi impuls. Fanatycy, którzy zrealizują ultraszybki układ mogą wsadzić zegar 62kHz, a wtedy to nawet grzybiarzom gały wyjdą na wierzch, byleby była możliwość redukcji do 31 i 15.5 kHz, ewentualnie 20.67kHz (62/3). I to byłby jedyny przypadek, gdzie przydałby się prosty dzielnik częstotliwości.

       Natomiast jeżeli nie masz ochoty na zabawę z zegarkami i dzielnikami częstotliwości, to mam ciekawą alternatywę: jakikolwiek wpis do CVCTL powoduje wykonanie "procedurki".

       I to już właściwie wszystko.

       Hmm... domyślam się, że praktyczna realizacja powyższych postulatów może być trudna, a cały ten "POVOX" może mieć rozmiary i cenę większą od samego kompa... Ale... To wszystko tylko wymysły mojej wybujałej wyobraźni, sprowokowanej przez przygody przy pisaniu procedury odtwarzającej sample na COVOXie. W większości komputerów układy odpowiedzialne za generowanie grafiki czy dźwięku odciążają w dużym stopniu procesor w tych działaniach, więc dlaczego Atarka miałaby pozostać w tyle?

       Tak w ogóle, to zastanawiam się, czy może to mieć jakiś sens, skoro Pasiu ciągle udoskonala swoją "Słodką szesnastkę" i może zamiast pisać wersje dla "POVOXa", zrobiłbym te same dla 65c816 z zegarem powiedzmy 16MHz, oczywiście w trybie natywnym... Co...?

       Tylko czy Turbo-816 będzie na tyle dostępne? Tak czy inaczej nie zapomnę nigdy o posiadaczach "starego" COVOXa (jak to ładnie brzmi...) i ciągle będą mieli nowe możliwości NeoTracker-a, ale przy częstotliwości 11.7 kHz... :(

       Powodzenia i... miłej zabawy :) przy pracy ;> nad "POVOXem" życzy

epi/Allegresse

pe|es: wiem, że już chcę o wiele za dużo, ale nie bijcie, dobra? ...
Może by tak jeszcze POVOX działał w drugą stronę, to znaczy zamiast przepisywać dane z pamięci na wyjście audio, przepisywałby je z wejścia (przez przetworniki A/D) do pamięci? To by było cudo. :)

...

Ał! Au! AAAAAAAA! Prosiłem, żeby nie bić! Aaauuuu!!! Dooobra... spadam!