Ninja Pumpkin Mutants

    @soundworks/plugin-sync

    1.1.0 • Public • Published

    @soundworks/plugin-sync

    soundworks plugin for synchronizing arbitrary clocks across devices. The plugin is basically a wrapper around the @ircam/sync library.

    Table of Contents

    Why

    Because "as a consequence of dealing with independent nodes, each one will have its own notion of time. In other words, we cannot assume that there is something like a global clock" in Maarten van Steen and Andrew S. Tanenbaum, A brief introduction to distributed systems, Computing, vol.98, n°10, 2016

    Installation

    npm install @soundworks/plugin-sync --save

    Example

    A working example can be found in the https://github.com/collective-soundworks/soundworks-examples repository.

    Usage

    Server installation

    Registering the plugin

    // index.js
    import { Server } from '@soundworks/core/server';
    import pluginSyncFactory from '@soundworks/plugin-sync/server';
    
    const server = new Server();
    server.pluginManager.register('sync', pluginSyncFactory, {
      // choose the clock to use as the reference, defaults to:
      // (where `startTime` is the time at which the plugin is instantiated)
      getTimeFunction: () => {
        const now = process.hrtime(startTime);
        return now[0] + now[1] * 1e-9;
      },
    }, []);

    Requiring the plugin

    // MyExperience.js
    import { AbstractExperience } from '@soundworks/core/server';
    
    class MyExperience extends AbstractExperience {
      constructor(server, clientType) {
        super(server, clientType);
        // require plugin in the experience
        this.sync = this.require('sync');
      }
    }

    Client installation

    Registering the plugin

    // index.js
    import { Client } from '@soundworks/core/client';
    import pluginSyncFactory from '@soundworks/plugin-sync/client';
    
    const client = new Client();
    client.pluginManager.register('sync', pluginSyncFactory, {
      // choose the clock to synchronize, defaults to:
      // (where `startTime` is the time at which the plugin is instantiated)
      getTimeFunction: () => Date.now() * 0.001 - startTime,
    }, []);

    Requiring the plugin

    // MyExperience.js
    import { Experience } from '@soundworks/core/client';
    
    class MyExperience extends Experience {
      constructor(client) {
        super(client);
        // require plugin in the experience
        this.sync = this.require('sync');
      }
    }

    Local time and synchronized time

    The following API is similar client-side and server-side

    // get current time from the local clock reference
    const localTime = this.sync.getLocalTime();
    // get time in the local clock reference according to the
    // time given in the synchronized clock reference
    const localTime = this.sync.getLocalTime(syncTime);
    
    // get time estimated in the synchronized clock reference
    const sync = this.sync.getSyncTime();
    // get time estimated in the synchronized clock reference
    // according the time given in the local clock reference
    const sync = this.sync.getSyncTime(localTime);

    Auditing synchronization process

    On the client side you can track the synchronization process, by checking the synchronization reports

    // be notified when a new report is available
    this.sync.onReport(report => {
      console.log(report);
    });
    
    // or in a pull fashion
    const report = this.sync.getReport();

    Synchronizing audio events with waves-masters

    waves-masters is a library that provides low-level abstraction for scheduling and transport. It can be installed from npm running:

    npm install --save waves-masters

    To synchronize the audio clock, the application should use the @soundworks/plugin-platform to resume the audioContext before starting the synchronization process.

    Therefore additionally to importing and requiring the plugin as describe in the @soundworks/plugin-platform documentation. The following code must be added client-side:

    // index.js
    import pluginSyncFactory from '@soundworks/plugin-sync/client';
    import pluginPlatformFactory from '@soundworks/plugin-platform/client';
    
    const AudioContext = window.AudioContext || window.webkitAudioContext;
    const audioContext = new AudioContext();
    
    client.pluginManager.register('platform', pluginPlatformFactory, {
      features: [
        ['web-audio', audioContext],
      ]
    }, []);
    
    // `getTimeFunction` makes use of the `audioContext.currentTime`
    // to synchronize according to the audio clock reference.
    // The last argument define the platform plugin as a dependency of the
    // sync plugin, so that sync process starts with a resumed audio clock.
    client.pluginManager.register('sync', pluginSyncFactory, {
      getTimeFunction: () => audioContext.currentTime,
    }, ['platform']);

    Instantiate a scheduler running in the sync reference clock and add a synchronized audio engine:

    // MyExperience.js
    import { AbstractExperience } from '@soundworks/core/client';
    import { Scheduler, TimeEngine } from 'waves-masters';
    
    class MyExperience {
      constructor(client, audioContext) {
        super(client);
    
        this.audioContext = audioContext;
    
        this.platform = this.require('platform');
        this.sync = this.require('sync');
      }
    
      async start() {
        super.start();
    
        // Create a scheduler that schedule events in the sync time reference
        const getTimeFunction = () => this.sync.getSyncTime();
        // Provide a conversion function that allows the scheduler to compute
        // the audio time from it own scheduling time reference.
        // As `currentTime` is in the sync time reference we gave in
        // `getTimeFunction` and that the sync plugin is configured to use
        // the audio clock as a local reference, we therefore just need to convert
        // back to the local time.
        const currentTimeToAudioTimeFunction =
          currentTime => this.sync.getLocalTime(currentTime);
    
        // create the scheduler
        const scheduler = new Scheduler(getTimeFunction, {
          currentTimeToAudioTimeFunction
        });
    
        // create a idiot engine
        const engine = {
          // - `currentTime` is the current time of the scheduler
          // (aka the `syncTime`)
          // - `audioTime` is the audioTime as computed by the provided
          // `currentTimeToAudioTimeFunction`
          // `dt` is the time between the actual call of the function
          // and the time of the scheduled event
          advanceTime: (currentTime, audioTime, dt) => {
            const sine = this.audioContext.createOscillator();
            sine.connect(this.audioContext.destination);
            // play the sound in the audio clock reference
            sine.start(audioTime);
            sine.stop(audioTime + 0.1);
            // return time of next event in the scheduler (sync) time reference
            return currentTime + 1;
          }
        }
    
        // add engine to the scheduler
        scheduler.add(engine);
      }
    }

    Resources

    • 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-01304889v1>

    Credits

    The code has been initiated in the framework of the WAVE and CoSiMa research projects, funded by the French National Research Agency (ANR).

    License

    BSD-3-Clause

    Keywords

    none

    Install

    npm i @soundworks/plugin-sync

    DownloadsWeekly Downloads

    10

    Version

    1.1.0

    License

    BSD-3-Clause

    Unpacked Size

    21.2 kB

    Total Files

    8

    Last publish

    Collaborators

    • b-ma