* MultiPlayer
* Copyright (C) 1992 Bryan Ford
*
* 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.
*
* I (the author of MultiPlayer) can be contacted on the Internet at
* "bryan.ford@m.cc.utah.edu".  See "Player.doc" for other addresses.
*
* $Id: player.asm,v 5.1 92/09/14 18:43:30 BAF Exp $
*

* System includes
        include "exec/types.i"
        include "exec/nodes.i"
        include "exec/memory.i"
        include "exec/interrupts.i"
        include "exec/funcdef.i"
        include "exec/exec_lib.i"
        include "exec/execbase.i"
        include "exec/ables.i"
        include "hardware/cia.i"
        include "hardware/intbits.i"
        include "hardware/dmabits.i"
        include "hardware/custom.i"
        include "resources/cia.i"
        include "dos/dos.i"
        include "dos/dos_lib.i"
        include "intuition/intuition_lib.i"
        include "libraries/xpk.i"
        include "bry/macros.i"
_LVOAddICRVector        equ     -$06    ; LVO offsets for ciab.resource
_LVORemICRVector        equ     -$0c
_LVOAbleICR             equ     -$12
_LVOSetICR              equ     -$18


        include "player.i"

        code    text

        xref    toupper,toupperlong,stripblanks,isalpha
        xref    allocaudio,freeaudio,intsongpos,intrepeat,clearflash
        xref    _progload,_progstart
        xref    NoteSys,NoteSysMasterVolBal

        xref    ntstart,trekstart,ntpackstart,ststart,npstart
        xref    fcostart,fc14start,dssstart
        xref    jamstart,soundmonstart
        xref    oktastart,medstart
        xref    soundstart

        xref    _LinkerDB,_SysBase,_DOSBase,_IntuitionBase,_BattMemBase,_XpkBase
        xref    _sysflags,_playmode,_flashflags
        xref    GlobSetByte,GlobSetWord,GlobSetLong
        xref    mvolume,mbalance,mspeed,volume,balance,speed,modflags,filter
        xref    _infowinattach,_infowindetach

        xdef    loadfile,unloadfile
        xdef    loadmod,startmod,playmod,stopmod,endmod,contmod,contplaymod,setsong
        xdef    _loadmod,_startmod,_playmod,_stopmod,_endmod,_contmod,_contplaymod,_setsong,_iscantcont
        xdef    dienomem,diecorrupt,dieerrplay,diemes
        xdef    settint,setnormtint,stoptint,hookfunc,dmawait
        xdef    getfreqmodspeed,ntgetsongname,ntgetsongauthor,parseauthor,prepstring,addinfo
        xdef    changetint,changetintcia
        xdef    playervolume,playerspeed,_playervolume,_playerspeed
        xdef    addinfo,addinfopad
        xdef    allocmod,allocmodchip

        xdef    modtype,numsongs,cursong
        xdef    modspec,modmem,modsize,modend
        xdef    suppmem,suppsize,suppend
        xdef    modname,authorname,modtypename,songcatalog
        xdef    jumpforward,jumpback
        xdef    _modtype,_modname,_authorname,_modtypename,_songcatalog,_playing,_infolist
        xdef    _numsongs,_cursong,_songtime,_songendtime,_songpos,_songlen
        xdef    _fadevol,_fadeinc
        xdef    nocontinue              ; For rexxcont's benefit
        xdef    nomemtext

*** gmodcheck - Check to see if a particular entry in the GMOD header is used
* d0 = Offset of entrypoint
* a5 = GMOD header
* Returns: NZ if entrypoint is used, Z if it is nonexistent or do-nothing
gmodcheck:
        gmodbout d0,9$
        cmp.w   #RTSINST,0(a5,d0.l)
        rts
9$      cmp.l   d0,d0
        rts

*** closeloadhan - Close any currently opened loadhan
closeloadhan:
        move.l  loadhan(a4),d1          ; Close the loading handle if opened
        beq.s   1$
        move.l  a6,-(sp)
        move.l  _DOSBase(a4),a6
        jsr     _LVOClose(a6)
        clr.l   loadhan(a4)
        move.l  (sp)+,a6
1$      rts


*** allocmod/allocmodchip - Allocate memory attached to this module (freed when module is unloaded)
* d0 = Amount of memory to allocate
* Returns: d0 = Memory block, NULL on failure
allocmodchip
        move.l  #MEMF_CHIP,d0
        bra     allocmod_in
allocmod
        moveq   #0,d1
allocmod_in
        move.l  a6,-(sp)
        move.l  _IntuitionBase(a4),a6
        jsr     _LVOAllocRemember(a6)
        move.l  (sp)+,a6
        rts


*** loadfile - Load a file into a memory filespec
* a0 = Pointer to filename
* a1 = Pointer to filespec
* Returns: NE if successful, EQ if file not found, die if other error
loadfile:
        apush
        move.l  a1,a2

        tst.l   fs_Mem(a2)              ; See if we can decompress
        bne.s   1$                      ; Can't decompress on a reload
        move.l  _XpkBase(a4),d0
        bne     5$
1$
        move.l  a0,d1                   ; Open module
        move.l  #MODE_OLDFILE,d2
        move.l  _DOSBase(a4),a6
        jsr     _LVOOpen(a6)
        move.l  d0,loadhan(a4)
        move.l  d0,d7
        beq     99$

        move.l  d7,d1                   ; Seek to end of module
        moveq   #0,d2
        move.l  #OFFSET_END,d3
        jsr     _LVOSeek(a6)
        tst.l   d0
        bne     dienofile

        move.l  d7,d1                   ; Seek back to beginning and find size
        moveq   #0,d2
        move.l  #OFFSET_BEGINNING,d3
        jsr     _LVOSeek(a6)
        move.l  d0,d6
        ble     dienofile

        move.l  fs_Mem(a2),d0           ; See if the memory is already allocated
        beq.s   2$
        cmp.l   fs_FileSize(a2),d6      ; Make sure the file wasn't compressed
        beq.s   3$
        bra     diedontcompress
2$
        move.l  _SysBase(a4),a6         ; Allocate memory
        move.l  d6,d0
        moveq   #MEMF_CHIP!MEMF_PUBLIC,d1
        jsr     _LVOAllocMem(a6)
        move.l  d0,fs_Mem(a2)
        beq     dienocmem
        move.l  d6,fs_FileSize(a2)
        move.l  d6,fs_AllocSize(a2)
3$      move.l  d0,d2
        add.l   d6,d0
        move.l  d0,fs_EndMem(a2)

        move.l  _DOSBase(a4),a6         ; Load module
        move.l  d7,d1
        move.l  d6,d3
        jsr     _LVORead(a6)
        cmp.l   d0,d6
        bne     dienofile

        bsr     closeloadhan            ; Close module

        bra     9$

5$      move.l  d0,a6

        clr.l   -(sp)                   ; Set up the TagList
        lea     xpkerrmsg(a4),a1
        move.l  a1,-(sp)
        move.l  #XPK_GetError,-(sp)
        moveq   #1,d1
        move.l  d1,-(sp)
        move.l  #XPK_PassThru,-(sp)
        moveq   #MEMF_CHIP,d1
        move.l  d1,-(sp)
        move.l  #XPK_OutMemType,-(sp)
        lea     fs_FileSize(a2),a1
        move.l  a1,-(sp)
        move.l  #XPK_GetOutLen,-(sp)
        lea     fs_AllocSize(a2),a1
        move.l  a1,-(sp)
        move.l  #XPK_GetOutBufLen,-(sp)
        move.l  a2,-(sp)
        move.l  #XPK_GetOutBuf,-(sp)
        move.l  a0,-(sp)
        move.l  #XPK_InName,-(sp)

        move.l  sp,a0                   ; Try to load the module
        jsr     _LVOXpkUnpack(a6)
        adda.w  #7*8+4,sp               ; Pop the TagList off the stack
        tst.l   d0
        bne.s   88$

        move.l  fs_Mem(a2),d0           ; Calculate the module end position
        add.l   fs_FileSize(a2),d0
        move.l  d0,fs_EndMem(a2)

9$      moveq   #1,d0
99$     apop
        rts

88$     moveq   #XPKERR_IOERRIN,d1      ; See if it was a DOS error
        cmp.l   d0,d1
        beq     99$
        moveq   #XPKERR_NOMEM,d1        ; See if it was not enough memory
        cmp.l   d0,d1
        beq     dienocmem
        lea     xpkerrmsg(a4),a0        ; Use XPK's error message
        bra     diemes

