mattgodbolt/jsbeeb

Issue with SN76489 emulation?

NegativeCharge opened this issue · 28 comments

Track 5 (Cybernoid) on the following disc has high pitched audio artefacts around 2 to 4 seconds in (Tested on Windows 11/Microsoft Edge 96). I don’t have physical hardware to test this on, but these artefacts are not present when using b-em. Not sure if this is a bug in the SN76489 emulation, or I have a bug in my converter.

https://bbc.godbolt.org/?autoboot&disc1=https://negativecharge.github.io/files/beebcpctunes-03.ssd

Whenever these have come up before it's been differences in the way the SN76489 treats a value of zero. Could that be explained here? We've been back and forth on it (https://github.com/mattgodbolt/jsbeeb/commits/main/soundchip.js) and you can see we've done a number of changes to make it work with more games. @scarybeasts - any insights?

The interesting thing here is that the artefacts only occur at the start of the tune. This section repeats at around 47s and here there’s no artefacts to be heard. I’ll see if I can created a minimal example of where this occurs.

I don’t think it relates to the handling of zero in this case as I have plenty of other conversions that play fine. I’ve also noticed the same issue with conversion of the same .ay track using https://github.com/simondotm/ym2149f and that shares none of the same code as my converter.

I’ll investigate some more. Thanks!

Oh that is interesting! I wonder what can be going on. The code's so simple (in theory...) but we've found a number of interesting cases :)

The sound write routine is somewhat unusual:

[ITRP] 1172: LDX #$FF
[ITRP] 1174: STX $FE43
[ITRP] 1177: STA $FE4F
[ITRP] 117A: INX
[ITRP] 117B: STX $FE40
[ITRP] 117E: LDA $FE40
[ITRP] 1181: ORA #$08
[ITRP] 1183: STA $FE40
[ITRP] 1186: RTS

It looks like 14 cycles between turning the sound chip write gate on and off, which is on the limit jsbeeb will accept.
The read from $FE40 is also unusual. There's potential for that to return an unexpected result, because the value returned depends on a lot of other registers and state.
I wonder if it'd work better in jsbeeb if the code at $117E and $1181 were replaced with NOP:NOP:NOP:NOP:LDA #$08

Nice find @scarybeasts . I remember tweaking that when trying to get Repton 2's Speech thing working. From the datasheet it says the chip won't accept data that quickly...so...I guess I tried to simulate that. Of course what a real chip does "depends".

@mattgodbolt I'm current more suspicious of the LDA &FE40 than the timing. I think the timing should work because it is 14 cycles, which is equal to the minimum jsbeeb claims to accept.

This is the same SN register write code by @simondotm from the VGC player lib used in all of our recent demos!

See sn_write fn here:
https://github.com/simondotm/vgm-player-bbc/blob/master/lib/vgcplayer.asm

Kieran beat me to it :-) …but yes, I would expect JSBeeb to have issues with several demos if that was the case. I did try replacing the sn_write with the nop code but the issue still occurs.

I’m beginning to suspect this write, but need to run some tests:

0x000009B2: 63 Wait: 882 samples (1/50 s) (total 87318 (00:01.98))
0x000009B3: 50 88 SN76496: Latch/Data: Tone Ch 0 -> 0x008
0x000009B5: 50 1A SN76496: Data: 1A
0x000009B7: 50 9F SN76496: Latch/Data: Volume Ch 0 -> 0xF = 0%
0x000009B9: 50 A1 SN76496: Latch/Data: Tone Ch 1 -> 0x001
0x000009BB: 50 00 SN76496: Data: 00
0x000009BD: 50 BF SN76496: Latch/Data: Volume Ch 1 -> 0xF = 0%
0x000009BF: 50 C7 SN76496: Latch/Data: Tone Ch 2 -> 0x007
0x000009C1: 50 04 SN76496: Data: 04
0x000009C3: 50 DF SN76496: Latch/Data: Volume Ch 2 -> 0xF = 0%
0x000009C5: 50 E3 SN76496: Noise Type: 3 - Periodic, Ch 2
0x000009C7: 50 F5 SN76496: Latch/Data: Volume Ch 3 -> 0x5 = 31%

What do we think is the "right" thing to do? Is jsbeeb's emulation of the read busted?

I’ve traced the first set of artefacts to the following 4 commands. It appears a tone value of 4 on Channel 2 played as periodic noise on Channel 3 causes the issue.

If I mute Channel 3 (0xf) instead of 0x3 and 0x5, the artefacts go. So, it would appear the sound emulation doesn’t like a value this low.

I’m not sure what the behaviour of real hardware is, and which emulator is performing correctly. I’ll try and test on FPGA, but this is no substitute for testing on real hardware.

