VDU 7; OSBYTE 0-14, 16, 18, 19, 117, 118, 124-126, 128-130, 136, 137, 144, 147, 149, 151, 154, 155, 160, 247; OSWORD 0-8; OSBYTE entry point; OSWORD entry point; *LINE; *CODE; Serial baud rate / sound buffer table; GSINIT; GSREAD; Keyboard handling routines - 1360 bytes (8.3%)


§1. OSBYTE/OSWORD lookup table.

 Valid OSBYTEs are in the arbitrary ranges 0-21, 117-160, 166-255. The range 166-255 reads
 and writes to a contiguous range of memory locations, and are implemented by a single
 routine. Otherwise the reasoning behind these ranges is unclear. Perhaps 0-21 were
 anticipated (erroneously or otherwise) to be the more commonly used OSBYTEs?

 Valid OSWORDs are in the range 0-13, and OSWORDs 224-255 are sent through USERV.
.osbyteAndOSWORDRoutineTable = $e5b3
    !word .osbyte0EntryPoint        OSBYTE   0 - Generate error message/return version number
    !word .osbyte1EntryPoint        OSBYTE   1 - Read/Write user flag
    !word .osbyte2EntryPoint        OSBYTE   2 - select input stream
    !word .osbyte3EntryPoint        OSBYTE   3 - Select output stream
    !word .osbyte4EntryPoint        OSBYTE   4 - Enable / disable cursor editing
    !word .osbyte5EntryPoint        OSBYTE   5 - Select printer type
    !word .osbyte6EntryPoint        OSBYTE   6 - Set printer ignore character
    !word .osbyte7EntryPoint        OSBYTE   7 - Set RS-423 baud rate for receiving data
    !word .osbyte8EntryPoint        OSBYTE   8 - Set RS-423 baud rate for transmitting data
    !word .osbyte9EntryPoint        OSBYTE   9 - Duration of first colour
    !word .osbyte10EntryPoint       OSBYTE  10 - Duration of second colour
    !word .osbyte11EntryPoint       OSBYTE  11 - Set keyboard autorepeat delay
    !word .osbyte12EntryPoint       OSBYTE  12 - Set keyboard autorepeat rate
    !word .osbyte13EntryPoint       OSBYTE  13 - Disable events
    !word .osbyte14EntryPoint       OSBYTE  14 - Enable events
    !word .osbyte15EntryPoint       OSBYTE  15 - Flush selected buffer class
    !word .osbyte16EntryPoint       OSBYTE  16 - Select ADC channels
    !word .osbyte17EntryPoint       OSBYTE  17 - Start ADC conversions
    !word .osbyte18EntryPoint       OSBYTE  18 - Reset soft keys
    !word .osbyte19EntryPoint       OSBYTE  19 - Wait for vertical sync
    !word .osbyte20EntryPoint       OSBYTE  20 - 'Explode' soft character definitions
    !word .osbyte21EntryPoint       OSBYTE  21 - Flush specific buffer

    !word .osbyte117EntryPoint      OSBYTE 117 - Read VDU status
    !word .osbyte118EntryPoint      OSBYTE 118 - Set keyboard LEDs based on keyboard status
    !word .osbyte119EntryPoint      OSBYTE 119 - Close Spool / Exec files
    !word .osbyte120EntryPoint      OSBYTE 120 - Write key pressed data
    !word .osbyte121EntryPoint      OSBYTE 121 - Keyboard scan from value in X
    !word .osbyte122EntryPoint      OSBYTE 122 - Keyboard scan from 16 decimal
    !word .osbyte123EntryPoint      OSBYTE 123 - Warn OS about printer going dormant
    !word .osbyte124EntryPoint      OSBYTE 124 - Clear ESCAPE condition
    !word .osbyte125EntryPoint      OSBYTE 125 - Set ESCAPE condition
    !word .osbyte126EntryPoint      OSBYTE 126 - Acknowledge detection of ESCAPE condition
    !word .osbyte127EntryPoint      OSBYTE 127 - Check for EOF on open file
    !word .osbyte128EntryPoint      OSBYTE 128 - Read ADC Channel / get buffer status
    !word .osbyte129EntryPoint      OSBYTE 129 - Read key within time limit
    !word .osbyte130EntryPoint      OSBYTE 130 - Read machine high order address
    !word .osbyte131EntryPoint      OSBYTE 131 - Read OSHWM (OS High Water Mark)
    !word .osbyte132EntryPoint      OSBYTE 132 - Read HIMEM (start of screen memory)
    !word .osbyte133EntryPoint      OSBYTE 133 - Read start of screen memory for given MODE
    !word .osbyte134EntryPoint      OSBYTE 134 - Read text cursor position
    !word .osbyte135EntryPoint      OSBYTE 135 - Read character at text cursor position
    !word .osbyte136EntryPoint      OSBYTE 136 - Execute code via User Vector (*CODE)
    !word .osbyte137EntryPoint      OSBYTE 137 - Turn on tape motor
    !word .osbyte138EntryPoint      OSBYTE 138 - Put byte into buffer
    !word .osbyte139EntryPoint      OSBYTE 139 - Select file options (*OPT)
    !word .osbyte140EntryPoint      OSBYTE 140 - Select TAPE file system
    !word .osbyte141EntryPoint      OSBYTE 141 - Select ROM file system
    !word .osbyte142EntryPoint      OSBYTE 142 - Enter language ROM (at $8000)
    !word .osbyte143EntryPoint      OSBYTE 143 - Pass service commands to sideways ROMs
    !word .osbyte144EntryPoint      OSBYTE 144 - *TV
    !word .osbyte145EntryPoint      OSBYTE 145 - Get byte from buffer
    !word .osbyte146EntryPoint      OSBYTE 146 - Read a byte from FRED
    !word .osbyte147EntryPoint      OSBYTE 147 - Write a byte to FRED
    !word .osbyte148EntryPoint      OSBYTE 148 - Read a byte from JIM
    !word .osbyte149EntryPoint      OSBYTE 149 - Write a byte to JIM
    !word .osbyte150EntryPoint      OSBYTE 150 - Read a byte from SHEILA
    !word .osbyte151EntryPoint      OSBYTE 151 - Write a byte to SHEILA
    !word .osbyte152EntryPoint      OSBYTE 152 - Examine buffer status
    !word .osbyte153EntryPoint      OSBYTE 153 - Put byte in input buffer
    !word .osbyte154EntryPoint      OSBYTE 154 - Write to Video ULA's Video Control Register
    !word .osbyte155EntryPoint      OSBYTE 155 - Set palette value
    !word .osbyte156EntryPoint      OSBYTE 156 - Update ACIA Control Register and OS Copy
    !word .osbyte157EntryPoint      OSBYTE 157 - Fast Tube BPUT
    !word .osbyte158EntryPoint      OSBYTE 158 - Read byte from speech processor
    !word .osbyte159EntryPoint      OSBYTE 159 - Write byte to speech processor
    !word .osbyte160EntryPoint      OSBYTE 160 - Read VDU Variable

    !word .osbyte166to255EntryPoint OSBYTE 166 to 255 - Implemented via table lookup
    !word .uservJumper              OSWORD 224-255 (unknown OSWORDs sent through USERV)


OSWORD lookup table - effectively a continuation of the same table as above

    !word .osword0EntryPoint        OSWORD   0 - Read line from current input into memory
    !word .osword1EntryPoint        OSWORD   1 - Read system clock
    !word .osword2EntryPoint        OSWORD   2 - Write system clock
    !word .osword3EntryPoint        OSWORD   3 - Read interval timer
    !word .osword4EntryPoint        OSWORD   4 - Write interval timer
    !word .osword5EntryPoint        OSWORD   5 - Read a byte from I/O memory
    !word .osword6EntryPoint        OSWORD   6 - Write a byte to I/O memory
    !word .osword7EntryPoint        OSWORD   7 - Make a sound
    !word .osword8EntryPoint        OSWORD   8 - Define an envelope
    !word .osword9EntryPoint        OSWORD   9 - Read a pixel
    !word .osword10EntryPoint       OSWORD  10 - Read character definition
    !word .osword11EntryPoint       OSWORD  11 - Read palette
    !word .osword12EntryPoint       OSWORD  12 - Write Palette
    !word .osword13EntryPoint       OSWORD  13 - Read last two graphic cursor positions

§2. OSBYTE 136 - Execute code via User Vector (*CODE).

.osbyte136EntryPoint = $e657
    LDA #0                                              A=0
    fall through...

§3. Jumps to the User vector.

 This is called in response to *CODE, *LINE, or an OSWORD in the range 224-255. By default
 this does nothing, but user code can intercept the USERV vector to provide new functionality
 for these calls.

 On Entry:
       A=0         called from *CODE / *FX 136         .osbyteX / .osbyteY are parameters
       A=1         called from *LINE                   XY = address of string
       A=224-255   called from OSWORD 224-255          .oswordX / .oswordY are parameters
.uservJumper = $e659
    JMP (.vectorUSERV)                                  Jump via USERV

§4. OSBYTE 126 - Acknowledge detection of ESCAPE condition.

 If an ESCAPE condition has occurred, then this acknowledges (clears) it, informing the
 second processor of it via the Tube if present.
.osbyte126EntryPoint = $e65c
    LDX #0                                              X=0
    BIT .escapeFlag                                     check for ESCAPE flag
    BPL .osbyte124EntryPoint                            if (no ESCAPE flag) then branch
                                                        (just clear the ESCAPE condition)
    LDA .escapeEffects                                  get ESCAPE effects
    BNE +                                               if (escape effects is non-zero)
                                                        then branch (no effects)

                                                        reset VDU paging counter
                                                        close EXEC files
                                                        purge all buffers
                                                        Clear ESCAPE condition

    CLI                                                 allow interrupts
    STA .pagedModeCounter                               zero the number of lines printed
                                                        since last halt in paged mode
    JSR .closeAndOptionallyOpenANewEXECFile             close any open EXEC file (A=0, Z=0)
    JSR .flushAllBuffers                                clear all buffers
+
    LDX #$FF                                            X=$FF to indicate ESCAPE acknowledged
    fall through...

§5. OSBYTE 124 - Clear ESCAPE condition.

.osbyte124EntryPoint = $e673
    CLC                                                 clear carry
    fall through...

§6. OSBYTE 125 - Set ESCAPE condition.

 On Entry:
       The regular OSBYTE entry sets carry (meaning set the ESCAPE condition).
       If falling through from OSBYTE 124 above then carry is clear (meaning clear the ESCAPE
       condition).
.osbyte125EntryPoint = $e674
    ROR .escapeFlag                                     set/clear bit 7 of ESCAPE flag
    BIT .tubePresentFlag                                check if Tube is present
    BMI +                                               if (Tube present) then branch
    RTS                                                 
+
    JMP .tubeCopyESCAPEFlagToSecondProcessor            Jumps into a Tube entry point

§7. OSBYTE 137 - Turn on/off tape motor.

 On Entry:
       X = 0 to turn the motor off; otherwise turn the motor on
.osbyte137EntryPoint = $e67f
    LDA .serialULARegisterCopy                          get serial ULA control setting
    TAY                                                 Y=A
    ROL                                                 rotate left (shifts bit 7 into carry)
    CPX #1                                              if (X>=1, i.e. turn on motor) then
                                                        set carry
    ROR                                                 rotate carry back in serialULA copy
                                                        (bit 7)
    BVC .setSerialULADirectly                           ALWAYS branch

§8. OSBYTE 8 - Set RS-423 baud rate for transmitting data.

 On Entry:
       X = baud rate
.osbyte8EntryPoint = $e689
    LDA #%00111000                                      Once it's EOR'd with the next
                                                        instruction,
                                                        this value becomes %00000111 (set
                                                        transmit baud rate)
    fall through...

§9. OSBYTE 7 - Set RS-423 baud rate for receiving data.

 On Entry:
       X = baud rate
.osbyte7EntryPoint = $e68b
    EOR #%00111111                                      converts 7 -> $38 = %00111000 (set
                                                        receive baud rate)
                                                        converts $38 -> 7 = %00000111 (set
                                                        transmit baud rate)
    STA .tempStoreFA                                    store result
    LDY .serialULARegisterCopy                          get serial ULA control register
                                                        setting
    CPX #9                                              }
    BCS .putNewSettingYInXAndReturn                     } is it 9 or more then branch (exit)
    AND .bufferTypeAndSerialBaudRatesTable,X            and with byte from look up table
    STA .tempStoreFB                                    store it
    TYA                                                 put Y in A
    ORA .tempStoreFA                                    set the transmit or receive baud
                                                        rate bits to 1
    EOR .tempStoreFA                                    zero the transmit or receive baud
                                                        rate bits to 0
    ORA .tempStoreFB                                    set baud rate from value read from
                                                        table lookup
    ORA #$40                                            set bit 6
    EOR .tapeRS423SelectionFlag                         clear bit 6 if tape active (0=RS-423
                                                        or $40=TAPE)
.setSerialULADirectly = $e6a7
    STA .serialULARegisterCopy                          store serial ULA flag
    STA .serialULAControlRegister                       and write to control register
.putNewSettingYInXAndReturn = $e6ad
    TYA                                                 put Y in A to save old contents
.setXAndReturn = $e6ae
    TAX                                                 write new setting to X
    RTS                                                 

§10. OSBYTE 9 - Duration of first colour.

 On Entry:
      Y = 0
      X = new value (in 1/50 of a second)
.osbyte9EntryPoint = $e6b0
    INY                                                 Y=1
    CLC                                                 clear carry
    fall through...

§11. OSBYTE 10 - Duration of second colour.

 On Entry:
       Y = 0 (or 1 if falling through from OSBYTE 9 call above)
       X = new value (in 1/50 of a second)
.osbyte10EntryPoint = $e6b2
    LDA .videoULAFirstFlashingColourInterval,Y          get mark period count
    PHA                                                 push it
    TXA                                                 get new count
    STA .videoULAFirstFlashingColourInterval,Y          store it
    PLA                                                 get back original value
    TAY                                                 put it in Y
    LDA .videoULAFlashingColourIntervalCount            get value of flash counter
    BNE +                                               if (not zero) then branch
    STX .videoULAFlashingColourIntervalCount            restore old value
    LDA .videoULAVideoControlRegisterCopy               get current video ULA control
                                                        register setting
    PHP                                                 push flags
    ROR                                                 rotate bit 0 into carry, carry into
                                                        bit 7
    PLP                                                 get back flags
    ROL                                                 rotate back carry into bit 0
    STA .videoULAVideoControlRegisterCopy               store it in RAM copy
    STA .videoULAControlRegister                        and ULA control register
+
    BVC .putNewSettingYInXAndReturn                     ALWAYs branch

§12. OSBYTE 2 - Select input stream.

 On Entry:
       X contains stream number
       X = 0 (keyboard selected, RS-423 disabled)
       X = 1 (RS-423 selected and enabled)
       X = 2 (keyboard selected, RS-423 enabled)
.osbyte2EntryPoint = $e6d3
    TXA                                                 A=X
    AND #1                                              blank out bits 1 - 7
    PHA                                                 push A
    LDA .rs423ControlRegisterCopy                       and get current ACIA control setting
    ROL                                                 Bit 7 into carry
    CPX #1                                              }
    ROR                                                 } if (X>=1, i.e. RS-423 enabled)
                                                        } then set bit 7 of A, otherwise
                                                        } clear it
    CMP .rs423ControlRegisterCopy                       compare this with ACIA control
                                                        setting
    PHP                                                 push flags
    STA .rs423ControlRegisterCopy                       put A into ACIA control setting
    STA .acia6850ControlRegister                        and write to control register
    JSR .setRS423Active                                 set up RS-423 buffer
    PLP                                                 get back P
    BEQ +                                               if (new setting different from old)
                                                        then branch
    BIT .acia6850DataRegister                           set bit 6 and 7

+
    LDX .currentInputBuffer                             get current input buffer number
    PLA                                                 get back A
    STA .currentInputBuffer                             store it
    RTS                                                 

§13. OSBYTE 13 - Disable events.

 For information on Events, see .eventEntryPoint

 On Entry:
       X contains event number 0-9 to disable
.osbyte13EntryPoint = $e6f9
    TYA                                                 Y=0; A=0
    fall through...

§14. OSBYTE 14 - Enable events.

 For information on Events, see .eventEntryPoint

 On Entry:
       A contains zero for disabling the event, or non-zero to enable the event.
           This value is 14 if entry by OSBYTE 14 (which enables the event), or zero if
           falling through from OSBYTE 13 above (which disables the event).
       X contains event number 0-9 to enable
 On Exit:
       Y contains previous value
.osbyte14EntryPoint = $e6fa
    CPX #10                                             }
    BCS .setXAndReturn                                  } if X>9 then branch (set X from A
                                                        } and return)
    LDY .eventEnabledFlags,X                            get old event enable flag
    STA .eventEnabledFlags,X                            store new value in flag
    BVC .putNewSettingYInXAndReturn                     and exit

§15. OSBYTE 16 - Select ADC channels.

 Selects the range of channels for analogue to digital conversion (0-4)
 See .osbyte17EntryPoint

 On Entry:
       X contains channel number (1-4) or 0 to disable conversions
       Y = 0
.osbyte16EntryPoint = $e706
    BEQ +                                               if (X=0) then branch
    JSR .osbyte17EntryPoint                             start conversion
+
    LDA .maximumADCChannelNumber                        get current maximum ADC channel
                                                        number
    STX .maximumADCChannelNumber                        store new value
    TAX                                                 put old value in X
    RTS                                                 

§16. OSBYTE 129 - Read key within time limit.

 On Entry:
       X and Y contains either:
           time limit in centi-seconds (Y=$7F max)
       or Y=$FF and X=-ve INKEY value for an instant read of a specific key
       or Y=$FF and X=0 to read the machine type (see NAUG Section 24.4.1 Page 428)
.osbyte129EntryPoint = $e713
    TYA                                                 A=Y
    BMI .readNegativeKeyNumber                          if (Y negative) then branch
    CLI                                                 allow interrupts
    JSR .osbyte129Timed                                 and go to timed routine
    BCS +                                               if (carry set) then branch
    TAX                                                 then X=A
    LDA #0                                              A=0
+
    TAY                                                 Y=A
    RTS                                                 

.readNegativeKeyNumber = $e721
    TXA                                                 A=X
    EOR #$7F                                            convert to keyboard input
    TAX                                                 X=A
    JSR .keyJumper                                      then scan keyboard
    ROL                                                 put bit 7 into carry

    if (carry is set) then return $FFFF else return $0000
.setXYAndExit = $e729
    fall through...

§17. OSBYTE 130 - Read 'machine high order address'.

 Filing systems use 32 bit addresses, where $FF,$FF is used in the top two bytes to
 indicate the address is in the main (I/O processor) memory as opposed to the second
 processor memory. This routine returns the two values used for this purpose, namely $FF,$FF.

 On Entry:
       Carry set if entry is via OSBYTE 130
       Carry may be clear if falling through from routines above,
       either reading a key (.readNegativeKeyNumber) or doing an ADC conversion (.setXYAndExit).
.osbyte130EntryPoint = $e729
    LDX #$FF                                            X=$FF
    LDY #$FF                                            Y=$FF
    BCS +                                               if (carry set, depends on entry
                                                        point) then branch (exit)
    INX                                                 X=0
    INY                                                 Y=0
+
    RTS                                                 

§18. Get free space from an input buffer, or used space from an output buffer.

 For buffer numbers, see .bufferNumberKeyboard.

 On Entry:
       X=255 - buffer number
.countNegativeBuffer = $e732
    TXA                                                 buffer number in A
    EOR #$FF                                            invert it
    TAX                                                 X=A
    CPX #2                                              is X>=2 (C set if output buffer, get
                                                        bytes used)
.countBufferStartup = $e738
    CLV                                                 clear V flag (meaning count buffer)
    BVC .countOrPurgeBuffer                             ALWAYS branch (count buffer)

.purgeBuffer = $e73b
    BIT .allBitsSet                                     set V flag (meaning purge buffer) (N
                                                        flag also set)
.countOrPurgeBuffer = $e73e
    JMP (.vectorCNPV)                                   See .cnpEntryPoint for default
                                                        implementation

§19. Get free bytes from RS-423 Input Buffer.

 On Exit:
   carry set, all is OK
   carry clear, free space in buffer is low
.getRS423InputBufferFreeBytes = $e741
    SEC                                                 to check for free bytes
    LDX #1                                              X=1 to point to RS-423 input buffer
    JSR .countBufferStartup                             and count it
    CPY #1                                              check the high byte of the length
    BCS +                                               if (one or more) then branch (exit)
    CPX .rs423HandshakeExtent                           compare with minimum buffer space
                                                        free
+
    RTS                                                 

§20. OSBYTE 128 - Read ADC Channel, or get buffer status.

 On Entry:              On Exit:
 X=0                    Y contains number of last channel converted (1-4); X = fire buttons
 X=channel number (1-4) X,Y contain 16 bit value read from ADC channel (See .osbyte17EntryPoint)
 X<0 Y=$FF              X returns information about various buffers
 X=$FF (keyboard)       X = number of characters in buffer
 X=$FE (RS-423 input)   X = number of characters in buffer
 X=$FD (RS-423 output)  X = number of empty spaces in buffer
 X=$FC (printer)        X = number of empty spaces in buffer
 X=$FB (sound 0)        X = number of empty spaces in buffer
 X=$FA (sound 1)        X = number of empty spaces in buffer
 X=$F9 (sound 2)        X = number of empty spaces in buffer
 X=$F8 (sound 3)        X = number of empty spaces in buffer
 X=$F7 (Speech)         X = number of empty spaces in buffer
.osbyte128EntryPoint = $e74f
    BMI .countNegativeBuffer                            if (X is -ve) then branch (count
                                                        space free/used)
    BEQ .lastChannelConverted                           if (X is zero) then branch
    CPX #5                                              }
    BCS .setXYAndExit                                   } if channel is not valid then
                                                        } branch (set X and Y to 255 and
                                                        } exit)
    LDY .highByteLastByteFromADCChannel1 - 1,X          get last value read for channel of
                                                        interest (high byte)
    LDA .lowByteLastByteFromADCChannel1 - 1,X           get last value read for channel of
                                                        interest (low byte)
    TAX                                                 X=low byte
    RTS                                                 

.lastChannelConverted = $e75f
    LDA .systemVIARegisterB                             read system VIA port B
    ROR                                                 move high nybble to low
    ROR                                                 
    ROR                                                 
    ROR                                                 
    EOR #$FF                                            and invert it
    AND #3                                              isolate the FIRE buttons
    LDY .adcLastChannelRead                             read last channel read
    STX .adcLastChannelRead                             store zero in last channel read
    TAX                                                 A=X bits 0 and 1 indicate fire
                                                        buttons
    RTS                                                 

§21. OSBYTE - default entry point.

 This entry point is executed for all OSBYTE calls. It routes the call through to one of the
 individual OSBYTE entry points by means of a table (see .osbyteAndOSWORDRoutineTable).

 Note: OSBYTEs 0-127 only return a value in the X register, whereas OSBYTEs 128-255 can
 return values in X,Y and carry. This fact is used to speed up access via the Tube.
.osbyteEntryPoint = $e772
    PHA                                                 save A
    PHP                                                 save Processor flags
    SEI                                                 disable interrupts
    STA .osbyteA                                        }
    STX .osbyteX                                        } copy A,X,Y into zero page
    STY .osbyteY                                        }
    LDX #.romServiceCallUnrecognisedOSBYTE              set X so that (if necessary) we can
                                                        signal to ROMs that an unrecognised
                                                        osbyte is being attempted
    CMP #117                                            
    BCC .osbyte0to116                                   if (A < 117) then branch
    CMP #161                                            
    BCC .osbyte117To160                                 if (A < 161) then branch
    CMP #166                                            
    BCC .unknownOSBYTEOrOSWORD                          if (A < 166) then branch (with carry
                                                        clear)
    CLC                                                 clear carry
    at this point OSBYTE number in A >= 166
    fall through...

§22. osbyte166to255orOSWORD224to255.

.osbyte166to255orOSWORD224to255 = $e78a
    NOTE: Entry 161 and 162 in the table have special meanings, which are used here
    if entry is via OSBYTE, then carry is clear and we look up entry 161 in table
    if entry is via OSWORD, then carry is  set  and we look up entry 162 in table
    LDA #161                                            A = 161
    ADC #0                                              Add carry

.osbyte117To160 = $e78e
    SEC                                                 set carry
    SBC #95                                             if OSBYTE, converts A into range
                                                        (22-66); if OSWORD then A=67

.indexToTable = $e791
    ASL                                                 double A to get offset into table
    SEC                                                 set carry

.osbyteOrOSWORDTableLookup = $e793
    STY .osbyteY                                        store Y
    TAY                                                 Y=A

    check for Econet intercept
    [There's a quirk here:
    
     The following instruction sets V if bit 6 of .econetOSCallInterceptionFlag is
     set. If bit 7 is clear as well, then we call whatever OSBYTE routine with V set,
     which has some unexpected side effects. A few OSBYTE routines rely on V being clear,
     So for example:
    
          *FX 206,64      - sets the .econetOSCallInterceptionFlag to 64
          *FX 10,1        - set the flashing colour time
    
     This unexpectedly turns on RS-423 output, meaning you can't type at the keyboard
     any more!
    
     However, practically speaking there's no real reason you would need to set
     .econetOSCallInterceptionFlag to anything other than either zero or something with
     the top bit set so this probably never occurs in the wild.
    ]
    BIT .econetOSCallInterceptionFlag                   check Econet intercept flag (bit 7)
                                                        we expect bit 6 to be clear!
    BPL .skipEconetCall                                 if (no Econet intercept required)
                                                        then branch (skip Econet check)

    TXA                                                 A=X
    CLV                                                 V=0
    JSR .netvJumper                                     to JMP via ECONET vector
    BVS .finishUpOSBYTEOrOSWORD                         if (returned with V set) then branch
                                                        (finish up and exit)

.skipEconetCall = $e7a2
    read routine address from table and store in .tempStoreFA/B
    LDA .osbyteAndOSWORDRoutineTable + 1,Y              get address from table (high byte)
    STA .tempStoreFB                                    store as high byte
    LDA .osbyteAndOSWORDRoutineTable,Y                  get address from table (low byte)
    STA .tempStoreFA                                    store as low byte
    LDA .osbyteA                                        restore A
    LDY .osbyteY                                        restore Y
    BCS .setCarryXAndJumpOSBYTE                         if (carry is set, i.e. an OSBYTE
                                                        call) then branch

    OSWORD call. Set A to the value in the first byte of the parameter block
    LDY #0                                              
    LDA (.osbyteX),Y                                    read first byte from parameter
                                                        address block (XY)

.setCarryXAndJumpOSBYTE = $e7b6
    SEC                                                 set carry
    LDX .osbyteX                                        restore X

    Finally, call the OSBYTE / OSWORD routine address
    
    For an OSBYTE call:
      A,X,Y are set to the three parameters sent to .OSBYTE. These are also stored in
            .osbyteA, .osbyteX and .osbyteY.
      Carry is set.
      Zero flag is set if X is zero.
      Negative flag is set if X has it's top bit set.
      V is clear. (But see .osbyteOrOSWORDTableLookup above for a 'bug')
    
    For an OSWORD call:
      .oswordA hold the OSWORD number.
      A is set to the first byte of the parameter block at the address held in
    .oswordX/.oswordY
      X is set to .oswordX, the lower byte of the address
      Y is set to zero.
      Carry is set.
      Zero flag is set if X is zero.
      Negative flag is set if X has it's top bit set.
    
    JSR .osbyteOrOSWORDJumper                           call OSBYTE/OSWORD routine address
                                                        stored in (.tempStoreFA/B)

.finishUpOSBYTEOrOSWORD = $e7bc
    ROR                                                 C=bit 0
    PLP                                                 get back flags
    ROL                                                 bit 0=Carry
    PLA                                                 get back A
    CLV                                                 clear V
    RTS                                                 

§23. osbyte0to116.

.osbyte0to116 = $e7c2
    LDY #0                                              Y=0
    CMP #22                                             
    BCC .indexToTable                                   if (A < 22) then branch
    OSBYTE 22 to 116: these are unknown OSBYTE calls
    fall through with carry set...

§24. Unknown OSBYTE or OSWORD command. Pass it onto ROMs for servicing..

.unknownOSBYTEOrOSWORD = $e7c8
    PHP                                                 push flags
    PHP                                                 push flags

.pullTwiceAndOfferCallToROMs = $e7ca
    PLA                                                 pull flags
    PLA                                                 pull flags
    JSR .osbyte143EntryPoint                            offer paged ROMs service 7/8
                                                        unrecognised osbyte/word
    BNE .romsDontRecogniseUnknownCommand                if (ROMs don't recognise it) then
                                                        branch
    LDX .osbyteX                                        restore X
    JMP .finishUpOSBYTEOrOSWORD                         finish up

.romsDontRecogniseUnknownCommand = $e7d6
    PLP                                                 pull flags
    PLA                                                 and A
    BIT .allBitsSet                                     set N and V flags
    RTS                                                 

§25. Check if it's ok to print a message.

 Checks if the cassette filing system is in a critical (busy) state
 Check the catalogue status (are we currently doing a *CAT or equivalent)
 Checks the bits that allow printing short or long messages

 On Exit:
       A = 0  (and therefore Z set)   means don't print messages
       A != 0 (and therefore Z clear) means messages can be printed
.shouldPrintMessage = $e7dc
    LDA .tapeCritical                                   read cassette critical flag bit 7 =
                                                        busy
    BMI .zeroAAndExit                                   if (tape critically busy) then
                                                        branch (exit)
    LDA #%00001000                                      check current catalogue status bit
    AND .fsStatusByte                                   check tape filing system status byte
                                                        has the catalogue status bit set
    BNE +                                               if (catalogue bit set) then branch
                                                        (exit)
    LDA #%10001000                                      A=%10001000 (these are the bits
                                                        set if a message is required)
    AND .tapeCurrentOptionsByte                         AND with filing system options
                                                        (short/long message bits)
+
    RTS                                                 

§26. OSWORD - Default entry point.

 This entry point is executed for all OSWORD calls. It routes the call through to one of
 the individual OSWORD entry points by means of a table (see .osbyteAndOSWORDRoutineTable,
 at the end of which is the list of OSWORD entry points).
.oswordEntryPoint = $e7eb
    PHA                                                 Push A
    PHP                                                 Push flags
    SEI                                                 disable interrupts
    STA .oswordA                                        store A,X,Y
    STX .oswordX                                        
    STY .oswordY                                        
    LDX #.romServiceCallUnrecognisedOSWORD              set X so that if necessary, we can
                                                        signal to ROMs that an unrecognised
                                                        OSWORD is being attempted
    CMP #224                                            
    BCS .osbyte166to255orOSWORD224to255                 if (A >= 224) then branch (OSWORDs
                                                        224-255 call USERV)
    CMP #14                                             
    BCS .unknownOSBYTEOrOSWORD                          if (A >= 14) then branch (unknown
                                                        OSWORD)

    OSWORD 0-13
    ADC #68                                             add 68 to form pointer to table (68
                                                        to 81)
    ASL                                                 double it to form low byte of
                                                        pointer ($88 to $A2)
    BCC .osbyteOrOSWORDTableLookup                      ALWAYS branch

§27. OSWORD 5 - Read a byte from I/O memory.

 This allows the second processor to access main (first processor) memory across the Tube.

 On Entry:
       .oswordX/Y is the address of the five bytes of parameters:
           byte 0-3: 32 bit address of byte
           byte 4  : on exit byte read
       A is the LSB of the 32 bit address
.osword5EntryPoint = $e803
    JSR .setUpDataBlock                                 set up address of data block
    LDA (.tempStoreFA - 1,X)                            get byte
    STA (.oswordX),Y                                    store it
    RTS                                                 

§28. OSWORD 6 - Write a byte to I/O memory.

 This allows the second processor to access main (first processor) memory across the Tube.

 On Entry:
       .oswordX/Y is the address of the five bytes of parameters:
           byte 0-3: 32 bit address of byte
           byte 4  : byte to be written
       A is the LSB of the 32 bit address
.osword6EntryPoint = $e80b
    JSR .setUpDataBlock                                 set up address
    LDA (.oswordX),Y                                    get byte to write
    STA (.tempStoreFA - 1,X)                            write byte
.zeroAAndExit = $e812
    LDA #0                                              A=0
    RTS                                                 

§29. setUpDataBlock.

.setUpDataBlock = $e815
    STA .tempStoreFA                                    .tempStoreFA=A=low byte of address
    INY                                                 Y=1
    LDA (.oswordX),Y                                    get byte from block
    STA .tempStoreFB                                    .tempStoreFB=high byte of address
    LDY #4                                              Y=4
.exitWithX1 = $e81e
    LDX #1                                              X=1
    RTS                                                 

§30. OSBYTE 0 - Generate error message containing OS version information, or return OS version.

fx0.png

 If X is zero on entry, this generates an error message containing the OS version.
 Control is passed to the current BRK handler which by convention will print the error
 message and return control back to the current language. The calling program will not
 continue.

 If X is non-zero on entry, the OS version number is returned in X.
.osbyte0EntryPoint = $e821
    BNE .exitWithX1                                     if (X is not zero) then branch (exit
                                                        with X = 1 meaning OS 1.20)
    BRK                                                 generate error message
    !byte $F7                                           error number
    !text "OS 1.20",0                                   error string with zero terminator

§31. OSWORD 7 - Make a sound.

 On Entry:
       .oswordX/Y is the address of eight bytes of parameters:
           byte 0-1:  Channel ($HSFC as in the BASIC SOUND command)
           byte 2-3:  Amplitude
           byte 4-5:  Pitch
           byte 6-7:  Duration

 Channel
 -------
 H = hold bit (0-1) ensures that the existing sound currently playing on the given channel
     continues until the release phase of the envelope is totally finished before the next
     sound plays. Otherwise the sound is considered complete at the start of the release
     phase.
     The other sound parameters are then ignored.
 S = sync bits (0-2) is the number of future notes to be queued on other channels that are
     required before all the queued sounds start to play together at the same time.
 F = flush bit (0-1) stops any currently playing sound on the channel, forcing the new sound
     to play instead.
 C = channel (0-3). Channel 0 is the noise channel.

 If the high byte of the channel is $FF, then this is interpreted as a Speech command.

 Amplitude
 ---------
 The amplitude (loudness) is specified by a value in the range -15 (loudest) to 0 (silent).
 This is not a linear scale, with larger differences in volume between the louder sounds.
 Alternatively an envelope number can be specified (0-15).

 Pitch
 -----
 Given a pitch number in the range 0-255, the range of playable musical notes is from
 B2 to D8 in scientific notation (C4 being middle C). This is a range of 63 notes, over five
 octaves. See https://en.wikipedia.org/wiki/Scientific_pitch_notation.

 [Personally, I think one octave lower would have been nice. We basically only have one
 octave below middle C and over four octaves above. The range of notes is a little too
 much on the high end]

 The notes on the musical scale can be best approximated by the values in the following
 table. This table is similar to one in the original User Guide, but with more accurate
 values. Be aware that the pitch you hear also depends on the channel you play it on.
 See .soundPitchOffsetByChannelTable. The table below gives the best pitch value to use to
 most accurately achieve the musical note, and for each channel
 (Channel 1 / Channel 2 / Channel 3) where they differ.

                                  Octave
 Note    2     3      4      5               6               7               8
 ---------------------------------------------------------------------------------------
  C      -     4      52     100             147/148/149     195/196/197     242/244/246
  C#     -     8      56     104             152/152/153     200/201/202     246/249/251
  D      -     12     60     108             155/156/157     203/204/206     249/251/254
  D#     -     16     64     112             160/160/161     207/209/210     -
  E      -     20     68     115/116/116     163/164/165     211/213/214     -
  F      -     24     72     120             168/168/169     214/216/217     -
  F#     -     28     76     124             172/172/173     219/221/222     -
  G      -     32     80     128             175/176/177     222/224/226     -
  G#     -     36     84     131/132/132     179/180/181     226/228/229     -
  A      -     40     88     135/136/136     183/184/185     229/231/233     -
  A#     -     44     92     139/140/140     187/188/189     233/235/237     -
  B      0     48     96     144/144/145     192/193/194     237/240/242     -

 Pitch is ultimately passed to the sound chip as a 10 bit integer (0-1023), so by directly
 accessing the sound chip more accurate pitch can be achieved. This is done by sending data
 to the System VIA. See NAUG Section 21.2 Page 375 for a detailed description of the format
 required by the sound chip, and example code. Then see .setPitchNotNoise and
 see .sendToSoundChip for code to send the pitch.

 See .pitchLookupTableLow for the relationship between the 10-bit pitch and frequency.

 The best chip values for each musical note are as below:

         10 bit
         Chip
         Value         Frequency           Note = Ideal Frequency       Error
         ----------------------------------------------------------------------
           27          4629.63 Hz           D8  = 4698.64 Hz           69.01 Hz
           28          4464.29 Hz           C#8 = 4434.92 Hz           29.36 Hz
           30          4166.67 Hz           C8  = 4186.01 Hz           19.34 Hz
           32          3906.25 Hz           B7  = 3951.07 Hz           44.82 Hz
           34          3676.47 Hz           A#7 = 3729.31 Hz           52.84 Hz
           36          3472.22 Hz           A7  = 3520.00 Hz           47.78 Hz
           38          3289.47 Hz           G#7 = 3322.44 Hz           32.96 Hz
           40          3125.00 Hz           G7  = 3135.96 Hz           10.96 Hz
           42          2976.19 Hz           F#7 = 2959.96 Hz           16.24 Hz
           45          2777.78 Hz           F7  = 2793.83 Hz           16.05 Hz
           47          2659.57 Hz           E7  = 2637.02 Hz           22.55 Hz
           50          2500.00 Hz           D#7 = 2489.02 Hz           10.98 Hz
           53          2358.49 Hz           D7  = 2349.32 Hz            9.17 Hz
           56          2232.14 Hz           C#7 = 2217.46 Hz           14.68 Hz
           60          2083.33 Hz           C7  = 2093.00 Hz            9.67 Hz
           63          1984.13 Hz           B6  = 1975.53 Hz            8.59 Hz
           67          1865.67 Hz           A#6 = 1864.66 Hz            1.02 Hz
           71          1760.56 Hz           A6  = 1760.00 Hz            0.56 Hz
           75          1666.67 Hz           G#6 = 1661.22 Hz            5.45 Hz
           80          1562.50 Hz           G6  = 1567.98 Hz            5.48 Hz
           84          1488.10 Hz           F#6 = 1479.98 Hz            8.12 Hz
           89          1404.49 Hz           F6  = 1396.91 Hz            7.58 Hz
           95          1315.79 Hz           E6  = 1318.51 Hz            2.72 Hz
          100          1250.00 Hz           D#6 = 1244.51 Hz            5.49 Hz
          106          1179.25 Hz           D6  = 1174.66 Hz            4.59 Hz
          113          1106.19 Hz           C#6 = 1108.73 Hz            2.54 Hz
          119          1050.42 Hz           C6  = 1046.50 Hz            3.92 Hz
          127           984.25 Hz           B5  =  987.77 Hz            3.51 Hz
          134           932.84 Hz           A#5 =  932.33 Hz            0.51 Hz
          142           880.28 Hz           A5  =  880.00 Hz            0.28 Hz
          150           833.33 Hz           G#5 =  830.61 Hz            2.72 Hz
          159           786.16 Hz           G5  =  783.99 Hz            2.17 Hz
          169           739.64 Hz           F#5 =  739.99 Hz            0.34 Hz
          179           698.32 Hz           F5  =  698.46 Hz            0.13 Hz
          190           657.89 Hz           E5  =  659.26 Hz            1.36 Hz
          201           621.89 Hz           D#5 =  622.25 Hz            0.36 Hz
          213           586.85 Hz           D5  =  587.33 Hz            0.48 Hz
          225           555.56 Hz           C#5 =  554.37 Hz            1.19 Hz
          239           523.01 Hz           C5  =  523.25 Hz            0.24 Hz
          253           494.07 Hz           B4  =  493.88 Hz            0.19 Hz
          268           466.42 Hz           A#4 =  466.16 Hz            0.25 Hz
          284           440.14 Hz           A4  =  440.00 Hz            0.14 Hz
          301           415.28 Hz           G#4 =  415.30 Hz            0.02 Hz
          319           391.85 Hz           G4  =  392.00 Hz            0.15 Hz
          338           369.82 Hz           F#4 =  369.99 Hz            0.17 Hz
          358           349.16 Hz           F4  =  349.23 Hz            0.07 Hz
          379           329.82 Hz           E4  =  329.63 Hz            0.19 Hz
          402           310.95 Hz           D#4 =  311.13 Hz            0.18 Hz
          426           293.43 Hz           D4  =  293.66 Hz            0.24 Hz
          451           277.16 Hz           C#4 =  277.18 Hz            0.02 Hz
          478           261.51 Hz           C4  =  261.63 Hz            0.12 Hz
          506           247.04 Hz           B3  =  246.94 Hz            0.09 Hz
          536           233.21 Hz           A#3 =  233.08 Hz            0.13 Hz
          568           220.07 Hz           A3  =  220.00 Hz            0.07 Hz
          602           207.64 Hz           G#3 =  207.65 Hz            0.01 Hz
          638           195.92 Hz           G3  =  196.00 Hz            0.07 Hz
          676           184.91 Hz           F#3 =  185.00 Hz            0.09 Hz
          716           174.58 Hz           F3  =  174.61 Hz            0.03 Hz
          758           164.91 Hz           E3  =  164.81 Hz            0.09 Hz
          804           155.47 Hz           D#3 =  155.56 Hz            0.09 Hz
          851           146.89 Hz           D3  =  146.83 Hz            0.05 Hz
          902           138.58 Hz           C#3 =  138.59 Hz            0.01 Hz
          956           130.75 Hz           C3  =  130.81 Hz            0.06 Hz
         1012           123.52 Hz           B2  =  123.47 Hz            0.05 Hz
