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

STREAMS AND CHANNELS
by Toni Baker
part 1 of 5, ZX Computing December 1986

Streams and channels might sound like computer jargon, but if
you can master them you can control some powerful new facilities.

[Note that the code in STREAMS.TAP "PART3", "PART4" and "PART5"]
[includes the code from the previous part.                 JimG]


Whenever we PRINT something, or INPUT something, we are making use of
streams and channels. PRINT really means PRINT #2, although the #2 is
often omitted. Similarly, INPUT really means INPUT #0, and LPRINT
means PRINT #3. The number after the "#" symbol is called the STREAM
NUMBER.

Now, not every stream number is usable. If, after switching the
Spectrum on, you type PRINT #6, you'll get an error report "O Invalid
stream". This is because stream number 6 is not attached to a CHANNEL,
so before I do anything else I'd better explain what a CHANNEL is.

A CHANNEL is a device used for either inputting or outputting (or
both) bytes of data. The TV screen is a channel, since characters may
be printed onto it. Every channel has a name, consisting of a single
letter of the alphabet. The screen is called channel "S" because "S"
stands for Screen. An OPEN # statement is used to attach a channel to
a stream, so if you type in the BASIC statement OPEN #6,"S" then from
now on, whenever you use PRINT #6; the output will be printed to the
screen.

Similarly, the keyboard is a device used for inputting, therefore it
too is a channel. It has a name - "K" for Keyboard. You can attach a
stream to the keyboard just as easily as to the screen: OPEN #N,"K"
will attach stream number N to the keyboard. You can have a maximum of
sixteen streams altogether, and these are numbered from zero to
fifteen. There is no such thing as stream number sixteen, and if you
attempt to use it then you'll get an error message. Streams zero to
three, however, are already spoken for. Stream zero and stream one are
both attached to channel "K" (the keyboard), stream two is attached to
channel "S" (the screen), and stream three is attached to channel "P"
(the printer).

Channels "S" and "P" can only be used for output, so PRINT #2; is
allowed, but INPUT #2; is not. surprisingly though, channel "K" can be
used either for inputting or for outputting. INPUT #0; is the same as
a conventional INPUT statement, but PRINT# 0 will print onto the lower
two lines of the screen (where INPUT text normally appears). If you
follow a PRINT #0; statement with PAUSE 0, for example, then you'll
see this effect in action.


CHANNEL "P"

Channel "P" is a weird one. On the 16K and 48K versions of the
Spectrum, and also on the Spectrum 128 in 48K mode, channel "P" is the
ZX Printer. On the Spectrum 128 in 128K mode, however, channel "P" is
the built-in RS232 socket, into which you can plug a compatible
printer, another computer, or anything else you feel like.
Incidentally on the Spectrum 128 in 128K mode you can also INPUT from
channel "P". This simply means that the computer can accept data from
its RS232 port, which means that two computers can effectively talk to
each other via this link. It is not possible to print to a ZX type
printer from the 128 in 128K mode, at least not in BASIC, but that's
where this series comes in.

If you have connected a ZX Interface One then there will be other
channels too. Channel "M" - the microdrives; channel "N" - the
network; channels "T" and "B" - Interface One's RS232 port (as opposed
to the Spectrum 128's RS232 port).

This, then, concludes the list of standard channels available on the
Spectrum, but the use of channels to do anything else is vastly
under-rated. This four-part [Actually five-part. JimG] series is
devoted to changing all that. It is the purpose of this series to
explore strange new programming techniques, to seek out new channels
and new information, to boldly split infinitives which no stream has
split before.

We shall invent, in machine code, new channels which can print to the
screen in large or small letters; new channels which will allow the
use of WINDOWS on the screen (a la QL); new channels which allow
completely successful communication between Spectrums and QLs over the
network; new channels which enable the ZX Printer to work on the
Spectrum 128 in 128K mode; new channels which will allow users of the
Spectrum 128 to make use of Read and Write files in RAM-disc.


C.I.A.

So how does it all work? Well that is the question I intend to answer
in this article. It all hinges on an area of memory that you probably
haven't used before, called the "Channel Information Area", and which
happens to be situated directly below the BASIC program area. To
create a channel you merely have to organise the memory in the channel
information area in the correct way, and of course, provide the
machine code to effect PRINT and INPUT to and from the channel. Here's
how it works.

Every channel must have a "Channel Information Block" stored in the
channel information area. This is simply a chunk of memory dedicated
to the channel, containing all of its system variables and the
addresses of its input and output subroutines. The three primary
channels "K", "S" and "P" all use only five bytes, but they are
exceptions. All other channels require a minimum of eleven bytes, as
we shall see.

Incidentally, there is a fourth primary channel (that is, a channel
available on the unexpanded standard Spectrum) called channel "R" but
it is only available in machine code. We'll learn more of that later.

The channel information blocks for these four primary channels look
like this:

Bytes 0 and 1: Address of PRINT # routine.
Bytes 2 and 3: Address of INPUT # routine.
Byte 4: Name of channel (ASCII character code).

The Interface One channels all need a minimum of eleven bytes, and
their channel information is addressed by the IX register. Their
channel information blocks look like this:

IX+00 (2 bytes): The address 0008.
IX+02 (2 bytes): The address 0008.
IX+04: Name of channel (ASCII character code).
IX+05 (2 bytes): Address of PRINT # routine (in Shadow ROM).
IX+07 (2 bytes): Address of INPUT # routine (in Shadow ROM).
IX+09 (2 bytes): Length of channel information block (minimum 0008).
IX+0B: Any additional information.


ROM ROUTINES

The standard PRINT and INPUT subroutines in the ROM (RST 10 and CALL
15E6,INPUT_AD) will "expect" a primary channel. Therefore, control
will jump to the address given by bytes 0 and 1 (for RST 10), or to
the address given by bytes 2 and 3 (for INPUT_AD). In the case of the
Interface One channels, this address will in both cases be 0008. At
this address the Shadow ROM is paged in and a routine in the Shadow
ROM will redirect control to the address given by (IX+05/06) or
(IX+07/08).

Our channels will look different again. We too shall use IX to index
the channel information, however our channels will look like this:

IX+00 (2 bytes): Address of PRINT # routine.
IX+02 (2 bytes): Address of INPUT # routine.
IX+04: Name of channel (ASCII character code).
IX+05 (2 bytes): The number 1234, which identifies this as a user
defined channel.
IX+07 (2 bytes): Address of CLOSE # routine.
IX+09 (2 bytes): Length of channel information block (minimum 0008).
IX+0B: Any additional information.


STREAMS ARE IMPORTANT TOO

You see, each stream has its own unique two-byte system variable.
STRMS_00 is at 5C16, STRMS_01 is at 5C18, and so on. In addition there
are three streams which are available in machine code but not in
BASIC. These are stream FD, stream FE, and stream FF. They too have
system variables. STRMS_FD is at 5C10, STRMS_FE is at 5C12, and
STRMS_FF is at 5C14. Stream FD is permanently attached to channel "K"
and should not be changed. Similarly, stream FE is permanently
attached to channel "S".

Stream FF is interesting. It is permanently attached to channel "R" -
an internal machine code channel, which is capable of inserting bytes
into the Spectrum's dynamic memory layout. We'll make use of this
feature later on.

The STRMS variables themselves link the stream to the channel. If the
STRMS variable for any particular stream contains 0000 then it means
that the stream in question is closed (ie. it has no channel attached
to it). If the variable is non-zero then the stream is attached to a
channel. In particular it is attached to the channel whose channel
information block begins at address (CHANS)+(STRMS_n)-1.

It follows, therefore, that it must surely be easier, from a machine
code point of view, to OPEN a channel than to CLOSE it, since to open
a channel all you have to do is to make room for the new channel
information at the end of the CHANS area, and to assign the
appropriate STRMS variable to attach the stream to. Closing such a
channel, on the other hand, may be more difficult, because if the
channel information block is not the last one in the CHANS area then
all of the channel information blocks which follow it will have [to be
moved] down in memory, which in turn means that the corresponding
STRMS variable will have to be altered accordingly. Any or all of the
STRMS variables may have to be altered if this is the case.


CLOSE_A

The main program accompanying this article is called CLOSE_A [But
ignore it, as it's "riddled with bugs" according to the author (see
next issue). JimG]. Its purpose is to close one of our new channels.
On entry the A register must contain the stream number to be closed.
If the stream is already closed, or if the channel is not one of our
user-defined ones, then the subroutine will return, having done
nothing. If, on the other hand, the stream specified is attached to
one of our new channels then the routine will close the channel,
reclaim memory used by that channel, and adjust any of the STRMS
variables required. The carry flag must also be assigned on entry. If
the carry flag is set it means that any data stored in buffers must be
sent out, but if the carry flag is reset it means that any such data
is to be ignored.

Also included is a program CLEAR_CHANS which will close all such user
defined channels. Any data stored in buffers will be lost if this
routine is called.

You may think it strange starting with a CLOSE # routine, whilst not
having an OPEN # routine, but that's where the cliff-hanger comes in.
Next month I'll start opening some new channels - in particular I'll
deal with printing to the screen in large or small letters. Remember
that once a channel is opened it may be used at will in BASIC. As soon
as you have a channel which can print sixty- four characters across
the screen then you can use a common or garden BASIC PRINT statement
to print things to the channel.

