Episode 12: Sound (I)
Introducing “Studio2600”
Our game is still missing sound, but we need to know first what sounds are all about on the VCS. The information provided in the Stella Programmer's Guide is a bit scarce and enigmatic, so what about a tiny test application to explore the sound capabilities of the VCS?
TIA Sound
The TIA provides two separate, but identical sound channels or "noise-tone generators", which are mixed as a single mono audio source into the RF output of the console. There are 3 parameters per sound: Tone, frequency and volume. Accordingly, there are 3 registers for each of the two sound generators, a 4 bit audio control registers (AUDC0, AUDC1), a 5 bit audio frequency register (AUDF0, AUDF1), a 4 bit audio volume register (AUDV0, AUDV1).
Volume
Volume is strait forward: 4-bits of a control provide for 15 levels of volume and an off state (muted, value 0):
Volume — AUDV0, AUDV1
D3 | D2 | D1 | D0 | Audio Output Pull down current |
---|---|---|---|---|
0 | 0 | 0 | 0 | No output current |
0 | 0 | 0 | 1 | lowest |
0 | 0 | 1 | 0 | |
... | ... | ... | ... | |
1 | 1 | 1 | 0 | |
1 | 1 | 1 | 1 | highest |
Frequency
Frequency is where things becoming a bit quirky. The pitch is provided by a base frequency (30KHz) and divisons of this by the value which has been written to the corresponding control register (AUDF0, AUDF1). Since there are 5 bits, this provides for a scale of 32 distinct frequencies. However, "scale" may be a term rather outrageous in this regard, since these are clearly not chromatic tones and any overlap with western tone scales is purely incidental. Clearly, this was not set up for playing tunes, but for a quick way to do some coarse sound effects. (However, there are a few tones which may be used for a tune.)
Frequency — AUDF0, AUDF1
Hex | D4 | D3 | D2 | D1 | D0 | 30KHz divided by |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | no division |
1 | 0 | 0 | 0 | 0 | 1 | divide by 2 |
2 | 0 | 0 | 0 | 1 | 0 | divide by 3 |
... | ... | ... | ... | ... | ... | ... |
1E | 1 | 1 | 1 | 1 | 0 | divide by 31 |
1F | 1 | 1 | 1 | 1 | 1 | divide by 32 |
Tones / Sounds
Here, we're venturing in some kind of unknown territory, as there is no clear description of what the 16 sounds, apparently selectable by the 4-bit control register, specifically are. The Stella Programmer's Guide states rather vaguely:
The values written cause different kinds of sounds to be generated. Some are pure tones like a flute, others have various "noise" content like a rocket motor or explosion. Even though the TIA hardware manual lists the sounds created by each value, some experimentation will be necessary to find "your sound".
And this is what the cited TIA-1A Hardware Manual has to say on the matter:
This circuit contains a nine bit shift counter which may be controlled by the output code from a four bit audio control register (AUDC), and is clocked by the frequency select circuit. The control register can be loaded by the micro- processor at any time, and selects different shift counter feedback taps and count lengths to produce a variety of noise and tone qualities.
*Hem*
Let's have a look at the register's description:
AUDC0, AUDC1
Hex | D3 | D2 | D1 | D0 | Type of noise or division |
---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | set to 1 |
1 | 0 | 0 | 0 | 1 | 4 bit poly |
2 | 0 | 0 | 1 | 0 | div 15 -> 4 bit poly |
3 | 0 | 0 | 1 | 1 | 5 bit poly -> 4 bit poly |
4 | 0 | 1 | 0 | 0 | div 2 : pure tone |
5 | 0 | 1 | 0 | 1 | div 2 : pure tone |
6 | 0 | 1 | 1 | 0 | div 31 : pure tone |
7 | 0 | 1 | 1 | 1 | 5 bit poly -> div 2 |
8 | 1 | 0 | 0 | 0 | 9 bit poly (white noise) |
9 | 1 | 0 | 0 | 1 | 5 bit poly |
A | 1 | 0 | 1 | 0 | div 31 : pure tone |
B | 1 | 0 | 1 | 1 | set last 4 bits to 1 |
C | 1 | 1 | 0 | 0 | div 6 : pure tone |
D | 1 | 1 | 0 | 1 | div 6 : pure tone |
E | 1 | 1 | 1 | 0 | div 93 : pure tone |
F | 1 | 1 | 1 | 1 | 5 bit poly div 6 |
Apparently, sounds 0, (“set to 1”) and $B (“set last 4 bits to 1”) are for internal functionality only and do not produce an audible signal.
Oh, this was helpfull! — Maybe, there will be, in deed, some experimentation (…) be necessary to find "your sound(s)".
“Studio2600”
Or, It Was to be a Simple Sound Test,
But it Became a Synthesizer Application.
Time to write a tiny test application. All we need is to control 3 values, which will be written to the 3 registers of one of the sound channels (AUDF0, AUDC0, and AUDV0). While we will be updating the frequency and the sound/tone each frame, we're going to set the volume only, when the controller button is pressed. Otherwise we will mute the output (set the volume register to zero), since some of the tones produced may be annoying.
So, in practice, this will be an excersice in UI design for the VCS, in order to access the various audio values in a simple way. This is, what I eventually came up with:
In case, you came under the impression of programming for the Atari 2600 being intimidating, be told that, while it was only my second application for the VCS, this was done in a single day, where I had still time for a nice evening — and to grow a few grey hairs.
Regarding the grey hairs, it may be supposedly a simple thing to put 32 columns of something in a row. At least, there are the playfield graphics. The wide pixels of those will be just suitable, and there are 40 of them. However, if we want to paint the columns in alternating colors to form a distinctive grid, we'll need a second color.
Where will the second color come from? Obviously, the background color may be the way to go. But we'll have to switch it on and off just precisely at the right CPU cycle. On the other hand, there are the legends on the left (we want to know, which sound we're going to pick for our game), and we'll need to iterate over scan lines by an index. Since there is also another sprite for the indicator and we're having to use our high-speed sprite trick yet again, there's also a second indexed memory access involved. While the code fits just barely in a scan line, we have to deal with extra cycles caused by the various indexed lookups, since the height of the display exceeds 127 scan lines. (Including the padding in memory for our high-speed sprite, we're getting in trouble at 64 rows less the height of the sprite in question.)
*Hem*
First, I had some code that maintained just the right cycle count to spill over into the next scan line, without using WSYNC. It was all nice, but as soon as the indicator was moved above a certain row, things came out of sync. Is there a way to deal with the extra cycles (because, we may figure out at what positions they will come into effect on a given row)? No time for this.
In the end, after a few optimizations (now we're using WSYNC, which stabilizes things a bit), I just cut the big loop in halves. No extra cycles anymore, but we have to service two pairs of pointers now. Big deal.
Alternative solutions may have been to either generate the second color by the playfield graphics as well (by interlacing frames with fully painted rows and frames with the alternating pattern), or just to use a two-line kernel approach for this section of the screen and use the delay registers, we discussed earlier.
Another issue was dealing with the particulars of UI design. Finding a set of colors which will work nicely and fairly the same for NTSC and PAL. Doing without red on green, because color blindness is a thing. (So, we won't have a red indicator or switch it to red for visual feedback, when a sound is playing.) — Another few iterations of the compile–test cycle…
However, here it is, live demo and binaries for download (NTSC and PAL/60Hz):
Hey — Our little sound app made it to the frontpage of Hacker News!
▶ Next: Episode 13: Sound (II) — Doing it
◀ Previous: Episode 11: Color TV Systems
▲ Back to the index.
April 2018, Vienna, Austria
www.masswerk.at – contact me.
— This series is part of Retrochallenge 2018/04. —