Main Archive Specials Wiki | FAQ Links Submit Forum

AM Formantic Synthesis

References : Posted by Paul Sernine

Notes :
Here is another tutorial from Doc Rochebois.
It performs formantic synthesis without filters and without grains. Instead, it uses "double carrier amplitude modulation" to pitch shift formantic waveforms. Just beware the phase relationships to avoid interferences. Some patches of the DX7 used the same trick but phase interferences were a problem. Here, Thierry Rochebois avoids them by using cosine-phased waveforms.

Various formantic waveforms are precalculated and put in tables, they correspond to different formant widths.
The runtime uses many intances (here 4) of these and pitch shifts them with double carriers (to preserve the harmonicity of the signal).

This is a tutorial code, it can be optimized in many ways.
Have Fun


Code :
// FormantsAM.cpp

// Thierry Rochebois' "Formantic Synthesis by Double Amplitude Modulation"

// Based on a tutorial by Thierry Rochebois.
// Comments by Paul Sernine.

// The spectral content of the signal is obtained by adding amplitude modulated formantic
// waveforms. The amplitude modulations spectraly shift the formantic waveforms.
// Continuous spectral shift, without losing the harmonic structure, is obtained
// by using crossfaded double carriers (multiple of the base frequency).
// To avoid unwanted interference artifacts, phase relationships must be of the
// "cosine type".

// The output is a 44100Hz 16bit stereo PCM file.


//Approximates cos(pi*x) for x in [-1,1].
inline float fast_cos(const float x)
float x2=x*x;
return 1+x2*(-4+2*x2);

//Length of the table
#define L_TABLE (256+1) //The last entry of the table equals the first (to avoid a modulo)
//Maximal formant width
#define I_MAX 64
//Table of formants
float TF[L_TABLE*I_MAX];

//Formantic function of width I (used to fill the table of formants)
float fonc_formant(float p,const float I)
float a=0.5f;
int hmax=int(10*I)>L_TABLE/2?L_TABLE/2:int(10*I);
float phi=0.0f;
for(int h=1;h {
float hann=0.5f+0.5f*fast_cos(h*(1.0f/hmax));
float gaussienne=0.85f*exp(-h*h/(I*I));
float jupe=0.15f;
float harmonique=cosf(phi);
return a;

//Initialisation of the table TF with the fonction fonc_formant.
void init_formant(void)
{ float coef=2.0f/(L_TABLE-1);
for(int I=0;I for(int P=0;P TF[P+I*L_TABLE]=fonc_formant(-1+P*coef,float(I));

//This function emulates the function fonc_formant
// thanks to the table TF. A bilinear interpolation is
// performed
float formant(float p,float i)
i=i<0?0:i>I_MAX-2?I_MAX-2:i; // width limitation
float P=(L_TABLE-1)*(p+1)*0.5f; // phase normalisation
int P0=(int)P; float fP=P-P0; // Integer and fractional
int I0=(int)i; float fI=i-I0; // parts of the phase (p) and width (i).
int i00=P0+L_TABLE*I0; int i10=i00+L_TABLE;
//bilinear interpolation.
return (1-fI)*(TF[i00] + fP*(TF[i00+1]-TF[i00]))
+ fI*(TF[i10] + fP*(TF[i10+1]-TF[i10]));

// Double carrier.
// h : position (float harmonic number)
// p : phase
float porteuse(const float h,const float p)
float h0=floor(h); //integer and
float hf=h-h0; //decimal part of harmonic number.
// modulos pour ramener p*h0 et p*(h0+1) dans [-1,1]
float phi0=fmodf(p* h0 +1+1000,2.0f)-1.0f;
float phi1=fmodf(p*(h0+1)+1+1000,2.0f)-1.0f;
// two carriers.
float Porteuse0=fast_cos(phi0); float Porteuse1=fast_cos(phi1);
// crossfade between the two carriers.
return Porteuse0+hf*(Porteuse1-Porteuse0);
int main()
//Formant table for various french vowels (you can add your own)
float F1[]={ 730, 200, 400, 250, 190, 350, 550, 550, 450};
float A1[]={ 1.0f, 0.5f, 1.0f, 1.0f, 0.7f, 1.0f, 1.0f, 0.3f, 1.0f};
float F2[]={ 1090, 2100, 900, 1700, 800, 1900, 1600, 850, 1100};
float A2[]={ 2.0f, 0.5f, 0.7f, 0.7f,0.35f, 0.3f, 0.5f, 1.0f, 0.7f};
float F3[]={ 2440, 3100, 2300, 2100, 2000, 2500, 2250, 1900, 1500};
float A3[]={ 0.3f,0.15f, 0.2f, 0.4f, 0.1f, 0.3f, 0.7f, 0.2f, 0.2f};
float F4[]={ 3400, 4700, 3000, 3300, 3400, 3700, 3200, 3000, 3000};
float A4[]={ 0.2f, 0.1f, 0.2f, 0.3f, 0.1f, 0.1f, 0.3f, 0.2f, 0.3f};

float f0,dp0,p0=0.0f;
int F=7; //number of the current formant preset
float f1,f2,f3,f4,a1,a2,a3,a4;

FILE *f=fopen("sortie.pcm","wb");
for(int ns=0;ns<10*44100;ns++)
if(0==(ns%11025)){F++;F%=8;} //formant change
f0=12*powf(2.0f,4-4*ns/(10*44100.0f)); //sweep
f0*=(1.0f+0.01f*sinf(ns*0.0015f)); //vibrato
float un_f0=1.0f/f0;
p0+=dp0; //phase increment
{ //smoothing of the commands.
float r=0.001f;

//The f0/fn coefficients stand for a -3dB/oct spectral enveloppe
float out=
+ a3*(f0/f3)*formant(p0,150*un_f0)*porteuse(f3*un_f0,p0)
+ a4*(f0/f4)*formant(p0,300*un_f0)*porteuse(f4*un_f0,p0);

short s=short(15000.0f*out);
fwrite(&s,2,1,f);fwrite(&s,2,1,f); //fichier raw pcm stereo
return 0;


Added on : 24/04/07 by Baltazar
Comment :
Quite interesting and efficient for an algo that does not use any filter ;-)              

Added on : 14/08/07 by phoenix-69
Comment :
Very funny sound !

Added on : 19/08/08 by Wait[ DOT ]
Comment :
              What header files are you including?

Added on : 30/03/11 by barbarawright[ AT ]mail15[ DOT ]com
Comment :
Following my own investigation, millions of people all over the world get the <a href="">loans</a> at well known creditors. Therefore, there's good chances to find a auto loan in all countries.

Added on : 05/05/11 by patriciasanders[ AT ]mail15[ DOT ]com
Comment :
One remembers that our life seems to be expensive, but different people need cash for various stuff and not every person gets big sums cash. Therefore to get fast loans and just bank loan will be a proper way out.

Add your own comment
Comments are displayed in fixed width, no HTML code allowed!


Are you human?

Site created and maintained by Bram
Graphic design by line.out | Server sponsered by fxpansion