scribbletune 5.0.0-alpha.2 → 5.0.0

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.
Files changed (60) hide show
  1. package/{browser.js → dist/browser.js} +1 -1
  2. package/dist/browser.js.map +1 -0
  3. package/dist/index.d.ts +370 -0
  4. package/{index.js → dist/index.js} +1 -1
  5. package/dist/index.js.map +1 -0
  6. package/{max.js → dist/max.js} +1 -1
  7. package/dist/max.js.map +1 -0
  8. package/dist/scribbletune.js +1 -1
  9. package/dist/scribbletune.js.map +1 -1
  10. package/package.json +4 -1
  11. package/.editorconfig +0 -14
  12. package/.eslintignore +0 -4
  13. package/.eslintrc +0 -68
  14. package/.github/PULL_REQUEST_TEMPLATE.md +0 -13
  15. package/.nycrc.json +0 -21
  16. package/.travis.yml +0 -7
  17. package/.vscode/extensions.json +0 -17
  18. package/.vscode/launch.json +0 -64
  19. package/.vscode/settings.json +0 -44
  20. package/.vscode/tasks.json +0 -78
  21. package/CONTRIBUTING.md +0 -85
  22. package/browser.js.map +0 -1
  23. package/examples/arpeggiate.js +0 -87
  24. package/examples/bassline.js +0 -46
  25. package/examples/chords.js +0 -18
  26. package/examples/drummer.js +0 -46
  27. package/examples/hats.js +0 -11
  28. package/examples/kick.js +0 -11
  29. package/examples/progressions.js +0 -15
  30. package/examples/riff.js +0 -53
  31. package/examples/scale.js +0 -12
  32. package/examples/snare.js +0 -11
  33. package/examples/tempo.js +0 -20
  34. package/examples/tidal.js +0 -26
  35. package/index.d.ts +0 -62
  36. package/index.js.map +0 -1
  37. package/jest.config.js +0 -21
  38. package/max.js.map +0 -1
  39. package/runkit.js +0 -9
  40. package/src/arp.ts +0 -113
  41. package/src/browser-clip.ts +0 -240
  42. package/src/browser-index.ts +0 -26
  43. package/src/channel.ts +0 -631
  44. package/src/clip.ts +0 -249
  45. package/src/index.ts +0 -24
  46. package/src/max-index.ts +0 -24
  47. package/src/max.ts +0 -47
  48. package/src/midi.ts +0 -104
  49. package/src/progression.ts +0 -182
  50. package/src/session.ts +0 -104
  51. package/src/typings.d.ts +0 -297
  52. package/src/utils.ts +0 -203
  53. package/tests/arp.spec.ts +0 -42
  54. package/tests/browser-clip.spec.ts +0 -60
  55. package/tests/clip.spec.ts +0 -501
  56. package/tests/midi.spec.ts +0 -52
  57. package/tests/progression.spec.ts +0 -31
  58. package/tests/utils.spec.ts +0 -32
  59. package/tsconfig.json +0 -67
  60. package/webpack.config.js +0 -113
