[chuck-dev] UGen for dynamics processing: dyna
Graham Coleman
gc at gehennom.net
Sat Sep 9 19:03:43 EDT 2006
Chuck enthusiasts,
* introduction
Ever since encountering Zolzer's DAFX (great implementation-level guide on
music-dsp... along with Roads Computer Music Tutorial), I'd been wanting
to implement some of the basics for my own edification.
>From time to time chucking, I don't leave enough volume headroom (too
loud) on the dac, so sometimes, especially when playing a drum sample with
sndbuf, the sound will clip at the dac on my soundcard and make an
upleasant crunchy noise. This inspired me to write a Limiter.
* background
Dynamics Processors are objects that monitor the signal level of
a sound, and adjust a gain factor to accomplish some musical goal:
- Compressor- reduces the dynamic range of events above a threshold so
that they are more uniform in volume. By applying gain afterwards, this
results in a louder signal overall. Used frequently in music production
to produce steadier parts and louder masters.
- Limiter- keeps the signal level under some threshold by applying inverse
gain.
- Noise gate- zeros the signal when it is under some threshold to get rid
of bare audible noise.
- Expander- increases the overall dynamic range by making events under a
threshold quieter by a factor. Used in some cases with a Compressor to
preserve the dynamic range of a signal being transmitted.
for more information, see RaneNote on Dynamic Processors:
http://www.rane.com/note155.html at
* you are the prototype
I began by drafting the processing code in ChucK. It was nice to be
able to prototype in a higher-level language. Once things seem to work,
I was able to figure how to turn it into a native chuck object (for
performance and vanity's sake).
You can use "dyna-limit-chuck.ck" to test the chuck version "limiter.ck".
* implementation
Since the processors differ only by the gain transfer function, I was
able to implement them all with the limiter algorithm, by changing a
few constants- the threshold, whether the processor acts above or below
the threshold, and the slope/ratio.
First, I copied the skeleton from ugen_xxx.{h,cpp} to use as a model.
Then I began by creating a data object to hold the state of the processor.
Next I was able to implement a tick function by translating the chuck
code, and implement controls (chuck methods) for the parameters.
Finally, I was able to start testing it and write samples!
(Using the same basic design, one should be able to make a processor
that does all of these things at once by making a compound transfer
function with thresholds for each mode: http://www.rane.com/note1556.html
I will leave that work until "dynb".)
Though there is a peak-level CPU usage difference between the native
version and the chuck version, it's not as big as I thought.
* chuck samples
So that you can play and get an idea of the sound of each of the effects,
I've included sample code for each of the modes. Each sample plays a bit
of music: first with dyna bypassed (op=-1), then with the processing on.
(dyna-limit.ck, dyna-gate.ck, dyna-compress.ck, dyna-expand.ck)
Warning: dyna-limit.ck can be loud and will clip at your dac, so turn down
the main volume of your soundcard.
* documentation
I have included some suggested documentation in dyna.tex.
* how to install
Add "ugen_gkc.h" and "ugen_gkc.cpp" to your chuck sources, and to the
build process. You'll also have to add the following to chuck_compile.cpp:
towards the top of the file:
#include "ugen_gkc.h"
towards the middle of load_internal_modules():
//load gkc!
EM_log( CK_LOG_SEVERE, "module GKC..." );
load_module( env, gkc_query, "gkc", "global" );
* conclusion
I hope this is fun, and I hope this is useful.
Let it never be said that "the chuck stops here."
Graham
-------------- next part --------------
A non-text attachment was scrubbed...
Name: ugen_gkc.h
Type: text/x-chdr
Size: 861 bytes
Desc:
Url : http://lists.cs.princeton.edu/pipermail/chuck-dev/attachments/20060909/9a38eb79/attachment-0003.bin
-------------- next part --------------
A non-text attachment was scrubbed...
Name: ugen_gkc.cpp
Type: text/x-c++src
Size: 10061 bytes
Desc:
Url : http://lists.cs.princeton.edu/pipermail/chuck-dev/attachments/20060909/9a38eb79/attachment-0004.bin
-------------- next part --------------
//gkc: simple sample-wise Limiter
//translated from p100 of DAFX-Zolzer
public class Limiter extends delayp {
//ugens
UGen @ in;
//constants for the algorithm
1.0 => float slope;
0.5 => float tresh;
0.01 => float rt; //release const
0.4 => float at; //attack const
//maybe delay should be nonzero?
//run the limiter
public void limit() {
float xd; //sidechain - tracks the signal volume
while( true ) {
float f; //the gain function
//'a' is signal left after subtracting xd
math.fabs( in.last() )-xd => float a;
//a is a positive diff, don't let it be negative
if ( a < 0 ) 0=>a;
//the attack/release exponential filter
xd*(1-rt)+at*a => xd;
//this implements the function f
if ( xd > tresh ) {
//math.pow( 10, -slope*(math.log10(xd)-math.log10(tresh)) )
//=> f;
//this should be the same thing
math.pow( xd/tresh, -slope ) => f;
} else {
1.0 => f;
}
//set the gain on this
this.gain( f );
//move time forward
1::samp => now;
}
}
public void input( UGen in ) {
in @=> this.in; //capture the in ref
in => this; //connect input
}
}
-------------- next part --------------
A non-text attachment was scrubbed...
Name: dyna.tex
Type: text/x-tex
Size: 1141 bytes
Desc:
Url : http://lists.cs.princeton.edu/pipermail/chuck-dev/attachments/20060909/9a38eb79/attachment-0005.bin
-------------- next part --------------
//gkc: demonstration of dyna.limit() mode
//a limiter keeps the level from getting above a
//threshold by negatively compensating for any gains
//above that level.
//you can use this to avoid clipping at your dac
//WARNING: LOUD
sndbuf inst => dyna dyn => dac;
"special:dope" => inst.read;
//set mode and presets for limiter
dyn.limit();
//fiddle with the presets
dyn.thresh( 0.5 );
dyn.attackTime( 10::ms );
dyn.releaseTime( 100::ms );
dyn.slope( 1.0 );
//dyn.ratio( 1000000000 );
inst.gain( 0.6 );
for(0=>int i; ;i++) {
//toggle processing every 4 beats
if ( i%4==0 ) {
dyn.op( -dyn.op() );
<<<"op:",dyn.op()>>>;
}
//louder and louder "doh!"
inst.gain( 2.0*(i%4+2) );
inst.pos( 0 );
1::second => now;
}
-------------- next part --------------
//gkc: demonstration of dyna.gate() mode
//a noise gate zeros events which do not exceed
//a cutoff threshold.
//this can be used to remove noise when it is not coincident
//(and being potentially masked by) louder events
sndbuf inst => dyna dyn => dac;
"special:dope" => inst.read;
noise n => dyn;
n.gain( 0.005 );
//set the mode and presets
dyn.gate();
//fiddle with the presets
dyn.thresh( 0.01 );
dyn.attackTime( 11::ms );
dyn.releaseTime( 12::ms );
dyn.slope( -100000000 );
dyn.ratio( 0.0000001 );
inst.gain( 0.6 );
for(0=>int i; ;i++) {
//toggle processing every 4 beats
if ( i%4==0 ) {
dyn.op( -dyn.op() );
<<<"op:",dyn.op()>>>;
}
inst.pos( 0 );
1::second => now;
}
-------------- next part --------------
//gkc: demonstation of dyna.compress() mode
//a compressor reduces the gain of events above a
//volume threshold, according to a gain ratio:
//ratio = input:output or input/output
//this tends to make the volume of all the events more uniform
//usually, you apply gain to compensate for the reduction
//to make the overall signal louder
sndbuf inst => dyna dyn => dac;
"special:dope" => inst.read;
//set the mode and presets
dyn.compress();
//fiddle with the presets
dyn.thresh( math.pow(10, -25/20.0) ); //-25 db
dyn.attackTime( 10::ms );
dyn.releaseTime( 100::ms );
//dyn.slope( 0.5 );
dyn.ratio( 3.0 );
//some gain to compensate
//dyn.gain( 2.0 );
inst.gain( 0.6 );
for(0=>int i; ;i++) {
//toggle processing every 4 beats
if ( i%4==0 ) {
dyn.op( -dyn.op() );
<<<"op:",dyn.op()>>>;
}
//events of building volume
inst.gain( 0.1 * math.pow(2,(i%4+1)) );
inst.pos( 0 );
1::second => now;
}
-------------- next part --------------
//gkc: demonstation of dyna.expand() mode
//an expander applies a gain ratio to events
//that fall below a volume threshold
//It makes the quiet stuff quieter!
//In this case, we seem to be stuffing the karp
//down into the lowest bits of the sample.
//You should know what you're doing here (I don't).
//if pop producers are involved in a compression war,
//are lowercase producers involved in an gating/expansion war?
sndbuf inst => dyna dyn => dac;
"special:dope" => inst.read;
StifKarp karp => Echo ec => dyn;
karp.gain( 0.01 );
ec.delay( 250::ms );
ec.mix( 0.5 );
//set the mode and presets
dyn.expand();
//fiddle with the settings
dyn.thresh( 0.01 );
dyn.attackTime( 20::ms );
dyn.releaseTime( 5::ms );
//dyn.slope( -100000000 );
//dyn.ratio( 3.0 );
dyn.slope( -1 );
dyn.gain( 1.0 );
inst.gain( 0.2 );
fun void chimes(int a) {
for (0=>int i; i<4; i++) {
//quick sequence at just fifths
//long sequence at just fourths
karp.freq( 220 * math.pow(4.0/3.0, a) * math.pow(3.0/2.0, i) );
karp.gain( math.pow(2,(a+1))*0.01 );
karp.noteOn( 1.0 );
150::ms => now;
}
}
for(0=>int i; ;i++) {
//toggle processing every 4 beats
if ( i%4==0 ) {
dyn.op( -dyn.op() );
<<<"op:",dyn.op()>>>;
}
//inst.pos( 0 );
spork ~ chimes(i%4);
1::second => now;
}
-------------- next part --------------
//gkc: demonstration of dyna.limit() mode
//a limiter keeps the level from getting above a
//threshold by negatively compensating for any gains
//above that level.
//you can use this to avoid clipping at your dac
//WARNING: LOUD
sndbuf inst => Limiter lim => dac;
"special:dope" => inst.read;
lim.input( inst );
//set mode and presets for limiter
spork ~ lim.limit();
//fiddle with the presets
//dyn.thresh( 0.5 );
//dyn.attackTime( 10::ms );
//dyn.releaseTime( 100::ms );
//dyn.slope( 1.0 );
//dyn.ratio( 1000000000 );
inst.gain( 0.6 );
for(0=>int i; ;i++) {
//toggle processing every 4 beats
if ( i%4==0 ) {
lim.op( -lim.op() );
<<<"op:",lim.op()>>>;
}
//louder and louder "doh!"
inst.gain( 2.0*(i%4+2) );
inst.pos( 0 );
1::second => now;
}
More information about the chuck-dev
mailing list