/
Sequence.ts
300 lines (265 loc) · 7.49 KB
/
Sequence.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
import { TicksClass } from "../core/type/Ticks";
import { NormalRange, Positive, Seconds, Ticks, Time, TransportTime } from "../core/type/Units";
import { omitFromObject, optionsFromArguments } from "../core/util/Defaults";
import { isArray, isString } from "../core/util/TypeCheck";
import { Part } from "./Part";
import { ToneEvent, ToneEventCallback, ToneEventOptions } from "./ToneEvent";
type SequenceEventDescription<T> = Array<T | Array<T | Array<T | Array<T | Array<T | T[]>>>>>;
interface SequenceOptions<T> extends Omit<ToneEventOptions<T>, "value"> {
loopStart: number;
loopEnd: number;
subdivision: Time;
events: SequenceEventDescription<T>;
}
/**
* A sequence is an alternate notation of a part. Instead
* of passing in an array of [time, event] pairs, pass
* in an array of events which will be spaced at the
* given subdivision. Sub-arrays will subdivide that beat
* by the number of items are in the array.
* Sequence notation inspiration from [Tidal](http://yaxu.org/tidal/)
* @example
* const synth = new Tone.Synth().toDestination();
* const seq = new Tone.Sequence((time, note) => {
* synth.triggerAttackRelease(note, 0.1, time);
* // subdivisions are given as subarrays
* }, ["C4", ["E4", "D4", "E4"], "G4", ["A4", "G4"]]).start(0);
* Tone.Transport.start();
* @category Event
*/
export class Sequence<ValueType = any> extends ToneEvent<ValueType> {
readonly name: string = "Sequence";
/**
* The subdivison of each note
*/
private _subdivision: Ticks;
/**
* The object responsible for scheduling all of the events
*/
private _part: Part = new Part({
callback: this._seqCallback.bind(this),
context: this.context,
});
/**
* private reference to all of the sequence proxies
*/
private _events: SequenceEventDescription<ValueType> = [];
/**
* The proxied array
*/
private _eventsArray: SequenceEventDescription<ValueType> = [];
/**
* @param callback The callback to invoke with every note
* @param sequence The sequence
* @param subdivision The subdivision between which events are placed.
*/
constructor(
callback?: ToneEventCallback<ValueType>,
events?: SequenceEventDescription<ValueType>,
subdivision?: Time,
);
constructor(options?: Partial<SequenceOptions<ValueType>>);
constructor() {
super(optionsFromArguments(Sequence.getDefaults(), arguments, ["callback", "events", "subdivision"]));
const options = optionsFromArguments(Sequence.getDefaults(), arguments, ["callback", "events", "subdivision"]);
this._subdivision = this.toTicks(options.subdivision);
this.events = options.events;
// set all of the values
this.loop = options.loop;
this.loopStart = options.loopStart;
this.loopEnd = options.loopEnd;
this.playbackRate = options.playbackRate;
this.probability = options.probability;
this.humanize = options.humanize;
this.mute = options.mute;
this.playbackRate = options.playbackRate;
}
static getDefaults(): SequenceOptions<any> {
return Object.assign(omitFromObject(ToneEvent.getDefaults(), ["value"]), {
events: [],
loop: true,
loopEnd: 0,
loopStart: 0,
subdivision: "8n",
});
}
/**
* The internal callback for when an event is invoked
*/
private _seqCallback(time: Seconds, value: any): void {
if (value !== null) {
this.callback(time, value);
}
}
/**
* The sequence
*/
get events(): any[] {
return this._events;
}
set events(s) {
this.clear();
this._eventsArray = s;
this._events = this._createSequence(this._eventsArray);
this._eventsUpdated();
}
/**
* Start the part at the given time.
* @param time When to start the part.
* @param offset The offset index to start at
*/
start(time?: TransportTime, offset?: number): this {
this._part.start(time, offset ? this._indexTime(offset) : offset);
return this;
}
/**
* Stop the part at the given time.
* @param time When to stop the part.
*/
stop(time?: TransportTime): this {
this._part.stop(time);
return this;
}
/**
* The subdivision of the sequence. This can only be
* set in the constructor. The subdivision is the
* interval between successive steps.
*/
get subdivision(): Seconds {
return new TicksClass(this.context, this._subdivision).toSeconds();
}
/**
* Create a sequence proxy which can be monitored to create subsequences
*/
private _createSequence(array: any[]): any[] {
return new Proxy(array, {
get: (target: any[], property: PropertyKey): any => {
// property is index in this case
return target[property];
},
set: (target: any[], property: PropertyKey, value: any): boolean => {
if (isString(property) && isFinite(parseInt(property, 10))) {
if (isArray(value)) {
target[property] = this._createSequence(value);
} else {
target[property] = value;
}
} else {
target[property] = value;
}
this._eventsUpdated();
// return true to accept the changes
return true;
},
});
}
/**
* When the sequence has changed, all of the events need to be recreated
*/
private _eventsUpdated(): void {
this._part.clear();
this._rescheduleSequence(this._eventsArray, this._subdivision, this.startOffset);
// update the loopEnd
this.loopEnd = this.loopEnd;
}
/**
* reschedule all of the events that need to be rescheduled
*/
private _rescheduleSequence(sequence: any[], subdivision: Ticks, startOffset: Ticks): void {
sequence.forEach((value, index) => {
const eventOffset = index * (subdivision) + startOffset;
if (isArray(value)) {
this._rescheduleSequence(value, subdivision / value.length, eventOffset);
} else {
const startTime = new TicksClass(this.context, eventOffset, "i").toSeconds();
this._part.add(startTime, value);
}
});
}
/**
* Get the time of the index given the Sequence's subdivision
* @param index
* @return The time of that index
*/
private _indexTime(index: number): Seconds {
return new TicksClass(this.context, index * (this._subdivision) + this.startOffset).toSeconds();
}
/**
* Clear all of the events
*/
clear(): this {
this._part.clear();
return this;
}
dispose(): this {
super.dispose();
this._part.dispose();
return this;
}
//-------------------------------------
// PROXY CALLS
//-------------------------------------
get loop(): boolean | number {
return this._part.loop;
}
set loop(l) {
this._part.loop = l;
}
/**
* The index at which the sequence should start looping
*/
get loopStart(): number {
return this._loopStart;
}
set loopStart(index) {
this._loopStart = index;
this._part.loopStart = this._indexTime(index);
}
/**
* The index at which the sequence should end looping
*/
get loopEnd(): number {
return this._loopEnd;
}
set loopEnd(index) {
this._loopEnd = index;
if (index === 0) {
this._part.loopEnd = this._indexTime(this._eventsArray.length);
} else {
this._part.loopEnd = this._indexTime(index);
}
}
get startOffset(): Ticks {
return this._part.startOffset;
}
set startOffset(start) {
this._part.startOffset = start;
}
get playbackRate(): Positive {
return this._part.playbackRate;
}
set playbackRate(rate) {
this._part.playbackRate = rate;
}
get probability(): NormalRange {
return this._part.probability;
}
set probability(prob) {
this._part.probability = prob;
}
get progress(): NormalRange {
return this._part.progress;
}
get humanize(): boolean | Time {
return this._part.humanize;
}
set humanize(variation) {
this._part.humanize = variation;
}
/**
* The number of scheduled events
*/
get length(): number {
return this._part.length;
}
}