A legutóbb berakott funkcióbillentyûs fejlesztésnél pontosan miket is kapcsolgatunk?
A TONE_AND_NOISE_MODE beállítását.
Leírás az exolon.s-ben található Spectrum 128-as AY emulátor kódhoz (1. rész, a hozzászólások méretének korlátozása miatt):
macro ayVolTableMacro
ayVolumeTable:
defb 0, 1, 2, 3, 4, 5, 6, 9
defb 12, 17, 22, 28, 36, 44, 53, 63
endm
align 16
ayTablesBegin:
if (ayTablesBegin & 0030h) != 0010h
ayVolTableMacro
assert ($ & 000fh) == 0
endif
ayRegisterMaskTable:
defb 0ffh, 00fh, 0ffh, 00fh, 0ffh, 00fh, 01fh, 0ffh
defb 01fh, 01fh, 01fh, 0ffh, 0ffh, 00fh, 0ffh, 0ffh
assert ($ & 000fh) == 0
ayRegWriteTable:
defb low (ayRegisterWrite.l3 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l3 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l4 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l4 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l6 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l6 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l7 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l5 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l9 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l10 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l11 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l12 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l12 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l16 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l8 - (ayRegisterWrite.l1 + 2))
defb low (ayRegisterWrite.l8 - (ayRegisterWrite.l1 + 2))
assert ($ & 000fh) == 0
ayRegisters:
defb 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
defb 00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
assert ($ & 000fh) == 0
if (ayTablesBegin & 0030h) == 0010h
ayVolTableMacro
assert ($ & 000fh) == 0
endif
Ezekben a táblázatokban található az AY hangerőnek megfelelő DAVE hangerő (ayVolumeTable), az egyes AY regiszterekben ténylegesen használt bitek AND maszkja (ayRegisterMaskTable), a regiszterek írásakor használt ugrótáblázat (ayRegWriteTable, lásd lent), és a regiszterek aktuális értéke (ayRegisters). A táblázatok 16-al osztható címre vannak igazítva (align 16) az egyszerűbb és gyorsabb elérés érdekében, ezen kívül a sorrendjük változhat a memóriában, hogy lehetőség legyen további optimalizálásra:
00h: ayVolumeTable ayRegisterMaskTable ayRegWriteTable ayRegisters
10h: ayRegisterMaskTable ayRegWriteTable ayRegisters | ayVolumeTable
20h: ayVolumeTable ayRegisterMaskTable | ayRegWriteTable ayRegisters
30h: ayVolumeTable | ayRegisterMaskTable ayRegWriteTable ayRegistersA 00h..30h az első táblázat kezdőcímének az alsó 6 bitje, a '|' pedig a 64 byte-os határt jelzi. Íráskor a táblázatokat ayRegisterMaskTable, ayRegisters, ayRegWriteTable sorrendben kell használni. Amint látható, az első két esetben az ayRegisters címe egyszerű SET 5 művelettel számítható, utána pedig az ayRegWriteTable RES 4 művelettel. A harmadik esetben először ADD 20h-ra van szükség, de utána továbbra is használható a RES 4. A negyediknél pedig SET 5 után XOR 30h következik.
Ezzel az (általában nem használt) rutinnal olvashatók a regiszterek (a regiszter száma az A-ban adható meg, és az A-ban adja vissza az értéket):
ayRegisterRead:
and 0fh
or low ayRegisters
ld (.l1 + 1), a
.l1: ld a, (ayRegisters) ; *
or a
ret
Az írás természetesen már bonyolultabb:
ayRegisterWrite:
push af
push bc
push hl
and 0fh
or low ayRegisterMaskTable
ld l, a
ld h, high ayRegisterMaskTable
if ayRegisters == (ayRegisterMaskTable | 0020h)
ld a, c
and (hl)
set 5, l
else
if ((ayRegisters ^ ayRegisterMaskTable) & 0ff00h) == 0
add a, low (ayRegisters - ayRegisterMaskTable)
ld b, a
ld a, c
and (hl)
ld l, b
else
ld a, c
and (hl)
ld bc, ayRegisters - ayRegisterMaskTable
add hl, bc
endif
endif
cp (hl)
jr z, .l2 ; register not changed ?
ld (hl), a
if ayRegWriteTable == (ayRegisters & 0ffefh)
res 4, l
else
ld a, l
xor low (ayRegWriteTable ^ ayRegisters)
ld l, a
endif
ld a, (hl)
ld (.l1 + 1), a
.l1: jr .l8 ; *
.l2: ld a, l
xor low (ayRegisters + 13)
jr z, .l16 ; envelope restart ?
pop hl
pop bc
pop af
ret
Itt az A a regiszter száma, a C pedig az új érték. Amint látható, először az ayRegisterMaskTable segítségével AND művelet történik, hogy a nem használt bitek mindig 0-ra legyenek állítva, majd az új és az előző érték összehasonlítása. Ha nem változott, akkor nincs semmi teendő, kivéve, ha a regiszter a 13-as, mert a burkológörbét ilyenkor újra kell indítani (.l16). Egyébként tárolódik az új érték, és az ayRegWriteTable ugrótáblázat alapján elvégezhető az adott regiszternek megfelelő művelet (pl. frekvencia változtatása). A különböző feltételesen fordított részek a fent említett optimalizálást valósítják meg.
A regiszterek írása sorban:
.l3: call setChannelAFreq ; tone generator A frequency
pop hl
pop bc
pop af
ret
.l4: call setChannelBFreq ; tone generator B frequency
pop hl
pop bc
pop af
ret
.l5: call setChannelAFreq ; mixer
call setChannelBFreq
.l6: call setChannelCFreq ; tone generator C frequency
pop hl
pop bc
pop af
ret
Ez a 0-5. és 7. regiszter. A mixer (7-es regiszter) változtatásakor mindhárom csatornán újraállítja a frekvenciát, ugyanis a négyszögjel/zaj engedélyezésnek csak az adott csatorna DAVE frekvencia regisztereire (A0h-A5h) van hatása:
egyik sem: 0000h (nem hallható)
csak négyszögjel: négyszögjel frekvencia
csak zaj: zaj frekvencia + 3000h (17 bites polinom számláló)
mindkettő: változhat a TONE_AND_NOISE_MODE-tól függően (lásd lent)
A 0-5. regiszternél természetesen csak az adott csatorna frekvenciája frissítődik.
A csatorna frekvencia állításának a megvalósítása Spectrum 128-hoz:
setChannelAFreq:
ld c, 0a0h
ld a, (ayRegisters + 7)
ld hl, (ayRegisters)
jp setChannelFrequency
setChannelBFreq:
ld c, 0a2h
ld a, (ayRegisters + 7)
rrca
ld hl, (ayRegisters + 2)
jp setChannelFrequency
setChannelCFreq:
ld c, 0a4h
ld a, (ayRegisters + 7)
rrca
rrca
ld hl, (ayRegisters + 4)
setChannelFrequency:
if TONE_AND_NOISE_MODE == 0
rrca
jr nc, setToneGenFrequency ; tone generator enabled ?
and 04h
jr z, setNoiseGenFreq ; noise generator enabled ?
endif
if TONE_AND_NOISE_MODE == 1
bit 3, a
jr z, setNoiseGenFreq ; noise generator enabled ?
rrca
jr nc, setToneGenFrequency ; tone generator enabled ?
endif
if TONE_AND_NOISE_MODE > 15
and 09h
jr z, setToneGenAsNoise ; tone + noise generator enabled ?
srl a
jr nc, setToneGenFrequency ; tone generator only ?
jr z, setNoiseGenFreq ; noise generator only ?
endif
xor a ; channel disabled
out (c), a
inc c
out (c), a
ret
if TONE_AND_NOISE_MODE > 15
setToneGenAsNoise:
ld a, TONE_AND_NOISE_MODE
defb 0feh ; = CP nn
endif
setToneGenFrequency:
if TONE_AND_NOISE_MODE > 15
xor a
ld (.l1 + 1), a
endif
ld b, h
ld a, l
sra b
rra
sra b
rra
sra b
rra
adc a, l
ld l, a
dec hl
ld a, b
adc a, h
cp 10h
jr nc, .l3 ; overflow ?
.l1:
if TONE_AND_NOISE_MODE > 15
or 00h ; * non-zero for tone + noise
endif
.l2: out (c), l
inc c
out (c), a
ret
.l3: inc l
inc a
jr z, .l1
ld l, 0ffh
ld a, 0fh
jp .l1
setNoiseGenFreq:
ld a, (ayRegisters + 6)
cp 1
adc a, 0
ld h, a
rra
sla h
rra
adc a, h
dec a
out (c), a
inc c
ld a, 30h
out (c), a
ret
Ezek elvégzik a már említett frekvencia konverziót, és a mixer regiszter bitjeitől függően kerül sor 0 frekvencia, négyszögjel frekvencia, vagy zaj beállítására. A négyszögjel+zaj speciális eset, ezt szabályozza a TONE_AND_NOISE_MODE paraméter:
0: csak négyszögjel
1: csak zaj
>15: négyszögjel frekvencia torzítással
Vissza a többi regiszter írásához:
.l7: ld a, (ayRegisters + 7) ; noise generator frequency
if TONE_AND_NOISE_MODE != 1
xor 07h
ld b, a
and 09h
else
ld b, a
and 08h
endif
call z, setChannelAFreq
if TONE_AND_NOISE_MODE != 1
ld a, b
and 12h
else
bit 4, b
endif
call z, setChannelBFreq
if TONE_AND_NOISE_MODE != 1
ld a, b
and 24h
else
bit 5, b
endif
call z, setChannelCFreq
.l8: pop hl
pop bc
pop af
ret
Ez a - meglepően bonyolultra sikerült - zaj frekvencia beállítás. Az .l8 címke azokhoz a regiszterekhez van fenntartva, amelyeknek az írásakor nem történik semmi. Az egyes csatornák frekvenciájának a frissítése csak akkor történik meg, ha ott ténylegesen van is zaj, ez pedig függhet a TONE_AND_NOISE_MODE-tól: ha az 1, akkor a négyszögjel bitektől (7-es regiszter b0-b2) függetlenül csak a zaj engedélyezését kell figyelni, egyébként akkor van zaj, ha a zaj engedélyezett, de a négyszögjel nem (ez valamivel bonyolultabb eset). A setChannelFreq rutinok nem rontják el a B regisztert zaj esetén, így az itt felhasználható volt a mixer regiszter tárolására.
A hangerő regiszterek írása:
.l9: ld a, (ayRegisters + 8) ; channel A amplitude / envelope enable
ld c, 0a8h
jp setChannelAmplitude
.l10: ld a, (ayRegisters + 9) ; channel B amplitude / envelope enable
ld c, 0a9h
jp setChannelAmplitude
.l11: ld a, (ayRegisters + 10) ; channel C amplitude / envelope enable
if ENABLE_STEREO == 0
ld c, 0aah
else
ld c, 0aeh
endif
jp setChannelAmplitude
Ez eddig nem túl érdekes, a tényleges hangerő beállítást a setChannelAmplitude végzi. C-ben az adott csatorna bal hangerő portja van, kivéve a C csatornát sztereó módban, mert akkor a C csatorna csak a jobb oldalon hallható. Ez a hangerő állító rutin:
setChannelAmplitude:
if NO_ENVELOPE_IRQ == 0
di
endif
cp 10h
jr c, .l1
if NO_ENVELOPE_IRQ == 0
ld a, (envelopeInterrupt.l3 + 1)
else
xor a
endif
.l1: or low ayVolumeTable
ld l, a ; H = high ayRegWriteTable
if ((ayVolumeTable ^ ayRegWriteTable) & 0ff00h) != 0
if ayVolumeTable > ayRegWriteTable
inc h
else
dec h
endif
endif
ld a, (hl)
if ENABLE_STEREO != 0
bit 0, c
jr z, .l2
ld b, a
srl b
srl b
sub b
endif
out (c), a
set 2, c
.l2: out (c), a
if NO_ENVELOPE_IRQ == 0
ld hl, (ayRegisters + 8)
ld a, (ayRegisters + 10)
ld bc, 70h + (envelopeEnableTable & 0ff00h)
and c
add a, a
or h
and c
add a, a
or l
and c
rrca
rrca
rrca
rrca
or low envelopeEnableTable
ld c, a
ld a, (bc)
ld (envelopeInterrupt.l8 - 1), a
endif
pop hl
pop bc
pop af
if NO_ENVELOPE_IRQ == 0
ei
endif
ret
Burkológörbe emulációnál átmenetileg letiltja a megszakítást, mert egyébként a hangerő és a burkológörbe engedélyezésének az állítása közötti megszakítás problémát okozhatna. Ez a rutin egyébként az ayRegisterWrite-ból is visszatér, ezért vannak a POP utasítások a végén.
Először a hangerőt állítja be, tiltott burkológörbe esetén az alsó 4 bit, egyébként a burkológörbe aktuális állapota (envelopeInterrupt.l3 + 1, vagy 0 ha nincs burkológörbe emuláció) alapján, a már említett hangerő konvertáló táblázatot használva. Mivel a híváskor a H regiszterben még mindig az ayRegWriteTable felső 8 bitje van, ez kisebb optimalizálást tesz lehetővé ('ld h, high ayVolumeTable' megtakarítása). Mono módban a hangerőt egyszerűen kiírja mindkét portra, sztereó hangnál viszont a csatornától függően vagy csak az egyik oldalra kerül hang (A = bal, C = jobb), vagy mindkettőre (B) 75% szinten, hogy ez ne legyen sokkal hangosabb, mint az A és C csatorna.
Ha a burkológörbe emuláció engedélyezett, akkor még a megszakítási rutinban frissíteni kell, hogy melyik csatorna használja a burkológörbét. Erre a célra használható a 8 elemű és 8 byte-ra igazított envelopeEnableTable, amely egy ugrótábla JR utasításhoz:
envelopeEnableTable:
defb low (envelopeInterrupt.l9 - envelopeInterrupt.l8)
defb low (envelopeInterrupt.l8 - envelopeInterrupt.l8)
defb low (envelopeInterrupt.l11 - envelopeInterrupt.l8)
defb low (envelopeInterrupt.l10 - envelopeInterrupt.l8)
defb low (envelopeInterrupt.l13 - envelopeInterrupt.l8)
defb low (envelopeInterrupt.l12 - envelopeInterrupt.l8)
defb low (envelopeInterrupt.l15 - envelopeInterrupt.l8)
defb low (envelopeInterrupt.l14 - envelopeInterrupt.l8)
Van még további 3 írható regiszter, de ezeknek csak burkológörbe emulációval van jelentősége. Az első kettő a burkológörbe frekvencia:
.l12:
if NO_ENVELOPE_IRQ == 0
ld hl, (ayRegisters + 11) ; envelope generator frequency
ld a, h
or a
jr nz, .l13
ld a, MIN_ENV_FREQVAL
cp l
jr c, .l13
ld l, a ; limit envelope frequency
.l13: ld (envelopeInterrupt.l2 + 1), hl
pop hl
pop bc
pop af
ret
else
jr .l8
endif
Ez csak tárolja az új frekvenciát az IRQ rutinban egy 'LD BC, n' utasítás paraméterének, de egyben korlátozza is egy minimális értékre, mert a túl gyors burkológörbe problémákat okozhatna.
Az utolsó írható regiszter pedig a burkológörbe mód és újraindítás:
.l16:
if NO_ENVELOPE_IRQ == 0
di ; envelope generator mode / restart
ld hl, (envelopeInterrupt.l2 + 1)
ld (envelopeInterrupt.l1 + 1), hl
ld a, 38h ; = JR C, +nn
ld (envelopeInterrupt.l2 - 2), a ; enable envelope
ld a, (ayRegisters + 13)
or low envelopeModeTable
ld l, a
ld h, high envelopeModeTable
and 04h
ld a, (hl)
ld (envelopeInterrupt.l5 + 1), a
ld hl, 3c00h ; INC A, state = 0
jr nz, .l17 ; attack ?
ld hl, 3d0fh ; DEC A, state = 15
.l17: ld (envelopeInterrupt.l3 + 1), hl ; assume eInt.l4 = eInt.l3 + 2
ld a, l
or low ayVolumeTable
ld l, a
jp envelopeInterrupt.l7
else
jr .l8
endif
Itt a következők történnek:
- a frekvencia számláló inicializálása a frekvencia értékkel
- a burkológörbe engedélyezése (ha korábban már lefutott a burkológörbe, akkor - a CPU fogyasztás csökkentése céljából - egy feltételes JR utasítás feltétel nélkülire változott, amit most vissza kell állítani)
- a burkológörbe mód beállítása, amely egy JR utasítás módosítását jelenti ugrótáblázat alapján
- a kezdeti hangerő (15 vagy 0) és irány (DEC A vagy INC A) beállítása az 'attack' bittől függően
- a DAVE hangerő regisztereinek beállítása - ezt takarékos módon a kód az IRQ rutinba való beleugrással oldja meg, amely egyben a POP és EI műveleteket is elvégzi
Természetesen ezek közben a megszakítások tiltottak, hogy az IRQ rutint ne zavarja meg a futása közben a sok kód módosítás.
Az ugrótáblázat, amely 16 byte hosszú és 16 byte-ra igazított, a burkológörbe módokhoz:
envelopeModeTable:
defb low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l19 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l20 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l17 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l19 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l17 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l20 - (envelopeInterrupt.l5 + 2))
defb low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))