.osword7EntryPoint = $e82d
    INY                                                 increment Y to read byte 1 of block
    LDA (.oswordX),Y                                    read channel high byte 'HS'
    CMP #$FF                                            check if it's $FF
    BEQ .osword7Speech                                  if (high byte of channel is $FF)
                                                        then branch (handle speech command)
    CMP #$20                                            if (channel high byte is $20 or
                                                        more) then set carry
    LDX #.romServiceCallUnrecognisedOSWORD              X = paged ROM service request
                                                        'unrecognised OSWORD call'
    BCS .pullTwiceAndOfferCallToROMs                    if (channel >= $2000) then branch
                                                        (offer it to ROMs as unknown OSWORD)
    DEY                                                 point at start of block
    JSR .getChannelNumberAndFlush                       returns with carry set if flush is
                                                        required, and A=channel number (0-3)
    ORA #4                                              convert channel number to buffer
                                                        number (4-7)
    TAX                                                 X = buffer number
    BCC +                                               if (carry clear) then branch
    JSR .flushSoundBufferX                              flush buffer
    LDY #1                                              Y=1
+
    JSR .getSyncNumberAndHold                           get sync number (0-3) (carry set if
                                                        hold is required)
    STA .tempStoreFA                                    .tempStoreFA = sync number
    PHP                                                 save flags
    LDY #6                                              }
    LDA (.oswordX),Y                                    } get duration (low byte)
    PHA                                                 push it
    LDY #4                                              }
    LDA (.oswordX),Y                                    } get pitch (low byte)
    PHA                                                 push it
    LDY #2                                              }
    LDA (.oswordX),Y                                    } get amplitude (low byte) in the
                                                        } range -15 to 0 (static volume) or
                                                        } 1 to 4 (envelope controlled)

    At this point carry contains the 'hold' bit
    and .tempStoreFA contains the two 'sync' bits

    ROL                                                 multiply amplitude by two, and add
                                                        carry into bit zero = hold bit
    SEC                                                 
    SBC #2                                              subtract 2
    ASL                                                 }
    ASL                                                 } multiply by 4
    ORA .tempStoreFA                                    add sync value (0-3)

    
    To illustrate the changes made by the last six instructions, here is a table showing
    the changes at each step. Each row is a different value for the amplitude (values > 0
    are the envelope number 1-4), and the columns show the result after each subsequent
    instruction.
    
    Amplitude   Binary       ROL           SEC;SBC#2    ASL; ASL     ORA .tempStoreFA
                             h=hold bit                              ss=sync bits
    ---------------------------------------------------------------------------------
        4       %00000100    %0000100h     %0000011h    %00011h00    %00011hss
        3       %00000011    %0000011h     %0000010h    %00010h00    %00010hss
        2       %00000010    %0000010h     %0000001h    %00001h00    %00001hss
        1       %00000001    %0000001h     %0000000h    %00000h00    %00000hss
        0       %00000000    %0000000h     %1111111h    %11111h00    %11111hss
       -1       %11111111    %1111111h     %1111110h    %11110h00    %11110hss
       -2       %11111110    %1111110h     %1111101h    %11101h00    %11101hss
       -3       %11111101    %1111101h     %1111100h    %11100h00    %11100hss
       -4       %11111100    %1111100h     %1111011h    %11011h00    %11011hss
       -5       %11111011    %1111011h     %1111010h    %11010h00    %11010hss
       -6       %11111010    %1111010h     %1111001h    %11001h00    %11001hss
       -7       %11111001    %1111001h     %1111000h    %11000h00    %11000hss
       -8       %11111000    %1111000h     %1110111h    %10111h00    %10111hss
       -9       %11110111    %1110111h     %1110110h    %10110h00    %10110hss
      -10       %11110110    %1110110h     %1110101h    %10101h00    %10101hss
      -11       %11110101    %1110101h     %1110100h    %10100h00    %10100hss
      -12       %11110100    %1110100h     %1110011h    %10011h00    %10011hss
      -13       %11110011    %1110011h     %1110010h    %10010h00    %10010hss
      -14       %11110010    %1110010h     %1110001h    %10001h00    %10001hss
      -15       %11110001    %1110001h     %1110000h    %10000h00    %10000hss
    
    At this point:
    
    bit 7     = 0 for an envelope number
    bits 3-6  = envelope number - 1 (0-3), or volume in range (15 to 0)
    bit 2     = h, the hold bit
    bits 0-1  = ss, the sync bits (0-3)
    

    JSR .addByteToBuffer                                transfer byte to the sound buffer
    BCC .markAsUsedAndSendToBuffer                      if (byte successfully added to
                                                        buffer) then branch (send to buffer)
.finishUpSound = $e869
    PLA                                                 }
    PLA                                                 } failed to add to buffer, so pull
                                                        } the values off and fall through to
                                                        } exit via OSBYTE 117
    PLP                                                 }
    fall through...

§32. OSBYTE 117 - Read VDU status.

.osbyte117EntryPoint = $e86c
    LDX .vduStatusByte                                  get VDU status byte in X
    RTS                                                 

§33. VDU 7 (.charBELL).

 Generates a short tone.

 By default this plays a Treble C as a square wave for 0.3 seconds on channel 3. The
 frequency is as close as the sound chip can get to Treble C. It produces a 523.013Hz tone
 compared to the theoretical ideal 523.251Hz, and so is very close to the ideal pitch.

 The 523.013Hz figure for the frequency achieved takes into account that the OS changes the
 pitch slightly depending on the channel number (see .soundPitchOffsetByChannelTable). If
 it were played on channel 1 or 2, the frequency would not be as close to Treble C.
.vdu7EntryPoint = $e86f
    PHP                                                 push flags
    SEI                                                 disable interrupts
    LDA .soundBELLChannel                               get bell channel number in A
    AND #%00000111                                      clear all except bits 0-2
    ORA #%00000100                                      set bit 2 (=channel number + 4)
    TAX                                                 X=A=channel number+4=buffer number
    LDA .soundBELLAmplitudeEnvelope                     get bell amplitude/envelope number
    JSR .insJumper                                      store it in buffer X
    LDA .soundBELLDuration                              get bell duration
    PHA                                                 save it
    LDA .soundBELLPitch                                 get bell pitch
    PHA                                                 save it
.markAsUsedAndSendToBuffer = $e887
    SEC                                                 set carry
    ROR .channel0Occupancy - .bufferNumberSound0,X      and pass into bit 7 to indicate that
                                                        channel is active
    BMI .addPitchAndDurationToSoundBuffer               ALWAYS branch

§34. osword7Speech.

.osword7Speech = $e88d
    PHP                                                 push flags
    INY                                                 Y=2
    LDA (.oswordX),Y                                    get byte at offset 2
    PHA                                                 store it
    INY                                                 Y=3
    LDA (.oswordX),Y                                    get byte at offset 3
    PHA                                                 store it

    The stack now holds the two bytes of the SOUND command that normally specify amplitude
    These are the last two of three bytes we are going to add to the buffer. The first
    byte we add to the buffer is byte 0 from the sound command

    LDY #0                                              Y=0
    LDA (.oswordX),Y                                    get byte
    LDX #.bufferNumberSpeech                            X=8
    JSR .addByteToBuffer                                select speech buffer and pass A
    BCS .finishUpSound                                  if (carry set, i.e. failed to add to
                                                        buffer) then branch (restore stack
                                                        and exit)
    ROR .speechBufferEmptyFlag                          clear bit 7 of buffer empty flag to
                                                        show buffer is not empty
    fall through...

§35. addPitchAndDurationToSoundBuffer.

.addPitchAndDurationToSoundBuffer = $e8a4
    PLA                                                 pull pitch
    JSR .insJumper                                      enter it in buffer X
    PLA                                                 pull duration
    JSR .insJumper                                      enter it in buffer X
    PLP                                                 pull flags
    RTS                                                 

§36. OSWORD 8 - Define an envelope.

 On Entry:
       A = envelope number
       Y = 0

   .oswordX/Y is the address of 14 bytes of parameters:
       byte 0:   envelope number (also in A)
       byte 1:   bits 0-6 length of each step in centi-seconds
                 bit 7 clear means auto repeat
       byte 2:   change of Pitch per step in section 1 (-128 to +127)
       byte 3:   change of Pitch per step in section 2 (-128 to +127)
       byte 4:   change of Pitch per step in section 3 (-128 to +127)
       byte 5:   number of steps in section 1 (0 to 255)
       byte 6:   number of steps in section 2 (0 to 255)
       byte 7:   number of steps in section 3 (0 to 255)
       byte 8:   change of amplitude per step during attack  phase (-127 to +127)
       byte 9:   change of amplitude per step during decay   phase (-127 to +127)
       byte 10:  change of amplitude per step during sustain phase (-127 to +127)
       byte 11:  change of amplitude per step during release phase (-127 to +127)
       byte 12:  target level at end of attack phase (0 to 126)
       byte 13:  target level at end of decay  phase (0 to 126)
.osword8EntryPoint = $e8ae
    SBC #1                                              }
    ASL                                                 }
    ASL                                                 } set up appropriate displacement to
                                                        } storage area
    ASL                                                 } A=(A-1)*16 OR 15
    ASL                                                 }
    ORA #%00001111                                      }
    TAX                                                 X=A (destination offset)
    LDA #0                                              A=0 (value to write)
    LDY #16                                             Y = loop counter