*** unloadfile - Unload a file (if loaded)
* a0 = Pointer to filespec
unloadfile:
        move.l  fs_Mem(a0),a1
        clr.l   fs_Mem(a0)
        move.l  a1,d0
        beq.s   9$
        move.l  fs_AllocSize(a0),d0
        move.l  a6,-(sp)
        move.l  _SysBase(a4),a6
        jsr     _LVOFreeMem(a6)
        move.l  (sp)+,a6
9$      rts



*** findauthor - Find the author of the current song (into authorname)
* a5 = Pointer to GMOD header
findauthor:
        moveq   #0,d0
        move.l  cursong(a4),d1
        movea.w #gmod_GetSongAuthor,a1
        gmodmaycall a1
        move.l  d0,authorname(a4)
        bne.s   9$
        lea     unknowntxt(a4),a0
        move.l  a0,authorname(a4)
9$      rts

*** findcursong - Find the song to start playing, adjusting if necessary
* Returns: d0 = Adjusted song number
findcursong:
        move.l  cursong(a4),d0          ; Check the "recommended" song number

        bpl.s   19$                     ; See if it's the user's option
        cmp.b   #PM_RANDOM,_playmode(a4)
        bne.s   15$
        moveq   #0,d0                   ; FIXME: Select random song
        bra.s   19$
15$     moveq   #0,d0
19$
        cmp.l   numsongs(a4),d0         ; Make sure it's under the maximum
        bcs.s   49$
        moveq   #0,d0                   ; Always wrap around to song 0
49$
        move.l  d0,cursong(a4)          ; Store it back into cursong

        rts

*** setsong - Start playing a different song in the current module
* d0 = Song number to start playing
_setsong
        move.l  4(sp),d0
setsong
        move.l  numsongs(a4),d1         ; Make sure there are at least two songs
        subq.l  #1,d1
        beq.s   99$

        move.l  d0,cursong(a4)          ; Save new song number

        movem.l a5-a6,-(sp)
        move.l  gmodptr(a4),a5
        move.l  _SysBase(a4),a6
*       DISABLE                         ; No interrupts while song is stopped

        moveq   #gmod_StopMusic,d0      ; Stop the current song
        gmodmaycall d0

        bsr     findcursong             ; Find/adjust the song to play

        moveq   #gmod_StartMusic,d1     ; Start the new music
        gmodmaycall d1

        clr.l   _songtime(a4)           ; Clear the song timer

9$      bsr     findauthor              ; See who wrote this song

*       ENABLE
        movem.l (sp)+,a5-a6

99$     bset    #SB_WINDOWUP,_sysflags(a4)
        rts

*** catalogsongs - Create a catalog of songs for the current module
* a5 = GMOD pointer
catalogsongs:
        apush4

        lea     modremember(a4),a0      ; Allocate memory for catalog
        move.l  numsongs(a4),d0
        move.l  d0,d2
        addq.l  #1,d0
        lsl.l   #2,d0
        bsr     allocmod
        move.l  d0,songcatalog(a4)
        beq     dienomem

        move.l  d0,a2                   ; Create the song list
        movea.w #gmod_GetSongName,a6
        moveq   #0,d3
11$     move.l  d3,d1
        moveq   #0,d0
        gmodmaycall a6
        move.l  d0,(a2)+
        bne.s   15$
        lea     unknowntxt(a4),a0
        move.l  a0,-4(a2)
15$     addq.l  #1,d3
        subq.l  #1,d2
        bne.s   11$
        clr.l   (a2)

        apop4
        rts



*** freemodfile - Free the modfile string
freemodfile:
        move.l  modfile(a4),d0
        beq.s   9$
        move.l  a6,-(sp)
        move.l  d0,a1
        move.l  modfilesize(a4),d0
        move.l  _SysBase(a4),a6
        jsr     _LVOFreeMem(a6)
        clr.l   modfile(a4)
        move.l  (sp)+,a6
9$      rts

*** setmodfile - Copy a filename into the modfile buffer
* a0 = Pointer to filename to copy
setmodfile:
        movem.l a2/a6,-(sp)
        move.l  a0,a2
        bsr     freemodfile

        move.l  a2,a0                   ; Find the string length
        moveq   #0,d0
1$      addq.l  #1,d0
        tst.b   (a0)+
        bne.s   1$
        move.l  d0,modfilesize(a4)

        moveq   #0,d1                   ; Allocate the modfile buffer
        move.l  _SysBase(a4),a6
        jsr     _LVOAllocMem(a6)
        move.l  d0,modfile(a4)
        beq     dienomem

        move.l  d0,a0                   ; Copy the buffer
2$      move.b  (a2)+,(a0)+
        bne.s   2$

        movem.l (sp)+,a2/a6
        rts


*** addinfopad - Add a pad line to the info list (only if the list is already started)
addinfopad
        tst.l   infolist(a4)
        bz      addinfo_rts
        lea     nulllab(a4),a0
        ; Fall through...
*** addinfo - Add a string to the info list (allocate a new info list if necessary)
* a0 = Text
addinfo
        apush4
        move.l  a0,a3
        move.l  _IntuitionBase(a4),a6

        bsr     _infowindetach

        tst.l   infolist(a4)            ; Make sure we have a list to put things on
        bnz.b   \gotlist
        lea     modremember(a4),a0
        moveq   #MLH_SIZE,d0
        moveq   #0,d1
        jl      AllocRemember
        move.l  d0,infolist(a4)
        bz      \out
        move.l  d0,a1
        NEWLIST a1
\gotlist

\lloop
        move.l  a3,a2
        moveq   #-1,d2
\loop
        addq.w  #1,d2                   ; Search for a null in this line
        move.b  (a3)+,d0
        bz.b    \add
        cmp.b   #10,d0
        beq.b   \add
        cmp.w   #INFOCOLS,d2
        blo.b   \loop

        move.l  a3,a1                   ; Search backwards for a word break
        move.w  d2,d1
\bloop
        subq.w  #1,d2
        cmp.w   #INFOCOLS/2,d2          ; Fill at least half a line
        beq.b   \lbreak
        cmp.b   #' ',-(a3)
        bne.b   \bloop
        addq.w  #1,d2
        addq    #1,a3
        bra.b   \add

\lbreak
        lea     -1(a1),a3               ; Break without regard to words
        move.w  d1,d2
\add
        lea     modremember(a4),a0      ; Allocate a new info node
        moveq   #LN_SIZE+1,d0
        add.w   d2,d0
        moveq   #0,d1
        jl      AllocRemember
        tst.l   d0
        bz.b    \out

        move.l  d0,a1                   ; Add it to the list
        move.l  infolist(a4),a0
        ADDTAIL

        lea     LN_SIZE(a1),a1          ; Copy the string into it
        move.l  a1,LN_NAME-LN_SIZE(a1)
        bra.b   \copyin
\copy
        move.b  (a2)+,(a1)+
\copyin
        dbra    d2,\copy
        clr.b   (a1)

        tst.b   -1(a3)                  ; Next line
        bnz.b   \lloop
\out
        bsr     _infowinattach

        apop4
addinfo_rts
        rts


*** ldmod - (Internal) - Load module used by loadmod and startmod
* a0 = Pointer to filename
* Returns: CCR, d0 = progload()+1, 0 if everything successful, dies on failure
ldmod:
        move.l  modfile(a4),a0          ; Find the filename to display
        move.l  (a0),d0
        bsr     toupperlong
        cmpi.l  #'MOD.',d0
        bne.s   19$
        addq.l  #4,a0
