Jump to content
Crispy

TIA Sounding Off in the Digital Domain

Recommended Posts

Back when I was developing and testing the HDMI interface for my FPGA based Atari 2600, I noticed some minor, and some major differences in sound between the HDMI digital audio and the analog audio. After giving it some thought, I realized what was causing these differences, and I was able to rework my HDMI interface so that the digital audio sounds exactly like the analog audio.

 

For some reason I felt compelled to document my findings, so I sat down and wrote about it. I was going to put it all down in a post here, but I got carried away, and my paper ended up being eleven pages long. So, instead of creating a monster post, I'll just attach a PDF of my write up.

 

I am also including one of the test programs I wrote while investigating the issue. After reading my article, it will make more sense. This program sets up the audio registers to output a 748 Hz pure tone on one channel, and a 10.6 Hz tone on the other. Hold down the GAME SELECT switch to play the 748 Hz tone, and the GAME RESET switch to play the 10.6 Hz tone. Run this program on Stella, and then on a real 2600, and note the difference.

 

TIA Sounding Off in the Digital Domain.pdf

tremolo.bin

  • Like 13

Share this post


Link to post
Share on other sites

Thanks for this; I think it may be able to improve sound output in Stella.

 

Tell me, does the attached ROM sound any better with your improvements? It's one of the best ROMs to clearly show how different the sound generation is in Stella and a real console.

 

phaser06.bin

  • Like 1

Share this post


Link to post
Share on other sites

Thanks for this; I think it may be able to improve sound output in Stella.

 

Tell me, does the attached ROM sound any better with your improvements? It's one of the best ROMs to clearly show how different the sound generation is in Stella and a real console.

 

You're welcome. I was hoping that someone else besides me would get some use out of it.

 

To answer your question, yes, this demo sounds better, in terms of accuracy, when played through the improved HDMI interface. You can definitely hear the absence of the cross modulation I talked about in my paper when playing it through the old HDMI interface. I've attached a zip file with some recordings I made so that you can compare for yourself.

 

Unfortunately, none of these sound like what I'm hearing from Stella, although I do really like the sound of the effect that Stella produces. I'm not sure what the code in this ROM is doing. I'd have to sit down, and take a look at it. I'm wondering if the difference in sound has something to do with the behavior of the clock divider in Stella.

 

Phaser06.zip

  • Like 2

Share this post


Link to post
Share on other sites

Would you be willing to work with me in improving the output from Stella with this ROM (and by association, all ROMs)? I know you're working on your own FPGA, but I'd really appreciate it if you'd take a look at the TIASnd code from Stella:

 

https://sourceforge.net/p/stella/code/HEAD/tree/trunk/src/emucore/TIASnd.hxx

https://sourceforge.net/p/stella/code/HEAD/tree/trunk/src/emucore/TIASnd.cxx

  • Like 2

Share this post


Link to post
Share on other sites

I looked at the Phaser06 code, and found that it is writing to AUDF1 twice in a frame. On line 237, it sets AUDF1 to 15, and on line 253 sets it to 16. How often is TIASound::process() being called? If it's only once per frame then it's missing the mid frame clock divider changes, and that explains why the effect is not audible.

 

I didn't spend a lot of time looking at the TIASnd code, but I did see one thing that did stand out. Stella implements the clock divider counter with the following code.

// Process channel 1
if (div_n_cnt1 > 1)
{
    div_n_cnt1--;
}
else if (div_n_cnt1 == 1)
{
    process the audio
}

However, the clock divider logic is implemented in the TIA in the following manner. Note that div_cnt is actually a 5 bit binary counter which by nature increments from 31 to 0.

unsigned char div_cnt;

if (audio_tick) {
    if ((div_cnt == AUDF1) || (div_cnt == 31))
        div_cnt = 0;
    else
        div_cnt += 1;

    if (div_cnt == AUDF1)
        update_pulse_generator_state();
}

The comparator logic leads to an interesting side effect. If AUDF1 is changed to a value lower than the current count of div_cnt, then div_cnt will continue counting up to 31, and then wrap back to 0. It will then continue counting up to the new value stored in AUDF1 before it triggers a logic update.

 

For example, assume that AUDF1 is 3, and div_cnt is 0. During the time when div_cnt is 2, we set AUDF1 = 1. Now, div_cnt will count up to 31, wrap to 0, and then count to 1 before it triggers the next logic update. After that, the clock divider will continue to function as a divide by 2 divider.

 

