UNPKG

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