Log in

No account? Create an account

Previous Entry | Next Entry

NES DAC Linearity, Part I

In my ongoing adventure tinkering with the NES, one thing I had to try was playing some music through the built-in DAC. It's 7-bit, and you can get reasonable audio quality if you don't mind devoting the whole CPU (and quite a lot of memory) to it. I threw together a little demo that plays an 8 second music loop at around 30 KHz. Once the novelty of hearing it work wore off, I noticed the audio was distorted, particularly audible in the cymbols. Once I confirmed it wasn't just on my own emulator that it sounded distorted, I thought back to implementing my own NES sound emulation, primarily using apu_ref.txt as a reference. Toward the end of that document is an equation which models how the sound channels are mixed together, which is obviously not linear. Curious, I made a plot of DAC input versus mixer output (actually, I wrote a 6502 program which writes a ramp waveform to the DAC and captured the output). It's *extremely* bowed in the middle (see below), explaining the distortion. It was straightforward invert the curve and preprocess my audio accordingly, which improved the audio considerably.

I get the impression many emulators follow a curve similar to the one described in that document. I tried my test on Nestopia, FCEU, and (of course) my own emulator, and they behave similarly in this respect. On the other hand, Nintendulator seems to mix channels differently, because my original version of the audio test sounds better there. Unfortunately, I haven't tried this on a real NES yet, because it's bigger (256KB) than I can conveniently program to an EPROM right now, and I've have to build a new EPROM cart to host it even so (my current one only handles 64 KB). I should probably just buy a PowerPak and/or a CopyNES, but I'm still enjoying desoldering donor carts and playing with chips. =p

If I couldn't try the full music loop on a real NES, at least I could write some simpler test programs and run them from my EPROM cart. It also occured to me that aside from the nonlinearity due to the channel mixing, there might be some additional nonlinearity inherent in the DAC, significant enough for someone trying to get the best audio they could from the machine, and it'd be interesting to try and measure. For my first attempts at this, I took the approach of generating test signals in software from the NES, recording into my MOTU audio interface from the NES audio output, analyzing the resulting recordings with a little CL code, and finally plotting the results using Octave (shown below).

  • "Square rising edge" - a square wave at a few hundred Hz (220? 440? I forget.), increasing in amplitude each cycle from 0 to 127. I measured the height of the rising edge. I let this repeat 29 times and averaged the results. I screwed the program up and only captured every other amplitude level (incrementing the amplitude variable on every clock edge instead of just one).
  • "Pulse 1" - Same idea, ramping up the amplitude of a brief pulse. I measured the height of the largest rising (or rather falling) edge in the viscinity of the pulse. I let this loop for 20 or 30 minutes, averaging out 1,440 samples per amplitude level. The individual ramps didn't look right, having a curious moire-like modulation as the energy of the pulse fell differently into samples, but it averaged out into a very nice curve. Unfortunately, it was different enough from the previous curve to bother me, although it shared some common features.
  • "Pulse 2" - I thought I'd try lengthening the pulse, hoping I might get cleaner data. Instead I got the opposite - the resulting curve, even average over 1,300 samples per amplitude level, is extremely jagged and irregular. I haven't figured out what went wrong here

I can imagine various sources of systematic error that invalidate these measurements, but (ignoring the screwy "Pulse 2" measurements) there's a really interesting feature apparent. - namely, the jumps occuring around power of two transitions, most prominent in the three highest bits of the DAC input.

I'd really hoped these measurements would agree, so that I could divide out the affect of the mixer equation and have something like a definitive measurement of the DAC linearity which I could build into my emulator (and use to correct audio for playback on the real hardware). As it is, I'll have to try again. It would probably make a lot more sense to measure from the output pin on the NES CPU, rather than measuring after it has passed through the final mixing and filtering circuits, but I haven't tried this yet. Ideally I could program a DC level into the DAC and measure it on the output pin at my leisure. To this end I put together a little DAC test program which lets you select the output level and toggle the signal on and off. This program, along with various other hacks including the signal generators for the three tests above, is on my NES test cart image.