UNPKG

ixfx

Version:

Bundle of ixfx libraries

1,480 lines (1,479 loc) 77.2 kB
import { i as Interval } from "./types-1oz6G7XR.js"; import { a as HasCompletion } from "./continuously-DBwR-cNf.js"; import { n as SimpleEventEmitter } from "./index-DzASKzet.js"; import { L as AngleConvertible, R as AngleDirection, bn as Point, fn as Rect, k as Path, z as AngleRad } from "./index-CWMm4bvc.js"; import { z as RandomSource } from "./index-BtKPbZx1.js"; import { Q as interpolate, Z as BasicInterpolateOptions } from "./index-Cw7xYDWk.js"; import { d as Timer } from "./index-DHYwK41w.js"; //#region ../packages/modulation/src/types.d.ts type ModSettableOptions = { /** * Starting absolute value of source. */ startAt: number; /** * Starting relative value of source (eg 0.5 for 50%) */ startAtRelative: number; /** * If set, determines how many cycles. By default unlimited. * Use 1 for example for a one-shot wave. */ cycleLimit: number; /** * Function that returns current time in milliseconds. * Defaults to `performance.now`. Useful for testing. */ timeSource: () => number; }; type ModSettableFeedback = { /** * If set, resets absolute position of clock */ resetAt: number; /** * If set, resets relative position of clock */ resetAtRelative: number; }; type ModSettable = (feedback?: Partial<ModSettableFeedback>) => number; /** * A mod source returns numbers on a 0..1 scale. * Usually invoked just a function, some sources also support * 'feedback' allowing source to be adjusted dynamically. * * See Modulation.Sources for more. */ type ModSource = (feedback?: any) => number; /** * A function that modulates `v`. * * Example modulators: * {@link wave}: Generate different wave shapes * Raw access to waves: {@link arcShape}, {@link sineShape},{@link sineBipolarShape}, {@link triangleShape}, {@link squareShape} * {@link Easings}: Easing functions * {@link springShape}: Spring */ type ModFunction = (v: number) => number; type ModulatorTimed = HasCompletion & { /** * Computes the current value of the easing * * @returns {number} */ compute: () => number; /** * Reset the easing */ reset: () => void; /** * Returns true if the easing is complete * * @returns {boolean} */ get isDone(): boolean; }; type SpringOptions = Partial<{ /** * How much 'weight' the spring has. * Favour adjusting 'damping' or 'stiffness' before changing mass. * Default: 1 */ readonly mass: number; /** * Absorbs the energy, acting as a kind of friction. Helps * to avoid oscillations where the spring doesn't 'end' * Default: 10 */ readonly damping: number; /** * How bouncy the spring is * Default: 100 */ readonly stiffness: number; /** * Default: false */ readonly soft: boolean; /** * Default: 0.1 */ readonly velocity: number; /** * How many iterations to wait for spring settling. Longer values may be * needed if it seems the spring gets prematurely cut off. * Default: 10 */ readonly countdown: number; }>; //#endregion //#region ../packages/modulation/src/cubic-bezier.d.ts /** * Creates an easing function using a simple cubic bezier defined by two points. * * Eg: https://cubic-bezier.com/#0,1.33,1,-1.25 * a:0, b: 1.33, c: 1, d: -1.25 * * ```js * import { Easings } from "@ixfx/modulation.js"; * // Time-based easing using bezier * const e = Easings.time(fromCubicBezier(1.33, -1.25), 1000); * e.compute(); * ``` * @param b * @param d * @returns Value */ declare const cubicBezierShape: (b: number, d: number) => ModFunction; //#endregion //#region ../packages/modulation/src/drift.d.ts type Drifter = { update(v: number): number; reset(): void; }; /** * WIP * Returns a {@link Drifter} that moves a value over time. * * It keeps track of how much time has elapsed, accumulating `driftAmtPerMs`. * The accumulated drift is wrapped on a 0..1 scale. * ```js * // Set up the drifer * const d = drif(0.001); * * d.update(1.0); * // Returns 1.0 + accumulated drift * ``` * @param driftAmtPerMs * @returns */ declare const drift: (driftAmtPerMs: number) => Drifter; declare namespace easings_named_d_exports { export { arch, backIn, backInOut, backOut, bell, bounceIn, bounceInOut, bounceOut, circIn, circInOut, circOut, cubicIn, cubicOut, elasticIn, elasticInOut, elasticOut, expoIn, expoInOut, expoOut, quadIn, quadInOut, quadOut, quartIn, quartOut, quintIn, quintInOut, quintOut, sineIn, sineInOut, sineOut, smootherstep, smoothstep }; } declare const bounceOut: (x: number) => number; declare const quintIn: (x: number) => number; declare const quintOut: (x: number) => number; declare const arch: (x: number) => number; declare const smoothstep: (x: number) => number; declare const smootherstep: (x: number) => number; declare const sineIn: (x: number) => number; declare const sineOut: (x: number) => number; declare const quadIn: (x: number) => number; declare const quadOut: (x: number) => number; declare const sineInOut: (x: number) => number; declare const quadInOut: (x: number) => number; declare const cubicIn: (x: number) => number; declare const cubicOut: (x: number) => number; declare const quartIn: (x: number) => number; declare const quartOut: (x: number) => number; declare const expoIn: (x: number) => number; declare const expoOut: (x: number) => number; declare const quintInOut: (x: number) => number; declare const expoInOut: (x: number) => number; declare const circIn: (x: number) => number; declare const circOut: (x: number) => number; declare const backIn: (x: number) => number; declare const backOut: (x: number) => number; declare const circInOut: (x: number) => number; declare const backInOut: (x: number) => number; declare const elasticIn: (x: number) => number; declare const elasticOut: (x: number) => number; declare const bounceIn: (x: number) => number; declare const bell: (t: number) => number; declare const elasticInOut: (x: number) => number; declare const bounceInOut: (x: number) => number; //#endregion //#region ../packages/modulation/src/easing/line.d.ts /** * Interpolates points along a line. * By default it's a straight line, so use `bend` to make a non-linear curve. * @param bend -1...1. -1 will pull line up, 1 will push it down. * @returns */ declare const line: (bend?: number, warp?: number) => (value: number) => Point; //#endregion //#region ../packages/modulation/src/easing/types.d.ts /** * Easing name */ type EasingName = keyof typeof easings_named_d_exports; type EasingOptions = (EasingTickOptions | EasingTimeOptions) & { name?: EasingName; fn?: ModFunction; }; type EasingTimeOptions = { duration: Interval; }; type EasingTickOptions = { ticks: number; }; declare namespace easing_d_exports { export { EasingName, EasingOptions, EasingTickOptions, EasingTimeOptions, easings_named_d_exports as Named, create, get, getEasingNames, line, tickEasing, ticks$2 as ticks, time$1 as time, timeEasing }; } /** * Creates an easing function * ```js * const e = Easings.create({ duration: 1000, name: `quadIn` }); * const e = Easings.create({ ticks: 100, name: `sineOut` }); * const e = Easings.create({ * duration: 1000, * fn: (v) => { * // v will be 0..1 based on time * return Math.random() * v * } * }); * ``` * @param options * @returns */ declare const create: (options: EasingOptions) => () => number; /** * Creates an easing based on clock time. Time * starts being counted when easing function is created. * * `timeEasing` allows you to reset and check for completion. * Alternatively, use {@link time} which is a simple function that just returns a value. * * * @example Time based easing * ``` * const t = Easings.timeEasing(`quintIn`, 5*1000); // Will take 5 seconds to complete * ... * t.compute(); // Get current value of easing * t.reset(); // Reset to 0 * t.isDone; // _True_ if finished * ``` * * Thisi function is just a wrapper around Modulator.timedModulator. * @param nameOrFunction Name of easing, or an easing function * @param duration Duration * @returns Easing */ declare const timeEasing: (nameOrFunction: EasingName | ((v: number) => number), duration: Interval) => ModulatorTimed; /** * Produce easing values over time. When the easing is complete, the final * value continues to return. Timer starts when return function is first invoked. * * If you need to check if an easing is done or reset it, consider {@link timeEasing}. * * ```js * // Quad-in easing over one second * const e = Easings.time(`quadIn`, 1000); * * // Keep calling e() to get the current value * e(); * ``` * * This function is just a wrapper around Modulate.time * @param nameOrFunction Easing name or a function that produces 0..1 scale * @param duration Duration * @returns */ declare const time$1: (nameOrFunction: EasingName | ((v: number) => number), duration: Interval) => () => number; /** * Produce easing values with each invocation. When the easing is complete, the final * value continues to return. Timer starts when return function is first invoked. * * If you need to check if an easing is done or reset it, consider {@link tickEasing}. * * ```js * // Quad-in easing over 100 ticks * const e = Easings.ticks(`quadIn`, 100); * * // Keep calling e() to get the current value * e(); * ``` * * This is just a wrapper around Modulator.ticks * @param nameOrFunction Easing name or a function that produces 0..1 scale * @param totalTicks Total length of ticks * @returns */ declare const ticks$2: (nameOrFunction: EasingName | ((v: number) => number), totalTicks: number) => () => number; /** * Creates an easing based on ticks. * * `tickEasing` allows you to reset and check for completion. * Alternatively, use {@link ticks} which is a simple function that just returns a value. * * @example Tick-based easing * ``` * const t = Easings.tickEasing(`sineIn`, 1000); // Will take 1000 ticks to complete * t.compute(); // Each call to `compute` progresses the tick count * t.reset(); // Reset to 0 * t.isDone; // _True_ if finished * ``` * @param nameOrFunction Name of easing, or an easing function * @param durationTicks Duration in ticks * @returns Easing */ declare const tickEasing: (nameOrFunction: EasingName | ((v: number) => number), durationTicks: number) => ModulatorTimed; /** * Returns an easing function by name. Throws an error if * easing is not found. * * ```js * const fn = Easings.get(`sineIn`); * // Returns 'eased' transformation of 0.5 * fn(0.5); * ``` * @param easingName eg `sineIn` * @returns Easing function */ declare const get: (easingName: EasingName) => ModFunction; /** * Iterate over available easings. * @private * @returns Returns list of available easing names */ declare function getEasingNames(): Iterable<string>; //#endregion //#region ../packages/modulation/src/envelope/Types.d.ts type EnvelopeOpts = AdsrOpts & AdsrTimingOpts; /** * Options for the ADSR envelope. */ type AdsrOpts = Partial<{ /** * Attack bezier 'bend'. Bend from -1 to 1. 0 for a straight line */ readonly attackBend: number; /** * Decay bezier 'bend'. Bend from -1 to 1. 0 for a straight line */ readonly decayBend: number; /** * Release bezier 'bend'. Bend from -1 to 1. 0 for a straight line */ readonly releaseBend: number; /** * Peak level (maximum of attack stage) */ readonly peakLevel: number; /** * Starting level (usually 0) */ readonly initialLevel: number; /** * Sustain level. Only valid if trigger and hold happens */ readonly sustainLevel: number; /** * Release level, when envelope is done (usually 0) */ readonly releaseLevel: number; /** * When _false_, envelope starts from it's current level when being triggered. * _True_ by default. */ readonly retrigger: boolean; }>; type AdsrTimingOpts = Partial<{ /** * If true, envelope indefinately returns to attack stage after release * * @type {boolean} */ readonly shouldLoop: boolean; /** * Duration for attack stage * Unit depends on timer source * @type {number} */ readonly attackDuration: number; /** * Duration for decay stage * Unit depends on timer source * @type {number} */ readonly decayDuration: number; /** * Duration for release stage * Unit depends on timer source * @type {number} */ readonly releaseDuration: number; }>; type AdsrIterableOpts = { readonly signal?: AbortSignal; readonly sampleRateMs?: number; readonly env: EnvelopeOpts; }; /** * State change event */ interface StateChangeEvent { readonly newState: string; readonly priorState: string; } interface CompleteEvent {} type AdsrEvents = { readonly change: StateChangeEvent; readonly complete: CompleteEvent; }; declare const adsrStateTransitions: Readonly<{ attack: string[]; decay: string[]; sustain: string[]; release: string[]; complete: null; }>; type AdsrStateTransitions = Readonly<typeof adsrStateTransitions>; //#endregion //#region ../packages/modulation/src/envelope/AdsrBase.d.ts declare const defaultAdsrTimingOpts: { readonly attackDuration: 600; readonly decayDuration: 200; readonly releaseDuration: 800; readonly shouldLoop: false; }; /** * Base class for an ADSR envelope. * * It outputs values on a scale of 0..1 corresponding to each phase. */ declare class AdsrBase extends SimpleEventEmitter<AdsrEvents> { #private; protected attackDuration: number; protected decayDuration: number; protected releaseDuration: number; protected decayDurationTotal: number; /** * If _true_ envelope will loop */ shouldLoop: boolean; constructor(opts?: AdsrTimingOpts); dispose(): void; get isDisposed(): boolean; /** * Changes state based on timer status * @returns _True_ if state was changed */ protected switchStateIfNeeded(allowLooping: boolean): boolean; /** * Computes a stage's progress from 0-1 * @param allowStateChange * @returns */ protected computeRaw(allowStateChange?: boolean, allowLooping?: boolean): [stage: string | undefined, amount: number, prevStage: string]; /** * Returns _true_ if envelope has finished */ get isDone(): boolean; protected onTrigger(): void; /** * 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?: boolean): void; get hasTriggered(): boolean; compute(): void; /** * Release if 'trigger(true)' was previouslly called. * Has no effect if not triggered or held. * @returns */ release(): void; } //#endregion //#region ../packages/modulation/src/envelope/Adsr.d.ts declare const defaultAdsrOpts: { readonly attackBend: -1; readonly decayBend: -0.3; readonly releaseBend: -0.3; readonly peakLevel: 1; readonly initialLevel: 0; readonly sustainLevel: 0.6; readonly releaseLevel: 0; readonly retrigger: false; }; declare class AdsrIterator implements Iterator<number> { private adsr; constructor(adsr: Adsr); next(...args: [] | [undefined]): IteratorResult<number>; readonly [Symbol.toStringTag] = "Generator"; } /** * ADSR (Attack Decay Sustain Release) envelope. An envelope is a value that changes over time, * usually in response to an intial trigger. * * [See the ixfx Guide on Envelopes](https://ixfx.fun/modulation/envelopes/introduction/). * * @example Setup * ```js * const env = new Envelopes.Adsr({ * attackDuration: 1000, * decayDuration: 200, * sustainDuration: 100 * }); * ``` * * Options for envelope are as follows: * * ```js * initialLevel?: number * attackBend: number * attackDuration: number * decayBend: number * decayDuration:number * sustainLevel: number * releaseBend: number * releaseDuration: number * releaseLevel?: number * peakLevel: number * retrigger?: boolean * shouldLoop: boolean * ``` * * If `retrigger` is _false_ (default), a re-triggered envelope continues at current value * rather than resetting to `initialLevel`. * * If `shouldLoop` is true, envelope loops until `release()` is called. * * @example Using * ```js * env.trigger(); // Start envelope * ... * // Get current value of envelope * const [state, scaled, raw] = env.compute(); * ``` * * * `state` is a string, one of the following: 'attack', 'decay', 'sustain', 'release', 'complete' * * `scaled` is a value scaled according to the stage's _levels_ * * `raw` is the progress from 0 to 1 within a stage. ie. 0.5 means we're halfway through a stage. * * Instead of `compute()`, most usage of the envelope is just fetching the `value` property, which returns the same scaled value of `compute()`: * * ```js * const value = env.value; // Get scaled number * ``` * * @example Hold & release * ```js * env.trigger(true); // Pass in true to hold * ...envelope will stop at sustain stage... * env.release(); // Release into decay * ``` * * Check if it's done: * * ```js * env.isDone; // True if envelope is completed * ``` * * Envelope has events to track activity: 'change' and 'complete': * * ``` * env.addEventListener(`change`, ev => { * console.log(`Old: ${evt.oldState} new: ${ev.newState}`); * }) * ``` * * It's also possible to iterate over the values of the envelope: * ```js * const env = new Envelopes.Adsr(); * for await (const v of env) { * // v is the numeric value * await Flow.sleep(100); // Want to pause a little to give envelope time to run * } * // Envelope has finished * ``` */ declare class Adsr extends AdsrBase implements Iterable<number> { readonly attackPath: Path; readonly decayPath: Path; readonly releasePath: Path; readonly initialLevel: number; readonly peakLevel: number; readonly releaseLevel: number; readonly sustainLevel: number; readonly attackBend: number; readonly decayBend: number; readonly releaseBend: number; protected initialLevelOverride: number | undefined; readonly retrigger: boolean; private releasedAt; constructor(opts?: EnvelopeOpts); protected onTrigger(): void; [Symbol.iterator](): Iterator<number>; /** * Returns the scaled value * Same as .compute()[1] */ get value(): number; /** * 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?: boolean, allowLooping?: boolean): [stage: string | undefined, scaled: number, raw: number]; } declare namespace envelope_d_exports { export { Adsr, AdsrBase, AdsrEvents, AdsrIterableOpts, AdsrIterator, AdsrOpts, AdsrStateTransitions, AdsrTimingOpts, CompleteEvent, EnvelopeOpts, StateChangeEvent, adsr, adsrIterable, adsrStateTransitions, defaultAdsrOpts, defaultAdsrTimingOpts }; } /** * Returns a function that iterates over an envelope * ```js * const e = Envelopes.adsr(); * * e(); // Yields current value * ``` * * Starts the envelope the first time the return function is called. * When the envelope finishes, it continues to return the `releaseLevel` of the envelope. * * Options can be provided to set the shape of the envelope as usual, eg: * ```js * const e = Envelopes.adsr({ * attackDuration: 1000, * releaseDuration: 500 * }); * ``` * @param opts * @returns */ declare const adsr: (opts?: EnvelopeOpts) => () => number; /** * Creates and runs an envelope, sampling its values at `sampleRateMs`. * Note that if the envelope loops, iterator never returns. * * @example Init * ```js * import { Envelopes } from '@ixfx/modulation.js'; * import { IterableAsync } from '@ixfx/iterable.js'; * * const opts = { * attackDuration: 1000, * releaseDuration: 1000, * sustainLevel: 1, * attackBend: 1, * decayBend: -1 * }; * ``` * * ```js * // Add data to array * // Sample an envelope every 20ms into an array * const data = await IterableAsync.toArray(Envelopes.adsrIterable(opts, 20)); * ``` * * ```js * // Iterate with `for await` * // Work with values as sampled * for await (const v of Envelopes.adsrIterable(opts, 5)) { * // Work with envelope value `v`... * } * ``` * @param opts Envelope options * @returns */ declare function adsrIterable(opts: AdsrIterableOpts): AsyncGenerator<number>; declare namespace forces_d_exports { export { ForceAffected, ForceFn, ForceKind, MassApplication, PendulumOpts, TargetOpts, accelerationForce, angleFromAccelerationForce, angleFromVelocityForce, angularForce, apply, attractionForce, computeAccelerationToTarget, computeAttractionForce, computePositionFromAngle, computePositionFromVelocity, computeVelocity, constrainBounce, guard, magnitudeForce, nullForce, orientationForce, pendulumForce, springForce, targetForce, velocityForce }; } /** * Logic for applying mass */ type MassApplication = `dampen` | `multiply` | `ignored`; /** * Basic properties of a thing that can be * affected by forces */ type ForceAffected = { /** * Position. Probably best to use relative coordinates */ readonly position?: Point; /** * Velocity vector. * Probably don't want to assign this yourself, but rather have it computed based on acceleration and applied forces */ readonly velocity?: Point; /** * Acceleration vector. Most applied forces will alter the acceleration, culminating in a new velocity being set and the * acceleraton value zeroed */ readonly acceleration?: Point; /** * Mass. The unit is undefined, again best to think of this being on a 0..1 scale. Mass is particularly important * for the attraction/repulsion force, but other forces can incorporate mass too. */ readonly mass?: number; readonly angularAcceleration?: number; readonly angularVelocity?: number; readonly angle?: number; }; /** * A function that updates values of a thing. * * These can be created using the xxxForce functions, eg {@link attractionForce}, {@link accelerationForce}, {@link magnitudeForce}, {@link velocityForce} */ type ForceFn = (t: ForceAffected) => ForceAffected; /** * A vector to apply to acceleration or a force function */ type ForceKind = Point | ForceFn | null; /** * Throws an error if `t` is not of the `ForceAffected` shape. * @param t * @param name */ declare const guard: (t: ForceAffected, name?: string) => void; /** * `constrainBounce` yields a function that affects `t`'s position and velocity such that it * bounces within bounds. * * ```js * // Setup bounce with area constraints * // Reduce velocity by 10% with each impact * const b = constrainBounce({ width:200, height:500 }, 0.9); * * // Thing * const t = { * position: { x: 50, y: 50 }, * velocity: { x: 0.3, y: 0.01 } * }; * * // `b` returns an altereted version of `t`, with the * // bounce logic applied. * const bounced = b(t); * ``` * * `dampen` parameter allows velocity to be dampened with each bounce. A value * of 0.9 for example reduces velocity by 10%. A value of 1.1 will increase velocity by * 10% with each bounce. * @param bounds Constraints of area * @param dampen How much to dampen velocity by. Defaults to 1 meaning there is no damping. * @returns A function that can perform bounce logic */ declare const constrainBounce: (bounds?: Rect, dampen?: number) => (t: ForceAffected) => ForceAffected; /** * For a given set of attractors, returns a function that a sets acceleration of attractee. * Keep note though that this bakes-in the values of the attractor, it won't reflect changes to their state. For dynamic * attractors, it might be easier to use `computeAttractionForce`. * * @example Force * ```js * const f = Forces.attractionForce(sun, gravity); * earth = Forces.apply(earth, f); * ``` * * @example Everything mutually attracted * ```js * // Create a force with all things as attractors. * const f = Forces.attractionForce(things, gravity); * // Apply force to all things. * // The function returned by attractionForce will automatically ignore self-attraction * things = things.map(a => Forces.apply(a, f)); * ``` * @param attractors * @param gravity * @param distanceRange * @returns */ declare const attractionForce: (attractors: readonly ForceAffected[], gravity: number, distanceRange?: { readonly min?: number; readonly max?: number; }) => (attractee: ForceAffected) => ForceAffected; /** * Computes the attraction force between two things. * Value for `gravity` will depend on what range is used for `mass`. It's probably a good idea * to keep mass to mean something relative - ie 1 is 'full' mass, and adjust the `gravity` * value until it behaves as you like. Keeping mass in 0..1 range makes it easier to apply to * visual properties later. * * @example Attractee and attractor, gravity 0.005 * ```js * const attractor = { position: { x:0.5, y:0.5 }, mass: 1 }; * const attractee = { position: Points.random(), mass: 0.01 }; * attractee = Forces.apply(attractee, Forces.computeAttractionForce(attractor, attractee, 0.005)); * ``` * * @example Many attractees for one attractor, gravity 0.005 * ```js * attractor = { position: { x:0.5, y:0.5 }, mass: 1 }; * attractees = attractees.map(a => Forces.apply(a, Forces.computeAttractionForce(attractor, a, 0.005))); * ``` * * @example Everything mutually attracted * ```js * // Create a force with all things as attractors. * const f = Forces.attractionForce(things, gravity); * // Apply force to all things. * // The function returned by attractionForce will automatically ignore self-attraction * things = things.map(a => Forces.apply(a, f)); * ``` * * `attractor` thing attracting (eg, earth) * `attractee` thing being attracted (eg. satellite) * * * `gravity` will have to be tweaked to taste. * `distanceRange` clamps the computed distance. This affects how tightly the particles will orbit and can also determine speed. By default it is 0.001-0.7 * @param attractor Attractor (eg earth) * @param attractee Attractee (eg satellite) * @param gravity Gravity constant * @param distanceRange Min/max that distance is clamped to. * @returns */ declare const computeAttractionForce: (attractor: ForceAffected, attractee: ForceAffected, gravity: number, distanceRange?: { readonly min?: number; readonly max?: number; }) => Point; type TargetOpts = { /** * Acceleration scaling. Defaults to 0.001 */ readonly diminishBy?: number; /** * If distance is less than this range, don't move. * If undefined (default), will try to get an exact position */ readonly range?: Point; }; /** * A force that moves a thing toward `targetPos`. * * ```js * const t = Forces.apply(t, Forces.targetForce(targetPos)); * ``` * @param targetPos * @param opts * @returns */ declare const targetForce: (targetPos: Point, opts?: TargetOpts) => (t: ForceAffected) => ForceAffected; /** * Returns `pt` with x and y set to `setpoint` if either's absolute value is below `v` * @param pt * @param v * @returns */ /** * Apply a series of force functions or forces to `t`. Null/undefined entries are skipped silently. * It also updates the velocity and position of the returned version of `t`. * * ```js * // Wind adds acceleration. Force is dampened by mass * const wind = Forces.accelerationForce({ x: 0.00001, y: 0 }, `dampen`); * * // Gravity adds acceleration. Force is magnified by mass * const gravity = Forces.accelerationForce({ x: 0, y: 0.0001 }, `multiply`); * * // Friction is calculated based on velocity. Force is magnified by mass * const friction = Forces.velocityForce(0.00001, `multiply`); * * // Flip movement velocity if we hit a wall. And dampen it by 10% * const bouncer = Forces.constrainBounce({ width: 1, height: 1 }, 0.9); * * let t = { * position: Points.random(), * mass: 0.1 * }; * * // Apply list of forces, returning a new version of the thing * t = Forces.apply(t, * gravity, * wind, * friction, * bouncer * ); * ``` */ declare const apply: (t: ForceAffected, ...accelForces: readonly ForceKind[]) => ForceAffected; /** * Apples `vector` to acceleration, scaling according to mass, based on the `mass` option. * It returns a function which can later be applied to a thing. * * ```js * // Acceleration vector of (0.1, 0), ie moving straight on horizontal axis * const f = Forces.accelerationForce({ x:0.1, y:0 }, `dampen`); * * // Thing to move * let t = { position: ..., acceleration: ... } * * // Apply force * t = f(t); * ``` * @param vector * @returns Force function */ declare const accelerationForce: (vector: Point, mass?: MassApplication) => ForceFn; /** * A force based on the square of the thing's velocity. * It's like {@link velocityForce}, but here the velocity has a bigger impact. * * ```js * const thing = { * position: { x: 0.5, y:0.5 }, * velocity: { x: 0.001, y:0 } * }; * const drag = magnitudeForce(0.1); * * // Apply drag force to thing, returning result * const t = Forces.apply(thing, drag); * ``` * @param force Force value * @param mass How to factor in mass * @returns Function that computes force */ declare const magnitudeForce: (force: number, mass?: MassApplication) => ForceFn; /** * Null force does nothing * @returns A force that does nothing */ declare const nullForce: (t: ForceAffected) => ForceAffected; /** * Force calculated from velocity of object. Reads velocity and influences acceleration. * * ```js * let t = { position: Points.random(), mass: 0.1 }; * const friction = velocityForce(0.1, `dampen`); * * // Apply force, updating position and velocity * t = Forces.apply(t, friction); * ``` * @param force Force * @param mass How to factor in mass * @returns Function that computes force */ declare const velocityForce: (force: number, mass: MassApplication) => ForceFn; /** * Sets angle, angularVelocity and angularAcceleration based on * angularAcceleration, angularVelocity, angle * @returns */ declare const angularForce: () => (t: ForceAffected) => Readonly<{ angle: number; angularVelocity: number; angularAcceleration: 0; position?: Point; velocity?: Point; acceleration?: Point; mass?: number; }>; /** * Yields a force function that applies the thing's acceleration.x to its angular acceleration. * @param scaling Use this to scale the accel.x value. Defaults to 20 (ie accel.x*20). Adjust if rotation is too much or too little * @returns */ declare const angleFromAccelerationForce: (scaling?: number) => (t: ForceAffected) => Readonly<{ angularAcceleration: number; position?: Point; velocity?: Point; acceleration?: Point; mass?: number; angularVelocity?: number; angle?: number; }>; /** * Yields a force function that applies the thing's velocity to its angle. * This will mean it points in the direction of travel. * @param interpolateAmt If provided, the angle will be interpolated toward by this amount. Defaults to 1, no interpolation * @returns */ declare const angleFromVelocityForce: (interpolateAmt?: number) => (t: ForceAffected) => Readonly<{ angle: number; position?: Point; velocity?: Point; acceleration?: Point; mass?: number; angularAcceleration?: number; angularVelocity?: number; }>; /** * Spring force * * * ```js * // End of spring that moves * let thing = { * position: { x: 1, y: 0.5 }, * mass: 0.1 * }; * * // Anchored other end of spring * const pinnedAt = {x: 0.5, y: 0.5}; * * // Create force: length of 0.4 * const springForce = Forces.springForce(pinnedAt, 0.4); * * continuously(() => { * // Apply force * thing = Forces.apply(thing, springForce); * }).start(); * ``` * [Read more](https://www.joshwcomeau.com/animation/a-friendly-introduction-to-spring-physics/) * * @param pinnedAt Anchored end of the spring * @param restingLength Length of spring-at-rest (default: 0.5) * @param k Spring stiffness (default: 0.0002) * @param damping Damping factor to apply, so spring slows over time. (default: 0.995) * @returns */ declare const springForce: (pinnedAt: Point, restingLength?: number, k?: number, damping?: number) => (t: ForceAffected) => ForceAffected; /** * Pendulum force options */ type PendulumOpts = { /** * Length of 'string' thing is hanging from. If * undefined, the current length between thing and * pinnedAt is used. */ readonly length?: number; /** * Max speed of swing. Slower speed can reach equilibrium faster, since it * might not swing past resting point. * Default 0.001. */ readonly speed?: number; /** * Damping, how much to reduce velocity. Default 0.995 (ie 0.5% loss) */ readonly damping?: number; }; /** * The pendulum force swings something back and forth. * * ```js * // Swinger * let thing = { * position: { x: 1, y: 0.5 }, * mass: 0.1 * }; * * // Position thing swings from (middle of screen) * const pinnedAt = {x: 0.5, y: 0.5}; * * // Create force: length of 0.4 * const pendulumForce = Forces.pendulumForce(pinnedAt, { length: 0.4 }); * * continuously(() => { * // Apply force * // Returns a new thing with recalculated angularVelocity, angle and position. * thing = Forces.apply(thing, pendulumForce); * }).start(); * ``` * * [Read more](https://natureofcode.com/book/chapter-3-oscillation/) * * @param pinnedAt Location to swing from (x:0.5, y:0.5 default) * @param opts Options * @returns */ declare const pendulumForce: (pinnedAt?: Point, opts?: PendulumOpts) => (t: ForceAffected) => ForceAffected; /** * Compute velocity based on acceleration and current velocity * @param acceleration Acceleration * @param velocity Velocity * @param velocityMax If specified, velocity will be capped at this value * @returns */ declare const computeVelocity: (acceleration: Point, velocity: Point, velocityMax?: number) => Point; /** * Returns the acceleration to get from `currentPos` to `targetPos`. * * @example Barebones usage: * ```js * const accel = Forces.computeAccelerationToTarget(targetPos, currentPos); * const vel = Forces.computeVelocity(accel, currentVelocity); * * // New position: * const pos = Points.sum(currentPos, vel); * ``` * * @example Implementation: * ```js * const direction = Points.subtract(targetPos, currentPos); * const accel = Points.multiply(direction, diminishBy); * ``` * @param currentPos Current position * @param targetPos Target position * @param opts Options * @returns */ declare const computeAccelerationToTarget: (targetPos: Point, currentPos: Point, opts?: TargetOpts) => Point | { readonly x: 0; readonly y: 0; }; /** * Compute a new position based on existing position and velocity vector * @param position Position Current position * @param velocity Velocity vector * @returns Point */ declare const computePositionFromVelocity: (position: Point, velocity: Point) => Point; /** * Compute a position based on distance and angle from origin * @param distance Distance from origin * @param angleRadians Angle, in radians from origin * @param origin Origin point * @returns Point */ declare const computePositionFromAngle: (distance: number, angleRadians: number, origin: Point) => Point; /** * A force that orients things according to direction of travel. * * Under the hood, it applies: * * angularForce, * * angleFromAccelerationForce, and * * angleFromVelocityForce * @param interpolationAmt * @returns */ declare const orientationForce: (interpolationAmt?: number) => ForceFn; //#endregion //#region ../packages/modulation/src/gaussian.d.ts /** * Returns a roughly gaussian easing function * ```js * const fn = Easings.gaussian(); * ``` * * Try different positive and negative values for `stdDev` to pinch * or flatten the bell shape. * @param standardDeviation * @returns */ declare const gaussian: (standardDeviation?: number) => (t: number) => number; //#endregion //#region ../packages/core/src/text-tokenise.d.ts type Tokeniser = { split: (input: string) => string[]; joinWith: string; }; //#endregion //#region ../packages/modulation/src/interpolate/types.d.ts /** * Interpolation options. * * Limit: What to do if interpolation amount exceeds 0..1 range * clamp: lock to A & B (inclusive) Default. * wrap: wrap from end to start again * ignore: allow return values outside of A..B range * * Easing: name of easing function for non-linear interpolation * * Transform: name of function to transform `amount` prior to interpolate. This is useful for creating non-linear interpolation results. * * For example: * ```js * // Divide interpolation amount in half * const interpolatorInterval({ mins: 1 }, 10, 100, { * transform: (amount) => amount * Math.random() * }); * ``` * In the above example, the results would get more random over time. * `interpolatorInterval` will still step through the interpolation range of 0..1 in an orderly fashion, but we're transforming that range using a custom function before producing the result. * */ type InterpolateOptions = Partial<BasicInterpolateOptions> & { easing: EasingName; }; type BooleanInterpolateOptions = Partial<InterpolateOptions> & Partial<{ threshold: number; }>; type CenteredStringInterpolationOptions = Tokeniser; type StringInterpolateOptions = Partial<InterpolateOptions> & { style: `token` | `centered` | `human`; tokenise?: `character` | `word`; tokeniser?: Tokeniser; }; type AngleInterpolateOptions = Partial<InterpolateOptions> & { /** * How to interpolate between angles. Default is `short`, which means the shortest path between angles is taken. * `cw` means always interpolate in a clockwise direction, `ccw` means always interpolate in a counter-clockwise direction. */ direction?: AngleDirection; }; //#endregion //#region ../packages/modulation/src/interpolate/angle.d.ts declare function interpolatorAngle(amount: number, a: AngleConvertible, b: AngleConvertible, options?: AngleInterpolateOptions): number; declare function interpolatorAngle(a: AngleConvertible, b: AngleConvertible, options?: AngleInterpolateOptions): (amount: number) => number; declare function interpolatorAngleRadian(a: number, b: number, options?: AngleInterpolateOptions): (amount: number) => AngleRad; //#endregion //#region ../packages/modulation/src/interpolate/boolean.d.ts /** * Returns an interpolator function between two boolean values. * * Defaults to 0.5 as the threshold: * ```js * const i = interpolatorBoolean(false, true); * i(0); // false * i(0.5); // true * i(0.6); // true * ``` * * You can also specify a different threshold: * ```js * const i = interpolatorBoolean(false, true, { threshold: 0.8 }); * i(0.7); // false * i(0.8); // true * i(0.9); // true * ``` * * @param a * @param b * @param options * @returns Interpolator function */ declare function interpolatorBoolean(a: boolean, b: boolean, options?: BooleanInterpolateOptions): (amount: number) => boolean; //#endregion //#region ../packages/modulation/src/interpolate/number.d.ts /** * Returns a function that interpolates from A to B. * * It steps through the interpolation with each call to the returned function. * This means that the `incrementAmount` will hinge on the rate * at which the function is called. Alternatively, consider {@link interpolatorInterval} * which steps on the basis of clock time. * * ```js * // Interpolate from 0..1 by 0.01 * const v = interpolatorStepped(0.01, 100, 200); * v(); // Each call returns a value closer to target * // Eg: 100, 110, 120, 130 ... * ``` * * Under the hood, it calls `interpolate` with an amount that * increases by `incrementAmount` each time. * * When calling `v()` to step the interpolator, you can also pass * in new B and A values. Note that the order is swapped: the B (target) is provided first, and * then optionally A. * * ```js * const v = interpolatorStepped(0.1, 100, 200); // Interpolate 100->200 * v(300, 200); // Retarget to 200->300 and return result * v(150); // Retarget 200->150 and return result * ``` * * This allows you to maintain the current interpolation progress. * @param incrementAmount Amount to increment by * @param a Start value. Default: 0 * @param b End value. Default: 1 * @param startInterpolationAt Starting interpolation amount. Default: 0 * @param options Options for interpolation * @returns Interpolator function */ declare function interpolatorStepped(incrementAmount: number, a?: number, b?: number, startInterpolationAt?: number, options?: Partial<InterpolateOptions>): (retargetB?: number, retargetA?: number) => number; /** * Interpolates between A->B over `duration`. * Given the same A & B values, steps will be larger if it's a longer * duration, and shorter if it's a smaller duration. * * A function is returned, which when invoked yields a value between A..B. * * Alternatively to step through by the same amount regardless * of time, use {@link interpolatorStepped}. * * ```js * // Interpolate from 0..1 over one minute * const v = interpolatorInterval({mins:1}); * v(); // Compute current value * ``` * * Use start and end points: * ```js * // Interpolate from 100-200 over 10 seconds * const v = interpolatorInterval({secs:10}, 100, 200); * v(); // Compute current value * ``` * @param duration Duration for interpolation * @param a Start point * @param b End point * @param options Options for interpolation * @returns Interpolator function. */ declare function interpolatorInterval(duration: Interval, a?: number, b?: number, options?: Partial<InterpolateOptions>): (retargetB?: number, retargetA?: number) => number; //#endregion //#region ../packages/modulation/src/interpolate/object.d.ts type InterpolateObjectOptions<T> = { useFallbacks?: boolean; b: T; /** * Default interpolation options for numeric values */ optionsNumbers: InterpolateOptions; /** * Default interpolation options for string values */ optionsStrings: Partial<StringInterpolateOptions>; /** * Default interpolation options for boolean values */ optionsBooleans: Partial<BooleanInterpolateOptions>; /** * Default interpolation options if there's no handler for that property name or type. * It's a threshold from when return the A or B value. For example, with a threshold of 0.5, if progression is less than 0.5, return A, otherwise return B. */ fallbackThreshold: number; valueEq: (a: any, b: any) => boolean; }; /** * Interpolate child values between two objects. Non-recursive. * * ```js * const a = { name: `Alice`, age: 30, city: `New York`, radians: 0, point: { x: 0, y: 0 } }; * const b = { name: `ALICE`, age: 4, city: `New York`, radians: Math.PI * 2, length: 10, point: { x: 10, y: 10 } }; * * // Interpolate using default settings * const m = interpolatorObject(a); * const r = m(0.5, b); // Interpolate by 50% to value of `b` * // { name: `ALIce`, age: 17, city: `New York`, radians: 3.14, point: {x: 10, y:10}} * ``` * * Note in the above example the 'point' property isn't interpolated, because it's an object. In the case of unsupported data types like this, * the interpolator snaps between the A value and the B value based on a threshold (default: 0.5). In the above example, because the progression is 0.5, the interpolator returns the B value for 'point'. If the progression were 0.49, it would return the A value for 'point'. * * Provide handlers for interpolating specific properties: * ```js * import * as Points from '@ixfx/geometry/point'; * * // Use default interplators except for the 'point' property * const m = interpolatorObject(a, { * point: (a, b) => Points.interpolator(a, b), // Use @ixfx/geometry point interpolator for the 'point' property * }); * const r= m(0.5, b); * // Now the 'point' is interpolated as well: * // { name: `ALIce`, age: 17, city: `New York`, radians: 3.14, point: {x: 5, y:5}} * ``` * * If a handler for a given property is not defined, we use fallback interpolation for number, string and boolean value types. These * will use the default settings for their respective interpolator functions, or they can be provided: * ```js * const m = interpolatorObject(a, {}, { * optionsStrings: { style: `token`, tokenise: `character` }, // Use character tokenisation for string interpolation * optionsNumbers: { easing: `easeInOutQuad` }, // Use easeInOutQuad easing for number interpolation * optionsBooleans: { threshold: 0.8 }, // Use a threshold of 0.8 for boolean interpolation * }); * const r = m(0.5, b); * ``` * * When creating the interpolator you can pass in the initial target ('B' value) and also set the threshold used for unknown value types: * ```js * const m = interpolatorObject(a, {}, { b: targetValue, fallbackThreshold: 0.25 }); * m(0.6); // Don't need to pass in target, since it's already baked-in. * ``` * * If you don't pass in the target, it defaults to the start value, so the interpolator doesn't 'move'. Provide a target when calling the returned function * as shown in the earlier examples. * * The function is stateful in that the last set target is remembered. It's also possible to change the initial value: * ```js * m(0.5, newTarget); // Set a new target to interpolate to * m(0.5, newTarget, newOrigin); // Set both a new target and new origin * ``` * * When target or origin changes, we recreate the handlers defined on the `handlerFactory`, or set up the fallback defaults. * @param startingValue * @param handlerFactory * @param options * @returns */ declare function interpolatorObject<T>(startingValue: T, handlerFactory: Partial<{ [K in keyof T]: (valueA: T[K], valueB: T[K]) => (progression: number) => T[K] }>, options?: Partial<InterpolateObjectOptions<T>>): (progression: number, retarget?: T, pickupFrom?: T) => T; //#endregion //#region ../packages/modulation/src/interpolate/string.d.ts declare function interpolateString(amount: number, options?: Partial<StringInterpolateOptions>): (a: string, b: string) => string; declare function interpolateString(amount: number, a: string, b: string, options?: Partial<StringInterpolateOptions>): string; declare function interpolateString(a: string, b: string, options?: Partial<StringInterpolateOptions>): (amount: number) => string; /** * Interpolate by token. Returns a function that performs interpolation. * * ```js * import { Tokenise } from '@ixfx/core/text'; * // Create an interpolator * const i = interpolatorByTokens(`hello there`, `goodbye and farewell`, Tokenise.byWord()); * const i = interpolatorByTokens(`hello there`, `goodbye and farewell`, Tokenise.byCharacter()); * * // Use it: * i(0.5); // Gets 50% between the two strings, returning a string * ``` * @param a Start * @param b End * @param tokeniser Tokeniser * @returns Interpolator */ declare function interpolatorByTokens(a: string, b: string, tokeniser?: Tokeniser): (amount: number) => string; declare function interpolatorCentered(a: string, b: string, options?: CenteredStringInterpolationOptions): (amount: number) => string; declare function interpolatorHuman(a: string, b: string): (amount: number) => string; //#endregion //#region ../packages/modulation/src/jitter.d.ts type JitterOpts = { readonly relative?: number; readonly absolute?: number; readonly clamped?: boolean; readonly source?: RandomSource; }; type Jitterer = (value: number) => number; /** * Returns a {@link Jitterer} that works with absolute values, * ie. values outside of 0..1 range. * * Jitter amount is _absolute_, meaning a fixed value regardless of input value, * or _relative_, meaning it is scaled according to input value. * * ```js * // Jitter by -10 to +10 (absolute value: 10) * const j1 = jitterAbsolute({ absolute: 10 }); * j1(100); // Produces range of 90...110 * * // Jitter by -20 to +20 (relative value 20%) * const j2 = jitterAbsolute({ relative: 0.20 }); * j2(100); // Produces a range of -80...120 * ``` * * The expected used case is calling `jitterAbsolute` to set up a jitterer * and then reusing it with different input values, as above with the `j1` and `j2`. * * However to use it 'one-off', just call the returned function immediately: * ```js * const v = jitterAbsolute({ absolute: 10 })(100); // v is in range of 90-110 * ``` * * When `clamped` is true, return value is clamped to 0...value. * That is, rather than the usual bipolar jittering, the jittering only goes below. * ```js * const j = jitterAbsolute({ absolute: 10, clamped: true }) * j(100); // Produces range of 90-100 * ``` * @param options * @returns */ declare const jitterAbsolute: (options: JitterOpts) => Jitterer; /** * Jitters `value` by the absolute `jitter` amount. Returns a function. * * All values should be on a 0..1 scale, and the return value is by default clamped to 0..1. * Pass `clamped:false` as an option to allow for arbitary ranges. * * `jitter` returns a function that calculates jitter. If you only need a one-off * jitter, you can immediately execute the returned function: * ```js * // Compute 10% jitter of input 0.5 * const value = jitter({ relative: 0.1 })(0.5); * ``` * * However, if the returned jitter function is to be used again, * assign it to a variable: * ```js * const myJitter = jitter({ absolute: 0.5 }); * * // Jitter an input value 1.0 * const value = myJitter(1); * ``` * * A custom source for random numbers can be provided. Eg,