tone 14.8.35 → 14.8.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Tone/component/analysis/Meter.test.ts +25 -0
- package/Tone/component/analysis/Meter.ts +7 -5
- package/Tone/core/clock/TickSource.test.ts +52 -0
- package/Tone/core/clock/TickSource.ts +70 -9
- package/Tone/core/clock/Ticker.ts +11 -5
- package/Tone/core/context/Context.test.ts +2 -0
- package/Tone/core/context/Context.ts +29 -38
- package/Tone/core/context/ToneAudioBuffer.ts +52 -27
- package/Tone/event/Pattern.test.ts +31 -5
- package/Tone/event/Pattern.ts +19 -6
- package/Tone/event/PatternGenerator.test.ts +18 -26
- package/Tone/event/PatternGenerator.ts +52 -59
- package/Tone/instrument/MetalSynth.ts +48 -21
- package/Tone/signal/ToneConstantSource.test.ts +1 -1
- package/Tone/source/OneShotSource.ts +1 -1
- package/Tone/source/buffer/Player.test.ts +25 -0
- package/Tone/source/buffer/Player.ts +1 -1
- package/Tone/source/oscillator/ToneOscillatorNode.test.ts +1 -1
- package/Tone/version.ts +1 -1
- package/build/Tone.js +1 -1
- package/build/Tone.js.map +1 -1
- package/build/esm/component/analysis/Meter.d.ts +1 -1
- package/build/esm/component/analysis/Meter.js +5 -7
- package/build/esm/component/analysis/Meter.js.map +1 -1
- package/build/esm/core/clock/TickSource.d.ts +8 -0
- package/build/esm/core/clock/TickSource.js +49 -7
- package/build/esm/core/clock/TickSource.js.map +1 -1
- package/build/esm/core/clock/Ticker.d.ts +5 -1
- package/build/esm/core/clock/Ticker.js +6 -4
- package/build/esm/core/clock/Ticker.js.map +1 -1
- package/build/esm/core/context/Context.d.ts +12 -12
- package/build/esm/core/context/Context.js +26 -29
- package/build/esm/core/context/Context.js.map +1 -1
- package/build/esm/core/context/ToneAudioBuffer.js +25 -15
- package/build/esm/core/context/ToneAudioBuffer.js.map +1 -1
- package/build/esm/event/Pattern.d.ts +8 -0
- package/build/esm/event/Pattern.js +11 -4
- package/build/esm/event/Pattern.js.map +1 -1
- package/build/esm/event/PatternGenerator.d.ts +4 -4
- package/build/esm/event/PatternGenerator.js +52 -58
- package/build/esm/event/PatternGenerator.js.map +1 -1
- package/build/esm/instrument/MetalSynth.d.ts +0 -1
- package/build/esm/instrument/MetalSynth.js +15 -13
- package/build/esm/instrument/MetalSynth.js.map +1 -1
- package/build/esm/source/OneShotSource.js +1 -1
- package/build/esm/source/OneShotSource.js.map +1 -1
- package/build/esm/source/buffer/Player.js +2 -1
- package/build/esm/source/buffer/Player.js.map +1 -1
- package/build/esm/version.js +1 -1
- package/docs/tone.json +1 -1
- package/package.json +2 -2
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { getContext } from "../Global";
|
|
2
2
|
import { Tone } from "../Tone";
|
|
3
3
|
import { Samples, Seconds } from "../type/Units";
|
|
4
|
-
import { isAudioBuffer } from "../util/AdvancedTypeCheck";
|
|
5
4
|
import { optionsFromArguments } from "../util/Defaults";
|
|
6
5
|
import { noOp } from "../util/Interface";
|
|
7
6
|
import { isArray, isNumber, isString } from "../util/TypeCheck";
|
|
@@ -25,7 +24,6 @@ interface ToneAudioBufferOptions {
|
|
|
25
24
|
* @category Core
|
|
26
25
|
*/
|
|
27
26
|
export class ToneAudioBuffer extends Tone {
|
|
28
|
-
|
|
29
27
|
readonly name: string = "ToneAudioBuffer";
|
|
30
28
|
|
|
31
29
|
/**
|
|
@@ -54,23 +52,26 @@ export class ToneAudioBuffer extends Tone {
|
|
|
54
52
|
constructor(
|
|
55
53
|
url?: string | ToneAudioBuffer | AudioBuffer,
|
|
56
54
|
onload?: (buffer: ToneAudioBuffer) => void,
|
|
57
|
-
onerror?: (error: Error) => void
|
|
55
|
+
onerror?: (error: Error) => void
|
|
58
56
|
);
|
|
59
57
|
constructor(options?: Partial<ToneAudioBufferOptions>);
|
|
60
58
|
constructor() {
|
|
61
|
-
|
|
62
59
|
super();
|
|
63
60
|
|
|
64
|
-
const options = optionsFromArguments(
|
|
61
|
+
const options = optionsFromArguments(
|
|
62
|
+
ToneAudioBuffer.getDefaults(),
|
|
63
|
+
arguments,
|
|
64
|
+
["url", "onload", "onerror"]
|
|
65
|
+
);
|
|
65
66
|
|
|
66
67
|
this.reverse = options.reverse;
|
|
67
68
|
this.onload = options.onload;
|
|
68
69
|
|
|
69
|
-
if (
|
|
70
|
-
this.set(options.url);
|
|
71
|
-
} else if (isString(options.url)) {
|
|
70
|
+
if (isString(options.url)) {
|
|
72
71
|
// initiate the download
|
|
73
72
|
this.load(options.url).catch(options.onerror);
|
|
73
|
+
} else if (options.url) {
|
|
74
|
+
this.set(options.url);
|
|
74
75
|
}
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -132,11 +133,13 @@ export class ToneAudioBuffer extends Tone {
|
|
|
132
133
|
* @returns A Promise which resolves with this ToneAudioBuffer
|
|
133
134
|
*/
|
|
134
135
|
async load(url: string): Promise<this> {
|
|
135
|
-
const doneLoading: Promise<void> = ToneAudioBuffer.load(url).then(
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
const doneLoading: Promise<void> = ToneAudioBuffer.load(url).then(
|
|
137
|
+
(audioBuffer) => {
|
|
138
|
+
this.set(audioBuffer);
|
|
139
|
+
// invoke the onload method
|
|
140
|
+
this.onload(this);
|
|
141
|
+
}
|
|
142
|
+
);
|
|
140
143
|
ToneAudioBuffer.downloads.push(doneLoading);
|
|
141
144
|
try {
|
|
142
145
|
await doneLoading;
|
|
@@ -165,11 +168,15 @@ export class ToneAudioBuffer extends Tone {
|
|
|
165
168
|
fromArray(array: Float32Array | Float32Array[]): this {
|
|
166
169
|
const isMultidimensional = isArray(array) && array[0].length > 0;
|
|
167
170
|
const channels = isMultidimensional ? array.length : 1;
|
|
168
|
-
const len = isMultidimensional
|
|
171
|
+
const len = isMultidimensional
|
|
172
|
+
? (array[0] as Float32Array).length
|
|
173
|
+
: array.length;
|
|
169
174
|
const context = getContext();
|
|
170
175
|
const buffer = context.createBuffer(channels, len, context.sampleRate);
|
|
171
|
-
const multiChannelArray: Float32Array[] =
|
|
172
|
-
|
|
176
|
+
const multiChannelArray: Float32Array[] =
|
|
177
|
+
!isMultidimensional && channels === 1
|
|
178
|
+
? [array as Float32Array]
|
|
179
|
+
: (array as Float32Array[]);
|
|
173
180
|
|
|
174
181
|
for (let c = 0; c < channels; c++) {
|
|
175
182
|
buffer.copyToChannel(multiChannelArray[c], c);
|
|
@@ -195,7 +202,7 @@ export class ToneAudioBuffer extends Tone {
|
|
|
195
202
|
}
|
|
196
203
|
}
|
|
197
204
|
// divide by the number of channels
|
|
198
|
-
outputArray = outputArray.map(sample => sample / numChannels);
|
|
205
|
+
outputArray = outputArray.map((sample) => sample / numChannels);
|
|
199
206
|
this.fromArray(outputArray);
|
|
200
207
|
}
|
|
201
208
|
return this;
|
|
@@ -243,11 +250,21 @@ export class ToneAudioBuffer extends Tone {
|
|
|
243
250
|
assert(this.loaded, "Buffer is not loaded");
|
|
244
251
|
const startSamples = Math.floor(start * this.sampleRate);
|
|
245
252
|
const endSamples = Math.floor(end * this.sampleRate);
|
|
246
|
-
assert(
|
|
253
|
+
assert(
|
|
254
|
+
startSamples < endSamples,
|
|
255
|
+
"The start time must be less than the end time"
|
|
256
|
+
);
|
|
247
257
|
const length = endSamples - startSamples;
|
|
248
|
-
const retBuffer = getContext().createBuffer(
|
|
258
|
+
const retBuffer = getContext().createBuffer(
|
|
259
|
+
this.numberOfChannels,
|
|
260
|
+
length,
|
|
261
|
+
this.sampleRate
|
|
262
|
+
);
|
|
249
263
|
for (let channel = 0; channel < this.numberOfChannels; channel++) {
|
|
250
|
-
retBuffer.copyToChannel(
|
|
264
|
+
retBuffer.copyToChannel(
|
|
265
|
+
this.getChannelData(channel).subarray(startSamples, endSamples),
|
|
266
|
+
channel
|
|
267
|
+
);
|
|
251
268
|
}
|
|
252
269
|
return new ToneAudioBuffer(retBuffer);
|
|
253
270
|
}
|
|
@@ -333,7 +350,7 @@ export class ToneAudioBuffer extends Tone {
|
|
|
333
350
|
* @return A ToneAudioBuffer created from the array
|
|
334
351
|
*/
|
|
335
352
|
static fromArray(array: Float32Array | Float32Array[]): ToneAudioBuffer {
|
|
336
|
-
return
|
|
353
|
+
return new ToneAudioBuffer().fromArray(array);
|
|
337
354
|
}
|
|
338
355
|
|
|
339
356
|
/**
|
|
@@ -355,7 +372,6 @@ export class ToneAudioBuffer extends Tone {
|
|
|
355
372
|
* Loads a url using fetch and returns the AudioBuffer.
|
|
356
373
|
*/
|
|
357
374
|
static async load(url: string): Promise<AudioBuffer> {
|
|
358
|
-
|
|
359
375
|
// test if the url contains multiple extensions
|
|
360
376
|
const matches = url.match(/\[([^\]\[]+\|.+)\]$/);
|
|
361
377
|
if (matches) {
|
|
@@ -371,12 +387,19 @@ export class ToneAudioBuffer extends Tone {
|
|
|
371
387
|
}
|
|
372
388
|
|
|
373
389
|
// make sure there is a slash between the baseUrl and the url
|
|
374
|
-
const baseUrl =
|
|
375
|
-
|
|
390
|
+
const baseUrl =
|
|
391
|
+
ToneAudioBuffer.baseUrl === "" ||
|
|
392
|
+
ToneAudioBuffer.baseUrl.endsWith("/")
|
|
393
|
+
? ToneAudioBuffer.baseUrl
|
|
394
|
+
: ToneAudioBuffer.baseUrl + "/";
|
|
395
|
+
|
|
376
396
|
// encode special characters in file path
|
|
377
397
|
const location = document.createElement("a");
|
|
378
|
-
location.href =
|
|
379
|
-
location.pathname = (location.pathname + location.hash)
|
|
398
|
+
location.href = baseUrl + url;
|
|
399
|
+
location.pathname = (location.pathname + location.hash)
|
|
400
|
+
.split("/")
|
|
401
|
+
.map(encodeURIComponent)
|
|
402
|
+
.join("/");
|
|
380
403
|
|
|
381
404
|
const response = await fetch(location.href);
|
|
382
405
|
if (!response.ok) {
|
|
@@ -401,7 +424,9 @@ export class ToneAudioBuffer extends Tone {
|
|
|
401
424
|
static supportsType(url: string): boolean {
|
|
402
425
|
const extensions = url.split(".");
|
|
403
426
|
const extension = extensions[extensions.length - 1];
|
|
404
|
-
const response = document
|
|
427
|
+
const response = document
|
|
428
|
+
.createElement("audio")
|
|
429
|
+
.canPlayType("audio/" + extension);
|
|
405
430
|
return response !== "";
|
|
406
431
|
}
|
|
407
432
|
|
|
@@ -89,19 +89,21 @@ describe("Pattern", () => {
|
|
|
89
89
|
it("is invoked after it's started", () => {
|
|
90
90
|
let invoked = false;
|
|
91
91
|
return Offline(({ transport }) => {
|
|
92
|
+
const values = ["a", "b", "c"];
|
|
92
93
|
let index = 0;
|
|
93
94
|
const pattern = new Pattern((() => {
|
|
94
95
|
invoked = true;
|
|
95
|
-
expect(pattern.value).to.equal(index);
|
|
96
|
+
expect(pattern.value).to.equal(values[index]);
|
|
97
|
+
expect(pattern.index).to.equal(index);
|
|
96
98
|
index++;
|
|
97
|
-
}),
|
|
99
|
+
}), values).start(0);
|
|
98
100
|
transport.start();
|
|
99
101
|
}, 0.2).then(() => {
|
|
100
102
|
expect(invoked).to.be.true;
|
|
101
103
|
});
|
|
102
104
|
});
|
|
103
105
|
|
|
104
|
-
it("passes in the scheduled time and pattern
|
|
106
|
+
it("passes in the scheduled time and pattern note to the callback", () => {
|
|
105
107
|
let invoked = false;
|
|
106
108
|
return Offline(({ transport }) => {
|
|
107
109
|
const startTime = 0.05;
|
|
@@ -109,6 +111,8 @@ describe("Pattern", () => {
|
|
|
109
111
|
expect(time).to.be.a("number");
|
|
110
112
|
expect(time - startTime).to.be.closeTo(0.3, 0.01);
|
|
111
113
|
expect(note).to.be.equal("a");
|
|
114
|
+
expect(pattern.value).to.equal("a");
|
|
115
|
+
expect(pattern.index).to.be.equal(0);
|
|
112
116
|
invoked = true;
|
|
113
117
|
}), ["a"], "up");
|
|
114
118
|
transport.start(startTime);
|
|
@@ -121,16 +125,38 @@ describe("Pattern", () => {
|
|
|
121
125
|
it("passes in the next note of the pattern", () => {
|
|
122
126
|
let counter = 0;
|
|
123
127
|
return Offline(({ transport }) => {
|
|
128
|
+
const values = ["a", "b", "c"];
|
|
124
129
|
const pattern = new Pattern(((time, note) => {
|
|
125
|
-
expect(note).to.equal(counter % 3);
|
|
130
|
+
expect(note).to.equal(values[counter % 3]);
|
|
131
|
+
expect(pattern.value).to.equal(values[counter % 3]);
|
|
132
|
+
expect(pattern.index).to.be.equal(counter % 3);
|
|
126
133
|
counter++;
|
|
127
|
-
}),
|
|
134
|
+
}), values, "up").start(0);
|
|
128
135
|
pattern.interval = "16n";
|
|
129
136
|
transport.start(0);
|
|
130
137
|
}, 0.7).then(() => {
|
|
131
138
|
expect(counter).to.equal(6);
|
|
132
139
|
});
|
|
133
140
|
});
|
|
141
|
+
|
|
142
|
+
it("can modify the pattern type and values", () => {
|
|
143
|
+
let counter = 0;
|
|
144
|
+
return Offline(({ transport }) => {
|
|
145
|
+
const values = ["a", "b", "c"];
|
|
146
|
+
const pattern = new Pattern(((time, note) => {
|
|
147
|
+
expect(note).to.equal(values[counter % 3]);
|
|
148
|
+
expect(pattern.value).to.equal(values[counter % 3]);
|
|
149
|
+
expect(pattern.index).to.be.equal(counter % 3);
|
|
150
|
+
counter++;
|
|
151
|
+
}), ["a"], "down").start(0);
|
|
152
|
+
pattern.interval = "16n";
|
|
153
|
+
pattern.pattern = "up";
|
|
154
|
+
pattern.values = values;
|
|
155
|
+
transport.start(0);
|
|
156
|
+
}, 0.7).then(() => {
|
|
157
|
+
expect(counter).to.equal(6);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
134
160
|
});
|
|
135
161
|
|
|
136
162
|
});
|
package/Tone/event/Pattern.ts
CHANGED
|
@@ -27,12 +27,17 @@ export class Pattern<ValueType> extends Loop<PatternOptions<ValueType>> {
|
|
|
27
27
|
/**
|
|
28
28
|
* The pattern generator function
|
|
29
29
|
*/
|
|
30
|
-
private _pattern: Iterator<
|
|
30
|
+
private _pattern: Iterator<number>;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* The current index
|
|
34
|
+
*/
|
|
35
|
+
private _index?: number;
|
|
31
36
|
|
|
32
37
|
/**
|
|
33
38
|
* The current value
|
|
34
39
|
*/
|
|
35
|
-
|
|
40
|
+
private _value?: ValueType;
|
|
36
41
|
|
|
37
42
|
/**
|
|
38
43
|
* Hold the pattern type
|
|
@@ -67,7 +72,7 @@ export class Pattern<ValueType> extends Loop<PatternOptions<ValueType>> {
|
|
|
67
72
|
|
|
68
73
|
this.callback = options.callback;
|
|
69
74
|
this._values = options.values;
|
|
70
|
-
this._pattern = PatternGenerator(options.values, options.pattern);
|
|
75
|
+
this._pattern = PatternGenerator(options.values.length, options.pattern);
|
|
71
76
|
this._type = options.pattern;
|
|
72
77
|
}
|
|
73
78
|
|
|
@@ -83,8 +88,9 @@ export class Pattern<ValueType> extends Loop<PatternOptions<ValueType>> {
|
|
|
83
88
|
* Internal function called when the notes should be called
|
|
84
89
|
*/
|
|
85
90
|
protected _tick(time: Seconds): void {
|
|
86
|
-
const
|
|
87
|
-
this.
|
|
91
|
+
const index = this._pattern.next() as IteratorResult<ValueType>;
|
|
92
|
+
this._index = index.value;
|
|
93
|
+
this._value = this._values[index.value];
|
|
88
94
|
this.callback(time, this._value);
|
|
89
95
|
}
|
|
90
96
|
|
|
@@ -107,6 +113,13 @@ export class Pattern<ValueType> extends Loop<PatternOptions<ValueType>> {
|
|
|
107
113
|
return this._value;
|
|
108
114
|
}
|
|
109
115
|
|
|
116
|
+
/**
|
|
117
|
+
* The current index of the pattern.
|
|
118
|
+
*/
|
|
119
|
+
get index(): number | undefined {
|
|
120
|
+
return this._index;
|
|
121
|
+
}
|
|
122
|
+
|
|
110
123
|
/**
|
|
111
124
|
* The pattern type. See Tone.CtrlPattern for the full list of patterns.
|
|
112
125
|
*/
|
|
@@ -115,7 +128,7 @@ export class Pattern<ValueType> extends Loop<PatternOptions<ValueType>> {
|
|
|
115
128
|
}
|
|
116
129
|
set pattern(pattern) {
|
|
117
130
|
this._type = pattern;
|
|
118
|
-
this._pattern = PatternGenerator(this._values, this._type);
|
|
131
|
+
this._pattern = PatternGenerator(this._values.length, this._type);
|
|
119
132
|
}
|
|
120
133
|
}
|
|
121
134
|
|
|
@@ -13,22 +13,14 @@ describe("PatternGenerator", () => {
|
|
|
13
13
|
|
|
14
14
|
context("API", () => {
|
|
15
15
|
|
|
16
|
-
it("can be constructed with an
|
|
17
|
-
const pattern = PatternGenerator(
|
|
16
|
+
it("can be constructed with an number and type", () => {
|
|
17
|
+
const pattern = PatternGenerator(4, "down");
|
|
18
18
|
expect(getArrayValues(pattern, 10)).to.deep.equal([3, 2, 1, 0, 3, 2, 1, 0, 3, 2]);
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
it("
|
|
22
|
-
const values = [0, 1, 2, 3, 4];
|
|
23
|
-
const pattern = PatternGenerator(values);
|
|
24
|
-
expect(pattern.next().value).to.equal(0);
|
|
25
|
-
values.shift();
|
|
26
|
-
expect(pattern.next().value).to.equal(2);
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
it("throws an error with an empty array", () => {
|
|
21
|
+
it("throws an error with a number less than 1", () => {
|
|
30
22
|
expect(() => {
|
|
31
|
-
const pattern = PatternGenerator(
|
|
23
|
+
const pattern = PatternGenerator(0);
|
|
32
24
|
pattern.next();
|
|
33
25
|
}).to.throw(Error);
|
|
34
26
|
});
|
|
@@ -37,54 +29,54 @@ describe("PatternGenerator", () => {
|
|
|
37
29
|
context("Patterns", () => {
|
|
38
30
|
|
|
39
31
|
it("does the up pattern", () => {
|
|
40
|
-
const pattern = PatternGenerator(
|
|
32
|
+
const pattern = PatternGenerator(4, "up");
|
|
41
33
|
expect(getArrayValues(pattern, 6)).to.deep.equal([0, 1, 2, 3, 0, 1]);
|
|
42
34
|
});
|
|
43
35
|
|
|
44
36
|
it("does the down pattern", () => {
|
|
45
|
-
const pattern = PatternGenerator(
|
|
37
|
+
const pattern = PatternGenerator(4, "down");
|
|
46
38
|
expect(getArrayValues(pattern, 6)).to.deep.equal([3, 2, 1, 0, 3, 2]);
|
|
47
39
|
});
|
|
48
40
|
|
|
49
41
|
it("does the upDown pattern", () => {
|
|
50
|
-
const pattern = PatternGenerator(
|
|
42
|
+
const pattern = PatternGenerator(4, "upDown");
|
|
51
43
|
expect(getArrayValues(pattern, 10)).to.deep.equal([0, 1, 2, 3, 2, 1, 0, 1, 2, 3]);
|
|
52
44
|
});
|
|
53
45
|
|
|
54
46
|
it("does the downUp pattern", () => {
|
|
55
|
-
const pattern = PatternGenerator(
|
|
47
|
+
const pattern = PatternGenerator(4, "downUp");
|
|
56
48
|
expect(getArrayValues(pattern, 10)).to.deep.equal([3, 2, 1, 0, 1, 2, 3, 2, 1, 0]);
|
|
57
49
|
});
|
|
58
50
|
|
|
59
51
|
it("does the alternateUp pattern", () => {
|
|
60
|
-
const pattern = PatternGenerator(
|
|
52
|
+
const pattern = PatternGenerator(5, "alternateUp");
|
|
61
53
|
expect(getArrayValues(pattern, 10)).to.deep.equal([0, 2, 1, 3, 2, 4, 3, 0, 2, 1]);
|
|
62
54
|
});
|
|
63
55
|
|
|
64
56
|
it("does the alternateDown pattern", () => {
|
|
65
|
-
const pattern = PatternGenerator(
|
|
57
|
+
const pattern = PatternGenerator(5, "alternateDown");
|
|
66
58
|
expect(getArrayValues(pattern, 10)).to.deep.equal([4, 2, 3, 1, 2, 0, 1, 4, 2, 3]);
|
|
67
59
|
});
|
|
68
60
|
|
|
69
61
|
it("outputs random elements from the values", () => {
|
|
70
|
-
const
|
|
71
|
-
const pattern = PatternGenerator(
|
|
62
|
+
const numValues = 5;
|
|
63
|
+
const pattern = PatternGenerator(numValues, "random");
|
|
72
64
|
for (let i = 0; i < 10; i++) {
|
|
73
|
-
expect(
|
|
65
|
+
expect(pattern.next().value).to.be.at.least(0).and.at.most(numValues - 1);
|
|
74
66
|
}
|
|
75
67
|
});
|
|
76
68
|
|
|
77
69
|
it("does randomOnce pattern", () => {
|
|
78
|
-
const pattern = PatternGenerator(
|
|
79
|
-
expect(getArrayValues(pattern, 10).sort()).to.deep.equal([
|
|
70
|
+
const pattern = PatternGenerator(5, "randomOnce");
|
|
71
|
+
expect(getArrayValues(pattern, 10).sort()).to.deep.equal([0, 0, 1, 1, 2, 2, 3, 3, 4, 4]);
|
|
80
72
|
});
|
|
81
73
|
|
|
82
74
|
it("randomly walks up or down 1 step without repeating", () => {
|
|
83
75
|
const values = [0, 1, 2, 3, 4];
|
|
84
|
-
const pattern = PatternGenerator(
|
|
85
|
-
let currentIndex =
|
|
76
|
+
const pattern = PatternGenerator(5, "randomWalk");
|
|
77
|
+
let currentIndex = pattern.next().value;
|
|
86
78
|
for (let i = 0; i < 10; i++) {
|
|
87
|
-
const nextIndex =
|
|
79
|
+
const nextIndex = pattern.next().value;
|
|
88
80
|
expect(currentIndex).to.not.equal(nextIndex);
|
|
89
81
|
// change always equals 1
|
|
90
82
|
expect(Math.abs(currentIndex - nextIndex)).to.equal(1);
|
|
@@ -9,11 +9,11 @@ export type PatternName = "up" | "down" | "upDown" | "downUp" | "alternateUp" |
|
|
|
9
9
|
/**
|
|
10
10
|
* Start at the first value and go up to the last
|
|
11
11
|
*/
|
|
12
|
-
function* upPatternGen<T>(
|
|
12
|
+
function* upPatternGen<T>(numValues: number): IterableIterator<number> {
|
|
13
13
|
let index = 0;
|
|
14
|
-
while (index <
|
|
15
|
-
index =
|
|
16
|
-
yield
|
|
14
|
+
while (index < numValues) {
|
|
15
|
+
index = clamp(index, 0, numValues - 1);
|
|
16
|
+
yield index;
|
|
17
17
|
index++;
|
|
18
18
|
}
|
|
19
19
|
}
|
|
@@ -21,11 +21,11 @@ function* upPatternGen<T>(values: T[]): IterableIterator<T> {
|
|
|
21
21
|
/**
|
|
22
22
|
* Start at the last value and go down to 0
|
|
23
23
|
*/
|
|
24
|
-
function* downPatternGen<T>(
|
|
25
|
-
let index =
|
|
24
|
+
function* downPatternGen<T>(numValues: number): IterableIterator<number> {
|
|
25
|
+
let index = numValues - 1;
|
|
26
26
|
while (index >= 0) {
|
|
27
|
-
index =
|
|
28
|
-
yield
|
|
27
|
+
index = clamp(index, 0, numValues - 1);
|
|
28
|
+
yield index;
|
|
29
29
|
index--;
|
|
30
30
|
}
|
|
31
31
|
}
|
|
@@ -33,30 +33,23 @@ function* downPatternGen<T>(values: T[]): IterableIterator<T> {
|
|
|
33
33
|
/**
|
|
34
34
|
* Infinitely yield the generator
|
|
35
35
|
*/
|
|
36
|
-
function* infiniteGen<T>(
|
|
36
|
+
function* infiniteGen<T>(numValues: number, gen: typeof upPatternGen): IterableIterator<number> {
|
|
37
37
|
while (true) {
|
|
38
|
-
yield* gen(
|
|
38
|
+
yield* gen(numValues);
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
/**
|
|
43
|
-
* Make sure that the index is in the given range
|
|
44
|
-
*/
|
|
45
|
-
function clampToArraySize(index: number, values: any[]): number {
|
|
46
|
-
return clamp(index, 0, values.length - 1);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
42
|
/**
|
|
50
43
|
* Alternate between two generators
|
|
51
44
|
*/
|
|
52
|
-
function* alternatingGenerator<T>(
|
|
53
|
-
let index = directionUp ? 0 :
|
|
45
|
+
function* alternatingGenerator<T>(numValues: number, directionUp: boolean): IterableIterator<number> {
|
|
46
|
+
let index = directionUp ? 0 : numValues - 1;
|
|
54
47
|
while (true) {
|
|
55
|
-
index =
|
|
56
|
-
yield
|
|
48
|
+
index = clamp(index, 0, numValues - 1);
|
|
49
|
+
yield index;
|
|
57
50
|
if (directionUp) {
|
|
58
51
|
index++;
|
|
59
|
-
if (index >=
|
|
52
|
+
if (index >= numValues - 1) {
|
|
60
53
|
directionUp = false;
|
|
61
54
|
}
|
|
62
55
|
} else {
|
|
@@ -71,12 +64,12 @@ function* alternatingGenerator<T>(values: T[], directionUp: boolean): IterableIt
|
|
|
71
64
|
/**
|
|
72
65
|
* Starting from the bottom move up 2, down 1
|
|
73
66
|
*/
|
|
74
|
-
function* jumpUp<T>(
|
|
67
|
+
function* jumpUp<T>(numValues: number): IterableIterator<number> {
|
|
75
68
|
let index = 0;
|
|
76
69
|
let stepIndex = 0;
|
|
77
|
-
while (index <
|
|
78
|
-
index =
|
|
79
|
-
yield
|
|
70
|
+
while (index < numValues) {
|
|
71
|
+
index = clamp(index, 0, numValues - 1);
|
|
72
|
+
yield index;
|
|
80
73
|
stepIndex++;
|
|
81
74
|
index += (stepIndex % 2 ? 2 : -1);
|
|
82
75
|
}
|
|
@@ -85,12 +78,12 @@ function* jumpUp<T>(values: T[]): IterableIterator<T> {
|
|
|
85
78
|
/**
|
|
86
79
|
* Starting from the top move down 2, up 1
|
|
87
80
|
*/
|
|
88
|
-
function* jumpDown<T>(
|
|
89
|
-
let index =
|
|
81
|
+
function* jumpDown<T>(numValues: number): IterableIterator<number> {
|
|
82
|
+
let index = numValues - 1;
|
|
90
83
|
let stepIndex = 0;
|
|
91
84
|
while (index >= 0) {
|
|
92
|
-
index =
|
|
93
|
-
yield
|
|
85
|
+
index = clamp(index, 0, numValues - 1);
|
|
86
|
+
yield index;
|
|
94
87
|
stepIndex++;
|
|
95
88
|
index += (stepIndex % 2 ? -2 : 1);
|
|
96
89
|
}
|
|
@@ -99,78 +92,78 @@ function* jumpDown<T>(values: T[]): IterableIterator<T> {
|
|
|
99
92
|
/**
|
|
100
93
|
* Choose a random index each time
|
|
101
94
|
*/
|
|
102
|
-
function* randomGen<T>(
|
|
95
|
+
function* randomGen<T>(numValues: number): IterableIterator<number> {
|
|
103
96
|
while (true) {
|
|
104
|
-
const randomIndex = Math.floor(Math.random() *
|
|
105
|
-
yield
|
|
97
|
+
const randomIndex = Math.floor(Math.random() * numValues);
|
|
98
|
+
yield randomIndex;
|
|
106
99
|
}
|
|
107
100
|
}
|
|
108
101
|
|
|
109
102
|
/**
|
|
110
103
|
* Randomly go through all of the values once before choosing a new random order
|
|
111
104
|
*/
|
|
112
|
-
function* randomOnce<T>(
|
|
105
|
+
function* randomOnce<T>(numValues: number): IterableIterator<number> {
|
|
113
106
|
// create an array of indices
|
|
114
107
|
const copy: number[] = [];
|
|
115
|
-
for (let i = 0; i <
|
|
108
|
+
for (let i = 0; i < numValues; i++) {
|
|
116
109
|
copy.push(i);
|
|
117
110
|
}
|
|
118
111
|
while (copy.length > 0) {
|
|
119
112
|
// random choose an index, and then remove it so it's not chosen again
|
|
120
113
|
const randVal = copy.splice(Math.floor(copy.length * Math.random()), 1);
|
|
121
|
-
const index =
|
|
122
|
-
yield
|
|
114
|
+
const index = clamp(randVal[0], 0, numValues - 1);
|
|
115
|
+
yield index;
|
|
123
116
|
}
|
|
124
117
|
}
|
|
125
118
|
|
|
126
119
|
/**
|
|
127
|
-
* Randomly choose to walk up or down 1 index
|
|
120
|
+
* Randomly choose to walk up or down 1 index
|
|
128
121
|
*/
|
|
129
|
-
function* randomWalk<T>(
|
|
130
|
-
// randomly choose a starting index
|
|
131
|
-
let index = Math.floor(Math.random() *
|
|
122
|
+
function* randomWalk<T>(numValues: number): IterableIterator<number> {
|
|
123
|
+
// randomly choose a starting index
|
|
124
|
+
let index = Math.floor(Math.random() * numValues);
|
|
132
125
|
while (true) {
|
|
133
126
|
if (index === 0) {
|
|
134
|
-
index++; // at bottom
|
|
135
|
-
} else if (index ===
|
|
136
|
-
index--; // at top
|
|
127
|
+
index++; // at bottom, so force upward step
|
|
128
|
+
} else if (index === numValues - 1) {
|
|
129
|
+
index--; // at top, so force downward step
|
|
137
130
|
} else if (Math.random() < 0.5) { // else choose random downward or upward step
|
|
138
131
|
index--;
|
|
139
132
|
} else {
|
|
140
133
|
index++;
|
|
141
134
|
}
|
|
142
|
-
yield
|
|
135
|
+
yield index;
|
|
143
136
|
}
|
|
144
137
|
}
|
|
145
138
|
|
|
146
139
|
/**
|
|
147
|
-
* PatternGenerator returns a generator which will
|
|
148
|
-
*
|
|
149
|
-
* @param
|
|
140
|
+
* PatternGenerator returns a generator which will yield numbers between 0 and numValues
|
|
141
|
+
* according to the passed in pattern that can be used as indexes into an array of size numValues.
|
|
142
|
+
* @param numValues The size of the array to emit indexes for
|
|
150
143
|
* @param pattern The name of the pattern use when iterating over
|
|
151
144
|
* @param index Where to start in the offset of the values array
|
|
152
145
|
*/
|
|
153
|
-
export function* PatternGenerator
|
|
146
|
+
export function* PatternGenerator(numValues: number, pattern: PatternName = "up", index = 0): Iterator<number> {
|
|
154
147
|
// safeguards
|
|
155
|
-
assert(
|
|
148
|
+
assert(numValues >= 1, "The number of values must be at least one");
|
|
156
149
|
switch (pattern) {
|
|
157
150
|
case "up" :
|
|
158
|
-
yield* infiniteGen(
|
|
151
|
+
yield* infiniteGen(numValues, upPatternGen);
|
|
159
152
|
case "down" :
|
|
160
|
-
yield* infiniteGen(
|
|
153
|
+
yield* infiniteGen(numValues, downPatternGen);
|
|
161
154
|
case "upDown" :
|
|
162
|
-
yield* alternatingGenerator(
|
|
155
|
+
yield* alternatingGenerator(numValues, true);
|
|
163
156
|
case "downUp" :
|
|
164
|
-
yield* alternatingGenerator(
|
|
157
|
+
yield* alternatingGenerator(numValues, false);
|
|
165
158
|
case "alternateUp":
|
|
166
|
-
yield* infiniteGen(
|
|
159
|
+
yield* infiniteGen(numValues, jumpUp);
|
|
167
160
|
case "alternateDown":
|
|
168
|
-
yield* infiniteGen(
|
|
161
|
+
yield* infiniteGen(numValues, jumpDown);
|
|
169
162
|
case "random":
|
|
170
|
-
yield* randomGen(
|
|
163
|
+
yield* randomGen(numValues);
|
|
171
164
|
case "randomOnce":
|
|
172
|
-
yield* infiniteGen(
|
|
165
|
+
yield* infiniteGen(numValues, randomOnce);
|
|
173
166
|
case "randomWalk":
|
|
174
|
-
yield* randomWalk(
|
|
167
|
+
yield* randomWalk(numValues);
|
|
175
168
|
}
|
|
176
169
|
}
|