Updated apudsp.txt

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Updated apudsp.txt
by on (#155960)
Hello all,

After finally running through all 34,917 SPCs in the snesmusic.org archive and verifying the output of my FPGA SPC emulator against Blargg's snes_spc-0.9.0 emulator I went ahead and updated apudsp.txt with all of the fixes/bugs that I found in it that were contrary to Blargg's implementation. I also included a number of additional clarifications/details. I combed through all of the changes that I made three times to make sure they were right so this new version should be very accurate. To see what changes were made simply diff this version against Anomie's v1160 apudsp.txt.

https://dl.dropboxusercontent.com/u/36237540/SNES/jwdonal/apudsp_jwdonal.txt

I hope someone finds it useful.

Cya!

Jonathon
Re: Updated apudsp.txt
by on (#155963)
Maybe if you tell us explicitely what was wrong in the text file and what needed to be fixed, it would be more helpful. Don't hesitate to upload in on RHDN, also.
Re: Updated apudsp.txt
by on (#155964)
Don't know if this will be useful to anyone, but here's something I came up with a while back to do rate counters without division, though its starting state as presented here is one clocking off from apudsp.txt:

Code:
static INLINE bool CheckRC(unsigned rate)
{
 static const uint16 RateMasks[32] =
 {
  0x0000, 0xFFE0, 0x3FF8,
  0x1FE7, 0x7FE0, 0x1FF8,
  0x0FE7, 0x3FE0, 0x0FF8,
  0x07E7, 0x1FE0, 0x07F8,
  0x03E7, 0x0FE0, 0x03F8,
  0x01E7, 0x07E0, 0x01F8,
  0x00E7, 0x03E0, 0x00F8,
  0x0067, 0x01E0, 0x0078,
  0x0027, 0x00E0, 0x0038,
  0x0007, 0x0060, 0x0018,
          0x0020,
          0x0000
 };

 static const uint16 RateXOR[32] =
 {
  0xffff, 0x0000, 0x3e08,
  0x1d04, 0x0000, 0x1e08,
  0x0d04, 0x0000, 0x0e08,
  0x0504, 0x0000, 0x0608,
  0x0104, 0x0000, 0x0208,
  0x0104, 0x0000, 0x0008,
  0x0004, 0x0000, 0x0008,
  0x0004, 0x0000, 0x0008,
  0x0004, 0x0000, 0x0008,
  0x0004, 0x0000, 0x0008,
          0x0000,
          0x0000
 };

 return !((HGS.RateCounter & RateMasks[rate]) ^ RateXOR[rate]);
}

static INLINE void ResetRC(void)
{
 HGS.RateCounter = 0;
}

static INLINE void ClockRC(void)
{
 if(!(HGS.RateCounter & 0x7))
  HGS.RateCounter ^= 0x5;
   
 if(!(HGS.RateCounter & 0x18))
  HGS.RateCounter ^= 0x18;

 HGS.RateCounter -= 0x29;
}
Re: Updated apudsp.txt
by on (#155980)
Mednafen wrote:
Don't know if this will be useful to anyone, but here's something I came up with a while back to do rate counters without division
That's actually very cool that you figured that out, but I have to ask the same question of you as I did when I saw the original apudsp.txt modulus implementation...How in the heck did you figure out that this approach would work? I mean, it seems so arbitrary but there's obviously some logic behind it.

All I see is, "Well, all I should have to do is mask the counter with 7, XOR with with 5, mask with 0x18 and XOR with 0x18, then subtract 0x29! Then if I use that value to index into these tables of (seemingly even more arbitrary) values it should work! Of course! So simple!"

:shock: Wha??

Also, does this method also account for the relative synchronization when switching between different rates (i.e. the first cycle is shorter than the rest depending on when the switch occurs). I'll have to try this out myself when I get home to see if I get the same exact output.

Bregalad wrote:
Maybe if you tell us explicitly what was wrong in the text file and what needed to be fixed, it would be more helpful.
Just diff'ing the original and new files will give you that same information.

Bregalad wrote:
Don't hesitate to upload in on RHDN, also.
Thanks, hadn't thought of that. I'll do it when I get home later tonight.
Re: Updated apudsp.txt
by on (#156042)
Mednafen wrote:
Don't know if this will be useful to anyone, but here's something I came up with a while back to do rate counters without division
I tried to use this in Blargg's emulator and basically nothing worked - just some blips/beeps. I just replaced the original global counter code with yours. It was really straightforward so I don't think I could have done anything wrong.
Re: Updated apudsp.txt
by on (#156063)
Bregalad wrote:
Don't hesitate to upload in on RHDN, also.
It's been submitted and accepted to RHDN. Thanks for the advice.
Re: Updated apudsp.txt
by on (#156064)
Wow, you sure improved the document a lot. Thank you very much for your work.
Re: Updated apudsp.txt
by on (#156065)
No problem. It's the bare minimum that I can do for all the help that nesdev has given me.
Re: Updated apudsp.txt
by on (#156139)
jwdonal wrote:
Mednafen wrote:
Don't know if this will be useful to anyone, but here's something I came up with a while back to do rate counters without division
I tried to use this in Blargg's emulator and basically nothing worked - just some blips/beeps. I just replaced the original global counter code with yours. It was really straightforward so I don't think I could have done anything wrong.


Did you accidentally invert the meaning of the return value of CheckRC()?
Re: Updated apudsp.txt
by on (#156181)
Mednafen wrote:
Did you accidentally invert the meaning of the return value of CheckRC()?
Yep, actually I did. And to my amazement your method produces an exact identical match output to Blargg's modulus method for a very large SPC set that I compared against.

I'd like to add this method to the apudsp.txt document to give programmers a couple options to pick from. But to do that I will need some kind of description on why and how this works. Could you please explain how you came up with this? Especially how and why all of the XOR math works out or what it means.

Cool stuff!
Re: Updated apudsp.txt
by on (#156184)
RateCounter contains /5 and /3 dividers, and a /1 counter shoved into one variable. RateMasks selects how many bits of the /1 counter to use(to give /1, /2, /4, /8, etc.), and to optionally select the bits of the /5 or /3 divider(to optionally give rates like /5 or /10 or /20 etc., or /3 or /6 or /12 etc.). RateXOR(could also think of it as a RateCompare) is to adjust the relative timing offsets, and to handle the rate = 0 case.

The ^= could be replaced with |= in ClockRC, and have the same effect(it's only ^= because I was experimenting with counting up instead of down when I first wrote the code, and it wasn't hurting anything to leave it as-is).
Re: Updated apudsp.txt
by on (#156194)
This is super cool. Thanks for the explanation. I'll add it to apudsp.txt and update it on RHDN. Nice work.
Re: Updated apudsp.txt
by on (#156653)
This is my implementation of Rate counter, I test it side by side with blargg's implementation and seems no bit off. I actually use 3 different counter train and no division too.
mcounter1 is 11 bit binary counter
mcounter2 is 9 bit binary counter with a /3 prescaler
mcounter3 is 8 bit binary counter with a /5 prescaler

Each bit to output is very simple in terms of hardware, it can be implemented with one flip-flop and a 2 input AND gate, where the output of the AND gate propandgates to higher level stages.

Code:
int mcounter1;
int mcounter2;
int mcounter3;
int prescale2;
int prescale3;
int wcounter2;         // width of counter 2 adding prescaler to LSB
int wcounter3;         // width of counter 3 adding prescaler to LSB


inline void SPC_DSP::init_counter()
{
   mcounter1 = 0x7FF;
//----
   mcounter2 = 0xA5;
   prescale2 = 0;
//----
   mcounter3 = 0x94;
   prescale3 = 0x03;
}

inline void SPC_DSP::run_counters()
{
   if (mcounter1 == 0x7FF) {      // 11 bit counter
      mcounter1 = 0;
   } else {
      mcounter1++;
   }

   if (prescale2 == 2) {
      prescale2 = 0;
      if (mcounter2 == 0x1FF) {      // 9 bit counter
         mcounter2 = 0;
      } else {
         mcounter2++;
      }
   } else {
      prescale2++;
   }

   if (prescale3 == 4) {
      prescale3 = 0;
      if (mcounter3 == 0xFF) {      // 8 bit counter
         mcounter3 = 0;
      } else {
         mcounter3++;
      }
   } else {
      prescale3++;
   }
}

inline unsigned SPC_DSP::read_counter( int rate )
{
   int i;
   wcounter2 = (mcounter2 << 1) + ((prescale2 == 0x02) ? 1 : 0);
   wcounter3 = (mcounter3 << 1) + ((prescale3 == 0x04) ? 1 : 0);

   switch (rate) {
   case   31 : i = 0;                               break;   // 0 always advance
   case   30 : i = ((mcounter1 & 0x001) == 0x001) ? 0 : 1; break; // CA0
   case   29 : i = ((wcounter2 & 0x001) == 0x001) ? 0 : 1; break; // CB0
   case   28 : i = ((mcounter1 & 0x003) == 0x003) ? 0 : 1; break; // CA1
   case   27 : i = ((wcounter3 & 0x001) == 0x001) ? 0 : 1; break; // CC0
   case   26 : i = ((wcounter2 & 0x003) == 0x003) ? 0 : 1; break; // CB1
   case   25 : i = ((mcounter1 & 0x007) == 0x007) ? 0 : 1; break; // CA2
   case   24 : i = ((wcounter3 & 0x003) == 0x003) ? 0 : 1; break; // CC1
   case   23 : i = ((wcounter2 & 0x007) == 0x007) ? 0 : 1; break; // CB2
   case   22 : i = ((mcounter1 & 0x00F) == 0x00F) ? 0 : 1; break; // CA3
   case   21 : i = ((wcounter3 & 0x007) == 0x007) ? 0 : 1; break; // CC2
   case   20 : i = ((wcounter2 & 0x00F) == 0x00F) ? 0 : 1; break; // CB3
   case   19 : i = ((mcounter1 & 0x01F) == 0x01F) ? 0 : 1; break; // CA4
   case   18 : i = ((wcounter3 & 0x00F) == 0x00F) ? 0 : 1; break; // CC3
   case   17 : i = ((wcounter2 & 0x01F) == 0x01F) ? 0 : 1; break; // CB4
   case   16 : i = ((mcounter1 & 0x03F) == 0x03F) ? 0 : 1; break; // CA5
   case   15 : i = ((wcounter3 & 0x01F) == 0x01F) ? 0 : 1; break; // CC4
   case   14 : i = ((wcounter2 & 0x03F) == 0x03F) ? 0 : 1; break; // CB5
   case   13 : i = ((mcounter1 & 0x07F) == 0x07F) ? 0 : 1; break; // CA6
   case   12 : i = ((wcounter3 & 0x03F) == 0x03F) ? 0 : 1; break; // CC5
   case   11 : i = ((wcounter2 & 0x07F) == 0x07F) ? 0 : 1; break; // CB6
   case   10 : i = ((mcounter1 & 0x0FF) == 0x0FF) ? 0 : 1; break; // CA7
   case    9 : i = ((wcounter3 & 0x07F) == 0x07F) ? 0 : 1; break; // CC6
   case    8 : i = ((wcounter2 & 0x0FF) == 0x0FF) ? 0 : 1; break; // CB7
   case    7 : i = ((mcounter1 & 0x1FF) == 0x1FF) ? 0 : 1; break; // CA8
   case    6 : i = ((wcounter3 & 0x0FF) == 0x0FF) ? 0 : 1; break; // CC7
   case    5 : i = ((wcounter2 & 0x1FF) == 0x1FF) ? 0 : 1; break; // CB8
   case    4 : i = ((mcounter1 & 0x3FF) == 0x3FF) ? 0 : 1; break; // CA9
   case    3 : i = ((wcounter3 & 0x1FF) == 0x1FF) ? 0 : 1; break; // CC8
   case    2 : i = ((wcounter2 & 0x3FF) == 0x3FF) ? 0 : 1; break; // CB9
   case    1 : i = ((mcounter1 & 0x7FF) == 0x7FF) ? 0 : 1; break; // CA10
   case    0 : i = 1;                               break; // 1 always off
   }

   return i;
}