Teensy FM Synthesis Problems, not enough modulation

Electric Potato

Well-known member
Hey gang,

I've been trying to implement some algorithms from yamaha's DX7 on Teensy 3.1 using its sineModulated object but have had difficulty getting
the correct results. After a lot of testing, it seems like there is something off in the way that the Audio library is computing the modulations on its 'AudioSynthWaveformSineModulated' object. I have attached two videos which attempt to illustrate this problem. Comparison 2 is probably the most informative, as it demonstrates the expected results of fm modulation contrasted with the current output the Audio library is producing. With a simple modulator to carrier routing, you should be able to easily visualize how the mod signal is carving out new peaks and valleys in the carrier waveform, but in Teensy 3.1 it is changing the waveform in a weird way. I assume that the problem is occurring in 'synth_sine.cpp' and/or 'synth_sine.h' but am not sure what exactly to fiddle around with. From an acoustic perspective, the result of modulating a sineModulated object does sound fm-synthy up to a point, but it can't even come close to generating the full range of higher frequency harmonics (demonstrated in Comparison 1).

I've attached a barebones test sketch for anyone who wants to investigate this, just so you don't have to set up the audio connections yourself, but I should emphasize that this appears to be a fundamental problem with the library code, not anything relating to a particular sketch. Anyway, I hope someone can straighten this out, because a pocket sized FM synth would be pretty badass.

Comparison 1:
https://vimeo.com/160510378
Comparison 2:

test sketch for those interested:
Code:
#include <Audio.h>
#include <SPI.h>
#include <Wire.h>
#include <SD.h>

//objects
AudioSynthWaveformSineModulated carrier1;
AudioEffectEnvelope             carrier1Env;
AudioSynthWaveformSine          mod1;
AudioMixer4                     masterMix;
AudioOutputAnalog               dac;

//connections
AudioConnection                 c1(carrier1,carrier1Env);
AudioConnection                 c2(carrier1Env,0,masterMix,0);
AudioConnection                 c3(mod1,carrier1);
AudioConnection                 xx(masterMix,dac);

float amp = 0.0;

void setup(){
  
  AudioMemory(20);
  
  carrier1.amplitude(1.0);
  carrier1.frequency(220);
  mod1.amplitude(0.0);
  mod1.frequency(440);
  carrier1Env.noteOn();
}

void loop(){
 
  delay(250);
  
  amp+=.05;
  if(amp>1.0) amp = 0;
  
  mod1.amplitude(amp);
}
-epot
 
Last edited:
I changed some of the audio library files to allow higher amplitudes/gains than 1.0, but as soon
as you cross above 1.0, it doesn't add the correct harmonics, it sounds cool, but isn't
what I'm looking for.

what did you change?

no coffee yet, but .. my hunch would be it has to do with L#180 ?

Code:
mod = modinput->data[i];

where mod is an int16_t, so the more extreme modulation indices probably just bust that.

as for .amplitude(), it only seems to affect carriers / modulated objects (L#168 +), but not unmodulated modulators (L#185 +)?

mmh, wondering whether a dedicated "FM" object wouldn't be easier to use, where one could do stuff like

FM.set_algorithm(2);

FM.set_op2_freq(200);

etc

fwiw, here's a neat fixed point implementation for maple mini (not so yamaha esque, but still nice): https://github.com/Ixox/preen
occasionally i was toying with the idea of porting it to an audio object, but my attention span is too short.
 
I want to help you get this working, and I'd like to fix anything that's wrong or limiting in the audio library.

But I can't understand much of what you're saying. I can't hear results you're comparing, and even if I could, I'm not sure I could actually do much with only listening tests.

Is there any chance you could capture actual signal waveforms? Do you have an oscilloscope? Or could you use one of the sound card scope programs (perhaps on another PC) to capture the waveforms?

For thing like this:

I got very different results feeding a mixer with a gain of 10.0 into a sineModulated object vs feeding a waveformSine with an amplitude of 10.0 into that object.

of course I can investigate. But I need you to post the 2 programs I should run. Please understand I get lots of requests every day, and often when I try to recreate a problem without a complete program, I end up guessing or filling in some small but important detail differently. Please, double check that the 2 sample programs you post are complete and really do recreate the problem when copied back into Arduino and run with an unmodified copy of the audio library. I will try to recreate the issue, and if I can get it to happen here, odds are good I'll get to the bottom of it and fix any bugs involved.
 
re:

I got very different results feeding a mixer with a gain of 10.0 into a sineModulated object vs feeding a waveformSine with an amplitude of 10.0 into that object.

isn't it simply the case that mixer.gain(0,10.0); means something different than sine.amplitude(10.0); in that the former has a valid range 0 - 32767, while the latter caps at 1.0? so you'll end up with a different modulator.

but that won't change the behaviour of the modulation index "β", as far as i can see; as is, the variable "mod" can't be more than int16_t; whereas β can be > 1.0 . e.g see the pic on the right (Waveforms for each β):

https://en.wikipedia.org/wiki/Frequency_modulation_synthesis

i don't have a teensy here, but what happens when you'd change int16_t to int32_t mod, and add a function void beta(float n), to set the index / value of the mod parameter?

something along the lines of (omitting the left/right shifts):

uint32_t mod = modinput->data;
mod = signed_multiply_32x16b(beta, mod);
ph += inc + (multiply_32x32_rshift32(inc, mod) << 1);
 
mxxx- I just commented out lines in mixer.h and sine.h that clamp the max gain, ie ' if (n > 1) n=1 '. Like I said, it didn't have spectacular results so I'll check out the other things you mentioned today and report back.

Paul - Absolutely, I'll post some code...Also I'll record audio comparisons for that chart I made and post them with screen caps of the spectrum. I'll try and bang it out in the next few hours, thanks for helping!
 
Okay I added a test sketch to the main post and am getting some demo footage together, I'll probably post it some time before midnight
 
Hey Paul, I threw together a video (in the original post) that I hope gives a good demonstration of my chief problem: that I can't get sufficient FM modulation on Teensy. It shows the maximum amount of modulation achievable with Teensy and contrasts it with what FM8 (or a DX7) can do. I didn't have time to add any other demonstrations, but if need be I can put together another video later, thanks for the help man,

epot
 
fwiw,

i've played around with this for a few minutes today and it's easy enough to get something more closely resembling FM behaviour by 1/ making mod an int32_t, 2/ scaling it and 3/ simply adding it to the phase increment.

/// ... from synth_sine.cpp :

Code:
	        mod = signed_multiply_32x16b(_mod_index, modinput->data[i]);
	        //mod += signed_multiply_32x16b(_feedback, block->data[i]);
		ph += inc + mod; // (multiply_32x32_rshift32(inc, mod << 16) << 1);

DS1Z_QuickPrint2.png

that's not very scientific, but what i've noticed with the larger values of "mod" is that the waveform drifts. that seems to be the case for the present sineFM object, too; it's barely noticeable, because the modulation is so subtle but watching it on a scope, it does slowly morph.


my test sketch is simply:
(note i'm using a pcm5102a codec, not SGTL5000, so there's none of the i2c stuff. you'd have to add this)


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

AudioSynthWaveformSine modulator;
AudioSynthWaveformSineModulated   carrier;
AudioOutputI2S           codec;
AudioConnection patchCord0(modulator, carrier);
AudioConnection patchCord1(carrier, 0, codec, 0);
AudioConnection patchCord2(carrier, 0, codec, 1);

uint32_t _wait;
uint16_t _freq = 220;
float _index = 0;

void setup(void) 
{  
 
  AudioMemory(12);
  carrier.amplitude(0.8f);
  carrier.frequency(_freq);
  /* uncomment line below */
  //carrier.mod_index(1.0f);

  modulator.amplitude(1.0f);
  modulator.frequency(_freq*2);
}

void loop(void) 
{  
  
  if (millis() - _wait > 5) 
  {  
     _wait = millis();
     /* uncomment line below */
     //carrier.mod_index(_index++);
  }
}

so, using the default library object, the above will exhibit what seems to be the maximum modulation possible (= modulator.amplitude(1.0f)), which isn't very much.

uncommenting the two lines where it says carrier.mod_index( ) is just a quick and dirty hack to make things more FM-like, that's all.

to try and see or hear the difference, class AudioSynthWaveformSineModulated needs an additional method:

Code:
void mod_index(float n) {
		if (n < 0) n = 0;
		else if (n > 65535.0) n = 65535.0;
		_mod_index = n * 65536.0;
}

and corresponding private variable: int32_t _mod_index;

finally, lines 180-181 in synth_sine.cpp in turn need modification:

Code:
mod = signed_multiply_32x16b(_mod_index, modinput->data[i]);
ph += inc + mod;

where mod is now an int32_t, not int16_t
 
Last edited:
Ok, so I'm supposed to run that program on msg #1? I see some descriptions about pressing a few button combinations. Are there specific results for each that I'm supposed to compare against?

Or the code fragments in msg #8? Those don't look like a complete program I can copy into Arduino and upload onto a board here.

Try to imagine I'm juggling a dozen projects and answering lots of messages daily. It really help if you can be very clear and specific about what I should do to reproduce a problem. I can usually fix bugs very quickly... but any guesswork to figure out how to make it happen usually means nothing useful ends up happening.
 
Editing the old messages makes this much more difficult for me to follow.

Any chance you could post a reply with all the latest? Imagine I have a huge backlog of reported issues over the last several weeks while focusing on the Prop Shield. Please help me out here, with a new message with only the newest info I need to reproduce this problem. I will give it a try here on real hardware.
 
Hey Paul,

I edited the main question to make it as clear as I possibly can. Sorry it turned into a confusing report; my half-baked attempts to fix
the problem are probably pretty irrelevant and they cluttered everything up, so I revised this to just describe what I think is the single main problem.
I also switched out that larger sketch for a simple one, but I should emphasize that the sketch itself doesn't matter;
it's just a barebones implementation of fm synthesis that feeds a 440Hz sine wave into a 220Hz sineModulated object.
The problem seems to be something fundamentally off within in (I'm assuming) synth_sine.cpp or synth_sine.h.

thanks man, and hopefully this is clear enough now

-epot

ps. if I could change the title, I would remove 'not enough modulation' , since it now appears that it is more of a fundamental problem with the actual computation of the modulated carrier wave
 
Last edited:
I've got this issue on my list of stuff to investigate. Realistically, I may not get to it until next week. A number of lingering Prop Shield issues and the 1.28 release are top priorities right now. I've also got quite a backlog of unanswered emails and many issues & github pull requests that've piled up in the last few weeks while finishing up the Prop Shield.

I do consider this an important issue and I do intend to work on the FM scaling, even if it doesn't seem like it right now. The mapping from input signal to per-sample phase increment almost certainly needs more options. Right now, it can range from DC to twice the configured frequency. So you can't get more than 1 octave upward shift. The only available mapping is linear to octave, no ability for exponential.

Please, if you write anything more, stop editing the old message, ok?
 
I just saw this as I've just become interested in maybe building some kind of synth with Teensy. I'm not a programming expert, but I do know about FM synthesis. From looking at your waveforms in the second video, they look (and sound) fine to me for FM synthesis. BUT, it looks like the Teensy waveform is actually a 220Hz sine modulating a 220Hz sine (i.e, they're the same frequency). So it appears that the modulating waveform is not 440Hz which explains the difference in appearance to the FM8 waveform. Maybe that will help you with debugging your code?
 
I gave FM synthesis on the Teensy another try today. I was surprised that nearly nothing can be found from people using it - neither successfully nor with any problems.

I started to get the easiest scenario running: modulating a sine wave (AudioSynthWaveformSineModulated) with a sine wave (AudioSynthWaveformSine).

I compared to a simple max msp sketch and Ableton operator. As described above, it seems that there is not enough modulation. It would need way more gain of the modulator to get to the sounds where it gets interesting.

I am not into the mathematics of FM synthesis and don't know what happens exactly or what would be necessary. In my max msp patch I had to use up to 30dB of gain to get similar sounds as in the Operator plugin.

I would be glad to help to improve the FM capabilities of the Audio library. It seems that there is such a huge treasure in it. At the moment I don't have more time to document anything. At a later time I can give more details, code examples or whatever is needed.
 
Hello

I running into the same issue with "not enough modulation for FM Synthesis"
Is there any Fix in place to make this work?
 
Hi there. I would also be interested to know if the problem was solved. For [mod] -> [carr] there is still little modulation. If more mod [mod] -> [mod] -> [carr] still works poorly, it doesn't behave the way FM should.
4, 6 OP FM synth is the plan, but this is not really feasible.
 
It is now possible to have more modulation in the "waveformMod" object. I was seeing that only by accident and have tried it only in a quick test until now. There seems to be a lot more potential now.
 
This is the one you want.

https://www.pjrc.com/teensy/gui/?info=AudioSynthWaveformModulated

I've updated the sine_fm documentation to recommend using waveformMod

https://www.pjrc.com/teensy/gui/?info=AudioSynthWaveformSineModulated

The sine_fm waveform was one of the earliest parts of the audio library, I believe back in 2014. It was written with a simple algorithm which turned out to be not so useful. Later waveformMod was added, to address the limited and not-so-useful modulation of that early attempt. But the documentation wasn't (until now) updated to recommend using the new waveform synth.
 
Back
Top