Automation - Quefumas/gensound GitHub Wiki

Automating parameters in Gensound

Sometimes we want the arguments to a Signal or Transform to change overtime. Here are some typical examples:

  • Gradually changing the frequency of an Oscillator
  • Applying gradually changing gain to a track (in a more sophisticated way than a simple fade in/fade out)
  • Moving a signal between L/R channels according to a sine pattern
  • Lowering or raising the cutoff frequency of a filter with time
  • Gradually changing the rate in which a signal is played

Gensound currently offers partial support for automation. In future versions, support and documentation will be expanded, and it will also be easier to ensure that new Transforms support automation. It is likely that much of what is shown below will change eventually.

Below we see how to define automation patterns using subclasses of gensound.curve.Curve, and then we see how to use it with existing signals and transforms, including some examples.

Constant(value, duration)

Use this class to define a parameter that simply stays constant for a certain duration of time. By itself, this doesn't do anything special; but it is often needed when we want a certain parameter to stay constant for a certain time, as a part of a more complex behavior (see examples at the bottom).

from gensound.curve import Constant

c = Constant(5, 5e3)

Line(begin, end, duration)

This defines a parameter that changes linearly from the starting value begin to the finish value end, taking duration time to do so. For example, this can be used to implement a linear fade (when representing amplitudes), or a linear pitch rise (when representing frequency).

from gensound.curve import Line

# this can be used to describe frequency rising from A3 to E4
# in the span of 3 seconds
freq = Line(220, 330, 3e3)

Logistic(begin, end, duration)

This creates a logistic curve (sometimes described as being 'S'-shaped). This curve changes less evenly, and produces a different effect than a Line with the same arguments.

from gensound.curve import Logistic

# same as above, but the frequency will rise in a different rate
freq = Line(220, 330, 3e3)

SineCurve(frequency, depth, baseline, duration)

This describes a sine function with the given frequency, with amplitudes ranging from baseline - depth to baseline + depth. This kind of curve is especially useful to implement AutoSine, moving a certain signal between L/R channels according to a sine pattern.

from gensound.curve import SineCurve

# we can use the curve below to shift a signal between the panning
# of -90 (almost hard L) to 90 (almost hard R),
# completing a cycle 0.8 times a second
pan = SineCurve(0.8, depth=90, baseline=0, duration=10e3)

Log

Not entirely functional yet

Concatenating Curves

To concatenate two or more curves, use the | operator, as with Signals.

For example:

from gensound.curve import Logistic, Line, Constant

# we can use this to later create an oscillator going linearly from A3 to E4,
# pausing, then going back down to A3, but this time with a logistic curve
freq = Line(220, 330, 1e3) | Constant(330, 1e3) | Logistic(330, 220, 1e3)

Using curves

Here is a rough list of the places where we can replace a numeral argument with a Curve (see the examples below as well):

  • The frequency argument of any subclass of Oscillator (that is, Sine, Triangle, etc.)
  • As the main argument for Amplitude and Gain transforms (to change loudness)
  • As the main argument for the Pan transform (to change L/R)
  • As the rate argument of the Stretch transform (this makes the playing rate change with time, affecting pitch as well)
  • As the frequency argument of the Vibrato transform (so the rate of the vibrato can change over time)

Some examples

Play a triangle wave which rises linearly from A3 to E4, pauses for a moment, the descends with a logistic curve:

from gensound.signals import Triangle
from gensound.curve import Logistic, Line, Constant

freq = Line(220, 330, 1e3) | Constant(330, 1e3) | Logistic(330, 220, 1e3)
s = Triangle(freq, 4e3)
s.play()

Change the stretching rate of the audio to cause a pitch shift ranging from a quarter-tone below the original to a quarter-tone above.

from gensound.effects import Stretch
from gensound.curve import SineCurve

w = WAV(test_wav)[10e3:20e3]
rate = SineCurve(frequency=0.1, depth=2**(5/120)-1, baseline=1, duration=20e3)
w *= Stretch(rate=rate)
w.play()

Change the vibrato rate:

from gensound.effects import Vibrato
from gensound.curve import SineCurve

w = WAV(test_wav)[10e3:30e3]

vib_freq = SineCurve(frequency=0.5, depth=4, baseline=5, duration=20e3)
#vib_freq = SineCurve(frequency=0.5, depth=1, baseline=3, duration=20e3) # try this one too

w *= Vibrato(frequency=vib_freq, width=0.1)
w.play()

Autopan both L/R channels, with different settings:

from gensound.transforms import Pan
from gensound.curve import SineCurve

s = WAV(test_wav)[10e3:30e3] # pick 20 seconds of audio

curveL = SineCurve(frequency=0.2, depth=50, baseline=-50, duration=20e3)
# L channel will move in a Sine pattern between -100 (Hard L) and 0 (C)

curveR = SineCurve(frequency=0.12, depth=100, baseline=0, duration=20e3)
# R channel will move in a Sine pattern (different frequency) between -100 and 100
    
t = s[0]*Pan(curveL) + s[1]*Pan(curveR)

Fun with portamento

Here is a longer example, involving three separate oscillators traveling up and down, sometimes forming chords.

from gensound import Triangle, Pan, midC
from gensound.curve import Line, SineCurve, Constant, Logistic

sig = Triangle

# define the pitch curves for 3 voices (midC(x) means the frequency of the pitch x semitones above middle C)
v1 = Constant(midC(9), 2e3) | Line(midC(9), midC(10), 1e3) \
   | Constant(midC(10), 2e3) | Logistic(midC(10), midC(7), 1.5e3) \
   | Logistic(midC(7), midC(6), 2e3) | Constant(midC(6), 2e3)

v2 = Logistic(midC(6), midC(5), 2e3) | Constant(midC(5), 1e3) \
   | Logistic(midC(5), midC(4), 2e3) | Line(midC(4), midC(11), 1.5e3) \
   | Line(midC(11), midC(10), 2e3) | Constant(midC(10), 2e3)

v3 = Logistic(midC(1), midC(2), 2e3) | Constant(midC(2), 1e3) \
   | Line(midC(2), midC(0), 2e3) | Line(midC(0), midC(-3), 1.5e3) \
   | Line(midC(-3), midC(3), 2e3) | Constant(midC(3), 2e3)

# create a 3-voice stereo image
track = sig(v3)*Pan(0) + sig(v1)*Pan(-75) + sig(v2)*Pan(75)

track.play()