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ěkolik hudebních programů umožňujících psát celé skladby, podobně jako MOD na Amize, která byla v té době často vzorem.
D/A převodníků se používalo hned několik. Nejčastěji se vyskytovaly varianty osmibitové na portech rozhraní 8255 a nebo se používala AY-3-8912. Velikou nevýhodou AY je nelinearita výstupu, ale narozdíl od 8255 je zas v každém ZX Spectru 128k a i v mnoha dalších variantách ZX Spectra.
Pro interface s 8255 kompatibilní s UR-4 existuje zajímavý čtyřkanálový D/A převodník od Petra Simandla, přepínající cyklicky kanály dle nastavení registru od prvního po poslední, nebo první pár, druhý pár, pouze zápisem na první bránu 8255. Bohužel tehdy napsaný software neměl tak stabilní samplerate jako konkurenční programy a navzdory schopnosti přehrávat z FAT12 diskety amigácké MODy přímo bez konverze uživatelem se příliš nerozšířil.
Existuje i A/D převodník umožňující zaznamenání zvuku přímo na ZX Spectrum, ale následná příprava a zpracování není jednoduché. Na to má ZX Spectrum nejenom málo paměti, ale i malé rozlišení grafiky a krom pár konverzních utilit neexistuje opravdu pohodlný program pro editaci zvukového záznamu.
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.
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ý:
Se 7 bitovými samply se setkáte u přehrávače A.S.E, který je používá proto, aby mohl sčítat vzorky ve středním kanálu. Konvertované skladby MOD z Amigy jsou 4 kanálové, na 3 kanálovém D/A převodníku je nutné kanály nějak mixovat, buď dva do levého a dva do pravého kanálu a třetí ignorovat, nebo dva do středního a po jednom do levého a pravého kanálu. Autoři A.S.E. zvolili druhou možnost.
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
- ASE 2.0 - ZIP 28kB - Nepracuje v emulátoru, vyžaduje D/A převodník na portech 31,63,95
- Sampletracker 2.0 - ZIP 226kB - V X128 hraje jen na Speaker a AY, D/A není emulován
- Konvertor - ZIP 3kB - V X128 hraje jen na AY, D/A není emulován.
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ý