silphius/app/util/lfo.js
2024-07-22 01:31:52 -05:00

73 lines
2.1 KiB
JavaScript

import {Ticker, withResolvers} from '@/util/promise.js';
const Modulators = {
flat: () => 0.5,
random: () => Math.random(),
sawtooth: (unit) => unit,
sine: (unit) => 0.5 * (1 + Math.sin(unit * Math.PI * 2)),
square: (unit) => (unit < 0.5 ? 0 : 1),
triangle: (unit) => 2 * Math.abs(((unit + 0.75) % 1) - 0.5),
};
export default function lfo(object, properties) {
const oscillators = {};
const promises = [];
for (const key in properties) {
const property = properties[key];
const oscillator = {
count: Infinity,
elapsed: 0,
offset: 0,
...property,
};
if (!('median' in oscillator)) {
oscillator.median = oscillator.magnitude / 2;
}
if (!oscillator.modulators) {
oscillator.modulators = [Modulators.triangle];
}
else {
const {modulators} = oscillator;
for (const i in modulators) {
if ('string' === typeof modulators[i]) {
modulators[i] = Modulators[modulators[i]];
}
}
}
oscillator.low = oscillator.median - oscillator.magnitude / 2;
({promise: oscillator.promise, resolve: oscillator.stop} = withResolvers());
oscillator.promise.then(() => {
delete oscillators[key];
});
promises.push(oscillator.promise);
oscillators[key] = oscillator;
}
let stop;
const promise = new Ticker(
(resolve) => {
stop = resolve;
Promise.all(promises).then(resolve);
},
(elapsed) => {
for (const key in oscillators) {
const oscillator = oscillators[key];
oscillator.elapsed += elapsed;
if (oscillator.elapsed >= oscillator.frequency) {
if (0 === --oscillator.count) {
oscillator.stop();
return;
}
oscillator.elapsed = oscillator.elapsed % oscillator.frequency;
}
const x = (oscillator.offset + (oscillator.elapsed / oscillator.frequency)) % 1;
let y = 0;
for (const modulator of oscillator.modulators) {
y += modulator(x);
}
object[key] = oscillator.low + oscillator.magnitude * (y / oscillator.modulators.length);
}
},
);
return {stop, oscillators, promise};
}