Přehrávání samplovaného zvuku na ZX Spectru

Úvod

Ani ZX Spectrum neuniklo samplovaným zvukům navzdory jeho, pro tento účel, velmi malé paměti a nízkému výkonu CPU což znemožňovalo jakoukoli rozumnější realtime dekompresi. Přesto se samplované zvuky používaly a dokonce vzniklo i několi programů umožňujících tvořit něco jako mod na Amize, která byla velkým vzorem a programátoři se neustále snažili Amigu překonat a ukázat, že jejich oblíbené ZX Spectrum je prostě lepší (bohužel to není pravda, ale co naděláme).

D/A převodníků se používalo hned několik typů. Nejčastěji se vyskytovaly varianty osmibitové na portech rozhraní 8255 a nebo se používala AY-3-8912, později jsem se setkal se zajímavým převodníkem od Petra Simandla, který na UR-4 dokázal připojit D/A převodník se čtyřmi osmibitovými kanály a automatickým přepínáním kanálů po odeslání bytu na některý z nich. Tenkrát nám to připadalo jako naprosto úžasná věc, ale principielně se jedná o jednoduché leč elegantní řešení. Pochopitelně vše je myšleno v rámci české a slovenské republiky.

Existuje i převodník A/D umožňující získání nějkého toho zvuku i na ZX Spectrum, ale následná příprava a zpracování ZX Spectru není docela jednoduché, jak jsem zmínil paměti je málo...

Další zádrhel na který je potřeba dávat pozor je rychlost pamětí ZX Spectra. Některé oblasti RAM jsou obnovovány obvodem ULA a některé procesorem viz. následující tabulka (žlutě označené stránky má na starost ULA). ULA při obnovování dynamické paměti pozastavuje hodiny procesoru což má za následek jeho zpomalení zhruba a hlavně jeho rychlost nebude stejná, ale bude se mírně měnit podle toho jak dojde zrovna ke kolizi. Pro přehrávání samplů, kde musí být rychlost konstantní je to naprostá katastrofa. Pokud z této paměti bude program data číst projeví se problém jen velmi málo, ale pokud v ní program poběží získáte velmi zajímavé bublavé zkreslení zvuku. A ruských klonů Spectra se pomalé stránky někdy nevyskytují a občerstvování paměti je řešeno jinak. Nezapoměňte na to při kompilaci a spouštění ukázkových rutin.

stránkování paměti

Vzhledem k rychlosti CPU ZX Spectra a velikosti jeho paměti je potřeba šetřit kde se dá. Proto se obvykle vyskytují dva různé způsoby uložení samplů v paměti. První nejjednodušší využívá jeden byte pro jeden vzorek. Má smysl u osmibitových převodníků. Prakticky bez výjimky jsou vzorky v rozsahu od 0 do 255, kvůli jednoduchosti, kompatibilitě a možnosti používat D/A převodníky složené z rezistorů místo integrovaných, které by sice byly kvalitnější, ale v době použití neúnosně drahé. Tento způsob je na další zpracování asi nejjednodušší. Stejně lze ukládat vzorky pro jakýkoliv další převodník, i pro AY, ale protože D/A výstup z AY je nejvýše čtyřbitový nemá smysl pamětí plýtvat, takže se do jednoho bytu ukládají vzorky dva. V mém případě jsou to vzorky po sobě následující, ale viděl jsem i řešení, kdy v horních a dolních polovinách bytů byly uložené zcela nezávislé vzorky různých zvuků, jako by se jednalo o různou paměť. Moje řešení je o náročnější na vyčasování programu, druhé řešení na rozumné rozložení v paměti a délky zvuků.

D/A převodník připojení k 8255

