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

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

If you thought that you couldn't use the ZX Printer
with your new 128 then Toni Baker can prove you wrong.

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


This article contains not one, but two new channels which you can use
on your Spectrum. The first is a channel which enables users of the
Spectrum 128 (or the 128+2) to be able to use the ZX Printer - or some
other compatible printer such as an Alphacom - even in 128K mode. If
you don't have a Spectrum 128 then you don't need this new channel of
course, because you can use the ZX Printer anyway.

The second is not really a new channel at all, but a modification to
an existing channel. It enables the Spectrum to be able to communicate
freely with a QL via the Local Area Network available from the ZX
Interface One. Of course communication between these computers via the
network is already possible, and David Nowotnik has recently been
doing an excellent series in ZX Computing on that very task. It is not
my intention to duplicate any of his material, but merely to remove a
couple of deficiencies in the channel itself. We'll return to this
later, meanwhile back to the ZX Printer.


Channel Z

The program as such begins at address B544. If this seems a rather
arbitrary address to you then I should explain that the reason it
starts at B544 is that all the code follows on from the stuff in last
month's article (which ran from B000 to B543). Indeed, some of the
subroutines developed last month will be used in the routines given here.

The trick to using the ZX Printer on the 128 is to avoid any
possibility of erasing or corrupting the OLD printer buffer (addresses
5B00 to 5BFF) - this is because the memory in this range is used by
the 128 to store various paging subroutines, system variables, and an
alternative machine stack for use when accessing the vast banks of
paged memory. If we can arrange things so that we can avoid corrupting
this memory then the good old ZX Printer can still work normally. You
see, all of the ZX Printer software in the ROM was written before the
advent of the 128 and is not compatible with the requirements of the
new machine.

All we really need to do then is to create a NEW printer buffer
somewhere in memory, and use the new buffer instead of the old one.
The best possible place for this buffer would be as part of the
channel information block for the channel - that would mean that the
area occupied by the new printer buffer would be reclaimed whenever
you closed the channel.

We shall call this new channel "Z", for ZX-Printer. Take a look at
Figure One - it shows the structure of the channel information block
for the new channel. As you can see, the first eleven bytes store
completely standard information, using the standard which was
developed throughout this series. (IX+05/06) contains the constant
1234h, which identifies this as being a user-defined channel. This
means that it may be opened or closed using some of the software
listed last month (IX+0B), (IX+0C) and (IX+0D) also store information
in the same format as the "W" channel given last month - this is so
that we can exploit more of last month's subroutines. As you shall
see, a collection of new channels is far more advantageous than a
single new channel.

Finally, the new printer buffer itself runs from (IX+0E) upwards, and
is 100h bytes in length (same as the old printer buffer). The variable
Z_XCOORD (IX+0C) will be zero if the printer buffer is completely
empty, or non-zero otherwise. If the new buffer is full it will
contain 20h. Z_WIDTH (IX+0D) remains constant at 20h - the width of
the buffer. This is so that the TAB and comma-control routines from
last month may be exploited successfully.

The program begins at the label Z_CLOSE, which is the routine to
perform all of the peripheral tasks necessary to close the channel. In
fact this merely consists of testing whether or not the buffer is
empty, and printing a newline if it isn't.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Figure 1

IX+00  (2 bytes)     Z_PRINT   Address of channel "Z" output
IX+02  (2 bytes)     REPORT_J  Address of channel "Z" input
IX+04  (1 byte)      Z_NAME    Name of channel (ie. "Z")
IX+05  (2 bytes)     Z_IDEN    Constant 1234h identifies new channel
IX+07  (2 bytes)     Z_CLOSE   Address of close buffer routine
IX+09  (2 bytes)     Z_LEN     Length of channel info (ie. 010E)
IX+0B  (1 byte)      Z_FLAGS   Various flags
        Bit 7: Set if leading space not reqd. for keywords, reset otherwise
        Bit 6: Not used
        Bit 5: Not used
        Bit 4: Not used
        Bit 3: Set if INVERSE status is ON, reset otherwise
        Bit 2: Set if OVER status is ON, reset otherwise
        Bit 1: Set if exactly two additional parameters reqd., reset otherwise
        Bit 0: Set if exactly one additional parameter reqd., reset otherwise
