;
;     A Cheap and nasty MOD player for the Ultrasound   V0.00000001á
;
;   By Adam Seychell 
;
;    This MOD player was coded by me in about 3« days of coding so please 
; don't tell me it's a piece of crap because I already know it is.  You may
; not even class it as a MOD player but rather a program which plays some 
; samples from a file in almost a random order.......
; When playing 8 channel MODs it uses a wopping 00.2% of CPU time
; on my 386DX 33MHz.  Oh well, you can't have everything. ( hehehehe )
; At least I have made an effort to comment the code. This means you
; should be able to follow it and do some improvements on it, like add
; support for more song effects :-)
;    This is MASM 6.1 only code. I know TASM is more popular but I love the
; .IF  .ELSE .ELSEIF and .ENDIF  directives of MASM. May be the latest TASM
; does these, I only have TASM v2.5 wich sucks compared to MASM 6.1.
;
;
;  
.386
.model flat
.stack 4096
.code

Include  Macros.386
Include  GUS.INC
Include  DMA.INC
externdef Initalize_MOD_file    :near

NumOfActiveVoices       EQU     16


OPTION OLDSTRUCTS

GF1_Byte MACRO  _reg_,_data_
        mov     DX,GF1_Reg_Select
        mov     al,_reg_
        out     dx,al
        add     dl,2
        mov     al,_data_
        out     dx,al
ENDM

GF1_Word MACRO  _reg_,_data_
        mov     DX,GF1_Reg_Select
        mov     al,_reg_
        out     dx,al
        inc     dl
        mov     ax,_data_
        out     dx,ax
ENDM

;=========================================================================
;
;  Three procedures to set the Starting , Ending and Current location
;  registers of the currently selected voice.
;
; Expects:  EAX with location
;
;
;
;=========================================================================
EndingLocation PROC USES EBX
        mov     bl,4
        call Set_Voice_Address_Register
        ret
EndingLocation ENDP

StartingLocation PROC USES EBX
        mov     bl,2
        call Set_Voice_Address_Register
        ret
StartingLocation ENDP

CurrentLocation PROC USES EBX
        mov     bl,0Ah
        call Set_Voice_Address_Register
        ret
CurrentLocation  ENDP

Set_Voice_Address_Register PROC USES EAX EDI
        mov     edi,eax
        mov     dx, GF1_Reg_Select
        mov     al,bl
        out     dx,al
        inc     dl
        mov     eax, edi
        shr     eax, 11
        out     dx, ax                             ; location HIGH
        dec     dl
        mov     al,bl
        inc     al
        out     dx,al
        inc     dl
        mov     eax,edi
        shl     eax,5
        out     dx,ax                             ; location LOW
        ret
Set_Voice_Address_Register  ENDP




;===========================================================================
;
;  Set voice playing rate.
;
;  Expects:     CL  = Voice number
;               AX  = note to play. ( Amiga MOD file note values)
;
;===========================================================================
Set_VoicePeriod PROC USES EDI EAX EDX
                mov     edi,eax
                mov     dx,GF1_Voice_Select
	        mov     al,cl
	        out     dx,al
                imul    edi,617400/NumOfActiveVoices
                xor     edx,edx
                mov     eax,3546894*1024
                div     edi
                mov     edi,eax
                mov     dx,GF1_Reg_Select
                mov     al,1
                out     dx,al           	; set the FC register
                inc     dl
                mov     ax,di
                out     dx,ax
                ret
Set_VoicePeriod ENDP




;===========================================================================
;
; Set Voice Volume.
;
;  Expects:     CL  = Voice number
;               AL  = Linear volume.  Range 0 to 64
;
;===========================================================================
Set_VoiceVolume  PROC USES EAX EDX EBX
                movzx   eax,al
                cmp     al,64
                jbe @f
                 mov  al,64
         @@:    mov     di,mt_VolTable[eax*2]
                shl     di,4
                mov     dx,GF1_Voice_Select
	        mov     al,cl
	        out     dx,al
                mov     dx,GF1_Reg_Select
                mov     al,9
                out     dx,al             ; set the current Volume register
                inc     dl
                mov     ax,di
                out     dx,ax
                ret

