Need sound help emulating NES on SNES

Discussion of hardware and software development for Super NES and Super Famicom. See the SNESdev wiki for more information.

Moderator: Moderators

Forum rules
  • For making cartridges of your Super NES games, see Reproduction.
User avatar
Memblers
Site Admin
Posts: 4044
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Re: Need sound help emulating NES on SNES

Post by Memblers »

Thanks for posting a video, that's fantastic. I wouldn't have thought that dynamic recompiling would work very well, but there it is.

SMB was conspicuously absent from my NSF player, because I just couldn't get it to run for some reason. I don't think it's doing anything too tricky sound-wise, but it was kinda infamous for sounding wrong on a lot of emulators in the past (mostly the sound effects more than the music though, the pipe sound in particular).

For the noise channel to sound like that, seems like the code that detects writes to $400F isn't working. And it sounds like frequency sweeps aren't working. In my player, both of those are handled in main.asm 'detect_changes' subroutine, stuffing bit flags for everything into the $7F4116 variable. Also really weird how the triangle channel only shows up a few times. That indicates to me that in the same way, $400B writes aren't being detected. It could be that when the game changes the high byte of the frequency (you could play the NSF in a player with a register viewer to be sure), that's when the notes are working. But a lot of the time, only the low byte changes. Even when the high byte doesn't change, the game must write to it so the linear counter / length counter / sweep register etc. will be triggered. It was one of the weirder problems I remember solving with that detect_changes routine, how to detect the code writing the same value repeatedly to those registers. I just got lucky because writing zero to those on the NES is pretty rare, so I could count on games generally not writing zero to it normally (a rogue sound engine could have worked that way and broke my emulation, but luckily they tend to use the volume regs (and 4008 on triangle), or $4015, and in cases where they actually use the timers, they just let it expire). Since you'll be recompiling, I guess you won't have that problem exactly, but you'll have to address it in some way while you collate the sound register writes before sending it to the SPC.

It's entirely possible though that SMB1 is doing crazy weird stuff with the sound registers, because I never had it working in my player to actually observe it. I doubt it though. I'll take a look at it tomorrow. If i can remember what other NSF player had a register viewer other than mine, and the old DOS version of Nosefart. I'm certain there is one..

Also, there is one known bug in my update_dsp routine. It causes occasional glitches. The cause was something really weird with the CPU/SPC alignment, IIRC it worked fine on hardware but was breaking on every emulator. kevtris said inserting a NOP in there fixes it, I may be able to ask him tomorrow where that NOP should go exactly. But probably just in-between my $2140/$2141 accesses, they were just too close together I guess. I don't think that's affecting the audio here, but it's possible.
User avatar
za909
Posts: 249
Joined: Fri Jan 24, 2014 9:05 am
Location: Mijn hart woont al in Nederland

Re: Need sound help emulating NES on SNES

Post by za909 »

Some games do attempt to silence the triangle channel in weird ways that might cause some issues:
- Mega Man and Mega Man 2, plus other Capcom games from around that time silence the triangle by writing 0 to the period register, some even use $4015 afterwards, it's really odd, but it makes a popping sound.
- Tim & Geoff Follin used a similar method, only that the triangle spends releatively little time in this state in most of their music.
- Zombie Nation, Kickmaster, Doki Doki Yuuenchi, etc. use a sound driver that """silences""" the triangle by writing $7FF to the period register, where the overtones of the 4-bit quantization of the wave are still audible.

Is there any way to use the SPC-700 to directly set channel levels to simulate the effect of $4011 writes to add DPCM support? Writing a player for DPCM doesn't sound very difficult (Konami has done that themselves for Blades of Steel) but getting the timings right could be.
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: Need sound help emulating NES on SNES

Post by nocash »

Memblers wrote:One problem with my code is that the interpolation totally ruins the sharp edges of the waveforms. IIRC it was _mic who later released a sound emulator showing you can get a cleaner sound by using a set of larger samples played at the highest rates possible, as it will be affected less by the interpolation.
Yes, the square wave output can become square or not so square. When using a single 9-byte BRR sample (=with sixteen 4bit nibbles), it could look as so:

Code: Select all

  Incoming BRR Data                 ---> Interpolated Data
   _   _   _   _   _   _   _   _
  | | | | | | | | | | | | | | | |         .   .   .   .   .   .   .   .    Nibbles=7979797979797979, Shift=12, Filter=0
  | | | | | | | | | | | | | | | |   ---> / \ / \ / \ / \ / \ / \ / \ / \   HALF-volume ZIGZAG-wave
  | |_| |_| |_| |_| |_| |_| |_| |_          '   '   '   '   '   '   '   '
   ___     ___     ___     ___
  |   |   |   |   |   |   |   |            .'.     .'.     .'.     .'.     Nibbles=7799779977997799, Shift=12, Filter=0
  |   |   |   |   |   |   |   |     --->  /   \   /   \   /   \   /   \    FULL-volume SINE-wave
  |   |___|   |___|   |___|   |___       '     '.'     '.'     '.'     '.
   _______         _______                   ___             ___
  |       |       |       |                .'   '.         .'   '.         Nibbles=7777999977779999, Shift=12, Filter=0
  |       |       |       |         --->  /       \       /       \        SQUARE wave (with rounded edges)
  |       |_______|       |_______       '         '.___.'         '.____
   _______________                           ___________
  |               |                        .'           '.                 Nibbles=7777777799999999, Shift=12, Filter=0
  |               |                 --->  /               \                SQUARER wave (with rounded edges)
  |               |_______________       '                 '.____________
I've tried to output a melody with 130Hz..260Hz tones today. First using the "SINE" wave (2nd graph), then using the "SQUARER" wave (4th graph) played at quad speed. The difference was surprising, the "SQUARER" one sounded more or less okay, but the "SINE" wave wasn't audible at all - which, I guess the reason is that the speakers in my PC/TFT monitor are quite crappy.
I had thought that the difference between sine & square would be more of a cosmetic thing for audiophiles, or that sine waves would sound even better and clearer - but now I agree that it's important to have edges in square waves.

When using samples with more than 9 bytes it could be made even squarer, but larger samples would work only for lower frequencies. Even the 4th graph in the above examples can output max 7999Hz (with pitch=3FFFh).
So I guess one needs to switch between different BRR samples to cover all frequencies - which could bring up it's own problems (eg. abrupt change in "volume & appearance" when switching between different samples for <=7999Hz versus >=8000Hz tones).
Last edited by nocash on Tue Nov 20, 2018 8:38 am, edited 1 time in total.
User avatar
Bregalad
Posts: 8056
Joined: Fri Nov 12, 2004 2:49 pm
Location: Divonne-les-bains, France

Re: Need sound help emulating NES on SNES

Post by Bregalad »

As far as I'm aware the norm in SNES games is to have at least two full BRR blocks (18 bytes) of samples, even for short looped "synths" waves. Having 3, 4 or more BRR blocks per loop is common, too. Many SNES games had square waves and sometiemes pulse waves in their audio, so I'd recommend investigating this.
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Need sound help emulating NES on SNES

Post by tepples »

Applying a bit of overshoot to the waveform boosts the treble, which tricks Gaussian resampling into behaving more like cubic spline resampling. I don't have all my DSP tools in front of me at the moment, but try these waves and see which of each set most closely resembles what you remember. (As usual, you'll have to insert a header byte before each 8 pairs of nibbles: C0 before the first half and C3 before the second half.)

Code: Select all

BBBB 5555 5555 5555   5555 5555 5555 5555
ABBA 6555 5555 5555   5555 5555 5555 5556
9BB9 7555 5555 5555   5555 5555 5555 5557

BBBB BBBB 5555 5555   5555 5555 5555 5555
ABBB BBBA 6555 5555   5555 5555 5555 5556
9BBB BBB9 7555 5555   5555 5555 5555 5557

BBBB BBBB BBBB BBBB   5555 5555 5555 5555
ABBB BBBB BBBB BBBA   6555 5555 5555 5556
9BBB BBBB BBBB BBB9   7555 5555 5555 5557
Myself086
Posts: 158
Joined: Sat Nov 10, 2018 2:49 pm

Re: Need sound help emulating NES on SNES

Post by Myself086 »

Absolute writes to sound ports are replaced with JSL but not absolute indexed yet. On what changes should I set flags? I understand the bits at $7F4116 and $7F4115.

