[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