Talán 11-12 éves lehettem, amikor első alkalommal kerültem kapcsolatba a C64-essel, játékokon keresztül. Ezek között a játékok között sok olyan akadt, melynek a zenéje a mai napig nagy hatású, ezt bizonyítja a számos SID lejátszó, a pár ezer remix és egy online rádió létezése.
Így ahogyan nagyon sokaknak, nekem is kicsit mániámmá vált a SID. Másrészt szintén kedvenc témám a hangszintézis, ezért törvényszerű volt, hogy előbb-utóbb nekilássak egy SID lejátszó barkácsolásának.
A projekt eredeti célja kicsit más volt, egy SID – MIDI konvertert szerettem volna csinálni. Azonban ehhez szükségessé vált a SID zenék lejátszása és a lejátszás helyességének az ellenőrzése. Mi más lenne a legjobb ellenőrzés mint az, ha meg is lehet hallgatni a zenét?
Az emuláció feladatai
A SID zenék valójában kis programok, melyeket zseniális kóder-zenészek írtak, egyfajta versengő-rivalizáló demoszcéna miliőben. Leginkább az exe-zenékhez tudnám hasonlítani, amelyek hasonlóan a zenei adatok mellett a lejátszó kódját is magukba foglalják. Ezek a SID zenék természetesen a legendás chipen szólalnak meg (ezzel szemben az exe-zenék a hangszintézis kódját is tartalmazzák, hiszen nem célhardverre készülnek).
Ebből érezhető, hogy a SID zenék lejátszásának feladata két részre osztható: a programkód futtatásához szükséges C64-es komponensek és a SID emulálása.
A C64 emulációja
Erősen a projekthez szükséges ismeretekre szorítkozva, tehát a teljesség igénye nélkül fussuk át röviden a C64 felépítését.
A könnyebb áttekinthetőség kedvéért az ábrán színekkel kiemeltem a számunkra érdekes részeket.
A 6510 CPU (kék) feladata az utasítások dekódolása, végrehajtása és az adatbusz (sárga) és a címbusz (szürke) vezérlése. Az utasításokból egy logika (piros) állítja elő és kapuzza a címbuszra a címeket. Az ütemezésért, azaz a ciklusok végrehajtásáért a processzor felel, ennek az órajele (lila) közel 1 MHz.
Az emulációnak tehát ezeket a feladatokat kell utánoznia.
Az utasítások dekódolására, végrehajtására, valamint a címek előállítására készítettem egy 256 elemű táblázatot. A táblázat minden eleme két metódust tárol, az első metódus a megfelelő címzési mód a második pedig az adott utasítás megvalósítása. Nem akartam az utasítás dekódolásával vesződni, azaz a “piros” doboz logikáját implementálni, ami az utasítás 8 bitje alapján meghatározza az operandus címét. A processzor feladatát a második metódus végzi el, paraméterként megkapja az operandus címét. Az utasítás dekódolása azért is egyszerűbb ezzel a táblázatos módszerrel, mert a 256 utasítás közül 104 illegális, azaz olyan bitkombinációhoz tartozik, amelynek dekódolása során “a processzor és a számítógép működése instabillá válhat”. Természetesen a valóság ennél veszélytelenebb, így ravasz kóderek sok illegális utasítást is használtak, mivel azok sokszor több normál utasítás kombinációjának felelnek meg. A táblázatos módszer az emuláció gyorsasága miatt is jobb megoldásnak tűnt.
A cím dekódoló logikának csupán egy feladata van: a SID címtartományába eső műveletek nem a memóriát, hanem a SID regisztereit érik el. Mivel nem használom a SID címtartományára eső RAM-ot és a RAM-ROM közötti kapcsolást, ezért ez a lépés ki is hagyható. A 6510-es abban tér el lényegesen a 6502-estől, hogy kibővítették egy porttal, amelyen keresztül a címtartomány bizonyos részei más-más RAM/ROM elemekre képezhető le. Ez a port a $0000 és $0001 címeken található.
A ciklusok modellezésére készítettem egy metódust. Ezt minden alkalommal, amikor a C64 egy ciklust hajt végre (lényegében az olvasó és író műveleteknél) futtatni kell, ez a metódus aktualizálja a SID állapotát is. Definíció szerint a helyes időzítéshez ennek a metódusnak a meghívása a C64 órajelével megegyező gyakorisággal történik.
Nem törekedtem a tökéletes modellezésre, ezért a speciális esetekben pl. laphatár átlépésekor adódó extra ciklusokat nem vettem figyelembe.
A SID emulációja
A következő részben a SID működését igyekszem bemutatni, ahogyan azt a tervezőjével, Bob Yannes-szel készített beszélgetés és az eddigi legvalósághűbb emuláció, a reSid, forráskódja alapján megismertem.
Az oszcillátor alapja egy 24 bites akkumulátor, melynek értéke a kiválasztott hullámforma generálásának a bemenete. Ezt az akkumulátort növeli ciklusonként a 16 bites programozható frekvencia érték. Ebből és a C64 órajeléből kiszámítható a SID által generált jelek frekvenciájának terjedelme.
A fűrészfog jel egyszerűen az akkumulátor felső 12 bitje.
A háromszög jelnél az akkumulátor felső 12 bitje közül a legfelső (MSB) invertálja a többi 11 bitet, így a háromszög jel felbontása csak 11 bites, azaz a többi hullámforma fele.
A négyszögjel esetében a kimenet az akkumulátor felső 12 bitjének a beállított pulzus szélesség 12 bites értékével történő összehasonlítás eredménye.
A zajgenerátor egy 23 bites shift regiszter, melynek bizonyos bitjeit logikai műveletekkel visszacsatolják a bemenetre. A shift regiszter ütemezése (órajele) az akkumulátortól függ, így a zaj frekvenciája a többi jeléhez hasonlóan állítható. A visszacsatolások lényegesen meghatározzák a zaj hangszínét, viszont a 23 bit nagyon sok kombinációt eredményez, ezért ezt a logikát átvettem a reSid projektből.
A burkológörbe generátor egy 8 bites számláló, amelynél az egyes fázisok időzítése egy frekvenciaosztóval történik. Az adott ADSR értékekhez egy-egy táblázatból kiolvasott érték tartozik, amely a frekvencia osztója, így a számláló sebességét állítja. A Decay és Release fázisok görbéjének exponenciális jellegét egy további táblázat biztosítja. Amikor a számláló eléri a táblázat egy értékét a számlálás sebességét kettővel osztják. Ha a Sustain fázisban a sustain értékét kisebbre változtatjuk, a számláló tovább számol, azonban ha nagyobbra változtatjuk, nem történik semmi. Ennek egyszerűen az az oka, hogy a Sustain fázist egy összehasonlítás jelzi.
A SID sok jellegzetes hangszínéért két effekt, a kemény szinkron (hard sync) és a ring moduláció a felelős. Hard sync esetén a modulált oszcillátor akkumulátorát törölni kell, ha a moduláló oszcillátor akkumulátorának legfelső bitje 1-be vált. A ring moduláció csak a háromszög hullámforma kiválasztásakor működik, ugyanis abból áll, hogy a háromszög jel generálásánál történő invertálás bemenete nem az adott oszcillátor, hanem a moduláló oszcillátor akkumulátora. Ebből látszik, hogy ez nem valódi amplitudó moduláció (pláne nem ring moduláció), de a hatása leginkább ahhoz hasonlít. Azt hiszem éppen a demoscene számára nem kell magyarázni a hasonló “olyan mintha, de valójában teljesen más, de jól néz ki/jól szól” trükkök létjogosultságát.
A SID számomra legkevésbé ismert része a szűrője, ezért ennek modellezése teljesen eltér az eredeti hardvertől. Egy másik, számomra homályos terület a hullámformák kombinációja, melynek használatát a tervező nem szorgalmazta, mivel adott esetben “kiakaszthatja” a zajgenerátort és ezt csak egy hideg reset hozhatja helyre.
Említést érdemel még a digitális hangminták lejátszása. Ez az a terület, melyet jelenleg kutatok, a lejátszóm még nem támogatja ezeket.
Ebből a rövid leírásból is látszik az, amit maga Yannes is mondott: a chip helyszűke és a rendelkezésre álló idő rövidsége miatt igencsak trükkös és bugos lett. Ez természetesen megnehezíti az emulálást, hiszen a hibákat is jól kell utánozni. Véleményem szerint ugyanakkor nagyon tanulságos és szellemes megoldásokkal találkozhatunk, és az is kiderül, hogy a SID valójában digitális hangmintákat állított elő, 12 bites felbontásban, közel 1 MHz-es “mintavétellel”.
A lejátszó
A lejátszó implementációját az alábbi sematikus ábra vázolja.
A lejátszást a hanglejátszó eszközhöz szinkronizáltam. A hanglejátszó adott késleltetést (latency) igyekszik biztosítani, ami megszabja, hogy milyen időközönként és mennyi mintát kell generálni. Ha semmilyen interakcióra nincsen szükség, azaz teljesen passzív lejátszást szeretnénk, akkor tetszőleges késleltetést választhatunk, így a hanglejátszást akár a zene sebességéhez (50-60 Hz) vagy annak többszöröséhez lehet szinkronizálni.
A lejátszó lelke tehát a callback metódus, melyet a hanglejátszó eszköz hív meg. Ez a metódus futtatja a C64 ciklus emulációt, ami a SID emuláció által hangmintákat generál. Definíció szerint így közel 1 MHz-es jelet kapnánk, ami pl. 48kHz-es hanglejátszásnál túl nagy többlet. Azonban ha a SID emulációját mindig csak akkor futtatjuk, amikor ténylegesen egy hangmintára van szükség, megjelenik a szinkronizálás feladata. Az én elképzelésem az volt, hogy már a C64 emuláció vegye figyelembe a kívánt mintavételi frekvenciát. Azonban a SID szinkronizálását, konkrétan a zajgenerátor és a burkológörbe állapotának adott ciklussal történő léptetését még nem oldottam meg, ezért ez az optimalizálás hiányzik.
A callback metódusnak ügyelnie kell arra, hogy mennyi ciklusonként kell a zene lejátszó C64-es kódot futtatni, azaz a zene “állapotát” aktualizálni. Fontos, hogy egy-egy ilyen aktualizálás hosszabb ideig is eltarthat, közben sok-sok C64 ciklus fut le és akár több hangmintát is generálni kell.
A PAL rendszerű C64 órajele kb. 985 248 Hz, a PAL frekvencia 50,1245 Hz és legyen a kívánt hanglejátszás 48kHz-es. Ekkor átlagosan minden 20,5 ciklus után kell egy hangmintát generálni és minden 19 656 ciklus után kell meghívni a C64-es lejátszó kódot.
A cikk szuper! A csúszásért pedig sorry, egy csúnya wordpress bug miatt a 3.9-es verzióra updatelve nem volt elérhető az admin felület. :)
Köszi a cikket, zsírkirály!