Login

Subversion Repositories NedoOS

Rev

Rev 1959 | Blame | Compare with Previous | Last modification | View Log | Download | RSS feed

S3MMAXINSTRUMENTS = 128
S3MWAVEHEADERBUFFERSIZE = S3MMAXINSTRUMENTS*MOONWAVEHEADERSIZE
S3MSAMPLEDATASTART = MODSAMPLEDATASTART
S3MMAXCHANNELS = 32
S3MMAXPATTERNS = 100
S3MPATTERNSTEPCOUNT = 64
S3MMAXORDER = 256
S3MHEADERADDR = 0xc000
S3MMINPERIOD = 4

        struct S3MHEADER
songname ds 28
byte1a ds 1
filetype ds 1
unused1 ds 2
ordernum ds 2
instnum ds 2
pattnum ds 2
flags ds 2
createdwith ds 2
fileversion ds 2
id ds 4
globalvol ds 1
initialspeed ds 1
initialtempo ds 1
mastermult ds 1
ultraclicks ds 1
panningmagic ds 1
unused2 ds 8
special ds 2
chsettings ds S3MMAXCHANNELS
        ends

        struct S3MINSTRUMENT
type ds 1
dosname ds 12
paraptr ds 3
length ds 4
loopstart ds 4
loopend ds 4
volume ds 1
disknum ds 1
pack ds 1
flags ds 1
c2spd ds 4
tunefactorcoeff ds 2
tunefactorshift ds 1
unused ds 9
name ds 28
id ds 4
        ends

        struct S3MCHANNEL
index ds 1
instrument ds 1
oldinstrument ds 1
period ds 2
note ds 1
volume ds 1
tempcommand ds 2
vibratocommand ds 1
vibratotableposition ds 1
tremolocommand ds 1
tremolotableposition ds 1
wavecontrol ds 1
patternloopstart ds 1
patternloopcount ds 1
pankeyon ds 1
tunefactorcoeff ds 2
tunefactorshift ds 1
tunefactorcoeffoverride ds 2
tunefactorshiftoverride ds 1
portaspeed ds 1
volumeslide ds 1
        ends

        struct S3MSTEPDATA
note ds 1
instrument ds 1
volume ds 1
effectcommand ds 1
effectdata ds 1
extendedcommand ds 1
        ends

        struct S3MINFO
instparapointers ds 2
pattparapointers ds 2
choffset ds S3MMAXCHANNELS
chpanning ds S3MMAXCHANNELS
chnum ds 1
chinitsize ds 2
        ends

        struct S3MPLAYER
patterntableindex ds 1
patternstepindex ds 1
arpeggio ds 1
patterndelay ds 1
speed ds 1
speedstep ds 1
channels ds S3MCHANNEL*S3MMAXCHANNELS
stepdatabuffer ds S3MSTEPDATA*(S3MMAXCHANNELS+1)
        ends

s3mpatterntable equ s3mheader+S3MHEADER

s3mload
;de = input file name
;out: zf=1 if the file is ready for playing, zf=0 otherwise
        ld (s3mloadsamples.filename),de
        call memorystreamloadfile
        ret nz
;map header to S3MHEADERADDR
        ld a,(memorystreampages)
        SETPGC000
        call opl4init
        call s3mloadpatterns
        jp nz,memorystreamfree ;sets zf=0
        call s3mloadsamples
        jp nz,memorystreamfree ;sets zf=0
;init player state
        ld hl,s3mplayer
        ld de,s3mplayer+1
        ld bc,S3MPLAYER-1
        ld (hl),0
        ldir
;init channels
        ld ix,s3minfo.chpanning
        ld iy,s3mplayer.channels
        ld a,(s3minfo.chnum)
        ld b,a
        ld c,0
.initchloop
        ld (iy+S3MCHANNEL.index),c
        ld a,(ix)
        call s3msetpanning
        ld de,S3MCHANNEL
        add iy,de
        inc ix
        inc c
        djnz .initchloop
;init player state
        ld a,(s3mheader.initialtempo)
        call s3msetbpm
        xor a
        call s3msetnextstep
        ld a,(s3mheader.initialspeed)
        ld (s3mplayer.speed),a
        ld a,1
        ld (s3mplayer.speedstep),a
        xor a
        ld (s3mheader.byte1a),a
        ret

s3mloadpatterns
        call getpanningfunc
        ld (.getpanning),hl
;parapointers
        ld a,(s3mheader.ordernum)
        ld e,a
        ld d,0
        ld hl,s3mpatterntable
        add hl,de
        ld (s3minfo.instparapointers),hl
        ld a,(s3mheader.instnum)
        ld e,a
        add hl,de
        add hl,de
        ld (s3minfo.pattparapointers),hl
;fill channel tables
        ld a,(s3mheader.pattnum)
        ld e,a
        add hl,hl
        add hl,hl
        ld de,s3minfo.choffset
        ld bc,s3minfo.chpanning
        ld ix,s3mheader.chsettings
        ld iy,S3MMAXCHANNELS
        xor a
        ex af,af'
.chloop ld a,(ix)
        cp 16
        jr nc,.choff
.getpanning=$+1
        call 0
        ld (bc),a
        inc bc
        ex af,af'

        ld (de),a
        add a,S3MSTEPDATA
        ex af,af'
        inc de
        inc iyh
        ld a,iyh
        cp OPL4MAXWAVECHANNELS
        jr nc,.channelscapped
.choff  inc hl
        inc ix
        dec iyl
        jr nz,.chloop
        ld a,iyh
.channelscapped
        ld (s3minfo.chnum),a
        ld a,S3MMAXCHANNELS+1
        sub iyh
        ld b,a
        ex af,af'

        dec b
        jr z,.nofill
        ld (de),a
        inc de
        djnz $-2
.nofill cp S3MSTEPDATA*2
        jr nc,$+4
        ld a,S3MSTEPDATA*2
        sub S3MSTEPDATA
        ld l,a
        ld h,0
        ld (s3minfo.chinitsize),hl
        xor a
        ret

