; ATtiny2313 project
; MIDI Controlled SquareWave Organ v1

.INCLUDE "tn2313def.inc"

.DEF	Velocity	= R8

.DEF	SrSave		= R9

.DEF	Tone1H	= R10
.DEF	Tone1L	= R11
.DEF	Tone2H	= R12
.DEF	Tone2L	= R13

.DEF	ToneTH	= R14
.DEF	ToneTL	= R15

.DEF	ReadBufferL = R16
.DEF	ReadBufferH	= R17

.DEF	Temp	= R18

.DEF	Temp1 	= R19
.DEF	Temp2	= R20
.DEF	Temp3	= R21
.DEF 	Temp4	= R22

.DEF	ADDRH	= R23
.DEF 	ADDRL	= R24

.DEF	RxByte  = R25
.DEF	ByteCnt	= R26

; outi port, value :: send value to port
.MACRO OUTI
	ldi R16,@1 			
	out @0,R16
.ENDM

.MACRO ADI
	subi @0, -@1
.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	

.EQU BUFFERSIZE 	= 3		; Buffer size (3 bytes, accpeting only note on/off)
.EQU VOICENUM		= 2		; Number of available voices

; ---------
; CONSTANTS
; ---------

; MIDI protocol specific

; Use MIDI channel 1 -------

; Note on
.EQU NoteOn 	= 0x90

; Note off
.EQU NoteOff 	= 0x80

; Controller change
.EQU ControllerChange = 0xB0

; Controllers
.EQU AllNotesOff = 0x7B

; ---------------------------

; MIDI lowest note (C2 = 36)
.EQU LowNote = 36

; PORT MAPPING

.EQU Voice1 = PB3
.EQU Voice2 = PB4
.EQU LED	= PB0

; ------------
; SRAM mapping
; ------------
.DSEG

notebuffer:
	.BYTE BUFFERSIZE

voices:
	.BYTE VOICENUM

; -------------
; Begin program
; -------------
.CSEG
.ORG 0x0000

RJMP RESET ; 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
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
RETI ;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

; Program initialization
RESET:

	; Set inputs/outputs
	; (initially disable square wave output)
	outi DDRB, (1<<LED) | (0<<Voice1) | (0<<Voice2)

	; Set stack
	outi SPL, low(RAMEND)

	; LED = ON
	sbi PORTB, LED

	; Initialize hardware features
	rcall Timer1_Init				; Timer
	rcall USART_Init				; USART

	; Clear voices
	memwi voices, 0
	memwi voices+1, 0

	; Set timer interrupts
	outi TIMSK, (1<<OCIE1A) | (1<<OCIE1B)

	; Clear byte counter
	clr ByteCnt

	; Enable interrupts
	sei

back2:

	rjmp back2

; 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

USART_Init:

	outi UBRRH, 0
	outi UBRRL, 32 ; Baud rate = 38400 @ 20.0 MHz
	outi UCSRB, (1<<RXEN) | (1<<RXCIE) | (0<<UCSZ2) ; Transmitter enabled, interrupt enabled
	outi UCSRC, (0<<USBS) | (3<<UCSZ0) ; 8 bits data

	ret

; -------------------------------------
; Functions for tone and melody control
; -------------------------------------

; Temp1 holds note value

; -------------
; Set channel 1
; -------------
setCh1:

	; 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:

	; 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

; USART receive byte
USART0_RXC:

	; Turn interrupts off
	cli
	
	in SrSave, SREG
	
	; Get byte from register
	in RxByte, UDR

	; Write to buffer
	ldi YL, low(notebuffer)
	ldi YH, high(notebuffer)
	
	clr temp
	add YL, ByteCnt
	adc YH, temp

	st Y, RxByte

	; Check counter
	cpi ByteCnt, 0
	breq checkNoteOnOffMessage

	cpi ByteCnt, 2
	breq handleMidiNote

	rjmp incCounter

checkNoteOnOffMessage:

	; Check whether note is on or off,
	; otherwise discard
	cpi RxByte, NoteOn
	breq incCounter

	cpi RxByte, NoteOff
	breq incCounter

	cpi RxByte, ControllerChange
	breq incCounter

DiscardByte:
	
	clr ByteCnt
	rjmp getOutNoteCheck

incCounter:

	inc ByteCnt

getOutNoteCheck:

	out SREG, SrSave

	sei
	reti

handleMidiNote:

	; Turn note on or off, depending
	; on note type
	
	; Read memory			; temp - action, temp1 - note
	memr temp, notebuffer
	memr temp1, notebuffer+1

	mov temp2, temp1			; Store temp1 value in case of
								; controller

    memr velocity, notebuffer+2	; Store velocity

	subi temp1, LowNote		; Get real note number (1-...)
	inc temp1
	
	cpi temp, NoteOn
	breq doNoteOn

	cpi temp, NoteOff
	breq doNoteOff

	cpi temp, ControllerChange
	breq doControllerChange

doNoteOn:

	; Check for zero velocity note
	clr temp2
	cp velocity, temp2
	breq doNoteOff			; Do note off

	; Turn note on

	; Look at channel 1
	memr temp2, voices
	cpi temp2, 0
	brne NotFree1

	; Set note to this channel
	memw voices, temp1
	rcall setCh1

DoneNoteOnOff:
	; Clear counter
	clr ByteCnt
	rjmp getOutNoteCheck

	; Look at channel 2

NotFree1:
	
	; Check if this channel is free
	memr temp2, voices+1
	cpi temp2, 0
	brne DoneNoteOnOff	; No free channel found

	memw voices+1, temp1
	rcall setCh2

	rjmp DoneNoteOnOff

doNoteOff:

	; Check channel 1
	memr temp2, voices
	cp temp1, temp2
	brne notOnCh1
	
	; Free channel 1
	clr temp1
	rcall setCh1
	memwi voices, 0
	rjmp DoneNoteOnOff

notOnCh1:

	memr temp2, voices+1
	cp temp1, temp2
	brne DoneNoteOnOff	 ; Note not found

	clr temp1
	rcall setCh2
	memwi voices+1, 0
	rjmp DoneNoteOnOff

doControllerChange:

	; Check temp2 for "All notes off" message
	cpi temp2, AllNotesOff
	brne DoneNoteOnOff

	; Clear both channels
	memwi voices, 0
	memwi voices+1, 0
	
	cbi DDRB, voice1
	cbi DDRB, voice2

	rjmp DoneNoteOnOff

; -----------
; 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


