diff --git a/Tone/event/Loop.ts b/Tone/event/Loop.ts index f01b635e..125fb1e3 100644 --- a/Tone/event/Loop.ts +++ b/Tone/event/Loop.ts @@ -29,7 +29,7 @@ export interface LoopOptions extends ToneWithContextOptions { * Transport.start(); * @category Event */ -export class Loop extends ToneWithContext { +export class Loop extends ToneWithContext { readonly name: string = "Loop"; @@ -110,7 +110,7 @@ export class Loop extends ToneWithContext { * Internal function called when the notes should be called * @param time The time the event occurs */ - private _tick(time: Seconds) { + protected _tick(time: Seconds): void { this.callback(time); } diff --git a/Tone/event/Pattern.js b/Tone/event/Pattern.js deleted file mode 100644 index 969dbe27..00000000 --- a/Tone/event/Pattern.js +++ /dev/null @@ -1,126 +0,0 @@ -import Tone from "../core/Tone"; -import "../event/Loop"; -import "../control/CtrlPattern"; - -/** - * @class Tone.Pattern arpeggiates between the given notes - * in a number of patterns. See Tone.CtrlPattern for - * a full list of patterns. - * @example - * var pattern = new Tone.Pattern(function(time, note){ - * //the order of the notes passed in depends on the pattern - * }, ["C2", "D4", "E5", "A6"], "upDown"); - * @extends {Tone.Loop} - * @param {Function} callback The callback to invoke with the event. - * @param {Array} values The values to arpeggiate over. - */ -Tone.Pattern = function(){ - - var options = Tone.defaults(arguments, ["callback", "values", "pattern"], Tone.Pattern); - Tone.Loop.call(this, options); - - /** - * The pattern manager - * @type {Tone.CtrlPattern} - * @private - */ - this._pattern = new Tone.CtrlPattern({ - "values" : options.values, - "type" : options.pattern, - "index" : options.index - }); -}; - -Tone.extend(Tone.Pattern, Tone.Loop); - -/** - * The defaults - * @const - * @type {Object} - */ -Tone.Pattern.defaults = { - "pattern" : Tone.CtrlPattern.Type.Up, - "callback" : Tone.noOp, - "values" : [], -}; - -/** - * Internal function called when the notes should be called - * @param {Number} time The time the event occurs - * @private - */ -Tone.Pattern.prototype._tick = function(time){ - this.callback(time, this._pattern.value); - this._pattern.next(); -}; - -/** - * The current index in the values array. - * @memberOf Tone.Pattern# - * @type {Positive} - * @name index - */ -Object.defineProperty(Tone.Pattern.prototype, "index", { - get : function(){ - return this._pattern.index; - }, - set : function(i){ - this._pattern.index = i; - } -}); - -/** - * The array of events. - * @memberOf Tone.Pattern# - * @type {Array} - * @name values - */ -Object.defineProperty(Tone.Pattern.prototype, "values", { - get : function(){ - return this._pattern.values; - }, - set : function(vals){ - this._pattern.values = vals; - } -}); - -/** - * The current value of the pattern. - * @memberOf Tone.Pattern# - * @type {*} - * @name value - * @readOnly - */ -Object.defineProperty(Tone.Pattern.prototype, "value", { - get : function(){ - return this._pattern.value; - } -}); - -/** - * The pattern type. See Tone.CtrlPattern for the full list of patterns. - * @memberOf Tone.Pattern# - * @type {String} - * @name pattern - */ -Object.defineProperty(Tone.Pattern.prototype, "pattern", { - get : function(){ - return this._pattern.type; - }, - set : function(pattern){ - this._pattern.type = pattern; - } -}); - -/** - * Clean up - * @return {Tone.Pattern} this - */ -Tone.Pattern.prototype.dispose = function(){ - Tone.Loop.prototype.dispose.call(this); - this._pattern.dispose(); - this._pattern = null; -}; - -export default Tone.Pattern; - diff --git a/Tone/event/Pattern.test.ts b/Tone/event/Pattern.test.ts new file mode 100644 index 00000000..2fd77115 --- /dev/null +++ b/Tone/event/Pattern.test.ts @@ -0,0 +1,137 @@ +import { BasicTests } from "test/helper/Basic"; +import { Pattern } from "./Pattern"; +import { Offline } from "test/helper/Offline"; +import { Time } from "Tone/core/type/Time"; +import { expect } from "chai"; + +describe("Pattern", () => { + + BasicTests(Pattern); + + context("Constructor", () => { + + it("takes a callback, an array of values and a pattern name", () => { + return Offline(() => { + const callback = function() {}; + const pattern = new Pattern(callback, [0, 1, 2, 3], "down"); + expect(pattern.callback).to.equal(callback); + expect(pattern.values).to.deep.equal([0, 1, 2, 3]); + expect(pattern.pattern).to.equal("down"); + pattern.dispose(); + }); + }); + + it("can be constructed with no arguments", () => { + return Offline(() => { + const pattern = new Pattern(); + pattern.dispose(); + }); + }); + + it("can pass in arguments in options object", () => { + return Offline(() => { + const callback = function() {}; + const pattern = new Pattern({ + callback: callback, + iterations: 4, + probability: 0.3, + interval: "8t", + values: [1, 2, 3], + pattern: "upDown" + }); + expect(pattern.callback).to.equal(callback); + expect(pattern.interval.valueOf()).to.equal(Time("8t").valueOf()); + expect(pattern.iterations).to.equal(4); + expect(pattern.values).to.deep.equal([1, 2, 3]); + expect(pattern.probability).to.equal(0.3); + expect(pattern.pattern).to.equal("upDown"); + pattern.dispose(); + }); + }); + }); + + context("Get/Set", () => { + + it("can set values with object", () => { + return Offline(() => { + const callback = function() {}; + const pattern = new Pattern(); + pattern.set({ + callback: callback, + values: ["a", "b", "c"], + }); + expect(pattern.callback).to.equal(callback); + expect(pattern.values).to.deep.equal(["a", "b", "c"]); + pattern.dispose(); + }); + }); + + it("can set get a the values as an object", () => { + return Offline(() => { + const callback = function() {}; + const pattern = new Pattern({ + callback: callback, + pattern: "random", + probability: 0.3, + }); + const values = pattern.get(); + expect(values.pattern).to.equal("random"); + values.pattern = "upDown"; + expect(values.pattern).to.equal("upDown"); + expect(values.probability).to.equal(0.3); + pattern.dispose(); + }); + }); + }); + + context("Callback", () => { + + it("is invoked after it's started", () => { + let invoked = false; + return Offline(({ transport }) => { + let index = 0; + const pattern = new Pattern((() => { + invoked = true; + expect(pattern.value).to.equal(index); + index++; + }), [0, 1, 2]).start(0); + transport.start(); + }, 0.2).then(() => { + expect(invoked).to.be.true; + }); + }); + + it("passes in the scheduled time and pattern index to the callback", () => { + let invoked = false; + return Offline(({ transport }) => { + const startTime = 0.05; + const pattern = new Pattern(((time, note) => { + expect(time).to.be.a("number"); + expect(time - startTime).to.be.closeTo(0.3, 0.01); + expect(note).to.be.equal("a"); + invoked = true; + }), ["a"], "up"); + transport.start(startTime); + pattern.start(0.3); + }, 0.4).then(() => { + expect(invoked).to.be.true; + }); + }); + + it("passes in the next note of the pattern", () => { + let counter = 0; + return Offline(({ transport }) => { + const pattern = new Pattern(((time, note) => { + expect(note).to.equal(counter % 3); + counter++; + }), [0, 1, 2], "up").start(0); + pattern.interval = "16n"; + transport.start(0); + }, 0.7).then(() => { + expect(counter).to.equal(6); + }); + }); + }); + +}); + diff --git a/Tone/event/Pattern.ts b/Tone/event/Pattern.ts new file mode 100644 index 00000000..efaf589d --- /dev/null +++ b/Tone/event/Pattern.ts @@ -0,0 +1,121 @@ +import { Loop, LoopOptions } from "./Loop"; +import { PatternGenerator, PatternName } from "./PatternGenerator"; +import { ToneEventCallback } from "./ToneEvent"; +import { optionsFromArguments } from "../core/util/Defaults"; +import { Seconds } from "../core/type/Units"; +import { noOp } from "../core/util/Interface"; + +export interface PatternOptions extends LoopOptions { + pattern: PatternName; + values: ValueType[]; + callback: (time: Seconds, value?: ValueType) => void; +} + +/** + * Pattern arpeggiates between the given notes + * in a number of patterns. + * @example + * import { Pattern } from "tone"; + * const pattern = new Pattern((time, note) => { + * // the order of the notes passed in depends on the pattern + * }, ["C2", "D4", "E5", "A6"], "upDown"); + */ +export class Pattern extends Loop> { + + readonly name: string = "Pattern"; + + /** + * The pattern generator function + */ + private _pattern: Iterator; + + /** + * The current value + */ + private _value?: ValueType; + + /** + * Hold the pattern type + */ + private _type: PatternName; + + /** + * Hold the values + */ + private _values: ValueType[]; + + /** + * The callback to be invoked at a regular interval + */ + callback: (time: Seconds, value?: ValueType) => void; + + /** + * @param callback The callback to invoke with the event. + * @param values The values to arpeggiate over. + * @param pattern The name of the pattern + */ + constructor( + callback?: ToneEventCallback, + values?: ValueType[], + pattern?: PatternName, + ); + constructor(options?: Partial>); + constructor() { + + super(optionsFromArguments(Pattern.getDefaults(), arguments, ["callback", "values", "pattern"])); + const options = optionsFromArguments(Pattern.getDefaults(), arguments, ["callback", "values", "pattern"]); + + this.callback = options.callback; + this._values = options.values; + this._pattern = PatternGenerator(options.values, options.pattern); + this._type = options.pattern; + } + + static getDefaults(): PatternOptions { + return Object.assign(Loop.getDefaults(), { + pattern: "up" as "up", + values: [], + callback: noOp, + }); + } + + /** + * Internal function called when the notes should be called + */ + protected _tick(time: Seconds): void { + const value = this._pattern.next() as IteratorResult; + this._value = value.value; + this.callback(time, this._value); + } + + /** + * The array of events. + */ + get values(): ValueType[] { + return this._values; + } + set values(val) { + this._values = val; + // reset the pattern + this.pattern = this._type; + } + + /** + * The current value of the pattern. + */ + get value(): ValueType | undefined { + return this._value; + } + + /** + * The pattern type. See Tone.CtrlPattern for the full list of patterns. + */ + get pattern(): PatternName { + return this._type; + } + set pattern(pattern) { + this._type = pattern; + this._pattern = PatternGenerator(this._values, this._type); + } +} + diff --git a/Tone/control/PatternGenerator.test.ts b/Tone/event/PatternGenerator.test.ts similarity index 100% rename from Tone/control/PatternGenerator.test.ts rename to Tone/event/PatternGenerator.test.ts diff --git a/Tone/control/PatternGenerator.ts b/Tone/event/PatternGenerator.ts similarity index 95% rename from Tone/control/PatternGenerator.ts rename to Tone/event/PatternGenerator.ts index 2cf037e6..70000177 100644 --- a/Tone/control/PatternGenerator.ts +++ b/Tone/event/PatternGenerator.ts @@ -3,8 +3,7 @@ import { assert } from "../core/util/Debug"; /** * The name of the patterns */ -export type PatternName = "up" | "down" | "upDown" | "downUp" | -"alternateUp" | "alternateDown" | "random" | "randomOnce"; +export type PatternName = "up" | "down" | "upDown" | "downUp" | "alternateUp" | "alternateDown" | "random" | "randomOnce"; /** * Start at the first value and go up to the last @@ -128,8 +127,9 @@ function* randomOnce(values: T[]): IterableIterator { * of values and yield the items according to the passed in pattern * @param values An array of values to iterate over * @param pattern The name of the pattern use when iterating over + * @param index Where to start in the offset of the values array */ -export function* PatternGenerator(values: T[], pattern: PatternName = "up"): Iterator { +export function* PatternGenerator(values: T[], pattern: PatternName = "up", index: number = 0): Iterator { // safeguards assert(values.length > 0, "The array must have more than one value in it"); switch (pattern) {