mt_VolTable     dw  0000
                dw  1750,2503,2701,2741,2781,2944,2964,2981
                dw  3000,3017,3034,3052,3070,3207,3215,3224
                dw  3332,3340,3248,3256,3263,3271,3279,3287
                dw  3294,3303,3310,3317,3325,3458,3462,3466
                dw  3469,3473,3478,3481,3484,3489,3492,3495
                dw  3499,3502,3506,3509,3513,3517,3520,3524
                dw  3528,3532,3534,3538,3543,3545,3549,3552
                dw  3556,3558,3563,3565,3570,3573,3577,3580

Set_VoiceVolume ENDP


;=========================================================================
;
;  Routine to start a voice playing a particular MOD instrument.
;
;  Expects:    BL with instument number  (0..30)
;              CL voice to play it on.   (0..Number of channels in song - 1 )
;
;  Ouptut:     Voice playing with looping off and IRQ's enabled
;              The ending position is set to the end of the sample
;              The starting position is set to the start of the sample
;              When the voice finishes playing the sample it will
;               generate an IRQ. The voice IRQ handler will then reload
;               the starting and ending postions so the voice will loop
;               in the sample. If the sample repeat length is less than
;               2 bytes then no looping will be used and the IRQ handler
;               will simply stop the voice.
;
;=========================================================================
Play_instrument PROC USES EAX
        mov     dx,GF1_Voice_Select
        mov     al,cl
        out     dx,al

        GF1_Byte  0, 00000010b          ; Stop Voice & IRQ's disabled
                                        ; Must do this whenever altering
                                        ; the starting ,ending  and current
                                        ; location registers of the voice

        movzx   ebx,bl
        cmp     bl,31
        jae Invalid_instr
        movzx   ecx,cl
        mov     Voice_Instr[ECX],bl             ; save our instrument

        mov     eax,Instr_Start [EBX*4]
        shl     eax,4
        call    StartingLocation
        call    CurrentLocation

        mov     eax,Instr_Length [EBX*4]
        cmp     eax,10
        jb Invalid_instr
        add     eax,Instr_Start [EBX*4]
        dec     eax
        shl     eax,4
        call    EndingLocation

        GF1_Byte  0, 00100000b          ; Looping disabled & IRQ's enabled

Invalid_instr:
        ret
Play_instrument ENDP






;==========================================================================
;
;   Voice Wave table IRQ handler. This procedure gets called when ever
; the Ultrasound generates a IRQ from a voice that has reached its ending
; address.
;
;==========================================================================
Handle_VoiceWT PROC
Local   Voices_Serviced :dword

        mov     Voices_Serviced,0
SERVICE_LOOP:
        mov     dx,GF1_Reg_Select
        mov     al,8Fh
        out     dx,al
        add     dl,2
        in      al,dx                   ; Read the IRQ source register
        mov     ch,al
        and     ch,11000000b
        cmp     ch,11000000b
        je FIFO_empty                   ; check if any IRQ are left
        and     al,011111b
        mov     cl,al                   ; Save voice number
        mov     ebx,1
        shl     ebx,cl
        test    Voices_Serviced,ebx     ; see if voice has already been
        jnz SERVICE_LOOP                ; serviced. If so then ignore it.
        or    Voices_Serviced,ebx

        mov     dx,GF1_Voice_Select     ; Select Voice to operate with
        mov     al,cl
        out     dx,al

        GF1_Byte    0,00000010b        ; First disable IRQ's and stop it

       cmp  CL ,NumOfActiveVoices
       jae Invalid_instr

        ;*    set up the voice to play the rest of the MOD instrument     *
        ;------------------------------------------------------------------
        ; Ok, the voice has just finished playing the MOD instrument and
        ; has generated an IRQ because it has hit the ending address. Now
        ; the voice must loop in the sample ( only of repeat length is
        ; above 2). This is done simply by setting a new start and ending
        ; position with lopping enabled. The voices IRQs can be disabled
        ; since we are just let the voice loop continously.
        movzx   ecx,cl
        movzx   ebx,Voice_Instr[ECX]      ; get instrument voice is playing
        cmp     bl,31
        jae Invalid_instr

        mov     eax,Instr_ReptStart [EBX*4]
        shl     eax,4
        call    StartingLocation
        call    CurrentLocation

        mov     eax,Instr_ReptLength [EBX*4]
        cmp     eax,5
        jb Invalid_instr
        add     eax,Instr_ReptStart [EBX*4]
        inc     eax
        shl     eax,4
        call    EndingLocation

        GF1_Byte  0, 00001000b          ;  IRQ's disabled, lopping enabled