I confirm that pipes are the worst.
Memblers wrote:Also, there is one known bug in my update_dsp routine. It causes occasional glitches. The cause was something really weird with the CPU/SPC alignment, IIRC it worked fine on hardware but was breaking on every emulator. kevtris said inserting a NOP in there fixes it, I may be able to ask him tomorrow where that NOP should go exactly. But probably just in-between my $2140/$2141 accesses, they were just too close together I guess. I don't think that's affecting the audio here, but it's possible.
I noticed a bug while rewriting this and I probably fixed exactly what you're talking about. The index was written before the data byte but it should be the other way around. The SPC waits for index to change so it can read the data byte, otherwise it can fail to read the correct data. I never tried running it before changing update_dsp.
psycopathicteen
Posts: 3140
Joined: Wed May 19, 2010 6:12 pm

Re: Need sound help emulating NES on SNES

Post by psycopathicteen »

I was trying to make an SNES music program a couple months ago and I think I went up to 7, down to 3 and then plataued at 4, for the overshoot.
User avatar
Memblers
Site Admin
Posts: 4044
Joined: Mon Sep 20, 2004 6:04 am
Location: Indianapolis
Contact:

Re: Need sound help emulating NES on SNES

Post by Memblers »

Myself086 wrote:Absolute writes to sound ports are replaced with JSL but not absolute indexed yet. On what changes should I set flags? I understand the bits at $7F4116 and $7F4115.
I think games almost always write to the sound registers with absolute indexed. Before, I was thinking the flags in $7F4116 would be set anytime $4003,$4007,$400B,$400F are written, but now I'm not 100% certain. Looking at my code I see that I only set those flags when length counter enable is turned on. I'm having trouble understanding my old code 100% at the moment, doesn't seem like I can do an adequate job explaining it. The SPC code will only trigger decay if that bit is set, so I'm not sure why I have those 2 features seemingly interdependent.. either I'm misunderstanding my code, or I did it wrong (and I'm finding a bug).

edit: Also, sweep register flags (D6 and D7 of $7F4116) need to be set whenever $4002,$4006 are written

I've been looking through my code for the first time in a while (hard to believe it's been 17 years since I wrote most of this), made some observations:

Right off the bat in 'detect_changes', this branch label implies it has checked the decay enable (D4), but it's actually checking the length counter enable (D5). Very misleading.

Code: Select all

detect_changes:
        lda $7F4000
        and #%00100000
        bne decay_disabled0
D5 of $7F4116 selects mono/stereo mode for the 2 pulse channels, you probably noticed that.

There sure is ton of commented-out code left in there. From the bad old days before I used version control.

Lots of copy+pasted+manually edited code in there. Not sure why I didn't just use macros instead.

Lots of annoying inconsistencies. Like I have this defined:
no4016 = $56 ; $4016
But sometimes I refer to it as no4016, sometimes as $56. Ugh, WTF. Also some places where I use hex like 20h instead of $20 like everywhere else.. minor WTF.
Myself086 wrote:
Memblers wrote:Also, there is one known bug in my update_dsp routine. It causes occasional glitches. The cause was something really weird with the CPU/SPC alignment, IIRC it worked fine on hardware but was breaking on every emulator. kevtris said inserting a NOP in there fixes it, I may be able to ask him tomorrow where that NOP should go exactly. But probably just in-between my $2140/$2141 accesses, they were just too close together I guess. I don't think that's affecting the audio here, but it's possible.
I noticed a bug while rewriting this and I probably fixed exactly what you're talking about. The index was written before the data byte but it should be the other way around. The SPC waits for index to change so it can read the data byte, otherwise it can fail to read the correct data. I never tried running it before changing update_dsp.
Good find, that is exactly the fix. It's really weird that it apparently works on the actual hardware, but it's still a race condition and should be fixed.
Myself086
Posts: 158
Joined: Sat Nov 10, 2018 2:49 pm

Re: Need sound help emulating NES on SNES

Post by Myself086 »

*bump*

I've been working on this project on and off since my last post. I thought the audio was good enough at the time so I've been focusing on emulating other aspects of the NES. I am just now coming back to emulating sound.

Here's the latest test using Crystalis (mapper 4):
https://www.twitch.tv/videos/556053476

This game often writes to APU ports twice within the same frame. I've been thinking about directly sending data rather than detecting changes as Memblers was doing.