.copyEnvelopeLoop = $e8bb
    CPY #14                                             
    BCS +                                               if (Y >= 14) then branch (don't read
                                                        from block, just store zero in
                                                        envelope buffer)
    LDA (.oswordX),Y                                    get byte from parameter block
+
    STA .envelopeBuffer,X                               and store it in appropriate area
    DEX                                                 decrement X
    DEY                                                 decrement Y
    BNE .copyEnvelopeLoop                               if (not zero) then branch (loop back
                                                        and do it again)
    RTS                                                 
    Note that envelope number is NOT transferred. The envelope number is not needed in
    the buffer since the address in the buffer determines the envelope number.

§37. getChannelNumberAndFlush.

 On Exit:
   When Y = 0:
       A = channel number (0-3)
       C set if channel low byte > 15, i.e. if flush is required

   When Y = 1:
       A = Sync number (0-3)
       C set if channel low byte > 15, i.e. if hold is required
.getChannelNumberAndFlush = $e8c9
.getSyncNumberAndHold = $e8c9
    LDA (.oswordX),Y                                    get byte (channel parameter low byte
                                                        or sync parameter low byte)
    CMP #$10                                            is it greater than 15, if so set
                                                        carry
    AND #3                                              get hardware channel number
    INY                                                 increment Y to point to byte 1 of
                                                        the parameters
    RTS                                                 

§38. OSWORD 3 - Read interval timer.

 The interval timer is centi-seconds and 5 bytes long.

 On Entry:
       .oswordX/Y is the address for the five byte result
.osword3EntryPoint = $e8d1
    LDX #.countdownIntervalTimer - (.timeClockA - 5)    X is the offset from (clockA-5) to
                                                        countdown timer
    BNE .readFiveBytesIntoBlock                         ALWAYS branch

§39. OSWORD 1 - Read system clock.

 The system clock counts in centi-seconds since boot and is 5 bytes long.

 On Entry:
       .oswordX/Y is the address for the five byte result
.osword1EntryPoint = $e8d5
    LDX .timeClockSwitch                                X=current system clock store pointer
.readFiveBytesIntoBlock = $e8d8
    LDY #4                                              Y = loop counter
-
    LDA .timeClockA - 5,X                               read byte
    STA (.oswordX),Y                                    store it in parameter block
    INX                                                 X = X + 1
    DEY                                                 Y = Y - 1
    BPL -                                               if (Y >= 0) then branch (loop back
                                                        and do it again)
.exit22 = $e8e3
    RTS                                                 

§40. OSWORD 4 - Write interval timer.

 On Entry:
       .oswordX/Y is the address of the five byte value to set
.osword4EntryPoint = $e8e4
    LDA #.countdownIntervalTimer - (.timeClockA - 5)    A=15 is the offset from clockA to
                                                        countdown timer
    BNE .writeFiveBytesFromBlock                        ALWAYS branch

§41. OSWORD 2 - Write system clock.

 On Entry:
       .oswordX/Y is the address of the five byte value to set
.osword2EntryPoint = $e8e8
    LDA .timeClockSwitch                                get current clock store pointer
    EOR #15                                             and invert to get inactive clock
                                                        (toggle between 5 and 10)
    CLC                                                 clear carry (indicates write system
                                                        clock)
    fall through...

§42. Write system clock or interval timer.

 On Entry:
       carry clear indicates write system clock
       carry set indicates write interval timer
.writeFiveBytesFromBlock = $e8ee
    PHA                                                 store A
    TAX                                                 X=A
    LDY #4                                              Y=loop counter
.writeClockLoop = $e8f2
    LDA (.oswordX),Y                                    transfer 5 bytes from out parameter
                                                        block
    STA .timeClockA - 5,X                               to the clock or timer
    INX                                                 
    DEY                                                 
    BPL .writeClockLoop                                 if (Y >= 0) then branch (loop)
    PLA                                                 restore A
    BCS .exit22                                         if (carry set, i.e. write to
                                                        interval timer requested) then
                                                        branch (exit)
    STA .timeClockSwitch                                write back current clock (5 or 10)
                                                        into switch variable
    RTS                                                 

§43. OSWORD 0 - Read line from current input into memory.

 On Entry:
       .oswordX/.oswordY - address of parameter block:
           byte 0: address of input buffer for result (low)
           byte 1: address of input buffer for result (high)
           byte 2: maximum line length
           byte 3: minimum acceptable ASCII code
           byte 4: maximum acceptable ASCII code
.osword0EntryPoint = $e902
    LDY #4                                              Y=4
-
    LDA (.oswordX),Y                                    copy bytes 2,3,4 to absolute
                                                        addresses .osword0MaxLineLength,+1,+2
    STA .osword0MaxLineLength-2,Y                       
    DEY                                                 decrement Y
    CPY #2                                              until Y=1
    BCS -                                               

    LDA (.oswordX),Y                                    get address of input buffer (high
                                                        byte)
    STA .osword0BufferAddressHigh                       store it in temporary buffer
    DEY                                                 decrement Y
    STY .pagedModeCounter                               Y=0 store in print line counter for
                                                        paged mode
    LDA (.oswordX),Y                                    get address of input buffer (low
                                                        byte)
    STA .osword0BufferAddressLow                        and store in temporary buffer
    CLI                                                 allow interrupts
    BCC .readInputCharacter                             ALWAYS branch

§44. readLineInputBufferFull.

.readLineInputBufferFull = $e91d
    LDA #.charBELL                                      A=7
.retryReadWithoutIncrementingPosition = $e91f
    DEY                                                 decrement Y
.retryReadIncrementingPosition = $e920
    INY                                                 increment Y
.outputCharacterAndReadAgain = $e921
    JSR .OSWRCH                                         and call OSWRCH
    fall through...

§45. readInputCharacter.

.readInputCharacter = $e924
    JSR .OSRDCH                                         read character from input stream
    BCS .exitWithNegativeAndCarryFlagsSet               if (error occurred) then branch
                                                        (exit)
    TAX                                                 X=A
    LDA .characterDestinationsAvailableFlags            get character destination status
    ROR                                                 rotate the status twice to make
    ROR                                                 carry reflect the 'disable VDU
                                                        driver' bit
    TXA                                                 restore character read
    BCS +                                               if (vdu disabled) then branch (skip
                                                        forward)
    LDX .twosComplimentOfNumberOfBytesInVDUQueue        get number of items in VDU queue
    BNE .outputCharacterAndReadAgain                    if (not zero) then branch (output
                                                        character and read again)
+
    CMP #.charDELETE                                    
    BNE +                                               if (character is not DELETE) then
                                                        branch forward
    CPY #0                                              
    BEQ .readInputCharacter                             if (Y is zero, i.e. first character)
                                                        then branch (read next character)
    DEY                                                 decrement Y (position in buffer
                                                        decrements)
    BCS .outputCharacterAndReadAgain                    ALWAYS branch back to otput the
                                                        DELETE and read again
+
    CMP #.charDisableVDUOrDeleteLine                    is it delete line (CTRL-U)
    BNE +                                               if (not CTRL-U) then branch forward
    TYA                                                 Y=A
    BEQ .readInputCharacter                             if (we are still reading first
                                                        character) then branch back the read
                                                        character
    LDA #.charDELETE                                    
-
    JSR .OSWRCH                                         }
    DEY                                                 } output DELETE until Y=0
    BNE -                                               }
    BEQ .readInputCharacter                             ALWAYS branch (read character again)
+
    STA (.osword0BufferAddressLow),Y                    store character in designated buffer
    CMP #.charRETURN                                    
    BEQ .outputNewlineAndExit                           if (carriage return entered) then
                                                        branch
    CPY .osword0MaxLineLength                           check the line length
    BCS .readLineInputBufferFull                        if (buffer full) then branch (loop
                                                        to ring bell)
    CMP .osword0MinASCIICharacter                       check minimum character
    BCC .retryReadWithoutIncrementingPosition           if (less than minimum character)
                                                        then branch (read at existing
                                                        position)
    CMP .osword0MaxASCIICharacter                       check maximum character
    BEQ .retryReadIncrementingPosition                  if (equal to maximum character) then
                                                        accept the character and move on
    BCC .retryReadIncrementingPosition                  if (less than maximum character)
                                                        then accept the character and move on
    BCS .retryReadWithoutIncrementingPosition           ALWAYS branch (reject character and
                                                        continue)

.outputNewlineAndExit = $e96c
    JSR .OSNEWL                                         output CR/LF
    JSR .netvJumper                                     call Econet vector
.exitWithNegativeAndCarryFlagsSet = $e972
    LDA .escapeFlag                                     A=ESCAPE FLAG
    ROL                                                 put bit 7 into carry
    RTS                                                 

§46. OSBYTE 5 - Select printer type.

 On Entry:
       X=0       ignore printer output
       X=1       parallel output
       X=2       RS-423 output (will act as sink if RS-423 is enabled using OSBYTE with A=3)
       X=3       user printer
       X=4       Econet printer
       X=5-255   user printer
.osbyte5EntryPoint = $e976
    CLI                                                 allow interrupts briefly
    SEI                                                 disable interrupts
    BIT .escapeFlag                                     check if ESCAPE is pending
    BMI .exit23                                         if (ESCAPE is pending) then branch
                                                        (exit)
    BIT .printerBufferEmptyFlag                         check bit 7 for printer buffer empty
    BPL .osbyte5EntryPoint                              if (bit 7=0, not empty) then branch
                                                        back and try again (wait until
                                                        buffer is empty)
    JSR .selectPrinterType                              check for user defined routine
    LDY #0                                              Y=0
    STY .osbyteY                                        F1=0
    fall through...

§47. OSBYTE 1 - Read/Write user flag; OSBYTE 6 - Set printer ignore character.

 On Entry:
       OSBYTE 1:
           To read the user flag:  Y=$00
           To write the user flag: X=value to write, Y=$FF
       OSBYTE 6:
           X = value to write
 On Exit:
       X contains the previous value
.osbyte1EntryPoint = $e988
.osbyte6EntryPoint = $e988
    ORA #$0280 - .mosVariablesMinus166                  A set to read/write locations:

    At this point, A depends on the entry point. A is set up to affect a particular
    system variable:
    
    Entry via OSBYTE 1: $0281 (user flag)                - .mosVariablesMinus166
    Entry via OSBYTE 5: $0285 (printer destination)      - .mosVariablesMinus166
    Entry via OSBYTE 6: $0286 (printer ignore character) - .mosVariablesMinus166
    
    BNE .readWriteSystemVariable                        ALWAYS branch

