Oscillators

Oscillators are generators that produce value according to a wave shape. Common shapes include sine, sawtooth (or ramp), square and triangle.

In the below example, each oscillator runs for one cycle over two seconds. That means the frequency is 0.5 - half a cycle every second.

Oscillators are normalised to generate values in the range of 0 to 1. Inverse waves can be calculated by taking the value from one.

Sampling

How often to sample the oscillator depends on how you're using the value, and the oscillator frequency. If the sample rate is proportionally much slower than the oscillator, you'll miss the shape of the wave - sort of like seeing someone dance under a strobe light.

Usage

Initialisation

import { frequencyTimerSource } from 'https://unpkg.com/ixfx/dist/flow.js';
import { Oscillators } from 'https://unpkg.com/ixfx/dist/modulation.js';

// Create a timer for 10Hz (10 cycles per second)
const freq = frequencyTimerSource(10);

// Create a sine oscillator using `freq` time source
const osc = Oscillators.sine(freq);

// Instead of .sine one could choose:
//  sineBipolar, saw, triangle, square

Oscillators are number generators, which means we have to 'pull' values out of it. To sample the current value of the oscillator:

const v = osc.next().value;

To get the inverse, eg converting the upwards ramp of the saw to a downwards ramp:

const v = 1 - osc.next().value;

Typically you want to sample the oscillator's value over time. This might be every time your sketch updates its state, in a drawing loop, or based on a timer.

Below, we update state.oscValue with the oscillator value every 500 milliseconds:

let state = {
  oscValue: 0
}
setInterval(() => {
  state = {
    ...state,
    oscValue: osc.next().value;
  }
}, 500);

// Elsewhere, use state.oscValue ...

See a similar snippet in the plotter

Another pattern is to use ixfx's interval function to pull values from the oscillator at a certain rate. In the example below, reading from an oscillator can be enabled or disabled with buttons.

// If true, we're reading values
let running = false;
// Rate to pull values from the oscillator
const updateRateMs = 2; 

document.getElementById(`btnStart`)?.addEventListener(`click`, async evt => {
  // Oscillator to read
  const osc = Oscillators.sine(0.1);
  running = true;

  for await (const v of interval(osc, updateRateMs)) {
    // Do something with value from oscillator...
    console.log(v);
    if (!running) break; // Stop button pressed, exit for loop
  }
});

document.getElementById(`btnStop`)?.addEventListener(`click`, evt => {
  running = false;
});

Amplitude modulation

Amplitude modulation modulates the output of one oscillator by some other modulator. As before, a sine wave runs, while you can control the shape and frequency of the modulator.

The given expression is how the modulator effects the signal.

Example expressions:

  • source * mod: Dampens output. ie. if modulator is at 50%, signal is reduced by 50%. Try a triangle wave with frequency of 1.
  • source * Math.sqrt(mod): Reduces the influence of modulator by squaring its value first. If you set a triangle wave and frequency around 41, note how modulation bites in more when sine is at its higher values
  • source * Math.pow(mod, 2): Increases influence of modulator by raising to the second power. As a result, it seems the sine is the modulator, not the modulator.

See the modulation demos for an example of how to do frequency modulation.

Starter

Below is a skeleton for a sketch that defines settings, state and an update/apply loop. The oscillator is sampled on every loop.

import { frequencyTimer } from 'https://unpkg.com/ixfx/dist/flow.js';
import { Oscillators } from 'https://unpkg.com/ixfx/dist/modulation.js';

// Define settings
const settings = {
  osc: Oscillators.sine(frequencyTimer(0.01))
}

// Initialise state
let state = {
  oscValue: 0
}

// Update state
const updateState = () => {
  const {osc} = settings;
  state = {
    ...state,                  // Copy any other values in state
    oscValue: osc.next().value // Sample oscillator
  }
}

// Apply state
const applyState = () => {
  const { oscValue } = state;
  
  // Use oscValue somehow...
  document.getElementById(`oscValue`).innerText = oscValue.toString();
}

const loop = () => {
  updateState();
  applyState();
  window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop);

Starter skeleton on Glitch, Github