CHANNELS & STREAMS Introduction The Spectrum has a surprisingly modern system of input and output when the age of the Spectrum is considered. However, what is more surprising is the fact that the Spectrum manual barely scratches the surface of what is possible. I/O on the Spectrum is based on channels and streams. Since the standard Spectrum has only a limited range of I/O devices is makes sense that different commands are available for each I/O device. For example, PRINT is used to send output to the screen, whereas LPRINT is used to send output to the printer. The extra devices catered for by Interface 1 (microdrives, RS232, and networking) reduced the practicality of continuing to invent new commands, although this was provided for in the case of the microdrives. Streams and channels intuitively correspond to the software and hardware parts of I/O respectively. That is, a stream should be thought of merely as a collection of data going to or coming from a piece of hardware, and a channel should be associated with a particular piece of hardware such as a printer. On the Spectrum streams are numbered from 0 through 15, and their basic operations are reading and writing data. The BASIC statement INPUT #s; will read data from stream number s, 0 <= s <= 15, into the variables specified in the input-list. Conversely, the BASIC statement PRINT #s; will write data to stream s, 0 <= s <= 15. In general both INPUT # and PRINT # can be used in the same way as their ordinary counterparts INPUT and PRINT. In particular all the normal complexity of a PRINT statement can be used equally well in a PRINT # statement. In each case the data sent to the stream is exactly the same as the data which would be sent to the screen by the PRINT statement. The INPUT # statement is slightly more complicated in that is can both read and write data, as in INPUT "What is your name? "; A$. In fact each stream really has two components, an input stream and an output stream. Data written to the stream by either PRINT # or INPUT # goes to the output stream while input comes from the input stream. It is even possible to change streams part way through a PRINT statement, as in PRINT #3; "hello"; #6; "there". This is obviously fairly confusing though, so should probably be avoided unless their is a good reason for using this construct. Opening and Closing How do you know which stream numbers are associated with which channel? Before a stream is used it must be OPENed. Opening a stream serves two purposes. It associates the given stream with a particular piece of hardware (the channel), and actually signals the relevant device that it is going to be used. Stream are opened in BASIC using the syntax OPEN #s, c where s is the stream number being opened and c is a string specifying the channel to associate the stream with. Following this command any data sent to stream s will go to the specified channel. It is possible to open several streams to the same device, but each stream can only be associated with a single channel. The statement CLOSE #s is used to end the association of stream s with channel c. It also informs that associated hardware that it is no longer required by stream s. The unexpanded Spectrum supports four channels: "K" the keyboard channel, "S" the screen channel, "P" the printer channel, and "R" an internal channel used by the Spectrum to send data to the edit buffer. In practice the only channel that has both input and output of these is the keyboard channel. When the Spectrum is first powered up the following streams are opened automatically: 0: "K", 1: "K", 2: "S", 3: "P". Thus, the command LPRINT is really an alternative to writing PRINT #3. The "R" channel cannot be opened from BASIC. It is possible to redefine the standard channels, thus OPEN #2, "P" will cause output normally sent to the screen to be redirected to the printer. For example, OPEN #5, "K" associates stream 5 with the keyboard, and thereafter INPUT #5; A$ would behave in an identical manner to INPUT A$. Device Independence The most important advantage of using streams is in the writing of device independent programs. Say that you wish to give the user the option of having all output go to either the screen or to the printer. Without using streams it is necessary then to have separate output statements for each device, as in IF (output = printer) THEN LPRINT "Hello" ELSE PRINT "Hello" but using streams we can just open a particular stream (say 4) to the desired output device and thereafter use only one output statement PRINT #4; "Hello" Obviously this will result is a much shorter program, particularly, if there are many output statements in the program. Further, it is an easy matter to add even further output devices if they become an option later in the programs development. More Stream Commands BASIC also allows LIST and INKEY$ to be used to streams. LIST #s will send a copy of the program to stream s; e.g. normally LIST #3 is the same thing as LLIST. However, on the standard Spectrum INKEY$ can only be used with the keyboard channel. Memory Formats Technical section for machine code programmers. Knowing about the actually layout of the stream records in memory is useful if you want to add your own hardware devices to the Spectrum, or if you which to make you own specialized streams. The information that defines each channel is stored in the channel information area starting at CHANS and ending at PROG - 2. Each channel record has the following format: two-byte address of the output routine two-byte address of the input routine one-byte channel code letter where the input and output routines are address of machine code sub- routines. The output routine must accept Spectrum character codes passed to it in the A register. The input routine must return data in the form of Spectrum character codes, and signal that data is available by setting the carry flag. If no data is available then this is indicated be resetting both the carry and zero flags. Stubs should be provided if a channel does not support either input or output (e.g. the stub may simply call RST 8 with an error code). With Interface 1 attached an extended format is used. The above fields are followed by two-byte address 8K error routine 40 two-byte address 8K error routine 40 one-byte length of channel descriptor Data about which streams are associated with which channels is in a 38-byte area of memory starting at STRMS. The table is a series of 16-bit offsets to the channel record vectored from CHANS. A value of one indicates the channel record starting at CHANS, and so on. This accounts for 32 bytes of the 38. The remaining 6 bytes are for three hidden streams (253, 254, 255) used internally by BASIC. A zero entry in the table indicates a stream not open. It is possible to redirect existing channels to your own I/O routines. This can be used among other things to cause LPRINT to use your own printer driver rather than the one provided in the ROM. It allows you to perform I/O for your own hardware devices, or for you to write your own handlers from PRINT and INPUT. It is easiest to modify the existing "P" channel record. The "K" channel is not a good option for modification because its values are restored every time a INPUT statement is executed. It is possible to create new channel records elsewhere in memory, e.g. by changing the value of CURCHL. Another difficulty is that without Interface 1, OPEN will only work with K, S, and P and so it is necessary to provide some other way of opening your own channels. To make space for a new record a call should be made to the ROM routine at 5717. This will allocate the requested space and alter any system variables affected by the change. The amount of space required is passed in BC, and the address of the first location to be allocated is passed in HL. For example, LD BC, 100; LD HL, 23700; CALL 5717 will allocate 100 bytes starting at 23700. A new channel descriptor should start at one less than PROG.