This is the 29th day of my participation in the August More Text Challenge

One way Spark AR Studio uses a responsive programming model is to allow you to treat values as signals, which are special objects that contain a value that changes over time.

Signals can be bound to an object’s property so that when the value of the signal changes, the value of the object’s property is automatically updated.

For basic data types, there are equivalent signals, including numbers (ScalarSignal), strings (StringSignal), and BoolSignal (BoolSignal).

Bind objects to object properties

The syntax for binding a signal to an object is the same as for performing standard assignment.

In the following example, we bind the signal from the source object (the user’s face) to the target object (plane). So when FaceTracking. Face (0). Cameratransform. RotationY signal changes, plane. The transform. The value of x will be updated.

// Load in the required modules
const Scene = require('Scene');
const FaceTracking = require('FaceTracking');
    
// Enables async/await in JS [part 1]
(async function() {

    // Locate the plane we've added to the Scene panel
    const plane = await Scene.root.findFirst('plane0');
    
    // Bind the user's face rotation in the Y-axis to the X-position of our plane
    plane.transform.x = FaceTracking.face(0).cameraTransform.rotationY;
    
// Enables async/await in JS [part 2]}) ();Copy the code

This statement is treated as a signal binding because the left value of the assignment operator (=) is the response object and the right value is the signal.

If the rvalue is of standard Scalar, String, or Bool, the statement will be treated as a standard assignment.

Convert values to signals

When a number, string, or Boolean value is passed to a function or property Setter that requires a signal, the value is implicitly converted to a constant signal:

// Load in the required modules
const FaceTracking = require('FaceTracking');

// Enables async/await in JS [part 1]
(async function() { 

    // The numerical value 0.1 is automatically converted to a ScalarSignal
    const mouthOpen = FaceTracking.face(0).mouth.openness.gt(0.1);
    
// Enables async/await in JS [part 2]}) ();Copy the code

You can also explicitly convert the raw data type to its equivalent signal using the Val () method exposed by the ReactiveModule class (more on this class later when we operate with signals). The following example converts a Scalar(numerical) value to a ScalarSignal.

// Load in the required modules
const FaceTracking = require('FaceTracking');
const Reactive = require('Reactive');

// Enables async/await in JS [part 1]
(async function() { 

    // Convert the numerical value 0.1 to a ScalarSignal
    const scalarSignal = Reactive.val(0.1);
    
    // Use the converted value
    const mouthOpen = FaceTracking.face(0).mouth.openness.gt(scalarSignal);

// Enables async/await in JS [part 2]}) ();Copy the code

You can use the same method to convert a Boolean value to BoolSignal, or a string to StringSignal.

// Load in the required modules
const Reactive = require('Reactive');

// Enables async/await in JS [part 1]
(async function() { 

    // Convert the bool value 'true' to a BoolSignal
    const boolSignal = Reactive.val(true);
    
    // Convert the string value to a StringSignal
    const stringSignal = Reactive.val("This is a string");

// Enables async/await in JS [part 2]}) ();Copy the code

Retrieves a value from a signal

If you need to retrieve a value contained in a signal, you can use the pinLastValue() method exposed by the ScalarSignal, StringSignal, and BoolSignal classes:

// Load in the required modules
const Diagnostics = require('Diagnostics');
const FaceTracking = require('FaceTracking');

// Enables async/await in JS [part 1]
(async function() { 

    // Get the value of mouth openness when this line of code is executed
    const mouthOpennessValue = FaceTracking.face(0).mouth.openness.pinLastValue();
    
    // Log the value
    Diagnostics.log(mouthOpennessValue);

// Enables async/await in JS [part 2]}) ();Copy the code

As the value changes over time, the last value contained by the signal before the method was called is returned.

Alternatively, you can subscribe to events through the subscribeWithSnapshot() method, which provides the value of the signal to be used in the callback function:

// Load in the required modules
const Diagnostics = require('Diagnostics');
const FaceTracking = require('FaceTracking');
const TouchGestures = require('TouchGestures');

// Enables async/await in JS [part 1]
(async function() { 

    // Subscribe to tap gestures
    TouchGestures.onTap().subscribeWithSnapshot({
        
        // Get the value of mouth openness when the tap gesture is detected
        'val' : FaceTracking.face(0).mouth.openness
        
    }, function (gesture, snapshot) {
    
        // Log the value from the snapshot
        Diagnostics.log(snapshot.val);
    });

// Enables async/await in JS [part 2]}) ();Copy the code

Operate with signals

Because their values change over time, standard JavaScript operators such as +, -, *, and/cannot be executed directly on signals.

Instead, these types of calculations are performed using methods exposed by the ReactiveModule and ScalarSignal classes, as shown in the following example.

// Load in modules
const Diagnostics = require('Diagnostics');
const Reactive = require('Reactive');

// Enables async/await in JS [part 1]
(async function() {

    // Create a new scalar signal with an initial value of 5
    const originalValue = Reactive.val(5);

    // Add 1 to the signal with the add() method exposed by the ScalarSignal
    // class and store the result in a new variable
    const valuePlusOne = originalValue.add(1);

    // Add 2 to the signal with the add() method exposed by the ReactiveModule
    // class and store the result in a new variable
    const valuePlusTwo = Reactive.add(originalValue, 2);

    // Multiply the signal by 2 with the mul() method exposed by the ReactiveModule
    // class and store the result in a new variable
    const valueDoubled = Reactive.mul(originalValue, 2);

    // Log the signals' values to the console
    Diagnostics.log(originalValue.pinLastValue());
    Diagnostics.log(valuePlusOne.pinLastValue());
    Diagnostics.log(valuePlusTwo.pinLastValue());
    Diagnostics.log(valueDoubled.pinLastValue());

// Enables async/await in JS [part 2]    }) ();Copy the code

In addition, the BoolSignal and StringSignal classes expose their own set of methods for manipulating signals:

// Load in modules
const Diagnostics = require('Diagnostics');
const Reactive = require('Reactive');

// Enables async/await in JS [part 1]
(async function() {

    // Create a BoolSignal object
    const boolSignal = Reactive.val(true);

    // Perform a logical 'not' operation with boolSignal and store the result in a new variable
    const newBool = boolSignal.not();

    // Create a StringSignal object
    const stringSignal = Reactive.val("This is a string");

    // Concatenate 'stringSignal' with a new string and store the result in a new variable
    const newString = stringSignal.concat(" made longer.");

    // Log the signals' values to the console
    Diagnostics.log(boolSignal.pinLastValue());
    Diagnostics.log(newBool.pinLastValue());
    Diagnostics.log(stringSignal.pinLastValue());
    Diagnostics.log(newString.pinLastValue());

// Enables async/await in JS [part 2]}) ();Copy the code

Monitor the value of a signal

The Watch () method, exposed by the DiagnosticsModule (which supports diagnostic logging) class, allows you to add a signal to the Watch view in Spark AR Studio to monitor how its value changes over time.

// Load in the required modules
const Diagnostics = require('Diagnostics');
const FaceTracking = require('FaceTracking');

// Add the mouth openness signal to the watch view
Diagnostics.watch("Mouth Openness - ", FaceTracking.face(0).mouth.openness);

Copy the code

The view appears at the top right of the console: