;
; midi.inc: the midi data handling routines
;
; author: Marko Kanala, raato ]a t[ mulletronic.com
;
; one pattern sysex:
;
; F0 71 55 17 01 ... 164 bytes of pattern data in sysex format ... F7
;
; note: one pattern dump is always sent from and received to the
;       pattern_data address. So the currently active pattern
;       is sent when sending and overwritten when receiving.
;
; the default midi key numbers for sounds conform to the general midi level 1
;  percussion key map as follows:
;
; midi key number       midi note       drum sound      output
; ---------------       ---------       ----------      ------
; 35                    B0              bass drum
; 36                    C1              bass drum       X
; 37                    C#1             rim shot        X
; 38                    D1              snare drum      
; 39                    D#1             hand clap       X
; 40                    E1              snare drum      X
; 41                    F1              low tom         X
; 42                    F#1             closed hihat    X
; 43                    G1              low tom
; 44                    G#1             closed hihat
; 45                    A1              mid tom         X
; 46                    A#1             open hihat      X
; 47                    B1              mid tom
; 48                    C2              high tom        X
; 49                    C#2             crash cymbal    X
; 50                    D2              high tom
; 51                    D#2             ride cymbal     X
;
; Anything below 35 and after 51 is ignored.
;
; LICENSE
; Creative Commons Attribution-NonCommercial-ShareALike 2.5
;
; You are free:
;
; * to copy, distribute, display, and perform the work
; * to make derivative works
;
; Under the following conditions:
;	
; 1. Attribution. You must attribute the work in the manner specified by the 
;    author or licensor.
; 2. Noncommercial. You may not use this work for commercial purposes.
; 3. Share Alike. If you alter, transform, or build upon this work, you may 
;    distribute the resulting work only under a license identical to this one.
;
; * For any reuse or distribution, you must make clear to others the license 
;   terms of this work.
; * Any of these conditions can be waived if you get permission from the 
;   copyright holder.
;
; Your fair use and other rights are in no way affected by the above.
;
; This is a human-readable summary of the Legal Code.
; See full license at http://creativecommons.org/licenses/by-nc-sa/2.5/legalcode
;
; Copyright: Marko Kanala (raato@mulletronic.com).
;


;
; full memory dump sysex:
;
; f0 71 55 17 00 ... 64*256 bytes of data in sysex format ... F7
;
;

; note: the sequencer is probably prone for missing external clocks
;       etc when handling system exclusive. 

; called in busy loop while EVENT_SYSEX is set
midi_receiving_sysex:
	; i2c write of 256 bytes takes 40ms in worst case
    ;
	; midi transmits ca. 3906 bytes / second
	;  so 3906 / 1000 * 40 = about 156 bytes in 40ms
	;
	; we do the flashing in two phases so
	;  worst case flash delay is 20ms = about 78 bytes
	;  which should be more than enough for flashing
	;  128 bytes (256 / 2 phases).

	SETDISPLAY	s_sysex
	call	lcd_refresh			; refresh lcd if needed

	movlw	128
	cpfseq	sysexbytes
	bra		midi_receiving_sysex_nofh
	; 128 bytes of midi data received, we should flash
	;  the first half of the data
	movlw	0
	call	i2c_flash_save_sysex_128
	return
midi_receiving_sysex_nofh:
	movlw	255
	cpfseq	sysexbytes
	bra		midi_receiving_sysex_nosh
	; 255 bytes of midi data received, we should flash
	;  the second half of the data
	movlw	128
	call	i2c_flash_save_sysex_128
	; second half written, advance to next "page"
	incf	currentpattern				
	movlw	16
	cpfseq	currentpattern
	bra		midi_receiving_sysex_leds
	; advance to next bank
	incf	currentbank
	clrf	currentpattern
	movlw	4
	cpfseq	currentbank
	bra		midi_receiving_sysex_leds
	; 4 x 64 256-byte "pages" written, mem dump done!
	clrf	currentbank					; set A01 as current pattern
	clrf	currentpattern
	movlw	-1
	movwf	sysexcounter				; terminates sysex dump
	CLEAREVENT	EVENT_SYSEX
	SETDISPLAY	s_sysexdone
midi_receiving_sysex_nosh:
	return
midi_receiving_sysex_leds:
	; light up leds while receiving
	movf	currentbank, W
	call	get_bit
	movwf	leds_busb_1
	movlw	8
	cpfslt	currentpattern
	bra		midi_receiving_sysex_over7
	movf	currentpattern, W
	call	get_bit
	movwf	leds_busa_1
	clrf	leds_busa_2	
	return
midi_receiving_sysex_over7:
	subwf	currentpattern, W		; -8
	call	get_bit
	clrf	leds_busa_1
	movwf	leds_busa_2
	return

; called from irq when USART receive irq is set
midi_handlemidi:
	movff	RCREG, midibuffer
  	btfss	RCSTA, OERR
	bra		midi_handlebuffer
	; overrun error ? should be handled somehow
  	bcf		RCSTA, CREN
;	movlw	255
;	call	tr_delay
  	bsf		RCSTA, CREN
	movlw	-1				; abort sysex (?)
	movwf	sysexcounter
	CLEAREVENT	EVENT_SYSEX
	return

midi_handlebuffer:
	; processes the received midi buffer
	movf	sysexcounter, W	; if sysexcounter positive we are
	bnn		midi_sysex_data ;  waiting for data

	; ignore sync commands if midi sync in is set off
	IFSETTINGOFF	SETTING_MIDIS_IN, midi_handlebuffer_skipsync

	movlw	0xF8			; clock
	xorwf	midibuffer, W
	bz		midi_clock

	movlw	0xFA			; start
	xorwf	midibuffer, W
	bz		midi_start

	movlw	0xFB			; continue
	xorwf	midibuffer, W
	bz		midi_continue

	movlw	0xFC			; stop
	xorwf	midibuffer, W
	bz		midi_stop

midi_handlebuffer_skipsync:

;	; ignore incoming notes if midi notes in is set off
;	IFSETTINGOFF	SETTING_MIDIN_IN, midi_handlebuffer_skipnotes
;
;	decf	settings_midi_channel, W
;	iorlw	0x90			; note on
;	xorwf	midibuffer, W
;	bz		midi_note_on	
;
;	decf	settings_midi_channel, W
;	iorlw	0x80
;	xorwf	midibuffer, W
;	bz		midi_note_off
;
;midi_handlebuffer_skipnotes:

	movlw	0xF0			; sysex start
	xorwf	midibuffer, W
	bz		midi_sysex

	; something else ?
	return
;	tstfsz	midi_in_buffer
;	bra		midi_handlebuffer_gotnote
;	return					; nothing in buffer, just ignore
;midi_handlebuffer_gotnote:
;	tstfsz	midi_in_buffer_note
;	bra		midi_handlebuffer_gotvel
;	; the value we have must be a note value
;	movff	midibuffer, midi_in_buffer_note
;	return
;midi_handlebuffer_gotvel:
;	; the value we have must be a velocity value and
;	;  the midi_in_buffer is ready for processing
;	movff	midibuffer, midi_in_buffer_vel 
;	
;	movlw	0x8F			; if over 0x8f must be note on
;	cpfsgt	midi_in_buffer
;	bra		midi_handlebuffer_noteoff
;	call	midi_note_on_ready
;	return
;midi_handlebuffer_noteoff:
;	call	midi_note_off_ready
;	return

; midi clock received
midi_clock:
	IFEVENT	EVENT_MIDIRUN, midi_clock_run
	; ignore all midi clocks if we're not running
	return
midi_clock_run:
	; echo din sync on midi clock
	call	din_clock
	; midi sync 24 ppqn, call player twice
	call	player_tick
	call	player_tick
	; send midi clock out also if desired
	IFSETTINGONCALL SETTING_MIDIS_OUT, midi_out_clock
	return

; midi start received
midi_start:
	call	player_start
	SETEVENT EVENT_MIDIRUN
	IFSETTINGONCALL SETTING_MIDIS_OUT, midi_out_start
	return

; midi continue received
midi_continue:
	call	player_continue
	SETEVENT EVENT_MIDIRUN
	IFSETTINGONCALL SETTING_MIDIS_OUT, midi_out_continue
	return

; midi stop received
midi_stop:
	call	player_stop
	CLEAREVENT EVENT_MIDIRUN
	IFSETTINGONCALL SETTING_MIDIS_OUT, midi_out_stop
	return

;; midi note on/off received
;midi_note_on:
;midi_note_off:
;	movff	midibuffer, midi_in_buffer	; store the first byte
;	return
;
;; all three bytes of note on message received
;midi_note_on_ready:
;midi_note_off_ready:
;	; TODO these
;	clrf	midi_in_buffer
;	clrf	midi_in_buffer_note	
;	clrf	midi_in_buffer_vel
;	return

; midi sysex received
midi_sysex:
	clrf	sysexcounter	; reset sysexcounter to start
							;  the id matching
	clrf	sysexbytes
	return
; bytes after 0xF0
midi_sysex_data:
	btfsc	midibuffer, 7	; control byte / data byte ?
	bra		midi_sysex_Fx	
	; if sysexcounter >= 3 id matched
	movlw	3
	cpfslt	sysexcounter
	bra		midi_sysex_data_in
	; match the sysex id
	TABLE_ADDR	sysex_id
	movf	sysexcounter, W
	addwf	TBLPTRL
	tblrd*
	movf	TABLAT, W
	xorwf	midibuffer, W
	bz		midi_sysex_data_idmatch
	; id mismatch, ignore
	movlw	-1
	movwf	sysexcounter
	return
midi_sysex_data_idmatch:
	incf	sysexcounter
	return
midi_sysex_data_in:
	; data after F0 71 55 17
	movlw	3
	cpfsgt	sysexcounter
	bra		midi_sysex_data_checktype	; first byte determines the 
										;  message type	
	; type determined, read the raw data
	btfsc	sysexcounter, 0
	bra		midi_sysex_data_odd	
	; even byte, read bit 8
	rrncf	midibuffer, W				; 80000000
	movwf	sysexbuf
	incf	sysexcounter
	return
midi_sysex_data_odd:
	movf	midibuffer, W
	iorwf	sysexbuf					; 87654321, ready!
	decf	sysexcounter
	; write sysexbuf to sysexdata
	lfsr	FSR2, sysexdata
	movf	sysexbytes, W
	addwf	FSR2L
	movff	sysexbuf, POSTINC2
	movlw	SYSEX_TYPE_MEM
	cpfsgt	sysextype
	bra		midi_sysex_data_memdump
;midi_sysex_data_patterndump:
	; one pattern takes 164 bytes
	movlw	PATTERN_SIZE_FLASH
	cpfseq	FSR2L
	bra		midi_sysex_data_continue
	; 164 bytes read, copy sysexdata to current pattern,
	;  shitty code, but thrashes only FSR2
midi_sysex_writepattern_loop:
	lfsr	FSR2, sysexdata
	movf	sysexbytes, W
	addwf	FSR2L
	movff	INDF2, sysexbuf				; source byte
	lfsr	FSR2, pattern_data
	movf	sysexbytes, W
	addwf	FSR2L
	movff	sysexbuf, INDF2				; ... to destination
	decfsz	sysexbytes
	bra		midi_sysex_writepattern_loop
	; sequencer should probably be stopped on pattern receive
	;;; call player_stop	
	return

midi_sysex_data_memdump:
	; whole memory dump takes 64 * 256 pages
	tstfsz	FSR2L						; 256 bytes read ?
	; nope, wait for a full page
	bra		midi_sysex_data_continue
	; 256 bytes of data now in sysexdata,
	; NOTE: data is written to flash in busyloop, just rollover
	clrf	sysexbytes
	return
midi_sysex_data_continue:
	incf	sysexbytes
	return

midi_sysex_data_checktype:
	; midibuffer 00 = whole memory dump
	; midibuffer 01 = pattern dump
	movff	midibuffer, sysextype		; store type
	incf	sysexcounter
	tstfsz	sysextype					; check type
	return								; pattern dump, just return
; was full dump, set sysex flag
	SETEVENT	EVENT_SYSEX
	return

midi_sysex_Fx:
	; midi control byte received after F0, must match F7
	movlw	0xF7	; sysex end
	xorwf	midibuffer, W
	bz		midi_sysex_end
	; something else in middle of sysex,
	;  must be real-time messages, just ignore them
	return
midi_sysex_error:
	bsf		sysexstatus, SYSEX_ERROR
midi_sysex_end:
	; TODO: should handle the bytes received this far ?
	movlw	-1				; cancel sysex
	movwf	sysexcounter
	CLEAREVENT	EVENT_SYSEX
	return



; midi out -->

; player started, transmit start
midi_out_start:
	movlw	0xFA
	WAITMIDI
	movwf	TXREG
	return

; player continued, transmit continue
midi_out_continue:
	movlw	0xFB
	WAITMIDI
	movwf	TXREG
	return

; player stopped, transmit stop
midi_out_stop:
	movlw	0xFC
	WAITMIDI
	movwf	TXREG
	return

; transmit clock
midi_out_clock:
	movlw	0xF8
	WAITMIDI
	movwf	TXREG
	return

;
; triggers out current midi notes
;
; midi_ variables should be loaded with the velocities
;  to the notes to be triggered, 0 == off
;
midi_do_triggers:
	lfsr	FSR2, midi_velocities
	movlw	MIDI_NOTE_BDRUM
	call	midi_do_trigger
	movlw	MIDI_NOTE_SDRUM
	call	midi_do_trigger
	movlw	MIDI_NOTE_LTOM
	call	midi_do_trigger
	movlw	MIDI_NOTE_MTOM
	call	midi_do_trigger
	movlw	MIDI_NOTE_HTOM
	call	midi_do_trigger
	movlw	MIDI_NOTE_RSHOT
	call	midi_do_trigger
	movlw	MIDI_NOTE_HCLAP
	call	midi_do_trigger
	movlw	MIDI_NOTE_CHIHAT
	call	midi_do_trigger
	movlw	MIDI_NOTE_OHIHAT
	call	midi_do_trigger
	movlw	MIDI_NOTE_CRASH
	call	midi_do_trigger
	movlw	MIDI_NOTE_RIDE
	call	midi_do_trigger
	return

midi_do_trigger:
	movwf	temptrig			; note
	movf	POSTINC2, W
	bz		midi_do_not_trigger	; vel == 0 ?
	movwf	temptrigvol
; transmit note off
	decf	settings_midi_channel, W
	addlw	0x80
	WAITMIDI
	movwf	TXREG				; 0x80 + channel
	movf	temptrig, W
	WAITMIDI
	movwf	TXREG				; note number
	movlw	64
	WAITMIDI
	movwf	TXREG				; velocity
; transmit note on
;
; 0x9n channel + note number (0 - 127) + velocity (0 - 127)
	decf	settings_midi_channel, W
	addlw	0x90
	WAITMIDI
	movwf	TXREG				; 0x90 + channel
	movf	temptrig, W
	WAITMIDI
	movwf	TXREG				; note number
	movf	temptrigvol, W
	WAITMIDI	
	movwf	TXREG				; velocity
midi_do_not_trigger:
	return


; transmits the F0 <sysex id> sequence to midi,
;  note: thrashes table pointers
midi_out_sysex_id:
	WAITMIDI
	movlw	0xF0				; sysex start
	movwf	TXREG
	TABLE_ADDR	sysex_id
	tblrd*+
	movf	TABLAT, W
	WAITMIDI
	movwf	TXREG				; sys
	tblrd*+
	movf	TABLAT, W
	WAITMIDI
	movwf	TXREG				;    ex
	tblrd*
	movf	TABLAT, W
	WAITMIDI
	movwf	TXREG				;      id
	return

midi_out_sysex_end:
	WAITMIDI
	movlw	0xF7
	movwf	TXREG
	return

; transmit full memory dump as sysex,
;  sequencer should be stopped,
;  note: thrashes nextbank & nextpattern
midi_out_dump_all:
	clrf	nextbank
	clrf	nextpattern
	call	midi_out_sysex_id	; write sysex id
	WAITMIDI
	movlw	SYSEX_TYPE_MEM
	movwf	TXREG				; pattern dump id
	WAITMIDI
midi_out_dump_all_getloop:
	; light up leds while dumping
	movf	nextbank, W
	call	get_bit
	movwf	leds_busb_1
	movlw	8
	cpfslt	nextpattern
	bra		midi_out_dump_all_over7
	movf	nextpattern, W
	call	get_bit
	movwf	leds_busa_1
	clrf	leds_busa_2	
	bra		midi_out_dump_all_cont
midi_out_dump_all_over7:
	subwf	nextpattern, W		; -8
	call	get_bit
	clrf	leds_busa_1
	movwf	leds_busa_2
midi_out_dump_all_cont:
	; 1. read 256 bytes of data from flash to sysexdata
	call	i2c_flash_read_sysex_256
	; 2. write sysexdata to midi
	lfsr	FSR1, sysexdata
midi_out_dump_all_putloop:
	call	midi_out_byte_fsr1
	tstfsz	FSR1L				; 256 bytes sent ?
	bra		midi_out_dump_all_putloop	
	; 64 pages of 256 bytes sent ?
	incf	nextpattern
	movlw	16
	cpfseq	nextpattern
	bra		midi_out_dump_all_getloop
	clrf	nextpattern
	incf	nextbank
	movlw	4
	cpfseq	nextbank
	bra		midi_out_dump_all_getloop
	; all done, reset next* pointers
	movff	currentpattern, nextpattern
	movff	currentbank, nextbank
	bra 	midi_out_sysex_end

; transmit current pattern as sysex,
;  note: thrashes FSR1
midi_out_dump_patt:
	call	midi_out_sysex_id	; write sysex id
	movlw	SYSEX_TYPE_PAT
	WAITMIDI
	movwf	TXREG				; pattern dump id
	; write data from pattern_data to midi
	lfsr	FSR1, pattern_data
midi_out_dump_patt_loop:
	call	midi_out_byte_fsr1
	movlw	PATTERN_SIZE_FLASH	; done ?
	cpfseq	FSR1L
	bra		midi_out_dump_patt_loop
	bra 	midi_out_sysex_end

; writes out a byte from fsr1 to midi as
;  %00000008 %07654321
midi_out_byte_fsr1:
	movlw	b'10000000'
	andwf	INDF1, W			; %80000000
	rlncf	WREG				; %00000008
	WAITMIDI
	movwf	TXREG
	movlw	b'01111111'
	andwf	POSTINC1, W			; %07654321
	WAITMIDI
	movwf	TXREG
	return




External Labels :

lcd_refresh
i2c_flash_save_sysex_128
get_bit
din_clock
player_tick
player_start
player_continue
player_stop
midi_do_trigger
midi_out_sysex_id
i2c_flash_read_sysex_256
midi_out_byte_fsr1