getpanningfunc
        ld a,(s3mheader.panningmagic)
        cp 252
        ld hl,.readdefaultpanning
        ret z
        ld hl,.chsetttingspanning
        ret
.chsetttingspanning
        cp 8
        ld a,0x03
        ret c
        ld a,0x0c
        ret
.readdefaultpanning
        ld a,(hl)
        and 15
        ret

s3mfilestreamseekfast
;dehl = offset
        ld (.fileoffsetlo),hl
        ld (.fileoffsethi),de
        ld hl,(filestreamcurrentaddr)
        res 6,h
        res 7,h
        ld de,(filereadoffset+0)
        add hl,de
        ld bc,(filereadoffset+2)
        jr nc,$+3
        inc bc
        ex de,hl
.fileoffsetlo=$+1
        ld hl,0
        sub hl,de
        ex de,hl
.fileoffsethi=$+1
        ld hl,0
        sbc hl,bc
        ld a,h
        or l
        or d
        ld b,e
        ld hl,(.fileoffsetlo)
        ld de,(.fileoffsethi)
        jp nz,s3mfilestreamseek
        inc b
        dec b
        ret z
        ld hl,(filestreamcurrentaddr)
.loop   bit 6,h
        call nz,s3mloadfiledata
        inc hl
        djnz .loop
        ld (filestreamcurrentaddr),hl
        ret

s3mfilestreamseek
;dehl = offset
        push ix
        push iy
        ld a,(filehandle)
        ld b,a
        OS_SEEKHANDLE
        ld hl,0xffff
        ld (filestreamcurrentaddr),hl
        pop iy
        pop ix
        ret

s3mloadfiledata
        push af,bc,de
        exx
        ex af,af'
        push af,bc,de,hl,ix,iy
        ld a,(filehandle)
        ld b,a
        OS_TELLHANDLE
        ld (filereadoffset+0),hl
        ld (filereadoffset+2),de
        ld de,0x8000
        ld hl,0x4000
        call readstream_file
        pop iy,ix,hl,de,bc,af
        exx
        ex af,af'

        pop de,bc,af
        ld hl,0x8000
        ret

filestreamcurrentaddr ds 2
filereadoffset ds 4

s3mloadsamples
;output: zf=1 if samples are loaded, zf=0 otherwise
.filename=$+1
        ld de,0
        call openstream_file
        or a
        ret nz
        ld hl,0
        ld (filestreamcurrentaddr),hl
        dec hl
        ld (filereadoffset+0),hl
        ld (filereadoffset+2),hl
        ld a,(modfilebufferpage)
        SETPG8000
;read samples data from file
        ld hl,S3MSAMPLEDATASTART%65536
        ld a,S3MSAMPLEDATASTART/65536
        ld (.sampleaddresslo),hl
        ld (.sampleaddresshi),a
        ld hl,(s3minfo.instparapointers)
        ld iy,s3mwaveheaderbuffer
        ld a,(s3mheader.instnum)
        ld b,a
.mainloop
        push bc
;convert instrument parapointer to memory address
        ld e,(hl)
        inc hl
        ld d,(hl)
        dec hl
        ex de,hl
        add hl,hl
        add hl,hl
        add hl,hl
        add hl,hl
        ld bc,S3MHEADERADDR
        add hl,bc
        ex de,hl
        ld ix,de
;replace parapointer with pointer
        ld (hl),e
        inc hl
        ld (hl),d
        inc hl
        push hl
;start filling wavetable entry
        bit 0,(ix+S3MINSTRUMENT.flags)
        jr z,.noloop
        ld de,(ix+S3MINSTRUMENT.loopend)
        ld bc,(ix+S3MINSTRUMENT.loopstart)
        ld hl,de
        dec de
        sub hl,bc
        jr nz,.hasloop
.noloop
        ld bc,(ix+S3MINSTRUMENT.length)
        ld de,bc
        dec bc
.hasloop
        ld hl,0xffff
        sub hl,de
        ld (iy+ 3),b ;loop hi
        ld (iy+ 4),c ;loop lo
        ld (iy+ 5),h ;end hi
        ld (iy+ 6),l ;end lo
        ld (iy+ 7),0x00 ;LFO, VIB
        ld (iy+ 8),0xf0 ;AR, D1R
        ld (iy+ 9),0xff ;DL, D2R
        ld (iy+10),0x0f ;rate correction, RR
        ld (iy+11),0x00 ;AM
.sampleaddresslo=$+1
        ld hl,0
.sampleaddresshi=$+1
        ld d,0
        ld a,(ix+S3MINSTRUMENT.flags)
        rrca
        rrca
        rrca
        and 0x80
        or d
        ld (iy+0),a ;8/16 bits data, addr hi
        ld (iy+1),h ;addr mi
        ld (iy+2),l ;addr lo
        ld bc,(ix+S3MINSTRUMENT.length)
        ld a,b
        or c
        jp z,.nextsample
;upload sample
        push de
        push hl
        ld a,c
        dec bc
        inc b
        ld c,b
        ld b,a
        push bc
        call opl4setmemoryaddress
        ld de,0x1102
        call opl4writewave
;seek to samples data
        ld d,0
        ld e,(ix+S3MINSTRUMENT.paraptr+0)
        ld l,(ix+S3MINSTRUMENT.paraptr+1)
        ld h,(ix+S3MINSTRUMENT.paraptr+2)
        add hl,hl : rl de
        add hl,hl : rl de
        add hl,hl : rl de
        add hl,hl : rl de
        call s3mfilestreamseekfast
        pop bc
        ld hl,(filestreamcurrentaddr)
;start uploading
        opl4_wait
        ld a,6
        out (MOON_WREG),a
        bit 2,(ix+S3MINSTRUMENT.flags)
        jr nz,.uploadloop16bits
.uploadloop8bits
        bit 6,h
        call nz,s3mloadfiledata
        ld d,(hl)
        inc hl
        opl4_wait
        ld a,d
        add a,0x80
        out (MOON_WDAT),a
        djnz .uploadloop8bits
        dec c
        jr nz,.uploadloop8bits
