smplr 0.18.0 → 0.18.1
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/README.md +67 -45
- package/dist/index.d.mts +30 -19
- package/dist/index.d.ts +30 -19
- package/dist/index.js +57 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +57 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -349,9 +349,9 @@ seq.addTrack(piano, [
|
|
|
349
349
|
]);
|
|
350
350
|
|
|
351
351
|
seq.addTrack(drums, [
|
|
352
|
-
{ note: "kick",
|
|
352
|
+
{ note: "kick", at: "1:1" },
|
|
353
353
|
{ note: "snare", at: "1:2" },
|
|
354
|
-
{ note: "kick",
|
|
354
|
+
{ note: "kick", at: "1:3" },
|
|
355
355
|
{ note: "snare", at: "1:4" },
|
|
356
356
|
]);
|
|
357
357
|
|
|
@@ -363,60 +363,68 @@ seq.start();
|
|
|
363
363
|
|
|
364
364
|
Note positions and durations accept several formats:
|
|
365
365
|
|
|
366
|
-
| Format
|
|
367
|
-
|
|
368
|
-
| `"4n"`
|
|
369
|
-
| `"8n"`
|
|
370
|
-
| `"4n."`
|
|
371
|
-
| `"1m"`
|
|
372
|
-
| `"2:1"`
|
|
373
|
-
| `"2:3:48"`
|
|
374
|
-
| `96`
|
|
366
|
+
| Format | Meaning |
|
|
367
|
+
| ---------- | ------------------------------ |
|
|
368
|
+
| `"4n"` | quarter note |
|
|
369
|
+
| `"8n"` | eighth note |
|
|
370
|
+
| `"4n."` | dotted quarter (1.5×) |
|
|
371
|
+
| `"1m"` | one measure |
|
|
372
|
+
| `"2:1"` | bar 2, beat 1 (1-indexed) |
|
|
373
|
+
| `"2:3:48"` | bar 2, beat 3, +48 ticks |
|
|
374
|
+
| `96` | raw ticks (number passthrough) |
|
|
375
375
|
|
|
376
376
|
#### Constructor options
|
|
377
377
|
|
|
378
378
|
```js
|
|
379
379
|
const seq = new Sequencer(context, {
|
|
380
|
-
bpm: 120,
|
|
381
|
-
ppq: 480,
|
|
382
|
-
timeSignature: 4,
|
|
383
|
-
loop: false,
|
|
384
|
-
loopStart: 0,
|
|
385
|
-
loopEnd: "2:1",
|
|
386
|
-
lookaheadMs: 200,
|
|
387
|
-
intervalMs: 50,
|
|
388
|
-
humanize: {
|
|
380
|
+
bpm: 120, // default 120
|
|
381
|
+
ppq: 480, // pulses per quarter note, default 480
|
|
382
|
+
timeSignature: 4, // beats per bar, default 4
|
|
383
|
+
loop: false, // default false
|
|
384
|
+
loopStart: 0, // loop start position (ticks or string)
|
|
385
|
+
loopEnd: "2:1", // loop end position; defaults to end of longest track
|
|
386
|
+
lookaheadMs: 200, // scheduling lookahead, default 200
|
|
387
|
+
intervalMs: 50, // flush interval, default 50
|
|
388
|
+
humanize: { timingMs: 10, velocity: 8 }, // optional randomisation
|
|
389
389
|
});
|
|
390
390
|
```
|
|
391
391
|
|
|
392
392
|
#### Playback
|
|
393
393
|
|
|
394
394
|
```js
|
|
395
|
-
seq.start();
|
|
396
|
-
seq.pause();
|
|
397
|
-
seq.stop();
|
|
395
|
+
seq.start(); // start from beginning (or resume from pause if no offset given)
|
|
396
|
+
seq.pause(); // freeze position
|
|
397
|
+
seq.stop(); // stop and reset to 0
|
|
398
|
+
seq.togglePlayPause(); // pause if playing, start/resume otherwise
|
|
398
399
|
|
|
399
|
-
seq.state;
|
|
400
|
+
seq.state; // "stopped" | "playing" | "paused"
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Individual sequenced notes can be stopped by their id:
|
|
404
|
+
|
|
405
|
+
```js
|
|
406
|
+
seq.stopNote("intro-c"); // stop immediately
|
|
407
|
+
seq.stopNote("intro-c", time); // stop at a scheduled time
|
|
400
408
|
```
|
|
401
409
|
|
|
402
410
|
#### Tempo and position
|
|
403
411
|
|
|
404
412
|
```js
|
|
405
|
-
seq.bpm = 140;
|
|
406
|
-
seq.timeSignature = 3;
|
|
413
|
+
seq.bpm = 140; // change BPM live, no glitch
|
|
414
|
+
seq.timeSignature = 3; // change time signature
|
|
407
415
|
|
|
408
|
-
seq.position;
|
|
409
|
-
seq.position = "3:1";
|
|
416
|
+
seq.position; // current position as "bar:beat:tick" string
|
|
417
|
+
seq.position = "3:1"; // seek while playing or stopped
|
|
410
418
|
```
|
|
411
419
|
|
|
412
420
|
#### Loop
|
|
413
421
|
|
|
414
422
|
```js
|
|
415
423
|
seq.loop = true;
|
|
416
|
-
seq.loopStart = "1:1";
|
|
417
|
-
seq.loopEnd
|
|
424
|
+
seq.loopStart = "1:1"; // ticks or string notation
|
|
425
|
+
seq.loopEnd = "3:1"; // ticks or string notation
|
|
418
426
|
|
|
419
|
-
seq.progress;
|
|
427
|
+
seq.progress; // 0..1 within the loop range
|
|
420
428
|
```
|
|
421
429
|
|
|
422
430
|
#### Pattern API
|
|
@@ -440,19 +448,30 @@ seq.scheduleRepeat(callback, "4n", "2:1"); // start at bar 2
|
|
|
440
448
|
#### Events
|
|
441
449
|
|
|
442
450
|
```js
|
|
451
|
+
seq.on("statechange", (state) => {
|
|
452
|
+
// state: "playing" | "paused" | "stopped"
|
|
453
|
+
setSeqState(state);
|
|
454
|
+
});
|
|
455
|
+
|
|
443
456
|
seq.on("beat", (beat, time) => {
|
|
444
457
|
const delay = (time - context.currentTime) * 1000;
|
|
445
458
|
setTimeout(() => metronome.flash(), delay);
|
|
446
459
|
});
|
|
447
460
|
|
|
448
|
-
seq.on("bar",
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
seq.on("
|
|
452
|
-
|
|
453
|
-
|
|
461
|
+
seq.on("bar", (bar, time) => {
|
|
462
|
+
ui.updateBar(bar);
|
|
463
|
+
});
|
|
464
|
+
seq.on("loop", () => {
|
|
465
|
+
console.log("looped");
|
|
466
|
+
});
|
|
467
|
+
seq.on("end", () => {
|
|
468
|
+
console.log("done");
|
|
469
|
+
});
|
|
470
|
+
seq.on("start", () => {});
|
|
471
|
+
seq.on("stop", () => {});
|
|
472
|
+
seq.on("pause", () => {});
|
|
454
473
|
|
|
455
|
-
seq.off("beat", handler);
|
|
474
|
+
seq.off("beat", handler); // remove a listener
|
|
456
475
|
```
|
|
457
476
|
|
|
458
477
|
#### Note events
|
|
@@ -471,12 +490,12 @@ seq.on("noteOff", (event) => {
|
|
|
471
490
|
|
|
472
491
|
The `event` object (`NoteEvent`) contains:
|
|
473
492
|
|
|
474
|
-
| Field | Type | Description
|
|
475
|
-
|
|
493
|
+
| Field | Type | Description |
|
|
494
|
+
| ------------ | ------------------ | ------------------------------------------------------ |
|
|
476
495
|
| `noteId` | `string \| number` | The note's `id` if provided, otherwise its array index |
|
|
477
|
-
| `trackIndex` | `number` | Index of the track in the order it was added
|
|
478
|
-
| `noteIndex` | `number` | Index of the note within its track's notes array
|
|
479
|
-
| `note` | `SequencerNote` | The original note object
|
|
496
|
+
| `trackIndex` | `number` | Index of the track in the order it was added |
|
|
497
|
+
| `noteIndex` | `number` | Index of the note within its track's notes array |
|
|
498
|
+
| `note` | `SequencerNote` | The original note object |
|
|
480
499
|
|
|
481
500
|
You can set a custom `id` on any `SequencerNote` to use as `noteId`:
|
|
482
501
|
|
|
@@ -489,15 +508,18 @@ seq.addTrack(piano, [
|
|
|
489
508
|
|
|
490
509
|
#### Humanize
|
|
491
510
|
|
|
492
|
-
Add subtle randomisation to timing
|
|
511
|
+
Add subtle randomisation to timing and velocity for a more natural feel:
|
|
493
512
|
|
|
494
513
|
```js
|
|
495
514
|
const seq = new Sequencer(context, {
|
|
496
515
|
bpm: 90,
|
|
497
|
-
humanize: {
|
|
516
|
+
humanize: { timingMs: 12, velocity: 8 },
|
|
498
517
|
});
|
|
499
518
|
```
|
|
500
519
|
|
|
520
|
+
- `timingMs`: maximum random offset in milliseconds (±). Default 0.
|
|
521
|
+
- `velocity`: maximum random offset in MIDI velocity units (±). Default 0.
|
|
522
|
+
|
|
501
523
|
---
|
|
502
524
|
|
|
503
525
|
## Instruments
|
package/dist/index.d.mts
CHANGED
|
@@ -259,12 +259,8 @@ type SequencerInstrument = {
|
|
|
259
259
|
duration?: number;
|
|
260
260
|
velocity?: number;
|
|
261
261
|
noteId?: string | number;
|
|
262
|
-
onStart?: (event:
|
|
263
|
-
|
|
264
|
-
}) => void;
|
|
265
|
-
onEnded?: (event: {
|
|
266
|
-
noteId?: string | number;
|
|
267
|
-
}) => void;
|
|
262
|
+
onStart?: (event: unknown) => void;
|
|
263
|
+
onEnded?: (event: unknown) => void;
|
|
268
264
|
}): unknown;
|
|
269
265
|
};
|
|
270
266
|
/** Emitted with "noteOn" and "noteOff" events. */
|
|
@@ -285,9 +281,9 @@ type SequencerOptions = {
|
|
|
285
281
|
lookaheadMs?: number;
|
|
286
282
|
/** How often (ms) the flush loop runs. Default 50. */
|
|
287
283
|
intervalMs?: number;
|
|
288
|
-
/** Randomise timing (
|
|
284
|
+
/** Randomise timing (ms) and velocity per note for a human feel. */
|
|
289
285
|
humanize?: {
|
|
290
|
-
|
|
286
|
+
timingMs?: number;
|
|
291
287
|
velocity?: number;
|
|
292
288
|
};
|
|
293
289
|
};
|
|
@@ -313,6 +309,8 @@ declare class Sequencer {
|
|
|
313
309
|
private _totalTicks;
|
|
314
310
|
/** Guards against scheduling the auto-stop setTimeout more than once. */
|
|
315
311
|
private _endScheduled;
|
|
312
|
+
/** Active voices keyed by noteId, so individual notes can be stopped. */
|
|
313
|
+
private _activeVoices;
|
|
316
314
|
constructor(context: BaseAudioContext, options?: SequencerOptions);
|
|
317
315
|
addTrack(instrument: SequencerInstrument, notes: SequencerNote[]): this;
|
|
318
316
|
removeTrack(instrument: SequencerInstrument): this;
|
|
@@ -324,6 +322,16 @@ declare class Sequencer {
|
|
|
324
322
|
start(offsetTick?: number): this;
|
|
325
323
|
pause(): this;
|
|
326
324
|
stop(): this;
|
|
325
|
+
/**
|
|
326
|
+
* Stop a single note that was scheduled by the sequencer.
|
|
327
|
+
* @param noteId The id of the note (from SequencerNote.id or auto-assigned index).
|
|
328
|
+
* @param time Optional AudioContext time to schedule the stop.
|
|
329
|
+
*/
|
|
330
|
+
stopNote(noteId: string | number, time?: number): this;
|
|
331
|
+
/**
|
|
332
|
+
* Toggle between playing and paused. If stopped, starts from the beginning.
|
|
333
|
+
*/
|
|
334
|
+
togglePlayPause(): this;
|
|
327
335
|
get bpm(): number;
|
|
328
336
|
set bpm(value: number);
|
|
329
337
|
get timeSignature(): number;
|
|
@@ -359,17 +367,18 @@ declare class Sequencer {
|
|
|
359
367
|
/**
|
|
360
368
|
* Listen to a sequencer event.
|
|
361
369
|
*
|
|
362
|
-
* | Event
|
|
363
|
-
*
|
|
364
|
-
* | "
|
|
365
|
-
* | "
|
|
366
|
-
* | "
|
|
367
|
-
* | "
|
|
368
|
-
* | "
|
|
369
|
-
* | "
|
|
370
|
-
* | "
|
|
371
|
-
* | "
|
|
372
|
-
* | "
|
|
370
|
+
* | Event | Args |
|
|
371
|
+
* |----------------|---------------------------------------------------|
|
|
372
|
+
* | "statechange" | (state: "playing" \| "paused" \| "stopped") |
|
|
373
|
+
* | "start" | |
|
|
374
|
+
* | "stop" | |
|
|
375
|
+
* | "pause" | |
|
|
376
|
+
* | "end" | |
|
|
377
|
+
* | "loop" | |
|
|
378
|
+
* | "beat" | (beat: number, time: number) |
|
|
379
|
+
* | "bar" | (bar: number, time: number) |
|
|
380
|
+
* | "noteOn" | (event: NoteEvent) |
|
|
381
|
+
* | "noteOff" | (event: NoteEvent) |
|
|
373
382
|
*/
|
|
374
383
|
on(event: string, callback: (...args: any[]) => void): this;
|
|
375
384
|
off(event: string, callback: (...args: any[]) => void): this;
|
|
@@ -379,6 +388,8 @@ declare class Sequencer {
|
|
|
379
388
|
private _scheduleWindow;
|
|
380
389
|
private _emitBeatsInWindow;
|
|
381
390
|
private _emit;
|
|
391
|
+
/** Emit both the specific state event ("start"/"pause"/"stop") and the unified "statechange" event. */
|
|
392
|
+
private _emitStateChange;
|
|
382
393
|
/** Recompute _totalTicks from all track notes (at + duration). */
|
|
383
394
|
private _recomputeTotalTicks;
|
|
384
395
|
/** Format a raw tick count as "bar:beat:tick" (all 1-indexed). */
|
package/dist/index.d.ts
CHANGED
|
@@ -259,12 +259,8 @@ type SequencerInstrument = {
|
|
|
259
259
|
duration?: number;
|
|
260
260
|
velocity?: number;
|
|
261
261
|
noteId?: string | number;
|
|
262
|
-
onStart?: (event:
|
|
263
|
-
|
|
264
|
-
}) => void;
|
|
265
|
-
onEnded?: (event: {
|
|
266
|
-
noteId?: string | number;
|
|
267
|
-
}) => void;
|
|
262
|
+
onStart?: (event: unknown) => void;
|
|
263
|
+
onEnded?: (event: unknown) => void;
|
|
268
264
|
}): unknown;
|
|
269
265
|
};
|
|
270
266
|
/** Emitted with "noteOn" and "noteOff" events. */
|
|
@@ -285,9 +281,9 @@ type SequencerOptions = {
|
|
|
285
281
|
lookaheadMs?: number;
|
|
286
282
|
/** How often (ms) the flush loop runs. Default 50. */
|
|
287
283
|
intervalMs?: number;
|
|
288
|
-
/** Randomise timing (
|
|
284
|
+
/** Randomise timing (ms) and velocity per note for a human feel. */
|
|
289
285
|
humanize?: {
|
|
290
|
-
|
|
286
|
+
timingMs?: number;
|
|
291
287
|
velocity?: number;
|
|
292
288
|
};
|
|
293
289
|
};
|
|
@@ -313,6 +309,8 @@ declare class Sequencer {
|
|
|
313
309
|
private _totalTicks;
|
|
314
310
|
/** Guards against scheduling the auto-stop setTimeout more than once. */
|
|
315
311
|
private _endScheduled;
|
|
312
|
+
/** Active voices keyed by noteId, so individual notes can be stopped. */
|
|
313
|
+
private _activeVoices;
|
|
316
314
|
constructor(context: BaseAudioContext, options?: SequencerOptions);
|
|
317
315
|
addTrack(instrument: SequencerInstrument, notes: SequencerNote[]): this;
|
|
318
316
|
removeTrack(instrument: SequencerInstrument): this;
|
|
@@ -324,6 +322,16 @@ declare class Sequencer {
|
|
|
324
322
|
start(offsetTick?: number): this;
|
|
325
323
|
pause(): this;
|
|
326
324
|
stop(): this;
|
|
325
|
+
/**
|
|
326
|
+
* Stop a single note that was scheduled by the sequencer.
|
|
327
|
+
* @param noteId The id of the note (from SequencerNote.id or auto-assigned index).
|
|
328
|
+
* @param time Optional AudioContext time to schedule the stop.
|
|
329
|
+
*/
|
|
330
|
+
stopNote(noteId: string | number, time?: number): this;
|
|
331
|
+
/**
|
|
332
|
+
* Toggle between playing and paused. If stopped, starts from the beginning.
|
|
333
|
+
*/
|
|
334
|
+
togglePlayPause(): this;
|
|
327
335
|
get bpm(): number;
|
|
328
336
|
set bpm(value: number);
|
|
329
337
|
get timeSignature(): number;
|
|
@@ -359,17 +367,18 @@ declare class Sequencer {
|
|
|
359
367
|
/**
|
|
360
368
|
* Listen to a sequencer event.
|
|
361
369
|
*
|
|
362
|
-
* | Event
|
|
363
|
-
*
|
|
364
|
-
* | "
|
|
365
|
-
* | "
|
|
366
|
-
* | "
|
|
367
|
-
* | "
|
|
368
|
-
* | "
|
|
369
|
-
* | "
|
|
370
|
-
* | "
|
|
371
|
-
* | "
|
|
372
|
-
* | "
|
|
370
|
+
* | Event | Args |
|
|
371
|
+
* |----------------|---------------------------------------------------|
|
|
372
|
+
* | "statechange" | (state: "playing" \| "paused" \| "stopped") |
|
|
373
|
+
* | "start" | |
|
|
374
|
+
* | "stop" | |
|
|
375
|
+
* | "pause" | |
|
|
376
|
+
* | "end" | |
|
|
377
|
+
* | "loop" | |
|
|
378
|
+
* | "beat" | (beat: number, time: number) |
|
|
379
|
+
* | "bar" | (bar: number, time: number) |
|
|
380
|
+
* | "noteOn" | (event: NoteEvent) |
|
|
381
|
+
* | "noteOff" | (event: NoteEvent) |
|
|
373
382
|
*/
|
|
374
383
|
on(event: string, callback: (...args: any[]) => void): this;
|
|
375
384
|
off(event: string, callback: (...args: any[]) => void): this;
|
|
@@ -379,6 +388,8 @@ declare class Sequencer {
|
|
|
379
388
|
private _scheduleWindow;
|
|
380
389
|
private _emitBeatsInWindow;
|
|
381
390
|
private _emit;
|
|
391
|
+
/** Emit both the specific state event ("start"/"pause"/"stop") and the unified "statechange" event. */
|
|
392
|
+
private _emitStateChange;
|
|
382
393
|
/** Recompute _totalTicks from all track notes (at + duration). */
|
|
383
394
|
private _recomputeTotalTicks;
|
|
384
395
|
/** Format a raw tick count as "bar:beat:tick" (all 1-indexed). */
|
package/dist/index.js
CHANGED
|
@@ -1532,6 +1532,8 @@ var Sequencer = class {
|
|
|
1532
1532
|
this._totalTicks = 0;
|
|
1533
1533
|
/** Guards against scheduling the auto-stop setTimeout more than once. */
|
|
1534
1534
|
this._endScheduled = false;
|
|
1535
|
+
/** Active voices keyed by noteId, so individual notes can be stopped. */
|
|
1536
|
+
this._activeVoices = /* @__PURE__ */ new Map();
|
|
1535
1537
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j;
|
|
1536
1538
|
this._context = context;
|
|
1537
1539
|
this._ppq = (_a = options.ppq) != null ? _a : 480;
|
|
@@ -1553,7 +1555,7 @@ var Sequencer = class {
|
|
|
1553
1555
|
this._lookaheadSec = ((_e = options.lookaheadMs) != null ? _e : 200) / 1e3;
|
|
1554
1556
|
this._intervalMs = (_f = options.intervalMs) != null ? _f : 50;
|
|
1555
1557
|
this._humanize = {
|
|
1556
|
-
timing: (_h = (_g = options.humanize) == null ? void 0 : _g.
|
|
1558
|
+
timing: (_h = (_g = options.humanize) == null ? void 0 : _g.timingMs) != null ? _h : 0,
|
|
1557
1559
|
velocity: (_j = (_i = options.humanize) == null ? void 0 : _i.velocity) != null ? _j : 0
|
|
1558
1560
|
};
|
|
1559
1561
|
}
|
|
@@ -1590,7 +1592,7 @@ var Sequencer = class {
|
|
|
1590
1592
|
this._clock.resume();
|
|
1591
1593
|
this._scheduledThrough = this._context.currentTime;
|
|
1592
1594
|
this._startLoop();
|
|
1593
|
-
this.
|
|
1595
|
+
this._emitStateChange("playing");
|
|
1594
1596
|
return this;
|
|
1595
1597
|
}
|
|
1596
1598
|
const startTick = offsetTick != null ? offsetTick : 0;
|
|
@@ -1599,23 +1601,44 @@ var Sequencer = class {
|
|
|
1599
1601
|
this._endScheduled = false;
|
|
1600
1602
|
this._resetRepeatEvents(startTick);
|
|
1601
1603
|
this._startLoop();
|
|
1602
|
-
this.
|
|
1604
|
+
this._emitStateChange("playing");
|
|
1603
1605
|
return this;
|
|
1604
1606
|
}
|
|
1605
1607
|
pause() {
|
|
1606
1608
|
if (this._clock.state !== "playing") return this;
|
|
1607
1609
|
this._clock.pause();
|
|
1608
1610
|
this._stopLoop();
|
|
1609
|
-
this.
|
|
1611
|
+
this._emitStateChange("paused");
|
|
1610
1612
|
return this;
|
|
1611
1613
|
}
|
|
1612
1614
|
stop() {
|
|
1613
1615
|
this._clock.stop();
|
|
1614
1616
|
this._stopLoop();
|
|
1615
1617
|
this._endScheduled = false;
|
|
1616
|
-
this.
|
|
1618
|
+
this._activeVoices.clear();
|
|
1619
|
+
this._emitStateChange("stopped");
|
|
1617
1620
|
return this;
|
|
1618
1621
|
}
|
|
1622
|
+
/**
|
|
1623
|
+
* Stop a single note that was scheduled by the sequencer.
|
|
1624
|
+
* @param noteId The id of the note (from SequencerNote.id or auto-assigned index).
|
|
1625
|
+
* @param time Optional AudioContext time to schedule the stop.
|
|
1626
|
+
*/
|
|
1627
|
+
stopNote(noteId, time) {
|
|
1628
|
+
const stopFn = this._activeVoices.get(noteId);
|
|
1629
|
+
if (stopFn) {
|
|
1630
|
+
stopFn(time);
|
|
1631
|
+
this._activeVoices.delete(noteId);
|
|
1632
|
+
}
|
|
1633
|
+
return this;
|
|
1634
|
+
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Toggle between playing and paused. If stopped, starts from the beginning.
|
|
1637
|
+
*/
|
|
1638
|
+
togglePlayPause() {
|
|
1639
|
+
if (this._clock.state === "playing") return this.pause();
|
|
1640
|
+
return this.start();
|
|
1641
|
+
}
|
|
1619
1642
|
// ---------------------------------------------------------------------------
|
|
1620
1643
|
// Tempo
|
|
1621
1644
|
// ---------------------------------------------------------------------------
|
|
@@ -1719,17 +1742,18 @@ var Sequencer = class {
|
|
|
1719
1742
|
/**
|
|
1720
1743
|
* Listen to a sequencer event.
|
|
1721
1744
|
*
|
|
1722
|
-
* | Event
|
|
1723
|
-
*
|
|
1724
|
-
* | "
|
|
1725
|
-
* | "
|
|
1726
|
-
* | "
|
|
1727
|
-
* | "
|
|
1728
|
-
* | "
|
|
1729
|
-
* | "
|
|
1730
|
-
* | "
|
|
1731
|
-
* | "
|
|
1732
|
-
* | "
|
|
1745
|
+
* | Event | Args |
|
|
1746
|
+
* |----------------|---------------------------------------------------|
|
|
1747
|
+
* | "statechange" | (state: "playing" \| "paused" \| "stopped") |
|
|
1748
|
+
* | "start" | |
|
|
1749
|
+
* | "stop" | |
|
|
1750
|
+
* | "pause" | |
|
|
1751
|
+
* | "end" | |
|
|
1752
|
+
* | "loop" | |
|
|
1753
|
+
* | "beat" | (beat: number, time: number) |
|
|
1754
|
+
* | "bar" | (bar: number, time: number) |
|
|
1755
|
+
* | "noteOn" | (event: NoteEvent) |
|
|
1756
|
+
* | "noteOff" | (event: NoteEvent) |
|
|
1733
1757
|
*/
|
|
1734
1758
|
on(event, callback) {
|
|
1735
1759
|
if (!this._listeners.has(event)) {
|
|
@@ -1788,6 +1812,7 @@ var Sequencer = class {
|
|
|
1788
1812
|
this._stopLoop();
|
|
1789
1813
|
this._clock.stop();
|
|
1790
1814
|
this._emit("end");
|
|
1815
|
+
this._emit("statechange", "stopped");
|
|
1791
1816
|
}, delay);
|
|
1792
1817
|
}
|
|
1793
1818
|
}
|
|
@@ -1806,19 +1831,25 @@ var Sequencer = class {
|
|
|
1806
1831
|
const durationSec = note.duration !== void 0 ? this._clock.tickDuration(
|
|
1807
1832
|
parseTicks(note.duration, this._ppq, this._timeSignature)
|
|
1808
1833
|
) : void 0;
|
|
1809
|
-
const timingOffset = this._humanize.timing ? (Math.random() * 2 - 1) * this._humanize.timing : 0;
|
|
1834
|
+
const timingOffset = this._humanize.timing ? (Math.random() * 2 - 1) * this._humanize.timing / 1e3 : 0;
|
|
1810
1835
|
const velocityOffset = this._humanize.velocity ? Math.round((Math.random() * 2 - 1) * this._humanize.velocity) : 0;
|
|
1811
1836
|
const noteId = (_a = note.id) != null ? _a : noteIndex;
|
|
1812
1837
|
const noteEvent = { noteId, trackIndex, noteIndex, note };
|
|
1813
|
-
track.instrument.start({
|
|
1838
|
+
const result = track.instrument.start({
|
|
1814
1839
|
note: note.note,
|
|
1815
|
-
time: audioTime + timingOffset,
|
|
1840
|
+
time: Math.max(0, audioTime + timingOffset),
|
|
1816
1841
|
duration: durationSec,
|
|
1817
1842
|
velocity: ((_b = note.velocity) != null ? _b : 100) + velocityOffset,
|
|
1818
1843
|
noteId,
|
|
1819
1844
|
onStart: () => this._emit("noteOn", noteEvent),
|
|
1820
|
-
onEnded: () =>
|
|
1845
|
+
onEnded: () => {
|
|
1846
|
+
this._activeVoices.delete(noteId);
|
|
1847
|
+
this._emit("noteOff", noteEvent);
|
|
1848
|
+
}
|
|
1821
1849
|
});
|
|
1850
|
+
if (typeof result === "function") {
|
|
1851
|
+
this._activeVoices.set(noteId, result);
|
|
1852
|
+
}
|
|
1822
1853
|
}
|
|
1823
1854
|
}
|
|
1824
1855
|
for (const rep of this._repeatEvents) {
|
|
@@ -1858,6 +1889,12 @@ var Sequencer = class {
|
|
|
1858
1889
|
}
|
|
1859
1890
|
}
|
|
1860
1891
|
}
|
|
1892
|
+
/** Emit both the specific state event ("start"/"pause"/"stop") and the unified "statechange" event. */
|
|
1893
|
+
_emitStateChange(state) {
|
|
1894
|
+
const eventName = state === "playing" ? "start" : state === "paused" ? "pause" : "stop";
|
|
1895
|
+
this._emit(eventName);
|
|
1896
|
+
this._emit("statechange", state);
|
|
1897
|
+
}
|
|
1861
1898
|
/** Recompute _totalTicks from all track notes (at + duration). */
|
|
1862
1899
|
_recomputeTotalTicks() {
|
|
1863
1900
|
let max = 0;
|