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...

- Apple Noises and Other Sounds
  - Simple Tone
  - Apple "Bell" Subroutine
  - Machine-Gun Noise
  - Laser "SWOOP" Sound
  - Another Laser Blast
  - Inch-Worm Sounds
  - Touch-Tones Simulator
  - Morse Code Output
- Stuffing Object Code in Protected Places
- Multiplying on the 6502
- A String Swapper for Applesoft
- A Third Disassembler for the Apple II?

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.)