Unified · Split
| @@ -0,0 +1,79 @@ | |||
| 1 | #+TITLE: Env | ||
| 2 | * Overview | ||
| 3 | This document describes =env=, a trigger based envelope | ||
| 4 | generator with adjustable attack, hold, and release times. | ||
| 5 | This envelope is produced by feeding a gate signal into | ||
| 6 | a smoothing filter. The output returns an exponetial | ||
| 7 | envelope with very nice-sounding curves: a convex rise, | ||
| 8 | and a concave fall. | ||
| 9 | |||
| 10 | This sort of envelope is very ideal for percussive sounds | ||
| 11 | and for mimicking analogue synthesizers. The drawbacks | ||
| 12 | mainly have to do with precision. Attack and release | ||
| 13 | times are specified as time constants, which are a little | ||
| 14 | bit less intuitive than using seconds. The signal | ||
| 15 | generated is in range 0 to 1, but it will never reach 1 | ||
| 16 | exactly. | ||
| 17 | * Tangled Files | ||
| 18 | =env.c= and =env.h=, respectively. | ||
| 19 | * Components | ||
| 20 | ** Timer | ||
| 21 | A timer is used to produce a gate signal of a particular | ||
| 22 | duration. This kicks on during the hold portion of | ||
| 23 | the signal. | ||
| 24 | |||
| 25 | This timer is stored in a @!(ref "normalized_range" | ||
| 26 | "normalized range")!@, and is updated by a normalized | ||
| 27 | floating-point incrementor value. | ||
| 28 | ** Filter | ||
| 29 | The filter used is a pretty typical | ||
| 30 | @!(ref "smoother" "one-pole lowpass filter")!@, but with | ||
| 31 | two parameters for the release and attack portions of | ||
| 32 | the envelope. | ||
| 33 | ** State Management | ||
| 34 | The envelope has three main states: attack, hold, and | ||
| 35 | release. There is also a zero state to inidicate that | ||
| 36 | envelope is not playing anything. | ||
| 37 | ** Previous Output | ||
| 38 | The output from the previous sample is stored. | ||
| 39 | * Parameters | ||
| 40 | ** Attack Time (in tau units) | ||
| 41 | ** Release Time (in tau units) | ||
| 42 | ** Hold Time (in seconds) | ||
| 43 | * A Decent Epsilon Value | ||
| 44 | To break out of attack mode, and to have a clean silence | ||
| 45 | after release mode, a very small value known as an =epsilon= | ||
| 46 | will be used. This value should be small enough to be | ||
| 47 | perceptually perfect, and large enough to be numerically | ||
| 48 | stable. | ||
| 49 | |||
| 50 | High-quality tends to be 24-bit, so anything less than | ||
| 51 | =1/2^24= can be ignored, which is roughly =5e-8= (rounded | ||
| 52 | down), which | ||
| 53 | I *think* should be reasonable enough for 32-bit | ||
| 54 | floating-point numbers. | ||
| 55 | * Computation | ||
| 56 | The envelope will wait for a trigger. A trigger at any | ||
| 57 | point will cause a retrigger. Some work will be done to | ||
| 58 | write in behavior that will prevent clicks from happening. | ||
| 59 | |||
| 60 | When the trigger occurs, the state is set to attack, | ||
| 61 | and the gate signal is turned on. The attack time parameters | ||
| 62 | are updated at this point, and the filter is configured | ||
| 63 | to use the attack time parameters. | ||
| 64 | |||
| 65 | In attack mode, the on-gate signal is put through the | ||
| 66 | filter. The output is checked to see if exceeds a threshold | ||
| 67 | reasonably close to 1. If it does, that value is held, | ||
| 68 | and the state is set to be hold mode. | ||
| 69 | |||
| 70 | The timer is stored in a @!(ref "normalized_range" | ||
| 71 | "normalized range")!@, which allows the hold time to be | ||
| 72 | adjustable while it is in hold mode. During the period | ||
| 73 | in hold mode, the output signal will be the last returned | ||
| 74 | value of the attack envelope. | ||
| 75 | |||
| 76 | When timer reaches the end, the envelope goes into release | ||
| 77 | mode. This is very similar to to attack mode, except that | ||
| 78 | it uses release time coefficients and the input | ||
| 79 | signal is a off-gate. | ||