Cascading FM synthesis operations 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

https://www.soundonsound.com/sos/may00/articles/synth.htm
the code on the bottom looks like a triple-nested FM oscillator if i understand how the AdvanceOscillator() method works.  likely the SFreeSpinningOscillator class has a method of setting the initial phase of the oscillator.  you may get wildly different tone timbres with exactly the same code and different phases for the 4 oscillators.  

if you can get your hands on a DX7 alg chart





you can see that most of their algs are sum of two chains.  alg 2 has a triple nested oscillator chain where your d is the DX7 osc #3.  but i can't tell you what the initial phases of their oscillators are.  there are so many parameters that affect the timbre, sometimes in unpredictable ways.  so doing software to emulate a DX7 will involve some tweeking of knobs