Welcome, Guest. Please login or register.


Author Topic: Cybernoid (Read 13686 times)

Online Zozosoft

  • Global Moderator
  • EP addict
  • *
  • Posts: 14723
  • Country: hu
    • http://enterprise.iko.hu/
Re: Cybernoid
« Reply #60 on: 2010.May.13. 14:39:50 »
A legutóbb berakott funkcióbillentyûs fejlesztésnél pontosan miket is kapcsolgatunk?

Offline geco

  • EP addict
  • *
  • Posts: 7082
  • Country: hu
    • Támogató Támogató
Re: Cybernoid
« Reply #61 on: 2010.May.13. 14:48:14 »
Értettem, és köszönöm :)

Offline IstvanV

  • EP addict
  • *
  • Posts: 4822
Re: Cybernoid
« Reply #62 on: 2010.May.13. 16:23:40 »
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):

Code: ZiLOG Z80 Assembler
  1.     macro ayVolTableMacro
  2. ayVolumeTable:
  3.         defb   0,  1,  2,  3,  4,  5,  6,  9
  4.         defb  12, 17, 22, 28, 36, 44, 53, 63
  5.     endm
  6.  
  7.         align 16
  8.  
  9. ayTablesBegin:
  10.  
  11.     if (ayTablesBegin & 0030h) != 0010h
  12.         ayVolTableMacro
  13.  
  14.         assert  ($ & 000fh) == 0
  15.     endif
  16.  
  17. ayRegisterMaskTable:
  18.         defb  0ffh, 00fh, 0ffh, 00fh, 0ffh, 00fh, 01fh, 0ffh
  19.         defb  01fh, 01fh, 01fh, 0ffh, 0ffh, 00fh, 0ffh, 0ffh
  20.  
  21.         assert  ($ & 000fh) == 0
  22.  
  23. ayRegWriteTable:
  24.         defb  low (ayRegisterWrite.l3 - (ayRegisterWrite.l1 + 2))
  25.         defb  low (ayRegisterWrite.l3 - (ayRegisterWrite.l1 + 2))
  26.         defb  low (ayRegisterWrite.l4 - (ayRegisterWrite.l1 + 2))
  27.         defb  low (ayRegisterWrite.l4 - (ayRegisterWrite.l1 + 2))
  28.         defb  low (ayRegisterWrite.l6 - (ayRegisterWrite.l1 + 2))
  29.         defb  low (ayRegisterWrite.l6 - (ayRegisterWrite.l1 + 2))
  30.         defb  low (ayRegisterWrite.l7 - (ayRegisterWrite.l1 + 2))
  31.         defb  low (ayRegisterWrite.l5 - (ayRegisterWrite.l1 + 2))
  32.         defb  low (ayRegisterWrite.l9 - (ayRegisterWrite.l1 + 2))
  33.         defb  low (ayRegisterWrite.l10 - (ayRegisterWrite.l1 + 2))
  34.         defb  low (ayRegisterWrite.l11 - (ayRegisterWrite.l1 + 2))
  35.         defb  low (ayRegisterWrite.l12 - (ayRegisterWrite.l1 + 2))
  36.         defb  low (ayRegisterWrite.l12 - (ayRegisterWrite.l1 + 2))
  37.         defb  low (ayRegisterWrite.l16 - (ayRegisterWrite.l1 + 2))
  38.         defb  low (ayRegisterWrite.l8 - (ayRegisterWrite.l1 + 2))
  39.         defb  low (ayRegisterWrite.l8 - (ayRegisterWrite.l1 + 2))
  40.  
  41.         assert  ($ & 000fh) == 0
  42.  
  43. ayRegisters:
  44.         defb  00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
  45.         defb  00h, 00h, 00h, 00h, 00h, 00h, 00h, 00h
  46.  
  47.         assert  ($ & 000fh) == 0
  48.  
  49.     if (ayTablesBegin & 0030h) == 0010h
  50.         ayVolTableMacro
  51.  
  52.         assert  ($ & 000fh) == 0
  53.     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   ayRegisters


A 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):
Code: ZiLOG Z80 Assembler
  1. ayRegisterRead:
  2.         and   0fh
  3.         or    low ayRegisters
  4.         ld    (.l1 + 1), a
  5. .l1:    ld    a, (ayRegisters)          ; *
  6.         or    a
  7.         ret

Az írás természetesen már bonyolultabb:
Code: ZiLOG Z80 Assembler
  1. ayRegisterWrite:
  2.         push  af
  3.         push  bc
  4.         push  hl
  5.         and   0fh
  6.         or    low ayRegisterMaskTable
  7.         ld    l, a
  8.         ld    h, high ayRegisterMaskTable
  9.     if ayRegisters == (ayRegisterMaskTable | 0020h)
  10.         ld    a, c
  11.         and   (hl)
  12.         set   5, l
  13.     else
  14.       if ((ayRegisters ^ ayRegisterMaskTable) & 0ff00h) == 0
  15.         add   a, low (ayRegisters - ayRegisterMaskTable)
  16.         ld    b, a
  17.         ld    a, c
  18.         and   (hl)
  19.         ld    l, b
  20.       else
  21.         ld    a, c
  22.         and   (hl)
  23.         ld    bc, ayRegisters - ayRegisterMaskTable
  24.         add   hl, bc
  25.       endif
  26.     endif
  27.         cp    (hl)
  28.         jr    z, .l2                    ; register not changed ?
  29.         ld    (hl), a
  30.     if ayRegWriteTable == (ayRegisters & 0ffefh)
  31.         res   4, l
  32.     else
  33.         ld    a, l
  34.         xor   low (ayRegWriteTable ^ ayRegisters)
  35.         ld    l, a
  36.     endif
  37.         ld    a, (hl)
  38.         ld    (.l1 + 1), a
  39. .l1:    jr    .l8                       ; *
  40. .l2:    ld    a, l
  41.         xor   low (ayRegisters + 13)
  42.         jr    z, .l16                   ; envelope restart ?
  43.         pop   hl
  44.         pop   bc
  45.         pop   af
  46.         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:

Code: ZiLOG Z80 Assembler
  1. .l3:    call  setChannelAFreq           ; tone generator A frequency
  2.         pop   hl
  3.         pop   bc
  4.         pop   af
  5.         ret
  6. .l4:    call  setChannelBFreq           ; tone generator B frequency
  7.         pop   hl
  8.         pop   bc
  9.         pop   af
  10.         ret
  11. .l5:    call  setChannelAFreq           ; mixer
  12.         call  setChannelBFreq
  13. .l6:    call  setChannelCFreq           ; tone generator C frequency
  14.         pop   hl
  15.         pop   bc
  16.         pop   af
  17.         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:
Code: ZiLOG Z80 Assembler
  1. setChannelAFreq:
  2.         ld    c, 0a0h
  3.         ld    a, (ayRegisters + 7)
  4.         ld    hl, (ayRegisters)
  5.         jp    setChannelFrequency
  6.  
  7. setChannelBFreq:
  8.         ld    c, 0a2h
  9.         ld    a, (ayRegisters + 7)
  10.         rrca
  11.         ld    hl, (ayRegisters + 2)
  12.         jp    setChannelFrequency
  13.  
  14. setChannelCFreq:
  15.         ld    c, 0a4h
  16.         ld    a, (ayRegisters + 7)
  17.         rrca
  18.         rrca
  19.         ld    hl, (ayRegisters + 4)
  20.  
  21. setChannelFrequency:
  22.     if TONE_AND_NOISE_MODE == 0
  23.         rrca
  24.         jr    nc, setToneGenFrequency   ; tone generator enabled ?
  25.         and   04h
  26.         jr    z, setNoiseGenFreq        ; noise generator enabled ?
  27.     endif
  28.     if TONE_AND_NOISE_MODE == 1
  29.         bit   3, a
  30.         jr    z, setNoiseGenFreq        ; noise generator enabled ?
  31.         rrca
  32.         jr    nc, setToneGenFrequency   ; tone generator enabled ?
  33.     endif
  34.     if TONE_AND_NOISE_MODE > 15
  35.         and   09h
  36.         jr    z, setToneGenAsNoise      ; tone + noise generator enabled ?
  37.         srl   a
  38.         jr    nc, setToneGenFrequency   ; tone generator only ?
  39.         jr    z, setNoiseGenFreq        ; noise generator only ?
  40.     endif
  41.         xor   a                         ; channel disabled
  42.         out   (c), a
  43.         inc   c
  44.         out   (c), a
  45.         ret
  46.  
  47.     if TONE_AND_NOISE_MODE > 15
  48. setToneGenAsNoise:
  49.         ld    a, TONE_AND_NOISE_MODE
  50.         defb  0feh                      ; = CP nn
  51.     endif
  52.  
  53. setToneGenFrequency:
  54.     if TONE_AND_NOISE_MODE > 15
  55.         xor   a
  56.         ld    (.l1 + 1), a
  57.     endif
  58.         ld    b, h
  59.         ld    a, l
  60.         sra   b
  61.         rra
  62.         sra   b
  63.         rra
  64.         sra   b
  65.         rra
  66.         adc   a, l
  67.         ld    l, a
  68.         dec   hl
  69.         ld    a, b
  70.         adc   a, h
  71.         cp    10h
  72.         jr    nc, .l3                   ; overflow ?
  73. .l1:
  74.     if TONE_AND_NOISE_MODE > 15
  75.         or    00h                       ; * non-zero for tone + noise
  76.     endif
  77. .l2:    out   (c), l
  78.         inc   c
  79.         out   (c), a
  80.         ret
  81. .l3:    inc   l
  82.         inc   a
  83.         jr    z, .l1
  84.         ld    l, 0ffh
  85.         ld    a, 0fh
  86.         jp    .l1
  87.  
  88. setNoiseGenFreq:
  89.         ld    a, (ayRegisters + 6)
  90.         cp    1
  91.         adc   a, 0
  92.         ld    h, a
  93.         rra
  94.         sla   h
  95.         rra
  96.         adc   a, h
  97.         dec   a
  98.         out   (c), a
  99.         inc   c
  100.         ld    a, 30h
  101.         out   (c), a
  102.         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:

Code: ZiLOG Z80 Assembler
  1. .l7:    ld    a, (ayRegisters + 7)      ; noise generator frequency
  2.     if TONE_AND_NOISE_MODE != 1
  3.         xor   07h
  4.         ld    b, a
  5.         and   09h
  6.     else
  7.         ld    b, a
  8.         and   08h
  9.     endif
  10.         call  z, setChannelAFreq
  11.     if TONE_AND_NOISE_MODE != 1
  12.         ld    a, b
  13.         and   12h
  14.     else
  15.         bit   4, b
  16.     endif
  17.         call  z, setChannelBFreq
  18.     if TONE_AND_NOISE_MODE != 1
  19.         ld    a, b
  20.         and   24h
  21.     else
  22.         bit   5, b
  23.     endif
  24.         call  z, setChannelCFreq
  25. .l8:    pop   hl
  26.         pop   bc
  27.         pop   af
  28.         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:

Code: ZiLOG Z80 Assembler
  1. .l9:    ld    a, (ayRegisters + 8)      ; channel A amplitude / envelope enable
  2.         ld    c, 0a8h
  3.         jp    setChannelAmplitude
  4. .l10:   ld    a, (ayRegisters + 9)      ; channel B amplitude / envelope enable
  5.         ld    c, 0a9h
  6.         jp    setChannelAmplitude
  7. .l11:   ld    a, (ayRegisters + 10)     ; channel C amplitude / envelope enable
  8.     if ENABLE_STEREO == 0
  9.         ld    c, 0aah
  10.     else
  11.         ld    c, 0aeh
  12.     endif
  13.         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:

Code: ZiLOG Z80 Assembler
  1. setChannelAmplitude:
  2.     if NO_ENVELOPE_IRQ == 0
  3.         di
  4.     endif
  5.         cp    10h
  6.         jr    c, .l1
  7.     if NO_ENVELOPE_IRQ == 0
  8.         ld    a, (envelopeInterrupt.l3 + 1)
  9.     else
  10.         xor   a
  11.     endif
  12. .l1:    or    low ayVolumeTable
  13.         ld    l, a                      ; H = high ayRegWriteTable
  14.     if ((ayVolumeTable ^ ayRegWriteTable) & 0ff00h) != 0
  15.       if ayVolumeTable > ayRegWriteTable
  16.         inc   h
  17.       else
  18.         dec   h
  19.       endif
  20.     endif
  21.         ld    a, (hl)
  22.     if ENABLE_STEREO != 0
  23.         bit   0, c
  24.         jr    z, .l2
  25.         ld    b, a
  26.         srl   b
  27.         srl   b
  28.         sub   b
  29.     endif
  30.         out   (c), a
  31.         set   2, c
  32. .l2:    out   (c), a
  33.     if NO_ENVELOPE_IRQ == 0
  34.         ld    hl, (ayRegisters + 8)
  35.         ld    a, (ayRegisters + 10)
  36.         ld    bc, 70h + (envelopeEnableTable & 0ff00h)
  37.         and   c
  38.         add   a, a
  39.         or    h
  40.         and   c
  41.         add   a, a
  42.         or    l
  43.         and   c
  44.         rrca
  45.         rrca
  46.         rrca
  47.         rrca
  48.         or    low envelopeEnableTable
  49.         ld    c, a
  50.         ld    a, (bc)
  51.         ld    (envelopeInterrupt.l8 - 1), a
  52.     endif
  53.         pop   hl
  54.         pop   bc
  55.         pop   af
  56.     if NO_ENVELOPE_IRQ == 0
  57.         ei
  58.     endif
  59.         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:
Code: ZiLOG Z80 Assembler
  1. envelopeEnableTable:
  2.         defb  low (envelopeInterrupt.l9 - envelopeInterrupt.l8)
  3.         defb  low (envelopeInterrupt.l8 - envelopeInterrupt.l8)
  4.         defb  low (envelopeInterrupt.l11 - envelopeInterrupt.l8)
  5.         defb  low (envelopeInterrupt.l10 - envelopeInterrupt.l8)
  6.         defb  low (envelopeInterrupt.l13 - envelopeInterrupt.l8)
  7.         defb  low (envelopeInterrupt.l12 - envelopeInterrupt.l8)
  8.         defb  low (envelopeInterrupt.l15 - envelopeInterrupt.l8)
  9.         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:

Code: ZiLOG Z80 Assembler
  1. .l12:
  2.     if NO_ENVELOPE_IRQ == 0
  3.         ld    hl, (ayRegisters + 11)    ; envelope generator frequency
  4.         ld    a, h
  5.         or    a
  6.         jr    nz, .l13
  7.         ld    a, MIN_ENV_FREQVAL
  8.         cp    l
  9.         jr    c, .l13
  10.         ld    l, a                      ; limit envelope frequency
  11. .l13:   ld    (envelopeInterrupt.l2 + 1), hl
  12.         pop   hl
  13.         pop   bc
  14.         pop   af
  15.         ret
  16.     else
  17.         jr    .l8
  18.     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:

Code: ZiLOG Z80 Assembler
  1. .l16:
  2.     if NO_ENVELOPE_IRQ == 0
  3.         di                              ; envelope generator mode / restart
  4.         ld    hl, (envelopeInterrupt.l2 + 1)
  5.         ld    (envelopeInterrupt.l1 + 1), hl
  6.         ld    a, 38h                    ; = JR C, +nn
  7.         ld    (envelopeInterrupt.l2 - 2), a     ; enable envelope
  8.         ld    a, (ayRegisters + 13)
  9.         or    low envelopeModeTable
  10.         ld    l, a
  11.         ld    h, high envelopeModeTable
  12.         and   04h
  13.         ld    a, (hl)
  14.         ld    (envelopeInterrupt.l5 + 1), a
  15.         ld    hl, 3c00h                 ; INC A, state = 0
  16.         jr    nz, .l17                  ; attack ?
  17.         ld    hl, 3d0fh                 ; DEC A, state = 15
  18. .l17:   ld    (envelopeInterrupt.l3 + 1), hl    ; assume eInt.l4 = eInt.l3 + 2
  19.         ld    a, l
  20.         or    low ayVolumeTable
  21.         ld    l, a
  22.         jp    envelopeInterrupt.l7
  23.     else
  24.         jr    .l8
  25.     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:
Code: ZiLOG Z80 Assembler
  1. envelopeModeTable:
  2.         defb  low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
  3.         defb  low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
  4.         defb  low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
  5.         defb  low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
  6.         defb  low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
  7.         defb  low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
  8.         defb  low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
  9.         defb  low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
  10.         defb  low (envelopeInterrupt.l19 - (envelopeInterrupt.l5 + 2))
  11.         defb  low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
  12.         defb  low (envelopeInterrupt.l20 - (envelopeInterrupt.l5 + 2))
  13.         defb  low (envelopeInterrupt.l17 - (envelopeInterrupt.l5 + 2))
  14.         defb  low (envelopeInterrupt.l19 - (envelopeInterrupt.l5 + 2))
  15.         defb  low (envelopeInterrupt.l17 - (envelopeInterrupt.l5 + 2))
  16.         defb  low (envelopeInterrupt.l20 - (envelopeInterrupt.l5 + 2))
  17.         defb  low (envelopeInterrupt.l18 - (envelopeInterrupt.l5 + 2))
« Last Edit: 2010.May.13. 16:35:48 by IstvanV »

Offline IstvanV

  • EP addict
  • *
  • Posts: 4822
Re: Cybernoid
« Reply #63 on: 2010.May.13. 16:25:20 »
Leírás az exolon.s-ben található Spectrum 128-as AY emulátor kódhoz (2. rész):

A burkológörbe emuláció 1 kHz-es megszakításból történik. Ennek a belépési pontja 0038h címen van:

Code: ZiLOG Z80 Assembler
  1.         phase 0038h
  2.  
  3.         push  af
  4.         in    a, (0b4h)
  5.         and   02h
  6.         jp    nz, envelopeInterrupt
  7.         ld    a, 31h
  8.         out   (0b4h), a
  9.         ei
  10.         jp    0000h
  11.  
  12.         dephase

Itt a B4h port 1. bitje alapján dönthető el, hogy 1 kHz-es megszakítás történt-e. Ha igen, akkor azonnal ugrik a burkológörbe emulátor rutinra. Egyébként törli a video megszakítást (5. bit), engedélyezi a video (4. bit) és 1 kHz-es (0. bit) megszakítást, majd a játék megszakítási rutinjára ugrik (a szinte biztosan az elején található PUSH AF utasítást átlépve). Előbb azonban engedélyezi a megszakításokat, hogy az esetleg lassú rutin futása közben is tovább működhessen a burkológörbe emuláció. A JP utasításhoz természetesen az adott játéktól függő címet kell írni a 0000h helyére.

Következik a burkológörbe emulátor rutin, amely így kezdődik:

Code: ZiLOG Z80 Assembler
  1. envelopeInterrupt:
  2.         ld    a, 13h
  3.         out   (0b4h), a
  4.     if ENV_SRATE_DIV > 1
  5. .lsrd1: ld    a, 0                      ; *
  6.         add   a, (256 + (ENV_SRATE_DIV / 2)) / ENV_SRATE_DIV
  7.         ld    (.lsrd1 + 1), a
  8.         jr    c, .lsrd2
  9.         pop   af
  10.         ei
  11.         ret
  12. .lsrd2:
  13.     endif

Törli az 1 kHz-es megszakítást (1. bit), és engedélyezi a video (4. bit) és 1 kHz-es (0. bit) megszakítást. Lehetőség van az 1 kHz-es megszakítás leosztására is, amellyel CPU idő takarítható meg rosszabb minőség árán.

Code: ZiLOG Z80 Assembler
  1.         push  bc
  2.         push  hl
  3. .l1:    ld    hl, 0000h                 ; * envelope counter
  4.         ld    bc, 010000h - (((110840 * ENV_SRATE_DIV) + 500) / 1000)
  5.         add   hl, bc
  6.         jr    c, .l16                   ; * JR if envelope is stopped
  7. .l2:    ld    bc, 0ffffh                ; * envelope frequency
  8. .l3:    ld    a, 0                      ; * envelope state (0 to 15)
  9. .l4:    dec   a                         ; * envelope direction (INC A or DEC A)
  10.         cp    10h
  11. .l5:    jr    nc, .l18                  ; * envelope mode
  12. .l6:    add   hl, bc
  13.         jr    nc, .l4
  14.         ld    (.l3 + 1), a
  15.         ld    (.l1 + 1), hl
  16.         or    low ayVolumeTable
  17.         ld    l, a
  18. .l7:    ld    h, high ayVolumeTable
  19.         ld    a, (hl)
  20.         pop   hl
  21.         jr    .l9                       ; * envelope enable mode
  22. .l8:    ...
  23. .l16:   ld    (.l1 + 1), hl
  24.         pop   hl
  25.         pop   bc
  26.         pop   af
  27.         ei
  28.         ret

Itt az .l1-nél található számláló minden burkológörbe megszakításnál csökken. A 110840 az AY frekvenciák számításánál használt konstans (ami 62500 CPC-n), az 1000 az emuláció "órajele" osztás előtt, az 500 pedig annak a fele kerekítéshez. CPC-nél a 300 Hz-es megszakítást használva ezeket át kell írni 300-ra és 150-re. Ha a számláló nem csordul túl (vagy ha a burkológörbe már lefutott és a JR C utasítás helyére JR került), akkor .l16-nál az új érték egyszerűen tárolódik, és a rutin visszatér.
Túlcsordulás esetén a számláló újratöltésére, és a burkológörbe állapot (0-15) frissítésére kerül sor. Érdemes megfigyelni, hogy nagy burkológörbe frekvenciánál a kód egyszerre többet is tud lépni a burkológörbe állapoton; ezért van a frekvencia korlátozása, hogy ez a ciklus ne lassulhasson le nagyon. Ha a burkológörbe állapot is túlcsordul, akkor a módtól függően ezek történhetnek:

Code: ZiLOG Z80 Assembler
  1. .l17:   ld    l, low (ayVolumeTable + 15)       ; envelope modes 11 and 13
  2.         defb  01h                       ; = LD BC, nnnn
  3. .l18:   ld    l, low ayVolumeTable      ; envelope modes 0 to 7, 9, and 15
  4.         ld    a, 18h                    ; = JR +nn
  5.         ld    (.l2 - 2), a              ; stop envelope
  6.         jp    .l7
  7. .l19:   and   0fh                       ; envelope modes 8 and 12
  8.         jp    .l6
  9. .l20:   ld    a, (.l4)                  ; envelope modes 10 and 14
  10.         rrca
  11.         jr    c, .l21                   ; direction was DEC A ?
  12.         ld    a, 3dh
  13.         ld    (.l4), a                  ; direction = DEC A
  14.         ld    a, 15
  15.         jp    .l6
  16. .l21:   ld    a, 3ch
  17.         ld    (.l4), a                  ; direction = INC A
  18.         xor   a
  19.         jp    .l6

Az új kimenetnek megfelelően a DAVE hangerő regisztereket is be kell állítani, tehát a táblázatból olvasott konvertált hangerő a 8-9. regiszterekben beállított módtól függően a DAVE A8h-AFh portjaira kerülhet:

Code: ZiLOG Z80 Assembler
  1. .l8:    out   (0a8h), a                 ; envelope on channel A only
  2.     if ENABLE_STEREO == 0
  3.         out   (0ach), a
  4.     endif
  5. .l9:    pop   bc
  6.         pop   af
  7.         ei
  8.         ret
  9. .l10:   out   (0a8h), a                 ; envelope on channels A and B
  10.     if ENABLE_STEREO == 0
  11.         out   (0ach), a
  12.     endif
  13. .l11:
  14.     if ENABLE_STEREO != 0
  15.         ld    c, a                      ; envelope on channel B only
  16.         srl   c
  17.         srl   c
  18.         sub   c
  19.     endif
  20.         out   (0a9h), a
  21.         out   (0adh), a
  22.         pop   bc
  23.         pop   af
  24.         ei
  25.         ret
  26. .l12:   out   (0a8h), a                 ; envelope on channels A and C
  27.     if ENABLE_STEREO == 0
  28.         out   (0ach), a
  29.     endif
  30. .l13:
  31.     if ENABLE_STEREO == 0
  32.         out   (0aah), a
  33.     endif
  34.         out   (0aeh), a                 ; envelope on channel C only
  35.         pop   bc
  36.         pop   af
  37.         ei
  38.         ret
  39. .l14:   out   (0a8h), a                 ; envelope on channels A, B, and C
  40.     if ENABLE_STEREO == 0
  41.         out   (0ach), a
  42.     endif
  43. .l15:
  44.     if ENABLE_STEREO == 0
  45.         out   (0aah), a
  46.     endif
  47.         out   (0aeh), a                 ; envelope on channels B and C
  48.     if ENABLE_STEREO != 0
  49.         ld    c, a
  50.         srl   c
  51.         srl   c
  52.         sub   c
  53.     endif
  54.         out   (0a9h), a
  55.         out   (0adh), a
  56.         pop   bc
  57.         pop   af
  58.         ei
  59.         ret

