                           MIRA



             Mike Rains gives your Microdrive

                 a random access facility.



[ One note to begin with: as this is a program which works

  with Microdrives, it has been provided on a .mdr file,

  not a .tzx file as is more usual. If your emulator can't

  handle Microdrive files, well, you have no use for MIRA

  anyway... ]



One of the main restrictions of the Sinclair Microdrive

system is that it is only possible to access data files

from Basic which imposes a severe restriction on database

and other similar applications which access data files. For

example, changing a record in a data file involves opening

a new file, copying the preceding records from the old to

the new, writing the altered record to the new file,

copying the rest of the old file, deleting the original

file and renaming the new file as the old file.

  Even this last is not straightforward as Sinclair Basic

lacks a Rename command. In practice, the following code

illustrates the type of subroutine that would be required

to alter the nth record of a file of 100 records called

"data" in which each record is 24 bytes long. The new

record is in the string n$.



 9000 REM Alter a record (Sinclair BASIC)

 9010 OPEN #5;"m";1;"data"

 9020 OPEN #6;"m";1;"new.data"

 9030 IF n=1 THEN GOTO 9070

 9040 FOR p=1 TO n-1

 9050 INPUT #5; LINE x$: PRINT #6;x$

 9060 NEXT p

 9070 INPUT #5; LINE x$: PRINT #6;n$: REM write new record

 9080 IF n=100 THEN GOTO 9120

 9090 FOR p=n+1 TO 100

 9100 INPUT #5; LINE x$: PRINT #6;x$

 9110 NEXT p

 9120 CLOSE #5: CLOSE #6

 9130 ERASE "m";1;"data"

 9140 MOVE "m";1;"new.data" TO "m";1;"data"

 9150 ERASE "m";1;"new.data"

 9160 RETURN



  It should be clear that this is both tortuous and very

slow as it involves many operations of the Microdrive,

which also causes increased wear and tear on the cartridge

and drive. MIRA solves these problems by providing Sinclair

Basic with three new commands which together allow the

maintenance of random access files similar to those avail-

able on micros such as the BBC with disc interface.

  The new commands (RND#, POINT# and RESTORE#) are all

obtained by using existing Sinclair keywords but in new

contexts. The use and syntax of the new commands and the

theory behind their implementation are explained below but

first, as an example, the above subroutine re-written using

the MIRA commands.

  Note that a 24 byte record occupies 25 bytes in the file

because of the inclusion of a carriage return (CHR$ 13).



 9000 REM Alter a record (MIRA)

 9010 OPEN #5;"m";1;"data":RND#5

 9020POINT #5,25*(n-1): PRINT #5;n$

 9030 RESTORE #5

 9040 RETURN



  The first of the new commands, RND followed by a stream

identifier (e.g. #5) causes the file attached to the given

stream to be declared as a random access file. The file

must already have been opened with the standard OPEN #

command and must be a "read" file.

  The RND command cannot be used to create or open a file.

In the example above, the file "data" would originally need

to have been set up with code such as:



  100 DIM x$(24)

  110 OPEN #5;"m";1;"data"

  120 FOR p=1 TO 100: PRINT #5;x$: NEXT p

  130 CLOSE #5



  POINT followed by a stream identifier and a numeric

expression separated by a comma (e.g. POINT #5,n) moves the

internal pointer of the file attached to the given stream

to the n th byte of the file, where the next INPUT #,

PRINT #, or INKEY$# will take place, provided that the file

has been opened with OPEN # and made random with RND#.

  An error will be generated if you try to move the pointer

outside the limits of the file. The Basic loader program

for MIRA - listing 1 [on the .mdr as "Mira", and also as

"run" for your auto-loading convenience] - also defines a

function, FN p(x), which returns the current value of the

internal pointer of the file attached to stream x - e.g.

LET pos=FN p(5). For what it's worth, this will work with

any "read" file, not just a random access one.

  RESTORE followed by a stream identifier (e.g. #5) closes

the random access file attached to the given stream.

RESTORE # must be used in preference to CLOSE # to correct-

ly close a file that has been marked as random with RND#,

but may not be used to close any other type of file.

  The theory of adding new commands to Sinclair extended

Basic (i.e., with Interface 1 attached) is fully described

by A. Pernell, Master Your ZX Microdrive (Sunshine 1983)

and I. Logan, Spectrum Microdrive Book (Melbourne House

1983). In short, the shadow system variable VECTOR (address

23735), which usually contains the address of the ROM error

handling routine, must be loaded with the address of the

new commands routine which must then perform two distinct

functions.

  First, it must check the syntax of the new commands and

cause an error if it is incorrect, and then, at run-time

only, execute the command. "Run-time" means during the

running of a program or during the execution of a direct

command. Lines 9030-9040 of the MIRA Basic loader program

alter the value of VECTOR (line 9030 first ensures that the

shadow system variables have been created).

  As well as providing the new commands as described above,

MIRA must also provide new routines to replace the standard

PRINT and INPUT commands. To demonstrate how this is done

and to explain the actual operation of the new commands it

is necessary to first describe the structure of a Micro-

drive channel.

  Each Microdrive channel takes up 595 bytes in the area of

memory reserved for channel information. The start of this

area is contained in the system variable CHANS (address

23631). The actual location of a particular channel is

found by adding the displacement contained in the relevant

STRMS system variable - two bytes for each of streams -3 to

14 starting at address 23568 - to the value in CHANS and

subtracting 1. FN q(x) defined in the Basic loader program

performs this function for stream x. The byte allocation

within a Microdrive channel is shown in figure 1 and should

be referred to during the following discussion of the

operation of the various MIRA routines.

___________________________________________________________



  Figure 1. Microdrive channel structure.



  Byte   Length   Description

     0      2     "output" routine

     2      2     "input" routine

     4      1     channel specifier "M"

     5      2     shadow ROM output

     7      2     shadow ROM input

     9      2     length of this CHANS area (595)

    11      2     current buffer position (0-512)

    13      1     position of record in file (0-255)

    14     10     10 bytes of filename

    24      1     bit 0 - reset - "read" file

                          set   - "write" file

                  bits 1-7 - unused (all reset)

    25      1     drive number (1-8)

    26      2     drive map location

    28     12     12 bytes of header pre-amble

    40      1     bit 0 - set to signal header

                  bits 1-7 - unused

    41      1     sector number (0-255)

    42      2     unused (see text)

    44     10     10 bytes of cartridge name

    54      1     header checksum

    55     12     12 bytes of data pre-amble

    67      1     bit 0 - reset - not a header

                  bit 1 - reset - not end of file

                  bit 1 - set   - end of file

                  bit 2 - reset - a PRINT file

                  bit 2 - set   - not a PRINT file

                  bits 3-7 - unused (all reset)

    68      1     record number (0-255)

    69      2     number of bytes in record (0-512)

    71     10     10 bytes of filename

    81      1     checksum

    82    512     buffer data

   594      1     data checksum

___________________________________________________________



 The RND# command run-time routine performs three actions:

# Bit 0 of byte 24 in the relevant channel is set to

  signify a "write" file.

# The addresses of the new output and input routines are

  loaded into bytes 5-6 and 7-8 of the channel.

# Bit 0 of the unused 42nd byte of the channel is used as

  a flag to signal whether the buffer contents have been

  altered. RND# resets this bit to signify "unaltered".

 The following actions are performed by the POINT# command:

# The record number in which the desired file position lies

  is calculated and compared with the value in byte 13 of

  the channel.

# If they do not match, the "write buffer" and "read

  buffer" sub-routines are called to fetch the required

  record into the buffer.

# The desired position within the record is loaded into

  channel locations 11 and 12.

 The RESTORE # calls the "write buffer sub-routine to write

the current record if it is flagged as "altered" (i.e.: bit

0 of byte 42 is set), resets but 0 of channel byte 24 to

flag the file as "read" and then calls the shadow ROM

CLOSE # routine.

 The new output routine used by all PRINT # statements is

entered with the character to be written to the file in the

A register. The following actions are performed:

# The current buffer position is obtained from bytes 11 and

  12 of the channel, added to 82 - the start of the data

  area - to give the correct address, and the character

  from the A register stored there.

# The buffer position is incremented and re-stored in bytes

  11 and 12.

# The record is flagged as "altered" by setting bit 0 of

  byte 42.

# The "next record" sub-routine is called if the end of the

  present record has been reached.

 The new input routine used by INPUT # and INKEY$# state-

ments performs the following actions:

# Tests for the end of the current record and calls "next

  record" if required.

# Increments the buffer position in bytes 11 and 12 of the

  channel.

# Using this position - added to 82 as before - reads a

  character from the buffer into the A register and sets

  the carry flag to signal a successful read.

[ The article ended with some instructions for entry which

  were jumbled, and neither complete nor correct; they

  look like they were written for another version. The

  last remark is correct, though: ]

Note that MIRA will need to be re-activated by the use of

lines 9020-60 of listing 2 following a NEW command.





[ And now a bit on the contents of the .mdr. This includes

  "Mira" a.k.a. "run" and "MiraLoader" as described above,

  as well as the "c:mira" machine code file produced by

  the latter and read by the former.

    Since Mr. Rains did not provide a sample application

  of his tool, but I needed one myself to test that I had

  entered the MC correctly, I've written a very simple one

  called "Notepad", which I've also provided on the .mdr.

  Do not be deceived by its triviality; much more compli-

  cated applications could be written, but I did not have

  the time to do so for an example program.



    Finally, after writing this program, I discovered that

  it did not work, and after some work with a disassembler,

  I found that the cause of this was not a typo, but the

  fact that Mr. Rains had not used hook codes to call the

  Interface 1's internal routines, but absolute addresses.

  Of course Murphy ensured that my emulator has a different

  issue IF1 ROM than the one Mr. Rains coded for.

    Luckily, this was easy to patch: just a few addresses

  need to be re-POKEd, and it seems that the ones used did

  not change after the second issue of the IF1. The result

  of this is the final program on the .mdr, "MiraPatch".

  It loads the original machine code, backs it up, patches

  it for newer ROMs, and saves the new version under the

  old name. In fact, for my own convenience, the "c:mira"

  that is on the .mdr is the patched version (and the

  backup is also present).

    You may have an even newer (or just different) version

  of the ROM, in which case even this may not work; if so,

  sorry, I can't help, but you might try finding the

  correct addresses for your version, using the ones Mira

  uses, a copy of Gianluca Carri's "Spectrum Shadow Rom

  Disassembly" (available as a download from World of

  Spectrum, amongs others), and a disassembly of your own

  IF1 ROM. You can then enter the right addresses in

  MiraPatch and create your own version of the code.



                                   Richard Bos, May 2012. ]