How Time Point Streaming works
Time Point Streaming is our high frequency emitter mode. Control Point position and intensity values stream to the array at up to 40000 times a second.
In Amplitude Modulation, the application pushes updates by calling the update function.
The Ultrahaptics::TimePointStreaming::Emitter uses a callback mechanism:
- Your application registers a callback function.
- At regular intervals the Ultrahaptics SDK driver requests input by calling this registered function.
The result is a continuous stream of haptic output to the device.
It is up to you to write the callback implementation and define the Control Point behaviour. For this reason, Time Point Streaming is more complex than Amplitude Modulation. But it is also much more powerful and flexible. You have direct control of each Control Point’s waveform. This means you must apply modulation. Either modulate its intensity – amplitude modulation (small ‘a’, small ‘m’) – or position: Spatiotemporal Modulation.
Time Point Streaming walkthrough
- Include the UltrahapticsTimePointStreaming.hpp header file,
- Create a Time Point Streaming emitter Ultrahaptics::TimePointStreaming::Emitter,
- Set the maximum control point count using setMaximumControlPointCount. This is the number of control points your application can create,
- Write your emission callback function,
- Register the callback function with the emitter using setEmissionCallback,
- Start the emitter,
- The callback runs approximately every 1 millisecond,
- The callback provides an OutputInterval parameter. This represents a time window and a vector of time points. Iterate through the time points to set each Control Point’s position and intensity. The callback also provides an anonymous pointer to access any persistent context data. Use getSamplingInterval to get the duration of each time point.
- To stop the array either
(a) call the emitter’s stop function,
(b) disable individual Control Points or
(c) set Control Point intensity to 0.
Note: the term sample is often use to refer to a time point instance. The OutputInterval is dependent on the array’s sample rate, number of control points and USB latency. This may vary between calls and your waveform can span many output intervals.
Time Point Streaming Example: TimePointStreaming_Circle.cpp
Let’s look at how the Time Point Streaming callback mechanism updates control points by using an example. Here, we will move a single control point, with constant intensity, around a circle 20 cm above the array. You can find the TimePointStreaming_Circle.cpp file in the Ultrahaptics SDK’s Examples folder.
TimePointStreaming_Circle.cpp uses Spatiotemporal Modulation to create sensation. See TimePointStreaming_Focus.cpp for an amplitude modulation example.
Add the Time Point Streaming API by including the UltrahapticsTimePointStreaming.hpp header:
#include <cmath> #include <iostream> #include <chrono> #include <thread> #include <string> #include "UltrahapticsTimePointStreaming.hpp"
Define a structure to hold our context data. Here, we store the Control Point’s coordinates and intensity values between each callback. This struct also has a function to calculate the next Control Point position for a given radius and a circular frequency.
#ifndef M_PI #define M_PI 3.14159265358979323 #endif using Seconds = std::chrono::duration;<float>; static auto start_time = std::chrono::steady_clock::now(); // Structure for passing information on the type of point to create struct Circle { // The position of the control point Ultrahaptics::Vector3 position; // The intensity of the control point float intensity; // The radius of the circle float radius; // The frequency at which the control point goes around the circle float frequency; const Ultrahaptics::Vector3 evaluateAt(Seconds t){ // Calculate the x and y positions of the circle and set the height position.x = std::cos(2 * M_PI * frequency * t.count()) * radius; position.y = std::sin(2 * M_PI * frequency * t.count()) * radius; return position; } };
Here we define our callback function. This must match the format of TimePointEmissionCallback
as defined in the API. In our implementation we dereference our Circle structure and iterate through each time point, using its time stamp to calculate the new position.
Each time point (sample) references each Control Point using persistentControlPoint. Use the index of the Control Point we wish to set:
void my_emitter_callback(const Ultrahaptics::TimePointStreaming::Emitter &timepoint_emitter, Ultrahaptics::TimePointStreaming::OutputInterval &interval, const Ultrahaptics::HostTimePoint &submission_deadline, void *user_pointer) { // Cast the user pointer to the struct that describes the control point behaviour Circle *circle = static_cast<Circle*>(user_pointer); // Loop through the samples in this interval for (auto& sample : interval) { const Seconds t = sample - start_time; const Ultrahaptics::Vector3 position = circle->evaluateAt(t); // Set the position and intensity of the persistent control // point to that of the modulated wave at this point in time. sample.persistentControlPoint(0).setPosition(position); sample.persistentControlPoint(0).setIntensity(circle->intensity); } }
In the main function, we define our TimePointStreaming emitter, setting the maximum number of control points. We instantiate the Circle, defining its size and intensity and then register our callback and start the emitter. The emitter only stops when the Enter key is pressed.
int main(int argc, char *argv[]) { // Create a time point streaming emitter Ultrahaptics::TimePointStreaming::Emitter emitter; // Set the maximum control point count emitter.setMaximumControlPointCount(1); // Create a structure containing our control point data and fill it in Circle circle; // Set control point 20cm above the centre of the array at the radius circle.position = Ultrahaptics::Vector3(0.0f, 0.0f, 20.0f*Ultrahaptics::Units::centimetres); // Set the amplitude of the modulation of the wave to one (full modulation depth) circle.intensity = 1.0f; // Set the radius of the circle that the point is traversing circle.radius = 2.0f * Ultrahaptics::Units::centimetres; // Set how many times the point traverses the circle every second circle.frequency = 100.0f; // Set the callback function to the callback written above emitter.setEmissionCallback(my_emitter_callback, &circle); // Start the array emitter.start(); // Wait for enter key to be pressed. std::cout << "Hit ENTER to quit..." << std::endl; std::string line; std::getline(std::cin, line); // Stop the array emitter.stop(); return 0; }
Units in the Ultrahaptics SDK
Note that in our main function, we create an Ultrahaptics::Vector3 for the initial position of the Control Point 20 cm along the z-axis. The Ultrahaptics API uses metres in any reference to coordinate space. The API provides convenience constants under the Ultrahaptics::Units namespace to convert between metric units:
const double | mm = 0.001 |
const double | millimetres = 0.001 |
const double | cm = 0.01 |
const double | centimetres = 0.01 |
const double | m = 1. |
const double | metres = 1. |
0 Comments