For a brief time we tricked the divider into dividing by 34, which shouldn't be possible according to the TIA documentation. The same sort of thing is happening in the Phaser06 code, and unless I'm overlooking something, it appears that Stella is not handling it correctly. I'll look a little closer when I have some more time.

 

 

  • Like 2

Share this post


Link to post
Share on other sites

If you guys can get the E.T. ship landing sound effect to sound like it does on a real Atari 2600, just about every other sound effect will be perfect too since that seems to be one of the hardest ones to get right:

 

youtu.be/Njvf9L_NxHI?t=33s

  • Like 2

Share this post


Link to post
Share on other sites

Thanks for looking into this, and for any further info you can provide.

 

The TIASound::process() method is actually tied to the sound hardware callback, so it is called as much as necessary to fill the sound buffers. Typically the sound buffers are set to 512 bytes. Note that the writes to the TIA sound registers are queued, and when the sound buffers need more data, the callback looks at the queued writes and basically calls TIASound::process() for the entire range of data it needs.

Share this post


Link to post
Share on other sites

I'd be interested in hearing the Ms. Pacman tune without distortion. Any chance you could share it in a easily shareable format for us more-casual clickers? :)

Edited by NE146

Share this post


Link to post
Share on other sites

I'd be interested in hearing the Ms. Pacman tune without distortion. Any chance you could share it in a easily shareable format for us more-casual clickers? :)

You can hear this on Stella, since Stella sounds very close to my original attempt at audio. But, in case you wanted to hear the sound on my hardware, I made a couple of recordings. The Ms-Pacman_MMDC_Analog.aiff clip demonstrates the cross modulation distortion, and the Ms-Pacman_MMDC_Old_HDMI.aiff demonstrates additive mixing, which doesn't have the distortion.

 

Ms-Pacman_MMDC_Analog.aiff

Ms-Pacman_MMDC_Old_HDMI.aiff

Share this post


Link to post
Share on other sites

Thanks for looking into this, and for any further info you can provide.

 

The TIASound::process() method is actually tied to the sound hardware callback, so it is called as much as necessary to fill the sound buffers. Typically the sound buffers are set to 512 bytes. Note that the writes to the TIA sound registers are queued, and when the sound buffers need more data, the callback looks at the queued writes and basically calls TIASound::process() for the entire range of data it needs.

 

I don't have a clear view of the overall architecture of Stella, so I may be missing something, but it looks to me like some of the register writes are being missed during the bulk processing that occurs in the TIASound::process() method. Looking at the while loop at line 177, I don't see any mechanisms inside the loop to change div_n_cnt based on a new value of audf0 or audf1.

 

Anyway, looking at the Stella code made me curious, so I took a detour. I translated my Verilog code more or less directly to C, and wrote a test bench to exercise the logic. Turns out it actually works. I've attached the source code in case you want to see it. The test bench mimics the phaser06.bin ROM, and I've also attached an AIFF file encapsulating the raw data that my code generated.

 

Uhm, I'm not permitted to upload C source files?! OK, I'll zip it up then.

MMDC_audio_C.zip

phaser06_MMDC_C_code.aiff

  • Like 1

Share this post


Link to post
Share on other sites

You can hear this on Stella, since Stella sounds very close to my original attempt at audio. But, in case you wanted to hear the sound on my hardware, I made a couple of recordings. The Ms-Pacman_MMDC_Analog.aiff clip demonstrates the cross modulation distortion, and the Ms-Pacman_MMDC_Old_HDMI.aiff demonstrates additive mixing, which doesn't have the distortion.

 

attachicon.gifMs-Pacman_MMDC_Analog.aiff

attachicon.gifMs-Pacman_MMDC_Old_HDMI.aiff

 

I was surprised that Foobar2000 which I usually use to play MP3's was able to play these just fine.. very interesting! I too like the HDMI one better. I''ve always noticed the distortion at the end of the 2600 MsPac intro but assumed that was obviously just the way it was. Pretty cool to hear it without it. :)

Edited by NE146

Share this post


Link to post
Share on other sites

Wowsers, I'm kind of thrilled -- and flattered as heck -- that the only significant (?) thing I've ever written in ASM is being used to iron out accurate sound emulation! :D Here's the original thread with the source code:

 