§48. OSBYTE 12 - Set keyboard autorepeat rate.

.osbyte12EntryPoint = $e98c
    BNE .osbyte11EntryPoint                             if (.osbyteX != 0; i.e. not
                                                        resetting to defaults) then branch
    LDX #50                                             reset default delay and rate
    STX .keyboardAutoRepeatDelay                        set keyboard autorepeat delay
    LDX #8                                              set X=8 for default autorepeat rate
    fall through...

§49. OSBYTE 11 - Set keyboard autorepeat delay.

.osbyte11EntryPoint = $e995
    ADC #$CF                                            A=A+208 (carry set)
                                                        So...
                                                        A=219 if entered from OSBYTE 11
                                                        A=220 if entered from OSBYTE 12
    fall through...

§50. OSBYTE 3 - Select output stream; OSBYTE 4 - Enable/disable cursor editing.

.osbyte3EntryPoint = $e997
.osbyte4EntryPoint = $e997
    CLC                                                 clear carry
    ADC #$E9                                            A = A + 233
                                                        So...
                                                        A=236 if entered from OSBYTE 3
                                                        A=237 if entered from OSBYTE 4
                                                        A=196 if entered from OSBYTE 11
                                                        A=197 if entered from OSBYTE 12
.readWriteSystemVariable = $e99a
    STX .osbyteX                                        store X
    fall through...

§51. OSBYTE 166 to 255 - read/write system variable.

 Set newValue = (oldValue AND .osbyteY) EOR .osbyteX

 On Entry:
       A = offset from .mosVariablesMinus166 of variable to read/write
       .osbyteX = X parameter from entry to OSBYTE
       .osbyteY = Y parameter from entry to OSBYTE
 On Exit:
       X = oldValue
       Y = value of next system variable
.osbyte166to255EntryPoint = $e99c
    TAY                                                 Y=A
    LDA .mosVariablesMinus166,Y                         read current value
    TAX                                                 preserve this in X
    AND .osbyteY                                        }
    EOR .osbyteX                                        } new value = (old value AND Y) EOR X
    STA .mosVariablesMinus166,Y                         store it
    LDA .mosVariablesMinus166+1,Y                       get value of next byte into A
    TAY                                                 Y=A
.exit23 = $e9ac
    RTS                                                 

§52. Table of buffer types and serial baud rates.

 This table has dual use. It is used to set the serial baud rate (using bits 0-5), and
 also to identify a buffer as being a sound buffer (bit 7):

   - bit 7 is set if buffer X is a sound buffer
   - bit 6 is not used (always set)
   - bits 3,4,5 indicate the serial receive baud rate
   - bits 0,1,2 indicate the serial transmit baud rate

       111 =    75 baud
       011 =   150 baud
       101 =   300 baud
       001 =  1200 baud
       110 =  2400 baud
       010 =  4800 baud
       100 =  9600 baud
       000 = 19200 baud
.bufferTypeAndSerialBaudRatesTable = $e9ad
    !byte %01100100                                     Keyboard           9600 baud
                                                        (default)
    !byte %01111111                                     RS-423 Input         75 baud
    !byte %01011011                                     RS-423 output       150 baud
    !byte %01101101                                     serial printer      300 baud
    !byte %11001001                                     sound0             1200 baud
    !byte %11110110                                     sound1             2400 baud
    !byte %11010010                                     sound2             4800 baud
    !byte %11100100                                     sound3             9600 baud
    !byte %01000000                                     speech            19200 baud

§53. OSBYTE 19 - Wait for vertical sync.

.osbyte19EntryPoint = $e9b6
    LDA .verticalSyncCounter                            Read vertical sync counter
-
    CLI                                                 allow interrupts briefly
    SEI                                                 disable interrupts again
    CMP .verticalSyncCounter                            has the vertical sync counter
                                                        changed yet?
    BEQ -                                               if (the values are the same) then
                                                        branch (loop back)
    fall through...

§54. OSBYTE 160 - Read VDU Variable.

.osbyte160EntryPoint = $e9c0
    LDY .vduVariablesStart + 1,X                        get VDU variable (often the high
                                                        byte)
    LDA .vduVariablesStart,X                            get VDU variable (often the low byte)
    TAX                                                 
    RTS                                                 

§55. OSBYTE 18 - Reset soft keys.

.osbyte18EntryPoint = $e9c8
    LDA #$10                                            set consistency flag
    STA .softKeyConsistencyFlag                         
    LDX #0                                              X=0
-
    STA .softKeyPage,X                                  and wipe clean
    INX                                                 soft key buffer
    BNE -                                               until X=0 again
    STX .softKeyConsistencyFlag                         zero consistency flag
    RTS                                                 

§56. OSBYTE 118 - Set keyboard LEDs based on keyboard status.

 On Exit:
       carry is set if CTRL pressed
       N is set if SHIFT is pressed
.osbyte118EntryPoint = $e9d9
    PHP                                                 remember flags
    SEI                                                 disable interrupts
    LDA #$40                                            switch on CAPS and SHIFT LOCK lights
    JSR .turnOnKeyboardLightsAndTestEscape              via subroutine
    BMI .popRollAndExit                                 if (negative, i.e. ESCAPE condition
                                                        exists) then branch
    CLC                                                 clear V and C
    CLV                                                 calling main keyboard routine to
    JSR .keyJumper                                      switch on lights as required
.popRollAndExit = $e9e7
    PLP                                                 recall flags
    ROL                                                 and rotate carry into bit 0
    RTS                                                 

§57. turnOnKeyboardLightsAndTestEscape.

.turnOnKeyboardLightsAndTestEscape = $e9ea
    BCC .skipKeyboardLights                             if (carry clear) then branch
    LDY #7                                              switch on SHIFT LOCK light
    STY .systemVIARegisterB                             
    DEY                                                 Y=6
    STY .systemVIARegisterB                             switch on CAPS LOCK light

.skipKeyboardLights = $e9f5
    BIT .escapeFlag                                     check bit 7 of escape flag indicating
                                                        that ESCAPE condition exists
    RTS                                                 

§58. writeAToSystemVIARegisterB.

.writeAToSystemVIARegisterB = $e9f8
    PHP                                                 push flags
    SEI                                                 disable interupts
    STA .systemVIARegisterB                             write register B from Accumulator
    PLP                                                 get back flags
    RTS                                                 

§59. OSBYTE 154 - Write to Video ULA's Video Control Register.

 bit
   0 - Flash colour select (which of the flashing colours is displayed)
   1 - Teletext select
   2 - } These two bits determine the number of characters per row
   3 - } from this table:
        00 = 10 characters per row
        01 = 20 characters per row
        10 = 40 characters per row
        11 = 80 characters per row
   4 - CRTC clock rate select:
         0 = low frequency clock for MODEs 4-7
         1 = high frequency clock for MODEs 0-3
   5 - }
   6 - } These three bits determine the width of cursor in bytes:
   7 - }

       000 = 0 cursor off
       010 = 1 (for MODE 7)
       100 = 1 (for MODEs 0,3,4,6)
       110 = 2 (for MODEs 1,5)
       111 = 4 (for MODE 2)
.osbyte154EntryPoint = $e9ff
    TXA                                                 
.setVideoULA = $ea00
    PHP                                                 save flags
    SEI                                                 disable interrupts
    STA .videoULAVideoControlRegisterCopy               save RAM copy of new parameter
    STA .videoULAControlRegister                        write to control register
    LDA .videoULASecondFlashingColourInterval           get second flashing colour interval
    STA .videoULAFlashingColourIntervalCount            set flash counter to this value
    PLP                                                 get back status
    RTS                                                 

§60. OSBYTE 155 - Set palette value.

 On Entry:
       X contains value to write
.osbyte155EntryPoint = $ea10
    TXA                                                 A = X
.osbyte155Internal = $ea11
    EOR #7                                              convert to palette format required
                                                        for video ULA register
    PHP                                                 remember flags (including interrupt
                                                        disable flag)
    SEI                                                 prevent interrupts
    STA .videoULAPaletteValue                           store as current palette setting
    STA .videoULAPaletteRegister                        store actual colour in register
    PLP                                                 restore flags (including interrupt
                                                        disable flag)
    RTS                                                 

§61. gsinitForFilenameParsing.

.gsinitForFilenameParsing = $ea1d
    CLC                                                 clear carry
    fall through...

§62. GSINIT - Initialise parsing of a string.

 Initialises the parsing of a given string (the 'input' string). Usually the string is part
 of an OSCLI command line where a string is expected (e.g. the filename in a *LOAD or the
 definition of a function key in *KEY).

 Leading spaces are ignored. The input string can be surrounded by double quotation marks.
 The string is terminated by a carriage return, or the second double quotation mark, or
 (optionally) a space character (useful for filename parsing).

 Once initialised, the parsing occurs using multiple calls to GSREAD. Each call produces one
 character result based on the input string.

 Control codes are disallowed in the input string. However the bar character '|' can be
 used to introduce control codes into the parsed output of GSREAD, and the '|!' combination
 is used to introduce top bit set characters.

 '|' followed by a character (see exceptions below) gives the ASCII value minus 64.
       e.g. |G = control code 7  (bell)
            |M = control code 13 (carriage return)
 '|!' adds 128 to the following character.
 '||' is parsed as a single bar character.
 '|"' is parsed as double quotes.
 '|?' is parsed as the DELETE character (character 127).

 These can be combined, e.g. '|!|A' results in character 129 being parsed.

 On Entry:
       Address for string stored at .stringInputBufferAddressLow/High
       Y = offset into string
       C = 0: string is terminated by a space (used for filename parsing)
       C = 1: otherwise (used e.g. for defining a soft key with *KEY)

 On Exit:
       .stringInputOptions bit 7 = double-quote character found at start
                           bit 6 = don't stop on space character
       Y = offset of the first non-blank character
       A = first non-blank character
       Z is set if string is empty
.gsinitEntryPoint = $ea1e
    ROR .stringInputOptions                             Move carry into top bit of options
    JSR .skipSpacesAndCheckForCRInStringInput           get character from text
    INY                                                 increment Y to point at next
                                                        character
    CMP #.charDOUBLEQUOTE                               check for double quotes (") character
    BEQ +                                               if (double quotes) then branch
    DEY                                                 decrement Y
    CLC                                                 clear carry
