.........1.........2.........3.........4.........5.........6.........7.........8

STREAMS AND CHANNELS
by Toni Baker
part 5 of 5, ZX Computing April 1987

This month, Toni Baker opens up a new
channel to the 128's RAMdisc facility.

[Note that the code in STREAMS.TAP "PART5"]
[includes the code from "PART4".      JimG]


This is the final part in the Streams and Channels series, and this is
an article for people who own either a Spectrum 128 or a Spectrum
128+2. This month's new channel introduces SERIAL FILES to the 128K
machines.

People who have Interface Ones and Microdrives will already be used to
serial files on microdrive cartridge. In this case you open a
microdrive file either as a READ file (if the file already exists on
microdrive) or a WRITE file (in which case the file must be created on
microdrive). You can print text to a WRITE file, and then CLOSE and
OPEN it (so that it becomes a READ file) and you may then input your
text as either strings or numbers into a BASIC variable.


RAMdisc

RAMdisc files work in exactly the same way, except that you don't need
an Interface One. When you first OPEN a RAMdisc file for writing, a
file with a given name is created on the Spectrum's so-called "silicon
disc". Text or numbers may then be printed into this file. Once the
channel is closed no more printing to the file is possible. The file
may, however, be re-OPENed as a read file, in which case whatever is
in the file may be input into a BASIC variable.

As with microdrive files, RAMdisc files must be CLOSEd once all the
data has been printed to, or input from, the file. If a WRITE file is
not closed then some or all of the data may be lost, as it will not be
cleared from a special buffer. If a READ file is not closed then the
consequences are less serious, however, each RAMdisc channel requires
more than 1/2K, which may only be reclaimed by closing the file. You
should always close such a file once you have finished with it.

Once a file has been opened, it will appear in the RAMdisc catalogue,
which you can verify by typing CAT! in BASIC. It is impossible to LOAD
a RAMdisc serial file using a LOAD command, however it Is possible to
ERASE a RAMdisc serial file in the usual manner, by entering ERASE!
"filename". You should never ERASE a RAMdisc serial file which is
still in use (ie. which still has a stream attached to it). The
machine code program does in fact protect itself from this eventually,
so such an error would not be fatal - however, you will certainly get
spurious results if you break this rule.


Silicon disc

The key to how the machine code program works is the manipulation of
the memory organisation known as the silicon disc, or RAMdisc, which
is normally used to save programs, data, or machine code for as long
as the machine is switched on. RAMdisc files are much faster than
microdrive files, but the whole of RAMdisc is erased when the machine
is switched off. RAMdisc serial files will, of course, suffer from
precisely these advantages and disadvantages.

The RAMdisc memory itself is primarily organised by the CATALOGUE,
which is an index to all files saved in RAMdisc. The CATALOGUE resides
in RAM page seven. It is effectively a stack, which begins at address
7EBFF and grows downwards, with each entry taking twenty bytes. Figure
Two shows the meanings of these twenty bytes, with IX pointing to the
first of these bytes. At the end of the catalogue stack is a twenty
byte "End of catalogue" marker, only three bytes of which are used.
The system variable (SFNEXT) points to this marker, and is effectively
the stack-pointer for this catalogue stack.

So long as we keep the catalogue in its required format, we can
manipulate the RAMdisc organisation itself from machine code. This is
the aim of this issue's program.

The RAMdisc files themselves begin in RAM page 1, and grow upwards
through RAM pages 3, 4, 6 and 7 (care is taken to ensure that RAMdisc
files do not collide with the catalogue stack). To avoid any problems
with this strange page- numbering, all addresses in RAMdisc are
"page-coded". This means that one register will hold a page-code,
while another register-pair will hold an actual physical address.
These page codes are 0, 1, 2, 3 and 4 sequentially for RAMdisc files,
with page-code 5 being the conventional notation for normal (48K) RAM.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Figure 1
R CHANNEL INFORMATION BLOCK
---------------------------

IX+00  R_OUT      Address of RAMdisc file output routine (=B92B)
IX+02  R_IN       Address of RAMdisc file input routine (=B7D4)
IX+04  R_NAME     Name of channel (="R")
IX+05  R_IDEN     New channel identifier (=1234h)
IX+07  R_CLOSE    Address of RAMdisc file close routine (=B960)
IX+09  R_LEN      Length of channel information block (=021B)
IX+0B  R_CHBYTE   Pointer into buffer
IX+0D  R_CHREC    Record number within file
IX+0E  R_CHNAME   File name
IX+18  R_CHFLAG   Various flags, defined as follows:
     Bits 7 to 2: Not used
     Bit 1:       Set if End-Of-File at end of record, reset otherwise
     Bit 0:       Reset for a READ file, set for a WRITE file
IX+19  R_RECLEN   Length of record within buffer
IX+1B  R_BUFFER   Buffer storing current record
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Figure 2
CATALOGUE INDEX INFORMATION
---------------------------

IX+00  SF_NAME    File name
IX+0A  SF_START   Page-coded address of start of file
IX+0D  SF_LEN     Total number of bytes in file, including header info
IX+10  SF_END     Page-coded address of byte beyond end of file
IX+13  SF_FLAG    Reset unless catalogue information incomplete
                  (ie. reset normally)
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


Channel "R"

We shall call our new channel "R", which stands for RAMdisc serial
file. This is not to be confused with the ROM's internal "R" channel,
which is used for inserting bytes into normal (48K) dynamic RAM.