Nejobvyklejší zapojení D/A převodníku k tomuto rozhraní je tříkanálová varianta se třemi osmibitovými převodníky připojenými přímo na osmibitové brány A, B a C, takže není nic jednoduššího než poslat hodnotu na příslušný port (31, 63 a 95) a převodník hraje. Technická řešení jsou různá, nejčastěji odporová síť připojená přímo na 8255, viděl jsem i pár integrovaných D/A převodníků, ale na tom až tak moc nezáleží, z hlediska programu to je vždy stejné. Jediné co musí být zachováno je rozsah vstupních hodnot. Tj. 0 až 255, nikoliv -127 až +127 jako u převodníků A/D (např. ZN427), takový sampl je nutné nejprve zkonvertovat.

Použití portu bohužel koliduje s Kempston Joystickem a Amiga Myší, takže je případně nezapomeňte odpojit (myš), nebo alespoň nepoužívat (joystick) jinak se dočkáte (zvláště u myši) zajímavého intenzivního praskání, případně jiného zkreslení.

Jednotlivé kanály D/A převodníku se buď slučují monofonně, nebo stejně jako výstupy z AY-3-8912 jako ACB stereo.

Ještě než začnete (pokud začnete) pročítat zdrojové kódy, znovu upozorňuji, že program je nutné umístit do rychlé paměťové stránky např. na adresu 64000 do stránky 0, nebo na 32768 chcete-li využít celých 128kB. Začátek samplu budu vždy označovat SMSTART, a délku SMLENGHT, rychlost přehrávání SMSPEED. V praxi může být SMSTART=30000 a SMLENGHT=32768. SMSPEED obvykle bývá okolo 10 až 20, obecně čím větší číslo tím pomaleji se přehrává. A každá rutina přehrává různou rychlostí v závislosti na složitosti rutiny. Nepředpokládám použití všech v jednom jediném programu. V tom případě byste totiž museli spočítat u každé rutiny kolik taktů trvají smyčky a program upravit. Počet taktů pro každý vzorek by musel být naprosto stejný u všech rutin což není až tak jednoduché. Spíše bych to řešil experimentálně a nastavil časovací konstanty přibližně.

Přehrávání 8 bitového samplu na D/A převodník

Ve všech ukázkových programech vkládám do registrů přímo konkrétní hodnotu. V praxi byste nejspíš četli hodnotu z nějaké adresy a ta by mohla být společná pro více rutin. Ve většině editorů assambleru (Prometheus) stačí tuto hodnotu vložit do závorek a o zbytek se postará překladač.

8BITPLAY di                ; zakaž přerušení
         ld    a,128       ; nastav všechny porty 8255 jako výstupní
         out   (127),a
         ld    hl,SMSTART  ; do HL adresa 1. bytu samplu
         ld    de,SMLENGHT ; do DE délka samplu
         ld    c,SPEED     ; do reg. C vlož rychlost přehrávání
OPAKUJ   ld    a,(hl)      ; načti z paměti byte (vzorek samplu)
         out   (95),a      ; a zapiš ho na D/A převodník
         inc   hl          ; zvyš ukazatel do paměti o 1
         ld    b,c         ; rychlost vlož do reg. B
PAUZA    djnz  PAUZA       ; a proveď příslušný počet zpomalovacích smyček
         dec   de          ; sniž počet zbývajících bytů o 1
         ld    a,d         ; a otestuj jestli tento byte nebyl poslední
         or    e           ; (délka by byla 0)
         jp    nz,OPAKUJ   ; nebyl-li tento byte poslední opakuj vše znovu
         ei                ; povol přerušení
         ret               ; vrať se

Použití AY-3-8912 coby D/A převodníku

Tento prográmek je opravdu jednoduchý a lehce pochopitelný takže ho mírně zkomplikuju. Protože přehrávání 4 nebo 8 bitových samplů na D/A převodník je stejné nebudu ho uvádět. Zajímavější je zpracování 4 bitových vzorků, 4 bitový vzorek zabírá pouze 1/2 bytu a tak se přímo nabízí možnost uložit liché vzorky do jedné poloviny a sudé vzorky do poloviny druhé (horní). Během jednoho kroku je tedy potřeba poslat na D/A převodník 2 byty a k tomu dodržet správnou frekvenci (poměr časů pro 1 a druhý vzorek by měl být 1:1). To ovšem snadno odvodíte z programu pro AY takže se podíváme jak na něj.