;duplicate the last data sample
        opl4_wait
        ld a,d
        add a,0x80
        out (MOON_WDAT),a
        jr .doneupload
.uploadloop16bits
        bit 6,h
        call nz,s3mloadfiledata
        ld e,(hl)
        inc hl
        bit 6,h
        call nz,s3mloadfiledata
        ld d,(hl)
        inc hl
        opl4_wait
        ld a,d
        add a,0x80
        out (MOON_WDAT),a
        opl4_wait
        ld a,e
        out (MOON_WDAT),a
        djnz .uploadloop16bits
        dec c
        jr nz,.uploadloop16bits
        opl4_wait
        ld a,d
        add a,0x80
        out (MOON_WDAT),a
        opl4_wait
        ld a,e
        out (MOON_WDAT),a
.doneupload
        ld de,0x1002
        call opl4writewave
        ld (filestreamcurrentaddr),hl
        pop hl
        pop de
;set next write address
        ld bc,(ix+S3MINSTRUMENT.length)
        xor a
        bit 2,(ix+S3MINSTRUMENT.flags)
        jr z,.alreadyinbytes
        sla bc
        rla
.alreadyinbytes
        add hl,bc
        adc a,d
        ld bc,2 ; add +2 to account for duping the last data sample
        add hl,bc
        adc a,b
        ld (.sampleaddresslo),hl
        ld (.sampleaddresshi),a
;process c2spd
        ld hl,(ix+S3MINSTRUMENT.c2spd)
        push ix
        push iy
        call s3mgettunefactor
        pop iy
        pop ix
        ld (ix+S3MINSTRUMENT.tunefactorcoeff),hl
        ld (ix+S3MINSTRUMENT.tunefactorshift),a
.nextsample
        ld bc,MOONWAVEHEADERSIZE
        add iy,bc
        pop hl
        pop bc
        dec b
        jp nz,.mainloop
;switch back to memory steam
        call closestream_file
        ld a,(memorystreamcurrentpage)
        SETPG8000
;write headers
        ld ix,s3mwaveheaderbuffer
        ld hl,MOONSOUNDROMSIZE%65536
        ld d,MOONSOUNDROMSIZE/65536
        ld bc,S3MWAVEHEADERBUFFERSIZE
        call opl4writememory
        xor a
        ret

s3munload
        call opl4mute
        jp memorystreamfree

s3mplay
        call s3mwaittimer
        ld a,(s3mplayer.speedstep)
        dec a
        jp nz,.tn
        ld a,(s3mplayer.speed)
        ld (s3mplayer.speedstep),a
        ld a,(s3mplayer.patterndelay)
        or a
        jr z,.nopatterndelay
        dec a
        ld (s3mplayer.patterndelay),a
        ret
.nopatterndelay
        call s3mreadstepdata
        ld ix,s3mplayer.stepdatabuffer
        ld iy,s3mplayer.channels
        ld a,(s3minfo.chnum)
.t0loop push af
        ld (iy+S3MCHANNEL.tunefactorshiftoverride),0xff
        call s3msetinstrument
        call s3mhandlecommandT0
        ld d,(ix+S3MSTEPDATA.effectcommand)
        ld e,(ix+S3MSTEPDATA.extendedcommand)
        ld hl,0x130d
        sub hl,de
        call nz,s3mnewnote
        ld a,(ix+S3MSTEPDATA.volume)
        cp 255
        jr z,.novolumecmd
        clamp_volume_in_a
        ld (iy+S3MCHANNEL.volume),a
.novolumecmd
        ld a,(iy+S3MCHANNEL.volume)
        call s3msetvolume
        set 7,(iy+S3MCHANNEL.pankeyon)
        call s3mflushpankeyon
        ld de,S3MSTEPDATA
        add ix,de
        ld e,S3MCHANNEL
        add iy,de
        pop af
        dec a
        jp nz,.t0loop
        ret
.tn     ld (s3mplayer.speedstep),a
        ld a,(s3mplayer.arpeggio)
        inc a
        cp 3
        jr c,$+3
        xor a
        ld (s3mplayer.arpeggio),a
        ld ix,s3mplayer.stepdatabuffer
        ld iy,s3mplayer.channels
        ld a,(s3minfo.chnum)
        ld b,a
.tnloop push bc
        call s3mhandlecommandTN
        ld de,S3MSTEPDATA
        add ix,de
        ld e,S3MCHANNEL
        add iy,de
        pop bc
        djnz .tnloop
        ret

s3msetinstrument
;ix = step data
;iy = channel data
        ld a,(ix+S3MSTEPDATA.instrument)
        or a
        ret z
        ld (iy+S3MCHANNEL.instrument),a
        dec a
        ld hl,(s3minfo.instparapointers)
        ld e,a
        ld d,0
        add hl,de
        add hl,de
        ld e,(hl)
        inc hl
        ld d,(hl)
        push ix
        ld ix,de
        ld de,(ix+S3MINSTRUMENT.tunefactorcoeff)
        ld (iy+S3MCHANNEL.tunefactorcoeff),de
        ld a,(ix+S3MINSTRUMENT.tunefactorshift)
        ld (iy+S3MCHANNEL.tunefactorshift),a
        ld a,(ix+S3MINSTRUMENT.volume)
        ld (iy+S3MCHANNEL.volume),a
        ld (iy+S3MCHANNEL.vibratotableposition),0
        pop ix
        ret

s3mhandlecommandT0
;ix = step data
;iy = channel data
        ld a,(ix+S3MSTEPDATA.effectcommand)
        cp 23
        ret nc
        ld b,a
        add a,a
        add a,b
        ld (.effectcommandtable),a
