* 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.
*
* See Player.doc for info on contacting the author.
*
* Note:  This playroutine was not originally written by me.  In general
* these playroutines are public domain, so I am bringing the versions
* modified for MultiPlayer under the General Public License.  In the
* few cases of already-copyrighted playroutines, the above copyright
* notice applies only to the parts of the file written by me.
*
* $Id: dssplay.asm,v 4.1 92/07/12 10:40:43 BAF Exp $
*

        include "player.i"

        code    text

        xref    modmem,dmawait

        xdef    dssstart

dssstart:
        plstartret .gmod

        cnop    0,4
        dc.l    gmod_Hook
.gmod
        gmodnop
        gmodbra InitPlay                ; StartMusic
        gmodbra RemovePlayer            ; StopMusic
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop
        gmodnop                         ; GetFrequency
        gmodnop                         ; TimerTick
        lea     name(a4),a0             ; GetMakerName
        move.l  a0,d0
        rts


;**************************************************************************
;*
;*   DSS MODULE PLAY ROUTINE
;*   -----------------------
;*
;*   If you wish to use some DSS Tracker Modules into your own programs,
;*   you can read the following listing...
;*
;*   This file contains the needed functions for playing a DSS Module.
;*   You have to allocate, to load  and to free the memory (CHIP) yourself.
;*
;*  >Once the Module is loaded in CHIP memory:
;*
;*   -Set the [ SongPointer ] pointer (at the end of this file) with the
;*    Module's address.
;*   -Call the [ InitPlay() ] function.
;*
;*  >To stop the Module, you just have to call the [ RemovePlayer() ]
;*   function.
;*
;*   This program has been writen for the Aztec C 3.6 assembler.
;*   It can be easily adapted to another assembler.
;*
;**************************************************************************

;----- Amiga function ------

_LVOOpenResource        EQU        -498
_LVOAddICRVector        EQU        -6
_LVORemICRVector        EQU        -12

;------- ExecBase ----

SysBase                 EQU     4
PAL                     EQU     50
eb_PowerSupplyFreq      EQU     531

;------- CIAA --------

CIAAPRA         EQU     $bfe001
CIABTALO        EQU     $bfd400
CIABTAHI        EQU     $bfd500
CIABICR         EQU     $bfdd00
CIABCRA         EQU     $bfde00

CIABTBLO        EQU     $bfd600
CIABTBHI        EQU     $bfd700
CIABCRB         EQU     $bfdf00

;------ Song ----------

sng_Tempo       EQU     8
sng_Instr0      EQU     10
sng_Len         EQU     1436
sng_Seq         EQU     1438
sng_Data        EQU     1566

;------ InstrData -----

MAXINSTR        EQU     30
SIZEOF_insdt    EQU     46

instr_Name      EQU     0
instr_Start     EQU     30
instr_Len       EQU     34
instr_RStart    EQU     36
instr_RLen      EQU     40
instr_Vol       EQU     42
instr_Freq      EQU     44

;------- AudioData -

SIZEOF_ad       EQU     24

ad_SampPer      EQU     0
ad_Effet        EQU     2
ad_Info         EQU     3
ad_Volume       EQU     4
ad_SmpAdr       EQU     6
ad_SmpLen       EQU     10
ad_RepAdr       EQU     12
ad_RepLen       EQU     16
ad_ArpPer       EQU     18
ad_DMABit       EQU     20
ad_PitchPer     EQU     22

;------- AudioChips -------

DMACON          EQU     $dff096
DMASET          EQU     $8000

AUD0LCH         EQU     $dff0a0
AUD1LCH         EQU     $dff0b0
AUD2LCH         EQU     $dff0c0
AUD3LCH         EQU     $dff0d0
AUDxLEN         EQU     $4
AUDxPER         EQU     $6
AUDxVOL         EQU     $8
AUDxDAT         EQU     $a
SIZEOF_AUD      EQU     $10

;------ Block ---------

NEXTTRACK       EQU     4
NEXTLINE        EQU     16
BLOCKSIZE       EQU     1024
MAXTRACK        EQU     4

;------- Notes -------

DO2             EQU     1712
SI5             EQU     113
STOP            EQU     $7ff

;------- Effects -----

MASK_EFF        EQU     7
MASK_VAL        EQU     $f

ARPEGGIO        EQU     0
PITCHUP         EQU     1
PITCHDOWN       EQU     2
VOLUME          EQU     3
MASTERVOL       EQU     4
TEMPO           EQU     5
JUMP            EQU     6
FILTER          EQU     7

MAX_VOL         EQU     64
BREAK           EQU     $ff


;------------------ functions to call -----------------

; Timer interruption installation and Player initialization routines
; Return 0 (zero) if successful.
; Return 1 if the installation is not possible.

InitPlay:
        lea     clrstart,a0
        moveq   #(clrend-clrstart)/2-1,d0
.clr
        clr.w   (a0)+
        dbra    d0,.clr

        movem.l d1-d2/a0-a3/a6,-(sp)
        move.l  SysBase,a6
        lea     CiabName,a1
        moveq   #0,d0
        jsr     _LVOOpenResource(a6)
        move.l  d0,CiabBase
        beq.s   InitPlayError
        cmp.b   #PAL,eb_PowerSupplyFreq(a6)
        beq.s   initsong
        move.w  #2,IsNTSC
initsong:
        bsr     InitSampleAdr
        bsr     InitSamples
        bsr     InitInterrupt
        tst.l   d0
        beq     EndInitPlay
InitPlayError:
        moveq   #1,d0
EndInitPlay:
        movem.l (sp)+,d1-d2/a0-a3/a6
        rts

; Stop to play the module

RemovePlayer:
        movem.l d0-d1/a0-a1/a6,-(sp)
        bsr     DisablePlayer
        move.l  CiabBase,a6
        lea     TimerInt,a1
        moveq   #1,d0
        jsr     _LVORemICRVector(a6)
        bclr    #1,CIAAPRA
        movem.l (sp)+,d0-d1/a0-a1/a6
        rts

;-------------- internal functions -----------------

InitInterrupt:
        bsr     EnablePlayer
        move.l  CiabBase,a6
        lea     TimerInt,a1
        moveq   #1,d0
        jsr     _LVOAddICRVector(a6)
        tst.l   d0
        bne.s   InitInterruptEnd
        move.b  CIABCRB,d0
        and.b   #%10000000,d0
        or.b    #1,d0
        move.b  d0,CIABCRB
        move.b  #$82,CIABICR
        lea     TimerTable,a0
        move.w  IsNTSC,d0
        move.b  1(a0,d0),CIABTBLO
        move.b  0(a0,d0),CIABTBHI
        moveq   #0,d0
InitInterruptEnd:
        rts

InitSampleAdr:
        move.l  modmem,a1
        lea     sng_Len(a1),a0
        move.w  (a0)+,d0                ; d0 = songlen
        subq.w  #1,d0
        moveq   #0,d1
        move.l  d1,d2
SeekLastBlockLoop:
        move.b  (a0)+,d2
        cmp.b   d2,d1
        bhi.s   NotLastBlock
        move.b  d2,d1
NotLastBlock:
        dbra    d0,SeekLastBlockLoop
        addq.w  #1,d1
        moveq   #10,d0
        lsl.l   d0,d1                   ; d1 = block size
        lea     sng_Data(a1),a0
        add.l   d1,a0                   ; a0 -> first sample
        lea     sng_Instr0(a1),a2       ; a2 -> first instrument
        lea     SampleAdrTable,a3
        moveq   #MAXINSTR,d0
InitAdrLoop:
        moveq   #0,d1
        move.w  instr_Len(a2),d1        ; len ?
        beq.s   InitNextAdr
