User Tools

Site Tools


magazines:chacking5

Differences

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

Link to this comparison view

magazines:chacking5 [2015-04-17 04:34] (current)
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  0  1
 + ​0 ​ 0  1  1
 + ​0 ​ 1  0  no change
 + ​0 ​ 1  1  1
 + ​1 ​ 0  0  1             Clear $D017 -> flip-flop is set
 + ​1 ​ 0  1  1
 + ​1 ​ 1  0  no change ​    Set $D017   -> flip-flop resets at the end of line
 + ​1 ​ 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,​x ​   ; Stretch sinus position
 +0b9b    sta $0bd1       ; Set low bytes of indices
 +0b9e    sta $0bb8
 +0ba1    adc SINSPEED,​x ​ ; Add stretch sinus speed (carry is not set)
 +0ba4    and #$7f        ; Table is 128 bytes (twice)
 +0ba6    sta SINPOS,​x ​   ; Save new sinus position
 +0ba9    lda YSINPOS,​x ​  ; Get the Y sinus position
 +0bac    adc YSINSPEED,x ; Add Y sinus speed
 +0baf    sta YSINPOS,​x ​  ; Save new Y sinus position
 +0bb2    tay             ; Position to index register
 +0bb3    lda YSINUS,​y ​   ; 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,​x ​     ; 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,​y ​     ; 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,​y ​     ; 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,​X ​    ; 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,​X ​   ; 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,​X ​  ; 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,​X ​     ;  because STA nnnn,X is 5 cycles
 +        STA STRETCH+32,​X ​  ; ​ 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,​X ​      ; y-position
 +        LDA HEIGHT,​X ​      ; 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,​Y ​   ; 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,​X ​      ; 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 ​       ; 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,​x ​     ;copy ptrs to this
 +  sta currtrkhi,​y ​ ;​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,​x ​ ;init pos offsets
 +  sta patoffset,​x ​ ;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,​x ​ ;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,​x ​ ;get the data from
 +  lda ($02),​y ​     ;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,​y ​    ;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,​x ​ ;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),​y ​     ;get length of note
 +  sta savelnthcc,​x
 +  sta templnthcc
 +  and #$1f
 +  sta lengthleft,​x
 +
 +  bit templnthcc ​  ;test for append
 +  bvs appendnote
 +
 +  inc patoffset,​x ​ ;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),​y ​     ;get instr/​portamento
 +  bpl +
 +
 +  sta portaval,​x ​  ;save portamento val
 +  jmp ++
 +
 ++ sta instrnr,​x ​   ;save instr nr
 +
 ++ inc patoffset,x
 +
 +;3rd byte is the pitch of the note
 +;get the 'base frequency'​ here
 +
 +getpitch =*
 +
 +  iny
 +  lda ($04),​y ​     ;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,​x ​   ;instr num
 +  stx tempstore
 +  asl              ;instr num*8
 +  asl
 +  asl
 +  tax
 +
 +  lda instr+2,​x ​   ;get control reg val
 +  sta tempctrl
 +  lda instr+2,x
 +  and appendfl ​    ;​implement append
 +  sta $d404,y
 +
 +  lda instr+0,​x ​   ;get pulse width lo
 +  sta $d402,y
 +
 +  lda instr+1,​x ​   ;get pulse width hi
 +  sta $d403,y
 +
 +  lda instr+3,​x ​   ;get attack/​decay
 +  sta $d405,y
 +
 +  lda instr+4,​x ​   ;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,​x ​ ;preview 4th byte
 +  ldy patoffset,x
 +  lda ($04),y
 +
 +  cmp #$ff         ;​check for eop
 +  bne +
 +
 +  lda #$00         ;end of pat reached
 +  sta patoffset,​x ​ ;inc position for
 +  inc posoffset,​x ​ ;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,​x ​ ;length exceeded so
 +  and #$fe         ;​start the release
 +  sta $d404,​y ​     ;and kill adsr
 +  lda #$00
 +  sta $d405,y
 +  sta $d406,y
 +
 +
 +;vibrato routine
 +;(does alot of work)
 +
 +vibrato =*
 +
 +  lda instrnr,​x ​   ;instr num
 +  asl
 +  asl
 +  asl              ;instr num*8
 +  tay
 +  sty instnumby8 ​  ;save instr num*8
 +
 +  lda instr+7,​y ​   ;get instr fx byte
 +  sta instrfx
 +
 +  lda instr+6,​y ​   ;get pulse speed
 +  sta pulsevalue
 +
 +  lda instr+5,​y ​   ;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,​x ​   ;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,​x ​  ;​pulsedir 0 is up and
 +  bne pulsedown ​   ;1 is down
 +
 +  lda pulsespeed ​  ;​pulse width up
 +  clc
 +  adc instr+0,​y ​   ;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,​x ​  ;​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,​x ​  ;​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,​x ​  ;​check for portamento
 +  beq drums        ;none
 +
 +  and #$7e         ;toad unwanted bits
 +  sta tempstore
 +
 +  lda portaval,​x ​  ;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,​y ​     ;drum sound
 +
 +  lda voicectrl,​x ​ ;if ctrlreg is 0 then
 +  and #$fe         ;​noise is used always
 +  bne dumpctrl
 +
 +firstime =*
 +
 +  lda savefreqhi,​x ;noise is used for
 +  sta $d401,​y ​     ;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,​x ​   ;odd, note+12
 +  clc
 +  adc #$0c
 +  jmp ++
 +
 ++ lda notenum,​x ​   ;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
 +   ​--- ​ -  ---  ---  -------- ​ ---  ------
 +     ​1 ​ *  ASC  SEQ  HACK4     ​TXT ​ 120732
 +     ​2 ​    ​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 ​        ​T ​ LENGTH
 +   ​--- ​ -  ---  ---------------- -  ------
 +     ​1 ​ *  BIN  LRR-128 ​         P    9876
 +     ​2 ​    ​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 ​      ​8 ​    ​Filename
 +    8..10       ​3 ​    ​Extension
 +       ​11 ​      ​1 ​    ​Attributes:​ $10=Directory,​ $08=VolumeId
 +   ​12..21 ​     10     <​unused>​
 +   ​22..25 ​      ​4 ​    Date
 +   ​26..27 ​      ​2 ​    ​Starting FAT entry number
 +   ​28..31 ​      ​4 ​    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   ​1 ​  ​2 ​  ​3 ​  ​4 ​  ​5 ​  ​6 ​  ​7 ​  ​8 ​  ​9 ​  ​a ​  ​b ​  ​c ​  ​d ​  ​e ​  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   ​1 ​  ​2 ​  ​3 ​  ​4 ​  ​5 ​  ​6 ​  ​7 ​  ​8 ​  ​9 ​  ​a ​  ​b ​  ​c ​  ​d ​  ​e ​  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 ​        ​t ​ 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 (external edit)