.effectcommandtable=$+1
        jr $
        ret : ds 2          ;  0 -
        jp .doeffectA       ;  1 [Set Speed]
        ret : ds 2          ;  2 [Pattern Jump]
        ret : ds 2          ;  3 [Pattern Break]
        jp s3mvolumeslideT0 ;  4 [Volume Slide/Fine Volume Slide up/down]
        jp .doeffectE       ;  5 [Porta Down/Fine Porta Down/Xtra Fine Porta]
        jp .doeffectF       ;  6 [Porta Up/Fine Porta Up/Extra Fine Porta Down]
        ret : ds 2          ;  7 [Porta to note]
        jp .doeffectH       ;  8 [Vibrato]
        ret : ds 2          ;  9 [Tremor]
        jp .doeffectJ       ; 10 [Arpeggio]
        jp s3mvolumeslideT0 ; 11 [Vibrato+Volume Slide]
        jp s3mvolumeslideT0 ; 12 [Porta+Volume Slide]
        ret : ds 2          ; 13 -
        ret : ds 2          ; 14 -
        ret : ds 2          ; 15 [Sample Offset]
        ret : ds 2          ; 16 -
        ret : ds 2          ; 17 [Retrig + Volume Slide]
        jp .doeffectR       ; 18 [Tremolo]
        jp .doeffectS       ; 19 [Extended Command]
        jp .doeffectT       ; 20 [Set Tempo]
        jp .doeffectU       ; 21 [Fine Vibrato]
;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 22 [Global Volume]
        ret
.doeffectR
        ld b,(ix+S3MSTEPDATA.effectdata)
        ld c,(iy+S3MCHANNEL.tremolocommand)
        ld a,b
        and 0xf0
        jr z,.skiptremolohi
        xor c
        and 0xf0
        xor c
        ld c,a
.skiptremolohi
        ld a,b
        and 15
        jr z,.skiptremololo
        xor c
        and 15
        xor c
        ld c,a
.skiptremololo
        ld (iy+S3MCHANNEL.tremolocommand),c
        ret
.doeffectJ
        xor a
        ld (s3mplayer.arpeggio),a
        ld hl,(iy+S3MCHANNEL.period)
        jp s3msetfrequency
.doeffectE
        ld a,(ix+S3MSTEPDATA.effectdata)
        or a
        jr z,$+5
        ld (iy+S3MCHANNEL.portaspeed),a
        ld a,(iy+S3MCHANNEL.portaspeed)
        cp 0xe0
        ret c
        ld b,a
        and 15
        ld e,a
        ld d,0
        bit 4,b
        jp z,s3mportadown.slide
        ex de,hl
        add hl,hl
        add hl,hl
        ex de,hl
        jp s3mportadown.slide
.doeffectF
        ld a,(ix+S3MSTEPDATA.effectdata)
        or a
        jr z,$+5
        ld (iy+S3MCHANNEL.portaspeed),a
        ld a,(iy+S3MCHANNEL.portaspeed)
        cp 0xe0
        ret c
        ld b,a
        and 15
        ld e,a
        ld d,0
        bit 4,b
        jp z,s3mportaup.slide
        ex de,hl
        add hl,hl
        add hl,hl
        ex de,hl
        jp s3mportaup.slide
.doeffectH
.doeffectU
        ld a,(ix+S3MSTEPDATA.note)
        cp 254
        jr nc,.skipnote
        ld bc,(iy+S3MCHANNEL.period)
        ld (iy+S3MCHANNEL.tempcommand),bc
.skipnote
        ld b,(ix+S3MSTEPDATA.effectdata)
        ld c,(iy+S3MCHANNEL.vibratocommand)
        ld a,b
        and 0xf0
        jr z,.skipvibratohi
        xor c
        and 0xf0
        xor c
        ld c,a
.skipvibratohi
        ld a,b
        and 15
        jr z,.skipvibratolo
        xor c
        and 15
        xor c
        ld c,a
.skipvibratolo
        ld (iy+S3MCHANNEL.vibratocommand),c
        ret
.doeffectA
        ld a,(ix+S3MSTEPDATA.effectdata)
        ld (s3mplayer.speed),a
        ret
.doeffectT
        ld a,(ix+S3MSTEPDATA.effectdata)
        jp s3msetbpm
.doeffectS
        ld a,(ix+S3MSTEPDATA.extendedcommand)
        ld b,a
        add a,a
        add a,b
        ld (.exteffcommandtable),a
.exteffcommandtable=$+1
        jr $
        ret : ds 2    ;  0 [Set Filter on/off]
        ret : ds 2    ;  1 [Set Glissando on/off]
        jp .doexteff2 ;  2 [Set FineTune]
        jp .doexteff3 ;  3 [Set Vibrato Waveform]
        jp .doexteff4 ;  4 [Set Tremolo Waveform]
        ret : ds 2    ;  5 -
        ret : ds 2    ;  6 -
        ret : ds 2    ;  7 -
        jp .doexteff8 ;  8 [Set Pan Position]
        ret : ds 2    ;  9 -
        jp .doexteffA ; 10 [Stereo Control]
        ret : ds 2    ; 11 [Pattern Loop]
        jp .doexteffC ; 12 [Cut Note]
        jp .doexteffD ; 13 [Delay Note]
        jp .doexteffE ; 14 [Pattern Delay]
        ret           ; 15 [Funk Repeat]
.doexteff8
.doexteffA
        ld a,(ix+S3MSTEPDATA.effectdata)
        jp s3msetpanning
.doexteff2
        ld a,(ix+S3MSTEPDATA.effectdata)
        ld b,a
        add a,a
        add a,b
        get_array_value b,s3mfinetunefactors
        inc hl
        ld c,(hl)
        ld (iy+S3MCHANNEL.tunefactorcoeffoverride),bc
        inc hl
        ld a,(hl)
        ld (iy+S3MCHANNEL.tunefactorshiftoverride),a
        ret
.doexteff3
        ld a,(iy+S3MCHANNEL.wavecontrol)
        ld b,(ix+S3MSTEPDATA.effectdata)
        and 0xfc
        or b
        ld (iy+S3MCHANNEL.wavecontrol),a
        ld (iy+S3MCHANNEL.vibratotableposition),0
        ret
.doexteff4
        ld a,(iy+S3MCHANNEL.wavecontrol)
        ld b,(ix+S3MSTEPDATA.effectdata)
        sla b
        sla b
        and 0xf3
        or b
        ld (iy+S3MCHANNEL.wavecontrol),a
        ld (iy+S3MCHANNEL.tremolotableposition),0
        ret
.doexteffC
        ld a,(ix+S3MSTEPDATA.effectdata)
        or a
        jr z,.channeloff
        ld (iy+S3MCHANNEL.tempcommand),a
        ret
.channeloff
        ld (iy+S3MCHANNEL.volume),a
        jp s3msetvolume
.doexteffD
        ld a,(ix+S3MSTEPDATA.effectdata)
        or a
        ret z
        ld (iy+S3MCHANNEL.tempcommand),a
        ret
.doexteffE
        ld a,(ix+S3MSTEPDATA.effectdata)
        ld (s3mplayer.patterndelay),a
        ret

s3mhandlecommandTN
;ix = step data
;iy = channel data
        ld a,(ix+S3MSTEPDATA.effectcommand)
        cp 23
        ret nc
        ld b,a
        add a,a
        add a,b
        ld (.effectcommandtable),a
.effectcommandtable=$+1
        jr $
        ret : ds 2       ;  0 -
        ret : ds 2       ;  1 [Set Speed]
        jp .effectB      ;  2 [Pattern Jump]
        jp .effectC      ;  3 [Pattern Break]
        jp s3mvolumeslideTN ; 4 [Volume Slide/Fine Volume Slide up/down]
        jp s3mportadown  ;  5 [Porta Down/Fine Porta Down/Xtra Fine Porta]
        jp s3mportaup    ;  6 [Porta Up/Fine Porta Up/Extra Fine Porta Down]
        jp s3mtoneporta  ;  7 [Porta to note]
        jp s3mvibrato    ;  8 [Vibrato]
        ret : ds 2       ;  9 [Tremor]
        jp .doeffectJ    ; 10 [Arpeggio]
        jp .doeffectK    ; 11 [Vibrato+Volume Slide]
        jp .doeffectL    ; 12 [Porta+Volume Slide]
        ret : ds 2       ; 13 -
        ret : ds 2       ; 14 -
        ret : ds 2       ; 15 [Sample Offset]
        ret : ds 2       ; 16 -
        ret : ds 2       ; 17 [Retrig + Volume Slide]
        jp s3mtremolo    ; 18 [Tremolo]
        jp .doeffectS    ; 19 [Extended Command]
        ret : ds 2       ; 20 [Set Tempo]
        jp s3mfvibrato   ; 21 [Fine Vibrato]
;;;;;;;;;;;;;;;;;;;;;;;;;; 22 [Global Volume]
        ret
.doeffectK
        call s3mvibrato
        jp s3mvolumeslideTN
.doeffectL
        call s3mtoneporta
        jp s3mvolumeslideTN
.doeffectJ
        ld a,(ix+S3MSTEPDATA.effectdata)
        or a
        ret z
        ld hl,(s3mplayer.arpeggio)
        dec l
        jr z,.datahi
        inc l
        jr nz,.datalo
        ld hl,(iy+S3MCHANNEL.period)
        jp s3msetfrequency
.datahi rrca
        rrca
        rrca
        rrca
.datalo and 15
        add a,(iy+S3MCHANNEL.note)
        cp 96
        jr c,$+4
        ld a,95
        call s3mnotetoperiod
        call s3mtuneperiod
        jp s3msetfrequency
.effectB
        ld a,(s3mplayer.speedstep)
        dec a
        ret nz
        ld a,(ix+S3MSTEPDATA.effectdata)
        call s3mskipmarkers
        ld (s3mplayer.patterntableindex),a
        xor a
        jp s3msetnextstep
.effectC
        ld a,(s3mplayer.speedstep)
        dec a
        ret nz
        ld a,(s3mplayer.patterntableindex)
        inc a
        call s3mskipmarkers
        ld (s3mplayer.patterntableindex),a
        ld a,(ix+S3MSTEPDATA.effectdata)
        ld b,a
        and 0xf0
        xor b
        ld c,a
        xor b
        rrca
        rrca
        rrca
        rrca
        add a,a
        ld b,a
        add a,a
        add a,a
        add a,b
        add a,c
        jp s3msetnextstep
.doeffectS
        ld a,(ix+S3MSTEPDATA.extendedcommand)
        ld b,a
        add a,a
        add a,b
        ld (.exteffcommandtable),a
.exteffcommandtable=$+1
        jr $
        ret : ds 2    ;  0 [Set Filter on/off]
        ret : ds 2    ;  1 [Set Glissando on/off]
        ret : ds 2    ;  2 [Set FineTune]
        ret : ds 2    ;  3 [Set Vibrato Waveform]
        ret : ds 2    ;  4 [Set Tremolo Waveform]
        ret : ds 2    ;  5 -
        ret : ds 2    ;  6 -
        ret : ds 2    ;  7 -
        ret : ds 2    ;  8 [Set Pan Position]
        ret : ds 2    ;  9 -
        ret : ds 2    ; 10 [Stereo Control]
        jp .doexteffB ; 11 [Pattern Loop]
        jp .doexteffC ; 12 [Cut Note]
        jp .doexteffD ; 13 [Delay Note]
        ret : ds 2    ; 14 [Pattern Delay]
        ret           ; 15 [Funk Repeat]
.doexteffB
        ld a,(s3mplayer.speedstep)
        dec a
        ret nz
        ld a,(ix+S3MSTEPDATA.effectdata)
        or a
        jr nz,.checkloopcount
        ld a,(s3mplayer.patternstepindex)
        ld (iy+S3MCHANNEL.patternloopstart),a
        ret
.checkloopcount
        ld b,(iy+S3MCHANNEL.patternloopcount)
        inc b
        cp b
        jr c,.restartloop
        ld (iy+S3MCHANNEL.patternloopcount),b
        ld a,(iy+S3MCHANNEL.patternloopstart)
        jp s3msetnextstep
.restartloop
        ld (iy+S3MCHANNEL.patternloopcount),0
        ret
.doexteffC
        ld a,(iy+S3MCHANNEL.tempcommand)
        or a
        ret z
        dec a
        ld (iy+S3MCHANNEL.tempcommand),a
        ret nz
        ld (iy+S3MCHANNEL.volume),a
        jp s3msetvolume