19$     move.l  a0,modname(a4)

        lea     modspec(a4),a1          ; Load the module
        move.l  modfile(a4),a0
        bsr     loadfile
        beq     dienofile

        move.l  modmem(a4),a0           ; See if this is a program
        cmpi.l  #'PROG',(a0)
        beq     \isprog

        ; FIXME: Load mod prefs

        move.l  modmem(a4),a0
        pea     5$

        move.l  (a0),d0                 ; Modules IDable in first long
        cmpi.l  #'GMOD',d0              ; General executable module
        beq     gmodstart
        cmpi.l  #'XMOD',d0              ; Executable module
        beq     xmodstart
        cmpi.l  #'AMOD',d0              ; Absolute executable module
        beq     amodstart
        cmpi.l  #'BeEp',d0              ; JamCracker module
        beq     jamstart
        cmpi.l  #'SMOD',d0              ; Future Composer old module
        beq     fcostart
        cmpi.l  #'FC14',d0              ; Future Composer 1.4 module
        beq     fc14start
        cmpi.l  #'OKTA',d0              ; Oktalyzer module
        beq     oktastart
        cmpi.l  #'MMD0',d0              ; MED 3.xx module
        beq     medstart
        cmpi.l  #'MMD1',d0              ; OctaMED Pro module
        beq     medstart
        cmpi.l  #'MMU2',d0              ; Digital Sound Studio module
        beq     dssstart
        cmpi.l  #$48e7f1fe,d0           ; Dave Whittaker module
        beq     whitstart
        cmpi.l  #$08f90001,d0           ; SidMon 2.1 module
        beq     sid2start
        cmpi.l  #$48e700f0,d0           ; Mark II module
        beq     markiistart

        clr.b   d0                      ; Face The Music module
        cmpi.l  #'FTM'<<8,d0
        beq     ftmstart

        cmpi.l  #'8SVX',$8(a0)          ; 8SVX sounds (possibly sequenced)
        beq     soundstart

        move.l  $1a(a0),d0              ; SoundMonitor module
        clr.b   d0
        cmpi.l  #'V.2'<<8,d0
        beq     soundmonstart

        cmpi.l  #$4eaefeda,$80(a0)      ; SoundFX executable module
        beq     fxstart

        cmpi.l  #'PATT',$1f6(a0)        ; NoiseTracker packed module
        beq     ntpackstart

        move.l  $438(a0),d0             ; NoiseTrackerish module
        cmpi.l  #'M.K.',d0
        beq     ntststart
        cmpi.l  #'FLT4',d0
        beq     ntststart
        cmpi.l  #'EXO4',d0
        beq     ntststart

        cmpi.l  #'MTN'<<8,$5b8(a0)      ; SoundTracker 2.0-2.6 module
        beq     ststart

        cmpi.l  #'TA1A',$51c(a0)        ; Delta Music module
        beq     deltastart
        cmpi.l  #'TA1A',$27c(a0)        ; Delta Music module
        beq     delta2start

        cmpi.w  #$4efa,(a0)             ; Generic embedded module
        beq     genembstart

        move.l  a0,a1                   ; NoisePacker module
        move.w  (a1),d0
        cmp.w   #$1c,d0
        blo     \notnp
        lea     -4(a1,d0.w),a2
        tst.l   (a1)+
        bz      \notnp
        tst.l   (a1)+
        bz      \notnp
\nptest
        cmp.l   a2,a1
        bhs     npstart
        tst.l   (a1)+
        bnz     \notnp
        tst.l   (a1)+
        bnz     \nptest
\notnp

        moveq   #$60,d1                 ; ST/NT module
        move.b  (a0),d0                 ; FIXME: Need better detection here
        and.b   d1,d0
        beq.s   51$
        move.b  $13(a0),d0
        and.b   d1,d0
        beq     ntstart

51$     bra     dieunkmod               ; Doesn't match anything we know of

5$      move.l  d0,gmodptr(a4)          ; Save the GMOD header pointer
        move.l  d0,a5

        moveq   #gmod_NotePlayer,d1     ; Give the module our NotePlayer
        moveq   #0,d0
        lea     NoteSys,a0
        gmodmaycall d1
        tst.l   d0
        sne     portablemodule(a4)

        moveq   #gmod_InitMusic,d1      ; Call the initialization vector
        moveq   #0,d0
        gmodmaycall d1
        tst.l   d0
        beq.s   39$
        move.l  d0,a0
        bra     diemes
39$
        moveq   #gmod_GetNumSongs,d1    ; Find the number of songs
        moveq   #0,d0
        gmodmaycall d1
        tst.l   d0
        bne.s   7$
        moveq   #1,d0
7$      move.l  d0,numsongs(a4)

        moveq   #gmod_GetMakerName,d1   ; Find the module type
        moveq   #0,d0
        gmodmaycall d1
        move.l  d0,modtypename(a4)
        bne.s   61$
        lea     gmodname(a4),a0
        move.l  a0,modtypename(a4)
61$
        bsr     catalogsongs            ; Catalog the songs in the module

        bsr     findauthor              ; See who wrote this song

        moveq   #0,d0                   ; Put the scrolltext into the info window
        moveq   #gmod_GetScroll,d1
        gmodmaycall d1
        tst.l   d0
        bz.b    \noscroll
        move.l  d0,-(sp)
        bsr     addinfopad
        move.l  (sp)+,a0
        bsr     addinfo
\noscroll

        moveq   #gmod_Hook,d7           ; Give the module our Hook
        lea     playerhook(a4),a0
        move.l  #GMODHF_REPEAT!GMODHF_SEQUENCE,d1
        moveq   #0,d0
        gmodmaycall d7
        btst    #GMODHB_SEQUENCE,d0
        bz.s    \nohookseq
        bset    #MTB_SEQUENCE,modtype(a4)
\nohookseq:
        btst    #GMODHB_REPEAT,d0
        bz.s    \nohookrepeat
        bset    #MTB_REPEAT,modtype(a4)
\nohookrepeat:

        btst.b  #MTB_FILTER,modtype(a4)         ; See if we can control the filter
        bnz.b   \hasfilt
        lea     filter(a4),a0
        sub.l   a1,a1
        moveq   #-1,d0
        bsr     GlobSetByte
\hasfilt

        btst.b  #MTB_PROTRACKER,modtype(a4)     ; See if it's the Protracker player
        bnz.b   \ispt
        lea     modflags(a4),a0
        move.b  (a0),d0
        bset.b  #MNB_NOTPROTRACKER,d0
        bnz.b   \ispt
        sub.l   a1,a1
        bsr     GlobSetByte
\ispt

        btst    #MTB_SEQUENCE,modtype(a4)       ; See if the player can jump around
        bz.b    \nojump
        moveq   #gmod_Jump,d0
        bsr     gmodcheck
        bz.b    \nojump
        bset.b  #MTB_JUMP,modtype(a4)
\nojump

        moveq   #0,d0
        rts

\isprog:                                ; We found a module program!
        clr.l   -(sp)                   ; Load the program
        move.l  modfile(a4),-(sp)
        clr.l   -(sp)
        bsr     _progload
        lea     12(sp),sp
        move.l  d0,d2
        bsr     endmod                  ; Unload what we loaded
        move.l  d2,d0
        addq.l  #1,d0
        rts

*** plmod - (Internal) - Play module used by playmod and startmod
* d0 = 0 to start, 1 to continue
* Returns 0 if successful, 1 if couldn't continue, dies on other errors
plmod:
        move.l  d0,d7                   ; Save start/continue flag

        tst.b   _playing(a4)            ; Don't restart if already playing
        bne     9$

        move.l  gmodptr(a4),d0          ; Make sure a module is loaded
        beq     dienotloaded
        move.l  d0,a5

        move.b  noaudalloc(a4),d0
        or.b    portablemodule(a4),d0
        bnz     \noalloc
        bsr     allocaudio              ; Allocate the audio.device
\noalloc

        tst.l   d7                      ; Start or continue?
        bne.s   15$

        move.l  _SysBase(a4),a1         ; Find the clock frequency
        move.l  #715909,d0
        cmp.b   #50,PowerSupplyFrequency(a1)
        bne.s   \ntsc
        move.l  #709379,d0
\ntsc   move.l  d0,clockfreq(a4)

        bsr     findcursong             ; Find the song to start playing

        moveq   #gmod_StartMusic,d1     ; Start playing the music
        move.l  modmem(a4),a0           ; Convenience for built-in players
        gmodmaycall d1

        clr.l   _songtime(a4)           ; Clear the song timer

        bclr.b  #SB_REPEAT,_sysflags(a4) ; Kill any stray repeat flags

        bra.s   19$
15$
        moveq   #gmod_ContinueMusic,d1  ; Try to continue the music
        moveq   #0,d0
        gmodmaycall d1
        tst.l   d0
        beq     7$
19$
        tst.b   portablemodule(a4)      ; Set the module's master volume
        bnz     \hasvol
        moveq   #gmod_SetVolume,d0
        bsr     gmodcheck
        bz.b    \novol
\hasvol
        st.b    _playing(a4)
        bsr     playervolume
        clr.b   _playing(a4)
        bra.b   \finvol
\novol
        lea     volume(a4),a0           ; Disable the volume control
        sub.l   a1,a1
        moveq   #-1,d0
        bsr     GlobSetWord
\finvol

        lea     audisave(a4),a3         ; Set the audio channel interrupts
        lea     audints(a4),a2
        moveq   #gmod_Audio0,d2
        moveq   #INTB_AUD0,d3
        move.w  #INTF_SETCLR,d4
        bsr     88$
        bsr     88$
        bsr     88$
        bsr     88$
        move.w  d4,CUSTOM+intena

        moveq   #gmod_VBlank50,d0       ; Set the timing interrupt
        bsr     gmodcheck
        beq.s   21$
        moveq   #50,d0
        lea     gmod_VBlank50(a5),a0
        bra.s   28$
21$     moveq   #gmod_VBlank60,d0
        bsr     gmodcheck
        beq.s   22$
        moveq   #60,d0
        lea     gmod_VBlank60(a5),a0
        bra.s   28$
22$     moveq   #gmod_TimerTick,d0
        bsr     gmodcheck
        beq.s   29$
        moveq   #0,d0
        jsr     gmod_GetFrequency(a5)
        tst.l   d0
        beq.s   29$
        bpl.s   27$                     ; Convenience for internal players
        moveq   #50,d0
        bset    #MTB_MODSPEED,modtype(a4)
27$     lea     gmod_TimerTick(a5),a0
28$     move.l  modmem(a4),a1           ; a1 = module pointer (for our players)
        bsr     settint
        bra.b   \speeddone
29$
        lea     speed(a4),a0            ; No speed control - disable the speed gadget
        sub.l   a1,a1
        moveq   #0,d0
        bsr     GlobSetWord
\speeddone

        cmp.b   #2,filter(a4)           ; Set the filter
        bne.b   \filtoff
\filton
        bclr.b  #CIAB_LED,CIAA+ciapra
        bra.b   \filtdone
\filtoff
        bset.b  #CIAB_LED,CIAA+ciapra
\filtdone

        st.b    _playing(a4)            ; Set playing flag
9$      moveq   #0,d0                   ; Return success code
        rts

88$     move.l  a2,a1
        move.l  d2,d0
        bsr     gmodcheck
        beq.s   89$
        move.b  #NT_INTERRUPT,LN_TYPE(a1)       ; Set the interrupt vector
        lea     audiointname(a4),a0
        move.l  a0,LN_NAME(a1)
        lea     0(a5,d2.w),a0
        move.l  a0,IS_CODE(a1)
        move.l  a4,IS_DATA(a1)
        move.l  d3,d0
        move.l  _SysBase(a4),a6
        jsr     _LVOSetIntVector(a6)
        move.l  d0,(a3)
        bset    d3,d4                   ; Turn on the interrupt later
89$     addq.l  #4,d2
        addq.l  #4,a3
        addq.l  #1,d3
        adda.w  #IS_SIZE,a2
        rts

7$      bsr     stpmod                  ; Stop whatever was started

        moveq   #1,d0                   ; Return can't continue error code
        rts

*** stpmod - (Internal) - Stop a playing module
stpmod:
        tst.b   _playing(a4)            ; Turn the LED back on
        bz.b    \noled
        bclr.b  #CIAB_LED,CIAA+ciapra
\noled

        clr.b   _playing(a4)            ; Clear the playing flag

        bsr     stoptint                ; Stop timer interrupts

        lea     audisave(a4),a2         ; Stop audio channel interrupts
        moveq   #INTB_AUD0,d2
        move.w  #INTF_AUD0!INTF_AUD1!INTF_AUD2!INTF_AUD3,CUSTOM+intena
        move.w  #INTF_AUD0!INTF_AUD1!INTF_AUD2!INTF_AUD3,CUSTOM+intreq
        bsr.s   88$
        bsr.s   88$
        bsr.s   88$
        bsr.s   88$
        bra.s   85$
88$     move.l  d2,d0
        addq.l  #1,d2
        move.l  (a2)+,d1
        beq.s   89$
        clr.l   -4(a2)
        move.l  d1,a1
        move.l  _SysBase(a4),a6
        jmp     _LVOSetIntVector(a6)
89$     rts
85$
        move.l  gmodptr(a4),d0          ; Stop playing the music
        beq.s   29$
        move.l  d0,a5
        moveq   #gmod_StopMusic,d0
        gmodmaycall d0
29$
        bsr     clearflash

        bra     freeaudio               ; Free the audio channels


*** loadmod - Load a module without playing it
* a0 = Pointer to filename
* Returns: d0 = Null if successful, pointer to error message if failed
_loadmod
        move.l  4(sp),a0
loadmod
        movem.l d2-d7/a2-a6,-(sp)
        move.l  a0,a2

        bsr     endmod                  ; First make sure nothing is playing

        move.l  sp,stackpt(a4)          ; Save the stack pointer in case we die

        move.l  a2,a0                   ; Set/allocate the modfile string
        bsr     setmodfile

        bsr     ldmod                   ; Load the module
        bz.s    9$
        subq.l  #1,d0                   ; Find the real return code

9$      bset    #SB_WINDOWUP,_sysflags(a4)
        movem.l (sp)+,d2-d7/a2-a6
        rts