Here's how SNES APU ports will be used from now on (work in progress)
Port 0 in: which NES APU port is changing, bit 7 used as ready toggle bit*
Port 1 in: data written at the NES APU port
Port 2 in: scanline counter via HDMA
Port 3 in: reserved for sending DMC sound sample
Port 0 out: bits 0-6 are unused or always 0, bit 7 used as ready toggle bit*
Port 1 out: which channels are active, same as reading $4015
Port 2 out: unused
Port 3 out: always zero, potentially for JMP ($217f)
* Port 0 bit 7 will be identical for in&out when CPU can send data.

I've heard of a bug that combines (same as logical OR) the data on those ports if it is being read on one side at the same time the other side is writing to it. So for port 0, I may use bit 6 to be the opposite of bit 7.

The scanline counter can be used as a 1/4 frame timer but mainly to stream sound samples without halting the CPU.

As far as BRR samples go, I just made my own program for showing the interpolation on a graph. I don't know how accurate my graph is but I was able to adjust some wave forms.
- My testing with square wave had the best results when applying the overshoot that psycopathicteen described.
- Square waves with only 4 duty cycles use 5 overshoots in a row. "4444bbbbbb..." became "627fabbbbb...".
- Triangle wave looked too much like stairs so I reduced the shift on some parts but lost the tip on both ends.

This emulator is currently called "Project Nested" but is subject to change.
User avatar
dink
Posts: 157
Joined: Sun Jan 12, 2020 8:42 pm

Re: Need sound help emulating NES on SNES

Post by dink »

You've made some really awesome progress with this, loving it! :)
nocash
Posts: 1405
Joined: Fri Feb 24, 2012 12:09 pm
Contact:

Re: Need sound help emulating NES on SNES

Post by nocash »

Myself086 wrote: Mon Feb 24, 2020 4:04 amI've heard of a bug that combines (same as logical OR) the data on those ports if it is being read on one side at the same time the other side is writing to it. So for port 0, I may use bit 6 to be the opposite of bit 7.
Yes, that hardware bug is documented for APU to SNES direction (not sure if it happens in other direction, too).
If you are reading ONLY the toggle flag in bit7 then it shouldn't matter if the bit gets garbled for a moment (even if it gets a random value instead of logical OR, your code would still see it as either toggled, or not-yet-toggled, which is both fine).
For the lower bits in the same register, I would first wait until bit7 gets changed, then read the register again, and isolate the lower bits. That should be the most stable way.
Testing bit6 being opposite of bit7 doesn't work so well, the problem with a parallel bus is that some bits may settle earlier than others, so bit0-5 may be still invalid even when seeing a valid new value in bit6-7.
homepage - patreon - you can think of a bit as a bottle that is either half full or half empty
mic_
Posts: 922
Joined: Thu Oct 05, 2006 6:29 am

Re: Need sound help emulating NES on SNES

Post by mic_ »

IIRC it was _mic who later released a sound emulator showing you can get a cleaner sound by using a set of larger samples played at the highest rates possible, as it will be affected less by the interpolation.
Right. I did that in my SN76489 emulator for the SPC-700 (source here).
When the target frequency dropped below 2 or 1 kHz I would use a sample 4 or 8 times the normal length and play it at 4 or 8 times the pitch.
Myself086
Posts: 158
Joined: Sat Nov 10, 2018 2:49 pm

Re: Need sound help emulating NES on SNES

Post by Myself086 »

Thanks for the help guys. That should help me fix most problems.

mac_, do you mind if I use part of your code for my project?
mic_
Posts: 922
Joined: Thu Oct 05, 2006 6:29 am

Re: Need sound help emulating NES on SNES

Post by mic_ »

Sure, you can use the code as you wish.
By the way, I don't know if it's of any use, but I once wrote an NSF player for the TurboGrafx-16, which is kind of similar to what you're doing here (except I only I had to deal with emulating the parts necessary for NSF playback - not the entire system).
Myself086
Posts: 158
Joined: Sat Nov 10, 2018 2:49 pm

Re: Need sound help emulating NES on SNES

Post by Myself086 »

For sound, I only need to emulate ports $4000-$4013 and $4015. Anything on the CPU side is handled by the recompiler.

Crystalis had to be patched to improve performance and fix a known crash that would happen way more often than usual.

Recompiling is usually done just-in-time but known code can be done ahead of time by the exe. The latter uses feedback from the srm file.
Post Reply