Well, b00d's been buggin' me awhile on this. I wrote it a few days ago as a quick draft.
It's a possible way to perform 4 channel wavetable synthesis on an NES using its raw DAC channel and some code that runs at Hblank rate or thereabouts.
init: lda #<trackdata ;point to track data
sta track+0
lda #>trackdata
sta track+1
lda #000h
sta pointer
sta phase0
sta phase1
sta phase2
sta phase3
lda #0ffh
sta timer
rts
;perform channel update and rendering
update: clc
ldx phase0 ;index our sample table
lda table,x ;and add 4 channels worth together
ldx phase1
adc table,x
ldx phase2
adc table,x
ldx phase3
adc table,x
sta 04011h ;store in PCM reg 30 cycles to here
clc ;update phase accumulators
lda rate0
adc phase0
sta phase0
clc
lda rate1
adc phase1
sta phase1
clc
lda rate2
adc phase2
sta phase2
clc
lda rate3
adc phase3
sta phase3 ;74 cycles to here
jsr waithblank
inc timer
bne + ;update approx 31x a second
ldy pointer
lda (track),y
iny
sta rate0
lda (track),y
iny
sta rate1
lda (track),y
iny
sta rate2
lda (track),y
iny
sta rate3
sty pointer ;46 cycles
bne +
inc track+1
;add other fun things here, like changing the lookup table address (self-mod the LDA high byte)
;check for track end (look for 0 or something in the note data maybe)
+ jsr waithblank
jmp update
waithblank: do hblank wait here
trackdata: .db <blah> music data goes here, notes in groups of 4, updated 30x a second.
table: .db 256 byte waveform data goes here
.db another 256 byte waveform table can go here for a second waveform
;zeropage variables:
;phase accumulators
phase0: .dsb 1
phase1: .dsb 1
phase2: .dsb 1
phase3: .dsb 1
;update adders
rate0: .dsb 1
rate1: .dsb 1
rate2: .dsb 1
rate3: .dsb 1
timer: .dsb 1
pointer: .dsb 1
Here's how it works. First you call init to initalize everything, next you jump to update, and it will get stuck in an endless loop playing the music.
I kinda cheated on it since it's just a rough draft. The whole hblank waiting thing isn't fleshed out, but you could use an IRQ from a VRCx mapper- that would give an IRQ every 114/113 cycles which is fine.
There's plenty of time to perform the calculations and all that.
Music is just stored note/note/note/note for the 4 channels, and the values get shoved into the adder variables every time "timer" expires.
You can get fancy and add volume control and different wavetables and all that with some self modifying code. The code is so small that you can easily run it out of RAM without using too much up.
The "Table" wavetable holds 1 cycle of your waveform, be it a sawtooth, triangle, square, sine, or other kind (electric guitar?). Samples should be 5 bits in size, so that when 4 of them are added, the result will not exceed the NES' 7 bit DAC. This will provide maximum volume without clipping.
Well that's about it. I don't have time to play with the code so I thought I'd throw it out here to see what people think.
oh yeah, if your adder value is 00h, this will effectively silence that channel 'cause it will no longer update its wave position. Using 8 bit adders like this, you should be able to get several octaves of range without too much trouble. If you wish to get more range, store 2 or more cycles of your waveform in the wavetable.
[/code]
It's a possible way to perform 4 channel wavetable synthesis on an NES using its raw DAC channel and some code that runs at Hblank rate or thereabouts.
Code:
init: lda #<trackdata ;point to track data
sta track+0
lda #>trackdata
sta track+1
lda #000h
sta pointer
sta phase0
sta phase1
sta phase2
sta phase3
lda #0ffh
sta timer
rts
;perform channel update and rendering
update: clc
ldx phase0 ;index our sample table
lda table,x ;and add 4 channels worth together
ldx phase1
adc table,x
ldx phase2
adc table,x
ldx phase3
adc table,x
sta 04011h ;store in PCM reg 30 cycles to here
clc ;update phase accumulators
lda rate0
adc phase0
sta phase0
clc
lda rate1
adc phase1
sta phase1
clc
lda rate2
adc phase2
sta phase2
clc
lda rate3
adc phase3
sta phase3 ;74 cycles to here
jsr waithblank
inc timer
bne + ;update approx 31x a second
ldy pointer
lda (track),y
iny
sta rate0
lda (track),y
iny
sta rate1
lda (track),y
iny
sta rate2
lda (track),y
iny
sta rate3
sty pointer ;46 cycles
bne +
inc track+1
;add other fun things here, like changing the lookup table address (self-mod the LDA high byte)
;check for track end (look for 0 or something in the note data maybe)
+ jsr waithblank
jmp update
waithblank: do hblank wait here
trackdata: .db <blah> music data goes here, notes in groups of 4, updated 30x a second.
table: .db 256 byte waveform data goes here
.db another 256 byte waveform table can go here for a second waveform
;zeropage variables:
;phase accumulators
phase0: .dsb 1
phase1: .dsb 1
phase2: .dsb 1
phase3: .dsb 1
;update adders
rate0: .dsb 1
rate1: .dsb 1
rate2: .dsb 1
rate3: .dsb 1
timer: .dsb 1
pointer: .dsb 1
Here's how it works. First you call init to initalize everything, next you jump to update, and it will get stuck in an endless loop playing the music.
I kinda cheated on it since it's just a rough draft. The whole hblank waiting thing isn't fleshed out, but you could use an IRQ from a VRCx mapper- that would give an IRQ every 114/113 cycles which is fine.
There's plenty of time to perform the calculations and all that.
Music is just stored note/note/note/note for the 4 channels, and the values get shoved into the adder variables every time "timer" expires.
You can get fancy and add volume control and different wavetables and all that with some self modifying code. The code is so small that you can easily run it out of RAM without using too much up.
The "Table" wavetable holds 1 cycle of your waveform, be it a sawtooth, triangle, square, sine, or other kind (electric guitar?). Samples should be 5 bits in size, so that when 4 of them are added, the result will not exceed the NES' 7 bit DAC. This will provide maximum volume without clipping.
Well that's about it. I don't have time to play with the code so I thought I'd throw it out here to see what people think.
oh yeah, if your adder value is 00h, this will effectively silence that channel 'cause it will no longer update its wave position. Using 8 bit adders like this, you should be able to get several octaves of range without too much trouble. If you wish to get more range, store 2 or more cycles of your waveform in the wavetable.
[/code]