*** playmod - Play an already-loaded module
* d0 = Song number (-1 = user's choice)
* Returns: d0 = Null if successful, pointer to error message if failed
_playmod
        move.l  4(sp),d0
playmod
        movem.l d2-d7/a2-a6,-(sp)
        tst.l   d0                      ; Only change songs if it's specified
        bmi.s   1$
        move.l  d0,cursong(a4)          ; Save the song number
1$
        bsr     stpmod                  ; Stop any previously playing song

        move.l  sp,stackpt(a4)          ; Save the stack pointer in case we die

        moveq   #0,d0                   ; Start playing the module
        bsr     plmod

        bset    #SB_WINDOWUP,_sysflags(a4)

        moveq   #0,d0                   ; Return a success code
        movem.l (sp)+,d2-d7/a2-a6
        rts

*** startmod - Load a module and start it playing
* a0 = Pointer to filename
* d0 = Song number (-1 = user's choice)
* Returns: d0 = Null if successful, pointer to error message if failed
_startmod
        move.l  4(sp),a0
        move.l  8(sp),d0
startmod
        movem.l d2-d7/a2-a6,-(sp)
        move.l  d0,cursong(a4)          ; Save the song number
        move.l  a0,a2

        bsr     endmod                  ; First make sure nothing is playing

        move.l  sp,stackpt(a4)          ; Save the stack pointer in case we die

        move.l  a2,a0                   ; Set/allocate the modfile string
        bsr     setmodfile

        bsr     ldmod                   ; Load the module
        bz.s    5$
        subq.l  #1,d0
        bnz.s   9$
        move.l  d0,-(sp)
        move.l  a0,-(sp)
        bsr     _progstart              ; Start the program too
        addq    #8,sp
        bra.s   9$

5$      moveq   #0,d0                   ; Play the module
        bsr     plmod

        moveq   #0,d0
9$      bset    #SB_WINDOWUP,_sysflags(a4)
        movem.l (sp)+,d2-d7/a2-a6
        rts

*** contmod - Continue a module from where it left off
* Returns: d0 = Null if successful, error message if failed
contmod
_contmod
        tst.b   _playing(a4)            ; See if it's already playing
        bne.s   99$

        movem.l d2-d7/a2-a6,-(sp)
        move.l  sp,stackpt(a4)          ; Save the stack pointer in case we die

        moveq   #1,d0                   ; Continue the module
        bsr     plmod
        beq.s   19$
        lea     nocontinue(a4),a0       ; Can't continue - error message
        move.l  a0,d0
19$
        movem.l (sp)+,d2-d7/a2-a6
        rts

        bset    #SB_WINDOWUP,_sysflags(a4)

99$     moveq   #0,d0                   ; Return a success code immediately
        rts

*** contplaymod - Continue current module if possible, otherwise restart it
* Returns: d0 = Null if successful, error message if failed
contplaymod:
_contplaymod:
        bsr     contmod
        lea     nocontinue(a4),a0
        cmp.l   a0,d0
        bne.s   9$
        moveq   #-1,d0
        bsr     playmod
9$      rts

*** stopmod - Stop a playing module
* Returns: d0 = NULL
stopmod:
_stopmod:
        movem.l d2-d7/a2-a6,-(sp)       ; Stop the module
        bsr     stpmod
        movem.l (sp)+,d2-d7/a2-a6

        bset    #SB_WINDOWUP,_sysflags(a4)

        moveq   #0,d0
        rts

*** endmod - Stop and unload a module
* Returns: d0 = NULL
endmod:
_endmod:
        movem.l d2-d7/a2-a6,-(sp)
        move.l  sp,stackpt(a4)
        moveq   #0,d3
        bra.s   die

*** die - and other forms of death (from the module player only)
dienotloaded:                           ; No module loaded
        lea     notloaded(a4),a0
        bra.s   diemes
diedontcompress:                        ; Reloaded compressed module
        lea     dontcompress(a4),a0
        bra.s   diemes
dienofile:                              ; File not found
        lea     nofile(a4),a0
        bra.s   diemes
dieunkmod:                              ; File is not a module
        lea     unkmod(a4),a0
        bra.s   diemes
diecorrupt:                             ; Module is corrupt
        lea     cormod(a4),a0
        bra.s   diemes
dieerrplay:                             ; Error playing module
        lea     errplay(a4),a0
        bra.s   diemes
dienoabs:
        lea     noabs(a4),a0            ; Couldn't allocate absolute memory
        bra.s   diemes
dienotimer:
        lea     notimer(a4),a0          ; Couldn't allocate either CIAB timer
        bra.s   diemes
dienocmem:                              ; Not enough memory
        lea     nocmem(a4),a0
        bra.s   diemes
dienomem:                               ; Not enough memory
        lea     nomemtext(a4),a0
diemes:
        move.l  a0,d3
die:
        bsr     stpmod                  ; Stop playing the module

        move.l  gmodptr(a4),d0          ; Prepare to unload
        beq.s   29$
        move.l  d0,a5
        moveq   #gmod_EndMusic,d0
        gmodmaycall d0
        clr.l   gmodptr(a4)
29$
        clr.l   songcatalog(a4)         ; Erase all pointers to memory we are about to free
        clr.l   infolist(a4)

        bsr     _infowinattach          ; Clear out the info window

        lea     modremember(a4),a0      ; Free any miscellaneous memory we allocated for the module
        moveq   #1,d0
        move.l  _IntuitionBase(a4),a6
        jl      FreeRemember

        bsr     closeloadhan            ; Close any handle we were trying to load with

        lea     modspec(a4),a0          ; Free the module memory
        bsr     unloadfile

        lea     suppspec(a4),a0         ; Free any secondary data file
        bsr     unloadfile

        moveq   #1,d0                   ; Default to one song
        move.l  d0,numsongs(a4)
        clr.l   cursong(a4)

        clr.b   modtype(a4)             ; Default no special module type

        clr.l   authorname(a4)          ; Control-panel text fields
        clr.l   modtypename(a4)
        clr.l   modname(a4)

        clr.l   ntauthor(a4)

        clr.b   noaudalloc(a4)

        moveq   #-1,d0
        move.l  d0,_songlen(a4)

        move.w  d0,_fadevol(a4)

        bset    #SB_WINDOWUP,_sysflags(a4)

        bsr     freemodfile             ; Free the modfile buffer

        move.l  stackpt(a4),sp          ; Return to caller
        move.l  d3,d0
        movem.l (sp)+,d2-d7/a2-a6
        rts


*** moveabs - Move the module to an absolute position in memory (reload if necessary)
* a0 = Position to move module to
* Returns: a0 = New module position
moveabs:
        apush4

        move.l  modsize(a4),d0          ; Allocate the required memory
        move.l  d0,d2
        move.l  a0,a1
        move.l  a0,a2
        move.l  _SysBase(a4),a6
        jl      AllocAbs
        tst.l   d0
        bz.b    1$

        move.l  modmem(a4),a0           ; Copy the module to the final location
        move.l  a2,a1
        move.l  d2,d0
        jl      CopyMem

        move.l  modmem(a4),a1           ; Free the old memory block
        move.l  modalloc(a4),d0
        jl      FreeMem

        move.l  a2,modmem(a4)           ; Point to the new block
        move.l  d2,modalloc(a4)
        add.l   d2,a2
        move.l  a2,modend(a4)

        bra     9$

1$      move.l  modmem(a4),a1           ; First free the old block
        move.l  modalloc(a4),d0
        jl      FreeMem
        clr.l   modmem(a4)

        move.l  a2,a1                   ; Now see if we can allocate the memory
        move.l  d2,d0
        jl      AllocAbs
        tst.l   d0
        bz      dienoabs

        move.l  a2,modmem(a4)           ; Point to the new memory block
        move.l  d2,modalloc(a4)
        add.l   d2,a2
        move.l  a2,modend(a4)

        lea     modspec(a4),a1          ; Re-load the file at the required address
        move.l  modfile(a4),a0
        bsr     loadfile
        beq     dienofile

9$      move.l  modmem(a4),a0
        apop4
        rts


*** hookfunc - Hook function called by modules
* a1 = Parameter packet
hookfunc:
        move.l  (a1)+,d0
        cmpi.l  #GMODHF_SEQUENCE,d0
        beq.s   \sequence
        cmpi.l  #GMODHF_REPEAT,d0
        beq     intrepeat
        moveq   #0,d0
        rts

\sequence:
        move.l  (a1)+,d0
        move.l  (a1)+,d1
        bra     intsongpos


*** getfreqmodspeed - Get the default module speed and set the MODSPEED flag
getfreqmodspeed:
        moveq   #50,d0
        bset    #MTB_MODSPEED,modtype(a4)
        rts


*** parseauthor - Find the author name in a string which potentially contains one
* a0 = Pointer to string
* Returns:
* a0 = Pointer to beginning of author name or NULL if none found
* a1 = Pointer to first character relating to the author name (<= a0)
parseauthor:
        move.l  a0,a1
        bra.s   11$                     ; Search for 'by' keyword
1$      move.l  a0,a1
        move.b  (a0)+,d0
        bz.s    9$
        cmp.b   #$a9,d0                 ; Check for "©"
        beq     99$
        bsr     isalpha
        bnz.s   1$
11$     move.b  (a0),d0
        bsr     toupper
        cmp.b   #'C',d0                 ; Check for "(C)"
        bne.s   12$
        cmp.b   #'(',-1(a0)
        bne.s   12$
        cmp.b   #')',1(a0)
        beq.s   92$
12$     cmp.b   #'B',d0                 ; Check for "BY"
        bne.s   1$
        move.b  1(a0),d0
        bsr     toupper
        cmp.b   #'Y',d0
        bne.s   1$
        move.b  2(a0),d0
        bsr     isalpha
        bnz.s   1$
92$     addq.l  #2,a0
99$     rts

9$      sub.l   a0,a0                   ; Return null for both strings
        move.w  a0,a1
        rts

*** prepstring - Prepare author/song name string for display - chop puctuation, etc.
* a0 = String to prepare
* Returns:
* a0 = New pointer to string
prepstring:
        move.l  a2,-(sp)

        bsr     stripblanks             ; Strip leading and trailing blanks
        bz.s    9$

        move.l  a0,a1                   ; Hunt down and eliminate EdPlayer junk
1$      move.b  (a1)+,d0
        bz.s    19$
        cmp.b   #'`',d0
        bne.s   1$
        lea     -1(a1),a2
        move.l  a2,-(sp)
        cmp.b   #'`',(a1)+
        bne.s   13$
        subq.l  #1,a1
        addq.l  #1,(sp)
13$     move.b  (a1)+,(a2)+
        bne.s   13$
        move.l  (sp)+,a1
        bra.s   1$
19$
        subq.l  #1,a1                   ; Eliminate some punctuation at the end
21$     cmp.l   a0,a1
        beq.s   \empty
        cmp.b   #'.',-(a1)
        beq.s   25$
        cmp.b   #',',(a1)
        bne.s   29$
25$     clr.b   (a1)
        bra.s   21$
29$
9$      move.l  (sp)+,a2
        rts
\empty:
        sub.l   a0,a0
        bra.s   9$


*** ntgetsongname - Get song name (and possibly author) from a NoiseTracker module
ntgetsongname:
        move.l  modmem(a4),a0           ; See if the author's name is in the title
        bsr     parseauthor
        move.l  a0,d0
        bz.s    9$

        clr.b   (a1)                    ; Separate it from the title
        bsr     prepstring
        move.l  a0,ntauthor(a4)

9$      move.l  modmem(a4),a0           ; Return the title
        bsr     prepstring
        move.l  a0,d0
        rts

*** ntgetsongauthor - Get author name from a NoiseTracker module
ntgetsongauthor:
        movem.l a2-a3,-(sp)

        move.l  ntauthor(a4),d0         ; See if we already found the author
        bnz     9$

        bsr     addinfopad              ; Display the instrument list verbatim
        move.l  modmem(a4),a0
        lea     20(a0),a2
        lea     31*30(a2),a3
\info
        tst.b   (a2)
        bz.b    \noinfo
        move.l  a2,a0
        bsr     addinfo
\noinfo
        lea     30(a2),a2
        cmp.l   a3,a2
        bne.b   \info

        move.l  modmem(a4),a0           ; Find an entry with author keywords
        lea     20(a0),a2
        lea     31*30(a2),a3
1$      move.l  a2,a0
        bsr     parseauthor
        move.l  a0,d0
        bnz.s   2$
        lea     30(a2),a2
        cmp.l   a3,a2
        bne.s   1$

        move.l  modmem(a4),a0           ; Find an IntuiTracker scroll line
        lea     20(a0),a0
        lea     31*30(a0),a3
3$      cmp.b   #'#',(a0)+
        beq.s   4$
        lea     30-1(a0),a0
        cmp.l   a3,a0
        bne.s   3$

        moveq   #0,d0                   ; Nothing found at all
        bra.s   9$

2$      bsr     prepstring              ; Strip leading and trailing blanks
        move.l  a0,d0
        bnz.s   9$

        lea     30(a2),a0               ; Nothing on that line - use the next line
        cmp.b   #'#',(a0)               ; Skip leading IntuiTracker junk
        bne.s   4$
        addq.l  #1,a0
        cmp.b   #'#',(a0)               ; If there are *two*, it's probably decoration
        bne.s   4$
        subq.l  #1,a0
4$      bsr     prepstring
        move.l  a0,d0

9$      movem.l (sp)+,a2-a3
        rts


*** gmodstart - Code for general modules (GMOD's)
gmodstart:
        move.l  gmod_LoadAddress(a0),d1         ; Move the module if necessary
        beq.s   19$
        move.l  d1,a0
        bsr     moveabs
19$     move.l  a0,d0
        move.l  gmod_Maker(a0),gmodnametype(a4) ; Find the module's maker ID
        rts

*** amodstart - Code for Absolute Executable modules (AMOD's)
amodloc equ     $10

amodstart:
        move.l  amodloc(a0),a0          ; Move to the required position
        bsr     moveabs
        ; Fall through and execute like an XMOD...
*** xmodstart - Code for Executable modules (XMOD's)
xminitentry     equ     $4
xmvbentry       equ     $8
xmendentry      equ     $c

xmodstart:
        plstartret 9$

        cnop    0,4
        dc.l    gmod_Hook
9$      gmodnop
        gmodbra 2$                      ; StartMusic
        gmodbra 3$                      ; StopMusic
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodbra getfreqmodspeed         ; GetFrequency
        jmp     xmvbentry(a1)           ; TimerTick
        lea     xmodname(a4),a0         ; GetMakerName
        move.l  a0,d0
        rts

2$      move.l  modmem(a4),a0
        jmp     xminitentry(a0)

3$      move.l  modmem(a4),a0
        jmp     xmendentry(a0)

*** ntstart - Code to differentiate between ST/NT/PT and StarTrekker
ntststart:
        move.l  modfile(a4),a0          ; Check for a StarTrekker ".NT" file
        move.l  sp,a2
        moveq   #4,d0
11$     addq.l  #1,d0
        tst.b   (a0)+
        bne.s   11$
        bclr    #0,d0
        sub.l   d0,sp
        move.l  sp,a1
        move.l  modfile(a4),a0
        bra.s   13$
12$     move.b  d0,(a1)+
13$     move.b  (a0)+,d0
        bne.s   12$
        move.b  #'.',(a1)+
        move.b  #'N',(a1)+
        move.b  #'T',(a1)+
        clr.b   (a1)
        move.l  sp,a0
        lea     suppspec(a4),a1
        bsr     loadfile
        move.l  a2,sp
        bnz     trekstart               ; Go to appropriate startup
        bra     ntstart

*** genembstart - Code for generic embedded-player modules (such as NoiseTracker packed w/player)
genembstart:
        cmpi.w  #$4efa,4(a0)            ; Make sure the other two JMP's exist
        bne     diecorrupt
        cmpi.w  #$4efa,8(a0)
        bne     diecorrupt

        plstartret 9$

        cnop    0,4
        dc.l    gmod_Hook
9$      gmodnop
        gmodbra 2$                      ; StartMusic
        gmodbra 3$                      ; StopMusic
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodbra getfreqmodspeed         ; GetFrequency
        gmodbra 5$                      ; TimerTick
        lea     genembname(a4),a0       ; GetMakerName
        move.l  a0,d0
        rts

2$      movem.l d2-d7/a2-a6,-(sp)
        moveq   #0,d0
        move.l  modmem(a4),a5
        jsr     (a5)
        movem.l (sp)+,d2-d7/a2-a6
        rts

3$      movem.l d2-d7/a2-a6,-(sp)
        move.l  modmem(a4),a5
        jsr     8(a5)
        movem.l (sp)+,d2-d7/a2-a6
        rts

5$      movem.l d2-d7/a2-a6,-(sp)
        jsr     4(a1)
        movem.l (sp)+,d2-d7/a2-a6
        rts

*** fxstart - Code for SoundFX modules
fxstart:
        movem.l a5/a6,-(sp)
        move.l  modmem(a4),a5
        move.l  _SysBase(a4),a6

        lea     $80(a5),a0              ; Make sure it's a SoundFX module
        move.w  #($a20-$80)/4-1,d0
        moveq   #0,d1
1$      add.l   (a0)+,d1
        dbra    d0,1$
        cmpi.l  #$97adddd8,d1
        bne     diecorrupt

        move.l  $20(a5),d0              ; Relocate the player
        add.l   d0,d0
        add.l   d0,d0
        cmp.l   modsize(a4),d0
        bge     diecorrupt
        lea     $30(a5,d0.l),a0
        cmpi.l  #$3ec,-$c(a0)
        bne     diecorrupt
        moveq   #49-1,d0
        lea     $24(a5),a1
        move.l  a1,d2
12$     move.l  (a0)+,d1
        add.l   d2,0(a1,d1.l)
        dbra    d0,12$

        move.l  #$4e714e71,$196(a5)     ; NOP out AddICRVector
        move.l  #$4e714e71,$1a8(a5)     ; NOP out call to set CIA stuff

        bsr     clearcache

        movem.l (sp)+,a5/a6
        plstartret 9$

2$      movem.l d2-d7/a2-a6,-(sp)       ; Jump into player start code
        move.l  modmem(a4),a0
        jsr     $13e(a0)
        movem.l (sp)+,d2-d7/a2-a6
        rts

5$      move.l  modmem(a4),a0           ; Calculate interrupt frequency
        move.l  #715909,d0
        divu.w  $a64(a0),d0
        ext.l   d0
        rts

        cnop    0,4
        dc.l    gmod_Hook
9$      gmodnop
        gmodbra 2$                      ; StartMusic
        gmodnop
        gmodnop
        gmodnop
        gmodq   1
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodbra 5$                      ; GetFrequency
        jmp     $466(a1)                ; TimerTick
        lea     fxname(a4),a0           ; GetMakerName
        move.l  a0,d0
        rts

*** whitstart - Code for Dave Whittaker modules
dwinitentry     equ     $00
dwvbentry       equ     $0e
dwendentry      equ     $1c

whitstart:
        moveq   #6-1,d0         ; Make sure it's really a DW module
1$      move.w  $0(a0),d1
        add.w   $4(a0),d1
        add.w   $8(a0),d1
        add.w   $c(a0),d1
        cmpi.w  #$453b,d1
        bne     diecorrupt
        adda.w  #$e,a0
        dbra    d0,1$

        plstartret 9$

        cnop    0,4
        dc.l    gmod_Hook
9$      gmodnop
        gmodbra 2$                      ; StartMusic
        gmodbra 3$                      ; StopMusic
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodbra getfreqmodspeed         ; GetFrequency
        jmp     dwvbentry(a1)           ; TimerTick
        lea     whitname(a4),a0         ; GetMakerName
        move.l  a0,d0
        rts

2$      move.l  modmem(a4),a0
        jmp     dwinitentry(a0)

3$      move.l  modmem(a4),a0
        jmp     dwendentry(a0)

*** deltastart - Code for Delta Music modules
deltastart:
        plstartret 9$

        cnop    0,4
        dc.l    gmod_Hook
9$      gmodnop
        gmodbra 2$                      ; StartMusic
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodbra getfreqmodspeed         ; GetFrequency
        gmodbra 4$                      ; TimerTick
        lea     deltaname(a4),a0        ; GetMakerName
        move.l  a0,d0
        rts

2$      push    d2-d7/a2-a6
        move.l  modmem(a4),a0
        moveq   #1,d0
        jsr     (a0)
        pop     d2-d7/a2-a6
        rts

4$      push    d2-d7/a2-a6
        moveq   #0,d0
        jsr     (a1)
        pop     d2-d7/a2-a6
        rts

delta2start:                            ; Different version (older probably)
        plstartret 9$

        cnop    0,4
        dc.l    gmod_Hook
9$      gmodnop
        gmodbra 2$                      ; StartMusic
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodbra getfreqmodspeed         ; GetFrequency
        gmodbra 4$                      ; TimerTick
        lea     deltaname(a4),a0        ; GetMakerName
        move.l  a0,d0
        rts

2$      push    d2-d7/a2-a6
        move.l  modmem(a4),a0
        jsr     (a0)
        pop     d2-d7/a2-a6
        rts

4$      push    d2-d7/a2-a6
        jsr     $17c(a1)
        pop     d2-d7/a2-a6
        rts

*** sid2start - Code for SidMon modules
sid2start:
        plstartret 9$

        cnop    0,4
        dc.l    gmod_Hook
9$      gmodnop
        gmodbra 2$                      ; StartMusic
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodbra getfreqmodspeed         ; GetFrequency
        jmp     $16a(a1)                ; TimerTick
        lea     sidname(a4),a0          ; GetMakerName
        move.l  a0,d0
        rts

2$      move.l  modmem(a4),a0           ; Put an RTS after the interrupt routine
        move.w  #$4e75,$25c(a0)
        bsr     clearcache

        move.l  modmem(a4),a0           ; Call the initialization routine
        push    d2-d7/a2-a6
        jsr     $2c(a0)
        pop     d2-d7/a2-a6
        rts

*** markiistart - Code for Mark II modules
markiistart:
        cmpi.l  #$41fa035e,4(a0)        ; Check a little more...
        bne     diecorrupt

        plstartret 9$

        cnop    0,4
        dc.l    gmod_Hook
9$      gmodnop
        gmodbra 2$                      ; StartMusic
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodbra getfreqmodspeed         ; GetFrequency
        gmodbra 3$                      ; TimerTick
        lea     markiiname(a4),a0       ; GetMakerName
        move.l  a0,d0
        rts

2$      move.l  modmem(a4),a0
        moveq   #-1,d0
        push    d2-d7/a2-a6
        jsr     (a0)
        pop     d2-d7/a2-a6
        rts

3$      moveq   #0,d0
        push    d2-d7/a2-a6
        jsr     (a1)
        pop     d2-d7/a2-a6
        rts

*** ftmstart - Code for Face The Music modules

ftm_init        equ     4
ftm_end         equ     8
ftm_load        equ     12              ; d0 = filename, returns nonzero = success
ftm_unload      equ     16
ftm_play        equ     20
ftm_stop        equ     24
ftm_set1        equ     28
ftm_set2        equ     32
ftm_get1        equ     36
ftm_get2        equ     40
ftm_waitdone    equ     44
ftm_get3        equ     48
ftm_ccall       equ     52

ftmstart:
        apush4

        st.b    noaudalloc(a4)

        lea     modspec(a4),a0          ; We can't use the already-loaded module!
        bsr     unloadfile

        move.l  _DOSBase(a4),a6         ; Load PlayFTM
        lea     playftmname(a4),a0
        move.l  a0,d1
        jl      LoadSeg
        move.l  d0,ftmseg(a4)
        bz.b    \noplayer

        lsl.l   #2,d0                   ; Find jump table (a5, ftmtab)
        addq.l  #4,d0
        move.l  d0,ftmtab(a4)
        move.l  d0,a5

        jsr     ftm_init(a5)            ; Initialize PlayFTM

        move.l  modfile(a4),d0          ; Re-load the module through PlayFTM
        jsr     ftm_load(a5)
        tst.l   d0
        bz      \corrupt

        apop4
        plstartret 9$

\noplayer
        lea     ftmnoplayer(a4),a0
        bra     diemes

\corrupt
        lea     corrcomp(a4),a0
        bra     diemes

\start
        moveq   #0,d0                      ; Start playing
        moveq   #0,d1
        moveq   #0,d2
        moveq   #0,d3
        move.l  ftmtab(a4),a0
        jmp     ftm_play(a0)

\stop
        move.l  ftmtab(a4),a0
        jmp     ftm_stop(a0)

\end
        move.l  ftmtab(a4),a0
        jsr     ftm_unload(a0)
        jmp     ftm_end(a0)

        cnop    0,4
        dc.l    gmod_Hook
9$      gmodnop
        gmodbra \start
        gmodbra \stop
        gmodbra \end
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        lea     ftmname(a4),a0          ; GetMakerName
        move.l  a0,d0
        rts

*** unloadftm - FIXME - KLUDGE - unload the FTM player (called by main.c)
        xdef    _unloadftm
_unloadftm
        apush4
        move.l  _DOSBase(a4),a6
        move.l  ftmseg(a4),d1
        bz.b    1$
        jl      UnLoadSeg
1$      apop4
        rts


*** setnormtint - Set the timer interrupt using the current module's default speed
* a0 = Pointer to interrupt routine to be called on interrupts
* a1 = Data that should appear in register a1 on interrupt calls
setnormtint:
        moveq   #50,d0
        ; fall through...
*** settint - Set the timer interrupt
* a0 = Pointer to interrupt routine to be called on interrupts
* a1 = Data that should appear in register a1 on interrupt calls
* d0 = Number of ticks per second (frequency) to call interrupt routine
settint:
        movem.l a2/a3,-(sp)

        move.l  a0,sint+IS_CODE(a4)     ; Initialize the timer interrupt
        move.l  a1,sint+IS_DATA(a4)
        move.b  #NT_INTERRUPT,d1
        move.b  d1,sint+LN_TYPE(a4)
        move.b  d1,tint+LN_TYPE(a4)
        lea     sint(a4),a0
        move.l  a0,tint+IS_DATA(a4)
        move.l  _SysBase(a4),a0
        lea     _LVOCause(a0),a0
        move.l  a0,tint+IS_CODE(a4)

        move.w  d0,-(sp)

        move.l  _BattMemBase(a4),a6
        lea     tint(a4),a1             ; First try to get timer B
        moveq   #CIAICRB_TB,d0
        move.b  d0,timernum(a4)
        move.l  #CIAB+ciatblo,a2
        move.l  a2,timerlo(a4)
        move.l  #CIAB+ciacrb,a3
        jsr     _LVOAddICRVector(a6)
        tst.l   d0
        beq.s   1$

        lea     tint(a4),a1             ; Already taken - try timer A
        moveq   #CIAICRB_TA,d0
        move.b  d0,timernum(a4)
        move.l  #CIAB+ciatalo,a2
        move.l  a2,timerlo(a4)
        move.l  #CIAB+ciacra,a3
        jsr     _LVOAddICRVector(a6)
        tst.l   d0
        bne     dienotimer
1$
        and.b   #%11000000,(a3)         ; Set the control register
        tst.b   timernum(a4)
        beq.s   3$
        bclr    #6,(a3)
3$
        move.w  (sp)+,d0                ; Set the timer frequency
        bsr     changetint

        bset    #0,(a3)                 ; Start the timer

        movem.l (sp)+,a2-a3

        move.b  timernum(a4),d1         ; Enable the timer interrupt
        moveq   #-$80,d0
        bset    d1,d0
        jmp     _LVOAbleICR(a6)

*** changetint - Change the timer interrupt speed (callable from an interrupt)
* d0 = New frequency (Hz)
* All registers saved
changetint:
        tst.l   tint+IS_CODE
        bz.b    9$
        push    d0-d1/a0-a1
        lea     _LinkerDB,a1

        move.w  d0,tintfreq(a1)         ; Store the new frequency

        tst.l   tint+IS_CODE(a1)
        bz.b    9$

        mulu.w  mastfreq(a1),d0
        lsr.l   #8,d0
        bz.b    \zero

        move.l  clockfreq(a1),d1        ; Poke the new timer value
        divu.w  d0,d1
        move.l  timerlo(a1),a0
        move.b  d1,(a0)
        lsr.w   #8,d1
        move.b  d1,$100(a0)
\zero
        pop     d0-d1/a0-a1
9$      rts

*** changetintcia - Change the timer interrupt speed (callable from an interrupt)
* d0 = CIA speed value
* All registers saved
changetintcia:
        push    d0-d1/a0-a1
        lea     _LinkerDB,a1

        tst.l   tint+IS_CODE(a1)
        bz.b    9$

        move.l  timerlo(a1),a0          ; Poke the new timer value
        moveq   #0,d1
        move.w  d0,d1
        lsl.l   #8,d1
        divu.w  mastfreq(a1),d1
        move.b  d1,(a0)
        lsr.w   #8,d1
        move.b  d1,$100(a0)

        move.l  clockfreq(a1),d1        ; Find the new frequency to display
        divu.w  d0,d1
        move.w  d1,tintfreq(a1)

9$      pop     d0-d1/a0-a1
        rts

*** stoptint - Stop the timer interrupt if it was installed
stoptint:
        tst.l   tint+IS_CODE(a4)        ; Make sure it was installed
        beq.s   9$
        moveq   #0,d0                   ; Remove the interrupt handler
        move.b  timernum(a4),d0
        lea     tint(a4),a1
        move.l  _BattMemBase(a4),a6
        jsr     _LVORemICRVector(a6)
        clr.l   tint+IS_CODE(a4)
9$      rts



*** playervolume - Update the master volume for the currently playing module
playervolume
_playervolume
        apush4

        tst.b   _playing(a4)            ; Make sure we're playing something
        bz      \out

        move.w  mbalance(a4),d2         ; Find the new overall volume (d0)
        move.w  balance(a4),d3
        move.w  mvolume(a4),d1
        move.w  volume(a4),d0
        bpl.b   \hasvol
        moveq   #100,d0
        moveq   #100,d1
        moveq   #0,d2
        moveq   #0,d3
\hasvol
        mulu.w  d1,d0
        lsl.l   #8,d0
        divu.w  #10000,d0
        move.w  d0,d1

        tst.w   d2                      ; Adjust for master balance
        bz.b    \mgot
        bmi.b   \mmoreleft
\mmoreright
        neg.w   d2
        add.w   #50,d2
        mulu.w  d2,d0
        divu.w  #50,d0
        bra.b   \mgot
\mmoreleft
        add.w   #50,d2
        mulu.w  d2,d1
        divu.w  #50,d1
\mgot

        tst.w   d3                      ; Adjust for module balance
        bz.b    \got
        bmi.b   \moreleft
\moreright
        neg.w   d3
        add.w   #50,d3
        mulu.w  d3,d0
        divu.w  #50,d0
        bra.b   \got
\moreleft
        add.w   #50,d3
        mulu.w  d3,d1
        divu.w  #50,d1
\got

        move.w  _fadevol(a4),d2         ; Adjust for fading
        bmi.b   \nofade
        mulu.w  d2,d0
        lsr.w   #7,d0
        mulu.w  d2,d1
        lsr.w   #7,d1
\nofade

        tst.b   portablemodule(a4)      ; Tell the module about the new volume
        bnz     \portavol
        move.l  gmodptr(a4),a5
        moveq   #gmod_SetVolume,d2
        gmodmaycall d2
        bra     \out
\portavol
        bsr     NoteSysMasterVolBal
\out
        apop4
        rts

*** playerspeed - Update the master speed
playerspeed
_playerspeed
        push    a6

        move.w  speed(a4),d0            ; Calculate the new relative speed
        bnz.b   \ok
        moveq   #50,d0
\ok
        move.w  mspeed(a4),d1
        mulu.w  d1,d0
        lsl.l   #8,d0
        divu.w  #50*50,d0
        move.w  d0,mastfreq(a4)

        move.l  _SysBase(a4),a6         ; Set the new speed
        jl      Disable
        move.w  tintfreq(a4),d0
        bsr     changetint
        jl      Enable

        pop     a6
        rts


*** jumpforward - Jump one sequence forward in the module
jumpforward
        move.l  gmodptr(a4),d0
        bz      \out
        push    d5/a5
        move.l  d0,a5
        moveq   #0,d0
        move.l  songpos(a4),d1
        addq.l  #1,d1
        moveq   #gmod_Jump,d5
        gmodmaycall d5
        pop     d5/a5
\out
        rts

*** jumpback - Jump one sequence backward in the module
jumpback
        move.l  gmodptr(a4),d0
        bz      \out
        push    d5/a5
        move.l  d0,a5
        moveq   #0,d0
        move.l  songpos(a4),d1
        bz.b    \out1
        subq.l  #1,d1
        moveq   #gmod_Jump,d5
        gmodmaycall d5
\out1
        pop     d5/a5
\out
        rts


*** iscantcont - See if an error is a 'can't continue' error
* a0 = Pointer to message in question
* Returns: d0 = nonzero if a0 is a can't continue error
_iscantcont
        move.l  4(sp),a0
iscantcont
        moveq   #0,d0
        lea     nocontinue(a4),a1
        cmp.l   a1,a0
        seq     d0
        rts

*** dmawait - DMA delay wait
dmawait:
        movem.l d0-d1,-(sp)
        moveq   #7-1,d1
1$      move.b  $dff006,d0
2$      cmp.b   $dff006,d0
        beq.s   2$
        dbf     d1,1$
        movem.l (sp)+,d0-d1
        rts


*** clearcache - Clear the code cache
clearcache:
        move.l  a6,-(sp)
        move.l  4,a6
        cmpi.w  #36,LIB_VERSION(a6)
        bcs.s   13$
        jsr     _LVOCacheClearU(a6)
13$     move.l  (sp)+,a6
        rts


        data    __MERGED

_numsongs:
numsongs        dc.l    1               ; Number of songs in this module
_cursong:
cursong         dc.l    0               ; Current song number

playerhook      dc.l    0,0,hookfunc    ; Hook to send to modules

mastfreq        dc.w    $100            ; Master relative frequency ($100=normal)

_fadevol        dc.w    -1              ; Current fade volume

                even
gmodname        dc.b    "GMOD ("
gmodnametype    dc.l    0
                dc.b    ")",0

nocmem          dc.b    "Not enough chip memory",0
nomemtext       dc.b    "Not enough memory",0
unkmod          dc.b    "Unknown module type",0
cormod          dc.b    "Corrupt module",0
errplay         dc.b    "Error playing module",0
nofile          dc.b    "Can't load module",0
noabs           dc.b    "Required memory occupied",0
notimer         dc.b    "No CIAB timers available",0
dontcompress    dc.b    "Please decompress this module",0
genembname      dc.b    "Generic embedded-player",0
xmodname        dc.b    "XMOD/AMOD",0
whitname        dc.b    "Dave Whittaker",0
deltaname       dc.b    "Delta Music",0
sidname         dc.b    "SidMon",0
markiiname      dc.b    "Mark II",0
fxname          dc.b    "SoundFX",0
unknowntxt      dc.b    "Unknown",0
notloaded       dc.b    "No module loaded",0
nocontinue      dc.b    "Module cannot continue",0
audiointname    dc.b    "MultiPlayer audio interrupt",0
playftmname     dc.b    "PlayFTM:",0
ftmnoplayer     dc.b    "Can't load Face The Music player",0
corrcomp        dc.b    "Corrupt (or compressed) FTM module",0
ftmname         dc.b    "Face The Music"
nulllab         dc.b    0

        bss     __MERGED

stackpt         ds.l    1               ; Stack pointer when we die while loading
loadhan         ds.l    1               ; Handle currently being loaded
gmodptr         ds.l    1               ; Pointer to real or fake GMOD structure

modspec:                                ; Currently loaded module
modmem          ds.l    1
modsize         ds.l    1
modend          ds.l    1
modalloc        ds.l    1

suppspec:                               ; Supplemental data file (Startrekker, TFMX)
suppmem         ds.l    1
suppsize        ds.l    1
suppend         ds.l    1
suppalloc       ds.l    1

modfile         ds.l    1               ; Filename of module
_modname:
modname         ds.l    1               ; Displayed name of module
modfilesize     ds.l    1               ; Allocated size of modfile

_authorname:
authorname      ds.l    1               ; Name of song's author
_modtypename:
modtypename     ds.l    1               ; Type of module
_songcatalog:
songcatalog     ds.l    1               ; Catalog of song names for current module
_infolist:
infolist        ds.l    1               ; List of information lines for Info window

cursongprefs    ds.l    1               ; Pointer into songprefs for current song

clockfreq       ds.l    1               ; CIA clock frequency

_songtime       ds.l    1               ; Number of VBlanks this song has played
_songendtime    ds.l    1               ; When this song should end

_songpos
songpos         ds.l    1               ; Current sequence number
_songlen
songlen         ds.l    1               ; Total number of sequences in song

ntauthor        ds.l    1               ; Author name from NoiseTracker modules

ftmseg          ds.l    1               ; SegList of PlayFTM
ftmtab          ds.l    1               ; FTM jump table

timerlo         ds.l    1               ; Address of CIA timer low counter register

audints         ds.b    IS_SIZE*4       ; Audio channel interrupts
audisave        ds.l    4               ; Previous interrupt vectors

modremember     ds.l    1               ; Remember list attached to this module

tint            ds.b    IS_SIZE
sint            ds.b    IS_SIZE

tintfreq        ds.w    1               ; Timer interrupt frequency (un-user-adjusted)

_fadeinc        ds.w    1               ; +1 or -1 - which way to fade

xpkerrmsg       ds.b    80              ; Buffer for XPK error messages

_playing        ds.b    1               ; Module is currently playing
noaudalloc      ds.b    1               ; Don't allocate the audio hardware

timernum        ds.b    1               ; Which CIAB timer we got

_modtype
modtype         ds.b    1               ; Module type flags

portablemodule  ds.b    1               ; Nonzero if we're playing a portable module

        end
