Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 2 of 2

Thread: Fun with FM synthesis

  1. #1
    Senior Member
    Join Date
    Jul 2020
    Posts
    340

    Fun with FM synthesis

    I've played with some simple FM synthesis, and reworked the PlaySynthMusic example to use it,
    as well as trying to declutter all the toplevel boilerplate so it can be parameterized by a number of voices.

    The way I've addressed this is to bundle all the apparatus for a voice into a class, 3 oscillators and an
    envelope, like this:
    Code:
    // Implement an FM voice, 3 stages in the algorithm in sequence, plus the envelope
    class FMVoice
    {
    public:
      FMVoice () 
      {
        new AudioConnection (modulator, 0, middle, 0) ; // on heap so destructors not called when this 
        new AudioConnection (middle, 0, finalosc, 0) ; // constructor exits!
        new AudioConnection (finalosc, 0, env, 0) ;
      }
    
      AudioEffectEnvelope & output (void)  // need this to connect up and to set envelope parameters
      {
        return env ;
      }
    
     
      // depth1 is modulation depth applied to middle,
      // depth2 is modulation depth applied to final
      // f2 and f3 are multipliers for fundamental used in middle and modulator respectively, small integers
      void noteOn (float amp, float freq, float depth1, float depth2, int f2, int f3)
      {
        AudioNoInterrupts() ;
        modulator.frequency (f3 * freq) ;  // modulator is lowly sine wave generator, no begin method...
        modulator.amplitude (depth1) ;
        middle.phaseModulation (180) ; // mid range so no discontinuities
        middle.begin (depth2, f2 * freq, WAVEFORM_SINE) ;
        finalosc.phaseModulation (180) ; // mid range so no discontinuities
        finalosc.begin (amp, freq, WAVEFORM_SINE) ;
        env.noteOn () ;
        AudioInterrupts () ;
      }
      
      void noteOff (void)
      {
        env.noteOff() ;
      }
    
      bool isActive (void)
      {
        return env.isActive() ;
      }
    
      void silence ()  // save cycles when the envelope has finished releasing
      {
        finalosc.amplitude (0) ;
        middle.amplitude (0) ;
        modulator.amplitude (0) ;
      }
      
    protected:
      AudioSynthWaveformSine modulator ;
      AudioSynthWaveformModulated middle ;
      AudioSynthWaveformModulated finalosc ;
      AudioEffectEnvelope env ;
    };
    The whole reworked example is attached as a zip - I'd recommend finding something less annoying as the example
    data than the William Tell overture (I found a midi file of Bach's Tocatta / Fugue in D-minor, which can be listened too
    repeatedly without going crazy! - and put it through miditones, but it was copyright so didn't include it here alas).

    The FM parameters I found after a little tinkering sound fairly organ-like and are pretty funky on the 'scope!

    Click image for larger version. 

Name:	fm_synth.png 
Views:	4 
Size:	8.7 KB 
ID:	21224

    Comments welcome - the topic of how to structure audio components hierarchically is relevant, found a few comments
    about this in a search on teensy FM synthesis.

    BTW this is on Teensy 4.0 with 150MHz clock, about 22% audio CPU for 16 voices which is pretty reasonable...

    It was quite fun to play with FM synthesis for the first time

    [ I was partly inspired by reading this https://cs.gmu.edu/~sean/book/synthesis/Synthesis.pdf ]
    Attached Files Attached Files

  2. #2
    Senior Member
    Join Date
    Jul 2020
    Posts
    340
    This time I've made a library AudioStream object to implement 4-operator FM synthesis with 8 algorithms, like the DX9.

    The 4 operators have individual amplitude settings (which for modulators are the modulation depth), and the
    single input stream is used for overall modulation depth control which scales all the modulation depths. Operator
    4 has configurable feedback.

    The synth example code uses 16-fold polyphony and makes a Teensy 4.0 work pretty hard, note, I'm not sure a
    slower board will handle this.

    Code:
    class AudioSynthFM : public AudioStream
    {
    public:
      AudioSynthFM(void) : AudioStream(1, inputQueueArray) { ... }
    
      void algorithm (int alg_num) ;
      void frequencies (float freq1, float freq2, float freq3, float freq4) ;
      void amplitudes (float amp1, float amp2, float amp3, float amp4, float feedb) ;
      void enable (bool on) ;
      ...
    };
    algorithm() takes an integer in range 0 to 7 to set the FM algorithm, standard DX9 set.

    frequencies() takes Hz values for the four operators.

    amplitudes() takes 5 float values for the output amplitudes of the oscillators and operator 4's feedback amount.

    enable(false) is a convenience for shutting down the unit totally to save cycles, to be called when an output
    envelope drops from active to idle, and on noteOn.

    The example routes DC -> expression envelope -> AudioSynthFM -> note envelope -> mixers,
    and triggers both envelopes on noteOn.

    Code:
    zip fm2.zip FM2PlaySynth/* libraries/FM/*
      adding: FM2PlaySynth/FM2PlaySynth.ino (deflated 61%)
      adding: FM2PlaySynth/FMPlaySynth.h (deflated 47%)
      adding: FM2PlaySynth/william_tell_overture.c (deflated 83%)
      adding: libraries/FM/FM.cpp (deflated 62%)
      adding: libraries/FM/FM.h (deflated 49%)
    Attached Files Attached Files

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •