@ircam/sync

2.1.0 • Public • Published

@ircam/sync

Module that synchronises all clients to a server master clock.

Each client has access to a logical clock that synchronizes to the server clock. The module also provides helper functions that allows to convert the master clock, to and from, the local clock. Everybody can use the common master clock to schedule synchronized events. A good practice is to convert to local time at the last moment to trigger events, in order to minimize drift.

Table of Contents

Install

npm install [--save] @ircam/sync

Example use

This example show the usage of the library through a simple websocket transport with a naive ad-hoc ping / pong protocol.

Server-side

import { SyncServer } from '@ircam/sync';

const startTime = process.hrtime();
const getTimeFunction = () => {
  const now = process.hrtime(startTime);
  return now[0] + now[1] * 1e-9;
}

// 
const syncServer = new SyncServer(getTimeFunction);

const wss = new ws.Server({ server: httpServer });
wss.on('connection', (socket) => {
  // the `receiveFunction` and `sendFunction` functions aim at abstracting 
  // the transport layer between the SyncServer and the SyncClient
  const receiveFunction = callback => {
    socket.on('message', request => {
      request = JSON.parse(request);

      if (request[0] === 0) { // this is a ping
        // parse request
        const pingId = request[1];
        const clientPingTime = request[2];
        // notify the SyncServer
        callback(pingId, clientPingTime);
      }
    });
  };

  const sendFunction = (pingId, clientPingTime, serverPingTime, serverPongTime) => {
      // create response object
      const response = [
        1, // this is a pong
        pingId,
        clientPingTime,
        serverPingTime,
        serverPongTime,
      ];
      // send formatted response to the client
      socket.send(JSON.stringify(response));
  };

  syncServer.start(sendFunction, receiveFunction);
});

Client-side

import { SyncClient } from '@ircam/sync';

// return the local time in second
const getTimeFunction = () => {
  return performance.now() / 1000;
}

// init sync client
const syncClient = new SyncClient(getTimeFunction);
// init socket client
const socket = new WebSocket(url);

socket.addEventListener('open', () => {
  const sendFunction = (pingId, clientPingTime) => {
    const request = [
      0, // this is a ping
      pingId,
      clientPingTime,
    ];

    socket.send(JSON.stringify(request));
  };

  const receiveFunction = callback => {
    socket.addEventListener('message', e => {
      const response = JSON.parse(e.data);

      if (response[0] === 1) { // this is a pong
        const pingId = response[1];
        const clientPingTime = response[2];
        const serverPingTime = response[3];
        const serverPongTime = response[4];

        callback(pingId, clientPingTime, serverPingTime, serverPongTime);
      }
    });
  }

  // check the synchronization status, when this function is called for the 
  // first time, you can consider the synchronization process properly 
  // initiated.
  const statusFunction = status => console.log(status);
  // start synchronization process
  syncClient.start(sendFunction, receiveFunction, statusFunction);
});

// monitor the synchronized clock
setInterval(() => {
  const syncTime = syncClient.getSyncTime();
  console.log(syncTime);
}, 100);

API

Classes

SyncClient

SyncClient instances synchronize to the clock provided by the SyncServer instance. The default estimation behavior is strictly monotonic and guarantee a unique convertion from server time to local time.

SyncServer

The SyncServer instance provides a clock on which SyncClient instances synchronize.

SyncClient

SyncClient instances synchronize to the clock provided by the SyncServer instance. The default estimation behavior is strictly monotonic and guarantee a unique convertion from server time to local time.

Kind: global class
See: SyncClient~start method to actually start a synchronisation process.

new SyncClient(getTimeFunction, [options])

Param Type Default Description
getTimeFunction getTimeFunction
[options] Object
[options.pingTimeOutDelay] Object range of duration (in seconds) to consider a ping was not ponged back
[options.pingTimeOutDelay.min] Number 1 min and max must be set together
[options.pingTimeOutDelay.max] Number 30 min and max must be set together
[options.pingSeriesIterations] Number 10 number of ping-pongs in a series
[options.pingSeriesPeriod] Number 0.250 interval (in seconds) between pings in a series
[options.pingSeriesDelay] Number range of interval (in seconds) between ping-pong series
[options.pingSeriesDelay.min] Number 10 min and max must be set together
[options.pingSeriesDelay.max] Number 20 min and max must be set together
[options.longTermDataTrainingDuration] Number 120 duration of training, in seconds, approximately, before using the estimate of clock frequency
[options.longTermDataDuration] Number 900 estimate synchronisation over this duration, in seconds, approximately
[options.estimationMonotonicity] Boolean true When true, the estimation of the server time is strictly monotonic, and the maximum instability of the estimated server time is then limited to options.estimationStability.
[options.estimationStability] Number 160e-6 This option applies only when options.estimationMonotonicity is true. The adaptation to the estimated server time is then limited by this positive value. 80e-6 (80 parts per million, PPM) is quite stable, and corresponds to the stability of a conventional clock. 160e-6 is moderately adaptive, and corresponds to the relative stability of 2 clocks; 500e-6 is quite adaptive, it compensates 5 milliseconds in 1 second. It is the maximum value (estimationStability must be lower than 500e-6).

syncClient.start(sendFunction, receiveFunction, reportFunction)

Start a synchronisation process by registering the receive function passed as second parameter. Then, send regular messages to the server, using the send function passed as first parameter.

Kind: instance method of SyncClient

Param Type Description
sendFunction sendFunction
receiveFunction receiveFunction to register
reportFunction reportFunction if defined, is called to report the status, on each status change, and each time the estimation of the synchronised time updates.

syncClient.stop()

Stop the synchronization process

Kind: instance method of SyncClient

syncClient.getLocalTime([syncTime]) ⇒ Number

Get local time, or convert a synchronised time to a local time.

Kind: instance method of SyncClient
Returns: Number - local time, in seconds

Param Type Description
[syncTime] Number Get local time according to given given syncTime, if syncTime is not defined returns current local time.

syncClient.getSyncTime([localTime]) ⇒ Number

Get synchronised time, or convert a local time to a synchronised time.

Kind: instance method of SyncClient
Returns: Number - synchronised time, in seconds.

Param Type Description
[localTime] Number Get sync time according to given given localTime, if localTime is not defined returns current sync time.

SyncClient~getTimeFunction ⇒ Number

Kind: inner typedef of SyncClient
Returns: Number - strictly monotonic, ever increasing, time in second. When possible the server code should define its own origin (i.e. time=0) in order to maximize the resolution of the clock for a long period of time. When SyncServer~start is called the clock should already be running (cf. audioContext.currentTime that needs user interaction to start)

SyncClient~sendFunction : function

Kind: inner typedef of SyncClient
See: receiveFunction

Param Type Description
pingId Number unique identifier
clientPingTime Number time-stamp of ping emission

SyncClient~receiveFunction : function

Kind: inner typedef of SyncClient
See: sendFunction

Param Type Description
receiveCallback receiveCallback called on each message matching messageType.

SyncClient~receiveCallback : function

Kind: inner typedef of SyncClient

Param Type Description
pingId Number unique identifier
clientPingTime Number time-stamp of ping emission
serverPingTime Number time-stamp of ping reception
serverPongTime Number time-stamp of pong emission

SyncClient~reportFunction : function

Kind: inner typedef of SyncClient

Param Type Description
report Object
report.status String new, startup, training (offset adaptation), or sync (offset and speed adaptation).
report.statusDuration Number duration since last status change.
report.timeOffset Number time difference between local time and sync time, in seconds.
report.frequencyRatio Number time ratio between local time and sync time.
report.connection String offline or online
report.connectionDuration Number duration since last connection change.
report.connectionTimeOut Number duration, in seconds, before a time-out occurs.
report.travelDuration Number duration of a ping-pong round-trip, in seconds, mean over the the last ping-pong series.
report.travelDurationMin Number duration of a ping-pong round-trip, in seconds, minimum over the the last ping-pong series.
report.travelDurationMax Number duration of a ping-pong round-trip, in seconds, maximum over the the last ping-pong series.

SyncServer

The SyncServer instance provides a clock on which SyncClient instances synchronize.

Kind: global class
See: SyncServer~start method to actually start a synchronisation process.

new SyncServer(function)

Param Type Description
function getTimeFunction called to get the local time. It must return a time in seconds, monotonic, ever increasing.

syncServer.start(sendFunction, receiveFunction)

Start a synchronisation process with a SyncClient by registering the receive function passed as second parameter. On each received message, send a reply using the function passed as first parameter.

Kind: instance method of SyncServer

Param Type
sendFunction sendFunction
receiveFunction receiveFunction

syncServer.getLocalTime([syncTime]) ⇒ Number

Get local time, or convert a synchronised time to a local time.

Kind: instance method of SyncServer
Returns: Number - local time, in seconds
Note: getLocalTime and getSyncTime are basically aliases on the server.

Param Type Description
[syncTime] Number Get local time according to given given syncTime, if syncTime is not defined returns current local time.

syncServer.getSyncTime([localTime]) ⇒ Number

Get synchronised time, or convert a local time to a synchronised time.

Kind: instance method of SyncServer
Returns: Number - synchronised time, in seconds.
Note: getLocalTime and getSyncTime are basically aliases on the server.

Param Type Description
[localTime] Number Get sync time according to given given localTime, if localTime is not defined returns current sync time.

SyncServer~getTimeFunction ⇒ Number

Kind: inner typedef of SyncServer
Returns: Number - monotonic, ever increasing, time in second. When possible the server code should define its own origin (i.e. time=0) in order to maximize the resolution of the clock for a long period of time. When SyncServer~start is called the clock should be running (cf. audioContext.currentTime that needs user interaction to start)
Example

const startTime = process.hrtime();

const getTimeFunction = () => {
  const now = process.hrtime(startTime);
  return now[0] + now[1] * 1e-9;
};

SyncServer~sendFunction : function

Kind: inner typedef of SyncServer
See: receiveFunction

Param Type Description
pingId Number unique identifier
clientPingTime Number time-stamp of ping emission
serverPingTime Number time-stamp of ping reception
serverPongTime Number time-stamp of pong emission

SyncServer~receiveFunction : function

Kind: inner typedef of SyncServer
See: sendFunction

Param Type Description
receiveCallback receiveCallback called on each message matching messageType.

SyncServer~receiveCallback : function

Kind: inner typedef of SyncServer

Param Type Description
pingId Number unique identifier
clientPingTime Number time-stamp of ping emission

Caveats

The synchronisation process is continuous: after a call to the start method, it runs in the background. It is important to avoid blocking it, on the client side and on the server side.

In many cases, running the sync process in another thread is not an option as the local clock will be different accross threads or processes.

Publication

For more information, you can read this article presented at the Web Audio Conference 2016:

Jean-Philippe Lambert, Sébastien Robaszkiewicz, Norbert Schnell. Synchronisation for Distributed Audio Rendering over Heterogeneous Devices, in HTML5. 2nd Web Audio Conference, Apr 2016, Atlanta, GA, United States. ⟨hal-01304889⟩ - https://hal.archives-ouvertes.fr/hal-01304889v1

Note: the stabilisation of the estimated synchronous time has been added after the publication of this article.

License

BSD-3-Clause. See the LICENSE file.

Versions

Current Tags

  • Version
    Downloads (Last 7 Days)
    • Tag
  • 2.1.0
    3
    • latest

Version History

Package Sidebar

Install

npm i @ircam/sync

Weekly Downloads

3

Version

2.1.0

License

BSD-3-Clause

Unpacked Size

52.8 kB

Total Files

6

Last publish

Collaborators

  • jipodine
  • norbert.schnell
  • b-ma