It is currently Fri May 25, 2018 2:28 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 25 posts ]  Go to page 1, 2  Next
Author Message
PostPosted: Thu Nov 07, 2013 12:36 pm 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1344
Hi, so when playing Rockman 2 (Megaman 2), I am getting a buzzing sound all over the place. This can be fixed with band-limited resampling or sinc resampling, but those things are rather intensive.

aliaspider recently noted that by adding "if(period == 0) return 0;" to the top of my APU::Triangle::clock() function, that the buzzing went away completely, even with a simple hermite audio filter (many, many times less CPU intensive.)

Could anyone please look at my triangle emulation code and let me know if that is a proper fix (eg how the hardware really works), or if the game is really supposed to create this buzzing sound without proper audio resampling?

(Note that I use a variable-length integer class. So a uint5 type, when the value is 31, and you add one, will wrap to zero automatically. Much like your traditional uint8 type does now from 255->0. This is why there aren't as many bit-masks in the below code.)

Many thanks in advance!

Code:
struct Triangle {
  unsigned length_counter;

  uint8 linear_length;
  bool halt_length_counter;

  uint11 period;
  unsigned period_counter;

  uint5 step_counter;
  uint8 linear_length_counter;
  bool reload_linear;

  void clock_length();
  void clock_linear_length();
  uint8 clock();

  void power();
  void reset();
} triangle;


Code:
void APU::Triangle::clock_length() {
  if(halt_length_counter == 0) {
    if(length_counter > 0) length_counter--;
  }
}

void APU::Triangle::clock_linear_length() {
  if(reload_linear) {
    linear_length_counter = linear_length;
  } else if(linear_length_counter) {
    linear_length_counter--;
  }

  if(halt_length_counter == 0) reload_linear = false;
}

uint8 APU::Triangle::clock() {
  uint8 result = step_counter & 0x0f;
  if((step_counter & 0x10) == 0) result ^= 0x0f;
  if(length_counter == 0 || linear_length_counter == 0) return result;

  if(--period_counter == 0) {
    step_counter++;
    period_counter = period + 1;
  }

  return result;
}

void APU::Triangle::power() {
  reset();
}

void APU::Triangle::reset() {
  length_counter = 0;

  linear_length = 0;
  halt_length_counter = 0;
  period = 0;
  period_counter = 1;
  step_counter = 0;
  linear_length_counter = 0;
  reload_linear = 0;
}


Code:
void APU::write(uint16 addr, uint8 data) {
  const unsigned n = (addr >> 2) & 1;  //pulse#

  switch(addr) {
  ...
  case 0x4008:
    triangle.halt_length_counter = data & 0x80;
    triangle.linear_length = data & 0x7f;
    break;

  case 0x400a:
    triangle.period = (triangle.period & 0x0700) | (data << 0);
    break;

  case 0x400b:
    triangle.period = (triangle.period & 0x00ff) | (data << 8);

    triangle.reload_linear = true;

    if(enabled_channels & (1 << 2)) {
      triangle.length_counter = length_counter_table[(data >> 3) & 0x1f];
    }
    break;

  case 0x4015:
    ...
    if((data & 0x04) == 0) triangle.length_counter = 0;
    break;
  }
}

void APU::clock_frame_counter() {
  frame.counter++;

  if(frame.counter & 1) {
    pulse[0].clock_length();
    pulse[0].sweep.clock(0);
    pulse[1].clock_length();
    pulse[1].sweep.clock(1);
    triangle.clock_length();
    noise.clock_length();
  }

  pulse[0].envelope.clock();
  pulse[1].envelope.clock();
  triangle.clock_linear_length();
  noise.envelope.clock();

  if(frame.counter == 0) {
    if(frame.mode & 2) frame.divider += FrameCounter::NtscPeriod;
    if(frame.mode == 0) {
      frame.irq_pending = true;
      set_irq_line();
    }
  }
}


Top
 Profile  
 
PostPosted: Thu Nov 07, 2013 1:08 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6291
Location: Canada
The hardware triangle is NOT halted by 0 in the period register.

It's very normal to halt it in emulation because of the reason you mentioned. It's really hard to implement a filter than can cut it out as nicely as your analog hardware does.

You can verify this on hardware by setting the period to 0 and turning the triangle on and off. You'll hear a popping where the the triangle starts and stops.

My choice for NSFPlay was to halt on period 0 by default, but I gave the option to turn it off (even though my oversampling process is not good enough to suppress it entirely, it still comes through as a high pitched aliased ringing).


Top
 Profile  
 
PostPosted: Thu Nov 07, 2013 1:24 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 20069
Location: NE Indiana, USA (NTSC)
One hack that's probably close enough is to output a constant 7 while the period value is less than 2.


Top
 Profile  
 
PostPosted: Thu Nov 07, 2013 1:37 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7136
Location: Seattle
As everyone else as said, you've run into the world's worst aliasing problem. In practice, you're probably safest using tepples's suggestion, possible even for periods as low as 0-3, since all of those are ultrasonic for most people.


Top
 Profile  
 
PostPosted: Thu Nov 07, 2013 3:57 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6291
Location: Canada
AFAIK the only one that matters in practice is period 0. I've seen many games that use period 0 instead of halt (e.g. Mega Man 2, Silver Surfer) but I've never seen any try to use other ultrasonic periods for anything. There's no reason for anyone to do so, really.


Top
 Profile  
 
PostPosted: Thu Nov 07, 2013 6:30 pm 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1344
Alright, thank you very much for the information.

Was hoping for an easy and correct fix =(

Only cheap solutions seem to be to falsely halt on period 0, or to simply decimate the audio 32x prior to resampling.

I suppose I'll keep looking for a more efficient / simplistic resampling algorithm than windowed sinc / band-limited synthesis that can remove the aliasing ...


Top
 Profile  
 
PostPosted: Thu Nov 07, 2013 6:35 pm 
Offline

Joined: Sun Apr 13, 2008 11:12 am
Posts: 7136
Location: Seattle
Tepples's suggestion (just have the DAC output a constant value of 7.5 when ultrasonic) is actually what correct antialiasing would result in, so I'm not certain it's actually a hack. Especially if you keep the phase running for whenever the game brings the pitch back down to audible frequencies.


Top
 Profile  
 
PostPosted: Thu Nov 07, 2013 8:53 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 6291
Location: Canada
It's what correct anti-aliasing would do when the triangle state is not changing. It's not correct during any transition, however. As I mentioned, the triangle can still be halted in its high frequency state, and this absolutely makes audible noise on the hardware. This isn't important to emulating any game I know of, however. Possibly Mega Man 2's triangle has a bit more bite if you correctly emulate this, though aesthetically it may be worse off for it.

I prefer to halt the triangle instead of jumping to 7.5 because it very slightly reduces the popping noise of a halt by freezing at current level instead of jumping to 7. It's perhaps a tradeoff, though, since the triangle position also influences the nonlinear mix. Though, the standard nonlinear mix curve we currently use isn't totally accurate to begin with, not to mention the huge variability in hardware mix between the two APU pins, so that kind of subtlety is a bit lost on the NES as a general rule. The games that do this are really just trying to silence the triangle anyway, so it's not a big deal either way. Both methods result in the expected silent triangle, and the difference is quite subtle. (I doubt I could pick them apart in a blind test.)


Top
 Profile  
 
PostPosted: Fri Nov 08, 2013 6:06 am 
Offline

Joined: Thu Oct 05, 2006 6:29 am
Posts: 911
Quote:
This can be fixed with band-limited resampling or sinc resampling, but those things are rather intensive

Is the resampling performance really that big of a deal? (unless you're targeting really old hardware). I'm using Blargg's Blip_Buffer in a chiptune player I'm writing for mobile phones. As an example, my GBS player uses a total of 8 Blip_Synths (4-channel stereo), each clocked at 1 Mhz, and this runs just fine on my Galaxy S2+.


Top
 Profile  
 
PostPosted: Fri Nov 08, 2013 6:58 am 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 20069
Location: NE Indiana, USA (NTSC)
As I understand it, the performance of BLEP resampling algorithms like blargg's is O(n) in the number of transitions. Doing it without hacks is fine for square and DMC because they change less often. DMC is limited to how fast writes to $4011 can occur, and square is limited to two transitions per 144 cycles because it shuts off when the period value becomes less than 8. But triangle and noise can have a transition every 1 or 2 cycles. Do GBS files routinely drive the wavetable at period $7FF (inverted vs. NES) and noise at maximum rate?


Top
 Profile  
 
PostPosted: Fri Nov 08, 2013 7:13 am 
Offline

Joined: Thu Oct 05, 2006 6:29 am
Posts: 911
Quote:
Do GBS files routinely drive the wavetable at period $7FF (inverted vs. NES) and noise at maximum rate?

Probably not. My point was that I have 8 synths (which I know is overkill) and still get performance that is good enough for a mobile phone, and you'd really only need 1 or 2 synths for NES audio (ignoring expansion audio).


Top
 Profile  
 
PostPosted: Fri Nov 08, 2013 9:16 am 
Offline
User avatar

Joined: Sat Jan 22, 2005 8:51 am
Posts: 427
Location: Chicago, IL
byuu wrote:
This can be fixed with band-limited resampling or sinc resampling, but those things are rather intensive.

Aren't you famous for your emulators' CPU requirements? Surely, a little more wouldn't hurt :)

I wrote a low CPU utilization SSE filter (512 tap FIR. IIRC, it accounts for about .3% CPU utilization on my 1.86GHz Core 2 Duo laptop). If you (or anyone else) are interested, I'll post the code.

_________________
get nemulator
http://nemulator.com


Top
 Profile  
 
PostPosted: Fri Nov 08, 2013 9:53 am 
Offline

Joined: Wed Sep 13, 2006 12:45 pm
Posts: 56
FWIW, you can get a massive performance boost in a bandlimited polyphase resampler if you don't care if the sample rate conversion ratio is slightly off(fewer impulse response phases need to be stored, and you don't have to interpolate between phases anymore).

Like so:

1789772.72 * 7 / 261 = 48001.5672
1789772.72 * 12 / 487 = 44101.1756

...and by using 16-bit samples and impulse response coefficients, and creating four copies of each phase's impulse response with zero-padding on the front(and end for SIMD multiply granularity) to account for possible alignments of the input samples read position, you can utilize MMX to make a decent-quality resampler that'll run significantly faster than realtime even on a Pentium II.


Top
 Profile  
 
PostPosted: Fri Nov 08, 2013 10:33 am 
Offline
User avatar

Joined: Sat Jan 22, 2005 8:51 am
Posts: 427
Location: Chicago, IL
In the current release of nemulator, I decimate the APU output by 40 to get a ~44671Hz sample rate. I then adjust the buffer's playback rate accordingly and let Windows deal with resampling it to the sound card's playback rate. I wish I knew exactly what it was doing under the hood...

Anyway, decimating by 40 (using floats with appropriately sized filter and sample buffers) allows everything to be aligned on 16-byte boundaries, so the SSE implementation is really fast.

I'm currently working on an arbitrary sample rate converter (that doesn't require the OS to perform sample rate conversion). It's working well, but results in a few % additional CPU utilization. I think I'll be able to get it within a couple of points of the current implementation, though.

_________________
get nemulator
http://nemulator.com


Top
 Profile  
 
PostPosted: Fri Nov 08, 2013 11:27 pm 
Offline

Joined: Mon Mar 27, 2006 5:23 pm
Posts: 1344
James wrote:
Aren't you famous for your emulators' CPU requirements?


Probably, but I try not to entirely waste CPU cycles. They usually go to emulating fine details. The sinc filter cuts the framerate in half in NES and GB mode. When emulating the SNES+GB at the same time with that filter, it gets pretty bogged down.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 25 posts ]  Go to page 1, 2  Next

All times are UTC - 7 hours


Who is online

Users browsing this forum: Google Adsense [Bot] and 3 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group