* This file contains the assembly code for the music driver
* Copyright 1990 MicroIllusions, Inc.

* includes
		include 'exec/types.i'
		include 'exec/ables.i'
		include 'exec/ports.i'
		include 'hardware/intbits.i'
		include 'hardware/custom.i'

* imports
		xref    _geta4,_SysBase,_HandleNote,_StopSample
;		xref    _SysBase,_HandleNote,_StopSample
		xref    _LVOGetMsg,_LVOPutMsg,_LVOCause,_LVOSignal
		xref    _channeldata,_clockval,_clockinc,_playflag
		xref    _playeventptr,_stopeventptr,_stopevents,_loopeventptr
		xref    _amiga_samples,_sample_bank,_doneport,_playport
		xref	_sync_task,_sync_bit,_sync_val
		xref	_sfxeventptr,_clocksfx,_sfxchannel
		xref	_volume_divide

* exports
		xdef    _HandleNoteInt,_MusicTickInt,_EnvelopeStart

_intena 	equ     $00DFF09A       		; needed for ENABLE/DISABLE

FULL_VOLUME	equ		16

*
* Structure definitions (must match structs in music.c!)
*
		STRUCTURE envseg,0      		; segment of an envelope
		LONG    delta   			; $xxxx.xxxx delta
		UWORD   duration			; ticks to achieve target vol.
		UBYTE   volume  			; target volume level
		UBYTE   flags   			; flags for this segment
		LABEL   SIZEOF_envseg


		STRUCTURE channel,0     		; data about each channel
		APTR    current_sample  		; sample that was played
		APTR    env_data			; ptr to envelope data
		ULONG   value   			; current value ($xxxx.xxxx)
		LONG    increment       		; amount to add ($xxxx.xxxx)
		UWORD   count   			; iterations left in segment
		UWORD   targetvol       		; target volume level
		UWORD   status  			; envelope generation flags
		UWORD   period_entry    		; entry in period table used
		UWORD   chstoptime      		; clock time when this voice releases
		UBYTE   octave_shift    		; how many times to double period
		UBYTE   midi_note       		; pitch of note that was played
		UBYTE   midi_channel    		; midi channel note played on
		UBYTE   chan_priority
		LABEL   SIZEOF_channel


		STRUCTURE playmsg,MN_SIZE
		APTR    sample
		UBYTE   pmmidi_note
		UBYTE   pmmidi_channel
		UBYTE   pmaudio_channel
		UBYTE   pm_priority
		LABEL   SIZEOF_playmsg


		STRUCTURE sequence,0
		ULONG   seqnum  			; sequence number (ignored)
		UBYTE   startmode       		; autostart flag (ignored)
		UBYTE   clockmode       		; abs/rel clock (ignored)
		UWORD   startmeasure    		; starting measure
		UWORD   tempo   			; tempo in quarter notes per min.
		UWORD   beatsperbar     		; beats in each measure
		UWORD   beatsize			; 1=whole, 2=half, etc.
		UWORD   barsize 			; number of clock ticks per measure
		ULONG   numevents       		; number of events in sequence
		APTR    events  			; ptr to event list
		LABEL   SIZEOF_sequence


		STRUCTURE event,0
		UWORD   starttime       		; start time for event
		UBYTE   command 			; pitch ($00-$7F) or command
		UBYTE   data    			; attack vel. & channel or command data
		UWORD   stoptime			; stop time for event
		LABEL   SIZEOF_event

		far		code
		far		data

ENV_SUST		equ     0
ENV_HALT		equ     1
ENV_STOPPED     equ     2
ENV_RELEASE     equ     3

*
* _EnvelopeStart - setup variables for envelope generation
*
* Entry : 4(sp) = struct channel *, 8(sp) = struct envseg *
* Exit  : d0 = initial volume level
* Uses  : d0-d1/a0-a1
*
_EnvelopeStart
		move.l	a6,-(sp)
		move.l  8(sp),a0			; a0 = ptr to channel_data
		move.l  12(sp),a1			; a1 = ptr to array of envelope segments
		move.l  _SysBase,a6     		; get execbase for ENABLE/DISABLE

; debug
;		move.l	#64,d0
;		bra.s	dummy

		; keep ints from using incomplete data
		DISABLE

		move.w  duration(a1),d0
		beq     10$
		sub.w   #1,d0
