Tone.js/Tone/source/buffer/Players.ts

230 lines
5.7 KiB
TypeScript
Raw Normal View History

2019-09-10 03:39:32 +00:00
import { Volume } from "../../component/channel/Volume";
import { Param } from "../../core/context/Param";
import { ToneAudioBuffer } from "../../core/context/ToneAudioBuffer";
import { ToneAudioBuffers, ToneAudioBuffersUrlMap } from "../../core/context/ToneAudioBuffers";
import { OutputNode, ToneAudioNode } from "../../core/context/ToneAudioNode";
import { Decibels, Time } from "../../core/type/Units";
import { optionsFromArguments } from "../../core/util/Defaults";
import { assert } from "../../core/util/Debug";
2019-09-10 03:39:32 +00:00
import { noOp, readOnly } from "../../core/util/Interface";
import { BasicPlaybackState } from "../../core/util/StateTimeline";
import { Source, SourceOptions } from "../Source";
import { Player } from "./Player";
export interface PlayersOptions extends SourceOptions {
urls: ToneAudioBuffersUrlMap;
volume: Decibels;
mute: boolean;
onload: () => void;
onerror: (error: Error) => void;
2019-09-10 03:39:32 +00:00
baseUrl: string;
fadeIn: Time;
fadeOut: Time;
}
/**
* Players combines multiple [[Player]] objects.
2019-09-16 14:15:23 +00:00
* @category Source
2019-09-10 03:39:32 +00:00
*/
export class Players extends ToneAudioNode<PlayersOptions> {
readonly name: string = "Players";
/**
2019-09-14 20:39:18 +00:00
* The output volume node
2019-09-10 03:39:32 +00:00
*/
private _volume: Volume;
/**
* The volume of the output in decibels.
*/
readonly volume: Param<"decibels">;
2019-09-10 03:39:32 +00:00
/**
* The combined output of all of the players
*/
readonly output: OutputNode;
/**
* Players has no input.
*/
readonly input = undefined;
/**
* The container of all of the players
*/
private _players: Map<string, Player> = new Map();
/**
* The container of all the buffers
*/
private _buffers: ToneAudioBuffers;
/**
* private holder of the fadeIn time
*/
private _fadeIn: Time;
/**
* private holder of the fadeOut time
*/
private _fadeOut: Time;
/**
* @param urls An object mapping a name to a url.
* @param onload The function to invoke when all buffers are loaded.
*/
constructor(urls?: ToneAudioBuffersUrlMap, onload?: () => void);
/**
* @param urls An object mapping a name to a url.
* @param options The remaining options associated with the players
*/
constructor(urls?: ToneAudioBuffersUrlMap, options?: Partial<Omit<PlayersOptions, "urls">>);
2019-11-17 00:07:47 +00:00
constructor(options?: Partial<PlayersOptions>);
2019-09-10 03:39:32 +00:00
constructor() {
super(optionsFromArguments(Players.getDefaults(), arguments, ["urls", "onload"], "urls"));
const options = optionsFromArguments(Players.getDefaults(), arguments, ["urls", "onload"], "urls");
/**
2019-09-14 20:39:18 +00:00
* The output volume node
2019-09-10 03:39:32 +00:00
*/
this._volume = this.output = new Volume({
context: this.context,
volume: options.volume,
});
this.volume = this._volume.volume;
readOnly(this, "volume");
this._buffers = new ToneAudioBuffers({
urls: options.urls,
onload: options.onload,
baseUrl: options.baseUrl,
onerror: options.onerror
});
2019-09-10 03:39:32 +00:00
// mute initially
this.mute = options.mute;
this._fadeIn = options.fadeIn;
this._fadeOut = options.fadeOut;
}
static getDefaults(): PlayersOptions {
return Object.assign(Source.getDefaults(), {
baseUrl: "",
fadeIn: 0,
fadeOut: 0,
mute: false,
onload: noOp,
onerror: noOp,
2019-09-10 03:39:32 +00:00
urls: {},
volume: 0,
});
}
/**
* Mute the output.
*/
get mute(): boolean {
return this._volume.mute;
}
set mute(mute) {
this._volume.mute = mute;
}
/**
* The fadeIn time of the envelope applied to the source.
*/
get fadeIn(): Time {
return this._fadeIn;
}
set fadeIn(fadeIn) {
this._fadeIn = fadeIn;
this._players.forEach(player => {
player.fadeIn = fadeIn;
});
}
/**
* The fadeOut time of the each of the sources.
*/
get fadeOut(): Time {
return this._fadeOut;
}
set fadeOut(fadeOut) {
this._fadeOut = fadeOut;
this._players.forEach(player => {
player.fadeOut = fadeOut;
});
}
/**
* The state of the players object. Returns "started" if any of the players are playing.
*/
get state(): BasicPlaybackState {
const playing = Array.from(this._players).some(([_, player]) => player.state === "started");
return playing ? "started" : "stopped";
}
/**
2019-09-14 20:39:18 +00:00
* True if the buffers object has a buffer by that name.
* @param name The key or index of the buffer.
2019-09-10 03:39:32 +00:00
*/
has(name: string): boolean {
return this._buffers.has(name);
}
/**
2019-09-14 20:39:18 +00:00
* Get a player by name.
* @param name The players name as defined in the constructor object or `add` method.
2019-09-10 03:39:32 +00:00
*/
player(name: string): Player {
assert(this.has(name), `No Player with the name ${name} exists on this object`);
2019-09-10 03:39:32 +00:00
if (!this._players.has(name)) {
const player = new Player({
context: this.context,
fadeIn: this._fadeIn,
fadeOut: this._fadeOut,
url: this._buffers.get(name),
}).connect(this.output);
this._players.set(name, player);
}
return this._players.get(name) as Player;
}
/**
* If all the buffers are loaded or not
*/
get loaded(): boolean {
return this._buffers.loaded;
}
/**
2019-09-14 20:39:18 +00:00
* Add a player by name and url to the Players
* @param name A unique name to give the player
* @param url Either the url of the bufer or a buffer which will be added with the given name.
* @param callback The callback to invoke when the url is loaded.
2019-09-10 03:39:32 +00:00
*/
add(name: string, url: string | ToneAudioBuffer | AudioBuffer, callback?: () => void): this {
assert(!this._buffers.has(name), "A buffer with that name already exists on this object");
2019-09-10 03:39:32 +00:00
this._buffers.add(name, url, callback);
return this;
}
/**
* Stop all of the players at the given time
* @param time The time to stop all of the players.
*/
stopAll(time?: Time): this {
this._players.forEach(player => player.stop(time));
return this;
}
dispose(): this {
super.dispose();
this._volume.dispose();
this.volume.dispose();
this._players.forEach(player => player.dispose());
this._buffers.dispose();
return this;
}
}