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

Thread: FM synthesis, unpredictable ?

  1. #1
    Senior Member
    Join Date
    Dec 2018
    Posts
    186

    FM synthesis, unpredictable ?

    Hello,

    I am playing with FM synthesis, but I can't get "musical" results. The notes I produce are very quickly out of tune. Mastering a consistant pitch seems to be difficult.
    I tried to link the modulator frequency to the midi notes, but still out of tune very quickly.

    I need somme directions to master the art of FM synthesis !
    I use T4.0 and PCM5102 DAC (no audio shield).

    Emmanuel

    Code:
    #include <Audio.h>
    
    // GUItool: begin automatically generated code
    AudioSynthWaveformModulated FM_mod;         //xy=393,275
    AudioSynthWaveformModulated waveformMod1;   //xy=647,278
    AudioEffectEnvelope      envelope1;      //xy=827,277
    AudioAmplifier           Volume_amp;           //xy=991,277
    AudioOutputI2S           i2s1;           //xy=1162,277
    AudioConnection          patchCord1(FM_mod, 0, waveformMod1, 0);
    AudioConnection          patchCord2(waveformMod1, envelope1);
    AudioConnection          patchCord3(envelope1, Volume_amp);
    AudioConnection          patchCord4(Volume_amp, 0, i2s1, 0);
    AudioConnection          patchCord5(Volume_amp, 0, i2s1, 1);
    // GUItool: end automatically generated code
    
    
    float freq;
    float FM_index;
    const byte cc_fm_freq1 = 27;    //MIDI CC #
    const byte cc_fm_depth1 = 28;   //MIDI CC #
    const float noteFreqs[128] = {8.176, 8.662, 9.177, 9.723, 10.301, 10.913, 11.562, 12.25, 12.978, 13.75, 14.568, 15.434, 16.352, 17.324, 18.354, 19.445, 20.602, 21.827, 23.125, 24.5, 25.957, 27.5, 29.135, 30.868, 32.703, 34.648, 36.708, 38.891, 41.203, 43.654, 46.249, 48.999, 51.913, 55, 58.27, 61.735, 65.406, 69.296, 73.416, 77.782, 82.407, 87.307, 92.499, 97.999, 103.826, 110, 116.541, 123.471, 130.813, 138.591, 146.832, 155.563, 164.814, 174.614, 184.997, 195.998, 207.652, 220, 233.082, 246.942, 261.626, 277.183, 293.665, 311.127, 329.628, 349.228, 369.994, 391.995, 415.305, 440, 466.164, 493.883, 523.251, 554.365, 587.33, 622.254, 659.255, 698.456, 739.989, 783.991, 830.609, 880, 932.328, 987.767, 1046.502, 1108.731, 1174.659, 1244.508, 1318.51, 1396.913, 1479.978, 1567.982, 1661.219, 1760, 1864.655, 1975.533, 2093.005, 2217.461, 2349.318, 2489.016, 2637.02, 2793.826, 2959.955, 3135.963, 3322.438, 3520, 3729.31, 3951.066, 4186.009, 4434.922, 4698.636, 4978.032, 5274.041, 5587.652, 5919.911, 6271.927, 6644.875, 7040, 7458.62, 7902.133, 8372.018, 8869.844, 9397.273, 9956.063, 10548.08, 11175.3, 11839.82, 12543.85};
    
    
    void setup() {
      //***MEMORY***//
      AudioMemory(10);
    
      //***INIT MIDI***//
      usbMIDI.setHandleNoteOn(myNoteOn);
      usbMIDI.setHandleNoteOff(myNoteOff);
      usbMIDI.setHandleControlChange(myControlChange);
    
      //***OSCs INIT***//
      waveformMod1.begin(1, 0, WAVEFORM_SINE);
      FM_mod.begin(1, 0, WAVEFORM_SINE);
      waveformMod1.frequencyModulation(8);
      Volume_amp.gain(0.1);    //VOL
    }
    
    void loop() {
      usbMIDI.read();
    }
    
    
    //MIDI CC
    void myControlChange(byte channel, byte CC, byte value) {
      //OSC1 FM FREQ
      if (CC == cc_fm_freq1) {
        FM_index = (float)value / 2;
        Serial.print("FM INDEX = "); Serial.println(FM_index);
        FM_mod.frequency(freq * FM_index);
      }
    
      //OSC1 FM DEPTH
      if (CC == cc_fm_depth1) {
        float FM_depth = (float)value / 127;
        Serial.print("FM DEPTH = "); Serial.println(FM_depth);
        FM_mod.amplitude(FM_depth);
      }
    }
    
    
    //MIDI NOTE ON/OFF
    void myNoteOn(byte channel, byte midi_note, byte velocity) {
      freq = noteFreqs[midi_note];
      waveformMod1.frequency(freq);
      FM_mod.frequency(freq * FM_index);
      envelope1.noteOn();
    }
    
    void myNoteOff(byte channel, byte midi_note, byte velocity) {
      envelope1.noteOff();
    }

  2. #2
    Senior Member
    Join Date
    Jul 2020
    Posts
    1,748
    Quote Originally Posted by emmanuel63 View Post
    Hello,

    I am playing with FM synthesis, but I can't get "musical" results. The notes I produce are very quickly out of tune. Mastering a consistant pitch seems to be difficult.
    I tried to link the modulator frequency to the midi notes, but still out of tune very quickly.

    I need somme directions to master the art of FM synthesis !
    I use T4.0 and PCM5102 DAC (no audio shield).
    The modulation signal and modulated waveform need to be in precise lockstep, I suspect what's happening with
    your code is that when FM_mod and waveformMod1 convert their frequency values (floats) into the internal
    representation (fixpoint fraction of the sampling frequency), the results are slightly out, so that the phase of the
    modulator drifts against the carrier causing the timbre to vary over time.

    This is probably exacerbated by the way the modulated waveform acts (exponentially rather than linearly in frequency)
    - also FM synthesis actually uses phase modulation, not frequency modulation, and I'm not sure the standard audio lib
    supports this.

    You might want to have a look here: https://forum.pjrc.com/threads/62203...h-FM-synthesis I've an example FM synth object you can play with and
    see if it helps.

  3. #3
    Senior Member
    Join Date
    Dec 2018
    Posts
    186
    Thank you Mark,

    Using phase modulation greatly improved the result.
    I am trying know to dig in your code to understand your approach...

  4. #4
    I was experiencing difficulties with frequency modulation and made some changes to the WaveformModulated class you can see below. It solved my problem. What I did is add a third type of modulation "else if (moddata && fmoddata && modulation_type == 2)" which you can see below.

    Probably doesn't scratch your itch but I hope it might help someone else who struggled with FM as I have.

    Code:
    void AudioSynthWaveformModulated2::update(void)
    {
    	audio_block_t *block, *moddata, *shapedata, *fmoddata;
    	int16_t *bp, *bpf, *end;
    	int32_t val1, val2;
    	int16_t magnitude15;
    	uint32_t i, ph, index, index2, scale, priorphase;
    	const uint32_t inc = phase_increment;
      
    	moddata = receiveReadOnly(0);
    	shapedata = receiveReadOnly(1);
      fmoddata = receiveReadOnly(2);
    	ph = phase_accumulator;
    
    	priorphase = phasedata[AUDIO_BLOCK_SAMPLES-1];
    	if (moddata && modulation_type == 0) {
    		// Frequency Modulation
    		bp = moddata->data;
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			int32_t n = (*bp++) * modulation_factor; // n is # of octaves to mod
    			int32_t ipart = n >> 27; // 4 integer bits
    			n &= 0x7FFFFFF;          // 27 fractional bits
    			n = (n + 134217728) << 3;
    			n = multiply_32x32_rshift32_rounded(n, n);
    			n = multiply_32x32_rshift32_rounded(n, 715827883) << 3;
    			n = n + 715827882;
    			uint32_t scale = n >> (14 - ipart);
    			uint64_t phstep = (uint64_t)inc * scale;
    			uint32_t phstep_msw = phstep >> 32;
    			if (phstep_msw < 0x7FFE) {
    				ph += phstep >> 16;
    			} else {
    				ph += 0x7FFE0000;
    			}
    			phasedata[i] = ph;
    		}
    		release(moddata);
        if (fmoddata) release(fmoddata);
    	} else if (moddata && modulation_type == 1) {
    
    		// Phase Modulation
    		bp = moddata->data;
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			// more than +/- 180 deg shift by 32 bit overflow of "n"
    			uint32_t n = (uint16_t)(*bp++) * modulation_factor;
    			phasedata[i] = ph + n;
    			ph += inc;
    		}
    		release(moddata);
        if (fmoddata) release(fmoddata);
    	} else if (moddata && fmoddata && modulation_type == 2) {
        bpf = fmoddata->data;
        uint32_t inc2[AUDIO_BLOCK_SAMPLES];
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
          int32_t n = (*bpf++) * fmodulation_factor; // n is # of octaves to mod
          int32_t ipart = n >> 27; // 4 integer bits
          n &= 0x7FFFFFF;          // 27 fractional bits
          n = (n + 134217728) << 3;
          n = multiply_32x32_rshift32_rounded(n, n);
          n = multiply_32x32_rshift32_rounded(n, 715827883) << 3;
          n = n + 715827882;
          uint32_t scale = n >> (14 - ipart);
          uint64_t phstep = (uint64_t)inc * scale;
          uint32_t phstep_msw = phstep >> 32;
          if (phstep_msw < 0x7FFE) {
            inc2[i] = phstep >> 16;
          } else {
            inc2[i] = 0x7FFE0000;
          }
        }
        release(fmoddata);
     
        bp = moddata->data;
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
          // more than +/- 180 deg shift by 32 bit overflow of "n"
          uint32_t n = (uint16_t)(*bp++) * modulation_factor;
          phasedata[i] = ph + n;
          ph += inc2[i];
        }
        release(moddata);
    
      } else if (fmoddata && modulation_type == 2) {
    
        bpf = fmoddata->data;
        uint32_t inc2[AUDIO_BLOCK_SAMPLES];
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
          int32_t n = (*bpf++) * fmodulation_factor; // n is # of octaves to mod
          int32_t ipart = n >> 27; // 4 integer bits
          n &= 0x7FFFFFF;          // 27 fractional bits
          n = (n + 134217728) << 3;
          n = multiply_32x32_rshift32_rounded(n, n);
          n = multiply_32x32_rshift32_rounded(n, 715827883) << 3;
          n = n + 715827882;
          uint32_t scale = n >> (14 - ipart);
          uint64_t phstep = (uint64_t)inc * scale;
          uint32_t phstep_msw = phstep >> 32;
          if (phstep_msw < 0x7FFE) {
            ph += phstep >> 16;
          } else {
            ph += 0x7FFE0000;
          }
          phasedata[i] = ph;    
        
        
        }
        release(fmoddata);
      } else if (moddata && modulation_type == 2) {
        // Phase Modulation
        bp = moddata->data;
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
          // more than +/- 180 deg shift by 32 bit overflow of "n"
          uint32_t n = (uint16_t)(*bp++) * modulation_factor;
          phasedata[i] = ph + n;
          ph += inc;
        }
        release(moddata);
        if (fmoddata) release(fmoddata);		
    	}	else {
    		// No Modulation Input
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			phasedata[i] = ph;
    			ph += inc;
    		}
        if (fmoddata) release(fmoddata);
    	}
    	phase_accumulator = ph;
    
    	// If the amplitude is zero, no output, but phase still increments properly
    	if (magnitude == 0) {
    		if (shapedata) release(shapedata);
    		return;
    	}
    	block = allocate();
    	if (!block) {
    		if (shapedata) release(shapedata);
    		return;
    	}
    	bp = block->data;
    
    	// Now generate the output samples using the pre-computed phase angles
    	switch(tone_type) {
    	case WAVEFORM_SINE:
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			ph = phasedata[i];
    			index = ph >> 24;
    			val1 = AudioWaveformSine[index];
    			val2 = AudioWaveformSine[index+1];
    			scale = (ph >> 8) & 0xFFFF;
    			val2 *= scale;
    			val1 *= 0x10000 - scale;
    			*bp++ = multiply_32x32_rshift32(val1 + val2, magnitude);
    		}
    		break;
    
    	case WAVEFORM_ARBITRARY:
    		if (!arbdata) {
    			release(block);
    			if (shapedata) release(shapedata);
    			return;
    		}
    		// len = 256
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			ph = phasedata[i];
    			index = ph >> 24;
    			index2 = index + 1;
    			if (index2 >= 256) index2 = 0;
    			val1 = *(arbdata + index);
    			val2 = *(arbdata + index2);
    			scale = (ph >> 8) & 0xFFFF;
    			val2 *= scale;
    			val1 *= 0x10000 - scale;
    			*bp++ = multiply_32x32_rshift32(val1 + val2, magnitude);
    		}
    		break;
    
    	case WAVEFORM_PULSE:
    		if (shapedata) {
    			magnitude15 = signed_saturate_rshift(magnitude, 16, 1);
    			for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    				uint32_t width = ((shapedata->data[i] + 0x8000) & 0xFFFF) << 16;
    				if (phasedata[i] < width) {
    					*bp++ = magnitude15;
    				} else {
    					*bp++ = -magnitude15;
    				}
    			}
    			break;
    		} // else fall through to orginary square without shape modulation
    
    	case WAVEFORM_SQUARE:
    		magnitude15 = signed_saturate_rshift(magnitude, 16, 1);
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			if (phasedata[i] & 0x80000000) {
    				*bp++ = -magnitude15;
    			} else {
    				*bp++ = magnitude15;
    			}
    		}
    		break;
    
    	case WAVEFORM_SAWTOOTH:
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			*bp++ = signed_multiply_32x16t(magnitude, phasedata[i]);
    		}
    		break;
    
    	case WAVEFORM_SAWTOOTH_REVERSE:
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			*bp++ = signed_multiply_32x16t(0xFFFFFFFFu - magnitude, phasedata[i]);
    		}
    		break;
    
    	case WAVEFORM_TRIANGLE_VARIABLE:
    		if (shapedata) {
    			for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    				uint32_t width = (shapedata->data[i] + 0x8000) & 0xFFFF;
    				uint32_t rise = 0xFFFFFFFF / width;
    				uint32_t fall = 0xFFFFFFFF / (0xFFFF - width);
    				uint32_t halfwidth = width << 15;
    				uint32_t n;
    				ph = phasedata[i];
    				if (ph < halfwidth) {
    					n = (ph >> 16) * rise;
    					*bp++ = ((n >> 16) * magnitude) >> 16;
    				} else if (ph < 0xFFFFFFFF - halfwidth) {
    					n = 0x7FFFFFFF - (((ph - halfwidth) >> 16) * fall);
    					*bp++ = (((int32_t)n >> 16) * magnitude) >> 16;
    				} else {
    					n = ((ph + halfwidth) >> 16) * rise + 0x80000000;
    					*bp++ = (((int32_t)n >> 16) * magnitude) >> 16;
    				}
    				ph += inc;
    			}
    			break;
    		} // else fall through to orginary triangle without shape modulation
    
    	case WAVEFORM_TRIANGLE:
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			ph = phasedata[i];
    			uint32_t phtop = ph >> 30;
    			if (phtop == 1 || phtop == 2) {
    				*bp++ = ((0xFFFF - (ph >> 15)) * magnitude) >> 16;
    			} else {
    				*bp++ = (((int32_t)ph >> 15) * magnitude) >> 16;
    			}
    		}
    		break;
    	case WAVEFORM_SAMPLE_HOLD:
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			ph = phasedata[i];
    			if (ph < priorphase) { // does not work for phase modulation
    				sample = random(magnitude) - (magnitude >> 1);
    			}
    			priorphase = ph;
    			*bp++ = sample;
    		}
    		break;
    	}
     
    	if (tone_offset) {
    		bp = block->data;
    		end = bp + AUDIO_BLOCK_SAMPLES;
    		do {
    			val1 = *bp;
    			*bp++ = signed_saturate_rshift(val1 + tone_offset, 16, 0);
    		} while (bp < end);
    	}
    	if (shapedata) release(shapedata);
    	transmit(block, 0);
    	release(block);
    }

  5. #5
    Senior Member
    Join Date
    Dec 2018
    Posts
    186
    Thank you for this code. But I can't figure out how to implement it. Do I need to replace synth_waveform.ccp file in Teensyduino ?
    Do I need also to make some changes to synth_waveform.h file ?
    Could you please give me some directions ?
    Emmanuel

  6. #6
    Here is the .cpp file I created in its entirety:

    Code:
    #include <Arduino.h>
    #include "synth_waveform2.h"
    #include "arm_math.h"
    #include "utility/dspinst.h"
    
    void AudioSynthWaveformModulated2::update(void)
    {
    	audio_block_t *block, *moddata, *shapedata, *fmoddata;
    	int16_t *bp, *bpf, *end;
    	int32_t val1, val2;
    	int16_t magnitude15;
    	uint32_t i, ph, index, index2, scale, priorphase;
    	const uint32_t inc = phase_increment;
      
    	moddata = receiveReadOnly(0);
    	shapedata = receiveReadOnly(1);
      fmoddata = receiveReadOnly(2);
    
    	ph = phase_accumulator;
    
    	priorphase = phasedata[AUDIO_BLOCK_SAMPLES-1];
    	if (moddata && modulation_type == 0) {
    		// Frequency Modulation
    		bp = moddata->data;
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			int32_t n = (*bp++) * modulation_factor; // n is # of octaves to mod
    			int32_t ipart = n >> 27; // 4 integer bits
    			n &= 0x7FFFFFF;          // 27 fractional bits
    			n = (n + 134217728) << 3;
    			n = multiply_32x32_rshift32_rounded(n, n);
    			n = multiply_32x32_rshift32_rounded(n, 715827883) << 3;
    			n = n + 715827882;
    			uint32_t scale = n >> (14 - ipart);
    			uint64_t phstep = (uint64_t)inc * scale;
    			uint32_t phstep_msw = phstep >> 32;
    			if (phstep_msw < 0x7FFE) {
    				ph += phstep >> 16;
    			} else {
    				ph += 0x7FFE0000;
    			}
    			phasedata[i] = ph;
    		}
    		release(moddata);
        if (fmoddata) release(fmoddata);
    //    return;   
    	} else if (moddata && modulation_type == 1) {
    
    		// Phase Modulation
    		bp = moddata->data;
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			// more than +/- 180 deg shift by 32 bit overflow of "n"
    			uint32_t n = (uint16_t)(*bp++) * modulation_factor;
    			phasedata[i] = ph + n;
    			ph += inc;
    		}
    		release(moddata);
        if (fmoddata) release(fmoddata);
    	} else if (moddata && fmoddata && modulation_type == 2) {
        bpf = fmoddata->data;
        uint32_t inc2[AUDIO_BLOCK_SAMPLES];
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
          int32_t n = (*bpf++) * fmodulation_factor; // n is # of octaves to mod
          int32_t ipart = n >> 27; // 4 integer bits
          n &= 0x7FFFFFF;          // 27 fractional bits
          n = (n + 134217728) << 3;
          n = multiply_32x32_rshift32_rounded(n, n);
          n = multiply_32x32_rshift32_rounded(n, 715827883) << 3;
          n = n + 715827882;
          uint32_t scale = n >> (14 - ipart);
          uint64_t phstep = (uint64_t)inc * scale;
          uint32_t phstep_msw = phstep >> 32;
          if (phstep_msw < 0x7FFE) {
            inc2[i] = phstep >> 16;
          } else {
            inc2[i] = 0x7FFE0000;
          }
        }
        release(fmoddata);
     
        bp = moddata->data;
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
          // more than +/- 180 deg shift by 32 bit overflow of "n"
          uint32_t n = (uint16_t)(*bp++) * modulation_factor;
          phasedata[i] = ph + n;
          ph += inc2[i];
        }
        release(moddata);
    
      } else if (fmoddata && modulation_type == 2) {
    
        bpf = fmoddata->data;
        uint32_t inc2[AUDIO_BLOCK_SAMPLES];
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
          int32_t n = (*bpf++) * fmodulation_factor; // n is # of octaves to mod
          int32_t ipart = n >> 27; // 4 integer bits
          n &= 0x7FFFFFF;          // 27 fractional bits
          n = (n + 134217728) << 3;
          n = multiply_32x32_rshift32_rounded(n, n);
          n = multiply_32x32_rshift32_rounded(n, 715827883) << 3;
          n = n + 715827882;
          uint32_t scale = n >> (14 - ipart);
          uint64_t phstep = (uint64_t)inc * scale;
          uint32_t phstep_msw = phstep >> 32;
    
          if (phstep_msw < 0x7FFE) {
            ph += phstep >> 16;
          } else {
            ph += 0x7FFE0000;
          }
          phasedata[i] = ph;    
        
        
        }
        release(fmoddata);
      } else if (moddata && modulation_type == 2) {
        // Phase Modulation
        bp = moddata->data;
        for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
          // more than +/- 180 deg shift by 32 bit overflow of "n"
          uint32_t n = (uint16_t)(*bp++) * modulation_factor;
          phasedata[i] = ph + n;
          ph += inc;
        }
        release(moddata);
        if (fmoddata) release(fmoddata);		
    	}	else {
    		// No Modulation Input
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			phasedata[i] = ph;
    			ph += inc;
    		}
        if (fmoddata) release(fmoddata);
    	}
    	phase_accumulator = ph;
    
    	// If the amplitude is zero, no output, but phase still increments properly
    	if (magnitude == 0) {
    		if (shapedata) release(shapedata);
    		return;
    	}
    	block = allocate();
    	if (!block) {
    		if (shapedata) release(shapedata);
    		return;
    	}
    	bp = block->data;
    
    	// Now generate the output samples using the pre-computed phase angles
    	switch(tone_type) {
    	case WAVEFORM_SINE:
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			ph = phasedata[i];
    			index = ph >> 24;
    			val1 = AudioWaveformSine[index];
    			val2 = AudioWaveformSine[index+1];
    			scale = (ph >> 8) & 0xFFFF;
    			val2 *= scale;
    			val1 *= 0x10000 - scale;
    			*bp++ = multiply_32x32_rshift32(val1 + val2, magnitude);
    		}
    		break;
    
    	case WAVEFORM_ARBITRARY:
    		if (!arbdata) {
    			release(block);
    			if (shapedata) release(shapedata);
    			return;
    		}
    		// len = 256
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			ph = phasedata[i];
    			index = ph >> 24;
    			index2 = index + 1;
    			if (index2 >= 256) index2 = 0;
    			val1 = *(arbdata + index);
    			val2 = *(arbdata + index2);
    			scale = (ph >> 8) & 0xFFFF;
    			val2 *= scale;
    			val1 *= 0x10000 - scale;
    			*bp++ = multiply_32x32_rshift32(val1 + val2, magnitude);
    		}
    		break;
    
    	case WAVEFORM_PULSE:
    		if (shapedata) {
    			magnitude15 = signed_saturate_rshift(magnitude, 16, 1);
    			for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    				uint32_t width = ((shapedata->data[i] + 0x8000) & 0xFFFF) << 16;
    				if (phasedata[i] < width) {
    					*bp++ = magnitude15;
    				} else {
    					*bp++ = -magnitude15;
    				}
    			}
    			break;
    		} // else fall through to orginary square without shape modulation
    
    	case WAVEFORM_SQUARE:
    		magnitude15 = signed_saturate_rshift(magnitude, 16, 1);
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			if (phasedata[i] & 0x80000000) {
    				*bp++ = -magnitude15;
    			} else {
    				*bp++ = magnitude15;
    			}
    		}
    		break;
    
    	case WAVEFORM_SAWTOOTH:
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			*bp++ = signed_multiply_32x16t(magnitude, phasedata[i]);
    		}
    		break;
    
    	case WAVEFORM_SAWTOOTH_REVERSE:
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			*bp++ = signed_multiply_32x16t(0xFFFFFFFFu - magnitude, phasedata[i]);
    		}
    		break;
    
    	case WAVEFORM_TRIANGLE_VARIABLE:
    		if (shapedata) {
    			for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    				uint32_t width = (shapedata->data[i] + 0x8000) & 0xFFFF;
    				uint32_t rise = 0xFFFFFFFF / width;
    				uint32_t fall = 0xFFFFFFFF / (0xFFFF - width);
    				uint32_t halfwidth = width << 15;
    				uint32_t n;
    				ph = phasedata[i];
    				if (ph < halfwidth) {
    					n = (ph >> 16) * rise;
    					*bp++ = ((n >> 16) * magnitude) >> 16;
    				} else if (ph < 0xFFFFFFFF - halfwidth) {
    					n = 0x7FFFFFFF - (((ph - halfwidth) >> 16) * fall);
    					*bp++ = (((int32_t)n >> 16) * magnitude) >> 16;
    				} else {
    					n = ((ph + halfwidth) >> 16) * rise + 0x80000000;
    					*bp++ = (((int32_t)n >> 16) * magnitude) >> 16;
    				}
    				ph += inc;
    			}
    			break;
    		} // else fall through to orginary triangle without shape modulation
    
    	case WAVEFORM_TRIANGLE:
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			ph = phasedata[i];
    			uint32_t phtop = ph >> 30;
    			if (phtop == 1 || phtop == 2) {
    				*bp++ = ((0xFFFF - (ph >> 15)) * magnitude) >> 16;
    			} else {
    				*bp++ = (((int32_t)ph >> 15) * magnitude) >> 16;
    			}
    		}
    		break;
    	case WAVEFORM_SAMPLE_HOLD:
    		for (i=0; i < AUDIO_BLOCK_SAMPLES; i++) {
    			ph = phasedata[i];
    			if (ph < priorphase) { // does not work for phase modulation
    				sample = random(magnitude) - (magnitude >> 1);
    			}
    			priorphase = ph;
    			*bp++ = sample;
    		}
    		break;
    	}
     
    	if (tone_offset) {
    		bp = block->data;
    		end = bp + AUDIO_BLOCK_SAMPLES;
    		do {
    			val1 = *bp;
    			*bp++ = signed_saturate_rshift(val1 + tone_offset, 16, 0);
    		} while (bp < end);
    	}
    	if (shapedata) release(shapedata);
    	transmit(block, 0);
    	release(block);
    }
    and here is the .h file:
    Code:
    #ifndef synth_waveform2_h_
    #define synth_waveform2_h_
    
    #include <Arduino.h>
    #include "AudioStream.h"
    #include "arm_math.h"
    
    // waveforms.c
    extern "C" {
    extern const int16_t AudioWaveformSine[257];
    }
    
    
    #define WAVEFORM_SINE              0
    #define WAVEFORM_SAWTOOTH          1
    #define WAVEFORM_SQUARE            2
    #define WAVEFORM_TRIANGLE          3
    #define WAVEFORM_ARBITRARY         4
    #define WAVEFORM_PULSE             5
    #define WAVEFORM_SAWTOOTH_REVERSE  6
    #define WAVEFORM_SAMPLE_HOLD       7
    #define WAVEFORM_TRIANGLE_VARIABLE 8
    
    class AudioSynthWaveformModulated2 : public AudioStream
    {
    public:
    	AudioSynthWaveformModulated2(void) : AudioStream(3, inputQueueArray),
    		phase_accumulator(0), phase_increment(0), modulation_factor(32768), fmodulation_factor(32768),
    		magnitude(0), arbdata(NULL), sample(0), tone_offset(0),
    		tone_type(WAVEFORM_SINE), modulation_type(0) {
    	}
    
    	void frequency(float freq) {
    		if (freq < 0.0) {
    			freq = 0.0;
    		} else if (freq > AUDIO_SAMPLE_RATE_EXACT / 2) {
    			freq = AUDIO_SAMPLE_RATE_EXACT / 2;
    		}
    		phase_increment = freq * (4294967296.0 / AUDIO_SAMPLE_RATE_EXACT);
    		if (phase_increment > 0x7FFE0000u) phase_increment = 0x7FFE0000;
    	}
    	void amplitude(float n) {	// 0 to 1.0
    		if (n < 0) {
    			n = 0;
    		} else if (n > 1.0) {
    			n = 1.0;
    		}
    		magnitude = n * 65536.0;
    	}
    	void offset(float n) {
    		if (n < -1.0) {
    			n = -1.0;
    		} else if (n > 1.0) {
    			n = 1.0;
    		}
    		tone_offset = n * 32767.0;
    	}
    	void begin(short t_type) {
    		tone_type = t_type;
    	}
    	void begin(float t_amp, float t_freq, short t_type) {
    		amplitude(t_amp);
    		frequency(t_freq);
    		tone_type = t_type;
    	}
    	void arbitraryWaveform(const int16_t *data, float maxFreq) {
    		arbdata = data;
    	}
    	void frequencyModulation(float octaves) {
    		if (octaves > 12.0) {
    			octaves = 12.0;
    		} else if (octaves < 0.1) {
    			octaves = 0.1;
    		}
    		modulation_factor = octaves * 4096.0;
    		modulation_type = 0;
    	}
    	void phaseModulation(float degrees) {
    		if (degrees > 9000.0) {
    			degrees = 9000.0;
    		} else if (degrees < 30.0) {
    			degrees = 30.0;
    		}
    		modulation_factor = degrees * (65536.0 / 180.0);
    		modulation_type = 1;
    	}
      void pitchphaseModulation(float octaves, float degrees) {
        if (octaves > 12.0) {
          octaves = 12.0;
        } else if (octaves < 0.1) {
          octaves = 0.1;
        }
        fmodulation_factor = octaves * 4096.0;
        if (degrees > 9000.0) {
          degrees = 9000.0;
        } else if (degrees < 30.0) {
          degrees = 30.0;
        }
        modulation_factor = degrees * (65536.0 / 180.0);        
        modulation_type = 2;
      } 
    	virtual void update(void);
    
    private:
    	audio_block_t *inputQueueArray[3];
    	uint32_t phase_accumulator;
    //  uint32_t phase_accumulator2;
    	uint32_t phase_increment;
    	uint32_t modulation_factor;
      uint32_t fmodulation_factor;
    	int32_t  magnitude;
    	const int16_t *arbdata;
    	uint32_t phasedata[AUDIO_BLOCK_SAMPLES];
    //  uint32_t phasedata2[AUDIO_BLOCK_SAMPLES+1];
    	int16_t  sample; // for WAVEFORM_SAMPLE_HOLD
    	int16_t  tone_offset;
    	uint8_t  tone_type;
    	uint8_t  modulation_type;
    };
    
    
    #endif
    So it's a new class that is a copy of the existing AudioSynthWaveformModulated except now it accepts two inputs for modulation. If you add the cpp and h file to your project you can call this class instead of the base class that is part of the audio library. Everything is the same, except, for the new class, input 0 is the modulation input, and, input 2 is the vibrato input (I called it a pitch LFO). Example below for how I patch my operators and pitch LFO. Both Operator B and the PitchLFO are inputs to Operator A. (And I have the PitchLFO as an input to all operators). I'm an amateur and the code is as well, but, it does work.

    Code:
          this->patchCords[0] = new AudioConnection(*this->opAenv, 0, *this->mixer, 0);
          this->patchCords[1] = new AudioConnection(*this->opA, 0, *this->opAenv, 0);
          this->patchCords[2] = new AudioConnection(*this->PitchLFO, 0, *this->opA, 2);
          this->patchCords[3] = new AudioConnection(*this->opBenv, 0, *this->opA, 0);

  7. #7
    Senior Member
    Join Date
    Dec 2018
    Posts
    186
    Wow!! A big Thank you !
    I think your contribution will help many people who like playing FM synthesis.
    Thanks again
    Emmanuel

  8. #8
    Senior Member
    Join Date
    Dec 2018
    Posts
    186
    Hello Murdog,

    I tried your solution. I have the phase modulation working, but FM modulation doesn't work. I must have done something wrong.
    I have made a very simple sketch with a LFO modulating frequency of an oscillator. I have included your modified .h and .ccp files.
    If you have time, could you point me some directions to tackle the problem ?
    Emmanuel

    Code:
    //input 0 -> phase modulation
    //input 1 -> shape
    //input 2 -> FM modulation
    
    #include "synth_waveform2.h"
    
    #include <Audio.h>
    
    // GUItool: begin automatically generated code
    AudioSynthWaveform       lfo;      //xy=299,252
    AudioSynthWaveformModulated2 waveformMod1;   //xy=483,253
    AudioAmplifier           Volume_amp;     //xy=820,254
    AudioOutputI2S           i2s1;           //xy=985,256
    AudioConnection          patchCord1(lfo, 0, waveformMod1, 2);      //lfo connects to 2nd osc input
    AudioConnection          patchCord2(waveformMod1, Volume_amp);
    AudioConnection          patchCord3(Volume_amp, 0, i2s1, 0);
    AudioConnection          patchCord4(Volume_amp, 0, i2s1, 1);
    // GUItool: end automatically generated code
    
    
    void setup() {
      AudioMemory(20);
      waveformMod1.begin(0.5, 150, WAVEFORM_SINE);
      lfo.begin(0.2, 8, WAVEFORM_SINE);
      Volume_amp.gain(0.01);    //VOL
    }
    
    void loop() {
    }

  9. #9
    try this:
    Code:
    waveformMod1.pitchphaseModulation(1,720);
    waveformMod1.begin(0.5, 150, WAVEFORM_SINE);
    lfo.begin(0.2, 8, WAVEFORM_SINE);

  10. #10
    Senior Member
    Join Date
    Dec 2018
    Posts
    186
    Hey, it's working now !! Big thank you.
    Your code is really helpful. I hope this will be implemented on a new release.
    Emmanuel

  11. #11
    Quote Originally Posted by emmanuel63 View Post
    Hey, it's working now !! Big thank you.
    Your code is really helpful. I hope this will be implemented on a new release.
    Emmanuel
    Glad I could help!
    Yeah, I think it would be nice to have as an option in a future release as well. A cleaned up version, anyway.

Posting Permissions

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