#include <avr/io.h>

#include "sn76489.h"

; Stores the samples generated in the previous loop around the ISR.
.lcomm sample_left, 1
.lcomm sample_right, 1

; Number of clock cycles to run the emulated SN76489 for this frame (negative).
.lcomm sn76489_clock_step, 2

; Error in SN76489 clock cycles executed (accumulated across multiple samples).
.lcomm sn76489_cycle_error, 2

; Utility macro to provide labels to access the four SN76489 channels.
.macro sn76489_define_channel ch
.set sn76489_channel_\ch\()_period, (sn76489_channels + \ch * 6 + 0)
.set sn76489_channel_\ch\()_amplitude, (sn76489_channels + \ch * 6 + 2)
.set sn76489_channel_\ch\()_counter, (sn76489_channels + \ch * 6 + 3)
.set sn76489_channel_\ch\()_state, (sn76489_channels + \ch * 6 + 5)
.endm

; Define labels to access the four SN76489 channels.
sn76489_define_channel 0
sn76489_define_channel 1
sn76489_define_channel 2
sn76489_define_channel 3

; Updates a square wave tone channel.
.macro sn76489_update_tone_channel ch
	; Decrement the tone counter.
	lds YL,sn76489_channel_\ch\()_counter+0
	lds YH,sn76489_channel_\ch\()_counter+1
	lds ZL,sn76489_clock_step+0
	lds ZH,sn76489_clock_step+1
	add YL,ZL
	adc YH,ZH
	; Has it overflowed?
	brcs sn76489_channel_\ch\()_not_overflowed
	; It has overflowed!
	lds ZL,sn76489_channel_\ch\()_period+0
	lds ZH,sn76489_channel_\ch\()_period+1
	add YL,ZL
	adc YH,ZH
	; Invert the state of the channel.
	lds ZL,sn76489_channel_\ch\()_state
	com ZL
	sts sn76489_channel_\ch\()_state,ZL
sn76489_channel_\ch\()_not_overflowed:
	; Store the counter.
	sts sn76489_channel_\ch\()_counter+0,YL
	sts sn76489_channel_\ch\()_counter+1,YH
	; Is the channel active?
	lds ZL,sn76489_channel_\ch\()_state
	or ZL,ZL
	breq sn76489_channel_\ch\()_not_active
	
	; The channel is active.
	lds ZH,sn76489_channel_\ch\()_amplitude
	lds ZL,sn76489_stereo_mask
	
	; Add the amplitude for the left channel.
	lds YL,sample_left
	sbrc ZL,\ch+4
	add YL,ZH
	sts sample_left,YL
	
	; Add the amplitude for the right channel.
	lds YL,sample_right
	sbrc ZL,\ch+0
	add YL,ZH
	sts sample_right,YL
	
sn76489_channel_\ch\()_not_active:
.endm

.global TIMER1_COMPA_vect
TIMER1_COMPA_vect:
	; Preserve registers before generating samples.
	in r_sreg_save,_SFR_IO_ADDR(SREG)
	push ZL
	push ZH
	push YL
	push YH
	
	; Copy the samples generated in the previous loop to the output register.
	; This is done at the start of the ISR to ensure that new samples are written at 44100Hz,
	; as the emulation takes different amounts of time depending on how many channels are active.
	
	lds YL,sample_left
	sts _SFR_MEM_ADDR(OCR0A),YL
	
	lds YL,sample_right
	sts _SFR_MEM_ADDR(OCR0B),YL
	
	; Generate samples for this moment in time.
	
	; Start by clearing the sample accumulators.
	clr YL
	sts sample_left,YL
	sts sample_right,YL
	
	; Update the three regular tone channels.
	sn76489_update_tone_channel 0
	sn76489_update_tone_channel 1
	sn76489_update_tone_channel 2
	
	lds ZH,sn76489_channel_3_amplitude
	cpi ZH,0
	brne sn76489_channel_3_not_muted
	jmp sn76489_channel_3_not_active

sn76489_channel_3_not_muted:

	; Update the noise channel.
	; Decrement the tone counter.
	lds YL,sn76489_channel_3_counter+0
	lds YH,sn76489_channel_3_counter+1
	lds ZL,sn76489_clock_step+0
	lds ZH,sn76489_clock_step+1
	add YL,ZL
	adc YH,ZH
	; Has it overflowed?
	brcc sn76489_channel_3_overflowed
	jmp sn76489_channel_3_not_overflowed
	
	; It has overflowed!
sn76489_channel_3_overflowed:

	; Update the counter with its period.
	lds ZL,sn76489_channel_3_period+0
	andi ZL,3	
	breq sn76489_noise_0x10
	dec ZL
	breq sn76489_noise_0x20
	dec ZL
	breq sn76489_noise_0x40

sn76489_noise_c2:
	lds ZL,sn76489_channel_2_period+0
	lds ZH,sn76489_channel_2_period+1
	jmp sn76489_noise_updated_counter

sn76489_noise_0x40:
	ldi ZL,0x40
	clr ZH
	jmp sn76489_noise_updated_counter
	
sn76489_noise_0x20:
	ldi ZL,0x20
	clr ZH
	jmp sn76489_noise_updated_counter
	
sn76489_noise_0x10:
	ldi ZL,0x10
	clr ZH

sn76489_noise_updated_counter:

	; Add the period to he counter.
	add YL,ZL
	adc YH,ZH
	
	; Store the counter value.
	sts sn76489_channel_3_counter+0,YL
	sts sn76489_channel_3_counter+1,YH
	
	; Noise channel operates at half the frequency of the others, so check its update flag.
	lds YL,sn76489_noise_update
	com YL
	sts sn76489_noise_update,YL
	brne sn76489_generate_noise 
	jmp sn76489_channel_3_output_sample
	
