Vowel class transition steepness

Hi,
I am fiddling around with the Vowel class by Til Boverman et al.
There is a pdf explaining its uses, but I find it hard to transform the descriptions into code. If any of the devolopers read this: are there code examples corresponding to the sounds?
Anyway I am trying to understand how to manipulate the steepness of the transitions between formants for the use in DynKlang. I understand that there is a function ampAt that allows to plot these transitions for a range of frequences (in this case: from 0 to 4000 Hz)

//without manipulation
Vowel(\a).ampAt((0..4000)).ampdb.plot; 

// the second argument, the "filter Order" for the 4 (5?)  formants
Vowel(\a).ampAt((0..4000), [2, 1, 0.4, 2]).ampdb.plot;

This seems pretty convenient, but how do I get the manipulated transition curves into a SynthDef? My hope is to do something like this:

SynthDef(\dynklang, {
|vow=1|
    var sig, env, array,
    vowel, freqs, widths, dBs, range;
   
    vowel = Vowel(\a, \bass);
freqs = vowel.freqs;
dBs = vowel.dBs;
witdths= vowel.widths;
    range = (0..4000);
// this does not work
    vowel.ampAt(range, [\stp1.kr(1), \stp2.kr(1), \stp3.kr(1), \stp4.kr(1)]).ampdb;*
    #freqs, dBs, widths = vowel.asArray;*


	sig = DynKlang.ar(
		`[
            freqs , 
            dBs.dbamp,
	    widths
	]);
	Out.ar(\out.kr(0), sig);

}

I think it would be a nice sonification synth.
Can someone lead me to the right direction? Thanks.
Q-Boris

Hm… I’m not clear what you’re trying to do.

vowel.freqs will give you the center frequencies of the formants. Then it looks like you’re trying to synthesize sine waves at exactly those frequencies, but only those frequencies. “Steepness” doesn’t matter at the exact center frequencies – it only matters between the frequencies. So, if you’re not synthesizing any frequencies between the formants, then you’re not going to hear anything related to steepness.

You do produce a range of arbitrary frequencies (range = (0 .. 4000) – incidentally, summing sinewaves at integer frequencies will produce 1-second bandlimited impulses! where impulses are relevant to vocal synthesis, but the 1-second feature is also probably not what you wanted). But then it’s not actually used.

vowel.asArray only returns the vowel object’s characteristics – having called ampAt just before this does not modify the behavior of asArray.

So I guess maybe you want:

(
SynthDef(\dynklang, {
	|vow=1|
	var sig, env, array,
	vowel, range;
	
	vowel = Vowel(\a, \bass);
	
	// to avoid the impulse-y-ness, I'll choose random
	// frequency, log-linear distributed
	range = Array.fill(100, { exprand(200, 5000) });

	// start simple, I don't see any need for 4 widths at once
	// you can `.set(\stp1, ...)` to compare
	array = vowel.ampAt(range, \stp1.kr(1));
	
	sig = DynKlang.ar(
		`[
			range,  // these are already the frequencies
			array,  // these are already the amps
			
			// check documentation, these are *phases*, not widths
			Array.fill(range.size, 0)
			
		]  // suggest to close brackets on the same level where they were opened
	);
	
	// I'm using headphones, so 1/ don't blow out the volume
	// and 2/ stereo (.dup)
	Out.ar(\out.kr(0), (sig * \amp.kr(0.01)).dup);
	
}).add;
)

…? But not sure.

hjh

thank you @jamshark70. I think the Synth is sounding more like I imagined.
The reason I posted this was my attempt to recreate this example (as mentioned on page 4 of the pdf).
The first paragraph makes so much sense, thank you. However, I still don’t understand the second paragraph.

I am sorry, but what does 1- second bandlimited impulses mean?
I must also admit that I don’t understand what // you can ``.set(\stp1, ...) to compare` means. Could you point to an explanation, please?

Hey there,

thanks for being interested in the Vowel class.

You are in luck!
I was able to dig out the exact code to reproduce the sound you hear in the sound example. Wasn’t easy, it’s 12 years ago :slight_smile:

Since it is impossibly embarrassing to show the exact code, I adapted it to (my) modern style of coding and uploaded the result to sccode.org.

Also, there is a helpfile for the Vowel class and, additionally, there are some nice-sounding examples here.
They show nicely, how to approach working with the Vowel class. Maybe it helps you to get started…

In case sccode.org will get lost at some point, here is the code of the sound example:

(
{ // even odd harmonics
	var baseFreq = 100;
	var numFreqs = 69;
	var vowel = Vowel(\a, \bass);
	var startFilterOrder = 4.0;
	var stopFilterOrders = [0.8, 0.65, 0.5, 0.35, 0.1];
	var freqs, evenFreqs, oddFreqs, amps, evenAmps, oddAmps, orders;
	var dur = 12;
	var evenAmpMod = SinOsc.kr(freq: 1).exprange(0.04, 1).sqrt;
	var oddAmpMod = SinOsc.kr(freq: 1, phase: pi).exprange(0.04, 1).sqrt;
	var ampEnv = Env.linen(attackTime: 0.01, sustainTime: dur, releaseTime: 0.2).kr(doneAction: 2);
	
	
	evenFreqs = ((1, 3 .. numFreqs) * baseFreq);
	oddFreqs = ((2, 4 .. numFreqs) * baseFreq);
	
	
	// there are 5 formants in each vowel, ergo 5 filter orders may be provided
	orders = stopFilterOrders.collect{|stop, i|  
		XLine.kr(startFilterOrder, stop, dur * ( 4 + (2 * i)/dur))
	};
	
	evenAmps = vowel.ampAt(evenFreqs, orders ) * evenAmpMod;
	oddAmps = vowel.ampAt(oddFreqs, orders ) * oddAmpMod;
	
	
	freqs = evenFreqs ++ oddFreqs;
	amps = evenAmps ++ oddAmps;
	
	amps = amps.normalizeSum; // tame amplitudes
	
	DynKlang.ar(`[freqs, amps , nil] ) * 0.1 * ampEnv;
}.play
)
1 Like

is there any particular reason to use the vowel class vs. mod FM or single sideband PM to create formants?

the vowel class is primarily a helper that provides easy access to, well, vowels and theor formants. it does not do (and does not for force you to use) a specific synthesis method.

But in the end you want to make sound and use a specific synthesis method for example dynklank like in the original example, or?
So I would like to know, where the benefits are to use such a class over other approaches like mod FM or single sideband PM.

The Vowel class does not do any synthesis by itself.

So it is not an alternative to FM or PM. Therefore there is no way to compare Vowel to either of those.

Vowel provides you with usable parameters that you can apply in FM, PM, resonators, whatever. That’s all.

hjh

hey Everybody, thanks for your answers and ideas.
thank you Till, for the Code.
Q-Boris

sorry, my question was nonsense.

Ive just noticed that using the class leads to some questions every now and then (also in the thread i have shared) but basically you need arrays of amps and freqs and set the bws. The reason why you need these values becomes more clear if you have a look at the two approaches i have shared in the thread for creating formants.
When you understand the design of formants you can decide if you want to use the Vowel class to create these arrays or not IMO because there is no mysterious synthesis happening. Maybe what im saying is offtopic and unimportant to the thread and im glad a solution to the initial question was posted.
I just wanted to add this because part of the question was about the “transition steepness”
sorry for the noise.

Am I right in that this patch is kind of acting as an additive synth by mapping the harmonics amplitudes to the Vowel Object? Then passing them to the Klank Ugen. Kind of ? Trying to understand it, Thanks

essentially yes. Apart from that DynKlang is an array of SinOscs (in difference to Klank/DynKlank, which are arrays of resonators).

This is great, especially in the “first run” of this function. I translated this into a SynthDef and I struggle to change the “orders” in a running Synth.
I tried this

SynthDef(\oddeven, {
    |baseFreq = 100, vowelIndex|
	var numFreqs = 69;
	//var startFilterOrder = 0.1;
	var startFilterOrders = NamedControl.kr(\startFilterOrders, [0.1, 0.1, 0.1, 0.1]);
	var stopFilterOrders = NamedControl.kr(\stopFilterOrders, [2.0, 4.0, 0.25, 3.25]);
	var sig, baseIndex, vowel, freqs, evenFreqs, oddFreqs, amps, evenAmps, oddAmps, orders;
        //var dur = 12;
	var evenAmpMod = SinOsc.kr(freq: 1).exprange(\modRange1.kr(0.04), \modRange2.kr(0.6)).sqrt;
	var oddAmpMod = SinOsc.kr(freq: 1, phase: pi).exprange(\modRange1.kr(0.04), 
         \modRange2.kr(0.6)).sqrt;
	//var ampEnv = Env.linen(attackTime: 0.01, sustainTime: dur, releaseTime: 0.2).kr(doneAction: 2);

        vowel = Vowel(\a, \bass);

	evenFreqs = ((1, 3 .. numFreqs) * baseFreq);
	oddFreqs = ((2, 4 .. numFreqs) * baseFreq);


	// there are 5 formants in each vowel, ergo 5 filter orders may be provided
	orders = stopFilterOrders.collect{|stop, i|
        EnvGen.kr(Env.new([startFilterOrders[i], stop], 2*i, curve: \crv.kr(3) ));

	};

	evenAmps = vowel.ampAt(evenFreqs, orders) * evenAmpMod;
        oddAmps = vowel.ampAt(oddFreqs, orders) * oddAmpMod;

	freqs = evenFreqs ++ oddFreqs;
	amps = evenAmps ++ oddAmps;

	amps = amps.normalizeSum; // tame amplitudes

    sig = DynKlang.ar(`[freqs, amps, nil] ) * \amp.kr(0.1) ;
    Out.ar(\out.kr(0), sig!2);
}).add;
// Instantiating the Synth
x = Synth(\oddeven);
// setting
x.setn(\startFilterOrders, [2, 0.1, 2.0, 0.25], \stopFilterOrders, [3, 0.1, 0.5, 0.25])

