npm package: @ircam/sc-scheduling
Description: Set of abstractions for advanced audio scheduling in Node.js and the browser.
Latest version: 1.0.0

# sc-scheduling

Simple library to schedule events in Node.js and the browser.

The abstractions provided by the library are built in such way that they can be used in distributed contexts, allowing to synchronize events and controls among multiple devices and clients.

## Install

```
npm install --save @ircam/sc-scheduling
```

## Example Use

```js
import { Scheduler } from '@ircam/sc-scheduling';

const audioContext = new AudioContext();
// create a scheduler in the timeline of the audio context
const scheduler = new Scheduler(() => audioContext.currentTime);
// schedule an audio event to be scheduler every seconds
schedule.add(currentTime => {
  doSomethingAtCurrentTime(currentTime);
  // ask to be called back in 1 second from currentTime
  return currentTime + 1;
});
```

## Terminology

- `time` refers to the timeline of the parent
- `position` refers to the timeline of the children

- `Transport` is the thing that control timelines or engines with play / pause / etc.
- `Timeline` is an abstraction that can host abstractions and place them in time according to each others

## API

<!-- api -->
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->

### Table of Contents

*   [SchedulerProcessor][1]
    *   [Parameters][2]
*   [Scheduler][3]
    *   [Parameters][4]
    *   [Examples][5]
    *   [period][6]
    *   [lookahead][7]
    *   [currentTime][8]
    *   [audioTime][9]
    *   [processorTime][10]
    *   [defer][11]
    *   [has][12]
    *   [add][13]
    *   [reset][14]
    *   [remove][15]
    *   [clear][16]
*   [SchedulerEvent][17]
    *   [tickLookahead][18]
*   [TransportProcessor][19]
    *   [Parameters][20]
*   [Transport][21]
    *   [Parameters][22]
    *   [Examples][23]
    *   [serialize][24]
    *   [scheduler][25]
    *   [currentTime][26]
    *   [processorTime][27]
    *   [currentPosition][28]
    *   [getPositionAtTime][29]
    *   [start][30]
    *   [stop][31]
    *   [pause][32]
    *   [seek][33]
    *   [loop][34]
    *   [loopStart][35]
    *   [loopEnd][36]
    *   [speed][37]
    *   [cancel][38]
    *   [addEvent][39]
    *   [addEvents][40]
    *   [add][41]
    *   [has][42]
    *   [remove][43]
    *   [clear][44]
*   [TransportEvent][45]
    *   [Parameters][46]
    *   [type][47]
    *   [time][48]
    *   [position][49]
    *   [speed][50]
    *   [loop][51]
    *   [loopStart][52]
    *   [loopEnd][53]
    *   [tickLookahead][54]

## SchedulerProcessor

Processor to add into a [Scheduler][3].

The processor will be called back by the Scheduler at the time it request,
do some processing and return the next time at which it wants to be called back.

Note that the APIs of the `SchedulerProcessor` and of a `TransportProcessor`
are made in such way that it is possible to implement generic processors that
can be added both to a `Scheduler` and to a `Transport`.

Type: [function][55]

### Parameters

*   `currentTime` **[number][56]** Current time in the timeline of the scheduler
*   `processorTime` **[number][56]** Current time in the timeline of the processor
    see `Scheduler#options.currentTimeToProcessorTimeFunction`.
*   `event` **[SchedulerEvent][17]** Event that holds informations about the current
    scheduler call.

## Scheduler

The `Scheduler` interface implements a lookahead scheduler that can be used to
schedule events in an arbitrary timelines.

It aims at finding a tradeoff between time precision, real-time responsiveness
and the weaknesses of the native timers (i.e. `setTimeout` and `setInterval`)

