; ATtiny2313 project
.INCLUDE "tn2313def.inc"

; --------------------
; Register definitions
; --------------------

.DEF	State	= R8	; State register

.DEF	SrSave	= R9

.DEF	Tone1H	= R10
.DEF	Tone1L	= R11
.DEF	Tone2H	= R12
.DEF	Tone2L	= R13

.DEF	ToneTH	= R14
.DEF	ToneTL	= R15

.DEF	Temp	= R17

.DEF	Temp1 	= R18
.DEF	Temp2	= R19
.DEF	Temp3	= R20
.DEF 	Temp4	= R21

.DEF	ADDRH	= R22
.DEF 	ADDRL	= R23

.DEF	RxByte	= R24
.DEF	BitCnt	= R25

; -------------------
; State register bits
; -------------------

.EQU BitParity = 0
.EQU BitNoteOff = 1

; ------
; Voices
; ------

.EQU Voice1 = PB3
.EQU Voice2 = PB4

; -----------------
; MACRO definitions
; -----------------

; OUTI port, value :: send value to port
.MACRO OUTI
	ldi R16,@1 			
	out @0,R16
.ENDM

; MEMR register, (location) :: read memory to register
.MACRO MEMR
	lds @0, @1
.ENDM

; MEMW (location), register :: write register to memory
.MACRO MEMW
	sts @0, @1
.ENDM

; MEMWI (location), value :: write value to memory
.MACRO MEMWI
	ldi R16, @1
	sts @0, R16
.ENDM	

; ------------
; SRAM Mapping
; ------------

.DSEG

; Synth has 2 voices
voices:
	
	.BYTE 2

; ------------------
; Program code start
; ------------------

.CSEG
.ORG 0x0000

; -----------------
; Interrupt vectors
; -----------------

rjmp Init ; Reset Handler
reti ;RJMP INT0 ; External Interrupt0 Handler
reti ;RJMP INT1 ; External Interrupt1 Handler
reti ;RJMP TIM1_CAPT ; Timer1 Capture Handler
rjmp TIMER1_COMPA ; Timer1 CompareA Handler
reti ;RJMP TIM1_OVF ; Timer1 Overflow Handler
reti ;RJMP TIM0_OVF ; Timer0 Overflow Handler
reti ;RJMP USART0_RXC ; USART0 RX Complete Handler
reti ;RJMP USART0_DRE ; USART0,UDR Empty Handler
reti ;RJMP USART0_TXC ; USART0 TX Complete Handler
reti ;RJMP ANA_COMP ; Analog Comparator Handler
rjmp PCINT ; Pin Change Interrupt
rjmp TIMER1_COMPB ; Timer1 Compare B Handler
reti ;rjmp TIMER0_COMPA ; Timer0 Compare A Handler
reti ;RJMP TIMER0_COMPB ; Timer0 Compare B Handler
reti ;RJMP USI_START ; USI Start Handler
reti ;RJMP USI_OVERFLOW ; USI Overflow Handler
reti ;RJMP EE_READY ; EEPROM Ready Handler
reti ;rjmp WDT_OVERFLOW ; Watchdog Overflow Handler


; ------------------
; Main program start
; ------------------

Init:

	; Set inputs/outputs
	; (initially disable square wave output)
	outi DDRB, (1<<PB0) | (0<<PB3) | (0<<PB4) | (0<<PB5) | (0<<PB6)

	; Set stack
	outi SPL, low(RAMEND)
	
	; LED = ON
	sbi PORTB, 0
	
	; Initialize hardware features
	rcall Timer1_Init
	rcall Pcint_Init

	; Clear voices
	memwi voices, 0
	memwi voices+1, 0

	; Set timer interrupts
	outi TIMSK, (1<<OCIE1A) | (1<<OCIE1B)

	; Clear bit counter
	clr BitCnt

	; Clear state register
	clr State

	; Enable interrupts
	sei

back2:

	rjmp back2

; --------------------------
; Peripherals initialization
; --------------------------

; Initialize timer1
Timer1_Init:

	; Set toggle on compare
	outi TCCR1A, (1<<COM1A0) | (1<<COM1B0) | (0<<WGM10)

	; Enable timer with /8 prescaler
	outi TCCR1B, (2<<CS10)
	
	ret

; Initialize PCINT feature
; (interrupt on pin change)
Pcint_Init:

	; Set interrupt on pin change
	outi GIMSK, (1<<PCIE)
	outi PCMSK, (1<<PCINT5)

	ret

; ------------------
; Pin change handler
; ------------------
PCINT:

	cli

	in SrSave, SREG
	push SrSave
	
	; If Kbd CLK is low
	sbis PINB, PB5
	rcall RxKbd

	pop SrSave
	out SREG, SrSave

	sei

	reti

