-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Alexander Grahn
committed
Nov 10, 2023
1 parent
9931a0e
commit 9f0b3c0
Showing
4 changed files
with
268 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,243 @@ | ||
;(function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){ | ||
var WAAClock = require('./lib/WAAClock') | ||
|
||
module.exports = WAAClock | ||
if (typeof window !== 'undefined') window.WAAClock = WAAClock | ||
|
||
},{"./lib/WAAClock":2}],2:[function(require,module,exports){ | ||
var isBrowser = (typeof window !== 'undefined') | ||
|
||
var CLOCK_DEFAULTS = { | ||
toleranceLate: 0.1, | ||
toleranceEarly: 0.001 | ||
} | ||
|
||
// ==================== Event ==================== // | ||
var Event = function(clock, deadline, func) { | ||
this.clock = clock | ||
this.func = func | ||
this._cleared = false // Flag used to clear an event inside callback | ||
|
||
this.toleranceLate = clock.toleranceLate | ||
this.toleranceEarly = clock.toleranceEarly | ||
this._latestTime = null | ||
this._earliestTime = null | ||
this.deadline = null | ||
this.repeatTime = null | ||
|
||
this.schedule(deadline) | ||
} | ||
|
||
// Unschedules the event | ||
Event.prototype.clear = function() { | ||
this.clock._removeEvent(this) | ||
this._cleared = true | ||
return this | ||
} | ||
|
||
// Sets the event to repeat every `time` seconds. | ||
Event.prototype.repeat = function(time) { | ||
if (time === 0) | ||
throw new Error('delay cannot be 0') | ||
this.repeatTime = time | ||
if (!this.clock._hasEvent(this)) | ||
this.schedule(this.deadline + this.repeatTime) | ||
return this | ||
} | ||
|
||
// Sets the time tolerance of the event. | ||
// The event will be executed in the interval `[deadline - early, deadline + late]` | ||
// If the clock fails to execute the event in time, the event will be dropped. | ||
Event.prototype.tolerance = function(values) { | ||
if (typeof values.late === 'number') | ||
this.toleranceLate = values.late | ||
if (typeof values.early === 'number') | ||
this.toleranceEarly = values.early | ||
this._refreshEarlyLateDates() | ||
if (this.clock._hasEvent(this)) { | ||
this.clock._removeEvent(this) | ||
this.clock._insertEvent(this) | ||
} | ||
return this | ||
} | ||
|
||
// Returns true if the event is repeated, false otherwise | ||
Event.prototype.isRepeated = function() { return this.repeatTime !== null } | ||
|
||
// Schedules the event to be ran before `deadline`. | ||
// If the time is within the event tolerance, we handle the event immediately. | ||
// If the event was already scheduled at a different time, it is rescheduled. | ||
Event.prototype.schedule = function(deadline) { | ||
this._cleared = false | ||
this.deadline = deadline | ||
this._refreshEarlyLateDates() | ||
|
||
if (this.clock.context.currentTime >= this._earliestTime) { | ||
this._execute() | ||
|
||
} else if (this.clock._hasEvent(this)) { | ||
this.clock._removeEvent(this) | ||
this.clock._insertEvent(this) | ||
|
||
} else this.clock._insertEvent(this) | ||
} | ||
|
||
Event.prototype.timeStretch = function(tRef, ratio) { | ||
if (this.isRepeated()) | ||
this.repeatTime = this.repeatTime * ratio | ||
|
||
var deadline = tRef + ratio * (this.deadline - tRef) | ||
// If the deadline is too close or past, and the event has a repeat, | ||
// we calculate the next repeat possible in the stretched space. | ||
if (this.isRepeated()) { | ||
while (this.clock.context.currentTime >= deadline - this.toleranceEarly) | ||
deadline += this.repeatTime | ||
} | ||
this.schedule(deadline) | ||
} | ||
|
||
// Executes the event | ||
Event.prototype._execute = function() { | ||
if (this.clock._started === false) return | ||
this.clock._removeEvent(this) | ||
|
||
if (this.clock.context.currentTime < this._latestTime) | ||
this.func(this) | ||
else { | ||
if (this.onexpired) this.onexpired(this) | ||
console.warn('event expired') | ||
} | ||
// In the case `schedule` is called inside `func`, we need to avoid | ||
// overrwriting with yet another `schedule`. | ||
if (!this.clock._hasEvent(this) && this.isRepeated() && !this._cleared) | ||
this.schedule(this.deadline + this.repeatTime) | ||
} | ||
|
||
// Updates cached times | ||
Event.prototype._refreshEarlyLateDates = function() { | ||
this._latestTime = this.deadline + this.toleranceLate | ||
this._earliestTime = this.deadline - this.toleranceEarly | ||
} | ||
|
||
// ==================== WAAClock ==================== // | ||
var WAAClock = module.exports = function(context, opts) { | ||
var self = this | ||
opts = opts || {} | ||
this.tickMethod = opts.tickMethod || 'ScriptProcessorNode' | ||
this.toleranceEarly = opts.toleranceEarly || CLOCK_DEFAULTS.toleranceEarly | ||
this.toleranceLate = opts.toleranceLate || CLOCK_DEFAULTS.toleranceLate | ||
this.context = context | ||
this._events = [] | ||
this._started = false | ||
} | ||
|
||
// ---------- Public API ---------- // | ||
// Schedules `func` to run after `delay` seconds. | ||
WAAClock.prototype.setTimeout = function(func, delay) { | ||
return this._createEvent(func, this._absTime(delay)) | ||
} | ||
|
||
// Schedules `func` to run before `deadline`. | ||
WAAClock.prototype.callbackAtTime = function(func, deadline) { | ||
return this._createEvent(func, deadline) | ||
} | ||
|
||
// Stretches `deadline` and `repeat` of all scheduled `events` by `ratio`, keeping | ||
// their relative distance to `tRef`. In fact this is equivalent to changing the tempo. | ||
WAAClock.prototype.timeStretch = function(tRef, events, ratio) { | ||
events.forEach(function(event) { event.timeStretch(tRef, ratio) }) | ||
return events | ||
} | ||
|
||
// Removes all scheduled events and starts the clock | ||
WAAClock.prototype.start = function() { | ||
if (this._started === false) { | ||
var self = this | ||
this._started = true | ||
this._events = [] | ||
|
||
if (this.tickMethod === 'ScriptProcessorNode') { | ||
var bufferSize = 256 | ||
// We have to keep a reference to the node to avoid garbage collection | ||
this._clockNode = this.context.createScriptProcessor(bufferSize, 1, 1) | ||
this._clockNode.connect(this.context.destination) | ||
this._clockNode.onaudioprocess = function () { | ||
setTimeout(function() { self._tick() }, 0) | ||
} | ||
} else if (this.tickMethod === 'manual') null // _tick is called manually | ||
|
||
else throw new Error('invalid tickMethod ' + this.tickMethod) | ||
} | ||
} | ||
|
||
// Stops the clock | ||
WAAClock.prototype.stop = function() { | ||
if (this._started === true) { | ||
this._started = false | ||
this._clockNode.disconnect() | ||
} | ||
} | ||
|
||
// ---------- Private ---------- // | ||
|
||
// This function is ran periodically, and at each tick it executes | ||
// events for which `currentTime` is included in their tolerance interval. | ||
WAAClock.prototype._tick = function() { | ||
var event = this._events.shift() | ||
|
||
while(event && event._earliestTime <= this.context.currentTime) { | ||
event._execute() | ||
event = this._events.shift() | ||
} | ||
|
||
// Put back the last event | ||
if(event) this._events.unshift(event) | ||
} | ||
|
||
// Creates an event and insert it to the list | ||
WAAClock.prototype._createEvent = function(func, deadline) { | ||
return new Event(this, deadline, func) | ||
} | ||
|
||
// Inserts an event to the list | ||
WAAClock.prototype._insertEvent = function(event) { | ||
this._events.splice(this._indexByTime(event._earliestTime), 0, event) | ||
} | ||
|
||
// Removes an event from the list | ||
WAAClock.prototype._removeEvent = function(event) { | ||
var ind = this._events.indexOf(event) | ||
if (ind !== -1) this._events.splice(ind, 1) | ||
} | ||
|
||
// Returns true if `event` is in queue, false otherwise | ||
WAAClock.prototype._hasEvent = function(event) { | ||
return this._events.indexOf(event) !== -1 | ||
} | ||
|
||
// Returns the index of the first event whose deadline is >= to `deadline` | ||
WAAClock.prototype._indexByTime = function(deadline) { | ||
// performs a binary search | ||
var low = 0 | ||
, high = this._events.length | ||
, mid | ||
while (low < high) { | ||
mid = Math.floor((low + high) / 2) | ||
if (this._events[mid]._earliestTime < deadline) | ||
low = mid + 1 | ||
else high = mid | ||
} | ||
return low | ||
} | ||
|
||
// Converts from relative time to absolute time | ||
WAAClock.prototype._absTime = function(relTime) { | ||
return relTime + this.context.currentTime | ||
} | ||
|
||
// Converts from absolute time to relative time | ||
WAAClock.prototype._relTime = function(absTime) { | ||
return absTime - this.context.currentTime | ||
} | ||
},{}]},{},[1]) | ||
; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.