; Intellivision emulator AY-8914 process code (handles up to two chips
; for ECS support)
;
; Note: the macros support any number of sound chips, but the data
; structures as defined in INTVSND.PAS and here (and the code in the
; ProcessSound procedure) only support two chips!

.486
.MODEL FLAT

        AY_AFINE    EQU    0
        AY_BFINE    EQU    1
        AY_CFINE    EQU    2
        AY_EFINE    EQU    3
        AY_ACOARSE  EQU    4
        AY_BCOARSE  EQU    5
        AY_CCOARSE  EQU    6
        AY_ECOARSE  EQU    7

        AY_ENABLE   EQU    8
        AY_NOISEPER EQU    9
        AY_ESHAPE   EQU    10

        AY_AVOL     EQU    11
        AY_BVOL     EQU    12
        AY_CVOL     EQU    13

        AY_PORTA    EQU    14
        AY_PORTB    EQU    15

.DATA

        extrn _AYEnvForms     : byte  ; Array[0..15,0..31] Of Integer = (...);
        extrn VolScale        : byte  ; Array[0..15,0..31] Of Byte;

        extrn DToA            : byte  ; Array[0..15] Of Byte;
        extrn AYRegs          : byte  ; Array[0..18,0..MaxNumChips - 1] Of Word;

        extrn Incr0           : byte  ; Array[0..MaxNumChips - 1] Of Integer;
        extrn Incr1           : byte  ; Array[0..MaxNumChips - 1] Of Integer;
        extrn Incr2           : byte  ; Array[0..MaxNumChips - 1] Of Integer;
        extrn IncrEnv         : byte  ; Array[0..MaxNumChips - 1] Of LongInt;
        extrn IncrNoise       : byte  ; Array[0..MaxNumChips - 1] Of LongInt;
        extrn Counter0        : byte  ; Array[0..MaxNumChips - 1] Of Integer;
        extrn Counter1        : byte  ; Array[0..MaxNumChips - 1] Of Integer;
        extrn Counter2        : byte  ; Array[0..MaxNumChips - 1] Of Integer;
        extrn CountEnv        : byte  ; Array[0..MaxNumChips - 1] Of LongInt;
        extrn CountNoise      : byte  ; Array[0..MaxNumChips - 1] Of LongInt;
        extrn NoiseGen        : byte  ; Array[0..MaxNumChips - 1] Of LongInt;
        extrn Envelope        : byte  ; Array[0..MaxNumChips - 1] Of Integer;
        extrn StateNoise      : byte  ; Array[0..MaxNumChips - 1] Of Byte;
        extrn NumChips        : byte  ; Integer;

        extrn VoiceBuf  : byte

        _VolTemp   DD 0          ; 3 bytes; fourth is unused (each byte will hold 0..31)
        _Volume    DD 0          ; 2 words, 1 for each chip
        _VolNoise  DD 0          ; 2 words, 1 for each chip
        _Vol       DW 3 DUP (0)  ; 3 words

        _Buffer    DD 0
        _N         DD 0
        _VCnt      DD 0
.CODE

public ProcessSound

; ------------------------------------------------------

LoadEnv macro Reg,Chip,Mask1,Mask2
        local LE2,CE11,CE21

  ; If AYRegs[AY_AVOL + Reg,Chip] < 16 Then Vol0 := AYRegs[AY_AVOL,Chip] * 2 Else Vol0 := Envelope[Chip];

        MOV   DX,WORD PTR AYRegs[(AY_AVOL + Reg) * 4 + Chip * 2]
        ADD   DX,DX
        CMP   DX,32
        JB    LE2
        MOV   DX,WORD PTR Envelope[Chip * 2]
LE2:

  ; If (AYRegs[AY_ENABLE,Chip] And Mask1) <> 0 Then VolTemp0 := 0 Else VolTemp0 := Vol0;

        MOV   CX,WORD PTR AYRegs[AY_ENABLE * 4 + Chip * 2]
        MOV   AL,DL
        TEST  CX,Mask1
        JZ    CE11
        SUB   AL,AL
CE11:
        MOV   BYTE PTR _VolTemp[Reg],AL

  ; If (AYRegs[AY_ENABLE,Chip] And Mask2) <> 0 Then Vol0 := 0;

        TEST  CX,Mask2
        JZ    CE21
        SUB   DX,DX
CE21:
        MOV   WORD PTR _Vol[Reg * 2],DX
        endm

; ------------------------------------------------------

Cycle   macro Reg,Chip
        local CY1,CY3,CY4,CY5

  ; c0 :=  Counter0[Chip,Reg];

        MOV   AX,WORD PTR Counter0[Chip * 2 + Reg * 4]

  ; c1 :=  Counter0[Chip,Reg] + Incr0[Chip,Reg];

        MOV   BX,WORD PTR Incr0[Chip * 2 + Reg * 4]
        ADD   BX,AX

  ; If C0 < 0 Then L0[Chip,Reg] := -16 Else L0[Chip,Reg] := 16;

        MOV   CX,16        ; Save in CX for now
        CMP   AX,0
        JGE   CY1
        NEG   CX
CY1:
  ; Counter0[Chip] := c1 And $FFFF;

        MOV   WORD PTR Counter0[Chip * 2 + Reg * 4],BX

  ; If ((c0 Xor c1) And $8000) <> 0 Then
  ;  L0[Chip,Reg] := Round(L0[Chip,Reg] * ($8000 - (c0 And $7FFF) - (c1 And $7FFF)) / Incr0[Chip,Reg]);

        MOV   DX,AX
        XOR   DX,BX
        JNS   CY3
        AND   AX,7FFFh
        AND   BX,7FFFh
        MOV   SI,8000h
        SUB   SI,AX
        SUB   SI,BX
        MOV   AX,CX        ; Recall CX holds the future L0[Chip,Reg]
        IMUL  SI
        MOV   BX,WORD PTR Incr0[Chip * 2 + Reg * 4]
        IDIV  BX
        MOV   CX,AX
CY3:

  ; If L0[Chip,Reg] < 0
  ;  Then L0[Chip,Reg] := 0
  ;  Else L0[Chip,Reg] := Round(L0[Chip,Reg] * Vol0 / 16);

        TEST  CX,8000h
        JZ    CY4
        SUB   AX,AX
        JMP   CY5
CY4:
        SHL   CX,5
        MOVZX EBX,WORD PTR _Vol[Reg * 2]
        ADD   BX,CX
        MOV   AL,BYTE PTR VolScale[EBX]
CY5:
        ADD   WORD PTR _Volume[Chip * 2],AX

        endm

; ------------------------------------------------------

Process macro Chip
        local CEIsZero,Else1,Case1,NoNoiseWrap,NW1,Noise1

  ; Envelope[Chip] := DToA[_AYEnvForms[AYRegs[AY_ESHAPE,Chip],(CountEnv[Chip] Shr 16) And $1F]];

        MOV   CX,WORD PTR AYRegs[AY_ESHAPE * 4 + Chip * 2]
        MOV   AX,WORD PTR CountEnv[Chip * 4 + 2]
        AND   AX,1Fh
        ADD   AX,AX

        MOVZX EBX,CX
        SHL   BX,6
        ADD   BX,AX

        MOVZX EBX,WORD PTR _AYEnvForms[EBX]
        MOVZX AX,BYTE PTR DToA[EBX]
        MOV   WORD PTR Envelope[Chip * 2],AX

  ; If (CountEnv[Chip] And $FFE00000) <> 0 Then
  ; Begin

        TEST  WORD PTR CountEnv[Chip * 4 + 2],0FFE0h
        JZ    CEIsZero

        CMP   CX,8
        JB    Else1
        TEST  CX,1
        JNZ   Else1
Case1:
  ; Do only if AY_ESHAPE is 8, 10, 12, or 14

        SUB   DWORD PTR CountEnv[Chip * 4],200000h
        JMP   CEIsZero
Else1:
        MOV   DWORD PTR CountEnv[Chip * 4],100000h
        MOV   DWORD PTR IncrEnv[Chip * 4],0

  ; End;

