Formant Vowel Filter

Programming

Formula / C Code

For about seven years I have had a lot of fun manipulating sound, or more specifically sound generated from a software synthesizer I am programming, with DSP – Digital Signal Processing. For my synthesizer I have programmed not just tone generation via multiple synthesis methods, but also modulation, effects, and filtering of those tones.

In the past I have tried out new DSP code ideas directly in Visual Studio, compiling the C/C++ code, and then open my VST3 plugin in the Reaper DAW software., However, I recently came upon “Formula” and VST or stand-alone program that allow one to quickly create and test DSP C code within the App. You may find information about “Formula”, including a download link here; https://soundspear.com/product/formula

The App itself contains, at the time of writing, about formulas, pieces of C code, anywhere from filters, to delay, chorus, reverb, and distortion. One of these included formula’s, are “Formant vowel” which is a formant filter that can make a waveform such as a sawtooth, sound like a person speaking vowels. I incorporated this into my synthesizer, and quickly expanded the code from being able to “only” generate 5 vowels via a cutoff parameter ranging from 0 to 1, to code that could smoothly mix between two adjacent vowels. It sounds cool when applying an envelope, and/or LFO to the cutoff frequency. As I was unable to submit my enhanced version to the developer, I am publishing it here on my website for anyone to use as they wish.

Note if you’re not using the “Fomula” app to test this, you need to replace “KNOB_1” with your variable holding the current cutoff frequency, from 0.01f to 0.99f.

/*
    Author or source: Alex
    
    Public source code by alex@smartelectronix.com
    Simple example of implementation of formant filter
    Vowelnum can be 0,1,2,3,4 <=> A,E,I,O,U
    Good for spectral rich input like saw or square
    
    Expanded by René W. Feuerlein on June 25th, 2026
    to smoothly mix between two adjacent vowels, although
    mixing from 5th to 1st has issues, so note I'm mixing 
    from 5th to second. Sounds great with an envelope or 
    LFO modulating the cutoff frequency. DKDiveDude.com
*/

const double coeff[5][11]= {
	{ 8.11044e-06,
	8.943665402,        -36.83889529,   92.01697887,    -154.337906,    181.6233289,
	-151.8651235,   89.09614114,        -35.10298511,   8.388101016,    -0.923313471  ///A
	},
	{4.36215e-06,
	8.90438318, -36.55179099,   91.05750846,    -152.422234,    179.1170248,  ///E
	-149.6496211,87.78352223,   -34.60687431,   8.282228154,    -0.914150747
	},
	{ 3.33819e-06,
	8.893102966,        -36.49532826,   90.96543286,    -152.4545478,   179.4835618,
	-150.315433,        88.43409371,    -34.98612086,   8.407803364,    -0.932568035  ///I
	},
	{1.13572e-06,
	8.994734087,        -37.2084849,    93.22900521,    -156.6929844,   184.596544,   ///O
	-154.3755513,       90.49663749,    -35.58964535,   8.478996281,    -0.929252233
	},
	{4.09431e-07,
	8.997322763,        -37.20218544,   93.11385476,    -156.2530937,   183.7080141,  ///U
	-153.2631681,       89.59539726,    -35.12454591,   8.338655623,    -0.910251753
	}
};
//---------------------------------------------------------------------------------
static double memory[10]={0,0,0,0,0,0,0,0,0,0};
//---------------------------------------------------------------------------------

formula_main
{
    int vowelNum1 = fmin(4, 5 * KNOB_1);
    
    int vowelNum2 = vowelNum1 + 1;
    if (vowelNum2 > 4) vowelNum2 = 1;

    // Mix Ratio
    float cutoffMin = 0.01f;
    float cutoffMax = 0.99f;
    float cutoffSectionRange = (cutoffMax - cutoffMin) / 5;
    
    float mix1 = (vowelNum1 + 1) - (KNOB_1 - cutoffMin) / cutoffSectionRange;
    float mix2 = 1.0f - mix1;

	double res1 = (float) ( coeff[vowelNum1][0]  * input +
						 coeff[vowelNum1][1]  *memory[0] +
						 coeff[vowelNum1][2]  *memory[1] +
						 coeff[vowelNum1][3]  *memory[2] +
						 coeff[vowelNum1][4]  *memory[3] +
						 coeff[vowelNum1][5]  *memory[4] +
						 coeff[vowelNum1][6]  *memory[5] +
						 coeff[vowelNum1][7]  *memory[6] +
						 coeff[vowelNum1][8]  *memory[7] +
						 coeff[vowelNum1][9]  *memory[8] +
						 coeff[vowelNum1][10] *memory[9] );
						 
	double res2 = (float) ( coeff[vowelNum2][0]  * input +
						 coeff[vowelNum2][1]  *memory[0] +
						 coeff[vowelNum2][2]  *memory[1] +
						 coeff[vowelNum2][3]  *memory[2] +
						 coeff[vowelNum2][4]  *memory[3] +
						 coeff[vowelNum2][5]  *memory[4] +
						 coeff[vowelNum2][6]  *memory[5] +
						 coeff[vowelNum2][7]  *memory[6] +
						 coeff[vowelNum2][8]  *memory[7] +
						 coeff[vowelNum2][9]  *memory[8] +
						 coeff[vowelNum2][10] *memory[9] );

    float resCombined = res1 * mix1 + res2 * mix2;

	memory[9]= memory[8];
	memory[8]= memory[7];
	memory[7]= memory[6];
	memory[6]= memory[5];
	memory[5]= memory[4];
	memory[4]= memory[3];
	memory[3]= memory[2];
	memory[2]= memory[1];
	memory[1]= memory[0];   
	memory[0]=(double) resCombined;
	
	return resCombined;
}

Leave a Reply

Your email address will not be published. Required fields are marked *