+
    ROR .stringInputOptions                             bit 7 = double quote character found
                                                        bit 6 = space is not a terminator
    CMP #.charRETURN                                    set Z flag if CR found
    RTS                                                 

§63. GSREAD - Read one character by parsing the input string.

 Parses a character from the input string previously set up by GSINIT.
 See .gsinitEntryPoint

 On Entry:
       Address for string stored at .stringInputBufferAddressLow/High
       Y = offset into string
 On Exit:
       A = character read
       X is preserved
       Y = index of next character to be read
       Carry is set if end of string reached
       Overflow (V flag) is set if the character read was interpreted as a control code
.gsreadEntryPoint = $ea2f
    LDA #0                                              A=0
.gsreadInternal = $ea31
    STA .stringInputPlingFlag                           store A ($80 if previous character
                                                        is '!', zero otherwise)
    LDA (.stringInputBufferAddressLow),Y                read first character
    CMP #.charRETURN                                    check for CR
    BNE .notRETURN                                      if (not CR) then branch

    deal with RETURN character
    BIT .stringInputOptions                             check options
    BMI .brkBadString                                   if (second quotation mark needed)
                                                        then branch ('Bad string' message)
    BPL .finishedString                                 ALWAYS branch (string ok, finished)

.notRETURN = $ea3f
    CMP #.charSPACE                                     check for SPACE
    BCC .brkBadString                                   if (less then space, i.e. a control
                                                        character) then branch ('Bad string'
                                                        message)
    BNE .notSPACE                                       if (it's not a space) then branch

    Deal with ' ' SPACE character
    BIT .stringInputOptions                             check bit 6 and 7 of
                                                        .stringInputOptions
    BMI .finishUpReadClearV                             if (double-quote character found)
                                                        then branch
    BVC .finishedString                                 if (space character is a terminator)
                                                        then branch (finished string)

.notSPACE = $ea4b
    CMP #.charDOUBLEQUOTE                               check for DOUBLE QUOTE
    BNE .notDOUBLEQUOTE                                 if (not double quote) then branch

    Deal with '"' DOUBLE QUOTE character
    BIT .stringInputOptions                             check options
    BPL .finishUpReadClearV                             if (string didn't start with a
                                                        double quote) then branch

    We have found a string surrounded by double quotes.
    Check the next character isn't also a double quote, as that's an error.
    INY                                                 point at next character
    LDA (.stringInputBufferAddressLow),Y                get it
    CMP #.charDOUBLEQUOTE                               check for double quotes (") character
    BEQ .finishUpReadClearV                             if (third double quote character
                                                        found) then branch

.finishedString = $ea5a
    JSR .skipSpacesAndCheckForCRInStringInput           skip past any final SPACEs
    SEC                                                 carry set
    RTS                                                 

.notDOUBLEQUOTE = $ea5f
    CMP #.charBAR                                       check for bar '|' character
    BNE .finishUpReadClearV                             if (not bar) then branch

    deal with '|' vertical BAR character
    INY                                                 increase Y to point to next character
    LDA (.stringInputBufferAddressLow),Y                get next character
    CMP #.charBAR                                       check for '|' again
    BEQ .finishUpReadClearV                             if (bar found) then branch

    CMP #.charDOUBLEQUOTE                               check for double quotes (") character
    BEQ .finishUpReadClearV                             if (double quotes) then branch

    CMP #.charEXCLAMATIONMARK                           check for '!' character
    BNE +                                               if (not '!') then branch

    deal with '|!' characters
    INY                                                 increment Y to next character
    LDA #$80                                            set bit 7
    BNE .gsreadInternal                                 loop back (sets bit 7 in next
                                                        character)

    deal with remaining possible characters after the '|' BAR character
+
    CMP #.charSPACE                                     check for ' '
    BCC .brkBadString                                   if (less than space, i.e. a control
                                                        code) then branch ('Bad string'
                                                        message)

    CMP #.charQUESTIONMARK                              check for '|?'
    BEQ +                                               if ('|?') then branch (set delete
                                                        character)

    Remaining '|' followed by a character is treated as a control code
    JSR .implementCTRLCodes                             modify code as if CTRL pressed
    BIT .allBitsSet                                     Set V flag
    BVS .finishUpReadX                                  ALWAYS branch (finish up with V set)

+
    LDA #.charDELETE                                    delete character

.finishUpReadClearV = $ea89
    CLV                                                 clear V
.finishUpReadX = $ea8a
    INY                                                 increment Y
    ORA .stringInputPlingFlag                           
    CLC                                                 clear carry
    RTS                                                 

§64. brkBadString.

.brkBadString = $ea8f
    BRK                                                 
    !byte $FD                                           error number
    !text "Bad string",0                                message (with zero terminator)

§65. Apply SHIFT to character in A.

 Modify character in A as if the SHIFT key is being pressed
.implementSHIFT = $ea9c
    CMP #.charZERO                                      
    BEQ .exit24                                         if ('0' character found) then branch
                                                        (return) (SHIFT '0' = '0')
    CMP #.charAT                                        
    BEQ .exit24                                         if ('@' character found) then branch
                                                        (return) (SHIFT '@' = '@')
    BCC .flipBitFourIfNotCtrlCharacter                  if (A < '@') then branch
                                                        the branch converts characters
                                                              !"#$%&`()*+,-./ 123456789:;<=>?
                                                        into  123456789:;<=>? !"#$%&`()*+,-./
    CMP #.charDELETE                                    
    BEQ .exit24                                         if (DELETE character found) then
                                                        branch (return) (SHIFT DELETE =
                                                        DELETE)
    BCS .flipBitFourOnly                                if (greater than $7F) then branch
                                                        (toggle bit 4 and exit)

    At this point A>64 and A<127
    The following code converts the following characters:
           ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_£ abcdefghijklmnopqrstuvwxyz{|}~
    into   abcdefghijklmnopqrstuvwxyz{|}~£_ ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^

.flipBitsForShift = $eaac
    EOR #%00110000                                      flip bits 4 and 5
    CMP #(.charUNDERSCORE XOR %00110000)                }
    BEQ +                                               } if (it was originally underscore
                                                        } '_') then branch

    CMP #(.charPOUND XOR %00110000)                     }
    BNE .flipBitFourIfNotCtrlCharacter                  } if (it was NOT originally pound
                                                        } '£') then branch
+
    EOR #$1F                                            EOR for converting pound symbol
                                                        (which will become
                                                        underscore when the EOR at
                                                        .flipBitFourOnly is done)
.flipBitFourIfNotCtrlCharacter = $eab8
    CMP #.charSPACE + 1                                 }
    BCC .exit24                                         } if (control code) then branch
                                                        } (return)
.flipBitFourOnly = $eabc
    EOR #%00010000                                      flip bit 4
.exit24 = $eabe
    RTS                                                 

§66. Modify character in A as if the CTRL key is being pressed.

 This is used both when the CTRL key is being pressed, and for parsing strings using
 GSINIT / GSREAD. See .gsinitEntryPoint.

     Input     Output      Notes
     ASCII      CTRL
     Code       Code
     ----------------------------------------------------------------------
     32-63     32-63       VDU 32-63 (unchanged)
     '@'         0         VDU 0
     'A'         1         VDU 1
           ...
     'Z'        26         VDU 26
     '['        27         VDU 27
     '\'        28         VDU 28
     ']'        29         VDU 29
     '^'        30         VDU 30
     '_'        31         VDU 31
     '£'        31         VDU 31 this is treated as a special case.
                                  it would otherwise result in VDU 0 [why the special case?]
     'a'         1         VDU 1
           ...
     'z'        26         VDU 26
     '{'        27         VDU 27
     '|'        28         VDU 28
     '}'        29         VDU 29
     '~'        30         VDU 30
     127       127         DELETE
     128-255   flip bit 5  flip bit 5 [it's not clear why this is useful?]
.implementCTRLCodes = $eabf
    CMP #.charDELETE                                    check for DELETE
    BEQ ++                                              if (DELETE found) then branch (exit)
    BCS .flipBitsForShift                               if (greater than $7F) then branch
                                                        (process top bit characters)
    CMP #.charPOUND                                     }
    BNE +                                               } if (A is not '£') then branch
    LDA #.charUNDERSCORE                                A='_'
+
    CMP #.charAT                                        }
    BCC ++                                              } if (A < '@') then branch (return
                                                        } unchanged)
    AND #%00011111                                      at this point     A is between 64 to
                                                        126 inclusive
                                                        zero bits 5 to 7  A is between  0 to
                                                         31 inclusive
++
    RTS                                                 

§67. runBootString.

.runBootString = $ead2
    !text "/!BOOT",13

§68. OSBYTE 247 - Intercept BREAK.

 On Exit:
           Z clear if no user BREAK intercept code
 otherwise Z is set or cleared by user BREAK code
.osbyte247EntryPoint = $ead9
    LDA .breakInterceptJMPInstruction                  get first byte of user BREAK code
                                                       (looking for 'JMP address')
    EOR #$4C                                           produces 0 if JMP absolute
                                                       instruction is present
    BNE +                                              if (not zero) then branch (return)
    JMP .breakInterceptJMPInstruction                  jump to user BREAK code

§69. OSBYTE 144 - *TV.

 On Entry:
       X=vertical shift of display
       Y=interlace disable flag
 On Exit:
       X=old vertical shift of display
       Y=old interlace disable flag
.osbyte144EntryPoint = $eae3
    LDA .vduVerticalAdjust                              VDU vertical adjustment
    STX .vduVerticalAdjust                              store new value
    TAX                                                 put old value in X
    TYA                                                 put interlace flag in A
    AND #1                                              maximum value =1
    LDY .vduInterlaceValue                              get old value into Y
    STA .vduInterlaceValue                              put new value into A
+
    RTS                                                 

§70. OSBYTE 147 - Write a byte to FRED.

 X is offset within page
 Y is byte to write;
.osbyte147EntryPoint = $eaf4
    TYA                                                 
    STA .fredPage,X                                     
    RTS                                                 

§71. OSBYTE 149 - Write a byte to JIM.

 X is offset within page
 Y is byte to write
.osbyte149EntryPoint = $eaf9
    TYA                                                 
    STA .jimPage,X                                      
    RTS                                                 

§72. OSBYTE 151 - Write a byte to SHEILA.

 X is offset within page
 Y is byte to write
.osbyte151EntryPoint = $eafe
    TYA                                                 
    STA .sheilaPage,X                                   
    RTS