@aeonentertainment/mixer 0.1.0 → 0.3.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 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.1.0/aeonentertainment-mixer-0.1.0.tgz
18
+ npm install https://github.com/aeonentertainment/aeon-mixer/releases/download/v0.3.0/aeonentertainment-mixer-0.3.0.tgz
19
19
  ```
20
20
 
21
21
  ## Use
@@ -51,13 +51,18 @@ await engine.close();
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
53
  mic, and `push` takes PCM the host hands to `mixer.push`. Each slot has gain, pan, mute, solo,
54
- reverb, a pitch reading and a melody guide.
54
+ reverb, a pitch reading, a melody guide and pitch correction toward it, whose delay `stats()`
55
+ reports.
55
56
  - **The backing** is any format Symphonia decodes, resampled to 48 kHz. The armed key and speed
56
57
  are rendered before `start` resolves. Live, it supports seek, pause, resume, a key change and
57
58
  stem gains, and `start`, `seek` and `resume` report the position the lyric clock follows.
58
59
  - **`rtpReturn`** sends the mix to mediasoup's PlainTransport as 10 ms Opus with in-band FEC.
59
60
  `host` is an IP address, never a name.
60
61
  - **Reactions** play through `engine.reactions.play` between songs and during them.
62
+ - **The timer engine**, `openEngine({ output: { timer: true } })`, is for a box with no sound
63
+ card. It opens no device; its own thread renders a block at each deadline on the OS's
64
+ monotonic clock, and the mix leaves on `rtpReturn`. `measure-clock` prints its timing:
65
+ `cargo run --release -p devices --bin measure-clock` in `native/`.
61
66
  - **The offline engine**, `openEngine({ output: { offline: true } })`, opens no device and
62
67
  renders only when its caller runs `engine.pump(blocks)`. The tests run on it.
63
68
 
package/dist/index.d.mts CHANGED
@@ -19,7 +19,7 @@ declare class Engine$1 {
19
19
  */
20
20
  createMixer(kinds: Array<string>, rtpReturn?: RtpReturnInput | undefined | null): Mixer$1;
21
21
  get output(): OutputInfo$1;
22
- /** The outputs this OS offers, the default marked. Empty on the offline clock. */
22
+ /** The outputs this OS offers, the default marked. Empty on the offline and timer clocks. */
23
23
  outputs(): Array<OutputEntry>;
24
24
  /** Offline clock only: renders `blocks` blocks and returns them as interleaved stereo. */
25
25
  pump(blocks: number): Float32Array;
@@ -72,7 +72,7 @@ declare class Mixer$1 {
72
72
  * recording it for null.
73
73
  */
74
74
  recordSlot(index: number, path?: string | undefined | null): void;
75
- /** Every slot's ingest counters, as the audio thread last published them. */
75
+ /** Every slot's ingest counters and corrector delay, as the audio thread last published them. */
76
76
  stats(): Array<SlotStatsInfo>;
77
77
  /** The return stream's counters, or null for a mixer without one. */
78
78
  returnStats(): RtpReturnStats | null;
@@ -144,9 +144,8 @@ interface NoteInput {
144
144
  endFrame: number;
145
145
  }
146
146
  interface OpenOptions {
147
- /** The offline driver, pumped from JavaScript, instead of a device. */
148
- offline: boolean;
149
- /** An id from `outputs`, or the default output. */
147
+ clock: 'offline' | 'timer' | 'device';
148
+ /** A device clock's output: an id from `outputs`, or the default output. */
150
149
  device?: string;
151
150
  periodFrames: number;
152
151
  /** The jitter buffer's playout delay for every `rtp` and `l16` slot. */
@@ -199,6 +198,8 @@ interface SlotStatsInfo {
199
198
  concealed: number;
200
199
  resyncs: number;
201
200
  jitterMs: number;
201
+ correctorDelayMs: number;
202
+ correctorMaxDelayMs: number;
202
203
  }
203
204
  interface StemGainsInput {
204
205
  instrumental: number;
@@ -211,9 +212,15 @@ type Capability = "guideVocal" | "key" | "speed" | "seek" | "shortVersion" | "pa
211
212
  type IngestKind = "rtp" | "l16" | "push";
212
213
  type StemKind = "instrumental" | "guideVocals";
213
214
  interface OpenEngineOptions {
215
+ /**
216
+ * A device; `timer` for a box with no sound card, whose own thread renders on the OS's clock
217
+ * and whose mix leaves only on a mixer's `rtpReturn`; `offline` for tests, which pump it.
218
+ */
214
219
  output: {
215
220
  device?: string;
216
221
  periodFrames?: 64 | 128;
222
+ } | {
223
+ timer: true;
217
224
  } | {
218
225
  offline: true;
219
226
  };
@@ -285,6 +292,13 @@ interface SlotStats {
285
292
  concealed: number;
286
293
  resyncs: number;
287
294
  jitterMs: number;
295
+ /**
296
+ * How far the corrector holds this slot's voice now; 0 with correction off. It moves with the
297
+ * voice and one read is a sample of it, so show it and never add it to a budget or a clock.
298
+ */
299
+ correctorDelayMs: number;
300
+ /** The most that delay can reach; 0 with correction off. It holds still, so a budget adds this. */
301
+ correctorMaxDelayMs: number;
288
302
  }
289
303
  type EngineEvents = {
290
304
  fault: (error: Error) => void;
@@ -424,8 +438,8 @@ export declare class Mixer {
424
438
  /** An engine on the test clock. Its caller pumps it; nothing plays. */
425
439
  type OfflineEngine = Engine & Required<Pick<Engine, "pump">>;
426
440
  /**
427
- * Opens the engine for the life of the process: the device, or the offline driver, running the
428
- * silent graph until a mixer starts.
441
+ * Opens the engine for the life of the process: the device, the timer clock on a box with no
442
+ * sound card, or the offline driver, running the silent graph until a mixer starts.
429
443
  */
430
444
  export declare function openEngine(options: OpenEngineOptions & {
431
445
  output: {
package/dist/index.mjs CHANGED
@@ -546,16 +546,21 @@ var Engine = class {
546
546
  };
547
547
  if ("offline" in options.output) {
548
548
  this.#native = new NativeEngine({
549
- offline: true,
549
+ clock: "offline",
550
550
  periodFrames: 128,
551
551
  playoutDelayFrames
552
552
  }, deliver);
553
553
  this.pump = (blocks) => this.#native.pump(integerIn("blocks", blocks, 1, 1 << 20));
554
- } else {
554
+ } else if ("timer" in options.output) this.#native = new NativeEngine({
555
+ clock: "timer",
556
+ periodFrames: 128,
557
+ playoutDelayFrames
558
+ }, deliver);
559
+ else {
555
560
  const { device, periodFrames = 128 } = options.output;
556
561
  if (periodFrames !== 64 && periodFrames !== 128) throw new RangeError(`periodFrames must be 64 or 128, got ${String(periodFrames)}`);
557
562
  this.#native = new NativeEngine({
558
- offline: false,
563
+ clock: "device",
559
564
  periodFrames,
560
565
  playoutDelayFrames,
561
566
  ...device === void 0 ? {} : { device }
@@ -31,7 +31,7 @@ export declare class Engine {
31
31
  */
32
32
  createMixer(kinds: Array<string>, rtpReturn?: RtpReturnInput | undefined | null): Mixer
33
33
  get output(): OutputInfo
34
- /** The outputs this OS offers, the default marked. Empty on the offline clock. */
34
+ /** The outputs this OS offers, the default marked. Empty on the offline and timer clocks. */
35
35
  outputs(): Array<OutputEntry>
36
36
  /** Offline clock only: renders `blocks` blocks and returns them as interleaved stereo. */
37
37
  pump(blocks: number): Float32Array
@@ -85,7 +85,7 @@ export declare class Mixer {
85
85
  * recording it for null.
86
86
  */
87
87
  recordSlot(index: number, path?: string | undefined | null): void
88
- /** Every slot's ingest counters, as the audio thread last published them. */
88
+ /** Every slot's ingest counters and corrector delay, as the audio thread last published them. */
89
89
  stats(): Array<SlotStatsInfo>
90
90
  /** The return stream's counters, or null for a mixer without one. */
91
91
  returnStats(): RtpReturnStats | null
@@ -105,6 +105,16 @@ export interface BackingInput {
105
105
  speed: number
106
106
  }
107
107
 
108
+ /** What drives the audio thread. */
109
+ export declare const enum ClockKind {
110
+ /** The offline driver, pumped from JavaScript. */
111
+ Offline = 'offline',
112
+ /** The timer clock, for a box with no sound card: no device, and nothing to pump. */
113
+ Timer = 'timer',
114
+ /** An OS output. */
115
+ Device = 'device',
116
+ }
117
+
108
118
  export interface CorrectorInput {
109
119
  enabled: boolean
110
120
  strength: number
@@ -137,9 +147,8 @@ export interface NoteInput {
137
147
  }
138
148
 
139
149
  export interface OpenOptions {
140
- /** The offline driver, pumped from JavaScript, instead of a device. */
141
- offline: boolean
142
- /** An id from `outputs`, or the default output. */
150
+ clock: 'offline' | 'timer' | 'device'
151
+ /** A device clock's output: an id from `outputs`, or the default output. */
143
152
  device?: string
144
153
  periodFrames: number
145
154
  /** The jitter buffer's playout delay for every `rtp` and `l16` slot. */
@@ -199,6 +208,8 @@ export interface SlotStatsInfo {
199
208
  concealed: number
200
209
  resyncs: number
201
210
  jitterMs: number
211
+ correctorDelayMs: number
212
+ correctorMaxDelayMs: number
202
213
  }
203
214
 
204
215
  export interface StemGainsInput {
@@ -838,3 +838,4 @@ module.exports = nativeBinding
838
838
  module.exports.BackingRender = nativeBinding.BackingRender
839
839
  module.exports.Engine = nativeBinding.Engine
840
840
  module.exports.Mixer = nativeBinding.Mixer
841
+ module.exports.ClockKind = nativeBinding.ClockKind
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeonentertainment/mixer",
3
- "version": "0.1.0",
3
+ "version": "0.3.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"