    DOSSEG
    LOCALS 
    .MODEL SMALL
    .CODE
    .386

    ASSUME  cs:@code, ds:@code

    IDEAL

    INCLUDE "PROMOD.INC"
    
    ;To Initialize:
    ;   DspReset
    ;   TurnOnSpeaker
    ;   SetSampleRate
    ;   CalcForDMA => call whenever song speed changes
    ;
    ;To Start:
    ;   StartTransferDma
    ;
    ;To End:
    ;   mov [QuitDMA],1
    ;   TurnOffSpeaker

    BufferSeg       dw  0
    BufferOffset1   dw  0
    BufferOffset2   dw  0
    Buffer1Size     dw  1000
    Buffer2Size     dw  1000

    StereoOn        db  0       ;0= stereo is OFF.

    LptAddress1     dw  3bch
    LptAddress2     dw  3bch
    BaseAddress     dw  220h
    IntNumber       db  7
    DMANumber       db  1
    DMAPage         dw  83h
    DMAStart        dw  2

    DmaChartPage    dw  87h,83h,82h
    DMAStartChart   db  0,2,6

    WhichBuffer     dw  4

    QuitDma         dw  0

    ;MoreB           db  0,0
    ;IPage           db  0,0,0,0
    ;ILength         dw  1,1,1,1
    ;IOffset         dw  0,0,0,0

STRUC DMAInfo
    Page    dw  ?,?,?,?
    Length  dw  ?,?,?,?
    Offset  dw  ?,?,?,?
    Next    dw  4,4,0,0 ;change 1st to '2' for 2 buffers also 3rd to '6'
    WBuff   dw  3,0,4,0
    Current dw  0       ;0 to 3
ENDS DMAInfo

DMA DMAInfo <>
    
    DmaRoutine      dw  offset TransferDmaMONO
    OldInt          dd  0
    DacBuffOff      dw  0
    DacBuffLen      dw  0

哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪哪
;==-  DMA Interrupt  -==

PROC DMAINT FAR
    pusha
    cli

    mov     dx,[cs:BaseAddress]
    add     dx,0eh   
    in      al,dx                       ;acknowledge interrupt

    cmp     [cs:QuitDma],1
    je      @@TerminateDMA

    mov     bx,[cs:DMA.Current]         ;grab offset

    mov     ax,[cs:bx + DMA.NEXT]       ;set up for next buffer
    mov     [cs:DMA.Current],ax
    
    mov     ah,[BYTE LOW cs:bx + DMA.Page]
    mov     cx,[cs:bx + DMA.Length]
    mov     dx,[cs:bx + DMA.Offset]
    call    [cs:DmaRoutine]

    mov     bx,[cs:DMA.Current]         ;grab offset
    mov     ax,[cs:bx + DMA.WBuff]
    or      ax,ax
    je      @@DontSetBuffer
    mov     [cs:WhichBuffer],ax
@@DontSetBuffer:
    mov     al,20h
    out     20h,al              ;end of hardware interrupt

    popa
    sti
    iret
@@TerminateDMA:
    mov     [cs:QuitDma],0
    call    UnDoRealInt
    call    HaltDMA
    jmp     @@DontSetBuffer
ENDP DMAINT

;======================
;==- Start Transfer -==
;======================

PROC StartTransferDma NEAR
    pusha

    cmp     [cs:StereoOn],0
    je      NoStereo

    mov     ax,130eh        ;turn stereo on
    call    SetMixer
    mov     ax,0ff22h        ;set master volume = FF
    call    SetMixer
    mov     ax,0ff04h        ;set VOC volume = FF
    call    SetMixer

    mov     [cs:DmaRoutine],offset TransferDMASTEREO

NoStereo:
    mov     al,[cs:IntNumber]
    add     al,8
    cbw
    shl     al,2
    mov     bx,ax

    push    es
    sub     ax,ax
    mov     es,ax
    mov     ax,[es:bx]
    mov     [WORD LOW cs:OldInt],ax              ; Grab the current interrupt
    mov     ax,[es:bx+2]
    mov     [WORD HIGH cs:OldInt],ax              ; so we can restore it later
    pop     es
    
    mov     ax,[cs:DMA.Page]
    mov     ah,al
    mov     cx,[cs:DMA.Length]
    mov     dx,[cs:DMA.Offset]
    
    mov     al,5
    out     0ah,al          ;Mask off channel 1
    xor     al,al
    out     0ch,al          ;Clear byte pointer F/F to lowerbyte
    mov     al,49h  
    out     0bh,al          ;Set transfer mode to DAC (ADC = 45h)
    mov     al,dl           ;LSB of BASE_ADDRESS
    out     2,al
    mov     al,dh           ;MSB of BASE_ADDRESS
    out     2,al
    mov     al,ah
    out     83h,al          ;Page Number
    mov     al,cl
    out     3,al            ;LSB of DATA_Length
    mov     al,ch
    out     3,al            ;MSB of DATA_Length
    mov     al,1
    out     0ah,al          ;Enable Channel 1

    mov     ax,offset DMAInt
    call    DoRealInt

    cmp     [cs:StereoOn],0
    jne     @@StartStereo

    mov     al,14h          ;function 14h DMAmode 8-bit DAC
    call    Sendcommand
    mov     al,cl           ;send LSB of DATALENGTH
    call    SendCommand
    mov     al,ch           ;send MSB of DATALENGTH
    call    SendCommand
    popa
    ret

@@StartStereo:
    mov     al,48h          ;data length command
    call    Sendcommand
    mov     al,cl           ;send LSB of DATALENGTH
    call    SendCommand
    mov     al,ch           ;send MSB of DATALENGTH
    call    SendCommand
    mov     al,91h          ;tell it to GO!
    call    SendCommand

    popa
    ret
ENDP StartTransferDma
    
    ;ah= page number
    ;dx= BASE_ADDRESS
    ;cx= DATA_LENGTH (-1)
    ; This function sets up for write only!
PROC TransferDmaSTEREO NEAR
    mov     al,5
    out     0ah,al          ;Mask off channel 1
    xor     al,al
    out     0ch,al          ;Clear byte pointer F/F to lowerbyte
    mov     al,49h  
    out     0bh,al          ;Set transfer mode to DAC (ADC = 45h)_
    mov     al,dl           ;LSB of BASE_ADDRESS
    out     2,al
    mov     al,dh           ;MSB of BASE_ADDRESS
    out     2,al
    mov     al,ah
    out     83h,al          ;Page Number
    mov     al,cl
    out     3,al            ;LSB of DATA_Length
    mov     al,ch
    out     3,al            ;MSB of DATA_Length
    mov     al,1
    out     0ah,al          ;Enable Channel 1

    mov     al,48h          ;data length command
    call    Sendcommand
    mov     al,cl           ;send LSB of DATALENGTH
    call    SendCommand
    mov     al,ch           ;send MSB of DATALENGTH
    call    SendCommand
    mov     al,91h          ;tell it to GO!
    call    SendCommand

    ret
ENDP TransferDMASTEREO

    ;ah= page number
    ;dx= BASE_ADDRESS
    ;cx= DATA_LENGTH (-1)
    ; This function sets up for write only!
PROC TransferDmaMONO NEAR
    mov     al,5
    out     0ah,al          ;Mask off channel 1
    xor     al,al
    out     0ch,al          ;Clear byte pointer F/F to lowerbyte
    mov     al,49h  
    out     0bh,al          ;Set transfer mode to DAC (ADC = 45h)_
    mov     al,dl           ;LSB of BASE_ADDRESS
    out     2,al
    mov     al,dh           ;MSB of BASE_ADDRESS
    out     2,al
    mov     al,ah
    out     83h,al          ;Page Number
    mov     al,cl
    out     3,al            ;LSB of DATA_Length
    mov     al,ch
    out     3,al            ;MSB of DATA_Length
    mov     al,1
    out     0ah,al          ;Enable Channel 1

    mov     al,14h          ;function 14h DMAmode 8-bit DAC
    call    Sendcommand
    mov     al,cl           ;send LSB of DATALENGTH
    call    SendCommand
    mov     al,ch           ;send MSB of DATALENGTH
    call    SendCommand
    ret
ENDP TransferDMAMONO

    ;ah= SR (Time Constant = 256 - 1,000,000/HZ)
PROC SetSampleRate NEAR
    mov     al,40h
    call    SendCommand
    mov     al,ah
    call    SendCommand
    ret
ENDP SetSampleRate

PROC TurnOffSpeaker NEAR
    mov     al,0d3h         ;turn off speaker
    call    Sendcommand
    ret
ENDP TurnOffSpeaker

PROC HaltDMA NEAR
    mov     al,0d0h         ;Halt DMA
    call    SendCommand
    ret
ENDP HaltDma

    ;al = command
PROC SendCommand NEAR
    push    dx
    push    ax
    mov     dx,[cs:BaseAddress]
    add     dx,0ch
sendcommandloop:
    in      al,dx
    test    al,10000000b
    jnz     sendcommandloop
    pop     ax
    out     dx,al
    pop     dx
    ret
ENDP SendCommand

PROC TurnOnSpeaker NEAR
    mov     al,0d1h
    call    SendCommand
    ret
ENDP TurnOnSpeaker

    ;input- none 
    ;output al=0 successful
    ;       al=1 unsuccessful
    ;DESTROYED: ax,dx,cx
PROC DspReset NEAR
    mov     dx,[cs:baseaddress]
    add     dx,06h
    mov     al,1
    out     dx,al
    mov     cx,1000
    push    di
    rep     lodsb   ;wait for at least 3鍿
    pop     di
    xor     al,al
    out     dx,al

    add     dx,8         ;check WhichBuffer (22eh)
    mov     cx,12000
waitforstat:
    in      al,dx
    dec     cx
    je      errorstat
    test    al,10000000b
    jz      waitforstat

    mov     cx,10000
    sub     dx,4         ;(22ah)      
waitforstat2:
    in      al,dx
    dec     cx
    je      errorstat
    cmp     al,0aah
    jne     waitforstat2
    mov     al,0
    ret
errorstat:
    mov     al,1
    ret
ENDP DspReset

;========================
;==- Start Interrupts -==
;========================

    ;cs:ax=location of interrupt
PROC DoRealint NEAR
    pushf                           ; Push flags
    push    bx
    push    cx
    push    dx
    
    mov     dx,ax
    mov     al,[cs:IntNumber]
    add     al,8
    cbw                             ; Convrt byte to word
    shl     ax,2                    ; Shift w/zeros fill
    mov     bx,ax

    push    es
    sub     ax,ax
    mov     es,ax
    mov     [es:bx],dx
    mov     [es:bx+2],cs
    pop     es

    mov     cl,[cs:IntNumber]
    mov     ah,1
    shl     ah,cl                   ; Shift w/zeros fill
    not     ah
    in      al,21h                  ; port 21h, 8259-1 int IMR
    and     al,ah
    out     21h,al                  ; port 21h, 8259-1 int comands

    pop     dx
    pop     cx
    pop     bx
    popf                            ; Pop flags
    ret
ENDP DoRealint
  
PROC UnDoREALint NEAR
    pushf
    pusha
    
    mov     al,[cs:IntNumber]
    add     al,8
    cbw
    shl     ax,2
    mov     di,ax

    push    es
    sub     ax,ax
    mov     es,ax
    mov     ax,[WORD LOW  cs:OldInt]  
    mov     [es:di],ax
    mov     ax,[WORD HIGH cs:OldInt]              
    mov     [es:di+2],ax
    pop     es

    mov     cl,[cs:IntNumber]               ; (=7)
    mov     ah,1
    shl     ah,cl                   ; Shift w/zeros fill
    in      al,21h                  ; port 21h, 8259-1 int IMR
    or      al,ah
    out     21h,al                  ; port 21h, 8259-1 int comands
    popa
    popf                            ; Pop flags
    ret
ENDP UnDoREALint

;STRUC DMAInfo
;    Page    dw  ?,?,?,?
;    Length  dw  ?,?,?,?
;    Offset  dw  ?,?,?,?
;    Next    dw  4,4,0,0 ;change 1st to '2' for 2 buffers also 3rd to '6'
;    Current dw  0       ;0 to 3
;ENDS DMAInfo

PROC CalcForDMA NEAR
    pushad
    push    ds
    mov     ax,cs
    mov     ds,ax
    
    mov     [DMA.Next],4
    movzx   eax,[BufferSeg]
    movzx   edx,[BufferOffset1]      ;offset for part1
    call    MakePage
    
    cmp     cx,[buffer1size]        ;is max less than needed?
    jb      skiplengthset
    mov     cx,[buffer1size]
    jmp     nomoreb1
skiplengthset:
    mov     bx,[buffer1size]
    sub     bx,cx
    mov     [DMA.Next],2
    dec     bx
    mov     [2 + DMA.Length],bx
    mov     [2 + DMA.Offset],0
    mov     [BYTE LOW 2 + DMA.Page],ah
    inc     [BYTE LOW 2 + DMA.Page]
NoMoreb1:
    mov     [BYTE LOW DMA.Page],ah
    mov     [DMA.Offset],dx
    dec     cx
    mov     [DMA.Length],cx

    mov     [4 + DMA.Next],0
    movzx   eax,[BufferSeg]
    movzx   edx,[BufferOffset2]   ;offset for part1
    call    MakePage
    
    cmp     cx,[buffer2size]        ;is max less than needed?
    jb      skiplengthset2
    mov     cx,[buffer2size]
    jmp     nomoreb2
skiplengthset2:
    mov     bx,[buffer2size]
    sub     bx,cx
    mov     [4 + DMA.Next],6
    dec     bx

    mov     [6 + DMA.Length],bx
    mov     [6 + DMA.Offset],0
    mov     [BYTE LOW 6 + DMA.Page],ah
    inc     [BYTE LOW 6 + DMA.Page]
NoMoreb2:
    mov     [BYTE LOW 4 + DMA.Page],ah
    mov     [4 + DMA.Offset],dx
    dec     cx
    mov     [4 + DMA.Length],cx
    
    pop     ds
    popad
    sti
    ret
ENDP CalcForDMA

    ;input: eax=segment
    ;       edx=offset
    ;output: ah=page
    ;        dx=offset
    ;        cx=MAX_LENGTH
PROC MakePage NEAR 
    shl     eax,4
    add     edx,eax         ;dx = 32bit absolute address
    
    ror     edx,16
    mov     ah,dl           ;ah= page
    ror     edx,16

    mov     cx,dx
    neg     cx
    ret
ENDP MakePage
    
    ;al = selection#, ah= value
PROC SetMixer NEAR
    push    ax dx
    mov     dx,224h
    out     dx,al
    inc     dx

    jmp     $+2
    jmp     $+2

    mov     al,ah
    out     dx,al
    pop     dx ax
    ret
ENDP SetMixer

;====
;==== DAC Stuff...
;====

PROC SetUpInterrupt NEAR
    pushad
    push  ds

    mov   ax,0
    mov   ds,ax
    mov   bx,8*4                  ;interrupt 8
    mov   ax,[ds:bx]
    mov   [Word LOW  cs:OldInt],ax
    mov   ax,[ds:bx+2]
    mov   [Word HIGH cs:OldInt],ax

    mov   ax,offset TimerInt
    cmp   [cs:StereoOn],0
    je    @@NoStereo
    mov   ax,offset TimerIntStereo
    shr   [cs:HZ],1
@@NoStereo:    
    cli
    mov   [Word ds:bx],ax
    mov   [ds:bx+2],cs

    mov   al,36h
    out   43h,al                  ; timer program

    mov   eax,1193180
    movzx ebx,[cs:HZ]             ; # of ticks between interrupts
    xor   edx,edx
    div   ebx
                                  ; Clock Freq / HZ
    out   40h,al
    mov   al,ah
    out   40h,al

;    mov   al,5ch
;    out   21h,al
    sti

    pop   ds  
    popad
    ret
ENDP SetUpInterrupt
  
PROC RemoveInterrupt NEAR
    pusha
    push    ds
    
    cli
    mov ax,0
    mov ds,ax
    mov bx,8*4
    mov ax,[Word LOW  cs:OldInt]
    mov [ds:bx],ax
    mov ax,[Word HIGH cs:OldInt]
    mov [ds:bx+2],ax
    mov al,36h
    out 43h,al
    xor al,al
    out 40h,al
    out 40h,al

;    mov   al,0
;    out   21h,al
    sti
    
    pop     ds
    popa
    ret
ENDP  RemoveInterrupt

PROC TimerInt FAR
    pusha
    push    fs
    
    mov     fs,[cs:BufferSeg]
    mov     di,[cs:DacBuffOff]
    
    mov     al,[fs:di]
    mov     dx,[cs:LptAddress1]
    out     dx,al
    
    inc     [cs:DacBuffOff]
    mov     ax,[cs:DacBuffLen]
    cmp     [cs:DacBuffOff],ax
    jb      @@NotOver

;set up for next buffer
    cmp     [cs:WhichBuffer],3
    je      SetDac3
    cmp     [cs:WhichBuffer],5
    je      SetDac3

    mov     [cs:WhichBuffer],3
    mov     ax,[cs:BufferOffset2]
    mov     [cs:DacBuffOff],ax
    add     ax,[cs:Buffer2Size]
    sub     ax,2
    mov     [cs:DacBuffLen],ax
    jmp     @@NotOver

SetDac3:
    mov     [cs:WhichBuffer],4
    mov     ax,[cs:BufferOffset1]
    mov     [cs:DacBuffOff],ax
    add     ax,[cs:Buffer1Size]
    sub     ax,2
    mov     [cs:DacBuffLen],ax

@@NotOver:
    mov     al,20h      ;acknowledge hardware interrupt
    out     20h,al

    pop     fs
    popa
    iret
ENDP TimerInt

PROC TimerIntStereo FAR
    pusha
    push    fs
    
    mov     fs,[cs:BufferSeg]
    mov     di,[cs:DacBuffOff]
    
    mov     al,[fs:di]
    mov     dx,[cs:LptAddress1]
    out     dx,al
    mov     al,[fs:di + 1]
    mov     dx,[cs:LptAddress2]
    out     dx,al
    
    add     [cs:DacBuffOff],2
    mov     ax,[cs:DacBuffLen]
    cmp     [cs:DacBuffOff],ax
    jb      @@NotOver

;set up for next buffer
    cmp     [cs:WhichBuffer],3
    je      @@SetDac3
    cmp     [cs:WhichBuffer],5
    je      @@SetDac3

    mov     [cs:WhichBuffer],3
    mov     ax,[cs:BufferOffset2]
    mov     [cs:DacBuffOff],ax
    add     ax,[cs:Buffer2Size]
    sub     ax,2
    mov     [cs:DacBuffLen],ax
    jmp     @@NotOver

@@SetDac3:
    mov     [cs:WhichBuffer],4
    mov     ax,[cs:BufferOffset1]
    mov     [cs:DacBuffOff],ax
    add     ax,[cs:Buffer1Size]
    sub     ax,2
    mov     [cs:DacBuffLen],ax

@@NotOver:
    mov     al,20h      ;acknowledge hardware interrupt
    out     20h,al

    pop     fs
    popa
    iret
ENDP TimerIntStereo

    END

