I felt like writing a spc700 code to generate a 2 operator FM wave sample. To keep the spc700 time down, it's not going to render every single sample. Instead it's going to generate a single cycle waveform that gets updated every frame. I'm not sure how many channels it can do per frame, but if it can't do enough, you can lower the frame rate.
This code is supposed to generate a 64 sample loop. Carrier volume is not calculated, because it can be handled in hardware by the DSP.
Code:
fm_synth: //this uses bass.exe as the assembler
lda #$82
sta {brr_buffer}
sta {brr_buffer}+9
sta {brr_buffer}+18 //BRR samples need a header byte every 9th byte
inc
sta {brr_buffer}+27 //first 3 BRR blocks have a header byte of $82 (range = 8, filter = 0, loop flag set)
ldx #$00 //last block has a header byte of $83 (same as above, but with end flag set)
str {#_of_blocks}=#$04 //4 blocks of 16 samples = 64 samples
-;
inx
str {countdown}=#$08 //8 pairs of samples per block
-;
clc
adc {modulator_phase}={modulator_frequency} //add modulator frequency to phase
ldy {modulator_phase}
lda sine_table,y //use modulator phase to index an 8-bit sine wave
ldy {modulator_amplitude} //with a range from 0 to 255
mul //multiply the sine wave (A) with amplitude (Y) to get the modulation height (Y)
clc //I believe SPC700 uses unsigned multiply. I'll fix it if I'm wrong
adc {carrier_phase}={carrier_frequency} //add carrier frequency to carrier phase
tya
clc
adc {carrier_phase} //take the carrier phase and add the current modulation height
tay
lda waveform,y //use that to get the final 4-bit sample
sta {temp}
clc
adc {modulator_phase}={modulator_frequency} //do it all again for another sample
ldy {modulator_phase}
lda sine_table,y
ldy {modulator_amplitude}
mul
clc
adc {modulator_phase}={modulator_frequency}
tya
clc
adc {carrier_phase}
tay
lda {temp}
xcn //this time, grab the previous sample, and swap nibbles
ora waveform,y //OR it with the new sample, to have 2 samples in 1 byte
sta {brr_buffer},x //save the result
inx
dec {countdown}
bne -
dec {#_of_blocks}
bne --
rts
Did some cycle counting. Only ~54 cycles per sample. Fast enough to generate 4 channels at once, with 64 samples per waveform.