; bits in r5 status
IrqServiced  * 1
FramingError * 1:SHL:1
OverrunError * 1:SHL:2

TestAciaRxIrq
; r0 = uart/acia base address
; r1 = acia status register
; r3 corruptible
; r6 = IMR copy word
; r9 = port
     TST     r1, #IRQBit      ; any irq?
     TSTNE   r1, #RDRFBit     ; received data?
     ORRNES  pc, lr, #V_bit   ; return V_set if received data
     BICS    pc, lr, #V_bit

TestUartRxIrq
; r0 = uart/acia base address
; r3 corruptible
; r6 = IMR copy word
; r9 = port
; returns r1 set to uart ISR register contents IFF RxRDYirq mask bit is set
     MOV     r3, r9, LSL#3   ; port num X 8 for byte shift
     MOV     r3, r6, LSR r3  ; IMR byte
     TST     r3, #RxRDYirq   ; rx irq bit
     LDRNEB  r1, [r0, #IROffset] ;  Check interrupt status reg
     TSTNE   r1, #RxRDYirq       ; received data?
     ORRNES  pc, lr, #V_bit   ; return V_set if received data
     BICS    pc, lr, #V_bit

TestAciaTxIrq
; r0 = uart/acia base address
; r1 = acia status register
; r3 corruptible
; r6 = IMR copy word
; r9 = port
     TST     r1, #IRQBit      ; any irq?
     TSTNE   r1, #TDREBit     ; asking for data to transmit?
     ORRNES  pc, lr, #V_bit   ; return V_set for transmit data irq
     BICS    pc, lr, #V_bit

TestUartTxIrq
; r0 = uart/acia base address
; r3 corruptible
; r6 = IMR copy word
; r9 = port
; returns r1 set to uart ISR register contents IFF RxRDYirq mask bit is set
     MOV     r3, r9, LSL#3   ; port num X 8 for byte shift
     MOV     r3, r6, LSR r3  ; IMR byte
     TST     r3, #TxRDYirq   ; tx irq bit
     LDRNEB  r1, [r0, #IROffset] ; Check interrupt status reg
     TSTNE   r1, #TxRDYirq       ; received data?
     ORRNES  pc, lr, #V_bit   ; return V_set for transmit data irq
     BICS    pc, lr, #V_bit

IRQVectorCode ROUT
; Note that R12 contains direct workspace pointer, NOT indirected.
; This routine has to look at all midi Ports installed and service
; their interrupts
; Note. It tests all ports for received data first, and fetches it,
; since reception is time-critical.
; It then services any transmit irqs, and tests the uart/acia error bits
     STMFD   sp!, {r0-r6,r9, lr} ; n.b. lr for irq accepted is already pushed onto stack
     MOV     r5, #0  ; irq status bits set here
     LDR     r2, [r12, #ModeFlagBits]
     ORR     r2, r2, #InIrq  ; set flag preventing re-entrant irq enable
     MOV     r9, r2, LSR#NExtraPortsShift
     AND     r9, r9, #3
     LDR     r6, [r12, #IMRCurrent] ; up to 4 current uart IMR bytes
; r2 = mode flags
; r6 = imr copy
; r5 = irq status flag bits
; r9 = current port number (decremented from max)

Rxloop   ; loop and read each installed MIDI Port and service any rxdata irq
     BIC     r5, r5, #OverrunError:OR:FramingError ; reset error flags
     ADD     r4, r12, r9, LSL#2
     LDR     r0, [r4, #UARTbase] ;  ACIA/UART address
     TST     r0, #PortTypeTestBit ; test Port type using UART base address

; test uart/acia error status (is needed in RxData)
     LDRNEB  r1, [r0, #SROffset] ; uart status register
     MOVNE   r3, #&F0
     LDREQB  r1, [r0] ; acia status register
     MOVEQ   r3, #OVRNBit:OR:FEBit
     TST     r1, r3    ; test error bits in status reg
     BLNE    ReportError ; set r5 error bits

     TST     r0, #PortTypeTestBit
     BLEQ    TestAciaRxIrq ; returns V_set if received data
     BLNE    TestUartRxIrq ; ditto, & r1 = ISR IF v_set
     BLVS    RxData
     ORRVS   r5, r5, #IrqServiced  ; irq was serviced
     SUBS    r9, r9, #1
     BPL     Rxloop
; Rxloop END

; get number of installed ports
     MOV     r9, r2, LSR#NExtraPortsShift
     AND     r9, r9, #3

Txloop ; loop and read each installed MIDI Port and service any transmit irq
     ADD     r4, r12, r9, LSL#2
     LDR     r0, [r4, #UARTbase] ;  ACIA/UART address
     TST     r0, #PortTypeTestBit ; test Port type using UART base address
     LDREQB  r1, [r0] ; acia status register
     BLEQ    TestAciaTxIrq ; returns V_set if transmit data irq
     BLNE    TestUartTxIrq ; ditto
     BLVS    TxData
     ORRVS   r5, r5, #IrqServiced  ; irq was serviced
     SUBS    r9, r9, #1
     BPL     Txloop
; Txloop END

; check real_time tx request
     ANDS    r3, r2, #SendRT ; was there a request to send a Real Time message?
     BEQ     ExitIrq
; check for timing clock tx request.
; It will have been sent from all ports. Now update clock (just once!)
     MOV     r3, r3, LSR#InfoShift  ; move command to bottom byte
     CMP     r3, #1   ; IF Timing Clock THEN
     LDREQ   r4, [r12, #SongPosPointer]
     ADDEQ   r4, r4, #1            ; increment SPP
     STREQ   r4, [r12, #SongPosPointer]

ExitIrq
; clear real-time transmit request; it must have been attended to
     BIC     r2, r2, #SendRT
; re-entrant irq enable prevention flag.
     BIC     r2, r2, #InIrq
     STR     r2, [r12, #ModeFlagBits]
     TST     r5, #IrqServiced         ; test if any irq was serviced
     LDMNEFD sp!, {r0-r6, r9, lr, pc}^; if so return with pc on stack
     LDMFD   sp!, {r0-r6, r9, pc}^ ; else return with pc in r14 (no irq servicd here)


ReportError  ROUT
; r0 = uart address
; r2 = mode flags
; r1 = acia or uart status reg (N.B. NOT uart IMR)
; r9 = number of Port being serviced,
; set r5 overrun and framing error bits
     STMFD   sp!, {r3-r4, lr}
     MOV     r3, #0  ; may be overwritten with an error byte "F" or "O"
     TST     r0, #PortTypeTestBit
     MOVNE   r4, #CRResetEr         ;reset error
     STRNEB  r4, [r0, #CROffset]
     MOVNE   r4, #SRRxOE
     MOVEQ   r4, #OVRNBit
; r1 = status reg; r4 = overrun error mask
01   TST     r1, r4  ; Overrun error?
     MOVNE   r3, #"O"     ; irq latency is too long
     ORRNE   r5, r5, #OverrunError ; flag overrun error
     TST     r0, #PortTypeTestBit
     MOVNE   r4, #SRRxFE      ; uart
     MOVEQ   r4, #FEBit       ; acia
     TST     r1, r4 ; Framing error indicates garbage received
     MOVNE   r3, #"F"  ; Incoming MIDI data is corrupted or asynchronous
     ORRNE   r5, r5, #FramingError ; flag framing error
     CMP     r3, #0
     ADDNE   r4, r12, r9  ; port offset
     STRNEB  r3, [r4, #ErrorFlag] ; set error flag if not to zero
     ORRNE   r2, r2, #BackgroundError ; set error flag. Event will be generated by SIRQ code
     LDMFD   sp!, {r3-r4, pc}^

RxData ROUT
; r0 = uart address
; r1 = acia status reg or uart imr
; r2 = modeflags (updateable)
; r5 = error status
; r9 = port number (0-3)
     STMFD   sp!,{r3-r4,r6-r8,r10-r11, lr}
     LDR     R7, [R12, #SongPosPointer]
     TST     r2, #FastClock
     LDRNE   r8, [r12, #ms_count]  ; get value of hi res time, or..
     LDREQ   r8, [r12, #CurrentQBeat]  ; get value of soundsys bar/beat counter
     CMP     r8, #-1
     CMPNE   r8, #-2  ; these 2 values in timestamp are used to indicate receive error
     MOVEQ   r8, #0  ; prevent them occurring normally
; r7 = Song position pointer
; r8 = Current bar/beat value, or ms_count for time stamp
; take the data from the uart/acia receive buffer and put it in the receive
; fifo buffer.
     ADD     r3, r12, r9, LSL#2 ; buffer offset for this Port
     LDR     r4, [r3, #RxBufferPntrs]   ; buffer in and out pointers
     MOV     r10, r4, LSR#16      ; out pointer
     BIC     r4, r4, #&FF000000
     BIC     r4, r4, #&00FF0000   ; in
     ADD     R6, R4, #1      ; increment buffer in pointer for pre check
     LDR     r11, [r12, #RxBufferLen] ; size of buffer
     CMP     r6, r11
     MOVGE   r6, #0     ; circular buffer, reset pointer
;Check for rx buffer overflow. Note that I only test the SWI getbyte outpointer
; and not the soundsys outpointer. The sound system is presumed to keep up with
; the MIDI data rate, and it isnt worth knowing about if it doesnt!
     CMP     R6, r10  ; check that buffer is not full (outpntr=inpntr)
     BLEQ    SetOverflowError
     TST     r0, #PortTypeTestBit ; = 0 for io Port
     LDREQB  r11, [r0, #DataRegOffset]    ; get received data
     LDRNEB  r11, [r0, #HROffset]    ; get received data
     TST     r2, #StoreSysRealTime ; special mode; store real time data too
     BNE     %FT22
     CMP     r11, #SystemResetVal   ; system reset is only realtime that is ignored and stored in buffer
     BEQ     %FT22
     CMP     r11, #&F8 ;system realtime? (TC, start, cont, stop, AS)
     BGE     ExecSysRealTime
22   ADD     r3, r12, r9, LSL#2 ; buffer offset for this Port
     ORR     r6, r6, r10, LSL#16 ; combine in and out buffer pointers into 1 word
     STR     r6, [r3, #RxBufferPntrs]   ; store incremented pointer
     CMP     r4, r10
     ORREQ   r2, r2, #BufferFilling ; set flag if receive buffer was empty. cs will generate an event
     LDR     r6, [r3, #RxBufferStarts]  ; get address of start of buffer
     STRB    r11, [r6, r4]   ; store data in buffer
     LDR     r6, [r3, #RxTimesBufferStarts]  ; get address of start of timestamp buffer
     TST     r5, #OverrunError ; ensure error is stored as code -1 or -2 in timestamp
     MOVNE   r8, #-1
     TST     r5, #FramingError
     MOVNE   r8, #-2
     TST     r5, #OverrunError:OR:FramingError
     STRNE   r8, [r6, r4, LSL#2] ; store error code
     BNE     %FT12
     TST     r2, #ExternalCount ; will be stamped with SPP if receiving TCs
     STRNE   R7, [R6, R4, LSL#2] ; 32bit received time of data (SPP)
     BNE     %FT12
     TST     r2, #InternalCount ; will be stamped with SPP if sending TCs
     ANDNE   r3, r2, #FastClock
     TEQNE   r3, #FastClock   ; if fastclock modify to stamp with ms_count if sending
     STRNE   R7, [R6, R4, LSL#2] ; 32bit received time of data (SPP)
     STREQ   r8, [r6, r4, LSL#2] ; ms_count or bar/beat counter received time of data
12   CMP     r11, #&F8 ;system realtime? (TC, start, cont, stop, AS, sys reset)
     BGE     ExecSysRealTime
     TST     r11, #&80    ; status byte?
     BNE     %FT15    ; check for spp, then exit rx code
; is not status. Check if it is spp data byte
; Song Position Pointer is transmitted as Status, lsb, msb.
     TST     r2, #SPPawaitLsb:OR:SPPawaitMsb ; awaiting rest of spp?
     BEQ     RxEnd
     LDRB    r3, [r12, #SPPchannel] ; which Port was it received in?
     CMP     r3, r9                 ; this one?
     BNE     RxEnd
     TST     r2, #SPPawaitMsb
     BICNE   r2, r2, #SPPawaitLsb:OR:SPPawaitMsb ; msb received... finished
     EOREQ   r2, r2, #SPPawaitLsb:OR:SPPawaitMsb ; swap bits
     STREQB  r11, [R12, #SPPlsb]  ; store lsb
     LDRNEB  R7, [R12, #SPPlsb]  ; overwrite old spp with new
     ORRNE   R7, R7, r11, LSL#7   ; combine received msb with stored lsb
     MOVNE   R7, R7, LSL#1       ; x2
     RSBNE   R7, R7, R7, LSL#2   ; x3  (multiply by 6)
     B       RxEnd
15   CMP     r11, #SongPosPointerVal
     ORREQ   r2, r2, #SPPawaitLsb   ; next byte will be lsb
     BICEQ   r2, r2, #SPPawaitMsb
     STREQB  r9, [r12, #SPPchannel]
     BEQ     RxEnd
     TST     r2, #SPPawaitLsb:OR:SPPawaitMsb ; awaiting rest of spp?
     BEQ     RxEnd                           ; no
     LDRB    r3, [r12, #SPPchannel] ; status byte. should I clear SPP waiting status? Check channel
     CMP     r3, r9
     BICEQ   r2, r2, #SPPawaitLsb:OR:SPPawaitMsb ; this is the almost-never contingency of receiving spp status
     B       RxEnd     ; in a Port, then getting a new status before the spp data. Clear SPP waiting state

ExecSysRealTime
;Deal with System RealTime data immediately, and do not store it in the buffer
     TST     r2, #SysRealTimeNoExec ; is special flag set asking for no System Real Time auto-execution?
     BNE     RxEnd

     CMP     r11, #ActiveSensingVal
     BEQ     ActiveSensingRxd

     TST     r2, #InternalCount:OR:SendRT ; (internal count has priority)
     BICNE   r2, r2, #ExternalCount  ; if internal, clear external count bit
     BNE     RxEnd    ; if internal count, then ignore TC, start, cont, stop

     LDRB    R3, [R12, #IgnoreState]
     TST     R3, #IgnoreTimingBit   ; should I ignore timing messages?
     BICNE   r2, r2, #ExternalCount  ; clear external count bit
     BNE     RxEnd

     SUBS    r11, r11, #TimingClockVal
     BMI     RxEnd
     AND     r11, r11, #7
     ADD     pc, pc, r11, LSL#2
     B       RxEnd              ; dummy
     B       TimingClockRxd     ; F8
     B       RxEnd              ; F9
     B       StartRxd           ; FA
     B       ContinueRxd        ; FB
     B       StopRxd            ; FC
     B       RxEnd              ; FD
     B       ActiveSensingRxd   ; FE ; redundant; it was tested above
     B       RxEnd              ; FF
; System Reset is ignored. It is up to the application to deal with it.

TimingClockRxd
     TST     r2, #ExternalCount:OR:SetExternalC
     ADDNE   R7, R7, #1   ; inc SPP if external count bit set or about to be set
     TST     r2, #ExternalCount ; do nothing more if ext not set yet
     BEQ     RxEnd
; timing clock message can trigger Midi Scheduler or Sound Scheduler (NOT both)
     TST     r2, #Version3Facilities   ; new facilities?
     BEQ     %FT5     ; SoundScheduler tick required?
     LDR     r3, [r12, #next_schedule_t]
     MOVS    r3, r3
     BMI     %FT05     ; - = disabled MIDI scheduler
     CMP     r7, r3   ; has the next schedule time arrived?
     BLGE    DispatchMidiScheduler
     B       RxEnd
; disallow tick of BOTH schedulers on Timing Clock reception
05   TST     r2, #SynchSoundScheduler ; is external synch of sound scheduler required?
     TSTNE   r2, #Sound2Present ; ensure sound scheduler module is present
     BLNE    DispatchSoundScheduler
     B       RxEnd

StartRxd
     MOV     r7, #0            ; reset SPP and..
                               ; fall through Continue code
ContinueRxd
     ORR     r2, r2, #SetExternalC ; ask for ExternalCount
     B       RxEnd

StopRxd
     ORR     r2, r2, #ClearExternalC ; clear external count
     LDR     r8, [r12, #CurrentQBeat]
     BIC     r8, r8, #&FF000000  ; clear bar count
     BIC     r8, r8, #&00FF0000
     STR     r8, [r12, #CurrentQBeat]
     B       RxEnd

ActiveSensingRxd
     ORR     r2, r2, #RxActiveSenseBit  ; set active sensing bit
     MOV     r6, #1
     ADD     r3, r12, r9
     STRB    r6, [r3, #RxInactiveTime] ; and set counter to 1
     LDMFD   sp!,{r3-r4,r6-r8,r10-r11, pc}^ ; no need to update actv sns or spp

RxEnd
     TST     r2, #RxActiveSenseBit
     ADDNE   r3, r12, r9
     LDRNEB  r6, [r3, #RxInactiveTime] ; get rx active sense counter
     MOVNES  r6, r6   ; is this counter enabled ( <> 0 =>enabled )
     MOVNE   r6, #1
     STRNEB  r6, [r3, #RxInactiveTime] ; reset rx active sense counter
     STR     R7, [R12, #SongPosPointer]
     LDMFD   sp!,{r3-r4,r6-r8,r10-r11, pc}^


TxData ROUT                ; RealTime transmission has priority
; r0 = uart address
; r1 = acia status reg or uart imr
; r2 = modeflags (updateable)
; r9 = port number (0-3)
     STMFD   sp!,{r3-r6, lr}
     ANDS    r6, r2, #SendRT    ;have I got an order to send a RealTime Byte?
     BEQ     GetBufferedData
; send real time
     MOV     r6, r6, LSR#InfoShift  ; move command to bottom byte
     ADD     r6, r6, #TimingClockVal - 1 ; convert to Midi Real Time byte
     CMP     r6, #StartVal
     LDREQ   r3, [r12, #SongPosPointer]
     MOVEQ   r3, #0                ; if Start clear spp
     STREQ   r3, [r12, #SongPosPointer]
     CMP     r6, #StopVal          ; IF Stop
     BICEQ   r2, r2, #InternalCount ; clear InternalCount flag
     ORRNE   r2, r2, #InternalCount ; else set InternalCount flag
     LDREQ   r3, [r12, #CurrentQBeat]
     BICEQ   r3, r3, #&FF000000  ; clear bar count
     BICEQ   r3, r3, #&00FF0000
     STREQ   r3, [r12, #CurrentQBeat]
     BL      SendThisByte ; in r6
     LDMFD   sp!, {r3-r6,pc}^      ; return
GetBufferedData     
     ADD     r3, r12, r9, LSL#2 ; offset for current Port
     LDR     R4, [r3, #TxBufferPntrs]
     MOV     r5, r4, LSR#16       ; out pointer
     BIC     r4, r4, #&FF000000   ; in pointer
     BIC     r4, r4, #&00FF0000
     CMP     R4, R5   ; Check if buffer has data and send it
     BNE     GetTD    ; there is data to send
  ; tx buffer empty
     TST     r0, #PortTypeTestBit  ; =0 for MIDI / IO podule
     MOVNE   R4, #RxRDYirq
     MOVEQ   R4, #ACIAControlValue
     STRNEB  R4, [R0, #IROffset] ; Disable TxRDY interrupts now TX buffer empty
     STREQB  R4, [R0] ; Disable TDRE interrupts now TX buffer is empty
     ADD     r3, r12, r9 ; offset for current Port
     STRB    R4, [R3, #IMRCurrent] ; preserve copy of uart imr, or acia status
; transmit active sensing ?
     TST     r2, #TxActiveSenseBit
     LDMEQFD sp!, {r3-r6,pc}^ ; If no data to tx then return
     LDRB    R4, [R3, #TxInactiveTime]
     CMP     R4, #MaxTxInactivity
     MOVGE   R6, #ActiveSensingVal ;Time has expired. Send actv sensing status
     BLGE    SendThisByte
     LDMFD   sp!, {r3-r6,pc}^      ; return
GetTD
     ADD     r3, r12, r9, LSL#2
     LDR     r3, [r3, #TxBufferStarts]
     LDRB    R6, [R3, R5]   ; load byte from tx buffer
     BL      SendThisByte
     ADD     R5, R5, #1     ; increment buffer pointer
     CMP     R5, #TxBufferLen
     MOVGE   R5, #0          ; circular buffer, reset pointer
     ADD     r3, r12, r9, LSL#2 ; offset for current Port
     LDR     r4, [r3, #TxBufferPntrs]
     BIC     r4, r4, #&FF000000
     BIC     r4, r4, #&00FF0000
     ORR     r5, r4, r5, LSL#16  ; combine pointers
     STR     r5, [r3, #TxBufferPntrs]
     LDMFD   sp!, {r3-r6,pc}^      ; return

SendThisByte  ROUT ; r6 contains byte to send, r0 is uartbase;
     STMFD   sp!, {r3, r4, lr}
     TST     r0, #PortTypeTestBit
     STRNEB  R6, [R0, #HROffset] ;write transmit data to UART transmit register
     STREQB  R6, [R0, #DataRegOffset]  ;write tx data to ACIA transmit register
  ; reset active sensing counter
     TST     R2, #TxActiveSenseBit
     LDMEQFD sp!, {r3, r4, pc}^
     ADD     r3, r12, r9 ; offset for current Port
     LDRB    r4, [r3, #TxInactiveTime] ; get tx active sense counter
     MOVS    r4, r4      ; is it non-zero? => enabled
     MOVNE   r4, #1      ; min is 1; 0 is reserved for disabled state
     STRNEB  r4, [r3, #TxInactiveTime] ; reset tx active sense counter
     LDMFD   sp!, {r3, r4, pc}^

SetOverflowError
; r2 = modeflagbits (updated)
; r9 = port 0..3 (unchanged, except precautionary AND with 3)
; r12 = wp (unchanged)
     STMFD   sp!, {r0-r1, lr}
     AND     r9, r9, #3   ; make absolutely sure port is not out of range!
     MOV     r0, #"B" ;  MIDI receive data buffer full error
     ADD     r1, r12, r9 ; Port offset
     STRB    r0, [r1, #ErrorFlag] ; set error flag. Data will be overwritten
     ORR     r2, r2, #BackgroundError:OR:RxBufferOverflow ; set error flags
     MOV     r1, #1
     MOV     r1, r1, LSL r9 ; set port bit 0..3
     LDR     r0, [r12, #OverflowPort]
     ORR     r0, r0, r1  ; set port overflow error bit
     AND     r0, r0, #&F ; simple precautionary range-limit..can only be bits 0..3
     STR     r0, [r12, #OverflowPort]
     LDMFD   sp!, {r0-r1, pc}^

DispatchMidiScheduler
; NB this calls SchedulerTake which will re-enable irqs before returning
; disable uart interrupts to ensure this routine is not re-entered
     STMFD   sp!, {r0, r8, lr}
     MOV     r8, r2, LSR#NExtraPortsShift
     AND     r8, r8, #3        ; number of installed Ports
     BL      DisableUartIrqs
     STR     r7, [r12, #SongPosPointer]     ; save SPP
     STR     r2, [r12, #ModeFlagBits]     ; save mode flags
; change to svc mode to call SchedulerTake
     TEQP    pc, #I_bit + SVC_mode  ; check mode-change delay
     MOV     r0, r7  ; current time in r0
     STMFD   sp!, {lr} ; save svc-mode link register
     BL      SchedulerTake  ; execute the scheduled commands up to this time
     LDMFD   sp!, {lr} ; restore lr
     TEQP    pc, #I_bit + IRQ_mode  ; check delay
     MOVNV   r0, r0  ; delay
     LDR     r2, [r12, #ModeFlagBits]     ; restore mode flags
     LDR     r7, [r12, #SongPosPointer]     ; restore SPP
; re-enable uart interrupts
     MOV     r8, r2, LSR#NExtraPortsShift
     AND     r8, r8, #3        ; number of installed Ports
     BL      EnableUartIrqs
     LDMFD   sp!, {r0, r8, lr}

DispatchSoundScheduler
; NB this calls SoundScheduler which could call a SWI which could re-enable irqs
; disable uart interrupts to ensure this routine is not re-entered
     STMFD   sp!, {r0, r8, lr}
     MOV     r8, r2, LSR#NExtraPortsShift
     AND     r8, r8, #3        ; number of installed Ports
     BL      DisableUartIrqs
     STR     r7, [r12, #SongPosPointer]     ; save SPP
     STR     r2, [r12, #ModeFlagBits]     ; save mode flags
     STMFD   sp!, {r12}
     LDR     r0, [r12, #Sound_QTick]
 ; ensure does not enter centisecond code here (a plausible address in Sound_QTick)
     CMP      r0, r12
      MOVEQ    r0, #SoundSystemNIL  ; and the next test will fail with NE
      STREQ    r0, [r12, #Sound_QTick]
     TST     r0, #SoundSystemNIL    ; test for installed scheduler
     MOVEQ   r12, r0                ; wp
     LDREQ   r0, [r12]
     TSTEQ   r0, #SoundSystemNIL     ; Valid Level2?
     MOVEQ   lr, pc
     MOVEQ   pc, r0                  ; Call Level2
     LDMFD   sp!, {r12}  ; ..and return to here
     LDR     r2, [r12, #ModeFlagBits]     ; restore mode flags
     LDR     r7, [r12, #SongPosPointer]     ; restore SPP
; re-enable uart interrupts
     MOV     r8, r2, LSR#NExtraPortsShift
     AND     r8, r8, #3        ; number of installed Ports
     BL      EnableUartIrqs
     LDMFD   sp!, {r0, r8, pc}^

  END