InitThisAdr:
        move.l  a0,(a3)
        moveq   #0,d2
        cmp.w   #1,instr_RLen(a2)       ; rlen
        beq.s   GetSampleSize
        move.w  instr_RLen(a2),d2
        add.l   d2,d1
GetSampleSize:
        add.l   d1,d1
        bclr    #0,instr_Start+3(a2)    ; align instr_Start on a word
        add.l   instr_Start(a2),d1      ; Start
        add.l   d1,a0                   ; a0 -> nextsample
InitNextAdr:
        addq.l  #4,a3
        add.l   #SIZEOF_insdt,a2
        dbra    d0,InitAdrLoop
        rts

InitSamples:
        lea     SampleAdrTable,a0
        moveq   #MAXINSTR,d0
InitSamplesLoop:
        move.l  (a0)+,d1
        beq.s   InitSamplesLoopEnd
        move.l  d1,a1
        moveq   #3,d2
Clear4Bytes:
        clr.b   (a1)+
        dbra    d2,Clear4Bytes
InitSamplesLoopEnd:
        dbra    d0,InitSamplesLoop
        rts


DisablePlayer:
        clr.w   AUD0LCH+AUDxVOL
        clr.w   AUD1LCH+AUDxVOL
        clr.w   AUD2LCH+AUDxVOL
        clr.w   AUD3LCH+AUDxVOL
        move.w  #$0f,DMACON
        rts

EnablePlayer:
        clr.w   AUD0LCH+AUDxVOL
        clr.w   AUD1LCH+AUDxVOL
        clr.w   AUD2LCH+AUDxVOL
        clr.w   AUD3LCH+AUDxVOL
        rts

TimerIntRoutine:
        movem.l d1-d7/a0-a6,-(sp)
        bsr.s   PlayRoutine
        movem.l (sp)+,d1-d7/a0-a6
        moveq   #0,d0
        rts

PlayRoutine:
        move.l  modmem,a6
        move.w  8(a6),d0
        addq.w  #1,PlayTimer
        cmp.w   PlayTimer,d0            ; tempo == playtimer ?
        bne.s   PlayEffects             ; no, play effects
        clr.w   PlayTimer               ; yes, reset timer to zero
        bra     PlaySound               ; play note

PlayEffects:
        lea     ChannelData0,a0
        lea     AUD0LCH,a1
        moveq   #MAXTRACK-1,d3          ; 4 tracks
PlayEffLoop:
        move.w  (a0),d0
        and.w   #$7ff,d0
        cmp.w   #STOP,d0                ; note == STOP ?
        beq.s   PlayEffLoopEnd
        tst.b   ad_Info(a0)             ; effect value == 0 ?
        beq.s   PlayEffLoopEnd
        bsr.s   MakeEffects
PlayEffLoopEnd:
        add.l   #SIZEOF_ad,a0           ; a0 -> next channeldata
        add.w   #SIZEOF_AUD,a1          ; a1 -> next audio register
        dbra    d3,PlayEffLoop
        rts

MakeEffects:
        move.b  ad_Effet(a0),d0
        beq.s   EffArpeggio
        cmp.b   #PITCHUP,d0
        beq.s   EffPitchUp
        cmp.b   #PITCHDOWN,d0
        beq.s   EffPitchDown
        rts

EffArpeggio:
        moveq   #0,d0
        move.w  PlayTimer,d0
        cmp.w   #1,d0
        beq.s   EffArpOne
        cmp.w   #2,d0
        beq.s   EffArpTwo
        cmp.w   #3,d0
        beq.s   EffArpThree
        cmp.w   #4,d0
        beq.s   EffArpTwo
        cmp.w   #5,d0
        beq.s   EffArpOne
        rts

EffArpOne:
        move.b  ad_Info(a0),d0
        lsr.b   #4,d0                   ; d0 = high nibble value
        bra.s   EffArpSeek
