User Tools

Site Tools


magazines:chacking5
no way to compare when less than two revisions

Differences

This shows you the differences between two versions of the page.


magazines:chacking5 [2015-04-17 04:34] (current) – created - external edit 127.0.0.1
Line 1: Line 1:
 +<code>
 +                   @@@@@@@@
 +             @@@@@@@@@@@@@@@@@@
 +         @@@@@@            @@@@@@
 +      @@@@@
 +    @@@@@  @@@@  @@@@      @@      @@@@@   @@@@  @@@@  @@@@  @@@@  @@@@   @@@@@
 +  @@@@@    @@    @@      @@@@    @@   @@   @@  @@@     @@    @@@@  @@   @@   @@
 + @@@@@    @@@@@@@@     @@  @@   @@        @@@@@       @@    @@ @@ @@   @@
 +@@@@@    @@    @@    @@@@@@@@  @@   @@   @@  @@@     @@    @@  @@@@   @@   @@
 +@@@@@  @@@@  @@@@  @@@@  @@@@  @@@@@   @@@@  @@@@  @@@@  @@@@  @@@@   @@@@@@
 +@@@@@                                                                     @@
 + @@@@@@            @@@@@@             Issue #5
 +   @@@@@@@@@@@@@@@@@@              March 7, 1993
 +       @@@@@@@@
  
 +-----------------------------------------------------------------------------
 +Editor's Notes:
 +by Craig Taylor
 +
 +  It seems that each issue of C= Hacking has always began with a "Sorry, It's
 +  late but here it is message." - Well, this one will start out again like 
 +  that - This issue was originally scheduled to be out the middle of January
 +  but due to several delays in obtaining articles and my delaying trying to
 +  debug the multi-tasking source code it's been held up until now. 
 +  
 +  My apologies to the authors who have had their articles into me on time -
 +  school is coming first for me and having to do a lot of coding for several
 +  classes was the major contributing factor to the delays. 
 +
 +  Now, after the apologies are out of the way - Let's take a look at what has
 +  happened since last time I wrote.
 +
 +  - RUN magazine is no longer with us.
 +
 +  As one of the last hold-outs I was expecting RUN magazine to keep on printing
 +  until the Commodore 64/128's really did die out but apparently the publishers
 +  decided it wouldn't be so. This leaves the Twin Cities magazine as the only
 +  US magazine in publication for the Commodore (6502 based) computers that I am
 +  aware of. Speaking of Twin Cities (not sure if he's combining the 64/128 or
 +  just coming out with seperate Twin Cities magazines) does anybody know or
 +  have any information on when the next issue will be out? Or has my 
 +  lastest issue just not been sent out?
 +
 +  As I was writing this I got the latest issue of Twin Cities which has
 +  expanded to C=64 coverage also. The new issue looks very nice, about 53
 +  pages of so of good decent material. I'd recommend get a subscription for
 +  those of you who are looking to still hear about new Commodore products.
 +
 +  I'd like to get people's reactions on the demise of RUN and what will
 +  people will think will probably be the main source of information for C=
 +  owners.  A lot of people reading this magazine are on the comp.sys.cbm
 +  newsgroup but I'm wondering about individuals who do not have access to
 +  such a newsgroup and do not have access to the internet. Let me know what
 +  you think - hopefully through a friend w/ access to the internet. Sort of
 +  a catch-22 I guess.
 +
 +  - A Mail-Server has been setup to automate sending issue requests.
 +
 +  The full details of how to use the Mail-Server is in a documentation file
 +  contained within but this mail-server (whose source code is available for
 +  anyone who wishes to see it written in VAX DCL code) also allows file
 +  requests which will be uuencoded and sent to you. I am trying to have all of
 +  the programs in each issue available via request as for some people it is
 +  a minor pain trying to extract and compile the programs contained within.
 +
 +  - I saw a note recently that the speed-up board work was still being done.
 +
 +  Does anybody know anything further about this? I'm interested in this and
 +  how it would be carried out / done but aside from an occasional post here
 +  and there about it I actually hear very little. 
 +
 +  - There is also work on an Ansi C compiler being done.
 +
 +  Recently a group of people (about 9 currently) are working on a C compiler
 +  for the C=64 and C=128 which will eventually support the full ANSI C
 +  library. A large list of extensions have been proposed and the compiler
 +  will probably be released as either shareware or possibly, public domain.
 +
 +  Ack! - This magazine keeps growing. The last issue was approx.
 +  somewhere around 3000 lines, this one is just a tad over 6000. I'm
 +  sure that we're not suffering the quality just because of the
 +  quantity. :-) Be sure to take a look at the previous back issues
 +  available via the Mail-Server and don't be afraid to suggest comments
 +  or suggestions. While usually the authors are too busy to take ideas
 +  for new programs we always welcome to hear how useful you find certain
 +  programs included herein etc.
 +
 +  Also I am looking for articles on any type of software project, hardware
 +  project or general theory articles that you would like to submit. Just
 +  leave me a message via email at "duck@pembvax1.pembroke.edu". Note also
 +  that I've just signed up for a GENIE account and can be reached there via
 +  C.TAYLOR37 once my account is approved.
 +
 +=============================================================================
 +
 +  Please note that this issue and prior ones are available via anonymous
 +  FTP from ccosun.caltech.edu under pub/rknop/hacking.mag in addition to the
 +  mailserver which is documented in this issue.
 +
 +=============================================================================
 +  
 +  NOTICE: Permission is granted to re-distribute this "net-magazine", in 
 +  whole, freely for non-profit use. However, please contact individual
 +  authors for permission to publish or re-distribute articles seperately.
 +  A charge of no greater than 5 US. Dollars or equivlent may be charged for
 +  library service / diskette costs for this "net-magazine." 
 +
 +=============================================================================
 +</code>
 +====== In this Issue: ======
 +<code>
 +Mail-Server Documentation
 +
 +This articles describes how to access the mail-server for Commodore Hacking
 +and includes a list of currently available files and back-issues.
 +
 +Stretching Sprites
 +
 +It's possible to expand sprites to more than twice their original size, but 
 +there is no need to expand all of them equally. This article examins how to
 +expand them 2,3, or more multiples of their original size.
 +
 +Rob Hubbard's Music: Disassembled, Commented and Explained
 +
 +This article written by Anthony McSweeney, presents the valuable source to
 +Rob Hubbard's first music routine. This routine was used in Rob's first 20
 +or 30 musics, including such classics as Thing on a Spring (Gremlin Graphics),
 +Commando (Elite), Thrust (Firebird), International Karate (System 3), and
 +Proteus (also known as Warhawk, by Firebird). 
 +
 +ZPM3 and ZCCP Enhancements for CP/M Plus from Simeon Cran
 +
 +Although all the articles to date in C= Hacking have focused on 6510/ 8502
 +programming, there have been some interesting developments on the Z80 front.
 +C128 CP/M users should be aware of the benefits of a new set of enhancements
 +to the operating system that offers inreased speed and flexibility as well
 +as new features. If that isn't enough, this package will also run ZCPR 3.3
 +utilities and applications that won't run under standard CP/M Plus.
 +
 +Multi-Tasking on the C=128 - Part 1
 +
 +This article examines the rudiments of Multi-Tasking and also details the
 +system calls in the Multi-Tasking package to be released in the next issue
 +of C= Hacking.
 +
 +LITTLE RED WRITER: MS-DOS file reader/writer for the C128 and 1571/81.
 +
 +This article is an extension on Little Red Reader which was presented in the
 +last issue and allows for reading and writing of MS-Dos diskettes from and to
 +1571/81 drives. 
 +
 +=============================================================================
 +</code>
 +====== Mail-Server Documentation ======
 +<code>
 +by Craig Taylor (duck@pembvax1.pembroke.edu)
 +
 +What is a mail-server?
 +
 +   A mailserver is an automated job that will scan my mail file for messages
 +   with a subject line of "MAILSERV" and will then automatically carry out
 +   certain operations within the body of the mail message. This makes it easier
 +   on me and you. Easier for me so that I don't have to deal with 50+ messages
 +   each month asking for files to be sent out and also insures that your files
 +   that you requested will be sent within 24 hours. In addition it allows 
 +   files to be more easily sent and accessed in case you are not able to 
 +   extract the source files from C= Hacking. 
 +
 +   If you have FTP access please see the Editor's Notes at the start for
 +   information on R. Knop's FTP site and how to access it as you may find
 +   using that somewhat quicker to use.
 +
 +How to use the mail-server / What it is.
 +
 +   This mail-server is intended to help me keep track / more easily update my
 +   mailing list of individuals who wish to sub-scribe or get back-issues of
 +   C= Hacking mailed to them. 
 +
 +   To use it simply send a message to "duck@pembvax1.pembroke.edu" (me) with a
 +   subject line of "MAILSERV" and then with one of the following commands in the
 +   body of the mail message:
 +
 +Currently the following commands are supported:
 +
 +  help              - sends current documentation f file list
 +  send iss<number>. - sends issue # (1-4 currently). Remember the period!!
 +  subscribe         - subscribe to the mailing list automatically
 + *subscribe catalog - subscribes to a list that will be sent out 
 +                      everytime the catalog changes.
 +  catalog           - show list of available source /uuencoded binaries
 +  psend name        - send uuencoded binary.
 +
 +Commands no longer supported:
 +
 +  status            - returns the current commands (this list)
 +                      (use the help file)
 +
 +Please note that the mailserver is only run at 2:00 AM EST.
 +
 +Catalog List - Last update February 27, 1993.
 +
 +  iss1.                 - C= Hacking, Issue #1
 +  iss2.                 - C= Hacking, Issue #2
 +  iss3.                 - C= Hacking, Issue #3
 +  iss4.                 - C= Hacking, Issue #4
 +  iss5.                 - C= Hacking, Issue #5
 +  contents.lis          - Content Listing of Issues #1-4
 +  mailserv.012493       - VAX/DCL Mailserver .share file
 +
 + *invasion1.sfx         - Space Invasion Source (Starting with Issue 4)
 + *bmover.sfx            - Geos 128 Banking with Banks 2f3 (Issue #2)
 + *vdc-bg.sfx            - Use of 64K VDC RAM in Geos (Issue #3)
 + *c64.zip               - C64 Emulator for IBM
 + *lrr.sfx               - Little Red Reader (from C= Hacking #4)
 +
 +  -- Temporary Files -> Or files that will be deleted as needed for space
 +
 + *zedv075.sfx           - Zed-128 Text Editor 
 + *ramdosii.sfx          - REU Dos for the C=128 Allows > 512k REU.
 + 
 +  NOTE: Files marked with "*" should be requested via PSEND - they will be
 +        sent to you in uuencoded form. They may _not_ be requested via SEND.
 +
 +=============================================================================
 +</code>
 +====== The Demo Corner: Stretching Sprites ======
 +<code>
 +by Pasi 'Albert' Ojala (po87553@cs.tut.fi)
 +        Written: 16-May-91  Translation/Revision 01-Jun-92, Dec-92
 +
 +(All timings are in PAL, principles will apply to NTSC too)
 +
 +You might have heard that it is possible to expand sprites to more than
 +twice their original size. Imagine a sprite scroller with 6-times expanded
 +sprites. However, there is no need to expand all of them equally. Using
 +this technique, it is possible to make easy sinus effects and constantly
 +expanding and shrinking letters.
 +
 +The VIC (video interface controller) may be fooled in many things. One of
 +them is the vertical expansion of sprites. If you clear the expand flag and
 +then set it back straight away, VIC will think it has only displayed the
 +first one of the expanded lines. If we do the trick again, VIC will continue
 +to display the same data again and again. But why does VIC behave like this ?
 +
 +
 +_Logic gates will tell the truth_
 +
 +It is not really a bug, but a feature. The hardware design to implement the
 +vertical enlargement was just as simple as possible. Those, who do not care
 +about hardware should skip this part... The whole y-enlargement is handled
 +with five simple logical ports. Each sprite has an associated Set-Reset
 +flip-flop to tell whether to jump to the next sprite line (add three bytes
 +to the data counter) or not.
 +
 +Let's call the state of the flip-flop Q and the inputs R (reset) and S (set).
 +The function of a SR flip-flop is quite simple: if R is one, Q goes to zero,
 +if S is one, Q goes to one. Otherwise the state of the flip-flop does not
 +change. In this case the flip-flop is Set, if either the Y-enlargement bit
 +is zero or the state of the flip-flop is zero at the end of a scan line. The
 +flip-flop is reset, if both the state and the Y-enlargement are ones at the
 +end of the line.
 +
 +When you clear the bit in the vertical expansion register, the flip-flop will
 +be set regardless of the electron beam position on the scan line. If you
 +set the bit again before the end of the line, the flip-flop will be cleared
 +and VIC will be displaying the same sprite line again. In other words, VIC
 +will think that it is starting to display the second line of the expanded
 +sprite row. This way any of the lines in any of the sprites may be stretched
 +as wanted.
 +
 + .---- Current flipflop state (if one, enables add to sprite pointer)
 +  .---- Y-expansion bit.
 +  |  .--- End of line pulse (briefly one at end of line)
 +  |  |  .--- Next state (What state will become under these conditions)
 +  |  |  |
 +  0  0  1
 +  0  1  1
 +  1  0  no change
 +  1  1  1
 +  0  0  1             Clear $D017 -> flip-flop is set
 +  0  1  1
 +  1  0  no change     Set $D017   -> flip-flop resets at the end of line
 +  1  1  0
 +
 +So, simply, at any time, if vertical expand is zero, the add enable is set
 +to one. At the end of the line - before adding - the state is cleared if
 +vertical expand is one.
 +
 +
 +_Even odder ?_
 +
 +Something very weird happens when we clear the expansion bit right when VIC
 +is adding three to the sprite image counters. The values in the counters will
 +be increased only by two, and the data is then read from the wrong place.
 +
 +Normally the display of a sprite ends when VIC has shown all of the 21
 +lines of the sprite (the counter will end up to $3f). If there has been a
 +counter mixup, $3f is not reached after 21 lines and VIC will go on counting
 +and will display the sprite again, now normally. If we fool the counter only
 +once, the counter value $3f is reached when the sprite is displayed twice.
 +
 +
 +_Fiddling_
 +
 +I don't think the distorted counter effect can be used for anything, but
 +there is many things where the variable stretching could be used. When you
 +open the borders, you can be sure that there is a constant amount of time,
 +if you stretch the sprites to the whole lenght of the area. You may stretch
 +only the first and last lines, stretch the other lines by a constant or
 +using a table, or using a variable table or any of the combinations possible.
 +
 +
 +_A raster routine is a must_
 +
 +Because you have to access the VIC registers on each line during the stretch,
 +you need some kind of routine which can do other kinds of tricks besides the
 +stretch. You can open the side borders and change the background color and
 +maybe you have to shift the screen (and the bad lines with it) downwards.
 +[See previous C=Hacking Issues for talk about raster interrupts.]
 +
 +Look at the demo program. In the beginning of the raster routine there is
 +first some timing, then a loop that lasts exactly 46 clock cycles. It takes
 +exactly one scan line to execute. Inside the loop we first do the necassary
 +modifications to the vertical scroll register, then we change the background
 +color and then we open the side borders. And finally we handle the stretching
 +using the stretch data, where a zero-bit means that the corresponding sprite
 +will be stretched. A one-bit means that VIC is allowed to go to the next line
 +of the sprite data.
 +
 +
 +_Stretching takes time_
 +
 +Besides showing the stretched sprites we need time to generate the stretching
 +data, unless of course, the stretch is constant. We have to have 20
 +one-bits for each sprite in our table. It is not feasible to determine the
 +state of each byte in the table, instead you clear the table and plot the
 +needed bits.
 +
 +The routine is quite straightforward, but many optimizations may be applied
 +to make it faster. First we load Y with the stretch of the first line (the
 +y-coordinate of the data). Then we use it as an index to the table and plot
 +the right bit and increase Y with the expansion value. Then we do it again
 +until we have all of the 20 bits scattered to the table. The last sprite line
 +will then stretch until we stop the stretching, because the last line is
 +not allowed to be drawn.
 +
 +
 +_Speed is everything_
 +
 +The calculation itself is easy, but optimizing the routine is not. If all
 +of the sprites are stretched equally (by integer amounts) and from the same
 +position, the routine is the fastest possible.  You can also have variable
 +and smooth stretch.  Smooth stretch uses other than integer expansion values
 +and thus also needs more processor time.  If each sprite has to be stretched
 +individually, you need much more time to do it.
 +
 +The fastest routine I have ever written uses some serious selfmodification
 +tricks. There are also some other tricks to speed up the stretch, but they
 +are all secret ones.. :-)  Well, what the h*ck, I will include it anyway.
 +By the time you read this I have already made a faster routine..
 +
 +You can speed up that routine (by 17%) by unrolling the inner loop, but you
 +have to use a different addressing mode for ORA (zero-page). You also need
 +to place some restrictions to the tables used.. If you unroll both loops,
 +you can get ~25% faster routine than the Fore!-version.
 +
 +
 +_Demo program_
 +
 +I tried to collect all of the main principles of stretching and raster
 +routines to the demo program. I use the term "raster routine" when the
 +execution is tightly synchronized to the electron beam and to the screen
 +display. The program may be unclear in places, but I wanted to keep it as
 +short as possible. The routine opens the side borders, scrolls the screen
 +vertically, changes the background color and stretches the sprites.
 +
 +The stretcher routine allows different y-position and amount of expansion
 +for each sprite. This routine uses 1/8 fractions to do the counting, and so
 +it is much too slow to use in a real demo.  VIC registers are initialized
 +from a table, instead of setting them separately. Interrupt position is one
 +line above the sprites. The program does not open the top or bottom borders.
 +(I usually use a NMI to open the vertical borders, so that I only need to
 + use one raster-IRQ position.)
 +
 +I tried to make a NTSC version, but I couldn't get it to synchronize.
 +There are also less cycles available so you can't stretch all of the sprites
 +individually in NTSC (with this routine that is..).
 +
 +--------------------------------------------------------------------------
 +Fast-stretch from Megademo92 (part: Fore!)
 +
 +SINPOS          Stretch sinus index
 +SINSPEED        Stretch sinus index speed
 +YSINPOS         Y-sinus index
 +YSINSPEED       Y-sinus index speed
 +MASK            Bit mask for passess (usually $01,$02,$04,$08,$10..)
 +
 +YSINUS          Y-sinus table
 +STRETCH         Sprite line sizes   (LSB of the address must be 0)
 +SIZET           Sprite size/2 table (LSB of the address must be 0)
 +DATA            Stretch data table (cleared before this routine)
 +
 +[xx] marks selfmodification. For example loop counter, bit mask and
 +index to the stretch and size data tables are stored straight in the
 +code.
 +
 +0b90    lda #$06        ; Number of sprites-1 (here I used only 7 sprites)
 +0b92    sta $0b96
 +0b95    ldx #$[ff]      ; Load counter
 +0b97    clc             ; Clear carry for adc
 +0b98    lda SINPOS,   ; Stretch sinus position
 +0b9b    sta $0bd1       ; Set low bytes of indices
 +0b9e    sta $0bb8
 +0ba1    adc SINSPEED, ; Add stretch sinus speed (carry is not set)
 +0ba4    and #$7f        ; Table is 128 bytes (twice)
 +0ba6    sta SINPOS,   ; Save new sinus position
 +0ba9    lda YSINPOS,  ; Get the Y sinus position
 +0bac    adc YSINSPEED,x ; Add Y sinus speed
 +0baf    sta YSINPOS,  ; Save new Y sinus position
 +0bb2    tay             ; Position to index register
 +0bb3    lda YSINUS,   ; Get Y-position from table (can be 256 bytes long)
 +0bb6    sec             ; adc either sets or clears carry, we have to set it
 +0bb7    sbc SIZET[1e]   ; Subtract size of the sprite/2 to get the sprite
 +0bba    clc             ;  to stretch from the middle.
 +0bbb    tay             ; MaxSize/2 < Y-sinus < AreaHeight-MaxSize/2
 +0bbc    lda MASK,     ; Get the ora-mask for this pass
 +0bbf    sta $0bcb       ; Store mask
 +0bc2    sta $0bdb
 +0bc5    ldx #$13        ; 19 lines here + 1 after
 +0bc7    lda DATA,     ; Load & ora-mask & store
 +0bca    ora #[$01]
 +0bcc    sta DATA,y
 +0bcf    tya
 +0bd0    adc STRETCH[1e],x ; Add the stretch from the table (carry is not set)
 +0bd3    tay
 +0bd4    dex             ; decrease counter
 +0bd5    bne $0bc7       ; Do the 19 lines
 +0bd7    lda DATA,     ; Load & ora-mask & store the 20th line
 +0bda    ora #[$01]
 +0bdc    sta DATA,y
 +0bdf    dec $0b96       ; Next sprite(s)
 +0be2    bpl $0b95
 +0be4    rts
 +
 +Timings:
 +-------
 +clear 128 bytes: 514  + 12 cycles       8.16 lines
 +7 passes       : 3820 + 12 cycles       60.6 lines = 8.66 lines/pass
 +
 +The unrolled clear routine consists of one load (lda #$00) and 128
 +store instructions (sta $nnnn). 12 cycles are counted for JSR/RTS.
 +
 +Stretching of 8 sprites would take slightly less than 80 lines, which is one
 +fourth of the total raster time. Displaying a 128-line high stretcher takes
 +about 130 lines (counting sprite setup and synchronization), scroller couple
 +of lines more. Total 212 lines leaves 100 lines (6300 cycles) free for other
 +activities in a PAL system. In a NTSC system you would have only 50 lines
 +left.
 +
 +
 +A simple basic routine to create the stretch data:
 +-------------------------------------------------
 +a=0:for f=0 to 127:a=a+Height*(2+sin(f*PI/64)):poke Table+f,a:
 +poke Table+f+128,a:a=a-int(a):next f
 +
 +This will also handle the 'rounding'. Because of this we don't have to
 +handle fractions in the stretcher routine. The use of a table also gives the
 +opportunity to have a separate size for each sprite line. The table does
 +not need to be a sinus, it could have triangle or any other 'waveform' as
 +long as the minimum value in the table (sprite line size) is 1.
 +
 +
 +A basic routine to do the size/2 table:
 +--------------------------------------
 +a=0:for f=0 to 19:a=a+peek(Table+f):next f: rem get the size in position 0
 +for f=0 to 127:poke STable+f,a/2:a=a-peek(Table+f)+peek(Table+f+20):next f
 +
 +--------------------------------------------------------------------------
 +_Stretcher program_
 +
 +YSCROLL= $CF00 ; Vertical scroll table (moves bad lines)
 +STRETCH= $CF80 ; Stretch table
 +COLORS=  $CE80 ; Table for background colors
 +YCOORD=  $0380 ; Sprite y-positions (eight bytes)
 +HEIGHT=  $0388 ; Sprite stretches   (eight bytes)
 +YPOS=    52    ; Sprite y-coordinate
 +SPRCOL=  2     ; Sprite colors
 +
 +
 +*= $C000
 +
 +        SEI             ; Disable interrupts
 +        LDA #$7F
 +        STA $DC0D       ; Disable timer interrupts
 +        LDA #<IRQ       ; Our own interrupt handler
 +        STA $0314
 +        LDA #>IRQ
 +        STA $0315
 +        LDX #$3E        ; We create a sprite to cassette buffer
 +LOOP    LDA SPRITE,X
 +        STA $0340,X
 +        DEX
 +        BPL LOOP
 +        LDX #7
 +LOOP2   LDA #$D         ; Set the sprite image pointers
 +        STA $07F8,X
 +        LDA #SPRCOL     ; Set sprite colors
 +        STA $D027,X
 +        DEX
 +        BPL LOOP2
 +        LDX #$26
 +LOOP3   LDA VIDEO,    ; Init VIC
 +        STA $D000,X
 +        DEX
 +        BPL LOOP3
 +        LDX #$7F        ; Create the y-scroll table
 +LOOP4   TXA             ;  and clear the color table
 +        AND #$07
 +        ORA #$10        ; Non-blank screen
 +        STA YSCROLL,X
 +        LDA #$00
 +        STA COLORS,X
 +        DEX
 +        BPL LOOP4
 +        STA $3FFF
 +        LDX #23         ; Create a color table
 +LOOP5   LDA BACK,X
 +        STA COLORS+8,X
 +        STA COLORS+32,X
 +        STA COLORS+56,X
 +        STA COLORS+80,X
 +        STA COLORS+96,X
 +        DEX
 +        BPL LOOP5
 +        JSR CHANGE      ; Init sprite sizes and y-positions
 +        CLI             ; Enable interrupts
 +        RTS
 +
 +IRQ     LDX #$01
 +        LDY #$08        ; 'normal' $D016
 +        NOP             ; Timing
 +        NOP
 +        NOP
 +        BIT $EA         ; (Add NOP's etc. for NTSC)
 +LOOP6   LDA YSCROLL-1,X ; Move the screen (bad lines)      5
 +        STA $D011                                          4
 +        LDA COLORS,   ; Load the background color        4
 +        DEC $D016       ; Open the border                  6
 +        STA $D021       ; Set the background color         4
 +        STY $D016       ; Screen to normal                 4
 +        LDA STRETCH,  ; Stretch the sprites              4
 +        STA $D017                                          4
 +        EOR #$FF                                           2
 +        STA $D017                                          4
 +                        ; (Add NOP for NTSC     +2)
 +        INX             ; Increase counter                 2
 +        BPL LOOP6       ; Loop 127 times                 + 3
 +                                                         ---
 +        LDA #1          ; Ack the raster interrupt       =46
 +        STA $D019                                        +17(sprites)
 +                                                         ---
 +        JSR DOSTRETCH   ; New stretch                    =63(whole)
 +
 +        JMP $EA31
 +
 +SPRITE  BYT 0,0,0,3,$FB,0,7,$7E          ; An Example sprite
 +        BYT 0,$35,$DF,0,$1D,$77,0,$B7
 +        BYT $5D,0,$BD,$83,$7E,$EF,1,$DE
 +        BYT $BB,1,$78,$AE,3,$70,$EB,0
 +        BYT 0,$BA,3,$60,$EE,3,$D8,$FB
 +        BYT 2,$F6,$FE,$83,$BD,$9F,$BA,0
 +        BYT $37,$EE,0,$3D,$FB,0,7,$7E
 +        BYT 0,3,$DF,0,0,0,0
 +
 +VIDEO   BYT $E8,YPOS,$20,YPOS,$50,YPOS,$80,YPOS,$B0,YPOS
 +        BYT $E0,YPOS,$10.YPOS,$40,YPOS,$C1,$18,YPOS-1,0,0
 +        BYT $FF,8,$FF,$15,1,1,$FF,$FF,$FF,0,0,0,0,0,0,0,1,10
 +        ; Init values for VIC - sprites, interrupts, colors
 +
 +BACK    BYT 0,$B,$C,$F,1,$F,$C,$B   ; Example color bars
 +        BYT 0,6,$E,$D,1,$D,$E,6
 +        BYT 0,9,2,$A,1,$A,2,9
 +
 +DOSTRETCH
 +        LDX #31            ; Clear the table
 +        LDA #0             ; (Unrolling will help the speed,
 +LOOP7   STA STRETCH,     ;  because STA nnnn,X is 5 cycles
 +        STA STRETCH+32,  ;  and STA nnnx is only 4 cycles.)
 +        STA STRETCH+64,X
 +        STA STRETCH+96,X
 +        DEX
 +        BPL LOOP7
 +        STA REMAIND+1      ; Clear the remainder
 +        LDA #7
 +        STA COUNTER+1      ; Init counter for 8 loops
 +        LDA #$80
 +        STA MASK+1         ; First sprite 7, mask is $80
 +COUNTER LDX #$00           ; The argument is the counter
 +        LDY YCOORD,      ; y-position
 +        LDA HEIGHT,      ; Height of one line (5 bit integer part)
 +        STA ADD+1
 +        LDX #20            ; Handle 20 lines
 +LOOP8   LDA STRETCH+2,Y
 +MASK    ORA #$00
 +        STA STRETCH+2,   ; Set a one-bit
 +        STY YADD+1
 +REMAIND LDA #0
 +        AND #7             ; Previous remainder
 +ADD     ADC #0             ;  add to the height
 +        STA REMAIND+1      ; Save the new value
 +        LSR
 +        LSR
 +        LSR
 +        CLC                ; Take the integer part
 +YADD    ADC #0
 +        TAY                ; New value to y-register
 +        DEX
 +        BNE LOOP8
 +        LSR MASK+1         ; Use new mask
 +        DEC COUNTER+1      ; Next sprite
 +        BPL COUNTER
 +
 +CHANGE  LDA #$00
 +        ASL                ; Sprite height changes with 2x speed
 +        AND #$3F
 +        TAY                ; 64 bytes long table
 +        INC CHANGE+1       ; Increase the counter
 +        LDX #7             ; Do eight sprites
 +LOOP9   LDA SINUS,Y
 +        LSR
 +        LSR
 +        CLC                ; Use the same sinus as y-data
 +        ADC #8
 +        STA HEIGHT,      ; Sprite height will be from 1 to 3 lines
 +        TYA
 +        ADC #10            ; Next sprite enlargement will be 10 entries
 +        AND #$3F           ;  from this
 +        TAY
 +        DEX
 +        BPL LOOP9
 +        LDX #7
 +        LDA CHANGE+1
 +        AND #$3F
 +        TAY
 +LOOP10  LDA SINUS,       ; Y-position
 +        STA YCOORD,X
 +        TYA
 +        ADC #10            ; Next sprite position is 10 entries from this one
 +        AND #$3F
 +        TAY
 +        DEX
 +        BPL LOOP10
 +        RTS
 +
 +SINUS   BYT $20,$23,$26,$29,$2C,$2F,$31,$34 ; A part of a sinus table
 +        BYT $36,$38,$3A,$3C,$3D,$3E,$3F,$3F
 +        BYT $3F,$3F,$3F,$3E,$3D,$3C,$3A,$38
 +        BYT $36,$34,$31,$2F,$2C,$29,$26,$23
 +        BYT $20,$1C,$19,$16,$13,$10,$E,$B
 +        BYT 9,7,5,3,2,1,0,0,0,0,0,1,2,3,5,7
 +        BYT 9,$B,$E,$10,$13,$16,$19,$1C
 +
 +--------------------------------------------------------------------------
 +Stretching sprites demo program basic loader (PAL)
 +
 +1 S=49152
 +2 DEFFNH(C)=C-48+7*(C>64)
 +3 CH=0:READA$,A:PRINTA$:IFA$="END"THENPRINT"<clr>":SYS49152:END
 +4 FORF=0TO31:Q=FNH(ASC(MID$(A$,F*2+1)))*16+FNH(ASC(MID$(A$,F*2+2)))
 +5 CH=CH+Q:POKES,Q:S=S+1:NEXT:IFCH=ATHEN3
 +6 PRINT"CHECKSUM ERROR":END
 +100 DATA 78A9648D1403A9C08D1503A23EBD96C09D4003CA10F7A207A90D9DF807A9029D, 3614
 +101 DATA 27D0CA10F3A226BDD5C09D00D0CA10F7A27F8E0DDC8A290709109D00CFA9009D, 3897
 +102 DATA 80CECA10F08DFF3FA217BDFCC09D88CE9DA0CE9DB8CE9DD0CE9DE0CECA10EB20, 5281
 +103 DATA 67C15860A201A008EAEAEA24EABDFFCE8D11D0BD80CECE16D08D21D08C16D0BD, 4699
 +104 DATA 80CF8D17D049FF8D17D0E810E0EE19D02014C14C31EA00000003FB00077E0035, 3394
 +105 DATA DF001D7700B75D00BD837EEF01DEBB0178AE0370EB0000BA0360EE03D8FB02F6, 3628
 +106 DATA FE83BD9FBA0037EE003DFB00077E0003DF00000000E834203450348034B034E0, 3015
 +107 DATA 3410344034C118330000FF08FF150101FFFFFF00000000000000010A000B0C0F, 1859
 +108 DATA 010F0C0B00060E0D010D0E060009020A010A0209A21FA9009D80CF9DA0CF9DC0, 1876
 +109 DATA CF9DE0CFCA10F18D4DC1A9078D35C1A9808D45C1A200BC8003BD88038D51C1A2, 4314
 +110 DATA 14B982CF09009982CF8C5AC1A900290769008D4DC14A4A4A186900A8CAD0E24E, 3430
 +111 DATA 45C1CE35C110CDA9000A293FA8EE68C1A207B99EC14A4A1869089D880398690A, 3474
 +112 DATA 293FA8CA10ECA207AD68C1293FA8B99EC19D800398690A293FA8CA10F1602023, 3622
 +113 DATA 26292C2F313436383A3C3D3E3F3F3F3F3F3E3D3C3A383634312F2C292623201C, 1654
 +114 DATA 191613100E0B09070503020100000000000102030507090B0E101316191C0000, 296
 +200 DATA END,0
 +
 +--------------------------------------------------------------------------
 +Uuencoded C64 exutable for stretching sprites (PAL)
 +
 +begin 644 stretch.64
 +M`0@-"`$`4[(T.3$U,@`F"`(`EJ5(*$,ILD.K-#BJ-ZPH0[$V-"D`40@#`$-(?
 +MLC`ZAT$D+$$ZF4$D.HM!)+(B14Y$(J>9(I,B.IXT.3$U,CJ``(@(!`"!1K(P/
 +MI#,Q.E&RI4@HQBC**$$D+$:L,JHQ*2DIK#$VJJ5(*,8HRBA!)"Q&K#*J,BDI:
 +M*0"I"`4`0TBR0TBJ43J74RQ1.E.R4ZHQ.H(ZBT-(LD&G,P#!"`8`F2)#2$5#F
 +M2U-532!%4E)/4B(Z@``."60`@R`W.$$Y-C0X1#$T,#-!.4,P.$0Q-3`S03(S3
 +M14)$.39#,#E$-#`P,T-!,3!&-T$R,#=!.3!$.41&.#`W03DP,CE$+"`S-C$TP
 +M`%L)90"#(#(W1#!#03$P1C-!,C(V0D1$-4,P.40P,$0P0T$Q,$8W03(W1CA%4
 +M,$1$0SA!,CDP-S`Y,3`Y1#`P0T9!.3`P.40L(#,X.3<`J`EF`(,@.#!#14-!B
 +M,3!&,#A$1D8S1D$R,3="1$9#0S`Y1#@X0T4Y1$$P0T4Y1$(X0T4Y1$0P0T4Y1
 +M1$4P0T5#03$P14(R,"P@-3(X,0#U"6<`@R`V-T,Q-3@V,$$R,#%!,#`X14%%?
 +M045!,C1%04)$1D9#13A$,3%$,$)$.#!#14-%,39$,#A$,C%$,#A#,39$,$)$G
 +M+"`T-CDY`$(*:`"#(#@P0T8X1#$W1#`T.49&.$0Q-T0P13@Q,$4P144Q.40P4
 +M,C`Q-$,Q-$,S,45!,#`P,#`P,#-&0C`P,#<W13`P,S4L(#,S.30`CPII`(,@V
 +M1$8P,#%$-S<P,$(W-40P,$)$.#,W145&,#%$14)",#$W.$%%,#,W,$5",#`P<
 +M,$)!,#,V,$5%,#-$.$9",#)&-BP@,S8R.`#<"FH`@R!&13@S0D0Y1D)!,#`SN
 +M-T5%,#`S1$9",#`P-S=%,#`P,T1&,#`P,#`P,#!%.#,T,C`S-#4P,S0X,#,TX
 +M0C`S-$4P+"`S,#$U`"D+:P"#(#,T,3`S-#0P,S1#,3$X,S,P,#`P1D8P.$9&B
 +M,34P,3`Q1D9&1D9&,#`P,#`P,#`P,#`P,#`P,3!!,#`P0C!#,$8L(#$X-3D`<
 +M=@ML`(,@,#$P1C!#,$(P,#`V,$4P1#`Q,$0P13`V,#`P.3`R,$$P,3!!,#(PK
 +M.4$R,49!.3`P.40X,$-&.41!,$-&.41#,"P@,3@W-@##"VT`@R!#1CE$13!#0
 +M1D-!,3!&,3A$-$1#,4$Y,#<X1#,U0S%!.3@P.$0T-4,Q03(P,$)#.#`P,T)$G
 +M.#@P,SA$-3%#,4$R+"`T,S$T`!`,;@"#(#$T0CDX,D-&,#DP,#DY.#)#1CA#=
 +M-4%#,4$Y,#`R.3`W-CDP,#A$-$1#,31!-$$T03$X-CDP,$$X0T%$,$4R-$4LQ
 +M(#,T,S``70QO`(,@-#5#,4-%,S5#,3$P0T1!.3`P,$$R.3-&03A%138X0S%!C
 +M,C`W0CDY14,Q-$$T03$X-CDP.#E$.#@P,SDX-CDP02P@,S0W-`"J#'``@R`RJ
 +M.3-&03A#03$P14-!,C`W040V.$,Q,CDS1D$X0CDY14,Q.40X,#`S.3@V.3!!\
 +M,CDS1D$X0T$Q,$8Q-C`R,#(S+"`S-C(R`/<,<0"#(#(V,CDR0S)&,S$S-#,V*
 +M,S@S03-#,T0S13-&,T8S1C-&,T8S13-$,T,S03,X,S8S-#,Q,D8R0S(Y,C8R+
 +M,S(P,4,L(#$V-30`0PUR`(,@,3DQ-C$S,3`P13!",#DP-S`U,#,P,C`Q,#`PR
 +M,#`P,#`P,#`Q,#(P,S`U,#<P.3!",$4Q,#$S,38Q.3%#,#`P,"P@,CDV`$\-E
 +,R`"#($5.1"PP````>
 +``
 +end
 +size 1362
 +
 +=============================================================================
 +</code>
 +====== Rob Hubbard's Music: Disassembled, Commented and Explained ======
 +<code>
 +by Anthony McSweeney (u882859@postoffice.utas.edu.au)
 +
 +[Ed's Note: I questioned this article concerning copyright problems and he
 +has assured me that it is legal to present it in entirity like this as it is
 +past a certain # of years. Accordingly I'm presenting it and any concerns
 +should be taken up with him and not myself.]
 +
 +Introduction:
 +************
 +
 +  How do you introduce someone like Rob Hubbard?? He came, he saw and he
 +conquered the '64 world. In my estimation, this one man was resposible for
 +selling more '64 software than any other single person. Hell! I think that Rob
 +Hubbard was responsible for selling more COMMODORE 64's than any other person!
 +I certainly bought my '64 after being blown away by the Monty on the Run music
 +in December 1985. In the next few years, Rob would totally dominate the '64
 +music scene, releasing one hit after another. I will even say that some really
 +terrible games sold well only on the strength of their brilliant Rob Hubbard
 +music (eg. KnuckleBusters and W.A.R.).
 +
 +  So how did Rob achieve this success? Firstly (of course) he is a superb
 +composer and musician, able to make the tunes that bring joy to our hearts
 +everytime we hear them! (also consider the amazing diversity of styles of
 +music that Rob composed). Secondly, he was able to make music which was suited
 +to the strengths and limitations of the SID chip. Just recall the soundfx used
 +at the beginning of Thrust, or in the Delta in-game music. Perhaps the biggest
 +limitation of SID must be the meagre 3 channels that can be used, but most
 +Hubbard songs appear to have four, five or even more instruments going (just
 +listen to the beginning of Phantoms of the Asteriods for example... that's
 +only one channel used!!). I could really go on for (p)ages identifying the
 +outstanding things that Rob Hubbard did, so I will finally mention that Rob's
 +coding skills and his music routines were a major factor in his success.
 +
 +
 +The First Rob Hubbard Routine:
 +*****************************
 +
 +  Rob Hubbard created a superb music routine from the very first tune which
 +was released (Confuzion). Furthermore, Rob used this routine to make music
 +for a very long time, only changing it _slightly_ over time. The sourcecode
 +that I present here was used (with slight modifications) in: Confuzion, Thing
 +on a Spring, Monty on the Run, Action Biker, Crazy Comets, Commando, Hunter
 +Patrol, Chrimera, The Last V8, Battle of Britain, Human Race, Zoids, Rasputin,
 +Master of Magic, One Man & His Droid, Game Killer, Gerry the Germ, Geoff Capes
 +Strongman Challenge, Phantoms of the Asteroids, Kentilla, Thrust,
 +International Karate, Spellbound, Bump Set and Spike, Formula 1 Simulator,
 +Video Poker, Warhawk or Proteus and many, many more! All you would need to do
 +to play a different music is to change the music data at the bottom, and a few
 +lines of the code.
 +
 +  This particular routine has been ripped off by many famous groups and
 +people over the years, but I don't think that they were ever generous enough
 +to share it around. Can you remember The Judges and Red Software?? They made
 +the famous Red-Hubbard demo, and used it in Rhaa-Lovely and many of their
 +other productions. I'm sure that the (Atari) ST freaks reading this will love
 +Mad Max (aka Jochen Hippel), and remember the BIG demo which featured approx
 +100 Rob Hubbard tunes converted to the ST. Although I hate to admit it, I
 +decided to start sharing around my own sourcecode after receiving the amazing
 +Protracker sourcecode (340K!) on the Amiga (thanks Lars Hamre). That made me
 +shameful to be selfish, especially after I learned alot of from it. Why don't
 +YOU share around your old sourcecodes too!
 +
 +  The particular routine that is included below was ripped from Monty on the
 +Run, and it appeared in memory from $8000 to about $9554. The complete
 +routine had code for soundfx in it, which I have taken out for the sake of
 +clarity. Although the routine is really tiny - a mere 900 or 1000 bytes of
 +code, there are some amazingly complex concepts in it which require alot of
 +explanation if you don't know much about computer music or SID. Fortunately
 +for you, I have put in excellent label names for you, and also alot of really
 +helpful and amazing comments. In fact, I think this sourcecode must have a
 +much better structure and comments than Rob Hubbard's original!!! I think that
 +the best way to understand the sourcecode is to study it, and figure out what
 +is going on using the comments.
 +
 +  In addition to the comments in the source, there are *3* descriptions of
 +the routine in this article. The first tells you how to use the music routine
 +when it's viewed as an already assembled 'module'. The second goes through an
 +overview of the music and instrument data format, and is great for getting an
 +overall feel for what the code is doing. The third description looks at the
 +various sections of the code, and how they come together.
 +
 +
 +How to use the sourcecode:
 +*************************
 +
 +    jsr    music+0 to initialize the music number in the accumulator
 +    jsr    music+3 to play the music
 +    jsr    music+6 to stop the music and quieten SID
 +
 +  The music is supposed to run at 50Hz, or 50 times per second. Therefore
 +PAL users can run the music routine off the IRQ like this:
 +
 +    lda    #$00     ; init music number 0
 +    jsr    music+0
 +    sei             ; install the irq and a raster compare
 +    lda    #<irq
 +    ldx    #>irq
 +    sta    $314
 +    stx    $315
 +    lda    #$1b
 +    sta    $d011
 +    lda    #$01
 +    sta    $d01a
 +    lda    #$7f
 +    sta    $dc0d
 +    cli
 +loop =*
 +    jmp    loop     ; endless loop (music is now playing off interrupt :-)
 +
 +irq =*
 +    lda    #$01
 +    sta    $d019
 +    lda    #$3c
 +    sta    $d012
 +
 +    inc    $d020    ; play music, and show a raster for the time it takes
 +    jsr    music+3
 +    dec    $d020
 +
 +    lda    #$14
 +    sta    $d018
 +    jmp    $ea31
 +
 +  If this method is used on NTSC machines, then the music will be running at
 +60Hz and will sound much to fast - possibly it might sound terrible. I'm
 +afraid you'll have to put up with this unless YOU are good enough to make a
 +CIA interrupt at 50Hz. As I havn't had to worry about NTSC users before,
 +perhaps someone will send me the best way to do this...
 +
 +[Ed. Note: You could also keep a counter for the IRQ and don't execute it
 +every 6 interrupt. This will make it the right speed although the best
 +solution is for modifying the CIA to 50Hz like he mentions above.]
 +
 +How the music data is arranged:
 +******************************
 +
 +1. The music 'module' contains one or more songs.
 +
 +  Each RH music is made up of a 'bunch' of songs in a single module. Thus
 +the 'module' can have the title music, in-game music, and the game-over music
 +all using the same playroutine (and even the same instruments :). The source
 +that appears below only has the one song in it, and the music number is
 +automatically set to 0 as a result (line 20). The label 'songs' is where you
 +want to look for the pointers to the songs if you want to change this.
 +
 +2. Each song is made up of three tracks.
 +
 +  We all know that there are only 3 channels on the SID chip, so there are
 +also 3 tracks - one for each channel. When I said 'pointers to the songs'
 +above, I was therefore referring to 'pointers to the three tracks that make up
 +the song'...hence we are looking at the label 'songs' again. Each track needs
 +a high and low pointer, so there are 6 bytes needed to point to a song.
 +
 +3. Each track is made up of a list of pattern numbers
 +
 +  Each track consists of a list of the pattern numbers in the order in which
 +they are to be played. Here we are looking at the labels 'montymaintr1' and
 +'montymaintr2'and 'montymaintr3'. Therefore I can tell you that the initial
 +patterns played in this song are $11, $12 and $13 on channels 1,2 and 3
 +respectively. The track is either ended with a $ff or $fe byte. A $ff means
 +that the song needs to be looped when the end of the track is reached (like
 +the monty main tune), while a $fe means that the song is only to be played
 +once. The current offset into the track is often called the current POSITION
 +for that track.
 +
 +4. A pattern consists of a sequence of notes.
 +
 +  A pattern contains the data that says when the notes should be played, how
 +long they should be played for, at what pitch, with what instrument, should
 +there be ADSR, should there be bending (portamento) of the notes etc. Each
 +pattern is ended with a $ff byte, and when this is encountered, the next
 +pattern in the track will be played. Each note has up to a 4 byte
 +specification.
 +
 +- The first byte is always the length of the note from 0-31 or 0-$1f in hex.
 +  You will notice that the top three bits are not used for the length of the
 +  note, so they are used for other things.
 +- Bit#5 signals no release needed. - Bit#6 signals that this note is
 +  appended to the last one (no attack/etc). - Bit#7 signals that a new
 +  instrument or portamento is coming up.
 +
 +- The second byte is an optional byte, and it holds the instument number to
 +  use or the portamento value (ie a bended note). This byte will be needed
 +  according to whether bit#7 of the first byte is set or not...ie if the 1st
 +  byte was negative, then this byte is needed.
 +  - If the second byte is positive, then this is the new instrument number.
 +  - If the second byte is negative, then this is a bended note (portamento).
 +    and the value is the speed of the portamento (except for bits #7 and #0)
 +    Bit #0 of the portamento byte determines the direction of the bend.
 +    - Bit#0 = 0 then portamento is up.
 +    - Bit#0 = 1 then portamento is down.
 +
 +- The third byte of the specification is the pitch of the note. A pitch of
 +  0 is the lowest C possible. A pitch of 12 or $C(hex) is the next highest
 +  C above that. These pitches are denoted fairly universally as eg. 'C-1' and
 +  for sharps eg. 'D#3'. Notice that this routine uses pitches of higher than
 +  72 ($48) which is c-6 :-)
 +
 +- The fourth byte if it exists will denote the end of the pattern.
 +  ie. If the next byte is a $ff, then this is the end of the pattern.
 +
 +NOTE: I have labelled the various bytes with numbers for convenience. Bear
 +in mind that some of these are optional, so if the second byte is not needed,
 +then I will say that the pitch of the note coming up is the 'third byte',
 +even though it isn't really.
 +
 +Okay, here are some examples:
 +
 +eg. $84,$04,$24 means that the length of the note is 4 (from the lower 5 bits
 +    of the first byte), that the instrument to use is instrument number 4
 +    (the second byte, as indicated by bit #7 of the first byte), and that the
 +    pitch of the note is $24 or c-3.
 +eg. $D6,$98,$25,$FF means that the length of the note is 22 ($16), that this
 +    note should be appended to the last, that the second byte is a portamento
 +    (as both 1st and 2nd bytes -ve!), that the portamento is going up (as
 +    bit#0 = 0) with a speed of 24 ($18), that the pitch of the note is $25
 +    or c#3, and that this is the end of the pattern.
 +
 +It doesn't get any harder than that!! Did you realise that this is exactly
 +the way that Rob Hubbard made the music!! He worked out some musical ideas
 +on his cheap (musical) keyboard, and typed the notes into his assembler in
 +hex, just like this.
 +
 +
 +5. The instruments are an 8 byte data structure.
 +
 +  You are looking at the label 'instr' at the bottom of the sourcecode. The
 +8 bytes which come along first are instrument numnber 0, the next 8 define
 +instrument number 1, etc. Here are the meanings of the bytes, but I suggest
 +that you check out your programming manuals if you are unfamiliar with these:
 +
 +- Byte 0 is the pulse width low byte, and
 +- Byte 1 is the pulse width high byte. (also see byte 7).
 +
 +- Byte 2 is the control register byte.
 +  This specifies which type of sound should be used; sine, sawtooth etc.
 +
 +- Byte 3 is the attack and decay values, and
 +- Byte 4 is the sustain and release values.
 +  The note's volume is altered according to these values. When the attack and
 +  decay are over, the volume of the note is held at the sustain level. When
 +  length of a note is over, a release is done through the 'gate' bit in SID.
 +
 +- Byte 5 is the vibrato depth for the instrument.
 +
 +- Byte 6 is the pulse speed.
 +  Timbre is created by changing the shape of the waveform each 50th of a
 +  second, and this is the most common way of achieving it. The shape of
 +  the pulse waveform changes from square to a very rectangular at a speed
 +  according to this byte.
 +  N.B. if you are interested in how the pulse value number works, then
 +  e-mail me sometime, as I found this out (exhaustively) a few days ago!
 +
 +- Byte 7 is the instrument fx byte, and is the major thing which changes
 +  between different music routines. Each bit in this byte determines whether
 +  this instrument will have a certain effect in it.
 +  - Bit#0 signals that this is a drum. Drums are made from a noise channel
 +    and also a fast frequency down, with fast decay. Bass drums use a square
 +    wave, and only the first 50th of a second is a noise channel. This is
 +    the tell-tale instrument that gives away a Rob Hubbard routine! Hihats
 +    and other drums use noise all the time.
 +  - Bit#1 signals a 'skydive'. This is a slower frequency down, that I think
 +    sounds like somebody yelling as they fall out of a plane .. AHHHHhhhhgh..
 +    ..hence I call it a skydive!!
 +  - Bit#2 signals an octave arpeggio. It's a very limited arpeggio routine in
 +    this song. Listen for the arpeggio and the skydive when combined, which
 +    is used alot in Hubbard songs.
 +  - All the other bits have no meaning in this music, but were used alot in
 +    later music for the fx.
 +
 +A big reason that I presented this early routine, was because there was not
 +too much in the way of special fx to confuse you. As a result, you can
 +concentrate on the guts of the code instead of the special fx :-)
 +
 +
 +How the sourcecode works:
 +************************
 +
 +  The routines at the top of the sourcecode are concerned with turning the
 +music on and off, and you will see that this is done through a variable called
 +'mstatus' (or music status). If mstatus is set to $C0, then the music is
 +turned off and SID is quietened, thereafter mstatus is set to $80 which means
 +that the music is still off, but SID doesn't need to be quietened again. When
 +the music is initialized, then mstatus is given a value of $40 which kicks in
 +the further initialization stuff. If mstatus is any other value, then the
 +music is being played. For any of the initialization stuff to have any meaning
 +to you, you ofcourse have to understand the rest of the playroutine :-)
 +
 +  After we have got past the on/off/init stuff, we are at the label called
 +'contplay' at around line 100. The first thing you should notice is that this
 +is the start of a huge loop that is done *3* times - once for each channel.
 +The loop really *is* huge, as it ends right on the last few lines of the code
 +:-)
 +
 +  Now that we are talking about routines within the loop, we are talking about
 +these routines being applied to the channels independantly. There are 2 main
 +routines within the loop, one is called NoteWork, and the other is called
 +SoundWork. NoteWork checks to see whether a new note is needed on this
 +channel, and if it is, then the notedata is fetched and stuff is initialized.
 +If no note is needed, then SoundWork is called which processes the instruments
 +and does the portamento.
 +
 +  NoteWork first checks the speed at which the notes are fetched. If the delay
 +is still occurring, then new notes are not needed and soundwork is called.
 +N.B. that the speed for Monty on the Run is 1, which means that a note of
 +length $1f will last for 64 calls to the routine (ie just over a second). If
 +the speed of the song is reset, then NoteWork decrements the length of the
 +current note. When the length of the current note hits $ff (-1) then a new
 +note is needed, otherwise SoundWork is jumped to.
 +
 +  The data for a new note is collected at the label 'getnewnote'. In the
 +simplest case, this involves getting the next bytes of data from the current
 +pattern on this channel, but if the end of the pattern is reached, then the
 +next pattern number is fetched by reference to the current position within
 +this channel's track. In an even more complex situation, the end of a track is
 +reached, and the current position needs to be reset to 0 before the next
 +pattern number can be found.
 +
 +  You can see quite clearly in this part of the routine where the length of
 +the note is collected, and it is determined whether a 2nd byte is needed,
 +where the pitch is collected, and the end of the song checked. You can also
 +see where some of the data is collected from the current instrument and jammed
 +into the SID registers.
 +
 +  SoundWork is called if no new notes are needed, and it processes the
 +instruments and does the portamento etc. This part of the routine is neatly
 +expressed in sections which are really well commented and quite easy to
 +understand.
 +
 +- The first thing that occurs in SoundWork is that the 'gate bit' of SID is
 +  set when the length of the note is over - this causes a release of the note.
 +
 +- The vibrato routine is quite inefficient, but it's pretty good for 1985!
 +  Ofcourse vibrato is implemented by raising and lowering the pitch of the
 +  note ever-so-slightly causing the note to 'float'. The amount of the
 +  vibrato is determined in the current instrument.
 +
 +- The pulsework routine changes the pulsewidth between square wave and very
 +  rectangular wave according to the pulsespeed in the current instrument.
 +  (ie. it changes the sound of the instrument and thus alters the 'timbre')
 +  The routine goes backwards and forwards between the two; and switches
 +  when one extremity is reached. It's interesting to note that the current
 +  values of the pulse width are actually stored in the instrument :-)
 +
 +- Portamento is achieved by adding/subtracting an amount of frequency to
 +  the current frequency each time this part of the routine is called.
 +
 +- The instrument fx routines are also really easy to figure out, as they are
 +  well commented. Both the drums and the skydive do a very fast frequency
 +  down, so it is the most significant byte of the frequency which is reduced
 +  .. and not 16-bit maths (math?!) The arpeggio is only an octave arpeggio,
 +  so for the first 50th of a second, the current note is played, and for
 +  the next 50th of a second, current note+12 is played, followed by the
 +  current note again etc.
 +  ( If you don't know what an arpeggio is..it's generally when the notes of )
 +  ( a chord are played individually in a rapid succession. It produces a    )
 +  ( 'full' sound depending on the speed of the arpeggio. In most cases the  )
 +  ( note is changed 50 times per second, which gives a very nice sound. If  )
 +  ( you have listened to some computer music, then you will have definately )
 +  ( listened to an arpeggios all the time, even if you don't realize it!    )
 +
 +
 +Final Thoughts:
 +**************
 +
 +  *Bounce* I'm finally near the end of this article! It has been alot of work
 +to try to explain this routine, but I'm glad that I've done it *grin* If you
 +have any questions then please feel free to e-mail me, or even e-mail Craig if
 +it's after August 1993 and I'll make sure that I leave a forwarding address
 +with him. Also, please feel free to e-mail me and tell me what you think of
 +this article. I will only be bothered writing more of the same if I know that
 +someone is finding them useful/interesting. Also e-mail me if you are
 +interested in Amiga or ST music too, as I've done alot on both of those
 +machines.
 +
 +  I'm not sure whether Craig will be putting the actual sourcecode below this
 +text, or in some kind of Appendix. In either case, I SHALL take all legal
 +responsibilty for publishing Rob Hubbard's routine. Craig was quite reluctant
 +to publish the routine in his net-mag because of copyright reasons. As a
 +post-graduate law student that will be working as a commercial lawyer
 +(attourney for Americans :) specializing in copyright/patents for computer
 +software/hardware starting August this year, I don't believe that there are
 +any practical legal consequences for me.
 +
 +  I would have given an arm or a leg for a commented Rob Hubbard sourcecode in
 +the past, so I hope you enjoy this valuable offering.
 +-----------------------------------------------------------------------------
 +;rob hubbard
 +;monty on the run music driver
 +
 +;this player was used (with small mods)
 +;for his first approx 30 musix
 +
 +.org $8000
 +.obj motr
 +
 + jmp initmusic
 + jmp playmusic
 + jmp musicoff
 +
 +
 +;====================================
 +;init music
 +
 +initmusic =*
 +
 +  lda #$00         ;music num
 +  ldy #$00
 +  asl
 +  sta tempstore
 +  asl
 +  clc
 +  adc tempstore    ;now music num*6
 +  tax
 +
 +- lda songs,     ;copy ptrs to this
 +  sta currtrkhi, ;music's tracks to
 +  inx              ;current tracks
 +  iny
 +  cpy #$06
 +  bne -
 +
 +  lda #$00         ;clear control regs
 +  sta $d404
 +  sta $d40b
 +  sta $d412
 +  sta $d417
 +
 +  lda #$0f         ;full volume
 +  sta $d418
 +
 +  lda #$40         ;flag init music
 +  sta mstatus
 +
 +  rts
 +
 +
 +;====================================
 +;music off
 +
 +musicoff =*
 +
 +  lda #$c0         ;flag music off
 +  sta mstatus
 +  rts
 +
 +
 +;====================================
 +;play music
 +
 +playmusic =*
 +
 +  inc counter
 +
 +  bit mstatus      ;test music status
 +  bmi moff         ;$80 and $c0 is off
 +  bvc contplay     ;$40 init, else play
 +
 +
 +;==========
 +;init the song (mstatus $40)
 +
 +  lda #$00         ;init counter
 +  sta counter
 +
 +  ldx #3-1
 +- sta posoffset, ;init pos offsets
 +  sta patoffset, ;init pat offsets
 +  sta lengthleft,x ;get note right away
 +  sta notenum,x
 +  dex
 +  bpl -
 +
 +  sta mstatus      ;signal music play
 +  jmp contplay
 +
 +
 +;==========
 +;music is off (mstatus $80 or $c0)
 +
 +moff =*
 +
 +  bvc +            ;if mstatus $c0 then
 +  lda #$00
 +  sta $d404        ;kill voice 1,2,3
 +  sta $d40b        ;control registers
 +  sta $d412
 +
 +  lda #$0f         ;full volume still
 +  sta $d418
 +
 +  lda #$80         ;flag no need to kill
 +  sta mstatus      ;sound next time
 +
 ++ jmp musicend     ;end
 +
 +
 +;==========
 +;music is playing (mstatus otherwise)
 +
 +contplay =*
 +
 +  ldx #3-1         ;number of chanels
 +
 +  dec speed        ;check the speed
 +  bpl mainloop
 +
 +  lda resetspd     ;reset speed if needed
 +  sta speed
 +
 +
 +mainloop =*
 +
 +  lda regoffsets,x ;save offset to regs
 +  sta tmpregofst   ;for this channel
 +  tay
 +
 +
 +;check whether a new note is needed
 +
 +  lda speed        ;if speed not reset
 +  cmp resetspd     ;then skip notework
 +  beq checknewnote
 +  jmp vibrato
 +
 +checknewnote =*
 +
 +  lda currtrkhi, ;put base addr.w of
 +  sta $02          ;this track in $2
 +  lda currtrklo,x
 +  sta $03
 +
 +  dec lengthleft,x ;check whether a new
 +  bmi getnewnote   ;note is needed
 +
 +  jmp soundwork    ;no new note needed
 +
 +
 +;==========
 +;notework
 +;a new note is needed. get the pattern
 +;number/cc from this position
 +
 +getnewnote =*
 +
 +  ldy posoffset, ;get the data from
 +  lda ($02),     ;the current position
 +
 +  cmp #$ff         ;pos $ff restarts
 +  beq restart
 +
 +  cmp #$fe         ;pos $fe stops music
 +  bne getnotedata  ;on all channels
 +  jmp musicend
 +
 +;cc of $ff restarts this track from the
 +;first position
 +
 +restart =*
 +
 +  lda #$00         ;get note immediately
 +  sta lengthleft,x ;and reset pat,pos
 +  sta posoffset,x
 +  sta patoffset,x
 +  jmp getnewnote
 +
 +
 +;get the note data from this pattern
 +
 +getnotedata =*
 +
 +  tay
 +  lda patptl,    ;put base addr.w of
 +  sta $04          ;the pattern in $4
 +  lda patpth,y
 +  sta $05
 +
 +  lda #$00         ;default no portamento
 +  sta portaval,x
 +
 +  ldy patoffset, ;get offset into ptn
 +
 +  lda #$ff         ;default no append
 +  sta appendfl
 +
 +;1st byte is the length of the note 0-31
 +;bit5 signals no release (see sndwork)
 +;bit6 signals appended note
 +;bit7 signals a new instrument
 +;     or portamento coming up
 +
 +  lda ($04),     ;get length of note
 +  sta savelnthcc,x
 +  sta templnthcc
 +  and #$1f
 +  sta lengthleft,x
 +
 +  bit templnthcc   ;test for append
 +  bvs appendnote
 +
 +  inc patoffset, ;pt to next data
 +
 +  lda templnthcc   ;2nd byte needed?
 +  bpl getpitch
 +
 +;2nd byte needed as 1st byte negative
 +;2nd byte is the instrument number(+ve)
 +;or portamento speed(-ve)
 +
 +  iny
 +  lda ($04),     ;get instr/portamento
 +  bpl +
 +
 +  sta portaval,  ;save portamento val
 +  jmp ++
 +
 ++ sta instrnr,   ;save instr nr
 +
 ++ inc patoffset,x
 +
 +;3rd byte is the pitch of the note
 +;get the 'base frequency' here
 +
 +getpitch =*
 +
 +  iny
 +  lda ($04),     ;get pitch of note
 +  sta notenum,x
 +  asl              ;pitch*2
 +  tay
 +  lda frequenzlo,y ;save the appropriate
 +  sta tempfreq     ;base frequency
 +  lda frequenzhi,y
 +  ldy tmpregofst
 +  sta $d401,y
 +  sta savefreqhi,x
 +  lda tempfreq
 +  sta $d400,y
 +  sta savefreqlo,x
 +  jmp +
 +
 +appendnote =*
 +
 +  dec appendfl     ;clever eh?
 +
 +
 +;fetch all the initial values from the
 +;instrument data structure
 +
 ++ ldy tmpregofst
 +  lda instrnr,   ;instr num
 +  stx tempstore
 +  asl              ;instr num*8
 +  asl
 +  asl
 +  tax
 +
 +  lda instr+2,   ;get control reg val
 +  sta tempctrl
 +  lda instr+2,x
 +  and appendfl     ;implement append
 +  sta $d404,y
 +
 +  lda instr+0,   ;get pulse width lo
 +  sta $d402,y
 +
 +  lda instr+1,   ;get pulse width hi
 +  sta $d403,y
 +
 +  lda instr+3,   ;get attack/decay
 +  sta $d405,y
 +
 +  lda instr+4,   ;get sustain/release
 +  sta $d406,y
 +
 +  ldx tempstore    ;save control reg val
 +  lda tempctrl
 +  sta voicectrl,x
 +
 +
 +;4th byte checks for the end of pattern
 +;if eop found, inc the position and
 +;reset patoffset for new pattern
 +
 +  inc patoffset, ;preview 4th byte
 +  ldy patoffset,x
 +  lda ($04),y
 +
 +  cmp #$ff         ;check for eop
 +  bne +
 +
 +  lda #$00         ;end of pat reached
 +  sta patoffset, ;inc position for
 +  inc posoffset, ;the next time
 +
 ++ jmp loopcont
 +
 +
 +;==========
 +;soundwork
 +;the instrument and effects processing
 +;routine when no new note was needed
 +
 +soundwork =*
 +
 +;release routine
 +;set off a release when the length of
 +;the note is exceeded
 +;bit4 of the 1st note-byte can specify
 +;for no release
 +
 +  ldy tmpregofst
 +
 +  lda savelnthcc,x ;check for no release
 +  and #$20         ;specified
 +  bne vibrato
 +
 +  lda lengthleft,x ;check for length of
 +  bne vibrato      ;exceeded
 +
 +  lda voicectrl, ;length exceeded so
 +  and #$fe         ;start the release
 +  sta $d404,     ;and kill adsr
 +  lda #$00
 +  sta $d405,y
 +  sta $d406,y
 +
 +
 +;vibrato routine
 +;(does alot of work)
 +
 +vibrato =*
 +
 +  lda instrnr,   ;instr num
 +  asl
 +  asl
 +  asl              ;instr num*8
 +  tay
 +  sty instnumby8   ;save instr num*8
 +
 +  lda instr+7,   ;get instr fx byte
 +  sta instrfx
 +
 +  lda instr+6,   ;get pulse speed
 +  sta pulsevalue
 +
 +  lda instr+5,   ;get vibrato depth
 +  sta vibrdepth
 +  beq pulsework    ;check for no vibrato
 +
 +  lda counter      ;this is clever!!
 +  and #7           ;the counter's turned
 +  cmp #4           ;into an oscillating
 +  bcc +            ;value (01233210)
 +  eor #7
 ++ sta oscilatval
 +
 +  lda notenum,   ;get base note
 +  asl              ;note*2
 +  tay              ;get diff btw note
 +  sec              ;and note+1 frequency
 +  lda frequenzlo+2,y
 +  sbc frequenzlo,y
 +  sta tmpvdiflo
 +  lda frequenzhi+2,y
 +  sbc frequenzhi,y
 +
 +- lsr              ;divide difference by
 +  ror tmpvdiflo    ;2 for each vibrdepth
 +  dec vibrdepth
 +  bpl -
 +  sta tmpvdifhi
 +
 +  lda frequenzlo,y ;save note frequency
 +  sta tmpvfrqlo
 +  lda frequenzhi,y
 +  sta tmpvfrqhi
 +
 +  lda savelnthcc,x ;no vibrato if note
 +  and #$1f         ;length less than 8
 +  cmp #8
 +  bcc +
 +
 +  ldy oscilatval
 +
 +- dey              ;depending on the osc
 +  bmi +            ;value, add the vibr
 +  clc              ;freq that many times
 +  lda tmpvfrqlo    ;to the base freq
 +  adc tmpvdiflo
 +  sta tmpvfrqlo
 +  lda tmpvfrqhi
 +  adc tmpvdifhi
 +  sta tmpvfrqhi
 +  jmp -
 +
 ++ ldy tmpregofst   ;save the final
 +  lda tmpvfrqlo    ;frequencies
 +  sta $d400,y
 +  lda tmpvfrqhi
 +  sta $d401,y
 +
 +
 +;pulse-width timbre routine
 +;depending on the control/speed byte in
 +;the instrument datastructure, the pulse
 +;width is of course inc/decremented to
 +;produce timbre
 +
 +;strangely the delay value is also the
 +;size of the inc/decrements
 +
 +pulsework =*
 +
 +  lda pulsevalue   ;check for pulsework
 +  beq portamento   ;needed this instr
 +
 +  ldy instnumby8
 +  and #$1f
 +  dec pulsedelay,x ;pulsedelay-1
 +  bpl portamento
 +
 +  sta pulsedelay,x ;reset pulsedelay
 +
 +  lda pulsevalue   ;restrict pulse speed
 +  and #$e0         ;from $00-$1f
 +  sta pulsespeed
 +
 +  lda pulsedir,  ;pulsedir 0 is up and
 +  bne pulsedown    ;1 is down
 +
 +  lda pulsespeed   ;pulse width up
 +  clc
 +  adc instr+0,   ;add the pulsespeed
 +  pha              ;to the pulse width
 +  lda instr+1,y
 +  adc #$00
 +  and #$0f
 +  pha
 +  cmp #$0e         ;go pulsedown when
 +  bne dumpulse     ;the pulse value
 +  inc pulsedir,  ;reaches max ($0exx)
 +  jmp dumpulse
 +
 +pulsedown =*
 +
 +  sec              ;pulse width down
 +  lda instr+0,y
 +  sbc pulsespeed   ;sub the pulsespeed
 +  pha              ;from the pulse width
 +  lda instr+1,y
 +  sbc #$00
 +  and #$0f
 +  pha
 +  cmp #$08         ;go pulseup when
 +  bne dumpulse     ;the pulse value
 +  dec pulsedir,  ;reaches min ($08xx)
 +
 +dumpulse =*
 +
 +  stx tempstore    ;dump pulse width to
 +  ldx tmpregofst   ;chip and back into
 +  pla              ;the instr data str
 +  sta instr+1,y
 +  sta $d403,x
 +  pla
 +  sta instr+0,y
 +  sta $d402,x
 +  ldx tempstore
 +
 +
 +;portamento routine
 +;portamento comes from the second byte
 +;if it's a negative value
 +
 +portamento =*
 +
 +  ldy tmpregofst
 +  lda portaval,  ;check for portamento
 +  beq drums        ;none
 +
 +  and #$7e         ;toad unwanted bits
 +  sta tempstore
 +
 +  lda portaval,  ;bit0 signals up/down
 +  and #$01
 +  beq portup
 +
 +  sec              ;portamento down
 +  lda savefreqlo,x ;sub portaval from
 +  sbc tempstore    ;current frequency
 +  sta savefreqlo,x
 +  sta $d400,y
 +  lda savefreqhi,x
 +  sbc #$00         ;(word arithmetic)
 +  sta savefreqhi,x
 +  sta $d401,y
 +  jmp drums
 +
 +portup =*
 +
 +  clc              ;portamento up
 +  lda savefreqlo,x ;add portval to
 +  adc tempstore    ;current frequency
 +  sta savefreqlo,x
 +  sta $d400,y
 +  lda savefreqhi,x
 +  adc #$00
 +  sta savefreqhi,x
 +  sta $d401,y
 +
 +
 +;bit0 instrfx are the drum routines
 +;the actual drum timbre depends on the
 +;crtl register value for the instrument:
 +;ctrlreg 0 is always noise
 +;ctrlreg x is noise for 1st vbl and x
 +;from then on
 +
 +;see that the drum is made by rapid hi
 +;to low frequency slide with fast attack
 +;and decay
 +
 +drums =*
 +
 +  lda instrfx      ;check if drums
 +  and #$01         ;needed this instr
 +  beq skydive
 +
 +  lda savefreqhi,x ;don't bother if freq
 +  beq skydive      ;can't go any lower
 +
 +  lda lengthleft,x ;or if the note has
 +  beq skydive      ;finished
 +
 +  lda savelnthcc,x ;check if this is the
 +  and #$1f         ;first vbl for this
 +  sec              ;instrument-note
 +  sbc #$01
 +  cmp lengthleft,x
 +  ldy tmpregofst
 +  bcc firstime
 +
 +  lda savefreqhi,x ;not the first time
 +  dec savefreqhi,x ;so dec freqhi for
 +  sta $d401,     ;drum sound
 +
 +  lda voicectrl, ;if ctrlreg is 0 then
 +  and #$fe         ;noise is used always
 +  bne dumpctrl
 +
 +firstime =*
 +
 +  lda savefreqhi,x ;noise is used for
 +  sta $d401,     ;the first vbl also
 +  lda #$80         ;(set noise)
 +
 +dumpctrl =*
 +
 +  sta $d404,y
 +
 +
 +;bit1 instrfx is the skydive
 +;a long portamento-down from the note
 +;to zerofreq
 +
 +skydive =*
 +
 +  lda instrfx      ;check if skydive
 +  and #$02         ;needed this instr
 +  beq octarp
 +
 +  lda counter      ;every 2nd vbl
 +  and #$01
 +  beq octarp
 +
 +  lda savefreqhi,x ;check if skydive
 +  beq octarp        ;already complete
 +
 +  dec savefreqhi,x ;decr and save the
 +  ldy tmpregofst   ;high byte freq
 +  sta $d401,y
 +
 +
 +;bit2 instrfx is an octave arpeggio
 +;pretty tame huh?
 +
 +octarp =*
 +
 +  lda instrfx      ;check if arpt needed
 +  and #$04
 +  beq loopcont
 +
 +  lda counter      ;only 2 arpt values
 +  and #$01
 +  beq +
 +
 +  lda notenum,   ;odd, note+12
 +  clc
 +  adc #$0c
 +  jmp ++
 +
 ++ lda notenum,   ;even, note
 +
 ++ asl              ;dump the corresponding
 +  tay              ;frequencies
 +  lda frequenzlo,y
 +  sta tempfreq
 +  lda frequenzhi,y
 +  ldy tmpregofst
 +  sta $d401,y
 +  lda tempfreq
 +  sta $d400,y
 +
 +
 +;==========
 +;end of dbf loop
 +
 +loopcont =*
 +
 +  dex              ;dbf mainloop
 +  bmi musicend
 +  jmp mainloop
 +
 +musicend =*
 +
 +  rts
 +
 +
 +;====================================
 +;frequenz data
 +;====================================
 +
 +frequenzlo .byt $16
 +frequenzhi .byt $01
 + .byt $27,$01,$38,$01,$4b,$01
 + .byt $5f,$01,$73,$01,$8a,$01,$a1,$01
 + .byt $ba,$01,$d4,$01,$f0,$01,$0e,$02
 + .byt $2d,$02,$4e,$02,$71,$02,$96,$02
 + .byt $bd,$02,$e7,$02,$13,$03,$42,$03
 + .byt $74,$03,$a9,$03,$e0,$03,$1b,$04
 + .byt $5a,$04,$9b,$04,$e2,$04,$2c,$05
 + .byt $7b,$05,$ce,$05,$27,$06,$85,$06
 + .byt $e8,$06,$51,$07,$c1,$07,$37,$08
 + .byt $b4,$08,$37,$09,$c4,$09,$57,$0a
 + .byt $f5,$0a,$9c,$0b,$4e,$0c,$09,$0d
 + .byt $d0,$0d,$a3,$0e,$82,$0f,$6e,$10
 + .byt $68,$11,$6e,$12,$88,$13,$af,$14
 + .byt $eb,$15,$39,$17,$9c,$18,$13,$1a
 + .byt $a1,$1b,$46,$1d,$04,$1f,$dc,$20
 + .byt $d0,$22,$dc,$24,$10,$27,$5e,$29
 + .byt $d6,$2b,$72,$2e,$38,$31,$26,$34
 + .byt $42,$37,$8c,$3a,$08,$3e,$b8,$41
 + .byt $a0,$45,$b8,$49,$20,$4e,$bc,$52
 + .byt $ac,$57,$e4,$5c,$70,$62,$4c,$68
 + .byt $84,$6e,$18,$75,$10,$7c,$70,$83
 + .byt $40,$8b,$70,$93,$40,$9c,$78,$a5
 + .byt $58,$af,$c8,$b9,$e0,$c4,$98,$d0
 + .byt $08,$dd,$30,$ea,$20,$f8,$2e,$fd
 +
 +
 +regoffsets .byt $00,$07,$0e
 +tmpregofst .byt $00
 +posoffset  .byt $00,$00,$00
 +patoffset  .byt $00,$00,$00
 +lengthleft .byt $00,$00,$00
 +savelnthcc .byt $00,$00,$00
 +voicectrl  .byt $00,$00,$00
 +notenum    .byt $00,$00,$00
 +instrnr    .byt $00,$00,$00
 +appendfl   .byt $00
 +templnthcc .byt $00
 +tempfreq   .byt $00
 +tempstore  .byt $00
 +tempctrl   .byt $00
 +vibrdepth  .byt $00
 +pulsevalue .byt $00
 +tmpvdiflo  .byt $00
 +tmpvdifhi  .byt $00
 +tmpvfrqlo  .byt $00
 +tmpvfrqhi  .byt $00
 +oscilatval .byt $00
 +pulsedelay .byt $00,$00,$00
 +pulsedir   .byt $00,$00,$00
 +speed      .byt $00
 +resetspd   .byt $01
 +instnumby8 .byt $00
 +mstatus    .byt $c0
 +savefreqhi .byt $00,$00,$00
 +savefreqlo .byt $00,$00,$00
 +portaval   .byt $00,$00,$00
 +instrfx    .byt $00
 +pulsespeed .byt $00
 +counter    .byt $00
 +currtrkhi  .byt $00,$00,$00
 +currtrklo  .byt $00,$00,$00
 +
 +
 +;====================================
 +;monty on the run main theme
 +;====================================
 +
 +songs =*
 + .byt <montymaintr1
 + .byt <montymaintr2
 + .byt <montymaintr3
 + .byt >montymaintr1
 + .byt >montymaintr2
 + .byt >montymaintr3
 +
 +
 +;====================================
 +;pointers to the patterns
 +
 +;low pointers
 +patptl =*
 + .byt <ptn00
 + .byt <ptn01
 + .byt <ptn02
 + .byt <ptn03
 + .byt <ptn04
 + .byt <ptn05
 + .byt <ptn06
 + .byt <ptn07
 + .byt <ptn08
 + .byt <ptn09
 + .byt <ptn0a
 + .byt <ptn0b
 + .byt <ptn0c
 + .byt <ptn0d
 + .byt <ptn0e
 + .byt <ptn0f
 + .byt <ptn10
 + .byt <ptn11
 + .byt <ptn12
 + .byt <ptn13
 + .byt <ptn14
 + .byt <ptn15
 + .byt <ptn16
 + .byt <ptn17
 + .byt <ptn18
 + .byt <ptn19
 + .byt <ptn1a
 + .byt <ptn1b
 + .byt <ptn1c
 + .byt <ptn1d
 + .byt <ptn1e
 + .byt <ptn1f
 + .byt <ptn20
 + .byt <ptn21
 + .byt <ptn22
 + .byt <ptn23
 + .byt <ptn24
 + .byt <ptn25
 + .byt <ptn26
 + .byt <ptn27
 + .byt <ptn28
 + .byt <ptn29
 + .byt <ptn2a
 + .byt <ptn2b
 + .byt <ptn2c
 + .byt <ptn2d
 + .byt 0
 + .byt <ptn2f
 + .byt <ptn30
 + .byt <ptn31
 + .byt <ptn32
 + .byt <ptn33
 + .byt <ptn34
 + .byt <ptn35
 + .byt <ptn36
 + .byt <ptn37
 + .byt <ptn38
 + .byt <ptn39
 + .byt <ptn3a
 + .byt <ptn3b
 +
 +;high pointers
 +patpth =*
 + .byt >ptn00
 + .byt >ptn01
 + .byt >ptn02
 + .byt >ptn03
 + .byt >ptn04
 + .byt >ptn05
 + .byt >ptn06
 + .byt >ptn07
 + .byt >ptn08
 + .byt >ptn09
 + .byt >ptn0a
 + .byt >ptn0b
 + .byt >ptn0c
 + .byt >ptn0d
 + .byt >ptn0e
 + .byt >ptn0f
 + .byt >ptn10
 + .byt >ptn11
 + .byt >ptn12
 + .byt >ptn13
 + .byt >ptn14
 + .byt >ptn15
 + .byt >ptn16
 + .byt >ptn17
 + .byt >ptn18
 + .byt >ptn19
 + .byt >ptn1a
 + .byt >ptn1b
 + .byt >ptn1c
 + .byt >ptn1d
 + .byt >ptn1e
 + .byt >ptn1f
 + .byt >ptn20
 + .byt >ptn21
 + .byt >ptn22
 + .byt >ptn23
 + .byt >ptn24
 + .byt >ptn25
 + .byt >ptn26
 + .byt >ptn27
 + .byt >ptn28
 + .byt >ptn29
 + .byt >ptn2a
 + .byt >ptn2b
 + .byt >ptn2c
 + .byt >ptn2d
 + .byt 0
 + .byt >ptn2f
 + .byt >ptn30
 + .byt >ptn31
 + .byt >ptn32
 + .byt >ptn33
 + .byt >ptn34
 + .byt >ptn35
 + .byt >ptn36
 + .byt >ptn37
 + .byt >ptn38
 + .byt >ptn39
 + .byt >ptn3a
 + .byt >ptn3b
 +
 +
 +;====================================
 +;tracks
 +;====================================
 +
 +;track1
 +montymaintr1 =*
 + .byt $11,$14,$17,$1a,$00,$27,$00,$28
 + .byt $03,$05,$00,$27,$00,$28,$03,$05
 + .byt $07,$3a,$14,$17,$00,$27,$00,$28
 + .byt $2f,$30,$31,$31,$32,$33,$33,$34
 + .byt $34,$34,$34,$34,$34,$34,$34,$35
 + .byt $35,$35,$35,$35,$35,$36,$12,$37
 + .byt $38,$09,$2a,$09,$2b,$09,$0a,$09
 + .byt $2a,$09,$2b,$09,$0a,$0d,$0d,$0f
 + .byt $ff
 +
 +;track2
 +montymaintr2 =*
 + .byt $12,$15,$18,$1b,$2d,$39,$39
 + .byt $39,$39,$39,$39,$2c,$39,$39,$39
 + .byt $39,$39,$39,$2c,$39,$39,$39,$01
 + .byt $01,$29,$29,$2c,$15,$18,$39,$39
 + .byt $39,$39,$39,$39,$39,$39,$39,$39
 + .byt $39,$39,$39,$39,$39,$39,$39,$39
 + .byt $39,$39,$39,$39,$39,$39,$39,$39
 + .byt $39,$39,$39,$39,$39,$01,$01,$01
 + .byt $29,$39,$39,$39,$01,$01,$01,$29
 + .byt $39,$39,$39,$39,$ff
 +
 +;track3
 +montymaintr3 =*
 + .byt $13,$16,$19
 + .byt $1c,$02,$02,$1d,$1e,$02,$02,$1d
 + .byt $1f,$04,$04,$20,$20,$06,$02,$02
 + .byt $1d,$1e,$02,$02,$1d,$1f,$04,$04
 + .byt $20,$20,$06,$08,$08,$08,$08,$21
 + .byt $21,$21,$21,$22,$22,$22,$23,$22
 + .byt $24,$25,$3b,$26,$26,$26,$26,$26
 + .byt $26,$26,$26,$26,$26,$26,$26,$26
 + .byt $26,$26,$26,$02,$02,$1d,$1e,$02
 + .byt $02,$1d,$1f,$2f,$2f,$2f,$2f,$2f
 + .byt $2f,$2f,$2f,$2f,$2f,$2f,$2f,$2f
 + .byt $0b,$0b,$1d,$1d,$0b,$0b,$1d,$0b
 + .byt $0b,$0b,$0c,$0c,$1d,$1d,$1d,$10
 + .byt $0b,$0b,$1d,$1d,$0b,$0b,$1d,$0b
 + .byt $0b,$0b,$0c,$0c,$1d,$1d,$1d,$10
 + .byt $0b,$1d,$0b,$1d,$0b,$1d,$0b,$1d
 + .byt $0b,$0c,$1d,$0b,$0c,$23,$0b,$0b
 + .byt $ff
 +
 +
 +;====================================
 +;patterns
 +;====================================
 +
 +ptn00 =*
 + .byt $83,$00,$37,$01,$3e,$01,$3e,$03
 + .byt $3d,$03,$3e,$03,$43,$03,$3e,$03
 + .byt $3d,$03,$3e,$03,$37,$01,$3e,$01
 + .byt $3e,$03,$3d,$03,$3e,$03,$43,$03
 + .byt $42,$03,$43,$03,$45,$03,$46,$01
 + .byt $48,$01,$46,$03,$45,$03,$43,$03
 + .byt $4b,$01,$4d,$01,$4b,$03,$4a,$03
 + .byt $48,$ff
 +
 +ptn27 =*
 + .byt $1f,$4a,$ff
 +
 +ptn28 =*
 + .byt $03,$46,$01,$48,$01,$46,$03,$45
 + .byt $03,$4a,$0f,$43,$ff
 +
 +ptn03 =*
 + .byt $bf,$06
 + .byt $48,$07,$48,$01,$4b,$01,$4a,$01
 + .byt $4b,$01,$4a,$03,$4b,$03,$4d,$03
 + .byt $4b,$03,$4a,$3f,$48,$07,$48,$01
 + .byt $4b,$01,$4a,$01,$4b,$01,$4a,$03
 + .byt $4b,$03,$4d,$03,$4b,$03,$48,$3f
 + .byt $4c,$07,$4c,$01,$4f,$01,$4e,$01
 + .byt $4f,$01,$4e,$03,$4f,$03,$51,$03
 + .byt $4f,$03,$4e,$3f,$4c,$07,$4c,$01
 + .byt $4f,$01,$4e,$01,$4f,$01,$4e,$03
 + .byt $4f,$03,$51,$03,$4f,$03,$4c,$ff
 +
 +ptn05 =*
 + .byt $83,$04,$26,$03,$29,$03,$28,$03
 + .byt $29,$03,$26,$03,$35,$03,$34,$03
 + .byt $32,$03,$2d,$03,$30,$03,$2f,$03
 + .byt $30,$03,$2d,$03,$3c,$03,$3b,$03
 + .byt $39,$03,$30,$03,$33,$03,$32,$03
 + .byt $33,$03,$30,$03,$3f,$03,$3e,$03
 + .byt $3c,$03,$46,$03,$45,$03,$43,$03
 + .byt $3a,$03,$39,$03,$37,$03,$2e,$03
 + .byt $2d,$03,$26,$03,$29,$03,$28,$03
 + .byt $29,$03,$26,$03,$35,$03,$34,$03
 + .byt $32,$03,$2d,$03,$30,$03,$2f,$03
 + .byt $30,$03,$2d,$03,$3c,$03,$3b,$03
 + .byt $39,$03,$30,$03,$33,$03,$32,$03
 + .byt $33,$03,$30,$03,$3f,$03,$3e,$03
 + .byt $3c,$03,$34,$03,$37,$03,$36,$03
 + .byt $37,$03,$34,$03,$37,$03,$3a,$03
 + .byt $3d
 +
 +ptn3a =*
 + .byt $03,$3e,$07,$3e,$07,$3f,$07
 + .byt $3e,$03,$3c,$07,$3e,$57,$ff
 +
 +ptn07 =*
 + .byt $8b
 + .byt $00,$3a,$01,$3a,$01,$3c,$03,$3d
 + .byt $03,$3f,$03,$3d,$03,$3c,$0b,$3a
 + .byt $03,$39,$07,$3a,$81,$06,$4b,$01
 + .byt $4d,$01,$4e,$01,$4d,$01,$4e,$01
 + .byt $4d,$05,$4b,$81,$00,$3a,$01,$3c
 + .byt $01,$3d,$03,$3f,$03,$3d,$03,$3c
 + .byt $03,$3a,$03,$39,$1b,$3a,$0b,$3b
 + .byt $01,$3b,$01,$3d,$03,$3e,$03,$40
 + .byt $03,$3e,$03,$3d,$0b,$3b,$03,$3a
 + .byt $07,$3b,$81,$06,$4c,$01,$4e,$01
 + .byt $4f,$01,$4e,$01,$4f,$01,$4e,$05
 + .byt $4c,$81,$00,$3b,$01,$3d,$01,$3e
 + .byt $03,$40,$03,$3e,$03,$3d,$03,$3b
 + .byt $03,$3a,$1b,$3b,$8b,$05,$35,$03
 + .byt $33,$07,$32,$03,$30,$03,$2f,$0b
 + .byt $30,$03,$32,$0f,$30,$0b,$35,$03
 + .byt $33,$07,$32,$03,$30,$03,$2f,$1f
 + .byt $30,$8b,$00,$3c,$01,$3c,$01,$3e
 + .byt $03,$3f,$03,$41,$03,$3f,$03,$3e
 + .byt $0b,$3d,$01,$3d,$01,$3f,$03,$40
 + .byt $03,$42,$03,$40,$03,$3f,$03,$3e
 + .byt $01,$3e,$01,$40,$03,$41,$03,$40
 + .byt $03,$3e,$03,$3d,$03,$3e,$03,$3c
 + .byt $03,$3a,$01,$3a,$01,$3c,$03,$3d
 + .byt $03,$3c,$03,$3a,$03,$39,$03,$3a
 + .byt $03,$3c,$ff
 +
 +ptn09 =*
 + .byt $83,$00,$32,$01,$35,$01,$34,$03
 + .byt $32,$03,$35,$03,$34,$03,$32,$03
 + .byt $35,$01,$34,$01,$32,$03,$32,$03
 + .byt $3a,$03,$39,$03,$3a,$03,$32,$03
 + .byt $3a,$03,$39,$03,$3a,$ff
 +
 +ptn2a =*
 + .byt $03,$34,$01,$37,$01,$35,$03,$34
 + .byt $03,$37,$03,$35,$03,$34,$03,$37
 + .byt $01,$35,$01,$34,$03,$34,$03,$3a
 + .byt $03,$39,$03,$3a,$03,$34,$03,$3a
 + .byt $03,$39,$03,$3a,$ff
 +
 +ptn2b =*
 + .byt $03,$39,$03,$38,$03,$39,$03,$3a
 + .byt $03,$39,$03,$37,$03,$35,$03,$34
 + .byt $03,$35,$03,$34,$03,$35,$03,$37
 + .byt $03,$35,$03,$34,$03,$32,$03,$31
 + .byt $ff
 +
 +ptn0a =*
 + .byt $03
 + .byt $37,$01,$3a,$01,$39,$03,$37,$03
 + .byt $3a,$03,$39,$03,$37,$03,$3a,$01
 + .byt $39,$01,$37,$03,$37,$03,$3e,$03
 + .byt $3d,$03,$3e,$03,$37,$03,$3e,$03
 + .byt $3d,$03,$3e,$03,$3d,$01,$40,$01
 + .byt $3e,$03,$3d,$03,$40,$01,$3e,$01
 + .byt $3d,$03,$40,$03,$3e,$03,$40,$03
 + .byt $40,$01,$43,$01,$41,$03,$40,$03
 + .byt $43,$01,$41,$01,$40,$03,$43,$03
 + .byt $41,$03,$43,$03,$43,$01,$46,$01
 + .byt $45,$03,$43,$03,$46,$01,$45,$01
 + .byt $43,$03,$46,$03,$45,$03,$43,$01
 + .byt $48,$01,$49,$01,$48,$01,$46,$01
 + .byt $45,$01,$46,$01,$45,$01,$43,$01
 + .byt $41,$01,$43,$01,$41,$01,$40,$01
 + .byt $3d,$01,$39,$01,$3b,$01,$3d,$ff
 +
 +ptn0d =*
 + .byt $01,$3e,$01,$39,$01,$35,$01,$39
 + .byt $01,$3e,$01,$39,$01,$35,$01,$39
 + .byt $03,$3e,$01,$41,$01,$40,$03,$40
 + .byt $01,$3d,$01,$3e,$01,$40,$01,$3d
 + .byt $01,$39,$01,$3d,$01,$40,$01,$3d
 + .byt $01,$39,$01,$3d,$03,$40,$01,$43
 + .byt $01,$41,$03,$41,$01,$3e,$01,$40
 + .byt $01,$41,$01,$3e,$01,$39,$01,$3e
 + .byt $01,$41,$01,$3e,$01,$39,$01,$3e
 + .byt $03,$41,$01,$45,$01,$43,$03,$43
 + .byt $01,$40,$01,$41,$01,$43,$01,$40
 + .byt $01,$3d,$01,$40,$01,$43,$01,$40
 + .byt $01,$3d,$01,$40,$01,$46,$01,$43
 + .byt $01,$45,$01,$46,$01,$44,$01,$43
 + .byt $01,$40,$01,$3d,$ff
 +
 +ptn0f =*
 + .byt $01,$3e,$01
 + .byt $39,$01,$35,$01,$39,$01,$3e,$01
 + .byt $39,$01,$35,$01,$39,$01,$3e,$01
 + .byt $39,$01,$35,$01,$39,$01,$3e,$01
 + .byt $39,$01,$35,$01,$39,$01,$3e,$01
 + .byt $3a,$01,$37,$01,$3a,$01,$3e,$01
 + .byt $3a,$01,$37,$01,$3a,$01,$3e,$01
 + .byt $3a,$01,$37,$01,$3a,$01,$3e,$01
 + .byt $3a,$01,$37,$01,$3a,$01,$40,$01
 + .byt $3d,$01,$39,$01,$3d,$01,$40,$01
 + .byt $3d,$01,$39,$01,$3d,$01,$40,$01
 + .byt $3d,$01,$39,$01,$3d,$01,$40,$01
 + .byt $3d,$01,$39,$01,$3d,$01,$41,$01
 + .byt $3e,$01,$39,$01,$3e,$01,$41,$01
 + .byt $3e,$01,$39,$01,$3e,$01,$41,$01
 + .byt $3e,$01,$39,$01,$3e,$01,$41,$01
 + .byt $3e,$01,$39,$01,$3e,$01,$43,$01
 + .byt $3e,$01,$3a,$01,$3e,$01,$43,$01
 + .byt $3e,$01,$3a,$01,$3e,$01,$43,$01
 + .byt $3e,$01,$3a,$01,$3e,$01,$43,$01
 + .byt $3e,$01,$3a,$01,$3e,$01,$43,$01
 + .byt $3f,$01,$3c,$01,$3f,$01,$43,$01
 + .byt $3f,$01,$3c,$01,$3f,$01,$43,$01
 + .byt $3f,$01,$3c,$01,$3f,$01,$43,$01
 + .byt $3f,$01,$3c,$01,$3f,$01,$45,$01
 + .byt $42,$01,$3c,$01,$42,$01,$45,$01
 + .byt $42,$01,$3c,$01,$42,$01,$48,$01
 + .byt $45,$01,$42,$01,$45,$01,$4b,$01
 + .byt $48,$01,$45,$01,$48,$01,$4b,$01
 + .byt $4a,$01,$48,$01,$4a,$01,$4b,$01
 + .byt $4a,$01,$48,$01,$4a,$01,$4b,$01
 + .byt $4a,$01,$48,$01,$4a,$01,$4c,$01
 + .byt $4e,$03,$4f,$ff
 +
 +ptn11 =*
 + .byt $bf,$06,$56,$1f,$57,$1f,$56,$1f
 + .byt $5b,$1f,$56,$1f,$57,$1f,$56,$1f
 + .byt $4f,$ff
 +
 +ptn12 =*
 + .byt $bf,$0c,$68,$7f,$7f,$7f,$7f,$7f
 + .byt $7f,$7f,$ff
 +
 +ptn13 =*
 + .byt $bf,$08,$13,$3f,$13,$3f,$13,$3f
 + .byt $13,$3f,$13,$3f,$13,$3f,$13,$1f
 + .byt $13,$ff
 +
 +ptn14 =*
 + .byt $97,$09,$2e,$03,$2e,$1b,$32,$03
 + .byt $32,$1b,$31,$03,$31,$1f,$34,$43
 + .byt $17,$32,$03,$32,$1b,$35,$03,$35
 + .byt $1b,$34,$03,$34,$0f,$37,$8f,$0a
 + .byt $37,$43,$ff
 +
 +ptn15 =*
 + .byt $97,$09,$2b,$03,$2b,$1b,$2e,$03
 + .byt $2e,$1b,$2d,$03,$2d,$1f,$30,$43
 + .byt $17,$2e,$03,$2e,$1b,$32,$03,$32
 + .byt $1b,$31,$03,$31,$0f,$34,$8f,$0a
 + .byt $34,$43,$ff
 +
 +ptn16 =*
 + .byt $0f,$1f,$0f,$1f,$0f,$1f,$0f,$1f
 + .byt $0f,$1f,$0f,$1f,$0f,$1f,$0f,$1f
 + .byt $0f,$1f,$0f,$1f,$0f,$1f,$0f,$1f
 + .byt $0f,$1f,$0f,$1f,$0f,$1f,$0f,$1f
 + .byt $ff
 +
 +ptn17 =*
 + .byt $97,$09,$33,$03,$33,$1b,$37,$03
 + .byt $37,$1b,$36,$03,$36,$1f,$39,$43
 + .byt $17,$37,$03,$37,$1b,$3a,$03,$3a
 + .byt $1b,$39,$03,$39,$2f,$3c,$21,$3c
 + .byt $21,$3d,$21,$3e,$21,$3f,$21,$40
 + .byt $21,$41,$21,$42,$21,$43,$21,$44
 + .byt $01,$45,$ff
 +
 +ptn18 =*
 + .byt $97,$09,$30,$03,$30,$1b,$33,$03
 + .byt $33,$1b,$32,$03,$32,$1f,$36,$43
 + .byt $17,$33,$03,$33,$1b,$37,$03,$37
 + .byt $1b,$36,$03,$36,$2f,$39,$21,$39
 + .byt $21,$3a,$21,$3b,$21,$3c,$21,$3d
 + .byt $21,$3e,$21,$3f,$21,$40,$21,$41
 + .byt $01,$42,$ff
 +
 +ptn19 =*
 + .byt $0f,$1a,$0f,$1a,$0f,$1a,$0f,$1a
 + .byt $0f,$1a,$0f,$1a,$0f,$1a,$0f,$1a
 + .byt $0f,$1a,$0f,$1a,$0f,$1a,$0f,$1a
 + .byt $0f,$1a,$0f,$1a,$0f,$1a,$0f,$1a
 + .byt $ff
 +
 +ptn1a =*
 + .byt $1f,$46,$bf,$0a,$46,$7f,$7f,$ff
 +
 +ptn1b =*
 + .byt $1f,$43,$bf,$0a,$43,$7f,$ff
 +
 +ptn1c =*
 + .byt $83,$02,$13,$03,$13,$03,$1e,$03
 + .byt $1f,$03,$13,$03,$13,$03,$1e,$03
 + .byt $1f,$03,$13,$03,$13,$03,$1e,$03
 + .byt $1f,$03,$13,$03,$13,$03,$1e,$03
 + .byt $1f,$03,$13,$03,$13,$03,$1e,$03
 + .byt $1f,$03,$13,$03,$13,$03,$1e,$03
 + .byt $1f,$03,$13,$03,$13,$03,$1e,$03
 + .byt $1f,$03,$13,$03,$13,$03,$1e,$03
 + .byt $1f,$ff
 +
 +ptn29 =*
 + .byt $8f,$0b,$38,$4f,$ff
 +
 +ptn2c =*
 + .byt $83,$0e,$32,$07,$32,$07,$2f,$07
 + .byt $2f,$03,$2b,$87,$0b,$46,$83,$0e
 + .byt $2c,$03,$2c,$8f,$0b,$32,$ff
 +
 +ptn2d =*
 + .byt $43,$83,$0e,$32,$03,$32,$03,$2f
 + .byt $03,$2f,$03,$2c,$87,$0b,$38,$ff
 +
 +ptn39 =*
 + .byt $83,$01
 + .byt $43,$01,$4f,$01,$5b,$87,$03,$2f
 + .byt $83,$01,$43,$01,$4f,$01,$5b,$87
 + .byt $03,$2f,$83,$01,$43,$01,$4f,$01
 + .byt $5b,$87,$03,$2f,$83,$01,$43,$01
 + .byt $4f,$01,$5b,$87,$03,$2f,$83,$01
 + .byt $43,$01,$4f,$01,$5b,$87,$03,$2f
 + .byt $83,$01,$43,$01,$4f,$01,$5b,$87
 + .byt $03,$2f
 +
 +ptn01 =*
 + .byt $83,$01,$43,$01,$4f,$01,$5b,$87
 + .byt $03,$2f,$83,$01,$43,$01,$4f,$01
 + .byt $5b,$87,$03,$2f,$ff
 +
 +ptn02 =*
 + .byt $83,$02,$13,$03,$13,$03,$1f,$03
 + .byt $1f,$03,$13,$03,$13,$03,$1f,$03
 + .byt $1f,$ff
 +
 +ptn1d =*
 + .byt $03,$15,$03,$15,$03,$1f,$03,$21
 + .byt $03,$15,$03,$15,$03,$1f,$03,$21
 + .byt $ff
 +
 +ptn1e =*
 + .byt $03,$1a,$03,$1a,$03,$1c,$03,$1c
 + .byt $03,$1d,$03,$1d,$03,$1e,$03,$1e
 + .byt $ff
 +
 +ptn1f =*
 + .byt $03,$1a,$03,$1a,$03,$24,$03,$26
 + .byt $03,$13,$03,$13,$07,$1f,$ff
 +
 +ptn04 =*
 + .byt $03,$18,$03,$18,$03,$24,$03,$24
 + .byt $03,$18,$03,$18,$03,$24,$03,$24
 + .byt $03,$20,$03,$20,$03,$2c,$03,$2c
 + .byt $03,$20,$03,$20,$03,$2c,$03,$2c
 + .byt $ff
 +
 +ptn20 =*
 + .byt $03,$19,$03,$19,$03
 + .byt $25,$03,$25,$03,$19,$03,$19,$03
 + .byt $25,$03,$25,$03,$21,$03,$21,$03
 + .byt $2d,$03,$2d,$03,$21,$03,$21,$03
 + .byt $2d,$03,$2d,$ff
 +
 +ptn06 =*
 + .byt $03,$1a,$03,$1a
 + .byt $03,$26,$03,$26,$03,$1a,$03,$1a
 + .byt $03,$26,$03,$26,$03,$15,$03,$15
 + .byt $03,$21,$03,$21,$03,$15,$03,$15
 + .byt $03,$21,$03,$21,$03,$18,$03,$18
 + .byt $03,$24,$03,$24,$03,$18,$03,$18
 + .byt $03,$24,$03,$24,$03,$1f,$03,$1f
 + .byt $03,$2b,$03,$2b,$03,$1f,$03,$1f
 + .byt $03,$2b,$03,$2b,$03,$1a,$03,$1a
 + .byt $03,$26,$03,$26,$03,$1a,$03,$1a
 + .byt $03,$26,$03,$26,$03,$15,$03,$15
 + .byt $03,$21,$03,$21,$03,$15,$03,$15
 + .byt $03,$21,$03,$21,$03,$18,$03,$18
 + .byt $03,$24,$03,$24,$03,$18,$03,$18
 + .byt $03,$24,$03,$24,$03,$1c,$03,$1c
 + .byt $03,$28,$03,$28,$03,$1c,$03,$1c
 + .byt $03,$28,$03,$28
 +
 +ptn3b =*
 + .byt $83,$04,$36,$07
 + .byt $36,$07,$37,$07,$36,$03,$33,$07
 + .byt $32,$57,$ff
 +
 +ptn08 =*
 + .byt $83,$02,$1b,$03,$1b,$03,$27,$03
 + .byt $27,$03,$1b,$03,$1b,$03,$27,$03
 + .byt $27,$ff
 +
 +ptn21 =*
 + .byt $03,$1c,$03,$1c,$03,$28,$03,$28
 + .byt $03,$1c,$03,$1c,$03,$28,$03,$28
 + .byt $ff
 +
 +ptn22 =*
 + .byt $03,$1d,$03,$1d,$03,$29,$03,$29
 + .byt $03,$1d,$03,$1d,$03,$29,$03,$29
 + .byt $ff
 +
 +ptn23 =*
 + .byt $03,$18,$03,$18,$03,$24,$03,$24
 + .byt $03,$18,$03,$18,$03,$24,$03,$24
 + .byt $ff
 +
 +ptn24 =*
 + .byt $03,$1e,$03,$1e,$03,$2a,$03,$2a
 + .byt $03,$1e,$03,$1e,$03,$2a,$03,$2a
 + .byt $ff
 +
 +ptn25 =*
 + .byt $83,$05,$26,$01,$4a,$01,$34,$03
 + .byt $29,$03,$4c,$03,$4a,$03,$31,$03
 + .byt $4a,$03,$24,$03,$22,$01,$46,$01
 + .byt $30,$03,$25,$03,$48,$03,$46,$03
 + .byt $2d,$03,$46,$03,$24,$ff
 +
 +ptn0b =*
 + .byt $83,$02,$1a,$03,$1a,$03,$26,$03
 + .byt $26,$03,$1a,$03,$1a,$03,$26,$03
 + .byt $26,$ff
 +
 +ptn0c =*
 + .byt $03,$13,$03,$13,$03,$1d,$03,$1f
 + .byt $03,$13,$03,$13,$03,$1d,$03,$1f
 + .byt $ff
 +
 +ptn26 =*
 + .byt $87,$02,$1a,$87,$03,$2f,$83,$02
 + .byt $26,$03,$26,$87,$03,$2f,$ff
 +
 +ptn10 =*
 + .byt $07,$1a,$4f,$47,$ff
 +
 +ptn0e =*
 + .byt $03,$1f,$03,$1f,$03,$24,$03,$26
 + .byt $07,$13,$47,$ff
 +
 +ptn30 =*
 + .byt $bf,$0f,$32,$0f,$32,$8f,$90,$30
 + .byt $3f,$32,$13,$32,$03,$32,$03,$35
 + .byt $03,$37,$3f,$37,$0f,$37,$8f,$90
 + .byt $30,$3f,$32,$13,$32,$03,$2d,$03
 + .byt $30,$03,$32,$ff
 +
 +ptn31 =*
 + .byt $0f,$32
 + .byt $af,$90,$35,$0f,$37,$a7,$99,$37
 + .byt $07,$35,$3f,$32,$13,$32,$03,$32
 + .byt $a3,$e8,$35,$03,$37,$0f,$35,$af
 + .byt $90,$37,$0f,$37,$a7,$99,$37,$07
 + .byt $35,$3f,$32,$13,$32,$03,$2d,$a3
 + .byt $e8,$30,$03,$32,$ff
 +
 +ptn32 =*
 + .byt $07,$32,$03
 + .byt $39,$13,$3c,$a7,$9a,$37,$a7,$9b
 + .byt $38,$07,$37,$03,$35,$03,$32,$03
 + .byt $39,$1b,$3c,$a7,$9a,$37,$a7,$9b
 + .byt $38,$07,$37,$03,$35,$03,$32,$03
 + .byt $39,$03,$3c,$03,$3e,$03,$3c,$07
 + .byt $3e,$03,$3c,$03,$39,$a7,$9a,$37
 + .byt $a7,$9b,$38,$07,$37,$03,$35,$03
 + .byt $32,$af,$90,$3c,$1f,$3e,$43,$03
 + .byt $3e,$03,$3c,$03,$3e,$ff
 +
 +ptn33 =*
 + .byt $03,$3e
 + .byt $03,$3e,$a3,$e8,$3c,$03,$3e,$03
 + .byt $3e,$03,$3e,$a3,$e8,$3c,$03,$3e
 + .byt $03,$3e,$03,$3e,$a3,$e8,$3c,$03
 + .byt $3e,$03,$3e,$03,$3e,$a3,$e8,$3c
 + .byt $03,$3e,$af,$91,$43,$1f,$41,$43
 + .byt $03,$3e,$03,$41,$03,$43,$03,$43
 + .byt $03,$43,$a3,$e8,$41,$03,$43,$03
 + .byt $43,$03,$43,$a3,$e8,$41,$03,$43
 + .byt $03,$45,$03,$48,$a3,$fd,$45,$03
 + .byt $44,$01,$43,$01,$41,$03,$3e,$03
 + .byt $3c,$03,$3e,$2f,$3e,$bf,$98,$3e
 + .byt $43,$03,$3e,$03,$3c,$03,$3e,$ff
 +
 +ptn34 =*
 + .byt $03,$4a,$03,$4a,$a3,$f8,$48,$03
 + .byt $4a,$03,$4a,$03,$4a,$a3,$f8,$48
 + .byt $03,$4a,$ff
 +
 +ptn35 =*
 + .byt $01,$51,$01,$54,$01
 + .byt $51,$01,$54,$01,$51,$01,$54,$01
 + .byt $51,$01,$54,$01,$51,$01,$54,$01
 + .byt $51,$01,$54,$01,$51,$01,$54,$01
 + .byt $51,$01,$54,$ff
 +
 +ptn36 =*
 + .byt $01,$50,$01,$4f
 + .byt $01,$4d,$01,$4a,$01,$4f,$01,$4d
 + .byt $01,$4a,$01,$48,$01,$4a,$01,$48
 + .byt $01,$45,$01,$43,$01,$44,$01,$43
 + .byt $01,$41,$01,$3e,$01,$43,$01,$41
 + .byt $01,$3e,$01,$3c,$01,$3e,$01,$3c
 + .byt $01,$39,$01,$37,$01,$38,$01,$37
 + .byt $01,$35,$01,$32,$01,$37,$01,$35
 + .byt $01,$32,$01,$30,$ff
 +
 +ptn37 =*
 + .byt $5f,$5f,$5f
 + .byt $47,$83,$0e,$32,$07,$32,$07,$2f
 + .byt $03,$2f,$07,$2f,$97,$0b,$3a,$5f
 + .byt $5f,$47,$8b,$0e,$32,$03,$32,$03
 + .byt $2f,$03,$2f,$47,$97,$0b,$3a,$5f
 + .byt $5f,$47,$83,$0e,$2f,$0b,$2f,$03
 + .byt $2f,$03,$2f,$87,$0b,$30,$17,$3a
 + .byt $5f,$8b,$0e,$32,$0b,$32,$0b,$2f
 + .byt $0b,$2f,$07,$2c,$07,$2c,$ff
 +
 +ptn38 =*
 + .byt $87
 + .byt $0b,$34,$17,$3a,$5f,$5f,$84,$0e
 + .byt $32,$04,$32,$05,$32,$04,$2f,$04
 + .byt $2f,$05,$2f,$47,$97,$0b,$3a,$5f
 + .byt $5f,$84,$0e,$32,$04,$32,$05,$32
 + .byt $04,$2f,$04,$2f,$05,$2f,$ff
 +
 +ptn2f =*
 + .byt $03,$1a,$03,$1a,$03
 + .byt $24,$03,$26,$03,$1a,$03,$1a,$03
 + .byt $18,$03,$19,$03,$1a,$03,$1a,$03
 + .byt $24,$03,$26,$03,$1a,$03,$1a,$03
 + .byt $18,$03,$19,$03,$18,$03,$18,$03
 + .byt $22,$03,$24,$03,$18,$03,$18,$03
 + .byt $16,$03,$17,$03,$18,$03,$18,$03
 + .byt $22,$03,$24,$03,$18,$03,$18,$03
 + .byt $16,$03,$17,$03,$13,$03,$13,$03
 + .byt $1d,$03,$1f,$03,$13,$03,$13,$03
 + .byt $1d,$03,$1e,$03,$13,$03,$13,$03
 + .byt $1d,$03,$1f,$03,$13,$03,$13,$03
 + .byt $1d,$03,$1e,$03,$1a,$03,$1a,$03
 + .byt $24,$03,$26,$03,$1a,$03,$1a,$03
 + .byt $18,$03,$19,$03,$1a,$03,$1a,$03
 + .byt $24,$03,$26,$03,$1a,$03,$1a,$03
 + .byt $18,$03,$19,$ff
 +
 +
 +;====================================
 +;instruments
 +;====================================
 +
 +instr =*
 + .byt $80,$09,$41,$48,$60,$03,$81,$00
 + .byt $00,$08,$81,$02,$08,$00,$00,$01
 + .byt $a0,$02,$41,$09,$80,$00,$00,$00
 + .byt $00,$02,$81,$09,$09,$00,$00,$05
 + .byt $00,$08,$41,$08,$50,$02,$00,$04
 + .byt $00,$01,$41,$3f,$c0,$02,$00,$00
 + .byt $00,$08,$41,$04,$40,$02,$00,$00
 + .byt $00,$08,$41,$09,$00,$02,$00,$00
 + .byt $00,$09,$41,$09,$70,$02,$5f,$04
 + .byt $00,$09,$41,$4a,$69,$02,$81,$00
 + .byt $00,$09,$41,$40,$6f,$00,$81,$02
 + .byt $80,$07,$81,$0a,$0a,$00,$00,$01
 + .byt $00,$09,$41,$3f,$ff,$01,$e7,$02
 + .byt $00,$08,$41,$90,$f0,$01,$e8,$02
 + .byt $00,$08,$41,$06,$0a,$00,$00,$01
 + .byt $00,$09,$41,$19,$70,$02,$a8,$00
 + .byt $00,$02,$41,$09,$90,$02,$00,$00
 + .byt $00,$00,$11,$0a,$fa,$00,$00,$05
 + .byt $00,$08,$41,$37,$40,$02,$00,$00
 + .byt $00,$08,$11,$07,$70,$02,$00,$00
 +
 +.end
 +
 +=============================================================================
 +</code>
 +
 +====== ZPM3 and ZCCP Enhancements for CP/M Plus from Simeon Cran ======
 +<code>
 +by Randy Winchester (randy@mit.edu)
 +
 +Operating System Components
 +
 +The CP/M Plus operating system consists of three modules.  The CCP (Console
 +Command Processor), is the part of CP/M that you see when you first boot the
 +system.  The CCP prints the A> disk prompt, accepts user input, and loads
 +commands from disk.
 +
 +The BDOS (Basic Disk Operating System) handles the CP/M functions of disk,
 +console, and printer input/output, and the tasks of file management.
 +
 +The BIOS (Basic Input Output System) does the real input/output work for the
 +BDOS. The BIOS contains the code customized for the CP/M hardware that you're
 +using. On the C128, the BIOS contains the routines for driving the 40 and 80
 +column screens, using the REU as a RAM drive, and reading/writing several
 +different disk formats on 1571 and 1581 drives.  The BIOS can be thought of as
 +a collection of device drivers that are specific to your computer.
 +
 +
 +What's New - BIOS-R6
 +
 +BIOS-R6 (C128 BIOS modified by Randy Winchester and others) is the latest of
 +the modified versions of the C128 CP/M BIOS.  Most of the changes to the BIOS
 +result in faster processing speed. For example, all the code for driving a 40
 +column screen has been removed.  Almost everyone using CP/M is going to be
 +using it in 80 columns anyway.  Cutting this code takes a big load off the
 +system and increases overall speed by about 15%.  Similarly, the interrupt
 +driven RS232 has been set from 300 to 75 baud.  The higher the baud rate, the
 +more processor time is required to service RS232.  Since the RS232 code is
 +always running, decreasing the baud rate frees up cycles that the processor
 +needs to service RS232.  This doesn't affect the operation of terminal programs
 +which explicitly set the baud rate when they start up.
 +
 +Other features of BIOS-R6 include a screen dump function, commented source to
 +assist the programmer in producing customized systems, and support for
 +additional disk formats.  Some of the new disk formats include Commodore's
 +standard 1581 CP/M format, MAXI 71 (398K on 5.25" disks), and GP 1581 (796K on
 +3.5" disks).
 +
 +C128 CP/M programmers who want to add or change operating system features
 +should try to make changes to the BIOS.  For one thing, BIOS source code is
 +available, but not available for the BDOS or CCP.  (Source code is not
 +available for the BDOS and CCP replacements mentioned in this article either). 
 +Another reason is that the BDOS and CCP are intended to be "invariable"
 +operating system components - that is, they are identical for different
 +computers that run CP/M Plus.  A study of the BIOS source code will reveal
 +segments of code that can be removed if they aren't needed, and will provide
 +hints as to new features that can be added.
 +
 +The distribution package, BIOS-R6.LBR includes documentation, source code,
 +utilities, and support files.  BIOS-R6.LBR also contains the latest version of
 +ZPM3. [Ed. Note: The files mentioned in this article can be found via
 +anonymous FTP or via the mailserver through the "psend" command.]
 +
 +
 +ZPM3 Features
 +
 +ZPM3 is a replacement BDOS by Simeon Cran.  Since the BDOS is supposed to be
 +"invariable," why would anyone want to replace it? The answers to that are
 +pretty typical - bug fixes, speed enhancements, and new features!  ZPM3
 +interacts with the BIOS and CCP in most of the same ways as the standard
 +Digital Research BDOS, and for the most part appears to be a clone of the
 +standard BDOS.  The standard BDOS was coded in 8080 assembly to make it
 +compatible with machines that use the older slower 8080 processor.  Very few
 +(if any) CP/M Plus machines used the 8080. ZPM3 is coded in faster, compact Z80
 +assembly language, for the Z80 processor that is at the heart of most CP/M Plus
 +computers (including the C128).
 +
 +The ZPM3 documentation details fixes to several bugs that have plagued CP/M
 +Plus since day one.  Although the bugs sound somewhat obscure, there's no
 +telling when one might cause problems.
 +
 +ZPM3 is much faster than standard CP/M Plus.  The increased speed should be
 +obvious after using it for a short time.
 +
 +The new features offered by ZPM3 are remarkable.  Three closely related
 +features are enhanced command line editing, a history buffer that stores and
 +recalls multiple commands, and Automatic Command Prompting.  These features
 +work in concert to provide a flexible and convenient command line interface. 
 +Command line editing now has 20 control key functions for moving or deleting by
 +characters or whole words.  The most recent command lines (up to 250
 +characters) are stored in the history buffer, and can be recalled and reused,
 +or reedited if necessary.  Automatic Command Prompting is best appreciated if
 +seen in action.  It's similar to command line completion in Unix, except that
 +it's automatic, with matching responses coming directly from the history
 +buffer.  If you've recently entered a long command line with lots of options,
 +and need to reuse it (or edit it slightly first), typing the first few unique
 +characters will bring back the entire command from the history buffer if it's
 +still intact.  Automatic Command Prompting is so radical that it might take
 +some getting used to. If you don't think you can get used to it, it can be shut
 +off.
 +
 +The latest version of ZPM3, ZPM3N08.ARK, is included inside BIOS-R6.LBR, and
 +can also be found as a separate file.
 +
 +
 +
 +ZCCP Documentation, Version 1.0
 +
 +The remainder of this article will describe ZCCP and how to configure a system
 +disk to get a fully functional ZPM3/ZCCP system up and running.  BIOS-R6 and
 +ZPM3 both come with enough documentation to keep you busy for hours, but ZCCP
 +has never been distributed by itself, because up until this article, there has
 +not been any documentation for it.  Most of the documentation that follows was
 +figured out through experimentation and later verified by Simeon Cran.
 +
 +ZCCP Features
 +
 +This documentation is provided to assist the user in getting a ZCCP system up
 +and running.  It is not an exhaustive course on Z- System or ZCPR.  The
 +following list details which ZCPR features are provided with ZCCP, and which
 +ones aren't.
 +
 +    * ZCPR 3.3 compatibility.  ZCCP can run a wide range of utilities an
 +    applications created for ZCPR 3.3 and ZCPR 3.4.
 +
 +    * TCAP.  A Z3T termcap file describing terminal characteristics can be
 +    loaded into the system.  Z-System programs make use of the TCAP for output
 +    to the screen - a big improvement over the old method of patching
 +    individual programs with terminal control codes.  TCAP files are loaded by
 +    the ZCCP LOADSEG command.
 +
 +    * Named directories.  User areas can be assigned names.  Up to 12 user
 +    areas can be assigned names.  Named Directory Registers (*.NDR files) are
 +    loaded by the ZCCP LOADSEG command.
 +
 +    * Command Search Path.  ZCCP will search for commands along a user defined
 +    search path.  Up to six path elements (directories) can be defined.
 +
 +    * Environment block.  Contains TCAP, Named Directory, and Path information. 
 +    Also includes a map of active disk drives and other system information. 
 +    The environment block can be viewed with the Z-System SHOW utility.
 +
 +    * Flow control.  Conditional processing for batch files.  Relies on
 +    Z-System IF.COM for setting the flow state.  Other flow control commands
 +    (FI, ELSE, XIF, OR, AND) are resident.
 +
 +    * Multiple commands can be entered on the command line.  The command line
 +    buffer will hold up to 225 characters.  Commands should be separated by
 +    semicolons.
 +
 +    * Extended Command Processor.  If a command is not a built-in flow command,
 +    resident command, or located on disk along the search path, the command
 +    line is passed to an extended command processor.  A typical extended
 +    command processor is ARUNZ, a sophisticated batch file executor with alias
 +    features.  To use a program as an extended command processor, rename it to
 +    CMDRUN.COM and place it in the ROOT directory of your boot disk.
 +
 +    * Error handler.  In the event that the extended command processor can't
 +    handle a command, control is passed to an error handler.  Error handlers
 +    give information about the error (instead of the useless CP/M "?" message)
 +    and allow the command line to be edited and reused.
 +
 +    * Resident commands.  The following commands are built in: 
 +      CLS  - clears the screen 
 +      NOTE - text following the NOTE command is treated as a comment.
 +      FI   - Flow control:  terminate the current 
 +      IF level ELSE - Flow control:  toggle the flow state
 +      XIF  - Flow control:  exit all pending IF levels 
 +      OR   - Flow control:  OR IF tests to set flow state 
 +      AND  - Flow control:  AND IF tests to set flow state
 +
 +    * Shell stack.  Up to four shell levels can be defined.  Z-System provides
 +    a choice of several different shells.  Applications such as terminal
 +    programs and word processors can also be assigned shell status.
 +
 +    * ZCCP uses the LOADSEG command for direct loading of RSX files that have
 +    not been GENCOMed.  Example: LOADSEG SAVE.RSX loads SAVE.RSX.
 +
 +    There are some things that Z3Plus will do that ZCCP won't do.
 +
 +    - ZCCP does not support a Flow Command Package (FCP).  It relies on the
 +    transient IF command.  Other flow commands (FI, ELSE, XIF, OR, AND) are
 +    resident in ZCCP.
 +
 +    - A Resident Command Package (RCP) is not implemented.  CLS and NOTE are
 +    resident in ZCCP.  All other commands must be loaded from disk.  This isn't
 +    as much of a handicap as it might sound if you have a fast RAM drive, such
 +    as a CBM 17xx REU, Quick Brown Box, or RAMLink.
 +
 +    - ZCCP can not load type 4 programs (used with ZCPR 3.4).  It loads
 +    standard COM files at 100H, and type 3 programs that load higher in memory. 
 +    Most type 4 programs have type 3 or COM equivalents.
 +
 +    - ZCCP can not reexecute loaded programs.  This trick is usually performed
 +    on Z-Systems with a GO command that jumps to 100H. Since ZCCP also loads at
 +    100H, a GO command would only restart ZCCP.
 +
 +
 +The Files
 +
 +Three files are included in ZCCP.ARK:
 +
 + File name      Size  Description
 + ============   ====  ==========================================
 + CCP     .COM   3k    ZCCP replacement for CCP.COM
 + LOADSEG .COM   3k    Loader for named directories and termcaps
 + ZINSTAL .ZPM   1k    Segment containing environment information
 +
 +
 +Getting Started - Preparing a Boot Disk
 +
 +Format a Commodore CP/M format 5.25 or 3.5 inch disk.  ZCCP must be booted from
 +device 8 (CP/M drive A).
 +
 +Copy the files from ZCCP.ARK to user area 0 of the newly formatted disk.
 +
 +Copy CPM+.SYS to user 0 of the boot disk.  The CPM+.SYS must have been
 +generated using the BDOS segments from ZPM3.
 +
 +Locate a copy of a Z-System alias utility.  A good one is SALIAS16, although
 +others should work also.  Copy it to user 0 of the boot disk.
 +
 +At this point, hit the reset switch and boot the system with the new disk. 
 +After the system boots, you won't be able to do much with it.  The only
 +resident commands are CLS and NOTE, and ZCCP can only locate commands if they
 +are prefixed with the drive and user number.
 +
 +The next step is to create a startup alias.  When ZCCP boots, it looks for a
 +file named STARTZPM.COM and executes commands from it.  STARTZPM.COM is created
 +with a ZCPR alias utility.  Here is a listing of a STARTZPM.COM created with
 +SALIAS:
 +
 +     =============================================================
 +
 +     A0>SALIAS STARTZPM
 +
 +     15:                ; Logs the ROOT directory (A15) on the
 +                        ; current drive.
 +
 +     QD F/F             ; Installs Quick Brown Box ramdisk driver.
 +
 +     LOADSEG NAMES.NDR C128-XBR.Z3T
 +                        ; LOADSEG loads the Named Directory Register
 +                        ; and TCAP.
 +                        ; Directories can now be referred to by
 +                        ; name, as in the next command:
 +
 +     SETPTH10 /C COMMANDS REU 1581 $$$$ $$0 ROOT
 +                        ; SETPTH sets the command search path.
 +                        ; The /c option first clears any existing path.
 +                        ; Directories are then listed in the
 +                        ; order searched.  In this case, COMMANDS
 +                        ; is a 64K QBB ramdisk (drive/user F0) where
 +                        ; frequently used commands are stored.  REU is
 +                        ; a 1750 REU (drive/user M0).  1581 is a 1581
 +                        ; drive, (drive/user C15) where some 700K
 +                        ; of utilities and applications are
 +                        ; located.  $$$$ refers to the currently
 +                        ; logged drive and user area.  $$0 refers
 +                        ; to user area 0 of the current drive.
 +                        ; The ROOT directory is on drive A, user
 +                        ; 15, where startup utilities and system
 +                        ; files can be found.
 +
 +     1571 [AB           ; This speeds up 1571 disk drives A and B
 +                        ; by shutting off the redundant write verify.
 +
 +     AUTOTOG ON         ; Turns on keyboard control of ZPM3 Auto
 +                        ; Command Prompting.  Auto Command
 +                        ; Prompting is toggled by entering CTRL-Q.
 +
 +     COMMANDS:          ; Logs the commands directory.
 +
 +     IF ~EXIST CP.*     ; Test to see if commands are loaded.
 +                        ; This line reads:  "If the CP command
 +                        ; does not exist . . ." and sets the flow
 +                        ; state to true if the file doesn't exist.
 +        QD I/F          ; ". . . then initialize the QBB . . ."
 +        C1:CP C1:*.* F0:
 +                        ; ". . . copy all of the commands in
 +                        ; drive/user C1 to the commands (F0)
 +                        ; directory . . ."
 +     FI                 ; ". . . end if."
 +
 +     ROOT:              ; Log the root directory (A15).
 +
 +     CP C:ZF*.* M0:     ; Copy ZFILER.COM and ZFILER.CMD to the
 +                        ; REU directory (M0).
 +
 +     VERROR             ; Install VERROR error handler.
 +
 +     DATE S             ; Set the system time and date.
 +
 +     ZF                 ; Invoke ZFILER as a shell.
 +
 +     =============================================================
 +
 +Of course, your STARTZPM alias will vary depending on the hardware you need to
 +support, your software preferences, and your work habits.  This alias is close
 +to the upward size limit that ZCCP can handle based on the capacity of the
 +multiple command buffer.  At the very least, I recommend an alias that will set
 +up a search path and load a TCAP.
 +
 +Actually, I put the cart before the horse in this example.  If you try to
 +reboot your system with the LOADSEG command as listed, you'll notice that you
 +don't have a NAMES.NDR file.  There isn't one distributed with ZCCP either. 
 +Z-System utilities won't let you edit the NDR either, since the buffer for it
 +hasn't been created yet.  This turned out to be a nasty chicken/egg situation,
 +hopefully solved by the inclusion of a sample NAMES.NDR file containing simply
 +A0:SYSTEM and A15:ROOT.
 +
 +At this point, you should have a mostly functioning ZCCP system disk.  Press
 +reset and boot it up.  You might want to correct any problems with it or tweak
 +it to perfection before moving on.
 +
 +
 +List of Z-System Utilities for ZCCP
 +
 +Some of the following utilities are essential, others are nice to have.  The
 +version numbers listed are the latest known versions at the time that this
 +documentation was written.  Utilities can be found on ZNode BBSs, and some of
 +them are available on Simtel20 or its mirror sites.  Some of the more important
 +utilities will be uploaded to cco.caltech.edu.
 +
 +         SALIAS16  - already mentioned in the example above.  SALIAS (or one of
 +         the other ZCPR alias utilities) are essential.
 +
 +         ARCOPY    - not a ZCPR utility, but one of the best CP/M file copiers
 +         ever.
 +
 +         SD138B    - excellent DIRectory utility.  SD offers many different
 +         types of sorts, list formats, etc., displays date stamps, and supports
 +         output to a file.
 +
 +         MKDIR32   - utility for manipulating directory names and Named
 +         Directory Register (*.NDR) files.
 +
 +         ERASE57   - erases files.
 +
 +         ZFILER10  - a file management shell that can launch applications. It
 +         is programmable in that it can execute user defined macros from a
 +         file.  Multiple files can be "tagged" and operated on by other
 +         programs.  ZFILER is an excellent program, sort of a GUI desktop
 +         without the slow graphics.
 +
 +         C128-XGR  - a library of eXtended GRaphics termcaps for the C128. This
 +         file is essential if you want to use any ZCPR programs that need a
 +         TCAP.  These termcaps are the first for the C128 that implement
 +         character graphics, standout mode, and control of blinking reverse,
 +         and underline modes.
 +
 +         SETPTH10  - used to set the command search path.  Essential!
 +
 +         VERROR17  - error handler that displays the command line for
 +         reediting.  VERROR17 is the only error handler that I found that works
 +         with ZCCP.
 +
 +         ZEX50     - Z-System EXecutive is a powerful batch file processor that
 +         replaces the CP/M SUBMIT command.
 +
 +         LBRHLP22  - Z-System Help utility displays help files.  Help files can
 +         be crunched (*.HZP), and/or loaded from a HELP.LBR library.
 +
 +         ARUNZ09   - runs an alias script from a text file.  ARUNZ is
 +         frequently used as an extended command processor.  To use ARUNZ (or
 +         any other executable utility) as an extended command processor, rename
 +         it to CMDRUN.COM.
 +
 +         VLU102    - Video Library Utility views or extracts files from
 +         libraries.  Versions of VLU above 1.02 do not work reliably with
 +         ZPM3/ZCCP.
 +
 +         Z33IF16   - is the IF.COM discussed in the section on flow control.
 +
 +         SHOW14    - displays an immense amount of information about your
 +         Z-System.  SHOW also includes a memory patching function.
 +
 +         ZCNFG24   - configures Z-System program options.  Most Z-System
 +         programs are distributed with a configuration (*.CFG) file that
 +         produces a menu of configuration options when run with ZCNFG.
 +
 +         ZP17      - Z-System Patch utility edits files, disk sectors, or
 +         memory, and includes a built-in RPN calculator and number base
 +         converter.
 +
 +         ZMAN-NEW  - This is a manual describing Z-System features in depth. 
 +         It is based on earlier versions of Z-System, and is a little dated,
 +         but otherwise contains information that you won't find anywhere else. 
 +         Not everything in the manual applies to operation of ZPM3/ZCCP, but
 +         with the documentation presented here, you should be able to get a
 +         good idea of what works and what doesn't.
 +
 +
 +ZCCP Technical Notes
 +
 +ZCCP is a replacement CCP that implements ZCPR 3.3.  It loads at 100H and is
 +stored in the bank 0 CCP buffer for fast reloading as does the standard CCP. 
 +By contrast, Z3Plus loads into high memory and can be overwritten by transient
 +commands, requiring reloading Z3Plus from disk.  Because ZCCP replaces the CCP,
 +a ZCCP system has more TPA (transient program area) than a Z3Plus system.  A
 +ZCCP system on the C128 has more than 57K of TPA, almost the same amount as a
 +standard C128 CP/M system.
 +         
 +This should be enough information to get started with ZPM3/ZCCP. Set up a boot
 +disk, experiment with some Z-System utilities, read ZMAN-NEW, and get some
 +applications running.  You'll agree that ZPM3/ZCCP breaths new life into CP/M.
 +
 +=============================================================================
 +</code>
 +====== Multi-Tasking on the C=128 - Part 1 ======
 +<code>
 +by Craig Taylor (duck@pembvax1.pembroke.edu)
 +
 +I.    Introduction / Package Over-view..
 +
 + This article will detail the multi-tasking kernal which I have written butt
 + is still in the debugging stage . The documentation is being released now as
 + C= Hacking has been delayed for a month while this article and a few others
 + were in the process of being shaped. The source code listings, binaries, and
 + a few sample programs will be in the next issue of C= Hacking as well as
 + available on the mailserver and on R. Knop's FTP site when they are
 + available..
 +
 + The Commodore 128 does not support TRUE multi-tasking in that the processor
 + handles swapping from task to task. Rather the package will make use of the
 + interrupts occuring sixty times a second to determine when to switch tasks..
 + The Commodore 128 greatly simplifies things as in addation to the interrupts
 + it also has the provision to relocate zero page and the stack page. So the
 + package basically works by intercepting the IRQ vector, taking a look at the
 + current job, saving the stack pointer, finding the next active job, loading
 + the stack page and registers and resuming the normal IRQ as if nothing had
 + ever happened.
 +
 + Unfortunatly Commodore never thought of having multiple programs in memory
 + executing at any given time. Hence, problems will occur with file accesses,
 + with memory contention, and with an over-all slowdown in speed. The package
 + will detail how to handle device contentions, but it's recommended that
 + programmers make use of the C= 128 kernal call LKUPLA $ff59 containing the
 + logical file number they wish to use in .A; if the carry flag is set upon
 + return then it is safe to use, else find another one as another program is
 + using it. However, note that if you have multiple programs doing this then
 + you may have problems with one grabbing a logical file number after the
 + other process has checked for it. Multi-tasking is fun 'eh?  Problems like
 + this will be examined when we get into semaphores later in this article..
 +
 + Craig Bruce's Dynamic Memory Allocation article in the second issue of C=
 + Hacking should provide a very strong basis for a full-blown memoryy manager.
 + With minor modifications (basically just changing the initial allocations so
 + that the package is not killed) it should be able to work.  Also it will need
 + changes to make sure that processes don't try to allocate at the same time.
 + So a memory manager is not too much of a problem. Details of what changes
 + will be necessary shall be in the next issue.
 +
 + What is a process? What is a program? I've been using the terms almost
 + inter-changebly throughout this article at this point. Basically I'm calling
 + them the same. A process, or program is defined as a program with it's own
 + executable section, it's own data sections, and it's own stack and zero page.
 + (Note, however, that the multi-tasking package does not support relocation of
 + the zero page although this is likely to change).  The "kernal" of the
 + multi-tasker is basically that part of the package which governs which
 + process is executed or switched to next. Semaphores will be examined in
 + detail later; they function as flags for processes too know when it is safe
 + to execute something, and serve as signals betweenn them.
 +
 + Future versions of the package, (even though I know it does not exist out
 + side of my house yet), will support pipes and a more strongly typed kernal
 + so that processes may be prioritized.
 +
 +II.   A Look At Multi-Tasking
 +
 + The introduction introduced some basic elements of multi-tasking but I'll
 + repeat them here, defining them so that this article can be clear as some of
 + the concepts can get a bit confusing.
 +
 +    Background - A process is said to be in the "backgr ound" if it is not
 +    the foreground task and may or may not have input devi ces associated
 +    with it.
 +
 +    Foreground - A process is said to be "foreground" if it is the main
 +    active process and is holding the keyboard and screen display captive
 +    (ie: the user is actually working within it).
 +  
 +    Kernal  - A small section of code that performs low-leval work that is 
 +    needed by any programs in memory..
 +
 +    Multi-Tasking - Execution of more than one process at any given
 +    time.
 +
 +    Priority - A value associated with each process that determines how
 +    often, and possibly when a process is executed.
 +
 +    Process - The space in memory taken up by executable program code, any
 +    associated data, the stack and the registers associated and currently in
 +    use by it, including the current PC (program counter)..
 +
 +    Semaphores - Values that are globally accessed by processes to share and
 +    communicate information between each other and the kernal.
 +
 + Some CPU's have available a multi- tasking mode (the 386 and 486 are the
 + most famaliar ones that come to mind), y et the 8502 chip contained inside
 + the Commodore 128 was first designed before 1985 and lacks multi-tasking. It
 + would be nice if such a multi-tasking CPU in the 6502 family did exist but
 + it would also create problems with the 6502 style architecture and wouldd
 + produce severe compatibility problems.
 +
 + So how is the C=128 supposed to do multi-tasking? Well, we'll "simulate"
 + it..
 +
 + Basically if we had two programs in machine language:
 +
 +            Program 1:                            Program 2:
 +          - lda #65    ; the "A" character     - lda #64    ; the "@" character
 +            jsr $ffd2  ; print it                jsr $ffd2  ; print it
 +            jmp -                                jmp --
 +
 + And we wanted them to multi-task we'd expect something like the following:
 +
 +@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@A@AA
 +
 + It's unlikely that you'll get that in many multi-tasking environments,
 + even non-simulated ones. Since we're only going to be switching tasks every
 + 1/60 of a second then we're more likely to see an output similair to this:
 +
 +@@@@@@AAAAAAA@@@@@@@AAAAAAA@@@@@@@@AAAAAAA@@@@@@@AAAAAAAA@@@@@@@@AAAAAA@@@@@@@
 +
 + So that it seems a process will run for about 1/60 of a second beforee
 + switching to the next one.
 +  
 + We run into problems however. The KERNAL in the C128 that contains most
 + off the file handling, screen manipulations, and keyboard input routines. It
 + was never designed with the idea of multi-tasking in mind. So we're gonna
 + have code running in the KERNAL in two spots for the two differant processes
 + and it's more than likely we'll end up with something like:
 +
 +@@@@@@@@<and then a BRK or some strange error or never-never-land>>
 +
 +  There's got to be some way to fix it - There is - It's called a semaphore..
 +
 + A semaphore is a value that is checked before access is granted to another
 + group of memory locations. The semaphore is basically requested via the
 + following:
 +
 +        request_semaphore       sei
 +                                ldx semaphore
 +                                dex
 +                                beq +
 +                                cli
 +                              - ldy #$ff
 +                                dey 
 +                                bne -
 +                              + inc semaphore
 +                                cli
 +
 + Now the request_semaphore has to disable interrupts to prevent another task
 + from changing the semaphore value before this routine has had a chance. The
 + actual code for the request_semaphore will be very similair to the above.
 +
 + Using a similair routine that performs the opposite - setting the semaphore
 + to a zero value when finished we can dictate what program has control over
 + what device or what memory areas.
 +
 + The semaphores will be used to govern access to the KERNAL routines which
 + manipulate the locations in zero page etc, they'll also be used to manage the
 + memory manager when it is implemented as it'd be awkward for it to allocate
 + the same block of memory to two or more processes.
 +
 +III.  Multi-Tasking Function Calls (Package Calls)
 +
 +  OffSet | Name          | Notes
 +  -------+---------------+--------------------------------------------------
 +   $00   | SetUp         | .C=0 Init Package, .C=1 Uninstall Package
 +                            (including Kernal re-direction).
 +   $03   | SpawnProcess  | On return: .C=0 parent, .C = 1 child
 +   $06   | ChangePriority| .A = new foreground priority, .X = new background
 +   $09   | KillThisProc  | Kills Calling Process (no return)
 +   $0c   | KillOtherProc | Kills Process # .A
 +   $0f   | RequestSemaph | Requests Semaphore #.X
 +   $12   | ReleaseSemaph | Releases Semaphore #.X
 +   $14   | GetProcInfo   | Returns Process Information, Input=#A
 +                         of 0: Process Id
 +                         of 1: Process Foreground Priority
 +                         of 2: Process Background Priority
 +                         of 3+ Other Information To Be Decided Later
 +   $17   | PipeInit      | .AY - Address of Pipe, .X = Size/8
 +                          Return: .X - Pipe #
 +   $1a   | WritePipe     | .AY - Address of Null Term. Data, .X = Pipe #
 +   $1d   | ReadPipe      | .AY - Address to Put Data .X=Pipe #
 +   $20   | ....          |  \
 +   $2e   | ....          |   \ More Routines for the future.
 +  -------+---------------+--------------------------------------------------
 +
 +IV.   Availibility of the Packagee
 +
 +The package should be available at the time of the next issue. A further
 +examination of how the routines work shall be examined along with the source
 +code.
 +
 +Errors popped up in developing it and rather than delay C= Hacking any
 +further I decided to go ahead and release the above information so that
 +individuals can start developing appropriate routines. In addition,
 +please note that PIPEs _may_ or may not be supported in the next issue.
 +I have not fully made up my mind yet on them.
 +
 +V.    Referencess
 +
 +  Born to Code in C, Herbert Schildt, Osborne-McGraw Hill, p.203-252.
 +
 +  Notes from Operating Systems Course, Pembroke State Univ, Fall '92.
 +
 +=============================================================================
 +</code>
 +====== LITTLE RED READER: MS-DOS file reader/WRITER for the C128 and 1571/81. ======
 +<code>
 +by Craig Bruce  (csbruce@neumann.uwaterloo.ca)
 +
 +1. INTRODUCTION
 +
 +This article is a continuation of the Little Red Reader article from last
 +issue.  The program has been extended to write MS-DOS files, in addition to
 +reading them.  The program still works drive-to-drive so you'll still need two
 +disk drives (either physical or logical) to use it.  The program has also been
 +extended to allow MS-DOS files to be deleted and to allow the copying of
 +Commodore-DOS files between CBM-DOS disks (this makes it more convenient to
 +use the program with a temporary logical drive like RAMDOS).  Also, since I
 +have recently acquired a CMD FD-4000 floppy disk drive, I know that this
 +program works with MS-DOS disks with this drive (but only for the 720K
 +format).
 +
 +The program still has the same organization as last time: a menu-oriented
 +user-interface program written in BASIC that makes use of a package of MS-DOS
 +disk accessing routines written in machine language.  Oh, this program is
 +Public Domain Software, so feel free to distribute and/or mangle it as you
 +wish.  Just note any manglings on the "initializing" screen so people don't
 +blame me.
 +
 +The program runs on either the 40 or 80-column screens, but you will get
 +much better performance from the BASIC portion of the program by being
 +in 80-column mode and FAST mode.  A modification that someone might want
 +to make would be to spread-out the display for the 80-column screen and add
 +color to the rather bland display.
 +
 +2. USER GUIDE
 +
 +LOAD and RUN the "lrr.128" BASIC program file.  When the program is first run,
 +it will display an "initializing" message and will load in the binary machine
 +language package from the "current" Commodore DOS drive (the current drive is
 +obtained from PEEK(186) - the last device accessed).  The binary package is
 +loaded only on the first run and is not reloaded on subsequent runs if the
 +package ID field is in place.
 +
 +The system is designed to have two file selection menus: one for the MS-DOS
 +disk drive, and one for the Commodore-DOS disk drive (which may be a logical
 +disk drive).  The idea for copying is that you select the files in one of
 +these menus, and then program knows to copy them to the disk for the other
 +menu.  This idea of having two selection menus is also very consistent with
 +the original program.
 +
 +2.1. MS-DOS MENU
 +
 +When the program starts, the MS-DOS menu of the program is displayed.  It
 +looks like:
 +
 +   MS-DOS  MS=10:1581  CBM=8  FREE=715000
 +
 +   NUM  S  TRN  TYP  FILENAME  EXT  LENGTH
 +   ---  -  ---  ---  --------  ---  ------
 +      *  ASC  SEQ  HACK4     TXT  120732
 +         BIN  PRG  RAMDOS    SFX   34923
 +
 +   D=DIR M=MSDEV F=CBMDEV C=COPY Q=QUIT
 +   T=TOGGLE R=REMOVE X=CBMCPY /=MENU +-=PG
 +
 +except that immediately after starting up, "<directory not loaded>" will be
 +displayed rather than filenames.  The menu looks and operates pretty much as
 +it did in the last issue of C= Hacking.  The only differences are that the
 +number of bytes free on the drive are displayed (which is useful to know when
 +writing files) and there are some more commands.
 +
 +The directory ("D"), change ms-dos device ("M"), change commodore file device
 +("F"), toggle column contents ("T"), copy ms-dos files to cbm-dos disk ("C"),
 +quit ("Q"), paging ("+" and "-"), column change (SPACE or RETURN), and the
 +cursor movement commands all work the same as before.  They are all sticks to
 +use to flog the beast into submission.  The new commands are: "R" (remove ==
 +delete), "/" (change menu), and "X" (copy CBM files == "Xerox").
 +
 +The remove command is used to delete selected files from the MS-DOS disk.
 +After selecting this option, you will get an annoying "are you sure" question
 +and the the selected files will quickly disappear and the changes will finally
 +be written to disk.  Deleting a batch of MS-DOS files is much quicker than
 +deleting Commodore-DOS files since MS-DOS disks use a File Allocation Table
 +rather than the linked list of blocks organization that CBM uses.  In order to
 +make the BASIC program execute quicker, after deleting, the original order of
 +the filenames in the directory listing will be changed.  Be forewarned that
 +the delete operation is non-recoverable.
 +
 +The change menu command is used to move back and forth between the Commodore-
 +DOS and MS-DOS menus.
 +
 +2.2. COMMODORE-DOS MENU
 +
 +The Commodore-DOS menu, which displays the names of the Commodore files
 +selected for various operations, looks and works pretty much the same as
 +the MS-DOS menu:
 +
 +   CBMDOS  MS=10:1581  CBM=8  FREE=3211476
 +
 +   NUM  S  TRN  FILENAME          LENGTH
 +   ---  -  ---  ---------------- -  ------
 +      *  BIN  LRR-128          P    9876
 +         ASC  COM-HACKING-005  S  175412
 +
 +   D=DIR M=MSDEV F=CBMDEV C=COPY Q=QUIT
 +   T=TOGGLE R=REMOVE X=CBMCPY /=MENU +-=PG
 +
 +You'll notice, however, that the filetype field ("T" here) is moved and is
 +unchangable.  Also, the file lengths are not exact; they are reported as the
 +block count of the file multiplied by 254.  This menu is not maintained for
 +files being copied to the CBM-DOS disk from an MS-DOS disk.  You'll
 +have to re-execute the Directory instruction to get an updated listing.
 +
 +The "D" (directory) command has local effect when in this menu.  The
 +Commodore-DOS directory will be loaded from the current CBM device number.
 +Note that in order for this to work, the CBM device must be number eight
 +or greater (a disk drive).  Originally, the subroutine for this command was
 +written using only GET#'s from the disk and was very slow.  It was modified,
 +however, to call a machine language subroutine to read the information for
 +a directory entry from the directory listing, and hence the subroutine now
 +operates at a tolerable speed.
 +
 +The "C" (copy) command also has a different meaning when in this menu.  It
 +means to copy the selected CBM files to the MS-DOS disk.  See details below.
 +
 +The copy CBM files ("X") command is used to copy the files in the CBM-DOS menu
 +to another CBM-DOS disk unit.  Select the files you want to copy and then
 +press X.  You will then be asked what device number you want to copy the files
 +to.  The device can be another disk drive or any other device (except the
 +keyboard).  Using device number 0 does not mean the "null" device as it does
 +with copying MS-DOS to CBM.  If you are copying to a disk device and the file
 +already exists, then you will be asked if you wish to overwrite the file.  You
 +cannot copy to the same disk unit.  Also, all files are copied in binary mode
 +(regardless of what translation you have selected for a file).
 +
 +The copy CBM files command was included since all of the low-level gear
 +needed to implement it (specifically "commieIn" and "commieOut" below) was
 +also required by other functions.  This command can be very convenient when
 +working with RAMDOS.  For example, if you only had a 1571 as device 8 but you
 +have a RAM expander and have installed RAMDOS as device 9, then you would
 +copy MS-DOS files to RAMDOS using the MS-DOS menu, and then you would go to
 +the Commodore-DOS menu ("/"), read the directory, select all files, insert an
 +Commodore-DOS diskette into your 1571, and then use "X" to copy from the
 +RAMDOS device to the 1571.
 +
 +The remove command ("R") does not work for this directory.  You can SCRATCH
 +your CBM-DOS files your damn self.
 +
 +2.3. COPY CBM-DOS TO MS-DOS
 +
 +Before you can copy selected CBM-DOS files to an MS-DOS disk, the MS-DOS disk
 +directory must be already loaded (from the MS-DOS menu).  This is required
 +since the directory and FAT information are kept in memory at all times during
 +the execution of this program.
 +
 +When you enter copy mode, the screen will clear and the name of each selected
 +file is displayed as it is being copied.  If an error is encountered on either
 +the MS-DOS or CBM-DOS drive during copying, an error message will be displayed
 +and copying will continue (after you press a key for MS-DOS errors).  Please
 +note that not a whole lot of effort was put into error recovery.
 +
 +To generate an MS-DOS filename from an CBM-DOS filename, the following
 +algorithm is used.  The filename is searched from right to left for the last
 +"." character.  If there is no "." character, then the entire filename, up to
 +11 characters, is used as the MS-DOS filename.  Characters 9 to 11 will be
 +used as the extension.  If there is a "." character, the all characters before
 +it, up to eight, will be used as the MS-DOS filename and all characters after
 +the final ".", up to three, will be used as the MS-DOS extension.
 +
 +Then, the newly generated MS-DOS filename is scanned for any extra "."
 +characters or embedded spaces.  If any are found, they are replaced by the
 +underscore character ("_", which is the backarrow character on a Commodore
 +display).  Finally, all trailing underscores are removed from the end of both
 +the filename and extension portions of the MS-DOS filename.  Also, all
 +characters are converted to lowercase PETSCII (which is uppercase ASCII) when
 +they are copied into the MS-DOS filename.  Note that if the Commodore filename
 +is not in the 8/3 format of MS-DOS, then something in the name may be lost.
 +Some examples of filename conversion follow:
 +
 +CBM-DOS FILENAME       MS-DOS FILENAME
 +----------------       ---------------
 +"lrr.bin"              "lrr.bin"
 +"lrr.128.bin"          "lrr_128.bin"
 +"hello there.text"     "hello_th.tex"
 +"long_filename"        "long_fil.ena"
 +"file 1..3.s__5"       "file_1.s"
 +
 +It would have been time-consuming to have the program scan the MS-DOS
 +directory for a filename already existing on the disk, so LRR will put
 +multiple files on a disk with the same filename without complaining.  This
 +also gets rid of the problem of asking you if you want to overwrite the old
 +file or generate a new name.  However, in order to retrieve the file from
 +disk on an MS-DOS machine, you will probably have to use the RENAME command to
 +rename the first versions of the file on the disk to something else so MS-DOS
 +will scan further in the directory for the last version of the file with the
 +same filename.  There is no rename command in LRR because I never thought of
 +it in time.  It would have been fairly easy to put in.
 +
 +The date generated for a new MS-DOS file will be all zeros.  Some systems
 +interpret this as 12:00 am, 01-Jan-80 and others don't display a date at all
 +for this value.
 +
 +The physical copying of the file is done completely in machine language and
 +nothing is displayed on the screen while this is happening, but you can follow
 +things by looking at the blinking lights and listening for clicks and grinds.
 +
 +Since the FAT and directory are maintained in RAM during the entire copying
 +process and are only flushed to disk after the entire batch of files are
 +copied, copying is made more efficient, since there will be no costly seek
 +back to track 0 after writing each file (like MS-DOS does).  If you have a
 +number of small files to copy, then they will be knocked off in quick
 +succession, faster than many MS-DOS machines will copy them.
 +
 +To simplify the implementation, the current track of disk blocks for writing
 +is not maintained like it is for reading.  Also, a writing interleave of 1:1
 +is used for a 1571, which is not optimal.  However, since writing is such a
 +slow operation anyway, and since the 1571 is particularly bad by insisting on
 +verifying blocks, not much more overhead is introduced than is already
 +present.
 +
 +An interesting note about writing MS-DOS disks is that you can terminate LRR
 +in the middle of a copy (with STOP+RESTORE) or in the middle of copying a
 +batch of files, and the MS-DOS disk will remain in a perfectly consistent
 +state afterwards.  The state will be as if none of the files were copied.  The
 +reason is that the control information (the FAT and directory) is maintained
 +internally and is flushed only after copying is all completed.  But don't
 +terminate LRR while it is flushing the control information.
 +
 +Here is a table of copying speeds for copying to 1571, 1581, and CMD FD-4000
 +disk units with ASC and BIN translation modes.  All figures are in bytes/
 +second, which includes both reading the byte from a C= disk and writing it to
 +the MS-DOS disk.  The average speed for either the read or write operation
 +individually will be twice the speed given below.  These results were obtained
 +from copying a 156,273 byte text file (the text of C= Hacking Issue #4).
 +
 +   FROM   \ TO: FD-bin     FD-asc     81-bin     81-asc     71-bin     71-asc
 +   --------+    ------     ------     ------     ------     ------     ------
 +   RAMLink |     2,332      2,200      2,332      2,200      1,594      1,559
 +   RAMDOS  |     1,070      1,053      1,604      1,600      1,561      1,510
 +   FD4000  |                  -      1,645      1,597      1,499      1,464
 +   JD1581  |     1,662      1,619          -          -      1,474      1,440
 +   JD1571  |     1,050      1,024        953        933          -          -
 +
 +These figures are for transfer speed only, not counting the couple of seconds
 +of opening files and flushing the directory.  Note that all my physical drives
 +are JiffyDOS-ified, so your performance may be slower.  I am at a loss to
 +explain why an FD-4000 is so much slower than a 1581 for copying from a
 +RAMDOS file, but the same speed or better for copying from anything else.
 +
 +Since I don't have access to an actual MS-DOS machine, I have not tested the
 +files written onto an MS-DOS disk by LRR, except by reading them back with LRR
 +and BBR.  I do know, however, that earlier encarnations of this program did
 +work fine with MS-DOS machines.
 +
 +3. MS-DOS ROOT DIRECTORY
 +
 +It was brought to my attention that I made a mistake in the pervious article.
 +I was wrong about the offset of the attributes field in a directory entry.
 +The layout should have been as follows:
 +
 +   OFFSET     LEN     DESCRIPTION
 +   ------     ---     -----------
 +     0..7           Filename
 +    8..10           Extension
 +       11           Attributes: $10=Directory, $08=VolumeId
 +   12..21      10     <unused>
 +   22..25           Date
 +   26..27           Starting FAT entry number
 +   28..31           File length in bytes
 +
 +4. FILE COPYING PACKAGE
 +
 +As was mentioned above, Little Red Reader is split into two pieces: a BASIC
 +front-end user interface program and a package of machine language subroutines
 +for disk accessing.  The BASIC program handles the menu, user interaction, and
 +most of the MS-DOS directory searching/modifying.  The machine language
 +package handles the hardware input/output, File Allocation Table and file
 +structure manipulations.
 +
 +The file copying package is written in assembly language and is loaded into
 +memory at address $8000 on bank 0 and requires about 13K of memory.  The
 +package is loaded at this high address to be out of the way of the main BASIC
 +program, even if RAMDOS is installed.
 +
 +This section of the article is presented in its entirety, including all of the
 +information given last time.
 +
 +4.1. INTERFACE
 +
 +The subroutine call interface to the file copying package is summarized as
 +follows:
 +
 +   ADDRESS     DESCRIPTION
 +   -------     -----------
 +   PK          initPackage subroutine
 +   PK+3        msDir    (load MS-DOS directory/FAT) subroutine
 +   PK+6        msRead   (copy MS-DOS to CBM-DOS) subroutine
 +   PK+9        msWrite  (copy CBM-DOS to MS-DOS) subroutine
 +   PK+12       msFlush  subroutine
 +   PK+15       msDelete subroutine
 +   PK+18       msFormat subroutine [not implemented]
 +   PK+21       msBytesFree subroutine
 +   PK+24       cbmCopy  (copy CBM-DOS to CBM-DOS) subroutine
 +   PK+27       cbmDirent (read CBM-DOS directory entry) subroutine
 +
 +where "PK" is the load address of the package ($8000).
 +
 +The parameter passing interface is summarized as follows:
 +
 +   ADDRESS     DESCRIPTION
 +   -------     -----------
 +   PV          two-byte package identification number ($CB, 132)
 +   PV+2        errno : error code returned
 +   PV+3        MS-DOS device number (8 to 30)
 +   PV+4        MS-DOS device type ($00=1571, $FF=1581)
 +   PV+5        two-byte starting cluster number for file copying
 +   PV+7        low and mid bytes of file length for copying
 +   PV+9        pointer to MS-DOS directory entry for writing
 +   PV+11       CBM-DOS file block count
 +   PV+13       CBM-DOS file type ("S"=seq, "P"=prg, etc.)
 +   PV+14       CBM-DOS filename length
 +   PV+15       CBM-DOS filename characters (max 16 chars)
 +
 +Where "PV" is equal to PK+30.  Additional subroutine parameters are passed in
 +the processor registers.
 +
 +The MS-DOS device number and device type interface variables allow you to set
 +the MS-DOS drive and the package identification number allows the application
 +program to check if the package is already loaded into memory so that it only
 +has to load the package the first time the application is run and not on
 +re-runs.  The identification sequence is a value of $CB followed by a value of
 +132.
 +
 +4.1.1. INIT_PACKAGE SUBROUTINE
 +
 +The "initPackage" subroutine should be called when the package is first
 +installed, whenever the MS-DOS device number is changed, and whenever a new
 +disk is mounted to invalidate the internal track cache.  It requires no
 +parameters.
 +
 +4.1.2. MS_DIR SUBROUTINE
 +
 +The "msDir" subroutine will load the directory, FAT, and the Boot Sector
 +parameters into the internal memory of the package from the current MS-DOS
 +device number.  No (other) input parameters are needed and the subroutine
 +returns a pointer to the directory space in the .AY registers and the number
 +of directory entries in the .X register.  If an error occurs, then the
 +subroutine returns with the Carry flag set and the error code is available in
 +the "errno" interface variable.  The directory entry data is in the directory
 +space as it was read in raw from the directory sectors on the MS-DOS disk.
 +
 +4.1.3. MS_READ SUBROUTINE
 +
 +The "msRead" subroutine will copy a single file from the MS-DOS disk to a
 +specified CBM-Kernal logical file number (the CBM file must already be
 +opened).  If the CBM logical file number is zero, then the file data is simply
 +discarded after it is read from the MS-DOS file.  The starting cluster number
 +of the file to copy and the low and mid bytes of the file length are passed in
 +the PV+5 and PV+7 interface words.  The translation mode to use is passed in
 +the .A register ($00=binary, $FF=ascii) and the CBM logical file number to
 +output to is passed in the .X register.  If an error occurs, the routine
 +returns with the Carry flag set and the error code in the "errno" interface
 +variable.  There are no other output parameters.
 +
 +Note that since the starting cluster number and low-file length of the file to
 +be copied are required rather than the filename, it is the responsibility of
 +the front-end application program to dig through the raw directory sector data
 +to get this information.  The application must also open the Commodore-DOS
 +file of whatever filetype on whatever device is required; the package does not
 +need to know the Commodore-DOS device number.
 +
 +4.1.4. MS_WRITE SUBROUTINE
 +
 +The "msWrite" subroutine copies a single file from a specified CBM-Kernal
 +logical file number to a MS-DOS file.  The MS-DOS device number and type are
 +set above.  A pointer to the MS-DOS directory entry in the buffer returned by
 +the "msDir" call must be given in interface word PV+9 and the translation mode
 +and CBM lfn are passed in the .A and .X registers as in the "msRead" routine.
 +An error return is given in the usual way (.CS, errno).  Otherwise, there are
 +no return values.
 +
 +It is the responsibility of the calling program to initialize the MS-DOS
 +directory entry to all zeros and then set the filename and set the starting
 +cluster pointer to $0FFF.  This routine will update the starting cluster and
 +file length fields of the directory entry when it finishes.  The internal
 +"dirty flags" are modified so that the directory and FAT will be flushed on
 +the next call to "msFlush".
 +
 +4.1.5. MS_FLUSH SUBROUTINE
 +
 +The "msFlush" subroutine takes no input parameters other than the implicit
 +msDevice and msType.  If "dirty" (modified), the FAT will be written to the
 +MS-DOS disk, to both physical replicas of the disk FAT.  Then, each directory
 +sector that is dirty will be written to disk.  After flushing, the internal
 +dirty flags will be cleared.  An error return is given in the usual way.
 +There are no other output parameters.  If you call this routine and there are
 +no dirty flags set, then it will return immediately, without any writing to
 +disk.
 +
 +4.1.6. MS_DELETE SUBROUTINE
 +
 +The "msDelete" subroutine will deallocate all File Allocation Table entries
 +(and hence, data clusters) allocated to a file and mark the directory entry as
 +being deleted (by putting an $E5 into the first character of the filename).
 +The file is specified by giving the pointer to the directory entry in
 +interface word at PV+9.  After deallocating the file data, the internal
 +"dirty" flag will be set for the FAT and the sector that the directory entry
 +is on, but nothing will be written to disk.  There is no error return from
 +this routine.  It is the responsibility of the calling routine to eventually
 +call the "msFlush" routine.
 +
 +4.1.7. MS_FORMAT SUBROUTINE
 +
 +The "msFormat" subroutine is not implemented.  It's intended function was to
 +format the MS-DOS disk and generate and write the boot sector, initial FAT,
 +and initial directory entry data.
 +
 +4.1.8. MS_BYTES_FREE SUBROUTINE
 +
 +The "msBytesFree" subroutine will scan the currently loaded MS-DOS File
 +Allocation Table, count the number of clusters free, and return the number of
 +bytes free for file storage on the disk.  There are no input parameters and
 +the bytes free are returned in the .AYX registers (.A=low, .Y=mid, .X=high
 +byte).  The subroutine has no error returns and does not check if an MS-DOS
 +directory is actually loaded.
 +
 +4.1.9. CBM_COPY SUBROUTINE
 +
 +The "cbmCopy" subroutine will copy from an input CBM-Kernal logical file
 +number given in the .A register to an output CBM-Kernal lfn given in the .X
 +register, in up to 1024 byte chunks.  File contents are copied exactly (no
 +translation).  This routine does not care if the lfn's are on the same device
 +or not, but the input device must be a disk unit (either logical or physical).
 +An error return is given in the usual way.
 +
 +4.1.10. CBM_DIRENT SUBROUTINE
 +
 +The "cbmDirent" subroutine reads the next directory entry from the CBM-Kernal
 +lfn given in .A and puts the data into interface variables.  Of course, the
 +lfn is assumed to be open for reading a directory ("$").  The block count is
 +returned in the word at PV+11, the first character of the filetype is returned
 +at PV+13, the number of characters in the filename is returned in PV+14, and
 +the filename characters are returned in bytes PV+15 to PV+30.  An error return
 +is given in the usual way.
 +
 +This routine assumes that the first two bytes of the directory file have
 +already been read.  The first call to this routine will return the name of the
 +disk.  The end of a directory is signalled by a filename length of zero.  In
 +this case, the block count returned will be the number of blocks free on the
 +disk.
 +
 +4.2. IMPLEMENTATION
 +
 +This section presents the code that implements the MS-DOS file reading and
 +writing package.  It is here in a special form; each code line is preceded by
 +the % symbol.  The % sign is there to allow you to easily extract the
 +assembler code from the rest of this magazine (and all of my ugly comments).
 +On a Unix system, all you have to do is execute the following command line
 +(substitute filenames as appropriate):
 +
 +grep '^%' Hack5 | sed 's/^% //' | sed 's/^%//' >lrr.s
 +
 +% ; Little Red Reader/Writer utility package by Craig Bruce, 31-Jan-92
 +% ; Written for C= Hacking Net-Magazine; for C-128, 1571, 1581
 +%
 +
 +The code is written for the Buddy assembler and here are a couple setup
 +directives.  Note that my comments come before the section of code.
 +
 +% .org $8000
 +% .obj "lrr.bin"
 +%
 +% ;====jump table and parameters interface ====
 +%
 +% jmp initPackage ;()
 +% jmp msDir       ;( msDevice, msType ) : .AY=dirAddr, .X=direntCount
 +% jmp msRead      ;( msDevice, msType, startCluster, lenML,.A=trans,.X=cbmLfn )
 +% jmp msWrite     ;( msDevice, msType, writeDirent, .A=trans, .X=cbmLfn )
 +% jmp msFlush     ;( msDevice, msType )
 +% jmp msDelete    ;( writeDirent )
 +% jmp msFormat    ;( msDevice, msType )
 +% jmp msBytesFree ;( ) : .AYX=bytesFree
 +% jmp cbmCopy     ;( .A=inLfn, .X=outLfn )
 +% jmp cbmDirent   ;( .A=lfn )
 +%
 +% .byte $cb,132   ;identification (location pk+30)
 +
 +These interface variables are included in the package program space to
 +minimize unwanted interaction with other programs loaded at the same time,
 +such as the RAMDOS device driver.
 +
 +% errno           .buf 1
 +% msDevice        .buf 1
 +% msType          .buf 1    ;$00=1571, $ff=1581
 +% startCluster    .buf 2
 +% lenML           .buf 2    ;length medium and low bytes
 +% writeDirent     .buf 2    ;pointer to dirent
 +% cdirBlocks      .buf 2    ;cbm dirent blocks
 +% cdirType        .buf 1    ;cbm dirent filetype
 +% cdirFlen        .buf 1    ;cbm dirent filename length
 +% cdirName        .buf 16   ;cbm dirent filename
 +%
 +
 +This command is not currently implemented.  Its stub appears here.
 +
 +% msFormat = *
 +%    brk
 +%
 +% ;====global declaraions====
 +%
 +% kernelListen = $ffb1
 +% kernelSecond = $ff93
 +% kernelUnlsn  = $ffae
 +% kernelAcptr  = $ffa2
 +% kernelCiout  = $ffa8
 +% kernelSpinp  = $ff47
 +% kernelChkin  = $ffc6
 +% kernelChkout = $ffc9
 +% kernelClrchn = $ffcc
 +% kernelChrin  = $ffcf
 +% kernelChrout = $ffd2
 +%
 +% st = $90
 +% ciaClock = $dd00
 +% ciaFlags = $dc0d
 +% ciaData  = $dc0c
 +%
 +
 +These are the parameters and derived parameters from the boot sector.  They
 +are kept in the program space to avoid interactions.
 +
 +% clusterBlockCount .buf 1        ;1 or 2
 +% fatBlocks         .buf 1        ;up to 3
 +% rootDirBlocks     .buf 1        ;up to 8
 +% rootDirEntries    .buf 1        ;up to 128
 +% totalSectors      .buf 2        ;up to 1440
 +% firstFileBlock    .buf 1
 +% firstRootDirBlock .buf 1
 +% fileClusterCount  .buf 2
 +% lastFatEntry      .buf 2
 +%
 +
 +The cylinder (track) and side that is currently stored in the track cache
 +for reading.
 +
 +% bufCylinder     .buf 1
 +% bufSide         .buf 1
 +
 +These "dirty" flags record what has to be written out for a flush operation.
 +
 +% fatDirty        .buf 1
 +% dirDirty        .buf 8  ;flag for each directory block
 +% formatParms     .buf 6
 +%
 +
 +This package is split into a number of levels.  This level interfaces with the
 +Kernal serial bus routines and the burst command protocol of the disk drives.
 +
 +% ;====hardware level====
 +%
 +
 +Connect to the MS-DOS device and send the "U0" burst command prefix and the
 +burst command byte.
 +
 +% sendU0 = *  ;( .A=burstCommandCode ) : .CS=err
 +%    pha
 +%    lda #0
 +%    sta st
 +%    lda msDevice
 +%    jsr kernelListen
 +%    lda #$6f
 +%    jsr kernelSecond
 +%    lda #"u"
 +%    jsr kernelCiout
 +%    bit st
 +%    bmi sendU0Error
 +%    lda #"0"
 +%    jsr kernelCiout
 +%    pla
 +%    jsr kernelCiout
 +%    bit st
 +%    bmi sendU0Error
 +%    clc
 +%    rts
 +%
 +%    sendU0Error = *
 +%    lda #5
 +%    sta errno
 +%    sec
 +%    rts
 +%
 +
 +Toggle the "Data Accepted / Ready For More" clock signal for the burst
 +transfer protocol.
 +
 +% toggleClock = *
 +%    lda ciaClock
 +%    eor #$10
 +%    sta ciaClock
 +%    rts
 +%
 +
 +Wait for a burst byte to arrive in the serial data register of CIA#1 from the
 +fast serial bus.
 +
 +% serialWait = *
 +%    lda #$08
 +% -  bit ciaFlags
 +%    beq -
 +%    rts
 +%
 +
 +Wait for and get a burst byte from the fast serial bus, and send the "Data
 +Accepted" signal.
 +
 +% getBurstByte = *
 +%    jsr serialWait
 +%    ldx ciaData
 +%    jsr toggleClock
 +%    txa
 +%    rts
 +%
 +
 +Send the burst commands to "log in" the MS-DOS disk and set the Read sector
 +interleave factor.
 +
 +% mountDisk = *  ;() : .CS=err
 +%    lda #%00011010
 +%    jsr sendU0
 +%    bcc +
 +%    rts
 +% +  jsr kernelUnlsn
 +%    bit st
 +%    bmi sendU0Error
 +%    clc
 +%    jsr kernelSpinp
 +%    bit ciaFlags
 +%    jsr toggleClock
 +%    jsr getBurstByte
 +%    sta errno
 +%    and #$0f
 +%    cmp #2
 +%    bcs mountExit
 +
 +Grab the throw-away parameters from the mount operation.
 +
 +%    ldy #0
 +% -  jsr getBurstByte
 +%    sta formatParms,y
 +%    iny
 +%    cpy #6
 +%    bcc -
 +%    clc
 +
 +Set the Read sector interleave to 1 for a 1581 or 4 for a 1571.
 +
 +%    ;** set interleave
 +%    lda #%00001000
 +%    jsr sendU0
 +%    bcc +
 +%    rts
 +% +  lda #1            ;interleave of 1 for 1581
 +%    bit msType
 +%    bmi +
 +%    lda #4            ;interleave of 4 for 1571
 +% +  jsr kernelCiout
 +%    jsr kernelUnlsn
 +%    mountExit = *
 +%    rts
 +%
 +
 +Read all of the sectors of a given track into the track cache.
 +
 +% bufptr = 2
 +% secnum = 4
 +%
 +% readTrack = *  ;( .A=cylinder, .X=side ) : trackbuf, .CS=err
 +%    pha
 +%    txa
 +
 +Get the side and put it into the command byte.  Remember that we have to flip
 +the side bit for a 1581.
 +
 +%    and #$01
 +%    asl
 +%    asl
 +%    asl
 +%    asl
 +%    bit msType
 +%    bpl +
 +%    eor #$10
 +% +  jsr sendU0
 +%    pla
 +%    bcc +
 +%    rts
 +% +  jsr kernelCiout      ;cylinder number
 +%    lda #1               ;start sector number
 +%    jsr kernelCiout
 +%    lda #9               ;sector count
 +%    jsr kernelCiout
 +%    jsr kernelUnlsn
 +
 +Prepare to receive the track data.
 +
 +%    sei
 +%    clc
 +%    jsr kernelSpinp
 +%    bit ciaFlags
 +%    jsr toggleClock
 +%    lda #<trackbuf
 +%    ldy #>trackbuf
 +%    sta bufptr
 +%    sty bufptr+1
 +
 +Get the sector data for each of the 9 sectors of the track.
 +
 +%    lda #0
 +%    sta secnum
 +% -  bit msType
 +%    bmi +
 +
 +If we are dealing with a 1571, we have to set the buffer pointer for the next
 +sector, taking into account the soft interleave of 4.
 +
 +%    jsr get1571BufPtr
 +% +  jsr readSector
 +%    bcs trackExit
 +%    inc secnum
 +%    lda secnum
 +%    cmp #9
 +%    bcc -
 +%    clc
 +%    trackExit = *
 +%    cli
 +%    rts
 +%
 +
 +Get the buffer pointer for the next 1571 sector.
 +
 +% get1571BufPtr = *
 +%    lda #<trackbuf
 +%    sta bufptr
 +%    ldx secnum
 +%    clc
 +%    lda #>trackbuf
 +%    adc bufptr1571,x
 +%    sta bufptr+1
 +%    rts
 +%
 +% bufptr1571 = *
 +%    .byte 0,8,16,6,14,4,12,2,10
 +%
 +
 +Read an individual sector into memory at the specified address.
 +
 +% readSector = *  ;( bufptr ) : .CS=err
 +
 +Get and check the burst status byte for errors.
 +
 +%    jsr getBurstByte
 +%    sta errno
 +%    and #$0f
 +%    cmp #2
 +%    bcc +
 +%    rts
 +% +  ldx #2
 +%    ldy #0
 +%
 +
 +Receive the 512 sector data bytes into memory.
 +
 +%    readByte = *
 +%    lda #$08
 +% -  bit ciaFlags
 +%    beq -
 +%    lda ciaClock
 +%    eor #$10
 +%    sta ciaClock
 +%    lda ciaData
 +%    sta (bufptr),y
 +%    iny
 +%    bne readByte
 +%    inc bufptr+1
 +%    dex
 +%    bne readByte
 +%    rts
 +%
 +% oldClock = 5
 +%
 +
 +Write an individual sector to disk, from a specified memory address.
 +
 +% writeSector = *  ;( bufptr, .A=track, .X=side, .Y=sector ) : .CS=err
 +%    pha
 +%    sty secnum
 +
 +Get the side into the burst command byte
 +
 +%    txa
 +%    and #$01
 +%    asl
 +%    asl
 +%    asl
 +%    asl
 +%    ora #$02
 +%    bit msType
 +%    bpl +
 +%    eor #$10
 +% +  jsr sendU0
 +%    pla
 +%    bcc +
 +%    rts
 +
 +Send rest of parameters for burst command.
 +
 +% +  jsr kernelCiout      ;track number
 +%    lda secnum           ;sector number
 +%    jsr kernelCiout
 +%    lda #1               ;sector count
 +%    jsr kernelCiout
 +%    jsr kernelUnlsn
 +%    sei
 +%    lda #$40
 +%    sta oldClock
 +%    sec
 +%    jsr kernelSpinp      ;set for burst output
 +%    sei
 +%    bit ciaFlags
 +%    ldx #2
 +%    ldy #0
 +%
 +
 +Write the 512 bytes for the sector.
 +
 +%    writeByte = *
 +%    lda ciaClock
 +%    cmp ciaClock
 +%    bne writeByte
 +%    eor oldClock
 +%    and #$40
 +%    beq writeByte
 +%    lda (bufptr),y
 +%    sta ciaData
 +%    lda oldClock
 +%    eor #$40
 +%    sta oldClock
 +%    lda #8
 +% -  bit ciaFlags
 +%    beq -
 +%    iny
 +%    bne writeByte
 +%    inc bufptr+1
 +%    dex
 +%    bne writeByte
 +%
 +
 +Read back the burst status byte to see if anything went wrong with the write.
 +
 +%    clc
 +%    jsr kernelSpinp
 +%    bit ciaFlags
 +%    jsr toggleClock
 +%    jsr serialWait
 +%    ldx ciaData
 +%    jsr toggleClock
 +%    txa
 +%    sta errno
 +%    and #$0f
 +%    cmp #2
 +%    cli
 +%    rts
 +%
 +
 +This next level of routines deals with logical sectors and the track cache
 +rather than with hardware.
 +
 +% ;====logical sector level====
 +%
 +
 +Invalidate the track cache if the MS-DOS drive number is changed or if a new
 +disk is inserted.  This routine has to establish a RAM configuration of $0E
 +since it will be called from RAM0.  Configuration $0E gives RAM0 from $0000 to
 +$BFFF, Kernal ROM from $C000 to $FFFF, and the I/O space over the Kernal from
 +$D000 to $DFFF.  This configuration is set by all application interface
 +subroutines.
 +
 +% initPackage = *
 +%    lda #$0e
 +%    sta $ff00
 +%    lda #$ff
 +%    sta bufCylinder
 +%    sta bufSide
 +%    ldx #7
 +% -  sta dirDirty,x
 +%    dex
 +%    bpl -
 +%    sta fatDirty
 +%    clc
 +%    rts
 +%
 +
 +Locate a sector (block) in the track cache, or read the corresponding physical
 +track into the track cache if necessary.  This routine accepts the cylinder,
 +side, and sector numbers of the block.
 +
 +% sectorSave = 5
 +%
 +% readBlock = *  ;( .A=cylinder,.X=side,.Y=sector ) : .AY=blkPtr,.CS=err
 +
 +Check if the correct track is in the track cache.
 +
 +%    cmp bufCylinder
 +%    bne readBlockPhysical
 +%    cpx bufSide
 +%    bne readBlockPhysical
 +
 +If so, then locate the sector's address and return that.
 +
 +%    dey
 +%    tya
 +%    asl
 +%    clc
 +%    adc #>trackbuf
 +%    tay
 +%    lda #<trackbuf
 +%    clc
 +%    rts
 +%
 +
 +Here, we have to read the physical track into the track cache.  We save the
 +input parameters and call the hardware-level track-reading routine.
 +
 +%    readBlockPhysical = *
 +%    sta bufCylinder
 +%    stx bufSide
 +%    sty sectorSave
 +%    jsr readTrack
 +
 +Check for errors.
 +
 +%    bcc readBlockPhysicalOk
 +%    lda errno
 +%    and #$0f
 +%    cmp #11    ;disk change
 +%    beq +
 +%    sec
 +%    rts
 +
 +If the error that happened is a "Disk Change" error, then mount the disk and
 +try to read the physical track again.
 +
 +% +  jsr mountDisk
 +%    lda bufCylinder
 +%    ldx bufSide
 +%    ldy sectorSave
 +%    bcc readBlockPhysical
 +%    rts
 +%
 +
 +Here, the physical track has been read into the track cache ok, so we recover
 +the original input parameters and try the top of the routine again.
 +
 +%    readBlockPhysicalOk = *
 +%    lda bufCylinder
 +%    ldx bufSide
 +%    ldy sectorSave
 +%    jmp readBlock
 +%
 +
 +Divide the given number by 18.  This is needed for the calculations to convert
 +a logical sector number to the corresponding physical cylinder, side, and
 +sector numbers that the lower-level routines require.  The method of repeated
 +subtraction is used.  This routine would probably work faster if we tried to
 +repeatedly subtract 360 (18*20) at the top, but I didn't bother.
 +
 +% divideBy18 = *  ;( .AY=number ) : .A=quotient, .Y=remainder
 +%    ;** could repeatedly subtract 360 here
 +%    ldx #$ff
 +% -  inx
 +%    sec
 +%    sbc #18
 +%    bcs -
 +%    dey
 +%    bpl -
 +%    clc
 +%    adc #18
 +%    iny
 +%    tay
 +%    txa
 +%    rts
 +%
 +
 +Convert the given logical block number to the corresponding physical cylinder,
 +side, and sector numbers.  This routine follows the formulae given in the
 +previous article with a few simplifying tricks.
 +
 +% convertLogicalBlockNum = *  ;( .AY=blockNum ) : .A=cyl, .X=side,.Y=sec
 +%    jsr divideBy18
 +%    ldx #0
 +%    cpy #9
 +%    bcc +
 +%    pha
 +%    tya
 +%    sbc #9
 +%    tay
 +%    pla
 +%    ldx #1
 +% +  iny
 +%    rts
 +%
 +
 +Copy a sequential group of logical sectors into memory.  This routine is used
 +by the directory loading routine to load the FAT and Root Directory, and is
 +used by the cluster reading routine to retrieve all of the blocks of a
 +cluster.  After the given starting logical sector number is converted into its
 +physical cylinder, side, and sector equivalent, the physical values are
 +incremented to get the address of successive sectors of the group.  This
 +avoids the overhead of the logical to physical conversion.  Quite a number of
 +temporaries are needed.
 +
 +% destPtr = 6
 +% curCylinder = 8
 +% curSide = 9
 +% curSector = 10
 +% blockCountdown = 11
 +% sourcePtr = 12
 +%
 +% copyBlocks = *  ;( .AY=startBlock, .X=blockCount, ($6)=dest ) : .CS=err
 +%    stx blockCountdown
 +%    jsr convertLogicalBlockNum
 +%    sta curCylinder
 +%    stx curSide
 +%    sty curSector
 +%
 +%    copyBlockLoop = *
 +%    lda curCylinder
 +%    ldx curSide
 +%    ldy curSector
 +%    jsr readBlock
 +%    bcc +
 +%    rts
 +% +  sta sourcePtr
 +%    sty sourcePtr+1
 +%    ldx #2
 +%    ldy #0
 +
 +Here I unroll the copying loop a little bit to cut the overhead of the branch
 +instruction in half.  (A cycle saved... you know).
 +
 +% -  lda (sourcePtr),y
 +%    sta (destPtr),y
 +%    iny
 +%    lda (sourcePtr),y
 +%    sta (destPtr),y
 +%    iny
 +%    bne -
 +%    inc sourcePtr+1
 +%    inc destPtr+1
 +%    dex
 +%    bne -
 +
 +Increment the cylinder, side, sector values.
 +
 +%    inc curSector
 +%    lda curSector
 +%    cmp #10
 +%    bcc +
 +%    lda #1
 +%    sta curSector
 +%    inc curSide
 +%    lda curSide
 +%    cmp #2
 +%    bcc +
 +%    lda #0
 +%    sta curSide
 +%    inc curCylinder
 +% +  dec blockCountdown
 +%    bne copyBlockLoop
 +%    clc
 +%    rts
 +%
 +
 +Convert a given cluster number into the first corresponding logical block
 +number.
 +
 +% convertClusterNum = *  ;( .AY=clusterNum ) : .AY=logicalBlockNum
 +%    sec
 +%    sbc #2
 +%    bcs +
 +%    dey
 +% +  ldx clusterBlockCount
 +%    cpx #1
 +%    beq +
 +%    asl
 +%    sty 7
 +%    rol 7
 +%    ldy 7
 +% +  clc
 +%    adc firstFileBlock
 +%    bcc +
 +%    iny
 +% +  rts
 +%
 +
 +Read a cluster into the Cluster Buffer, given the cluster number.  The cluster
 +number is converted to a logical sector number and then the sector copying
 +routine is called.  The formula given in the previous article is used.
 +
 +% readCluster = *  ;( .AY=clusterNumber ) : clusterBuf, .CS=err
 +%    jsr convertClusterNum
 +%
 +%    ;** read logical blocks comprising cluster
 +%    ldx #<clusterBuf
 +%    stx 6
 +%    ldx #>clusterBuf
 +%    stx 7
 +%    ldx clusterBlockCount
 +%    jmp copyBlocks
 +%
 +
 +Write a logical block out to disk.  The real purpose of this routine is to
 +invalidate the read-track cache if the block to be written is contained in
 +the cache.
 +
 +% writeLogicalBlock = *  ;( .AY=logicalBlockNumber, bufptr ) : .CS=err
 +%    jsr convertLogicalBlockNum
 +%    cmp bufCylinder
 +%    bne +
 +%    cpx bufSide
 +%    bne +
 +%    pha
 +%    lda #$ff
 +%    sta bufCylinder
 +%    sta bufSide
 +%    pla
 +% +  jsr writeSector
 +%    rts
 +%
 +% writeClusterSave .buf 2
 +%
 +
 +Write a cluster-ful of data out to disk from the cluster buffer.  This routine
 +simply calls the write logical block routine once or twice, depending on the
 +cluster size of the disk involved.
 +
 +% writeCluster = *  ;( .AY=clusterNumber, clusterBuf ) : .CS=err
 +%    jsr convertClusterNum
 +%    ldx #<clusterBuf
 +%    stx bufptr
 +%    ldx #>clusterBuf
 +%    stx bufptr+1
 +%    sta writeClusterSave
 +%    sty writeClusterSave+1
 +%    jsr writeLogicalBlock
 +%    bcc +
 +%    rts
 +% +  lda clusterBlockCount
 +%    cmp #2
 +%    bcs +
 +%    rts
 +% +  lda writeClusterSave
 +%    ldy writeClusterSave+1
 +%    clc
 +%    adc #1
 +%    bcc +
 +%    iny
 +% +  jsr writeLogicalBlock
 +%    rts
 +%
 +
 +This next level of routines deal with the data structures of the MS-DOS disk
 +format.
 +
 +% ;====MS-DOS format level====
 +%
 +% bootBlock = 2
 +%
 +
 +Read the disk format parameters, directory, and FAT into memory.
 +
 +% msDir = *  ;( ) : .AY=dirbuf, .X=dirEntries, .CS=err
 +%    lda #$0e
 +%    sta $ff00
 +%
 +
 +Read the boot sector and extract the parameters.
 +
 +%    ;** get parameters from boot sector
 +%    lda #0
 +%    ldy #0
 +%    jsr convertLogicalBlockNum
 +%    jsr readBlock
 +%    bcc +
 +%    rts
 +% +  sta bootBlock
 +%    sty bootBlock+1
 +%    ldy #13              ;get cluster size
 +%    lda (bootBlock),y
 +%    sta clusterBlockCount
 +%    cmp #3
 +%    bcc +
 +%
 +
 +If a disk parameter is found to exceed the limits of LRR, error code #60 is
 +returned.
 +
 +%    invalidParms = *
 +%    lda #60
 +%    sta errno
 +%    sec
 +%    rts
 +%
 +% +  ldy #16              ;check FAT replication count, must be 2
 +%    lda (bootBlock),y
 +%    cmp #2
 +%    bne invalidParms
 +%    ldy #22              ;get FAT size in sectors
 +%    lda (bootBlock),y
 +%    sta fatBlocks
 +%    cmp #4
 +%    bcs invalidParms
 +%    ldy #17              ;get directory size
 +%    lda (bootBlock),y
 +%    sta rootDirEntries
 +%    cmp #129
 +%    bcs invalidParms
 +%    lsr
 +%    lsr
 +%    lsr
 +%    lsr
 +%    sta rootDirBlocks
 +%    ldy #19              ;get total sector count
 +%    lda (bootBlock),y
 +%    sta totalSectors
 +%    iny
 +%    lda (bootBlock),y
 +%    sta totalSectors+1
 +%    ldy #24              ;check sectors per track, must be 9
 +%    lda (bootBlock),y
 +%    cmp #9
 +%    bne invalidParms
 +%    ldy #26
 +%    lda (bootBlock),y
 +%    cmp #2               ;check number of sides, must be 2
 +%    bne invalidParms
 +%    ldy #14              ;check number of boot sectors, must be 1
 +%    lda (bootBlock),y
 +%    cmp #1
 +%    bne invalidParms
 +%
 +
 +Calculate the derived parameters.
 +
 +%    ;** get derived parameters
 +%    lda fatBlocks        ;first root directory sector
 +%    asl
 +%    clc
 +%    adc #1
 +%    sta firstRootDirBlock
 +%    clc                  ;first file sector
 +%    adc rootDirBlocks
 +%    sta firstFileBlock
 +%    lda totalSectors     ;number of file clusters
 +%    ldy totalSectors+1
 +%    sec
 +%    sbc firstFileBlock
 +%    bcs +
 +%    dey
 +% +  sta fileClusterCount
 +%    sty fileClusterCount+1
 +%    lda clusterBlockCount
 +%    cmp #2
 +%    bne +
 +%    lsr fileClusterCount+1
 +%    ror fileClusterCount
 +% +  clc
 +%    lda fileClusterCount
 +%    adc #2
 +%    sta lastFatEntry
 +%    lda fileClusterCount+1
 +%    adc #0
 +%    sta lastFatEntry+1
 +%
 +%    ;** load FAT
 +%    lda #<fatbuf
 +%    ldy #>fatbuf
 +%    sta 6
 +%    sty 7
 +%    lda #1
 +%    ldy #0
 +%    ldx fatBlocks
 +%    jsr copyBlocks
 +%    bcc +
 +%    rts
 +%
 +%    ;** load actual directory
 +% +  lda #<dirbuf
 +%    ldy #>dirbuf
 +%    sta 6
 +%    sty 7
 +%    lda firstRootDirBlock
 +%    ldy #0
 +%    ldx rootDirBlocks
 +%    jsr copyBlocks
 +%    bcc +
 +%    rts
 +% +  lda #<dirbuf
 +%    ldy #>dirbuf
 +%    ldx rootDirEntries
 +%    clc
 +%    rts
 +%
 +
 +This routine locates the given FAT table entry number and returns the value
 +stored in it.  Some work is needed to deal with the 12-bit compressed data
 +structure.
 +
 +% entryAddr = 2
 +% entryWork = 4
 +% entryBits = 5
 +% entryData0 = 6
 +% entryData1 = 7
 +% entryData2 = 8
 +%
 +% locateFatEntry = *  ;( .AY=fatEntryNumber ) : entryAddr, entryBits%1
 +
 +Divide the FAT entry number by two and multiply by three because two FAT
 +entries are stored in three bytes.  Then add the FAT base address and we have
 +the address of the three bytes that contain the FAT entry we are interested
 +in.  I retrieve the three bytes into zero-page memory for easy manipulation.
 +
 +%    sta entryBits
 +%    ;** divide by two
 +%    sty entryAddr+1
 +%    lsr entryAddr+1
 +%    ror
 +%
 +%    ;** times three
 +%    sta entryWork
 +%    ldx entryAddr+1
 +%    asl
 +%    rol entryAddr+1
 +%    clc
 +%    adc entryWork
 +%    sta entryAddr
 +%    txa
 +%    adc entryAddr+1
 +%    sta entryAddr+1
 +%
 +%    ;** add base, get data
 +%    clc
 +%    lda entryAddr
 +%    adc #<fatbuf
 +%    sta entryAddr
 +%    lda entryAddr+1
 +%    adc #>fatbuf
 +%    sta entryAddr+1
 +%    ldy #2
 +% -  lda (entryAddr),y
 +%    sta entryData0,y
 +%    dey
 +%    bpl -
 +%    rts
 +%
 +% getFatEntry = *  ;( .AY=fatEntryNumber ) : .AY=fatEntryValue
 +%    jsr locateFatEntry
 +%    lda entryBits
 +%    and #1
 +%    bne +
 +%
 +
 +If the original given FAT entry number is even, then we want the first 12-bit
 +compressed field.  The nybbles are extracted according to the diagram shown
 +earlier.
 +
 +%    ;** case 1: first 12-bit cluster
 +%    lda entryData1
 +%    and #$0f
 +%    tay
 +%    lda entryData0
 +%    rts
 +%
 +
 +Otherwise, we want the second 12-bit field.
 +
 +%    ;** case 2: second 12-bit cluster
 +% +  lda entryData1
 +%    ldx #4
 +% -  lsr entryData2
 +%    ror
 +%    dex
 +%    bne -
 +%    ldy entryData2
 +%    rts
 +%
 +% fatValue = 9
 +%
 +
 +Change the value in a FAT entry.  This routine is quite similar to the get
 +routine.
 +
 +% setFatEntry = *  ;( .AY=fatEntryNumber, (fatValue) )
 +%    jsr locateFatEntry
 +%    lda fatValue+1
 +%    and #$0f
 +%    sta fatValue+1
 +%    lda entryBits
 +%    and #1
 +%    bne +
 +%
 +%    ;** case 1: first 12-bit cluster
 +%    lda fatValue
 +%    sta entryData0
 +%    lda entryData1
 +%    and #$f0
 +%    ora fatValue+1
 +%    sta entryData1
 +%    jmp setFatExit
 +%
 +%    ;** case 2: second 12-bit cluster
 +% +  ldx #4
 +% -  asl fatValue
 +%    rol fatValue+1
 +%    dex
 +%    bne -
 +%    lda fatValue+1
 +%    sta entryData2
 +%    lda entryData1
 +%    and #$0f
 +%    ora fatValue
 +%    sta entryData1
 +%
 +%    setFatExit = *
 +%    ldy #2
 +% -  lda entryData0,y
 +%    sta (entryAddr),y
 +%    dey
 +%    bpl -
 +%    sty fatDirty
 +%    rts
 +%
 +
 +Mark the directory sector corresponding to the given directory entry as being
 +dirty so it will be written out to disk the next time the msFlush routine is
 +called.
 +
 +% dirtyDirent = *  ;( writeDirent )
 +%    sec
 +%    lda writeDirent
 +%    sbc #<dirbuf
 +%    lda writeDirent+1
 +%    sbc #>dirbuf
 +%    lsr
 +%    and #$07
 +%    tax
 +%    lda #$ff
 +%    sta dirDirty,x
 +%    rts
 +%
 +% delCluster = 14
 +%
 +
 +Delete the MS-DOS file whose directory entry is given.  Put the $E5 into
 +its filename, get its starting cluster and follow the chain of clusters
 +allocated to the file in the FAT, marking them as unallocated (value $000)
 +as we go.  Exit by marking the directory entry as "dirty".
 +
 +% msDelete = *  ;( writeDirent )
 +%    ldy #$0e
 +%    sty $ff00
 +%    lda writeDirent
 +%    ldy writeDirent+1
 +%    sta 2
 +%    sty 3
 +%    lda #$e5
 +%    ldy #0
 +%    sta (2),y
 +%    ldy #26
 +%    lda (2),y
 +%    sta delCluster
 +%    iny
 +%    lda (2),y
 +%    sta delCluster+1
 +% -  lda delCluster+1
 +%    cmp #5
 +%    bcc +
 +%    jmp dirtyDirent
 +% +  tay
 +%    lda delCluster
 +%    jsr getFatEntry
 +%    pha
 +%    tya
 +%    pha
 +%    lda #0
 +%    sta fatValue
 +%    sta fatValue+1
 +%    lda delCluster
 +%    ldy delCluster+1
 +%    jsr setFatEntry
 +%    pla
 +%    sta delCluster+1
 +%    pla
 +%    sta delCluster
 +%    jmp -
 +%
 +% flushBlock = 14
 +% flushCountdown = $60
 +% flushRepeats = $61
 +% flushDirIndex = $61
 +%
 +
 +Write the FAT and directory sectors from memory to disk, if they are dirty.
 +
 +% msFlush = *  ;( msDevice, msType ) : .CS=error
 +%    lda #$0e
 +%    sta $ff00
 +%    lda fatDirty
 +%    beq flushDirectory
 +%    lda #0
 +%    sta fatDirty
 +%
 +%    ;** flush fat
 +
 +Flush both copies of the FAT, if there are two; otherwise, only flush the one.
 +
 +%    lda #2
 +%    sta flushRepeats
 +%    lda #1
 +%    sta flushBlock
 +%
 +%    masterFlush = *
 +%    lda fatBlocks
 +%    sta flushCountdown
 +%    lda #<fatbuf
 +%    ldy #>fatbuf
 +%    sta bufptr
 +%    sty bufptr+1
 +% -  lda flushBlock
 +%    ldy #0
 +%    jsr writeLogicalBlock
 +%    bcc +
 +%    rts
 +% +  inc flushBlock
 +%    dec flushCountdown
 +%    bne -
 +%    dec flushRepeats
 +%    bne masterFlush
 +%
 +%    ;** flush directory
 +%    flushDirectory = *
 +%    lda firstRootDirBlock
 +%    sta flushBlock
 +%    lda rootDirBlocks
 +%    sta flushCountdown
 +%    lda #0
 +%    sta flushDirIndex
 +%    lda #<dirbuf
 +%    ldy #>dirbuf
 +%    sta bufptr
 +%    sty bufptr+1
 +% -  ldx flushDirIndex
 +%    lda dirDirty,x
 +%    beq +
 +%    lda #0
 +%    sta dirDirty,x
 +%    lda flushBlock
 +%    ldy #0
 +%    jsr writeLogicalBlock
 +%    dec bufptr+1
 +%    dec bufptr+1
 +% +  inc flushBlock
 +%    inc flushDirIndex
 +%    inc bufptr+1
 +%    inc bufptr+1
 +%    dec flushCountdown
 +%    bne -
 +%    clc
 +%    rts
 +%
 +% bfFatEntry = 14
 +% bfBlocks = $60
 +%
 +
 +Count the number of free FAT entries (value $000) from entry 2 up to the
 +highest FAT entry available for cluster allocation.  Then multiply this
 +by the number of bytes per cluster (either 512 or 1024).
 +
 +% msBytesFree = *  ;( ) : .AYX=fileBytesFree
 +%    ldy #$0e
 +%    sty $ff00
 +%    lda #2
 +%    ldy #0
 +%    sta bfFatEntry
 +%    sty bfFatEntry+1
 +%    sty bfBlocks
 +%    sty bfBlocks+1
 +% -  lda bfFatEntry
 +%    ldy bfFatEntry+1
 +%    jsr getFatEntry
 +%    sty 2
 +%    ora 2
 +%    bne +
 +%    inc bfBlocks
 +%    bne +
 +%    inc bfBlocks+1
 +% +  inc bfFatEntry
 +%    bne +
 +%    inc bfFatEntry+1
 +% +  lda bfFatEntry
 +%    cmp lastFatEntry
 +%    lda bfFatEntry+1
 +%    sbc lastFatEntry+1
 +%    bcc -
 +%    ldx clusterBlockCount
 +% -  asl bfBlocks
 +%    rol bfBlocks+1
 +%    dex
 +%    bne -
 +%    lda #0
 +%    ldy bfBlocks
 +%    ldx bfBlocks+1
 +%    rts
 +%
 +
 +This is the file copying level.  It deals with reading/writing the clusters of
 +MS-DOS files and copying the data they contain to/from the already-open CBM
 +Kernal file, possibly with ASCII/PETSCII translation.
 +
 +% ;====file copy level====
 +%
 +% transMode = 14
 +% lfn = 15
 +% cbmDataPtr = $60
 +% cbmDataLen = $62
 +% cluster = $64
 +%
 +
 +Copy the given cluster to the CBM output file.  This routine fetches the next
 +cluster of the file for the next time this routine is called, and if it hits
 +the NULL pointer of the last cluster of a file, it adjusts the number of valid
 +file data bytes the current cluster contains to FileLength % ClusterLength
 +(see note below).
 +
 +% copyFileCluster = *  ;( cluster, lfn, transMode ) : .CS=err
 +
 +Read the cluster and setup to copy the whole cluster to the CBM file.
 +
 +%    lda cluster
 +%    ldy cluster+1
 +%    jsr readCluster
 +%    bcc +
 +%    rts
 +% +  lda #<clusterBuf
 +%    ldy #>clusterBuf
 +%    sta cbmDataPtr
 +%    sty cbmDataPtr+1
 +%    lda #0
 +%    sta cbmDataLen
 +%    lda clusterBlockCount
 +%    asl
 +%    sta cbmDataLen+1
 +%
 +
 +Fetch the next cluster number of the file, and adjust the cluster data length
 +for the last cluster of the file.
 +
 +%    ;**get next cluster
 +%    lda cluster
 +%    ldy cluster+1
 +%    jsr getFatEntry
 +%    sta cluster
 +%    sty cluster+1
 +%    cpy #$05
 +%    bcc copyFileClusterData
 +%    lda lenML
 +%    sta cbmDataLen
 +%    lda #$01
 +%    ldx clusterBlockCount
 +%    cpx #1
 +%    beq +
 +%    lda #$03
 +% +  and lenML+1
 +
 +The following three lines were added in a last minute panic after realizing
 +that if FileLength % ClusterSize == 0, then the last cluster of the file
 +contains ClusterSize bytes, not zero bytes.
 +
 +%    bne +
 +%    ldx lenML
 +%    beq copyFileClusterData
 +% +  sta cbmDataLen+1
 +%
 +%    copyFileClusterData = *
 +%    jsr commieOut
 +%    rts
 +%
 +
 +Copy the file data in the MS-DOS cluster buffer to the CBM output file.
 +
 +% cbmDataLimit = $66
 +%
 +% commieOut = *  ;( cbmDataPtr, cbmDataLen ) : .CS=err
 +
 +If the the logical file number to copy to is 0 ("null device"), then don't
 +bother copying anything.
 +
 +%    ldx lfn
 +%    bne +
 +%    clc
 +%    rts
 +
 +Otherwise, prepare the logical file number for output.
 +
 +% +  jsr kernelChkout
 +%    bcc commieOutMore
 +%    sta errno
 +%    rts
 +%
 +
 +Process the cluster data in chunks of up to 255 bytes or the number of data
 +bytes remaining in the cluster.
 +
 +%    commieOutMore = *
 +%    lda #255
 +%    ldx cbmDataLen+1
 +%    bne +
 +%    lda cbmDataLen
 +% +  sta cbmDataLimit
 +%    ldy #0
 +% -  lda (cbmDataPtr),y
 +%    bit transMode
 +%    bpl +
 +
 +If we have to translate the current ASCII character, look up the PETSCII value
 +in the translation table and output that value.  If the translation table
 +entry value is $00, then don't output a character (filter out invalid
 +character codes).
 +
 +%    tax
 +%    lda transBuf,x
 +%    beq commieNext
 +% +  jsr kernelChrout
 +%    commieNext = *
 +%    iny
 +%    cpy cbmDataLimit
 +%    bne -
 +%
 +
 +Increment the cluster buffer pointer and decrement the cluster buffer character
 +count according to the number of bytes just processed, and repeat the above if
 +more file data remains in the current cluster.
 +
 +%    clc
 +%    lda cbmDataPtr
 +%    adc cbmDataLimit
 +%    sta cbmDataPtr
 +%    bcc +
 +%    inc cbmDataPtr+1
 +% +  sec
 +%    lda cbmDataLen
 +%    sbc cbmDataLimit
 +%    sta cbmDataLen
 +%    bcs +
 +%    dec cbmDataLen+1
 +% +  lda cbmDataLen
 +%    ora cbmDataLen+1
 +%    bne commieOutMore
 +
 +If we are finished with the cluster, then clear the CBM Kernal output channel.
 +
 +%    jsr kernelClrchn
 +%    clc
 +%    rts
 +%
 +
 +The file copying main routine.  Set up for the starting cluster, and call
 +the cluster copying routine until end-of-file is reached.  Checks for a
 +NULL cluster pointer in the directory entry to handle zero-length files.
 +
 +% msRead = *  ;( cluster, lenML, .A=transMode, .X=lfn ) : .CS=err
 +%    ldy #$0e
 +%    sty $ff00
 +%    sta transMode
 +%    stx lfn
 +%    lda startCluster
 +%    ldy startCluster+1
 +%    sta cluster
 +%    sty cluster+1
 +%    jmp +
 +% -  jsr copyFileCluster
 +%    bcc +
 +%    rts
 +% +  lda cluster+1
 +%    cmp #$05
 +%    bcc -
 +%    clc
 +%    rts
 +%
 +% inLfn = $50
 +% generateLf = $51
 +% cbmDataMax = $52
 +% reachedEof = $54
 +% prevSt = $55
 +%
 +
 +Set the translation and input logical file number and set up for reading
 +from a CBM-Kernal input file.
 +
 +% commieInInit = *  ;( .A=transMode, .X=inLfn )
 +%    sta transMode
 +%    stx inLfn
 +%    lda #0
 +%    sta generateLf
 +%    sta reachedEof
 +%    sta prevSt
 +%    rts
 +%
 +
 +Read up to "cbmDataMax" bytes into the specified buffer from the established
 +CBM logical file number.  The number of bytes read is returned in
 +"cbmDataLen" If end of file occurs, "cbmDataLen" will be zero and the .Z
 +flag will be set.  Regular error return.
 +
 +% commieIn = *  ;( cbmDataPtr++, cbmDataMax ) : cbmDataLen, .CS=err, .Z=eof
 +
 +Establish input file, or return immediately if already past eof.
 +
 +%    lda #0
 +%    sta cbmDataLen
 +%    sta cbmDataLen+1
 +%    ldx reachedEof
 +%    beq +
 +%    lda #0
 +%    clc
 +%    rts
 +% +  ldx inLfn
 +%    jsr kernelChkin
 +%    bcc commieInMore
 +%    sta errno
 +%    rts
 +%
 +
 +Read next chunk of up to 255 bytes into input buffer.
 +
 +%    commieInMore = *
 +%    lda #255
 +%    ldx cbmDataMax+1
 +%    bne +
 +%    lda cbmDataMax
 +% +  sta cbmDataLimit
 +%    ldy #0
 +% -  jsr commieInByte
 +%    bcc +
 +%    rts
 +% +  beq +
 +%    sta (cbmDataPtr),y
 +%    iny
 +%    cpy cbmDataLimit
 +%    bne -
 +%
 +
 +Prepare to read another chunk, or exit.
 +
 +% +  sty cbmDataLimit
 +%    clc
 +%    lda cbmDataPtr
 +%    adc cbmDataLimit
 +%    sta cbmDataPtr
 +%    bcc +
 +%    inc cbmDataPtr+1
 +% +  clc
 +%    lda cbmDataLen
 +%    adc cbmDataLimit
 +%    sta cbmDataLen
 +%    bcc +
 +%    inc cbmDataLen+1
 +% +  sec
 +%    lda cbmDataMax
 +%    sbc cbmDataLimit
 +%    sta cbmDataMax
 +%    bcs +
 +%    dec cbmDataMax+1
 +% +  lda reachedEof
 +%    bne +
 +%    lda cbmDataMax
 +%    ora cbmDataMax+1
 +%    bne commieInMore
 +
 +Shut down reading and exit.
 +
 +% +  jsr kernelClrchn
 +%    lda cbmDataLen
 +%    ora cbmDataLen+1
 +%    clc
 +%    rts
 +%
 +
 +Read a single byte from the CBM-Kernal input logical file number.  Translate
 +character into ASCII and expand CR into CR+LF if necessary.  Return EOF if
 +previous character returned was last from disk input channel.
 +
 +% commieInByte = *  ;( ) : .A=char, .CS=err, .Z=eof, reachedEof
 +%    ;** check for already past eof
 +%    lda reachedEof
 +%    beq +
 +%    brk
 +%    ;** check for generated linefeed
 +% +  lda generateLf
 +%    beq +
 +%    lda #0
 +%    sta generateLf
 +%    lda #$0a
 +%    clc
 +%    rts
 +%    ;** check for eof
 +% +  lda prevSt
 +%    and #$40
 +%    beq +
 +%    lda #$ff
 +%    sta reachedEof
 +%    lda #0
 +%    clc
 +%    rts
 +%    ;** read actual character
 +% +  jsr kernelChrin
 +%    ldx st
 +%    stx prevSt
 +%    bcc +
 +%    sta errno
 +%    jsr kernelClrchn
 +%    rts
 +%    ;** translate if necessary
 +% +  bit transMode
 +%    bpl +
 +%    tax
 +%    lda transBufToAscii,x
 +%    beq commieInByte
 +
 +Note here that the translated character is checked to see if it is a carriage
 +return, rather than checking the non-translated character, to see if a
 +linefeed must be generated next.  Thus, you could define that a Commodore
 +carriage return be translated into a linefeed (for Unix) and no additional
 +unwanted linefeed would be generated.
 +
 +%    cmp #$0d
 +%    bne +
 +%    sta generateLf
 +%    ;** exit
 +% +  ldx #$ff
 +%    clc
 +%    rts
 +%
 +% firstFreeFatEntry = $5a
 +%
 +
 +Search FAT for a free cluster, and return the cluster (FAT entry) number.  A
 +global variable "firstFreeFarEntry" is maintained which points to the first
 +FAT entry that could possibly be free, to avoid wasting time searching from
 +the very beginning of the FAT every time.  Clusters are allocated in
 +first-free order.
 +
 +% allocateFatEntry = *  ;( ) : .AY=fatEntry, .CS=err
 +% -  lda firstFreeFatEntry
 +%    cmp lastFatEntry
 +%    lda firstFreeFatEntry+1
 +%    sbc lastFatEntry+1
 +%    bcc +
 +%    rts
 +% +  lda firstFreeFatEntry
 +%    ldy firstFreeFatEntry+1
 +%    jsr getFatEntry
 +%    sty 2
 +%    ora 2
 +%    bne +
 +%    lda firstFreeFatEntry
 +%    ldy firstFreeFatEntry+1
 +%    clc
 +%    rts
 +% +  inc firstFreeFatEntry
 +%    bne -
 +%    inc firstFreeFatEntry+1
 +%    jmp -
 +%
 +% msFileLength = $5c  ;(3 bytes)
 +%
 +
 +Allocate a new cluster to a file, link it into the file cluster chain, and
 +write the cluster buffer to disk in that cluster, adding "cbmDataLen" bytes
 +to the file.
 +
 +% msWriteCluster = *  ; (*) : .CS=err
 +%    ;** get a new cluster
 +%    jsr allocateFatEntry
 +%    bcc +
 +%    rts
 +%    ;** make previous fat entry point to new cluster
 +% +  sta fatValue
 +%    sty fatValue+1
 +%    lda cluster
 +%    ora cluster+1
 +%    beq +
 +%    lda cluster
 +%    ldy cluster+1
 +%    ldx fatValue
 +%    stx cluster
 +%    ldx fatValue+1
 +%    stx cluster+1
 +%    jsr setFatEntry
 +%    jmp msClusterNew
 +
 +Handle case of no previous cluster - make directory entry point to new
 +cluster.
 +
 +% +  lda writeDirent
 +%    ldy writeDirent+1
 +%    sta 2
 +%    sty 3
 +%    ldy #26
 +%    lda fatValue
 +%    sta (2),y
 +%    sta cluster
 +%    iny
 +%    lda fatValue+1
 +%    sta (2),y
 +%    sta cluster+1
 +%
 +%    ;** make new fat entry point to null
 +%    msClusterNew = *
 +%    lda #$ff
 +%    ldy #$0f
 +%    sta fatValue
 +%    sty fatValue+1
 +%    lda cluster
 +%    ldy cluster+1
 +%    jsr setFatEntry
 +%    ;** write new cluster data
 +% +  lda cluster
 +%    ldy cluster+1
 +%    jsr writeCluster
 +%    bcc +
 +%    rts
 +%    ;** add cluster length to file length
 +% +  clc
 +%    lda msFileLength
 +%    adc cbmDataLen
 +%    sta msFileLength
 +%    lda msFileLength+1
 +%    adc cbmDataLen+1
 +%    sta msFileLength+1
 +%    bcc +
 +%    inc msFileLength+2
 +% +  clc
 +%    rts
 +%
 +
 +Copy a CBM-Kernal file to an MS-DOS file, possibly with translation.
 +
 +% msWrite = *  ;( msDevice, msType, writeDirent, .A=trans, .X=cbmLfn ) :.CS=err
 +%    ldy #$0e
 +%    sty $ff00
 +%    ;** initialize
 +
 +Set input file translation and logical file number, init cluster, file length,
 +FAT allocation first free pointer (to cluster #2, the first data cluster).
 +
 +%    jsr commieInInit
 +%    lda #0
 +%    sta cluster
 +%    sta cluster+1
 +%    sta firstFreeFatEntry+1
 +%    sta msFileLength
 +%    sta msFileLength+1
 +%    sta msFileLength+2
 +%    lda #2
 +%    sta firstFreeFatEntry
 +%
 +%    ;** copy cluster from cbm file
 +% -  lda #<clusterBuf
 +%    ldy #>clusterBuf
 +%    sta cbmDataPtr
 +%    sty cbmDataPtr+1
 +%    lda clusterBlockCount
 +%    asl
 +%    tay
 +%    lda #0
 +%    sta cbmDataMax
 +%    sty cbmDataMax+1
 +%    jsr commieIn
 +%    bcc +
 +%    rts
 +% +  beq +
 +%    jsr msWriteCluster
 +%    bcc -
 +%    rts
 +%
 +%    ;** wrap up after writing - set file length, dirty flag, exit.
 +% +  lda writeDirent
 +%    ldy writeDirent+1
 +%    sta 2
 +%    sty 3
 +%    ldx #0
 +%    ldy #28
 +% -  lda msFileLength,x
 +%    sta (2),y
 +%    iny
 +%    inx
 +%    cpx #3
 +%    bcc -
 +%    jsr dirtyDirent
 +%    clc
 +%    rts
 +%
 +
 +This level deals exclusively with Commodore files.
 +
 +% ;===== commodore file level =====
 +%
 +
 +Copy from an input disk logical file number to an output lfn, in up to 1024
 +byte chunks.  This routine makes use of the existing "commieIn" and
 +"commieOut" routines.  No file translation is available; binary translation is
 +used for both commieIn and commieOut.
 +
 +% cbmCopy = *  ;( .A=inLfn, .X=outLfn )
 +%    ldy #$0e
 +%    sty $ff00
 +%    stx lfn
 +%    tax
 +%    lda #0
 +%    jsr commieInInit
 +% -  lda #<clusterBuf
 +%    ldy #>clusterBuf
 +%    sta cbmDataPtr
 +%    sty cbmDataPtr+1
 +%    lda #<1024
 +%    ldy #>1024
 +%    sta cbmDataMax
 +%    sty cbmDataMax+1
 +%    jsr commieIn
 +%    bcs +
 +%    beq +
 +%    lda #<clusterBuf
 +%    ldy #>clusterBuf
 +%    sta cbmDataPtr
 +%    sty cbmDataPtr+1
 +%    jsr commieOut
 +%    bcs +
 +%    jmp -
 +% +  rts
 +%
 +
 +Read a single directory entry from the given logical file number, which is
 +assumed to be open for reading a directory ("$").  The data of the directory
 +entry are returned in the interface variables.
 +
 +% cbmDirent = *  ;( .A=lfn )
 +
 +Initialize.
 +
 +%    ldy #$0e
 +%    sty $ff00
 +%    tax
 +%    jsr kernelChkin
 +%    bcc +
 +%    cdirErr = *
 +%    lda #0
 +%    sta cdirFlen
 +%    sta cdirBlocks
 +%    sta cdirBlocks+1
 +%    rts
 +%    ;** get block count
 +% +  jsr cdirGetch
 +%    jsr cdirGetch
 +%    jsr cdirGetch
 +%    sta cdirBlocks
 +%    jsr cdirGetch
 +%    sta cdirBlocks+1
 +%    ;** look for filename
 +%    lda #0
 +%    sta cdirFlen
 +% -  jsr cdirGetch
 +%    cmp #34
 +%    beq +
 +%    cmp #"b"
 +%    bne -
 +%    jsr kernelClrchn
 +%    rts
 +%    ;** get filename
 +% +  ldy #0
 +% -  jsr cdirGetch
 +%    cmp #34
 +%    beq +
 +%    sta cdirName,y
 +%    iny
 +%    bne -
 +% +  sty cdirFlen
 +
 +Look for and get file type.
 +
 +% -  jsr cdirGetch
 +%    cmp #" "
 +%    beq -
 +%    sta cdirType
 +
 +Scan for end of directory entry, return.
 +
 +% -  jsr cdirGetch
 +%    cmp #0
 +%    bne -
 +%    jsr kernelClrchn
 +%    rts
 +%
 +
 +Get a single character of the directory entry, watching for end of file (which
 +would indicate error here).
 +
 +% cdirGetch = *
 +%    jsr kernelChrin
 +%    bcs +
 +%    bit st
 +%    bvs +
 +%    rts
 +% +  pla
 +%    pla
 +%    jsr kernelClrchn
 +%    jmp cdirErr
 +%
 +% ;===== data =====
 +%
 +
 +This is the translation table used to convert from ASCII to PETSCII.  You can
 +modify it to suit your needs if you wish.  If you cannot reassemble this file,
 +then you can sift through the binary file and locate the table and change it
 +there.  An entry of $00 means the corresponding ASCII character will not be
 +translated.  You'll notice that I have set up translations for the following
 +ASCII control characters into PETSCII: Backspace, Tab, Linefeed (CR), and
 +Formfeed.  I also translate the non-PETSCII characters such as {, |, ~, and _
 +according to what they probably would have been if Commodore wasn't so
 +concerned with the graphics characters.
 +
 +% transBuf = *
 +%        ;0                               f
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$14,$09,$0d,$00,$93,$00,$00,$00 ;0
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;1
 +% .byte $20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$2a,$2b,$2c,$2d,$2e,$2f ;2
 +% .byte $30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3a,$3b,$3c,$3d,$3e,$3f ;3
 +% .byte $40,$c1,$c2,$c3,$c4,$c5,$c6,$c7,$c8,$c9,$ca,$cb,$cc,$cd,$ce,$cf ;4
 +% .byte $d0,$d1,$d2,$d3,$d4,$d5,$d6,$d7,$d8,$d9,$da,$5b,$5c,$5d,$5e,$5f ;5
 +% .byte $c0,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f ;6
 +% .byte $50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$5a,$db,$dc,$dd,$de,$df ;7
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;8
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;9
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;a
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;b
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;c
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;d
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;e
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;f
 +%
 +
 +This is the translation table used to convert from PETSCII to ASCII.  You can
 +modify it to suit your needs, similar to the ASCII to PETSCII table.  An entry
 +of $00 means the corresponding PETSCII character will not be translated.
 +You'll notice that I have set up translations for the following PETSCII
 +control characters into ASCII: Delete (into Backspace), Tab, Carriage Return
 +(into CR+LF), and ClearScreen (into Fordfeed).  Appropriate translations into
 +the ASCII characters {, }, ^, _, ~, \, and | are also set up.
 +
 +% transBufToAscii = *
 +%        ;0                               f
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$09,$00,$00,$00,$0d,$00,$00 ;0
 +% .byte $00,$00,$00,$00,$08,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;1
 +% .byte $20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$2a,$2b,$2c,$2d,$2e,$2f ;2
 +% .byte $30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$3a,$3b,$3c,$3d,$3e,$3f ;3
 +% .byte $40,$61,$62,$63,$64,$65,$66,$67,$68,$69,$6a,$6b,$6c,$6d,$6e,$6f ;4
 +% .byte $70,$71,$72,$73,$74,$75,$76,$77,$78,$79,$7a,$5b,$5c,$5d,$5e,$5f ;5
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;6
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;7
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;8
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;9
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;a
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;b
 +% .byte $60,$41,$42,$43,$44,$45,$46,$47,$48,$49,$4a,$4b,$4c,$4d,$4e,$4f ;c
 +% .byte $50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$5a,$7b,$7c,$7d,$7e,$7f ;d
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00 ;e
 +% .byte $00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$00,$7e ;f
 +%
 +% ;====bss storage (size=11,264 bytes)====
 +%
 +
 +This is where the track cache, etc. are stored.  This section requires 11K of
 +storage space but does not increase the length of the binary program file
 +since these storage areas are DEFINED rather than allocated with ".buf"
 +directives.  The Unix terminology for this type of uninitialized data is
 +"bss".
 +
 +% bss = *
 +% trackbuf   = bss
 +% clusterBuf = trackbuf+4608
 +% fatbuf     = clusterBuf+1024
 +% dirbuf     = fatbuf+1536
 +% end        = dirbuf+4096
 +
 +5. USER-INTERFACE PROGRAM
 +
 +This section presents the listing of the user-interface BASIC program.  You
 +should be aware that you can easily change some of the defaults to your own
 +preferences if you wish.  In particular, you may wish to change the "dv" and
 +"dt" variables in lines 25 and 26.  This program is not listed in the "%"
 +format that the assembler listing is since you can recover this listing from
 +the uuencoded binary program file.  The listing is here in its entirety.
 +
 +10 print chr$(147);"little red reader 128 version 1.00"
 +11 print : print"by craig bruce 09-feb-93 for c=hacking" : print
 +12 :
 +20 cd=peek(186):if cd<8 then cd=8 : rem ** default cbm-dos drive **
 +25 dv=9:dt=0 :  rem ** ms-dos drive, type (0=1571,255=1581)
 +26 if dv=cd then dv=8:dt=0 : rem ** alternate ms-dos drive
 +27 :
 +30 print "initializing..." : print
 +40 bank0 : pk=dec("8000") : pv=pk+30
 +50 if peek(pv+0)=dec("cb") and peek(pv+1)=132 then 60
 +55 print"loading machine language routines..." : bload"lrr.bin",u(cd)
 +60 poke pv+3,dv : poke pv+4,dt : sys pk
 +70 dim t,r,b,i,a$,c,dt$,fl$,il$,x,x$
 +71 cm$="dmftc+-q "+chr$(13)+chr$(145)+chr$(17)+chr$(157)+chr$(29)+chr$(19)
 +72 cm$=cm$+chr$(147)+"/rnx"+chr$(92)
 +75 dl=-1 : cf=-1 : me=0 : ca=0 : ma=0
 +80 dim di$(1,300),cl(128),sz(128),dp(128),cn$(300)
 +90 if dt=255 then dt$="1581" :else dt$="1571"
 +100 fl$=chr$(19)+chr$(17)+chr$(17)+chr$(17)+chr$(17)
 +110 il$=fl$:fori=1to19:il$=il$+chr$(17):next
 +120 goto 500
 +130 :
 +131 rem ** load ms-dos directory **
 +140 print"loading ms-dos directory..." : print
 +150 sys pk : sys pk+3
 +160 dl=0
 +170 rreg bl,dc,bh,s : e=peek(pv+2)
 +180 if (s and 1) then gosub 380 : dl=-1 : return
 +190 print"scanning ms-dos directory..." : print
 +200 db=bl+256*bh
 +205 sys pk+21 : rreg bl,x,bh : ma=bl+bh*256+x*65536
 +210 if dc=0 then 360
 +220 for dp=db to db+32*(dc-1) step 32
 +230 if peek(dp)=0 or peek(dp)=229 then 350
 +240 if peek(dp+11) and 24 then 350
 +250 dl=dl+1
 +
 +Line 260 sets the default selection, translation, and filetypes for MS-DOS
 +files.  Change to your liking.
 +
 +260 d$=right$(" "+str$(dl),3)+"     asc  seq  " : rem ** default sel/tr/ft **
 +270 a$="" : fori=0to10 : a$=a$+chr$(peek(dp+i)) : next
 +280 a$=left$(a$,8)+"  "+right$(a$,3)
 +290 print dl; a$
 +300 d$=d$+a$+"  "
 +310 cl(dl)=peek(dp+26)+256*peek(dp+27)
 +320 sz=peek(dp+28)+256*peek(dp+29)+65536*peek(dp+30)
 +330 di$(0,dl)=d$+right$("    "+str$(sz),6)
 +335 dp(dl)=dp
 +340 sz(dl)=sz
 +350 next dp
 +360 return
 +370 :
 +371 rem ** report ms-dos disk error **
 +380 print chr$(18);"ms-dos disk error #";mid$(str$(e),2);
 +390 print " ($";mid$(hex$(e),3);"), press key.";chr$(146)
 +400 getkey a$ : return
 +410 :
 +411 rem ** screen heading **
 +420 print chr$(147);chr$(18);
 +421 if me=0 then print"ms-dos";:x=ma:else print"cbmdos";:x=ca
 +422 print chr$(146);"  ms=";mid$(str$(dv),2);":";dt$;
 +430 print"  cbm=";mid$(str$(cd),2);"  free=";mid$(str$(x),2)
 +440 print : return
 +450 :
 +451 rem ** screen footing **
 +460 print il$;"d=dir m=msdev f=cbmdev c=copy q=quit   "
 +470 print     "t=toggle r=remove x=cbmcpy /=menu +-=pg";
 +480 return
 +490 :
 +491 rem ** main routine **
 +500 t=1 : c=0
 +501 r=0
 +510 if me=0 then mf=dl:mc=2 : else mf=cf:mc=1
 +520 gosub 420
 +521 if me<>0 then 542
 +530 print "num  s  trn  typ  filename  ext  length"
 +540 print "---  -  ---  ---  --------  ---  ------"
 +541 goto 550
 +542 print "num  s  trn  filename          length"
 +543 print "---  -  ---  ---------------- -  ------"
 +550 gosub 460
 +560 b=t+17 : if b>mf then b=mf
 +570 print fl$;: if t>mf then 590
 +580 for i=t to b : print di$(me,i) : next
 +590 if mf<0 then print chr$(18);"<directory not loaded>";chr$(146)
 +591 if mf=0 then print chr$(18);"<no files>";chr$(146)
 +600 if mf<=0 then 660
 +610 print left$(il$,r+5);chr$(18);
 +620 on c+1 goto 630,640,650
 +630 print spc(4);mid$(di$(me,t+r),5,3) : goto 660
 +640 print spc(7);mid$(di$(me,t+r),8,5) : goto 660
 +650 print spc(12);mid$(di$(me,t+r),13,5) : goto 660
 +660 getkey a$
 +670 i=instr(cm$,a$)
 +680 if mf>0 then print left$(il$,r+5);di$(me,t+r)
 +690 if i=0 then 600
 +700 on i goto 760,1050,1110,950,1150,1000,1020,730,860,860,770,790,810,830,850
 +705 on i-15 goto 500,713,1400,713,1500,713
 +710 stop
 +711 :
 +712 rem ** various menu options **
 +713 me=-(me=0)
 +714 goto500
 +730 print chr$(147);"have an awesome day." : bank15
 +740 end
 +760 if me=1 then gosub 420 : gosub 2500 : goto 500
 +765 gosub 420 : gosub 140 : goto 500
 +770 r=r-1 : if r<0 then r=b-t
 +780 goto 600
 +790 r=r+1 : if t+r>b then r=0
 +800 goto 600
 +810 c=c-1 : if c<0 then c=mc
 +820 goto 600
 +830 c=c+1 : if c>mc then c=0
 +840 goto 600
 +850 r=0 : c=0 : goto 600
 +860 if mf<=0 then 600
 +870 x=t+r : on c+1 gosub 890,910,930
 +880 print left$(il$,r+5);di$(me,x) : goto 600
 +890 if mid$(di$(me,x),6,1)=" " then x$="*" :else x$=" "
 +900 mid$(di$(me,x),6,1)=x$ : return
 +910 if mid$(di$(me,x),9,1)="a" then x$="bin" :else x$="asc"
 +920 mid$(di$(me,x),9,3)=x$ : return
 +930 if mid$(di$(me,x),14,1)="s" then x$="prg" :else x$="seq"
 +940 mid$(di$(me,x),14,3)=x$ : return
 +950 if mf<=0 then 600
 +960 for x=1 to mf
 +970 on c+1 gosub 890,910,930
 +980 next x
 +990 goto 520
 +1000 r=0:if b=mf then t=1 : goto 510
 +1010 t=t+18 : goto 510
 +1020 if mf<=0 then 660
 +1025 r=0:if t=1 then t=mf-(mf-int(mf/18)*18)+1 : if t<=mf then 510
 +1030 t=t-18 : if t<1 then t=1
 +1040 goto 510
 +1050 print il$;chr$(27);"@";
 +1060 input"ms-dos device number (8-30)";dv
 +1061 if cd=dv thenprint"ms-dos and cbm-dos devices must be different!":goto1060
 +1070 x=71 : input"ms-dos device type  (71/81)";x
 +1080 if x=8 or x=81 or x=1581 then dt=255:dt$="1581" :else dt=0:dt$="1571"
 +1090 poke pv+3,dv : poke pv+4,dt : sys pk : dl=-1 : ma=0
 +1100 goto 500
 +1110 print il$;chr$(27);"@";
 +1120 input "cbm-dos device number (0-30)";cd
 +1130 if cd=dv thenprint"ms-dos and cbm-dos devices must be different!":goto1120
 +1140 cf=-1 : ca=0 : goto 500
 +1141 :
 +1142 rem ** copy files **
 +1150 if me=1 then 2000
 +1151 print chr$(147);"copy ms-dos -> cbm-dos":print:print
 +1160 if dl<=0 then fc=0 : goto 1190
 +1170 fc=0 : for f=1 to dl : if mid$(di$(0,f),6,1)="*" then gosub 1200
 +1180 next f
 +1190 print : print"files copied =";fc;" - press key"
 +1191 getkey a$ : goto 520
 +1200 fc=fc+1
 +1210 x$=mid$(di$(0,f),19,8)+"."+mid$(di$(0,f),29,3)
 +1220 cf$="":fori=1tolen(x$):if mid$(x$,i,1)<>" " then cf$=cf$+mid$(x$,i,1)
 +1230 next
 +1231 if right$(cf$,1)="." then cf$=left$(cf$,len(cf$)-1)
 +1232 cf$=cf$+","+mid$(di$(0,f),14,1)
 +1240 print str$(fc);". ";chr$(34);cf$;chr$(34);tab(20);sz(f)"bytes";
 +1245 print tab(35);mid$(di$(0,f),9,3)
 +1250 cl=cl(f) : lb=sz(f) - int(sz(f)/65536)*65536
 +1260 if cd>=8 then dopen#1,(cf$+",w"),u(cd) :else if cd<>0 then open 1,cd,7
 +1265 if cd<8 then 1288
 +1270 if ds<>63 then 1288
 +1275 x$="y" : print "cbm file exists; overwrite (y/n)";
 +1280 close 1 : input x$ : if x$="n" then fc=fc-1 : return
 +1285 scratch(cf$),u(cd)
 +1286 dopen#1,(cf$+",w"),u(cd)
 +1288 if cd<8 then 1320
 +1300 if ds<20 then 1320
 +1310 print chr$(18)+"cbm disk error: "+ds$ : fc=fc-1 : close1 : return
 +1320 poke pv+6,cl/256 : poke pv+5,cl-peek(pv+6)*256
 +1330 poke pv+8,lb/256 : poke pv+7,lb-peek(pv+8)*256
 +1340 tr=0 : if mid$(di$(0,f),9,1)="a" then tr=255
 +1346 x=1 : if cd=0 then x=0
 +1350 sys pk+6,tr,x
 +1355 rreg x,x,x,s : e=peek(pv+2)
 +1356 if (s and 1) then gosub 380 : fc=fc-1
 +1360 if cd<>0 and cd<8 then close1
 +1370 if cd>=8 then dclose#1 : if ds>=20 then 1310
 +1380 return
 +1398 :
 +1399 rem ** remove ms-dos file **
 +1400 print chr$(147);"remove (delete) selected ms-dos files:":print
 +1401 if me<>0 then print"ms-dos menu must be selected!" : goto2030
 +1402 a$="y":input"are you like sure about this (y/n)";a$
 +1403 print:if a$="n" then goto 520
 +1410 if dl<=0 then fc=0 : goto 1440
 +1420 fc=0 : f=1
 +1425 if mid$(di$(0,f),6,1)="*" then gosub 1470 : fc=fc+1 : f=f-1
 +1430 f=f+1 : if f<=dl then 1425
 +1434 print"flushing..."
 +1435 sys pk+12
 +1440 print : print"files removed =";fc;" - press key"
 +1445 sys pk+21 : rreg a,x,y : ma=a+y*256+x*65536
 +1450 getkey a$ : goto 500
 +1470 print"removing ";chr$(34);mid$(di$(0,f),19,13);chr$(34)
 +1490 poke pv+10,dp(f)/256 : poke pv+9,dp(f)-peek(pv+10)*256
 +1492 sys pk+15
 +1494 di$(0,f)=di$(0,dl):sz(f)=sz(dl):dp(f)=dp(dl):cl(f)=cl(dl)
 +1495 dl=dl-1
 +1496 return
 +1498 :
 +1499 rem ** copy cbm files **
 +1500 print chr$(147);"copy cbm-dos to cbm-dos:":print
 +1501 if cf<=0 then print"commodore directory not loaded" : goto 2030
 +1502 x=0 : input"device number to copy to";x : print
 +1503 if x<=0 or x>=64 then print"bad device number!" : goto 2030
 +1504 if x=cd then print"cannot copy to same device" : goto 2030
 +1505 for f=1 to cf : if mid$(di$(1,f),6,1)<>"*" then 1570
 +1506 print di$(1,f) : open1,cd,2,cn$(f)+",r"
 +1507 if x<8 then open 2,x,7 : goto1550
 +1508 cf$=cn$(f)+","+mid$(di$(1,f),31,1)+",w"
 +1509 open2,x,3,cf$
 +1510 if ds<>63 then 1530
 +1511 close2
 +1512 x$="y":input"file exists: overwrite (y/n)";x$ : if x$="n" then 1560
 +1520 scratch(cn$(f)),u(x)
 +1525 open2,x,3,cf$
 +1530 if ds>20 then print chr$(18);"cbm dos error: ";ds$ : goto1560
 +1550 sys pk+24,1,2
 +1560 close1 : close2
 +1570 next f
 +1580 print : print"finished - press a key" : getkey a$ : goto510
 +1998 :
 +1999 rem ** copy cbm-dos to ms-dos **
 +2000 print chr$(147);"copy cbm-dos to ms-dos:" : print : print
 +2010 if dl>=0 then 2035
 +2020 print"ms-dos directory must be loaded first"
 +2030 print : print"press any key" : getkey a$ : goto 510
 +2035 fc=0
 +2036 for f=1 to cf : if mid$(di$(1,f),6,1)<>"*" then 2045
 +2040 fc=fc+1 : c$=cn$(f)
 +2041 printmid$(str$(fc),2);" ";mid$(di$(1,f),14,16);mid$(di$(1,f),34);":";
 +2042 gosub2050 : print left$(m$,8);".";right$(m$,3)
 +2043 tr=0 : if mid$(di$(1,f),9,1)="a" then tr=255
 +2044 gosub2100
 +2045 next
 +2046 print"flushing..." : sys pk+12
 +2047 sys pk+21 : rreg a,x,y : ma=a+y*256+x*65536
 +2048 print: print"files copied =";fc : goto2030
 +2049 :
 +2050 x=instr(c$,".") : if x=0 then m$=c$+"           " : goto2090
 +2055 x=len(c$)+1 : do : x=x-1 : loop until mid$(c$,x,1)="."
 +2060 m$=left$(left$(c$,x-1)+"        ",8)
 +2070 x$=mid$(c$,x+1)+"   "
 +2080 m$=m$+x$
 +2090 m$=left$(m$,11)
 +2091 fori=1to11:x$=chr$(asc(mid$(m$,i,1))and127):if x$="."orx$=" " then x$="_"
 +2092 mid$(m$,i,1)=x$ : next i
 +2093 i=8 : do while i>1 and mid$(m$,i,1)="_" : mid$(m$,i,1)=" " : i=i-1 : loop
 +2094 i=11 : do while i>8 and mid$(m$,i,1)="_" : mid$(m$,i,1)=" " : i=i-1 : loop
 +2098 return
 +2099 :
 +2100 fori=0to0
 +2105 for dp=db to db+32*(dc-1) step 32
 +2110 if peek(dp)=0 or peek(dp)=229 then 2140
 +2120 next dp
 +2130 print"no free ms-dos directory entires" : return
 +2140 next i
 +2160 fori=1tolen(m$):pokedp+i-1,asc(mid$(m$,i,1)) and 127:next
 +2170 fori=11to31:poke dp+i,0:next
 +2180 pokedp+26,255:pokedp+27,15
 +2190 poke pv+10,dp/256:poke pv+9,dp-peek(pv+10)*256
 +2200 open1,cd,2,c$
 +2300 sys pk+9,tr,1 : rreg x,x,x,s
 +2301 close1
 +2305 if s and 1 then e=peek(pv+2) : gosub380 : return
 +
 +Line 2310 sets the default MS-DOS selection, translation, and filetype after
 +copying to MS-DOS disk, based on the CBM-DOS filetype.  Change to your liking.
 +
 +2310 x$="     asc  seq  ":if tr=0 then x$="     bin  prg  "
 +2320 dl=dl+1 : d$=right$(" "+str$(dl),3)+x$
 +2330 d$=d$+left$(m$,8)+"  "+right$(m$,3)
 +2340 cl(dl)=peek(dp+26)+256*peek(dp+27)
 +2350 sz=peek(dp+28)+256*peek(dp+29)+65536*peek(dp+30)
 +2360 di$(0,dl)=d$+right$("        "+str$(sz),8)
 +2370 dp(dl)=dp
 +2380 sz(dl)=sz
 +2395 return
 +2498 :
 +2499 rem ** load commodore dos directory **
 +2500 print"loading commodore dos directory..." : print
 +2501 if cd<8 then print"cbmdos device must be >= 8!" : goto2030
 +2505 open1,cd,0,"$0":get#1,a$,a$ : cf=-1 : q$=chr$(34)
 +2506 do
 +2507 sys pk+27,1 : b=peek(pv+11)+256*peek(pv+12) : t$=chr$(peek(pv+13))
 +2510 x=peek(pv+14)
 +2520 if x=0 then exit
 +2530 x$="" : for i=pv+15 to pv+15+x-1 : x$=x$+chr$(peek(i)) : next
 +2575 cf=cf+1
 +2590 if cf=0 then print"disk="q$x$q$ : print : goto2650
 +2600 cn$(cf)=x$
 +2610 a$=left$(x$+"                 ",17)+t$+right$("       "+str$(b*254),8)
 +
 +Lines 2620 and 2625 set the default CBM-DOS selection and translation modes
 +based on the filetype.  Change to your liking.
 +
 +2620 di$(1,cf)=right$("  "+str$(cf),3)+"     asc  "+a$
 +2625 if t$<>"s" then mid$(di$(1,cf),9,3)="bin"
 +2630 print di$(1,cf)
 +2650 loop
 +2670 ca=b*256 : close1 : return
 +
 +6. UUENCODED FILES
 +
 +Here are the binary executables in uuencoded form.  The CRC32s of the two
 +files are as follows:
 +
 +crc32 = 3896271974 for "lrr-128"
 +crc32 = 2918283051 for "lrr.bin"
 +
 +The "lrr.128" file is the main BASIC program and the "lrr.bin" file contains
 +the machine lanugage disk-accessing routines.
 +
 +begin 640 lrr-128
 +M`1PS'`H`F2#'*#$T-RD[(DQ)5%1,12!2140@4D5!1$52(#$R."!615)324].
 +M(#$N,#`B`&D<"P"9(#H@F2)"62!#4D%)1R!"4E5#12`P.2U&14(M.3,@1D]2
 +M($,]2$%#2TE.1R(@.B"9`&\<#``Z`*L<%`!#1++"*#$X-BDZBR!#1+,X(*<@
 +M0T2R."`Z((\@*BH@1$5&055,5"!#0DTM1$]3($12259%("HJ`.8<&0!$5K(Y
 +M.D14LC`@.B`@CR`J*B!-4RU$3U,@1%))5D4L(%194$4@*#`],34W,2PR-34]
 +M,34X,2D`'!T:`(L@1%:R0T0@IR!$5K(X.D14LC`@.B"/("HJ($%,5$523D%4
 +M12!-4RU$3U,@1%))5D4`(AT;`#H`/AT>`)D@(DE.251)04Q)6DE.1RXN+B(@
 +M.B"9`&`=*`#^`C`@.B!02[+1*"(X,#`P(BD@.B!05K)02ZHS,`")'3(`BR#"
 +M*%!6JC`ILM$H(D-"(BD@KR#"*%!6JC$ILC$S,B"G(#8P`,D=-P"9(DQ/041)
 +M3D<@34%#2$E.12!,04Y'54%'12!23U5424Y%4RXN+B(@.B#^$2),4E(N0DE.
 +M(BQ5*$-$*0#J'3P`ER!05JHS+$16(#H@ER!05JHT+$14(#H@GB!02P`.'D8`
 +MAB!4+%(L0BQ)+$$D+$,L1%0D+$9,)"Q)3"0L6"Q8)`!('D<`0TTDLB)$3494
 +M0RLM42`BJL<H,3,IJL<H,30U*:K'*#$W*:K'*#$U-RFJQR@R.2FJQR@Q.2D`
 +M:!Y(`$--)+)#322JQR@Q-#<IJB(O4DY8(JK'*#DR*0"/'DL`1$RRJS$@.B!#
 +M1K*K,2`Z($U%LC`@.B!#0;(P(#H@34&R,`#!'E``AB!$220H,2PS,#`I+$-,
 +M*#$R."DL4UHH,3(X*2Q$4"@Q,C@I+$-.)"@S,#`I`.D>6@"+($14LC(U-2"G
 +M($14)+(B,34X,2(@.M4@1%0DLB(Q-3<Q(@`/'V0`1DPDLL<H,3DIJL<H,3<I
 +MJL<H,3<IJL<H,3<IJL<H,3<I`#,?;@!)3"2R1DPD.H%)LC&D,3DZ24PDLDE,
 +M)*K'*#$W*3J"`#T?>`")(#4P,`!#'X(`.@!E'X,`CR`J*B!,3T%$($U3+41/
 +M4R!$25)%0U1/4ED@*BH`C!^,`)DB3$]!1$E.1R!-4RU$3U,@1$E214-43U)9
 +M+BXN(B`Z()D`GA^6`)X@4$L@.B">(%!+JC,`IQ^@`$1,LC``Q1^J`/X)($),
 +M+$1#+$)(+%,@.B!%LL(H4%:J,BD`YQ^T`(L@*%,@KR`Q*2"G((T@,S@P(#H@
 +M1$RRJS$@.B".``\@O@"9(E-#04Y.24Y'($U3+41/4R!$25)%0U1/4EDN+BXB
 +M(#H@F0`@(,@`1$*R0DRJ,C4VK$)(`%`@S0">(%!+JC(Q(#H@_@D@0DPL6"Q"
 +M2"`Z($U!LD),JD)(K#(U-JI8K#8U-3,V`&$@T@"+($1#LC`@IR`S-C``@2#<
 +M`($@1%"R1$(@I"!$0JHS,JPH1$.K,2D@J2`S,@"A(.8`BR#"*$10*;(P(+`@
 +MPBA$4"FR,C(Y(*<@,S4P`+L@\`"+(,(H1%"J,3$I(*\@,C0@IR`S-3``QR#Z
 +M`$1,LD1,JC$`"R$$`40DLLDH(B`BJL0H1$PI+#,IJB(@("`@($%30R`@4T51
 +M("`B(#H@CR`J*B!$149!54Q4(%-%3"]44B]&5"`J*@`V(0X!022R(B(@.B"!
 +M2;(PI#$P(#H@022R022JQRC"*$10JDDI*2`Z(((`4B$8`4$DLL@H020L."FJ
 +M(B`@(JK)*$$D+#,I`%\A(@&9($1,.R!!)`!Q(2P!1"2R1"2J022J(B`@(@"2
 +M(38!0TPH1$PILL(H1%"J,C8IJC(U-JS"*$10JC(W*0"^(4`!4UJRPBA$4*HR
 +M."FJ,C4VK,(H1%"J,CDIJC8U-3,VK,(H1%"J,S`I`.$A2@%$220H,"Q$3"FR
 +M1"2JR2@B("`@("*JQ"A36BDL-BD`[R%/`410*$1,*;)$4`#](50!4UHH1$PI
 +MLE-:``8B7@&"($10``PB:`&.`!(B<@$Z`#<B<P&/("HJ(%)%4$]25"!-4RU$
 +M3U,@1$E32R!%4E)/4B`J*@!D(GP!F2#'*#$X*3LB35,M1$]3($1)4TL@15)2
 +M3U(@(R([RBC$*$4I+#(I.P"1(H8!F2`B("@D(CO**-(H12DL,RD[(BDL(%!2
 +M15-3($M%62XB.\<H,30V*0"?(I`!H?D@020@.B".`*4BF@$Z`,`BFP&/("HJ
 +M(%-#4D5%3B!(14%$24Y'("HJ`-0BI`&9(,<H,30W*3O'*#$X*3L``R.E`8L@
 +M346R,""G()DB35,M1$]3(CLZ6+)-03K5()DB0T)-1$]3(CLZ6+)#00`L(Z8!
 +MF2#'*#$T-BD[(B`@35,](CO**,0H1%8I+#(I.R(Z(CM$5"0[`%DCK@&9(B`@
 +M0T)-/2([RBC$*$-$*2PR*3LB("!&4D5%/2([RBC$*%@I+#(I`&,CN`&9(#H@
 +MC@!I(\(!.@"$(\,!CR`J*B!30U)%14X@1D]/5$E.1R`J*@"X(\P!F2!)3"0[
 +M(D0]1$E2($T]35-$158@1CU#0DU$158@0SU#3U!9(%$]455)5"`@("(`[2/6
 +M`9D@("`@(")4/51/1T=,12!2/5)%34]612!8/4-"34-062`O/4U%3E4@*RT]
 +M4$<B.P#S(^`!C@#Y(^H!.@`2).L!CR`J*B!-04E.(%)/551)3D4@*BH`("3T
 +M`52R,2`Z($.R,``H)/4!4K(P`$\D_@&+($U%LC`@IR!-1K)$3#I-0[(R(#H@
 +MU2!-1K)#1CI-0[(Q`%DD"`*-(#0R,`!K)`D"BR!-1;.Q,""G(#4T,@";)!("
 +MF2`B3E5-("!3("!44DX@(%194"`@1DE,14Y!344@($585"`@3$5.1U1((@#+
 +M)!P"F2`B+2TM("`M("`M+2T@("TM+2`@+2TM+2TM+2T@("TM+2`@+2TM+2TM
 +M(@#5)!T"B2`U-3``!24>`ID@(DY532`@4R`@5%).("!&24Q%3D%-12`@("`@
 +M("`@(%0@($Q%3D=42"(`-24?`ID@(BTM+2`@+2`@+2TM("`M+2TM+2TM+2TM
 +M+2TM+2TM("T@("TM+2TM+2(`/R4F`HT@-#8P`%HE,`)"LE2J,3<@.B"+($*Q
 +M348@IR!"LDU&`',E.@*9($9,)#LZ((L@5+%-1B"G(#4Y,`"3)40"@2!)LE0@
 +MI"!"(#H@F2!$220H344L22D@.B""`,@E3@*+($U&LS`@IR"9(,<H,3@I.R(\
 +M1$E214-43U)9($Y/5"!,3T%$140^(CO'*#$T-BD`\25/`HL@34:R,""G()D@
 +MQR@Q."D[(CQ.3R!&24Q%4SXB.\<H,30V*0`#)E@"BR!-1K.R,""G(#8V,``;
 +M)F("F2#(*$E,)"Q2JC4I.\<H,3@I.P`S)FP"D2!#JC$@B2`V,S`L-C0P+#8U
 +M,`!8)G8"F2"F-"D[RBA$220H344L5*I2*2PU+#,I(#H@B2`V-C``?2:``ID@
 +MIC<I.\HH1$DD*$U%+%2J4BDL."PU*2`Z((D@-C8P`*0FB@*9(*8Q,BD[RBA$
 +M220H344L5*I2*2PQ,RPU*2`Z((D@-C8P`*XFE`*A^2!!)`"^)IX"2;+4*$--
 +M)"Q!)"D`Y":H`HL@34:Q,""G()D@R"A)3"0L4JHU*3M$220H344L5*I2*0#T
 +M)K("BR!)LC`@IR`V,#``/R>\`I$@22")(#<V,"PQ,#4P+#$Q,3`L.34P+#$Q
 +M-3`L,3`P,"PQ,#(P+#<S,"PX-C`L.#8P+#<W,"PW.3`L.#$P+#@S,"PX-3``
 +M9B?!`I$@2:LQ-2")(#4P,"PW,3,L,30P,"PW,3,L,34P,"PW,3,`;"?&`I``
 +M<B?'`CH`DR?(`H\@*BH@5D%224]54R!-14Y5($]05$E/3E,@*BH`HB?)`DU%
 +MLJLH346R,"D`JR?*`HDU,#``UB?:`ID@QR@Q-#<I.R)(059%($%.($%715-/
 +M344@1$%9+B(@.B#^`C$U`-PGY`*````H^`*+($U%LC$@IR"-(#0R,"`Z((T@
 +M,C4P,"`Z((D@-3`P`!HH_0*-(#0R,"`Z((T@,30P(#H@B2`U,#``-"@"`U*R
 +M4JLQ(#H@BR!2LS`@IR!2LD*K5``^*`P#B2`V,#``6"@6`U*R4JHQ(#H@BR!4
 +MJE*Q0B"G(%*R,`!B*"`#B2`V,#``>R@J`T.R0ZLQ(#H@BR!#LS`@IR!#LDU#
 +M`(4H-`.)(#8P,`">*#X#0[)#JC$@.B"+($.Q34,@IR!#LC``J"A(`XD@-C`P
 +M`+XH4@-2LC`@.B!#LC`@.B")(#8P,`#0*%P#BR!-1K.R,""G(#8P,`#P*&8#
 +M6+)4JE(@.B"1($.J,2"-(#@Y,"PY,3`L.3,P`!,I<`.9(,@H24PD+%*J-2D[
 +M1$DD*$U%+%@I(#H@B2`V,#``02EZ`XL@RBA$220H344L6"DL-BPQ*;(B("(@
 +MIR!8)+(B*B(@.M4@6"2R(B`B`%TIA`/**$1))"A-12Q8*2PV+#$ILE@D(#H@
 +MC@"/*8X#BR#**$1))"A-12Q8*2PY+#$ILB)!(B"G(%@DLB)"24XB(#K5(%@D
 +MLB)!4T,B`*LIF`/**$1))"A-12Q8*2PY+#,ILE@D(#H@C@#>*:(#BR#**$1)
 +M)"A-12Q8*2PQ-"PQ*;(B4R(@IR!8)+(B4%)'(B`ZU2!8)+(B4T51(@#[*:P#
 +MRBA$220H344L6"DL,30L,RFR6"0@.B".``TJM@.+($U&L[(P(*<@-C`P`!PJ
 +MP`.!(%BR,2"D($U&`#0JR@.1($.J,2"-(#@Y,"PY,3`L.3,P`#PJU`."(%@`
 +M1BK>`XD@-3(P`&,JZ`-2LC`ZBR!"LDU&(*<@5+(Q(#H@B2`U,3``=BKR`U2R
 +M5*HQ."`Z((D@-3$P`(@J_`.+($U&L[(P(*<@-C8P`,`J`012LC`ZBR!4LC$@
 +MIR!4LDU&JRA-1JNU*$U&K3$X*:PQ."FJ,2`Z((L@5+.R348@IR`U,3``V2H&
 +M!%2R5*LQ."`Z((L@5+,Q(*<@5+(Q`.,J$`2)(#4Q,`#X*AH$F2!)3"0[QR@R
 +M-RD[(D`B.P`>*R0$A2)-4RU$3U,@1$5624-%($Y534)%4B`H."TS,"DB.T16
 +M`&(K)02+($-$LD16(*>9(DU3+41/4R!!3D0@0T)-+41/4R!$159)0T53($U5
 +M4U0@0D4@1$E&1D5214Y4(2(ZB3$P-C``CBLN!%BR-S$@.B"%(DU3+41/4R!$
 +M159)0T4@5%E012`@*#<Q+S@Q*2([6`#/*S@$BR!8LC@@L"!8LC@Q(+`@6+(Q
 +M-3@Q(*<@1%2R,C4U.D14)+(B,34X,2(@.M4@1%2R,#I$5"2R(C$U-S$B`/\K
 +M0@27(%!6JC,L1%8@.B"7(%!6JC0L1%0@.B">(%!+(#H@1$RRJS$@.B!-0;(P
 +M``DL3`2)(#4P,``>+%8$F2!)3"0[QR@R-RD[(D`B.P!&+&`$A2`B0T)-+41/
 +M4R!$159)0T4@3E5-0D52("@P+3,P*2([0T0`BBQJ!(L@0T2R1%8@IYDB35,M
 +M1$]3($%.1"!#0DTM1$]3($1%5DE#15,@35535"!"12!$249&15)%3E0A(CJ)
 +M,3$R,`"C+'0$0T:RJS$@.B!#0;(P(#H@B2`U,#``J2QU!#H`P"QV!(\@*BH@
 +M0T]062!&24Q%4R`J*@#2+'X$BR!-1;(Q(*<@,C`P,`#\+'\$F2#'*#$T-RD[
 +M(D-/4%D@35,M1$]3("T^($-"32U$3U,B.IDZF0`8+8@$BR!$3+.R,""G($9#
 +MLC`@.B")(#$Q.3``3RV2!$9#LC`@.B"!($:R,2"D($1,(#H@BR#**$1))"@P
 +M+$8I+#8L,2FR(BHB(*<@C2`Q,C`P`%<MG`2"($8`@RVF!)D@.B"9(D9)3$53
 +M($-/4$E%1"`](CM&0SLB("T@4%)%4U,@2T59(@"5+:<$H?D@020@.B")(#4R
 +M,`"A+;`$1D.R1D.J,0#.+;H$6"2RRBA$220H,"Q&*2PQ.2PX*:HB+B*JRBA$
 +M220H,"Q&*2PR.2PS*0`)+L0$0T8DLB(B.H%)LC&DPRA8)"DZBR#**%@D+$DL
 +M,2FSL2(@(B"G($-&)+)#1B2JRBA8)"Q)+#$I``\NS@2"`#@NSP2+(,DH0T8D
 +M+#$ILB(N(B"G($-&)++(*$-&)"S#*$-&)"FK,2D`62[0!$-&)+)#1B2J(BPB
 +MJLHH1$DD*#`L1BDL,30L,2D`C2[8!)D@Q"A&0RD[(BX@(CO'*#,T*3M#1B0[
 +MQR@S-"D[HS(P*3M36BA&*2)"651%4R([`*@NW029(*,S-2D[RBA$220H,"Q&
 +M*2PY+#,I`-<NX@1#3+)#3"A&*2`Z($Q"LE-:*$8I(*L@M2A36BA&*:TV-34S
 +M-BFL-C4U,S8`$2_L!(L@0T2QLC@@IR#^#2,Q+"A#1B2J(BQ7(BDL52A#1"D@
 +M.M4@BR!#1+.Q,""G()\@,2Q#1"PW`",O\02+($-$LS@@IR`Q,C@X`#<O]@2+
 +M($13L[$V,R"G(#$R.#@`:B_[!%@DLB)9(B`Z()D@(D-"32!&24Q%($5825-4
 +M4SL@3U9%4E=2251%("A9+TXI(CL`DB\`!:`@,2`Z((4@6"0@.B"+(%@DLB).
 +M(B"G($9#LD9#JS$@.B".`*,O!07R*$-&)"DL52A#1"D`O2\&!?X-(S$L*$-&
 +M)*HB+%<B*2Q5*$-$*0#/+P@%BR!#1+,X(*<@,3,R,`#B+Q0%BR!$4[,R,""G
 +M(#$S,C``&#`>!9D@QR@Q."FJ(D-"32!$25-+($524D]2.B`BJD13)"`Z($9#
 +MLD9#JS$@.B"@,2`Z((X`0C`H!9<@4%:J-BQ#3*TR-38@.B"7(%!6JC4L0TRK
 +MPBA05JHV*:PR-38`;#`R!9<@4%:J."Q,0JTR-38@.B"7(%!6JC<L3$*KPBA0
 +M5JHX*:PR-38`EC`\!512LC`@.B"+(,HH1$DD*#`L1BDL.2PQ*;(B02(@IR!4
 +M4K(R-34`K3!"!5BR,2`Z((L@0T2R,""G(%BR,`"],$8%GB!02ZHV+%12+%@`
 +MV#!+!?X)(%@L6"Q8+%,@.B!%LL(H4%:J,BD`^#!,!8L@*%,@KR`Q*2"G((T@
 +M,S@P(#H@1D.R1D.K,0`0,5`%BR!#1+.Q,""O($-$LS@@IR"@,0`U,5H%BR!#
 +M1+&R.""G(/X/(S$@.B"+($13L;(R,""G(#$S,3``.S%D!8X`03%V!3H`8#%W
 +M!8\@*BH@4D5-3U9%($U3+41/4R!&24Q%("HJ`)@Q>`69(,<H,30W*3LB4D5-
 +M3U9%("A$14Q%5$4I(%-%3$5#5$5$($U3+41/4R!&24Q%4SHB.ID`SS%Y!8L@
 +M346SL3`@IR"9(DU3+41/4R!-14Y5($U54U0@0D4@4T5,14-4140A(B`Z((DR
 +M,#,P``,R>@5!)+(B62(ZA2)!4D4@64]5($Q)2T4@4U5212!!0D]55"!42$E3
 +M("A9+TXI(CM!)``:,GL%F3J+($$DLB).(B"G((D@-3(P`#8R@@6+($1,L[(P
 +M(*<@1D.R,"`Z((D@,30T,`!%,HP%1D.R,"`Z($:R,0!Z,I$%BR#**$1))"@P
 +M+$8I+#8L,2FR(BHB(*<@C2`Q-#<P(#H@1D.R1D.J,2`Z($:R1JLQ`)4RE@5&
 +MLD:J,2`Z((L@1K.R1$P@IR`Q-#(U`*@RF@69(D9,55-(24Y'+BXN(@"T,IL%
 +MGB!02ZHQ,@#A,J`%F2`Z()DB1DE,15,@4D5-3U9%1"`](CM&0SLB("T@4%)%
 +M4U,@2T59(@`-,Z4%GB!02ZHR,2`Z(/X)($$L6"Q9(#H@34&R0:I9K#(U-JI8
 +MK#8U-3,V`!\SJ@6A^2!!)"`Z((D@-3`P`$XSO@69(E)%34]624Y'("([QR@S
 +M-"D[RBA$220H,"Q&*2PQ.2PQ,RD[QR@S-"D`@#/2!9<@4%:J,3`L1%`H1BFM
 +M,C4V(#H@ER!05JHY+$10*$8IJ\(H4%:J,3`IK#(U-@",,]0%GB!02ZHQ-0#*
 +M,]8%1$DD*#`L1BFR1$DD*#`L1$PI.E-:*$8ILE-:*$1,*3I$4"A&*;)$4"A$
 +M3"DZ0TPH1BFR0TPH1$PI`-8SUP5$3+)$3*LQ`-PSV`6.`.(SV@4Z`/TSVP6/
 +M("HJ($-/4%D@0T)-($9)3$53("HJ`"<TW`69(,<H,30W*3LB0T]062!#0DTM
 +M1$]3(%1/($-"32U$3U,Z(CJ9`&`TW06+($-&L[(P(*<@F2)#3TU-3T1/4D4@
 +M1$E214-43U)9($Y/5"!,3T%$140B(#H@B2`R,#,P`(PTW@58LC`@.B"%(D1%
 +M5DE#12!.54U"15(@5$\@0T]062!43R([6"`Z()D`P#3?!8L@6+.R,""P(%BQ
 +MLC8T(*<@F2)"040@1$5624-%($Y534)%4B$B(#H@B2`R,#,P`/0TX`6+(%BR
 +M0T0@IR"9(D-!3DY/5"!#3U!9(%1/(%-!344@1$5624-%(B`Z((D@,C`S,``C
 +M->$%@2!&LC$@I"!#1B`Z((L@RBA$220H,2Q&*2PV+#$IL[$B*B(@IR`Q-3<P
 +M`$@UX@69($1))"@Q+$8I(#H@GS$L0T0L,BQ#3B0H1BFJ(BQ2(@!D->,%BR!8
 +MLS@@IR"?(#(L6"PW(#H@B3$U-3``C37D!4-&)+)#3B0H1BFJ(BPBJLHH1$DD
 +M*#$L1BDL,S$L,2FJ(BQ7(@"<->4%GS(L6"PS+$-&)`"P->8%BR!$4[.Q-C,@
 +MIR`Q-3,P`+<UYP6@,@#W->@%6"2R(EDB.H4B1DE,12!%6$E35%,Z($]615)7
 +M4DE412`H62].*2([6"0@.B"+(%@DLB).(B"G(#$U-C``"C;P!?(H0TXD*$8I
 +M*2Q5*%@I`!DV]06?,BQ8+#,L0T8D`$TV^@6+($13L3(P(*<@F2#'*#$X*3LB
 +M0T)-($1/4R!%4E)/4CH@(CM$4R0@.B"),34V,`!=-@X&GB!02ZHR-"PQ+#(`
 +M:388!J`Q(#H@H#(`<38B!H(@1@"B-BP&F2`Z()DB1DE.25-(140@+2!04D53
 +M4R!!($M%62(@.B"A^2!!)"`Z((DU,3``J#;.!SH`RS;/!X\@*BH@0T]062!#
 +M0DTM1$]3(%1/($U3+41/4R`J*@#Z-M`'F2#'*#$T-RD[(D-/4%D@0T)-+41/
 +M4R!43R!-4RU$3U,Z(B`Z()D@.B"9``TWV@>+($1,L;(P(*<@,C`S-0`Z-^0'
 +MF2)-4RU$3U,@1$E214-43U)9($U54U0@0D4@3$]!1$5$($9)4E-4(@!C-^X'
 +MF2`Z()DB4%)%4U,@04Y9($M%62(@.B"A^2!!)"`Z((D@-3$P`&PW\P=&0[(P
 +M`)LW]`>!($:R,2"D($-&(#H@BR#**$1))"@Q+$8I+#8L,2FSL2(J(B"G(#(P
 +M-#4`LS?X!T9#LD9#JC$@.B!#)+)#3B0H1BD`[3?Y!YG**,0H1D,I+#(I.R(@
 +M(CO**$1))"@Q+$8I+#$T+#$V*3O**$1))"@Q+$8I+#,T*3LB.B([``\X^@>-
 +M,C`U,"`Z()D@R"A-)"PX*3LB+B([R2A-)"PS*0`Y./L'5%*R,"`Z((L@RBA$
 +M220H,2Q&*2PY+#$ILB)!(B"G(%12LC(U-0!#./P'C3(Q,#``23C]!X(`9CC^
 +M!YDB1DQ54TA)3D<N+BXB(#H@GB!02ZHQ,@"2./\'GB!02ZHR,2`Z(/X)($$L
 +M6"Q9(#H@34&R0:I9K#(U-JI8K#8U-3,V`+8X``B9.B"9(D9)3$53($-/4$E%
 +M1"`](CM&0R`Z((DR,#,P`+PX`0@Z`/(X`@A8LM0H0R0L(BXB*2`Z((L@6+(P
 +M(*<@322R0R2J(B`@("`@("`@("`@(B`Z((DR,#DP`"`Y!PA8LL,H0R0IJC$@
 +M.B#K(#H@6+)8JS$@.B#L(/P@RBA#)"Q8+#$ILB(N(@!!.0P(322RR"C(*$,D
 +M+%BK,2FJ(B`@("`@("`@(BPX*0!8.18(6"2RRBA#)"Q8JC$IJB(@("`B`&4Y
 +M(`A-)+)-)*I8)`!U.2H(322RR"A-)"PQ,2D`L3DK"(%)LC&D,3$Z6"2RQRC&
 +M*,HH320L22PQ*2FO,3(W*3J+(%@DLB(N(K!8)+(B("(@IR!8)+(B7R(`R#DL
 +M",HH320L22PQ*;)8)"`Z(((@20`&.BT(2;(X(#H@ZR#]($FQ,2"O(,HH320L
 +M22PQ*;(B7R(@.B#**$TD+$DL,2FR(B`B(#H@2;))JS$@.B#L`$4Z+@A)LC$Q
 +M(#H@ZR#]($FQ.""O(,HH320L22PQ*;(B7R(@.B#**$TD+$DL,2FR(B`B(#H@
 +M2;))JS$@.B#L`$LZ,@B.`%$Z,P@Z`%PZ-`B!2;(PI#``?#HY"($@1%"R1$(@
 +MI"!$0JHS,JPH1$.K,2D@J2`S,@"=.CX(BR#"*$10*;(P(+`@PBA$4"FR,C(Y
 +M(*<@,C$T,`"F.D@(@B!$4`#2.E((F2).3R!&4D5%($U3+41/4R!$25)%0U1/
 +M4ED@14Y425)%4R(@.B".`-HZ7`B"($D`!CMP"(%)LC&DPRA-)"DZET10JDFK
 +M,2S&*,HH320L22PQ*2D@KR`Q,C<Z@@`>.WH(@4FR,3&D,S$ZER!$4*I)+#`Z
 +M@@`W.X0(ET10JC(V+#(U-3J71%"J,C<L,34`83N.")<@4%:J,3`L1%"M,C4V
 +M.I<@4%:J.2Q$4*O"*%!6JC$P*:PR-38`<#N8")\Q+$-$+#(L0R0`C3O\")X@
 +M4$NJ.2Q44BPQ(#H@_@D@6"Q8+%@L4P"4._T(H#$`MSL!"8L@4R"O(#$@IR!%
 +MLL(H4%:J,BD@.B"-,S@P(#H@C@#N.P8)6"2R(B`@("`@05-#("!315$@("(Z
 +MBR!44K(P(*<@6"2R(B`@("`@0DE.("!04D<@("(`$3P0"41,LD1,JC$@.B!$
 +M)++)*"(@(JK$*$1,*2PS*:I8)``P/!H)1"2R1"2JR"A-)"PX*:HB("`BJLDH
 +M320L,RD`43PD"4-,*$1,*;+"*$10JC(V*:HR-3:LPBA$4*HR-RD`?3PN"5-:
 +MLL(H1%"J,C@IJC(U-JS"*$10JC(Y*:HV-34S-JS"*$10JC,P*0"D/#@)1$DD
 +M*#`L1$PILD0DJLDH(B`@("`@("`@(JK$*%-:*2PX*0"R/$()1%`H1$PILD10
 +M`,`\3`E36BA$3"FR4UH`QCQ;"8X`S#S""3H`]3S#"8\@*BH@3$]!1"!#3TU-
 +M3T1/4D4@1$]3($1)4D5#5$]262`J*@`C/<0)F2),3T%$24Y'($-/34U/1$]2
 +M12!$3U,@1$E214-43U)9+BXN(B`Z()D`5SW%"8L@0T2S.""G()DB0T)-1$]3
 +M($1%5DE#12!-55-4($)%(#X](#@A(B`Z((DR,#,P`(4]R0F?,2Q#1"PP+"(D
 +M,"(ZH2,Q+$$D+$$D(#H@0T:RJS$@.B!1)++'*#,T*0"+/<H)ZP#$/<L)GB!0
 +M2ZHR-RPQ(#H@0K+"*%!6JC$Q*:HR-3:LPBA05JHQ,BD@.B!4)++'*,(H4%:J
 +M,3,I*0#3/<X)6++"*%!6JC$T*0#A/=@)BR!8LC`@IR#M`!<^X@E8)+(B(B`Z
 +M(($@2;)05JHQ-2"D(%!6JC$UJEBK,2`Z(%@DLE@DJL<HPBA)*2D@.B""`",^
 +M#PI#1K)#1JHQ`$L^'@J+($-&LC`@IR"9(D1)4TL](E$D6"11)"`Z()D@.B")
 +M,C8U,`!:/B@*0TXD*$-&*;)8)`"9/C(*022RR"A8)*HB("`@("`@("`@("`@
 +M("`@("`B+#$W*:I4)*K)*"(@("`@("`@(JK$*$*L,C4T*2PX*0#'/CP*1$DD
 +M*#$L0T8ILLDH(B`@(JK$*$-&*2PS*:HB("`@("!!4T,@("*J020`[CY!"HL@
 +M5"2SL2)3(B"G(,HH1$DD*#$L0T8I+#DL,RFR(D))3B(`_CY&"ID@1$DD*#$L
 +B0T8I``0_6@KL`!H_;@I#0;)"K#(U-B`Z(*`Q(#H@C@``````
 +`
 +end
 +begin 640 lrr.bin
 +M`(!,#(),7(-,A89,\X=,,(5,Z(1,/8!,HX5,2XA,@(C+A```````````````
 +M````````````````````````````````````````````````````````````
 +M````2*D`A9"M(8`@L?^I;R"3_ZE5(*C_))`P#ZDP(*C_:""H_R20,`(88*D%
 +MC2"`.&"M`-U)$(T`W6"I""P-W/#[8""3@*X,W""*@(I@J1H@6X"0`6`@KO\D
 +MD##.&"!'_RP-W""*@"";@(T@@"D/R0*P):``()N`F56`R,`&D/48J0@@6X"0
 +M`6"I`2PB@#`"J00@J/\@KO]@2(HI`0H*"@HL(H`0`DD0(%N`:)`!8""H_ZD!
 +M(*C_J0D@J/\@KO]X&"!'_RP-W""*@*GVH(J%`H0#J0"%!"PB@#`#($:!(%Z!
 +ML`GF!*4$R0F0ZQA88*GVA0*F!!BIBGU5@84#8``($`8.!`P""B";@(T@@"D/
 +MR0*0`6"B`J``J0@L#=SP^ZT`W4D0C0#=K0S<D0+(T.GF`\K0Y&!(A`2**0$*
 +M"@H*"0(L(H`0`DD0(%N`:)`!8""H_Z4$(*C_J0$@J/\@KO]XJ4"%!3@@1_]X
 +M+`W<H@*@`*T`W<T`W=#X104I0/#RL0*-#-RE!4E`A06I""P-W/#[R-#=Y@/*
 +MT-@8($?_+`W<((J`().`K@S<((J`BHT@@"D/R0)88*D.C0#_J?^-2H"-2X"B
 +M!YU-@,H0^HU,@!A@S4J`T!#L2X#0"XB8"AAIBJBI]AA@C4J`CDN`A`4@\8"0
 +M&:T@@"D/R0OP`CA@(*:`K4J`KDN`I`60VV"M2H"N2X"D!4PF@J+_Z#CI$K#Z
 +MB!#W&&D2R*B*8"!L@J(`P`F0"$B8Z0FH:*(!R&"&"R!^@H4(A@F$"J4(I@FD
 +M"B`F@I`!8(4,A`VB`J``L0R1!LBQ#)$&R-#TY@WF!\K0[>8*I0K)"I`2J0&%
 +M"N8)I0G)`I`&J0"%">8(Q@O0NQA@..D"L`&(KCZ`X`'P!PJ$!R8'I`<8;42`
 +MD`'(8"#C@J+VA@:BG(8'KCZ`3)&"('Z"S4J`T`_L2X#0"DBI_XU*@(U+@&@@
 +MC(%@```@XX*B]H8"HIR&`XTK@XPL@R`0@Y`!8*T^@,D"L`%@K2N#K"R#&&D!
 +MD`'((!"#8*D.C0#_J0"@`"!^@B`F@I`!8(4"A`.@#;$"C3Z`R0.0!ZD\C2"`
 +M.&"@$+$"R0+0\:`6L0*-/X#)!+#FH!&Q`HU!@,F!L-M*2DI*C4"`H!.Q`HU"
 +M@,BQ`HU#@*`8L0+)"="_H!JQ`LD"T+>@#K$"R0'0KZT_@`H8:0&-18`8;4"`
 +MC42`K4*`K$.`..U$@+`!B(U&@(Q'@*T^@,D"T`9.1X!N1H`8K4:`:0*-2("M
 +M1X!I`(U)@*GVH*"%!H0'J0&@`*X_@""1@I`!8*GVH*:%!H0'K46`H`"N0(`@
 +MD8*0`6"I]J"FKD&`&&"%!80#1@-JA02F`PHF`QAE!(4"BF4#A0,8I0)I]H4"
 +MI0-IH(4#H`*Q`ID&`(@0^&`@1(2E!2D!T`BE!RD/J*4&8*4'H@1&"&K*T/JD
 +M"&`@1(2E"BD/A0JE!2D!T`^E"84&I0<I\`4*A0=,Q82B!`8))@K*T/FE"H4(
 +MI0<I#P4)A0>@`KD&`)$"B!#XC$R`8#BM)X#I]JTH@.FF2BD'JJG_G4V`8*`.
 +MC`#_K2>`K"B`A0*$`ZGEH`"1`J`:L0*%#LBQ`H4/I0_)!9`#3-.$J*4.('2$
 +M2)A(J0"%"84*I0ZD#R"2A&B%#VB%#DP(A:D.C0#_K4R`\"ZI`(U,@*D"A6&I
 +M`84.K3^`A6"I]J"@A0*$`Z4.H``@$(.0`6#F#L9@T/#&8=#?K46`A0ZM0("%
 +M8*D`A6&I]J"FA0*$`Z9AO4V`\!"I`)U-@*4.H``@$(/&`\8#Y@[F8>8#Y@/&
 +M8-#=&&"@#HP`_ZD"H`"%#H0/A&"$8:4.I`\@=(2$`@4"T`;F8-`"YF'F#M`"
 +MY@^E#LU(@*4/[4F`D-NN/H`&8"9ARM#YJ0"D8*9A8*5DI&4@_X*0`6"I]J"<
 +MA6"$8:D`A6*M/H`*A6.E9*1E('2$A62$9<`%D!RM)8"%8JD!KCZ`X`'P`JD#
 +M+2:`T`6N)8#P`H5C(#6&8*8/T`(88"#)_Y`$C2"`8*G_IF/0`J5BA6:@`+%@
 +M)`X0!JJ]]HCP`R#2_\C$9M#L&*5@96:%8)`"YF$XI6+E9H5BL`+&8Z5B!6/0
 +MQ"#,_QA@H`Z,`/^%#H8/K2.`K"2`A62$94RAAB#JA9`!8*5ER060]!A@A0Z&
 +M4*D`A5&%5(558*D`A6*%8Z94\`2I`!A@IE`@QO^0!(T@@&"I_Z93T`*E4H5F
 +MH``@((>0`6#P!Y%@R,1FT/&$9ABE8&5FA6"0`N9A&*5B96:%8I`"YF,XI5+E
 +M9H52L`+&4Z54T`:E4@53T+@@S/^E8@5C&&"E5/`!`*51\`BI`(51J0H88*55
 +M*4#P"*G_A52I`!A@(,__II"&59`'C2"`(,S_8"0.$`RJO?:)\,?)#=`"A5&B
 +M_QA@I5K-2("E6^U)@)`!8*5:I%L@=(2$`@4"T`:E6J1;&&#F6M#<YEM,8X<@
 +M8X>0`6"%"80*I60%9?`2I62D9:8)AF2F"H9E()*$3,>'K2>`K"B`A0*$`Z`:
 +MI0F1`H5DR*4*D0*%9:G_H`^%"80*I62D92"2A*5DI&4@+8.0`6`8I5QE8H5<
 +MI5UE8X5=D`+F7AA@H`Z,`/\@J8:I`(5DA66%6X5<A5V%7JD"A5JI]J"<A6"$
 +M8:T^@`JHJ0"%4H13(+:&D`%@\`8@C(>0X&"M)X"L*("%`H0#H@"@'+5<D0+(
 +MZ.`#D/8@TX088*`.C`#_A@^JJ0`@J8:I]J"<A6"$8:D`H`2%4H13(+:&L!+P
 +M$*GVH)R%8(1A(#6&L`-,6(A@H`Z,`/^J(,;_D`RI`(TL@(TI@(TJ@&`@Y(@@
 +MY(@@Y(B-*8`@Y(B-*H"I`(TL@"#DB,DB\`C)0M#U(,S_8*``(.2(R2+P!IDM
 +M@,C0\XPL@"#DB,D@\/F-*X`@Y(C)`-#Y(,S_8"#/_[`%))!P`6!H:"#,_TR+
 +MB```````````%`D-`),`````````````````````````("$B(R0E)B<H*2HK
 +M+"TN+S`Q,C,T-38W.#DZ.SP]/C]`P<+#Q,7&Q\C)RLO,S<[/T-'2T]35UM?8
 +MV=I;7%U>7\!!0D-$149'2$E*2TQ-3D]045)35%565UA96MO<W=[?````````
 +M````````````````````````````````````````````````````````````
 +M````````````````````````````````````````````````````````````
 +M```````````````````````````````````````````````````````)````
 +M#0````````@``````````````"`A(B,D)28G*"DJ*RPM+B\P,3(S-#4V-S@Y
 +M.CL\/3X_0&%B8V1E9F=H:6IK;&UN;W!Q<G-T=79W>'EZ6UQ=7E\`````````
 +M````````````````````````````````````````````````````````````
 +M``````````````````````````````````````````````````````````!@
 +M04)#1$5&1TA)2DM,34Y/4%%24U155E=865I[?'U^?P``````````````````
 +2``````````````````````!^````
 +`
 +end
 +
 +7. THE FUTURE
 +
 +Future improvements to this program would include implementation of MS-DOS
 +formatting, more file manipluation commands (such as Rename), re-writing the
 +user-interface BASIC program in machine language, and making a file buffering
 +facility for those people with only one disk drive.  However, I don't intend
 +to do much more to this program.  My intentions are to put this functionality
 +into a device driver for a new operating system (or at least, operating
 +environment) for the C-128.  Anyone else is free to improve this program.
 +
 +=============================================================================
 +</code>
 +====== In the Next Issue: ======
 +<code>
 +TWO-KEY ROLLOVER
 +
 +This article will examine how a two-key rollover mechanism would work for the
 +keyboards of the C-128 and 64 and will present Kernal-wedge implementations
 +for both machines.  Webster's doesn't seem to know, so I'll tell you that this
 +means that the machine will act sensibly if you are holding down one key and
 +then press another without releasing the first.  This will be useful to fast
 +touch typers.
 +
 +The Second Rob Hubbard Music Routine
 +
 +  In this article, the second Rob Hubbard music routine will be presented in
 +the same way as the first. Future issues will hopefully examine various other
 +music routines including various Martin Galway, Benn Daglish, Jeoren Tel,
 +and Manaics of Noise routines. Note: Unfortunately the author completes
 +university (and thus loses internet access) in August 1993.
 +
 +DYCP - Horizontal Scrolling
 +
 +DYCP - is a name for a horizontal scroller, where characters go smoothly
 +up and down during their voyage from right to left. One possibility is a
 +scroll with only 8 characters - one character per sprite, but a real demo
 +coder won't be satisfied with that.
 +
 +Multi-Tasking on the C=128 - Part 2
 +
 +This article will examine the actual code that makes up the multi-tasking
 +kernal in detail and include some example programs explaining it use.
 +
 +The 1351 Mouse Demystified
 +
 +This article will explain how the 1351 mouse works as well as provide an easy
 +to use interface in machine language for both basic and machine language
 +programmers.
 +========================================================================END===
 +</code>
magazines/chacking5.txt · Last modified: 2015-04-17 04:34 by 127.0.0.1