In a bit more detail...
With no pitch bending, notes are pretty easily specified as a number of semitones above some base. A number of games use the MIDI note range, which is 128 tones, ranging from C-1 to somewhere around C10. The usable range on the NES is a little smaller than that, ranging from C2 to B9 for the 2a03 square channels, C1 to B8 for the triangle (it runs an octave lower for any particular frequency dropped in), and A0-B9 for the VRC6 (12 bit frequency dividers)
Here's the freq table I use(d) for the bloopageddon engine:
Code:
freqtable:
.word $FE3, $EED, $E1F ; 00 A0
.word $D44, $C98, $BDF, $B32, $A97, $9F3 ; 03 C1
.word $972, $8E0, $865, $7F1, $776, $70F
.word $6A2, $64B, $5EF, $598, $54B, $4F9 ; 0F C2
.word $4B8, $46F, $432, $3F8, $3BB, $387
.word $350, $325, $2F7, $2CC, $2A5, $27C ; 1B C3
.word $25C, $237, $218, $1FB, $1DD, $1C3
.word $1A8, $192, $17B, $165, $152, $13E ; 27 C4
.word $12D, $11B, $10C, $0FD, $0EE, $0E1
.word $0D3, $0C9, $0BD, $0B2, $0A8, $09E ; 33 C5
.word $096, $08D, $085, $07E, $076, $070
.word $069, $064, $05E, $059, $054, $04F ; 3F C6
.word $04B, $046, $042, $03F, $03B, $037
.word $034, $031, $02F, $02C, $029, $027 ; 4B C7
.word $025, $023, $021, $01F, $01D, $01B
.word $01A, $018, $017, $015, $014, $013 ; 57 C8
.word $012, $011, $010, $00F, $00E, $00D
.word $00C, $00C, $00B, $00A, $00A, $009 ; 63 C9
.word $008, $008, $007
As you can see, the differences in the divider values for each pair of notes decreases as you crawl up the octaves. Given that, the amount you add/subtract from the value above to bend between a pair of notes, or to do some tremolo effect or whatnot has to change also as you go up. Adding +/- 5 to the above would be close to an inaudible fraction of a semitone at the lower end of the scale, but up around C8, would be 2-4 semitones.
My suggestion is to represent pitch bend not as a direct amount to add/subtract, but as a fraction of the difference between a note and the following one. $1B.00 would be C3, no shift, $1B.80 would be halfway from C3 to C#3, etc. Thus, adding $0C.00 to any note would always be a 1-octave shift.
To come up with the actual register value needed for any given note $AA.BB, the value would be: table[AA] + (table[AA+1]-table[AA])*BB/256. for 1B.80, that would be:
Code:
table[1B] = $350
table[1C] = $325
diff = -$3B = $FFC5
diff * $80 = -$1D80 = $FE280
last / 256 = -$1E = $FFE2
$350 - $1D = $333 (or $350 + $FFE2)
To combine this with arpeggiation, your arpeggio steps would be whole semitone values, so in the engine you'd track probably the following values per-note:
Code:
a = Base tone
bb.cc = Current pitchbend amount (assuming it's 16 bits for thoroughness)
d = Current arpeggio offset
Each frame (could save some time by checking for changes) you produce the actual note N as a + b + d. The actual period to stuff into the register would be (table[N+1] - table[N]) * c >> 8.
Code:
Pros:
linear pitchbends, vibratos
interacts smoothly with arpeggios
no extraneous tables
multi-semitone bends will work automatically
Cons:
multiplication isn't fast
The freq table can get reduced in size if you go with the approach of having one octave, and shifting down appropriately, but that will be slower, and complicates the note format a bit. The multiply could be sped up greatly by only caring about the top 4 bits of the fraction, at the cost of bend precision.