For most of the Apple II line of computers, the only way to actually make a sound on the computer was to read from I/O port $C030 (i.e. hexadecimal C030, which is decimal 49200). This would "click" the computer's speaker once. In order to turn this into actual sound, it was necessary to click the speaker multiple times in rapid succession, creating a sound. It was quite easy to create single notes this way, since you could set up a specific oscillation frequency with a simple timing loop, thus resulting in a nice sound wave, but it was a bit of a programming feat to be able to make a program that did this while simultaneously doing everything else the program needs to do. Of course, the greatest trick of all was to actually create digitized sound from this speaker, by "clicking" it at varying speeds to produce recognizable sound effects. This is tricky, but not impossible; it's quite similar to the way many programs created digitized sound on the IBM PC's internal speaker (which similarly was made to only play single notes) through elaborate timing routines.
To create a starting point, here's how to make a short program that will produce a tone on an Apple II computer:
From the Applesoft BASIC prompt, type CALL -151. This command calls a machine-language debugger routine known fondly as the "monitor", which is located in the ROM of every Apple II. (Note that original Apple IIs actually started in the monitor by default, so if you're using one of those, you won't actually need to type anything to get into the monitor. You will need to use this command on Apple II+, Apple IIe, and other later machines.)
When you first start the monitor, the prompt looks like an asterisk (*). From this prompt, enter an exclamation mark (!) and press ENTER. This will take you into the assembler, a mode which is so powerful and awesome that you can simply enter in assembler mnemonics, and they will be assembled on-the-fly into machine-language instructions in RAM. Definitely one of the great things about the Apple II.
Now that you're in the assembler, the prompt looks like an exclamation mark. You can now start typing in assembler instructions, but the assembler defaults to memory location 0, which might not be the best place to start putting your own programs. Instead, let's use memory address $300, which is often used to store short machine-language programs like this. With our first line, we'll specify that the program should start at $300. We can do this by typing the following at the ! prompt:
This places an LDA $C030 instruction (which causes the accumulator to load whatever is located at memory location $C030) into memory address $300. Note that in this case, it's not the actual value at $C030 that we're interested in. What this instruction loads the accumulator with is actually irrelevant; we're just reading from $C030 to make the speaker click.
Now that we've typed this line, the assembler automatically remembers what address the last instruction stopped at, so we don't have to keep typing the addresses for each line. We can simply tell the assembler to continue at the next available memory location by preceding each assembler instruction with a space. So type the following lines, and precede each with a space (they're actually written here with a space preceding them, but I explicitly mention this in case you didn't notice):
LDX #$0A DEX BNE $305 JMP $300
This concludes our program. After the first line which clicks the speaker, the program loads index register X with a value of $0A. (By the way, you can change this value to change the pitch of the note that the program plays.) The following line decrements index register X (i.e. it decreases the number stored in that register by 1). The BNE instruction checks to see if the register has dropped all the way to zero yet, and if not, to branch to $305 (which is the location of the DEX instruction) so that X gets decremented again. The DEX and BNE instructions are a simple delay loop: They will stall the CPU until index register X goes all the way to zero. By increasing the value that gets LDXed, you can make this loop take more time, and vice-versa. Finally, when X reaches zero, the BNE instruction no longer branches, and the program reaches a jump back to $300, where the program begins again, clicking the speaker another time. When this happens quickly enough, the clicking sounds merge together and form a tone.
Now that we've entered our program, just press ENTER on a blank line to go from the ! prompt back to the original * prompt. Here, type 300G (which tells the monitor to run the machine-language program beginning at $300) and you should hear a tone.
In terms of this style of soundmaking, further information than this is beyond the scope of this article, as using this technique is more a matter of multitasking and synchronized programming, rather than actual sound programming.
(Note for those using an actual Apple IIgs: These instructions were perfected using an Apple IIgs emulator. Although they have been tested as working on a real Apple IIgs, there are some quirks. For starters, these programs seem to rarely work if the System Speed setting in the Control Panel is set to Fast. Try setting it to Normal instead. I suspect there is some timing issue that causes the computer to run through these instructions too fast, but I am not sure where to insert a delay, since the programs work every time on an emulator. Even after setting the System Speed to Normal, I never got the programs to work the first time, but I could get them to run by simply typing RUN several times at the BASIC command prompt. Eventually, some marginal factor seemed to click into place. Also, you may want to use a lower volume than the maximum of 255 I use here, since on a real Apple IIgs, this setting is very loud!)
The "gs" in the Apple IIgs' name stands for "graphics and sound". Appropriately enough, the IIgs has vastly expanded capabilities over its predecessors on both of these fronts. In addition, because the IIgs is designed to be entirely backward-compatible with its preceding Apple II brethren, it does in fact support the speaker-clicking trick described above. The rest of this article will focus on the unique sound system of the Apple IIgs and how to create sound with it.
The sound chip used in the Apple IIgs is the Ensoniq 5503 DOC (Digital Oscillator Chip). It is the same chip used in several professional-quality sound synthesizers. This sounds pretty good up front, because it suggests that this chip has great sound capabilities, and indeed it does, but this chip brings with it a level of complexity that makes it more difficult to make sounds on than most computer sound chips of the day.
The first thing you should probably know about the 5503 is that it's a sampling synthesizer. It can only work with "patches", which are actual digitized sound samples. It has its own internal memory in which it can store these samples. This is partly a good thing, because it means that you can copy real digitized sound data directly into the chip and have it play back the sound on command. However, this also makes it more difficult to quickly play a simple sound with the chip, since you need to create a waveform and copy it to the 5503 before it can make any sound at all; unlike the famous SID chip used in the Commodore 64, the 5503 does not permit you to create quick synth sounds with a few POKEs to the chip's registers.
The second thing you should probably know is how the Apple IIgs interfaces with the 5503. In short, the Apple IIgs has no direct connection with the 5503; instead, the two are only tenuously connected through an I/O chip called, appropriately enough, the GLU (General Logic Unit). Sending data to the 5503 from the CPU (whether you're sending configuration commands or waveform data) is a matter of sending it one byte at a time through the GLU. This is quite clunky and annoying, but it does mean that the sound system is quite independent of the CPU, such that once you get the 5503 set up and creating sound, it can keep doing so without a lot of intervention from the CPU. In fact, the 5503 may keep on playing music or sound effects even if the CPU crashes. This makes sound programming somewhat easier than on other Apple II computers (on which it was necessary to intersperse speaker-click instructions with all the other stuff the program needed to do). With the IIgs, you can simply send the 5503 a quick instruction to play some sound already stored in its memory, and it will do so, allowing the CPU to then focus on other stuff.
Doing a quick demonstation program with the 5503 which plays a simple sound is essentially a two-step process: First, you need to load the sound sample into the synth sample memory. Secondly, you need to set the registers on the 5503 chip itself to start playing that sound. As such, I'll go ahead and cover these topics one at a time.
To transfer digital sound data into the memory reserved for the 5503 on the Apple IIgs, you need to go through the following steps:
1. Select the appropriate patch memory location by writing its number to Apple IIgs memory locations $C03E (decimal 49214) and $C03F (decimal 49215). These locations are the low and high bytes (respectively) of the byte in the patch memory you want to write to.
2. Output a byte to location $C03C (decimal 49212) with bit 6 set to 1. This is necessary to let the 5503 know that you are writing to its sound sample memory, not setting a register. (If bit 6 is 0, this signifies a write to a register.)
More specifically, I usually send a value of $4F (79 decimal) to this location. This is the IIgs' sound control register, and it takes the following basic format: The first bit indicates whether the sound system is busy or not, with a 1 meaning busy, 0 meaning not busy. Obviously this should be set to 0. The next bit, as mentioned, specifies whether you want to write to a register on the 5503 or to its waveform RAM; again, a 1 means a sample write. The next bit enables or disables auto-increment of the 5503's address pointer; I usually leave this at 0 so I can manually set each location. The next bit must always be 0. Finally, the last 4 bits collectively form a master volume control for the 5503; the higher the number here, the louder the output. So for a basic register write, the first 4 bits are all 0, and the last 4 bits can be whatever you want, but I'll use 1111 for the loudest output. The resulting value is 01001111 in binary, which, again, translates to $4F or 79 in decimal.
3. Output the actual data byte you want to send to the patch memory to the Apple IIgs memory location $C03D (decimal 49213).
Now that we've taken a broad view of the basic steps needed to write to the 5503's sample memory, let's look at a simple program to load the simplest-possible square wave sample into the memory:
10 FOR A = 0 TO 127 20 POKE 49214,A 30 POKE 49215,0 40 POKE 49212,79 50 POKE 49213,1 60 NEXT A 70 FOR A = 128 TO 255 80 POKE 49214,A 90 POKE 49215,0 100 POKE 49212,79 110 POKE 49213,255 120 NEXT A
The lines from 10 to 60 fill in bytes 0 to 127 of the sample memory with values of 1. This is the lowest-possible value you can program into sample memory with the 5503, since a value of 0 in the sound table data will make the oscillator on the chip halt.
The lines from 70 to 120, likewise, load sample memory locations 128 to 255 with values of 255. Since these synth memory locations are 8 bits wide, this is the largest value you can put in.
The result is a 256-byte string of sample data, which is the smallest sample size the 5503 chip supports. Notice that the first 128 bits of the sample are filled with the lowest-possible values, while the last 128 bits are filled with the highest-possible values. This simply ends up creating a square wave in the sample memory, since the values jump immediately from the extreme-low to the extreme-high.
If you want to create a somewhat softer sound than the square wave, you can, of course, use a sine wave. Credit and thanks to SheppyWare's AmperSound GS program for information on this technique. The AmperSound program uses the following line of code:
FOR A=0 TO 255:POKE 8192+A,128+127*SIN(A/(128/3.1415926)):NEXT A
This line doesn't actually load the sine wave into patch memory; rather, it simply loads it into the Apple IIgs' memory at location $2000 (8192 decimal). AmperSound then uses its own "load from IIgs RAM to patch RAM" routine to transfer the sample. However, since we're doing this using pure POKEs instead of third-party binary routines, here's a short program that creates the sine wave and then transfers it to the patch memory:
10 FOR A=0 TO 255:POKE 8192+A,128+127*SIN(A/(128/3.1415926)):NEXT A 20 FOR A=0 TO 255 30 POKE 49214,A 40 POKE 49215,0 50 POKE 49212,79 60 POKE 49213,PEEK(8192+A) 70 NEXT A
Setting a register on the 5503 with the Apple IIgs entails the following steps:
1. Select the appropriate register by writing its number to memory locations $C03E (decimal 49214) and $C03F (decimal 49215). These locations are the low and high bytes (respectively) of the register on the 5503 you want to set.
2. Output a byte to location $C03C (decimal 49212) with bit 6 set to 0. This is necessary to let the 5503 know that you are setting a register, not writing to its internal waveform memory. (If bit 6 is 1, this signifies a write to waveform memory.)
More specifically, I usually send a value of $0F (15 decimal) to this location. This is the IIgs' sound control register, and it takes the following basic format: The first bit indicates whether the sound system is busy or not, with a 1 meaning busy, 0 meaning not busy. Obviously this should be set to 0. The next bit, as mentioned, specifies whether you want to write to a register on the 5503 or to its waveform RAM; again, a 0 means a register write. The next bit enables or disables auto-increment of the 5503's address pointer; I usually leave this at 0 so I can manually set each location. The next bit must always be 0. Finally, the last 4 bits collectively form a master volume control for the 5503; the higher the number here, the louder the output. So for a basic register write, the first 4 bits are all 0, and the last 4 bits can be whatever you want, but I'll use 1111 for the loudest output. The resulting value is 00001111 in binary, which, again, translates to $0F or 15 in decimal.
3. Output the actual data byte you want to send to the register to memory location $C03D (decimal 49213).
As an example, here are a couple of actual register-setting procedures that you can use to control an oscillator that is already running.
First, here's how to control the volume of an oscillator. The 5503's volume register number for any given oscillator can be calculated by taking the number $40 (decimal 64) and adding the number of the oscillator to it. For example, the volume register for oscillator 0 is located at $40, the volume register for oscillator 1 is located at $41, etc. The 5503 has 32 oscillators, so the final volume register, for oscillator 31, is located at $5F (decimal 95).
Knowing this, we can then set the volume of, say, oscillator 0 through the following sequence of POKEs:
POKE 49214,64 POKE 49215,00 POKE 49212,15 POKE 49213,100
The first two lines of this program select oscillator 0's volume register. The register's number is 64 in decimal, so we set the least significant byte to be 64. Obviously, the most significant byte is 0. Next, we set the 5503's control register to 15 decimal (00001111 binary). Finally, we output the value 100 (a fairly arbitrary value) to the volume register. The volume registers are 8 bits, meaning the value you send to them can be anywhere from 0 to 255.
In a similar way, let's set the frequency registers for an oscillator. Each oscillator on the 5503 has two frequency registers (one forms the most significant byte, the other forms the least significant byte). These registers control the sampling frequency at which the oscillator runs. For each oscillator, the least-significant-byte frequency register is simply the number of the oscillator (so the LSB registers are numbered from $00 to $1F), and the most-significant-byte frequency register is the number of the oscillator plus $20, meaning that the MSB registers are numbered from $20 to $3F. Knowing this, we can pretty easily deduce that the LSB frequency register for oscillator 0 is register 0, and the MSB frequency register for oscillator 0 is $20 (decimal 32). Knowing this, we can use the following sequence of POKEs to change the LSB frequency register of oscillator 0, for a small change in its frequency:
POKE 49214,0 POKE 49215,0 POKE 49212,15 POKE 49213,50
Similarly, the following will change the MSB frequency register of oscillator 0, for a much larger change in its frequency:
POKE 49214,32 POKE 49215,0 POKE 49212,15 POKE 49213,50
Both of these code snippets POKE a value of 50 into the frequency registers; this is, again, a fairly arbitrary value, and you can go ahead and experiment with different values to see what you come up with.
Because setting registers using four POKEs is tedious and repetitive, I wrote the following subroutine to set a register on the 5503:
POKE 49214,REGGY POKE 49215,0 POKE 49212,15 POKE 49213,WENLAV RETURN
Before calling this subroutine, you need to set two variables: REGGY is the number of the register you're setting, and WENLAV stores the new value you want to set it to. WENLAV is "NEW VAL" with both words inverted, because both "NEW" and "VAL" are reserved Applesoft BASIC keywords, and thus using either of those actual words would have caused the program to fail.
Once you actually get a sound sample loaded into the 5503's memory, you need to set a few registers before it'll actually start playing the sound. Here's a brief overview of the process:
First of all, the 5503 DOC has a master register called the Oscillator Enable register. This register's number is $E1 (225 decimal, not to be confused with 255 decimal!). Its value is simply the number of oscillators you want to be active at a time, multiplied by 2. By default, only the first oscillator (oscillator number 0) is active, so this register has a default value of 2 in it when the IIgs boots up, but even so, it doesn't hurt to explicitly set this register just to be on the safe side.
Beyond that master register, most of the other registers you need to control are oscillator-specific, meaning each of the 32 individual oscillators on the 5503 has its own set of each register. For the purposes of this simple demo program, we'll stick with the first oscillator, oscillator number 0.
Each oscillator has an Oscillator Control register, which sets a few aspects of the oscillator's operation including whether the oscillator can generate interrupts, and what mode the oscillator is in. To keep things simple and allow the oscillator to run in free-run mode, you can simply set this register to a value of 0. The Oscillator Control register for oscillator 0 has a register number of $A0 (160 decimal).
Each oscillator also has a Volume register. As seen previously, the volume register for oscillator 0 is register number $40 (64 decimal). These registers simply contain numbers from 0 to 255, with 255 being the loudest.
Each oscillator also has a high and a low frequency register. The low-frequency register has a relatively small effect on the oscillator's frequency, while the high-frequency register has a very large impact on the oscillator's frequency. For this demonstation program, we'll ignore the low-frequency register and set the high-frequency register to 1. Higher values mean higher frequencies, but the high-frequency register is so sensitive that even setting it to 1 generates a rather high frequency when using a 256-byte sound sample. The high-frequency register for oscillator 0 is register number $20 (32 decimal).
Each oscillator also has a Wavetable Size register. Again, the smallest sample size the 5503 chip supports is 256 bytes, for which you can set the wavetable size register to a value of 0. The wavetable size register for oscillator 0 is register number $C0 (192 decimal).
Finally, each oscillator has a Wavetable Pointer register, which contains the beginning page number of the oscillator's wavetable. For the purposes of this demo program, we can also set this to 0. The wavetable pointer register for oscillator 0 is register number $80 (128 decimal).
Knowing all this, then, here's a simple program to play a square wave:
10 FOR A = 0 TO 127 20 POKE 49214,A 30 POKE 49215,0 40 POKE 49212,79 50 POKE 49213,1 60 NEXT A 70 FOR A = 128 TO 255 80 POKE 49214,A 90 POKE 49215,0 100 POKE 49212,79 110 POKE 49213,255 120 NEXT A 500 REGGY = 225 510 WENLAV = 2 520 GOSUB 1000 530 REGGY = 160 540 WENLAV = 0 550 GOSUB 1000 560 REGGY = 64 570 WENLAV = 255 580 GOSUB 1000 590 REGGY = 32 600 WENLAV = 1 610 GOSUB 1000 620 REGGY = 192 630 WENLAV = 0 640 GOSUB 1000 650 REGGY = 128 660 WENLAV = 0 670 GOSUB 1000 680 END 1000 POKE 49214,REGGY 1010 POKE 49215,0 1020 POKE 49212,15 1030 POKE 49213,WENLAV 1040 RETURN
Lines 10 to 120 of this program load a square wave into patch memory. Lines 1000 to 1040 are the subroutine to set a register on the 5503 DOC, and the rest of the program simply sets the registers. This program sets the Oscillator Enable register to turn on oscillator 0, then it sets a bunch of settings for oscillator 0: The mode is set to free-run, the volume is set to the maximum, the high frequency is adjusted so that it's not 0, the patch size is set to 256 bytes, and the patch pointer is set to 0. Presto, your IIgs should play a square wave after running this program.
Note that once you turn on the oscillator, there is no way to turn it off in this program, although setting the volume register to 0 would work and be fairly simple to do. If you ran this program from a clean boot, I would probably just turn off the computer to make the sound stop. When you boot back up again, the sound will be gone.
In the same vein, here's a nearly-identical program that simply loads a sine wave instead of a square wave, resulting in a nice sine wave playing through your IIgs' sound synthesizer:
10 FOR A=0 TO 255:POKE 8192+A,128+127*SIN(A/(128/3.1415926)):NEXT A 20 FOR A=0 TO 255 30 POKE 49214,A 40 POKE 49215,0 50 POKE 49212,79 60 POKE 49213,PEEK(8192+A) 70 NEXT A 500 REGGY = 225 510 WENLAV = 2 520 GOSUB 1000 530 REGGY = 160 540 WENLAV = 0 550 GOSUB 1000 560 REGGY = 64 570 WENLAV = 255 580 GOSUB 1000 590 REGGY = 32 600 WENLAV = 1 610 GOSUB 1000 620 REGGY = 192 630 WENLAV = 0 640 GOSUB 1000 650 REGGY = 128 660 WENLAV = 0 670 GOSUB 1000 680 END 1000 POKE 49214,REGGY 1010 POKE 49215,0 1020 POKE 49212,15 1030 POKE 49213,WENLAV 1040 RETURN
Back to the main page