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