Study the programs I've listed. They are not relocatable, because they
are designed to link in with all the other bits of program which
you'll get in the next three episodes. If you still haven't properly
understood the concepts of channels or streams then maybe next month
you'll be a whole lot wiser, when I shall start giving you some
concrete examples of the ideas in use.

See you then.

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Listing 1

B000 08       CLOSE_A   EX   AF,AF'        ;Store the carry flag
B001 CD2117             CALL STR_DATA_A    ;HL points to STRMS info
B004 78                 LD   A,B
B005 B1                 OR   C
B006 C8                 RET  Z             ;Return if stream already closed
B007 E5                 PUSH HL            ;Stack pointer to STRMS info
B008 21E2A3             LD   HL,#A3E2
B00B 09                 ADD  HL,BC
B00C E1                 POP  HL            ;HL points to STRMS info
B00D D0                 RET  NC            ;Return if channel is K, S, R or P
B00E DD2A4F5C           LD   IX,(CHANS)
B012 DD09               ADD  IX,BC
B014 DD2B               DEC  IX            ;IX points to channel info
B016 DD7E05             LD   A,(IX+#05)
B019 FE34               CP   #34
B01B C0                 RET  NZ            ;Return if not a user-defined channel
B01C DD7E06             LD   A,(IX+#06)
B01F FE12               CP   #12
B021 C0                 RET  NZ            ;Return if not a user-defined channel
B022 3600               LD   (HL),#00
B024 23                 INC  HL
B025 3600               LD   (HL),#00      ;Load STRMS info with 0000 to
                                           ;signal "stream closed"
B027 C5                 PUSH BC            ;Stack CHANS displacement
B028 DD6E07             LD   L,(IX+#07)
B02B DD6608             LD   H,(IX+#08)    ;HL= address of CLOSE # routine
B02E 08                 EX   AF,AF'
B02F DC2C16             CALL C,CALL_JUMP   ;Call subroutine if required
B032 DDE5               PUSH IX
B034 E1                 POP  HL            ;HL= address of channel info
B035 DD4E09             LD   C,(IX+#09)
B038 DD460A             LD   B,(IX+#0A)    ;BC= length of channel info
B03B C5                 PUSH BC            ;Stack length of channel info
B03C CDE819             CALL RECLAIM_2     ;Reclaim memory used by channel
B03F 3E10               LD   A,#10         ;A= number of streams
B041 21165C             LD   HL,#5C16      ;HL points to STRMS data for stream 0
B044 22745C   C_LOOP    LD   (T_ADDR),HL   ;Store STRMS pointer
B047 5E                 LD   E,(HL)
B048 23                 INC  HL
B049 56                 LD   D,(HL)        ;DE= STRMS data for stream A
B04A C1                 POP  BC            ;BC= length of channel info reclaimed
B04B E1                 POP  HL            ;HL= CHANS disp. of channel closed
B04C E5                 PUSH HL
B04D C5                 PUSH BC
B04E A7                 AND  A
B04F ED52               SBC  HL,DE
B051 300B               JR   NC,C_CONT     ;Jump if channel info for stream A
                                           ;has moved
B053 EB                 EX   DE,HL         ;HL= STRMS data for stream A
B054 A7                 AND  A
B055 ED42               SBC  HL,BC         ;Reduce to allow for reclaimed area
B057 EB                 EX   DE,HL         ;DE= modified STRMS data
B058 2A745C             LD   HL,(T_ADDR)   ;HL points to STRMS data
B05B 73                 LD   (HL),E
B05C 23                 INC  HL
B05D 72                 LD   (HL),D        ;Store modified data
B05E 2A745C   C_CONT    LD   HL,(T_ADDR)   ;HL points to STRMS data
B061 23                 INC  HL
B062 23                 INC  HL            ;HL points to next STRMS variable
B063 3D                 DEC  A
B064 20DE               JR   NZ,C_LOOP     ;Repeat for all sixteen streams
B066 F1                 POP  AF
B067 F1                 POP  AF            ;Balance the stack
B068 C9                 RET
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Listing 2

B069 3E10     CLEARCHAN LD   A,#10         ;A= number of streams
B06B 3D       CC_LOOP   DEC  A             ;A= next stream number
B06C F5                 PUSH AF            ;Stack stream number and Zero flag
B06D A7                 AND  A             ;Reset carry to signal
                                           ;"Do not send data"
B06E CD00B0             CALL CLOSE_A       ;Close stream A if it is a
                                           ;user-defined stream
B071 F1                 POP  AF            ;A= current stream number
B072 20F7               JR   NZ,CC_LOOP    ;Repeat for all streams
B074 C9                 RET
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