Neither x.set nor x.setn seems to have an effect on the filter orders.
I can put an argument in these lines, ignoring the previously created orders. (this has an effect on the calculation of the evenAmps and oddAmps.)

evenAmps = vowel.ampAt(evenFreqs, \order.kr(0.5)) * evenAmpMod;
oddAmps = vowel.ampAt(oddFreqs, \order.kr(0.5)) * oddAmpMod;

But I would like to play around with the filter orders as an array. Is this possible?

Envelope points are read only at the moment of starting a segment. Once the last level is reached, it isn’t modulateable in the envelope.

What you could do is apply the modulation to the stop-order. Eg, the envelope could go begin at start/stop and run to 1.0. Then multiply the stop-order control input by this.

hjh

Thank you for the hint. This sounds interesting but embarrassingly, I don’t get it. This probably makes no sense, but this is what I understand.

apply the modulation to the stop-order

(in the SynthDef):

var filterMod =  SinOsc.kr(\modFreq.kr(1.0)); 
var stopFilterOrders = NamedControl.kr(\stopFilterOrders, [2.0, 4.0, 0.25, 3.25] * filterMod;

the envelope could go begin at start/stop and run to 1.0:

orders = stopFilterOrders.collect{|stop, i|
       EnvGen.kr(Env.new([startFilterOrders[i], stop, 1.0], 2*i, curve: \crv.kr(3) ));

//Then multiply the stop-order control input by this:

EnvGen.kr(Env.new([startFilterOrders[i], stop * filterMod , 1.0], 2*i, curve: \crv.kr(3) ));// modulating the stop-order?

As for the setting of the Synth, how would that work? Can I feed new stopOrder arrays to the running Synth?
Thank you.

This is new, isn’t it? Inventing a new problem before solving the old one – important in programming to not get distracted.

What I meant is like this:

In that formula, the envelope’s ending position can’t adapt, but stop can.

hjh

Inventing a new problem before solving the old one – important in programming to not get distracted.
wise words.

I don’t really understand why this suddenly works, but it does. Thank you!