Skip to content

Modulators

In ixfx, a modulate function is simply one that takes an input number and returns an output. Values are usually on the 0..1 scale, but functions can overshoot these, for example a spring which might bounce past the end.

For example:

// Returns a random number up to the value of v
const fn(v) {
return v * Math.random();
}
// Or expressed more succinctly:
const fn = (v) => v * Math.random();

We often want to modulate with some temporality, and ixfx has two main ways of doing so. One is most obviously by clock time, setting up a modulator to run over a given period of milliseconds, for example. Time is useful because it’s very human-relatable and is independent of how fast other parts of your code are running.

The second technique is ‘ticks’. A tick is essentially a counter that increases by one whenever it is used. This allows you to move something forward exactly when you want.

When work with temporality, the input for the modulate function represents the progress towards completion (either time or total number of ticks) on a scale of 0..1.

const fn(v) {
// v: will be close to 0 when modulator first starts
// values will increase up to 1, representing completion.
}

With those two forms of temporality out of the way, we can look at two ways of using time and ticks with modulation.

Simple

The very simple approach is to use Modulation.time and Modulation.ticks . These both take a modulation function and some duration, returning a function that yields a value.

// Wrap the modulation function `fn` with a duration of 5 seconds
const m = Modulation.time(fn, { secs: 5 });
m(); // Compute value

Here’s a more complete example:

import { Modulation, Flow } from "https://unpkg.com/ixfx/dist/bundle.js";
// Our modulation function
const fn = (t) => {
// As time increases, range of random increases...
return t * Math.random()
}
const m = Modulation.time(fn, { secs: 5 });
Flow.continuously(() => {
const value = m();
// do something with value...
},20).start();

Instead of providing your own modulation function, you might want to use one from ixfx, for example:

import { Modulation } from "https://unpkg.com/ixfx/dist/bundle.js";
// Use the quad-in curve
Modulation.time(Modulation.Easings.Named.quadIn, { secs: 5 });
// Use a spring
Modulation.time(Modulation.springShape(), { secs: 5 });
// Use a triangle-shaped waveform
Modulation.time(Modulation.triangleShape(), { secs: 5 });

Using other functions from ixfx we can do things like compute the numeric value of several modulators at once:

import { Modulation, Data } from "https://unpkg.com/ixfx/dist/bundle.js";
let rawState = {
wave1: Modulation.time(Modulation.triangleShape(), { secs: 5 }),
wave2: Modulation.time(Modulation.sineShape(), { secs: 10 })
}
const values = Data.resolveFields(rawState);
values.wave1; // number
values.wave2; // number

What about envelopes? Envelopes already have an in-built time context, Modulation.time and ticks aren’t very useful. Envelopes.adsr provides the handiness of a simple function that returns a value.

Advanced

More advanced control is also possible. For example if you want to reset a modulator, or check if it has completed.

Then you want to use timeModulator and tickModulator . Like their simpler counterparts, they both take a modulation function and a duration of time or total ticks.

Instead of returning a function, they return a Type ModulatorTimed has this type:

type ModulatorTimed = {
// Returns value at this point
// Usually 0-1, but some functions can overshoot
compute():number
// Reset to start
reset():void
// Returns _true_ if time source is complete
get isDone():boolean
}

With this return object, we can work with the modulator:

import { Modulation, Flow } from "https://unpkg.com/ixfx/dist/bundle.js";
// Our modulation function
const fn = (t) => {
// For an input of 't' (0..1, where 1 means complete)
// return a value
return t * Math.random()
}
const m = Modulation.timeModulator(fn, { secs: 5 });
Flow.continuously(() => {
if (m.isDone) return false; // Exit from loop
if (Math.random() > 0.9) m.reset(); // Occasionally reset
// do something with value...
const value = m.compute();
},20).start();

More practical uses for Type ModulatorTimed is resetting a modulator based on a ‘pointerup’ event, adding a CSS class when a modulator is finished and so on.