tone 14.8.36 → 14.8.39
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/component/filter/FeedbackCombFilter.test.ts +16 -0
- package/Tone/core/clock/Ticker.ts +11 -5
- package/Tone/core/context/BaseContext.ts +1 -2
- package/Tone/core/context/Context.test.ts +2 -0
- package/Tone/core/context/Context.ts +35 -50
- package/Tone/core/context/DummyContext.test.ts +1 -1
- package/Tone/core/context/DummyContext.ts +1 -1
- package/Tone/core/context/ToneAudioBuffer.ts +52 -27
- package/Tone/core/worklet/ToneAudioWorklet.ts +1 -1
- package/Tone/effect/BitCrusher.test.ts +16 -0
- 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/event/Sequence.ts +1 -1
- 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/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/BaseContext.d.ts +1 -1
- package/build/esm/core/context/BaseContext.js.map +1 -1
- package/build/esm/core/context/Context.d.ts +14 -15
- package/build/esm/core/context/Context.js +32 -38
- package/build/esm/core/context/Context.js.map +1 -1
- package/build/esm/core/context/DummyContext.d.ts +1 -1
- package/build/esm/core/context/DummyContext.js +1 -1
- package/build/esm/core/context/DummyContext.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/core/worklet/ToneAudioWorklet.js +1 -1
- package/build/esm/core/worklet/ToneAudioWorklet.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/event/Sequence.d.ts +1 -1
- package/build/esm/event/Sequence.js +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
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
|
}
|
package/Tone/event/Sequence.ts
CHANGED
|
@@ -20,7 +20,7 @@ interface SequenceOptions<T> extends Omit<ToneEventOptions<T>, "value"> {
|
|
|
20
20
|
* in an array of events which will be spaced at the
|
|
21
21
|
* given subdivision. Sub-arrays will subdivide that beat
|
|
22
22
|
* by the number of items are in the array.
|
|
23
|
-
* Sequence notation inspiration from [Tidal](http://
|
|
23
|
+
* Sequence notation inspiration from [Tidal Cycles](http://tidalcycles.org/)
|
|
24
24
|
* @example
|
|
25
25
|
* const synth = new Tone.Synth().toDestination();
|
|
26
26
|
* const seq = new Tone.Sequence((time, note) => {
|
|
@@ -1,9 +1,22 @@
|
|
|
1
1
|
import { Envelope, EnvelopeOptions } from "../component/envelope/Envelope";
|
|
2
2
|
import { Filter } from "../component/filter/Filter";
|
|
3
3
|
import { Gain } from "../core/context/Gain";
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
import {
|
|
5
|
+
ToneAudioNode,
|
|
6
|
+
ToneAudioNodeOptions,
|
|
7
|
+
} from "../core/context/ToneAudioNode";
|
|
8
|
+
import {
|
|
9
|
+
Frequency,
|
|
10
|
+
NormalRange,
|
|
11
|
+
Positive,
|
|
12
|
+
Seconds,
|
|
13
|
+
Time,
|
|
14
|
+
} from "../core/type/Units";
|
|
15
|
+
import {
|
|
16
|
+
deepMerge,
|
|
17
|
+
omitFromObject,
|
|
18
|
+
optionsFromArguments,
|
|
19
|
+
} from "../core/util/Defaults";
|
|
7
20
|
import { noOp, RecursivePartial } from "../core/util/Interface";
|
|
8
21
|
import { Multiply } from "../signal/Multiply";
|
|
9
22
|
import { Scale } from "../signal/Scale";
|
|
@@ -23,17 +36,15 @@ export interface MetalSynthOptions extends MonophonicOptions {
|
|
|
23
36
|
* Inharmonic ratio of frequencies based on the Roland TR-808
|
|
24
37
|
* Taken from https://ccrma.stanford.edu/papers/tr-808-cymbal-physically-informed-circuit-bendable-digital-model
|
|
25
38
|
*/
|
|
26
|
-
const inharmRatios: number[] = [1.0, 1.483, 1.932, 2.546, 2.
|
|
39
|
+
const inharmRatios: number[] = [1.0, 1.483, 1.932, 2.546, 2.63, 3.897];
|
|
27
40
|
|
|
28
41
|
/**
|
|
29
42
|
* A highly inharmonic and spectrally complex source with a highpass filter
|
|
30
43
|
* and amplitude envelope which is good for making metallophone sounds.
|
|
31
44
|
* Based on CymbalSynth by [@polyrhythmatic](https://github.com/polyrhythmatic).
|
|
32
|
-
* Inspiration from [Sound on Sound](https://shorturl.at/rSZ12).
|
|
33
45
|
* @category Instrument
|
|
34
46
|
*/
|
|
35
47
|
export class MetalSynth extends Monophonic<MetalSynthOptions> {
|
|
36
|
-
|
|
37
48
|
readonly name: string = "MetalSynth";
|
|
38
49
|
|
|
39
50
|
/**
|
|
@@ -84,10 +95,13 @@ export class MetalSynth extends Monophonic<MetalSynthOptions> {
|
|
|
84
95
|
*/
|
|
85
96
|
readonly envelope: Envelope;
|
|
86
97
|
|
|
87
|
-
constructor(options?: RecursivePartial<MetalSynthOptions>)
|
|
98
|
+
constructor(options?: RecursivePartial<MetalSynthOptions>);
|
|
88
99
|
constructor() {
|
|
89
100
|
super(optionsFromArguments(MetalSynth.getDefaults(), arguments));
|
|
90
|
-
const options = optionsFromArguments(
|
|
101
|
+
const options = optionsFromArguments(
|
|
102
|
+
MetalSynth.getDefaults(),
|
|
103
|
+
arguments
|
|
104
|
+
);
|
|
91
105
|
|
|
92
106
|
this.detune = new Signal({
|
|
93
107
|
context: this.context,
|
|
@@ -158,12 +172,15 @@ export class MetalSynth extends Monophonic<MetalSynthOptions> {
|
|
|
158
172
|
static getDefaults(): MetalSynthOptions {
|
|
159
173
|
return deepMerge(Monophonic.getDefaults(), {
|
|
160
174
|
envelope: Object.assign(
|
|
161
|
-
omitFromObject(
|
|
175
|
+
omitFromObject(
|
|
176
|
+
Envelope.getDefaults(),
|
|
177
|
+
Object.keys(ToneAudioNode.getDefaults())
|
|
178
|
+
),
|
|
162
179
|
{
|
|
163
180
|
attack: 0.001,
|
|
164
181
|
decay: 1.4,
|
|
165
182
|
release: 0.2,
|
|
166
|
-
}
|
|
183
|
+
}
|
|
167
184
|
),
|
|
168
185
|
harmonicity: 5.1,
|
|
169
186
|
modulationIndex: 32,
|
|
@@ -177,24 +194,33 @@ export class MetalSynth extends Monophonic<MetalSynthOptions> {
|
|
|
177
194
|
* @param time When the attack should be triggered.
|
|
178
195
|
* @param velocity The velocity that the envelope should be triggered at.
|
|
179
196
|
*/
|
|
180
|
-
protected _triggerEnvelopeAttack(
|
|
197
|
+
protected _triggerEnvelopeAttack(
|
|
198
|
+
time: Seconds,
|
|
199
|
+
velocity: NormalRange = 1
|
|
200
|
+
): this {
|
|
181
201
|
this.envelope.triggerAttack(time, velocity);
|
|
182
|
-
this._oscillators.forEach(osc => osc.start(time));
|
|
202
|
+
this._oscillators.forEach((osc) => osc.start(time));
|
|
183
203
|
if (this.envelope.sustain === 0) {
|
|
184
|
-
this._oscillators.forEach(osc => {
|
|
185
|
-
osc.stop(
|
|
204
|
+
this._oscillators.forEach((osc) => {
|
|
205
|
+
osc.stop(
|
|
206
|
+
time +
|
|
207
|
+
this.toSeconds(this.envelope.attack) +
|
|
208
|
+
this.toSeconds(this.envelope.decay)
|
|
209
|
+
);
|
|
186
210
|
});
|
|
187
211
|
}
|
|
188
212
|
return this;
|
|
189
213
|
}
|
|
190
|
-
|
|
214
|
+
|
|
191
215
|
/**
|
|
192
216
|
* Trigger the release of the envelope.
|
|
193
217
|
* @param time When the release should be triggered.
|
|
194
218
|
*/
|
|
195
219
|
protected _triggerEnvelopeRelease(time: Seconds): this {
|
|
196
220
|
this.envelope.triggerRelease(time);
|
|
197
|
-
this._oscillators.forEach(osc =>
|
|
221
|
+
this._oscillators.forEach((osc) =>
|
|
222
|
+
osc.stop(time + this.toSeconds(this.envelope.release))
|
|
223
|
+
);
|
|
198
224
|
return this;
|
|
199
225
|
}
|
|
200
226
|
|
|
@@ -213,7 +239,7 @@ export class MetalSynth extends Monophonic<MetalSynthOptions> {
|
|
|
213
239
|
return this._oscillators[0].modulationIndex.value;
|
|
214
240
|
}
|
|
215
241
|
set modulationIndex(val) {
|
|
216
|
-
this._oscillators.forEach(osc => (osc.modulationIndex.value = val));
|
|
242
|
+
this._oscillators.forEach((osc) => (osc.modulationIndex.value = val));
|
|
217
243
|
}
|
|
218
244
|
|
|
219
245
|
/**
|
|
@@ -226,7 +252,7 @@ export class MetalSynth extends Monophonic<MetalSynthOptions> {
|
|
|
226
252
|
return this._oscillators[0].harmonicity.value;
|
|
227
253
|
}
|
|
228
254
|
set harmonicity(val) {
|
|
229
|
-
this._oscillators.forEach(osc => (osc.harmonicity.value = val));
|
|
255
|
+
this._oscillators.forEach((osc) => (osc.harmonicity.value = val));
|
|
230
256
|
}
|
|
231
257
|
|
|
232
258
|
/**
|
|
@@ -254,13 +280,14 @@ export class MetalSynth extends Monophonic<MetalSynthOptions> {
|
|
|
254
280
|
}
|
|
255
281
|
set octaves(val) {
|
|
256
282
|
this._octaves = val;
|
|
257
|
-
this._filterFreqScaler.max =
|
|
283
|
+
this._filterFreqScaler.max =
|
|
284
|
+
this._filterFreqScaler.min * Math.pow(2, val);
|
|
258
285
|
}
|
|
259
286
|
|
|
260
287
|
dispose(): this {
|
|
261
288
|
super.dispose();
|
|
262
|
-
this._oscillators.forEach(osc => osc.dispose());
|
|
263
|
-
this._freqMultipliers.forEach(freqMult => freqMult.dispose());
|
|
289
|
+
this._oscillators.forEach((osc) => osc.dispose());
|
|
290
|
+
this._freqMultipliers.forEach((freqMult) => freqMult.dispose());
|
|
264
291
|
this.frequency.dispose();
|
|
265
292
|
this.detune.dispose();
|
|
266
293
|
this._filterFreqScaler.dispose();
|
|
@@ -152,7 +152,7 @@ describe("ToneConstantSource", () => {
|
|
|
152
152
|
expect(source.getStateAtTime(0)).to.equal("stopped");
|
|
153
153
|
expect(source.getStateAtTime(currentTime)).to.equal("started");
|
|
154
154
|
setTimeout(() => {
|
|
155
|
-
currentTime = source.
|
|
155
|
+
currentTime = source.now();
|
|
156
156
|
source.stop(0);
|
|
157
157
|
expect(source.getStateAtTime(currentTime + 0.01)).to.equal("stopped");
|
|
158
158
|
source.dispose();
|
|
@@ -168,7 +168,7 @@ export abstract class OneShotSource<
|
|
|
168
168
|
|
|
169
169
|
// schedule the stop callback
|
|
170
170
|
this._stopTime = this.toSeconds(time) + fadeOutTime;
|
|
171
|
-
this._stopTime = Math.max(this._stopTime, this.
|
|
171
|
+
this._stopTime = Math.max(this._stopTime, this.now());
|
|
172
172
|
if (fadeOutTime > 0) {
|
|
173
173
|
// start the fade out curve at the given time
|
|
174
174
|
if (this._curve === "linear") {
|
|
@@ -696,5 +696,30 @@ describe("Player", () => {
|
|
|
696
696
|
expect(buff.getRmsAtTime(duration + 0.1)).to.be.closeTo(0, 0.1);
|
|
697
697
|
});
|
|
698
698
|
});
|
|
699
|
+
|
|
700
|
+
it("stops only last activeSource when restarting at intervals < latencyHint", (done) => {
|
|
701
|
+
const originalLookAhead = getContext().lookAhead;
|
|
702
|
+
getContext().lookAhead = .3;
|
|
703
|
+
const player = new Player({
|
|
704
|
+
onload(): void {
|
|
705
|
+
player.start(undefined, undefined, 1);
|
|
706
|
+
setTimeout(() => player.restart(undefined, undefined, 1), 50);
|
|
707
|
+
setTimeout(() => player.restart(undefined, undefined, 1), 100);
|
|
708
|
+
setTimeout(() => player.restart(undefined, undefined, 1), 150);
|
|
709
|
+
setTimeout(() => {
|
|
710
|
+
player.restart(undefined, undefined, 1);
|
|
711
|
+
const checkStopTimes = new Set();
|
|
712
|
+
player["_activeSources"].forEach(source => {
|
|
713
|
+
checkStopTimes.add(source["_stopTime"]);
|
|
714
|
+
});
|
|
715
|
+
getContext().lookAhead = originalLookAhead;
|
|
716
|
+
// ensure each source has a different stopTime
|
|
717
|
+
expect(checkStopTimes.size).to.equal(player["_activeSources"].size);
|
|
718
|
+
done();
|
|
719
|
+
}, 250);
|
|
720
|
+
},
|
|
721
|
+
url: "./audio/sine.wav",
|
|
722
|
+
});
|
|
723
|
+
});
|
|
699
724
|
});
|
|
700
725
|
});
|
|
@@ -284,7 +284,7 @@ export class Player extends Source<PlayerOptions> {
|
|
|
284
284
|
}
|
|
285
285
|
|
|
286
286
|
protected _restart(time?: Seconds, offset?: Time, duration?: Time): void {
|
|
287
|
-
this.
|
|
287
|
+
[...this._activeSources].pop()?.stop(time); // explicitly stop only the most recently created source, to avoid edge case when > 1 source exists and _stop() erroneously sets all stop times past original end offset
|
|
288
288
|
this._start(time, offset, duration);
|
|
289
289
|
}
|
|
290
290
|
|
|
@@ -191,7 +191,7 @@ describe("ToneOscillatorNode", () => {
|
|
|
191
191
|
expect(osc.getStateAtTime(0)).to.equal("stopped");
|
|
192
192
|
expect(osc.getStateAtTime(currentTime)).to.equal("started");
|
|
193
193
|
setTimeout(() => {
|
|
194
|
-
currentTime = osc.
|
|
194
|
+
currentTime = osc.now();
|
|
195
195
|
osc.stop(0);
|
|
196
196
|
expect(osc.getStateAtTime(currentTime + 0.01)).to.equal("stopped");
|
|
197
197
|
osc.dispose();
|
package/Tone/version.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const version: string = "14.8.
|
|
1
|
+
export const version: string = "14.8.39";
|