Internal-channel-"R" is quite interesting in fact. It is permanently
attached to stream minus-one, so to use it from machine code all you
have to do is select stream minus-one as the current stream (by
loading A with FF and calling address 1601h). It is impossible to use
from BASIC. Prior to selecting stream minus-one the system variable
K_CUR must be made to point somewhere into dynamic RAM. Printing to
stream FF will then insert characters into dynamic memory at the point
indicated by (K_CUR).

Our new channel is also called "R", but its use is much more exciting
- and of course it MAY be used from BASIC.

We require a channel information block over 1/2K in size. Most of this
is in fact a huge 0200h byte buffer. For reasons of speed, the buffer
is used most of the time, with RAMdisc itself only being accessed once
the end of the buffer is reached. It is of course important to realise
that the location of a RAMdisc file is not constant - it may move
either if another file is erased, or if more bytes are inserted into
another RAMdisc serial file. This means that the file has to be
relocated every time we wish to read or write into it. To save time we
make use of a temporary buffer virtually all of the time.

Figure One shows the actual organisation of the channel information
block for our R channel. Note that R_CHREC, R_RECLEN, and bit one of
R_CHFLAG are used only if the file is a READ file. The remaining
variables are used for both types of file.

To interface with BASIC, an example machine code program is appended
to the end. Essentially, a RAMdisc serial file may be opened by
loading the A register with the stream number of the stream to be
opened, whilst the actual filename is stored in the system variable
N_STR1 at address 5B67, and then calling the label R_OPEN. Four
additional entry points are included, labelled OPEN_4, OPEN_5, CLOSE_4
and CLOSE_5. Calling OPEN_4 will open a RAMdisc serial file called
"FILE1" and attach it to stream four. Similarly, calling OPEN_5 will
open a RAMdisc serial file called "FILE2" and attach it to stream
five. The routines CLOSE_4 and CLOSE_5 will of course close these new
channels.

This means that the new channel may easily be used in BASIC. Take a
look at Figure Three. It contains a BASIC program which demonstrates
the RAMdisc serial files at work, first as WRITE files, then as READ
files. Try it - you may be surprised at how fast it all works.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Figure 3

1000 REM WRITE FILE DEMO
1010 RANDOMIZE USR 47713: REM OPEN #4,"R_FILE1"
1020 RANDOMIZE USR 47720: REM OPEN #5,"R_FILE2"
1030 FOR i=1 TO 512
1040 INPUT "": PRINT i
1050 PRINT #4;2*i
1060 PRINT #5;i*i
1070 NEXT i
1080 RANDOMIZE USR 47736: REM CLOSE #4
1090 RANDOMIZE USR 47740: REM CLOSE #5
1100 REM READ FILE DEMO
1110 RANDOMIZE USR 47713: REM OPEN #4,"R_FILE1"
1120 RANDOMIZE USR 47720: REM OPEN #5,"R_FILE2"
1130 FOR i=1 TO 512
1140 INPUT #4;a
1150 INPUT #5;b
1160 PRINT a,b
1170 NEXT i
1180 RANDOMIZE USR 47736: REM CLOSE #4
1190 RANDOMIZE USR 47740: REM CLOSE #5
1200 STOP 
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


Of course you won't always want your files to be called "FILE1" or
"FILE2'; and you won't always want to use streams four or five. That
is why the more general entry point R_OPEN is included, which, as has
already been stated, requires that A contains the stream number, and
(N_STR1) contains the filename (with trailing spaces if required). You
will have to write your own machine code to patch any other
combination into BASIC, utilising this routine.

The potential for streams and channels is limitless. It is
theoretically possible, for instance, to have a channel which utilises
a RANDOM ACCESS FILE, or INDEXED FILE, in RAMdisc - though the program
would have to be much more complicated. I have shown you enough of the
potential use for streams and channels to whet your appetite a little,
and there I shall leave you. If demand is high enough, I may return
with more. Good programming everyone, and may the force be with you.


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ERRATUM
In OPEN_NEW at address B06D (Streams and Channels Part Two, January
Issue, page 68) there is an instruction missing. The instruction is
POP BC (hex code C1). It should be the eleventh instruction of the
routine, occurring between OR C and JR Z,OPEN_NEW_2. Sorry about that.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
The following are vectored routines in the Spectrum's ROM 0. Notice that there
are two alternative such vector tables, one for the Spectrum 128 and one for
the 128+2. This is because the two machines have different ROMs. If the ROM of
the 128+2 is changed at some indefinite point in the future, then it will be
necessary to rewrite this table further.

(Spectrum 128 only)
B6FC C3AC05   V_ERROR   JP   #05AC         ;Generate an error report
B6FF C3641C   V_PAGE    JP   #1C64         ;Change current RAM page
B702 C3971C   V_NEWCAT  JP   #1C97         ;Create new entry in catalogue
B705 C3F31C   V_SPACE   JP   #1CF3         ;Ensure enough space in RAMdisc area
B708 C3121D   V_FIND    JP   #1D12         ;Find catalogue entry for filename
B70B C3561D   V_CATEND  JP   #1D56         ;Tidy up last catalogue entry

(Spectrum 128+2 only)
B6FC C3AC05   V_ERROR   JP   #05CB         ;Generate an error report
B6FF C3641C   V_PAGE    JP   #1C83         ;Change current RAM page
B702 C3971C   V_NEWCAT  JP   #1CB6         ;Create new entry in catalogue
B705 C3F31C   V_SPACE   JP   #1D12         ;Ensure enough space in RAMdisc area
B708 C3121D   V_FIND    JP   #1D31         ;Find catalogue entry for filename
B70B C3561D   V_CATEND  JP   #1D75         ;Tidy up last catalogue entry

The rest of the program will be the same, whichever version of the Spectrum you
have. The following subroutine will decrement a page-coded address held in
register triplet BHL.

B70E 2B       DEC_BHL   DEC  HL            ;Decrement HL
B70F 78                 LD   A,B           ;A= page code
B710 FE05               CP   #05
B712 C8                 RET  Z             ;Return if using standard RAM
B713 CB74               BIT  6,H
B715 C0                 RET  NZ            ;Return unless HL has crossed
                                           ;a page boundary
B716 CBF4               SET  6,H           ;Correct address in HL
B718 05                 DEC  B             ;Decrement page code
B719 C9                 RET                ;Return

The following subroutine works a bit like a glorified LDDR instruction, which
works in RAMdisc area as well as in standard memory. Its action is threefold:
(1) Decrement BHL and BHL'; (2) Load one byte from address (BHL) to address
(BHL'); (3) If BHL is not equal to CDE then go to step (1).

B71A CD0EB7   RTRANSFER CALL DEC_BHL       ;Decrement page-coded address in BHL
B71D D9                 EXX
B71E CD0EB7             CALL DEC_BHL       ;Decrement page-coded address in BHL'
B721 D9                 EXX
B722 78                 LD   A,B           ;A= paging code of FROM address
B723 CDFFB6             CALL V_PAGE        ;Page in the FROM memory page
B726 7E                 LD   A,(HL)        ;A= byte to load
B727 F5                 PUSH AF            ;Stack this byte
B728 D9                 EXX
B729 78                 LD   A,B           ;A= paging code of TO address
B72A CDFFB6             CALL V_PAGE        ;Page in the TO memory page
B72D F1                 POP  AF            ;A= byte to load
B72E 77                 LD   (HL),A        ;Load byte into memory as required
B72F D9                 EXX
B730 78       RTRANSFE2 LD   A,B           ;A= paging code of FROM address
B731 B9                 CP   C
B732 20E6               JR   NZ,RTRANSFER  ;Loop back if not equal to the paging
                                           ;code of the limiting address
B734 ED52               SBC  HL,DE         ;Set Zero flag if address = limit
B736 19                 ADD  HL,DE         ;(ADD HL,DE doesn't affect Zero flag)
B737 20E1               JR   NZ,RTRANSFER  ;Loop back unless limit has been
                                           ;reached
B739 C9                 RET                ;Return

The next subroutine will calculate the page-coded address which is BC bytes
further on from AHL. It assumes that BC is always less than 4000h.

B73A 09       ADD_HL_BC ADD  HL,BC         ;Increment HL by BC bytes
B73B FE05               CP   #05
B73D C8                 RET  Z             ;Return if using standard RAM
B73E CB74               BIT  6,H
B740 C0                 RET  NZ            ;Return unless page boundary crossed
B741 CBFC               SET  7,H
B743 CBF4               SET  6,H           ;Correct address in HL
B745 3C                 INC  A             ;Increment page code
B746 C9                 RET                ;Return

This subroutine will search for the file whose name is specified in the channel
information area, in the RAMdisc catalogue, giving an error if the file does not
exist. On exit IX will point to the catalogue entry.

B747 DDE5     FIND_FILE PUSH IX
B749 E1                 POP  HL            ;HL points to channel information
B74A 010E00             LD   BC,#000E
B74D 09                 ADD  HL,BC         ;HL points to filename
B74E 0E0A               LD   C,#0A         ;BC= length of filename (ten)
B750 11675B             LD   DE,N_STR1     ;DE points to system variable
B753 EDB0               LDIR               ;Copy filename into system variable
B755 CD08B7             CALL V_FIND        ;Find catalogue entry for this name
B758 C0                 RET  NZ            ;Return if file exists, with IX
                                           ;pointing to catalogue entry
B759 CDFCB6             CALL V_ERROR       ;Generate error message
B75C 23                 DEFB #23           ;"h File does not exist"

The next subroutine is designed to match up the buffer for R-channel with the
corresponding region of RAMdisc memory. The subroutine will leave BHL pointing
to the first byte beyond the RAMdisc segment, CDE pointing to the start of the
RAMdisc segment, and BHL' pointing to the first byte beyond the corresponding
region in the R-channel buffer. It will also signal whether or not this is an
end-of-file block. It requires that IX initially points to the channel
information block.

B75D DDE5     R_MATCH   PUSH IX            ;Stack channel info address
B75F DD7E0D             LD   A,(R_CHREC)
B762 F5                 PUSH AF            ;Stack record number to match
B763 CD47B7             CALL FIND_FILE     ;IX points to catalogue entry
B766 C1                 POP  BC            ;B= record number
B767 CB20               SLA  B             ;B= record number x 2
B769 0E01               LD   C,#01         ;BC= 200h * record number + 1
B76B 37                 SCF
B76C 08                 EX   AF,AF'        ;Signal "End of file block"
B76D DD6E0D             LD   L,(SF_LEN)
B770 DD660E             LD   H,(SF_LEN+1)
B773 DD7E0F             LD   A,(SF_LEN+2)  ;AHL= length of file (17 bit)
B776 A7                 AND  A
B777 ED42               SBC  HL,BC
B779 DE00               SBC  A,#00         ;AHL= length of remainder of file
B77B A7                 AND  A
B77C 2008               JR   NZ,RM_NO_EOF  ;Jump if high part of AHL is non-zero
B77E 110102             LD   DE,#0201
B781 ED52               SBC  HL,DE
B783 19                 ADD  HL,DE
B784 3804               JR   C,RM_EOF      ;Jump if AHL less than 0201h
B786 210002   RM_NO_EOF LD   HL,#0200      ;HL= length of record (0200h max)
B789 08                 EX   AF,AF'        ;Signal "Not end of file block"
B78A EB       RM_EOF    EX   DE,HL         ;DE= length of record
B78B DD6E0A             LD   L,(SF_START)
B78E DD660B             LD   H,(SF_START+1)
B791 DD7E0C             LD   A,(SF_START+2);AHL= coded addr of start of file
B794 CD3AB7             CALL ADD_HL_BC     ;AHL= coded addr of RAMdisc segment
B797 C5                 PUSH BC            ;Stack 200h * record number + 1
B798 D5                 PUSH DE            ;Stack length of record
B799 E5                 PUSH HL
B79A F5                 PUSH AF            ;Stack page-coded address of segment
B79B 42                 LD   B,D
B79C 4B                 LD   C,E           ;BC= length of record
B79D CD3AB7             CALL ADD_HL_BC     ;AHL= page-coded address of byte
                                           ;following RAMdisc segment
B7A0 47                 LD   B,A           ;BHL= this address
B7A1 F1                 POP  AF
B7A2 4F                 LD   C,A
B7A3 D1                 POP  DE            ;CDE= page-coded address of segment
B7A4 D9                 EXX                ;Use alternative registers
B7A5 D1                 POP  DE            ;DE'= length of record
B7A6 C1                 POP  BC            ;BC'= 200h * record number + 1
B7A7 DDE1               POP  IX            ;IX points to channel info area
B7A9 DD7319             LD   (R_RECLEN),E
B7AC DD721A             LD   (R_RECLEN+1),D;Store length of record
B7AF DDE5               PUSH IX
B7B1 E1                 POP  HL            ;HL' points to channel information
B7B2 011B00             LD   BC,#001B
B7B5 09                 ADD  HL,BC         ;HL' points to the R-channel buffer
B7B6 19                 ADD  HL,DE         ;HL' points to byte following
                                           ;current record
B7B7 0605               LD   B,#05         ;B'= 05, signalling "Standard RAM"
B7B9 D9                 EXX                ;Use normal registers
B7BA DDCB188E           RES  1,(R_CHFLAG)  ;Signal "Not end of file block"
B7BE 08                 EX   AF,AF'
B7BF D0                 RET  NC            ;Return unless end of file block
B7C0 DDCB18CE           SET  1,(R_CHFLAG)  ;Signal "End of file block"
B7C4 C9                 RET                ;Return

This subroutine actually assigns the R-channel buffer in preparation for use
with a READ channel. Note that it calls the RTRANSFER subroutine from label
RTRANSFE2 in order to deal with the zero case, when the buffer is to be
considered empty.

B7C5 CD5DB7   R_ASSIGN  CALL R_MATCH       ;Match buffer with RAMdisc segment
B7C8 CD30B7             CALL RTRANSFE2     ;Copy bytes into buffer
B7CB DD360B00 RBUFFEXIT LD   (R_CHBYTE),#00
B7CF DD360C00           LD   (R_CHBYTE+1),#00  ;Reset pointer into buffer
B7D3 C9                 RET                ;Return

There now follows the INPUT routine for channel R. It isolates INPUT from
INKEY$ and deals with each accordingly.

B7D4 2A3D5C   R_INPUT   LD   HL,(ERR_SP)   ;HL points to error return address
B7D7 5E                 LD   E,(HL)
B7D8 23                 INC  HL
B7D9 56                 LD   D,(HL)        ;DE= error return address
B7DA 217F10             LD   HL,ED_ERROR
B7DD A7                 AND  A
B7DE ED52               SBC  HL,DE
B7E0 2021               JR   NZ,R_INKEY    ;Jump if dealing with INKEY$
B7E2 ED7B3D5C           LD   SP,(ERR_SP)   ;Clear machine stack as far as
                                           ;return from EDITOR routine
B7E6 E1                 POP  HL
B7E7 E1                 POP  HL
B7E8 223D5C             LD   (ERR_SP),HL   ;Restore normal error return address
B7EB CD03B8   R_INPLOOP CALL R_INKEY       ;Input a single character into A
B7EE FE0D               CP   #0D
B7F0 C8                 RET  Z             ;Return if character is "enter"
B7F1 FDCB377E           BIT  7,(FLAG_X)
B7F5 2007               JR   NZ,R_INPUT_2  ;Jump if doing INPUT LINE
B7F7 FE22               CP   #22
B7F9 2003               JR   NZ,R_INPUT_2  ;Jump unless character is "quotes"
B7FB CD850F             CALL ADD_CHAR_1    ;Register quotes twice
B7FE CD850F   R_INPUT_2 CALL ADD_CHAR_1    ;Insert character into INPUT area
B801 18E8               JR   R_INPLOOP     ;Loop back to input rest of string

The following routine inputs a single character from an R-channel and returns
it in the A register.

B803 CD005B   R_INKEY   CALL SWAP          ;Page in ROM 0
B806 2A5A5B             LD   HL,(RETADDR)
B809 E5                 PUSH HL            ;Stack return address in ROM 0
B80A D9                 EXX
B80B C5                 PUSH BC
B80C D5                 PUSH DE
B80D E5                 PUSH HL            ;Stack alternative register set
B80E DD2A515C           LD   IX,(CURCHL)   ;IX points to channel information
B812 DDCB1846           BIT  0,(R_CHFLAG)
B816 2804               JR   Z,R_INKEY_2   ;Jump if this is a READ file
B818 CDFCB6   R_ERROR   CALL V_ERROR       ;Generate report code
B81B 1D                 DEFB #1D           ;"b Wrong file type"
B81C DD5E0B   R_INKEY_2 LD   E,(R_CHBYTE)
B81F DD560C             LD   D,(R_CHBYTE+1);DE= position of next byte to read
B822 DDCB184E           BIT  1,(R_CHFLAG)
B826 280F               JR   Z,RINKYREAD   ;Jump unless this is an EOF block
B828 DD6E19             LD   L,(R_RECLEN)
B82B DD661A             LD   H,(R_RECLEN+1);HL= length of current record
B82E A7                 AND  A
B82F ED52               SBC  HL,DE
B831 2004               JR   NZ,RINKYREAD  ;Jump unless we have reached the
                                           ;end of the (EOF) record
B833 CDFCB6             CALL V_ERROR       ;Generate error report
B836 07                 DEFB #07           ;"8 End of file"
B837 DDE5     RINKYREAD PUSH IX
B839 E1                 POP  HL            ;HL points to channel information
B83A 011B00             LD   BC,#001B
B83D 09                 ADD  HL,BC         ;HL points to buffer
B83E 19                 ADD  HL,DE         ;HL points to next byte to read
B83F 7E                 LD   A,(HL)        ;A= byte which INKEY$ must return
B840 F5                 PUSH AF
B841 13                 INC  DE            ;Increment pointer
B842 DD730B             LD   (R_CHBYTE),E
B845 DD720C             LD   (R_CHBYTE+1),D;Store incremented pointer
B848 15                 DEC  D
B849 15                 DEC  D
B84A 2006               JR   NZ,RINKYEXIT  ;Jump unless buffer to be renewed
B84C DD340D             INC  (R_CHREC)     ;Increment record number
B84F CDC5B7             CALL R_ASSIGN      ;Assign and reset buffer
B852 F1       RINKYEXIT POP  AF            ;A= byte just read from buffer
B853 37                 SCF                ;Set Carry, so that INKEY$ # works
                                           ;properly
B854 E1       RINOUEXIT POP  HL
B855 D1                 POP  DE
B856 C1                 POP  BC
B857 D9                 EXX                ;Restore alternative registers
B858 E1       R_EXIT    POP  HL            ;HL= return address into ROM 0
B859 225A5B             LD   (RETADDR),HL  ;Store in system variable
B85C C3005B             JP   SWAP          ;Page in ROM 1 and return

The next subroutine is designed to insert additional bytes into an already
existing file stored in RAMdisc. Any files which need to be moved in order to
make room for these extra bytes will be so moved, and re-indexed to accomodate.
The subroutine should be entered with AHL containing the page-coded address at
which to insert the bytes, and BC containing the number of bytes to insert.

B85F C5       RMAKEROOM PUSH BC            ;Stack number of bytes to insert
B860 E5                 PUSH HL
B861 F5                 PUSH AF            ;Stack page-coded address at
                                           ;which to insert
B862 AF                 XOR  A             ;A= 00, Carry flag reset
B863 67                 LD   H,A
B864 6F                 LD   L,A           ;AHL= zero
B865 ED42               SBC  HL,BC
B867 9F                 SBC  A,A           ;AHL= minus no. of bytes to insert
B868 CD05B7             CALL V_SPACE       ;Ensure enough room for extra bytes
B86B F1                 POP  AF
B86C E1                 POP  HL            ;AHL= address at which to insert
B86D C1                 POP  BC            ;BC= number of bytes to insert
B86E C5                 PUSH BC
B86F E5                 PUSH HL
B870 F5                 PUSH AF
B871 3E04               LD   A,#04
B873 CDFFB6             CALL V_PAGE        ;Select page containing catalogue
B876 DD2A835B           LD   IX,(SF_NEXT)  ;IX points to "End of cat" index
B87A DD6E0A             LD   L,(SF_START)
B87D DD660B             LD   H,(SF_START+1)
B880 DD7E0C             LD   A,(SF_START+2);AHL= page-coded address of first
                                           ;spare byte in RAMdisc area
B883 F5                 PUSH AF
B884 E5                 PUSH HL            ;Stack this address
B885 CD3AB7             CALL ADD_HL_BC     ;AHL= page-coded address of first
                                           ;RAMdisc byte which will remain spare
                                           ;after more bytes are inserted
B888 47                 LD   B,A           ;BHL= this address
B889 D9                 EXX                ;BHL'= this address
B88A E1                 POP  HL
B88B C1                 POP  BC            ;BHL points to 1st spare byte (old)
B88C F1                 POP  AF
B88D D1                 POP  DE            ;ADE= address at which to insert
B88E 4F                 LD   C,A           ;CDE= address at which to insert
B88F D5                 PUSH DE
B890 F5                 PUSH AF            ;Stack this address
B891 CD30B7             CALL RTRANSFE2     ;Move bytes which need to be moved
B894 3E04               LD   A,#04
B896 CDFFB6             CALL V_PAGE        ;Select page containing catalogue
B899 C1                 POP  BC
B89A D1                 POP  DE            ;BDE= position of insertion
B89B DD6E0A   R_MR_LOOP LD   L,(SF_START)  ;AHL= previous page-coded address of
B89E DD660B             LD   H,(SF_START+1);a RAMdisc file (or next spare byte)
B8A1 DD7E0C             LD   A,(SF_START+2);which may have been moved
B8A4 B8                 CP   B
B8A5 382E               JR   C,R_MRFOUND   ;Jump if file address precedes
                                           ;point of insertion
B8A7 ED52               SBC  HL,DE
B8A9 19                 ADD  HL,DE
B8AA 3829               JR   C,R_MRFOUND   ;Jump if file address precedes
                                           ;point of insertion
B8AC EB                 EX   DE,HL
B8AD E3                 EX   (SP),HL
B8AE EB                 EX   DE,HL         ;DE= number of bytes inserted
B8AF 19                 ADD  HL,DE
B8B0 3005               JR   NC,R_MR_ADDR
B8B2 CBFC               SET  7,H
B8B4 CBF4               SET  6,H
B8B6 3C                 INC  A             ;AHL= new address of file
B8B7 EB       R_MR_ADDR EX   DE,HL
B8B8 E3                 EX   (SP),HL
B8B9 EB                 EX   DE,HL         ;BDE= position of insertion
B8BA DD750A             LD   (SF_START),L
B8BD DD740B             LD   (SF_START+1),H
B8C0 DD770C             LD   (SF_START+2),A;Store new start address of file
B8C3 C5                 PUSH BC
B8C4 011400             LD   BC,#0014
B8C7 DD09               ADD  IX,BC         ;IX points to index for next file
B8C9 C1                 POP  BC            ;BDE= position of insertion
B8CA DD7510             LD   (SF_END),L
B8CD DD7411             LD   (SF_END+1),H
B8D0 DD7712             LD   (SF_END+2),A  ;Store new address for next file
B8D3 18C6               JR   R_MR_LOOP     ;Loop back to deal with this file
B8D5 DD6E0D   R_MRFOUND LD   L,(SF_LEN)
B8D8 DD660E             LD   H,(SF_LEN+1)
B8DB DD7E0F             LD   A,(SF_LEN+2)  ;AHL= previous length of file
B8DE EB                 EX   DE,HL
B8DF E3                 EX   (SP),HL
B8E0 EB                 EX   DE,HL         ;DE= number of bytes inserted
B8E1 19                 ADD  HL,DE
B8E2 CE00               ADC  A,#00         ;AHL= new length of file
B8E4 DD750D             LD   (SF_LEN),L
B8E7 DD740E             LD   (SF_LEN+1),H
B8EA DD770F             LD   (SF_LEN+2),A  ;Store new length of file
B8ED 78                 LD   A,B           ;A= page-code of point of insertion
B8EE 42                 LD   B,D
B8EF 4B                 LD   C,E           ;BC= number of bytes inserted
B8F0 E1                 POP  HL            ;AHL= page-coded address of point
                                           ;at which bytes were inserted
B8F1 C9                 RET                ;Return

The following subroutine will transfer the contents of the R-channel buffer
into the corresponding RAMdisc file.

B8F2 DD4E0B   R_STORE   LD   C,(R_CHBYTE)
B8F5 DD460C             LD   B,(R_CHBYTE+1);BC= number of bytes in buffer
B8F8 DDE5               PUSH IX            ;Stack address of R-channel info
B8FA C5                 PUSH BC            ;Stack number of bytes in buffer
B8FB CD47B7             CALL FIND_FILE     ;IX points to file entry in cat.
B8FE C1                 POP  BC            ;BC= number of bytes in buffer
B8FF DD6E10             LD   L,(SF_END)
B902 DD6611             LD   H,(SF_END+1)
B905 DD7E12             LD   A,(SF_END+2)  ;AHL= page-coded address of first
                                           ;byte beyond end of file
B908 CD5FB8             CALL RMAKEROOM     ;Insert enough room for contents
                                           ;of buffer
B90B DDE1               POP  IX            ;IX points to channel information
B90D CD3AB7             CALL ADD_HL_BC     ;AHL points one byte beyond the
                                           ;last of the new bytes
B910 C5                 PUSH BC            ;Stack number of bytes in buffer
B911 47                 LD   B,A           ;BHL= address of last new byte + 1
B912 D9                 EXX                ;BHL'= address of last new byte + 1
B913 DDE5               PUSH IX
B915 E1                 POP  HL            ;HL= address of channel information
B916 011B00             LD   BC,#001B
B919 09                 ADD  HL,BC         ;HL points to start of buffer
B91A C1                 POP  BC            ;BC= number of bytes in buffer
B91B E5                 PUSH HL            ;Stack address of start of buffer
B91C 09                 ADD  HL,BC         ;HL points to byte beyond buffer
B91D D1                 POP  DE            ;DE points to start of buffer
B91E 010505             LD   BC,#0505      ;BHL and CDE now page-coded addresses
B921 CD30B7             CALL RTRANSFE2     ;Copy buffer into RAMdisc area
B924 78                 LD   A,B           ;A= 05
B925 CDFFB6             CALL V_PAGE        ;Page in normal RAM
B928 C3CBB7             JP   RBUFFEXIT     ;Reset pointer into buffer and return

Now comes the output routine, whose job it is to print the character stored in
the A register to an R channel (ie. to store it firstly in the buffer, and
ultimately in a RAMdisc file).

B92B CD005B   R_PRINT   CALL SWAP          ;Page in ROM 0
B92E 2A5A5B             LD   HL,(RETADDR)
B931 E5                 PUSH HL            ;Stack return address into ROM 0
B932 D9                 EXX
B933 C5                 PUSH BC
B934 D5                 PUSH DE
B935 E5                 PUSH HL            ;Stack alternative register set
B936 DD2A515C           LD   IX,(CURCHL)   ;IX points to channel information
B93A DDCB1846           BIT  0,(R_CHFLAG)
B93E CA18B8             JP   Z,R_ERROR     ;Error if this is a READ file
B941 DD5E0B             LD   E,(R_CHBYTE)
B944 DD560C             LD   D,(R_CHBYTE+1);DE= number of bytes in buffer
B947 DDE5               PUSH IX
B949 E1                 POP  HL            ;HL points to channel information
B94A 011B00             LD   BC,#001B
B94D 09                 ADD  HL,BC         ;HL points to start of buffer
B94E 19                 ADD  HL,DE         ;HL points to next spare byte
B94F 77                 LD   (HL),A        ;Store byte in buffer
B950 13                 INC  DE            ;DE= new number of bytes in buffer
B951 DD730B             LD   (R_CHBYTE),E
B954 DD720C             LD   (R_CHBYTE+1),D;Store new number of chars in buffer
B957 15                 DEC  D
B958 15                 DEC  D
B959 CCF2B8             CALL Z,R_STORE     ;If buffer is now full, then empty
                                           ;contents into RAMdisc file
B95C A7                 AND  A             ;Reset Carry
B95D C354B8             JP   RINOUEXIIT    ;Jump to exit routine

Next we have the routine to CLOSE an R channel. All that is necessary is that
the buffer contents be ignored (READ file) or stored in RAMdisc (WRITE file).

B960 CD005B   R_CLOSE   CALL SWAP          ;Page in ROM 0
B963 2A5A5B             LD   HL,(RETADDR)
B966 E5                 PUSH HL            ;Stack return address into ROM 0
B967 DDE5               PUSH IX            ;Stack pointer to channel info
B969 2A3D5C             LD   HL,(ERR_SP)   ;HL points to error return address
B96C E5                 PUSH HL            ;Stack this pointer
B96D 21FEFF             LD   HL,#FFFE
B970 39                 ADD  HL,SP         ;HL= SP minus two
B971 223D5C             LD   (ERR_SP),HL   ;Set new error return address
B974 DDCB1846           BIT  0,(R_CHFLAG)
B978 C4F2B8             CALL NZ,R_STORE    ;If this is a WRITE file, then empty
                                           ;buffer contents into RAMdisc
B97B E1                 POP  HL            ;NOTE: This is also the return point
                                           ;from any errors that may have
                                           ;occurred during R_STORE
B97C 223D5C             LD   (ERR_SP),HL   ;Restore error pointer to normal
B97F DDE1               POP  IX            ;Restore channel info pointer
B981 215827   R_OC_EXIT LD   HL,#2758
B984 D9                 EXX                ;HL'= 2758 to prevent crash
B985 C358B8             JP   R_EXIT        ;Jump to exit routine

At last we have the routine to OPEN an R channel. On entry the A register must
contain the stream number to which the channel is to be attached, and the 10-
byte system variable N_STR1 must contain the filename of the READ or WRITE file
to be opened. If the filename is less than 10 characters long then it should be
followed by trailing spaces.

B988 CD005B   R_OPEN    CALL SWAP          ;Page in ROM 0
B98B 2A5A5B             LD   HL,(RETTADDR)
B98E E5                 PUSH HL            ;Stack return address into ROM 0
B98F F5                 PUSH AF            ;Stack stream number
B990 3E52               LD   A,"R"         ;A= name of this channel ("R")
B992 CD94B5             CALL SEARCHALL     ;Search for an existing R channel
B995 381F               JR   C,R_OP_OK     ;Jump if none found
B997 DDE5     R_OP_LOOP PUSH IX
B999 E1                 POP  HL            ;HL points to channel information
                                           ;for already existing R channel
B99A 010E00             LD   BC,#000E
B99D 09                 ADD  HL,BC         ;HL points to filename for
                                           ;already existing R channel
B99E 11675B             LD   DE,N_STR1     ;DE points to intended filename
                                           ;for this channel
B9A1 060A               LD   B,#0A         ;B= length of filename
B9A3 1A       R_OP_NAME LD   A,(DE)
B9A4 13                 INC  DE
B9A5 BE                 CP   (HL)
B9A6 23                 INC  HL
B9A7 2006               JR   NZ,#R_OPRETRY ;Jump if filenames are different
B9A9 10F8               DJNZ R_OP_NAME     ;Test all 10 characters of filename.
                                           ;If filenames are identical then
B9AB CDFCB6   R_OPERROR CALL V_ERROR       ;generate error report
B9AE 20                 DEFB #20           ;"e File already exists"
B9AF 1652     R_OPRETRY LD   D,"R"         ;D= name of this channel ("R")
B9B1 CDAAB5             CALL SEARCHNXT     ;Search for next existing R channel
B9B4 30E1               JR   NC,R_OP_LOOP  ;Loop back if one found
B9B6 CD08B7   R_OP_OK   CALL V_FIND        ;Search for RAMdisc file with
                                           ;given name
B9B9 F5                 PUSH AF            ;Stack the Zero flag
B9BA 2811               JR   Z,R_OP_CONT   ;Jump if no file found (ie. if this
                                           ;is to be a WRITE file)
B9BC DD6E0A             LD   L,(SF_START)
B9BF DD660B             LD   H,(SF_START+1)
;[The next line was incorrectly printed as DD7E0B in the magazine.  JimG]
B9C2 DD7E0C             LD   A,(SF_START+2);AHL= page-coded address of file
                                           ;with given name
B9C5 CDFFB6             CALL V_PAGE        ;Select page containing first byte
                                           ;of this file
B9C8 7E                 LD   A,(HL)        ;A= type-of-file code
B9C9 FE04               CP   #04
B9CB 20DE               JR   NZ,R_OPERROR  ;Give error unless a READ file
B9CD 3E05     R_OP_CONT LD   A,#05
B9CF CDFFB6             CALL V_PAGE        ;Page in normal RAM
B9D2 F1                 POP  AF            ;Retrieve Zero flag
B9D3 08                 EX   AF,AF'        ;Store in A'
B9D4 F1                 POP  AF            ;A= stream number
B9D5 08                 EX   AF,AF'        ;A'= stream number
B9D6 F5                 PUSH AF            ;Stack the Zero flag, which determines
                                           ;whether this is a READ or WRITE file
B9D7 3E52               LD   A,"R"         ;A= name of this channel ("R")
B9D9 011B02             LD   BC,#021B      ;BC= length of channel info block
B9DC 11D4B7             LD   DE,R_INPUT    ;DE= address of input routine
B9DF 212BB9             LD   HL,R_PRINT    ;HL= address of output routine
B9E2 DD2160B9           LD   IX,R_CLOSE    ;IX= address of close routine
B9E6 EF                 RST  #28
B9E7 6DB0               DEFW #B06D,OPEN_NEW;Create channel information block
B9E9 DDE5               PUSH IX
B9EB E1                 POP  HL            ;HL points to channel information
B9EC 010B00             LD   BC,#000B
B9EF 09                 ADD  HL,BC         ;HL points to variable R_CHBYTE
B9F0 70                 LD   (HL),B
B9F1 23                 INC  HL
B9F2 70                 LD   (HL),B        ;Reset R_CHBYTE
B9F3 23                 INC  HL
B9F4 70                 LD   (HL),B        ;Reset R_CHREC
B9F5 23                 INC  HL
B9F6 EB                 EX   DE,HL         ;DE points to R_CHNAME
B9F7 21675B             LD   HL,N_STR1     ;HL points to filename given
B9FA 0E0A               LD   C,#0A         ;DE= length of filename (ten)
B9FC EDB0               LDIR               ;Copy filename into channel info
B9FE F1                 POP  AF            ;Retrieve Zero flag
B9FF 2809               JR   Z,R_OPWRITE   ;Jump if this is to be a WRITE file
BA01 DDCB1886           RES  0,(R_CHFLAG)  ;Signal "This is a READ file"
BA05 CDC5B7             CALL R_ASSIGN      ;Assign buffer from RAMdisc file
BA08 1840               JR   R_OP_EXIT     ;Jump to exit routine
BA0A DDCB18C6 R_OPWRITE SET  0,(R_CHFLAG)  ;Signal "This is a WRITE file"
BA0E CD02B7             CALL V_NEWCAT      ;Create new catalogue entry
BA11 21FFFF             LD   HL,#FFFF
BA14 7C                 LD   A,H           ;AHL= minus one
BA15 CD05B7             CALL V_SPACE       ;Ensure enough room for one byte
BA18 3E04               LD   A,#04
BA1A CDFFB6             CALL V_PAGE        ;Select page containing catalogue
BA1D DD6E0A             LD   L,(SF_START)
BA20 DD660B             LD   H,(SF_START+1)
BA23 DD7E0C             LD   A,(SF_START+2);AHL= page-coded address of first
                                           ;spare byte in RAMdisc
BA26 F5                 PUSH AF            ;Stack page-code
BA27 CDFFB6             CALL V_PAGE        ;Select page containing first
                                           ;spare byte in RAMdisc
BA2A F1                 POP  AF            ;AHL points to first spare byte
BA2B 3604               LD   (HL),#04      ;Store 04 as type-of-file code
BA2D 010100             LD   BC,#0001
BA30 CD3AB7             CALL ADD_HL_BC     ;AHL= page-coded address of new
                                           ;first spare byte in RAMdisc
BA33 5F                 LD   E,A           ;EHL= this address
BA34 3E04               LD   A,#04
BA36 CDFFB6             CALL V_PAGE        ;Select page containing catalogue
BA39 DD7510             LD   (SF_END),L
BA3C DD7411             LD   (SF_END+1),H
BA3F DD7312             LD   (SF_END+2),E  ;Store address of end of file
BA42 CD0BB7             CALL V_CATEND      ;Tidy up catalogue entry
BA45 3E05               LD   A,#05
BA47 CDFFB6             CALL V_PAGE        ;Select normal RAM
BA4A C381B9   R_OP_EXIT JP   R_OC_EXIT     ;Jump to exit routine

And finally, we have the routines which integrate the R channel with BASIC.
These are only example routines, and you may of course rewrite them to your own
specifications. OPEN_4 will open stream four to a serial file called FILE1;
OPEN_5 will open stream five to a serial file called FILE2; CLOSE_4 will close
stream four; and CLOSE_5 will close stream five.

BA4D 46494C4531         DEFM FILE1
     2020202020         DEFM five spaces   ;Name of first file
     46494C4532         DEFM FILE2
     2020202020         DEFM five spaces   ;Name of second file
BA61 3E04     OPEN_4    LD   A,#04         ;A= stream number
BA63 214DBA             LD   HL,FILE_1     ;HL points to filename
BA66 1805               JR   OPEN_4_5      ;Jump to open stream
BA68 3E05     OPEN_5    LD   A,#05         ;A= stream number
BA6A 2157BA             LD   HL,FILE_2     ;HL points to filename
BA6D 11675B   OPEN_4_5  LD   DE,N_STR1     ;DE points to system variable
BA70 010A00             LD   BC,#000A      ;BC= length of filename (ten)
BA73 EDB0               LDIR               ;Copy filename into system variable
BA75 C388B9             JP   R_OPEN
BA78 3E04     CLOSE_4   LD   A,#04         ;A= stream number
BA7A 1802               JR   CLOSE_4_5
BA7C 3E05     CLOSE_5   LD   A,#05         ;A= stream number
BA7E C300B0   CLOSE_4_5 JP   CLOSE_NEW     ;Jump to close channel
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
