
; LEZIONE14-5c.s  ** SUONA SAMPLE MOLTO LUNGHI **

; Nota: il sample usato e' meno lungo di 128k, per risparmiare spazio su
; disco. Provate la routine con un vostro sample piu' lungo di 128k!!

WLMB	macro
\@:	btst	#6,$bfe001
	bne.s	\@
	endm

WRMB	macro
\@:	btst	#10,$dff016
	bne.s	\@
	endm


	SECTION	PlayLongSamples,CODE

Start:
	bset	#1,$bfe001		;spegne il filtro passa-basso
					;>>>> PARAMETRI <<<<
	lea	sample,a0		;indirizzo sample
	move.l	#sample_end-sample,d0	;lunghezza sample in byte
	move.w	#21056,d1		;frequenza di lettura
	moveq	#64,d2			;volume

	moveq	#0,d3			;suona voce 0
	bsr.w	playlongsample_init
	WLMB
	moveq	#1,d3			;suona voce 1
	bsr.w	playlongsample_init
	WRMB
	moveq	#2,d3			;suona voce 2
	bsr.s	playlongsample_init
	WLMB
	moveq	#3,d3			;suona voce 3
	bsr.s	playlongsample_init
	WRMB
	moveq	#0,d3			;spegni voce 0 
	bsr.w	playlongsample_restore
	WLMB
	moveq	#1,d3			;spegni voce 1
	bsr.w	playlongsample_restore
	WRMB
	moveq	#2,d3			;spegni voce 2
	bsr.w	playlongsample_restore
	WLMB
	moveq	#3,d3			;spegni voce 3
	bsr.w	playlongsample_restore
	rts


***************************************
*****  Play Long Sample Routines  *****
***************************************

