1 | import { TicksClass } from "../core/type/Ticks.js";
|
2 | import {
|
3 | NormalRange,
|
4 | Positive,
|
5 | Seconds,
|
6 | Ticks,
|
7 | Time,
|
8 | TransportTime,
|
9 | } from "../core/type/Units.js";
|
10 | import { omitFromObject, optionsFromArguments } from "../core/util/Defaults.js";
|
11 | import { isArray, isString } from "../core/util/TypeCheck.js";
|
12 | import { Part } from "./Part.js";
|
13 | import { ToneEvent, ToneEventCallback, ToneEventOptions } from "./ToneEvent.js";
|
14 |
|
15 | type SequenceEventDescription<T> = Array<T | SequenceEventDescription<T>>;
|
16 |
|
17 | interface SequenceOptions<T> extends Omit<ToneEventOptions<T>, "value"> {
|
18 | loopStart: number;
|
19 | loopEnd: number;
|
20 | subdivision: Time;
|
21 | events: SequenceEventDescription<T>;
|
22 | }
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 |
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 | export class Sequence<ValueType = any> extends ToneEvent<ValueType> {
|
41 | readonly name: string = "Sequence";
|
42 |
|
43 | |
44 |
|
45 |
|
46 | private _subdivision: Ticks;
|
47 |
|
48 | |
49 |
|
50 |
|
51 | private _part: Part = new Part({
|
52 | callback: this._seqCallback.bind(this),
|
53 | context: this.context,
|
54 | });
|
55 |
|
56 | |
57 |
|
58 |
|
59 | private _events: SequenceEventDescription<ValueType> = [];
|
60 |
|
61 | |
62 |
|
63 |
|
64 | private _eventsArray: SequenceEventDescription<ValueType> = [];
|
65 |
|
66 | |
67 |
|
68 |
|
69 |
|
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 |
|
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 |
|
137 |
|
138 |
|
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 |
|
147 |
|
148 |
|
149 | stop(time?: TransportTime): this {
|
150 | this._part.stop(time);
|
151 | return this;
|
152 | }
|
153 |
|
154 | |
155 |
|
156 |
|
157 |
|
158 |
|
159 | get subdivision(): Seconds {
|
160 | return new TicksClass(this.context, this._subdivision).toSeconds();
|
161 | }
|
162 |
|
163 | |
164 |
|
165 |
|
166 | private _createSequence(array: any[]): any[] {
|
167 | return new Proxy(array, {
|
168 | get: (target: any[], property: PropertyKey): any => {
|
169 |
|
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 |
|
188 | return true;
|
189 | },
|
190 | });
|
191 | }
|
192 |
|
193 | |
194 |
|
195 |
|
196 | private _eventsUpdated(): void {
|
197 | this._part.clear();
|
198 | this._rescheduleSequence(
|
199 | this._eventsArray,
|
200 | this._subdivision,
|
201 | this.startOffset
|
202 | );
|
203 |
|
204 | this.loopEnd = this.loopEnd;
|
205 | }
|
206 |
|
207 | |
208 |
|
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 |
|
236 |
|
237 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
331 |
|
332 | get length(): number {
|
333 | return this._part.length;
|
334 | }
|
335 | }
|