The main idea is to use a mapper that supports timed IRQs that are fired at a programmable arbitrary rate. I think VRC mappers, and the FME-7 supports this, and the Famicom Disk System. Perhaps other more obscure mappers as well, I don't know. The rate can be anything really.
Now, if we can generate IRQs periodically, it's possible to write to $4011 periodically without monopolizing the CPU for this task alone, and thus get an extra sound channel, that doesn't sound terrible like DMC does. The problem is that doing it the "trivial" way is just copying data from ROM to $4011, but it wastes tremendous amount of ROM to replay samples, so this is not practical for a NES game - only for tech demoes.
Another idea would be to synthesize sound, but this requires CPU power, and the NES bascially doesn't have that. But I think I found a way to synthesize sound using very low CPU. The idea is to generate sine waves, and take values from a sine table which is stored in 256 bytes of ROM. Sine waves have no harmonics, and no harmonics aliasing, so it's very easy to resample them to any rate with a fixed sample rate, and it will continue to sound good, unlike any other waveform such as square or saw waves.
So the IRQ mixing code looks like something like that:
Code: Select all
IRQ:
pha
txa
pha
tya
pha
lda #$00
sta Temp
ldy #nchans
- lda PhaseL,Y
clc
adc FreqL,Y
sta PhaseL,Y
lda PhaseH,Y
adc FreqH,Y
sta PhaseH,Y
tax
lda SineTable,X
clc
adc Temp
sta Temp
dey
bne -
lda Temp
lsr A ; Optional - convert 8-bit to 7 bit unsigned
sta $4011
pla
tay
pla
tax
pla
rti
Code: Select all
ldx #Chan1
ldy #Chan2
lda FreqL,X
sta FreqL,Y
lda FreqH,X
sta FreqH,Y
lda PhaseL,X
sta PhaseL,Y
lda PhaseH,X
clc
adc Volume
sta PhaseH,Y
I didn't mention it, but of course the sine table is pre-shifted so that it doesn't overflow, so maybe it's range will be -64..64 or something like that instead of -256..256.