Scripting
Signals

Signals

One of the ways Spark AR Studio makes use of a reactive programming model is by allowing you to treat values as signals, which are special objects containing a value that can change 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.

There are signal equivalents for primitive data types including number (ScalarSignal), string (StringSignal) and boolean (BoolSignal) values.

Binding a signal to an object’s property

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

In the example below we bind a signal from a source object - the user’s face, to a target object - a plane. As a result, the value of plane.transform.x will be updated whenever the value of the FaceTracking.face(0).cameraTransform.rotationY signal changes.

// 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]
})();
                

The statement is treated as a signal binding because the value left of the assignment operator (=) is a reactive object and the right value is a signal. If the right value were a standard scalar, string or boolean value the statement would be treated as a standard assignment.

Converting values to signals

When a number, string or boolean value is passed to a function or property setter that expects 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]
})();
                

You can also explicitly convert a primitive data type to its signal equivalent with the val() method exposed by the ReactiveModule class (more on this class later). The example below 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]
})();
                

You can use this same method to convert a boolean to a BoolSignal, or a string to a 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]
})();
                

Retrieving a value from a signal

If you need to retrieve the value that a signal contains 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]
})();
                

As the value changes over time, this returns the last value the signal contained before the method was called.

Alternatively, events can be subscribed to via the subscribeWithSnapshot() method, which provides the value of the signal for use 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]
})();
                

Performing operations with signals

As their value changes over time, standard JavaScript operators such as +, -, * and / can’t be performed directly on signals.

Instead, these type of calculations are performed via methods exposed by the ReactiveModule class and the ScalarSignal class as seen in the example below.

// 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]    
})();
                

Additionally, 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]
})();
                

Monitoring a signal’s value

The watch() method exposed by the DiagnosticsModule 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);
                

The watch view appears in the top right of the console:

Was this article helpful?