IX+0C  (1 byte)      Z_XCOORD  X coordinate of print position in buffer
IX+0D  (1 byte)      Z_WIDTH   Width of buffer (ie. 20h)
IX+0E  (100h bytes)  Z_BUFFER  New printer buffer
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -


Newline

The Z_NEWLINE routine may be compared to the ROM routine COPY_BUFF at
address 0ECD. Its purpose is to transfer the contents of the buffer to
the ZX Printer itself, before finally erasing the previous buffer
contents. The routine Z_EMPTY does the actual erasing. You will notice
a couple of differences between the ROM routine COPY_BUFF and my
routine Z_NEWLINE. Firstly, the new printer buffer is used instead of
the old one, and secondly the subroutine Z_COPY_LINE is called instead
of the original COPY_LINE.

In fact Z_COPY_LINE is itself very similar to the ROM's COPY_LINE
routine (at 0EFA). The only difference in evidence is the effect of
pressing the BREAK key - in my routine Z_EMPTY is called instead of
CLEAR_PRB. This removes any possible chance of corrupting the old
printer buffer area.

A general purpose subroutine is included next - SEARCH_CH_ALL. It is
entered with the A register containing the name of a channel. The
routine will search through the channel information area looking for a
channel with this name. If it finds one it will return with IX
pointing to the channel information block and the carry reset,
otherwise the carry will be set. We shall make use of this subroutine
later.


Z_PRINT

Z_PRINT is the routine which "prints" a character held in the A
register onto the new printer buffer. Note that the subroutine at
address B12F comes from last month's article and will sort out all
keywords, control parameters, and will deal with both the
comma-control and the TAB function. On return from this subroutine the
C register will contain any INVERSE or OVER parameters, or the x
coordinate of any AT parameters. The Z_PRINT routine itself is really
quite simple, bearing in mind that it has to deal with ASCII
characters, block graphics, and UDGs, as well as OVER, INVERSE, AT and
ENTER. Follow it through to see how all the various cases are dealt with.

Finally (for this channel) we have Z_OPEN, which opens the channel and
attaches it to a stream. On entry, the A register must contain the
stream number to which the channel is to be attached and the
subroutine will do the rest. It makes use of another subroutine from
last article - the OPEN_NEW routine which will open user defined channels.


Channel Q

When communicating with the QL via the normal network channel you will
notice a couple of problems. The first is that on the Spectrum the
code for "enter" is 0D (thirteen), whereas on the QL the code for
"enter" is 0A (ten). This means that, for instance, PRINT A$ on the QL
cannot be matched by INPUT A$ on the Spectrum, nor vice versa. It is
normally necessary to use PRINT A$;CHR$(13); on the QL in order that
the Spectrum may use INPUT A$. Conversely, INPUT A$ on the QL needs to
be matched by PRINT A$;CHR$ 10; on the Spectrum.

What I intend to do is to modify the "N" channel attached to a
particular stream so that the deficiencies are removed. In a modified
"N" channel these problems disappear. PRINT A$ on the Spectrum may be
matched by INPUT A$ on the QL, and vice versa. Other surprising
advantages turn up; if you LIST a Spectrum program over a modified
network channel then the QL will be able to LOAD the program with the
single command LOAD NET1_2 (assuming that the Spectrum is station
number 2) - or to transfer the program directly onto a QL microdrive
ready for loading later with the single QL command COPY NET1_2 TO
MDV1_PROG.

The second problem is a little more complex, but not much so. The
Spectrum command INPUT A$ actually expects to receive a string
expression, not the contents of an actual string (although this is not
the case with INPUT LINE). Fortunately for us, we seldom notice this
because the Spectrum provides the surrounding quotes - this converts
the string text into a string expression and everything is hunky dory
- with one exception. If the string printed over the network by the QL
contains a quotes character then the Spectrum will be unable to
evaluate the result as a string expression and will halt with error "C
Nonsense in BASIC". The cure to this problem is very simple. If,
during Spectrum INPUT (as opposed to INKEY$ or INPUT LINE) a quotes
character is received from a modified network channel THEN IT MUST BE
CONVERTED TO TWO CONSECUTIVE QUOTES CHARACTERS in order that the
expression will still evaluate.

Let's take a look at the subroutines now. We begin with SHADOW_DE
which will call a subroutine in the Shadow ROM whose address is DE. It
achieves this by fooling the Spectrum into thinking that we are
returning to the Shadow ROM from a Spectrum subroutine, and then
simply paging out the Shadow ROM on completion.

Q_PRINT is the routine which "prints" a character over the modified
network. As you can see it is extremely simple, merely testing for an
"enter" character and converting such to 0A, before using the original
Shadow-PRINT subroutine to print the character.

Q_INPUT is more complicated, though not much so. Firstly, the routine
tests the error return address to see whether the character input is
due to an INPUT command, or due to an INKEY$ function. If it turns out
to be merely INKEY$ then things are just as simple as for Q_PRINT. The
shadow input routine is called to read a character, and if that
character is 0A then it is converted to "enter".

If, however, we are dealing with INPUT (as opposed to INKEY$) then the
first thing we must do is abandon altogether use of the EDITOR routine
in the ROM (which we do by emptying the machine stack as far as the
return address from the EDITOR call) and then supplying a new routine
to replace EDITOR. We can call Q_INKEY$ as a sub- routine to fetch a
single character from the network (converting 0A to 0D) and the flags
will tell us if the character is valid or not. Then we can call a ROM
subroutine ADD_CHAR_1 to actually insert the character into the input
line. Quote characters must be inserted twice, not once (except during
INPUT LINE). Finally, if an "enter" character is received then we may
return, as if from the EDITOR routine, with the finished string
expression in the INPUT area of RAM.

MODIFY_N is the routine which will convert a "N" channel to do as it's
told. On entry the A register must contain the stream number of an
existing "N" channel which must have been opened normally. In such a
channel (IX+00/01) and (IX+02/03) will normally contain the value 0008
which, if called, will page in the Shadow ROM and then call the actual
output or input subroutine addressed by (IX+05/06) and (IX+07/08)
respectively.

The MODIFY_N subroutine will merely convert these 0008s to the
addresses of Q_PRINT and Q_INPUT, leaving the addresses of the Shadow
routines unchanged. The Shadow routines will then only be called when
required by Q_PRINT or by Q_INPUT.

Finally, a five byte demonstration of the "Z" channel. Simply calling
Z_DEMO from BASIC will open a new channel "Z", and attach it to stream
number four.


Channel R

In the next and final part of this series we shall examine the use of
the existing channel "R", which may only ever be used in machine code.
We shall also be creating a new channel, also called "R" (R for
RAM-disc) which will enable you to create READ and WRITE files in the
Spectrum 128's RAM-disc area, just as you can on the microdrives. Till
then, keep smiling, and don't take anything seriously.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
B544 DD7E0C   Z_CLOSE   LD   A,(Z_XCOORD)  ;A= x coordinate within buffer
B547 A7                 AND  A
B548 C8                 RET  Z             ;Return immediately if buffer empty

