| Volume 1 -- Issue 5 | February 1981 |
The number of subscribers keeps climbing! From 0 in September, to 45 in October, 85 in November, 179 on Christmas Eve, and now 242 in late January!
In This Issue...
Bug Reports
1. Several readers have reported a problem with the COPY program in the December issue. As written, if you try to copy a block of lines to a point before the first line of the program, the block is inserted between the first and second bytes of the first line. Ouch! To fix it, insert lines 2221-2225 and change line 2250:
2221 LDA A2L
2222 CMP A1L
2223 LDA A2H
2224 SBC A1H
2225 BCC .5
2250 .5 LDA SS MOVE IN SOURCE BLOCK
|
2. When I typed up Lee Meador's article for the January issue, I inadvertently changed one address to a crazy value. The address $2746 in the 4th paragraph on page 9 should be $1246.
3. The Variable Cross Reference program for Applesoft from the November issue leaves something behind after it has run. If you LIST the Applesoft program after running VCR, the line number of the first line will come out garbage. This only happens the first time you use the LIST command. For some reason, typing CALL 1002 before the LIST will fix it. I haven't found out the cause or cure yet. If you find it first, let me know!
| Making Noise and Other Sounds | Bob Sander-Cederlof |
The Apple's built-in speaker is one of its most delightful features. To be sure, it is very limited; but I have used it for everything from sound effects in games to music in six parts (weird-sounding guitar chords) and even speech. Too many ways to put all in one AAL article! I will describe some of the sound effects I have used, and maybe you can go on from there.
The speaker hardware is very simple. A flip-flop controls the current through the speaker coil. Everytime you address $C030, the flip-flop changes state. This in turn reverses the current through the speaker coil. If the speaker cone was pulled in, it pops out; if it was out, it pulls in. If we "toggle" the state at just the right rate, we can make a square-wave sound. By changing the time between reversals dynamically, we can make very complex sounds. We have no control over the amplitude of the speaker motions, only the frequency.
Simple Tone: This program generates a tone burst of 128 cycles (or 256 half-cycles, or 256 pulses), with each half-cycle being 1288 Apple clocks. Just to make it easy, let's call Apple's clock 1MHz. It is really a little faster, but that will be close enough. So the tone will be about 388 Hertz (cycles per second, if you are as old as me!).
How did I figure out those numbers? To get the time for a half-cycle (which I am going to start calling a pulse), I added up the Apple 6502 cycles for each instruction in the loop. LDA SPEAKER takes 4 cycles. DEX is 2 cycles, and BNE is 3 cycles when it branches. The DEX-BNE pair will be executed 256 times for each pulse, but the last time BNE does not branch; BNE only takes 2 cycles when it does not branch. The DEY-BNE pair will branch during each pulse, so we use 5 cycles there. So the total is 4+256*5-1+5=1288 cycles. I got the frequency by the formula f=1/T; T is the time for a whole cycle, or 2576 microseconds.
1000 *---------------------------------
1010 * SIMPLE TONE
1020 *---------------------------------
1030 SPEAKER .EQ $C030
1040 *---------------------------------
1050 TONE LDY #0 START CYCLE COUNTER
1060 LDX #0 START DELAY COUNTER
1070 .1 LDA SPEAKER TOGGLE SPEAKER
1080 .2 DEX DELAY LOOP
1090 BNE .2
1100 DEY QUIT AFTER 128 CYCLES
1110 BNE .1
1120 RTS
|
Apple "Bell" Subroutine: Inside your monitor ROM there is a subroutine at $FBE2 which uses the speaker to make a bell-like sound. Here is a copy of that code. Notice that the pulse width is controlled by calling another monitor subroutine, WAIT.
1000 *---------------------------------
1010 * APPLE "BELL" ROUTINE
1020 *---------------------------------
1030 .OR $FBE2 IN MONITOR ROM
1040 .TA $800
1050 *---------------------------------
1060 WAIT .EQ $FCA8 MONITOR DELAY ROUTINE
1070 SPEAKER .EQ $C030
1080 *---------------------------------
1090 M.FBE2 LDY #192 # OF HALF-CYCLES
1100 BELL2 LDA #12 SET UP DELAY OF 500 MICROSECONDS
1110 JSR WAIT FOR A HALF CYCLE OF 1000 HERTZ
1120 LDA SPEAKER TOGGLE SPEAKER
1130 DEY COUNT THE HALF CYCLE
1140 BNE BELL2 NOT FINISHED
1150 RTS
|
Machine-Gun Noise: What if we use a random pulse width? Then we get something called noise, instead of a tone. We can create a burst of pulses of random-sounding width by using values from some arbitrary place in the Apple's memory as loop counts. The program uses the 256 values starting at $BA00 (which is inside DOS). If you make just one burst like that, it doesn't sound like much. But if you make ten in a row, you get a pattern of repetitious random noise bursts that in this case sounds like machine-gun fire. Doesn't it? Well, close enough....
1000 *---------------------------------
1010 * MACHINE-GUN NOISE
1020 *---------------------------------
1030 SPEAKER .EQ $C030
1040 CNTR .EQ $00
1050 *---------------------------------
1060 NOISE LDX #64 LENGTH OF NOISE BURST
1070 *---------------------------------
1080 LDA #10 NUMBER OF NOISE BURSTS
1090 STA CNTR
1100 .2 LDA SPEAKER TOGGLE SPEAKER
1110 LDY $BA00,X GET PULSE WIDTH PSEUDO-RANDOMLY
1120 .1 DEY DELAY LOOP FOR PULSE WIDTH
1130 BNE .1
1140 DEX GET NEXT PULSE OF THIS NOISE BURST
1150 BNE .2
1160 DEC CNTR GET NEXT NOISE BURST
1170 BNE .2
1180 RTS RETURN
|
Laser "SWOOP" Sound: We can change the pulse width by making it go from wide to narrow in steps of 5 microseconds. It sounds like a low tone that gradually slides higher and higher until it is beyond the range of the human ear (or the Apple speaker). I used this program in a "space war" game to go with the laser fire. Even though the sound was entirely generated before the laser even appeared on the screen, it looks and sounds like the light beam and sound are simultaneous.
I have indicated in line 1110 that you should try experimenting with some other values for the maximum pulse width count. I have included a separate entry point at SWOOP2 to make ten swoops in a row. Try the various values for the maximum width and run each one from SWOOP2. You might also experiment with running the pulse width in the opposite direction (from narrow to wide) by changing line 1200 to INC PULSE.WIDTH.
1000 *---------------------------------
1010 * LASER "SWOOP" SOUND
1020 *---------------------------------
1030 SPEAKER .EQ $C030
1040 PULSE.COUNT .EQ $00
1050 PULSE.WIDTH .EQ $01
1060 SWOOP.COUNT .EQ $02
1070 *---------------------------------
1080 SWOOP LDA #1 ONE PULSE AT EACH WIDTH
1090 STA PULSE.COUNT
1100 LDA #160 START WITH MAXIMUM WIDTH
1110 * (ALSO TRY VALUES OF 40, 80, 128, AND 160.)
1120 STA PULSE.WIDTH
1130 .1 LDY PULSE.COUNT
1140 .2 LDA SPEAKER TOGGLE SPEAKER
1150 LDX PULSE.WIDTH
1160 .3 DEX DELAY LOOP FOR ONE PULSE
1170 BNE .3
1180 DEY LOOP FOR NUMBER OF PULSES
1190 BNE .2 AT EACH PULSE WIDTH
1200 DEC PULSE.WIDTH SHRINK PULSE WIDTH
1210 BNE .1 TO LIMIT OF ZERO
1220 RTS
1230 *---------------------------------
1240 * MULTI-SWOOPER
1250 *---------------------------------
1260 SWOOP2 LDA #10 NUMBER OF SWOOPS
1270 STA SWOOP.COUNT
1280 .1 JSR SWOOP
1290 DEC SWOOP.COUNT
1300 BNE .1
1310 RTS
|
Another Laser Blast: This one sounds very much the same as the swoop of the previous program, but it uses less memory. You should try experimenting with the pulse widths of the first and last pulses in lines 1060 and 1130. You could also try changing the direction by substituting a DEX in line 1120.
1000 *---------------------------------
1010 * ANOTHER LASER BLAST
1020 *---------------------------------
1030 SPEAKER .EQ $C030
1040 *---------------------------------
1050 BLAST LDY #10 NUMBER OF SHOTS
1060 .1 LDX #64 PULSE WIDTH OF FIRST PULSE
1070 .2 TXA START A PULSE WITHIN A SHOT
1090 .3 DEX DELAY FOR ONE PULSE
1100 BNE .3
1105 TAX
1110 LDA SPEAKER TOGGLE SPEAKER
1120 INX
1130 CPX #192 PULSE WIDTH OF LAST PULSE
1140 BNE .2
1150 DEY FINISHED SHOOTING?
1160 BNE .1 NO
1170 RTS
|
Inch-Worm Sounds: I stumbled onto this one by accident, while looking for some sound effects for a lo-res graphics demo. The demo shows what is supposed to be an inch-worm, inching itself across the screen. By plugging various values (as indicated in lines 1100 and 1130), I got some sounds that synchronized beautifully with the animation. Complete with an exhausted sigh at the end!
1000 *---------------------------------
1010 * INCH-WORM SOUNDS
1020 *---------------------------------
1030 SPEAKER .EQ $C030
1040 PULSE.WIDTH .EQ $00
1050 PULSE.STEP .EQ $01
1060 PULSE.LIMIT .EQ $02
1070 *---------------------------------
1080 INCH.WORM
1090 LDA #1 SET STEP TO 1
1100 * (ALSO TRY 77, 129, 179)
1110 STA PULSE.STEP
1120 LDA #176 SET PULSE.WIDTH AND LIMIT TO 176
1130 * (ALSO TRY 88)
1140 STA PULSE.WIDTH
1150 STA PULSE.LIMIT
1160 .1 LDA SPEAKER TOGGLE SPEAKER
1170 LDX PULSE.WIDTH DELAY LOOP FOR PULSE WIDTH
1180 .2 PHA LONGER DELAY LOOP
1190 PLA
1200 DEX END OF PULSE?
1210 BNE .2 NO
1220 CLC CHANGE PULSE WIDTH BY STEP
1230 LDA PULSE.WIDTH
1240 ADC PULSE.STEP
1250 STA PULSE.WIDTH
1260 CMP PULSE.LIMIT UNTIL IT REACHES THE LIMIT
1270 BNE .1
1280 RTS
|
Touch-Tones Simulator: I used this one with a telephone demo program. The screen shows a touch tone pad. As you press digits on the keyboard, the corresponding button on the screen lights up (displays in inverse mode). Then the demo program CALLs this machine language code to produce the twin-tone sound that your telephone makes. It isn't perfect, you can't fool the Bell System. But it makes a good demo!
I will describe the program from the top down. The four variables in page zero are kept in a "safe" area, inside Applesoft's floating point accumulator. Applesoft doesn't use these locations while executing a CALLed machine language routine.
The Applesoft demo program stores the button number (0-9) in location $E7. This could be done with "POKE 231,DGT", but I had more fun using "SCALE=DGT". SCALE= is a hi-res graphics command, but all it really does is store the value as a one-byte integer in $E7. Since we aren't using hi-res graphics, the location is perfectly safe to use.
CALL 768 gets us to line 1150, TWO.TONES. This is the main routine. It uses the button number to select the two tone numbers from LOW.TONES and HIGH.TONES. ONE.TONE is called to play first the low tone, then the high tone, back and forth, for ten times each. This is my attempt to fool the ear, to make it sound like both are being played at once.
1000 *---------------------------------
1010 * TOUCH TONES SIMULATOR
1020 *---------------------------------
1030 SPEAKER .EQ $C030
1040 *---------------------------------
1050 DOWNTIME .EQ $9D
1060 UPTIME .EQ $9E
1070 LENGTH .EQ $9F
1080 CHORD.TIME .EQ $A0
1090 *---------------------------------
1100 BUTTON .EQ $E7 SET BY "SCALE= # "
1110 * USE VALUES FROM 0 THRU 9
1120 *---------------------------------
1130 .OR $300
1140 *---------------------------------
1150 TWO.TONES
1160 LDA #10
1170 STA CHORD.TIME
1180 .3 LDX BUTTON
1190 LDA LOW.TONES,X
1200 JSR ONE.TONE
1210 LDA HIGH.TONES,X
1220 JSR ONE.TONE
1230 DEC CHORD.TIME
1240 BNE .3
1250 RTS
1260 *---------------------------------
1270 ONE.TONE
1280 TAY
1290 LDA DOWNTIME.TABLE,Y
1300 STA DOWNTIME
1310 LDA UPTIME.TABLE,Y
1320 STA UPTIME
1330 LDA LENGTH.TABLE,Y
1340 STA LENGTH
1350 *---------------------------------
1360 PLAY LDY UPTIME
1370 LDA SPEAKER
1380 DEC LENGTH
1390 BEQ .4 FINISHED
1400 .1 DEY
1410 BNE .1
1420 BEQ .2
1430 .2 LDY DOWNTIME
1440 LDA SPEAKER
1450 DEC LENGTH
1460 BEQ .4
1470 .3 DEY
1480 BNE .3
1490 BEQ PLAY
1500 .4 RTS
1510 *---------------------------------
1520 DOWNTIME.TABLE
1530 .HS 8E807468514942
1540 *---------------------------------
1550 UPTIME.TABLE
1560 .HS 8E807469514942
1570 *---------------------------------
1580 LENGTH.TABLE
1590 .HS 1412100F201D1A
1600 *---------------------------------
1610 LOW.TONES
1620 .HS 03000000010101020202
1630 HIGH.TONES
1640 .HS 05040506040506040506
1650 *---------------------------------
1660 * SIMULATED DRIVER
1670 *---------------------------------
1680 MON.WAIT .EQ $FCA8
1690 PUNCH.ALL
1700 LDA #0
1710 STA BUTTON
1720 .1 JSR TWO.TONES
1730 LDA #0
1740 JSR MON.WAIT
1750 INC BUTTON
1760 LDA BUTTON
1770 CMP #10
1780 BCC .1
1790 RTS
|
ONE.TONE wiggles the speaker for LENGTH half-cycles. Each half-cycle is controlled by either the UPTIME or DOWNTIME counts. These three parameters are selected from three tables, according to the tone number selected by TWO.TONES. Lines 1270-1340 pick up the values from the three tables and load the page zero variables. Lines 1360-1500 do the actual speaker motions and time everything. The purpose of having two routines, one for uptime and one for downtime, is to be able to more closely approximate the frequency. For example, if the loop count we ought to use is 104.5, we could use an uptime of 104 and a down time of 105; this makes the total time for the full cycle correct. The redundant BEQ in line 1420 is there to make the loop times for UPTIME and DOWNTIME exactly the same.
Since you do not have my Applesoft program, which drives this, I wrote a simulated drive to just "push" the buttons 0-9. Lines 1650-1790 do this. I separated each button push by a call to the monitor WAIT subroutine, to make them easier to distinguish.
1650 *------------------------------------
1660 * SIMULATED DRIVER
1670 *------------------------------------
1680 MON.WAIT .EQ $FCA8
1690 PUNCH.ALL
1700 LDA #0
1710 STA BUTTON
1720 .1 JSR TWO.TONES
1730 LDA #0
1740 JSR MON.WAIT
1750 INC BUTTON
1760 LDA BUTTON
1770 CMP #10
1780 BCC .1
1790 RTS
|
Morse Code Output: I have always thought that computers really only need one output line and one input line for communicating with humans. I could talk to my Apple with a code key, and it could beep back at me. One of the first programs I attempted in 6502 language was a routine to echo characters in Morse code. I looked it up about two hours ago, and shuddered at my sloppy, inefficient, hard to follow code. So, I wrote a new one.
I broke the problem down into three littler ones: 1) getting the characters which are to be output; 2) converting the ASCII codes to the right number of dots and dashes; and 3) making tones and spaces of the right length.
SETUP.MORSE (lines 1190-1240) links my output routine through the monitor output vector. Line 1240 JMPs to $3EA to re-hook DOS after me.
MORSE (lines 1260-1310) are an output filter. If the character code is less than $B0, I don't know how to send it in Morse code; therefore, I just go to $FDF0 to finish the output on the screen. Codes exist for these other characters, but I did not look them up. If you want a complete routine, you should modify line 1260 to CMP #$A0 and add the extra codes to the code table (lines 1130-1170).
SEND.CHAR looks up the Morse code for the character in the code table, and splits it into the number of code elements (low-order three bits) and the code elements themselves (high-order five bits). If a code element is zero, a short beep (dot) is sounded. If an element is one, three calls to the short beep routine make one long beep (dash). Between elements, a silence equal to the length of a short beep intervenes. After the last beep of a character, a longer silence, equal to three short silences, is produced. A 00 code from the code table makes a silent gap of three times the inter-character gap.
EL.SPACE and EL.DIT are nearly identical. The only difference is that EL.DIT makes a sound by addressing the speaker, while EL.SPACE does not. The value of EL.PITCH determines the pulse width, and EL.SPEED determines the number of pulses for an inter-element-space or a short beep. If the code stream is too fast for you, you can slow it down by increasing either or both of these two numbers.
1000 *---------------------------------
1010 * MORSE CODE OUTPUT
1020 *---------------------------------
1030 SPEAKER .EQ $C030
1040 DUMMY .EQ $C000
1050 *---------------------------------
1060 SAVEX .BS 1
1070 SAVEY .BS 1
1080 EL.COUNT .BS 1
1090 EL.CODE .BS 1
1100 EL.SPEED .EQ 120
1110 EL.PITCH .EQ 80
1120 *---------------------------------
1130 CODES .HS FD7D3D1D0D0585C5E5F5 0, 1-9
1140 .HS 000000000000
1150 .HS 004284A4830124C3040274A344C2 @, A-M
1160 .HS 82E364D443038123146394B4C4 N-Z
1170 .HS 000000000000
1180 *---------------------------------
1190 SETUP.MORSE
1200 LDA #MORSE
1210 STA $36
1220 LDA /MORSE
1230 STA $37
1240 JMP $3EA
1250 *---------------------------------
1260 MORSE CMP #$B0 SEE IF PRINTING CHAR
1270 BCC .1 NO
1280 PHA SAVE CHAR ON STACK
1290 JSR SEND.CHAR
1300 PLA GET CHAR OFF STACK
1310 .1 JMP $FDF0
1320 *---------------------------------
1330 SEND.CHAR
1340 STX SAVEX
1350 STY SAVEY
1360 SEC
1370 SBC #$B0
1380 TAX
1390 LDA CODES,X
1400 STA EL.CODE
1410 AND #7 GET ELEMENT COUNT
1420 BEQ .4 NO CODE
1430 STA EL.COUNT
1440 .1 ASL EL.CODE PUT NEXT ELEMENT INTO CARRY
1450 BCC .2 MAKE 'DIT'
1460 JSR EL.DIT MAKE 'DAH' FROM 3 DITS
1470 JSR EL.DIT
1480 .2 JSR EL.DIT MAKE 'DIT'
1490 JSR EL.SPACE
1500 DEC EL.COUNT
1510 BNE .1
1520 .3 JSR CH.SPACE
1530 LDX SAVEX
1540 LDY SAVEY
1550 RTS
1560 .4 JSR CH.SPACE
1570 JSR CH.SPACE
1580 JMP .3
1590 *---------------------------------
1600 CH.SPACE
1610 JSR EL.SPACE
1620 JSR EL.SPACE
1630 EL.SPACE
1640 LDY #EL.SPEED
1650 .1 LDX #EL.PITCH
1660 LDA DUMMY
1670 .2 DEX
1680 BNE .2
1690 DEY
1700 BNE .1
1710 RTS
1720 *---------------------------------
1730 EL.DIT LDY #EL.SPEED
1740 .1 LDX #EL.PITCH
1750 LDA SPEAKER
1760 .2 DEX
1770 BNE .2
1780 DEY
1790 BNE .1
1800 RTS
|
| Stuffing Object Code in Protected Places |
Bob Sander-Cederlof |
Several users of Version 4.0 have asked for a way to defeat the protection mechanism, so that they can store object code directly into the language card. One customer has a EPROM burner which accepts code at $D000. He wants to let the assembler write it out there directly, even though he could use the .TA directive and later a monitor move command. Or, he could use the .TF directive, and a BLOAD into his EPROM.
For whatever reason, if you really want to do it, all you have to do is type the following patch just before you assemble: $1A25:EA EA. In case you want to put it back, or check before you patch, what should be there is B0 28.
| Multiplying on the 6502 | Bob Sander-Cederlof |
Brooke Boering wrote an excellent article, "Multiplying on the 6502", in MICRO--The 6502 Journal, December, 1980, pages 71-74. If you are wondering how to do it, or you want a faster routine for a special application, look up that article.
Brooke begins by explaining and timing the multiply subroutine found in the old Apple Monitor ROM. The time to multiply two 16-bit values and get a 32-bit result varies from 935 to 1511 microseconds, depending on how many "1" bits are in the multiplier. He proceeds to modify that subroutine to cut the execution time by 40%!
Finally, he presents two limited versions which are still quite useful in some applications. His 8x16 multiply averages only 383 microseconds, and his 8x8 version averages 192 microseconds.
Here is the code for his 16x16 version, which averages 726 microseconds. It has the same setup as the routine in the Apple ROM. On entry, the multiplicand should be in AUXL,AUXH ($54,55); the multiplier should be in ACL,ACH ($50,51); whatever is in XTNDL,XTNDH ($52,53) will be added to the product. Normally, XTNDL and XTNDH should be cleared to zero before starting to multiply. However, I have used this routine to convert from decimal to binary; I put the next digit in XTNDL and clear XTNDH, and then multiply the previous result by ten. The "next digit" is automatically added to the product that way. (I have corrected the typographical error in the listing as published in MICRO.)
1000 *---------------------------------
1010 * FASTER 16X16 MULTIPLY
1020 * BY BROOKE W. BOERING
1030 * NEARLY AS PUBLISHED IN MICRO--THE 6502 JOURNAL
1040 * PAGE 72, DECEMBER, 1980.
1050 *---------------------------------
1060 ACL .EQ $50
1070 ACH .EQ $51
1080 XTNDL .EQ $52
1090 XTNDH .EQ $53
1100 AUXL .EQ $54
1110 AUXH .EQ $55
1120 *---------------------------------
1130 RMUL LDY #16 16-BIT MULTIPLIER
1140 .1 LDA ACL (AC * AUX) + XTND
1150 LSR CHECK NEXT BIT OF MULTIPLIER
1160 BCC .2 IF ZERO, DON'T ADD MULTIPLICAND
1170 CLC ADD MULTIPLICAND TO PARTIAL PRODUCT
1180 LDA XTNDL
1190 ADC AUXL
1200 STA XTNDL
1210 LDA XTNDH
1220 ADC AUXH
1230 STA XTNDH
1240 .2 ROR XTNDH SHIFT PARTIAL PRODUCT
1250 ROR XTNDL
1260 ROR ACH
1270 ROR ACL
1280 DEY NEXT BIT
1290 BNE .1 UNTIL ALL 16
1300 RTS
1310 *---------------------------------
1320 * TEST ROUTINE FOR MULTIPLY
1330 *---------------------------------
1340 SETUP.Y
1350 LDA #$4C PUT "JMP TESTMPY" IN $358-35A
1360 STA $3F8
1370 LDA #TESTMPY
1380 STA $3F9
1390 LDA /TESTMPY
1400 STA $3FA
1410 RTS
1420 *---------------------------------
1430 TESTMPY
1440 LDA $3C MOVE A1L,A1H TO ACL,ACH
1450 STA ACL
1460 LDA $3D
1470 STA ACH
1480 LDA $3E MOVE A2L,A2H TO AUXL,AUXH
1490 STA AUXL
1500 LDA $3F
1510 STA AUXH
1520 LDA $42 MOVE A4L,A4H TO XTNDL,XTNDH
1530 STA XTNDL
1540 LDA $43
1550 STA XTNDH
1560 JSR RMUL MULTIPLY
1570 LDA XTNDH PRINT 32-BIT RESULT
1580 JSR $FDDA
1590 LDA XTNDL
1600 JSR $FDDA
1610 LDA ACH
1620 JSR $FDDA
1630 LDA ACL
1640 JMP $FDDA
|
I wrote a test routine for the multiply, so that I could check it out. After assembling the whole program, I typed "MGO SETUP.Y" to link the control-Y Monitor Command to my test routine. Control-Y will parse three 16-bit hexadecimal values this way: val1<val2.val3cY stores val1 in $42,$43; val2 in $3C,$3D; and val3 in $3E,$3F. ("cY" stands for control-Y.)
I define val1 to be the initial value for XTNDL,XTNDH; this should normally be zero. The two values to be multiplied are val2 and val3. After TESTMPY receives control from the control-Y processor, it moves the three values into the right locations for the multiply subroutine. Then JSR RMUL calls the multiply routine. The following lines (1570-1640) print the 32-bit result by calling a routine in the monitor ROM which prints a byte in hex from the A-register.
| A String Swapper for Applesoft | Bob Sander-Cederlof |
Practically every program rearranges data in some way. Many times you must sort alphanumeric data, and Applesoft makes this relatively easy. At the heart of most sort algorithms you will have to swap two items.
If the items are numbers, you might do it like this: T=A(I) : A(I)=A(J) : A(J)=T. If the items are in string variables, you might use this: T$=A$(I) : A$(I)=A$(J) : A$(J)=T.
Before long, Applesoft's wonderful string processor eats up all available memory and your program screeches to a halt with no warning. You think your computer died. Just about the time you reach for the power switch, it comes to life again (if you aren't too impatient!); the garbage collection procedure has found enough memory to continue processing. If only Applesoft had a command to swap the pointers of two strings, this wouldn't happen.
What are pointers? Look on page 137 of your Applesoft Reference Manual. The third column shows how string variables are stored in memory. Each string, whether a simple variable or an element of an array, is represented by three bytes: the first byte tells how many bytes are in the string value at this time; the other two bytes are the address of the first byte of the string value. The actual string value may be anywhere in memory. I am calling the three bytes which define a string a "pointer".
All right, how can we add a string swap command? The authors of Applesoft thoughtfully provided us with the "&" command; it allows us to add as many new commands to the language as we want. (Last month I showed you how to add a computed GOSUB command using the &.) We could make up our own swap command; perhaps something like &SWAP A$(I) WITH A$(J). However, to keep it a little simpler, I wrote it this way: &A$(I),A$(J).
The program is in two sections. The first part, called SETUP, simply sets up the &-vector at $3F5, $3F6, and $3F7. It stores a "JMP SWAP" instruction there. When Applesoft finds an ampersand (&) during execution, it will jump to $3F5; our JMP SWAP will start up the second section.
SWAP calls on two routines inside the Applesoft ROMs: PTRGET ($DFE3) and SCAN.COMMA ($DEBE). I found the addresses for these routines in the article "Applesoft Internal Entry Points", by John Crossley, pages 12-18 of the March/April 1980 issue of The Apple Orchard. I also have disassembled and commented the Applesoft ROMs, so I checked to see if there were any bad side effects. Both routines assume that Applesoft is about to read the next character of your program. PTRGET assumes you are sitting on the first character of a variable name. SCAN.COMMA hopes you are sitting on a comma.
SWAP merely calls PTRGET to get the address of the pointer for the first variable, check for an intervening comma, and then calls PTRGET again to get the pointer address for the second variable. Then lines 1350-1430 exchange the three bytes for the two pointers.
1000 *---------------------------------
1010 * STRING SWAP FOR APPLESOFT
1020 * "BRUN B.STRING.SWAP" TO SET IT UP;
1030 * THEN "&A$,B$" MEANS SWAP A$ AND B$.
1040 *---------------------------------
1050 .OR $300
1060 .TF B.STRING.SWAP
1070 *---------------------------------
1080 AMPERSAND.VECTOR .EQ $3F5
1090 *---------------------------------
1100 PTRGET .EQ $DFE3 SCAN FOR VARIABLE NAME,
1110 * SEARCH FOR ITS ADDRESS,
1120 * LEAVE ADDRESS IN $83,$84
1130 * AND A,Y
1140 *---------------------------------
1150 SCAN.COMMA .EQ $DEBE IF NEXT CHARACTER IS
1160 * IS A COMMA, SCAN OVER
1170 * IT; IF NOT, SYNTAX ERROR.
1180 *---------------------------------
1190 A.PNTR .EQ $85,86
1200 B.PNTR .EQ $83,84
1210 *---------------------------------
1220 SETUP LDA #SWAP SET UP AMPERSAND VECTOR
1230 STA AMPERSAND.VECTOR+1
1240 LDA /SWAP
1250 STA AMPERSAND.VECTOR+2
1260 LDA #$4C JMP OPCODE
1270 STA AMPERSAND.VECTOR
1280 RTS
1290 *---------------------------------
1300 SWAP JSR PTRGET GET POINTER TO FIRST STRING
1310 STA A.PNTR
1320 STY A.PNTR+1
1330 JSR SCAN.COMMA CHECK FOR COMMA
1340 JSR PTRGET
1350 LDY #2 PREPARE TO SWAP 3 BYTES
1360 .1 LDA (A.PNTR),Y
1370 PHA
1380 LDA (B.PNTR),Y
1390 STA (A.PNTR),Y
1400 PLA
1410 STA (B.PNTR),Y
1420 DEY NEXT BYTE
1430 BPL .1
1440 RTS RETURN
|
How about a demonstration? I have a list of 20 names (all are subscribers to the Apple Assembly Line), and I want to sort them into alphabetical order. Since I am just writing this to demonstrate using the swap command, I will use one of the WORST sort algorithms: the bubble sort.
Line 100 clears the screen and prints a title line. Line 110 loads the swap program and calls SETUP at 768 ($0300). Line 120 reads in the 20 names from the DATA statement in line 130, and calls a subroutine at line 200 to print the names in a column.
Lines 150-170 are the bubble sort algorithm. If two names are out of order, they are swapped at the end of line 160. Line 180 prints the sorted list of names in a second column.
100 TEXT : HOME : PRINT "DEMO USE OF 'STRING SWAP' ROUTINE"
110 DIM A$(20): PRINT CHR$(4)"BLOAD B.STRING.SWAP": CALL 768
120 FOR I = 1 TO 20: READ A$(I): NEXT : P = 1: GOSUB 200
130 DATA AMES,BURKE,PUTNEY,LEE,LEVY,RAMSDELL,BISHOP,RANDALL,
LANDSMAN,LEIPER,OSLISLO,KOVACS,MEADOR,KRIEGSMAN,MERCIER,
WHITE,LEVY,BLACK,SCHORNAK,STITT
140 REM BUBBLE SORT
150 M = 20
160 M = M - 1:SW = 0: FOR I = 1 TO M: IF A$(I+1) < A$(I) THEN
SW = 1: & A$(I+1),A$(I): REM SWAP
170 NEXT : IF SW THEN 160
180 P = 20: GOSUB 200: END
200 VTAB 3: FOR I = 1 TO 20: HTAB P: PRINT A$(I): NEXT : RETURN
|
| A Third Disassembler for the Apple II? |
Lee Meador |
A couple of months back, I was reading the most recent copy of the Apple Assembly Line. I was horrified! For some time I had been working on a disassembler that would work with the S-C Assembler II. There before my very eyes were not one, but two, advertisements for the disassemblers that would do the same things mine would do, and in some areas they would do a better job. Sure, my disassembler would form the source statements, put in the labels, take care of non-6502 machine language bytes and it would even provide a cross-reference listing as it went along. But, my program forms the source directly in memory. So, you can't disassemble much more than around 1.5K at a time. Secondly, I hadn't gotten it to the point that you could specify various commands and disassemble code, tables, strings, 16-bit data, etc. all in one pass. I had a separate program to do each one.
After thinking about it for some time I decided to sell what I've got. But how could I compete with the other two disassemblers with more features? They are even relatively low priced.
I did come up with a solution. (As if you hadn't guessed that by now.) I am offering my disassembler with the S-C source code and comments. In fact, you will need to assemble the program yourself to get it to work. If you want to join the various modules to create the program I was working toward, you can do that. If you don't like the way I did some part of the program you are free to make whatever changes you like. If the code you want to disassemble lies in the same place in memory as the disassembler, then you just reassemble the program somewhere else. You ARE limited to the size of memory. The disassembler, the table of labels, the program you are disassembling and the source version will have to fit in memory. For large programs you should disassemble in 1K pieces. But, if you choose too large a piece, be prepared to re-boot your system.
Let me make it clear that this disassembler works correctly. I have used it on thousands of bytes of machine language files. It does not die without cause. The source code it produces can be saved and when reassembled, the result matches the original program.
If you choose to order this from me you will get a 13-sector disk with the S-C Assembler II source code for the various programs. You will not get a well-written manual and you will not be able to boot and go. But, where else can you get a working version of a disassembler for only $2500 with the source code?
Ask for Lee Meador's Disassembler. Enclose check or money order for $25.00 (add $5.00 if ouside North America.) Your disassembler source code will be mailed within a day or two of when we receive your order.
Lee Meador, Box 3621, Arlington, TX 76010
(817)469-6019