/*
;**************************************************************************
;*
;*   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) and the
;*   audio channels 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 written for the Aztec C 3.6 assembler.
;*   It can be easily adapted to another assembler.
;*   
;*   Here is a example for using this player from a C program (Note that
;*   the values MODULE_FILENAME and MODULE_FILESIZE must be replaced by
;*   by the name and the size of the module file you wish to play.)
;*   
;*   ---------------------------------------------------------------------
;*   ---------------------------------------------------------------------
;*
;*      extern LONG InitPlay();
;*      extern VOID RemovePlayer();
;*
;*      extern BYTE *SongPointer;
;*  
;*      main()
;*      {
;*       BYTE *buffer;
;*       struct FileHandle *file;
;*       
;*       if(buffer = (BYTE *)AllocMem(MODULE_FILESIZE, MEMF_CHIP|MEMF_CLEAR))
;*        {
;*         if(file = Open(MODULE_FILENAME, MODE_OLDFILE))
;*          {
;*           Read(file, buffer, MODULE_FILESIZE);
;*           Close(file);
;*
;*           SongPointer = buffer;
;*  
;*           if(NOT InitPlay())
;*            {
;*             Delay(1000L);
;*             RemovePlayer();
;*            }
;*          }
;*         FreeMem(buffer, MODULE_FILESIZE);
;*        }
;*      }
;*
;*   ---------------------------------------------------------------------
;*   ---------------------------------------------------------------------
;*  
;*  
;**************************************************************************
*/


;**************************************************************************
;*
;*                           DEFINITIONS
;*
;**************************************************************************

;**************************************************************************
;----- 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_Speed       EQU     8
sng_Tempo       EQU     9
sng_Instr0      EQU     10
sng_Len         EQU     1436
sng_Seq         EQU     1438
sng_Data        EQU     1566

NOTE_MASK       EQU     $7ff
LONIB_MASK      EQU     $f
NIBBLE_SHIFT    EQU     4


;**************************************************************************
;------ InstrData -----
;**************************************************************************

MAXINSTR        EQU     30
SIZEOF_insdt    EQU     46

instr_Start     EQU     30
instr_Len       EQU     34
instr_RLen      EQU     40


;**************************************************************************
;------- ChannelData -
;**************************************************************************

SIZEOF_ad        EQU     34
        
ad_SampPer       EQU     0              ; W
ad_Effect        EQU     2              ; B
ad_Info          EQU     3              ; B
ad_Tune          EQU     4              ; B
ad_Volume        EQU     5              ; B
ad_SmpAdr        EQU     6              ; L
ad_SmpLen        EQU     10             ; W
ad_RepAdr        EQU     12             ; L
ad_RepLen        EQU     16             ; W
ad_RealPer       EQU     18             ; W
ad_DMABit        EQU     20             ; W
ad_PitchCtrl     EQU     22             ; B
ad_PortaCtrl     EQU     23             ; B
ad_BlkLoopStart  EQU     24             ; W
ad_BlkLoopCount  EQU     26             ; B
ad_VolumeChange  EQU     27             ; B
ad_SmpOffset     EQU     28             ; W
ad_PortaDir      EQU     30             ; B
ad_PortaSpeed    EQU     31             ; B
ad_PortaDestPer  EQU     32             ; W


;**************************************************************************
;------- 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 -------
;**************************************************************************

SIZEOF_NoteTable EQU    96
MIN_PER          EQU    2
MAX_PER          EQU    0

DO2             EQU     1712
SI5             EQU     113
STOP            EQU     $7ff


;**************************************************************************
;------- Effects -----
;**************************************************************************

ARPEGGIO        EQU     0
PITCHSLIDEUP    EQU     1
PITCHSLIDEDN    EQU     2
VOLUME          EQU     3
MASTERVOL       EQU     4
TEMPO           EQU     5
JUMPTOPOS       EQU     6
FILTER          EQU     7
PITCHUP         EQU     8
PITCHDN         EQU     9
PITCHCTRL       EQU     $a
SPEED           EQU     $b
VOLUMEUP        EQU     $c
VOLUMEDN        EQU     $d
VOLSLIDEUP      EQU     $e
VOLSLIDEDN      EQU     $f
MASTERVOLUP     EQU     $10
MASTERVOLDN     EQU     $11
MASTSLIDEUP     EQU     $12
MASTSLIDEDN     EQU     $13
LOOPSTART       EQU     $14
JUMPTOLOOP      EQU     $15
NOTEREPLAY      EQU     $16
NOTEDELAY       EQU     $17
NOTECUT         EQU     $18
INSTRSTART      EQU     $19
INSTRTUNE       EQU     $1a
PORTAMENTO      EQU     $1b
PORTAVOLSLIDEUP EQU     $1c
PORTAVOLSLIDEDN EQU     $1d
PORTAMENTOCTRL  EQU     $1e

MAX_VOL         EQU     64
OLDBREAK        EQU     $ff
AVERAGE_SPEED   EQU     125
MIN_SPEED       EQU     $1c


;**************************************************************************
;*
;*                     END OF DEFINITIONS
;*
;**************************************************************************


;**************************************************************************
;*
;*                          CODE
;*
;**************************************************************************

                CSEG

;******************************************************
;*                                                    *
;*                  functions to call                 *
;*                                                    *
;******************************************************

;**************************************************************************
;* Timer interruption installation and Player initialization routines.
;* Return 0 (zero) in D0 if successful.
;* Return 1 in D0 if the installation has failed.
;**************************************************************************

_InitPlay:
        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  #4,IsNTSC
InitSong:
        bsr.s   InitSampleAdr
        bsr.s   InitSamples
        bsr.s   InitInterrupt
        tst.l   d0
        beq.s   EndInitPlay
InitPlayError:
        moveq   #1,d0
EndInitPlay:
        movem.l (sp)+,d1-d2/a0-a3/a6
        rts


;**************************************************************************
;* Stop the replay of the module and remove the interrupt.
;**************************************************************************

_RemovePlayer:
        movem.l d0-d1/a0-a1/a6,-(sp)
        bsr.s   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                 *
;*                                                    *
;******************************************************

;**************************************************************************
;* Init the CIA interrup.
;* Return 0 in D0 if successful.
;**************************************************************************

InitInterrupt:
        bsr.s   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.l  (a0,d0),d0
        move.l  d0,TimerValue
        moveq   #0,d1
        move.l  _SongPointer,a0
        move.b  sng_Speed(a0),d1
        bne.s   SetTimer
        moveq   #AVERAGE_SPEED,d1
SetTimer:
        divu    d1,d0
        move.b  d0,CIABTBLO
        lsr.w   #8,d0
        move.b  d0,CIABTBHI
        moveq   #0,d0
InitInterruptEnd:
        rts


;**************************************************************************
;* Init the Samples Address Table.
;**************************************************************************

InitSampleAdr:
        move.l  _SongPointer,a1
        lea     sng_Len(a1),a0
        move.w  (a0)+,d0                ; d0 = song len
        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)       ; repeat len
        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 boundary
        add.l   instr_Start(a2),d1      ; Start
        add.l   d1,a0                   ; a0 -> next sample
InitNextAdr:
        addq.l  #4,a3
        add.l   #SIZEOF_insdt,a2
        dbra    d0,InitAdrLoop
        rts


;**************************************************************************
;* Clear the first longword of each sample.
;**************************************************************************

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


;**************************************************************************
;* Clear all Volume audio regs and stop audio DMA.
;**************************************************************************

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


;**************************************************************************
;* Clear all Volume audio regs.
;**************************************************************************

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


;**************************************************************************
;* CIA interrupt routine.
;**************************************************************************

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


;**************************************************************************
;* Main Player Routine.
;**************************************************************************

PlayRoutine:
        moveq   #0,d0
        move.l  _SongPointer,a6
        move.b  sng_Tempo(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
        clr.w   ReplayTimer
        move.w  #3,ArpTimer
        bra.s   PlaySound               ; play note


;**************************************************************************
;* Play effects between notes.
;**************************************************************************

PlayEffects:
        lea     ChannelData0,a0
        lea     AUD0LCH,a1
        moveq   #MAXTRACK-1,d3          ; 4 tracks
PlayEffLoop:
        move.w  (a0),d0
        and.w   #NOTE_MASK,d0
        cmp.w   #STOP,d0                ; note == STOP ?
        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


;**************************************************************************
;* Effect Dispatcher.
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;**************************************************************************

MakeEffects:
        move.b  ad_Effect(a0),d0
        tst.b   ad_Info(a0)
        beq.s   MakeEffectsEnd
        cmp.b   #ARPEGGIO,d0
        beq.s   EffArpeggio
        cmp.b   #PITCHSLIDEUP,d0
        beq.s   EffPitchSlideUp
        cmp.b   #PITCHSLIDEDN,d0
        beq.s   EffPitchSlideDn
        cmp.b   #VOLSLIDEUP,d0
        beq.s   EffVolSlideUp
        cmp.b   #VOLSLIDEDN,d0
        beq.s   EffVolSlideDn
        cmp.b   #MASTSLIDEUP,d0
        beq.s   EffMastVolSlideUp
        cmp.b   #MASTSLIDEDN,d0
        beq.s   EffMastVolSlideDn
        cmp.b   #NOTEREPLAY,d0
        beq.s   EffNoteReplay
        cmp.b   #NOTEDELAY,d0
        beq.s   EffNoteDelay
        cmp.b   #NOTECUT,d0
        beq.s   EffNoteCut
        cmp.b   #PORTAVOLSLIDEUP,d0
        beq.s   EffPortaVolSlideUp
        cmp.b   #PORTAVOLSLIDEDN,d0
        beq.s   EffPortaVolSlideDn
MakeEffectsEnd:
        cmp.b   #PORTAMENTO,d0
        beq.s   EffPortamento
        rts


;**************************************************************************
;* Effect = ARPEGGIO (0)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;**************************************************************************

EffArpeggio:
        moveq   #0,d2
        move.w  ArpTimer,d2             ; d2 = timer arpeggio
        sub.w   PlayTimer,d2            ; d2 = ArpTimer - PlayTimer
        beq.s   EffArpThree             ; PlayTimer = 0 [3]
        cmp.w   #1,d2
        beq.s   EffArpTwo               ; PlayTimer = 2 [3]
EffArpOne:
        move.b  ad_Info(a0),d2
        lsr.b   #NIBBLE_SHIFT,d2        ; d2 = high nibble value
        bra.s   EffArpSeek
EffArpTwo:
        move.b  ad_Info(a0),d2
        and.b   #LONIB_MASK,d2          ; d2 = low nibble value
EffArpSeek:
        add.w   d2,d2                   ; d2 = offset notetable
        move.w  ad_RealPer(a0),d1       ; d1 = normal period
        bsr.s   FindMaxMinPointer
        mulu    #SIZEOF_NoteTable/4,d0
        lea     NoteTable,a3
        add.l   d0,a3                   ; a3 -> real period table
EffArpLoop:
        move.w  (a3,d2.w),d0            ; d0 = new period
        cmp.w   MIN_PER(a2),d0
        beq.s   EffArpFound
        cmp.w   (a3)+,d1                ; (a3) == normal period ?
        bne.s   EffArpLoop
        bra.s   EffArpFound
EffArpThree:
        addq.w  #3,ArpTimer
        move.w  ad_RealPer(a0),d0       ; d0 = normal period
EffArpFound:
        move.w  d0,AUDxPER(a1)
        rts


;**************************************************************************
;* Effect = PITCHSLIDEUP (1)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;**************************************************************************

EffPitchSlideUp:        
        bsr.s   FindMaxMinPointer
        moveq   #0,d0
        move.b  ad_Info(a0),d0               ; d0 = effect value
        sub.w   d0,ad_RealPer(a0)
        move.w  ad_RealPer(a0),d0
        cmp.w   MIN_PER(a2),d0               ; more than lowest period ?
        bge.s   EffPitchOK
        move.w  MIN_PER(a2),ad_RealPer(a0)
        bra.s   EffPitchOK

;**************************************************************************
;* Effect = PITCHSLIDEDN (2)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;**************************************************************************

EffPitchSlideDn:
        bsr.s   FindMaxMinPointer
        moveq   #0,d0
        move.b  ad_Info(a0),d0
        add.w   d0,ad_RealPer(a0)
        move.w  ad_RealPer(a0),d0
        cmp.w   MAX_PER(a2),d0               ; less than highest period ?
        ble.s   EffPitchOK
        move.w  MAX_PER(a2),ad_RealPer(a0)
EffPitchOK:
        move.w  ad_RealPer(a0),d0
        tst.b   ad_PitchCtrl(a0)
        beq.s   SetPitchPeriod
        bsr.s   FindNearestNote
SetPitchPeriod:
        move.w  d0,AUDxPER(a1)               ; new period in reg
        rts


;**************************************************************************
;* Find the pointer on MaxMinNoteTable.
;* INPUT:
;*      a0 -> current ChannelData
;*
;* OUTPUT:
;*      a2 -> Max-Min pour FineTune
;*      d0 = FineTune * 4
;**************************************************************************

FindMaxMinPointer:
        moveq   #0,d0
        lea     MaxMinNoteTable,a2
        move.b  ad_Tune(a0),d0
        lsl.w   #2,d0
        add.l   d0,a2                   ; a2 -> min/max period for tuning
        rts


;**************************************************************************
;* Effect = VOLSLIDEUP ($E)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;**************************************************************************

EffVolSlideUp:                          ; increment volume
        moveq   #0,d0
        move.b  ad_Info(a0),d0          ; d0 = effect value
EffVolSlideUpSet:
        add.b   d0,ad_Volume(a0)        ; add d0 to volume
        cmp.b   #MAX_VOL,ad_Volume(a0)  ; volume <= 64 ?
        ble.s   EffVolSlideOK
        move.b  #MAX_VOL,ad_Volume(a0)  ; volume = 64
        bra.s   EffVolSlideOK

;**************************************************************************
;* Effect = VOLSLIDEDN ($F)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;**************************************************************************

EffVolSlideDn:                          ; decrement volume
        moveq   #0,d0
        move.b  ad_Info(a0),d0          ; d0 = effect value
EffVolSlideDnSet:
        sub.b   d0,ad_Volume(a0)        ; substract d0 from volume
        bge.s   EffVolSlideOK           ; d0 >= 0 ?
        clr.b   ad_Volume(a0)           ; d0 = 0
EffVolSlideOK:
        move.b  ad_Volume(a0),d0        ; d0 = volume
        sub.w   MasterVolume,d0         ; substract Master vol
        bge.s   EffVolSlideSet          ; d0 >= 0 ?
        moveq   #0,d0
EffVolSlideSet:
        move.w  d0,AUDxVOL(a1)
EffVolSlideEnd:
        rts


;**************************************************************************
;* Effect = MASTSLIDEUP ($12)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;**************************************************************************

EffMastVolSlideUp:
        moveq   #0,d0
        move.b  ad_Info(a0),d0          ; d0 = effect value
EffMastVolSlideUpSet:
        sub.w   d0,MasterVolume         ; substract d0 from Mast vol
        tst.w   MasterVolume
        bge.s   EffMastVolSlideOK       ; Mast vol >= 0 ?
        clr.w   MasterVolume
        bra.s   EffMastVolSlideOK

;**************************************************************************
;* Effect = MASTSLIDEDN ($13)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;**************************************************************************

EffMastVolSlideDn:
        moveq   #0,d0
        move.b  ad_Info(a0),d0
EffMastVolSlideDnSet:
        add.w   d0,MasterVolume
        cmp.w   #MAX_VOL,MasterVolume
        ble.s   EffMastVolSlideOK
        move.w  #MAX_VOL,MasterVolume
EffMastVolSlideOK:
        moveq   #0,d0
        movem.l a0-a1,-(sp)
        lea     ChannelData0,a0
        lea     AUD0LCH,a1
        moveq   #MAXTRACK-1,d1          ; 4 tracks
EffMastVolSlideSet:
        bsr.s   EffVolSlideOK
        add.l   #SIZEOF_ad,a0           ; a0 -> next channeldata
        add.l   #SIZEOF_AUD,a1          ; a1 -> next audioreg
        dbra    d1,EffMastVolSlideSet
        movem.l (sp)+,a0-a1
        rts


;**************************************************************************
;* Effect = NOTEREPLAY ($16)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;*      a6 = SongPointer
;**************************************************************************

EffNoteReplay:
        moveq   #0,d0
        move.b  ad_Info(a0),d0
        cmp.b   sng_Tempo(a6),d0
        bge.s   EffNoteReplayEnd            ; if info >= Tempo, abort
        tst.w   ReplayTimer                 ; replaytimer != 0
        bne.s   TimeToReplay
        move.w  d0,ReplayTimer              ; replaytimer = info
TimeToReplay:
        move.w  PlayTimer,d1
        cmp.w   ReplayTimer,d1              ; replaytimer == timer ?
        bne.s   EffNoteReplayEnd
        add.w   d0,ReplayTimer              ; replaytimer += info
ReplayNote:
        move.w  ad_DMABit(a0),d0
        move.w  d0,DMACON                   ; stop audio DMA
        move.l  ad_SmpAdr(a0),(a1)          ; set sample addr
        move.w  ad_SmpLen(a0),AUDxLEN(a1)   ; set sample len
        bsr.s   WaitForDMA
        or.w    #DMASET,d0
        move.w  d0,DMACON                   ; start audio DMA
        bsr.s   WaitForDMA
        move.l  ad_RepAdr(a0),(a1)          ; set rep addr
        move.w  ad_RepLen(a0),AUDxLEN(a1)   ; set rep len
EffNoteReplayEnd:
        rts


;**************************************************************************
;* Effect = NOTEDELAY ($17)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;*      a6 = SongPointer
;**************************************************************************

EffNoteDelay:
        move.b  ad_Info(a0),d0
        cmp.b   sng_Tempo(a6),d0            ; if info < Tempo, do effect
        blt.s   DoNoteDelay
        clr.w   ad_Effect(a0)               ; else, effect = info = 0
        bra.s   EffNoteDelayEnd
DoNoteDelay:
        cmp.b   PlayTimer+1,d0              ; timer == info ?
        bne.s   EffNoteDelayEnd
        move.w  ad_SampPer(a0),d0           ; period || sample ?
        beq.s   EffNoteDelayEnd
        and.w   #NOTE_MASK,d0
        beq.s   ReplayNote
        bsr.s   FindRealPeriod
        move.w  d0,AUDxPER(a1)
        move.w  d0,ad_RealPer(a0)
        bne.s   ReplayNote                  ; do replay
EffNoteDelayEnd:
        rts


;**************************************************************************
;* Effect = NOTECUT ($18)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;*      a6 = SongPointer
;**************************************************************************

EffNoteCut:
        move.b  ad_Info(a0),d0
        cmp.b   sng_Tempo(a6),d0            ; if info < Tempo, do effect
        blt.s   DoNoteCut
        clr.w   ad_Effect(a0)               ; else, effect = info = 0
        bra.s   EffNoteCutEnd
DoNoteCut:
        cmp.b   PlayTimer+1,d0              ; timer == info ?
        bne.s   EffNoteCutEnd
        clr.b   ad_Volume(a0)               ; yes, clear volume
        clr.w   AUDxVOL(a1)
EffNoteCutEnd:
        rts


;**************************************************************************
;* Effect = PORTAMENTO ($1B)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;**************************************************************************

EffPortamento:
        move.b  ad_Info(a0),d0                     ; info == 0 ?
        beq.s   DoPortamento
        move.b  d0,ad_PortaSpeed(a0)               ; speed = info
        clr.b   ad_Info(a0)
DoPortamento:                                      
        tst.w   ad_PortaDestPer(a0)                ; destperiod == 0 ?
        beq.s   EffPortamentoEnd                   ; yes, end
        moveq   #0,d0
        move.b  ad_PortaSpeed(a0),d0
        tst.b   ad_PortaDir(a0)                    ; dir == 1 ?
        bne.s   PortamentoUp                       ; yes, increase pitch
        add.w   d0,ad_RealPer(a0)                  ; period += info
        move.w  ad_PortaDestPer(a0),d0
        cmp.w   ad_RealPer(a0),d0                  ; destperiod > period ?
        bgt.s   PortamentoOK
        move.w  ad_PortaDestPer(a0),ad_RealPer(a0) ; period = destperiod
        clr.w   ad_PortaDestPer(a0)                ; destperiod = 0
        bra.s   PortamentoOK
PortamentoUp:
        sub.w   d0,ad_RealPer(a0)                  ; period -= info
        move.w  ad_PortaDestPer(a0),d0
        cmp.w   ad_RealPer(a0),d0                  ; destperiod < period ?
        blt.s   PortamentoOK
        move.w  ad_PortaDestPer(a0),ad_RealPer(a0) ; period = destperiod
        clr.w   ad_PortaDestPer(a0)                ; destperiod = 0
PortamentoOK:
        move.w  ad_RealPer(a0),d0                  ; d0 = period
        tst.b   ad_PortaCtrl(a0)                   ; glissando ?
        beq.s   SetPortaPeriod
        bsr.s   FindNearestNote
SetPortaPeriod:
        move.w  d0,AUDxPER(a1)
EffPortamentoEnd:
        rts


;**************************************************************************
;* Effect = PORTAVOLSLIDEUP ($1C)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;**************************************************************************

EffPortaVolSlideUp:
        bsr.s   DoPortamento
        bra.s   EffVolSlideUp


;**************************************************************************
;* Effect = PORTAVOLSLIDEDN ($1D)
;* INPUT:
;*      a0 -> current ChannelData
;*      a1 -> current AudioRegs
;**************************************************************************

EffPortaVolSlideDn:
        bsr.s   DoPortamento
        bra.s   EffVolSlideDn


;**************************************************************************
;* Find the nearest note from the real period.
;* INPUT:
;*      a0 -> current ChannelData
;*      d0 = Real period
;*
;* OUTPUT:
;*      d0 = Nearest note
;**************************************************************************

FindNearestNote:
        movem.l d1/a0,-(sp)
        moveq   #0,d1
        move.b  ad_Tune(a0),d1
        lea     NoteTable,a0
        mulu    #SIZEOF_NoteTable,d1
        add.l   d1,a0
        moveq   #(SIZEOF_NoteTable/2)-1,d1
FindNearestNoteLoop:
        cmp.w   (a0)+,d0
        bge.s   NearestNoteFound
        dbra    d1,FindNearestNoteLoop
NearestNoteFound:
        move.w  -2(a0),d0
        movem.l (sp)+,d1/a0
        rts


;**************************************************************************
;* Play a sound on each channel.
;* Called when PlayTimer = Tempo.
;* INPUT:
;*      a6 = SongPointer
;**************************************************************************

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
        move.w  OffsetBlock,d0
        add.l   d0,d1                               ; d1 = offset into block data
        clr.w   AudioDMA
        lea     AUD0LCH,a3
        lea     ChannelData0,a4
        moveq   #MAXTRACK-1,d7                      ; 4 tracks
PlayLoop:
        bsr.s   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
        bsr.s   WaitForDMA
        lea     ChannelData0,a0                     ; new values into audio registers
        lea     AUD0LCH,a1
        moveq   #MAXTRACK-1,d0
SetNewValue:
        cmp.b   #NOTEDELAY,ad_Effect(a0)
        beq.s   SetNewValueIterate
        move.l  ad_RepAdr(a0),(a1)
        move.w  ad_RepLen(a0),AUDxLEN(a1)
SetNewValueIterate:
        add.l   #SIZEOF_ad,a0
        add.w   #SIZEOF_AUD,a1
        dbra    d0,SetNewValue
        tst.w   BlockLoopStatus                     ; block loop ?
        beq.s   NoPosChange
        move.w  NewOffsetBlock,OffsetBlock          ; new value for offset in block
        clr.w   BlockLoopStatus
NoPosChange:
        add.w   #NEXTLINE,OffsetBlock               ; modify offsetblock
        cmp.w   #BLOCKSIZE,OffsetBlock              ; end of block ?
        blt.s   TestJump
ChangePosition:
        clr.w   OffsetBlock                         ; offset block = 0
        move.w  #-1,ChannelData0+ad_BlkLoopStart    ; clear block loop starts
        move.w  #-1,ChannelData1+ad_BlkLoopStart
        move.w  #-1,ChannelData2+ad_BlkLoopStart
        move.w  #-1,ChannelData3+ad_BlkLoopStart
        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   TestJump
        clr.w   CurrentPos                          ; position = 0
TestJump:
        tst.w   BreakStatus                         ; jump or break ?
        beq.s   PlaySoundEnd
        clr.w   BreakStatus
        bra.s   ChangePosition
PlaySoundEnd:
        rts


;******************************************************************
;* Wait Audio DMA reset.
;* 
;* Note :
;*  it is perhaps better to do this with the copper!
;*                   \__\|          moveq   #$79,d2
;*                      /|label1:   move.b  $dff007,d3
;*                       |label2:   cmp.b   $dff007,d3
;*                       |          beq.s   label2
;*                       |          dbra    d2,label1
;*
;******************************************************************

WaitForDMA:
        movem.l d0-d1,-(sp)
        move.b  CIABCRA,d1              ; wait for CIAB timerA
        move.b  d1,d0
        and.b   #%11000000,d0           ; save freq and mode bits
        or.b    #%00001000,d0           ; oneshot
        move.b  d0,CIABCRA
        move.b  #$2f,CIABTALO           ; wait for 420 µs
        move.b  #1,CIABTAHI
PlayDelay1:
        btst.b  #0,CIABCRA              ; end of wait ?
        bne.s   PlayDelay1
        move.b  d1,CIABCRA              ; CIABPRA old value
        move.b  #%00000001,CIABICR      ; cancel intreq CIAB timerA
        movem.l (sp)+,d0-d1
        rts


;**************************************************************************
;* Find the real period based on the instrument tuning.
;* INPUT:
;*      a0 -> current ChannelData
;*      d0 = Period store in song
;*
;* OUTPUT:
;*      d0 = Real Period
;**************************************************************************

FindRealPeriod:
        movem.l d1-d2/a0,-(sp)
        moveq   #0,d1
        move.b  ad_Tune(a0),d1                 ; d1 = instr tuning
        beq.s   FindRealPeriodEnd              ; tuning == 0 ?
        lea     NoteTable,a0
        moveq   #(SIZEOF_NoteTable/2)-1,d2
FindRealPeriodLoop
        cmp.w   (a0)+,d0                       ; d0 == period in table ?
        beq.s   StandardPeriodFound
        dbra    d2,FindRealPeriodLoop
StandardPeriodFound:
        mulu    #SIZEOF_NoteTable,d1           ; d1 = offset period tables
        move.w  -2(a0,d1),d0                   ; d0 = real period
FindRealPeriodEnd:
        movem.l (sp)+,d1-d2/a0
        rts


;**************************************************************************
;* Set all Portamento parameters.
;* INPUT:
;*      d6 = Period
;*      a4 -> current ChannelData
;**************************************************************************

FindPortaData:
        movem.l d0/a0,-(sp)
        move.w  d6,d0
        move.l  a4,a0
        bsr.s   FindRealPeriod
        move.w  d0,ad_PortaDestPer(a4)         ; d0 = destperiod
        clr.b   ad_PortaDir(a4)
        move.w  ad_PortaDestPer(a0),d0
        cmp.w   ad_RealPer(a4),d0              ; destperiod > period ?
        beq.s   ClearPortamento                ; if =, no porta
        bgt.s   FindPortaDataEnd               ; if >, dir = 0
        move.b  #1,ad_PortaDir(a4)             ; else, dir = 1
        bra.s   FindPortaDataEnd
ClearPortamento:
        clr.w   ad_PortaDestPer(a4)
FindPortaDataEnd:
        movem.l (sp)+,d0/a0
        rts


;**************************************************************************
;* Play a note on the current Audio channel.
;*      a0 -> SongData
;*      a3 -> current AudioRegs
;*      a4 -> current ChannelData
;*      d1 = offset block
;**************************************************************************

PlayInstr:
        lea     sng_Instr0(a6),a2
        move.l  (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  (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_Tune(a4)               ; Tune + Volume
NoSampleChange:
        move.w  ad_SampPer(a4),d6
        beq.s   TestEffects
        and.w   #NOTE_MASK,d6                  ; note == 0 ?
        beq.s   TestEffects
        cmp.b   #PORTAMENTO,ad_Effect(a4)
        beq.s   ActivatePorta
        cmp.b   #PORTAVOLSLIDEUP,ad_Effect(a4)
        beq.s   ActivatePorta
        cmp.b   #PORTAVOLSLIDEDN,ad_Effect(a4)
        bne.s   CutAudioDMA
ActivatePorta:
        bsr     FindPortaData
        bra     UseInstrVol
CutAudioDMA:
        move.w  ad_DMABit(a4),d0
        move.w  d0,DMACON                      ; cut audio DMA for that track
        bsr.s   WaitForDMA
        cmp.w   #STOP,d6                       ; note == 'OFF' ?
        bne.s   TestInstrTune
        clr.w   AUDxVOL(a3)                    ; clear audio volume
        or.w    d0,AudioDMA
        rts
TestInstrTune:
        cmp.b   #INSTRTUNE,ad_Effect(a4)       ; effect == INSTRTUNE ?
        bne.s   TestNoteDelay
        bsr.s   EffInstrTune
        bra.s   SetPlayRegs
TestNoteDelay:
        cmp.b   #NOTEDELAY,ad_Effect(a4)       ; effect == NOTEDELAY ?
        beq.s   UseInstrVol
TestInstrStart:
        cmp.b   #INSTRSTART,ad_Effect(a4)
        bne.s   SetPlayRegs
        bsr.s   EffSampleOffset
SetPlayRegs:                                   ; set the audio registers
        move.l  ad_SmpAdr(a4),(a3)             ; sample address == NULL ?
        beq.s   TestEffects
        move.w  ad_SmpLen(a4),AUDxLEN(a3)
        movem.l d0/a0,-(sp)
        move.w  d6,d0
        move.l  a4,a0
        bsr.s   FindRealPeriod
        move.w  d0,d6
        movem.l (sp)+,d0/a0
        move.w  d6,AUDxPER(a3)
        move.w  d6,ad_RealPer(a4)
        or.w    d0,AudioDMA
TestEffects:
        move.b  ad_Effect(a4),d0               ; d0 = effect
        beq.s   UseInstrVol
        cmp.b   #MASTERVOL,d0
        beq.s   EffMaster
        cmp.b   #VOLUMEUP,d0
        beq.s   EffVolumeUp
        cmp.b   #VOLUMEDN,d0
        beq.s   EffVolumeDn
        cmp.b   #MASTERVOLUP,d0
        beq.s   EffMastVolUp
        cmp.b   #MASTERVOLDN,d0
        beq.s   EffMastVolDn
        bsr.s   EffVolume
        cmp.b   #TEMPO,d0
        beq.s   EffTempo
        cmp.b   #JUMPTOPOS,d0
        beq.s   EffJumpToPos
        cmp.b   #FILTER,d0
        beq.s   EffFilter
        cmp.b   #PITCHUP,d0
        beq.s   EffPitchUp
        cmp.b   #PITCHDN,d0
        beq.s   EffPitchDn
        cmp.b   #PITCHCTRL,d0
        beq.s   EffPitchCtrl
        cmp.b   #SPEED,d0
        beq.s   EffSpeed
        cmp.b   #LOOPSTART,d0
        beq.s   EffBlockLoopStart
        cmp.b   #JUMPTOLOOP,d0
        beq.s   EffBlockLoopJump
        cmp.b   #PORTAMENTOCTRL,d0
        beq.s   EffPortaCtrl
        rts


;**************************************************************************
;* Set the volume for the current track.
;* INPUT:
;*      a3 -> current AudioRegs
;*      a4 -> current ChannelData
;*      d6 = Real Period
;**************************************************************************

EffVolume:                              ; modify volume
        cmp.b   #VOLUME,d0
        bne.s   UseInstrVol
        moveq   #0,d2
        move.b  ad_Info(a4),d2          ; d0 = new volume
        move.b  d2,ad_Volume(a4)
        bra.s   SetInstrVol
UseInstrVol:
        tst.w   d6
        beq.s   EffVolumeEnd
        move.b  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


;**************************************************************************
;* Effect = MASTERVOL (4)
;* INPUT:
;*      a4 -> current ChannelData
;**************************************************************************

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:
        bra.s   UseInstrVol


;**************************************************************************
;* Effect = TEMPO (5)
;* INPUT:
;*      a4 -> current ChannelData
;*      a6 = SongPointer
;**************************************************************************

EffTempo:                               ; modify tempo
        move.b  ad_Info(a4),d0
        beq.s   EffTempoEnd
        clr.w   PlayTimer
        clr.w   ReplayTimer
        move.w  #3,ArpTimer
        move.b  d0,sng_Tempo(a6)
EffTempoEnd:
        rts


;**************************************************************************
;* Effect = JUMPTOPOS (6)
;* INPUT:
;*      a4 -> current ChannelData
;*      a6 = SongPointer
;**************************************************************************

EffJumpToPos:                           ; jump or break
        moveq   #0,d0
        move.b  ad_Info(a4),d0
        cmp.w   sng_Len(a6),d0          ; if newpos > songlen, abort
        bgt.s   EffJumpToPosEnd
        tst.w   d0                      ; BREAK
        beq.s   EffBreak
        cmp.w   #OLDBREAK,d0
        beq.s   EffBreak
        subq.b  #2,d0
        move.w  d0,NewPosJump
        bra.s   EffJumpSet
EffBreak:
        move.w  CurrentPos,NewPosJump
EffJumpSet:
        move.w  #1,BreakStatus
EffJumpToPosEnd:
        rts


;**************************************************************************
;* Effect = FILTER (7)
;* INPUT:
;*      a4 -> current ChannelData
;**************************************************************************

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


;**************************************************************************
;* Effect = PITCHUP (8)
;* INPUT:
;*      a3 -> current AudioReg
;*      a4 -> current ChannelData
;**************************************************************************

EffPitchUp:
        tst.b   ad_Info(a4)
        beq.s   EffPitchUpEnd
        movem.l a0-a2,-(sp)
        move.l  a4,a0
        move.l  a3,a1
        move.w  ad_PitchCtrl(a0),-(sp)     ; save porta+pitch controls
        clr.b   ad_PitchCtrl(a0)           ; clear pitch control
        bsr.s   EffPitchSlideUp
        move.w  (sp)+,ad_PitchCtrl(a0)     ; restore porta+pitch controls
        movem.l (sp)+,a0-a2
EffPitchUpEnd:
        rts


;**************************************************************************
;* Effect = PITCHDN (9)
;* INPUT:
;*      a3 -> current AudioReg
;*      a4 -> current ChannelData
;**************************************************************************

EffPitchDn:
        tst.b   ad_Info(a4)
        beq.s   EffPitchDnEnd
        movem.l a0-a2,-(sp)
        move.l  a4,a0
        move.l  a3,a1
        move.w  ad_PitchCtrl(a0),-(sp)     ; save porta+pitch controls
        clr.b   ad_PitchCtrl(a0)           ; clear pitch control
        bsr.s   EffPitchSlideDn
        move.w  (sp)+,ad_PitchCtrl(a0)     ; restore porta+pitch controls
        movem.l (sp)+,a0-a2
EffPitchDnEnd:
        rts


;**************************************************************************
;* Effect = PITCHCTRL ($A)
;* INPUT:
;*      a4 -> current ChannelData
;**************************************************************************

EffPitchCtrl:
        move.b  ad_Info(a4),ad_PitchCtrl(a4)
        rts

;**************************************************************************
;* Effect = SPEED ($B)
;* INPUT:
;*      a4 -> current ChannelData
;*      a6 = SongPointer
;**************************************************************************

EffSpeed:
        moveq   #0,d0
        move.b  ad_Info(a4),d0
        cmp.w   #MIN_SPEED,d0           ; info >= 28 ?
        blt.s   EffSpeedEnd
        move.l  d1,-(sp)
        move.b  d0,sng_Speed(a6)
        move.l  TimerValue,d1
        divu    d0,d1                   ; d1 = timervalue/speed
        move.b  d1,CIABTBLO
        lsr.w   #8,d1
        move.b  d1,CIABTBHI
        clr.w   PlayTimer               ; reset counters
        clr.w   ReplayTimer
        move.w  #3,ArpTimer
        move.l  (sp)+,d1
EffSpeedEnd:
        rts


;**************************************************************************
;* Effect = VOLUMEUP ($C)
;* INPUT:
;*      a3 -> current AudioReg
;*      a4 -> current ChannelData
;**************************************************************************

EffVolumeUp:
        moveq   #0,d0
        move.b  ad_Info(a4),d0
        beq.s   EffVolumeUpEnd
        move.l  a0,-(sp)
        move.l  a4,a0
        move.l  a3,a1
        bsr.s   EffVolSlideUpSet
        move.l  (sp)+,a0
EffVolumeUpEnd
        bra.s   UseInstrVol


;**************************************************************************
;* Effect = VOLUMEDN ($D)
;* INPUT:
;*      a3 -> current AudioReg
;*      a4 -> current ChannelData
;**************************************************************************

EffVolumeDn:
        moveq   #0,d0
        move.b  ad_Info(a4),d0
        beq.s   EffVolumeDnEnd
        move.l  a0,-(sp)
        move.l  a4,a0
        move.l  a3,a1
        bsr.s   EffVolSlideDnSet
        move.l  (sp)+,a0
EffVolumeDnEnd:
        bra.s   UseInstrVol


;**************************************************************************
;* Effect = MASTERVOLUP ($10)
;* INPUT:
;*      a4 -> current ChannelData
;**************************************************************************

EffMastVolUp:
        moveq   #0,d0
        move.b  ad_Info(a4),d0
        beq.s   EffMastVolUpEnd
        movem.l d1/a0,-(sp)
        move.l  a4,a0
        bsr.s   EffMastVolSlideUpSet
        movem.l (sp)+,d1/a0
EffMastVolUpEnd:
        bra.s   UseInstrVol


;**************************************************************************
;* Effect = MASTERVOLDN ($11)
;* INPUT:
;*      a4 -> current ChannelData
;**************************************************************************

EffMastVolDn:
        moveq   #0,d0
        move.b  ad_Info(a4),d0
        beq.s   EffMastVolDnEnd
        movem.l d1/a0,-(sp)
        move.l  a4,a0
        bsr.s   EffMastVolSlideDnSet
        movem.l (sp)+,d1/a0
EffMastVolDnEnd:
        bra.s   UseInstrVol


;**************************************************************************
;* Effect = LOOPSTART ($14)
;* INPUT:
;*      a4 -> current ChannelData
;**************************************************************************

EffBlockLoopStart:
        tst.b   ad_Info(a4)                     ; value == 0 => loop start
        bne.s   EffBlockLoopStartEnd
        move.w  OffsetBlock,ad_BlkLoopStart(a4) ; store current offset
EffBlockLoopStartEnd:
        rts


;**************************************************************************
;* Effect = JUMPTOLOOP ($15)
;* INPUT:
;*      a4 -> current ChannelData
;**************************************************************************

EffBlockLoopJump:
        move.b  ad_Info(a4),d0
        beq.s   EffBlockLoopClear
        tst.w   ad_BlkLoopStart(a4)                 ; loop start == -1 ?
        bmi.s   EffBlockLoopEnd
        tst.b   ad_BlkLoopCount(a4)                 ; counter == 0 ?
        bne.s   DecBlockLoopCount
        move.b  d0,ad_BlkLoopCount(a4)              ; store value into counter
        bra.s   EffBlockLoopSet
DecBlockLoopCount:
        subq.b  #1,ad_BlkLoopCount(a4)              ; decrement counter
        beq.s   EffBlockLoopClear                   ; counter == 0 ?
EffBlockLoopSet:
        move.w  ad_BlkLoopStart(a4),NewOffsetBlock  ; new offset
        sub.w   #NEXTLINE,NewOffsetBlock
        move.w  #1,BlockLoopStatus                  ; loop enable
EffBlockLoopClear:
        move.w  #-1,ad_BlkLoopStart(a4)             ; loop start = -1
EffBlockLoopEnd:
        rts


;**************************************************************************
;* Effect = INSTRSTART ($19)
;* INPUT:
;*      a4 -> current ChannelData
;**************************************************************************

EffSampleOffset:
        move.l  d0,-(sp)
        moveq   #0,d0
        move.b  ad_Info(a4),d0
        beq.s   UseSampleOffset         ; if info == 0, use old offset
        lsl.w   #7,d0
        move.w  d0,ad_SmpOffset(a4)
UseSampleOffset:
        move.w  ad_SmpOffset(a4),d0
        beq.s   EffSampleOffsetEnd
        cmp.w   ad_SmpLen(a4),d0        ; offset >= oneshot ?
        bge.s   OneWordLen              ; yes => oneshot == 1
        sub.w   d0,ad_SmpLen(a4)        ; oneshot -= offset
        add.w   d0,d0                   ; offset in bytes
        add.l   d0,ad_SmpAdr(a4)        ; adr += offset
        bra.s   EffSampleOffsetEnd
OneWordLen:
        move.w  #1,ad_SmpLen(a4)
EffSampleOffsetEnd:
        move.l  (sp)+,d0
        rts


;**************************************************************************
;* Effect = INSTRTUNE ($1A)
;* INPUT:
;*      a4 -> current ChannelData
;**************************************************************************

EffInstrTune:
        move.l  d0,-(sp)
        move.b  ad_Info(a4),d0
        and.b   #LONIB_MASK,d0
        move.b  d0,ad_Tune(a4)
        move.l  (sp)+,d0
        rts


;**************************************************************************
;* Effect = PORTAMENTOCTRL ($1E)
;* INPUT:
;*      a4 -> current ChannelData
;**************************************************************************

EffPortaCtrl:
        move.b  ad_Info(a4),ad_PortaCtrl(a4)
        rts


;**************************************************************************
;*
;*                     END OF CODE
;*
;**************************************************************************


;**************************************************************************
;*
;*                         DATA
;*
;**************************************************************************

                DSEG

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


PlayOn:             dc.w    0
PlayTimer:          dc.w    0
CurrentPos:         dc.w    0
OffsetBlock:        dc.w    0
AudioDMA:           dc.w    0
MasterVolume:       dc.w    0
BreakStatus:        dc.w    0
NewPosJump:         dc.w    0
BlockLoopStatus:    dc.w    0
NewOffsetBlock:     dc.w    0
ReplayTimer:        dc.w    0
ArpTimer:           dc.w    3

ChannelData0:
        dc.w    0      ; Sample (5 bits) + Periode (11 bits)   0
        dc.b    0      ; Effect                                1
        dc.b    0      ; Info                                  2
        dc.b    0      ; Tune                                  4
        dc.b    0      ; Volume                                5
        dc.l    0      ; Sample Adr                            6
        dc.w    0      ; Sample Len                           10
        dc.l    0      ; Repeat Adr                           12
        dc.w    0      ; Repeat Len                           16
        dc.w    0      ; Real period                          18
        dc.w    1      ; DMA bit                              20
        dc.b    0      ; Pitch control (0=off, 1=on)          22
        dc.b    0      ; Portamento control (0=off, 1=on)     23
        dc.w    -1     ; Block Loop Start                     24
        dc.b    0      ; Block Loop Count                     26
        dc.b    0      ; pad bit                              27
        dc.w    0      ; Sample Offset                        28
        dc.b    0      ; Portamento direction                 30
        dc.b    0      ; Portamento speed                     31
        dc.w    0      ; Portamemto dest period               32
ChannelData1:
        ds.w    10
        dc.w    2,0,-1,0,0,0,0
ChannelData2:
        ds.w    10
        dc.w    4,0,-1,0,0,0,0
ChannelData3:
        ds.w    10
        dc.w    8,0,-1,0,0,0,0

NoteTable:
; Instr Fine Tune = 0
        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
; Instr Fine Tune = 1
        dc.w    1700,1604,1514,1430,1348,1274,1202,1134,1070,1010,954,900
        dc.w    850,802,757,715,674,637,601,567,535,505,477,450
        dc.w    425,401,379,357,337,318,300,284,268,253,239,225
        dc.w    213,201,189,179,169,159,150,142,134,126,119,113
; Instr Fine Tune = 2
        dc.w    1688,1592,1504,1418,1340,1264,1194,1126,1064,1004,948,894
        dc.w    844,796,752,709,670,632,597,563,532,502,474,447
        dc.w    422,398,376,355,335,316,298,282,266,251,237,224
        dc.w    211,199,188,177,167,158,149,141,133,125,118,112
; Instr Fine Tune = 3
        dc.w    1676,1582,1492,1408,1330,1256,1184,1118,1056,996,940,888
        dc.w    838,791,746,704,665,628,592,559,528,498,470,444
        dc.w    419,395,373,352,332,314,296,280,264,249,235,222
        dc.w    209,198,187,176,166,157,148,140,132,125,118,111
; Instr Fine Tune = 4
        dc.w    1664,1570,1482,1398,1320,1246,1176,1110,1048,990,934,882
        dc.w    832,785,741,699,660,623,588,555,524,495,467,441
        dc.w    416,392,370,350,330,312,294,278,262,247,233,220
        dc.w    208,196,185,175,165,156,147,139,131,124,117,110
; Instr Fine Tune = 5
        dc.w    1652,1558,1472,1388,1310,1238,1168,1102,1040,982,926,874
        dc.w    826,779,736,694,655,619,584,551,520,491,463,437
        dc.w    413,390,368,347,328,309,292,276,260,245,232,219
        dc.w    206,195,184,174,164,155,146,138,130,123,116,109
; Instr Fine Tune = 6
        dc.w    1640,1548,1460,1378,1302,1228,1160,1094,1032,974,920,868
        dc.w    820,774,730,689,651,614,580,547,516,487,460,434
        dc.w    410,387,365,345,325,307,290,274,258,244,230,217
        dc.w    205,193,183,172,163,154,145,137,129,122,115,109
; Instr Fine Tune = 7
        dc.w    1628,1536,1450,1368,1292,1220,1150,1086,1026,968,914,862
        dc.w    814,768,725,684,646,610,575,543,513,484,457,431
        dc.w    407,384,363,342,323,305,288,272,256,242,228,216
        dc.w    204,192,181,171,161,152,144,136,128,121,114,108
; Instr Fine Tune = 8 (-8)
        dc.w    1814,1712,1616,1524,1440,1356,1280,1208,1140,1076,1016,960
        dc.w    907,856,808,762,720,678,640,604,570,538,508,480
        dc.w    453,428,404,381,360,339,320,302,285,269,254,240
        dc.w    226,214,202,190,180,170,160,151,143,135,127,120
; Instr Fine Tune = 9 (-7)
        dc.w    1800,1700,1604,1514,1430,1350,1272,1202,1134,1070,1010,954
        dc.w    900,850,802,757,715,675,636,601,567,535,505,477
        dc.w    450,425,401,379,357,337,318,300,284,268,253,238
        dc.w    225,212,200,189,179,169,159,150,142,134,126,119
; Instr Fine Tune = 10 (-6)
        dc.w    1788,1688,1592,1504,1418,1340,1264,1194,1126,1064,1004,948
        dc.w    894,844,796,752,709,670,632,597,563,532,502,474
        dc.w    447,422,398,376,355,335,316,298,282,266,251,237
        dc.w    223,211,199,188,177,167,158,149,141,133,125,118
; Instr Fine Tune = 11 (-5)
        dc.w    1774,1676,1582,1492,1408,1330,1256,1184,1118,1056,996,940
        dc.w    887,838,791,746,704,665,628,592,559,528,498,470
        dc.w    444,419,395,373,352,332,314,296,280,264,249,235
        dc.w    222,209,198,187,176,166,157,148,140,132,125,118
; Instr Fine Tune = 12 (-4)
        dc.w    1762,1664,1570,1482,1398,1320,1246,1176,1110,1048,988,934
        dc.w    881,832,785,741,699,660,623,588,555,524,494,467
        dc.w    441,416,392,370,350,330,312,294,278,262,247,233
        dc.w    220,208,196,185,175,165,156,147,139,131,123,117
; Instr Fine Tune = 13 (-3)
        dc.w    1750,1652,1558,1472,1388,1310,1238,1168,1102,1040,982,926
        dc.w    875,826,779,736,694,655,619,584,551,520,491,463
        dc.w    437,413,390,368,347,328,309,292,276,260,245,232
        dc.w    219,206,195,184,174,164,155,146,138,130,123,116
; Instr Fine Tune = 14 (-2)
        dc.w    1736,1640,1548,1460,1378,1302,1228,1160,1094,1032,974,920
        dc.w    868,820,774,730,689,651,614,580,547,516,487,460
        dc.w    434,410,387,365,345,325,307,290,274,258,244,230
        dc.w    217,205,193,183,172,163,154,145,137,129,122,115
; Instr Fine Tune = 15 (-1)
        dc.w    1724,1628,1536,1450,1368,1292,1220,1150,1086,1026,968,914
        dc.w    862,814,768,725,684,646,610,575,543,513,484,457
        dc.w    431,407,384,363,342,323,305,288,272,256,242,228
        dc.w    216,203,192,181,171,161,152,144,136,128,121,114

MaxMinNoteTable: dc.w    DO2,SI5            ; Instr Fine Tune = 0
                 dc.w    1700,113           ; Instr Fine Tune = 1
                 dc.w    1688,112           ; Instr Fine Tune = 2
                 dc.w    1676,111           ; Instr Fine Tune = 3
                 dc.w    1664,110           ; Instr Fine Tune = 4
                 dc.w    1652,109           ; Instr Fine Tune = 5
                 dc.w    1640,109           ; Instr Fine Tune = 6
                 dc.w    1628,108           ; Instr Fine Tune = 7
                 dc.w    1814,120           ; Instr Fine Tune = 8 (-8)
                 dc.w    1800,119           ; Instr Fine Tune = 9 (-7)
                 dc.w    1788,118           ; Instr Fine Tune = 10 (-6)
                 dc.w    1774,118           ; Instr Fine Tune = 11 (-5)
                 dc.w    1762,117           ; Instr Fine Tune = 12 (-4)
                 dc.w    1750,116           ; Instr Fine Tune = 13 (-3)
                 dc.w    1736,115           ; Instr Fine Tune = 14 (-2)
                 dc.w    1724,114           ; Instr Fine Tune = 15 (-1)

SampleAdrTable: ds.l    31

TimerValue:     dc.l    0
TimerTable:     dc.l    1773447,1789773

_SongPointer:   dc.l    0               ; address of Module in CHIP memory

;**************************************************************************
;*
;*                       END OF DATA
;*
;**************************************************************************


                END
