;****** thx-play.m/--overview-- ******************************************
;
;   PURPOSE
;       To provide an interface to the THX2 player.
;
;   OVERVIEW
;       THX2  is  a  'chip' music tracker by Martin Wodok (Dexter/Abyss). It
;       comes with a rather cumbersome binary replayer, so you may play THX2
;       songs  in  your  own  programs.  This  module  provides  an easy and
;       powerful  interface  to  the  THX2 player, providing a wide range of
;       functions.
;
;        - Basic controls:     Init, Free, Play, Stop, Pause, Wind
;        - Volume controls:    GetVolume, SetVolume
;        - MultiSong controls: GetNumSongs, SetSong
;        - Sound FX controls:  PlayNote, StopNote, NoteFX
;        - Misc controls:      SignalEnd, SongEnded, SyncByte
;
;
;       You  need  to read a THX song in from disk or INCBIN it, this is not
;       one  of  the functions in thx-play.m. You should load it into PUBLIC
;       memory,  it does not have to be CHIP memory. The module 'tools/file'
;       is handy for loading files from disk.
;
;       The  music  play  is,  as  you  would  expect, interrupt-driven, and
;       asynchronous. This interface automatically provides fallback support
;       for a VSYNC interrupt driven replayer if it cannot grab a CIA timer.
;
;       The  interface  is  68000  compatible,  an optimised version for the
;       68020 or better is also included as thx-play_020.m
;
;   EXAMPLE
;       More thorough examples are included with the distribution.
;
;       MODULE 'tools/thx-play','tools/file'
;       PROC main()
;         DEF mod
;         IF mod:=loadfile(arg, 0, MEMF_PUBLIC)
;           IF thxInit(mod)=0
;             thxPlay()
;             REPEAT; WaitTOF(); UNTIL CtrlC() OR thxSongEnded()
;             thxStop()
;
;             thxFree()
;           ENDIF
;           freefile(mod)
;         ENDIF
;       ENDPROC
;
;
;****************************************************************************
;
;
	incdir	include:
	include	hardware/intbits.i
	include	exec/nodes.i
	include	lvo/exec_lib.i

	incdir	bin
	include	thx-offsets.i


*--------------------------------------------------------------------------------------------------------*
* misc routines

;****** thx-play.m/thxSignalEnd ******************************************
;
;   NAME
;       thxSignalEnd -- Signal() when song ends.
;
;   SYNOPSIS
;       void thxSignalEnd(signals)
;
;   FUNCTION
;       Asks  THX  to send the signals you specify back to you when the song
;       ends.  If songend occurs and the signal is sent, it will not be sent
;       again  unless  you  call thxSignalEnd() again to reload the trigger.
;       The signal will also be cancelled if you call thxStop() directly, or
;       indirectly  through  thxSetSong()  or  thxFree().  You may call this
;       function  at  any  time, even when the player is not initialised, or
;       while  a  song  is  playing. The task you call it from is stored, so
;       don't  call  it  from asynchronous threads that may disappear before
;       songend is reached.
;
;   NOTE
;       The detection of songend is crap (sorry Dexter :^)
;
;   INPUTS
;       signals - a 32bit set of signals, to be sent back to calling task
;                 when songend occurs.
;
;   EXAMPLE
;       thxSignalEnd(SIGBREAKF_CTRL_C)  will send you a CTRL-C when the song
;       ends.
;
;   SEE ALSO
;       thxSongEnded(), exec.library/Signal()
;
;****************************************************************************
;
;
	xdef	thxSignalEnd_i
thxSignalEnd_i
	move.b	mod_OK(pc),d0
	beq.s	.nosong
	move.l	4.w,a6
	suba.l	a1,a1
	jsr	-294(a6)	; _LVOFindTask(a6)
	lea	task(pc),a0
	move.l	d0,(a0)+
	move.l	4(sp),(a0)+
	st.w	(a0)		; set 'signal enable' _last_
.nosong	rts


;****** thx-play.m/thxSongEnded ******************************************
;
;   NAME
;       thxSongEnded -- detect if song has ended.
;
;   SYNOPSIS
;       ended = thxSongEnded()
;
;   FUNCTION
;       Returns  nonzero  value if the player has detected the end of a song
;       and is now looping.
;
;   NOTE
;       The detection of songend is crap (sorry Dexter :^)
;
;   RESULT
;       value - nonzero if song is now looping, zero otherwise.
;
;   SEE ALSO
;       thxSignalEnd()
;
;****************************************************************************
;
;
	xdef	thxSongEnded
thxSongEnded
	move.l	THX+thxBSS_P(pc),d0
	beq.s	.exit
	move.l	d0,a0
	moveq	#0,d0
	move.b	thx_pSongEnd(a0),d0
	beq.s	.exit
	moveq	#-1,d0
.exit	rts


;****** thx-play.m/thxGetSyncByte ******************************************
;
;   NAME
;       thxGetSyncByte -- get sync byte value.
;
;   SYNOPSIS
;       value = thxGetSyncByte()
;
;   FUNCTION
;       Gets the current setting of the 'external timing' byte, which can be
;       set  to any byte value at any moment in time during play of the song
;       BY  the  song  itself,  using  the  8  command  in the tracker. This
;       function  is  here to allow you to mark specific events in the music
;       with   the   8   command  and  a  value,  then  wait  until  calling
;       thxSyncByte()  returns that value. The returned value doesn't change
;       until another 8 command in the song changes it.
;
;   NOTE
;       Be  very  careful  not  to  busy-wait on a new value if there is the
;       possibility the song is paused or not playing.
;
;   RESULT
;       value - current value of the sync byte.
;
;****************************************************************************
;
;
	xdef	thxGetSyncByte
thxGetSyncByte
	move.l	THX+thxBSS_P(pc),d0
	beq.s	.exit
	move.l	d0,a0
	moveq	#0,d0
	move.b	(a0),d0	; thx_pExternalTiming(a0),d0
.exit	rts


*--------------------------------------------------------------------------------------------------------*
* note routines

;****** thx-play.m/thxPlayNote ******************************************
;
;   NAME
;       thxPlayNote -- start playing a user-specified note.
;
;   SYNOPSIS
;       void thxPlayNote(instrument, note, channel)
;
;   FUNCTION
;       Plays  one of the instruments in the THX module at a particular note
;       on  a particular channel. It is up to you to ensure that the channel
;       you  play  the  note  on is empty and so will not interfere with the
;       note  being  played.  This function is to allow you to play your own
;       notes  during  THX  play,  for  example  as  part of a game as sound
;       effects.  The  note  played is subject to the same conditions as the
;       song itself, such as the global volume control. In addition, you can
;       apply  'FX'  commands to the note. In effect, what is happening when
;       you  call  thxPlayNote()  is  that  the  'track data' for the chosen
;       channel being played is overwritten (not the module itself, just the
;       data  output). It is overwritten on the first line by your specified
;       instrument  with  note  and  FX,  then  on  consecutive lines by the
;       'blank'  note and instrument. This 'overwriting' stops only when you
;       call thxStopNote(), or stop the module naturaly.
;
;   INPUTS
;       instrument - an instrument from the song, from 1-63. You should know
;                    which instrument you want to play!
;       note       - The halfnote (pitch) at which the instrument is to be
;                    played, from 1 (C-1) to 60 (B-5).
;       channel    - The channel on which the note is played, from 0 to 3.
;
;   EXAMPLE
;       thxPlayNote(12, 8, 2) is equivalent to this in THX Sound System's
;       tracker view:
;
;       ---00000 | ---00000 | G-112000 | ---00000
;       ---00000 | ---00000 | ---00000 | ---00000
;       ---00000 | ---00000 | ---00000 | ---00000
;       [...]
;
;   SEE ALSO
;       thxStopNote(), thxNoteFX()
;
;****************************************************************************
;
;
	xdef	thxPlayNote_iii
thxPlayNote_iii
	move.l	12(sp),d0
	lea	ins(pc),a0
	move.b	d0,(a0)

	move.l	8(sp),d0
	lea	note(pc),a0
	move.b	d0,(a0)

	move.l	4(sp),d0
	lea	chan(pc),a0
	move.b	d0,(a0)

	lea	donote(pc),a0
	st.b	(a0)
	rts

;****** thx-play.m/thxStopNote ******************************************
;
;   NAME
;       thxStopNote -- stop playing user-specified note.
;
;   SYNOPSIS
;       void thxStopNote()
;
;   FUNCTION
;       Stops  anything you started with thxPlayNote(). Please be aware that
;       notes  which  don't  fade  away  on  their own will first need to be
;       silenced with thxNoteFX($C, $00), or such
;
;   SEE ALSO
;       thxPlayNote()
;
;****************************************************************************
;
;
	xdef	thxStopNote
thxStopNote
	lea	donote(pc),a0
	clr.b	(a0)
	rts

;****** thx-play.m/thxNoteFX ******************************************
;
;   NAME
;       thxNoteFX -- perform FX command on user-specified note.
;
;   SYNOPSIS
;       void thxNoteFX(command, parameter)
;
;   FUNCTION
;       Performs  an effect command on the user-specified note. You can call
;       this  at  any  time,  even before you play the note, if you want the
;       note  to  start  off  with an initial effect. See THX Sound System's
;       documentation for the full list of commands and their parameters.
;
;   INPUTS
;       command   - the effect command, eg $C is the Set Volume command.
;       parameter - the parameter to the command, eg $40 is full volume.
;
;   NOTE
;       No validation of the command or its parameter is done. Beware feeding wrong
;       or  out of range values. Range for command is $0 to $F, parameter is $00 to
;       $FF.
;
;   SEE ALSO
;       thxPlayNote()
;
;****************************************************************************
;
;
	xdef	thxNoteFX_ii
thxNoteFX_ii
	move.l	8(sp),d0
	lea	fx(pc),a0
	move.b	d0,(a0)
	move.l	4(sp),d0
	lea	fxval(pc),a0
	move.b	d0,(a0)
	rts


*--------------------------------------------------------------------------------------------------------*
* multisong routines

;****** thx-play.m/thxGetNumSongs ******************************************
;
;   NAME
;       thxGetNumSongs -- get number of subsongs.
;
;   SYNOPSIS
;       songs = thxGetNumSongs()
;
;   FUNCTION
;       Returns  the  number  of subsongs in the module, if any. You can use
;       the  thxSetSong()  function  to  play one of the subsongs, if that's
;       possible.
;
;   RESULT
;       songs - 0 if there are no subsongs (only the main song), otherwise
;               returns the number of subsongs.
;
;   SEE ALSO
;       thxSetSong()
;
;****************************************************************************
;
;
	xdef	thxGetNumSongs
thxGetNumSongs
	move.l	THX+thxBSS_P(pc),d0
	beq.s	.exit
	move.l	d0,a0
	moveq	#0,d0
	move.b	thx_pSubsongs(a0),d0
.exit	rts


;****** thx-play.m/thxSetSong ******************************************
;
;   NAME
;       thxSetSong -- set song to be played.
;
;   SYNOPSIS
;       thxSetSong(song)
;
;   FUNCTION
;       Sets  which  song  to play, if a module contains more than one song.
;       Most  modules  only  contain  one  song,  but  some  modules contain
;       sub-songs  as  well  as  the  main one. You can use this function to
;       specify  which  one  should be played. If you call this function and
;       there is already a song playing, it will be stopped first.
;
;   INPUTS
;       song - 0 to set the main song to be played, any other number will
;              change to that subsong, if it exists. Otherwise, no change
;              will be made (other than the stoppage).
;
;   NOTE
;       It is up to you to start playing the module again.
;
;   SEE ALSO
;       thxGetNumSongs()
;
;****************************************************************************
;
;
	xdef	thxSetSong_i
thxSetSong_i
	bsr.s	thxStop
	bsr.s	thxGetNumSongs
	move.l	4(sp),d1	; get arg from E
	cmp.l	d0,d1
	bhi.s	.exit
	move.l	d1,d0
	move.b	mod_OK(pc),d1
	beq.s	.exit
	lea	song(pc),a0
	move.w	d0,(a0)
	movem.l	d3-d7/a2-a6,-(sp)
	moveq	#1,d1
	bsr	THX+thxInitSubSong
	movem.l	(sp)+,d3-d7/a2-a6
.exit	rts


*--------------------------------------------------------------------------------------------------------*
* volume routines

;****** thx-play.m/thxGetVolume ******************************************
;
;   NAME
;       thxGetVolume -- get master volume.
;
;   SYNOPSIS
;       volume = thxGetVolume()
;
;   FUNCTION
;       Returns the current master volume value. Does not stop play.
;
;   RESULT
;       volume - current volume setting from 0 (silent) to 64 (loudest)
;
;   SEE ALSO
;       thxSetVolume()
;
;****************************************************************************
;
;
	xdef	thxGetVolume
thxGetVolume
	move.l	THX+thxBSS_P(pc),d0
	beq.s	.exit
	move.l	d0,a0
	moveq	#0,d0
	move.b	thx_pMainVolume(a0),d0
.exit	rts


;****** thx-play.m/thxSetVolume ******************************************
;
;   NAME
;       thxSetVolume -- set master volume.
;
;   SYNOPSIS
;       void thxSetVolume(volume)
;
;   FUNCTION
;       Sets the master volume. Does not stop play.
;
;   INPUTS
;       volume - from 0 (silent) to 64 (loudest)
;
;   NOTE
;       This  function  can take up to two frames to take an audible effect.
;       If the song is paused, will not take effect until unpaused.
;
;   SEE ALSO
;       thxGetVolume()
;
;****************************************************************************
;
;
	xdef	thxSetVolume_i
thxSetVolume_i
	move.l	4(sp),d0
	cmp.l	#64,d0
	bhi.s	.exit
	move.l	THX+thxBSS_P(pc),d1
	beq.s	.exit
	move.l	d1,a0
	move.b	d0,thx_pMainVolume(a0)
.exit	rts


*--------------------------------------------------------------------------------------------------------*
* basic routines

;****** thx-play.m/thxPlay ******************************************
;
;   NAME
;       thxPlay -- start playing the song.
;
;   SYNOPSIS
;       void thxPlay()
;
;   FUNCTION
;       Starts  playing  the module. If the module has just been initialised
;       or  stopped,  or  the  subsong has just been changed, then play will
;       start  at  the  beginning  of  the  song/subsong. Otherwise, it will
;       continue from where it was paused.
;
;   SEE ALSO
;       thxStop(), thxPause()
;
;****************************************************************************
;
;
	xdef	thxPlay
thxPlay
	move.l	THX+thxBSS_P(pc),d0
	beq.s	.exit
	move.l	d0,a0
	st.b	thx_pPlaying(a0)
.exit	rts


;****** thx-play.m/thxStop ******************************************
;
;   NAME
;       thxStop -- stop playing a song/module.
;
;   SYNOPSIS
;       void thxStop()
;
;   FUNCTION
;       Stops  the  module.  Can  be restarted from the beginning again with
;       thxPlay().  If  you  call this, you can free the memory used by your
;       thx module without calling thxFree() which has the unpleasant effect
;       of  requiring  to  recalculate all the filters if you call thxInit()
;       again.
;
;   SEE ALSO
;       thxPlay(), thxFree()
;
;****************************************************************************
;
;
	xdef	thxStop
thxStop
	movem.l	d3-d7/a2-a6,-(sp)
	move.b	mod_OK(pc),d0
	beq.s	.nosong
	lea	dosig(pc),a0
	clr.w	(a0)
	bsr	THX+thxStopSong
	bsr.s	thxPause
	move.l	mod(pc),d0
	beq.s	.nosong
	move.l	d0,a0
	bsr	THX+thxInitModule
	moveq	#0,d0
	lea	song(pc),a0
	move.w	(a0),d0
	moveq	#1,d1		; don't play immediately
	bsr	THX+thxInitSubSong
.nosong	movem.l	(sp)+,d3-d7/a2-a6
	rts


;****** thx-play.m/thxPause ******************************************
;
;   NAME
;       thxPause -- pause play of a song.
;
;   SYNOPSIS
;       void thxPause()
;
;   FUNCTION
;       Pauses the playing module. Call thxPlay() to continue play again.
;
;   SEE ALSO
;       thxPlay()
;
;****************************************************************************
;
;
	xdef	thxPause
thxPause
	move.l	THX+thxBSS_P(pc),d0
	beq.s	.exit
	move.l	d0,a0
	clr.b	thx_pPlaying(a0)
.exit	rts


