396 lines · 7.8 KB · Text
RawDownload
#+TITLE: Env* OverviewThis document describes =env=, a trigger based envelopegenerator with adjustable attack, hold, and release times.This envelope is produced by feeding a gate signal intoa smoothing filter. The output returns an exponetialenvelope with very nice-sounding curves: a convex rise,and a concave fall. This sort of envelope is very ideal for percussive soundsand for mimicking analogue synthesizers. The drawbacksmainly have to do with precision. Attack and releasetimes are specified as time constants, which are a littlebit less intuitive than using seconds. The signalgenerated is in range 0 to 1, but it will never reach 1exactly.* Tangled Files=env.c= and =env.h=, respectively. #+NAME: env.h#+BEGIN_SRC c :tangle env.h#ifndef SK_ENV_H#define SK_ENV_H#ifndef SKFLT#define SKFLT float#endif <<typedefs>><<funcdefs>> #ifdef SK_ENV_PRIV<<structs>>#endif #endif#+END_SRC #+NAME: env.c#+BEGIN_SRC c :tangle env.c#include <math.h>#define SK_ENV_PRIV#include "env.h"<<macros>><<funcs>>#+END_SRC* InitializationInitialize the envelope with =sk_env_init=. #+NAME: funcdefs#+BEGIN_SRC cvoid sk_env_init(sk_env *env, int sr);#+END_SRC #+NAME: funcs#+BEGIN_SRC cvoid sk_env_init(sk_env *env, int sr){    env->sr = sr;    <<init>>}#+END_SRC* Struct Components** Struct#+NAME: typedefs#+BEGIN_SRC ctypedef struct sk_env sk_env;#+END_SRC #+NAME: structs#+BEGIN_SRC cstruct sk_env {    int sr;    <<sk_env>>};#+END_SRC** TimerA timer is used to produce a gate signal of a particularduration. This kicks on during the hold portion ofthe signal. This timer is stored in a @!(ref "normalized_range""normalized range")!@, and is updated by a normalizedfloating-point incrementor value. #+NAME: sk_env#+BEGIN_SRC cfloat timer;float inc;#+END_SRC #+NAME: init#+BEGIN_SRC cenv->timer = 0;env->inc = 0;#+END_SRC** FilterThe filter used is a pretty typical@!(ref "smoother" "one-pole lowpass filter")!@, but withtwo parameters for the release and attack portions ofthe envelope. #+NAME: sk_env#+BEGIN_SRC cSKFLT atk_env;SKFLT rel_env;#+END_SRC #+NAME: init#+BEGIN_SRC cenv->atk_env = 0;env->rel_env = 0;#+END_SRC** State ManagementThe envelope has three main states: attack, hold, andrelease. There is also a zero state to inidicate thatenvelope is not playing anything. #+NAME: sk_env#+BEGIN_SRC cint mode;#+END_SRC #+NAME: macros#+BEGIN_SRC cenum {    MODE_ZERO,    MODE_ATTACK,    MODE_HOLD,    MODE_RELEASE};#+END_SRC #+NAME: init#+BEGIN_SRC cenv->mode = MODE_ZERO;#+END_SRC ** Previous OutputThe output from the previous sample is stored. #+NAME: sk_env#+BEGIN_SRC cSKFLT prev;#+END_SRC #+NAME: init#+BEGIN_SRC cenv->prev = 0;#+END_SRC* ParametersAttack, release, and hold time utilize @!(ref"parameter_caching" "parameter caching")!@ in order toreduce needless computations.** Attack Time (in tau units)Set the attack time with =sk_env_attack=. #+NAME: funcdefs#+BEGIN_SRC cvoid sk_env_attack(sk_env *env, SKFLT atk);#+END_SRC #+NAME: funcs#+BEGIN_SRC cvoid sk_env_attack(sk_env *env, SKFLT atk){    env->atk = atk;}#+END_SRC #+NAME: sk_env#+BEGIN_SRC cSKFLT atk;SKFLT patk;#+END_SRC Set to arbitrary default value. #+NAME: init#+BEGIN_SRC csk_env_attack(env, 0.1);env->patk = -1;#+END_SRC** Release Time (in tau units)Set the release time with =sk_env_release=. #+NAME: funcdefs#+BEGIN_SRC cvoid sk_env_release(sk_env *env, SKFLT rel);#+END_SRC #+NAME: funcs#+BEGIN_SRC cvoid sk_env_release(sk_env *env, SKFLT rel){    env->rel= rel;}#+END_SRC #+NAME: sk_env#+BEGIN_SRC cSKFLT rel;SKFLT prel;#+END_SRC Set to arbitrary default value. #+NAME: init#+BEGIN_SRC csk_env_release(env, 0.1);env->prel= -1;#+END_SRC** Hold Time (in seconds)The hold time is set with =sk_env_hold=. #+NAME: funcdefs#+BEGIN_SRC cvoid sk_env_hold(sk_env *env, SKFLT hold);#+END_SRC #+NAME: funcs#+BEGIN_SRC cvoid sk_env_hold(sk_env *env, SKFLT hold){    env->hold = hold;}#+END_SRC #+NAME: sk_env#+BEGIN_SRC cSKFLT hold;SKFLT phold;#+END_SRC #+NAME: init#+BEGIN_SRC csk_env_hold(env, 0.1);env->phold = -1;#+END_SRC* A Decent Epsilon ValueTo break out of attack mode, and to have a clean silenceafter release mode, a very small value known as an =epsilon=will be used. This value should be small enough to beperceptually perfect, and large enough to be numericallystable. The ideal epsilon value is around=5e-8=. High-quality tends to be 24-bit, so anything lessthan =1/2^24= can be ignored, which is roughly =5e-8=(rounded down), whichI *think* should be reasonable enough for 32-bitfloating-point numbers. #+NAME: macros#+BEGIN_SRC c#define EPS 5e-8#+END_SRC* ComputationComputation is done with =sk_env_tick=. It takes in oneinput value, which expects to be a trigger. #+NAME: funcdefs#+BEGIN_SRC cSKFLT sk_env_tick(sk_env *env, SKFLT trig);#+END_SRC #+NAME: funcs#+BEGIN_SRC cSKFLT sk_env_tick(sk_env *env, SKFLT trig){    SKFLT out;    out = 0;     <<check_for_trigger>>     switch (env->mode) {        <<zero_mode>>        <<attack_mode>>        <<hold_mode>>        <<release_mode>>        default:            break;    }    return out;}#+END_SRC The envelope will wait for a trigger. A trigger at anypoint will cause a retrigger. Some work will be done towrite in behavior that will prevent clicks from happening. When the trigger occurs, the state is set to attack,and the gate signal is turned on. The attack time parametersare updated at this point, and the filter is configuredto use the attack time parameters. #+NAME: check_for_trigger#+BEGIN_SRC cif (trig != 0) {    env->mode = MODE_ATTACK;     if (env->patk != env->atk) {        env->patk = env->atk;        env->atk_env = exp(-1.0 / (env->atk * env->sr));    }}#+END_SRC In attack mode, the on-gate signal is put through thefilter. The difference between the current and previoussamples is measured.If it falls under an epsilon value, the current value isheld and it set to be hold mode. Figuring out when the attack is done is tricky.Using threshold detection has proven to be unreliable withdifferent attack times, so the delta technique used belowwas used instead. #+NAME: attack_mode#+BEGIN_SRC ccase MODE_ATTACK: {    out = env->atk_env*env->prev + (1.0 - env->atk_env);     if ((out - env->prev) <= EPS) {        env->mode = MODE_HOLD;        env->timer = 0;         if (env->phold != env->hold) {            if (env->hold <= 0) {                env->inc = 1.0;            } else {                env->phold = env->hold;                env->inc = 1.0 / (env->hold * env->sr);            }        }    }     env->prev = out;    break;}#+END_SRC The timer is stored in a @!(ref "normalized_range""normalized range")!@, which allows the hold time to beadjustable while it is in hold mode. During the periodin hold mode, the output signal will be the last returnedvalue of the attack envelope. When timer reaches the end, the envelope goes into releasemode. #+NAME: hold_mode#+BEGIN_SRC ccase MODE_HOLD: {    out = env->prev;    env->timer += env->inc;     if (env->timer >= 1.0) {        env->mode = MODE_RELEASE;         if (env->prel != env->rel) {            env->prel = env->rel;            env->rel_env = exp(-1 / (env->rel * env->sr));        }    }    break;}#+END_SRC Release mode is very similar to to attack mode, except thatit uses release time coefficients and the inputsignal is a off-gate. #+NAME: release_mode#+BEGIN_SRC ccase MODE_RELEASE: {    out = env->rel_env*env->prev;    env->prev = out;     if (out <= EPS) {       env->mode = MODE_ZERO;    }    break;}#+END_SRC The envelope will remain in release mode until the outputsignal goes below the epsilon threshold. At this point,the envelope will go into zero mode, where no computationhappens and zero is returned. #+NAME: zero_mode#+BEGIN_SRC ccase MODE_ZERO:    break;#+END_SRC

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t