APU Sweep Test ROMs

Discuss emulation of the Nintendo Entertainment System and Famicom.

Moderator: Moderators

Post Reply
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

APU Sweep Test ROMs

Post by blargg »

I wrote a couple more NES APU emulator test ROMs to check the sweep unit. It tests the upper silence cutoffs for each shift value, the lower cutoff, and the subtract mode. I might write a few more tests soon.

test_apu_sweep.zip

The archive contains two iNES ROMs, the asm source, and sound files of the output when run on a NES. Reply if you get a different result than expected.
Last edited by blargg on Fri Dec 31, 2010 7:02 am, edited 2 times in total.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Post by Disch »

both of them are working for me now... although I had to add a tweak to get sweep_cutoff to work.

For the most part it played as expected... only notes *very briefly* played before the noise signal (when you're not supposed to hear anything). I mean briefly as in it's only making 1 full square briefly -- it sounded mostly like a pop (or really several pops since it's happening on each test).

My previous method is I have a flag which indicates if the sweep unit is silencing the channel (1 if channel is silenced by sweep unit, 0 otherwise). I refreshed the value by checking the conditions on every $4001/2/3 write...as well as on sweep unit clocks.

The tweak I added makes it so the flag can only go from 1->0 on Sweep Unit clocks... meaning if the sweep unit is silenceing the channel... it will always be silent until at least the next sweep unit clock.

The tweak fixed sweep_cutoff.... but I'm not sure on it's accuracy. Could you verify for me? Is that what I'm supposed to be doing or am I doing something else wrong?
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

Disch wrote:My previous method is I have a flag which indicates if the sweep unit is silencing the channel (1 if channel is silenced by sweep unit, 0 otherwise). I refreshed the value by checking the conditions on every $4001/2/3 write...as well as on sweep unit clocks.
That seems fine. In the code below it would be equivalent to caching the value of is_silenced() and updating it at the end of clock_sweep() and write_4001(). In the recording of the NES there are slight clicks before the noise burst at the middle. My guess is that you aren't silencing until the period is set above 0x7ff, thus this doesn't happen until the next sweep clock; the correct behavior is to silence the channel if the shifter/adder's output is above 0x7ff, even before the sweep clock has occurred (see code below).
The tweak I added makes it so the flag can only go from 1->0 on Sweep Unit clocks... meaning if the sweep unit is silenceing the channel... it will always be silent until at least the next sweep unit clock.
Definitely incorrect, since silencing has no relation to clocking. The only thing clocking does is drive the actual sweeping (adjusting the period in third and fourth registers).

Here's the code I use for sweep handling (which matches what is described in the APU reference). Note the comment in clock_sweep() about the different behavior of the second square channel.

Code: Select all

int square_period;    // value in $4002/$4003
int sweep_period = 1; // must never be zero
int sweep_delay = 1;  // must never be zero
int sweep_shift;
bool sweep_enabled;
bool sweep_reload;
bool sweep_negate;

void clock_sweep()
{
    if ( --sweep_delay == 0 )
    {
        sweep_delay = sweep_period;
        
        if ( sweep_enabled && square_period >= 8 )
        {
            int offset = square_period >> sweep_shift;
            if ( sweep_negate ) {
                // no + 1 for second square channel
                square_period -= offset + 1;
            }
            else if ( square_period + offset < 0x800 ) {
                square_period += offset;
            }
        }
    }
    
    if ( sweep_reload )  {
        sweep_reload = false;
        sweep_delay = sweep_period;
    }
}

bool is_silenced()
{
    int offset = square_period >> sweep_shift;
    return square_period < 8 ||
            (!sweep_negate && square_period + offset >= 0x800);
}

void write_4001( int n )
{
    sweep_negate  = (n >> 3) & 1;
    sweep_shift   = n & 7;
    sweep_period  = ((n >> 4) & 7) + 1;
    sweep_enabled = (n & 0x80) && sweep_shift;
    sweep_reload = true;
}
Last edited by blargg on Fri Mar 04, 2005 2:31 am, edited 1 time in total.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Post by Disch »

blargg wrote:That seems fine. In the code below it would be equivalent to caching the value of is_silenced() and updating it at the end of clock_sweep() and write_4001(). In the recording of the NES there are slight clicks before the noise burst at the middle. My guess is that you aren't silencing until the period is set above 0x7ff, thus this doesn't happen until the next sweep clock; the correct behavior is to silence the channel if the shifter/adder's output is above 0x7ff, even before the sweep clock has occurred (see code below).
Nope... I'm doing it with a method similar to yours. Sample code below.
blargg wrote:Definitely incorrect, since silencing has no relation to clocking. The only thing clocking does is drive the actual sweeping (adjusting the period in third and fourth registers).
K... that tweak removed (I didn't think it was proper).

code in my emu (edited for ease of read):

Code: Select all

void CheckSweepForcedSilence()
{
  bSweepForceSilence = 0;
  if(nFreqTimer < 8)      bSweepForceSilence = 1;
  else if(!bSweepMode)
  {
    if((nFreqTimer + (nFreqTimer >> nSweepShift) & 0x800)
      bSweepForceSilence = 1;
  }
}

void write_4001( u8 v )
{
  RunAPU(nCPUCycle);
  bSweepEnabled =   (v & 0x80) && (v & 0x07);
  nSweepTimer =      (v & 0x70) >> 4;
  bSweepMode =       v & 0x08;
  nSweepShift =        v & 0x07;
  bSweepReset =       1;
  CheckSweepForcedSilence();
}

void clock_sweep()
{
  if(bSweepEnabled && !bSweepForceSilence)
  {
    if(nSweepCount)
      nSweepCount--;
    else
    {
      nSweepCount = nSweepTimer;
      if(bSweepMode)  nFreqTimer -= (nFreqTimer >> nSweepShift) + 1;  //no +1 on square 2
      else  nFreqTimer += (nFreqTimer >> nSweepShift);
      CheckSweepForceSilence();
    }
  }
  if(bSweepReset)
  {
    bSweepReset = 0;
    nSweepCount = nSweepTimer;
  }
}

If bSweepForceSilence is ever on, the channel is silenced and output is 0. CheckSweepForcedSilence() is also called at the end of 4002 and 4003 writes (whenever the frequency changes -- could this be the problem? perhaps it's only supposed to be checked on 4001 writes?)

I'm gonna look into what's going on more and see if I can't figure out my problem
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

Your code looks correct, and worked when I pasted it in my emulator and ran the cutoff test. As you mentioned, you should also call CheckSweepForcedSilence() after writes to $4002 and $4003.

If you post a sound sample (8-bit WAVE compresses best) I'll examine it.
User avatar
Quietust
Posts: 1920
Joined: Sun Sep 19, 2004 10:59 pm
Contact:

Post by Quietust »

Initially, my emulator was not producing proper results - the first test had one single sweep before the burst of noise (the rest was fine), and the 2nd test didn't play everything in the same pitch. FCE Ultra has similar problems, though the first test had even more noise at the beginning.

It turns out I was running the half-frame stuff (i.e. sweeps) on quarter-frames 1 and 3 rather than 0 and 2 - changing this immediately fixed the results on both tests.

Looking at the APU Frame Counter wiki page, the frame sequences seem wrong somehow - are these taking into account the fact that writing $4017 starts the 4-frame sequence after one quarter frame versus starting the 5-frame sequence immediately?

If you could come up with some proper tests to verify all of that behaviour, we emulator authors would greatly appreciate it.
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Post by Disch »

I figured out what's happening. In between register writes, bSweepForceSilence is briefly 0... so it outputs until it's shut off a few cycles later... creating a quick pop.

For example the demo does the following writes (status of bSweepForceSilence is in parenthesis):

A1 -> 4001 (1)
07 -> 4002 (1)
00 -> 4003 (1)
F1 -> 4002 (0) * here
07 -> 4003 (1)

The few short cycles between the 4002 write and 4003 write, the channel is outputting nonzero... creating the pop.
User avatar
blargg
Posts: 3715
Joined: Mon Sep 27, 2004 8:33 am
Location: Central Texas, USA
Contact:

Post by blargg »

Disch, sorry about the slight pops that the ROM makes. I looked at the asm earlier today and saw that it does create them because I'm setting the sweep shift a few cycles after changing the period; setting it before keeps the silencing in effect continuously. Maybe the pops are louder on your emulator due to the method of band-limiting you use (averaging). I'll make sure future sound test ROMs generate more unambiguous output.

Quietust, I would like to make comprehensive sound test ROMs that help find subtle sound issues, since they can have a significant effect on sound effect accuracy. Test ROMs would also serve to double-check the current understanding of APU operation.

I'd like to also make identical test NSFs for players, but making the tests into a routine that gets called 60 times a second and returns each time will be more difficult. I'm not sure if NSF players can handle an NSF whose init routine never returns. Disch, do you know of any NSFs that never return from their init or play routines, just sit there in a loop handling timing themselves?

Regarding the frame counter, I've edited the NesDevWiki page to be (hopefully) clearer about the delay for the 4-step sequence and lack of one for the 5-step sequence. In the side effects for a write to $4017, I rewrote it to "The sequencer is restarted at step 1 of the selected mode. If mode is 1 the sequencer is then clocked, causing the first step to be carried out immediately. Finally, the divider is reloaded, resulting in a 1/240 second delay before the sequencer is next clocked."
User avatar
Disch
Posts: 1848
Joined: Wed Nov 10, 2004 6:47 pm

Post by Disch »

blargg wrote:Disch, sorry about the slight pops that the ROM makes. I looked at the asm earlier today and saw that it does create them because I'm setting the sweep shift a few cycles after changing the period; setting it before keeps the silencing in effect continuously. Maybe the pops are louder on your emulator due to the method of band-limiting you use (averaging). I'll make sure future sound test ROMs generate more unambiguous output.
I've actually implimented a a method based on your BL-synth docs (the whole sine wave transition buffer thing). However the pops are a lot more significant than they should be -- the up transition and down transition are waaaaay further apart than just a few cycles. There must be a slight problem with my sound timing somewhere.. I'm still looking into it.
Disch, do you know of any NSFs that never return from their init or play routines, just sit there in a loop handling timing themselves?
Not very many. The only ones I know of (that intentionally don't return from the Play routine) are ones that do timed $4011 writes... and even then it's only a few tracks on the whole NSF. First one that comes to mind is Mitokoumon. Xod did that TMNT theme song dealie with an NSF as well (did the whole TMNT cartoon theme song through $4011 streaming). I think Blades of Steel has a few tracks too...
User avatar
Zepper
Formerly Fx3
Posts: 3262
Joined: Fri Nov 12, 2004 4:59 pm
Location: Brazil
Contact:

Post by Zepper »

Quietust wrote:Looking at the APU Frame Counter wiki page, the frame sequences seem wrong somehow - are these taking into account the fact that writing $4017 starts the 4-frame sequence after one quarter frame versus starting the 5-frame sequence immediately?
Well, the wiki is still outdated (missing the information above).
Post Reply