Na úvodní článek o čipu POKEY dnes navážeme. Ukážeme si totiž některé možnosti, které integrovaný obvod POKEY nabízí pro generování zvuků. Připomeňme si, že POKEY obsahuje čtyři zvukové kanály, z nichž každý dokáže dělit zvolenou vstupní frekvenci zadanou osmibitovou konstantou a vytvářet tak zvuky o potřebné frekvenci (jedná se přitom o čistý obdélníkový signál). Dále je možné tento obdélníkový signál zkombinovat s výstupem z takzvaných poly čítačů (resp. posuvných registrů se zpětnou vazbou), které dokážou generovat pseudonáhodný signál s určitou periodou odvozenou od šířky posuvného registru.
Co se dozvíte v článku
- My God, it's full of squares
- Modifikace vstupní frekvence pro všechny čtyři zvukové kanály
- Demonstrační příklad: změna vstupní frekvence z 64 kHz na 15 kHz
- Nejnižší tón, který lze získat ze zvukového kanálu s osmibitovým děličem
- Demonstrační příklad: přehrání nejnižšího tónu osmibitovým zvukovým kanálem
- Zázněje získané ze dvou zvukových kanálů s podobnou výstupní frekvencí
- Demonstrační příklad: praktické přehrání dvou tónů vytvářejících zázněje
- Zázněje získané ze čtyř zvukových kanálů s podobnou výstupní frekvencí
- Demonstrační příklad: praktické přehrání čtyř tónů vytvářejících zázněje
- Různé varianty zapojení poly čítačů a jejich vliv na výsledný zvuk
- Demonstrační příklad: přepínání mezi všemi variantami zapojení poly čítačů
- Jeden z limitů osmibitových zvukových kanálů
- Demonstrační příklad: ovládání frekvence joystickem a zjištění limitů osmibitových zvukových kanálů
- Spojení dvou kanálů do jednoho kanálu se šestnáctibitovou děličkou, využití vstupní frekvence CPU
- Demonstrační příklad: ovládání frekvence 16bitového kanálu joystickem
- Sampling
- Příloha: Makefile pro překlad všech demonstračních příkladů
- Repositář s demonstračními příklady
- Odkazy na Internetu
Nakonec čip POKEY umožňuje kombinaci zvukových kanálů dvěma způsoby: buď se dva osmibitové děličky spojí do děličky šestnáctibitové nebo je jeden kanál použit jako (velmi primitivní) horní propust pro kanál druhý. Nezávisle na tom, jak se kanály vzájemně ovlivňují je jejich výstup (obdélníkový signál o určité amplitudě) „sečten“ a poslán na jediný výstupní pin AUD (Audio Output).
My God, it's full of squares
Čip POKEY generuje všechny zvuky jen s využitím obdélníkových (pravidelných nebo pseudonáhodných) signálů, které jsou nakonec sečteny a vyvedeny na jediný (analogový) pin. Jinými slovy nepodporuje žádné generátory obálky, takže se v tomto ohledu odlišuje například od AY-3–8910 nebo SIDa (Commodore C64). A liší se i audio výstupem vyvedeným na jediný analogový pin, takže POKEY nelze (samostatně) použít pro stereo výstup. Na druhou stranu však existovala rozšíření Atari o druhý čip POKEY.
Pro zajímavost budou v dnešním článku vizualizovány průběhy audio signálů získaných z emulátoru (!), takže výsledek nemusí odpovídat chování reálného čipu POKEY (sice jsou zvýrazněny překmity, ovšem obdélníkový signál je poté ustálený, což u reálného POKEY nenastává). Pro vizualizaci je použitý následující (relativně) triviální skript napsaný v Pythonu:
import matplotlib.pyplot as plt
import numpy as np
import wave
import sys
spf = wave.open("atari000.wav", "r")
signal = spf.readframes(-1)
signal = np.frombuffer(signal, np.int16)
# jen monofonní záznam (což u Atari platí)
if spf.getnchannels() == 2:
print("Just mono files")
sys.exit(0)
plt.figure(1)
# zde lze realizovat výběr části signálu (okno)
plt.plot(signal[od:do])
plt.show()
plt.savefig("01.png")
Vizualizované výsledky pro oba demonstrační příklady z předchozího článku vypadají takto:
Obrázek 1: Čistý tón je představovaný obdélníkovým signálem.
Obrázek 2: Změna časové základny, jeden obdélník čistého tónu.
Obrázek 3: Výstup z poly čítače (pseudonáhodný šum).
Obrázek 4: Výstup z poly čítače (pseudonáhodný šum) při změně časové základny.
Modifikace vstupní frekvence pro všechny čtyři zvukové kanály
Připomeňme si, že každý zvukový kanál se ovládá přes dvojici hardwarových řídicích registrů AUDCx a AUDFx, kde x=1..4. Ovšem navíc existuje i jeden „globální“ řídicí registr nazvaný AUDCTL, ve kterém se nultým bitem přepíná vstupní frekvence pro všechny zvukové kanály z přibližně 64 kHz (výchozí hodnota) na 15 kHz (předpokládejme nyní, že ostatní bity jsou nulové, což je ostatně výchozí stav):
| Bit | Stručný popis |
|---|---|
| 7 | záměna 17bitového poly čítače za 9bitový čítač |
| 6 | vstupní hodinový signál pro kanál 1 bude mít frekvenci CPU (1,77 nebo 1,79 MHz) |
| 5 | vstupní hodinový signál pro kanál 3 bude mít frekvenci CPU (1,77 nebo 1,79 MHz) |
| 4 | vstupem kanálu číslo 2 bude kanál číslo 1 (spojení kanálů do 16bitového děliče) |
| 3 | vstupem kanálu číslo 4 bude kanál číslo 3 (spojení kanálů do 16bitového děliče) |
| 2 | konfigurace primitivní horní propusti pro kanál 1 tvořené kanálem 3 |
| 1 | konfigurace primitivní horní propusti pro kanál 2 tvořené kanálem 4 |
| 0 | vstupní hodinový signál bude mít frekvenci cca 15kHz a ne 64kHz |
Skutečně používané frekvence ovšem nejsou přesně rovny 64 kHz a 15 kHz, protože příslušné signály jsou získány z hodinových cyklů mikroprocesoru dělením konstantami 114 a 28 (což souvisí se zobrazením řádků na TV atd.). Přesné hodnoty pro počítač určený pro normu PAL jsou:
f15kHz = 1773,447 / 114 = 15,556 kHz f64kHz = 1773,447 / 28 = 63,337 kHz
Hodnoty bitů 5 a 6 mají praktický význam pouze v případě, že je dvojice kanálů spojena do jednoho 16bitového kanálu. To si pochopitelně taktéž ukážeme.
Demonstrační příklad: změna vstupní frekvence z 64 kHz na 15 kHz
V dnešním prvním demonstračním příkladu se nejdříve přehraje komorní A, tedy tón o frekvenci 440 Hz (v podání POKEY ovšem jen přibližně). Následně se po stisku klávesy sníží vstupní frekvence z 64 kHz na 15 kHz, čímž pochopitelně dojde i ke snížení přehrávaného tónu na přibližně 109 Hz:
; ---------------------------------------------------------------------
; Generování zvuků čipem POKEY.
;
; Přehrání tónu komorního A (440 Hz) zvukovým kanálem číslo 1.
; Změna vstupní frekvence z 64 kHz na 15 kHz.
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PURE_TONE_MASK = %10100000
INPUT_FREQ_15KHZ = %00000001
.proc main
jsr get_key ; čekání na stisk klávesy
lda #PURE_TONE_MASK+10 ; hlasitost (0-15)
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
lda #71 ; dělič základní frekvence
sta AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY
lda #00 ; základní tón odvozený od 64kHz
sta AUDCTL ; zápis do řídicího registru čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #INPUT_FREQ_15KHZ ; základní tón odvozený od 15kHz
sta AUDCTL ; zápis do řídicího registru čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #0 ; vypnutí zvuku (nulová hlasitost)
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
loop: jmp loop
.endproc
; ---------------------------------------------------------------------
; čekání na stisk klávesy
; ---------------------------------------------------------------------
.proc get_key
KBHANDLER = $e424 ; rutina pro cteni klavesy
lda KBHANDLER+1 ; cteni horni casti adresy ulozene v ROM
pha ; ulozeni na zasobnik
lda KBHANDLER ; cteni dolni casti adresy ulozene v ROM
pha ; ulozeni na zasobnik
rts ; vyber adresy ze zasobniku + skok
; zde neni nutne mit RTS
.endproc
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Nejnižší tón, který lze získat ze zvukového kanálu s osmibitovým děličem
Osmibitové zvukové kanály mají jednu velkou výhodu – jsou čtyři, což umožňuje snadnou implementaci polyfonie (v trackerech se bude jednat o skladby se čtyřmi stopami). Ovšem pouze osmibitová dělička znamená, že u vysokých frekvencí je možné jen velmi hrubý výběr frekvence (rozdíl mezi dělením jedničkou a dvojkou je či může být příliš velký) a taktéž nejnižší tón může být pro některé účely příliš vysoký. Ostatně si to můžeme vypočítat, a to v situaci, kdy nastavíme nejnižší možnou vstupní frekvenci na vstupu do zvukového kanálu.
Připomeňme si, že základem pro výpočty je frekvence CPU, který má ve variantě pro PAL frekvenci:
fclk = 1,7734470 Mhz = 1773447 Hz
Nulový bit registru AUDCTL nastavíme na jedničku, takže frekvence na vstupu zvukových kanálů bude vypočtena vydělením frekvence CPU konstantou 114 (a nikoli pouze 28). Číselně:
fin = fclk / 114 = 1773447 / 114 = 15556 Hz
Dále nastavíme pátý a sedmý bit registru AUDC1 na jedničku a šestý bit na nulu. To znamená, že se bude přehrávat čistý obdélníkový tón a navíc se vstupní frekvence v tomto případě dále dělí dvěma:
fin2 = fin / 2 = 15556 / 2 = 7778 Hz
Dělitel zapisovaný do registru AUDF1 nastavíme na nejvyšší možnou hodnotu 255, takže skutečná frekvence, která bude slyšet z reproduktoru, bude rovna:
fout = fin2 / (255+1) = 7778 / 256 = 30,38 Hz
Přepis předchozího postupu do formy programu je snadný:
INPUT_FREQ_15KHZ = %00000001 lda #INPUT_FREQ_15KHZ ; základní tón odvozený od 15kHz sta AUDCTL ; zápis do řídicího registru čipu POKEY lda #255 ; dělič základní frekvence sta AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY
Obrázek 5: Emulátor ukazuje stále stejný průběh signálu, nezávisle na jeho frekvenci.
Demonstrační příklad: přehrání nejnižšího tónu osmibitovým zvukovým kanálem
Po překladu a spuštění následujícího demonstračního příkladu se nejdříve přehraje tón o nejnižší frekvenci získaný ze vstupní frekvence 64 kHz a následně (po stisku klávesy) se přepne vstupní frekvence na 15 kHz, takže se přehraje tón o frekvenci přibližně 30 Hz:
; ---------------------------------------------------------------------
; Generování zvuků čipem POKEY.
;
; Změna vstupní frekvence z 64 kHz na 15 kHz.
; Nastavení děličky na nejvyšší možnou hodnotu 255.
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PURE_TONE_MASK = %10100000
INPUT_FREQ_15KHZ = %00000001
.proc main
jsr get_key ; čekání na stisk klávesy
lda #PURE_TONE_MASK+10 ; hlasitost (0-15)
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
lda #255 ; dělič základní frekvence
sta AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY
lda #00 ; základní tón odvozený od 64kHz
sta AUDCTL ; zápis do řídicího registru čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #INPUT_FREQ_15KHZ ; základní tón odvozený od 15kHz
sta AUDCTL ; zápis do řídicího registru čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #0 ; vypnutí zvuku (nulová hlasitost)
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
loop: jmp loop
.endproc
; ---------------------------------------------------------------------
; čekání na stisk klávesy
; ---------------------------------------------------------------------
.proc get_key
KBHANDLER = $e424 ; rutina pro cteni klavesy
lda KBHANDLER+1 ; cteni horni casti adresy ulozene v ROM
pha ; ulozeni na zasobnik
lda KBHANDLER ; cteni dolni casti adresy ulozene v ROM
pha ; ulozeni na zasobnik
rts ; vyber adresy ze zasobniku + skok
; zde neni nutne mit RTS
.endproc
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Zázněje získané ze dvou zvukových kanálů s podobnou výstupní frekvencí
Pokusme se zjistit, jaký zvuk bude čipem POKEY vygenerován v případě, že povolíme dva zvukové kanály 1+2 a nastavíme je na stejnou hlasitost a na vytváření čistých tónů. POKEY je nakonfigurován do výchozího stavu, tj. vstupní signál do zvukových kanálů bude nastaven na 64 kHz a všechny kanály poběží samostatně (osmibitové děliče):
lda #PURE_TONE_MASK+10 ; hlasitost (0-15) sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY sta AUDC2 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 2 čipu POKEY
Děliče zvukových kanálů nastavíme na hodnoty 255 a 254, tedy na nejvyšší možné (a současně i vzájemně odlišné) hodnoty:
lda #255 ; dělič základní frekvence sta AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY lda #254 ; dělič základní frekvence sta AUDF2 ; zápis do registru děliče frekvence pro zvukový kanál číslo 2 čipu POKEY
Frekvence obou tónů můžeme snadno vypočítat:
fin2 = fin / 2 = 63337 / 2 = 31688 Hz
Skutečná frekvence, která bude slyšel z reproduktoru, bude rovna:
fout1 = fin2 / (255+1) = 31688 / 256 = 123,8 Hz
a:
fout2 = fin2 / (254+1) = 31688 / 255 = 124,26 Hz
Frekvenční rozdíl mezi oběma tóny (tedy necelý 1 Hz) bude tvořit zázněje (beats) (a onen 1 Hz je jejich frekvence). Jedná se o klasický trik používaný na Atari v mnoha hrách.
Povšimněte si, že součet obou generovaných obdélníkových signálů není lineární Hodnota odpovídající stavu, kdy je jeden signál v logické jedničce a druhý v nule, je evidentně posunuta nahoru. Projevuje se zde logaritmické měřítko na vertikální ose (amplituda výsledného signálu):
Obrázek 6: Nelineární součet dvou signálů o přibližně stejné frekvenci ze dvou zvukových kanálů.
Z dalšího průběhu je patrný vliv součtu obou tónů a okolo cyklu 3000 můžeme vidět, že se oba průběhu vyrušují.
Obrázek 7: Při změně časové základny je ve druhé třetině patrný vznik zázněje.
Demonstrační příklad: praktické přehrání dvou tónů vytvářejících zázněje
Opět si pochopitelně ukážeme přehrání tónů tvořících zázněje prakticky. V dalším demonstračním příkladu se použijí pouze dva zvukové kanály 1 a 2, u nichž jsou děličky nastaveny na hodnoty 254 a 255, což po vydělení znamená, že signály budou mít jen nepatrně odlišnou periodu. A právě tato odlišnost povede ke slyšitelným změnám v přehrávaném zvuku. Pro zajímavost je možné přepínání mezi vstupní frekvencí 64 kHz a 15 kHz:
; ---------------------------------------------------------------------
; Generování zvuků čipem POKEY.
;
; Vygenerování tónu se záznějemi.
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PURE_TONE_MASK = %10100000
INPUT_FREQ_15KHZ = %00000001
.proc main
jsr get_key ; čekání na stisk klávesy
lda #PURE_TONE_MASK+10 ; hlasitost (0-15)
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
sta AUDC2 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 2 čipu POKEY
lda #255 ; dělič základní frekvence
sta AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY
lda #254 ; dělič základní frekvence
sta AUDF2 ; zápis do registru děliče frekvence pro zvukový kanál číslo 2 čipu POKEY
lda #00 ; základní tón odvozený od 64kHz
sta AUDCTL ; zápis do řídicího registru čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #INPUT_FREQ_15KHZ ; základní tón odvozený od 15kHz
sta AUDCTL ; zápis do řídicího registru čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #0 ; vypnutí zvuku (nulová hlasitost)
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
loop: jmp loop
.endproc
; ---------------------------------------------------------------------
; čekání na stisk klávesy
; ---------------------------------------------------------------------
.proc get_key
KBHANDLER = $e424 ; rutina pro cteni klavesy
lda KBHANDLER+1 ; cteni horni casti adresy ulozene v ROM
pha ; ulozeni na zasobnik
lda KBHANDLER ; cteni dolni casti adresy ulozene v ROM
pha ; ulozeni na zasobnik
rts ; vyber adresy ze zasobniku + skok
; zde neni nutne mit RTS
.endproc
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Zázněje získané ze čtyř zvukových kanálů s podobnou výstupní frekvencí
Zázněje vytvářené dvojicí kanálů jsme si již ukázali v předchozím textu. Ovšem nic nám nebrání ve vygenerování složitějšího výsledného zvukového signálu, ve kterém se na záznějích podílí všechny čtyři zvukové kanály. Nejdříve nastavíme jejich hlasitost a současně určíme, že mají produkovat čistý (obdélníkový) tón:
lda #PURE_TONE_MASK+10 ; hlasitost (0-15) sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY sta AUDC2 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 2 čipu POKEY sta AUDC3 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 3 čipu POKEY sta AUDC4 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 4 čipu POKEY
Frekvence kanálů, resp. přesněji řečeno nastavení jejich děliček, je zvoleno tak, aby byly frekvence relativně nízké a aby se lišily jen velmi málo:
lda #255 ; dělič základní frekvence sta AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY lda #254 ; dělič základní frekvence sta AUDF2 ; zápis do registru děliče frekvence pro zvukový kanál číslo 2 čipu POKEY lda #253 ; dělič základní frekvence sta AUDF3 ; zápis do registru děliče frekvence pro zvukový kanál číslo 3 čipu POKEY lda #252 ; dělič základní frekvence sta AUDF4 ; zápis do registru děliče frekvence pro zvukový kanál číslo 4 čipu POKEY
A takto bude vypadat výsledek po jeho vizualizaci. Opět je patrné (a to ještě více, než v případě dvou kanálů), že součet amplitud není lineární:
Obrázek 8: Výsledný signál složený ze čtyř signálů a nepatrně rozdílných frekvencích.
Obrázek 9: Zázněje při součtu čtyř signálů.
Demonstrační příklad: praktické přehrání čtyř tónů vytvářejících zázněje
Demonstrační příklad z předchozího textu, který po svém spuštění využil dva zvukové kanály, je možné velmi snadno upravit (resp. spíše rozšířit) do podoby využívající všechny čtyři kanály. Děličky jsou nastaveny na hodnoty 255, 254, 253 a 252, takže zázněje budou opět vznikat, ale budou mít komplikovanější průběh, než při použití pouze dvojice zvukových kanálů (klávesou se opět přepíná mezi základní vstupní frekvencí 64 kHz a 15 kHz):
; ---------------------------------------------------------------------
; Generování zvuků čipem POKEY.
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PURE_TONE_MASK = %10100000
INPUT_FREQ_15KHZ = %00000001
.proc main
jsr get_key ; čekání na stisk klávesy
lda #PURE_TONE_MASK+10 ; hlasitost (0-15)
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
sta AUDC2 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 2 čipu POKEY
sta AUDC3 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 3 čipu POKEY
sta AUDC4 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 4 čipu POKEY
lda #255 ; dělič základní frekvence
sta AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY
lda #254 ; dělič základní frekvence
sta AUDF2 ; zápis do registru děliče frekvence pro zvukový kanál číslo 2 čipu POKEY
lda #253 ; dělič základní frekvence
sta AUDF3 ; zápis do registru děliče frekvence pro zvukový kanál číslo 3 čipu POKEY
lda #252 ; dělič základní frekvence
sta AUDF4 ; zápis do registru děliče frekvence pro zvukový kanál číslo 4 čipu POKEY
lda #00 ; základní tón odvozený od 64kHz
sta AUDCTL ; zápis do řídicího registru čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #INPUT_FREQ_15KHZ ; základní tón odvozený od 15kHz
sta AUDCTL ; zápis do řídicího registru čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #0 ; vypnutí zvuku (nulová hlasitost)
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
loop: jmp loop
.endproc
; ---------------------------------------------------------------------
; čekání na stisk klávesy
; ---------------------------------------------------------------------
.proc get_key
KBHANDLER = $e424 ; rutina pro cteni klavesy
lda KBHANDLER+1 ; cteni horni casti adresy ulozene v ROM
pha ; ulozeni na zasobnik
lda KBHANDLER ; cteni dolni casti adresy ulozene v ROM
pha ; ulozeni na zasobnik
rts ; vyber adresy ze zasobniku + skok
; zde neni nutne mit RTS
.endproc
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Různé varianty zapojení poly čítačů a jejich vliv na výsledný zvuk
V předchozím článku jsme si řekli, že výstup každého ze zvukových kanálů může být získán i kombinací sekvence bitů získaných z poly čítače (posuvného registru se zpětnou vazbou), který je zkombinován s čistým obdélníkovým signálem – tento signál vlastně „sampluje“ výstup z poly čítače nebo z dvojice poly čítačů (v závislosti na zvoleném zapojení):
Obrázek 10: Volba způsobu zapojení zvukového kanálu 1-4 přes registr AUDCx.
Poly čítače na čipu POKEY mají různou bitovou délku. Kratší délky vedou ke vzniku zabarveného tónu (podle frekvence se může napodobit motor, kulomet atd.), delší bitové délky přidávají do výsledku více (pseudo)náhodnosti. Nejdelší poly čítač má délku sedmnácti bitů a výsledek lze v tomto případě považovat za prakticky zcela náhodný. Bude pochopitelně vhodné si vše ověřit v praxi, takže začneme definicí bitů zapisovaných do registru AUDCTL:
PURE_TONE_MASK = %10100000 POLY_4_BIT_MASK = %11000000 POLY_5_BIT_MASK = %01100000 POLY_17_BIT_MASK = %10000000 POLY_4_5_BIT_MASK = %01000000 POLY_17_5_BIT_MASK = %00000000
Následně budeme přehrávat tón o stejné frekvenci, ovšem vždy rozdílně zabarvený. Přepínání mezi různými variantami zabarvení se provede libovolnou klávesou (pokud ovšem touto klávesou není Shift, Control nebo Break :-):
lda #PURE_TONE_MASK+10 ; hlasitost (0-15) sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY jsr get_key ; čekání na stisk klávesy lda #POLY_4_BIT_MASK+10 ; hlasitost (0-15) + noise sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY jsr get_key ; čekání na stisk klávesy lda #POLY_5_BIT_MASK+10 ; hlasitost (0-15) + noise sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY jsr get_key ; čekání na stisk klávesy lda #POLY_17_BIT_MASK+10 ; hlasitost (0-15) + noise sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY jsr get_key ; čekání na stisk klávesy lda #POLY_4_5_BIT_MASK+10 ; hlasitost (0-15) + noise sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY jsr get_key ; čekání na stisk klávesy lda #POLY_17_5_BIT_MASK+10 ; hlasitost (0-15) + noise sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY jsr get_key ; čekání na stisk klávesy
Obrázek 11: Další ukázka kombinace dvou zvukových kanálů.
Obrázek 12: Jeden z průběhů šumu zkombinovaného s hodinovým signálem.
Demonstrační příklad: přepínání mezi všemi variantami zapojení poly čítačů
V dnešním čtvrtém demonstračním příkladu je možné libovolnou klávesou přepínání mezi různými variantami zapojení poly čítačů. V prvním „kole“ je výsledek získaný z poly čítače/čítačů modulován zvukovým signálem odpovídajícím komornímu A (440 Hz) a posléze v kole druhém nejvyšší možnou nastavitelnou frekvencí 65kHz/2 (otázka pro čtenáře: bude něco slyšitelné? a z jakého důvodu?):
; ---------------------------------------------------------------------
; Generování zvuků čipem POKEY.
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
; Bit 7 Bit 6 Bit 5 Výsledek
; 0 0 0 17 bit poly → 5 bit poly
; 0 0 1 5 bit poly
; 0 1 0 4 bit poly → 5 bit poly
; 0 1 1 5 bit poly
; 1 0 0 17 bit poly
; 1 0 1 čistý tón
; 1 1 0 4 bit poly
; 1 1 1 čistý tón
PURE_TONE_MASK = %10100000
POLY_4_BIT_MASK = %11000000
POLY_5_BIT_MASK = %01100000
POLY_17_BIT_MASK = %10000000
POLY_4_5_BIT_MASK = %01000000
POLY_17_5_BIT_MASK = %00000000
; Frekvence CPU (PAL) = 1.7734470 MHz:
; f_clk = 1773447 Hz
;
; Pokud je AUDCTL.0 nastaven na nulu, dělí se 28:
; f_in = f_clk / 28 = 1773447 / 28 = 63337 Hz
;
; Pokud je AUDCTL.7 == 1 and AUDCTL.5 == 1, dělí se f_in dvěma
; f_in_2 = f_in / 2 = 63337 / 2 = 31688 Hz
;
; Tato frekvence se přímo dělí obsahem registru AUDF1 (+1), takže pro #71:
; f_out = f_in_2 / (71+1) = 31688 / 72 = 439 Hz
;
; S přimhouřením obou uší tedy dostaneme komorní A (440 Hz)
.proc main
jsr get_key ; čekání na stisk klávesy
lda #00 ; základní tón odvozený od 64kHz
sta AUDCTL ; zápis do řídicího registru čipu POKEY
lda #71 ; dělič základní frekvence
sta AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY
next:
lda #PURE_TONE_MASK+10 ; hlasitost (0-15)
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #POLY_4_BIT_MASK+10 ; hlasitost (0-15) + noise
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #POLY_5_BIT_MASK+10 ; hlasitost (0-15) + noise
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #POLY_17_BIT_MASK+10 ; hlasitost (0-15) + noise
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #POLY_4_5_BIT_MASK+10 ; hlasitost (0-15) + noise
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #POLY_17_5_BIT_MASK+10 ; hlasitost (0-15) + noise
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
jsr get_key ; čekání na stisk klávesy
lda #1 ; dělič základní frekvence (nejvyssi frekvence)
sta AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY
jmp next
lda #0 ; vypnutí zvuku (nulová hlasitost)
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
loop: jmp loop
.endproc
; ---------------------------------------------------------------------
; čekání na stisk klávesy
; ---------------------------------------------------------------------
.proc get_key
KBHANDLER = $e424 ; rutina pro cteni klavesy
lda KBHANDLER+1 ; cteni horni casti adresy ulozene v ROM
pha ; ulozeni na zasobnik
lda KBHANDLER ; cteni dolni casti adresy ulozene v ROM
pha ; ulozeni na zasobnik
rts ; vyber adresy ze zasobniku + skok
; zde neni nutne mit RTS
.endproc
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Jeden z limitů osmibitových zvukových kanálů
Problém zvukových kanálů s osmibitovou děličkou spočívá mj. i v tom, že je těžké dosáhnout dostatečně přesné frekvence u vyšších tónů. V předchozích kapitolách byl ukázán efekt záznějů spočívající v tom, že nízké tóny mohou mít velmi blízké frekvence. Ovšem u vyšších tónů tomu tak není, protože výsledná frekvence vznikne podílem a u vyšších tónů se nacházíme na strmé části křivky.
Ostatně si to můžeme ukázat i prakticky. V dalším příkladu bude možné pomocí joysticku měnit hodnotu osmibitového dělitele prvního zvukového kanálu. Tato hodnota je uložena v paměťové buňce označené návěštím divider a její výchozí hodnota je nastavena na 71, což odpovídá komornímu A (440 Hz):
; dělitel frekvence divider: .byte 71
Modifikace dělitele náklonem joysticku řeší následující subrutina, která je volána v průběhu VBI a tedy s přesně známou periodou (50× nebo 60× za sekundu):
; ---------------------------------------------------------------------
; subrutina pro obsluhu VBI
; ---------------------------------------------------------------------
.proc horizontal_movement
ldx divider
lda STICK0 ; čtení joysticku
cmp #11 ; je nakloněn doleva?
bne not_left
dex ; změna děliče
not_left:
cmp #7 ; je nakloněn doprava?
bne not_right
inx ; změna děliče
not_right:
stx AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY
stx divider
jmp XITVBV ; zpracovat zbytek odloženého VBLANKu
rts
.endproc
Nastavení vektoru VBI již dobře známe, takže jen v krátkosti:
; nastavit vektor pro odloženou VBI
lda #>horizontal_movement
sta VVBLKD+1
lda #<horizontal_movement
sta VVBLKD
Sami si můžete ověřit, že u nízkých tónů je přesnost nastavení dostatečná, kdežto u vysokých tónů tomu tak není: i krátký náklon joysticku vede k razantní změně výsledného zvuku.
Demonstrační příklad: ovládání frekvence joystickem a zjištění limitů osmibitových zvukových kanálů
Výše uvedený postup spočívající v použití subrutiny volané v rámci VBI je použit v dalším demonstračním příkladu, jehož úplný zdrojový kód vypadá následovně:
; ---------------------------------------------------------------------
; Generování zvuků čipem POKEY.
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PURE_TONE_MASK = %10100000
.proc main
lda #00 ; základní tón odvozený od 64kHz
sta AUDCTL ; zápis do řídicího registru čipu POKEY
lda #PURE_TONE_MASK+10 ; hlasitost (0-15)
sta AUDC1 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 1 čipu POKEY
; nastavit vektor pro odloženou VBI
lda #>horizontal_movement
sta VVBLKD+1
lda #<horizontal_movement
sta VVBLKD
loop:
jmp loop ; program vlastně nice nedělá - jen cyklí!
.endproc
; ---------------------------------------------------------------------
; subrutina pro obsluhu VBI
; ---------------------------------------------------------------------
.proc horizontal_movement
ldx divider
lda STICK0 ; čtení joysticku
cmp #11 ; je nakloněn doleva?
bne not_left
dex ; změna děliče
not_left:
cmp #7 ; je nakloněn doprava?
bne not_right
inx ; změna děliče
not_right:
stx AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY
stx divider
jmp XITVBV ; zpracovat zbytek odloženého VBLANKu
rts
.endproc
; dělič frekvence
divider: .byte 71
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Spojení dvou kanálů do jednoho kanálu se šestnáctibitovou děličkou, využití vstupní frekvence CPU
Autor čipu POKEY, kterým je legendární Doug Neubauer (mám ho na stejné úrovni, jako Woze, dokonce se k němu chovali stejně špatně, jako Steve Jobs k Wozovi), si dobře uvědomoval, že osmibitové děličky nebudou pro tvorbu hudby dostatečně široké. Z tohoto důvodu POKEY navrhl takovým způsobem, že umožnil spojení dvou osmibitových kanálů do jednoho kanálu šestnáctibitového. To ovšem nestačí, protože by se stále dělila relativně nízká frekvence 64 kHz nebo dokonce jen 15 kHz (a problém s výběrem vysokých frekvencí by se neodstranil). A proto je možné na vstup 16bitových kanálů připojit přímo frekvenci CPU. Tuto frekvenci má (pro slyšitelné zvuky) cenu dělit hodnotami vyššími než cca 60, takže sice „ztrácíme“ 60 možných hodnot, ovšem na výběr jich při spojení kanálů není pouze 255, ale 65535.
Ještě jednou (ale nikoli naposledy) se podívejme na bity řídicího registru AUDCTL. Spojení kanálů a výběr vyšší frekvence se řídí čtyřmi bity:
| Bit | Stručný popis |
|---|---|
| 6 | vstupní hodinový signál pro kanál 1 bude mít frekvenci CPU (1,77 nebo 1,79 MHz) |
| 5 | vstupní hodinový signál pro kanál 3 bude mít frekvenci CPU (1,77 nebo 1,79 MHz) |
| 4 | vstupem kanálu číslo 2 bude kanál číslo 1 (spojení kanálů do 16bitového děliče) |
| 3 | vstupem kanálu číslo 4 bude kanál číslo 3 (spojení kanálů do 16bitového děliče) |
To znamená, že v praxi lze realizovat:
- Spojení kanálů 1+2 a přepnutí vstupní frekvence kanálu 1 na frekvenci CPU. Kanály 3 a 4 zůstanou osmibitové.
- Spojení kanálů 3+4 a přepnutí vstupní frekvence kanálu 3 na frekvenci CPU. Kanály 1 a 2 zůstanou osmibitové.
- Spojení kanálů 1+2 i 3+4 + přepnutí vstupních frekvencí kanálů 1 i 3.
Ostatní kombinace nemají z praktického pohledu význam.
Abychom mohli čitelně nastavovat jednotlivé bity registru AUDCTL, nadefinujeme příslušné bitové masky:
CPU_FREQ_CHANNEL_1_MASK = %01000000 CPU_FREQ_CHANNEL_3_MASK = %00100000 JOIN_CANNELS_1_2_MASK = %00010000 JOIN_CANNELS_3_4_MASK = %00001000
Příklad povolení spojení kanálů 1+2 do jednoho 16bitového kanálu a nastavení vstupní frekvence na frekvenci CPU:
lda #CPU_FREQ_CHANNEL_1_MASK + JOIN_CANNELS_1_2_MASK ; tón odvozený od frekvence CPU
sta AUDCTL ; zápis do řídicího registru čipu POKEY
Toto nastavení použijeme v dnešním posledním demonstračním příkladu, ve kterém povolíme změnu obou osmibitových částí šestnáctibitové dělicí konstanty. Tyto části jsou uloženy ve dvou bajtech v operační paměti:
; děliče frekvence divider1: .byte 10 divider2: .byte 10
Ve VBI subrutině, tedy 50× nebo 60× za sekundu, bude možné pohybem prvního joysticku měnit (nezávisle na sobě) jak horních osm bitů, tak i spodních osm bitů dělicí konstanty a přehrát tak tón o (prakticky) libovolné slyšitelné i neslyšitelné frekvenci:
; ---------------------------------------------------------------------
; subrutina pro obsluhu VBI
; ---------------------------------------------------------------------
.proc horizontal_and_vertical_movement
ldx divider1
ldy divider2
lda STICK0 ; čtení joysticku
cmp #11 ; je nakloněn doleva?
bne not_left
dex ; změna děliče
not_left:
cmp #7 ; je nakloněn doprava?
bne not_right
inx ; změna děliče
not_right:
cmp #14 ; je nakloněn nahoru?
bne not_up
dey ; změna Y-ové souřadnice hráče
not_up:
cmp #13 ; je nakloněn dolů?
bne not_down
iny ; změna Y-ové souřadnice hráče
not_down:
stx AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY
sty AUDF2 ; zápis do registru děliče frekvence pro zvukový kanál číslo 2 čipu POKEY
stx divider1
sty divider2
jmp XITVBV ; zpracovat zbytek odloženého VBLANKu
rts
.endproc
Demonstrační příklad: ovládání frekvence 16bitového kanálu joystickem
V dnešním posledním demonstračním příkladu je implementován postup popsaný v předchozí kapitole: dva osmibitové kanály jsou spojeny do jednoho kanálu 16bitového a pomocí joysticku lze ovládat jak spodních, tak i horních osm bitů dělitele. Rychlost změn je ovlivněna televizní normou: 50 či 60 změn za sekundu:
; ---------------------------------------------------------------------
; Generování zvuků čipem POKEY.
; ---------------------------------------------------------------------
.include "atari.inc"
.CODE
PURE_TONE_MASK = %10100000
; Bit Stručný popis
; 7 záměna 17bitového poly čítače za 9bitový čítač
; 6 vstupní hodinový signál pro kanál 1 bude mít frekvenci CPU (1,77 nebo 1,79 MHz)
; 5 vstupní hodinový signál pro kanál 3 bude mít frekvenci CPU (1,77 nebo 1,79 MHz)
; 4 vstupem kanálu číslo 2 bude kanál číslo 1 (spojení kanálů do 16bitového děliče)
; 3 vstupem kanálu číslo 4 bude kanál číslo 3 (spojení kanálů do 16bitového děliče)
; 2 konfigurace primitivní horní propusti pro kanál 1 tvořené kanálem 3
; 1 konfigurace primitivní horní propusti pro kanál 2 tvořené kanálem 4
; 0 vstupní hodinový signál bude mít frekvenci cca 15kHz a ne 64kHz
CPU_FREQ_CHANNEL_1_MASK = %01000000
CPU_FREQ_CHANNEL_3_MASK = %00100000
JOIN_CANNELS_1_2_MASK = %00010000
JOIN_CANNELS_3_4_MASK = %00001000
.proc main
lda #CPU_FREQ_CHANNEL_1_MASK + JOIN_CANNELS_1_2_MASK ; tón odvozený od frekvence CPU
sta AUDCTL ; zápis do řídicího registru čipu POKEY
lda #PURE_TONE_MASK+10 ; hlasitost (0-15)
sta AUDC2 ; zápis do registru řízení hlasitosti pro zvukový kanál číslo 2 čipu POKEY
; nastavit vektor pro odloženou VBI
lda #>horizontal_movement
sta VVBLKD+1
lda #<horizontal_movement
sta VVBLKD
loop:
jmp loop ; program vlastně nice nedělá - jen cyklí!
.endproc
; ---------------------------------------------------------------------
; subrutina pro obsluhu VBI
; ---------------------------------------------------------------------
.proc horizontal_and_vertical_movement
ldx divider1
ldy divider2
lda STICK0 ; čtení joysticku
cmp #11 ; je nakloněn doleva?
bne not_left
dex ; změna děliče
not_left:
cmp #7 ; je nakloněn doprava?
bne not_right
inx ; změna děliče
not_right:
cmp #14 ; je nakloněn nahoru?
bne not_up
dey ; změna Y-ové souřadnice hráče
not_up:
cmp #13 ; je nakloněn dolů?
bne not_down
iny ; změna Y-ové souřadnice hráče
not_down:
stx AUDF1 ; zápis do registru děliče frekvence pro zvukový kanál číslo 1 čipu POKEY
sty AUDF2 ; zápis do registru děliče frekvence pro zvukový kanál číslo 2 čipu POKEY
stx divider1
sty divider2
jmp XITVBV ; zpracovat zbytek odloženého VBLANKu
rts
.endproc
; děliče frekvence
divider1: .byte 10
divider2: .byte 10
end:
.segment "EXEHDR"
.word $ffff ; uvodni sekvence bajtu v souboru XEX
.word main ; zacatek kodoveho segmentu
.word end - 1 ; konec kodoveho segmentu
.segment "AUTOSTRT" ; segment s pocatecni adresou
.word RUNAD ; naplni se pouze adresy RUNAD a RUNAD+1
.word RUNAD+1
.word main ; adresa vstupniho bodu do programu
; finito
Sampling
V případě potřeby (a také dostatečného výkonu mikroprocesoru) je možné generování obdélníkového signálu zcela vypnout a řídit pouze amplitudu na výstupu každého zvukového kanálu – tímto způsobem lze přehrávat i nasamplované zvuky, ovšem kvůli výše uvedeným vlastnostem čipu POKEY je možné použít pouze šestnáct úrovní hlasitosti, tj. jedná se o čtyřbitový sampling s dynamickým rozsahem pouze 24 dB (naproti tomu CD-Audio využívá šestnáctibitový sampling s dynamickým rozsahem 96 dB a zvuková karta Sound Blaster 1 používá osmibitový sampling s dynamickým rozsahem cca 48 dB, což zhruba odpovídá magnetofonovému záznamu). V případě potřeby je však možné digitalizovaný zvuk přehrávat současně na všech čtyřech zvukových kanálech, čímž se počet úrovní – a tím i dynamický rozsah – nepatrně zvyšuje (součet intenzit ovšem není lineární ale logaritmický). Nasamplovaná řeč byla na osmibitových počítačích Atari použita například ve hrách Ghostbusters či Berzerk.
Ukázky, jak mohou samply s řečí znít:
- Ghostbusters (prvních pár sekund, jen jediné slovo)
https://www.youtube.com/watch?v=BGWynJYhx1k - Berzerk
https://www.youtube.com/watch?v=cwkVuth-CGs
Příloha: Makefile pro překlad všech demonstračních příkladů
Všechny minule i dnes popsané demonstrační příklady, pro jejichž překlad je zapotřebí použít assembler ca65 a linker ld65, je možné přeložit s využitím souboru Makefile, jehož obsah je vypsán pod tímto odstavcem:
execs := dummy.xex print_a.xex \
background_color_1.xex background_color_2.xex \
color_computation_1.xex color_computation_2.xex \
subroutine_1.xex subroutine_2.xex \
hex_number_1.xex hex_number_2.xex \
hex_number_3.xex hex_number_4.xex \
hex_number_5.xex hex_number_6.xex \
hex_number_7.xex hex_number_8.xex \
hex_number_9.xex \
fill_block_1.xex fill_block_2.xex \
fill_block_3.xex fill_block_4.xex \
fill_block_5.xex \
pmg_01.xex pmg_02.xex \
pmg_03.xex pmg_04.xex \
pmg_05.xex pmg_06.xex \
pmg_07.xex pmg_08.xex \
pmg_09.xex pmg_10.xex \
pmg_11.xex pmg_12.xex \
pmg_13.xex pmg_14.xex \
pmg_15.xex pong.xex \
pmg_stick_1.xex pmg_stick_2.xex \
pmg_stick_3.xex pmg_stick_4.xex \
pmg_stick_5.xex pmg_stick_6.xex \
pmg_collisions_1.xex pmg_collisions_2.xex \
pmg_collisions_3.xex pmg_collisions_4.xex \
antic_1.xex antic_2.xex \
antic_3.xex antic_4.xex \
antic_5.xex antic_6.xex \
antic_7.xex antic_8.xex \
antic_9.xex antic_A.xex \
antic_B.xex \
antic_bitmap_1.xex antic_bitmap_2.xex \
antic_bitmap_3.xex antic_bitmap_4.xex \
antic_bitmap_5.xex antic_bitmap_6.xex \
antic_bitmap_7.xex antic_bitmap_8.xex \
antic_dli_1.asm antic_dli_2.asm \
antic_dli_3.asm antic_dli_4.asm \
antic_dli_5.asm antic_dli_pmg.asm \
sound_1.xex sound_2.xex \
sound_3.xex sound_4.xex \
sound_5.xex sound_6.xex \
sound_7.xex sound_8.xex \
sound_9.xex
all: $(execs)
clean:
rm -f *.o
rm -f *.xex
.PHONY: all clean
%.o: %.asm
ca65 $< -t atari -o $@ -l $(basename $<)_list.asm --list-bytes 100
%.xex: %.o
ld65 -C linker.cfg $< -o $@ -m $(basename $<).map
antic_bitmap_6.xex: antic_bitmap_6.o
ld65 -C linker_image.cfg $< -o $@ -m $(basename $<).map
antic_bitmap_7.xex: antic_bitmap_7.o
ld65 -C linker_image_2.cfg $< -o $@ -m $(basename $<).map
antic: \
antic_1.xex \
antic_2.xex \
antic_3.xex \
antic_4.xex \
antic_5.xex \
antic_6.xex \
antic_7.xex \
antic_8.xex \
antic_9.xex \
antic_A.xex \
antic_B.xex
antic_bitmap: \
antic_bitmap_1.xex \
antic_bitmap_2.xex \
antic_bitmap_3.xex \
antic_bitmap_4.xex \
antic_bitmap_5.xex \
antic_bitmap_6.xex \
antic_bitmap_7.xex \
antic_bitmap_8.xex
antic_dli: \
antic_dli_1.xex \
antic_dli_2.xex \
antic_dli_3.xex \
antic_dli_4.xex \
antic_dli_5.xex \
antic_dli_pmg.xex
antic_vbi: \
antic_vbi_1.xex \
antic_vbi_2.xex \
antic_vbi_3.xex \
antic_vbi_dli_1.xex \
antic_vbi_dli_2.xex \
antic_vbi_dli_3.xex \
antic_vbi_dli_4.xex
antic_scrolling: \
antic_scrolling_1.xex \
antic_scrolling_2.xex \
antic_scrolling_3.xex \
antic_scrolling_4.xex
gtia: \
gtia_mode_1.xex \
gtia_mode_2.xex \
gtia_mode_3.xex \
gtia_mode_4.xex \
gtia_mode_5.xex \
gtia_mode_6.xex \
gtia_mode_7.xex \
gtia_mode_8.xex \
gtia_mode_9.xex \
gtia_mode_A.xex \
gtia_mode_B.xex
sound: \
sound_1.xex \
sound_2.xex \
sound_3.xex \
sound_4.xex \
sound_5.xex \
sound_6.xex \
sound_7.xex \
sound_8.xex \
sound_9.xex
Výsledkem překladu jsou soubory s koncovkou .xex, které je možné přímo spustit v emulátoru osmibitových počítačů Atari.
Repositář s demonstračními příklady
Všechny demonstrační příklady, s nimiž jsme se v předchozích článcích i v článku dnešním seznámili a které jsou určeny pro překlad s využitím assembleru ca65, jsou dostupné, jak je zvykem, na GitHubu. V tabulce níže jsou uvedeny odkazy na jednotlivé zdrojové kódy příkladů psané v assembleru i „listingy“ vygenerované samotným assemblerem, ze kterých je patrné, jakým způsobem se jednotlivé příklady přeložily do výsledného XEX souboru:
Odkazy na Internetu
- MOS 6502 instruction set
http://www.6502.org/users/obelisk/6502/instructions.html - EXE File Format Description
https://gury.atari8.info/refs/file_formats_exe.php - XEX Filter – A toolkit to analyze and manipulate Atari binary files
https://www.vitoco.cl/atari/xex-filter/index.html - chkxex.py
https://raw.githubusercontent.com/seban-slt/tcx_tools/refs/heads/master/chkxex.py - ca65 Users Guide
https://cc65.github.io/doc/ca65.html - cc65 Users Guide
https://cc65.github.io/doc/cc65.html - ld65 Users Guide
https://cc65.github.io/doc/ld65.html - da65 Users Guide
https://cc65.github.io/doc/da65.html - Překladače jazyka C pro historické osmibitové mikroprocesory
https://www.root.cz/clanky/prekladace-jazyka-c-pro-historicke-osmibitove-mikroprocesory/ - Překladače programovacího jazyka C pro historické osmibitové mikroprocesory (2)
https://www.root.cz/clanky/prekladace-programovaciho-jazyka-c-pro-historicke-osmibitove-mikroprocesory-2/ - Getting Started Programming in C: Coding a Retro Game with C Part 2
https://retrogamecoders.com/getting-started-with-c-cc65/ - NES game development in 6502 assembly – Part 1
https://kibrit.tech/en/blog/nes-game-development-part-1 - NES 6502 Programming Tutorial – Part 1: Getting Started
https://dev.xenforo.relay.cool/index.php?threads/nes-6502-programming-tutorial-part-1-getting-started.858389/ - Minimal NES example using ca65
https://github.com/bbbradsmith/NES-ca65-example - List of 6502-based Computers and Consoles
https://www.retrocompute.co.uk/list-of-6502-based-computers-and-consoles/ - 6502 – the first RISC µP
http://ericclever.com/6500/ - 3 Generations of Game Machine Architecture
http://www.atariarchives.org/dev/CGEXPO99.html - “Hello, world” from scratch on a 6502 — Part 1
https://www.youtube.com/watch?v=LnzuMJLZRdU - A Tour of 6502 Cross-Assemblers
https://bumbershootsoft.wordpress.com/2016/01/31/a-tour-of-6502-cross-assemblers/ - Adventures with ca65
https://atariage.com/forums/topic/312451-adventures-with-ca65/ - example ca65 startup code
https://atariage.com/forums/topic/209776-example-ca65-startup-code/ - 6502 PRIMER: Building your own 6502 computer
http://wilsonminesco.com/6502primer/ - 6502 Instruction Set
https://www.masswerk.at/6502/6502_instruction_set.html - Chip Hall of Fame: MOS Technology 6502 Microprocessor
https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor - Single-board computer
https://en.wikipedia.org/wiki/Single-board_computer - www.6502.org
http://www.6502.org/ - 6502 PRIMER: Building your own 6502 computer – clock generator
http://wilsonminesco.com/6502primer/ClkGen.html - Great Microprocessors of the Past and Present (V 13.4.0)
http://www.cpushack.com/CPU/cpu.html - Jak se zrodil procesor?
https://www.root.cz/clanky/jak-se-zrodil-procesor/ - Osmibitové mikroprocesory a mikrořadiče firmy Motorola (1)
https://www.root.cz/clanky/osmibitove-mikroprocesory-a-mikroradice-firmy-motorola-1/ - Mikrořadiče a jejich použití v jednoduchých mikropočítačích
https://www.root.cz/clanky/mikroradice-a-jejich-pouziti-v-jednoduchych-mikropocitacich/ - Mikrořadiče a jejich aplikace v jednoduchých mikropočítačích (2)
https://www.root.cz/clanky/mikroradice-a-jejich-aplikace-v-jednoduchych-mikropocitacich-2/ - 25 Microchips That Shook the World
https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world - Comparison of instruction set architectures
https://en.wikipedia.org/wiki/Comparison_of_instruction_set_architectures - How To Start Learning Atari 8 Bit Assembly For Free
https://forums.atariage.com/topic/300732-how-to-start-learning-atari-8-bit-assembly-for-free/ - WUDSN (Demo Group)
https://www.wudsn.com/ - Machine Language For Beginners
https://www.atariarchives.org/mlb/ - Assembly language: all about I/O
https://www.atarimagazines.com/v3n8/AllAbout_IO.html - Sedmdesátiny assemblerů: lidsky čitelný strojový kód
https://www.root.cz/clanky/sedmdesatiny-assembleru-lidsky-citelny-strojovy-kod/ - Color names
https://atariwiki.org/wiki/Wiki.jsp?page=Color%20names - ATASCII
https://en.wikipedia.org/wiki/ATASCII - Put characters in display ram isn't ATASCII?
https://forums.atariage.com/topic/359973-put-characters-in-display-ram-isnt-atascii/ - ATASCII And Internal Character Code Values
https://www.atariarchives.org/mapping/appendix10.php - Reading ATASCII from the keyboard in assembly
https://forums.atariage.com/topic/361733-reading-atascii-from-the-keyboard-in-assembly/ - Why does the 6502 JSR instruction only increment the return address by 2 bytes?
https://retrocomputing.stackexchange.com/questions/19543/why-does-the-6502-jsr-instruction-only-increment-the-return-address-by-2-bytes - Pushing return address to stack off by 1 byte
https://forums.atariage.com/topic/378206-pushing-return-address-to-stack-off-by-1-byte/ - Intel x86 documentation has more pages than the 6502 has transistors
https://www.righto.com/2013/09/intel-x86-documentation-has-more-pages.html - Clearing a Section of Memory
http://www.6502.org/source/general/clearmem.htm - Practical Memory Move Routines by Bruce Clark
http://www.6502.org/source/general/memory_move.html - 6502 Assembly Programming Guide
https://neumont-gamedev.github.io/posts/retrogamedev-6502-guide/ - Off-by-one error
https://en.wikipedia.org/wiki/Off-by-one_error - 6502 cycle times
https://www.nesdev.org/wiki/6502_cycle_times - Atari TIA
http://www.atarihq.com/danb/tia.shtml - TIA Playfield
http://www.atarihq.com/danb/TIA/Playfield.shtml - Atari Inc.:
ANTIC C012296 (NTSC) Revision D
Atari Incorporated, Sunnyvale CA, 1982 - Atari Inc.:
GTIA C014805 (NTSC) Revision A
Atari Incorporated, Sunnyvale CA, 1982 - Atari 5200
http://www.atariage.com/software_search.html?SystemID=5200 - Atari 5200 Hardware and Accessories
http://www.atariage.com/5200/archives/hardware.html - Atari 5200 Screenshots
http://www.atariage.com/system_items.html?SystemID=5200&ItemTypeID=SCREENSHOT - History of video game consoles (second generation): Wikipedia
http://en.wikipedia.org/wiki/History_of_video_game_consoles_(second_generation) - Atari 5200: Wikipedia
http://en.wikipedia.org/wiki/Atari_5200 - Player-Missile Graphics
https://www.atariarchives.org/agagd/chapter5.php - Sprite (computer graphics)
https://en.wikipedia.org/wiki/Sprite_(computer_graphics) - Atari Graphics Demonstrations by Underground Software, 1985 | Atari 8 bit Demo
https://www.youtube.com/watch?v=h7N9EYSyCkw - Atari 8-bit Display List Interrupts: A Complete(ish) Tutorial
https://playermissile.com/dli_tutorial/ - Atari Assembler Editor manual
https://atariwiki.org/wiki/attach/Atari%20Assembler%20Editor/ATARI%20Assembler%20Editor%20User-s%20Manual-OCR.pdf - Atari 8-bit Fine Scrolling: A Complete(ish) Tutorial
https://playermissile.com/scrolling_tutorial/index.html - Atari Fine Scrolling
https://www.atarimagazines.com/compute/issue67/338_1_Atari_Fine_Scrolling.php - CTIA / GTIA Pinout Diagram
https://user.xmission.com/~trevin/atari/gtia_pinout.html - GTIA Modes
https://page6.org/archive/issue02/page10.htm - 56 graphic modes
https://www.atari800×l.eu/docs/kb/kb-hardware-0005-atari-8bit-56-graphic-modes.html - UNLOCKING THE 56 GRAPHIC MODES
https://www.atarimagazines.com/v3n5/allmodes.html - GTIA Modes 9, 10 & 11 + ANTIC data and color clocks
https://forums.atariage.com/topic/366256-gtia-modes-9–10–11-antic-data-and-color-clocks/ - ANTIC, GTIA and timing info
https://atarimax.com/jindroush.atari.org/atanttim.html - 6.10 Cycle timing
https://github.com/AnimaInCorpore/A8E/blob/main/AHRM/6.%20CTIA-GTIA/10.%20Cycle%20timing.md - Doug Neubauer
https://en.wikipedia.org/wiki/Doug_Neubauer - POKEY
https://en.wikipedia.org/wiki/POKEY - Inside the Atari 800XL
https://www.goto10retro.com/p/800×l-inside - Grayscale Project – „Jam Session“ – Chiptune Visualization / Atari SAP
https://www.youtube.com/watch?v=Qx-AHgvwrHo&list=PL92E73FD91764173B - POKEY MUSIC ( ATARI XL / XE ) =+ MUSIC FROM POLAND += DEMO
https://www.youtube.com/watch?v=5PswfMjMop4&list=RD5PswfMjMop4&start_radio=1 - Atari.org
http://www.atari.org/ - Atari POKEY
http://www.absoluteastronomy.com/topics/Atari_POKEY - Chiptune
http://www.absoluteastronomy.com/topics/Chiptune - ASAP – Another Slight Atari Player
http://asap.sourceforge.net/ - Atari SAP music archive
http://asma.atari.org/ - RASTER Music Tracker (RMT)
http://raster.infos.cz/atari/rmt/rmt.htm - POKEY explorer
https://github.com/ivop/pokey-explorer - POKEY C012294 Documentation
https://7800.8bitdev.org/index.php/POKEY_C012294_Documentation - Pokey Registers
https://user.xmission.com/~trevin/atari/pokey_regs.html - POKEY CO12294
http://krap.pl/mirrorz/atari/homepage.ntlworld.com/kryten_droid/Atari/800XL/atari_hw/pokey.htm