For an in-depth explaination of the pattern, see [https://web.dev/audio-scheduling/][57]

### Parameters

*   `getTimeFunction` **[function][55]** Function that returns a time in seconds,
    defining the timeline in which the scheduler is running.
*   `$1` **[Object][58]**  (optional, default `{}`)

    *   `$1.period`   (optional, default `0.02`)
    *   `$1.lookahead`   (optional, default `0.05`)
    *   `$1.queueSize`   (optional, default `1e3`)
    *   `$1.currentTimeToProcessorTimeFunction`   (optional, default `identity`)
    *   `$1.currentTimeToAudioTimeFunction`   (optional, default `null`)
    *   `$1.maxRecursions`   (optional, default `100`)
    *   `$1.verbose`   (optional, default `false`)
*   `options` **[object][58]** Options of the scheduler

### Examples

```javascript
import { Scheduler } from '@ircam/sc-scheduling';
import { getTime } from '@ircam/sc-utils';

const scheduler = new Scheduler(getTime);

const processor = (currentTime, processorTime, infos) => {
  console.log(currentTime);
  return currentTime + 0.1; // ask to be called back every 100ms
}

// start processor in 1 second
scheduler.add(processor, getTime() + 1);
```

### period

Period of the scheduler, in seconds.

Minimum time span between the scheduler checks for events, in seconds.
Throws if negative or greater than lookahead.

Type: [number][56]

### lookahead

Lookahead duration, in seconds.
Throws if negative or lower than period.

Type: [number][56]

### currentTime

Current time in the scheduler timeline, in seconds.

Basically an accessor for `getTimeFunction` parameter given in constructor.

Type: [number][56]

### audioTime

\[deprecated] Scheduler current audio time according to `currentTime`

Type: [number][56]

### processorTime

Processor time, in seconds, according to `currentTime` and the transfert
function provided in `options.currentTimeToProcessorTimeFunction`.

If `options.currentTimeToProcessorTimeFunction` has not been set, is equal
to `currentTime`.

Type: [number][56]

### defer

Execute a function once at a given time.

Calling `defer` compensates for the tick lookahead introduced by the scheduling
with a `setTimeout`. Can be usefull for example to synchronize audio events
which natively scheduled with visuals which have no internal timing/scheduling
ability.

Be aware that this method will introduce small timing error of 1-2 ms order
of magnitude due to the `setTimeout`.

#### Parameters

*   `deferedProcessor` **[SchedulerProcessor][1]** Callback function to schedule.
*   `time` **[number][56]** Time at which the callback should be scheduled.

#### Examples

```javascript
const scheduler = new Scheduler(getTime);

scheduler.add((currentTime, processorTime) => {
  // schedule some audio event
  playSomeSoundAt(processorTime);
  // defer execution of visual display to compensate the tickLookahead
  scheduler.defer(displaySomeSynchronizedStuff, currentTime);
  // ask the scheduler to call back in 1 second
  return currentTime + 1;
});
```

### has

Check whether a given processor has been added to this scheduler

#### Parameters

*   `processor` **[SchedulerProcessor][1]** Processor to test.

Returns **[boolean][59]**&#x20;

### add

Add a processor to the scheduler.

Note that given `time` is considered a logical time and that no particular
checks are made on it as it might break synchronization between several
processors. So if the given time is in the past, the processor will be called
in a recursive loop until it reaches current time.
This is the responsibility of the consumer code to handle such possible issues.

#### Parameters

*   `processor` **[SchedulerProcessor][1]** Processor to add to the scheduler
*   `time` **[number][56]** Time at which the processor should be launched. (optional, default `this.currentTime`)
*   `priority` **[Number][56]** Additional priority in case of equal time between
    two processor. Higher priority means the processor will processed first. (optional, default `0`)

### reset

Reset next time of a given processor.

If time is not a number, the processor is removed from the scheduler.

Note that given `time` is considered a logical time and that no particular
checks are made on it as it might break synchronization between several
processors. So if the given time is in the past, the processor will be called
in a recursive loop until it reaches current time.
This is the responsibility of the consumer code to handle such possible issues.

Be aware that calling this method within a processor callback function won't
work, because the reset will always be overriden by the processor return value.

#### Parameters

*   `processor` **[SchedulerProcessor][1]** The processor to reschedule
*   `time` **[number][56]** Time at which the processor must be rescheduled (optional, default `undefined`)

### remove

Remove a processor from the scheduler.

#### Parameters

*   `processor` **[SchedulerProcessor][1]** The processor to reschedule

### clear

Clear the scheduler.

## SchedulerEvent

Scheduler information provided as third argument of a callback registered
in the scheduler

### tickLookahead

Delta time between tick time and current time, in seconds

Type: [Number][56]

## TransportProcessor

Processor to add into a [Transport][21].

The processor will be called back by the Transport on each transport event to
define its behavior according to event. Between these events, it can be called
as a regular [SchedulerProcessor][1] to do some processing.

Note that the APIs of the `SchedulerProcessor` and of a `TransportProcessor`
are made in such way that it is possible to implement generic processors that
can be added both to a `Scheduler` and to a `Transport`.

Type: [function][55]

### Parameters

*   `currentPosition` **[number][56]** Current position in the timeline of the transport.
*   `processorTime` **[number][56]** Current time in the timeline of the processor
    see `Scheduler#options.currentTimeToProcessorTimeFunction`.
*   `event` **([TransportEvent][45] | [SchedulerEvent][17])** Event that holds informations
    about the current transport or scheduler call.

## Transport

The Transport abstraction allows to define and manipulate a timeline.

All provided Transport commands (e.g. start, stop, etc) can be scheduled in the
underlying scheduler timeline which makes it usable in distributed and synchronized
contexts.

### Parameters

*   `scheduler` **[Scheduler][3]** Instance of scheduler into which the transport
    should run
*   `initialState` **[object][58]** Initial state of the transport, to synchronize
    it from another transport state (see `Transport#dumpState()`). (optional, default `null`)

### Examples

```javascript
import { Scheduler, Transport, TransportEvent } from '@ircam/sc-scheduling';
import { getTime } from '@ircam/sc-utils';

const scheduler = new Scheduler(getTime);
const transport = new Transport(scheduler);

const processor = (position, time, infos) => {
  if (infos instanceof TransportEvent) {
     // ask to be called back only when the transport is running
     return infos.speed > 0 ? position : Infinity;
  }

  console.log(position);
  return position + 0.1; // ask to be called back every 100ms
}

transport.add(processor);
// start transport in 1 second
transport.start(getTime() + 1);
```

### serialize

Retrieves the current state and event queue for the transport as a raw object.

The returned value can be used to initialize the state of another synchronized
transport, cf. `initialValue` argument from constructor.

Returns **[object][58]**&#x20;

### scheduler

Pointer to the underlying scheduler.

Type: [Scheduler][3]

### currentTime

Current time from scheduler timeline, in seconds.

Type: [number][56]

### processorTime

Current processor time, in seconds.

Type: [number][56]

### currentPosition

Current transport position, in seconds.

Type: [number][56]

### getPositionAtTime

Estimated position at given time according to the transport current state.

#### Parameters

*   `time` **[number][56]** Time to convert to position

Returns **[number][56]**&#x20;

### start

Start the transport at a given time.

#### Parameters

*   `time` **[number][56]** Time to execute the command (optional, default `this.currentTime`)

Returns **([object][58] | null)** Raw event or `null` if event discarded

### stop

Stop the transport at a given time, position will be reset to zero.

#### Parameters

*   `time` **[number][56]** Time to execute the command (optional, default `this.currentTime`)

Returns **([object][58] | null)** Raw event or `null` if event discarded

### pause

Pause the transport at a given time, position will remain untouched.

#### Parameters

*   `time` **[number][56]** Time to execute the command (optional, default `this.currentTime`)

Returns **([object][58] | null)** Raw event or `null` if event discarded

### seek

Seek to a new position in the timeline at a given time.

#### Parameters

*   `position` **[number][56]** New transport position
*   `time` **[number][56]** Time to execute the command (optional, default `this.currentTime`)

Returns **([object][58] | null)** Raw event or `null` if event discarded

### loop

Set the transport loop state at a given time.

#### Parameters

*   `value` **[boolean][59]** Loop state
*   `time` **[number][56]** Time to execute the command (optional, default `this.currentTime`)

Returns **([object][58] | null)** Raw event or `null` if event discarded

### loopStart

Set the transport loop start point at a given time.

#### Parameters

*   `position` **[number][56]** Position of loop start point
*   `time` **[number][56]** Time to execute the command (optional, default `this.currentTime`)

Returns **([object][58] | null)** Raw event or `null` if event discarded

### loopEnd

Set the transport loop end point at a given time.

#### Parameters

*   `position` **[number][56]** Position of loop end point
*   `time` **[number][56]** Time to execute the command (optional, default `this.currentTime`)

Returns **([object][58] | null)** Raw event or `null` if event discarded

### speed

Set transport speed at a given time.

Note that speed must be strictly positive.
*Experimental*

#### Parameters

*   `value` &#x20;
*   `time` **[number][56]** Time to execute the command (optional, default `this.currentTime`)
*   `speed` **[number][56]** Speed of the transport, must be strictly > 0

Returns **([object][58] | null)** Raw event or `null` if event discarded

### cancel

Cancel all currently scheduled event after the given time.

#### Parameters

*   `time` **[number][56]** Time to execute the command (optional, default `this.currentTime`)

Returns **([object][58] | null)** Raw event or `null` if event discarded

### addEvent

Add raw event to the transport queue.

Most of the time, you should use the dedicated higher level methods. However
this is useful to control several transports from a central event producer.
In particular this can be used to synchronize several transport on the network
according you have access to a synchronized timeline in which the schedulers
are running, cf. e.g. [https://github.com/ircam-ismm/sync][60])

#### Parameters

*   `event` **[object][58]** Raw event as returned by the transport control methods

#### Examples

```javascript
const scheduler = new Scheduler(getTime);
const primary = new Transport(scheduler);
// create a "copy" of the primary transport
const secondary = new Transport(scheduler, primary.serialize());
// perform some control command and share it with the secondary transport
const event = primary.start(getTime() + 1);
// `event` (as well as `primary.serialize()`) could e.g. be sent over the network
secondary.addEvent(event);
```

### addEvents

Add a list of raw events to the transport queue.

#### Parameters

*   `eventList` &#x20;
*   `event` **[Array][61]<[object][58]>** List of raw events

### add

Add an processor to the transport.

When a processor is added to the transport, it called with an 'init' event
to allow it to respond properly to the current state of the transport.
For example, if the transport has already been started.

#### Parameters

*   `processor` **[TransportProcessor][19]** Engine to add to the transport

<!---->

*   Throws **any** Throw if the processor has already been added to this or another transport

### has

Define if a given processor has been added to the transport.

#### Parameters

*   `processor` **[TransportProcessor][19]** Engine to check

Returns **[boolean][59]**&#x20;

### remove

Remove a processor from the transport.

#### Parameters

*   `processor` **[TransportProcessor][19]** Engine to remove from the transport

<!---->

*   Throws **any** Throw if the processor has not been added to the transport

### clear

Remove all processors, cancel all registered transport event and pause transport

## TransportEvent

Event emitted by the Transport when a change occurs

### Parameters

*   `transportState` &#x20;
*   `tickLookahead` &#x20;

### type

Type of the event

Type: [string][62]

### time

Time of the event

Type: [number][56]

### position

Position of the event in timeline

Type: [number][56]

### speed

Current speed of the transport (0 is stopped or paused, 1 if started)

Type: [number][56]

### loop

Wether the transport is looping

Type: [boolean][59]

### loopStart

Start position of the loop

Type: [number][56]

### loopEnd

Stop position of the loop

Type: [number][56]

### tickLookahead

Delta time between tick time and event time, in seconds

Type: [number][56]

[1]: #schedulerprocessor

[2]: #parameters

[3]: #scheduler

[4]: #parameters-1

[5]: #examples

[6]: #period

[7]: #lookahead

[8]: #currenttime

[9]: #audiotime

[10]: #processortime

[11]: #defer

[12]: #has

[13]: #add

[14]: #reset

[15]: #remove

[16]: #clear

[17]: #schedulerevent

[18]: #ticklookahead

[19]: #transportprocessor

[20]: #parameters-7

[21]: #transport

[22]: #parameters-8

[23]: #examples-2

[24]: #serialize

[25]: #scheduler-1

[26]: #currenttime-1

[27]: #processortime-1

[28]: #currentposition

[29]: #getpositionattime

[30]: #start

[31]: #stop

[32]: #pause

[33]: #seek

[34]: #loop

[35]: #loopstart

[36]: #loopend

[37]: #speed

[38]: #cancel

[39]: #addevent

[40]: #addevents

[41]: #add-1

[42]: #has-1

[43]: #remove-1

[44]: #clear-1

[45]: #transportevent

[46]: #parameters-24

[47]: #type

[48]: #time

[49]: #position

[50]: #speed-1

[51]: #loop-1

[52]: #loopstart-1

[53]: #loopend-1

[54]: #ticklookahead-1

[55]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function

[56]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number

[57]: https://web.dev/audio-scheduling/

[58]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

[59]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean

[60]: https://github.com/ircam-ismm/sync

[61]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array

[62]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

<!-- apistop -->


## License

[BSD-3-Clause](./LICENSE)