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

Thread: Teensy FM Synthesis Problems, not enough modulation

  1. #1

    Teensy FM Synthesis Problems, not enough modulation

    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 by Electric Potato; 03-31-2016 at 02:00 AM.

  2. #2
    Senior Member
    Join Date
    Feb 2013
    Posts
    563
    Quote Originally Posted by Electric Potato View Post

    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.

  3. #3
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,556
    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.

  4. #4
    Senior Member
    Join Date
    Feb 2013
    Posts
    563
    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/Freque...tion_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[i];
    mod = signed_multiply_32x16b(beta, mod);
    ph += inc + (multiply_32x32_rshift32(inc, mod) << 1);

  5. #5
    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!

  6. #6
    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

  7. #7
    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

  8. #8
    Senior Member
    Join Date
    Feb 2013
    Posts
    563
    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);
    Click image for larger version. 

Name:	DS1Z_QuickPrint2.png 
Views:	124 
Size:	45.2 KB 
ID:	6856

    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 by mxxx; 03-29-2016 at 09:32 AM.

  9. #9
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,556
    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.

  10. #10
    Senior Member
    Join Date
    Feb 2013
    Posts
    563
    Quote Originally Posted by PaulStoffregen View Post

    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.
    please ignore #8, it's just supplementary info. for the sake of completeness, i've added the full info

  11. #11
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,556
    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.

  12. #12
    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 by Electric Potato; 03-31-2016 at 02:04 AM.

  13. #13
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,556
    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?

  14. #14
    Junior Member
    Join Date
    Aug 2016
    Location
    Adelaide, Australia
    Posts
    3
    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?

  15. #15
    Junior Member
    Join Date
    Mar 2019
    Posts
    5
    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.

Posting Permissions

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