.........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

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

