@azgaar/tone
Version:
A fork of Web Audio framework for making interactive music in the browser.
178 lines (163 loc) • 4.6 kB
text/typescript
import { Tone } from "../Tone";
import { optionsFromArguments } from "../util/Defaults";
import { noOp } from "../util/Interface";
import { isString } from "../util/TypeCheck";
import { ToneMediaElement } from "./ToneMediaElement";
import { assert } from "../util/Debug";
export interface ToneMediaElementsUrlMap {
[name: string]: string;
[name: number]: string;
}
interface ToneMediaElementsOptions {
urls: ToneMediaElementsUrlMap;
onload: () => void;
onerror?: (error: Error) => void;
baseUrl: string;
}
/**
* A data structure for holding multiple MediaElements in a Map-like datastructure.
*
* @example
* const pianoMediaElementSamples = new Tone.ToneMediaElements({
* A1: "https://tonejs.github.io/audio/casio/A1.mp3",
* A2: "https://tonejs.github.io/audio/casio/A2.mp3",
* }, () => {
* const player = new Tone.Player().toDestination();
* // play one of the samples when they all load
* player.element = pianoSamples.get("A2");
* player.start();
* });
* @example
* // To pass in additional parameters in the second parameter
* const elements = new Tone.ToneMediaElements({
* urls: {
* A1: "A1.mp3",
* A2: "A2.mp3",
* },
* onload: () => console.log("loaded"),
* baseUrl: "https://tonejs.github.io/audio/casio/"
* });
* @category Core
*/
export class ToneMediaElements extends Tone {
readonly name: string = "ToneMediaElements";
/**
* All of the elements
*/
private _elements: Map<string, ToneMediaElement> = new Map();
/**
* A path which is prefixed before every url.
*/
baseUrl: string;
/**
* Keep track of the number of loaded elements
*/
private _loadingCount = 0;
/**
* @param urls An object literal or array of urls to load.
* @param onload The callback to invoke when the elements are loaded.
* @param baseUrl A prefix url to add before all the urls
*/
constructor(
urls?: ToneMediaElementsUrlMap,
onload?: () => void,
baseUrl?: string
);
constructor(options?: Partial<ToneMediaElementsOptions>);
constructor() {
super();
const options = optionsFromArguments(
ToneMediaElements.getDefaults(),
arguments,
["urls", "onload", "baseUrl"],
"urls"
);
this.baseUrl = options.baseUrl;
// add each one
Object.keys(options.urls).forEach((name) => {
this._loadingCount++;
const url = options.urls[name];
this.add(
name,
url,
this._elementLoaded.bind(this, options.onload),
options.onerror
);
});
}
static getDefaults(): ToneMediaElementsOptions {
return {
baseUrl: "",
onerror: noOp,
onload: noOp,
urls: {},
};
}
/**
* True if the elements object has a element by that name.
* @param name The key or index of the element.
*/
has(name: string | number): boolean {
return this._elements.has(name.toString());
}
/**
* Get a element by name. If an array was loaded,
* then use the array index.
* @param name The key or index of the element.
*/
get(name: string | number): ToneMediaElement {
assert(
this.has(name),
`ToneMediaElements has no element named: ${name}`
);
return this._elements.get(name.toString()) as ToneMediaElement;
}
/**
* A element was loaded. decrement the counter.
*/
private _elementLoaded(callback: () => void): void {
this._loadingCount--;
if (this._loadingCount === 0 && callback) {
callback();
}
}
/**
* Add a element by name and url to the elements
* @param name A unique name to give the element
* @param url Either the url of the bufer, or a element which will be added with the given name.
* @param callback The callback to invoke when the url is loaded.
* @param onerror Invoked if the element can't be loaded
*/
add(
name: string | number,
url: string,
callback: () => void = noOp,
onerror: (e: Error) => void = noOp
): this {
if (isString(url)) {
// don't include the baseUrl if the url is a base64 encoded sound
if (
this.baseUrl &&
url.trim().substring(0, 11).toLowerCase() === "data:audio/"
) {
this.baseUrl = "";
}
this._elements.set(
name.toString(),
new ToneMediaElement(this.baseUrl + url, callback, onerror)
);
} else {
this._elements.set(
name.toString(),
new ToneMediaElement(url, callback, onerror)
);
}
return this;
}
dispose(): this {
super.dispose();
this._elements.forEach((element) => element.dispose());
this._elements.clear();
return this;
}
}