10$     move.w  d0,count(a0)
		move.l  delta(a1),d0
		move.l  d0,increment(a0)
		move.l  d0,value(a0)    		; use first inc as value since starting at zero
		move.b  volume(a1),d1
		ext.w   d1
		move.w  d1,targetvol(a0)
		move.b  flags(a1),d1
		ext.w   d1
		move.w  d1,status(a0)

		lea     SIZEOF_envseg(a1),a1
		move.l  a1,env_data(a0) ; set ptr to envseg list

		ENABLE

		swap    d0      			; return int part of initial volume
		ext.l   d0
dummy	move.l	(sp)+,a6
		rts

*
* _MusicTickInt - tick interrupt for envelope generation
*
* scratch registers: d0-d1/a0-a1/a5-a6 (trashable in an interrupt)
*
mti_regs reg		d2-d4/d7/a2-a4/a6

_MusicTickInt
		movem.l mti_regs,-(sp)       	; save regs for system

		jsr		_geta4

		move.l	_sfxeventptr,d0				; is there a sfx note?
		beq.s	noplaysfx					;	no, skip ahead

		move.l	d0,a1						; put in proper register
		moveq	#0,d1
		move.l	d1,_sfxeventptr				; we've handled the sfx
		move.w	stoptime(a1),d1				; get stoptime
		swap	d1							; make normal time value
		move.w	d1,stoptime(a1)				; clear stoptime

		tst.l	_clocksfx					; any sound effect right now?
		beq.s	1$							; no, skip ahead

		move.l	_sfxchannel,a0				; let old sound effect end
		bset.b  #ENV_RELEASE,status+1(a0)   ; start releasing this note

1$		move.l	d1,_clocksfx				; set sound effect clock
		moveq	#1,d4						; sfx priority
		bsr		playnote					; play the sfx
		bra.s	noendsfx

noplaysfx
		move.l	_clocksfx,d0
		beq.s	noendsfx
		sub.l	_clockinc,d0
		ble.s	1$
		move.l	d0,_clocksfx
		bra		noendsfx

1$		clr.l	_clocksfx
		move.l	_sfxchannel,a0
		bset.b  #ENV_RELEASE,status+1(a0)       ; start releasing this note

* if no song playing exit quickly
noendsfx
		tst.w   _playflag
		beq     doenvelope

* update the clock

		move.l  _clockval,d7    		; get clock value ($xxxx.xxxx)
		add.l   _clockinc,d7    		; add fixed point increment
		move.l  d7,_clockval    		; store new clock value
		swap    d7      				; d7.w = clock tick

* check if any stop events pending

		moveq	#0,d4					; normal note priority
		move.l  _stopeventptr,a2		; get pointer to next stop event

checkstop
		move.l  (a2),d0 			; get stop event
		cmp.w   d0,d7   			; compare clock to stop time
		blo     nostopevent     		; if clock lower than stop time, don't stop

; debug
;		bra.s	nextstop

		swap    d0      			; move command/data bytes into low word
		move.w  d0,d1   			; make a copy
		and.w   #$0030,d1       		; extract amiga voice number
		lsr.w   #2,d1
		move.l  infotable(pc,d1.w),a0   	; get ptr to amiga channeldata
		and.b   #$0f,d0 			; extract midi channel number
		cmp.b   midi_channel(a0),d0     	; does it match the channeldata?
		bne     nextstop			; if not, skip it (must have been overwritten)

		lsr.w   #8,d0   			; extract midi pitch
		cmp.b   midi_note(a0),d0		; does it match the channeldata?
		bne     nextstop			; if not, skip it (must have been overwritten)

		bset.b  #ENV_RELEASE,status+1(a0)       ; start releasing this note

nextstop
		sub.l   #4,a2   				; move to next stop event (may have same stop time)
		move.l  a2,_stopeventptr		; save new ptr
		bra     checkstop       		; do it again

* this table here to be close for $xx(PC,Xn) addressing

infotable       dc.l    _channeldata
		dc.l    _channeldata+SIZEOF_channel
		dc.l    _channeldata+(SIZEOF_channel*2)
		dc.l    _channeldata+(SIZEOF_channel*3)

* check if time to do another event

nostopevent
		move.l  _playeventptr,a1		; get pointer to next event

		cmp.w   starttime(a1),d7		; compare clock to start time
		blo     doenvelope      		; if lower, then no events now

		add.l   #SIZEOF_event,_playeventptr     ; point to next event

		tst.b   command(a1)     		; is it a note command?
		bmi     notnote 			; if minus then not a note

		bsr		playnote				; play the note
		bra     nextevent

notnote
		move.b	command(a1),d0
	 	cmp.b   #$A0,d0					; is it a SYNC event?
		beq		syncevent

	 	cmp.b   #$80,d0					; is it a TEMPO event?
		beq		tempoevent

	 	cmp.b   #$FF,d0					; is it an END event?
		beq     terminate

		move.b	d0,d1					; get a copy
		and.b	#$f0,d1
		cmp.b	#$C0,d1					; is it a PRGM event?
		bne		nextevent

prgmevent
		and.w	#$000f,d0				; get channel #
		add.w	d0,d0					; index as APTR
		add.w	d0,d0
		move.b	data(a1),d1				; get new sample number
		and.w	#$001f,d1				; max 31 samples (for now)
		add.w	d1,d1					; also index as APTR
		add.w	d1,d1
		lea		_amiga_samples,a2		; get sample at PRGM#
		move.l	(a2,d1.w),a3
		lea		_sample_bank,a2			; and insert at sample bank CHAN#
		move.l	a3,(a2,d0.w)
		bra		nextevent

tempoevent
		moveq	#0,d0
		move.b	data(a1),d0				; get tempo / 2
		add.w	d0,d0					; tempo
		bsr		SetTempo
		bra		nextevent

terminate
		move.l	_loopeventptr,d0		; get loop pointer
		bne.s	loopsong				; if not NULL, go loop the song

		clr.w   _playflag
		bra     doenvelope

loopsong
		move.l  #0,_clockval    		; reset clock value to zero
		moveq	#0,d7					; local copy also

		move.l	d0,_playeventptr		; put loop point in playeventptr
										; and continue on...
nextevent
		bra     nostopevent

syncevent
		move.b	data(a1),d0
		beq.s	sync_video				; NOTE=0 means sync with video
		cmp.b	#1,d0
		beq.s	sync_begin_rep			; NOTE=1 means begin repeat
		cmp.b	#2,d0
		beq.s	sync_end_rep			; NOTE=2 means end repeat
		bra		nextevent

sync_video
		move.l	_sync_task,d0			; SYNC active?
		beq.s	nextevent				; no, go to next event

		movem.l	a0-a1,-(sp)
		lea		_sync_val,a0			; address of sync_val
		move.b	stoptime+1(a1),(a0)		; put data there
		move.l	d0,a1					; task in a1
		move.l	_sync_bit,d0			; signal mask in d0
		move.l  _SysBase,a6     		; get execbase
		jsr     _LVOSignal(a6)			; signal task
		movem.l (sp)+,a0-a1
		bra		nextevent

		xref	_repeventptr,_rep_val,_clockrep

sync_begin_rep
		move.b	stoptime+1(a1),_rep_val	; set repeat value
		move.l	_playeventptr,d0		; get current event pointer
		move.l	d0,_repeventptr			; set as repeat point
		swap	d7
		move.l	d7,_clockrep			; save clock
		swap	d7
		bra		nextevent

sync_end_rep
		move.b	_rep_val,d0				; get rep_val
		beq		nextevent				; just in case
		subq.b	#1,d0					; deduct 1 from counter
		move.b	d0,_rep_val
		beq		nextevent				; of now zero, no loop
		move.l	_repeventptr,d0			; get repeat point
		move.l	d0,_playeventptr		; set as current point
		move.l	_clockrep,d7			; set clock to repeat point
		move.l	d7,_clockval
		swap	d7

		movem.l d0-d1/a0-a1,-(sp)       ; save regs C trashes

		pea		0
		jsr     _StopSample     		; stop the sample 0
		addq.l	#1,(sp)
		jsr     _StopSample     		; stop the sample 1
		addq.l	#1,(sp)
		jsr     _StopSample     		; stop the sample 2
		addq.l	#1,(sp)
		jsr     _StopSample     		; stop the sample 3
		addq.w   #4,sp   				; cleanup stack

		movem.l (sp)+,d0-d1/a0-a1       ; restore regs

		bra		nextevent

* process all envelopes

doenvelope
; debug
;		bra.l	MusicTickX

		lea     _channeldata,a0 		; a0 = ptr to channel data
		moveq   #0,d2   			; d2 = channel number

MusicTick10
		move.w  status(a0),d0   		; get status flags

		btst.l  #ENV_STOPPED,d0 		; is this envelope in use?
		bne     MusicTick90     		; b-yes, do next generator

		btst.l  #ENV_HALT,d0    		; is this envelope the last?
		beq     MusicTick20     		; b-no, continue on

		tst.w   count(a0)       		; is there any time left?
		bne     MusicTick20     		; b-yes, continue on

		move.w  #1<<ENV_STOPPED,status(a0)  ; mark as stopped (free)

		movem.l d0-d3/a0-a3,-(sp)       ; save regs C trashes

		move.l  d2,-(sp)				; push channel # (Note: 32 BIT INTS!)
		jsr     _StopSample     		; stop the sample
		addq.w   #4,sp   				; cleanup stack

		movem.l (sp)+,d0-d3/a0-a3       ; restore regs

		bra     MusicTick90     		; continue with next channel

MusicTick20
		btst.l  #ENV_RELEASE,d0 		; start the release?
		beq     MusicTick50     		; b-no, continue on

		bclr.b  #ENV_RELEASE,status+1(a0)       ; reset release flag

		move.l  env_data(a0),a1 		; a1 = ptr to next envelope segment

		move.l	a1,d1					; are we REALLY sure this channel on?
		beq.s	MusicTick90				;	well if not, just skip

		move.w  targetvol(a0),d1		; d1 will hold volume release starts at

MusicTick30
		move.w  status(a0),d0   		; get channel status
		bne     MusicTick40     		; if any flags set, exit this loop

		move.b  volume(a1),d1   		; keep last volume
		ext.w   d1
		move.b  flags(a1),d0    		; get envelope segment flags
		ext.w   d0
		move.w  d0,status(a0)   		; store in channel status

		lea     SIZEOF_envseg(a1),a1    	; move to next segment
		bra     MusicTick30

MusicTick40
		btst.l  #ENV_SUST,d0    		; are we sustaining?
		beq     MusicTick50     		; b-no

		move.w  duration(a1),count(a0)
		move.l  delta(a1),increment(a0)
		clr.l   value(a0)
		move.b  d1,value+1(a0)  		; start at sustain last volume level
		move.w  #$8000,value+2(a0)      	; + .5
		move.b  volume(a1),d0   		; convert volume to word
		ext.w   d0
		move.w  d0,targetvol(a0)
		move.b  flags(a1),d0    		; convert flags to word
		ext.w   d0
		move.w  d0,status(a0)

		lea     SIZEOF_envseg(a1),a1    	; bump to next envseg ptr
		move.l  a1,env_data(a0)

MusicTick50
		tst.w   count(a0)       		; test time left
		bne     MusicTick60     		; if any time left do envelope

		tst.w   status(a0)      		; any status bits set?
		bne     MusicTick70     		; if yes, don't load next segment

		move.w  targetvol(a0),value(a0) 	; jump to target volume level
		move.w  #$8000,value+2(a0)      	; add .5 to avoid roundoff

		move.l  env_data(a0),a1 		; get ptr to new envelope segment

		move.l	a1,d0					; are we REALLY sure this channel on?
		beq.s	MusicTick90				;	well if not, just skip

		move.b  volume(a1),d0   		; get new target volume
		ext.w   d0
		move.w  d0,targetvol(a0)
		move.w  duration(a1),count(a0)  	; get count
		move.l  delta(a1),increment(a0) 	; get increment
		move.b  flags(a1),status+1(a0)  	; get flags

		lea     SIZEOF_envseg(a1),a1    	; move to next segment
		move.l  a1,env_data(a0)

		tst.w   count(a0)       		; see if any time
		beq     MusicTick80     		; if none go straight to target

MusicTick60
		move.l  increment(a0),d0		; get delta
		add.l   d0,value(a0)    		; add delta value

		sub.w   #1,count(a0)    		; decrement time left

MusicTick70
		move.w  value(a0),d0    		; get int part of value

MusicTick80
		and.w	#$007f,d0
		move.w	_volume_divide,d1		; get scale value
		bne.s	2$
		moveq	#0,d0					; if zero, volume = 0
		bra.s	1$

2$		cmp.w	#FULL_VOLUME,d1			; if full, no scaling needed
		beq.s	1$

		mulu.w	d1,d0					; * volume_divide
		lsr.w	#4,d0					; / 16
