The well-tempered Apple. Michael Fink.
Games and other programs have made Apple users aware of the Apple speaker as a source of music and sound effects. Nearly every guide to Apple programming gives the memory address (49200, -16336, or $C030) at which you can PEEK and "tweak" the speaker. Some sources list a machine language routine which enables you, by supplying one value (1-255) for frequency and another (1-255) for duration, to produce a pitch through the speaker. CALLING such a routine several times in succession, each time supplying new frequency and duration values, results in a sequence of tones, a melody. Problem: Tuning And Range
This is all well and good for fun-and-games programming. But for genuine musical applications--or better quality recreational software--those routines have serious limitations in tuning accuracy and range (low bass to high treble) because of the limited range of frequency values.
If you try various values in one of those machine language routines, you will have no trouble going high in the range, but the lowest pitch you can access, unfortunately, is somewhere around g.sub.1. That is certainly not an acceptable lower limit for even the simplest melody.
However, the root of the problem lies not so much in the limited numeric values as in the high operating speed of the machine language routine. The routine, as it stands, simply pushes the speaker in and out at a frequency which cannot be adjusted below a defined limit. Clearly, what is needed is a way of delaying each tweak of the speaker, of slowing down the routine in a controlled manner, so that the bottom limit can be lowered significantly through program control. As will be seen, solving the range problem also solves the pitch accuracy problem. What is needed is a way of delaying each tweak of the speaker. Solution: A Delay Factor
The following routine produces a pitch somewhere around f: 10 X= - 16336 20 S= PEEK (X): GOTO 20 But it is probably the highest pitch available by operating the speaker purely from Basic. Because of the inherent slowness of working through the Basic interpreter, there is a built-in delay between one tweak of the speaker and the next.
The delay may be lengthened by adding program lines or delaying loops, and the result will be a lowered pitch. But there is no way of raising the pitch, since the built-in delay in the operation of the Basic interpreter cannot be reduced. Thus, music in a Basic program has a severe upper pitch limit problem due to its inherent slowness, and music in a machine language program has an equally severe lower pitch limit due to its inherent higher speed.
By applying the principle of delay illustrated above to machine language routines, I arrived at the solution to both the range and tuning problems of the Apple speaker. I employed a fairly common machine language routine (Listing 1) that uses X and Y registers for work with a frequency value (POKEd into $0301) and a decrementing duration value (POKEd into $0300). However, I added a third value, the delay factor, and POKEd it into $0302 for control within the subroutine itself, which begins at $0303.
The value of the delay factor (1-255) is loaded into the accumulator at $0309. Then, at $030C a resident subroutine, JSR $FCA8, is executed. Using the value currently in the accumulator, this subroutine causes the machine language routine to WAIT a specified length of time. (For L = delay factor value, the delay will equal .5 (26 + 27L + 5L.sup.2.) microseconds, and the accumulator will be zeroed at the end.)
This is the crux of the whole operation, and its implications are twofold. 1) Using large delay factor values makes it possible to lower pitch all the way down to about 1 Hz. 2) It is now possible to arrive at an accurately tuned, equal tempered scale. By increasing or decreasing the delay factor value, the pitch can be lowered or raised by a significant degree. That is, with the frequency value constant, incrementing or decrementing the delay factor changes the pitch by about a half-step. This leaves the frequency value free to "fine tune" the desired pitch. Tuning The Apple
Using the machine language routine and principles mentioned above, I have arrived at numeric values for a wide range, equal tempered chromatic scale extending from C to e.sub.3 (see Figure 1). Each pitch is accurate to [plus-or-minus] 1 cent (1% of the distance to the next higher or lower equal tempered pitch). That degree of difference is not apparent to the human ear.
If you wish, you can extend the scale even further in either direction, but you will need to use some sort of tuning device. I used a Korg Chromatic Tuner (model WT-12) and placed it near or against the left side of the Apple where the microphone could pick up the speaker easily and the meter could also be read. Using the Korg, I tested the pitch on four different Apples to make sure that the clock rate of the processor would be consistent among various computers. Accuracy was within .5 cents. Playing The Apple
Accessing pitch from a Basic program using the machine language routine is not difficult. Two things are necessary: a subroutine that will POKE the three values into memory and CALL the machine language routine that drives the speaker, and a method for assigning values to the three variables for each note. Accessing pitch from a Basic program using the machine language routine is not difficult.
The speaker driver subroutine can be quite simple: 10 REM SPEAKER DRIVER 20 POKE 769, F 30 POKE 770, L 40 POKE 768, D 50 CALL 771 60 RETURN
For storing variable values, I suggest creating an array of data to be read into the program with one DATA line for each sound. The array can be set up to be used either sequentially or by random access. If arranged sequentially, as in a melody, values are assigned to F (frequency), L (delay factor), and D (duration) for each of N notes: 500 FOR I=1 TON 501 READ F(I), L(I), D(I) 502 NEXT I 1000 REM THREE BLIND MICE 1001 DATA 6, 8, 50 1002 DATA 5, 11, 60 1003 DATA 8, 14, 45
If several melodies are required in a program, random access may be more desirable. In that case, the array is made up of the entire, full-range scale of N notes that may be needed but only F and L values are read in at first: 500 FOR I=1 TON 501 READ F(I), L(I) 502 NEXT I 1000 REME-MAJOR SCALE 1001 DATA 23, 25 1002 DATA 12, 22 1003 DATA 3, 19 Here, the D value for a given note is assigned only when the speaker driver subroutine is CALLED for that note. The Rhythm Trade-Off
My method of making music through the Apple speaker is three-dimensional, and there are always trade-offs when juggling three dimensions. The trade-off here is one of frequency/delay vs. duration, and the challenge is to maximize tuning accuracy while maintaining as much flexibility as possible in duration or rhythm. Long notes are no problem, but the programmer should be able to sound a pitch for a very short duration, as well. The brevity of any note in my system is dependent somewhat on the value of the delay factor. That is why each pitch in Figure 1 is assigned the lowest possible delay factor consistent with close tuning.
Since pitch and duration are so interdependent in any program using the Apple speaker, there is no reliable ratio between duration values. For a given duration (say, either a quarter note or 0.5 second) one pitch will require one value and another pitch will require something quite different. Therefore, duration values must be assembled by experimentation. Conclusion
Although synthesizer cards and complete music systems offer more expensive and more sophisticated means of making music with an Apple, there are still many applications to be explored using immediately accessible sound through the speaker. With accurate and full-range pitch, the speaker possesses more of the capabilities of a real synthesizer, and by relatively simple means games and educational programs can include music that actually sounds like music.