EffArpTwo:
        move.b  ad_Info(a0),d0
        and.b   #MASK_VAL,d0            ; d0 = low nibble value
        bra.s   EffArpSeek
EffArpThree:
        move.w  ad_ArpPer(a0),d2        ; d2 = normal period
        bra.s   EffArpFound
EffArpSeek:
        add.w   d0,d0                   ; d0 = offset notetable
        move.w  ad_ArpPer(a0),d1        ; d1 = normal period
        lea     NoteTable,a2
EffArpLoop:
        move.w  0(a2,d0.w),d2           ; d2 = new period
        cmp.w   (a2)+,d1                ; (a2) == normal period
        bne.s   EffArpLoop
EffArpFound:
        move.w  d2,AUDxPER(a1)
        rts

EffPitchUp:
        moveq   #0,d0
        move.b  ad_Info(a0),d0          ; d0 = effect value
        sub.w   d0,ad_PitchPer(a0)
        cmp.w   #SI5,ad_PitchPer(a0)    ; less than lowest period ?
        bpl.s   EffPitchOK
        move.w  #SI5,ad_PitchPer(a0)
        bra.s   EffPitchOK
EffPitchDown:
        moveq   #0,d0
        move.b  ad_Info(a0),d0
        add.w   d0,ad_PitchPer(a0)
        cmp.w   #DO2,ad_PitchPer(a0)    ; more than highest period ?
        bmi.s   EffPitchOK
        move.w  #DO2,ad_PitchPer(a0)
EffPitchOK:
        move.w  ad_PitchPer(a0),AUDxPER(a1)
        rts

PlaySound:
        lea     sng_Data(a6),a0
        lea     sng_Seq(a6),a1
        move.w  CurrentPos,d0
        move.w  d0,NewPosJump
        moveq   #0,d1
        move.b  0(a1,d0.w),d1           ; d1 = block number
        moveq   #10,d0
        lsl.l   d0,d1
        add.l   OffsetBlock,d1          ; d1 = offset into block data
        clr.w   AudioDMA

        lea     AUD0LCH,a3
        lea     ChannelData0,a4
        moveq   #MAXTRACK-1,d7          ; 4 tracks
PlayLoop:
        bsr     PlayInstr
        add.w   #SIZEOF_AUD,a3
        add.l   #SIZEOF_ad,a4
        dbra    d7,PlayLoop

        move.w  AudioDMA,d0             ; start audio DMA
        or.w    #DMASET,d0
        move.w  d0,DMACON

        move.b  CIABCRA,d1              ; wait loop
        move.b  d1,d0
        and.b   #%11000000,d0
        or.b    #%00001000,d0
        move.b  d0,CIABCRA
        move.b  #$2f,CIABTALO
        move.b  #1,CIABTAHI
PlayDelay1:
        btst.b  #0,CIABCRA
        bne.s   PlayDelay1
        move.b  d1,CIABCRA
        move.b  #%00000001,CIABICR

        lea     ChannelData0,a0         ; set new value into audio registers
        lea     AUD0LCH,a1
        moveq   #MAXTRACK-1,d0
SetNewValue:
        move.l  ad_RepAdr(a0),(a1)
        move.w  ad_RepLen(a0),AUDxLEN(a1)
        add.l   #SIZEOF_ad,a0
        add.w   #SIZEOF_AUD,a1
        dbra    d0,SetNewValue
        cmp.l   #BLOCKSIZE-NEXTLINE,OffsetBlock  ; end of block ?
        beq.s   ChangePosition
        add.l   #NEXTLINE,OffsetBlock   ; modif. offsetblock
        tst.w   BreakStatus             ; jump or break ?
        beq.s   PlaySoundEnd
        clr.w   BreakStatus
ChangePosition:
        clr.l   OffsetBlock             ; offset block = 0
        move.w  NewPosJump,CurrentPos
        addq.w  #1,CurrentPos           ; new position
        move.w  sng_Len(a6),d0
        move.w  CurrentPos,d1
        cmp.w   d0,d1                   ; end of song ?
        bne.s   PlaySoundEnd
        clr.w   CurrentPos              ; position = 0
