; ====================================== SIMPLE.ASM
; FULLZOOT, enhanced to remove hardware components:
;   1) replace external network w/ internal pullups
;   2) invert MIDI signal and remove PNP output isolator

; MIDI OUT pin = PORTA.3

; CPU configuration
        processor 16f84
        include   
        __config  _HS_OSC & _WDT_OFF & _PWRTE_ON

; ---------------------------------- Macros
; Note: One-line macros speed code changes during experiments
;       with inverted/noninverted MIDI OUT hardware

MidiIdle        macro       ; set MIDI OUT pin to 'idle' (High)
        bsf     PORTA,0x03
        endm

MidiData        macro       ; set MIDI OUT pin to 'data' (Low)
        bcf     PORTA, 0x03
        endm

; ---------------------------------- Variables
j       equ     0x0C    ; timing loop variable (sendmidi)
temp    equ     0x0D    ; timing loop variable (sendmidi:)
xmit    equ     0x0E    ; for passing byte to SENDMIDI
note    equ     0x0F    ; pass note value to on/off routines
rowIs   equ     0x10    ; state of buttons in current row
delta   equ     0x11    ; map out which button(s) changed

row0Was equ     0x20    ; vars contain saved state of buttons
row1Was equ     0x21    ; ...in each row of the instrument
row2Was equ     0x22
row3Was equ     0x23
row4Was equ     0x24
row5Was equ     0x25
row6Was equ     0x26
row7Was equ     0x27

note0   equ     0x30   ; vars to pass active row's notes
note1   equ     0x31   ; ...to 'parse' subroutine
note2   equ     0x32
note3   equ     0x33
note4   equ     0x34
note5   equ     0x35
note6   equ     0x36
note7   equ     0x37

; ---------------------------------- Program
        org     0x000      ; start at address 0
start
        movlw   b'00000000'     ; PORTA is outputs (MIDI OUT)
        tris    PORTA

        movlw   b'11111111'     ; teach PORTB to be inputs
        tris    PORTB           ; SET at initialization
        movwf   PORTB           ; set inputs HIGH (unpressed)
        movwf   row0Was         ; set all rows to "all up"
        movwf   row1Was
        movwf   row2Was
        movwf   row3Was
        movwf   row4Was
        movwf   row5Was
        movwf   row6Was
        movwf   row7Was

        movlw   b'00000000'     ; Enable port b pullup resistors
        option

        MidiIdle                ; begin w/ MIDI OUT at Idle state

        movlw   D'192'          ; MIDI Change Program on 0...
        movwf   xmit
        call    sendmidi
        movlw   D'23'           ; ...to Bandoneon
        movwf   xmit
        call    sendmidi

row0:
        bcf     PORTA,2         ; set selectors = 000
        bcf     PORTA,1
        bcf     PORTA,0
        nop                     ; steady...
        movf    PORTB,w         ; get current state of buttons
        movwf   rowIs           ; ... and set it aside
        xorwf   row0Was,w       ; itIs XOR itWas:
        btfsc   STATUS,2        ; if zero flag == 1 (true(!)),
        goto    row1            ;    no change, so try next row
        movwf   delta           ; 1 == bit(s) that changed
        movf    rowIs,w         ; Remember itWas = itIs
        movwf   row0Was         ; Save state for next time
        call    setRow0         ; pass correct notes to parse
        call    parse           ; read notes and turn on/off

row1:
        bcf     PORTA,2         ; set selectors = 001
        bcf     PORTA,1
        bsf     PORTA,0
        nop                     ; steady...
        movf    PORTB,w         ; get current state of buttons
        movwf   rowIs           ; ... and set it aside
        xorwf   row1Was,w       ; itIs XOR itWas:
        btfsc   STATUS,2        ; if zero flag == 1 (true(!))
        goto    row2            ;    no change, so try next row
        movwf   delta           ; 1 == bit(s) that changed
        movf    rowIs,w         ; Remember itWas = itIs
        movwf   row1Was         ; Save state for next time
        call    setRow1         ; pass correct notes to parse
        call    parse           ; read notes and turn on/off

row2:
        bcf     PORTA,2         ; set selectors = 010
        bsf     PORTA,1
        bcf     PORTA,0
        nop                     ; steady...
        movf    PORTB,w         ; get current state of buttons
        movwf   rowIs           ; ... and set it aside
        xorwf   row2Was,w       ; itIs XOR itWas:
        btfsc   STATUS,2        ; if zero flag == 1 (true(!))
        goto    row3            ;    no change, so try next row
        movwf   delta           ; 1 == bit(s) that changed
        movf    rowIs,w         ; Remember itWas = itIs
        movwf   row2Was         ; Save state for next time
        call    setRow2         ; pass correct notes to parse
        call    parse           ; read notes and turn on/off

row3:
        bcf     PORTA,2         ; set selectors = 011
        bsf     PORTA,1
        bsf     PORTA,0
        nop                     ; steady...
        movf    PORTB,w         ; get current state of buttons
        movwf   rowIs           ; ... and set it aside
        xorwf   row3Was,w       ; itIs XOR itWas:
        btfsc   STATUS,2        ; if zero flag == 1 (true(!))
        goto    row4            ;    no change, so try next row
        movwf   delta           ; 1 == bit(s) that changed
        movf    rowIs,w         ; Remember itWas = itIs
        movwf   row3Was         ; Save state for next time
        call    setRow3         ; pass correct notes to parse
        call    parse           ; read notes and turn on/off

row4:
        bsf     PORTA,2         ; set selectors = 100
        bcf     PORTA,1
        bcf     PORTA,0
        nop                     ; steady...
        movf    PORTB,w         ; get current state of buttons
        movwf   rowIs           ; ... and set it aside
        xorwf   row4Was,w       ; itIs XOR itWas:
        btfsc   STATUS,2        ; if zero flag == 1 (true(!))
        goto    row5            ;    no change, so try next row
        movwf   delta           ; 1 == bit(s) that changed
        movf    rowIs,w         ; Remember itWas = itIs
        movwf   row4Was         ; Save state for next time
        call    setRow4         ; pass correct notes to parse
        call    parse           ; read notes and turn on/off

row5:
        bsf     PORTA,2         ; set selectors = 101
        bcf     PORTA,1
        bsf     PORTA,0
        nop                     ; steady...
        movf    PORTB,w         ; get current state of buttons
        movwf   rowIs           ; ... and set it aside
        xorwf   row5Was,w       ; itIs XOR itWas:
        btfsc   STATUS,2        ; if zero flag == 1 (true(!))
        goto    row6            ;    no change, so try next row
        movwf   delta           ; 1 == bit(s) that changed
        movf    rowIs,w         ; Remember itWas = itIs
        movwf   row5Was         ; Save state for next time
        call    setRow5         ; pass correct notes to parse
        call    parse           ; read notes and turn on/off

row6:
        bsf     PORTA,2         ; set selectors = 110
        bsf     PORTA,1
        bcf     PORTA,0
        nop                     ; steady...
        movf    PORTB,w         ; get current state of buttons
        movwf   rowIs           ; ... and set it aside
        xorwf   row6Was,w       ; itIs XOR itWas:
        btfsc   STATUS,2        ; if zero flag == 1 (true(!))
        goto    row7            ;    no change, so try next row
        movwf   delta           ; 1 == bit(s) that changed
        movf    rowIs,w         ; Remember itWas = itIs
        movwf   row6Was         ; Save state for next time
        call    setRow6         ; pass correct notes to parse
        call    parse           ; read notes and turn on/off

row7:
        bsf     PORTA,2         ; set selectors = 111
        bsf     PORTA,1
        bsf     PORTA,0
        nop                     ; steady...
        movf    PORTB,w         ; get current state of buttons
        movwf   rowIs           ; ... and set it aside
        xorwf   row7Was,w       ; itIs XOR itWas:
        btfsc   STATUS,2        ; if zero flag == 1 (true(!))
        goto    row0            ;    no change here, so start over
        movwf   delta           ; 1 == bit(s) that changed
        movf    rowIs,w         ; Remember itWas = itIs
        movwf   row7Was         ; Save state for next time
        call    setRow7         ; pass correct notes to parse
        call    parse           ; read notes and turn on/off

        goto    row0            ; bottom of main polling loop

; ---------------------------------------------
; Read delta map to identify "todo" buttons.
; If button is PRESSED, turn appropriate note On.
; If RELEASED, turn appropriate note Off.
; Notes for buttons 0-7 passed in note0-note7.
parse:
        btfss   delta,0         ; if delta.0==0...
        goto    bit1            ; ... try the next bit

        movf    note0,w
        movwf   note

        btfss   rowIs,0          ; skip if NOT pressed
        call    noteOn
        btfsc   rowIs,0          ; skip if PRESSED
        call    noteOff

bit1:   btfss   delta,1         ; if delta.1==0...
        goto    bit2            ; ... try the next bit

        movf    note1,w
        movwf   note

        btfss   rowIs,1          ; skip if NOT pressed
        call    noteOn
        btfsc   rowIs,1          ; skip if PRESSED
        call    noteOff

bit2:   btfss   delta,2         ; if delta.2==0...
        goto    bit3            ; ... try the next bit

        movf    note2,w
        movwf   note

        btfss   rowIs,2          ; skip if NOT pressed
        call    noteOn
        btfsc   rowIs,2          ; skip if PRESSED
        call    noteOff

bit3:   btfss   delta,3         ; if delta.3==0...
        goto    bit4            ; ... try the next bit

        movf    note3,w
        movwf   note

        btfss   rowIs,3          ; skip if NOT pressed
        call    noteOn
        btfsc   rowIs,3          ; skip if PRESSED
        call    noteOff

bit4:   btfss   delta,4         ; if delta.4==0...
        goto    bit5            ; ... try the next bit

        movf    note4,w
        movwf   note

        btfss   rowIs,4          ; skip if NOT pressed
        call    noteOn
        btfsc   rowIs,4          ; skip if PRESSED
        call    noteOff

bit5:   btfss   delta,5         ; if delta.5==0...
        goto    bit6            ; ... try the next bit

        movf    note5,w
        movwf   note

        btfss   rowIs,5          ; skip if NOT pressed
        call    noteOn
        btfsc   rowIs,5          ; skip if PRESSED
        call    noteOff

bit6:   btfss   delta,6         ; if delta.6==0...
        goto    bit7            ; ... try the next bit

        movf    note6,w
        movwf   note

        btfss   rowIs,6          ; skip if NOT pressed
        call    noteOn
        btfsc   rowIs,6          ; skip if PRESSED
        call    noteOff

bit7:   btfss   delta,7         ; if delta.7==0...
        goto    eoBits          ; ... try the next bit

        movf    note7,w
        movwf   note

        btfss   rowIs,7          ; skip if NOT pressed
        call    noteOn
        btfsc   rowIs,7          ; skip if PRESSED
        call    noteOff

eoBits: return

; --------------------------------------------------
; note value arrives in file register "note"
noteOn:
        movlw   0x90            ; note on, channel 1
        movwf   xmit
        call    sendmidi

        movf    note,w          ; note value to xmit
        movwf   xmit
        call    sendmidi

        movlw   0x40            ; velocity 64 (medium)
        movwf   xmit
        call    sendmidi
        return


; ---------------------------------------------------
; note value arrives in file register "note"
noteOff:
        movlw   0x80            ; note off, channel 1
        movwf   xmit
        call    sendmidi

        movf    note,w          ; move note value to xmit
        movwf   xmit
        call    sendmidi

        movlw   0x40            ; velocity
        movwf   xmit
        call    sendmidi
        return

; --------------------------------------------------
; sendmidi transmits one midi byte on RA3.
; W/ 10mhz xtal, 80 instructions per midi bit.
; Byte to be sent is passed in register xmit.
sendmidi:

startb: MidiData                ; begin start bit
        movlw   D'24'           ; delay 73 clocks: 2+(23*3 + 1*2)
        movwf   temp            ; |
loop1:  decfsz  temp,f          ; |
        goto    loop1           ; end delay

        movlw   D'8'            ; counter to cycle through 8 bits...
        movwf   j               ; ...is in j

sendloop:                       ; burns 5 cycles before setting bit
        rrf     xmit,f
        btfsc   STATUS, C
        goto    send1

send0:  nop
        MidiData
        goto    endloop

send1:  MidiIdle
        nop
        nop

endloop:                        ;
        movlw   D'23'           ;delay 70 instructions 2+(22*3 + 1*2)
        movwf   temp            ; |
loop2:  decfsz  temp,f          ; |
        goto    loop2           ; end delay

        decfsz  j,f
        goto    sendloop

stopb:
        nop
        nop
        nop
        nop
        nop
        MidiIdle

        movlw   D'26'           ; delay 79 clocks: 2+(25*3 + 1*2)
        movwf   temp            ; |
loop3:  decfsz  temp,f          ; |
        goto    loop3           ; end delay

        return

; ---------------------------------------------
; Called if row0 is detected to have changed.
; Sets note0-note7 to pass to parse subroutine.
; ---------------------------------------------
setRow0:        ; Notes for row 0
        movlw   D'80'
        movwf   note0
        movlw   D'82'
        movwf   note1
        movlw   D'84'
        movwf   note2
        movlw   D'86'
        movwf   note3
        movlw   D'88'
        movwf   note4
        movlw   D'90'
        movwf   note5
        movlw   D'92'
        movwf   note6
        movlw   D'94'
        movwf   note7
        return

setRow1:        ; Notes for row 1
        movlw   D'75'
        movwf   note0
        movlw   D'77'
        movwf   note1
        movlw   D'79'
        movwf   note2
        movlw   D'81'
        movwf   note3
        movlw   D'83'
        movwf   note4
        movlw   D'85'
        movwf   note5
        movlw   D'87'
        movwf   note6
        movlw   D'89'
        movwf   note7
        return

setRow2:        ; Notes for row 2
        movlw   D'68'
        movwf   note0
        movlw   D'70'
        movwf   note1
        movlw   D'72'
        movwf   note2
        movlw   D'74'
        movwf   note3
        movlw   D'76'
        movwf   note4
        movlw   D'78'
        movwf   note5
        movlw   D'80'
        movwf   note6
        movlw   D'82'
        movwf   note7
        return

setRow3:        ; Notes for row 3
        movlw   D'63'
        movwf   note0
        movlw   D'65'
        movwf   note1
        movlw   D'67'
        movwf   note2
        movlw   D'69'
        movwf   note3
        movlw   D'71'
        movwf   note4
        movlw   D'73'
        movwf   note5
        movlw   D'75'
        movwf   note6
        movlw   D'77'
        movwf   note7
        return

setRow4:        ; Notes for row 4
        movlw   D'56'
        movwf   note0
        movlw   D'58'
        movwf   note1
        movlw   D'60'
        movwf   note2
        movlw   D'62'
        movwf   note3
        movlw   D'64'
        movwf   note4
        movlw   D'66'
        movwf   note5
        movlw   D'68'
        movwf   note6
        movlw   D'70'
        movwf   note7
        return

setRow5:        ; Notes for row 5
        movlw   D'51'
        movwf   note0
        movlw   D'53'
        movwf   note1
        movlw   D'55'
        movwf   note2
        movlw   D'57'
        movwf   note3
        movlw   D'59'
        movwf   note4
        movlw   D'61'
        movwf   note5
        movlw   D'63'
        movwf   note6
        movlw   D'65'
        movwf   note7
        return

setRow6:        ; Notes for row 6
        movlw   D'44'
        movwf   note0
        movlw   D'46'
        movwf   note1
        movlw   D'48'
        movwf   note2
        movlw   D'50'
        movwf   note3
        movlw   D'52'
        movwf   note4
        movlw   D'54'
        movwf   note5
        movlw   D'56'
        movwf   note6
        movlw   D'58'
        movwf   note7
        return

setRow7:        ; Notes for row 7
        movlw   D'39'
        movwf   note0
        movlw   D'41'
        movwf   note1
        movlw   D'43'
        movwf   note2
        movlw   D'45'
        movwf   note3
        movlw   D'47'
        movwf   note4
        movlw   D'49'
        movwf   note5
        movlw   D'51'
        movwf   note6
        movlw   D'53'
        movwf   note7
        return

; ---------------------------------------------
        end