User Tools

Site Tools



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

Link to this comparison view

magazines:chacking20 [2015-04-17 04:34] (current)
Line 1: Line 1:
 +====== C=Hacking #20 ======
 +                   ########​
 +             ##################​
 +         ###### ​           ######
 +      #####
 +    #####  ####  ####      ##      #####   #### ​ ####  ####  ####  ####   #####
 +  #####    ##    ##      ####    ##   ## ​  ## ​ ###     ## ​   ####  ##   ## ​  ##
 + ##### ​   ######## ​    ## ​ ##   ## ​       #####       ## ​   ## ## ##   ##
 +#####    ##    ##    ######## ​ ##   ## ​  ## ​ ###     ## ​   ##  ####   ## ​  ##
 +#####  ####  ####  ####  ####  #####   #### ​ ####  ####  ####  ####   ######​
 +#####                                                                    ##
 + ###### ​           ###### ​          Issue #20
 +   ################## ​            April 18, 2001
 +       ########​
 + Ineptitude:​ If you can't learn to do something well, learn to enjoy
 +     doing it poorly.
 + -- "​Demotivator"​ poster
 +Hoo-ah! ​ Time for another late (but hopefully great) issue of C=Hacking!
 +This issue features several nifty articles on both software and hardware
 +projects. ​ There seems to be a lot of activity in the hardware area right
 +now, so hopefully we'll see more hardware articles in future issues.
 +In the software area, however...
 +If I may pithily berate for a moment, I'd like to observe that talking about
 +programming on comp.sys.cbm is -- this may come as a surprise to some -- not
 +the same as actually programming. ​ I'd like to once again encourage those who
 +have been talking about projects, or have half-completed projects sitting
 +around on an FD disk somewhere, to go for it and get the job done!
 +Just like... um... C=Hacking (hey, it gets done... eventually...).
 +The format for this issue has changed a little, with all the news and stuff
 +moved into the previously skimpy "​Jiffies"​ section. ​ Therefore, this is now
 +just the '​me'​ column.
 +So '​me'​ would like to thank all the authors in this issue for their time
 +and effort (and patience), and all of the true C= Hackers out there for their
 +spirit and work and cool projects.
 +'​Me'​ is also very happy to announce that he is getting married on August 17.
 +And heck, you're ALL INVITED (I think an SX-64 would make an excellent
 +wedding present, don't you?).
 +And finally, me still thinks the 64 is the niftiest computer ever made.
 +.                                    C=H #20
 +===== Contents =====
 + o Voluminous ruminations from your unfettered editor.
 + o News, things, and stuff.
 +Side Hacking
 + o "​Super/​Turbo CPU VDC Hack", by Henry Sopko <​>​
 +   Normally it is not possible to access the VDC chip when using
 +   a SuperCPU64 or Turbo CPU on a 128.  The 1-wire hack described
 +   in this article fixes that situation!
 + o "16K Memory Expansion for the VIC-20",​ by Per Olofsson
 +   <​>​. ​ This article describes a nifty way to
 +   add more memory to the VIC-20, along with some basic circuit
 +   design information for the hardware neophyte (i.e. people like me!).
 + o "Quick Quadratic Splines",​ by moi <​>​
 +   A spline is a powerful tool for drawing a curve through an arbitrary
 +   set of points -- for motion/​animation,​ for arbitrary curves (like
 +   fonts), and numerous other tasks. ​ This article describes _quadratic_
 +   splines and some fast C64 implementations,​ and includes a program
 +   for experimenting with splines.
 +Main Articles
 + o "​VIC-20 Kernal ROM Disassembly Project",​ by Richard Cini
 +   <​>​
 +   The ever-dependable Richard Cini has written the fourth article
 +   in the quest for a complete disassembly of the VIC-20 kernal. ​ This
 +   installment focuses on device I/O routines: SETNAM, SETLF, OPEN,
 +   and beyond.
 + o "MODs and Digital Mixing",​ by Jolse Maginnis
 +        <​>​.
 +   Josmod is a program for JOS, by Jolse, that can play Amiga MOD files
 +   (and their newer successors). ​ This article describes the general
 +   functioning of the program, the layout of a MOD file, and how to mix
 +   multiple digital samples in real-time (and hence play MODs!).
 + o "The C64 Digi", by Robin Harbron <​>,​ Levente
 +   Harsfalvi <​>,​ and Stephen Judd <​>​
 +   This article is, we hope, a complete reference on digital sampling
 +   and the C64, including: general theory, SID hardware description,​
 +   and methods of playback (changing $d418, pulse width modulation,
 +   and various tricks). ​ Numerous code examples are given, along with
 +   a program that does true 8-bit playback at 16KHz -- it requires a
 +   SuperCPU, but it is most impressive, and chances are awfully good
 +   that you've never heard a digi like this out of SID before.
 +===== Credits =====
 +Editor, The Big Kahuna, The Car'​a'​carn..... Stephen L. Judd
 +C=Hacking logo by.......................... Mark Lawrence
 +Special thanks to the folks who have helped out with reviewing and such,
 +to the article authors for being patient, and to all the cbm hackers that
 +make this possible!
 +Legal disclaimer:
 + 1) If you screw it up it's your own fault!  ​
 + 2) If you use someone'​s stuff without permission you're a dork!
 +About the authors:
 +Jolse Maginnis is a 20 year old programmer and web page designer,
 +currently taking a break from CS studies. ​ He first came into contact
 +with the C64 at just five or six years of age, when his parents brought
 +home their "​work"​ computer. ​ He started out playing games, then moved on
 +to BASIC, and then on to ML.  He always wanted to be a demo coder, and in
 +1994 met up with a coder at a user's group meeting, and has since worked
 +on a variety of projects from NTSC fixing to writing demo pages and intros
 +and even a music collection. ​ JOS is taking up all his C64 time and he
 +is otherwise playing/​watching sports, out with his girlfriend, or at a
 +movie or concert somewhere. ​ He'd just like to say that "​everyone MUST
 +buy a SuperCPU, it's the way of the future"​ and that if he can afford
 +one, anyone can!
 +Richard Cini is a 31 year old vice president of Congress Financial
 +Corporation,​ and first became involved with Commodore 8-bits in 1981, when
 +his parents bought him a VIC-20 as a birthday present. ​ Mostly he used it
 +for general BASIC programming,​ with some ML later on, for projects such as
 +controlling the lawn sprinkler system, and for a text-to-speech synthesyzer.
 +All his CBM stuff is packed up right now, along with his other "​classic" ​
 +computers, including a PDP11/34 and a KIM-1. ​ In addition to collecting
 +old computers Richard enjoys gardening, golf, and recently has gotten
 +interested in robotics. ​ As to the C= community, he feels that it
 +is unique in being fiercely loyal without being evangelical,​ unlike
 +some other communities,​ while being extremely creative in making the 
 +best use out of the 64.
 +Robin Harbron is a 28 year old internet tech support at a local
 +independent phone company. ​ He first got involved with C= 8-bits
 +in 1980, playing with school PETs, and in 1983 his parents convinced
 +him to spend the extra money on a C64 instead of getting a VIC-20.
 +Like most of us he played a lot of games, typed in games out of
 +magazines, and tried to write his own games. ​ Now he writes demos,
 +dabbles with Internet stuff, writes C= magazine articles, and, yes,
 +plays games. ​ He is currently working on a few demos and a few games,
 +as well as the "​in-progress-but-sometimes-stalled-for-a-real-long-time-
 +until-inspiration-hits-again Internet stuff"​. ​ He is also working on
 +raising a family, and enjoys music (particularly playing bass and guitar), ​
 +church, ice hockey and cricket, and classic video games.
 +Levente Harsfalvi is a 26 year old microcontroller programmer who works at
 +a small local company. ​ His first C= encounter was a Plus/4 at school, at
 +the age of 12, and later (1988) his parents bought a C-16.  After learning
 +BASIC and ASM coding he joined a Plus/4 demo group (GOTU, and later Coroners),
 +and has worked on game conversions,​ an FLI editor, music software (including
 +a SID emulator to play c64 music on TED) and numerous other software and
 +hardware projects. ​ More recently he has begun taking some measurements on
 +the Plus/4 to figure out things such as how the sound generator works and is
 +working on a C-16 demo.  Outside of the C= he enjoys cycling and running,
 +and ~50km walking tours.
 +For information on the mailing list, ftp and web sites, send some email
 +While http://​​fridge/​chacking is the main C=Hacking homepage,
 +C=Hacking is available many other places including
 + http://​​pub/​cbm/​magazines/​c=hacking/​
 + http://​​pub/​micro/​commodore/​magazines/​c=hacking/​
 +===== Jiffies =====
 +$01 Not _too_ long ago, an effort was made to write down the pin assignments
 +    for the video port of all the major C= computers. ​ The result, as compiled
 +    by William Levak <​>,​ is:
 +            8              7
 +Commodore ​         6           Video Connector
 +            3              1
 +              5         4
 +                   2
 +                                                    Plus4     ​C16/​C128
 +     ​CBM-II ​     VIC20     ​VIC20CR ​   C64     Pin C64A/​SX64 ​  ​C64B/​C/​E
 +    --------- ------------ ------- ---------- --- ---------- ----------
 +(R) Luminance +5 V         +5 V    Monochrome ​ 1  Monochrome Monochrome (Y)
 +    Ground ​   Ground ​      ​Ground ​ Ground ​     2  Ground ​    ​Ground
 +(B) V. Sync.  Audio        Audio   ​Audio ​      ​3 ​ Audio      Audio      (W)
 +(W) Video     50 Ohm Video Video   ​Video ​      ​4 ​ Video      Video
 +(Y) H. Sync.  Video        Video   Audio In    5  Audio In   Audio In
 +                                               ​6 ​ Chroma ​    ​Chroma ​    (R)
 +                                               7
 +                                               ​8 ​            +5 V
 +$02 "​Professor Dredd" <​>​ has uploaded the programs
 +    from "​Inside Commodore DOS" to his webpage at
 + http://​​profdredd
 +$03 Todd Elliot has written a version of Pasi Ojala'​s zip/unzip program
 +    for GEOS/​Wheels,​ available at:
 + http://​​~albert/​Dev/​gunzip-geos/​
 +$04 Jeri has been hard at work on her video/​cpu-board:​
 + http://​​cm_easy/​
 +$05 Jolse has been hard at work on JOS (but you'll have to wait until next
 +    issue for the article!):
 +    Here's the latest Jos news:
 +    Support for CMD HD, including native partitions. (Read only atm, and no
 +    1581 partitions)
 +    Enhanced shell with filename completion and recursive wildcards.
 +    Improvements to the GUI - the architecture now allows for a different
 +    window interface styles transparent to the application.
 +    Numerous showstopper bugs killed.
 +    Improvements to the httpd server to allow directory listings.
 +    Swiftlink/​T232/​Duart drivers.
 +    Started writing tutorials for programmers wanting to give Jos a go.
 +    Plus heaps more things not worth mentioning..
 +    cya!,
 +    Jolse
 +$06 Philip '​Pepto'​ Timmermann has made some very accurate measurements
 +    (and RGB calculations) of VIC-II colors:
 + http://​​projects/​colorvic/​
 +$07 And finally, I have written a 2D graphics library for use by assembly
 +    language programs (plot points, draw lines and circles, that kind of
 +    thing). ​ It's super-easy to use and pretty fast, so go ahead and use it
 +    if you need some hires drawing routines!
 +    For more information,​ pop on over to
 + http://​​fridge/​grlib/​
 +====== Side Hacking ======
 +===== Super/Turbo CPU VDC Hack =====
 +                           ​Super/​Turbo CPU VDC Hack
 +                                     for
 +         ​Commodore 128 with SuperCPU 64 or Turbo Master CPU Accelerators
 +      ​  ​       by Henry Sopko
 +             (
 +As many of you probably know, accessing the VDC (8563) chip is not
 +possible when using a SuperCPU 64 (version 1 tested only!) or the
 +TurboMaster CPU (latest revision), until now. With this 1 wire hack,
 +you can have access to the C128 (flat only tested) VDC 80 column
 +chip with your SCPU 64 or TurboMaster CPU!
 +I take no responsibility whatsoever to any damage that may occur to
 +your Computer or SCPU 64/TM CPU resulting from this hardware hack!
 +You do this hack totally at YOUR OWN RISK!
 +[C=Hacking disclaimer: if you screw it up, it's your fault!]
 +-------------------------------------------------------------------- ​
 +This hack was only tested on a flat C128, using CMD's SCPU 64 (version 1).
 +Tested also was the Turbo Master CPU 4.09 MHz accelerator from Schnedler
 +Systems (latest revision).
 +Parts required: (1) long wire (12 inches or so, cutting it to length).
 +Dissassemble your C128, taking the metal shield completely off.  With the
 +motherboard exposed, find the 8563 VDC chip (U22). After locating this chip,
 +remove it (take note of the notch position, so you correctly re-insert the
 +chip). Bend out PIN 9 (R/W) of the 8563 just enough so it does not touch the
 +socket or any metal (in the flat 128, theres not much room, so be carefull).
 +Now re-insert the 8563 chip. Take a wire (you can either solder the wire to
 +pin 9 as I did, or use Microclips -- your choice). ​ Now, connect the wire by
 +soldering or using Microclips to PIN 5 on the CARTRIDGE EXPANSION PORT. Thats
 +I have done this hack quite a while ago without any signs of problems
 +whatsoever. The C128 functions the same with or without a SCPU 64 or TM CPU
 +connected after preforming this hack. I really did this hack for the Turbo
 +Master CPU so I could access the VDC. Turns out, that the SCPU 64 accelerator
 +(and maybe others?) work with this hack as well. The better choice of course
 +is to buy a CMD SuperCPU 128 to take full advantage of the Commodore 128 in
 +both modes!
 +===== 16K Memory Expansion for the Vic-20 =====
 +By Per Olofsson <​>​.
 +Thanks to Ruud Baltissen, Pasi Ojala and Spiro Trikaliotis for help and
 +suggestions. ​ The latest version of this project may be found at
 +I tried to keep the expansion as simple as possible, requiring no chips
 +besides the two memory chips, and a minimum of soldering. It uses two 6264
 +SRAM chips (62256 also works) piggy-backed to the Kernal and Basic 2364
 +ROMs. It's recommended that you do the expansion one chip at a time, as this
 +greatly simplifies troubleshooting. After the first chip checks out OK, do the
 +second one. I'll start out by explaining a few basic principles.
 +o Static Electricity
 +We'll be using CMOS RAM chips, and they are very sensitive to static
 +electricity. If you don't have an anti-static wrist strap, make sure you touch
 +a grounded surface before you touch the chips. Do not work sitting on shag
 +carpet in a mohair sweater while petting the cat :)
 +o Be Careful With ICs
 +ICs are sensitive to heat, and keeping the soldering iron for too long on a
 +pin can toast the chip. If you need to redo a solder joint on a pin, wait for
 +it to cool down first.
 +o Up, Down, and Pin Numbering
 +The top of a DIP IC (the kind used in Vic-20s) is marked with a small notch,
 +looking something like this:
 +       ___ ___
 +  1  =|   ​V ​  ​|= ​ 8
 +  2  =|       ​|= ​ 7
 +  3  =|       ​|= ​ 6
 +  4  =|       ​|= ​ 5
 +      `-------'​
 +As you can see, the pin numbers start at 1, and the first pin is the top left,
 +going down to the bottom left, then from the bottom right to the top right.
 +Piggy-backing is a quick way of adding another IC on top of an existing
 +one. Normally you would create a PCB with chip sockets, connect it to the
 +expansion port and populate it with memory chips, but with piggy-backing you
 +simply add chips on top of internal ones.  This works when the new chip's pins
 +has signals that are identical to, or closely matches, the layout of the one
 +it's being mounted on top of.
 +                                                6264 RAM
 +                                                ___ ___
 +             2364 ROM                 ​NC ​  ​1 ​ =|   ​V ​  ​|= ​ 28  Vcc
 +             ___ ___                 ​A12 ​  ​2 ​ =|       ​|= ​ 27  /WE
 +   ​A7 ​  ​1 ​ =|   ​V ​  ​|= ​ 24  Vcc       ​A7 ​  ​3 ​ =|       ​|= ​ 26  CS2
 +   ​A6 ​  ​2 ​ =|       ​|= ​ 23  A8        A6   ​4 ​ =|       ​|= ​ 25  A8
 +   ​A5 ​  ​3 ​ =|       ​|= ​ 22  A9        A5   ​5 ​ =|       ​|= ​ 24  A9
 +   ​A4 ​  ​4 ​ =|       ​|= ​ 21  A12       ​A4 ​  ​6 ​ =|       ​|= ​ 23  A11
 +   ​A3 ​  ​5 ​ =|       ​|= ​ 20  /CS       ​A3 ​  ​7 ​ =|       ​|= ​ 22  /OE
 +   ​A2 ​  ​6 ​ =|       ​|= ​ 19  A10       ​A2 ​  ​8 ​ =|       ​|= ​ 21  A10
 +   ​A1 ​  ​7 ​ =|       ​|= ​ 18  A11       ​A1 ​  ​9 ​ =|       ​|= ​ 20  /CS1
 +   ​A0 ​  ​8 ​ =|       ​|= ​ 17  D7        A0  10  =|       ​|= ​ 19  D7
 +   ​D0 ​  ​9 ​ =|       ​|= ​ 16  D6        D0  11  =|       ​|= ​ 18  D6
 +   ​D1 ​ 10  =|       ​|= ​ 15  D5        D1  12  =|       ​|= ​ 17  D5
 +   ​D2 ​ 11  =|       ​|= ​ 14  D4        D2  13  =|       ​|= ​ 16  D4
 +  Vss  12  =|       ​|= ​ 13  D3       ​Vss ​ 14  =|       ​|= ​ 15  D3
 +            `-------' ​                         `-------'​
 +As you can see there are 8 that don't match. However, we don't have to rewire
 +all of them. NC means "No Connection"​ so we can just ignore that pin. Note
 +that if you're using 62256 chips you'll have to wire this one too, see
 +below. We want to connect CS2 to Vcc, and we'll also swap A11 and A12, leaving
 +5 pins to solder on each chip. Swapping address bus pins works on RAM chips,
 +as it only affects how bits are stored internally -- when you read them out
 +again, they'​re swapped back. This also works for the databus.
 +We'll mount one 6264 on top of the Kernal ROM, and one 6264 on top of the
 +Basic ROM. The ROMs are marked UE11 and UE12 and can be found in the bottom
 +right of the motherboard. The wiring is identical for the two chips, except
 +for the /OE and /CS1 pins. The pins that we want to rewire we carefully bend
 +up so that they don't connect to the ROM. You want to bend them almost all the
 +way up (about 150 degrees) so that you can reach them with the soldering
 +iron. The pins are very sensitive, so make sure you bend the right pins --
 +bending a pin back again could easily break it.
 +Sometimes the pins on the RAM are too wide apart to make a good connection
 +when you piggy-back it. In this case, bend all the pins on the RAM slightly
 +inwards. You can do this by putting it on the side on a flat, hard surface and
 +press gently.
 +o Soldering A11/A12 and Vcc
 +You could get A11, A12, and Vcc from several places on the motherboard,​ but as
 +they'​re available on the ROMs we'll just solder small wires from the ROM to
 +the RAM. Remember that we're swapping A11 and A12, so connect pin 18 on the
 +ROM to pin 2 on the RAM. Connect pins 26 and 28 on the RAM to eachother.
 +o Soldering /WE
 +Pin 27 on the RAMs should be connected to pin 34 on the 6502 CPU. The CPU is
 +the 40-pin chip in socket UE10 on the motherboard,​ right next to the ROM
 +chips. Pin 34 is the 7th pin if you count from the top right pin on the CPU.
 +o /OE and /CS1
 +In the Vic-20 memory is divided into 8 blocks of 8K each. Block 0 is further
 +divided into 8 1K blocks, of which 5 are populated with RAM.  Block 4 is used
 +by the VIC and the VIA chips, block 6 is the Basic ROM, and block 7 is the
 +kernal ROM. This leaves four blocks (1, 2, 3 and 5) available for cartridges
 +and RAM expansions. For RAM to be visible to the basic interpreter,​ you must
 +start by adding ram in block 1, then 2 and then 3. RAM in block 5 is never
 +detected by the basic. 8K cartridges use block 5, and 16K cartridges use block
 +5 together with another one, usually 3. To be as compatible as possible with
 +existing cartridges and to expand basic memory I'll use block 1 and 2 for this
 +expansion, but you could use any two of the available blocks you want. I've
 +added instructions for making the blocks selectable by switches below.
 +The block signals are available on the 74LS138 decoder chip marked UC5 on the
 +left side of the motherboard. Block 1 is on pin 14, block 2 is on pin 13,
 +block 3 is on pin 12 and block 5 is on pin 10. If you look closely you'll see
 +that the signals for block 1 and 2 go out from the chip a few mm to a small
 +pad. It's much easier to connect your wires to these pads than the pins on the
 +decoder chip. Solder a wire from block 1 to pin 20 and 22 on the first RAM
 +chip, and a wire from block 2 to pin 20 and 22 on the second RAM chip.
 +You're done!
 +That's all. When you power up the Vic-20, you should be greeted with a 19967
 +BYTES FREE message. If you've only done one chip so far, you'll get 11775
 +BYTES FREE, provided you connected it to block 1. If you've connected memory
 +to other blocks but not block 1, you'll just get the normal 3583 BYTES FREE
 +message. To test your memory, try this program:
 +  10 input "test which block";​b
 +  20 s = b*8192 : t = s+8191 : e = 0
 +  30 print "​testing databus"​
 +  40 for a = s to t : poke a, 85 : if peek(a) ​ 85 then gosub 1000
 +  50 poke a, 170 : if peek(a) ​ 170 then gosub 1000
 +  60 next : de = e
 +  70 e=0 : print "​testing high address bus"
 +  80 for a = s to t : poke a, int(a/256) : next
 +  90 for a = s to t : if peek(a) ​ int(a/256) then gosub 1000
 +  100 next : he = e
 +  110 e=0 : print "​testing low address bus"
 +  120 for a = s to t : poke a, a and 255 : next
 +  130 for a = s to t : if peek(a) ​ a and 255 then gosub 1000
 +  140 next : print
 +  150 print de;"​errors found in databus test"
 +  160 print he;"​errors found in high address bus test"
 +  170 print e;"​errors found in low address bus test"
 +  999 end
 +  1000 e = e+1 : print "error at";a : return
 +The program takes a couple of minutes to run.
 +  The computer doesn'​t start at all, or all you get is a black screen
 +  ​
 +   ​You'​ve probably shorted or toasted something. Not good, this could
 +   have damaged the computer. Recheck all your soldering and make sure
 +   that you haven'​t accidentally connected something wrong.
 +  The computer powers up with 3583 bytes free
 +  ​
 +   ​Memory in block 1 is either not connected or not working. Check the
 +   ​connections between block 1, /CS1 and /OE, between R/W and /WE, and
 +   ​between Vcc, CS2 and Vcc.
 +  The computer powers up with something other than 3583, 11775 or 19967 bytes
 +  free
 +  ​
 +   ​Memory is functioning partially, check A11 and A12. This could also
 +   ​indicate that a RAM chip is faulty.
 +  The computer powers up with the correct number of bytes free, but the memory
 +  test program fails
 +  ​
 +   ​Memory is functioning partially. If the databus test fails, check the
 +   ​connections between block 1, /CS1 and /OE, between R/W and /WE,
 +   ​between Vcc, CS2 and Vcc, and D0 through D7. If the address bus test
 +   ​fails,​ check A0 through A12.
 +Using 62256 chips instead of 6264
 +You can freely substitute 62256 chips for the 6264. Only two pins differ:
 +             6264 RAM                          62256 RAM
 +             ___ ___                           ___ ___
 +   ​NC ​  ​1 ​ =|   ​V ​  ​|= ​ 28  Vcc     ​A14 ​  ​1 ​ =|   ​V ​  ​|= ​ 28  Vcc
 +  A12   ​2 ​ =|       ​|= ​ 27  /WE     ​A12 ​  ​2 ​ =|       ​|= ​ 27  /WE
 +   ​A7 ​  ​3 ​ =|       ​|= ​ 26  CS2      A7   ​3 ​ =|       ​|= ​ 26  A13
 +   ​A6 ​  ​4 ​ =|       ​|= ​ 25  A8       ​A6 ​  ​4 ​ =|       ​|= ​ 25  A8
 +   ​A5 ​  ​5 ​ =|       ​|= ​ 24  A9       ​A5 ​  ​5 ​ =|       ​|= ​ 24  A9
 +   ​A4 ​  ​6 ​ =|       ​|= ​ 23  A11      A4   ​6 ​ =|       ​|= ​ 23  A11
 +   ​A3 ​  ​7 ​ =|       ​|= ​ 22  /OE      A3   ​7 ​ =|       ​|= ​ 22  /OE
 +   ​A2 ​  ​8 ​ =|       ​|= ​ 21  A10      A2   ​8 ​ =|       ​|= ​ 21  A10
 +   ​A1 ​  ​9 ​ =|       ​|= ​ 20  /CS1     ​A1 ​  ​9 ​ =|       ​|= ​ 20  /CS1
 +   ​A0 ​ 10  =|       ​|= ​ 19  D7       ​A0 ​ 10  =|       ​|= ​ 19  D7
 +   ​D0 ​ 11  =|       ​|= ​ 18  D6       ​D0 ​ 11  =|       ​|= ​ 18  D6
 +   ​D1 ​ 12  =|       ​|= ​ 17  D5       ​D1 ​ 12  =|       ​|= ​ 17  D5
 +   ​D2 ​ 13  =|       ​|= ​ 16  D4       ​D2 ​ 13  =|       ​|= ​ 16  D4
 +  Vss  14  =|       ​|= ​ 15  D3      Vss  14  =|       ​|= ​ 15  D3
 +            `-------' ​                        ​`-------'​
 +Pin 26 (A13) can be connected to Vcc just like on the 6264, but we need to
 +wire pin 1 (A14) to either Vcc, Vss or another address bus pin. It's probably
 +easiest to wire it to pin 2 (A12). We won't be using the greater capacity of
 +the 62256 using this method (doing so would require some kind of decoder
 +logic) but sometimes 62256 chips are cheaper than 6264, or maybe you just have
 +some lying around.
 +Adding Block Select and Write Protect Switches
 +Adding block select switches allows you to chose which blocks should be
 +populated on the fly. This allows you to chose between a basic expansion and a
 +"​cartridge emulator"​ that allows you to load cartridge images into ram and run
 +them. I added one switch for each chip, giving the first chip the option
 +between block 1 and 3, and the second between 2 and 5.
 +  block 1 -----o
 +                \
 +                 ​o----- to /CS1 and /WE on chip 1
 +  block 3 -----o
 +  block 2 -----o
 +                \
 +                 ​o----- to /CS1 and /WE on chip 2
 +  block 5 -----o
 +As a kind of copy protection, some cartridge images try to modify themselves
 +to detect if they'​re running from RAM. If we add write protect switches we'll
 +be able to run those as well. Switch to write enabled while loading, then
 +switch to write protected and reset.
 +      R/W -----o
 +                \
 +                 ​o----- to /WE on chip 1
 +     +5 V -----o
 +      R/W -----o
 +                \
 +                 ​o----- to /WE on chip 2
 +     +5 V -----o
 ++5 V is the same as Vcc.
 +===== Quick Quadratic Splines =====
 +S. Judd <​>​
 +Splines are neat.
 +Splines are a way of drawing curves -- specifically,​ drawing a curve through
 +any set of _points_ that you choose; for example, a curve that starts at (0,0),
 +goes over to (100,50), then loops back to (50,0), and continues on through
 +any number of points can be drawn just by specifying the points.
 +This is a very useful thing! ​ One place splines are used is in drawing fonts.
 +With a relatively small list of points, it becomes possible to draw letters
 +in arbitrary shapes (and scale those shapes easily). ​ Another application
 +is animation -- it is possible to specify a long motion path using just
 +a few points along the path.  Furthermore,​ an animated object might have
 +different parts that move differently -- arms, legs, head, etc.  By just
 +specifying a few frames of the animation, splines can be used to generate
 +the in-between frames.
 +Chances are good that by now you are thinking about some of your own
 +applications for splines, so let's see how they work.
 +Most articles/​books/​etc. that talk about splines talk about "cubic splines"​.
 +This article is going to talk about "​quadratic splines",​ which never seem
 +to be mentioned. ​ Which is a pity, since quadratic splines are perfectly
 +adequate for many (if not most) spline applications and are far more
 +computationally efficient.
 +Graphically,​ a two-dimensional quadratic spline looks something like:
 +              * P1
 +             / \
 +            /   \
 +           / ​    \
 +          /  . .  \
 +         / .     . \
 +        /.         .\
 +       /​. ​           .
 +      /.              * P2
 +     ​. ​
 +    . 
 +P0 *
 +(Another brilliant display of ASCII art).  The curve starts at P0, moves
 +towards P1, and then turns around and heads towards P2, where it ends.  If
 +we draw lines from P0 to P1 and from P1 to P2, these lines are tangent to
 +the curve at the endpoints P0 and P2; that is, these lines say what
 +"​direction"​ the curve is going at the endpoints. ​ Using the above diagram,
 +it should be very easy to visualize how changing P1 changes the shape of
 +the curve.
 +So here, for your computing pleasure, is a quadratic spline:
 +P(t) = P0*(1-t)^2 + 2*P1*t*(1-t) + P2*t^2
 +where P0, P1, P2 are constant values, and t ranges from zero to one.  If
 +you don't like equations, then how about a computer program to compute
 +the above:
 +10 P0=10:​P1=100:​P2=37
 +20 FOR T=0 TO 1 STEP 1/16
 +30 PRINT T,​P0*(1-T)*(1-T) + 2*P1*T*(1-T) + P2*T*T
 +40 NEXT
 +This is actually very easy to understand. ​ When t=0, P(t) = P0.  As t starts
 +to increase, (1-t) starts to decrease and P(t) starts moving towards P1.  As
 +t gets even larger P(t) starts moving towards P2, until finally, at t=1,
 +P(t) = P2.
 +You may be wondering why there is that 2*P1 in the equation, instead of
 +just P1, but that will become apparent shortly.
 +At this point there is an important thing to notice about the above
 +equations: adding _any_ constant to P0 P1 and P2 is just like adding the
 +constant to the entire spline P.  Let P0=P0+C, P1=P1+C, P2=P2+C; the
 +spline is then
 +P'(t) = (P0+C)*(1-t)^2 + 2*(P1+C)*t*(1-t) + (P2+C)*t^2
 +      = [ P0*(1-t)^2 + 2*P2*t*(1-t) + P2*t^2 ] + C*[(1-t)^2 + 2*t*(1-t) + t^2]
 +      = P(t) + C
 +This is one reason that factor of 2 multiplying P1 is important -- it makes
 +the expression multiplying C add up to 1.  This property means that splines
 +can be _translated_ easily. ​ Note that one value you can translate everything
 +by is P0 -- subtract P0 from each point and the spline becomes
 +P(t) - P0 = 2*(P1-P0)*t*(1-t) + (P2-P1)*t^2
 +and hence
 +P(t) = 2*(P1-P0)*t*(1-t) + (P2-P1)*t^2 + P0
 +which gets rid of the P0*(1-t)^2 term.  Depending on how the spline algorithm
 +works, this can save some computation time.
 +Now, the points P0, P1 etc. are called "​control points"​. ​ If you're on the ball
 +(or if you've tried the BASIC program above) you've noticed that while the
 +spline starts at P0 and ends at P2, it never actually reaches P1 -- it just
 +heads towards it but then heads away towards P2.  If a different P1 is
 +chosen, the spline still starts at P0 and ends at P2 but takes a different
 +path between the endpoints. ​ P1 controls the shape of the curve.
 +So now let's say we want a curve that passes through three points, s0, s1,
 +and s2, using a quadratic spline. ​ P0 and P2 are easy to choose:
 + P0 = s0
 + P2 = s2
 +P1 can actually be chosen at this point to be any number of points. ​ All we
 +do is choose a value for t -- call it t1 -- when the spline should hit s1:
 +P(t1) = s1 => P1 = (s1 - P0*(1-t1)^2 - P2*t1^2) / (2*(t1*(1-t1))
 +(just substitute t=t1 into the equation for P(t) and solve for P1).  So if
 +t1=1/2, say, then
 +P1 = (s1 - P0/4 - P2/4) / (2 * 1/4)
 +   = 2*s1 - (P0 + P2)/2
 +and the spline will hit s1 halfway through the iteration at t=1/2.
 +In a typical application,​ of course, the curve will need to go through tens,
 +hundreds, even thousands of points. ​ Naturally, the way to do this is by
 +joining a bunch of splines together.
 +If we just choose a spline for every three points, using the above equation
 +for P1, there will typically be sharp corners where the splines join
 +together. ​ Sometimes this is a good thing -- for example, when drawing
 +something like a valentine we might want sharp corners. ​ But usually
 +what is needed is a nice smooth curve through all the points.
 +With quadratic splines this is a piece of cake.  Each spline has three
 +control points: the two endpoints, and the middle control point P1.  The
 +endpoints are simply chosen to be the points we want to pass through,
 +and P1 can then be chosen to make all the connections smooth.
 +That is, given a list of points s0, s1, s2, ... to put a curve through,
 +choose s0 and s1 to be the endpoints of the first spline, s1 and s2 to
 +be the endpoints of the second spline, and so on, and choose the middle
 +control points to make everything smooth.
 +"​Smooth"​ here means a continuous first derivative -- if you don't know what
 +a derivative is then don't worry about it, it just means that the "​slope"​ of
 +each spline is the same where they join together. ​ Given a spline
 +S1(t) = P0*(1-t)^2 + 2*P1*t*(1-t) + P2*t^2
 +the derivative at t=1 is given by
 +S1' = 2*(P2 - P1)
 +P2-P1 is simply the line segment running from P1 to P2, which verifies what
 +was said earlier: at P2 the curve is tangent to a line drawn from P1 to P2.
 +              * P1
 +             / \
 +            /   \
 +           / ​    \
 +          /  . .  \
 +         / .     . \
 +        /.         .\
 +       /​. ​           .
 +      /.              * P2
 +     ​. ​
 +    . 
 +P0 *
 +P2-P1 is the direction the curve is headed in.  So to join another spline
 +smoothly to the first one, simply extend this line segment and make sure
 +the second middle control point lies somewhere on that line:
 +              * P1
 +             / \
 +            /   \
 +           / ​    \
 +          /  . .  \
 +         / .     . \
 +        /.         .\
 +       /​. ​           .
 +      /.              * P2
 +     ​. ​                ​. ​      * P5
 +    .                   ​\. ​  ./
 +P0 *                     \ ../
 +                          \ /
 +                           * P4
 +Since the first spline is tangent to P2-P1, and the second spline is tangent
 +to P4-P2, the two splines have the same slope at P2, and join smoothly.
 +Note that P4 can be _anywhere_ along the P2-P1 line, and choosing different
 +values for P4 will change the curve.
 +To make this more precise, let the two splines be given by
 +S1(t) = P0*(1-t)^2 + 2*P1*t*(1-t) + P2*t^2
 +S2(t) = P3*(1-t)^2 + 2*P4*t*(1-t) + P5*t^2
 +To join them smoothly, the slope of the first spline must be proportional
 +to the slope of the second spline at the joint:
 +P4-P3 = c*(P2-P1)
 +where c is some constant (the _direction_ of the two slopes need to be the
 +same, but not the magnitude). ​ So, to make the second spline fit smoothly
 +to the first we simply choose the middle control point P4 as
 +P4 = P3 + c*(P2-P1)
 +or, in plain English, starting from P3 (the joining point) move some
 +distance c in the direction P2-P1. ​ To see why the factor c is important
 +just scroll up a page or two to the diagram, and imagine how the curve
 +changes as P4 slides back and forth along the line.
 +We now have the tools to construct a smooth curve that passes through any
 +points s0, s1, s2, s3, ...  Let the first spline pass through s0, s1,
 +and s2, using the t=1/2 formula given earlier:
 +P0 = s0
 +P1 = 2*s1 - (s0 + s2)/2
 +P2 = s2
 +P1 could be chosen in other ways, of course, but the above usually works
 +pretty well.  Then choose each succeeding spline to match up smoothly with
 +the previous spline:
 +P3 = s2 ;start at spline 1 endpoint
 +P4 = P3 + c1*(P2-P1) ;​smooth joint
 +P5 = s3 ;next point
 +P6 = s3
 +P7 = P6 + c2*(P5-P4)
 +P8 = s4
 +and so on.  (When drawing smooth curves, it is of course really only
 +necessary to store the middle control points P4 P7 etc. for each spline).
 +The choice of the constants c1 c2 etc. really depends on how you want the
 +curve to look.
 +As you can see above, each extra point requires another spline. ​ One spline
 +per point may sound like a lot, but there are a lot of pixels in-between each
 +pair of points, and more importantly these are quadratic splines and hence
 +can be made to go very, very fast.
 +Computing the spline
 +Note that the control points are one-time calculations;​ those three constant
 +values completely determine the spline. ​ One way of drawing the spline is
 +to use some lookup tables for t^2, 2*t*(t-1), and (1-t)^2 and do a little
 +fast multiply magic. ​ This is fast and straightforward,​ so I won't talk
 +about it much except to mention that signed multiplies may be required,
 +and each curve is limited to 256 points (t can go from 0 to 255 in the
 +lookup tables). ​ Note also that it would be nice to have an "​incremental"​
 +plot routine, i.e. one that just updates pointers when necessary, instead
 +of recomputing all the bitmap pointers at every iteration.
 +Which of course brings us to a second way of drawing the spline: to ask
 +"how does the spline change when I increment t?"
 +Let's say that t is incremented by dt at each step (t -> t+dt). ​ At each
 +step, then, the spline changes by
 +S(t+dt) = P0*(1-t-dt)^2 + P1*2*(t+dt)*(1-t-dt) + P2*(t+dt)^2
 + = S(t) + k1*dt^2 + k1*2*t*dt + k2
 +where k1 = P0 - 2*P1 + P2 and k2 = 2*dt*(P1 - P0).  That is, to advance one
 +step, we add
 + k1*dt^2 + 2*k1*t*dt + k2
 +to the current value. ​ Take a look at how the above value changes as t
 +t k1*dt^2 + 2*k1*t*dt + k2
 +-- ------------------------
 +0 k1*dt^2  ​   + k2 =   ​k1*dt^2 + k2
 +dt k1*dt^2 + 2*k1*dt^2 + k2 = 3*k1*dt^2 + k2
 +2*dt k1*dt^2 + 4*k1*dt^2 + k2 = 5*k1*dt^2 + k2
 +3*dt k1*dt^2 + 6*k1*dt^2 + k2 = 7*k1*dt^2 + k2
 +and so on.  This means that all we have to do to advance the spline is
 +something like
 + c0 = k1*dt^2
 + C = c0 + k2
 + S = P0
 + S = S + C
 + plot S
 + C = C + 2*c0
 + loop
 +Which is really, really fast.  Instead of specifying the spline with the
 +three values P0, P1, and P2 we can specify it by the three values P0, k1,
 +and k2.
 +It is possible to understand this iterative method somewhat. ​ The update ​
 +constant k1 can be written as
 + k1 = (P0-P1) + (P2-P1)
 +whereas k2 goes like
 + k2 ~= (P1-P0).
 +P1-P0 is the "​direction"​ line segment towards P1, so the curve starts moving
 +towards P1.  But k1 starts to balance that out with the P0-P1 term (the line
 +segment pointing in the opposite direction), while moving towards P2 with the
 +P2-P1 term, which is the direction line segment from P1 to P2.  Now, isn't
 +that all nice and clear now?
 +The Program
 +At the end of this article I've included a sort of "​spline laboratory"​ --
 +it's a little BASIC program that lets you experiment with different aspects
 +of splines and see what they look like.  It uses BLARG for the graphics, so
 +I've included that as well.
 +When you run it, it plots three points on the screen to draw a curve through.
 +You can move these points around, as well as add new points, draw the splines,
 +draw the control point "​a-frames",​ and so on.  It has a ton of commands,
 +basically because I just added them as I got curious about different things:
 ++/- Move to next/​previous point
 +cursor keys Move current point
 +return Draw spline
 +space Clear screen
 +* Toggle point display
 +< >​ Decrease/​increase accuracy (number of bits in iteration)
 +1 <​- Increase/​decrease time step (number of points in spline)
 += List control points (numerical values)
 +. Draw control point a-frame
 +S Toggle smooth splines (see below)
 +A Add more points
 +Q Quit
 +Pressing "​A"​ adds three points at a time, starting at the last point in
 +the curve. ​ The reason for adding three points, instead of just one, is
 +because of the "​S"​mooth spline feature. ​ When smooth splines are selected,
 +the program will draw a smooth curve through all the points. ​ When toggled
 +off, the program will draw a spline through every three points, using the
 +"t=1/2 method"​ to select the middle control point.
 +The program uses the fast iteration method described earlier, using fixed
 +precision arithmetic (since this is what an assembly program would do).
 +The < and > keys are used to change this precision, since certain splines
 +need more precision than others.
 +Finally, the value CC=0.4 is set in line 10.  This is the "​smoothing"​
 +constant, i.e. c1 in the equation for P4 below:
 +P3 = s2 ;start at spline 1 endpoint
 +P4 = P3 + c1*(P2-P1) ;​smooth joint
 +P5 = s3 ;next point
 +You might want to experiment with different values, to see what happens
 +(or else let each spline have their own value).
 +Anyways, the program is not meant to be terribly profound -- just a starting
 +point and something to play with to work out your own ideas on!
 +Cubic Splines
 +Just for completeness,​ here is a cubic spline:
 +P(t) = P0*(1-t)^3 + 3*P1*t*(1-t)^2 + 3*P2*(1-t)*t^2 + P3*t^3
 +As you can see, it has four terms instead of three, and is cubic in t.  But
 +the idea is the same -- at t=0 it starts at P0, as t increases moves
 +towards P1, then towards P2, and finally ends at P3.
 +You can also see that it is significantly more computationally involved than
 +the quadratic spline. ​ Moreover, computing the middle control points P1 and P2
 +is also fairly involved -- much more involved than with the quadratic spline.
 +So, why use a cubic spline? ​ Two reasons: first, you can specify the derivative
 +at _both_ endpoints -- the direction line P1-P0 will be the slope at the
 +starting point, and the direction P3-P2 will be the slope at the end point.
 +This gives some flexibility needed for certain applications.
 +Second, you can alternatively match _second_ derivatives at the connection
 +points, which again can be useful for certain applications.
 +For more information about cubic splines, just check out any decent graphics
 +The Program
 +begin 644
 +M4$L#! H    & $]LQQJSS3>​D,​0< ​ /0(   ​* ​   8FQA<​F<​N;​6TN;​P\ ​ 0(4
 +MPL+^4\_'​F4[Y_:​DAIF.>?​VK@A0>"​!DA88.S^D04BAY[?​O'​_S XWSAQZ?]#YH
 +M1@S\/​_3[H@OV)CBP<​T?​@RB'>​9PH0^-2G+$%F?​5H5Y-6@3JF"#​ IR:​M&​A4HL@
 +MW[3HU*E!CQ;​1OBC2GR"​=!I7J-&​E0%L>​ @\R_M*A+D$R?​6BDV5:​1)G2XQ?​BK4
 +M!\D1..B"​A/​^#​_ P<​]D*0PH%#​IY^Y?​\#​4_\&:​_M]Y?​T#​TN88S-OSSR"'​2FW32
 +M_WN/! X?​8W+PL"​MFHQXZ0>​3HH;​M'​)RWH(>​-3/​CQ&,​. C@S=\CLD?​0X?​@J_<​U
 +MXPZ+[X=N'​[[YY"#​ABT\N$KYZ,​.V>​F7P][)<​D'​\)<​D<​G?​51-. LG*X2]8#PFI
 +M9'​G)AFD0I#​2FH==/#​AL^A#<?​))TTY7^V'​X$9AW _G?K)ME-+ <>​I@YP+$(&​]
 +M%S1>​$:​9%G(K;​A9PA/​U0-'​CEL!=DG!UJZ8OJ D ."​59P2W\VO5*G2MGR-[;​P!
 +MX0 0ZTW!!^\9/​G@4C#​6#​Y1O/​8P7['​S!0D! 1$A08\/​_?​GQ\?'​AP@.1LF '1A
 +M 7I5+(/​L[^J9"​Y86B+,​\1=6E!Y:​O"​%MC:​8*D"​Q;:​M'&​3%DCHG >​22K\BX8&​0
 +M<​-4P$28$MXRK>​K^1:​H,​T'​-Q=\;​W3Q3] \E;#​39LD?​Q)R'/​RAMX'​R@,"​LM*@ ​
 +M0C=OANA]_<​-TF/​PD;/​2E^Z=%$'​-0<​Z^_;​_[3\)4#​-'​U"​[L8PAXU=0.V!SI +
 +M[$:​9!OT"​[R[5SO]>​YU;​_BIV9*AWV&/​Y =KR@P)(#​B]B.@]'​IAI_A8+E4=B[K
 +M&#:6M Q$JXC=I<​GLS9<&"​_H ]O UPJR8_ _-?&​H,"​B?​O<​):​^\73?​-O8]6K1,​
 +M@YMV"​S^$:​@G5_K:​WR\4Q ?:​9%\OWFF2FQS=;​V,​$4-F9R:​\U@:​$OA=#&​-&​K[(
 +MJC[1SWQ_#​*Q ,​9M/#​89T2GE=QJ=,​NIS&​8/​+>​1_KF^6.(>:​AB\Z?​.O>​2<​4 Z;
 +M.B2;​OL+)>​X&​P(I('​X@A+62CM E#:​5ML4R_BI;​ZM(N1=5P[%J\!O45LR,​_M-Y
 +M(BS!)#​W!V/​NDT?​V1]X.!PN!?​6Y0-/"​B%;​I1B9 KPH *D40$Y\6#! _P/​L'​5$
 +MZ"​E!0F_96F#​WA ,;​$=\:​8'>​D*9#​W:​QQM2OX-^QIOY'​QKC- ;​]>​2GQMGHN'​4G
 +M!-F(>​)!Y <​*'​$?​-B";​E53Q-@4QBI=3 OFH#​)8.C[3BMAW'​XKA(I%@\04#​063
 +M44%R:​M"​-NH)$GMO/​PJ%/​GIGZ<​*&​%>​9JI2:​1ND/&​F1]P8L8#​M;​I^.[GH:​O 2,
 +M-'​N#​G'​ELFN],​(V?​74&​)P2@Q R:​D',​$$_,>​$4F. $F&#​XR./​DN%OMG\QJBJ!!
 +MQ4P?​9"​8T= &/​KT2408-GM#​R8;​10!:​$X:​H#​.Y4T8@DA'​Y"​ P&4$L#! H    &
 +M %1LQQJZ:;<​U#​@8 ​ %P)   ​) ​   <​W!L:​6YE;&​%B%!$2! ($!08%#​@8.!PD(
 +M/​@<>​!W[__PT1$P<&​! ,&​!006]_?​W)P,​B3(BP(,#​R,​6T"#​(E0(4"​B5&​7'​U#​DU
 +MJ=8BQK>​I<​^A0V3!=T@0X%.%#​@(9!8AV*TB;​-E"​RS&​.-0L1CC68P=H%:​$% $^
 +M!NFT9TR9-W4>?​3JUJE"​0,​F'"​A D0,​$(>​_'​LJ%*/​K\"​_,​%,​=XC./​1#​]EE(!]'​
 +M^2\C# K0* SS!'​UJTZ=/​J2(%:​91IT*,​ >​R,​T"​M!)_F7JS)*O,​9Z6[#'​D#​=GV
 +M$_KXGQSA4H#​C8]Q=I_JA ,\CA HP<​)*QD[*_3G&​O>​Q+7I3C>​%PSP/​T*R -6#
 +MA)&​-0\F6XLH$J#&​A6X"​A@Y+4N1@$\64?​ _O,​E"​!/​@VP<​ [ "​O)D0+P!K%=E9
 +M)NYM.XAMOUD3(.^$G=I9I<​A3#?>​K45"​R*5RKSD0[<​ZDG!$59AU &​\/​N$H^C9
 +M5IA+FJ-(>​\Z<​. %N5[A+FEV*L*08'​!N5G!H>​Q"&:​Q!":​9!*:​1/​ U8\>​9M?​(5
 +M]O+G*?​Y6\KZ*^ ​ ++O1EK;​-CBPQR;​0BW2Q<​B:​WD3ONH97]95&​09O!"​^5&​G0P
 +MSE7C9.7\5>/​A^>​O >​*FE9<​O:'​+E5ENI"​D0$3T=+%+F09D+S.S#​I@EX/​UA38#​
 +M8 +-##​(I"​TU 2*_GA3NZ>​4JW2 ;I;NR%0 -X0@HS@/,​9EUC&​^D*E <>​]'<​16
 +M 19?:'X V@$#​E+[P_("?​=7"​.'​FKXJ0"​[+T0_L#​$(/​4V_4/​Y )3932,​Y@^?​L+
 +M^0\,#"​*M($FMC >​HDJ%!@EAEHZ0:​XZRLYJK$LU.5L8SXS&​G^C&;​8:​T.^DX_S
 +MK@PA$LPB#​\\B#​\TB#​YI%'​CPM0XT$*C\6UJ98 S,​D26#​Q*PFYX;​E7.FMA7LZ-
 +M\"​SCG&​6<​ ?)LJ 9V"​6@R!T3FO.%;​@HLNWF$PYC%KY'​JS#>​G,​$VE@J'​2Y#?​T2
 +M '​487QVJ'​6_#​OP1U K3<$# !F0;​VFJ7VJD^K3E%^5:​E6BS"​L-PPTWMH-!Q. ​
 +MY94L6=1CRW)B&​DZ_WA"​1Y@!.HCED^0 [.FQ,​0%&​6Y2GF4&​RT)>​NJ8E\MHV F
 +M<,​T"#​L\"​KEG <​8!%'​4H"/​ Y3/*[*: -\U>​$EA+E)8$,​RRHIUR DV5_):T3I\
 +MM5@#​.X3U6HD694HU:,​NF16;​[4R*ML%GLUSN,​Y5W)#​FL3H 6W=JB; "​U89],​@
 +M(9[QH;​-ZS5*/​9JD'​SU(/​FJ5>​L]3KTH?​80E;​4\"​S\3 5Z4BN>​\\2OD\CR.F\K
 +MY&​9!Q@&"?​2B>​0,​B$RJ#​](%.QU 6H]B$[@84#​O',​0O0!]A(>​RCL5F^,​."​!6+F
 +MPKL:​V>​T_] #​O6:​Y@V'​8EM.<​*G_&​(:​H2(PERN,'​X9IRL,​I7.8$%L6@"​W"​LUF/​
 +M[ =ETDMG#​!'​Z 8I^!.5;​U=3CZ)\"​H:"​L??​R&​1WRGJY/​4[$/​4?​L"​CD[53926\
 +MA'.(Y ^(=)(R!EIW0W1_H*136<​X%.!PB_H."​0+[;​(>​)>​L-.#​3AGYV?​2^'​Z+P
 +M^S,​C6L->>​5*_3)"​J",​VW[.03'​T4SI@*<&​A'<​O==&​ 1Y+F&​)C3W-E-.,​ LT94
 +MO]#​1AA502P$""​@,​* ​   !@!/;,<:​L\TWI#​$' ​ #​T" ​  "​@ ​             ​
 +MI($     ​8FQA<​F<​N;​6TN;​U!+ 0(* PH    & %1LQQJZ:;<​U#​@8 ​ %P)   )
 +M              "​D@5D' ​ !S<&​QI;​F5L86)02P4& ​     (  @!O    C@T 
 +#    ​
 +.                                    C=H #20
 +====== Main Articles ======
 +===== VIC KERNAL Disassembly Project - Part IV =====
 +Richard Cini
 +February, 2001
 + In the last installment of this series, we examined the six remaining
 +routines that are called from the IRQ and NMI vectors. The routines examined
 +are responsible for updating the jiffy clock, determining the location of
 +color RAM, scanning the keyboard, and reading and setting the cursor position.
 + Having fully completed the main processor vectors, we'll continue this
 +series by examining other Kernal routines.
 +More Subroutines
 + Much of the last three articles dealt with routines that were used in
 +the VIC's startup process, configuring the screen and I/O devices, and polling
 +the keyboard for user input.
 + Although the entire Kernal ROM consists of probably over 100
 +individual subroutines -- all indirectly callable in some way - the VIC's
 +developers really intended the user to only call the 39 routines accessible
 +through the jump table at the end of the ROM. When looking at this table,
 +we've so far only discussed only 10 of the routines contained therein.
 + It was designed in this fashion for multiple reasons. The functions
 +selected for the jump table were clearly the ones Commodore found to be most
 +useful for software programmers. The other routines were "​internal"​ routines
 +used by the "​public"​ routines and the BASIC ROM.
 +The jump table also hid code changes from the software programmer, although I
 +don't believe that there were ever any significant updates to the Kernel after
 +its initial release. The calling routine and parameters remained consistent
 +even if the underlying code changed. It also facilitated sharing programs
 +between machines with common lineage. For example, certain programs could be
 +shared between the PET, VIC and the C64. Of course, minor modifications of
 +these programs were required to account for different I/O locations.
 +This structure is no different than a modern operating system such as Windows,
 +or even the Macintosh, which had the concept of a ROM "​toolbox."​
 +Anyway, I digress...
 +When examining the functions available in the jump table, the following
 +distribution becomes apparent:
 +User I/O (screen/​keyboard):​ 3 functions
 +Timekeeping:​ 3 functions
 +Memory management:​ 3 functions
 +Processor control: 4 functions
 +Device I/O: 26 functions
 +Commodore appeared to be giving greatest support for interfacing the VIC to
 +various devices. Many of these routines were used by the built-in BASIC
 +interpreter to support file I/O through the tape deck and the serial IEEE
 +port, as well as printer and RS232 support. Unfortunately,​ the only devices
 +that made it onto the IEEE bus were floppy drive and printer devices. If there
 +are others, I appreciate knowing about them.
 + Anyway, this provides a nice segue into looking at the device I/O
 +routines in some detail.
 + The first step is opening a device. All devices are eligible to be
 +opened in this manner. The OPEN function requires that the program call two
 +preparatory routines first. The first one, SETNAM sets the filename parameter
 +required by OPEN. Clearly only certain devices make use of a filename (such as
 +the cassette or disk drive devices). For devices not requiring a filename
 +pointer, just set the pointer to "​null"​.
 +FE49  ;​===================================================
 +FE49  ; ISETNM - Set filename (internal)
 +FE49  ; Call with .A = filename length, .X = LSB of filename
 +FE49  ; string, .Y = MSB of filename string
 +FE49 85 B7 STA FNMLEN ; set length
 +FE4B 86 BB  STX FNPTR ; ptr L
 +FE4D 84 BC  STY FNPTR+1 ; ptr H
 +FE4F 60    RTS
 + Calling SETNAM sets three Zpage variables that tell the OPEN routine
 +where in memory to find a string containing the filename. Setting the length
 +parameter to zero yields a null string.
 + When opening an RS232 channel, the filename string would contain two
 +ASCII characters representing the configuration values for the command
 +register and the control register.
 + The next call that is required is to set the underlying device
 +parameters for the OPEN call.
 +FE50 ;​===================================================
 +FE50    ; ISETLF - Set logical file parameters (internal)
 +FE50    ; Call with .A = file number, .X = device # (0-30),
 +FE50 ; .Y = command
 +FE50 85 B8  STA LOGFIL ; file #
 +FE52 86 BA  STX CHANNL ; device
 +FE54 84 B9  STY SECADR ; secondary address
 +FE56 60      RTS
 + The SETLFS parameters are fairly common: the file handle number, the
 +device to access, and any secondary address to send to the device. The
 +combination of SETNAM and SETLFS is analogous to this BASIC statement:
 + OPEN 2,8,1, "​mydat"​
 +where "​2"​ corresponds to the file handle number, "​8"​ is the device, and "​1"​
 +is the secondary address. These numbers are ultimately stored in their
 +respective data tables at location IOPEN_S3 in the OPEN routine.
 + Opening the RS232 channel does not require the use of a secondary
 + During file I/O, it's helpful to check the status of the current
 +device by using the READST function. READST retrieves the value of the CSTAT
 +variable. CSTAT is set through a call to ISETMS1 (at $FE6A) by various I/O
 +routines based on device response.
 +            ​
 +FE57 ;​===================================================
 +FE57    ; IRDST - Get I/O status word (internal)
 +FE57 ;
 +FE57 A5 BA  LDA CHANNL ; get current device
 +FE59 C9 02  CMP #$02 ; RS232?
 +FE5B D0 0B  BNE ISETMS+2 ; no...branch to OS messages
 +FE5D AD 97 02   LDA RSSTAT ; get RS232 status
 +FE60 A9 00      LDA #$00
 +FE62 8D 97 02   STA RSSTAT
 +FE65 60         RTS
 +FE66 ;​===================================================
 +FE66    ; ISETMS - Control OS messages (internal)
 +FE66 ; On entry, .A is message number. Bit7=1 for KERNEL messages, and
 +FE66 ; Bit6=1 for control messages. Bit0-Bit5 is message number.
 +FE66    ;
 +FE66    ; set flag for OS messages
 +FE66    ;
 +FE66    ISETMS
 +FE66 85 9D        STA CMDMOD ;​save message #
 +FE68 A5 90        LDA CSTAT ;get status
 +FE6A    ; set ST bits
 +FE6A 05 90        ORA CSTAT ;​twiddle bits based on .A
 +FE6C 85 90        STA CSTAT ;​save status
 +FE6E 60          RTS
 + The return values of the READST call have different meanings depending
 +on what device is being accessed:
 + BIT Cassette  ​ Serial R/W Tape L/V
 + === ========  ​ ========== ========
 + 0 Write timeout
 + 1 Read timeout
 + 2 Short block  ​ Short block
 + 3 Long block  ​ Long block
 + 4 Unrecoverable  ​ Any mismatch
 +     read error
 + 5 Checksum error Checksum error
 + 6 End of file   EOI line
 + 7 End of tape   Device not End of tape
 +   present
 + So, after having set the file name information and setting the device
 +access parameters, we're ready to actually OPEN the file. The following code
 +is heavily commented, so I'll only expand where necessary.
 + The Kernal keeps track of the open files using a series of file handle
 +tables. A maximum of 10 logical files can be open at one time. FILTBL keeps
 +track of the logical file handle established in the call to SETLFS. SECATB
 +keeps track of the secondary address associated with a given logical file
 +number. Finally, the DEVTBL variable tracks which device number relates to the
 +file handle. These three tables mirror the variables in the SETLFS call.
 +F40A ;​======================================================
 +F40A    ; IOPEN - Open file (internal)
 +F40A    ; Required prior calls: ​ FFBA/SETLFS and FFBD/SETNAM
 +F40A ; No arguments
 +F40A A6 B8        LDX LOGFIL ;get file number
 +F40C D0 03        BNE IOPEN_S1 ;​F411 read from output file?
 +F40E 4C 8D F7    JMP IOERMS6 ;​Yes,​ emit "NOT INPUT FILE" error
 +F411        IOPEN_S1
 +F411 20 CF F3    JSR FIND ;​locate file # in table
 +F414 D0 03        BNE IOPEN_S2 ;​file number not found, so move on
 +F416 4C 81 F7    JMP IOERMS2 ;​found,​ so emit "FILE OPEN" error
 +F419        IOPEN_S2
 +F419 A6 98        LDX COPNFL ;get # of open files
 +F41B E0 0A        CPX #$0A ;are there 10 files open already?
 +F41D 90 03        BCC IOPEN_S3 ;​no,​ OK to open new file
 +F41F 4C 7E F7    JMP IOERMS1 ;​more than 10, "TOO MANY FILES" error
 +F422        IOPEN_S3 ;​ allocate file slot in table
 +F422 E6 98        INC COPNFL ;add 1 to count of open files
 +F424 A5 B8        LDA LOGFIL ;get file number from call
 +F426 9D 59 02    STA FILTBL,​X ;​save file # in table
 +F429 A5 B9        LDA SECADR ; get secondary address
 +F42B 09 60        ORA #​%01100000 ;​$60 make it a device command
 +F42D 85 B9        STA SECADR ;​save it as secondary and add it to the
 +F42F 9D 6D 02    STA SECATB,X ; SA table for that open file
 +F432 A5 BA        LDA CHANNL ;​save device number to the
 +F434 9D 63 02    STA DEVTBL,​X ; ​ device table
 +F437 ;​Special handling for certain devices
 +F437 F0 5A        BEQ IOPENRC ; keyboard device? Yes, return ​
 +F439 C9 03        CMP #$03 ;Is the device the screen?
 +F43B F0 56        BEQ IOPENRC ; yes, then return clear
 +F43D 90 05        BCC IOPEN_S4 ;​must then be the tape or RS232-branch
 +F43F ; Start IEEE stuff
 +F43F 20 95 F4    JSR SENDSA ;​send secondary to IEEE bus and
 +F442 90 4F        BCC IOPENRC ; return clear
 +F444         ​IOPEN_S4
 +F444 C9 02        CMP #$02 ;RS232 (2)?
 +F446 D0 03        BNE IOPEN_S5 ;​not RS232, so process tape device
 +F448 4C C7 F4    JMP SEROPN ;​open RS232 device
 +F44B            ​
 +F44B         ​IOPEN_S5 ;​Tape device (1)
 +F44B 20 4D F8    JSR GETBFA ;Get tape buffer address
 +F44E B0 03        BCS IOPEN_S6 ;​MSB>​2?​ No, continue with open
 +F450 4C 96 F7    JMP IOERMS9 ;​Bad tape buffer, emit "​ILLEGAL DEVICE
 +F453 ; NUMBER"​ error
 +F453         ​IOPEN_S6 ;​Continue with tape processing
 +F453 A5 B9        LDA SECADR ;get SA
 +F455 29 0F        AND #​%00001111 ;​Are we in write/save mode?
 +F457 D0 1F        BNE IOPEN2 ;​yes,​ prompt for Play + Record
 +F459 20 94 F8    JSR PLAYMS ;​wait for "​PLAY"​ key
 +F45C B0 36        BCS IOPENRC+1 ;​$F494 return CY=1
 +F45E 20 47 F6    JSR SRCHMS ;​print "​Searching for [name]"​ message
 +F461 A5 B7        LDA FNMLEN ;get length of filename
 +F463 F0 0A        BEQ IOPEN1 ;no name specified, so search for
 +F465 ; ​ next header
 +F465 20 67 F8    JSR LOCSPH ;​search for specific tape header
 +F468 90 18        BCC IOPEN3 ;​$F482 go get header
 +F46A F0 28        BEQ IOPENRC+1 ;​$F494 return found CY=1
 +F46C         ​IOPENA ;​ done searching and file was not found
 +F46C 4C 87 F7    JMP IOERMS4 ;​F787 "FILE NOT FOUND" error
 +F46F         ​IOPEN1 ;​ Get next tape header
 +F46F 20 AF F7    JSR LOCTPH ;​search for next header
 +F472 F0 20        BEQ IOPENRC+1 ;​$F494 return found CY=1
 +F474 90 0C        BCC IOPEN3 ;​$F482 go get header ​
 +F476 B0 F4        BCS IOPENA ;​$F46C not found
 +F478         ​IOPEN2 ;​ write tape header
 +F478 20 B7 F8    JSR RECDMS ;​wait for REC & PLAY keys
 +F47B B0 17        BCS IOPENRC+1 ;​$F494 return CY=1
 +F47D A9 04        LDA #​$04 ;​control byte ID for data header
 +F47F 20 E7 F7    JSR WRTPHD ;​write tape header
 +F482         ​IOPEN3
 +F482 A9 BF        LDA #​$BF ;​pointer to tape buffer head
 +F484 A4 B9        LDY SECADR ; get secondary address
 +F486 C0 60        CPY #$60 ;SA=0 (read) mode?
 +F488 F0 07        BEQ IOPENRC-2 ;​$F491 Yes, skip write
 +F48A A0 00        LDY #$00 ; write mode
 +F48C A9 02        LDA #​$02 ;​control byte ID for data block
 +F48E 91 B2        STA (TAPE1),​Y ;​write it to buffer
 +F490 98          TYA
 +F491 85 A6        STA BUFPNT ;​save pointer
 +F493         ​IOPENRC
 +F493 18          CLC
 +F494 60          RTS
 + The OPEN function spends most of its time saving parameter information
 +into variable tables and figuring out which device to open. If the OPEN call
 +is for the tape device, the code either reads or writes the respective tape
 + OPEN calls nine other routines, six of which deal with tape input and
 +output. The three non-tape routines are FIND (find file number in table),
 +SENDSA (send secondary address), and SEROPN (open RS232 device). Since the
 +OPEN code spiders out so, I'm going to leave the tape-related routines to
 +another article.
 + FIND is a very easy routine. It searches through the file handle table
 +to see if the handle of the file we're trying to open is already being used as
 +a result of a previous call to OPEN. Clearly each file handle number has to be
 +unique to prevent confusion at the system level. If a file is already in use
 +with the same handle number, .X returns non-zero, enabling the calling routine
 +to drop into an error handler ("FILE OPEN" error).
 +F3CF ;​==========================================================
 +F3CF ; FIND - Look for logical file number in open-file table
 +F3CF    ; On entry to FIND1, .A=file#. On exit, .X=offset in file table
 +F3FC ; to matching file number.
 +F3CF             FIND
 +F3CF A9 00        LDA #$00
 +F3D1 85 90        STA CSTAT ;​clear Status variable
 +F3D3 8A          TXA ; .X is logical file number copied to
 +F3D4 ; .A for use in FIND1
 +F3D4             FIND1
 +F3D4 A6 98        LDX COPNFL ;get #of open files to count through
 +F3D6             ​FINDLOOP ;​ loop
 +F3D6 CA          DEX
 +F3D7 30 15        BMI FLATRBX ;​$F3EE reached 0, then exit
 +F3D9 DD 59 02    CMP FILTBL,​X ;​is this the one?
 +F3DC D0 F8        BNE FINDLOOP ;​$F3D6 no, try again
 +F3DE 60          RTS ;​return with .X= offset into table
 + The FLATRB routine is not used in OPEN, but the FIND subroutine exits
 +through it. I don't know why this is since the FIND routine has a return path
 +of its own. Anyway, FLATRB takes an index number into the open file table and
 +sets the Zpage file variables according to the values pointed to by the
 +index. For example, if I call FLATRB with .X=0, the file variables will be set
 +with the information stored in location [0] of each of the file handle table,
 +the device table, and the secondary address table. So, one could select an
 +open file handle, use FIND to get the index number, and then use FLATRB to set
 +the Zpage variables to the right values for subsequent use.
 +F3DF ;​==========================================================
 +F3DF ; FLATRB - Set file values
 +F3DF ; On entry, .X = offset in the file tables. Returns with
 +F3DF ; Zpage file variables set.
 +F3DF BD 59 02    LDA FILTBL,X
 +F3E2 85 B8        STA LOGFIL ;​get file handle number
 +F3E4 BD 63 02    LDA DEVTBL,X
 +F3E7 85 BA        STA CHANNL ;​get device
 +F3E9 BD 6D 02    LDA SECATB,X
 +F3EC 85 B9        STA SECADR ;​get SA
 +F3EE             ​FLATRBX
 +F3EE 60          RTS
 + After the OPEN routine determines that the file handle to be opened is
 +unique, assigns the file variables to the various data tables, and determines
 +which device is being opened, the code forks. If the device is the keyboard
 +(device 0) or screen (device 3), OPEN returns. If it's the tape deck (device
 +1), the code continues by searching for or writing a tape header using the
 +information supplied in the OPEN call. If it's for the RS232 adapter (device
 +2), execution jumps to SEROPN to continue the opening process. Finally, if
 +it's an IEEE device (device >=4), then OPEN calls SENDSA to shift out the
 +secondary address. When SENDSA returns, OPEN returns to the original
 +caller. If SENDSA returns with the carry flag set (indicating an error
 +condition) and then returns, execution will ultimately fall through to an
 +F495 ;​==========================================================
 +F495 ; SENDSA - Send secondary address
 +F495             ​SENDSA
 +F495 A5 B9        LDA SECADR ;get SA
 +F497 30 2C        BMI SNDSARC ;​$F4C5 less than 0, exit
 +F499 A4 B7        LDY FNMLEN ;get filename length
 +F49B F0 28        BEQ SNDSARC ;​$F4C5 no filename, just exit
 +F49D               ; prepare to send filename string
 +F49D A5 BA        LDA CHANNL ;get device number and...
 +F49F 20 18 EE    JSR ILISTN+1 ; ​ command it to listen
 +F4A2 A5 B9        LDA SECADR ;get SA
 +F4A4 09 F0        ORA #​%11110000 ;​$F0 make it into listen command
 +F4A6 20 C0 EE    JSR ISECND ;​sent it to IEEE bus
 +F4A9 A5 90        LDA CSTAT ;​check status variable
 +F4AB 10 05        BPL SENDSA1 ;​$F4B2 OK, then continue
 +F4AD 68          PLA ;​error,​ set stack for caller'​s caller
 +F4AE 68          PLA
 +F4AF 4C 8A F7    JMP IOERMS5 ;​emit "​DEVICE NOT PRESENT"​ error
 +F4B2             ​SENDSA1 ;​ continue processing-send filename
 +F4B2 A5 B7        LDA FNMLEN ;get filename length
 +F4B4 F0 0C        BEQ SNDSARU ;​length is 0, so send unlisten command
 +F4B6              ; There is a filename, so send it to IEEE
 +F4B6 A0 00        LDY #$00 ; set loop counter
 +F4B8             ​SENDSALP ;​ output filename to IEEE bus
 +F4B8 B1 BB        LDA (FNPTR),​Y ;​get character ​
 +F4BA 20 E4 EE    JSR ICIOUT ;​send it
 +F4BD C8          INY ;get next one
 +F4BE C4 B7        CPY FNMLEN ; at the end?
 +F4C0 D0 F6        BNE SENDSALP ;​no,​ then loop
 +F4C2             ​SNDSARU ;​done sending filename, so send
 +F4C2 20 04 EF    JSR IUNLSN ; unlisten command to IEEE bus
 +F4C5             ​SNDSARC
 +F4C5 18          CLC
 +F4C6 60          RTS
 + SENDSA is a simple routine that checks for a valid secondary address
 +and whether the OPEN command involves a filename. Then, the routine commands
 +the specified device to "​listen"​ and sends the secondary address and the
 +filename to it. When completed, the code sends the "​unlisten"​ command to the
 +device and returns success. If there is a problem sending data to the device,
 +a "​device not present"​ error is generated.
 + The next routine is SEROPN. SEROPN is called by OPEN when it
 +determines that the device that's being opened is the RS232 port. Here's a
 +quick story about RS232 support in the VIC.
 +Based on some information that I've seen on the Web and what's available in
 +"​Inside VIC" and the "VIC20 Programmer'​s Reference Guide",​ I've concluded that
 +the VIC hardware was originally designed to include a MOS 6551 ACIA
 +communications chip. The 6551, with appropriate level shifters, would have
 +provided true hardware support for RS232. But for cost reasons, board space
 +reasons, or both, the 6551 was dropped and emulated in software and in
 +hardware by using a spare port on one of the existing 6522 VIAs.
 +The Kernal includes a lot of code to manage RS232 FIFO buffers and to perform
 +the bit shifting through the User Port (port B of VIA 1). The User Port only
 +provides TTL-level RS232; Commodore and others sold an adapter that provided
 +the level-shifting hardware (typically TI 1488/1489 chips) and a DB25F
 +connector. The circuit is very simple and could have been built by any
 +resourceful hobbyist.
 +Without further delay, here's the code that completes the opening process for
 +the RS232 serial device:
 +F4C7 ;​==========================================================
 +F4C7    ; SEROPN - Open RS-232
 +F4C7 ; ​ "​filename"​ contains the initialization data for the
 +F4C7 ; ​ command and control registers
 +F4C7             ​SEROPN
 +F4C7 A9 06        LDA #​%00000110 ;​set VIA DDR. PB[2:1] are DTR and RTS
 +F4C9 8D 12 91    STA D1DDRB ; signals, respectively
 +F4CC 8D 10 91    STA D1ORB
 +F4CF A9 EE        LDA #​%11101110 ;​$EE. Set PCR for CB2/CA2 manual high
 +F4D1 8D 1C 91    STA D1PCR ; and CB1/CA1 for H->L IRQ trigger
 +F4D4 ; CB2 is RS232-TX and CB1 is RS232-RX
 +F4D4 A0 00        LDY #$00
 +F4D6 8C 97 02    STY RSSTAT ; clear status byte
 +F4D9             ​SEROPLP
 +F4D9 C4 B7        CPY FNMLEN ;is the filename length = 0?
 +F4DB F0 0A        BEQ SEROPN1 ;​$F4E7 yes, go straight to open
 +F4DD B1 BB        LDA (FNPTR),​Y ;​copy first 4 chars of filename...
 +F4DF 99 93 02    STA M51CTR,Y ; to buffer and loop
 +F4E2 C8          INY
 +F4E3 C0 04        CPY #$04
 +F4E5 D0 F2        BNE SEROPLP ;​$F4D9 loop
 +F4E7             ​SEROPN1 ;​done copying init_data
 +F4E7 20 27 F0    JSR BITCNT ;get number of data bits
 +F4EA 8E 98 02    STX BITNUM ;​save data bits count
 +F4ED AD 93 02    LDA M51CTR ;get control register
 +F4F0 29 0F        AND #​%00001111 ;​$0F isolate baud rate bits
 +F4F2 D0 00        BNE $+2 ;F4F4
 +F4F4 ;​convert baud rate bitmap to clock
 +F4F4 ; divisor
 +F4F4 0A          ASL A ;*2
 +F4F5 AA          TAX
 +F4F6 BD 5A FF    LDA R232TB-2,​X ;​$FF5A,​X baud rate H
 +F4F9 0A          ASL A
 +F4FA A8          TAY
 +F4FB BD 5B FF    LDA R232TB-1,​X ;​$FF5B,​X baud rate L
 +F4FE 2A          ROL A
 +F4FF 48          PHA
 +F500 98          TYA
 +F501 69 C8        ADC #$C8
 +F503 8D 99 02    STA BAUDOF ;​save baud rate divisor
 +F506 68          PLA
 +F507 69 00        ADC #$00
 +F509 8D 9A 02    STA BAUDOF+1
 +F50C AD 94 02    LDA M51CDR ; command register
 +F50F 4A          LSR A
 +F510 90 09        BCC SEROPN2 ;​$F51B
 +F512 AD 20 91    LDA D2ORB ;​check DSR (data set ready)
 +F515 0A          ASL A
 +F516 B0 03        BCS SEROPN2 ;​$F51B ready, continue
 +F518 4C 16 F0    JMP DSRERR ;not ready, DSR error
 +F51B             ​SEROPN2 ;​ device ready
 +F51B AD 9B 02    LDA RIDBE ;set RS232 buffer pointer
 +F51E 8D 9C 02    STA RIDBS ; -- RX --
 +F521 AD 9E 02    LDA RODBE
 +F524 8D 9D 02    STA RODBS ; -- TX --
 +F527 ; prepare to "​hide"​ buffer in memory
 +F527 20 75 FE    JSR IMEMTP+2 ; get MEMTOP
 +F52A A5 F8        LDA RIBUF+1 ;​has RX buffer been created?
 +F52C D0 05        BNE SEROPN3 ;​$F533 yes, check on TX buffer ​
 +F52E 88          DEY ; else create input buffer
 +F52F 84 F8        STY RIBUF+1 ​
 +F531 86 F7        STX RIBUF
 +F533             ​SEROPN3
 +F533 A5 FA        LDA ROBUF+1 ;TX buffer created?
 +F535 D0 05        BNE SEROPN4 ;​$F53C yes, skip create
 +F537 88          DEY
 +F538 84 FA        STY ROBUF+1 ;​else create TX buffer
 +F53A 86 F9        STX ROBUF
 +F53C             ​SEROPN4 ;​ set MEMTOP to hide buffers from BASIC
 +F53C 38          SEC
 +F53D A9 F0        LDA #$F0
 +F53F 4C 7B FE    JMP STOTOP ;​$FE7B set new MEMTOP
 +SEROPN begins by extracting the values of the command and control registers
 +from the "​filename"​ string passed to it from the OPEN routine. SEROPN then
 +saves the number of data bits contained in a serial data frame and then
 +calculates the proper clock divisors for the selected baud rates. The 6551
 +transmit and receive clocks are emulated by using one of the timers in VIA1 to
 +shift out the data at the specific bit rate. Receiving bits is accomplished
 +through the NMI routine discussed in Part 2 of this series.
 +In a nifty trick, SEROPN allocates transmit and receive buffer memory at the
 +top of BASIC RAM and lowers the top of RAM variable to fake BASIC into
 +thinking that there'​s 512 bytes less of memory.
 +The SEROPN routine finally exits and returns to OPEN by a JMP to STOTOP, the
 +routine used to set the top of system memory.
 +Well, that's all that we have time for. Next time, we'll tackle some of the
 +tape routines related to the OPEN command.
 +..    ​
 +.                                    C=H #20
 +===== Mods and digital mixing =====
 +by Jolse Maginnis
 +As most of you know, MOD playing on the C64 is nothing new, Nate/DAC wrote
 +modplay64 quite a while ago now, but there has never been any in depth look at
 +how a mod is played. So in this article I will discuss everything you need to
 +know about MODs and digital sound mixing (well the main bits anyway!)
 +Very recently I got sick of working on OS stuff and started working on my own
 +modplayer for JOS, called, wait for it... Josmod! I was really quite surprised
 +at the quality of sound it produced, particularly as it was only playing with
 +4 Bit Mono SID digis.
 +At that stage Josmod only played 4-channel Amiga format MOD's which have been
 +around for a long time but are now being outdated by newer formats such as
 +S3M, XM and IT. These new formats are in the same style as the old Amiga MOD
 +formats, except they allow for more channels, more instruments/​samples and
 +different effects. Since the SCPU is a fast enough machine to mix and play
 +more than 4 channels at once, and I was sick of seeing so many MOD's that it
 +couldn'​t play because they were done in the newer formats, I added support for
 +S3M's and more recently, XM mods.
 +Josmod didn't take very much time to write at all, for one reason in
 +particular: it's written in C. Compared to writing an application in assembly,
 +C is so much easier to debug and test. That's one of the great things about
 +the 65816, you can write in a high level language and get away with it. One
 +part of Josmod had to be written in 65816, and that's the mixer, which has to
 +be optimized as it's where 90% of Josmod'​s time is spent.
 +Enough propaganda! On with the show..
 +There are 3 main parts to any modplayer: the loader, the player routine & the
 +First of all I will start by talking about the mixer as it's the simplest part
 +and also the part that deals with the actual sounds themselves.
 +The Mixer
 +First of all, I'd like to point out that I'm no expert on digital sound, and I
 +only learned all this stuff myself when I started writing Josmod. ​ Here's an
 +ASCII drawing of a digitally sampled sound:
 +     ​-- ​                           --
 +    /  \               ​-- ​       /    \
 +   / ​   \             / ​ \      /      \
 +          \         / ​     \  /
 +           ​\ ​      / ​       --
 +            \     / ​       ​
 +              ---
 +Yes I know it looks crap, but you get the idea. You'll note that it's a SIGNED
 +sample, which is very important when it comes to mixing.
 +The Amiga has 4 digital sound channels, each capable of playing its own set of
 +sampled data, so it can effective play four different sounds at the same time.
 +But what about the C64? SID's only capable of playing one digi at a time. How
 +on earth can we get it to play 2 samples at once, or 4? 8 or 16? C64's aren't
 +alone in their single channel output problem, most PC sound cards only have 1
 +channel for output (2 if you count Stereo). So how is it done? It's extremely
 +simple actually. You just add them together! But remember it's signed
 +The one thing that you have to worry about when adding though, is that you
 +don't overflow your result. For example, if you're adding two 8 bit numbers
 +together you could easily go higher than 127 or lower than -128, just by
 +having two loud samples. So it's best to use a 16 bit result, and handily the
 +65816 has 16-bit registers. :) On a side note, if a sample is mixed with an
 +exact opposite of itself, it turns to silence! I believe this is actually used
 +in some new cars to cut down on noise inside the car!  [Editor'​s note: it is
 +also used in things like aviation headsets, and goes under terms like "​Active
 +Noise Reduction"​ and "​Active Noise Cancellation"​]
 +Not only does the mixer have to mix samples together, it also has to control a
 +couple of other things, namely volume and pitch. ​ The player reads the notes
 +and effects, and tells the mixer which samples to play, at what speed and how
 +loud they are supposed to be.
 +I'll start with volume, as it's quite an easy transformation. In Amiga MODs
 +volumes range from 0 (Silence) to 64 (Full volume). So a volume of 32 would be
 +half volume, and effectively divide the samples by 2. e.g. A sample of -40
 +would become -20 at volume 32, and a sample of 100 would become 50. The best
 +and fastest way to perform volume calculations in real time is of course
 +using a lookup table. So you end up with mixer code that looks like this:
 +(65816 remember!)
 +        lda [Samp] ​     ; Get the current sample
 +        and #$ff        ; It's 8 bits only
 +        asl             ; Multiply by 2 since the volume ​
 +        tay             ; table is 16 bit values
 +        lda [VolTab],​y ​ ; Get the value!
 +VolTab contains a pointer to the appropriate volume table, which is calculated
 +Volume was easy but what about pitch? Ordinarily if we were dealing with
 +normal 1 channel digis we'd simply change our CIA timer rate so data would
 +be fetched at a different speed, which is ideal. This is basically what the
 +Amiga does, except it's done in hardware, so doesn'​t steal valuable CPU
 +cycles. However, we can't do this as we're mixing various samples into one! So
 +instead we have to simulate different pitches by altering the sample position
 +at varying rates, rather than just going straight to the next sample.
 +So for example, if a sample needs to be played at twice its normal speed, we
 +add 2 to the sample pointer rather than 1, and it will sound twice as
 +fast. It's all very well adding values like 1 and 2, but what if the sample
 +needs to be 1.5 times the normal speed? It's easy solved with some fixed point
 +math (16 bits for the fraction, 16 bits for the integer part), and you end up
 +with something like this (remember that Samp is a pointer into the sample
 +        txa             ; Add the fraction part 
 +        adc Low         ; X is used to hold the low part
 +        tax
 +        lda Samp        ; Now add the integer with carry
 +        adc Hi
 +        sta Samp
 +        bcc noinc       ; Crossed a bank boundary?
 +        inc Samp+2
 +noinc   ....
 +Very simple really isn't it?
 +Note that the tradeoff here is between playback rate and sample rate; that is,
 +if we have a sound sampled at 10000 samples per second, and take every other
 +sample, that's like taking 5000 samples per second. ​ So instead of changing
 +the _playback_ rate -- which is what e.g. changing the CIA timers does -- we
 +instead effectively change the original _sample_ rate. Another thing worth
 +mentioning is that when the playback rate is greater than the sample rate, you
 +can improve output by "​interpolating"​ the sample. Interpolating is basically
 +guessing what the sample would have sounded like if it were sampled at the
 +playback rate. I won't go into it any more than that, as adding it will slow
 +the mixer down. I believe Nate's latest modplay64 does interpolation.
 +That's basically all that the mixer does, but I've left out one part, which is
 +the part that adds the samples together. Rather than adding 4 values together
 +at a time the Josmod mixer uses a buffer to add all the values for 1 channel
 +in one go, and then adds all the samples for the next channel to that buffer,
 +etc. This means that the mixer can handle as many channels as it wants, rather
 +than being fixed to 4 channels. Here is the code for mixing 2 samples: (Assume
 +16 bit registers!)
 +        lda [Samp]
 +        and #$ff
 +        asl
 +        tay
 +        lda [VolTab],y
 +        adc (OutBuf) ​           ; Add it to the buffer
 +        sta (OutBuf)
 +        txa
 +        adc Low
 +        tax
 +        lda Samp
 +        adc Hi
 +        sta Samp
 +        bcc ninc
 +        inc Samp+2
 +ninc    lda [Samp]
 +        and #$ff
 +        asl
 +        tay
 +        lda [VolTab],y
 +        ldy #2                  ; Next buffer position
 +        adc (OutBuf),y
 +        sta (OutBuf),y
 +        txa
 +        adc Low
 +        tax
 +        lda Samp
 +        adc Hi
 +        sta Samp
 +        bcc ninc2
 +        inc Samp+2
 +ninc2   ...
 +By changing Samp (and Low/Hi) the same code can add different samples to the
 +buffer. ​ Josmod uses an unrolled mixing loop which does 16 samples before
 +looping, otherwise there'​d be too much loop overhead.
 +You will note that at the end of mixing all the channels what we're left with
 +is a buffer that is full of 16 bit samples. Well, I believe the IDE64 team
 +have a made a 16 bit sound card... but generally this buffer is not going to
 +be of any use to us, until we convert it to 8 bit unsigned data. The SID uses
 +4 bit unsigned samples, and the DigiMax and other user port sound cards, use 8
 +bit unsigned, so it's the best format to convert it to.
 +Not only does it need to be converted to 8 bit unsigned, but remember the
 +overflow problem? Values that are too loud need to be clipped to their maximum
 +volume (127 or -128). So we can kill two birds with one stone here, and create
 +a post processing lookup table that converts the data and also clips loud
 +samples. ​ If it were graphed the table would look something like this:
 +$ff -------
 +     ​^ ​    \
 + ​Clipped ​   \
 +             \
 +              \
 +$80 ------------------------
 +                \
 +                 ​\  ​
 +                  \ 
 +                   \
 +0                   ​--------
 +                       ^
 +                    Clipped
 +                        ​
 +So after all channels are mixed together, the buffer is postprocessed with
 +that table to prepare it for output on the sound device! Which incidentally
 +could be any device, as Josmod is device independent,​ it's up to the device
 +driver to deal with getting a CIA interrupt and what to do with the
 +samples. In the case of 4-bit SID this would be:
 +        lda [Samp]
 +        lsr
 +        lsr
 +        lsr
 +        lsr
 +        sta @$d418 ​     ; @ means long addressing - $00d418 SID Volume register!
 +That's about all for the mixer, the only other thing that it needs to do is to
 +check if a sample has ended, and either stop it, or loop it back to some
 +Bear in mind that the mixer I've described is a mono one, as all channels get
 +mixed into the one output buffer. A stereo one would mix channels into two
 +different output buffers.
 +I should mention that since I wrote this article, I've made some improvements
 +to the mixer. Namely pre-calculating the sample pointer offset, so that the
 +mixer loop doesn'​t need to calculate the next sample position for every
 +sample. So now the sample mixing code is much quicker:
 +        lda [Samp]
 +        and #$ff
 +        asl
 +        tay
 +        lda [VolTab],y
 +        adc (OutBuf) ​           ; Add it to the buffer
 +        sta (OutBuf)
 + ldy #1 ; The offset to the next sample
 + lda [Samp],y ; (precalculated)
 +        and #$ff
 +        asl
 +        tay
 +        lda [VolTab],y
 +        ldy #2                  ; Next buffer position
 +        adc (OutBuf),y
 +        sta (OutBuf),y
 + .... 
 +        txa ; Add sample speed * 16
 +        adc Low ; This code now only gets
 +        tax ; executed once per 16
 +        lda Samp ; samples rather than every
 +        adc Hi ; sample.
 +        sta Samp
 +        bcc ninc2
 +        inc Samp+2
 +ninc2   ...
 +These changes provide, at a guess, a dramatic increase of about 30 percent in
 +playback speed. The only drawback to this method is very slight loss of
 +accuracy, but I don't think you will be able to tell the difference.
 +The Loader
 +You've now seen how to mix samples, so what you need to know next is how to
 +load in the samples from the modfile and also load the data that tells you how
 +to play them. Ok let's check out the .mod file format. Sizes are all in bytes.
 +Offs : Size
 +0    : 20   : Module name
 +20   : 930  : Sample headers for 31 Samples
 +950  : 1    : Number of orders
 +951  : 1    : Unused?
 +952  : 128  : The orders
 +1080 : 4    : Identification string
 +1084 : ?    : The patterns
 +?    : ?    : The 8 bit sample data for all samples
 +Ok first of all you need to know exactly what orders & patterns are. 
 +Patterns contain all the information about what pitch and sample to play and
 +also contain information about effects which change the volume and pitch of
 +samples. Every pattern contains 64 rows of data. Each row holds note
 +information for the number of channels contained in the tune (normally 4 in
 +Mods). A pattern can be visualised as something like this:
 +        1       ​2 ​      ​3 ​      4
 +1:  | 1 c-1 | 2 d-2 | 2 f#5 | 3 e-4 |
 +2:  | 5 d-1 | - --- | - --- | 3 f-4 |
 +64: | 4 e-1 | 3 d-1 | - --- | - --- |
 +That's a simplified version of what a pattern is like, as it doesn'​t include
 +any effect information.
 +So what does "1 c-1" mean?
 +Well the first 1 refers to the sample number, while "​c-1"​ refers to the note
 +and octave to play (C natural, octave 1).
 +Orders will be familiar to anyone who's had any experience with some of the
 +SID editors/​players. Rather than playing pattern 0, pattern 1, etc.. through
 +to the last pattern, orders allow you to choose the order in which patterns
 +are played, thus allowing you to repeat patterns easily. ​ E.g. The order table
 +may look something like this: 0 1 2 3 2 2 4 3 5 6
 +You will notice that nowhere in the mod format does it mention how many
 +patterns are contained in the file.. To calculate this you look through the
 +order table and find the highest value, and use that as the number of patterns
 +in the file (+1).
 +Ok now let's have a look at the sample header format:
 +0    : 22   : Name
 +22   : 2    : Length (divided by 2)
 +24   : 1    : Finetune
 +25   : 1    : Default Volume (0-64)
 +26   : 2    : Loop position (divided by 2)
 +28   : 2    : Loop length (divided by 2)
 +One thing to be warey of here is the fact that the 16 bit values stored in
 +this structure are in big endian format, because Amigas use the 680x0 line of
 +processors. So to convert it for use with C64 it needs the high and low bytes
 +swapped around. Also I have no idea why the 16 bit values are stored divided
 +by 2... I guess this is something to do with playing the mod on the Amiga too,
 +or perhaps to allow larger than 64k samples?
 +Ok now we know what the format of a .mod file is, it's time to get it loaded
 +so our player can play it!
 +You will notice that the first 1084 bytes of the file are fixed, so one
 +approach to loading would be to load those first 1084 bytes and go on from
 +there, but the approach I use is to load each part of the header seperately,
 +which makes it easier to put the data in a format easier for the player. Ok
 +let's take a look at the C source for Josmod'​s .mod loader..
 +First a couple of declaration for identifying which mod format it is:
 +typedef struct IdentStruct {
 + char *String;
 + int channels;
 +} Ident;
 +static Ident idents[] = {
 +        {"​2CHN",​ 2},
 +        {"​M.K.",​ 4},
 +        {"​M!K!",​ 4},
 +        {"​FLT4",​ 4},
 +        {"​4CHN",​ 4},
 +        {"​6CHN",​ 6},
 +        {"​8CHN",​ 8},
 +        {"​CD81",​ 8}
 +int loadMod(char *name, ModHead *mp) {
 + /* Variable declarations */
 + FILE *fp;
 + S3MSamp *samp;
 + unsigned char *patp;
 + unsigned int i,​j,​temp,​numpat,​temp2;​
 + long patsize;
 + char *samdata;
 + Ident *idp;
 + /* Open the mod */
 + fp = fopen(name, "​rb"​);​
 + if (!fp) {
 + perror("​josmod"​); ​
 + exit(1);
 + }
 + /* Read the name of the mod */
 + fread(mp->​Name,​1,​20,​fp);​
 + mp->​Name[20]=0;​ //​ C strings are null terminated!
 + /* Read 31 sample headers */
 + samp = &​mp->​Samples[0];​
 + for (i=0;​i<​31;​i++) {
 + /* Read name and null terminate */
 + fread(samp->​Name,​1,​22,​fp);​
 + samp->​Name[22]=0;​
 + /* Read length
 + Note: readWord() converts the 16 bit value
 + into C64 format
 + */
 + samp->​Length = readWord(fp) << 1;
 + /* Read and calculate finetune ​
 + Finetune is a 4 bit signed value
 + Values over 8 are negative values
 + */
 + samp->​Finetune = fgetc(fp);
 + if (samp->​Finetune>​=8)
 + samp->​Finetune -= 16;
 + /* Read volume */
 + samp->​Volume = fgetc(fp);
 + /* Read and calculate ending/​looping positions */
 + temp = readWord(fp) << 1;
 + samp->​Replen = readWord(fp) << 1;
 + /* If the loop length is higher than 2, this
 + is a looping sample, otherwise it's a standard
 + non looping sample that stops at the end */
 + if (samp->​Replen > 2) {
 + samp->​Looped = 1;
 + samp->​End = (char *) temp + samp->​Replen;​
 + if ((unsigned int) samp->​End > samp->​Length)
 + samp->​End = (char *) samp->​Length;​
 + } else {
 + samp->​End = (char *) samp->​Length;​
 + samp->​Looped = 0;
 + }
 + /* Go to next sample structure */
 + samp++;
 + }
 + /* Read orders and calculate number of patterns */
 + mp->​NumOrders = fgetc(fp); fgetc(fp);
 + numpat=0;
 + for (i=0;​i<​128;​i++) {
 + temp = fgetc(fp);
 + if (temp > numpat)
 + numpat = temp;
 + mp->​Orders[i] = temp;
 + }
 + numpat++;
 + mp->​NumPatterns = numpat;
 + /* Identify type of mod */
 + fread(mp->​Type,​1,​4,​fp);​
 + mp->​Type[4]=0;​
 + mp->​Channels=0;​
 + idp = &​idents[0];​
 + for (i=0;​i<​8;​i++) {
 + if (!strncmp(idp->​String,​mp->​Type,​4)) {
 + mp->​Channels=idp->​channels;​
 + continue;​
 + }
 + idp++;
 + }
 + /* If we couldn'​t identify it, this isn't a mod file! 
 + close the file and return. Otherwise print number of
 + channels. */
 + if (!mp->​Channels) {
 + fclose(fp);​
 + return 0;
 + } else {
 + printf("​Channels %d\n",​mp->​Channels);​
 + }
 + /* Calculate pattern sizes and allocate memory for patterns */
 + mp->​LineSize = 4 * mp->​Channels;​
 + mp->​PatSize = 64 * mp->​LineSize;​
 + patsize = mp->​PatSize * numpat;
 + patp = xmalloc(patsize);​
 + mp->​Patterns = patp;
 + /* Load patterns */
 + for (i=0;​i<​numpat;​i++) {
 + for (j=mp->​PatSize/​4;​j;​j--) {
 + /* Each note is stored in 4 bytes. ​
 + Contained in the 4 bytes is:
 + Sample number.
 + Note
 + Effect
 + Effect Parameter
 + conv2Note() converts the mod format note format
 + into a format easier for Josmod.
 + */
 + * (unsigned int *) (patp+2) = readWord(fp);​
 + * (unsigned int *) patp = readWord(fp);​
 + conv2Note(patp);​
 + patp += 4;
 + }
 + }
 + /* Load Samples */
 + samp = &​mp->​Samples[0];​
 + for (i=0;​i<​31;​i++) {
 + printf("​Sample %d: %s\n",​i,​samp->​Name);​
 + if (samp->​Length) {
 + /* If the sample exists, allocate memory
 + for it, and read it in. Then fix up it's end 
 + pointer, then call fixSamp() to prepare it for
 + mixer output. */
 + samdata = xmalloc((long) samp->​Length + 128);
 + fread(samdata,​1,​samp->​Length,​fp);​
 + samp->​Samp = samdata;
 + samp->​End = (unsigned long) samp->​End + samdata;
 + fixSamp(samp->​Looped,​ samp->​End,​ samp->​Replen,​ 128);
 + } else samp->​Samp = NULL; 
 + samp++;
 + }
 + /* Close the file and return successfully */
 + fclose(fp);​
 + return ​ 1;
 +That's all for the loader! Now that patterns, orders, and samples are all
 +loaded and in a suitable format, the player is ready to play it!
 +Unfortunately that's all for now, as the player is quite a complicated topic,
 +I'll save it for a later article. But before I finish, I do want to give you a
 +basic outline of what happens in the player, so you can see where the mixer
 +fits into all this.
 +The Player
 +Mods have two speed variables which control the way a mod is played. One is
 +BPM, or Beats Per Minute. BPM specifies the speed at which the mod player
 +routine is called, the default value is 125 BPM, or 50 times a second. As we
 +all know, 50hz is the speed of PAL TV's, so it's no coincidence that this is
 +the default, as it allowed Amiga players to setup their play routines on an
 +interrupt synchronised with the screen, the same as with SID players.
 +BUT since we don't have the luxury of having hardware to play the samples for
 +us, it's not a good idea to hook our player to a raster interrupt, instead we
 +calculate how many output samples need to mixed before we update our mod. We
 +calculate that with the following:
 +playbackrate / (BPM*2 / 5)
 +This value is what I call the "​TickSize"​. Everytime the player is updated, it
 +will mix TickSize bytes of samples, then loop and update the mod again, mix
 +TickSize bytes again, etc..
 +The other speed variable is the number of Ticks per beat, and defaults to 6.
 +This means that the mod player only fetches the next row of pattern data every
 +6 Ticks. E.g. After 6*TickSize samples have been mixed.
 +Each channel in the Mod has assocatied with it, a mixer structure, which looks
 +like this:
 +typedef struct {
 + char *Samp; // Current sample position
 + char *End; // End or loop position
 + unsigned long Replen; // How far to loop back
 + unsigned long Speed; // Speed/pitch of sample
 + int Low; // Fractional part of sample position
 + int Repeats;​ //​ Whether or not it repeats
 + int *VolTab;​ //​ Pointer to volume table
 + int Active; // Whether to mix this channel ​
 + // or not
 +} MixChan;
 +The player has to prepare these structures for each channel and on every tick
 +update the appropriate fields depending on the data contained in the patterns.
 +I'll leave you with psuedo code for what the player looks like:
 +tickbeat = 6
 +row = 64
 +tickcount = 0
 +orderup = 0
 +make all channels inactive
 +while (orderup < numorders) {
 + if (row == 64) {
 + get next pattern from orders
 + orderup++
 + }
 + if (tickcount == 0) {
 + get row data from pattern
 + process note - set sample speed
 + process sample number - set sample position volume
 + process effect - speed, volume and position effects
 + } else {
 + process effect
 + }
 + mix ticksize samples
 + if (mixing buffer full) {
 + play buffer
 + }
 + tickcount++
 + if (tickcount >= tickbeat) {
 + tickcount = 0
 + move to next row
 + row++
 + }
 +That's basically what the mixer does. The majority of the player code deals
 +with effects processing, most of which are easy to process. Effects include:
 +Portamento - sliding a note's pitch up or down.
 +Volume setting/​sliding
 +BPM and Tick Beat changes
 +Pattern looping
 +Vibrato and Tremolo - Slight alterations in pitch and volume.
 +Some effects are only processed on Tick 0, and some are processed on other
 +Anyway that's all for now, the player code will be examined in a later
 +article. ​ In the meantime you can view the source code in HTML form at:
 +..    ​
 +.                                    C=H #20
 +===== The C64 Digi =====
 +Robin Harbron <​>​
 + ​Levente Harsfalvi <​>​
 +  Stephen Judd <​>​
 +Digis -- digitally sampled audio -- are fairly common on the 64.  This is
 +meant to be a comprehensive article on digis: how they work, examples,
 +different playback methods on the 64 (volume register and Pulse Width
 +Modulation),​ and some tricks. ​ We'll even show you how to play 6-bit and
 +even 8-bit digis in high quality on a 64, which is really pretty neat to
 +The first part discusses digis from a fundamental point of view -- just
 +what a digi is, acoustic signals, and things like that.  The most common
 +method of playing digis is via the volume register at $d418, and the next
 +two sections are devoted to this technique. ​ Section two discusses some
 +SID fundamentals,​ and the reason why $d418 may be used for digis (and why
 +later-model SIDs don't play digis correctly); Section three discusses
 +$d418-digis from a software perspective:​ how to play them, tricks for
 +improving them, how to boost digis on 8580 SIDs, and how to detect what
 +kind of SID (6581 or 8580) is in the machine. ​ The fourth and final part
 +of this article discusses pulse width modulation, and includes example source
 +code and a binary that plays a true 7-bit digi at around 16KHz -- something
 +which, we think, has never been done before.
 +Without further ado...
 +Digis: Overview
 + The whole point of playing a digi on a 64 is to provide something
 +for your ear to hear.  So let's begin by discussing just what an acoustic
 +signal is and how that relates to digis.
 + Probably everyone knows that "​sound"​ is how your ear responds to
 +changes in air pressure -- that is, when you clap your hands together,
 +it compresses the air between your hands in a special way, and that
 +higher pressure moves outwards into the surrounding air (since it's at
 +lower pressure). ​ That pressure change propagates along and when it
 +encounters your ear it causes the ear drums to move, causing three little
 +bones to move, causing some fluid to move, causing tiny, exquisitely
 +sensitive hairs to move, transmitting a signal that your brain converts
 +to "​sound"​.
 + An audio speaker also changes the air pressure in response to a
 +signal. ​ If you take a coil of wire and change the voltage on it, it
 +generates a magnetic field; if a magnet is placed inside the coil, the
 +changing magnetic field will place a force on the magnet, causing it to
 +move, causing some air to be pushed along, causing a change in pressure,
 +causing a signal to propagate to your ear which your brain interprets as
 +Van Halen. ​ All a stereo (CD player, etc.) does is send a varying voltage
 +signal to the speaker. ​ As that voltage level goes up and down the magnet
 +moves back and forth, and so the speaker converts that electrical energy
 +into an accoustic wave.
 + For us, the trick is to coax SID into sending a specific voltage
 +signal to the speaker, the way a stereo or CD player might. ​ And a CD player
 +is of course a very apt comparison, since it is itself a digi player.
 + Just for reference, a really good pair of ears can hear signals from
 +around 20Hz to 22KHz, with the sensitivity dropping considerably outside
 +of around 100Hz to 10KHz. ​ A CD player has a playback rate of 44KHz, and
 +the highest frequency SID can generate from the frequency registers is
 +around 4KHz.  If you've ever set SID to maximum frequency and heard just
 +how high 4KHz is, you can appreciate that even 10KHz is _really_ high, and
 +actually quite difficult to hear.  In human speech, most of the information
 +content of vowel sounds is contained in the range 300Hz - 3KHz, and above
 +around 1KHz for consonant sounds; most information in musical sounds is in
 +the range 100Hz - 3KHz.
 +Discrete Sampling
 + To understand digis a little better, consider the more general
 +case of a discretely sampled signal -- a continuous signal sampled at
 +discrete time intervals. ​ Let's say we had some device producing a
 +_continuous_ sinusoidal signal in time:
 +         ​* ​ *
 +      *        *
 +    *            *
 +   ​* ​             *
 +  *                *
 + ​* ​                 *                      *
 +*                    *                    *                ​
 +                      *                  *
 +                       ​* ​               *
 +                        *              *
 +                         ​* ​           *
 +                           ​* ​       *
 +                              *  *
 +----------------------------------------------->​ time
 +(yes, I did miss my calling as an ASCII artist)
 +To turn the signal into a _discrete_ signal, we simply sample the
 +signal at discrete intervals of time.  For example, let's say the above
 +signal lasts one second, and is input into a device which measures the
 +value every 1/​4-second. ​ The device will spit out four numbers: 0, 1, 0,
 +and -1:
 +          *
 +*                   *
 +                              *
 +The sampling frequency here is four samples per second -- 4 Hz.  If we were
 +to then play back this signal at the sampling frequency, we'd get a signal
 +          **********
 +********** ​         **********
 +                              **********
 +So one thing sampling does is to "​staircase"​ a signal -- the sample becomes
 +some sort of "​average"​ value over the sample period. ​ Increasing the sample
 +rate -- taking more samples per second -- will smooth things out, and the
 +sampled signal will look (and sound!) more like the original signal.
 +Now let's say we just took two samples in that one second -- 2 Hz sampling
 +rate -- and just happened to catch the signal at its maximum and minimum
 +values (the peak and trough). ​ Upon playback, the signal would look like
 +                    *
 +                    *
 +                    *
 +                    *
 +                    *
 +                    *
 +      *********************
 +That is, a square (pulse) wave.  If you're on the ball, you've noticed
 +that the frequency of the new signal is 1 Hz -- exactly half the sampling
 +frequency. ​ This is also called the Nyquist frequency. ​ In general, the
 +_maximum_ frequency that can be captured in a discrete sample (called the
 +Nyquist critical frequency) is half the sampling frequency -- as you can
 +see above, it takes two data points to get a single (nonzero) frequency.
 +So, for example, the highest frequency a CD player -- which has a sampling/
 +playback rate of 44KHz -- can capture is 22KHz, well above the range of
 +normal human hearing.
 +Thus, increasing the sample rate increases the frequency range captured
 +in the discrete signal. ​ This is why a digi at a high sample rate in general
 +sounds better than a digi sampled at a low sample rate.
 +BUT -- there is more to life than sample rate: there is also sample
 +resolution. ​ The sample resolution -- 4-bit samples, 8-bit samples, etc. --
 +determines how accurately the sample measures the actual signal. ​ For
 +example, let's say we sample sin(x) when x=0.5:
 + sin(0.5) = 0.4794255...
 +No matter what sample resolution we use, there will always be some error
 +in the measurement,​ and the _true_ value of the sample will be the
 +_measured_ value plus some error.
 +In general the sampling errors are random and uniformly distributed,​ so
 +the sampled signal corresponds to the original signal plus some noise (the
 +random errors). ​ That is why you almost always hear some sort of hiss on
 +a normal C64 digi, which uses a resolution of 4 bits per sample.
 +So, increasing the sample _resolution_ decreases the amount of noise introduced
 +into the sampled signal (and increases the dynamic range), and increasing the
 +sample _rate_ increases the frequency range.
 +If you're _really_ on the ball, you've noticed that the 1-Hz square pulse
 +above actually contains frequencies higher than 1Hz, simply because a
 +square pulse contains higher harmonics in addition to the 1Hz fundamental
 +frequency. ​ And you've also no doubt realized that the sampled pulse wave
 +would sound different than the original sine wave (due, of course, to the
 +added harmonics) -- it's at the right frequency, but it will sound like a
 +pulse wave instead of a sinusoid.
 +Have we somehow broken the Nyquist limit?
 +The answer is no, because of a nifty thing called the Discrete Sampling
 +Theorem, which says that, given the samples h_n of a bandwidth-limited
 +function h(t), the original function h(t) is given by
 + h(t) = dt * Sum{ h_n * sin(2*pi*f_c*(t-n*dt)) / (pi*(t-n*dt)) }
 +where dt is the sampling period and f_c is the cutoff/​critical frequency.
 +What this means is that the original signal can be _reconstructed_ from the
 +discrete samples, not that it is _equivalent_ to the discrete samples.
 +The Nyquist limit is the highest frequency that can be _reconstructed_ from
 +the discrete samples, not the highest frequency that will be produced if you
 +"​staircase"​ the discrete samples through a speaker. ​ If the original
 +signal is bandwidth-limited,​ and there are at least two samples for the
 +highest frequency, then the signal can be completely reconstructed.
 +Since a "​normal"​ digi contains all these extra frequencies,​ shouldn'​t a digi
 +sound "​different"​ than a "​true"​ analog signal? ​ Sure.  On the other hand, many
 +of the extra frequencies are beyond the range of human hearing, and the rest
 +can often be removed using a filter -- all CD players filter the output, for
 +example. ​ So sometimes it is worthwhile to turn on a low/band pass filter
 +when playing a C64 digi, especially at lower sample rates.
 +And that more or less summarizes basic discrete sampling theory.
 +D418 Playback -- Hardware
 +The SID contains both analog and digital subparts on one silicon plate -- in
 +other words, it is a mixed signal device.
 +At the time, the SID was certainly the best of the microcomputer sound chips.
 +This may be mostly due to its mixed signal design, which the designers used
 +to solve certain problems.
 +The hard thing in a sound generator design is to implement waveforms, volume
 +control, and mixing. Things like that don't really fit into the digital
 +'​either 0 or 1' philosophy, unless lot of data bits and arithmetic functions
 +are involved. In a fully digital sound chip, the waveforms could be generated
 +by ROM lookup tables. The mixing function could be derived from binary
 +addition, while the volume control from division or multiplication. Unless the
 +sound functionality is greatly simplified, the arithmetic functions must be
 +present and they must be implemented in hardware. Finally, the D/A conversion
 +could be done by (fast) pulse width modulation just at the output stage.
 +(Most of today'​s wavetable sound cards operate like this).
 +This method implies heavy arithmetic hardware, which was not an option for
 +designers back then. Still, most sound chips were fully digital, and all
 +suffer from the required compromises (i.e. generating square waves only,
 +no dedicated channel volume control, etc. - both TED and the VIC-I are obvious
 +The solution that one finds in the SID design is very straightforward:​ mixing
 +and variable volume level is problematic in a digital circuit when dealing
 +with waveforms, so simply avoid doing it. In the SID, only the microcomputer
 +interface, the registers, the oscillators (phase accumulating oscillators),​
 +and other controller logic are digital; the mixing and volume control parts
 +are fully analog. There are digital to analog converters providing analog
 +voltage levels from the digital state variables. The SID D/As are in fact
 +'​multiplying'​ D/As, having an analog input (AIN), an input base voltage
 +(IBASE), and a digital input. They operate by amplifying the input voltage
 +offset (AIN-IBASE) by a factor proportional to the number on the digital input
 +and adding this offset back to the base level.
 +This mixed signal design also allowed some other features to be implemented. ​
 +The most important one is the analog filter (that is, a two integrator loop,
 +bi-quadratic filter, according to Yannes). With that, the SID points beyond a
 +home computer sound chip - it is a true analog subtractive synth (marketing as
 +such was cancelled because of manufacturing capacity reasons).
 +Here is a detailed map on the SID inners (analog path; probably my most
 +beautiful ASCII ever :-D). Info can be found in the SID patents (US 4,677,890;
 +1986), the MOS 6581 technical document (can be found somewhere on the Net), or
 +the back of the Programmer'​s Reference Guide (PRG).
 +                ----------------- ​ 11bit  ------------
 +                |Cutoff freq reg|-------->​|Cutoff D/​A|---------o
 +                ----------------- ​        ​------------ ​        |
 +                    $d415-16 ​                                  |
 +                                                               |
 +                ----------------- ​ 4bit   ​------------ ​        |
 +                |Resonance reg. |-------->​|Reson. D/A |-o      |
 +                ----------------- ​        ​------------- |      |
 +                    $d417.[4-7] ​                        ​| ​     |
 +                                                        |      |
 +                                 ​=0 ​                    ​v ​     v
 +----------- ​   ----------- ​    >​o------------>​| ​   ------------------
 +|wave D/A |--->​|env. D/A |-->​o/ ​              ​| ​   |                |
 +----------- ​   ----------- ​     o--->​| ​       |    |                |
 +     ​^ ​             ^          ^ =1  o--------|--->​| ​               |
 +     ​|12bit ​        ​|8bit ​     |     ​| ​       |    |                |
 +     ​| ​             |          |     ​| ​       |    |                |
 +----------- ​   ----------- ​    ​| ​    ​| ​       |    |                |
 +|OSC1 +   ​| ​   |ADSR cnt+|     ​| ​    ​| ​       |    |                |
 +|wave sel.|    |env. log.|     ​| ​    ​| ​       |    |                |
 +----------- ​   ----------- ​    ​| ​    ​| ​       |    |                |
 +$d400-03, ​     $d405-06, ​   $d417.0 ​ |        |    |                |
 +$d404.[1-7] ​   $d404.0 ​              ​| ​       |    |     ​FILTER ​    |
 +                                     ​| ​       |    |                |
 +                                 ​=0 ​ |        |    |                |
 +----------- ​   ----------- ​    >​o----|------->​| ​   |                |
 +|wave D/A |--->​|env. D/A |-->​o/ ​     |        |    |                |
 +----------- ​   ----------- ​     o--->​| ​       |    |                |
 +     ​^ ​             ^          ^ =1  |        |    |                |
 +     ​|12bit ​        ​|8bit ​     |     ​| ​       |    |                |
 +     ​| ​             |          |     ​| ​       |    |                |
 +----------- ​   ----------- ​    ​| ​    ​| ​       |    |                |
 +|OSC2 +   ​| ​   |ADSR cnt+|     ​| ​    ​| ​       |    |                |
 +|wave sel.|    |env. log.|     ​| ​    ​| ​       |    |                |
 +----------- ​   ----------- ​    ​| ​    ​| ​       |    |                |
 +$d407-0a, ​     $d40c-0d, ​   $d417.1 ​ |        |    |                |
 +$d40b.[1-7] ​   $d40b.0 ​              ​| ​       |    | LP   ​BP ​  ​HP ​  |
 +                                     ​| ​    =0 |    ------------------
 +                                 ​=0 ​ |    >​o->​| ​      ​| ​   |    |
 +----------- ​   ----------- ​    >​o----|--o/ ​   |   *** o    o    o  ​
 +|wave D/A |--->​|env. D/A |-->​o/ ​     |     o- |      /    /    /
 +----------- ​   ----------- ​     o--->​| ​ ^  =1 |   =0 V    V    V   =1
 +     ​^ ​             ^          ^ =1  |  |     ​| ​     o o  o o  o o
 +     ​|12bit ​        ​|8bit ​     |     ​| ​ |     ​| ​     | |  | |  | |
 +     ​| ​             |          |     ​|$d418.7 |<​-------o ​   |    |
 +----------- ​   ----------- ​    ​| ​    ​| ​       |<​------------o ​   |
 +|OSC3 +   ​| ​   |ADSR cnt+|     ​| ​    ​| ​       |<​-----------------o
 +|wave sel.|    |env. log.|     ​| ​    ​| ​       |
 +----------- ​   ----------- ​    ​| ​    ​| ​       |
 +$d40e-11, ​     $d413-14, ​   $d417.2 ​ |        |
 +$d412.[1-7] ​   $d412.0 ​              ​| ​       |    -----------------
 +                                     ​| ​       |    | Master volume |AUDIO
 +                                 ​=0 ​ |        o--->​| ​    ​D/​A ​      ​|----->​
 +                               >​o----|------->​| ​   ----------------- OUT
 +EXT IN --------------------->​o/ ​     |                     ^
 +                                o--->​| ​                    |4bit
 +                               ^ =1           ​^ ​           |
 +                               ​| ​             |       ​$d418.[0-3]
 +                            $d417.3 ​ ^        |
 +                                     ​| ​       |
 +                    Analog mixing ---|--------|
 +***: Filter type select switches, $d418.[4-6] respectively
 +$d418 digis
 +The most common method of playing a digi is to use the register at $d418.
 +When someone plays a digi using the master volume register, the situation is
 +similar to the waveform D/A converters. Both D/As are multiplying D/As --
 +signal amplifiers whose amplification is proportional to the input digital
 +number. If there is a nonzero signal offset on the D/A input it will be
 +multiplied proportionally by this number.
 +Playing digis with $d418 is possible because there is indeed a relatively
 +large DC voltage offset on the master volume D/A. This offset is present
 +right from the moment when the SID is powered up.
 +Where can this DC offset come from?
 +There is a mixer before the master volume D/A (see figure). If there'​s a DC
 +offset on the D/A input, it must come from there. ...And going further,
 +the DC offset on the mixer must also come from somewhere. ​ But where?
 +Signals come from the three ADSR volume D/As, the EXTIN line, and the three
 +outputs of the filter. Fortunately,​ all paths that go to the mixer have analog
 +switches (all paths can be disconnected from the mixer individually,​ if that's
 +The above analog switches are driven by the filter selection bits ($d417 bits
 +0-3), the voice 3 off bit ($d418 bit 7) and the filter type selection bits
 +($d418 bits 4-6).
 +After a reset, the filter selector bits are all 0 (all signals are routed
 +towards the master mixer), the 'voice 3 off' switch is on, and the filter type
 +selector bits are 0 (filter outputs are unconnected). In this state, only
 +EXTIN and the three SID voice signals are present on the mixer. EXTIN can
 +be eliminated as the source since it has no DC offset (as long as the computer
 +was not hacked, see notes on the 8580).
 +The ADSR volume D/A is similar to the previously mentioned multiplying D/As.
 +If the digital number on the input is 0, the input analog signal offset can't
 +pass through (as measurements verify). This is the case when SID is reset,
 +setting the envelope counters to zero.  Therefore, nothing behind the ADSR
 +multiplying D/As can have any effect on the DC offset of the mixer.
 +So, the DC offset must come from the ADSR multiplying D/As. Another
 +measurement shows that even the mixer itself has a small DC offset.
 +Tests and results
 +I did some tests that support this theory. They were done 'by hand', by simply
 +using a digital voltmeter + the FC3 monitor.
 +The chip was a 6581(R1), 0883, Hong Kong (an early 6581).
 +When turned on the voltage on the AUDIO OUT was about 5.5 volts (slowly
 +decreasing as it warmed up, stopping at about 5.43 after some 10 mins - all
 +subsequent tests were done after this time period).
 +Writing $0f to $d418 raised the output voltage to 6.15 volts. Therefore, the
 +maximum output amplitude that can be achieved when playing digis is 0.72 volts
 +in this '​mode'​ (without wiggling any other SID settings to achieve higher
 +voltage levels) -- remember that what counts is the maximum voltage
 +_difference_,​ not the maximum absolute voltage.
 +The next test is to determine if the mixer has its own DC offset (with all
 +possible paths are disconnected). It's possible to do. With the volume at
 +maximum (to maximize any effect), all voices are routed towards the filter
 +($d417 = $0f), while making sure that the filter outputs are not routed to the
 +mixer ($d418 = $0f). In this state no paths can drive the mixer. The result
 +is 5.39 volts. When the volume changes, the output also changes towards the
 +previous 5.43 volts --> there is a (very small) DC offset from just the mixer.
 +What could be the DC offset value of each individual SID voice (i.e. the base
 +level difference of the multiplying D/​As)? ​ Doing the above, but leaving one
 +voice routed to the mixer ($d417 = $0e, $0d or $0b) gives 5.69 volts.
 +5.69-5.43 = 0.26 volts, and 5.43 + 3*0.26 = 6.21, almost 6.15 volts.
 +To determine if the ADSR multiplying D/As act as expected, I used pulse
 +waves with zero frequency and 0 or $fff pulse width (two cases), to make the
 +input signal of the ADSR multiplying D/A the minimum and maximum possible
 +level. After careful checking, the output changed a few hundredth volts
 +(about 0.01 volt per voice). ​ So the D/A doesn'​t close up completely, but
 +it's still O.K.
 +To prove that these offsets are equal for all voices, I did another test. Some
 +people know that the filter inverts phase (multiplies the input signal by -1).
 +Machine is reset, $d417 = $01, $d418 = $9f. (Voice 1 is routed through the
 +filter, voice 3 is cut off from the mixer completely ($d418.7), low pass
 +filter is selected, volume = $0f). The output voltage was 5.41 volts, just
 +very slightly below the "​default"​ output level. This means that the DC of
 +voice2 + (-1*) DC of voice 1 resulted in about 0 relative offset. Doing
 +similar tests proved that the DC offsets for the voices match each other
 +almost exactly (within a few hundredths of a volt).
 +These measurements all support the idea that the DC offset comes from the
 +ADSR multiplying D/As, that the offset is mostly independent from the waveform
 +D/A converters (as long as sustain levels are 0), and that the offsets are
 +equal for all voices. In addition, a small DC offset is supplied by the master
 +signal mixer itself.
 +What if we try different sustain settings? For this test, set the volume to
 +maximum, as usual. Set the sustain level to $0f for all voices ($d406, $d40d,
 +$d414 = $f0). Start the attack, but with no waveform selected ($d404, $d40b,
 +$d412 = 01). The output level is now 5.21 volts, a little bit below the '​0'​
 +offset of the audio output! (Doing the test with just one voice (all 
 +others disconnected),​ the output is 5.29 volts).
 +Finally, we can do some experiments with the pulse waveform. ​ The pulse
 +waveform is useful for these tests, since at zero frequency we can set both
 +the minimum and the maximum constant DC levels at the voice D/A just by using
 +the pulse width registers. Reset the computer. Set voice 1 to zero frequency,
 +pulse level $0fff, sustain level 15, and $d404=$41 (pulse waveform + gate on).
 +Route only voice 1 through the mixer ($d417 = $0e). The output voltage is
 +similar to the test when no waveform was selected -- 5.29 volts! This seems to
 +show that "​waveform accu = $0fff" is the same as when no waveform is selected
 +(i.e. the waveform D/A digital input pins are pulled high when they'​re not
 +driven, as seen in most other NMOS chips).
 +When the pulse width is 0 in the above test the output changes to 6.34 volts.
 +This seems to be strange (a multiplying D/A giving higher signal level for
 +multiplying something by 0).
 +Now, when the ADSR multiplying D/A is closed, the output is 5.70 volts. When
 +it's fully open, the output changes from 5.29 volts (wave acc= $fff) to 6.34
 +volts (wave acc = 0). One reasonable answer is that the base voltage of the
 +waveform D/A is higher, and the analog input is tied lower than the base
 +voltage of the ADSR D/A -- the effect is that the SID waveforms will lie
 +'​around'​ the ADSR multiplying D/A base voltage, more or less symmetrically.
 +This was surely done intentionally,​ to reduce absolute voltage levels (for
 +linearity). ​ In the 6581, the big DC offset is probably a result of having the
 +ADSR D/As and the master volume D/A at different base levels (the difference
 +appears as true DC offset on the master volume D/A). If both were the same
 +(presumably at VDD/2), and the waveform D/A parameters were selected similarly
 +(operation is symmetric to VDD/2), there would be no final DC offset at all.
 +Rather like the 8580...
 +Other issues
 +So now we know why $d418 digis are possible - but still, there are some things
 +to note.
 +The DC offset on the master volume D/A changes with different SID settings,
 +and whatever affects the DC offset on the mixer will affect the digi
 +volume. For example, even the filter output signals have a small DC offset.
 +Just do a test - set the volume to 0f, then simply turn one filter route on
 +(for example, $d418 = $1f). You'll hear a small click (i.e. a small DC offset
 +change on the mixer), even if the filter has no input.
 +Moreover, as seen above, the DC offset can be eliminated completely (just by
 +SID register settings), leading to no audible digi sound at the output. ​ In
 +other words, whatever affects the DC offset on the mixer _will_ affect the
 +digi volume.
 +One place where this is important is playing a digi with a tune: there'​s a
 +constantly changing signal going to the mixer instead of a constant DC offset,
 +so playing a digi on the master volume also causes distortion for both the SID
 +voices and the digi sound (since they'​re cross modulated). To reduce this
 +effect most 3+1 like SID + digi players play samples by writing 8-offset sample
 +values to $d418 (ie. adding 8 to 3-bit sample values and writing this to $d418
 +- see players used by Jeroen Tel and other famous composers using digi). This
 +trick reduces the modulating effect while still maintaining good digi volume.
 +The DC offsets used to create awful clicking sometimes. For example, the
 +filter inverts phase. If the filter is currently routed to the mixer, there'​ll
 +be a large '​click'​ (2 times the DC offset) when a voice is on and its routing
 +is changed to or from the filter.
 +The 8580
 +This is a completely redesigned chip. I don't know details, but it was
 +probably redesigned by the time all other chips in the C64 were done for CSGs
 +new manufacturing technology and the C64c. It is a '​better'​ chip from the
 +technical side (but in my opinion it sounds crude in comparison to the 6581,
 +at least the R4 series). The 6581 was designed in months. Bob Yannes had to do
 +everything from scratch and use the manufacturing technology MOS currently had
 +(NMOS). And it shows. ​ First, it has high background noise. The DC offsets are
 +really also a misfeature. The D/A converters are sometimes non-monotonic (at
 +least, the waveform D/As and the filter cutoff D/A have some drops at the
 +change of the most significant bits). The op-amps in the active (resonant)
 +filter are simple, linearized NMOS inverters ;-) (loopbacked,​ they act like
 +more or less linear op-amplifiers around VDD/2). And I still haven'​t mentioned
 +bugs in the digital side (ADSR envelope bugs). Because of the above, one
 +probably won't find two identical 6581 chips -- each sounds a little bit
 +different (mostly due to the filter). Since the active components of the
 +filter are far from ideal, the filter is strongly nonlinear (the cutoff curve
 +changes with signal amplitude). On the other hand, these things are what make
 +the SID sound so unique.
 +Most of the problems were fixed in the 8580. It has much less background
 +noise. The chips sound the same (there are hardly any differences between
 +different 8580s). Most of the DC offset issues (the clicks) were elminated. It
 +needs less power, and lower VDD level. Something was changed also in the
 +digital logic, but the ADSR part was not touched. The '​combined'​ waveforms are
 +a bit different (and more useable from the musician'​s point of view).
 +The clicks were reduced, which means that there is no (or no significant) DC
 +offset on the master volume D/A in the 8580.
 +(I have not done any measurements,​ but after listening to a lot of 3+1 channel
 +type musics, I have a strong suspicion that even if sounds are turned on, the
 +average DC offset on the master volume D/A is still minimal).
 +To fix this in software, you'll have to wait until the next section of this
 +To fix this in hardware, people use a simple hack: take a resistor of about
 +330k and tie the SID EXTIN line to GND through that (directly, beside the
 +chip, on the mainboard).
 +The EXTIN line goes directly to the mixer, and thus the master volume D/A, or
 +can also be routed through the filter. In either case, unless the filter is
 +disconnected,​ the above hack will give a pretty large DC offset, similar to
 +the original 6581s. So, digi sounds can be played :-) (even with SID music
 +playing simultaneously,​ similar to the 6581).
 +This solution is good as a work around, but there'​s one thing to note: this is
 +not completely the same as the 6581 ADSR D/A offset voltage. At least, this
 +offset is negative (should that pin rather be tied to VDD?). Programs that
 +depend on the 6581s way of DC offsets will not work correctly (but I know of
 +very few such programs, so at worst you'll experience slightly different digi
 +sound only occasionally -- but hey, the 8580 sounds different anyway). Another
 +problem is that when EXTIN is routed through the filter the DC offset may
 +cause strong distortion since the DC operating point of the filter is changed
 +-- bad news if the '​semi-linear'​ amplifiers in the filter are picky about
 +absolute DC level. ​ Some music (not neccessarily involved with digis) indeed
 +do route EXTIN through the filter, for noise reduction on older C64s (with the
 +earlier C64 mainboards that pick up lots of '​digital'​ background noise from
 +EXTIN). DC distortion can also occur occasionally for the same reason but on
 +the master volume D/A (the higher the difference from VDD/2, the greater the
 +risk of experiencing nonlinearity and clipping distortion).
 +Some final words
 +A lot of this information comes from Dag Lem, who is certainly the No. 1 SID
 +hacker for me ;-). Take a look at reSID, his SID emulator library (the sources
 +can be downloaded from somewhere). reSID contains so much reverse engineered
 +information of the real SID that you won't believe it -- check it out if
 +you're interested.
 +D418 Playback -- Software
 +$D418 digis are by far the most common playback method. ​ The volume register
 +gives 16 different amplitudes (0-15), and so can provide 4-bit digi playback.
 +In its most basic form, this is an extremely easy routine to code.  Simply
 +load each 4-bit sample, and store it in the volume register ($d418).
 +Assuming $fd/$fe are pointing to the beginning of a series of samples, the
 +following code will play it back:
 + ldy #0
 +:loop lda ($fd),y
 + sta $d418
 + ldx #5 ;some delay value
 +:delay dex
 + bne :delay
 + iny
 + bne :loop
 + inc $fe
 + jmp :loop
 +The ldx #5 would have to be adjusted depending on the speed of the
 +sample - the lower this number (not including zero) the faster the
 +sample will play back.
 +There are a number of improvements we could make to this code - first
 +of all, this method takes twice as much RAM to store the sample as is
 +necessary. ​ Because we're dealing with 4-bit samples, we can store 2
 +samples in each byte.  This can be handled simply by alternately
 +masking out the high bits (with AND #15) to play the sample stored in
 +the low nybble, and by shifting the high nybble down to the low nybble
 +to play the high nybble (LSR : LSR : LSR : LSR).  A lookup table may also
 +be used to save processor cycles (but use more RAM).
 +Another improvement is to move the routine to zero-page, and use self-
 +modifying code.  In general, this results in the fastest digi players.
 +We should of course have the routine check for the end of the sample --
 +typically just checking the high byte of the zero-page pointer is enough
 +(in this case, checking $fe).  Typically digis are page aligned anyway, so
 +just zeroing out the unused part (if any) of the last page is fine.
 +Finally, it is often important that each sample of a digi is played back
 +at regular intervals. ​ If the samples aren't played at a steady speed,
 +extra distortion is audible. ​ In the example above, playback is steady
 +for a full page (256) of samples - but several extra cycles are added
 +by incrementing the zero-page pointer to the digi.  The situation
 +worsens when we start adding extra code to check for the end of the
 +digi, and even the main loop starts getting irregular when we add the
 +code for the simple form of packing discussed earlier (2 4-bit samples
 +per byte). ​
 +These problems can be solved by careful cycle counting and adding NOP
 +and harmless BIT instructions in strategic places to make each
 +iteration the same number of cycles, regardless of which branch is
 +taken - people who have written a stable raster routine, or done some
 +Atari 2600 coding have likely done this sort of painstaking work
 +NMI-driven digis
 +More commonly, however, we enlist the help of CIA #2 and have it
 +generate regular Non-Maskable Interrupts which we use to call our digi
 +player. ​ This has two important advantages - first, it makes timing
 +much more simple. ​ Second, it frees your main program to do other
 +things while the digi is playing "in the background"​.
 +To experiment, I pulled a 4-bit packed digi from the extras disk
 +included with Super Snapshot 5.22.  It's the beginning seconds of the
 +introduction to Classic Star Trek (Space, the Final Frontier).
 +Here's the source for a fairly "​frills-free"​ NMI based digi player,
 +with my comments after blocks of code:
 +start    = $1400
 +end      = $7cff
 +freq     = 141
 +ptr      = $fd
 +Labels start and end simply point to the beginning and end of the
 +digi.  Freq isn't actually the frequency - it's the number of
 +processor cycles between interrupts necessary to play the digi at the
 +desired speed/​pitch. ​ If you know the frequency (in hz) of the digi,
 +simply divide your CIA clock speed (approximately 1000000 hz) by the
 +digi frequency. ​ In this case, the digi runs at approximately 7100 hz.
 +We use two zero page locations to form a 16-bit pointer to the current
 +sample in the digi to play.
 +         *= $1000
 +         ;​disable interrupts
 +         lda #$7f
 +         sta $dc0d
 +         sta $dd0d
 +         lda $dc0d
 +         lda $dd0d
 +         sei
 +This code simply disables interrupts and initializes both CIA timers.
 +         ;​blank screen
 +         lda $d011
 +         and #255-16
 +         sta $d011
 +Just like erratically timed code can introduce distortion when a digi is
 +played back, the VIC steals cycles from the processor that can cause
 +interrupts to not occur precisely when you'd like them to.  This routine will
 +work without the screen blanked, but the extra noise introduced when the
 +screen is on is noticeable when the time between samples is less than around
 +2.5-3 times the time the processor is stopped. ​ Another option is to use some
 +multiple of the raster timing as the sampling rate, and start the routine on a
 +non-badline,​ to ensure that the interrupts never occur on a badline. ​ (A final
 +option is to use a raster-driven interrupt for the digi; with the SCPU, it is
 +actually possible to drive an IFLI display and play a digi at the same time,
 +badlines and all -- email Robin for more info, or maybe wait for a future
 +article!). ​ But the simplest thing to do is to blank the screen :).
 +         ;​switch out roms
 +         lda #$35
 +         sta 1
 +         ;​point to our player routine
 +         lda #<nmi
 +         sta $fffa
 +         lda #>nmi
 +         sta $fffb
 +Unless using the KERNAL routines is necessary in my program, I always
 +switch out the ROMs.  One of the biggest benefits is that our NMI
 +routine will be immediately called, rather than using $0318/$0319 and
 +waiting for the KERNAL to indirectly call your routine.
 +         ;​initialize player
 +         lda #<start
 +         sta ptr
 +         lda #>start
 +         sta ptr+1
 +         ldy #0
 +         sty flag
 +         lda (ptr),y
 +         sta sample
 +This section simply initializes the various memory locations that the
 +player uses - sets ptr/ptr+1 to point to the beginning of the digi,
 +loads the first sample, and clears the flag that handles the
 +alternating between the lower and upper nybble of the packed samples.
 +         ;​setup CIA #2
 +         lda #<freq
 +         sta $dd04
 +         lda #>freq
 +         sta $dd05
 +Sets Timer A on CIA #2 to freq.  ​
 +         lda #%10000001
 +         sta $dd0d
 +Enables Timer A interrupts on CIA #2.
 +         lda #%00010001
 +         sta $dd0e
 +Sets Timer A to run in continuous mode.  As soon as Timer A counts
 +down to zero, it will automatically be reloaded to the last writes to
 +$dd04/$dd05 and begin counting down again.
 +endless ​ jmp endless
 +For this example, we just put the computer in an endless loop.
 +         pha
 +         txa
 +         pha
 +         tya
 +         pha
 +         ;play 4-bit sample
 +         lda sample
 +         and #15
 +         sta $d418
 +We play the sample while all the code is still linear - before any
 +branches have occurred. ​ This is to minimize the distorting effects I
 +mentioned earlier. ​ The AND #15 is used so we don't inadvertently
 +enable the filter bits in $d418 with the high nybble packed into
 +         ;​clear NMI source
 +         lda $dd0d
 +By reading $dd0d, we are acknowledging the source of the interrupt,
 +and the CIA will now generate another interrupt next time Timer A
 +counts down to zero.
 +         ;just something to look at
 +         inc $d020
 +         ;​every other NMI do 1) or 2):
 +         lda flag
 +         bne lower
 +Now we deal with "​unpacking"​ the samples.
 +         ;1) shift upper nybble down
 +upper    lda sample
 +         lsr a
 +         lsr a
 +         lsr a
 +         lsr a
 +         sta sample
 +         jmp exit
 +When flag is set to zero, we shift the high nybble of sample down to
 +the low nybble so it's ready to be played next NMI.
 +         ;2) get a new packed sample
 +         ; ​  then point to next
 +lower    ldy #0
 +         lda (ptr),y
 +         sta sample
 +         inc ptr
 +         bne checkend
 +         inc ptr+1
 +When flag is set to one, we load a new packed sample into sample, and
 +point ptr at the next packed sample.
 +         ;if end of sample, point to
 +         ;​beginning again
 +checkend lda ptr
 +         cmp #<end
 +         bne exit
 +         lda ptr+1
 +         cmp #>end
 +         bne exit
 +         lda #<start
 +         sta ptr
 +         lda #>start
 +         sta ptr+1
 +Simply check for the end of the digi, and if we've reached it, loop
 +back to the beginning of the digi.
 +         ;​toggle flag and exit NMI
 +exit     lda flag
 +         eor #1
 +         sta flag
 +         pla
 +         tay
 +         pla
 +         tax
 +         pla
 +         rti
 +         ;​sample'​s lower nybble holds
 +         ;the 4-bit sample to played
 +         ;next NMI - the upper nybble
 +         ;​holds the next nybble to be
 +         ;​played on "​odd"​ NMIs, and is
 +         ;​undefined on "​even"​ NMIs.
 +sample ​  .byte 0
 +         ;flag simply toggles between 0
 +         ;and 1 - used to decide whether
 +         ;to play upper or lower nybble
 +flag     .byte 0
 +Improving D418 Digis
 +D418 digis tend to generate a lot of noise, because, of course, the 4-bit
 +sample resolution. ​ Over the years people have come up with numerous tricks to
 +improve the sound of a d418 digi; here are some that we know of and have tried.
 +The first, and most obvious, thing to do is to use the low-pass filter, since
 +a lot of the noise is at higher frequencies. ​ Unfortunately this won't work,
 +since the filters occur in SID before the volume amplifier -- all the filters
 +can do is change the DC offset that makes the digi possible. ​ This trick
 +will work for methods that use SID voices, however (such as Pulse Width
 +Modulation, discussed in the next section).
 +Another trick is to "​dither"​ the sound, as discussed in C=Hacking #11.  The
 +idea here is to generate an intermediate "​average"​ value by toggling between
 +two values. ​ For example, if d418 is set to '​8'​ half of the time, and '​9'​ the
 +other half, its '​average'​ value will be 8.5.  So this is somewhat like adding
 +an extra bit of resolution. ​ In principle, you can extend this further: if it
 +is '​8'​ one-third of the time and '​9'​ for the remaining two-thirds, the average
 +value will be 8.66.  And so on.
 +Now, we aren't _really_ increasing the sample resolution here, but are instead
 +increasing the sample playback rate -- we're playing two samples ('​8'​ and '​9'​
 +for example) where before we played just one.  Don't get too carried away
 +thinking about "​average"​ voltage levels (after all, there is an average
 +voltage for the entire digi but that's not what you hear!) -- what's important
 +is how well the sampled signal represents the original signal. ​ If the
 +original signal is rising from 8 to 9 during the sample interval, this type
 +of trick will work well.
 +Which leads us to another trick: interpolation. ​ This is really a compression
 +trick, more than a '​resolution'​ trick. ​ Let's say that one sample value is 5,
 +and the next value is 9.  It might be reasonable to expect an '​intermediate'​
 +value of 7, to play right after the 5.  Once again, the idea is to increase
 +the playback rate to better-represent the original signal. ​ This type of trick
 +increases the playback rate without increasing the amount of data -- and as
 +always, your mileage may vary.  Many modern soundcards and CD-players use
 +Another curious trick is to add noise to the signal -- that is, the 4-bit
 +sample corresponds to the original signal plus noise. ​ Sometimes, by adding
 +noise to the signal playback the noise can actually cancel! ​ The '​dithering'​
 +trick above can be viewed in this way.
 +Boosting 8580 Digis
 +As most people know, there are '​old'​ SIDs (6581) and '​new'​ SIDs (8580), and
 +$d418 digis do not work right on 8580 SIDs, (such as in the 128D, most 128s,
 +and the 64C) for the reasons discussed earlier -- the 8580 does not have a
 +residual voltage leading into the amplitude modulator.
 +The software fix for this is pretty simple: have SID generate a signal, and
 +hence a voltage, for the volume register to modify. ​ You can actually use
 +pretty much any waveform to do this, but a pulse is the simplest, since a
 +pulse wave just toggles between two voltage levels. ​ Moreover, page 463 of 
 +the PRG says, "The TEST bit, when set to a one, resets and locks Oscillator 1
 +at zero until the TEST bit is cleared. ​ The Noise waveform output of
 +Oscillator 1 is also reset and the Pulse waveform output is held at a DC
 +level." ​ So it's not really necessary to worry about the frequency or pulse
 +width, by using the test bit.
 +BUT -- it is very important to set the sustain level to $f.  The ASDR envelope
 +generators generate the voltage. ​ A sustain level of 0 gives no improvement.
 +So, to '​boost'​ a digi on a later-model SID, you can just turn on a pulse with
 +the test bit set:
 + LDA #$FF
 + STA $D406
 + LDA #$49
 + STA $D404
 +Setting more voices gives the digi a substantial extra boost:
 + LDA #$FF
 + STA $D406
 + STA $D406+7
 + STA $D406+14
 + LDA #$49
 + STA $D404
 + STA $D404+7
 + STA $D404+14
 +The moral is: if you're writing a digi routine, and want it to work on all
 +computers, be sure to boost the digi.
 +And for completeness,​ using more channels is a commonly used trick to enhance
 +digi resolution on the Plus/4. The TED digi resolution (the volume register)
 +is 3 bits.  Fortunately,​ all channel on/off bits + the volume level are in the
 +same register ($ff11). If one source is on, the output DC is about half of the
 +level when both are turned on. This trick can be extended further to results
 +in a 'semi 4-bit' or 5-bit digi table (the dynamic range is enhanced, but
 +there are larger steps at the table end than at the start). ​ This trick could
 +also be used in SID if the sound sources were accurately preset, but runs into
 +problems due to the non-matching SID-versions and having the control bits in
 +multiple registers.
 +SID Type Auto-Detect
 +The following routine will detect what type of SID is in use.  I've
 +tested it on a fair cross-section of my collection of computers - my
 +NTSC 128D, two 64Cs, two "​breadbox"​ C-64s, and my PAL breadbox 64.  In
 +all cases the code performed 100% accurately - but still, there may be
 +cases where it fails. ​ I'd be interested to know if anyone finds any
 +faults in the routine, so I can improve it!
 +How does the routine work?  I was told that the old SID (6581) and the
 +new SID (8580) behave differently when set to play combined
 +waveforms. ​ I coded a fairly simple routine to use the REU to sample
 +$d41b (the upper 8 bits of Oscillator 3's waveform output) for a full
 +64k bank.  Then I experimented with various frequencies and
 +combinations of waveforms on Oscillator 3 until I found consistently
 +different results with the two different SIDs.
 +When I combined the triangle and sawtooth waveforms and then sampled
 +$d41b I found that most of the time the oscillator was just putting
 +out zeros, with occasional bursts of numbers. ​ These "​bursts"​ were
 +consistently near $ff on the 8580, while the 6581 was always well
 +below $80 - often $3f was the highest it would get.
 +So, the detection code ended up being quite simple - I'll explain each
 +block of code:
 +         *= $4000
 +start    sei
 +         lda #11
 +         sta $d011
 +Disable bad-lines (by blanking the screen). ​ This prevents badlines
 +from interfering with the detection process.
 +         ;sid setup here!
 +         lda #$20
 +         sta $d40e
 +         sta $d40f
 +Set Oscillator 3's Frequency Control to $2020. ​ I just randomly chose
 +this value when experimenting,​ and it worked, so I kept it.  The trick
 +here is to set a value fast enough that the oscillator will make a
 +number of cycles (so we can get a good sample of the values coming
 +out) but not so fast that it might miss any of the "​bursts"​ I was
 +mentioning earlier.
 +         lda #%00110001
 +         sta $d412
 +Combine the triangle and sawtooth waveforms and start the ADSR cycle.
 +         ldx #0
 +         stx high
 +loop     lda $d41b
 +         cmp high
 +         bcc ahead
 +         sta high
 +ahead    dex
 +         bne loop
 +This loop takes 256 samples of Oscillator 3's output, saving the
 +highest value in location high.
 +         lda #%00110000
 +         sta $d412
 +Stop Oscillator 3.
 +         cli
 +         lda #27
 +         sta $d011
 +Turn the screen back on.
 +         lda high
 +         rts
 +high     .byte 0
 +Return from the routine with the highest value sampled from Oscillator
 +3 in the accumulator. ​ This allows you to branch based on the high
 +    bmi SID8580
 +    bpl SID6581
 +Pulse Width Modulation
 + The primary limitation of using the volume register is, of course,
 +that it is only 4-bits. ​ Pulse width modulation (PWM) allows us to get
 +around that limitation.
 + In general, there are lots of ways of transmitting information.
 +If you've ever used a radio you've encountered both amplitude modulation,
 +where the signal is encoded as the amplitude of some carrier wave, and
 +frequency modulation, where the signal is encoded by changing the frequency
 +of the carrier wave.  In both cases, the idea is to strip out the encoded
 +information and throw away the carrier.
 + Yet another possibility is pulse width modulation: use a pulse
 +wave at some carrier frequency, and modulate the pulse width. ​ Pulse width
 +modulation has several nice properties for transmitting signals; we can
 +take advantage of it to play digis.
 + Pulse waves, of course, take on only two possible values: zero and
 +one (low and high, etc.). ​ Over a single period, a pulse wave will in general
 +be low for some amount of time and then high for some amount of time.
 +The _duty cycle_ of a pulse wave is the amount of time it spends in the high
 +state compared to the total period. ​ For example, a square wave, which is
 +low exactly half the time and high the other half, has a duty cycle of 50%:
 +       ______ ______
 +       |    ​| ​   |    |
 +       |    |    |    |
 + _____| ​   |____| ​   |____ ...
 +Remember that, regarding SID, a signal like the above is simply a voltage
 +level. ​ What is the _average_ voltage over a single period? ​ Since a square
 +wave is zero half the time and one the other half the average value is
 +just 1/2.  If instead the pulse had a duty cycle of 75%, it would be low
 +for 1/4 the cycle and high for 3/4, giving an average value of 3/4.
 +So the _average_ value of a single pulse is simply the duty cycle. ​ So if
 +we change the duty cycle for each pulse we can essentially generate a
 +series of average voltage values -- and since a digi is nothing more than
 +a series of average signal values, we can use PWM to play a digi.
 +To make this more precise, let's say we had a digi sampled at 1KHz -- one
 +thousand samples per second. ​ Since each sample value will be approximated
 +by a pulse, we need one thousand pulses per second. ​ The duty cycle of
 +the first pulse will be the first sample value, the duty cycle of the
 +second pulse will be the second sample value, and so on.  Note that
 +the sample rate is the carrier frequency -- the frequency of the modulated
 +pulse train, 1KHz in this case.
 +(Actually, to be more accurate, we need _at least_ 1000 pulses per second --
 +for example, we could use 2000 pulses per second, and represent each sample
 +value using two pulses. ​ So the more correct statement is that the pulse
 +carrier frequency is the maximum sample playback frequency.).
 +The advantage for playing C64 digis is that we have much more resolution
 +for the pulse width, and probably not in the way you think! ​ Because you
 +are probably thinking that SID has this nice 12-bit pulse width that
 +we can use here.  The problem is that the absolute highest frequency SID
 +can produce, using the frequency registers, is about 4KHz, which would
 +be the maximum playback rate.
 +There'​s still another catch -- the carrier wave is still there! ​ Imagine
 +trying to encode a signal that was constant, say 1/2 everywhere. ​ To
 +generate a "​digi"​ value of 1/2, you'd use a square wave, half down and
 +half up.  So while the _average_ value of each pulse would be 1/2, the
 +actual signal would be a square wave at the carrier frequency (look at
 +the little picture above if you don't see it -- its average value is 1/2).
 +Trying to modulate a 4KHz carrier wave results in a piercing 4KHz tone,
 +and a _maximum_ sample rate of 4KHz (and this assumes that you can sync your
 +code up exactly with SID).  So that's pretty worthless for digis.
 + - BUT -
 +What if we could change the voltage level manually? ​ Let's say some
 +hypothetical machine language program toggled the voltage level
 +on each machine cycle -- the result would be a square wave of
 +frequency 0.5 _mega_ hertz. ​ Okay, let's say it changed the voltage
 +level every 10 machine cycles -- the result would be a carrier
 +frequency of around 50 KHz.  The point here is that a machine language
 +program can generate its own pulse waveform, and do so at much higher
 +frequencies than SID can produce.
 +Toggling the voltage levels turns out to be very simple. ​ As was
 +described earlier, the way to "​boost"​ digis on later SIDs is to use
 +a pulse waveform at frequency zero.  Depending on the value of the
 +pulse width register, SID will set the output voltage to either high
 +or low.  So all a program has to do is set up a pulse waveform at zero
 +frequency and use the pulse width registers to toggle the voltage --
 +set $d403 to either $00 or $ff to toggle low/​high. ​ (You could also use
 +$d418 to toggle low/hi, but this method should produce more uniform
 +results, and unlike $d418 can be filtered).
 +So now we're cooking -- we've got a program that can generate a pulse
 +train. ​ The next step is to change the width of each pulse to represent
 +the sample values in our digi.  Remember that the duty cycle -- the
 +percentage of time the pulse spends high -- is the average value for that
 +pulse. ​ But also remember that each digi sample represets an average
 +value over the sample period. ​ If the pulse period is equal to the sample
 +period, then _the duty cycle is exactly the sample value_!
 +Example: let's say that we have an 8-bit sampled digi, so that values go
 +from 0-255, and our program generates pulses with a period of 256 "​ticks"​.
 +Now pick a sample value, say 56.  All the program has to do is hold the
 +pulse high for 56 "​ticks",​ and low for the remaining 255-56 = 199 "​ticks",​
 +and it will have the correct average value: 56/​256. ​ So a program to play
 +8-bit samples might look like
 +1 - Load .X with next sample value
 +2 - Load .Y with 256-.X
 +3 - Set pulse high
 +4 - Loop for .X iterations (each loop iteration is one "​tick"​)
 +5 - Set pulse low
 +6 - Loop for .Y iterations
 +7 - Loop back to step 1
 +Let's say that each "​tick"​ takes m cycles, and the sample size is 2^n, so
 +that there are 2^n ticks per sample. ​ A stock machine runs at around
 +10^6 cycles/​second,​ so...
 + (10^6 cycles/​second) / (2^n ticks/​sample * m cycles/​tick)
 + = 10^6 cycles/​second / (m * 2^n cycles/​sample)
 + = 10^6 / (m * 2^n) samples/​second
 +So, for example, let's say we had n=6-bit samples -- 2^6 = 64 -- and could
 +generate pulses with a resolution of one machine cycle -- m=1.  Then
 +we could play that 6-bit sample at 10^6/64 = 15.6KHz. ​ That is _really
 +very good_! ​ In principle -- possibly using the CIA timers, possibly using
 +fixed delay loops, possibly using a massively unrolled loop -- this can
 +be done on a stock machine. ​ (I did try using the CIA timers, but the
 +number of cycles to set up the timers was too big, and made it sound poor;
 +I've included the code below though.)
 +At this point it becomes a numbers game.  As we increase the sample size
 +(increase m or n above), we _decrease_ the sampling rate -- if, in the
 +above example, we instead use 8-bit samples, the sampling frequency drops
 +by a factor of four to around 4 KHz.  So there'​s a tradeoff between
 +resolution and sampling frequency.
 +AND... we still have this issue of the carrier frequency. ​ You should be
 +able to convince yourself that the sampling frequency above is exactly
 +the carrier frequency. ​ So with the 8-bit resolution example there
 +would be an awful 4KHz tone running through the playback. ​ There are
 +only two ways to beat the carrier frequency: push it high enough that
 +you no longer hear it, or else push it high enough that you can use the
 +filters to dampen it down.
 +How high is high enough? ​ You can judge for yourself, but 15 KHz is
 +pretty tough to hear, unless you have good ears and the volume is really
 +loud -- so 6-bit samples are within reach on a stock machine.
 +But add a SuperCPU into the picture, and the numbers get _really_ nice.
 +Everyone knows that a SCPU can interact with the C64 at 1MHz, and
 +hence generate pulses with 1MHz resolution, using code like
 + lda #$ff
 + sta $d403 ;Set level high
 +:loop lda $d011 ;wait for C64 cycle
 + dex
 + bne :loop
 +where .X contains the sample value. ​ But what happens if we try to move
 +beyond that 1MHz?  What if we put some NOPs into the above delay loop,
 +in place of the lda $d011? ​ Well, in principle it means that the duty
 +cycles won't always be right, which corresponds to some sampling error.
 +In practice, however, it works _really well_! ​ Consider what happens when
 +the above code is changed to:
 + nop
 + nop
 + dex
 + bne :loop
 +The earlier formula still applies, but now using 20MHz cycles:
 + 20 * 10^6 / (m * 2^n) samples/​second
 +In this example each loop iteration -- each "​tick"​ -- is nine 20MHz cycles,
 +giving a playback rate of approximately 17Khz for 7-bit samples. ​ Which
 +And it can even be pushed to 8-bit samples (although I personally don't think
 +they sound any better, at least with the code I've tried; maybe the code can
 +be improved). ​ Using loops like
 +     dex
 +     beq :done
 +     dex
 +     beq :done
 +     ...
 +     dex
 +     bne :loop
 +it is possible to "​fine-tune"​ the loop tick to somewhere between 4-5 cycles,
 +giving a playback rate between 15KHz and 19KHz, for an 8-bit sample. ​ Pretty
 +cool.  The code is also a little more involved (with 7-bit samples we can
 +use BMI for the loop branches; not so with 8-bits). ​ But it really is
 +possible to play 8-bit samples at 19KHz on a C64 (plus SuperCPU).
 +Using two voices
 +You may be thinking, Hey, we've got three pulse waves to work with, can
 +we improve the performance by using multiple pulses?
 +Let's say we have two pulses, P1 and P2, with the same period. ​ When both
 +are activated, the pulses simply add together -- that is, the total voltage
 +is just the sum of the individual voltages, and therefore the _average_
 +voltage is the sum of the individual pulse averages:
 + avg voltage = D1 + D2
 +where D1 and D2 are the duty cycles of pulses P1 and P2.  In the simplest
 +case, this gives us an extra bit of resolution -- if D1 and D2 are both
 +7-bit values, say, then D1+D2 is an 8-bit value.
 +Consider, for a moment, what would happen if we were to change the amplitude
 +of the second pulse -- that is, let's say the maximum voltage it took on
 +was 1/16 of the maximum voltage of the first pulse. ​ The average voltage
 +would then be
 + avg = D1 + D2/16
 +This then gives us _four_ extra bits of resolution, with each bit to the
 +_right_ of the decimal place. ​ For example, if D1 and D2 are 4-bit numbers,
 +with D1=xxxx and D2=yyyy, then the avg will be a number like xxxx.yyyy
 +(four bits to the left of the decimal place and four to the right).
 +Of course, we can change the pulse amplitude by changing the sustain
 +setting, so in principle this gives a very easy and efficient way of
 +playing high-resolution digis. ​ In practice, I have not been able to
 +make it work very well.  I used a sustain setting of 1 and split an
 +8-bit sample into two 4-bit pulses; I believe the result sounds better
 +than 4-bits, but certainly doesn'​t sound anywhere near 8-bits. ​ My
 +suspicion is that it is because the second pulse voltage is not really
 +1/16 of the first pulse, which corresponds once again to adding noise
 +to the sample value.
 +To find out, we can just measure the output at different sustain levels.
 +The following table gives the voltage output for voice 1 using a pulse
 +waveform at zero frequency and volume 15:
 +    Pulse Width      Diff
 +SU  000    fff   000 fff
 +0f  6.34   ​5.29 ​  ​.08 .07
 +0e  6.26   ​5.36 ​  ​.02 .01
 +0d  6.24   ​5.37 ​  ​.06 .05
 +0c  6.18   ​5.42 ​  ​.03 .02
 +0b  6.15   ​5.44 ​  ​.05 .03
 +0a  6.10   ​5.47 ​  ​.03 .02
 +09  6.07   ​5.49 ​  ​.04 .02
 +08  6.03   ​5.51 ​  ​.03 .02
 +07  6.00   ​5.53 ​  ​.05 .03
 +06  5.95   ​5.56 ​  ​.03 .02
 +05  5.92   ​5.58 ​  ​.05 .02
 +04  5.87   ​5.60 ​  ​.04 .03
 +03  5.83   ​5.63 ​  ​.04 .02
 +02  5.79   ​5.65 ​  ​.06 .02
 +01  5.75   ​5.67 ​  ​.05 .02
 +00  5.70   5.69
 +Voice 2 is identical within a few hundredths of a volt.  If this test is
 +repeated using voices 1 and 2 simultaneously,​ the result is:
 +    Pulse Width
 +SU  000    fff
 +0f  7.30   5.25
 +0e  7.12   5.36
 +0d  7.09   5.37 (!)
 +0c  6.95   5.46
 +0b  6.88   5.49
 +0a  6.78   5.54
 +09  6.72   5.58
 +08  6.62   5.62
 +07  6.58   5.65
 +06  6.47   5.70
 +05  6.40   5.73
 +04  6.31   5.78
 +03  6.22   5.82
 +02  6.13   5.87
 +01  6.07   5.90
 +00  5.97   5.95
 +Note the weird step at $0d -- the response is definitely not linear!
 +Now, to summarize, when using one voice, the "​positive"​ amplitude (about the
 +mean 5.70V) is .64V and the "​negative"​ amplitude is .41V, giving a spread of
 +1.05V. ​ With two voices together, the amplitudes are 1.33V, 0.72V, and 2.05V
 +respectively. ​ If the two signals were simply added together, the numbers
 +should be 1.28V, 0.82V, and 2.1V.
 +What we originally wanted was a signal like
 + D1 + D2/16
 +that is, another pulse that is 1/16 the value of the '​full'​ pulse. ​ 1/16 of
 +the positive amplitude is .64V/16 = .04V, and 1/16 of the negative amplitude
 +is .41V/16 = .026V. ​ A setting of sustain level 1, on the other hand, gives
 +voltage offsets of 0.05 and 0.02, giving approximately
 + .64V / .05V = D1 / 12.8
 + .41V / .02V = D1 / 20.5
 +So, in summary, whereas I wanted D1 + D2/16, I was actually getting something
 +that varied from D2/12.8 to D2/20.5, even if the two voices summed together
 +There may still be a way to make all this work right, which would be great,
 +but I'm tired :).  The code from my attempts is below.
 +I also could not get two 7-bit pulses to sound like an 8-bit pulse. ​ I took
 +an 8-bit pulse and divided it in half, assiging each half to a pulse
 +(and giving the extra bit to pulse 2, if an extra bit was present).
 +I suspect that another issue is that it is impossible to update both
 +pulses simultaneously,​ meaning some delay between pulses, which translates
 +to adding -- surprise! -- noise to the signal. ​ Perhaps it would be
 +more effective at lower resolutions,​ however.
 +If someone has some success using these techniques I'd be interested in
 +hearing it.
 +SID lockups
 +Blindly applying these PWM algorithms has a way of locking up SID -- like,
 +locking him up hard.  To be honest, I don't have a good explanation for why
 +this happens, and I haven'​t yet found a good method of prevention -- toggling
 +the test bit, playing a real sound for a short time, toggling the gate bit,
 +and so on, just don't seem to "​initialize"​ SID reliably enough. ​ Sometimes the
 +code works, and sometimes it doesn'​t -- it's the same code both times. ​ Often
 +resetting the machine will make things work; I'm not sure what hardware resets
 +take place within SID, but the kernal certainly zeros him out so that's a
 +possibility. ​ The other observation is that playing a tune seems to 'clear
 +out' whatever is blocking SID.  So there _must_ be some kind of software
 +solution to the problem.
 +In the example code pressing RESTORE restarts the code, which will usually
 +clear the '​blockage'​ after a tap or two, if it happens.
 +If anyone has some thoughts on this issue (or even better, an explanation
 +of what is going on!) I'd love to hear them.
 +The Code
 +And that about sums stuff up, we think. ​ All that remains is a zipfile
 +containing code examples of the things we've discussed. ​ The archive
 +contents are:
 +digi-$1200.o ​ pwm-2cia.d.s ​ pwm-jmj ​      ​pwm8.d.s
 +digi-$1200.s ​ pwm-cia.f.s ​  ​pwm.d.s
 +pwm-$1200.s ​  ​pwm-cia.g.s ​  ​pwm2.c.s
 +The main example program is "​pwm-jmj",​ for the SuperCPU. ​ This file contains
 +code at $1000 and a sample at $1200 (the sample is a sample I made on my
 +Amiga years and years ago, of Jean Michel-Jarre;​ it's really not very clean,
 +but it still sounds pretty nifty). ​ So, to run it just load and SYS 4096.
 +You _need_ a SCPU to run it; it plays the sample at 7-bits and around 16KHz,
 +using pulse width modulation (pwm-$1200.s is the source code). ​ If you want
 +to compare it to a 4-bit $d418 digi, just load "​digi-$1200.o",​8,​1 and
 +sys 4096.  The other files are different code experiments -- using two
 +pulses, using the CIAs, 8-bit samples, and so on (all written in Sirius,
 +of course) and may provide ideas for anyone wanting to experiment further.
 +If you want to try your own sample with the code, I suggest converting it
 +to RAW format. ​ RAW format is just the digi, with no headers, and uses
 +signed 8-bit numbers. ​ It's what I converted the sample to, and what the
 +code is designed to play!
 +Okay, enough talk -- go listen to that cool 7-bit 16KHz JMJ digi!
 +begin 644
 +M4$L#! H    !  -IQQIT41>​R90 ​  &​L ​   ,    9&​EG:​2TD,​3(P,"​YO ""​D
 +M E!(7RH)A?​8-+,​0OU;​]&​%:​@Y;&​1!(H!&​%R0^:​(3!8B,​ U!H%"​%E XD,#$H,T
 +M(D -3ZL(T%)LA'​E*7Q) 2G(JX4A-E(=6'​95!TV=.'​[1TYO:​5VI<,#​+1P+Z$E
 +M@- ()A8F 0%02P,​$"​@ ​   8 _&​C'&​I6%<​YMA 0  <​@( ​  ​P ​  ​!D:​6=I+20Q
 +M,C P+G,,"​@$3 @,​$4TH%:​OO[6P8 ​ 1;W]_>W 0)4V5 E2.))CR8%610KU*)2
 +MDS8MZI2J3I#​0KS=O@,​W3F2L'&​1/​F2YDR7\*$ 7U#​T$^E'​@5),​B:,​YAM"?​9K4
 +M6H.U!GY1[<​H8$+^8=FA3*)FW":,​M,​!Y]*L7ASC%A8#​P.>​BB3GG<​E^57L 5!+
 +M P0*  0 !@#​Y:,<::​WXHL L" ​ !X P  "​P ​  '​!W;​2TD,​3(P,"​YS3@<​(K0/​]
 +M'​0,​="​ 5-!@@M!P,​$!0@&"​ <6" 8%*!T$!00% Q4& P<&! 4D"​!0%%@@&​!1@-
 +M&/​V-_OY^!/​[^"​!X(+@@.)P8>​!@X'​!@<>​!GX$_NX/​ 0(# @,"! ,T!AL%N_S\
 +M/ L  P(#​!P47!O?​W]V>​1(Z?​_]!?​HSN]WXOK3[=P9L\DO)BKVPS8Z;/​BWWDB5
 +M_%9P3A7U: B<​9J4AKS(-)IUS/​=Q'​O-X(/​ K^RJ7S:​.B4TO72>​=S,​DX_C"​ P=
 +MRJ;?​SR;​JF57<​EG+[\5@2(3>​A6-7#​7%!+ P0*  0 !@#​\:,<:​]D)"​*[$" ​ #P
 +M!@  #    '​!W;​2TR8VEA+F0N<​TD'"​*X#​_AX#'​@@&​3@<&&​ X'% 85%@<&​!P8%
 +M!Q@>! 44 P0&​!0,'"​ 0%) @4 P87!@4^"/​YN__^?​!?​__.#​\8#​P@''​P8?​%Q\'​
 +M?​P7_[PXB(Q(#​)0P%!PP$!NS]W04+$003! 8#​1F<&​]_>​G4:​-&​^5%>​H"?​NO!O3
 +MW/​KD;​K?​C7W3(,​!_V0@<​4\%LB)T5>​%\X-E3_5"​W?​KZK3=BZUN]7;​^OL "":​)Z
 +M%UA+D*SAZ1L-4R:​549+;,​W $+BWI8G1L7UV37B)0WK*8]Y+P+887U!RAZ=NP
 +MY=?​[=;​GS_[[51EZ15:​))7B$I[_U/​X/​MT /'"#>​JYI'​[Z&​2DSEKW,​U)]T,​64A
 +M,​*S:,​0ANR^DB3&​C^Q.79] ?​XQ1=E&​GP6HE;​O"​2LMY?>​C&​I;​SCB*'&&​2#​L[6O
 +M'​(;​0#,​\9@Z*@U,​7L!RUNID1R,​TU:​(A;​@M"​C\][I?​%I,​54$L#​! H !  & /=H
 +MQQKFO<<​O:​P( ​ &​D% ​  ​+ ​   <'​=M+6-I82YF+G-'​![T#​_1T#'​0@&​318-"​ T'
 +M%!8%%@<&​%P4H'​00%! 4#​!!4#​!P@$!00#​! @%% 4F!3T(_3W^_LX%_OY(+A@.
 +M" <>​!AX7'​@=^!/​[N$0$#​ @,"​$P('​)28,​!@3\#/​V]!@L ​ P<​3!0,​G%O?​W]S>​1
 +MZ:​$TOY#​!KRNYV][FQ>​XSW;?​\=-O,​+[547:​L?​V:​H\K9?​2HV_ OFG69:​XKX;​)M
 +M5AE'​MUAT&​L[7\GC8<​REY ?​-8MDGHS2#​9,​Y2B)5\B_-_O-M-/'​!XGU16Z2^$4
 +M?​7^:​4MW"​Y?​X^C+ K)4%<​+'​G*=SR.!JZ-<​(N;>​]+*=H-<;​[ /​RYAP[BL9^T'#​
 +M9DA$-H-)$Z',​8$;​A'​W1/​I-(44$L#​! H !  & /​=HQQJHW6A*AP( ​ .0%   +
 +M    <'​=M+6-I82YG+G-&​![T#​_1T#'​0@&​30<&#​0@-!Q06!387!"​@=! 4$!0,$
 +M%0,'"​ 0%! ,%" 44!28%/​0C]/?​[^S@7^_D@N&​ X(!QX&'​A<>​!WX$_NX0 0,"
 +M P(3 C4'​%@P5_ S]O08, ​ ,' P8"​%C<&​]_?​W)Y$C1_J17A"​G=-Z-SM_<​*=RN
 +M"?​^B0H7]L!HFD$#?​$C$QXKIP-G3QM"​_VYMJSNY=M;​_N:​XGU!!A9T^X:​= SXA
 +M8N%E=&​)%*JP)AA49/​\HP)<​XPG@CET^ W7%WJ.2K;​MU?​!-MT8;​Q9S+W$?​SX<​1
 +M?​.?​['​6,​57XXP*S( >​5YU=B*EXWV#​8JV&​%:​Q,"?​7<&#​CG7E!OW-FN$3*_FZE_
 +M>​8D4R-$MYNG8GZ\UU.^IG+* #<​DV&;​T9)'​OZ<​C2$KQ'​^[W:;​Z48_YR<​8EXG4
 +M G,​RILX>​=BF>​4O'​+5,​%:"​!>​UR&;​.2_6-F;&',​\VJT5(6J6?​A<​\V#​2EM87TJN
 +MB0MC%"​57!#​R?;​GQ@VOXTI5JY7.GN W#​CZF2(BB6/​A3R)HX%KH]QB93YY9:​M!
 +MJC?​8!V1(./​=E#/​V@H=(GGDIOTD2HT#​D8_+WN24JJ E!+ P0*  ( !@ *:<<:
 +MTV5I'​GL[ ​  "​4 ​  ​!P ​  '​!W;​2UJ;​6H< ​ $" P0%!QD'"​0LN"​QX?"​P\+7PD+
 +M"​0LO"​P\*_X\<​ @,"​)*4$!01%!@<&​!R8'​%@=F%P9'​!@<​F!08%)A<​!0DR-L#​%!
 +MZJGQ-L8X,#​7$QAD('​V!A_:​DE%MYWN/​D-?​F. U!L'​$/​P"​H/​^_L0'"#​Z(Z/​U!]
 +MZGA;​1X2>​ O$(_[3^I("​5JD[Z%S5\!:​);​('​7UZA%#​VUV)8^40]YO7#​[V[^?​VE
 +M]R<'"​[VZ>​Y#​0:​G=""​+4;​*].'​$%L'​Y YS,​)?​_'​PB2_'​]XH.\4NF/​.HB."​]!C1
 +MM42(OR[M3X(_$#​W[BA ARV_L%WIJ+HA^6O4..&​W4%D#​X QL/​G-[X+W3A(%,​W
 +M!/​@(]0D^%C"​X"​0QN X,;​P>​!6,​+@9#&​X'​@V=[MV\X9^H A9^H\!\4/​N.<​XF&​F
 +M)M@8<<​[&​BP3?<​4[L./>​+_J\9Y.C^("​L(_M9_F;/​ U#​8+!\0*<​GU$IKMS8MPO
 +M'​*)XD**&;​])#​F+1:/​7KP *%,>​C"​H-M>>​$0<​N795[L08/​B+4!Q!IN31$BTM5,​
 +M(T(@7>​L/​$O+-R5.S3@5)DNA,&​(Q/​]2E(J45;​%G5*M:​A4@*EOF#​]G@ B&/4CI
 +M_T'​^'​@]R*CG$J_Y)U8-Q&​*?​BYTJ7-,#​ K3 ;?​DKA,​%L_MM@86?​2#;​!WI\)5'​
 +M&​3K#​U@I^F^;​ U@L*O^9 L50K7OSHT"​-#​BP\K-ESIL^G3D /​9OV>​_GKSX\.'​3
 +M?​R]?'​OWY]^C?​V[^_?​_]^@/​L#​_L]'​^ 4"​OZ7,​B_/?​I]<​NFC;​V]?;​9WY=?​O___
 +M@@L3/​HR8<​6)'​C1Y-;​DS9,>​3'​D"​ [G@1:​=FO*A?​7GMZ\__ORWP!]7^.G'​QR]/​
 +MXL2-%"​]:​K#​@18T2,​%F=V]2I3X;​[<​J__WY>>?​5W\>​_;​C G3[]^_;​RU=>​7_QY_
 +M_7^:'​F#"​B"​QI+@PHGSSPX>​-6?;​])7S\@__T&​^R<,​N- @PH<​)+S:​D6)'​BQHH;​
 +M/?​[V[^<​);​G:​)/​[]>​N)\O?​WT\X8YW^//​GR\\??​R!&​C?​OG=Q^F?/;​NTF5_0 '@
 +M'​N'"​A0\;/​GP0^ R:​M\2&​$2<​NC+BR:​M"​-_.WK&​]5N?​]VE)[]^O/​MTQ-?​N[45?​
 +M<​./​KS_TT2[\%/​MRX$/​]WY,​6SF3\]X,​Z':​1]@[8@50+L":​KX [B=$2IG^X,:>​
 +M![[S^<​(#'​FQX\&​%!A@\V_4**#​R\P?​X5V[_'​G4YX@%28D>​ "​\\UW[=Z?​U*),​C
 +M/​$T1XJMC]_5T[SX;​-,​BP [U^L+WY^0$1*DCM@@4##​A!W@X=XD+S _P0+(DSH
 +M\(!J5E@19<​^<​(2<​2R/​S^ 0Y;'​JU4*\^_QRB.)U^>​SMQIZ_@^W7Y9^_X,​%>​('​
 +M7UW\N]0*W@83+#​K.0O05*SCZ"​+$U+A<​4\/​[\^Q^\>​%*E1P6,​[/​WX M=A[]>'​
 +M[??​5ZO?​G/​2]?/​=_R#>​KSW;​7Z_ D9&​EP?​GARXB_]^MW]@>​3'?​M?​R 6,-/XA+S
 +M-5^C3R#​+B^SX4$#​OG3?​[=(:​B0U&​AV%[QHOT RC[A ?​F)M?​E]\P779\?​NK=Z)
 +M#​U%<​WUZSDVR!!:​+=>​ <,​D"​P_]"​]O\J"​A]0VNY*O\ 8=> .8RW 3H[8(%"​O6,​
 +M(Y:​_Y,​8(MQT@TZEV/​$@977XZ95['?​\V/'​[^^><​_/​[=L M#\ >​LJ0D"​=M^5_@
 +MM?+^ ?​[KCS2E64[<​8<​L:​02R_R0^W/​ %A';​]]^?':​(\'​3E,​!!KZS62LWR.&​5Z
 +M^+(_V ^H;​[CY$1_B>?,​0G+T$^%@AP@.D)=_[-I5B/​8'​@EDA1(@/​4]_<​KXI?​_
 +MQ@MW^4S$\(,​ ];'"​Y3';​\P+'​] A]OU#​SOU4390=8-1*UZ6GK^I*O/​45.BZ#​Q
 +M\UV.[;​6+MIVU9[#​9"​P)?​H8+ 3V"​])F7__I9GA0W639=K<​M6Z9O<​*5#​SJW?<​N
 +M5='​6?​\H&​^UM4JK>​2]P%H"​]1@@P:​(XX ![+XLUZY%FRX7\C1KX[M6NG@2S?​Y,​
 +MXX5ULI0E:​E.C);​LZ8X,​$ 80"<​MP#​YD>​H(>​G-?​_\^TA&​[^.%$"​CG/​$2H!<​=H#​
 +MJ(H7*IYKC^#​C^?/​@HW3C?​[DK\^\1 PM(C0\,​*QD*&​P"​U,​K>​^\5E KR%AZ(4+
 +M4@XS3-"​YX%<​MX'​S.8_KT5#;?​M\<​XNN7_Q4,​V$+B:​!?​F/​KH$-9 N)+T(E::;!
 +MF%^O+B\>​BE]1K%!WR%A@?:​\F4IM_/​XZ/​5#​U ​ 800*4*Z/​D]A!=E*C$^0(('​+
 +M+4 [&​K24-[["​T?/​S\[9"​A?​-YCVYS@*S/​ 0*GWX1O18L%#​+/​4!50M-+CJ<​P!Z
 +ME;​*1R#​\>​)ZO0[$\0@)B^"​1#​.!M(9S=K\^?​E&​S%&​]G0 ;P \&IT]Q ]14.ED:
 +M;​0PU7<​U#​SH<;​T9[.JZA?​ )/Y0/V2MQ*] JHC9<​WC4L"​E;​T8"​CQ6DFX29#​0@+
 +M#​1OVA\C4<​+3]]NW;​\U_'​0/​LJU51N!] V_E?,​J!KX@*%B!-5 M"​Q(T;>​E.NMC
 +MAXZ'​RF0W[@X8,​.#,​UY[N] ,​VQ#​CBF:​YY<​V?/​H$./​)EW:​U.E3ITV7(B4*=.=-
 +M5&?/​FRE9GCAKA?​T?/​)QH>​F;​CZM:​O-V^N7/​CMW:​E9ER9]>?/​DQ(T!!QJW 04.
 +MP <​GRO6F6[NVG;​OW<​.+'​ES>?​OIT[N*"​UN+@M@]\W">​0U=<​Z7-F4T&​X<​UC"​]R
 +MZM&​-)_?​]NW9MV*U7DSZEJV>​V7-GQXT6!CSCQX$*MZ35 "​B9\&''​DQYDO96NI
 +M G=<​.8KO]@WDLTV;​ENP9RQINKUE@I5CYZ1-M?​LG@;​F*XD2(A;:​% ?_J Q'OB
 +M;​AJA4S)E\N0:?​US@"​*[*;​RIK=[ ]KQXHZ1O?​V'​. K"​N"'​L$BJQ2PG"'​P-0@N
 +M/W[<6. 7+Q8X4./&​C15.L+^<​N3/​8KWX]NW;​MVK?​!MC?​7A-KERJK3IU?​7,​MW.
 +MJ'​E=G-7B,>​YU6:​X03ZJII8Z/​YO* @TX;"​V%=35Q'​R,​*F",​(I[A"​X.005X7:​(
 +MN9!%H>​$1=?​8;​1M--\H1V0/&​H1D)\R0$!,​)J&​FA^$588@7"​E3,​)OS 21J<!3W
 +MXXI_YZX-LG +,​V?/​%CAB]]T:​7H8B=??​YYP('​C<​KBQ7T*D_NW:​O<​X>​!A["​0<​5
 +MH9I& ],&"​!87($GHJ9!1(0O7%2T(%\X+[MG$N\/​-(@87W%\C!NS&​(/​6DR(-@
 +M/​+RGP6S!H+I"​X IQ]+K&​P21+EBP!.U+\D2-D%VI;​I&#,​II :​*ZCI"#​4<,​$!$
 +M)-7 #​^*J4!M!*J&​4B0'​ZXRI4]7,​7*C;​MZ^IV8W(E4>​+&​[P(7TDAIB-QT*37.
 +MV<​C/​O5\S(KDFK9(^1[$G,​(O2S?​[N.NDFM$%J#​7$B60S1/​6V84R!R. .EJ+%F
 +MLB/​X.(+(HK&​U^@+Z'&​$DZCUMUMJP +V*+(<​!?​!2!NK:​N@.Y_M89<​@;​S>​\N.-
 +M$\+)%)"​8OCT_#​*[GC()W0QD+ (AR:/​1N$997462[^Q365VWNOR^H %/W]_(*
 +M!>​@.XO]TB9"​*'​50T#​WA)9N"​2D)S+#​1J#​2(5^&​LFB$!K?​QP=4D2IK+ JJX ,6
 +M ]&​WV#​1JA@YTB@_@7<​.L:<​\_X#​R)<​SMPI^_E[XLR/​3)I2?​WCN8<​OO[UY<>​71
 +M8B^ 1FIU5K;​KV?​OVM'​NC\U3]K3^>?​%DS)\^>​.W7BI"​E#​Z7NE"​4V8PVJ;​7M"​[
 +M,​=:<:​I."​O%T P?​0_:/&​]$+WUL[)^)Q)Y??​Z0#​\>​G B;&​73SY]._H4OV!.'​L1
 +MWKBUK%$R-$$&​EWYGB>​Z>​(*=^L@7 #​9<​*_"​808[)R^Q C(/<​(_IV2\=Z[9\FF
 +M37)JZ !)TY Q<​E!Z-FD2)$@0(V\1.?​.7,​MSZ+N[61:​U.+4K#​ZA1)HRS1]<​AD
 +M7X(>​HWF+]$"​-R5F*>​HKDBLX$ N7^%5/​.W-$U:​_93JU[5FD42>​\3)9.);​LX"​5
 +MTK40<​=2G:​K7 '​_V9<​Z;​3O/​CPX<​.%!ST&​U-#​C)XK\PH4'​$Q+\0M\(V$",​GSB1
 +MZEX<​F7+GU+:​44L []&​C2IF@I2\JC]C'​N%98&​7AP:​Q#​T\]6:?​8M_%B:​4,​\MIG
 +M2LH:​LYB3V"​1M3&​Z),​^A2)V"​ERQ9[UHQ2B;#​9JE6;​ELUB'​ <​8IUM7CC$FN.O>​
 +MY;,​J[%>/​XL^-$[CGOFWS*H65L5ZUFF4B_[7J4Z- >​2!4N4*5IE/​B'​1>#:​[XH
 +M1O%_A[OM#​)]*[O !,​99"​MDC=A'​I[<​GVE9DR>​CS82)UYLW/?​6O:​L2,​\1;​S;​-9
 +MZ_MRKY;​$3G^C&/​@</​*%M(HI,​5[!<​+HU8R.KR ;8E&94 0?​62M#​TB\/​XZS2:​\
 +ME96$L2"​G2J*Q0DHZ+URP@M:​6*3H4*)6D2A0E Z[.9(<,Q PTL.:'​-?​WN85*X
 +M]#(,<$ NX@?​$)DJD2J3$E_!LO0N]NT>​1O7 )X008]K"​-M!1] G+"​N!4XLQ@&​
 +ME[A<​V2CTB!Z)=(HF@JS'​7B#​EJ,​FP B<-,5/.I 95-IJ&#​-!?​02I1'#​)$'​9WH
 +M$T"​1X80(UF0,​( =%9FB(7HU.*GE=X*H0L/​U[I@0,​\?​DYB>​[G)%$JD1U*L'?#​
 +M1^5Z4#​HM+Q>​43JJ*-/​0BR MEA VF4+Q@D/ !)YWF!+1M\9B*C3%4([HMIN*0
 +M'​.(2]16Y<&​E(6AOR)?​(=5:,​5J++FTSFD:​1&​*3#​Y(A&​\Y1$VI>​$ P$3A/0!0)
 +MJ3*_*9D)Y&​D(&​G7H;​K4QN&​HG %'​$*N,​[L>​]'​)Z#​!18ATE>​A,​@_,​Q6RT(I#​L#​
 +M^$,2OZH_ X!KOB?​!*Z"​JK]O].E?"​Z'#​_'​%&​TN(:​^-\ZRY*SZ[7!13%A)H[*3
 +M F?TZN?OH]-D EL@/​YC7@&​1_/​IR$T?​TTD C7(M__\0'​!=8?​4L@<​@:​33I^P\(
 +M$Z!\87\E*LZW!5>​A2V"?:"​$T0K,​D-S4.=4E"​+299VTGYF++UZ]?​G)UE @X@'
 +M'&​3L@$I!I4)"​CONO0Z3GJ2]FQA GD!U1 @U\KEF6: <;​QDZEB@RP22*9MT=?​
 +M#​AXF-E*[+,​_,​10@8 1B_#​%HQ7CA$XO#<​A%]O\P2TB^O[\!]48 Y0,​]"​GOQBR
 +MDM\)*RJ'<​U\>​9J.K .J!#​7[@DB2/​_$!:​35,​CW"&#​MAW"​!OGC4AGA;​S$@^&​L;​
 +M3'\C G9M-K<?​]OGI8-!>​A_<​_[VZ,​V^#​*H!W;:​XMCS\<'​NOCEW>​1*N %6#'"​5
 +M9W@>​8"/,​.//#​E?​(\66!V1*WO'​QB_X/​]S>?​F-!3OR&​VV '/​59^Y(9$VI4J/​+E
 +MSY4),​38%&​D%7]W\?​_?;​TU=>​IPXYM\>​J]UTNI<;​8^/​LT#​\XU*Q73#​ (P_3_&'​
 +MSYY^[Y&​EK:​+\ ^1K5/​Z'​K14$6_#​7&​VHD*@-27OK3 P@^ LQ^3T]%I$47N#​ =
 +M(RM9)L II?​@"​7^/​)"​*'​=3DIU\-/​1QTDW/​EOX @&​DMO($(.$0JL"​J[9 8.T[T
 +MMNE,L \PE"​L\#​!D$55#"​D.,&​V>​2^Y8DI>​W#​LG7!0H8&​RUW8U",​GGG"​\HK\Z)
 +M]+B6'​P=F?#​0@M3!$)?​XPP #​(\9'​OKO[>​GS\;​P9+##​+*EOR2C70*:​3+8?​8CC)
 +MSS0Z\^*^LN=S88N*O]S*<​[?​E1U>​OA**H"​JDS:​HHQ&​O :​KNU75[Z]'​@$M-=<'​
 +ML-R*H?TY/ D\)8/​R6&'"/​[:​L*51F!<#​J>​4URYKP\ATA&​)KTA1'​Q=A.U9/​6G+
 +MWYN)G[\_@.3;​P-VG- XJ *,​I$#​=8UB\0ZSU#&​IC9PIW0= (84HWNCDC-]O,​!
 +M63B"​@I:"​KY@'​%PDS.-U=#:​IV3.)XZH#​0"​8-RM$9]!ZJ^2 5)5SBV[#​A4IJ'​@
 +MTW&​\BN5\28$HG:'​*6JR/"​BU;#​=E56OJ^?​3WR$1#​M?​D^JP&#​0!T%F&​KDQFS 2
 +MX_,:​8.B&​)G7NVQP_7HW![:​@60F+)M79!9_;​UI'​CG.V"​A)A ;<!E\P O,#\@)
 +MB7Z'​(](-VC#​.MT'​=CF$HE;​ [&​IT.=/​I5YYNV<&​_**"​R7=TK;​E'​8$HU&​RDHC&​
 +M!/B@J I,​AMJV](1-@*^(=/​UXJD$X( UJ 8.X=$D<​M56-:​1*%(>​P3^J-"​0YQ,​
 +MHFD.(AZ* =B!R8U%G&​ *"​C>​47VOKFZ'​A_/​D6NOGGFU:​$612=:​D!$#​%JR?​EAP
 +MOST 3PJ3\!C(2>,​!:​QWU#​ 7J!MX777E"​=>​_[:​% :​B/:​Z[8DP2,​(@) J1[4XO
 +M#_+^>8 $X1DL -$\@(.C"​M,​1&​RP%^B\J6+52S7\ ?​L*=]4%1+*]/​@^7=<​9HR
 +MC'​_?​44<​@I/​=Y3C6<​_R6@\( 2#​G40&​C-0S%<​_IS&;​JR&​]^TGH*).EHF)V!X%:​
 +M4*![V(Y$0F@IH"​E@X +1@DYDA,​J@KJ%IK2I0GH?​Z!O?​OE\?​1-7#​\>​30TM,/​R
 +MO]?​IUR?​XH%*@H&​B !ZPQT'​L4!;​)_?​^(+3L8,​E+ "​5P[P1"​!7EA+K>​X*3.XM,​
 +MHPD9?​1'​BHUA__W]O#​)22\($1!!/​H8F$#&​P'​*(.$"<​@P%D(PO!P',​3F( &​3A"​
 +M8>​%Y"​@%J&​RH($AF06:;​IHV%D0R,​3 OB_'​1H<​[SX0'​$-YFBHT9"​EJM;​XT.1*Q
 +M,​W$RW='​XX("​F"​*502(:​ )*U9LKW4"​$3A>​0\*<​E"​(= 9@I^( CL@ #@120A!#
 +MQ0B D)3*8D7AB!8H .:# 1@;​UZ]?​CSY4;​J2O%;​(J50<​$@2(D:'​*=#​QZPR?​C@
 +M@#​(:​*I'​YWR9 &​Q43.)+0?>​)HHGC*5/​\K7VL/​]QM24"​R3QM<​EF4 NY "Q$53U
 +MOUGE =.S/​Q.37A/​(M8$$GLQ'​8L4 QB@3.#​2$05(DP/​[&​E9Y(*B+IB(0N,&​0E
 +MTC.K'​4ZO$5*NJA! J8SOXRN0D:​\0\\0,​]7&​5(J'​+3<#​F$CD+-C>​XR/​C :PK 
 +M*:​(>​0#​6H"​4*1:​H0%Y!%%+$\XL/​ %I"​RAJ,​U9XE.N)P72ZP1\#​P+I@RH?​MB/​!
 +M2X]T@Y6;​(NCQ)81M4VR/​_/​Q%JEYEE/​\&"​NN-$PQ,​HCC-*6Y8 4QP@X<​KD3*%
 +MU>​OT"#​*%"​= 0(OX#​PDM)DJ]9G\1P1YXA \:#​09"#​+J:​\_HT*VX-+BV^_=F!Y
 +M2W^+$/​M?​%S_PHLRE/​55^N/​HKG% ",​8:​Z%^AOO'​!8-V*%OW!_^@ C_P=(U^T+
 +MZN*OG7:​V)LS2DV./;​FVG?​FT@MXI_@Q!4<'​9?​CDU,​X NB'​S#​CPI +-106-E[R
 +M8D<​+:"​L2H7<​LLC(]1@;​!NX#?​[+XI@ERZ,​\7DW:​F"​] ?​P=JG7W!$"​EUA)#&​F#​
 +M$OZU)N(!\ZI[_6 _#​Z0)$$J6?​E\^"​VY<​C]%I\A]S=I1(OQ_+F;​XZE_$[6;​I^
 +MP0E!>​7$4SM8N4$_CD ?​_3?'​.3)F@=K^.;​="​!N[MA[?​=..;<​GB7L$\U5^JG5S
 +M$L<"​L"​K8+;​Z'​4)=DZVM_+T+\G^.'​L.NW%&​GA\ \HFB"​MV8F2D+P\@,​I WARA
 +MQ% WD#​AA\7G$Z\=#​Z*3HL,​D)/​8)\Z$^-IU=9O;​MC+?#​YM3G7Z\2[^93+C:​.L
 +M>​@V]HK@Z!*Q@7192>​Z*2M7W>'​\V]0"​U VAQ=;​X)H^PXOF+MTM?​P&​7)(&​8XH&​
 +M!O8 $EVX#​$+'​.D07)!6&​0+M"​-ZEFDS_\'​P"<​*)J0^?​K]"​BZA0'​G_@)!G?​BY'​
 +MVE#C8/P &?​S$&​_JO]_24V.R,​!DESLO+_=U2NP.7MVKM=<​5F 5()N6%MQHC/​-
 +M#@WN "​4GK9$:​2*'<​757&​-X%S%#​.L-Z:​0#​(FJ21IFZ?​U%P$P +MO!/​A=:​LOX ​
 +M1Q-K=90<'​ T(CF#.6AKB F-_2)WO!8X("​87Z 15#​SJ?​4LJ;​WTW^78T!PUN/​-
 +M7;​($N([X(*JR25PU@_V#​ TCUP _2+$4XDO@&"​. 9@<​4<​0<​DJN%7=Z:​$=^C@"​
 +M^A;​@L.4#​5[!WB ​ "​BMZJY'​2:​[/​GW@$U)3>,​JIBT0D'​A++=<​C-H'​A>​50'#​S2 ​
 +M\+Z(2B0&​8FA(AR4-S0:​4P(347"​[$(+F4;​N1E42#​K6B6%(268D 1E'>​E=2COT
 +M,#B >​TB,​Q!$P&&"​3:​\Z4P)76K>​KY08B[!R992HP_0$'​Z<​]OF8E4-@W1'​)$\#​
 +M(/​SO1N=ZUL3ZUB8QJUH&&​^BR!#;​(IYG>​@DWXA/​ JTSM-'​3DE1M6B%5U7C?​W%
 +M1V>'​[2TM:​B8#​H##​%H !S4$2B<​((%@S1 B!\E+[:​@8X<​FY:​!?​@<​3PP'​YD#​5*K
 +ME<​34Q-1#"​6F?​4,&​*#​+R!48$68;"​B?​V)_4A,​3F,​LT ^,​)3,​$*=C(=J""​I[H$"​
 +M5@SD3"​36^,#​= NTP[/​]?​YA!%MWQYJ(&​4$IJ=\[FH%"​)*-5VN>​Z5D(PV&&​HI^
 +M4$R0(,​$"​%!.R:​GR U% 9C30:​^2'​)("​!NY@*A:​Y- ED:​WQZMUJ(YWT#"​_[!C!
 +MBH(+S$Q8P)>​._ IY&​1!<​0,&​Q/​@F/​W;>​KHZ!^%J^1\1IQE:​63,​G3#​!CN%#​Z@%
 +M)&​H2Z(AP"​QP@:​N0FP@]F OZGYM#​E4.5RYJ@JA3+KNV.06*--0GP#​R9#​(4ID_
 +M4 FO:​)@+Q@(%Y)(HJK"​ZA)6,​+MW@R)J0'​4Q)&​M9'/"​2F*!*A#​9(J(A5AN5X&​
 +M@1Q0Y.<​@J$:​Y)FG%ZBHG<​+"​_360$M\0+$Z"​(P/​2G-Y?​ STG5<​NR@,​C%G! @)
 +MY*8((K/​\>​1$9W \;D39B @APPG*AAHKY[UN3T;​((5 @CT=&'​)"​%X@5ZC7;​1^
 +M'​L&'​ Y@A@2LL%]9*IRWH[3>​H/,​4@[*'​K[#&​J4Y$'​BC(.@ EA  ]#'​.=S%+(,​
 +MQ.[&​[E1LG3Q$DV@RMS0*LA\1VNF"​X/​XV0\D;​([I&​UYG X!S_X<​T0#:​T'​5!U8
 +MG#​9RK++X48#​5<​69X##​ =+N?;​AJ1(%+WP4-DP'​.T<​C,​_9AUQ9\^O?​MTXF. .Y
 +M [4%WN5*E"​0>​@B5LA0CV^8IZR*Y"/​63#​]-:​E68![ *L(-O'>​MEN;​[FPY<​N/&​
 +MC 3?,>​3(D1\_CBQY$N%W?​NSX\>​3%G0U'​ODRY<​F7/​BC-CSFP(_#/​W8BI/​4VBW
 +M(U9'​6#​$K&​27C1HK"​*XOB]*D4*=DE4>​$W,​LQYQ^;​%(^8!])_ @<​_]0P&​Z[%:​G
 +M3Y>​Z['​G2I<​D7(52'​!3&​^3S >​38^G7_PW[MBC.X^./#​FQ9$,​8P8>​D/​( %%#A 
 +M>"&&​H*RILI I^9(:​]&​YW-RKE!Q>"​6.![&//​.X*AZF\*33]"&​7N>​+*FB7QS=V
 +MD%1%2'​GQ;​IA1 JH>​\F()Z_Y ^!5%7B@0(OT.%]PO<​\;​L>​3-HC1ANG4%]-/​Y(
 +MR9@1/​R[4XA^,​B'​ ?​%CRH- ​ Q6M%Y4)E'​\GKERY]+[P:​.G+AN8 ^X51V%3Y=^
 +M7=K555BV[UPK8>#​):#​.TE7AW:​]@8)K73\H8UB3&​IMZ.)#​I\N D$7DLM)-?ES
 +MVI3ITB80\*UBW=I5:​]0L?​L1NY]ZUB_=OGM+T*P !\J:​ER^YK=R]>​O'​OZ_&​_?​
 +M+U_NC-ES)458/​M]9-*DK7['​L^M7J<​O+E'​I!W;​U](_MV[<​D_V B_W:==2\RCS
 +M=^_.M>;​Q;​E\>​^,​(RF.2<​D3,​3H"​B,​BXR[VA4I%?​QG0F%DED?​XRI\_N[P\;​ E?
 +M'​R^D1B]+3%]C!MFT@I-L*V!(KD##​B;​9&​0AVABV >​87Z=.C!"​N!&​8'​^C/​GS_*
 +M4R5,​8:​)#​WGQ+X9LXRH)QU/​EJ$*]4T9?​N /​=[ZU+C':​!)(K?​3FIS8EE\DF'<​\
 +M;​=$A"​KC%EM>:​ -#​1#​[PDIZ1;#​^\(&​ND- 8Y'​MEBY4O.7J,"​_ 0)^Z,Z7*IJ@
 +MDW11Q??​C@_-!+GNYZ12V'​V(W <​%WFM0((!^UZ1'"​VVRD/​VV<​=X]>​L8W*^&​7-
 +M'​!.X$.#​=W&:​_98O1,​GR6*4=V #​G\P!():​.*%"​R% _/​8LK5F<​OY[#​0EH:​Z0S+
 +MM2XUF4:?​CH3D<​T DGS^?​3HG#​\^?​92- ​ 2EJR[A8S "​CX052D-4]*!K900Z"​O
 +M%T$0Q=)ON8+T)<​7J=_[N!7?​*E"​I9N.#​Z(Y;​A1U2Y+!<​ ,​I;​Y"/​-*N7?​@6>​J!
 +M4F/​9,#​(AW;​%C!PP<,​U!I:​ ?​)"​=8\&​6424QA 18<​61WPZV22^-].!^Q,​1N%_A
 +M4!&​6HKJL,​G25)34)"​DYG'​AKQJ,​1%)DB6IV(Y?​OQX 1W481+M<​A@J<​PIC0O.A
 +M\)Q@+V/​__XY$-+'​[L<​^P@>,​.@:​.4\\</​=01CQ(CACA *U!<O? YOU& 5T'TH
 +M$C4CRJE"​%++P,​FD-$D1X@)='?#​RAB* ;%70G(G#D $0)[I? @V 4^E-1$I86
 +M64VR]D10L9$!$@\D4"/ ​ B82R [#​\_0R0G)A>​Y.#​KJ,​^*&​5&​N)WNF% 3A+9C
 +M=Z1!M",&​ 0JID*>​9P(@$&​_@TPB)A 3X2#, CL0#'​0?​)H?​RR.[R5(B-[T@T'​5
 +M5>?​RA!>'?​@CZBN $<A2P QP."​.!+0@9(5,​*3L5=O/​7O[?​_1;​H8/​RQ"​\&​R?​3I
 +M'//​]_@$Z,,​B%]- L=$IZ/​$Z04GSP8E2?​$6H@U^OPF'​T[PD"​!W@ '^ %ZM-]9
 +MCKI87K^^?​.O/​S^--':​%Y5^BS'​2+P1_X4._0#​O=I!B$6#​K7%I3 1B/-=;R1 6
 +M##​W;​_56V+"​ ":​K[#<​XL&​6T\F,​G@X=M.MVV-(QO+8GV+].A9MV[=SZ];​IZ SV
 +M:​M2D0XK.-%W$@$Z[M.,​ EC,​]%5I2,​EJ3/​C[\]RZK=7/​VS5-;​EAS9\>​+#​A0DM
 +M=*"​7A)HE6]X\673MS;​I7A:​RM]Z?​^/#​9S9G?​7U<​U/​[Y]QY+P[M!/​ E+2D?)3T
 +MWDR%3(RS"​-)W*@FP?​P('?​!YE!/​!_ N>​]=M\4[JQ$9M8K//​X7)=R ​ ]3>ZN\R
 +M#@9417F] 9Z<​H%PB)C^S9U A:&​NC1S#​*<​[T64<​1[_I@ZGMET[-08:​^L1@AND
 +M7XHLZ&​[8G2@AS Z2AP/​C5;​].7?​ISI[6\9]JW.2=:#>"​ =#​%P!2[X]*C7@0<​F
 +MSJ@DZ3E2D'​ UL-\H\R6'​%M0(?&​BL$$$ &​B(%@J,,​LJ70HC=3FB1!A3)(D!]!
 +MAC!]@1%+TLQA24YR-;​^*I1#​YS"​6^YI0U5$XC5S9K=D[B7L-7G 8T"​ER"​KP;​Q
 +MU725V[I]._=/​Z>​ SP1AFKH+]/​I\D5 5NKEI*D\G8:​1.4<​5&​D3Z4>​S?​F";​2\B
 +MJ&:​W_]@@#​HV %,​UF[-.?​+]OEBQLOMBT?​C^;/​-_=]>​_9K5KP=^O.E<​6'​$DH+Q
 +MRYL[^?​O1I5%1XZE5J:​)=YP;​^N_8IECSHSZA&​Z=:;​*T^R<​FT94[3DC@ENSJ ]
 +M0/​1QKR?​."​\0_KV^C!Y#​HAMWY#<;​78RL"​W72+%"​%)HJ%X@HQD B=^B1NPIE_.
 +MXU2V^XMPA[V22TEFHT%_[HS!R=;​9<​T=!E4,​5ZA\M^N.4]?​@[ C_G3!6:6CR%
 +M2Y^8^R<​L4 -&​D50Q:​L9D<​CM-5D<​AN->​3SQY].7-G?,​[*"​C',​KMU[6*7,​7-FG
 +MI"​H5-&​E.@EP \GQ4$GHPK>​ "​KB[1!D0,​(Q1PM:​OA?​U B)Y ?​@0C,​0J\_HZ9\
 +M\\8D7O,​F#​T<​@H.BB29-L U'​D9/&​9+TC1+0*?​98M8,​@W0OVG3QB^6V4-*^R8-
 +M5=(Y](?'/​HEBF.2,​1$P2IE^ZN":​O"/​P6R$3:​](IN5"​P5 .] Z$90SQ231P5=
 +M@HMBZJPV] JN.*E&'​-PQC*R1,​@YNIFO;​I#​)U[!!$72.:​19JVT95 DRQ:[([3
 +M)>​LBO^PC^_DSF:​R6W)O5E/​TWE.-J.YF=*]-<​)%D<​L17XX.X$/​9*,​Q^B6L.) ​
 +M%VAQR*(R/​.^?​0*$+ RF_"​77:​_.!H;​*"&​0E24U)#​\[X*E4Z"​U_H9%0IW (462
 +M-(G"​VS+W+U\TV,​+A"​9*FD(KT2(1*J!CXXP?/​+J&,​HB4J;​ @/​PQ4^)^#​S?​1$Y
 +M2"​63,​P0NR.= 816B"​[E/​L.:​(#​+YDR$0@)PEYZ2 \'​4HD]*Y!YG0G*ETIU-:​I
 +ML?​%#​%E>>​V=%(^%W@4 M!, C^9X>​@,​.]XD4""​*F;​$\28JVI+1C&​!_1PT&​@-50
 +M078  X43-"​5&<​-8)1_(#​8R0"​C4>​.!'&​8&​G:::​(,&​Y"​CZ4F &​HR,​JA#​KC#'​BT
 +M^B07&​!$0$=P/​(5 C?​G!RT4)&"​EF!JB9MBQ&​^X,​)34$3C +#​QO\!A?​S@BE4@4
 +MR)8W&/​1%HBM%U(SGQR/​ P?​DH%-!=1I>&​ 9 D"​!?​Y'​PB"​O"​IJ5V)UZ*BB2C,​(
 +M7(#:'​.!)II(,​0BFP0 %D'​G"​DBSS4)T,​U2-3'​R0="​V&​EZ+M AJ 4YP7]+)$UA
 +M(6 F!Y<​M*$$H.03+GD#​ @0XJO61<​L$#​_N&​+0%7_T+"?<​4DO<​VKRL"​FPO5Z[L
 +MYM7W9\YLQ*8\2S.8:​J'​3<:​9'​63[]>​H!!4YD!"​+AH*J%=%3Q+:​STTZLJ&​ ^H(
 +M$H/​Q]M3+^QDZ0TA:​E(R5$.$B18!"​=T!SI_!1P KR"/​(4DBN80$<"​ML]L0!W^
 +M\61$>​B)+/,​[7TH&​E!>​YIAA>​X)"​SN'​FX,​S.@R'&​C2U"""​5B-VH0$<​.Z TT=L3
 +M _4"​V"<​@$HKN@OL/​X,​X$^T$>​J1[_8 :​-_<​H!JG+U]-WX,"​][>&​_K^R<​KS=*L
 +M!R?​K$/​9<​YZGEM-_\<​O<#​8C65$P^NB[\'​%5JXY0O-X31N*&​%6I<​!) "​ZA+L1+
 +M/​LXB(HMQM2,​5^/​G]V3R-$':​0,​!_)WUV'​XVXCEKMUW1R=D/​ V"​*MK+"​=,​+DQK
 +MX)]^GI:​F8(UNU,​S&​UZ*3 IZV?​U!)[&​ B"&​1O089,​$E1'​ARY.6/​%D#<>​KUHS.
 +M"""​04_<>​[V_.(#​5MX-V\B'​8 :​KL4;​%N4T..(7C:​0=C0'​(7;​-27F &​!;​=P.61
 +M\BT(EMR0!M"​W?​3'​FT9(#​XU&​6HD.6(/​K.D4T+3QOHA]_6\28W<​787 7/​^__F ​
 +MGOO7 TCS0Z(E]L^'​1 @HGA^ G"​JIKV)51"#​1I$<​OLB;​+CQAV69 Z^FMUK7 8
 +MQ-]@0X01!DRNR""&​\(,​%@AU0"​YVFTDHO9+$ #​MS^&​@B9X ?​(OJ>>​TY/​@ 7;^
 +M@0Q-L@RY$%_%QN*6MM!\4KO [Q:​L3>​O?​1+;​2$%*;​6>​!Q6FQJ<​B^^(0B:​C'​4%
 +M1W ?​J(#​(\*ZO\0"​U5XXU/​=Z);​!!&​XA24SOV&​_O;​]@9ZA^]@=\,​X0K$JS7X"​$
 +M567A$!<,​8 *#​FC>​!&#​@_>#​5'​R+$=(#?​Q ]5;"​T2:​L@0?​802=6L.<​G! E3$?X
 +MZ?​C03N=ABHSJ[\BL%NF\7@2/​F&​9P/​C3:​^.JJ0O T)!EK'​3\]31FX1B"​*9]$#​
 +MDM9%;/,​48)TLQ\I]*$5)%% X0A3M!Z"​3PY]"​2=$.(X6OMZ^1]X"​EXP'​9_@ 4
 +M(658M;​0&​HY1[ +&​3W.GZ'​50- 7<​\."​\\\W)W)H\+W.2&​T>​=HX 3GN#Z^GTU9
 +MM/​W5/​60(/&​ *I">​H6*2X+7"​W>​.7W\^1^]*?​P!]$.Q:​D'​W%<'​1CE;​ -\#VE@H
 +MA>​8$4F!\@.LABBT)#​EV89 ,​A>​R>​%%IA,​J:​+"​J[I"​U -T@6CIN!T"​(V?​-Z4!&​
 +M1X$3FB7#​-;​@\KVV00M-J07AELQ$:​X&​*@F\4("​ (C^.*P@QHP!ZCQ5&​AY$ 3(
 +M"​-#​40!.63$X48D!!8$TV1P"#​[CX'​$#​%A '<;":​YF/​A\=ZW4S@6:​KB9(+Q._G
 +MM8%!) S3"​QS'​Q\>​ZE"​Z"?​7P_((I@],&​D <​$,'​1!QM*V"​53-$\+60*=0#​(1X:​
 +M64)56BBTO"​9I1?​CA@Y*O2? ​ , JX_GX- J%1--5)B(: 6:​\OP*K3LJKGOH[!
 +MH,$)I OL**&​SO-O1N;,&​M&​UDM,​A:​)P!U;:"​(5+V51AS0P->​NY4H-K#​A1JTJ>​
 +M&​8&​MH@+2_F 7@FE^Q>​E71</​[ @Q8>&​@X0 J.K@GC(V,​1V91$('​]!)H&<​*,>​7
 +M-V6"​Z]>​7EVENETC<​7EY4X/​W]Y'​2T-M$M2IV'​Z(#​+((;​I!$P+&​ "​CW6PPH#​\]
 +M EF\$38@3E>​=3-M EHX(#"​- -0:​0AT+.$PZ"​G"​ATSPD,​Y*"​A=!M!2D<​D"​L)5
 +MS^-K/​(_-0PV-Y@;​4IL#​\$#​1H7-R/​W(PP <​6E!IH3)%4["​. 5ZA"​L(^X3<​SW0
 +M'​N%2V/​^C._[7TR1Y <​BF=O)]0.VD\W0QCZ!P5,​-]3_2($.*Z&​=WL"​)QNCI"#​
 +M?OEW/]D!T 8Y6YF'​K%7*"​1":​_85.4@R#,​X*T!:​UD4+E(VOLK9/​1@"​XRE86@#​
 +M?​)]\1:​.FGF%^;​PJZ41#​*A!M < )K J!0O#​4H_4;​T*JNN6.@#​M4J[L()?​R(YV
 +MXG3P1KV*N(4.4X**QH7"​)5)$J^Q]"​CB[ _,​HK&​+[]^@:​ZL 1/​D"​I2G0#​XH\1
 +MJ*C('​%+DD"​(DI4%O%":​ )D*4V@9!07\)$V '​^%7^%R7#​ 0/​T"​N)'​KI]/​3R-*
 +MF4&​\0"​.* B&​EJ>​A#​9GG RNYB.N>​3D"#<​]^?​.E/​39(((\$B:​@&​OOCGDAM!1.%
 +M'​9"​S"<​A5A%;​)E['​]>​5($NL.ZWB7+=UP@@,​0&​[*#​LH*[[ :​E1:​MM;​%6TX$C<​)
 +M(V%,'​CC 1$2,​RQ9N("​XS?​OKOOGB>/​P]B<​3NR(TP("​QA-3/​L+8]$9,​A DIO_Q
 +MYW5P/#​3.N/​_[W. $,#​_04M15O>#​AX@8SL/​A!"::​6KXSG#​E2HAW:​DP,;?,​ZH+
 +M$S589G187]-WX\8#​1WQPN5A*15V[YC]/>​WI,​I!]*_@3H$3- F3'​LFOH5$A!L
 +M $H0^!_TF74%S(K37^Q9TO^Z;​03%,​M^SGN!@E(= 0P[]M4D#​3TASXKN4T9[^
 +MONEBA(W99QBELG0%-17@:​8M!)EV;​DP&​O*_^6( NNXX!69)T^>​WF KU!+ P0*
 +M  0 !@#​Z:,<:​33MY=.T! ​ !4 P  !P   '​!W;​2YD+G-*!QB= _T= QT(!4T&
 +M""​T'​% 4(!@@'​%@@&​!2@=!"​4#​!18#​%@0%) @$ P0%!@@&​!1@-%_V=_OYN!/​[^
 +M+A<>​!PXG!1X&'​@8''​@=^!?​[N#​P$"​ P(# @0#- 8;​!;​O\_#​P& ​ 42]_?​WMU&​C
 +MTF_NT.F?​XO;​3[C+5[!Q_7^17.>​W^33O'​ CNUR-E<​IEH>#​-PUL?<​WE1OX2][Q
 +M+K"&?​@T=\7REG]"​GO32,​U^$/​6]ND'​CYFF94&​M\CX8-)RH3=P-KPW @^;^]IJ
 +MY4)#​IU27U<​J%F_($XCR!P2]CTP]FDX3"​!&​93#​4L!4$L#​! H !  & /9HQQIR
 +MY4!WL@( ​ (<​% ​  ​( ​   <'​=M,​BYC+G-,​!QB=!/​T= QT(!BT7)@@6% 46" 8(
 +M!@@6*!T$!10#​)@,&"​ 0&) 8%! ,​%%A08#​1C]_3W^S@7^_@X(#​A<>​!PX'#​@<&​
 +M'​@8>​!@<>​!WX&​_NX. 1)#! ,'​%0P$/​ :L_?T%#0 " P8#​!@<&​%P;​W]_='​D2,​7
 +M[ 4_PDVY_S7=*]_$-YST'​Q,​J=H=M=)BP?​]URJM6<;​0--Z.3;​_OA7;​F\__%+[
 +MA:​D$XTQ4K:​+BB5JRUJBT&"​ W_2 4(+5LJ:​I"​DRU_E]8+IVV5ZHN5;​65"​[5[0
 +MUL/​\O^#​2//​2HM??​ D=P$MA=?​7]8_ ZF?​I<:​8SM)$I>​4V1-9*.%[%5JKU-,​K7
 +M;​T!P5[.-YY3*BONREKWK1F6EXCQZ."<​QO(XQ.YK1DA)'​-&/,​] 502P,​$"​@ $
 +M  8 ^FC'&​LJ@5',​0 @  _@,   ​@ ​  ​!P=VTX+F0N<​T<​^!WX#​_@X'​ QX'​!DX&​
 +M!RX&) <&​!P8'​!@<&​!2<>​ P85 R8#​%@0&​) <​D%@<​%!A<​.%_Y^__^/​!/​__+Q<?​
 +M!P\G!A\&'​Q8?​!G\%_^\/​ 2(3! ,$ R4,​!WP&​7/​W]'​0H $@87!@<&​]_?​W9]&​B
 +M;​D_[;​C<​DC^6W1 P4)-3KIYN8=2NIUO]ZZ#​)>​X;​+:​__MACTT=KB[,​O+^]*?​UT
 +MU;"​N8(2?<​O>​(+FY;​5QW:​7%FA+%8-S@I5Z!:​N]@A3(J50) E?,​C-1.1=5\I9B
 +M-3YY:​L\;​1AF?​]'​PQ9Q5_R\^F=6%Y79#​^2CE6)M3^@C9QK+PGN#​2/​ VKM'​5+/​
 +M*Q.=>​QK#&​YCS:<​R:​EC+1F.-F*E!+ 0(* PH    !  -IQQIT41>​R90 ​  &​L ​
 +M   , ​             "​D@0 ​   !D:​6=I+20Q,​C P+F]02P$""​@,​* ​   !@#\
 +M:,<:​E85SFV$! ​ !R @  #               ​I(&/ ​   9&​EG:​2TD,​3(P,"​YS
 +M4$L! @H#"@ $  8 ^6C'&​FM^*+ + @  > ,   ​L ​             *2!&​@( ​
 +M '​!W;​2TD,​3(P,"​YS4$L! @H#"@ $  8 _&​C'&​O9"​0BNQ @  \ 8   ​P ​    
 +M         ​*2!3@0 ​ '​!W;​2TR8VEA+F0N<​U!+ 0(* PH !  & /​=HQQKFO<<​O
 +M:P(  &​D% ​  ​+ ​             "​D@2D' ​ !P=VTM8VEA+F8N<​U!+ 0(* PH 
 +M!  & /​=HQQJHW6A*AP( ​ .0%   ​+ ​             "​D@;​T) ​ !P=VTM8VEA
 +M+F<​N<​U!+ 0(* PH  @ &  IIQQK396D>>​SL ​  ​)0 ​  ' ​             "D
 +M@6T,  !P=VTM:​FUJ4$L! @H#"@ $  8 ^FC'&​DT[>​73M 0  5 ,   < ​    
 +M         ​*2!#​4@ ​ '​!W;​2YD+G-02P$""​@,​* ​ 0 !@#​V:,<:<​N5 =[(" ​ "'​
 +M!0  " ​              ​I($?​2@ ​ <'​=M,​BYC+G-02P$""​@,​* ​ 0 !@#Z:,<:
 +MRJ!4<Q " ​ #^ P  " ​              ​I('​W3 ​  <'​=M."​YD+G-02P4& ​   ​
 +/  H "@ O @  +4\     
 +..    ​
 +.                                    C=H #20
 +cu next time!
 +.                                    - fin -
magazines/chacking20.txt ยท Last modified: 2015-04-17 04:34 (external edit)