I'm trying to understand how to program cascading FM synthesis operations. I can find a good deal of information out there on how to do it on various pieces of hardware, but unfortunately, there isn't enough information to be able to replicate these things in software.
I know that some details may be hardware specific, and that "replicating results" may be complex as there could be specific numbers of bits being used for calculations, or perhaps hardware that alters the results (tubes), but I'm just trying to figure out what is generally considered to be the correct way to do cascading FM synthesis operations.
Let's say that I have the concept of a free spinning oscillator object in my program which stores phase, and has a function to advance phase based on the frequency passed in:
struct SFreeSpinningOscillator {
SFreeSpinningOscillator () : m_phase(0.0f) { }
// advances the oscillator and returns the current value of the sine wave
float AdvanceOscillator (float frequency, float sampleRate)
{
float ret = std::sinf(m_phase * c_pi * 2.0f);
m_phase += frequency / sampleRate;
return ret;
}
// note that phase isn't a true angle, but is just a percent 0..1
float m_phase;
};
With the above I know that i can get vibrato easily enough with something like this:
SFreeSpinningOscillator a, b;
....
float modulator = a.AdvanceOscillator(10.0f, sampleRate) * 5.0f;
float carrier = b.AdvanceOscillator(noteFrequency + modulator, sampleRate);
return carrier;
The above will make it so the carrier value is a sine wave at frequency "noteFrequency" which varies it's frequency +/- 5hz on a sine wave, 10 times a second.
When talking about FM synthesis, it's more likely to be something like the below where the modulator's frequency and amplitude is based on the note's frequency:
float modulator = a.AdvanceOscillator(noteFrequency * 0.5, sampleRate) * noteFrequency;
float carrier = b.AdvanceOscillator(noteFrequency + modulator, sampleRate);
return carrier;
This is where I start to get confused though. What if I have two or more modulators? Are their frequencies and amplitudes ALSO based on the basic note frequency? Or are they based on the frequency of the last modulator?
If it's based on the basic note frequency, how do the amplitudes of the modulator waves fit in? If it's based on the modulator frequency, why even calculate the wave forms?
In other words, is the below correct? or is there some other way that it is usually or commonly done?
SFreeSpinningOscillator a, b, c, d;
....
float modulator3 = a.AdvanceOscillator(noteFrequency * 0.5, sampleRate) * noteFrequency;
float modulator2 = b.AdvanceOscillator(noteFrequency * modulator3 * 0.2, sampleRate) * noteFrequency;
float modulator = c.AdvanceOscillator(noteFrequency * modulator2 * 2.8, sampleRate) * noteFrequency;
float carrier = d.AdvanceOscillator(noteFrequency + modulator, sampleRate);
return carrier;
Here are some of the sources I've been reading and have found most helpful:
http://the-all.org/tx81z/fm_overview.html
http://www.soundonsound.com/sos/apr00/articles/synthsecrets.htm