;**  MIDI SWI code **

ConnectAllVoices ROUT ; connect voice 2 to all channels (or voice 1 if 2 unavailable))
; use voice 2 in preference because voice 1 is usually weedy beep,
; while voice 2 is good stringlib
     STMFD   sp!,{r0-r3, lr}
     MOV     r0, #0
     MOV     r1, #0
     SWI     XSound_InstallVoice ; gives max voices + 1 in r1
     LDMVSFD sp!,{r0-r3, pc}^    ; error return quietly
     CMP     r1, #1
     LDMLEFD sp!,{r0-r3, pc}^    ; no voices installed return quietly
     CMP     r1, #2
     MOV     r3, #1   ; voice
     MOVGT   r3, #2   ; if more than 1 voice generator attach 2nd voice to all channels
     MOV     r2, #1   ; channel
10   MOV     r1, r3   ; voice -> r1
     MOV     r0, r2   ; channel -> r0
     SWI     XSound_AttachVoice
     LDMVSFD sp!, {r0-r3, pc}^  ; exit quietly if error
     ADD     r2, r2, #1 ; next channel
     CMP     r2, #8
     BLE     %BT10
     LDMFD   sp!,{r0-r3, pc}^

AddrInterfaceEntry ROUT
     CMP     R11, #(EndOfSWIJumpTable-SWIJumpTable)/4
     MOVCSS  PC, lr
     STMFD   sp!, {R2-R10, lr}
; R12 is workspace pointer
     B       %FT01

MIDI_SWI_Code
     CMP     R11, #(EndOfSWIJumpTable-SWIJumpTable)/4
     MOVCSS  PC, lr
     STMFD   sp!, {R2-R10, lr}
     LDR     R12, [R12]     ; workspace pointer
01   TEQP    pc, #3   ; enable irq
     MOV     r9, #0 ; default Port to use
     LDR     r2, [r12, #ModeFlagBits]
     MOV     r8, r2, LSR#NExtraPortsShift
     ANDS    r8, r8, #3  ; number of extra Ports installed
; enter all swis with:
; svc mode; irq enabled
; r2-r10,lr saved
; r2 = modeflagbits (nb must not write back cos irq enabled)
; r8 = n extra Ports installed (0-3) (n+1 = number of Ports)
; z-bit set according to r8 (EQ is true for one Port only installed)
; all SWIs should behave compatibly for 1 Port installed; and may
; be slightly different for more than 1
     ADD     PC, PC, R11, LSL #2
     B       SWIExit            ; dummy instruction
SWIJumpTable
     B       SoundEnableSWI               ;1
     B       SetModeSWI                   ;2
     B       SetTxChannelSWI              ;3
     B       SetTxActiveSensingSWI        ;4
     B       InqSongPositionPointerSWI    ;5
     B       InqBufferSizeSWI             ;6
     B       InqErrorSWI                  ;7
     B       RxByteSWI                    ;8
     B       RxCommandSWI                 ;9
     B       TxByteSWI                    ;10
     B       TxCommandSWI                 ;11
     B       TxNoteOffSWI                 ;12
     B       TxNoteOnSWI                  ;13
     B       TxPolyKeyPressureSWI         ;14
     B       TxControlChangeSWI           ;15
     B       TxLocalControlSWI            ;16
     B       TxAllNotesOffSWI             ;17
     B       TxOmniModeOffSWI             ;18
     B       TxOmniModeOnSWI              ;19
     B       TxMonoModeOnSWI              ;20
     B       TxPolyModeOnSWI              ;21
     B       TxProgramChangeSWI           ;22
     B       TxChannelPressureSWI         ;23
     B       TxPitchWheelSWI              ;24
     B       TxSongPositionPointerSWI     ;25
     B       TxSongSelectSWI              ;26
     B       TxTuneRequestSWI             ;27
     B       TxStartSWI                   ;28
     B       TxContinueSWI                ;29
     B       TxStopSWI                    ;30
     B       TxSystemResetSWI             ;31
     B       IgnoreTimingSWI              ;32
     B       SynchSoundSchedulerSWI       ;33
     B       FastClockSWI                 ;34
     B       InitSWI                      ;35
     B       SetBufferSizeSWI             ;36
     B       InterfaceSWI                 ;37
EndOfSWIJumpTable

GetConfig  ; get and store current configured number of channnels
     STMFD   sp!, {r0-r4, lr}
     MOV     r0, #0
     MOV     r1, #0
     MOV     r2, #0
     MOV     r3, #0
     MOV     r4, #0
     SWI     XSound_Configure
     STRVC   r0, [r12, #SoundConfiguration]
     LDMVCFD sp!, {r0-r4, pc}^
     STR     r0, [sp]            ; return with error if error
     LDMFD   sp!, {r0-r4, pc}
SetConfig   ; set an arbitrary number of channels (number in r3)
     STMFD   sp!, {r0-r4, lr}
     MOV     r0, r3
     MOV     r1, #0
     MOV     r2, #0
     MOV     r3, #0
     MOV     r4, #0
     SWI     XSound_Configure
     LDMVCFD sp!, {r0-r4, pc}^
     STR     r0, [sp]            ; return with error if error
     LDMFD   sp!, {r0-r4, pc}

InterfaceSWI
; supply and return addresses to call for more efficient interface to this module
;
; Out: r0 = workspace pointer to call me with
;      r1 = external accessible address of SWI MIDI_, with r11 = SWIoffset from SoundEnable
     MOV     r0, r12
     ADR     r1, AddrInterfaceEntry
     B       SWIExit

SoundEnableSWI ; enter with R0 =0 to disconnect sound system
              ;                =1 for connection to MIDI IN data
              ;                =2 for connection to MIDI OUT data
              ;  + 2 x Port number if more than 1 Port
; r1 = 1 to enable touch sensitivity
;    = 2 to disable touch sensitivity
     ANDEQ   r0, r0, #3        ; if only 1 Port installed
     TEQP    pc, #I_bit:OR:3     ; disable irq
     BL      DoAllNotesOff
     MOV     R4, #0       ; first clear state of sound system driver
     ADD     R2, R12, #ClearSpaceStart ; start of workspace to clear
     ADD     R3, R12, #ClearSpaceEnd   ; end of wkspace to clear
SSClearSoundSys
     STR     R4, [R2], #4         ; clear sound system driver workspace
     CMP     R2, R3
     BLT     SSClearSoundSys
     LDR     R4, [R12, #ModeFlagBits]
     TST     r4, #SoundEnableBit
     BLEQ    GetConfig     ; if sound not enabled store no. of channels set
     BVS     SWIExit
     SUBS    r0, r0, #1  ; minus if r0 was 0
     BPL     %FT10
; disable sound
     ADD     r0, r0, #1  ; restore r0 value
     BIC     r4, r4, #SoundEnableBit   ; disable sound
     STR     r4, [r12, #ModeFlagBits]
     LDR     r3, [r12, #SoundConfiguration]
     BL      SetConfig   ; restore old number of channels
     B       SWIExit
10   ORR     r4, r4, #SoundEnableBit ; set sound enable bit if not 0
     BL      ConnectAllVoices  ; ensure voices are connected to all channels if sound system is to be used
     MOV     r3, #MaxVoices
     BL      SetConfig   ; set new number of channels. Expect no error
     MOV     r9, r0, LSR#1 ; Port number requested
     CMP     r9, r8 ; check if it is in the range of the number of Ports installed
     MOVGT   r9, #0
     TST     r0, #1      ; bit 0 is in/out connection
     BICEQ   R4, R4, #ConnectionBit ; transfer value of R0 to connection bit
     ORRNE   R4, R4, #ConnectionBit
     ADD     r3, r12, r9, LSL#2 ; Port offset
     LDREQ   R2, [r3, #RxBufferPntrs] ; attach to appropriate buffer
     LDRNE   R2, [r3, #TxBufferPntrs] ; according to value of connection bit
     BIC     r2, r2, #&FF000000
     BIC     r2, r2, #&00FF0000  ; get IN pointer
     STR     R2, [R12, #BufferSoundOutPntr]
     BEQ     %FT01
     ADD     r3, r12, r9   ; Port offset
     LDRB    R2, [r3, #SendStatus] ; get the current running status if connecting to MIDI out
     CMP     R2, #&F0                  ; ignore system real time
     BGE     %FT01
     STRB    R2, [r12, #RunningStatus]
     MOV     R3, #&20    ; default expect 2 bytes
     CMP     R2, #ChannelPressureVal  ; IF Channel pressure
     CMPNE   R2, #ProgramChangeVal    ; OR Program change
     MOVEQ   R3, #&10    ; THEN expect 1 byte
     STRB    R3, [R12, #ByteCounter]  ; number of bytes for this status
01   CMP     r1, #1            ; set or unset touch sensitivity bit
     BICEQ   r4, r4, #TouchSenseOff
     CMP     r1, #2
     ORREQ   r4, r4, #TouchSenseOff
     STR     R4, [R12, #ModeFlagBits]
     STRB    r9, [r12, #InterpretChannel] ; which Port buffer to interpret data from
     ADD     r0, r0, #1  ; restore r0 value
     B       SWIExit

SetTxChannelSWI ROUT ; input 1-16, 0 to read current setting
; if multiPorts are installed, input 17-32 for port 2, 33-48 for p3, 49-64 for p4
     AND     R0, R0, #&FF
     CMP     R0, #0
     LDREQB  R0, [R12, #TxChannel]    ; if 0 then return current channel
     ADDEQ   R0, R0, #1
     BEQ     SWIExit
     MOV     r9, #0 ; Port asked for
     MOVS    r8, r8 ; n Ports installed set flags
     BEQ     %FT10  ; only 1 Port installed
; multi Ports..check number input against number of installed Ports
     SUB     r2, r0, #1   ; channel range 0-15
     MOV     r9, r2, LSR#4 ; Port number
     CMP     r9, r8
     BLE     %FT20  ; valid multi-Port number. nb neednt test for -ve now
10   CMP     R0, #MaxChannelNumber
     MOVGT   R0, #MaxChannelNumber  ; = 16
     SUB     R2, R0, #1   ; channel range 0-15
20   STRB    R2, [R12, #TxChannel]
     MOV     R2, #0
     ADD     r3, r12, r9 ; Port offset
     STRB    R2, [r3, #SendStatus]  ; reset running status 
     B       SWIExit

SetModeSWI ROUT; mode bits are inverted -1. R1 byte 0 is N, byte 1 is M
     AND     R0, R0, #&FF    ; mode1 -> 11, 2 -> 10, 3 -> 01, mode4 -> 00
     AND     R3, R1, #&FF    ; new N  (N is channel number)
     MOV     R4, R1, LSR#8
     AND     R4, R4, #&FF   ; new M  (M is number of channels in mode 4)
     LDRB    R7, [R12, #CurrentChannel]
     TEQP    pc, #I_bit:OR:3     ; disable irq
     LDR     R6, [R12, #ModeFlagBits]   ; get mode byte
;change mode
     CMP     R0, #0
     BEQ     ChangeChannel
     CMP     R0, #4
     MOVGT   R0, #4
     SUB     R2, R0, #1
     MOV     r2, r2, LSL#ModeFlagsShift
     EOR     R2, R2, #OmniBit:OR:PolyBit ; invert new mode bits
     BIC     R6, R6, #OmniBit:OR:PolyBit ; clear old mode bits
     ORR     R6, R6, R2    ; Update mode
ChangeChannel     
     CMP     R3, #0        ; if R1=0 then return current N and M in R1
     BEQ     ChangeM
     CMP     R3, #16
     MOVGT   R3, #16
     SUB     R7, R3, #1    ; update current channel
ChangeM
     CMP     R4, #0
     BEQ     ModeSetExit
     CMP     R4, #8
     MOVGT   R4, #8
     SUB     R4, R4, #1    ; M-1
     STRB    r4, [r12, #MonoChannels]  ; store new M
ModeSetExit
     STR     R6, [R12, #ModeFlagBits]
     TEQP    pc, #3     ; enable irq
     STRB    R7, [R12, #CurrentChannel]
     EOR     R0, R6, #OmniBit:OR:PolyBit ; convert 2 mode bits to mode number
     AND     R0, R0, #OmniBit:OR:PolyBit
     MOV     r0, r0, LSR#ModeFlagsShift
     ADD     R0, R0, #1    ; return current mode number in R0
     ADD     R1, R7, #1    ; N+1 = channel number
     LDRB    r4, [r12, #MonoChannels]
     ADD     R4, R4, #1      ; convert from M-1 to M
     ORR     R1, R1, R4, LSL#8 ; combine M into byte 1 of R1 to return
     B       SWIExit

SetTxActiveSensingSWI ROUT ;cause byte or AS to be sent every 20 cs (200ms)
; if multi Ports then send AS from Port number in r0 bits 1 and 2
; return bits 0-3 of r0 set according to state of transmitted active sensing in Ports 0-3
; return bits 4-7 of r0 set according to state of received active sensing in Ports 0-3
     ANDEQ   r0, r0, #1  ; only one Port
     TEQP    pc, #I_bit:OR:3     ; disable irq
     MOVS    r0, r0
     BMI     %FT10       ; negative input = just read current status
     MOV     r9, #0
     TST     r0, #1              ; AS on or off?
     MOVEQ   r2, #0               ; clear count
     MOVNE   r2, #MaxTxInactivity  ; set count to send AS byte immediately
     MOV     r9, r0, LSR#1 ; Port number
     CMP     r9, r8        ; max Ports
; non-zero inactive time indicates that this Port is active-sensing
; count of zero means disabled
     ADDLE   r3, r12, r9         ; offset
; (do not update if invalid Port; just read current status
     STRLEB  r2, [r3, #TxInactiveTime]  ; store byte
; clear any unnecessary counters
10   LDR     r2, [r12, #TxInactiveTime] ; get whole word
     CMP     r8, #0     ; if only one Port clear all other active-sense counters
     ANDEQ   r2, r2, #&FF
     CMP     r8, #2
     BICLT   r2, r2, #&FF0000   ; at most 2 Ports
     BICLE   r2, r2, #&FF000000 ; at most 3 Ports
     STR     r2, [r12, #TxInactiveTime] ; ( ensure consistency )
     MOVS    r2, r2                     ; test if 0
     LDR     R6, [R12, #ModeFlagBits]   ; get mode flags
     BICEQ   R6, R6, #TxActiveSenseBit ; clear Tx active sense bit if AS off
     ORRNE   R6, R6, #TxActiveSenseBit ; Set Tx AS bit
     STR     R6, [R12, #ModeFlagBits]    ; set active sensing mode
; return tx active sensing states in r0 bits 0-3
     MOV     r0, #0
     TST     r2, #&FF
     ORRNE   r0, r0, #1
     TST     r2, #&FF00
     ORRNE   r0, r0, #2
     TST     r2, #&FF0000
     ORRNE   r0, r0, #4
     TST     r2, #&FF000000
     ORRNE   r0, r0, #8
     TST     r6, #RxActiveSenseBit
     BEQ     SWIExit
     LDR     r2, [r12, #RxInactiveTime] ; get rx active sensing word = 4 counters
; set rx active sensing bits 4-7
     TST     r2, #&FF
     ORRNE   r0, r0, #&10
     TST     r2, #&FF00
     ORRNE   r0, r0, #&20
     TST     r2, #&FF0000
     ORRNE   r0, r0, #&40
     TST     r2, #&FF000000
     ORRNE   r0, r0, #&80
     B       SWIExit

InqSongPositionPointerSWI ROUT
     LDR     r0, [R12, #SongPosPointer]  ; must divide by 6
     MOV     r2, r0          ; r2 = SPP
     MOV     r3, #3
     DivRem  r0, r2, r3, r4  ; r0 = r2 / 3
     MOV     r0, r0, LSR#1   ; /2
     LDR     r2, [R12, #ModeFlagBits]
     MOV     r1, #0
     TST     r2, #ExternalCount
     ORRNE   r1, r1, #1:SHL:0
     TST     r2, #InternalCount
     ORRNE   r1, r1, #1:SHL:1
     TST     r2, #FastClock
     ORRNE   r1, r1, #1:SHL:2
     TST     r2, #Version3Facilities
     ORRNE   r1, r1, #1:SHL:3
     TST     r2, #StoreSysRealTime
     ORRNE   r1, r1, #1:SHL:4
     TST     r2, #SysRealTimeNoExec
     ORRNE   r1, r1, #1:SHL:5
     B       SWIExit         ; r0 = SPP/6

InqBufferSizeSWI ROUT
; r0 = 0 for rx buffer, 1 for tx buffer of Port 1
;    = 2 for rx buffer, 3 for tx buffer of Port 2  etc.
     MOV     r9, #0
     BEQ     %FT10 ; only 1 Port installed; behave as before
     MOV     r9, r0, LSR#1 ; Port number -> r9    
     CMP     r9, r8 ; compare input parameter with max Ports
     MOVGT   r0, #0
     BGT     SWIExit       ; exit with zero if non-existent Port
10   TST     r0, #1   ; rx buffer = 0; tx buffer = 1
     ADD     r3, r12, r9, LSL#2 ; buffer offset
     LDREQ   R0, [R3, #RxBufferPntrs]
     LDRNE   R0, [R3, #TxBufferPntrs]
     MOV     r4, r0, LSR#16        ; out pointer
     BIC     r0, r0, #&FF000000
     BIC     r0, r0, #&00FF0000    ; in pointer
     LDREQ   r8, [r12, #RxBufferLen] ; length of rx buffer
     MOVNE   r8, #TxBufferLen        ; length of tx buffer
     SUBS    r0, r4, r0          ; r0 = out pointer - in pointer
     ADDLE   r0, r0, r8          ; add buffer length if negative or zero (circular buffer)
     B       SWIExit

InqErrorSWI  ROUT
; return and reset all 4 error bytes (for max 4 Ports)
     TEQP    pc, #I_bit:OR:3     ; disable irq
     LDR     R0, [r12, #ErrorFlag]
     MOV     R2, #0
     STR     R2, [r12, #ErrorFlag]
     B       SWIExit

GetNextByte ; r9 = requested buffer; r2 = number of byte in command for rx error reporting
; return r0 = byte; r1 = received time, or -1 or -2 for receive errors (Overrun and Framing)
     CMP     r4, r5   ; Check if buffer has data
     ORREQS  pc, lr, #V_bit   ; if not return with V-set = abort
     STMFD   sp!, {r3,r5-r6, lr}
     ADD     r3, r12, r9, LSL#2
     LDR     r6, [r3, #RxBufferStarts]
     LDRB    r0, [r6, r4]  ; Load rx'd data from rx buffer
     LDR     r6, [r3, #RxTimesBufferStarts]
     LDR     r1, [r6, r4, LSL#2]  ; load received time of byte
     CMP     r1, #-1        ; overrun error when byte received
     CMPNE   r1, #-2        ; framing error when byte received
     MOVEQ   r3, #0
     STREQ   r3, [r6, r4, LSL#2]  ; reset received time to zero to ensure error is ignored next time
     LDMEQFD sp!, {r3,r5-r6, lr}
     ORREQS  pc, lr, #V_bit   ; return V-set showing error
     ADD     r2, r2, #1       ; byte number in this command for rx error reporting
     ADD     R4, R4, #1    ; increment buffer pointer
     LDR     r3, [r12, #RxBufferLen] 
     CMP     R4, r3
     MOVGE   R4, #0        ; circular buffer, reset pointer
     TST     R0, #&80      ; status byte?
     BEQ     GNBDataByte
     AND     r5, R0, #&F0        ; clear bottom 4 bits of status byte
     CMP     r5, #&F0            ; sys msg?
     BNE     GNBNotSysMsg
     MOV     r6, #&0             ; expect 0 data bytes for sys msg, except..
     CMP     R0, #SongPosPointerVal ; song position pointer status
     MOVEQ   r6, #&20             ; expect 2 data bytes
     BEQ     GNBExpectBytes
     CMP     R0, #SongSelectVal     ; song select status
     CMPNE   R0, #&F0               ; system exclusive message.
     MOVEQ   r6, #&10             ; expect 1 data byte
     B       GNBExpectBytes
GNBNotSysMsg
     MOV     r6, #&20    ; default expect 2 bytes
     CMP     r5, #ChannelPressureVal  ; IF Channel pressure
     CMPNE   r5, #ProgramChangeVal    ; OR Program change
     MOVEQ   r6, #&10    ; THEN expect 1 byte
GNBExpectBytes
     CMP     r6, #0      ; if data byte(s) expected 
     ADD     r3, r12, r9 ; buffer offset for Port requested
     STRNEB  R0, [r3, #RxStatus]    ; then store in running status
     LDREQB  r6, [r3, #RxByteCounter]
     ORREQ   r6, r6, #1:SHL:7        ; else set real-time status bit
     STRB    r6, [r3, #RxByteCounter]  ; number of bytes for this status
     LDMFD   sp!, {r3,r5-r6, pc} ; z-set = message finished
GNBDataByte
     ADD     r3, r12, r9 ; buffer offset for Port requested
     LDRB    r5, [r3, #RxByteCounter] ;LS 4bits is counter : MS 4b is expected
     ADD     r5, r5, #1   ; increment bytes-received counter
     MOV     r6, r5, LSR#4  ; expected bytes
     AND     R3, r5, #&F    ; received bytes
     CMP     R3, r6   ; have I got enough data bytes yet?
     BICEQ   r5, r5, #&F ; if so then reset byte counter
     ADD     r3, r12, r9 ; buffer offset for Port requested
     STRNEB  R0, [r3, #RxByte1]  ; else store first data byte
     STRB    r5, [r3, #RxByteCounter] ; store byte counter
     LDMFD   sp!, {r3,r5-r6, pc} ; z-set = message finished

RxByteSWI ROUT; this must store running status and first data bytes for RxCommand
     MOV     r10, #0  ; indicates that this is an rxbyte (not rxcommand)
     B       RxByteEntry

RxCommandSWI ROUT
; if more than one Port is installed then read the number in r0 and read
; the data from the buffer of that Port. If r0 = -1 read each Port until
; data is found
     MOV     r10, #1 ; to indicate later that this is rxcommand, not rxbyte
RxByteEntry
     MOV     r1, #0 ; clear r1 is expected
     MOV     r9, #0 ; number of Port requested
     MOVEQ   r0, #0 ; only one Port installed..clear r0
     BEQ     %FT10  ; only 1 Port
; more than 1 Port is installed so read the requested Port number in r0
; check range is 0-max Port number (1-3)
; set r0 to zero if invalid Port number (if greater than installed number)
     CMP     r0, r8
     MOVGT   r9, #0
     BGT     %FT10   ; invalid r0 set to 0
     MOVS    r9, r0  ; check r0 is positive
     MOVMI   r9, #0  ; if negative, set requested Port number to 0
; 1st check for buffer full error
10   TEQP    pc, #3:OR:I_bit
     LDR     r2, [r12, #ModeFlagBits]
     TST     r2, #RxBufferOverflow  ; test..
     BLNE    TestOverflow ; will not return if the buffer overflow was this port
     TEQP    pc, #3
     ADD     r3, r12, r9, LSL#2 ; buffer pointer offset for Port requested
     ADD     r3, r3, #RxBufferPntrs
     LDR     r5, [r3] ; r3 will be updated on next load
     MOV     r4, r5, LSR#16   ; out pointer
     BIC     r5, r5, #&FF000000
     BIC     r5, r5, #&00FF0000  ; in pointer
     CMP     r4, r5   ; Check if buffer has data and get it
     BNE     %FT20

     CMP     r0, #-1  ;  -1 in r0 = look at all buffers
     MOV     r0, #0   ; return with 0 if no data
     BNE     SWIExit
; special case of r0 = -1 on entry => test all receive buffers for data
; test Port 1 .. there must be more than 1 installed to get here
     MOV     r9, #1
     LDR     r5, [r3, #4]!  ; pre-index, writeback
     MOV     r4, r5, LSR#16   ; out pointer
     BIC     r5, r5, #&FF000000
     BIC     r5, r5, #&00FF0000  ; in pointer
     CMP     r4, r5   ; Check if buffer 1 has data
     BNE     %FT20
     MOV     r9, #2
     CMP     r8, r9        ; ensure at least 3 are installed
     BLT     SWIExit
     LDR     r5, [r3, #4]!
     MOV     r4, r5, LSR#16   ; out pointer
     BIC     r5, r5, #&FF000000
     BIC     r5, r5, #&00FF0000  ; in pointer
     CMP     r4, r5   ; Check if buffer 2 has data
     BNE     %FT20
     MOV     r9, #3
     CMP     r8, r9        ; ensure at least 4 are installed
     BLT     SWIExit
     LDR     r5, [r3, #4]!
     MOV     r4, r5, LSR#16   ; out pointer
     BIC     r5, r5, #&FF000000
     BIC     r5, r5, #&00FF0000  ; in pointer
     CMP     r4, r5   ; Check if buffer 3 has data
     BEQ     SWIExit

; enter with r9 = requested Port, r8 = nPorts
20   MOV     r2, #0  ; count of byte number in command
     CMP     r10, #0    ; was this entered from rxbyte?
     BNE     %FT22      ; rxbyte gets only one byte
     BL      GetNextByte  ; rxbyte
     B       %FT26
22   BL      GetNextByte  ; rxcommand start
     BVS     %FT25     ; v if error or no more bytes to get yet
     BLNE    GetNextByte  ; z flag is set when all instruction bytes are got
     BVS     %FT25
     BLNE    GetNextByte  ; max 3 bytes to get
     MOVNE   r0, #-1  ; check enough bytes rxd. This doesn't happen to rxbyte
25   MOVVS   r0, #-1   ; indicate error
26   CMP     r1, #-1
     CMPNE   r1, #-2  ; error values
     BEQ     RxError
; update buffer pointer
30   TEQP    pc, #3:OR:I_bit ; disable irq
     ADD     r3, r12, r9, LSL#2
     LDR     r5, [r3, #RxBufferPntrs]   ; reload values (in pntr may have been changed in irq)
     BIC     r5, r5, #&FF000000
     BIC     r5, r5, #&00FF0000  ; preserve in pointer
     ORR     r5, r5, r4, LSL#16 ; combine pointers
     STR     r5, [r3, #RxBufferPntrs]    ; R4 is new value
     TEQP    pc, #3   ; enable irq
     CMP     r0, #-1
     MOVEQ   r0, #0
     MOVEQ   r1, #0
     BEQ     SWIExit  ; if whole command not received then exit with zero
     TST     r10, #1    ; was this entered from rxbyte?
     ANDEQ   R2, R2, #1        ; 1 if byte received, else 0
     ORREQ   R0, R0, R2, LSL#24 ; combine byte number into top byte
     ORREQ   r0, r0, r9, LSL#28 ; combine Port number into top nibble
     BEQ     SWIExit    ; if so exit now
     ADD     r3, r12, r9 ; buffer offset for Port requested
     LDRB    R2, [r3, #RxByteCounter]
     TST     R2, #1:SHL:7      ; top bit set = real-time status msg
     BICNE   R2, R2, #1:SHL:7  ; clear real-time status bit if set
     STRNEB  R2, [r3, #RxByteCounter] ; and save
     MOVNE   R2, #0            ; => no data bytes
     MOV     R2, R2, LSR#4
     AND     R2, R2, #3        ; number of data bytes expected (0, 1 or 2)
     CMP     R2, #2
     MOVGT   R2, #2            ; force max of 2
     MOV     r4, R2, LSL#3     ; x8 ie shift in whole bytes in next instruction
     MOV     R0, R0, LSL r4    ; get last byte in the right position in R0
     ADD     R2, R2, #1        ; include status byte (count = 1, 2 or 3)
     ORR     R0, R0, R2, LSL#24 ; combine byte number into top byte
     ORR     r0, r0, r9, LSL#28 ; combine Port number into top nibble
     CMP     R2, #2            ; R2 is 1, 2 or 3
     BLT     SWIExit             ; exit if just status and no data
     LDRB    r4, [r3, #RxStatus]
     ORR     R0, R0, r4          ; combine status into byte 0
     LDRGTB  r4, [r3, #RxByte1]
     ORRGT   R0, R0, r4, LSL#8   ; combine first data byte if 2 data
     B       SWIExit

TxByteSWI ROUT; current running status is ignored and discarded
     MOVNE   r9, r0, LSR#28 ; Port number requested in top nibble
     MOVEQ   r9, #0
     CMP     r9, r8   ; check Port number is not greater than number installed
     MOVGT   r9, #0
10   AND     R0, R0, #&FF ; ensure only bottom byte is used
     ADD     r3, r12, r9, LSL#2
     TEQP    pc, #I_bit:OR:3     ; disable irq
     LDR     R4, [R3, #TxBufferPntrs]
     MOV     r5, r4, LSR#16   ; out pntr
     BIC     r4, r4, #&FF000000
     BIC     r4, r4, #&00FF0000 ; in pntr
     ADD     r6, r4, #1       ; increment buffer in pointer
     CMP     r6, #TxBufferLen
     MOVGE   r6, #0
     CMP     r6, r5    ; Check if buffer is full (pointers are equal)
     BEQ     TxBufferFull
     ORR     r5, r6, r5, LSL#16
     STR     r5, [r3, #TxBufferPntrs] ; update pointer
     LDR     r5, [r3, #TxBufferStarts]
     STRB    r0, [r5, r4]  ; put byte in transmit buffer
     MOV     r6, #0
     ADD     r3, r12, r9
     STRB    r6, [r3, #SendStatus]  ; clear running status.
     LDR     r2, [r12, #ModeFlagBits]
     TST     r2, #InIrq
     BLEQ    EnableTxIRQ
 ; don't actually enable irqs if I am being called (indirectly) from the irq routine
     BLNE    EnablePretendTxIRQ
     B       SWIExit

TxCommandSWI ROUT; This does use current running status, but does not make the
; noteoff -> noteon optimisation, and does not use the value of TxChannel
; if new facilities enabled then inspect r1 for schedule time
     MOVNE   r9, r0, LSR#28 ; Port number requested in top nibble
     MOVEQ   r9, #0
     CMP     r9, r8         ; check Port number is not greater than number installed
     MOVGT   r9, #0
10   TST     R0, #&80       ; is there a status byte in the right place?
     BEQ     SWIExit        ; if not then ignore it
     MOVS    r1, r1         ; is there a schedule time in r1?
     LDRNE   r2, [r12, #ModeFlagBits]
     TSTNE   r2, #Version3Facilities ; check new facilities enabled
     BLNE    Schedule       ; if so schedule it and exit
     BNE     SWIExit   ; this will be re-entered in this routine when the schedule time arrives
; find command byte number by comparing status with known values
     MOV     R2, R0, LSR#24 ; this ensures that if I dont recognise the status
     AND     R2, R2, #3     ; then it will default to using the supplied byte#
     AND     R3, R0, #&F8   ; get status
     CMP     R3, #&F8       ; sys real time?
     MOVEQ   R2, #1         ; system real time has 1 byte
     BEQ     AssembleCommand
     AND     R3, R0, #&FF
     CMP     R3, #TuneRequestVal
     CMPNE   R3, #EOXVal
     MOVEQ   R2, #1            ; 1 byte for these two
     BEQ     AssembleCommand
     CMP     R3, #SongSelectVal
     MOVEQ   R2, #2            ; 2 byte for Song sel
     BEQ     AssembleCommand
     AND     R3, R0, #&F0
     CMP     R3, #ControlChangeVal
     MOVLE   R2, #3            ; 3 for <= ControlChange (=8,9,A,B)
     BLE     AssembleCommand
     CMP     R3, #PitchWheelVal
     MOVEQ   R2, #3
     BEQ     AssembleCommand
     CMP     R3, #ProgramChangeVal
     CMPNE   R3, #ChannelPressureVal
     MOVEQ   R2, #2            ; only 2 bytes for these 2 commands
     BEQ     AssembleCommand
AssembleCommand  ROUT;  R2 contains number of bytes (including status)
     SUBS    R4, R2, #1          ; 0, 1 or 2
     MOV     R5, R4, LSL#3       ; x8   ie 0, 8 or 16
     AND     R3, R0, #&FF        ; status in byte 0
     MOV     R3, R3, LSL R5      ; shift status to right position
     BEQ     SendBytes           ; if R4 = 0 and R2 = 1
     SUBS    R4, R4, #1          ; 0 or 1
     MOVEQ   R7, R0, LSR#8       ; shift data byte 1 to right position
     ANDEQ   R7, R7, #&FF
     ANDNE   R7, R0, #&FF00
     ORR     R3, R3, R7
     MOVNE   R7, R0, LSR#16      ; last byte
     ANDNE   R7, R7, #&FF
     ORRNE   R3, R3, R7
     B       SendBytes

; Channel voice messages
Pack3Bytes
     AND     R0, R0, #&7F    ; byte range lim
     AND     R1, R1, #&7F
     LDRB    R4, [R12, #TxChannel]  ; channel no. 0-63
     MOV     r9, r4, LSR#4   ; requested Port number
     AND     r4, r4, #&F     ; midi channel
     ORR     R3, R3, R4, LSL#16 ; combine channel no. into word
     ORR     R3, R3, R0, LSL#8 ; 2nd byte
     ORR     R3, R3, R1        ; 3rd byte (last)
     MOV     R2, #3        ; 3 bytes to send
     MOV     PC, lr
Pack2Bytes
     AND     R0, R0, #&7F    ; byte range lim
     LDRB    R4, [R12, #TxChannel]  ; channel no.
     MOV     r9, r4, LSR#4   ; requested Port number
     AND     r4, r4, #&F     ; midi channel
     ORR     R3, R3, R4, LSL#8 ; combine channel no. into word
     ORR     R3, R3, R0      ; 2nd byte
     MOV     R2, #2          ; 2 bytes to send
     MOV     PC, lr
Pack1Byte
     LDRB    R4, [R12, #TxChannel]  ; channel no.
     MOV     r9, r4, LSR#4   ; requested Port number
     MOV     R2, #1          ; 1 byte to send (in r3)
     MOV     PC, lr

TxNoteOffSWI ; can use NoteOn RS with V=0 if same channel no. and V=64
     MOV     R3, #NoteOffVal:SHL:16 ; status in byte 3
     BL      Pack3Bytes
     ADD     R10, R12, R9    ; Port offset
     LDRB    R10, [R10, #SendStatus]  ; running status
     AND     R5, R10, #&F0
     CMP     R5, #NoteOnVal  ; if running status noteon then set vel 0 if 64
     ANDEQ   R5, R10, #&F    ; channel num
     CMPEQ   R5, R4          ; compare channel number with current TxChannel
     ANDEQ   R5, R3, #&7F    ; velocity
     CMPEQ   R5, #64         ; check for velocity of 64 if RS is noteon
     BICEQ   R3, R3, #&FF    ; set velocity to 0
     MOVEQ   R2, #2          ; don't send status if noteon RS and v=64
     B       SendBytes

TxNoteOnSWI
     MOV     R3, #NoteOnVal:SHL:16 ; status
     BL      Pack3Bytes
     B       SendBytes

TxPolyKeyPressureSWI
     MOV     R3, #PolyKeyPressureVal:SHL:16 ; status
     BL      Pack3Bytes
     B       SendBytes

TxControlChangeSWI
     MOV     R3, #ControlChangeVal:SHL:16 ; status
     BL      Pack3Bytes
     B       SendBytes

; Channel mode messages
ChannelModeMessage
     MOV     R3, #ControlChangeVal:SHL:16 ; status
     LDRB    R2, [R12, #TxChannel]  ; channel no.
     MOV     r9, r2, LSR#4          ; Port number
     AND     r2, r2, #&F
     ORR     R3, R3, R2, LSL#16
     MOV     R2, #3
     MOV     PC, lr

TxLocalControlSWI
     AND     R0, R0, #&7F
     BL      ChannelModeMessage
     ORR     R3, R3, #LocalControlVal:SHL:8   ; =122    : data byte1
     ORR     R3, R3, R0     ; data byte2
     B       SendBytes

TxAllNotesOffSWI
     BL      ChannelModeMessage
     ORR     R3, R3, #AllNotesOffVal:SHL:8    ; =123    : data byte1
     B       SendBytes

TxOmniModeOffSWI
     BL      ChannelModeMessage
     ORR     R3, R3, #OmniModeOffVal:SHL:8   ; =124    : data byte1
     B       SendBytes

TxOmniModeOnSWI
     BL      ChannelModeMessage
     ORR     R3, R3, #OmniModeOnVal:SHL:8   ; =125    : data byte1
     B       SendBytes

TxMonoModeOnSWI
     BL      ChannelModeMessage
     AND     R0, R0, #&FF  ; data byte 2 = number of channels lim 16
     CMP     R0, #MaxChannelNumber
     MOVGT   R0, #MaxChannelNumber
     ORR     R3, R3, #MonoModeOnVal:SHL:8   ; =126    : data byte1
     ORR     R3, R3, R0   ; data byte 2 = number of channels
     B       SendBytes

TxPolyModeOnSWI
     BL      ChannelModeMessage
     ORR     R3, R3, #PolyModeOnVal:SHL:8   ; =127    : data byte1
     B       SendBytes

; Channel voice messages
TxProgramChangeSWI
     MOV     R3, #ProgramChangeVal:SHL:8   ; status
     BL      Pack2Bytes
     B       SendBytes

TxChannelPressureSWI
     MOV     R3, #ChannelPressureVal:SHL:8 ; status
     BL      Pack2Bytes
     B       SendBytes

TxPitchWheelSWI  ; converts 14bit data into two 7bits in two bytes
     CMP     R0, #&4000
     MOVGE   R0, #&3F00
     ORRGE   R0, R0, #&FF             ; 3FFF is top of range
     MOV     R2, R0, LSR#7  ; MSB
     AND     R2, R2, #&7F
     AND     R3, R0, #&7F ; LSB
     ORR     R2, R2, R3, LSL#8
     MOV     R3, #PitchWheelVal:SHL:16 ; status
     ORR     R3, R3, R2
     LDRB    R2, [R12, #TxChannel]  ; channel no.
     MOV     r9, r2, LSR#4          ; Port number
     AND     r2, r2, #&F
     ORR     R3, R3, R2, LSL#16
     BL      Pack1Byte
     MOV     R2, #3
     B       SendBytes

; System common messages
TxSongPositionPointerSWI
     BIC     R0, R0, #&C000
     MOV     R2, R0, LSL#1         ; x2
     RSB     R2, R2, R2, LSL#2     ; x3
     STR     R2, [R12, #SongPosPointer] ; update local SPP before transmitting
     MOV     R3, R0, LSR#7  ; MSB
     AND     R3, R3, #&7F
     AND     R2, R0, #&7F
     ORR     R3, R3, R2, LSL#8 ; LSB
     ORR     R3, R3, #SongPosPointerVal:SHL:16
     BL      Pack1Byte
     MOV     R2, #3
     B       SendBytes

TxSongSelectSWI
     AND     R3, R0, #&7F  ; song no.
     ORR     R3, R3, #SongSelectVal:SHL:8
     BL      Pack1Byte
     MOV     R2, #2
     B       SendBytes

TxTuneRequestSWI
     MOV     R3, #TuneRequestVal
     BL      Pack1Byte
     B       SendBytes

; System real time messages
TxStartSWI
     MOV     R2, #SendStart
     B       TxSysRealTime

TxContinueSWI
     MOV     R2, #SendCont
     B       TxSysRealTime

TxStopSWI
     MOV     R2, #SendStop

TxSysRealTime ROUT
     TEQP    pc, #I_bit:OR:3     ; disable irq
     LDR     R3, [R12, #ModeFlagBits]
     ORR     R3, R3, #InternalCount ; enable internal count
     BIC     R3, R3, #SendRT:OR:ExternalCount ; disable external count
     ORR     R3, R3, R2
     STR     R3, [R12, #ModeFlagBits]
     MOV     r9, r8       ; midi Port number
10   TST     r3, #InIrq   ; don't enable irqs if I am being called (indirectly) from the irq routine
     BLEQ    EnableTxIRQ
     BLNE    EnablePretendTxIRQ
     SUBS    r9, r9, #1     ; midi Port number
     BPL     %BT10
     B       SWIExit

TxSystemResetSWI ROUT
     MOV     R3, #SystemResetVal
     BL      Pack1Byte
     B       SendBytes

IgnoreTimingSWI
     CMP     R0, #0
     LDRB    R2, [R12, #IgnoreState]
     ORRNE   R2, R2, #IgnoreTimingBit
     BICEQ   R2, R2, #IgnoreTimingBit
     STRB    R2, [R12, #IgnoreState]
     B       SWIExit

SynchSoundSchedulerSWI
     TEQP    pc, #I_bit:OR:3     ; disable irq
     LDR     r3, [r12, #ModeFlagBits]
     TST     r3, #SynchSoundScheduler
     MOVEQ   r2, #0 ; return r0 value. put in r2 for the moment
     MOVNE   r2, #1
     MOVS    r0, r0 ; test if r0 is 0
     ORRNE   r3, r3, #SynchSoundScheduler ; enable Sound Scheduler sync to incoming Timing Clock messages
     BICEQ   r3, r3, #SynchSoundScheduler ; disable Sound Scheduler sync
     STR     r3, [r12, #ModeFlagBits]
     MOV     r0, r2 ; return previous value of SynchSoundScheduler flag in r0
     B       SWIExit

FastClockSWI  ; value of r0 sets rate of sending timing clock messages (after start)
; reset hi-res timing bit if r0 is zero, else set
; if r0 < 0 just read current value of fast clock
     TEQP    pc, #I_bit:OR:3  ; disable irq
     LDR     r3, [r12, #ModeFlagBits]
     ORR     r3, r3, #Version3Facilities
     STR     r3, [r12, #ModeFlagBits]    ; enable new facilities
     LDR     r3, [r12, #ms_count] ; get previous ms_count
     MOVS    r0, r0
     STRPL   r1, [r12, #ms_count] ; reset count to value in r1
     MOV     r1, r3               ; return with previous count
     BMI     SWIExit              ; don't change anything if r0 is negative
     STR     r0, [r12, #tc_rate]
     BL      ClaimReleaseTimer  ; enter with z set to release; z clear to claim
     BVS     SWIExit ; and error will be passed back
 ; load value into timer and start it. IRQ will be generated at 1ms intervals
     BLNE    StartTimerIRQ ; (if r0 > 0)
     B       SWIExit

InitSWI ROUT
; r0 = 0 do system reset
; else
;    bit 0 set to clear current transmitted runnning status
;    bit 1 set to flush RxBuffers and reset interpreter,
;    bit 2 set to flush TxBuffers and reset interpreter,
;    bit 3 set to flush scheduler
;    bit 4 set to reset current error
;    bit 30 set to set StoreSysRealTime
;    bit 31 set to set SysRealTimeNoExec

; return r0 = number of midi Ports installed
     CMP     r0, #0
     BLEQ    ResetSystem
     ADDEQ   r0, r8, #1   ; number of Ports installed
     BEQ     SWIExit

     MOV     r2, #0
     TEQP    pc, #I_bit:OR:3   ; disable irq for consistent pointers

     TST     r0, #1
     STRNE   r2, [r12, #SendStatus]
     STRNE   r2, [r12, #StatusAge]

     TST     r0, #1:SHL:1
     ADD     r3, r12, #RxBufferPntrs
     STRNE   r2, [r3], #4
     STRNE   r2, [r3], #4
     STRNE   r2, [r3], #4
     STRNE   r2, [r3]

     TST     r0, #1:SHL:2
     ADD     r3, r12, #TxBufferPntrs
     STRNE   r2, [r3], #4
     STRNE   r2, [r3], #4
     STRNE   r2, [r3], #4
     STRNE   r2, [r3]

     TST     r0, #6
     BLNE    DoAllNotesOff  ; only for bits 1 or 2 set
     STRNE   r2, [r12, #BufferSoundOutPntr]

     TST     r0, #1:SHL:3
     STRNE   r2, [r12, #Slot_p]

     TST     r0, #1:SHL:4
     STRNE   r2, [r12, #ErrorFlag]

     LDR     r2, [r12, #ModeFlagBits]

     TST     r0, #1:SHL:1  ; side-effect of clearing the rx buffer is to clear any buffer overflow error
;*************
;     BICNE   r2, r2, #RxBufferOverflow
;     MOVNE   r3, #0
;     STRNE   r3, [r12, #OverflowPort]
;*************
     TST     r0, #1:SHL:3  ; requested clear scheduler
     BICNE   r2, r2, #WarnedEmptyScheduler ; clear also event flag

; test for special top bits set
     TST     r0, #1:SHL:30
     ORRNE   r2, r2, #StoreSysRealTime

     TST     r0, #1:SHL:31
     ORRNE   r2, r2, #SysRealTimeNoExec

     STR     r2, [r12, #ModeFlagBits]

     ADD     r0, r8, #1   ; number of Ports installed
     B       SWIExit

SetBufferSizeSWI ROUT
; enter with:
;  r0 = 0 for set rx buffer size
;  r1 = new buffer size
;       0 ; interrogate current value
; exit with:
;  r0 = buffer size
;  r1 = total size of new buffers
     CMP     r0, #0  ; check r0 = 0 for rx buffer size
     BNE     SWIExit
     ADD     r6, r8, #1  ; number of Ports installed
     TEQP    pc, #3 + I_bit  ; disable irq
; if r1 = 0 or same as current size exit with current info
     CMP     r1, #0
     LDR     r5, [r12, #RxBufferLen]
     CMPNE   r1, r5
     BNE     %FT10
; get current info in r0 and r1
     MOV     r0, r5
     CMP     r6, #2
     MOVLT   r1, r5            ; x 1
     MOVEQ   r1, r5, LSL#1     ; x 2
     RSBGT   r1, r5, r5, LSL#2 ; x 3
     CMP     r6, #3
     MOVGT   r1, r5, LSL#2     ; X 4
     ADD     r1, r1, r1, LSL#2     ; X 5 conversion to bytes
     B       SWIExit
10   MOV     r8, #0
; clear all Rx buffers
     ADD     r9, r12, #RxBufferPntrs
     STR     r8, [r9], #4
     STR     r8, [r9], #4
     STR     r8, [r9], #4
     STR     r8, [r9]

 ; side-effect of clearing the rx buffers is to clear any buffer overflow error
;********
;     LDR     r2, [r12, #ModeFlagBits]
;     BIC     r2, r2, #RxBufferOverflow
;     STR     r2, [r12, #ModeFlagBits]
;     MOV     r2, #0
;     STR     r2, [r12, #OverflowPort]
;********
     ADD     r4, r12, #RxBufferStarts
     BL      FreeBuffers
     MOV     r5, r1       ; size to claim
     MOV     r0, r5
; enter ClaimBuffers with
;   r5 = size of buffers to claim
;   r6 = number of buffers to claim
;   r4 = address of workspace pointers to starts of 4 buffers (though there will usually be fewer)
; exit with v - set if unable to claim buffers
;   r5 = total space claimed (bytes)
     BL      ClaimBuffers
     MOV     r7, r5  ; tally of total space claimed
; if error put back old buffers and presume there won't be an error
     LDRVS   r5, [r12, #RxBufferLen]
     ADDVS   r4, r12, #RxBufferStarts
     BLVS    ClaimBuffers
     BVS     UnableToClaim
; rx times buffers
     ADD     r4, r12, #RxTimesBufferStarts
     BL      FreeBuffers
     MOV     r5, r1, LSL#2  ; size
     BL      ClaimBuffers
; finally update buffer size if no error
     STRVC   r1, [r12, #RxBufferLen]
     ADDVC   r1, r7, r5  ; total space claimed
     BVC     SWIExit  ; no error exit
; if error free rx buffers and reclaim old size
     ADD     r4, r12, #RxBufferStarts
     BL      FreeBuffers
     LDR     r5, [r12, #RxBufferLen]
     ADD     r4, r12, #RxBufferStarts
     BL      ClaimBuffers
     MOV     r7, r4         ; total size tally
     MOV     r5, r5, LSL#2  ; size
     ADD     r4, r12, #RxBufferStarts
     BL      ClaimBuffers
     ADD     r1, r7, r4
UnableToClaim
     ADR     R0, ErrBlockNoRoomPos ; used above by initialise_module
     LDR     r1, [r0]
     SUBS    r0, r0, r1   ; true address of error_block
     TEQP    pc, #V_bit + 3   ; set v flag
     B       SWIExit      ; return with error and V set

ErrBlockNoRoomPos & . - ErrorBlock_MHNoRoom

Schedule ROUT ; enter with r1 = schedule time, r0 = parameter to TxCommand
; return with r0 = -1 if failed with queue full or -2 if schedule time<last schedule time
; else r0 = number of slots free in queue
     STMFD   sp!, {r2-r6,lr}
     LDR     r2, [r12, #ModeFlagBits]
     TST     r2, #WarnedEmptyScheduler
     BICNE   r2, r2, #WarnedEmptyScheduler
     STRNE   r2, [r12, #ModeFlagBits]
     ADD     r3, r12, #SchedulerSpace ; pointer to scheduler array
     LDR     r2, [r12, #Slot_p]
     MOV     r4, r2, LSR#16      ; top 16b = next free slot
     BIC     r2, r2, #&FF000000
     BIC     r2, r2, #&00FF0000  ;  bottom 16b = next to empty slot
     CMP     r4, r2   ; check if the buffer is not empty
     BEQ     %FT10    ; buffer is empty...no need to check last slot time
     SUBS    r5, r4, #1                ; get previous slot
     ADDMI   r5, r5, #MaxSchedulerSlots  ; possible wrap
     ADD     r5, r3, r5, LSL#3  ; pointer to previous free slot
     LDR     r6, [r5, #ScheduleTime] ; previous schedule time
     CMP     r1, r6           ; check that this time is not before previous time
     MOVLT   r0, #-2
     LDMLTFD sp!, {r2-r6,pc}^ ; if so return (fatal) error and do not schedule anything
10   ADD     r5, r4, #1       ; increment to next free slot
     CMP     r5, #MaxSchedulerSlots
     MOVGE   r5, #0   ; wrap round circular buffer
     CMP     r5, r2   ; check that the buffer is not full (inc'd ptr = to-empty ptr)
     MOVEQ   r0, #-1   ; scheduler queue full error
     LDMEQFD sp!, {r2-r6,pc}^  ; if so return (fatal) error and do not schedule anything
     ORR     r6, r2, r5, LSL#16 ; combine next-to-empty and inc'd next-free ptrs
     STR     r6, [r12, #Slot_p] ; and store them
     ADD     r5, r3, r4, LSL#3  ; pointer to next free slot in array
     STR     r0, [r5, #ScheduleValue]
     STR     r1, [r5, #ScheduleTime]   ; put data in slot
; put a copy of the next-to-schedule time in a convenient place for
; the millisecond scheduler to get it quickly
     ADD     r3, r3, r2, LSL#3  ; pointer to next-to-empty slot in array
     LDR     r3, [r3, #ScheduleTime]
     STR     r3, [r12, #next_schedule_t]
     MOV     r4, r6, LSR#16      ; top 16b = next free slot
     SUBS    r0, r2, r4
     ADDLES  r0, r0, #MaxSchedulerSlots ; return number of free slots
     SUB     r0, r0, #1
     LDMFD   sp!, {r2-r6,pc}^

SchedulerTake ROUT; enter with r0 = current time,
; exit with r0 = -1 if nothing done, -2 if txbuffer full error.
; NB these error values are ignored by the caller as they are background (irq) routines
; take all items off q that were scheduled for times earlier than or equal to now
; or as many as will fit in tx buffer, and TxCommand them
; enter in svc mode (3). Called in background from irq or ms routine
     STMFD   sp!, {r1-r6,lr}
     LDR     r2, [r12, #Slot_p]
     MOV     r4, r2, LSR#16      ; top 16b = next free slot
     BIC     r2, r2, #&FF000000
     BIC     r2, r2, #&00FF0000  ;  bottom 16b = next to empty slot
     CMP     r2, r4   ; check if the buffer is empty (free ptr = to-empty ptr)
     MOVEQ   r0, #-1  ; if empty do nothing exit
     STREQ   r0, [r12, #next_schedule_t] ; reset next schedule time
     LDMEQFD sp!, {r1-r6,pc}^
10   ADD     r5, r12, #SchedulerSpace
     ADD     r5, r5, r2, LSL#3  ; pointer to next-to-empty slot in array
     LDR     r6, [r5, #ScheduleTime]   ; get schedule time
     CMP     r6, r0   ; compare schedule time with current time
; Normal finished-up-to-current-time exit
     STRGT   r6, [r12, #next_schedule_t] ; update copy of next schedule time
     ORRGT   r6, r2, r4, LSL#16 ; combine inc'd next-to-empty and next-free ptrs
     STRGT   r6, [r12, #Slot_p] ; store updated buffer pointers
     LDMGTFD sp!, {r1-r6,pc}^   ; Normal finished exit (schedule time is later)

     ADD     r2, r2, #1       ; increment to next slot to empty
     CMP     r2, #MaxSchedulerSlots
     MOVGE   r2, #0   ; wrap round circular buffer
     MOV     r6, r0  ; save r0
     LDR     r0, [r5, #ScheduleValue] ; get parameters
     MOV     r1, #0                   ; immediate action (don't reschedule it!)
     BL      CallTxCommandSWICode         ; and TxCommand
     MOVVS   r0, #-2  ; tx buffer full error. This should be avoided. Data is lost!
     LDMVSFD sp!, {r1-r6,pc}^
     MOV     r0, r6  ; restore r0
     CMP     r2, r4   ; check if the buffer is empty (free ptr = to-empty ptr)
     BNE     %BT10       ; loop until finished
     ORR     r6, r2, r4, LSL#16 ; combine inc'd next-to-empty and next-free ptrs
     STR     r6, [r12, #Slot_p] ; store updated buffer pointers
; empty scheduler exit
     LDMFD   sp!, {r1-r6,pc}^

CallTxCommandSWICode ; but don't re-enable irq
; set up various necessary bits before calling the specific TxCommand code
     STMFD   sp!, {R2-R10, lr}
     LDR     r2, [r12, #ModeFlagBits]
     MOV     r8, r2, LSR#NExtraPortsShift
     ANDS    r8, r8, #3  ; number of extra Ports installed
     B       TxCommandSWI

EnableTxIRQ ROUT
; enable transmit-irq in uart
; enter with:
; r9 = Port number (0..3)
; r12 = wp 
     STMFD   sp!, {r2-r3, lr}
     TEQP    pc, #3:OR:I_bit      ; disable interrupts
     ADD     r2, r12, r9, LSL#2   ; Port address offset
     LDR     r2, [r2, #UARTbase] ; ACIA/UART base address
     TST     r2, #PortTypeTestBit
     MOVEQ   r3, #ACIATxIRQEnable
     STREQB  r3, [r2]             ; enable TDRE interrupts
     MOVNE   r3, #RxRDYirq:OR:TxRDYirq
     STRNEB  r3, [r2, #IROffset]  ; enable TxRDY interrupts
     ADD     r2, r12, r9  ; Port address offset for required byte of IMR
     STRB    r3, [r2, #IMRCurrent]
     LDMFD   sp!, {r2-r3, pc}^

EnablePretendTxIRQ ROUT
; just set flags in imr, and do not enable irq in uart
; enter with:
; r9 = Port number (0..3)
; r12 = wp 
     STMFD   sp!, {r2-r3, lr}
     ADD     r2, r12, r9, LSL#2   ; Port address offset
     LDR     r2, [r2, #UARTbase] ; ACIA/UART base address
     TST     r2, #PortTypeTestBit
     MOVEQ   r3, #ACIATxIRQEnable
     MOVNE   r3, #RxRDYirq:OR:TxRDYirq
     ADD     r2, r12, r9  ; Port address offset for required byte of IMR
     STRB    r3, [r2, #IMRCurrent]
     LDMFD   sp!, {r2-r3, pc}^

SendBytes ROUT
; enter with:
; R2=number of bytes,
; R3=bytes,LSB sent last
; r9=Port to use
; put bytes in transmit buffer fifo, to be transmitted by the irq routine.
     AND     r2, r2, #3 ; limit of 3 bytes can be sent at once
     ADD     r7, r12, r9, LSL#2   ; buffer offset
     TEQP    pc, #3:OR:I_bit      ; disable interrupts for consistent buffer pointers
     LDR     r4, [r7, #TxBufferPntrs]
     ADD     r7, r12, r9  ; buffer offset
     LDRB    r6, [r7, #SendStatus]  ; running status
     LDRB    r7, [r7, #StatusAge]   ; age of running status
     ORR     r6, r6, r7, LSL#8 ; status and status age
; r9 = which Port to send from
NextByte ; get next byte to send   preserve R2, R3, r4, r6
     BIC     r5, r4, #&FF000000   ; in pointer
     BIC     r5, r5, #&00FF0000
     ADD     r7, r5, #1     ; increment buffer in pointer
     CMP     r7, #TxBufferLen
     MOVGE   r7, #0           ; circular buffer, reset pointer
     CMP     r7, r4, LSR#16    ; Check if buffer is full
; r4 bytes 0,1 = in pointer; bytes 2,3 = out pointer
; r7 is incremented in pointer
     BEQ     TxBufferFull
     SUB     R2, R2, #1       ; number of parameters remaining
     MOV     R8, R2, LSL #3    ; x8
     MOV     R8, R3, LSR R8    ; shift to next byte to be sent
     AND     R8, R8, #&FF
; r8 is byte to be sent
     TST     R8, #&80          ; status byte? (top bit set)
     BEQ     StoreByte  ; data
; decide whether to send this status byte
     AND     r5, r8, #&F0
     CMP     r5, #&F0 ; System common or system real-time message must be txd
     BNE     %FT10
     TST     r8, #8 ; system realtime doesnt interrupt RS but sys common does
     MOVEQ   r6, #0 ; reset RS (and status age) to ensure new status is sent if sys common
     B       StoreByte

; 11/8/88 addition. Test for channel mode messages, and ensure no RS
10   TEQ     r5, #&B0
     BLEQ    TestChannelMode
; end of addition

     ADD     r6, r6, #&100   ; inc status age (number of status bytes missed under RS)
     AND     r5, r6, #&FF    ; running status
     CMP     r5, r8          ; compare with running status
     MOVNE   r6, r8          ; reset RS, and set status age to 0
     BNE     StoreByte       ; and send it,    if different
     CMP     r6, #MaxRunningStatusAge:SHL:8 ; send status if more than (4)
     BLT     LoopBack  ; if not (common or realtime or old) then RS, dont send
     BIC     r6, r6, #&FF00  ; reset status age. Enough status bytes have been missed
StoreByte
     ADD     r5, r12, r9, LSL#2
     LDR     r5, [r5, #TxBufferStarts]  ; start of txbuffer
     BIC     r10, r4,  #&00FF0000
     BIC     r10, r10, #&FF000000 ; in pntr
     STRB    r8, [r5, r10]  ; put byte in transmit buffer
     MOV     r4, r4, LSR#16    ; out pntr
     ORR     r4, r7, r4, LSL#16 ; combine pre-incremented buffer in pointer
LoopBack
     CMP     R2, #0   
     BGT     NextByte
; store state and exit
     ADD     r10, r12, r9   ; buffer offset
     STRB    r6, [r10, #SendStatus]  ; store new status in RS
     MOV     r6, r6, LSR#8
     STRB    r6, [r10, #StatusAge]   ; age of running status
     ADD     r10, r12, r9, LSL#2     ; Port buffer pointer offset
     STR     r4, [R10, #TxBufferPntrs] ; save updated buffer in pointer
; enable transmit-interrupt in uart; except if this is indirectly called from the irq routine
; in which case set a flag in the mode flags, and in the appropriate IMR. The irq routine will
; sort it out. This avoids accidently re-entering the irq routine.
     LDR     r2, [r12, #ModeFlagBits]
     TST     r2, #InIrq 
     BLEQ    EnableTxIRQ
 ; don't enable irqs, just set flags if I am being called (indirectly) from the irq routine
     BLNE    EnablePretendTxIRQ
SWIExit
     LDMVCFD sp!, {R2-R10, PC}^   ; normal exit if no error
     LDMFD   sp!, {r2-r10, lr}
     ORRS    pc, lr, #V_bit      ; pass on any error
TxBufferFull
     ADR     r0, ErrorBlock_MIDI_TxBufferFull
     LDMFD   sp!, {r2-r10, lr}
     ORRS    pc, lr, #V_bit      ; set buffer full error
RxError
     CMP     r1, #-1  ; overrun error
     ADREQ   r0, ErrorBlock_MIDI_RxOverrunError
     CMP     r1, #-2  ; framing error
     ADREQ   r0, ErrorBlock_MIDI_RxFramingError
     MOV     r1, r2   ; pass on number of byte (0..2) with received error in r1
     LDMFD   sp!, {r2-r10, lr}
     ORRS    pc, lr, #V_bit      ; set error

TestOverflow ; exit with overflow error if relevant to this port
; r2 = modeflagbits (var), restored to workspace if modified
; r9 = port number 0..3 (ANDed with 3 to ensure range limit)
; r12 = wp (unchanged)
     STMFD   sp!, {r0-r1, lr}
     AND     r9, r9, #3  ; simple precautionary range-limit 0..3 port number
     MOV     r1, #1
     MOV     r1, r1, LSL r9 ; set port bit 0..3
     LDR     r0, [r12, #OverflowPort]
     AND     r0, r0, #&F ; simple precautionary range-limit..can only be bits 0..3
     TST     r0, r1      ; test port ovrflow error bit
     LDMEQFD sp!, {r0-r1, pc}^ ; return, since overflow is not in current port
     BICS    r0, r0, r1 ; clear the error bit..it is to be being reported now
     STR     r0, [r12, #OverflowPort]
  ; ..and clear receive buffer full error status if all OverflowError bits zero
     BICEQ   r2, r2, #RxBufferOverflow
     STREQ   r2, [r12, #ModeFlagBits]
     LDMFD   sp!, {r0-r1, lr} ; discard BL return address for error exit 
     ADR     r0, ErrorBlock_MIDI_RxBufferOverflowError
     LDMFD   sp!, {r2-r10, lr}
     ORRS    pc, lr, #V_bit      ; set error

   MakeErrorBlock  MIDI_TxBufferFull
   MakeErrorBlock  MIDI_RxFramingError
   MakeErrorBlock  MIDI_RxOverrunError
   MakeErrorBlock  MIDI_RxBufferOverflowError

; Test for channel mode messages, and ensure no RS
TestChannelMode ROUT
; preserve state, and have a look at 1st data byte of channel mode message
     STMFD   sp!, {r2, r8, lr}
     SUBS    R2, R2, #1       ; number of parameters remaining
     LDMMIFD sp!, {r2, r8, pc}^
     MOV     R8, R2, LSL #3    ; x8
     MOV     R8, R3, LSR R8    ; shift to next byte to be sent
     AND     R8, R8, #&FF
     TST     R8, #&80          ; status byte? (top bit set)
     LDMNEFD sp!, {r2, r8, pc}^  ; if so then don't worry. This status (channel mode) is wasted. This should never happen.
     CMP     r8, #LocalControlVal  ; check for channel mode data1 range 122-127
     MOVGE   r6, #0            ; if channel mode then clear RS buffer to ensure this status is sent
     LDMFD   sp!, {r2, r8, pc}^

  END
