Tone.js/Tone/source/buffer/ToneBufferSource.ts

254 lines
6.7 KiB
TypeScript
Raw Normal View History

import { connect } from "../../core/context/ToneAudioNode";
2019-06-18 01:52:23 +00:00
import { Param } from "../../core/context/Param";
import { ToneAudioBuffer } from "../../core/context/ToneAudioBuffer";
import { GainFactor, Positive, Seconds, Time } from "../../core/type/Units";
2019-06-18 01:52:23 +00:00
import { defaultArg, optionsFromArguments } from "../../core/util/Defaults";
2019-07-11 13:57:06 +00:00
import { noOp } from "../../core/util/Interface";
import { isDefined } from "../../core/util/TypeCheck";
import { assert } from "../../core/util/Debug";
2019-07-22 20:17:49 +00:00
import { OneShotSource, OneShotSourceCurve, OneShotSourceOptions } from "../OneShotSource";
import { EQ, GTE, LT } from "../../core/util/Math";
2019-07-22 20:17:49 +00:00
export type ToneBufferSourceCurve = OneShotSourceCurve;
2019-06-18 01:52:23 +00:00
2019-09-04 22:34:42 +00:00
export interface ToneBufferSourceOptions extends OneShotSourceOptions {
url: string | AudioBuffer | ToneAudioBuffer;
2019-07-22 20:17:49 +00:00
curve: ToneBufferSourceCurve;
2019-06-18 01:52:23 +00:00
playbackRate: Positive;
fadeIn: Time;
fadeOut: Time;
loopStart: Time;
loopEnd: Time;
loop: boolean;
onload: () => void;
onerror: (error: Error) => void;
2019-06-18 01:52:23 +00:00
}
/**
2019-09-14 20:39:18 +00:00
* Wrapper around the native BufferSourceNode.
2019-09-16 14:15:23 +00:00
* @category Source
2019-06-18 01:52:23 +00:00
*/
export class ToneBufferSource extends OneShotSource<ToneBufferSourceOptions> {
2019-09-04 23:18:44 +00:00
readonly name: string = "ToneBufferSource";
2019-06-18 01:52:23 +00:00
/**
2019-09-14 20:39:18 +00:00
* The oscillator
2019-06-18 01:52:23 +00:00
*/
private _source = this.context.createBufferSource();
protected _internalChannels = [this._source];
2019-06-18 01:52:23 +00:00
/**
2019-09-14 20:39:18 +00:00
* The frequency of the oscillator
2019-06-18 01:52:23 +00:00
*/
readonly playbackRate: Param<"positive">;
2019-06-18 01:52:23 +00:00
/**
* The private instance of the buffer object
*/
private _buffer: ToneAudioBuffer;
/**
* indicators if the source has started/stopped
*/
2019-11-17 18:09:19 +00:00
private _sourceStarted = false;
private _sourceStopped = false;
2019-06-18 01:52:23 +00:00
2019-08-27 15:47:52 +00:00
/**
* @param url The buffer to play or url to load
2019-08-30 16:03:42 +00:00
* @param onload The callback to invoke when the buffer is done playing.
2019-08-27 15:47:52 +00:00
*/
constructor(url?: ToneAudioBuffer | AudioBuffer | string, onload?: () => void);
2019-07-22 20:17:49 +00:00
constructor(options?: Partial<ToneBufferSourceOptions>);
2019-06-18 01:52:23 +00:00
constructor() {
super(optionsFromArguments(ToneBufferSource.getDefaults(), arguments, ["url", "onload"]));
const options = optionsFromArguments(ToneBufferSource.getDefaults(), arguments, ["url", "onload"]);
2019-06-18 01:52:23 +00:00
connect(this._source, this._gainNode);
this._source.onended = () => this._stopSource();
2019-06-18 01:52:23 +00:00
/**
2019-09-14 20:39:18 +00:00
* The playbackRate of the buffer
2019-06-18 01:52:23 +00:00
*/
this.playbackRate = new Param({
context: this.context,
2019-09-16 03:32:40 +00:00
param: this._source.playbackRate,
units: "positive",
value: options.playbackRate,
2019-06-18 01:52:23 +00:00
});
// set some values initially
this.loop = options.loop;
this.loopStart = options.loopStart;
this.loopEnd = options.loopEnd;
this._buffer = new ToneAudioBuffer(options.url, options.onload, options.onerror);
this._internalChannels.push(this._source);
2019-06-18 01:52:23 +00:00
}
static getDefaults(): ToneBufferSourceOptions {
return Object.assign(OneShotSource.getDefaults(), {
url: new ToneAudioBuffer(),
2019-06-18 01:52:23 +00:00
loop: false,
2019-09-16 03:32:40 +00:00
loopEnd: 0,
loopStart: 0,
2019-06-18 01:52:23 +00:00
onload: noOp,
onerror: noOp,
2019-09-16 03:32:40 +00:00
playbackRate: 1,
2019-06-18 01:52:23 +00:00
});
}
/**
2019-09-14 20:39:18 +00:00
* The fadeIn time of the amplitude envelope.
2019-06-18 01:52:23 +00:00
*/
get fadeIn(): Time {
return this._fadeIn;
}
set fadeIn(t: Time) {
this._fadeIn = t;
}
/**
2019-09-14 20:39:18 +00:00
* The fadeOut time of the amplitude envelope.
2019-06-18 01:52:23 +00:00
*/
get fadeOut(): Time {
return this._fadeOut;
}
set fadeOut(t: Time) {
this._fadeOut = t;
}
/**
* The curve applied to the fades, either "linear" or "exponential"
*/
2019-07-22 20:17:49 +00:00
get curve(): ToneBufferSourceCurve {
2019-06-18 01:52:23 +00:00
return this._curve;
}
2019-07-22 20:17:49 +00:00
set curve(t) {
2019-06-18 01:52:23 +00:00
this._curve = t;
}
/**
2019-09-14 20:39:18 +00:00
* Start the buffer
* @param time When the player should start.
* @param offset The offset from the beginning of the sample to start at.
2019-10-23 03:04:52 +00:00
* @param duration How long the sample should play. If no duration is given, it will default to the full length of the sample (minus any offset)
* @param gain The gain to play the buffer back at.
2019-06-18 01:52:23 +00:00
*/
2019-06-19 21:20:20 +00:00
start(time?: Time, offset?: Time, duration?: Time, gain: GainFactor = 1): this {
assert(this.buffer.loaded, "buffer is either not set or not loaded");
const computedTime = this.toSeconds(time);
2019-06-18 01:52:23 +00:00
// apply the gain envelope
this._startGain(computedTime, gain);
2019-06-18 01:52:23 +00:00
// if it's a loop the default offset is the loopstart point
if (this.loop) {
offset = defaultArg(offset, this.loopStart);
} else {
// otherwise the default offset is 0
offset = defaultArg(offset, 0);
}
// make sure the offset is not less than 0
let computedOffset = Math.max(this.toSeconds(offset), 0);
2019-06-18 01:52:23 +00:00
// start the buffer source
if (this.loop) {
// modify the offset if it's greater than the loop time
const loopEnd = this.toSeconds(this.loopEnd) || this.buffer.duration;
const loopStart = this.toSeconds(this.loopStart);
const loopDuration = loopEnd - loopStart;
// move the offset back
if (GTE(computedOffset, loopEnd)) {
computedOffset = ((computedOffset - loopStart) % loopDuration) + loopStart;
2019-06-18 01:52:23 +00:00
}
// when the offset is very close to the duration, set it to 0
if (EQ(computedOffset, this.buffer.duration)) {
computedOffset = 0;
}
2019-06-18 01:52:23 +00:00
}
2019-06-18 01:52:23 +00:00
// this.buffer.loaded would have return false if the AudioBuffer was undefined
this._source.buffer = this.buffer.get() as AudioBuffer;
this._source.loopEnd = this.toSeconds(this.loopEnd) || this.buffer.duration;
if (LT(computedOffset, this.buffer.duration)) {
2019-06-18 01:52:23 +00:00
this._sourceStarted = true;
this._source.start(computedTime, computedOffset);
2019-06-18 01:52:23 +00:00
}
// if a duration is given, schedule a stop
if (isDefined(duration)) {
let computedDur = this.toSeconds(duration);
// make sure it's never negative
computedDur = Math.max(computedDur, 0);
this.stop(computedTime + computedDur);
2019-06-18 01:52:23 +00:00
}
return this;
}
protected _stopSource(time?: Seconds): void {
if (!this._sourceStopped && this._sourceStarted) {
2019-06-18 01:52:23 +00:00
this._sourceStopped = true;
this._source.stop(this.toSeconds(time));
this._onended();
2019-06-18 01:52:23 +00:00
}
}
/**
* If loop is true, the loop will start at this position.
*/
get loopStart(): Time {
return this._source.loopStart;
}
set loopStart(loopStart: Time) {
this._source.loopStart = this.toSeconds(loopStart);
}
/**
* If loop is true, the loop will end at this position.
*/
get loopEnd(): Time {
return this._source.loopEnd;
}
set loopEnd(loopEnd: Time) {
this._source.loopEnd = this.toSeconds(loopEnd);
}
/**
* The audio buffer belonging to the player.
*/
get buffer(): ToneAudioBuffer {
return this._buffer;
}
set buffer(buffer: ToneAudioBuffer) {
this._buffer.set(buffer);
}
/**
* If the buffer should loop once it's over.
*/
get loop(): boolean {
return this._source.loop;
}
set loop(loop: boolean) {
this._source.loop = loop;
if (this._sourceStarted) {
this.cancelStop();
}
}
/**
2019-09-14 20:39:18 +00:00
* Clean up.
2019-06-18 01:52:23 +00:00
*/
dispose(): this {
super.dispose();
this._source.onended = null;
this._source.disconnect();
this._buffer.dispose();
this.playbackRate.dispose();
return this;
}
}