FM synthesis block, audio design tool.

Status
Not open for further replies.

RABB17

Well-known member
Hello, I am currently working on a multi-function synth for the Teensy 4.0. Currently, I am designing an FM patch and I had a question regarding the design of an operator style block using the audio design tool. Essentially, I would like to know if either of the blocks below is more "correct" for the design and interconnection of two operators, or if the two design blocks are functionally equivalent. The blocks differ from the diagram in that there is more routing complexity and each operator can modulate itself.

blockFMTeensy.png
blockFM.JPG
 
Last edited:
Alright, so today I have some free time so I'm going to code up the second block today and compare scope outputs between block 1 and block 2 with Ableton's Operator and FM8. I'll post my results.
 
Well, neither of those look correct to me. Try something like this:

Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioSynthWaveform       osc2;      //xy=387.50000381469727,256.25000381469727
AudioEffectEnvelope      envelope1;      //xy=543.7500076293945,253.7500057220459
AudioSynthWaveformModulated osc1;   //xy=735.0000076293945,260.00000381469727
AudioEffectEnvelope      envelope2;      //xy=920.0000114440918,261.25000381469727
AudioOutputI2S           i2s1;           //xy=1125,255
AudioConnection          patchCord1(osc2, envelope1);
AudioConnection          patchCord2(envelope1, 0, osc1, 0);
AudioConnection          patchCord3(osc1, envelope2);
AudioConnection          patchCord4(envelope2, 0, i2s1, 0);
AudioConnection          patchCord5(envelope2, 0, i2s1, 1);
AudioControlSGTL5000     sgtl5000_1;     //xy=1125.000015258789,301.2500057220459
// GUItool: end automatically generated code

osc2 is the modulator, and osc1 is the carrier. Notice how osc2 is sending its output into the modulation input of osc1.

This code won't do anything by itself, you will still need to write some kind of interface (midi, buttons, etc) and setup code (envelopes, etc) to make it actually do something. One other note, in the Teensy Audio library, the Envelope block is basically a VCA plus an envelope generator in one object, it's very handy.
 
Thank you for the reply... If the above interconnection scheme is as I've included below, it is pretty much the same as block 1, that I posted. However, in my block design, the amps and mixers are used as internal routing cables(think operator a to b vs b to a), and waveformMod are used as modulators as opposed to waveform because it will allow for feedback modulation as in Massive's FM8. You can simply set the incoming modulator input to 0 amplitude to get a pure waveform from the waveformMod block. The first block does in fact work fine I have already coded up a midi synth example. I just haven't had time to compare scope results to existing FM vst's for comparison. The hmm kernel of the ? I was asking was really the diff between multiplying in the envelope with the 'vca' multiply module and just inputing the waveform into the asdr envelope. As someone recently ported microdexxed to the teensy I was hoping someone had had experience setting up the operator blocks. Apologies if I misinterpreted your reply

Capture1.JPG
My block diagrams were not intended to be functional as displayed, neither mixer is routed to an output mixer array which would then be needed to be routed to i2s or some other out of course. I am intending to chain 8 operators for 1 voice, which the t4 should be able to do easily I believe if the 64 voice polyphony another user posted for microdexxed was correct
 
Last edited:
ie. what is the difference between A and B, as the multiply module is described as a 'VCA'

simplified.png Capture2.JPG
 
Last edited:
So I decided to run a few tests to compare these two blocks. They both appear to function, however, the FFT's for the two seem to differ somewhat in various locations. I have attached a simple sketch to illustrate. Perhaps someone can give me some insight into this issue? Also, when waveform type 4(triangle) is selected as the carrier, there seems to be no output in either case, which also has me a bit stumped. **LOUD VOLUME, PLS DO NOT LISTEN THROUGH SPEAKERS W/O ADJUSTING**
Code:
#include <Audio.h>
#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SerialFlash.h>