Přehrávání 4 bitového samplu na AY-3-8912

4BITAY   di                   ; zakaž přerušení, potřebujeme konstantní rychlost programu
         push   ix            ; reg. IX nesmí být změněn, chcete-li se vrátit do BASICu
         ld     de,SMLENGHT   ; reg. HL budeme potřebovat jinde
         ld     ix,SMSTART
         ld     bc,65533      ; reg. BC se použije k adresování AY
                              ; proto v něm není délka samplu
         ld     a,7           ; nejdříve je nutno nastavit registry AY
         out    (c),a         ; aby to vůbec něco hrálo
         ld     b,191
         ld     a,255
         out    (c),a
         ld     b,253
         ld     a,10
         out    (c),a
         ld     a,SMSPEED     ; do reg. A vezmi rychlost
         add    a,a           ; zdvojnásob ji
         ld     c,a           ; a uschovej do C
         ld     hl,65280
OPAKUJ   ld     a,(ix+000)    ; načti vzorek z paměti
         and    %00001111     ; tímto "odříznu" dolní půlku bytu - pro jistotu

         ld     l,a
         ld     a,(hl)
         or     %10000000
         out    (253),a

         ld     b,c           ; vezmi do B z C uschovanou rychlost
WAIT1    djnz   WAIT1         ; a čekej = proveď příslušný počet smyček
         inc    ix            ; zvyš ukazatel na vzorky samplu
         dec    de            ; sniž počet zbývajících bytů
         ld     a,d
         or     e
         jp     nz,OPAKUJ
         pop    ix
         ei
         ret                  ; vrať se

Jak Vám je jistě jasné tento způsob je zbytečně náročný na paměť takže si ukážeme další.

Přehrávání 4 bitového komprimovaného samplu na AY-3-8912

         ent   $              ; direktiva překladače PROMETHEUS, která
                              ; znamená, že odtud bude program spouštěn

4PCKAY   di                   ; zakaž přerušení, tentokrát je to nutné
         ld     de,(SMLENGHT) ; načti parametry samplu
         ld     hl,(SMSTART)
         rlc    d
         rlc    e
         ld     bc,65533      ; reg. BC bude použit k adresování portu AY
                              ; BC=65533=253+256*255
         ld     a,7           ; zvol registr 7 (AY) 
         out    (c),a         ; zapiš na port s adresou v BC hodnotu v A
         ld     b,191         ; BC=49149=253+256*191
         ld     a,255         ; vypnout hraní šumu i tónů
         out    (c),a
         ld     b,253         ; BC=65533
         ld     a,10          ; zvol registr 10 (AY), hlasitost kanálu C
         out    (c),a
         ld     a,(SMSPEED)   ; vezmi rychlost přehrávání
         ld     c,a           ; uschovej ji do C
SMLOOP   ld     a,(hl)        ; přečti vzorek
         nop                  ; 4 takty nic nedělej
         nop
         nop
         nop
         and    %00001111     ; ponech ze vzorku jen 4 dolní bity
         or     %10000000     ; nastav bit 7 na log. 1
         out    (253),a       ; a přehraj (vlastně provede nastavení hlasitosti AY)
         ld     b,c           ; skopíruj rychlost z C do B
SMPLPAU1 djnz   SMPLPAU1      ; a proveď příslušný počet zpomal. smyček
         nop                  ; 4 takty nic nedělej
         nop
         nop
         nop
         nop
         nop
         nop
         nop
         ld     a,(hl)        ; vezmi znovu vzorek z paměti
         rrca                 ; 4x odrotuj, takže se 4 horní bity dostanou 
         rrca                 ; na pozici 4 dolních
         rrca
         rrca
         and    %00001111     ; a to co by mohlo zůstat nahoře vynuluj
         or     %10000000     ; nastav bit 7 na log.1
         out    (253),a       ; a přehraj
         ld     b,c
SMPLPAU2 djnz   SMPLPAU2      ; proveď potřebné zpomalení
         inc    hl            ; posuň ukazatel na další vzorek (2 po 4 bitech v 1 bytu)
         dec    de            ; a testuj jestli tento byte nebyl poslední
         ld     a,d
         or     e
         jp     nz,SMPLOOP    ; případně opakuj
         ei
         ret                  ; vrať se

SMSTART  defw   49152         ; změňte si podle potřeby
SMDELKA  defw   16384
SMSPEED  defb   10

Přehrávání 4 bit. komprimovaného samplu na AY-3-8912 s linearizací

AY má logaritmické nastavování hlasitosti a samply se shodou okolností dají přehrávat na AY jedině pomocí změn hlasitosti. Proto lze AY použít jako tři 4bitové D/A převodníky. Jenže právě v té hlasitosti je háček. K přehrávání samplovaného zvuku je nezbytný lineární převodník. Proto se používá jednoduchá linearizační tabulka, pomocí níž lze vzorek transformovat tak, aby se výsledek více přiblížil tomu co požadujeme. A skutečně se tím na skutečné AY kvalita zlepší a přiblíží se tak 4 bitovému lineárnímu D/A. Emulátory ovšem obvykle (co kdyby náhodou ... ) AY emulují jako lineární a proto v nich má tato linearizace efekt spíše opačný, tedy kvalitu zvuku zhorší (zkuste si to).

         ent   $              ; direktiva překladače PROMETHEUS, která
                              ; znamená, že odtud bude program spouštěn

PL4BITAY di                   ; zakaž přerušení, tentokrát je to nutné
         push   ix
         ld     de,(SMLENGHT) ; načti parametry samplu
         ld     ix,(SMSTART)
         rlc    d
         rlc    e
         ld     bc,65533      ; reg. BC bude použit k adresování portu AY
                              ; BC=65533=253+256*255
         ld     a,7           ; zvol registr 7 (AY) 
         out    (c),a         ; zapiš na port s adresou v BC hodnotu v A
         ld     b,191         ; BC=49149=253+256*191
         ld     a,255         ; vypnout hraní šumu i tónů
         out    (c),a
         ld     b,253         ; BC=65533
         ld     a,10          ; zvol registr 10 (AY), hlasitost kanálu C
         out    (c),a
         ld     a,(SMSPEED)   ; vezmi rychlost přehrávání
         ld     c,a           ; uschovej ji do C
         ld     hl,LIN_TAB    ; do HL adresu linearizační tabulky (H=xxx L=0 !!)
SMLOOP   ld     a,(ix+0)      ; přečti vzorek
         nop                  ; 4 takty nic nedělej
         nop
         nop
         nop
         and    %00001111     ; ponech ze vzorku jen 4 dolní bity
         ld     l,a           ; do L ulož amplitudu vzorku
         ld     a,(hl)        ; a přečti odpovídající hodnotu z lin. tabulky
         or     %10000000     ; nastav bit 7 na log. 1
         out    (253),a       ; a přehraj (vlastně provede nastavení hlasitosti AY)
         ld     b,c           ; skopíruj rychlost z C do B
SMPLPAU1 djnz   SMPLPAU1      ; a proveď příslušný počet zpomal. smyček
         nop                  ; 4 takty nic nedělej
         nop
         nop
         nop
         nop
         nop
         nop
         nop
         ld     a,(ix+0)      ; vezmi znovu vzorek z paměti
         rrca                 ; 4x odrotuj, takže se 4 horní bity dostanou 
         rrca                 ; na pozici 4 dolních
         rrca
         rrca
         and    %00001111     ; a to co by mohlo zůstat nahoře vynuluj
         ld     l,a           ; zlinearizuj 
         ld     a,(hl)
         or     %10000000     ; nastav bit 7 na log.1
         out    (253),a       ; a přehraj
         ld     b,c
SMPLPAU2 djnz   SMPLPAU2      ; proveď potřebné zpomalení
         inc    ix            ; posuň ukazatel na další vzorek (2 po 4 bitech v 1 bytu)
         dec    de            ; a testuj jestli tento byte nebyl poslední
         ld     a,d
         or     e
         jp     nz,SMPLOOP    ; případně opakuj
         pop    ix
         ei
         ret                  ; vrať se

         org    49152         ; může být jakýkoliv násobek 256

LIN_TAB  defb   3,5,7,8,9,10
         defb   11,11,12,12,13
         defb   13,14,15,15,15

SMSTART  defw   0
SMDELKA  defw   16384
SMSPEED  defb   10

V tomto programu jsem použil variantu s hodnotami na nějaké adrese, kde mohou být sdíleny pro více rutin. Přednastavené hodnoty ukazují do ROM, takže spustíte-li takto opsaný program, přehraje Vám na AY celkem čistý šum.

Pokud linearizační tabulku u návěští LIN_TAB nahradíte posloupností 0,1,2, .... až 15,16 tak se bude přehrávat zvuk lineárně (rutina bude mít pro oba způsoby naprosto stejnou rychlost!).

Konverze z 8 bitového na 4 bitový sampl:

V tomto případě je trochu zbytečné zakazovat přerušení, protože rutina nemusí běžet stabilní rychlostí, ale na druhou stranu je-li zakázané rutina proběhne o něco rychleji.

4TO8BIT  di
         ld     hl,(SMSTART)  ; do registrů HL, DE, IX načti parametry samplu 
         push   ix
         ld     ix,(SMSTART)
         ld     de,(SMLENGHT)
OPAKUJ   ld     a,(hl)        ; čti byte
         ld     b,a           ; vlož ho do reg. B
         inc    hl            ; posuň ukazatel na další adresu (HL=HL+1)
         ld     a,(hl)        ; čti další byte
         and    %00001111     ; ponech z něho jen dolní půlku  
         ld     c,a           ; a tentokrát ho vlož do reg. C
         inc    hl            ; podruhé posuň ukazatel
         dec    de            ; sniž počet zbývajících bytů o 1
         ld     a,b           ; vezmi 1. uschovaný byte z reg. B do A
         and    %00001111     ; ponech z bytu jen dolní polovinu (horní vynuluj)
         rl     c             ; a odrotuj o 4 bity nahoru (udělej z ní horní půlku)
         rl     c
         rl     c
         rl     c
         or     c             ; teď k tomu přidej dolní půlku 2. bytu uschovanou v reg. C
         ld     (ix+000),a    ; a celé to ulož na adresu v paměti
         inc    ix            ; zvyš ukazatel na výsledný sampl o 1
         ld     a,d           ; testuj jestli nebyl zpracován poslední byte
         or     e
         jp     nz,OPAKUJ     ; pakliže nebyl poslední, opakuj celou smyčku
         pop    ix            ; obnov IX (kvůli volání z BASICu)
         ei
         ret                  ; vrať se

SMSTART  defw   49152         ; změňte si podle potřeby
SMLENGHT defw   16384

Filtrace 8 bitového samplu - pro 4 bity nevhodné:

Tento prográmek je velice jednoduchý příklad toho jak lze odstranit (nebo potlačit) šum a špičky. Nehodí se pro všechny případy, ale obvykle stačí. Jestli se nepletu tak tato rutina je víceméně ekvivalentní funkci SMOOTH, která se vyskytuje v některých editorech jako např. Sound Forge.

FILTER   di                   ; zákaz přerušení není nezbytně nutný, ale trochu
                              ; zrychlí provedení programu
         push   ix            ; reg. IX nesmí být změněn, chcete-li se vrátit do BASICu
         ld     de,(SMLENGHT) ; načti parametry samplu, který bude vyfiltrován
         ld     ix,(SMSTART)
FILTER_1 ld     l,(ix+0)      ; načtu jeden vzorek samplu
         ld     h,0
         ld     c,(ix+0)      ; a načtu druhý vzorek (opět jen 8 bitů
         ld     b,0           ; do 16 bitového registru)
         add    hl,bc         ; teď je sečtu
         rr     h             ; a výsledek v reg. HL vydělím 2
         rr     l             ; takže v L zůstane aritmetický průměr
         ld     (ix+0),l      ; a uložím to na adresu 1 z průměrovaných vzorků
         inc    ix            ; nyní se posunu o 1 vzorek dál
         dec    de
         ld     a,d
         or     e
         jp     nz,FILTER_1   ; opakuj až do konce samplu
         pop    ix
         ei
         ret                  ; vrať se

SMSTART  defw   49152         ; změňte si podle potřeby
SMLENGHT defw   16384

Linearizace 4 bitového samplu pro AY:

V některých případech se hodí zlinearizovat sampl před přehráváním. Např. máte-li program, který hraje jedině na AY nemá smysl v něm zachovávat lineární verzi samplu. Už proto, že čas ušetřený během přehrávání se dá využít i jinak.

LINSMPL  di
         
         ei
         ret

         org    49152-256     ; adresa musí být celočíselný násobek 256

LIN_TAB  defb   3,5,7,8,9,10
         defb   11,11,12,12,13
         defb   13,14,15,15,15

SMSTART  defw   49152         ; změňte si podle potřeby
SMLENGHT defw   16384

Konverze 8 bitového samplu na 7 bitový:

Na závěr bych ještě uvedl tento velice jednoduchý program. 7 bitové samply se sice obvykle nevyskytují, ale setkáte se s nimi u přehrávače A.S.E, který je používá proto, aby nemusel hlídat přetečení při sčítání vzorků. Přehrává rotiž 4 kanálové MODy na 3 kanálový D/A převodník takže do jednoho z kanálů převodníku musí sečíst dva vzorky. (zkuste program KONVERT7 použít 8x za sebou na obrazovku a získáte celkem zajímavý grafický efekt

KONVERT7 di
         ld     hl,(SMSTART)  ; načti parametry samplu
         ld     de,(SMLENGHT)
KONVER7L ld     a,(hl)        ; vezmi vzorek
         rra                  ; rotuj vpravo o 1 bit
         and    %01111111     ; tato instrukce není nutná, RRA vkládá zleva 0
         ld     (hl),a        ; ulož vzorek zpátky
         inc    hl            ; zvyš adresu vzorku o 1
         dec    de
         ld     a,e
         or     d
         jp     nz,KONVER7L   ; a opakuj až do posledního vzorku
         ei
         ret

SMSTART  defw   49152         ; změňte si podle potřeby
SMLENGHT defw   16384

Některé programy, které s tímto tématem souvisejí si tu můžete stáhnout. Všechny pracují s Betadiskem a musíte je tedy načíst do souboru TRD (příp. zapsat na disketu). Pak teprve je lze použít v emulátoru X128 (na Betadisku).

Download

Ještě poznámku na konec. Doufám, že v žádné rutině není chyba, ale zaručit 100% to nemůžu. Stejně tak nemůžu zaručit, že mnou napsané řešení je jediné možné, nebo nejlepší. Prostě to funguje a je to snadno pochopitelné což je hlavní.

Zdroj informací: praxe, zkušenosti, disassemblování některých jednoduchých rutin, moje staré zdrojové texty jejichž původ už mi není zcela známý

[ Zpět na hlavní stránku ]

Cygnusova stránka o ZX Spectru a kompatibilních počítačích byla napsána (přepsána) výhradně pomocí svobodného Open Source softwaru. V případě že naleznete chybu, nebo byste rádi cokoliv co se ZX Spectrem souvisí, neváhejte mi napsat na některý z mých emailů, nebo pracovně do zaměstnání. Stručně o mém webu se můžete dočíst zde.