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:
300:LDA $C030
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.
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.
Unfortunately, creating sound waveforms and readying them within the 5503 is a complicated task. I have not successfully figured out how to do this yet, meaning that as of right now, this page is incomplete. I hope to be able to add this information later, but for now, I do have the following to add.
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.
That's all for now... Hopefully more will come later.