Végül az AY emuláció inicializálása:

Code: ZiLOG Z80 Assembler
  1. ayReset:
  2.         di
  3.         ld    hl, ayRegisters - 1
  4.         ld    bc, 10afh
  5.         xor   a
  6. .l1:    inc   hl
  7.         out   (c), a
  8.         ld    (hl), a
  9.         dec   c
  10.         djnz  .l1
  11.         res   3, l                      ; register 7
  12.         ld    (hl), 3fh
  13.     if NO_ENVELOPE_IRQ == 0
  14.         ld    (envelopeInterrupt.l3 + 1), a
  15.         ld    a, 18h                    ; = JR +nn
  16.         ld    (envelopeInterrupt.l2 - 2), a
  17.         ld    hl, MIN_ENV_FREQVAL
  18.         ld    (envelopeInterrupt.l2 + 1), hl
  19.         ld    a, low (envelopeInterrupt.l9 - envelopeInterrupt.l8)
  20.         ld    (envelopeInterrupt.l8 - 1), a
  21.         ld    hl, irqCodeBegin
  22.         ld    de, 0038h
  23.         ld    c, low (irqCodeEnd - irqCodeBegin)
  24.         ldir
  25.     endif
  26.         ld    a, 0ch
  27.         out   (0bfh), a
  28.         ld    a, 10h
  29.         out   (0a6h), a                 ; use 17-bit noise generator
  30.     if NO_ENVELOPE_IRQ == 0
  31.         ld    a, 33h
  32.         out   (0b4h), a                 ; enable 1 kHz and video interrupts
  33.         ei
  34.     endif
  35.         ret

Itt az A6h portra írt 10h-t érdemes említeni, amely felcseréli a 7 és 17 bites polinom számlálókat. Így a DAVE első három csatornáján az utóbbi használható.

A CPC-s betöltőben az AY emulátor kissé eltér a frekvencia konverzió módosítása és a 300 Hz-es megszakítás használatának lehetősége miatt. Ezen kívül ott van olyan rutin, amely mind a 14 regisztert írja egyszerre egy táblázatból - ez gyorsabb, mint az ayRegisterWrite-ot 14-szer hívni, és a CPC-s programok gyakran használnak ilyen megoldást 50 Hz-es megszakításból.
« Last Edit: 2010.May.13. 16:51:03 by IstvanV »

Online Zozosoft

  • Global Moderator
  • EP addict
  • *
  • Posts: 14723
  • Country: hu
    • http://enterprise.iko.hu/
Re: Cybernoid
« Reply #64 on: 2010.May.13. 16:44:34 »
Ezen kívül ott van olyan rutin, amely mind a 14 regisztert írja egyszerre egy táblázatból - ez gyorsabb, mint az ayRegisterWrite-ot 14-szer hívni, és a CPC-s programok gyakran használnak ilyen megoldást 50 Hz-es megszakításból.
Ilyen rutin a Spectrumos-os verzióhoz is kéne, ott is gyakran csinálnak ilyet a programok!

Offline IstvanV

  • EP addict
  • *
  • Posts: 4822
Re: Cybernoid
« Reply #65 on: 2010.May.13. 16:47:43 »
Ilyen rutin a Spectrumos-os verzióhoz is kéne, ott is gyakran csinálnak ilyet a programok!

Szerintem azt minimális változtatással (eltérő PUSH utasítások az .l3 után) be lehet másolni a cybrnoid.s-ből.

Online Zozosoft

  • Global Moderator
  • EP addict
  • *
  • Posts: 14723
  • Country: hu
    • http://enterprise.iko.hu/
Re: Cybernoid
« Reply #66 on: 2010.May.18. 23:33:42 »
Játszogattam az új Cybernoidokkal, sokkal szebb mint a Spectrumos. Kár, hogy anno nem volt ismert a CPC nálunk, sokkal szebb átiratok születhettek volna.