B549 DDE5     Z_NEWLINE PUSH IX
B54B E1                 POP  HL            ;HL= address of channel info
B54C 010E00             LD   BC,#000E
B54F 09                 ADD  HL,BC         ;HL points to new printer buffer
B550 F3                 DI                 ;Disable interrupts
B551 0608               LD   B,#08
B553 C5       Z_NL_LOOP PUSH BC
B554 CD73B5             CALL ZCOPYLINE     ;Output next row to ZX Printer
B557 C1                 POP  BC
B558 10F9               DJNZ Z_NL_LOOP     ;Output all eight rows
B55A 3E04               LD   A,#04
B55C D3FB               OUT  (#FB),A       ;Switch off printer motor
B55E FB                 EI                 ;Enable interrupts

B55F DDE5     Z_EMPTY   PUSH IX
B561 E1                 POP  HL            ;HL= address of channel info
B562 010E00             LD   BC,#000E
B565 09                 ADD  HL,BC         ;HL points to new printer buffer
B566 AF                 XOR  A             ;A= 00
B567 77       ZEMPTLOOP LD   (HL),A        ;Zero next byte of buffer
B568 23                 INC  HL            ;HL points to next byte in buffer
B569 10FC               DJNZ ZEMPTLOOP     ;Zero entire buffer
B56B DDCB0BFE           SET  7,(Z_FLAGS)   ;Signal "Leading space not required"
B56F DD770C             LD   (Z_XCOORD),A  ;Reset x coordinate in buffer
B572 C9                 RET                ;Return

B573 78       ZCOPYLINE LD   A,B           ;A= row number to output
B574 FE03               CP   #03
B576 9F                 SBC  A,A           ;A= FF (last 2 rows); 00 (otherwise)
B577 E602               AND  #02           ;A= 02 (last 2 rows); 00 (otherwise)
B579 D3FB               OUT  (#FB),A       ;Switch on printer motor, but with
                                           ;slow speed for last two rows
B57B 57                 LD   D,A           ;D= "last 2 rows" flag
B57C CD541F   Z_CL_LOOP CALL BREAK_KEY     ;Test BREAK key
B57F 380A               JR   C,Z_CL_NEXT   ;Jump forward unless BREAK pressed
B581 3E04               LD   A,#04
B583 D3FB               OUT  (#FB),A       ;Switch off printer motor
B585 FB                 EI                 ;Enable interrupts
B586 CD5FB5             CALL Z_EMPTY       ;Empty the new printer buffer
B589 CF                 RST  #08           ;Report "D, BREAK - CONT repeats"
B58A 0C                 DEFB #0C
B58B DBFE     Z_CL_NEXT IN   A,(#FE)       ;A= status of printer
B58D 87                 ADD  A,A
B58E F8                 RET  M             ;Return if printer not connected
B58F 30EB               JR   NC,Z_CL_LOOP  ;Wait until printer stylus is ready
B591 C3120F             JP   COPY_L_2A     Jump to print next row from buffer

B594 DD2A4F5C SEARCHALL LD   IX,(CHANS)    ;IX points to base of chan info area
B598 011400             LD   BC,#0014
B59B 57                 LD   D,A           ;D= name of channel to search for
B59C DD09     SCHCHLOOP ADD  IX,BC         ;IX points to next chan info area
B59E DD7E00   SEARCH_CH LD   A,(IX+#00)
B5A1 FE80               CP   #80
B5A3 37                 SCF
B5A4 C8                 RET  Z             ;Return with Carry set if channel
                                           ;not found
B5A5 DD7E04             LD   A,(IX+#04)    ;A= name of channel pointed to
B5A8 BA                 CP   D
B5A9 C8                 RET  Z             ;Return with IX pointing to channel
                                           ;information area, and Carry reset,
                                           ;if search successful
B5AA DD4E09   SEARCHNXT LD   C,(IX+#09)
B5AD DD460A             LD   B,(IX+#0A)    ;BC= length of channel info
B5B0 18EA               JR   SCHCHLOOP     ;Loop back to continue search

B5B2 DD2A515C Z_PRINT   LD   IX,(CURCHL)   ;IX points to channel info area
B5B6 CD2FB1             CALL CHR_TYPE2     ;Deal with keywords, control params,
                                           ;TAB and comma control
B5B9 F8                 RET  M             ;Return if tasks complete
B5BA F5                 PUSH AF
B5BB 283B               JR   Z,ZGRAPHICS   ;Jump with graphics characters
B5BD 3033               JR   NC,Z_ASCII    ;Jump with ASCII characters
B5BF FE0D               CP   #0D
B5C1 2005               JR   NZ,Z_PRCTRLS  ;Jump unless char is "enter"
B5C3 CD49B5             CALL Z_NEWLINE     ;Print a newline
B5C6 187F               JR   Z_EXIT        ;and jump to exit
B5C8 D614     Z_PRCTRLS SUB  #14
B5CA 387B               JR   C,Z_EXIT      ;Jump with controls 00 to 13
B5CC 0608               LD   B,#08         ;B has bit 3 set
B5CE 2805               JR   Z,Z_INVOVER   ;Jump with "INVERSE control"
B5D0 3D                 DEC  A
B5D1 0604               LD   B,#04         ;B has bit 2 set
B5D3 200F               JR   NZ,Z_AT       ;Jump unless char is "OVER control"
B5D5 CB19     Z_INVOVER RR   C
B5D7 9F                 SBC  A,A           ;A= 00 (INVERSE 0 or OVER 0)
                                           ;or FF (INVERSE 1 or OVER 1)
B5D8 DDAE0B             XOR  (Z_FLAGS)     ;A= flags byte, but complemented if
                                           ;parameter is one
B5DB A0                 AND  B             ;A all bits reset, except that bit
                                           ;3 (INVERSE) or bit 2 (OVER) will be
                                           ;taken from flags byte, and
                                           ;complemented if parameter is one
B5DC DDAE0B             XOR  (Z_FLAGS)
B5DF DD770B             LD   (Z_FLAGS),A   ;Bit 3 (INVERSE) or bit 2 (OVER) of
                                           ;flags byte will be assigned with
                                           ;parameter
B5E2 1863               JR   Z_EXIT        ;Jump to exit
B5E4 3D       Z_AT      DEC  A
B5E5 2060               JR   NZ,Z_EXIT     ;Jump unless control is "AT control"
B5E7 79                 LD   A,C           ;A= required x coordinate
B5E8 FE20               CP   #20
B5EA D29F1E             JP   NC,REPORT_B   ;Error if out of range
B5ED DD770C             LD   (Z_XCOORD),A  ;Assign x coordinate as required
B5F0 1855               JR   Z_EXIT        ;Jump to exit
B5F2 ED5B365C Z_ASCII   LD   DE,(CHARS)    ;DE= addr of character set - 100h
B5F6 1811               JR   Z_CHR_1       ;Jump forward
B5F8 D690     ZGRAPHICS SUB  #90
B5FA 3009               JR   NC,Z_UDG      ;Jump with user-defined graphics
B5FC 47                 LD   B,A
B5FD CD380B             CALL PO_GR_1       ;Construct graphic in MEMBOT area
B600 21925C             LD   HL,MEMBOT     ;HL points to graphic form
B603 180B               JR   Z_CHR_2       ;Jump forward
B605 ED5B7B5C Z_UDG     LD   DE,(UDG)      ;DE= address of user-defined graphics
B609 6F       Z_CHR_1   LD   L,A
B60A 2600               LD   H,#00
B60C 29                 ADD  HL,HL
B60D 29                 ADD  HL,HL
B60E 29                 ADD  HL,HL
B60F 19                 ADD  HL,DE         ;HL points to required graphic form
B610 DD7E0C   Z_CHR_2   LD   A,(Z_XCOORD)  ;A= current x coordinate in buffer
B613 FE20               CP   #20
B615 E5                 PUSH HL
B616 CC49B5             CALL Z,Z_NEWLINE   ;Print a newline if buffer full
B619 D1                 POP  DE            ;DE points to graphic form
B61A DD7E0C             LD   A,(Z_XCOORD)  ;A= current x coordinate in buffer
B61D 3C                 INC  A             ;A= new x coordinate
B61E DD770C             LD   (Z_XCOORD),A  ;Store in system variable
B621 C60D               ADD  A,#0D
B623 4F                 LD   C,A
B624 0600               LD   B,#00         ;BC= displacement to posn in buffer
B626 DDE5               PUSH IX
B628 E1                 POP  HL            ;HL points to channel information
B629 09                 ADD  HL,BC         ;HL points to current posn in buffer
B62A 0608               LD   B,#08
B62C EB       Z_CHRLOOP EX   DE,HL         ;DE= current buffer position
                                           ;HL points to graphic form
B62D 1A                 LD   A,(DE)        ;A= byte from buffer
B62E DDCB0B56           BIT  2,(Z_FLAGS)
B632 2001               JR   NZ,Z_CHROVER  ;Jump if OVER off
B634 AF                 XOR  A
B635 DDCB0B5E Z_CHROVER BIT  3,(Z_FLAGS)
B639 2801               JR   Z,Z_CHR_INV   ;Jump if INVERSE on
B63B 2F                 CPL
B63C AE       Z_CHR_INV XOR  (HL)          ;A= byte from graphic form, with
                                           ;OVER and INVERSE taken into account
B63D 12                 LD   (DE),A        ;Store in buffer
B63E E5                 PUSH HL            ;Stack pointer into graphic form
B63F 212000             LD   HL,#0020
B642 19                 ADD  HL,DE         ;HL points to appropriate byte in
                                           ;buffer for next row of character
B643 D1                 POP  DE            ;DE points into graphic form
B644 13                 INC  DE            ;DE points to next byte to use
B645 10E5               DJNZ Z_CHRLOOP     ;Print whole character into buffer
B647 F1       Z_EXIT    POP  AF            ;A= character just printed
B648 A7                 AND  A             ;Reset the Carry flag
B649 C9                 RET                ;Return

B64A 08       Z_OPEN    EX   AF,AF'        ;A'= stream number to attach channel
B64B 3E5A               LD   A,"Z"
B64D CD94B5             CALL SEARCHALL     ;Search for another "Z" channel
B650 D2C415             JP   NC,REPORT_J   ;Error if one exists
B653 DD2144B5           LD   IX,Z_CLOSE    ;IX= close buffer address
B657 21B2B5             LD   HL,Z_PRINT    ;HL= output address
B65A 11C415             LD   DE,REPORT_J   ;DE= input address
B65D 010E01             LD   BC,#010E      ;BC= length of channel info
B660 CD6DB0             CALL OPEN_NEW      ;Open the channel
B663 DD360B00           LD   (Z_FLAGS),#00 ;Reset the flags
B667 DD360D20           LD   (Z_WIDTH),#20 ;Specify buffer width = 20h chars
B66B C35FB5             JP   Z_EMPTY       ;Empty the buffer, and return

B66E 210007   SHADOW_DE LD   HL,UNPAGE
B671 E5                 PUSH HL            ;Stack UNPAGE address in Shadow ROM
B672 D5                 PUSH DE            ;Stack Shadow subroutine address
B673 65                 LD   H,L           ;HL= 0000
B674 E5                 PUSH HL            ;Stack 0000 signalling "Return to
                                           ;Shadow ROM address"
B675 C30800             JP   #0008         ;Jump to call Shadow subroutine

B678 DD2A515C Q_PRINT   LD   IX,(CURCHL)   ;IX points to channel information
B67C F5                 PUSH AF            ;Stack character to print
B67D FE0D               CP   "enter"
B67F 2002               JR   NZ,Q_PRINT_2  ;Jump unless character is "enter"
B681 3E0A               LD   A,#0A         ;A= QL's code for "enter"
B683 DD5E05   Q_PRINT_2 LD   E,(IX+#05)
B686 DD5606             LD   D,(IX+#06)    ;DE= Shadow output address
B689 CD6EB6             CALL SHADOW_DE     ;Call network output in Shadow ROM
B68C F1                 POP  AF            ;A= character just printed
B68D A7                 AND  A             ;Reset the Carry flag
B68E C9                 RET                ;Return

B68F DD2A515C Q_INPUT   LD   IX,(CURCHL)   ;IX points to channel information
B693 2A3D5C             LD   HL,(ERR_SP)   ;HL points to error return address
B696 5E                 LD   E,(HL)
B697 23                 INC  HL
B698 56                 LD   D,(HL)        ;DE= error return address
B699 217F10             LD   HL,ED_ERROR
B69C A7                 AND  A
B69D ED52               SBC  HL,DE
B69F 2814               JR   Z,Q_INPUT_2   ;Jump if in an INPUT channel
B6A1 DD5E07   Q_INKEY$  LD   E,(IX+#07)
B6A4 DD5608             LD   D,(IX+#08)    ;DE= Shadow input address
B6A7 CD6EB6             CALL SHADOW_DE     ;Call network input from Shadow ROM
B6AA 47                 LD   B,A           ;B= character just input
B6AB F5                 PUSH AF            ;Stack the flags
B6AC FE0A               CP   #0A
B6AE 2002               JR   NZ,Q_INKEY$2  ;Jump unless this is a QL "enter"
B6B0 060D               LD   B,#0D         ;B= Spectrum "enter" character
B6B2 F1       Q_INKEY$2 POP  AF            ;Restore the flags
B6B3 78                 LD   A,B           ;A= character to return
B6B4 C9                 RET                ;Return
B6B5 ED7B3D5C Q_INPUT_2 LD   SP,(ERR_SP)   ;Empty the machine stack down as
                                           ;far as ED_ERROR
B6B9 E1                 POP  HL            ;Drop the address ED_ERROR
B6BA E1                 POP  HL            ;HL= normal error return address ptr
B6BB 223D5C             LD   (ERR_SP),HL   ;Restore pointer to error return addr
B6BE CDA1B6   Q_INPLOOP CALL Q_INKEY$      ;Read next character from network
B6C1 3804               JR   C,QINPSTORE   ;Jump if character OK
B6C3 28F9               JR   Z,Q_INPLOOP   ;Jump if still waiting
B6C5 CF                 RST  #08           ;Report "8, End of file"
B6C6 07                 DEFB #07
B6C7 FE0D     QINPSTORE CP   #0D
B6C9 C8                 RET  Z             ;Return (INPUT is now finished) if
                                           ;character is "enter"
B6CA CD850F             CALL ADD_CHAR_1    ;Store the char in the INPUT line
B6CD FDCB377E           BIT  7,(FLAG_X)
B6D1 20EB               JR   NZ,QINPLOOP   ;Loop back if doing INPUT LINE
B6D3 FE22               CP   #22
B6D5 CC850F             CALL Z,ADD_CHAR_1  ;Double-up quote characters
B6D8 18E4               JR   Q_INPLOOP     ;Go back for rest of INPUT

B6DA CD2117   MODIFY_N  CALL STR_DATA1     ;BC= STRMS data for stream supplied
B6DD 78                 LD   A,B
B6DE B1                 OR   C
B6DF 280C               JR   Z,MOD_ERROR   ;Error if stream not in use
B6E1 2A4F5C             LD   HL,(CHANS)    ;HL points to base of chan info area
B6E4 09                 ADD  HL,BC         ;HL points to 2nd byte of chan info
B6E5 23                 INC  HL
B6E6 23                 INC  HL
B6E7 23                 INC  HL            ;HL points to 5th byte of chan info
B6E8 7E                 LD   A,(HL)        ;A= name of channel
B6E9 FE4E               CP   "N"
B6EB 2802               JR   Z,MOD_QL      ;Jump only if channel is "N"
B6ED CF       MOD_ERROR RST  #08           ;Report "O, Invalid stream"
B6EE 17                 DEFB #17
B6EF 2B       MOD_QL    DEC  HL
B6F0 36B6               LD   (HL),#B6
B6F2 2B                 DEC  HL
B6F3 368F               LD   (HL),#8F      ;Store new input address
B6F5 2B                 DEC  HL
B6F6 36B6               LD   (HL),#B6
B6F8 2B                 DEC  HL
B6F9 3678               LD   (HL),#78      ;Store new output address
B6FB C9                 RET

;[This last bit of code gets overwritten by the code in Part 5.  JimG]

B6FC 3E04     Z_DEMO    LD   A,#04         ;A= stream number to attach
B6FE C34AB6             JP   Z_OPEN
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
