
	incdir	Include:
	include	misc/AYPlayer.i
	include	misc/mine.i
	incdir	''

*** Amadeus AY Player for my DT2 Play AY Emulator Interface, (C) 1994 Raxoft
*** This is an example how your own AY Player could look like...

* 1 Initial version
* 2 Added pushtranspose (143) a poptranspose (144) commands
* 3 Protected volume envelope streams from causing enforcer hits

;This is how single Amadeus song looks like.
;Note that each module can contain several songs like this one...
;See aym_ structure for more info

	STRUCTURE	AMAD_Song,0
	UWORD	ams_getabs	;where the original channelA located in ZX ram
	UBYTE	ams_andsix	;strange F.F.s thing. Should be 31 normally.
	UBYTE	ams_loops	;how many loops before songend
	UWORD	ams_looplen	;len of one song loop in VBIs
	WORD	ams_fadeoffset	;precise fade specification
	UWORD	ams_fadelen	;how long to fade (not supported by DT so far)
				;set to zero for neverending song
	UBYTE	ams_assignA	;0-3 specifies, what amiga channel will be
	UBYTE	ams_assignB	;assigned to each ay channel (A-C, noise)
	UBYTE	ams_assignC
	UBYTE	ams_assignN
	;original ZX data follow. First three ptrs to init channels
	UWORD	ams_channelA	;these are the original ZX data.
	UWORD	ams_channelB	;ayp_getabs says, where the ams_channelA was
	UWORD	ams_channelC	;located when it was in ZX RAM.
	;and so on...

; The Player itself. See AYPlayer.i for more info about players in general.

dd	;Section	AmadeusCode,code	commented because of QUICK_HACK

	AYPLAYERHEADER	AMAD
	dc.b	12-1		;custom ayfreq transpose: up octave - 1 tone
AYbase	ds.l	1		;where ay registers should be "outed"
AYass	ds.l	1		;where ay channel assignment should be copied
AYfreq	ds.l	1		;from where we can take AY frequencies
	dc.w	0		;initplayer
	dc.w	0		;endplayer
	dc.w	initsound-*
	dc.w	0		;endsound
	dc.w	interrupt-*
	dc.w	0		;No pattern support (patterns are pretty
	dc.w	0		;limitating and bad idea IMHO)
	dc.b	'Amadeus 1.1',0
	dc.b	'(C) 1987 Frantisek Fuka - Fuxoft',0
	dc.b	'(C) 1992-1997 Patrik Rak - Raxoft',0
	even

;Here is the song preparation entry... PlayAY passes us ptr to our custom
;AMAD_SongStructure in a0, so it is our job to initialize our player pointers
;Also we calculate the songlen we will use later.
;Also, this is where we CAN assign AY channels to amiga channels
;(otherwise standard 0,1,2,3 assignment is made.)

initsound
	lea	ams_channelA(a0),a1
	moveq	#0,d0
	move.w	(a0)+,d0	;careful with the words >32767 :-)
	sub.l	d0,a1
	move.l	a1,getabsset	;for ZX -> Amiga RAM relocation
	move.b	(a0)+,andsix	;FFs internal
	moveq	#0,d0
	move.b	(a0)+,d0	;loops
	mulu	(a0)+,d0	;looplen
	add.w	(a0)+,d0	;add/sub offset
	lea	songlen,a1
	move.w	d0,(a1)+	;store songlen
	move.w	(a0)+,(a1)	;store fadelen
	move.l	AYass(pc),a1
	move.l	(a0)+,(a1)	;copy channel assignment
	move.l	a0,a1		;point to channelA
	bra.b	cold		;skip to orig. amad init routine

;This routine is called every 1/50th of second (usually)
;Here you should process your music data and load the AY regs
;also, you can do the song len counting here - return null if song should
;continue. To signal end, return non zero value telling how many VBIs should
;DT fade.

interrupt

	bsr.w	warm	;call orig. amadeus int. routine

	;This is how signal songend routine should ALWAYS look like
	;Note that you MAY NOT return nonzero value more then once per song!!!
	
	moveq	#0,d0	;signal continue
	lea	songlen,a0
	tst.w	(a0)
	beq.b	.exit	;already signalled
	subq.w	#1,(a0)+	;decrease songlen and point to fadelen
	bne.b	.exit	;not end yet
	move.w	(a0),d0	;if zero, then will never fade. Otherwise fade for x VBIs
.exit	rts


***************************************************************************

;Amadeus (Amiga Version for AY-3-8912 Emulator) 1.00 by Raxoft 1992
;Based on Original Amadeus V8 (C) Fuxoft 1987

;I am not going to comment this source too detailed, because no one is going
;to read it and understand Amadeus DATA format anyway... So only roughly...

;This code is rewrite from Z80 code, as you can see from the code design.
;I did only few optimalizations, because I didn't want to create some
;incompatibility by my mistake... So have it in mind and don't blame me...

;Code has been sligthly rearanged by me and Delirium in 1994.

;===========================================================================

;Support routines for easier Z-80 emulation in 68000 code

;GetAbs replaces this code, which is used very often in amadeus (and other
;routines as well):
;	LD	(HL),E		;imagine that HL is a1 and DE is D7
;	INC	HL
;	LD	(HL),D
;	INC	HL
;and DE is not just some value, it is another Address Pointer to ZX data...
;This allows that there is no need to change the original data at all...

getabs	moveq	#0,d7		;Simulates reading word address from ZX
	move.b	(a1)+,d6	;memory and recalculates it to the
	move.b	(a1)+,d7	;amiga memory. Note that ZX has LSB
	lsl.w	#8,d7		;followed MSB. And word can be on
	move.b	d6,d7		;odd address as well.
	add.l	getabsset,d7	;Returns Address in d7, also increases
	rts			;that pointer...

;Because data in ZX memory often use direct addressing, it is necessary to
;recalculate it to your Amiga memory. Let's say you have ZX data, which were
;located on 53000, and you have them on $534a3 in your amiga memory. So you
;have to set this to $534a3-53000, otherwise the getabs won't work properly...

getabsset	dc.l	0		;Base for your ZX memory

;===========================================================================


;This code initiates all important values needed later.
;On ZX it also installed IM2 routine.

;a1 contains pointer to first data stream

cold	lea	worksp,a6	;Workspace

	bsr.b	getabs		;Read three channel pointers
	move.l	d7,(a6)		;and install them to workspace
	bsr.b	getabs	
	move.l	d7,wrk2(a6)
	bsr.b	getabs	
	move.l	d7,wrk3(a6)

	move.w	#$0108,d0	;Set note len to 1 and strobe to 8
	move.w	d0,wrk1+len(a6)
	move.w	d0,wrk2+len(a6)
	move.w	d0,wrk3+len(a6)

	lea	stacks+stacksize-worksp(a6),a1	;Init Channel stacks
	move.l	a1,stk1-worksp(a6)
	lea	stacksize(a1),a1
	move.l	a1,stk2-worksp(a6)
	lea	stacksize(a1),a1
	move.l	a1,stk3-worksp(a6)

	clr.w	wrk1+flags(a6)	;reset flags and transpositions
	clr.w	wrk2+flags(a6)
	clr.w	wrk3+flags(a6)

	clr.l	wrk1+freqbeg(a6);reset frequency envelopes starts
	clr.l	wrk2+freqbeg(a6);don't think it is necessary
	clr.l	wrk3+freqbeg(a6);but recoded it as well :-)

	clr.l	wrk1+envbeg(a6);reset volume envelopes starts
	clr.l	wrk2+envbeg(a6);this is checked against zero to prevent
	clr.l	wrk3+envbeg(a6);enforcer hit in some amadeus tunes

	sf	barva-worksp(a6);Clear noise color/frequency
	rts

;===========================================================================

;This was the original routine which was called by Z80 every interrupt, pushed
;registers, called warm, poped registers, EI, RET. Now, it is the same as WARM
;indeed
	
;inter	equ	warm

;===========================================================================

;This routine originally disabled IM2, turned on IM1 and stopped AY sound.
;Now it does only the latter, although you can also call AYoff and it will do
;the same as calling StopAMAD & AYemulate. It just puts 255 to the strobe
;register

;No need for this routine, since the playAY will turn of the AY anyway
;(something not possible on ZX :-)

;stopamad	lea	worksp(pc),a6
;	moveq	#-1,d0
;	bra.b	stopout

;===========================================================================

;Main routine, should be called every 50th of second, followed by the call
;to AYemulate.

;Of course that you first have to install Data for amadeus, THEN call Cold, 
;and THEN you can call this.

;It does everything: advances in all channels, generates new notes, new
;envelopes, all effects, everything...

warm	lea	worksp,a6	;Workspace pointer to a6 as base

	lea	(a6),a5			;Proccess Channel 1
	move.l	stk1-worksp(a6),a4	;get stack pointer
	moveq	#1,d0			;channel number
	bsr.b	main			;Oh man, DO IT !!!
	move.l	a4,stk1-worksp(a6)	;store stack pointer
	
	lea	wrk2(a6),a5		;Channel 2
	move.l	stk2-worksp(a6),a4
	moveq	#2,d0
	bsr.b	main
	move.l	a4,stk2-worksp(a6)
	
	lea	wrk3(a6),a5		;Channel 3
	move.l	stk3-worksp(a6),a4
	moveq	#3,d0
	bsr.b	main
	move.l	a4,stk3-worksp(a6)
	
	move.b	wrk3+master(a6),d0	;Get enable bits of all
	add.b	d0,d0			;three channels and generate
	or.b	wrk2+master(a6),d0	;strobe
	add.b	d0,d0
	or.b	wrk1+master(a6),d0
	
stopout	moveq	#7,d5			;"Out" it to the AY backup reg.
	bsr.b	out			;and fall down to outy

;===========================================================================

;routine which originally OUTed all register stored in memory to the AY
;at once. Now, it just copies them from AY Backup registers (note that
;everything was prepared in memory at first) to the "AY register" (in our
;case, just another part of memory:-)

;Originally it was send there from reg 13 to reg 0, but here is no reason
;to do that so... Although in Z80 it was better solution :-)

outy	moveq	#14/2-1,d5		;14 register to copy
	lea	output-worksp(a6),a1 	;pointer to my ay output buffer
	move.l	AYbase,a3		;pointer to AY registers
	
outy1	move.w	(a1)+,(a3)+		;And feed them all :-)
	dbra	d5,outy1
	rts

;===========================================================================

;Originally it was a little longer routine, which stored one byte to the
;output table. Motorola makes it in one instruction :-)

out	move.b	d0,output-worksp(a6,d5.w)
	rts

;===========================================================================

;Main processing routine for single channel

;a4 stack pointer for this buffer

main	move.b	d0,hlas-worksp(a6)	;store channel number
	
	subq.b	#1,len(a5)	;decrease note length, skip
	beq.w	nova		;if we need to read new one.
	
hl11	subq.b	#1,envper(a5)	;dec volume envelope delay counter
	bne.B	hlst		;skip if still same volume
	
	move.l	envpos(a5),d0	;Pointer to Vol Envelope stream
	beq.b	nohit1		;!!!Just to be sure, FF is sclerotic !!!
	move.l	d0,a1		;On ZX it loads 243 from 0, but here... ?

hl1	move.b	(a1)+,d0	;Read next volume/command
	cmp.b	#128,d0		;is it Jump?
	bne.b	neskhl		;Skip if not.
	
	bsr.w	getabs		;Get the Jump destination
	move.l	d7,a1		;
	bra.b	hl1		;And continue there...
	
neskhl	cmp.b	#30,d0		;0-30 used for volume 0-15
	bcs.b	neskh0		;followed by volume delay
	
	sub.b	#50,d0		;values above 50 used for
nohit1	move.b	d0,vol(a5)	;volume 0-15, which has automatically
	move.b	#1,envper(a5)	;env volume delay = 1 (often used ->
	bra.b	contx1		;safes data)
	
neskh0	move.b	d0,vol(a5)	;Ranges 0-30 and 50-255 are strange,
	move.b	(a1)+,envper(a5);but better than try to always add
					;128 or like by yourself... And FF
					;wrote his musics in asm, of course...
contx1	move.l	a1,envpos(a5)	;Store the volume envelope position
	
hlst	move.w	freq(a5),d0	;Skip if freq is 0 -> Generates
	beq.b	playit		;no tone
	
	btst	#2,flags(a5)	;Is it first Edge? If it is, so go and
	bne.b	playit		;playit
	
	move.l	freqpos(a5),d0	;Pointer to Freq envelope stream
	beq.b	playit	;!!!Just to be sure, FF is sclerotic !!!
	move.l	d0,a1	;On ZX it loads 243 from 0, but here... ?
		
frqc1	moveq	#0,d0		;Get next freq/command
	move.b	(a1)+,d0	
	move.l	a1,freqpos(a5)	;Store pointer to stream for next time
	
	cmp.b	#128,d0		;Is it jump?
	bne.b	nojpfr		;skip if not
	
	bsr.w	getabs		;Get jump destination address
	move.l	d7,a1		;as new stream
	bra.b	frqc1		;and continue there
	
nojpfr	cmp.b	#130,d0		;Is it Tone Slide On command?
	bne.b	nicfr1		;Nope
	
	bset	#3,flags(a5)	;Set that we will do Tone Slides
	bra.b	frqc1		;next freq/command
	
nicfr1	cmp.b	#131,d0		;Is it Freq Slide On command?
	bne.b	nicfr2		;Nope
	
	bclr	#3,flags(a5)	;Clear it, so we now do Freq Slides
	bra.b	frqc1		;next freq/command
	
nicfr2	cmp.b	#132,d0		;Is it Tone/Noise Toggler?
	bne.b	nicfr3		;Nope
	
	eor.b	#9,master(a5)	;Toggle Tone / Noise on flags
	bra.b	frqc1		;Next command

;d0 was no command, so it is either Tone or Freq change value (depends)
	
nicfr3	btst	#3,flags(a5)	;Are we in Tone Slide mode?
	beq.b	nojpf0		;Skip if in Freq Slide mode.
	
	add.b	tone(a5),d0	;Add/Sub value from currently played
	move.b	d0,tone(a5)	;note and store it back

;Note that following part can perhaps read values out of table, if not
;used carefully in music data! Also note, that value 0 is not here checked for
;silence - so it sometimes happened, that when one channel was playing only
;noise (like drum), and the transposition unforunatelly set the note to zero,
;value from (freq-2) was loaded to freq. It was OK till it was not zero (it
;was never so on ZX Amadeus), but when it happened (and of course it happened),
;value of 0 was loaded to freq here, thus disabling tone (which was disabled
;by strobe anyway), but also noise (by seting volume to zero), although it
;should play... Took me quite a while, why in several songs the drums
;sometimes sounded quite strange... That's the reason why I put
;dc.w 65535 before freqs table in FXS, just to be NOT ZERO... Now, the
;freq table goes up to 13.75 Hz, so there is a non zero value...
	
	add.b	d0,d0		;Get AY frequency from the table
	move.l	AYfreq(pc),a3	;Note that first note is 1,
	move.w	(a3,d0.w),freq(a5)	;not 0 - 0 is for silence
	bra.b	playit		;and go to play sound
	
nojpf0	ext.w	d0		;So it is Freq change value
	
	add.w	d0,d0	;Correction - change AY freq change to
	add.w	d0,d0	;Amiga freq change...
	add.w	d0,d0
	
	add.w	d0,freq(a5)	;And store new frequency
	
;Well, so volume and Frequency is already generated, so it is showtime

playit	move.b	barva-worksp(a6),d0	;"Out" Noise freq

	and.b	andsix-worksp(a6),d0	;FF sometimes use only range 0-15 ?!?!?

	moveq	#6,d5	;(Why is it here in single channel processing?)
	bsr.w	out	;(Well, don't ask me :-)

	bclr	#2,flags(a5)	;Set flag First Edge of Note played

	moveq	#7,d5
	add.b	hlas-worksp(a6),d5 ;Get Channel number + 7 (8-10)
	move.w	freq(a5),d0	;Check frequency - if zero, then
	beq.b	mlc		;no tone AND no noise !!!
	
	move.b	vol(a5),d0	;Else "Out" channel volume

mlc	bsr.w	out		;Channel volume OR zero

	subq.b	#8,d5	;Get channel number - 1 (0-2)
	add.b	d5,d5	;Get Freq reg position (0,2,4)
	
	move.b	freq(a5),d0	;"Out" frequency
	bsr.w	out
	
	addq.b	#1,d5
	move.b	freq+1(a5),d0
	bsr.w	out
	
navrat	rts			;return

;===========================================================================

;Just store channel position as we proccess it... Originally much longer
;routine, of course :-), because of z80...

;putpos	move.l	a1,(a5)
;	rts

;changed to inline instruction
;and changed spec subroutines so it is not almost used... :-)

putpos	macro
	move.l	a1,(a5)
	endm

;===========================================================================

;This is called whenever new note is going to be played

nova	move.l	(a5),a1		;Get channel position
nova2	moveq	#0,d0		;Get next note/command
	move.b	(a1)+,d0
	putpos			;Remember position
	
	btst	#7,d0		;Is it special command?
	bne.B	spec		;Go there if it is

	moveq	#0,d5		;Fake freq = 0 -> will cause silence
					;later
	tst.b	d0		;Is the note 0 (silence) ?
	beq.B	notran		;Go further if it is...
	
	add.b	trans(a5),d0	;Add current Transposition to note
	move.b	d0,tone(a5)	;and store it
	bclr	#3,flags(a5)	;Turn Freq Slides on (default)
	add.b	d0,d0		;Get note frequency
	move.l	AYfreq,a3
	move.w	(a3,d0.w),d5
	
notran	move.b	(a1)+,len(a5)	;Get number of VBIs for which it will
	putpos		;sound (0=256) and store pointer
	move.w	d5,freq(a5)	;store frequency or 0 for silence
	move.l	freqbeg(a5),freqpos(a5) ;Re-Set Freq Env stream
	bset	#2,flags(a5)	;Say This is First Edge of Note
				;to prevent early Freq Stream proccessing
				;in case we will now jump to hl11 to
				;keep everything synchronized
				
;Next part is little tricky. It handles continuous Volume Envelope streams,
;so eg. Volume can still fade even if we play a lot different notes.
;Bit 0 of flags says, if we want to turn this feature on, bit 1 says if
;it is already on. To turn it off, it is necessary turn both bits off.
;It is quite clever, allows control even in music nesting etc.

	btst	#1,flags(a5)	;Already continuous Vol Envs?
	bne.w	hl11		;Proccess it if so..
	
	btst	#0,flags(a5)	;Do we want turn it on?
	beq.b	nic0		;Skip if not...
	
	bset	#1,flags(a5)	;From now, Vol Envs are continuous...
	
nic0	move.l	envbeg(a5),d0	;Get begin of Vol Env stream
	move.l	d0,a3		;!!!Just to be sure, FF is sclerotic !!!
	beq.b	nohit2		;On ZX it loads 243 from 0, but here... ?
	move.b	(a3)+,vol(a5)	;and load first volume and delay
	move.b	(a3)+,envper(a5);note that cannot use that +50 mode
nohit2	move.l	a3,envpos(a5)	;here. Re-Set Vol Env position.
	bra.w	playit		;And finally, play the note...

;===========================================================================

;This part proccesses all speciall commands found in the main channel stream,
;like gosubs, for-nexts, etc.

;Note that we trust the data - so there is no Command Code check, and no
;Stack overflow check because of too much Gosubs or For-Nexts

spec	lea	nova2(pc),a0	;to save some bytes
	add.b	d0,d0		;Execute proper routine...
				;Perhaps should be slightly
	lea.l	jptabl(pc,d0.w),a3 ;more CRASH PROOF :-)
	add.w	(a3),a3
	jmp	(a3)
	
jptabl	dc.w	jp-*,call-*,for_-*,next-*
	dc.w	zm6-*,zmch-*,frqsta-*,def-*
	dc.w	transp-*,ret_-*
	dc.w	join-*,unjoin-*,usr-*
	dc.w	add6-*,addtra-*
	dc.w	pushtra-*,poptra-*
	
;===========================================================================

jp	bsr.w	getabs		;Stream Jump to some address
	move.l	d7,a1		;load new position.
	jmp	(a0)
	
;===========================================================================

call	bsr.w	getabs		;Stream call. Remember position
	move.l	a1,-(a4)
	move.l	d7,a1		;on the stack and load new one.
	jmp	(a0)
	
;===========================================================================

for_	move.b	(a1)+,-(a4)
	subq.l	#1,a4
	move.l	a1,-(a4)	;For. Remember count and position
	jmp	(a0)		;on the steck
	
;===========================================================================

next	subq.b	#1,5(a4)
	beq.b	next1
	move.l	(a4),a1
	jmp	(a0)
next1	addq.l	#6,a4
	jmp	(a0)
	
;===========================================================================

zm6	move.b	(a1)+,barva-worksp(a6) ;change Frequency of Noise Channel
	jmp	(a0)
	
;===========================================================================

zmch	move.b	(a1)+,master(a5);Select Tone AND/OR Noise On/Off
	jmp	(a0)	;Note also Toggler in Freq Env Stream, used
			;to generate pretty interesting sounds...
	
;===========================================================================

def	bsr.w	getabs		;Define Volume Envelope Stream begin
	move.l	d7,envbeg(a5)	;for following notes
	jmp	(a0)
	
;===========================================================================

transp	move.b	(a1)+,trans(a5)	;Change transposition directly
	jmp	(a0)
	
;===========================================================================

ret_	move.l	(a4)+,a1	;Return from subchannel. Get position from stack.
	jmp	(a0)
	
;===========================================================================

frqsta	bsr.w	getabs		;Define Frequency Envelope Stream begin
	move.l	d7,freqbeg(a5)	;for following notes
	jmp	(a0)
	
;===========================================================================

join	bset	#0,flags(a5)	;Turn on Continuous Volume Envelopes
	bra.b	unjin
;	bclr	#1,flags(a5)
;	jmp	(a0)
	
;===========================================================================

unjoin	bclr	#0,flags(a5)	;Turn off Continuous Volume Envelopes
unjin	bclr	#1,flags(a5)
	jmp	(a0)
	
;===========================================================================

usr	addq.l	#2,a1	;This function was used for USER calls to any
			;Z80 code (eg. for synchronizing picture with
	jmp	(a0)	;sound as in Indiana Jones 3 by F.F.)
				;Not implemented, OF COURSE :-)

;===========================================================================

add6	move.b	(a1)+,d0	;Increase/Decrease Noise Channel Freq
	add.b	d0,barva-worksp(a6)	;Used for some effects...
	jmp	(a0)
	
;===========================================================================

addtra	move.b	(a1)+,d0	;Increase/Decrease Transposition level
	add.b	d0,trans(a5)
	jmp	(a0)
	
;===========================================================================

pushtra	move.b	trans(a5),d0	;Store Transposition level
	move.w	d0,-(a4)
	jmp	(a0)
	
;===========================================================================

poptra	move.w	(a4)+,d0	;Retore Transposition level
	move.b	d0,trans(a5)
	jmp	(a0)
	
;===========================================================================


	section	AmadeusData,bss

output	ds.b	14	;Output space for AY registers

barva	ds.b	1	;Noise Frequency
hlas	ds.b	1	;Temporary, for channel number

stk1	ds.l	1	;Stack pointers for all three channels
stk2	ds.l	1
stk3	ds.l	1

stacksize	equ	128	;even bigger then on ZX I think...

worksp	ds.b	3*30	;WorkSpace for three channels, see below
stacks	ds.b	3*stacksize	;Space for Channel Stacks, used to store
				;Gosub & For-Next informations.

wrk1	equ	0	;Offset definitions
wrk2	equ	30
wrk3	equ	60

data	equ	0	;l	;Current Main Channel position
len	equ	4	;b	;Delay remaining to the end of note
master	equ	5	;b	;Partial Strobe (0,3), see AY strobe
envpos	equ	6	;l	;Current Volume Envelope Stream Pos.
envper	equ	10	;b	;Delay till pickup of next volume
vol	equ	11	;b	;current volume
freq	equ	12	;w	;current frequency
envbeg	equ	14	;l	;Start of Volume Envelope Stream
freqpos	equ	18	;l	;Current Freq Env Stream position.
flags	equ	22	;b	;Flags - see code for more
trans	equ	23	;b	;Current Transposition
freqbeg	equ	24	;l	;Start of Freq Env Stream
tone	equ	28	;b	;Current note

songlen	ds.w	1
fadelen	ds.w	1
andsix	ds.b	1