Invalid_instr:

       jmp SERVICE_LOOP

FIFO_empty:
        ret
Handle_VoiceWT  ENDP









IRQ_source      db 0
;=========================================================================
;
;     The main Ultrasound Interrupt Handler
;
;   ( The GUS's IRQ handler is the hart of any Ultrasound code )
;
;=========================================================================
GUS_ISR PROC
        pushad
        push    ds
        mov     ax, Seg  GUS_ISR
        mov     ds,ax

Service_GUS_IRQ_LOOP:
        mov     dx,GF1_IRQ_status
        in      al,dx
        mov     IRQ_source,al

;********  Check for Voice Wave Table IRQ **************
        test    IRQ_source,00100000b
        jz  Not_Voice_WaveTable

            call    Handle_VoiceWT

Not_Voice_WaveTable:

;********  Check for Voice Volume Ramp IRQ **************
        test    IRQ_source,01000000b
        jz  Not_Voice_VolumeRamp


Not_Voice_VolumeRamp:

;******  Check for  TIMER 1 IRQ **************
        test    IRQ_source,00100b
        jz  Not_Timer1

         ;
         ;  Run the MOD player.
         ;
                call    MOD_Ticker
         ;
         ; Pulse IRQ enable bit from 0 to 1. This allows more Timer IRQ's
         ;
               outp  GF1_Reg_Select,45h
               inp   GF1_Data_high
               and   al,NOT 0100b               ; Clear Timer 1 IRQ
               out   dx,al
               or   al, 0100b                  ; Enable Timer 1 IRQ
               out   dx,al

Not_Timer1:

;********  Check for  TIMER 2 IRQ **************
        test    IRQ_source,01000b
        jz  Not_Timer2
         ;
         ; Pulse IRQ enable bit from 0 to 1 to allows more Timer IRQ's
         ;
                  outp  GF1_Reg_Select,45h
	          inp   GF1_Data_high
                  and   al,NOT 1000b                  ; Clear Timer 2 IRQ
	          out   dx,al
                  or   al, 1000b                      ; Enable Timer 2 IRQ
	          out   dx,al

Not_Timer2:

;********  Check for  DMA Terminal Count  IRQ **************
        test    IRQ_source,10000000b
        jz  Not_DMA_TC

    ;
    ;  Read DRAM DMA controll register to allow more DRAM DMA IRQs.
    ;
        mov     dx,GF1_REG_Select
        mov     al,041h
        out     dx,al
        mov     dx,GF1_DATA_High
        in      al,dx
        or      DMAPlay_TC,1          ; Set flag to show we got a DMA IRQ


Not_DMA_TC:

        test    IRQ_source,11101111b
        jz Service_GUS_IRQ_LOOP        ; Keep on servicing IRQ's until there
                                       ; is none left

        mov     al,20h                 ; Send EOI command to the PICs
        out     20h,al
        out     0a0h,al
        pop     ds
        popad
        iretd
GUS_ISR ENDP


SampleRec     STRUC
S_Name       db 22 DUP (0)
S_Length     dw 0
S_FineTune   db 0
S_Volume     db 0
S_ReptStart  dw 0
S_ReptLength dw 0
SampleRec ENDS

MODHeader STRUC
SongName        db 20 DUP (0)
Samples         SampleRec 31 DUP (<>)
SongLength      db 0
PRepeat         db 0
Sequence        db 128 DUP (0)
MOD_tag         dd 0
MODHeader ENDS

Header MODHeader <>

Channels                db 0

FileHandleModF          dw 0
filename_ptr            dd 0
filenameEnd_ptr         dd 0
align 4
Pattern_PTR     	dd 0
DMA_DRAM_address        dd 0
DMA_buffer_addr         dd 0
DMA_buffer_phys         dd 0
Patterns_Size           dd 0
BytesRead               dd 0
IRQ_chan     db 0
DMA_chan     db 0
DMAPlay_TC   db 0
_loading_mesg   db 10,10,13,'Loading$'
dummy_cmd_tail          db  1,' '

align 4
Instr_ReptLength        dd 31 DUP (0)
Instr_ReptStart         dd 31 DUP (0)
Instr_Start             dd 31 DUP (0)
Instr_Length            dd 31 DUP (0)
Instr_Volume            db 31 DUP (0)
Instr_FineTune          db 31 DUP (0)
Voice_Instr             db 31 DUP (-1)





Start32:

Initalize_MOD_file:


;}}
;}}}    Read the ULTRASND to get the IRQ and DMA settings
;}}
        call    GetUltraConfig
        jc exit_error_gus
        mov     DMA_chan,CL
        mov     IRQ_chan,BL



;}}
;}}}    Reset the Ultrasound and it's DMA and IRQ values.
;}}
        call    Ultrasound_Reset
        jc exit_error_gus


;}}
;}}}    Set the Ultrasound's IRQ vector
;}}
        mov     edx,offset GUS_ISR
        mov     cx,cs                           ; CX:EDX = selectro:offset
        mov     bl,IRQ_chan                     ; Convert IRQ to interrupt
        cmp     bl,8                            ; number
        jb Jpic1
        add     bl,60h
Jpic1:  add     bl,8
        mov     ax,0205h
        int     31h



;}}
;}}}  Load the MOD file header (song name, sample data, pattern order, ect )
;}}

        mov     ax,0EE02h                       ; GET DOS32 MEMORY INFO
        int     31h                             ; Returns ESI -> PSP segment
        mov     edi,esi
        movzx   ecx,byte ptr [edi+80h]
        inc     ecx
        add     edi,81h
        mov     al,' '
        repe    scasb
        dec     edi
        and     ecx,ecx
        jz exit_error_usage
        push    edi
        repne   scasb
        mov     byte ptr [edi],0
        mov     filenameEnd_ptr,edi
        pop     edx
        mov     filename_ptr,edx
        mov     ax,3D02h            ; open a file function
        int     21h
        mov     FileHandleModF,ax
        jc exit_error_file

        BlockRead        ModF,Offset Header,SIZEOF Header


;}}
;}}}     See how many channles the MOD uses 4,8,16 or 32
;}}
        mov    Channels,32
        cmp     ds:Header.MOD_tag,'HC32'
        je  gotChans
        mov    Channels,16
        cmp     ds:Header.MOD_tag,'HC61'
        je  gotChans
        mov    Channels,8
        cmp     ds:Header.MOD_tag,'NHC8'
        je  gotChans
        cmp     ds:Header.MOD_tag,'ATCO'
        je  gotChans
        mov    Channels,4
gotChans:



;}}
;}}} Get the maximum pattern used, so can calculate where the samples start
;}}
        xor     eax,eax
        xor     ebx,ebx
SrchM:  mov     CL,Header.Sequence[EBX]
        cmp     CL,al
        jbe @f
        mov     al,CL
@@:     inc     bl
        cmp     bl,ds:Header.SongLength
        jb SrchM
        inc     eax
                              ;--> EAX has maximum pattern + 1

;}}
;}}}    Allocate some memory for all the patterns
;}}
        movzx   ebx,Channels            ; calulate bytes needed for patterns
        mul     ebx
        shl     eax,8
        mov     Patterns_Size,eax              ; save amount used
        add     eax,0fffh
        mov     edx,eax
        mov     ax,0EE42h                       ; DOS32 allocate mem service
        int     31h                            ; allocate EDX bytes
        jc exit_error
        mov     Pattern_PTR,edx                ; save pattern data pointer


;}}
;}}}    Load in the pattern data
;}}

        BlockRead       ModF,Pattern_PTR,Patterns_Size
        jc exit_error

;
;       Print the song name
;

        xor     ecx,ecx
@@:     mov     al,Header.SongName[ecx]
        cmp     al,0
        jz @f
        mov     ah,0Eh
        xor     bh,bh
        int     10h
        inc     ecx
        cmp     ecx,20
        jb @b
@@:
        mov     edx,offset  _loading_mesg
        mov     ah,9
        int     21h



;}}
;}}}    Extract all the instrument data form the file into a useful format
;}}

        xor     edi,edi
        xor     esi,esi
        xor     ecx,ecx
SET_DATA_LOOP:
        mov     Instr_Start [ecx*4],edi

        movzx   eax,Header.Samples.S_Length[ESI]
        xchg    ah,al
        shl     eax,1
        add     edi,eax
        mov     Instr_Length[ecx*4],eax

        movzx   eax,Header.Samples.S_reptLength[ESI]
        xchg    ah,al
        shl     eax,1
        mov     Instr_ReptLength[ecx*4],eax

        movzx   eax,Header.Samples.S_reptStart[ESI]
        xchg    ah,al
        shl     eax,1
        add     eax,Instr_Start [ecx*4]
        mov     Instr_ReptStart [ecx*4],eax

        mov     al,Header.Samples.S_Volume[ESI]
        mov     Instr_Volume[ecx],al

        mov     al,Header.Samples.S_FineTune[ESI]
        mov     Instr_FineTune[ecx],al

        add     esi,SIZEOF SampleRec
        inc     ecx
        cmp     ecx,31
        jb     SET_DATA_LOOP


;}}
;}}}    Setup Ultrasound for transfering DMA
;}}

      ; ***** Allocate a 16KB DMA buffer  ******

        mov     ax,0EE41h
        int     31h
        jc exit_error_mem
        mov     DMA_buffer_addr,edx
        mov     DMA_buffer_phys,ebx


FILL_GUS_LOOP:


     ;********** Read a 16KB of sample from the MOD file into the DMA buffer

        BlockRead  ModF,DMA_buffer_addr,4000h
        jc  exit_error
        mov     BytesRead,eax

        cli                             ; Must not be interrupted when
                                        ; programming the DMA and GUS.

      ;****** program the 8237 DMA contoller  *************

        mov     al,01001000b             ; DMA mode register
        mov     ah,DMA_Chan              ; Channel number ( 0..7 )
        mov     ecx,04000h               ; Bytes to transfer
        mov     ebx,DMA_buffer_PHYS      ; Physical base address
        call    DMA_setup                ; Do it ( see DMA.ASM )

      ;********* Set the GUS's DMA DRAM staring address register *****

        mov     edi,DMA_DRAM_address
        test    DMA_chan,100b
        jz _8bitDMA
           ; ---- do 16 bit DMA address translation ( see the SDK ) -----
          mov     eax,edi
          shr     edi,1
          and     edi,01ffffh     ; zero out bit 17..19
          and     eax,0c0000h     ; get bits 18 and 19
          or      edi,eax
_8bitDMA:
        shr     edi,4
        mov     al,042h                       ; DMA Start Address
        outp    GF1_Reg_Select,42h
        outpW    GF1_Data_Low,di

      ;******* Set the GUS's DRAM DMA Control Register  **********

        mov     ah,DMA_chan
        and     ah,100b
        mov     cl,00100001b       ;Write, 650KB/s, 8bit data, 16/8bit DMA
        or      cl,ah
        GF1_Byte  41h,cl           ; The DMA cycle will now start

        sti                        ; Leave critical state

   ;***** Wait around until the Ultrasound generates a DMA TC IRQ ******

                mov     ecx,100000h
waitTC:         test    DMAPlay_TC,1
                loopz  waitTC
                jz   exit_error_irq
                mov     DMAPlay_TC,0

        mov     al,'.'                        ; display loading progress
        mov     ah,0Eh
        xor     bh,bh
        int     10h

        mov     eax,BytesRead
        add     DMA_DRAM_address,eax
        and     eax,eax
        jnz FILL_GUS_LOOP

        ;Ok, finished filling the GUS's DRAM with samples

;}}
;}}} ********  Cancel DRAM  DMA   ***************
;}}

        GF1_Byte 41h,00000000b                     ;stop DMA IRQ's

        close ModF                                 ; close the MOD file


;}}
;}}}  Set the number of active voices.
;}}
        cli
        GF1_Byte       0Eh,( NumOfActiveVoices -1 ) or 0C0h
        sti



;}}
;}}}  Set the panning positon for each voice
;}}
        cli
        mov     cl,0
        mov     bl,2
SetPlop:
        outp    GF1_Voice_Select,cl
        GF1_Byte      0Ch,bl                      ; Voice Pan register
        inc     cl
        xor     bl,0fh
        outp    GF1_Voice_Select,cl
        GF1_Byte      0Ch,bl                      ; Voice Pan register
        inc     cl
        outp    GF1_Voice_Select,cl
        GF1_Byte      0Ch,bl                      ; Voice Pan register
        inc     cl
        xor     bl,0fh
        outp    GF1_Voice_Select,cl
        GF1_Byte      0Ch,bl                      ; Voice Pan register
        inc     cl
        cmp     cl,NumOfActiveVoices
        jb SetPlop
        sti

;}}
;}}}  reset some of the song varibles
;}}
        mov     Speed,6
        mov     Song_position,0
        mov     Current_Line,0
        mov     Current_Tick,0


;}}
;}}}  Set Timer 1 ( 80æS )  to  50Hz ( period = 20000 æS )
;}}}   for the MOD player ticks
;}}

        outp            GF1_Timer_data,1      ; unmask timer 1 and start it
        GF1_Byte        46h,255-( 20000/80 )  ; Timer 1 Count register
        GF1_Byte        45h,0100b             ; Enable Timer 1 IRQ



; THE SONG WILL START ON THE FIRST TIMER TICK ( See MOD_ticker proc )



;************** LOAD AND EXECUTE A PROGRAM ( command.com ) *****************

;
; search for the "COMSPEC=" environemnt varibles
;

        mov     ax,0EE02h            ; Get DOS32 address information
        int     31h                  ; Returns EDI -> environment address
get_str_loop:
        cmp     dword ptr [edi],'SMOC'          ; cmp the string "COMSPEC="
        jne not_string
        cmp     dword ptr [edi+4],'=CEP'
        je got_string                           ; if equal exit loop
not_string:
        mov     al,0                            ; Scan environment for a zero
        mov     ecx,20000                       ; and get next varible
        repne   scasb
        jmp  get_str_loop                       ; loop around again

got_string:
        add     edi,8
        mov     edx,edi                         ; EDX hold address of the
                                                ; COMSPEC string


;
; Call the 32bit version of the "load and execute" DOS service
;
        mov     ah,4Bh
        mov     al,0
        mov     edi,00000                       ; DS:EDI -> envrironment
        mov     esi,Offset dummy_cmd_tail       ; DS:ESI -> command tail
                                                ; DS:EDX -> ASCIIZ string
        Int     21h




        GF1_Byte        45h,0000b       ; Stop the GUS's timer 1


        mov     edx,offset keyplay_mesg
        mov     ah,9
        int     21h

byebye:

        mov     cl,-1

@@:
        mov     ah,0
        int     16h
        cmp    ah,1
        je exit4
        mov     al,ah
        sub     al,3Bh
        cmp     al,10
        jb Set_a_note

         mov     bl,ah
         sub     bl,2
         cmp     bl,31
         jae done
         inc     cl
         cli
         call    play_instrument
         mov     al,64
         call    Set_VoiceVolume
         mov     ax,Curnt_Note
         call    Set_VoicePeriod
         sti
         jmp done

Set_a_note:
        xor     ah,ah
        add     ax,3
        shl     ax,7
        mov     Curnt_Note,ax
        call    Set_VoicePeriod

done:
        cmp     cl,NumOfActiveVoices
        jb   @b
        jmp byebye

exit4:
        mov     edx,offset end_mesg
        mov     ah,9
        int     21h
exit2:
        call    Ultrasound_init

        mov     ah,4ch
        int     21h
Curnt_Note      dw 200

keyplay_mesg    db 'Hit the keyboard keys to play the MOD instruments.  ESC to abort',10,13,36
end_mesg    db 10,10,13,'Your computer is back to normal.',10,13,36

parmBlock       db 10h dup (0)
meme db 0,0,0,0
exit_error_gus:
        mov     edx,offset mesg1
        jmp   exit_error
mesg1   db ' Ultrasound card not found $'
exit_error_irq:
        mov     edx,offset mesg2
        jmp   exit_error
mesg2   db ' GF1 not generating IRQ''s. Try a different IRQ setting',10,13
        db ' in the environemnt string ULTRASND$'
exit_error_mem:
        mov     edx,offset mesg3
        jmp   exit_error
mesg3   db 'Not enough base memory for DMA buffer ( need about 75KB free )$'
exit_error_file:
        mov     edx,offset mesg4
        jmp   exit_error
mesg4   db 'can''t open mod file $'
exit_error_usage:
        mov     edx,offset mesg5
        jmp   exit_error
mesg5   db 'Usage:    modplay  <filename.MOD> ',10,13,36


exit_error:
        mov     ah,9
        int     21h
        jmp exit2









     ;  song varibles
;----------------------------------------------------------------
align 4
CurrentVoice_effect     dd 16 DUP (0)
CurrentVoice_Args       db 16 DUP (0)
Destinamtion_note       dw 16 DUP (0)
Speed           	db 6
Song_position   	db 0
Current_Line    	db 0
Current_Tick            db 0
Skip_volSet             db False
VoiceNote               dw 16 DUP (0)
VoiceVolume     	db 16 DUP (0)
;----------------------------------------------------------------




;=========================================================================
;
;  OK.  This is basically the entire MOD players code. This procedure is
; driven by the Ultrasounds TIMER 1 interrupt which is set to a rate of
; 50Hz.
;
;
;=========================================================================
MOD_Ticker PROC


   CMP  Current_Tick , 0
   jne PlaySame_line

        call    Play_Pattern_line

PlaySame_line:


        ;*** update the effects for each voice *******
        xor     ecx,ecx
update_effect:
        mov     AH,CurrentVoice_Args[ecx]          ; get the argument
        call    CurrentVoice_effect[ecx*4]
        inc     ecx
        cmp     cl,channels
        jb update_effect


         inc    Current_Tick
         mov    al,Current_Tick
         cmp    al,Speed
         jb   dtick
          mov    Current_Tick,0
          inc    Current_Line
          cmp    Current_Line,64
          jb  dLine
           mov    Current_Line,0
           inc    Song_position
           mov   al,Song_position
           cmp   al,Header.SongLength
           jb dpatt
           mov Song_position,0

dpatt:
dtick:
dLine:

        ret
MOD_Ticker ENDP


;--------------------------------------------------------------------------
;  Play all the instruments on the current pattern line.
; Also update any new effects on the voices.
;--------------------------------------------------------------------------
Play_Pattern_line PROC
        movzx   eax,Song_position
        movzx   esi,Header.Sequence[eax]        ; Get the current pattern
        movzx   eax,channels                    ;Get bytes per line
        shl     eax,2
        mov     ebx,eax
        shl     ebx,6                           ; get bytes per pattern
        imul    esi,ebx                         ; multiply by pattern number
        mul     Current_Line                    ; multiply by line number
        add     esi,eax

        add     esi,Pattern_PTR
        xor     ecx,ecx
        mov     Skip_volSet,False

           ;******* get the Pattern data for each channel ***
playlineloop:

                mov   CurrentVoice_effect[ecx*4], offset Nul_effect
             ;*** get the effect ( AL) and argument ( AH )*******
                mov     al,[esi+2]
                and     al,0Fh
                mov     ah,[esi+3]
                and     ax,ax
                jz no_effect_used

        .IF  al ==  1
                  mov   CurrentVoice_effect[ecx*4], offset Slide_to_Note
                  mov   Destinamtion_note[ecx*2],0
        .ELSEIF al == 2
                  mov   CurrentVoice_effect[ecx*4], offset Slide_to_Note
                  mov   Destinamtion_note[ecx*2],-1

       .ELSEIF al == 3
                  mov   CurrentVoice_effect[ecx*4], offset Slide_to_Note
                  mov   bx,[esi]
                  xchg  bh,bl
                  and   ebx,0fffh
                  jz @f
                   mov   Destinamtion_note[ecx*2],bx
              @@: and  ah,ah
                  jnz  @f
                   mov  ah,CurrentVoice_Args[ecx]   ; use last argument
                @@:
;        .ELSEIF al == 4         ;****** vibrato **************
;                  mov   CurrentVoice_effect[ecx*4], offset Slide_to_Note
;                  mov   Destinamtion_note[ecx*2],-1

        .ELSEIF al == 0Ah         ;******  Volume slide **************
                  mov   CurrentVoice_effect[ecx*4], offset Slide_Volume
        .ELSEIF al == 0Ch         ;****** Set Volume **************
                  mov    al,ah
                  cmp    al,64
                  jna @f
                   mov  al,64
              @@: mov     VoiceVolume[ecx],al     ; save the volume
                  call   Set_VoiceVolume
                  mov   Skip_volSet,True
        .ELSEIF al == 0Dh         ;****** Pattern Break **************
                movzx   ebx,ah
                and     bl,0f0h
                shr     bl,4
                imul    ebx,10
                mov     bh,ah
                and     bh,0Fh
                add     bl,bh
                dec     bl
                mov     Current_Line,bl
                inc     Song_position
                mov     Current_tick,-1
                ret

        .ELSEIF al == 0Fh       ; ******* Set the Speed **********
                 cmp ah,0
	         jz ignSp
	         cmp ah,31
                 ja ignSp
                 mov  speed,ah
           ignSp:

        .ENDIF

              mov   CurrentVoice_Args[ecx],ah       ; save the argument

no_effect_used:
                mov     bl,[esi+2]       ;Extract instrument number into BL
	        shr     bl,4
	        mov     bh,[esi]
	        and     bh,0f0h
                or      bl,bh
                sub     bl,1
                jc   Skip_Note

                mov     eax,[esi]              ; get Note value
                xchg    ah,al
                and     eax,00fffh
                jz    Skip_Note
                mov    VoiceNote[ecx*2],ax      ; save the note
                call    Set_VoicePeriod

                cmp Skip_volSet , True
                je @f
                mov     al,Instr_Volume[EBX]
                mov     VoiceVolume[ecx],al     ; save the volume
                call    Set_VoiceVolume
            @@:
                call    Play_instrument          ; Start the voice playing
Skip_Note:
               add     esi,4
               inc     cl
               cmp     cl,Channels
               jb  playlineloop

                ret
Play_Pattern_line ENDP







;***** nul effect *************
Nul_Effect PROC
        ret
Nul_Effect ENDP

;******** Slide the Volume *************
Slide_Volume PROC
        test    ah,0F0h
        jnz  UP
        and     ah,0Fh
        sub   VoiceVolume[ecx],ah
        jnc  @f
         mov   VoiceVolume[ecx],0
@@:     jmp exit1


UP:     shr     ah,4
        add   VoiceVolume[ecx],ah
        cmp   VoiceVolume[ecx],64
        jbe @f
         mov   VoiceVolume[ecx],64
  @@:

exit1:  mov     al,VoiceVolume[ecx]
        call    Set_VoiceVolume
        ret
Slide_Volume ENDP

;****** Slide to Note ( dec/increase period )************
Slide_To_note PROC
        mov   bx,Destinamtion_note[ecx*2]  ; get Note value to slide towords
        movzx ax,ah
        cmp VoiceNote[ecx*2],bx
        jb up
        ja down
        ret
up:
        add VoiceNote[ecx*2],ax
        jmp exit1
down:
        sub VoiceNote[ecx*2],ax
exit1:
        mov     ax,VoiceNote[ecx*2]
        .If   ax > 856
          mov  ax,856
        .elseif ax < 113
          mov  ax,113
        .endif
        mov     VoiceNote[ecx*2],ax
        call    Set_VoicePeriod
Skip_Note:
        ret
Slide_To_note ENDP


END Start32
