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 vconst 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 secondsconst 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 functionconst 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 curveModulation.time(Modulation.Easings.Named.quadIn, { secs: 5 });// Use a springModulation.time(Modulation.springShape(), { secs: 5 });// Use a triangle-shaped waveformModulation.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; // numbervalues.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 functionconst 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.