Metropoli BBS
VIEWER: prodma.asm MODE: TEXT (LATIN1)
    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

    OldDmaIntOff    dw  0
    OldDmaIntSeg    dw  0

    RealDmaOff      dw  0
    RealDmaSeg      dw  0

    DmaRoutine      dw  offset TransferDmaMONO

    OldInt          dd  0
    DacBuffOff      dw  0
    DacBuffLen      dw  0

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

DMAINT1:
    pusha
    cli
    mov     [cs:WhichBuffer],3

    mov     ax,[cs:BufferOffset1]
    mov     [word cs:QTDS.CurBuffptr],ax
    mov     ax,[cs:Buffer1Size]
    mov     [word cs:QTDS.BuffSize],ax

    mov     dx,[cs:BaseAddress]
    add     dx,0eh   
    in      al,dx           ;acknowledge interrupt
    cmp     [cs:QUITDMA],0
    jne     DoneInt7        ;if we are to quit, quit now
    cmp     [cs:MoreB],0
    jne     DoDma2
    jmp     DoDma3

DMAINT2:
    pusha
    cli
    mov     dx,[cs:BaseAddress]
    add     dx,0eh   
    in      al,dx           ;acknowledge interrupt
    cmp     [cs:QUITDMA],0
    jne     DoneInt7        ;if we are to quit, quit now
    jmp     DoDma3

DoneInt7:
    call    Haltdma
    call    UndoRealInt
    jmp     SkipMask2

DMAINT3:
    pusha
    cli
    mov     [cs:WhichBuffer],4

    mov     ax,[cs:BufferOffset2]
    mov     [word cs:QTDS.CurBuffPtr],ax
    mov     ax,[cs:Buffer2Size]
    mov     [word cs:QTDS.BuffSize],ax
    
    mov     dx,[cs:BaseAddress]
    add     dx,0eh   
    in      al,dx           ;acknowledge interrupt
    cmp     [cs:QUITDMA],0
    jne     DoneInt7        ;if we are to quit, quit now
    cmp     [cs:MoreB],0
    jne     DoDma4
    jmp     DoDma1

DMAINT4:
    pusha
    cli
    mov     dx,[cs:BaseAddress]
    add     dx,0eh   
    in      al,dx           ;acknowledge interrupt
    cmp     [cs:QUITDMA],0
    jne     DoneInt7        ;if we are to quit, quit now
    jmp     DoDma1

DoDma1:
    mov     ah,[cs:IPage]     ;set up for dma 1
    mov     cx,[cs:Ilength]
    mov     dx,[cs:Ioffset]
    mov     bx,offset DMAInt1
    jmp     short skipmask
DoDma2:
    mov     ah,[cs:IPage+1]     ;set up for dma 2
    mov     cx,[cs:ILength+2]
    mov     dx,[cs:IOffset+2]
    mov     bx,offset DMAInt2
    jmp     short skipmask
DoDma3:
    mov     ah,[cs:IPage+2]     ;set up for dma 3
    mov     cx,[cs:ILength+4]
    mov     dx,[cs:IOffset+4]
    mov     bx,offset DMAInt3
    jmp     short skipmask
DoDma4:
    mov     ah,[cs:IPage+3]     ;set up for dma 4
    mov     cx,[cs:ILength+6]
    mov     dx,[cs:IOffset+6]
    mov     bx,offset DMAInt4
    ;jmp     short skipmask

SkipMask:
    call    [cs:DmaRoutine]
Skipmask2:
    mov     al,20h
    out     20h,al          ;end of hardware interrupt
    
    sti
    popa
    iret

;======================
;==- 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     [cs:OldDmaIntOff],ax              ; Grab the current interrupt
    mov     ax,[es:bx+2]
    mov     [cs:OldDmaIntSeg],ax              ; so we can restore it later
    pop     es

    mov     ah,[cs:IPage]     ;set up for dma 1
    mov     cx,[cs:Ilength]
    mov     dx,[cs:Ioffset]
    mov     bx,offset DMAInt1

    call    [cs:DmaRoutine]     ;start playing
    
    popa
    ret
ENDP StartTransferDma
    
    ;ah= page number
    ;dx= BASE_ADDRESS
    ;cx= DATA_LENGTH (-1)
    ;bx= address of interrupt
    ; This function sets up for write only!
PROC TransferDmaSTEREO NEAR
;    dec     cx
    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,bx
    call    DoREALint

    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)
    ;bx= address of interrupt
    ; This function sets up for write only!
PROC TransferDmaMONO NEAR
    
;    dec     cx
    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,bx
    call    DoREALint

    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æS
    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,[cs:OldDmaIntOff]              
    mov     [es:di],ax
    mov     ax,[cs:OldDmaIntSeg]              
    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

PROC CalcForDMA NEAR
    pushad
    push    ds
    mov     ax,cs
    mov     ds,ax
    
    mov     [MoreB],0
    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     [MoreB],1
    dec     bx
    mov     [ILength+2],bx
    mov     [IOffset+2],0
    mov     [IPage+1],ah
    inc     [IPage+1]
NoMoreb1:
    mov     [IPage],ah
    mov     [IOffset],dx
    dec     cx
    mov     [ILength],cx

    mov     [MoreB+1],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     [MoreB+1],1
    dec     bx
    mov     [ILength+6],bx
    mov     [IOffset+6],0
    mov     [IPage+3],ah
    inc     [IPage+3]
NoMoreb2:
    mov     [IPage+2],ah
    mov     [IOffset+4],dx
    dec     cx
    mov     [ILength+4],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 cs:OldInt]
    mov [ds:bx],ax
    mov ax,[Word cs:OldInt+2]
    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

[ RETURN TO DIRECTORY ]