SPECTRUM 128 ROM 0 DISASSEMBLY
; The Spectrum ROMs are copyright Amstrad, who have kindly given permission
; to reverse engineer and publish Spectrum ROM disassemblies.
; =====
; NOTES
; =====
; ------------
; Release Date
; ------------
; 9th February 2010
; ------------------------
; Disassembly Contributors
; ------------------------
; Matthew Wilson (www.matthew-wilson.net/spectrum/rom/)
; Andrew Owen (cheveron-AT-gmail.com)
; Geoff Wearmouth (gwearmouth-AT-hotmail.com)
; Rui Tunes
; Paul Farrow (www.fruitcake.plus.com)
; -------
; Markers
; -------
; The following markers appear throughout the disassembly:
;
; [...] = Indicates a comment about the code.
; ???? = Information to be determined.
;
; For bugs, the following marker format is used:
;
; [*BUG* - xxxx. Credit: yyyy] = Indicates a confirmed bug, with a description 'xxxx' of it and the discoverer 'yyyy'.
; [*BUG*? - xxxx. Credit: yyyy] = Indicates a suspected bug, with a description 'xxxx' of it and the discoverer 'yyyy'.
;
; Since many of the Spectrum 128 ROM routines were re-used in the Spectrum +2 and +3, where a bug was originally identified
; in the Spectrum +2 or +3 the discoverer is acknowledged along with who located the corresponding bug in the Spectrum 128.
;
; For every bug identified, an example fix is provided and the author acknowledged. Some of these fixes can be made directly within the routines
; affected since they do not increase the length those routines. Others require the insertion of extra instructions and hence these cannot be
; completely fitted within the routines affected. Instead a jump must be made to a patch routine located within a spare area of the ROM.
; Fortunately there is 0.5K of unused routines located at $2336-$2536 (ROM 0) which are remnants of the original Spanish 128, and another unused routine
; located at $3FC3-$3FCE (ROM 0). This is sufficient space to implement all of the bug fixes suggested.
; =================
; ASSEMBLER DEFINES
; =================
;TASM directives:
#DEFINE DEFB .BYTE
#DEFINE DEFW .WORD
#DEFINE DEFM .TEXT
#DEFINE DEFS .BLOCK
#DEFINE END .END
#DEFINE EQU .EQU
#DEFINE ORG .ORG
; ==============================
; REFERENCE INFORMATION - PART 1
; ==============================
; ==========================
; 128 BASIC Mode Limitations
; ==========================
; There are a number of limitations when using 128 BASIC mode, some of which are not present when using the equivalent 48 BASIC mode operations.
; These are more design decisions than bugs.
;
; - The RAM disk VERIFY command does not verify but simply performs a LOAD.
; - The renumber facility will not renumber line numbers that are defined as an expression, e.g. GO TO VAL "10".
; - The printer output routine cannot handle binary data and hence EPSON printer ESC codes cannot be sent.
; - The Editor has the following limitations:
; - Variables cannot have the same name as a keyword. This only applies when entering a program and not when one is loaded in.
; - Line number 0 is not supported and will not list properly. It is not possible to directly insert such a line, not even in 48 BASIC mode,
; and so line number 0 is not officially supported.
; - There is a practical limitation on the size of lines that can be entered. It is limited to 20 indented rows, which is the size of the editing buffers.
; Typed lines greater than 20 rows get inserted into the BASIC program, but only the first 20 rows are shown on screen. Editing such a line causes
; it to be truncated to 20 rows. There is no warning when the 20 row limit is exceeded.
; - It is not possible to directly enter embedded control codes, or to correctly edit loaded in programs that contain them. Loaded programs that
; contain them will run correctly so long as the lines are not edited.
; - It is not possible to embed the string of characters ">=", "<=" or "<>" into a string or REM statement without them being tokenized
; (this is perhaps more an oversight than a design decision).
; - In 48 BASIC mode if the line '10 REM abc: PRINT xyz' is typed then the word PRINT is stored as a new keyword since the colon (arguably incorrectly)
; reverts to 'K' mode. In 128 BASIC mode, typing the same line stores each letter as a separate character.
; ==================
; Timing Information
; ==================
; Clock Speed = 3.54690 MHz (48K Spectrum clock speed was 3.50000 MHz)
; Scan line = 228 T-states (48K Spectrum was 224 T-states).
; TV scan lines = 311 total, 63 above picture (48K Spectrum had 312 total, 64 above picture).
; ===========
; I/O Details
; ===========
; -------------
; Memory Paging
; -------------
; Memory paging is controlled by I/O port:
; $7FFD (Out) - Bits 0-2: RAM bank (0-7) to page into memory map at $C000.
; Bit 3 : 0=SCREEN 0 (normal display file in bank 5), 1=SCREEN 1 (shadow display file in bank 7).
; Bit 4 : 0=ROM 0 (128K Editor), 1=ROM 1 (48K BASIC).
; Bit 5 : 1=Disable further output to this port until a hard reset occurs.
; Bit 6-7 : Not used (always write 0).
;
; The Editor ROM (ROM 0) always places a copy of the last value written to port $7FFD
; into new system variable BANK_M ($5B5C).
;
; ----------
; Memory Map
; ----------
; ROM 0 or 1 resides at $0000-$3FFF.
; RAM bank 5 resides at $4000-$7FFF always.
; RAM bank 2 resides at $8000-$BFFF always.
; Any RAM bank may reside at $C000-$FFFF.
;
; -------------------
; Shadow Display File
; -------------------
; The shadow screen may be active even when not paged into the memory map.
;
; ----------------
; Contended Memory
; ----------------
; Physical RAM banks 1, 3, 5 and 7 are contended with the ULA.
;
; -----------------
; Logical RAM Banks
; -----------------
; Throughout ROM 0, memory banks are accessed using a logical numbering scheme, which
; maps to physical RAM banks as follows:
;
; Logical Bank Physical Bank
; ------------ -------------
; $00 $01
; $01 $03
; $02 $04
; $03 $06
; $04 $07
; $05 $00
;
; This scheme makes the RAM disk code simpler than having to deal directly with physical RAM bank numbers.
; -------------------------
; AY-3-8912 Sound Generator
; -------------------------
; The AY-3-8912 sound generator is controlled by two I/O ports:
; $FFFD (Out) - Select a register 0-14.
; $FFFD (In) - Read from the selected register.
; $BFFD (In/Out) - Write to the selected register. The status of the register can also be read back.
;
; The AY-3-8912 I/O port A is used to drive the RS232 and Keypad sockets.
;
; Register Function Range
; -------- -------- -----
; 0 Channel A fine pitch 8-bit (0-255)
; 1 Channel A course pitch 4-bit (0-15)
; 2 Channel B fine pitch 8-bit (0-255)
; 3 Channel B course pitch 4-bit (0-15)
; 4 Channel C fine pitch 8-bit (0-255)
; 5 Channel C course pitch 4-bit (0-15)
; 6 Noise pitch 5-bit (0-31)
; 7 Mixer 8-bit (see end of file for description)
; 8 Channel A volume 4-bit (0-15, see end of file for description)
; 9 Channel B volume 4-bit (0-15, see end of file for description)
; 10 Channel C volume 4-bit (0-15, see end of file for description)
; 11 Envelope fine duration 8-bit (0-255)
; 12 Envelope course duration 8-bit (0-255)
; 13 Envelope shape 4-bit (0-15)
; 14 I/O port A 8-bit (0-255)
;
; See the end of this document for description on the sound generator registers.
;
; ----------------------------------
; I/O Port A (AY-3-8912 Register 14)
; ----------------------------------
; This controls the RS232 and Keypad sockets.
; Select the port via a write to port $FFFD with 14, then read via port $FFFD and write via port $BFFD. The state of port $BFFD can also be read back.
;
; Bit 0: KEYPAD CTS (out) - 0=Spectrum ready to receive, 1=Busy
; Bit 1: KEYPAD RXD (out) - 0=Transmit high bit, 1=Transmit low bit
; Bit 2: RS232 CTS (out) - 0=Spectrum ready to receive, 1=Busy
; Bit 3: RS232 RXD (out) - 0=Transmit high bit, 1=Transmit low bit
; Bit 4: KEYPAD DTR (in) - 0=Keypad ready for data, 1=Busy
; Bit 5: KEYPAD TXD (in) - 0=Receive high bit, 1=Receive low bit
; Bit 6: RS232 DTR (in) - 0=Device ready for data, 1=Busy
; Bit 7: RS232 TXD (in) - 0=Receive high bit, 1=Receive low bit
;
; See the end of this document for the pinouts for the RS232 and KEYPAD sockets.
; ------------------
; Standard I/O Ports
; ------------------
; See the end of this document for descriptions of the standard Spectrum I/O ports.
; ==================
; Error Report Codes
; ==================
; ---------------------------
; Standard Error Report Codes
; ---------------------------
; See the end of this document for descriptions of the standard error report codes.
; ----------------------
; New Error Report Codes
; ----------------------
; a - MERGE error MERGE! would not execute for some reason - either size or file type wrong.
; b - Wrong file type A file of an inappropriate type was specified during RAM disk operation, for instance a CODE file in LOAD!"name".
; c - CODE error The size of the file would lead to an overrun of the top of memory.
; d - Too many brackets Too many brackets around a repeated phrase in one of the arguments.
; e - File already exists The file name specified has already been used.
; f - Invalid name The file name specified is empty or above 10 characters in length.
; g - File does not exist [Never used by the ROM].
; h - File does not exist The specified file could not be found.
; i - Invalid device The device name following the FORMAT command does not exist or correspond to a physical device.
; j - Invalid baud rate The baud rate for the RS232 was set to 0.
; k - Invalid note name PLAY came across a note or command it didn't recognise, or a command which was in lower case.
; l - Number too big A parameter for a command is an order of magnitude too big.
; m - Note out of range A series of sharps or flats has taken a note beyond the range of the sound chip.
; n - Out of range A parameter for a command is too big or too small. If the error is very large, error L results.
; o - Too many tied notes An attempt was made to tie too many notes together.
; p - (c) 1986 Sinclair Research Ltd This error is given when too many PLAY channel strings are specified. Up to 8 PLAY channel strings are supported
; by MIDI devices such as synthesisers, drum machines or sequencers. Note that a PLAY command with more than 8 strings
; cannot be entered directly from the Editor. The Spanish 128 produces "p Bad parameter" for this error. It could be
; that the intention was to save memory by using the existing error message of "Q Parameter error" but the change of report
; code byte was overlooked.
; ================
; System Variables
; ================
; --------------------
; New System Variables
; --------------------
; These are held in the old ZX Printer buffer at $5B00-$5BFF.
; Note that some of these names conflict with the system variables used by the ZX Interface 1.
SWAP EQU $5B00 ; 20 Swap paging subroutine.
YOUNGER EQU $5B14 ; 9 Return paging subroutine.
ONERR EQU $5B1D ; 18 Error handler paging subroutine.
PIN EQU $5B2F ; 5 RS232 input pre-routine.
POUT EQU $5B34 ; 22 RS232 token output pre-routine. This can be patched to bypass the control code filter.
POUT2 EQU $5B4A ; 14 RS232 character output pre-routine.
TARGET EQU $5B58 ; 2 Address of subroutine to call in ROM 1.
RETADDR EQU $5B5A ; 2 Return address in ROM 0.
BANK_M EQU $5B5C ; 2 Copy of last byte output to I/O port $7FFD.
RAMRST EQU $5B5D ; 1 Stores instruction RST $08 and used to produce a standard ROM error. Changing this instruction allows 128 BASIC to be extended (see end of this document for details).
RAMERR EQU $5B5E ; 1 Error number for use by RST $08 held in RAMRST.
BAUD EQU $5B5F ; 2 Baud rate timing constant for RS232 socket. Default value of 11. [Name clash with ZX Interface 1 system variable at $5CC3]
SERFL EQU $5B61 ; 2 Second character received flag:
; Bit 0 : 1=Character in buffer.
; Bits 1-7: Not used (always hold 0).
; $5B62 ; Received Character.
COL EQU $5B63 ; 1 Current column from 1 to WIDTH.
WIDTH EQU $5B64 ; 1 Paper column width. Default value of 80. [Name clash with ZX Interface 1 Edition 2 system variable at $5CB1]
TVPARS EQU $5B65 ; 1 Number of inline parameters expected by RS232 (e.g. 2 for AT).
FLAGS3 EQU $5B66 ; 1 Flags: [Name clashes with the ZX Interface 1 system variable at $5CB6]
; Bit 0: 1=BASIC/Calculator mode, 0=Editor/Menu mode.
; Bit 1: 1=Auto-run loaded BASIC program. [Set but never tested by the ROM]
; Bit 2: 1=Editing RAM disk catalogue.
; Bit 3: 1=Using RAM disk commands, 0=Using cassette commands.
; Bit 4: 1=Indicate LOAD.
; Bit 5: 1=Indicate SAVE.
; Bit 6; 1=Indicate MERGE.
; Bit 7: 1=Indicate VERIFY.
N_STR1 EQU $5B67 ; 10 Used by RAM disk to store a filename. [Name clash with ZX Interface 1 system variable at $5CDA]
; Used by the renumber routine to store the address of the BASIC line being examined.
HD_00 EQU $5B71 ; 1 Used by RAM disk to store file header information (see RAM disk Catalogue section below for details). [Name clash with ZX Interface 1 system variable at $5CE6]
; Used as column pixel counter in COPY routine.
; Used by FORMAT command to store specified baud rate.
; Used by renumber routine to store the number of digits in a pre-renumbered line number reference. [Name clash with ZX Interface 1 system variable at $5CE7]
HD_0B EQU $5B72 ; 2 Used by RAM disk to store header info - length of block.
; Used as half row counter in COPY routine.
; Used by renumber routine to generate ASCII representation of a new line number.
HD_0D EQU $5B74 ; 2 Used by RAM disk to store file header information (see RAM disk Catalogue section below for details). [Name clash with ZX Interface 1 system variable at $5CE9]
HD_0F EQU $5B76 ; 2 Used by RAM disk to store file header information (see RAM disk Catalogue section below for details). [Name clash with ZX Interface 1 system variable at $5CEB]
; Used by renumber routine to store the address of a referenced BASIC line.
HD_11 EQU $5B78 ; 2 Used by RAM disk to store file header information (see RAM disk Catalogue section below for details). [Name clash with ZX Interface 1 system variable at $5CED]
; Used by renumber routine to store existing VARS address/current address within a line.
SC_00 EQU $5B7A ; 1 Used by RAM disk to store alternate file header information (see RAM disk Catalogue section below for details).
SC_0B EQU $5B7B ; 2 Used by RAM disk to store alternate file header information (see RAM disk Catalogue section below for details).
SC_0D EQU $5B7D ; 2 Used by RAM disk to store alternate file header information (see RAM disk Catalogue section below for details).
SC_0F EQU $5B7F ; 2 Used by RAM disk to store alternate file header information (see RAM disk Catalogue section below for details).
OLDSP EQU $5B81 ; 2 Stores old stack pointer when TSTACK in use.
SFNEXT EQU $5B83 ; 2 End of RAM disk catalogue marker. Pointer to first empty catalogue entry.
SFSPACE EQU $5B85 ; 3 Number of bytes free in RAM disk (3 bytes, 17 bit, LSB first).
ROW01 EQU $5B88 ; 1 Stores keypad data for row 3, and flags:
; Bit 0 : 1=Key '+' pressed.
; Bit 1 : 1=Key '6' pressed.
; Bit 2 : 1=Key '5' pressed.
; Bit 3 : 1=Key '4' pressed.
; Bits 4-5: Always 0.
; Bit 6 : 1=Indicates successful communications to the keypad.
; Bit 7 : 1=If communications to the keypad established.
ROW23 EQU $5B89 ; 1 Stores keypad key press data for rows 1 and 2:
; Bit 0: 1=Key ')' pressed.
; Bit 1: 1=Key '(' pressed.
; Bit 2: 1=Key '*' pressed.
; Bit 3: 1=Key '/' pressed.
; Bit 4: 1=Key '-' pressed.
; Bit 5: 1=Key '9' pressed.
; Bit 6: 1=Key '8' pressed.
; Bit 7: 1=Key '7' pressed.
ROW45 EQU $5B8A ; 1 Stores keypad key press data for rows 4 and 5:
; Bit 0: Always 0.
; Bit 1: 1=Key '.' pressed.
; Bit 2: Always 0.
; Bit 3: 1=Key '0' pressed.
; Bit 4: 1=Key 'ENTER' pressed.
; Bit 5: 1=Key '3' pressed.
; Bit 6: 1=Key '2' pressed.
; Bit 7: 1=Key '1' pressed.
SYNRET EQU $5B8B ; 2 Return address for ONERR routine.
LASTV EQU $5B8D ; 5 Last value printed by calculator.
RNLINE EQU $5B92 ; 2 Address of the length bytes in the line currently being renumbered.
RNFIRST EQU $5B94 ; 2 Starting line number when renumbering. Default value of 10.
RNSTEP EQU $5B96 ; 2 Step size when renumbering. Default value of 10.
STRIP1 EQU $5B98 ; 32 Used as RAM disk transfer buffer (32 bytes to $5BB7).
; Used to hold Sinclair stripe character patterns (16d bytes to $5BA7).
; ...
TSTACK EQU $5BFF ; n Temporary stack (grows downwards). The byte at $5BFF is not actually used.
; -------------------------
; Standard System Variables
; -------------------------
; These occupy addresses $5C00-$5CB5.
; See the end of this document for descriptions of the standard system variables.
; ------------------
; RAM Disk Catalogue
; ------------------
; The catalogue can occupy addresses $C000-$EBFF in physical RAM bank 7, starting at $EBFF and growing downwards.
;
; Each entry contains 20 bytes:
; Bytes $00-$09: Filename.
; Bytes $0A-$0C: Start address of file in RAM disk area.
; Bytes $0D-$0F: Length of file in RAM disk area.
; Bytes $10-$12: End address of file in RAM disk area (used as current position indicator when loading/saving).
; Byte $13 : Flags:
; Bit 0 : 1=Entry requires updating.
; Bits 1-7: Not used (always hold 0).
;
; The catalogue can store up to 562 entries, and hence the RAM disk can never hold more than 562 files no matter
; how small the files themselves are. Note that filenames are case sensitive.
;
; The shadow screen (SCREEN 1) also resides in physical RAM bank 7 and so if more than 217 catalogue
; entries are created then SCREEN 1 will become corrupted [Credit: Toni Baker, ZX Computing Monthly].
; However, since screen 1 cannot be used from BASIC, it may have been a design decision to allow the
; RAM disk to overwrite it.
;
; The actual files are stored in physical RAM banks 1, 3, 4 and 6 (logical banks 0, 1, 2, 3),
; starting from $C000 in physical RAM bank 1 and growing upwards.
;
; A file consists of a 9 byte header followed by the data for the file. The header bytes
; have the following meaning:
; Byte $00 : File type - $00=Program, $01=Numeric array, $02=Character array, $03=Code/Screen$.
; Bytes $01-$02: Length of program/code block/screen$/array ($1B00 for screen$).
; Bytes $03-$04: Start of code block/screen$ ($4000 for screen$).
; Bytes $05-$06: Offset to the variables (i.e. length of program) if a program. For an array, $05 holds the variable name.
; Bytes $07-$08: Auto-run line number for a program ($80 in high byte if no auto-run).
; --------------------------
; Editor Workspace Variables
; --------------------------
; These occupy addresses $EC00-$FFFF in physical RAM bank 7, and form a workspace used by 128 BASIC Editor.
;
; $EC00 3 Byte 0: Flags used when inserting a line into the BASIC program (first 4 bits are mutually exclusive).
; Bit 0: 1=First row of the BASIC line off top of screen.
; Bit 1: 1=On first row of the BASIC line.
; Bit 2: 1=Using lower screen and only first row of the BASIC line visible.
; Bit 3: 1=At the end of the last row of the BASIC line.
; Bit 4: Not used (always 0).
; Bit 5: Not used (always 0).
; Bit 6: Not used (always 0).
; Bit 7: 1=Column with cursor not yet found.
; Byte 1: Column number of current position within the BASIC line being inserted. Used when fetching characters.
; Byte 2: Row number of current position within the BASIC line is being inserted. Used when fetching characters.
; $EC03 3 Byte 0: Flags used upon an error when inserting a line into the BASIC program (first 4 bits are mutually exclusive).
; Bit 0: 1=First row of the BASIC line off top of screen.
; Bit 1: 1=On first row of the BASIC line.
; Bit 2: 1=Using lower screen and only first row of the BASIC line visible.
; Bit 3: 1=At the end of the last row of the BASIC line.
; Bit 4: Not used (always 0).
; Bit 5: Not used (always 0).
; Bit 6: Not used (always 0).
; Bit 7: 1=Column with cursor not yet found.
; Byte 1: Start column number where BASIC line is being entered. Always holds 0.
; Byte 2: Start row number where BASIC line is being entered.
; $EC06 2 Count of the number of editable characters in the BASIC line up to the cursor within the Screen Line Edit Buffer.
; $EC08 2 Version of E_PPC used by BASIC Editor to hold last line number enter.
; $EC0C 1 Current menu index.
; $EC0D 1 Flags used by 128 BASIC Editor:
; Bit 0: 1=Screen Line Edit Buffer (including Below-Screen Line Edit Buffer) is full.
; Bit 1: 1=Menu is displayed.
; Bit 2: 1=Using RAM disk.
; Bit 3: 1=Current line has been altered.
; Bit 4: 1=Return to calculator, 0=Return to main menu.
; Bit 5: 1=Do not process the BASIC line (used by the Calculator).
; Bit 6: 1=Editing area is the lower screen, 0=Editing area is the main screen.
; Bit 7: 1=Waiting for key press, 0=Got key press.
; $EC0E 1 Mode:
; $00 = Edit Menu mode.
; $04 = Calculator mode.
; $07 = Tape Loader mode. [Effectively not used as overwritten by $FF]
; $FF = Tape Loader mode.
; $EC0F 1 Main screen colours used by the 128 BASIC Editor - alternate ATTR_P.
; $EC10 1 Main screen colours used by the 128 BASIC Editor - alternate MASK_P.
; $EC11 1 Temporary screen colours used by the 128 BASIC Editor - alternate ATTR_T.
; $EC12 1 Temporary screen colours used by the 128 BASIC Editor - alternate MASK_T.
; $EC13 1 Temporary store for P_FLAG:
; Bit 0: 1=OVER 1, 0=OVER 0.
; Bit 1: Not used (always 0).
; Bit 2: 1=INVERSE 1, INVERSE 0.
; Bit 3: Not used (always 0).
; Bit 4: 1=Using INK 9.
; Bit 5: Not used (always 0).
; Bit 6: 1=Using PAPER 9.
; Bit 7: Not used (always 0).
; $EC14 1 Not used.
; $EC15 1 Holds the number of editing lines: 20 for the main screen, 1 for the lower screen.
; $EC16 735 Screen Line Edit Buffer. This represents the text on screen that can be edited. It holds 21 rows,
; with each row consisting of 32 characters followed by 3 data bytes. Areas of white
; space that do not contain any editable characters (e.g. the indent that starts subsequent
; rows of a BASIC line) contain the value $00.
; Data Byte 0:
; Bit 0: 1=The first row of the BASIC line.
; Bit 1: 1=Spans onto next row.
; Bit 2: Not used (always 0).
; Bit 3: 1=The last row of the BASIC line.
; Bit 4: 1=Associated line number stored.
; Bit 5: Not used (always 0).
; Bit 6: Not used (always 0).
; Bit 7: Not used (always 0).
; Data Bytes 1-2: Line number of corresponding BASIC line (stored for the first row of the BASIC line only, holds $0000).
; $EEF5 1 Flags used when listing the BASIC program:
; Bit 0 : 0=Not on the current line, 1=On the current line.
; Bit 1 : 0=Previously found the current line, 1=Not yet found the current line.
; Bit 2 : 0=Enable display file updates, 1=Disable display file updates.
; Bits 3-7: Not used (always 0).
; $EEF6 1 Store for temporarily saving the value of TVFLAG.
; $EEF7 1 Store for temporarily saving the value of COORDS.
; $EEF9 1 Store for temporarily saving the value of P_POSN.
; $EEFA 2 Store for temporarily saving the value of PRCC.
; $EEFC 2 Store for temporarily saving the value of ECHO_E.
; $EEFE 2 Store for temporarily saving the value of DF_CC.
; $EF00 2 Store for temporarily saving the value of DF_CCL.
; $EF01 1 Store for temporarily saving the value of S_POSN.
; $EF03 2 Store for temporarily saving the value of SPOSNL.
; $EF05 1 Store for temporarily saving the value of SCR_CT.
; $EF06 1 Store for temporarily saving the value of ATTR_P.
; $EF07 1 Store for temporarily saving the value of MASK_P.
; $EF08 1 Store for temporarily saving the value of ATTR_T.
; $EF09 1512 Used to store screen area (12 rows of 14 columns) where menu will be shown.
; The rows are stored one after the other, with each row consisting of the following:
; - 8 lines of 14 display file bytes.
; - 14 attribute file bytes.
; $F4F1-$F6E9 Not used. 505d bytes.
; $F6EA 2 The jump table address for the current menu.
; $F6EC 2 The text table address for the current menu.
; $F6EE 1 Cursor position info - Current row number.
; $F6EF 1 Cursor position info - Current column number.
; $F6F0 1 Cursor position info - Preferred column number. Holds the last user selected column position. The Editor will attempt to
; place the cursor on this column when the user moves up or down to a new line.
; $F6F1 1 Edit area info - Top row threshold for scrolling up.
; $F6F2 1 Edit area info - Bottom row threshold for scrolling down.
; $F6F3 1 Edit area info - Number of rows in the editing area.
; $F6F4 1 Flags used when deleting:
; Bit 0 : 1=Deleting on last row of the BASIC line, 0=Deleting on row other than the last row of the BASIC line.
; Bits 1-7: Not used (always 0).
; $F6F5 1 Number of rows held in the Below-Screen Line Edit Buffer.
; $F6F6 2 Intended to point to the next location to access within the Below-Screen Line Edit Buffer, but incorrectly initialised to $0000 by the routine at $30D6 (ROM 0) and then never used.
; $F6F8 735 Below-Screen Line Edit Buffer. Holds the remainder of a BASIC line that has overflowed off the bottom of the Screen Line Edit Buffer. It can hold 21 rows, with each row
; consisting of 32 characters followed by 3 data bytes. Areas of white space that do not contain any editable characters (e.g. the indent that starts subsequent rows of a BASIC line)
; contain the value $00.
; Data Byte 0:
; Bit 0: 1=The first row of the BASIC line.
; Bit 1: 1=Spans onto next row.
; Bit 2: Not used (always 0).
; Bit 3: 1=The last row of the BASIC line.
; Bit 4: 1=Associated line number stored.
; Bit 5: Not used (always 0).
; Bit 6: Not used (always 0).
; Bit 7: Not used (always 0).
; Data Bytes 1-2: Line number of corresponding BASIC line (stored for the first row of the BASIC line only, holds $0000).
; $F9D7 2 Line number of the BASIC line in the program area being edited (or $0000 for no line).
; $F9DB 1 Number of rows held in the Above-Screen Line Edit Buffer.
; $F9DC 2 Points to the next location to access within the Above-Screen Line Edit Buffer.
; $F9DE 700 Above-Screen Line Edit Buffer. Holds the rows of a BASIC line that has overflowed off the top of the Screen Line Edit Buffer.
; It can hold 20 rows, with each row consisting of 32 characters followed by 3 data bytes. Areas of white space that do not
; contain any editable characters (e.g. the indent that starts subsequent rows of a BASIC line) contain the value $00.
; Data Byte 0:
; Bit 0: 1=The first row of the BASIC line.
; Bit 1: 1=Spans onto next row.
; Bit 2: Not used (always 0).
; Bit 3: 1=The last row of the BASIC line.
; Bit 4: 1=Associated line number stored.
; Bit 5: Not used (always 0).
; Bit 6: Not used (always 0).
; Bit 7: Not used (always 0).
; Data Bytes 1-2: Line number of corresponding BASIC line (stored for the first row of the BASIC line only, holds $0000).
; $FC9A 2 The line number at the top of the screen, or $0000 for the first line.
; $FC9E 1 $00=Print a leading space when constructing keyword.
; $FC9F 2 Address of the next character to fetch within the BASIC line in the program area, or $0000 for no next character.
; $FCA1 2 Address of the next character to fetch from the Keyword Construction Buffer, or $0000 for no next character.
; $FCA3 11 Keyword Construction Buffer. Holds either a line number or keyword string representation.
; $FCAE-$FCFC Construct a BASIC Line routine. <<< RAM routine - See end of file for description >>>
; $FCFD-$FD2D Copy String Into Keyword Construction Buffer routine. <<< RAM routine - See end of file for description >>>
; $FD2E-$FD69 Identify Character Code of Token String routine. <<< RAM routine - See end of file for description >>>
; $FD6A 1 Flags used when shifting BASIC lines within edit buffer rows [Redundant]:
; Bit 0 : 1=Set to 1 but never reset or tested. Possibly intended to indicate the start of a new BASIC line and hence whether indentation required.
; Bit 1-7: Not used (always 0).
; $FD6B 1 The number of characters to indent subsequent rows of a BASIC line by.
; $FD6C 1 Cursor settings (indexed by IX+$00) - initialised to $00, but never used.
; $FD6D 1 Cursor settings (indexed by IX+$01) - number of rows above the editing area.
; $FD6E 1 Cursor settings (indexed by IX+$02) - initialised to $00 (when using lower screen) or $14 (when using main screen), but never subsequently used.
; $FD6F 1 Cursor settings (indexed by IX+$03) - initialised to $00, but never subsequently used.
; $FD70 1 Cursor settings (indexed by IX+$04) - initialised to $00, but never subsequently used.
; $FD71 1 Cursor settings (indexed by IX+$05) - initialised to $00, but never subsequently used.
; $FD72 1 Cursor settings (indexed by IX+$06) - attribute colour.
; $FD73 1 Cursor settings (indexed by IX+$07) - screen attribute where cursor is displayed.
; $FD74 9 The Keyword Conversion Buffer holding text to examine to see if it is a keyword.
; $FD7D 2 Address of next available location within the Keyword Conversion Buffer.
; $FD7F 2 Address of the space character between words in the Keyword Conversion Buffer.
; $FD81 1 Keyword Conversion Buffer flags, used when tokenizing a BASIC line:
; Bit 0 : 1=Buffer contains characters.
; Bit 1 : 1=Indicates within quotes.
; Bit 2 : 1=Indicates within a REM.
; Bits 3-7: Not used (always reset to 0).
; $FD82 2 Address of the position to insert the next character within the BASIC line workspace. The BASIC line
; is created at the spare space pointed to by E_LINE.
; $FD84 1 BASIC line insertion flags, used when inserting a characters into the BASIC line workspace:
; Bit 0 : 1=The last character was a token.
; Bit 1 : 1=The last character was a space.
; Bits 2-7: Not used (always 0).
; $FD85 2 Count of the number of characters in the typed BASIC line being inserted.
; $FD87 2 Count of the number of characters in the tokenized version of the BASIC line being inserted.
; $FD89 1 Holds '<' or '>' if this was the previously examined character during tokenization of a BASIC line, else $00.
; $FD8A 1 Locate Error Marker flag, holding $01 is a syntax error was detected on the BASIC line being inserted and the equivalent position within
; the typed BASIC line needs to be found with, else it holds $00 when tokenizing a BASIC line.
; $FD8B 2 Stores the stack pointer for restoration upon an insertion error into the BASIC line workspace.
; $FD8C-$FF23 Not used. 408 bytes.
; $FF24 2 Never used. An attempt is made to set it to $EC00. This is a remnant from the Spanish 128, which stored the address of the Screen Buffer here.
; The value is written to RAM bank 0 instead of RAM bank 7, and the value never subsequently accessed.
; $FF26 2 Not used.
; $FF28-$FF60 Not used. On the Spanish 128 this memory holds a routine that copies a character into the display file. The code to copy to routine into RAM,
; and the routine itself are present in ROM 0 but are never executed. <<< RAM routine - See end of file for description >>>
; $FF61-$FFFF Not used. 159 bytes.
; ========================
; Called ROM 1 Subroutines
; ========================
ERROR_1 EQU $0008
PRINT_A_1 EQU $0010
GET_CHAR EQU $0018
NEXT_CHAR EQU $0020
BC_SPACES EQU $0030
TOKENS EQU $0095
BEEPER EQU $03B5
BEEP EQU $03F8
SA_ALL EQU $075A
ME_CONTRL EQU $08B6
SA_CONTROL EQU $0970
PRINT_OUT EQU $09F4
PO_T_UDG EQU $0B52
PO_MSG EQU $0C0A
TEMPS EQU $0D4D
CLS EQU $0D6B
CLS_LOWER EQU $0D6E
CL_ALL EQU $0DAF
CL_ATTR EQU $0E88
CL_ADDR EQU $0E9B
CLEAR_PRB EQU $0EDF
ADD_CHAR EQU $0F81
ED_ERROR EQU $107F
CLEAR_SP EQU $1097
KEY_INPUT EQU $10A8
KEY_M_CL EQU $10DB
MAIN_4 EQU $1303
ERROR_MSGS EQU $1391
MESSAGES EQU $1537
REPORT_J EQU $15C4
OUT_CODE EQU $15EF
CHAN_OPEN EQU $1601
CHAN_FLAG EQU $1615
POINTERS EQU $1664
CLOSE EQU $16E5
MAKE_ROOM EQU $1655
LINE_NO EQU $1695
SET_MIN EQU $16B0
SET_WORK EQU $16BF
SET_STK EQU $16C5
OPEN EQU $1736
LIST_5 EQU $1822
NUMBER EQU $18B6
LINE_ADDR EQU $196E
EACH_STMT EQU $198B
NEXT_ONE EQU $19B8
RECLAIM EQU $19E5
RECLAIM_2 EQU $19E8
E_LINE_NO EQU $19FB
OUT_NUM_1 EQU $1A1B
CLASS_01 EQU $1C1F
VAL_FET_1 EQU $1C56
CLASS_04 EQU $1C6C
EXPT_2NUM EQU $1C7A
EXPT_1NUM EQU $1C82
EXPT_EXP EQU $1C8C
CLASS_09 EQU $1CBE
FETCH_NUM EQU $1CDE
USE_ZERO EQU $1CE6
STOP EQU $1CEE
F_REORDER EQU $1D16
LOOK_PROG EQU $1D86
NEXT EQU $1DAB
PASS_BY EQU $1E39
RESTORE EQU $1E42
REST_RUN EQU $1E45
RANDOMIZE EQU $1E4F
CONTINUE EQU $1E5F
GO_TO EQU $1E67
COUT EQU $1E7A ; Should be OUT but renamed since some assemblers detect this as an instruction.
POKE EQU $1E80
FIND_INT2 EQU $1E99
TEST_ROOM EQU $1F05
PAUSE EQU $1F3A
PRINT_2 EQU $1FDF
PR_ST_END EQU $2048
STR_ALTER EQU $2070
INPUT_1 EQU $2096
IN_ITEM_1 EQU $20C1
CO_TEMP_4 EQU $21FC
BORDER EQU $2294
PIXEL_ADDR EQU $22AA
PLOT EQU $22DC
PLOT_SUB EQU $22E5
CIRCLE EQU $2320
DR_3_PRMS EQU $238D
LINE_DRAW EQU $2477
SCANNING EQU $24FB
SYNTAX_Z EQU $2530
LOOK_VARS EQU $28B2
STK_VAR EQU $2996
STK_FETCH EQU $2BF1
D_RUN EQU $2C15
ALPHA EQU $2C8D
NUMERIC EQU $2D1B
STACK_BC EQU $2D2B
FP_TO_BC EQU $2DA2
PRINT_FP EQU $2DE3
HL_MULT_DE EQU $30A9
STACK_NUM EQU $33B4
TEST_ZERO EQU $34E9
KP_SCAN EQU $3C01
TEST_SCREEN EQU $3C04
CHAR_SET EQU $3D00
;**************************************************
; =========================
; RESTART ROUTINES - PART 1
; =========================
; RST $10, $18 and $20 call the equivalent subroutines in ROM 1, via RST $28.
;
; RST $00 - Reset the machine.
; RST $08 - Not used. Would have invoked the ZX Interface 1 if fitted.
; RST $10 - Print a character (equivalent to RST $10 ROM 1).
; RST $18 - Collect a character (equivalent to RST $18 ROM 1).
; RST $20 - Collect next character (equivalent to RST $20 ROM 1).
; RST $28 - Call routine in ROM 1.
; RST $30 - Not used.
; RST $38 - Not used.
; -----------------------
; RST $00 - Reset Machine
; -----------------------
ORG $0000
L0000: DI ; Ensure interrupts are disabled.
LD BC,$692B ;
L0004: DEC BC ; Delay about 0.2s to allow screen switching mechanism to settle.
LD A,B ;
OR C ;
JR NZ,L0004 ; [There is no RST $08. No instruction fetch at L0008 hence ZX Interface 1 will not be paged in from this ROM. Credit: Paul Farrow].
JP L00C7 ; to the main reset routine.
L000C: DEFB $00, $00 ; [Spare bytes]
DEFB $00, $00 ;
; ---------------------------
; RST $10 - Print A Character
; ---------------------------
L0010: RST 28H ; Call corresponding routine in ROM 1.
DEFW PRINT_A_1 ; $0010.
RET ;
L0014: DEFB $00, $00 ; [Spare bytes]
DEFB $00, $00 ;
; -----------------------------
; RST $18 - Collect A Character
; -----------------------------
L0018: RST 28H ; Call corresponding routine in ROM 1.
DEFW GET_CHAR ; $0018.
RET ;
L001C: DEFB $00, $00 ; [Spare bytes]
DEFB $00, $00 ;
; --------------------------------
; RST $20 - Collect Next Character
; --------------------------------
L0020: RST 28H ; Call corresponding routine in ROM 1.
DEFW NEXT_CHAR ; $0020.
RET ;
L0024: DEFB $00, $00 ; [Spare bytes]
DEFB $00, $00 ;
; -------------------------------
; RST $28 - Call Routine in ROM 1
; -------------------------------
; RST 28 calls a routine in ROM 1 (or alternatively a routine in RAM while
; ROM 1 is paged in). Call as follows: RST 28 / DEFW address.
L0028: EX (SP),HL ; Get the address after the RST $28 into HL,
; saving HL on the stack.
PUSH AF ; Save the AF registers.
LD A,(HL) ; Fetch the first address byte.
INC HL ; Point HL to the byte after
INC HL ; the required address.
LD (RETADDR),HL ; $5B5A. Store this in RETADDR.
DEC HL ; (There is no RST $30)
LD H,(HL) ; Fetch the second address byte.
LD L,A ; HL=Subroutine to call.
POP AF ; Restore AF.
JP L005C ; Jump ahead to continue.
L0037: DEFB $00 ; [Spare byte]
; ==========================
; MASKABLE INTERRUPT ROUTINE
; ==========================
; This routine preserves the Hl register pair. It then performs the following:
; - Execute the ROM switching code held in RAM to switch to ROM 1.
; - Execute the maskable interrupt routine in ROM 1.
; - Execute the ROM switching code held in RAM to return to ROM 0.
; - Return to address $0048 (ROM 0).
L0038: PUSH HL ; Save HL register pair.
LD HL,L0048 ; Return address of $0048 (ROM 0).
PUSH HL ;
LD HL,SWAP ; $5B00. Address of swap ROM routine held in RAM at $5B00.
PUSH HL ;
LD HL,L0038 ; Maskable interrupt routine address $0038 (ROM 0).
PUSH HL ;
JP SWAP ; $5B00. Switch to other ROM (ROM 1) via routine held in RAM at $5B00.
L0048: POP HL ; Restore the HL register pair.
RET ; End of interrupt routine.
; ===============================
; ERROR HANDLER ROUTINES - PART 1
; ===============================
; ------------------
; 128K Error Routine
; ------------------
L004A: LD BC,$7FFD ;
XOR A ; ROM 0, Bank 0, Screen 0, 128K mode.
DI ; Ensure interrupts are disabled whilst paging.
OUT (C),A ;
LD (BANK_M),A ; $5B5C. Note the new paging status.
EI ; Re-enable interrupts.
DEC A ; A=$FF.
LD (IY+$00),A ; Set ERR_NR to no error ($FF).
JP L0321 ; Jump ahead to continue.
; =========================
; RESTART ROUTINES - PART 2
; =========================
; -----------------------------------------
; Call ROM 1 Routine (RST $28 Continuation)
; -----------------------------------------
; Continuation from routine at $0028 (ROM 0).
L005C: LD (TARGET),HL ; $5B58. Save the address in ROM 0 to call.
LD HL,YOUNGER ; $5B14. HL='Return to ROM 0' routine held in RAM.
EX (SP),HL ; Stack HL.
PUSH HL ; Save previous stack address.
LD HL,(TARGET) ; $5B58. HL=Retrieve address to call. [There is no NMI code. Credit: Andrew Owen].
EX (SP),HL ; Stack HL.
JP SWAP ; $5B00. Switch to other ROM (ROM 1) and return to address to call.
; ============
; RAM ROUTINES
; ============
; The following code will be copied to locations $5B00 to $5B57, within the old ZX Printer buffer.
; -----------------
; Swap to Other ROM (copied to $5B00)
; -----------------
; Switch to the other ROM from that currently paged in.
; [The switching between the two ROMs invariably enables interrupts, which may not always be desired
; (see the bug at $09CD (ROM 0) in the PLAY command). To overcome this issue would require a rewrite
; of the SWAP routine as follows, but this is larger than the existing routine and so cannot simply be
; used in direct replacement of it. A work-around solution is to poke a JP instruction at the start of
; the SWAP routine in the ZX Printer buffer and direct control to the replacement routine held somewhere
; else in RAM. Credit: Toni Baker, ZX Computing Monthly]
;
; [However, the PLAY commnad bug may be fixed in another manner within the PLAY command itself, in which
; case there is no need to modify the SWAP routine.]
;
; SWAP:
; PUSH AF ; Stack AF.
; PUSH BC ; Stack BC.
;
; LD A,R ; P/V flag=Interrupt status.
; PUSH AF ; Stack interrupt status.
;
; LD BC,$7FFD ; BC=Port number required for paging.
; LD A,(BANK_M) ; A=Current paging configuration.
; XOR $10 ; Complement 'ROM' bit.
; DI ; Disable interrupts (in case an interrupt occurs between the next two instructions).
; LD (BANK_M),A ; Store revised paging configuration.
; OUT (C),A ; Page ROM.
;
; POP AF ; P/V flag=Former interrupt status.
; JP PO,SWAP_EXIT ; Jump if interrupts were previously disabled.
;
; EI ; Re-enable interrupts.
;
; SWAP_EXIT:
; POP BC ; Restore BC.
; POP AF ; Restore AF.
; RET ;
;SWAP
L006B: PUSH AF ; Save AF and BC.
PUSH BC ;
LD BC,$7FFD ;
LD A,(BANK_M) ; $5B5C.
XOR $10 ; Select other ROM.
DI ; Disable interrupts whilst switching ROMs.
LD (BANK_M),A ; $5B5C.
OUT (C),A ; Switch to the other ROM.
EI ;
POP BC ; Restore BC and AF.
POP AF ;
RET ;
; ---------------------------
; Return to Other ROM Routine (copied to $5B14)
; ---------------------------
; Switch to the other ROM from that currently paged in
; and then return to the address held in RETADDR.
;YOUNGER
L007F: CALL SWAP ; $5B00. Toggle to the other ROM.
PUSH HL ;
LD HL,(RETADDR) ; $5B5A.
EX (SP),HL ;
RET ; Return to the address held in RETADDR.
; ---------------------
; Error Handler Routine (copied to $5B1D)
; ---------------------
; This error handler routine switches back to ROM 0 and then
; executes the routine pointed to by system variable TARGET.
;ONERR
L0088: DI ; Ensure interrupts are disabled whilst paging.
LD A,(BANK_M) ; $5B5C. Fetch current paging configuration.
AND $EF ; Select ROM 0.
LD (BANK_M),A ; $5B5C. Save the new configuration
LD BC,$7FFD ;
OUT (C),A ; Switch to ROM 0.
EI ;
JP L00C3 ; Jump to L00C3 (ROM 0) to continue.
; -------------------------
; 'P' Channel Input Routine (copied to $5B2F)
; -------------------------
; Called when data is read from channel 'P'.
; It causes ROM 0 to be paged in so that the new RS232 routines
; can be accessed.
;PIN
L009A: LD HL,L06D8 ; RS232 input routine within ROM 0.
JR L00A2 ;
; --------------------------
; 'P' Channel Output Routine (copied to $5B34)
; --------------------------
; Called when data is written to channel 'P'.
; It causes ROM 0 to be paged in so that the new RS232 routines
; can be accessed.
; Entry: A=Byte to send.
;POUT
L009F: LD HL,L07CA ; RS232 output routine within ROM 0.
L00A2: EX AF,AF' ; Save AF registers.
LD BC,$7FFD ;
LD A,(BANK_M) ; $5B5C. Fetch the current paging configuration
PUSH AF ; and save it.
AND $EF ; Select ROM 0.
DI ; Ensure interrupts are disabled whilst paging.
LD (BANK_M),A ; $5B5C. Store the new paging configuration.
OUT (C),A ; Switch to ROM 0.
JP L05E6 ; Jump to the RS232 channel input/output handler routine.
; ------------------------
; 'P' Channel Exit Routine (copied to $5B4A)
; ------------------------
; Used when returning from a channel 'P' read or write operation.
; It causes the original ROM to be paged back in and returns back to
; the calling routine.
;POUT2
L00B5: EX AF,AF' ; Save AF registers. For a read, A holds the byte read and the flags the success status.
POP AF ; Retrieve original paging configuration.
LD BC,$7FFD ;
DI ; Ensure interrupts are disabled whilst paging.
LD (BANK_M),A ; $5B5C. Store original paging configuration.
OUT (C),A ; Switch back to original paging configuration.
EI ;
EX AF,AF' ; Restore AF registers. For a read, A holds the byte read and the flags the success status.
RET ; <<< End of RAM Routines >>>
; ===============================
; ERROR HANDLER ROUTINES - PART 2
; ===============================
; ---------------
; Call Subroutine
; ---------------
; Called from ONERR ($5B1D) to execute the routine pointed
; to by system variable SYNRET.
L00C3: LD HL,(SYNRET) ; $5B8B. Fetch the address to call.
JP (HL) ; and execute it.
; ================================
; INITIALISATION ROUTINES - PART 1
; ================================
; --------------------------------------------
; Reset Routine (RST $00 Continuation, Part 1)
; --------------------------------------------
; Continuation from routine at $0000 (ROM 0). It performs a test on all RAM banks.
; This test is crude and can fail to detect a variety of RAM errors.
L00C7: LD B,$08 ; Loop through all RAM banks.
L00C9: LD A,B ;
EXX ; Save B register.
DEC A ; RAM bank number 0 to 7. 128K mode, ROM 0, Screen 0.
LD BC,$7FFD ;
OUT (C),A ; Switch RAM bank.
LD HL,$C000 ; Start of the current RAM bank.
LD DE,$C001 ;
LD BC,$3FFF ; All 16K of RAM bank.
LD A,$FF ;
LD (HL),A ; Store $FF into RAM location.
CP (HL) ; Check RAM integrity.
JR NZ,L0131 ; Jump if RAM error found.
XOR A ;
LD (HL),A ; Store $00 into RAM location.
CP (HL) ; Check RAM integrity.
JR NZ,L0131 ; Jump if difference found.
LDIR ; Clear the whole page
EXX ; Restore B registers.
DJNZ L00C9 ; Repeat for other RAM banks.
LD (ROW01),A ; $5B88. Signal no communications in progress to the keypad.
LD C,$FD ;
LD D,$FF ;
LD E,$BF ;
LD B,D ; BC=$FFFD, DE=$FFBF.
LD A,$0E ;
OUT (C),A ; Select AY register 14.
LD B,E ; BC=$BFFD.
LD A,$FF ;
OUT (C),A ; Set AY register 14 to $FF. This will force a communications reset to the keypad if present.
JR L0137 ; Jump ahead to continue.
L00FF: DEFB $00 ; [Spare byte]
; ====================
; ROUTINE VECTOR TABLE
; ====================
L0100: JP L17AF ; BASIC interpreter parser.
L0103: JP L1838 ; 'Line Run' entry point.
L0106: JP L1ECF ; Transfer bytes to logical RAM bank 4.
L0109: JP L1F04 ; Transfer bytes from logical RAM bank 4.
L010C: JP L004A ; 128K error routine.
L010F: JP L03A2 ; Error routine. Called from patch at L3B3B in ROM 1.
L0112: JP L182A ; 'Statement Return' routine. Called from patch at L3B4D in ROM 1.
L0115: JP L18A8 ; 'Statement Next' routine. Called from patch at L3B5D in ROM 1.
L0118: JP L012D ; Scan the keypad.
L011B: JP L0A05 ; Play music strings.
L011E: JP L11A3 ; MIDI byte output routine.
L0121: JP L06D8 ; RS232 byte input routine.
L0124: JP L07CA ; RS232 text output routine.
L0127: JP L08A3 ; RS232 byte output routine.
L012A: JP L08F0 ; COPY (screen dump) routine.
L012D: RST 28H ; Call keypad scan routine in ROM 1.
DEFW KP_SCAN-$0100 ; $3B01. [*BUG* - The address jumps into the middle of the keypad decode routine in ROM 1. It
RET ; looks like it is supposed to deal with the keypad and so the most likely
; addresses are $3A42 (read keypad) or $39A0 (scan keypad). At $3C01 in
; ROM 1 is a vector jump command to $39A0 to scan the keypad and this is
; similar enough to the $3B01 to imply a simple error in one of the bytes. Credit: Paul Farrow]
; ================================
; INITIALISATION ROUTINES - PART 2
; ================================
; ---------------
; Fatal RAM Error
; ---------------
; Set the border colour to indicate which RAM bank was found faulty:
; RAM bank 7 - Black.
; RAM bank 6 - White.
; RAM bank 5 - Yellow.
; RAM bank 4 - Cyan.
; RAM bank 3 - Green.
; RAM bank 2 - Magenta.
; RAM bank 1 - Red.
; RAM bank 0 - Blue.
L0131: EXX ; Retrieve RAM bank number + 1 in B.
LD A,B ; Indicate which RAM bank failed by
OUT ($FE),A ; setting the border colour.
L0135: JR L0135 ; Infinite loop.
; --------------------------------------------
; Reset Routine (RST $00 Continuation, Part 2)
; --------------------------------------------
; Continuation from routine at $00C7 (ROM 0).
L0137: LD B,D ; Complete setting up the sound chip registers.
LD A,$07 ;
OUT (C),A ; Select AY register 7.
LD B,E ;
LD A,$FF ; Disable AY-3-8912 sound channels.
OUT (C),A ;
LD DE,SWAP ; $5B00. Copy the various paging routines to the old printer buffer.
LD HL,L006B ; The source is in this ROM.
LD BC,$0058 ; There are eighty eight bytes to copy.
LDIR ; Copy the block of bytes.
LD A,$CF ; Load A with the code for the Z80 instruction 'RST $08'.
LD (RAMRST),A ; $5B5D. Insert into new System Variable RAMRST.
LD SP,TSTACK ; $5BFF. Set the stack pointer to last location of old buffer.
LD A,$04 ;
CALL L1C64 ; Page in logical RAM bank 4 (physical RAM bank 7).
LD IX,$EBEC ; First free entry in RAM disk.
LD (SFNEXT),IX ; $5B83.
LD (IX+$0A),$00 ;
LD (IX+$0B),$C0 ;
LD (IX+$0C),$00 ;
LD HL,$2BEC ;
LD A,$01 ; AHL=Free space in RAM disk.
LD (SFSPACE),HL ; $5B85. Current address.
LD (SFSPACE+2),A ; $5B87. Current RAM bank.
LD A,$05 ;
CALL L1C64 ; Page in logical RAM bank 5 (physical RAM bank 0).
LD HL,$FFFF ; Load HL with known last working byte - 65535.
LD ($5CB4),HL ; P_RAMT. Set physical RAM top to 65535.
LD DE,CHAR_SET+$01AF ; $3EAF. Set DE to address of the last bitmap of 'U' in ROM 1.
LD BC,$00A8 ; There are 21 User Defined Graphics to copy.
EX DE,HL ; Swap so destination is $FFFF.
RST 28H ;
DEFW MAKE_ROOM+$000C ; Calling this address (LDDR/RET) in the main ROM
; cleverly copies the 21 characters to the end of RAM.
EX DE,HL ; Transfer DE to HL.
INC HL ; Increment to address first byte of UDG 'A'.
LD ($5C7B),HL ; UDG. Update standard System Variable UDG.
DEC HL ;
LD BC,$0040 ; Set values 0 for PIP and 64 for RASP.
LD ($5C38),BC ; RASP. Update standard System Variables RASP and PIP.
LD ($5CB2),HL ; RAMTOP. Update standard System Variable RAMTOP - the last
; byte of the BASIC system area. Any machine code and
; graphics above this address are protected from NEW.
; Entry point for NEW with interrupts disabled and physical RAM bank 0 occupying
; the upper RAM region $C000 - $FFFF, i.e. the normal BASIC memory configuration.
L019D: LD HL,CHAR_SET-$0100 ; $3C00. Set HL to where, in theory character zero would be.
LD ($5C36),HL ; CHARS. Update standard System Variable CHARS.
LD HL,($5CB2) ; RAMTOP. Load HL with value of System Variable RAMTOP.
INC HL ; Address next location.
LD SP,HL ; Set the Stack Pointer.
IM 1 ; Select Interrupt Mode 1.
LD IY,$5C3A ; Set the IY register to address the standard System
; Variables and many of the new System Variables and
; even those of ZX Interface 1 in some cases.
SET 4,(IY+$01) ; FLAGS. Signal 128K mode.
; [This bit was unused and therefore never set by 48K BASIC]
EI ; With a stack and the IY register set, interrupts can
; be enabled.
LD HL,$000B ; Set HL to eleven, timing constant for 9600 baud.
LD (BAUD),HL ; $5B5F. Select default RS232 baud rate of 9600 baud.
XOR A ; Clear accumulator.
LD (SERFL),A ; $5B61. Indicate no byte waiting in RS232 receive buffer.
LD (COL),A ; $5B63. Set RS232 output column position to 0.
LD (TVPARS),A ; $5B65. Indicate no control code parameters expected.
LD HL,$EC00 ; [*BUG* - Should write to RAM bank 7. Main RAM has now been corrupted. The value stored is subsequently never used. Credit: Geoff Wearmouth]
LD ($FF24),HL ; This is a remnant from the Spanish 128, which used this workspace variable to hold the location of the Screen Buffer. It also suffered from the
; same bug, and in fact there was never a need to write to the value at this point since it was written again later during the initialisation process.
; [The 1985 Sinclair Research ESPAGNOL source code says that this instruction will write to the (previously cleared)
; main BASIC RAM during initialization but that a different page of RAM will be present during NEW.
; Stuff and Nonsense! Assemblers and other utilities present above RAMTOP will be corrupted by the BASIC NEW command
; since $FF24, and later $EC13, will be written to even if they are above RAMTOP.]
LD A,$50 ; Set printer width to a default of 80.
LD (WIDTH),A ; $5B64. Set RS232 printer output width to 80 columns.
LD HL,$000A ; Set HL to ten, the initial renumber line and also the increment.
LD (RNFIRST),HL ; $5B94. Store it as the initial line number when renumbering.
LD (RNSTEP),HL ; $5B96. Store it as the renumber line increment.
LD HL,$5CB6 ; Load HL with the address following the System Variables.
LD ($5C4F),HL ; CHANS. Set standard System Variable CHANS.
LD DE,L0589 ; Point to Initial Channel Information in this ROM.
; This is similar to that in main ROM but
; channel 'P' has input and output addresses in the
; new $5Bxx region.
LD BC,$0015 ; There are 21 bytes to copy.
EX DE,HL ; Switch pointer so destination is CHANS.
LDIR ; Copy the block of bytes.
EX DE,HL ; Transfer DE to HL.
DEC HL ; Decrement to point to $80 end-marker.
LD ($5C57),HL ; DATADD. Update standard System Variable DATADD to this
; resting address.
INC HL ; Bump address.
LD ($5C53),HL ; PROG. Set standard System Variable PROG.
LD ($5C4B),HL ; VARS. Set standard System Variable VARS.
LD (HL),$80 ; Insert the Variables end-marker.
INC HL ; Increment the address.
LD ($5C59),HL ; E_LINE.Set standard System Variable E_LINE.
LD (HL),$0D ; Insert a carriage return.
INC HL ; Increment address.
LD (HL),$80 ; Insert the $80 end-marker.
INC HL ; increment address.
LD ($5C61),HL ; WORKSP. Set the standard System Variable WORKSP.
LD ($5C63),HL ; STKBOT.Set the standard System Variable STKBOT.
LD ($5C65),HL ; STKEND. Set the standard System Variable STKEND.
LD A,$38 ; Set colour attribute to black ink on white paper.
LD ($5C8D),A ; ATTR_P. Set the standard System Variable ATTR_P.
LD ($5C8F),A ; MASK_P. Set the standard System Variable MASK_P.
LD ($5C48),A ; BORDCR. Set the standard System Variable BORDCR.
XOR A ; Clear the accumulator.
LD ($EC13),A ; Temporary P_FLAG. Temporary store for P-FLAG.
; [*BUG* - Should write to RAM bank 7. Main RAM has now been corrupted again. The effect of the bug can
; be seen by typing INVERSE 1: PRINT "Hello", followed by NEW, followed by PRINT "World", and will cause
; the second word to also be printed in inverse. Credit: Geoff Wearmouth]
LD A,$07 ; Load the accumulator with colour for white.
OUT ($FE),A ; Output to port $FE changing border to white.
LD HL,$0523 ; The values five and thirty five.
LD ($5C09),HL ; REPDEL. Set the standard System Variables REPDEL and REPPER.
DEC (IY-$3A) ; Set KSTATE+0 to $FF.
DEC (IY-$36) ; Set KSTATE+4 to $FF.
LD HL,L059E ; Set source to Initial Stream Data in this ROM (which is identical to that in main ROM).
LD DE,$5C10 ; Set destination to standard System Variable STRMS-FD
LD BC,$000E ; There are fourteen bytes to copy.
LDIR ; Block copy the bytes.
RES 1,(IY+$01) ; Update FLAGS - signal printer not is use.
LD (IY+$00),$FF ; Set standard System Variable ERR_NR to $FF (OK-1).
LD (IY+$31),$02 ; Set standard System Variable DF_SZ to two lines.
RST 28H ;
DEFW CLS ; $0D6B. Clear screen.
RST 28H ; Attempt to display TV tuning test screen.
DEFW TEST_SCREEN ; $3C04. Will return if BREAK is not being pressed.
LD DE,L0561 ; Sinclair copyright message.
CALL L057D ; Print a message terminated by having bit 7 set.
LD (IY+$31),$02 ; Set standard System Variable DF_SZ to two lines.
SET 5,(IY+$02) ; TV_FLAG. Signal lower screen will require clearing.
LD HL,TSTACK ; $5BFF. Set HL to location of temporary stack and
LD (OLDSP),HL ; $5B81. store in OLDSP.
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
LD A,$38 ; Set colours to black ink on white paper.
LD ($EC11),A ; Temporary ATTR_T used by the 128 BASIC Editor.
LD ($EC0F),A ; Temporary ATTR_P used by the 128 BASIC Editor.
; [Note this is where $EC13 (temporary P_FLAG) and $FF24 should be set]
CALL L2584 ; Initialise mode and cursor settings. IX will point at editing settings information.
CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
JP L259F ; Jump to show the Main menu.
; ===================================
; COMMAND EXECUTION ROUTINES - PART 1
; ===================================
; --------------------
; Execute Command Line
; --------------------
; A typed in command resides in the editing workspace. Execute it.
; The command could either be a new line to insert, or a line number to delete, or a numerical expression to evaluate.
L026B: LD HL,FLAGS3 ; $5B66.
SET 0,(HL) ; Select BASIC/Calculator mode.
LD (IY+$00),$FF ; ERR_NR. Set to '0 OK' status.
LD (IY+$31),$02 ; DF_SZ. Reset the number of rows in the lower screen.
LD HL,ONERR ; $5B1D. Return address should an error occur.
PUSH HL ; Stack it.
LD ($5C3D),SP ; Save the stack pointer in ERR_SP.
LD HL,L02BA ; Return address in ROM 0 after syntax checking.
LD (SYNRET),HL ; $5B8B. Store it in SYNRET.
CALL L228E ; Point to start of typed in BASIC command.
CALL L22CB ; Is the first character a function token, i.e. the start of a numerical expression?
JP Z,L21F8 ; Jump if so to evaluate it.
CP '(' ; $28. Is the first character the start of an expression?
JP Z,L21F8 ; Jump if so to evaluate it.
CP '-' ; $2D. Is the first character the start of an expression?
JP Z,L21F8 ; Jump if so to evaluate it.
CP '+' ; $2B. Is the first character the start of an expression?
JP Z,L21F8 ; Jump if so to evaluate it.
CALL L22E0 ; Is text just a number or a numerical expression?
JP Z,L21F8 ; Jump if a numerical expression to evaluate it.
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
LD A,($EC0E) ; Fetch mode.
CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
CP $04 ; Calculator mode?
JP NZ,L17AF ; Jump if not to parse and execute the BASIC command line, returning to L02BA (ROM 0).
;Calculator mode
CALL L2297 ; Is it a single LET command?
JP Z,L17AF ; Jump if so to parse and execute the BASIC command line, returning to L02BA (ROM 0).
;Otherwise ignore the command
POP HL ; Drop ONERR return address.
RET ;
; -----------------------------------
; Return from BASIC Line Syntax Check
; -----------------------------------
; This routine is returned to when a BASIC line has been syntax checked.
L02BA: BIT 7,(IY+$00) ; Test ERR_NR.
JR NZ,L02C1 ; Jump ahead if no error.
RET ; Simply return if an error.
;The syntax check was successful, so now proceed to parse the line for insertion or execution
L02C1: LD HL,($5C59) ; ELINE. Point to start of editing area.
LD ($5C5D),HL ; Store in CH_ADD.
RST 28H ;
DEFW E_LINE_NO ; $19FB. Call E_LINE_NO in ROM 1 to read the line number into editing area.
LD A,B ;
OR C ;
JP NZ,L03F7 ; Jump ahead if there was a line number.
; --------------------------------------
; Parse a BASIC Line with No Line Number
; --------------------------------------
RST 18H ; Get character.
CP $0D ; End of the line reached, i.e. no BASIC statement?
RET Z ; Return if so.
CALL L21EF ; Clear screen if it requires it.
BIT 6,(IY+$02) ; TVFLAG. Clear lower screen?
JR NZ,L02DF ; Jump ahead if no need to clear lower screen.
RST 28H ;
DEFW CLS_LOWER ; $0D6E. Clear the lower screen.
L02DF: RES 6,(IY+$02) ; TVFLAG. Signal to clear lower screen.
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
LD HL,$EC0D ; Editor flags.
BIT 6,(HL) ; Using lower screen area for editing?
JR NZ,L02F4 ; Jump ahead if so.
INC HL ;
LD A,(HL) ; Fetch the mode.
CP $00 ; In Edit Menu mode?
CALL Z,L3881 ; If so then clear lower editing area display.
L02F4: CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
LD HL,$5C3C ; TVFLAG.
RES 3,(HL) ; Signal mode has not changed.
LD A,$19 ; 25.
SUB (IY+$4F) ; S_POSN+1. Subtract the current print row position.
LD ($5C8C),A ; SCR_CT. Set the number of scrolls.
SET 7,(IY+$01) ; FLAGS. Not syntax checking.
LD (IY+$0A),$01 ; NSPPC. Set line to be jumped to as line 1.
LD HL,$3E00 ; The end of GO SUB stack marker.
PUSH HL ; Place it on the stack.
LD HL,ONERR ; $5B1D. The return address should an error occur.
PUSH HL ; Place it on the stack.
LD ($5C3D),SP ; ERR_SP. Store error routine address.
LD HL,L0321 ; Address of error handler routine in ROM 0.
LD (SYNRET),HL ; $5B8B. Store it in SYNRET.
JP L1838 ; Jump ahead to the main parser routine to execute the line.
; ===============================
; ERROR HANDLER ROUTINES - PART 3
; ===============================
; ---------------------
; Error Handler Routine
; ---------------------
L0321: LD SP,($5CB2) ; RAMTOP.
INC SP ; Reset SP to top of memory map.
LD HL,TSTACK ; $5BFF.
LD (OLDSP),HL ; $5B81.
HALT ; Trap error conditions where interrupts are disabled.
RES 5,(IY+$01) ; FLAGS. Signal ready for a new key press.
LD HL,FLAGS3 ; $5B66.
BIT 2,(HL) ; Editing RAM disk catalogue?
JR Z,L034A ; Jump if not.
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
LD IX,(SFNEXT) ; $5B83.
LD BC,$0014 ; Catalogue entry size.
ADD IX,BC ; Remove last entry.
CALL L1D56 ; Update catalogue entry (leaves logical RAM bank 4 paged in)
CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
;Print error code held in ERR_NR
L034A: LD A,($5C3A) ; Fetch error code from ERR_NR.
INC A ; Increment error code.
L034E: PUSH AF ; Save the error code.
LD HL,$0000 ;
LD (IY+$37),H ; Clear ATTR_T.
LD (IY+$26),H ; Clear STRLEN.
LD ($5C0B),HL ; Clear DEFADD.
LD HL,$0001 ;
LD ($5C16),HL ; STRMS+$0006. Restore channel 'K' stream data.
RST 28H ;
DEFW SET_MIN ; $16B0. Clears editing area and areas after it.
RES 5,(IY+$37) ; FLAGX. Signal not INPUT mode.
RST 28H ;
DEFW CLS_LOWER ; $0D6E. Clear lower editing screen.
SET 5,(IY+$02) ; TVFLAG. Indicate lower screen does not require clearing.
POP AF ; Retrieve error code.
LD B,A ; Store error code in B.
CP $0A ; Is it a numeric error code (1-9)?
JR C,L037F ; If so jump ahead to print it.
CP $1D ; Is it one of the standard errors (A-R)?
JR C,L037D ; If so jump ahead to add 7 and print.
ADD A,$14 ; Otherwise add 20 to create code for lower case letters.
JR L037F ; and jump ahead to print.
; [Could have saved 2 bytes by using ADD A,$0C instead
; of these two instructions]
L037D: ADD A,$07 ; Increase code to point to upper case letters.
L037F: RST 28H ;
DEFW OUT_CODE ; $15EF. Print the character held in the A register.
LD A,$20 ; Print a space.
RST 10H ;
LD A,B ; Retrieve the error code.
CP $1D ; Is it one of the standard errors (A-R)?
JR C,L039C ; Jump if an standard error message (A-R).
;Print a new error message
; [Note that there is no check to range check the error code value and therefore whether a message exists for it.
; Poking directly to system variable ERR_NR with an invalid code (43 or above) will more than likely cause a crash]
SUB $1D ; A=Code $00 - $0E.
LD B,$00 ;
LD C,A ; Pass code to BC.
LD HL,L046C ; Error message vector table.
ADD HL,BC ;
ADD HL,BC ; Find address in error message vector table.
LD E,(HL) ;
INC HL ;
LD D,(HL) ; DE=Address of message to print.
CALL L057D ; Print error message.
JR L03A2 ; Jump ahead.
;Print a standard error message.
L039C: LD DE,ERROR_MSGS ; $1391. Position of the error messages in ROM 1.
RST 28H ; A holds the error code.
DEFW PO_MSG ; $0C0A. Call message printing routine.
;Continue to print the line and statement number
L03A2: XOR A ; Select the first message ", " (a 'comma' and a 'space').
LD DE,MESSAGES-1 ; $1536. Message base address in ROM 1.
RST 28H ;
DEFW PO_MSG ; Print a comma followed by a space.
LD BC,($5C45) ; PPC. Fetch current line number.
RST 28H ;
DEFW OUT_NUM_1 ; $1A1B. Print the line number.
LD A,$3A ; Print ':'.
RST 10H ;
LD C,(IY+$0D) ; SUBPPC. Fetch current statement number.
LD B,$00 ;
RST 28H ;
DEFW OUT_NUM_1 ; $1A1B. Print the statement number.
RST 28H ;
DEFW CLEAR_SP ; $1097. Clear editing and workspace areas.
LD A,($5C3A) ; ERR_NR. Fetch the error code.
INC A
JR Z,L03DF ; Jump ahead for "0 OK".
CP $09 ;
JR Z,L03CC ; Jump for "A Invalid argument", thereby advancing to the next statement.
CP $15 ;
JR NZ,L03CF ; Jump unless "M Ramtop no good".
L03CC: INC (IY+$0D) ; SUBPPC. Advance to the next statement.
L03CF: LD BC,$0003 ;
LD DE,$5C70 ; OSPPC. Continue statement number.
LD HL,$5C44 ; NSPPC. Next statement number.
BIT 7,(HL) ; Is there a statement number?
JR Z,L03DD ; Jump if so.
ADD HL,BC ; HL=SUBPPC. The current statement number.
L03DD: LDDR ; Copy SUBPPC and PPC to OSPPC and OLDPPC, for use by CONTINUE.
L03DF: LD (IY+$0A),$FF ; NSPPC. Signal no current statement number.
RES 3,(IY+$01) ; FLAGS. Select K-Mode.
LD HL,FLAGS3 ; $5B66.
RES 0,(HL) ; Select 128 Editor mode.
JP L25CB ; Jump ahead to return control to the Editor.
; ---------------------------------------------
; Error Handler Routine When Parsing BASIC Line
; ---------------------------------------------
L03EF: LD A,$10 ; Error code 'G - No room for line'.
LD BC,$0000 ;
JP L034E ; Jump to print the error code.
; ===================================
; COMMAND EXECUTION ROUTINES - PART 2
; ===================================
; -------------------------------------
; Parse a BASIC Line with a Line Number
; -------------------------------------
; This routine handles executing a BASIC line with a line number specified, or just a line number
; specified on its own, i.e. delete the line.
L03F7: LD ($5C49),BC ; E_PPC. Store the line as the current line number with the program cursor.
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
LD A,B ; [This test could have been performed before paging in bank 7 and hence could have benefited from a slight speed improvement.
OR C ; The test is redundant since BC holds a non-zero line number]
JR Z,L040A ; Jump if no line number.
LD ($5C49),BC ; E_PPC. Current edit line number. [Redundant instruction - Line number has already been stored]
LD ($EC08),BC ; Temporary E_PPC used by BASIC Editor.
L040A: CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
LD HL,($5C5D) ; CH_ADD. Point to the next character in the BASIC line.
EX DE,HL ;
LD HL,L03EF ; Address of error handler routine should there be no room for the line.
PUSH HL ; Stack it.
LD HL,($5C61) ; WORKSP.
SCF ;
SBC HL,DE ; HL=Length of BASIC line.
PUSH HL ; Stack it.
LD H,B ;
LD L,C ; Transfer edit line number to HL.
RST 28H ;
DEFW LINE_ADDR ; $196E. Returns address of the line in HL.
JR NZ,L0429 ; Jump if the line does not exist.
;The line already exists so delete it
RST 28H ;
DEFW NEXT_ONE ; $19B8. Find the address of the next line.
RST 28H ;
DEFW RECLAIM_2 ; $19E8. Delete the line.
L0429: POP BC ; BC=Length of the BASIC line.
LD A,C ;
DEC A ; Is it 1, i.e. just an 'Enter' character, and hence only
OR B ; a line number was entered?
JR NZ,L0442 ; Jump if there is a BASIC statement.
;Just a line number entered. The requested line has already been deleted so move the program cursor to the next line
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
PUSH HL ; Save the address of the line.
LD HL,($5C49) ; E_PPC. Fetch current edit line number.
CALL L334A ; Find closest line number (or L0000 if no line).
LD ($5C49),HL ; E_PPC. Store current edit line number. Effectively refresh E_PPC.
POP HL ; HL=Address of the line.
CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
JR L046A ; Jump ahead to exit.
L0442: PUSH BC ; BC=Length of the BASIC line. Stack it.
INC BC ;
INC BC ;
INC BC ;
INC BC ; BC=BC+4. Allow for line number and length bytes.
DEC HL ; Point to before the current line, i.e. the location to insert bytes at.
LD DE,($5C53) ; PROG. Get start address of the BASIC program.
PUSH DE ; Stack it.
RST 28H ;
DEFW MAKE_ROOM ; $1655. Insert BC spaces at address HL.
POP HL ; HL=Start address of BASIC program.
LD ($5C53),HL ; PROG. Save start address of BASIC program.
POP BC ; BC=Length of the BASIC line.
PUSH BC ;
INC DE ; Point to the first location of the newly created space.
LD HL,($5C61) ; WORKSP. Address of end of the BASIC line in the workspace.
DEC HL ;
DEC HL ; Skip over the newline and terminator bytes.
LDDR ; Copy the BASIC line from the workspace into the program area.
LD HL,($5C49) ; E_PPC. Current edit line number.
EX DE,HL ;
POP BC ; BC=Length of BASIC line.
LD (HL),B ; Store the line length.
DEC HL ;
LD (HL),C ;
DEC HL ;
LD (HL),E ; DE=line number.
DEC HL ;
LD (HL),D ; Store the line number.
L046A: POP AF ; Drop item (address of error handler routine).
RET ; Exit with HL=Address of the line.
; ===============================
; ERROR HANDLER ROUTINES - PART 4
; ===============================
; ------------------------------
; New Error Message Vector Table
; ------------------------------
; Pointers into the new error message table.
L046C: DEFW L048C ; Error report 'a'.
DEFW L0497 ; Error report 'b'.
DEFW L04A6 ; Error report 'c'.
DEFW L04B0 ; Error report 'd'.
DEFW L04C1 ; Error report 'e'.
DEFW L04D4 ; Error report 'f'.
DEFW L04E0 ; Error report 'g'.
DEFW L04E0 ; Error report 'h'.
DEFW L04F3 ; Error report 'i'.
DEFW L0501 ; Error report 'j'.
DEFW L0512 ; Error report 'k'.
DEFW L0523 ; Error report 'l'
DEFW L0531 ; Error report 'm'.
DEFW L0542 ; Error report 'n'.
DEFW L054E ; Error report 'o'.
DEFW L0561 ; Error report 'p'.
; -----------------------
; New Error Message Table
; -----------------------
L048C: DEFM "MERGE erro" ; Report 'a'.
DEFB 'r'+$80
L0497: DEFM "Wrong file typ" ; Report 'b'.
DEFB 'e'+$80
L04A6: DEFM "CODE erro" ; Report 'c'.
DEFB 'r'+$80
L04B0: DEFM "Too many bracket" ; Report 'd'.
DEFB 's'+$80
L04C1: DEFM "File already exist" ; Report 'e'.
DEFB 's'+$80
L04D4: DEFM "Invalid nam" ; Report 'f'.
DEFB 'e'+$80
L04E0: DEFM "File does not exis" ; Report 'g' & 'h'.
DEFB 't'+$80
L04F3: DEFM "Invalid devic" ; Report 'i'.
DEFB 'e'+$80
L0501: DEFM "Invalid baud rat" ; Report 'j'.
DEFB 'e'+$80
L0512: DEFM "Invalid note nam" ; Report 'k'.
DEFB 'e'+$80
L0523: DEFM "Number too bi" ; Report 'l'.
DEFB 'g'+$80
L0531: DEFM "Note out of rang" ; Report 'm'.
DEFB 'e'+$80
L0542: DEFM "Out of rang" ; Report 'n'.
DEFB 'e'+$80
L054E: DEFM "Too many tied note" ; Report 'o'.
DEFB 's'+$80
L0561: DEFB $7F ; '(c)'.
DEFM " 1986 Sinclair Research Lt" ; Copyright. [There should have been an error report "p Bad parameterr" here as there was in the Spanish 128,
DEFB 'd'+$80 ; or the error code byte at $232F (ROM 0) should have been $19 for "Q Parameter error"]
; -------------
; Print Message
; -------------
; Print a message which is terminated by having bit 7 set, pointed at by DE.
L057D: LD A,(DE) ; Fetch next byte.
AND $7F ; Mask off top bit.
PUSH DE ; Save address of current message byte.
RST 10H ; Print character.
POP DE ; Restore message byte pointer.
LD A,(DE) ;
INC DE ;
ADD A,A ; Carry flag will be set if byte is $FF.
JR NC,L057D ; Else print next character.
RET ;
; ================================
; INITIALISATION ROUTINES - PART 3
; ================================
; ---------------------------------
; The 'Initial Channel Information'
; ---------------------------------
; Initially there are four channels ('K', 'S', 'R', & 'P') for communicating with the 'keyboard', 'screen', 'work space' and 'printer'.
; For each channel the output routine address comes before the input routine address and the channel's code.
; This table is almost identical to that in ROM 1 at $15AF but with changes to the channel P routines to use the RS232 port
; instead of the ZX Printer.
; Used at $01DD (ROM 0).
L0589: DEFW PRINT_OUT ; $09F4 - K channel output routine.
DEFW KEY_INPUT ; $10A8 - K channel input routine.
DEFB 'K' ; $4B - Channel identifier 'K'.
DEFW PRINT_OUT ; $09F4 - S channel output routine.
DEFW REPORT_J ; $15C4 - S channel input routine.
DEFB 'S' ; $53 - Channel identifier 'S'.
DEFW ADD_CHAR ; $0F81 - R channel output routine.
DEFW REPORT_J ; $15C4 - R channel input routine.
DEFB 'R' ; $52 - Channel identifier 'R'.
DEFW POUT ; $5B34 - P Channel output routine.
DEFW PIN ; $5B2F - P Channel input routine.
DEFB 'P' ; $50 - Channel identifier 'P'.
DEFB $80 ; End marker.
; -------------------------
; The 'Initial Stream Data'
; -------------------------
; Initially there are seven streams - $FD to $03.
; This table is identical to that in ROM 1 at $15C6.
; Used at $0226 (ROM 0).
L059E: DEFB $01, $00 ; Stream $FD leads to channel 'K'.
DEFB $06, $00 ; Stream $FE leads to channel 'S'.
DEFB $0B, $00 ; Stream $FF leads to channel 'R'.
DEFB $01, $00 ; Stream $00 leads to channel 'K'.
DEFB $01, $00 ; Stream $01 leads to channel 'K'.
DEFB $06, $00 ; Stream $02 leads to channel 'S'.
DEFB $10, $00 ; Stream $03 leads to channel 'P'.
; ===============================
; ERROR HANDLER ROUTINES - PART 5
; ===============================
; --------------------
; Produce Error Report
; --------------------
L05AC: POP HL ; Point to the error byte.
LD BC,$7FFD ;
XOR A ; ROM 0, Screen 0, Bank 0, 128 mode.
DI ; Ensure interrupts disable whilst paging.
LD (BANK_M),A ; $5B5C. Store new state in BANK_M.
OUT (C),A ; Switch to ROM 0.
EI ;
LD SP,($5C3D) ; Restore SP from ERR_SP.
LD A,(HL) ; Fetch the error number.
LD (RAMERR),A ; $5B5E. Store the error number.
INC A ;
CP $1E ; [*BUG* - This should be $1D. As such, error code 'a' will be diverted to ROM 1 for handling. Credit: Paul Farrow]
JR NC,L05C8 ; Jump if not a standard error code.
;Handle a standard error code
RST 28H ;
DEFW RAMRST ; $5B5D. Call the error handler routine in ROM 1.
;Handle a new error code
L05C8: DEC A ;
LD (IY+$00),A ; Store in ERR_NR.
LD HL,($5C5D) ; CH_ADD.
LD ($5C5F),HL ; X_PTR. Set up the address of the character after the '?' marker.
RST 28H ;
DEFW SET_STK ; $16C5. Set the calculator stack.
RET ; Return to the error routine.
; ----------------------------
; Check for BREAK into Program
; ----------------------------
L05D6: LD A,$7F ; Read keyboard row B - SPACE.
IN A,($FE) ;
RRA ; Extract the SPACE key.
RET C ; Return if SPACE not pressed.
LD A,$FE ; Read keyboard row CAPS SHIFT - V.
IN A,($FE) ;
RRA ; Extract the CAPS SHIFT key.
RET C ; Return if CAPS SHIFT not pressed.
CALL L05AC ; Produce an error.
DEFB $14 ; "L Break into program"
; ======================
; RS232 PRINTER ROUTINES
; ======================
; ------------------------------
; RS232 Channel Handler Routines
; ------------------------------
; This routine handles input and output RS232 requested. It is similar to the
; routine in the ZX Interface 1 ROM at $0D5A, but in that ROM the routine is only used
; for input.
L05E6: EI ; Enabled interrupts.
EX AF,AF' ; Save AF registers.
LD DE,POUT2 ; $5B4A. Address of the RS232 exit routine held in RAM.
PUSH DE ; Stack it.
RES 3,(IY+$02) ; TVFLAG. Indicate not automatic listing.
PUSH HL ; Save the input/output routine address.
LD HL,($5C3D) ; Fetch location of error handler routine from ERR_SP.
LD E,(HL) ;
INC HL ;
LD D,(HL) ; DE=Address of error handler routine.
AND A ;
LD HL,ED_ERROR ; $107F in ROM 1.
SBC HL,DE ;
JR NZ,L0637 ; Jump if error handler address is different, i.e. due to INKEY$# or PRINT#.
; Handle INPUT#
; -------------
POP HL ; Retrieve the input/output routine address.
LD SP,($5C3D) ; ERR_SP.
POP DE ; Discard the error handler routine address.
POP DE ; Fetch the original address of ERR_SP (this was stacked at the beginning of the INPUT routine in ROM 1).
LD ($5C3D),DE ; ERR_SP.
L060A: PUSH HL ; Save the input/output routine address.
LD DE,L0610 ; Address to return to.
PUSH DE ; Stack the address.
JP (HL) ; Jump to the RS232 input/output routine.
;Return here from the input/output routine
L0610: JR C,L061B ; Jump if a character was received.
JR Z,L0618 ; Jump if a character was not received.
L0614: CALL L05AC ; Produce an error "8 End of file".
DEFB $07 ;
;A character was not received
L0618: POP HL ; Retrieve the input routine address.
JR L060A ; Jump back to await another character.
;A character was received
L061B: CP $0D ; Is it a carriage return?
JR Z,L062D ; Jump ahead if so.
LD HL,(RETADDR) ; $5B5A. Fetch the return address.
PUSH HL ;
RST 28H ;
DEFW ADD_CHAR+4 ; $0F85. Insert the character into the INPUT line.
POP HL ;
LD (RETADDR),HL ; $5B5A. Restore the return address.
POP HL ; Retrieve the input routine address.
JR L060A ; Jump back to await another character.
;Enter was received so end reading the stream
L062D: POP HL ; Discard the input routine address.
LD A,(BANK_M) ; $5B5C. Fetch current paging configuration.
OR $10 ; Select ROM 1.
PUSH AF ; Stack the required paging configuration.
JP POUT2 ; $5B4A. Exit.
; Handle INKEY$# and PRINT#
; -------------------------
L0637: POP HL ; Retrieve the input/output routine address.
LD DE,L063D ;
PUSH DE ; Stack the return address.
JP (HL) ; Jump to input or output routine.
;Return here from the input/output routine. When returning from the output routine, either the carry or zero flags should always
;be set to avoid the false generation of error report "8 End of file" [though this is not always the case - see bugs starting at $086C (ROM 0)].
L063D: RET C ; Return if a character was received.
RET Z ; Return if a character was not received or was written.
JR L0614 ; Produce error report "8 End of file".
; --------------
; FORMAT Routine
; --------------
; The format command sets the RS232 baud rate, e.g. FORMAT "P"; 9600.
; It attempts to match against one of the supported baud rates, or uses the next
; higher baud rate if a non-standard value is requested. The maximum baud rate supported
; is 9600, and this is used for any rates specified that are higher than this.
L0641: RST 28H ; [Could just do RST $18]
DEFW GET_CHAR ; $0018.
RST 28H ; Get an expression.
DEFW EXPT_EXP ; $1C8C.
BIT 7,(IY+$01) ; FLAGS.
JR Z,L0661 ; Jump ahead if syntax checking.
RST 28H ;
DEFW STK_FETCH ; $2BF1. Fetch the expression.
LD A,C ;
DEC A ;
OR B ;
JR Z,L0659 ; Jump ahead if string is 1 character long.
CALL L05AC ; Produce error report.
DEFB $24 ; "i Invalid device".
L0659: LD A,(DE) ; Get character.
AND $DF ; Convert to upper case.
CP 'P' ; $50. Is it channel 'P'?
JP NZ,L1912 ; Jump if not to produce error report "C Nonsense in BASIC".
L0661: LD HL,($5C5D) ; CH_ADD. Next character to be interpreted.
LD A,(HL) ;
CP $3B ; Next character must be ';'.
JP NZ,L1912 ; Jump if not to produce error report "C Nonsense in BASIC".
RST 28H ; Skip past the ';' character.
DEFW NEXT_CHAR ; $0020. [Could just do RST $20]
RST 28H ; Get a numeric expression from the line.
DEFW EXPT_1NUM ; $1C82.
BIT 7,(IY+$01) ; FLAGS. Checking syntax mode?
JR Z,L067D ; Jump ahead if so.
RST 28H ; Get the result as an integer.
DEFW FIND_INT2 ; $1E99.
LD (HD_00),BC ; $5B71. Store the result temporarily for use later.
L067D: RST 28H ; [Could just do RST $18]
DEFW GET_CHAR ; $0018. Get the next character in the BASIC line.
CP $0D ; It should be ENTER.
JR Z,L0689 ; Jump ahead if it is.
CP ':' ; $3A. Or the character is allowed to be ':'.
JP NZ,L1912 ; Jump if not to produce error report "C Nonsense in BASIC".
L0689: CALL L18A1 ; Check for end of line.
LD BC,(HD_00) ; $5B71. Get the baud rate saved earlier.
LD A,B ; Is it zero?
OR C ;
JR NZ,L0698 ; Jump if not, i.e. a numeric value was specified.
CALL L05AC ; Produce error report.
DEFB $25 ; "j invalid baud rate"
;Lookup the timing constant to use for the specified baud rate
L0698: LD HL,L06B8 ; Table of supported baud rates.
L069B: LD E,(HL) ;
INC HL ;
LD D,(HL) ;
INC HL ;
EX DE,HL ; HL=Supported baud rate value.
LD A,H ;
CP $25 ; Reached the last baud rate value in the table?
JR NC,L06AF ; Jump is so to use a default baud rate of 9600.
AND A ;
SBC HL,BC ; Table entry matches or is higher than requested baud rate?
JR NC,L06AF ; Jump ahead if so to use this baud rate.
EX DE,HL ;
INC HL ; Skip past the timing constant value
INC HL ; for this baud rate entry.
JR L069B ;
;The baud rate has been matched
L06AF: EX DE,HL ; HL points to timing value for the baud rate.
LD E,(HL) ;
INC HL ;
LD D,(HL) ; DE=Timing value for the baud rate.
LD (BAUD),DE ; $5B71. Store new value in system variable BAUD.
RET ;
; ---------------
; Baud Rate Table
; ---------------
; Consists of entries of baud rate value followed by timing constant to use in the RS232 routines.
L06B8: DEFW $0032, $0AA5 ; Baud=50.
DEFW $006E, $04D4 ; Baud=110.
DEFW $012C, $01C3 ; Baud=300.
DEFW $0258, $00E0 ; Baud=600.
DEFW $04B0, $006E ; Baud=1200.
DEFW $0960, $0036 ; Baud=2400.
DEFW $12C0, $0019 ; Baud=4800.
DEFW $2580, $000B ; Baud=9600.
; -------------------
; RS232 Input Routine
; -------------------
; Exit: Carry flag set if a byte was read with the byte in A. Carry flag reset upon error.
L06D8: LD HL,SERFL ; $5B61. SERFL holds second char that can be received
LD A,(HL) ; Is the second-character received flag set?
AND A ; i.e. have we already received data?
JR Z,L06E5 ; Jump ahead if not.
LD (HL),$00 ; Otherwise clear the flag
INC HL ;
LD A,(HL) ; and return the data which we received earlier.
SCF ; Set carry flag to indicate success
RET ;
; -------------------------
; Read Byte from RS232 Port
; -------------------------
; The timing of the routine is achieved using the timing constant held in system variable BAUD.
; Exit: Carry flag set if a byte was read, or reset upon error.
; A=Byte read in.
L06E5: CALL L05D6 ; Check the BREAK key, and produce error message if it is being pressed.
DI ; Ensure interrupts are disabled to achieve accurate timing.
EXX ;
LD DE,(BAUD) ; $5B71. Fetch the baud rate timing constant.
LD HL,(BAUD) ; $5B71.
SRL H ;
RR L ; HL=BAUD/2. So that will sync to half way point in each bit.
OR A ; [Redundant byte]
LD B,$FA ; Waiting time for start bit.
EXX ; Save B.
LD C,$FD ;
LD D,$FF ;
LD E,$BF ;
LD B,D ;
LD A,$0E ;
OUT (C),A ; Selects register 14, port I/O of AY-3-8912.
IN A,(C) ; Read the current state of the I/O lines.
OR $F0 ; %11110000. Default all input lines to 1.
AND $FB ; %11111011. Force CTS line to 0.
LD B,E ; B=$BF.
OUT (C),A ; Make CTS (Clear To Send) low to indicate ready to receive.
LD H,A ; Store status of other I/O lines.
;Look for the start bit
L070E: LD B,D ;
IN A,(C) ; Read the input line.
AND $80 ; %10000000. Test TXD (input) line.
JR Z,L071E ; Jump if START BIT found.
L0715: EXX ; Fetch timeout counter
DEC B ; and decrement it.
EXX ; Store it.
JR NZ,L070E ; Continue to wait for start bit if not timed out.
XOR A ; Reset carry flag to indicate no byte read.
PUSH AF ; Save the failure flag.
JR L0757 ; Timed out waiting for START BIT.
L071E: IN A,(C) ; Second test of START BIT - it should still be 0.
AND $80 ; Test TXD (input) line.
JR NZ,L0715 ; Jump back if it is no longer 0.
IN A,(C) ; Third test of START BIT - it should still be 0.
AND $80 ; Test TXD (input) line.
JR NZ,L0715 ; Jump back if it is no longer 0.
;A start bit has been found, so the 8 data bits are now read in.
;As each bit is read in, it is shifted into the msb of A. Bit 7 of A is preloaded with a 1
;to represent the start bit and when this is shifted into the carry flag it signifies that 8
;data bits have been read in.
EXX ;
LD BC,$FFFD ;
LD A,$80 ; Preload A with the START BIT. It forms a shift counter used to count
EX AF,AF' ; the number of bits to read in.
L0731: ADD HL,DE ; HL=1.5*(BAUD).
NOP ; (4) Fine tune the following delay.
NOP ;
NOP ;
NOP ;
;BD-DELAY
L0736: DEC HL ; (6) Delay for 26*BAUD.
LD A,H ; (4)
OR L ; (4)
JR NZ,L0736 ; (12) Jump back to until delay completed.
IN A,(C) ; Read a bit.
AND $80 ; Test TXD (input) line.
JP Z,L074B ; Jump if a 0 received.
;Received one 1
EX AF,AF' ; Fetch the bit counter.
SCF ; Set carry flag to indicate received a 1.
RRA ; Shift received bit into the byte (C->76543210->C).
JR C,L0754 ; Jump if START BIT has been shifted out indicating all data bits have been received.
EX AF,AF' ; Save the bit counter.
JP L0731 ; Jump back to read the next bit.
;Received one 0
L074B: EX AF,AF' ; Fetch the bit counter.
OR A ; Clear carry flag to indicate received a 0.
RRA ; Shift received bit into the byte (C->76543210->C).
JR C,L0754 ; Jump if START BIT has been shifted out indicating all data bits have been received.
EX AF,AF' ; Save the bit counter.
JP L0731 ; Jump back to read next bit.
;After looping 8 times to read the 8 data bits, the start bit in the bit counter will be shifted out
;and hence A will contain a received byte.
L0754: SCF ; Signal success.
PUSH AF ; Push success flag.
EXX
;The success and failure paths converge here
L0757: LD A,H ;
OR $04 ; A=%1111x1xx. Force CTS line to 1.
LD B,E ; B=$BF.
OUT (C),A ; Make CTS (Clear To Send) high to indicate not ready to receive.
EXX
LD H,D ;
LD L,E ; HL=(BAUD).
LD BC,$0007 ;
OR A ;
SBC HL,BC ; HL=(BAUD)-7.
L0766: DEC HL ; Delay for the stop bit.
LD A,H ;
OR L ;
JR NZ,L0766 ; Jump back until delay completed.
LD BC,$FFFD ; HL will be $0000.
ADD HL,DE ; DE=(BAUD).
ADD HL,DE ;
ADD HL,DE ; HL=3*(BAUD). This is how long to wait for the next start bit.
;The device at the other end of the cable may send a second byte even though
;CTS is low. So repeat the procedure to read another byte.
L0771: IN A,(C) ; Read the input line.
AND $80 ; %10000000. Test TXD (input) line.
JR Z,L077F ; Jump if START BIT found.
DEC HL ; Decrement timeout counter.
LD A,H ;
OR L ;
JR NZ,L0771 ; Jump back looping for a start bit until a timeout occurs.
;No second byte incoming so return status of the first byte read attempt
POP AF ; Return status of first byte read attempt - carry flag reset for no byte received or
EI ; carry flag set and A holds the received byte.
RET
L077F: IN A,(C) ; Second test of START BIT - it should still be 0.
AND $80 ; Test TXD (input) line.
JR NZ,L0771 ; Jump back if it is no longer 0.
IN A,(C) ; Third test of START BIT - it should still be 0.
AND $80 ; Test TXD (input) line.
JR NZ,L0771 ; Jump back if it is no longer 0.
;A second byte is on its way and is received exactly as before
LD H,D ;
LD L,E ; HL=(BAUD).
LD BC,$0002 ;
SRL H ;
RR L ; HL=(BAUD)/2.
OR A ;
SBC HL,BC ; HL=(BAUD)/2 - 2.
LD BC,$FFFD ;
LD A,$80 ; Preload A with the START BIT. It forms a shift counter used to count
EX AF,AF' ; the number of bits to read in.
L079D: NOP ; Fine tune the following delay.
NOP ;
NOP ;
NOP ;
ADD HL,DE ; HL=1.5*(BAUD).
L07A2: DEC HL ; Delay for 26*(BAUD).
LD A,H ;
OR L ;
JR NZ,L07A2 ; Jump back to until delay completed.
IN A,(C) ; Read a bit.
AND $80 ; Test TXD (input) line.
JP Z,L07B7 ; Jump if a 0 received.
;Received one 1
EX AF,AF' ; Fetch the bit counter.
SCF ; Set carry flag to indicate received a 1.
RRA ; Shift received bit into the byte (C->76543210->C).
JR C,L07C0 ; Jump if START BIT has been shifted out indicating all data bits have been received.
EX AF,AF' ; Save the bit counter.
JP L079D ; Jump back to read the next bit.
;Received one 0
L07B7: EX AF,AF' ; Fetch the bit counter.
OR A ; Clear carry flag to indicate received a 0.
RRA ; Shift received bit into the byte (C->76543210->C).
JR C,L07C0 ; Jump if START BIT has been shifted out indicating all data bits have been received.
EX AF,AF' ; Save the bit counter.
JP L079D ; Jump back to read next bit.
;Exit with the byte that was read in
L07C0: LD HL,SERFL ; $5B61.
LD (HL),$01 ; Set the flag indicating a second byte is in the buffer.
INC HL ;
LD (HL),A ; Store the second byte read in the buffer.
POP AF ; Return the first byte.
EI ; Re-enable interrupts.
RET
; --------------------
; RS232 Output Routine
; --------------------
; This routine handles control codes, token expansion, graphics and UDGs. It therefore cannot send binary data and hence cannot support
; EPSON format ESC control codes [Credit: Andrew Owen].
; The routine suffers from a number of bugs as described in the comments below. It also suffers from a minor flaw in the design, which prevents
; interlacing screen and printer control codes and their parameters. For example, the following will not work correctly:
;
; 10 LPRINT CHR$ 16;
; 20 PRINT AT 0,0;
; 30 LPRINT CHR$ 0;"ABC"
;
; The control byte 16 gets stored in TVDATA so that the system knows how to interpret its parameter byte. However, the AT control code 22
; in line 20 will overwrite it. When line 30 is executed, TVDATA still holds the control code for 'AT' and so this line is interpreted as
; PRINT AT instead of PRINT INK. [Credit: Ian Collier (+3)]
;
; Entry: A=character to output.
; Exit : Carry flag reset indicates success.
L07CA: PUSH AF ; Save the character to print.
LD A,(TVPARS) ; $5B65. Number of parameters expected.
OR A ;
JR Z,L07E0 ; Jump if no parameters.
DEC A ; Ignore the parameter.
LD (TVPARS),A ; $5B65.
JR NZ,L07DB ; Jump ahead if we have not processed all parameters.
;All parameters processed
POP AF ; Retrieve character to print.
JP L0872 ; Jump ahead to continue.
L07DB: POP AF ; Retrieve character to print.
LD ($5C0F),A ; TVDATA+1. Store it for use later.
RET ;
L07E0: POP AF ; Retrieve character to print.
CP $A3 ; Test against code for 'SPECTRUM'.
JR C,L07F2 ; Jump ahead if not a token.
;Process tokens
LD HL,(RETADDR) ; $5B5A. Save RETADDR temporarily.
PUSH HL ;
RST 28H ;
DEFW PO_T_UDG ; $0B52. Print tokens via call to ROM 1 routine PO-T&UDG.
POP HL ;
LD (RETADDR),HL ; $5B5A. Restore the original contents of RETADDR.
SCF ;
RET ;
L07F2: LD HL,$5C3B ; FLAGS.
RES 0,(HL) ; Suppress printing a leading space.
CP ' ' ; $20. Is character to output a space?
JR NZ,L07FD ; Jump ahead if not a space.
SET 0,(HL) ; Signal leading space required.
L07FD: CP $7F ; Compare against copyright symbol.
JR C,L0803 ; Jump ahead if not a graphic or UDG character.
LD A,'?' ; $3F. Print a '?' for all graphic and UDG characters.
L0803: CP $20 ; Is it a control character?
JR C,L081E ; Jump ahead if so.
;Printable character
L0807: PUSH AF ; Save the character to print.
LD HL,COL ; $5B63. Point to the column number.
INC (HL) ; Increment the column number.
LD A,(WIDTH) ; $5B64. Fetch the number of columns.
CP (HL) ;
JR NC,L081A ; Jump if end of row not reached.
CALL L0822 ; Print a carriage return and line feed.
LD A,$01 ;
LD (COL),A ; $5B63. Set the print position to column 1.
L081A: POP AF ; Retrieve character to print.
JP L08A3 ; Jump ahead to print the character.
;Process control codes
L081E: CP $0D ; Is it a carriage return?
JR NZ,L0830 ; Jump ahead if not.
;Handle a carriage return
L0822: XOR A ;
LD (COL),A ; $5B63. Set the print position back to column 0.
LD A,$0D ;
CALL L08A3 ; Print a carriage return.
LD A,$0A ;
JP L08A3 ; Print a line feed.
L0830: CP $06 ; Is it a comma?
JR NZ,L0853 ; Jump ahead if not.
;Handle a comma
LD BC,(COL) ; $5B63. Fetch the column position.
LD E,$00 ; Will count number of columns to move across to reach next comma position.
L083A: INC E ; Increment column counter.
INC C ; Increment column position.
LD A,C ;
CP B ; End of row reached?
JR Z,L0848 ; Jump if so.
L0840: SUB $08 ;
JR Z,L0848 ; Jump if column 8, 16 or 32 reached.
JR NC,L0840 ; Column position greater so subtract another 8.
JR L083A ; Jump back and increment column position again.
;Column 8, 16 or 32 reached. Output multiple spaces until the desired column position is reached.
L0848: PUSH DE ; Save column counter in E.
LD A,$20 ;
CALL L07CA ; Output a space via a recursive call.
POP DE ; Retrieve column counter to E.
DEC E ; More spaces to output?
RET Z ; Return if no more to output.
JR L0848 ; Repeat for the next space to output.
L0853: CP $16 ; Is it AT?
JR Z,L0860 ; Jump ahead to handle AT.
CP $17 ; Is it TAB?
JR Z,L0860 ; Jump ahead to handle TAB.
CP $10 ; Check for INK, PAPER, FLASH, BRIGHT, INVERSE, OVER.
RET C ; Ignore if not one of these.
JR L0869 ; Jump ahead to handle INK, PAPER, FLASH, BRIGHT, INVERSE, OVER.
;Handle AT and TAB
L0860: LD ($5C0E),A ; TV_DATA. Store the control code for use later, $16 (AT) or $17 (TAB).
LD A,$02 ; Two parameters expected (even for TAB).
LD (TVPARS),A ; $5B65.
RET ; Return with zero flag set.
;Handle INK, PAPER, FLASH, BRIGHT, INVERSE, OVER
L0869: LD ($5C0E),A ; TV_DATA. Store the control code for use later.
LD A,$02 ; Two parameters expected. [*BUG* - Should be 1 parameter. 'LPRINT INK 4' will produce error report 'C Nonsense in BASIC'. Credit: Toni Baker, ZX Computing Monthly].
LD (TVPARS),A ; $5B65.
RET ; [*BUG* - Should return with the carry flag reset and the zero flag set. It causes a statement such as 'LPRINT INK 1;' to produce error report '8 End of file'.
; It is due to the main RS232 processing loop using the state of the flags to determine the success/failure response of the RS232 output routine. Credit: Ian Collier (+3), Andrew Owen (128)]
; [The bug can be fixed by inserting a XOR A instruction before the RET instruction. Credit: Paul Farrow]
;All parameters processed
L0872: LD D,A ; D=Character to print.
LD A,($5C0E) ; TV_DATA. Fetch the control code.
CP $16 ; Is it AT?
JR Z,L0882 ; Jump ahead to handle AT parameter.
CP $17 ; Is it TAB?
CCF ; [*BUG* - Should return with the carry flag reset and the zero flag set. It causes a statement such as 'LPRINT INK 1;' to produce error report '8 End of file'.
; It is due to the main RS232 processing loop using the state of the flags to determine the success/failure response of the RS232 output routine. Credit: Toni Baker, ZX Computing Monthly]
RET NZ ; Ignore if not TAB.
; [The bug can be fixed by replacing the instructions CCF and RET NZ with the following. Credit: Paul Farrow.
;
; JR Z,NOT_TAB
;
; XOR A
; RET
;
;NOT_TAB: ; ]
;Handle TAB parameter
LD A,($5C0F) ; TV_DATA+1. Fetch the saved parameter.
LD D,A ; Fetch parameter to D.
;Process AT and TAB
L0882: LD A,(WIDTH) ; $5B64.
CP D ; Reached end of row?
JR Z,L088A ; Jump ahead if so.
JR NC,L0890 ; Jump ahead if before end of row.
;Column position equal or greater than length of row requested
L088A: LD B,A ; (WIDTH).
LD A,D ; TAB/AT column position.
SUB B ; TAB/AT position - WIDTH.
LD D,A ; The new required column position.
JR L0882 ; Handle the new TAB/AT position.
L0890: LD A,D ; Fetch the desired column number.
OR A ;
JP Z,L0822 ; Jump to output a carriage return if column 0 required.
L0895: LD A,(COL) ; $5B63. Fetch the current column position.
CP D ; Compare against desired column position.
RET Z ; Done if reached requested column.
PUSH DE ; Save the number of spaces to output.
LD A,$20 ;
CALL L07CA ; Output a space via a recursive call.
POP DE ; Retrieve number of spaces to output.
JR L0895 ; Keep outputting spaces until desired column reached.
; ------------------------
; Write Byte to RS232 Port
; ------------------------
; The timing of the routine is achieved using the timing constant held in system variable BAUD.
; Entry: A holds character to send.
; Exit: Carry and zero flags reset.
L08A3: PUSH AF ; Save the byte to send.
LD C,$FD ;
LD D,$FF ;
LD E,$BF ;
LD B,D ;
LD A,$0E ;
OUT (C),A ; Select AY register 14 to control the RS232 port.
L08AF: CALL L05D6 ; Check the BREAK key, and produce error message if it is being pressed.
IN A,(C) ; Read status of data register.
AND $40 ; %01000000. Test the DTR line.
JR NZ,L08AF ; Jump back until device is ready for data.
LD HL,(BAUD) ; $5B5F. HL=Baud rate timing constant.
LD DE,$0002 ;
OR A ;
SBC HL,DE ;
EX DE,HL ; DE=(BAUD)-2.
POP AF ; Retrieve the byte to send.
CPL ; Invert the bits of the byte (RS232 logic is inverted).
SCF ; Carry is used to send START BIT.
LD B,$0B ; B=Number of bits to send (1 start + 8 data + 2 stop).
DI ; Disable interrupts to ensure accurate timing.
;Transmit each bit
L08C8: PUSH BC ; Save the number of bits to send.
PUSH AF ; Save the data bits.
LD A,$FE ;
LD H,D ;
LD L,E ; HL=(BAUD)-2.
LD BC,$BFFD ; AY-3-8912 data register.
JP NC,L08DA ; Branch to transmit a 1 or a 0 (initially sending a 0 for the start bit).
;Transmit a 0
AND $F7 ; Clear the RXD (out) line.
OUT (C),A ; Send out a 0 (high level).
JR L08E0 ; Jump ahead to continue with next bit.
;Transmit a 1
L08DA: OR $08 ; Set the RXD (out) line.
OUT (C),A ; Send out a 1 (low level).
JR L08E0 ; Jump ahead to continue with next bit.
;Delay the length of a bit
L08E0: DEC HL ; (6) Delay 26*BAUD cycles.
LD A,H ; (4)
OR L ; (4)
JR NZ,L08E0 ; (12) Jump back until delay is completed.
NOP ; (4) Fine tune the timing.
NOP ; (4)
NOP ; (4)
POP AF ; Retrieve the data bits to send.
POP BC ; Retrieve the number of bits left to send.
OR A ; Clear carry flag.
RRA ; Shift the next bit to send into the carry flag.
DJNZ L08C8 ; Jump back to send next bit until all bits sent.
EI ; Re-enable interrupts.
RET ; Return with carry and zero flags reset.
; --------------------
; COPY Command Routine
; --------------------
; This routine copies 22 rows of the screen, outputting them to the printer a
; half row at a time. It is designed for EPSON compatible printers supporting
; double density bit graphics and 7/72 inch line spacing.
; Only the pixel information is processed; the attributes are ignored.
L08F0: LD HL,HD_0B ; Half row counter.
LD (HL),$2B ; Set the half row counter to 43 half rows (will output 44 half rows in total).
L08F5: LD HL,L0979 ; Point to printer configuration data (7/72 inch line spacing, double density bit graphics).
CALL L095F ; Send the configuration data to printer.
CALL L0915 ; Output a half row, at double height.
LD HL,L0980 ; Table holds a line feed only.
CALL L095F ; Send a line feed to printer.
LD HL,HD_0B ; $5B72. The half row counter is tested to see if it is zero
XOR A ; and if so then the line spacing is reset to its
CP (HL) ; original value.
JR Z,L090E ; Jump if done, resetting printer line spacing.
DEC (HL) ; Decrement half row counter.
JR L08F5 ; Repeat for the next half row.
;Copy done so reset printer line spacing before exiting
L090E: LD HL,L0982 ; Point to printer configuration data (1/6 inch line spacing).
CALL L095F ; Send the configuration data to printer.
RET ; [Could have saved 1 byte by using JP L095F (ROM 0)]
; ---------------
; Output Half Row
; ---------------
L0915: LD HL,HD_00 ; $5B71. Pixel column counter.
LD (HL),$FF ; Set pixel column counter to 255 pixels.
L091A: CALL L0926 ; Output a column of pixels, at double height.
LD HL,HD_00 ; $5B71. Pixel column counter.
XOR A ;
CP (HL) ; Check if all pixels in this row have been output.
RET Z ; Return if so.
DEC (HL) ; Decrement pixel column counter.
JR L091A ; Repeat for all pixels in this row.
;Output a column of pixels (at double height)
L0926: LD DE,$C000 ; D=%11000000. Used to hold the double height pixel.
LD BC,(HD_00) ; $5B71. C=Pixel column counter, B=Half row counter.
SCF ;
RL B ; B=2xB+1
SCF ;
RL B ; B=4xB+3. The pixel row coordinate.
LD A,C ; Pixel column counter.
CPL ;
LD C,A ; C=255-C. The pixel column coordinate.
XOR A ; Clear A. Used to generate double height nibble of pixels to output.
PUSH AF ;
PUSH DE ;
PUSH BC ; Save registers.
L093A: CALL L096D ; Test whether pixel (B,C) is set
POP BC ;
POP DE ; Restore registers.
LD E,$00 ; Set double height pixel = 0.
JR Z,L0944 ; Jump if pixel is reset.
LD E,D ; The double height pixel to output (%11000000, %00110000, %00001100 or %00000011).
L0944: POP AF ;
OR E ; Add the double height pixel value to the byte to output.
PUSH AF ;
DEC B ; Decrement half row coordinate.
SRL D ;
SRL D ; Create next double height pixel value (%00110000, %00001100 or %00000011).
PUSH DE ;
PUSH BC ;
JR NC,L093A ; Repeat for all four pixels in the half row.
POP BC ;
POP DE ; Unload the stack.
POP AF ;
LD B,$03 ; Send double height nibble of pixels output 3 times.
; -----------------------
; Output Nibble of Pixels
; -----------------------
; Send each nibble of pixels (i.e. column of 4 pixels) output 3 times so that
; the width of a pixel is the same size as its height.
L0955: PUSH BC ;
PUSH AF ;
CALL L08A3 ; Send byte to RS232 port.
POP AF ;
POP BC ;
DJNZ L0955 ;
RET ;
; ----------------------------
; Output Characters from Table
; ----------------------------
; This routine is used to send a sequence of EPSON printer control codes out to the RS232 port.
; It sends (HL) characters starting from HL+1.
L095F: LD B,(HL) ; Get number of bytes to send.
INC HL ; Point to the data to send.
L0961: LD A,(HL) ; Retrieve value.
PUSH HL ;
PUSH BC ;
CALL L08A3 ; Send byte to RS232 port.
POP BC ;
POP HL ;
INC HL ; Point to next data byte to send.
DJNZ L0961 ; Repeat for all bytes.
RET ;
; -------------------------------
; Test Whether Pixel (B,C) is Set
; -------------------------------
; Entry: B=Pixel line
; C=Pixel column.
; Exit : A=$00 if pixel is reset
; A>$00 if pixel is set (actually the value of the bit corresponding to the pixel within the byte).
L096D: RST 28H ; Get address of (B,C) pixel into HL and pixel position within byte into A.
DEFW PIXEL_ADDR ; $22AA.
LD B,A ; B=Pixel position within byte (0-7).
INC B ;
XOR A ; Pixel mask.
SCF ; Carry flag holds bit to be rotated into the mask.
L0974: RRA ; Shift the mask bit into the required bit position.
DJNZ L0974 ;
AND (HL) ; Isolate this pixel from A.
RET ;
; ---------------------------------
; EPSON Printer Control Code Tables
; ---------------------------------
L0979: DEFB $06 ; 6 characters follow.
DEFB $1B, $31 ; ESC '1' - 7/72 inch line spacing.
DEFB $1B, $4C, $00, $03 ; ESC 'L' 0 3 - Double density (768 bytes per row).
L0980: DEFB $01 ; 1 character follows.
DEFB $0A ; Line feed.
L0982: DEFB $02 ; 2 characters follow.
DEFB $1B, $32 ; ESC '2' - 1/6 inch line spacing.
; =====================
; PLAY COMMAND ROUTINES
; =====================
; Up to 3 channels of music/noise are supported by the AY-3-8912 sound generator.
; Up to 8 channels of music can be sent to support synthesisers, drum machines or sequencers via the MIDI interface,
; with the first 3 channels also played by the AY-3-8912 sound generator. For each channel of music, a MIDI channel
; can be assigned to it using the 'Y' command.
;
; The PLAY command reserves and initialises space for the PLAY command. This comprises a block of $003C bytes
; used to manage the PLAY command (IY points to this command data block) and a block of $0037 bytes for each
; channel string (IX is used to point to the channel data block for the current channel). [Note that the command
; data block is $04 bytes larger than it needs to be, and each channel data block is $11 bytes larger than it
; needs to be]
;
; Entry: B=The number of strings in the PLAY command (1..8).
; -------------------------
; Command Data Block Format
; -------------------------
; IY+$00 / IY+$01 = Channel 0 data block pointer. Points to the data for channel 0 (string 1).
; IY+$02 / IY+$03 = Channel 1 data block pointer. Points to the data for channel 1 (string 2).
; IY+$04 / IY+$05 = Channel 2 data block pointer. Points to the data for channel 2 (string 3).
; IY+$06 / IY+$07 = Channel 3 data block pointer. Points to the data for channel 3 (string 4).
; IY+$08 / IY+$09 = Channel 4 data block pointer. Points to the data for channel 4 (string 5).
; IY+$0A / IY+$0B = Channel 5 data block pointer. Points to the data for channel 5 (string 6).
; IY+$0C / IY+$0D = Channel 6 data block pointer. Points to the data for channel 6 (string 7).
; IY+$0E / IY+$0F = Channel 7 data block pointer. Points to the data for channel 7 (string 8).
; IY+$10 = Channel bitmap. Initialised to $FF and a 0 rotated in to the left for each string parameters
; of the PLAY command, thereby indicating the channels in use.
; IY+$11 / IY+$12 = Channel data block duration pointer. Points to duration length store in channel 0 data block (string 1).
; IY+$13 / IY+$14 = Channel data block duration pointer. Points to duration length store in channel 1 data block (string 2).
; IY+$15 / IY+$16 = Channel data block duration pointer. Points to duration length store in channel 2 data block (string 3).
; IY+$17 / IY+$18 = Channel data block duration pointer. Points to duration length store in channel 3 data block (string 4).
; IY+$19 / IY+$1A = Channel data block duration pointer. Points to duration length store in channel 4 data block (string 5).
; IY+$1B / IY+$1C = Channel data block duration pointer. Points to duration length store in channel 5 data block (string 6).
; IY+$1D / IY+$1E = Channel data block duration pointer. Points to duration length store in channel 6 data block (string 7).
; IY+$1F / IY+$20 = Channel data block duration pointer. Points to duration length store in channel 7 data block (string 8).
; IY+$21 = Channel selector. It is used as a shift register with bit 0 initially set and then shift to the left
; until a carry occurs, thereby indicating all 8 possible channels have been processed.
; IY+$22 = Temporary channel bitmap, used to hold a working copy of the channel bitmap at IY+$10.
; IY+$23 / IY+$24 = Address of the channel data block pointers, or address of the channel data block duration pointers
; (allows the routine at $0A6E (ROM 0) to be used with both set of pointers).
; IY+$25 / IY+$26 = Stores the smallest duration length of all currently playing channel notes.
; IY+$27 / IY+$28 = The current tempo timing value (derived from the tempo parameter 60..240 beats per second).
; IY+$29 = The current effect waveform value.
; IY+$2A = Temporary string counter selector.
; IY+$2B..IY+$37 = Holds a floating point calculator routine.
; IY+$38..IY+$3B = Not used.
; -------------------------
; Channel Data Block Format
; -------------------------
; IX+$00 = The note number being played on this channel (equivalent to index offset into the note table).
; IX+$01 = MIDI channel assigned to this string (range 0 to 15).
; IX+$02 = Channel number (range 0 to 7), i.e. index position of the string within the PLAY command.
; IX+$03 = 12*Octave number (0, 12, 24, 36, 48, 60, 72, 84 or 96).
; IX+$04 = Current volume (range 0 to 15, or if bit 4 set then using envelope).
; IX+$05 = Last note duration value as specified in the string (range 1 to 9).
; IX+$06 / IX+$07 = Address of current position in the string.
; IX+$08 / IX+$09 = Address of byte after the end of the string.
; IX+$0A = Flags:
; Bit 0 : 1=Single closing bracket found (repeat string indefinitely).
; Bits 1-7: Not used (always 0).
; IX+$0B = Open bracket nesting level (range $00 to $04).
; IX+$0C / IX+$0D = Return address for opening bracket nesting level 0 (points to character after the bracket).
; IX+$0E / IX+$0F = Return address for opening bracket nesting level 1 (points to character after the bracket).
; IX+$10 / IX+$11 = Return address for opening bracket nesting level 2 (points to character after the bracket).
; IX+$12 / IX+$13 = Return address for opening bracket nesting level 3 (points to character after the bracket).
; IX+$14 / IX+$15 = Return address for opening bracket nesting level 4 (points to character after the bracket).
; IX+$16 = Closing bracket nesting level (range $FF to $04).
; IX+$17...IX+$18 = Return address for closing bracket nesting level 0 (points to character after the bracket).
; IX+$19...IX+$1A = Return address for closing bracket nesting level 1 (points to character after the bracket).
; IX+$1B...IX+$1C = Return address for closing bracket nesting level 2 (points to character after the bracket).
; IX+$1D...IX+$1E = Return address for closing bracket nesting level 3 (points to character after the bracket).
; IX+$1F...IX+$20 = Return address for closing bracket nesting level 4 (points to character after the bracket).
; IX+$21 = Tied notes counter (for a single note the value is 1).
; IX+$22 / IX+$23 = Duration length, specified in 96ths of a note.
; IX+$24...IX+$25 = Subsequent note duration length (used only with triplets), specified in 96ths of a note.
; IX+$26...IX+$36 = Not used.
L0985: DI ; Disable interrupts to ensure accurate timing.
;Create a workspace for the play channel command strings
PUSH BC ; B=Number of channel string (range 1 to 8). Also used as string index number in the following loop.
LD DE,$0037 ;
LD HL,$003C ;
L098D: ADD HL,DE ; Calculate HL=$003C + ($0037 * B).
DJNZ L098D ;
LD C,L ;
LD B,H ; BC=Space required (maximum = $01F4).
RST 28H ;
DEFW BC_SPACES ; $0030. Make BC bytes of space in the workspace.
DI ; Interrupts get re-enabled by the call mechanism to ROM 1 so disable them again.
PUSH DE ;
POP IY ; IY=Points at first new byte - the command data block.
PUSH HL ;
POP IX ; IX=Points at last new byte - byte after all channel information blocks.
LD (IY+$10),$FF ; Initial channel bitmap with value meaning 'zero strings'
;Loop over each string to be played
L09A0: LD BC,$FFC9 ; $-37 ($37 bytes is the size of a play channel string information block).
ADD IX,BC ; IX points to start of space for the last channel.
LD (IX+$03),$3C ; Default octave is 5.
LD (IX+$01),$FF ; No MIDI channel assigned.
LD (IX+$04),$0F ; Default volume is 15.
LD (IX+$05),$05 ; Default note duration.
LD (IX+$21),$00 ; Count of the number of tied notes.
LD (IX+$0A),$00 ; Signal not to repeat the string indefinitely.
LD (IX+$0B),$00 ; No opening bracket nesting level.
LD (IX+$16),$FF ; No closing bracket nesting level.
LD (IX+$17),$00 ; Return address for closing bracket nesting level 0.
LD (IX+$18),$00 ; [No need to initialise this since it is written to before it is ever tested]
; [*BUG* - At this point interrupts are disabled and IY is now being used as a pointer to the master
; PLAY information block. Unfortunately, interrupts are enabled during the STK_FETCH call and
; IY is left containing the wrong value. This means that if an interrupt were to occur during
; execution of the subroutine then there would be a one in 65536 chance that (IY+$40) will be
; corrupted - this corresponds to the volume setting for music channel A.
; Rewriting the SWAP routine to only re-enable interrupts if they were originally enabled
; would cure this bug (see end of file for description of her suggested fix). Credit: Toni Baker, ZX Computing Monthly]
; [An alternative and simpler solution to the fix Toni Baker describes would be to stack IY, set IY to point
; to the system variables at $5C3A, call STK_FETCH, disable interrupts, then pop the stacked value back to IY. Credit: Paul Farrow]
RST 28H ; Get the details of the string from the stack.
DEFW STK_FETCH ; $2BF1.
DI ; Interrupts get re-enabled by the call mechanism to ROM 1 so disable them again.
LD (IX+$06),E ; Store the current position within in the string, i.e. the beginning of it.
LD (IX+$07),D ;
LD (IX+$0C),E ; Store the return position within the string for a closing bracket,
LD (IX+$0D),D ; which is initially the start of the string in case a single closing bracket is found.
EX DE,HL ; HL=Points to start of string. BC=Length of string.
ADD HL,BC ; HL=Points to address of byte after the string.
LD (IX+$08),L ; Store the address of the character just
LD (IX+$09),H ; after the string.
POP BC ; B=String index number (range 1 to 8).
PUSH BC ; Save it on the stack again.
DEC B ; Reduce the index so it ranges from 0 to 7.
LD C,B ;
LD B,$00 ;
SLA C ; BC=String index*2.
PUSH IY ;
POP HL ; HL=Address of the command data block.
ADD HL,BC ; Skip 8 channel data pointer words.
PUSH IX ;
POP BC ; BC=Address of current channel information block.
LD (HL),C ; Store the pointer to the channel information block.
INC HL ;
LD (HL),B ;
OR A ; Clear the carry flag.
RL (IY+$10) ; Rotate one zero-bit into the least significant bit of the channel bitmap.
; This initially holds $FF but once this loop is over, this byte has
; a zero bit for each string parameter of the PLAY command.
POP BC ; B=Current string index.
DEC B ; Decrement string index so it ranges from 0 to 7.
PUSH BC ; Save it for future use on the next iteration.
LD (IX+$02),B ; Store the channel number.
JR NZ,L09A0 ; Jump back while more channel strings to process.
POP BC ; Drop item left on the stack.
;Entry point here from the vector table at $011B
L0A05: LD (IY+$27),$1A ; Set the initial tempo timing value.
LD (IY+$28),$0B ; Corresponds to a 'T' command value of 120, and gives two crotchets per second.
PUSH IY ;
POP HL ; HL=Points to the command data block.
LD BC,$002B ;
ADD HL,BC ;
EX DE,HL ; DE=Address to store RAM routine.
LD HL,L0A31 ; HL=Address of the RAM routine bytes.
LD BC,$000D ;
LDIR ; Copy the calculator routine to RAM.
LD D,$07 ; Register 7 - Mixer.
LD E,$F8 ; I/O ports are inputs, noise output off, tone output on.
CALL L0E7C ; Write to sound generator register.
LD D,$0B ; Register 11 - Envelope Period (Fine).
LD E,$FF ; Set period to maximum.
CALL L0E7C ; Write to sound generator register.
INC D ; Register 12 - Envelope Period (Coarse).
CALL L0E7C ; Write to sound generator register.
JR L0A7D ; Jump ahead to continue.
; [Could have saved these 2 bytes by having the code at $0A7D (ROM 0) immediately follow]
; -------------------------------------------------
; Calculate Timing Loop Counter <<< RAM Routine >>>
; -------------------------------------------------
; This routine is copied into the command data block (offset $2B..$37) by
; the routine at $0A05 (ROM 0).
; It uses the floating point calculator found in ROM 1, which is usually
; invoked via a RST $28 instruction. Since ROM 0 uses RST $28 to call a
; routine in ROM 1, it is unable to invoke the floating point calculator
; this way. It therefore copies the following routine to RAM and calls it
; with ROM 1 paged in.
;
; The routine calculates (10/x)/7.33e-6, where x is the tempo 'T' parameter value
; multiplied by 4. The result is used an inner loop counter in the wait routine at $0F76 (ROM 0).
; Each iteration of this loop takes 26 T-states. The time taken by 26 T-states
; is 7.33e-6 seconds. So the total time for the loop to execute is 2.5/TEMPO seconds.
;
; Entry: The value 4*TEMPO exists on the calculator stack (where TEMPO is in the range 60..240).
; Exit : The calculator stack holds the result.
L0A31: RST 28H ; Invoke the floating point calculator.
DEFB $A4 ; stk-ten. = x, 10
DEFB $01 ; exchange. = 10, x
DEFB $05 ; division. = 10/x
DEFB $34 ; stk-data. = 10/x, 7.33e-6
DEFB $DF ; - exponent $6F (floating point number 7.33e-6).
DEFB $75 ; - mantissa byte 1
DEFB $F4 ; - mantissa byte 2
DEFB $38 ; - mantissa byte 3
DEFB $75 ; - mantissa byte 4
DEFB $05 ; division. = (10/x)/7.33e-6
DEFB $38 ; end-calc.
RET ;
; --------------
; Test BREAK Key
; --------------
; Test for BREAK being pressed.
; Exit: Carry flag reset if BREAK is being pressed.
L0A3E: LD A,$7F ;
IN A,($FE) ;
RRA ;
RET C ; Return with carry flag set if SPACE not pressed.
LD A,$FE ;
IN A,($FE) ;
RRA ;
RET ; Return with carry flag set if CAPS not pressed.
; -------------------------------------------
; Select Channel Data Block Duration Pointers
; -------------------------------------------
; Point to the start of the channel data block duration pointers within the command data block.
; Entry: IY=Address of the command data block.
; Exit : HL=Address of current channel pointer.
L0A4A: LD BC,$0011 ; Offset to the channel data block duration pointers table.
JR L0A52 ; Jump ahead to continue.
; ----------------------------------
; Select Channel Data Block Pointers
; ----------------------------------
; Point to the start of the channel data block pointers within the command data block.
; Entry: IY=Address of the command data block.
; Exit : HL=Address of current channel pointer.
L0A4F: LD BC,$0000 ; Offset to the channel data block pointers table.
L0A52: PUSH IY ;
POP HL ; HL=Point to the command data block.
ADD HL,BC ; Point to the desired channel pointers table.
LD (IY+$23),L ;
LD (IY+$24),H ; Store the start address of channels pointer table.
LD A,(IY+$10) ; Fetch the channel bitmap.
LD (IY+$22),A ; Initialise the working copy.
LD (IY+$21),$01 ; Channel selector. Set the shift register to indicate the first channel.
RET ;
; -------------------------------------------------
; Get Channel Data Block Address for Current String
; -------------------------------------------------
; Entry: HL=Address of channel data block pointer.
; Exit : IX=Address of current channel data block.
L0A67: LD E,(HL) ;
INC HL ;
LD D,(HL) ; Fetch the address of the current channel data block.
PUSH DE ;
POP IX ; Return it in IX.
RET ;
; -------------------------
; Next Channel Data Pointer
; -------------------------
L0A6E: LD L,(IY+$23) ; The address of current channel data pointer.
LD H,(IY+$24) ;
INC HL ;
INC HL ; Advance to the next channel data pointer.
LD (IY+$23),L ;
LD (IY+$24),H ; The address of new channel data pointer.
RET ;
; ---------------------------
; PLAY Command (Continuation)
; ---------------------------
; This section is responsible for processing the PLAY command and is a continuation of the routine
; at $0985 (ROM 0). It begins by determining the first note to play on each channel and then enters
; a loop to play these notes, fetching the subsequent notes to play at the appropriate times.
L0A7D: CALL L0A4F ; Select channel data block pointers.
L0A80: RR (IY+$22) ; Working copy of channel bitmap. Test if next string present.
JR C,L0A8C ; Jump ahead if there is no string for this channel.
;HL=Address of channel data pointer.
CALL L0A67 ; Get address of channel data block for the current string into IX.
CALL L0B5C ; Find the first note to play for this channel from its play string.
L0A8C: SLA (IY+$21) ; Have all channels been processed?
JR C,L0A97 ; Jump ahead if so.
CALL L0A6E ; Advance to the next channel data block pointer.
JR L0A80 ; Jump back to process the next channel.
;The first notes to play for each channel have now been determined. A loop is entered that coordinates playing
;the notes and fetching subsequent notes when required. Notes across channels may be of different lengths and
;so the shortest one is determined, the tones for all channels set and then a waiting delay entered for the shortest
;note delay. This delay length is then subtracted from all channel note lengths to leave the remaining lengths that
;each note needs to be played for. For the channel with the smallest note length, this will now have completely played
;and so a new note is fetched for it. The smallest length of the current notes is then determined again and the process
;described above repeated. A test is made on each iteration to see if all channels have run out of data to play, and if
;so this ends the PLAY command.
L0A97: CALL L0F91 ; Find smallest duration length of the current notes across all channels.
PUSH DE ; Save the smallest duration length.
CALL L0F42 ; Play a note on each channel.
POP DE ; DE=The smallest duration length.
L0A9F: LD A,(IY+$10) ; Channel bitmap.
CP $FF ; Is there anything to play?
JR NZ,L0AAB ; Jump if there is.
CALL L0E93 ; Turn off all sound and restore IY.
EI ; Re-enable interrupts.
RET ; End of play command.
L0AAB: DEC DE ; DE=Smallest channel duration length, i.e. duration until the next channel state change.
CALL L0F76 ; Perform a wait.
CALL L0FC1 ; Play a note on each channel and update the channel duration lengths.
CALL L0F91 ; Find smallest duration length of the current notes across all channels.
JR L0A9F ; Jump back to see if there is more to process.
; ----------------------------
; PLAY Command Character Table
; ----------------------------
; Recognised characters in PLAY commands.
L0AB7: DEFM "HZYXWUVMT)(NO!"
; ------------------
; Get Play Character
; ------------------
; Get the current character from the PLAY string and then increment the
; character pointer within the string.
; Exit: Carry flag set if string has been fully processed.
; Carry flag reset if character is available.
; A=Character available.
L0AC5: CALL L0EE3 ; Get the current character from the play string for this channel.
RET C ; Return if no more characters.
INC (IX+$06) ; Increment the low byte of the string pointer.
RET NZ ; Return if it has not overflowed.
INC (IX+$07) ; Else increment the high byte of the string pointer.
RET ; Returns with carry flag reset.
; --------------------------
; Get Next Note in Semitones
; --------------------------
; Finds the number of semitones above C for the next note in the string,
; Entry: IX=Address of the channel data block.
; Exit : A=Number of semitones above C, or $80 for a rest.
L0AD1: PUSH HL ; Save HL.
LD C,$00 ; Default is for a 'natural' note, i.e. no adjustment.
L0AD4: CALL L0AC5 ; Get the current character from the PLAY string, and advance the position pointer.
JR C,L0AE1 ; Jump if at the end of the string.
CP '&' ; $26. Is it a rest?
JR NZ,L0AEC ; Jump ahead if not.
LD A,$80 ; Signal that it is a rest.
L0ADF: POP HL ; Restore HL.
RET ;
L0AE1: LD A,(IY+$21) ; Fetch the channel selector.
OR (IY+$10) ; Clear the channel flag for this string.
LD (IY+$10),A ; Store the new channel bitmap.
JR L0ADF ; Jump back to return.
L0AEC: CP '#' ; $23. Is it a sharpen?
JR NZ,L0AF3 ; Jump ahead if not.
INC C ; Increment by a semitone.
JR L0AD4 ; Jump back to get the next character.
L0AF3: CP '$' ; $24. Is it a flatten?
JR NZ,L0AFA ; Jump ahead if not.
DEC C ; Decrement by a semitone.
JR L0AD4 ; Jump back to get the next character.
L0AFA: BIT 5,A ; Is it a lower case letter?
JR NZ,L0B04 ; Jump ahead if lower case.
PUSH AF ; It is an upper case letter so
LD A,$0C ; increase an octave
ADD A,C ; by adding 12 semitones.
LD C,A ;
POP AF ;
L0B04: AND $DF ; Convert to upper case.
SUB $41 ; Reduce to range 'A'->0 .. 'G'->6.
JP C,L0F22 ; Jump if below 'A' to produce error report "k Invalid note name".
CP $07 ; Is it 7 or above?
JP NC,L0F22 ; Jump if so to produce error report "k Invalid note name".
PUSH BC ; C=Number of semitones.
LD B,$00 ;
LD C,A ; BC holds 0..6 for 'a'..'g'.
LD HL,L0DF9 ; Look up the number of semitones above note C for the note.
ADD HL,BC ;
LD A,(HL) ; A=Number of semitones above note C.
POP BC ; C=Number of semitones due to sharpen/flatten characters.
ADD A,C ; Adjust number of semitones above note C for the sharpen/flatten characters.
POP HL ; Restore HL.
RET ;
; ----------------------------------
; Get Numeric Value from Play String
; ----------------------------------
; Get a numeric value from a PLAY string, returning 0 if no numeric value present.
; Entry: IX=Address of the channel data block.
; Exit : BC=Numeric value, or 0 if no numeric value found.
L0B1D: PUSH HL ; Save registers.
PUSH DE ;
LD L,(IX+$06) ; Get the pointer into the PLAY string.
LD H,(IX+$07) ;
LD DE,$0000 ; Initialise result to 0.
L0B28: LD A,(HL) ;
CP '0' ; $30. Is character numeric?
JR C,L0B45 ; Jump ahead if not.
CP ':' ; $3A. Is character numeric?
JR NC,L0B45 ; Jump ahead if not.
INC HL ; Advance to the next character.
PUSH HL ; Save the pointer into the string.
CALL L0B50 ; Multiply result so far by 10.
SUB '0' ; $30. Convert ASCII digit to numeric value.
LD H,$00 ;
LD L,A ; HL=Numeric digit value.
ADD HL,DE ; Add the numeric value to the result so far.
JR C,L0B42 ; Jump ahead if an overflow to produce error report "l number too big".
EX DE,HL ; Transfer the result into DE.
POP HL ; Retrieve the pointer into the string.
JR L0B28 ; Loop back to handle any further numeric digits.
L0B42: JP L0F1A ; Jump to produce error report "l number too big".
; [Could have saved 1 byte by directly using JP C,L0F1A (ROM 0) instead of using this JP and
; the two JR C,L0B42 (ROM 0) instructions that come here]
;The end of the numeric value was reached
L0B45: LD (IX+$06),L ; Store the new pointer position into the string.
LD (IX+$07),H ;
PUSH DE ;
POP BC ; Return the result in BC.
POP DE ; Restore registers.
POP HL ;
RET ;
; -----------------
; Multiply DE by 10
; -----------------
; Entry: DE=Value to multiple by 10.
; Exit : DE=Value*10.
L0B50: LD HL,$0000 ;
LD B,$0A ; Add DE to HL ten times.
L0B55: ADD HL,DE ;
JR C,L0B42 ; Jump ahead if an overflow to produce error report "l number too big".
DJNZ L0B55 ;
EX DE,HL ; Transfer the result into DE.
RET ;
; ----------------------------------
; Find Next Note from Channel String
; ----------------------------------
; Entry: IX=Address of channel data block.
L0B5C: CALL L0A3E ; Test for BREAK being pressed.
JR C,L0B69 ; Jump ahead if not pressed.
CALL L0E93 ; Turn off all sound and restore IY.
EI ; Re-enable interrupts.
CALL L05AC ; Produce error report. [Could have saved 1 byte by using JP L05D6 (ROM 0)]
DEFB $14 ; "L Break into program"
L0B69: CALL L0AC5 ; Get the current character from the PLAY string, and advance the position pointer.
JP C,L0DA2 ; Jump if at the end of the string.
CALL L0DF0 ; Find the handler routine for the PLAY command character.
LD B,$00 ;
SLA C ; Generate the offset into the
LD HL,L0DCA ; command vector table.
ADD HL,BC ; HL points to handler routine for this command character.
LD E,(HL) ;
INC HL ;
LD D,(HL) ; Fetch the handler routine address.
EX DE,HL ; HL=Handler routine address for this command character.
CALL L0B84 ; Make an indirect call to the handler routine.
JR L0B5C ; Jump back to handle the next character in the string.
;Comes here after processing a non-numeric digit that does not have a specific command routine handler
;Hence the next note to play has been determined and so a return is made to process the other channels.
L0B83: RET ; Just make a return.
L0B84: JP (HL) ; Jump to the command handler routine.
; --------------------------
; Play Command '!' (Comment)
; --------------------------
; A comment is enclosed within exclamation marks, e.g. "! A comment !".
; Entry: IX=Address of the channel data block.
L0B85: CALL L0AC5 ; Get the current character from the PLAY string, and advance the position pointer.
JP C,L0DA1 ; Jump if at the end of the string.
CP '!' ; $21. Is it the end-of-comment character?
RET Z ; Return if it is.
JR L0B85 ; Jump back to test the next character.
; -------------------------
; Play Command 'O' (Octave)
; -------------------------
; The 'O' command is followed by a numeric value within the range 0 to 8,
; although due to loose range checking the value MOD 256 only needs to be
; within 0 to 8. Hence O256 operates the same as O0.
; Entry: IX=Address of the channel data block.
L0B90: CALL L0B1D ; Get following numeric value from the string into BC.
LD A,C ; Is it between 0 and 8?
CP $09 ;
JP NC,L0F12 ; Jump if above 8 to produce error report "n Out of range".
SLA A ; Multiply A by 12.
SLA A ;
LD B,A ;
SLA A ;
ADD A,B ;
LD (IX+$03),A ; Store the octave value.
RET ;
; ----------------------------
; Play Command 'N' (Separator)
; ----------------------------
; The 'N' command is simply a separator marker and so is ignored.
; Entry: IX=Address of the channel data block.
L0BA5: RET ; Nothing to do so make an immediate return.
; ----------------------------------
; Play Command '(' (Start of Repeat)
; ----------------------------------
; A phrase can be enclosed within brackets causing it to be repeated, i.e. played twice.
; Entry: IX=Address of the channel data block.
L0BA6: LD A,(IX+$0B) ; A=Current level of open bracket nesting.
INC A ; Increment the count.
CP $05 ; Only 4 levels supported.
JP Z,L0F2A ; Jump if this is the fifth to produce error report "d Too many brackets".
LD (IX+$0B),A ; Store the new open bracket nesting level.
LD DE,$000C ; Offset to the bracket level return position stores.
CALL L0C27 ; HL=Address of the pointer in which to store the return location of the bracket.
LD A,(IX+$06) ; Store the current string position as the return address of the open bracket.
LD (HL),A ;
INC HL ;
LD A,(IX+$07) ;
LD (HL),A ;
RET ;
; --------------------------------
; Play Command ')' (End of Repeat)
; --------------------------------
; A phrase can be enclosed within brackets causing it to be repeated, i.e. played twice.
; Brackets can also be nested within each other, to 4 levels deep.
; If a closing bracket if used without a matching opening bracket then the whole string up
; until that point is repeated indefinitely.
; Entry: IX=Address of the channel data block.
L0BC2: LD A,(IX+$16) ; Fetch the nesting level of closing brackets.
LD DE,$0017 ; Offset to the closing bracket return address store.
OR A ; Is there any bracket nesting so far?
JP M,L0BF0 ; Jump if none. [Could have been faster by jumping to L0BF3 (ROM 0)]
;Has the bracket level been repeated, i.e. re-reached the same position in the string as the closing bracket return address?
CALL L0C27 ; HL=Address of the pointer to the corresponding closing bracket return address store.
LD A,(IX+$06) ; Fetch the low byte of the current address.
CP (HL) ; Re-reached the closing bracket?
JR NZ,L0BF0 ; Jump ahead if not.
INC HL ; Point to the high byte.
LD A,(IX+$07) ; Fetch the high byte address of the current address.
CP (HL) ; Re-reached the closing bracket?
JR NZ,L0BF0 ; Jump ahead if not.
;The bracket level has been repeated. Now check whether this was the outer bracket level.
DEC (IX+$16) ; Decrement the closing bracket nesting level since this level has been repeated.
LD A,(IX+$16) ; [There is no need for the LD A,(IX+$16) and OR A instructions since the DEC (IX+$16) already set the flags]
OR A ; Reached the outer bracket nesting level?
RET P ; Return if not the outer bracket nesting level such that the character
; after the closing bracket is processed next.
;The outer bracket level has been repeated
BIT 0,(IX+$0A) ; Was this a single closing bracket?
RET Z ; Return if it was not.
;The repeat was caused by a single closing bracket so re-initialise the repeat
LD (IX+$16),$00 ; Restore one level of closing bracket nesting.
XOR A ; Select closing bracket nesting level 0.
JR L0C0B ; Jump ahead to continue.
;A new level of closing bracket nesting
L0BF0: LD A,(IX+$16) ; Fetch the nesting level of closing brackets.
INC A ; Increment the count.
CP $05 ; Only 5 levels supported (4 to match up with opening brackets and a 5th to repeat indefinitely).
JP Z,L0F2A ; Jump if this is the fifth to produce error report "d Too many brackets".
LD (IX+$16),A ; Store the new closing bracket nesting level.
CALL L0C27 ; HL=Address of the pointer to the appropriate closing bracket return address store.
LD A,(IX+$06) ; Store the current string position as the return address for the closing bracket.
LD (HL),A ;
INC HL ;
LD A,(IX+$07) ;
LD (HL),A ;
LD A,(IX+$0B) ; Fetch the nesting level of opening brackets.
L0C0B: LD DE,$000C ;
CALL L0C27 ; HL=Address of the pointer to the opening bracket nesting level return address store.
LD A,(HL) ; Set the return address of the nesting level's opening bracket
LD (IX+$06),A ; as new current position within the string.
INC HL ;
LD A,(HL) ; For a single closing bracket only, this will be the start address of the string.
LD (IX+$07),A ;
DEC (IX+$0B) ; Decrement level of open bracket nesting.
RET P ; Return if the closing bracket matched an open bracket.
;There is one more closing bracket then opening brackets, i.e. repeat string indefinitely
LD (IX+$0B),$00 ; Set the opening brackets nesting level to 0.
SET 0,(IX+$0A) ; Signal a single closing bracket only, i.e. to repeat the string indefinitely.
RET ;
; ------------------------------------
; Get Address of Bracket Pointer Store
; ------------------------------------
; Entry: IX=Address of the channel data block.
; DE=Offset to the bracket pointer stores.
; A=Index into the bracket pointer stores.
; Exit : HL=Address of the specified pointer store.
L0C27: PUSH IX ;
POP HL ; HL=IX.
ADD HL,DE ; HL=IX+DE.
LD B,$00 ;
LD C,A ;
SLA C ;
ADD HL,BC ; HL=IX+DE+2*A.
RET ;
; ------------------------
; Play Command 'T' (Tempo)
; ------------------------
; A temp command must be specified in the first play string and is followed by a numeric
; value in the range 60 to 240 representing the number of beats (crotchets) per minute.
; Entry: IX=Address of the channel data block.
L0C32: CALL L0B1D ; Get following numeric value from the string into BC.
LD A,B ;
OR A ;
JP NZ,L0F12 ; Jump if 256 or above to produce error report "n Out of range".
LD A,C ;
CP $3C ;
JP C,L0F12 ; Jump if 59 or below to produce error report "n Out of range".
CP $F1 ;
JP NC,L0F12 ; Jump if 241 or above to produce error report "n Out of range".
;A holds a value in the range 60 to 240
LD A,(IX+$02) ; Fetch the channel number.
OR A ; Tempo 'T' commands have to be specified in the first string.
RET NZ ; If it is in a later string then ignore it.
LD B,$00 ; [Redundant instruction - B is already zero]
PUSH BC ; C=Tempo value.
POP HL ;
ADD HL,HL ;
ADD HL,HL ; HL=Tempo*4.
PUSH HL ;
POP BC ; BC=Tempo*4. [Would have been quicker to use the combination LD B,H and LD C,L]
PUSH IY ; Save the pointer to the play command data block.
RST 28H ;
DEFW STACK_BC ; $2D2B. Place the contents of BC onto the stack. The call restores IY to $5C3A.
DI ; Interrupts get re-enabled by the call mechanism to ROM 1 so disable them again.
POP IY ; Restore IY to point at the play command data block.
PUSH IY ; Save the pointer to the play command data block.
PUSH IY ;
POP HL ; HL=pointer to the play command data block.
LD BC,$002B ;
ADD HL,BC ; HL =IY+$002B.
LD IY,$5C3A ; Reset IY to $5C3A since this is required by the floating point calculator.
PUSH HL ; HL=Points to the calculator RAM routine.
LD HL,L0C76 ;
LD (RETADDR),HL ; $5B5A. Set up the return address.
LD HL,YOUNGER ;
EX (SP),HL ; Stack the address of the swap routine used when returning to this ROM.
PUSH HL ; Re-stack the address of the calculator RAM routine.
JP SWAP ; $5B00. Toggle to other ROM and make a return to the calculator RAM routine.
; --------------------
; Tempo Command Return
; --------------------
; The calculator stack now holds the value (10/(Tempo*4))/7.33e-6 and this is stored as the tempo value.
; The result is used an inner loop counter in the wait routine at $0F76 (ROM 0). Each iteration of this loop
; takes 26 T-states. The time taken by 26 T-states is 7.33e-6 seconds. So the total time for the loop
; to execute is 2.5/TEMPO seconds.
L0C76: DI ; Interrupts get re-enabled by the call mechanism to ROM 1 so disable them again.
RST 28H ;
DEFW FP_TO_BC ; $2DA2. Fetch the value on the top of the calculator stack.
DI ; Interrupts get re-enabled by the call mechanism to ROM 1 so disable them again.
POP IY ; Restore IY to point at the play command data block.
LD (IY+$27),C ; Store tempo timing value.
LD (IY+$28),B ;
RET ;
; ------------------------
; Play Command 'M' (Mixer)
; ------------------------
; This command is used to select whether to use tone and/or noise on each of the 3 channels.
; It is followed by a numeric value in the range 1 to 63, although due to loose range checking the
; value MOD 256 only needs to be within 0 to 63. Hence M256 operates the same as M0.
; Entry: IX=Address of the channel data block.
L0C84: CALL L0B1D ; Get following numeric value from the string into BC.
LD A,C ; A=Mixer value.
CP $40 ; Is it 64 or above?
JP NC,L0F12 ; Jump if so to produce error report "n Out of range".
;Bit 0: 1=Enable channel A tone.
;Bit 1: 1=Enable channel B tone.
;Bit 2: 1=Enable channel C tone.
;Bit 3: 1=Enable channel A noise.
;Bit 4: 1=Enable channel B noise.
;Bit 5: 1=Enable channel C noise.
CPL ; Invert the bits since the sound generator's mixer register uses active low enable.
; This also sets bit 6 1, which selects the I/O port as an output.
LD E,A ; E=Mixer value.
LD D,$07 ; D=Register 7 - Mixer.
CALL L0E7C ; Write to sound generator register to set the mixer.
RET ; [Could have saved 1 byte by using JP L0E7C (ROM 0)]
; -------------------------
; Play Command 'V' (Volume)
; -------------------------
; This sets the volume of a channel and is followed by a numeric value in the range
; 0 (minimum) to 15 (maximum), although due to loose range checking the value MOD 256
; only needs to be within 0 to 15. Hence V256 operates the same as V0.
; Entry: IX=Address of the channel data block.
L0C95: CALL L0B1D ; Get following numeric value from the string into BC.
LD A,C ;
CP $10 ; Is it 16 or above?
JP NC,L0F12 ; Jump if so to produce error report "n Out of range".
LD (IX+$04),A ; Store the volume level.
; [*BUG* - An attempt to set the volume for a sound chip channel is now made. However, this routine fails to take into account
; that it is also called to set the volume for a MIDI only channel, i.e. play strings 4 to 8. As a result, corruption
; occurs to various sound generator registers, causing spurious sound output. There is in fact no need for this routine
; to set the volume for any channels since this is done every time a new note is played - see routine at $0A97 (ROM 0).
; the bug fix is to simply to make a return at this point. This routine therefore contains 11 surplus bytes. Credit: Ian Collier (+3), Paul Farrow (128)]
LD E,(IX+$02) ; E=Channel number.
LD A,$08 ; Offset by 8.
ADD A,E ; A=8+index.
LD D,A ; D=Sound generator register number for the channel.
LD E,C ; E=Volume level.
CALL L0E7C ; Write to sound generator register to set the volume for the channel.
RET ; [Could have saved 1 byte by using JP L0E7C (ROM 0)]
; ------------------------------------
; Play Command 'U' (Use Volume Effect)
; ------------------------------------
; This command turns on envelope waveform effects for a particular sound chip channel. The volume level is now controlled by
; the selected envelope waveform for the channel, as defined by the 'W' command. MIDI channels do not support envelope waveforms
; and so the routine has the effect of setting the volume of a MIDI channel to maximum, i.e. 15. It might seem odd that the volume
; for MIDI channels is set to 15 rather than just filtered out. However, the three sound chip channels can also drive three MIDI
; channels and so it would be inconsistent for these MIDI channels to have their volume set to 15 but have the other MIDI channels
; behave differently. However, it could be argued that all MIDI channels should be unaffected by the 'U' command.
; There are no parameters to this command.
; Entry: IX=Address of the channel data block.
L0CAD: LD E,(IX+$02) ; Get the channel number.
LD A,$08 ; Offset by 8.
ADD A,E ; A=8+index.
LD D,A ; D=Sound generator register number for the channel. [This is not used and so there is no need to generate it. It was probably a left
; over from copying and modifying the 'V' command routine. Deleting it would save 7 bytes. Credit: Ian Collier (+3), Paul Farrow (128)]
LD E,$1F ; E=Select envelope defined by register 13, and reset volume bits to maximum (though these are not used with the envelope).
LD (IX+$04),E ; Store that the envelope is being used (along with the reset volume level).
RET ;
; ------------------------------------------
; Play command 'W' (Volume Effect Specifier)
; ------------------------------------------
; This command selects the envelope waveform to use and is followed by a numeric value in the range
; 0 to 7, although due to loose range checking the value MOD 256 only needs to be within 0 to 7.
; Hence W256 operates the same as W0.
; Entry: IX=Address of the channel data block.
L0CBA: CALL L0B1D ; Get following numeric value from the string into BC.
LD A,C ;
CP $08 ; Is it 8 or above?
JP NC,L0F12 ; Jump if so to produce error report "n Out of range".
LD B,$00 ;
LD HL,L0DE8 ; Envelope waveform lookup table.
ADD HL,BC ; HL points to the corresponding value in the table.
LD A,(HL) ;
LD (IY+$29),A ; Store new effect waveform value.
RET ;
; -----------------------------------------
; Play Command 'X' (Volume Effect Duration)
; -----------------------------------------
; This command allows the duration of a waveform effect to be specified, and is followed by a numeric
; value in the range 0 to 65535. A value of 1 corresponds to the minimum duration, increasing up to 65535
; and then maximum duration for a value of 0. If no numeric value is specified then the maximum duration is used.
; Entry: IX=Address of the channel data block.
L0CCE: CALL L0B1D ; Get following numeric value from the string into BC.
LD D,$0B ; Register 11 - Envelope Period Fine.
LD E,C ;
CALL L0E7C ; Write to sound generator register to set the envelope period (low byte).
INC D ; Register 12 - Envelope Period Coarse.
LD E,B ;
CALL L0E7C ; Write to sound generator register to set the envelope period (high byte).
RET ; [Could have saved 1 byte by using JP L0E7C (ROM 0)]
; -------------------------------
; Play Command 'Y' (MIDI Channel)
; -------------------------------
; This command sets the MIDI channel number that the string is assigned to and is followed by a numeric
; value in the range 1 to 16, although due to loose range checking the value MOD 256 only needs to be within 1 to 16.
; Hence Y257 operates the same as Y1.
; Entry: IX=Address of the channel data block.
L0CDD: CALL L0B1D ; Get following numeric value from the string into BC.
LD A,C ;
DEC A ; Is it 0?
JP M,L0F12 ; Jump if so to produce error report "n Out of range".
CP $10 ; Is it 10 or above?
JP NC,L0F12 ; Jump if so to produce error report "n Out of range".
LD (IX+$01),A ; Store MIDI channel number that this string is assigned to.
RET ;
; ----------------------------------------
; Play Command 'Z' (MIDI Programming Code)
; ----------------------------------------
; This command is used to send a programming code to the MIDI port. It is followed by a numeric
; value in the range 0 to 255, although due to loose range checking the value MOD 256 only needs
; to be within 0 to 255. Hence Z256 operates the same as Z0.
; Entry: IX=Address of the channel data block.
L0CEE: CALL L0B1D ; Get following numeric value from the string into BC.
LD A,C ; A=(low byte of) the value.
CALL L11A3 ; Write byte to MIDI device.
RET ; [Could have saved 1 byte by using JP L0E7C (ROM 0)]
; -----------------------
; Play Command 'H' (Stop)
; -----------------------
; This command stops further processing of a play command. It has no parameters.
; Entry: IX=Address of the channel data block.
L0CF6: LD (IY+$10),$FF ; Indicate no channels to play, thereby causing
RET ; the play command to terminate.
; --------------------------------------------------------
; Play Commands 'a'..'g', 'A'..'G', '1'.."12", '&' and '_'
; --------------------------------------------------------
; This handler routine processes commands 'a'..'g', 'A'..'G', '1'.."12", '&' and '_',
; and determines the length of the next note to play. It provides the handling of triplet and tied notes.
; It stores the note duration in the channel data block's duration length entry, and sets a pointer in the command
; data block's duration lengths pointer table to point at it. A single note letter is deemed to be a tied
; note count of 1. Triplets are deemed a tied note count of at least 2.
; Entry: IX=Address of the channel data block.
; A=Current character from play string.
L0CFB: CALL L0E19 ; Is the current character a number?
JP C,L0D81 ; Jump if not number digit.
;The character is a number digit
CALL L0DAC ; HL=Address of the duration length within the channel data block.
CALL L0DB4 ; Store address of duration length in command data block's channel duration length pointer table.
XOR A ;
LD (IX+$21),A ; Set no tied notes.
CALL L0EC8 ; Get the previous character in the string, the note duration.
CALL L0B1D ; Get following numeric value from the string into BC.
LD A,C ;
OR A ; Is the value 0?
JP Z,L0F12 ; Jump if so to produce error report "n Out of range".
CP $0D ; Is it 13 or above?
JP NC,L0F12 ; Jump if so to produce error report "n Out of range".
CP $0A ; Is it below 10?
JR C,L0D32 ; Jump if so.
;It is a triplet semi-quaver (10), triplet quaver (11) or triplet crotchet (12)
CALL L0E00 ; DE=Note duration length for the duration value.
CALL L0D74 ; Increment the tied notes counter.
LD (HL),E ; HL=Address of the duration length within the channel data block.
INC HL ;
LD (HL),D ; Store the duration length.
L0D28: CALL L0D74 ; Increment the counter of tied notes.
INC HL ;
LD (HL),E ;
INC HL ; Store the subsequent note duration length in the channel data block.
LD (HL),D ;
INC HL ;
JR L0D38 ; Jump ahead to continue.
;The note duration was in the range 1 to 9
L0D32: LD (IX+$05),C ; C=Note duration value (1..9).
CALL L0E00 ; DE=Duration length for this duration value.
L0D38: CALL L0D74 ; Increment the tied notes counter.
L0D3B: CALL L0EE3 ; Get the current character from the play string for this channel.
CP '_' ; $5F. Is it a tied note?
JR NZ,L0D6E ; Jump ahead if not.
CALL L0AC5 ; Get the current character from the PLAY string, and advance the position pointer.
CALL L0B1D ; Get following numeric value from the string into BC.
LD A,C ; Place the value into A.
CP $0A ; Is it below 10?
JR C,L0D5F ; Jump ahead for 1 to 9 (semiquaver ... semibreve).
;A triplet note was found as part of a tied note
PUSH HL ; HL=Address of the duration length within the channel data block.
PUSH DE ; DE=First tied note duration length.
CALL L0E00 ; DE=Note duration length for this new duration value.
POP HL ; HL=Current tied note duration length.
ADD HL,DE ; HL=Current+new tied note duration lengths.
LD C,E ;
LD B,D ; BC=Note duration length for the duration value.
EX DE,HL ; DE=Current+new tied note duration lengths.
POP HL ; HL=Address of the duration length within the channel data block.
LD (HL),E ;
INC HL ;
LD (HL),D ; Store the combined note duration length in the channel data block.
LD E,C ;
LD D,B ; DE=Note duration length for the second duration value.
JR L0D28 ; Jump back.
;A non-triplet tied note
L0D5F: LD (IX+$05),C ; Store the note duration value.
PUSH HL ; HL=Address of the duration length within the channel data block.
PUSH DE ; DE=First tied note duration length.
CALL L0E00 ; DE=Note duration length for this new duration value.
POP HL ; HL=Current tied note duration length.
ADD HL,DE ; HL=Current+new tied not duration lengths.
EX DE,HL ; DE=Current+new tied not duration lengths.
POP HL ; HL=Address of the duration length within the channel data block.
JP L0D3B ; Jump back to process the next character in case it is also part of a tied note.
;The number found was not part of a tied note, so store the duration value
L0D6E: LD (HL),E ; HL=Address of the duration length within the channel data block.
INC HL ; (For triplet notes this could be the address of the subsequent note duration length)
LD (HL),D ; Store the duration length.
JP L0D9C ; Jump forward to make a return.
; This subroutine is called to increment the tied notes counter
L0D74: LD A,(IX+$21) ; Increment counter of tied notes.
INC A ;
CP $0B ; Has it reached 11?
JP Z,L0F3A ; Jump if so to produce to error report "o too many tied notes".
LD (IX+$21),A ; Store the new tied notes counter.
RET ;
;The character is not a number digit so is 'A'..'G', '&' or '_'
L0D81: CALL L0EC8 ; Get the previous character from the string.
LD (IX+$21),$01 ; Set the number of tied notes to 1.
;Store a pointer to the channel data block's duration length into the command data block
CALL L0DAC ; HL=Address of the duration length within the channel data block.
CALL L0DB4 ; Store address of duration length in command data block's channel duration length pointer table.
LD C,(IX+$05) ; C=The duration value of the note (1 to 9).
PUSH HL ; [Not necessary]
CALL L0E00 ; Find the duration length for the note duration value.
POP HL ; [Not necessary]
LD (HL),E ; Store it in the channel data block.
INC HL ;
LD (HL),D ;
JP L0D9C ; Jump to the instruction below. [Redundant instruction]
L0D9C: POP HL ;
INC HL ;
INC HL ; Modify the return address to point to the RET instruction at $0B83 (ROM 0).
PUSH HL ;
RET ; [Over elaborate when a simple POP followed by RET would have sufficed, saving 3 bytes]
; -------------------
; End of String Found
; -------------------
;This routine is called when the end of string is found within a comment. It marks the
;string as having been processed and then returns to the main loop to process the next string.
L0DA1: POP HL ; Drop the return address of the call to the comment command.
;Enter here if the end of the string is found whilst processing a string.
L0DA2: LD A,(IY+$21) ; Fetch the channel selector.
OR (IY+$10) ; Clear the channel flag for this string.
LD (IY+$10),A ; Store the new channel bitmap.
RET ;
; --------------------------------------------------
; Point to Duration Length within Channel Data Block
; --------------------------------------------------
; Entry: IX=Address of the channel data block.
; Exit : HL=Address of the duration length within the channel data block.
L0DAC: PUSH IX ;
POP HL ; HL=Address of the channel data block.
LD BC,$0022 ;
ADD HL,BC ; HL=Address of the store for the duration length.
RET ;
; -------------------------------------------------------------------------
; Store Entry in Command Data Block's Channel Duration Length Pointer Table
; -------------------------------------------------------------------------
; Entry: IY=Address of the command data block.
; IX=Address of the channel data block for the current string.
; HL=Address of the duration length store within the channel data block.
; Exit : HL=Address of the duration length store within the channel data block.
; DE=Channel duration.
L0DB4: PUSH HL ; Save the address of the duration length within the channel data block.
PUSH IY ;
POP HL ; HL=Address of the command data block.
LD BC,$0011 ;
ADD HL,BC ; HL=Address within the command data block of the channel duration length pointer table.
LD B,$00 ;
LD C,(IX+$02) ; BC=Channel number.
SLA C ; BC=2*Index number.
ADD HL,BC ; HL=Address within the command data block of the pointer to the current channel's data block duration length.
POP DE ; DE=Address of the duration length within the channel data block.
LD (HL),E ; Store the pointer to the channel duration length in the command data block's channel duration pointer table.
INC HL ;
LD (HL),D ;
EX DE,HL ;
RET ;
; -----------------------
; PLAY Command Jump Table
; -----------------------
; Handler routine jump table for all PLAY commands.
L0DCA: DEFW L0CFB ; Command handler routine for all other characters.
DEFW L0B85 ; '!' command handler routine.
DEFW L0B90 ; 'O' command handler routine.
DEFW L0BA5 ; 'N' command handler routine.
DEFW L0BA6 ; '(' command handler routine.
DEFW L0BC2 ; ')' command handler routine.
DEFW L0C32 ; 'T' command handler routine.
DEFW L0C84 ; 'M' command handler routine.
DEFW L0C95 ; 'V' command handler routine.
DEFW L0CAD ; 'U' command handler routine.
DEFW L0CBA ; 'W' command handler routine.
DEFW L0CCE ; 'X' command handler routine.
DEFW L0CDD ; 'Y' command handler routine.
DEFW L0CEE ; 'Z' command handler routine.
DEFW L0CF6 ; 'H' command handler routine.
; ------------------------------
; Envelope Waveform Lookup Table
; ------------------------------
; Table used by the play 'W' command to find the corresponding envelope value
; to write to the sound generator envelope shape register (register 13). This
; filters out the two duplicate waveforms possible from the sound generator and
; allows the order of the waveforms to be arranged in a more logical fashion.
L0DE8: DEFB $00 ; W0 - Single decay then off. (Continue off, attack off, alternate off, hold off)
DEFB $04 ; W1 - Single attack then off. (Continue off, attack on, alternate off, hold off)
DEFB $0B ; W2 - Single decay then hold. (Continue on, attack off, alternate on, hold on)
DEFB $0D ; W3 - Single attack then hold. (Continue on, attack on, alternate off, hold on)
DEFB $08 ; W4 - Repeated decay. (Continue on, attack off, alternate off, hold off)
DEFB $0C ; W5 - Repeated attack. (Continue on, attack on, alternate off, hold off)
DEFB $0E ; W6 - Repeated attack-decay. (Continue on, attack on, alternate on, hold off)
DEFB $0A ; W7 - Repeated decay-attack. (Continue on, attack off, alternate on, hold off)
; --------------------------
; Identify Command Character
; --------------------------
; This routines attempts to match the command character to those in a table.
; The index position of the match indicates which command handler routine is required
; to process the character. Note that commands are case sensitive.
; Entry: A=Command character.
; Exit : Zero flag set if a match was found.
; BC=Indentifying the character matched, 1 to 15 for match and 0 for no match.
L0DF0: LD BC,$000F ; Number of characters + 1 in command table.
LD HL,L0AB7 ; Start of command table.
CPIR ; Search for a match.
RET ;
; ---------------
; Semitones Table
; ---------------
; This table contains an entry for each note of the scale, A to G,
; and is the number of semitones above the note C.
L0DF9: DEFB $09 ; 'A'
DEFB $0B ; 'B'
DEFB $00 ; 'C'
DEFB $02 ; 'D'
DEFB $04 ; 'E'
DEFB $05 ; 'F'
DEFB $07 ; 'G'
; -------------------------
; Find Note Duration Length
; -------------------------
; Entry: C=Duration value (0 to 12, although a value of 0 is never used).
; Exit : DE=Note duration length.
L0E00: PUSH HL ; Save HL.
LD B,$00 ;
LD HL,L0E0C ; Note duration table.
ADD HL,BC ; Index into the table.
LD D,$00 ;
LD E,(HL) ; Fetch the length from the table.
POP HL ; Restore HL.
RET ;
; -------------------
; Note Duration Table
; -------------------
; A whole note is given by a value of 96d and other notes defined in relation to this.
; The value of 96d is the lowest common denominator from which all note durations
; can be defined.
L0E0C: DEFB $80 ; Rest [Not used since table is always indexed into with a value of 1 or more]
DEFB $06 ; Semi-quaver (sixteenth note).
DEFB $09 ; Dotted semi-quaver (3/32th note).
DEFB $0C ; Quaver (eighth note).
DEFB $12 ; Dotted quaver (3/16th note).
DEFB $18 ; Crotchet (quarter note).
DEFB $24 ; Dotted crotchet (3/8th note).
DEFB $30 ; Minim (half note).
DEFB $48 ; Dotted minim (3/4th note).
DEFB $60 ; Semi-breve (whole note).
DEFB $04 ; Triplet semi-quaver (1/24th note).
DEFB $08 ; Triplet quaver (1/12th note).
DEFB $10 ; Triplet crochet (1/6th note).
; -----------------
; Is Numeric Digit?
; -----------------
; Tests whether a character is a number digit.
; Entry: A=Character.
; Exit : Carry flag reset if a number digit.
L0E19: CP '0' ; $30. Is it '0' or less?
RET C ; Return with carry flag set if so.
CP ':' ; $3A. Is it more than '9'?
CCF ;
RET ; Return with carry flag set if so.
; -----------------------------------
; Play a Note On a Sound Chip Channel
; -----------------------------------
; This routine plays the note at the current octave and current volume on a sound chip channel. For play strings 4 to 8,
; it simply stores the note number and this is subsequently played later.
; Entry: IX=Address of the channel data block.
; A=Note value as number of semitones above C (0..11).
L0E20: LD C,A ; C=The note value.
LD A,(IX+$03) ; Octave number * 12.
ADD A,C ; Add the octave number and the note value to form the note number.
CP $80 ; Is note within range?
JP NC,L0F32 ; Jump if not to produce error report "m Note out of range".
LD C,A ; C=Note number.
LD A,(IX+$02) ; Get the channel number.
OR A ; Is it the first channel?
JR NZ,L0E3F ; Jump ahead if not.
;Only set the noise generator frequency on the first channel
LD A,C ; A=Note number (0..107), in ascending audio frequency.
CPL ; Invert since noise register value is in descending audio frequency.
AND $7F ; Mask off bit 7.
SRL A ;
SRL A ; Divide by 4 to reduce range to 0..31.
LD D,$06 ; Register 6 - Noise pitch.
LD E,A ;
CALL L0E7C ; Write to sound generator register.
L0E3F: LD (IX+$00),C ; Store the note number.
LD A,(IX+$02) ; Get the channel number.
CP $03 ; Is it channel 0, 1 or 2, i.e. a sound chip channel?
RET NC ; Do not output anything for play strings 4 to 8.
;Channel 0, 1 or 2
LD HL,L1096 ; Start of note lookup table.
LD B,$00 ; BC=Note number.
LD A,C ; A=Note number.
SUB $15 ; A=Note number - 21.
JR NC,L0E57 ; Jump if note number was 21 or above.
LD DE,$0FBF ; Note numbers $00 to $14 use the lowest note value.
JR L0E5E ; [Could have saved 4 bytes by using XOR A and dropping through to L0E57 (ROM 0)]
;Note number 21 to 107 (range 0 to 86)
L0E57: LD C,A ;
SLA C ; Generate offset into the table.
ADD HL,BC ; Point to the entry in the table.
LD E,(HL) ;
INC HL ;
LD D,(HL) ; DE=Word to write to the sound chip registers to produce this note.
L0E5E: EX DE,HL ; HL=Register word value to produce the note.
LD D,(IX+$02) ; Get the channel number.
SLA D ; D=2*Channel number, to give the tone channel register (fine control) number 0, 2, or 4.
LD E,L ; E=The low value byte.
CALL L0E7C ; Write to sound generator register.
INC D ; D=Tone channel register (coarse control) number 1, 3, or 5.
LD E,H ; E=The high value byte.
CALL L0E7C ; Write to sound generator register.
BIT 4,(IX+$04) ; Is the envelope waveform being used?
RET Z ; Return if it is not.
LD D,$0D ; Register 13 - Envelope Shape.
LD A,(IY+$29) ; Get the effect waveform value.
LD E,A ;
CALL L0E7C ; Write to sound generator register.
RET ; [Could have saved 4 bytes by dropping down into the routine below.]
; ----------------------------
; Set Sound Generator Register
; ----------------------------
; Entry: D=Register to write.
; E=Value to set register to.
L0E7C: PUSH BC ;
LD BC,$FFFD ;
OUT (C),D ; Select the register.
LD BC,$BFFD ;
OUT (C),E ; Write out the value.
POP BC ;
RET ;
; -----------------------------
; Read Sound Generator Register
; -----------------------------
; Entry: A=Register to read.
; Exit : A=Value of currently selected sound generator register.
L0E89: PUSH BC ;
LD BC,$FFFD ;
OUT (C),A ; Select the register.
IN A,(C) ; Read the register's value.
POP BC ;
RET ;
; ------------------
; Turn Off All Sound
; ------------------
L0E93: LD D,$07 ; Register 7 - Mixer.
LD E,$FF ; I/O ports are inputs, noise output off, tone output off.
CALL L0E7C ; Write to sound generator register.
;Turn off the sound from the AY-3-8912
LD D,$08 ; Register 8 - Channel A volume.
LD E,$00 ; Volume of 0.
CALL L0E7C ; Write to sound generator register to set the volume to 0.
INC D ; Register 9 - Channel B volume.
CALL L0E7C ; Write to sound generator register to set the volume to 0.
INC D ; Register 10 - Channel C volume.
CALL L0E7C ; Write to sound generator register to set the volume to 0.
CALL L0A4F ; Select channel data block pointers.
;Now reset all MIDI channels in use
L0EAC: RR (IY+$22) ; Working copy of channel bitmap. Test if next string present.
JR C,L0EB8 ; Jump ahead if there is no string for this channel.
CALL L0A67 ; Get address of channel data block for the current string into IX.
CALL L118D ; Turn off the MIDI channel sound assigned to this play string.
L0EB8: SLA (IY+$21) ; Have all channels been processed?
JR C,L0EC3 ; Jump ahead if so.
CALL L0A6E ; Advance to the next channel data block pointer.
JR L0EAC ; Jump back to process the next channel.
L0EC3: LD IY,$5C3A ; Restore IY.
RET ;
; ---------------------------------------
; Get Previous Character from Play String
; ---------------------------------------
; Get the previous character from the PLAY string, skipping over spaces and 'Enter' characters.
; Entry: IX=Address of the channel data block.
L0EC8: PUSH HL ; Save registers.
PUSH DE ;
LD L,(IX+$06) ; Get the current pointer into the PLAY string.
LD H,(IX+$07) ;
L0ED0: DEC HL ; Point to previous character.
LD A,(HL) ; Fetch the character.
CP ' ' ; $20. Is it a space?
JR Z,L0ED0 ; Jump back if a space.
CP $0D ; Is it an 'Enter'?
JR Z,L0ED0 ; Jump back if an 'Enter'.
LD (IX+$06),L ; Store this as the new current pointer into the PLAY string.
LD (IX+$07),H ;
POP DE ; Restore registers.
POP HL ;
RET ;
; --------------------------------------
; Get Current Character from Play String
; --------------------------------------
; Get the current character from the PLAY string, skipping over spaces and 'Enter' characters.
; Exit: Carry flag set if string has been fully processed.
; Carry flag reset if character is available.
; A=Character available.
L0EE3: PUSH HL ; Save registers.
PUSH DE ;
PUSH BC ;
LD L,(IX+$06) ; HL=Pointer to next character to process within the PLAY string.
LD H,(IX+$07) ;
L0EEC: LD A,H ;
CP (IX+$09) ; Reached end-of-string address high byte?
JR NZ,L0EFB ; Jump forward if not.
LD A,L ;
CP (IX+$08) ; Reached end-of-string address low byte?
JR NZ,L0EFB ; Jump forward if not.
SCF ; Indicate string all processed.
JR L0F05 ; Jump forward to return.
L0EFB: LD A,(HL) ; Get the next play character.
CP ' ' ; $20. Is it a space?
JR Z,L0F09 ; Ignore the space by jumping ahead to process the next character.
CP $0D ; Is it 'Enter'?
JR Z,L0F09 ; Ignore the 'Enter' by jumping ahead to process the next character.
OR A ; Clear the carry flag to indicate a new character has been returned.
L0F05: POP BC ; Restore registers.
POP DE ;
POP HL ;
RET ;
L0F09: INC HL ; Point to the next character.
LD (IX+$06),L ;
LD (IX+$07),H ; Update the pointer to the next character to process with the PLAY string.
JR L0EEC ; Jump back to get the next character.
; --------------------------
; Produce Play Error Reports
; --------------------------
L0F12: CALL L0E93 ; Turn off all sound and restore IY.
EI ;
CALL L05AC ; Produce error report.
DEFB $29 ; "n Out of range"
L0F1A: CALL L0E93 ; Turn off all sound and restore IY.
EI ;
CALL L05AC ; Produce error report.
DEFB $27 ; "l Number too big"
L0F22: CALL L0E93 ; Turn off all sound and restore IY.
EI ;
CALL L05AC ; Produce error report.
DEFB $26 ; "k Invalid note name"
L0F2A: CALL L0E93 ; Turn off all sound and restore IY.
EI ;
CALL L05AC ; Produce error report.
DEFB $1F ; "d Too many brackets"
L0F32: CALL L0E93 ; Turn off all sound and restore IY.
EI ;
CALL L05AC ; Produce error report.
DEFB $28 ; "m Note out of range"
L0F3A: CALL L0E93 ; Turn off all sound and restore IY.
EI ;
CALL L05AC ; Produce error report.
DEFB $2A ; "o Too many tied notes"
; -------------------------
; Play Note on Each Channel
; -------------------------
; Play a note and set the volume on each channel for which a play string exists.
L0F42: CALL L0A4F ; Select channel data block pointers.
L0F45: RR (IY+$22) ; Working copy of channel bitmap. Test if next string present.
JR C,L0F6C ; Jump ahead if there is no string for this channel.
CALL L0A67 ; Get address of channel data block for the current string into IX.
CALL L0AD1 ; Get the next note in the string as number of semitones above note C.
CP $80 ; Is it a rest?
JR Z,L0F6C ; Jump ahead if so and do nothing to the channel.
CALL L0E20 ; Play the note if a sound chip channel.
LD A,(IX+$02) ; Get channel number.
CP $03 ; Is it channel 0, 1 or 2, i.e. a sound chip channel?
JR NC,L0F69 ; Jump if not to skip setting the volume.
;One of the 3 sound chip generator channels so set the channel's volume for the new note
LD D,$08 ;
ADD A,D ; A=0 to 2.
LD D,A ; D=Register (8 + string index), i.e. channel A, B or C volume register.
LD E,(IX+$04) ; E=Volume for the current channel.
CALL L0E7C ; Write to sound generator register to set the output volume.
L0F69: CALL L116E ; Play a note and set the volume on the assigned MIDI channel.
L0F6C: SLA (IY+$21) ; Have all channels been processed?
RET C ; Return if so.
CALL L0A6E ; Advance to the next channel data block pointer.
JR L0F45 ; Jump back to process the next channel.
; ------------------
; Wait Note Duration
; ------------------
; This routine is the main timing control of the PLAY command.
; It waits for the specified length of time, which will be the
; lowest note duration of all active channels.
; The actual duration of the wait is dictated by the current tempo.
; Entry: DE=Note duration, where 96d represents a whole note.
;Enter a loop waiting for (135+ ((26*(tempo-100))-5) )*DE+5 T-states
L0F76: PUSH HL ; (11) Save HL.
LD L,(IY+$27) ; (19) Get the tempo timing value.
LD H,(IY+$28) ; (19)
LD BC,$0064 ; (10) BC=100
OR A ; (4)
SBC HL,BC ; (15) HL=tempo timing value - 100.
PUSH HL ; (11)
POP BC ; (10) BC=tempo timing value - 100.
POP HL ; (10) Restore HL.
;Tempo timing value = (10/(TEMPO*4))/7.33e-6, where 7.33e-6 is the time for 26 T-states.
;The loop below takes 26 T-states per iteration, where the number of iterations is given by the tempo timing value.
;So the time for the loop to execute is 2.5/TEMPO seconds.
;
;For a TEMPO of 60 beats (crotchets) per second, the time per crotchet is 1/24 second.
;The duration of a crotchet is defined as 24 from the table at $0E0C, therefore the loop will get executed 24 times
;and hence the total time taken will be 1 second.
;
;The tempo timing value above has 100 subtracted from it, presumably to approximately compensate for the overhead time
;previously taken to prepare the notes for playing. This reduces the total time by 2600 T-states, or 733us.
L0F86: DEC BC ; (6) Wait for tempo-100 loops.
LD A,B ; (4)
OR C ; (4)
JR NZ,L0F86 ; (12/7)
DEC DE ; (6) Repeat DE times
LD A,D ; (4)
OR E ; (4)
JR NZ,L0F76 ; (12/7)
RET ; (10)
; -----------------------------
; Find Smallest Duration Length
; -----------------------------
; This routine finds the smallest duration length for all current notes
; being played across all channels.
; Exit: DE=Smallest duration length.
L0F91: LD DE,$FFFF ; Set smallest duration length to 'maximum'.
CALL L0A4A ; Select channel data block duration pointers.
L0F97: RR (IY+$22) ; Working copy of channel bitmap. Test if next string present.
JR C,L0FAF ; Jump ahead if there is no string for this channel.
;HL=Address of channel data pointer. DE holds the smallest duration length found so far.
PUSH DE ; Save the smallest duration length.
LD E,(HL) ;
INC HL ;
LD D,(HL) ;
EX DE,HL ; DE=Channel data block duration length.
LD E,(HL) ;
INC HL ;
LD D,(HL) ; DE=Channel duration length.
PUSH DE ;
POP HL ; HL=Channel duration length.
POP BC ; Last channel duration length.
OR A ;
SBC HL,BC ; Is current channel's duration length smaller than the smallest so far?
JR C,L0FAF ; Jump ahead if so, with the new smallest value in DE.
;The current channel's duration was not smaller so restore the last smallest into DE.
PUSH BC ;
POP DE ; DE=Smallest duration length.
L0FAF: SLA (IY+$21) ; Have all channel strings been processed?
JR C,L0FBA ; Jump ahead if so.
CALL L0A6E ; Advance to the next channel data block duration pointer.
JR L0F97 ; Jump back to process the next channel.
L0FBA: LD (IY+$25),E ;
LD (IY+$26),D ; Store the smallest channel duration length.
RET ;
; ---------------------------------------------------------------
; Play a Note on Each Channel and Update Channel Duration Lengths
; ---------------------------------------------------------------
; This routine is used to play a note and set the volume on all channels.
; It subtracts an amount of time from the duration lengths of all currently
; playing channel note durations. The amount subtracted is equivalent to the
; smallest note duration length currently being played, and as determined earlier.
; Hence one channel's duration will go to 0 on each call of this routine, and the
; others will show the remaining lengths of their corresponding notes.
; Entry: IY=Address of the command data block.
L0FC1: XOR A ;
LD (IY+$2A),A ; Holds a temporary channel bitmap.
CALL L0A4F ; Select channel data block pointers.
L0FC8: RR (IY+$22) ; Working copy of channel bitmap. Test if next string present.
JP C,L105A ; Jump ahead if there is no string for this channel.
CALL L0A67 ; Get address of channel data block for the current string into IX.
PUSH IY ;
POP HL ; HL=Address of the command data block.
LD BC,$0011 ;
ADD HL,BC ; HL=Address of channel data block duration pointers.
LD B,$00 ;
LD C,(IX+$02) ; BC=Channel number.
SLA C ; BC=2*Channel number.
ADD HL,BC ; HL=Address of channel data block duration pointer for this channel.
LD E,(HL) ;
INC HL ;
LD D,(HL) ; DE=Address of duration length within the channel data block.
EX DE,HL ; HL=Address of duration length within the channel data block.
PUSH HL ; Save it.
LD E,(HL) ;
INC HL ;
LD D,(HL) ; DE=Duration length for this channel.
EX DE,HL ; HL=Duration length for this channel.
LD E,(IY+$25) ;
LD D,(IY+$26) ; DE=Smallest duration length of all current channel notes.
OR A ;
SBC HL,DE ; HL=Duration length - smallest duration length.
EX DE,HL ; DE=Duration length - smallest duration length.
POP HL ; HL=Address of duration length within the channel data block.
JR Z,L0FFC ; Jump if this channel uses the smallest found duration length.
LD (HL),E ;
INC HL ; Update the duration length for this channel with the remaining length.
LD (HL),D ;
JR L105A ; Jump ahead to update the next channel.
;The current channel uses the smallest found duration length
; [A note has been completed and so the channel volume is set to 0 prior to the next note being played.
; This occurs on both sound chip channels and MIDI channels. When a MIDI channel is assigned to more than
; one play string and a rest is used in one of those strings. As soon as the end of the rest period is
; encountered, the channel's volume is set to off even though one of the other play strings controlling
; the MIDI channel may still be playing. This can be seen using the command PLAY "Y1a&", "Y1N9a". Here,
; string 1 starts playing 'a' for the period of a crotchet (1/4 of a note), where as string 2 starts playing
; 'a' for nine periods of a crotchet (9/4 of a note). When string 1 completes its crotchet, it requests
; to play a period of silence via the rest '&'. This turns the volume of the MIDI channel off even though
; string 2 is still timing its way through its nine crotchets. The play command will therefore continue for
; a further seven crotchets but in silence. This is because the volume for note is set only at its start
; and no coordination occurs between strings to turn the volume back on for the second string. It is arguably
; what the correct behaviour should be in such a circumstance where the strings are providing conflicting instructions,
; but having the latest command or note take precedence seems a logical approach. Credit: Ian Collier (+3), Paul Farrow (128)]
L0FFC: LD A,(IX+$02) ; Get the channel number.
CP $03 ; Is it channel 0, 1 or 2, i.e. a sound chip channel?
JR NC,L100C ; Jump ahead if not a sound generator channel.
LD D,$08 ;
ADD A,D ;
LD D,A ; D=Register (8+channel number) - Channel volume.
LD E,$00 ; E=Volume level of 0.
CALL L0E7C ; Write to sound generator register to turn the volume off.
L100C: CALL L118D ; Turn off the assigned MIDI channel sound.
PUSH IX ;
POP HL ; HL=Address of channel data block.
LD BC,$0021 ;
ADD HL,BC ; HL=Points to the tied notes counter.
DEC (HL) ; Decrement the tied notes counter. [This contains a value of 1 for a single note]
JR NZ,L1026 ; Jump ahead if there are more tied notes.
CALL L0B5C ; Find the next note to play for this channel from its play string.
LD A,(IY+$21) ; Fetch the channel selector.
AND (IY+$10) ; Test whether this channel has further data in its play string.
JR NZ,L105A ; Jump to process the next channel if this channel does not have a play string.
JR L103D ; The channel has more data in its play string so jump ahead.
;The channel has more tied notes
L1026: PUSH IY ;
POP HL ; HL=Address of the command data block.
LD BC,$0011 ;
ADD HL,BC ; HL=Address of channel data block duration pointers.
LD B,$00 ;
LD C,(IX+$02) ; BC=Channel number.
SLA C ; BC=2*Channel number.
ADD HL,BC ; HL=Address of channel data block duration pointer for this channel.
LD E,(HL) ;
INC HL ;
LD D,(HL) ; DE=Address of duration length within the channel data block.
INC DE ;
INC DE ; Point to the subsequent note duration length.
LD (HL),D ;
DEC HL ;
LD (HL),E ; Store the new duration length.
L103D: CALL L0AD1 ; Get next note in the string as number of semitones above note C.
LD C,A ; C=Number of semitones.
LD A,(IY+$21) ; Fetch the channel selector.
AND (IY+$10) ; Test whether this channel has a play string.
JR NZ,L105A ; Jump to process the next channel if this channel does not have a play string.
LD A,C ; A=Number of semitones.
CP $80 ; Is it a rest?
JR Z,L105A ; Jump to process the next channel if it is.
CALL L0E20 ; Play the new note on this channel at the current volume if a sound chip channel, or simply store the note for play strings 4 to 8.
LD A,(IY+$21) ; Fetch the channel selector.
OR (IY+$2A) ; Insert a bit in the temporary channel bitmap to indicate this channel has more to play.
LD (IY+$2A),A ; Store it.
;Check whether another channel needs its duration length updated
L105A: SLA (IY+$21) ; Have all channel strings been processed?
JR C,L1066 ; Jump ahead if so.
CALL L0A6E ; Advance to the next channel data pointer.
JP L0FC8 ; Jump back to update the duration length for the next channel.
; [*BUG* - By this point, the volume for both sound chip and MIDI channels has been set to 0, i.e. off. So although the new notes have been
; set playing on the sound chip channels, no sound is audible. For MIDI channels, no new notes have yet been output and hence these
; are also silent. If the time from turning the volume off for the current note to the time to turn the volume on for the next note
; is short enough, then it will not be noticeable. However, the code at $1066 (ROM 0) introduces a 1/96th of a note delay and as a result a
; 1/96th of a note period of silence between notes. The bug can be resolved by simply deleting the two instructions below that introduce
; the delay. A positive side effect of the bug in the 'V' volume command at $0C95 (ROM 0) is that it can be used to overcome the gaps of silence
; between notes for sound chip channels. By interspersing volume commands between notes, a new volume level is immediately set before
; the 1/96th of a note delay is introduced for the new note. Therefore, the delay occurs when the new note is audible instead of when it
; is silent. For example, PLAY "cV15cV15c" instead of PLAY "ccc". The note durations are still 1/96th of a note longer than they should
; be though. This technique will only work on the sound chip channels and not for any MIDI channels. Credit: Ian Collier (+3), Paul Farrow (128)]
L1066: LD DE,$0001 ; Delay for 1/96th of a note.
CALL L0F76 ;
CALL L0A4F ; Select channel data block pointers.
;All channel durations have been updated. Update the volume on each sound chip channel, and the volume and note on each MIDI channel
L106F: RR (IY+$2A) ; Temporary channel bitmap. Test if next string present.
JR NC,L108C ; Jump ahead if there is no string for this channel.
CALL L0A67 ; Get address of channel data block for the current string into IX.
LD A,(IX+$02) ; Get the channel number.
CP $03 ; Is it channel 0, 1 or 2, i.e. a sound chip channel?
JR NC,L1089 ; Jump ahead if so to process the next channel.
LD D,$08 ;
ADD A,D ;
LD D,A ; D=Register (8+channel number) - Channel volume.
LD E,(IX+$04) ; Get the current volume.
CALL L0E7C ; Write to sound generator register to set the volume of the channel.
L1089: CALL L116E ; Play a note and set the volume on the assigned MIDI channel.
L108C: SLA (IY+$21) ; Have all channels been processed?
RET C ; Return if so.
CALL L0A6E ; Advance to the next channel data pointer.
JR L106F ; Jump back to process the next channel.
; -----------------
; Note Lookup Table
; -----------------
; Each word gives the value of the sound generator tone registers for a given note.
; There are 9 octaves, containing a total of 108 notes. These represent notes 21 to
; 128. Notes 0 to 20 cannot be reproduced on the sound chip and so note 21 will be
; used for all of these (they will however be sent to a MIDI device if one is assigned
; to a channel). [Note that both the sound chip and the MIDI port can not play note 128
; and so its inclusion in the table is a waste of 2 bytes]. The PLAY command does not allow
; octaves higher than 8 to be selected directly. Using PLAY "O8G" will select note 115. To
; select higher notes, sharps must be included, e.g. PLAY "O8#G" for note 116, PLAY "O8##G"
; for note 117, etc, up to PLAY "O8############G" for note 127. Attempting to access note
; 128 using PLAY "O8#############G" will lead to error report "m Note out of range".
L1096: DEFW $0FBF ; Octave 1, Note 21 - A (27.50Hz, Ideal=27.50Hz, Error=-0.01%) C0
DEFW $0EDC ; Octave 1, Note 22 - A# (29.14Hz, Ideal=29.16Hz, Error=-0.08%)
DEFW $0E07 ; Octave 1, Note 23 - B (30.87Hz, Ideal=30.87Hz, Error=-0.00%)
DEFW $0D3D ; Octave 2, Note 24 - C (32.71Hz, Ideal=32.70Hz, Error=+0.01%) C1
DEFW $0C7F ; Octave 2, Note 25 - C# (34.65Hz, Ideal=34.65Hz, Error=-0.00%)
DEFW $0BCC ; Octave 2, Note 26 - D (36.70Hz, Ideal=36.71Hz, Error=-0.01%)
DEFW $0B22 ; Octave 2, Note 27 - D# (38.89Hz, Ideal=38.89Hz, Error=+0.01%)
DEFW $0A82 ; Octave 2, Note 28 - E (41.20Hz, Ideal=41.20Hz, Error=+0.00%)
DEFW $09EB ; Octave 2, Note 29 - F (43.66Hz, Ideal=43.65Hz, Error=+0.00%)
DEFW $095D ; Octave 2, Note 30 - F# (46.24Hz, Ideal=46.25Hz, Error=-0.02%)
DEFW $08D6 ; Octave 2, Note 31 - G (49.00Hz, Ideal=49.00Hz, Error=+0.00%)
DEFW $0857 ; Octave 2, Note 32 - G# (51.92Hz, Ideal=51.91Hz, Error=+0.01%)
DEFW $07DF ; Octave 2, Note 33 - A (55.01Hz, Ideal=55.00Hz, Error=+0.01%)
DEFW $076E ; Octave 2, Note 34 - A# (58.28Hz, Ideal=58.33Hz, Error=-0.08%)
DEFW $0703 ; Octave 2, Note 35 - B (61.75Hz, Ideal=61.74Hz, Error=+0.02%)
DEFW $069F ; Octave 3, Note 36 - C ( 65.39Hz, Ideal= 65.41Hz, Error=-0.02%) C2
DEFW $0640 ; Octave 3, Note 37 - C# ( 69.28Hz, Ideal= 69.30Hz, Error=-0.04%)
DEFW $05E6 ; Octave 3, Note 38 - D ( 73.40Hz, Ideal= 73.42Hz, Error=-0.01%)
DEFW $0591 ; Octave 3, Note 39 - D# ( 77.78Hz, Ideal= 77.78Hz, Error=+0.01%)
DEFW $0541 ; Octave 3, Note 40 - E ( 82.41Hz, Ideal= 82.41Hz, Error=+0.00%)
DEFW $04F6 ; Octave 3, Note 41 - F ( 87.28Hz, Ideal= 87.31Hz, Error=-0.04%)
DEFW $04AE ; Octave 3, Note 42 - F# ( 92.52Hz, Ideal= 92.50Hz, Error=+0.02%)
DEFW $046B ; Octave 3, Note 43 - G ( 98.00Hz, Ideal= 98.00Hz, Error=+0.00%)
DEFW $042C ; Octave 3, Note 44 - G# (103.78Hz, Ideal=103.83Hz, Error=-0.04%)
DEFW $03F0 ; Octave 3, Note 45 - A (109.96Hz, Ideal=110.00Hz, Error=-0.04%)
DEFW $03B7 ; Octave 3, Note 46 - A# (116.55Hz, Ideal=116.65Hz, Error=-0.08%)
DEFW $0382 ; Octave 3, Note 47 - B (123.43Hz, Ideal=123.47Hz, Error=-0.03%)
DEFW $034F ; Octave 4, Note 48 - C (130.86Hz, Ideal=130.82Hz, Error=+0.04%) C3
DEFW $0320 ; Octave 4, Note 49 - C# (138.55Hz, Ideal=138.60Hz, Error=-0.04%)
DEFW $02F3 ; Octave 4, Note 50 - D (146.81Hz, Ideal=146.83Hz, Error=-0.01%)
DEFW $02C8 ; Octave 4, Note 51 - D# (155.68Hz, Ideal=155.55Hz, Error=+0.08%)
DEFW $02A1 ; Octave 4, Note 52 - E (164.70Hz, Ideal=164.82Hz, Error=-0.07%)
DEFW $027B ; Octave 4, Note 53 - F (174.55Hz, Ideal=174.62Hz, Error=-0.04%)
DEFW $0257 ; Octave 4, Note 54 - F# (185.04Hz, Ideal=185.00Hz, Error=+0.02%)
DEFW $0236 ; Octave 4, Note 55 - G (195.83Hz, Ideal=196.00Hz, Error=-0.09%)
DEFW $0216 ; Octave 4, Note 56 - G# (207.57Hz, Ideal=207.65Hz, Error=-0.04%)
DEFW $01F8 ; Octave 4, Note 57 - A (219.92Hz, Ideal=220.00Hz, Error=-0.04%)
DEFW $01DC ; Octave 4, Note 58 - A# (232.86Hz, Ideal=233.30Hz, Error=-0.19%)
DEFW $01C1 ; Octave 4, Note 59 - B (246.86Hz, Ideal=246.94Hz, Error=-0.03%)
DEFW $01A8 ; Octave 5, Note 60 - C (261.42Hz, Ideal=261.63Hz, Error=-0.08%) C4 Middle C
DEFW $0190 ; Octave 5, Note 61 - C# (277.10Hz, Ideal=277.20Hz, Error=-0.04%)
DEFW $0179 ; Octave 5, Note 62 - D (294.01Hz, Ideal=293.66Hz, Error=+0.12%)
DEFW $0164 ; Octave 5, Note 63 - D# (311.35Hz, Ideal=311.10Hz, Error=+0.08%)
DEFW $0150 ; Octave 5, Note 64 - E (329.88Hz, Ideal=329.63Hz, Error=+0.08%)
DEFW $013D ; Octave 5, Note 65 - F (349.65Hz, Ideal=349.23Hz, Error=+0.12%)
DEFW $012C ; Octave 5, Note 66 - F# (369.47Hz, Ideal=370.00Hz, Error=-0.14%)
DEFW $011B ; Octave 5, Note 67 - G (391.66Hz, Ideal=392.00Hz, Error=-0.09%)
DEFW $010B ; Octave 5, Note 68 - G# (415.13Hz, Ideal=415.30Hz, Error=-0.04%)
DEFW $00FC ; Octave 5, Note 69 - A (439.84Hz, Ideal=440.00Hz, Error=-0.04%)
DEFW $00EE ; Octave 5, Note 70 - A# (465.72Hz, Ideal=466.60Hz, Error=-0.19%)
DEFW $00E0 ; Octave 5, Note 71 - B (494.82Hz, Ideal=493.88Hz, Error=+0.19%)
DEFW $00D4 ; Octave 6, Note 72 - C (522.83Hz, Ideal=523.26Hz, Error=-0.08%) C5
DEFW $00C8 ; Octave 6, Note 73 - C# (554.20Hz, Ideal=554.40Hz, Error=-0.04%)
DEFW $00BD ; Octave 6, Note 74 - D (586.46Hz, Ideal=587.32Hz, Error=-0.15%)
DEFW $00B2 ; Octave 6, Note 75 - D# (622.70Hz, Ideal=622.20Hz, Error=+0.08%)
DEFW $00A8 ; Octave 6, Note 76 - E (659.77Hz, Ideal=659.26Hz, Error=+0.08%)
DEFW $009F ; Octave 6, Note 77 - F (697.11Hz, Ideal=698.46Hz, Error=-0.19%)
DEFW $0096 ; Octave 6, Note 78 - F# (738.94Hz, Ideal=740.00Hz, Error=-0.14%)
DEFW $008D ; Octave 6, Note 79 - G (786.10Hz, Ideal=784.00Hz, Error=+0.27%)
DEFW $0085 ; Octave 6, Note 80 - G# (833.39Hz, Ideal=830.60Hz, Error=+0.34%)
DEFW $007E ; Octave 6, Note 81 - A (879.69Hz, Ideal=880.00Hz, Error=-0.04%)
DEFW $0077 ; Octave 6, Note 82 - A# (931.43Hz, Ideal=933.20Hz, Error=-0.19%)
DEFW $0070 ; Octave 6, Note 83 - B (989.65Hz, Ideal=987.76Hz, Error=+0.19%)
DEFW $006A ; Octave 7, Note 84 - C (1045.67Hz, Ideal=1046.52Hz, Error=-0.08%) C6
DEFW $0064 ; Octave 7, Note 85 - C# (1108.41Hz, Ideal=1108.80Hz, Error=-0.04%)
DEFW $005E ; Octave 7, Note 86 - D (1179.16Hz, Ideal=1174.64Hz, Error=+0.38%)
DEFW $0059 ; Octave 7, Note 87 - D# (1245.40Hz, Ideal=1244.40Hz, Error=+0.08%)
DEFW $0054 ; Octave 7, Note 88 - E (1319.53Hz, Ideal=1318.52Hz, Error=+0.08%)
DEFW $004F ; Octave 7, Note 89 - F (1403.05Hz, Ideal=1396.92Hz, Error=+0.44%)
DEFW $004B ; Octave 7, Note 90 - F# (1477.88Hz, Ideal=1480.00Hz, Error=-0.14%)
DEFW $0047 ; Octave 7, Note 91 - G (1561.14Hz, Ideal=1568.00Hz, Error=-0.44%)
DEFW $0043 ; Octave 7, Note 92 - G# (1654.34Hz, Ideal=1661.20Hz, Error=-0.41%)
DEFW $003F ; Octave 7, Note 93 - A (1759.38Hz, Ideal=1760.00Hz, Error=-0.04%)
DEFW $003B ; Octave 7, Note 94 - A# (1878.65Hz, Ideal=1866.40Hz, Error=+0.66%)
DEFW $0038 ; Octave 7, Note 95 - B (1979.30Hz, Ideal=1975.52Hz, Error=+0.19%)
DEFW $0035 ; Octave 8, Note 96 - C (2091.33Hz, Ideal=2093.04Hz, Error=-0.08%) C7
DEFW $0032 ; Octave 8, Note 97 - C# (2216.81Hz, Ideal=2217.60Hz, Error=-0.04%)
DEFW $002F ; Octave 8, Note 98 - D (2358.31Hz, Ideal=2349.28Hz, Error=+0.38%)
DEFW $002D ; Octave 8, Note 99 - D# (2463.13Hz, Ideal=2488.80Hz, Error=-1.03%)
DEFW $002A ; Octave 8, Note 100 - E (2639.06Hz, Ideal=2637.04Hz, Error=+0.08%)
DEFW $0028 ; Octave 8, Note 101 - F (2771.02Hz, Ideal=2793.84Hz, Error=-0.82%)
DEFW $0025 ; Octave 8, Note 102 - F# (2995.69Hz, Ideal=2960.00Hz, Error=+1.21%)
DEFW $0023 ; Octave 8, Note 103 - G (3166.88Hz, Ideal=3136.00Hz, Error=+0.98%)
DEFW $0021 ; Octave 8, Note 104 - G# (3358.81Hz, Ideal=3322.40Hz, Error=+1.10%)
DEFW $001F ; Octave 8, Note 105 - A (3575.50Hz, Ideal=3520.00Hz, Error=+1.58%)
DEFW $001E ; Octave 8, Note 106 - A# (3694.69Hz, Ideal=3732.80Hz, Error=-1.02%)
DEFW $001C ; Octave 8, Note 107 - B (3958.59Hz, Ideal=3951.04Hz, Error=+0.19%)
DEFW $001A ; Octave 9, Note 108 - C (4263.10Hz, Ideal=4186.08Hz, Error=+1.84%) C8
DEFW $0019 ; Octave 9, Note 109 - C# (4433.63Hz, Ideal=4435.20Hz, Error=-0.04%)
DEFW $0018 ; Octave 9, Note 110 - D (4618.36Hz, Ideal=4698.56Hz, Error=-1.71%)
DEFW $0016 ; Octave 9, Note 111 - D# (5038.21Hz, Ideal=4977.60Hz, Error=+1.22%)
DEFW $0015 ; Octave 9, Note 112 - E (5278.13Hz, Ideal=5274.08Hz, Error=+0.08%)
DEFW $0014 ; Octave 9, Note 113 - F (5542.03Hz, Ideal=5587.68Hz, Error=-0.82%)
DEFW $0013 ; Octave 9, Note 114 - F# (5833.72Hz, Ideal=5920.00Hz, Error=-1.46%)
DEFW $0012 ; Octave 9, Note 115 - G (6157.81Hz, Ideal=6272.00Hz, Error=-1.82%)
DEFW $0011 ; Octave 9, Note 116 - G# (6520.04Hz, Ideal=6644.80Hz, Error=-1.88%)
DEFW $0010 ; Octave 9, Note 117 - A (6927.54Hz, Ideal=7040.00Hz, Error=-1.60%)
DEFW $000F ; Octave 9, Note 118 - A# (7389.38Hz, Ideal=7465.60Hz, Error=-1.02%)
DEFW $000E ; Octave 9, Note 119 - B (7917.19Hz, Ideal=7902.08Hz, Error=+0.19%)
DEFW $000D ; Octave 10, Note 120 - C ( 8526.20Hz, Ideal= 8372.16Hz, Error=+1.84%) C9
DEFW $000C ; Octave 10, Note 121 - C# ( 9236.72Hz, Ideal= 8870.40Hz, Error=+4.13%)
DEFW $000C ; Octave 10, Note 122 - D ( 9236.72Hz, Ideal= 9397.12Hz, Error=-1.71%)
DEFW $000B ; Octave 10, Note 123 - D# (10076.42Hz, Ideal= 9955.20Hz, Error=+1.22%)
DEFW $000B ; Octave 10, Note 124 - E (10076.42Hz, Ideal=10548.16Hz, Error=-4.47%)
DEFW $000A ; Octave 10, Note 125 - F (11084.06Hz, Ideal=11175.36Hz, Error=-0.82%)
DEFW $0009 ; Octave 10, Note 126 - F# (12315.63Hz, Ideal=11840.00Hz, Error=+4.02%)
DEFW $0009 ; Octave 10, Note 127 - G (12315.63Hz, Ideal=12544.00Hz, Error=-1.82%)
DEFW $0008 ; Octave 10, Note 128 - G# (13855.08Hz, Ideal=13289.60Hz, Error=+4.26%)
; -------------------------
; Play Note on MIDI Channel
; -------------------------
; This routine turns on a note on the MIDI channel and sets its volume, if MIDI channel is assigned to the current string.
; Three bytes are sent, and have the following meaning:
; Byte 1: Channel number $00..$0F, with bits 4 and 7 set.
; Byte 2: Note number $00..$7F.
; Byte 3: Note velocity $00..$78.
; Entry: IX=Address of the channel data block.
L116E: LD A,(IX+$01) ; Is a MIDI channel assigned to this string?
OR A ;
RET M ; Return if not.
;A holds the assigned channel number ($00..$0F)
OR $90 ; Set bits 4 and 7 of the channel number. A=$90..$9F.
CALL L11A3 ; Write byte to MIDI device.
LD A,(IX+$00) ; The note number.
CALL L11A3 ; Write byte to MIDI device.
LD A,(IX+$04) ; Fetch the channel's volume.
RES 4,A ; Ensure the 'using envelope' bit is reset so
SLA A ; that A holds a value between $00 and $0F.
SLA A ; Multiply by 8 to increase the range to $00..$78.
SLA A ; A=Note velocity.
CALL L11A3 ; Write byte to MIDI device.
RET ; [Could have saved 1 byte by using JP L11A3 (ROM 0)]
; ---------------------
; Turn MIDI Channel Off
; ---------------------
; This routine turns off a note on the MIDI channel, if a MIDI channel is assigned to the current string.
; Three bytes are sent, and have the following meaning:
; Byte 1: Channel number $00..$0F, with bit 7 set.
; Byte 2: Note number $00..$7F.
; Byte 3: Note velocity $40.
; Entry: IX=Address of the channel data block.
L118D: LD A,(IX+$01) ; Is a MIDI channel assigned to this string?
OR A ;
RET M ; Return if not.
;A holds the assigned channel number ($00..$0F)
OR $80 ; Set bit 7 of the channel number. A=$80..$8F.
CALL L11A3 ; Write byte to MIDI device.
LD A,(IX+$00) ; The note number.
CALL L11A3 ; Write byte to MIDI device.
LD A,$40 ; The note velocity.
CALL L11A3 ; Write byte to MIDI device.
RET ; [Could have saved 1 byte by using JP L11A3 (ROM 0)]
; ------------------------
; Send Byte to MIDI Device
; ------------------------
; This routine sends a byte to the MIDI port. MIDI devices communicate at 31250 baud,
; although this routine actually generates a baud rate of 31388, which is within the 1%
; tolerance supported by MIDI devices.
; Entry: A=Byte to send.
L11A3: LD L,A ; Store the byte to send.
LD BC,$FFFD ;
LD A,$0E ;
OUT (C),A ; Select register 14 - I/O port.
LD BC,$BFFD ;
LD A,$FA ; Set RS232 'RXD' transmit line to 0. (Keep KEYPAD 'CTS' output line low to prevent the keypad resetting)
OUT (C),A ; Send out the START bit.
LD E,$03 ; (7) Introduce delays such that the next bit is output 113 T-states from now.
L11B4: DEC E ; (4)
JR NZ,L11B4 ; (12/7)
NOP ; (4)
NOP ; (4)
NOP ; (4)
NOP ; (4)
LD A,L ; (4) Retrieve the byte to send.
LD D,$08 ; (7) There are 8 bits to send.
L11BE: RRA ; (4) Rotate the next bit to send into the carry.
LD L,A ; (4) Store the remaining bits.
JP NC,L11C9 ; (10) Jump if it is a 0 bit.
LD A,$FE ; (7) Set RS232 'RXD' transmit line to 1. (Keep KEYPAD 'CTS' output line low to prevent the keypad resetting)
OUT (C),A ; (11)
JR L11CF ; (12) Jump forward to process the next bit.
L11C9: LD A,$FA ; (7) Set RS232 'RXD' transmit line to 0. (Keep KEYPAD 'CTS' output line low to prevent the keypad resetting)
OUT (C),A ; (11)
JR L11CF ; (12) Jump forward to process the next bit.
L11CF: LD E,$02 ; (7) Introduce delays such that the next data bit is output 113 T-states from now.
L11D1: DEC E ; (4)
JR NZ,L11D1 ; (12/7)
NOP ; (4)
ADD A,$00 ; (7)
LD A,L ; (4) Retrieve the remaining bits to send.
DEC D ; (4) Decrement the bit counter.
JR NZ,L11BE ; (12/7) Jump back if there are further bits to send.
NOP ; (4) Introduce delays such that the stop bit is output 113 T-states from now.
NOP ; (4)
ADD A,$00 ; (7)
NOP ; (4)
NOP ; (4)
LD A,$FE ; (7) Set RS232 'RXD' transmit line to 0. (Keep KEYPAD 'CTS' output line low to prevent the keypad resetting)
OUT (C),A ; (11) Send out the STOP bit.
LD E,$06 ; (7) Delay for 101 T-states (28.5us).
L11E7: DEC E ; (4)
JR NZ,L11E7 ; (12/7)
RET ; (10)
; =============================================
; CASSETTE / RAM DISK COMMAND ROUTINES - PART 1
; =============================================
; ------------
; SAVE Routine
; ------------
L11EB: LD HL,FLAGS3 ; $5B66.
SET 5,(HL) ; Indicate SAVE.
JR L1205
; ------------
; LOAD Routine
; ------------
L11F2: LD HL,FLAGS3 ; $5B66.
SET 4,(HL) ; Indicate LOAD.
JR L1205
; --------------
; VERIFY Routine
; --------------
L11F9: LD HL,FLAGS3 ; $5B66.
SET 7,(HL) ; Indicate VERIFY.
JR L1205
; -------------
; MERGE Routine
; -------------
L1200: LD HL,FLAGS3 ; $5B66.
SET 6,(HL) ; Indicate MERGE.
L1205: LD HL,FLAGS3 ; $5B66.
RES 3,(HL) ; Indicate using cassette.
RST 18H ; Get current character.
CP '!' ; $21. '!'
JP NZ,L13BE ; Jump ahead to handle cassette command.
;RAM disk operation
LD HL,FLAGS3 ; $5B66.
SET 3,(HL) ; Indicate using RAM disk.
RST 20H ; Move on to next character.
JP L13BE ; Jump ahead to handle RAM disk command.
L1219: CALL L05AC ; Produce error report.
DEFB $0B ; "C Nonsense in BASIC"
; -------------------------
; RAM Disk Command Handling
; -------------------------
; The information relating to the file is copied into memory in $5B66 (FLAGS3)
; to ensure that it is available once other RAM banks are switched in.
; This code is very similar to that in the ZX Interface 1 ROM at $08F6.
; Entry: HL=Start address.
; IX=File header descriptor.
L121D: LD (HD_0D),HL ; $5B74. Save start address.
LD A,(IX+$00) ; Transfer header file information
LD (HD_00),A ; $5B71. from IX to HD_00 onwards.
LD L,(IX+$0B) ;
LD H,(IX+$0C) ;
LD (HD_0B),HL ; $5B72.
LD L,(IX+$0D) ;
LD H,(IX+$0E) ;
LD (HD_11),HL ; $5B78.
LD L,(IX+$0F) ;
LD H,(IX+$10) ;
LD (HD_0F),HL ; $5B76.
;A copy of the header information has now been copied from IX+$00 onwards to HD_00 onwards
OR A ; Test file type.
JR Z,L124E ; Jump ahead for a program file.
CP $03 ;
JR Z,L124E ; Jump ahead for a CODE/SCREEN$ file.
;An array type
LD A,(IX+$0E) ;
LD (HD_0F),A ; $5B76. Store array name.
L124E: PUSH IX ; IX points to file header.
POP HL ; Retrieve into HL.
INC HL ; HL points to filename.
LD DE,N_STR1 ; $5B67.
LD BC,$000A ;
LDIR ; Copy the filename.
LD HL,FLAGS3 ; $5B66.
BIT 5,(HL) ; SAVE operation?
JP NZ,L1BAD ; Jump ahead if SAVE.
; Load / Verify or Merge
; ----------------------
LD HL,HD_00 ; $5B71.
LD DE,SC_00 ; $5B7A.
LD BC,$0007 ;
LDIR ; Transfer requested details from HD_00 onwards into SC_00 onwards.
CALL L1C2E ; Find and load requested file header into HD_00 ($5B71).
;The file exists else the call above would have produced an error "h file does not exist"
LD A,(SC_00) ; $5B7A. Requested file type.
LD B,A ;
LD A,(HD_00) ; $5B71. Loaded file type.
CP B ;
JR NZ,L1280 ; Error 'b' if file types do not match.
CP $03 ; Is it a CODE file type?
JR Z,L1290 ; Jump ahead to avoid MERGE program/array check.
JR C,L1284 ; Only file types 0, 1 and 2 are OK.
L1280: CALL L05AC ; Produce error report.
DEFB $1D ; "b Wrong file type"
L1284: LD A,(FLAGS3) ; $5B66.
BIT 6,A ; Is it a MERGE program/array operation?
JR NZ,L12C5 ; Jump ahead if so.
BIT 7,A ; Is it a VERIFY program/array operation?
JP Z,L12DB ; Jump ahead if LOAD.
;Either a verify program/array or a load/verify CODE/SCREEN$ type file
L1290: LD A,(FLAGS3) ; $5B66.
BIT 6,A ; MERGE operation?
JR Z,L129B ; Jump ahead if VERIFY.
;Cannot merge CODE/SCREEN$
CALL L05AC ; Produce error report.
DEFB $1C ; "a MERGE error"
; ------------------------
; RAM Disk VERIFY! Routine
; ------------------------
L129B: LD HL,(SC_0B) ; $5B7B. Length requested.
LD DE,(HD_0B) ; $5B72. File length.
LD A,H ;
OR L ;
JR Z,L12AE ; Jump ahead if requested length is 0, i.e. not specified.
SBC HL,DE ; Is file length <= requested length?
JR NC,L12AE ; Jump ahead if so; requested length is OK.
;File was smaller than requested
CALL L05AC ; Produce error report.
DEFB $1E ; "c CODE error"
L12AE: LD HL,(SC_0D) ; $5B7D. Fetch start address.
LD A,H ;
OR L ; Is length 0, i.e. not provided?
JR NZ,L12B8 ; Jump ahead if start address was provided.
LD HL,(HD_0D) ; $5B74. Not provided so use file's start address.
L12B8: LD A,(HD_00) ; $5B71. File type.
AND A ; Is it a program?
JR NZ,L12C1 ; Jump ahead if not.
LD HL,($5C53) ; PROG. Set start address as start of program area.
L12C1: CALL L137E ; Load DE bytes at address pointed to by HL.
; [The Spectrum 128 manual states that the VERIFY keyword is not used with the RAM disk yet it clearly is,
; although verifying a RAM disk file simply loads it in just as LOAD would do. To support verifying, the routine
; at $1E37 (ROM 0) which loads blocks of data would need to be able to load or verify a block. The success status would
; then need to be propagated back to here via routines at $137E (ROM 0), $1C4B (ROM 0) and $1E37 (ROM 0)]
RET ; [Could have saved 1 byte by using JP L137E (ROM 0), although could have saved a lot more by not supporting the
; VERIFY keyword at all]
; -----------------------
; RAM Disk MERGE! Routine
; -----------------------
L12C5: LD BC,(HD_0B) ; $5B72. File length.
PUSH BC ; Save the length.
INC BC ; Increment for terminator $80 (added later).
RST 28H ;
DEFW BC_SPACES ; $0030. Create room in the workspace for the file.
LD (HL),$80 ; Insert terminator.
EX DE,HL ; HL=Start address.
POP DE ; DE=File length.
PUSH HL ; Save start address.
CALL L137E ; Load DE bytes to address pointed to by HL.
POP HL ; Retrieve start address.
RST 28H ;
DEFW ME_CONTRL+$0018 ;$08CE. Delegate actual merge handling to ROM 1.
RET ;
; ----------------------
; RAM Disk LOAD! Routine
; ----------------------
L12DB: LD DE,(HD_0B) ; $5B72. File length.
LD HL,(SC_0D) ; $5B7D. Requested start address.
PUSH HL ; Save requested start address.
LD A,H ;
OR L ; Was start address specified? (0 if not).
JR NZ,L12ED ; Jump ahead if start address specified.
;Start address was not specified
INC DE ; Allow for variable overhead.
INC DE ;
INC DE ;
EX DE,HL ; HL=File Length+3.
JR L12F6 ; Jump ahead to test if there is room.
;A start address was specified
L12ED: LD HL,(SC_0B) ; $5B7B. Requested length.
EX DE,HL ; DE=Requested length. HL=File length.
SCF ;
SBC HL,DE ; File length-Requested Length-1
JR C,L12FF ; Jump if file is smaller than requested.
;Test if there is room since file is bigger than requested
L12F6: LD DE,$0005 ;
ADD HL,DE ;
LD B,H ;
LD C,L ; Space required in BC.
RST 28H ;
DEFW TEST_ROOM ; $1F05. Will automatically produce error '4' if out of memory.
;Test file type
L12FF: POP HL ; Requested start address.
LD A,(HD_00) ; $5B71. Get requested file type.
L1303: AND A ; Test file type.
JR Z,L1335 ; Jump if program file type.
; Array type
; ----------
LD A,H ;
OR L ; Was start address of existing array specified?
JR Z,L1315 ; Jump ahead if not.
;Start address of existing array was specified
DEC HL ;
LD B,(HL) ;
DEC HL ;
LD C,(HL) ; Fetch array length.
DEC HL ;
INC BC ;
INC BC ;
INC BC ; Allow for variable header.
RST 28H ;
DEFW RECLAIM_2 ; $19E8. Delete old array.
;Insert new array entry into variables area
L1315: LD HL,($5C59) ; E_LINE.
DEC HL ; Point to end
LD BC,(HD_0B) ; $5B72. Array length.
PUSH BC ; Save array length.
INC BC ; Allow for variable header.
INC BC ;
INC BC ;
LD A,(SC_0F) ; $5B7F. Get array name.
PUSH AF ; Save array name.
RST 28H ;
DEFW MAKE_ROOM ; $1655. Create room for new array.
INC HL
POP AF ;
LD (HL),A ; Store array name.
POP DE ;
INC HL ;
LD (HL),E ;
INC HL ;
LD (HL),D ; Store array length.
INC HL ;
L1331: CALL L137E ; Load DE bytes to address pointed to by HL.
RET ; [Could have saved 1 byte by using JP L137E (ROM 0)]
; Program type
; ------------
L1335: LD HL,FLAGS3 ; $5B66.
RES 1,(HL) ; Signal do not auto-run BASIC program.
LD DE,($5C53) ; PROG. Address of start of BASIC program.
LD HL,($5C59) ; E_LINE. Address of end of program area.
DEC HL ; Point before terminator.
RST 28H ;
DEFW RECLAIM ; $19E5. Delete current BASIC program.
LD BC,(HD_0B) ; $5B72. Fetch file length.
LD HL,($5C53) ; PROG. Address of start of BASIC program.
RST 28H ;
DEFW MAKE_ROOM ; $1655. Create room for the file.
INC HL ; Allow for terminator.
LD BC,(HD_0F) ; $5B76. Length of variables.
ADD HL,BC ; Determine new address of variables.
LD ($5C4B),HL ; VARS.
LD A,(HD_11+1) ; $5B79. Fetch high byte of auto-run line number.
LD H,A ;
AND $C0 ;
JR NZ,L1370 ; If holds $80 then no auto-run line number specified.
LD A,(HD_11) ; $5B78. Low byte of auto-run line number.
LD L,A ;
LD ($5C42),HL ; NEWPPC. Set line number to run.
LD (IY+$0A),$00 ; NSPPC. Statement 0.
LD HL,FLAGS3 ; $5B66.
SET 1,(HL) ; Signal auto-run BASIC program.
L1370: LD HL,($5C53) ; PROG. Address of start of BASIC program.
LD DE,(HD_0B) ; $5B72. Program length.
DEC HL ;
LD ($5C57),HL ; NXTLIN. Set the address of next line to the end of the program.
INC HL ;
JR L1331 ; Jump back to load program bytes.
; -------------------
; RAM Disk Load Bytes
; -------------------
; Make a check that the requested length is not zero before proceeding to perform
; the LOAD, MERGE or VERIFY. Note that VERIFY simply performs a LOAD.
; Entry: HL=Destination address.
; DE=Length.
; IX=Address of catalogue entry.
; HD_00-HD_11 holds file header information.
L137E: LD A,D ;
OR E ;
RET Z ; Return if length is zero.
CALL L1C4B ; Load bytes
RET ; [Could have used JP L1C4B (ROM 0) to save 1 byte]
; ------------------------------
; Get Expression from BASIC Line
; ------------------------------
; Returns in BC.
L1385: RST 28H ; Expect an expression on the BASIC line.
DEFW EXPT_EXP ; $1C8C.
BIT 7,(IY+$01) ; Return early if syntax checking.
RET Z ;
PUSH AF ; Get the item off the calculator stack
RST 28H ;
DEFW STK_FETCH ; $2BF1.
POP AF ;
RET ;
; -----------------------
; Check Filename and Copy
; -----------------------
; Called to check a filename for validity and to copy it into N_STR1 ($5B67).
L1393: RST 20H ; Advance the pointer into the BASIC line.
CALL L1385 ; Get expression from BASIC line.
RET Z ; Return if syntax checking.
PUSH AF ; [No need to save AF - see comment below]
LD A,C ; Check for zero length.
OR B ;
JR Z,L13BA ; Jump if so to produce error report "f Invalid name".
LD HL,$000A ; Check for length greater than 10.
SBC HL,BC ;
JR C,L13BA ; Jump if so to produce error report "f Invalid name".
PUSH DE ; Save the filename start address.
PUSH BC ; Save the filename length.
LD HL,N_STR1 ; $5B67. HL points to filename buffer.
LD B,$0A ;
LD A,$20 ;
L13AD: LD (HL),A ; Fill it with 10 spaces.
INC HL ;
DJNZ L13AD ;
POP BC ; Restore filename length.
POP HL ; Restore filename start address.
LD DE,N_STR1 ; $5B67. DE points to where to store the filename.
LDIR ; Perform the copy.
POP AF ; [No need to have saved AF as not subsequently used]
RET ;
L13BA: CALL L05AC ; Produce error report.
DEFB $21 ; "f Invalid name"
; ------------------------------------
; Cassette / RAM Disk Command Handling
; ------------------------------------
; Handle SAVE, LOAD, MERGE, VERIFY commands.
; Bit 3 of FLAGS3 indicates whether a cassette or RAM disk command.
; This code is very similar to that in ROM 1 at $0605.
L13BE: RST 28H
DEFW EXPT_EXP ; $1C8C. Pass the parameters of the 'name' to the calculator stack.
BIT 7,(IY+$01) ;
JR Z,L1407 ; Jump ahead if checking syntax.
LD BC,$0011 ; Size of save header, 17 bytes.
LD A,($5C74) ; T_ADDR. Indicates which BASIC command.
AND A ; Is it SAVE?
JR Z,L13D2 ; Jump ahead if so.
LD C,$22 ; Otherwise need 34d bytes for LOAD, MERGE and VERIFY commands.
; 17 bytes for the header of the requested file, and 17 bytes for the files tested from tape.
L13D2: RST 28H ;
DEFW BC_SPACES ; $0030. Create space in workspace.
PUSH DE ; Get start of the created space into IX.
POP IX ;
LD B,$0B ; Clear the filename.
LD A,$20 ;
L13DC: LD (DE),A ; Set all characters to spaces.
INC DE ;
DJNZ L13DC ;
LD (IX+$01),$FF ; Indicate a null name.
RST 28H ; The parameters of the name are fetched.
DEFW STK_FETCH ; $2BF1.
LD HL,$FFF6 ; = -10.
DEC BC ;
ADD HL,BC ;
INC BC ;
JR NC,L1400 ; Jump ahead if filename length within 10 characters.
LD A,($5C74) ; T_ADDR. Indicates which BASIC command.
AND A ; Is it SAVE?
JR NZ,L13F9 ; Jump ahead if not since LOAD, MERGE and VERIFY can have null filenames.
CALL L05AC ; Produce error report.
DEFB $0E ; "F Invalid file name"
;Continue to handle the name of the program.
L13F9: LD A,B
OR C ;
JR Z,L1407 ; Jump forward if the name has a null length.
LD BC,$000A ; Truncate longer filenames.
;The name is now transferred to the work space (second location onwards)
L1400: PUSH IX ;
POP HL ; Transfer address of the workspace to HL.
INC HL ; Step to the second location.
EX DE,HL ;
LDIR ; Copy the filename.
;The many different parameters, if any, that follow the command are now considered.
;Start by handling 'xxx "name" DATA'.
L1407: RST 18H ; Get character from BASIC line.
CP $E4 ; Is it 'DATA'?
JR NZ,L145F ; Jump if not DATA.
; 'xxx "name" DATA'
; -----------------
LD A,($5C74) ; T_ADDR. Check the BASIC command.
CP $03 ; Is it MERGE?
JP Z,L1219 ; "C Nonsense in BASIC" if so.
RST 20H ; Get next character from BASIC line.
RST 28H ;
DEFW LOOK_VARS ; $28B2. Look in the variables area for the array.
JR NC,L142F ; Jump if handling an existing array.
LD HL,$0000 ; Signal 'using a new array'.
BIT 6,(IY+$01) ; FLAGS. Is it a string Variable?
JR Z,L1425 ; Jump forward if so.
SET 7,C ; Set bit 7 of the array's name.
L1425: LD A,($5C74) ; T_ADDR.
DEC A ; Give an error if trying to
JR Z,L1444 ; SAVE or VERIFY a new array.
CALL L05AC ; Produce error report.
DEFB $01 ; "2 Variable not found"
;Continue with the handling of an existing array
L142F: JP NZ,L1219 ; Jump if not an array to produce "C Nonsense in BASIC".
BIT 7,(IY+$01) ; FLAGS.
JR Z,L1451 ; Jump forward if checking syntax.
LD C,(HL) ;
INC HL ; Point to the 'low length' of the variable.
LD A,(HL) ; The low length byte goes into
LD (IX+$0B),A ; the work space.
INC HL ;
LD A,(HL) ; The high length byte goes into
LD (IX+$0C),A ; the work space.
INC HL ; Step past the length bytes.
;The next part is common to both 'old' and 'new' arrays
L1444: LD (IX+$0E),C ; Copy the array's name.
LD A,$01 ; Assume an array of numbers - Code $01.
BIT 6,C ;
JR Z,L144E ; Jump if it is so.
INC A ; Indicate it is an array of characters - Code $02.
L144E: LD (IX+$00),A ; Save the 'type' in the first location of the header area.
;The last part of the statement is examined before joining the other pathways
L1451: EX DE,HL ; Save the pointer in DE.
RST 20H ;
CP ')' ; $29. Is the next character a ')'?
JR NZ,L142F ; Give report C if it is not.
RST 20H ; Advance to next character.
CALL L18A1 ; Move on to the next statement if checking syntax.
EX DE,HL ; Return the pointer to the HL.
; (The pointer indicates the start of an existing array's contents).
JP L1519 ; Jump forward.
; Now Consider 'SCREEN$'
L145F: CP $AA ; Is the present code the token 'SCREEN$'?
JR NZ,L1482 ; Jump ahead if not.
; 'xxx "name" SCREEN$'
; --------------------
LD A,($5C74) ; T_ADDR_lo. Check the BASIC command.
CP $03 ; Is it MERGE?
JP Z,L1219 ; Jump to "C Nonsense in BASIC" if so since it is not possible to have 'MERGE name SCREEN$'.
RST 20H ; Advance pointer into BASIC line.
CALL L18A1 ; Move on to the next statement if checking syntax.
LD (IX+$0B),$00 ; Length of the block.
LD (IX+$0C),$1B ; The display area and the attribute area occupy $1800 locations.
LD HL,$4000 ; Start of the block, beginning of the display file $4000.
LD (IX+$0D),L ;
LD (IX+$0E),H ; Store in the workspace.
JR L14CF ; Jump forward.
; Now consider 'CODE'
L1482: CP $AF ; Is the present code the token 'CODE'?
JR NZ,L14D5 ; Jump ahead if not.
; 'xxx "name" CODE'
; -----------------
LD A,($5C74) ; T_ADDR_lo. Check the BASIC command.
CP $03 ; Is it MERGE?
JP Z,L1219 ; Jump to "C Nonsense in BASIC" if so since it is not possible to have 'MERGE name CODE'.
RST 20H ; Advance pointer into BASIC line.
RST 28H ;
DEFW PR_ST_END ; $2048.
JR NZ,L14A0 ; Jump forward if the statement has not finished
LD A,($5C74) ; T_ADDR_lo.
AND A ; It is not possible to have 'SAVE name CODE' by itself.
JP Z,L1219 ; Jump if so to produce "C Nonsense in BASIC".
RST 28H ;
DEFW USE_ZERO ; $1CE6. Put a zero on the calculator stack - for the 'start'.
JR L14AF ; Jump forward.
;Look for a 'starting address'
L14A0: RST 28H ;
DEFW EXPT_1NUM ; $1C82. Fetch the first number.
RST 18H ;
CP ',' ; $2C. Is the present character a ','?
JR Z,L14B4 ; Jump if it is - the number was a 'starting address'
LD A,($5C74) ; T_ADDR_lo.
AND A ; Refuse 'SAVE name CODE' that does not have a 'start' and a 'length'.
JP Z,L1219 ; Jump if so to produce "C Nonsense in BASIC".
L14AF: RST 28H ;
DEFW USE_ZERO ; $1CE6. Put a zero on the calculator stack - for the 'length'.
JR L14B8 ; Jump forward.
;Fetch the 'length' as it was specified
L14B4: RST 20H ; Advance to next character.
RST 28H ;
DEFW EXPT_1NUM ; $1C82. Fetch the 'length'.
;The parameters are now stored in the header area of the work space
L14B8: CALL L18A1 ; But move on to the next statement now if checking syntax.
RST 28H ;
DEFW FIND_INT2 ; $1E99. Compress the 'length' into BC.
LD (IX+$0B),C ; Store the length of the CODE block.
LD (IX+$0C),B ;
RST 28H ;
DEFW FIND_INT2 ; $1E99. Compress the 'starting address' into BC.
LD (IX+$0D),C ; Store the start address of the CODE block.
LD (IX+$0E),B ;
LD H,B ; Transfer start address pointer to HL.
LD L,C ;
;'SCREEN$' and 'CODE' are both of type 3
L14CF: LD (IX+$00),$03 ; Store file type = $03 (CODE).
JR L1519 ; Rejoin the other pathways.
; 'xxx "name"' / 'SAVE "name" LINE'
; ---------------------------------
;Now consider 'LINE' and 'no further parameters'
L14D5: CP $CA ; Is the present code the token 'LINE'?
JR Z,L14E2 ; Jump ahead if so.
CALL L18A1 ; Move on to the next statement if checking syntax.
LD (IX+$0E),$80 ; Indicate no LINE number.
JR L14F9 ; Jump forward.
;Fetch the 'line number' that must follow 'LINE'
L14E2: LD A,($5C74) ; T_ADDR_lo. Only allow 'SAVE name LINE number'.
AND A ; Is it SAVE?
JP NZ,L1219 ; Produce "C Nonsense in BASIC" if not.
RST 20H ; Advance pointer into BASIC line.
RST 28H ; Get LINE number onto calculator stack
DEFW EXPT_1NUM ; $1C82. Pass the number to the calculator stack.
CALL L18A1 ; Move on to the next statement if checking syntax.
RST 28H ; Retrieve LINE number from calculator stack
DEFW FIND_INT2 ; $1E99. Compress the 'line number' into BC.
LD (IX+$0D),C ; Store the LINE number.
LD (IX+$0E),B ;
;'LINE' and 'no further parameters' are both of type 0
L14F9: LD (IX+$00),$00 ; Store file type = $00 (program).
LD HL,($5C59) ; E_LINE. The pointer to the end of the variables area.
LD DE,($5C53) ; PROG. The pointer to the start of the BASIC program.
SCF ;
SBC HL,DE ; Perform the subtraction to find the length of the 'program + variables'.
LD (IX+$0B),L ;
LD (IX+$0C),H ; Store the length.
LD HL,($5C4B) ; VARS. Repeat the operation but this
SBC HL,DE ; time storing the length of the
LD (IX+$0F),L ; 'program' only.
LD (IX+$10),H ;
EX DE,HL ; Transfer pointer to HL.
;In all cases the header information has now been prepared:
;- The location 'IX+00' holds the type number.
;- Locations 'IX+01 to IX+0A' holds the name ($FF in 'IX+01' if null).
;- Locations 'IX+0B & IX+0C' hold the number of bytes that are to be found in the 'data block'.
;- Locations 'IX+0D to IX+10' hold a variety of parameters whose exact interpretation depends on the 'type'.
;The routine continues with the first task being to separate SAVE from LOAD, VERIFY and MERGE.
L1519: LD A,(FLAGS3) ; $5B66.
BIT 3,A ; Using RAM disk?
JP NZ,L121D ; Jump if the operation is on the RAM disk.
LD A,($5C74) ; T_ADDR_lo. Get the BASIC command.
AND A ; Is it SAVE?
JR NZ,L152B ; Jump ahead if not.
RST 28H ;
DEFW SA_CONTROL ; $0970. Run the save routine in ROM 1.
RET ;
;In the case of a LOAD, VERIFY or MERGE command the first seventeen bytes of the 'header area'
;in the work space hold the prepared information, as detailed above;
;and it is now time to fetch a 'header' from the tape.
L152B: RST 28H ;
DEFW SA_ALL+$0007 ; $0761. Run the load/merge/verify routine in ROM 1.
RET ;
; ========================
; EDITOR ROUTINES - PART 1
; ========================
; ----------------------------------------------
; Relist the BASIC Program from the Current Line
; ----------------------------------------------
; This routine lists the BASIC program from the current line number. It initially shows the last line displayed but rows may subsequently
; be scrolled up until the required BASIC line has been found. The structure of the ROM program only supports listing BASIC lines that are
; 20 rows or less; larger lines are shown truncated to 20 rows.
L152F: LD HL,$EEF5 ; Flags.
RES 0,(HL) ; Signal this is not the current line.
SET 1,(HL) ; Signal not yet located the current line.
;A loop is entered to display a screenful of program listing. If the current line number is not found in the lines displayed then all
;lines are scrolled up and the listing reproduced. This procedure repeats until the current line number has been found and displayed.
L1536: LD HL,($5C49) ; E_PPC. Fetch current line number.
LD A,H ;
OR L ; Is there a currently selected line?
JR NZ,L1540 ; Jump ahead if so.
LD ($EC06),HL ; Set to $0000 to indicate no editable characters before the cursor.
L1540: LD A,($F9DB) ; Fetch the number of rows of the BASIC line that are in the Above-Screen Line Edit Buffer,
PUSH AF ; i.e. that are off the top of the screen.
LD HL,($FC9A) ; Line number of the BASIC line at the top of the screen (or 0 for the first line).
CALL L334A ; Find closest line number (or L0000 if no subsequent line exists).
LD ($F9D7),HL ; Store the line number of the BASIC line being edited in the buffer.
CALL L3222 ; Set default Above-Screen Line Edit Buffer settings.
CALL L30D6 ; Set default Below-Screen Line Edit Buffer settings.
POP AF ; A=Number of rows of the BASIC line that are in the Above-Screen Line Edit Buffer.
L1554: OR A ; Are there any rows off the top of the screen?
JR Z,L1563 ; Jump ahead if not.
;The current settings indicate that the top BASIC line straggles into the Above-Screen Line Edit Buffer. It is therefore necessary to insert the current
;BASIC line into the Below-Screen Line Edit Buffer and then shift the appropriate number of rows into the Above-Screen Line Edit Buffer.
PUSH AF ; Save the number of rows off the top of the screen.
CALL L30DF ; Copy a BASIC line from the program area into the Below-Screen Line Edit Buffer.
EX DE,HL ; DE=Address of the Below-Screen Line Edit Buffer.
CALL L326A ; Shift up a row into the Above-Screen Line Edit Buffer.
POP AF ; Retrieve the number of rows off the top of the screen.
DEC A ; Decrement the number of rows.
JR L1554 ; Jump back to shift up another row if required.
;Either there the top BASI Cline does not straggle off the top of the the screen or the appropriate number of rows have been copied into the
;Above-Screen Line Edit Buffer. In the latter case, the Below-Screen Line Edit Buffer contains the remaining rows of the BASIC line and which
;be copied into the top of the Screen Line Edit Buffer.
L1563: LD C,$00 ; C=Row 0.
CALL L30B4 ; DE=Start address in Screen Line Edit Buffer of the first row, as specified in C.
LD B,C ; B=Row 0.
LD A,($EC15) ; The number of editing rows on screen.
LD C,A ; C=Number of editing rows on screen.
PUSH BC ; B=Row number, C=Number of editing rows on screen.
PUSH DE ; DE=Start address in Screen Line Edit Buffer of the first row.
;Enter a loop to copy BASIC line rows into the Screen Line Edit Buffer. The Below-Screen Line Edit Buffer is used as a temporary store for holding each BASIC line
;as it is copied into the Screen Line Edit Buffer. If the top BASIC line straggles above the screen then this loop is entered with the remains of the line
;already in the Below-Screen Line Edit Buffer.
L156F: CALL L30DF ; Shift up all rows of the BASIC line in the Below-Screen Line Edit Buffer, or if empty then copy a BASIC line from the program area into it.
; If no BASIC line available then empty the first row of the Below-Screen Line Edit Buffer.
LD A,($EEF5) ; Listing flags.
BIT 1,A ; Has the current line been previously found?
JR Z,L1596 ; Jump if so.
;The current line has not yet been found so examine the current row in case it is the current line
PUSH DE ; DE=Start address in Screen Line Edit Buffer of the current row.
PUSH HL ; HL=Address of the first row in the Below-Screen Line Edit Buffer.
LD DE,$0020 ;
ADD HL,DE ; Point to the flag byte for the first row.
BIT 0,(HL) ; Is it the first row of a BASIC line?
JR Z,L1594 ; Jump if not.
;The Below-Screen Line Edit Buffer contains a complete BASIC line so determine whether this is the current line
INC HL ;
LD D,(HL) ; Get line number into DE.
INC HL ;
LD E,(HL) ;
OR A ;
LD HL,($5C49) ; E_PPC. Current line number.
SBC HL,DE ;
JR NZ,L1594 ; Jump ahead unless this is the current line.
LD HL,$EEF5 ;
SET 0,(HL) ; Signal this is the current line.
L1594: POP HL ; HL=Address of the current row in the Below-Screen Line Edit Buffer.
POP DE ; DE=Start address in Screen Line Edit Buffer of the current row.
;Copy the row of the BASIC line from the Below-Screen Line Edit Buffer into the Screen Line Edit Buffer
L1596: PUSH BC ; B=Row number, C=Number of editing rows on screen.
PUSH HL ; HL=Address of the current row in the Below-Screen Line Edit Buffer.
LD BC,$0023 ;
LDIR ; Copy the first row of the BASIC line in the Below-Screen Line Edit Buffer into the next row of the Screen Line Edit Buffer.
POP HL ; HL=Address of the current row in the Below-Screen Line Edit Buffer.
POP BC ; B=Row number, C=Number of editing rows on screen.
PUSH DE ; DE=Start address in Screen Line Edit Buffer of the next row.
PUSH BC ; B=Row number, C=Number of editing rows on screen.
EX DE,HL ; DE=Address of the current row in the Below-Screen Line Edit Buffer.
LD HL,$EEF5 ; Flags.
BIT 0,(HL) ; Is this the current line?
JR Z,L15D3 ; Jump if not.
;This is the current line so scan across the BASIC line to locate the cursor column position
LD B,$00 ; Column 0.
L15AB: LD HL,($EC06) ; HL=Count of the number of editable characters in the BASIC line up to the cursor within the Screen Line Edit Buffer.
LD A,H ;
OR L ; Are there any editable characters in this row prior to the cursor?
JR Z,L15C0 ; Jump if there are none, i.e. cursor at start of the row.
;There are editable characters on this row prior to the cursor
; [*BUG* - Entering ' 10 REM' or '0010 REM' will insert the line into the program area but instead of placing the cursor on the following row it is placed after the following
; BASIC line, or if the line inserted was the last in the program then the cursor is placed on row 20. The bug occurs due to the leading spaces or zeros, and hence will apply
; to every BASIC command. When the line is inserted into the Screen Line Edit Buffer, the leading spaces are discarded and hence the line length is shorter than that typed
; in. However, it is the typed in line length that is used when parsing the BASIC line in the Screen Line Edit Buffer and as a result this causes an
; attempt to find the remaining characters on the following row of the Screen Line Edit Buffer. If another BASIC line is on the following Screen Line Edit Buffer
; row then the search completes and the cursor is placed on the row after this BASIC line. If there is not a BASIC line on the following
; row then the search continues on the next row. Since this will also be empty, the search advances onto the next row, and then the next, and so
; on until row 20 is reached. To fix the bug, the typed in character count until the cursor (held in $EC06) ideally needs to be adjusted to match the actual number of characters stored
; in the Screen Line Edit Buffer. However, this is not a trivial change to implement. A simpler solution to fix the bug is to intercept when a move to the next row is made and
; to determine whether the BASIC line actually continues on this row. Credit: Paul Farrow]
; [To fix the bug, the POP HL and JR NC,L15CB (ROM 0) instructions following the call to $2E41 (ROM 0) should be replaced with the following. Credit: Paul Farrow.
;
; PUSH DE ; DE=Address of the start of the row of the BASIC line in the Screen Line Edit Buffer.
; PUSH AF ; Save the flags.
;
; LD HL,$0020 ;
; ADD HL,DE ;
; EX DE,HL ; DE=Address of the flag byte for the row in the Screen Line Edit Buffer.
;
; POP AF ; Restore the flags.
; JR C,CHAR_FOUND ; Jump if editable column found.
;
; LD A,(DE) ; Fetch the flag byte.
; BIT 1,A ; Does the BASIC line span onto the next row?
; JR NZ,SPANS_ROW ; Jump if it does.
;
; POP DE ; DE=Address of the start of the BASIC row in the Screen Line Edit Buffer.
; POP HL
; LD HL,$0000 ; Signal no editable characters left on the row.
; LD ($EC06),HL ;
; JP L15C0 (ROM 0) ; Jump since all characters on the row have been scanned through.
;
;SPANS_ROW:
; POP DE ; DE=Address of the start of the BASIC row in the Screen Line Edit Buffer.
; POP HL ;
; JP L15CB (ROM 0) ; Jump if no editable columns left on the row.
;
;CHAR_FOUND:
; POP DE ; DE=Address of the start of the BASIC row in the Screen Line Edit Buffer.
; POP HL ; ]
PUSH HL ;
CALL L2E41 ; Find editable position on this row from the previous column to the right, returning column number in B.
POP HL ;
JR NC,L15CB ; Jump if no editable character found on this row, i.e. there must be more characters on the next row.
;An editable character was found to the right on the current row
DEC HL ; Decrement the count of characters prior to the cursor.
INC B ; Advance to next column.
LD ($EC06),HL ; Update the count of the number of editable characters up to the cursor.
JR L15AB ; Jump back to test next column.
;Column position of cursor located, find the closest editable character
L15C0: CALL L2E41 ; Find editable position on this row from the previous column to the right, returning column number in B.
CALL NC,L2E63 ; If no editable character found then find editable position to the left, returning column number in B.
LD HL,$EEF5 ; Flags.
LD (HL),$00 ; Signal 'not the current line', 'current line has previously been found' and 'update display file enabled'.
;Store the current cursor position
L15CB: LD A,B ; A=Column number. This will be the preferred column number.
POP BC ; B=Row number, C=Number of editing rows on screen.
PUSH BC ;
LD C,B ; C=Row number.
LD B,A ; B=Column number.
CALL L2A11 ; Store this as the current cursor editing position.
;Move to next row
L15D3: POP BC ; B=Row number, C=Number of editing rows on screen.
POP DE ; DE=Start address in Screen Line Edit Buffer of the next row.
LD A,C ; A=Number of editing rows on screen.
INC B ; Next row.
CP B ; Reached the bottom screen row?
JR NC,L156F ; Jump back if not to display the next row.
;The bottom screen row has been exceeded
LD A,($EEF5) ; Listing flags.
BIT 1,A ; Has the current line been previously found?
JR Z,L1602 ; Jump if so.
;Current line has not yet been found
BIT 0,A ; Is this the current line?
JR NZ,L1602 ; Jump if so.
;This is not the current line
LD HL,($5C49) ; E_PPC. Current line number.
LD A,H ;
OR L ;
JR Z,L15F4 ; Jump if there is no current line number.
LD ($FC9A),HL ; Store it as the line number at top of the screen.
CALL L3222 ; Set default Above-Screen Line Edit Buffer settings to clear the count of the number of rows it contains.
JR L15FD ; Jump forward.
;There is no current line number
L15F4: LD ($FC9A),HL ; Set the line number at top of the screen to $0000, i.e. first available.
CALL L3352 ; Create line number representation in the Keyword Construction Buffer of the next BASIC line.
LD ($5C49),HL ; E_PPC. Current line number is the first in the BASIC program.
L15FD: POP DE ; DE=Start address in Screen Line Edit Buffer of the first row.
POP BC ; B=Row number, C=Number of editing rows on screen.
JP L1536 ; Jump back to continue listing the program until the current line is found.
;The bottom line is the current line
L1602: POP DE ; DE=Start address in Screen Line Edit Buffer of the first row.
POP BC ; B=Row number, C=Number of editing rows on screen.
CP A ; Set the zero flag if current line has yet to be found, hence signal do not update cursor position settings.
; ----------------------------------------------------------
; Print All Screen Line Edit Buffer Rows to the Display File
; ----------------------------------------------------------
; Print all rows of the edit buffer to the display file, and updating the cursor position settings if required.
; Entry: Zero flag reset if update of cursor position settings required.
; B=Row number.
; C=Number of editing rows on screen.
L1605: PUSH AF ; Save the zero flag.
LD A,C ; Save the number of editing rows on screen.
LD C,B ; C=Row number.
CALL L30B4 ; DE=Start address in Screen Line Edit Buffer of row held in C
EX DE,HL ; and transfer into HL.
L160C: PUSH AF ; A=Number of editing rows on screen.
CALL L3604 ; Print a row of the edit buffer to the screen.
POP AF ;
LD DE,$0023 ;
ADD HL,DE ; Point to the start of the next row.
L1615: INC C ; Advance to the next row.
CP C ; All rows printed?
JR NC,L160C ; Jump back if not to print next row.
;All rows printed
POP AF ; Retrieve the zero flag.
RET Z ; Return if 'not the current line' and 'current line has previously been found'.
;Find the new cursor column position
CALL L2A07 ; Get current cursor position (C=row, B=column, A=preferred column).
L161E: CALL L2B78 ; Find next Screen Line Edit Buffer editable position to right, moving to next row if necessary. Returns column number in B.
LD HL,($EC06) ; Fetch the number of editable characters on this row prior to the cursor.
DEC HL ; Decrement the count.
LD A,H ; Are there any characters?
OR L ;
LD ($EC06),HL ; Store the new count.
JR NZ,L161E ; Jump if there are some characters prior to the cursor.
JP L2A11 ; Store cursor editing position, with preferred column of 0.
RET ; [Redundant byte]
; ---------------------
; Clear Editing Display
; ---------------------
L1630: LD B,$00 ; Top row of editing area.
LD A,($EC15) ; The number of editing rows on screen.
LD D,A ; D=Number of rows in editing area.
JP L3B5E ; Clear specified display rows.
; -----------------------------------------------------------------
; Shift All Edit Buffer Rows Up and Update Display File if Required
; -----------------------------------------------------------------
; This routine shifts all edit buffer rows up, updating the display file if required.
; Entry: HL=Address of the 'Bottom Row Scroll Threshold' within the editing area information.
; Exit : Carry flag set if edit buffer rows were shifted.
L1639: LD B,$00 ; Row number to start shifting from.
PUSH HL ; Save the address of the 'Bottom Row Scroll Threshold' within the editing area information.
;Attempt to shift a row into the Above-Screen Line Edit Buffer
LD C,B ; Find the address of row 0.
CALL L30B4 ; DE=Start address in Screen Line Edit Buffer of the row specified in C.
CALL L326A ; Attempt to shift the top row of the Screen Line Edit Buffer into the Above-Screen Line Edit Buffer.
POP HL ; Retrieve the address of the 'Bottom Row Scroll Threshold' within the editing area information.
RET NC ; Return if the Above-Screen Line Edit Buffer is full, i.e. no edit buffer rows shifted.
;A change to the number of rows in the Above-Screen Line Edit Buffer occurred
CALL L30DF ; Shift up rows of the BASIC line in Below-Screen Line Edit Buffer, inserting the next line BASIC line if the buffer becomes empty.
; Returns with HL holding the address of the first row in the Below-Screen Line Edit Buffer.
; Shift All Screen Line Edit Buffer Rows Up and Update Display File if Required
; -----------------------------------------------------------------------------
L1648: PUSH BC ; B=Row counter.
PUSH HL ; HL=Address of first row in the Below-Screen Line Edit Buffer.
LD HL,$0023 ; DE=Address of the current row in the Screen Line Edit Buffer.
ADD HL,DE ; HL=Address of the next row in the Screen Line Edit Buffer.
LD A,($EC15) ;
LD C,A ; C=Number of editing rows on screen.
CP B ; Any rows to shift?
JR Z,L1663 ; Jump if not.
;Shift all Screen Line Edit Buffer rows up
PUSH BC ; C=Number of editing rows on screen.
L1656: PUSH BC ; C=Number of editing rows on screen.
LD BC,$0023 ; DE=Current Screen Line Edit Buffer row, HL=Next Screen Line Edit Buffer row.
LDIR ; Shift one row of the Screen Line Edit Buffer up.
POP BC ; C=Number of editing rows on screen.
LD A,C ; Fetch the number of editing rows on screen.
INC B ; Next row.
CP B ; All rows shifted?
JR NZ,L1656 ; Repeat for all edit buffer rows to shift.
;All Screen Line Edit Buffer rows have been shifted up
POP BC ; C=Number of editing rows on screen, B=Row number, i.e. 0.
L1663: POP HL ; HL=Address of the first row in the Below-Screen Line Edit Buffer.
L1664: CALL L3618 ; Shift up all edit rows in the display file if updating required.
LD BC,$0023 ; HL=Address of the first row in the Below-Screen Line Edit Buffer, DE=Address of last row in Screen Line Edit Buffer.
LDIR ; Copy the first row of the Below-Screen Line Edit Buffer into the last row of the Screen Line Edit Buffer.
SCF ; Signal that edit buffer rows were shifted.
POP BC ; B=Row counter.
RET ;
; -------------------------------------------------------------------
; Shift All Edit Buffer Rows Down and Update Display File if Required
; -------------------------------------------------------------------
; This routine shifts all edit buffer rows down, updating the display file if required.
; Exit : Carry flag set if edit buffer rows were shifted.
; B=Last row number to shift.
;Shift all rows in the Above-Screen Line Edit Buffer, shifting in a new BASIC line if applicable
L166F: LD B,$00 ; Last row number to shift.
CALL L322B ; Attempt to shift down the Above-Screen Line Edit Buffer, loading in a new BASIC line if it is empty.
RET NC ; Return if Above-Screen Line Edit Buffer is empty, i.e. no edit buffer rows were shifted.
;Entry point from routine at $2ED3 (ROM 0) to insert a blank row
L1675: PUSH BC ; B=Last row number to shift.
PUSH HL ; HL=Address of next row to use within the Above-Screen Line Edit Buffer.
;Shift all rows in the Below-Screen Line Edit Buffer down, shifting in a new BASIC line if applicable
LD A,($EC15) ; A=Number of editing rows on screen.
LD C,A ; C=Number of editing rows on screen.
CALL L30B4 ; DE=Start address in Screen Line Edit Buffer of the last editing row.
CALL L311E ; Shift down all rows in the Below-Screen Line Edit Buffer, or empty the buffer a row does not straggle off the bottom of the screen.
JR NC,L16A9 ; Jump if the Below-Screen Line Edit Buffer is full.
DEC DE ; DE=Address of the last flag byte of the penultimate editing row in the Screen Line Edit Buffer.
LD HL,$0023 ; Length of an edit buffer row.
ADD HL,DE ; HL=Address of the last flag byte of the last editing row in the Screen Line Edit Buffer.
EX DE,HL ; DE=Address of last flag byte of last editing row in Screen Line Edit Buffer, HL=Address of last flag byte of penultimate editing row in Screen Line Edit Buffer.
PUSH BC ; C=Number of editing rows on screen, B=Last row number to shift.
LD A,B ;
CP C ; Any rows to shift?
JR Z,L169A ; Jump if not.
L168E: PUSH BC ; C=Row number to shift, B=Last row number to shift.
LD BC,$0023 ;
LDDR ; Copy one row of the Screen Line Edit Buffer down.
POP BC ; C=Number of editing rows on screen, B=Row shift counter.
L1695: LD A,B ; A=Row shift counter.
DEC C ;
CP C ;
JR C,L168E ; Repeat for all edit buffer rows to shift.
;All Screen Line Edit Buffer rows have been shifted down
L169A: EX DE,HL ; HL=Address of last flag byte of first editing row in Screen Line Edit Buffer, DE=Address of byte before start of first editing row in Screen Line Edit Buffer.
INC DE ; DE=Start of first row in Screen Line Edit Buffer.
POP BC ; C=Number of editing rows on screen, B=Last row number to shift.
POP HL ; HL=Address of next row to use within the Above-Screen Line Edit Buffer.
CALL L362C ; Shift down all edit rows in the display file if updating required.
LD BC,$0023 ;
LDIR ; Copy the next row of the Above-Screen Line Edit Buffer into the first row of the Screen Line Edit Buffer.
SCF ; Signal Below-Screen Line Edit Buffer is not full.
POP BC ; B=Last row number to shift.
RET ;
;The Below-Screen Line Edit Buffer is full
L16A9: POP HL ; Restore registers.
POP BC ; B=Last row number to shift.
RET ;
; ---------------------------------------------------------
; Insert Character into Edit Buffer Row, Shifting Row Right
; ---------------------------------------------------------
; This routine shifts a byte into an edit buffer row, shifting all existing
; characters right until either the end of the row is reached or the specified
; end column is reached.
; Entry: DE=Start address of an edit buffer row.
; A=Character to shift into left of row.
; B=Column to start shifting at.
; Exit : A=Byte shifted out from last column.
; HL=Points byte after row (i.e. flag byte).
; Zero flag set if the character shifted out was a null ($00).
L16AC: PUSH DE ; Save DE.
LD H,$00 ;
LD L,B ; HL=Start column number.
L16B0: ADD HL,DE ; HL=Address of the starting column.
LD D,A ; Store the character to shift in.
LD A,B ; A=Start column number.
;Shift all bytes in the row to the right.
L16B3: LD E,(HL) ; Fetch a character from the row.
LD (HL),D ; Replace it with the character to shift in.
LD D,E ; Store the old character for use next time.
INC HL ; Point to the next column.
INC A ;
CP $20 ; End of row reached?
JR C,L16B3 ; Jump if not to shift the next character.
LD A,E ; A=Character that was shifted out.
CP $00 ; Return with zero flag set if the character was $00.
POP DE ; Restore DE
RET ;
; --------------------------------------------------------
; Insert Character into Edit Buffer Row, Shifting Row Left
; --------------------------------------------------------
; This routine shifts a byte into an edit buffer row, shifting all existing
; characters left until either the beginning of the row is reached or the specified
; end column is reached.
; Entry: DE=Start address of an edit buffer row.
; A=Character to shift into right of row.
; B=Column to stop shifting at.
; Exit : A=Byte shifted out.
; HL=Points byte before row.
; Zero flag set if the character shifted out was a null ($00).
L16C1: PUSH DE ; Save DE.
LD HL,$0020 ; 32 columns.
L16C5: ADD HL,DE ; Point to the flag byte for this row.
PUSH HL ; Save it.
LD D,A ; Store the character to shift in.
LD A,$1F ; Maximum of 31 shifts.
JR L16D3 ; Jump ahead to start shifting.
L16CC: LD E,(HL) ; Fetch a character from the row.
LD (HL),D ; Replace it with the character to shift in.
LD D,E ; Store the old character for use next time.
CP B ; End column reached?
JR Z,L16D6 ; Jump if so to exit.
DEC A ; Decrement column counter.
L16D3: DEC HL ; Point back a column.
JR L16CC ; Loop back to shift the next character.
L16D6: LD A,E ; A=Character that was shifted out.
CP $00 ; Return with zero flag set if the character was $00.
POP HL ; Fetch address of next flag byte for the row.
POP DE ; Restore DE.
RET ;
; =======================================================
; BASIC LINE AND COMMAND INTERPRETATION ROUTINES - PART 1
; =======================================================
; -----------------------
; The Syntax Offset Table
; -----------------------
; Similar in construction to the table in ROM 1 at $1A48.
L16DC: DEFB $B1 ; DEF FN -> $178D (ROM 0)
DEFB $C9 ; CAT -> $17A6 (ROM 0)
DEFB $BC ; FORMAT -> $179A (ROM 0)
DEFB $BE ; MOVE -> $179D (ROM 0)
DEFB $C3 ; ERASE -> $17A3 (ROM 0)
DEFB $AF ; OPEN # -> $1790 (ROM 0)
DEFB $B4 ; CLOSE # -> $1796 (ROM 0)
DEFB $93 ; MERGE -> $1776 (ROM 0)
DEFB $91 ; VERIFY -> $1775 (ROM 0)
DEFB $92 ; BEEP -> $1777 (ROM 0)
DEFB $95 ; CIRCLE -> $177B (ROM 0)
DEFB $98 ; INK -> $177F (ROM 0)
DEFB $98 ; PAPER -> $1780 (ROM 0)
DEFB $98 ; FLASH -> $1781 (ROM 0)
DEFB $98 ; BRIGHT -> $1782 (ROM 0)
DEFB $98 ; INVERSE -> $1783 (ROM 0)
DEFB $98 ; OVER -> $1784 (ROM 0)
DEFB $98 ; OUT -> $1785 (ROM 0)
DEFB $7F ; LPRINT -> $176D (ROM 0)
DEFB $81 ; LLIST -> $1770 (ROM 0)
DEFB $2E ; STOP -> $171E (ROM 0)
DEFB $6C ; READ -> $175D (ROM 0)
DEFB $6E ; DATA -> $1760 (ROM 0)
DEFB $70 ; RESTORE -> $1763 (ROM 0)
DEFB $48 ; NEW -> $173C (ROM 0)
DEFB $94 ; BORDER -> $1789 (ROM 0)
DEFB $56 ; CONTINUE -> $174C (ROM 0)
DEFB $3F ; DIM -> $1736 (ROM 0)
DEFB $41 ; REM -> $1739 (ROM 0)
DEFB $2B ; FOR -> $1724 (ROM 0)
DEFB $17 ; GO TO -> $1711 (ROM 0)
DEFB $1F ; GO SUB -> $171A (ROM 0)
DEFB $37 ; INPUT -> $1733 (ROM 0)
DEFB $77 ; LOAD -> $1774 (ROM 0)
DEFB $44 ; LIST -> $1742 (ROM 0)
DEFB $0F ; LET -> $170E (ROM 0)
DEFB $59 ; PAUSE -> $1759 (ROM 0)
DEFB $2B ; NEXT -> $172C (ROM 0)
DEFB $43 ; POKE -> $1745 (ROM 0)
DEFB $2D ; PRINT -> $1730 (ROM 0)
DEFB $51 ; PLOT -> $1755 (ROM 0)
DEFB $3A ; RUN -> $173F (ROM 0)
DEFB $6D ; SAVE -> $1773 (ROM 0)
DEFB $42 ; RANDOMIZE -> $1749 (ROM 0)
DEFB $0D ; IF -> $1715 (ROM 0) [No instruction fetch at $1708 hence ZX Interface 1 will not be paged in by this ROM. Credit: Paul Farrow].
DEFB $49 ; CLS -> $1752 (ROM 0)
DEFB $5C ; DRAW -> $1766 (ROM 0)
DEFB $44 ; CLEAR -> $174F (ROM 0)
DEFB $15 ; RETURN -> $1721 (ROM 0)
DEFB $5D ; COPY -> $176A (ROM 0)
; --------------------------
; The Syntax Parameter Table
; --------------------------
; Similar to the parameter table in ROM 1 at $1A7A.
L170E: DEFB $01 ; CLASS-01 LET
DEFB '=' ; $3D. '='
DEFB $02 ; CLASS-02
L1711: DEFB $06 ; CLASS-06 GO TO
DEFB $00 ; CLASS-00
DEFW GO_TO ; $1E67. GO TO routine in ROM 1.
L1715: DEFB $06 ; CLASS-06 IF
DEFB $CB ; 'THEN'
DEFB $0E ; CLASS-0E
DEFW L1967 ; New IF routine in ROM 0.
L171A: DEFB $06 ; CLASS-06 GO SUB
DEFB $0C ; CLASS-0C
DEFW L1A53 ; New GO SUB routine in ROM 0.
L171E: DEFB $00 ; CLASS-00 STOP
DEFW STOP ; $1CEE. STOP routine in ROM 1.
L1721: DEFB $0C ; CLASS-0C RETURN
DEFW L1A6F ; New RETURN routine in ROM 0.
L1724: DEFB $04 ; CLASS-04 FOR
DEFB '=' ; $3D. '='
DEFB $06 ; CLASS-06
DEFB $CC ; 'TO'
DEFB $06 ; CLASS-06
DEFB $0E ; CLASS-0E
DEFW L1981 ; New FOR routine in ROM 0.
L172C: DEFB $04 ; CLASS-04 NEXT
DEFB $00 ; CLASS-00
DEFW NEXT ; $1DAB. NEXT routine in ROM 1.
L1730: DEFB $0E ; CLASS-0E PRINT
DEFW L2178 ; New PRINT routine in ROM 0.
L1733: DEFB $0E ; CLASS-0E INPUT
DEFW L218C ; New INPUT routine in ROM 0.
L1736: DEFB $0E ; CLASS-0E DIM
DEFW L21D5 ; New DIM routine in ROM 0.
L1739: DEFB $0E ; CLASS-0E REM
DEFW L1862 ; New REM routine in ROM 0.
L173C: DEFB $0C ; CLASS-0C NEW
DEFW L21AA ; New NEW routine in ROM 0.
L173F: DEFB $0D ; CLASS-0D RUN
DEFW L1A02 ; New RUN routine in ROM 0.
L1742: DEFB $0E ; CLASS-0E LIST
DEFW L1B75 ; New LIST routine in ROM 0.
L1745: DEFB $08 ; CLASS-08 POKE
DEFB $00 ; CLASS-00
DEFW POKE ; $1E80. POKE routine in ROM 1.
L1749: DEFB $03 ; CLASS-03 RANDOMIZE
DEFW RANDOMIZE ; $1E4F. RANDOMIZE routine in ROM 1.
L174C: DEFB $00 ; CLASS-00 CONTINUE
DEFW CONTINUE ; $1E5F. CONTINUE routine in ROM 1.
L174F: DEFB $0D ; CLASS-0D CLEAR
DEFW L1A0D ; New CLEAR routine in ROM 0.
L1752: DEFB $00 ; CLASS-00 CLS
DEFW CLS ; $0D6B. CLS routine in ROM 1.
L1755: DEFB $09 ; CLASS-09 PLOT
DEFB $00 ; CLASS-00
DEFW PLOT ; $22DC. PLOT routine in ROM 1
L1759: DEFB $06 ; CLASS-06 PAUSE
DEFB $00 ; CLASS-00
DEFW PAUSE ; $1F3A. PAUSE routine in ROM 1.
L175D: DEFB $0E ; CLASS-0E READ
DEFW L19AB ; New READ routine in ROM 0.
L1760: DEFB $0E ; CLASS-0E DATA
DEFW L19EB ; New DATA routine in ROM 0.
L1763: DEFB $03 ; CLASS-03 RESTORE
DEFW RESTORE ; $1E42. RESTORE routine in ROM 1.
L1766: DEFB $09 ; CLASS-09 DRAW
DEFB $0E ; CLASS-0E
DEFW L21BE ; New DRAW routine in ROM 0.
L176A: DEFB $0C ; CLASS-0C COPY
DEFW L21A7 ; New COPY routine in ROM 0.
L176D: DEFB $0E ; CLASS-0E LPRINT
DEFW L2174 ; New LPRINT routine in ROM 0.
L1770: DEFB $0E ; CLASS-0E LLIST
DEFW L1B71 ; New LLIST routine in ROM 0.
L1773: DEFB $0B ; CLASS-0B SAVE
L1774: DEFB $0B ; CLASS-0B LOAD
L1775: DEFB $0B ; CLASS-0B VERIFY
L1776: DEFB $0B ; CLASS-0B MERGE
L1777: DEFB $08 ; CLASS-08 BEEP
DEFB $00 ; CLASS-00
DEFW BEEP ; $03F8. BEEP routine in ROM 1.
L177B: DEFB $09 ; CLASS-09 CIRCLE
DEFB $0E ; CLASS-0E
DEFW L21AE ; New CIRCLE routine in ROM 0.
L177F: DEFB $07 ; CLASS-07 INK
L1780: DEFB $07 ; CLASS-07 PAPER
L1781: DEFB $07 ; CLASS-07 FLASH
L1782: DEFB $07 ; CLASS-07 BRIGHT
L1783: DEFB $07 ; CLASS-07 INVERSE
L1784: DEFB $07 ; CLASS-07 OVER
L1785: DEFB $08 ; CLASS-08 OUT
DEFB $00 ; CLASS-00
DEFW COUT ; $1E7A. OUT routine in ROM 1.
L1789: DEFB $06 ; CLASS-06 BORDER
DEFB $00 ; CLASS-00
DEFW BORDER ; $2294. BORDER routine in ROM 1.
L178D: DEFB $0E ; CLASS-0E DEF FN
DEFW L1A8C ; New DEF FN routine in ROM 0.
L1790: DEFB $06 ; CLASS-06 OPEN #
DEFB ',' ; $2C. ','
DEFB $0A ; CLASS-0A
DEFB $00 ; CLASS-00
DEFW OPEN ; $1736. OPEN # routine in ROM 1.
L1796: DEFB $06 ; CLASS-06 CLOSE #
DEFB $00 ; CLASS-00
DEFW CLOSE ; $16E5. CLOSE # routine in ROM 1.
L179A: DEFB $0E ; CLASS-0E FORMAT
DEFW L0641 ; FORMAT routine in ROM 0.
L179D: DEFB $0A ; CLASS-0A MOVE
DEFB ',' ; $2C. ','
DEFB $0A ; CLASS-0A
DEFB $0C ; CLASS-0C
DEFW L1AF0 ; Just execute a RET.
L17A3: DEFB $0E ; CLASS-0E ERASE
DEFW L1C0C ; New ERASE routine in ROM 0.
L17A6: DEFB $0E ; CLASS-0E CAT
DEFW L1BE5 ; New CAT routine in ROM 0.
L17A9: DEFB $0C ; CLASS-0C SPECTRUM
DEFW L1B2B ; SPECTRUM routine in ROM 0.
L17AC: DEFB $0E ; CLASS-0E PLAY
DEFW L2317 ; PLAY routine in ROM 0.
; (From Logan & O'Hara's 48K ROM disassembly):
; The requirements for the different command classes are as follows:
; CLASS-00 - No further operands.
; CLASS-01 - Used in LET. A variable is required.
; CLASS-02 - Used in LET. An expression, numeric or string, must follow.
; CLASS-03 - A numeric expression may follow. Zero to be used in case of default.
; CLASS-04 - A single character variable must follow.
; CLASS-05 - A set of items may be given.
; CLASS-06 - A numeric expression must follow.
; CLASS-07 - Handles colour items.
; CLASS-08 - Two numeric expressions, separated by a comma, must follow.
; CLASS-09 - As for CLASS-08 but colour items may precede the expressions.
; CLASS-0A - A string expression must follow.
; CLASS-0B - Handles cassette/RAM disk routines.
; In addition the 128 adds the following classes:
; CLASS-0C - Like class 00 but calling ROM 0. (Used by SPECTRUM, MOVE, COPY, NEW, GO SUB, RETURN)
; CLASS-0D - Like class 06 but calling ROM 0. (Used by CLEAR, RUN)
; CLASS-0E - Handled in ROM 0. (Used by PLAY, ERASE, CAT, FORMAT, CIRCLE, LPRINT, LLIST, DRAW, DATA, READ, LIST, DIM, INPUT, PRINT, FOR, IF)
; ------------------------------------------
; The 'Main Parser' Of the BASIC Interpreter
; ------------------------------------------
; The parsing routine of the BASIC interpreter is entered at $17AF (ROM 0) when syntax is being checked,
; and at $1838 (ROM 0) when a BASIC program of one or more statements is to be executed.
; This code is similar to that in ROM 1 at $1B17.
L17AF: RES 7,(IY+$01) ; FLAGS. Signal 'syntax checking'.
RST 28H ;
DEFW E_LINE_NO ; $19FB. CH-ADD is made to point to the first code after any line number
XOR A ;
LD ($5C47),A ; SUBPPC. Set to $00.
DEC A ;
LD ($5C3A),A ; ERR_NR. Set to $FF.
JR L17C1 ; Jump forward to consider the first statement of the line.
; ------------------
; The Statement Loop
; ------------------
; Each statement is considered in turn until the end of the line is reached.
L17C0: RST 20H ; Advance CH-ADD along the line.
L17C1: RST 28H ;
DEFW SET_WORK ; $16BF. The work space is cleared.
INC (IY+$0D) ; SUBPPC. Increase SUBPPC on each passage around the loop.
JP M,L1912 ; Only '127' statements are allowed in a single line. Jump to report "C Nonsense in BASIC".
RST 18H ; Fetch a character.
LD B,$00 ; Clear the register for later.
CP $0D ; Is the character a 'carriage return'?
JP Z,L1863 ; jump if it is.
CP ':' ; $3A. Go around the loop again if it is a ':'.
JR Z,L17C0 ;
;A statement has been identified so, first, its initial command is considered
LD HL,L1821 ; Pre-load the machine stack with the return address.
PUSH HL ;
LD C,A ; Save the command temporarily
RST 20H ; in the C register whilst CH-ADD is advanced again.
LD A,C ;
SUB $CE ; Reduce the command's code by $CE giving the range indexed from $00.
JR NC,L17F4 ; Jump for DEF FN and above.
ADD A,$CE ;
LD HL,L17A9 ;
CP $A3 ; Is it 'SPECTRUM'?
JR Z,L1800 ; Jump if so into the scanning loop with this address.
LD HL,L17AC ;
CP $A4 ; Is it 'PLAY'?
JR Z,L1800 ; Jump if so into the scanning loop with this address.
JP L1912 ; Produce error report "C Nonsense in BASIC".
L17F4: LD C,A ; Move the command code to BC (B holds $00).
LD HL,L16DC ; The base address of the syntax offset table.
ADD HL,BC ;
LD C,(HL) ;
ADD HL,BC ; Find address for the command's entries in the parameter table.
JR L1800 ; Jump forward into the scanning loop with this address.
;Each of the command class routines applicable to the present command are executed in turn.
;Any required separators are also considered.
L17FD: LD HL,($5C74) ; T_ADDR. The temporary pointer to the entries in the parameter table.
L1800: LD A,(HL) ; Fetch each entry in turn.
INC HL ; Update the pointer to the entries for the next pass.
LD ($5C74),HL ; T_ADDR.
LD BC,L17FD ; Pre-load the machine stack with the return address.
PUSH BC ;
LD C,A ; Copy the entry to the C register for later.
CP $20 ;
JR NC,L181A ; Jump forward if the entry is a 'separator'.
LD HL,L18B5 ; The base address of the 'command class' table.
LD B,$00 ;
ADD HL,BC ; Index into the table.
LD C,(HL) ;
ADD HL,BC ; HL=base + code + (base + code).
PUSH HL ; HL=The starting address of the required command class routine.
RST 18H ; Before making an indirect jump to the command class routine pass the command code
DEC B ; to the A register and set the B register to $FF.
RET ; Return to the stacked address.
; --------------------------
; The 'Separator' Subroutine
; --------------------------
; The report 'Nonsense in BASIC is given if the required separator is not present.
; But note that when syntax is being checked the actual report does not appear on the screen - only the 'error marker'.
; This code is similar to that in ROM 1 at $1B6F.
L181A: RST 18H ; The current character is
CP C ; fetched and compared to the entry in the parameter table.
JP NZ,L1912 ; Give the error report if there is not a match.
RST 20H ; Step past a correct character
RET ; and return.
; ---------------------------------
; The 'Statement Return' Subroutine
; ---------------------------------
; After the correct interpretation of a statement, a return is made to this entry point.
; This code is similar to that in ROM 1 at $1B76.
L1821: CALL L05D6 ; Check for BREAK
JR C,L182A ; [This code is redundant since the BREAK key check routine at L05D6 (ROM 0)
; will automatically produce the error report code if BREAK is being pressed
; and so 6 bytes could have been saved here. Credit: Paul Farrow].
CALL L05AC ; Produce error report.
DEFB $14 ; "L Break into program"
L182A: BIT 7,(IY+$0A) ; NSPPC - statement number in line to be jumped to
JP NZ,L18A8 ; Jump forward if there is not a 'jump' to be made.
LD HL,($5C42) ; NEWPPC, line number to be jumped to.
BIT 7,H ;
JR Z,L184C ; Jump forward unless dealing with a further statement in the editing area.
; --------------------------
; The 'Line Run' Entry Point
; --------------------------
; This entry point is used wherever a line in the editing area is to be 'run'.
; In such a case the syntax/run flag (bit 7 of FLAGS) will be set.
; The entry point is also used in the syntax checking of a line in the editing area
; that has more than one statement (bit 7 of FLAGS will be reset).
; This code is similar to that in ROM 1 at $1B8A.
L1838: LD HL,$FFFE ; A line in the editing area is considered as line '-2'.
LD ($5C45),HL ; PPC.
LD HL,($5C61) ; WORKSP. Make HL point to the end marker of the editing area.
DEC HL ;
LD DE,($5C59) ; E_LINE. Make DE point to the location before the end marker of the editing area.
DEC DE ;
LD A,($5C44) ; NSPPC. Fetch the number of the next statement to be handled.
JR L1882 ; Jump forward.
; -------------------------
; The 'Line New' Subroutine
; -------------------------
; There has been a jump in the program and the starting address of the new line has to be found.
; This code is similar to that in ROM 1 at 1B9E.
L184C: RST 28H ;
DEFW LINE_ADDR ; $196E. The starting address of the line, or the 'first line after' is found.
LD A,($5C44) ; NSPPC. Collect the statement number.
JR Z,L1870 ; Jump forward if the required line was found.
AND A ; Check the validity of the statement number - must be zero.
JR NZ,L189D ; Jump if not to produce error report "N Statement lost".
LD B,A ; Also check that the 'first
LD A,(HL) ; line after' is not after the
AND $C0 ; actual 'end of program'.
LD A,B ;
JR Z,L1870 ; Jump forward with valid addresses; otherwise signal the error 'OK'.
CALL L05AC ; Produce error report.
DEFB $FF ; "0 OK"
; -----------
; REM Routine
; -----------
; The return address to STMT-RET is dropped which has the effect of forcing the rest of the
; line to be ignored.
; This code is similar to that in ROM 1 at $1BB2.
L1862: POP BC ; Drop the statement return address.
; ----------------------
; The 'Line End' Routine
; ----------------------
; If checking syntax a simple return is made but when 'running' the address held by NXTLIN
; has to be checked before it can be used.
; This code is similar to that in ROM 1 at $1BB3.
L1863: BIT 7,(IY+$01) ;
RET Z ; Return if syntax is being checked.
LD HL,($5C55) ; NXTLIN.
LD A,$C0 ; Return if the address is after the end of the program - the 'run' is finished.
AND (HL) ;
RET NZ ;
XOR A ; Signal 'statement zero' before proceeding.
; ----------------------
; The 'Line Use' Routine
; ----------------------
; This routine has three functions:
; i. Change statement zero to statement '1'.
; ii. Find the number of the new line and enter it into PPC.
; iii. Form the address of the start of the line after.
; This code is similar to that in ROM 1 at $1BBF.
L1870: CP $01 ; Statement zero becomes statement 1.
ADC A,$00 ;
LD D,(HL) ; The line number of the line to be used is collected and
INC HL ; passed to PPC.
LD E,(HL) ;
LD ($5C45),DE ; PPC.
INC HL ;
LD E,(HL) ; Now find the 'length' of the line.
INC HL ;
LD D,(HL) ;
EX DE,HL ; Switch over the values.
ADD HL,DE ; Form the address of the start of the line after in HL and the
INC HL ; location before the 'next' line's first character in DE.
; -----------------------
; The 'Next Line' Routine
; -----------------------
; On entry the HL register pair points to the location after the end of the 'next' line
; to be handled and the DE register pair to the location before the first character of the line.
; This applies to lines in the program area and also to a line in the editing area - where the
; next line will be the same line again whilst there are still statements to be interpreted.
; This code is similar to that in ROM 1 at $1BD1.
L1882: LD ($5C55),HL ; NXTLIN. Set NXTLIN for use once the current line has been completed.
EX DE,HL ;
LD ($5C5D),HL ; CH_ADD. CH_ADD points to the location before the first character to be considered.
LD D,A ; The statement number is fetched.
LD E,$00 ; The E register is cleared in case the 'Each Statement' routine is used.
LD (IY+$0A),$FF ; NSPPC. Signal 'no jump'.
DEC D ;
LD (IY+$0D),D ; SUB_PPC. Statement number-1.
JP Z,L17C0 ; Jump if the first statement.
INC D ; For later statements the 'starting address' has to be found.
RST 28H ;
DEFW EACH_STMT ; $198B.
JR Z,L18A8 ; Jump forward unless the statement does not exist.
L189D: CALL L05AC ; Produce error report.
DEFB $16 ; "N Statement lost"
; --------------------------
; The 'CHECK-END' Subroutine
; --------------------------
; This is called when the syntax of the edit-line is being checked. The purpose of the routine is to
; give an error report if the end of a statement has not been reached and to move on to the next
; statement if the syntax is correct.
; The routine is the equivalent of routine CHECK_END in ROM 1 at $1BEE.
L18A1: BIT 7,(IY+$01) ; Very like CHECK-END at 1BEE in ROM 1
RET NZ ; Return unless checking syntax.
POP BC ; Drop scan loop and statement return addresses.
POP BC ;
; -----------------------
; The 'STMT-NEXT' Routine
; -----------------------
; If the present character is a 'carriage return' then the 'next statement' is on the 'next line',
; if ':' it is on the same line; but if any other character is found then there is an error in syntax.
; The routine is the equivalent of routine STMT_NEXT in ROM 1 at $1BF4.
L18A8: RST 18H ; Fetch the present character.
CP $0D ; Consider the 'next line' if
JR Z,L1863 ; it is a 'carriage return'.
CP ':' ; $3A. Consider the 'next statement'
JP Z,L17C0 ; if it is a ':'.
JP L1912 ; Otherwise there has been a syntax error so produce "C Nonsense in BASIC".
; -------------------------
; The 'Command Class' Table
; -------------------------
L18B5: DEFB L18D9-$ ; CLASS-00 -> L18D9 = $24
DEFB L18F9-$ ; CLASS-01 -> L18F9 = $43
DEFB L18FD-$ ; CLASS-02 -> L18FD = $46
DEFB L18D6-$ ; CLASS-03 -> L18D6 = $1E
DEFB L1905-$ ; CLASS-04 -> L1905 = $4C
DEFB L18DA-$ ; CLASS-05 -> L18DA = $20
DEFB L190E-$ ; CLASS-06 -> L190E = $53
DEFB L191A-$ ; CLASS-07 -> L191A = $5E
DEFB L190A-$ ; CLASS-08 -> L190A = $4D
DEFB L1944-$ ; CLASS-09 -> L1944 = $86
DEFB L1916-$ ; CLASS-0A -> L1916 = $57
DEFB L1948-$ ; CLASS-0B -> L1948 = $88
DEFB L18C7-$ ; CLASS-0C -> L18C7 = $06
DEFB L18C4-$ ; CLASS-0D -> L18C4 = $02
DEFB L18C8-$ ; CLASS-0E -> L18C8 = $05
; -----------------------------------
; The 'Command Classes - 0C, 0D & 0E'
; -----------------------------------
; For commands of class-0D a numeric expression must follow.
L18C4: RST 28H ; Code 0D enters here.
DEFW FETCH_NUM ; $1CDE.
;The commands of class-0C must not have any operands. e.g. SPECTRUM.
L18C7: CP A ; Code 0C enters here. Set zero flag.
;The commands of class-0E may be followed by a set of items. e.g. PLAY.
L18C8: POP BC ; Code 0E enters here.
; Retrieve return address.
CALL Z,L18A1 ; If handling commands of classes 0C & 0D and syntax is being
; checked move on now to consider the next statement.
EX DE,HL ; Save the line pointer in DE.
; After the command class entries and the separator entries in the parameter table have
; been considered the jump to the appropriate command routine is made.
; The routine is similar to JUMP-C-R in ROM 1 at $1C16.
LD HL,($5C74) ; T_ADDR.
LD C,(HL) ; Fetch the pointer to the entries in the parameter table
INC HL ; and fetch the address of the
LD B,(HL) ; required command routine.
EX DE,HL ; Exchange the pointers back.
PUSH BC ; Make an indirect jump to the command routine.
RET ;
; -----------------------------------
; The 'Command Classes - 00, 03 & 05'
; -----------------------------------
; These routines are the equivalent of the routines in ROM 1 starting at $1C0D.
; The commands of class-03 may, or may not, be followed by a number. e.g. RUN & RUN 200.
L18D6: RST 28H ; Code 03 enters here.
DEFW FETCH_NUM ; $1CDE. A number is fetched but zero is used in cases of default.
;The commands of class-00 must not have any operands. e.g. COPY & CONTINUE.
L18D9: CP A ; Code 00 enters here. Set the zero flag.
;The commands of class-05 may be followed by a set of items. e.g. PRINT & PRINT "222".
L18DA: POP BC ; Code 05 enters here. Drop return address.
CALL Z,L18A1 ; If handling commands of classes 00 & 03 and syntax is being
; checked move on now to consider the next statement.
EX DE,HL ; Save the line pointer in DE.
LD HL,($5C74) ; T_ADDR. Fetch the pointer to the entries in the parameter table.
LD C,(HL) ;
INC HL ;
LD B,(HL) ; Fetch the address of the required command routine.
EX DE,HL ; Exchange the pointers back.
PUSH HL ; Save command routine address.
LD HL,L18F8 ; The address to return to (the RET below).
LD (RETADDR),HL ; $5B5A. Store the return address.
LD HL,YOUNGER ; $5B14. Paging subroutine.
EX (SP),HL ; Replace the return address with the address of the YOUNGER routine.
PUSH HL ; Save the original top stack item.
LD H,B ;
LD L,C ; HL=Address of command routine.
EX (SP),HL ; Put onto the stack so that an indirect jump will be made to it.
JP SWAP ; $5B00. Switch to other ROM and 'return' to the command routine.
;Comes here after ROM 1 has been paged in, the command routine called, ROM 0 paged back in.
L18F8: RET ; Simply make a return.
; ------------------------
; The 'Command Class - 01'
; ------------------------
; Command class 01 is concerned with the identification of the variable in a LET, READ or INPUT statement.
L18F9: RST 28H ; Delegate handling to ROM 1.
DEFW CLASS_01 ; $1C1F.
RET ;
; ------------------------
; The 'Command Class - 02'
; ------------------------
; Command class 02 is concerned with the actual calculation of the value to be assigned in a LET statement.
L18FD: POP BC ; Code 02 enters here. Delegate handling to ROM 1.
RST 28H ;
DEFW VAL_FET_1 ; $1C56. "... used by LET, READ and INPUT statements to
; first evaluate and then assign values to the
; previously designated variable" (Logan/O'Hara)
CALL L18A1 ; Move on to the next statement if checking syntax
RET ; else return here.
; ------------------------
; The 'Command Class - 04'
; ------------------------
; The command class 04 entry point is used by FOR & NEXT statements.
L1905: RST 28H ; Code 04 enters here. Delegate handling to ROM 1.
DEFW CLASS_04 ; $1C6C.
RET ;
; ------------------------
; The 'Command Class - 08'
; ------------------------
; Command class 08 allows for two numeric expressions, separated by a comma, to be evaluated.
L1909: RST 20H ; [Redundant byte]
L190A: RST 28H ; Delegate handling to ROM 1.
DEFW EXPT_2NUM ; $1C7A.
RET ;
; ------------------------
; The 'Command Class - 06'
; ------------------------
; Command class 06 allows for a single numeric expression to be evaluated.
L190E: RST 28H ; Code 06 enters here. Delegate handling to ROM 1.
DEFW EXPT_1NUM ; $1C82.
RET ;
; ----------------------------
; Report C - Nonsense in BASIC
; ----------------------------
L1912: CALL L05AC ; Produce error report. [Could have saved 4 bytes by using the identical routine at L1219 (ROM 0) instead]
DEFB $0B ; "C Nonsense in BASIC"
; ------------------------
; The 'Command Class - 0A'
; ------------------------
; Command class 0A allows for a single string expression to be evaluated.
L1916: RST 28H ; Code 0A enters here. Delegate handling to ROM 1.
DEFW EXPT_EXP ; $1C8C.
RET ;
; ------------------------
; The 'Command Class - 07'
; ------------------------
; Command class 07 is the command routine for the six colour item commands.
; Makes the current temporary colours permanent.
L191A: BIT 7,(IY+$01) ; The syntax/run flag is read.
RES 0,(IY+$02) ; TV_FLAG. Signal 'main screen'.
JR Z,L1927 ; Jump ahead if syntax checking.
RST 28H ; Only during a 'run' call TEMPS to ensure the temporary
DEFW TEMPS ; $0D4D. colours are the main screen colours.
L1927: POP AF ; Drop the return address.
LD A,($5C74) ; T_ADDR.
SUB (L177F & $00FF)+$28 ; Reduce to range $D9-$DE which are the token codes for INK to OVER.
RST 28H ;
DEFW CO_TEMP_4 ; $21FC. Change the temporary colours as directed by the BASIC statement.
CALL L18A1 ; Move on to the next statement if checking syntax.
LD HL,($5C8F) ; ATTR_T. Now the temporary colour
LD ($5C8D),HL ; ATTR_P. values are made permanent
LD HL,$5C91 ; P_FLAG.
LD A,(HL) ; Value of P_FLAG also has to be considered.
;The following instructions cleverly copy the even bits of the supplied byte to the odd bits.
;In effect making the permanent bits the same as the temporary ones.
RLCA ; Move the mask leftwards.
XOR (HL) ; Impress onto the mask
AND $AA ; only the even bits of the
XOR (HL) ; other byte.
LD (HL),A ; Restore the result.
RET ;
; ------------------------
; The 'Command Class - 09'
; ------------------------
; This routine is used by PLOT, DRAW & CIRCLE statements in order to specify the default conditions
; of 'FLASH 8; BRIGHT 8; PAPER 8;' that are set up before any embedded colour items are considered.
L1944: RST 28H ; Code 09 enters here. Delegate handling to ROM 1.
DEFW CLASS_09 ; $1CBE.
RET
; ------------------------
; The 'Command Class - 0B'
; ------------------------
; This routine is used by SAVE, LOAD, VERIFY & MERGE statements.
L1948: POP AF ; Drop the return address.
LD A,(FLAGS3) ; $5B66.
AND $0F ; Clear LOAD/SAVE/VERIFY/MERGE indication bits.
LD (FLAGS3),A ; $5B66.
LD A,($5C74) ; T_ADDR-lo.
SUB 1+(L1773 & $00FF) ; Correct by $74 so that SAVE = $00, LOAD = $01, VERIFY = $02, MERGE = $03.
LD ($5C74),A ; T_ADDR-lo.
JP Z,L11EB ; Jump to handle SAVE.
DEC A ;
JP Z,L11F2 ; Jump to handle LOAD.
DEC A ;
JP Z,L11F9 ; Jump to handle VERIFY.
JP L1200 ; Jump to handle MERGE.
; ----------
; IF Routine
; ----------
; On entry the value of the expression between the IF and the THEN is the
; 'last value' on the calculator stack. If this is logically true then the next
; statement is considered; otherwise the line is considered to have been finished.
L1967: POP BC ; Drop the return address.
BIT 7,(IY+$01) ;
JR Z,L197E ; Jump forward if checking syntax.
;Now 'delete' the last value on the calculator stack
L196E: LD HL,($5C65) ; STKEND.
LD DE,$FFFB ; -5
ADD HL,DE ; The present 'last value' is deleted.
LD ($5C65),HL ; STKEND. HL point to the first byte of the value.
RST 28H ;
DEFW TEST_ZERO ; $34E9. Is the value zero?
JP C,L1863 ; If the value was 'FALSE' jump to the next line.
L197E: JP L17C1 ; But if 'TRUE' jump to the next statement (after the THEN).
; -----------
; FOR Routine
; -----------
; This command routine is entered with the VALUE and the LIMIT of the FOR statement already
; on the top of the calculator stack.
L1981: CP $CD ; Jump forward unless a 'STEP' is given.
JR NZ,L198E ;
RST 20H ; Advance pointer
CALL L190E ; Indirectly call EXPT_1NUM in ROM 1 to get the value of the STEP.
CALL L18A1 ; Move on to the next statement if checking syntax.
JR L19A6 ; Otherwise jump forward.
;There has not been a STEP supplied so the value '1' is to be used.
L198E: CALL L18A1 ; Move on to the next statement if checking syntax.
LD HL,($5C65) ; STKEND.
LD (HL),$00 ;
INC HL ;
LD (HL),$00 ;
INC HL ;
LD (HL),$01 ;
INC HL ;
LD (HL),$00 ;
INC HL ;
LD (HL),$00 ; Place a value of 1 on the calculator stack.
INC HL ;
LD ($5C65),HL ; STKEND.
;The three values on the calculator stack are the VALUE (v), the LIMIT (l) and the STEP (s).
;These values now have to be manipulated. Delegate handling to ROM 1.
L19A6: RST 28H ;
DEFW F_REORDER ; $1D16.
RET ;
; ------------
; READ Routine
; ------------
L19AA: RST 20H ; Come here on each pass, after the first, to move along the READ statement.
L19AB: CALL L18F9 ; Indirectly call CLASS_01 in ROM 1 to consider whether the variable has
; been used before, and find the existing entry if it has.
BIT 7,(IY+$01) ;
JR Z,L19E2 ; Jump forward if checking syntax.
RST 18H ; Save the current pointer CH_ADD in X_PTR.
LD ($5C5F),HL ; X_PTR.
LD HL,($5C57) ; DATADD.
LD A,(HL) ; Fetch the current DATA list pointer
CP $2C ; and jump forward unless a new
JR Z,L19CB ; DATA statement has to be found.
LD E,$E4 ; The search is for 'DATA'.
RST 28H ;
DEFW LOOK_PROG ; $1D86.
JR NC,L19CB ; Jump forward if the search is successful.
CALL L05AC ; Produce error report.
DEFB $0D ; "E Out of Data"
; Pick up a value from the DATA list.
L19CB: INC HL ; Advance the pointer along the DATA list.
LD ($5C5D),HL ; CH_ADD.
LD A,(HL) ;
RST 28H ;
DEFW VAL_FET_1 ; $1C56. Fetch the value and assign it to the variable.
RST 18H ;
LD ($5C57),HL ; DATADD.
LD HL,($5C5F) ; X_PTR. Fetch the current value of CH_ADD and store it in DATADD.
LD (IY+$26),$00 ; X_PTR_hi. Clear X_PTR.
LD ($5C5D),HL ; CH_ADD. Make CH-ADD once again point to the READ statement.
LD A,(HL) ;
L19E2: RST 18H ; GET the present character
CP ',' ; $2C. Check if it is a ','.
L19E5: JR Z,L19AA ; If it is then jump back as there are further items.
CALL L18A1 ; Return if checking syntax
RET ; or here if not checking syntax.
; ------------
; DATA Routine
; ------------
; During syntax checking a DATA statement is checked to ensure that it contains a series
; of valid expressions, separated by commas. But in 'run-time' the statement is passed by.
L19EB: BIT 7,(IY+$01) ; Jump forward unless checking syntax.
JR NZ,L19FC ;
;A loop is now entered to deal with each expression in the DATA statement.
L19F1: RST 28H ;
DEFW SCANNING ; $24FB. Scan the next expression.
CP ',' ; $2C. Check for the correct separator ','.
CALL NZ,L18A1 ; but move on to the next statement if not matched.
RST 20H ; Whilst there are still expressions to be checked
JR L19F1 ; go around again.
;The DATA statement has to be passed-by in 'run-time'.
L19FC: LD A,$E4 ; It is a 'DATA' statement that is to be passed-by.
;On entry the A register will hold either the token 'DATA' or the token 'DEF FN'
;depending on the type of statement that is being 'passed-by'.
L19FE: RST 28H ;
DEFW PASS_BY ; $1E39. Delegate handling to ROM 1.
RET
; -----------
; RUN Routine
; -----------
; The parameter of the RUN command is passed to NEWPPC by calling the GO TO command routine.
; The operations of 'RESTORE 0' and 'CLEAR 0' are then performed before a return is made.
L1A02: RST 28H
DEFW GO_TO ; $1E67.
LD BC,$0000 ; Now perform a 'RESTORE 0'.
RST 28H
DEFW REST_RUN ; $1E45.
JR L1A10 ; Exit via the CLEAR command routine.
; -------------
; CLEAR Routine
; -------------
; This routine allows for the variables area to be cleared, the display area cleared
; and RAMTOP moved. In consequence of the last operation the machine stack is rebuilt
; thereby having the effect of also clearing the GO SUB stack.
L1A0D: RST 28H ;
DEFW FIND_INT2 ; $1E99. Fetch the operand - using zero by default.
L1A10: LD A,B ; Jump forward if the operand is
OR C ; other than zero. When called
JR NZ,L1A18 ; from RUN there is no jump.
LD BC,($5CB2) ; RAMTOP. Use RAMTOP if the parameter is 0.
L1A18: PUSH BC ; BC = Address to clear to. Save it.
LD DE,($5C4B) ; VARS.
LD HL,($5C59) ; E LINE.
DEC HL ;
RST 28H ; Delete the variables area.
DEFW RECLAIM ; $19E5.
RST 28H ; Clear the screen
DEFW CLS ; $0D6B.
;The value in the BC register pair which will be used as RAMTOP is tested to ensure it
;is neither too low nor too high.
LD HL,($5C65) ; STKEND. The current value of STKEND
LD DE,$0032 ; is increased by 50 before
ADD HL,DE ; being tested. This forms the
POP DE ; ADE = address to clear to lower limit.
SBC HL,DE ;
JR NC,L1A3B ; Ramtop no good.
LD HL,($5CB4) ; P_RAMT. For the upper test the value
AND A ; for RAMTOP is tested against P_RAMT.
SBC HL,DE ;
JR NC,L1A3F ; Jump forward if acceptable.
L1A3B: CALL L05AC ; Produce error report.
DEFB $15 ; "M Ramtop no good"
L1A3F: LD ($5CB2),DE ; RAMTOP.
POP DE ; Retrieve interpreter return address from stack
POP HL ; Retrieve 'error address' from stack
POP BC ; Retrieve the GO SUB stack end marker.
; [*BUG* - It is assumed that the top of the GO SUB stack will be empty and hence only
; contain the end marker. This will not be the case if CLEAR is used within a subroutine,
; in which case BC will now hold the calling line number and this will be stacked in place
; of the end marker. When a RETURN command is encountered, the GO SUB stack appears to contain
; an entry since the end marker was not the top item. An attempt to return is therefore made.
; The CLEAR command handler within the 48K Spectrum ROM does not make any assumption about
; the contents of the GO SUB stack and instead always re-inserts the end marker. The bug could
; be fixed by inserting the line LD BC,$3E00 after the POP BC. Credit: Ian Collier (+3), Paul Farrow (128)]
LD SP,($5CB2) ; RAMTOP.
INC SP ;
PUSH BC ; Stack the GO SUB stack end marker.
PUSH HL ; Stack 'error address'.
LD ($5C3D),SP ; ERR_SP.
PUSH DE ; Stack the interpreter return address.
RET
; --------------
; GO SUB Routine
; --------------
; The present value of PPC and the incremented value of SUBPPC are stored on the GO SUB stack.
L1A53: POP DE ; Save the return address.
LD H,(IY+$0D) ; SUBPPC. Fetch the statement number and increment it.
INC H ;
EX (SP),HL ; Exchange the 'error address' with the statement number.
INC SP ; Reclaim the use of a location.
LD BC,($5C45) ; PPC.
PUSH BC ; Next save the present line number.
PUSH HL ; Return the 'error address' to the machine stack
LD ($5C3D),SP ; ERR-SP. and reset ERR-SP to point to it.
PUSH DE ; Stack the return address.
RST 28H ;
DEFW GO_TO ; $1E67. Now set NEWPPC & NSPPC to the required values.
LD BC,$0014 ; But before making the jump make a test for room.
RST 28H ;
DEFW TEST_ROOM ; $1F05. Will automatically produce error '4' if out of memory.
RET
; --------------
; RETURN Routine
; --------------
; The line number and the statement number that are to be made the object of a 'return'
; are fetched from the GO SUB stack.
L1A6F: POP BC ; Fetch the return address.
POP HL ; Fetch the 'error address'.
POP DE ; Fetch the last entry on the GO SUB stack.
LD A,D ; The entry is tested to see if
CP $3E ; it is the GO SUB stack end marker.
JR Z,L1A86 ; Jump if it is.
DEC SP ; The full entry uses three locations only.
EX (SP),HL ; Exchange the statement number with the 'error address'.
EX DE,HL ; Move the statement number.
LD ($5C3D),SP ; ERR_SP. Reset the error pointer.
PUSH BC ; Replace the return address.
LD ($5C42),HL ; NEWPPC. Enter the line number.
LD (IY+$0A),D ; NSPPC. Enter the statement number.
RET ;
L1A86: PUSH DE ; Replace the end marker and
PUSH HL ; the 'error address'.
CALL L05AC ; Produce error report.
DEFB $06 ; "7 RETURN without GO SUB"
; --------------
; DEF FN Routine
; --------------
; During syntax checking a DEF FN statement is checked to ensure that it has the correct form.
; Space is also made available for the result of evaluating the function.
; But in 'run-time' a DEF FN statement is passed-by.
L1A8C: BIT 7,(IY+$01)
JR Z,L1A97 ; Jump forward if checking syntax.
LD A,$CE ; Otherwise bass-by the
JP L19FE ; 'DEF FN' statement.
;First consider the variable of the function.
L1A97: SET 6,(IY+$01) ; Signal 'a numeric variable'.
RST 28H ;
DEFW ALPHA ; $2C8D. Check that the present code is a letter.
JR NC,L1AB6 ; Jump forward if not.
RST 20H ; Fetch the next character.
CP '$' ; $24.
JR NZ,L1AAA ; Jump forward unless it is a '$'.
RES 6,(IY+$01) ; Change bit 6 as it is a string variable.
RST 20H ; Fetch the next character.
L1AAA: CP '(' ; $28. A '(' must follow the variable's name.
JR NZ,L1AEA ; Jump forward if not.
RST 20H ; Fetch the next character
CP ')' ; $29. Jump forward if it is a ')'
JR Z,L1AD3 ; as there are no parameters of the function.
;A loop is now entered to deal with each parameter in turn.
L1AB3: RST 28H ;
DEFW ALPHA ; $2C8D.
L1AB6: JP NC,L1912 ; The present code must be a letter.
EX DE,HL ; Save the pointer in DE.
RST 20H ; Fetch the next character.
CP '$' ; $24.
JR NZ,L1AC1 ; Jump forward unless it is a '$'.
EX DE,HL ; Otherwise save the new pointer in DE instead.
RST 20H ; Fetch the next character.
L1AC1: EX DE,HL ; Move the pointer to the last character of the name to HL.
LD BC,$0006 ; Now make six locations after that last character.
RST 28H ;
DEFW MAKE_ROOM ; $1655.
INC HL ;
INC HL ;
LD (HL),$0E ; Enter a 'number marker' into the first of the new locations.
CP ',' ; $2C. If the present character is a ',' then jump back as
JR NZ,L1AD3 ; there should be a further parameter.
RST 20H ;
JR L1AB3 ; Otherwise jump out of the loop.
;Next the definition of the function is considered.
L1AD3: CP ')' ; $29. Check that the ')' does exist.
JR NZ,L1AEA ; Jump if not.
RST 20H ; The next character is fetched.
CP '=' ; $3D. It must be an '='.
JR NZ,L1AEA ; Jump if not.
RST 20H ; Fetch the next character.
LD A,($5C3B) ; FLAGS.
PUSH AF ; Save the nature (numeric or string) of the variable
RST 28H ;
DEFW SCANNING ; $24FB. Now consider the definition as an expression.
POP AF ; Fetch the nature of the variable.
XOR (IY+$01) ; FLAGS. Check that it is of the same type
AND $40 ; as found for the definition.
L1AEA: JP NZ,L1912 ; Give an error report if required.
CALL L18A1 ; Move on to consider the next statement in the line.
; ------------
; MOVE Routine
; ------------
L1AF0: RET ; Simply return.
; ======================
; MENU ROUTINES - PART 1
; ======================
; ---------------
; Run Tape Loader
; ---------------
; Used by Main Menu - Tape Loader option.
L1AF1: LD HL,$EC0E ; Fetch mode.
LD (HL),$FF ; Set Tape Loader mode.
CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
RST 28H ;
DEFW SET_MIN ; $16B0. Clear out editing area.
LD HL,($5C59) ; E_LINE.
LD BC,$0003 ; Create 3 bytes of space for the LOAD "" command.
RST 28H ;
DEFW MAKE_ROOM ; $1655.
LD HL,L1B6E ; Address of command bytes for LOAD "".
LD DE,($5C59) ; E_LINE.
LD BC,$0003 ;
LDIR ; Copy LOAD "" into the line editing area.
CALL L026B ; Parse and execute the BASIC line.
; [Will not return here but will exit via the error handler routine]
; -----------------------
; List Program to Printer
; -----------------------
; Used by Edit Menu - Print option.
L1B14: CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
RST 28H ;
DEFW SET_MIN ; $16B0. Clear out editing area.
LD HL,($5C59) ; E_LINE.
LD BC,$0001 ; Create 1 byte of space.
RST 28H ;
DEFW MAKE_ROOM ; $1655.
LD HL,($5C59) ; E_LINE.
LD (HL),$E1 ; Copy LLIST into the line editing area.
CALL L026B ; Parse and execute the BASIC line.
; [Will not return here but will exit via the error handler routine]
; =======================================================
; BASIC LINE AND COMMAND INTERPRETATION ROUTINES - PART 2
; =======================================================
; ----------------
; SPECTRUM Routine
; ----------------
; Return to 48K BASIC Mode. This routine will force caps lock is off.
L1B2B: CALL L1B53 ; Overwrite 'P' channel data to use the ZX Printer.
LD SP,($5C3D) ; ERR_SP. Purge the stack.
POP HL ; Remove error handler address.
LD HL,MAIN_4 ; $1303. The main execution loop within ROM 1.
PUSH HL ;
LD HL,PRINT_A_1+$0003 ; $0013. Address of a $FF byte within ROM 1, used to generate error report "0 OK".
PUSH HL ;
LD HL,ERROR_1 ; $0008. The address of the error handler within ROM 1.
PUSH HL ;
; [*BUG* - Although the channel 'P' information has been reconfigured to use the ZX Printer, the ZX printer buffer and
; associated system variables still need to be cleared. Failure to do so means that the first use of the ZX Printer will
; cause garbage to the printed, i.e. the paging routines and new system variables still present in the ZX Printer buffer.
; Subsequently printer output will then be ok since the ZX Printer buffer and system variables will be cleared.
; Worse still, there is the possibility that new data to be printed will be inserted beyond the ZX Printer buffer since
; ROM 1 does not trap whether the ZX Printer system variable PR_POSN and PR_CC hold invalid values. The bug can be fixed
; by inserting the following instructions, which cause the ZX Printer buffer to be cleared immediately after switching to
; ROM 1 and before the error report "0 OK" is produced. Credit: Paul Farrow and Andrew Owen.
;
; LD HL,CLEAR_PRB ; Address of the routine in ROM 1 to clear the ZX Printer buffer and associated system variables.
; PUSH HL ;
; SET 1,(IY+$01) ; FLAGS. Signal the printer is in use.]
LD A,$20 ; Force 48K mode.
LD (BANK_M),A ; $5B5C.
JP SWAP ; $5B00. Swap to ROM 1 and return via a RST $08 / DEFB $FF.
; ======================
; MENU ROUTINES - PART 2
; ======================
; ---------------------------
; Main Menu - 48 BASIC Option
; ---------------------------
L1B47: LD HL,$0000 ; Stack a $0000 address to return to.
PUSH HL ;
LD A,$20 ; Force 48 mode.
LD (BANK_M),A ; $5B5C
JP SWAP ; $5B00. Swap to ROM 1, return to L0000.
; --------------------
; Set 'P' Channel Data
; --------------------
; This routine overwrites the 'P' channel data with the 'S' channel data, i.e. the default values when using the ZX Printer.
L1B53: LD HL,($5C4F) ; CHANS.
LD DE,$0005 ;
ADD HL,DE ; HL=Address 'S' channel data.
LD DE,$000A ;
EX DE,HL ; HL=$000A, DE=Address 'S' channel data.
ADD HL,DE ; HL=Address 'P' channel data.
EX DE,HL ; DE=Address 'P' channel data, HL=Address 'S' channel data.
LD BC,$0004 ;
LDIR ; Copy the 'S' channel data over the 'P' channel data.
RES 3,(IY+$30) ; FLAGS2. Signal caps lock unset. [Not really necessary for switching back to 48 BASIC mode]
RES 4,(IY+$01) ; FLAGS. Signal not 128K mode.
RET ;
; ---------------------
; LOAD "" Command Bytes
; ---------------------
; Used by the Tape Loader routine.
L1B6E: DEFB $EF, $22, $22 ; LOAD ""
; =======================================================
; BASIC LINE AND COMMAND INTERPRETATION ROUTINES - PART 3
; =======================================================
; -------------
; LLIST Routine
; -------------
L1B71: LD A,$03 ; Printer channel.
JR L1B77 ; Jump ahead to join LIST.
; ------------
; LIST Routine
; ------------
L1B75: LD A,$02 ; Main screen channel.
L1B77: LD (IY+$02),$00 ; TV_FLAG. Signal 'an ordinary listing in the main part of the screen'.
RST 28H ;
DEFW SYNTAX_Z ; $2530.
JR Z,L1B83 ; Do not open the channel if checking syntax.
RST 28H ;
DEFW CHAN_OPEN ; $1601. Open the channel.
L1B83: RST 28H ;
DEFW GET_CHAR ; $0018. [Could just do RST $18]
RST 28H ;
DEFW STR_ALTER ; $2070. See if the stream is to be changed.
JR C,L1BA3 ; Jump forward if unchanged.
RST 28H
DEFW GET_CHAR ; $0018. Get current character.
CP $3B ; Is it a ';'?
JR Z,L1B96 ; Jump if it is.
CP ',' ; $2C. Is it a ','?
JR NZ,L1B9E ; Jump if it is not.
L1B96: RST 28H ;
DEFW NEXT_CHAR ; $0020. Get the next character.
CALL L190E ; Indirectly call EXPT-1NUM in ROM 1 to check that
; a numeric expression follows, e.g. LIST #5,20.
JR L1BA6 ; Jump forward with it.
L1B9E: RST 28H ;
DEFW USE_ZERO ; $1CE6. Otherwise use zero and
JR L1BA6 ; jump forward.
;Come here if the stream was unaltered.
L1BA3: RST 28H ;
DEFW FETCH_NUM ; $1CDE. Fetch any line or use zero if none supplied.
L1BA6: CALL L18A1 ; If checking the syntax of the edit-line move on to the next statement.
RST 28H ;
DEFW LIST_5+3 ; $1825. Delegate handling to ROM 1.
RET
; ----------------------
; RAM Disk SAVE! Routine
; ----------------------
L1BAD: LD (OLDSP),SP ; $5B81. Save SP.
LD SP,TSTACK ; $5BFF. Use temporary stack.
CALL L1C97 ; Create new catalogue entry.
LD BC,(HD_0B) ; $5B72. get the length of the file.
LD HL,$FFF7 ; -9 (9 is the length of the file header).
OR $FF ; Extend the negative number into the high byte.
SBC HL,BC ; AHL=-(length of file + 9).
CALL L1CF3 ; Check for space in RAM disk (produce "4 Out of memory" if no room).
LD BC,$0009 ; File header length.
LD HL,HD_00 ; $5B71. Address of file header.
CALL L1DAC ; Store file header to RAM disk.
LD HL,(HD_0D) ; $5B74. Start address of file data.
LD BC,(HD_0B) ; $5B72. Length of file data.
CALL L1DAC ; Store bytes to RAM disk.
CALL L1D56 ; Update catalogue entry (leaves logical RAM bank 4 paged in).
LD A,$05 ; Page in logical RAM bank 5 (physical RAM bank 0).
CALL L1C64 ;
LD SP,(OLDSP) ; $5B81. Use original stack.
RET ;
; ------------
; CAT! Routine
; ------------
L1BE5: RST 28H ; Get the current character.
DEFW GET_CHAR ; $0018. [Could just do RST $18 here]
CP '!' ; $21. Is it '!'?
JP NZ,L1912 ; Jump to "C Nonsense in BASIC" if not.
RST 28H ; Get the next character.
DEFW NEXT_CHAR ; $0020. [Could just do RST $20 here]
CALL L18A1 ; Check for end of statement.
LD A,$02 ; Select main screen.
RST 28H ;
DEFW CHAN_OPEN ; $1601.
LD (OLDSP),SP ; $5B81. Store SP.
LD SP,TSTACK ; $5BFF. Use temporary stack.
CALL L20D2 ; Print out the catalogue.
LD A,$05 ; Page in logical RAM bank 5 (physical RAM bank 0).
CALL L1C64 ;
LD SP,(OLDSP) ; $5B81. Use original stack.
RET ;
; --------------
; ERASE! Routine
; --------------
L1C0C: RST 28H ; Get character from BASIC line.
DEFW GET_CHAR ; $0018.
CP '!' ; $21. Is it '!'?
JP NZ,L1912 ; Jump to "C Nonsense in BASIC" if not.
CALL L1393 ; Get the filename into N_STR1.
CALL L18A1 ; Make sure we've reached the end of the BASIC statement.
LD (OLDSP),SP ; $5B81. Store SP.
LD SP,TSTACK ; $5BFF. Use temporary stack.
CALL L1F5F ; Do the actual erasing (leaves logical RAM bank 4 paged in).
LD A,$05 ; Restore RAM configuration.
CALL L1C64 ; Page in logical RAM bank 5 (physical RAM bank 0).
LD SP,(OLDSP) ; $5B81. Use original stack.
RET ;
; ==================================
; RAM DISK COMMAND ROUTINES - PART 2
; ==================================
; -------------------------
; Load Header from RAM Disk
; -------------------------
L1C2E: LD (OLDSP),SP ; $5B81. Store SP.
LD SP,TSTACK ; $5BFF. Use temporary stack.
CALL L1D35 ; Find file (return details pointed to by IX). Leaves logical RAM bank 4 paged in.
;The file exists else the call above would have produced an error "h file does not exist"
LD HL,HD_00 ; $5B71. Load 9 header bytes.
LD BC,$0009 ;
CALL L1E37 ; Load bytes from RAM disk.
LD A,$05 ; Restore RAM configuration.
CALL L1C64 ; Page in logical RAM bank 5 (physical RAM bank 0).
LD SP,(OLDSP) ; $5B81. Use original stack.
RET ;
; ------------------
; Load from RAM Disk
; ------------------
; Used by LOAD, VERIFY and MERGE. Note that VERIFY will simply perform a LOAD.
; Entry: HL=Destination address.
; DE=Length (will be greater than zero).
; IX=File descriptor.
; IX=Address of catalogue entry (IX+$10-IX+$12 points to the address of the file's data, past its header).
; HD_00-HD_11 holds file header information.
L1C4B: LD (OLDSP),SP ; $5B81. Store SP
LD SP,TSTACK ; $5BFF. Use temporary stack.
LD B,D ;
LD C,E ; BC=Length.
CALL L1E37 ; Load bytes from RAM disk.
CALL L1D56 ; Update catalogue entry (leaves logical RAM bank 4 paged in).
LD A,$05 ; Restore RAM configuration.
CALL L1C64 ; Page in logical RAM bank 5 (physical RAM bank 0).
LD SP,(OLDSP) ; $5B81. Use original stack.
RET ;
; ========================
; PAGING ROUTINES - PART 1
; ========================
; ---------------------
; Page Logical RAM Bank
; ---------------------
; This routine converts between logical and physical RAM banks and pages the
; selected bank in.
; Entry: A=Logical RAM bank.
L1C64: PUSH HL ; Save BC and HL.
PUSH BC ;
LD HL,L1C81 ; Physical banks used by RAM disk.
LD B,$00 ;
LD C,A ; BC=Logical RAM bank.
ADD HL,BC ; Point to table entry.
LD C,(HL) ; Look up physical page.
DI ; Disable interrupts whilst paging.
LD A,(BANK_M) ; $5B5C. Fetch the current configuration.
AND $F8 ; Mask off current RAM bank.
OR C ; Include new RAM bank.
LD (BANK_M),A ; $5B5C. Store the new configuration.
LD BC,$7FFD ;
OUT (C),A ; Perform the page.
EI ; Re-enable interrupts.
POP BC ; Restore BC and HL.
POP HL ;
RET ;
; -------------------------------
; Physical RAM Bank Mapping Table
; -------------------------------
L1C81: DEFB $01 ; Logical bank $00.
DEFB $03 ; Logical bank $01.
DEFB $04 ; Logical bank $02.
DEFB $06 ; Logical bank $03.
DEFB $07 ; Logical bank $04.
DEFB $00 ; Logical bank $05.
; ==================================
; RAM DISK COMMAND ROUTINES - PART 3
; ==================================
; -----------------
; Compare Filenames
; -----------------
; Compare filenames at N_STR1 and IX.
; Exit: Zero flag set if filenames match.
; Carry flag set if filename at DE is alphabetically lower than filename at IX.
L1C87: LD DE,N_STR1 ; $5B67.
; Compare filenames at DE and IX
L1C8A: PUSH IX ;
POP HL ;
LD B,$0A ; Maximum of 10 characters.
L1C8F: LD A,(DE) ;
INC DE ;
CP (HL) ; compare each character.
INC HL ;
RET NZ ; Return if characters are different.
DJNZ L1C8F ; Repeat for all characters of the filename.
RET ;
; --------------------------
; Create New Catalogue Entry
; --------------------------
; Add a catalogue entry with filename contained in N_STR1.
; Exit: HL=Address of next free catalogue entry.
; IX=Address of newly created catalogue entry.
L1C97: CALL L1D12 ; Find entry in RAM disk area, returning IX pointing to catalogue entry (leaves logical RAM bank 4 paged in).
JR Z,L1CA0 ; Jump ahead if does not exist.
CALL L05AC ; Produce error report.
DEFB $20 ; "e File already exists"
L1CA0: PUSH IX ;
LD BC,$3FEC ; 16384-20 (maximum size of RAM disk catalogue).
ADD IX,BC ; IX grows downwards as new RAM disk catalogue entries added.
; If adding the maximum size to IX does not result in the carry flag being set
; then the catalogue is full, so issue an error report "4 Out of Memory".
POP IX ;
JR NC,L1D0E ; Jump if out of memory.
LD HL,$FFEC ; -20 (20 bytes is the size of a RAM disk catalogue entry).
LD A,$FF ; Extend the negative number into the high byte.
CALL L1CF3 ; Ensure space in RAM disk area.
LD HL,FLAGS3 ; $5B66.
SET 2,(HL) ; Signal editing RAM disk catalogue.
PUSH IX ;
POP DE ; DE=Address of new catalogue entry.
LD HL,N_STR1 ; $5B67. Filename.
L1CBE: LD BC,$000A ; 10 characters in the filename.
LDIR ; Copy the filename.
SET 0,(IX+$13) ; Indicate catalogue entry requires updating.
LD A,(IX+$0A) ; Set the file access address to be the
LD (IX+$10),A ; start address of the file.
LD A,(IX+$0B) ;
LD (IX+$11),A ;
LD A,(IX+$0C) ;
LD (IX+$12),A ;
XOR A ; Set the fill length to zero.
LD (IX+$0D),A ;
LD (IX+$0E),A ;
LD (IX+$0F),A ;
LD A,$05 ;
CALL L1C64 ; Logical RAM bank 5 (physical RAM bank 0).
PUSH IX ;
POP HL ; HL=Address of new catalogue entry.
LD BC,$FFEC ; -20 (20 bytes is the size of a catalogue entry).
ADD HL,BC ;
LD (SFNEXT),HL ; $5B83. Store address of next free catalogue entry.
RET ;
; --------------------------
; Adjust RAM Disk Free Space
; --------------------------
; Adjust the count of free bytes within the RAM disk.
; The routine can produce "4 Out of memory" when adding.
; Entry: AHL=Size adjustment (negative when a file added, positive when a file deleted).
; A=Bit 7 set for adding data, else deleting data.
L1CF3: LD DE,(SFSPACE) ; $5B85.
EX AF,AF' ; A'HL=Requested space.
LD A,(SFSPACE+2) ; $5B87. ADE=Free space on RAM disk.
LD C,A ; CDE=Free space.
EX AF,AF' ; AHL=Requested space.
BIT 7,A ; A negative adjustment, i.e. adding data?
JR NZ,L1D0A ; Jump ahead if so.
;Deleting data
ADD HL,DE ;
ADC A,C ; AHL=Free space left.
L1D03: LD (SFSPACE),HL ; $5B85. Store free space.
LD (SFSPACE+2),A ; $5B87.
RET ;
;Adding data
L1D0A: ADD HL,DE ;
ADC A,C ;
JR C,L1D03 ; Jump back to store free space if space left.
L1D0E: CALL L05AC ; Produce error report.
DEFB 03 ; "4 Out of memory"
; ---------------------------------
; Find Catalogue Entry for Filename
; ---------------------------------
; Entry: Filename stored at N_STR1 ($5B67).
; Exit : Zero flag set if file does not exist.
; If file exists, IX points to catalogue entry.
; Always leaves logical RAM bank 4 paged in.
L1D12: LD A,$04 ; Page in logical RAM bank 4 (physical RAM bank 7).
CALL L1C64 ;
LD IX,$EBEC ; Point to first catalogue entry.
L1D1B: LD DE,(SFNEXT) ; $5B83. Pointer to last catalogue entry.
OR A ; Clear carry flag.
PUSH IX ;
POP HL ; HL=First catalogue entry.
SBC HL,DE ;
RET Z ; Return with zero flag set if end of catalogue reached
; and hence filename not found.
CALL L1C87 ; Test filename match with N_STR1 ($5B67).
JR NZ,L1D2E ; Jump ahead if names did not match.
OR $FF ; Reset zero flag to indicate filename exists.
RET ;
L1D2E: LD BC,$FFEC ; -20 bytes (20 bytes is the size of a catalogue entry).
ADD IX,BC ; Point to the next directory entry.
JR L1D1B ; Test the next name.
; ------------------
; Find RAM Disk File
; ------------------
; Find a file in the RAM disk matching name held in N_STR1,
; and return with IX pointing to the catalogue entry.
L1D35: CALL L1D12 ; Find entry in RAM disk area, returning IX pointing to catalogue entry (leaves logical RAM bank 4 paged in).
JR NZ,L1D3E ; Jump ahead if it exists.
CALL L05AC ; Produce error report.
DEFB $23 ; "h File does not exist"
L1D3E: LD A,(IX+$0A) ; Take the current start address (bank + location)
LD (IX+$10),A ; and store it as the current working address.
LD A,(IX+$0B) ;
LD (IX+$11),A ;
LD A,(IX+$0C) ;
LD (IX+$12),A ;
LD A,$05 ; Page in logical RAM bank 5 (physical RAM bank 0).
CALL L1C64 ;
RET ; [Could have saved 1 byte by using JP L1C64 (ROM 0)]
; ----------------------
; Update Catalogue Entry
; ----------------------
; Entry: IX=Address of catalogue entry (IX+$10-IX+$12 points to end of the file).
; Exits with logical RAM bank 4 paged in.
L1D56: LD A,$04 ; Page in logical RAM bank 4 (physical RAM bank 7).
CALL L1C64 ;
BIT 0,(IX+$13) ;
RET Z ; Ignore if catalogue entry does not require updating.
RES 0,(IX+$13) ; Indicate catalogue entry updated.
LD HL,FLAGS3 ; $5B66.
RES 2,(HL) ; Signal not editing RAM disk catalogue.
LD L,(IX+$10) ; Points to end address within logical RAM bank.
LD H,(IX+$11) ;
LD A,(IX+$12) ; Points to end logical RAM bank.
LD E,(IX+$0A) ; Start address within logical RAM bank.
LD D,(IX+$0B) ;
LD B,(IX+$0C) ; Start logical RAM bank.
OR A ; Clear carry flag.
SBC HL,DE ; HL=End address-Start address. Maximum difference fits within 14 bits.
SBC A,B ; A=End logical RAM bank-Start logical RAM bank - 1 if addresses overlap.
RL H ;
RL H ; Work out how many full banks of 16K are being used.
SRA A ; Place this in the upper two bits of H.
RR H ;
SRA A ;
RR H ; HL=Total length.
LD (IX+$0D),L ; Length within logical RAM bank.
LD (IX+$0E),H ;
LD (IX+$0F),A ;
;Copy the end address of the previous entry into the new entry
LD L,(IX+$10) ; End address within logical RAM bank.
LD H,(IX+$11) ;
LD A,(IX+$12) ; End logical RAM bank.
LD BC,$FFEC ; -20 bytes (20 bytes is the size of a catalogue entry).
ADD IX,BC ; Address of next catalogue entry.
LD (IX+$0A),L ; Start address within logical RAM bank.
LD (IX+$0B),H ;
LD (IX+$0C),A ; Start logical RAM bank.
RET ;
; ----------------------
; Save Bytes to RAM Disk
; ----------------------
; Entry: IX=Address of catalogue entry.
; HL=Source address in conventional RAM.
; BC=Length.
; Advances IX+$10-IX+$12 as bytes are saved so that always points to next location to fill,
; eventually pointing to the end of the file.
L1DAC: LD A,B ; Check whether a data length of zero was requested.
OR C ;
RET Z ; Ignore if so since all bytes already saved.
PUSH HL ; Save the source address.
LD DE,$C000 ; DE=The start of the upper RAM bank.
EX DE,HL ; HL=The start of the RAM bank. DE=Source address.
SBC HL,DE ; HL=RAM bank start - Source address.
JR Z,L1DD5 ; Jump ahead if saving bytes from $C000.
JR C,L1DD5 ; Jump ahead if saving bytes from an address above $C000.
;Source is below $C000
PUSH HL ; HL=Distance below $C000 (RAM bank start - Source address).
SBC HL,BC ;
JR NC,L1DCC ; Jump if requested bytes are all below $C000.
;Source spans across $C000
LD H,B ;
LD L,C ; HL=Requested length.
POP BC ; BC=Distance below $C000.
OR A ;
SBC HL,BC ; HL=Bytes occupying upper RAM bank.
EX (SP),HL ; Stack it. HL=Source address.
LD DE,$C000 ; Start of upper RAM bank.
PUSH DE ;
JR L1DF4 ; Jump forward.
;Source fits completely below upper RAM bank (less than $C000)
L1DCC: POP HL ; Forget the 'distance below $C000' count.
POP HL ; HL=Source address.
LD DE,$0000 ; Remaining bytes to transfer.
PUSH DE ;
PUSH DE ; Stack dummy Start of upper RAM bank.
JR L1DF4 ; Jump forward.
;Source fits completely within upper RAM bank (greater than or equal $C000)
L1DD5: LD H,B ;
LD L,C ; HL=Requested length.
LD DE,$0020 ; DE=Length of buffer.
OR A ;
SBC HL,DE ; HL=Requested length-Length of buffer = Buffer overspill.
JR C,L1DE4 ; Jump if requested length will fit within the buffer.
;Source spans transfer buffer
EX (SP),HL ; Stack buffer overspill. HL=$0000.
LD B,D ;
LD C,E ; BC=Buffer length.
JR L1DE9 ; Jump forward.
;Source fits completely within transfer buffer
L1DE4: POP HL ; HL=Destination address.
LD DE,$0000 ; Remaining bytes to transfer.
PUSH DE ; Stack 'transfer buffer in use' flag.
;Transfer a block
L1DE9: PUSH BC ; Stack the length.
LD DE,STRIP1 ; $5B98. Transfer buffer.
LDIR ; Transfer bytes.
POP BC ; BC=Length.
PUSH HL ; HL=New source address.
LD HL,STRIP1 ; $5B98. Transfer buffer.
L1DF4: LD A,$04 ; Page in logical RAM bank 4 (physical RAM bank 7).
CALL L1C64 ;
LD E,(IX+$10) ;
LD D,(IX+$11) ; Fetch the address from the current logical RAM bank.
LD A,(IX+$12) ; Logical RAM bank.
CALL L1C64 ; Page in appropriate logical RAM bank.
L1E05: LDI ; Transfer a byte from the file to the required RAM disk location or transfer buffer.
LD A,D ;
OR E ; Has DE been incremented to $0000?
JR Z,L1E24 ; Jump if end of RAM bank reached.
L1E0B: LD A,B ;
OR C ;
JP NZ,L1E05 ; Repeat until all bytes transferred.
LD A,$04 ; Page in logical RAM bank 4 (physical RAM bank 7).
CALL L1C64 ;
LD (IX+$10),E ;
LD (IX+$11),D ; Store the next RAM bank source address.
LD A,$05 ; Page in logical RAM bank 5 (physical RAM bank 0).
CALL L1C64 ;
POP HL ; HL=Source address.
POP BC ; BC=Length.
JR L1DAC ; Re-enter this routine to transfer another block.
;The end of a RAM bank has been reached so switch to the next bank
L1E24: LD A,$04 ; Page in logical RAM bank 4 (physical RAM bank 7).
CALL L1C64 ;
INC (IX+$12) ; Increment to the new logical RAM bank.
LD A,(IX+$12) ; Fetch the new logical RAM bank.
LD DE,$C000 ; The start of the RAM disk
CALL L1C64 ; Page in next RAM bank.
JR L1E0B ; Jump back to transfer another block.
; ------------------------
; Load Bytes from RAM Disk
; ------------------------
; Used for loading file header and data.
; Entry: IX=RAM disk catalogue entry address. IX+$10-IX+$12 points to the next address to fetch from the file.
; HL=Destination address.
; BC=Requested length.
L1E37: LD A,B ; Check whether a data length of zero was requested.
OR C ;
RET Z ; Ignore if so since all bytes already loaded.
PUSH HL ; Save the destination address.
LD DE,$C000 ; DE=The start of the upper RAM bank.
EX DE,HL ; HL=The start of the RAM bank. DE=Destination address.
SBC HL,DE ; HL=RAM bank start - Destination address.
JR Z,L1E67 ; Jump if destination is $C000.
JR C,L1E67 ; Jump if destination is above $C000.
;Destination is below $C000
L1E45: PUSH HL ; HL=Distance below $C000 (RAM bank start - Destination address).
SBC HL,BC ;
JR NC,L1E5C ; Jump if requested bytes all fit below $C000.
;Code will span across $C000
LD H,B ;
LD L,C ; HL=Requested length.
POP BC ; BC=Distance below $C000.
OR A ;
SBC HL,BC ; HL=Bytes destined for upper RAM bank.
EX (SP),HL ; Stack it. HL=Destination address.
LD DE,$0000 ; Remaining bytes to transfer.
PUSH DE ;
LD DE,$C000 ; Start of upper RAM bank.
PUSH DE ;
EX DE,HL ; HL=Start of upper RAM bank.
JR L1E80 ; Jump forward.
;Code fits completely below upper RAM bank (less than $C000)
L1E5C: POP HL ; Forget the 'distance below $C000' count.
POP HL ; HL=Destination address.
LD DE,$0000 ; Remaining bytes to transfer.
PUSH DE ;
PUSH DE ; Stack dummy Start of upper RAM bank.
PUSH DE ;
EX DE,HL ; HL=$0000, DE=Destination address.
JR L1E80 ; Jump forward.
;Code destined for upper RAM bank (greater than or equal to $C000)
L1E67: LD H,B ;
LD L,C ; HL=Requested length.
LD DE,$0020 ; DE=Length of buffer.
OR A ;
SBC HL,DE ; HL=Requested length-Length of buffer = Buffer overspill.
JR C,L1E76 ; Jump if requested length will fit within the buffer.
;Code will span transfer buffer
EX (SP),HL ; Stack buffer overspill. HL=$0000.
LD B,D ;
LD C,E ; BC=Buffer length.
JR L1E7B ; Jump forward.
;Code will all fit within transfer buffer
L1E76: POP HL ; HL=Destination address.
LD DE,$0000 ; Remaining bytes to transfer.
PUSH DE ; Stack 'transfer buffer in use' flag.
L1E7B: PUSH BC ; Stack the length.
PUSH HL ; Stack destination address.
LD DE,STRIP1 ; $5B98. Transfer buffer.
;Transfer a block
L1E80: LD A,$04 ; Page in logical RAM bank 4 (physical RAM bank 7).
CALL L1C64 ;
LD L,(IX+$10) ; RAM bank address.
LD H,(IX+$11) ;
LD A,(IX+$12) ; Logical RAM bank.
CALL L1C64 ; Page in appropriate logical RAM bank.
;Enter a loop to transfer BC bytes, either to required destination or to the transfer buffer
L1E91: LDI ; Transfer a byte from the file to the required location or transfer buffer.
LD A,H ;
OR L ; Has HL been incremented to $0000?
JR Z,L1EBC ; Jump if end of RAM bank reached.
L1E97: LD A,B ;
OR C ;
JP NZ,L1E91 ; Repeat until all bytes transferred.
LD A,$04 ; Page in logical RAM bank 4 (physical RAM bank 7).
CALL L1C64 ;
LD (IX+$10),L ;
LD (IX+$11),H ; Store the next RAM bank destination address.
LD A,$05 ; Page in logical RAM bank 5 (physical RAM bank 0).
CALL L1C64 ;
POP DE ; DE=Destination address.
POP BC ; BC=Length.
LD HL,STRIP1 ; $5B98. Transfer buffer.
LD A,B ;
OR C ; All bytes transferred?
JR Z,L1EB7 ; Jump forward if so.
LDIR ; Transfer code in buffer to the required address.
L1EB7: EX DE,HL ; HL=New destination address.
POP BC ; BC=Remaining bytes to transfer.
JP L1E37 ; Re-enter this routine to transfer another block.
;The end of a RAM bank has been reached so switch to the next bank
L1EBC: LD A,$04 ; Page in logical RAM bank 4 (physical RAM bank 7).
CALL L1C64 ;
INC (IX+$12) ; Increment to the new logical RAM bank.
LD A,(IX+$12) ; Fetch the new logical RAM bank.
LD HL,$C000 ; The start of the RAM disk.
CALL L1C64 ; Page in next logical RAM bank.
JR L1E97 ; Jump back to transfer another block.
; -------------------------------------------------
; Transfer Bytes to RAM Bank 4 - Vector Table Entry
; -------------------------------------------------
; This routine can be used to transfer bytes from the current RAM bank into logical RAM bank 4.
; It is not used in this ROM and is a remnant of the original Spanish Spectrum 128 ROM 0.
; Entry: HL=Source address in conventional RAM.
; DE=Destination address in logical RAM bank 4 (physical RAM bank 7).
; BC=Number of bytes to save.
L1ECF: PUSH AF ; Save AF.
LD A,(BANK_M) ; $5B5C. Fetch current physical RAM bank configuration.
PUSH AF ; Save it.
PUSH HL ; Save source address.
PUSH DE ; Save destination address.
PUSH BC ; Save length.
LD IX,N_STR1+3 ; $5B6A.
LD (IX+$10),E ; Store destination address as the current address pointer.
LD (IX+$11),D ;
LD (IX+$12),$04 ; Destination is in logical RAM bank 4 (physical RAM bank 7).
CALL L1DAC ; Store bytes to RAM disk.
;Entered here by load vector routine
L1EE8: LD A,$05 ; Page in logical RAM bank 5 (physical RAM bank 0).
CALL L1C64 ;
POP BC ; Get length.
POP DE ; Get destination address.
POP HL ; Get source address.
ADD HL,BC ; HL=Address after end of source.
EX DE,HL ; DE=Address after end of source. HL=Destination address.
ADD HL,BC ; HL=Address after end of destination.
EX DE,HL ; HL=Address after end of source. DE=Address after end of destination.
POP AF ; Get original RAM bank configuration.
LD BC,$7FFD ;
DI ; Disable interrupts whilst paging.
OUT (C),A ;
LD (BANK_M),A ; $5B5C.
EI ; Re-enable interrupts.
LD BC,$0000 ; Signal all bytes loaded/saved.
POP AF ; Restore AF.
RET ;
; ---------------------------------------------------
; Transfer Bytes from RAM Bank 4 - Vector Table Entry
; ---------------------------------------------------
; This routine can be used to transfer bytes from logical RAM bank 4 into the current RAM bank.
; It is not used in this ROM and is a remnant of the original Spanish Spectrum 128 ROM 0.
; Entry: HL=Source address in logical RAM bank 4 (physical RAM bank 7).
; DE=Destination address in current RAM bank.
; BC=Number of bytes to load.
L1F04: PUSH AF ; Save AF.
LD A,(BANK_M) ; $5B5C. Fetch current physical RAM bank configuration.
PUSH AF ; Save it.
PUSH HL ; Save source address.
PUSH DE ; Save destination address.
PUSH BC ; Save length.
LD IX,N_STR1+3 ; $5B6A.
LD (IX+$10),L ; Store source address as the current address pointer.
LD (IX+$11),H ;
LD (IX+$12),$04 ; Source is in logical RAM bank 4 (physical RAM bank 7).
EX DE,HL ; HL=Destination address.
CALL L1E37 ; Load bytes from RAM disk.
JR L1EE8 ; Join the save vector routine above.
; ========================
; PAGING ROUTINES - PART 2
; ========================
; ----------------------------
; Use Normal RAM Configuration
; ----------------------------
; Page in physical RAM bank 0, use normal stack and stack TARGET address.
; Entry: HL=TARGET address.
L1F20: EX AF,AF' ; Save AF.
LD A,$00 ; Physical RAM bank 0.
DI ; Disable interrupts whilst paging.
CALL L1F3A ; Page in physical RAM bank 0.
POP AF ; AF=Address on stack when CALLed.
LD (TARGET),HL ; $5B58. Store HL.
LD HL,(OLDSP) ; $5B81. Fetch the old stack.
LD (OLDSP),SP ; $5B81. Save the current stack.
LD SP,HL ; Use the old stack.
EI ; Re-enable interrupts.
LD HL,(TARGET) ; $5B58. Restore HL.
PUSH AF ; Re-stack the return address.
EX AF,AF' ; Get AF back.
RET ;
; ---------------
; Select RAM Bank
; ---------------
; Used twice by the ROM to select either physical RAM bank 0 or physical RAM bank 7.
; However, it could in theory also be used to set other paging settings.
; Entry: A=RAM bank number.
L1F3A: PUSH BC ; Save BC
LD BC,$7FFD ;
OUT (C),A ; Perform requested paging.
LD (BANK_M),A ; $5B5C.
POP BC ; Restore BC.
RET ;
; -------------------------------
; Use Workspace RAM Configuration
; -------------------------------
; Page in physical RAM bank 7, use workspace stack and stack TARGET address.
; Entry: HL=TARGET address.
L1F45: EX AF,AF' ; Save A.
DI ; Disable interrupts whilst paging.
POP AF ; Fetch return address.
LD (TARGET),HL ; $5B58. Store HL.
LD HL,(OLDSP) ; $5B81. Fetch the old stack.
LD (OLDSP),SP ; $5B81. Save the current stack.
LD SP,HL ; Use the old stack.
LD HL,(TARGET) ; $5B58. Restore HL.
PUSH AF ; Stack return address.
LD A,$07 ; RAM bank 7.
CALL L1F3A ; Page in RAM bank 7.
EI ; Re-enable interrupts.
EX AF,AF' ; Restore A.
RET ;
; ==================================
; RAM DISK COMMAND ROUTINES - PART 4
; ==================================
; ---------------------
; Erase a RAM Disk File
; ---------------------
; N_STR1 contains the name of the file to erase.
L1F5F: CALL L1D12 ; Find entry in RAM disk area, returning IX pointing to catalogue entry (leaves logical RAM bank 4 paged in).
JR NZ,L1F68 ; Jump ahead if it was found. [Could have saved 3 bytes by using JP Z,L1D3E (ROM 0)]
CALL L05AC ; Produce error report.
DEFB $23 ; "h File does not exist"
L1F68: LD L,(IX+$0D) ; AHL=Length of file.
LD H,(IX+$0E) ;
LD A,(IX+$0F) ; Bit 7 of A will be 0 indicating to delete rather than add.
CALL L1CF3 ; Free up this amount of space.
PUSH IY ; Preserve current value of IY.
LD IY,(SFNEXT) ; $5B83. IY points to next free catalogue entry.
LD BC,$FFEC ; BC=-20 (20 bytes is the size of a catalogue entry).
ADD IX,BC ; IX points to the next catalogue entry
LD L,(IY+$0A) ; AHL=First spare byte in RAM disk file area.
LD H,(IY+$0B) ;
LD A,(IY+$0C) ;
POP IY ; Restore IY to normal value.
LD E,(IX+$0A) ; BDE=Start of address of next RAM disk file entry.
LD D,(IX+$0B) ;
LD B,(IX+$0C) ;
OR A ;
SBC HL,DE ;
SBC A,B ;
RL H ;
RL H ;
SRA A ;
RR H ;
SRA A ;
RR H ; HL=Length of all files to be moved.
LD BC,$0014 ; 20 bytes is the size of a catalogue entry.
ADD IX,BC ; IX=Catalogue entry to delete.
LD (IX+$10),L ; Store file length in the 'deleted' catalogue entry.
LD (IX+$11),H ;
LD (IX+$12),A ;
LD BC,$FFEC ; -20 (20 bytes is the size of a catalogue entry).
ADD IX,BC ; IX=Next catalogue entry.
LD L,(IX+$0A) ; DHL=Start address of next RAM disk file entry.
LD H,(IX+$0B) ;
LD D,(IX+$0C) ;
LD BC,$0014 ; 20 bytes is the size of a catalogue entry.
ADD IX,BC ; IX points to catalogue entry to delete.
LD A,D ; Page in logical RAM bank for start address of entry to delete.
CALL L1C64 ;
LD A,(BANK_M) ; $5B5C.
LD E,A ; Save current RAM bank configuration in E.
LD BC,$7FFD ; Select physical RAM bank 7.
LD A,$07 ;
DI ; Disable interrupts whilst performing paging operations.
OUT (C),A ; Page in selected RAM bank.
EXX ; DHL'=Start address of next RAM disk file entry.
LD L,(IX+$0A) ; DHL=Start of address of RAM disk file entry to delete.
LD H,(IX+$0B) ;
LD D,(IX+$0C) ;
LD A,D ;
CALL L1C64 ; Page in logical RAM bank for file entry (will update BANK_M).
LD A,(BANK_M) ; $5B5C.
LD E,A ; Get RAM bank configuration for the file in E.
LD BC,$7FFD ;
EXX ; DHL=Start address of next RAM disk file entry.
; At this point we have the registers and alternate registers pointing
; to the actual bytes in the RAM disk for the file to be deleted and the next file,
; with length bytes of the catalogue entry for the file to be deleted containing
; the length of bytes for all subsequent files that need to be moved down in memory.
; A loop is entered to move all of these bytes where the delete file began.
; DHL holds the address of the byte to be moved.
; E contains the value which should be OUTed to $5B5C to page in the relevant RAM page.
L1FEA: LD A,$07 ; Select physical RAM bank 7.
DI ; Disable interrupts whilst performing paging operations.
OUT (C),A ; Page in selected RAM bank.
LD A,(IX+$10) ; Decrement end address.
SUB $01 ;
LD (IX+$10),A ;
JR NC,L200D ; If no carry then the decrement is finished.
LD A,(IX+$11) ; Otherwise decrement the middle byte.
SUB $01 ;
LD (IX+$11),A ;
JR NC,L200D ; If no carry then the decrement is finished.
LD A,(IX+$12) ; Otherwise decrement the highest byte.
SUB $01 ;
LD (IX+$12),A ;
JR C,L203E ; Jump forward if finished moving the file.
L200D: OUT (C),E ; Page in RAM bank containing the next file.
LD A,(HL) ; Get the byte from the next file.
INC L ; Increment DHL.
JR NZ,L2024 ; If not zero then the increment is finished.
INC H ; Otherwise increment the middle byte.
JR NZ,L2024 ; If not zero then the increment is finished.
EX AF,AF' ; Save the byte read from the next file.
INC D ; Advance to next logical RAM bank for the next file.
LD A,D ;
CALL L1C64 ; Page in next logical RAM bank for next file entry (will update BANK_M).
LD A,(BANK_M) ; $5B5C.
LD E,A ; Get RAM bank configuration for the next file in E.
LD HL,$C000 ; The next file continues at the beginning of the next RAM bank.
EX AF,AF' ; Retrieve the byte read from the next file.
L2024: EXX ; DHL=Address of file being deleted.
DI ; Disable interrupts whilst performing paging operations.
OUT (C),E ; Page in next RAM bank containing the next file.
LD (HL),A ; Store the byte taken from the next file.
INC L ; Increment DHL.
JR NZ,L203B ; If not zero then the increment is finished.
INC H ; Otherwise increment the middle byte.
JR NZ,L203B ; If not zero then the increment is finished.
INC D ; Advance to next logical RAM bank for the file being deleted.
LD A,D ;
CALL L1C64 ; Page in next logical RAM bank for file being deleted entry (will update BANK_M).
LD A,(BANK_M) ; $5B5C.
LD E,A ; Get RAM bank configuration for the file being deleted in E.
LD HL,$C000 ; The file being deleted continues at the beginning of the next RAM bank.
L203B: EXX ; DHL=Address of byte in next file.
; DHL'=Address of byte in file being deleted.
JR L1FEA ;
;The file has been moved
L203E: LD A,$04 ; Page in logical RAM bank 4 (physical RAM bank 7).
CALL L1C64 ;
LD A,$00 ;
LD HL,$0014 ; AHL=20 bytes is the size of a catalogue entry.
L2048: CALL L1CF3 ; Delete a catalogue entry.
LD E,(IX+$0D) ;
LD D,(IX+$0E) ;
LD C,(IX+$0F) ; CDE=File length of file entry to delete.
LD A,D ;
RLCA ;
RL C ;
RLCA ;
RL C ; C=RAM bank.
LD A,D ;
AND $3F ; Mask off upper bits to leave length in this bank (range 0-16383).
LD D,A ; DE=Length in this bank.
PUSH IX ; Save address of catalogue entry to delete.
L2061: PUSH DE ;
LD DE,$FFEC ; -20 (20 bytes is the size of a catalogue entry).
ADD IX,DE ; Point to next catalogue entry.
POP DE ; DE=Length in this bank.
LD L,(IX+$0A) ;
LD H,(IX+$0B) ;
LD A,(IX+$0C) ; AHL=File start address.
OR A ;
SBC HL,DE ; Will move into next RAM bank?
SUB C ;
BIT 6,H ;
JR NZ,L207C ; Jump if same RAM bank.
SET 6,H ; New address in next RAM bank.
DEC A ; Next RAM bank.
L207C: LD (IX+$0A),L ;
LD (IX+$0B),H ;
LD (IX+$0C),A ; Save new start address of file.
LD L,(IX+$10) ;
LD H,(IX+$11) ;
LD A,(IX+$12) ; Fetch end address of file.
OR A ;
SBC HL,DE ; Will move into next RAM bank?
SUB C ;
BIT 6,H ;
JR NZ,L2099 ; Jump if same RAM bank.
SET 6,H ; New address in next RAM bank.
DEC A ; Next RAM bank.
L2099: LD (IX+$10),L ;
LD (IX+$11),H ;
LD (IX+$12),A ; Save new end address of file.
PUSH IX ;
POP HL ; HL=Address of next catalogue entry.
PUSH DE ;
LD DE,(SFNEXT) ; $5B83.
OR A ;
SBC HL,DE ; End of catalogue reached?
POP DE ; DE=Length in this bank.
JR NZ,L2061 ; Jump if not to move next entry.
LD DE,(SFNEXT) ; $5B83. Start address of the next available catalogue entry.
POP HL ;
PUSH HL ; HL=Start address of catalogue entry to delete.
OR A ;
SBC HL,DE ;
LD B,H ;
LD C,L ; BC=Length of catalogue entries to move.
POP HL ;
PUSH HL ; HL=Start address of catalogue entry to delete.
LD DE,$0014 ; 20 bytes is the size of a catalogue entry.
ADD HL,DE ; HL=Start address of previous catalogue entry.
EX DE,HL ; DE=Start address of previous catalogue entry.
POP HL ; HL=Start address of catalogue entry to delete.
DEC DE ; DE=End address of catalogue entry to delete.
DEC HL ; HL=End address of next catalogue entry.
LDDR ; Move all catalogue entries.
LD HL,(SFNEXT) ; $5B83. Start address of the next available catalogue entry.
LD DE,$0014 ; 20 bytes is the size of a catalogue entry.
ADD HL,DE ;
LD (SFNEXT),HL ; $5B83. Store the new location of the next available catalogue entry.
RET ;
; ------------------------
; Print RAM Disk Catalogue
; ------------------------
; This routine prints catalogue filenames in alphabetically order.
; It does this by repeatedly looping through the catalogue to find
; the next 'highest' name.
L20D2: LD A,$04 ; Page in logical RAM bank 4
CALL L1C64 ; (physical RAM bank 7)
LD HL,L2121 ; HL points to ten $00 bytes, the initial comparison filename.
L20DA: LD BC,L212B ; BC point to ten $FF bytes.
LD IX,$EBEC ; IX points to first catalogue entry.
L20E1: CALL L05D6 ; Check for BREAK.
PUSH IX ; Save address of catalogue entry.
EX (SP),HL ; HL points to current catalogue entry. Top of stack points to ten $00 data.
LD DE,(SFNEXT) ; $5B83. Find address of next free catalogue entry.
OR A ;
SBC HL,DE ; Have we reached end of catalogue?
POP HL ; Fetch address of catalogue entry.
JR Z,L2111 ; Jump ahead if end of catalogue reached.
LD D,H ;
LD E,L ; DE=Current catalogue entry.
PUSH HL ;
PUSH BC ;
CALL L1C8A ; Compare current filename (initially ten $00 bytes).
POP BC ;
POP HL ;
JR NC,L210A ; Jump if current catalogue name is 'above' the previous.
LD D,B ;
LD E,C ; DE=Last filename
PUSH HL ;
PUSH BC ;
CALL L1C8A ; Compare current filename (initially ten $FF bytes).
POP BC ;
POP HL ;
JR C,L210A ; Jump if current catalogue name is 'below' the previous.
PUSH IX ;
POP BC ; BC=Address of current catalogue entry name.
L210A: LD DE,$FFEC ; -20 (20 bytes is the size of a catalogue entry).
ADD IX,DE ; Point to next catalogue entry.
JR L20E1 ; Check next filename.
L2111: PUSH HL ; HL points to current catalogue entry.
LD HL,L212B ; Address of highest theoretical filename data.
OR A ;
SBC HL,BC ; Was a new filename to print found?
POP HL ;
RET Z ; Return if all filenames printed.
LD H,B ;
LD L,C ; HL=Address of current catalogue entry name.
CALL L2135 ; Print the catalogue entry.
JR L20DA ; Repeat for next filename.
; -----------------------------
; Print Catalogue Filename Data
; -----------------------------
L2121: DEFB $00, $00, $00, $00, $00 ; Lowest theoretical filename.
DEFB $00, $00, $00, $00, $00
L212B: DEFB $FF, $FF, $FF, $FF, $FF ; Highest theoretical filename.
DEFB $FF, $FF, $FF, $FF, $FF
; ----------------------------
; Print Single Catalogue Entry
; ----------------------------
; Entry: HL=Address of filename.
; BC=Address of filename.
L2135: PUSH HL ; Save address of filename.
PUSH BC ;
POP HL ; [No need to transfer BC to HL since they already have the same value].
LD DE,N_STR1 ; $5B67. Copy the filename to N_STR1 so that it
LD BC,$000A ; is visible when this RAM bank is paged out.
LDIR ;
LD A,$05 ; Page in logical RAM bank 5 (physical RAM bank 0).
CALL L1C64 ;
LD HL,(OLDSP) ; $5B81.
LD (OLDSP),SP ; $5B81. Save temporary stack.
LD SP,HL ; Use original stack.
LD HL,N_STR1 ; $5B67. HL points to filename.
LD B,$0A ; 10 characters to print.
L2152: LD A,(HL) ; Print each character of the filename.
PUSH HL ;
PUSH BC ;
RST 28H ;
DEFW PRINT_A_1 ; $0010.
POP BC ;
POP HL ;
INC HL ;
DJNZ L2152 ;
LD A,$0D ; Print a newline character.
RST 28H ;
DEFW PRINT_A_1 ; $0010.
RST 28H ;
DEFW TEMPS ; $0D4D. Copy permanent colours to temporary colours.
LD HL,(OLDSP) ; $5B81.
LD (OLDSP),SP ; $5B81. Save original stack.
LD SP,HL ; Switch back to temporary stack.
LD A,$04 ; Page in logical RAM bank 4 (physical RAM bank 7).
CALL L1C64 ;
POP HL ; HL=Address of filename.
RET ;
; =======================================================
; BASIC LINE AND COMMAND INTERPRETATION ROUTINES - PART 4
; =======================================================
; --------------
; LPRINT Routine
; --------------
L2174: LD A,$03 ; Printer channel.
JR L217A ; Jump ahead.
; --------------
; PRINT Routine
; --------------
L2178: LD A,$02 ; Main screen channel.
L217A: RST 28H ;
DEFW SYNTAX_Z ; $2530.
JR Z,L2182 ; Jump forward if syntax is being checked.
RST 28H ;
DEFW CHAN_OPEN ; $1601.
L2182: RST 28H ;
DEFW TEMPS ; $0D4D.
RST 28H ;
DEFW PRINT_2 ; $1FDF. Delegate handling to ROM 1.
CALL L18A1 ; "C Nonsense in BASIC" during syntax checking if not
; at end of line or statement.
RET ;
; -------------
; INPUT Routine
; -------------
; This routine allows for values entered from the keyboard to be assigned
; to variables. It is also possible to have print items embedded in the
; INPUT statement and these items are printed in the lower part of the display.
L218C: RST 28H ;
DEFW SYNTAX_Z ; $2530.
JR Z,L2199 ; Jump forward if syntax is being checked.
LD A,$01 ; Open channel 'K'.
RST 28H ;
DEFW CHAN_OPEN ; $1601.
RST 28H ; Clear the lower part of the display.
DEFW CLS_LOWER ; $0D6E. [*BUG* - This call will re-select channel 'S' and so should have been called prior to opening
; channel 'K'. It is a direct copy of the code that appears in the standard Spectrum ROM (and ROM 1). It is
; debatable whether it is better to reproduce the bug so as to ensure that the INPUT routine operates the same
; in 128K mode as it does in 48K mode. Credit: Geoff Wearmouth]
L2199: LD (IY+$02),$01 ; TV_FLAG. Signal that the lower screen is being handled. [Not a bug as has been reported elsewhere. The confusion seems to have
; arisen due to the incorrect system variable being originally mentioned in the Spectrum ROM Disassembly by Logan and O'Hara]
RST 28H ;
DEFW IN_ITEM_1 ; $20C1. Call the subroutine to deal with the INPUT items.
CALL L18A1 ; Move on to the next statement if checking syntax.
RST 28H ;
DEFW INPUT_1+$000A ; $20A0. Delegate handling to ROM 1.
RET ;
; ------------
; COPY Routine
; ------------
L21A7: JP L08F0 ; Jump to new COPY routine.
; -----------
; NEW Routine
; -----------
L21AA: DI ;
JP L019D ; Re-initialise the machine.
; --------------
; CIRCLE Routine
; --------------
; This routine draws an approximation to the circle with centre co-ordinates
; X and Y and radius Z. These numbers are rounded to the nearest integer before use.
; Thus Z must be less than 87.5, even when (X,Y) is in the centre of the screen.
; The method used is to draw a series of arcs approximated by straight lines.
L21AE: RST 18H ; Get character from BASIC line.
CP ',' ; $2C. Check for second parameter.
JR NZ,L21EB ; Jump ahead (for error C) if not.
RST 20H ; Advance pointer into BASIC line.
RST 28H ; Get parameter.
DEFW EXPT_1NUM ; $1C82. Radius to calculator stack.
CALL L18A1 ; Move to consider next statement if checking syntax.
RST 28H ;
DEFW CIRCLE+$000D ; $232D. Delegate handling to ROM 1.
RET ;
; ------------
; DRAW Routine
; ------------
; This routine is entered with the co-ordinates of a point X0, Y0, say, in
; COORDS. If only two parameters X, Y are given with the DRAW command, it
; draws an approximation to a straight line from the point X0, Y0 to X0+X, Y0+Y.
; If a third parameter G is given, it draws an approximation to a circular arc
; from X0, Y0 to X0+X, Y0+Y turning anti-clockwise through an angle G radians.
L21BE: RST 18H ; Get current character.
CP ',' ; $2C.
JR Z,L21CA ; Jump if there is a third parameter.
CALL L18A1 ; Error C during syntax checking if not at end of line/statement.
RST 28H ;
DEFW LINE_DRAW ; $2477. Delegate handling to ROM 1.
RET ;
L21CA: RST 20H ; Get the next character.
RST 28H ;
DEFW EXPT_1NUM ; $1C82. Angle to calculator stack.
CALL L18A1 ; Error C during syntax checking if not at end of line/statement.
RST 28H ;
DEFW DR_3_PRMS+$0007 ; $2394. Delegate handling to ROM 1.
RET ;
; -----------
; DIM Routine
; -----------
; This routine establishes new arrays in the variables area. The routine starts
; by searching the existing variables area to determine whether there is an existing
; array with the same name. If such an array is found then it is 'reclaimed' before
; the new array is established. A new array will have all its elements set to zero
; if it is a numeric array, or to 'spaces' if it is an array of strings.
L21D5: RST 28H ; Search to see if the array already exists.
DEFW LOOK_VARS ; $28B2.
JR NZ,L21EB ; Jump if array variable not found.
RST 28H
DEFW SYNTAX_Z ; $2530.
JR NZ,L21E7 ; Jump ahead during syntax checking.
RES 6,C ; Test the syntax for string arrays as if they were numeric.
RST 28H ;
DEFW STK_VAR ; $2996. Check the syntax of the parenthesised expression.
CALL L18A1 ; Error when checking syntax unless at end of line/statement.
;An 'existing array' is reclaimed.
L21E7: RST 28H ;
DEFW D_RUN ; $2C15. Delegate handling to ROM 1.
RET ;
; ----------------------------------
; Error Report C - Nonsense in BASIC
; ----------------------------------
L21EB: CALL L05AC ; Produce error report.
DEFB $0B ; "C Nonsense in BASIC"
; --------------------
; Clear Screen Routine
; --------------------
; Clear screen if it is not already clear.
L21EF: BIT 0,(IY+$30) ; FLAGS2. Is the screen clear?
RET Z ; Return if it is.
RST 28H ;
DEFW CL_ALL ; $0DAF. Otherwise clear the whole display.
RET ;
; ---------------------------
; Evaluate Numeric Expression
; ---------------------------
; This routine is called when a numerical expression is typed directly into the editor or calculator.
; A numeric expression is any that begins with '(', '-' or '+', or is one of the function keywords, e.g. ABS, SIN, etc,
; or is the name of a numeric variable.
L21F8: LD HL,$FFFE ; A line in the editing area is considered as line '-2'.
LD ($5C45),HL ; PPC. Signal no current line number.
;Check the syntax of the BASIC line
RES 7,(IY+$01) ; Indicate 'syntax checking' mode.
CALL L228E ; Point to start of the BASIC command line.
RST 28H ;
DEFW SCANNING ; $24FB. Evaluate the command line.
BIT 6,(IY+$01) ; Is it a numeric value?
JR Z,L223A ; Jump to produce an error if a string result.
RST 18H ; Get current character.
CP $0D ; Is it the end of the line?
JR NZ,L223A ; Jump if not to produce an error if not.
;The BASIC line has passed syntax checking so now execute it
SET 7,(IY+$01) ; If so, indicate 'execution' mode.
CALL L228E ; Point to start of the BASIC command line.
LD HL,L0321 ; Set up the error handler routine address.
LD (SYNRET),HL ; $5B8B.
RST 28H ;
DEFW SCANNING ; $24FB. Evaluate the command line.
BIT 6,(IY+$01) ; Is it a numeric value?
JR Z,L223A ; Jump to produce an error if a string result.
LD DE,LASTV ; $5B8D. DE points to last calculator value.
LD HL,($5C65) ; STKEND.
LD BC,$0005 ; The length of the floating point value.
OR A ;
SBC HL,BC ; HL points to value on top of calculator stack.
LDIR ; Copy the value in the workspace to the top of the calculator stack.
JP L223E ; [Could have saved 1 byte by using a JR instruction]
L223A: CALL L05AC ; Produce error report.
DEFB $19 ; "Q Parameter error"
L223E: LD A,$0D ; Make it appear that 'Enter' has been pressed.
CALL L226F ; Process key press.
LD BC,$0001 ;
RST 28H ;
DEFW BC_SPACES ; $0030. Create a byte in the workspace.
LD ($5C5B),HL ; K_CUR. Address of the cursor.
PUSH HL ; Save it.
LD HL,($5C51) ; CURCHL. Current channel information.
PUSH HL ; Save it.
LD A,$FF ; Channel 'R', the workspace.
RST 28H ;
DEFW CHAN_OPEN ; $1601.
RST 28H
DEFW PRINT_FP ; $2DE3. Print a floating point number to the workspace.
POP HL ; Get the current channel information address.
RST 28H ;
DEFW CHAN_FLAG ; $1615. Set appropriate flags back for the old channel.
POP DE ; DE=Address of the old cursor position.
LD HL,($5C5B) ; K_CUR. Address of the cursor.
AND A ;
SBC HL,DE ; HL=Length of floating point number.
L2264: LD A,(DE) ; Fetch the character and make it appear to have been typed.
CALL L226F ; Process the key press.
INC DE ;
DEC HL ; Decrement floating point number character count.
LD A,H ;
OR L ;
JR NZ,L2264 ; Repeat for all characters.
RET ;
; -----------------
; Process Key Press
; -----------------
; Entry: A=Key code.
L226F: PUSH HL ; Save registers.
PUSH DE ;
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
LD HL,$EC0D ; Editor flags.
RES 3,(HL) ; Reset 'line altered' flag
PUSH AF ;
LD A,$02 ; Main screen
RST 28H ;
DEFW CHAN_OPEN ; $1601.
POP AF ;
CALL L2669 ; Process key press.
LD HL,$EC0D ; Editor flags.
RES 3,(HL) ; Reset 'line altered' flag
CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
POP DE ; Restore registers.
POP HL ;
RET ;
; ---------------------------
; Find Start of BASIC Command
; ---------------------------
; Point to the start of a typed in BASIC command
; and return first character in A.
L228E: LD HL,($5C59) ; E_LINE. Get the address of command being typed in.
DEC HL ;
LD ($5C5D),HL ; CH_ADD. Store it as the address of next character to be interpreted.
RST 20H ; Get the next character.
RET ;
; ---------------
; Is LET Command?
; ---------------
; A typed in command resides in the editing workspace.
; This function tests whether the text is a single LET command.
; Exit: Zero flag set if a single LET command.
L2297: CALL L228E ; Point to start of typed in command.
CP $F1 ; Is it 'LET'?
RET NZ ; Return if not with zero flag reset.
LD HL,($5C5D) ; CH_ADD. HL points to next character.
L22A0: LD A,(HL) ; Fetch next character.
INC HL ;
CP $0D ; Has end of line been found?
RET Z ; Return if so with zero flag set.
CP ':' ; $3A. Has start of new statement been found?
JR NZ,L22A0 ; Loop back if not.
OR A ; Return zero flag reset indicating a multi-statement
RET ; LET command.
; ----------------------
; Is Operator Character?
; ----------------------
; Exit: Zero flag set if character is an operator.
L22AB: LD B,A ; Save B.
LD HL,L22BD ; Start of operator token table.
L22AF: LD A,(HL) ; Fetch character from the table.
INC HL ; Advance to next entry.
OR A ; End of table?
JR Z,L22B9 ; Jump if end of table reached.
CP B ; Found required character?
JR NZ,L22AF ; Jump if not to try next character in table.
;Found
LD A,B ; Restore character to A.
RET ; Return with zero flag set to indicate an operator.
;Not found
L22B9: OR $FF ; Reset zero flag to indicate not an operator.
LD A,B ; Restore character to A.
RET ;
; ---------------------
; Operator Tokens Table
; ---------------------
L22BD: DEFB $2B, $2D, $2A ; '+', '-', '*'
DEFB $2F, $5E, $3D ; '/', '^', '='
DEFB $3E, $3C, $C7 ; '>', '<', '<='
DEFB $C8, $C9, $C5 ; '>=', '<>', 'OR'
DEFB $C6 ; 'AND'
DEFB $00 ; End marker.
; ----------------------
; Is Function Character?
; ----------------------
; Exit: Zero set if a function token.
L22CB: CP $A5 ; 'RND'. (first 48K token)
JR C,L22DD ; Jump ahead if not a token with zero flag reset.
CP $C4 ; 'BIN'.
JR NC,L22DD ; Jump ahead if not a function token.
CP $AC ; 'AT'.
JR Z,L22DD ; Jump ahead if not a function token.
CP $AD ; 'TAB'.
JR Z,L22DD ; Jump ahead if not a function token.
CP A ; Return zero flag set if a function token.
RET ;
L22DD: CP $A5 ; Return zero flag set if a function token.
RET ;
; ----------------------------------
; Is Numeric or Function Expression?
; ----------------------------------
; Exit: Zero flag set if a numeric or function expression.
L22E0: LD B,A ; Fetch character code.
OR $20 ; Make lowercase.
CP 'a' ; $61. Is it 'a' or above?
JR C,L22ED ; Jump ahead if not a letter.
CP '{' ; $7B. Is it below '{'?
L22E9: JR NC,L22ED ; Jump ahead if not.
CP A ; Character is a letter so return
RET ; with zero flag set.
L22ED: LD A,B ; Fetch character code.
CP '.' ; $2E. Is it '.'?
RET Z ; Return zero flag set indicating numeric.
CALL L230A ; Is character a number?
JR NZ,L2307 ; Jump ahead if not a number.
L22F6: RST 20H ; Get next character.
CALL L230A ; Is character a number?
JR Z,L22F6 ; Repeat for next character if numeric.
CP '.' ; $2E. Is it '.'?
RET Z ; Return zero flag set indicating numeric.
CP 'E' ; $45. Is it 'E'?
RET Z ; Return zero flag set indicating numeric.
CP 'e' ; $65. Is it 'e'?
RET Z ; Return zero flag set indicating numeric.
JR L22AB ; Jump to test for operator tokens.
L2307: OR $FF ; Reset the zero flag to indicate non-alphanumeric.
RET ;
; ---------------------
; Is Numeric Character?
; ---------------------
; Exit: Zero flag set if numeric character.
L230A: CP '0' ; $30. Is it below '0'?
JR C,L2314 ; Jump below '0'.
CP ':' ; $3A. Is it below ':'?
JR NC,L2314 ; Jump above '9'
CP A ;
RET ; Set zero flag if numeric.
L2314: CP '0' ; $30. This will cause zero flag to be reset.
RET ;
; ------------
; PLAY Routine
; ------------
L2317: LD B,$00 ; String index.
RST 18H ;
L231A: PUSH BC ;
RST 28H ; Get string expression.
DEFW EXPT_EXP
POP BC ;
INC B ;
CP ',' ; $2C. A ',' indicates another string.
JR NZ,L2327 ; Jump ahead if no more.
RST 20H ; Advance to the next character.
JR L231A ; Loop back.
L2327: LD A,B ; Check the index.
CP $09 ; Maximum of 8 strings (to support synthesisers, drum machines or sequencers).
JR C,L2330 ;
CALL L05AC ; Produce error report.
DEFB $2B ; "p (c) 1986 Sinclair Research Ltd" [*BUG* - This should be "Parameter error". The Spanish 128
; produces "p Bad parameter" but to save memory perhaps the UK 128 was intended to use the existing
; "Q Parameter error" and the change of the error code byte here was overlooked. In that case it would
; have had a value of $19. Note that generation of this error when using the main screen editor will
; result in a crash. Credit: Andrew Owen]
L2330: CALL L18A1 ; Ensure end-of-statement or end-of-line.
JP L0985 ; Continue with PLAY code.
; ========================
; UNUSED ROUTINES - PART 1
; ========================
; There now follows 513 bytes of routines that are not used by the ROM, from $2336 (ROM 0) to $2536 (ROM 0).
; They are remnants of the original Spanish 128's ROM code, although surprisingly they appear in a different
; order within that ROM.
; ----------------
; Return to Editor
; ----------------
; [Never called by this ROM]
L2336: LD HL,TSTACK ; $5BFF.
LD (OLDSP),HL ; $5B81.
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
JP L25CB ; Jump ahead to the Editor.
; ------------------------
; BC=HL-DE, Swap HL and DE
; ------------------------
; Exit: BC=HL-DE.
; DE=HL, HL=DE.
;
; [Never called by this ROM]
L2342: AND A ;
SBC HL,DE ;
LD B,H ;
LD C,L ; BC=HL-DE.
ADD HL,DE ;
EX DE,HL ; HL=DE, DE=HL.
RET ;
; ----------------------
; Create Room for 1 Byte
; ----------------------
; Creates a single byte in the workspace, or automatically produces an error '4' if not.
;
; [Never called by this ROM]
L234A: LD BC,$0001 ; Request 1 byte.
PUSH HL ;
PUSH DE ;
CALL L2358 ; Test whether there is space. If it fails this will cause the error
POP DE ; handler in ROM 0 to be called. If MAKE_ROOM were called directly and
POP HL ; and out of memory condition detected then the ROM 1 error handler would
RST 28H ; be called instead.
DEFW MAKE_ROOM ; $1655. The memory check passed so safely make the room.
RET
; ------------------
; Room for BC Bytes?
; ------------------
; Test whether there is room for the specified number of bytes in the spare memory,
; producing error "4 Out of memory" if not. This routine is very similar to that at
; $3F66 with the exception that this routine assumes IY points at the system variables.
; Entry: BC=Number of bytes required.
; Exit : Returns if the room requested is available else an error '4' is produced.
;
; [Called by the routine at L234A (ROM 0), which is itself never called by this ROM]
L2358: LD HL,($5C65) ; STKEND.
ADD HL,BC ; Would adding the specified number of bytes overflow the RAM area?
JR C,L2368 ; Jump to produce an error if so.
EX DE,HL ; DE=New end address.
LD HL,$0082 ; Would there be at least 130 bytes at the top of RAM?
ADD HL,DE ;
JR C,L2368 ; Jump to produce an error if not.
SBC HL,SP ; If the stack is lower in memory, would there still be enough room?
RET C ; Return if there would.
L2368: LD (IY+$00),$03 ; Signal error "4 Out of Memory".
JP L0321 ; Jump to error handler routine.
; ---------
; HL = A*32
; ---------
; [Called by routines at L2383 (ROM 0) and $23B8 (ROM 0), which are themselves never called by this ROM]
L236F: ADD A,A ; A*2.
ADD A,A ; A*4. Then multiply by 8 in following routine.
; --------
; HL = A*8
; --------
; [Called by the routine at L23E1 (ROM 0), which ultimately is itself never called by this ROM]
L2371: LD L,A ;
LD H,$00 ;
ADD HL,HL ; A*2.
ADD HL,HL ; A*4.
ADD HL,HL ; A*8.
RET ; Return HL=A*8.
; -------------------------
; Find Amount of Free Space
; -------------------------
; Exit: Carry flag set if no more space, else HL holds the amount of free space.
;
; [Never called by this ROM]
L2378: LD HL,$0000 ;
ADD HL,SP ; HL=SP.
LD DE,($5C65) ; STKEND.
OR A ;
SBC HL,DE ; Effectively SP-STKEND, i.e. the amount of available space.
RET ;
; -----------------------
; Print Screen Buffer Row
; -----------------------
; Prints row from the screen buffer to the screen.
; Entry: A=Row number.
;
; [Never called by this ROM]
L2384: RES 0,(IY-$39) ; KSTATE+1. Signal do not invert attribute value. [IY+$3B on the Spanish 128]
CALL L236F ; HL=A*32. Number of bytes prior to the requested row.
PUSH HL ; Save offset to requested row to print.
LD DE,($FF24) ; Fetch address of screen buffer.
ADD HL,DE ; Point to row entry.
LD D,H ;
LD E,L ; DE=Address of row entry.
EX (SP),HL ; Stack address of row entry. HL=Offset to requested row to print.
PUSH HL ; Save offset to requested row to print.
PUSH DE ; Save address of row entry.
LD DE,$5800 ; Attributes file.
ADD HL,DE ; Point to start of corresponding row in attributes file.
EX DE,HL ; DE=Start address of corresponding row in attributes file.
POP HL ; HL=Address of row entry.
LD BC,$0020 ; 32 columns.
LD A,($5C8F) ; ATTR_T. Fetch the temporary colours.
CALL L249B ; Set the colours for the 32 columns in this row, processing
; any colour control codes from the print string.
POP HL ; HL=Offset to requested row to print.
LD A,H ;
LD H,$00 ; Calculate corresponding display file address.
ADD A,A ;
ADD A,A ;
ADD A,A ;
ADD A,$40 ;
LD D,A ;
LD E,H ;
ADD HL,DE ;
EX DE,HL ; DE=Display file address.
POP HL ; HL=Offset to requested row to print.
LD B,$20 ; 32 columns.
JP L23E1 ; Print one row to the display file.
; ---------------------------
; Blank Screen Buffer Content
; ---------------------------
; Sets the specified number of screen buffer positions from the specified row to $FF.
; Entry: A=Row number.
; BC=Number of bytes to set.
;
; [Never called by this ROM]
L23B8: LD D,$FF ; The character to set the screen buffer contents to.
CALL L236F ; HL=A*32. Offset to the specified row.
LD A,D ;
LD DE,($FF24) ; Fetch the address of the screen buffer.
ADD HL,DE ; HL=Address of first column in the requested row.
LD E,L ;
LD D,H ;
INC DE ; DE=Address of second column in the requested row.
LD (HL),A ; Store the character.
DEC BC ;
LDIR ; Repeat for all remaining bytes required.
RET ;
; -----------------------------------
; Print Screen Buffer to Display File
; -----------------------------------
; [Never called by this ROM]
L23CB: CALL L2488 ; Set attributes file from screen buffer.
LD DE,$4000 ; DE=First third of display file.
LD HL,($FF24) ; Fetch address of screen buffer.
LD B,E ; Display 256 characters.
CALL L23E1 ; Display string.
LD D,$48 ; Middle third of display file.
CALL L23E1 ; Display string.
LD D,$50 ; Last third of display file.
LD B,$C0 ; Display 192 characters.
; ----------------------------------------------
; Print Screen Buffer Characters to Display File
; ----------------------------------------------
; Displays ASCII characters, UDGs, graphic characters or two special symbols in the display file,
; but does not alter the attributes file. Character code $FE is used to represent the error marker
; bug symbol and the character code $FF is used to represent a null, which is displayed as a space.
; Entry: DE=Display file address.
; HL=Points to string to print.
; B=Number of characters to print.
;
; [Used by routine at L23CB (ROM 0) and called by the routine at $2383 (ROM 0), both of which are themselves never called by this ROM]
L23E1: LD A,(HL) ; Fetch the character.
PUSH HL ; Save string pointer.
PUSH DE ; Save display file address.
CP $FE ; Was if $FE (bug) or $FF (null)?
JR C,L23EC ; Jump ahead if not.
SUB $FE ; Reduce range to $00-$01.
JR L2422 ; Jump ahead to show symbol.
;Comes here if character code if below $FE
L23EC: CP $20 ; Is it a control character?
JR NC,L23F7 ; Jump ahead if not.
;Comes here if a control character
LD HL,L2527 ; Graphic for a 'G' (not a normal G though). Used to indicate embedded colour control codes.
AND A ; Clear the carry flag to indicate no need to switch back to RAM bank 7.
EX AF,AF' ; Save the flag.
JR L242B ; Jump ahead to display the symbol.
L23F7: CP $80 ; Is it a graphic character or UDG?
JR NC,L2409 ; Jump ahead if so.
;Comes here if an ASCII character
CALL L2371 ; HL=A*8.
LD DE,($5C36) ; CHARS.
ADD HL,DE ; Point to the character bit pattern.
POP DE ; Fetch the display file address.
CALL $FF28 ; Copy character into display file (via RAM Routine).
; Can't use routine at $242C (ROM 0) since it does not perform a simple return.
JR L2450 ; Continue with next character.
;Comes here if a graphic character or UDG
L2409: CP $90 ; Is it a graphic character?
JR NC,L2411 ; Jump ahead if not.
;Comes here if a graphic character
SUB $7F ; Reduce range to $01-$10.
JR L2422 ; Jump ahead to display the symbol.
;Comes here if a UDG
L2411: SUB $90 ; Reduce range to $00-$6D.
CALL L2371 ; HL=A*8.
POP DE ; Fetch display file address.
CALL L1F20 ; Use Normal RAM Configuration (RAM bank 0) to allow access to character bit patterns.
PUSH DE ; Save display file address.
LD DE,($5C7B) ; UDG. Fetch address of UDGs.
SCF ; Set carry flag to indicate need to switch back to RAM bank 7.
JR L2429 ; Jump ahead to locate character bit pattern and display the symbol.
;Come here if (HL) was $FE or $FF, or with a graphic character.
;At this point A=$00 if (HL) was $FE indicating a bug symbol, or $01 if (HL) was $FF indicating a null,
;or A=$01-$10 if a graphic character.
L2422: LD DE,L252F ; Start address of the graphic character bitmap table.
CALL L2371 ; HL=A*8 -> L0000 or $0008.
AND A ; Clear carry flag to indicate no need to switch back to RAM bank 7.
L2429: EX AF,AF' ; Save switch bank indication flag.
ADD HL,DE ; Point to the symbol bit pattern data.
L242B: POP DE ; Fetch display file address. Drop through into routine below.
; ------------------------------------
; Copy A Character <<< RAM Routine >>>
; ------------------------------------
; Routine copied to RAM at $FF36-$FF55 by subroutine at $246F (ROM 0).
; Also used in ROM from above routine.
;
; This routine copies 8 bytes from HL to DE. It increments HL and D after
; each byte, restoring D afterwards.
; It is used to copy a character into the display file.
; Entry: HL=Character data.
; DE=Display file address.
;
; [Called by a routine that is itself never called by this ROM]
L242C: LD C,D ; Save D.
LD A,(HL) ;
LD (DE),A ; Copy byte 1.
INC HL ;
INC D ;
LD A,(HL) ;
LD (DE),A ; Copy byte 2.
INC HL ;
INC D ;
LD A,(HL) ;
LD (DE),A ; Copy byte 3.
INC HL ;
INC D ;
LD A,(HL) ;
LD (DE),A ; Copy byte 4.
INC HL ;
INC D ;
LD A,(HL) ;
LD (DE),A ; Copy byte 5.
INC HL ;
INC D ;
LD A,(HL) ;
LD (DE),A ; Copy byte 6.
INC HL ;
INC D ;
LD A,(HL) ;
LD (DE),A ; Copy byte 7.
INC HL ;
INC D ;
LD A,(HL) ;
LD (DE),A ; Copy byte 8.
LD D,C ; Restore D. <<< Last byte copied to RAM >>>
; When the above routine is used in ROM, it drops through to here.
L244C: EX AF,AF' ; Need to switch back to RAM bank 7?
CALL C,L1F45 ; If so then switch to use Workspace RAM configuration (physical RAM bank 7).
L2450: POP HL ; Fetch address of string data.
INC HL ; Move to next character.
INC DE ; Advance to next display file column.
DJNZ L23E1 ; Repeat for all requested characters.
RET ;
; ---------------------------------
; Toggle ROMs 1 <<< RAM Routine >>>
; ---------------------------------
; Routine copied to RAM at $FF28-$FF35 by subroutine at $246F (ROM 0).
;
; This routine toggles to the other ROM than the one held in BANK_M.
; Entry: A'= Current paging configuration.
;
; [Called by a routine that is itself never called by this ROM]
L2456: PUSH BC ; Save BC
DI ; Disable interrupts whilst paging.
LD BC,$7FFD ;
LD A,(BANK_M) ; $5B5C. Fetch current paging configuration.
XOR $10 ; Toggle ROMs.
OUT (C),A ; Perform paging.
EI ; Re-enable interrupts.
EX AF,AF' ; Save the new configuration in A'. <<< Last byte copied to RAM >>>
; ---------------------------------
; Toggle ROMs 2 <<< RAM Routine >>>
; ---------------------------------
; Routine copied to RAM at $FF56-$FF60 by subroutine at $246F (ROM 0).
;
; This routine toggles to the other ROM than the one specified.
; It is used to page back to the original configuration.
; Entry: A'= Current paging configuration.
;
; [Called by a routine that is itself never called by this ROM]
L2464: EX AF,AF' ; Retrieve current paging configuration.
DI ; Disable interrupts whilst paging.
LD C,$FD ; Restore Paging I/O port number.
XOR $10 ; Toggle ROMs.
OUT (C),A ; Perform paging.
EI ; Re-enable interrupts.
POP BC ; Restore BC.
RET ; <<< Last byte copied to RAM >>>
; -----------------------------------------
; Construct 'Copy Character' Routine in RAM
; -----------------------------------------
; This routine copies 3 sections of code into RAM to construct a single
; routine that can be used to copy the bit pattern for a character into
; the display file.
;
; Copy $2456-$2463 (ROM 0) to $FF28-$FF35 (14 bytes).
; Copy $242C-$244B (ROM 0) to $FF36-$FF55 (32 bytes).
; Copy $2464-$246E (ROM 0) to $FF56-$FF60 (11 bytes).
;
; [Never called by this ROM]
L246F: LD HL,L2456 ; Point to the 'page in other ROM' routine.
LD DE,$FF28 ; Destination RAM address.
LD BC,$000E ;
LDIR ; Copy the routine.
PUSH HL ;
LD HL,L242C ; Copy a character routine.
LD C,$20 ;
LDIR ; Copy the routine.
POP HL ; HL=$2464 (ROM 0), which is the address of the 'page back to original ROM' routine.
LD C,$0B ;
LDIR ; Copy the routine.
RET ;
; --------------------------------------
; Set Attributes File from Screen Buffer
; --------------------------------------
; This routine parses the screen buffer string contents looking for colour
; control codes and changing the attributes file contents correspondingly.
;
; [Called by the routine at L23CB (ROM 0), which is itself never called by this ROM]
L2488: RES 0,(IY-$39) ; KSTATE+1. Signal do not invert attribute value. [Spanish 128 uses IY-$3B]
LD DE,$5800 ; The start of the attributes file.
LD BC,$02C0 ; 22 rows of 32 columns.
LD HL,($FF24) ; The address of the string to print.
LD A,($5C8D) ; ATTR_P.
LD ($5C8F),A ; ATTR_T. Use the permanent colours.
; --------------------------------------
; Set Attributes for a Screen Buffer Row
; --------------------------------------
; Entry: A=Colour byte.
; HL=Address within screen buffer.
; BC=Number of characters.
; DE=Address within attributes file.
L249B: EX AF,AF' ; Save the colour byte.
;The main loop returns here on each iteration
L249C: PUSH BC ; Save the number of characters.
LD A,(HL) ; Fetch a character from the buffer.
CP $FF ; Is it blank?
JR NZ,L24AA ; Jump ahead if not.
LD A,($5C8D) ; ATTR_P. Get the default colour byte.
LD (DE),A ; Store it in the attributes file.
INC HL ; Point to next screen buffer position.
INC DE ; Point to next attributes file position.
JR L2507 ; Jump ahead to handle the next character.
;Not a blank character
L24AA: EX AF,AF' ; Get the colour byte.
LD (DE),A ; Store it in the attributes file.
INC DE ; Point to the next attributes file position.
EX AF,AF' ; Save the colour byte.
INC HL ; Point to the next screen buffer position.
CP $15 ; Is the string character OVER or above?
JR NC,L2507 ; Jump if it is to handle the next character.
CP $10 ; Is the string character below INK?
JR C,L2507 ; Jump if it is to handle the next character.
;Screen buffer character is INK, PAPER, FLASH, BRIGHT or INVERSE.
DEC HL ; Point back to the previous screen buffer position.
JR NZ,L24C2 ; Jump if not INK.
;Screen character was INK so insert the new ink into the attribute byte.
INC HL ; Point to the next screen buffer position.
LD A,(HL) ; Fetch the ink colour from the next screen buffer position.
LD C,A ; and store it in C.
EX AF,AF' ; Get the colour byte.
AND $F8 ; Mask off the ink bits.
JR L2505 ; Jump ahead to store the new attribute value and then to handle the next character.
L24C2: CP $11 ; Is the string character PAPER?
JR NZ,L24D1 ; Jump ahead if not.
;Screen character was PAPER so insert the new paper into the attribute byte.
INC HL ; Point to the next screen buffer position.
LD A,(HL) ; Fetch the paper colour from the next screen buffer position.
ADD A,A ;
ADD A,A ;
ADD A,A ; Multiple by 8 so that ink colour become paper colour.
LD C,A ;
EX AF,AF' ; Get the colour byte.
AND $C7 ; Mask off the paper bits.
JR L2505 ; Jump ahead to store the new attribute value and then to handle the next character.
L24D1: CP $12 ; Is the string character FLASH?
JR NZ,L24DE ; Jump ahead if not.
;Screen character was FLASH
INC HL ; Point to the next screen buffer position.
LD A,(HL) ; Fetch the flash status from the next screen buffer position.
RRCA ; Shift the flash bit into bit 0.
LD C,A ;
EX AF,AF' ; Get the colour byte.
AND $7F ; Mask off the flash bit.
JR L2505 ; Jump ahead to store the new attribute value and then to handle the next character.
L24DE: CP $13 ; Is the string character BRIGHT?
JR NZ,L24EC ; Jump ahead if not.
;Screen character was BRIGHT
INC HL ; Point to the next screen buffer position.
LD A,(HL) ; Fetch the bright status from the next screen buffer position.
RRCA ;
RRCA ; Shift the bright bit into bit 0.
LD C,A ;
EX AF,AF' ; Get the colour byte.
AND $BF ; Mask off the bright bit.
JR L2505 ; Jump ahead to store the new attribute value and then to handle the next character.
L24EC: CP $14 ; Is the string character INVERSE?
INC HL ; Point to the next screen buffer position.
JR NZ,L2507 ; Jump ahead if not to handle the next character.
;Screen character was INVERSE
LD C,(HL) ; Fetch the inverse status from the next screen buffer position.
LD A,($5C01) ; KSTATE+1. Fetch inverting status (Bit 0 is 0 for non-inverting, 1 for inverting).
XOR C ; Invert status.
RRA ; Shift status into the carry flag.
JR NC,L2507 ; Jump if not inverting to handle the next character.
LD A,$01 ; Signal inverting is active.
XOR (IY-$39) ; KSTATE+1. Toggle the status.
LD ($5C01),A ; KSTATE+1. Store the new status.
EX AF,AF' ; Get the colour byte.
CALL L2513 ; Swap ink and paper in the colour byte.
L2505: OR C ; Combine the old and new colour values.
EX AF,AF' ; Save the new colour byte.
L2507: POP BC ; Fetch the number of characters.
DEC BC ;
LD A,B ;
OR C ;
JP NZ,L249C ; Repeat for all characters.
EX AF,AF' ; Get colour byte.
LD ($5C8F),A ; ATTR_T. Make it the new temporary colour.
RET ;
; ---------------------------------
; Swap Ink and Paper Attribute Bits
; ---------------------------------
; Entry: A=Attribute byte value.
; Exit : A=Attribute byte value with paper and ink bits swapped.
;
; [Called by the routine at L2488 (ROM 0), which is itself never called by this ROM]
L2513: LD B,A ; Save the original colour byte.
AND $C0 ; Keep only the flash and bright bits.
LD C,A ;
LD A,B ;
ADD A,A ; Shift ink bits into paper bits.
ADD A,A ;
ADD A,A ;
AND $38 ; Keep only the paper bits.
OR C ; Combine with the flash and bright bits.
LD C,A ;
LD A,B ; Get the original colour byte.
RRA ;
RRA ;
RRA ; Shift the paper bits into the ink bits.
AND $07 ; Keep only the ink bits.
OR C ; Add with the paper, flash and bright bits.
RET ;
; --------------
; Character Data
; --------------
;Graphic control code indicator
L2527: DEFB $00 ; 0 0 0 0 0 0 0 0
DEFB $3C ; 0 0 1 1 1 1 0 0 XXXX
DEFB $62 ; 0 1 1 0 0 0 1 0 XX X
DEFB $60 ; 0 1 1 0 0 0 0 0 XX
DEFB $6E ; 0 1 1 0 1 1 1 0 XX XXX
DEFB $62 ; 0 1 1 0 0 0 1 0 XX X
DEFB $3E ; 0 0 1 1 1 1 1 0 XXXX
DEFB $00 ; 0 0 0 0 0 0 0 0
;Error marker
L252F: DEFB $00 ; 0 0 0 0 0 0 0 0
DEFB $6C ; 0 1 1 0 1 1 0 0 XX XX
DEFB $10 ; 0 0 0 1 0 0 0 0 X
DEFB $54 ; 0 1 0 1 0 1 0 0 X X X
DEFB $BA ; 1 0 1 1 1 0 1 0 X XXX X
DEFB $38 ; 0 0 1 1 1 0 0 0 XXX
DEFB $54 ; 0 1 0 1 0 1 0 0 X X X
DEFB $82 ; 1 0 0 0 0 0 1 0 X X
; <<< End of Unused ROM Routines >>>
; =================
; KEY ACTION TABLES
; =================
; -------------------------
; Editing Keys Action Table
; -------------------------
; Each editing key code maps to the appropriate handling routine.
; This includes those keys which mirror the functionality of the
; add-on keypad; these are found by trapping the keyword produced
; by the keystrokes in 48K mode.
; [Surprisingly there is no attempt to produce an intelligible layout;
; instead the first 16 keywords have been used. Additionally the entries for DELETE
; and ENTER should probably come in the first six entries for efficiency reasons.]
L2537: DEFB $15 ; Number of table entries.
DEFB $0B ; Key code: Cursor up.
DEFW L2A94 ; CURSOR-UP handler routine.
DEFB $0A ; Key code: Cursor Down.
DEFW L2AB5 ; CURSOR-DOWN handler routine.
DEFB $08 ; Key code: Cursor Left.
DEFW L2AD7 ; CURSOR-LEFT handler routine.
DEFB $09 ; Key code: Cursor Right.
DEFW L2AE3 ; CURSOR-RIGHT handler routine.
DEFB $AD ; Key code: Extend Mode + P.
DEFW L2A4F ; TEN-ROWS-UP handler routine.
DEFB $AC ; Key code: Symbol Shift + I.
DEFW L2A25 ; TEN-ROWS-DOWN handler routine.
DEFB $AF ; Key code: Extend Mode + I.
DEFW L29D4 ; WORD-LEFT handler routine.
DEFB $AE ; Key code: Extend Mode + Shift + J.
DEFW L29E1 ; WORD-RIGHT handler routine.
DEFB $A6 ; Key code: Extend Mode + N, or Graph + W.
DEFW L2983 ; TOP-OF-PROGRAM handler routine.
DEFB $A5 ; Key code: Extend Mode + T, or Graph + V.
DEFW L29AB ; END-OF-PROGRAM handler routine.
DEFB $A8 ; Key code: Extend Mode Symbol Shift + 2, or Graph Y.
DEFW L2A87 ; START-OF-LINE handler routine.
DEFB $A7 ; Key code: Extend Mode + M, or Graph + X.
DEFW L2A7A ; END-OF-LINE handler routine.
DEFB $AA ; Key code: Extend Mode + Shift + K.
DEFW L291B ; DELETE-RIGHT handler routine.
DEFB $0C ; Key code: Delete.
DEFW L292B ; DELETE handler routine.
DEFB $B3 ; Key code: Extend Mode + W.
DEFW L3017 ; DELETE-WORD-RIGHT handler routine.
DEFB $B4 ; Key code: Extend Mode + E.
DEFW L2FBC ; DELETE-WORD-LEFT handler routine.
DEFB $B0 ; Key code: Extend Mode + J.
DEFW L3072 ; DELETE-TO-END-OF-LINE handler routine.
DEFB $B1 ; Key code: Extend Mode + K.
DEFW L303E ; DELETE-TO-START-OF-LINE handler routine.
DEFB $0D ; Key code: Enter.
DEFW L2944 ; ENTER handler routine.
DEFB $A9 ; Key code: Extend Mode + Symbol Shift + 8, or Graph + Z.
DEFW L269B ; TOGGLE handler routine.
DEFB $07 ; Key code: Edit.
DEFW L2704 ; MENU handler routine.
; ----------------------
; Menu Keys Action Table
; ----------------------
; Each menu key code maps to the appropriate handling routine.
L2577: DEFB $04 ; Number of entries.
DEFB $0B ; Key code: Cursor up.
DEFW L272E ; MENU-UP handler routine.
DEFB $0A ; Key code: Cursor down.
DEFW L2731 ; MENU-DOWN handler routine.
DEFB $07 ; Key code: Edit.
DEFW L2717 ; MENU-SELECT handler routine.
DEFB $0D ; Key code: Enter.
DEFW L2717 ; MENU-SELECT handler routine.
; ======================
; MENU ROUTINES - PART 3
; ======================
; ------------------------
; Initialise Mode Settings
; ------------------------
; Called before Main menu displayed.
L2584: CALL L28BE ; Reset Cursor Position.
LD HL,$0000 ; No top line.
LD ($FC9A),HL ; Line number at top of screen.
LD A,$82 ; Signal waiting for key press, and menu is displayed.
LD ($EC0D),A ; Store the Editor flags.
LD HL,$0000 ; No current line number.
LD ($5C49),HL ; E_PPC. Current line number.
CALL L35BC ; Reset indentation settings.
CALL L365E ; Reset to 'L' Mode
RET ; [Could have saved one byte by using JP L365E (ROM 0)]
; --------------
; Show Main Menu
; --------------
L259F: LD HL,TSTACK ; $5BFF.
LD (OLDSP),HL ; $5B81.
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
LD A,$02 ; Select main screen.
RST 28H ;
DEFW CHAN_OPEN ; $1601.
L25AD: LD HL,L2744 ; Jump table for Main Menu.
LD ($F6EA),HL ; Store current menu jump table address.
LD HL,L2754 ; The Main Menu text.
LD ($F6EC),HL ; Store current menu text table address.
PUSH HL ; Store address of menu on stack.
LD HL,$EC0D ; Editor flags.
SET 1,(HL) ; Indicate 'menu displayed'.
RES 4,(HL) ; Signal return to main menu.
DEC HL ; Current menu index.
LD (HL),$00 ; Select top entry.
POP HL ; Retrieve address of menu.
CALL L36A8 ; Display menu and highlight first item.
JP L2653 ; Jump ahead to enter the main key waiting and processing loop.
; ========================
; EDITOR ROUTINES - PART 2
; ========================
; -----------------------------------------------
; Return to Editor / Calculator / Menu from Error
; -----------------------------------------------
L25CB: LD IX,$FD6C ; Point IX at editing settings information.
LD HL,TSTACK ; $5BFF.
LD (OLDSP),HL ; $5B81.
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
LD A,$02 ;
RST 28H ;
DEFW CHAN_OPEN ; $1601. Select main screen.
CALL L3668 ; Reset 'L' mode.
LD HL,$5C3B ; FLAGS.
L25E3: BIT 5,(HL) ; Has a key been pressed?
JR Z,L25E3 ; Wait for a key press.
LD HL,$EC0D ; Editor flags.
RES 3,(HL) ; Signal line has not been altered.
BIT 6,(HL) ; Is editing area the lower screen?
JR NZ,L2604 ; If so then skip printing a banner and jump ahead to return to the Editor.
LD A,($EC0E) ; Fetch mode.
CP $04 ; Calculator mode?
JR Z,L2601 ; Jump ahead if so.
CP $00 ; Edit Menu mode?
JP NZ,L28C7 ; Jump if not to re-display Main menu.
;Edit menu Print mode
CALL L3848 ; Clear screen and print "128 BASIC" in the banner line.
JR L2604 ; Jump ahead to return to the Editor.
;Calculator mode
L2601: CALL L384D ; Clear screen and print "Calculator" in the banner line.
; --------------------
; Return to the Editor
; --------------------
; Either as the result of a re-listing, an error or from completing the Edit Menu Print option.
; [*BUG* - Occurs only with ZX Interface 1 attached and a BASIC line such as 1000 OPEN #4, "X" (the line number must be greater than 999).
; This produces the error message "Invalid device expression, 1000:1" but the message is too long to fit on a single line. When using the lower screen
; for editing, spurious effects happen to the bottom lines. When using the full screen editor, a crash occurs. Credit: Toni Baker, ZX Computing Monthly]
; [The bug is caused by system variable DF_SZ being increased to 3 as a result of the error message spilling onto an extra line. The error can be resolved
; by inserting a LD (IY+$31),$02 instruction at $2604 (ROM 0). Credit: Paul Farrow]
L2604: CALL L30D6 ; Reset Below-Screen Line Edit Buffer settings to their default values.
CALL L3222 ; Reset Above-Screen Line Edit Buffer settings to their default values.
LD A,($EC0E) ; Fetch the mode.
CP $04 ; Calculator mode?
JR Z,L2653 ; Jump ahead if not to wait for a key press.
;Calculator mode
LD HL,($5C49) ; E_PPC. Fetch current line number.
LD A,H ;
OR L ; Is there a current line number?
JR NZ,L262D ; Jump ahead if so.
LD HL,($5C53) ; PROG. Address of start of BASIC program.
LD BC,($5C4B) ; VARS. Address of start of variables area.
AND A ;
SBC HL,BC ; HL=Length of program.
JR NZ,L262A ; Jump if a program exists.
;No program exists
LD HL,$0000 ;
LD ($EC08),HL ; Set no line number last edited.
L262A: LD HL,($EC08) ; Fetch line number of last edited line.
L262D: CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
RST 28H ; Find address of line number held in HL, or the next line if it does not exist.
DEFW LINE_ADDR ; $196E. Return address in HL.
RST 28H ; Find line number for specified address, and return in DE.
DEFW LINE_NO ; $1695. Fetch the line number for the line found.
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
LD ($5C49),DE ; E_PPC. Save the current line number.
LD HL,$EC0D ; Editor flags.
BIT 5,(HL) ; Process the BASIC line?
JR NZ,L2653 ; Jump ahead if calculator mode.
LD HL,$0000 ;
LD ($EC06),HL ; Signal no editable characters in the line prior to the cursor.
CALL L152F ; Relist the BASIC program.
CALL L29F2 ; Set attribute at editing position so as to show the cursor.
CALL L2944 ; Call the ENTER handler routine.
; -----------------
; Main Waiting Loop
; -----------------
; Enter a loop to wait for a key press. Handles key presses for menus, the Calculator and the Editor.
L2653: LD SP,TSTACK ; $5BFF. Use temporary stack.
CALL L3668 ; Reset 'L' mode.
CALL L367F ; Wait for a key. [Note that it is possible to change CAPS LOCK mode whilst on a menu]
PUSH AF ; Save key code.
LD A,($5C39) ; PIP. Tone of keyboard click.
CALL L26EC ; Produce a key click noise.
POP AF ; Retrieve key code.
CALL L2669 ; Process the key press.
JR L2653 ; Wait for another key.
; -----------------
; Process Key Press
; -----------------
; Handle key presses for the menus and the Editor.
; Entry: A=Key code.
; Zero flag set if a menu is being displayed.
L2669: LD HL,$EC0D ; Editor flags.
BIT 1,(HL) ; Is a menu is displayed?
PUSH AF ; Save key code and flags.
LD HL,L2577 ; Use menu keys lookup table.
JR NZ,L2677 ; Jump if menu is being displayed.
LD HL,L2537 ; Use editing keys lookup table.
L2677: CALL L3FCE ; Find and call the action handler for this key press.
JR NZ,L2681 ; Jump ahead if no match found.
CALL NC,L26E7 ; If required then produce error beep.
POP AF ; Restore key code.
RET ;
;No action defined for key code
L2681: POP AF ; Restore key code and flags.
JR Z,L2689 ; Jump if menu is not being displayed.
;A menu is being displayed, so just ignore key press
XOR A ; Select 'L' mode.
LD ($5C41),A ; MODE.
RET ;
;A menu is not being displayed
L2689: LD HL,$EC0D ; Editor flags.
BIT 0,(HL) ; Is the Screen Line Edit Buffer is full?
JR Z,L2694 ; Jump if not to process the key code.
;The buffer is full so ignore the key press
CALL L26E7 ; Produce error beep.
RET ; [Could have save a byte by using JP L26E7 (ROM 0)]
L2694: CP $A3 ; Was it a supported function key code?
JR NC,L2653 ; Ignore by jumping back to wait for another key.
; [*BUG* - This should be RET NC since it was called from the loop at L2653 (ROM 0). Repeatedly pressing an unsupported
; key will result in a stack memory leak and eventual overflow. Credit: John Steven (+3), Paul Farrow (128)]
JP L28F1 ; Jump forward to handle the character key press.
; --------------------------
; TOGGLE Key Handler Routine
; --------------------------
; Toggle between editing in the lower and upper screen areas.
; Also used by the editing menu SCREEN option.
L269B: LD A,($EC0E) ; Fetch mode.
CP $04 ; Calculator mode?
RET Z ; Return if so (TOGGLE has no effect in Calculator mode).
CALL L1630 ; Clear Editing Display.
LD HL,$EC0D ; Editor flags.
RES 3,(HL) ; Reset 'line altered' flag.
LD A,(HL) ;
XOR $40 ; Toggle screen editing area flag.
LD (HL),A ;
AND $40 ;
JR Z,L26B6 ; Jump forward if the editing area is now the upper area.
CALL L26BB ; Set the lower area as the current editing area.
JR L26B9 ; Jump forward.
L26B6: CALL L26CE ; Set the upper area as the current editing area.
L26B9: SCF ; Signal do not produce an error beep.
RET ;
; -------------------
; Select Lower Screen
; -------------------
; Set the lower screen as the editing area.
L26BB: CALL L3881 ; Clear lower editing area display.
LD HL,$EC0D ; Editor flags.
SET 6,(HL) ; Signal using lower screen.
CALL L2E2D ; Reset to lower screen.
CALL L3A88 ; Set default lower screen editing cursor settings.
CALL L28DF ; Set default lower screen editing settings.
JR L26D9 ; Jump ahead to continue.
; -------------------
; Select Upper Screen
; -------------------
; Set the upper screen as the editing area.
L26CE: LD HL,$EC0D ; Editor flags.
RES 6,(HL) ; Signal using main screen.
CALL L28BE ; Reset Cursor Position.
CALL L3848 ; Clear screen and print the "128 BASIC" banner line.
L26D9: LD HL,($FC9A) ; Line number at top of screen.
LD A,H ;
OR L ; Is there a line?
CALL NZ,L334A ; If there is then get the address of BASIC line for this line number.
CALL L152F ; Relist the BASIC program.
JP L29F2 ; Set attribute at editing position so as to show the cursor, and return.
; ------------------
; Produce Error Beep
; ------------------
; This is the entry point to produce the error beep, e.g. when trying to cursor up or down past the BASIC program.
; It produces a different tone and duration from the error beep of 48K mode. The change is pitch is due to the SRL A
; instruction at $26EA (ROM 0), and the change in duration is due to the instruction at $26F1 (ROM 0) which loads HL with $0C80 as opposed
; to $1A90 which is used when in 48K mode. The key click and key repeat sounds are produced by entering at $26EC (ROM 0) but with A
; holding the value of system variable PIP. This produces the same tone as 48K mode but is of a much longer duration due to
; HL being loaded with $0C80 as opposed to the value of $00C8 used in 48K mode. The Spanish 128 uses the same key click tone
; and duration in 128K mode as it does in 48K mode, leading to speculation that the Spectrum 128 (and subsequent models) should
; have done the same and hence suffer from a bug. However, there is no reason why this should be the case, and it can easily be
; imagined that the error beep note duration of 48K mode would quickly become very irritating when in 128K mode where it is likely
; to occur far more often. Hence the reason for its shorter duration. The reason for the longer key click is less clear, unless it
; was to save memory by using a single routine. However, it would only have required an additional 3 bytes to set HL independently
; for key clicks, which is not a great deal considering there is 1/2K of unused routines at $2336 (ROM 0). Since the
; INPUT command is handled by ROM 1, it produces key clicks at the 48K mode duration even when executed from 128 BASIC mode.
L26E7: LD A,($5C38) ; RASP.
SRL A ; Divide by 2.
;This entry point is called to produce the key click tone. In 48K mode, the key click sound uses an HL value of L00C8
;and so is 16 times shorter than in 128K mode.
L26EC: PUSH IX ;
LD D,$00 ; Pitch;
LD E,A ;
LD HL,$0C80 ; Duration.
L26F4: RST 28H ;
DEFW BEEPER ; $03B5. Produce a tone.
POP IX ;
RET ;
; --------------------
; Produce Success Beep
; --------------------
L26FA: PUSH IX ;
LD DE,$0030 ; Frequency*Time;
LD HL,$0300 ; Duration.
JR L26F4 ; Jump to produce the tone.
; ======================
; MENU ROUTINES - PART 4
; ======================
; ===============================
; Menu Key Press Handler Routines
; ===============================
; -----------------------------
; Menu Key Press Handler - MENU
; -----------------------------
; This is executed when the EDIT key is pressed, either from within a menu or from the BASIC editor.
L2704: CALL L29EC ; Remove cursor, restoring old attribute.
LD HL,$EC0D ; HL points to Editor flags.
SET 1,(HL) ; Signal 'menu is being displayed'.
DEC HL ; HL=$EC0C.
LD (HL),$00 ; Set 'current menu item' as the top item.
L270F: LD HL,($F6EC) ; Address of text for current menu.
CALL L36A8 ; Display menu and highlight first item.
SCF ; Signal do not produce an error beep.
RET ;
; -------------------------------
; Menu Key Press Handler - SELECT
; -------------------------------
L2717: LD HL,$EC0D ; HL points to Editor flags.
RES 1,(HL) ; Clear 'displaying menu' flag.
DEC HL ; HL=$EC0C.
LD A,(HL) ; A=Current menu option index.
LD HL,($F6EA) ; HL points to jump table for current menu.
PUSH HL ;
PUSH AF ;
CALL L373E ; Restore menu screen area.
POP AF ;
POP HL ;
CALL L3FCE ; Call the item in the jump table corresponding to the
; currently selected menu item.
JP L29F2 ; Set attribute at editing position so as to show the cursor, and return.
; ----------------------------------
; Menu Key Press Handler - CURSOR UP
; ----------------------------------
L272E: SCF ; Signal move up.
JR L2732 ; Jump ahead to continue.
; ------------------------------------
; Menu Key Press Handler - CURSOR DOWN
; ------------------------------------
L2731: AND A ; Signal moving down.
L2732: LD HL,$EC0C ;
LD A,(HL) ; Fetch current menu index.
PUSH HL ; Save it.
LD HL,($F6EC) ; Address of text for current menu.
CALL C,L37A7 ; Call if moving up.
CALL NC,L37B6 ; Call if moving down.
POP HL ; HL=Address of current menu index store.
LD (HL),A ; Store the new menu index.
; Comes here to complete handling of Menu cursor up and down. Also as the handler routines
; for Edit Menu return to 128 BASIC option and Calculator menu return to Calculator option,
; which simply make a return.
L2742: SCF ;
RET ;
; ===========
; Menu Tables
; ===========
; ---------
; Main Menu
; ---------
; Jump table for the main 128K menu, referenced at $25AD (ROM 0).
L2744: DEFB $05 ; Number of entries.
DEFB $00
DEFW L2831 ; Tape Loader option handler.
DEFB $01
DEFW L286C ; 128 BASIC option handler.
DEFB $02
DEFW L2885 ; Calculator option handler.
DEFB $03
DEFW L1B47 ; 48 BASIC option handler.
DEFB $04
DEFW L2816 ; Tape Tester option handler.
; Text for the main 128K menu
L2754: DEFB $06 ; Number of entries.
DEFM "128 " ; Menu title.
DEFB $FF
L275E: DEFM "Tape Loade"
DEFB 'r'+$80
L2769: DEFM "128 BASI"
DEFB 'C'+$80
L2772: DEFM "Calculato"
DEFB 'r'+$80
DEFM "48 BASI"
DEFB 'C'+$80
L2784: DEFM "Tape Teste"
DEFB 'r'+$80
DEFB ' '+$80 ; $A0. End marker.
; ---------
; Edit Menu
; ---------
; Jump table for the Edit menu
L2790: DEFB $05 ; Number of entries.
DEFB $00
DEFW L2742 ; (Return to) 128 BASIC option handler.
DEFB $01
DEFW L2851 ; Renumber option handler.
DEFB $02
DEFW L2811 ; Screen option handler.
DEFB $03
DEFW L2862 ; Print option handler.
DEFB $04
DEFW L281C ; Exit option handler.
; Text for the Edit menu
L27A0: DEFB $06 ; Number of entries.
DEFM "Options "
DEFB $FF
DEFM "128 BASI"
DEFB 'C'+$80
DEFM "Renumbe"
DEFB 'r'+$80
DEFM "Scree"
DEFB 'n'+$80
DEFM "Prin"
DEFB 't'+$80
DEFM "Exi"
DEFB 't'+$80
DEFB ' '+$80 ; $A0. End marker.
; ---------------
; Calculator Menu
; ---------------
; Jump table for the Calculator menu
L27CB: DEFB $02 ; Number of entries.
DEFB $00
DEFW L2742 ; (Return to) Calculator option handler.
DEFB $01
DEFW L281C ; Exit option handler.
; Text for the Calculator menu
L27D2: DEFB 03 ; Number of entries.
DEFM "Options "
DEFB $FF
DEFM "Calculato"
DEFB 'r'+$80
DEFM "Exi"
DEFB 't'+$80
DEFB ' '+$80 ; $A0. End marker.
; ----------------
; Tape Loader Text
; ----------------
L27EB: DEFB $16, $01, $00 ; AT 1,0;
DEFB $10, $00 ; INK 0;
DEFB $11, $07 ; PAPER 7;
DEFB $13, $00 ; BRIGHT 1;
DEFM "To cancel - press BREAK twic"
DEFB 'e'+$80
; =====================
; Menu Handler Routines
; =====================
; -------------------------
; Edit Menu - Screen Option
; -------------------------
L2811: CALL L269B ; Toggle between editing in the lower and upper screen areas.
JR L2874 ; Jump ahead.
; ------------------------------
; Main Menu - Tape Tester Option
; ------------------------------
L2816: CALL L3857 ; Clear screen and print the "Tape Tester" in the banner.
CALL L3BE9 ; Run the tape tester, exiting via the 'Exit' option menu handler.
; -----------------------------------------
; Edit Menu / Calculator Menu - Exit Option
; -----------------------------------------
L281C: LD HL,$EC0D ; Editor flags.
RES 6,(HL) ; Indicate main screen editing.
CALL L28BE ; Reset Cursor Position.
LD B,$00 ; Top row to clear.
LD D,$17 ; Bottom row to clear.
CALL L3B5E ; Clear specified display rows.
CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
JP L259F ; Jump back to show the menu.
; ------------------------------
; Main Menu - Tape Loader Option
; ------------------------------
L2831: CALL L3852 ; Clear screen and print "Tape Loader" in the banner line.
LD HL,$5C3C ; TVFLAG.
SET 0,(HL) ; Signal using lower screen area.
LD DE,L27EB ; Point to message "To cancel - press BREAK twice".
CALL L057D ; Print the text.
RES 0,(HL) ; Signal using main screen area.
SET 6,(HL) ; [This bit is unused in the 48K Spectrum and only ever set in 128K mode via the Tape Loader option.
; It is never subsequently tested or reset. It may have been the intention to use this to indicate that
; the screen requires clear after loading to remove the "Tape Loader" banner and the lower screen
; message "To cancel - press BREAK twice"]
LD A,$07 ; Tape Loader mode.
LD ($EC0E),A ; [Redundant since call to L1AF1 (ROM 0) will set it to $FF]
LD BC,$0000 ;
CALL L372B ; Perform 'Print AT 0,0;'.
JP L1AF1 ; Run the tape loader.
; ---------------------------
; Edit Menu - Renumber Option
; ---------------------------
L2851: CALL L3888 ; Run the renumber routine.
CALL NC,L26E7 ; If not successful then produce error beep if required.
LD HL,$0000 ; There is no current line number.
LD ($5C49),HL ; E_PPC. Current line number.
LD ($EC08),HL ; Temporary E_PPC used by BASIC Editor.
JR L2865 ; Jump ahead to display the "128 BASIC" banner if required, set the menu mode and return.
; ------------------------
; Edit Menu - Print Option
; ------------------------
L2862: CALL L1B14 ; Perform an LLIST.
;Edit Menu - Renumber option joins here
L2865: LD HL,$EC0D ; Editor flags.
BIT 6,(HL) ; Using lower editing screen?
JR NZ,L2874 ; Jump ahead if so.
L286C: LD HL,$5C3C ; TVFLAG.
RES 0,(HL) ; Allow leading space.
CALL L3848 ; Clear screen and print the "128 BASIC" banner line.
;Edit Menu - Screen option joins here
L2874: LD HL,$EC0D ; Editor flags.
RES 5,(HL) ; Signal not to process the BASIC line.
RES 4,(HL) ; Signal return to main menu.
LD A,$00 ; Select Edit menu mode. [Could have saved 1 byte by using XOR A]
LD HL,L2790 ; Edit Menu jump table.
LD DE,L27A0 ; Edit Menu text table.
JR L28B1 ; Store the new mode and menu details.
; -----------------------------
; Main Menu - Calculator Option
; -----------------------------
L2885: LD HL,$EC0D ; Editor flags.
SET 5,(HL) ; Signal to process the BASIC line.
SET 4,(HL) ; Signal return to calculator.
RES 6,(HL) ; Signal editing are is the main screen.
CALL L28BE ; Reset cursor position.
CALL L384D ; Clear screen and print "Calculator" in the banner line.
LD A,$04 ; Set calculator mode.
LD ($EC0E),A ; Store mode.
LD HL,$0000 ; No current line number.
LD ($5C49),HL ; E_PPC. Store current line number.
CALL L152F ; Relist the BASIC program.
LD BC,$0000 ; B=Row. C=Column. Top left of screen.
LD A,B ; Preferred column.
CALL L29F8 ; Store editing position and print cursor.
LD A,$04 ; Select calculator mode.
LD HL,L27CB ; Calculator Menu jump table
LD DE,L27D2 ; Calculator Menu text table
;Edit Menu - Print option joins here
L28B1: LD ($EC0E),A ; Store mode.
LD ($F6EA),HL ; Store address of current menu jump table.
LD ($F6EC),DE ; Store address of current menu text.
JP L2604 ; Return to the Editor.
; ========================
; EDITOR ROUTINES - PART 3
; ========================
; ---------------------
; Reset Cursor Position
; ---------------------
L28BE: CALL L2E1F ; Reset to main screen.
CALL L3A7F ; Set default main screen editing cursor details.
JP L28E8 ; Set default main screen editing settings.
; -------------------
; Return to Main Menu
; -------------------
L28C7: LD B,$00 ; Top row of editing area.
LD D,$17 ; Bottom row of editing area.
CALL L3B5E ; Clear specified display rows.
JP L25AD ; Jump to show Main menu.
; ---------------------------------
; Main Screen Error Cursor Settings
; ---------------------------------
; Main screen editing cursor settings.
; Gets copied to $F6EE.
L28D1: DEFB $06 ; Number of bytes in table.
DEFB $00 ; $F6EE = Cursor position - row 0.
DEFB $00 ; $F6EF = Cursor position - column 0.
DEFB $00 ; $F6F0 = Cursor position - column 0 preferred.
DEFB $04 ; $F6F1 = Top row before scrolling up.
DEFB $10 ; $F6F2 = Bottom row before scrolling down.
DEFB $14 ; $F6F3 = Number of rows in the editing area.
; ---------------------------------
; Lower Screen Good Cursor Settings
; ---------------------------------
; Lower screen editing cursor settings.
; Gets copied to $F6EE.
L28D8: DEFB $06 ; Number of bytes in table.
DEFB $00 ; $F6EE = Cursor position - row 0.
DEFB $00 ; $F6EF = Cursor position - column 0.
DEFB $00 ; $F6F0 = Cursor position - column 0 preferred.
DEFB $00 ; $F6F1 = Top row before scrolling up.
DEFB $01 ; $F6F2 = Bottom row before scrolling down.
DEFB $01 ; $F6F3 = Number of rows in the editing area.
; ----------------------------------------
; Initialise Lower Screen Editing Settings
; ----------------------------------------
; Used when selecting lower screen. Copies 6 bytes from $28D9 (ROM 0) to $F6EE.
L28DF: LD HL,L28D8 ; Default lower screen editing information.
LD DE,$F6EE ; Editing information stores.
JP L3FBA ; Copy bytes.
; ---------------------------------------
; Initialise Main Screen Editing Settings
; ---------------------------------------
; Used when selecting main screen. Copies 6 bytes from $28D2 (ROM 0) to $F6EE.
L28E8: LD HL,L28D1 ; Default main screen editing information.
LD DE,$F6EE ; Editing information stores.
JP L3FBA ; Copy bytes.
; -------------------------------
; Handle Key Press Character Code
; -------------------------------
; This routine handles a character typed at the keyboard, inserting it into the Screen Line Edit Buffer
; as appropriate.
; Entry: A=Key press character code.
L28F1: LD HL,$EC0D ; Editor flags.
OR A ; Clear carry flag. [Redundant instruction since carry flag return state never checked]
OR A ; [Redundant instruction]
BIT 0,(HL) ; Is the Screen Line Edit Buffer is full?
JP NZ,L29F2 ; Jump if it is to set attribute at editing position so as to show the cursor, and return.
RES 7,(HL) ; Signal got a key press.
SET 3,(HL) ; Signal current line has been altered.
PUSH HL ; Save address of the flags.
PUSH AF ; Save key code.
CALL L29EC ; Remove cursor, restoring old attribute.
POP AF ;
PUSH AF ; Get and save key code.
CALL L2E81 ; Insert the character into the Screen Line Edit Buffer.
POP AF ; Get key code.
LD A,B ; B=Current cursor column position.
CALL L2B78 ; Find next Screen Line Edit Buffer editable position to right, moving to next row if necessary.
POP HL ; Get address of the flags.
SET 7,(HL) ; Signal wait for a key.
JP NC,L29F2 ; Jump if new position not available to set cursor attribute at existing editing position, and return.
LD A,B ; A=New cursor column position.
JP C,L29F8 ; Jump if new position is editable to store editing position and print cursor.
; [This only needs to be JP L29F8 (ROM 0), thereby saving 3 bytes, since a branch to $29F2 (ROM 0) would have been taken above if the carry flag was reset]
JP L29F2 ; Set attribute at editing position so as to show the cursor, and return.
; --------------------------------
; DELETE-RIGHT Key Handler Routine
; --------------------------------
; Delete a character to the right. An error beep is not produced if there is nothing to delete.
;
; Symbol: DEL
; -->
;
; Exit: Carry flag set to indicate not to produce an error beep.
L291B: LD HL,$EC0D ; HL points to Editor flags.
SET 3,(HL) ; Indicate 'line altered'.
CALL L29EC ; Remove cursor, restoring old attribute. Exit with C=row, B=column.
CALL L2F12 ; Delete character to the right, shifting subsequent rows as required.
SCF ; Signal do not produce an error beep.
LD A,B ; A=The new cursor editing position.
JP L29F8 ; Store editing position and print cursor, and then return.
; --------------------------
; DELETE Key Handler Routine
; --------------------------
; Delete a character to the left. An error beep is not produced if there is nothing to delete.
;
; Symbol: DEL
; <--
;
; Exit: Carry flag set to indicate not to produce an error beep.
L292B: LD HL,$EC0D ; HL points to Editor flags.
RES 0,(HL) ; Signal that the Screen Line Edit Buffer is not full.
SET 3,(HL) ; Indicate 'line altered'.
CALL L29EC ; Remove cursor, restoring old attribute. Exit with C=row, B=column.
CALL L2B5B ; Select previous column position (Returns carry flag set if editable).
CCF ; Signal do not produce an error beep if not editable.
JP C,L29F2 ; Jump if not editable to set attribute at editing position so as to show the cursor, and return.
CALL L2F12 ; Delete character to the right, shifting subsequent rows as required.
SCF ; Signal do not produce an error beep.
LD A,B ; A=The new cursor editing position.
JP L29F8 ; Store editing position and print cursor, and then return.
; -------------------------
; ENTER Key Handler Routine
; -------------------------
; This routine handles ENTER being pressed. If not on a BASIC line then it does nothing. If on an
; unaltered BASIC line then insert a blank row after it and move the cursor to it. If on an altered
; BASIC line then attempt to enter it into the BASIC program, otherwise return to produce an error beep.
; Exit: Carry flag reset to indicate to produce an error beep.
L2944: CALL L29EC ; Remove cursor, restoring old attribute.
PUSH AF ; Save preferred column number.
CALL L30B4 ; DE=Start address in Screen Line Edit Buffer of the row specified in C.
PUSH BC ; Stack current editing position.
LD B,$00 ; Column 0.
CALL L2E41 ; Is this a blank row? i.e. Find editable position on this row to the right, returning column number in B.
POP BC ; Retrieve current editing position.
JR C,L295E ; Jump ahead if editable position found, i.e. not a blank row.
;No editable characters on the row, i.e. a blank row
LD HL,$0020 ;
ADD HL,DE ; Point to the flag byte for the row.
LD A,(HL) ; Fetch the flag byte.
CPL ; Invert it.
AND $09 ; Keep the 'first row' and 'last row' flags.
JR Z,L297A ; Jump if both flags were set indicating not on a BASIC line.
;On a BASIC line
L295E: LD A,($EC0D) ; Editor flags.
BIT 3,A ; Has the current line been altered?
JR Z,L296A ; Jump ahead if not.
;The current BASIC line has been altered
CALL L2C8E ; Enter line into program.
JR NC,L297F ; Jump if syntax error to produce an error beep.
L296A: CALL L2C4C ; Find end of the current BASIC line in the Screen Line Edit Buffer, scrolling up rows as required. Returns column number into B.
CALL L2B78 ; Find address of end position in current BASIC line. Returns address into HL.
CALL L2ECE ; Insert a blank line in the Screen Line Edit Buffer, shifting subsequent rows down.
;Display the cursor on the first column of the next row
LD B,$00 ; First column.
POP AF ; A=Preferred column number.
SCF ; Signal do not produce an error beep.
JP L29F8 ; Store editing position and print cursor, and then return.
;Cursor is on a blank row, which is not part of a BASIC line
L297A: POP AF ; Discard stacked item.
SCF ; Signal do not produce an error beep.
JP L29F2 ; Set attribute at current editing position so as to show the cursor, and return.
;A syntax error occurred so return signalling to produce an error beep
L297F: POP AF ; Discard stacked item.
JP L29F2 ; Set attribute at current editing position so as to show the cursor, and return.
; ----------------------------------
; TOP-OF-PROGRAM Key Handler Routine
; ----------------------------------
; Move to the first row of the first line of the BASIC program. An error beep is not produced if there is no program.
;
; Symbol: ------
; / \/ \
; | |
;
; Exit: Carry flag set to indicate not to produce an error beep.
L2983: LD A,($EC0E) ; Fetch mode.
CP $04 ; Calculator mode?
RET Z ; Exit if so.
;Editor mode
CALL L29EC ; Remove cursor, restoring old attribute.
LD HL,$0000 ; The first possible line number.
CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
RST 28H ; Find address of line number 0, or the next line if it does not exist.
DEFW LINE_ADDR ; $196E. Return address in HL.
RST 28H ; Find line number for specified address, and return in DE.
DEFW LINE_NO ; $1695. DE=Address of first line in the BASIC program.
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
LD ($5C49),DE ; E_PPC. Store the current line number.
LD A,$0F ; Paper 1, Ink 7 - Blue.
CALL L3A96 ; Set the cursor colour.
CALL L152F ; Relist the BASIC program.
SCF ; Signal do not produce an error beep.
JP L29F2 ; Set attribute at editing position so as to show the cursor, and return.
; ----------------------------------
; END-OF-PROGRAM Key Handler Routine
; ----------------------------------
; Move to the last row of the bottom line of the BASIC program. An error beep is not produced if there is no program.
;
; Symbol: | |
; \ /\ /
; ------
;
; Exit: Carry flag set to indicate not to produce an error beep.
L29AB: LD A,($EC0E) ; Fetch mode.
CP $04 ; Calculator mode?
RET Z ; Exit if so.
;Editor mode
CALL L29EC ; Remove cursor, restoring old attribute.
LD HL,$270F ; The last possible line number, 9999.
CALL L1F20 ; Use Normal RAM Configuration (physical RAM bank 0).
RST 28H ; Find address of line number 9999, or the previous line if it does not exist.
DEFW LINE_ADDR ; $196E. Return address in HL.
EX DE,HL ; DE=Address of last line number.
RST 28H ; Find line number for specified address, and return in DE.
DEFW LINE_NO ; $1695. DE=Address of last line in the BASIC program.
CALL L1F45 ; Use Workspace RAM configuration (physical RAM bank 7).
LD ($5C49),DE ; E_PPC. Store the current line number.
LD A,$0F ; Paper 1, Ink 7 - Blue.
CALL L3A96 ; Set the cursor colour.
CALL L152F ; Relist the BASIC program.
SCF ; Signal do not produce an error beep.
JP L29F2 ; Set attribute at editing position so as to show the cursor, and return.
; -----------------------------
; WORD-LEFT Key Handler Routine
; -----------------------------
; This routine moves to the start of the current word that the cursor is on, or if it is on the first
; character of a word then it moves to the start of the previous word. If there is no word to move to
; then signal to produce an error beep.
;
; Symbol: <--
; <--
;
; Exit: Carry flag reset to indicate to produce an error beep.
L29D4: CALL L29EC ; Remove cursor, restoring old attribute.
CALL L2BEA ; Find start of the current word to the left.
JP NC,L29F2 ; Jump if no word to the left to restore cursor attribute at current editing position, and return.
; [Could have saved 4 bytes by joining the routine below, i.e. JR L29E7]
LD A,B ; A=New cursor column number. Carry flag is set indicating not to produce an error beep.
JP L29F8 ; Store editing position and print cursor, and then return.
; ------------------------------
; WORD-RIGHT Key Handler Routine
; ------------------------------
; This routine moves to the start of the next word. If there is no word to move to then signal to produce an error beep.
;
; Symbol: -->
; -->
;
; Exit: Carry flag reset to indicate to produce an error beep.
L29E1: CALL L29EC ; Remove cursor, restoring old attribute.
CALL L2C09 ; Find start of the current word to the right.
JR NC,L29F2 ; Jump if no word to the right to restore cursor attribute at current editing position, and return.
LD A,B ; A=The new cursor editing column number. Carry is set indicating not to produce an error beep.
JR L29F8 ; Store editing position and print cursor, and then return.
; -------------
; Remove Cursor
; -------------
; Remove editing cursor colour from current position.
; Exit: C=row number.
; B=Column number.
L29EC: CALL L2A07 ; Get current cursor position (C=row, B=column, A=preferred column).
JP L364F ; Restore previous colour to character square
; -----------
; Show Cursor
; -----------
; Set editing cursor colour at current position.
; Exit: C=row number.
; B=Column number.
L29F2: CALL L2A07 ; Get current cursor position (C=row, B=column, A=preferred column).
JP L3640 ; Set editing position character square to cursor colour to show it.
; [Could have saved 1 byte by using a JR instruction to join the end of the routine below]
; --------------
; Display Cursor
; --------------
; Set editing cursor position and colour and then show it.
; Entry: C=Row number.
; B=Column number.
; A=Preferred column number.
L29F8: CALL L2A11 ; Store new editing position.
PUSH AF ;
PUSH BC ;
LD A,$0F ; Paper 1, Ink 7 - Blue.
CALL L3A96 ; Store new cursor colour.
POP BC ;
POP AF ;
JP L3640 ; Set editing position character square to cursor colour to show it.
; ---------------------
; Fetch Cursor Position
; ---------------------
; Returns the three bytes of the cursor position.
; Exit : C=Row number.
; B=Column number
; A=Preferred column number.
L2A07: LD HL,$F6EE ; Editing info.
LD C,(HL) ; Row number.
INC HL ;
LD B,(HL) ; Column number.
INC HL ;
LD A,(HL) ; Preferred column number.
INC HL ;
RET ;
; ---------------------
; Store Cursor Position
; ---------------------
; Store new editing cursor position.
; Entry: C=Row number.
; B=Column number.
; A=Preferred column number.
L2A11: LD HL,$F6EE ; Editing information.
LD (HL),C ; Row number.
INC HL ;
LD (HL),B ; Column number.
INC HL ;
LD (HL),A ; Preferred column number.
RET ;
; --------------------------------------------------
; Get Current Character from Screen Line Edit Buffer
; --------------------------------------------------
; Entry: C=Row number.
; B=Column number.
; Exit : A=Character.
L2A1A: PUSH HL ;
CALL L30B4 ; DE=Start address in Screen Line Edit Buffer of the row specified in C.
LD H,$00 ; [Could have saved 2 bytes by calling the unused routine at L2E7B (ROM 0)]
LD L,B ;
ADD HL,DE ; Point to the column position within the row.
LD A,(HL) ; Get character at this position.
POP HL ;
RET ;
; ---------------------------------
; TEN-ROWS-DOWN Key Handler Routine
; ---------------------------------
; Move down 10 rows within the BASIC program, attempting to place the cursor as close to the preferred column number as possible.
; An error beep is produced if there is not 10 rows below.
;
; Symbol: | |
; \ /\ /
;
; Exit: Carry flag reset to indicate to produce an error beep.
L2A25: CALL L29EC ; Remove cursor, restoring old attribute.
LD E,A ; E=Preferred column.
LD D,$0A ; The ten lines to move down.
L2A2B: PUSH DE ;
CALL L2B30 ; Move down to the next row, shifting rows up as appropriate. If moving onto a new BASIC line then
POP DE ; insert the previous BASIC line into the BASIC program if it has been altered. Returns new row number in C.
JR NC,L29F2 ; Jump if there was no row below to set attribute at editing position so as to show the cursor, and return.
LD A,E ; A=Preferred column.
CALL L2A11 ; Store cursor editing position.
LD B,E ; B=Preferred column.
CALL L2AF9 ; Find closest Screen Line Edit Buffer editable position to the right else to the left, returning column number in B.
JR NC,L2A42 ; Jump if no editable position found on the row, i.e. a blank row.
DEC D ; Decrement row counter.
JR NZ,L2A2B ; Repeat to move down to the next row.
LD A,E ; A=Preferred column.
JR C,L29F8 ; Jump if editable row exists to store editing position and print cursor, and then return.
; [Redundant check of the carry flag, should just be JR L29F8 (ROM 0)]
;A blank row was found below, must be at the end of the BASIC program
L2A42: PUSH DE ;
CALL L2B0B ; Move back up to the previous row.
POP DE ;
LD B,E ; B=Preferred column.
CALL L2AF9 ; Find closest Screen Line Edit Buffer editable position to the right else to the left, returning column number in B.
LD A,E ; A=Preferred column.
OR A ; Carry will be reset indicating to produce an error beep.
JR L29F8 ; Store editing position and print cursor, and then return.
; -------------------------------
; TEN-ROWS-UP Key Handler Routine
; -------------------------------
; Move up 10 rows within the BASIC program, attempting to place the cursor as close to the preferred column number as possible.
; An error beep is produced if there is not 10 rows above.
;
; Symbol: / \/ \
; | |
;
; Exit: Carry flag reset to indicate to produce an error beep.
L2A4F: CALL L29EC ; Remove cursor, restoring old attribute.
LD E,A ; E=Preferred column.
LD D,$0A ; The ten lines to move up.
L2A55: PUSH DE ;
CALL L2B0B ; Move up to the previous row, shifting rows down as appropriate. If moving onto a new BASIC line then
POP DE ; insert the previous BASIC line into the BASIC program if it has been altered.
JR NC,L29F2 ; Jump if there was no row above to set cursor attribute colour at existing editing position, and return.
LD A,E ; A=Preferred column.
CALL L2A11 ; Store cursor editing position.
LD B,E ; B=Preferred column.
CALL L2B02 ; Find closest Screen Line Edit Buffer editable position to the left else right, return column number in B.
JR NC,L2A6D ; Jump if no editable positions were found in the row, i.e. it is a blank row.
DEC D ; Decrement row counter.
JR NZ,L2A55 ; Repeat to move up to the previous row.
LD A,E ; A=Preferred column.
JP C,L29F8 ; Jump if editable row exists to store editing position and print cursor, and then return.
; [Redundant check of the carry flag, should just be JP L29F8 (ROM 0)]
;A blank row was found above, must be at the start of the BASIC program [???? Can this ever be the case?]
L2A6D: PUSH AF ; Save the preferred column number and the flags.
CALL L2B30 ; Move back down to the next row. Returns new row number in C.
LD B,$00 ; Column 0.
CALL L2BD4 ; Find editable position in the Screen Line Edit Buffer row to the right, return column position in B.
POP AF ; A=Preferred column. Carry will be reset indicating to produce an error beep.
JP L29F8 ; Store editing position and print cursor, and then return.
; -------------------------------
; END-OF-LINE Key Handler Routine
; -------------------------------
; Move to the end of the current BASIC line. An error beep is produced if there is no characters in the current BASIC line.
;
; Symbol: -->|
; -->|
;
; Exit: Carry flag reset to indicate to produce an error beep and set not to produce an error beep.
L2A7A: CALL L29EC ; Remove cursor, restoring old attribute.
CALL L2C4C ; Find the end of the current BASIC line in the Screen Line Edit Buffer.
JP NC,L29F2 ; Jump if a blank row to set attribute at existing editing position so as to show the cursor, and return.
LD A,B ; A=The new cursor editing column number. Carry is set indicating not to produce an error beep.
JP L29F8 ; Store editing position and print cursor, and then return.
; ---------------------------------
; START-OF-LINE Key Handler Routine
; ---------------------------------
; Move to the start of the current BASIC line. An error beep is produced if there is no characters in the current BASIC line.
;
; Symbol: |<--
; |<--
;
; Exit: Carry flag reset to indicate to produce an error beep.
L2A87: CALL L29EC ; Remove cursor, restoring old attribute.
CALL L2C31 ; Find the start of the current BASIC line in the Screen Line Edit Buffer.
JP NC,L29F2 ; Jump if a blank row to set attribute at existing editing position so as to show the cursor, and return.
LD A,B ; A=The new cursor editing position. Carry is set indicating not to produce an error beep.
JP L29F8 ; Store editing position and print cursor, and then return.
; -----------------------------
; CURSOR-UP Key Handler Routine
; -----------------------------
; Move up 1 row, attempting to place the cursor as close to the preferred column number as possible.
; An error beep is produced if there is no row above.
; Exit: Carry flag reset to indicate to produce an error beep.
L2A94: CALL L29EC ; Remove cursor, restoring old attribute.
LD E,A ; E=Preferred column.
PUSH DE ;
CALL L2B0B ; Move up to the previous row, shifting rows down as appropriate. If moving onto a new BASIC line then
POP DE ; insert the previous BASIC line into the BASIC program if it has been altered.
JP NC,L29F2 ; Jump if there was no row above to set cursor attribute colour at existing editing position, and return.
LD B,E ; B=Preferred column.
CALL L2B02 ; Find closest Screen Line Edit Buffer editable position to the left else right, return column number in B.
LD A,E ; A=Preferred column.
JP C,L29F8 ; Jump if an editable position was found to store editing position and print cursor, and then return.
;A blank row was found above, must be at the start of the BASIC program [???? Can this ever be the case?]
PUSH AF ; Save the preferred column number and the flags.
CALL L2B30 ; Move down to the next row, shifting rows up as appropriate. Returns new row number in C.
LD B,$00 ; Column 0.
CALL L2AF9 ; Find closest Screen Line Edit Buffer editable position to the right.
POP AF ; A=Preferred column. Carry flag is reset indicating to produce an error beep.
JP L29F8 ; Store editing position and print cursor, and then return.
; -------------------------------
; CURSOR-DOWN Key Handler Routine
; -------------------------------
; Move down 1 row, attempting to place the cursor as close to the preferred column number as possible.
; An error beep is produced if there is no row below.
; Exit: Carry flag reset to indicate to produce an error beep.
L2AB5: CALL L29EC ; Remove cursor, restoring old attribute.
LD E,A ; E=Preferred column.
PUSH DE ;
CALL L2B30 ; Move down to the next row, shifting rows up as appropriate. If moving onto a new BASIC line then
POP DE ; insert the previous BASIC line into the BASIC program if it has been altered. Returns new row number in C.
JP NC,L29F2 ; Jump if there was no row below to set attribute at editing position so as to show the cursor, and return.
LD B,E ; B=Preferred column.
CALL L2B02 ; Find closest Screen Line Edit Buffer editable position to the left else right, return column number in B.
LD A,E ; A=Preferred column.
JP C,L29F8 ; Jump if an editable position was found to store editing position and print cursor, and then return.
;A blank row was found above, must be at the start of the BASIC program [???? Can this ever be the case?]
PUSH DE ; Save the preferred column.
CALL L2B0B ; Move up to the previous row, shifting rows down as appropriate.
POP DE ;
LD B,E ; B=Preferred column.
CALL L2AF9 ; Find closest Screen Line Edit Buffer editable position to the right else to the left, returning column number in B.
LD A,E ; A=Preferred column.
OR A ; Reset carry flag to indicate to produce an error beep.
JP L29F8 ; Store editing position and print cursor, and then return.
; -------------------------------
; CURSOR-LEFT Key Handler Routine
; -------------------------------
; Move left 1 character, stopping if the start of the first row of the first BASIC line is reached.
; An error beep is produced if there is no character to the left or no previous BASIC line to move to.
; Exit: Carry flag reset to indicate to produce an error beep.
L2AD7: CALL L29EC ; Remove cursor, restoring old attribute. Returns with C=row, B=column.
CALL L2B5B ; Find next Screen Line Edit Buffer editable position to left, wrapping to previous row as necessary.
JP C,L29F8 ; Jump if editable position found to store editing position and print cursor, and then return.
;A blank row was found above, must be at the start of the BASIC program
JP L29F2 ; Set cursor attribute at existing editing position, and return. Carry flag is reset indicating to produce an error beep.
; --------------------------------
; CURSOR-RIGHT Key Handler Routine
; --------------------------------
; Move right 1 character, stopping if the end of the last row of the last BASIC line is reached.
; An error beep is produced if there is no character to the right or no next BASIC line to move to.
; Exit: Carry flag reset to indicate to produce an error beep.
L2AE3: CALL L29EC ; Remove cursor, restoring old attribute.
CALL L2B78 ; Find next Screen Line Edit Buffer editable position to right, wrapping to next row if necessary.
JP C,L29F8 ; Jump if editable position found to store editing position and print cursor, and then return.
;A blank row was found below, must be at the end of the BASIC program
PUSH AF ; Save the carry flag and preferred column number.
CALL L2B0B ; Move up to the previous row, shifting rows down as appropriate.
LD B,$1F ; Column 31.
CALL L2BDF ; Find the last editable column position searching to the left, returning the column number in B. (Returns carry flag set if there is one)
;
POP AF ; Carry flag is reset indicating to produce an error beep.
JP L29F8 ; Store editing position and print cursor, and then return.
; =============================
; Edit Buffer Routines - Part 1
; =============================
; -----------------------------------------------------------------------------
; Find Closest Screen Line Edit Buffer Editable Position to the Right else Left
; -----------------------------------------------------------------------------
; This routine searches the specified Screen Line Edit Buffer row from the specified column to the right
; looking for the first editable position. If one cannot be found then a search is made to the left.
; Entry: B=Column number.
; Exit : Carry flag set if character at specified column is editable.
; B=Number of closest editable column.
; HL=Address of closest editable position.
L2AF9: PUSH DE ;
CALL L2BD4 ; Find Screen Line Edit Buffer editable position from previous column (or current column if the previous column does not exist) to the right, return column position in B.
CALL NC,L2BDF ; If no editable character found then search to the left for an editable character, return column position in B.
POP DE ;
RET ;
; -----------------------------------------------------------------------------
; Find Cl