do re bB
the BASICs of batari music
update
Also check out
atari-sound-forger!
background
Music on the Atari 2600 is a little odd. To put it simply,
the way an Atari makes sounds means it can't play the "normal" notes
that make up Western music.
The good news is that batari BASIC makes it pretty easy to experiment
with the sound capabilities of the 2600. It doesn't have built in
sound commands, per se, but it's pretty easy to write the logic to set
the 6 special TIA variables that control what's coming out of your
TV speakers while your bB program is under way.
(Incidentally, everything in this page (except for the code samples)
applies to people coding in ASM as well.)
The 2600 can play two tones at once. It has several different "voices" ("distortions") that either voice can be set to (some good for music, some good for sound or drum effects), and each voice is set to one of 32
values that control the pitch.
This page can't make a musician out of you...it will help if you know
the very basics of music:
what notes are, and how letters are used to describe notes and all that stuff. Also, this page will mostly discuss music, not ideas for Sound Effects, which have to share the same two voices.
theory
Why does an Atari play "different" notes than everything else?
I'm no sound scientist, and not
much of a musician, but here's the general idea as I understand it: the
"pitch" of a tone (how high or low it sounds) depends on its "frequency".
The higher the frequency, the higher the note sounds. If the frequency is twice as fast, the note is one octave higher (from middle C to high C, say.)
The math for going from one to another can be complex -- see
this page for an explanation. But
the Atari takes a shortcut...it takes a base frequency and then divides it by
between 1 and 32. So they don't quite "match up" to "real" notes, because it
uses a different set of differences of the frequencies.
practice
There are 6 variables, 3 for each of the 2 voices. The ones for
the first voice end in "0", the ones for the other voice end in "1":
AUDV0/AUDV1: volume. Set between 0 (for "off") and 15 (maximum)
AUDF0/AUDF1: frequency. Set between 0 ("higest") and 31 ("lowest")
AUDC0/AUDC1: channel. The "type" of voice, 0 to 15.
Here's how Glenn Saunders
describes the various channels:
DECIMAL
DISTORTION
VALUE WHAT IT SOUNDS LIKE
---------- ---------------------
00 & 11 TOTALLY SILENT
01 Buzzy tones
02 Carries distortion 1 downward into a rumble.
03 Flangy wavering tones, like a UFO
04 & 05 Pure tones
06 & 10 Inbetween pure tone and buzzy tones (Adventure death uses this)
Maybe filters off the highs here
07 & 09 Reedy tones, much brighter, down to Enduro car rumble
08 White noise/explosions/lightning, jet/spacecraft engine
12 & 13 Pure tones, goes much lower in pitch than 04 & 05.
14 Electronic tones, mostly lows, extends to rumble.
15 Electronic tones, mostly highs, extends to rumble.
So as "Batari" puts it himself:
setting the values, for instance, by AUDV0=10:AUDC0=12:AUDF0=4 will produce a tone, and
it will stay on until you set AUDV0=0. typically, a frame counter is set up that keeps
sounds on for a specified number of frames (which occur 60 times a second.)
pitch picking
So, assuming you have a certain set of notes you'd like to hear on your Atari,
what values should you push into AUDF#?
There are two ways to go about this. If you want to match the
actual pitch (for example, if you were trying to use an Atari as an instrument
to be played along with some other instruments), you might use Eckhard Stolberg's
Atari 2600 VCS Sound Frequency And Waveform Guide.
(local mirror)
It tells you what frequencies to use for what notes on what channels and how far off your tuning will be.
However, most human ears (the ones without "perfect pitch") don't care what the
actual frequencies are, just as long as the
relative notes fit
each other. Thomas Jentzsch made a rough but powerful tool called "Tune2600". Originally it was a DOS-ish command line tool but I've given it a new front
end and put it on the web:
webTune2600. It uses a Piano-like UI and, thanks to B. Watson, a guitar-based one as well.
Enter the notes you'd like to hear into the UI and then hit "Calculate Matching 2600 Values". You'll see a table like the following...
Note |
Optimal |
TIA |
Cents Off |
Dist (AUDC_) |
Pitch (AUDF_) |
c2 |
32.6 |
32.7 |
6.9% |
6 |
30 |
d2 |
36.4 |
36.2 |
-9.8% |
6 |
27 |
e2 |
40.7 |
40.6 |
-5.6% |
6 |
24 |
g2 |
48.1 |
48.3 |
8.5% |
6 |
20 |
-
The first column is the note name. (The letter is the name, the number is
what octave it's in.)
- "Optimal" is the desired frequency for this tuning, TIA is what the Atari will actually produce
- "Cents Off" isn't a coupon, it's how far off the note is.
- "Dist" is the distortion channel to use,
what to put in AUDC0 or AUDC1.
For better or worse, the distortion value can change the range of notes we're playing...(Actually, if you get more than one value in this column, you
might want to try going back to the UI, hitting "Show Options",
and then changing the value in the "Octaves" dropdown. Sometimes that causes
Tune2600 to use different values which might lead to using
the same distortion for all notes. That can make life
easier for our coding, because then we don't have to worry about changing AUDC0 or AUDC1 as we play the song.)
- Finally, "Pitch" is the value to put in the frequency register, either AUDF0 or AUDF1.
One final thing: Europe and the USA use different standards for their
TV pictures. The North American standard is "NTSC", and the European
standard is "PAL". For music purposes, the important thing to know
is that NTSC updates the screen (and therefore, bB does everything
outside the kernal) at 60 frames a second, and PAL runs at 50 FPS.
This means their tuning can be different. (Under "Options", Tune2600
can use either standard.)
got the beat
Once we know what distortion and frequency to use (and we pick a volume
to play...I
usually use 8 for AUDV0 or AUDV1, in the middle of the volume range)
then we just need to know how long to play each note.
I'm goint to use the NTSC standard here, 60 FPS vs. PAL's 50. (PAL will tend to be a bit slower and lower if the programmer doesn't especialy code to compensate.)
60 FPS means that if we hold a tone for 60 frames (e.g.. calling bB's
drawkernal routine) a note will last one second.
One popular modern tempo for music is "120 BPM" or Beats Per Minute,
a nice fast pace. So, lets do the simple math to see how many frames a beat will last:
X frames = 1 beat * 1 minute * 60 seconds * 60 frames
120 beats 1 minute 1 second
In this case, X is 30. In other words, the number of frames for a single beat is (3600 / # of BPM). (300 / BMP for PAL.)
Of course, two notes in a row without a break between them will
sound like one big note. Therefore, at least for repeated notes
you may want to put in a period of silence between...say,
28 frames of AUDV0 = 8, then 5 frames of AUDV0 = 0 for silence.
all together now
Now we should be able to make a simple music engine in batari BASIC...here's
a simple engine I came up with. The parts that we'll have to customize for the new music are in red.
dim musicPointer=a
dim musicTimer=b
rem VOLUME DOWN AND SET CHANNELS
AUDV0=0
AUDC0=DISTORTION
rem INITIALIZE POINTERS AND TIMERS
musicPointer=0
musicTimer=0
startLoop
rem TIME TO UPDATE NOTE?
if musicTimer = 0 then gosub changeMusicNote
musicTimer = musicTimer - 1
drawscreen
goto startLoop
changeMusicNote
AUDF0 = musicData[musicPointer]
if musicData[musicPointer] = $FF then AUDV0 = 0 else AUDV0 = 8
musicPointer = musicPointer + 1
musicTimer = musicData[musicPointer]
musicPointer = musicPointer + 1
if musicPointer > (number_of_notes * 2) - 1 then musicPointer = 0
return
data musicData
LIST OF FREQUENCY, LENGTH
end
|
(With this engine, putting "-1" for a note's frequency means "silence",
setting AUDV0 to 0.)
So, lets pick a simple tune...previously we used webTune2600 to get c, d, e, and g, which are the notes we need for "Mary Had A Little Lamb". Here's the tune with the lyrics and notes:
Mary had a little lamb,
E D C D E E E
little lamb, little lamb
D D D E G G
Mary had a little lamb,
E D C D E E E
Its fleece was white as snow
E D D E D C
Replacing notes with the AUDF0 values:
Mary had a little lamb,
24 27 30 27 24 24 24
little lamb, little lamb
27 27 27 24 20 20
Mary had a little lamb,
24 27 30 27 24 24 24
Its fleece was white as snow
24 27 27 24 27 30
Every syllable in the song takes about a beat, except for
some of the "lamb"s and "snow" which take 2 or 4.
So the lengths are:
Mary had a little lamb,
30 30 30 30 30 30 60
little lamb, little lamb
30 30 60 30 30 60
Mary had a little lamb,
30 30 30 30 30 30 30
Its fleece was white as snow
30 30 30 30 30 120
Merging those into the note frequency, note length format the engine uses:
24,
30,
27,
30,
30,
30,
27,
30,
24,
30,
24,
30,
24,
60
27,
30,
27,
30,
27,
60,
24,
30,
20,
30,
20,
60
24,
30,
27,
30,
30,
30,
27,
30,
24,
30,
24,
30,
24,
30
24,
30,
27,
30,
27,
30,
24,
30,
27,
30,
30,
120
One final issue: when we have two notes of the same frequency in a row, they sound like one big note, so lets fix that by taking 2 frames off of each note that's the same pitch as the next note and making them a rest (rests are indicated by "-1" for the value.)
24,
30,
27,
30,
30,
30,
27,
30,
24,
28,-1,2,
24,
28,-1,2,
24,
60
27,
28,-1,2,
27,
28,-1,2,
27,
60,
24,
30,
20,
28,-1,2,
20,
60
24,
30,
27,
30,
30,
30,
27,
30,
24,
28,-1,2,
24,
28,-1,2,
24,
28,-1,2
24,
30,
27,
28,-1,2,
27,
30,
24,
30,
27,
30,
30,
120
Now all we need is to count the notes and rests...35, or 70 pieces of data, one less than that is 69 (heh heh) and that's the value we plug into the loop. So here's the entire program:
dim musicPointer=a
dim musicTimer=b
rem VOLUME DOWN AND SET CHANNELS
AUDV0=0
AUDC0=6
rem INITIALIZE POINTERS AND TIMERS
musicPointer=0
musicTimer=0
startLoop
rem TIME TO UPDATE NOTE?
if musicTimer = 0 then gosub changeMusicNote
musicTimer = musicTimer - 1
drawscreen
goto startLoop
changeMusicNote
AUDF0 = musicData[musicPointer]
if musicData[musicPointer] = $FF then AUDV0 = 0 else AUDV0 = 8
musicPointer = musicPointer + 1
musicTimer = musicData[musicPointer]
musicPointer = musicPointer + 1
if musicPointer > 69 then musicPointer = 0
return
data musicData
24,30,27,30,30,30,27,30,24,28,-1,2,24,28,-1,2,24,60
27,28,-1,2,27,28,-1,2,27,60,24,30,20,28,-1,2,20,60
24,30,27,30,30,30,27,30,24,28,-1,2,24,28,-1,2,24,28,-1,2
24,30,27,28,-1,2,27,30,24,30,27,30,30,120
end
|
And thus Mary has her little lamb! Exciting, huh? It's more bass-y than I expected, but I like it.
If you're too lazy to compile and run that, here's the
binary image of it.
Of course, you can use this same basic engine idea for both voices...
A while back I ported the music I made for the game "JoustPong"...it uses one voice for a
simple bassline and the other voice for a bit of drum. Here's the
source code for that, and the
binary image - a decent example of running two music loops at once, one for each channel.
next steps
Well, now you know all the basics of 2600 music. The example engine I gave you is flexible, but limited: it doesn't
change distortion values or volume. (You would have to give up ROM space or use another RAM counter, or both, to
be able to change those on the fly.) Also, there might be ways of making it more efficient for certain tunes
or other applications. Another idea would be to decrease the note volume each frame, that makes a nice,
somewhat less "computer music" sound.
There are other neat tricks too, like playing the same notes at the same time with different distortion values, or
faking two meoldies at once by switching between them rapidly.
Paul Slocum's
sequencer kit has some neat stuff, including the
2600 Sound And Music Programming Guide (local mirror). He's more focused on "real musicians" but he's done some great stuff...check out the rest of his site!
back to the batari BASIC page