1$
		move.w  d2,d1   				; get channel number
		lsl.w   #4,d1   				; *16
		move.l  #$00DFF0A8,a1   		; channel 0 volume reg
		move.w  d0,0(a1,d1)     		; store in hardware volume reg
		bne.s	MusicTick90

		move.b	chan_priority(a0),d1	; get priority
		clr.b	chan_priority(a0)		; zero volume = no priority
;		move.w	#$0f00,$00dff180
		tst.b	d1
		beq.s	MusicTick90

;		move.w	#$00f0,$00dff180
		movem.l d2-d3/a0/a2-a3,-(sp)    ; save regs C trashes

		move.l  d2,-(sp)				; push channel # (Note: 32 BIT INTS!)
		jsr     _StopSample     		; stop the sample
		addq.w   #4,sp   				; cleanup stack

		movem.l (sp)+,d2-d3/a0/a2-a3    ; restore regs

MusicTick90
		lea     SIZEOF_channel(a0),a0   ; point to next channel
		add.w   #1,d2   				; inc channel number
		cmp.w   #4,d2   				; done all four?
		blo     MusicTick10     		; loop if not

MusicTickX
		moveq   #0,d0   				; Z set... continue VBlank processing

		movem.l (sp)+,mti_regs       	; restore registers
		rts

*
* find channel & play a note
*

playnote
		moveq   #0,d1   			; clear out d1
		moveq	#-4,d2				; preset d2-H to search down

* determine which Amiga channel to play note on
		move.b  data(a1),d1     	; get midi channel
		and.w   #$0003,d1       	; mod 4, d1=prefered channel
		btst	#0,d1				; if channel 0 or 2	
		beq.s	1$					;	search down
		moveq	#4,d2				;	else search up

1$		asl.w   #2,d1   			; shift for long indexing
			; use high word of d2 for direction of scan
		swap	d2
		move.w	#3,d2   			; number of channels to check - 1
		bra.s	findfree

* this table here to be close for $xx(PC,Xn) addressing

infotable1
        dc.l    _channeldata
		dc.l    _channeldata+SIZEOF_channel
		dc.l    _channeldata+(SIZEOF_channel*2)
		dc.l    _channeldata+(SIZEOF_channel*3)

findfree
		move.l  infotable1(pc,d1.w),a0   	; get channelinfo
; debug
;		bra		foundchan

		btst.b  #ENV_STOPPED,status+1(a0)	; is this channel free?
		bne     foundchan       			; branch if it is
		swap	d2							; get scan direction
		add.w	d2,d1   					; next channel
		swap	d2
		and.w   #$000c,d1       			; mod 4 (still shifted)
		dbra    d2,findfree     			; loop utill all 4 checked

		move.w   #3,d2   					; number of channel to check - 1
findrelease
		move.l  infotable1(pc,d1.w),a0   	; get channelinfo
		btst.b  #ENV_HALT,status+1(a0)  	; is channel releasing?
		bne     foundchan       			; branch if it is
		swap	d2							; get scan direction
		add.w	d2,d1   					; next channel
		swap	d2
		and.w   #$000c,d1       			; mod 4
		dbra    d2,findrelease  			; loop until all 4 checked

		moveq   #-1,d0  					; real old time of $FFFF in d0.w
		move.w	#3,d2   					; number of channel to check -1
		moveq   #0,d3   					; just in case...

findold
		move.l  infotable1(pc,d1.w),a0   	; get channelinfo
		cmp.w   chstoptime(a0),d0       	; compare current best time to channel stop time
		blo     notoldnuf       			; if current best time still more recent, then branch
		move.w  chstoptime(a0),d0       	; otherwise, remember time
		move.w  d1,d3   					; remember channel #
notoldnuf
		swap	d2							; get scan direction
		add.w	d2,d1   					; next channel
		swap	d2
		and.w   #$000c,d1       			; mod 4
		dbra    d2,findold      			; loop until all 4 checked

		move.w  d3,d1   					; put channel index back in d1
		move.l  infotable1(pc,d1.w),a0  	; a0 = ptr to winning channelinfo

