Updating APU Regs Multiple Times Per Frame

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Updating APU Regs Multiple Times Per Frame

Post by lidnariq »

supercat wrote:I might use a 555 timer simply wired to the IRQ.
The ordinary cheap resistors and capacitors needed for the 555 will drift in value enough to become audibly out of tune, unfortunately. (Threshold is 0.4%). Even the jitter might even be bad enough to be noticeable during operation.

One could buy higher-precision parts, but I don't know if a 555 with those better RCs is still cheaper than various higher-precision options. (e.g. 74'393 generating an IRQ every 128cy)
supercat
Posts: 161
Joined: Thu Apr 18, 2019 9:13 am

Re: Updating APU Regs Multiple Times Per Frame

Post by supercat »

lidnariq wrote:
supercat wrote:I might use a 555 timer simply wired to the IRQ.
The ordinary cheap resistors and capacitors needed for the 555 will drift in value enough to become audibly out of tune, unfortunately. (Threshold is 0.4%). Even the jitter might even be bad enough to be noticeable during operation.

One could buy higher-precision parts, but I don't know if a 555 with those better RCs is still cheaper than various higher-precision options. (e.g. 74'393 generating an IRQ every 128cy)
Being +/-5% would be horrible if anything was trying to play along with the NES music, but if all music uses the same pitch reference it would probably be tolerable. The biggest issue I see with any kind of interrupt-driven music is interrupt timing jitter, which would require a feature that could be incorporated in a CPLD but so far as I know hasn't been yet: make the mapping of $FFFE vary based upon the three LSBs of a cycle counter, so depending upon when the interrupt arrives code could execute:

Code: Select all

   ; If IRQ fires with minimum delay
   stx IRQ_X
   sty IRQ_Y
   sta IRQ_A
   lda next_sample
   sta $4011
or

Code: Select all

   ; If IRQ fires after one cycle
   stx IRQ_X
   sta IRQ_A
   nop
   lda next_sample
   sta $4011
   sty IRQ_Y
etc. up to

Code: Select all

   ; If IRQ fires after six cycles
   sta IRQ_A
   lda next_sample
   sta $4011
   stx IRQ_X
   sty IRQ_Y
Having the $FFFE mapping select one of eight locations based upon the three bottom bits of the counter would allow code to eliminate the timing jitter. I've never seen that done, though.
User avatar
za909
Posts: 249
Joined: Fri Jan 24, 2014 9:05 am
Location: Mijn hart woont al in Nederland

Re: Updating APU Regs Multiple Times Per Frame

Post by za909 »

My two cents' worth is that you don't need to run the ENTIRE sound engine multiple time per frame to achieve interesting effects, just put some subroutines in an IRQ that modulate pitch, duty cycle or volume. Also, referring to tips about using the DMC IRQ: If you don't want to use samples otherwise, you can also use $00 dummy samples for the IRQ timing to produce no unintentional audio. If you do this you can still write to $4011 to produce popping sounds, and then the $00 samples drive the level back to 0. Or, you can alternate between playing a 1-byte $00, or $FF sample to make use of the DMC level's attenuation of the triangle-noise-dmc output to gain limited control over the amplitude of the triangle channel (but be careful, this affects all three channels on the second output pin).
supercat
Posts: 161
Joined: Thu Apr 18, 2019 9:13 am

Re: Updating APU Regs Multiple Times Per Frame

Post by supercat »

lidnariq wrote:One could buy higher-precision parts, but I don't know if a 555 with those better RCs is still cheaper than various higher-precision options. (e.g. 74'393 generating an IRQ every 128cy)
Using the top bit of a simple counter would make it awkward to reduce CPU utilization below 50%. Most of the one-chip counter-based solutions I know of would either go low for only one cycle or for half the period, when what's needed is for the timer to go low for something between 8 and 20 cycles--something the 555 can easily achieve. If one wants a sample rate of one sample per 128 cycles, the best way to achieve that would probably be to use bit 7 of the counter, and have the ISR output two samples 128 cycles apart (output a pre-calculated value, then calculate two samples, store the second for use with the next ISR, and output the first).

Note that NMI would interfere with the audio. Given the problems with polling $2002 to find the start of vblank, the best workaround may be to have code add 1536 to a 3-byte counter on each interrupt and, when it overflows, subtract 199,485 (for NTSC), 212,784 (PAL), or 178,684 (Dendy) and run the program's vblank handler.

Since the NES has more RAM than the Atari 2600, my four-voice audio driver could be quite efficient if I had a good IRQ source. Using DMC interrupts, however, a rough prototype would spend about 200 cycles every 432 figuring audio for four samples but then have to spend 142 cycles killing time between output samples. Perhaps when using DMC interrupts a six-voice or eight-voice driver would make more sense. While a four-voice music player that imposes a 50% CPU load might be preferable to an eight-voice player that poses an 80% load, if the minimum CPU load is going to be 80% one may as well get the maximum music out of it.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Updating APU Regs Multiple Times Per Frame

Post by lidnariq »

Having quickly prototyped up a simulation in PureData of multiplexing at various audio rates, any variation in multiplex rate produces painfully obvious sounds of heterodyning. Some frequencies of multiplex just produce objectionable nonharmonic content, regardless of anything else. I really don't think a 555 could possibly produce acceptable results. It's just not stable enough over short periods of time, and the aging characteristics are awful.

Also, since the pulse channels reset phase when you update the upper byte of the period, multiplexing the pulse channels would only work in the topmost ~5 octaves (A440 and up). Triangle and noise and looped DPCM should be ok, albeit limited.
User avatar
za909
Posts: 249
Joined: Fri Jan 24, 2014 9:05 am
Location: Mijn hart woont al in Nederland

Re: Updating APU Regs Multiple Times Per Frame

Post by za909 »

Even if the NMI handler allows IRQs to get through, distortion from OAM DMA is still very hard, if not impossible to mitigate completely. Only the DMC DMA can interrupt it, so I suppose one solution is to find a good "interpolation" DMC sample to play before starting OAM DMA, or to adjust the sample pointer(s) after the DMA to skip the samples that should've played during the DMA. Either way, sprite usage can definitely mess with anything pitched here.
supercat
Posts: 161
Joined: Thu Apr 18, 2019 9:13 am

Re: Updating APU Regs Multiple Times Per Frame

Post by supercat »

za909 wrote:Even if the NMI handler allows IRQs to get through, distortion from OAM DMA is still very hard, if not impossible to mitigate completely. Only the DMC DMA can interrupt it, so I suppose one solution is to find a good "interpolation" DMC sample to play before starting OAM DMA, or to adjust the sample pointer(s) after the DMA to skip the samples that should've played during the DMA. Either way, sprite usage can definitely mess with anything pitched here.
Alternatively, if one isn't using too many sprites, one may be able to get by without OAM DMA. From what I understand of the DMA corruption issue (and experimentation with my own NES is consistent with this), if e.g. code only needs sprite zero and the last 16 sprites, one could set OAMADDR to $C0 (which might corrupt sprites 0, 1, 48, and 49), then feed 68 bytes (reloading the data for sprites 48-63 and 0) and end by storing an $FF for the sprite 1 position. The remaining bytes of sprite 1 would be randomly corrupted, but that wouldn't matter since it wouldn't be displayed anyhow, and since OAMADDR would be pointing at a partially-written first row, the hardware-forced reload of address zero wouldn't cause any further corruption.
supercat
Posts: 161
Joined: Thu Apr 18, 2019 9:13 am

Re: Updating APU Regs Multiple Times Per Frame

Post by supercat »

lidnariq wrote:I really don't think a 555 could possibly produce acceptable results. It's just not stable enough over short periods of time, and the aging characteristics are awful.
I think a 555 free running at about 16KHz should be reasonably stable over the very-short term (<100ms) aside from a certain amount of random jitter which could actually be a good thing. If the main CPU is running almost entirely 2-3 cycle instructions for some parts of a frame, but is running many 4-8 cycle instructions during other parts, and if there's no IRQ timing jitter caused by anything other than the instruction mix, that could create a noticeable 60Hz modulation in the audio. Adding a little random jitter on top of that could make the effect less noticeable. Note that the 555 wouldn't be used to modulate other tone generators, but instead generate audio samples directly.

The idea I find most intriguing, though, would be a mapper that could vary the IRQ vector based upon the timing of when the interrupt is taken. Such a mapper could also have an "IRQ service mode" latch which gets set on any access to $FFxx, cleared on any read of $01xx, and force the cart to behave as though most regions are banked to the last address regardless of the state of any banking registers. This would allow the IRQ to use address space that's banked differently from the main-line code without having to save/set/restore the banking configuration. Audio quality in the presence of timing jitter may be tolerable, but removing the jitter or limiting it to an average of one cycle every three lines in a somewhat-randomized pattern would make the sound much cleaner.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Updating APU Regs Multiple Times Per Frame

Post by lidnariq »

