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: