

	public	_stdout


; Simple Sound Engine © Copyright 1990 Software Alchemy All Rights Reserved
; Revision 0.70 Monday January 8 1990 Anselm Hook
; Revision 0.71 Jan 29 90
; Revision 0.80 Feb 2 90

; Audio can be crunched by discarding the lowest 4 bits of data, and then
; overlapping the second half of the data with the first, although data is
; lost by this technique it still sounds pretty good.  I didn't do this here.

;
; There are 2 bugs:
;
;	1) I am *still* losing serial data...
;	   My current hypothesis is that the InternalSYNC routine is
;	   breaking up packets despite theory to the contrary.
;	   There is no fix for this except having a proper externally
;	   supplied SYNC, if I do detect external SYNC I do turn mine off,
;	   in which case it should work properly, although my keyboard
;	   doesn't supply a sync to me.
;	   [I am using a 256 byte circular buffer with raw serial input.]
;	   search for the flag 'AZUREVIDIAN' to turn internal sync on.
;
;	2) Sounds do not get turned off quickly enough in StopSound and
;	   as a consequence more channels are in use at any moment than
;	   required, this gives a clipping effect to playback.  This doesn't
;	   happen in the sequencer, but only in the playback routine.
;
;	   Another problem is that the playback runs at level 6, which means
;	   sound-interupts may not happen as quickly as possible.  I know
;	   this causes a problem because when I had the level 6 just set a
;	   flag, and ran the playback from non-interupt mode, it was flawless.
;	   [Ie: sound-interupts also help stop sounds and free up channels].
;
;	   You will notice the effect in the game music, it sounds like it
;	   goes into solo mode for small sequences.


; Note I spell Interupt with one 'R', I forward this motion.

;
; 64 Instruments currently, 16 Midi Channels (ie: 16 noises at any time-slice)
; NOTE:  Pass sound # to play in D0, must be multiple of 16.
; The MusicSequencer relys on an external MIDI SYNC.
; But the MusicEngine, which plays your finished product does its own timing.
;

;
; I think the following two features would be nice to have,
; although I am not going to program them myself:
;
; #1)
; overlay mode erases any notes colliding with same channel at same sync time
;    overlay mode is good for final touch ups, and for multiple keyboards.
;    An entire music score could be built with one pass of the sequencer.
; #2)
; erase mode erases any notes on same channel right to end of sequence
;    erase mode is good for a single keyboard enviroment where multiple
;    passes of the sequencer (on diff tracks) are used to build a music score.
;

	blanks	on				; Bezerk Aztek declarations

MIDI_NULL	equ $00				; Supported Midi Commands
MIDI_NOTE	equ $90
MIDI_NOTEOFF	equ $A0
MIDI_ENDSONG	equ $FC
MIDI_SYNC	equ $F8

MIDI_FORCERTS	equ $ef				; Internal Commands

MIDI_NOTECHANGE equ $B0				; Unsupported Commands
MIDI_CHANGESAM	equ $C0
MIDI_SONGSTART	equ $FA



MAXSAM equ 64					; # of Instruments Maximum Allowed...
BUFFERSIZE equ $20000				; Memory of Music Score


	public	SoundEngineStart		; Warm up Audio Engine
	public	SoundEngineStop			; Stop Audio Engine
	public	SoundPlay			; Play Requested Sound D0>>4
	public	SoundStop			; Stop Requested Sound D0>>4

	public	SoundGetHunk
	public	SoundDecodeHunk
	public	SoundReleaseHunk

	public	MusicEngineStart
	public	MusicEngineStop
	public	MusicEnginePause
	public	MusicEngineUnPause

	public	MusicSequencer

	public	JustPlayIt
	public	StopTheMusic

 public Cases	;(use for debugging "optimizations" )
 public MCases


;**************************************************************************
; stand alone module, just compile and link to itself only, and run.
StandAlone
	clr.l	d0
Borrow	lea	skipme(pc),a0			;(flag, sequencer or player)
	move.l	d0,(a0)

	lea	one(pc),a0
	bsr	dumpmess
	move.l	#BUFFERSIZE,d0
	bsr	MusicMem			; Get and Prep Mem
	beq.s	StandX3

	lea	two(pc),a0
	bsr	dumpmess
	lea	FileName(pc),a0
	bsr	SoundGetHunk			; A0 = Pointer to File
	beq.s	StandX2
	lea	fh(pc),a1
	move.l	a0,(a1)

	move.l	a0,-(a7)
	lea	three(pc),a0
	bsr	dumpmess
	move.l	(a7)+,a0
	bsr	SoundDecodeHunk			; Decode from A0
	beq.s	StandX

	lea	seven(pc),a0			; say LOAD SCORE
	bsr	dumpmess

	lea	FileName2(pc),a0
	move.l	a0,d1
	bsr	FindFileSize			; d0 = size or zero
	beq.s	noload
	move.l	MusicBuffer(pc),a0
	add.l	d0,a0
	cmp.l	MusicBufferEnd(pc),a0		; too big?
	bcc.s	noload
	lea	FileName2(pc),a1
	move.l	a1,d1				; D1 = Name
	move.l	MusicBuffer(pc),a3		; A3 = Dest
	move.l	d0,a2				; A2 = Size
	move.l	d0,a5				; (save size)
	bsr	LoadFile
	beq.s	noload
	move.l	MusicBuffer(pc),a3		; Advance to end of load
	add.l	a5,a3
	move.b	#MIDI_ENDSONG,(a3)+		; Make sure we have a stop
	move.b	#MIDI_ENDSONG,(a3)+		; make sure stop is visible
	move.b	#MIDI_ENDSONG,(a3)+
	lea	BufferMerge(pc),a1		; (dummy, flag merge)
	move.l	a3,(a1)
	lea	four(pc),a0			; Say loaded
	bsr	dumpmess
	bra.s	load
noload	move.l	MusicBuffer(pc),a3

load	lea	five(pc),a0
	bsr	dumpmess

	move.l	skipme(pc),d0
	bne	JustPlayItCont
						; (A3 = End of Song IN & OUT)


	btst	#7,$bfe001
	bne.s	1$
;----------------------------------------------------------------------------
	lea	didstartit(pc),a0		; hack
	move.b	#1,(a0)
	bsr	SoundEngineStart		; (playback only mode)
	move.l	MusicBuffer(pc),a0
	bsr	MusicEngineStart
	moveq	#24,d0				; Speed!
	bsr	MusicEngineUnPause
0$	btst	#6,$bfe001
	bne.s	0$
	bsr	MusicEngineStop
	bsr	SoundEngineStop
	bra.s	StandX
;----------------------------------------------------------------------------
1$


;----------------------------------------------------------------------------
	bsr	MusicSequencer			; Play and allow sequencing
;----------------------------------------------------------------------------
						; (A3 = End of Song IN & OUT)
						; SAVE SCORE
	sub.l	MusicBuffer(pc),a3
	cmp.l	#20,a3				; Don't save if very small
	bcs.s	StandX
	cmp.l	#BUFFERSIZE-3,a3		; bug???
	bcc.s	StandX
	move.b	#MIDI_ENDSONG,(a3)+		; Make sure we have a stop
	move.b	#MIDI_ENDSONG,(a3)+		; make sure stop is visible
	move.b	#MIDI_ENDSONG,(a3)+
	move.l	a3,a2				; a2 = size
	move.l	MusicBuffer(pc),a3		; a3 = source
	lea	FileName2(pc),a0
	move.l	a0,d1				; D1 = Name
	bsr	SaveFile
	lea	six(pc),a0
	bsr	dumpmess

StandX	move.l	fh(pc),a1
	bsr	SoundReleaseHunk
StandX2	move.l	MusicBuffer(pc),a1
	move.l	#BUFFERSIZE,d0
	bsr	FreeMemory
StandX3	moveq	#0,d0
	rts

;----------------------------------------------------------------------------
JustPlayIt
	bsr	SoundEngineStart		; (always start audio at least)
	moveq	#-1,d0
	bra	Borrow				; (either comes here or err)
JustPlayItCont
	lea	didstartit(pc),a0
	move.b	#1,(a0)
	move.l	MusicBuffer(pc),a0
	bra	MusicEngineStart		; Just play song (interupts)
StopTheMusic
	bsr	SoundEngineStop
	move.b	didstartit(pc),d0		; (allow game without music)
	beq.s	nope
	bsr	MusicEngineStop			; stop interupts
	bra	StandX
nope	rts
didstartit:
	dc.l 0
;----------------------------------------------------------------------------


FileName	dc.b	'music.hunk',0,0
		even
FileName2	dc.b	'music.score',0,0
		even
fh		dc.b	'ANDY HOOK '
skipme		dc.l	0

;**************************************************************************

SoundEngineStart
	move.l	#$dff000,a6			; A6 = Base of Hardware
	lea	SoundVector(pc),a0
	move.l	$70,(a0)			; (save)
	bsr	SoundEngineStop			; (stop any previous activity)
	lea	SoundInterupt(pc),a0		; A0 = My Audio Handler
	move.l	a0,$70				; Audio Interupt Level 4
	move.w	#$c780,$9a(a6)			; Enable Audio Interupts
	rts

SoundEngineStop
	move.l	#$dff000,a6			; A6 = Base of Hardware
	move.w	#$000f,$96(a6)			; Turn OFF All Audio DMA
	move.w	#$0780,$9c(a6)			; Clear Audio Interupts Pend
	move.w	#$0780,$9a(a6)			; Disable Audio Interupts
	move.l	SoundVector(pc),$70		; (restore)
	bchg	#1,$bfe001			; Audio Filter On/Off
	rts

	dc.b	"As we become less like those we know, we "
	dc.b	"seem to become more like those we hear of"

;
; Sound Interupts occur only when its time to kill a sound
;
SoundInterupt
	move.w	#$2780,$dff09a			; Prevent interupt collisions
	movem.l	d0/d1/a0/a6,-(a7)
	lea	$dff000,a6
	move.w	$1e(a6),d0			; Interupt Request Read
	and.w	#$0780,d0
	moveq	#0,d1
	move.w	d0,d1				; (save)
 	lsr.w	#7,d1
	move.w	d1,$96(a6)			; Stop these Audio DMA
	bsr	SoundPause
	move.w	d0,$9c(a6)			; Clear these Audio Pending

	or.l	#$01000010,d1			; Frequency Fast, Volume Low
	lea	SoundChannel(pc),a0
	btst	#0,d1
	beq.s	1$
	clr.b	(a0)
	clr.b	4(a0)
	move.l	d1,$a6(a6)
1$	btst	#1,d1
	beq.s	2$
	clr.b	1(a0)
	clr.b	5(a0)
	move.l	d1,$b6(a6)
2$	btst	#2,d1
	beq.s	3$
	clr.b	2(a0)
	clr.b	6(a0)
	move.l	d1,$c6(a6)
3$	btst	#3,d1
	beq.s	4$
	clr.b	3(a0)
	clr.b	7(a0)
	move.l	d1,$d6(a6)
4$
	;eor.w	#$8780,d0 (optional)
	;move.w	d0,$9a(a6)
	move.w #$a780,$dff09a
	movem.l	(a7)+,d0/d1/a0/a6
	rte

;
; Pointer to Audio Data
;
SoundRaw	dc.l	0
SoundVector	dc.l	0


;
; User List of available Sounds
; Format
;	Delta-Start.l, Length, Frequency, Volume, Priority, 0,0,0
;
SoundList	dcb.w	16*MAXSAM,0

;
; Sound Channel Arbitration Manager
;	x = Active Priority, 0 = Available
;
SoundChannel	dc.b	0,0,0,0
SoundOwner	dc.b	0,0,0,0
SoundExtra	dc.b	0,0,0,0			;<- Extra Info for Note-OFF
		dc.b	0,0,0,0
;
;	Arbitration system finds best channel to free based on priority
;	of sound request.  Sounds of the same priority may overrun each other.
;	D0 = Requested Sound to Play
;	D7 = Off extra-information tag (optional)
SoundPlay
	move.w	#$2000,$dff09a			; Allow Game Audio Also!!!
	lea	SoundList(pc),a0
	lea	(a0,d0.w),a0
	move.l	6(a0),d5			; Pre-get Frequency, Volume
	moveq	#0,d7				; (off tag defaults to zero)
	bsr	SoundPlayTwo
	move.w	#$a000,$dff09a
	rts

SoundPlayTwo
	tst.b	d5				; Is Really Note Off?
	beq	SoundStop
	lea	SoundList(pc),a0
	lea	(a0,d0.w),a0			; A0 = Pointer to Sound Info
	move.w	10(a0),d2			; D2 = Request Priority
	moveq	#3,d4				; D4 = Channel # , Loop
	moveq	#99,d3
1$	move.b	SoundChannel(pc,d4.w),d1	; D1 = Channel Priority
	beq.s	3$				; Available Sound Channel?
	cmp.b	d1,d2
	bcs.s	2$
; cmp.b d2,d1
; bcc.s 2$
	move.w	d4,d3				; D3 = Last Available Priority
2$	dbf	d4,1$
	cmp.b	#99,d3
	beq.s	SoundPlay900			; No Low Priority Slots Open
	move.w	d3,d4
3$
						; D4 = Channel #0-3
	lea	SoundChannel(pc),a1
	move.b	d2,(a1,d4.w)			; D2 = New Sound Priority
	move.b	d0,4(a1,d4.w)			; D0 = New Sound BackPtr
	move.b	d7,8(a1,d4.w)			; D7 = Optional Off Tag
	swap	d7
	move.b	d7,12(a1,d4.w)
	swap	d7

	moveq	#1,d0
	lsl.w	d4,d0				; D0 = Hardware Int # 1-2-4-8
	move.w	d0,d1
	lsl.w	#7,d1				; D1 = (0-8)*128=Hardware DMA
						; D0 = %0000000000001234
						; D1 = %0000012340000000

	move.l	#$dff000,a6			; A6 = Base of Hardware
	move.w	d0,$96(a6)			; Turn off Channel DMA
	bsr	SoundPause			; Delay against audio clicking
	move.w	d1,$9a(a6)			; Disable Int

	lea	$dff0a0,a2			; A2 = Audio Hardware
	lsl.w	#4,d4				; 0-3 * 16
	add.w	d4,a2

	;move.l	SoundRaw(pc),a1			; A1 = Base of Sound Data
	;add.l	(a0),a1
	move.l	(a0),a1				; (jan 29 90, offsets are precomputed now)

 	move.l	a1,(a2)				; Memory
	move.w	4(a0),4(a2)			; Length
	move.l	d5,6(a2)			; Frequency, Volume
	bsr	SoundPause

	or.w	#$8000,d0
	move.w	d0,$96(a6)			; Re-enable DMA
	bsr	SoundPause			; (wait for started)
						; 4(a2) Repeat can go here

	move.w	d1,$9c(a6)			; Clear Started-Sound Flag
	or.w	#$8000,d1
	move.w	d1,$9a(a6)			; Re-enable Interupt
SoundPlay900
	rts

SoundPause
	move.l	d1,-(a7)			; Time Delay to prevent clicks
	move.w	#320,d1
1$	tst.b	d1
	dbf	d1,1$
	move.l	(a7)+,d1
	rts



;
;	Stop a requested sound, scans active sounds and stops if active
;		D0 = sound to stop
;		D7 = optional sentry/signature/tag/ID for more accurate off
SoundStop
	movem.l	d0-d3/a0/a6,-(a7)
	lea	$dff000,a6
						; D0 = Sound to stop
	lea	SoundOwner(pc),a0
	moveq	#3,d1				; D1 = Loop Control
1$	cmp.b	(a0,d1.w),d0			; D0 = Found Active Sound?
	bne.s	2$
	cmp.b	4(a0,d1.w),d7			; Tags Match?
	bne.s	2$
	move.l	d7,d2
	swap	d2
	cmp.b	8(a0,d1.w),d2
	bne.s	2$

	clr.b	-4(a0,d1.w)
	clr.b	0(a0,d1.w)

	move.l	d1,d2
	lsl.b	#4,d2				; D2 = Channel Hardware
	lea	$dff0a0,a1			; A1 = Audio Hardware Base
	move.w	#1,8(a1,d2.w)			; Channel Volume (0 makes pop)
	move.w	#100,6(a1,d2.w)			; Period (0 makes click)

	moveq	#1,d2
	lsl.b	d1,d2				; D2 = Channel Bit #
	move.w	d2,$96(a6)			; Stop Audio
	bsr	SoundPause
	lsl.w	#7,d2
	move.w	d2,$9c(a6)			; Clear Interupt Pending

2$	dbf	d1,1$
	movem.l	(a7)+,d0-d3/a0/a6
nyet:	rts

;***************************************************************************
;***************************************************************************
; Music Engine written entirely in The Transform Language © 1990 Anselm Hook
; The Music Sequencer was tested using a YAMAHA SHS-10R Midi Synth Keyboard.

;
; For those of you who are already freaked out by all the numbers:
;
; We had a bunch of hardware level stuff to setup the SoundEngine Interupts,
; Now we are going to setup a bunch more interupts for the MusicEngine so
; that it can keep track of time properly.  Aside from that the only other
; hardware stuff is the direct SERIAL I/O code....
;

;
;  Simply load the music file up and pass start in a0
;  end must be terminated with a $fc
;
MusicEngineStart
						; a0 = the data!
	lea	musicstart(pc),a1
	move.l	a0,(a1)
	lea	musicpos(pc),a1
	move.l	a0,(a1)
	rts

MusicEngineUnPause
	move.b	didstartit(pc),d1
	beq	nyet
	lea	musicpos(pc),a0			; Start/Restart Music
	move.l	musicstart(pc),(a0)
	lea	MusicServer(pc),a0
	bra	TimerStartSpeed

MusicEngineStop
MusicEnginePause
	move.b	didstartit(pc),d1
	beq	nyet
	bra	TimerStop


;
;	MusicServer, an interupt driven routine which plays your music.
;
MusicServer
	btst	#1,$bfdd00			; Clear INT_FLAG + Test Case
	beq.s	MusicServer100
	movem.l	d0-d7/a0-a6,-(a7)
	bsr	MusicServerTwo
	movem.l	(a7)+,d0-d7/a0-a6
MusicServer100
	move.w	#$2000,$dff09c			; Clear interupt
	rte

MusicServerTwo
	lea	musicpos(pc),a0
	move.l	(a0),a3
Music3	move.b	(a3)+,d0
Music4	move.b	d0,d1
	and.w	#15,d1
	lsr.b	#3,d0				; 0-15 * 2
	and.w	#30,d0
	jmp	Cases(pc,d0.w)
Cases	bra.s	Music3				; #0
	bra.s	Music3				; #1
	bra.s	Music3				; #2
	bra.s	Music3				; #3
	bra.s	Music3				; #4
	bra.s	Music3				; #5
	bra.s	Music3				; #6
	bra.s	Music3				; #7
	bra.s	Music3				; #8
	bra.s	Note				; #9
	bra.s	Music3				; #10
	bra.s	Music3				; #11
	bra.s	Music3				; #12
	bra.s	Music3				; #13
	rts					; #14	FORCE RTS
Sync	cmp.b	#12,d1				; #15   END?
	bne.s	1$
	move.l	musicstart(pc),a3
1$	lea	musicpos(pc),a0
	move.l	a3,(a0)
	rts

;---------------------------------------------------------------------------
periods	dc.w 856,808,760,720,680,640,604,572,540,508,480,452,428,404,380,360
	dc.w 340,320,302,286,270,254,240,226,214,202,190,180,170,160,151,143
	dc.w 135,127,120,113,107,101
;---------------------------------------------------------------------------

Note	add.b	d1,d1				; d1 = midi channel 0-15
	move.w	instr(pc,d1.w),d6		; D6 = Instr of Channel 0-15
	move.b	-1(a3),d7			; Signature for Accurate OFF
NoteTwo	move.b	(a3)+,d0			; D0 = Note #0-x
	cmp.b	#$35+64,d0
	bcc.s	Music4
	sub.b	#$35,d0				; Yamaha Lowest Key #
	swap	d7
	move.b	d0,d7				; construct signature id.w
	swap	d7				; made of command,channel,note

	add.b	d0,d0
	move.w	periods(pc,d0.w),d5		; D5 =Note=Rate(in upper word)
	swap	d5
	move.b	(a3)+,d5			; D5 = Vol %xxxxxxxxxx111111
 lsr.b #1,d5			; music volume to half, yamaha only!!!
	move.w	d6,d0

	bsr	SoundPlayTwo			; Don't corrupt d6/a3-a7
	bra.s	NoteTwo				; Any more events in packet?

;---------------------------------------------------------------------------
instr	dc.w	0,16,32,48,64,80,96,112,128,144,160,176,192,208,224,240
	;dc.w	256,17*16,18*16,19*16,20*16,21*16,22*16,23*16,24*16,25*16
	;dc.w	26*16,27*16,28*16,29*16,30*16,31*16
;---------------------------------------------------------------------------

NoteChange
	add.b	d1,d1				; Channel 0-15
	move.b	(a3)+,d0			; Instr # 0-x
	lsl	#4,d0				; *16 to for SoundPlay format
	lea	instr(pc),a0
	move.b	d0,(a0,d1.w)
	bra.s	Music3

musicpos	dc.l	0
musicstart	dc.l	0

;************************************************************************
;************************************************************************
; Utilities for use with music engine

;
; Music sequencer:
;	RMB  -  Restart Music - Will merge your music with previous score.
;	LMB  -  Exit, saves music to disk
;


;Sequencer theory:
;
;Sequencer - Switch thru midi-events
;	(get-events)
;	note-on
;		play, store
;	pulse
;		read bufferA till pulse
;			play,store (overlay/erase mode)
;		write pulse
;
;play  - call music player manually.
;store - write bufferB
;


;************************************************************************
MusicSequencer
	bsr	MusicRestart
	bsr	SerialStart
	bsr	SoundEngineStart
	lea	InternalSync(pc),a0
	bsr	TimerStart
	bsr	MusicIn
	bsr	TimerStop
	bsr	SoundEngineStop
	bra	SerialStop
;************************************************************************


;************************************************************************
;
;	Music In Store, main routine
;		Watches for user restart/quit input
;		Reads raw-serial data, plays and stores it (till sync)
;		Upon sync, merges in old data
;		adds sync and loops...
;
Unknown
MusicIn
	btst	#6,$bfe001			; LMB
	bne.s	1$
	rts
1$	btst	#7,$bfe001			; RMB  (#6 is port #2 bytheway)
	bne.s	2$				; (remember to refresh sensors)
0$	move.w	#$ff0,$dff180			; YELLOW
	btst	#7,$bfe001			; (must release also)
	beq.s	0$
	bsr	MusicRestart
2$	bsr	MidiIn				; Wait for Midi Event
MusIn2	move.b	d0,d1				; D1 = Channel + Command
	lsr.b	#3,d0				; D0 = Command
	and.w	#30,d0
	jmp	MCases(pc,d0.w)
MCases	bra.s	Unknown
	bra.s	Unknown
	bra.s	Unknown
	bra.s	Unknown
	bra.s	Unknown
	bra.s	Unknown
	bra.s	Unknown
	bra.s	Unknown
	bra.s	Unknown
	bra.s	MNote				; #9
	bra.s	Unknown				; #10
	bra.s	Unknown				; #11
	bra.s	Unknown				; #12
	bra.s	Unknown				; #13
	bra.s	Unknown				; #14
	bra	MSync				; #15
;
; Store and play user input
;
MNote	cmp.l	MusicBufferEnd(pc),a3
	bcc.s	MEnd

; within this sync I can receive any other packet
; or more of these (notes)
; if I do not recognize the packet I should pass it back to the
; main routine for it to determine the packet type.

	bsr	MidiIn				; D0 = Note #
	cmp.b	#$35,d0				; Not my packet
	bcs.s	MusIn2
	cmp.b	#$35+64,d0			; Not my packet
	bcc.s	MusIn2
	move.b	d0,d2
	bsr	MidiIn				; D0 = Volume
	cmp.b	#$41,d0				; Transmission bug???
	bcc.s	MusIn2

	tst.b	d1				; Command+Channel (once only)
	beq.s	1$
	move.b	d1,d7				; D7 = Off Signature, dont hurt me!
	move.b	d1,(a3)+
	and.w	#15,d1
	add.b	d1,d1
	lea	instr(pc),a0
	move.w	(a0,d1.w),d6			; D6 = Instrument # (dont damage)
1$	clr.b	d1

; whenever we play a note it goes to one of the four channels, now we are
; going to pass a note off for that note, but we may inadvertantly be turning
; off another note which happens to be using that channel dma.  To actually
; turn off the proper note we must be able to positively identify that
; particular note and midi source channel.

; so remembering a sound involves remembering its host frequency and channel.
;
; whenever we do a midi-change to a specific channel we must clear all note
; offs for that channel; so that a note off for an old instrument doesn't
; affect any new ones.
;
	move.b	d2,0(a3)			; Note #0-x
	move.b	d0,1(a3)			; Volume
	move.b	#MIDI_FORCERTS,2(a3)		; (return to me)
	move.w	d1,-(a7)
	bsr	NoteTwo				; A3+=3, PASS D6!!!
	move.w	(a7)+,d1
	subq.w	#1,a3				; Delete FORCE_RTS
	bra.s	MNote

MEnd	move.w	#$f00,$dff180
	bra	MusicIn


;
; We've played user input, now merge in old score (of this sync).
;
; We install a fake handler and pass old data as fresh input
; The full capacity of the MusicSequencer can be applied to old data now.
; The only thing we do is catch the old sync and abort at that time.
; (Thus the old data timing is run off new external syncs also)
;
MSync	move.w	#$ff01,$dff034			; Hardware Refresh for RMB
	cmp.b	#$f8,d1
	bne	Unknown
	cmp.l	MusicBufferEnd(pc),a3
	bcc.s	MEnd
	lea	ReadFn(pc),a0			; On pass #2?
	lea	Sinking(pc),a1
	cmp.l	(a0),a1
	beq.s	MSync2
	move.l	(a0),4(a0)			; Save Old Fn
	move.l	a1,(a0)
	bra	MusicIn				; Eat Fake Events till SYNC
MSync2	move.l	4(a0),(a0)
	move.b	#MIDI_SYNC,(a3)+
	bra	MusicIn
Sinking	move.b	#MIDI_SYNC,d0
	cmp.l	a3,a4				; New Overran Old ???
	bcs.s	SyncHit
	cmp.l	MusicBufferEnd(pc),a4		; Read Hit end
	bcc.s	SyncHit
	move.b	(a4)+,d0			; Read Old Music
SyncHit	rts

MidiIn	move.l	ReadFn(pc),a0
	jmp	(a0)
;***********************************************************************

	dc.b	"When you find yourself between  "
	dc.b	"the Devil and the deep blue sea "

MusicRestart
	lea	ReadFn(pc),a0
	lea	SerialInWait(pc),a1
	move.l	a1,(a0)

	move.l	BufferMerge(pc),a0		; First Time?
	cmp.l	#0,a0
	beq.s	UseOld

; a single buffer has the source being copied back to the dest.
; the trick is that the source must start suitably far away from the dest.
; as a matter of fact the best possible place for the source to be is as
; close to the very end as possible, so overwrite happens last if possible.

	move.l	MusicBuffer(pc),a1
	move.l	MusicBufferEnd(pc),a0
	cmp.l	a3,a1
	beq.s	2$
1$	move.b	-(a3),-(a0)			; Copy up to end...
	cmp.l	a3,a1
	bcs.s	1$				; A3 will equal *MusicBuffer
2$	move.l	a0,a4				; A4 = SOURCE, A3 = DEST
4$	cmp.l	a1,a0
	bcc.s	3$
	move.b	#MIDI_SYNC,-(a0)		; (Erase Rest of Buffer)
	bne.s	4$
3$	bra.s	Neateh

UseOld	move.l	MusicBufferEnd(pc),a4		; A4 = Tracks SOURCE (default)
Neateh	move.l	MusicBuffer(pc),a3		; A3 = Tracks DEST
	lea	BufferMerge(pc),a0
	move.l	a4,(a0)
	rts


MusicMem
	move.l	d0,a2
	bsr	AllocateMemory
	beq.s	MusicMemFail
	move.l	d0,d1
	add.l	a2,d1
	lea	MusicBuffer(pc),a0
	move.l	d0,(a0)+			; (start/end)
	sub.l	#10,d1				; for checking if *near* end
	move.l	d1,(a0)
	move.l	d0,a0
	move.b	#MIDI_SYNC,d0			; Prepare recording buffer
1$	move.b	d0,(a0)+
	cmp.l	a0,d1
	bne.s	1$
	moveq	#1,d0
MusicMemFail
	rts

ReadFn		dc.l	0,0
MusicBuffer	dc.l	0			; Start both buffers
MusicBufferEnd	dc.l	0			; End of both buffers
BufferMerge	dc.l	0			; Start of Source buffer

;****************************************************************************
;****************************************************************************

	;some serial in stuff i don't set:
	;move.w	#115,$dff032			; BAUD = 31250
	;bclr	#6,$bfde00			; CIAB, SPMODE=Input
	;move.b	#3,$bfd200			; MODE = Input
	;move.b	#$ff,$bfd000			; Turn on everything...

SerialOut
	move.w	#115,$dff032			; BAUD = 31250
	move.b	#$c0,$bfd200			; MODE = Output
	move.b	#0,$bfd000
1$	btst	#13-8,$dff018			; Buffer Ready?
	beq.s	1$
	move.w	d0,$dff030			; Output new byte
	rts

SerialInWait
	move.w	d1,-(a7)
	lea	SerialPos(pc),a0

	move.w	(a0),d1				; My Read Pos
SWait	move.b	3(a0),d0			; Sync Timer Interupt Pos
	cmp.b	d1,d0				; Caught up, wait for more
	beq.s	AddSync

	move.b	4(a0,d1.w),d0			; D0 = Value to Return

	cmp.b	#MIDI_SYNC,d0			; External SYNC found!!!
	bne.s	1$
	move.w	#80,-4(a0)			; Disable internal SYNCing
1$	tst.w	-4(a0)
	beq.s	2$
	subq.w	#1,-4(a0)			; Try to enable...

2$	addq.b	#1,d1				; Add molulo 255
	move.b	d1,1(a0)			; Store modulo 255
	move.w	(a7)+,d1
	rts


AddSync
	bra.s	SWait				; AZUREVIDIAN (remove me)
	tst.w	-4(a0)				; Externally overriden?
	bne.s	SWait
	tst.w	-2(a0)
	beq.s	SWait
4$	subq.w	#1,-2(a0)
	move.b	#MIDI_SYNC,d0
	move.w	(a7)+,d1
	rts

;
; the theory for internal syncs is that the midi-device should dump data
; in packets, such that serialwait should never wait for the rest of a packet
; If this is true then serialwait only waits during interm periods between
; packets.  This is a safe time to issue a sync without breaking packets.
;
; In reality I get so many syncs that they just about override everything
; else.
;

SerialInterupt
	btst	#11-8,$dff01c			; My Interupt?
	beq.s	2$

	movem.l	d0/d1/a0,-(a7)
	move.w	$dff018,d0			; Read Byte from Hardware
	lea	SerialPos(pc),a0		; Circular buffer
	move.w	2(a0),d1			; Get Buffer Pointer, clean.w
	move.b	d0,4(a0,d1.w)			; Store data byte to buffer
	addq.b	#1,d1				; Advance pointer Modulo 255
	move.b	d1,3(a0)			; store pointer Modulo 255
	movem.l	(a7)+,d0/d1/a0

2$	move.w	#$0800,$dff09c			; Clear INTF_RBF in INTREQ
	rte
		dc.w	0			; Sync Disable Time Flag
		dc.w	0			; Sync Flag
SerialPos	dc.w	0,0			; Ptr #2,#3
		dcb.l	256/4,0			; Serial Buffer

;****************************************************************************

SerialStart
	move.w	#$0800,$dff09a
	move.w	#115,$dff032			; BAUD = 31250
	lea	SerialVector(pc),a0
	move.l	$74,(a0)			; Save Old Vector
	lea	SerialInterupt(pc),a0
	move.l	a0,$74
	move.w	#$0800,$dff09c			; Clear any previous
	move.w	#$c800,$dff09a			; Turn on Serial Interupt
	rts

SerialStop
	move.w	#$0800,$dff09a
	lea	SerialVector(pc),a0
	move.l	(a0),$74
	rts
SerialVector	dc.l	0

;****************************************************************************

InternalSync
	btst	#1,$bfdd00			; Mine?
	beq.s	0$

	move.l	a0,-(a7)
	lea	SerialPos(pc),a0
	addq.w	#1,-2(a0)			; One More Sync!!!
	move.l	(a7)+,a0

0$	move.w	#$2000,$dff09c
	rte

