Obsah
1. Tvorba her pro Atari 2600 v Batari BASICu: úkol pro hardcode programátory (3)
4. Opakování vykreslení stejného hráče na jednom obrazovém řádku
5. Větší množství objektů ve vykreslované scéně
7. Trik použitý při vykreslení skóre
9. Úplný zdrojový kód demonstračního příkladu
10. Generování zvuků čipem TIA
11. Zvukový subsystém čipu TIA
12. Řídicí registry určené pro nastavení zvuků
14. Jednoduchý zvuk vytvořený v Batari BASICu
15. Změna frekvence tónu modifikací dělitele
16. Zvuky získané zpětnovazebními posuvnými registry
17. Repositář s demonstračními příklady
1. Tvorba her pro Atari 2600 v Batari BASICu: úkol pro hardcode programátory (3)
Připomeňme si nejdříve základní informace z předchozího článku. Čip TIA v herní konzoli Atari 2600 dokáže zobrazit pouhých šest grafických objektů, k nimž se přidává pozadí realizované konstantní barvou:
# | Typ objektu | Orig.název | Objem paměti | Šířka reprezentovaná jedním bitem |
---|---|---|---|---|
1 | Pozadí | Background | 0 bitů | × |
2 | Hrací plocha | Playground | 20 bitů | 4× základní šířka pixelu |
3 | Míč | Ball | 1 bit | 1×, 2×, 4×, 8× šířka pixelu |
4 | Hráč 0 | Player 0 | 8 bitů | 1×, 2×, 4× šířka pixelu |
5 | Střela 0 | Missile 0 | 1 bit | 1×, 2×, 4×, 8× šířka pixelu |
6 | Hráč 1 | Player 1 | 8 bitů | 1×, 2×, 4× šířka pixelu |
7 | Střela 1 | Missile 1 | 1 bit | 1×, 2×, 4×, 8× šířka pixelu |
Obrázek 1: Screenshot ze hry Spy Hunter.
Ve druhé tabulce jsou vypsány vlastnosti jednotlivých grafických objektů, které bylo možné měnit zápisem vhodných hodnot do řídicích registrů čipu TIA:
# | Typ objektu | Orig.název | Volba barvy | Další možnosti |
---|---|---|---|---|
1 | Pozadí | Background | ano | × |
2 | Hrací plocha | Playground | pro jedničkové bity | opakování či zrcadlení v pravé polovině obrazu |
3 | Míč | Ball | = barva hrací plochy | posun, relativní posun, vertikální posun o jeden řádek, změna velikosti |
4 | Hráč 0 | Player 0 | pro jedničkové bity | posun, změna šířky, opakování 2×, 3× |
5 | Střela 0 | Missile 0 | = barva hráče 0 | -//- |
6 | Hráč 1 | Player 1 | pro jedničkové bity | posun, změna šířky, opakování 2×, 3× |
7 | Střela 1 | Missile 1 | = barva hráče 1 | -//- |
Obrázek 2: Úvodní obrazovka hry Jungle Hunt s prvním herním prostředím.
2. Hráči (players)
Posledním typem grafických objektů poskytovaných resp. přesněji řečeno vykreslovaných čipem TIA jsou hráči neboli players. Podporováno je vykreslení dvou hráčů označovaných jako player0 a player1. Hráči mohou být, jak již ostatně víme z předchozího článku, širocí osm pixelů a jsou definováni bitovým vzorkem (neboli jedním bajtem). V případě, že se bajt s definicí hráče nebude při vykreslování snímku měnit, bude hráč vykreslen formou vertikálního vzorku, který bude na všech obrazových řádcích totožný. Výška hráče může být v Batari BASICu libovolná a definice jeho bitmapy je realizována ve struktuře nazvané taktéž player0 nebo player1. Nastavovat lze pozici hráčů na obrazovce, barva hráčů je určena registry COLUP0 a COLUP1. A – jak jsme si řekli minule – je možné změnit prioritu zobrazení hráčů (i střel) vůči hernímu poli (playfield).
Podívejme se však nejdříve na velmi jednoduchý demonstrační příklad, v němž je použit hráč, který pochází z jedné varianty slavné hry Space Invaders. Bitmapa definující tvar hráče je zapsána bitovým vzorkem (výška, tedy počet řádků, je proměnný):
player0: %01000010 %10000001 %01011010 %11111111 %11011011 %01111110 %00111100 %00011000 end
Nastavení pozice hráče na obrazovce se provádí s využitím pseudoproměnných:
player0x = 80 player0y = 48
Barva hráče i pozadí se potom nastavuje přímo ve vykreslovací smyčce:
mainloop COLUPF = 14 COLUP0 = $1E drawscreen goto mainloop
Obrázek 3: Scéna vykreslená dnešním prvním demonstračním příkladem.
Podívejme se nyní na úplný zdrojový kód tohoto demonstračního příkladu, který obsahuje i definici herního pole (playfield):
playfield: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX end player0: %01000010 %10000001 %01011010 %11111111 %11011011 %01111110 %00111100 %00011000 end player0x = 80 player0y = 48 mainloop COLUPF = 14 COLUP0 = $1E drawscreen goto mainloop
Pohyb hráče po herním světě se realizuje snadno změnou obsahu pseudoproměnných player0× a player0y, které lze modifikovat například pohybem joysticku:
if joy0up then player0y = player0y - 1 if joy0down then player0y = player0y + 1 if joy0left then player0x = player0x - 1 if joy0right then player0x = player0x + 1
Tento způsob ovládání hráče je ukázán v dnešním druhém demonstračním příkladu:
playfield: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX end player0: %01000010 %10000001 %01011010 %11111111 %11011011 %01111110 %00111100 %00011000 end player0x = 80 player0y = 48 mainloop COLUPF = 14 COLUP0 = $1E if joy0up then player0y = player0y - 1 if joy0down then player0y = player0y + 1 if joy0left then player0x = player0x - 1 if joy0right then player0x = player0x + 1 drawscreen goto mainloop
3. Šířka vykreslování hráčů
Minule jsme si řekli, že šířka střel se nastavuje v horních bitech řídicích registrů nazvaných NUSIZ0 (pro první střelu) a NUSIZ1 (pro střelu druhou). Existují čtyři podporované šířky střel, jak je to ostatně patrné z následující tabulky:
NUSIZ0/NUSIZ1 | Stručný popis |
---|---|
$0× | střela s šířkou jednoho pixelu |
$1× | střela s šířkou dvou pixelů |
$2× | střela s šířkou čtyř pixelů |
$3× | střela s šířkou osmi pixelů |
Spodní čtyři bity slouží k nastavení opakování zobrazení hráčů/střel a taktéž ke zvětšení hráčů ve směru osy x (což je vlastně stejné, jako zvětšení střel). Ovšem hráče je možné zvětšit pouze 2× a 4×, nikoli již 8×:
NUSIZ0/NUSIZ1 | Stručný popis |
---|---|
$x0 | hráč v základním měřítku |
$x5 | hráč zvětšený dvojnásobně ve směru osy x |
$x7 | hráč zvětšený čtyřnásobně ve směru osy x |
V tomto demonstračním příkladu je hráč zvětšený 2×:
playfield: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX end player0: %01000010 %10000001 %01011010 %11111111 %11011011 %01111110 %00111100 %00011000 end player0x = 80 player0y = 48 mainloop NUSIZ0 = $05 COLUPF = 14 COLUP0 = $1E if joy0up then player0y = player0y - 1 if joy0down then player0y = player0y + 1 if joy0left then player0x = player0x - 1 if joy0right then player0x = player0x + 1 drawscreen goto mainloop
Obrázek 4: Hráč zvětšený 2×.
A v tomto příkladu je zvětšen 4× ve směru osy x a 2× ve směru osy y (opakováním vzorku):
playfield: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX end player0: %01000010 %01000010 %10000001 %10000001 %01011010 %01011010 %11111111 %11111111 %11011011 %11011011 %01111110 %01111110 %00111100 %00111100 %00011000 %00011000 end player0x = 80 player0y = 48 mainloop NUSIZ0 = $07 COLUPF = 14 COLUP0 = $1E if joy0up then player0y = player0y - 1 if joy0down then player0y = player0y + 1 if joy0left then player0x = player0x - 1 if joy0right then player0x = player0x + 1 drawscreen goto mainloop
Obrázek 5: Hráč zvětšený 4×.
4. Opakování vykreslení stejného hráče na jednom obrazovém řádku
Popišme si ještě další možné kombinace hodnot uložených v řídicích registrech NUSIZ0 a NUSIZ1. Kromě zvětšení střel a/nebo hráčů lze docílit toho, že se tyto grafické objekty budou na jednom řádku opakovat, a to buď dvakrát či třikrát. Měnit lze i relativní vzdálenost těchto kopií od sebe:
NUSIZ0/NUSIZ1 | Stručný popis |
---|---|
$x0 | hráč v základním měřítku |
$x1 | dvojnásobná kopie hráče i střely |
$x2 | dvojnásobná kopie hráče i střely ve větší vzdálenosti od sebe |
$x3 | trojnásobná kopie hráče i střely |
$x4 | dvojnásobná kopie hráče i střely s dvojnásobným zvětšením |
$x6 | trojnásobná kopie hráče i střely ve větší vzdálenosti od sebe |
Změnu způsobu zobrazení hráčů a střel lze realizovat například tlačítkem joysticku, kterým budeme přepínat mezi výše uvedenými stavy, konkrétně mezi hodnotami $00, $01, $02, $03, $04 a $06 (vynecháme tedy stav $05). Proměnná a slouží pro „zpomalení“ změny zobrazení grafických objektů:
if joy0fire then a = a + 1 rem Zpomaleni zmeny if a > 8 then b = b + 1: a = 0 if b = 5 then b = 6 if b = 7 then b = 0 NUSIZ0 = b
Některé možnosti zobrazení toho samého hráče ve scéně:
Obrázek 6: Zopakování hráče na stejném obrazovém řádku.
Obrázek 7: Zopakování hráče na stejném obrazovém řádku.
Obrázek 8: Zopakování hráče na stejném obrazovém řádku.
Úplný zdrojový kód takto upraveného demonstračního příkladu vypadá následovně:
playfield: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX end player0: %01000010 %01000010 %10000001 %10000001 %01011010 %01011010 %11111111 %11111111 %11011011 %11011011 %01111110 %01111110 %00111100 %00111100 %00011000 %00011000 end player0x = 80 player0y = 48 mainloop COLUPF = 14 COLUP0 = $1E if joy0up then player0y = player0y - 1 if joy0down then player0y = player0y + 1 if joy0left then player0x = player0x - 1 if joy0right then player0x = player0x + 1 if joy0fire then a = a + 1 rem Zpomaleni zmeny if a > 8 then b = b + 1: a = 0 if b = 5 then b = 6 if b = 7 then b = 0 NUSIZ0 = b drawscreen goto mainloop
5. Větší množství objektů ve vykreslované scéně
Standardní kernel implementovaný v jazyce Batari BASIC dokáže současně zobrazit všech šest grafických objektů současně, tedy:
- Herní pole (playfield)
- Míč (ball) ve stejné barvě jako herní pole
- Dvojici hráčů (players)
- Dvojici střel (missiles) ve stejné barvě, jako příslušní hráči
Všechny zmíněné objekty s výjimkou herního pole jsou plně polohovatelné, což je ukázáno i v dalším demonstračním příkladu, kde:
- Hráči se pohybují v horizontálním směru a odráží se od okrajů obrazovky
- Střely se pohybují pod úhlem 45° a taktéž se odráží od okrajů obrazovky
- Míč se ovládá prvním joystickem ve všech osmi možných směrech
Herní scéna vypadá následovně:
Obrázek 9: Větší množství objektů ve vykreslované scéně.
Zdrojový kód tohoto demonstračního příkladu naleznete na adrese https://github.com/tisnik/8bit-fame/blob/master/batari-Basic/players5.bas i pod tímto odstavcem:
playfield: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX end player0: %01000010 %10000001 %01011010 %11111111 %11011011 %01111110 %00111100 %00011000 end player1: %10100101 %01011010 %00100100 %11111111 %11011011 %01111110 %00111100 %00011000 end player0x = 20 player0y = 20 player1x = 140 player1y = 30 missile0x = 80 missile0y = 40 missile0height = 4 missile1x = 80 missile1y = 60 missile1height = 4 ballx = 80 bally = 50 ballheight = 8 rem Posun hrace #1 v horizontalnim smeru let a = 1 rem Posun hrace #2 v horizontalnim smeru let b = 1 rem Posun strely 0 v horizontalnim i vertikalnim smeru let c = 1 let d = 1 rem Posun strely 1 v horizontalnim i vertikalnim smeru let e = 1 let f = -1 mainloop rem Sirka strel NUSIZ0 = $20 NUSIZ1 = $20 rem Sirka mice CTRLPF = $31 rem Barvy pozadi i hracu COLUPF = 14 COLUP0 = $1E COLUP1 = $4E player0x = player0x + a if player0x >= 152 then player0x = 152: a =- 1 if player0x <= 0 then player0x = 0: a = 1 player1x = player1x + b if player1x >= 152 then player1x = 152: b =- 1 if player1x <= 1 then player1x = 1: b = 1 missile0x = missile0x + c if missile0x >= 152 then missile0x = 152: c =- 1 if missile0x <= 1 then missile0x = 1: c = 1 missile0y = missile0y + d if missile0y >= 80 then missile0y = 80: d =- 1 if missile0y <= 10 then missile0y = 10: d = 1 missile1x = missile1x + e if missile1x >= 152 then missile1x = 152: e =- 1 if missile1x <= 1 then missile1x = 1: e = 1 missile1y = missile1y + f if missile1y >= 80 then missile1y = 80: f =- 1 if missile1y <= 10 then missile1y = 10: f = 1 if joy0up then bally = bally - 1 if joy0down then bally = bally + 1 if joy0left then ballx = ballx - 1 if joy0right then ballx = ballx + 1 drawscreen goto mainloop
6. Zobrazení skóre
Standardní kernel Batari BASICu podporuje zobrazení skóre v dolní části herní scény. Skóre je celočíselná hodnota s maximálním rozsahem šesti číslic, které lze využít k více účelům. Ve finální hře se pochopitelně může jednat o skutečné skóre, ovšem při vývoji zde je možné zobrazit různé ladicí informace (protože jiný výstup textu není praktický). Podívejme se nyní na způsob zobrazení skóre v dolní části herní scény. Barva skóre je odvozena od obsahu pseudoproměnné scorecolor (barvy PAL nebo NTSC), vlastní obsah je pak uložen v další pseudoproměnné nazvané score:
scorecolor = $98 rem Pocatecni hodnota score score = 0 mainloop if joy0fire then score = score + 1 drawscreen goto mainloop
Obrázek 10: Postupně se měnící skóre zobrazené v dolní části herní scény.
Obrázek 11: Postupně se měnící skóre zobrazené v dolní části herní scény.
7. Trik použitý při vykreslení skóre
Zajímavé bude zjistit, jakým trikem je vlastně skóre vykresleno. Jedná se totiž o relativně složitý grafický vzor (složitý v kontextu čipu TIA), takže bude vhodné se přiučit, jak kernel dokáže takový vzor vlastně zobrazit:
Obrázek 12: Trik použitý při vykreslení skóre.
8. Detekce kolizí podruhé
Nyní, když víme, jakým způsobem můžeme zobrazit skóre, se můžeme vrátit k příkladu se všemi grafickými objekty, z nichž hráči i střely jsou zobrazeny 3× (pouze u míče nelze tohoto triku docílit):
Obrázek 13: Snímek získaný z dema.
Všechny objekty se ve scéně pohybují a srážejí (dochází tedy ke kolizím). V takovém případě je změněna hodnota skóre, které se zde používá pro ladicí účely:
if collision(missile0, player0) then COLUPF = $38:score = score + 1 if collision(missile0, player1) then COLUPF = $48:score = score + 10 if collision(missile1, player0) then COLUPF = $58:score = score + 100 if collision(missile1, player1) then COLUPF = $68:score = score + 1000 if collision(missile0, missile1) then COLUPF = $78:score = score + 10000 if collision(ball, player0) then COLUP0 = $48:score = score + 100000 if collision(ball, player1) then COLUP1 = $48:score = score + 100000 if collision(ball, missile0) then COLUP0 = $48:score = score + 100000 if collision(ball, missile1) then COLUP1 = $48
I přes relativně velké množství pohybujících se objektů je kód dema po překladu menší než 2kB:
16:43 $ 2600basic.sh collisions_2.bas Found dasm version: DASM 2.20.14.1 Starting build of collisions_2.bas batari Basic v1.6-SNAPSHOT (c)2021 2600 Basic compilation complete. 2368 bytes of ROM space left Complete. (0) Build complete.
Obrázek 14: Další snímek získaný z dema.
9. Úplný zdrojový kód demonstračního příkladu
Úplný zdrojový kód demonstračního příkladu popsaného v předchozí kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/batari-Basic/score.bas:
playfield: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX end player0: %01000010 %10000001 %01011010 %11111111 %11011011 %01111110 %00111100 %00011000 end player1: %10100101 %01011010 %00100100 %11111111 %11011011 %01111110 %00111100 %00011000 end player0x = 20 player0y = 20 player1x = 140 player1y = 30 missile0x = 80 missile0y = 40 missile0height = 4 missile1x = 80 missile1y = 60 missile1height = 4 ballx = 80 bally = 50 ballheight = 8 rem Posun hrace #1 v horizontalnim smeru let a = 1 rem Posun hrace #2 v horizontalnim smeru let b = 1 rem Posun strely 0 v horizontalnim i vertikalnim smeru let c = 1 let d = 1 rem Posun strely 1 v horizontalnim i vertikalnim smeru let e = 1 let f = -1 mainloop rem Sirka strel NUSIZ0 = $20 NUSIZ1 = $20 rem Sirka mice CTRLPF = $31 rem Barvy pozadi i hracu COLUPF = 14 COLUP0 = $1E COLUP1 = $4E player0x = player0x + a if player0x >= 152 then player0x = 152: a =- 1 if player0x <= 0 then player0x = 0: a = 1 player1x = player1x + b if player1x >= 152 then player1x = 152: b =- 1 if player1x <= 1 then player1x = 1: b = 1 missile0x = missile0x + c if missile0x >= 152 then missile0x = 152: c =- 1 if missile0x <= 1 then missile0x = 1: c = 1 missile0y = missile0y + d if missile0y >= 80 then missile0y = 80: d =- 1 if missile0y <= 10 then missile0y = 10: d = 1 missile1x = missile1x + e if missile1x >= 152 then missile1x = 152: e =- 1 if missile1x <= 1 then missile1x = 1: e = 1 missile1y = missile1y + f if missile1y >= 80 then missile1y = 80: f =- 1 if missile1y <= 10 then missile1y = 10: f = 1 if joy0up then bally = bally - 1 if joy0down then bally = bally + 1 if joy0left then ballx = ballx - 1 if joy0right then ballx = ballx + 1 if collision(missile0, player0) then COLUPF = $38:score = score + 1 if collision(missile0, player1) then COLUPF = $48:score = score + 10 if collision(missile1, player0) then COLUPF = $58:score = score + 100 if collision(missile1, player1) then COLUPF = $68:score = score + 1000 if collision(missile0, missile1) then COLUPF = $78:score = score + 10000 if collision(ball, player0) then COLUP0 = $48:score = score + 100000 if collision(ball, player1) then COLUP1 = $48:score = score + 100000 if collision(ball, missile0) then COLUP0 = $48:score = score + 100000 if collision(ball, missile1) then COLUP1 = $48 drawscreen goto mainloop
10. Generování zvuků čipem TIA
Ve druhé části dnešního článku si (i když s několika zjednodušeními) popíšeme zvukové možnosti čipu TIA neboli plným jménem Television Interface Adaptor. Připomeňme si (již naposledy), že se jedná o jeden z pouhých tří integrovaných obvodů tvořících ústřední prvky v minulosti velmi populární osmibitové herní konzole Atari 2600 neboli též Atari VCS (k přejmenování došlo společně se vznikem další herní konzole Atari 5200).
Čip TIA se kromě generování grafického obrazu, což bylo téma předchozího textu, stará i o zvukovou syntézu. Ta je z dnešního pohledu dosti zvláštní. Na jednu stranu je způsob zvukové syntézy velmi jednoduchý a v některých ohledech připomíná způsob zvukové syntézy použité o několik let později v čipu POKEY v osmibitových domácích mikropočítačích Atari (ale i v relativně velkém množství herních automatů). Jednoduchost spočívá v možnosti generování pouze obdélníkových signálů (pravidelných popř. sice nepravidelných, ale periodických), jejichž amplituda může být nastavena na hodnoty 0–15 (řídí se tedy čtyřmi bity řídicího registru resp. dvojice řídicích registrů).
Na druhou stranu však způsob interního zapojení posuvných registrů se zpětnou vazbou nabízí poměrně velké množství kombinací, které vlastně nenajdeme ani u čipu POKEY, který je jinak možné v oblasti generování zvuků a hudby považovat za nástupce čipu TIA (totéž platí o GTIA, ovšem v oblasti grafiky). TIA se tak stále používá v některých aplikacích chiptune, samozřejmě vedle dalších čipů (POKEY, pochopitelně SID, Texas Instruments SN76489, Texas Instruments SN76496, SN76477 a Ricoh 2A03/2A07 z NESu).
Vzhledem k tomu, že se čip TIA stará jak o vytváření video signálu, tak i pro syntézu zvuků a hudby, je v něm několik modulů společných pro oba jinak rozličné subsystémy. Zejména se to týká způsobu generování základního hodinového signálu (přesněji řečeno dvojice signálů), které vstupují do zvukového systému a od jejichž frekvence se odvíjí i frekvence přehrávaných tónů. Podrobnosti budou uvedeny v navazující kapitole.
11. Zvukový subsystém čipu TIA
Základem všech signálů řídicích celou herní konzoli Atari 2600 je oscilátor, který u NTSC varianty této konzole generuje hodinový signál o frekvenci 3,579545 MHz a u PAL varianty poněkud nižší frekvenci 3,546894 MHz (což mimochodem znamená, že některé hry běží poněkud odlišnou rychlostí v závislosti na použité televizní normě). Tento základní signál se nazývá pixel clock (v některých dokumentech též color clock), protože kromě dalších činností řídí i generování pixelů na obrazových řádcích (262 řádků a necelých 60 snímků za sekundu u normy NTSC, 312 řádků a necelých 50 snímků za sekundu u normy PAL). Současně se tento signál dělí třemi a výsledek o frekvencích 1,193182 MHz (NTSC) popř. 1,182298 MHz (PAL) slouží jako hlavní řídicí hodinový signál mikroprocesoru MOS 6507. Z tohoto důvodu se tento signál nazývá CPU clock a proto se dočteme, že hodinová frekvence konzole Atari 2600 je 1,19 MHz, což ovšem platí jen pro NTSC variantu.
Oba dva výše zmíněné signály, tj. jak pixel clock, tak i CPU clock, jsou dále děleny konstantou 114 použitou mj. video subsystémem pro vykreslení jednoho obrazového řádku (mimochodem: 114×2=228, což je celkový počet pixelů + overscanu). Současně však takto vydělené signály vstupují i do audio subsystému, který nás zajímá nyní. Jejich frekvence je rovna, jak lze snadno vypočítat, 31399 Hz a 10466 Hz pro systém NTSC a 31113 Hz a 10371 Hz pro systém PAL. V původních originálních materiálech je ovšem jen lakonicky napsáno „na vstupu audio systému je signál o frekvenci přibližně 30 kHz“, což je nepřesné a může se to negativně projevit při volbě konstant při přehrávání not („rozladění“). Proto mějte při čtení dalšího textu na paměti, že vstupními frekvencemi zvukového subsystému je dvojice 31399/10466 nebo 31113/10371 Hz.
Obrázek 15: Zvukový subsystém čipu TIA.
12. Řídicí registry určené pro nastavení zvuků
V případě integrovaného obvodu TIA jsou programátorům k dispozici dva na sobě nezávislé programovatelné zvukové kanály. Každý z těchto kanálů je řízen trojicí řídicích registrů, celkem tedy může programátor modifikovat (pouze) šest řídicích registrů. Tyto registry mají názvy AUDF0, AUDF1, AUDC0, AUDC1, AUDV0 a AUDV1. Vzhledem k tomu, že možnosti obou zvukových kanálů jsou totožné (všechny obvody zvukového subsystému jsou za děličkou vstupních signálů zdvojeny), budeme v dalším textu popisovat pouze kanál první, který je řízený trojicí registrů AUDF0, AUDC0 a AUDV0. Náhradou 0 za 1 pak bude totéž platit i pro druhý zvukový kanál.
První řídicí registr zvukového kanálu (AUDF0), přesněji řečeno pět bitů tohoto registru, určuje konstantu 1 až 32 použitou při dělení vstupní frekvence. Buď se použije frekvence pixel_clock/114 nebo CPU_clock/114. Výsledkem tohoto vydělení je obdélníkový signál o frekvenci přibližně 1 kHz až 30 kHz (popř. 300 Hz až 10 kHz, ovšem opět jen přibližně), který je přiváděn do dvojice konfigurovatelných posuvných registrů (LFSR) se zpětnou vazbou. První registr má délku pěti bitů, druhý délku čtyř bitů, ovšem je možné je spojit do jediného registru o délce devíti bitů (viz další text).
Tyto dva posuvné registry slouží jak pro generování čistého obdélníkového signálu, tak i pro vytváření šumu (noise generator) s různou charakteristikou. Konfigurace (způsob zapojení) obou posuvných registrů je řízena registrem AUDC0, přičemž význam mají pouze čtyři spodní bity. Jednotlivé kombinace určují jak zdroj signálu (tedy, jak již víme pixel_clock/114 či CPU_clock/114), tak i způsob zapojení zpětných vazeb v obou posuvných registrech.
Jedna z kombinací umožňuje generovat konstantní signál o hodnotě 1, což je výhodné, protože je možné zvuk jednoduše samplovat rychlou změnou obsahu registru AUDV0 (ovšem většinou je toto řešení poměrně náročné na CPU cykly). Další dvě kombinace přepínají pětibitový posuvný registr do režimu, v němž se stále opakuje sekvence 0101010…, tj. posuvný registr zde slouží jako dělička vstupní frekvence dvěma (popř. šesti, protože CPU_clock=pixel_clock/3). Právě tento režim se používal při přehrávání čistých tónů (ovšem rozladěných).
Další dostupný režim taktéž používá posuvný registr, tentokrát ovšem takovým způsobem, že se vstupní signál dělí hodnotou 31. Opakuje se v něm totiž sekvence 31 bitů, z nichž 13 sousedních bitů je nulových, zbytek jedničkových (to, že počet jedniček a nul není zcela totožný, se projeví ve výsledném zvuku jen nepatrně). Opět platí, že CPU_clock má třetinovou frekvenci, takže v oficiálních materiálech se hovoří o dělení konstantou 93; ve skutečnosti tato „magická“ konstanta odpovídá výpočtu 3×31.
Další režimy již využívají oba posuvné registry pro tvorbu šumu. Pokud je použit jen čtyřbitový registr, je perioda opakování pouze 15 bitů, což je opět možné využít pro generování tónů, ovšem zkreslených. Podobně při použití pětibitového LFSR s periodou opakování 31. Spojením obou dvou registrů za sebe se generuje šum o periodě 511 bitů (podobného efektu lze mimochodem docílit i u čipu POKEY).
Nejsložitější je režim, v němž pětibitový LFSR řídil čtyřbitový LFSR: pokud se na výstupu prvního LFSR objeví jednička, provede se posun i ve čtyřbitovém LFSR; v opačném případě se druhý LFSR ignoruje. Výsledkem je pseudonáhodný signál s periodou 15×31=465 bitů (další „magická“ hodnota, která není v původních materiálech nijak vysvětlena a musela být až mnohem později vydedukována ze schémat čipu TIA).
Příklad posuvného registru se zpětnou vazbou:
+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ +---> | 1| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| 0| -> Output | +--+--+--+--+--+--+--+--+--+--+--+--+-++--+--+-++ | ______ 0 | | | 0 / //---+ | +-------------------------------( XOR (( 0 | \_____\\------------+
Obrázek 16: Princip práce čtyřbitového poly čítače generujícího binární pseudonáhodný šum.
Výsledný signál, nezávisle na tom, zda se jedná o čistý obdélníkový průběh či o šum, je následně zesílen 1 až šestnáctkrát na základě hodnoty posledního řídicího registru (AUDV0, AUDV1). Interně se nejedná o nic složitého – čtyři bity, resp. přesněji řečeno čtyři logické úrovně, jsou přivedeny na jednoduchou odporovou síť se čtyřmi rezistory o hodnotách 3k7, 7k5, 15k a 30k (takže se vlastně nejedná o zesílení, ale naopak o konfigurovatelné zeslabení). Výsledné napětí, které na rezistorech vznikne, je vyvedeno na výstupní pin čipu TIA (ten tedy musí obsahovat dva piny s analogovým zvukovým výstupem, každý zvukový kanál má vyhrazen jeden pin).
Zvuk generovaný čipem TIA je možné poměrně jednoduše rozeznat od zvuku dalších herních konzolí nebo domácích mikropočítačů, už jen z toho důvodu, že prakticky všechny noty jsou kvůli velmi krátkému čítači, který je použitý pro dělení vstupní frekvence, rozladěny. Avšak i přesto pro TIA dodnes vznikají různá více či méně vážně míněná hudební díla, a to včetně poměrně úspěšných pokusů o čtyřbitový sampling (což ovšem vyžaduje použití „supercartridgí“ s dostatečně velkou EPROM, protože interní RAM o kapacitě celých 128 bajtů je samozřejmě pro tyto účely nepoužitelná).
13. Tabulka not
Pro přehrávání „čistých“ tónů, tedy použití obdélníkového signálu (a nikoli výstupu ze zpětnovazebních posuvných registrů), se používají režimy 4 a 5 (totožné) a 12 a 13 (taktéž totožné, ovšem s třetinovou frekvencí; důvody viz výše). Převod mezi hodnotami zapisovanými do registrů AUDF0 a AUDF1 a notami byl získán z informací dostupných na adrese https://www.biglist.com/lists/stella/archives/199704/msg00007.html. U každé noty je navíc uveden rozdíl v centech oproti čisté notě (zhruba platí, že rozdíl o více než deset centů je velmi dobře rozeznatelný; hudba v podání čipu TIA je tedy prakticky vždy rozladěna.
Tato tabulka platí pro režimy 4 a 5, tedy pro základní obdélníkový signál získaný děličkou základní frekvence hodnotami 0 až 31:
Hodnota | Nota | Rozdíl (cent) |
---|---|---|
00 | × | × |
01 | B | ? |
02 | E | ? |
03 | B | ? |
04 | G | ? |
05 | E | ? |
06 | C# | ? |
07 | B | ? |
08 | A | ? |
09 | G | ? |
10 | F | +50 |
11 | E | –20 |
12 | D | 0 |
13 | C# | +20 |
14 | C | –5 |
15 | B | –15 |
16 | A# | –20 |
17 | A | –20 |
18 | G# | –15 |
19 | G | 0 |
20 | F# | +15 |
21 | F | +40 |
22 | F | –50 |
23 | E | –20 |
24 | D# | +15 |
25 | D | +50 |
26 | D | –20 |
27 | C# | +20 |
28 | C# | –50 |
29 | C | 0 |
30 | B | +50 |
31 | B | –15 |
Následující tabulka platí pro režimy 12 a 13, které mají 3× nižší frekvenci (pro stejné hodnoty) v porovnání s režimy 4 a 5:
Hodnota | Nota | Rozdíl (cent) |
---|---|---|
00 | A | –20? |
01 | E | –40? |
02 | A | –20 |
03 | E | –10? |
04 | C | 0? |
05 | A | –40 |
06 | F# | +10 |
07 | E | –20 |
08 | D | –20 |
09 | C | 0 |
10 | A# | +30 |
11 | A | –20 |
12 | G | +50 |
13 | F# | +20 |
14 | F | 0? |
15 | E | –20 |
16 | D# | –20 |
17 | D | –20 |
18 | C# | –15 |
19 | C | 0 |
20 | B | +15 |
21 | A# | +32 |
22 | A# | –50 |
23 | A | –20 |
24 | G# | +10 |
25 | G | +50 |
26 | G | –25 |
27 | F# | +15 |
28 | F# | –50 |
29 | F | 0 |
30 | E | +50 |
31 | E | –20 |
14. Jednoduchý zvuk vytvořený v Batari BASICu
Vyzkoušejme si nyní vygenerovat si jednoduchý zvuk. K tomuto účelu postačuje nastavit trojici řídicích registrů, například pro první zvukový kanál:
rem Hlasitost AUDV0 = 15 rem Tvar zvukove vlny AUDC0 = 4 rem Frekvence: A4 AUDF0 = 11
V demonstračním příkladu bude zvuk zapínán stiskem tlačítka joysticku. Jak to ovšem provést? Po stisku tlačítka se nastaví hlasitost na 15, po puštění se sníží zpět na nulu. Navíc zapnutí zvuku způsobí změnu barvy hráče (ostatně proto je hráč vůbec zobrazen):
if joy0fire then COLUP0 = $48:AUDV0 = 15 if !joy0fire then AUDV0 = 0
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
playfield: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ ................................ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX end player0: %01000010 %10000001 %01011010 %11111111 %11011011 %01111110 %00111100 %00011000 end player0x = 20 player0y = 20 rem Hlasitost AUDV0 = 0 rem Tvar zvukove vlny AUDC0 = 4 rem Frekvence: A4 AUDF0 = 11 mainloop rem Barvy pozadi i hrace COLUPF = 14 COLUP0 = $1E if joy0fire then COLUP0 = $48:AUDV0 = 15 if !joy0fire then AUDV0 = 0 drawscreen goto mainloop
15. Změna frekvence tónu modifikací dělitele
V dalším příkladu je možné frekvenci zvuku měnit pohybem joysticku doleva a doprava. Vzhledem k tomu, že obsah řídicích registrů není možné číst, použijeme pomocnou proměnnou f pro uložení dělitele v rozsahu 0 až 31, kterou následně přiřadíme do registru AUDF0:
if joy0left && f > 0 then f = f - 1 if joy0right && f < 31 then f = f + 1 AUDF0 = f
Dělitel je zobrazen jako skóre a navíc je hráč posunut, takže jeho horizontální pozice odpovídá zvolené frekvenci generovaného tónu:
player0x = 20 + f*4 score = f + 0
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
player0: %01000010 %10000001 %01011010 %11111111 %11011011 %01111110 %00111100 %00011000 end player0x = 20 + 11 player0y = 20 scorecolor = $98 rem Pocatecni hodnota score score = 11 rem Hlasitost AUDV0 = 15 rem Tvar zvukove vlny AUDC0 = 4 rem Frekvence f = 11 AUDF0 = f mainloop rem Barvy pozadi i hrace COLUPF = 14 COLUP0 = $1E if joy0left && f > 0 then f = f - 1 if joy0right && f < 31 then f = f + 1 AUDF0 = f player0x = 20 + f*4 score = f + 0 drawscreen goto mainloop
16. Zvuky získané zpětnovazebními posuvnými registry
V dnešním posledním demonstračním příkladu je možné joystickem měnit nejenom frekvenci tónu, ale i způsob zapojení zpětnovazebních posuvných registrů (což jsou hodnoty 0 až 15 zapisované do registru AUDC0, z nichž některé mají totožný význam a další naopak vedou k tomu, že se neozve žádný tón):
if joy0left && f > 0 then f = f - 1:score = score - 1 if joy0right && f < 31 then f = f + 1:score = score + 1 if joy0up && s > 0 then s = s - 1:score = score - 1000 if joy0down && s < 15 then s = s + 1:score = score + 1000 AUDF0 = f AUDC0 = s
Vybrané hodnoty (dělitel i zapojení posuvných registrů) se zobrazí jak v ukazateli skóre, tak i horizontální pozicí obou hráčů:
player0x = 20 + f*4 player1x = 20 + s*4
Úplný zdrojový kód tohoto demonstračního příkladu vypadá následovně:
player0: %01000010 %10000001 %01011010 %11111111 %11011011 %01111110 %00111100 %00011000 end player1: %10100101 %01011010 %00100100 %11111111 %11011011 %01111110 %00111100 %00011000 end rem Hlasitost AUDV0 = 15 rem Tvar zvukove vlny s = 0 AUDC0 = s rem Frekvence f = 11 AUDF0 = f player0x = 20 + f * 4 player0y = 20 player1x = 20 + s * 4 player1y = 40 scorecolor = $98 rem Pocatecni hodnota score score = 0 + f mainloop rem Barva pozadi i hracu COLUPF = 14 COLUP1 = $4E if joy0left && f > 0 then f = f - 1:score = score - 1 if joy0right && f < 31 then f = f + 1:score = score + 1 if joy0up && s > 0 then s = s - 1:score = score - 1000 if joy0down && s < 15 then s = s + 1:score = score + 1000 AUDF0 = f AUDC0 = s player0x = 20 + f*4 player1x = 20 + s*4 drawscreen goto mainloop
17. Repositář s demonstračními příklady
Všechny předminule, minule i dnes popisované demonstrační příklady určené pro překlad Batari-BASICem byly uloženy do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:
18. Odkazy na Internetu
- STELLA PROGRAMMER'S GUIDE
https://alienbill.com/2600/101/docs/stella.html - Batari BASIC GitHub repositář
https://github.com/batari-Basic/batari-Basic - Programming Tutorial
https://atariage.com/forums/topic/111938-programming-tutorial/ - batari Basic Commands
https://www.randomterrain.com/atari-2600-memories-batari-basic-commands.html - About batari Basic
https://bataribasic.com/ - Rationale
https://bataribasic.com/rationale.html - Games That Push The Limits of the Atari 2600
https://www.youtube.com/watch?v=zM0IsWdIc_g - Vývojové nástroje používané v dobách osmibitových mikropočítačů
https://www.root.cz/clanky/vyvojove-nastroje-pouzivane-v-dobach-osmibitovych-mikropocitacu/ - Programovací jazyky používané na platformě osmibitových domácích mikropočítačů Atari
https://www.root.cz/clanky/programovaci-jazyky-pouzivane-na-platforme-osmibitovych-domacich-mikropocitacu-atari/ - Programovací jazyky používané na platformě osmibitových domácích mikropočítačů Atari (2)
https://www.root.cz/clanky/programovaci-jazyky-pouzivane-na-platforme-osmibitovych-domacich-mikropocitacu-atari-2/ - Barvové palety čipu TIA
http://www.qotile.net/minidig/docs/tia_color.html - Crazy Limit Pushing Games From the Last Years of the Atari 2600!
https://www.youtube.com/watch?v=ADy1F8v59YU - Atari 2600 VCS Top 100 Games Hits (past week)
http://www.atarimania.com/top-atari-atari-2600-vcs-_G2_7.html - Tobikomi – The Sound Of Thunder [Atari TIA Chip]
https://www.youtube.com/watch?v=j0w-IZ6nAMQ - TIA Visual Objects
https://github.com/jigo2600/jigo2600/blob/master/doc/TIA_Visual_Objects.md - TIA Sound
https://github.com/jigo2600/jigo2600/blob/master/doc/TIA_Sound.md - How To Make An Atari Game
https://www.youtube.com/watch?v=Ww3her2zk_I - Let's Make an Atari 2600 Game! – Part 1
https://www.youtube.com/watch?v=Iqo_oARxjEg - Let's Make an Atari 2600 Game! – Part 2
https://www.youtube.com/watch?v=hFFQjwFbzV8 - Let's Make an Atari 2600 Game! – Part 3
https://www.youtube.com/watch?v=lZ0AL6jCBXY - Let's Make an Atari 2600 Game! Part 4 – Title screens and challenges
https://www.youtube.com/watch?v=-G2kmsmqk-E - Let's Make an Atari 2600 Game! Part 5 – Sound
https://www.youtube.com/watch?v=9rX2eo20×q8 - Let's Make an Atari 2600 game! 6 – Realtime RPG combat
https://www.youtube.com/watch?v=alRGuQ9gjRA - Let's Make an Atari 2600 Game! Part 7 – Monsters
https://www.youtube.com/watch?v=vaAlYC_8YSA - Let's Make an Atari 2600 Game! Part 8 – 3D Engine
https://www.youtube.com/watch?v=c1dPY1ROZe4 - Let's Make an Atari 2600 Game – Part 9 – Homemade cartridge
https://www.youtube.com/watch?v=xKlMohF_9Cc - Bird Poop! – Atari 2600 Homebrew – batari Basic
https://www.youtube.com/watch?v=-m4gKis0vBg - DP Interviews: Bob Whitehead (By Scott Stilphen)
http://www.digitpress.com/library/interviews/interview_bob_whitehead.html - The dasm macro assembler
http://dasm-dillon.sourceforge.net/ - Official home of dasm, a versatile macro assembler
https://dasm-assembler.github.io/ - Dokumentace k DASMu
https://raw.githubusercontent.com/dasm-assembler/dasm/master/docs/dasm.pdf - Atari Programming Workshop Chapter links
http://atariage.com/forums/viewtopic.php?t=47479 - Various Development Kits
http://devkits.handheldmuseum.com/ - Classic Console Development
http://sebastianmihai.com/ccd/ - Atari 2600 development – Snappy (batari basic)
http://sebastianmihai.com/main.php?t=47 - Atari VCS (Atari 2600) – fotografie
http://oldcomputers.net/atari-vcs.html - History of Consoles: Atari VCS/2600 (1977)
http://gamester81.com/history-of-consoles-atari-vcs2600–1977/ - Iag Bogost: Racing the Beam
http://www.bogost.com/books/video_computer_system.shtml - Atari 2600 Programming Tutorial
http://www.randomterrain.com/atari-2600-memories-tutorial-andrew-davie-01.html - Atari 2600 Development Cartridge *Super Deluxe*~!
http://jazz-disassemblies.blogspot.cz/2013/09/atari-2600-development-cartridge-super.html - Atari „Alpine“ Devkit (pro Atari Jaguar)
http://justclaws.atari.org/devcats/hardware/ataridev.htm - 6502 compatible assembler and emulator in javascript
http://www.6502asm.com/ - Atari 2600 Programming
http://atariage.com/2600/programming/ - Retrozone – Brand new original homebrew games by current programmers
http://www.retrousb.com/index.php?cPath=30 - ATARI VCS/2600 TIA CHIPS
http://www.ataricompendium.com/faq/vcs_tia/vcs_tia.html - Elena, New programming language for the ZX Spectrum Next
https://www.vintageisthenewold.com/elena-new-programming-language-for-the-zx-spectrum-next - ZX Spectrum development with modern tools
http://www.breakintoprogram.co.uk/software_development/zx-spectrum-development-with-modern-tools - Z80 Development Toolchain
http://www.breakintoprogram.co.uk/computers/zx-spectrum/assembly-language/z80-development-toolchain - Space Invaders Sprite Sheet
https://www.deviantart.com/gooperblooper22/art/Space-Invaders-Sprite-Sheet-135338373 - [stella] PRECISE 2600 sound chart
https://www.biglist.com/lists/stella/archives/199704/msg00007.html - [stella] Frequency and Waveform Guide
https://www.biglist.com/lists/stella/archives/199706/msg00048.html