It is currently Tue Dec 12, 2017 3:42 am

All times are UTC - 7 hours





Post new topic Reply to topic  [ 48 posts ]  Go to page 1, 2, 3, 4  Next
Author Message
PostPosted: Thu Aug 04, 2016 12:51 pm 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7314
Location: Chexbres, VD, Switzerland
I just got an idea that would be awesome for NES development. Basically it would allow an undefined number of extra sine audio channels, probably 1, 2 or 4 channels, with volume control, and that using $4011. I probably won't ever be able to put this idea to good use (as to, make a decent game using music that uses this), so I prefer to share the idea and hope someone else use it. If they give me credit, even better :)

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:
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


The mixing code is thus relatively simple and fax, and executes in constant time. But that's not all, get a bunch of sine waves is not very exciting in itself. What is exciting is that you can control their volume, and that without touching the code above, by pairing two dephased sines of the same frequency. In the sound code, something like that:
Code:
   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


Will combine Chan1 and Chan2 sines to produce a sine wave of controllable volume, and that without using any multiplication anywhere. I just tought this idea was very cool, so I wished to share it. I have no idea how much "channels" would be feasible in practice, it's a tradeoff between amount of channels, CPU usage and sampling frequency (and thus, maximum sine pitch). I think expecting 4 sine channels without volume control, or 2 sine channels with volume control, could already greatly expand what is possible to do for NES music !

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.


Top
 Profile  
 
PostPosted: Thu Aug 04, 2016 1:16 pm 
Offline
User avatar

Joined: Fri Nov 19, 2004 7:35 pm
Posts: 3968
Aside from every other issue with using $4011, sprite DMA is a big latency hog. It takes 4.5 scanlines of uninterrupted time to perform sprite DMA.

Back to random ideas about the code...
If you updated at ~5000Hz, that would be running the sound code once every ~3 scanlines. If the update code ate up 113 cycles, that would be a loss of about 33% speed overall.
There would be aliasing, since it changes at discrete levels.

_________________
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!


Top
 Profile  
 
PostPosted: Thu Aug 04, 2016 1:31 pm 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7314
Location: Chexbres, VD, Switzerland
Quote:
Aside from every other issue with using $4011, sprite DMA is a big latency hog. It takes 4.5 scanlines of uninterrupted time to perform sprite DMA.

This is a serious problem. The only sane solution I can think of is doing nothing and let the IRQ handler run late, which will create a hearable distortion, but hopefully still tolerable...

My count is 49 cycles for the constant part and 39 cycles for the looped part. With 4 channels w/o volume or 2 channels with volume, that makes a total of 204 cycles. This is a bit optimizable by unrolling the mixing loop and storing registers in ZP instead of the stack.


Top
 Profile  
 
PostPosted: Thu Aug 04, 2016 1:37 pm 
Offline
User avatar

Joined: Fri Nov 19, 2004 7:35 pm
Posts: 3968
Mockup of what a song at 5512Hz (aliased) might sound like.
Just a mockup, some simple song made with a sine wave and downsampled to 5512Hz, and resampled to 44100 with aliasing.


Attachments:
junk3.ogg [61.32 KiB]
Downloaded 94 times

_________________
Here come the fortune cookies! Here come the fortune cookies! They're wearing paper hats!
Top
 Profile  
 
PostPosted: Fri Aug 05, 2016 11:38 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7314
Location: Chexbres, VD, Switzerland
So, with this code, we get 2 sine waves of controllable volume in a 110 cycle IRQ routine. It could be possible to save a few cycles by placing it in RAM and use selfmod-constants for variables used only once, that is the Freq and Volume variables. It is not possible anymore to get 4 sines without volume control, but I think the mega speedup makes it worth the deal. I ignore how many cycles it takes for the 6502 to enter IRQ, does anyone have any idea?

This creates a situation close to the one described by Dwedit, where code could be run every 3 lines with 1/3 CPU usage.

I don't think the demo sounds very good, but it doesn't sound horrible either, and remember, it's NES, and 2 extra sound channels in addition to the normal 2a03 stuff. We could use only 1 channel and update faster, too, it could sound better.

Code:
IRQ:
   sta SaveAZP      ; 3
   stx SaveXZP      ; 6
   sty SaveYZP      ; 9

   lda PhaseL1      ; 12
   clc         ; 14
   adc FreqL1      ; 17
   sta PhaseL1      ; 20
   lda PhaseH1      ; 23
   adc FreqH1      ; 26
   sta PhaseH1      ; 29
   tax         ; 31 Phase A
   clc         ; 33
   adc Volume1      ; 36 Selfmod constant
   tay         ; 38 Phase B
   lda SineTable,Y   ; 42
   clc         ; 44
   adc SineTable,X   ; 48
   sta Temp      ; 51

   lda PhaseL2      ; 54
;  clc         ; Assume C clear here if the 1st sine didn't overflow
   adc FreqL2      ; 57
   sta PhaseL2      ; 60
   lda PhaseH2      ; 63
   adc FreqH2      ; 66
   sta PhaseH2      ; 69
   tax         ; 71 Phase A
   clc         ; 73
   adc Volume2      ; 76 Selfmod constant
   tay         ; 78 Phase B

   lda Temp      ; 81
   clc         ; 83
   adc SineTable,Y   ; 87