supercat wrote:one could set OAMADDR to $C0 (which might corrupt sprites 0, 1, 48, and 49),
On at least one alignment, any write to OAMADDR causes something that looks like DRAM decay.
supercat wrote:Adding a little random jitter on top of that could make the effect less noticeable.
Not enough. Adding sample-to-sample jitter will degrade what sound quality remains, but in a way that sounds entirely different from the missing sample updates during OAMDMA.
Note that the 555 wouldn't be used to modulate other tone generators, but instead generate audio samples directly.
Not being able to use any of the NES's built-in five voices in exchange for a softsynth is going to be a very hard sell.

Even if it's comparatively stable in the 1/10th of a second range, the temperature coefficient of the various parts will cause obvious problems to anyone who has even the tiniest bit of a sense of pitch. It will drift audibly during operation. The threshold of 0.4% is audible, even if it doesn't sound like a lot, and is the upper bound of what you'd get from using a PZT resonator. RCs, especially at slower speeds, are an order of magnitude worse.
supercat
Posts: 161
Joined: Thu Apr 18, 2019 9:13 am

Re: Updating APU Regs Multiple Times Per Frame

Post by supercat »

lidnariq wrote:Not being able to use any of the NES's built-in five voices in exchange for a softsynth is going to be a very hard sell.
Pitfall II on the Atari 2600 used a free-running oscillator to generate the pitches for the DPC which had no relation whatsoever to the pitches on the TIA's own sound generators, one of which was used for game sound effects. I don't think there was any particular effort to calibrate the oscillator, but it sounds fine.
On at least one alignment, any write to OAMADDR causes something that looks like DRAM decay.
An inability to use sprites would be a bigger impediment using such a player on anything other than a title or intermission screen, but many cool title and intermission screens don't need animated sprites. I've not observed the DRAM decay hitting anything other than the "old" or "new" rows, though I can imagine it might hit any row whose address has a mix of old and new bits. If that occurs, that would mean that the only quasi-safe change to the OAM row address would be from 00000 to 10000 [which could corrupt row 0 or 16, but nothing else]. Having to write 32 sprites/frame would be annoying, but depending upon what the OAM does with read-modify-write operations, zeroing out sprites would take either 10 or 16 cycles each, so 32 would be 1024 cycles. Annoying, but not impossible.
Even if it's comparatively stable in the 1/10th of a second range, the temperature coefficient of the various parts will cause obvious problems to anyone who has even the tiniest bit of a sense of pitch. It will drift audibly during operation. The threshold of 0.4% is audible, even if it doesn't sound like a lot, and is the upper bound of what you'd get from using a PZT resonator. RCs, especially at slower speeds, are an order of magnitude worse.
Cheap caps are +/-10%, but 0.1uF caps rated +/-5% are still pretty cheap (under $0.02). If one wanted game music to be in tune with anything else, a semitone variation would be intolerable, but an RC using 5% caps isn't going to wobble by anything near 5% in a reasonable time frame. Wobble at rates below 10Hz or so turns into vibrato, which can mask a host of other ills.
lidnariq
Posts: 11432
Joined: Sun Apr 13, 2008 11:12 am

Re: Updating APU Regs Multiple Times Per Frame

Post by lidnariq »

supercat wrote:
lidnariq wrote:Not being able to use any of the NES's built-in five voices in exchange for a softsynth is going to be a very hard sell.
Pitfall II on the Atari 2600 used a free-running oscillator to generate the pitches for the DPC which had no relation whatsoever to the pitches on the TIA's own sound generators, one of which was used for game sound effects. I don't think there was any particular effort to calibrate the oscillator, but it sounds fine.
emphasis mine. That's vitally important. Sound effects usually aren't in tune and usually don't need to be. The loss of one sound generator is a lot easier to chew than the loss of three.

The TIA's native sound generators start out of tune with themselves and just get worse from there. Composing music for them is an academic exercise, comparable to writing 12-tone music. It's technically doable, but it's horribly constrained, and requires accepting that some notes are either just not available, and/or are going to sound sour, and/or are going to inscrutably change tone color.

A large capacitor is less stable over time, and a large resistor is more sensitive to crosstalk, while an RC that runs at a couple megahertz is comparatively stable. But if the RC has a high operating frequency one has to add a divider, and at that point one may as well start with the NES's M2 signal.

Vibrato doesn't sound good when it's a function of what's being displayed on screen, or what routine the CPU is currently executing, or anything with irregular period.
supercat
Posts: 161
Joined: Thu Apr 18, 2019 9:13 am

Re: Updating APU Regs Multiple Times Per Frame

Post by supercat »