// GUItool: begin automatically generated code
AudioSynthWaveformDc     dc1;            //xy=55,122
AudioSynthWaveform       waveform1;      //xy=183,52
AudioEffectEnvelope      envelope2;      //xy=187,122
AudioSynthWaveform       waveform2;      //xy=188,161
AudioEffectEnvelope      envelope1;      //xy=328,52
AudioEffectMultiply      multiply1;      //xy=332,138
AudioSynthWaveformModulated waveformMod2;   //xy=490,145
AudioSynthWaveformModulated waveformMod1;   //xy=492,58
AudioEffectEnvelope      envelope4;      //xy=655,145
AudioEffectEnvelope      envelope3;      //xy=656,58
AudioMixer4              mixer1;         //xy=833,93
AudioAnalyzeFFT1024      myFFT;      //xy=984,163
AudioOutputI2S           i2s1;           //xy=991,100
AudioConnection          patchCord1(dc1, envelope2);
AudioConnection          patchCord2(waveform1, envelope1);
AudioConnection          patchCord3(envelope2, 0, multiply1, 0);
AudioConnection          patchCord4(waveform2, 0, multiply1, 1);
AudioConnection          patchCord5(envelope1, 0, waveformMod1, 0);
AudioConnection          patchCord6(multiply1, 0, waveformMod2, 0);
AudioConnection          patchCord7(waveformMod2, envelope4);
AudioConnection          patchCord8(waveformMod1, envelope3);
AudioConnection          patchCord9(envelope4, 0, mixer1, 1);
AudioConnection          patchCord10(envelope3, 0, mixer1, 0);
AudioConnection          patchCord11(mixer1, 0, i2s1, 0);
AudioConnection          patchCord12(mixer1, 0, i2s1, 1);
AudioConnection          patchCord13(mixer1, myFFT);
AudioControlSGTL5000     sgtl5000_1;     //xy=995,20
// GUItool: end automatically generated code

int numWavType = 9;
float freq[] = {0, 130.8, 261.6, 523.2, 1046.4, 2092.8, 4185.6, 8371.2, 16472.4, 33483.8, 66969.6, 133939.2, 267878.4, 535756.8};
//float freq[] = {0, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000};
float n = 0.0;