RxKbd:

	; Clear T
	clt

	; If PB6 set, store in T
	sbic PINB, PB6
	set

	; Check if this is first bit
	cpi BitCnt, 0
	breq firstBit
	
	; Check parity
	cpi BitCnt, 9
	breq parityCheck

	; Check if this is last bit
	cpi BitCnt, 10
	breq rxFinished

	; Store bit
	lsr RxByte
	bld RxByte, 7

	; Store parity bit
	clr Temp1
	bld Temp1, BitParity
	eor State, Temp1

RxBitDone:
	; Increment bit counter
	inc BitCnt

	ret

firstBit:
	
	; Clear byte
	clr RxByte

	; Clear parity bit
	ldi R16, 0xFE
	and State, R16

	rjmp RxBitDone

parityCheck:
	
	; Store parity bit
	clr Temp1
	bld Temp1, BitParity
	eor State, Temp1

	rjmp RxBitDone

rxFinished:

	; Parity check should give 1 (odd parity)
	sbrs State, BitParity
	rjmp discardByte
	
	; Clear bit counter
	clr BitCnt
	
	; Call byte handler
	rcall handleByte

	ret

discardByte:
	
	; Clear variables
	clr BitCnt
	clr RxByte

	ret

handleByte:

	; Check for note off message
	cpi RxByte, 0xF0
	breq setNextNoteOff
	
	; Check for global note off
	cpi RxByte, 0x76
	breq doAllNoteOff

	ldi Temp, 0

	ldi ZH, high(scanCodes*2)
	ldi ZL, low(scanCodes*2)

back3:

	lpm

	cp RxByte, R0
	breq foundCode

	inc Temp
	adiw ZL, 1

	cpi Temp, 37
	brne back3

	; Note not found

	; Clear note off bit
	ldi R16, ~(1<<BitNoteOff)
	and State, R16

	ret

foundCode:

	inc Temp
	
	; Shift octave higher
	subi Temp, -12

	mov Temp2, Temp

	; Check note off flag
	sbrc State, BitNoteOff
	rjmp doNoteOff
	
	; Look for free voice
	
	; Check if note already used on ch2
	memr Temp, voices+1
	cp Temp, Temp2
	breq noVoiceChange
	
	memr Temp, voices
	
	; Check if note already used
	cp Temp, Temp2
	breq noVoiceChange
	
	; Check if voice is free
	cpi Temp, 0
	brne checkCh2
	
	; Set on channel 1
	memw voices, Temp2
	rcall setCh1
	ret

checkCh2:

	; Check if note already used on ch1
	memr Temp, voices
	cp Temp, Temp2
	breq noVoiceChange

	memr Temp, voices+1

	; Check if note already used
	cp Temp, Temp2
	breq noVoiceChange

	; Check if voice is free
	cpi Temp, 0
	brne noVoiceChange
	
	; Set on channel 2
	memw voices+1, Temp2
	rcall setCh2

noVoiceChange:

	ret

doAllNoteOff:

	memwi voices, 0
	rcall setCh1
	
	memwi voices+1, 0
	rcall setCh2

	ret

setNextNoteOff:
	
	; Set note off bit
	ldi R16, (1<<BitNoteOff)
	or State, R16
	ret

doNoteOff:
	
	; Clear note off bit
	ldi R16, ~(1<<BitNoteOff)
	and State, R16

	memr Temp, voices

	cp Temp2, Temp
	brne tryOffCh2
	
	memwi voices, 0
	rcall setCh1

	ret

tryOffCh2:
	
	memr Temp, voices+1

	cp Temp, Temp2
	brne noNoteOff

	memwi voices+1, 0
	rcall setCh2

noNoteOff:

	; Not found
	ret

; -------------------------------------
; Functions for tone and melody control
; -------------------------------------

; Temp1 holds note value

; -------------
; Set channel 1
; -------------
setCh1:
	
	memr Temp1, voices

	; If pause don't load tone
	cpi Temp1, 0
	breq isPause1

	dec Temp1
	
	ldi ADDRH, high(tblNotes*2)
	ldi ADDRL, low(tblNotes*2)
	rcall getVals

	; Get tone value
	mov Tone1H, Temp3
	mov Tone1L, Temp4

	in ToneTH, OCR1AH
	in ToneTL, OCR1AL
	
	add ToneTL, Tone1L
	adc ToneTH, Tone1H

	out OCR1AH, ToneTH
	out OCR1AL, ToneTL

	; Set output for channel 1
	sbi DDRB, voice1

	rjmp noPause1
	
isPause1:
	
	; Disable output for channel 1
	cbi DDRB, voice1

noPause1:

	; Return to caller
	ret

; -------------
; Set channel 2
; -------------
setCh2:
	
	memr Temp1, voices+1

	; If pause don't load tone
	cpi Temp1, 0
	breq isPause2

	dec Temp1
	
	ldi ADDRH, high(tblNotes*2)
	ldi ADDRL, low(tblNotes*2)
	rcall getVals

	; Get tone value	
	mov Tone2H, Temp3
	mov Tone2L, Temp4
	
	in ToneTH, OCR1BH
	in ToneTL, OCR1BL
	
	add ToneTL, Tone2L
	adc ToneTH, Tone2H

	out OCR1BH, ToneTH
	out OCR1BL, ToneTL

	; Set output for channel 2
	sbi DDRB, voice2

	rjmp noPause2
	
