summaryrefslogtreecommitdiff
path: root/tran/follow.alg
diff options
context:
space:
mode:
Diffstat (limited to 'tran/follow.alg')
-rw-r--r--tran/follow.alg106
1 files changed, 106 insertions, 0 deletions
diff --git a/tran/follow.alg b/tran/follow.alg
new file mode 100644
index 0000000..9765897
--- /dev/null
+++ b/tran/follow.alg
@@ -0,0 +1,106 @@
+(FOLLOW-ALG
+(NAME "follow")
+(SUPPORT-FUNCTIONS
+"/* Description: this is a sophisticated envelope follower.
+ The input is an envelope, e.g. something produced with
+ the AVG function. The purpose of this function is to
+ generate a smooth envelope that is generally not less
+ than the input signal. In other words, we want to \"ride\"
+ the peaks of the signal with a smooth function. The
+ algorithm is as follows: keep a current output value
+ (called the \"value\"). The value is allowed to increase
+ by at most rise_factor and decrease by at most fall_factor.
+ Therefore, the next value should be between
+ value * rise_factor and value * fall_factor. If the input
+ is in this range, then the next value is simply the input.
+ If the input is less than value * fall_factor, then the
+ next value is just value * fall_factor, which will be greater
+ than the input signal. If the input is greater than value *
+ rise_factor, then we compute a rising envelope that meets
+ the input value by working bacwards in time, changing the
+ previous values to input / rise_factor, input / rise_factor^2,
+ input / rise_factor^3, etc. until this new envelope intersects
+ the previously computed values. There is only a limited buffer
+ in which we can work backwards, so if the new envelope does not
+ intersect the old one, then make yet another pass, this time
+ from the oldest buffered value forward, increasing on each
+ sample by rise_factor to produce a maximal envelope. This will
+ still be less than the input.
+
+ The value has a lower limit of floor to make sure value has a
+ reasonable positive value from which to begin an attack.
+
+ Because this algorithm can make 2 passes through the buffer on
+ sharply rising input signals, it is not particularly fast. The
+ assumption is that it operates on fairly short buffers at low
+ sample rates appropriate for gain control, so this should not
+ matter.
+ */
+
+static sample_type *create_buf(double floor, long lookahead)
+{
+ sample_type *buf = (sample_type *) malloc(lookahead * sizeof(sample_type));
+ int i;
+
+ for (i = 0; i < lookahead; i++) buf[i] = (sample_type) floor;
+ return buf;
+}
+")
+(ARGUMENTS ("sound_type" "sndin") ("double" "floor") ("double" "risetime")
+ ("double" "falltime") ("long" "lookahead"))
+(START (MIN sndin))
+(STATE ("long" "lookahead" "lookahead = lookahead + 1")
+ ("sample_type *" "delaybuf" "create_buf(floor, lookahead)")
+ ("sample_type *" "delayptr" "susp->delaybuf")
+ ("sample_type *" "prevptr" "susp->delaybuf + lookahead - 1;
+ *(susp->prevptr) = (sample_type) floor;")
+ ("sample_type *" "endptr" "susp->delaybuf + lookahead")
+ ("double" "floor" "floor; floor = log(floor);")
+ ("double" "rise_factor" "exp(- floor / (sndin->sr * risetime + 0.5))")
+ ("double" "fall_factor" "exp(floor / (sndin->sr * falltime + 0.5))")
+ ("double" "value" "susp->floor"))
+(CONSTANT "feedback" "rise_factor" "fall_factor" "endptr")
+(NOT-REGISTER delaybuf)
+(ALWAYS-SCALE sndin)
+(TERMINATE (MIN sndin))
+(INNER-LOOP " sample_type current = sndin;
+ sample_type high = (sample_type) (*prevptr * rise_factor);
+ sample_type low = (sample_type) (*prevptr * fall_factor);
+ if (low < floor) low = (sample_type) floor;
+ if (current < low) *delayptr = (sample_type) low;
+ else if (current < high) *delayptr = current;
+ else /* current > high */ {
+ /* work back from current */
+ double rise_inverse = 1.0 / rise_factor;
+ double temp = current * rise_inverse;
+ boolean ok = false;
+ sample_type *ptr = prevptr;
+ int i;
+
+ for (i = 0; i < lookahead - 2; i++) {
+ if (*ptr < temp) {
+ *ptr-- = (sample_type) temp;
+ temp *= rise_inverse;
+ if (ptr < susp->delaybuf)
+ ptr = endptr - 1;
+ } else {
+ ok = true;
+ break;
+ }
+ }
+ if (!ok && (*ptr < temp)) {
+ temp = *ptr;
+ for (i = 0; i < lookahead - 1; i++) {
+ ptr++;
+ if (ptr == endptr) ptr = susp->delaybuf;
+ temp *= rise_factor;
+ *ptr = (sample_type) temp;
+ }
+ } else *delayptr = current;
+ }
+ prevptr = delayptr++;
+ if (delayptr == endptr) delayptr = susp->delaybuf;
+ output = *delayptr;")
+(FINALIZATION "free(susp->delaybuf);")
+)
+