UNPKG

ixfx

Version:

A framework for programming interactivity

1,270 lines (1,247 loc) 37.5 kB
import { float, floatSource } from "./chunk-SM5R6ZUU.js"; import { getEdgeX, getEdgeY } from "./chunk-AKHRG6J4.js"; import { divide, easing_exports, gaussian, interpolate2 as interpolate, interpolateAngle, point_exports, polar_exports, quadraticSimple, tickModulator, ticks, time, timeModulator, toPath } from "./chunk-R4JIRHLR.js"; import { repeat, resolveWithFallbackSync } from "./chunk-3PQXJLTZ.js"; import { StateMachineWithEvents, elapsedMillisecondsAbsolute, frequencyTimer } from "./chunk-LHELVIWO.js"; import { SimpleEventEmitter, intervalToMs } from "./chunk-72EKR3DZ.js"; import { scale } from "./chunk-DLFRRV7R.js"; import { clamp } from "./chunk-QAEJS6HO.js"; import { defaultRandom } from "./chunk-5VWJ6TUI.js"; import { throwIntegerTest, throwNumberTest } from "./chunk-UC4AQMTL.js"; import { __export } from "./chunk-L5EJU35C.js"; // src/modulation/index.ts var modulation_exports = {}; __export(modulation_exports, { Easings: () => easing_exports, Envelopes: () => envelope_exports, Forces: () => Forces_exports, Oscillators: () => Oscillator_exports, Sources: () => source_exports, arcShape: () => arcShape, crossfade: () => crossfade, cubicBezierShape: () => cubicBezierShape, drift: () => drift, gaussian: () => gaussian, jitter: () => jitter, jitterAbsolute: () => jitterAbsolute, mix: () => mix, mixModulators: () => mixModulators, noop: () => noop, pingPong: () => pingPong, pingPongPercent: () => pingPongPercent, sineBipolarShape: () => sineBipolarShape, sineShape: () => sineShape, spring: () => spring, springShape: () => springShape, springValue: () => springValue, squareShape: () => squareShape, tickModulator: () => tickModulator, ticks: () => ticks, time: () => time, timeModulator: () => timeModulator, timingSourceFactory: () => timingSourceFactory, triangleShape: () => triangleShape, wave: () => wave, waveFromSource: () => waveFromSource, weightedAverage: () => weightedAverage }); // src/modulation/envelope/index.ts var envelope_exports = {}; __export(envelope_exports, { Adsr: () => Adsr, AdsrBase: () => AdsrBase, AdsrIterator: () => AdsrIterator, adsr: () => adsr, adsrIterable: () => adsrIterable, adsrStateTransitions: () => adsrStateTransitions, defaultAdsrOpts: () => defaultAdsrOpts, defaultAdsrTimingOpts: () => defaultAdsrTimingOpts }); // src/modulation/envelope/Types.ts var adsrStateTransitions = Object.freeze({ attack: [`decay`, `release`], decay: [`sustain`, `release`], sustain: [`release`], release: [`complete`], complete: null }); // src/modulation/envelope/AdsrBase.ts var defaultAdsrTimingOpts = Object.freeze({ attackDuration: 600, decayDuration: 200, releaseDuration: 800, shouldLoop: false }); var AdsrBase = class extends SimpleEventEmitter { #sm; #timeSource; #timer; #holding; #holdingInitial; #disposed = false; #triggered = false; constructor(opts = {}) { super(); this.attackDuration = opts.attackDuration ?? defaultAdsrTimingOpts.attackDuration; this.decayDuration = opts.decayDuration ?? defaultAdsrTimingOpts.decayDuration; this.releaseDuration = opts.releaseDuration ?? defaultAdsrTimingOpts.releaseDuration; this.shouldLoop = opts.shouldLoop ?? defaultAdsrTimingOpts.shouldLoop; this.#sm = new StateMachineWithEvents( adsrStateTransitions, { initial: `attack` } ); this.#sm.addEventListener(`change`, (event) => { if (event.newState === `release` && this.#holdingInitial) { this.#timer?.reset(); } super.fireEvent(`change`, event); }); this.#sm.addEventListener(`stop`, (event) => { super.fireEvent(`complete`, event); }); this.#timeSource = () => elapsedMillisecondsAbsolute(); this.#holding = this.#holdingInitial = false; this.decayDurationTotal = this.attackDuration + this.decayDuration; } dispose() { if (this.#disposed) return; this.#sm.dispose(); } get isDisposed() { return this.#disposed; } /** * Changes state based on timer status * @returns _True_ if state was changed */ switchStateIfNeeded(allowLooping) { if (this.#timer === void 0) return false; let elapsed2 = this.#timer.elapsed; const wasHeld = this.#holdingInitial && !this.#holding; let hasChanged = false; let state = this.#sm.state; do { hasChanged = false; state = this.#sm.state; switch (state) { case `attack`: { if (elapsed2 > this.attackDuration || wasHeld) { this.#sm.next(); hasChanged = true; } break; } case `decay`: { if (elapsed2 > this.decayDurationTotal || wasHeld) { this.#sm.next(); hasChanged = true; } break; } case `sustain`: { if (!this.#holding || wasHeld) { elapsed2 = 0; this.#sm.next(); this.#timer.reset(); hasChanged = true; } break; } case `release`: { if (elapsed2 > this.releaseDuration) { this.#sm.next(); hasChanged = true; } break; } case `complete`: { if (this.shouldLoop && allowLooping) { this.trigger(this.#holdingInitial); } } } } while (hasChanged && state !== `complete`); return hasChanged; } /** * Computes a stage's progress from 0-1 * @param allowStateChange * @returns */ computeRaw(allowStateChange = true, allowLooping = true) { if (this.#timer === void 0) { return [void 0, 0, this.#sm.state]; } if (allowStateChange) this.switchStateIfNeeded(allowLooping); const previousStage = this.#sm.state; const elapsed2 = this.#timer.elapsed; let relative = 0; const state = this.#sm.state; switch (state) { case `attack`: { relative = elapsed2 / this.attackDuration; break; } case `decay`: { relative = (elapsed2 - this.attackDuration) / this.decayDuration; break; } case `sustain`: { relative = 1; break; } case `release`: { relative = Math.min(elapsed2 / this.releaseDuration, 1); break; } case `complete`: { return [`complete`, 1, previousStage]; } default: { throw new Error(`State machine in unknown state: ${state}`); } } return [state, relative, previousStage]; } /** * Returns _true_ if envelope has finished */ get isDone() { return this.#sm.isDone; } onTrigger() { } /** * Triggers envelope, optionally _holding_ it. * * If `hold` is _false_ (default), envelope will run through all stages, * but sustain stage won't have an affect. * * If `hold` is _true_, it will run to, and stay at the sustain stage. * Use {@link release} to later release the envelope. * * If event is already trigged it will be _retriggered_. * Initial value depends on `opts.retrigger` * * _false_ (default): envelope continues at current value. * * _true_: envelope value resets to `opts.initialValue`. * * @param hold If _true_ envelope will hold at sustain stage */ trigger(hold = false) { this.onTrigger(); this.#triggered = true; this.#sm.reset(); this.#timer = this.#timeSource(); this.#holding = hold; this.#holdingInitial = hold; } get hasTriggered() { return this.#triggered; } compute() { } /** * Release if 'trigger(true)' was previouslly called. * Has no effect if not triggered or held. * @returns */ release() { if (this.isDone || !this.#holdingInitial) return; this.#holding = false; this.compute(); } }; // src/modulation/envelope/Adsr.ts var defaultAdsrOpts = Object.freeze({ attackBend: -1, decayBend: -0.3, releaseBend: -0.3, peakLevel: 1, initialLevel: 0, sustainLevel: 0.6, releaseLevel: 0, retrigger: false }); var AdsrIterator = class { constructor(adsr2) { this.adsr = adsr2; } next(...args) { if (!this.adsr.hasTriggered) { this.adsr.trigger(); } const c = this.adsr.compute(); return { value: c[1], done: c[0] === `complete` }; } get [Symbol.toStringTag]() { return `Generator`; } }; var Adsr = class extends AdsrBase { constructor(opts = {}) { super(opts); this.retrigger = opts.retrigger ?? defaultAdsrOpts.retrigger; this.initialLevel = opts.initialLevel ?? defaultAdsrOpts.initialLevel; this.peakLevel = opts.peakLevel ?? defaultAdsrOpts.peakLevel; this.releaseLevel = opts.releaseLevel ?? defaultAdsrOpts.releaseLevel; this.sustainLevel = opts.sustainLevel ?? defaultAdsrOpts.sustainLevel; this.attackBend = opts.attackBend ?? defaultAdsrOpts.attackBend; this.releaseBend = opts.releaseBend ?? defaultAdsrOpts.releaseBend; this.decayBend = opts.decayBend ?? defaultAdsrOpts.decayBend; const max = 1; this.attackPath = toPath( quadraticSimple( { x: 0, y: this.initialLevel }, { x: max, y: this.peakLevel }, -this.attackBend ) ); this.decayPath = toPath( quadraticSimple( { x: 0, y: this.peakLevel }, { x: max, y: this.sustainLevel }, -this.decayBend ) ); this.releasePath = toPath( quadraticSimple( { x: 0, y: this.sustainLevel }, { x: max, y: this.releaseLevel }, -this.releaseBend ) ); } onTrigger() { this.initialLevelOverride = void 0; if (!this.retrigger) { const [_stage, scaled, _raw] = this.compute(true, false); if (!Number.isNaN(scaled) && scaled > 0) { this.initialLevelOverride = scaled; } } } [Symbol.iterator]() { return new AdsrIterator(this); } /** * Returns the scaled value * Same as .compute()[1] */ get value() { return this.compute(true)[1]; } /** * Compute value of envelope at this point in time. * * Returns an array of [stage, scaled, raw]. Most likely you want to use {@link value} to just get the scaled value. * @param allowStateChange If true (default) envelope will be allowed to change state if necessary before returning value */ compute(allowStateChange = true, allowLooping = true) { const [stage, amt] = super.computeRaw(allowStateChange, allowLooping); if (stage === void 0) return [void 0, Number.NaN, Number.NaN]; let v; switch (stage) { case `attack`: { v = this.attackPath.interpolate(amt).y; if (this.initialLevelOverride !== void 0) { v = scale(v, 0, 1, this.initialLevelOverride, 1); } this.releasedAt = v; break; } case `decay`: { v = this.decayPath.interpolate(amt).y; this.releasedAt = v; break; } case `sustain`: { v = this.sustainLevel; this.releasedAt = v; break; } case `release`: { v = this.releasePath.interpolate(amt).y; if (this.releasedAt !== void 0) { v = scale(v, 0, this.sustainLevel, 0, this.releasedAt); } break; } case `complete`: { v = this.releaseLevel; this.releasedAt = void 0; break; } default: { throw new Error(`Unknown state: ${stage}`); } } return [stage, v, amt]; } }; // src/modulation/envelope/index.ts var adsr = (opts = {}) => { const envelope = new Adsr(opts); const finalValue = envelope.releaseLevel; const iterator = envelope[Symbol.iterator](); return () => resolveWithFallbackSync(iterator, { overrideWithLast: true, value: finalValue }); }; async function* adsrIterable(opts) { const envelope = new Adsr(opts.env); const sampleRateMs = opts.sampleRateMs ?? 100; envelope.trigger(); const r = repeat(() => envelope.value, { while: () => !envelope.isDone, delay: sampleRateMs, signal: opts.signal }); for await (const v of r) { yield v; } } // src/modulation/source/index.ts var source_exports = {}; __export(source_exports, { bpm: () => bpm, elapsed: () => elapsed, hertz: () => hertz, perMinute: () => perMinute, perSecond: () => perSecond, ticks: () => ticks2 }); // src/modulation/source/Ticks.ts function ticks2(totalTicks, options = {}) { throwIntegerTest(totalTicks, `aboveZero`, `totalTicks`); const exclusiveStart = options.exclusiveStart ?? false; const exclusiveEnd = options.exclusiveEnd ?? false; const cycleLimit = options.cycleLimit ?? Number.MAX_SAFE_INTEGER; const startPoint = exclusiveStart ? 1 : 0; const endPoint = exclusiveEnd ? totalTicks - 1 : totalTicks; let cycleCount = 0; let v = options.startAt ?? startPoint; if (options.startAtRelative) { let totalTicksForReal = totalTicks; if (exclusiveStart) totalTicksForReal--; if (exclusiveEnd) totalTicksForReal--; v = Math.round(options.startAtRelative * totalTicksForReal); } return (feedback) => { if (feedback) { if (feedback.resetAt !== void 0) { v = feedback.resetAt; } if (feedback.resetAtRelative !== void 0) { v = Math.floor(feedback.resetAtRelative * totalTicks); } } if (cycleCount >= cycleLimit) return 1; let current = v / totalTicks; v++; if (v > endPoint) { cycleCount++; v = startPoint; } return current; }; } // src/modulation/source/Time.ts function elapsed(interval, options = {}) { const cycleLimit = options.cycleLimit ?? Number.MAX_SAFE_INTEGER; const limitValue = 1; let start = options.startAt ?? performance.now(); let cycleCount = 0; const intervalMs = intervalToMs(interval, 1e3); if (options.startAtRelative) { throwNumberTest(options.startAtRelative, `percentage`, `startAtRelative`); start = performance.now() - intervalMs * options.startAtRelative; } return (feedback) => { if (feedback) { if (feedback.resetAt !== void 0) { start = feedback.resetAt; if (start === 0) start = performance.now(); } if (feedback.resetAtRelative !== void 0) { throwNumberTest(feedback.resetAtRelative, `percentage`, `resetAtRelative`); start = performance.now() - intervalMs * feedback.resetAtRelative; } } if (cycleCount >= cycleLimit) return limitValue; const now = performance.now(); const elapsedCycle = now - start; if (elapsedCycle >= intervalMs) { cycleCount += Math.floor(elapsedCycle / intervalMs); start = now; if (cycleCount >= cycleLimit) return limitValue; } return elapsedCycle % intervalMs / intervalMs; }; } function bpm(bpm2, options = {}) { const interval = 60 * 1e3 / bpm2; return elapsed(interval, options); } function hertz(hz, options = {}) { const interval = 1e3 / hz; return elapsed(interval, options); } // src/modulation/source/PerSecond.ts var perSecond = (amount, options = {}) => { const perMilli = amount / 1e3; let min = options.min ?? Number.MIN_SAFE_INTEGER; let max = options.max ?? Number.MAX_SAFE_INTEGER; const clamp2 = options.clamp ?? false; if (clamp2 && options.max) throw new Error(`Use either 'max' or 'clamp', not both.`); if (clamp2) max = amount; let called = performance.now(); return () => { const now = performance.now(); const elapsed2 = now - called; called = now; const x = perMilli * elapsed2; if (x > max) return max; if (x < min) return min; return x; }; }; var perMinute = (amount, options = {}) => { return perSecond(amount / 60, options); }; // src/modulation/CubicBezier.ts var cubicBezierShape = (b, d) => (t) => { const s = 1 - t; const s2 = s * s; const t2 = t * t; const t3 = t2 * t; return 3 * b * s2 * t + 3 * d * s * t2 + t3; }; // src/modulation/Drift.ts var drift = (driftAmtPerMs) => { let lastChange = performance.now(); const update = (v = 1) => { const elapsed2 = performance.now() - lastChange; const amt = driftAmtPerMs * elapsed2 % 1; lastChange = performance.now(); const calc = (v + amt) % 1; return calc; }; const reset = () => { lastChange = performance.now(); }; return { update, reset }; }; // src/modulation/Forces.ts var Forces_exports = {}; __export(Forces_exports, { accelerationForce: () => accelerationForce, angleFromAccelerationForce: () => angleFromAccelerationForce, angleFromVelocityForce: () => angleFromVelocityForce, angularForce: () => angularForce, apply: () => apply, attractionForce: () => attractionForce, computeAccelerationToTarget: () => computeAccelerationToTarget, computeAttractionForce: () => computeAttractionForce, computePositionFromAngle: () => computePositionFromAngle, computePositionFromVelocity: () => computePositionFromVelocity, computeVelocity: () => computeVelocity, constrainBounce: () => constrainBounce, guard: () => guard, magnitudeForce: () => magnitudeForce, nullForce: () => nullForce, orientationForce: () => orientationForce, pendulumForce: () => pendulumForce, springForce: () => springForce, targetForce: () => targetForce, velocityForce: () => velocityForce }); var guard = (t, name = `t`) => { if (t === void 0) { throw new Error(`Parameter ${name} is undefined. Expected ForceAffected`); } if (t === null) { throw new Error(`Parameter ${name} is null. Expected ForceAffected`); } if (typeof t !== `object`) { throw new TypeError( `Parameter ${name} is type ${typeof t}. Expected object of shape ForceAffected` ); } }; var constrainBounce = (bounds, dampen = 1) => { if (!bounds) bounds = { width: 1, height: 1 }; const minX = getEdgeX(bounds, `left`); const maxX = getEdgeX(bounds, `right`); const minY = getEdgeY(bounds, `top`); const maxY = getEdgeY(bounds, `bottom`); return (t) => { const position = computePositionFromVelocity( t.position ?? point_exports.Empty, t.velocity ?? point_exports.Empty ); let velocity = t.velocity ?? point_exports.Empty; let { x, y } = position; if (x > maxX) { x = maxX; velocity = point_exports.invert(point_exports.multiplyScalar(velocity, dampen), `x`); } else if (x < minX) { x = minX; velocity = point_exports.invert(point_exports.multiplyScalar(velocity, dampen), `x`); } if (y > maxY) { y = maxY; velocity = point_exports.multiplyScalar(point_exports.invert(velocity, `y`), dampen); } else if (position.y < minY) { y = minY; velocity = point_exports.invert(point_exports.multiplyScalar(velocity, dampen), `y`); } return Object.freeze({ ...t, position: { x, y }, velocity }); }; }; var attractionForce = (attractors, gravity, distanceRange = {}) => (attractee) => { let accel = attractee.acceleration ?? point_exports.Empty; for (const a of attractors) { if (a === attractee) continue; const f = computeAttractionForce(a, attractee, gravity, distanceRange); accel = point_exports.sum(accel, f); } return { ...attractee, acceleration: accel }; }; var computeAttractionForce = (attractor, attractee, gravity, distanceRange = {}) => { if (attractor.position === void 0) { throw new Error(`attractor.position not set`); } if (attractee.position === void 0) { throw new Error(`attractee.position not set`); } const distributionRangeMin = distanceRange.min ?? 0.01; const distributionRangeMax = distanceRange.max ?? 0.7; const f = point_exports.normalise( point_exports.subtract(attractor.position, attractee.position) ); const d = clamp(point_exports.distance(f), distributionRangeMin, distributionRangeMax); return point_exports.multiplyScalar( f, gravity * (attractor.mass ?? 1) * (attractee.mass ?? 1) / (d * d) ); }; var targetForce = (targetPos, opts = {}) => { const fn = (t) => { const accel = computeAccelerationToTarget( targetPos, t.position ?? { x: 0.5, y: 0.5 }, opts ); return { ...t, acceleration: point_exports.sum(t.acceleration ?? point_exports.Empty, accel) }; }; return fn; }; var apply = (t, ...accelForces) => { if (t === void 0) throw new Error(`t parameter is undefined`); for (const f of accelForces) { if (f === null || f === void 0) continue; t = typeof f === `function` ? f(t) : { ...t, acceleration: point_exports.sum(t.acceleration ?? point_exports.Empty, f) }; } const velo = computeVelocity( t.acceleration ?? point_exports.Empty, t.velocity ?? point_exports.Empty ); const pos = computePositionFromVelocity(t.position ?? point_exports.Empty, velo); const ff = { ...t, position: pos, velocity: velo, // Clear accel, because it has been integrated into velocity acceleration: point_exports.Empty }; return ff; }; var accelerationForce = (vector, mass = `ignored`) => (t) => Object.freeze({ ...t, acceleration: massApplyAccel(vector, t, mass) //Points.sum(t.acceleration ?? Points.Empty, op(t.mass ?? 1)) }); var massApplyAccel = (vector, thing, mass = `ignored`) => { let op; switch (mass) { case `dampen`: { op = (mass2) => divide(vector, mass2, mass2); break; } case `multiply`: { op = (mass2) => point_exports.multiply(vector, mass2, mass2); break; } case `ignored`: { op = (_mass) => vector; break; } default: { throw new Error( // eslint-disable-next-line @typescript-eslint/restrict-template-expressions `Unknown 'mass' parameter '${mass}. Expected 'dampen', 'multiply' or 'ignored'` ); } } return point_exports.sum(thing.acceleration ?? point_exports.Empty, op(thing.mass ?? 1)); }; var magnitudeForce = (force, mass = `ignored`) => (t) => { if (t.velocity === void 0) return t; const mag = point_exports.distance(point_exports.normalise(t.velocity)); const magSq = force * mag * mag; const vv = point_exports.multiplyScalar(point_exports.invert(t.velocity), magSq); return Object.freeze({ ...t, acceleration: massApplyAccel(vv, t, mass) }); }; var nullForce = (t) => t; var velocityForce = (force, mass) => { const pipeline = point_exports.pipeline( // Points.normalise, point_exports.invert, (v) => point_exports.multiplyScalar(v, force) ); return (t) => { if (t.velocity === void 0) return t; const v = pipeline(t.velocity); return Object.freeze({ ...t, acceleration: massApplyAccel(v, t, mass) }); }; }; var angularForce = () => (t) => { const accumulator = t.angularAcceleration ?? 0; const vel = t.angularVelocity ?? 0; const angle = t.angle ?? 0; const v = vel + accumulator; const a = angle + v; return Object.freeze({ ...t, angle: a, angularVelocity: v, angularAcceleration: 0 }); }; var angleFromAccelerationForce = (scaling = 20) => (t) => { const accel = t.acceleration ?? point_exports.Empty; return Object.freeze({ ...t, angularAcceleration: accel.x * scaling }); }; var angleFromVelocityForce = (interpolateAmt = 1) => (t) => { const a = point_exports.angleRadian(t.velocity ?? point_exports.Empty); return Object.freeze({ ...t, angle: interpolateAmt < 1 ? interpolateAngle(interpolateAmt, t.angle ?? 0, a) : a }); }; var springForce = (pinnedAt, restingLength = 0.5, k = 2e-4, damping = 0.999) => (t) => { const direction = point_exports.subtract(t.position ?? point_exports.Empty, pinnedAt); const mag = point_exports.distance(direction); const stretch = Math.abs(restingLength - mag); const f = point_exports.pipelineApply( direction, point_exports.normalise, (p) => point_exports.multiplyScalar(p, -k * stretch) ); const accel = massApplyAccel(f, t, `dampen`); const velo = computeVelocity( accel ?? point_exports.Empty, t.velocity ?? point_exports.Empty ); const veloDamped = point_exports.multiply(velo, damping, damping); return { ...t, velocity: veloDamped, acceleration: point_exports.Empty }; }; var pendulumForce = (pinnedAt, opts = {}) => (t) => { if (!pinnedAt) pinnedAt = { x: 0, y: 0 }; const length = opts.length ?? point_exports.distance(pinnedAt, t.position ?? point_exports.Empty); const speed = opts.speed ?? 1e-3; const damping = opts.damping ?? 0.995; let angle = t.angle; if (angle === void 0) { if (t.position) { angle = point_exports.angleRadian(pinnedAt, t.position) - Math.PI / 2; } else { angle = 0; } } const accel = -1 * speed / length * Math.sin(angle); const v = (t.angularVelocity ?? 0) + accel; angle += v; return Object.freeze({ angularVelocity: v * damping, angle, position: computePositionFromAngle(length, angle + Math.PI / 2, pinnedAt) }); }; var computeVelocity = (acceleration, velocity, velocityMax) => { const p = point_exports.sum(velocity, acceleration); return velocityMax === void 0 ? p : point_exports.clampMagnitude(p, velocityMax); }; var computeAccelerationToTarget = (targetPos, currentPos, opts = {}) => { const diminishBy = opts.diminishBy ?? 1e-3; const direction = point_exports.subtract(targetPos, currentPos); if (opts.range && // If direction is less than range, return { x: 0, y: 0} point_exports.compare(point_exports.abs(direction), opts.range) === -2) { return point_exports.Empty; } return point_exports.multiplyScalar(direction, diminishBy); }; var computePositionFromVelocity = (position, velocity) => point_exports.sum(position, velocity); var computePositionFromAngle = (distance, angleRadians, origin) => polar_exports.toCartesian(distance, angleRadians, origin); var _angularForce = angularForce(); var _angleFromAccelerationForce = angleFromAccelerationForce(); var orientationForce = (interpolationAmt = 0.5) => { const angleFromVel = angleFromVelocityForce(interpolationAmt); return (t) => { t = _angularForce(t); t = _angleFromAccelerationForce(t); t = angleFromVel(t); return t; }; }; // src/modulation/Jitter.ts var jitterAbsolute = (options) => { const { relative, absolute } = options; const clamped = options.clamped ?? false; const source = options.source ?? defaultRandom; if (absolute !== void 0) { return (value) => { const abs = source() * absolute * 2 - absolute; const valueNew = value + abs; if (clamped) return clamp(valueNew, 0, value); return valueNew; }; } if (relative !== void 0) { return (value) => { const rel = value * relative; const abs = source() * rel * 2 - rel; const valueNew = value + abs; if (clamped) return clamp(valueNew, 0, value); return valueNew; }; } throw new Error(`Either absolute or relative fields expected`); }; var jitter = (options = {}) => { const clamped = options.clamped ?? true; let r = (_) => 0; if (options.absolute !== void 0) { throwNumberTest( options.absolute, clamped ? `percentage` : `bipolar`, `opts.absolute` ); const absRand = floatSource({ min: -options.absolute, max: options.absolute, source: options.source }); r = (v) => v + absRand(); } else if (options.relative === void 0) { throw new TypeError(`Either absolute or relative jitter amount is required.`); } else { const rel = options.relative ?? 0.1; throwNumberTest( rel, clamped ? `percentage` : `bipolar`, `opts.relative` ); r = (v) => v + float({ min: -Math.abs(rel * v), max: Math.abs(rel * v), source: options.source }); } const compute = (value) => { throwNumberTest(value, clamped ? `percentage` : `bipolar`, `value`); let v = r(value); if (clamped) v = clamp(v); return v; }; return compute; }; // src/modulation/Mix.ts var mix = (amount, original, modulation) => { const m = modulation * amount; const base = (1 - amount) * original; return base + original * m; }; var mixModulators = (balance, a, b) => (amt) => interpolate(balance, a(amt), b(amt)); var crossfade = (a, b) => { return (amt) => { const mixer = mixModulators(amt, a, b); return mixer(amt); }; }; // src/modulation/Noop.ts var noop = (v) => v; // src/modulation/Oscillator.ts var Oscillator_exports = {}; __export(Oscillator_exports, { saw: () => saw, sine: () => sine, sineBipolar: () => sineBipolar, square: () => square, triangle: () => triangle }); var piPi = Math.PI * 2; function* sine(timerOrFreq) { if (timerOrFreq === void 0) throw new TypeError(`Parameter 'timerOrFreq' is undefined`); if (typeof timerOrFreq === `number`) { timerOrFreq = frequencyTimer(timerOrFreq); } while (true) { yield (Math.sin(timerOrFreq.elapsed * piPi) + 1) / 2; } } function* sineBipolar(timerOrFreq) { if (timerOrFreq === void 0) throw new TypeError(`Parameter 'timerOrFreq' is undefined`); if (typeof timerOrFreq === `number`) { timerOrFreq = frequencyTimer(timerOrFreq); } while (true) { yield Math.sin(timerOrFreq.elapsed * piPi); } } function* triangle(timerOrFreq) { if (typeof timerOrFreq === `number`) { timerOrFreq = frequencyTimer(timerOrFreq); } while (true) { let v = timerOrFreq.elapsed; if (v < 0.5) { v *= 2; } else { v = 2 - v * 2; } yield v; } } function* saw(timerOrFreq) { if (timerOrFreq === void 0) throw new TypeError(`Parameter 'timerOrFreq' is undefined`); if (typeof timerOrFreq === `number`) { timerOrFreq = frequencyTimer(timerOrFreq); } while (true) { yield timerOrFreq.elapsed; } } function* square(timerOrFreq) { if (typeof timerOrFreq === `number`) { timerOrFreq = frequencyTimer(timerOrFreq); } while (true) { yield timerOrFreq.elapsed < 0.5 ? 0 : 1; } } // src/modulation/PingPong.ts var pingPongPercent = function(interval = 0.1, lower, upper, start, rounding) { if (lower === void 0) lower = 0; if (upper === void 0) upper = 1; if (start === void 0) start = lower; throwNumberTest(interval, `bipolar`, `interval`); throwNumberTest(upper, `bipolar`, `end`); throwNumberTest(start, `bipolar`, `offset`); throwNumberTest(lower, `bipolar`, `start`); return pingPong(interval, lower, upper, start, rounding); }; var pingPong = function* (interval, lower, upper, start, rounding) { if (lower === void 0) throw new Error(`Parameter 'lower' is undefined`); if (interval === void 0) { throw new Error(`Parameter 'interval' is undefined`); } if (upper === void 0) throw new Error(`Parameter 'upper' is undefined`); if (rounding === void 0 && interval <= 1 && interval >= 0) { rounding = 10 / interval; } else if (rounding === void 0) rounding = 1234; if (Number.isNaN(interval)) throw new Error(`interval parameter is NaN`); if (Number.isNaN(lower)) throw new Error(`lower parameter is NaN`); if (Number.isNaN(upper)) throw new Error(`upper parameter is NaN`); if (Number.isNaN(start)) throw new Error(`upper parameter is NaN`); if (lower >= upper) throw new Error(`lower must be less than upper`); if (interval === 0) throw new Error(`Interval cannot be zero`); const distance = upper - lower; if (Math.abs(interval) >= distance) { throw new Error(`Interval should be between -${distance} and ${distance}`); } let incrementing = interval > 0; upper = Math.floor(upper * rounding); lower = Math.floor(lower * rounding); interval = Math.floor(Math.abs(interval * rounding)); if (interval === 0) { throw new Error(`Interval is zero (rounding: ${rounding})`); } start = start === void 0 ? lower : Math.floor(start * rounding); if (start > upper || start < lower) { throw new Error( `Start (${start / rounding}) must be within lower (${lower / rounding}) and upper (${upper / rounding})` ); } let v = start; yield v / rounding; let firstLoop = true; while (true) { v = v + (incrementing ? interval : -interval); if (incrementing && v >= upper) { incrementing = false; v = upper; if (v === upper && firstLoop) { v = lower; incrementing = true; } } else if (!incrementing && v <= lower) { incrementing = true; v = lower; if (v === lower && firstLoop) { v = upper; incrementing = false; } } yield v / rounding; firstLoop = false; } }; // src/modulation/Spring.ts function* spring(opts = {}, timerOrFreq) { if (timerOrFreq === void 0) timerOrFreq = elapsedMillisecondsAbsolute(); else if (typeof timerOrFreq === `number`) { timerOrFreq = frequencyTimer(timerOrFreq); } const fn = springShape(opts); let doneCountdown = opts.countdown ?? 10; while (doneCountdown > 0) { const s = fn(timerOrFreq.elapsed / 1e3); yield s; if (s === 1) { doneCountdown--; } else { doneCountdown = 100; } } } function springValue(opts = {}, timerOrFreq) { const s = spring(opts, timerOrFreq); return () => { const v = s.next(); if (v.done) return 1; return v.value; }; } var springShape = (opts = {}) => { const from = 0; const to = 1; const mass = opts.mass ?? 1; const stiffness = opts.stiffness ?? 100; const soft = opts.soft ?? false; const damping = opts.damping ?? 10; const velocity = opts.velocity ?? 0.1; const delta = to - from; if (soft || 1 <= damping / (2 * Math.sqrt(stiffness * mass))) { const angularFrequency = -Math.sqrt(stiffness / mass); const leftover = -angularFrequency * delta - velocity; return (t) => to - (delta + t * leftover) * Math.E ** (t * angularFrequency); } else { const dampingFrequency = Math.sqrt(4 * mass * stiffness - damping ** 2); const leftover = (damping * delta - 2 * mass * velocity) / dampingFrequency; const dfm = 0.5 * dampingFrequency / mass; const dm = -(0.5 * damping) / mass; return (t) => to - (Math.cos(t * dfm) * delta + Math.sin(t * dfm) * leftover) * Math.E ** (t * dm); } }; // src/modulation/TimingSourceFactory.ts var timingSourceFactory = (source, duration, options = {}) => { switch (source) { case `elapsed`: return () => elapsed(duration, options); case `bpm`: return () => bpm(duration, options); case `hertz`: return () => hertz(duration, options); default: throw new Error(`Unknown source '${source}'. Expected: 'elapsed', 'hertz' or 'bpm'`); } }; // src/modulation/Waveforms.ts function triangleShape(period = 1) { period = 1 / period; const halfPeriod = period / 2; return (t) => { const v = Math.abs(t % period - halfPeriod); return v; }; } function squareShape(period = 1) { period = 1 / period; const halfPeriod = period / 2; return (t) => { return t % period < halfPeriod ? 1 : 0; }; } function sineShape(period = 1) { period = period * (Math.PI * 2); return (t) => { const v = (Math.sin(t * period) + 1) / 2; return v; }; } function arcShape(period = 1) { period = period * (Math.PI * 2); return (t) => Math.abs(Math.sin(t * period)); } function sineBipolarShape(period = 1) { period = period * (Math.PI * 2); return (t) => Math.sin(t * period); } function wave(options) { const shape = options.shape ?? `sine`; const invert = options.invert ?? false; const period = options.period ?? 1; let sourceFn; throwIntegerTest(period, `aboveZero`, `period`); const sourceOptions = { ...options }; if (options.ticks) { sourceFn = ticks2(options.ticks, sourceOptions); } else if (options.hertz) { sourceFn = hertz(options.hertz, sourceOptions); } else if (options.millis) { sourceFn = elapsed(options.millis, sourceOptions); } else if (options.source) { sourceFn = options.source; } else { const secs = options.secs ?? 5; sourceFn = elapsed(secs * 1e3, sourceOptions); } let shaperFn; switch (shape) { case `saw`: shaperFn = (v) => v; break; case `sine`: shaperFn = sineShape(period); break; case `sine-bipolar`: shaperFn = sineBipolarShape(period); break; case `square`: shaperFn = squareShape(period); break; case `triangle`: shaperFn = triangleShape(period); break; case `arc`: shaperFn = arcShape(period); break; default: throw new Error(`Unknown wave shape '${shape}'. Expected: sine, sine-bipolar, saw, triangle, arc or square`); } return waveFromSource(sourceFn, shaperFn, invert); } function waveFromSource(sourceFn, shaperFn, invert = false) { return (feedback) => { let v = sourceFn(feedback?.clock); if (feedback?.override) v = feedback.override; v = shaperFn(v); if (invert) v = 1 - v; return v; }; } // src/modulation/WeightedAverage.ts var weightedAverage = (currentValue, targetValue, slowDownFactor) => { return (currentValue * (slowDownFactor - 1) + targetValue) / slowDownFactor; }; export { envelope_exports, source_exports, cubicBezierShape, drift, Forces_exports, jitterAbsolute, jitter, mix, mixModulators, crossfade, noop, Oscillator_exports, pingPongPercent, pingPong, spring, springValue, springShape, timingSourceFactory, triangleShape, squareShape, sineShape, arcShape, sineBipolarShape, wave, waveFromSource, weightedAverage, modulation_exports }; //# sourceMappingURL=chunk-RREUF3ZT.js.map