void setup() {
  Serial.begin(115200);

  AudioMemory(20);

  sgtl5000_1.enable();
  sgtl5000_1.volume(1.0);

  dc1.amplitude(1.0);

  mixer1.gain(1, 1.0);
  mixer1.gain(0, 0.0);

  waveform1.frequency(130.8);
  waveform1.pulseWidth(.15);
  waveform1.amplitude(1.0);
  waveform1.begin(WAVEFORM_SINE);

  waveform2.frequency(130.8);
  waveform2.pulseWidth(.15);
  waveform2.amplitude(1.0);
  waveform2.begin(WAVEFORM_SINE);

  waveformMod1.frequency(130.8);
  waveformMod1.amplitude(1.0);
  waveformMod1.frequencyModulation(1.0);
  waveformMod1.begin(WAVEFORM_SINE);

  waveformMod2.frequency(130.8);
  waveformMod2.amplitude(1.0);
  waveformMod2.frequencyModulation(1.0);
  waveformMod2.begin(WAVEFORM_SINE);

  envelope1.attack(10);
  envelope1.decay(0);
  envelope1.hold(140);
  envelope1.release(50);
  envelope1.sustain(1.0);
  //envelope1.on

  envelope2.attack(10);
  envelope2.decay(0);
  envelope2.hold(140);
  envelope2.release(50);
  envelope2.sustain(1.0);

  envelope3.attack(10);
  envelope3.decay(0);
  envelope3.hold(140);
  envelope3.release(50);
  envelope3.sustain(1.0);

  envelope4.attack(10);
  envelope4.decay(0);
  envelope4.hold(140);
  envelope4.release(50);
  envelope4.sustain(1.0);

  myFFT.windowFunction(AudioWindowHanning1024);

    //uncomment for 1st block, comment for 2nd
    /*
    mixer1.gain(0, 1.0);
    mixer1.gain(1, 0.0);
    for (int i = 0; i < numWavType - 1; i++)
    {
      waveformMod1.begin(i);
      Serial.print("Carrier Waveform Type: ");
      Serial.print(i);
      Serial.print("\n");
      for (int j = 1; j < 2; j++)
      {
        waveformMod1.frequency(freq[j]);
        //Serial.print("Carrier Freq: ");
        //Serial.print(freq[j]);
        //Serial.print("\n");
        for (int k = 1; k < 4; k++) //13
        {
          //Serial.print("Octave ratio: ");
          //Serial.print(k);
          //Serial.print("\n");
          waveformMod1.frequencyModulation((float)k);
          for (int l = 0; l < numWavType - 1; l++)
          {
            //Serial.print("Mod Waveform Type: ");
            //Serial.print(l);
            //Serial.print("\n");
            waveform1.begin(l);
            for (int m = 0; m < 13; m++) //14
            {
              //Serial.print("Mod Freq: ");
              //Serial.print(freq[m]);
              //Serial.print("\n");
              waveform1.frequency(freq[m]);
              envelope3.noteOn();
              envelope1.noteOn();
              delay(150);
              envelope3.noteOff();
              envelope1.noteOff();
              delay(50);
              Serial.print("FFT: ");
              for (int p = 0; p < 40; p++)
              {
                n = myFFT.read(p);
                if (n >= 0.01)
                {
                  Serial.print(n);
                  Serial.print(" ");
                }
                else
                {
                  Serial.print("  -  "); 
                }
              }
              Serial.println();
            }
          }
        }
      }
    }
      Serial.end();
      */
  
  //2nd block, comment out below to run 1st block
  mixer1.gain(1, 1.0);
  mixer1.gain(0, 0.0);
  for (int i = 0; i < numWavType - 1; i++)
  {
    waveformMod2.begin(i);
    Serial.print("Carrier Waveform Type: ");
    Serial.print(i);
    Serial.print("\n");
    for (int j = 1; j < 2; j++)
    {
      waveformMod2.frequency(freq[j]);
      //Serial.print("Carrier Freq: ");
      //Serial.print(freq[j]);
      //Serial.print("\n");
      for (int k = 1; k < 4; k++) //13
      {
        //Serial.print("Octave ratio: ");
        //Serial.print(k);
        //Serial.print("\n");
        waveformMod2.frequencyModulation((float)k);
        for (int l = 0; l < numWavType - 1; l++)
        {
          //Serial.print("Mod Waveform Type: ");
          //Serial.print(l);
          //Serial.print("\n");
          waveform2.begin(l);
          for (int m = 0; m < 13; m++) //14
          {
            //Serial.print("Mod Freq: ");
            //Serial.print(freq[m]);
            //Serial.print("\n");
            waveform2.frequency(freq[m]);
            envelope4.noteOn();
            envelope2.noteOn();
            delay(150);
            envelope4.noteOff();
            envelope2.noteOff();
            delay(50);
            Serial.print("FFT: ");
            for (int p = 0; p < 40; p++)
            {
              n = myFFT.read(p);
              if (n >= 0.01)
              {
                Serial.print(n);
                Serial.print(" ");
              }
              else
              {
                Serial.print("  -  "); // don't print "0.00"
              }
            }
            Serial.println();
          }
        }
      }
    }
  }
  Serial.end();
}

void loop()
{
}
 
Well, it seems the difference in outputs is due to the differences in effect_multiply.cpp and effect_envelope.cpp. At this point, I think I'm just going to run with effect_envelope solution on a hunch. It's pretty hard to objectively compare fidelity issues with so many variants possible. If I'm feeling ambitious with nothing to do later I will dump both audio blocks directly and compare them to a matlab simulation, but as it stands I think the library default should suffice.

TL:DR The blocks both function to the same effect in regards to being a VCA, however, there are frequency variations across permutations of carriers/modulators and I'm not sure as of yet which is better sounding, objective as that metric is.
 
Status
Not open for further replies.
Back
Top