FM synthesis, unpredictable ?

emmanuel63

Well-known member
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();
}
 
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-Fun-with-FM-synthesis I've an example FM synth object you can play with and
see if it helps.
 
Thank you Mark,

Using phase modulation greatly improved the result.
I am trying know to dig in your code to understand your approach...
 
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);
}
 
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
 
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);
 
Wow!! A big Thank you !
I think your contribution will help many people who like playing FM synthesis.
Thanks again
Emmanuel
 
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() {
}
 
try this:
Code:
waveformMod1.pitchphaseModulation(1,720);
waveformMod1.begin(0.5, 150, WAVEFORM_SINE);
lfo.begin(0.2, 8, WAVEFORM_SINE);
 
Hey, it's working now !! Big thank you.
Your code is really helpful. I hope this will be implemented on a new release.
Emmanuel
 
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.
 
Hello,
I have been using your code and it is working.
I tried to use it with band limited waveforms, but unfortunately it doesn't work with it. I have been digging the code, but I am stuck. Would you have any idea ?
Emmanuel
 
Back
Top