@aeonentertainment/mixer 0.5.0 → 0.6.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.
- package/README.md +6 -6
- package/dist/index.d.mts +59 -15
- package/dist/index.mjs +64 -22
- package/native/binding/index.d.ts +22 -5
- package/native/binding/index.darwin-arm64.node +0 -0
- package/native/binding/index.darwin-x64.node +0 -0
- package/native/binding/index.linux-arm64-gnu.node +0 -0
- package/native/binding/index.linux-x64-gnu.node +0 -0
- package/native/binding/index.win32-x64-msvc.node +0 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ Each [release](https://github.com/aeonentertainment/aeon-mixer/releases) carries
|
|
|
15
15
|
Depend on its URL, and the lockfile pins its hash:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
npm install https://github.com/aeonentertainment/aeon-mixer/releases/download/v0.
|
|
18
|
+
npm install https://github.com/aeonentertainment/aeon-mixer/releases/download/v0.6.0/aeonentertainment-mixer-0.6.0.tgz
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
## Use
|
|
@@ -50,11 +50,11 @@ await engine.close();
|
|
|
50
50
|
|
|
51
51
|
- **Slots** are the mics: 8 per engine by default, up to 16 with `openEngine({ slots })`. `rtp`
|
|
52
52
|
takes Opus over UDP from a phone or mediasoup, `l16` takes raw PCM over loopback from a house
|
|
53
|
-
mic,
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
they found.
|
|
53
|
+
mic's sender, `capture` takes a mic on this machine by an id from `engine.inputDevices()`, and
|
|
54
|
+
`push` takes PCM the host hands to `mixer.push`. Each slot has gain, pan, mute, solo, reverb,
|
|
55
|
+
a pitch reading, a melody guide and pitch correction toward it, whose delay `stats()` reports.
|
|
56
|
+
A slot can also take the room's echo of the mix out of its input and duck itself when its loop
|
|
57
|
+
through the speakers starts to howl; both add no delay, and `stats()` reports what they found.
|
|
58
58
|
- **The backing** is any format Symphonia decodes, resampled to 48 kHz. The armed key and speed
|
|
59
59
|
are rendered before `start` resolves. Live, it supports seek, pause, resume, a key change and
|
|
60
60
|
stem gains, and `start`, `seek` and `resume` report the position the lyric clock follows.
|
package/dist/index.d.mts
CHANGED
|
@@ -13,15 +13,22 @@ declare class Engine$1 {
|
|
|
13
13
|
*/
|
|
14
14
|
constructor(options: OpenOptions, onEvents: (arg: Array<NativeEvent>) => void);
|
|
15
15
|
/**
|
|
16
|
-
* Builds a mixer with one slot per
|
|
17
|
-
* and l16 slots bind their ports here and report them through
|
|
18
|
-
*
|
|
16
|
+
* Builds a mixer with one slot per input, in order: `"rtp"`, `"l16"`, `"push"` or
|
|
17
|
+
* `"capture"`. The rtp and l16 slots bind their ports here and report them through
|
|
18
|
+
* `Mixer.port`, the capture slots start their inputs here, and a return stream gets its socket
|
|
19
|
+
* and send task here too.
|
|
19
20
|
*/
|
|
20
|
-
createMixer(
|
|
21
|
+
createMixer(slots: Array<SlotInput>, rtpReturn?: RtpReturnInput | undefined | null): Mixer$1;
|
|
21
22
|
/** The output playing now. After a move the events thread sends the same as `outputChanged`. */
|
|
22
23
|
get output(): OutputInfo$1;
|
|
23
24
|
/** The outputs this OS offers, the default marked. Empty on the offline and timer clocks. */
|
|
24
25
|
outputs(): Array<OutputEntry>;
|
|
26
|
+
/**
|
|
27
|
+
* The inputs this OS offers, the default marked: the devices a `capture` slot may name. Empty
|
|
28
|
+
* on the offline clock, which opens none. A box on the timer clock plays nothing locally but
|
|
29
|
+
* may still have a mic plugged in.
|
|
30
|
+
*/
|
|
31
|
+
inputs(): Array<OutputEntry>;
|
|
25
32
|
/** Offline clock only: renders `blocks` blocks and returns them as interleaved stereo. */
|
|
26
33
|
pump(blocks: number): Float32Array;
|
|
27
34
|
/** Writes one slot of the snapshot and publishes the whole of it. */
|
|
@@ -61,7 +68,7 @@ declare class Engine$1 {
|
|
|
61
68
|
declare class Mixer$1 {
|
|
62
69
|
/** Moves a finished render's stems into the graph as its backing path, before `swap`. */
|
|
63
70
|
installBacking(render: BackingRender): void;
|
|
64
|
-
/** The UDP port a slot listens on, or null for a push slot. */
|
|
71
|
+
/** The UDP port a slot listens on, or null for a push or capture slot. */
|
|
65
72
|
port(index: number): number | null;
|
|
66
73
|
/**
|
|
67
74
|
* Hands interleaved stereo PCM to a push slot, which folds it to mono as every slot is
|
|
@@ -210,6 +217,15 @@ interface RtpReturnStats {
|
|
|
210
217
|
/** Blocks the audio thread dropped because the network thread had not drained the ring. */
|
|
211
218
|
droppedBlocks: number;
|
|
212
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* One slot as `createMixer` asks for it. `device` and `channel` belong to a `capture` slot: an id
|
|
222
|
+
* from `inputs`, or the default input, and the device's channel from 0.
|
|
223
|
+
*/
|
|
224
|
+
interface SlotInput {
|
|
225
|
+
kind: string;
|
|
226
|
+
device?: string;
|
|
227
|
+
channel?: number;
|
|
228
|
+
}
|
|
213
229
|
interface SlotParamsInput {
|
|
214
230
|
/** Linear amplitude. */
|
|
215
231
|
gain: number;
|
|
@@ -257,7 +273,7 @@ declare const NativeEngine: typeof Engine$1;
|
|
|
257
273
|
type NativeEngine = Engine$1;
|
|
258
274
|
type NativeMixer = Mixer$1;
|
|
259
275
|
type Capability = "guideVocal" | "key" | "speed" | "seek" | "shortVersion" | "pause" | "level";
|
|
260
|
-
type IngestKind = "rtp" | "l16" | "push";
|
|
276
|
+
type IngestKind = "rtp" | "l16" | "push" | "capture";
|
|
261
277
|
type StemKind = "instrumental" | "guideVocals";
|
|
262
278
|
interface OpenEngineOptions {
|
|
263
279
|
/**
|
|
@@ -291,10 +307,20 @@ interface OutputInfo {
|
|
|
291
307
|
readonly name: string;
|
|
292
308
|
} | null;
|
|
293
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* One slot as a mixer asks for it. A `capture` slot is an input on this machine, a house mic:
|
|
312
|
+
* `device` is an id from `inputDevices()`, the default input when left out, and `channel` the
|
|
313
|
+
* device's channel from 0, default 0.
|
|
314
|
+
*/
|
|
315
|
+
type SlotConfig = {
|
|
316
|
+
kind: Exclude<IngestKind, "capture">;
|
|
317
|
+
} | {
|
|
318
|
+
kind: "capture";
|
|
319
|
+
device?: string;
|
|
320
|
+
channel?: number;
|
|
321
|
+
};
|
|
294
322
|
interface MixerConfig {
|
|
295
|
-
slots: ReadonlyArray<
|
|
296
|
-
kind: IngestKind;
|
|
297
|
-
}>;
|
|
323
|
+
slots: ReadonlyArray<SlotConfig>;
|
|
298
324
|
backing?: {
|
|
299
325
|
stems: ReadonlyArray<{
|
|
300
326
|
kind: StemKind;
|
|
@@ -396,7 +422,11 @@ interface SlotStats {
|
|
|
396
422
|
bleedDb: number;
|
|
397
423
|
removedDb: number;
|
|
398
424
|
} | null;
|
|
399
|
-
/**
|
|
425
|
+
/**
|
|
426
|
+
* The delay the stream's earliest packets wait now; zero for a `push` slot. A `capture` slot
|
|
427
|
+
* has no jitter buffer: this is how long its mic's samples wait from the input to the mix, the
|
|
428
|
+
* input's period included, and zero until it first plays.
|
|
429
|
+
*/
|
|
400
430
|
playoutDelayMs: number;
|
|
401
431
|
/** How often the howl guard has ducked this slot. */
|
|
402
432
|
howlCatches: number;
|
|
@@ -461,25 +491,33 @@ interface MixerHost extends RenderHost {
|
|
|
461
491
|
swapOut(mixer: Mixer): Promise<void>;
|
|
462
492
|
/** Hears the engine's fault until the returned function is called. */
|
|
463
493
|
onFault(listener: (error: Error) => void): () => void;
|
|
494
|
+
/**
|
|
495
|
+
* Holds `release` until the returned function is called, and calls it when the engine closes:
|
|
496
|
+
* a mixer's capture inputs and ports outlive no engine, whatever state the mixer is in.
|
|
497
|
+
*/
|
|
498
|
+
hold(release: () => void): () => void;
|
|
464
499
|
isLive(mixer: Mixer): boolean;
|
|
465
500
|
anyLive(): boolean;
|
|
466
501
|
underruns(): number;
|
|
467
502
|
}
|
|
468
503
|
/**
|
|
469
|
-
* One mixing graph for one performance. Its `rtp` and `l16` slots listen on their ports
|
|
470
|
-
* here until `destroy`; its return stream sends the
|
|
504
|
+
* One mixing graph for one performance. Its `rtp` and `l16` slots listen on their ports, and its
|
|
505
|
+
* `capture` slots hold their inputs open, from here until `destroy`; its return stream sends the
|
|
506
|
+
* mix from `start` until `destroy`. Settings
|
|
471
507
|
* made while armed are kept here and written to the engine when `start` swaps the graph in, so
|
|
472
508
|
* arming the next song never touches the live one.
|
|
473
509
|
*/
|
|
474
510
|
export declare class Mixer {
|
|
475
511
|
#private;
|
|
476
|
-
/** Each slot's kind and, for `rtp` and `l16`, the UDP port it listens on; `null` for
|
|
512
|
+
/** Each slot's kind and, for `rtp` and `l16`, the UDP port it listens on; `null` for the rest. */
|
|
477
513
|
readonly slots: ReadonlyArray<{
|
|
478
514
|
index: number;
|
|
479
515
|
kind: IngestKind;
|
|
480
516
|
port: number | null;
|
|
481
517
|
}>;
|
|
482
|
-
constructor(host: MixerHost,
|
|
518
|
+
constructor(host: MixerHost, inputs: ReadonlyArray<SlotInput & {
|
|
519
|
+
kind: IngestKind;
|
|
520
|
+
}>, events: Emitter<MixerEvents>, backing: BackingConfig | null, rtpReturn: RtpReturnInput | null);
|
|
483
521
|
setSlot(index: number, settings: Partial<SlotSettings>): void;
|
|
484
522
|
/** The slot's whole chain: an effect not in `effects` is disabled. */
|
|
485
523
|
setSlotEffects(index: number, effects: ReadonlyArray<SlotEffect>): void;
|
|
@@ -578,9 +616,15 @@ export declare class Engine {
|
|
|
578
616
|
name: string;
|
|
579
617
|
isDefault: boolean;
|
|
580
618
|
}>>;
|
|
619
|
+
/** The inputs a `capture` slot may name, the default marked. None on an offline engine. */
|
|
620
|
+
inputDevices(): Promise<ReadonlyArray<{
|
|
621
|
+
id: string;
|
|
622
|
+
name: string;
|
|
623
|
+
isDefault: boolean;
|
|
624
|
+
}>>;
|
|
581
625
|
createMixer(config: MixerConfig): Promise<Mixer>;
|
|
582
626
|
on<K extends keyof EngineEvents>(event: K, listener: EngineEvents[K]): () => void;
|
|
583
627
|
/** Destroys the live mixer, stops the clock and joins the events thread. */
|
|
584
628
|
close(): Promise<void>;
|
|
585
629
|
}
|
|
586
|
-
export type { Capability, EngineEvents, IngestKind, MelodyGuide, MixerConfig, MixerEvents, OfflineEngine, OpenEngineOptions, OutputInfo, SlotEffect, SlotSettings, SlotStats, StemKind };
|
|
630
|
+
export type { Capability, EngineEvents, IngestKind, MelodyGuide, MixerConfig, MixerEvents, OfflineEngine, OpenEngineOptions, OutputInfo, SlotConfig, SlotEffect, SlotSettings, SlotStats, StemKind };
|
package/dist/index.mjs
CHANGED
|
@@ -72,14 +72,27 @@ function framesToMs(frames) {
|
|
|
72
72
|
const INGEST_KINDS = [
|
|
73
73
|
"rtp",
|
|
74
74
|
"l16",
|
|
75
|
-
"push"
|
|
75
|
+
"push",
|
|
76
|
+
"capture"
|
|
76
77
|
];
|
|
77
78
|
/** A kind as a JavaScript caller may have spelt it, so an unknown one never crosses. */
|
|
78
79
|
function ingestKind(name, value) {
|
|
79
80
|
const kind = INGEST_KINDS.find((known) => known === value);
|
|
80
|
-
if (kind === void 0) throw new RangeError(`${name} must be rtp, l16 or
|
|
81
|
+
if (kind === void 0) throw new RangeError(`${name} must be rtp, l16, push or capture, got ${value}`);
|
|
81
82
|
return kind;
|
|
82
83
|
}
|
|
84
|
+
/** A slot as the addon takes it: its kind, and for a `capture` slot its device and channel. */
|
|
85
|
+
function slotInput(name, slot) {
|
|
86
|
+
const kind = ingestKind(`${name}.kind`, slot.kind);
|
|
87
|
+
if (slot.kind !== "capture") return { kind };
|
|
88
|
+
const { device, channel = 0 } = slot;
|
|
89
|
+
if (device !== void 0 && (typeof device !== "string" || device === "")) throw new TypeError(`${name}.device must be an id from inputDevices()`);
|
|
90
|
+
return {
|
|
91
|
+
kind,
|
|
92
|
+
channel: integerIn(`${name}.channel`, channel, 0, 255),
|
|
93
|
+
...device === void 0 ? {} : { device }
|
|
94
|
+
};
|
|
95
|
+
}
|
|
83
96
|
/**
|
|
84
97
|
* Where the mix goes: an IP literal, since resolving a name would block the control thread, and a
|
|
85
98
|
* dynamic RTP payload type, since Opus has no static one.
|
|
@@ -108,6 +121,20 @@ function pcm(name, value, channels) {
|
|
|
108
121
|
return value;
|
|
109
122
|
}
|
|
110
123
|
// src/backing.ts
|
|
124
|
+
/**
|
|
125
|
+
* `config` checked, before anything opens: a mixer runs this ahead of its native mixer, so bad
|
|
126
|
+
* backing settings reject `createMixer` with no port bound and no input held.
|
|
127
|
+
*/
|
|
128
|
+
function backingSettings(config) {
|
|
129
|
+
return {
|
|
130
|
+
input: toInput(config),
|
|
131
|
+
stopAtFrames: config.stopAtMs === void 0 ? void 0 : msToFrames(finiteIn("stopAtMs", config.stopAtMs, 0, Number.MAX_SAFE_INTEGER)),
|
|
132
|
+
stemGains: {
|
|
133
|
+
instrumental: finiteIn("stemGains.instrumental", config.stemGains.instrumental, 0, 4),
|
|
134
|
+
guide: finiteIn("stemGains.guideVocals", config.stemGains.guideVocals, 0, 4)
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
111
138
|
/** `stopAtMs` fades the stems out over this before falling silent: docs/design.md section 4. */
|
|
112
139
|
const STOP_FADE_MS = 3e3;
|
|
113
140
|
/**
|
|
@@ -130,18 +157,15 @@ var Backing = class {
|
|
|
130
157
|
#render;
|
|
131
158
|
#graph;
|
|
132
159
|
/**
|
|
133
|
-
*
|
|
160
|
+
* Starts the render of checked `settings` on the worker thread. The stems move into `graph` as
|
|
134
161
|
* soon as the job is done, so a render that fails is reported then and not at `start`.
|
|
135
162
|
*/
|
|
136
|
-
constructor(host,
|
|
163
|
+
constructor(host, settings, graph, report) {
|
|
137
164
|
this.#graph = graph;
|
|
138
|
-
const input =
|
|
165
|
+
const { input, stopAtFrames, stemGains } = settings;
|
|
139
166
|
this.speed = input.speed;
|
|
140
|
-
this.stopAtFrames =
|
|
141
|
-
this.#stemGains =
|
|
142
|
-
instrumental: finiteIn("stemGains.instrumental", config.stemGains.instrumental, 0, 4),
|
|
143
|
-
guide: finiteIn("stemGains.guideVocals", config.stemGains.guideVocals, 0, 4)
|
|
144
|
-
};
|
|
167
|
+
this.stopAtFrames = stopAtFrames;
|
|
168
|
+
this.#stemGains = stemGains;
|
|
145
169
|
this.#render = host.native.startRender(input);
|
|
146
170
|
this.rendered = host.watchRender(this.#render.id, (fraction) => {
|
|
147
171
|
this.#fraction = fraction;
|
|
@@ -241,13 +265,14 @@ function defaultSlotParams() {
|
|
|
241
265
|
};
|
|
242
266
|
}
|
|
243
267
|
/**
|
|
244
|
-
* One mixing graph for one performance. Its `rtp` and `l16` slots listen on their ports
|
|
245
|
-
* here until `destroy`; its return stream sends the
|
|
268
|
+
* One mixing graph for one performance. Its `rtp` and `l16` slots listen on their ports, and its
|
|
269
|
+
* `capture` slots hold their inputs open, from here until `destroy`; its return stream sends the
|
|
270
|
+
* mix from `start` until `destroy`. Settings
|
|
246
271
|
* made while armed are kept here and written to the engine when `start` swaps the graph in, so
|
|
247
272
|
* arming the next song never touches the live one.
|
|
248
273
|
*/
|
|
249
274
|
var Mixer = class {
|
|
250
|
-
/** Each slot's kind and, for `rtp` and `l16`, the UDP port it listens on; `null` for
|
|
275
|
+
/** Each slot's kind and, for `rtp` and `l16`, the UDP port it listens on; `null` for the rest. */
|
|
251
276
|
slots;
|
|
252
277
|
#host;
|
|
253
278
|
#native;
|
|
@@ -256,6 +281,7 @@ var Mixer = class {
|
|
|
256
281
|
#guides;
|
|
257
282
|
#backing;
|
|
258
283
|
#offFault;
|
|
284
|
+
#letGo;
|
|
259
285
|
#phase = "armed";
|
|
260
286
|
#failed = false;
|
|
261
287
|
/**
|
|
@@ -267,18 +293,20 @@ var Mixer = class {
|
|
|
267
293
|
this.#failed = true;
|
|
268
294
|
this.#events.emit("error", error);
|
|
269
295
|
};
|
|
270
|
-
constructor(host,
|
|
296
|
+
constructor(host, inputs, events, backing, rtpReturn) {
|
|
271
297
|
this.#host = host;
|
|
272
298
|
this.#events = events;
|
|
273
|
-
|
|
274
|
-
this
|
|
299
|
+
const settings = backing === null ? null : backingSettings(backing);
|
|
300
|
+
this.#native = host.native.createMixer([...inputs], rtpReturn);
|
|
301
|
+
this.#letGo = host.hold(() => this.#release());
|
|
302
|
+
this.slots = inputs.map(({ kind }, index) => ({
|
|
275
303
|
index,
|
|
276
304
|
kind,
|
|
277
305
|
port: this.#native.port(index)
|
|
278
306
|
}));
|
|
279
|
-
this.#params =
|
|
280
|
-
this.#guides =
|
|
281
|
-
this.#backing =
|
|
307
|
+
this.#params = inputs.map(defaultSlotParams);
|
|
308
|
+
this.#guides = inputs.map(() => null);
|
|
309
|
+
this.#backing = settings === null ? null : new Backing(host, settings, this.#native, {
|
|
282
310
|
progress: (fraction) => {
|
|
283
311
|
if (this.#phase !== "destroyed") this.#events.emit("renderProgress", fraction);
|
|
284
312
|
},
|
|
@@ -363,7 +391,8 @@ var Mixer = class {
|
|
|
363
391
|
*/
|
|
364
392
|
recordSlot(index, path) {
|
|
365
393
|
const slot = this.#slot(index);
|
|
366
|
-
|
|
394
|
+
const kind = this.slots[slot]?.kind;
|
|
395
|
+
if (kind !== "rtp" && kind !== "l16") throw new Error(`slot ${slot} is a ${String(kind)} slot; only rtp and l16 slots record`);
|
|
367
396
|
if (this.#phase === "destroyed") throw new Error("the mixer is destroyed");
|
|
368
397
|
if (path === "") throw new RangeError("path must name a file");
|
|
369
398
|
this.#native.recordSlot(slot, path);
|
|
@@ -485,6 +514,7 @@ var Mixer = class {
|
|
|
485
514
|
#release() {
|
|
486
515
|
this.#phase = "destroyed";
|
|
487
516
|
this.#offFault();
|
|
517
|
+
this.#letGo();
|
|
488
518
|
this.#native.release();
|
|
489
519
|
}
|
|
490
520
|
#command(send) {
|
|
@@ -559,6 +589,8 @@ var Engine = class {
|
|
|
559
589
|
#events = new Emitter();
|
|
560
590
|
/** Swaps in flight, oldest first; a `retired` event completes the oldest. */
|
|
561
591
|
#retirements = [];
|
|
592
|
+
/** Each mixer's release until it is destroyed; `close` calls whatever is left. */
|
|
593
|
+
#held = /* @__PURE__ */ new Set();
|
|
562
594
|
/** Render jobs on the worker thread, by the id their progress events carry. */
|
|
563
595
|
#renders = /* @__PURE__ */ new Map();
|
|
564
596
|
#live = null;
|
|
@@ -607,6 +639,10 @@ var Engine = class {
|
|
|
607
639
|
swapIn: (mixer, graph, events, onTimeout) => this.#swapIn(mixer, graph, events, onTimeout),
|
|
608
640
|
swapOut: (mixer) => this.#swapOut(mixer),
|
|
609
641
|
onFault: (listener) => this.#events.on("fault", listener),
|
|
642
|
+
hold: (release) => {
|
|
643
|
+
this.#held.add(release);
|
|
644
|
+
return () => this.#held.delete(release);
|
|
645
|
+
},
|
|
610
646
|
isLive: (mixer) => this.#live?.mixer === mixer,
|
|
611
647
|
anyLive: () => this.#live !== null,
|
|
612
648
|
underruns: () => this.#underruns
|
|
@@ -630,12 +666,17 @@ var Engine = class {
|
|
|
630
666
|
this.#assertRunning();
|
|
631
667
|
return this.#native.outputs();
|
|
632
668
|
}
|
|
669
|
+
/** The inputs a `capture` slot may name, the default marked. None on an offline engine. */
|
|
670
|
+
async inputDevices() {
|
|
671
|
+
this.#assertRunning();
|
|
672
|
+
return this.#native.inputs();
|
|
673
|
+
}
|
|
633
674
|
async createMixer(config) {
|
|
634
675
|
this.#assertRunning();
|
|
635
676
|
if (config.slots.length > this.#slots) throw new RangeError(`the engine has ${this.#slots} slots; the mixer asks for ${config.slots.length}`);
|
|
636
|
-
const
|
|
677
|
+
const slots = config.slots.map((slot, i) => slotInput(`slots[${i}]`, slot));
|
|
637
678
|
const destination = config.rtpReturn === void 0 ? null : rtpReturn(config.rtpReturn);
|
|
638
|
-
return new Mixer(this.#host,
|
|
679
|
+
return new Mixer(this.#host, slots, new Emitter(), config.backing ?? null, destination);
|
|
639
680
|
}
|
|
640
681
|
on(event, listener) {
|
|
641
682
|
return this.#events.on(event, listener);
|
|
@@ -646,6 +687,7 @@ var Engine = class {
|
|
|
646
687
|
if (this.#live && this.#state === "running") await this.#live.mixer.destroy();
|
|
647
688
|
this.#state = "closed";
|
|
648
689
|
this.#native.close();
|
|
690
|
+
for (const release of this.#held) release();
|
|
649
691
|
this.#stop(/* @__PURE__ */ new Error("the engine is closed"));
|
|
650
692
|
}
|
|
651
693
|
#swapIn(mixer, graph, events, onTimeout) {
|
|
@@ -25,15 +25,22 @@ export declare class Engine {
|
|
|
25
25
|
*/
|
|
26
26
|
constructor(options: OpenOptions, onEvents: (arg: Array<NativeEvent>) => void)
|
|
27
27
|
/**
|
|
28
|
-
* Builds a mixer with one slot per
|
|
29
|
-
* and l16 slots bind their ports here and report them through
|
|
30
|
-
*
|
|
28
|
+
* Builds a mixer with one slot per input, in order: `"rtp"`, `"l16"`, `"push"` or
|
|
29
|
+
* `"capture"`. The rtp and l16 slots bind their ports here and report them through
|
|
30
|
+
* `Mixer.port`, the capture slots start their inputs here, and a return stream gets its socket
|
|
31
|
+
* and send task here too.
|
|
31
32
|
*/
|
|
32
|
-
createMixer(
|
|
33
|
+
createMixer(slots: Array<SlotInput>, rtpReturn?: RtpReturnInput | undefined | null): Mixer
|
|
33
34
|
/** The output playing now. After a move the events thread sends the same as `outputChanged`. */
|
|
34
35
|
get output(): OutputInfo
|
|
35
36
|
/** The outputs this OS offers, the default marked. Empty on the offline and timer clocks. */
|
|
36
37
|
outputs(): Array<OutputEntry>
|
|
38
|
+
/**
|
|
39
|
+
* The inputs this OS offers, the default marked: the devices a `capture` slot may name. Empty
|
|
40
|
+
* on the offline clock, which opens none. A box on the timer clock plays nothing locally but
|
|
41
|
+
* may still have a mic plugged in.
|
|
42
|
+
*/
|
|
43
|
+
inputs(): Array<OutputEntry>
|
|
37
44
|
/** Offline clock only: renders `blocks` blocks and returns them as interleaved stereo. */
|
|
38
45
|
pump(blocks: number): Float32Array
|
|
39
46
|
/** Writes one slot of the snapshot and publishes the whole of it. */
|
|
@@ -74,7 +81,7 @@ export declare class Engine {
|
|
|
74
81
|
export declare class Mixer {
|
|
75
82
|
/** Moves a finished render's stems into the graph as its backing path, before `swap`. */
|
|
76
83
|
installBacking(render: BackingRender): void
|
|
77
|
-
/** The UDP port a slot listens on, or null for a push slot. */
|
|
84
|
+
/** The UDP port a slot listens on, or null for a push or capture slot. */
|
|
78
85
|
port(index: number): number | null
|
|
79
86
|
/**
|
|
80
87
|
* Hands interleaved stereo PCM to a push slot, which folds it to mono as every slot is
|
|
@@ -217,6 +224,16 @@ export interface RtpReturnStats {
|
|
|
217
224
|
droppedBlocks: number
|
|
218
225
|
}
|
|
219
226
|
|
|
227
|
+
/**
|
|
228
|
+
* One slot as `createMixer` asks for it. `device` and `channel` belong to a `capture` slot: an id
|
|
229
|
+
* from `inputs`, or the default input, and the device's channel from 0.
|
|
230
|
+
*/
|
|
231
|
+
export interface SlotInput {
|
|
232
|
+
kind: string
|
|
233
|
+
device?: string
|
|
234
|
+
channel?: number
|
|
235
|
+
}
|
|
236
|
+
|
|
220
237
|
export interface SlotParamsInput {
|
|
221
238
|
/** Linear amplitude. */
|
|
222
239
|
gain: number
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aeonentertainment/mixer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Low-latency LAN audio mixer for Aeon Karaoke: phone and house mics, a backing track, one mix to the room.",
|
|
5
5
|
"bugs": {
|
|
6
6
|
"url": "https://github.com/aeonentertainment/aeon-mixer/issues"
|