foundchan
		cmp.b	chan_priority(a0),d4		; check priority
		bmi		cannotsteal

		move.w  d1,d3   					; get channel *4
		lsr.w   #2,d3   					; divide by 4
		ext.l   d3      					; d3 = audio channel number

		tst.w	stoptime(a1)				; used by sfx
		beq		nostoptime

		cmp.w   stoptime(a1),d7 			; compare clock to stop time
		bhs     nostoptime      			; if >= then no stop event needed

		move.l  command(a1),d0  			; get stop event from real event

		swap    d1
		asl.l   #2,d1   				; d1 = $xxCxxxxx (where C = audio channel 0-3)
		and.l   #$7f0fffff,d0   			; clear unwanted bits
		or.l    d1,d0   			; combine with audio channel number

		add.l   #4,_stopeventptr		; point to next stop event
		move.l  _stopeventptr,a2		; get stop event ptr
		move.l  d0,(a2) 			; place event at next spot in the queue

placestop
		cmp.w   -2(a2),d0       		; compare this events stop time with the next events
		bls     stopsorted      		; if <= then list sorted
		move.l  -4(a2),(a2)     		; otherwise swap these two stop event
		move.l  d0,-4(a2)
		sub.l   #4,a2   			; dec ptr to check next (previous) event
		bra     placestop       		; keep going till its ordered

nostoptime
		bset	#7,d4					; if pri & $80, immediate release
;		bset.b  #ENV_RELEASE,status+1(a0)
stopsorted
		movem.l a0-a1/d1,-(sp)

		move.l  _doneport,a0
		move.l  _SysBase,a6     		; get execbase
		jsr     _LVOGetMsg(a6)  		; d0 = GetMsg(doneport);

		movem.l (sp)+,a0-a1/d1

		tst.l   d0
		beq     nextevent

		move.l  d0,a2   			; a2 = ptr to playmsg

		move.b  data(a1),d0     		; get data byte
		bpl.s	1$						; if positive, normal note

		not.b	d0						; negative, actually sound effect
		moveq	#0,d1
		move.b	d0,d1					; real sample number!!
		and.w   #$000f,D0       		; extract midi channel
		lea		_amiga_samples,a3       ; a3 = ptr to array of amiga sample ptrs
		bra.s	2$
		
1$		and.w   #$000f,D0       		; extract midi channel
		move.w	d0,d1
		lea		_sample_bank,a3       	; a3 = ptr to array of amiga sample ptrs

2$		move.b  d0,pmmidi_channel(a2)  	; set midi channel in playmsg

		add.w	d1,d1		   			; *4 for long indexing
		add.w	d1,d1
		move.l  0(a3,d1.w),d0   		; get sample for midi channel
		move.l  d0,sample(a2)   		; set sample ptr in playmsg

		move.b  command(a1),d0  		; get midi pitch
		move.b  d0,pmmidi_note(a2)      	; set midi pitch in playmsg
		move.b  d3,pmaudio_channel(a2)  	; set audio channel in playmsg
		move.b	d4,pm_priority(a2)			; set priority

		move.w  stoptime(a1),chstoptime(a0)     ; put stop time in channeldata

		clr.w   status(a0)

		movem.l a0-a1,-(sp)

		move.l  _playport,a0    		; PutMsg(playport, playmsg)
		move.l  a2,a1
		move.l  _SysBase,a6     		; get execbase
		jsr     _LVOPutMsg(a6)

		movem.l (sp)+,a0-a1

cannotsteal
		rts


*
* _HandleNoteInt - start a note playing
*
* scratch registers: d0-d1/a0-a1/a5-a6
*
_HandleNoteInt
		movem.l d2-d3/a2-a4/a6,-(sp)       	; save regs for system

		jsr		_geta4

		jsr     _HandleNote     		; call C function

		movem.l (sp)+,d2-d3/a2-a4/a6       	; restore regs
		rts

*****************************************************
*													*
* SetTempo() - Calculate the clockinc based on		*
*				the supplied tempo value			*
* Entry: tempo (quarter notes per minute)			*
* Exit : None										*
*													*
*****************************************************

		xdef	_SetTempo
		xref	_frameval
_SetTempo
		move.l	4(sp),d0
SetTempo				;	clockinc = (((tempo * 24) << 12) / frameval) << 4;
		mulu.w	#24,d0
		swap	d0
		lsr.l	#4,d0
		move.l	_frameval,d1
		divu.w	d1,d0
		swap	d0
		clr.w	d0
		swap	d0
		lsl.l	#4,d0
		move.l	d0,_clockinc
		rts

* EOF *