CEIsZero:

        MOV   EAX,DWORD PTR IncrEnv[Chip * 4]
        ADD   DWORD PTR CountEnv[Chip * 4],EAX

        LoadEnv 0,Chip,08h,1

        LoadEnv 1,Chip,10h,2

        LoadEnv 2,Chip,20h,4

  ; VolNoise[Chip] := VolTemp0 + VolTemp1 + VolTemp2;

        SUB   AX,AX
        MOV   AL,BYTE PTR _VolTemp[0]              ; 0..31
        ADD   AL,BYTE PTR _VolTemp[1]              ; 0..31
        ADD   AL,BYTE PTR _VolTemp[2]              ; 0..31
        MOV   WORD PTR _VolNoise[Chip * 2],AX      ; AX holds 0..93

  ; These strange tricks are needed for getting rid
  ; of nasty interferences between sampling frequency
  ; and "rectangular sound" (which is also the output
  ; of real AY-3-8910) we produce.

        Cycle 0,Chip

        Cycle 1,Chip

        Cycle 2,Chip

  ; CountNoise[Chip] := (CountNoise[Chip] And $FFFF) + IncrNoise[Chip];

        MOV   WORD PTR CountNoise[Chip * 4 + 2],0
        MOV   EAX,DWORD PTR IncrNoise[Chip * 4]
        ADD   DWORD PTR CountNoise[Chip * 4],EAX

  ; If (CountNoise[Chip] And $FFFF0000) <> 0 Then
  ; Begin

        TEST  WORD PTR CountNoise[Chip * 4 + 2],0FFFFh
        JZ    NoNoiseWrap

  ; The following code is a random bit generator :)

        SHL   DWORD PTR NoiseGen[Chip * 4],1

  ;   If (NoiseGen[Chip] And $80000000) <> 0 Then
  ;    NoiseGen[Chip] := NoiseGen[Chip] Xor $00040001;

        TEST  BYTE PTR NoiseGen[Chip * 4 + 3],80h
        JZ    NW1
        XOR   DWORD PTR NoiseGen[Chip * 4],40001h
NW1:

  ; Get the LSB of the noise generator and put it into StateNoise

        MOV   AL,BYTE PTR NoiseGen[Chip * 4]
        MOV   BYTE PTR StateNoise[Chip],AL

  ; End;

NoNoiseWrap:

  ; Decide if there is to be noise volume by testing bit 0

        TEST  BYTE PTR StateNoise[Chip],1
        JNZ   Noise1
        MOV   WORD PTR _VolNoise[Chip * 2],0
Noise1:

        endm

; ------------------------------------------------------

ProcessSound proc
        PUSHAD

        ; Retrieve parameters

        MOV   DWORD PTR _Buffer,EAX
        MOV   DWORD PTR _N,EDX
        TEST  EDX,EDX
        JZ    Exiting

        ; Initialize local variables

        MOV   EDI,EAX
        MOV   DWORD PTR _VCnt,0
BufLoop:
        MOV   DWORD PTR _Volume,0
        MOV   DWORD PTR _VolNoise,0
        Process 0
        CMP   WORD PTR NumChips,2
        JB    OnlyOneChip
        Process 1
        JMP   TwoChips
OnlyOneChip:
        MOV   AX,WORD PTR _Volume    ; 0..3*31
        ADD   AX,WORD PTR _VolNoise  ; 0..31
        JMP   DoneMixing
TwoChips:
        MOV   AX,WORD PTR _Volume
        ADD   AX,WORD PTR _VolNoise
        ADD   AX,WORD PTR _Volume[2]
        ADD   AX,WORD PTR _VolNoise[2]
        SHR   AL,1
DoneMixing:

        ; Mix in IntelliVoice samples

        MOV   EBX,DWORD PTR _VCnt
        MOV   BL,BYTE PTR VoiceBuf[EBX]
        SHR   AL,1
        SHR   BL,1
        ADD   AL,BL

        MOV   BYTE PTR [EDI],AL

        INC   EDI
        INC   DWORD PTR _VCnt
        DEC   DWORD PTR _N
        JNZ   BufLoop
Exiting:
        POPAD
        RET
ProcessSound endp

        end

; ----------------------------------------------------------------------
; IntvWin - Mattel Intellivision Emulator
; Copyright (C) 2000 John Dullea
;
;  This program is free software; you can redistribute it and/or modify
;  it under the terms of the GNU General Public License as published by
;  the Free Software Foundation; either version 2 of the License, or
;  (at your option) any later version.
;
;  This program is distributed in the hope that it will be useful,
;  but WITHOUT ANY WARRANTY; without even the implied warranty of
;  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;  GNU General Public License for more details.
;
;  You should have received a copy of the GNU General Public License
;  along with this program; if not, write to the Free Software
;  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
; ----------------------------------------------------------------------
