AmpComp basic psychoacoustic amplitude compensation



superclass: UGen


implements the (optimized) formula: compensationFactor = (root / freq) ** exp 

Higher frequencies are normally perceived as louder, which AmpComp compensates. 


*ar(freq, root, exp)

*kr(freq, root, exp)

*ir(freq, root, exp)

freq input frequency value. For freq == root, the output is 1.0.

root root freq relative to which the curve is calculated (usually lowest freq)

default value: C (60.midicps)

exp exponent: how steep the curve decreases for increasing freq (see plots below)

default value 0.3333

see also [AmpCompA]




// compare a sine without compensation


{ SinOsc.ar(MouseX.kr(300, 15000, 1)) * 0.1 }.play;


// with one that uses amplitude compensation

(

{ 

var freq;

freq = MouseX.kr(300, 15000, 1);

SinOsc.ar(freq) * 0.1 * AmpComp.kr(freq, 300) 

}.play;

)



// different sounds cause quite different loudness perception, 

// and the desired musical behavior can vary, so the exponent can be tuned:

(

{ 

var freq;

freq = MouseX.kr(300, 15000, 1);

Pulse.ar(freq) * 0.1 * AmpComp.kr(freq, 300, 1.3) 

}.play;

)


// the curves:


// exp = 0.3333

(200,210..10000).collect {|freq| (200/freq) ** 0.3333 }.plot;


// nearly linear for semitone steps:


(48..72).midicps.collect {|freq| (48.midicps/freq) ** 0.3333 }.plot; 

{ AmpComp.ar(Line.ar(48, 72, 1).midicps, 48.midicps) }.plot(1.0);


// exp = 1.2

(200,210..10000).collect {|freq| (200/freq) ** 1.2 }.plot;

(48..72).midicps.collect {|freq| (200/freq) ** 1.2 }.plot;

{ AmpComp.ar(Line.ar(48, 72, 1).midicps, 48.midicps, 1.2) }.plot(1.0);



// amplitude compensation in frequency modulation

(

{ 

var freq;

freq = MouseX.kr(300, 15000, 1);

freq = freq * SinOsc.ar(MouseY.kr(3, 200, 1), 0, 0.5, 1);

SinOsc.ar(freq) * 0.1 * AmpComp.ar(freq, 300) 

}.play;

)


// without amplitude compensation

(

{ 

var freq;

freq = MouseX.kr(300, 15000, 1);

freq = freq * SinOsc.ar(MouseY.kr(3, 200, 1), 0, 0.5, 1);

SinOsc.ar(freq) * 0.1

}.play;

)


// in granular synthesis:

(

SynthDef("pgrain", 

{ arg out = 0, sustain=0.01, amp=0.5, pan = 0;

var freq = MouseX.kr(300, 7000, 1);

var window = Env.sine(sustain, amp * AmpComp.ir(freq));

Out.ar(out, 

Pan2.ar(

SinOsc.ar(freq),

pan

) * EnvGen.ar(window, doneAction:2)

)

}

).send(s);

)


// send grains

(

fork {

loop {

s.sendBundle(0.1, [\s_new, \pgrain, -1,1,1]);

0.02.wait;

};

}

)



// try different synth defs:



// without AmpComp:


(

SynthDef("pgrain", 

{ arg out = 0, sustain=0.01, amp=0.5, pan = 0;

var freq = MouseX.kr(300, 7000, 1);

var window = Env.sine(sustain, amp);

Out.ar(out, 

Pan2.ar(

SinOsc.ar(freq),

pan

) * EnvGen.ar(window, doneAction:2)

)

}

).send(s);

)


// with AmpCompA

(

SynthDef("pgrain", 

{ arg out = 0, sustain=0.01, amp=0.5, pan = 0;

var freq = MouseX.kr(300, 7000, 1);

var window = Env.sine(sustain, amp * AmpCompA.ir(freq));

Out.ar(out, 

Pan2.ar(

SinOsc.ar(freq),

pan

) * EnvGen.ar(window, doneAction:2)

)

}

).send(s);

)