;****** thx-play.m/thxWind ******************************************
;
;   NAME
;       thxWind -- wind the song forward or back.
;
;   SYNOPSIS
;       void thxWind(direction)
;
;   FUNCTION
;       Advances forward or backwards through the song by a specified number
;       of  positions. Please use the value 1 to skip forward and -1 to skip
;       back, for future compatibility.
;
;   INPUTS
;       direction - if positive, winds on to the next position.
;                   if negative, winds back to the previous position,
;                   if 0, ignored.
;
;   NOTE
;       Be  wary  of  stepping  beyond  the  end  of  a song. Also note this
;       function only takes effect only once a frame.
;
;****************************************************************************
;
;
	xdef	thxWind_i
thxWind_i
	move.l	4(sp),d0
	movem.l	d3-d7/a2-a6,-(sp)
	move.b	mod_OK(pc),d1
	beq.s	.exit
	move.l	d0,d7
	beq.s	.exit
	bmi.s	.neg
	bsr	THX+thxNextPattern
	bra.s	.exit
.neg	bsr	THX+thxPrevPattern
.exit	movem.l	(sp)+,d3-d7/a2-a6
	rts


;****** thx-play.m/thxInit ******************************************
;
;   NAME
;       thxInit -- initialise player and module.
;
;   SYNOPSIS
;       error = thxInit(moduleptr)
;
;   FUNCTION
;       Initialises  the  player (if needed) and initializes the module. You
;       may  also  call  thxInit(NIL)  to  initialise the player but not the
;       module.  Does not start to play the module until you call thxPlay().
;       You  must  call  this each time you want to play a different module.
;       The allocations made for the player are made only the first time you
;       call  thxInit(), no matter how many modules you want. If allocations
;       fail, they will be automatically freed.
;
;   INPUTS
;       module - pointer to a THX module or NIL
;
;   RESULT
;       error - 0 means all went OK, any other value means something FAILED.
;
;   SEE ALSO
;       thxFree(), thxPlay()
;
;****************************************************************************
;
;
	xdef	thxInit_i
thxInit_i
	lea	mod(pc),a0
	move.l	4(sp),(a0)
	lea	mod_OK(pc),a0
	clr.b	(a0)			; mod is NOT ok until we say so
	lea	song(pc),a0
	clr.w	(a0)
	movem.l	d3-d7/a2-a6,-(sp)	; 40 bytes onto stack
	move.b	init_OK(pc),d0
	bne.s	.init_d
	moveq	#0,d0
	moveq	#0,d1
	sub.l	a0,a0	;auto-allocate public (fast)
	sub.l	a1,a1	;auto-allocate chip
	bsr	THX+thxInitPlayer
	tst.l	d0
	bne	.fail
	lea	init_OK(pc),a0
	st.b	(a0)

.init_d	move.l	mod(pc),d0
	beq.s	.nomod
	move.l	d0,a0
	bsr	THX+thxInitModule
	moveq	#0,d0
	moveq	#1,d1		; don't play immediately
	bsr	THX+thxInitSubSong


.nomod	move.b	cia_OK(pc),d0
	bne.s	.inimod
	lea	vbcode(pc),a0
	moveq	#0,d0
	bsr	THX+thxInitCIA
	tst.l	d0
	bne.s	.nocia
	lea	cia_OK(pc),a0
	st.b	(a0)
	bra.s	.inimod

.nocia	move.b	vb_OK(pc),d0
	bne.s	.inimod
	lea	int(pc),a0
	clr.l	(a0)+
	clr.l	(a0)+
	lea	intcode(pc),a0
	lea	vbcode(pc),a1
	move.l	a1,(a0)		; int.code:=vbcode
	lea	intname(pc),a0
	lea	vbname(pc),a1
	move.l	a1,(a0)		; int.ln.name:=vbname
	moveq	#INTB_VERTB,d0
	lea	int(pc),a1
	move.l	4.w,a6
	jsr	_LVOAddIntServer(a6)
	lea	vb_OK(pc),a0
	st.b	(a0)

.inimod	move.l	mod(pc),d0
	beq.s	.done
	move.l	d0,a0
	bsr	THX+thxInitModule
	tst.l	d0
	bne.s	.fail

	moveq	#0,d0
	moveq	#1,d1
	bsr	THX+thxInitSubSong

	lea	mod_OK(pc),a0
	st.b	(a0)

.done	moveq	#0,d0
	bra.s	.exit

.fail	bsr.s	thxFree
	moveq	#-1,d0
.exit	movem.l	(sp)+,d3-d7/a2-a6
	rts

