189 lines
3.3 KiB
Racket
189 lines
3.3 KiB
Racket
#lang scribble/manual
|
|
@(require (for-label racket))
|
|
|
|
@title{AmpComp}
|
|
Basic psychoacoustic amplitude compensation.@section{related}
|
|
Classes/AmpCompA
|
|
@section{categories}
|
|
UGens>Analysis>Amplitude
|
|
|
|
|
|
@section{description}
|
|
|
|
|
|
Implements the (optimized) formula:
|
|
|
|
|
|
@racketblock[
|
|
compensationFactor = (root / freq) ** exp
|
|
::
|
|
|
|
|
|
Higher frequencies are normally perceived as louder, which AmpComp
|
|
compensates.
|
|
|
|
|
|
]
|
|
@section{classmethods}
|
|
|
|
|
|
@section{method}
|
|
ar, kr, ir
|
|
|
|
@section{argument}
|
|
freq
|
|
|
|
Input frequency value. For freq == root, the output is 1.0.
|
|
|
|
|
|
@section{argument}
|
|
root
|
|
|
|
Root freq relative to which the curve is calculated
|
|
(usually lowest freq).
|
|
|
|
|
|
@section{argument}
|
|
exp
|
|
|
|
Exponent: how steep the curve decreases for increasing freq.
|
|
|
|
@section{discussion}
|
|
|
|
Note that for frequencies very much smaller than root the amplitudes can become very high.
|
|
In this case limit the freq with
|
|
@racketblock[freq.max(minval)::, or use AmpCompA.
|
|
|
|
]
|
|
@section{Examples}
|
|
|
|
|
|
|
|
@racketblock[
|
|
// 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: Done.freeSelf)
|
|
)
|
|
}
|
|
).add;
|
|
)
|
|
|
|
// 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: Done.freeSelf)
|
|
)
|
|
}
|
|
).add;
|
|
)
|
|
|
|
// 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: Done.freeSelf)
|
|
)
|
|
}
|
|
).add;
|
|
)
|
|
::
|
|
|
|
]
|
|
|
|
|