magazines:discovery2
no way to compare when less than two revisions
Differences
This shows you the differences between two versions of the page.
— | magazines:discovery2 [2015-04-17 04:35] (current) – created - external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
+ | < | ||
+ | __ __ | ||
+ | | ||
+ | | ||
+ | / __ / || || |__|__|| | ||
+ | | ||
+ | | ||
+ | | ||
+ | _ | ||
+ | | ||
+ | |||
+ | |||
+ | | ||
+ | |||
+ | |||
+ | I s s u e 2 : October 1, 1996 | ||
+ | |||
+ | |||
+ | P R E A M B L E | ||
+ | |||
+ | |||
+ | We greet you to the second issue of disC=overy, the Journal of the Commodore | ||
+ | Enthusiast. | ||
+ | ones who still hold our beloved 8-bit machines in high regard and respect. | ||
+ | In honor of your committment to these classic platforms we have pledged | ||
+ | ourselves to assemble this entire journal on modest C64 and C128 systems. | ||
+ | It is our sincerest hope that you will find our efforts to be of interest | ||
+ | and special joy. We thank you from the bottom of our hearts and look forward | ||
+ | to forging a solid productive relationship with the C= 8-bit community. | ||
+ | |||
+ | - Mike Gordillo, Steven Judd, Ernest Stokes, and the authors of disC=overy. | ||
+ | |||
+ | |||
+ | A R T I C L E S O F O P E R A T I O N | ||
+ | |||
+ | |||
+ | Article 1 : Mission Statement | ||
+ | |||
+ | Our intent is to present useful information in order to enhance and preserve | ||
+ | the knowledge base of the Commodore 8-bit domain, including, but not limited | ||
+ | to, the Commodore 64 and Commodore 128 home computers. | ||
+ | require that every article contain what in our discretion should be a viable | ||
+ | Commodore 8-bit hardware and/or software point of relevance. | ||
+ | issue should include material that can both potentially enlighten the most | ||
+ | saavy of users as well as the layman. | ||
+ | others engaged in similar endeavours. | ||
+ | to stave off entropy as long as possible. | ||
+ | |||
+ | |||
+ | Article 2 : disC=overy Staff | ||
+ | |||
+ | The current staff of disC=overy, the Journal of the Commodore Enthusiast, | ||
+ | is as follows: | ||
+ | |||
+ | Editor-in-Chief | ||
+ | Associate/ | ||
+ | Webmaster | ||
+ | |||
+ | disC=overy, issue 2 logo by ' | ||
+ | |||
+ | We invite any and all interested parties to join us as authors, panelists, | ||
+ | and staff members. | ||
+ | |||
+ | |||
+ | Article 3 : General Procedures | ||
+ | |||
+ | - Submission Outline - | ||
+ | |||
+ | a. Articles may range in size from 1 kilobyte and up. Approximately 15 | ||
+ | | ||
+ | |||
+ | b. Sufficient technical content about Commodore 8-bit home computers, | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | |||
+ | - Staff Priorities - | ||
+ | |||
+ | The Editor-in-Chief shall supervise the organization of each issue in regards | ||
+ | to grammatical and syntactical errors, flow of content, and overall layout of | ||
+ | presentation. | ||
+ | panel whose function it shall be to referee literary work which the Editor | ||
+ | in-Chief has deemed to be of advanced technical merit. | ||
+ | iand disC=overy, the Journal of the Commodore Enthusiast, shall retain | ||
+ | copyright solely on the unique and particular presentation of its included body | ||
+ | of literary work in its entirety. | ||
+ | responsibilities with regards to the content of their particular literary | ||
+ | work. Authors shall be required to submit their works to the Editor-in-Chief | ||
+ | approximately two weeks prior to publication. | ||
+ | |||
+ | |||
+ | Article 4 : Peer Review | ||
+ | |||
+ | To the best of our knowledge, disC=overy shall be the first Commodore 8-bit | ||
+ | journal with a review panel dedicated to uphold the technical integrity and | ||
+ | legitimacy of its content. | ||
+ | shall be responsible for the formation of the panel. | ||
+ | panelists shall have the option of anonymity if desired. | ||
+ | review works primarily for technical merit if the Editor-in-Chief and | ||
+ | the Associate Editor deem it necessary. | ||
+ | their works in accordance with the panel' | ||
+ | Chief shall have final discretion regarding all such " | ||
+ | |||
+ | |||
+ | Article 5 : Distribution | ||
+ | |||
+ | Although we welcome open distribution by non-commercial organizations, | ||
+ | are currently three " | ||
+ | parties. | ||
+ | or via the World Wide Web at http:// | ||
+ | at FTP site : ftp.eskimo.com - directory / | ||
+ | Several versions of this journal may be available for your convenience, | ||
+ | check with the aforementioned sources. | ||
+ | |||
+ | |||
+ | Article 6 : Disclaimers | ||
+ | |||
+ | The Editor-in-Chief and disC=overy, the Journal of the Commodore Enthusiast, | ||
+ | retain all copyrights regarding the presentation of its articles. | ||
+ | retain all copyrights on their specific articles in and of themselves, | ||
+ | regarding the full legal responsibility concerning the originality of their | ||
+ | works and its contents. | ||
+ | |||
+ | The Editor-in-Chief and disC=overy, the Journal of the Commodore Enthusiast, | ||
+ | grants the reader an exclusive license to redistribute each issue in its | ||
+ | entirety without modification or omission under the following additional | ||
+ | stipulations: | ||
+ | |||
+ | - If distribution involves physical media and is part of a commercial, | ||
+ | not-for-profit, | ||
+ | charge shall not exceed $4 1996 United States Dollars per issue | ||
+ | unless more than one issue is distributed on a single media item | ||
+ | (i.e., two or more issues on one disk), in which case maximum | ||
+ | allowable charge shall not exceed $4 1996 United States Dollars per | ||
+ | media item. All dollar values given assume shipping costs are | ||
+ | -included- as part of the maximum allowable charge. | ||
+ | |||
+ | - If distribution involves non-physical media and is part of a | ||
+ | commercial, not-for-profit, | ||
+ | allowable charge shall be limited to the actual cost of the | ||
+ | distribution, | ||
+ | other electronic means. | ||
+ | |||
+ | **!** - Software included within articles (as text) may be subject to separate | ||
+ | distribution requirements as binary executables. | ||
+ | with authors regarding distribution of software in binary form. | ||
+ | |||
+ | It is understood that distribution denotes acceptance of the terms listed and | ||
+ | that under no condition shall any particular party claim copyright or public | ||
+ | domain status to disC=overy, the Journal of the Commodore Enthusiast, in its | ||
+ | entirety. | ||
+ | |||
+ | The Editor-in-Chief and disC=overy, the Journal of the Commodore Enthusiast, | ||
+ | reserve the right to modify any and all portions of the Preamble and the | ||
+ | Articles of Operation. | ||
+ | |||
+ | |||
+ | ::::::::::: | ||
+ | :::::::::::::::::::::: | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | -Software Section- | ||
+ | |||
+ | /S01 - " | ||
+ | $d000 Interpretation Technique" | ||
+ | by Roland Toegel and 'Count Zero' | ||
+ | |||
+ | /S02 - " | ||
+ | $d000 by ' | ||
+ | |||
+ | /S03 - "The Raster StarterKit, A classic ' | ||
+ | $d000 by ' | ||
+ | |||
+ | /S04 - " | ||
+ | $d000 by Mike Gordillo | ||
+ | |||
+ | /S05 - "A demo of ' | ||
+ | $d000 by John Kaiser | ||
+ | |||
+ | /S06 - "SID Primer: The Working Man's Guide to SID" | ||
+ | $d400 by Stephen L. Judd | ||
+ | |||
+ | /S07 - " | ||
+ | $d400 by Andreas Varga | ||
+ | |||
+ | /S08 - "Z80, The basics of it" | ||
+ | 0100h by ' | ||
+ | |||
+ | |||
+ | -Hardware Section- | ||
+ | |||
+ | /H01 - " | ||
+ | by Ravid Noam | ||
+ | |||
+ | /H02 - "The 8 bit Modplay 128 Board, a three-diode addition" | ||
+ | by Nate Dannenberg | ||
+ | |||
+ | /H03 - "The Virtual PLUS/4 : Upgrading your C16 to 64 Kilobytes!" | ||
+ | by Martin Gierich | ||
+ | |||
+ | /H04 - " | ||
+ | by Ron Fick | ||
+ | |||
+ | /H05 - "The Metal Shop" | ||
+ | with SMS Mike Eglestone | ||
+ | |||
+ | |||
+ | -Corrections- | ||
+ | |||
+ | $2bad - E R R A T A | ||
+ | |||
+ | |||
+ | ::::::::::: | ||
+ | / | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | : Innovation in the 90s : | ||
+ | |||
+ | The Super Hi-Res Interlace Flexible Line Interpretation Technique | ||
+ | |||
+ | | ||
+ | |||
+ | |||
+ | Prelude from Count Zero | ||
+ | --------+--+--+-------- | ||
+ | |||
+ | First seen earlier this year, the new SHIFLI video technique that is described | ||
+ | in this article is -the- paramount example of programming brilliance. | ||
+ | inventor of the technique, Roland Toegel (Crossbow of Crest), was helpful | ||
+ | in providing the original SHIFLI documentation, | ||
+ | German into the English text that follows for the exclusive use of the | ||
+ | disC=overy journal. | ||
+ | |||
+ | So without further ado, let us now learn from Mr. Toegel how to achieve | ||
+ | the award winning : | ||
+ | |||
+ | ' | ||
+ | |||
+ | Please note that the technique is described primarily for Commodore computers | ||
+ | based on the European/ | ||
+ | require an extra 2 cycles per line. This may require the programmer to time | ||
+ | out the routines by hand, but this should not be a major obstacle to overcome. | ||
+ | |||
+ | Count Zero | ||
+ | -- | ||
+ | |||
+ | i. Forward | ||
+ | |||
+ | As the inventor of the SHI-FLI mode, I am pleased to have the services of | ||
+ | Count Zero and the disC=overy journal for the dissemination of my technique | ||
+ | into the English language. | ||
+ | reader to be already familiar to a high degree with VIC-II programming on the | ||
+ | C64. I would suggest books such as ' | ||
+ | found at ftp.funet.fi /pub/cbm ..etc., for a solid base of instruction. | ||
+ | some terms used in this document (e.g., mix-color) are meant to be uniquely | ||
+ | descriptive and hence, will not be found in any ' | ||
+ | The terminology is a result of the strain that occurs when new methodology | ||
+ | meets old semantics. | ||
+ | words to be self-evident in the context which they are used. | ||
+ | -- | ||
+ | |||
+ | 1. Introduction to Super Hires | ||
+ | |||
+ | Super-Hires Interlace FLI : The absolute successor of the Super-Hires-Modes. | ||
+ | Just like normal Super-Hires, | ||
+ | 12 Characters or 4 Sprites next to each other. | ||
+ | area is centered though using Char-Position 15 to 26 (included) on the | ||
+ | screen. | ||
+ | 8 Sprites and to have as much flexibility as possible on choosing colors or | ||
+ | pixels, 2 sprites are overlayed, 4 times next to each other (8 sprites on | ||
+ | the rasterline) over the bitmap graphics. | ||
+ | are used for a 96 * 21 pixel-wide area, a small multiplexer is needed to | ||
+ | juggle all 8 sprites for 8 times (every 21 rasterlines and with individual | ||
+ | patterns). | ||
+ | Bitmap Mode in an 8*8 pixel block. | ||
+ | are the same throughout the whole picture. | ||
+ | |||
+ | |||
+ | 2. Super Hires with FLI !?! | ||
+ | |||
+ | FLI is for most coders still quite hard. Sprites over FLI for most quite | ||
+ | impossible. Maybe one or two sprites, but 8 !?! next to each other and | ||
+ | still FLI in each rasterline! | ||
+ | |||
+ | First of all you need to know how to do FLI and what it does and also | ||
+ | what effect sprites have on it. | ||
+ | |||
+ | |||
+ | 2.1 | ||
+ | |||
+ | On each eighth rasterline (the Badlines, the first rasterline of each charline) | ||
+ | the VIC stops the processor for 40-43 cycles to read the new Characters and | ||
+ | colors of the video and color-ram. | ||
+ | registers $D011 and $D012 are the same. Now if you change on each rasterline | ||
+ | the bits 4-7 of $D018, which holds the length of the video-ram (handling the | ||
+ | colors on bitmap graphics) and set the bits 0-2 of $D011 to get a badline on | ||
+ | each rasterline, you will get new colors on each rasterline in the bitmap | ||
+ | graphics. | ||
+ | cycles are used whereby for the *used cycles - 22* char of the textline the | ||
+ | next 3 chars the byte $FF (light grey) is read from the video ram and the | ||
+ | FLI effect starts after that (the FLI bug). In the multicolor mode for the | ||
+ | color-ram the next byte in the program after writing to $D011 is chosen as | ||
+ | the color. | ||
+ | |||
+ | > NOTE: The last 3 sentences were pretty hard to translate and I advise you | ||
+ | > to read other articles about FLI aswell, if you want to know more | ||
+ | > about Multicolor FLI. (CZ) | ||
+ | |||
+ | |||
+ | 2.2 | ||
+ | |||
+ | So what does a sprite do over FLI? Pretty simple, as it just eats up some | ||
+ | cycles. | ||
+ | our FLI routine without loops, we just need 2 LDA, STA commands (for $D018 | ||
+ | and $D011), using 12 cycles per rasterline. | ||
+ | sprites that makes 31 cycles. | ||
+ | char-positions 9,10 and 11 and from char 12+, the FLI effect comes up. This | ||
+ | doesn' | ||
+ | We therefore get 3 cycles per rasterline for other commands. | ||
+ | |||
+ | |||
+ | 3. Mulitplexing over FLI | ||
+ | |||
+ | Now we rather have to increase the Y-Coordinates of the sprites by 21 pixels | ||
+ | each 21 rasterlines and give them new patterns. | ||
+ | video-rams on FLI for the colors and the sprite-pointers are always at the | ||
+ | end of the video-ram, we are supposed to write 8 * 8 values for the patterns | ||
+ | plus 8 values for the Y-Coordinates, | ||
+ | Thats far too much for a single rasterline and adding the FLI routine will | ||
+ | bust the limits. | ||
+ | |||
+ | |||
+ | 3.1 | ||
+ | |||
+ | The trick is not to change anything at all! On the other hand we don't want | ||
+ | the patterns to look the same everywhere. | ||
+ | (21 pixels) is not capable of being divided by the height of a textline | ||
+ | (8) and the smallest mutual multiple is 168 (meaning 21 * 8). As we are | ||
+ | writing (due to the FLI) a new value to $D018 on each rasterline and we use | ||
+ | 8 video rams, we can abuse this and have different sprite-pointers on every | ||
+ | video-ram. | ||
+ | located becomes a little bit confusing, but it doesn' | ||
+ | as we don't have to change the pointers. | ||
+ | |||
+ | |||
+ | 3.1.1 Table to illustrate the sprite pattern-handling | ||
+ | |||
+ | Spriteline | ||
+ | |||
+ | 1 | ||
+ | 2 | ||
+ | 3 | ||
+ | 4 | ||
+ | 5 | ||
+ | 6 | ||
+ | 7 | ||
+ | 8 | ||
+ | |||
+ | 9 | ||
+ | 10 2 10 | ||
+ | 11 3 11 | ||
+ | 12 4 12 | ||
+ | 13 5 13 | ||
+ | 14 6 14 | ||
+ | 15 7 15 | ||
+ | 16 8 16 | ||
+ | |||
+ | 17 1 17 | ||
+ | 18 2 18 | ||
+ | 19 3 19 | ||
+ | 20 4 20 | ||
+ | 21 5 21 | ||
+ | -------------------------------- | ||
+ | 1 | ||
+ | |||
+ | 2 | ||
+ | 3 | ||
+ | |||
+ | 4 | ||
+ | . | ||
+ | . | ||
+ | . | ||
+ | 20 1 41 | ||
+ | 21 2 42 | ||
+ | -------------------------------- | ||
+ | 1 | ||
+ | 2 | ||
+ | 3 | ||
+ | . | ||
+ | . | ||
+ | . | ||
+ | |||
+ | |||
+ | 3.1.2 | ||
+ | |||
+ | Small example for Sprite 0 under the following conditions: | ||
+ | |||
+ | Used 8 Video-Rams | ||
+ | content of the sprite-pointer: | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | Thus the Sprite-Patterns are in memory from $6000-$61FF. | ||
+ | |||
+ | Therefore the pattern-handling looks like this: | ||
+ | |||
+ | Screenline | ||
+ | |||
+ | 1 | ||
+ | 2 | ||
+ | 3 | ||
+ | 4 | ||
+ | 5 | ||
+ | 6 | ||
+ | 7 | ||
+ | 8 | ||
+ | 9 | ||
+ | 10 $605B-$605D | ||
+ | 11 $609E-$60A0 | ||
+ | 12 $60E1-$60E3 | ||
+ | 13 $6124-$6126 | ||
+ | 14 $6167-$6169 | ||
+ | 15 $61AA-$61AC | ||
+ | 16 $61ED-$61EF | ||
+ | 17 $6030-$6032 | ||
+ | 18 $6073-$6075 | ||
+ | 19 $60B6-$60B8 | ||
+ | 20 $60F9-$60FB | ||
+ | 21 $613C-$613E | ||
+ | 22 $6180-$6182 | ||
+ | 23 $61C3-$61C5 | ||
+ | . . | ||
+ | . . | ||
+ | . . | ||
+ | |||
+ | |||
+ | 3.1.3 | ||
+ | |||
+ | As the changing of the video-ram on the editor-routine happens inside of the | ||
+ | textscreen (but the spritepointers are read inside the sideborder), | ||
+ | takes effect one rasterline later. | ||
+ | the bitmap of color ram 2 are read, the sprite pointers or video ram 1 are | ||
+ | still active. | ||
+ | (instead of 168) and why the first textline of the first video-ram and the | ||
+ | first rasterline of the bitmap stays empty. | ||
+ | |||
+ | |||
+ | 3.2 | ||
+ | |||
+ | As the changing of the sprite pointers more or less happens by itself, we just | ||
+ | have to make sure the correct Y-Value comes into the game. These are still | ||
+ | 8 values, but they don't have to be set in one rasterline and we got 21 | ||
+ | rasterlines to set them. As we have just 3 cycles left on each rasterline on | ||
+ | the FLI routine described and that wouldn' | ||
+ | we have to change the FLI routine a little bit. | ||
+ | |||
+ | |||
+ | 3.2.1 Load new sprite Y-Value | ||
+ | |||
+ | As the height of all sprites is the same, we just have to do a single | ||
+ | LDX # | ||
+ | anything else with the last free cycle. | ||
+ | |||
+ | LDX #$VALUE | ||
+ | LDA #$08 | ||
+ | STA $D018 | ||
+ | LDA #$38 | ||
+ | STA $D011 | ||
+ | |||
+ | |||
+ | 3.2.2 Load next but one $D011 value | ||
+ | |||
+ | As a STA $SPRITE0Y needs 4 cycles, we cannot include it in the next rasterline, | ||
+ | but we can already load the next $D011 value into the Y-Register. | ||
+ | cycle stays unused. | ||
+ | |||
+ | LDY #$3A | ||
+ | LDA #$18 | ||
+ | STA $D018 | ||
+ | LDA #$39 | ||
+ | STA $D011 | ||
+ | |||
+ | |||
+ | 3.2.3 Write new Sprite Y-Value | ||
+ | |||
+ | As we already did the loading of the $D011 value for the next line, we now | ||
+ | have 5 cycles left and therefore enough time for a STA $SPRITE0Y. | ||
+ | |||
+ | STX $SPRITE0Y | ||
+ | LDA #$28 | ||
+ | STA $D018 | ||
+ | STY $D011 | ||
+ | |||
+ | Now plot 3.2.2 and 3.2.3 have to be repeated for the remaining 7 sprites with | ||
+ | changed values for $D011 and $D018. | ||
+ | the new sprite Y-value and 8 * 2 rasterlines to write the new sprite Y-Value. | ||
+ | We now have 17 rasterlines and the 4 remaining ones just need an additional | ||
+ | NOP so that all rasterlines use the same amount of cycles. | ||
+ | |||
+ | |||
+ | 3.2.4 | ||
+ | |||
+ | For simplification of the routine, which generates the FLI routine, in the | ||
+ | remaining 4 rasterlines an LDX #$VALUE was used instead of an NOP. | ||
+ | |||
+ | |||
+ | 4. Memory-allocation | ||
+ | |||
+ | Now video-rams, the bitmap, and the sprites have to be placed reasonable in | ||
+ | a VIC-Bank. | ||
+ | for graphics due to the overlay of the Char-rom we choose the back from | ||
+ | $4000 - $7FFF for now. | ||
+ | |||
+ | |||
+ | 4.1 | ||
+ | |||
+ | The 8 video-rams need $2000 Bytes. | ||
+ | |||
+ | |||
+ | 4.2 | ||
+ | |||
+ | The bitmap needs $1F40 Bytes. | ||
+ | |||
+ | |||
+ | 4.3 | ||
+ | |||
+ | As we need 2 sprites overlayed (four times next to each other and 8 times | ||
+ | below each other), we need 2 * 4 * 8 sprites, meaning 64 overall. | ||
+ | $1000 bytes for the sprites. | ||
+ | already allocate and recognize that only $7F40 - $7FFF, enough memory for | ||
+ | 3 sprites, is left open. How do we rectify this situation? | ||
+ | |||
+ | As the video-rams and the bitmap just need a small part for displaying the | ||
+ | picture, the sprites can be put into the spare parts of the video-rams and | ||
+ | the bitmap. | ||
+ | rams. | ||
+ | |||
+ | A textline of a bitmap covers $140 bytes. | ||
+ | $60 bytes though and is centered. | ||
+ | a textline of the bitmap is free. As a sprite needs $40 bytes, we can put 2 | ||
+ | sprites in each textline of the bitmap (one to the left and one to the right). | ||
+ | Due to the height of the picture (21 textlines), this results in space for | ||
+ | 42 sprites. | ||
+ | textline for sprites, resulting in 5 sprites per line. Continuing this until | ||
+ | textline 24 (included), we have space for 15 additional sprites. | ||
+ | we already have 57 sprites and just 7 are missing now. These we could place | ||
+ | in the remaining free area of the bitmap ($7E00-$7FFF), | ||
+ | efficient as we have some space left in the video-rams. | ||
+ | |||
+ | The Textline of a video-ram contains $28 bytes. | ||
+ | needs the middle $0C bytes. | ||
+ | of the picture are supposed to be invisible, we need to set a background-color | ||
+ | in the video-ram (in our case, the color light-grey $FF). So we don't have | ||
+ | enough spare room for the sprites to the left and the right of the picture | ||
+ | in the video-ram. | ||
+ | |||
+ | If we finish the FLI Routine from textline 22 on and keep the video-ram on | ||
+ | until the end of the screen (filling the this area ($4370-$43E8) with the | ||
+ | backgroundcolor $FF to hide the sprites) we can use the remaining 7 video-rams | ||
+ | from textline 22 (from $4770, $4B70, $4F70, $5370, $5770, $5B70, $5F70) for | ||
+ | one sprite each. Now we have placed all 64 sprites and the allocation of the | ||
+ | sprite pointers looks like this: | ||
+ | |||
+ | $43F8 80 84 85 89 8A 8E 8F 93 | ||
+ | $47F8 94 98 99 9D 9E A2 A3 A7 | ||
+ | $4BF8 A8 AC AD B1 B2 B6 B7 BB | ||
+ | $4FF8 BC C0 C1 C5 C6 CA CB CF | ||
+ | $53F8 D0 D4 D5 D9 DA DE DF E3 | ||
+ | $57F8 E4 E8 E9 EA EB EC ED EE | ||
+ | $5BF8 EF F0 F1 F2 F3 F4 F5 F6 | ||
+ | $5FF8 F7 1E 2E 3E 4E 5E 6E 7E | ||
+ | |||
+ | The pointers from $80 to $E4 are the 2 sprites which are left and right | ||
+ | next to the picture in the bitmap. | ||
+ | |||
+ | The pointers from $E8 to $F7 are the sprites from textline 22 to 24 below | ||
+ | the picture in the bitmap. | ||
+ | |||
+ | The pointers from $1E to $7E are the sprites from textline 22 to 24 below | ||
+ | the picture in the video-rams. | ||
+ | |||
+ | |||
+ | 5. Interlace | ||
+ | |||
+ | Until now we had the normal Super Hires FLI mode, supplying the basics for | ||
+ | interlace. | ||
+ | displayed 25 times per second (PAL). | ||
+ | from $4000 - $7FFF and we have another VIC-Bank ($C000-$FFFF) with the same | ||
+ | assumptions we can easily place the 2nd picture there. | ||
+ | |||
+ | We had in the Super Hires FLI mode (on a 8 * 1 pixel-area) the choice between | ||
+ | 4 colors (2 sprite-colors, | ||
+ | colors). | ||
+ | meaning the combined 4 colors from picture one and two. When using interlace, | ||
+ | mix-colors are created except for the case when the same colors are used for | ||
+ | both pictures on the same 8 * 1 pixel-area (Check the following example) : | ||
+ | |||
+ | |||
+ | Pic2 -> Sprite1:$E [ Sprite2:$0 [ FLI1:$6 [ FLI2:$9 | ||
+ | ---------------------------------------------------------------- | ||
+ | Pic1 Sprite1: | ||
+ | [ | ||
+ | V | ||
+ | FLI2 : | ||
+ | |||
+ | |||
+ | This results in the following 16 mixcolors: | ||
+ | |||
+ | 1. White-Lightblue | ||
+ | 2. Cyan-Lightblue | ||
+ | 3. Lightblue-Lightblue (pure Lightblue) | ||
+ | 4. Blue-Lightblue | ||
+ | 5. White-Black | ||
+ | 6. Cyan-Black | ||
+ | 7. Lightblue-Black | ||
+ | 8. Blue-Black | ||
+ | 9. White-Blue | ||
+ | 10. Cyan-Blue | ||
+ | 11. Lightblue-Blue | ||
+ | 12. Blue-Blue (pure Blue) | ||
+ | 13. White-Brown | ||
+ | 14. Cyan-Brown | ||
+ | 15. Lightblue-Brown | ||
+ | 16. Blue-Brown | ||
+ | |||
+ | |||
+ | When choosing the colors you should take care that the brightness-values of | ||
+ | the 2 mix-colors are about the same and that they do not differ by more than | ||
+ | 2 brightness steps, as things otherwise start to flicker too much. | ||
+ | (e.g. Black-White flickers a lot). | ||
+ | |||
+ | Here is a table with brightness-values from light to dark. | ||
+ | (Colors on the same line have the same brightness) | ||
+ | |||
+ | $1 : White | ||
+ | $7, $D : Yellow, Lightgreen | ||
+ | $3, $F : Cyan, | ||
+ | $5, $A : Green, | ||
+ | $C, $E : Grey, | ||
+ | $4, $8 : Lilac (Purple), Orange | ||
+ | $2, $B : Red , | ||
+ | $6, $9 : Blue, Brown | ||
+ | $0 : Black | ||
+ | |||
+ | |||
+ | 6. Additional Graphics (Not handled by the editor) | ||
+ | |||
+ | We found out that on the left or right of the picture in the bitmap, $70 bytes | ||
+ | was left for spritedata. | ||
+ | still have $30 bytes (6 Chars or 48 Pixels) left to both sides of the picture. | ||
+ | |||
+ | |||
+ | 6.1 Left to the picture | ||
+ | |||
+ | Our Super Hires Picture starts at position 15. The spare $30 bytes are from | ||
+ | position 9 to 14. As we use 14 cycles in our FLI routine and the 8 sprites use | ||
+ | 19 cycles per rasterline, the light-grey FLI Bug now uses the chars 11, 12, 14. | ||
+ | Thus meaning we could use char 14 for Hires FLI. On chars 9 and 10 we could | ||
+ | just use 2 different colors (respectively 4 mix-colors for interlace) on the | ||
+ | height of 21 textlines in the bitmap, as the FLI effect starts from Char 14 and | ||
+ | before that no new data (colors in this case) are read from the video-ram. | ||
+ | The colors are in the first video-ram in memory from $4008+$4009 respectively | ||
+ | $C008+$c009. | ||
+ | |||
+ | |||
+ | 6.2 Right to the picture | ||
+ | |||
+ | Our Super Hires Picture lasts until char-position 26. The spare $30 bytes in | ||
+ | the bitmap are from position 27 - 32. Here we could use all 6 chars for Hires | ||
+ | FLI (or Interlace Hires FLI). | ||
+ | |||
+ | |||
+ | 7. Memory-allocation of a picture startable with RUN | ||
+ | |||
+ | The included SHIFLI picture, once unpacked, can be easily modified for your own | ||
+ | use, as follows : | ||
+ | |||
+ | $0801-$080C Basic Startline | ||
+ | $080D-$0860 Routine for copying the Graphic-data to the correct memory area | ||
+ | $0861-$095B Routine which is setting the I/O registers and creates the | ||
+ | display-routine (from $085F-$10FB) | ||
+ | $095C-$475B Data of the 1. Picture (to be copied to $4000) | ||
+ | $475C-$855B Data of the 2. Picture (to be copied to $C000) | ||
+ | |||
+ | |||
+ | 8. Conclusion | ||
+ | |||
+ | That's it ... for further information check the editor code and other sources, | ||
+ | most likely available at ftp sites such as ftp.funet.fi /pub/cbm ..etc., etc. | ||
+ | Included is the uuencoded version of the X96 Graphics C-64 Contest Winner by | ||
+ | Deekay/ | ||
+ | Style. | ||
+ | -- | ||
+ | For questions or comments concerning this article : | ||
+ | Roland Toegel is available at : toegelrd@trick.informatik.uni-stuttgart.de | ||
+ | Count Zero/ | ||
+ | |||
+ | |||
+ | begin 644 x96-winner | ||
+ | M 0@+", | ||
+ | M$ CHT/&@ (3WA/ | ||
+ | M!2"! ;#SYO9I H7WH@, | ||
+ | M"N;YT +F^L; | ||
+ | M_L#OT ZD_\ 'T BI-X4!6$P-"# | ||
+ | M]_ 2I? | ||
+ | M @(!^< | ||
+ | M..D8RLK@" | ||
+ | M;?@I#XWQ ;W;N? 0D3O(R=' | ||
+ | MC> | ||
+ | MT%CP_J($ZLK0_" | ||
+ | M@> | ||
+ | MUL3K;< | ||
+ | M0Y2.%=Z' | ||
+ | M:< | ||
+ | M2BI, | ||
+ | M9N-=>& | ||
+ | M^]S)ZG14X: | ||
+ | M$& | ||
+ | M8BO/ | ||
+ | M+; | ||
+ | MIP9: | ||
+ | M!@ CZC' | ||
+ | M!EX,? | ||
+ | MYD_=; | ||
+ | MN5UW" | ||
+ | MD$ZEA\CE9=3.$.RDF*7" | ||
+ | MC5"; | ||
+ | MP+I%]K1M=QXN/", | ||
+ | M[\$<< | ||
+ | M+4X< | ||
+ | M' | ||
+ | MK]V]]< | ||
+ | MD9X' | ||
+ | MQ# | ||
+ | M> | ||
+ | M,; | ||
+ | M<" | ||
+ | MA/ | ||
+ | MA=S6<" | ||
+ | MI!F> | ||
+ | M9; A]P9@P, | ||
+ | ML\X5A_!8@\# | ||
+ | M #" -]9_', | ||
+ | M'; | ||
+ | MW0MOG" | ||
+ | MB12PNA)X> | ||
+ | MTZ\!/# | ||
+ | M)RDH$7Z-R]OSLYN8[? | ||
+ | M2]-> | ||
+ | M+06C^S" | ||
+ | M1GMHJ#^ .(X0J68)*LN@<# | ||
+ | MW4\3L DLMKL.HCL# | ||
+ | M23X3/ | ||
+ | M65WUWM_NG# | ||
+ | M83J5(" | ||
+ | MU^8W*WLL03$]BTTD D # | ||
+ | M\27V@$R $21L_K+<& | ||
+ | M^]Q\CM70_? | ||
+ | M7@SAU/ | ||
+ | M)K.# | ||
+ | M%*\L>&> | ||
+ | MCJ< | ||
+ | MO\L%" | ||
+ | M38# | ||
+ | MYB?S(=;S / | ||
+ | M&" | ||
+ | MT=U38<? | ||
+ | MX[> | ||
+ | M" | ||
+ | M[8A[0=W> | ||
+ | M)L%6PZ%5 LL\Q8QA8V)" | ||
+ | M4.T-ZF'> | ||
+ | M\$ %0)<'" | ||
+ | MX+RI^@$BMT2@? | ||
+ | M" | ||
+ | M)A, | ||
+ | M3> | ||
+ | M:,# | ||
+ | M+]' | ||
+ | MSIL9AQ/ | ||
+ | MV(L& | ||
+ | MTL3> M?& | ||
+ | M\U" | ||
+ | MG!!9^7!< | ||
+ | M6 @?B @\I5Q)(=L/X , | ||
+ | M!OK" | ||
+ | MXUO# | ||
+ | MK1^S$LJ# | ||
+ | MO(/< :*!D ', | ||
+ | M<&: | ||
+ | M8PL+" | ||
+ | MP? | ||
+ | MGAJ=,; | ||
+ | M F:-^_Q6[5= ; | ||
+ | M_\@R$; | ||
+ | M)A8>< | ||
+ | MXL\' | ||
+ | M> | ||
+ | M\^R# | ||
+ | MGYRQ(/ | ||
+ | MQ\3P\6' | ||
+ | M\?& | ||
+ | MBN%@JLAF26& | ||
+ | M*N L$Y # | ||
+ | MAR[Q=@. @9B8F)G9. _A? | ||
+ | M?;S%>QL V; | ||
+ | MP & | ||
+ | M\C@A_O/ | ||
+ | M!> | ||
+ | MH)@DLD]0S< | ||
+ | M H$I7S?< | ||
+ | M, | ||
+ | M R# | ||
+ | MHK.6L< | ||
+ | MDF14,I,S +: | ||
+ | MP/# | ||
+ | M$A< | ||
+ | M5VFYRM\? | ||
+ | M$!H< | ||
+ | M9J6" | ||
+ | MQEV9O-E" | ||
+ | M[^]O; | ||
+ | MRZ7.[L& | ||
+ | M& | ||
+ | M3O^^? | ||
+ | M75/ | ||
+ | M S" | ||
+ | MI..@P E, | ||
+ | MV/ | ||
+ | MQ, | ||
+ | M /SEO C$[$(_UY^JU^2HD : | ||
+ | M0D4: | ||
+ | M$Z6K& | ||
+ | M93]_: | ||
+ | MY@65A@I5KW], | ||
+ | M!?AE7A=N $SFE' | ||
+ | M/ | ||
+ | M8XS %W@Q X</ | ||
+ | MTIS_,# | ||
+ | M0\HV< | ||
+ | M#< | ||
+ | M>? | ||
+ | M\# | ||
+ | M" | ||
+ | M$+09)9: | ||
+ | MN!@%42FWF%I$$EP$TL/ | ||
+ | M> | ||
+ | MD6PVO+9D" | ||
+ | M.YUHS0%0" | ||
+ | M+RLX X / | ||
+ | MG1!2 Q7I_[" | ||
+ | MQP& | ||
+ | M2=840(AX< | ||
+ | MP $PEC='/# | ||
+ | M-C-U=]?)@ UFMF0((Q | ||
+ | M0+LA^#&< | ||
+ | M59\JE1' | ||
+ | M*]!LU4Q)DPC+E# | ||
+ | M4< | ||
+ | MZ$> | ||
+ | MAD*AAVB3S3: | ||
+ | MOE,> | ||
+ | MG#;/ | ||
+ | M _ @]< | ||
+ | MHZ> | ||
+ | MXLERJ$PKOR, | ||
+ | M2: | ||
+ | M5L\7; | ||
+ | M$*I+7A^@*# | ||
+ | MW^QEQ.E *E 73IMQS+8ZULM< | ||
+ | M%R*UE;? | ||
+ | MSK#& | ||
+ | MK@)YQFD+" | ||
+ | M@"# | ||
+ | MRLO/ | ||
+ | MC5.OSU6W:< | ||
+ | MFFY%-$D+P=$-+%7BM%9-=DY:, | ||
+ | MR2/ | ||
+ | M3GB< | ||
+ | M PE#D #Z 98\ -# | ||
+ | MD]J362=-N!RRZ; | ||
+ | MZJ15.IRD< | ||
+ | MR& | ||
+ | M8H2'# B > | ||
+ | M)? | ||
+ | M]J !+U-GU(*FW+M*UF# | ||
+ | M$*)QE: | ||
+ | M+[5& | ||
+ | M-VD$81R9VN25QZ3)!DP&: | ||
+ | M, .@' | ||
+ | M\? | ||
+ | M 5[!P< | ||
+ | M4A!& | ||
+ | M0: 0T=' | ||
+ | MRK0=2A? | ||
+ | M8!E7%S!#D, (5@7D& | ||
+ | M#," | ||
+ | M)-6'C 5RCKCBD*K[EHB^E(B)]92/ | ||
+ | M_8: | ||
+ | MP' | ||
+ | M*@RE': | ||
+ | M.\$J!I^ O/BM-U .? | ||
+ | MA0@!5CO? G#, | ||
+ | M72FH: | ||
+ | M0\\Z1FH# | ||
+ | MM C P7.-0L? | ||
+ | M9? | ||
+ | M ; | ||
+ | MAVC19DEK!4$> | ||
+ | MEQ\18V(R; | ||
+ | M=C(: | ||
+ | M #X>#.N7 1Z(E2A!<# | ||
+ | M91 W^1 D; | ||
+ | M$: | ||
+ | M) X=;! 1, | ||
+ | MB(S@^/ | ||
+ | MK" | ||
+ | M='> | ||
+ | M0%X)T@(1OT$9S' | ||
+ | M2< | ||
+ | M& | ||
+ | M%JQ]8$ 8>J XMCT? | ||
+ | MA-E5!&# | ||
+ | M%E8#; 7&>< | ||
+ | M& | ||
+ | M@NI4> | ||
+ | M8%^Y4/ | ||
+ | M$(+]B$XSQY,&"# | ||
+ | MB#7RT+,B 6!, | ||
+ | MLL?#; | ||
+ | M6WT]/> | ||
+ | M# | ||
+ | M4DTP[LJB-F! (PJ88 (< | ||
+ | M/ | ||
+ | M& | ||
+ | MO Q-GKH2-ZQ6PH3!RXLC9^> | ||
+ | M%: | ||
+ | M@@' | ||
+ | M[QC & # | ||
+ | ME# | ||
+ | M)_;/ | ||
+ | M%PNHOX3J]> | ||
+ | M35'' | ||
+ | MK$@/B A$/ | ||
+ | M-, | ||
+ | M\-GQ()U<, | ||
+ | M04)L"/ | ||
+ | M%NT %Y08]RT4+Q& | ||
+ | M!Q@^ Q1MZQS)^)QJ< | ||
+ | ME!E3I9C/ | ||
+ | MY; | ||
+ | M%PT: | ||
+ | M!HE88FAG4W$" | ||
+ | M4HC%A@)<<;; | ||
+ | M@!R/ | ||
+ | M@^, | ||
+ | M# | ||
+ | MX*I5G$4' | ||
+ | M(3OW," | ||
+ | M !G _& (8+C5& | ||
+ | MAH[HLK=R3" | ||
+ | M=]!@[\R ! < | ||
+ | M+Y5& | ||
+ | MT5I.X/ | ||
+ | M# | ||
+ | M%G]?/ | ||
+ | M7OYF_/ | ||
+ | MU?; | ||
+ | M_,(O<YP # | ||
+ | MH. D@9XFADA> | ||
+ | M# \GGW6$%4, | ||
+ | MO\<; | ||
+ | MBJ 2 !4 D H0W^5# | ||
+ | M*].NJD@> | ||
+ | M3O^N+B)0UE' | ||
+ | M 1 6*$(5@L6" | ||
+ | M9T7D^> | ||
+ | M_/ <? | ||
+ | M@A @M+" | ||
+ | MP6P]A0(R" | ||
+ | M,P$CI%G@? YH-0TR$1L,# | ||
+ | M5J[8[' | ||
+ | M# | ||
+ | M4NQ' PK(: | ||
+ | M!MVXCC< " | ||
+ | M4&_M?$ L# | ||
+ | MJ @CF# | ||
+ | M-7+J" | ||
+ | M+*)03J9 8:R,[ $E9)QGZL9,, | ||
+ | M!DI!@!DT(5ES9[? | ||
+ | MSN; | ||
+ | MH$# | ||
+ | M"0B $/ | ||
+ | M+GC@ 200!R1> | ||
+ | MV1PGSYS@JB\Y*XVXV? | ||
+ | M_1:57P!A 6% VEP!4HF *GS, | ||
+ | M((# Y/# | ||
+ | MLJQ@]I%\+" | ||
+ | M;6 && | ||
+ | M" | ||
+ | MF9D9& | ||
+ | M& ' | ||
+ | M 1ANBA: | ||
+ | M/ | ||
+ | M, | ||
+ | MH" | ||
+ | M0+G" | ||
+ | M@D& | ||
+ | MMZ; | ||
+ | M@0'& | ||
+ | M/ | ||
+ | M*# P" | ||
+ | M""' | ||
+ | M9@!@P,;# | ||
+ | M_&< 0B+@TJ,'> | ||
+ | M& | ||
+ | MP# | ||
+ | M' | ||
+ | M]((X" | ||
+ | M_ ## | ||
+ | M006" | ||
+ | M; | ||
+ | MX@(14# | ||
+ | M321N30Q># | ||
+ | M"V B"" | ||
+ | M"V,: C4(" | ||
+ | M%1,N 9 H VDT# | ||
+ | M!_ [# | ||
+ | ME@ "; | ||
+ | MD]C)// | ||
+ | M" | ||
+ | MB-# | ||
+ | M+3DV.B $!04+ 1DO Q(%1*HK !@@(" .+Q Z$Q09# 66' | ||
+ | M> | ||
+ | ; | ||
+ | |||
+ | end | ||
+ | |||
+ | |||
+ | / | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | | ||
+ | |||
+ | A look at simple scroll-text routines | ||
+ | |||
+ | by XmikeX, | ||
+ | |||
+ | with contributions from Asger Alstrup | ||
+ | |||
+ | |||
+ | The first ' | ||
+ | (one character wide, one character long :) scroll-text mover. | ||
+ | even this task was a chore. | ||
+ | wring it out (thank you Paul G-S) and eventually come up with the source | ||
+ | as listed below. | ||
+ | time, Mr. Asger Alstrup graciously volunteered the suggestions following | ||
+ | the original code. I would like to thank him for his insights in this | ||
+ | matter. | ||
+ | -- | ||
+ | |||
+ | !Simple 1*1 Scrolly - (C) 1994 XmikeX | ||
+ | |||
+ | org $3000 - a fake op...tells the assembler where we want the code to start. | ||
+ | change this as you see fit, but keep it away from your text data. | ||
+ | lda #$00 - | ||
+ | sta $fd - This is just so that we can do neato ind. index | ||
+ | lda #$32 - addressing later.. ie., we are setting up the | ||
+ | sta $fe - spot where scrolly text will be pulled from ! | ||
+ | |||
+ | mainloop ldx #$c7 ; Prepare the scrolly register for a "hard right" | ||
+ | bitloop | ||
+ | | ||
+ | | ||
+ | |||
+ | ;(we need to clear X because we are going to use it in a delay | ||
+ | ; | ||
+ | ;(if we don't introduce a delay, the text will scroll by | ||
+ | ; WAY TOO FAST to READ ! ! | ||
+ | ; machine of ours is, eh?) - delays not set in stone, play | ||
+ | ; with them :) higher, lower, etc... | ||
+ | |||
+ | ldx #$05 ; just a delay loop - ldx counted down by dex | ||
+ | waitloop ldy #$ff ; a delay loop within a delay loop | ||
+ | wl2 dey ; decrement y in our delay looop | ||
+ | bne wl2 ; keep counting y down until y = 0 | ||
+ | | ||
+ | bne waitloop ; repeat the y loop until x = 0 | ||
+ | | ||
+ | | ||
+ | | ||
+ | cpx #$bf ; coarse scroll anyone? | ||
+ | bne bitloop | ||
+ | ldx #$00 ; clear X | ||
+ | rt lda $7c1, | ||
+ | sta $7c0, | ||
+ | | ||
+ | cpx #$28 ; hex $28 = decimal 40 = width of VIC-II screen | ||
+ | bne rt ; still in coarse scroll? | ||
+ | |||
+ | inc $fd - here is where the indexing pays off, with | ||
+ | bne ty - these little lines all we gotta do is stick | ||
+ | inc $fe - our scrolly-text data beginning (in this case) | ||
+ | ty ldy #$00 - at location $3200 | ||
+ | lda ($fd), | ||
+ | sta $7e7 - shove the data into rightmost corner | ||
+ | jmp mainloop - let it scrooooolll, | ||
+ | |||
+ | |||
+ | $3200 is 12800 in decimal I believe. | ||
+ | some character data there, I would just poke it in. | ||
+ | |||
+ | poke 12800,65 = a at $3200 | ||
+ | poke 12801,66 = b at $3201 | ||
+ | |||
+ | etc | ||
+ | etc | ||
+ | etc | ||
+ | |||
+ | I like the code as it is, but I am looking for an even smoother method. | ||
+ | Any suggestions? | ||
+ | |||
+ | |||
+ | [...] | ||
+ | |||
+ | Hello XmikeX, | ||
+ | |||
+ | In regards to your scroll-text routine : | ||
+ | |||
+ | > ;(if we don't introduce a delay, the text will scroll by | ||
+ | > ; WAY TOO FAST to READ ! ! | ||
+ | > ; machine of ours is, eh?) - delays not set in stone, play | ||
+ | > ; with them :) higher, lower, etc... | ||
+ | > | ||
+ | > LDX #$05 ; just a delay loop - ldx counted down by dex | ||
+ | > | ||
+ | > | ||
+ | > BNE wl2 ; keep counting y down until y = 0 | ||
+ | > | ||
+ | > BNE waitloop ; repeat the y loop until x = 0 | ||
+ | |||
+ | [even more snipped here] | ||
+ | |||
+ | In the following, I'll describe *very* basic features like the | ||
+ | concepts of rasters and frames. This is probably only interesting | ||
+ | for newcomers in the assembly-domain. | ||
+ | |||
+ | If you are looking for a smoother scroll, you would use what is commonly | ||
+ | known as " | ||
+ | |||
+ | Your tv-set displays the image on the screen using an electron-beam | ||
+ | which moves from the top, left corner down towards the lower, right | ||
+ | corner in horizontal lines. If my memory serves my right, PAL (mostly | ||
+ | european) screens have 312 lines, while NTSC (in the USA) screens have | ||
+ | 262 lines. We count these lines starting from 0 (263 total), and call them | ||
+ | " | ||
+ | |||
+ | The Commodore 64 has a few registers in the VIC (the chip that generates | ||
+ | the video-image) which reflect where the electron-beam is. We have one | ||
+ | register at adress $D012 which contains the first 8 bits of the raster, | ||
+ | and another at adress $D011, where bit 7 describes the 9th bit of the | ||
+ | rastervalue. | ||
+ | |||
+ | You can safely regard the rasterline as a Y-coordinate on the screen | ||
+ | that starts from above and works its way down. | ||
+ | |||
+ | In this way, you can use this formula to obtain the rastervalue: | ||
+ | |||
+ | raster= peek(53266)+ (peek(53265) and 128) * 2; | ||
+ | |||
+ | Note, that this isn't of much use in a basic-program because | ||
+ | basic simply is too slow, and when the calculation is done, | ||
+ | the rasterline has changed so much that the value of " | ||
+ | is more random than useful. | ||
+ | |||
+ | Well, the value in $D012 goes from 0 to 255 while $D011 bit 7 is 0, | ||
+ | refrecting that the rasterline goes from 0-255. Then bit 7 in $D011 | ||
+ | is set to 1, and $D012 goes from 0 to 55 on a PAL-system, refrecting | ||
+ | that the rasterline is 256-311. This is what is know as a " | ||
+ | and the cycle starts over with $D012 being 0, and bit 7 in $D011 also | ||
+ | being 0. | ||
+ | |||
+ | You can exploit this register to get a smooth scroll in several ways. | ||
+ | I'll describe the most primitive of these techniques in the following. | ||
+ | |||
+ | A smooth scroll is achieved, if the text is moved every frame, not | ||
+ | more, not less. In other words, the delay loop in the scroll should | ||
+ | be adjusted so that the entire scroll-loop is performed exactly once | ||
+ | per frame. | ||
+ | |||
+ | Okay, that's fine, but how do I achieve this? | ||
+ | |||
+ | You simply monitor the rasterline. A simple way of doing this is to | ||
+ | use a bit of code like this, which waits until the rasterline is | ||
+ | at position 0 exactly. | ||
+ | |||
+ | wait lda $d012 ;Wait until the lower bits of the rastervalue | ||
+ | cmp #0 ;is 0. | ||
+ | bne wait | ||
+ | |||
+ | lda $d011 ;We also need to check if the high bit of | ||
+ | and #$80 ;the rastervalue is 0, in order to distinguish | ||
+ | bne wait ;raster 0 from raster 256. | ||
+ | |||
+ | Another, slighty optimized version, could be: | ||
+ | |||
+ | lda #$ff | ||
+ | wait cmp $d012 | ||
+ | bne wait | ||
+ | |||
+ | which waits until the rasterline is $ff. This one exploits the fact | ||
+ | that the rasterline never exceeds 311, so $d011 bit 7 will always | ||
+ | be 0, when $d012 is >55. | ||
+ | |||
+ | You should try to play around with such wait-loops, and with a little | ||
+ | effort you should be able to do a smooth scroll. | ||
+ | |||
+ | You can also try to use $d020 to set the border-color at different | ||
+ | rasterlines, | ||
+ | |||
+ | wait1 lda $d012 ;Wait for rasterline $30 | ||
+ | cmp #$30 | ||
+ | bne wait1 | ||
+ | lda $d011 | ||
+ | bmi wait1 | ||
+ | |||
+ | lda #1 ;Set border-color to white | ||
+ | sta $d020 | ||
+ | |||
+ | wait2 lda $d012 ;Wait for rasterline $108 | ||
+ | cmp #$08 | ||
+ | bne wait2 | ||
+ | |||
+ | lda #0 ;Set border-color to black | ||
+ | sta $d020 | ||
+ | jmp wait1 ;And keep looping | ||
+ | |||
+ | Notice that the second wait-loop exploits that the next | ||
+ | time $d012 will be $08 after rasterline $30, will be | ||
+ | at rasterline $108, which makes a check on $d011 unnecessary. | ||
+ | |||
+ | This little program will produce a white stripe in the | ||
+ | border of the screen, but the area where the color | ||
+ | changes will probably " | ||
+ | around in a small area. | ||
+ | |||
+ | This is because we only check when the rasterLINE is right. | ||
+ | Ideally, we would also want to check for the right " | ||
+ | coloumn" | ||
+ | (x,y) point, but unfortunately the c64 doesn' | ||
+ | such a rastercolumn register, so removing the flicker can be tricky | ||
+ | business. | ||
+ | |||
+ | Of course there' | ||
+ | VIC provides a facility to automatically announce to the program | ||
+ | when a certain rasterline arives, so that we needn' | ||
+ | rastervalue ourselves in a loop. This technique is known as raster- | ||
+ | interrupts, but I'll leave that subject to another time. | ||
+ | |||
+ | Until then, welcome to the world of rasters, and happy hacking! | ||
+ | |||
+ | Asger Alstrup | ||
+ | -- | ||
+ | For questions and comments, ' | ||
+ | of disC=overy. | ||
+ | |||
+ | |||
+ | / | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | The Raster StarterKit, | ||
+ | -+-+-+-+-+-+ | ||
+ | A classic ' | ||
+ | |||
+ | by XmikeX and Dokken/ | ||
+ | |||
+ | |||
+ | My early experiences with VIC-II programming on the C64 led me to the | ||
+ | conclusion that being able to manipulate the VIC-II on a rasterline level | ||
+ | is essential to success as a " | ||
+ | far beyond this simple dogma, but it is recognized that all have to start | ||
+ | somewhere. | ||
+ | still use this bit of code to form the core of many of my programs. | ||
+ | would like at this time to thank Dokken/ | ||
+ | the base code that follows. | ||
+ | instruction to those who might be more familiar with BASIC than ML, the | ||
+ | source below has all values in decimal (same as with BASIC peek/poke). | ||
+ | However, please note that raster programming requires quick action and is | ||
+ | best left to ML code. | ||
+ | |||
+ | *=49152 | ||
+ | |||
+ | sei | ||
+ | lda #127 | ||
+ | sta 56333 | ||
+ | lda #18 ; the first 3 STA's just tell the 64 to | ||
+ | sta 53265 ; do IRQ' | ||
+ | lda #1 ; says to do a IRQ at rasters 0-255, but | ||
+ | sta 53274 ; whatever | ||
+ | lda #100 | ||
+ | sta 53266 ; the IRQ'll happen at raster line 100 | ||
+ | lda #<main | ||
+ | sta 788 ; low byte of IRQ routine | ||
+ | lda #>main | ||
+ | sta 789 ; high byte of IRQ routine | ||
+ | cli | ||
+ | |||
+ | jkjk | ||
+ | jmp jkjk ; this just happily jumps back to itself | ||
+ | ; whenever we're not in an IRQ | ||
+ | |||
+ | main | ||
+ | rol 53273 ; or: lda #1:sta 53273 tell the 64 an IRQ | ||
+ | ; happened or something goofy like that | ||
+ | ldx #1 | ||
+ | jsr rast | ||
+ | ldx #0 | ||
+ | jsr rast | ||
+ | |||
+ | lda #126 ; this sets up a raster IRQ at scan line | ||
+ | sta 53266 ; 126 for the ' | ||
+ | lda #<rain | ||
+ | sta 788 | ||
+ | lda #>rain | ||
+ | sta 789 | ||
+ | |||
+ | jmp $ea81 ; ok. you can use either $ea81 or $ea31 | ||
+ | ; $ea31 with an RTS where the 'jmp jkjk' | ||
+ | ; is allows the 64 to process everything | ||
+ | ; normally. | ||
+ | ; proggy and have an ML interrupt doing | ||
+ | ; something on top of it like maybe playing | ||
+ | ; a happy tune or something. | ||
+ | ; $ea81 gives you total control with no | ||
+ | ; overhead. | ||
+ | ; takes a decent chunk of time). | ||
+ | ; $ea81, if you want to read the keyboard, | ||
+ | ; you get to do it explicitly. | ||
+ | |||
+ | |||
+ | rain | ||
+ | rol 53273 | ||
+ | |||
+ | ldx #6 ; rasterline is now set at color : blue! | ||
+ | jsr rast | ||
+ | ldx #0 | ||
+ | jsr rast | ||
+ | |||
+ | lda #100 ; this sets up a raster IRQ at scan line | ||
+ | sta 53266 ; 100 for the ' | ||
+ | lda #<main | ||
+ | sta 788 | ||
+ | lda #>main | ||
+ | sta 789 | ||
+ | |||
+ | jmp $ea81 | ||
+ | |||
+ | |||
+ | rast ; this routine just makes a raster bar out of | ||
+ | lda 53266 ; the value in the x register. | ||
+ | rast2 ; only works for 7 of 8 scan lines cuz the | ||
+ | cmp 53266 ; 64 needs to chunk away at graphix | ||
+ | beq rast2 | ||
+ | stx 53280 | ||
+ | stx 53281 | ||
+ | rts | ||
+ | |||
+ | |||
+ | Try and expand this routine to include many many rasters or color bars, | ||
+ | or anything you dream up. It would be prudent to point out at this time | ||
+ | that a text such as " | ||
+ | VIC-II chip registers. | ||
+ | description of VIC-II register function. | ||
+ | |||
+ | Goodbye. | ||
+ | -- | ||
+ | For more information or general commentary on this article, XmikeX may be | ||
+ | reached through the Editor-in-Chief of disC=overy. | ||
+ | |||
+ | |||
+ | / | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | | ||
+ | |||
+ | by Mike Gordillo | ||
+ | |||
+ | |||
+ | As a self-proclaimed "demo freak", | ||
+ | represents one of the most interesting discussions concerning C-64 that I have | ||
+ | ever witnessed on IRC (Internet Relay Chat). | ||
+ | in the hopes of encouraging further participation and patronage of IRC channel | ||
+ | # | ||
+ | " | ||
+ | -- | ||
+ | |||
+ | < | ||
+ | tried double interrupts and stuff and every now and then I'd get like | ||
+ | this 1-2 cycle jitter I couldn' | ||
+ | |||
+ | <_dW> My raster is completely stable.. or so I think. | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | | ||
+ | |||
+ | <_dW> Fungus: it works like this: set up a raster int on line ' | ||
+ | then in the irq code change the $0314-$0315 vector to point to a | ||
+ | | ||
+ | NOPs 'till it strikes. When the second raster hits, you'll be | ||
+ | at most 1 cycle off, which can then be corrected by code like | ||
+ | LDA # n+1 : CMP $d012 : BEQ sync (sync: rest of the code) | ||
+ | |||
+ | < | ||
+ | & ffff. I still got goofy jitter. It would be totally stable for like | ||
+ | 5 second then screw 1-2 cycles to the left exactly 3 times. Then would | ||
+ | be stable for another 5 secs. CIA's are Disabled as well as NMI's: NTSC. | ||
+ | |||
+ | <_dW> Fungus: you must have left out something... what method did you use? | ||
+ | |||
+ | < | ||
+ | |||
+ | <_dW> double ints and? | ||
+ | |||
+ | < | ||
+ | FFFE FFFF too, and yes, all in NTSC R-8 VIC chip so 65 cycles. | ||
+ | |||
+ | < | ||
+ | | ||
+ | and nops | ||
+ | |||
+ | <_dW> Fungus: send me mail at agonzalez@nlaredo.globalpc.net | ||
+ | | ||
+ | |||
+ | < | ||
+ | like rotzoomers/ | ||
+ | |||
+ | <_dW> Sorex: the one with the plasma in dawnfall/ | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | | ||
+ | |||
+ | <_dW> Fungus: that could cause an interrupt, if they' | ||
+ | is clear. | ||
+ | |||
+ | < | ||
+ | $d012 equivalent? | ||
+ | |||
+ | <hld> XmikeX - the vic20 has no $d012 equivalent - so you cant tell | ||
+ | what raster you are on. | ||
+ | |||
+ | < | ||
+ | poll the raster register and SEI and never clear it. Makes it preety | ||
+ | easy. It's just that the Vic-20 treats 2 raster lines as one. I made | ||
+ | a cool looking 16 color ROL scroller on it once. (Basically, its $d012 | ||
+ | | ||
+ | I prefer to just poll $9004 ($d012) Easy to do. Then you can do | ||
+ | all sorts of twisted stuff. The VIC is a different Animal. Really | ||
+ | neat registers. Remove the borders with out an INT even! | ||
+ | | ||
+ | of some cool stuff. You can make the screen scroll in any direction | ||
+ | | ||
+ | big color Memory. Full Screen High res too, no borders.=] | ||
+ | |||
+ | [...] | ||
+ | |||
+ | <_dW> hld: you did real raytracing in your 4k entry? | ||
+ | |||
+ | <hld> dw - yup.. My 4k entry distorted a checker board. The code was about $4f0 | ||
+ | bytes (and 20KB of tables ;) | ||
+ | |||
+ | <_dW> Are you using lots of rom routines? | ||
+ | |||
+ | <hld> dw - It uses ROM routines to create the 20KB tables, but it is pure | ||
+ | | ||
+ | I have no division at all in this tracer.. only +,*,srqt | ||
+ | | ||
+ | is all pre-calced in floating point, but i do some nifty weird | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | [...] | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | it a bit and see how far out I can go and still have the routine update | ||
+ | | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | of the next buffer. Man, that EATS time... but a character or bitmap | ||
+ | | ||
+ | does, the amount of zeros to stuff into memory is significantly less. | ||
+ | |||
+ | <_dW> Wave: are you doing a complete clear? or a redraw-clear? | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | lda #$00 | ||
+ | ldy #$00 | ||
+ | sta $2000, | ||
+ | sta $2080,y | ||
+ | sta $2100,y | ||
+ | ... <etc> ... | ||
+ | iny | ||
+ | bpl < | ||
+ | |||
+ | Each STA line will zap 128 bytes in that loop... I may unroll it | ||
+ | | ||
+ | |||
+ | < | ||
+ | |||
+ | LDX #0 | ||
+ | LDA #times/4 | ||
+ | | ||
+ | STY ....,X | ||
+ | BNE | ||
+ | DEX | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | you mentioned. | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | | ||
+ | |||
+ | < | ||
+ | |||
+ | sta $2000,y | ||
+ | sta $2080,y | ||
+ | sta $2100,y | ||
+ | sta $2180,y | ||
+ | sta $2200,y | ||
+ | sta $2280,y | ||
+ | sta $2300,y | ||
+ | sta $2380,y | ||
+ | sta $2400,y | ||
+ | sta $2480,y | ||
+ | sta $2500,y | ||
+ | sta $2580,y | ||
+ | sta $2600,y | ||
+ | sta $2680,y | ||
+ | sta $2700,y | ||
+ | sta $2780,y | ||
+ | iny | ||
+ | bpl < | ||
+ | rts | ||
+ | |||
+ | I cover the whole area in one loop, except I kill 128 bytes with | ||
+ | each STA. | ||
+ | |||
+ | < | ||
+ | And why pick 128 bytes? | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | means that all the loop overhead (agreed - not too much, but there) | ||
+ | is multiplied by 256. If you loop 128 times, I would think it should | ||
+ | | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | an average number for overhead cost. | ||
+ | |||
+ | < | ||
+ | no way...even if I write a program to generate them. | ||
+ | |||
+ | < | ||
+ | point of diminishing returns... | ||
+ | |||
+ | < | ||
+ | what you think when you code) and one branch. (BPL or BMI respectively). | ||
+ | BTW, 256 has the same functionality... Both will set a processor flag. | ||
+ | Hmmm, actually all numbers will with a DEY. | ||
+ | |||
+ | < | ||
+ | by pages..... | ||
+ | |||
+ | < | ||
+ | has no overhead? So if thats true, if you only looped twice, you'd | ||
+ | cut out half the STA's, but increase the overhead by a factor or 2. | ||
+ | The overhead is small though (couple cycles for dey, couple for the | ||
+ | | ||
+ | |||
+ | < | ||
+ | | ||
+ | just takes more space.... | ||
+ | |||
+ | < | ||
+ | | ||
+ | that STA loop. =) After all, you can only have 40 or 41 STA's to use | ||
+ | a Bxx instruction. | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | I can't believe I am thinking of making a 6144 byte clear routine and | ||
+ | | ||
+ | Three bytes per STA... 2048 bytes to clear... (2048 * 3 = 6144) * two | ||
+ | | ||
+ | |||
+ | < | ||
+ | Each time you gotta INY/DEY and CMP and/or Bxx (branch) or JMP | ||
+ | at the end of the loop, with the number of times through the loop | ||
+ | | ||
+ | | ||
+ | |||
+ | < | ||
+ | it up in one page! otherwise 6 cycles) | ||
+ | |||
+ | < | ||
+ | to deal with at all... | ||
+ | | ||
+ | |||
+ | [...] | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | I was able to obtain. | ||
+ | see much of a real difference it made to the overall performance of | ||
+ | the code. | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | while inbetween running each version. I should run them one right after | ||
+ | | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | Hmmm, if I am doing my math right, then it is actually 2.5 ms. | ||
+ | | ||
+ | | ||
+ | | ||
+ | This means one cycle is more or less .0000009 seconds which | ||
+ | is approx. NTSC 1Mhz rate. My calculator lacks the required | ||
+ | | ||
+ | So if we forget about 'bad lines' (VIC-II DMA), I am saving | ||
+ | | ||
+ | its center completely in 8-bit increments, hence the routine is | ||
+ | | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | out and instead work on speeding up my line drawing code. | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | 0 AND 1 = 0 | ||
+ | 0 EOR 0 = 0 | ||
+ | 0 EOR 1 = 1 | ||
+ | 1 EOR 1 = 0 | ||
+ | Etc., Etc., Etc...and remember, anytime you got that N in front | ||
+ | | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | vs speed of execution (plotter) | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | | ||
+ | pixel on in the chargrid right? | ||
+ | |||
+ | < | ||
+ | |||
+ | 1> point rotater (basically, change all the thetas) | ||
+ | 2> Resolve r,theta into x,y | ||
+ | 3> plot x,y on charmap | ||
+ | |||
+ | < | ||
+ | | ||
+ | | ||
+ | |||
+ | < | ||
+ | polar just to rotate it and bring back. | ||
+ | |||
+ | < | ||
+ | | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | makes the Rotation part very fast... just inc/dec the thetas. | ||
+ | |||
+ | < | ||
+ | | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | have works & looks good... who cares? | ||
+ | | ||
+ | with an X high bit. Think for a moment... :) | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | |||
+ | < | ||
+ | I think... | ||
+ | |||
+ | < | ||
+ | done, I think I'll put this all down in writing. :) | ||
+ | |||
+ | [...end of transcript...] | ||
+ | |||
+ | |||
+ | / | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | A demo of ' | ||
+ | -+*+- | ||
+ | = The exploration of rotation :) | ||
+ | |||
+ | by John Kaiser (Waveform/ | ||
+ | |||
+ | |||
+ | Shoooom! Greetings from the Wave! =) | ||
+ | |||
+ | In this article I am going to talk about a relatively simple two- | ||
+ | dimensional vector routine. Before I dive in, however, I'd like to | ||
+ | give some credit to Steven Judd, who through one of his C= Hacking | ||
+ | articles showed me the way to the core of a fast line drawing routine | ||
+ | as well as an amazing method of quick multiplication. | ||
+ | give thanks to those ' | ||
+ | for their most-nifty insight. | ||
+ | been taunting with my (I think, I hope..) 'super fast pixel plotter', | ||
+ | are finally able to see the source for it. Its so extremely simple, you'll | ||
+ | probably kick yourself for not coming up with it yourself. | ||
+ | |||
+ | [Ed. Note : According to S. Judd, the 'quick multiplication' | ||
+ | which John is referring to was originally formulated by George Taylor.] | ||
+ | |||
+ | For all of those expecting a full blown demo in this article, I am afraid | ||
+ | I have to disappoint you. The "real world" has been taking a heavy toll on | ||
+ | me during the last month so the amount of time I was able to spend working | ||
+ | on this article was extremely limited. What you will see, however, is a | ||
+ | fully functional rotating vector routine, in a relatively basic form. | ||
+ | There are tons of uses for the routines involved, and infact, my next demo | ||
+ | uses quite a few variations on the code you are about to see. | ||
+ | |||
+ | It is interesting to note that I did not set out to code a rotating vector | ||
+ | routine. =) I have another project I'm working on that involves some of | ||
+ | the same routines. While working I thought, "Hey, I could probably rotate | ||
+ | a two dimensional shape pretty easy with this." | ||
+ | |||
+ | And Voila! You get something like the source code that follows. | ||
+ | |||
+ | Preface To The Code | ||
+ | ------------------- | ||
+ | The trick to this code is referencing the endpoints of a line by a radius | ||
+ | and theta, as opposed to regular X and Y. | ||
+ | |||
+ | The advantage to referencing the endpoints - or even just individual | ||
+ | pixels - like this is that to rotate a point, you merely add or subtract | ||
+ | from the theta value. This process ends up being extremely fast, | ||
+ | especially when compared to taking an X,Y coordinate and sending it | ||
+ | through a whole slew of mathematics just to move it around. Observe a | ||
+ | quick comparison of the steps needed to rotate a number of endpoints about | ||
+ | their geometric center: | ||
+ | |||
+ | Old Method: | ||
+ | | ||
+ | 1. Get an X,Y endpoint | ||
+ | 2. Convert to radius and theta (or some other convenient rotation friendly | ||
+ | reference) | ||
+ | 3. Apply the rotation | ||
+ | 4. Convert back to X,Y | ||
+ | 5. Store result | ||
+ | 6. Loop back until all endpoints are finished | ||
+ | |||
+ | Nifty Method: | ||
+ | | ||
+ | 1. Get a radius, | ||
+ | 2. Apply the rotation | ||
+ | 3. Store result | ||
+ | 4. Loop back until all endpoints are finished | ||
+ | |||
+ | What is missing is the slow, and sometimes bulky conversion routines. Even | ||
+ | if you have a fast conversion routine, this method still is faster because | ||
+ | you have less steps from beginning to end. You have less code to execute | ||
+ | per endpoint. | ||
+ | |||
+ | One other trick to this particular code is that my circles have 256 units | ||
+ | to them, while most peoples circles have 360 degrees. Rather than have a | ||
+ | boring name like " | ||
+ | comparison, a normal circle has 360 degrees, while my circles have 256 | ||
+ | byts. | ||
+ | |||
+ | Why use a different unit of measurement? | ||
+ | realize that in our nifty 8bit machines, the largest value a single byte - | ||
+ | register, or memory location - can hold is 255. By using the byt unit of | ||
+ | measurement, | ||
+ | to worry about fiddling with high bits. This way of thinking saves many | ||
+ | many processor cycles, and many many hairs on a coder' | ||
+ | |||
+ | Finally, before I slam you with the source code (which, by the way, is in | ||
+ | TurboAssembler format) there is one more trick to this particular code. | ||
+ | Sometimes the only way to get real speed out of a Commodore is to reduce | ||
+ | the code necessary to perform calculations. How do the great coders do | ||
+ | that? The keyword(s) are LOOKUP TABLES. | ||
+ | |||
+ | This code uses tables for the plot routine and for the multiplication | ||
+ | routine. I wrote basic programs to create the tables and will do my best | ||
+ | to explain how they work, near the end of this article. As to the tables | ||
+ | themselves, I'll explain how the code uses them when I get to the relevant | ||
+ | section of the source code. | ||
+ | |||
+ | Wow, what a preface! Now, on to bigger and better things: The documented | ||
+ | and commented Source Code! | ||
+ | |||
+ | The Source Code - In TurboAssembler Format | ||
+ | ----------------------------------------- | ||
+ | Okay, here it is. I'll be commenting on the code throughout the remainder | ||
+ | of the article in hopes to further clarify what the code is doing. | ||
+ | |||
+ | First, no code is complete without a little self glorification before the | ||
+ | origin and equates are setup... | ||
+ | |||
+ | ; | ||
+ | ; A Demo of disC=overy - By: Waveform | ||
+ | ; | ||
+ | ; (c) 1996 for disC=overy Magazine | ||
+ | ;"The Journal of the Commodore Enthusiast" | ||
+ | ; | ||
+ | |||
+ | The afore mentioned origin and equates... | ||
+ | |||
+ | *= $0820 | ||
+ | |||
+ | points | ||
+ | angleinc | ||
+ | curshape | ||
+ | theta = $0340 ; | ||
+ | radiu = $0350 ; | ||
+ | itheta | ||
+ | iradiu | ||
+ | |||
+ | scrloc | ||
+ | ; left corner of | ||
+ | ; char matrix on | ||
+ | ; screen | ||
+ | |||
+ | msin = $3000 ; | ||
+ | mcos = $3100 ; | ||
+ | msqr = $3200 ; | ||
+ | lobyte | ||
+ | hibyte0 | ||
+ | hibyte1 | ||
+ | bitmask | ||
+ | |||
+ | Okay: " | ||
+ | " | ||
+ | " | ||
+ | has three predefined shapes. Feel free to experiment | ||
+ | with the ones I included or add your own! | ||
+ | " | ||
+ | shape can have up to 16 endpoints defined. To change this | ||
+ | " | ||
+ | these tables. | ||
+ | " | ||
+ | program executes. At any point in time, these two tables | ||
+ | hold the current values of all the endpoints for the | ||
+ | current shape. | ||
+ | " | ||
+ | | ||
+ | " | ||
+ | | ||
+ | are static and are not changed by the program. It is | ||
+ | | ||
+ | alter the coordinates of a endpoint relative to its | ||
+ | | ||
+ | " | ||
+ | the screen. 1196 places it in the center of the screen. | ||
+ | " | ||
+ | | ||
+ | that msin+0 = sine of 0 byts, msin+64 = sine of 64 byts, etc. | ||
+ | " | ||
+ | way as the sine table. | ||
+ | " | ||
+ | to the sine and cosine table, except that this table is a | ||
+ | table of squares of both positive and negative numbers. More | ||
+ | on this later. | ||
+ | " | ||
+ | plot routine as the low byte of the address where the | ||
+ | pixel to be plotted lives. | ||
+ | " | ||
+ | plot routine as the high byte in buffer #0 where the | ||
+ | pixel to be plotted lives. | ||
+ | " | ||
+ | be plotted lives. | ||
+ | " | ||
+ | the byte where the pixel to be plotted lives. | ||
+ | |||
+ | ; | ||
+ | jsr demoinit | ||
+ | ; | ||
+ | |||
+ | That's just a JSR to the demo initialization routine... | ||
+ | |||
+ | main lda #%11111110 ;scan matrix | ||
+ | sta $dc00 ; | ||
+ | lda $dc01 ; | ||
+ | |||
+ | cmp #%11101111 ;f1 | ||
+ | beq keyf1 | ||
+ | cmp #%11011111 ;f3 | ||
+ | beq keyf3 | ||
+ | cmp #%10111111 ;f5 | ||
+ | beq keyf5 | ||
+ | cmp #%11110111 ;f7 | ||
+ | beq keyf7 | ||
+ | cmp #%11111101 ;return | ||
+ | beq keyreturn | ||
+ | | ||
+ | lda #%01111111 ;scan matrix | ||
+ | sta $dc00 ; | ||
+ | lda $dc01 ; | ||
+ | |||
+ | cmp #%11101111 ;space | ||
+ | beq keyspace | ||
+ | cmp #%01111111 ;return | ||
+ | beq keystop | ||
+ | |||
+ | What the above does is scan the keyboard matrix to see if the user has | ||
+ | pressed one of the keys our program is looking for. Later on you'll see | ||
+ | that we disabled the CIA interrupts that cause the computer to scan the | ||
+ | keyboard, so we needed a way to tell if one of our " | ||
+ | pressed. We " | ||
+ | different rows of the keyboard matrix. | ||
+ | |||
+ | reentry | ||
+ | jsr drawshape | ||
+ | |||
+ | jmp main ;loop | ||
+ | ; | ||
+ | |||
+ | The little code snippet above is where the JSRs to the real workhorses | ||
+ | are. As you can see this demo is pretty simple. =) We rotate the current | ||
+ | shape, and then we draw the shape. Then we go back to the beginning and | ||
+ | see if the user wants to do something. | ||
+ | |||
+ | keyf1 jmp rotup | ||
+ | keyf3 jmp rotdn | ||
+ | keyf5 jmp expup | ||
+ | keyf7 jmp expdn | ||
+ | keyreturn | ||
+ | keyspace | ||
+ | keystop | ||
+ | ; | ||
+ | |||
+ | Above is a little jump table to the various little routines that do what | ||
+ | needs to be done when a key is pressed... | ||
+ | |||
+ | rotup inc angleinc | ||
+ | jmp reentry | ||
+ | ; | ||
+ | rotdn dec angleinc | ||
+ | jmp reentry | ||
+ | ; | ||
+ | | ||
+ | The above two " | ||
+ | run-though of the main loop. As you can see, referencing the endpoints as | ||
+ | radius and theta takes alot of the work out of rotating a shape about its | ||
+ | center! Each time the user presses the F1 key, the angle that the shape | ||
+ | rotates per run-though increases. Holding down the F1 key will cause the | ||
+ | shape to spin up quite rapidly. Indeed, if you continue to hold down the | ||
+ | F1 key, the speed of the rotation will appear to increase to the point | ||
+ | where it begins slowing down again. | ||
+ | |||
+ | Obviously, F1 increases the angle of rotation, and F3 will decrease it. | ||
+ | |||
+ | expup ldy #$00 | ||
+ | expup1 | ||
+ | clc | ||
+ | adc #$01 ;add one | ||
+ | cmp #63 ;at maximum? | ||
+ | bcc expup2 | ||
+ | lda #63 | ||
+ | expup2 | ||
+ | iny | ||
+ | cpy points | ||
+ | bne expup1 | ||
+ | |||
+ | jmp reentry | ||
+ | ; | ||
+ | expdn ldy #$00 | ||
+ | expdn1 | ||
+ | sec | ||
+ | sbc #$01 ; | ||
+ | cmp #2 ;at minimum? | ||
+ | bcs expdn2 | ||
+ | lda #2 | ||
+ | expdn2 | ||
+ | iny | ||
+ | cpy points | ||
+ | bne expdn1 | ||
+ | |||
+ | jmp reentry | ||
+ | ; | ||
+ | |||
+ | What the above two routines do is expand or shrink the shape. These two | ||
+ | routines work well because of the way our shapes are defined. The shapes | ||
+ | that come with this demo are all equidistant from the objects center. So, | ||
+ | it is easy to change the shapes size by altering all of the shapes radius | ||
+ | coordinate components. Obviously, more detailed shapes that have endpoints | ||
+ | that are at varying radii from the center will need improved routines if | ||
+ | the shape is to maintain its proportions through the expansion and | ||
+ | shrinking. | ||
+ | |||
+ | Notice the error checking, also. Bad things happen when are radii get too | ||
+ | large or too small for our line drawing routine to handle properly. | ||
+ | |||
+ | newshape | ||
+ | lda curshape | ||
+ | cmp #3 ;last shape? | ||
+ | bne newshape1 | ||
+ | lda #$00 ; | ||
+ | sta curshape | ||
+ | |||
+ | newshape1 | ||
+ | bne newshape2 | ||
+ | jsr initobj20 | ||
+ | jmp reentry | ||
+ | newshape2 | ||
+ | bne newshape3 | ||
+ | jsr initobj10 | ||
+ | jmp reentry | ||
+ | newshape3 | ||
+ | |||
+ | jmp reentry | ||
+ | ; | ||
+ | |||
+ | The above routine handles the users request to change the displayed shape. | ||
+ | There are better ways to handle this, but since this demo only has three | ||
+ | shapes built in to it, the quick and dirty way suffices without being | ||
+ | extremely bulky. | ||
+ | |||
+ | stoprot | ||
+ | sta angleinc | ||
+ | jmp reentry | ||
+ | ; | ||
+ | |||
+ | The above little bit of code simply stops the rotation of the current | ||
+ | shape. It merely stores a zero into " | ||
+ | of rotation applied to the shape. | ||
+ | |||
+ | stopdemo | ||
+ | sta $dc0d ; interrupts | ||
+ | |||
+ | jmp $fe66 ;exit via | ||
+ | ; kernal warm | ||
+ | ; start | ||
+ | ; | ||
+ | |||
+ | The above code resets the CIA to its default setting so that when our demo | ||
+ | exits back out to basic, the user is able to type something. =) It then | ||
+ | exits via the Kernal warm start vector to reset other important things | ||
+ | like the VIC chip. =) | ||
+ | |||
+ | rotate | ||
+ | rotate1 | ||
+ | clc | ||
+ | adc angleinc | ||
+ | sta theta, | ||
+ | iny | ||
+ | cpy points | ||
+ | bne rotate1 | ||
+ | |||
+ | rts | ||
+ | ; | ||
+ | |||
+ | Okay, there it is. The code that actually rotates the shape. It takes the | ||
+ | current value of the theta coordinate for each endpoint and adds the value | ||
+ | of " | ||
+ | it up a bit by unrolling this loop, but for this demo, there aren't enough | ||
+ | endpoints to work through to get that much of a savings. | ||
+ | |||
+ | drawshape | ||
+ | |||
+ | lda theta, | ||
+ | sta gxytheta | ||
+ | lda radiu, | ||
+ | sta gxyradius | ||
+ | jsr getxy ; | ||
+ | |||
+ | lda xpos ; | ||
+ | | ||
+ | adc #64 ; Cartesian | ||
+ | sta x1 ; | ||
+ | lda ypos ; | ||
+ | | ||
+ | adc #64 ; | ||
+ | sta y1 ; | ||
+ | |||
+ | The above code starts off the shape drawing routine. It gets the first | ||
+ | endpoint prior to entering the loop below. The loop below joins endpoint | ||
+ | to endpoint with a line. | ||
+ | |||
+ | You will notice that we do actually convert from the radius, | ||
+ | to the X,Y system, finally. This is necessary because I haven' | ||
+ | written a routine that will draw lines only from radius and theta. =) Also | ||
+ | notice that it adds 64 to both the X and Y coordinate. This is to center | ||
+ | the shape in our character matrix. The routine that converts from | ||
+ | radius, | ||
+ | also normalizes our coordinates to make them easier to plot in our | ||
+ | character matrix. (It makes the range of possible values equal to 0 to | ||
+ | +127) | ||
+ | |||
+ | ldy #$01 ; | ||
+ | ds2 lda theta,y | ||
+ | sta gxytheta | ||
+ | lda radiu,y | ||
+ | sta gxyradius | ||
+ | jsr getxy | ||
+ | lda xpos | ||
+ | clc | ||
+ | adc #64 | ||
+ | sta x2 | ||
+ | lda ypos | ||
+ | clc | ||
+ | adc #64 | ||
+ | sta y2 | ||
+ | sty dsy ;preserve .y | ||
+ | jsr drawline | ||
+ | ldy dsy ;restore .y | ||
+ | |||
+ | lda x2 ;make endpoint | ||
+ | sta x1 ; of this line | ||
+ | lda y2 ; start point | ||
+ | sta y1 ; of next line | ||
+ | |||
+ | | ||
+ | cpy points | ||
+ | bne ds2 ; | ||
+ | |||
+ | That's the routine that puts the shape on the screen. You may of noticed | ||
+ | that each shape has one extra endpoint. For example, the triangle has four | ||
+ | endpoints. This is to allow the shape drawing routine a place to end the | ||
+ | last line. We of course, want to make the last endpoint equal to the first | ||
+ | endpoint. This causes the routine to complete the shape. | ||
+ | |||
+ | lda $d018 ;show our work | ||
+ | eor #%00000010 ; | ||
+ | sta $d018 ; | ||
+ | |||
+ | Once we have finished drawing the shape in the buffer, we tell the VIC to | ||
+ | display the buffer. This technique is known as double buffering. We | ||
+ | display one buffer, while we do all our work in the other. When we are | ||
+ | done with the work, we display that buffer, and do all our work in the | ||
+ | first. | ||
+ | |||
+ | A nifty trick in situations like these is to use the VIC to our advantage. | ||
+ | Since we are using a character matrix to do our drawing in, we make both | ||
+ | of our buffers in sequential character matrix slots in memory. Then to | ||
+ | swap the displayed buffers, we merely toggle a bit in the VIC register | ||
+ | that tells the VIC which matrix to display. =) | ||
+ | |||
+ | A little confused? Well hopefully this will clear things up, at least a | ||
+ | little. We start our program with the VIC pointing at buffer#0 which is at | ||
+ | $2000. Bits 3,2,and 1 of $d018 control which address the VIC finds the | ||
+ | character matrix. Here is a nifty table showing the addresses which | ||
+ | correlate to those bits in $d018: | ||
+ | |||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | You can see how we can make the VIC do all the work of displaying the | ||
+ | right buffer. All we have to do is toggle bit 1 of $d018 to make the VIC | ||
+ | switch between the character matrix at $2000 and the one at $2800. | ||
+ | |||
+ | lda $d018 ;clear the | ||
+ | and #%00000010 ; next buffer | ||
+ | beq ds4 ; for drawing | ||
+ | jsr blank0 | ||
+ | jmp ds5 ; | ||
+ | ds4 jsr blank1 | ||
+ | |||
+ | This code checks which buffer is currently being displayed and the JSRs to | ||
+ | the routine to clear the OTHER one, in preparation for drawing in. | ||
+ | |||
+ | ds5 rts | ||
+ | |||
+ | dsy .byte $00 | ||
+ | ; | ||
+ | |||
+ | Finish up... " | ||
+ | |||
+ | getxy sty gxyy ; | ||
+ | sta gxya ; | ||
+ | |||
+ | ldy gxytheta | ||
+ | lda mcos, | ||
+ | | ||
+ | sbc gxyradius | ||
+ | | ||
+ | lda msqr, | ||
+ | sta gxytemp | ||
+ | |||
+ | ldy gxytheta | ||
+ | lda mcos, | ||
+ | | ||
+ | adc gxyradius | ||
+ | | ||
+ | lda msqr, | ||
+ | | ||
+ | sbc gxytemp | ||
+ | sta xpos ;=x coordinate | ||
+ | |||
+ | Ah, now you get to see the fast multiplication in action. This routine is | ||
+ | based heavily on the routine outlined by Steven Judd in C=Hacking. He has | ||
+ | already documented fairly well how the table of squares work, so I'll not | ||
+ | re-invent the wheel by explaining precisely how it works. What I will do | ||
+ | is step you through the steps of what this code does. | ||
+ | |||
+ | Okay, know that you can arrive at A*B with a function like: | ||
+ | |||
+ | | ||
+ | |||
+ | I created a table of squares such that the offset from the beginning of | ||
+ | the table was the (x) in the f(x) above. For example, location $3300 + 0 = | ||
+ | 0, since obviously (0^2)/4 is still 0. By the same token, $3300 + 9 = 20, | ||
+ | since (9^2)/4 = 20.25. | ||
+ | |||
+ | In our case, the radius component is the (A) and the (cos (theta)) | ||
+ | component is the (B). Those who didn't sleep through trigonometry class | ||
+ | will recall that when converting from radius, | ||
+ | coordinate is R * cos(theta). Hence, our A*B routine. | ||
+ | |||
+ | Those that have read ahead, will note that I made some adjustments to the | ||
+ | tables to the effect that the sin and cos tables are multiplied by 64, and | ||
+ | that our table of squares is really the result of a function where f(x) = | ||
+ | (x^2)/4*64. The reason for this is to maintain some level of accuracy in | ||
+ | our tables. If you don't understand why this was done, I'll explain it | ||
+ | further when I detail the basic programs that build our tables. | ||
+ | |||
+ | ldy gxytheta | ||
+ | lda msin, | ||
+ | | ||
+ | sbc gxyradius | ||
+ | | ||
+ | lda msqr, | ||
+ | sta gxytemp | ||
+ | |||
+ | ldy gxytheta | ||
+ | lda msin, | ||
+ | | ||
+ | adc gxyradius | ||
+ | | ||
+ | lda msqr, | ||
+ | | ||
+ | sbc gxytemp | ||
+ | sta ypos ;=y coordinate | ||
+ | |||
+ | ldy gxyy ; | ||
+ | lda gxya ; | ||
+ | |||
+ | rts | ||
+ | ; | ||
+ | | ||
+ | gxyradius | ||
+ | gxytheta | ||
+ | | ||
+ | xpos .byte $00 | ||
+ | ypos .byte $00 | ||
+ | |||
+ | gxyy .byte $00 | ||
+ | gxya .byte $00 | ||
+ | gxytemp | ||
+ | ; | ||
+ | |||
+ | The code above does exactly the same thing the last piece of code did, | ||
+ | except this time we were calculating the Y coordinate, and of course, to | ||
+ | be trigonomically correct, our multiplication performs this equation: | ||
+ | |||
+ | Y = R * sin(theta). | ||
+ | |||
+ | The labels at the end are for temporary storage of values during the | ||
+ | multiplication routine. | ||
+ | |||
+ | plot | ||
+ | |||
+ | lda $d018 ;plot in | ||
+ | and #%00000010 ; correct | ||
+ | bne plot2 ; buffer | ||
+ | |||
+ | plot1 lda lobyte, | ||
+ | sta $02 ; | ||
+ | lda hibyte1, | ||
+ | sta $03 ; | ||
+ | lda ($02), | ||
+ | ora bitmask, | ||
+ | sta ($02), | ||
+ | |||
+ | | ||
+ | rts | ||
+ | ; | ||
+ | plot2 lda lobyte, | ||
+ | sta $02 ; | ||
+ | lda hibyte0, | ||
+ | sta $03 ; | ||
+ | lda ($02), | ||
+ | ora bitmask, | ||
+ | sta ($02), | ||
+ | |||
+ | | ||
+ | rts | ||
+ | ; | ||
+ | |||
+ | Shoooom! There it is! If you blinked you may of missed it. The plot | ||
+ | routine is quite small, and its even smaller if your program doesn' | ||
+ | to decide which buffer it needs to plot in. | ||
+ | |||
+ | In detail, first it checks which buffer we are working in and branches to | ||
+ | the appropriate plot routine. | ||
+ | |||
+ | How it works: the X register holds our X coordinate, and the Y register | ||
+ | holds our Y coordinate. Both our X and Y registers hold a value between 0 | ||
+ | and 127. Knowing that, we can quickly and easily use some cleverly thought | ||
+ | out planning ahead to make plotting a pixel super fast and very clean. | ||
+ | |||
+ | Things we planned for ahead of time: We drew our character grid on the | ||
+ | screen such that the addresses that define the character data are laid out | ||
+ | to our advantage. Then we made a couple tables with data that makes it | ||
+ | extremely simple to look up the address of the byte where we want to plot | ||
+ | a pixel. | ||
+ | |||
+ | Sound a little vague? I'll explain in greater detail when we get to the | ||
+ | basic programs that set up the tables and also the little routine that | ||
+ | lays out our character grid. | ||
+ | |||
+ | First, it looks up an address based on our X coordinate. Each byte has 8 | ||
+ | pixels so, our table looks like this: | ||
+ | |||
+ | $3300 $00 $00 $00 $00 $00 $00 $00 $00 | ||
+ | $3308 $80 $80 $80 $80 $80 $80 $80 $80 | ||
+ | $3310 $00 $00 $00 $00 $00 $00 $00 $00 | ||
+ | $3318 $80 $80 $80 $80 $80 $80 $80 $80 | ||
+ | ... | ||
+ | |||
+ | Notice that the values change every eight locations? Each byte has eight | ||
+ | pixels. For example, imagine that the X coordinate is 0. We get the first | ||
+ | (0th) byte from our low byte table and see its a zero. Its easy to see | ||
+ | that we would always be plotting in the first (0th) byte until X is | ||
+ | greater than 7 (i.e.: 8) in which case the value in our low byte table is | ||
+ | $80. $80 is 128 bytes over from our first column of bytes, and holds the | ||
+ | byte that has pixels 8-15 in it. If you are still confused, I will explain | ||
+ | this further when we get to the routine that builds our character grid on | ||
+ | the screen. | ||
+ | |||
+ | Now that we have the low byte, we need a high byte. Again we look into a | ||
+ | cleverly planned table using X as our index. The high byte table looks | ||
+ | alot like the low byte table except that we change values every sixteen | ||
+ | bytes. Take a look: | ||
+ | |||
+ | $3380 $20 $20 $20 $20 $20 $20 $20 $20 | ||
+ | $3388 $20 $20 $20 $20 $20 $20 $20 $20 | ||
+ | $3390 $21 #21 $21 $21 $21 $21 $21 $21 | ||
+ | $3398 $21 #21 $21 $21 $21 $21 $21 $21 | ||
+ | ... | ||
+ | |||
+ | This is because each column only has 128 pixels in it. If the X coordinate | ||
+ | is 0-7, then the byte that holds our pixel is at $2000. When the X | ||
+ | coordinate is 8-15, the byte is at $2080. Only when X is 16-23 does our | ||
+ | high byte change from $20 to, in this example, $21, since the bye we are | ||
+ | looking for would be $2100. | ||
+ | |||
+ | Now, we got an address based on the value of X. But what about Y? We | ||
+ | aren't always going to be plotting in the first top row of pixels! Since | ||
+ | we were clever in how we laid out our character grid, Y becomes an offset | ||
+ | from our address. We figured our how far to go across in bytes from a | ||
+ | table, but we don't need a table to figure out how far down to go. We | ||
+ | simply address the byte using indexed addressing. =) So, for example if | ||
+ | our Y coordinate was 5, the instruction LDA ($02),Y would get the 5th byte | ||
+ | down from the byte we arrived at earlier. | ||
+ | |||
+ | Nifty eh? | ||
+ | |||
+ | After we get the byte, we need to turn on a pixel. This is where the | ||
+ | bitmask table comes in. Our bitmask table looks like this: | ||
+ | |||
+ | $3480 $80 $40 $20 $10 $08 $04 $02 $01 | ||
+ | $3488 $80 $40 $20 $10 $08 $04 $02 $01 | ||
+ | $3490 $80 $40 $20 $10 $08 $04 $02 $01 | ||
+ | $3498 $80 $40 $20 $10 $08 $04 $02 $01 | ||
+ | ... | ||
+ | |||
+ | It should be obvious how this table works. This table allows us to arrive | ||
+ | at the right pixel to turn on (via ORA) when we plot. Notice that it | ||
+ | repeats every eight bytes, and notice that there are eight pixels in a | ||
+ | byte. =) | ||
+ | |||
+ | drawline | ||
+ | lda x1 ; | ||
+ | sbc x2 ; | ||
+ | sta dx ; | ||
+ | |||
+ | | ||
+ | lda y1 ; | ||
+ | sbc y2 ; | ||
+ | sta dy ; | ||
+ | |||
+ | | ||
+ | lda dx ; | ||
+ | bpl drawline2 | ||
+ | eor #%11111111 ;make dx | ||
+ | adc #%00000001 ; positive | ||
+ | sta dx ; | ||
+ | | ||
+ | lda dy ; | ||
+ | bpl drawline1 | ||
+ | eor #%11111111 ;make dy | ||
+ | adc #%00000001 ; positive | ||
+ | sta dy ; | ||
+ | |||
+ | lda dx | ||
+ | cmp dy | ||
+ | bcs dl00 | ||
+ | jmp dl10 | ||
+ | |||
+ | drawline1 | ||
+ | cmp dy | ||
+ | bcs dl20 | ||
+ | jmp dl30 | ||
+ | |||
+ | drawline2 | ||
+ | bpl drawline3 | ||
+ | eor #%11111111 | ||
+ | adc #%00000001 | ||
+ | sta dy | ||
+ | |||
+ | lda dx | ||
+ | cmp dy | ||
+ | bcs drawline4 | ||
+ | jmp dl50 | ||
+ | |||
+ | drawline3 | ||
+ | cmp dy | ||
+ | bcs drawline5 | ||
+ | jmp dl70 | ||
+ | el rts | ||
+ | |||
+ | drawline4 | ||
+ | drawline5 | ||
+ | ; | ||
+ | |||
+ | The above code determines the slope of the line to be drawn. This is the | ||
+ | part of my code that I *know* in my gut I can improve on, but as of yet | ||
+ | have been unable to. There are eight separate little routines below that | ||
+ | draw a line. Each one is slightly different, they are written to handle | ||
+ | each of the eight possible types of line slopes that can be encountered | ||
+ | when drawing a line between two points. | ||
+ | |||
+ | The values DX and DY are calculated such that DX=X2-X1, and DY=Y2-Y1. | ||
+ | |||
+ | dl00 lda #$00 ;0 - dx | ||
+ | sbc dx ; | ||
+ | |||
+ | ldx x1 ;plot first | ||
+ | ldy y1 ; pixel | ||
+ | jsr plot ; | ||
+ | |||
+ | dl00a clc ;step in x | ||
+ | | ||
+ | adc dy ; until time | ||
+ | bcc dl00b ; to take a | ||
+ | | ||
+ | sbc dx ; step in y | ||
+ | dl00b jsr plot ; | ||
+ | cpx x2 ; | ||
+ | bne dl00a ; | ||
+ | |||
+ | rts | ||
+ | ; | ||
+ | |||
+ | All eight of these routines are basically the same with only two real | ||
+ | differences: | ||
+ | |||
+ | 1) The value which is being stepped through until it's time to step the | ||
+ | other value. | ||
+ | 2) The direction of the stepping | ||
+ | |||
+ | Here is a step-by-step description of what the above does: | ||
+ | |||
+ | 1. Subtract DX (change in X) from zero | ||
+ | 2. Plot the first pixel | ||
+ | 3. Step upwards in values of X, until it is time to take a step in Y | ||
+ | 4. Plot the pixel | ||
+ | 5. Loop back until we've reached the second endpoint | ||
+ | |||
+ | Each of the following routines works exactly the same way. | ||
+ | |||
+ | dl10 lda #$00 | ||
+ | sbc dy | ||
+ | |||
+ | ldx x1 | ||
+ | ldy y1 | ||
+ | jsr plot | ||
+ | |||
+ | dl10a clc ;step +y | ||
+ | | ||
+ | adc dx ; to +x | ||
+ | bcc dl10b ; | ||
+ | | ||
+ | sbc dy ; | ||
+ | dl10b jsr plot ; | ||
+ | cpy y2 ; | ||
+ | bne dl10a ; | ||
+ | | ||
+ | rts | ||
+ | ; | ||
+ | dl20 lda #$00 | ||
+ | sbc dx | ||
+ | | ||
+ | ldx x1 | ||
+ | ldy y1 | ||
+ | jsr plot | ||
+ | | ||
+ | dl20a clc ;step +x | ||
+ | | ||
+ | adc dy ; to -y | ||
+ | bcc dl20b ; | ||
+ | | ||
+ | sbc dx ; | ||
+ | dl20b jsr plot ; | ||
+ | cpx x2 ; | ||
+ | bne dl20a ; | ||
+ | | ||
+ | rts | ||
+ | ; | ||
+ | dl30 lda #$00 | ||
+ | sbc dy | ||
+ | | ||
+ | ldx x1 | ||
+ | ldy y1 | ||
+ | jsr plot | ||
+ | | ||
+ | dl30a clc ;step -y | ||
+ | | ||
+ | adc dx ; to +x | ||
+ | bcc dl30b ; | ||
+ | | ||
+ | sbc dy ; | ||
+ | dl30b jsr plot ; | ||
+ | cpy y2 ; | ||
+ | bne dl30a ; | ||
+ | |||
+ | rts | ||
+ | ; | ||
+ | dl40 lda #$00 | ||
+ | sbc dx | ||
+ | |||
+ | ldx x1 | ||
+ | ldy y1 | ||
+ | jsr plot | ||
+ | |||
+ | dl40a clc ;step -x | ||
+ | | ||
+ | adc dy ; to +y | ||
+ | bcc dl40b ; | ||
+ | | ||
+ | sbc dx ; | ||
+ | dl40b jsr plot ; | ||
+ | cpx x2 ; | ||
+ | bne dl40a ; | ||
+ | | ||
+ | rts | ||
+ | ; | ||
+ | dl50 lda #$00 | ||
+ | sbc dy | ||
+ | |||
+ | ldx x1 | ||
+ | ldy y1 | ||
+ | jsr plot | ||
+ | |||
+ | dl50a clc ;step +y | ||
+ | | ||
+ | adc dx ; to -x | ||
+ | bcc dl50b ; | ||
+ | | ||
+ | sbc dy ; | ||
+ | dl50b jsr plot ; | ||
+ | cpy y2 ; | ||
+ | bne dl50a ; | ||
+ | | ||
+ | rts | ||
+ | ; | ||
+ | dl60 lda #$00 | ||
+ | sbc dx | ||
+ | | ||
+ | ldx x1 | ||
+ | ldy y1 | ||
+ | jsr plot | ||
+ | |||
+ | dl60a clc ;step -x | ||
+ | | ||
+ | adc dy ; to -y | ||
+ | bcc dl60b ; | ||
+ | | ||
+ | sbc dx ; | ||
+ | dl60b jsr plot ; | ||
+ | cpx x2 ; | ||
+ | bne dl60a ; | ||
+ | |||
+ | rts | ||
+ | ; | ||
+ | dl70 lda #$00 | ||
+ | sbc dy | ||
+ | |||
+ | ldx x1 | ||
+ | ldy y1 | ||
+ | jsr plot | ||
+ | |||
+ | dl70a clc ;step -y | ||
+ | | ||
+ | adc dx ; to -x | ||
+ | bcc dl70b ; | ||
+ | | ||
+ | sbc dy ; | ||
+ | dl70b jsr plot ; | ||
+ | cpy y2 ; | ||
+ | bne dl70a ; | ||
+ | |||
+ | rts | ||
+ | ; | ||
+ | x1 .byte $00 | ||
+ | y1 .byte $00 | ||
+ | x2 .byte $00 | ||
+ | y2 .byte $00 | ||
+ | dx .byte $00 | ||
+ | dy .byte $00 | ||
+ | ; | ||
+ | |||
+ | The values above are temporary holding places for the values used and | ||
+ | generated by the drawline routines. | ||
+ | |||
+ | demoinit | ||
+ | sta $d020 ; background | ||
+ | sta $d021 ; and border | ||
+ | |||
+ | lda #147 ;clear screen | ||
+ | jsr $ffd2 ; | ||
+ | |||
+ | lda #$7f ;disable cia | ||
+ | sta $dc0d ; time irqs | ||
+ | |||
+ | jsr blank0 | ||
+ | jsr blank1 | ||
+ | |||
+ | lda #$00 | ||
+ | sta char | ||
+ | |||
+ | ldy #$00 ;dirty way to | ||
+ | demoinit4 | ||
+ | sta $fa ; character | ||
+ | lda #> | ||
+ | sta $fb ; the screen | ||
+ | ldx #$00 ; | ||
+ | demoinit5 | ||
+ | sta ($fa), | ||
+ | inc char ; is the | ||
+ | lda $fa ; value in | ||
+ | | ||
+ | adc #40 ; | ||
+ | sta $fa ; | ||
+ | bcc demoinit6 | ||
+ | inc $fb ; | ||
+ | demoinit6 | ||
+ | cpx #16 ; | ||
+ | bne demoinit5 | ||
+ | | ||
+ | cpy #16 ; | ||
+ | bne demoinit4 | ||
+ | |||
+ | ldy #$00 ;fill color | ||
+ | lda #$01 ; memory with | ||
+ | demoinit10 sta $d800, | ||
+ | sta $d900, | ||
+ | sta $da00, | ||
+ | sta $db00, | ||
+ | | ||
+ | bne demoinit10 | ||
+ | |||
+ | lda $d018 ; | ||
+ | and # | ||
+ | ora # | ||
+ | sta $d018 ; | ||
+ | |||
+ | jsr initobj00 | ||
+ | |||
+ | lda #$00 ;init | ||
+ | sta angleinc | ||
+ | sta curshape | ||
+ | |||
+ | rts | ||
+ | |||
+ | char .byte $00 | ||
+ | ; | ||
+ | |||
+ | The above initialization routine sets everything up for the demo. In order | ||
+ | of appearance, here is an explanation of what each part of this | ||
+ | initialization does. | ||
+ | |||
+ | 1. Turn the border and background dark blue. | ||
+ | 2. Clear the screen | ||
+ | 3. Disable the CIA timer IRQs | ||
+ | 4. Clear both of the drawing buffers | ||
+ | 5. Draw our character grid on the screen | ||
+ | 6. Fill Color RAM with WHITE | ||
+ | 7. Point the VIC to the first buffer (Buffer#0) | ||
+ | 8. Initialize the first shape | ||
+ | 9. Initialize the " | ||
+ | |||
+ | Drawing the character grid on the screen could possibly of been done a | ||
+ | little better. But as it says in the comments, its quick and dirty, but it | ||
+ | does the job. =) | ||
+ | |||
+ | Using our brains a bit, we draw the character grid on the screen in such a | ||
+ | way that starting with zero (the @ sign) we place sequential characters on | ||
+ | the screen in columns, 16 down, by 16 across. This enables us to use a | ||
+ | very nifty and fast plotter as described above. Laying out our character | ||
+ | grid in this manner, gives us 128 sequential bytes in the first column, | ||
+ | 128 sequential bytes in the second, and so on. | ||
+ | |||
+ | You can make character grids of any size (up to 16*16) in this manner very | ||
+ | easily. A smaller grid frees up characters to be used for other things, | ||
+ | like say a niftycool border or other graphics to be used along side of the | ||
+ | character grid where your vectors are being drawn. You just have to adjust | ||
+ | your low byte and high byte tables accordingly. | ||
+ | |||
+ | blank0 | ||
+ | lda #$00 | ||
+ | blank0a | ||
+ | sta $2080,y | ||
+ | sta $2100,y | ||
+ | sta $2180,y | ||
+ | sta $2200,y | ||
+ | sta $2280,y | ||
+ | sta $2300,y | ||
+ | sta $2380,y | ||
+ | sta $2400,y | ||
+ | sta $2480,y | ||
+ | sta $2500,y | ||
+ | sta $2580,y | ||
+ | sta $2600,y | ||
+ | sta $2680,y | ||
+ | sta $2700,y | ||
+ | sta $2780,y | ||
+ | iny | ||
+ | bpl blank0a | ||
+ | rts | ||
+ | ; | ||
+ | |||
+ | Clears the first buffer (buffer#0). Unfortunately, | ||
+ | are real cycle hogs. There just aren't many fast ways to zero out 4k of | ||
+ | memory. | ||
+ | |||
+ | blank1 | ||
+ | lda #$00 | ||
+ | blank1a | ||
+ | sta $2880,y | ||
+ | sta $2900,y | ||
+ | sta $2980,y | ||
+ | sta $2a00,y | ||
+ | sta $2a80,y | ||
+ | sta $2b00,y | ||
+ | sta $2b80,y | ||
+ | sta $2c00,y | ||
+ | sta $2c80,y | ||
+ | sta $2d00,y | ||
+ | sta $2d80,y | ||
+ | sta $2e00,y | ||
+ | sta $2e80,y | ||
+ | sta $2f00,y | ||
+ | sta $2f80,y | ||
+ | iny | ||
+ | bpl blank1a | ||
+ | rts | ||
+ | ; | ||
+ | |||
+ | The above clears the second buffer (buffer# | ||
+ | |||
+ | initobj00 | ||
+ | sta points | ||
+ | |||
+ | ldy #$00 | ||
+ | initobj01 | ||
+ | sta itheta,y | ||
+ | sta theta,y | ||
+ | lda radiu00,y | ||
+ | sta iradiu,y | ||
+ | sta radiu,y | ||
+ | iny | ||
+ | cpy points | ||
+ | bne initobj01 | ||
+ | rts | ||
+ | ; | ||
+ | initobj10 | ||
+ | sta points | ||
+ | |||
+ | ldy #$00 | ||
+ | initobj11 | ||
+ | sta itheta,y | ||
+ | sta theta,y | ||
+ | lda radiu10,y | ||
+ | sta iradiu,y | ||
+ | sta radiu,y | ||
+ | iny | ||
+ | cpy points | ||
+ | bne initobj11 | ||
+ | rts | ||
+ | ; | ||
+ | initobj20 | ||
+ | sta points | ||
+ | |||
+ | ldy #$00 | ||
+ | initobj21 | ||
+ | sta itheta,y | ||
+ | sta theta,y | ||
+ | lda radiu20,y | ||
+ | sta iradiu,y | ||
+ | sta radiu,y | ||
+ | iny | ||
+ | cpy points | ||
+ | bne initobj21 | ||
+ | rts | ||
+ | ; | ||
+ | theta00 | ||
+ | radiu00 | ||
+ | |||
+ | theta10 | ||
+ | radiu10 | ||
+ | |||
+ | theta20 | ||
+ | .byte 224, | ||
+ | radiu20 | ||
+ | .byte 40,40,40,40 | ||
+ | | ||
+ | The above short routines are the three that define the three shapes I | ||
+ | included in this demo. The initialization for a shape is very simple. You | ||
+ | store the number of endpoints in the " | ||
+ | information from the appropriate table into the " | ||
+ | tables. I also copy the values into the " | ||
+ | case there is a routine that needs to reference the initial values rather | ||
+ | than the current values. | ||
+ | |||
+ | Also note, that as mentioned before, the last end point is the same as the | ||
+ | first endpoint. This closes off the shape by drawing a line back to the | ||
+ | first endpoint. | ||
+ | |||
+ | Well, there you have it! Documented source code for a demonstration of a | ||
+ | different way to play with two-dimensional vectors. What follows are the | ||
+ | basic programs used to create the data tables used by the above program, | ||
+ | and of course, the uuencoded programs themselves. | ||
+ | |||
+ | The BASIC Programs | ||
+ | ------------------ | ||
+ | The first program is the program which builds the sine, cosine and squares | ||
+ | tables. | ||
+ | |||
+ | 1 rem -+- make sin/cos/sqr 2.0 -+- | ||
+ | 2 rem -+- -+- | ||
+ | 3 rem -+- by: waveform | ||
+ | 4 rem -+- for: disC=overy magazine -+- | ||
+ | 5 rem -+- on: 09-14-96 | ||
+ | |||
+ | 10 rem ::: make sin and cos tables ::: | ||
+ | 12 ba=12288: | ||
+ | |||
+ | " | ||
+ | |||
+ | 14 forby=0to255 | ||
+ | 16 de=by*1.407: | ||
+ | 18 s=int((sin(ra)*64)+.5) | ||
+ | 20 ifs< | ||
+ | 22 c=int((cos(ra)*64)+.5) | ||
+ | 24 ifc< | ||
+ | 26 poke ba + by,s:poke ba + 256 + by,c | ||
+ | 28 next | ||
+ | |||
+ | The above loop makes both the sine table and the cosine table. Also please | ||
+ | note that in place of the word " | ||
+ | use Commodore Basic' | ||
+ | |||
+ | Step-by-step, | ||
+ | |||
+ | 1. Convert from byts to degrees. | ||
+ | 2. Convert from degrees to radians. This is necessary since Commodore' | ||
+ | Basic trigonomic functions work with radians. | ||
+ | 3. Calculate the sine of the angle. | ||
+ | 4. If the sine is less than 0 (i.e.. negative) adjust the number. | ||
+ | 5. Calculate the cosine of the angle | ||
+ | 6. If the cosine is negative, adjust the number. | ||
+ | 7. Store both the sine and cosine in their tables. | ||
+ | 8. Loop back until all 256 byts have been calculated and stored in a | ||
+ | table. | ||
+ | |||
+ | When we calculate the sine or cosine of an angle, it becomes quickly | ||
+ | evident that except when the result is 0 or 1, the result is always a | ||
+ | decimal number. Our Commodores don't have a quick and easy way to store a | ||
+ | decimal number, certainly not in one byte. | ||
+ | |||
+ | Rather than mess with program code to deal with decimal numbers, we can | ||
+ | resolve this issue rather quickly by some planning ahead here when we | ||
+ | create our tables. | ||
+ | |||
+ | If we take a decimal number, for instance .707107 (the sine of 45 degrees, | ||
+ | or 32 byts) and multiply that number by a constant, in our case 64, we can | ||
+ | arrive at a number which is much easier for our computers to store: 45.25. | ||
+ | The fractional part of the number is chopped off when we store the value | ||
+ | to memory, but what remains is a value that we can work with. | ||
+ | |||
+ | Incidentally, | ||
+ | caused when the fractional portion of the number is chopped off. | ||
+ | |||
+ | What is this about " | ||
+ | store a decimal number now, but what about a number that's negative? | ||
+ | |||
+ | One of the nifty things about the math instructions on a Commodore is that | ||
+ | they work the same for both unsigned and signed arithmetic. But to store a | ||
+ | negative number you have to use twos compliment. To arrive at the twos | ||
+ | compliment (negative) number, you flip all of the bits, and add one. | ||
+ | Commodore basic doesn' | ||
+ | thing: subtract the number from 255. Subtracting a number from 255 has the | ||
+ | same effect as EORing a number by %11111111, which would, of course, flip | ||
+ | all of the bits. We then add one, and POOF, the twos complement of a | ||
+ | number. | ||
+ | |||
+ | The niftiness about twos compliment and signed values in assembly, is that | ||
+ | it gives us a way to represent negative numbers. When you ADD or SBC with | ||
+ | these numbers, the instructions work just as they did before, but the | ||
+ | perform the task you'd expect by using a negative number. | ||
+ | |||
+ | Steven Judd talks a bit about this in the same articles for C=Hacking that | ||
+ | he wrote detailing how the table of squares works for fast multiplication. | ||
+ | Also, you can read more about this in virtually any book or reference work | ||
+ | that talks about the 65xx line of microprocessors. | ||
+ | |||
+ | Having mentioned that, how do we arrive at the right answer later on if | ||
+ | one of our values has been multiplied by a factor of 64? Read on... | ||
+ | |||
+ | 50 rem ::: make sqr table ::: | ||
+ | 52 forby=0to127 | ||
+ | 54 sq=(by*by)/ | ||
+ | 56 poke ba + 512 +by,sq:next | ||
+ | 60 forby=0to127 | ||
+ | 62 sq=(by*by)/ | ||
+ | 64 poke ba + 512 +255-by, | ||
+ | |||
+ | The above loop creates a table of squares. I did a little planning ahead | ||
+ | as well, and realized that the table need only be 128 bytes long. Recall | ||
+ | our niftycool formula for fast multiplication: | ||
+ | |||
+ | When we do the first part of the formula (f(a+b)) you can see from how our | ||
+ | program works that the radius is never larger than 64, and at the most, | ||
+ | the value from our sine table will be is 64. Remember from trig class that | ||
+ | sines and cosines range from 0 to 1. =) Since we multiplied our sine and | ||
+ | cosine values by 64, the largest value we could ever get is 64. | ||
+ | |||
+ | Well, you say, but since the values you pull from your sine and cosine | ||
+ | table can be negative, what happens if the result of A+B or A-B is | ||
+ | negative? The result of a squaring will always be positive, so we don't | ||
+ | have to store any numbers using twos compliment, but we will end up with | ||
+ | situations where the sum or the difference of A and B will be negative. We | ||
+ | resolve this by building a second table of squares above the first, and we | ||
+ | build it downward in memory. | ||
+ | |||
+ | Hopefully, this example will clear up any confusion: | ||
+ | |||
+ | If A (our radius) is 20, and the sine of our angle is -40, the sum of A | ||
+ | and B is -20. The value in the register is 236, which means -20 in two | ||
+ | complement. Since we built another table above the first 128 byte table of | ||
+ | squares, we are free and clear. Our foresight put the correct value for | ||
+ | f(-20) in that location. | ||
+ | |||
+ | Now, there is one more issue to deal with. We multiplied all of our sines | ||
+ | and cosines by a factor of 64. Somewhere we have to divide by 64 to keep | ||
+ | the equation equal (and to not freak out my 7th grade algebra instructor) | ||
+ | so we take a look at the function we have for f(x): | ||
+ | |||
+ | | ||
+ | |||
+ | We realize we can do that *divide-by-64* thing here, to arrive at: | ||
+ | |||
+ | | ||
+ | |||
+ | Or, written another way: | ||
+ | |||
+ | | ||
+ | |||
+ | I left it as 4*64 in the program for clarity sake. | ||
+ | |||
+ | 100 rem ::: save to disk ::: | ||
+ | 102 open1, | ||
+ | 104 open2, | ||
+ | 106 print# | ||
+ | 108 fort=0to767 | ||
+ | 110 print# | ||
+ | 112 next | ||
+ | 114 close2 | ||
+ | |||
+ | The above simply saves the table we created to disk, with a loading | ||
+ | address of $3000. =) Just what the demo needs! =) | ||
+ | |||
+ | Now, on to the second program... | ||
+ | |||
+ | The second program is the program to build the low byte, high byte and | ||
+ | bitmask tables. | ||
+ | |||
+ | 1 rem -+- make base/mask table 2.0 -+- | ||
+ | 2 rem -+- -+- | ||
+ | 3 rem -+- by: waveform | ||
+ | 4 rem -+- for: disC=overy magazine -+- | ||
+ | 5 rem -+- on: 09-14-96 | ||
+ | |||
+ | 10 ba = 13056 : cb=8192 | ||
+ | |||
+ | " | ||
+ | " | ||
+ | |||
+ | 12 bh=int(ba/ | ||
+ | 100 c=0:fort=0 to 15 | ||
+ | 102 forq=0to7 | ||
+ | 104 b1=cb+(t*128) | ||
+ | 106 hb=int(b1/ | ||
+ | 108 poke ba + c,lb:poke ba + 128 + c,hb | ||
+ | 110 c=c+1: | ||
+ | |||
+ | The above loop creates the low byte table as well as the high byte table | ||
+ | for the first buffer (buffer#0) | ||
+ | |||
+ | 120 c=0:fort=0 to 15 | ||
+ | 122 forq=0to7 | ||
+ | 124 b1=cb+2048+(t*128) | ||
+ | 126 hb=int(b1/ | ||
+ | 128 poke ba +256 +c,hb | ||
+ | 130 c=c+1: | ||
+ | |||
+ | The above loop creates the high byte table for the second buffer | ||
+ | (buffer#1) | ||
+ | |||
+ | 150 fort=0 to 15 | ||
+ | 152 forq=0to7 | ||
+ | 154 poke ba + 384+(t*8)+q, | ||
+ | 156 nextq,t | ||
+ | |||
+ | The above loop creates the bitmask table. | ||
+ | |||
+ | 200 open1, | ||
+ | 202 open2, | ||
+ | 204 print# | ||
+ | 206 fort=0to511 | ||
+ | 208 print# | ||
+ | 210 next | ||
+ | 212 close2 | ||
+ | |||
+ | Here we have a small routine to save the tables created by this program to | ||
+ | disk, with a loading address of $3300. | ||
+ | |||
+ | UUEncoded Files | ||
+ | --------------- | ||
+ | Below you will find the uuencoded files detailed in this article. I | ||
+ | included TurboAssmembler source, Object code (sys 2080 to run), both | ||
+ | tables needed by the program to run, as well as the basic programs used to | ||
+ | create them. | ||
+ | |||
+ | I will make all of these available in a zip package on my web site: | ||
+ | http:// | ||
+ | |||
+ | How To Get The Demo To Run | ||
+ | -------------------------- | ||
+ | There wasn't enough time to write a loader for this demo before the | ||
+ | deadline, and even though my gracious host allowed me the opportunity to | ||
+ | add one, it is really more trouble than its worth. | ||
+ | |||
+ | So for those of you explorers who want to know how things are really | ||
+ | done, here's a quick and simple method of launching this demo: | ||
+ | |||
+ | Step One: load " | ||
+ | Step Two: load " | ||
+ | Step Three: load " | ||
+ | Step Four: sys2080 | ||
+ | |||
+ | [Ed. Note : To prevent possible compatibility problems with unix file-handling, | ||
+ | the UUencoded files in this article will extract with filenames as seen | ||
+ | in the loading sequence above. | ||
+ | table generators, then the new tables will be named to their original | ||
+ | designations (i.e., sin/cos/sqr instead of sin_cos_sqr). | ||
+ | to change filenames in your loading procedure to match.] | ||
+ | |||
+ | Demo-Controls | ||
+ | ------------- | ||
+ | F1 : increments angle (apparent speed) | ||
+ | F3 : decrements angle | ||
+ | F5 : expands shape | ||
+ | F7 : shrinks shape | ||
+ | |||
+ | < | ||
+ | < | ||
+ | < | ||
+ | |||
+ | -- | ||
+ | For more information or general commentary on this article, Mr. John Kaiser | ||
+ | (Waveform/ | ||
+ | |||
+ | |||
+ | Addenum by S. Judd, Technical Editor | ||
+ | ------+------^-------^------+------- | ||
+ | |||
+ | John shrewdly omitted a few " | ||
+ | clearly as a learning experience. | ||
+ | bit of sneaky optimization can be undertaken, as follows : | ||
+ | |||
+ | [...] | ||
+ | |||
+ | > ; | ||
+ | > | ||
+ | > | ||
+ | > sec | ||
+ | > sbc #$01 ; | ||
+ | > cmp #2 ;at minimum? | ||
+ | > bcs expdn2 | ||
+ | > lda #2 | ||
+ | > | ||
+ | > iny | ||
+ | > cpy points | ||
+ | > bne expdn1 | ||
+ | |||
+ | This can be written more efficiently, | ||
+ | |||
+ | expdn LDX POINTS | ||
+ | :L1 LDA RADIU,X | ||
+ | CMP #3 | ||
+ | BCC :SKIP | ||
+ | DEC RADIU,X | ||
+ | :SKIP DEX | ||
+ | BPL :L1 ;Only allows 128 points though | ||
+ | |||
+ | It is always better to start Y large and count downwards when you can. | ||
+ | His loop takes 4+2+3+2+3/ | ||
+ | the rewrite takes 4+2+3/ | ||
+ | less bytes too. Here it is not such a huge deal -- 6 cycles savings | ||
+ | per loop for the one usually used -- but this kind of trick can sometimes | ||
+ | lead to immense savings. | ||
+ | |||
+ | [...] | ||
+ | |||
+ | > | ||
+ | |||
+ | In ' | ||
+ | |||
+ | > | ||
+ | > clc | ||
+ | > adc angleinc | ||
+ | > sta theta, | ||
+ | > iny | ||
+ | > cpy points | ||
+ | > bne rotate1 | ||
+ | > rts | ||
+ | |||
+ | [...] | ||
+ | |||
+ | > ; | ||
+ | > | ||
+ | > sta $02 ; | ||
+ | > lda hibyte0, | ||
+ | > sta $03 ; | ||
+ | > | ||
+ | > lda ($02), | ||
+ | > ora bitmask, | ||
+ | > sta ($02), | ||
+ | > | ||
+ | > pla ;restore .a | ||
+ | > rts | ||
+ | > ; | ||
+ | |||
+ | |||
+ | Please note : | ||
+ | |||
+ | 1- Using a JSR plot each time adds 12 cycles (JSR + RTS) | ||
+ | |||
+ | 2- Most of the time you plot within the same byte. | ||
+ | That is, reloading $02/$03 each time is redundant by a | ||
+ | | ||
+ | not going to change by more than 1 at each iteration!) | ||
+ | |||
+ | Every cycle saved in a line drawing routine produces huge dividends. | ||
+ | you have an object with just three lines in it, and each line has 100 | ||
+ | points in it, you suddenly start saving thousands of cycles, (i.e., using | ||
+ | a JSR adds 3600 cycles immediately). | ||
+ | coordinate each time translates to many extra thousand cycles too. | ||
+ | In raster time, we're talking well over half the screen here! | ||
+ | |||
+ | In general, if it's in a loop, you can't optimize it enough :). | ||
+ | |||
+ | Again, though, doing things this way makes things much clearer (which makes | ||
+ | life easier on the programmer too). | ||
+ | -- | ||
+ | |||
+ | begin 644 disc-demo.sou | ||
+ | M.RTM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+0T[("# | ||
+ | M(, | ||
+ | M.3DV($9/ | ||
+ | M(, | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+0T@(" | ||
+ | M5%, | ||
+ | M(# | ||
+ | M,#, | ||
+ | M2$5405, | ||
+ | M(# | ||
+ | M,#, | ||
+ | M(" | ||
+ | M1494($-/ | ||
+ | M4DE8($].# | ||
+ | M(" | ||
+ | M, | ||
+ | M.R`@(" | ||
+ | M5$53# | ||
+ | M24)95$4Q(" | ||
+ | M2R`@(" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2T-(" | ||
+ | M3TE.250@(" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM# | ||
+ | M.U-# | ||
+ | M(" | ||
+ | M,# | ||
+ | M(", | ||
+ | M(" | ||
+ | M(" | ||
+ | M648W# | ||
+ | M(" | ||
+ | M.U-# | ||
+ | M(" | ||
+ | M,# | ||
+ | M(" | ||
+ | M5$]0# | ||
+ | M(" | ||
+ | M(" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM# | ||
+ | M648S(" | ||
+ | M(" | ||
+ | M04-%(" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+0U23U154" | ||
+ | M(" | ||
+ | M145.5%)9# | ||
+ | M+2T-4D]41$X@(" | ||
+ | M(" | ||
+ | M+2TM+2TM+2TM+2TM# | ||
+ | M1$$@4D%$254L60T@(" | ||
+ | M(" | ||
+ | M($U!6$E-54T_# | ||
+ | M02`C-C, | ||
+ | M(" | ||
+ | M24Y44PT@(" | ||
+ | M14Y44ED@(" | ||
+ | M+2TM+2TM+2TM+2TM# | ||
+ | M1$$@4D%$254L60T@(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M3" | ||
+ | M4" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+0U.15=32$%012`@($E.0R!# | ||
+ | M(" | ||
+ | M(# | ||
+ | M# | ||
+ | M(" | ||
+ | M(", | ||
+ | M4TA!4$4R# | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M24%.1TQ%# | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2T-4U1/ | ||
+ | M," | ||
+ | M(%)/ | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM# | ||
+ | M3$1!(", | ||
+ | M1" | ||
+ | M(" | ||
+ | M3" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM# | ||
+ | M(" | ||
+ | M151!# | ||
+ | M.T%$1" | ||
+ | M5$A%5$$-(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M+2TM+2TM# | ||
+ | M# | ||
+ | M(" | ||
+ | M($Q$02!2041)52Q9(" | ||
+ | M65)!1$E54R`@.R!# | ||
+ | M(" | ||
+ | M4@T@(" | ||
+ | M1$, | ||
+ | M(" | ||
+ | M(" | ||
+ | M(# | ||
+ | M62`C)# | ||
+ | M5$$L60T@(" | ||
+ | M041)52Q9# | ||
+ | M4B!' | ||
+ | M(" | ||
+ | M($Q$02!94$]3# | ||
+ | M(" | ||
+ | M4D5315)612`N60T@(" | ||
+ | M3$E.10T@(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(%DQ(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M4B`C)3`P,# | ||
+ | M(" | ||
+ | M(" | ||
+ | M($13-" | ||
+ | M2S`@(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M+2TM+2TM+2TM+2T-1T546%D@(" | ||
+ | M5D4@+ED-(" | ||
+ | M# | ||
+ | M0T]3+%D@(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M041# | ||
+ | M.PT@(" | ||
+ | M(" | ||
+ | M.RU& | ||
+ | M3D%410T-(" | ||
+ | M3$1!($U324XL62`@(" | ||
+ | M# | ||
+ | M(" | ||
+ | M0BD-(" | ||
+ | M(" | ||
+ | M3BQ9(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M4" | ||
+ | M3T]21$E.051%# | ||
+ | M(" | ||
+ | M(" | ||
+ | M+2TM+2TM+2T-1UA94D%$2553(" | ||
+ | M12`D,# | ||
+ | M)# | ||
+ | M, | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM# | ||
+ | M.U!215-%4E9%(" | ||
+ | M($E.# | ||
+ | M(" | ||
+ | M($Q/ | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M# | ||
+ | M(" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM# | ||
+ | M3$1!($Q/ | ||
+ | M(" | ||
+ | M(" | ||
+ | M+%D@(" | ||
+ | M($].# | ||
+ | M3$$@(" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM# | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M1$$@1%@@(" | ||
+ | M3D1, | ||
+ | M(" | ||
+ | M(" | ||
+ | M(# | ||
+ | M(" | ||
+ | M(", | ||
+ | M(" | ||
+ | M(" | ||
+ | M3D4Q(" | ||
+ | M1$PR, | ||
+ | M(" | ||
+ | M, | ||
+ | M($19# | ||
+ | M(" | ||
+ | M3# | ||
+ | M(" | ||
+ | M3#< | ||
+ | M24Y%-2`@2DU0($1, | ||
+ | M+2TM+2TM+2TM+0U$3# | ||
+ | M# | ||
+ | M(%@Q(" | ||
+ | M(" | ||
+ | M1$PP, | ||
+ | M(" | ||
+ | M($19(" | ||
+ | M0B`@(" | ||
+ | M(" | ||
+ | M4" | ||
+ | M(" | ||
+ | M(" | ||
+ | M+2TM+2TM+2TM+2TM+2TM# | ||
+ | M(" | ||
+ | M# | ||
+ | M(" | ||
+ | M24P@3D5%1`T@(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M+2TM# | ||
+ | M(" | ||
+ | M2E-2(%!, | ||
+ | M# | ||
+ | M(" | ||
+ | M($1,, | ||
+ | M(" | ||
+ | M3U0@(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(%@Q# | ||
+ | M, | ||
+ | M1$59(" | ||
+ | M6" | ||
+ | M(# | ||
+ | M0R!$62`@(" | ||
+ | M(" | ||
+ | M3#, | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM# | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(%1/ | ||
+ | M(" | ||
+ | M(" | ||
+ | M4%@@6# | ||
+ | M# | ||
+ | M+2TM+2TM+2TM+2TM# | ||
+ | M0D, | ||
+ | M(" | ||
+ | M(# | ||
+ | M3D5%1`T@(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(%)44PT@(" | ||
+ | M# | ||
+ | M(" | ||
+ | M(%!, | ||
+ | M(" | ||
+ | M(" | ||
+ | M-C!" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M3$1!(", | ||
+ | M# | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M62`@(" | ||
+ | M(" | ||
+ | M02`@(" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM# | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M+2TM+2TM+2T-1$5-3TE.250@(" | ||
+ | M10T@(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M2E-2(" | ||
+ | M(# | ||
+ | M344@25)14PT-(" | ||
+ | M5$@-(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M1$$@(SQ30U), | ||
+ | M(" | ||
+ | M.R!-051225@@3TX-(" | ||
+ | M4T-2145.# | ||
+ | M(" | ||
+ | M1D$I+%D@(" | ||
+ | M(" | ||
+ | M04Q512!)3@T@(" | ||
+ | M(" | ||
+ | M02`@(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M($).12!$14U/ | ||
+ | M(" | ||
+ | M34]262!7251(# | ||
+ | M(" | ||
+ | M1$$P," | ||
+ | M(" | ||
+ | M24Y)5# | ||
+ | MULG# | ||
+ | M(" | ||
+ | M5$$@)$0P, | ||
+ | M.S%35" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M+2TM+2TM+2TM+2T-0DQ!3DLP(" | ||
+ | M02`C)# | ||
+ | M)# | ||
+ | M(" | ||
+ | M02`D, | ||
+ | M5$$@)# | ||
+ | M4U1!(" | ||
+ | M(%-402`D, | ||
+ | M(" | ||
+ | M(" | ||
+ | M0DQ!3DLP00T@(" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM# | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+0U)3DE43T)*,# | ||
+ | M# | ||
+ | M3DE43T)*,# | ||
+ | M+%D-(" | ||
+ | M53`P+%D-(" | ||
+ | M4D%$254L60T@(" | ||
+ | M(" | ||
+ | M+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2T-24Y)5$]" | ||
+ | M(" | ||
+ | M3$19(", | ||
+ | M4U1!($E42$5402Q9# | ||
+ | M(" | ||
+ | M(" | ||
+ | M4%D@4$])3E13# | ||
+ | M(%)44PT[+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM+2TM | ||
+ | M# | ||
+ | M(" | ||
+ | M(" | ||
+ | M# | ||
+ | M254L60T@(" | ||
+ | M(" | ||
+ | M(" | ||
+ | M+2TM+2TM+2TM+0U42$5403`P(" | ||
+ | M," | ||
+ | M+# | ||
+ | M2$5403(P(" | ||
+ | M651%(# | ||
+ | C+# | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | begin 644 disc-demo.obj | ||
+ | M(`@@A`NI_HT`W*T!W, | ||
+ | MR7_P& | ||
+ | M" | ||
+ | MS# | ||
+ | M# | ||
+ | MS@FY4`.-S0D@=`FMSPD8: | ||
+ | M": | ||
+ | MQ*T8T$D" | ||
+ | M`# | ||
+ | M"; | ||
+ | M`KT`-(4# | ||
+ | MK7\+[8$+C8, | ||
+ | MK8(+S8, | ||
+ | M8$SV" | ||
+ | M" | ||
+ | MZ& | ||
+ | MU`G, | ||
+ | M@PNN? | ||
+ | M&, | ||
+ | M(-0)S($+T.U@````````J0: | ||
+ | M`*FLA? | ||
+ | M`-B9`-F9`-J9`-O(T/& | ||
+ | M@"" | ||
+ | M$, | ||
+ | M+IF`+ID`+YF`+\@0S6" | ||
+ | M8*D%C3P# | ||
+ | M`YE``[G?# | ||
+ | MH``H*" | ||
+ | 4& | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | begin 644 m.sin_cos_sqr | ||
+ | M`0@H" | ||
+ | MCR`M*RT@(" | ||
+ | M($)9.B!7059%1D]232`@(" | ||
+ | M25-#/ | ||
+ | M-B`@(" | ||
+ | M04), | ||
+ | M, | ||
+ | M," | ||
+ | MJC$`@0D6`$.RM2@HOBA202FL-C0IJBXU*0" | ||
+ | MJC$`N0D: | ||
+ | M, | ||
+ | M4U& | ||
+ | M`(%" | ||
+ | M, | ||
+ | M9@"?, | ||
+ | M+T-/ | ||
+ | M`., | ||
+ | #&AH: | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | begin 644 m.base_mask | ||
+ | M`0@H" | ||
+ | MCR`M*RT@(" | ||
+ | M($)9.B!7059%1D]232`@(" | ||
+ | M25-#/ | ||
+ | M-B`@(" | ||
+ | M`$)(LK4H0D& | ||
+ | M& | ||
+ | M.DQ" | ||
+ | M2$(`> | ||
+ | M-P" | ||
+ | M0C& | ||
+ | M_`F6`(%4LC`@I" | ||
+ | MJE$L, | ||
+ | M(CJ@, | ||
+ | M*$)(*3L`B`K.`(%4LC" | ||
+ | M" | ||
+ | M& | ||
+ | #&AH: | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | begin 644 sin_cos_sqr | ||
+ | M`# | ||
+ | M.# | ||
+ | M-30S, | ||
+ | M^/; | ||
+ | MQ,/# | ||
+ | MTM/ | ||
+ | M/ | ||
+ | M" | ||
+ | MS< | ||
+ | MQ\C(R< | ||
+ | M``(# | ||
+ | M.3H[.SP\/ | ||
+ | M`@(# | ||
+ | M$Q04%146%Q< | ||
+ | M-# | ||
+ | M(B$A(!\>' | ||
+ | M" | ||
+ | M```````:& | ||
+ | M& | ||
+ | I& | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | begin 644 base_mask | ||
+ | M`#, | ||
+ | M@(" | ||
+ | M@(" | ||
+ | M(" | ||
+ | M(R, | ||
+ | M)28F)B8F)B8F)B8F)B8F)B8G)R< | ||
+ | M*" | ||
+ | M*RLK*RLK*RPL+" | ||
+ | M+BXN+BXN+BXN+B\O+R\O+R\O+R\O+R\O+R^`0" | ||
+ | M$`@$`@& | ||
+ | M@$`@$`@$`@& | ||
+ | M!`(!@$`@$`@$`@& | ||
+ | M& | ||
+ | M& | ||
+ | *& | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | |||
+ | / | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | SID Primer: The Working Man's Guide to SID | ||
+ | ---------- | ||
+ | by | ||
+ | |||
+ | | ||
+ | | ||
+ | |||
+ | |||
+ | The Sound Interface Device (SID): it is one of the signature chips | ||
+ | in the C64, but to this day a number of people still do not have a good | ||
+ | understanding of SID -- the meaning of its registers, the idea behind how | ||
+ | it generates sounds, etc. (I myself was in this category not so long ago). | ||
+ | This article exists to rectify this situation once and for all with a general | ||
+ | overview of the chip which everyone should be able to comprehend. | ||
+ | included a program for experimenting with SID, which allows the user to change | ||
+ | the various registers and see (not to mention hear!) the effect. | ||
+ | |||
+ | To use the program, just load it and run. Use the cursor keys | ||
+ | to move between the various settings, and use the +/- keys to change | ||
+ | those settings. | ||
+ | amounts, you can press shift +/-. Large jumps will work on any setting | ||
+ | that takes up more than one byte. Press 1, 2, or 3 to switch between | ||
+ | the settings for voices 1, 2, and (guess!). | ||
+ | as the waveforms, are simple toggles, and the space bar is used to toggle | ||
+ | these on and off. Finally, * will toggle the output of voice 3 on and off, | ||
+ | although you need to press it very fast, as I only included it for the sake | ||
+ | of completeness and did not debounce it or anything (all keys repeat). | ||
+ | The upper left corner displays all of the (relevant) SID registers, | ||
+ | so when you change a particular setting you will see the corresponding | ||
+ | change in the appropriate SID registers. | ||
+ | 54272 ($D400), these values reflect the contents of SID+register number. | ||
+ | The two registers in the lower right corner of the SID box show the current | ||
+ | values of the voice 3 oscillator and envelope generator. | ||
+ | impatient, set the sustain to 15, select the triangle waveform, use shift+ | ||
+ | to get a reasonably large frequency, and set the gate bit, which turns the | ||
+ | sound on. Viola! | ||
+ | |||
+ | SID is pretty straightforward. | ||
+ | There are also three filters which the output of a voice may be run through. | ||
+ | SID is also used to read the paddles (i.e. potentiometer settings). | ||
+ | |||
+ | All three voices have a number of features in common. | ||
+ | are four possible waveforms which may be selected: triangle, sawtooth, | ||
+ | pulse, and noise. | ||
+ | sound. | ||
+ | in a sort of mashing together of the waveforms, although noise should | ||
+ | never be selected at the same time as the others. | ||
+ | |||
+ | The frequency of each voice runs from about 0 Hz to around | ||
+ | 4000 Hz, with 65536 steps in-between. | ||
+ | generated via ring modulation. | ||
+ | exact frequency may be calculated as : | ||
+ | |||
+ | FREQUENCY = (REGISTER VALUE * CLOCK)/ | ||
+ | |||
+ | where CLOCK=1022730 for NTSC systems and CLOCK=985250 for PAL systems. | ||
+ | |||
+ | One of the more important features of a SID voice is the ADSR | ||
+ | envelope. | ||
+ | create an envelope using a stereo: first turn the volume all the way | ||
+ | to zero (my volume goes to 11, so it takes me a while). | ||
+ | turning it up to some level, say 5. As soon as it hits 5, start going | ||
+ | back down again, until it hits 2. Let it sit at 2 until you get tired | ||
+ | of sitting, and then turn it back down to zero. | ||
+ | |||
+ | ADSR stands of course for Attack Decay Sustain Release. | ||
+ | first part, turning it up to 5, is the attack phase. | ||
+ | attack changes how quickly the volume goes to its maximum. | ||
+ | second phase, turning down to 2, is the decay phase. | ||
+ | the decay changes the rate at which the volume decays from the | ||
+ | maximum volume to the sustain volume, which in this case is 2. | ||
+ | Changing the sustain value changes this sustain level. | ||
+ | will remain at this volume until the release phase is initiated. | ||
+ | Changing the release value changes the rate at which the sound will | ||
+ | decay to zero. | ||
+ | |||
+ | To start the attack phase, simply set the gate bit. To | ||
+ | start the release phase, clear the gate bit. The program may be | ||
+ | used to investigate the envelope, via the voice 3 envelope generator | ||
+ | output register: select voice 3, and set the ADSR values to, say | ||
+ | attack 14, decay 10, sustain 10, and release 8. Set the gate bit, | ||
+ | and the register will first increase, then decrese, then sit still, | ||
+ | until the gate bit is cleared, at which point the sound will decay | ||
+ | towards zero. | ||
+ | |||
+ | There are two more important features for each voice: ring | ||
+ | modulation and synchronization. | ||
+ | overtones, i.e. gives it a bell-like or gong-like sound. | ||
+ | combines two waveforms in a special way, and tends to amplify higher | ||
+ | frequencies contained in the waveform. | ||
+ | |||
+ | These two features modulate the voice with the one " | ||
+ | it; that is, Voice 1 is modulated by voice 3, voice 2 by voice 1, etc. | ||
+ | Ring modulation can only be applied to the triangle waveform; that is, | ||
+ | if ring modulation is selected for voice 1, then voice 1 must have | ||
+ | the triangle waveform selected. | ||
+ | playing with voice 1, and select ring modulation. | ||
+ | and select a waveform and a frequency -- the ADSR settings may all be 0. | ||
+ | There are also three filters available, which may be combined. | ||
+ | not separate filters for each voice, but rather one set of filters which | ||
+ | voices may all run through. | ||
+ | number of frequencies. | ||
+ | frequencies in a special way. | ||
+ | |||
+ | To use the filters, the cutoff frequency must first be selected. | ||
+ | All frequency components above or below this cutoff frequency will be | ||
+ | reduced in volume -- the further away these frequencies are, the more they | ||
+ | will be attenuated. | ||
+ | the cutoff through, and attenuate frequencies higher than the cutoff. | ||
+ | The high pass filter does the opposite. | ||
+ | frequencies on both sides of the cutoff. | ||
+ | selected simultaneously, | ||
+ | As an example, let's say we had a sawtooth wave playing at 100 Hz. | ||
+ | This wave contains a number of higher harmonics, in particular | ||
+ | harmonics at 200 Hz, 300 Hz, 400 Hz, 500 Hz, and so on. If the low | ||
+ | pass filter were selected, and the cutoff frequency was set at 380 Hz, | ||
+ | the 200 Hz and 300 Hz frequencies would pass right through, but the 400 Hz | ||
+ | 500 Hz etc. frequencies would be attenuated, the 500 Hz harmonic being | ||
+ | decreased more than the 400 Hz harmonic. | ||
+ | |||
+ | Resonance is a special feature which boosts frequencies near | ||
+ | the cutoff frequency. | ||
+ | The downside of the filters is that they vary quite a bit between | ||
+ | different SID chips, so filtered sounds on one machine may sound quite | ||
+ | different than the identical settings on another machine. | ||
+ | Beach-Head even allowed the user to change the filter settings, to try | ||
+ | to compensate for this. According to " | ||
+ | frequency is : | ||
+ | |||
+ | FREQUENCY = (REGISTER VALUE * 5.8) + 30 Hz | ||
+ | |||
+ | Note that the cutoff frequency is only 11 bits wide, i.e. has values | ||
+ | from 0 to 2047. | ||
+ | |||
+ | One other setting is bit 7 of location 54286 ($D418), which | ||
+ | disconnects the output of voice 3. This lets voice 3 be used, in | ||
+ | particular the envelope output register, without having to listen to it. | ||
+ | The waveforms are the " | ||
+ | sensors on the side of your head interperet pressure variations in | ||
+ | the air. Speakers convert changes in voltage into pressure. | ||
+ | waveform generators are what control this voltage. | ||
+ | program may be used to get a feel for how the waveforms look, by | ||
+ | using voice 3 and a very low frequency setting (like 1), and seeing | ||
+ | the change in the waveform output register. | ||
+ | |||
+ | The first waveform is the Triangle wave. This is SID's closest | ||
+ | approximation to a pure sine wave. It starts at some value, increases up | ||
+ | to its maximum value, then decreases down to its minimum value, and | ||
+ | so on. Mathematically, | ||
+ | |||
+ | sin(x) - sin(3x)/9 + sin(5x)/25 - sin(7x)/49 + ... | ||
+ | |||
+ | so a triangle wave with fundamental frequency 100 Hz contains frequencies | ||
+ | of 300 Hz, 500 Hz, 700 Hz, and so on. Note that the amplitude of | ||
+ | each harmonic decreases as the square of the frequency. | ||
+ | |||
+ | The second waveform is the Sawtooth. | ||
+ | to its maximum, like the Triangle, but once it gets there it suddenly | ||
+ | drops down to its minimum, so it is like half of a triangle. | ||
+ | may be expressed as : | ||
+ | |||
+ | sin(x) + sin(2x)/2 + sin(3x)/3 + sin(4x)/4 + ... | ||
+ | |||
+ | As you can see it has a much higher harmonic content than the triangle | ||
+ | wave -- more harmonics are present, and their amplitudes decrease less | ||
+ | rapidly than the triangle wave (e.g. compare sin(3x)/3 with sin(3x)/9). | ||
+ | |||
+ | Next up is the Pulse, or Square, waveform. | ||
+ | is either high or low. With SID you can set how much of the time is | ||
+ | spent high and how much is spent low. The ratio of the time the signal | ||
+ | is high to the time of a complete cycle is called the duty cycle. | ||
+ | duty cycle of 1:2 is a special case called a square wave : | ||
+ | |||
+ | sin(x) + sin(3x)/3 + sin(5x)/5 + ... | ||
+ | |||
+ | The neat thing about pulse waves is that as the duty cycle is changed the | ||
+ | harmonic content varies widely -- try using the program to change the | ||
+ | pulse width and it will be obvious. | ||
+ | irregular energy distribution among the various harmonics; compare | ||
+ | with a sawtooth, where the harmonics decrease smoothly. | ||
+ | |||
+ | The final waveform is the Noise waveform. | ||
+ | randomly generated waveform; that is, random values are output through | ||
+ | the waveform generator according to the frequency setting. | ||
+ | the result can't be written down as a harmonic expression like the above | ||
+ | waveforms, but the frequency spectrum as a whole may be described. | ||
+ | White Noise contains all frequencies in equal proportion. | ||
+ | what is known as Blue Noise: a minimum frequency is set and all frequencies | ||
+ | above this minimum are generated with equal probability; | ||
+ | is biased towards higher frequencies. | ||
+ | |||
+ | A few words about ring modulation and synchronization: | ||
+ | modulation is a multiplication of two signals. | ||
+ | consider multiplying two sine waves together : | ||
+ | |||
+ | sin(f1) * sin(f2) = sin(f1-f2+pi/ | ||
+ | |||
+ | where a handy trig identity is used. The important thing to notice | ||
+ | is that two new frequencies are generated, f1+f2 and f1-f2, with | ||
+ | smaller amplitude and different phase. | ||
+ | at one frequency with another whose frequency differs by 1 or 2, | ||
+ | using the program. | ||
+ | slightly off from one another, which generates beats. | ||
+ | using ring modulation creates a whole slew of new sum and difference | ||
+ | harmonics, which gives a bell-like sound. | ||
+ | frequencies higher than the maximum SID frequency setting may be | ||
+ | generated in this way. | ||
+ | |||
+ | SID doesn' | ||
+ | generate all the sum and difference frequencies, | ||
+ | multiplication going on. See the Yannes interview, elsewhere in this | ||
+ | issue, for more information. | ||
+ | |||
+ | Synchronization synchronizes one voice to another. | ||
+ | if voice 1 is synchronized to voice 3, the voice 1 waveform will "start | ||
+ | over" according to the frequency of voice 3. Adding a discontinuity | ||
+ | like this has the effect of generating higher harmonics, and can change | ||
+ | the pitch as well. Imagine a triangle waveform, counting downwards, | ||
+ | when suddenly the waveform is reset to zero and starts counting up again. | ||
+ | The triangle is now starting to resemble a sawtooth, which, as was | ||
+ | pointed out earlier, contains more high frequencies with larger | ||
+ | amplitudes. | ||
+ | |||
+ | That then is a fairly complete summary of SID. For more detail | ||
+ | on the inner workings of SID see the interview with Bob Yannes, designer | ||
+ | of the SID chip, elsewhere in this issue. | ||
+ | SID, see just about any book on the Commodore 64. Otherwise, have some | ||
+ | fun playing with SID! | ||
+ | |||
+ | |||
+ | begin 600 wave3.run | ||
+ | M`0@`" | ||
+ | MJ8" | ||
+ | M("" | ||
+ | M55-404E..@4P," | ||
+ | MF49215$Z!2`@(" | ||
+ | M!2!6, | ||
+ | M4D533TY!3D-%.@4P, | ||
+ | M($A)(& | ||
+ | MG6*1G6*1G6*1G6*1G6(`J0B%_ZE!A? | ||
+ | MV)E0V)EXV)F@V)G(V)GPV)D8V8@0Z*(`J0" | ||
+ | MBB" | ||
+ | M# | ||
+ | MT`RI#"" | ||
+ | MH`$@> | ||
+ | M_< | ||
+ | M(!@/" | ||
+ | MT!.@!B`8# | ||
+ | MR4+0%Z`& | ||
+ | MI@^E^LE# | ||
+ | M/ | ||
+ | M, | ||
+ | MI? | ||
+ | MR4? | ||
+ | MH`H@> | ||
+ | M`\# | ||
+ | M_+T!P(7[H`4@O0^E^LE)T$R@`R![# | ||
+ | MP-`# | ||
+ | M# | ||
+ | M# | ||
+ | M787^J0: | ||
+ | M_JD& | ||
+ | M!H7_HA> | ||
+ | M^RT7P/ | ||
+ | M&& | ||
+ | M_J7ZR5' | ||
+ | M"" | ||
+ | MJ0> | ||
+ | MJ<" | ||
+ | M(.3_IOW)*]`3K17`&& | ||
+ | MP$P"# | ||
+ | M!*E`A? | ||
+ | M((4/ | ||
+ | MP$7[G0# | ||
+ | M8$A*2DI*()H/:" | ||
+ | MR&" | ||
+ | MA? | ||
+ | -BI' | ||
+ | ` | ||
+ | end | ||
+ | |||
+ | |||
+ | / | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | | ||
+ | |||
+ | An interview with Bob Yannes | ||
+ | |||
+ | | ||
+ | |||
+ | |||
+ | This is an interview with the creator of the SID chip, namely Bob | ||
+ | | ||
+ | | ||
+ | gives us a very detailed description of the SID's technology. | ||
+ | |||
+ | The interview was done via e-mail in August 1996 by Andreas Varga. | ||
+ | Some questions came from Linus Walleij. | ||
+ | |||
+ | [A note from your friendly technical editor: There is a little addendum | ||
+ | at the end of this interview, with a few comments to clarify or expand | ||
+ | upon some of the statements in the interview. | ||
+ | | ||
+ | |||
+ | These comments are due in large part to a series of very helpful | ||
+ | | ||
+ | | ||
+ | phase accumulating oscillator works :). | ||
+ | | ||
+ | |||
+ | -- | ||
+ | |||
+ | |||
+ | > Did you foresee that people would actually treat your little | ||
+ | > VLSI-chip like an instrument? | ||
+ | |||
+ | | ||
+ | | ||
+ | time) and before I knew anything at all about VLSI chip design. One of | ||
+ | the reasons I was hired was my knowledge of music synthesis was deemed | ||
+ | | ||
+ | chip, I was attempting to create a single-chip synthesizer voice which | ||
+ | | ||
+ | | ||
+ | |||
+ | |||
+ | > Are you aware of the existence of programs like SIDPLAY, | ||
+ | > PlaySID,... which emulate the SID chip up to the smallest click ? | ||
+ | |||
+ | I only recently became aware of them (through your website). | ||
+ | | ||
+ | am constantly amazed and gratified at the number of people who have | ||
+ | been positively affected by the SID chip and the Commodore 64 (which I | ||
+ | also designed) and who continue to do productive things with them | ||
+ | | ||
+ | |||
+ | |||
+ | > Have you heard the tunes by Rob Hubbard, Martin Galway, Tim | ||
+ | > Follin, Jeroen Tel, and all the other composers ? | ||
+ | |||
+ | | ||
+ | |||
+ | |||
+ | > Did you believe this was possible to do with your chip? | ||
+ | |||
+ | Since I haven' | ||
+ | | ||
+ | | ||
+ | | ||
+ | |||
+ | |||
+ | > How much of the architecture in the SID inspired you when | ||
+ | > working with the Ensoniq synthesizers? | ||
+ | |||
+ | The SID chip was my first attempt at a phase-accumulating | ||
+ | | ||
+ | Due to time constraints, | ||
+ | | ||
+ | | ||
+ | | ||
+ | per chip. Aside from that, little else of SID is to be found in our | ||
+ | | ||
+ | for the Apple II (the basis of the Alpha Syntauri system). The DOC I | ||
+ | chip (used in the Mirage and ESQ-1) was modeled on this sound card. | ||
+ | Our current designs, which include waveform interpolation, | ||
+ | | ||
+ | on anything other than our imaginations. | ||
+ | |||
+ | |||
+ | > How big impact do you think the SID had on the synthesizer | ||
+ | > industry? | ||
+ | |||
+ | Well, I don't think it had much impact on the synthesizer | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | was to be able to sell the SID chip to synthesizer manufacturers. SID | ||
+ | chip production was completely consumed by the Commodore 64 and by the | ||
+ | time chips were readily available, I had left Commodore and never had | ||
+ | the opportunity to improve the fidelity of the chip. | ||
+ | |||
+ | |||
+ | > What would you have changed in the SIDs design, if you had a | ||
+ | > bigger budget from Commodore ? | ||
+ | |||
+ | The issue wasn't budget, it was development time and chip size | ||
+ | | ||
+ | chip, VIC II chip and Commodore 64 were incredibly tight (some would | ||
+ | say impossibly tight)--we did things faster than Commodore had ever | ||
+ | done before and were never able to repeat after! If I had had more | ||
+ | time, I would have developed a proper MOS op-amp which would have | ||
+ | | ||
+ | voice was supposed to be zero. This lead to poor signal-to-noise | ||
+ | | ||
+ | would also have greatly improved the filter, particularly in achieving | ||
+ | high resonance. I originally planned to have an exponential look-up | ||
+ | table to provide a direct translation for the equal-tempered scale, | ||
+ | but it took up too much silicon and it was easy enough to do in | ||
+ | | ||
+ | |||
+ | | ||
+ | > The SID is very complex for its time. Why didn't you settle | ||
+ | > with an easier design ? | ||
+ | |||
+ | I thought the sound chips on the market (including those in | ||
+ | the Atari computers) were primitive and obviously had been designed | ||
+ | by people who knew nothing about music. As I said previously, I was | ||
+ | | ||
+ | | ||
+ | |||
+ | |||
+ | > Do you still own a C64 (or another SID-equipped computer) ? | ||
+ | |||
+ | Sure, I have a couple of them (including the portable), but I | ||
+ | | ||
+ | |||
+ | |||
+ | > Did Commodore ever plan to build an improved successor to the SID? | ||
+ | |||
+ | I don't know. After I left I don't think there was anyone | ||
+ | there who knew enough about music synthesis to do much more than | ||
+ | | ||
+ | the SID chip before we had to release to production, but I doubt it | ||
+ | would have made any difference to the success of the Commodore 64. | ||
+ | | ||
+ | |||
+ | > Can you give us a short overview of the SIDs internal architecture ? | ||
+ | |||
+ | | ||
+ | Each " | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | D/A converter provided overall manual volume control. | ||
+ | |||
+ | As I recall, the Oscillator is a 24-bit phase-accumulating design of | ||
+ | which the lower 16-bits are programmable for pitch control. [1] The output | ||
+ | of the accumulator goes directly to a D/A converter through a waveform | ||
+ | | ||
+ | would be used as an address into memory which contained a wavetable, | ||
+ | but SID had to be entirely self-contained and there was no room at all | ||
+ | for a wavetable on the chip. | ||
+ | |||
+ | The Sawtooth waveform was created by sending the upper 12-bits of the | ||
+ | | ||
+ | |||
+ | The Triangle waveform was created by using the MSB of the accumulator | ||
+ | to invert the remaining upper 11 accumulator bits using EXOR gates. | ||
+ | These 11 bits were then left-shifted (throwing away the MSB) and sent | ||
+ | to the Waveform D/A (so the resolution of the triangle waveform was | ||
+ | half that of the sawtooth, but the amplitude and frequency were the | ||
+ | | ||
+ | |||
+ | The Pulse waveform was created by sending the upper 12-bits of the | ||
+ | | ||
+ | | ||
+ | sent to all 12 bits of the Waveform D/A. | ||
+ | |||
+ | The Noise waveform was created using a 23-bit pseudo-random sequence | ||
+ | | ||
+ | the input through combinatorial logic). [2] The shift register was clocked | ||
+ | by one of the intermediate bits of the accumulator to keep the | ||
+ | | ||
+ | | ||
+ | to the Waveform D/A. | ||
+ | |||
+ | Since all of the waveforms were just digital bits, the Waveform | ||
+ | | ||
+ | would be sent to the Waveform D/A. The multiplexers were single | ||
+ | | ||
+ | the waveforms to be selected. The combination was actually a logical | ||
+ | | ||
+ | | ||
+ | the pseudo-random sequence generator by filling it with zeroes. [3] | ||
+ | | ||
+ | |||
+ | The output of the Waveform D/A (which was an analog voltage at this | ||
+ | | ||
+ | | ||
+ | word which modulated the amplitude of the waveform came from the | ||
+ | | ||
+ | |||
+ | The Envelope Generator was simply an 8-bit up/down counter which, when | ||
+ | | ||
+ | from 255 down to the programmed Sustain value at the Decay rate, | ||
+ | | ||
+ | | ||
+ | |||
+ | A programmable frequency divider was used to set the various rates | ||
+ | | ||
+ | | ||
+ | | ||
+ | the frequency divider. Depending on what state the Envelope Generator | ||
+ | was in (i.e. ADS or R), the appropriate register would be selected and | ||
+ | that number would be translated and loaded into the divider. Obviously | ||
+ | it would have been better to have individual bit control of the | ||
+ | | ||
+ | | ||
+ | Using this approach, I was able to cram a wide range of rates into 4 | ||
+ | bits, allowing the ADSR to be defined in two bytes instead of eight. | ||
+ | The actual numbers in the look-up table were arrived at subjectively | ||
+ | by setting up typical patches on a Sequential Circuits Pro-1 and | ||
+ | | ||
+ | seem strange)! | ||
+ | |||
+ | In order to more closely model the exponential decay of sounds, | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | happy how well this worked considering the simplicity of the | ||
+ | | ||
+ | |||
+ | A digital comparator was used for the Sustain function. The upper four | ||
+ | bits of the Up/Down counter were compared to the programmed Sustain | ||
+ | value and would stop the clock to the Envelope Generator when the | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | steps of 16. Again, more register bits would have provided higher | ||
+ | | ||
+ | |||
+ | When the Gate bit was cleared, the clock would again be enabled, | ||
+ | | ||
+ | | ||
+ | it was changed to a lower value during the Sustain portion of the | ||
+ | | ||
+ | | ||
+ | |||
+ | The 8-bit output of the Envelope Generator was then sent to the | ||
+ | | ||
+ | | ||
+ | was modulating the output of the Envelope Generator, but the result is | ||
+ | the same). | ||
+ | |||
+ | Hard Sync was accomplished by clearing the accumulator of an | ||
+ | | ||
+ | |||
+ | Ring Modulation was accomplished by substituting the accumulator MSB | ||
+ | of an oscillator in the EXOR function of the triangle waveform | ||
+ | | ||
+ | That is why the triangle waveform must be selected to use Ring Modulation. | ||
+ | |||
+ | The Filter was a classic multi-mode (state variable) VCF design. There | ||
+ | was no way to create a variable transconductance amplifier in our NMOS | ||
+ | | ||
+ | | ||
+ | | ||
+ | had no audible affect so I disconnected it!). | ||
+ | |||
+ | | ||
+ | Each bit would turn on one of the weighted resistors and allow a | ||
+ | | ||
+ | | ||
+ | | ||
+ | sent to the final amplifier (a notch filter was created by enabling | ||
+ | both the high and low-pass outputs simultaneously). | ||
+ | |||
+ | The filter is the worst part of SID because I could not create | ||
+ | | ||
+ | In addition, the resistance of the FETs varied considerably with | ||
+ | | ||
+ | | ||
+ | was better than nothing and I didn't have time to make it better. | ||
+ | |||
+ | | ||
+ | | ||
+ | a 4-bit multiplying D/A converter which allowed the volume of the | ||
+ | | ||
+ | | ||
+ | | ||
+ | in real-time. Game programs often used this method to synthesize | ||
+ | | ||
+ | |||
+ | An external audio input could also be mixed in at the final amp or | ||
+ | | ||
+ | |||
+ | The Modulation registers were probably never used since they could | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | These registers just give microprocessor access to the upper 8 bits of | ||
+ | the instantaneous value of the waveform and envelope of Voice 3. Since | ||
+ | you probably wouldn' | ||
+ | | ||
+ | Voice 3. | ||
+ | |||
+ | |||
+ | > Any other interesting tidbits or anecdotes ? | ||
+ | | ||
+ | The funniest thing I remember was getting in a whole bunch of | ||
+ | C-64 video games which had been written in Japan. The Japanese are so | ||
+ | | ||
+ | code according to a SID spec. sheet (which I had written before SID | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | | ||
+ | had written their code correctly according to the spec. and that was | ||
+ | all that mattered! | ||
+ | -- | ||
+ | For questions or comments, Mr. Andreas Varga may be reached through his SID | ||
+ | Homepage on the World Wide Web at - http:// | ||
+ | |||
+ | |||
+ | Addendum by S. Judd, Technical Editor | ||
+ | ------+-----+----------+-----+------- | ||
+ | |||
+ | References / suggested reading: | ||
+ | |||
+ | " | ||
+ | IEEE Specturm, March 1985 | ||
+ | |||
+ | " | ||
+ | |||
+ | " | ||
+ | |||
+ | The SID Homepage, maintained by Andreas Varga: | ||
+ | http:// | ||
+ | |||
+ | "SID Primer: The Working Man's Guide to SID" | ||
+ | by Stephen L. Judd, disC=overy issue 2 (Yep, this issue!) | ||
+ | |||
+ | " | ||
+ | |||
+ | " | ||
+ | |||
+ | The first article makes for very interesting reading and should be available | ||
+ | at most public libraries. | ||
+ | and accurate explanations of SID and the theory behind its general features, | ||
+ | as well as actual implementation. | ||
+ | general information. | ||
+ | overview of the chip. The last two are included as good references for | ||
+ | information on programming SID, especially since they are easy to acquire. | ||
+ | |||
+ | The program included with the disC=overy article (wave3) is useful for | ||
+ | investigating the SID. By selecting voice 3, and using a low freq. (e.g. a | ||
+ | frequency setting of 1 or 2 say) the output of the waveform generator may be | ||
+ | seen visually. | ||
+ | selection may all be investigated in this manner. | ||
+ | |||
+ | Notes: | ||
+ | |||
+ | [1] In the words of Andreas Boose: | ||
+ | |||
+ | "The phase accumulating oscillator is just a 24-bit accumulator | ||
+ | which is increased by the 16-bit value of the frequency register | ||
+ | every phi2 cycle. | ||
+ | | ||
+ | |||
+ | Note that although he uses 12 bit, the resulting *resolution* of | ||
+ | this signal is only 12 bit on lower frequencies, | ||
+ | | ||
+ | drops down to nearly 8-bit when the frequency register is at its | ||
+ | max value (65535)." | ||
+ | |||
+ | It should now be clear why all of SID's waveforms are linear in | ||
+ | nature (i.e. composed of straight lines): instead of using this | ||
+ | counter as an index into a wave table, only the counter itself | ||
+ | is used in generating the waveforms (and counting up is an awfully | ||
+ | linear process). | ||
+ | possibility of modulating the waveform via rapid changes in the | ||
+ | frequency register. | ||
+ | |||
+ | [2] Asger Alstrup Nielsen has done a good deal of research into SID's | ||
+ | random number generator for generating the noise waveform. | ||
+ | told that the algorithm was implemented by Michael Schwendt in | ||
+ | SIDplay and the result was not too accurate. | ||
+ | visit the SID home page, in the references above. | ||
+ | |||
+ | [3] From the IEEE article referenced above: | ||
+ | |||
+ | The precise capabilities of the sound chip are not clear even | ||
+ | today, largely because of incorrect specifications having been written | ||
+ | when the chip was first designed. | ||
+ | and copied and rewritten by various people until it made practically | ||
+ | no sense anymore," | ||
+ | is the claim that the chip can logically AND several waveforms. ... | ||
+ | "There is no interlock to make sure that if one bit is on, the others | ||
+ | are off," Yannes said. "That would have taken too much silicon." | ||
+ | So if more than one waveform is selected, the internal nodes of the | ||
+ | output multiplexer are discharged, and what emerges is the minimum | ||
+ | of amplitudes." | ||
+ | |||
+ | The meaning of that last statement is unclear, as the result | ||
+ | is certainly not the minimum of the waveform values either. | ||
+ | very simple way to see what the result looks like is to use the " | ||
+ | program in the other disC=overy article with voice 3. To test the | ||
+ | logical ANDing hypothesis, " | ||
+ | output at $FF by selecting pulse and a nonzero pulse width. | ||
+ | a very low note frequency, 1 or 2 say. When the waveform output | ||
+ | becomes $FF set the pulse width to zero: the pulse output is now | ||
+ | stuck at $FF. Select another waveform, such as sawtooth, and watch | ||
+ | (and listen to) the result! | ||
+ | level is set (to 15 say) and that the gate bit is set as well). | ||
+ | the only thing that can be said about multiple waveforms is that they | ||
+ | are periodic with the expected frequency. | ||
+ | |||
+ | In short, nobody really knows what the result is when multiple | ||
+ | waveforms are selected! | ||
+ | |||
+ | [4] It should be clear that synchronization generates a new waveform at | ||
+ | a fundamental frequency related to the preceding voice. | ||
+ | resetting the accumulator to zero will in general introduce | ||
+ | a discontinuity into the waveform. | ||
+ | amplify the presence of high frequencies, | ||
+ | taking the Fourier transform of a discontinuous function. | ||
+ | for instance the triangle waveform, whose mode amplitudes decay | ||
+ | as 1/k^2, and the sawtooth waveform, whose mode amplitudes decay | ||
+ | as 1/k, where k is the wave number. | ||
+ | make sharp transitions. | ||
+ | |||
+ | Try synchronizing a triangle waveform -- if the synchronization | ||
+ | frequencies are very different, the result will unsurprisingly sound | ||
+ | like a sawtooth. | ||
+ | even the fundamental pitch of the note will change significantly. | ||
+ | |||
+ | [5] Ring Modulation. | ||
+ | elsewhere in this issue of disC=overy, will explain ring modulation | ||
+ | as a multiplication of two waveforms, which generates sum and | ||
+ | difference frequencies. | ||
+ | and another signal is cos(w2*t), then the ring modulated output is: | ||
+ | |||
+ | cos(w1*t) * cos(w2*t) = 1/2 (cos((w1+w2)*t) + cos((w1-w2)*t) ) | ||
+ | |||
+ | Voila! Two new waves, with frequencies which are the sum and difference | ||
+ | of the two frequencies. | ||
+ | slightly, very noticable beats will result. | ||
+ | |||
+ | SID works a little differently. | ||
+ | any waveforms. | ||
+ | related to the original waveform, which contains all the sum and | ||
+ | difference frequencies. | ||
+ | |||
+ | Recall that the triangle waveform may be written essentially as: | ||
+ | |||
+ | f(x) = { x, MSB=0 i.e. 0 < x <= max/2 | ||
+ | { max-x, | ||
+ | |||
+ | where max is the maximum value of the accumulator, | ||
+ | counts upwards on one half of the cycle and downwards on the other | ||
+ | half. | ||
+ | |||
+ | Ring modulation uses the MSB of the preceeding voice to evaluate | ||
+ | the function above. | ||
+ | since when x=max/2 the two pieces of the function have the same | ||
+ | value. | ||
+ | was couting upwards, might suddenly change values significantly | ||
+ | and start counting downwards. | ||
+ | the result on a piece of paper. | ||
+ | |||
+ | |||
+ | V3: At some frequency a little higher than V1 (waveform doesn' | ||
+ | V1: A triangle wave at some base frequency, modulated by V3. | ||
+ | |||
+ | Modulated output of V1: | ||
+ | |||
+ | ------------*-------*[--------------------------------- maximum value | ||
+ | [* * [ | ||
+ | [ * | ||
+ | | ||
+ | * [ | ||
+ | | ||
+ | * | ||
+ | | ||
+ | * * [ [ * | ||
+ | | ||
+ | *----------*[--------[*-------------------------------- minimum value | ||
+ | ^ | ||
+ | [ | ||
+ | [ | ||
+ | [ | ||
+ | [ | ||
+ | [ [ | ||
+ | [ Curve hits FF and wraps around to 00; MSB is high so result | ||
+ | [ is 255-value (i.e. counting downwards) | ||
+ | [ | ||
+ | V3 hits midway point in cycle; MSB is now high; V1 goes from t | ||
+ | to 255-t, goes downwards. | ||
+ | |||
+ | Well it's not really 0..255, but the meaning should be clear. :) | ||
+ | |||
+ | There are now two important features: first, a number of | ||
+ | discontinuities have been suddenly introduced, which tends | ||
+ | to amplify higher frequencies. | ||
+ | frequency of the resulting waveform is quite different than | ||
+ | that of the pure triangle. | ||
+ | another way, what is the period: how long must one wait before | ||
+ | the waveform repeats itself? | ||
+ | |||
+ | In terms of SID, let both of the accumulators start at zero, and let | ||
+ | one count up faster than the other. | ||
+ | the amount of time it takes for its counter to return to zero. At | ||
+ | some point both counters will return to zero, and of course the | ||
+ | modulated wave will then repeat. | ||
+ | modulated waveform. | ||
+ | |||
+ | Let one wave have period T1 (frequency w1) and the other have period T3 | ||
+ | (and frequency w3). When m*T1 = n*T3, for some integers m and n, the | ||
+ | wave will repeat. | ||
+ | three repetitions of wave 3 the waves will both be at zero again. | ||
+ | resulting wave will then have frequencies w=(p*w1 + q*w3) for integer | ||
+ | p,q. To see this, consider a wave like: | ||
+ | |||
+ | f(t) = e^i 2*pi (p*w1 + q*w3) t, where w1=1/T1, w3=1/T3 | ||
+ | |||
+ | let t = T = m*T1 = n*T3, then | ||
+ | |||
+ | f(T) = e^i 2*pi (p*m + q*n) = f(2*pi) = f(0) | ||
+ | |||
+ | Thus, such a wave has period T (although not necessarily least | ||
+ | period T), for *any* p,q integer. | ||
+ | waveform contains all of the sum and difference frequencies, | ||
+ | w1-w3, w1+w3, 2*w1+w3, etc. | ||
+ | |||
+ | Q.E.D. | ||
+ | |||
+ | In summary, SID's ring modulation produces a new waveform containing | ||
+ | sum and difference frequencies of the modulating waveforms, with | ||
+ | a strong high frequency content due to the discontinuities in the | ||
+ | resulting modulated waveform. | ||
+ | |||
+ | Note that by using ring modulation (not to mention sync) frequencies | ||
+ | much much higher than than the upper frequency limit (~4000 Hz) of the | ||
+ | frequency register may be generated. | ||
+ | |||
+ | |||
+ | / | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | --- Z80, The basics of it --- | ||
+ | |||
+ | by Raver/ | ||
+ | |||
+ | |||
+ | ------------------------------------------------------------------------------ | ||
+ | Introduction | ||
+ | ------------------------------------------------------------------------------ | ||
+ | This article is a brief overview of the Z80 CPU processor. | ||
+ | at the American company ZILOG. It was and still is a very popular CPU. Used | ||
+ | in many machines, from the early Cromenco' | ||
+ | in 1996 it is a prime workhorse in many microcontroller operations. | ||
+ | |||
+ | The Z80 first saw action in a Commodore computer with the introduction of | ||
+ | the CP/M cartridge for the C64. Later, CBM introduced the C128 home computer, | ||
+ | which has a built in Z80. Both Z80 implementations are accurately regarded | ||
+ | as half-done, limiting the processor in many ways. Focusing on the short- | ||
+ | comings however, would be the topic of another article. | ||
+ | concentrate here on the Z80 itself rather than on how it interacts with | ||
+ | any given specific Commodore system or operating system protocol. | ||
+ | this will be of sufficient value for further exploration on your particular | ||
+ | system configuration. | ||
+ | |||
+ | ------------------------------------------------------------------------------ | ||
+ | The Z80 Registers. | ||
+ | ------------------------------------------------------------------------------ | ||
+ | AF - can be only used as two 8bit registers, but push on stack and change to | ||
+ | alternative always both. " | ||
+ | of operations can be done with " | ||
+ | " | ||
+ | |||
+ | BC - 16bit register, can be used as two 8bit registers " | ||
+ | usually used as "back counter" | ||
+ | |||
+ | DE - 16bit register, can be used as two 8bit registers " | ||
+ | usually used as " | ||
+ | destination. | ||
+ | |||
+ | HL - 16bit register, can be used as two 8bit registers " | ||
+ | very special register, there are lots of commands available only for this | ||
+ | register. It's sometimes called "16bit accumulator" | ||
+ | |||
+ | IX - 16bit register. It can be used as two 8bit registers " | ||
+ | but it's undocumented feature. That register have one very useful addressing | ||
+ | mode, but arn't widely used, cause it's slow (14-23 cycles). | ||
+ | |||
+ | IY - the same as " | ||
+ | for this one. | ||
+ | |||
+ | SP - 16bit register. "Stack pointer", | ||
+ | of stack. Stack can be pointed to every memory location (#0000 - #FFFF). | ||
+ | |||
+ | I - 8bit register, which points to interrupt. | ||
+ | |||
+ | R - 8bit register for memory regenerating. | ||
+ | |||
+ | PC - 16bit register, " | ||
+ | executing command. | ||
+ | |||
+ | ! Z80 has two independent sets of registers: " | ||
+ | be changed by EXX registers " | ||
+ | EX AF, AF' register " | ||
+ | Processor don't know, with which one of two sets it works now. | ||
+ | |||
+ | ------------------------------------------------------------------------------- | ||
+ | Commands: What moves numbers in registers and memory? | ||
+ | ------------------------------------------------------------------------------- | ||
+ | LD A,#04 - moves 4 into register " | ||
+ | |||
+ | LD HL,#4000 - moves 16384 into register " | ||
+ | |||
+ | LD (#C000),A - moves value of register " | ||
+ | |||
+ | LD (HL),B - moves value of register " | ||
+ | |||
+ | LD A,(HL) - moves value, which is held in memory location pointed by " | ||
+ | into register " | ||
+ | |||
+ | LD (IY+#03),A - moves value of register " | ||
+ | register " | ||
+ | |||
+ | ------------------------------------------------------------------------------- | ||
+ | Commands: What moves blocks of bytes? | ||
+ | ------------------------------------------------------------------------------- | ||
+ | LDIR - very cool command, moves block of bytes pointed by " | ||
+ | location pointed by " | ||
+ | |||
+ | | ||
+ | |||
+ | LD HL,SOURCE | ||
+ | LD DE, | ||
+ | LD BC,LENGHT | ||
+ | LDIR | ||
+ | |||
+ | | ||
+ | lots of LDI - it's much faster! There is another block command LDDR, the | ||
+ | difference is: LDIR moves blocks from start to end, LDDR moves them from end | ||
+ | to start. | ||
+ | |||
+ | Also, there are commands for searching block of bytes in memory and commands | ||
+ | to put in port block of bytes and take from port block of bytes. | ||
+ | |||
+ | ------------------------------------------------------------------------------- | ||
+ | The Stack. | ||
+ | ------------------------------------------------------------------------------- | ||
+ | Stack is very useful on Z80. It's pointed by register " | ||
+ | anywhere you want, because " | ||
+ | CPU, the SP (Stack Pointer) is merely an 8bit offset of page 1 ($01xx). | ||
+ | is why the 65xx stack is limited to page 1). | ||
+ | |||
+ | Stack is used for commands like CALL, it pushes on stack return address, which | ||
+ | is later used by an RET. The stack can be used as a very fast block-byte mover | ||
+ | and buffer refresher, for example. | ||
+ | |||
+ | | ||
+ | on stack and POP pops 16bit value from stack. Remember, the stack can be of | ||
+ | any 16bit (0-65535) size and placed anywhere. | ||
+ | |||
+ | ------------------------------------------------------------------------------- | ||
+ | Interrupts. | ||
+ | ------------------------------------------------------------------------------- | ||
+ | Z80 has two interrupt modes - NMI and INT. NMI is a non-maskable interrupt | ||
+ | and INT is maskable. NMI defines the main hardware interrupts and generally, | ||
+ | they can not be used by coder. Fortunately, | ||
+ | register I holds value of interrupt vector. There is IM0, IM1 and IM2 mode of | ||
+ | INT. Rather use have IM2 mode. Command to return from intrrupt routine on Z80 | ||
+ | is RETI. | ||
+ | |||
+ | ------------------------------------------------------------------------------- | ||
+ | Port control. | ||
+ | ------------------------------------------------------------------------------- | ||
+ | The commands to control port are: OUT and IN. OUT writes value to port, but IN | ||
+ | reads value from it. All controling of music processors, disk controller and | ||
+ | RAM pages swithching is done by OUT. But some, for example to check, if key is | ||
+ | pressed, you must use command IN. There can be following combinations of IN: | ||
+ | |||
+ | LD A,high value of port address | ||
+ | IN A,(low value of port address) | ||
+ | |||
+ | or | ||
+ | |||
+ | LD BC,full 16bit port address | ||
+ | IN A,(C) | ||
+ | |||
+ | and for OUT: | ||
+ | |||
+ | LD A,value to send to port | ||
+ | LD C,high value of port address | ||
+ | OUT (low value of port address),A | ||
+ | |||
+ | or | ||
+ | |||
+ | LD A,value to send to port | ||
+ | LD BC,full 16bit port adress | ||
+ | OUT (C),A | ||
+ | |||
+ | |||
+ | There is more to the Z80 of course (Flags, etc.), but this should get you | ||
+ | started. | ||
+ | Spracklen (co-author of Sargon chess). | ||
+ | will yield great rewards with Z80, as with any human endeavour persued | ||
+ | at length. :) | ||
+ | |||
+ | Raver | ||
+ | -- | ||
+ | For questions or comments on this article, Raver may be reached at the | ||
+ | following internet address : raver@10mb.com | ||
+ | |||
+ | |||
+ | ::::::::::: | ||
+ | \H01:::::::::::::::::::::::::: | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | Charger up! | ||
+ | |||
+ | The VIC-20 to ATARI 2600 'Ram Cartridge' | ||
+ | | ||
+ | | ||
+ | |||
+ | |||
+ | A while back, I was searching the internet and found several hundred ROM | ||
+ | images for the Atari 2600 video computer system. | ||
+ | ways to transfer the images to the 2600, but none used a Commodore computer | ||
+ | as a host. Seeing that my own VIC-20 was equipped with sufficient hardware | ||
+ | to become a host, I set about the task of devising a VIC-20 --> Atari 2600 | ||
+ | programming interface. | ||
+ | build it. Be warned that most of the wiring is in schematics (.gif format- | ||
+ | -two of the .uue's at the end of this article) so you do need to have the | ||
+ | ability to read and carry out a schematic. | ||
+ | to outline some of the more salient points required to build the interface | ||
+ | and thereby ease the process. | ||
+ | this at your own risk. Neither myself nor the staff of disC=overy is | ||
+ | responsible for any use or misuse of the information presented in this | ||
+ | article. | ||
+ | -- | ||
+ | |||
+ | What you need to own: | ||
+ | |||
+ | - Atari 2600 video game. | ||
+ | |||
+ | - Commodore Vic20 plus ram expansion of 8k or more. If you don't have 8k | ||
+ | | ||
+ | | ||
+ | |||
+ | - Commodore 1540/ | ||
+ | | ||
+ | numb. from 8 to 1 in the basic program running on the Vic. The " | ||
+ | three times in the program. | ||
+ | |||
+ | |||
+ | What you have to build: | ||
+ | |||
+ | RAM CARTRIDGE | ||
+ | ------------- | ||
+ | | ||
+ | a 32k cmos ram chip, plus backup battery and some additional parts. | ||
+ | The ram cart is plugged directly to the atari like a real cartridge. | ||
+ | To load games to the ram, the cart has to be plugged to the main | ||
+ | circuit. The ram cart can hold up to 16 games of 2k or 8 games of 4k | ||
+ | length. | ||
+ | |||
+ | <Note : Schematic for the ram cartridge is in CART.GIF> | ||
+ | |||
+ | Components description: | ||
+ | |||
+ | - U1 : ...62..256... - the name isn't important. Has to be a 32k by 8bit | ||
+ | | ||
+ | and low-power data retention ability, i.e. ability to remember the | ||
+ | data when the operating voltage is falling to 1.5V or lower. Most | ||
+ | CMOS chips have these capabilities. It has to come in the standard | ||
+ | 28 pin dual-in-line package. | ||
+ | |||
+ | 32k static ram pinout diagram: | ||
+ | | ||
+ | A14 -|1 28|- Vcc Vcc - operating voltage +5V, | ||
+ | A12 -|2 27|- R/W or 1.5V in sdandby mode. | ||
+ | A7 | ||
+ | A6 | ||
+ | A5 | ||
+ | A4 | ||
+ | A3 | ||
+ | A2 | ||
+ | A1 | ||
+ | A0 | ||
+ | D0 | ||
+ | D1 | ||
+ | D2 | ||
+ | ground -|14 15|- D3 | ||
+ | | ||
+ | |||
+ | |||
+ | - U2 : CMOS 4011 - quad two-input nand gate, 14 pin IC. | ||
+ | In this circuit and in the main circuit, it can be replaced by a CMOS | ||
+ | hex inverter or by NPN transistor + 2 resistors -- implementation for | ||
+ | each inverter. I used 4011 because I have many of this kind. | ||
+ | |||
+ | - Q1 : 2N2222. Can be replaced by any silicon NPN small signal | ||
+ | | ||
+ | | ||
+ | |||
+ | - D1 : 1N4001. Can be replaced by any similar rectifier diode. | ||
+ | |||
+ | - D2 : 1N4148 , or similar. | ||
+ | |||
+ | - C1 : 100 micro-farad , 10V, electrolithic capacitor. | ||
+ | |||
+ | - R1 : 22 kilo-ohm resistor . | ||
+ | |||
+ | - R2 : 10 kilo-ohm resistor . | ||
+ | |||
+ | - R3 : 500 kilo-ohm resistor . | ||
+ | |||
+ | - R4 : 50 kilo-ohm resistor . | ||
+ | | ||
+ | All resistors are small power resistors. 0.125 Watt type should be big | ||
+ | enough. | ||
+ | |||
+ | - BAT1 : 3.6V Nickel-Cadmium rechargable battery. The same as the CMOS-RAM | ||
+ | | ||
+ | | ||
+ | |||
+ | - S1...S5 : two-position switch. | ||
+ | |||
+ | - PCB edge connector : The ram cartridge has to be plugged to the Atari like | ||
+ | a regular cartridge, so it must end with double-side printed circuit | ||
+ | edge connector, with 24 contacts, 12 on each side, and with the same size | ||
+ | as of a regular cartridge edge connector. | ||
+ | The Connector can be an old atari cartridge as the PCB connector. Just | ||
+ | remove the rom chip and connect the old cart to the rest of the ram | ||
+ | cartridge circuit by wires. Using one or two flat-cables instead of 24 | ||
+ | seperate wires will give a nicer look. | ||
+ | Another option is that the connector and the rest of the cartridge can | ||
+ | be on the same printed circuit. I know There are EPROM cartridges made | ||
+ | for the Atari2600, where instead of the rom they have socket for eprom | ||
+ | chip, plus the 4011 chip. The whole ram-cartridge can be built on this | ||
+ | platform, saving a lot of wiring work. | ||
+ | |||
+ | - >> Important notes << - | ||
+ | |||
+ | 1. When using one of these options, remember to disconnect the ground | ||
+ | (contact 13) from the signal ground (contact 12), as the latter one | ||
+ | is the read/write control. | ||
+ | |||
+ | 2. It is strongly advised that the ram cart is made in a way obtaining | ||
+ | only one possible way to plug in the cartridge, Otherwise, every time | ||
+ | you plug it to the atary or to the main circuit, you have to be | ||
+ | carefull not to plug it the wrong way, i.e. upside down - this can | ||
+ | cause permanent damage. | ||
+ | |||
+ | Edge-connector diagram : | ||
+ | (looking at the edge connectors of an atari cartridge, when the cartridge' | ||
+ | big label is up) | ||
+ | |||
+ | D3 | ||
+ | | ||
+ | ----------------------------------------------------------- printed circuit | ||
+ | 13 | ||
+ | GND D2 | ||
+ | |||
+ | |||
+ | Functional description: | ||
+ | (you don't have to read this if the schematic is clear to you) | ||
+ | |||
+ | - CS inverter: the nand gate in the lower-right corner - invert the | ||
+ | | ||
+ | | ||
+ | |||
+ | - chip select protection: R3,R4,Q1 - lock the ram when the power is down, ie. | ||
+ | when Atari is off. R1 and the CS inverter also contribute to this task. | ||
+ | |||
+ | - R/W inverter: the upper nand gate. Like with the chip select, converts | ||
+ | | ||
+ | |||
+ | - back-up section : the battery BAT1 feeds the ram and the 4011 when the | ||
+ | | ||
+ | the stand-by current from the battery when power is down. D2 and C1 improves | ||
+ | the power supply in transition times. | ||
+ | |||
+ | - s3,s4 and s5 : select one of the eight 4k blocks avilable. | ||
+ | |||
+ | - s1: 4k-mode / 2k-mode selection. | ||
+ | |||
+ | - s2: in 2k-mode - select which one of the 2k-blocks is used. | ||
+ | |||
+ | |||
+ | Notes: | ||
+ | |||
+ | Build the cart in a way you can hold it in your hands without touching the | ||
+ | metal parts. Shorting something with your fingers can lead to data lost. | ||
+ | If the circuit seems too complicated, | ||
+ | |||
+ | - The rechargable battery. Can be replaced by a cheaper non-rechargable | ||
+ | | ||
+ | | ||
+ | plus the voltage drop over D2, we get minimum voltage of 3.6 that the | ||
+ | | ||
+ | | ||
+ | can be used, since most of the cmos ram chips can retain their data under | ||
+ | 1.5, 1.0 or even 0.7V stand-by operating voltage. If the problem is with the | ||
+ | 4011, it can be replaced. Each one of the two gates used in this circuit | ||
+ | can be replaced by a NPN transistor and two resistors, and this combination | ||
+ | | ||
+ | |||
+ | - The chip select protection circuit: Q1, R3 and R4. Before the 3.6V battery, | ||
+ | there was 2.4V battery. After some months, the cartridge became forgetful. | ||
+ | Then I added these parts, but the problem didn't get solved. Only when the | ||
+ | | ||
+ | | ||
+ | Now, R3,R4 and Q1 seem useless, but I have decided not to remove them, | ||
+ | | ||
+ | If you want to check this, remove them and see if the ram cart still | ||
+ | | ||
+ | | ||
+ | |||
+ | - s3,s4 and s5 : If you use standard pin-compatible 8k ram chip instead of | ||
+ | the 32k ram, you don't need s3 and s4. But then the cartridge can hold only | ||
+ | two 4k atari games or four 2k games. s5 can be replaced by shorting A12 of | ||
+ | the ram to ground or to +5V, but then the amount of data that can be stored | ||
+ | on the chip is divided by two. | ||
+ | |||
+ | - s1,s2 : After selecting the 4k bank by s3..s5, s1 is used to select between | ||
+ | two options: one 4k game or two 2k games. In 2k mode, s2 selects between the | ||
+ | two 2k banks. s1 and s2 can be omitted if you load the 2k games as 4k games- | ||
+ | you load a 4k file which it's last 2k half is a copy of the first 2k half. | ||
+ | In this case, the A11 pin of the ram should be connected directly to the A11 | ||
+ | | ||
+ | |||
+ | |||
+ | THE MAIN CIRCUIT | ||
+ | ---------------- | ||
+ | The task of the main circuit is to load games into the ram cartridge. Also, | ||
+ | it can read to save the content of a real cartridge. The circuit is connected | ||
+ | to the Vic20' | ||
+ | |||
+ | <Note : Schematic for the main circuit is in MAIN.GIF> | ||
+ | |||
+ | [Ed. Note : Assuming the VIC-20' | ||
+ | then my guess is that this portion of the project could be adapted to the | ||
+ | C-64 with some minor hardware and software revisions. | ||
+ | |||
+ | Components description: | ||
+ | |||
+ | - U1,U2,U3 : CMOS 4094 - shift register, 16 pin IC. | ||
+ | |||
+ | - U4 : TTL 74LS165/ | ||
+ | |||
+ | - U5 : CMOS 4011 - quad two-input nand gate, 14 pin IC. | ||
+ | |||
+ | - Q2,Q4 : 2N2222. Can be replaced by any silicon NPN small signal | ||
+ | | ||
+ | Vce(max) > 10V , Ic(max) > 30mA , Hfe(min) > 100 . | ||
+ | |||
+ | - Q1 : 2N2907. Can be replaced by any silicon PNP small signal | ||
+ | | ||
+ | |||
+ | - Q3 : 2N2905. Can be replaced by any silicon PNP transistor, with | ||
+ | these ratings : | ||
+ | Vce(max) > 10V , Ic(max) > 300mA , Hfe(min) > 50 , | ||
+ | metal package (TO-3,TO-5 etc). | ||
+ | |||
+ | - LED : any common small (2mm size) visible LED. The LED and its | ||
+ | resistor R9 are optional. | ||
+ | |||
+ | - R1, | ||
+ | |||
+ | - R4,R7 : 1 kilo-ohm resistor. | ||
+ | |||
+ | - R5 : 220 kilo-ohm resistor. | ||
+ | |||
+ | - R9 : 5 kilo-ohm resistor. | ||
+ | |||
+ | All resistors are small power resistors. 0.125 Watt type should be big | ||
+ | enough. | ||
+ | |||
+ | - Vic20 User Port interface : The user port in the Vic20 is a double-side | ||
+ | printed circuit edge connector, with 24 contacts, 12 on each side. The | ||
+ | interface is a female connector in the same size which can be plagged on | ||
+ | it, and contacted to the main board by 8 wires. | ||
+ | Be careful not to plug the interface to the Vic20 the wrong way, | ||
+ | i.e. replacing the top side with the bottom side. Such a mistake can | ||
+ | damage the VIA-6522 on your Vic20 or some parts in the external circuit. | ||
+ | Write " | ||
+ | you can shape two little plastic parts and insert them in the female | ||
+ | connector, against the two a-symetric slots in the user-port edge | ||
+ | connector. This will prevent you from doing such a dangerous mistake. | ||
+ | |||
+ | - Atari2600 cartridge interface : exactly like the one in the Atari - a | ||
+ | female PCB connector with 24 contacts, 12 on each side. like in the case | ||
+ | of the VIC's user port, it is recomended to ensure that there is only one | ||
+ | way to plug in the ram cartridge or the regular atari2600 cartridge. | ||
+ | The easiest way to obtain this interface is to take it out of old Atari | ||
+ | video game cart. Then the interface can include its plastic housing which | ||
+ | eliminates the possibility to insert a cartridge the wrong way. You can use | ||
+ | the entire case of the atari cart. to hold the main circuit inside. | ||
+ | |||
+ | |||
+ | Functional description: | ||
+ | (you don't have to read this if the schematic is clear to you) | ||
+ | |||
+ | - U1, U2 and U3 : three 8 bit serial to parallel shift registers, which | ||
+ | | ||
+ | + 12 bit adrress. | ||
+ | |||
+ | - U4 : converts the parallel data byte read from the cartridge to serial data | ||
+ | sent to CB2 under control of CB1. | ||
+ | |||
+ | - chip-select inverter - the lower nand gate inverts active-low chip select | ||
+ | | ||
+ | is software programmable and can produce active-low signal, the default | ||
+ | | ||
+ | is high. Also this gate isolates the vic from the plugged card - you can | ||
+ | never know what you plug in, so this method of operation is prefered. | ||
+ | |||
+ | - Q4, R5 and R6 : Q4 and R5 enables U4 to send data to the serial line CB2 | ||
+ | when PB7 is in read mode. Otherwise CB2 is used to send data to the 4094s. | ||
+ | R6 is pull-up resistor for data coming from U4. | ||
+ | |||
+ | - read/write inverter: the upper nand gate - similar to the chip select | ||
+ | | ||
+ | |||
+ | - R7, R8: this voltage divider protects the nand gate from overload when PB7 | ||
+ | is in write mode and a real cart is plugged. R8 is also pull-down resistor | ||
+ | for the read/write in the ram cart. | ||
+ | |||
+ | - power control: Q1, Q2, Q3, R1, R2, R3 and R4 amplifies the 0.2mA control | ||
+ | | ||
+ | | ||
+ | |||
+ | Notes: | ||
+ | |||
+ | - The LED isn't necessary, but it helps you know when the main circuit is | ||
+ | | ||
+ | | ||
+ | |||
+ | - The ground sign on the schematic doesn' | ||
+ | that everything ended with it is connected with each other. It can be really | ||
+ | | ||
+ | |||
+ | - The 4011 can be replaced. See the notes about the 4011 in the ram | ||
+ | cartridge section of this document. | ||
+ | |||
+ | - The power control can, theoriticaly, | ||
+ | base of Q3 is then connected to the hook up of R1 and R2, instead of the base | ||
+ | of Q1. But the current amplification of Q3 has to be high. We don't want to | ||
+ | | ||
+ | more than 4.8V, under load currents of 50mA, 100mA and more. | ||
+ | If we don't chage R1, then I(PB5) = 0.2mA. The user port of the Vic20 is | ||
+ | | ||
+ | | ||
+ | Now we get Hfe(min)= 250. Still can't be obtained from every 2N2905-type | ||
+ | | ||
+ | | ||
+ | 4.3V output, not enough for U4 and the memory chip on the cart. | ||
+ | One transistor can be saved if PB5 turns into active-high instead of | ||
+ | | ||
+ | in the Vic is being reseted. | ||
+ | |||
+ | |||
+ | The software part: | ||
+ | |||
+ | The program which controls the ram-cart loading and the cartridge saving | ||
+ | is a 3.5k Basic program, running on the Vic20. It uses 0.5k machine language | ||
+ | program. | ||
+ | |||
+ | The program files are: | ||
+ | - ATARI14/ | ||
+ | - ATARI MCP the machine code program. | ||
+ | |||
+ | [Ed. Note : The uuencoded versions of these files will extract with these | ||
+ | | ||
+ | the filename field in the uuencoded versions at the end of this article. | ||
+ | | ||
+ | | ||
+ | |||
+ | The basic program is written for using a disk drive. If you have only tape, | ||
+ | change the " | ||
+ | times: When loading games, when saving cartridge content and when loading the | ||
+ | machine code program. | ||
+ | Every time the program is runned, a checksum is made over the m.code program | ||
+ | in memory, and only if the checksum fails, the m.code program is reloaded. | ||
+ | Something i forgot - after reloading, the m.c. program isn't being checked- | ||
+ | so it's downloading from the PC has to be done carefully. To check it, load | ||
+ | first the machine code program with ", | ||
+ | then load and run the basic program. If the m.c. program isn't reloaded, then | ||
+ | you know it had been downloaded properly. | ||
+ | If the ram expansion on the vic is non volatile, the mc. program will remain | ||
+ | in memory when the Vic is off. In the case of 16k+ ram expansion, the basic | ||
+ | start can be raised to $4000 and then the basic program becomes non volatile | ||
+ | too. In this case you have to change the memory settings in the program' | ||
+ | first lines. | ||
+ | |||
+ | The current memory configuration: | ||
+ | $1000 4096 - $11FF 4095 : screen bytes | ||
+ | $1200 4608 - $2DFF 11775 : basic program + variables | ||
+ | $2E00 11776 - $2FFF 12287 : machine language code | ||
+ | $3000 12288 - $3FFF 16383 : 4k game buffer | ||
+ | |||
+ | | ||
+ | disk and the cartridge byte by byte, and thus to compress the entire thing | ||
+ | to an unexpanded Vic20. But It's not simple and limits the program' | ||
+ | to grow up. | ||
+ | |||
+ | |||
+ | Operating instructions: | ||
+ | |||
+ | SAVING-CARTRIDGE CONTENT | ||
+ | ------------------------ | ||
+ | 1. The main circuit has to be plugged to the Vic20' | ||
+ | is turned on. | ||
+ | |||
+ | 2. Run the basic program, if it is not already running. | ||
+ | |||
+ | 3. Plug in the Atari2600 game cartridge. | ||
+ | |||
+ | 4. From the main menu, select "copy 2k" / "copy 4k" with or without check. | ||
+ | Before selecting, check that the cartridge is plugged in. DON'T plug it | ||
+ | in or out when the vic is reading it. It is avdised to connect a LED | ||
+ | (+ its resistor) to the main circuit, to see when it is powered on. Only when | ||
+ | the power is off, the cartridge can be inserted or removed. | ||
+ | |||
+ | 5. If you selected copy with check, the saved file will be reloaded & compared | ||
+ | with the cartridge content. | ||
+ | |||
+ | 6. The cartridge content is now saved in a file. The format of the file is 2 | ||
+ | bytes - low and high byte of the start adrress of the buffer, followed by the | ||
+ | content itself - 2048 or 4096 bytes. | ||
+ | |||
+ | 7. Now you will be asked for continue. If yes, the procedure repeats itself in | ||
+ | the same copy-type selection. | ||
+ | |||
+ | | ||
+ | |||
+ | - 2k games can be saved as 4k games, by choosing "copy 4k" on the menu. | ||
+ | The 4k file following the start adrress will be a double image of a 2k game. | ||
+ | |||
+ | - The saving proccess can be done with no cart plugged in without damaging | ||
+ | | ||
+ | |||
+ | - You can hit RUN/STOP + RESTORE anytime and the main circuit will be | ||
+ | | ||
+ | |||
+ | LOADING GAMES TO THE RAM CARTRIDGE | ||
+ | ---------------------------------- | ||
+ | 1. The main circuit has to be plugged to the Vic20' | ||
+ | is turned on. | ||
+ | |||
+ | 2. Run the basic program, if it's not already running. | ||
+ | |||
+ | 3. Insert the ram cartridge, if it isn't already plugged in. DON' | ||
+ | in or out when the vic is writing to reading it. It is avdised to connect a | ||
+ | LED (+ its resistor) to the main circuit, to see when it is powered on. Only | ||
+ | when the power is off, the cartridge can be inserted or removed. | ||
+ | |||
+ | 4. Select the desired switch combination on the ram-cart. Check that it is not | ||
+ | the combination of an already loaded game, which you don't want to be | ||
+ | overwritten. | ||
+ | If loading 4k game,move s1 to 4k mode. For a 2k game, move s1 to 2k mode | ||
+ | and select (with s2) one of the 2k banks. s3, s4 and s5 selects between | ||
+ | 4k banks, and gives 8 combinations. | ||
+ | |||
+ | 5. From the main menu, select "load game". Type the name of the game and it | ||
+ | will be loaded. No disk browser at this stage. | ||
+ | |||
+ | | ||
+ | |||
+ | - When loading 2k games which have been saved as 4k games, move s1 to 4k | ||
+ | mode. | ||
+ | |||
+ | - The loading proccess can be done with no cart plugged in without damaging | ||
+ | | ||
+ | |||
+ | - You can hit RUN/STOP + RESTORE anytime and the main circuit will be | ||
+ | | ||
+ | |||
+ | |||
+ | begin 644 atari14/c | ||
+ | M 1(<$@$ CR!3059%(D P.D%405)), | ||
+ | MDR( -1(% (\ 6!(+ (\@*BH@051!4DD@0T%34T545$4@34%.04=%4B J*@!> | ||
+ | M$@P CP" | ||
+ | M$C( C2 U,#8P ,@2/ " | ||
+ | M2$E.12!# | ||
+ | M.H\@4$]715(@3T9& | ||
+ | M2P N$W(!ER X,C0L3$\ZER X, | ||
+ | M-38I.DQ/ | ||
+ | M5 "' | ||
+ | MER X, | ||
+ | M345-3U)9 -,3N &> | ||
+ | M DA)LK4H042M, | ||
+ | M+$A).H\@4T]54D-% $043@)(2; | ||
+ | MER X,C0L3$\ZER X, | ||
+ | M3[), | ||
+ | M=@*/ | ||
+ | M%(H" | ||
+ | M5" | ||
+ | M*" | ||
+ | M344@(" | ||
+ | M)" | ||
+ | MC@# | ||
+ | M5D4 _16Z!)\R+# | ||
+ | M12!005)!345415)3 " | ||
+ | M+# | ||
+ | M-CDZCR!3150@1DE, | ||
+ | M%G %GC8U-# | ||
+ | M3$]!1 #.%H(%C2 Q,S0P .D6R 67-S@P+# ZES< | ||
+ | MT@6> | ||
+ | M*0 @%]<%C@ K%]P%CR!3059% # | ||
+ | M($9)3$4@4$%204TN &P7\ 67-S@L3$\ZES< | ||
+ | MLK4H14]& | ||
+ | M +< | ||
+ | M3U(@2T59(%-44D]+10# | ||
+ | MBT$DLB(BIS$V, | ||
+ | M(%1(12 24D%-DB!# | ||
+ | M4D5!1%DN(@!E& | ||
+ | M3D53(" | ||
+ | M05-3151410# | ||
+ | M1R B3DTD /T82 A, | ||
+ | M%QE2" | ||
+ | MH2!!)# | ||
+ | MBQEP" | ||
+ | M" | ||
+ | M,"D [AEO" | ||
+ | MB3(T.3 | ||
+ | M,# | ||
+ | M*BH@34%)3B!-14Y5(" | ||
+ | M(@"'& | ||
+ | M251(($-(14-+(@" | ||
+ | M3U!9(# | ||
+ | M" | ||
+ | M" | ||
+ | MLC ZB3(W,S0 > | ||
+ | MBT$DLB(S(J< | ||
+ | M,# | ||
+ | M& | ||
+ | M2T4@04Y9($M%62 @(" | ||
+ | M5T%)5" | ||
+ | M040@0DQ/ | ||
+ | M(DY-) " | ||
+ | M4T%610# | ||
+ | M'" | ||
+ | M'4 +BT$DLB).(J< | ||
+ | M' | ||
+ | MBPN9(DQ/ | ||
+ | M+TXI(@" | ||
+ | M';, | ||
+ | M$X\ )A[.$X$@0[(X, | ||
+ | M(#4P-S( , | ||
+ | M, | ||
+ | M' | ||
+ | M-" | ||
+ | M, | ||
+ | M*BH " | ||
+ | M%(L@PB@W.# | ||
+ | M3++# | ||
+ | C-S@S*: | ||
+ | |||
+ | end | ||
+ | |||
+ | |||
+ | begin 644 atari mcp | ||
+ | M " | ||
+ | M& | ||
+ | M" | ||
+ | MK1V12(H)" | ||
+ | M& | ||
+ | M[XT; | ||
+ | MK1R1*1\)((T< | ||
+ | M+Z L:Z%JR 2+B!U+]# | ||
+ | MKO 'J1 @: | ||
+ | MT AH:" N+TS$+V" | ||
+ | M()XO8*G_C1" | ||
+ | 3 | ||
+ | |||
+ | end | ||
+ | |||
+ | |||
+ | begin 644 main.gif | ||
+ | M1TE& | ||
+ | M @ # | ||
+ | M%P0:; | ||
+ | MK32AU--; | ||
+ | M? | ||
+ | M -, 2@4 U]<:U ! U]1& | ||
+ | MU"' | ||
+ | M/ | ||
+ | M4Z%0> | ||
+ | M@8_ZVA(-,; | ||
+ | M ZR< | ||
+ | MY]SNM!8=)UCWWMR> | ||
+ | MYYFU> | ||
+ | M:ES1 U(=T0686' | ||
+ | M7\-5]*)< | ||
+ | MAC9%J=M+U0E%Q& | ||
+ | MGSFT!N6? | ||
+ | MFJ.BR< | ||
+ | MI1+, | ||
+ | M#:< | ||
+ | M%\ 0TVK)64V)*9^8/ | ||
+ | M;, | ||
+ | M[5CS8D? | ||
+ | MPS" | ||
+ | M> | ||
+ | M%T!/L $>< | ||
+ | M' | ||
+ | M/.#]$DB2 ?; | ||
+ | MF< | ||
+ | M& | ||
+ | MI]1HQ39^RXUIU CZ[$& | ||
+ | M& | ||
+ | M"; | ||
+ | M3,& | ||
+ | M< | ||
+ | MF: | ||
+ | MJ.@GRQDBPJI1C47%96=0\F+Z+' | ||
+ | M!7U< | ||
+ | M0!:R6 D; | ||
+ | M:# | ||
+ | MA53#/ C58: | ||
+ | M\^UOPWBA(PQ@4> | ||
+ | M[<O F3C3RI28CGAD/ | ||
+ | M+8H!KQ7> | ||
+ | M36Q(%/? | ||
+ | M> | ||
+ | M/ | ||
+ | MOKI" | ||
+ | M: | ||
+ | M@_7.A\[, | ||
+ | M# | ||
+ | M" | ||
+ | MAQ' | ||
+ | M"& | ||
+ | MRL(YHYYV/ | ||
+ | MYXSHU; | ||
+ | MD ,?> | ||
+ | M# | ||
+ | MN!SY=5S2WL^@3<" | ||
+ | M)-S" | ||
+ | M=Z_V0I' | ||
+ | M/O3G;#9R: _D/ | ||
+ | M51&G= 6U1J' | ||
+ | M#,^T;A-P XY!< | ||
+ | M015A!((I" | ||
+ | MH" | ||
+ | M4XI4T5: | ||
+ | M0' | ||
+ | MY" | ||
+ | M D8JQX# | ||
+ | M/ | ||
+ | M#H!PQ_ A\TB.T=1%QI84H60YB^B 5]B5>?> | ||
+ | M.V" | ||
+ | M7%EXAKB: | ||
+ | MGFD&" | ||
+ | M3/ | ||
+ | MD-OX4< | ||
+ | M1[5E' | ||
+ | M" | ||
+ | M$D-" | ||
+ | M: | ||
+ | MN@:# | ||
+ | M ' | ||
+ | MV:#: | ||
+ | MZ# | ||
+ | MFK!Z-ELY]P )!"' | ||
+ | M$H\(2[*/ | ||
+ | M$$FT1P5; | ||
+ | M"/ | ||
+ | M" | ||
+ | ML@: | ||
+ | MQ%L: | ||
+ | MN N^P? | ||
+ | MGBE+G-DJ4IG45" | ||
+ | M" | ||
+ | M..^W3" | ||
+ | M8S@XJY); | ||
+ | MQL0; Q51R(*L7' | ||
+ | M? | ||
+ | M=< | ||
+ | M?, | ||
+ | M&< | ||
+ | M9RP+& | ||
+ | M76T]; | ||
+ | M4WE> | ||
+ | M'< | ||
+ | M= " | ||
+ | M",/ | ||
+ | MK-L*Y%!^ " | ||
+ | MT+# | ||
+ | MP$.HT& | ||
+ | MU=%M" | ||
+ | M3O@EL9J[=BUM6(J8SUAHN=G< | ||
+ | MOPS\=P3(FY*3$FC..:& | ||
+ | M("? | ||
+ | MN9H4G33+> | ||
+ | MW$Y/< | ||
+ | M.WM@G> | ||
+ | M''?';> | ||
+ | M# | ||
+ | MI_' | ||
+ | M? | ||
+ | MP; | ||
+ | M< | ||
+ | M].# | ||
+ | M^F[DJ" | ||
+ | MS0-\)$CJG? | ||
+ | M!HL " | ||
+ | M, | ||
+ | M0P& | ||
+ | M%V& | ||
+ | M04: | ||
+ | M0S4%> | ||
+ | M6P,: | ||
+ | MXTDHZ20]W;'" | ||
+ | M; | ||
+ | M,&& | ||
+ | M=$Q(T.QA!$Y\W ,"" | ||
+ | MI)06SNJAET4!BT" | ||
+ | MH24: | ||
+ | MG< | ||
+ | M, | ||
+ | MN/> | ||
+ | M\$9D0" | ||
+ | MG5YX;& | ||
+ | M$6XJZSI!XNL/ | ||
+ | M]: | ||
+ | M< | ||
+ | M> | ||
+ | MRQ-T/ | ||
+ | MH++EHS%6N8S7]I_]7N? | ||
+ | M9E" | ||
+ | MS& | ||
+ | MONF#9 RJWZL0EI?< | ||
+ | M)2]T!OE?&:#' | ||
+ | M-4YG4X@2# | ||
+ | M# | ||
+ | M<"; | ||
+ | M0: | ||
+ | M*<32,E 8(S .*! , | ||
+ | M" | ||
+ | MZ(G>" | ||
+ | MD SI# | ||
+ | M1 H2*RTT9D' | ||
+ | MHS]6O? | ||
+ | MV& | ||
+ | MS\7_; | ||
+ | M? | ||
+ | M*G=S& | ||
+ | MUCTJJZZ8KGG(, | ||
+ | M , | ||
+ | M 7!PES< | ||
+ | M7+X-' | ||
+ | M& | ||
+ | M4'## | ||
+ | MA0TCOEB? | ||
+ | M[3KH74YB> | ||
+ | MB(*& | ||
+ | M612.& | ||
+ | MF2O@!K]M> | ||
+ | M%+L< | ||
+ | M5=YO.& | ||
+ | M7-XYUXW=.UPEG? | ||
+ | M?888B.C_S3 ? | ||
+ | MY6M*$' | ||
+ | M4D*-!D@" | ||
+ | M\# | ||
+ | M< 7_=U3@4SC: | ||
+ | M]AZN !, | ||
+ | M/V5[/E)IIP L? | ||
+ | MKZ$Q=9*+PL, | ||
+ | MPLA? | ||
+ | M> | ||
+ | M& | ||
+ | M B>*GB$, 4E4!P$W[GB1UR, | ||
+ | M+' | ||
+ | M6)F612@2/$ I4V?', | ||
+ | M1^4R$0R9: | ||
+ | MF8[2)4Z0> | ||
+ | MA#?& | ||
+ | M71, | ||
+ | MHG(IN%%S 99T9SC=9@(G" | ||
+ | MH&<> | ||
+ | M1Z$JE!C" | ||
+ | M$9P> | ||
+ | M*1Z-\)<< | ||
+ | M+DH7& | ||
+ | M.H2+ _6@+*DPFVIH!8:, | ||
+ | MMHE\' | ||
+ | M\NHUPK%7BYJ@-; | ||
+ | MVW W1S6C-U06!Y*)Y7.=^" | ||
+ | M8T1D" | ||
+ | M Z& | ||
+ | M: | ||
+ | MJ_!@? | ||
+ | M8MR7G3YSDZVB" | ||
+ | MNF 15N-X6?&' | ||
+ | MA$!Z& | ||
+ | MXA!ZJ0XN*9I/ | ||
+ | M,+N4 C%X8:? | ||
+ | M: | ||
+ | MY+XX9I- # | ||
+ | M8[)/ | ||
+ | MOO5)]GLX0DJ" | ||
+ | M: | ||
+ | M' | ||
+ | MXMRJ' | ||
+ | ME-%I4-F^$)V[[RRTTKRR' | ||
+ | MHRBQS< | ||
+ | M7P0LH, | ||
+ | MM=R# | ||
+ | M7V> | ||
+ | M' | ||
+ | M1E# | ||
+ | ML9VT> | ||
+ | M]1X\5\8W\: | ||
+ | MS< | ||
+ | M-A$83J$ S+M%E00]D./ | ||
+ | MU0,< | ||
+ | M V.M$C& | ||
+ | M_TUPZI0X4EE CPB*3TX68? | ||
+ | ML@? | ||
+ | M W9VF.9VP" | ||
+ | MFT: | ||
+ | M6 QYR=> | ||
+ | M$!L< | ||
+ | MC_0." | ||
+ | M+A)S3PO6+27-# | ||
+ | M3MRL+-W; | ||
+ | M@' | ||
+ | MCE3? | ||
+ | MP3D$ SW()=TY%7BW? | ||
+ | M-^# | ||
+ | M; | ||
+ | M-F(4$UN3; | ||
+ | M;IDL A\$' | ||
+ | M5FNU%ZTA!)JHP5NYK: | ||
+ | M+< | ||
+ | ML, | ||
+ | M, | ||
+ | M%IXH0D' | ||
+ | M):](D 9; | ||
+ | MB%0/S8_\2 4QG-T*: | ||
+ | MTAV\> | ||
+ | MC& | ||
+ | M@WPN> | ||
+ | MXY0ZP$C=< | ||
+ | MX& | ||
+ | MU0_]5? | ||
+ | M9BLXP26Z)(" | ||
+ | MP]P;/ | ||
+ | M$, | ||
+ | M=-[*X^& | ||
+ | ML:> | ||
+ | MO9%*^3T@_Q: | ||
+ | M@MO8TQHE# | ||
+ | M*:; | ||
+ | MI@:[" 77_LKMB(,; | ||
+ | M$ESKZ"> | ||
+ | M' | ||
+ | M3, | ||
+ | M%F0B, | ||
+ | M_ ,:? | ||
+ | M(Y[SR.1E[MO6]U" | ||
+ | MAB? | ||
+ | M- $S8> | ||
+ | M622'> | ||
+ | M]L!J? | ||
+ | M, | ||
+ | MB ?KV]LN ; | ||
+ | M> | ||
+ | M0FT/ | ||
+ | M", | ||
+ | M5# | ||
+ | M.YAJO?& | ||
+ | M7;? | ||
+ | MEM06-]3))/ | ||
+ | M!3< | ||
+ | MNSQF.9\9+@2/ | ||
+ | MAT@AKNZPR+L> | ||
+ | MYP-]# | ||
+ | M=(KW, | ||
+ | MI/ | ||
+ | M3< | ||
+ | M.G)# | ||
+ | M*9VDP]' | ||
+ | MLBY*M*AHT? | ||
+ | MT0' | ||
+ | MEY^EK.& | ||
+ | MK4__# | ||
+ | MUC(U]F5? | ||
+ | M"" | ||
+ | MP; | ||
+ | M0' | ||
+ | M#LQP!<7 +BK& | ||
+ | M=M, A&> | ||
+ | M> | ||
+ | M;? | ||
+ | MCD$A7M86*RP5+O4H)^K2? | ||
+ | M(F]X0]0' | ||
+ | M!T7! CVRQC6GQ6(JZ& | ||
+ | MX)2.A" | ||
+ | MXE145866P!]\.%YHF987)!VDJ(#; | ||
+ | MQY4 -SZ%" | ||
+ | M69N': | ||
+ | MF" | ||
+ | MG< | ||
+ | M^E4; | ||
+ | M: | ||
+ | M+ INU' | ||
+ | M^^%9E38L1$$)LHB; | ||
+ | M5/ | ||
+ | M825A0N< | ||
+ | MD^%",& | ||
+ | M, I5B WV5Y($?] : | ||
+ | MF2O& | ||
+ | MIP, | ||
+ | MZY#O1K 0BZD]2Y4= [(^VR(> | ||
+ | MU98$ZG] @JM4P@T$=W2R\S& | ||
+ | M 4]HNZ!38G\N8UPZ< | ||
+ | MNZ2BD& | ||
+ | MV+5[LJ^7)JF%%8</ | ||
+ | MBE.6> | ||
+ | MKA%!A<&; | ||
+ | M> | ||
+ | MA"" | ||
+ | M' | ||
+ | M4_2$' | ||
+ | M/ | ||
+ | M)PI11R&< | ||
+ | M? | ||
+ | M9GB!9*, | ||
+ | ME4ZZML: | ||
+ | MM%" | ||
+ | M] " | ||
+ | MZ7< | ||
+ | MH7)/ | ||
+ | M>, | ||
+ | M=KGZY=-[V*M9+< | ||
+ | M3)[+? | ||
+ | M=PK B5: | ||
+ | M%]*J' | ||
+ | MKS N/ | ||
+ | ME]U" | ||
+ | MS0E[ H9? | ||
+ | M 2, | ||
+ | MOL)+># | ||
+ | M# | ||
+ | MIPG$U=2UE, | ||
+ | MX" | ||
+ | M(\5QW% 4/O- .Q/ | ||
+ | M]DR< | ||
+ | M\[29 3+W3MFH$^1T(D%ADY!J%1)%U2HE? | ||
+ | M$@*^AR=[$WGL' | ||
+ | MVI; | ||
+ | M$!L6.IV#' | ||
+ | M\0: | ||
+ | MJX? | ||
+ | MW/ | ||
+ | MRL83-& | ||
+ | M[%=W' | ||
+ | M)8MHI8& | ||
+ | M(, | ||
+ | M@TE:P0J[M @8 " | ||
+ | MZ)&D+% C4ST\2)." | ||
+ | M1HC<& | ||
+ | MNB& | ||
+ | M\R-$0L& | ||
+ | MW@:: | ||
+ | MUCGMN"? | ||
+ | M9-C%0S@Y; | ||
+ | MRVG37%L4IO+#? | ||
+ | M> | ||
+ | MZABA74>? | ||
+ | MB73LA.$8%=U0YVZ? | ||
+ | MBS90U@& | ||
+ | M; | ||
+ | M=!#/ | ||
+ | M Z=9!86+- ? | ||
+ | M%L"> | ||
+ | M[Q=^HWWH=N5\*\]FP; | ||
+ | MUERRS=8=QQ4;' | ||
+ | MUXSOT=UT$(, | ||
+ | M/ | ||
+ | M4=7A< | ||
+ | M4" | ||
+ | ME" | ||
+ | MH$$EU%8G" | ||
+ | M: | ||
+ | MFX#; | ||
+ | M=.0D4P8YL9W2: | ||
+ | M< | ||
+ | MEBUE(; | ||
+ | M" | ||
+ | M(-D78Y$G; | ||
+ | M%D" | ||
+ | MP=+M# | ||
+ | M,> | ||
+ | M 3-M:' | ||
+ | MF79A%-R8; | ||
+ | MX< | ||
+ | MNY?< | ||
+ | M" | ||
+ | MQST2\L8'" | ||
+ | M[AAN" | ||
+ | M(%/ | ||
+ | M" | ||
+ | M, | ||
+ | M-; | ||
+ | MKMT& | ||
+ | MFT3XL.< | ||
+ | M._6= 71Z5V4491][8LJCK)3-8UG%3(Z/ | ||
+ | M& | ||
+ | M]!V6> | ||
+ | MC2# | ||
+ | MK^? | ||
+ | M1IUE" | ||
+ | M:;G%@P$68 5F@T+8< | ||
+ | M% S(6CCH$RJ&/ | ||
+ | M4S' | ||
+ | M(87$T7> | ||
+ | M-W& | ||
+ | M2#42*(%= 2YTIA-+$" | ||
+ | MEG[< | ||
+ | M\%$&-8 AES' | ||
+ | MQ; | ||
+ | M(2(<: | ||
+ | MMG@7& | ||
+ | M!4F0QR-0@B=> | ||
+ | MEQ& | ||
+ | M8D0BID*1R< | ||
+ | M# | ||
+ | M? | ||
+ | MD*A(C.-U? [: | ||
+ | MA4CX3=6R/ | ||
+ | M0T9*? | ||
+ | MH8I6%& | ||
+ | M\(RG\Z.A^9D 2325LA"? | ||
+ | MY@:M) *7PFAYUC5X2I& | ||
+ | MBG1A4DOG52M_D5RD%8> | ||
+ | MLIP(JIQ, | ||
+ | M-(VDF: | ||
+ | M< | ||
+ | M3O8WT2.QF9: | ||
+ | MU1A: | ||
+ | MN> | ||
+ | M<, | ||
+ | MJSMJ.E6)O8L< | ||
+ | M2<> < | ||
+ | MA/ | ||
+ | MKP_,' | ||
+ | M' | ||
+ | MA*3R=B34PG(' | ||
+ | M? | ||
+ | M;"?: | ||
+ | M))"" | ||
+ | M^S)< | ||
+ | M;& | ||
+ | M('' | ||
+ | M(CW2)%W2)GW2*)W2*KW2+-W2+OW2, | ||
+ | M0!W40CW41%W41GW42)W42KW43-W43OW44!W54CW55%W55GW56)W56KW57-W5 | ||
+ | M7OW58!W68CW69%W69GW6: | ||
+ | M? | ||
+ | MFKW9G-W9GOW9H!W: | ||
+ | MN)W; | ||
+ | MW=9]W=B=W=J]W=S=W=[]W> | ||
+ | MW_1=W_9]W_B=W_J]W_S=W_[]WP >X (^X 1>X 9^X B>X J^X S>X [^X! > | ||
+ | MX1(^X11> | ||
+ | MXC > | ||
+ | MY$[^Y% > | ||
+ | MYFS> | ||
+ | MGNB*ONB, | ||
+ | M? | ||
+ | M7NS&? | ||
+ | M | ||
+ | M | ||
+ | M | ||
+ | C | ||
+ | |||
+ | end | ||
+ | |||
+ | |||
+ | begin 644 cart.gif | ||
+ | M1TE& | ||
+ | M @ # | ||
+ | MI_O1& | ||
+ | M< | ||
+ | MH# | ||
+ | M@E.0S\, | ||
+ | M\ZH, | ||
+ | MP84: | ||
+ | M4H8" | ||
+ | MP*1E[C9+: | ||
+ | M0L4.&> | ||
+ | MX[F3N 9L7# | ||
+ | MX;: | ||
+ | M? | ||
+ | M@< | ||
+ | M? | ||
+ | M!.@Y8%]04N" | ||
+ | M6< | ||
+ | MF4%PBIRQ_RIN%VQSZ94IK)N' | ||
+ | MK'> | ||
+ | M9" | ||
+ | MELG*" | ||
+ | M5; | ||
+ | MYHMZ*PPN! S" | ||
+ | M+42QQ*$[M, | ||
+ | MVI,& | ||
+ | M& | ||
+ | M& | ||
+ | M' | ||
+ | M^53(# | ||
+ | M9QP$!L\LZG*6< | ||
+ | M1R.TR0!1 PZTX./ | ||
+ | M< | ||
+ | ME5 QLXO0;, | ||
+ | M58[" | ||
+ | MP.& | ||
+ | M? | ||
+ | M, | ||
+ | MK7" | ||
+ | MD(VL9" | ||
+ | M4T.=5DO; | ||
+ | M$NSSA& | ||
+ | M9]I& | ||
+ | MA"=\O =+KBI@8, | ||
+ | M([Q@)M& | ||
+ | M/ | ||
+ | MDREDV< | ||
+ | M, | ||
+ | M]2R)2& | ||
+ | MW' | ||
+ | M96?]H5 HL1=G' | ||
+ | MXAC_E> | ||
+ | M)' | ||
+ | M/ | ||
+ | MK4S< | ||
+ | MTN$ESWE(0]D9$' | ||
+ | MZ!__YC4TWHJ' | ||
+ | MHF]TZX? | ||
+ | MI7YR^_O? | ||
+ | M=1.X@6XS(!=T6!$H=JN'? | ||
+ | M@F46@SJH'" | ||
+ | M(558; | ||
+ | M; | ||
+ | M<DDH4 E86H/ | ||
+ | M(E-FT' | ||
+ | M\6"_ 1H? | ||
+ | MZ' | ||
+ | M]WC)*$ 7X@\> | ||
+ | M* )%Y" | ||
+ | M24_0IY$5PSN^A' | ||
+ | MUE5? | ||
+ | M"" | ||
+ | MN*8H)+B8RH& | ||
+ | MT1=F==B%JFE? | ||
+ | MN]5, | ||
+ | M[O_" | ||
+ | MDJ& | ||
+ | M!Z/ | ||
+ | M)RC:.G 81@P1>& | ||
+ | M"]_T #> | ||
+ | M GR$: | ||
+ | M^A +HC X1& | ||
+ | M, | ||
+ | M,*@W!A@ R4/ | ||
+ | MV2_(.I); | ||
+ | MQ6VVTIFO=AH$DS: | ||
+ | M]WP=9RBDDFM> | ||
+ | M, | ||
+ | M& | ||
+ | M& | ||
+ | M> | ||
+ | MN-B2 X& | ||
+ | M" | ||
+ | MG!^PG+%9DQ34O_4E9%RT K& | ||
+ | M-[X; | ||
+ | MMX8P;; | ||
+ | M$Q(# | ||
+ | M/ | ||
+ | M?& | ||
+ | M;& | ||
+ | M; | ||
+ | M[!M; | ||
+ | M;, | ||
+ | M1< | ||
+ | M^KK$>' | ||
+ | M,DH48? : | ||
+ | MN[-32@ [PD35A@W' | ||
+ | M(E1LRT? | ||
+ | M*!12NE!& | ||
+ | MAX527$46A? | ||
+ | M; | ||
+ | MH2WIRV*X3]6)RW_<" | ||
+ | M7& | ||
+ | M%3W4' | ||
+ | M.B, | ||
+ | M2B!6& | ||
+ | MHC98![]J: | ||
+ | ML: | ||
+ | M' | ||
+ | M: | ||
+ | MW0R=$=HB2V1BY2, | ||
+ | M,& | ||
+ | MKRZW3> | ||
+ | M/ | ||
+ | M# | ||
+ | MEF2\J_]? | ||
+ | M@-XK< | ||
+ | M_R# | ||
+ | MJ]CLQ' | ||
+ | M-' | ||
+ | M' | ||
+ | M5=102& | ||
+ | M" | ||
+ | M2L@%* F5W, | ||
+ | MZ )BB' | ||
+ | M& | ||
+ | M$(RLKU%@\W)!=D)]' | ||
+ | M& | ||
+ | M? | ||
+ | M=+C# !QB5XD(B4!5X8ICF95%=I*0, | ||
+ | M=%7@+TI)-IH$$T;# | ||
+ | M6ED#AS)LI$ I2F*& | ||
+ | M1D=AGY!M(LHLO0C1I W> | ||
+ | M9& | ||
+ | M" | ||
+ | MDH& | ||
+ | MC89$VL' | ||
+ | M U<; | ||
+ | M-LY75X2WV' | ||
+ | MA' | ||
+ | M+!# | ||
+ | MU%]WU 2#": | ||
+ | MQ7AV\]JL5)N10> | ||
+ | MS[TH$_0H5O9& | ||
+ | M/ (8E> | ||
+ | MC0LKO$E=$; | ||
+ | M05K?> | ||
+ | M)MI 4QQ7QY2AK* VZ]!+0)2PP0M2I%J*^Q[06 > | ||
+ | M&; | ||
+ | M% .8%'; | ||
+ | MG\V@Z4U> | ||
+ | M9S^; | ||
+ | M$7 8 [: | ||
+ | M< | ||
+ | M);T/\2Y$ 7.& 0QCUD^NA!F8H& | ||
+ | M5[WR25^/# | ||
+ | M, | ||
+ | M9LHT0 < | ||
+ | M6BM@CTJ!6Y^C KJFEUM19)4.]8661' | ||
+ | ME4? | ||
+ | MNW_TZ@4;;& | ||
+ | MR0 Q\+KHFM# | ||
+ | M2, | ||
+ | MR&; | ||
+ | M# | ||
+ | M@: | ||
+ | M8EG)S>" | ||
+ | M#: | ||
+ | MH; | ||
+ | MXUX9/ | ||
+ | M/ | ||
+ | M-P, | ||
+ | M@=/## | ||
+ | M< | ||
+ | M & | ||
+ | MM" | ||
+ | ML2LX1F8F-R44002#, | ||
+ | MY1JW87-B0" | ||
+ | M$2MA)& | ||
+ | M< | ||
+ | M*((TLU1_$QAHLPA%_!=W+HA6@Z"' | ||
+ | M? | ||
+ | MRX=ZVU5BZ; | ||
+ | M> | ||
+ | MVK0 RF8M1; | ||
+ | M# | ||
+ | M& | ||
+ | M(V' | ||
+ | M3@RQ#; | ||
+ | M5O%(9@$HE*& | ||
+ | MEE60$XSSF(G_ET-_*' | ||
+ | M5HC^X Z\TBWN.3B# | ||
+ | M"' | ||
+ | M!9HQD9PCYI/ | ||
+ | M(& | ||
+ | MAB(WDZ3X(C.J$H)+L(!CU 8"& | ||
+ | MA? | ||
+ | MG!EC7# | ||
+ | M]+)K%; | ||
+ | M.7: | ||
+ | MW3!P.(FA: | ||
+ | MC'E, 22N? | ||
+ | MAN? | ||
+ | M' | ||
+ | M' | ||
+ | M#/ | ||
+ | MJ0" | ||
+ | M=XQJLH' | ||
+ | MPVBEXY%ED[GK*[68Z[Y: | ||
+ | MQK> | ||
+ | M6$2Q1Q\2DW54, | ||
+ | M41]_ PI+)WB<<" | ||
+ | M1V-2*8-F=WG V0\R& | ||
+ | M.< | ||
+ | MYUBSG!.U_:> | ||
+ | M , | ||
+ | MN5I3EGN-2' | ||
+ | MMT)0^: | ||
+ | M$8W2V# | ||
+ | M3>' | ||
+ | M@_LE@[A :NX9K_ )U(-' | ||
+ | M=C ';> 4UFXX:& | ||
+ | M2KK+V*K5V/ | ||
+ | M<: | ||
+ | M' | ||
+ | M:M^@ (0*--WM: | ||
+ | M& | ||
+ | MC 129QE(; | ||
+ | M:> | ||
+ | MT<W_U(? UN1E_KA0? | ||
+ | M_\RMHNB-76Z? | ||
+ | MVEDQKH+))G@' | ||
+ | M+LP!, | ||
+ | MT_[HILS/:?' | ||
+ | M+$H!^%ER[MTA" | ||
+ | M\QD> | ||
+ | MDPR$YS+/ | ||
+ | MK> | ||
+ | MX C& | ||
+ | M, | ||
+ | M-RJ9" | ||
+ | M_) K_)^<"> | ||
+ | M$UB8WU91EM<' | ||
+ | M[%3-1^43AYTVFG 887R*EE]RB9, | ||
+ | MHEFI: | ||
+ | M: | ||
+ | MQ\B=R: | ||
+ | M9' | ||
+ | ML6, | ||
+ | M, | ||
+ | M=)-7FA)< | ||
+ | MA1$Y-X*V2CY<: | ||
+ | M-> | ||
+ | MMYFL+-> | ||
+ | M1Q+W& | ||
+ | M7H*7P%C" | ||
+ | MVX6!6-!.: | ||
+ | M%Q; | ||
+ | M74^I&0 FHHZ& : | ||
+ | MU&< | ||
+ | MYX& | ||
+ | M %& | ||
+ | M$<&/# | ||
+ | MG" | ||
+ | M& | ||
+ | M; | ||
+ | MP, | ||
+ | MNLR[, | ||
+ | M*< | ||
+ | MSDWN]YW' | ||
+ | MR/ | ||
+ | MJP# | ||
+ | M=""?' | ||
+ | ME1Q' | ||
+ | M#5DXIQ=]^] "< | ||
+ | MO_5Q&" | ||
+ | MU[7F+S>, | ||
+ | MDN: | ||
+ | MP& | ||
+ | M!5, | ||
+ | M" | ||
+ | MA< | ||
+ | M7 V& | ||
+ | M4B4UN\O4CNQFA1V( # | ||
+ | MS=R9*KDLU9/ | ||
+ | M2 RXRCWQNS=5, | ||
+ | M>,FTQ2# HB*7? | ||
+ | M!K_1T@H8KTL/ | ||
+ | M# | ||
+ | MF? | ||
+ | M%$IV.XP, | ||
+ | M,R+E,B K>C FO? | ||
+ | M9XU& | ||
+ | M1]@KW0%C8" | ||
+ | MF+0& | ||
+ | M=4W& | ||
+ | MN[@W++: | ||
+ | MG#> | ||
+ | MJ5P9E5A? | ||
+ | M,Q[IO9 : | ||
+ | M0]8I/ | ||
+ | M._!; | ||
+ | M+Z@? | ||
+ | MMO86/ | ||
+ | M1E1G" | ||
+ | M@N+E0DGV"#?'< | ||
+ | M(/V0 1.(4\*89QI@$0U59]3 -\SX)B>" | ||
+ | MA9& | ||
+ | M$]*58W# | ||
+ | M080G& | ||
+ | MUC;Z> LU@HRXLEJMEE088TXM*$DRR5: | ||
+ | MI&R9 $ZQYY3@(Y$[N8Y6R# | ||
+ | M!G" | ||
+ | M.9' | ||
+ | MAAJ[A%BFUW5^)RT: | ||
+ | M]WY6R' | ||
+ | M[+",# | ||
+ | MJG5[XVF> | ||
+ | MZ6FA*YJ=Q; | ||
+ | M4#" %S.2NUA0_Q1M< | ||
+ | MHH %*@, | ||
+ | M? | ||
+ | MJC< | ||
+ | M< | ||
+ | M%U& | ||
+ | MK@' | ||
+ | M& | ||
+ | M=F7O^I\K6Z$M: | ||
+ | MT; | ||
+ | M& | ||
+ | M$' | ||
+ | M4; | ||
+ | M(8T) ; | ||
+ | M]M: | ||
+ | M9[!W" | ||
+ | M: | ||
+ | MCVO$, | ||
+ | M5J)H5IS%*XS& | ||
+ | M5' | ||
+ | M!X8# | ||
+ | MREKLRT=2N$*Z> | ||
+ | M' | ||
+ | MR? | ||
+ | M=-'# | ||
+ | M?: | ||
+ | MFU2H& | ||
+ | MF]13)5GVJ5XV7=; | ||
+ | M# | ||
+ | M(\& | ||
+ | M" | ||
+ | MHLUEC^W=[' | ||
+ | M]WUB# | ||
+ | MX.L5': | ||
+ | M.< | ||
+ | MNL8::& | ||
+ | M</ | ||
+ | MON=MEX@M*0S: | ||
+ | MZRS7ZF' | ||
+ | M; | ||
+ | MB!87HM%C XF0=P\0(: | ||
+ | M@.ZG; | ||
+ | M' | ||
+ | M+*;: | ||
+ | MW_; | ||
+ | MXFY.;< | ||
+ | M0G' | ||
+ | M0-!^2*[# | ||
+ | M; | ||
+ | MW/ | ||
+ | M1FE@]I1.I' | ||
+ | MR]]Z@8*# | ||
+ | MJZRMKJ^PL; | ||
+ | MV-G: | ||
+ | M2+" | ||
+ | MR; | ||
+ | M2K6JU: | ||
+ | MR[> | ||
+ | M3; | ||
+ | M7\Z\N?/ | ||
+ | MGT^_OOW[^// | ||
+ | M&&: | ||
+ | M//;HXX] !BGDD$06:> | ||
+ | M8(8IYIADEFGFF6BFJ>::; | ||
+ | MA!9JZ*& | ||
+ | M | ||
+ | 1 | ||
+ | |||
+ | end | ||
+ | |||
+ | |||
+ | \H02:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | The 8 bit Modplay 128 Board, a three-diode addition | ||
+ | |||
+ | by Nate Dannenberg | ||
+ | |||
+ | |||
+ | Hello all, Nate Dannenberg here again, with an update to my original | ||
+ | schematic and assembly instructions for the 8-bit DAC Circuit described | ||
+ | in disC=overy issue #1. | ||
+ | |||
+ | This change will require three 1N914 diodes, and some solder. | ||
+ | |||
+ | What we will be doing here is rigging the DAC Circuit to trigger all four | ||
+ | tracks simultaneously at the moment track 1 has been updated and clocked. | ||
+ | The point of this is that, when used with my MODplayer software, all four | ||
+ | tracks will output their samples at the same time, thus eliminating the | ||
+ | switching and mixing noise that the circuit was producing before. | ||
+ | |||
+ | Those who used this device for only 1 track output will probably not hear | ||
+ | any improvement, | ||
+ | in the " | ||
+ | |||
+ | 1) Remove the jumper or wire that connect pins 7 and 8 of the MAX505 | ||
+ | | ||
+ | We want to take pin 8 completely out of the circuit. | ||
+ | |||
+ | 2) Connect all three diode' | ||
+ | " | ||
+ | the diode' | ||
+ | |||
+ | 3) Connect one each of the diodes' | ||
+ | the MAX505, or to the corresponding pins on the User Port plug. | ||
+ | | ||
+ | That's it! | ||
+ | |||
+ | Have fun and enjoy the wonders of 8 bit four track digital sound! | ||
+ | -- | ||
+ | For questions or general commentary on this article, Mr. Dannenberg may be | ||
+ | reached at the following addresses : | ||
+ | |||
+ | Internet | ||
+ | |||
+ | Snail Mail : Digital Audio Concepts, Ltd | ||
+ | c/o Nate Dannenberg | ||
+ | 9804 Northcliff Drive | ||
+ | | ||
+ | |||
+ | Phone : (214) 319-9879 data, or (214) 320-1386 voice | ||
+ | |||
+ | |||
+ | \H03:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | The Virtual PLUS/4 : | ||
+ | |||
+ | | ||
+ | |||
+ | by Martin Gierich | ||
+ | |||
+ | |||
+ | How many times have you heard from PLUS/4 fans, "Your C16 is only good | ||
+ | as a PLUS/4 parts depot" ? How many times have you been offerred money | ||
+ | for your C16 by these same TED-chip-seeking adepts? | ||
+ | because now your C16 can be turned into the pseudo-equivalent of a PLUS/4 | ||
+ | with the ability to run a great deal of PLUS/4 software. | ||
+ | C16 and PLUS/4 belong to the " | ||
+ | is actually a PLUS/4 with a few chips missing, notably the 6551 UART (and | ||
+ | userport) and a full 64 KB of memory. | ||
+ | a C16 to a full 64 KB, allowing us to run many PLUS/4 or " | ||
+ | software (minus userport-terminal programs and some assorted misc. software). | ||
+ | |||
+ | |||
+ | ::: | ||
+ | |||
+ | | ||
+ | in your C16 manual. You should be familiar with soldering. I have | ||
+ | | ||
+ | I can give you no warranty, do it on your own risk ! Neither myself | ||
+ | nor the staff of the disC=overy journal is responsible for any use or | ||
+ | | ||
+ | |||
+ | |||
+ | 1. Buy two 64x4 bit dynamic RAM chips like "TMS 4464" or " | ||
+ | They should cost less than US$10 together. | ||
+ | |||
+ | 2. Remove your two old "TMS 4416" RAM chips from your C16. They are | ||
+ | | ||
+ | | ||
+ | then I have desoldered the pins. | ||
+ | |||
+ | 3. Now solder two 18 pin sockets in where the old RAM chips have been. | ||
+ | Again do it carefully to avoid destruction ! Check out where pin 1 | ||
+ | is. Then plug in the new RAM chips. | ||
+ | |||
+ | 4. Check everything again, then switch on your C16. It should still | ||
+ | show "12KB free". | ||
+ | |||
+ | 5. Adress lines A0 to A13 are connected to the multiplexers U7 and U8. | ||
+ | You need to connect A14 and A15 to them instead of +5V at U7 pin 2 | ||
+ | and U8 pin 14 (but the +5V connections at pin 16 must be left). | ||
+ | For example get them from the CPU (U2) pin 21 and 22. First scratch | ||
+ | the +5V connections. U8 pin 14 is connceted on the lower side only | ||
+ | and should be easy to disconnect. U7 pin 2 is more tricky. | ||
+ | | ||
+ | side (U7). Pin 16 still gets +5V from the lower side. | ||
+ | |||
+ | Now you have two choices: | ||
+ | |||
+ | 6a. To always have 64KB: | ||
+ | | ||
+ | | ||
+ | |||
+ | 6b. To choose between 16KB and 64KB: | ||
+ | You need a double switch (or whatever this is called, it has 6 | ||
+ | | ||
+ | the switch. Connect U2 pin 21 and 22 to the two upper pins of | ||
+ | the switch. Connect +5V or Ground to the two lower pins of the | ||
+ | | ||
+ | in your 64KB area. I have used FB13 to get Ground and FB14 to get | ||
+ | +5V. | ||
+ | |||
+ | Keep the connections short ! | ||
+ | |||
+ | 7. Check everything again carefully. Then switch on your C16. It | ||
+ | | ||
+ | |||
+ | 8. It is a good idea to replace the 7805 (labelled VR1) with a 78S05, | ||
+ | | ||
+ | |||
+ | Simple, eh? Enjoy ! | ||
+ | -- | ||
+ | For questions or general commentary on this article, Mr. Martin Gierich may | ||
+ | be reached at the following internet address : uj3w@rz.uni-karlsruhe.de | ||
+ | |||
+ | |||
+ | \H04:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | Continued Lt. Kernal Hard Drive Support | ||
+ | |||
+ | by Ron Fick | ||
+ | |||
+ | | ||
+ | |||
+ | |||
+ | Probably my best enjoyment from this great Commodore 8 bit hobby is helping | ||
+ | fellow Commodore enthusiasts with my expertise on electronic hardware. | ||
+ | have been an electronics design engineer for 20 some years and I have an | ||
+ | in all types of computers, but the Commodore has always been my favorite | ||
+ | for hardware/ | ||
+ | |||
+ | One peripheral lacking on Commodore 64's and 128's was a means of mass data | ||
+ | storage. | ||
+ | where constant 24 hour use is required. | ||
+ | drives for the Pet series but none were produced by Commodore for the newer | ||
+ | 64's and 128' | ||
+ | |||
+ | Several designs came to market, but the one that has stayed popular the | ||
+ | longest is the Lt. Kernal hard drive system. | ||
+ | Southwick and Lloyd Sponenburg came up with the initial design of the Lt. | ||
+ | Kernal. | ||
+ | and quickly found that system drudgingly slow for loading and saving programs. | ||
+ | Even when he upgraded the system to use a 1541 disk drive, Roy was frustrated. | ||
+ | You all know how you can take a coffee break while some programs load off a 41, | ||
+ | which didn't impress Roy since he and Lloyd were software/ | ||
+ | on mini-computers at the time. | ||
+ | |||
+ | The Lt. Kernal hard drive system for the C64 was introduced in December of | ||
+ | 1984 and it's main customers were businesses who found it could fill their | ||
+ | needs and was a lot less expensive than PC's at the time. Since the Lt. Kernal | ||
+ | is designed to use the ' | ||
+ | bus that disk drives use, it's transfer rate is many times faster than ' | ||
+ | 15xx serial transfer rates. | ||
+ | run up to 65 kilobytes per second. | ||
+ | Xetec Inc., provided additional design and manufacturing of the Lt. Kernals | ||
+ | and they became quite popular to hobbyists running Commodore BBS' | ||
+ | an expensive accessory, retailing -then- at over $1000, but then hobbies are | ||
+ | supposed to cost money, aren't they? | ||
+ | |||
+ | Here are some of the facinating features designed into the Lt. Kernal and | ||
+ | its operating system: | ||
+ | |||
+ | - Fourty-two additional or enhanced system commands | ||
+ | - Automatic power-up execution of any application program | ||
+ | - Up to 7 files can be open for reading or writing simultaneously | ||
+ | in addition to the command/ | ||
+ | - Built in backup & restore software using a speedy " | ||
+ | - User can set screen & character colors | ||
+ | - CP/M software can be run on the Lt. Kernal system | ||
+ | - DOS allows Key File indexing which is not available on disk drives | ||
+ | - Can run copy protected software with limitations | ||
+ | - Ability to use up to 15 C64's or C128's on one drive simultaneously | ||
+ | using the multiplexer which I also build and support | ||
+ | |||
+ | |||
+ | Xetec performed a great service to Commodore users by manufacturing several | ||
+ | peripherals for Commodores: printer adapters and software, plus accessories | ||
+ | for Atari' | ||
+ | such accessories dwindled and Xetec decided to close it's doors in April of | ||
+ | 1995. They had an auction in May of that year to sell off their inventory. | ||
+ | Prior to the auction, Xetec contacted me since they knew I was in the habit | ||
+ | of assisting Commodore users with my electronics expertise. | ||
+ | genuinely cared for the future of the Lt. Kernal users, and since these | ||
+ | precious unique parts would be forever lost to Commodore users by ending up | ||
+ | at surplus electronics dealers, I was contacted. | ||
+ | account to rescue these parts and even though it was tempting to set up a | ||
+ | business of Lt. Kernal repair, I knew if Xetec wasn't making a profit at it, | ||
+ | I wouldn' | ||
+ | |||
+ | My work with Lt. Kernals is strictly a hobby. | ||
+ | and eventually will be pleased if I break even on my investment in parts. | ||
+ | Besides, if I made a business out of my hobby, I might just suffer burnout | ||
+ | and that could ruin my hobby. | ||
+ | |||
+ | Word of mouth has provided me with all the work I can handle with the Lt. | ||
+ | Kernal. | ||
+ | popular hard drives and I hope I'll have enough spare parts to continue to | ||
+ | support them for a long time to come. | ||
+ | |||
+ | Currently, the CMD hard drive system is the only commercially-available | ||
+ | Commodore hard drive system and it is better suited for the regular Commodore | ||
+ | hobbyist since it is more software compatible to your normal Commodore programs | ||
+ | than the Lt. Kernal system. | ||
+ | Lt. Kernal since most Commodore bbs software was written originally with a Lt. | ||
+ | Kernal in mind. The Lt. Kernal Multiplexer allows, for example, the Commodore | ||
+ | sysop to do true multi-tasking with his bbs without tying up the bbs for Sysop | ||
+ | functions and even provides the capability of running a multi-line bbs on the | ||
+ | Commodore. | ||
+ | around 50 Commodore boards in the USA and Canada all networked and a good deal | ||
+ | of those bbs's are running on Lt. Kernal hard drives. | ||
+ | Image bbs networks are all linked via this network and other Commodore networks | ||
+ | are encouraged to contact me to join this Commodore information highway. | ||
+ | bbs, the Batcave (303)/ | ||
+ | around 700 active members with around 100 of those Commodore 8 bit users and | ||
+ | guess what, it runs on a 105 meg Lt. Kernal! | ||
+ | cheaper every day at garage sales and support available for these great | ||
+ | Commodore hard drive systems, there' | ||
+ | |||
+ | -- | ||
+ | To our knowledge, Mr. Ron Fick is the best and last source of Xetec | ||
+ | hardware in 1996. If you have any problems with your Xetec peripherals | ||
+ | or would like more information on Ron's stock of Xetec products, do not | ||
+ | hesistate to email him at rfick@nyx.net | ||
+ | |||
+ | |||
+ | \H05:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | >> The Metal Shop --- | ||
+ | *+* | ||
+ | --- with SMS Mike Eglestone << | ||
+ | |||
+ | SysOp of Diamondback BBS (305)258-5039 | ||
+ | |||
+ | |||
+ | Senior Master Sergeant Mike Eglestone has been a devout C= Hardware guru | ||
+ | for over ten years and is the Sysop of one of the most active BBS's on the | ||
+ | Commnet Commodore BBS network. | ||
+ | magazines in the Commodore 8 bit community such as dieHard magazine and the | ||
+ | very distinguished Commodore World. | ||
+ | our questions pertaining to hardware concerns. | ||
+ | staff of disC=overy, and Mr. Eglestone are NOT responsible for the use or | ||
+ | misuse of any information presented in this article. | ||
+ | -- | ||
+ | |||
+ | >> Dear SMS Mike, | ||
+ | >> | ||
+ | >> How do you run two or more SFD 1001 drives together with only one | ||
+ | >> interface? I have a Quicksilver 128 hooked up to my SFD and was wondering | ||
+ | >> how an additional SFD could be added to it? Could you tell me please? | ||
+ | >> | ||
+ | >> GF | ||
+ | |||
+ | GF, | ||
+ | |||
+ | | ||
+ | works just exactly like the serial bus on the standard C= drives, but you do | ||
+ | the hookup externally (cable to cable). | ||
+ | |||
+ | My cables had MALE and FEMALE plugs on the same end. The cables hooked into | ||
+ | each other at each plug point. Male into drive, Female side out. Next Male | ||
+ | pluged into the back of that same connection, and so on down the line. | ||
+ | |||
+ | If you don't have the cables with the double connectors, you can't make the | ||
+ | parallel hookup. I don't know where you might find those cables anymore. | ||
+ | They used to be quite common. I gave all my old ones away about six years ago. | ||
+ | |||
+ | You might want to check with Bo Fain at Centsible Systems. He runs across a | ||
+ | wide variety of things that are not currently available. | ||
+ | |||
+ | (318) 687-4613 | ||
+ | -- | ||
+ | |||
+ | >> Dear SMS Mike, | ||
+ | >> | ||
+ | >> When I try to load up a program with my 1581, the power light starts to | ||
+ | >> flicker and then refuses to load. What is going on here? | ||
+ | >> | ||
+ | >> RB | ||
+ | |||
+ | RB, | ||
+ | |||
+ | If the power light only flickers when the drive is turning a disk, it sounds | ||
+ | more like a loose connection inside the drive. | ||
+ | |||
+ | Just open up the case and check the drive power plug inside. | ||
+ | |||
+ | | ||
+ | drive. None!! 5 Volts and 12 Volts... That's IT! | ||
+ | |||
+ | The drive unit can be seperated from the mother board in a few minutes, but | ||
+ | you don't even have to do that. Just check the drive plug. Run the drive with | ||
+ | the top off and LOOK... It ain't no big deal and you can't hurt anything. | ||
+ | |||
+ | Just pull the two screws (on the bottom) that hold the top on, Turn it over | ||
+ | and lift the back of the top, take the top off... The front may fall off too, | ||
+ | but it just sits there on a couple of pins... | ||
+ | |||
+ | | ||
+ | again.. Hell guy, I run two of them with NO tops at all.. Gotta smack one of | ||
+ | them now and then (to get it turning); it's easy with the top off. | ||
+ | The plugs to the drive are obvious. Pull them out and put them back a few | ||
+ | times.. Might just be dirty connections. | ||
+ | |||
+ | | ||
+ | to me once, the power light can flicker after a file misread. | ||
+ | there would be to clean it lightly with drive-head cleaning solution available | ||
+ | from drive cleaning kits at Radio Shack and most any computer store vendor. | ||
+ | -- | ||
+ | |||
+ | >> Dear SMS Mike, | ||
+ | >> | ||
+ | >> I have an odd 1581 problem. | ||
+ | >> and sometimes falsely indicates that the write protect is set. There is a | ||
+ | >> small microswitch that detects the write enable and presumable the disk | ||
+ | >> swap as well. What does your SAMS guide say about this? It could also | ||
+ | >> be the circuitry. | ||
+ | >> generally available? | ||
+ | >> | ||
+ | >> RM | ||
+ | |||
+ | RM, | ||
+ | |||
+ | The write protect sensor is nothing more than a optical cell. It is either | ||
+ | logic high or logic low depending upon the position of the write protect tab | ||
+ | on the disk. No moving parts involved. | ||
+ | |||
+ | I would venture a guess that you have one of the old Newtronics drives in | ||
+ | that unit. If this is the case, the only way I have ever been able to recover | ||
+ | from a failed reset is to re-initialize the drive then try to re-inseart the | ||
+ | disk again. You should HEAR that dust shield slide back out of the way of the | ||
+ | read write heads, folowed by a very short motor run. That is the indexing | ||
+ | cycle for track 40. Unlike the other drives, the 1581 uses a fixed track index | ||
+ | sensor to pre-position the stepper motor. It's a real tricky system to align | ||
+ | properly. | ||
+ | |||
+ | SAMS doesn' | ||
+ | sensors on those old Newtronics Drives. There are three of them involved in | ||
+ | the alignment process, and they have to be darn near perfect. | ||
+ | |||
+ | Not much help here, guy... Those drives are no longer manufactured! | ||
+ | |||
+ | Note : On one of my old drives, I just removed the write protect sensor and | ||
+ | soldered the connections closed. It worked again, but you could not write | ||
+ | protect a disk anymore. Sometimes you have to fudge a bit! | ||
+ | -- | ||
+ | |||
+ | >> Dear SMS Mike, | ||
+ | |||
+ | >> I have this old Apple IIE that I would like to infuse with software. I don't | ||
+ | >> have any telecommunications software or hardware for it though. I do have | ||
+ | >> a C-64 with modem and 1541 drive. I hear the disk ][ Apple drives and the | ||
+ | >> 1541 use a similar encoding scheme based on GCR. Is there a way to r/w apple | ||
+ | >> disks on my C-64 & 1541, especially without hardware modification. | ||
+ | >> | ||
+ | >> OP | ||
+ | |||
+ | OP, | ||
+ | |||
+ | | ||
+ | " | ||
+ | there was once an Apple II+ hardware emulator for the C-64. This ' | ||
+ | unit from Mimic systems allowed R/W to Apple disks, but I am not sure what | ||
+ | role (if any!) a 1541 played in this arrangement. | ||
+ | so many C= software/ | ||
+ | that a disk ][ interface was available for the C-64. I do not know if such | ||
+ | an interface exists, but the possibility might be worth investigating. | ||
+ | |||
+ | As far as the actual drives are concerned, the following are known as | ||
+ | problems to be overcome for a potential 1541 - disk ][ reader : | ||
+ | |||
+ | - The 1541 rotates its disks at fixed speeds while its R/W head frequency | ||
+ | can be changed in four steps (in series from 250 khz to 300 khz I believe). | ||
+ | The disk ][ apparently has a variable speed drive engine which allows its | ||
+ | R/W head to sit at one fixed frequency as it read/ | ||
+ | here we have two opposite paradigms regarding what remains fixed and what | ||
+ | becomes variable to get the end-result of being able to read/write " | ||
+ | Therefore, a 1541 could possibly read/write to a disk ][ format only in | ||
+ | those areas on the disk where the paradigms cancel each other (i.e., where | ||
+ | 1541 Static Rotation + Variable Freq. = disk ][ Variable Rotation + Static | ||
+ | Freq.) | ||
+ | so it cannot match most of the changes incurred by the disk ]['s variable | ||
+ | rotation. | ||
+ | to be done. | ||
+ | |||
+ | - But that's not all, GCR does not equal GCR! The disk ][ and 1541 use a | ||
+ | different GCR scheme! | ||
+ | an 8-9 encoding. | ||
+ | system on the C-64 end to match AppleDOS 3.xx whatever.. Not trivial. | ||
+ | |||
+ | - As an aside, some Apple sources I checked with tend to suggest that the | ||
+ | disk ][ runs at a constant 300 RPM. This is contrary to my admittedly | ||
+ | limited experience with Apple hardware, but I was given a canonical | ||
+ | source to reference this point : BENEATH APPLE DOS. It is supposed to | ||
+ | be much like the classic tome : INSIDE COMMODORE DOS. If you or anyone | ||
+ | else wishes to attempt a 1541 to disk ][ project, these are the books to | ||
+ | get acquainted with. | ||
+ | -- | ||
+ | |||
+ | >> Dear SMS Mike, | ||
+ | |||
+ | >> I hear the SuperCPU 128 will require a daughterboard or clip lead to the | ||
+ | >> VDC chip. I don't want to open up my 128. What gives? | ||
+ | >> | ||
+ | >> PO | ||
+ | |||
+ | PO, | ||
+ | |||
+ | I have not heard this, but it would not surprise me. The VDC is poorly | ||
+ | interfaced to main memory (for that matter, so is the Z80) in the C128. I | ||
+ | can imagine a few reasons why additional hardware links above and beyond | ||
+ | a simple cartridge plug-in might be required for Super-CPU & VDC operation. | ||
+ | I won't speculate much on this forum, since I lack proper information about | ||
+ | the SuperCPU paradigm and the VDC itself. | ||
+ | it, I am reminded of the VDC article in disC=overy issue 1 (by S. Judd). | ||
+ | believe the goal of that article was to explore enough of the VDC internals | ||
+ | to "time it out" so to speak. | ||
+ | I have never run into a way to make a stable raster on the VDC. Hmmmm, | ||
+ | according to my sources, the VDC runs at the 16 Mhz dotclock while everything | ||
+ | else on an NTSC C128 system runs at 14, | ||
+ | a stable raster 100% under those conditions. | ||
+ | question, we will have to wait until CMD releases the SuperCPU 128 to find | ||
+ | out -what- is required and -why-. | ||
+ | -- | ||
+ | |||
+ | >> Dear SMS Mike, | ||
+ | >> | ||
+ | >> I bought a VIC-20 at a thrift shop the other day, and OF COURSE, the | ||
+ | >> thing came with no cabling or power supply. | ||
+ | >> power supply and had no problems using standard C= serial cables for | ||
+ | >> drive access, but how do I get a video/audio signal out of it? By | ||
+ | >> the way, the VIC-20 will let me blindly type in and load a directory, | ||
+ | >> so I'm pretty certain it works. | ||
+ | >> | ||
+ | >> FN | ||
+ | |||
+ | FN, | ||
+ | |||
+ | Here is the quickest way I know how to do it. Just remember that this is a | ||
+ | VIC-20 Video Cable for use with TV/ | ||
+ | Now looking at the back of your VIC-20, you will see the 5 DIN video port, | ||
+ | as follows : | ||
+ | |||
+ | / | ||
+ | / \ | ||
+ | / | ||
+ | [ . | ||
+ | [ 5 | ||
+ | | ||
+ | \ . / | ||
+ | | ||
+ | |||
+ | Pin 1 : +5-6 V, max. 10 mAmps (WARNING : DO NOT CONNECT THIS PIN!) | ||
+ | Pin 2 : Ground | ||
+ | Pin 3 : Audio Out | ||
+ | Pin 4 : Video Low (Not Connected) | ||
+ | Pin 5 : Video High | ||
+ | |||
+ | You'll need a 5-pin DIN connector, two RCA jacks, and RCA cables. | ||
+ | |||
+ | Take the 5-pin DIN connector (remember that the pins will be reversed when | ||
+ | you view the connector as it faces you) and connect the pins, as follows : | ||
+ | |||
+ | Pin 3 (Audio Out) to an RCA jack (label this one as audio out) | ||
+ | Pin 5 (Video Out) to a second RCA jack (label this one as video out) | ||
+ | Pin 2 (Ground) to the ground sleeve/ | ||
+ | |||
+ | Using RCA-style cables, connect these Audio/ | ||
+ | Audio/ | ||
+ | watch your VIC-20 in action. | ||
+ | see the powerup message with a CYAN border and WHITE background (NTSC VIC-20). | ||
+ | |||
+ | Your video difficulties should now be resolved. | ||
+ | in securing power supplies for the VIC-20. | ||
+ | the traditional steps for those who might not be familiar with the VIC-20 is | ||
+ | to swing your VIC to where the On/Off switch is located and find the power | ||
+ | socket next to the switch. | ||
+ | port (for cartridges) at the back of your computer). | ||
+ | |||
+ | If you look at your VIC-20' | ||
+ | than 2 prongs :) on it, you are in luck. A regular C-64 power supply will | ||
+ | work with your VIC-20. | ||
+ | |||
+ | If you look at your VIC-20' | ||
+ | out, then you have to search! | ||
+ | delivered at 1 Ampere (with two prongs, each delivering power at 180 degrees | ||
+ | out of phase from each other) and hook it up. If this is not an option, you | ||
+ | can pull 9 Volts AC @ 1 Ampere from a C64 or flat C128 power supply. | ||
+ | example, the online 'zine C= Hacking, issue #6, has an article on building | ||
+ | power supplies for the C-64. With two extra wires drawing off the 9 VAC and | ||
+ | the proper two-prong nylon connector, the power supply described in the article | ||
+ | becomes an excellent " | ||
+ | |||
+ | |||
+ | ::::::::::: | ||
+ | $2bad::::::::::::::::::::::::::: | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | |||
+ | |||
+ | E R R A T A : | ||
+ | |||
+ | |||
+ | >> /S04 - "A complete dissection of Gfx-Zone" | ||
+ | >> $d000 by " | ||
+ | |||
+ | - In TBB's music player code, "lda #$7f : sta $dc0d" was described as | ||
+ | | ||
+ | |||
+ | - The author may be reached through the Editor-in-Chief of disC=overy. | ||
+ | |||
+ | |||
+ | >> /S05 - "A Beginner' | ||
+ | >> $d400 by Sean M. Pappalardo | ||
+ | |||
+ | - SHIFT and SPACE inserts the sustain marker in the blocks (+++), | ||
+ | * NOT * SHIFT and RETURN, as written in the article. | ||
+ | |||
+ | - The author' | ||
+ | | ||
+ | |||
+ | |||
+ | >> /S07 - "Some preliminary data on VDC timing" | ||
+ | >> $d600 by Stephen L. Judd | ||
+ | |||
+ | - At least one of the uuencoded files included in the original article | ||
+ | was found to be corrupt. | ||
+ | Note: ' | ||
+ | the ML object file (vdc-explorer.o). | ||
+ | |||
+ | begin 644 vdc-explorer | ||
+ | M 1PP' H F2 BFY/ | ||
+ | M"R Q $P<% " | ||
+ | M4$Q/ | ||
+ | M-3, | ||
+ | M+%0S*$XI+%0T*$XI+%0U*$XI+%0V*$XI+%0W*$XI+%0Y*$XI+$%6*# | ||
+ | M4 " | ||
+ | MJC(Z4C.R05*J, | ||
+ | M6@!!5B@Q*; | ||
+ | M-BFR,# | ||
+ | M,S P(BD PQUX %0Q*$DILL(H5$535" | ||
+ | M1@# | ||
+ | M22D 'QZ, %0S*$DILL(H4C(IJC(U-JS" | ||
+ | M*$DI $T> | ||
+ | M-" | ||
+ | M5#4H22D J1ZJ %0V*$DILL(H4C0IJC(U-JS" | ||
+ | MJE0V*$DI -<>M !4-RA)*; | ||
+ | M*: | ||
+ | M1D8IJC(U-JS" | ||
+ | M$2)).U0Q*$DI.U0R*$DI.U0S*$DI.U0T*$DI.U0U*$DI.U0V*$DI.U0W*$DI | ||
+ | M.U0Y*$DI &,?W "" | ||
+ | M040](D]((B!%6%!%0U1%1# | ||
+ | MHZ, | ||
+ | M25-415(](E-2 | ||
+ | M,# *2 B 9DBGU1%4U0S.A& | ||
+ | M4D5' | ||
+ | M140@5D%, | ||
+ | MHZ.CHZ, | ||
+ | M(E12 / | ||
+ | M040](E13 !0A: & | ||
+ | MF2PB/ | ||
+ | M? &-(#8P, !I(88!F2(> | ||
+ | M3R!214%$4R!214< | ||
+ | M140@5D%, | ||
+ | MG9V=G: | ||
+ | M3U)9($9%5$-(/ | ||
+ | M3%5%/ | ||
+ | M(%=!250@1D]2(%9$0R!& | ||
+ | MG"+@ 9DBGE1%4U0W.A& | ||
+ | M, | ||
+ | M-3: | ||
+ | M4D%' | ||
+ | M(#8P, !5(Q(" | ||
+ | M*: | ||
+ | M05!04D]8(%9$0R!424U%($9/ | ||
+ | M-*U& | ||
+ | MLC$Z1K(R.HDX, | ||
+ | ,-C P !\D8@*. | ||
+ | |||
+ | end | ||
+ | |||
+ | begin 644 vdc-explorer.o | ||
+ | M !-XJ? | ||
+ | M+ # | ||
+ | M^XP.W3BI_^T$W8W, | ||
+ | M[03=C< | ||
+ | MW3BI_^T$W8W0%*G_[07=C=$4J1F-# | ||
+ | MUJ(?C@#6+ # | ||
+ | MUHX UBP UA# | ||
+ | M[03=C=04J? | ||
+ | MC0[=J6:. -8L -80^XT!UJ(8J0" | ||
+ | MUJ(2C@#6+ #6$/N,#MVM =: | ||
+ | : | ||
+ | |||
+ | end | ||
+ | |||
+ | |||
+ | >> /S09 - " | ||
+ | | ||
+ | by Pontus Berg | ||
+ | |||
+ | - The author' | ||
+ | for questions and comments at : PBG@hk.mobitel.telia.se | ||
+ | |||
+ | -- | ||
+ | Note : None of these errors are present in the ' | ||
+ | | ||
+ | |||
+ | |||
+ | ::::::::::: | ||
+ | \END:::::::::::::::::::::::::: | ||
+ | :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: | ||
+ | </ |
magazines/discovery2.txt · Last modified: 2015-04-17 04:35 by 127.0.0.1