expmap is an exponential signal mapper with variable slope. It takes in a normalized linear value, and applies an exponential curve to it.

Two version of expmap are provided: a naive implementation that is easy to read and adapt, and a more optimized version that uses parameter caching to reduce division operations and calls to exp.


This is all there is to it:

{1 - e^{xs} \over 1 - e^{s}}

Where x is the normalized linear input signal, and s is the slope.

As gets more negative, the exponential curve will be more convex, with more values staying closer to 1. As gets more positive, the exponential curve will get more concave, with more values staying closer to 0.

Tangled Files

expmap.c and expmap.h are the tangled files. If SK_EXPMAP_PRIV is defined, it exposes the structs.

#include <math.h>
#include "expmap.h"

#ifndef SK_EXPMAP_H
#define SK_EXPMAP_H

#ifndef SKFLT
#define SKFLT float




Naive Function (stateless).

The function sk_expmap_stateless will compute this exponential function without any internal state. The code is quite clear and simple, but requires two calls exp, which is an expensive operation.

SKFLT sk_expmap_stateless(SKFLT in, SKFLT slope);

SKFLT sk_expmap_stateless(SKFLT in, SKFLT slope)
    return (1 - exp(in*slope)) / (1 - exp(slope));


Called sk_expmap.

typedef struct sk_expmap sk_expmap;

struct sk_expmap {

Input/Ouput Caching

To avoid computation, some caching of the input/output variables are used.

pin is the cached input value, will be used to avoid computation. At init, it is set to be an (invalid) negative value.

SKFLT pin;

em->pin = -1;

pout is the cached computed output value. It is used if no computation is needed. By default, it is set to be -1. It is assumed that this value shouldn't actually ever be returned.

SKFLT pout;

em->pout = -1;

Scaling Constant

scale is the constant 1/(1 - exp(slope). A scaler used to shave off an expensive exp operation and div operation.

SKFLT scale;

The scaling value can be computed with the static function compute_scale, given an input slope.

static SKFLT compute_scale(SKFLT slope);

static SKFLT compute_scale(SKFLT slope)
    return 1 / (1 - exp(slope));

After the slope is initialized, the scale is initialized to be the slope.

em->scale = compute_scale(em->slope);


Initialization is done with sk_expmap_init.

void sk_expmap_init(sk_expmap *em);

void sk_expmap_init(sk_expmap *em)

Changing Slope

The slope of expmap can be changed with sk_expmap_slope.

void sk_expmap_slope(sk_expmap *em, SKFLT slope);

void sk_expmap_slope(sk_expmap *em, SKFLT slope)
    em->slope = slope;

The slope parameter uses caching to avoid re-computation.

SKFLT slope;
SKFLT pslope;

The slope is set to be 1 to begin.

sk_expmap_slope(em, 1);
em->pslope = 1;


A single sample of audio is computed with sk_expmap_tick.

SKFLT sk_expmap_tick(sk_expmap *em, SKFLT in);

SKFLT sk_expmap_tick(sk_expmap *em, SKFLT in)
    SKFLT out = 0;
    return out;

Before a sample is computed, the slope parameter is checked for updates. If it is updated, the scale needs to be re-computed.

if (em->slope != em->pslope) {
    em->pslope = em->slope;
    em->scale = compute_scale(em->slope);

Computation is done only if the input value in is different from the cached input value pin. If it is, it updates the cached output value pout.

the output variable out is set to be the cached value pout.

if (in != em->pin) {
    em->pin = in;
    em->pout = (1 - exp(in * em->slope)) * em->scale;
out = em->pout;