;  clc         ; Assume C clear here if there is no overflow adding sine with the previous buffer value
   adc SineTable,X   ; 91
   sta $4011      ; 95

   lda SaveAZP      ; 98
   ldx SaveXZP      ; 101
   ldy SaveYZP      ; 104
   rti         ; 110


Top
 Profile  
 
PostPosted: Fri Aug 05, 2016 11:52 am 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5891
Location: Canada
Bregalad wrote:
This is a serious problem. The only sane solution I can think of is doing nothing and let the IRQ handler run late, which will create a hearable distortion, but hopefully still tolerable...

You'd get less distortion if you could skip the update and catch up instead. Shifting the whole timeline back causes worse problems than a sample-and-hold over a missed sample while retaining consistent timing.


Top
 Profile  
 
PostPosted: Fri Aug 05, 2016 12:25 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19334
Location: NE Indiana, USA (NTSC)
IRQ is 7 cycles in (same timing as BRK) and 6 out (RTI).


Top
 Profile  
 
PostPosted: Fri Aug 05, 2016 12:48 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5891
Location: Canada
Dwedit wrote:
Mockup of what a song at 5512Hz (aliased) might sound like.

Wow, yeah that's harsh. I think the NES' lowpass filter would help slightly, but the SNR here seems way too high for it to help enough. I think partly the problem is just that the sine wave is the one signal that will be most noticably affected by aliasing, since it has no other component frequencies to cover up the aliasing. Maybe a brighter waveform (e.g. saw) would make it slightly less noticeable, but 5kHz is really prominent to most human ears. This samplerate might just be too low to be practical.

8kHz was a common samplerate for a while, but even that was usually used in conjunction with a suitable lowpass filter.


Top
 Profile  
 
PostPosted: Fri Aug 05, 2016 1:04 pm 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7314
Location: Chexbres, VD, Switzerland
Anything else than a sinewave rules out the oportunity of smooth volume control while using only 256 bytes of ROM, and make playback at any given frequency very difficult without major aliasing problems.


Top
 Profile  
 
PostPosted: Fri Aug 05, 2016 1:08 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5891
Location: Canada
Bregalad wrote:
Anything else than a sinewave rules out the oportunity of smooth volume control while using only 256 bytes of ROM.

Yes, I think by mathematical definition no other waveform could use that phase trick to control volume.

There are probably other waveforms that have a smooth volume control at low cost, though, by other means. A square wave, for example, could trivially do it, and wouldn't need a lookup table either.


Top
 Profile  
 
PostPosted: Fri Aug 05, 2016 1:17 pm 
Offline
User avatar

Joined: Mon Jan 03, 2005 10:36 am
Posts: 2982
Location: Tampere, Finland
The mockup doesn't sound too bad to me. Call me crazy but I might even prefer a slightly harsher sound to a proper (overly clean) sine wave. However, the OAM DMA will distort it further. In specific cases (demos or simple games) one might be able to get by without OAM DMA (at 4+4 cycles for LDA/STA per byte, it'd take exactly one vblank to upload all sprites manually).

The trick to control the volume is pretty cool.

_________________
Download STREEMERZ for NES from fauxgame.com! — Some other stuff I've done: kkfos.aspekt.fi


Top
 Profile  
 
PostPosted: Fri Aug 05, 2016 1:18 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5891
Location: Canada
Actually, I'm curious about Dwedit's example now; was going by how it sounded, but looking at the OGG in an editor the resampling seems to have emphasized the aliasing?


Attachments:
junk3_waveform.jpg
junk3_waveform.jpg [ 167.06 KiB | Viewed 1423 times ]
Top
 Profile  
 
PostPosted: Fri Aug 05, 2016 1:27 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19334
Location: NE Indiana, USA (NTSC)
When you use a harsh brick-wall low-pass filter for resampling, the Gibbs phenomenon causes overshoot by 10 percent, the height of the first sidelobe on each side of sinc. The visual effect of this ringing is to emphasize the aliasing. To minimize ringing, use a less sharp low-pass filter, such as a filter with a sinusoidal rolloff between 3f/8 and f/2.


Top
 Profile  
 
PostPosted: Fri Aug 05, 2016 2:21 pm 
Offline
User avatar

Joined: Sun Jan 22, 2012 12:03 pm
Posts: 5891
Location: Canada
Yes, I know it's gibbs ringing, but I was not expecting this much ringing from the kind of simple conditions I presumed Dwedit was simulating. It might have been introduced by the OGG compression method too, unsure. The thing is, you can't build a suitable lowpass filter on the NES, the only possible resampling is sample-and-hold. There is a filtering from the "ideal" sample-and-hold to the 44kHz signal, but the effects of that are so small here as to be irrelevant; the primary problem of 5kHz aliasing is already dominating.

I wrote a quick python program to generate an example of my own, easy to modify to add other conditions. Naive resampling, sine table quantized to 8 bits, frequency sweep, you can modify it in other ways if you like (e.g. 60hz interruptions of various kind), but you can hear from the example already that the aliasing is quite strong.

So... Dwedit's example is not far off from mine, now that I've tested it; I just wasn't sure what was going on because of that ringing, which was unexpected. The aliasing is pretty strong.


Attachments:
sine_test.zip [66.34 KiB]
Downloaded 50 times
Top
 Profile  
 
PostPosted: Fri Aug 05, 2016 2:33 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19334
Location: NE Indiana, USA (NTSC)
Can an all-up or all-down DMC sample reduce sample and hold artifacts?


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

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 7 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