Louis Couka

Useful Mapping Curves for Audio Application

Published: 12/04/2017 | Author: Louis Couka

Table Of Contents

Introduction

In this document we present useful curving fonctions that has been intended to be used in audio software.

Parameters

the curve functions are written as:

$$f\left(x,g\left(c\right)\right)$$

with:

  • \(x \in [0, 1]\), the value input
  • \(c \in [-1, 1]\), the curving factor
  • \(g\), a mapping function that fine-tunes the curving factor \(c\) for more natural behavior within its range from -100% to 100%.

In the following we will also use \(u = g(c)\).

Symmetry property

Users may desire symmetry properties along the diagonals, which can be verified using the following equalities:

The curving function is odd-symmetric about the \(y=x\) diagonal iff : $$f\left(x,u\right)=-f\left(1-x,-u\right)$$

The curving function is even-symmetric about the \(y=x\) diagonal iff : $$f\left(x,u\right)=f^{-1}\left(x,-u\right)$$

Exponential Curve

This function allows morphing between linear, exponential, and reverse exponential curves, which is convenient for musical applications, particularly for modeling exponential decays.

$$ f(x, u) = \begin{cases} \frac{1-e^{-xu}}{1-e^{-u}} & \text{if \(u \neq 0\)} \newline x & \text{otherwise} \end{cases} $$

To avoid numerical overflows due to large exponent values, one can use the following equivalent piecewise definition:

$$ \boxed{ f(x, u) = \begin{cases} \frac{1-e^{-xu}}{1-e^{-u}} & \text{if \(u > 0\)} \newline \frac{e^{\left(1-x\right)u}-e^{u}}{1-e^{u}} & \text{if \(u < 0\)} \newline x & \text{otherwise} \end{cases}} $$

The mapping function chosen \(g(c)\) is a simple, efficient way to map the range \([-1, 1]\) to \([-\infty, +\infty]\):

$$\boxed{g(c) = 8\frac{c}{c_{2}-1}}$$

For real-time applications, the curve can be computed recursively to avoid the costly exponentiation operation, optimizing CPU usage.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import numpy as np
import matplotlib.pyplot as plt

def plot_curve(fun, sym=True):
    plt.figure(figsize = [6, 6])
    curveValues = np.linspace(0, 1, 11)[1:]
    x = np.linspace(0, 1, 1000)
    for curveValue in curveValues:
        plt.plot(x, fun(x, curveValue), color = "C0")
        plt.plot(x, 1 - fun(1 - x, curveValue) if sym else fun(x, -curveValue), color = "C0")
    plt.plot(x, x, color = "white")
    plt.axis('equal')
    
def g(c): # map -1/1 to -inf/+inf
    c *= 0.999999
    return 8 * c / (c * c - 1)

def f(x, c):
    u = g(c)
    m = np.exp(u)
    #print(20*np.log10(m)) # see how much dB offset from the real exp curve
    return (np.exp((1 - x) * u) - m) / (1 - m)

plot_curve(f)
plt.title("Exponential Curve")
plt.show()

Python - Console Output

Squircle Curve

The squircle is a generalized shape that transitions between a square and a circle. Its equation is defined as \(x^a + y^a = 1\), where the special case of \(a = 2\) yields a perfect circle.

This shape is notably used by Apple to create smoother corners on their app icons, offering a more organic appearance compared to sharp edges 1.

The squircle curve is symmetric along the \(y = x\) diagonal but not along the \(y = 1 - x\) diagonal. To achieve full symmetry, we can define the curve piecewise, as shown below. Depending on the desired aesthetics, the two cases can be inverted to slightly adjust the curve without significant impact.

$$ \boxed{ f(x, u) = \begin{cases} 1 - \sqrt[u]{1 - x^{u}} & \text{if } u \geq 0 \newline \sqrt[u]{1 - (1 - x)^{u}} & \text{otherwise} \end{cases} } $$

To map the squircle curve to the \([-1, 1]\) range correctly, we apply the following mapping function:

$$ \boxed{g(c) = -\frac{\ln(2)}{\ln\left(\frac{1 + c}{2}\right)}} $$

This function has the property of making the progression along the \(y = 1 - x\) axis linear, resulting in a more natural distribution of the curve.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
def g(c): # map -1/1 to -inf/+inf
    c *= 0.999999
    return - np.log(2) / np.log((1+c)/2) # Same as: 1 / (1 - np.log2(c+1))

def f(x, c):
    u = g(c)
    return (1-(1-x)**u)**(1/u)

plot_curve(f)
plt.title("Squircle Curve")
plt.show()

Python - Console Output

Weighted Bézier Curve

This curve is based on weighted Bézier curves 2, is fully symmetrical, and avoids the use of exponential operations, potentially improving computational speed. The curve can be expressed as a function of \(x\):

$$ \boxed{ f(x, u) = x + 2\left(\sqrt{ux(1 + ux - x)} - ux\right) } $$

The mapping function can be defined as:

$$ \boxed{g(c) = \left(\frac{c}{1 - c}\right)^{2}} $$

Note that the tangent is always straight at the corners.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
def g(c):
    c *= 0.99999
    c = c/(1-c)
    return c*c

def f(x, c):
    u = g(c)
    ux = u*x
    return x+2*(np.sqrt(ux*(1+ux-x))-ux)

plot_curve(f)
plt.title("Weighted Bézier Curve")
plt.show()

Python - Console Output

Sin-like Curve

When designing the parametric LFO for Shade, I needed a continuous function capable of smoothly transitioning between triangle, sine, and square waveforms. This led to the development of an even-symmetric function that exhibits behavior similar to a quarter-period of the sine wave for certain values of \(u\).

More details can be found here.

The function is defined as:

$$ \boxed{f\left(x,u\right) = 1 - (1 - x)^u} $$

And the mapping function is:

$$ \boxed{g(c) = \left(\frac{1 + c}{1 - c}\right)^{1.41}} $$

The value 1.41 was chosen to achieve a natural spread of the curve, with the sinusoidal shape occurring at approximately 20%.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
def g(c):
    c *= 0.999999 # Avoid div by zero
    return ((1 + c) / (1 - c)) ** 1.41

def f(x, c):
    return 1-(1-x)**g(c)

plot_curve(f, False)
x = np.linspace(0, 1, 1000)
y = np.sin(np.pi/2*x)
plt.plot(x, y, "--", c="white", label="\\(sin(x\pi/2)\\)")
plt.legend()
plt.title("Power Curve")
plt.show()

Python - Console Output