0x0000054F: 63 Wait: 882 samples (1/50 s) (total 42336 (00:00.96))
0x00000550: 50 88 SN76496: Latch/Data: Tone Ch 0 -> 0x008
0x00000552: 50 1A SN76496: Data: 1A
0x00000554: 50 94 SN76496: Latch/Data: Volume Ch 0 -> 0x4 = 40%
0x00000556: 50 A1 SN76496: Latch/Data: Tone Ch 1 -> 0x001
0x00000558: 50 00 SN76496: Data: 00
0x0000055A: 50 BF SN76496: Latch/Data: Volume Ch 1 -> 0xF = 0%
0x0000055C: 50 C1 SN76496: Latch/Data: Tone Ch 2 -> 0x001
0x0000055E: 50 00 SN76496: Data: 00
0x00000560: 50 DF SN76496: Latch/Data: Volume Ch 2 -> 0xF = 0%
0x00000562: 50 E3 SN76496: Noise Type: 3 - Periodic, Ch 2
0x00000564: 50 FF SN76496: Latch/Data: Volume Ch 3 -> 0xF = 0%

0x00000566: 63 Wait: 882 samples (1/50 s) (total 43218 (00:00.98))
0x00000567: 50 88 SN76496: Latch/Data: Tone Ch 0 -> 0x008
0x00000569: 50 1A SN76496: Data: 1A
0x0000056B: 50 9F SN76496: Latch/Data: Volume Ch 0 -> 0xF = 0%
0x0000056D: 50 A1 SN76496: Latch/Data: Tone Ch 1 -> 0x001
0x0000056F: 50 00 SN76496: Data: 00
0x00000571: 50 BF SN76496: Latch/Data: Volume Ch 1 -> 0xF = 0%
0x00000573: 50 C7 SN76496: Latch/Data: Tone Ch 2 -> 0x007
0x00000575: 50 04 SN76496: Data: 04
0x00000577: 50 DF SN76496: Latch/Data: Volume Ch 2 -> 0xF = 0%
0x00000579: 50 E3 SN76496: Noise Type: 3 - Periodic, Ch 2
0x0000057B: 50 F5 SN76496: Latch/Data: Volume Ch 3 -> 0x5 = 31%

0x0000057D: 63 Wait: 882 samples (1/50 s) (total 44100 (00:01.00))
0x0000057E: 50 88 SN76496: Latch/Data: Tone Ch 0 -> 0x008
0x00000580: 50 1A SN76496: Data: 1A
0x00000582: 50 96 SN76496: Latch/Data: Volume Ch 0 -> 0x6 = 25%
0x00000584: 50 A1 SN76496: Latch/Data: Tone Ch 1 -> 0x001
0x00000586: 50 00 SN76496: Data: 00
0x00000588: 50 BF SN76496: Latch/Data: Volume Ch 1 -> 0xF = 0%
0x0000058A: 50 C1 SN76496: Latch/Data: Tone Ch 2 -> 0x001
0x0000058C: 50 00 SN76496: Data: 00
0x0000058E: 50 DF SN76496: Latch/Data: Volume Ch 2 -> 0xF = 0%
0x00000590: 50 E3 SN76496: Noise Type: 3 - Periodic, Ch 2
0x00000592: 50 FF SN76496: Latch/Data: Volume Ch 3 -> 0xF = 0%

0x00000594: 63 Wait: 882 samples (1/50 s) (total 44982 (00:01.02))
0x00000595: 50 88 SN76496: Latch/Data: Tone Ch 0 -> 0x008
0x00000597: 50 1A SN76496: Data: 1A
0x00000599: 50 9F SN76496: Latch/Data: Volume Ch 0 -> 0xF = 0%
0x0000059B: 50 A1 SN76496: Latch/Data: Tone Ch 1 -> 0x001
0x0000059D: 50 00 SN76496: Data: 00
0x0000059F: 50 BF SN76496: Latch/Data: Volume Ch 1 -> 0xF = 0%
0x000005A1: 50 C1 SN76496: Latch/Data: Tone Ch 2 -> 0x001
0x000005A3: 50 00 SN76496: Data: 00
0x000005A5: 50 DF SN76496: Latch/Data: Volume Ch 2 -> 0xF = 0%
0x000005A7: 50 E3 SN76496: Noise Type: 3 - Periodic, Ch 2
0x000005A9: 50 FF SN76496: Latch/Data: Volume Ch 3 -> 0xF = 0%

So, it would appear the sound emulation doesn’t like a value this low.

I don't think that's quite what's happening here. For reasons I don't understand the sound player both writes and reads from the sound hardware - https://github.com/simondotm/vgm-player-bbc/blob/ff8281069f571c4669972aa693a0e2818b90b0d2/lib/vgcplayer.asm#L139-L141 -- and I'm not sure why (and why jsbeeb isn't giving the right answer here)

So at @scarybeasts cunning idea I:

  • hacked a local build to reduce the min write to 8 cycles - no luck
  • also poked out the read from FE40:
117b 8e 40 fe.@.STX $fe40
117e ea.NOP
117f a9 00..LDA #$00
1181 09 08..ORA #$08
1183 8d 40 fe.@.STA $fe40
1186 60`RTS

http://localhost:8080/?autoboot&disc1=https://negativecharge.github.io/files/beebcpctunes-03.ssd&patch=@117a,117e:eaa900 is how I poked it out...

still squeaks! Argh :)

On a whim I turned off the noise channel and the squeak stopped happning....investingating.

Chatting offline with @scarybeasts and we suspect something funky in the way jsbeeb does its very high frequency noise. Still working on it though.

It appears a tone value of 4 on Channel 2 played as periodic noise on Channel 3 causes the issue.

right (I missed this in the back and forth)

@NegativeCharge could you highlight where in that trace above that the period of channel 2 gets set to 4? I see this:

0x00000573: 50 C7 SN76496: Latch/Data: Tone Ch 2 -> 0x007
0x00000575: 50 04 SN76496: Data: 04

But that would appear to set the period of channel 2 (mirrored on channel 3) to 0x47 ?

Further down, there's the sequence:

0x0000057B: 50 F5 SN76496: Latch/Data: Volume Ch 3 -> 0x5 = 31%
[...skip...]
0x0000058A: 50 C1 SN76496: Latch/Data: Tone Ch 2 -> 0x001
0x0000058C: 50 00 SN76496: Data: 00
0x0000058E: 50 DF SN76496: Latch/Data: Volume Ch 2 -> 0xF = 0%
0x00000590: 50 E3 SN76496: Noise Type: 3 - Periodic, Ch 2
0x00000592: 50 FF SN76496: Latch/Data: Volume Ch 3 -> 0xF = 0%

This appears to set the period of channel 2 (mirrored on channel 3) to 0x01, whilst the volume on channel 3 (periodic noise) is 31% -- but only very briefly before the volume is set to 0%.

Do you have any sense of how much actual machine time elapses between 0x0000058C and 0x00000592 ?

Sorry, yes 47, not 4. Here’s an easier to read trace. This runs at 50hz so there are 0.02s between writes:

CH0,VOL0,CH1,VOL1,CH2,VOL2,CH3,VOL3
0x001A8,0x2,0x00001,0xF,0x00001,0xF,0x10003,0xF
0x001A8,0xF,0x00001,0xF,0x00047,0xF,0x10003,0x3
0x001A8,0x4,0x00001,0xF,0x00001,0xF,0x10003,0xF
0x001A8,0xF,0x00001,0xF,0x00047,0xF,0x10003,0x5
0x001A8,0x6,0x00001,0xF,0x00001,0xF,0x10003,0xF
0x001A8,0xF,0x00001,0xF,0x00001,0xF,0x10003,0xF

by manually puppetteering the soundchip from the JS debugger I found that going from the first row to the second row produces the squeak! (I added a "pokey" debug feature to let me put in values externally) :

soundChip.pokey(0x001A8,0x2,0x00001,0xF,0x00001,0xF,0x10003,0xF); // mid pure noise
soundChip.pokey(0x001A8,0xF,0x00001,0xF,0x00047,0xF,0x10003,0x3); // SQUUEEEEAK

Interestingly - and a big clue - the squeak stops after a short time and goes back to the mid-ish periodic noise we would expect.

I have something concrete to dig in to now!

Right! Progress: I've found that the counters I use to check for transitions can go very negative when they're set to super high frequencies....then toggling back to not-so-high and it takes a while for the counter to pop back up positive!

(and while negative they will toggle the channel at maximum frequency)

@NegativeCharge Thanks for your patience and awesome work (and of course @scarybeasts too) -- https://bbc.godbolt.org/?disc1=https://negativecharge.github.io/files/beebcpctunes-03.ssd&autoboot sounds "ok" to me now, what do you think?

I'm going to work on a better solution.

Thanks @mattgodbolt and @scarybeasts! Yes, that’s fixed it. Thanks for your perseverance on this one - I couldn’t be sure if it was a problem with the emulation or my converter producing an out of range set of writes for the SN. Glad you found the issue. Most appreciated!

@mattgodbolt - I hate to be the bearer of bad news, but with the above fix applied I’m eventually hearing the noise channel stop playing completely. This is apparent around 1 minute in when there is a lot of activity on the noise channel but no drums / low frequency noise is being played by jsbeeb.

@mattgodbolt - FYI, changing the patch to use Math.min rather than %= seems to solve the issue for me. However, it’s been years since I’ve written any Javascript so don’t know if that’s the correct approach:

e.g.

if (counter[channel] < 0)
counter[channel] = Math.min(add,0);

Thanks @NegativeCharge - I'll investigate. I avoided the Min to keep "some" fidelity, but...this is probably safest.

(in this case it can just be counter[channel] = 0; )

That said: I can't (currently!) think why the % wouldn't work. I'll definitely update to be = 0 for now (I have another branch where I get rid of this hack entierly)

Thanks Matt! Yes, that seems to have fixed it. Most appreciated.