SuperCollider CLASSES (extension)

Vowel

convenience class to store and manipulate formant information
Inherits from: Object

Description

A Vowel contains and handles data to describe formants that allow to distinguish the vowel sounds \a, \e, \i, \o, \u for the registers \bass, \tenor, \counterTenor, \alto and \soprano. The current implementation uses formant data that originally appeared in the C-sound Manual: http://www.csounds.com/manual/html/MiscFormants.html

2011 -- Florian Grond, Till Bovermann

Supported by: the Ambient Intelligence Group, CITEC ( http://www.techfak.uni-bielefeld.de/ags/ami ) Bielefeld University, and the TAI Studio ( http://TAI-Studio.org ), Department of Media, Aalto University, Helsinki. Many thanks go to Alberto de Campo and and Julian Rohrhuber.

Class Methods

*formLib

holds formant information

Vowel.formLib.at(\a)
Vowel.formLib.at(\a, \bass)
Vowel.formLib.at(\a, \bass, \freq)
Vowel.formLib.postTree;

Returns:

a Dictionary

*new (vowel: 'a', register: 'bass')

creates a new instance of Vowel

Arguments:

vowel

select a vowel by the symbols \a, \e, \i, \o, \u. default value is 'a'.

register

select a register by the symbols \bass, \tenor, \counterTenor, \alto, \soprano. Default value is 'bass'.

Returns:

a Vowel

Discussion:

Vowel(); // defaults to A bass
Vowel(\a, \bass); 
Vowel(\e, \tenor); 
Vowel(\i, \counterTenor); 
Vowel(\o, \alto); 
Vowel(\u, \soprano); 

{Formants.ar(100, Vowel(\e, \bass)) * 0.1 }.play
{Formants.ar(200, Vowel(\o, \alto)) * 0.1 }.play
{Formants.ar(300, Vowel(\i, \soprano)) * 0.1 }.play

The class exhibits multichannel expansion behaviour

{Formants.ar(100, Vowel([\e, \o], [\bass])) * 0.1 }.play
{Formants.ar(100, Vowel(\e, [\bass,\alto])) * 0.1 }.play
{Formants.ar(100, Vowel([\e, \o], [\bass,\alto])) * 0.1 }.play

*basicNew (freqs, dBs, widths)

You can also explicitly set the formants

Vowel.basicNew([ 300, 400, 2700, 3800, 4950 ], [ 0, -16, -35, -40, -6 ], [ 50, 10, 170, 180, 200 ])

Arguments:

freqs

an array of freqs

dBs

an array of dBs

widths

an array of widths in Hz

Returns:

a Vowel

*compose (vowelNames, registers, weights)

compose your own vowel based on the ones defined in formTable. Wraps when argument lengths differ (as in channel expansion).

Arguments:

vowelNames

an array describing the vowels' character: \a, \e, \i, \o, \u.

registers

an array describing the vowels' voice: \bass, \tenor, \counter, \alt, \sopran.

weights

an array describing the vowels' weight relative to the others. Should be a normalized sum.

Returns:

a Vowel

Discussion:

Vowel.compose([\a,\e,\i], [\bass,\soprano,\alto], [0.2, 0.3, 0.5]);
Vowel.compose([\a,\e], [\soprano], [9, 4].normalizeSum);
Vowel.compose([\a], [\tenor, \counterTenor, \soprano], [1, 4, 2].normalizeSum);

(
{ var v = Vowel.compose(
    [\a, \e, \i], 
    [\soprano, \bass, \tenor, \counterTenor, \alto], 
    ({10.rand}!5).normalizeSum
);
Formants.ar(50 + 300.rand, v) * 0.1; 
}.play;
)

Inherited class methods

Undocumented class methods

*initLib

*load (path)

Instance Methods

-freqs

-freqs = value

Array of freqs, one for each formant.

v = Vowel(\e, \tenor)
v.freqs
v.freqs_({|i| 100 * (i+1) }!5)
v.freqs

-dBs

-dBs = value

Array of dBs,

v = Vowel(\u, \counterTenor)
v.dBs
v.dBs_({|i| -10 * (i) }!5)
v.dBs

-widths

-widths = value

array of widths in Hz, one for each formant.

v = Vowel(\o, \soprano)
v.widths
v.widths_({ 500.rand }!5)
v.widths

-amps

array of amps in dB, one for each formant.

-rqs

array of rQ values, one for each formant.

-midinotes

array of midinotes, one for each formant.

-addFormants (freq: 0, db: 1, width: -100)

v = Vowel()
v.addFormants([3000,4000], -6, [200, 200])

{Formants.ar(100, v) * 0.1 }.play
{Formants.ar(100, Vowel()) * 0.1}.play
        
{[Formants.ar(100, Vowel()) * 0.1, Formants.ar(100, v) * 0.1 ]}.play

v.removeFormants([5,6])

Arguments:

freq
db
width

-removeFormants (index)

Arguments:

index

index of the formant to remove

-ampAt (freq, filterOrder: 1)

get amp value for freq according to order of filter

Vowel(\u).ampAt(100);    
    
~range = {|i|i*2}!2000;    
Vowel(\u).ampAt(~range  ).plot;
Vowel(\u).ampAt(~range  ).ampdb.plot(minval: -100, maxval: 0);    

Vowel(\u).ampAt(~range, 0.5  ).plot;
Vowel(\u).ampAt(~range, 0.5  ).ampdb.plot(minval: -100, maxval: 0);

Vowel(\u).ampAt(~range, 3  ).plot;
Vowel(\u).ampAt(~range,  3 ).ampdb.plot(minval: -100, maxval: 0);

Vowel(\u).ampAt(~range, [3,1,0.5,2]  ).plot
Vowel(\u).ampAt(~range,  [3,1,0.5,2]  ).ampdb.plot(minval: -100, maxval: 0)

Arguments:

freq
filterOrder

-plot (fmin: 0, fmax: 6000, fstep: 2, order: 1)

plot the frequency spectrum of the vowel.

Arguments:

fmin
fmax
fstep
order

-asArray

serialise the vowel into an array

Returns:

an arrays of [ freqs, dBs, widths ]

-asEvent

convert into an Event

-asKeyValuePairs (id: "")

convert into an array of key value pairs

Arguments:

id

optional argument added to each key

-addControls (id: "", rate: 'kr', lag)

creates controls in the wrapping synth such that the vowel's parameters can be controlled seamlessly from the language.

Arguments:

id

Optional. If more than one vowel need to be controlled inside one Synth, this id can be used to differentiate them from each other.

rate

either \kr or \ar

lag

array of lag times

Discussion:

(
x = SynthDef(\vowel, {|freq = 420|
    Out.ar(0, 
        Formants.ar(freq.lag(0.1), Vowel(\a).addControls(lag: 1)) * 0.01
    )
}).play
)
        
x.setn(*([\freq, exprand(100, 1000)] ++ Vowel(\u, \soprano).asKeyValuePairs));
x.setn(*([\freq, exprand(100, 500)] ++ Vowel(\e, \bass).asKeyValuePairs));
x.setn(*([\freq, exprand(400, 1000)] ++ Vowel(\a, \soprano).asKeyValuePairs));

-+ (that, adverb)

plus

Arguments:

that
adverb

-- (that, adverb)

minus

Arguments:

that
adverb

-* (that, adverb)

multiply

Arguments:

that
adverb

-/ (that, adverb)

divide

Arguments:

that
adverb

-blend (that, blendFrac: 0.5)

blends two vowels by linear interpolation between of midinotes, widths and dBs.

Arguments:

that

a Vowel

blendFrac

coefficient. Range from 0.0 (this) to 1.0 (that).

Returns:

a new instance of Vowel

-brightenRel (bright: 1, refFormant: 0)

lift the upper formants by multiplying their dBs with a factor. The sum of all dBs remains fixed.

Arguments:

bright

coefficient to brighten the vowel. Typical ranges are from 1 (no change) to 3.

refFormant

reference formant, whose amplitude remains unchanged. Default value is 0 (first formant)

Discussion:

NOTE: If the coefficient bright is > 1 and the refFormant is > 0, the resulting signal may raise to a very big amplitude.

Vowel().brightenRel(1  ).dBs.postln; 
Vowel().brightenRel(0.1).dBs.postln;
Vowel().brightenRel(1.5).dBs.postln;

// refFormant = 0 loud on the left
{Formants.ar(100, Vowel().brightenRel(MouseX.kr(0, 2), 0 )) * 0.1}.play
// refFormant = 1
{Formants.ar(100, Vowel().brightenRel(MouseX.kr(0, 2), 1 )) * 0.1}.play
// refFormant = 2
{Formants.ar(100, Vowel().brightenRel(MouseX.kr(0, 2), 2 )) * 0.1}.play
// refFormant = 3
{Formants.ar(100, Vowel().brightenRel(MouseX.kr(0, 2), 3 )) * 0.1}.play
// refFormant = 4 loud on the right
{Formants.ar(100, Vowel().brightenRel(MouseX.kr(0, 2), 4 )) * 0.1}.play

-brightenLin (bright: 1, refFormant: 0)

allows to lift the upper formants by multiplying their dBs. the sum of all dBs remains constant.

Arguments:

bright

coefficient to brighten the vowel. Typical ranges are from 1 (no change) to 3.

refFormant

reference formant, whose amplitude remains unchanged. Default value is 0 (first formant)

Discussion:

Vowel().brightenLin(-1).dBs.postln; 
Vowel().brightenLin( 0).dBs.postln;
Vowel().brightenLin( 1).dBs.postln;

// refFormant = 0 loud on the left
{Formants.ar(100, Vowel().brightenLin(MouseX.kr(0, 20), 0 )) * 0.1}.play
// refFormant = 1
{Formants.ar(100, Vowel().brightenLin(MouseX.kr(0, 20), 1 )) * 0.1}.play
// refFormant = 2
{Formants.ar(100, Vowel().brightenLin(MouseX.kr(0, 20), 2 )) * 0.1}.play
// refFormant = 3
{Formants.ar(100, Vowel().brightenLin(MouseX.kr(0, 20), 3 )) * 0.1}.play
// refFormant = 4 loud on the right
{Formants.ar(100, Vowel().brightenLin(MouseX.kr(0, 20), 4 )) * 0.1}.play

-brightenExp (bright: 1)

allows to relatively lift the upper formants by keeping the sum of gains of all formants constant.

Arguments:

bright

coefficient to brighten the vowel. Typical ranges are from 1 (no change) to 3.

Discussion:

Vowel().brightenExp(1).dBs.postln; 
Vowel().brightenExp(0.1).dBs.postln;
Vowel().brightenExp(1.5).dBs.postln;

{Formants.ar(100, Vowel(\a, \bass).brightenExp(MouseX.kr(0,5)))  * 0.1 }.play;

-asKlankRef (baseFreq: 440, numPartials: 13, ring: 1)

(describe method here)

Arguments:

baseFreq

undocumented

numPartials
ring

-printOn (stream)

prettyprint support

Arguments:

stream

-storeArgs

archival support

Inherited instance methods

Undocumented instance methods

-asFlatArray

-performBinaryOpOnSimpleNumber (aSelector, aNumber)

-performBinaryOpOnVowel (aSelector, aVowel)

-save (path, timbre: 'mytimbre', register: 'myregister')

Examples

In SynthDefs:

(
SynthDef(\vowblend,{|freq = 100, b1 = 0.5, b2 = 0.5, b3 = 0.5, b4 = 0.5 bright = 0, pan = 0|
    var va = Vowel(\a, \bass),
        ve = Vowel(\e, \tenor),
        vi = Vowel(\i, \counterTenor),
        vo = Vowel(\o, \alto),
        vu = Vowel(\u, \soprano),
        sig;
       
        sig =  Formants.ar(
            freq, 
            va
                .blend(ve, b1)
            .blend(vi, b2)
            .blend(vo, b3)
            .blend(vu, b4)
            .brightenExp(bright, 1) 
        ) 
        * EnvGen.kr(Env.perc, 3.0, doneAction: 2);

    Out.ar(0,  Pan2.ar(sig, pan, 0.1));
}).add
)


(
Task({  32.do({ arg i; 
    Synth(\vowblend, [    
        \pan, i.linlin(0,32, -1, 1 ),
        \freq, i.linlin(0,32, 30, 66 ).midicps,
        \b1, 2.rand,
        \b2, 2.rand, 
        \b3, 2.rand,
        \b4, 2.rand, 
        \bright,  1.5.rand
    ]); 
    0.25.wait

});}).play
)

using addControls to create buses

(
x = SynthDef(\test, {
    Out.ar(0, 
        Formants.ar(420, Vowel(\a).addControls(3)) * 0.01
    )
}).play
)

x.inspect

x.setn(*Vowel(\i).asKeyValuePairs(3));

JITLib style:

NdefMixer(s);

Ndef(\vowel, {Formants.ar(200, Vowel(\a, \soprano))  * 0.01 }).play

(
Ndef(\vowel, {
    Formants.ar(200, 
        Vowel(\o, \soprano)
            .blend(Vowel(\i, \tenor), SinOsc.kr(10).range(0,1))) * 0.01
}).play
)


(
Ndef(\vowel, {
    Formants.ar(LFNoise0.kr(10).exprange(100, 400), 
        Vowel(\o, \soprano)
            .brightenExp(SinOsc.kr(2).range(0,1), 1),
        unfold: true
    ).mean * 0.01
}).play
)


Ndef(\vowel, {
    Formants.ar(200, Vowel(\a, \soprano).addControls(4)) * 0.01
}).play

Ndef(\vowel).setn(*Vowel(\e, \bass).asKeyValuePairs(4).flatten)
Ndef(\vowel).setn(*Vowel(\u).asKeyValuePairs(4).flatten)


Ndef(\vowel, {
    Formants.ar(200, Vowel(\a, \soprano), unfold: true).scramble.keep(2) * 0.1
}).play

Ndef(\vowel).free(2)

Ndef(\vowel).fadeTime = 4;

(
Ndef(\vowel, {
    Formants.ar([1, 2, 4] * 240 * {LFNoise1.kr(5, 0.003, 1)}!3, Vowel(\a, [\bass, \tenor, \soprano]), 
        freqMods: LFNoise1.ar(4*[0.1, 0.2, 0.3, 0.4, 0.5].scramble, 0.1,         ampMods: [1, 1, 1, 0]
)).sum * 0.1
}).play
)

Fun.

( // CPU demanding 
~freqs = {|i| 50 * 1.5.pow(i) }!9;
~numChan = 2;
r = Routine{
    var sustain = 8, transition = 3, overlap = 4;
    var period = 1.5 * 2.sqrt;
    var harms, amps;
    0.5.wait;
    inf.do{
        harms = {|i| (i+1) * ~freqs.choose }!60;
        amps  = Vowel([\a,\e,\i,\o,\u].choose,[\bass,\tenor,\counterTenor,\alto,\soprano].choose).ampAt(harms);
        { PanAz.ar(~numChan, DynKlank.ar( `[~freqs,amps,amps],
            Decay.ar(Impulse.ar( exp(1)/5.0 ), SinOsc.kr( pi/9.0, 1.0.rand ).range(0.05,0.7) )   ) *
            EnvGen.kr(Env.new([-40,-20, -30,-40].dbamp, [2/5.0, 1/5.0,2/5.0],'exponential'), 1.0, timeScale: 35, levelScale: 0.1, doneAction: 2) ,SinOsc.kr(0.5, 1.0.rand) )}.play;
        period.wait;
    }
};
r.play;
)
r.stop;    //stop spawning new synths


(
Ndef(\vowel).fadeTime = 5;
Ndef(\vowel, {
    var freqs, dBs, widths, out;
    var baseFreq = LFNoise0.kr([5, 10] * 0.1).round(0.1).exprange(50, 200) * [2, 1.01];
    #freqs, dBs, widths =  (Vowel(\i, \soprano).blend(Vowel(\o, \bass), LFNoise1.kr(0.1265))).blend(Vowel(\e, \bass), LFNoise1.kr(10)).asArray;
    
    //freqs = freqs * SinOsc.ar([0.1, 0.2, 0.3, 0.4].scramble, Rand(), 0.1, 1);
    freqs = freqs * LFNoise1.ar([0.1, 0.2, 0.3, 0.4].scramble, 0.1, 1);
    
    out = [freqs, widths, dBs.dbamp].flop.collect{ |args| 
        Formant.ar(baseFreq, *args); 
    }.flop;

    out = out.collect{|vocal|
        Splay.ar(vocal)
    }.sum.postln;

    out
        * LFPulse.ar([9, 9.01], 0, 0.4).range(0, 1).lag(0.01, 0.5) 
        * LFPulse.ar(0.1, [0, 0.35], [0.9, 0.8]).lag(0.01)

 * 0.1

}).play
)