sdh@joevax.UUCP (The Doctor) (08/26/86)
There have been a few postings recently from people asking questions about how to get good sound from the Apple (with peripherals), so I'm posting a few 6502 sources for producing sound from the Apple's speaker and getting quality beyond what one would think possible. The traditional way of doing sound routines on the apple is to take the point of view, All I have to do is write a loop that clicks the speaker, and does a delay. The number of times through the loop is the duration, and the delay will generate the frequency. Such a routine looks like this: SPKR .EQ $C030 FREQ .EQ $00 DUR .EQ $01 SOUND BIT $C030 ; CLICK SPEAKER, BIT DOESN'T ALTER REGISTERS LDA FREQ LSR ; PUT FREQ/2 IN X TAX .1 LDY FREQ ; PUT FREQ IN Y .2 DEY ; DECREMENT Y UNTIL ITS ZERO BNE .2 DEX ; DO THE SAME FOR X BNE .1 DEC DUR BNE SOUND RTS A very simple routine, and very useable, but it has a few fundamental problems, the use of a delay as the determinant of frequency causes the duration to decrease as the frequency increases, that is higher pitches will have much shorter apparent durations, even though they will go through the main loop the same number of times as lower pitches. The trick here is to get a routine that isn't delay dependant. One way to think about this process is to remeber that frequency is defined as the number of cycles per unit time. In our case, the unit of time is trips through the main loop, and the cycle is the speaker being clicked. So instead of clicking the speaker every time through, we'll click every nth time where n becomes the reciprocal of the frequency, or the period. Here is a simple routine to do that. SPKR .EQ $C030 FREQ .EQ $00 DUR1 .EQ $01 DUR2 .EQ $02 ; Using two bytes to get longer durations. SOUND LDY FREQ ; Put frequency in y SND1 BEQ EXIT ; Zero frequency makes no sense here DEY ; Decrement Y BNE NOCLCK ; No click if not zero BIT SPKR LDY FREQ ; To get here, y must have reached zero, so we reload NOCLCK DEC DUR1 BNE SND1 DEC DUR2 BNE SND1 EXIT RTS Now we have a routine that not only insures that low frequencies are the same durations a high frequencies, there is also a much better function for determining frequency. If you choose, it is not hard to come up with a formula that comforms to that of a just scale, but there will be some error in it towards the high end of the range, since the the branches will take a variable amount of processor cyles depending on whether or not they are successful. This is a minor inconvenience compared with the problems in the first routine. The duration was set to two bytes this time because very little time is spent in the main loop, so longer durations are imperative. The fact that there is so much extra time tells us that we could probably duplicate the actual frequency routine, providing us with 2 simultaneous voices. Such a program would look like this: SPKR .EQ $C030 FREQ1 .EQ $00 FREQ2 .EQ $01 DUR1 .EQ $02 DUR2 .EQ $03 SFR1 .EQ $04 ; scratch frequency SFR2 .EQ $05 ; this will free up our registers. SOUND LDA FREQ1 BEQ EXIT STA SFR1 ; Avoid zero frequencies LDA FREQ2 BEQ EXIT ; Ditto STA SFR2 LOOP DEC SFR1 BNE NCLK1 ; Only click if zero BIT SPKR LDA FREQ1 ; restore old frequency STA SFR1 NCLK1 DEC SFR2 BNE NCLK2 ; Same as above, but with second frequency; BIT SPKR LDA FREQ1 ; restore old frequency STA SFR2 NCLK2 DEC DUR1 BNE LOOP DEC DUR2 BNE LOOP EXIT RTS This routine gives us 2 voices of equalized duration that now only changes the accumulator, letting you use the x and y registers for other things (like indexes to tables of frequencies and durations for music) without having to waste time saving them. For something really impressive, try using $FE and $FF for the frequencies. This gives you a very deep tone that sounds similar to a sound used on Williams arcade games (Defender, Stargate, Robotron etc). If you want an additional parameter, eliminate the first STA SFR1 and STA SFR2 instructions, this will make the initial value of SFR1 and SFR2 the phase of the waveform generated. There is another little quirk about the Apple's speaker we can take advantage of. Tray the following program: SPKR .EQ $C030 SOUND BIT SPKR JMP SOUND ; Loop forever This program appears to make no noise at all! If you listen very closely (you may have to remove the cover of the machine) you will hear a very faint tone. The reason its so quiet is because that of the actual mechanics of the speaker. It only makes a click every other time its soft switch is referenced. Whene it clicks, the machine is pushing the speaker cone out (I think) and the other time its pulling it back in. If you get a second click very soone after the first, you choke off the sound -sort of like hitting a cymbal and dampening it immediately. By controlling how soon after the speaker has been clicked that we dampen it, we can control the volume IN SOFTWARE! Here is a routine to do that. For speed purposes, the code must be self-modifying, otherwise its gets tough to control the frequency accurately. SPKR .EQ $C030 FREQ .EQ $00 SFRQ .EQ $04 ; scratch frequency DUR1 .EQ $01 DUR2 .EQ $02 VOL .EQ $03 SOUND LDA VOL AND #$0F; Limit the user to 16 volumes TAY ; Put in Y so we can incrememnt (faster than adding 1) INY ; guarantee non-zero volume STY VENTR+1 ; Store in volume entry location LDA #$11 SEC SBC VENTR+1 ; Subtract volume level for equalization STA EQUAL+1 ; Put in equalizer LDA FREQ BEQ EXIT ; Don't do a zero frequency STA SFRQ ; Put in scratch LOOP DEC SFRQ BNE NCLCK ; Only click if zero BIT SPKR VENTR LDY #$00 ; doesn't matter what value we put here. It gets set above. LOOP1 DEY BNE LOOP1 ; VERY short delay. Won't affect frequency if we equalize BIT SPKR ; Muffler. EQUAL LDY #$00 ; This value is also set above. LOOP2 DEY BNE LOOP2 LDA FREQ ; Reset frequency STA SFRQ NCLCK DEC DUR1 BNE LOOP DEC DUR2 BNE LOOP EXIT RTS The reason for the equalizer is that we are doing a delay right after we click the speaker. If we're not careful to make sure that the section from VENTR to NCLCK takes the same amount of time, no matter what the volume, it will have a strong effect of the frequency. What we do is, if the volume is 1, we do a delay of 1 before the second click, and a delay of $10 after for a total of 11 (A 2 will have an equalization delay of $0F, 3 will have $0E and so on). This means that the same value for the frequency will have the apparent frequency at all volumes (this isn't completely true since the equalization is only a greedy approximation, but close enough). That's all I'm going to write on this subject. Other things to try are: 2 voices with independant volume controls Dynamic volume control Attack & Decay These are a little tricky, but not too hard. Steve Hawley joevax!sdh PS Does this mean I'm an Apple II guru?