UNPKG

tone

Version:

A Web Audio framework for making interactive music in the browser.

569 lines 18.1 kB
import * as tslib_1 from "tslib"; import { Tone } from "../Tone"; import { isDefined } from "./TypeCheck"; import { assert } from "./Debug"; /** * Similar to Tone.Timeline, but all events represent * intervals with both "time" and "duration" times. The * events are placed in a tree structure optimized * for querying an intersection point with the timeline * events. Internally uses an [Interval Tree](https://en.wikipedia.org/wiki/Interval_tree) * to represent the data. */ var IntervalTimeline = /** @class */ (function (_super) { tslib_1.__extends(IntervalTimeline, _super); function IntervalTimeline() { var _this = _super !== null && _super.apply(this, arguments) || this; _this.name = "IntervalTimeline"; /** * The root node of the inteval tree */ _this._root = null; /** * Keep track of the length of the timeline. */ _this._length = 0; return _this; } /** * The event to add to the timeline. All events must * have a time and duration value * @param event The event to add to the timeline */ IntervalTimeline.prototype.add = function (event) { assert(isDefined(event.time), "Events must have a time property"); assert(isDefined(event.duration), "Events must have a duration parameter"); event.time = event.time.valueOf(); var node = new IntervalNode(event.time, event.time + event.duration, event); if (this._root === null) { this._root = node; } else { this._root.insert(node); } this._length++; // Restructure tree to be balanced while (node !== null) { node.updateHeight(); node.updateMax(); this._rebalance(node); node = node.parent; } return this; }; /** * Remove an event from the timeline. * @param event The event to remove from the timeline */ IntervalTimeline.prototype.remove = function (event) { var e_1, _a; if (this._root !== null) { var results = []; this._root.search(event.time, results); try { for (var results_1 = tslib_1.__values(results), results_1_1 = results_1.next(); !results_1_1.done; results_1_1 = results_1.next()) { var node = results_1_1.value; if (node.event === event) { this._removeNode(node); this._length--; break; } } } catch (e_1_1) { e_1 = { error: e_1_1 }; } finally { try { if (results_1_1 && !results_1_1.done && (_a = results_1.return)) _a.call(results_1); } finally { if (e_1) throw e_1.error; } } } return this; }; Object.defineProperty(IntervalTimeline.prototype, "length", { /** * The number of items in the timeline. * @readOnly */ get: function () { return this._length; }, enumerable: true, configurable: true }); /** * Remove events whose time time is after the given time * @param after The time to query. */ IntervalTimeline.prototype.cancel = function (after) { var _this = this; this.forEachFrom(after, function (event) { return _this.remove(event); }); return this; }; /** * Set the root node as the given node */ IntervalTimeline.prototype._setRoot = function (node) { this._root = node; if (this._root !== null) { this._root.parent = null; } }; /** * Replace the references to the node in the node's parent * with the replacement node. */ IntervalTimeline.prototype._replaceNodeInParent = function (node, replacement) { if (node.parent !== null) { if (node.isLeftChild()) { node.parent.left = replacement; } else { node.parent.right = replacement; } this._rebalance(node.parent); } else { this._setRoot(replacement); } }; /** * Remove the node from the tree and replace it with * a successor which follows the schema. */ IntervalTimeline.prototype._removeNode = function (node) { if (node.left === null && node.right === null) { this._replaceNodeInParent(node, null); } else if (node.right === null) { this._replaceNodeInParent(node, node.left); } else if (node.left === null) { this._replaceNodeInParent(node, node.right); } else { var balance = node.getBalance(); var replacement = void 0; var temp = null; if (balance > 0) { if (node.left.right === null) { replacement = node.left; replacement.right = node.right; temp = replacement; } else { replacement = node.left.right; while (replacement.right !== null) { replacement = replacement.right; } if (replacement.parent) { replacement.parent.right = replacement.left; temp = replacement.parent; replacement.left = node.left; replacement.right = node.right; } } } else if (node.right.left === null) { replacement = node.right; replacement.left = node.left; temp = replacement; } else { replacement = node.right.left; while (replacement.left !== null) { replacement = replacement.left; } if (replacement.parent) { replacement.parent.left = replacement.right; temp = replacement.parent; replacement.left = node.left; replacement.right = node.right; } } if (node.parent !== null) { if (node.isLeftChild()) { node.parent.left = replacement; } else { node.parent.right = replacement; } } else { this._setRoot(replacement); } if (temp) { this._rebalance(temp); } } node.dispose(); }; /** * Rotate the tree to the left */ IntervalTimeline.prototype._rotateLeft = function (node) { var parent = node.parent; var isLeftChild = node.isLeftChild(); // Make node.right the new root of this sub tree (instead of node) var pivotNode = node.right; if (pivotNode) { node.right = pivotNode.left; pivotNode.left = node; } if (parent !== null) { if (isLeftChild) { parent.left = pivotNode; } else { parent.right = pivotNode; } } else { this._setRoot(pivotNode); } }; /** * Rotate the tree to the right */ IntervalTimeline.prototype._rotateRight = function (node) { var parent = node.parent; var isLeftChild = node.isLeftChild(); // Make node.left the new root of this sub tree (instead of node) var pivotNode = node.left; if (pivotNode) { node.left = pivotNode.right; pivotNode.right = node; } if (parent !== null) { if (isLeftChild) { parent.left = pivotNode; } else { parent.right = pivotNode; } } else { this._setRoot(pivotNode); } }; /** * Balance the BST */ IntervalTimeline.prototype._rebalance = function (node) { var balance = node.getBalance(); if (balance > 1 && node.left) { if (node.left.getBalance() < 0) { this._rotateLeft(node.left); } else { this._rotateRight(node); } } else if (balance < -1 && node.right) { if (node.right.getBalance() > 0) { this._rotateRight(node.right); } else { this._rotateLeft(node); } } }; /** * Get an event whose time and duration span the give time. Will * return the match whose "time" value is closest to the given time. * @return The event which spans the desired time */ IntervalTimeline.prototype.get = function (time) { if (this._root !== null) { var results = []; this._root.search(time, results); if (results.length > 0) { var max = results[0]; for (var i = 1; i < results.length; i++) { if (results[i].low > max.low) { max = results[i]; } } return max.event; } } return null; }; /** * Iterate over everything in the timeline. * @param callback The callback to invoke with every item */ IntervalTimeline.prototype.forEach = function (callback) { if (this._root !== null) { var allNodes_1 = []; this._root.traverse(function (node) { return allNodes_1.push(node); }); allNodes_1.forEach(function (node) { if (node.event) { callback(node.event); } }); } return this; }; /** * Iterate over everything in the array in which the given time * overlaps with the time and duration time of the event. * @param time The time to check if items are overlapping * @param callback The callback to invoke with every item */ IntervalTimeline.prototype.forEachAtTime = function (time, callback) { if (this._root !== null) { var results = []; this._root.search(time, results); results.forEach(function (node) { if (node.event) { callback(node.event); } }); } return this; }; /** * Iterate over everything in the array in which the time is greater * than or equal to the given time. * @param time The time to check if items are before * @param callback The callback to invoke with every item */ IntervalTimeline.prototype.forEachFrom = function (time, callback) { if (this._root !== null) { var results = []; this._root.searchAfter(time, results); results.forEach(function (node) { if (node.event) { callback(node.event); } }); } return this; }; /** * Clean up */ IntervalTimeline.prototype.dispose = function () { _super.prototype.dispose.call(this); if (this._root !== null) { this._root.traverse(function (node) { return node.dispose(); }); } this._root = null; return this; }; return IntervalTimeline; }(Tone)); export { IntervalTimeline }; //------------------------------------- // INTERVAL NODE HELPER //------------------------------------- /** * Represents a node in the binary search tree, with the addition * of a "high" value which keeps track of the highest value of * its children. * References: * https://brooknovak.wordpress.com/2013/12/07/augmented-interval-tree-in-c/ * http://www.mif.vu.lt/~valdas/ALGORITMAI/LITERATURA/Cormen/Cormen.pdf * @param low * @param high */ var IntervalNode = /** @class */ (function () { function IntervalNode(low, high, event) { // the nodes to the left this._left = null; // the nodes to the right this._right = null; // the parent node this.parent = null; // the number of child nodes this.height = 0; this.event = event; // the low value this.low = low; // the high value this.high = high; // the high value for this and all child nodes this.max = this.high; } /** * Insert a node into the correct spot in the tree */ IntervalNode.prototype.insert = function (node) { if (node.low <= this.low) { if (this.left === null) { this.left = node; } else { this.left.insert(node); } } else if (this.right === null) { this.right = node; } else { this.right.insert(node); } }; /** * Search the tree for nodes which overlap * with the given point * @param point The point to query * @param results The array to put the results */ IntervalNode.prototype.search = function (point, results) { // If p is to the right of the rightmost point of any interval // in this node and all children, there won't be any matches. if (point > this.max) { return; } // Search left children if (this.left !== null) { this.left.search(point, results); } // Check this node if (this.low <= point && this.high > point) { results.push(this); } // If p is to the left of the time of this interval, // then it can't be in any child to the right. if (this.low > point) { return; } // Search right children if (this.right !== null) { this.right.search(point, results); } }; /** * Search the tree for nodes which are less * than the given point * @param point The point to query * @param results The array to put the results */ IntervalNode.prototype.searchAfter = function (point, results) { // Check this node if (this.low >= point) { results.push(this); if (this.left !== null) { this.left.searchAfter(point, results); } } // search the right side if (this.right !== null) { this.right.searchAfter(point, results); } }; /** * Invoke the callback on this element and both it's branches * @param {Function} callback */ IntervalNode.prototype.traverse = function (callback) { callback(this); if (this.left !== null) { this.left.traverse(callback); } if (this.right !== null) { this.right.traverse(callback); } }; /** * Update the height of the node */ IntervalNode.prototype.updateHeight = function () { if (this.left !== null && this.right !== null) { this.height = Math.max(this.left.height, this.right.height) + 1; } else if (this.right !== null) { this.height = this.right.height + 1; } else if (this.left !== null) { this.height = this.left.height + 1; } else { this.height = 0; } }; /** * Update the height of the node */ IntervalNode.prototype.updateMax = function () { this.max = this.high; if (this.left !== null) { this.max = Math.max(this.max, this.left.max); } if (this.right !== null) { this.max = Math.max(this.max, this.right.max); } }; /** * The balance is how the leafs are distributed on the node * @return Negative numbers are balanced to the right */ IntervalNode.prototype.getBalance = function () { var balance = 0; if (this.left !== null && this.right !== null) { balance = this.left.height - this.right.height; } else if (this.left !== null) { balance = this.left.height + 1; } else if (this.right !== null) { balance = -(this.right.height + 1); } return balance; }; /** * @returns true if this node is the left child of its parent */ IntervalNode.prototype.isLeftChild = function () { return this.parent !== null && this.parent.left === this; }; Object.defineProperty(IntervalNode.prototype, "left", { /** * get/set the left node */ get: function () { return this._left; }, set: function (node) { this._left = node; if (node !== null) { node.parent = this; } this.updateHeight(); this.updateMax(); }, enumerable: true, configurable: true }); Object.defineProperty(IntervalNode.prototype, "right", { /** * get/set the right node */ get: function () { return this._right; }, set: function (node) { this._right = node; if (node !== null) { node.parent = this; } this.updateHeight(); this.updateMax(); }, enumerable: true, configurable: true }); /** * null out references. */ IntervalNode.prototype.dispose = function () { this.parent = null; this._left = null; this._right = null; this.event = null; }; return IntervalNode; }()); //# sourceMappingURL=IntervalTimeline.js.map