Skip to content

Array modifiers

unreviewed draft

fast, smooth, ease, offset and fit — methods that control how an array argument steps through its values over time.

Any numeric argument can be an array; hydra then plays its values in sequence. The modifiers are methods hydra adds to every JavaScript array. Each frame, the position in the array is

i=time⋅speed⋅bpm60+offseti = \text{time}\cdot\text{speed}\cdot\frac{\text{bpm}}{60} + \text{offset}

and without smoothing the value is arr[floor(i % length)]. With bpm = 30 (the default) and fast(1), each value lasts 2 seconds.

With smooth(s), the glide from one value to the next starts s/2s/2 steps before the boundary and ends s/2s/2 after it; ease reshapes that glide.

fast source in array-utils.js (hydra-synth 1.4.0) ↗
Stepping shape sizes
shape(4, [0.2, 0.4, 0.3]).out()
Open in hydra ↗
Faster, smooth color cycle
osc(20, 0.1, [0, 1.5, 3].fast(2).smooth(1)).out()
Open in hydra ↗
Eased rotation between quarter turns
shape(3).rotate([0, 1.5708, 3.1416, 4.7124].ease('easeInOutCubic')).out()
Open in hydra ↗
Rescale a sequence into a useful range
osc([1, 2, 3, 4].fit(10, 60)).out()
Open in hydra ↗

Half-step smoothing holds, then glides across each boundary:

Sets how quickly the array advances through its values.

ParameterDefaultUseful rangeUnitsNo-op at
speed10.1 … 8steps per beat (steps/second = speed × bpm / 60)1

speed= 1

Steps per beat. At the default bpm of 30, fast(1) holds each value for 2 seconds.

Useful range
0.1 … 8
Units
steps per beat (steps/second = speed × bpm / 60)
No-op at
1
Negative values
without smooth, the value becomes undefined (renders as 0); with smooth, steps backwards
Accepts
number · array · function

fast(0) does not freeze the array: 0 is falsy and is treated as 1. To hold a value, pass a plain number.

0.5
1
2
4
Value over 8 s at bpm = 30 for each speed: [0, 1, 0.5, 0.25].fast({speed})

Interpolates between neighbouring values instead of jumping.

ParameterDefaultUseful rangeUnitsNo-op at
smooth10 … 1fraction of a step0 (hard steps)

smooth= 1

Fraction of each step spent gliding to the next value; the glide is centered on the step boundary.

Useful range
0 … 1
Units
fraction of a step
No-op at
0 (hard steps)
Accepts
number · array · function

smooth(1) glides the whole time (a continuous ramp); smooth(0.5) holds for half a step and glides for half. Values above 1 never finish a glide, so the value jumps at every step boundary.

0
0.25
0.5
1
Value over 8 s at bpm = 30 for each smooth: [0, 1, 0.5, 0.25].smooth({smooth})

Chooses the shape of the glide between values, and turns smoothing on.

ParameterDefaultUseful rangeUnitsNo-op at
ease'linear'——none: calling ease always turns smoothing on ('linear' is the same as smooth(1))

ease= 'linear'

Name of an easing curve, or your own function mapping t∈[0,1]t \in [0,1] to [0,1][0,1].

Useful range
—
No-op at
none: calling ease always turns smoothing on ('linear' is the same as smooth(1))
Values
'linear' 'easeInQuad' 'easeOutQuad' 'easeInOutQuad' 'easeInCubic' 'easeOutCubic' 'easeInOutCubic' 'easeInQuart' 'easeOutQuart' 'easeInOutQuart' 'easeInQuint' 'easeOutQuint' 'easeInOutQuint' 'sin'
Accepts
string · function

Calling ease sets smooth to 1. Call .smooth(s) after .ease() for a partial glide. An unknown name is silently ignored (no smoothing, no warning).

'linear'
'easeInQuad'
'easeOutCubic'
'easeInOutQuint'
'sin'
Value over 8 s at bpm = 30 for each ease: [0, 1, 0.5, 0.25].ease({ease})

Shifts where in the array playback starts, by a fraction of one step.

ParameterDefaultUseful rangeUnitsNo-op at
offset0.50 … 1steps (0.5 = half a step = 1 s at fast(1), bpm 30)0 (or any whole number)

offset= 0.5

Phase shift in steps; only the fractional part is kept.

Useful range
0 … 1
Units
steps (0.5 = half a step = 1 s at fast(1), bpm 30)
No-op at
0 (or any whole number)
Negative values
kept negative (-0.25 stays -0.25); without smooth this gives undefined (renders 0) at the start
Periodic
every 1 — stored as offset % 1, so offset(2) is 0 and offset(1.5) is 0.5
Accepts
number · array · function
0
0.25
0.5
0.75
Value over 8 s at bpm = 30 for each offset: [0, 1, 0.5, 0.25].offset({offset})

Returns a new array linearly rescaled so its minimum is low and maximum is high.

ParameterDefaultUseful rangeUnitsNo-op at
low0-2 … 2—the array's current minimum (with high = its maximum)
high1-2 … 2—the array's current maximum

low= 0

Value the array's smallest element maps to.

Useful range
-2 … 2
No-op at
the array's current minimum (with high = its maximum)
Accepts
number · array · function
0
-1
0.5
Value over 8 s at bpm = 30 for each low: [0, 1, 0.5, 0.25].fit({low}, 1)

high= 1

Value the array's largest element maps to.

Useful range
-2 … 2
No-op at
the array's current maximum
Accepts
number · array · function

low > high flips the array. If every element is equal, all results are NaN (renders 0).

1
2
0
Value over 8 s at bpm = 30 for each high: [0, 1, 0.5, 0.25].fit(0, {high})
  • offset keeps only the fractional part, so it can shift by less than one step but never skip whole values; rotate the array literal instead.
  • An unknown ease name is ignored silently (probe: _smooth and _ease stay undefined).
  • fit on an array whose values are all equal divides by zero: every element is NaN, rendered as 0.
  • Arrays are sampled with the global clock: changing speed or bpm changes every array’s pace.

Documents hydra-synth 1.4.0 (AGPL-3.0; this site runs it unmodified). Annotations drafted by Claude (Anthropic) from the hydra-synth source and checked against rendered output — see about. Site code AGPL-3.0, prose CC BY 4.0,source.