lidnariq wrote:That's vitally important. Sound effects usually aren't in tune and usually don't need to be. The loss of one sound generator is a lot easier to chew than the loss of three.
I'd look at it differently: if one could swallow the loss of CPU time, especially in any vblank where it's necessary to perform sprite updates, one would gain the ability to use all of the built-in voices for sound-effects. I doubt a game would be very practical unless one used an IRQ that could be synchronized with horizontal sync, but a Frogger/Preppie style game with excellent music might be practical given such hardware.
The TIA's native sound generators start out of tune with themselves and just get worse from there. Composing music for them is an academic exercise, comparable to writing 12-tone music. It's technically doable, but it's horribly constrained, and requires accepting that some notes are either just not available, and/or are going to sound sour, and/or are going to inscrutably change tone color.
I've done two programs that played music on the TIA. Toyshop Trouble was in C major and was mostly in tune except for the Gs which were absolutely horrid, but for which I compensated a little by using two different "B" notes--a slightly sharp one for use when the base note was C or F, and a flat one for use when the bass note was G. The Stella's Stocking menu used my BTP2 (Better Than Pitfall II) music player so I was able to compose it for four voices with a five octave chromatic range. There are ten pieces of music in the menu program, in nine different keys. If I remember right, C major was the most solidly in tune, and C#/Db was the worst.
A large capacitor is less stable over time, and a large resistor is more sensitive to crosstalk, while an RC that runs at a couple megahertz is comparatively stable. But if the RC has a high operating frequency one has to add a divider, and at that point one may as well start with the NES's M2 signal.
If I were going to use a 555, I would set its pitch to 15-16khz (middle C would be 262Hz when the oscillator frequency is 15,720Hz), probably using a 0.1uF cap (which is why I mentioned that value).
Vibrato doesn't sound good when it's a function of what's being displayed on screen, or what routine the CPU is currently executing, or anything with irregular period.
Such things shouldn't affect the oscillation rate of the 555. They might affect the level of jitter in the IRQ timing, but as I said I think the multi-voice music driver would probably be most useful for title screens.

Here's a very rough test/demo of a 4-voice driver. It plays ascending chromatic scales at four different rates on the four channels using the DPC for timing. I should use less-harmonically-rich waveforms for the upper notes since aliasing becomes noticeable in the top octave, but I think the quality is adequate to show that with some tweaking the effect could be listenable. As I mentioned earlier, the ISR ends up with an absurd number of cycles to burn when doing "only" four voices (between the third and fourth "lda linearityLookup / sta $4011" and "lda linearityLookup / sta $4011" it does nothing useful except reload the calling program's X and Y register values), so there's lots of room to add more audio fanciness.

PS--The demo sounds lousy on MESEN, but much better on my actual NES console.
Attachments
irqAudio.nes
(40.02 KiB) Downloaded 278 times
LoneKiltedNinja
Posts: 63
Joined: Mon Jul 07, 2008 7:40 pm

Re: Updating APU Regs Multiple Times Per Frame

Post by LoneKiltedNinja »

This is a couple levels above anything I've thought to try, but I'm following over half of it, I think. Kind of a crazy snapshot that in a bit under a decade hobbyist-level NES audio tech has gone from hypothetical discourse on how to pack more tones on the tonal channels to a practical demonstration of softsynth on DMC.

It would help if I had any backing whatsoever in the official theory being accomplished by supercat's 4-phase lookup+composition sample last page, or familiarity with the actual code behind the referenced Atari demos, but I think I get the gist of canning up 4 sample buffers, stacking the values, and passing the final result through a cleanup-lookup. Will take a bit more staring at it to see what the phase0 and phase0l values are doing, tho. And, being tremendously late to the evident party, wow putting code in zeropage and just updating the load/store/jump absolutes in-situ is brilliant.

I am, however, confused and the internet is not helping (and is indeed putting this thread as the top hit however I phrase the query): what is location $d011? I see 0 relevant NES references, and 1 Apple II reference to key input, but nothing there or in my on-hand cheat-sheets notes it as a control register for anything. Dummy write to pad time / update flags? Also, I assume you're purely driving the DMC output level via $4011 here, not expecting it to ever engage in its own process of sample playback?
Psych Software- failing to profit from retro/indie development since before it was cool
http://www.psychsoftware.org
tepples
Posts: 22708
Joined: Sun Sep 19, 2004 11:12 pm
Location: NE Indiana, USA (NTSC)
Contact:

Re: Updating APU Regs Multiple Times Per Frame

Post by tepples »

$D011 is a video control register on Commodore 64. It was probably a typo for $4011.
Post Reply