Apple Assembly   Line
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


Apple Assembly Line is published monthly by S-C SOFTWARE, P. O. Box 5537, Richardson, TX 75080. Subscription rate is $12/year, in the U.S.A., Canada, and Mexico. Other countries add $6/year for extra postage. All material herein is copyrighted by S-C SOFTWARE, all rights reserved. Unless otherwise indicated, all material herein is authored by Bob Sander-Cederlof. (Apple is a registered trademark of Apple Computer, Inc.)