Home Artists Posts Import Register

Content

Hi Everyone,

one of the most discussed bugs of the PSX core was finally fixed end of last year and  I want to take the chance to wrap up what it was about.

The game in question is "Alien: Resurrection" and the bug I'm talking about is famous "door bug".

Usually that a door is supposed to open after a keycard is used and a voice stated that Warehouse access has been granted. While the sound played fine in the core, the door did not open up, leaving us softlocked.

When searching old information about this bug, which also hit several emulators in the past, it quickly reveals that the issue is related to the sound chip SPU. 

And indeed, the way the voice streaming is done with the SPU and also uses Interrupt requests(IRQ) which often have shown to be problematic before. 

But how does all that work?

The SPU has 512 Kbyte of RAM available. This RAM is divided into different sections:

To play long voice lines, it may not be possible to store all the sound data in the SPU RAM at once, but instead data must be reloaded while it plays.

This can be done with a ring buffer that reserved some KByte of data for a voice and when a certain sample has already played back, it can be exchanged for a future sample.

In this example the voice line is 8 samples long and the ring buffer has a size of 4 samples. Whenever a sample has been played, new samples are fetched from CD and replace the old ones. Sample 4 is currently played back, sample 5 is already fetched and sample 6 will soon be fetched from CD and replace sample 3.

While this example is very easy and short, the ring buffer in Alien is much larger with 3072 samples in size, but still working the same. The larger ringbuffer gives plenty of space and time to buffer and refill so this is never an issue. 

Especially the refill rate is higher than the playback rate, so the game must pause the refilling and wait until more time has passed to start refilling again. But what is a good time to wait? Ideally the game could wait for some event to happen and get notified that more samples need to be fetched.

This is exactly what the IRQ is doing here: the game can set a trigger point at which it will get notified when a certain sample has been played.

In this example, the current playback is still at address 0x200, but the game has cached samples up to address 0x500.

The IRQ trigger address is set to 0x300, so when the playback will reach that point,  the game gets notified and can already refill the area from 0x200 to 0x300 with new data that will be played back once the audio playback reaches 0x500 and wraps back to 0x200.

This may sound like it's very timing critical, but given how slow audio really if for a 33 Mhz CPU, there is plenty of time.


The second mechanic we need to look at is the sample playback itself. Samples cannot be played back as pure data in the SPU, but are instead ADPCM encoded blocks, which consist of 16 byte each, containing a header of 2 byte and 14 byte of payload with  28 samples in total:

The header contains additional information that can be used to tell the SPU which block shall be played after the current block. For example in the the ring buffer case above, it is useful to let it continue after 0x500 to 0x200.

But there is another reason this is often used, at first it might be shocking:

The SPU doesn't have a way to fully turn of any of it's 24 voices. You can mute a voices, you can make it play really slow, but you cannot stop it. Every voice will fetch new blocks from SPU RAM all the time and continue playing.

If you do nothing about it, the voice will read through the whole SPU RAM and wraparound to the start and continue from there. This sounds rather irritating when you also use a memory access to trigger a IRQ: stray voices would trigger such IRQ randomly.

To prevent that, there is very simple trick used by most games to "turn off a voice" : 

Let the ADPCM block loop to itself. This way the voice is contained in it's 16 byte area and cannot ever leave it again.

Something similar is what Alien does when the playback has finished: a block in the ringbuffer will be programmed to loop to a previous block and all these contain muted samples.

But then this happens in case of the door bug:

The behavior of Alien is completly fine, the data is correct, there is no race condition. The SPU just never reaches the last IRQ address. This in result will make the game think the voice isn't fully played and the door will not open.

For debugging it further, I tricked the game into thinking the voice is fully played by sending only one additional SPU IRQ from a debug trigger and the door would open. This made clear that really only the last IRQ was missing. But still, it was not clear where this could come from.

The core was stuck at this point for a long time.

...

End of last year "Barone" came out of nowhere and researched the games visible and audible behavior in different conditions. You can still see the initial research in the Github issue:

https://github.com/MiSTer-devel/PSX_MiSTer/issues/182

But I can tell you, that is only the tip the iceberg. He several hours of more research and even more on questions back from my side.

It was really amazing to see how much information about this bug can be observed from the game itself. Not by watching game internals, core internals or SPU behavior, but instead carefully listening to the voices and finding reproducible behavior.

It turned that there where places right after starting a new game that also showed the same bug, but with different result: some voices are muted that should have been played. Having access to several, easy reachable and reproducible places that trigger the bug was the key to fix the bug.

Who also helped finding the solution? Our old friend DuckStation.

I was able to debug some situation in DuckStation that doesn't work in the PSX core and I found that DuckStation does indeed trigger a SPU IRQ at the end of the playback. So DuckStation doesn't show this bug and while it required a hack to achieve that, having a solution that works, no matter how it's reached, can help a lot.

But where does the IRQ come from in DuckStation? 

There is a function that is called on writing some SPU registers which was able to "late trigger" a IRQ that usually should have been triggered earlier already. This is not something that would happen in the real console like this, but maybe it's a good enough approximation to keep calculation effort down?

This function does trigger the IRQ in case the transfer address is matching the IRQ address. What does that mean?

Previously, I told you that voice playback can trigger a SPU IRQ when reaching a certain point in the SPU RAM address. But not only the voices read the SPU RAM, there are also other situations where the SPU RAM is accessed, e.g. when transferring sample data into SPU RAM. This can trigger a IRQ as well.

This mechanic was already implemented into the core: whenever data to the SPU RAM was written or read back by the CPU or DMA, it could trigger a IRQ.

But data isn't written all the time: when the voice is fully transmitted, there is no need to write data into the SPU RAM anymore...and therefore no reason to trigger?

That was the big mistake in the assumption, because it turned out the transfer address is indeed matching the IRQ address in the case where the voice line has fully played in Alien and the door still does not open.

But how would it even be possible that this is the reason when there are no SPU RAM accesses...or are there still accesses?

I remembered a sentence from the documentation about the SPU reverb feature and it instantly made sense:

The reverb function of the SPU, which most people know from the startup sound of the BIOS, also requires SPU RAM reads and writes. If reverb is turned off, there would be no RAM accesses required at all, but they still happen: what would be a write before is now a read. So it has no effect on the RAM, but the logic inside the SPU stays mostly the same.

What happens with the Data transfer to SPU RAM is the same: even if there is nothing to write, the logic is still in place and to not modify the RAM, the write is simply replaced with a read. The read doesn't hurt anything and was likely a simple solution for the engineers.

After implementing that hehavior the game works indeed:

The door does open now, all sounds play as they should and the solution does not require a hack, as it behaves like the original hardware.

The fix is available in the unstable build and there will be release containing this fix and other recent fix soon.

Have fun!


Unstable build:

https://github.com/MiSTer-unstable-nightlies/PSX_MiSTer/releases/download/unstable-builds/PSX_unstable_20230109_050e2b.rbf

Comments

Muriel Melvin

It’s been like 25+ years since I thought about it but I suddenly understand a lot better why IRQ conflicts with old PC sound cards sounded like they did. Also never thought about ring buffers in terms of audio before. Really old DJ CD players used to loop a buffer for cueing before jog wheels were common. It just occurred to me those things probably used a ring buffer to handle the CD reads to work with cueing and pitch shifting.

Anonymous

I love these detailed write ups! Thanks to everyone that worked on figuring this out and improving our understanding of the original hardware!