isPause2:
	
	; Disable output for channel 2
	cbi DDRB, voice2

noPause2:

	; Return to caller
	ret

; -------------------------------
; Load values from program memory
; -------------------------------

getVals:
	
	mov ZH, ADDRH
	mov ZL, ADDRL

	lsl Temp1

	clr R16
	add ZL, Temp1
	adc ZH, R16

	lpm
	mov Temp4, R0

	adiw ZL, 1
	lpm
	mov Temp3, R0

	ret

; ----------------------
; Tone control channel 1
; ----------------------

TIMER1_COMPA:
	
	; Disable interrupts
	cli
	
	; Save SREG
	in SrSave, SREG

	; Set next 1/2 * period
	in ToneTH, OCR1AH
	in ToneTL, OCR1AL
	add ToneTL, Tone1L
	adc ToneTH, Tone1H
	out OCR1AH, ToneTH
	out OCR1AL, ToneTL
	
	; Restore SREG
	out SREG, SrSave
	
	; Enable interrupts
	sei

	reti

; ----------------------
; Tone control channel 2
; ----------------------

TIMER1_COMPB:
	
	cli

	in SrSave, SREG

	in ToneTH, OCR1BH
	in ToneTL, OCR1BL
	add ToneTL, Tone2L
	adc ToneTH, Tone2H
	out OCR1BH, ToneTH
	out OCR1BL, ToneTL

	out SREG, SrSave

	sei

	reti


; -----------
; Data tables
; -----------

; Note table :: tone = 1/(8e-7*N)
tblNotes:
	.dw		19111		; C2	
	.dw		18039		; C#2
	.dw		17026		; D2 
	.dw		16071		; D#2
	.dw		15169		; E2 
	.dw		14317		; F2 
	.dw		13514		; F#2
	.dw		12755		; G2 
	.dw		12039		; G#2
	.dw		11364		; A2 
	.dw		10726		; A#2
	.dw		10124		; B2 
	.dw		9556 		; C3 
	.dw		9019 		; C#3
	.dw		8513 		; D3 
	.dw		8035 		; D#3
	.dw		7584 		; E3 
	.dw		7159 		; F3 
	.dw		6757 		; F#3
	.dw		6378 		; G3 
	.dw		6020 		; G#3
	.dw		5682 		; A3 
	.dw		5363 		; A#3
	.dw		5062 		; B3 
	.dw		4778 		; C4 
	.dw		4510 		; C#4
	.dw		4257 		; D4 
	.dw		4018 		; D#4
	.dw		3792 		; E4 
	.dw		3579 		; F4 
	.dw		3378 		; F#4
	.dw		3189 		; G4 
	.dw		3010 		; G#4
	.dw		2841 		; A4	
	.dw		2681 		; A#4
	.dw		2531 		; B4 
	.dw		2389 		; C5	
	.dw		2255 		; C#5
	.dw		2128 		; D5 
	.dw		2009 		; D#5
	.dw		1896 		; E5 
	.dw		1790 		; F5 
	.dw		1689 		; F#5
	.dw		1594 		; G5 
	.dw		1505 		; G#5
	.dw		1420 		; A5 
	.dw		1341 		; A#5
	.dw		1265 		; B5 
	.dw		1194 		; C6 
	.dw		1127 		; C#6
	.dw		1064 		; D6 
	.dw		1004 		; D#6
	.dw		948  		; E6 
	.dw		895  		; F6 
	.dw		845  		; F#6
	.dw		797  		; G6 
	.dw		752  		; G#6
	.dw		710  		; A6 
	.dw		670  		; A#6
	.dw		633  		; B6
	.dw		597			; C7

; Scan code table
; 	Arranged in order: count position
; 	to get note position, e.g. 22 = "2"
scanCodes:
	.db 0x1A, 0x1B	; Z, S
	.db 0x22, 0x23	; X, D
	.db 0x21, 0x2A	; C, V
	.db 0x34, 0x32	; G, B
	.db 0x33, 0x31	; H, N
	.db 0x3B, 0x3A	; J, M
	.db 0x41, 0x4B	; <, L
	.db 0x49, 0x4C	; >, :
	.db 0x4A, 0x15	; ?, Q
	.db 0x1E, 0x1D	; 2, W
	.db 0x26, 0x24	; 3, E
	.db 0x25, 0x2D	; 4, R
	.db 0x2C, 0x36	; T, 6
	.db 0x35, 0x3D	; Y, 7
	.db 0x3C, 0x43	; U, I
	.db 0x46, 0x44	; 9, O
	.db 0x45, 0x4D	; 0, P
	.db 0x4E, 0x54	; -, [
	.db 0x5B, 0x00	; ]

