Tutorial

Value Providers

What are Value Providers?

All moves take a finite amount of steps. Ayva determines how many steps a given move will take based on the duration of the move and the frequency of updates (as specified in the configuration). A value provider allows you to, well, provide the value for an axis at a given step. 😊

It is nothing more than a function that takes parameters describing the current step—as well as the movement overall—and returns what the value should be. When paired with a target value and speed (or duration), a value provider can be used as a ramp function that describes the shape of the motion towards a target. A value provider may also simply provide a value without regard to a target value. Both scenarios are covered in the following sections.

Ramp

Value providers are passed as the value property. The following example demonstrates using the built-in ramp functions covered in the previous section as value providers for individual movements:

// Use the default ramp (cosine) to slowly move to the top (no value provider specified).
ayva.move({
  to: 1,
  speed: 0.25
});

// "Fall" towards the bottom (parabolic)
ayva.move({ 
  to: 0,
  speed: 0.75,
  value: Ayva.RAMP_PARABOLIC
});

// "Bounce" back to the top (negative parabolic)
ayva.move({
  to: 1,
  speed: 0.75,
  value: Ayva.RAMP_NEGATIVE_PARABOLIC
});

// Linear move back to the bottom.
ayva.move({
  to: 0,
  speed: 0.75,
  value: Ayva.RAMP_LINEAR
});

// Move slowly back to the top with cosine.
// Note: The default ramp is cosine so we did not actually have to explicitly express it here.
ayva.move({
  to: 1,
  speed: 0.25,
  value: Ayva.RAMP_COS
});

Try it out!

Custom Ramp

Value providers are called with a single argument—a parameters object with properties you can use to compute the value of the current step. To create a ramp function, you can make use of the to, from, and x properties. These are the target value, the start value, and the fraction of the move that should be completed by the end of the current step, respectively. The following example demonstrates how one might implement a simple linear shaped movement using these parameters:

const myLinearRamp = (parameters) => {
  const to = parameters.to;
  const from = parameters.from;
  const x = parameters.x;

  return from + (to - from) * x;
};

ayva.move({
  to: 0,
  speed: 0.25,
  value: myLinearRamp
});

Try it out!

This example could be made more succinct by passing the function directly and making use of object destructuring to capture the properties we want from the parameters object:

ayva.move({
  to: 0,
  speed: 0.25,
  value: ({ to, from, x }) => from + (to - from) * x
});

Note that the special property x is a value between 0 (exclusive) and 1 (inclusive). i.e. x attains the value of 1 at the end of the movement.

Ayva.ramp

The pattern from + (to - from) * (...) is common enough that Ayva provides a shorthand for it in the form of a static function: Ayva.ramp. This method takes a value provider and converts it into a ramp function using the from + (to - from) * (...) pattern. The previous example could therefore be rewritten even more succinctly as:

ayva.move({
  to: 0,
  speed: 0.25,
  value: Ayva.ramp(({ x }) => x)
});

Note: Care should be taken when creating ramp functions. In order to actually result in reaching the target, they should attain the value 1 at x = 1. However, this is not enforced in any way.

Custom Motion

A target value does not have to be specified when using a value provider. The following example generates motion using a sin wave.

const bpm = 24; // Beats per minute. Try tweaking this value.

ayva.move({
  duration: 60,
  value: ({ x }) => {
    const result = Math.sin(x * Math.PI * 2 * bpm);

    // Sin is in the range [-1, 1]. So we must shift and scale into the range [0, 1].
    return (result + 1) / 2;
  }
});

Try it out!

When you do not specify a target value you must specify a duration (and not a speed). Ayva needs a way to compute how many steps the move will take. Therefore the following is invalid:

// This will result in an error no matter what the value provider is!
ayva.move({
  speed: 1,
  value: () => {
    // ...
  }
});

Complexity

A value provider naturally may contain as much logic as you want. It may also return null or undefined to indicate no movement for a particular step. Here is an example that performs a stroke with a twist that only happens after half the stroke is finished (multiaxis movements are covered in greater detail in the next section):

// Stroke to the top.
ayva.move({ 
  to: 1, 
  speed: 0.25 
});

// Slowly stroke down with a twist that
// occurs only on the last half of the move.
ayva.move({
  to: 0,
  duration: 5
},{
  axis: 'twist',
  value: ({ x }) => {
    if (x >= 0.5) {
      // Convert range [0.5, 1] to range [0, 1]
      const y = Ayva.map(x, 0.5, 1); 

      // Perform one cycle of sin. 
      // We map it from sin's range of [-1, 1] to the axis range of [0, 1].
      return Ayva.map(Math.sin(y * Math.PI * 2), -1, 1, 0, 1);
    } else {
      // No movement when x < 0.5
      // While we explicitly return null for this example, we also  
      // could have simply not returned a value at all (i.e. undefined)
      return null;
    }
  }
});

Try it out!

Note: This example makes use of the convenience methodAyva.map(), which maps values from one range to another—with a default target range of [0, 1].

Parameters

There are many more parameters available for value providers to use if needed. The full list is provided below:

Parameter Type Attributes Description
axis String

The name of the axis being moved.

from Number

The value of the axis when the move was started.

to Number <optional>

The target value of the axis. This property is only present if it was specified in the movement.

speed Number <optional>

The speed of the movement. This property is only present if to was specified.

direction Number <optional>

The direction of the movement. This property is only present if to was specified.

-1 if to < from
 1 if to > from
 0 if to = from

frequency Number

The update frequency (50 Hz in the default configuration).

period Number

The length of time between updates in seconds ( 1 / frequency).

duration Number

The duration of the move in seconds.

stepCount Number

The total number of steps of the move.

index Number

The index of the current step.

currentValue Number

The current value of the axis.

x Number

The fraction of the move that should be completed by the end of this step.