.doexteffD
        ld a,(iy+S3MCHANNEL.tempcommand)
        or a
        ret z
        dec a
        ld (iy+S3MCHANNEL.tempcommand),a
        ret nz
        jp s3mnewnote

s3mnewnote
;ix = step data
;iy = channel data
        ld a,(iy+S3MCHANNEL.instrument)
        or a
        ret z
        ld a,(ix+S3MSTEPDATA.effectcommand)
        cp 0x07
        jr z,.effectG
        cp 0x0C
        jr z,.effectL
        ld a,(ix+S3MSTEPDATA.note)
        inc a
        ret z
        xor a
        call s3msetvolume
        res 7,(iy+S3MCHANNEL.pankeyon)
        call s3mflushpankeyon
        ld a,(ix+S3MSTEPDATA.note)
        cp 254
        jr nz,.validnote
        ld (iy+S3MCHANNEL.volume),0
        ret
.validnote
        ld (iy+S3MCHANNEL.note),a
        call s3mnotetoperiod
        call s3mtuneperiod
        ld (iy+S3MCHANNEL.period),hl
        call s3msetfrequency
        ld a,(iy+S3MCHANNEL.instrument)
        cp (iy+S3MCHANNEL.oldinstrument)
        ret z
        ld (iy+S3MCHANNEL.oldinstrument),a
        jp s3msetsamplenumber
.effectG
        ld a,(ix+S3MSTEPDATA.effectdata)
        or a
        jr z,.effectL
        ld (iy+S3MCHANNEL.portaspeed),a
.effectL
        ld a,(ix+S3MSTEPDATA.note)
        cp 254
        ret nc
        ld (iy+S3MCHANNEL.note),a
        call s3mnotetoperiod
        call s3mtuneperiod
        ld (iy+S3MCHANNEL.tempcommand),hl
        ret

s3mskipmarkers
;intput: a = pattern table index
;output: a = pattern table index pointing at non-marker
        ld hl,s3mheader.ordernum
        ld b,(hl)
        ld e,a
        ld d,0
        ld hl,s3mpatterntable
        add hl,de
.loop   cp b
        jr c,$+6
        xor a
        ld hl,s3mpatterntable
        ld c,a
        ld a,(hl)
        cp 254
        ld a,c
        ret c
        inc hl
        inc a
        jp .loop

s3mgetpatternpos
;a = pattern table index
;out: dehl = file stream position
        ld e,a
        ld d,0
        ld hl,s3mpatterntable
        add hl,de
        ld e,(hl)
        ld hl,(s3minfo.pattparapointers)
        add hl,de
        add hl,de
;parapointer
        ld a,(hl)
        inc hl
        ld h,(hl)
        ld l,a
        xor a
        add hl,hl : rla
        add hl,hl : rla
        add hl,hl : rla
        add hl,hl : rla
        ld e,a
;skip the first two bytes containing pattern data size
        inc l
        inc l
        ret

memorystreamnextpagewrap
;wraps read address rather than resetting to 0x8000
        push hl
        call memorystreamnextpage
        pop hl
        res 6,h
        set 7,h
        ret

s3msetnextstep
;a = step index
;output: memory stream at patterntableindex/patternstepindex
        dec a ;save decremented patternstepindex to cancel out its increment in T0
        ld (s3mplayer.patternstepindex),a
        ld a,(s3mplayer.patterntableindex)
        call s3mgetpatternpos
        call memorystreamseek
        ld a,(s3mplayer.patternstepindex)
        inc a
        ret z
        ld b,a
        ld hl,(memorystreamcurrentaddr)
.steploop
        ld a,(hl)
        or a
        jr z,.nextstep
.loop   and 0xe0
        ld e,d
        add a,a
        rl e
        add a,a
        rl e
        rlca
        adc a,e
        inc a
        ld e,a
        add hl,de
        bit 6,h
        call nz,memorystreamnextpagewrap
        ld a,(hl)
        or a
        jp nz,.loop
.nextstep
        inc hl
        djnz .steploop
        ld (memorystreamcurrentaddr),hl
        ret

s3mreadstepdata
;input: memory stream
;output: filled stepdatabuffer, memory stream at next pattern step
        ld a,(s3mplayer.patternstepindex)
        inc a
        cp S3MPATTERNSTEPCOUNT
        jr c,.nextpatternstep
        ld a,(s3mplayer.patterntableindex)
        inc a
        call s3mskipmarkers
        ld (s3mplayer.patterntableindex),a
        call s3mgetpatternpos
        call memorystreamseek
        xor a
.nextpatternstep
        ld (s3mplayer.patternstepindex),a
;set defaults
        ld ix,s3mplayer.stepdatabuffer
        ld (ix+S3MSTEPDATA.note),0xff
        ld (ix+S3MSTEPDATA.volume),0xff
        ld (ix+S3MSTEPDATA.instrument),0
        ld (ix+S3MSTEPDATA.effectcommand),0xff
        ld hl,s3mplayer.stepdatabuffer
        ld de,s3mplayer.stepdatabuffer+S3MSTEPDATA
        ld bc,(s3minfo.chinitsize)
        ldir
;read data
        ld hl,(memorystreamcurrentaddr)
        memory_stream_read_byte a
        or a
        jr z,.skiploop
.loop   ld b,a
        and 0x1f
        ex de,hl
        get_array_value l,s3minfo.choffset
        ex de,hl
        ld d,0
        ld ix,s3mplayer.stepdatabuffer
        add ix,de
        bit 5,b
        jr z,.skipnoteinstrument
        memory_stream_read_byte a
        cp 254
        jr nc,.specialnote
        ld c,a
        rrca
        rrca
        and 0x3c
        neg
        add a,c
.specialnote
        ld (ix+S3MSTEPDATA.note),a
        memory_stream_read_byte a
        ld (ix+S3MSTEPDATA.instrument),a
.skipnoteinstrument
        bit 6,b
        jr z,.skipvolume
        memory_stream_read_byte a
        ld (ix+S3MSTEPDATA.volume),a
