SPC questions

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
SPC questions
by on (#33597)
I don't know accurate knownledge about the SPC currently is, but I've a couple of questions :

- What kind of delay there is with key on/key off operations ? I've tried to make test programms, if I key on without ever keying off, I get an annoying slapping sound (I don't know about on the real hardware). If I key off right before I key on, on some SPC players/emulators the key on is ignored. Is the solution to key off a decent time before each key on the only accurate/viable solution ?

- About the DIV instruction.... It takes 16 bit input and only 8-bit output. What happen if you do 65535/1 ? The result would sure not fit 8-bit quotient+8-bit remainder. I guess you only get low 8 bits of result but the real remainder (that's the only thing that make sense when using shift-and-substract method). What does division by zero output ? I guess result is always $ff and remainder is the original dividend (that would make the most sense again) but no doccument specifies that.

- Is it possible to change echo delay during playback very quickly to get reverb ?

- When echo is enabled back on a channel after being disabled, I suppose this only allow output from that channel to go into the echo buffer to be heard again later at lower volume. I've seen some SPC players/emulators emulate it so that as soon as echo is enabled past sound data is heard from that channel even if it was disabled before. As I don't see how the past data could go into the echo buffer (unless each channel has its own individual echo buffer, which isn't the case), I guess this can only be wrong, but again I'm not sure.

- The processor have DI and EI instruction to enable/disable interrups. But is there any other source of interrupt than reset ? (I doubt reset is maskable). It's said that reset vector is stored at $fffe but I don't see any other interrupt vector mentionned in docs.
Re: SPC questions
by on (#33600)
Bregalad wrote:
- What kind of delay there is with key on/key off operations ?


Key On / Off are both polled once every 64 cycles (every other sample).

With Key Off, the voice switches to Release mode (but envelope is not changed).

With Key On, the voice switches to Attack, Envelope is reset to 0, and BRR decoding resets. Those last two can cause a very ugly pop if done carelessly (this is probably the "slap" you're talking about).

If you Key On / Key Off at the same time (or within the same 64 cycle window), iirc Key On takes priority, but the channel will be keyed off 64 cycles later because Key On is automatically cleared, whereas Key Off isn't. In either case, the end result would be abrupt silence because envelope is reset to 0 on keyon, then switched to Release on keyoff -- and there's not enough time in between them for any samples to become audible (keying on causes about 5 samples of silence to allow for the BRR decoder to get samples ready)

Quote:
Is the solution to key off a decent time before each key on the only accurate/viable solution ?


Pretty much, yeah. Abrupt envelope changes will cause ugly pops, and the best way I can think of to avoid that is to let Release finish its job before restarting. At full envelope ($7FF) it'll take 256 samples (8192 cycles) for the envelope to release completely. So:

Code:
mov $F2, #$5C
mov $F3, #%00000001   ; key off voice 0

; ~8192 cycles later
mov $F2, #$5C
mov $F3, #0            ; clear keyoff
mov $F2, #$4C
mov $F3, #%00000001   ; key on voice 0


Since keyon is cleared automatically once processed, you don't have to clear it yourself. However that is not true for keyoff -- you must manually clear it.

Code:
About the DIV instruction.... It takes 16 bit input and only 8-bit output. What happen if you do 65535/1 ?


Any division that results in a quotient higher than $200 will return more-or-less meaningless results. Programs can check the V flag after division to make sure such a case didn't happen (V will be set when quotient >= $100).

The shift algorithm in Anomie's doc can't be right -- I tried it with some sample numbers and got incorrect results ($1000 / $80 got me $1F) -- unless the SPC's division really is that unreliable?

I took the following code from BSNES:

Code:
  ya = regs.ya;
//overflow set if quotient >= 256
  regs.p.v = !!(regs.y >= regs.x);
  regs.p.h = !!((regs.y & 15) >= (regs.x & 15));
  if(regs.y < (regs.x << 1)) {
  //if quotient is <= 511 (will fit into 9-bit result)
    regs.a = ya / regs.x;
    regs.y = ya % regs.x;
  } else {
  //otherwise, the quotient won't fit into regs.p.v + regs.a
  //this emulates the odd behavior of the S-SMP in this case
    regs.a = 255    - (ya - (regs.x << 9)) / (256 - regs.x);
    regs.y = regs.x + (ya - (regs.x << 9)) % (256 - regs.x);
  }
//result is set based on a (quotient) only
  regs.p.n = !!(regs.a & 0x80);
  regs.p.z = (regs.a == 0);


Quote:
- Is it possible to change echo delay during playback very quickly to get reverb ?


Not entirely sure what you're trying to accomplish, but probably not. The Echo delay reg is only used when the echo comes full circle back to the start of the ring buffer. So if you have an echo size of $1000 samples (EDL=8), and you change EDL to 0 -- it could be up to $1000 samples before the newly written delay takes effect.

Quote:
- When echo is enabled back on a channel after being disabled, I suppose this only allow output from that channel to go into the echo buffer to be heard again later at lower volume.


Correct -- though it doesn't necessarily have to be a lower volume ;P (but it probably should for the sake of not damaging your eardrums)

Quote:
- The processor have DI and EI instruction to enable/disable interrups. But is there any other source of interrupt than reset ? (I doubt reset is maskable). It's said that reset vector is stored at $fffe but I don't see any other interrupt vector mentionned in docs.


Not that I know of. I look at the I flag on the SPC like I look at the D flag on the 2A03. The only impact it really has is changing the value pushed when status is put on the stack.

by on (#33609)
Quote:
The shift algorithm in Anomie's doc can't be right -- I tried it with some sample numbers and got incorrect results ($1000 / $80 got me $1F) -- unless the SPC's division really is that unreliable?

Anomie's and my division algorithm are both correct, I believe. I'm pretty sure I compared his for every input with mine. My version was compared with hardware for all 16.8 million possible inputs (took about 13 minutes to run on a SNES). bsnes uses mine, which just takes advantage of the host's division operator, rather than doing itcloser to how the SPC-700 probably does it in hardware, as Anomie's does.
Quote:
The processor have DI and EI instruction to enable/disable interrups. But is there any other source of interrupt than reset ? (I doubt reset is maskable).

No interrupt pin known, so EI, DI, and SLEEP are almost useless. Presumably SLEEP would halt the processor until an interrupt was received, much like WAI on the 65816.

Bregalad, bsnes should handle all the things you mention accurately. Are you unable to run it for some reason?

by on (#33614)
Ah, it's a good thing to know BSNES is that accurate. I knew it was accurate when it comes to video, but I wouldn't exept it to mean the SPC is accurate too, becuase as far I know Nintendulator is very very accurate for vido it isn't that accurate for sound.

Thanks for the usefull replies. BTW using the hosts division wouldn't throw an exeption if divizor is zero, as opposed to the old shift method which would work in all cases ?

by on (#33615)
Bregalad wrote:
BTW using the hosts division wouldn't throw an exeption if divizor is zero, as opposed to the old shift method which would work in all cases ?

The denominator can never be zero in the above code. In the first case, for y to be < x*2, x must be non-zero. In the second case, 256-x is always non-zero.

by on (#33618)
blargg wrote:
Anomie's and my division algorithm are both correct, I believe. I'm pretty sure I compared his for every input with mine.


Could I have transcribed it wrong or something?

Here is the algorithm in question:

Code:
  uint17 yva=YA
  uint17 x=X<<9
  loop 9 times {
      ROL yva
      if(yva>x) yva=yva XOR 1
      if(yva&1) yva-=x       // remember, clip to 17 bits
  }


A test program I wrote:

Code:
      // YA and X gotten from user
      yva = YA;
      x = X << 9;
      for(i = 0; i < 9; ++i)
      {
         yva <<= 1;
         if(yva & 0x20000)   yva ^= 0x20001;
         if(yva > x)         yva ^= 1;
         if(yva & 1)         yva = (yva - x) & 0x1FFFF;
      }

      printf("YVA=%02X,%d,%02X",
         yva >> 9,
         (yva >> 8) & 1,
         yva & 0xFF);

      if(X)
      {
         printf(" -- mod=%02X, quo=%02X\n",
            YA % X,
            YA / X);
      }
      else
         printf("\n");


output of program when given "1000 80" is:

YVA=80,0,1F -- mod=00, quo=20

That would be right if you subtract 80 from Y and add 1 to A (which I just saw, now, actually. When I ran this test before, somehow I didn't see that). So I guess you'd have to do an additional "if(Y>=X)" check afterward?

Also, while I didn't test it, it looks theoretically possible for V to be clear with Anomie's algorithm when the quotient is > $1FF, whereas with your C version, V would always be set in that case. I'll have to look at that closer and come up with an example for that though -- I could be wrong.

by on (#33619)
How odd, the older version of anomie's algorithm had >= x, not >x. Here's code that I've tested for all 2^24 combinations against mine, and against the output when tested on the SNES:
Code:
int x9 = x << 9;
int yva = y*0x100 + a;
for ( int i = 0; i < 9; i++ )
{
    yva = yva * 2;
    if ( yva & 0x20000 )
        yva = (yva ^ 1) & 0x1FFFF;
   
    if ( yva >= x9 ) // this differs from anomie's doc
        yva = yva ^ 1;
   
    if ( yva & 1 )
        yva = (yva - x9) & 0x1FFFF;
}

v_flag = (yva & 0x100) != 0;
y = yva>>9 & 0xFF;
a = yva & 0xFF;