;	            _____________
;	         /\/ ____       (__
;	        /,_ /   ¬\ ____, \/
;	        \/ ______________/
;	        /   ¬(____/\__//
;	       / __ _       \  \
;	       \ \ \/\    ,  )  \
;	       /\ \   \_ _ _____/
;	      /    \   \ /\// /
;	     /    \ \   Y  / /
;	  __/   ¬_/\ \/\__/ /
;	 / ¬`----'¯¯\______/
;	/  __      __ \

; [a0=sample adr]
; [d0.l=lunghezza.b sample, d1.w=frequenza, d2.w=volume]
; [d3.w=voce (0..3)]

;* L'AutoVettore Lv4 IRQ deve essere disponibile *

Clock		equ	3546895
_LVOSupervisor	equ	-30
AFB_68010	equ	0
AttnFlags	equ	296

PlayLongSample_init:
	movem.l	d0-d7/a0-a1/a5-a6,-(sp)
	and.w	#3,d3			;canale 0-3
 	lea	$dff000,a6
	moveq	#%1,d4
	lsl.w	d3,d4
	move.w	d4,d6
	and.w	$2(a6),d4		;maschera DMA della voce
	move.w	#%1<<7,d5
	lsl.w	d3,d5
	move.w	d5,d7
	and.w	$1c(a6),d5		;maschera INT della voce
	add.w	d3,d3			;d3=d3*2: esprime offset di word
	lea	olddmas(pc),a1
	move.w	d4,(a1,d3.w)		;salva stato vecchio del DMA della voce
	lea	oldints(pc),a1
	move.w	d5,(a1,d3.w)		;salva stato vecchio del INT della voce
	move.w	d7,$9c(a6)		;azzera eventuali IRQ della voce
	move.w	d6,$96(a6)		;spegne DMA della voce
	move.w	d7,$9a(a6)		;spegne INT della voce
	sub.l	a1,a1			
	move.l	4.w,a6
	btst	#afb_68010,attnflags+1(a6)
	beq.s	.no010
	lea	.getvbr(pc),a5
	jsr	_LVOSupervisor(a6)
.No010:	cmp.l	#lv4irq,$70(a1)
	beq.s	.nochg
	move.l	$70(a1),oldlv4		;salva l'autovettore del livello 4
	move.l	#lv4irq,$70(a1)		;imposta il nuovo autovettore
.NoChg:	lsl.w	#4-1,d3			;d3=d3*8: ora esprime offset di 16 byte
	lea	$dff0a0,a6
	move.w	d2,$8(a6,d3.w)		;imposta AUDxVOL
	move.l	#clock,d2
	divu.w	d1,d2			;d2.w=clock/freq = periodo di camp.
	move.w	d2,$6(a6,d3.w)		;imposta AUDxPER
	lea	$dff000,a6
	move.w	d7,$9c(a6)
	or.w	#$8000,d7
	move.w	d7,$9a(a6)		;accende INT della voce
	lea	plsregs(pc,d3.w),a1
	movem.l	d0/a0,(a1)		;registri fissi
	movem.l	d0/a0,4*2(a1)		;registri di lavoro
	move.w	d7,$9c(a6)		;forza l'IRQ per cominciare...
	movem.l	(sp)+,d0-d7/a0-a1/a5-a6
	rts
.GetVBR:
	dc.l	$4e7a9801	;movec	vbr,a1	;base dei vettori di eccezione
	rte
;--------------------------------------
PLSRegs:	;DEVONO STARE TRA _INIT E _IRQ PER IL MODO DI INDIRIZZAMENTO
		;USATO: XX(pc,Rn) CHE CONSENTE SOLO 8 BIT CON SEGNO AD "XX"
PLSAud0Regs:	dc.l	0,0	;lunghezza,puntatore - fissi
		dc.l	0,0	;lunghezza,puntatore - variabili
PLSAud1Regs:	dc.l	0,0	;lunghezza,puntatore - fissi
		dc.l	0,0	;lunghezza,puntatore - variabili
PLSAud2Regs:	dc.l	0,0	;lunghezza,puntatore - fissi
		dc.l	0,0	;lunghezza,puntatore - variabili
PLSAud3Regs:	dc.l	0,0	;lunghezza,puntatore - fissi
		dc.l	0,0	;lunghezza,puntatore - variabili
;--------------------------------------
PlayLongSample_IRQ:
		;[d3.w=voce]
	movem.l	d0-d3/a0-a1/a6,-(sp)
	and.w	#3,d3			;canali 0-3
	move.w	d3,d2
	lsl.w	#4,d3			;d3=d3*16: esprime offset di 16 byte
	lea	plsregs(pc,d3.w),a1
	movem.l	4*2(a1),d0/a0		;grabba i registri di lavoro
	lea	$dff0a0,a6
	move.l	a0,$0(a6,d3.w)		;imposta AUDxLC
	move.l	d0,d1			;d1.l=lunghezza mancante
	and.l	#~(128*1024-1),d1	;mancano ancora piu' di 128 kB
	bne.s	.long			;se SI: vai a .long
	move.l	d0,d1			;se NO: usa lungh. mancante (< 128 kB)
.Long:	lsr.l	#1,d1			;trasforma la lungh. da suonare in WORD
	move.w	d1,$4(a6,d3.w)		;imposta AUDxLEN
	add.l	#128*1024,a0		;punta a0 al prossimo blocco
	sub.l	#128*1024,d0		;lunghezza MENO 128 kB
	bhi.s	.noloop			;d0 => 1 ? (manca ancora ALMENO 1 byte)
	movem.l	(a1),d0/a0		;se NO: reimposta registri originali
.NoLoop:movem.l	d0/a0,4*2(a1)		;salva comunque d0 e a0 nelle copie
	move.w	#%1<<7,d0
	lsl.w	d2,d0
	move.w	d0,$dff09c		;azzera IRQ della voce per non subire
					;un nuovo interrupt appena uscito
	moveq	#%1,d0
	lsl.w	d2,d0
	or.w	#$8000,d0
	move.w	d0,$dff096		;accende DMA della voce
	movem.l	(sp)+,d0-d3/a0-a1/a6
	rts
;--------------------------------------
PlayLongSample_restore:
		;[d3.w=voce (0..3)]
	movem.l	d0-d3/a0/a6,-(sp)
	and.w	#3,d3			;canali 0-3
 	lea	$dff000,a6
	moveq	#%1,d0
	lsl.w	d3,d0
	move.w	#%1<<7,d1
	lsl.w	d3,d1
	move.w	d0,$96(a6)		;spegne DMA della voce
	move.w	d1,$9a(a6)		;spegne INT della voce
	move.w	d1,$9c(a6)		;azzera eventuali IRQ della voce
	move.w	d0,-(sp)
	move.w	$1c(a6),d0
	and.w	#$0780,d0		;spente tutte le voci = ultima voce ?
	bne.s	.NoOFF
	sub.l	a0,a0			;se SI:...
	move.l	4.w,a6
	btst	#afb_68010,attnflags+1(a6)
	beq.s	.no010
	lea	.getvbr(pc),a5
	jsr	_LVOSupervisor(a6)
.No010:	move.l	oldlv4(pc),$70(a0)	;...reimposta il vecchio autovettore
.NoOFF:	move.w	(sp)+,d0
	lea	$dff000,a6
	add.w	d3,d3			;d3=d3*2: esprime offset di word
	move.w	oldints(pc,d3.w),d2
	and.w	d1,d2
	beq.s	.intoff
	or.w	#$8000,d1
.INTOFF:move.w	d1,$9a(a6)		;ripristina vecchio INT della voce
	move.w	olddmas(pc,d3.w),d2
	and.w	d0,d2
	beq.s	.dmaoff
	or.w	#$8000,d0
.DMAOFF:move.w	d0,$96(a6)		;ripristina vecchio DMA della voce
	movem.l	(sp)+,d0-d3/a0/a6
	rts
.GetVBR:
	dc.l	$4e7a8801	;movec	vbr,a0	;base dei vettori di eccezione
	rte
;--------------------------------------
OldINTs:dc.w	0,0,0,0
OldDMAs:dc.w	0,0,0,0
OldLv4:	dc.l	0


***************************************
*****  Level 4 Interrupt Handler  *****
***************************************

	cnop	0,8
Lv4IRQ:	
	movem.l	d0/d3,-(sp)
	link.w	a0,#0
.IRQLp:	pea	.irqlp(pc)
	move.w	$dff01e,d0
	and.w	$dff01c,d0
	moveq	#3,d3
	btst	#10,d0				;aud3 IRQ ?
	bne.w	playlongsample_irq		;se SI: bracha alla _IRQ
	moveq	#2,d3
	btst	#9,d0				;aud2 IRQ ?
	bne.w	playlongsample_irq
	moveq	#1,d3
	btst	#8,d0				;aud1 IRQ ?
	bne.w	playlongsample_irq
	moveq	#0,d3
	btst	#7,d0				;aud0 IRQ ?
	bne.w	playlongsample_irq
	unlk	a0
	movem.l	(sp)+,d0/d3
	rte

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

	SECTION	Sample,DATA_C

; Nota: il sample e' tratto da "NASP" by Pyratronik/IBB

Sample:	incbin	"assembler3:sorgenti8/carrasco.21056"
Sample_end:

	END


Questa volta la routine è il massimo della versatilità, in quanto ogni voce può
suonare sample molto lunghi indipendentemene dalle altre: l'handler d'interrupt
controlla ogni voce singolarmente; inoltre, qualsiasi voce può essere spenta
con la _RESTORE in qualsiasi momento senza corrompere in alcun modo le altre.
Unico neo: le routine di _INIT e di _IRQ sono relativamente lunghe, e
- soprattutto se avete una CPU lenta - se volete suonare lo stesso sample
a più voci, vi conviene usare il sorgente 5a od il 5b, piuttosto che lanciare
più volte questa "PlayLongSample", poichè potreste notare una sorta di eco,
dovuto al distacco delle chiamate.

N.B.:	questa volta l'handler di interrupt è un po' più complesso del normale
	in due punti: innanzitutto, con il PEA, pushamo nell stack l'indirizzo
	della label a cui l'RTS della _IRQ deve far ritorno (visto che si
	jumpa a quest'ultima con dei BNE), in modo che la CPU ricominci
	ogni volta a controllare tutte le voci in caso di interrupt (nel caso
	della voce appena attivata, appena acceso il DMA dalla _IRQ stessa, si
	genera subito un'altra IRQ perchè è stata appena cominciata la lettura
	di un blocco (il primo), per cui, appena tornati all'handler, è meglio
	ripetere la verifica, che uscire con l'RTE e poi tornare per richiesta
	di livello 4): in questo modo, si esce dall'handler solo quando
	effettivamente non vi è alcuna richiesta in attesa; in somma: facendo
	così, vegono soddisfatte tutte le richieste di TUTTE le voci
	eventualmente generate durante la permanenza nell'handler da parte
	della CPU.
	Come seconda novità, abbiamo l'utilizzo di una maschera tra richieste
	ed interrupt effettivamente abilitati sulla quale avvengono i BTST:
	l'interrupt di livello 4 viene generato da una qualsiasi delle voci,
	per cui, testando solo l'INTREQ sarebbe stato possibile che scattassero
	BNE di voci che in effetti avevano la richiesta di interrupt (per
	motivi vari, e più o meno misteriosi), ma realmente erano state chiuse
	con la _RESTORE.
	****** QUESTA TECNICA E' CONSIGLIATA PER QUALSIASI HANDLER DI
	INTERRUPT, POICHE' PUO' SEMPRE ACCADERE CHE AVVENGANO DELLE RICHIESTE
	EFFETTIVAMENE DISABILITATE, MA NELL'HANDLER DI LIVELLO COMUNE SI
	TESTI POSITIVAMENTE IL BIT NEL SOLO INTREQ ******
	Ad esempio, pensate di avere una copperlist che genera ogni tanto
	l'interrupt del copper, e di avere una routine di handler che testa
	sia COPER che VBLANK, mentre quest'ultimo è momentaneamente spento:
	cosa accade ? Ad ogni raster si imposta il bit VBLANK in INTREQ
	che, certo, non "spara" l'interrupt, interrupt che, però, è generato
	dal copper che fa entrare la CPU nell'handler di livello 3, nel quale
	esegue GIUSTAMENTE la routine per il copper, ma, testando anche il bit
	di VBLANK, esegue ERRONEAMENTE anche quella del vertical blank subito
	dopo, prima dell'RTE !