.skipvolume
        bit 7,b
        jr z,.skipeffect
        memory_stream_read_byte a
        memory_stream_read_byte c
        ld (ix+S3MSTEPDATA.effectcommand),a
        cp 19
        ld a,c
        jr nz,.notextended
        srl c
        srl c
        srl c
        srl c
        ld (ix+S3MSTEPDATA.extendedcommand),c
        and 15
.notextended
        ld (ix+S3MSTEPDATA.effectdata),a
.skipeffect
        memory_stream_read_byte a
        or a
        jr nz,.loop
.skiploop
        ld (memorystreamcurrentaddr),hl
        ret

s3mnotetoperiod
;a = note
;out: hl = period
        add a,a
        get_array_value a,st3periods
        inc hl
        ld h,(hl)
        ld l,a
        ret

s3mtuneperiod
;iy = channel data
;hl = period
;out: hl = period
        ld bc,(iy+S3MCHANNEL.tunefactorcoeffoverride)
        ld a,(iy+S3MCHANNEL.tunefactorshiftoverride)
        or a
        jp p,.hasoverride
        ld bc,(iy+S3MCHANNEL.tunefactorcoeff)
        ld a,(iy+S3MCHANNEL.tunefactorshift)
        or a
.hasoverride
        push af
        ex de,hl
        call uintmul16
        pop af
        jr z,.noshift
        ld b,a
.shiftloop
        add hl,hl
        rl de
        djnz .shiftloop
.noshift
        ld hl,-S3MMINPERIOD
        add hl,de
        ex de,hl
        ret c
        ld hl,S3MMINPERIOD
        ret

s3mgettunefactor
;hl = c2spd
;output: hl = factor, a = shift
;poor man's floating point: period*8363/c2spd = (period*factor)>>(16-shift)
        ld de,0
        exx
        ld de,8363
        ld hl,0
        call uintdiv32
        ld a,d
        or e
        ret z
        ex de,hl
        push hl
        ld b,16
        add hl,hl
        dec b
        jr nc,$-2
        ld a,h
        or l
        jr z,$+3
        inc b
        inc b
        pop hl
        ex de,hl
        ld a,b
.shiftloop
        srl de
        rr hl
        djnz .shiftloop
        ret

s3msetbpm equ modsetbpm
s3mwaittimer equ modwaittimer

s3msetfrequency
;iy = channel data
;hl = period
        ld a,(modperiodlookuppage)
        SETPGC000
        ld a,h
        sub 0x10
        jp c,.firsthalf
;(hl-4096)/8+4096
        srl a : rr l
        srl a : rr l
        srl a : rr l
        add a,0x10
        ld h,a
.firsthalf
        add hl,hl
;two-bytes lookup
        ld de,S3MHEADERADDR-2
        add hl,de
        ld d,(hl)
        inc hl
        ld l,(hl)
        ld a,(memorystreampages)
        SETPGC000
        ld a,(iy+S3MCHANNEL.index)
        add a,0x38
        ld e,a
        call opl4writewave
        ld d,l
        ld a,e
        sub 0x18
        ld e,a
        jp opl4writewave

s3mportaup
;iy = channel data
        ld a,(iy+S3MCHANNEL.portaspeed)
        cp 0xe0
        ret nc
        rlca
        rlca
        ld d,a
        and 0xfc
        ld e,a
        xor d
        ld d,a
.slide  ld hl,(iy+S3MCHANNEL.period)
        sub hl,de
        jr c,.clamp
        ex de,hl
        ld hl,-S3MMINPERIOD
        add hl,de
        ex de,hl
        jr c,$+5
.clamp  ld hl,S3MMINPERIOD
        ld (iy+S3MCHANNEL.period),hl
        jp s3msetfrequency

s3mportadown
;iy = channel data
        ld a,(iy+S3MCHANNEL.portaspeed)
        cp 0xe0
        ret nc
        rlca
        rlca
        ld d,a
        and 0xfc
        ld e,a
        xor d
        ld d,a
.slide  ld hl,(iy+S3MCHANNEL.period)
        add hl,de
        ex de,hl
        ld hl,-36000
        add hl,de
        ex de,hl
        jr nc,$+5
        ld hl,35999
        ld (iy+S3MCHANNEL.period),hl
        jp s3msetfrequency

s3mtoneporta
;iy = channel data
        ld hl,(iy+S3MCHANNEL.period)
        ld bc,(iy+S3MCHANNEL.tempcommand)
        ld de,hl
        sub hl,bc
        ex de,hl
        jp z,s3msetfrequency ;period == tempcommand
        ld e,(iy+S3MCHANNEL.portaspeed)
        ld d,0
        jr c,.pospitchbend ;period < tempcommand
        sla de
        sla de
        sbc hl,de
        jr nc,$+5
        ld hl,0
        ld de,hl
        sub hl,bc
        ex de,hl
        jr nc,.finalize ;period >= tempcommand
        ld hl,bc
        jr .finalize
.pospitchbend
        sla de
        sla de
        add hl,de
        ld de,hl
        sub hl,bc
        ex de,hl
        jr c,$+4 ;period < tempcommand
        ld hl,bc
.finalize
        ld (iy+S3MCHANNEL.period),hl
        jp s3msetfrequency

        macro s3m_vibrato is_fine
        ld a,(iy+S3MCHANNEL.vibratotableposition)
        rrca
        rrca
        and 0x1f
        get_array_value e,modvibratotable
        ld hl,0
        ld a,(iy+S3MCHANNEL.vibratocommand)
        and 15
        call nz,mul8x8
        xor a
        add hl,hl : rla
        if !is_fine
        add hl,hl : rla
        add hl,hl : rla
        endif
        ld e,h
        ld d,a
        ld hl,(iy+S3MCHANNEL.period)
        bit 7,(iy+S3MCHANNEL.vibratotableposition)
        jr z,.periodup
        sub hl,de
        jr .finalize
.periodup
        add hl,de
.finalize
        ld (iy+S3MCHANNEL.tempcommand),hl
        call s3msetfrequency
        ld a,(iy+S3MCHANNEL.vibratocommand)
        rrca
        rrca
        and 0x3c
        add a,(iy+S3MCHANNEL.vibratotableposition)
        ld (iy+S3MCHANNEL.vibratotableposition),a
        endm

s3mfvibrato
;iy = channel data
        s3m_vibrato 1
        ret

s3mvibrato
;iy = channel data
        s3m_vibrato 0
        ret

s3msetvolume
;iy = channel data
;a = volume
        get_array_value d,modvolumetable
        sll d
        ld a,(iy+S3MCHANNEL.index)
        add a,0x50
        ld e,a
        jp opl4writewave

s3msetsamplenumber
;iy = channel data
;a = sample number
        add a,0x7f
        ld d,a
        ld a,(iy+S3MCHANNEL.index)
        add a,0x08
        ld e,a
        call opl4writewave
;wait for the header to load
        in a,(MOON_STAT)
        and 3
        jr nz,$-4
        ret

s3mflushpankeyon
;iy = channel data
        ld d,(iy+S3MCHANNEL.pankeyon)
        ld a,(iy+S3MCHANNEL.index)
        add a,0x68
        ld e,a
        jp opl4writewave

s3mvolumeslideT0
;ix = step data
;iy = channel data
        ld a,(ix+S3MSTEPDATA.effectdata)
        or a
        jr z,$+5
        ld (iy+S3MCHANNEL.volumeslide),a
        ld a,(iy+S3MCHANNEL.volumeslide)
        cp 0xf0
        jr c,.checkslideup
        and 15
        ld b,a
        ld a,(iy+S3MCHANNEL.volume)
        sub b
        jr nc,$+3
        xor a
        ld (iy+S3MCHANNEL.volume),a
        jp s3msetvolume
.checkslideup
        rrca
        rrca
        rrca
        rrca
        cp 0xf0
        ret c
        and 15
        add a,(iy+S3MCHANNEL.volume)
        clamp_volume_in_a
        ld (iy+S3MCHANNEL.volume),a
        jp s3msetvolume

s3mvolumeslideTN
;iy = channel data
        ld a,(iy+S3MCHANNEL.volumeslide)
        cp 0x10
        jr nc,.checkslideup
        ld b,a
        ld a,(iy+S3MCHANNEL.volume)
        sub b
        jr nc,$+3
        xor a
        ld (iy+S3MCHANNEL.volume),a
        jp s3msetvolume
.checkslideup
        rrca
        rrca
        rrca
        rrca
        cp 0x10
        ret nc
        add a,(iy+S3MCHANNEL.volume)
        clamp_volume_in_a
        ld (iy+S3MCHANNEL.volume),a
        jp s3msetvolume

s3msetpanning
;iy = channel data
;a = panning
        get_array_value l,modpantable
        ld a,(iy+S3MCHANNEL.pankeyon)
        and 0x80
        or l
        ld (iy+S3MCHANNEL.pankeyon),a
        ret

s3mtremolo
;iy = channel data
        ld a,(iy+S3MCHANNEL.tremolotableposition)
        rrca
        rrca
        and 0x1f
        get_array_value e,modvibratotable
        ld hl,0
        ld a,(iy+S3MCHANNEL.tremolocommand)
        and 15
        call nz,mul8x8
        ld a,h
        sla l
        rla
        sla l
        rla
        bit 7,(iy+S3MCHANNEL.tremolotableposition)
        jr z,.volumeup
        ld b,a
        ld a,(iy+S3MCHANNEL.volume)
        sub b
        jr nc,.finalize
        xor a
        jr .finalize
.volumeup
        add a,(iy+S3MCHANNEL.volume)
        clamp_volume_in_a
.finalize
        call s3msetvolume
        ld a,(iy+S3MCHANNEL.tremolocommand)
        rrca
        rrca
        and 0x3c
        add a,(iy+S3MCHANNEL.tremolotableposition)
        ld (iy+S3MCHANNEL.tremolotableposition),a
        ret

s3mfinetunefactors
        db 0x87,0x96,0x01 ;7895
        db 0x86,0xcd,0x01 ;7941
        db 0x86,0x0f,0x01 ;7985
        db 0x85,0x0b,0x01 ;8046
        db 0x84,0x0a,0x01 ;8107
        db 0x83,0x0a,0x01 ;8169
        db 0x82,0x09,0x01 ;8232
        db 0x81,0x48,0x01 ;8280
        db 0x80,0x00,0x01 ;8363 (No finetune)
        db 0xfe,0x7a,0x00 ;8413
        db 0xfc,0xf9,0x00 ;8463
        db 0xfb,0x04,0x00 ;8529
        db 0xf9,0x7f,0x00 ;8581
        db 0xf7,0x7a,0x00 ;8651
        db 0xf5,0x6f,0x00 ;8723
        db 0xf4,0x7b,0x00 ;8757

st3periods
        ;    C     C#    D     D#    E     F     F#    G     G#    A     A#    B
        dw 27392,25856,24384,23040,21696,20480,19328,18240,17216,16256,15360,14496 ;0
        dw 13696,12928,12192,11520,10848,10240, 9664, 9120, 8608, 8128, 7680, 7248 ;1
        dw  6848, 6464, 6096, 5760, 5424, 5120, 4832, 4560, 4304, 4064, 3840, 3624 ;2
        dw  3424, 3232, 3048, 2880, 2712, 2560, 2416, 2280, 2152, 2032, 1920, 1812 ;3
        dw  1712, 1616, 1524, 1440, 1356, 1280, 1208, 1140, 1076, 1016,  960,  906 ;4
        dw   856,  808,  762,  720,  678,  640,  604,  570,  538,  508,  480,  453 ;5
        dw   428,  404,  381,  360,  339,  320,  302,  285,  269,  254,  240,  226 ;6
        dw   214,  202,  190,  180,  170,  160,  151,  143,  135,  127,  120,  113 ;7
        dw   107,  101,   95,   90,   85,   80,   75,   71,   67,   63,   60,   56 ;8
st3periodtablesize=($-st3periods)/2