package/src/channel.ts DELETED
@@ -1,631 +0,0 @@
1
- import { clip, getNote, getDuration } from './browser-clip';
2
- import { errorHasMessage, IIndexable } from './utils';
3
-
4
- /**
5
- * Get the next logical position to play in the session
6
- * Tone has a build-in method `Tone.Transport.nextSubdivision('4n')`
7
- * but I think it s better to round off as follows for live performance
8
- */
9
- const getNextPos = (
10
- clip: null | { align?: string; alignOffset?: string }
11
- ): number | string => {
12
- // TODO: (soon) convert to using transportPosTicks (fewer computations)
13
- const arr = Tone.Transport.position.split(':');
14
- // If we are still around 0:0:0x, then set start position to 0
15
- if (arr[0] === '0' && arr[1] === '0') {
16
- return 0;
17
- }
18
-
19
- // Else set it to the next bar
20
- const transportPosTicks = Tone.Transport.ticks;
21
- const align = clip?.align || '1m';
22
- const alignOffset = clip?.alignOffset || '0';
23
- const alignTicks: number = Tone.Ticks(align).toTicks();
24
- const alignOffsetTicks: number = Tone.Ticks(alignOffset).toTicks();
25
- const nextPosTicks = Tone.Ticks(
26
- Math.floor(transportPosTicks / alignTicks + 1) * alignTicks +
27
- alignOffsetTicks
28
- );
29
- // const nextPosBBS = nextPosTicks.toBarsBeatsSixteenths();
30
- // return nextPosBBS; // Extraneous computations
31
- return nextPosTicks;
32
- };
33
-
34
- /**
35
- * Channel
36
- * A channel is made up of a Tone.js Player/Instrument, one or more
37
- * Tone.js sequences (known as clips in Scribbletune)
38
- * & optionally a set of effects (with or without presets)
39
- *
40
- * API:
41
- * clips -> Get all clips for this channel
42
- * addClip -> Add a new clip to the channel
43
- * startClip -> Start a clip at the provided index
44
- * stopClip -> Stop a clip at the provided index
45
- * activeClipIdx -> Get the clip that is currently playing
46
- */
47
- export class Channel {
48
- idx: number | string;
49
- name: string;
50
- activePatternIdx: number;
51
- channelClips: any;
52
- clipNoteCount: number;
53
- instrument: any;
54
- external: any;
55
- initializerTask: Promise<void>;
56
- hasLoaded: boolean; // if (!this.hasLoaded) - don't play this channel. Either still loading, or (initOutputProducer() rejected,
57
- hasFailed: boolean | Error;
58
- private eventCbFn: EventFn | undefined;
59
- private playerCbFn: playerObserverFnc | undefined;
60
- private counterResetTask: number | undefined;
61
- constructor(params: ChannelParams) {
62
- this.idx = params.idx || 0;
63
- this.name = params.name || 'ch ' + params.idx;
64
- this.activePatternIdx = -1;
65
- this.channelClips = [];
66
- this.clipNoteCount = 0;
67
-
68
- // Filter out unrequired params and create clip params object
69
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
70
- const { clips, samples, sample, synth, ...params1 } = params;
71
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
72
- const { external, sampler, buffer, ...params2 } = params1;
73
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
74
- const { player, instrument, volume, ...params3 } = params2;
75
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
76
- const { eventCb, playerCb, effects, ...params4 } = params3;
77
- const { context = Tone.getContext(), ...originalParamsFiltered } = params4;
78
-
79
- this.eventCbFn = eventCb;
80
- this.playerCbFn = playerCb;
81
-
82
- // Async section
83
- this.hasLoaded = false;
84
- this.hasFailed = false;
85
- this.initializerTask = this.initOutputProducer(context, params).then(() => {
86
- return this.initInstrument(context, params).then(() => {
87
- return this.adjustInstrument(context, params).then(() => {
88
- return this.initEffects(context, params);
89
- });
90
- });
91
- });
92
- // End Async section
93
-
94
- // Sync section
95
- let clipsFailed: { message: string } | false = false;
96
- try {
97
- params.clips.forEach((c: any, i: number) => {
98
- try {
99
- this.addClip({
100
- ...c,
101
- ...originalParamsFiltered,
102
- });
103
- } catch (e) {
104
- // Annotate the error with Clip info
105
- throw new Error(
106
- `${errorHasMessage(e) ? e.message : e} in clip ${i + 1}`
107
- );
108
- }
109
- }, this);
110
- } catch (e) {
111
- clipsFailed = e as { message: string }; // Stash the error
112
- }
113
- // End Sync section
114
-
115
- // Reconcile sync section with async section
116
- this.initializerTask
117
- .then(() => {
118
- if (clipsFailed) {
119
- throw clipsFailed;
120
- }
121
- this.hasLoaded = true;
122
- this.eventCb('loaded', {}); // Report async load completion.
123
- })
124
- .catch(e => {
125
- this.hasFailed = e;
126
- this.eventCb('error', { e }); // Report async errors.
127
- });
128
- }
129
-
130
- static setTransportTempo(valueBpm: number): void {
131
- Tone.Transport.bpm.value = valueBpm;
132
- }
133
-
134
- static startTransport(): void {
135
- Tone.start();
136
- Tone.Transport.start();
137
- }
138
-
139
- static stopTransport(deleteEvents = true): void {
140
- Tone.Transport.stop();
141
- if (deleteEvents) {
142
- // Delete all events in the Tone.Transport
143
- Tone.Transport.cancel();
144
- }
145
- }
146
-
147
- setVolume(volume: number): void {
148
- // ? this.volume = volume;
149
-
150
- // Change volume of the player
151
- // if (this.player) {
152
- // this.player.volume.value = volume;
153
- // }
154
-
155
- // Change volume of the sampler
156
- // if (this.sampler) {
157
- // this.sampler.volume.value = volume;
158
- // }
159
-
160
- // Change volume of the instrument
161
- if (this.instrument) {
162
- this.instrument.volume.value = volume;
163
- }
164
-
165
- // Change volume of the external
166
- if (this.external) {
167
- this.external.setVolume?.(volume);
168
- }
169
- }
170
-
171
- startClip(idx: number, position?: number | string): void {
172
- const clip = this.channelClips[idx];
173
- position = position || (position === 0 ? 0 : getNextPos(clip));
174
- // Stop any other currently running clip
175
- if (this.activePatternIdx > -1 && this.activePatternIdx !== idx) {
176
- this.stopClip(this.activePatternIdx, position);
177
- }
178
-
179
- if (clip && clip.state !== 'started') {
180
- // We need to schedule that for just before when clip?.start(position) events start coming.
181
- this.counterResetTask = Tone.Transport.scheduleOnce(
182
- (/* time: Tone.Seconds */) => {
183
- this.clipNoteCount = 0;
184
- },
185
- position
186
- );
187
-
188
- this.activePatternIdx = idx;
189
- // clip?.stop(position); // DEBUG: trying to clear out start/stop events
190
- // clip?.clear(position); // DEBUG: trying to clear out events
191
- clip?.start(position);
192
- }
193
- }
194
-
195
- stopClip(idx: number, position?: number | string): void {
196
- const clip = this.channelClips[idx];
197
- position = position || (position === 0 ? 0 : getNextPos(clip));
198
- clip?.stop(position);
199
- if (idx === this.activePatternIdx) {
200
- this.activePatternIdx = -1;
201
- }
202
- }
203
-
204
- addClip(clipParams: ClipParams, idx?: number): void {
205
- idx = idx || this.channelClips.length;
206
- if (clipParams.pattern) {
207
- this.channelClips[idx as number] = clip(
208
- {
209
- ...clipParams,
210
- },
211
- this
212
- );
213
- // Pass certain clipParams into getNextPos()
214
- ['align', 'alignOffset'].forEach(key => {
215
- if ((clipParams as IIndexable)[key]) {
216
- this.channelClips[idx as number][key] = (clipParams as IIndexable)[
217
- key
218
- ];
219
- }
220
- });
221
- } else {
222
- // Allow creation of empty clips
223
- this.channelClips[idx as number] = null;
224
- }
225
- }
226
-
227
- /**
228
- * @param {Object} ClipParams clip parameters
229
- * @return {Function} function that can be used as the callback in Tone.Sequence https://tonejs.github.io/docs/Sequence
230
- */
231
- getSeqFn(params: ClipParams): SeqFn {
232
- if (this.external) {
233
- return (time: string, el: string) => {
234
- if (el === 'x' || el === 'R') {
235
- const counter = this.clipNoteCount;
236
- if (this.hasLoaded) {
237
- const note = getNote(el, params, counter)[0];
238
- const duration = getDuration(params, counter);
239
- const durSeconds = Tone.Time(duration).toSeconds();
240
- this.playerCb({ note, duration, time, counter });
241
- try {
242
- this.external.triggerAttackRelease?.(note, durSeconds, time);
243
- } catch (e) {
244
- this.eventCb('error', { e }); // Report play errors.
245
- }
246
- }
247
- this.clipNoteCount++;
248
- }
249
- };
250
- } else if (this.instrument instanceof Tone.Player) {
251
- return (time: string, el: string) => {
252
- if (el === 'x' || el === 'R') {
253
- const counter = this.clipNoteCount;
254
- if (this.hasLoaded) {
255
- this.playerCb({ note: '', duration: '', time, counter });
256
- try {
257
- this.instrument.start(time);
258
- } catch (e) {
259
- this.eventCb('error', { e }); // Report play errors.
260
- }
261
- }
262
- this.clipNoteCount++;
263
- }
264
- };
265
- } else if (
266
- this.instrument instanceof Tone.PolySynth ||
267
- this.instrument instanceof Tone.Sampler
268
- ) {
269
- return (time: string, el: string) => {
270
- if (el === 'x' || el === 'R') {
271
- const counter = this.clipNoteCount;
272
- if (this.hasLoaded) {
273
- const note = getNote(el, params, counter);
274
- const duration = getDuration(params, counter);
275
- this.playerCb({ note, duration, time, counter });
276
- try {
277
- this.instrument.triggerAttackRelease(note, duration, time);
278
- } catch (e) {
279
- this.eventCb('error', { e }); // Report play errors.
280
- }
281
- }
282
- this.clipNoteCount++;
283
- }
284
- };
285
- } else if (this.instrument instanceof Tone.NoiseSynth) {
286
- return (time: string, el: string) => {
287
- if (el === 'x' || el === 'R') {
288
- const counter = this.clipNoteCount;
289
- if (this.hasLoaded) {
290
- const duration = getDuration(params, counter);
291
- this.playerCb({ note: '', duration, time, counter });
292
- try {
293
- this.instrument.triggerAttackRelease(duration, time);
294
- } catch (e) {
295
- this.eventCb('error', { e }); // Report play errors.
296
- }
297
- }
298
- this.clipNoteCount++;
299
- }
300
- };
301
- } else {
302
- return (time: string, el: string) => {
303
- if (el === 'x' || el === 'R') {
304
- const counter = this.clipNoteCount;
305
- if (this.hasLoaded) {
306
- const note = getNote(el, params, counter)[0];
307
- const duration = getDuration(params, counter);
308
- this.playerCb({ note, duration, time, counter });
309
- try {
310
- this.instrument.triggerAttackRelease(note, duration, time);
311
- } catch (e) {
312
- this.eventCb('error', { e }); // Report play errors.
313
- }
314
- }
315
- this.clipNoteCount++;
316
- }
317
- };
318
- }
319
- }
320
-
321
- private eventCb(event: string, params: any): void {
322
- if (typeof this.eventCbFn === 'function') {
323
- params.channel = this;
324
- this.eventCbFn(event, params);
325
- }
326
- }
327
-
328
- private playerCb(params: any): void {
329
- if (typeof this.playerCbFn === 'function') {
330
- params.channel = this;
331
- this.playerCbFn(params);
332
- }
333
- }
334
-
335
- /**
336
- * Check Tone.js object loaded state and either invoke `resolve` right away, or attach to and wait using Tone onload cb.
337
- * It's an ugly hack that reaches into Tone's internal ._buffers or ._buffer to insert itself into .onload() callback.
338
- * Tone has different ways to pull the onload callback from within to the API, so this implementation is very brittle.
339
- * The sole reason for its existence is to handle async loaded state of Tone instruments that we allow to pass in from outside.
340
- * If that option is eliminated, then this hacky function can be killed (or re-implemented via public onload API)
341
- * @param toneObject Tone.js object (will work with non-Tone objects that have same loaded/onload properties)
342
- * @param resolve onload callback
343
- */
344
- private checkToneObjLoaded(toneObject: any, resolve: () => void) {
345
- const skipRecursion = toneObject instanceof Tone.Sampler; // Sampler has a Map of ToneAudioBuffer, and our method to find inner .onload() does not work since there is no single one.
346
-
347
- // eslint-disable-next-line no-prototype-builtins
348
- if ('loaded' in toneObject) {
349
- if (toneObject.loaded) {
350
- resolve();
351
- return;
352
- }
353
- if (skipRecursion) {
354
- return;
355
- }
356
- // Try Recursion into inner objects:
357
- let handled = false;
358
- ['buffer', '_buffer', '_buffers'].forEach(key => {
359
- if (key in toneObject) {
360
- this.checkToneObjLoaded(toneObject[key], resolve);
361
- handled = true;
362
- }
363
- });
364
- if (handled) {
365
- return;
366
- }
367
- }
368
-
369
- // Check object type if it has load/onload (and _buffers or _buffer), then call resolve()
370
- // The list was created for Tone@14.8.0 by grepping and reviewing the source code.
371
- // Known objecs to have:
372
- const hasOnload =
373
- toneObject instanceof Tone.ToneAudioBuffer ||
374
- toneObject instanceof Tone.ToneBufferSource ||
375
- // Falback for "future" objects
376
- ('loaded' in toneObject && 'onload' in toneObject);
377
-
378
- if (!hasOnload) {
379
- // console.log('resolve() for ch "%o" idx %o onload NOT FOUND', this.name, this.idx);
380
- // This is not a good assumption. E.g. it does not work for Tone.ToneAudioBuffers
381
- resolve();
382
- } else {
383
- const oldOnLoad = toneObject.onload;
384
- toneObject.onload = () => {
385
- if (oldOnLoad && typeof oldOnLoad === 'function') {
386
- toneObject.onload = oldOnLoad;
387
- oldOnLoad();
388
- }
389
- // console.log('resolve() for ch "%o" idx %o', this.name, this.idx);
390
- resolve();
391
- };
392
- }
393
- }
394
-
395
- private recreateToneObjectInContext(
396
- toneObject: any, // Tone.PolySynth | Tone.Player | Tone.Sampler | Tone['' | '']
397
- context: any
398
- ): Promise<any> {
399
- context = context || Tone.getContext();
400
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
401
- return new Promise<any>((resolve, reject) => {
402
- // Tone.PolySynth | Tone.Player | Tone.Sampler | Tone['' | '']
403
- if (toneObject instanceof Tone.PolySynth) {
404
- const newObj = Tone.PolySynth(Tone[toneObject._dummyVoice.name], {
405
- ...toneObject.get(),
406
- context,
407
- });
408
- this.checkToneObjLoaded(newObj, () => resolve(newObj));
409
- } else if (toneObject instanceof Tone.Player) {
410
- const newObj = Tone.Player({
411
- url: toneObject._buffer,
412
- context,
413
- onload: () => this.checkToneObjLoaded(newObj, () => resolve(newObj)),
414
- });
415
- } else if (toneObject instanceof Tone.Sampler) {
416
- const { attack, curve, release, volume } = toneObject.get();
417
- const paramsFromSampler = {
418
- attack,
419
- curve,
420
- release,
421
- volume,
422
- };
423
- const paramsFromBuffers = {
424
- baseUrl: toneObject._buffers.baseUrl,
425
- urls: Object.fromEntries(toneObject._buffers._buffers.entries()),
426
- };
427
- const newObj = Tone.Sampler({
428
- ...paramsFromSampler,
429
- ...paramsFromBuffers,
430
- context,
431
- onload: () => this.checkToneObjLoaded(newObj, () => resolve(newObj)),
432
- });
433
- } else {
434
- const newObj = Tone[toneObject.name]({
435
- ...toneObject.get(),
436
- context,
437
- onload: () => this.checkToneObjLoaded(newObj, () => resolve(newObj)),
438
- });
439
- this.checkToneObjLoaded(newObj, () => resolve(newObj));
440
- }
441
- });
442
- }
443
-
444
- private initOutputProducer(
445
- context: any,
446
- params: ChannelParams
447
- ): Promise<void> {
448
- context = context || Tone.getContext();
449
- return new Promise<void>((resolve, reject) => {
450
- /*
451
- * 1. The params object can be used to pass a sample (sound source) OR a synth(Synth/FMSynth/AMSynth etc) or samples.
452
- * Scribbletune will then create a Tone.js Player or Tone.js Instrument or Tone.js Sampler respectively
453
- * 2. It can also be used to pass a Tone.js Player object or instrument that was created elsewhere
454
- * (mostly by Scribbletune itself in the channel creation method)
455
- **/
456
-
457
- if (params.synth) {
458
- if (params.instrument) {
459
- throw new Error(
460
- 'Either synth or instrument can be provided, but not both.'
461
- );
462
- }
463
- if ((params.synth as SynthParams).synth) {
464
- const synthName = (params.synth as SynthParams).synth;
465
- // const presetName = (params.synth as SynthParams).presetName; // Unused here
466
- const preset = (params.synth as SynthParams).preset || {};
467
- this.instrument = new Tone[synthName]({
468
- ...preset,
469
- context,
470
- // Use onload for cases when synthName calls out Tone.Sample/Player/Sampler.
471
- // It could be a universal way to load Tone.js instruments.
472
- onload: () => this.checkToneObjLoaded(this.instrument, resolve),
473
- // This onload is ignored in all synths. Therefore we call checkToneObjLoaded() again below.
474
- // It is safe to call resolve() multiple times for Promise<void>
475
- });
476
- this.checkToneObjLoaded(this.instrument, resolve);
477
- } else {
478
- this.instrument = params.synth; // TODO: This is dangerous by-reference assignment.
479
- console.warn(
480
- 'The "synth" parameter with instrument will be deprecated in the future. Please use the "instrument" parameter instead.'
481
- );
482
- // params.synth describing the Tone[params.synth.synth] is allowed.
483
- this.checkToneObjLoaded(this.instrument, resolve);
484
- }
485
- } else if (typeof params.instrument === 'string') {
486
- this.instrument = new Tone[params.instrument]({ context });
487
- this.checkToneObjLoaded(this.instrument, resolve);
488
- } else if (params.instrument) {
489
- this.instrument = params.instrument; // TODO: This is dangerous by-reference assignment. Tone.instrument has context that holds all other instruments. Client side params get polluted with circular references. If params come from e.g. react-ApolloClient data, Apollo tools crash on circular references.
490
- this.checkToneObjLoaded(this.instrument, resolve);
491
- } else if (params.sample || params.buffer) {
492
- this.instrument = new Tone.Player({
493
- url: params.sample || params.buffer,
494
- context,
495
- onload: () => this.checkToneObjLoaded(this.instrument, resolve),
496
- });
497
- } else if (params.samples) {
498
- this.instrument = new Tone.Sampler({
499
- urls: params.samples,
500
- context,
501
- onload: () => this.checkToneObjLoaded(this.instrument, resolve),
502
- });
503
- } else if (params.sampler) {
504
- this.instrument = params.sampler; // TODO: This is dangerous by-reference assignment.
505
- this.checkToneObjLoaded(this.instrument, resolve);
506
- } else if (params.player) {
507
- this.instrument = params.player; // TODO: This is dangerous by-reference assignment.
508
- this.checkToneObjLoaded(this.instrument, resolve);
509
- } else if (params.external) {
510
- this.external = { ...params.external }; // Sanitize object by shallow clone
511
- this.instrument = {
512
- context,
513
- volume: { value: 0 },
514
- };
515
- // Do not call! this.checkToneObjLoaded(this.instrument, resolve);
516
-
517
- if (params.external.init) {
518
- return params.external
519
- .init(context.rawContext)
520
- .then(() => {
521
- resolve();
522
- })
523
- .catch((e: any) => {
524
- reject(
525
- new Error(
526
- `${e.message} loading external output module of channel idx ${
527
- this.idx
528
- }, ${this.name ?? '(no name)'}`
529
- )
530
- );
531
- });
532
- } else {
533
- resolve();
534
- }
535
- } else {
536
- throw new Error(
537
- 'One of required synth|instrument|sample|sampler|samples|buffer|player|external is not provided!'
538
- );
539
- }
540
-
541
- if (!this.instrument) {
542
- throw new Error('Failed instantiating instrument from given params.');
543
- }
544
- });
545
- }
546
-
547
- private initInstrument(context: any, params: ChannelParams): Promise<void> {
548
- context = context || Tone.getContext();
549
- if (!params.external && this.instrument?.context !== context) {
550
- return this.recreateToneObjectInContext(this.instrument, context).then(
551
- newObj => {
552
- this.instrument = newObj;
553
- }
554
- );
555
- } else {
556
- // Nothing to do
557
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
558
- return new Promise<void>((resolve, reject) => {
559
- resolve();
560
- });
561
- }
562
- }
563
-
564
- private adjustInstrument(context: any, params: ChannelParams): Promise<void> {
565
- context = context || Tone.getContext();
566
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
567
- return new Promise<void>((resolve, reject) => {
568
- if (params.volume) {
569
- // this.instrument.volume.value = params.volume;
570
- this.setVolume(params.volume);
571
- }
572
- resolve();
573
- });
574
- }
575
-
576
- private initEffects(context: any, params: ChannelParams): Promise<void> {
577
- context = context || Tone.getContext();
578
-
579
- const createEffect = (effect: any): Promise<any> => {
580
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
581
- return new Promise<any>((resolve, reject) => {
582
- if (typeof effect === 'string') {
583
- resolve(new Tone[effect]({ context }));
584
- } else if (effect.context !== context) {
585
- return this.recreateToneObjectInContext(effect, context);
586
- } else {
587
- resolve(effect);
588
- }
589
- }).then(effectOut => {
590
- return effectOut.toDestination();
591
- });
592
- };
593
-
594
- const startEffect = (eff: any) => {
595
- return typeof eff.start === 'function' ? eff.start() : eff;
596
- };
597
-
598
- const toArray = (someVal: any): any[] => {
599
- if (!someVal) {
600
- return [];
601
- }
602
- if (Array.isArray(someVal)) {
603
- return someVal;
604
- }
605
- return [someVal];
606
- };
607
-
608
- const effectsIn = toArray(params.effects);
609
- if (params.external) {
610
- if (effectsIn.length !== 0) {
611
- throw new Error('Effects cannot be used with external output');
612
- }
613
- return Promise.resolve();
614
- }
615
-
616
- // effects = params.effects.map(createEffect).map(startEffect);
617
- return Promise.all(effectsIn.map(createEffect))
618
- .then(results => results.map(startEffect))
619
- .then(effects => {
620
- this.instrument.chain(...effects).toDestination();
621
- });
622
- }
623
-
624
- get clips(): any[] {
625
- return this.channelClips;
626
- }
627
-
628
- get activeClipIdx(): number {
629
- return this.activePatternIdx;
630
- }
631
- }