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}; }