10 REM > MIDI/src - *MIDI v1.20
   20 REM Redirect SOUND commands to MIDI port
   30 REM Syntax: *MIDI (ON|OFF|<channel>) (&<addr>)
   40 :
   50 REM v1.00 20-Oct-1989 Initial version
   60 REM v1.01 20-Dec-1989 Added <addr> parameter
   70 REM v1.02 15-Jan-1991 Modifed clock for EMR interface
   80 REM v1.10 20-Mar-1992 Tidied up code, added <channel> parameter
   90 REM v1.11 21-Mar-1992 Added default parameters
  100 REM v1.12 25-Jul-2010 Works out clock setting
  110 REM v1.13 28-Jul-2010 Some optimisation and tidying
  120 REM v1.20 30-Jul-2010 AllOff loops from SOUND x,-N,x,x, ignores chan>16
  130 :
  140 OSARGS=&FFDA:OSBYTE=&FFF4:WORDV=&20C
  150 :
  160 DIM mcode% &280:load%=&FFFF0900
  170 ptr=&A8:num=&AA:regA=&EF:ctrl=&F0
  180 :
  190 FOR P=0 TO 1
  200   P%=load%:O%=mcode%
  210   [OPT P*3+4
  220   .defAddr:EQUB &FCF0 AND 255          :\ Default I/O port
  230   .defClk :EQUB 0                      :\ Default clock setting 0,&15,&16
  240   EQUS "v1.20":EQUS STRING$(1,CHR$0)   :\ Spare padding
  250   :
  260   .RdNum
  270   LDX #0
  280   .RdNumLp
  290   LDA (ptr),Y:CMP #ASC"0":BCC RdNumEnd
  300   CMP #ASC"9"+1:BCC P%+4:SBC #7
  310   AND #15:STA num
  320   TXA:ASL A:ASL A:ASL A:ASL A
  330   ORA num:TAX:INY:BNE RdNumLp
  340   .RdNumEnd
  350   TXA:RTS
  360   .SkipSpc
  370   LDA (ptr),Y:INY:CMP #ASC" ":BEQ SkipSpc:RTS
  380   :
  390   .go%
  400   LDX defAddr:JSR setPorts             :\ Set default I/O port
  410   LDA defClk:STA mClock                :\ Set default Clock
  420   LDA #1:LDX #ptr:LDY #0:JSR OSARGS
  430   LDA (ptr),Y:CMP #13:BEQ initON       :\ *MIDI -> Do *MIDI ON
  440   AND #&DF:CMP #ASC"O":BNE initNum     :\ Not *MIDI ON/OFF
  450   INY:LDA (ptr),Y:AND #&DF
  460   CMP #ASC"N":BEQ initON               :\ *MIDI ON
  470   CMP #ASC"F":BEQ initOFF:DEY          :\ *MIDI OFF
  480   .initNum
  490   CMP #ASC"&"AND&DF:BEQ initAddr       :\ &nnnn - I/O Address
  500   JSR RdNum:CMP #&10:BCC P%+4:SBC #7   :\ Get channel as dec or hex
  510   JSR midiSetChannel                   :\ Convert 1..16 to 0..15
  520   JSR SkipSpc:CMP #ASC"&":BNE initON
  530   .initAddr
  540   INY:JSR RdNum:JSR setPorts           :\ Set I/O address
  550   .initON
  560   LDX mClock:BNE initClock             :\ Use coded default
  570   LDA #&15:STA mClock:JSR midiReset    :\ Set to Divide By 16
  580   PHP:SEI:LDY #4                       :\ Time TxData clearing, X=0
  590   .initLp1:LDA #0:JSR midiOutWR        :\ Send note=0,vel=0
  600   .initLp2:DEX:JSR midiTxRDY:BEQ initLp2
  610   DEY:BNE initLp1:PLP:TXA:AND #&40     :\ X should be about &B0
  620   BEQ initClock:INC mClock             :\ If not, use Divide By 64
  630   .initClock
  640   JSR midiReset                        :\ Reset ACIA and set Clock
  650   JSR cmpWORDV:BEQ initExit            :\ Already redirected
  660   LDA WORDV+0:STA oldWORDV+0
  670   LDA WORDV+1:STA oldWORDV+1
  680   LDA #midiWORD AND 255:STA WORDV+0
  690   LDA #midiWORD DIV 256:STA WORDV+1
  700   .initExit
  710   RTS
  720   .initOFF
  730   JSR cmpWORDV:BNE initOffExit         :\ Not redirected
  740   LDA oldWORDV+0:STA WORDV+0
  750   LDA oldWORDV+1:STA WORDV+1
  760   .initOffExit
  770   RTS
  780   :
  790   .midiReset
  800   LDA #3:JSR midiReset2
  810   LDA #&15:]:mClock=P%-1:[OPT P*3+4
  820   .midiReset2
  830   STA &FCF0:]:mCommand=P%-2:[OPT P*3+4:RTS
  840   .setPorts
  850   STX mCommand:STX mStatus:INX:STX mData:RTS
  860   .cmpWORDV
  870   LDA WORDV+0:CMP #midiWORD AND 255:BNE cmpDone
  880   LDA WORDV+1:CMP #midiWORD DIV 256:.cmpDone
  890   .midiCORE                            :\ Resident code starts here
  900   .noNewNote
  910   RTS
  920   :
  930   \ OSWORD intercept
  940   \ ----------------
  950   .midiWORD
  960   CMP #7:BEQ midiSOUND
  970   .oldWORD
  980   JMP (oldWORDV)
  990   .oldSOUND
 1000   LDX ctrl+0:LDY ctrl+1
 1010   LDA #7:BNE oldWORD
 1020   :
 1030   .midiSOUND
 1040   STX ctrl+0:STY ctrl+1:LDY #0
 1050   LDA (ctrl),Y:AND #15:BEQ oldSOUND    :\ Channel 0, pass to MOS
 1060   TAX:INY:LDA (ctrl),Y:INY             :\ X=1..15, up to 15 notes
 1070   CMP #&FE:BEQ midiControl             :\ SOUND -257 -> MIDI Control
 1080   CMP #&20:BCS oldSOUND                :\ Channel >= &2000, pass to MOS
 1090   CMP #&10:BCS noNewNote               :\ Ignore &Hxxx notes
 1100   LDA lastnote,X:BMI noOldNote:PHA     :\ Nothing being played
 1110   LDA #&80:STA lastnote,X:JSR midiCmd  :\ Send NoteOff to current channel
 1120   PLA:JSR midiOut:JSR midiOutZero      :\ Send note and velocity
 1130   .noOldNote
 1140   :
 1150   \ At this point Carry=CC
 1160   LDA (ctrl),Y:BEQ noNewNote           :\ Volume=0
 1170   SBC #0:EOR #255:BPL P%+4:LDA #15     :\ Convert 0..-15 to 0..15
 1180   ASL A:ASL A:ASL A:PHA                :\ Convert to velocity and save
 1190   LDA #&90:JSR midiCmd                 :\ Send NoteOn to current channel
 1200   LDY #4:LDA (ctrl),Y                  :\ Pitch
 1210   LSR A:LSR A:CLC:ADC #35:STA lastnote,X :\ Note number
 1220   JSR midiOut:PLA:BPL midiOut          :\ Send note and velocity
 1230   \ JSR midiOut
 1240   \ BIT &262\BMI oldSOUND\RTS
 1250   :
 1260   SOUND -258,Command >&7F,Note,Velocity - send raw output
 1270   SOUND -257,Command >&7F,Note,Velocity - send to current channel
 1280   SOUND -257,Data <&80,0,0              - send raw data byte
 1290   SOUND -257,<0,param1,param2           - midi control
 1300   \ -----------------------------------------------------
 1310   \ X=Channel low nybble, Y=>Command low byte
 1320   .midiControl
 1330   CPX #&0F                             :\ CS=SOUND -257, CC=SOUND -258
 1340   LDA (ctrl),Y:TAX                     :\ Get Command byte
 1350   INY:LDA (ctrl),Y:BMI midiSystem      :\ SOUND -257/8,<0   - System control
 1360   TXA:BPL midiOut                      :\ SOUND -257/8,<&80 - send raw byte
 1370   JSR midiCmdRaw                       :\ SOUND -257/8,>&7F,note,vol
 1380   CMP #&F0:BCS midiCtrlDone:PHA        :\ &Fx, one byte only
 1390   INY:LDA (ctrl),Y:CMP #&80:PHP        :\ Copy b7 of note number to Carry
 1400   AND #127:JSR midiOut                 :\ Send note number
 1410   PLP:INY:LDA (ctrl),Y:ASL A           :\ Copy previous b7 into b0
 1420   INY:ORA (ctrl),Y:AND #127:TAX        :\ Merge with velocity
 1430   PLA:CMP #&E0:BCS midiCtrl3           :\ &Ex, send third byte
 1440   CMP #&C0:BCS midiCtrlDone            :\ &Cx/&Dx, have sent two bytes
 1450   .midiCtrl3
 1460   TXA:BPL midiOut                      :\ Send velocity
 1470   :
 1480   SOUND -257/8,<0,0,0 - MIDI system control
 1490   \ -----------------------------------------
 1500   \ A=&80-&FF, X=Command low byte &00-&FF, Y=>Command high byte
 1510   .midiSystem
 1520   INY:LDA (ctrl),Y:BNE midiSetChannel  :\ SOUND -257/8,-1,n,x
 1530   \
 1540   SOUND -257,-1,0,0 - All Notes Off
 1550   \ ---------------------------------
 1560   DEX                                  :\ Loop SOUND x,1-count,x,x times
 1570   .midiAllLp1
 1580   TAY                                  :\ Start at note 0
 1590   .midiAllOff
 1600   LDA #&80:JSR midiCmd:TYA:JSR midiOut :\ Send Note Off for all notes
 1610   JSR midiOutZero:INY:BPL midiAllOff
 1620   INX:BNE midiAllLp1                   :\ Loop twice for SOUND x,-1,x,x
 1630   TYA:LDX #15                          :\ A=&80 for Note Off
 1640   .midiAllLp2
 1650   STA lastnote,X:DEX:BNE midiAllLp2    :\ Clear last note buffer
 1660   .midiCtrlDone
 1670   RTS
 1680   :
 1690   SOUND -257,-1,chan,0 - set MIDI channel
 1700   \ ---------------------------------------
 1710   .midiSetChannel
 1720   BMI midiCtrlEnable                   :\ SOUND -257/8,nn,<0,0
 1730   CLC:SBC #0:AND #15:STA mChannel      :\ SOUND -257/8,-1,chn,0 - set chan
 1740   INX                                  :\ X=0, enable MOS sound
 1750   \
 1760   SOUND -257,-255,-1,0 - disable MOS sound
 1770   SOUND -257,-256,-1,0 - enable MOS sound
 1780   \ ---------------------------------------------------
 1790   .midiCtrlEnable
 1800   STX &0262:RTS                        :\ Set MOS sound and return
 1810   :
 1820   .midiOutZero
 1830   LDA #0
 1840   .midiCmdRaw
 1850   BCC midiOut                          :\ CS=SOUND -257, CC=SOUND -258
 1860   .midiCmd
 1870   PHA:AND #&F0:STA regA:PLA            :\ Save command nybble
 1880   CLC:ADC #0:]:mChannel=P%-1:[OPT P*3+4:\ Add channel to command byte
 1890   AND #&0F:ORA regA                    :\ Mask channel and add in command
 1900   .midiOut:PHA                         :\ midiCmd always returns CC
 1910   .midiOutLp
 1920   BIT &FF:BMI midiOutEsc               :\ Abort if Escape pending
 1930   JSR midiTxRDY:BEQ midiOutLp          :\ Loop until TxData empty
 1940   .midiOutEsc:PLA
 1950   .midiOutWR
 1960   STA &FCF1:]:mData=P%-2:[OPT P*3+4
 1970   RTS
 1980   .midiTxRDY
 1990   LDA &FCF0:]:mStatus=P%-2:[OPT P*3+4:AND #2
 2000   .lastnote
 2010   RTS
 2020   EQUS STRING$(15,CHR$&80)             :\ Last note sent
 2030   \ If run out of space, use &0870-&087F for note buffer
 2040   :
 2050   .oldWORDV
 2060 ]:NEXT:PRINT"0";~2+P%AND&FFFF
 2070 IFmidiCORE<&09C0:PRINT"WARNING: Overlaps ENVELOPEs"
 2080 IFP%>&FFFF0AFE:PRINT"WARNING: Overlaps soft keys"
 2090 :
 2100 A$="*SAVE MIDI "+STR$~mcode%+" "+STR$~O%+" "+STR$~(go%OR&FFFF0000)+" "+STR$~load%
 2110 PRINTA$;:OSCLIA$:PRINT