mirror of
https://github.com/Tonejs/Tone.js
synced 2024-12-27 03:53:07 +00:00
warn if event is scheduled without using the scheduled time.
addresses #959
This commit is contained in:
parent
52cf924ee7
commit
6dd22e752f
4 changed files with 147 additions and 34 deletions
|
@ -7,6 +7,8 @@ import { TransportTime } from "../type/TransportTime";
|
|||
import { Transport } from "./Transport";
|
||||
// importing for side affects
|
||||
import "../context/Destination";
|
||||
import { warns } from "test/helper/Basic";
|
||||
import { Synth } from "Tone/instrument/Synth";
|
||||
|
||||
describe("Transport", () => {
|
||||
|
||||
|
@ -546,6 +548,19 @@ describe("Transport", () => {
|
|||
});
|
||||
});
|
||||
|
||||
it("warns if the scheduled time was not used in the callback", async () => {
|
||||
return Offline(({ transport }) => {
|
||||
const synth = new Synth();
|
||||
transport.schedule(() => {
|
||||
warns(() => {
|
||||
synth.triggerAttackRelease("C2", 0.1);
|
||||
});
|
||||
}, 0);
|
||||
transport.start(0);
|
||||
}, 0.3).then(() => {
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
context("scheduleRepeat", () => {
|
||||
|
|
|
@ -2,15 +2,29 @@ import { TimeClass } from "../../core/type/Time";
|
|||
import { PlaybackState } from "../../core/util/StateTimeline";
|
||||
import { TimelineValue } from "../../core/util/TimelineValue";
|
||||
import { Signal } from "../../signal/Signal";
|
||||
import { onContextClose, onContextInit } from "../context/ContextInitialization";
|
||||
import {
|
||||
onContextClose,
|
||||
onContextInit,
|
||||
} from "../context/ContextInitialization";
|
||||
import { Gain } from "../context/Gain";
|
||||
import { ToneWithContext, ToneWithContextOptions } from "../context/ToneWithContext";
|
||||
import {
|
||||
ToneWithContext,
|
||||
ToneWithContextOptions,
|
||||
} from "../context/ToneWithContext";
|
||||
import { TicksClass } from "../type/Ticks";
|
||||
import { TransportTimeClass } from "../type/TransportTime";
|
||||
import {
|
||||
BarsBeatsSixteenths, BPM, NormalRange, Seconds,
|
||||
Subdivision, Ticks, Time, TimeSignature, TransportTime
|
||||
BarsBeatsSixteenths,
|
||||
BPM,
|
||||
NormalRange,
|
||||
Seconds,
|
||||
Subdivision,
|
||||
Ticks,
|
||||
Time,
|
||||
TimeSignature,
|
||||
TransportTime,
|
||||
} from "../type/Units";
|
||||
import { enterScheduledCallback } from "../util/Debug";
|
||||
import { optionsFromArguments } from "../util/Defaults";
|
||||
import { Emitter } from "../util/Emitter";
|
||||
import { readOnly, writable } from "../util/Interface";
|
||||
|
@ -32,7 +46,14 @@ interface TransportOptions extends ToneWithContextOptions {
|
|||
ppq: number;
|
||||
}
|
||||
|
||||
type TransportEventNames = "start" | "stop" | "pause" | "loop" | "loopEnd" | "loopStart" | "ticks";
|
||||
type TransportEventNames =
|
||||
| "start"
|
||||
| "stop"
|
||||
| "pause"
|
||||
| "loop"
|
||||
| "loopEnd"
|
||||
| "loopStart"
|
||||
| "ticks";
|
||||
|
||||
interface SyncedSignalEvent {
|
||||
signal: Signal;
|
||||
|
@ -64,8 +85,9 @@ type TransportCallback = (time: Seconds) => void;
|
|||
* Tone.Transport.start();
|
||||
* @category Core
|
||||
*/
|
||||
export class Transport extends ToneWithContext<TransportOptions> implements Emitter<TransportEventNames> {
|
||||
|
||||
export class Transport
|
||||
extends ToneWithContext<TransportOptions>
|
||||
implements Emitter<TransportEventNames> {
|
||||
readonly name: string = "Transport";
|
||||
|
||||
//-------------------------------------
|
||||
|
@ -163,9 +185,11 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
|
||||
constructor(options?: Partial<TransportOptions>);
|
||||
constructor() {
|
||||
|
||||
super(optionsFromArguments(Transport.getDefaults(), arguments));
|
||||
const options = optionsFromArguments(Transport.getDefaults(), arguments);
|
||||
const options = optionsFromArguments(
|
||||
Transport.getDefaults(),
|
||||
arguments
|
||||
);
|
||||
|
||||
// CLOCK/TEMPO
|
||||
this._ppq = options.ppq;
|
||||
|
@ -213,21 +237,34 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
this.emit("loopEnd", tickTime);
|
||||
this._clock.setTicksAtTime(this._loopStart, tickTime);
|
||||
ticks = this._loopStart;
|
||||
this.emit("loopStart", tickTime, this._clock.getSecondsAtTime(tickTime));
|
||||
this.emit(
|
||||
"loopStart",
|
||||
tickTime,
|
||||
this._clock.getSecondsAtTime(tickTime)
|
||||
);
|
||||
this.emit("loop", tickTime);
|
||||
}
|
||||
}
|
||||
// handle swing
|
||||
if (this._swingAmount > 0 &&
|
||||
if (
|
||||
this._swingAmount > 0 &&
|
||||
ticks % this._ppq !== 0 && // not on a downbeat
|
||||
ticks % (this._swingTicks * 2) !== 0) {
|
||||
ticks % (this._swingTicks * 2) !== 0
|
||||
) {
|
||||
// add some swing
|
||||
const progress = (ticks % (this._swingTicks * 2)) / (this._swingTicks * 2);
|
||||
const amount = Math.sin((progress) * Math.PI) * this._swingAmount;
|
||||
tickTime += new TicksClass(this.context, this._swingTicks * 2 / 3).toSeconds() * amount;
|
||||
const progress =
|
||||
(ticks % (this._swingTicks * 2)) / (this._swingTicks * 2);
|
||||
const amount = Math.sin(progress * Math.PI) * this._swingAmount;
|
||||
tickTime +=
|
||||
new TicksClass(
|
||||
this.context,
|
||||
(this._swingTicks * 2) / 3
|
||||
).toSeconds() * amount;
|
||||
}
|
||||
// invoke the timeline events scheduled on this tick
|
||||
this._timeline.forEachAtTime(ticks, event => event.invoke(tickTime));
|
||||
enterScheduledCallback(true);
|
||||
this._timeline.forEachAtTime(ticks, (event) => event.invoke(tickTime));
|
||||
enterScheduledCallback(false);
|
||||
}
|
||||
|
||||
//-------------------------------------
|
||||
|
@ -246,7 +283,10 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
* console.log("measure 16!");
|
||||
* }, "16:0:0");
|
||||
*/
|
||||
schedule(callback: TransportCallback, time: TransportTime | TransportTimeClass): number {
|
||||
schedule(
|
||||
callback: TransportCallback,
|
||||
time: TransportTime | TransportTimeClass
|
||||
): number {
|
||||
const event = new TransportEvent(this, {
|
||||
callback,
|
||||
time: new TransportTimeClass(this.context, time).toTicks(),
|
||||
|
@ -274,7 +314,7 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
callback: TransportCallback,
|
||||
interval: Time | TimeClass,
|
||||
startTime?: TransportTime | TransportTimeClass,
|
||||
duration: Time = Infinity,
|
||||
duration: Time = Infinity
|
||||
): number {
|
||||
const event = new TransportRepeatEvent(this, {
|
||||
callback,
|
||||
|
@ -293,7 +333,10 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
* @param time The time the callback should be invoked.
|
||||
* @returns The ID of the scheduled event.
|
||||
*/
|
||||
scheduleOnce(callback: TransportCallback, time: TransportTime | TransportTimeClass): number {
|
||||
scheduleOnce(
|
||||
callback: TransportCallback,
|
||||
time: TransportTime | TransportTimeClass
|
||||
): number {
|
||||
const event = new TransportEvent(this, {
|
||||
callback,
|
||||
once: true,
|
||||
|
@ -338,8 +381,12 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
*/
|
||||
cancel(after: TransportTime = 0): this {
|
||||
const computedAfter = this.toTicks(after);
|
||||
this._timeline.forEachFrom(computedAfter, event => this.clear(event.id));
|
||||
this._repeatedEvents.forEachFrom(computedAfter, event => this.clear(event.id));
|
||||
this._timeline.forEachFrom(computedAfter, (event) =>
|
||||
this.clear(event.id)
|
||||
);
|
||||
this._repeatedEvents.forEachFrom(computedAfter, (event) =>
|
||||
this.clear(event.id)
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -381,6 +428,8 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
* Tone.Transport.start("+1", "4:0:0");
|
||||
*/
|
||||
start(time?: Time, offset?: TransportTime): this {
|
||||
// start the context
|
||||
this.context.resume();
|
||||
let offsetTicks;
|
||||
if (isDefined(offset)) {
|
||||
offsetTicks = this.toTicks(offset);
|
||||
|
@ -486,7 +535,10 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
* Tone.Transport.setLoopPoints(0, "1m");
|
||||
* Tone.Transport.loop = true;
|
||||
*/
|
||||
setLoopPoints(startPosition: TransportTime, endPosition: TransportTime): this {
|
||||
setLoopPoints(
|
||||
startPosition: TransportTime,
|
||||
endPosition: TransportTime
|
||||
): this {
|
||||
this.loopStart = startPosition;
|
||||
this.loopEnd = endPosition;
|
||||
return this;
|
||||
|
@ -550,7 +602,9 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
if (this.loop) {
|
||||
const now = this.now();
|
||||
const ticks = this._clock.getTicksAtTime(now);
|
||||
return (ticks - this._loopStart) / (this._loopEnd - this._loopStart);
|
||||
return (
|
||||
(ticks - this._loopStart) / (this._loopEnd - this._loopStart)
|
||||
);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
|
@ -626,7 +680,7 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
* @return The context time of the next subdivision.
|
||||
* @example
|
||||
* // the transport must be started, otherwise returns 0
|
||||
* Tone.Transport.start();
|
||||
* Tone.Transport.start();
|
||||
* Tone.Transport.nextSubdivision("4n");
|
||||
*/
|
||||
nextSubdivision(subdivision?: Time): Seconds {
|
||||
|
@ -638,7 +692,7 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
const now = this.now();
|
||||
// the remainder of the current ticks and the subdivision
|
||||
const transportPos = this.getTicksAtTime(now);
|
||||
const remainingTicks = subdivision - transportPos % subdivision;
|
||||
const remainingTicks = subdivision - (transportPos % subdivision);
|
||||
return this._clock.nextTickTime(remainingTicks, now);
|
||||
}
|
||||
}
|
||||
|
@ -710,9 +764,18 @@ export class Transport extends ToneWithContext<TransportOptions> implements Emit
|
|||
// EMITTER MIXIN TO SATISFY COMPILER
|
||||
//-------------------------------------
|
||||
|
||||
on!: (event: TransportEventNames, callback: (...args: any[]) => void) => this;
|
||||
once!: (event: TransportEventNames, callback: (...args: any[]) => void) => this;
|
||||
off!: (event: TransportEventNames, callback?: ((...args: any[]) => void) | undefined) => this;
|
||||
on!: (
|
||||
event: TransportEventNames,
|
||||
callback: (...args: any[]) => void
|
||||
) => this;
|
||||
once!: (
|
||||
event: TransportEventNames,
|
||||
callback: (...args: any[]) => void
|
||||
) => this;
|
||||
off!: (
|
||||
event: TransportEventNames,
|
||||
callback?: ((...args: any[]) => void) | undefined
|
||||
) => this;
|
||||
emit!: (event: any, ...args: any[]) => this;
|
||||
}
|
||||
|
||||
|
@ -722,10 +785,10 @@ Emitter.mixin(Transport);
|
|||
// INITIALIZATION
|
||||
//-------------------------------------
|
||||
|
||||
onContextInit(context => {
|
||||
onContextInit((context) => {
|
||||
context.transport = new Transport({ context });
|
||||
});
|
||||
|
||||
onContextClose(context => {
|
||||
onContextClose((context) => {
|
||||
context.transport.dispose();
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ import { FrequencyClass } from "../type/Frequency";
|
|||
import { TimeClass } from "../type/Time";
|
||||
import { TransportTimeClass } from "../type/TransportTime";
|
||||
import { Frequency, Hertz, Seconds, Ticks, Time } from "../type/Units";
|
||||
import { assertUsedScheduleTime } from "../util/Debug";
|
||||
import { getDefaultsFromInstance, optionsFromArguments } from "../util/Defaults";
|
||||
import { RecursivePartial } from "../util/Interface";
|
||||
import { isArray, isBoolean, isDefined, isNumber, isString, isUndef } from "../util/TypeCheck";
|
||||
|
@ -104,6 +105,7 @@ export abstract class ToneWithContext<Options extends ToneWithContextOptions> ex
|
|||
* Tone.getTransport().bpm.rampTo(60, 30);
|
||||
*/
|
||||
toSeconds(time?: Time): Seconds {
|
||||
assertUsedScheduleTime(time);
|
||||
return new TimeClass(this.context, time).toSeconds();
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { isUndef } from "./TypeCheck";
|
||||
|
||||
/**
|
||||
* Assert that the statement is true, otherwise invoke the error.
|
||||
* @param statement
|
||||
|
@ -14,17 +16,48 @@ export function assert(statement: boolean, error: string): asserts statement {
|
|||
*/
|
||||
export function assertRange(value: number, gte: number, lte = Infinity): void {
|
||||
if (!(gte <= value && value <= lte)) {
|
||||
throw new RangeError(`Value must be within [${gte}, ${lte}], got: ${value}`);
|
||||
throw new RangeError(
|
||||
`Value must be within [${gte}, ${lte}], got: ${value}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the given value is within the range
|
||||
* Warn if the context is not running.
|
||||
*/
|
||||
export function assertContextRunning(context: import("../context/BaseContext").BaseContext): void {
|
||||
export function assertContextRunning(
|
||||
context: import("../context/BaseContext").BaseContext
|
||||
): void {
|
||||
// add a warning if the context is not started
|
||||
if (!context.isOffline && context.state !== "running") {
|
||||
warn("The AudioContext is \"suspended\". Invoke Tone.start() from a user action to start the audio.");
|
||||
warn(
|
||||
"The AudioContext is \"suspended\". Invoke Tone.start() from a user action to start the audio."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If it is currently inside a scheduled callback
|
||||
*/
|
||||
let isInsideScheduledCallback = false;
|
||||
let printedScheduledWarning = false;
|
||||
|
||||
/**
|
||||
* Notify that the following block of code is occurring inside a Transport callback.
|
||||
*/
|
||||
export function enterScheduledCallback(insideCallback: boolean): void {
|
||||
isInsideScheduledCallback = insideCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that a time was passed into
|
||||
*/
|
||||
export function assertUsedScheduleTime(
|
||||
time?: import("../type/Units").Time
|
||||
): void {
|
||||
if (isUndef(time) && isInsideScheduledCallback && !printedScheduledWarning) {
|
||||
printedScheduledWarning = true;
|
||||
warn("Events scheduled inside of scheduled callbacks should use the passed in scheduling time. See https://github.com/Tonejs/Tone.js/wiki/Accurate-Timing");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue