;-------------------------------------------------------------------------------------------------------------------------
;Phil's SX28-based sound card. V0.09
;Freq reg = Desired freq / 0.099730491  <-Constant derived from channel IRQs per sec / (65536 * 16)

;Notes:
;Digital filter not yet tested

;------------------------------------------------------------------------------------------------------------------------
;Chip Config......

DEVICE		SX28AC,TURBO,BANKS8,OSCHS3,SYNC,OPTIONX,IFBD
		
		;chip model, 1 instruction per clock, standard memory size
		;High Speed (external) oscillator, Synchronize inputs internally
		;Use standard 8 level stack, disable internal oscillator resistor
		;See docs for other settings

;--------------------------------------------------------------------------------------------------------------------------

RESET		initialize	;address to jump to on reset / power up

;-------------------------------------------------------------------------------------------------------------------------
IRC_CAL		IRC_SLOW	;not necessary - just stops a SASM warning
;-------------------------------------------------------------------------------------------------------------------------
FREQ		64_000_000	;not necessary - for SXsim debugging only
;-------------------------------------------------------------------------------------------------------------------------

;Some useful macros

SKIPIFZERO	macro
		SB status.2
		endm

SKIPNOTZERO	macro
		SNB status.2
		endm

SKIPONCARRY	macro
		SB status.0
		endm

SKIPNOCARRY	macro
		SNB status.0
		endm

CLR_CF		macro
		CLRB status.0
		endm

SET_CF		macro
		SETB status.0
		endm

WAIT1CPUCYCLE	macro
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		nop
		endm

;---------------------------------------------------------------------------------------------------------------------

;Port A Pins:
;------------

New_Data_Detect		equ 0
Data_Addr_Select 	equ 1
Clear_busy_pulse	equ 2
IRQ_flipflop_clk	equ 3


;Global registers
;----------------

mixed_outputmsb		equ $8
mixed_outputlsb 	equ $9
temp			equ $a
channel_FSR		equ $b
temp2			equ $c
filter_delta		equ $d
next_DAC_value		equ $e
settings		equ $f

;Bits in global reg $f
;---------------------

filter_on_off		equ 0


;Per channel registers
;----------------------

freq_lo			equ $10			;repeated 1 bank per channel
freq_hi			equ $11			;so at $10,$30,$50,$70
volume			equ $12			;
pulse_width		equ $13			;
control			equ $14			;

output  		equ $15			;
osc_lo			equ $16			;
osc_hi    		equ $17			;
osc_carry		equ $18			;

old_osc_carry		equ $19			;
signed_scale		equ $1a			;
sample_addr		equ $1b			;
randoml			equ $1c			;
randomm			equ $1d			;
randomh			equ $1e			;

;------------------------------------------------------------------------------------------------------------------------
; one irq per channel, mixed output updated every forth irq, output offset each irq if filter enabled
;-----------------------------------------------------------------------------------------------------------------------

irq_code:	mov w,#256-148			;1	IRQ interval is 2nd number + 5 
		mov RTCC,w			;1      execution time: 31 cycle: chan 0,  11 cycles chans 1-3
		
		mov w,#$40			;1
		add channel_FSR,w		;1
		clr_cf				;1
		mov w,>>channel_FSR		;1
		mov FSR,w			;1
		skipifzero			;2/1
		jmp accumulate			;0/3
			
		mov w,next_DAC_value		;1  - back to channel 0 so...
		mov RC,w			;1  - present previous cached value to DAC (Port C)
		
		rr mixed_outputmsb		;1 - shift 10 bit mix sum down to 8 bit output
		rr mixed_outputlsb		;1
		rr mixed_outputmsb		;1
		rr mixed_outputlsb		;1
		mov w,#$80			;1 - convert output back to 0-$ff range
		add w,mixed_outputlsb		;1
		mov next_DAC_value,w		;1	
		clr mixed_outputmsb		;1
		clr mixed_outputlsb		;1

		mov w,RC-w			;1 - calculate filter delta
		mov filter_delta,w		;1
		rr filter_delta			;1 - divide by 2
		mov w,>>filter_delta		;1 - divide by 2 again
		and w,#%00111111		;1 - 
		snb filter_delta.6		;2 - check sign (at divided by two stage) 
		or w,#%11000000			;0 - maintain negetive polarity if required 
		mov filter_delta,w		;1
		jmp no_f_delta			;3  - no filter delta this irq as output has a REAL vlaue 

;--------------------------------------------------------------------------------------------------------------------------

accumulate:	mov w,filter_delta		;1
		snb settings.filter_on_off	;2
		sub RC,w			;0		

no_f_delta:	mov w,osc_carry			;1 	- add 16 bit freq to 16 bit accumulator 
		mov old_osc_carry,w		;1
		mov w,freq_lo			;1
	        add osc_lo,w			;1
        	mov w,freq_hi			;1
        	skipnocarry			;2
         	movsz w,++freq_hi		;0/2
         	add osc_hi,w			;1/0
        	skipnocarry			;2
		inc osc_carry			;
	
;------------------------------------------------------------------------------------------------------------------------
		
		mov w,control			;1 - get waveform type			- 8 cycles total
		and w,#7			;1 - range 0-7 only
		jmp pc+w			;6 - jump to apt code
		jmp silence			;ctrl = 0
		jmp sawtooth			;ctrl = 1 
		jmp triangle			;ctrl = 2 
		jmp pulse			;ctrl = 3 
		jmp samples_irq			;ctrl = 4
		jmp samples_simple		;ctrl = 5
		jmp whitenoise			;ctrl = 6      
		jmp op_end			;ctrl = 7
		
;-----------------------------------------------------------------------------------------------------------------------

silence:	clr output			; centre = 0
		jmp op_mix

;-----------------------------------------------------------------------------------------------------------------------

sawtooth:	mov w,<>osc_carry		;1 - sawtooth code = 9 cycles + scale
		and w,#%11110000
		mov output,w
		mov w,<>osc_hi
		and w,#%00001111
		or output,w
		jmp op_scale

;-----------------------------------------------------------------------------------------------------------------------
	
triangle:	mov w,<>osc_carry		;2 - triangle code = 13 cycles + scale
		and w,#%11110000
		mov output,w
		mov w,<>osc_hi
		and w,#%00001111
		or output,w
		snb output.7
		not output
		CLR_CF
		rl output
		jmp op_scale

;-----------------------------------------------------------------------------------------------------------------------
	
pulse:		mov w,<>osc_carry		;1 - pulse wave code = 15 cycles total + scale
		and w,#%11110000
		mov output,w
		mov w,<>osc_hi
		and w,#%00001111
		or output,w
		mov w,output
		mov w,pulse_width-w		;1
		mov w,#$0			;1
		skipnocarry			;2
		mov w,#$ff			;
		mov output,w			;1
		jmp op_scale			;3 

;-----------------------------------------------------------------------------------------------------------------------

samples_irq:	mov w,old_osc_carry		;1	- sampled sound 
		mov w,osc_carry-w		;1	- only need a new sample if there was an osc_carry
		skipnotzero			;1/2  
		jmp op_mix			;3/0 

		mov w,FSR			;1
		mov temp,w			;1
		mov w,sample_addr		;1
		mov FSR,w			;1
		mov w,INDF			;1
		mov temp2,w			;1
		mov w,temp			;1
		mov FSR,w			;1
		mov w,temp2			;1
		mov output,w			;1

		inc sample_addr			;1
		setb sample_addr.4		;1
		mov w,sample_addr		;1
		xor w,#$10			;1
		skipnotzero			;1/2
		jmp sample_irq0			;3
		mov w,sample_addr		;1
		xor w,#$d0			;1
		skipifzero			;1/2
		jmp op_scale			;3	- 24 cycles if no irq issued

sample_irq1:	setb RA.IRQ_flipflop_clk	;1	- sample address now = $d0 (so buffer 0 can be refilled)
		jmp op_scale			;3      - set f/f and leave line high (irq=1)  26 cycles on irq 1
		
sample_irq0:	mov w,RA			;1	
		and w,#%0110			;1	- clear irq f/f line
		mov RA,W			;1
		setb sample_addr.7		;1	- set sample address to $90 (so buffer 1 can be refilled)
		or w,#%1000			;1	- set irq f/f line
		mov RA,w			;1
		and w,#%0110			;1	- clear irq f/f line again (so Z80 knows its irq 0)
		mov RA,W			;1
		jmp op_scale			;3	- 32 cycles on irq 0 issued