;****** thx-play.m/thxFree ******************************************
;
;   NAME
;       thxFree -- free resources held by player.
;
;   SYNOPSIS
;       void thxFree()
;
;   FUNCTION
;       Stops any THX module playing and frees resources used by the player.
;       You can call this whether thxInit() suceeded or not.
;
;   SEE ALSO
;       thxInit()
;
;****************************************************************************
;
;
	xdef	thxFree
thxFree	movem.l	d3-d7/a2-a6,-(sp)	; 40 bytes onto stack
	bsr	thxStop

	move.b	cia_OK(pc),d0
	beq.s	.nocia
	bsr	THX+thxKillCIA
	
.nocia	move.b	vb_OK(pc),d0
	beq.s	.novb
	moveq	#INTB_VERTB,d0
	lea	int(pc),a1
	move.l	4.w,a6
	jsr	_LVORemIntServer(a6)

.novb	move.b	init_OK(pc),d0
	beq.s	.noinit
	bsr	THX+thxKillPlayer

.noinit	lea	cia_OK(pc),a2
	clr.l	(a2)		; clear cia_OK, vb_OK, init_OK and mod_OK
	movem.l	(sp)+,d3-d7/a2-a6
	rts

*--------------------------------------------------------------------------------------------------------*

vbcode	movem.l	d0-d7/a0-a6,-(sp)
	move.l	THX+thxBSS_P(pc),a0
	tst.b	thx_pPlaying(a0)
	beq.s	.exit

	tst.b	thx_pSongEnd(a0)
	beq.s	.play		; don't signal if not songend
	move.w	dosig(pc),d0
	beq.s	.play		; don't signal if dosig=0
	move.l	4.w,a6
	move.l	task(pc),a1
	move.l	signal(pc),d0
	jsr	_LVOSignal(a6)
	lea	dosig(pc),a0
	clr.w	(a0)

.play	bsr.s	THX+thxInterrupt

	lea	donote(pc),a0
	move.b	(a0)+,d0	; test donote
	beq.s	.exit

	moveq	#0,d3
	move.b	(a0),d3		; d3=ins
	clr.b	(a0)+

	moveq	#0,d1
	move.b	(a0),d1		; d1=note
	clr.b	(a0)+

	moveq	#0,d4
	move.b	(a0)+,d4	; d4=chan

	moveq	#0,d2
	move.b	(a0)+,d2	; d2=fx

	moveq	#0,d0
	move.b	(a0)+,d0	; d0=fxval

	lea	THX(pc),a1
	bsr	thxBang

.exit	movem.l	(sp)+,d0-d7/a0-a6
	ori.w	#%100,ccr	; set Z flag
	rts

	ifd	_USE020_
THX	incbin	thx-replayer020.bin
thxBang	incbin	thx-bang020.bin
	else
THX	incbin	thx-replayer000.bin
thxBang	incbin	thx-bang000.bin
	endc
	cnop	0,4

; Init/Free vars
cia_OK	dc.b	0	; BOOL int routine is CIA, and is init'd and running
vb_OK	dc.b	0	; BOOL int routine is VBI, and is init'd and running
init_OK	dc.b	0	; BOOL initialisation (memory, etc) is done
mod_OK	dc.b	0	; BOOL module is initialised, play can commence
mod	dc.l	0	; APTR the current module

; multisong vars
song	dc.w	0	; UWORD subsong currently playing

; signalend() vars
dosig	dc.w	0	; BOOL we have yet to signal
task	dc.l	0	; APTR the task to signal
signal	dc.l	0	; ULONG signalset to send

; notebang vars
donote	dc.b	0	; BOOL bang in effect
ins	dc.b	0	; UBYTE (1-63) instrument to bang
note	dc.b	0	; UBYTE (0-60) halftone to bang at
chan	dc.b	0	; UBYTE (0-3) channel to bang on
fx	dc.b	0	; UBYTE ($0-$F) fx command for bang
fxval	dc.b	0	; UBYTE ($00-$FF) fx parameter


; interruptserver structure
int	dc.l	0,0		; succ,pred
	dc.b	NT_INTERRUPT,-1	; nodetype, priority
intname	dc.l	0,0		; name, iv_data
intcode	dc.l	0		; iv_code

vbname	dc.b	"Nora Batty's original THX2 player (tm)",0
	cnop	0,4
