mirror of
https://github.com/Tonejs/Tone.js
synced 2025-01-13 04:18:52 +00:00
Don't reschedule source when offset is very small
This offset is due to floating point error Fixes #999 Fixes #944
This commit is contained in:
parent
aeaaa1e871
commit
444d6179c4
3 changed files with 182 additions and 76 deletions
|
@ -2,11 +2,19 @@ import { Volume } from "../component/channel/Volume";
|
|||
import "../core/context/Destination";
|
||||
import "../core/clock/Transport";
|
||||
import { Param } from "../core/context/Param";
|
||||
import { OutputNode, ToneAudioNode, ToneAudioNodeOptions } from "../core/context/ToneAudioNode";
|
||||
import {
|
||||
OutputNode,
|
||||
ToneAudioNode,
|
||||
ToneAudioNodeOptions,
|
||||
} from "../core/context/ToneAudioNode";
|
||||
import { Decibels, Seconds, Time } from "../core/type/Units";
|
||||
import { defaultArg } from "../core/util/Defaults";
|
||||
import { noOp, readOnly } from "../core/util/Interface";
|
||||
import { BasicPlaybackState, StateTimeline, StateTimelineEvent } from "../core/util/StateTimeline";
|
||||
import {
|
||||
BasicPlaybackState,
|
||||
StateTimeline,
|
||||
StateTimelineEvent,
|
||||
} from "../core/util/StateTimeline";
|
||||
import { isDefined, isUndef } from "../core/util/TypeCheck";
|
||||
import { assert, assertContextRunning } from "../core/util/Debug";
|
||||
import { GT } from "../core/util/Math";
|
||||
|
@ -20,9 +28,9 @@ export interface SourceOptions extends ToneAudioNodeOptions {
|
|||
}
|
||||
|
||||
/**
|
||||
* Base class for sources.
|
||||
* Base class for sources.
|
||||
* start/stop of this.context.transport.
|
||||
*
|
||||
*
|
||||
* ```
|
||||
* // Multiple state change events can be chained together,
|
||||
* // but must be set in the correct order and with ascending times
|
||||
|
@ -36,8 +44,9 @@ export interface SourceOptions extends ToneAudioNodeOptions {
|
|||
* state.start("+0.3").stop("+0.2");
|
||||
* ```
|
||||
*/
|
||||
export abstract class Source<Options extends SourceOptions> extends ToneAudioNode<Options> {
|
||||
|
||||
export abstract class Source<
|
||||
Options extends SourceOptions
|
||||
> extends ToneAudioNode<Options> {
|
||||
/**
|
||||
* The output volume node
|
||||
*/
|
||||
|
@ -129,7 +138,9 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
|
|||
get state(): BasicPlaybackState {
|
||||
if (this._synced) {
|
||||
if (this.context.transport.state === "started") {
|
||||
return this._state.getValueAtTime(this.context.transport.seconds) as BasicPlaybackState;
|
||||
return this._state.getValueAtTime(
|
||||
this.context.transport.seconds
|
||||
) as BasicPlaybackState;
|
||||
} else {
|
||||
return "stopped";
|
||||
}
|
||||
|
@ -155,7 +166,11 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
|
|||
// overwrite these functions
|
||||
protected abstract _start(time: Time, offset?: Time, duration?: Time): void;
|
||||
protected abstract _stop(time: Time): void;
|
||||
protected abstract _restart(time: Seconds, offset?: Time, duration?: Time): void;
|
||||
protected abstract _restart(
|
||||
time: Seconds,
|
||||
offset?: Time,
|
||||
duration?: Time
|
||||
): void;
|
||||
|
||||
/**
|
||||
* Ensure that the scheduled time is not before the current time.
|
||||
|
@ -178,12 +193,24 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
|
|||
* source.start("+0.5"); // starts the source 0.5 seconds from now
|
||||
*/
|
||||
start(time?: Time, offset?: Time, duration?: Time): this {
|
||||
let computedTime = isUndef(time) && this._synced ? this.context.transport.seconds : this.toSeconds(time);
|
||||
let computedTime =
|
||||
isUndef(time) && this._synced
|
||||
? this.context.transport.seconds
|
||||
: this.toSeconds(time);
|
||||
computedTime = this._clampToCurrentTime(computedTime);
|
||||
// if it's started, stop it and restart it
|
||||
if (!this._synced && this._state.getValueAtTime(computedTime) === "started") {
|
||||
if (
|
||||
!this._synced &&
|
||||
this._state.getValueAtTime(computedTime) === "started"
|
||||
) {
|
||||
// time should be strictly greater than the previous start time
|
||||
assert(GT(computedTime, (this._state.get(computedTime) as StateTimelineEvent).time), "Start time must be strictly greater than previous start time");
|
||||
assert(
|
||||
GT(
|
||||
computedTime,
|
||||
(this._state.get(computedTime) as StateTimelineEvent).time
|
||||
),
|
||||
"Start time must be strictly greater than previous start time"
|
||||
);
|
||||
this._state.cancel(computedTime);
|
||||
this._state.setStateAtTime("started", computedTime);
|
||||
this.log("restart", computedTime);
|
||||
|
@ -196,18 +223,26 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
|
|||
const event = this._state.get(computedTime);
|
||||
if (event) {
|
||||
event.offset = this.toSeconds(defaultArg(offset, 0));
|
||||
event.duration = duration ? this.toSeconds(duration) : undefined;
|
||||
event.duration = duration
|
||||
? this.toSeconds(duration)
|
||||
: undefined;
|
||||
}
|
||||
const sched = this.context.transport.schedule(t => {
|
||||
const sched = this.context.transport.schedule((t) => {
|
||||
this._start(t, offset, duration);
|
||||
}, computedTime);
|
||||
this._scheduled.push(sched);
|
||||
|
||||
// if the transport is already started
|
||||
// and the time is greater than where the transport is
|
||||
if (this.context.transport.state === "started" &&
|
||||
this.context.transport.getSecondsAtTime(this.immediate()) > computedTime) {
|
||||
this._syncedStart(this.now(), this.context.transport.seconds);
|
||||
if (
|
||||
this.context.transport.state === "started" &&
|
||||
this.context.transport.getSecondsAtTime(this.immediate()) >
|
||||
computedTime
|
||||
) {
|
||||
this._syncedStart(
|
||||
this.now(),
|
||||
this.context.transport.seconds
|
||||
);
|
||||
}
|
||||
} else {
|
||||
assertContextRunning(this.context);
|
||||
|
@ -227,14 +262,23 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
|
|||
* source.stop("+0.5"); // stops the source 0.5 seconds from now
|
||||
*/
|
||||
stop(time?: Time): this {
|
||||
let computedTime = isUndef(time) && this._synced ? this.context.transport.seconds : this.toSeconds(time);
|
||||
let computedTime =
|
||||
isUndef(time) && this._synced
|
||||
? this.context.transport.seconds
|
||||
: this.toSeconds(time);
|
||||
computedTime = this._clampToCurrentTime(computedTime);
|
||||
if (this._state.getValueAtTime(computedTime) === "started" || isDefined(this._state.getNextState("started", computedTime))) {
|
||||
if (
|
||||
this._state.getValueAtTime(computedTime) === "started" ||
|
||||
isDefined(this._state.getNextState("started", computedTime))
|
||||
) {
|
||||
this.log("stop", computedTime);
|
||||
if (!this._synced) {
|
||||
this._stop(computedTime);
|
||||
} else {
|
||||
const sched = this.context.transport.schedule(this._stop.bind(this), computedTime);
|
||||
const sched = this.context.transport.schedule(
|
||||
this._stop.bind(this),
|
||||
computedTime
|
||||
);
|
||||
this._scheduled.push(sched);
|
||||
}
|
||||
this._state.cancel(computedTime);
|
||||
|
@ -274,23 +318,36 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
|
|||
if (!this._synced) {
|
||||
this._synced = true;
|
||||
this._syncedStart = (time, offset) => {
|
||||
if (offset > 0) {
|
||||
if (GT(offset, 0)) {
|
||||
// get the playback state at that time
|
||||
const stateEvent = this._state.get(offset);
|
||||
// listen for start events which may occur in the middle of the sync'ed time
|
||||
if (stateEvent && stateEvent.state === "started" && stateEvent.time !== offset) {
|
||||
if (
|
||||
stateEvent &&
|
||||
stateEvent.state === "started" &&
|
||||
stateEvent.time !== offset
|
||||
) {
|
||||
// get the offset
|
||||
const startOffset = offset - this.toSeconds(stateEvent.time);
|
||||
const startOffset =
|
||||
offset - this.toSeconds(stateEvent.time);
|
||||
let duration: number | undefined;
|
||||
if (stateEvent.duration) {
|
||||
duration = this.toSeconds(stateEvent.duration) - startOffset;
|
||||
duration =
|
||||
this.toSeconds(stateEvent.duration) -
|
||||
startOffset;
|
||||
}
|
||||
this._start(time, this.toSeconds(stateEvent.offset) + startOffset, duration);
|
||||
this._start(
|
||||
time,
|
||||
this.toSeconds(stateEvent.offset) + startOffset,
|
||||
duration
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
this._syncedStop = time => {
|
||||
const seconds = this.context.transport.getSecondsAtTime(Math.max(time - this.sampleTime, 0));
|
||||
this._syncedStop = (time) => {
|
||||
const seconds = this.context.transport.getSecondsAtTime(
|
||||
Math.max(time - this.sampleTime, 0)
|
||||
);
|
||||
if (this._state.getValueAtTime(seconds) === "started") {
|
||||
this._stop(time);
|
||||
}
|
||||
|
@ -317,7 +374,7 @@ export abstract class Source<Options extends SourceOptions> extends ToneAudioNod
|
|||
}
|
||||
this._synced = false;
|
||||
// clear all of the scheduled ids
|
||||
this._scheduled.forEach(id => this.context.transport.clear(id));
|
||||
this._scheduled.forEach((id) => this.context.transport.clear(id));
|
||||
this._scheduled = [];
|
||||
this._state.cancel(0);
|
||||
// stop it also
|
||||
|
|
|
@ -8,7 +8,6 @@ import { getContext } from "Tone/core/Global";
|
|||
import { Player } from "./Player";
|
||||
|
||||
describe("Player", () => {
|
||||
|
||||
const buffer = new ToneAudioBuffer();
|
||||
|
||||
beforeEach(() => {
|
||||
|
@ -20,15 +19,18 @@ describe("Player", () => {
|
|||
SourceTests(Player, buffer);
|
||||
|
||||
it("matches a file", () => {
|
||||
return CompareToFile(() => {
|
||||
const player = new Player(buffer).toDestination();
|
||||
player.start(0.1).stop(0.2);
|
||||
player.playbackRate = 2;
|
||||
}, "player.wav", 0.005);
|
||||
return CompareToFile(
|
||||
() => {
|
||||
const player = new Player(buffer).toDestination();
|
||||
player.start(0.1).stop(0.2);
|
||||
player.playbackRate = 2;
|
||||
},
|
||||
"player.wav",
|
||||
0.005
|
||||
);
|
||||
});
|
||||
|
||||
context("Constructor", () => {
|
||||
|
||||
it("can be constructed with a Tone.Buffer", () => {
|
||||
const player = new Player(buffer);
|
||||
expect(player.buffer.get()).to.equal(buffer.get());
|
||||
|
@ -60,7 +62,6 @@ describe("Player", () => {
|
|||
});
|
||||
|
||||
context("onstop", () => {
|
||||
|
||||
it("invokes the onstop method when the player is explicitly stopped", () => {
|
||||
let wasInvoked = false;
|
||||
return Offline(() => {
|
||||
|
@ -105,7 +106,6 @@ describe("Player", () => {
|
|||
});
|
||||
|
||||
context("Loading", () => {
|
||||
|
||||
it("loads a url which was passed in", (done) => {
|
||||
const player = new Player("./audio/sine.wav", () => {
|
||||
expect(player.loaded).to.be.true;
|
||||
|
@ -131,12 +131,12 @@ describe("Player", () => {
|
|||
|
||||
it("invokes onerror if no url", (done) => {
|
||||
const source = new Player({
|
||||
url: "./nosuchfile.wav",
|
||||
url: "./nosuchfile.wav",
|
||||
onerror(e) {
|
||||
expect(e).to.be.instanceOf(Error);
|
||||
source.dispose();
|
||||
done();
|
||||
}
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -152,11 +152,9 @@ describe("Player", () => {
|
|||
url: "./audio/sine.wav",
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
context("Reverse", () => {
|
||||
|
||||
it("can get/set reverse", () => {
|
||||
const player = new Player();
|
||||
player.reverse = true;
|
||||
|
@ -166,7 +164,9 @@ describe("Player", () => {
|
|||
|
||||
it("can be played in reverse", () => {
|
||||
const shorterBuffer = buffer.slice(0, buffer.duration / 2);
|
||||
const audioBuffer = (shorterBuffer.get() as AudioBuffer).getChannelData(0);
|
||||
const audioBuffer = (
|
||||
shorterBuffer.get() as AudioBuffer
|
||||
).getChannelData(0);
|
||||
const lastSample = audioBuffer[audioBuffer.length - 1];
|
||||
expect(lastSample).to.not.equal(0);
|
||||
return Offline(() => {
|
||||
|
@ -180,11 +180,9 @@ describe("Player", () => {
|
|||
expect(firstSample).to.equal(lastSample);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
context("Looping", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
return buffer.load("./audio/short_sine.wav");
|
||||
});
|
||||
|
@ -243,7 +241,7 @@ describe("Player", () => {
|
|||
player.toDestination();
|
||||
player.start(0);
|
||||
player.loop = true;
|
||||
}, buffer.duration * 1.5).then(buff => {
|
||||
}, buffer.duration * 1.5).then((buff) => {
|
||||
expect(buff.getRmsAtTime(0)).to.be.above(0);
|
||||
expect(buff.getRmsAtTime(buffer.duration * 0.5)).to.be.above(0);
|
||||
expect(buff.getRmsAtTime(buffer.duration)).to.be.above(0);
|
||||
|
@ -252,7 +250,8 @@ describe("Player", () => {
|
|||
});
|
||||
|
||||
it("offset is the loopStart when set to loop", () => {
|
||||
const testSample = buffer.toArray(0)[Math.floor(0.1 * getContext().sampleRate)];
|
||||
const testSample =
|
||||
buffer.toArray(0)[Math.floor(0.1 * getContext().sampleRate)];
|
||||
return Offline(() => {
|
||||
const player = new Player(buffer);
|
||||
player.loopStart = 0.1;
|
||||
|
@ -271,10 +270,10 @@ describe("Player", () => {
|
|||
player.loop = true;
|
||||
player.toDestination();
|
||||
player.start(0, 0, playDur);
|
||||
}, buffer.duration * 2).then(buff => {
|
||||
}, buffer.duration * 2).then((buff) => {
|
||||
for (let time = 0; time < buffer.duration * 2; time += 0.1) {
|
||||
const val = buff.getRmsAtTime(time);
|
||||
if (time < (playDur - 0.01)) {
|
||||
if (time < playDur - 0.01) {
|
||||
expect(val).to.be.greaterThan(0);
|
||||
} else if (time > playDur) {
|
||||
expect(val).to.equal(0);
|
||||
|
@ -286,9 +285,11 @@ describe("Player", () => {
|
|||
it("correctly compensates if the offset is greater than the loopEnd", () => {
|
||||
return Offline(() => {
|
||||
// make a ramp between 0-1
|
||||
const ramp = new Float32Array(Math.floor(getContext().sampleRate * 0.3));
|
||||
const ramp = new Float32Array(
|
||||
Math.floor(getContext().sampleRate * 0.3)
|
||||
);
|
||||
for (let i = 0; i < ramp.length; i++) {
|
||||
ramp[i] = (i / (ramp.length)) * 0.3;
|
||||
ramp[i] = (i / ramp.length) * 0.3;
|
||||
}
|
||||
const buff = ToneAudioBuffer.fromArray(ramp);
|
||||
const player = new Player(buff).toDestination();
|
||||
|
@ -306,7 +307,6 @@ describe("Player", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
context("PlaybackRate", () => {
|
||||
|
@ -344,7 +344,6 @@ describe("Player", () => {
|
|||
});
|
||||
|
||||
context("Get/Set", () => {
|
||||
|
||||
it("can be set with an options object", () => {
|
||||
const player = new Player();
|
||||
expect(player.loop).to.be.false;
|
||||
|
@ -399,13 +398,12 @@ describe("Player", () => {
|
|||
expect(player.playbackRate).to.equal(0.5);
|
||||
player.dispose();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
context("Start Scheduling", () => {
|
||||
|
||||
it("can be start with an offset", () => {
|
||||
const testSample = buffer.toArray(0)[Math.floor(0.1 * getContext().sampleRate)];
|
||||
const testSample =
|
||||
buffer.toArray(0)[Math.floor(0.1 * getContext().sampleRate)];
|
||||
return Offline(() => {
|
||||
const player = new Player(buffer.get());
|
||||
player.toDestination();
|
||||
|
@ -418,9 +416,11 @@ describe("Player", () => {
|
|||
it("is stopped and restarted when start is called twice", () => {
|
||||
return Offline(() => {
|
||||
// make a ramp between 0-1
|
||||
const ramp = new Float32Array(Math.floor(getContext().sampleRate * 0.3));
|
||||
const ramp = new Float32Array(
|
||||
Math.floor(getContext().sampleRate * 0.3)
|
||||
);
|
||||
for (let i = 0; i < ramp.length; i++) {
|
||||
ramp[i] = (i / (ramp.length - 1));
|
||||
ramp[i] = i / (ramp.length - 1);
|
||||
}
|
||||
const buff = new ToneAudioBuffer().fromArray(ramp);
|
||||
const player = new Player(buff).toDestination();
|
||||
|
@ -442,9 +442,11 @@ describe("Player", () => {
|
|||
|
||||
it("can seek to a position at the given time", () => {
|
||||
return Offline(() => {
|
||||
const ramp = new Float32Array(Math.floor(getContext().sampleRate * 0.3));
|
||||
const ramp = new Float32Array(
|
||||
Math.floor(getContext().sampleRate * 0.3)
|
||||
);
|
||||
for (let i = 0; i < ramp.length; i++) {
|
||||
ramp[i] = (i / (ramp.length)) * 0.3;
|
||||
ramp[i] = (i / ramp.length) * 0.3;
|
||||
}
|
||||
const buff = new ToneAudioBuffer().fromArray(ramp);
|
||||
const player = new Player(buff).toDestination();
|
||||
|
@ -466,7 +468,7 @@ describe("Player", () => {
|
|||
const player = new Player(buffer);
|
||||
player.toDestination();
|
||||
player.start(0).stop(0.1);
|
||||
return time => {
|
||||
return (time) => {
|
||||
whenBetween(time, 0.1, Infinity, () => {
|
||||
expect(player.state).to.equal("stopped");
|
||||
});
|
||||
|
@ -475,9 +477,13 @@ describe("Player", () => {
|
|||
});
|
||||
};
|
||||
}, 0.3).then((buff) => {
|
||||
buff.forEachBetween((sample) => {
|
||||
expect(sample).to.equal(0);
|
||||
}, 0.11, 0.15);
|
||||
buff.forEachBetween(
|
||||
(sample) => {
|
||||
expect(sample).to.equal(0);
|
||||
},
|
||||
0.11,
|
||||
0.15
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -495,7 +501,10 @@ describe("Player", () => {
|
|||
return Offline(() => {
|
||||
const player = new Player(buffer);
|
||||
player.toDestination();
|
||||
player.start(0, 0, 0.05).start(0.1, 0, 0.05).start(0.2, 0, 0.05);
|
||||
player
|
||||
.start(0, 0, 0.05)
|
||||
.start(0.1, 0, 0.05)
|
||||
.start(0.2, 0, 0.05);
|
||||
player.stop(0.1);
|
||||
}, 0.3).then((buff) => {
|
||||
expect(buff.getTimeOfLastSound()).to.be.closeTo(0.1, 0.02);
|
||||
|
@ -518,7 +527,7 @@ describe("Player", () => {
|
|||
const player = new Player(buffer);
|
||||
player.toDestination();
|
||||
player.start(0, 0, 0.1);
|
||||
return time => {
|
||||
return (time) => {
|
||||
whenBetween(time, 0.1, Infinity, () => {
|
||||
expect(player.state).to.equal("stopped");
|
||||
});
|
||||
|
@ -549,17 +558,42 @@ describe("Player", () => {
|
|||
|
||||
it("plays synced to the Transport", () => {
|
||||
return Offline(({ transport }) => {
|
||||
const player = new Player(buffer).sync().start(0).toDestination();
|
||||
const player = new Player(buffer)
|
||||
.sync()
|
||||
.start(0)
|
||||
.toDestination();
|
||||
transport.start(0);
|
||||
}, 0.05).then((buff) => {
|
||||
expect(buff.isSilent()).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
it("does not play twice when the offset is very small", () => {
|
||||
// addresses #999 and #944
|
||||
return CompareToFile(
|
||||
() => {
|
||||
const player = new Player(buffer).toDestination();
|
||||
player.sync().start(0);
|
||||
getContext().transport.bpm.value = 125;
|
||||
getContext().transport.setLoopPoints(0, "1:0:0");
|
||||
getContext().transport.loop = true;
|
||||
getContext().transport.start(0);
|
||||
},
|
||||
"playerSyncLoop.wav",
|
||||
0.01
|
||||
);
|
||||
});
|
||||
|
||||
it("offsets correctly when started by the Transport", () => {
|
||||
const testSample = buffer.toArray(0)[Math.floor(0.13125 * getContext().sampleRate)];
|
||||
const testSample =
|
||||
buffer.toArray(0)[
|
||||
Math.floor(0.13125 * getContext().sampleRate)
|
||||
];
|
||||
return Offline(({ transport }) => {
|
||||
const player = new Player(buffer).sync().start(0, 0.1).toDestination();
|
||||
const player = new Player(buffer)
|
||||
.sync()
|
||||
.start(0, 0.1)
|
||||
.toDestination();
|
||||
transport.start(0, 0.03125);
|
||||
}, 0.05).then((buff) => {
|
||||
expect(buff.toArray()[0][0]).to.equal(testSample);
|
||||
|
@ -569,9 +603,11 @@ describe("Player", () => {
|
|||
it("starts at the correct position when Transport is offset and playbackRate is not 1", () => {
|
||||
return Offline(({ transport }) => {
|
||||
// make a ramp between 0-1
|
||||
const ramp = new Float32Array(Math.floor(getContext().sampleRate * 0.3));
|
||||
const ramp = new Float32Array(
|
||||
Math.floor(getContext().sampleRate * 0.3)
|
||||
);
|
||||
for (let i = 0; i < ramp.length; i++) {
|
||||
ramp[i] = (i / (ramp.length));
|
||||
ramp[i] = i / ramp.length;
|
||||
}
|
||||
const buff = ToneAudioBuffer.fromArray(ramp);
|
||||
const player = new Player(buff).toDestination();
|
||||
|
@ -586,9 +622,11 @@ describe("Player", () => {
|
|||
|
||||
it("starts with an offset when synced and started after Transport is running", () => {
|
||||
return Offline(({ transport }) => {
|
||||
const ramp = new Float32Array(Math.floor(getContext().sampleRate * 0.3));
|
||||
const ramp = new Float32Array(
|
||||
Math.floor(getContext().sampleRate * 0.3)
|
||||
);
|
||||
for (let i = 0; i < ramp.length; i++) {
|
||||
ramp[i] = (i / (ramp.length)) * 0.3;
|
||||
ramp[i] = (i / ramp.length) * 0.3;
|
||||
}
|
||||
const buff = new ToneAudioBuffer().fromArray(ramp);
|
||||
const player = new Player(buff).toDestination();
|
||||
|
@ -606,9 +644,11 @@ describe("Player", () => {
|
|||
|
||||
it("can pass in an offset when synced and started after Transport is running", () => {
|
||||
return Offline(({ transport }) => {
|
||||
const ramp = new Float32Array(Math.floor(getContext().sampleRate * 0.3));
|
||||
const ramp = new Float32Array(
|
||||
Math.floor(getContext().sampleRate * 0.3)
|
||||
);
|
||||
for (let i = 0; i < ramp.length; i++) {
|
||||
ramp[i] = (i / (ramp.length)) * 0.3;
|
||||
ramp[i] = (i / ramp.length) * 0.3;
|
||||
}
|
||||
const buff = new ToneAudioBuffer().fromArray(ramp);
|
||||
const player = new Player(buff).toDestination();
|
||||
|
@ -630,12 +670,18 @@ describe("Player", () => {
|
|||
it("fades in and out correctly", () => {
|
||||
let duration = 0.5;
|
||||
return Offline(() => {
|
||||
const onesArray = new Float32Array(getContext().sampleRate * duration);
|
||||
const onesArray = new Float32Array(
|
||||
getContext().sampleRate * duration
|
||||
);
|
||||
onesArray.forEach((sample, index) => {
|
||||
onesArray[index] = 1;
|
||||
});
|
||||
const onesBuffer = ToneAudioBuffer.fromArray(onesArray);
|
||||
const player = new Player({ url: onesBuffer, fadeOut: 0.1, fadeIn: 0.1 }).toDestination();
|
||||
const player = new Player({
|
||||
url: onesBuffer,
|
||||
fadeOut: 0.1,
|
||||
fadeIn: 0.1,
|
||||
}).toDestination();
|
||||
player.start(0);
|
||||
}, 0.6).then((buff) => {
|
||||
expect(buff.getRmsAtTime(0)).to.be.closeTo(0, 0.1);
|
||||
|
@ -643,7 +689,10 @@ describe("Player", () => {
|
|||
expect(buff.getRmsAtTime(0.1)).to.be.closeTo(1, 0.1);
|
||||
duration -= 0.1;
|
||||
expect(buff.getRmsAtTime(duration)).to.be.closeTo(1, 0.1);
|
||||
expect(buff.getRmsAtTime(duration + 0.05)).to.be.closeTo(0.5, 0.1);
|
||||
expect(buff.getRmsAtTime(duration + 0.05)).to.be.closeTo(
|
||||
0.5,
|
||||
0.1
|
||||
);
|
||||
expect(buff.getRmsAtTime(duration + 0.1)).to.be.closeTo(0, 0.1);
|
||||
});
|
||||
});
|
||||
|
|
BIN
test/audio/compare/playerSyncLoop.wav
Normal file
BIN
test/audio/compare/playerSyncLoop.wav
Normal file
Binary file not shown.
Loading…
Reference in a new issue