;---------------------------------------------------------------------------------------------------------------------

samples_simple:	mov w,#$ff			; 	just re-write the volume register, 5 cycles + scale
		mov output,w			;
		jmp op_scale			;

;-----------------------------------------------------------------------------------------------------------------------

whitenoise:	mov w,old_osc_carry		;1	white noise = 24 cycles + scale
		mov w,osc_carry-w		;1	only need a new value if there was an osc_carry
		skipnotzero			;1/2
		jmp op_mix			;    

		mov w,randoml			;1 	- use linear feedback shift reg for make a random number in W
		or w,randomh			;1
		skipnotzero			;2
		not randomh		
		mov w,randoml			;1
		snb randoml.6			;2
		xor w,#1			; 
		snb randoml.4			;2
		xor w,#1			; 
		snb randoml.3			;2
		xor w,#1			;
		mov temp,w			;1
		clr_cf				;1
		rr randomh			;1
		rr randomm			;1
		rr randoml			;1
		snb temp.0			;2
		setb randomh.7			;
		mov w,randoml			;1				
		mov output,w			;1

;---------------------------------------------------------------------------------------------------------------------

op_scale:	mov w,#$80			;1 - convert to "centre zero" (+127 0 -128) sample   (scale = 43 cycles)
		mov w,output-w			;1 
		mov signed_scale,w		;1  -  note sign polarity of sample 
		snb signed_scale.7		;2
		not w				;
	
		clr_cf				;1  -  scale w based on the the volume setting
		clr temp			;1
		clr output			;1

		snb volume.0			;2
		add output,w			;
		rr output			;1
		rr temp				;1
		snb volume.1			;2
		add output,w			;
		rr output			;1
		rr temp				;1
		snb volume.2			;2
		add output,w			;
		rr output			;1
		rr temp				;1
		snb volume.3			;2
		add output,w			;
		rr output			;1
		rr temp				;1
		snb volume.4			;2
		add output,w			;
		rr output			;1
		rr temp				;1		
		snb volume.5			;2
		add output,w			;
		rr output			;1
		rr temp				;1
		snb volume.6			;2
		add output,w			;
		rr output			;1
		rr temp				;1
		snb volume.7			;2
		add output,w			;
		rr output			;1		
		
		snb signed_scale.7		;2  - restore polarity of result..
		not output			;
		snb signed_scale.7		;2  - with a two's compliment (prevents 00 -> ff)
		inc output			;

;----------------------------------------------------------------------------------------------------------------------
	
op_mix:		snb output.7			;1/2  - test polarity of output		(mix = 21 cycles max)
		jmp submix			;3/0

		mov w,output			;1 - add this value to main output
		add mixed_outputlsb,w		;1
		skipnocarry			;2
		inc mixed_outputmsb		;
op_end:		mov w,RTCC			;1
		and w,#3			;1
		jmp pc+w			;3
		nop				;1
		nop				;1
		nop				;1
		nop				;1
		reti				;3

submix:		mov w,--output			;1 - sub this value from main output
		not w				;1
		sub mixed_outputlsb,w		;1
		skiponcarry			;2
		dec mixed_outputmsb		;
		mov w,RTCC			;1
		and w,#3			;1
		jmp pc+w			;3
		nop				;1
		nop				;1
		nop				;1
		nop				;1
		reti				;3
	
;-----------------------------------------------------------------------------------------------------------------------
;-----------------------------------------------------------------------------------------------------------------------
;-----------------------------------------------------------------------------------------------------------------------

initialize:	mov w,#%1			;set Port A bit 0 to input 
		mov !RA,w			
		mov w,#$ff			;set all port b pins to inputs 
		mov !RB,w
		mov w,#$00			;set all port c pins to outputs 
		mov !RC,w
		
		mov w,#$00			;set all port outputs low
		mov ra,w			
		mov rb,w
		mov rc,w
		
		mov m,#$0d			;mode $0d = adjust voltage levels
		mov !ra,w
		mov !rb,w
		mov !rc,w
	
		clr $8				;clear working regsiters
		clr $9
		clr $a
		clr $b
		clr $c
		clr $d
		clr $e
		clr $f

		clr FSR				;clear all SX SRAM registers
