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?