http://atariage.com/forums/topic/140331-advanced-sound-techniques-how-do-they-work/?do=findComment&comment=1702472

 

RevEng attempted a fix to get it to sound right on real hardware, though I haven't been able to try it myself:

 

http://atariage.com/forums/topic/247042-tia-perceptual-tuning/?do=findComment&comment=3400302

 

And, another thread that might be helpful:

 

http://atariage.com/forums/topic/191275-question-about-music/

 

I'd love to find a way to get real hardware to sound like what Stella does (pending my getting a chance to check out RevEng's work -- maybe he's gotten it).

 

BTW Random Terrain, I'm definitely hearing a 60Hz tone in the E.T. sound that isn't present in Stella's version -- I think that's the most striking difference -- so I wonder if the low hum is an artifact caused by something being updated once per frame, i.e. at 60Hz.

Share this post


Link to post
Share on other sites

 

Anyway, looking at the Stella code made me curious, so I took a detour. I translated my Verilog code more or less directly to C, and wrote a test bench to exercise the logic. Turns out it actually works. I've attached the source code in case you want to see it. The test bench mimics the phaser06.bin ROM, and I've also attached an AIFF file encapsulating the raw data that my code generated.

 

Thanks for this. Would you be willing to release this code under the GPL2 license, so that I can include (some version of) it in Stella?

Share this post


Link to post
Share on other sites

 

Thanks for this. Would you be willing to release this code under the GPL2 license, so that I can include (some version of) it in Stella?

Sure, no problem. Feel free to use it.

 

Please use this one though. I renamed a couple of signals with names that make more sense.

 

MMDC_audio_C.zip

  • Like 5

Share this post


Link to post
Share on other sites

If you guys can get the E.T. ship landing sound effect to sound like it does on a real Atari 2600, just about every other sound effect will be perfect too since that seems to be one of the hardest ones to get right:

 

youtu.be/Njvf9L_NxHI?t=33s

 

So out of curiosity, I ran ET on my FPGA 2600, and made a recording of the ship landing sound. Then I played the game for a few seconds, at which point I shut off the power, and deleted it from my Harmony cartridge.

 

ET-MMDC.aiff

Share this post


Link to post
Share on other sites

So out of curiosity, I ran ET on my FPGA 2600, and made a recording of the ship landing sound. Then I played the game for a few seconds, at which point I shut off the power, and deleted it from my Harmony cartridge.

 

attachicon.gifET-MMDC.aiff

Related links:

 

To What Degree Do You Love E.T.?

E.T. Manual and Tips Sheet

E.T. Map

E.T. Tips and Reminders

E.T. Appreciation Page

E.T. Cake

  • Like 1

Share this post


Link to post
Share on other sites

 

I'll have to grab a big bag of Reese's Pieces, and sit down and have another go at it.

 

The ship landing sound is definitely a good test. It sounds like it's using a subtle phasing effect, which apparently is something that is giving emulators trouble. Thanks for the link. Do you know of any other games or demos that are good for demonstrating sound issues?

Share this post


Link to post
Share on other sites

Crispy, there are several places in your code where you have (variable &&1), which simply evaluates to 'variable'. Is this a typo; did you mean to use '&' instead, or can the '&& 1' be removed completely? The reason I ask is that when I compile the code with clang, I get the warning "warning: use of logical '&&' with constant operand [-Wconstant-logical-operand]" on each line where this occurs.

Share this post


Link to post
Share on other sites

Crispy, there are several places in your code where you have (variable &&1), which simply evaluates to 'variable'. Is this a typo; did you mean to use '&' instead, or can the '&& 1' be removed completely? The reason I ask is that when I compile the code with clang, I get the warning "warning: use of logical '&&' with constant operand [-Wconstant-logical-operand]" on each line where this occurs.

 

Ah Ha! I was wondering if that was going to trip you up. No, it's not a typo. In my translation from Verilog to C I wanted to keep the C code as true to the Verilog as possible. There are several single bit signals in my Verilog code (mostly the LFSR feedback signals) and I wanted to constrain the values of those signals to 0 and 1 in the C code. Since C doesn't have a boolean datatype that would take care of such things by nature, I was forced to play some tricks.

 

Take the case of (x & 0x04). If bit 2 is set in x, the expression will evaluate to 4, but I want 1 instead. I could achieve this with the expression ((x & 0x04) == 0x04), but I like the compactness of (x & 0x04)&&1.

 

There's another case that may need explanation. Verilog has what is called a reduction operator, and it's really slick. Let's say you have a 5 bit bus that is named my_bus, and you want to create a 1 bit flag that is true when any of the bits in the bus are set. Well, you could write something like this.

 

flag = my_bus[4] | my_bus[3] | my_bus[2] | my_bus[1] | my_bus[0];

 

But the reduction operator is much nicer.

 

flag = |mybus;

 

The same thing can be achieved in C with the logical AND operator.

 

flag = mybus&&1;

 

So, whenever you see a logical operator in my C code, you can assume that it's being used to promote an integer value to a boolean value. I hope this helps.

Edited by Crispy

Share this post


Link to post
Share on other sites

Take the case of (x & 0x04). If bit 2 is set in x, the expression will evaluate to 4, but I want 1 instead. I could achieve this with the expression ((x & 0x04) == 0x04), but I like the compactness of (x & 0x04)&&1.

From what Stephen says, it sounds like the compiler's optimizing your trick of (x & 0x04) && 1 into just (x & 0x04), which doesn't produce the result you want.

 

Probaly need to use replace the instances of && 1 with != 0 as in (x & 0x04) != 0

Share this post


Link to post
Share on other sites

Thanks for the info, but I'm re-writing this in C++, and since C++ does have a bool operator, I want to use it correctly, and get rid of the warnings. Also, this is a symptom of another issue; the code in question is mixing boolean (true/false) and bitwise (integer) operations, which is what the warning is really complaining about.

 

EDIT: Also, this compiles and runs with g++, but compiles (with warnings) and segfaults with clang++, so there's something being done differently by the compilers.

Share this post


Link to post
Share on other sites

Thanks for the info, but I'm re-writing this in C++, and since C++ does have a bool operator, I want to use it correctly, and get rid of the warnings. Also, this is a symptom of another issue; the code in question is mixing boolean (true/false) and bitwise (integer) operations, which is what the warning is really complaining about.

 

EDIT: Also, this compiles and runs with g++, but compiles (with warnings) and segfaults with clang++, so there's something being done differently by the compilers.

 

Oh, ugh, the EVIL C++. Burn it! Burn the witch!

Share this post


Link to post
Share on other sites

Wowsers, I'm kind of thrilled -- and flattered as heck -- that the only significant (?) thing I've ever written in ASM is being used to iron out accurate sound emulation! :D Here's the original thread with the source code:

 

http://atariage.com/forums/topic/140331-advanced-sound-techniques-how-do-they-work/?do=findComment&comment=1702472

 

RevEng attempted a fix to get it to sound right on real hardware, though I haven't been able to try it myself:

 

http://atariage.com/forums/topic/247042-tia-perceptual-tuning/?do=findComment&comment=3400302

 

And, another thread that might be helpful:

 

http://atariage.com/forums/topic/191275-question-about-music/

 

I'd love to find a way to get real hardware to sound like what Stella does (pending my getting a chance to check out RevEng's work -- maybe he's gotten it).

 

BTW Random Terrain, I'm definitely hearing a 60Hz tone in the E.T. sound that isn't present in Stella's version -- I think that's the most striking difference -- so I wonder if the low hum is an artifact caused by something being updated once per frame, i.e. at 60Hz.

 

OK, so I had to try my hand at this. Here's my attempt at getting your code to sound on real hardware like it does on Stella. The idea here is to keep track of the TIA audio clock divider count in order to ensure that writes to AUDF1 don't cause a short or long cycle count. At first I used a variable to count clock cycles during a frame, but then realized that a simpler way to do it is to make the frame length an even multiple of the clock divider count. Because of this, my code generates frames that are 271 lines long. My Sony Bravia TV handles it just fine, but I can't guarantee that all TVs will.

 

In the end, it sounds close, but not exact, and that is due to the differences in the way that the two audio channels are mixed together.

 

phaser06_extra_lines.bin

Edited by Crispy
  • Like 1

Share this post


Link to post
Share on other sites

 

Oh, ugh, the EVIL C++. Burn it! Burn the witch!

 

Yes, some days (like today) I feel like that too.

 

In any event, I got it fixed, and now it compiles in g++ and clang++, and generates the same output.

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   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...