;>>> rx1_303a.asm <<< ;******************************************** ;*** MIDI RECEIVER SOFTWARE FOR PIC 16F84 *** ;*** *** ;*** (c) Copyright Trevor Page 1999 *** ;******************************************** ;******************************************** ;* VERSION 1.303a-16x84-4 * ;*Intended for use with Roland TB303 clones.* ;*Features: PITCH & FILTER analogue outputs.* ;* Gate, Accent and Slide logic outputs. * ;******************************************** ;***4MHz Version*** ;********** DETAILS OF OPERATION ************* ;PORT A: ;bit 0 = DAC select output (0= pitch, 1= filter cutoff) ;bit 1 = data write output (pulsed low to write data) ;bit 2 = Attack output (active high) ;bit 3 = slide output (active high) ;bit 4 = gate output (active high) ; ;Port B: ;bits 0-6 = data outputs (LSB-MSB) ;bit 7 = MIDI input ;Bits 0-3 are frequently switched to output mode to check the current MIDI channel. MIDI channel selection is achieved ;with the use of high value pullup resistors on these pins. 0000 = channel 1, 0001 channel 2.... etc. ; ; ;Filter cutoff (FILTER) output is controllable via controller no. 100. ;Attack is ON when the note velocity is greater than 100. ;Fixed MIDI channel = channel 1. ; ;ATTACK output: This output transits to 1 when the velocity of the last note received is greater than 100. It returns ;to 0 when a note is received with a velocity equal to or less than 100. ; ;SLIDE output: Goes to 1 when two or more notes are held. Does not return to 0 until ALL notes are released. ; ;GATE output: Goes to 1 when any number of notes are held. Returns to 0 only when all notes are released. ; ;The FILTER DAC is initally loaded with a value of 64 with the intention of setting the filter cutoff to mid position. ; ;********************************************* #define PAGE0 bcf STATUS,5 #define PAGE1 bsf STATUS,5 PORTB: equ h'06' ;Port B data register PORTA: equ h'05' ;Port A data register TRISB: equ h'06' ;Port B Data Direction Register [DDR] TRISA: equ h'05' ;Port A Data Direction Register [DDR] STATUS: equ h'03' ;Microcontroller Status Register byte C: equ h'0' ;Carry flag bit W: equ h'0' ;Working Register F: equ h'1' ;File Z: equ h'2' ;Zero flag bit PCL: equ h'02' ;program counter BYTE: equ h'0C' ;Temporary MIDI data storage CHANNEL:equ h'0E' ;Working MIDI channel TEMP1: equ h'0F' ;temporary data manipulation storage file 1 TEMP2: equ h'1C' ;temporary data manipulation storage file 2 ;<<< $10 to $19 Reserved for note stack >>> SCRATCHNOTE: equ h'1A' ;Stores the value of any note that is to be deleted from stack LASTSTATUSBYTE: equ h'1B' ;Stores the last status byte received, *if processed*- needed for Running Status support. NOTESP: equ h'0D' ;Note Stack Pointer INDF: equ h'00' ;INDirect File FSR: equ h'04' ;File Special Register ; org h'0000' goto begin org h'0004' ; org h'0005' ; begin call clearNoteStack ;Clear all contents of the NoteStack and set the stack pointer ; movlw 0 ;Put null value 0 into LASTSTATUS BYTE. movwf LASTSTATUSBYTE ; ; ; clrf PORTB ;***Initialise Ports A and B*** clrf PORTA ; PAGE1 ; movlw B'10000000' ;All bits on either ports are outputs EXCEPT Port B, bit 7 = MIDI input. movwf TRISB ; clrf TRISA ; PAGE0 ; ; ;Set FILTER DAC to mid position (value 64) movlw 64 ;Put value 64 onto Port B movwf PORTB ; bsf PORTA,0 ;Select the FILTER DAC bsf PORTA,1 ; bcf PORTA,1 ;Pulse Port A bit 1 low to write data to the FILTER DAC goto start ; processFirstByte: ;***Process the Status Byte*** ;!!!This subroutine manipulates the contents of BYTE!!! btfss BYTE,7 ;If the MSB in the byte is 0, it must be Running Status information. Branch accordingly goto processRunningStatus ;to processRunningStatus. ; movf BYTE,W ; movwf TEMP2 ;Keep a record of this status byte- may be needed later for running status support. ;A record is kept of it only if this version can handle the particular message type. ;Iff so, the status byte is recorded from TEMP2 into LASTSTATUSBYTE in the respective ;message handling routine. ; andlw b'11110000' ;Check to see if the received status byte is a system message, or the start of a xorlw b'11110000' ;2 or 3 byte system message. If so, handleSystemMessage is CALLED to deal with the btfss STATUS,Z ;system message. Once the system message has been dealt with, the program returns goto getchan ;back to here... call handleSystemMessage; goto start ;...and a jump is then made back to start. ; getchan: ; movlw b'00001111' ;Extract channel number from Status Byte. andwf BYTE,W ; xorwf CHANNEL,W ;Branch back to start if wrong channel. btfss STATUS,Z ; goto killLastStatusByte ; swapf BYTE,F ;Otherwise swap nybbles in Status Byte and movlw b'00001111' ;extract the Message Type. andwf BYTE,F ; movlw 8 ; subwf BYTE,W ;Branch to appropriate message handling addwf PCL,F ;routine according to the type of message. goto handleNoteOff ;1000 note off ;} goto handleNoteOn ;1001 note on ; goto killLastStatusByte ;1010 poly key pressure ; goto handleController ;1011 control change ; Lookup table goto killLastStatusByte ;1100 program change ; goto killLastStatusByte ;1101 overall key pressure ; goto killLastStatusByte ;1110 pitch wheel ;} killLastStatusByte: ;Jump to here if this version does NOT handle the sent status byte. Killing the old movlw 0 ;status byte data ensures the system does not respond to any new data using the old movwf LASTSTATUSBYTE ;status byte information. We also jump to here is the new status byte is intended goto start ;for another channel- we don't want a response to data aimed at another channel! processRunningStatus: ;***Process data bytes that have been sent without a status byte*** movf LASTSTATUSBYTE,W ;Must move the last status byte into TEMP1 for manipulation. No manipulation is to movwf TEMP1 ;be done with BYTE here- it carries data and that data is to be dealt with in the ;appropriate section. ; movlw b'00001111' ;Extract channel number from *last received* Status Byte. andwf TEMP1,W ; xorwf CHANNEL,W ;Branch back to start if wrong channel. btfss STATUS,Z ; goto start ; swapf TEMP1,F ;Otherwise swap nybbles in Status Byte and movlw b'00001111' ;extract the Message Type. andwf TEMP1,F ; movf TEMP1,W ;Branch to appropriate message handling addwf PCL,F ;routine according to the type of message. goto start ;0000 - keep this here- will be accessed if LASTSTATUSBYTE contains the null value, 0. nop ; nop ; nop ;***ALL NOPs ESSENTIAL FOR CORRECT LOOKUP TABLE OPERATION*** nop ; nop ; nop ; nop ; goto handleRunningStatusNoteOff ;1000 note off goto handleRunningStatusNoteOn ;1001 note on goto start ;1010 poly key pressure goto handleRunningStatusController ;1011 control change goto start ;1100 program change goto start ;1101 overall key pressure goto start ;1110 pitch wheel goto start ;1111 SYSTEM MESSAGES- not currently implemented. start: ;***System waiting for a new MIDI message*** ; bsf PORTA,1 ;Port A bit 1 must always be returned to high, as it is changed to 0 to write data. call getByte ;Get the first MIDI byte for the new message BSF SCRATCHNOTE,7 ;Will have been made use of by now and must be disabled. Makes sure that SCRATCNOTE BSF SCRATCHNOTE,6 ;holds an invalid note value. goto processFirstByte ; getByte: ;*****RECEIVE SINGLE MIDI BYTE***** waitForIdle: ;***This subroutine also deals with NoteStack updating and channel switch checking*** btfss PORTB,7 ;Make sure input is 1 first goto waitForIdle ; waitForStartBit: ;Wait for start bit of MIDI byte (low pulse) btfsc PORTB,7 ; goto waitForStartBit ; bcf STATUS,C ;initial setup movlw b'10000000' ; movwf BYTE ; ; PAGE1 ;Check least significant nybble on port B for the current MIDI channel setting, movlw b'10001111' ;and put that value into CHANNEL. The LS nybble of Port B are made inputs to do this. movwf TRISB ;They are put back to outputs in the LOOP section. PAGE0 ; movf PORTB,W ; andlw b'00001111' ; movwf CHANNEL ; nop ;Five extra NOP's added 16-8-99. These compensate for the fact that the bit is sampled nop ;five microseconds before, and not at the end, of the 32uS 'timing' loop. This should nop ;cure any problems with slow low-high transitions. nop ; nop ; nop ; nop ;***ALL NOPs ESSENTIAL FOR CORRECT DATA SYNC*** ; ; movlw h'11' ;initial setup for Note Stack Updating. movwf FSR ;Points FSR (File Special Register) at the next lowest note stack position. ; ; LOOP: ; ;************************Note Stack Updating************************* ;Must be 20 steps long no matter which branches are taken to ensure accurately timed MIDI reception ; movf INDF,W ;Write the value 10000000 into current stack position if it contains xorwf SCRATCHNOTE,W ;the note that is to be deleted movlw b'10000000' ; btfss STATUS,Z ; goto check ; movwf INDF ; decf NOTESP,F ;Must decrement the stack pointer: a note has been deleted. ; check xorwf INDF,W ;See if the current stack address contains no note (W still contains 10000000 at this moment) btfss STATUS,Z ; goto delay ;branch if the current stack address does contain a note value. incf FSR,F ;...Otherwise start shifting stack data down. movf INDF,W ;Get data from next position up in stack decf FSR,F ; movwf INDF ;stick that data into the current stack position address ; incf FSR,F ;MUST also now assign the address above as No Note (%10000000) movlw b'10000000' ; movwf INDF ; decf FSR,F ; goto final ; ; delay: nop ;The additional delay required depends on the path previously taken. nop ;This subroutine MUST take 20 steps no matter which branches are taken. nop ; nop ;***ALL NOPs ESSENTIAL FOR CORRECT DATA SYNC*** nop ; nop ; nop ; nop ; nop ; final: incf FSR,F ;Finally move up one in the stack for next time. nop ;***ALL NOPs ESSENTIAL FOR CORRECT DATA SYNC*** PAGE1 ; movlw b'10000000' ;All bits on either ports are outputs EXCEPT Port B, bit 7 = MIDI input. This is to movwf TRISB ;restore the port data direction registers after they were changed for checking the PAGE0 ;current midi channel. nop ; rrf BYTE,F ;Bit added to current byte here btfsc PORTB,7 ; bsf BYTE,7 ; btfss STATUS,C ; goto LOOP ; return ; handleNoteOn: ;***Note On messages handled here*** call getByte ;get the note value byte ; btfsc BYTE,7 ;*!!!*Check that the fetched byte is NOT a System Real-Time message. Real-time messages are goto handleNoteOn ;not supported and must be trapped since they can be sent in the middle of other messages. ; movf TEMP2,W ; movwf LASTSTATUSBYTE ; ; handleRunningStatusNoteOn: ; movf BYTE,W ; movwf TEMP1 ;here TEMP1 contains the note value ; getVel: call getByte ;get the note velocity byte ; btfsc BYTE,7 ;*!!!*Check that the fetched byte is NOT a System Real-Time message. Real-time messages are goto getVel ;not supported and must be trapped since they can be sent in the middle of other messages. ; movf BYTE,F ;see if velocity value held in BYTE is zero- btfsc STATUS,Z ;if it is, branch to handle Note Off message. goto switchNoteOff ;Otherwise continue on to switchNoteOn. ; switchNoteOn: ; ; movlw h'11' ;Turn the SLIDE bit on if the new note will be the second in the xorwf NOTESP,W ;stack, i.e. two notes being held down. btfsc STATUS,Z ; bsf PORTA,3 ;The SLIDE output pin is Port A, bit 3. ; ;***ATTACK processing section: Port A, Bit 1 is set to one iff velocity > 100*** movf BYTE,W ;Send the new note velocity, held in BYTE, to Working Register. bcf STATUS,C ; sublw b'01100100' ;Subtract the value in W from literal 100. btfss STATUS,C ; goto clrAttack ; bcf PORTA,2 ;Clear accent bit if the result is positive goto addNewNote ; clrAttack: ; bsf PORTA,2 ;Set the accent bit if the result is negative ; ; addNewNote: ; incf NOTESP,F ;Need not worry about the SP increasing beyond the range of the stack movf NOTESP,W ;Put the new note value (held in TEMP1) into the stack address pointed movwf FSR ;at by NOTESP. movf TEMP1,W ; movwf INDF ; ; movwf PORTB ;Output this new note value to the Note CV DAC ; bcf PORTA,0 ;Make sure PITCH DAC is selected (Port A bit 0 = 0 ) bcf PORTA,1 ;Pulse Port A bit 1 low bsf PORTA,4 ;Turn gate pulse on (or ensure that it is on) ; movlw h'19' ;This section checks to see if a note has been inserted into the top of xorwf NOTESP,W ;the Note Stack, i.e. $19. If so, the note at the bottom of the stack ($11) movlw b'10000000' ;is to be cleared, thus causing the entire stack to move down one step. btfss STATUS,Z ;This ensures a limit of 8 notes stored in the stack. goto start ; movwf h'11' ; decf NOTESP,F ; goto start ; handleNoteOff: ;***Note Off messages handled here*** call getByte ;get note value to be turned off ; btfsc BYTE,7 ;*!!!*Check that the fetched byte is NOT a System Real-Time message. Real-time messages are goto handleNoteOff ;not supported and must be trapped since they can be sent in the middle of other messages. ; movf TEMP2,W ; movwf LASTSTATUSBYTE ; ; handleRunningStatusNoteOff: ; movf BYTE,W ; movwf TEMP1 ; getVel2: ; call getByte ;get note velocity info, not used for switching note off. ; btfsc BYTE,7 ;*!!!*Check that the fetched byte is NOT a System Real-Time message. Real-time messages are goto getVel2 ;not supported and must be trapped since they can be sent in the middle of other messages. ; switchNoteOff: ; ; ;;;movlw $11 ;Ensure that SLIDE is turned off (0) if no notes are being held. ;;;xorwf NOTESP,W ;***COMBINED WITH turnGateOff IN THIS VERSION*** ;;;btfsc STATUS,Z ; ;;;bcf PORTA,3 ; ; movf NOTESP,W ;Put the current stack position into register FSR as further code will need to movwf FSR ;check the value of the current note being played. ; movf TEMP1,W ;TEMP1 currently holds note value to be turned off. Store it movwf SCRATCHNOTE ;into SCRATCHNOTE for deletion later. ; xorwf INDF,W ;Branch back to start providing the current note being played is not the btfss STATUS,Z ;one to be deleted. goto start ; ; movlw h'10' ;Go back to the start if the stack pointer is already at the bottom xorwf FSR,W ;of the stack. In this case there are no notes on and the btfsc STATUS,Z ;gate pulse is already off. goto start ; ; decf FSR,W ; xorlw h'10' ;If the pointer is now pointing to the bottom of the stack ($10) then branch to turnGateOff (kill the btfsc STATUS,Z ;gate pulse). goto turnGateOff ; ; decf FSR,F ;Extract the 'new' note value previous to the current one.(The current note is still movf INDF,W ;held in the FSR at this time). ; movwf PORTB ;Get data ready onto port B for the PITCH DAC ;Select VCO DAC and therefore load the data on Port B into it: bcf PORTA,0 ;Make sure PITCH DAC is selected (Port A bit 0 = 0) bcf PORTA,1 ;Pulse Port A bit 1 low ; goto start ; turnGateOff: bcf PORTA,4 ;Turn off the gate pulse (0) bcf PORTA,3 ;***Ensure SLIDE is also turned off*** goto start ; handleController: ; call getByte ;get the controller number btfsc BYTE,7 ;*!!!*Check that the fetched byte is NOT a System Real-Time message. Real-time messages are goto handleNoteOn ;not supported and must be trapped since they can be sent in the middle of other messages. movf TEMP2,W ; movwf LASTSTATUSBYTE ; ; handleRunningStatusController: ;(Jump to here if the current message is Running Status, i.e. no Status Byte) movf BYTE,W ; movwf TEMP1 ;here TEMP1 contains controller number getval: call getByte ;and BYTE contains the new controller value btfsc BYTE,7 ;*!!!*Check that the fetched byte is NOT a System Real-Time message. Real-time messages are goto getval ;not supported and must be trapped since they can be sent in the middle of other messages. ;****AT THIS POINT, THE CONTROLLER NUMBER IS IN TEMP1 AND ITS VALUE IS IN BYTE**** ; try123: movlw d'123' ;*********Check for controller 123, data =0: All Notes Off*************** xorwf TEMP1,W ;If it is, then (a) clear the notestack and (b) turn the gate pulse off. btfss STATUS,Z ; goto try100 ; movf BYTE,F ; btfss STATUS,Z ;make sure data byte = 0, otherwise message is not a valid 'All Notes Off' instruction. goto try100 ; call clearNoteStack ; goto turnGateOff ; ; try100: movlw d'100' ;*********Check to see if the controller number is 100- if so then output to FILTER******** xorwf TEMP1,W ; btfss STATUS,Z ; goto try101 ; movf BYTE,W ;Put data onto Port B movwf PORTB ; bsf PORTA,0 ;Make sure FILTER DAC is selected (Port A bit 0 = 1) bcf PORTA,1 ;Pulse Port A bit 1 low to load data onto the DAC goto start ; ; ; try101: ;NO FURTHER CONTROLLERS TO BE IMPLEMENTED IN THIS VERSION goto start handleSystemMessage: ;Handle system messages here. ; movf BYTE,W ;This section kills the record of the last status byte if the received System Message andlw b'11111000' ;byte is NOT a system real-time message. xorlw b'11111000' ; movlw 0 ; btfss STATUS,Z ; movwf LASTSTATUSBYTE ; ; ; *** [handling routines for System Messages should be placed here if implemented] *** ; return ;!!! A return must always be made after dealing with System Messages- a jump ;back to start must NOT be made. clearNoteStack: ; movlw b'10000000' ;Clear entire note stack with No Note values (MSB set only; note values are 0 to 127) movwf h'10' ;This subroutine is always called on power-up, but may also be called if Control movwf h'11' ;number 123 is received ('All Notes Off'). movwf h'12' ; movwf h'13' ; movwf h'14' ; movwf h'15' ; movwf h'16' ; movwf h'17' ; movwf h'18' ; movwf h'19' ; movlw b'11111111' ;There is no note to be deleted from the stack, so put an invalid note value movwf SCRATCHNOTE ;into SCRATCHNOTE movlw h'10' ; movwf NOTESP ;Point Note Stack Pointer to bottom of stack return ; end