Jump to content

TROGBlog

  • entries
    47
  • comments
    219
  • views
    125,176

wav2atari.pl


TROGDOR

4,491 views

I have a habit of checking out the 2600 programming forums every week or so, and two weeks ago I started commenting on the topic Advanced sound techniques: how do they work? My interest in this topic was mostly in sample playback. I've been messing with digital sound samples since 1990, and I was very impressed when I found out a couple years ago that the 2600 was capable of playing back decent quality sound samples. My only exposure to this was the Berzerk Voice-Enhanced, which is a thing of beauty (and a joy forever.)

 

While responding to that topic, I mentioned if I couldn't find a decent program to translate 8-bit PCM wav files into 4-bit Atari format, I'd try writing one myself. Well, tonight after I put the kids to bed, I had a couple beers and set to work. An hour later, I had wav2atari.pl:

 

use Getopt::Long;

$result = GetOptions ("wav=s", \$wavfile, "outfile=s", \$outfile, "help", \&usage);

sub usage {
print "\n$0 -wav <wav_file> -outfile <out_file> -help

This program reads in the specified wav file.  The sound data is translated
into a 4-bit Atari format that is dasm readible.  Two samples are encoded
into each byte of output.  A comment is placed every 256 bytes to denote
a new page in Atari ROM.

Note that the first of the two samples goes into the lower nibble.  This
works optimally in Atari code.  The first sample can be written directly to
AUDV0, since the register will strip off the higher 4 bits automatically.
Then the second sample can be written to AUDV0 after 4 LSRs.  This prevents
the need for temp variables and saves precious cycles during playback.

The asm data is written to the specified -outfile.  RIFF PCM header
information is written to STDOUT.

Currently only 8-bit PCM wav files are supported.\n";

exit;
}

#$wavfile = shift;

if ($wavfile eq "") {
die "ERROR: Must provide input wave file.\n";
}

open (WAV, $wavfile)
or die "Could not open wavfile '$wavfile'.\n";

# Read ChunkID
read (WAV, $data, 4);
if ($data ne "RIFF") {
die "ERROR: This doesn't appear to be a RIFF PCM WAV file.\n";
}
print "      ChunkID = $data\n";

# Read ChunkSize
read (WAV, $data, 4);
$data = unpack ("V1", $data);
print "    ChunkSize = $data\n";

# Read Format
read (WAV, $data, 4);
print "       Format = $data\n";
if ($data ne "WAVE") {
die "ERROR: This is a RIFF file, but it doesn't appear to be a WAVE file.\n";
}

# Read Subchunk1ID
read (WAV, $data, 4);
print "  Subchunk1ID = $data\n";

# Read Subchunk1Size
read (WAV, $data, 4);
$data = unpack ("V1", $data);
print "Subchunk1Size = $data\n";

# Read AudioFormat
read (WAV, $data, 2);
$data = unpack ("v1", $data);
print "  AudioFormat = $data\n";

# Read NumChannels
read (WAV, $data, 2);
$data = unpack ("v1", $data);
print "  NumChannels = $data\n";

# Read SampleRate
read (WAV, $data, 4);
$data = unpack ("V1", $data);
print "   SampleRate = $data\n";

# Read ByteRate
read (WAV, $data, 4);
$data = unpack ("V1", $data);
print "     ByteRate = $data\n";

# Read BlockAlign
read (WAV, $data, 2);
$data = unpack ("v1", $data);
print "   BlockAlign = $data\n";

# Read BitsPerSample
read (WAV, $data, 2);
$data = unpack ("v1", $data);
print "BitsPerSample = $data\n";

# Read Subchunk2ID
read (WAV, $data, 4);
print "  Subchunk2ID = $data\n";

# Read Subchunk2Size
read (WAV, $data, 4);
$data = unpack ("V1", $data);
print "Subchunk2Size = $data\n";

# Read in all the data.

$data_byte_count = 0;
$page_count = 0;
$output = "";
while (read (WAV, $data, 2)) {
@data = unpack ("C2", $data);

if ($data_byte_count % 512 == 0) {
	$output .= "; Page $page_count\n";
	$page_count++;
}

# If there is an odd number of bytes in the sample, drop the last byte.
if ($data[1] eq "") {
	print "Warning: last byte of sound data was ignored because this file\n";
	print "contains an odd number of sound bytes.\n";
	$data_byte_count++;
	last;
}

# Note, the first of the two bytes goes into the lower nibble.  This works
# optimally in Atari code.  The first sample can be written directly to
# AUDV0, since the register will strip off the higher 4 bits automatically.
# Then the second sample can be written to AUDV0 after 4 LSRs.  This prevents
# the need for temp variables and saves precious cycles during playback.

#strip off the lower 4 bits.
$lownibble = $data[0] >> 4;

#shift 4 bits to the right, effectively stripping off the lower 4 bits.
$highnibble = $data[1] & 240;

$condensedbyte = $highnibble + $lownibble;
$output .= sprintf ("	.byte #%%%08b\n", $condensedbyte);

$data_byte_count += 2;
}

print "$data_byte_count bytes of sound data processed.\n";

close WAV;

open (OUTPUT, ">$outfile");
print OUTPUT $output;
close OUTPUT;

I had expected this program wouldn't be too difficult to write. The guts of the program is only about 10 lines. The rest is just spitting out the RIFF header info.

 

For those who aren't familiar with a .pl file, it's a perl script. You'll have to have a perl interpreter installed on your system to use the script. But it should be easy to translate this program into your scripting language of choice if you don't happen to use perl.

 

 

My quest to get my own samples working on the Atari started on Google. I quickly found a nice spec for the PCM wave format here. Next I grabbed a small random wav from Google to use as my test subject:

 

hello.wav

I'm pretty sure this particular hello is Graham Chapman from Monty Python and the Holy Grail. All the better.

 

After I fed this wav file through wav2atari.pl, I made some modifications to my unsound wave generation demo, and then some more tweaking, and suddenly, it worked! Hearing this sample play back correctly in a 2600 emulator is one of the most satisfying moments I've ever had programming for the 2600. :)

 

The atari binary still needs work. I just threw this together, so the sample playback rates are not perfectly balanced (the delay between the low nibble sample and the high nibble sample should be exactly the same, but they're not), yet it still sounds pretty good.

 

If I get the time and energy, I'll enhance wav2atari to work on 16-bit samples, and add a downsampling option so you can specify the output sample rate. I also need to clean up the playback asm so the delays are balanced.

 

The zip file I'm including below contains the wav2atari.pl script, the HELLO.BIN Atari binary, the hello.asm dasm assembly file, and the original hello.wav file for comparison. Enjoy!

 

HelloWorld.zip

Here's another demo that varies the pitch of the sample:

 

HELLO2.BIN

Here's the modified source code:

 

hello2.txt

The next thing to do is get a looping 256 byte guitar wave. :cool:

  • Like 1

10 Comments


Recommended Comments

Nice Stuff! I ain't no programmer but i had fun playing around with your program and i even managed to make a working .bin myself.. ;)

I hope you keep working on it!

Link to comment

Thanks Impaler. I've added a variant that demonstrates high resolution variations in pitch. This uses a NOP jump table to create delays with 2 cycle resolution. If necessary, it's also possible to get single cycle resolution using a C9 jump table. These subtle variations in delay change the playback rate of the sample, which alters the pitch of the sample.

 

I couldn't figure out how to attach it to this comment, so I put it at the end of the blog post above.

Link to comment

Nice work - the quality is way better than I was expecting! It looks like there is plenty of room for optimisation in the code - it would be great to have a playfield display during playback, e.g. for a title screen?

 

Chris

Link to comment

Thanks Chris. Playfield display with samples is possible, but you'd have to have a separate block of code to handle the kernel and the off-screen code to keep everything in sync. Note also that this would work for constant-rate samples, but would not be possible for variable-rate playback, unless you do something complex like wave tables.

 

I've been working on making a song with a guitar sample, but I'm finding a surprising amount of aliasing coming from the 16-bit to 8-bit downsampling. I expected aliasing from a reduction in sample rate, but I wasn't expecting it from a reduction in bit resolution. I'm going to consult some audiophile forums to see if any pre- or post-processing is possible to prevent this downsample aliasing. It adds too much distortion. (Then again, this wouldn't be a problem if your goal was a distorted guitar. ;) )

Link to comment

Hey! Nice little bit of code you got here! I tried getting it working last night, but I think I'm getting a bit too greedy on my wave sizes. I'm also trying to incorporate it into Music Kit 2 for fun. Dunno if I'll be successful, but I'll give it a whirl. I know I'll have a lot of hacking to do regarding pretty much everything... We'll see.

 

Any tips, suggestions, insight, or motivational speech? ;P

Link to comment
I've been working on making a song with a guitar sample, but I'm finding a surprising amount of aliasing coming from the 16-bit to 8-bit downsampling. I expected aliasing from a reduction in sample rate, but I wasn't expecting it from a reduction in bit resolution.

Quantization noise - the usual way to improve it is to add a noise signal (less than 1 bit of the output resolution) to the input before the requantization. Unfortunately, this only really works at high sample rates. Low sample rates & low resolution = low fidelity signal.

Link to comment

Thanks B00daW. I'd suggest getting a small, simple sample working first, and then grow from there.

 

I've discovered that it's tough to do anything longer than 2 seconds without bank switching. I squeezed out another sample last month.

 

EricBall, thanks for the info. I'll do more research on it as time allows. The Hello sample seemed reasonably clear, even though it was down-sampled from 8 bits to 4 bits.

Link to comment
I expected aliasing from a reduction in sample rate, but I wasn't expecting it from a reduction in bit resolution.

 

In my BTP2 music driver, every pitch is a power-of-two multiple of a submultiple of the output sample rate (15.75KHz, matching horizontal scan). This means that any unwanted frequencies resulting from quantizing noise will be "on pitch". For example, when outputting the top C (about 2096Hz) there will be some distortion noise at 1048Hz, but since 1048Hz is also a "C", it's not particularly objectionable.

 

I was somewhat surprised to discover, when I was simulating a more sophisticated synthesis engine, that there is surprisingly little room for improvement in my BTP2 driver. One could offer a somewhat better selection of timbres, but trying for more precise frequency outputs ends up making things sound worse because it disrupts the regular pattern of the quantizing noise.

Link to comment

How can I open the perl program?, I have already strawberry perl, but when I try to run it nothing happens at all, please help me.

Link to comment

How can I open the perl program?, I have already strawberry perl, but when I try to run it nothing happens at all, please help me.

That question was pretty dumb, I figured it out immediately, after posting it, however I've had a question in mind for a long long time now, how can I add the asm routine onto my bB code and have the sound running at the same time as the title screen? Thanks.

Link to comment
Guest
Add a comment...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...