;************************************************************************
;
; A0 = Handler function, must turn on yourself (poke $dff09a,$8000+$2000)
;
TimerStart
	moveq	#24,d0				; Speed!
TimerStartSpeed
	move.w	#$2000,$dff09a			; Disable external interupt
	lea	TimerVector(pc),a1
	move.l	$78,(a1)
	move.l	a0,$78				; Set up new interupt server
	lea	$bfd000,a1
	move.b	#$7f,$d00(a1)			; Disable CIAB interupt
	move.b	#204,$400(a1)			; TimerA for 1 millisecond
	move.b	#2,$500(a1)
	move.b	d0,$600(a1)			; TimerB for x milliseconds
	move.b	#0,$700(a1)
	move.b	#$01,$e00(a1)			; TimerA continuous
	move.b	#$41,$f00(a1)			; TimerB continuous,count A
	move.b	#$82,$d00(a1)			; Enable TimerB interupt
	move.w	#$2000,$dff09c			; Clear Interupt
	move.w	#$e000,$dff09a			; Enable external interupt
	rts

TimerStop
	move.w	#$2000,$dff09a			; Disable external interupt
	move.l	TimerVector(pc),$78		; Restore old timer handler
	move.b	#0,$bfde00			; TimerA continuous
	move.b	#0,$bfdf00			; TimerB continuous
	move.w	#$2000,$dff09c			; Clear Interupt
	move.w	#$a000,$dff09a			; Enable external Interupt
	rts

TimerVector:	dc.l	0

;****************************************************************************
;****************************************************************************

;
; FORMAT
;	'SAND' , # of Sample.w, # Size of header.w,
;	[ HEADER.l ] * # Size of header  (including 'SAND' Sentry ID).
;	[ SAMPLES ]  (whatever memory they take)
;

; I am currently forcing only 16 samples, because of hardcoded memory space.

SoundDecodeHunk
	cmp.l	#'SAND',(a0)
	bne.s	DecodeFail
	move.w	4(a0),d0			; # = Samples
	cmp.w	#999,d0
	bcc	DecodeFail
	move.w	6(a0),d1			; # = Size of Header
	cmp.w	#999,d0
	bcc	DecodeFail

	lsl.w	#2,d1				; * 4 (get longwords)
	move.l	a0,a1
	addq.w	#8,a0				; A0 = Start of Offsets
	add.w	d1,a1				; A1 = Start of Data

	lea	SoundList(pc),a2		; A2 = DEST  (List to Build)

	cmp.w	#MAXSAM,d0			; Allow 64 Samples Max, 1 Min
	bcs.s	1$
	moveq	#MAXSAM,d0
1$	subq.w	#1,d0
	bmi.s	DecodeFail

2$	move.l	a1,d1				; START + OFFSET = RAW DATA ADDRESS
	move.l	(a0)+,d2			; Delta Start
	move.l	(a0),d3				; Delta End
	add.l	d2,d1
	sub.l	d2,d3
	lsr.w	#1,d3

	move.l	d1,(a2)				; Start
	move.w	d3,4(a2)			; Length in words
	move.w	#214,6(a2)			; Frequency
	move.w	#64,8(a2)			; Volume
	move.w	#2,10(a2)			; Priority
	move.w	#0,12(a2)			; (pad)
	move.w	#0,14(a2)			; (pad)

	add.w	#16,a2				; Next Sample

	dbf	d0,2$
	moveq	#1,d0
	rts
DecodeFail
	moveq	#0,d0
	rts


; The Go Anywhere Loader
; You might find the following useful, its a forget-about-memory-management
; file loading subroutine, you just pass it the name and it returns to you
; a pointer to the start.  When exiting you call ReleaseFile (with the
; same pointer you got earlier) , which just frees up the memory that it used
; I finally decided that I needed a permanent utility like this...
;***************************************************************************
;***************************************************************************

LVODelay	equ -30-168
LVODeviceProc	equ -30-144
LVOSeek		equ -30-36
LVOOutput	equ -30-30
LVOInput	equ -30-24
LVOOpen		equ -30
LVOWrite	equ -30-18
LVORead		equ -30-12
LVOClose	equ -30-6
LVOLock		equ -$54
LVOUnLock	equ -$5A
LVOExamine	equ -$66
LVOOpenLibrary	equ -408
AllocMem	equ -30-168
FreeMem		equ -30-180
AllocAbs	equ -30-$cc

;***********************************************************************
;***********************************************************************
;
; GetFile, Load a file, return pointer to memory, this is a generic utility
;	Passed
;		A0 = File Name
;	Returns
;		A0 = Pointer to Start, (Save this Pointer for Close Down)
;
LoadScore
SoundGetHunk
GetFile
	move.l	a0,a3
	bsr	GetDosBase			; A6 = Dos Base *** 
	move.l	a3,d1				; D1 = File Name to Examine
	bsr.s	FindFileSize			; D0 = File Size
	beq.s	GetFailure
	addq.l	#4,d0				; +4 for internal header
	move.l	d0,a2				; A2 = Internal Size
	bsr	AllocateMemory			; Pass D0=FileSize, return D0=Mem
	beq.s	GetFailure
	move.l	a3,d1				; D1 = File Name to Load
	move.l	d0,a3				; A3 = Start
	move.l	a2,(a3)+			; A2 = Size (hide it for later freeing)
	bsr	LoadFile
	beq	ReleaseFile
	move.l	a3,a0				; Success
	moveq	#1,d0
GetFailure
	rts

;***********************************************************************
;
; ReleaseFile, generic utility to work in conjunction with GetFile
;	Passed
;		A1 = Pointer to GetFile Handle on file
ReleaseScore
SoundReleaseHunk
ReleaseFile
	move.l	-(a1),d0			; D0 = Get Header, Size to Release
	bsr	FreeMemory	
	moveq	#0,d0				; (return as failure always)
	rts
;***********************************************************************
AllocateMemory
						; D0 = Amount
	move.l	#2,d1				; chip only
	move.l	a6,-(a7)
	move.l	4,a6				; Exec base
	jsr	AllocMem(a6)
	move.l	(a7)+,a6
	tst.l	d0
	rts
;***********************************************************************
FreeMemory
						; A1 = Where, D0 = Amount
	move.l	a6,-(a7)
	move.l	4,a6
	jsr	FreeMem(a6)
	move.l	(a7)+,a6
	rts
;***********************************************************************
;***********************************************************************
;
; Returns A6 = DosBase (I *know* that this will never fail)
;
GetDosBase
	move.l	4,a6				; Open DOS.LIBRARY
	lea	DosName(pc),a1
	moveq	#0,d0				; (any rev #)
	jsr	LVOOpenLibrary(a6)
	move.l	d0,a6				; A6 = DOS.LIBRARY
	rts
;***********************************************************************
;***********************************************************************
;
; LoadFile, a generic utility
;	Passed
;		D1=NAME, A3=DEST, A2=SIZE
;	Returns
;		success=1/fail=0
;
LoadFile
	movem.l	d1/a2/a3,-(a7)
	bsr	GetDosBase
	movem.l	(a7)+,d1/a2/a3
						; D1 = Get FileName
	move.l	#1005,d2			; D2 = MODE_OLDFILE  LOAD
	jsr	LVOOpen(a6)			; Open the file
	move.l	d0,d4				; d4 is reserved as file ptr
	beq	LoadFailure
	move.l	a3,d2				; D2 = Start
	move.l	a2,d3				; D3 = Size
	move.l	d4,d1				; D1 = File Pointer
	jsr	LVORead(a6)
	move.l	d4,d1				; FP
	jsr	LVOClose(a6)			; Close File
	moveq	#1,d0				; Success
LoadFailure
	rts
;***********************************************************************
;***********************************************************************
;	D1=NAME, A3=SOURCE, A2=SIZE
SaveFile
	movem.l	d1/a2/a3,-(a7)
	bsr	GetDosBase
	movem.l	(a7)+,d1/a2/a3
	move.l	#1006,d2			; D2 = MODE_NEWFILE SAVE
	jsr	LVOOpen(a6)			; Open the file
	move.l	d0,d4				; d4 is reserved as file ptr
	beq	LoadFailure
	move.l	a3,d2				; D2 = Start
	move.l	a2,d3				; D3 = Size
	move.l	d4,d1				; D1 = File Pointer
	jsr	LVOWrite(a6)
	move.l	d4,d1				; FP
	jsr	LVOClose(a6)			; Close File
	moveq	#1,d0				; Success
	rts

;***********************************************************************
;***********************************************************************
;
;  Find the size of a file, a generic utility
;	Passed
;		D1 = Pointer to File Name (may have to be longword aligned)
;	Returns
;		D0 = Size of File or 0.
;
;LVOLock	equ -$54
;LVOUnLock	equ -$5A
;LVOExamine	equ -$66
;LVOOpenLibrary	equ -408
;

FindFileSize
						; D1 = File Name to Examine
	moveq	#-2,d2
	jsr	LVOLock(a6)			; D1=Name,D2=Mode
	move.l	d0,d4
	beq.s	Failure
	lea	FileInfoBlock(pc),a2

	move.l	a2,d0				; manually force long word alignment
	btst	#1,d0   			; (i'm not sure if this is necessary but...)
	beq.s	1$
	addq.w #2,a2
1$
	move.l	a2,d2
	move.l	d4,d1
	jsr	LVOExamine(a6)			; D1=Lock,D2=FIB
	move.l	d0,-(a7)
	move.l	d4,d1
	jsr	LVOUnLock(a6)			; D1=Lock
	move.l	(a7)+,d0
	beq.s	Failure
	move.l	$007C(a2),d0			; D0 = Size of File
	rts
Failure	moveq	#0,d0				; D0 = Failure
	rts

DosName:	dc.b	'dos.library',0
		even
;;;		cnop	0,4			; Longword Aligned 
;;;						; (actually this may load wrong)
FileInfoBlock	dcb.b	268+2,0

;***********************************************************************
;***********************************************************************


dumpmess
	movem.l	d0-d7/a0-a6,-(a7)
	moveq	#0,d3
	move.b	(a0)+,d3			; D3 = Length
	move.l	a0,d2				; D2 = What
	bsr	GetDosBase
;	jsr	LVOOutput(a6)			; Get LVOOutput Handle
;	move.l	d0,d1				; D1 = Output Handle
	move.l	_stdout(pc),d1
	beq.s	dumpnot
	jsr	LVOWrite(a6)			; LVOWrite
	movem.l	(a7)+,d0-d7/a0-a6
dumpnot
	rts

one	dc.b	64-24,'Sequencer attempting to allocate memory',10,0
	even
two	dc.b	64-24,'Sequencer attempting to load music.hunk',10,0
	even
three	dc.b	66-24,'Sequencer attempting to decode music.hunk',10,0
	even
four:	dc.b	58-24,'Sequencer loaded user music.score',10,0
	even
five:	dc.b	43-24,'Sequencer is happy',10,0
	even
six:	dc.b	54-24,'Sequencer saved "music.score"',10,0
	even
seven:	dc.b	60-24,'Sequencer looking for "music.score"',10,0
	end

; end of tetrissound.s