PlaySoundEnd:
        rts

PlayInstr:
        lea     sng_Instr0(a6),a2
        move.l  0(a0,d1.l),ad_SampPer(a4)
        addq.l  #NEXTTRACK,d1
        moveq   #0,d0
        move.b  ad_SampPer(a4),d0
        lsr.b   #3,d0                   ; d0 = instrument number
        tst.b   d0                      ; d0 == 0 ?
        beq.s   NoSampleChange

        lea     SampleAdrTable,a5
        subq.b  #1,d0
        move.l  d0,d3
        lsl.w   #2,d0                   ; d0 = SampleAdrTable offset
        mulu    #SIZEOF_insdt,d3
        add.l   #instr_Start,d3
        add.l   d3,a2                   ; a2 -> Start InstrData
        move.l  0(a5,d0.w),d4
        add.l   (a2)+,d4                ; Start
        move.l  d4,ad_SmpAdr(a4)
        beq.s   NoSampleChange
        move.w  (a2)+,ad_SmpLen(a4)     ; Len
        move.l  (a2)+,d5                ; RStart
        move.w  (a2)+,d2                ; RLen
        bne.s   SetRepeat
        moveq   #1,d2
        moveq   #0,d5
SetRepeat:
        move.w  d2,ad_RepLen(a4)
        add.l   ad_SmpAdr(a4),d5
        move.l  d5,ad_RepAdr(a4)
        move.w  (a2),ad_Volume(a4)      ; Volume
NoSampleChange:
        move.w  ad_SampPer(a4),d6
        and.w   #$7ff,d6                ; note == 0 ?
        beq.s   TestEffects
        move.w  d6,ad_ArpPer(a4)
        move.w  ad_DMABit(a4),d0
        move.w  d0,DMACON               ; cut DMA audio for this track

        move.b  CIABCRA,d2              ; wait loop
        move.b  d2,d3
        and.b   #%11000000,d3
        or.b    #%00001000,d3
        move.b  d3,CIABCRA
        move.b  #$2f,CIABTALO
        move.b  #1,CIABTAHI
PlayDelay2:
        btst.b  #0,CIABCRA
        bne.s   PlayDelay2
        move.b  d2,CIABCRA
        move.b  #%00000001,CIABICR

        cmp.w   #STOP,d6                ; note == 'OFF' ?
        bne.s   SetPlayRegs
        clr.w   AUDxVOL(a3)             ; set audio volume to 0
        or.w    d0,AudioDMA
        rts

SetPlayRegs:
        move.l  ad_SmpAdr(a4),(a3)      ; sample address == NULL ?
        beq.s   TestEffects
        move.w  ad_SmpLen(a4),AUDxLEN(a3)
        move.w  d6,AUDxPER(a3)
        or.w    d0,AudioDMA
        move.w  d6,ad_PitchPer(a4)
TestEffects:
        move.b  ad_Effet(a4),d0         ; d0 = effect
        bsr.s   EffVolume
        tst.b   d0
        beq.s   TestEffectsEnd
        cmp.b   #FILTER,d0
        beq     EffFilter
        cmp.b   #MASTERVOL,d0
        beq.s   EffMaster
        cmp.b   #TEMPO,d0
        beq.s   EffSpeed
        cmp.b   #JUMP,d0
        beq.s   EffJump
TestEffectsEnd:
        rts

EffVolume:                              ; modify volume
        cmp.w   #VOLUME,d0
        bne.s   UseInstrVol
        moveq   #0,d2
        move.b  ad_Info(a4),d2          ; d0 = new volume
        bra.s   SetInstrVol
UseInstrVol:
        tst.w   d6
        beq.s   EffVolumeEnd
        move.w  ad_Volume(a4),d2