clr_reg_loop:	setb FSR.4
		clr INDF
		incsz FSR
		jmp clr_reg_loop

		mov w,#$90
		mov FSR,w			;set samples in sram to $80 (centred) non-signed
		mov w,#$80
clr_samp_loop:	setb FSR.4
		mov INDF,w
		incsz FSR
		jmp clr_samp_loop

		mov w,#%10011111		;RTCC counts on instruction clock, no prescale
		mov !OPTION,w			;RTCC rollover interrupts enabled
		
		mov w,#$c0
		mov channel_FSR,w
		mov w,#$80
		mov next_DAC_value,w

		setb RA.Clear_busy_pulse	;clear any pending data
		WAIT1CPUCYCLE
		clrb RA.Clear_busy_pulse	

;-----------------------------------------------------------------------------------------------------------------------

		bank $10
		mov w,#$00			;init values channel 0
		mov freq_lo,w	
		mov w,#$00
		mov freq_hi,w			
		mov w,#$80
		mov pulse_width,w		
		mov w,#$00
		mov volume,w			
		mov w,#$0
		mov control,w			
		mov w,#$90
		mov sample_addr,w
		mov w,#$1e			
		mov randoml,w
		mov w,#$ac
		mov randomm,w
		mov w,#$47
		mov randomh,w

		bank $30
		mov w,#$00			;init values channel 1
		mov freq_lo,w	
		mov w,#$00
		mov freq_hi,w			
		mov w,#$80
		mov pulse_width,w		
		mov w,#$0
		mov volume,w			
		mov w,#$0
		mov control,w	
		mov w,#$90
		mov sample_addr,w
		mov w,#$4e			
		mov randoml,w
		mov w,#$67
		mov randomm,w
		mov w,#$b9
		mov randomh,w

		bank $50
		mov w,#$00			;init values channel 2
		mov freq_lo,w	
		mov w,#$00
		mov freq_hi,w			
		mov w,#$80
		mov pulse_width,w		
		mov w,#$0
		mov volume,w			
		mov w,#$0
		mov control,w	
		mov w,#$90
		mov sample_addr,w
		mov w,#$9f			
		mov randoml,w
		mov w,#$b2
		mov randomm,w
		mov w,#$13
		mov randomh,w

		bank $70
		mov w,#$00			;init values channel 3
		mov freq_lo,w	
		mov w,#$00
		mov freq_hi,w			
		mov w,#$80
		mov pulse_width,w		
		mov w,#$0
		mov volume,w			
		mov w,#$0
		mov control,w	
		mov w,#$90
		mov sample_addr,w
		mov w,#$33			
		mov randoml,w
		mov w,#$96
		mov randomm,w
		mov w,#$25
		mov randomh,w

		bank $10

;-----------------------------------------------------------------------------------------------------------------------
;background code
;-----------------------------------------------------------------------------------------------------------------------

waiting:	sb RA.New_Data_Detect		;1
		jmp waiting			;3

		clrb RA.Data_Addr_Select	;1	switch addr latch on
		WAIT1CPUCYCLE			;8	- wait 125ns - ensure new data has been latched
		WAIT1CPUCYCLE			;8	- wait 125ns - and is ready for output
		WAIT1CPUCYCLE			;8	- wait 125ns - these days are critical!

		mov w,RB			;1	read port B to get address of updated register
		mov FSR,w			;1	stash address in FSR

		setb RA.Data_Addr_Select	;1	switch data latch on  
		WAIT1CPUCYCLE			;8	allow time for signals to propagate

		mov w,RB			;1	read in data to write to selected register
		mov INDF,w			;1      update the preselected SX register
		
		setb RA.Clear_busy_pulse	;1	"clear busy" clock pulse = goes high: edge trigger
		WAIT1CPUCYCLE			;8      allow time for signals to propagate etc
		clrb RA.Clear_busy_pulse	;1	
		jmp waiting			;3	

;---------------------------------------------------------------------------------------------------------------------------