sn76489_generate_noise:
	; Are we generating periodic noise or white noise?
	lds YL,sn76489_channel_3_period+0
	bst YL,2
	brts sn76489_noise_white

sn76489_noise_periodic:
	; Generating periodic noise.
	
	lds YL,sn76489_shift_register_width
	cpi YL,16

	lds YL,sn76489_shift_register+0
	lds YH,sn76489_shift_register+1
	
	breq sn76489_noise_periodic_16

sn76489_noise_periodic_15:

	lsr YH
	ror YL

	brcc sn76489_noise_periodic_15_no_carry

	ori YH,0x40

sn76489_noise_periodic_15_no_carry:

	; Store the updated shift register.
	sts sn76489_shift_register+0,YL
	sts sn76489_shift_register+1,YH

	; We're done.
	jmp sn76489_channel_3_output_sample

sn76489_noise_periodic_16:
	
	lsr YH
	ror YL
	
	brcc sn76489_noise_periodic_16_no_carry
	
	ori YH,0x80

sn76489_noise_periodic_16_no_carry:
	
	; Store the updated shift register.
	sts sn76489_shift_register+0,YL
	sts sn76489_shift_register+1,YH

	; We're done.
	jmp sn76489_channel_3_output_sample
	
sn76489_noise_white:

	; Generate white noise.
	
	push XL
	push XH
	
; "Tap" a single bit when producing white noise.
.macro sn76489_tap_bit
	; Y contains the tapped bits.
	lsr YH
	ror YL
	brcc sn76489_bit_not_tapped_\@
	; Z contains the shift register.
	lsr ZH
	ror ZL
	; XL contains the parity.
	adc XL,XH
	jmp sn76489_bit_tapped_\@
sn76489_bit_not_tapped_\@\():
	lsr ZH
	ror ZL
sn76489_bit_tapped_\@\():
.endm
	
	clr XL
	clr XH
	
	lds YL,sn76489_tapped_bits+0
	lds YH,sn76489_tapped_bits+1

	lds ZL,sn76489_shift_register+0
	lds ZH,sn76489_shift_register+1

	; Handle all sixteen possibile tapped bits.
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	sn76489_tap_bit
	
	mov ZL,XL
	
	pop XH
	pop XL
	
	; How wide is the shift register?
	lds YL,sn76489_shift_register_width
	cpi YL,16

	lds YL,sn76489_shift_register+0
	lds YH,sn76489_shift_register+1
	
	breq sn76489_noise_white_16

sn76489_noise_white_15:

	lsr YH
	ror YL
	
	lsr ZL
	brcc sn76489_noise_white_15_no_carry

	ori YH,0x40

sn76489_noise_white_15_no_carry:

	; Store the updated shift register.
	sts sn76489_shift_register+0,YL
	sts sn76489_shift_register+1,YH

	; We're done.
	jmp sn76489_channel_3_output_sample

sn76489_noise_white_16:
	
	lsr YH
	ror YL
	
	lsr ZL
	brcc sn76489_noise_white_16_no_carry
	
	ori YH,0x80

sn76489_noise_white_16_no_carry:

	; Store the updated shift register.
	sts sn76489_shift_register+0,YL
	sts sn76489_shift_register+1,YH

	; We're done.
	jmp sn76489_channel_3_output_sample

sn76489_channel_3_not_overflowed:
	sts sn76489_channel_3_counter+0,YL
	sts sn76489_channel_3_counter+1,YH

sn76489_channel_3_output_sample:

	; Is the channel active?
	lds ZL,sn76489_shift_register+0
	andi ZL,0x01
	brne sn76489_channel_3_not_active
	
	; The channel is active.
	lds ZH,sn76489_channel_3_amplitude
	lds ZL,sn76489_stereo_mask
	
	; Add the amplitude for the left channel.
	lds YL,sample_left
	sbrc ZL,3+4
	add YL,ZH
	sts sample_left,YL
	
	; Add the amplitude for the right channel.
	lds YL,sample_right
	sbrc ZL,3+0
	add YL,ZH
	sts sample_right,YL
	
sn76489_channel_3_not_active:
	
	; If sample_time > 0, decrement it (used for timing).	
	lds YL,vgm_sample_timer+0
	lds YH,vgm_sample_timer+1
	
	mov ZL,YL
	or ZL,YH
	breq sample_timer_zero
	
	ldi ZL,-1
	add YL,ZL
	adc YH,ZL
	
	sts vgm_sample_timer+0,YL
	sts vgm_sample_timer+1,YH

sample_timer_zero:

	; Calculate the ClockStep
	
	
	ldi YH,-1
	sts sn76489_clock_step+1,YH
	
	lds YL,sn76489_cycle_error+0
	lds YH,sn76489_cycle_error+1
	
	lds ZL,sn76489_frequency+1
	lds ZH,sn76489_frequency+2
	
	sub YL,ZL
	sbc YH,ZH
	
	ldi ZL,lo8(2762)
	ldi ZH,hi8(2762)
	
	push ZL
	
	clr ZL
	
calculate_clock_step_loop:
	dec ZL
	add YL,ZL
	adc YH,ZH
	brmi calculate_clock_step_loop	
	
	sts sn76489_clock_step+0,ZL
	
	pop ZL
	
	sts sn76489_cycle_error+0,YL
	sts sn76489_cycle_error+1,YH	
	
	; Restore registers.
	pop YH
	pop YL
	pop ZH
	pop ZL
	out _SFR_IO_ADDR(SREG),r_sreg_save
	reti