SetInstrVol:
        sub.w   MasterVolume,d2         ; new volume - master >= 0
        bge.s   SetVolumeReg
        moveq   #0,d2                   ; new volume = 0
SetVolumeReg:
        move.w  d2,AUDxVOL(a3)          ; new volume into audio reg
EffVolumeEnd:
        rts

EffMaster:                              ; modify master volume
        moveq   #MAX_VOL,d2
        move.b  ad_Info(a4),d0
        sub.b   d0,d2
        blt.s   EffMasterEnd
        move.b  d2,MasterVolume+1
EffMasterEnd:
        rts

EffSpeed:                               ; modify tempo
        move.b  ad_Info(a4),d0
        beq.s   EffSpeedEnd
        clr.w   PlayTimer
        move.b  d0,sng_Tempo+1(a6)
EffSpeedEnd:
        rts

EffJump:                                ; jump or break
        move.w  #1,BreakStatus
        move.b  ad_Info(a4),d0
        cmp.b   #BREAK,d0
        beq.s   EffBreak
        subq.b  #2,d0
        ext.w   d0
        move.w  d0,NewPosJump
        rts
EffBreak:
        move.w  CurrentPos,NewPosJump
        rts

EffFilter:                              ; filter on/off
        move.b  ad_Info(a4),d0
        bne.s   SetFilter
        bset    #1,CIAAPRA
        rts
SetFilter:
        bclr    #1,CIAAPRA
        rts

        data    __MERGED

CiabBase:       dc.l    0
IsNTSC:         dc.w    0

CiabName:       dc.b    'ciab.resource',0
TimerIntName:   dc.b    'DSS Tracker Player',0
                cnop    0,2

TimerInt:
        dc.l  0                 ; Interrupt.is_Node.ln_Succ
        dc.l  0                 ; Interrupt.is_Node.ln_Pred
        dc.b  2                 ; Interrupt.is_Node.ln_Type = NT_INTERRUPT
        dc.b  -10               ; Interrupt.is_Node.ln_Pri
        dc.l  TimerIntName      ; Interrupt.is_Node.ln_Name
        dc.l  0                 ; Interrupt.is_Data
        dc.l  TimerIntRoutine   ; Interrupt.is_Code

clrstart

PlayOn:         dc.w    0
PlayTimer:      dc.w    0
CurrentPos:     dc.w    0
OffsetBlock:    dc.l    0
AudioDMA:       dc.w    0
MasterVolume:   dc.w    0
BreakStatus:    dc.w    0
NewPosJump:     dc.w    0

clrend

ChannelData0:
        dc.w    0               ; Period                0
        dc.w    0               ; Sample+Info           2
        dc.w    0               ; Volume                4
        dc.l    0               ; Sample address        6
        dc.w    0               ; Sample length        10
        dc.l    0               ; Repeat address       12
        dc.w    0               ; Repeat length        16
        dc.w    0               ; Shazam period        18
        dc.w    1               ; DMA bit              20
        dc.w    0               ; Pitch  period        22
ChannelData1:
        ds.w    10
        dc.w    2
        dc.w    0
ChannelData2:
        ds.w    10
        dc.w    4
        dc.w    0
ChannelData3:
        ds.w    10
        dc.w    8
        dc.w    0

        dc.w    1712
NoteTable:
        dc.w    1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960,906
        dc.w    856,808,762,720,678,640,604,570,538,508,480,453
        dc.w    428,404,381,360,339,320,302,285,269,254,240,226
        dc.w    214,202,190,180,170,160,151,143,135,127,120,113
        dc.w    113,113,113,113,113,113,113,113,113,113,113,113
        dc.w    113,113,113,113,113,113,113,113,113,113,113,113
        dc.w    113,113,113,113,113,113

TimerTable:     dc.w    14187,14318

SampleAdrTable: ds.l    31

name    dc.b    "Digital Sound Studio",0

        end
