mirror of
https://github.com/Tonejs/Tone.js
synced 2024-12-26 03:23:11 +00:00
Add Pattern.index
Pattern.index returns the current index of the pattern. This method previously existed but was lost after the TypeScript conversion.
This commit is contained in:
parent
aad2d8c890
commit
205c438511
4 changed files with 116 additions and 92 deletions
|
@ -89,19 +89,21 @@ describe("Pattern", () => {
|
|||
it("is invoked after it's started", () => {
|
||||
let invoked = false;
|
||||
return Offline(({ transport }) => {
|
||||
const values = ["a", "b", "c"];
|
||||
let index = 0;
|
||||
const pattern = new Pattern((() => {
|
||||
invoked = true;
|
||||
expect(pattern.value).to.equal(index);
|
||||
expect(pattern.value).to.equal(values[index]);
|
||||
expect(pattern.index).to.equal(index);
|
||||
index++;
|
||||
}), [0, 1, 2]).start(0);
|
||||
}), values).start(0);
|
||||
transport.start();
|
||||
}, 0.2).then(() => {
|
||||
expect(invoked).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it("passes in the scheduled time and pattern index to the callback", () => {
|
||||
it("passes in the scheduled time and pattern note to the callback", () => {
|
||||
let invoked = false;
|
||||
return Offline(({ transport }) => {
|
||||
const startTime = 0.05;
|
||||
|
@ -109,6 +111,8 @@ describe("Pattern", () => {
|
|||
expect(time).to.be.a("number");
|
||||
expect(time - startTime).to.be.closeTo(0.3, 0.01);
|
||||
expect(note).to.be.equal("a");
|
||||
expect(pattern.value).to.equal("a");
|
||||
expect(pattern.index).to.be.equal(0);
|
||||
invoked = true;
|
||||
}), ["a"], "up");
|
||||
transport.start(startTime);
|
||||
|
@ -121,16 +125,38 @@ describe("Pattern", () => {
|
|||
it("passes in the next note of the pattern", () => {
|
||||
let counter = 0;
|
||||
return Offline(({ transport }) => {
|
||||
const values = ["a", "b", "c"];
|
||||
const pattern = new Pattern(((time, note) => {
|
||||
expect(note).to.equal(counter % 3);
|
||||
expect(note).to.equal(values[counter % 3]);
|
||||
expect(pattern.value).to.equal(values[counter % 3]);
|
||||
expect(pattern.index).to.be.equal(counter % 3);
|
||||
counter++;
|
||||
}), [0, 1, 2], "up").start(0);
|
||||
}), values, "up").start(0);
|
||||
pattern.interval = "16n";
|
||||
transport.start(0);
|
||||
}, 0.7).then(() => {
|
||||
expect(counter).to.equal(6);
|
||||
});
|
||||
});
|
||||
|
||||
it("can modify the pattern type and values", () => {
|
||||
let counter = 0;
|
||||
return Offline(({ transport }) => {
|
||||
const values = ["a", "b", "c"];
|
||||
const pattern = new Pattern(((time, note) => {
|
||||
expect(note).to.equal(values[counter % 3]);
|
||||
expect(pattern.value).to.equal(values[counter % 3]);
|
||||
expect(pattern.index).to.be.equal(counter % 3);
|
||||
counter++;
|
||||
}), ["a"], "down").start(0);
|
||||
pattern.interval = "16n";
|
||||
pattern.pattern = "up";
|
||||
pattern.values = values;
|
||||
transport.start(0);
|
||||
}, 0.7).then(() => {
|
||||
expect(counter).to.equal(6);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
@ -27,12 +27,17 @@ export class Pattern<ValueType> extends Loop<PatternOptions<ValueType>> {
|
|||
/**
|
||||
* The pattern generator function
|
||||
*/
|
||||
private _pattern: Iterator<ValueType>;
|
||||
private _pattern: Iterator<number>;
|
||||
|
||||
/**
|
||||
* The current index
|
||||
*/
|
||||
private _index?: number;
|
||||
|
||||
/**
|
||||
* The current value
|
||||
*/
|
||||
private _value?: ValueType;
|
||||
private _value?: ValueType;
|
||||
|
||||
/**
|
||||
* Hold the pattern type
|
||||
|
@ -67,7 +72,7 @@ export class Pattern<ValueType> extends Loop<PatternOptions<ValueType>> {
|
|||
|
||||
this.callback = options.callback;
|
||||
this._values = options.values;
|
||||
this._pattern = PatternGenerator(options.values, options.pattern);
|
||||
this._pattern = PatternGenerator(options.values.length, options.pattern);
|
||||
this._type = options.pattern;
|
||||
}
|
||||
|
||||
|
@ -83,8 +88,9 @@ export class Pattern<ValueType> extends Loop<PatternOptions<ValueType>> {
|
|||
* Internal function called when the notes should be called
|
||||
*/
|
||||
protected _tick(time: Seconds): void {
|
||||
const value = this._pattern.next() as IteratorResult<ValueType>;
|
||||
this._value = value.value;
|
||||
const index = this._pattern.next() as IteratorResult<ValueType>;
|
||||
this._index = index.value;
|
||||
this._value = this._values[index.value];
|
||||
this.callback(time, this._value);
|
||||
}
|
||||
|
||||
|
@ -107,6 +113,13 @@ export class Pattern<ValueType> extends Loop<PatternOptions<ValueType>> {
|
|||
return this._value;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current index of the pattern.
|
||||
*/
|
||||
get index(): number | undefined {
|
||||
return this._index;
|
||||
}
|
||||
|
||||
/**
|
||||
* The pattern type. See Tone.CtrlPattern for the full list of patterns.
|
||||
*/
|
||||
|
@ -115,7 +128,7 @@ export class Pattern<ValueType> extends Loop<PatternOptions<ValueType>> {
|
|||
}
|
||||
set pattern(pattern) {
|
||||
this._type = pattern;
|
||||
this._pattern = PatternGenerator(this._values, this._type);
|
||||
this._pattern = PatternGenerator(this._values.length, this._type);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -13,22 +13,14 @@ describe("PatternGenerator", () => {
|
|||
|
||||
context("API", () => {
|
||||
|
||||
it("can be constructed with an array and type", () => {
|
||||
const pattern = PatternGenerator([0, 1, 2, 3], "down");
|
||||
it("can be constructed with an number and type", () => {
|
||||
const pattern = PatternGenerator(4, "down");
|
||||
expect(getArrayValues(pattern, 10)).to.deep.equal([3, 2, 1, 0, 3, 2, 1, 0, 3, 2]);
|
||||
});
|
||||
|
||||
it("can be resized smaller when the index is after the previous length", () => {
|
||||
const values = [0, 1, 2, 3, 4];
|
||||
const pattern = PatternGenerator(values);
|
||||
expect(pattern.next().value).to.equal(0);
|
||||
values.shift();
|
||||
expect(pattern.next().value).to.equal(2);
|
||||
});
|
||||
|
||||
it("throws an error with an empty array", () => {
|
||||
it("throws an error with a number less than 1", () => {
|
||||
expect(() => {
|
||||
const pattern = PatternGenerator([]);
|
||||
const pattern = PatternGenerator(0);
|
||||
pattern.next();
|
||||
}).to.throw(Error);
|
||||
});
|
||||
|
@ -37,54 +29,54 @@ describe("PatternGenerator", () => {
|
|||
context("Patterns", () => {
|
||||
|
||||
it("does the up pattern", () => {
|
||||
const pattern = PatternGenerator([0, 1, 2, 3], "up");
|
||||
const pattern = PatternGenerator(4, "up");
|
||||
expect(getArrayValues(pattern, 6)).to.deep.equal([0, 1, 2, 3, 0, 1]);
|
||||
});
|
||||
|
||||
it("does the down pattern", () => {
|
||||
const pattern = PatternGenerator([0, 1, 2, 3], "down");
|
||||
const pattern = PatternGenerator(4, "down");
|
||||
expect(getArrayValues(pattern, 6)).to.deep.equal([3, 2, 1, 0, 3, 2]);
|
||||
});
|
||||
|
||||
it("does the upDown pattern", () => {
|
||||
const pattern = PatternGenerator([0, 1, 2, 3], "upDown");
|
||||
const pattern = PatternGenerator(4, "upDown");
|
||||
expect(getArrayValues(pattern, 10)).to.deep.equal([0, 1, 2, 3, 2, 1, 0, 1, 2, 3]);
|
||||
});
|
||||
|
||||
it("does the downUp pattern", () => {
|
||||
const pattern = PatternGenerator([0, 1, 2, 3], "downUp");
|
||||
const pattern = PatternGenerator(4, "downUp");
|
||||
expect(getArrayValues(pattern, 10)).to.deep.equal([3, 2, 1, 0, 1, 2, 3, 2, 1, 0]);
|
||||
});
|
||||
|
||||
it("does the alternateUp pattern", () => {
|
||||
const pattern = PatternGenerator([0, 1, 2, 3, 4], "alternateUp");
|
||||
const pattern = PatternGenerator(5, "alternateUp");
|
||||
expect(getArrayValues(pattern, 10)).to.deep.equal([0, 2, 1, 3, 2, 4, 3, 0, 2, 1]);
|
||||
});
|
||||
|
||||
it("does the alternateDown pattern", () => {
|
||||
const pattern = PatternGenerator([0, 1, 2, 3, 4], "alternateDown");
|
||||
const pattern = PatternGenerator(5, "alternateDown");
|
||||
expect(getArrayValues(pattern, 10)).to.deep.equal([4, 2, 3, 1, 2, 0, 1, 4, 2, 3]);
|
||||
});
|
||||
|
||||
it("outputs random elements from the values", () => {
|
||||
const values = [0, 1, 2, 3, 4];
|
||||
const pattern = PatternGenerator(values, "random");
|
||||
const numValues = 5;
|
||||
const pattern = PatternGenerator(numValues, "random");
|
||||
for (let i = 0; i < 10; i++) {
|
||||
expect(values.indexOf(pattern.next().value)).to.not.equal(-1);
|
||||
expect(pattern.next().value).to.be.at.least(0).and.at.most(numValues - 1);
|
||||
}
|
||||
});
|
||||
|
||||
it("does randomOnce pattern", () => {
|
||||
const pattern = PatternGenerator([4, 5, 6, 7, 8], "randomOnce");
|
||||
expect(getArrayValues(pattern, 10).sort()).to.deep.equal([4, 4, 5, 5, 6, 6, 7, 7, 8, 8]);
|
||||
const pattern = PatternGenerator(5, "randomOnce");
|
||||
expect(getArrayValues(pattern, 10).sort()).to.deep.equal([0, 0, 1, 1, 2, 2, 3, 3, 4, 4]);
|
||||
});
|
||||
|
||||
it("randomly walks up or down 1 step without repeating", () => {
|
||||
const values = [0, 1, 2, 3, 4];
|
||||
const pattern = PatternGenerator(values, "randomWalk");
|
||||
let currentIndex = values.indexOf(pattern.next().value);
|
||||
const pattern = PatternGenerator(5, "randomWalk");
|
||||
let currentIndex = pattern.next().value;
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const nextIndex = values.indexOf(pattern.next().value);
|
||||
const nextIndex = pattern.next().value;
|
||||
expect(currentIndex).to.not.equal(nextIndex);
|
||||
// change always equals 1
|
||||
expect(Math.abs(currentIndex - nextIndex)).to.equal(1);
|
||||
|
|
|
@ -9,11 +9,11 @@ export type PatternName = "up" | "down" | "upDown" | "downUp" | "alternateUp" |
|
|||
/**
|
||||
* Start at the first value and go up to the last
|
||||
*/
|
||||
function* upPatternGen<T>(values: T[]): IterableIterator<T> {
|
||||
function* upPatternGen<T>(numValues: number): IterableIterator<number> {
|
||||
let index = 0;
|
||||
while (index < values.length) {
|
||||
index = clampToArraySize(index, values);
|
||||
yield values[index];
|
||||
while (index < numValues) {
|
||||
index = clamp(index, 0, numValues - 1);
|
||||
yield index;
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
@ -21,11 +21,11 @@ function* upPatternGen<T>(values: T[]): IterableIterator<T> {
|
|||
/**
|
||||
* Start at the last value and go down to 0
|
||||
*/
|
||||
function* downPatternGen<T>(values: T[]): IterableIterator<T> {
|
||||
let index = values.length - 1;
|
||||
function* downPatternGen<T>(numValues: number): IterableIterator<number> {
|
||||
let index = numValues - 1;
|
||||
while (index >= 0) {
|
||||
index = clampToArraySize(index, values);
|
||||
yield values[index];
|
||||
index = clamp(index, 0, numValues - 1);
|
||||
yield index;
|
||||
index--;
|
||||
}
|
||||
}
|
||||
|
@ -33,30 +33,23 @@ function* downPatternGen<T>(values: T[]): IterableIterator<T> {
|
|||
/**
|
||||
* Infinitely yield the generator
|
||||
*/
|
||||
function* infiniteGen<T>(values: T[], gen: typeof upPatternGen): IterableIterator<T> {
|
||||
function* infiniteGen<T>(numValues: number, gen: typeof upPatternGen): IterableIterator<number> {
|
||||
while (true) {
|
||||
yield* gen(values);
|
||||
yield* gen(numValues);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the index is in the given range
|
||||
*/
|
||||
function clampToArraySize(index: number, values: any[]): number {
|
||||
return clamp(index, 0, values.length - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Alternate between two generators
|
||||
*/
|
||||
function* alternatingGenerator<T>(values: T[], directionUp: boolean): IterableIterator<T> {
|
||||
let index = directionUp ? 0 : values.length - 1;
|
||||
function* alternatingGenerator<T>(numValues: number, directionUp: boolean): IterableIterator<number> {
|
||||
let index = directionUp ? 0 : numValues - 1;
|
||||
while (true) {
|
||||
index = clampToArraySize(index, values);
|
||||
yield values[index];
|
||||
index = clamp(index, 0, numValues - 1);
|
||||
yield index;
|
||||
if (directionUp) {
|
||||
index++;
|
||||
if (index >= values.length - 1) {
|
||||
if (index >= numValues - 1) {
|
||||
directionUp = false;
|
||||
}
|
||||
} else {
|
||||
|
@ -71,12 +64,12 @@ function* alternatingGenerator<T>(values: T[], directionUp: boolean): IterableIt
|
|||
/**
|
||||
* Starting from the bottom move up 2, down 1
|
||||
*/
|
||||
function* jumpUp<T>(values: T[]): IterableIterator<T> {
|
||||
function* jumpUp<T>(numValues: number): IterableIterator<number> {
|
||||
let index = 0;
|
||||
let stepIndex = 0;
|
||||
while (index < values.length) {
|
||||
index = clampToArraySize(index, values);
|
||||
yield values[index];
|
||||
while (index < numValues) {
|
||||
index = clamp(index, 0, numValues - 1);
|
||||
yield index;
|
||||
stepIndex++;
|
||||
index += (stepIndex % 2 ? 2 : -1);
|
||||
}
|
||||
|
@ -85,12 +78,12 @@ function* jumpUp<T>(values: T[]): IterableIterator<T> {
|
|||
/**
|
||||
* Starting from the top move down 2, up 1
|
||||
*/
|
||||
function* jumpDown<T>(values: T[]): IterableIterator<T> {
|
||||
let index = values.length - 1;
|
||||
function* jumpDown<T>(numValues: number): IterableIterator<number> {
|
||||
let index = numValues - 1;
|
||||
let stepIndex = 0;
|
||||
while (index >= 0) {
|
||||
index = clampToArraySize(index, values);
|
||||
yield values[index];
|
||||
index = clamp(index, 0, numValues - 1);
|
||||
yield index;
|
||||
stepIndex++;
|
||||
index += (stepIndex % 2 ? -2 : 1);
|
||||
}
|
||||
|
@ -99,78 +92,78 @@ function* jumpDown<T>(values: T[]): IterableIterator<T> {
|
|||
/**
|
||||
* Choose a random index each time
|
||||
*/
|
||||
function* randomGen<T>(values: T[]): IterableIterator<T> {
|
||||
function* randomGen<T>(numValues: number): IterableIterator<number> {
|
||||
while (true) {
|
||||
const randomIndex = Math.floor(Math.random() * values.length);
|
||||
yield values[randomIndex];
|
||||
const randomIndex = Math.floor(Math.random() * numValues);
|
||||
yield randomIndex;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly go through all of the values once before choosing a new random order
|
||||
*/
|
||||
function* randomOnce<T>(values: T[]): IterableIterator<T> {
|
||||
function* randomOnce<T>(numValues: number): IterableIterator<number> {
|
||||
// create an array of indices
|
||||
const copy: number[] = [];
|
||||
for (let i = 0; i < values.length; i++) {
|
||||
for (let i = 0; i < numValues; i++) {
|
||||
copy.push(i);
|
||||
}
|
||||
while (copy.length > 0) {
|
||||
// random choose an index, and then remove it so it's not chosen again
|
||||
const randVal = copy.splice(Math.floor(copy.length * Math.random()), 1);
|
||||
const index = clampToArraySize(randVal[0], values);
|
||||
yield values[index];
|
||||
const index = clamp(randVal[0], 0, numValues - 1);
|
||||
yield index;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Randomly choose to walk up or down 1 index in the values array
|
||||
*/
|
||||
function* randomWalk<T>(values: T[]): IterableIterator<T> {
|
||||
function* randomWalk<T>(numValues: number): IterableIterator<number> {
|
||||
// randomly choose a starting index in the values array
|
||||
let index = Math.floor(Math.random() * values.length);
|
||||
let index = Math.floor(Math.random() * numValues);
|
||||
while (true) {
|
||||
if (index === 0) {
|
||||
index++; // at bottom of array, so force upward step
|
||||
} else if (index === values.length - 1) {
|
||||
} else if (index === numValues - 1) {
|
||||
index--; // at top of array, so force downward step
|
||||
} else if (Math.random() < 0.5) { // else choose random downward or upward step
|
||||
index--;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
yield values[index];
|
||||
yield index;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PatternGenerator returns a generator which will iterate over the given array
|
||||
* of values and yield the items according to the passed in pattern
|
||||
* @param values An array of values to iterate over
|
||||
* PatternGenerator returns a generator which will yield numbers between 0 and numValues
|
||||
* according to the passed in pattern that can be used as indexes into an array of size numValues.
|
||||
* @param numValues The size of the array to emit indexes for
|
||||
* @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<T>(values: T[], pattern: PatternName = "up", index = 0): Iterator<T> {
|
||||
export function* PatternGenerator(numValues: number, pattern: PatternName = "up", index = 0): Iterator<number> {
|
||||
// safeguards
|
||||
assert(values.length > 0, "The array must have more than one value in it");
|
||||
assert(numValues >= 1, "The number of values must be at least one");
|
||||
switch (pattern) {
|
||||
case "up" :
|
||||
yield* infiniteGen(values, upPatternGen);
|
||||
yield* infiniteGen(numValues, upPatternGen);
|
||||
case "down" :
|
||||
yield* infiniteGen(values, downPatternGen);
|
||||
yield* infiniteGen(numValues, downPatternGen);
|
||||
case "upDown" :
|
||||
yield* alternatingGenerator(values, true);
|
||||
yield* alternatingGenerator(numValues, true);
|
||||
case "downUp" :
|
||||
yield* alternatingGenerator(values, false);
|
||||
yield* alternatingGenerator(numValues, false);
|
||||
case "alternateUp":
|
||||
yield* infiniteGen(values, jumpUp);
|
||||
yield* infiniteGen(numValues, jumpUp);
|
||||
case "alternateDown":
|
||||
yield* infiniteGen(values, jumpDown);
|
||||
yield* infiniteGen(numValues, jumpDown);
|
||||
case "random":
|
||||
yield* randomGen(values);
|
||||
yield* randomGen(numValues);
|
||||
case "randomOnce":
|
||||
yield* infiniteGen(values, randomOnce);
|
||||
yield* infiniteGen(numValues, randomOnce);
|
||||
case "randomWalk":
|
||||
yield* randomWalk(values);
|
||||
yield* randomWalk(numValues);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue