@camstack/types 1.2.177 → 1.2.179

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.
@@ -74,6 +74,30 @@ export type RecordingObjectTriggerClass = z.infer<typeof RecordingObjectTriggerC
74
74
  * sensori su UNA camera è già oltre qualunque installazione osservata.
75
75
  */
76
76
  export declare const MAX_SENSOR_TRIGGER_DEVICES = 16;
77
+ /**
78
+ * What the bands editor OFFERS when an operator first ticks "audio above
79
+ * threshold" on a band. Not a schema default: an absent `audioThresholdDbfs`
80
+ * means the audio source is not listened to at all, and it stays absent until
81
+ * asked for.
82
+ *
83
+ * It lived as a bare `-30` inline in `RecordingBandsEditor.tsx`, which is how
84
+ * a calibration hides: unnamed, unexplained, and invisible to any guard. D460
85
+ * gave it a name so the claim below can be read and disagreed with.
86
+ *
87
+ * **Not shifted at the D459 epoch.** `-30 dBFS is loud enough to be worth
88
+ * recording` is a statement about the scale, not a number anybody read off the
89
+ * pre-epoch decoder — there is no recorded measurement behind it, and absent
90
+ * evidence of measurement the provenance test says a scale statement stays put
91
+ * (the reasoning is D460's, and the same answer `SILENCE_FLOOR_DBFS` gets).
92
+ *
93
+ * As with `audioMarkerMinDbfs`, the epoch makes this seed START working rather
94
+ * than become too permissive: on the old scale a PCMU camera had to reach a
95
+ * true -18 dBFS to read -30, so the trigger was nearly dead on 19 of 25
96
+ * cameras. A band seeded with it now fires roughly as the editor implies.
97
+ *
98
+ * @dbfs-era itu
99
+ */
100
+ export declare const RECORDING_AUDIO_TRIGGER_OFFERED_DBFS = -30;
77
101
  /** Which detectors trigger an `events`-mode band. */
78
102
  export declare const RecordingTriggersSchema: z.ZodObject<{
79
103
  motion: z.ZodOptional<z.ZodBoolean>;
@@ -4,6 +4,7 @@ import type { BrokerConsumerAttribution } from '../capabilities/stream-broker.ca
4
4
  import type { FrameRef } from '../types/frame-view.js';
5
5
  import type { FrameFormat } from '../types/io.js';
6
6
  import type { FrameHandle } from './frame-handle.js';
7
+ import type { AudioChunkFormat } from '../audio/audio-chunk-format.js';
7
8
  export declare const DecoderStatsSchema: z.ZodObject<{
8
9
  inputFps: z.ZodNumber;
9
10
  outputFps: z.ZodNumber;
@@ -167,6 +168,12 @@ export interface DecodedAudioChunk {
167
168
  readonly sampleRate: number;
168
169
  readonly channels: number;
169
170
  readonly timestamp: number;
171
+ /**
172
+ * Byte format of `data`. ABSENT MEANS `f32le` (D455). A chunk carrying
173
+ * `pcmu` / `pcma` holds ONE byte per sample and is only ever delivered to a
174
+ * subscription whose `accept` list named that format.
175
+ */
176
+ readonly format?: AudioChunkFormat;
170
177
  }
171
178
  export interface AudioSubscribeOptions {
172
179
  /**
@@ -175,6 +182,12 @@ export interface AudioSubscribeOptions {
175
182
  * (`audio-analyzer`, …). Missing tag → recorded as `'unknown'`.
176
183
  */
177
184
  readonly tag?: string;
185
+ /**
186
+ * Byte formats this subscriber can READ (D455). The broker serves a chunk's
187
+ * own format when it is named here and expands to `f32le` otherwise, so a
188
+ * subscriber is never handed bytes it cannot interpret. Absent = `f32le`.
189
+ */
190
+ readonly accept?: readonly AudioChunkFormat[];
178
191
  }
179
192
  /**
180
193
  * Broker lifecycle state. Inferred from `BrokerStatusSchema` — the enum the
@@ -1,36 +1,3 @@
1
- /**
2
- * audio-condition — the authoring logic behind the sustained-sound condition
3
- * (`NcConditions.audio`), pure and UI-framework-free.
4
- *
5
- * WHY IT LIVES HERE
6
- * ─────────────────
7
- * Same reason as `condition-taxonomy.ts` next door: two editors author this
8
- * condition (the admin UI and the viewer), the vocabulary it offers is already
9
- * in this package (`AUDIO_MACRO_LABELS`), and a second copy of "what a cleared
10
- * threshold writes" is a mirror free to drift. The viewer keeps its own copy
11
- * because it is a separate app with its own toolchain; every surface that CAN
12
- * import `@camstack/types` uses this one.
13
- *
14
- * Four behaviours here are not cosmetic:
15
- *
16
- * 1. **dBFS is negative-going.** `0` is full scale; digital silence reads
17
- * {@link NC_AUDIO_DBFS_FLOOR}. A control that let an operator author `60`
18
- * meaning "loud" would write a threshold no sample can reach, and the rule
19
- * would look broken rather than mis-configured. Every write is clamped.
20
- * 2. **Neither filter given = the engine REFUSES.** With no level floor and no
21
- * labels every sample is trivially a hit, so the window would fire on
22
- * silence. {@link audioIsFailClosed} is how an editor says that BEFORE the
23
- * save instead of shipping a rule that never notifies and never says why.
24
- * 3. **Both label spellings are the same label.** The hub's taxonomy serves
25
- * `audio-dog`; the macro catalog and an operator both say `dog`, and the
26
- * matcher normalizes the prefix away on both sides. Selection therefore
27
- * compares NORMALIZED ids — a rule authored in the admin UI must not read
28
- * as "nothing selected" on the phone.
29
- * 4. **A stored label the build does not know stays visible.** It is offered
30
- * as an explicitly-unknown, removable choice, never silently dropped:
31
- * rewriting the rule the operator is looking at is worse than showing a
32
- * stale id.
33
- */
34
1
  import { type NcAudioCondition } from '../capabilities/notification-rules.cap.js';
35
2
  import type { NcTaxonomy } from '../catalogs/nc-taxonomy.js';
36
3
  /** Strip the taxonomy's `audio-` namespace: `audio-dog` and `dog` are one label. */
@@ -50,8 +17,21 @@ export declare function normalizeAudioLabel(value: string): string;
50
17
  export declare function audioKindId(macroId: string): string;
51
18
  /** Level bounds, in dBFS. Negative-going: the ceiling is full scale. */
52
19
  export declare const NC_AUDIO_DB_MIN = -96;
20
+ /**
21
+ * Full scale. The ceiling of dBFS itself on any decoder, not a calibration —
22
+ * untouched by D459 and untouchable by anything like it.
23
+ *
24
+ * @dbfs-era scale-bound
25
+ */
53
26
  export declare const NC_AUDIO_DB_MAX = 0;
54
- /** Step of the level control — 3 dB is one perceptual notch, not one unit. */
27
+ /**
28
+ * Step of the level control — 3 dB is one perceptual notch, not one unit.
29
+ *
30
+ * A step between two levels, so it survives any uniform scale change: 3 dB is
31
+ * the same interval before and after the D459 epoch.
32
+ *
33
+ * @dbfs-era relative
34
+ */
55
35
  export declare const NC_AUDIO_DB_STEP = 3;
56
36
  /**
57
37
  * Offered when the operator turns the level filter ON. NOT a schema default:
@@ -66,8 +46,42 @@ export declare const NC_AUDIO_DB_STEP = 3;
66
46
  * operator turned the filter on for, and the rule looks broken rather than
67
47
  * mis-seeded. The offered floor is the QUIET end of that measured band; the
68
48
  * editor says which way the number runs rather than leaving it to be inferred.
49
+ *
50
+ * ⚠ **That measurement was taken on the pre-D459 scale, and D460 moved this
51
+ * constant because of it.** Until {@link AUDIO_SCALE_EPOCH_MS} the native RTP
52
+ * G.711 decoder reported levels 12.04 dB low (PCMU) or 18.06 dB low (PCMA).
53
+ *
54
+ * This is the ONE absolute-dBFS number in the repo that is a MEASUREMENT
55
+ * rather than a statement about the scale, and it is the only one with a
56
+ * recorded provenance — which is exactly why it is also the only shared
57
+ * constant that converts. Three things agree that the camera it was measured
58
+ * on spoke PCMU:
59
+ *
60
+ * - 19 of this hub's 25 cameras with audio are PCMU, 5 AAC, 1 L16, 0 PCMA
61
+ * (codec census over 10 h of `audio: track detected`, 2026-09-11);
62
+ * - **-55..-26 dBFS is not a close cry on a true scale.** -26 is a quiet
63
+ * room. Read as a PCMU measurement the band is -43..-14, which is exactly
64
+ * where a cry a metre from the microphone sits;
65
+ * - there is no PCMA camera on this installation, so the only candidate
66
+ * correction is mu-law's.
67
+ *
68
+ * So the band is restated, not re-guessed: the offered floor stays "the QUIET
69
+ * end of the measured band", and the band moves to where it was always really
70
+ * sitting. {@link ituDbfsFromPreEpoch} does the arithmetic so the derivation
71
+ * survives in the source instead of becoming a number somebody preferred.
72
+ *
73
+ * `Math.round` is the control's own granularity, not a fudge: the editor's
74
+ * level field is an integer stepper and `patchAudio` clamps through
75
+ * `clampInt`, so a fractional seed could never reach storage anyway.
76
+ *
77
+ * **This changes no existing rule.** It is what an editor OFFERS when the
78
+ * level filter is switched on with nothing stored; every `dbThreshold` an
79
+ * operator already authored is untouched, and those are a separate question
80
+ * — see D460 on why they are declared rather than migrated.
81
+ *
82
+ * @dbfs-era pre-itu-pcmu
69
83
  */
70
- export declare const NC_AUDIO_DB_OFFERED = -55;
84
+ export declare const NC_AUDIO_DB_OFFERED: number;
71
85
  export declare const NC_AUDIO_HIT_PERCENT_MIN = 1;
72
86
  export declare const NC_AUDIO_HIT_PERCENT_MAX = 100;
73
87
  export declare const NC_AUDIO_SAMPLING_MIN_SEC = 1;
@@ -1,5 +1,121 @@
1
1
  const require_event_category = require("./event-category-BNJ9EE5B.js");
2
2
  let zod = require("zod");
3
+ //#region src/audio/audio-chunk-format.ts
4
+ /**
5
+ * The audio chunk plane's byte format, and the ONE expansion from a coded
6
+ * window to float samples (D455).
7
+ *
8
+ * ## Why a format at all
9
+ *
10
+ * D450 took the plane off its 8 → 16 kHz upsample: it carries the SOURCE
11
+ * RATE, and the one consumer that needs 16 kHz resamples next to the model.
12
+ * It left the FORMAT alone — the broker still turned each G.711 byte into a
13
+ * 4-byte f32le sample before the bytes entered the transport, so every leg of
14
+ * the plane carried four times the source. The plane crosses hub-main twice on
15
+ * the way to the analyzer, and the fleet's G.711 cameras are ~79 % of it.
16
+ *
17
+ * So the plane carries the source BYTES too, and whoever needs floats expands
18
+ * them where it needs them. That is the same argument D450 made for the rate,
19
+ * one step further along the same wire.
20
+ *
21
+ * ## Why the expansion lives here
22
+ *
23
+ * Two packages need it and they must never disagree: `addon-pipeline`'s broker
24
+ * (which still has to serve a subscriber that did NOT ask for coded bytes —
25
+ * `AudioChunkPlane` expands per subscription) and
26
+ * `addon-pipeline-orchestrator`'s `AudioWindowAccumulator` (which flushes an
27
+ * f32le window to the analyzer cap, whose `AudioChunkInput` contract is
28
+ * unchanged and stays f32le). Both bundle the bare `@camstack/types` entry
29
+ * into their own dist (`self-contained` externals), so this travels with a
30
+ * `camstack deploy` and needs no published server.
31
+ *
32
+ * A second μ-law table anywhere else is the defect this module exists to
33
+ * prevent. (`stream-broker.ts`'s `mulawToPcm` / `alawToPcm` are the ENCODE
34
+ * direction for the WebRTC egress — a different transform, not a copy.)
35
+ *
36
+ * ## Absent means f32le
37
+ *
38
+ * `format` is optional on the wire and its absence means `f32le` — today's
39
+ * bytes, byte for byte. A peer that never heard of the field is served what it
40
+ * has always been served, because the broker only emits a coded window to a
41
+ * subscription that DECLARED it accepts one (`AudioSubscribeOptions.accept`).
42
+ * That is the D448 `rawForward` negotiation, and it is what makes this
43
+ * deployable one addon at a time across three nodes.
44
+ */
45
+ /** Every byte format the audio chunk plane can carry. `f32le` is the default. */
46
+ var AUDIO_CHUNK_FORMATS = [
47
+ "f32le",
48
+ "pcmu",
49
+ "pcma"
50
+ ];
51
+ /** Type guard for a value that arrived over the wire as a format name. */
52
+ function isAudioChunkFormat(value) {
53
+ return typeof value === "string" && AUDIO_CHUNK_FORMATS.includes(value);
54
+ }
55
+ /**
56
+ * Bytes one sample of `format` occupies. This is the number every duration
57
+ * calculation over a chunk's `data` must use — a window accumulator that
58
+ * hard-codes 4 reports a quarter of the audio it actually holds and flushes
59
+ * four-second windows to a classifier expecting one.
60
+ */
61
+ function audioChunkBytesPerSample(format) {
62
+ return format === "f32le" ? 4 : 1;
63
+ }
64
+ /**
65
+ * Expand a CODED audio window to freshly-owned f32le bytes, 4-byte aligned at
66
+ * offset 0, so a caller can build a `Float32Array` view over them directly.
67
+ * The input may be a msgpack subview at any offset; nothing here reads it as
68
+ * anything but bytes.
69
+ *
70
+ * `f32le` is accepted and returns a copy, but a caller that already knows the
71
+ * format is `f32le` should not call this at all — it has the bytes. Both
72
+ * callers branch on the format first for exactly that reason, and the copy
73
+ * here exists so the return type is always owned rather than sometimes
74
+ * aliasing the argument.
75
+ */
76
+ function expandAudioChunkToF32le(data, format) {
77
+ if (format === "f32le") {
78
+ const copy = new Uint8Array(data.byteLength);
79
+ copy.set(data);
80
+ return copy;
81
+ }
82
+ const table = format === "pcmu" ? ULAW_TABLE : ALAW_TABLE;
83
+ const out = new Float32Array(data.length);
84
+ for (let i = 0; i < data.length; i++) out[i] = table[data[i]];
85
+ return new Uint8Array(out.buffer);
86
+ }
87
+ /**
88
+ * Build the μ-law decode table (ITU-T G.711). Each of the 256 byte values maps
89
+ * to a 16-bit PCM sample, normalised to [-1.0, 1.0] for f32le output.
90
+ *
91
+ * Moved here verbatim from `audio-rtp-decoder.ts`, which no longer decodes:
92
+ * it buffers the coded bytes and the plane's consumers expand.
93
+ */
94
+ function buildUlawTable() {
95
+ const table = new Float32Array(256);
96
+ for (let i = 0; i < 256; i++) {
97
+ const complemented = ~i & 255;
98
+ const sign = (complemented & 128) !== 0 ? -1 : 1;
99
+ const exponent = complemented >> 4 & 7;
100
+ table[i] = sign * ((8 * (complemented & 15) + 132 << exponent) - 132) / 32768;
101
+ }
102
+ return table;
103
+ }
104
+ /** Build the A-law decode table (ITU-T G.711). */
105
+ function buildAlawTable() {
106
+ const table = new Float32Array(256);
107
+ for (let i = 0; i < 256; i++) {
108
+ const xored = i ^ 85;
109
+ const sign = (xored & 128) !== 0 ? 1 : -1;
110
+ const exponent = xored >> 4 & 7;
111
+ const mantissa = xored & 15;
112
+ table[i] = sign * (exponent === 0 ? 16 * mantissa + 8 : 16 * mantissa + 264 << exponent - 1) / 32768;
113
+ }
114
+ return table;
115
+ }
116
+ var ULAW_TABLE = buildUlawTable();
117
+ var ALAW_TABLE = buildAlawTable();
118
+ //#endregion
3
119
  //#region src/interfaces/config-ui.ts
4
120
  /** Predefined tabs with standard label, icon, and sort order.
5
121
  *
@@ -1533,11 +1649,20 @@ var SubscribeFramesResultSchema = zod.z.object({
1533
1649
  * (the wire-serialisable supertype of `Buffer`) to match `DecodedFrameSchema`
1534
1650
  * / `EncodedPacketSchema`'s precedent; a `Buffer` is assignable to it.
1535
1651
  */
1652
+ var AudioChunkFormatSchema = zod.z.enum(AUDIO_CHUNK_FORMATS);
1536
1653
  var DecodedAudioChunkSchema = zod.z.object({
1537
1654
  data: zod.z.instanceof(Uint8Array),
1538
1655
  sampleRate: zod.z.number().int().positive(),
1539
1656
  channels: zod.z.number().int().positive(),
1540
- timestamp: zod.z.number()
1657
+ timestamp: zod.z.number(),
1658
+ /**
1659
+ * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
1660
+ * byte, for any peer that never heard of this field. A coded window
1661
+ * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
1662
+ * subscription that DECLARED it accepts one, so absence can never mean
1663
+ * "coded bytes a consumer will read as floats" (D455).
1664
+ */
1665
+ format: AudioChunkFormatSchema.optional()
1541
1666
  });
1542
1667
  /**
1543
1668
  * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). The
@@ -1549,7 +1674,18 @@ var DecodedAudioChunkSchema = zod.z.object({
1549
1674
  var SubscribeAudioChunksInputSchema = zod.z.object({
1550
1675
  brokerId: zod.z.string(),
1551
1676
  /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
1552
- tag: zod.z.string().optional()
1677
+ tag: zod.z.string().optional(),
1678
+ /**
1679
+ * Byte formats this subscriber can READ, best first. The broker serves the
1680
+ * chunk's own format when it is in this list and expands to `f32le`
1681
+ * otherwise, so a subscriber is never handed bytes it cannot interpret.
1682
+ *
1683
+ * Absent (or without the source format) means `f32le` — the behaviour every
1684
+ * subscriber had before D455, unchanged. This is the negotiation half of
1685
+ * the source-bytes lever: it is what lets the broker and its consumers
1686
+ * deploy one at a time across three nodes.
1687
+ */
1688
+ accept: zod.z.array(AudioChunkFormatSchema).readonly().optional()
1553
1689
  });
1554
1690
  /** Result of `stream-broker.subscribeAudioChunks`. */
1555
1691
  var SubscribeAudioChunksResultSchema = zod.z.object({
@@ -3701,6 +3837,7 @@ function createDeviceProxy(api, binding, opts) {
3701
3837
  getDeviceSettings: (input) => dispatchSystem("addonSettings", "getDeviceSettings", "query", input),
3702
3838
  updateDeviceSettings: (input) => dispatchSystem("addonSettings", "updateDeviceSettings", "mutation", input)
3703
3839
  },
3840
+ audioAnalyzer: { detachDevice: (input) => dispatchSystem("audioAnalyzer", "detachDevice", "mutation", input) },
3704
3841
  cameraPipelineConfig: {
3705
3842
  getDeviceSettingsContribution: (input) => dispatchSystem("cameraPipelineConfig", "getDeviceSettingsContribution", "query", input),
3706
3843
  getDeviceLiveContribution: (input) => dispatchSystem("cameraPipelineConfig", "getDeviceLiveContribution", "query", input),
@@ -4113,6 +4250,18 @@ function sleepCancellable(ms, signal) {
4113
4250
  });
4114
4251
  }
4115
4252
  //#endregion
4253
+ Object.defineProperty(exports, "AUDIO_CHUNK_FORMATS", {
4254
+ enumerable: true,
4255
+ get: function() {
4256
+ return AUDIO_CHUNK_FORMATS;
4257
+ }
4258
+ });
4259
+ Object.defineProperty(exports, "AudioChunkFormatSchema", {
4260
+ enumerable: true,
4261
+ get: function() {
4262
+ return AudioChunkFormatSchema;
4263
+ }
4264
+ });
4116
4265
  Object.defineProperty(exports, "BOOT_RECOVERY_BACKOFF_MS", {
4117
4266
  enumerable: true,
4118
4267
  get: function() {
@@ -4395,6 +4544,12 @@ Object.defineProperty(exports, "asString", {
4395
4544
  return asString;
4396
4545
  }
4397
4546
  });
4547
+ Object.defineProperty(exports, "audioChunkBytesPerSample", {
4548
+ enumerable: true,
4549
+ get: function() {
4550
+ return audioChunkBytesPerSample;
4551
+ }
4552
+ });
4398
4553
  Object.defineProperty(exports, "collectHydratedFieldEntries", {
4399
4554
  enumerable: true,
4400
4555
  get: function() {
@@ -4473,6 +4628,12 @@ Object.defineProperty(exports, "event", {
4473
4628
  return event;
4474
4629
  }
4475
4630
  });
4631
+ Object.defineProperty(exports, "expandAudioChunkToF32le", {
4632
+ enumerable: true,
4633
+ get: function() {
4634
+ return expandAudioChunkToF32le;
4635
+ }
4636
+ });
4476
4637
  Object.defineProperty(exports, "expandCapMethods", {
4477
4638
  enumerable: true,
4478
4639
  get: function() {
@@ -4485,6 +4646,12 @@ Object.defineProperty(exports, "hydrateSchema", {
4485
4646
  return hydrateSchema;
4486
4647
  }
4487
4648
  });
4649
+ Object.defineProperty(exports, "isAudioChunkFormat", {
4650
+ enumerable: true,
4651
+ get: function() {
4652
+ return isAudioChunkFormat;
4653
+ }
4654
+ });
4488
4655
  Object.defineProperty(exports, "isDeviceConfigCap", {
4489
4656
  enumerable: true,
4490
4657
  get: function() {
@@ -1,5 +1,121 @@
1
1
  import { t as EventCategory } from "./event-category-DAXzJeTX.mjs";
2
2
  import { z } from "zod";
3
+ //#region src/audio/audio-chunk-format.ts
4
+ /**
5
+ * The audio chunk plane's byte format, and the ONE expansion from a coded
6
+ * window to float samples (D455).
7
+ *
8
+ * ## Why a format at all
9
+ *
10
+ * D450 took the plane off its 8 → 16 kHz upsample: it carries the SOURCE
11
+ * RATE, and the one consumer that needs 16 kHz resamples next to the model.
12
+ * It left the FORMAT alone — the broker still turned each G.711 byte into a
13
+ * 4-byte f32le sample before the bytes entered the transport, so every leg of
14
+ * the plane carried four times the source. The plane crosses hub-main twice on
15
+ * the way to the analyzer, and the fleet's G.711 cameras are ~79 % of it.
16
+ *
17
+ * So the plane carries the source BYTES too, and whoever needs floats expands
18
+ * them where it needs them. That is the same argument D450 made for the rate,
19
+ * one step further along the same wire.
20
+ *
21
+ * ## Why the expansion lives here
22
+ *
23
+ * Two packages need it and they must never disagree: `addon-pipeline`'s broker
24
+ * (which still has to serve a subscriber that did NOT ask for coded bytes —
25
+ * `AudioChunkPlane` expands per subscription) and
26
+ * `addon-pipeline-orchestrator`'s `AudioWindowAccumulator` (which flushes an
27
+ * f32le window to the analyzer cap, whose `AudioChunkInput` contract is
28
+ * unchanged and stays f32le). Both bundle the bare `@camstack/types` entry
29
+ * into their own dist (`self-contained` externals), so this travels with a
30
+ * `camstack deploy` and needs no published server.
31
+ *
32
+ * A second μ-law table anywhere else is the defect this module exists to
33
+ * prevent. (`stream-broker.ts`'s `mulawToPcm` / `alawToPcm` are the ENCODE
34
+ * direction for the WebRTC egress — a different transform, not a copy.)
35
+ *
36
+ * ## Absent means f32le
37
+ *
38
+ * `format` is optional on the wire and its absence means `f32le` — today's
39
+ * bytes, byte for byte. A peer that never heard of the field is served what it
40
+ * has always been served, because the broker only emits a coded window to a
41
+ * subscription that DECLARED it accepts one (`AudioSubscribeOptions.accept`).
42
+ * That is the D448 `rawForward` negotiation, and it is what makes this
43
+ * deployable one addon at a time across three nodes.
44
+ */
45
+ /** Every byte format the audio chunk plane can carry. `f32le` is the default. */
46
+ var AUDIO_CHUNK_FORMATS = [
47
+ "f32le",
48
+ "pcmu",
49
+ "pcma"
50
+ ];
51
+ /** Type guard for a value that arrived over the wire as a format name. */
52
+ function isAudioChunkFormat(value) {
53
+ return typeof value === "string" && AUDIO_CHUNK_FORMATS.includes(value);
54
+ }
55
+ /**
56
+ * Bytes one sample of `format` occupies. This is the number every duration
57
+ * calculation over a chunk's `data` must use — a window accumulator that
58
+ * hard-codes 4 reports a quarter of the audio it actually holds and flushes
59
+ * four-second windows to a classifier expecting one.
60
+ */
61
+ function audioChunkBytesPerSample(format) {
62
+ return format === "f32le" ? 4 : 1;
63
+ }
64
+ /**
65
+ * Expand a CODED audio window to freshly-owned f32le bytes, 4-byte aligned at
66
+ * offset 0, so a caller can build a `Float32Array` view over them directly.
67
+ * The input may be a msgpack subview at any offset; nothing here reads it as
68
+ * anything but bytes.
69
+ *
70
+ * `f32le` is accepted and returns a copy, but a caller that already knows the
71
+ * format is `f32le` should not call this at all — it has the bytes. Both
72
+ * callers branch on the format first for exactly that reason, and the copy
73
+ * here exists so the return type is always owned rather than sometimes
74
+ * aliasing the argument.
75
+ */
76
+ function expandAudioChunkToF32le(data, format) {
77
+ if (format === "f32le") {
78
+ const copy = new Uint8Array(data.byteLength);
79
+ copy.set(data);
80
+ return copy;
81
+ }
82
+ const table = format === "pcmu" ? ULAW_TABLE : ALAW_TABLE;
83
+ const out = new Float32Array(data.length);
84
+ for (let i = 0; i < data.length; i++) out[i] = table[data[i]];
85
+ return new Uint8Array(out.buffer);
86
+ }
87
+ /**
88
+ * Build the μ-law decode table (ITU-T G.711). Each of the 256 byte values maps
89
+ * to a 16-bit PCM sample, normalised to [-1.0, 1.0] for f32le output.
90
+ *
91
+ * Moved here verbatim from `audio-rtp-decoder.ts`, which no longer decodes:
92
+ * it buffers the coded bytes and the plane's consumers expand.
93
+ */
94
+ function buildUlawTable() {
95
+ const table = new Float32Array(256);
96
+ for (let i = 0; i < 256; i++) {
97
+ const complemented = ~i & 255;
98
+ const sign = (complemented & 128) !== 0 ? -1 : 1;
99
+ const exponent = complemented >> 4 & 7;
100
+ table[i] = sign * ((8 * (complemented & 15) + 132 << exponent) - 132) / 32768;
101
+ }
102
+ return table;
103
+ }
104
+ /** Build the A-law decode table (ITU-T G.711). */
105
+ function buildAlawTable() {
106
+ const table = new Float32Array(256);
107
+ for (let i = 0; i < 256; i++) {
108
+ const xored = i ^ 85;
109
+ const sign = (xored & 128) !== 0 ? 1 : -1;
110
+ const exponent = xored >> 4 & 7;
111
+ const mantissa = xored & 15;
112
+ table[i] = sign * (exponent === 0 ? 16 * mantissa + 8 : 16 * mantissa + 264 << exponent - 1) / 32768;
113
+ }
114
+ return table;
115
+ }
116
+ var ULAW_TABLE = buildUlawTable();
117
+ var ALAW_TABLE = buildAlawTable();
118
+ //#endregion
3
119
  //#region src/interfaces/config-ui.ts
4
120
  /** Predefined tabs with standard label, icon, and sort order.
5
121
  *
@@ -1533,11 +1649,20 @@ var SubscribeFramesResultSchema = z.object({
1533
1649
  * (the wire-serialisable supertype of `Buffer`) to match `DecodedFrameSchema`
1534
1650
  * / `EncodedPacketSchema`'s precedent; a `Buffer` is assignable to it.
1535
1651
  */
1652
+ var AudioChunkFormatSchema = z.enum(AUDIO_CHUNK_FORMATS);
1536
1653
  var DecodedAudioChunkSchema = z.object({
1537
1654
  data: z.instanceof(Uint8Array),
1538
1655
  sampleRate: z.number().int().positive(),
1539
1656
  channels: z.number().int().positive(),
1540
- timestamp: z.number()
1657
+ timestamp: z.number(),
1658
+ /**
1659
+ * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
1660
+ * byte, for any peer that never heard of this field. A coded window
1661
+ * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
1662
+ * subscription that DECLARED it accepts one, so absence can never mean
1663
+ * "coded bytes a consumer will read as floats" (D455).
1664
+ */
1665
+ format: AudioChunkFormatSchema.optional()
1541
1666
  });
1542
1667
  /**
1543
1668
  * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). The
@@ -1549,7 +1674,18 @@ var DecodedAudioChunkSchema = z.object({
1549
1674
  var SubscribeAudioChunksInputSchema = z.object({
1550
1675
  brokerId: z.string(),
1551
1676
  /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
1552
- tag: z.string().optional()
1677
+ tag: z.string().optional(),
1678
+ /**
1679
+ * Byte formats this subscriber can READ, best first. The broker serves the
1680
+ * chunk's own format when it is in this list and expands to `f32le`
1681
+ * otherwise, so a subscriber is never handed bytes it cannot interpret.
1682
+ *
1683
+ * Absent (or without the source format) means `f32le` — the behaviour every
1684
+ * subscriber had before D455, unchanged. This is the negotiation half of
1685
+ * the source-bytes lever: it is what lets the broker and its consumers
1686
+ * deploy one at a time across three nodes.
1687
+ */
1688
+ accept: z.array(AudioChunkFormatSchema).readonly().optional()
1553
1689
  });
1554
1690
  /** Result of `stream-broker.subscribeAudioChunks`. */
1555
1691
  var SubscribeAudioChunksResultSchema = z.object({
@@ -3701,6 +3837,7 @@ function createDeviceProxy(api, binding, opts) {
3701
3837
  getDeviceSettings: (input) => dispatchSystem("addonSettings", "getDeviceSettings", "query", input),
3702
3838
  updateDeviceSettings: (input) => dispatchSystem("addonSettings", "updateDeviceSettings", "mutation", input)
3703
3839
  },
3840
+ audioAnalyzer: { detachDevice: (input) => dispatchSystem("audioAnalyzer", "detachDevice", "mutation", input) },
3704
3841
  cameraPipelineConfig: {
3705
3842
  getDeviceSettingsContribution: (input) => dispatchSystem("cameraPipelineConfig", "getDeviceSettingsContribution", "query", input),
3706
3843
  getDeviceLiveContribution: (input) => dispatchSystem("cameraPipelineConfig", "getDeviceLiveContribution", "query", input),
@@ -4113,4 +4250,4 @@ function sleepCancellable(ms, signal) {
4113
4250
  });
4114
4251
  }
4115
4252
  //#endregion
4116
- export { DecodedAudioChunkSchema as $, DEVICE_STATUS_METHOD as A, hydrateSchema as At, toNodeId as B, ChargingStatus as C, createEvent as Ct, adminUiCapability as D, WELL_KNOWN_TAB_MAP as Dt, DeviceType as E, WELL_KNOWN_TABS as Et, resolveCapMount as F, scopeKey as G, ReadinessTimeoutError as H, systemMethod as I, CAM_PROFILE_ORDER as J, BrokerStatsSchema as K, CAP_NODE_PIN_CONTEXT_KEY as L, expandCapMethods as M, isDeviceConfigCap as N, DEFAULT_RUNTIME_STATE_DURABILITY as O, collectHydratedFieldEntries as Ot, method as P, CameraStreamSchema as Q, nodePin as R, DEVICE_CHILDREN_BATCH_MAX as S, createDurableState as St, DeviceRole as T, isEvent as Tt, emitDownForOwnedCaps as U, ReadinessRegistry as V, readinessKey as W, CamStreamKindSchema as X, CamProfileSchema as Y, CamStreamResolutionSchema as Z, createMirrorSource as _, DATAPLANE_SECRET_HEADER as _t, asJsonObject as a, ProfileSlotSchema as at, deviceOpsCapability as b, BaseAddon as bt, parseJsonArray as c, StreamSourceSchema as ct, BOOT_RECOVERY_BACKOFF_MS as d, SubscribeFramesInputSchema as dt, DecodedFrameSchema as et, DEVICE_SCOPED_CAPS as f, SubscribeFramesResultSchema as ft, createLazyTrpcSource as g, selectAssignedProfileSlots as gt, createEventBusSliceSource as h, parseProfileBrokerId as ht, asJsonArray as i, ProfileRtspEntrySchema as it, event as j, resolveHydratedFieldValue as jt, DEVICE_SETTINGS_CONTRIBUTION_METHODS as k, collectHydratedFieldValues as kt, parseJsonObject as l, SubscribeAudioChunksInputSchema as lt, createDeviceProxy as m, makeSourceBrokerId as mt, sleepCancellable as n, FrameHandleFormatSchema as nt, asNumber as o, ProfileSlotStatusSchema as ot, isDeviceScopedCap as p, makeProfileBrokerId as pt, BrokerStatusSchema as q, asBoolean as r, FrameHandleSchema as rt, asString as s, StreamSourceEntrySchema$1 as st, sleep as t, EncodedPacketSchema as tt, parseJsonUnknown as u, SubscribeAudioChunksResultSchema as ut, createSliceHandle as v, SHARE_VIEW_KINDS as vt, DeviceFeature as w, emitReadiness as wt, viewerUiCapability as x, normalizeAddonInitResult as xt, RawStateResultSchema as y, DisposerChain as yt, readNodePin as z };
4253
+ export { CameraStreamSchema as $, DEVICE_STATUS_METHOD as A, collectHydratedFieldValues as At, toNodeId as B, ChargingStatus as C, createDurableState as Ct, adminUiCapability as D, WELL_KNOWN_TABS as Dt, DeviceType as E, isEvent as Et, resolveCapMount as F, expandAudioChunkToF32le as Ft, scopeKey as G, ReadinessTimeoutError as H, systemMethod as I, isAudioChunkFormat as It, BrokerStatusSchema as J, AudioChunkFormatSchema as K, CAP_NODE_PIN_CONTEXT_KEY as L, expandCapMethods as M, resolveHydratedFieldValue as Mt, isDeviceConfigCap as N, AUDIO_CHUNK_FORMATS as Nt, DEFAULT_RUNTIME_STATE_DURABILITY as O, WELL_KNOWN_TAB_MAP as Ot, method as P, audioChunkBytesPerSample as Pt, CamStreamResolutionSchema as Q, nodePin as R, DEVICE_CHILDREN_BATCH_MAX as S, normalizeAddonInitResult as St, DeviceRole as T, emitReadiness as Tt, emitDownForOwnedCaps as U, ReadinessRegistry as V, readinessKey as W, CamProfileSchema as X, CAM_PROFILE_ORDER as Y, CamStreamKindSchema as Z, createMirrorSource as _, selectAssignedProfileSlots as _t, asJsonObject as a, ProfileRtspEntrySchema as at, deviceOpsCapability as b, DisposerChain as bt, parseJsonArray as c, StreamSourceEntrySchema$1 as ct, BOOT_RECOVERY_BACKOFF_MS as d, SubscribeAudioChunksResultSchema as dt, DecodedAudioChunkSchema as et, DEVICE_SCOPED_CAPS as f, SubscribeFramesInputSchema as ft, createLazyTrpcSource as g, parseProfileBrokerId as gt, createEventBusSliceSource as h, makeSourceBrokerId as ht, asJsonArray as i, FrameHandleSchema as it, event as j, hydrateSchema as jt, DEVICE_SETTINGS_CONTRIBUTION_METHODS as k, collectHydratedFieldEntries as kt, parseJsonObject as l, StreamSourceSchema as lt, createDeviceProxy as m, makeProfileBrokerId as mt, sleepCancellable as n, EncodedPacketSchema as nt, asNumber as o, ProfileSlotSchema as ot, isDeviceScopedCap as p, SubscribeFramesResultSchema as pt, BrokerStatsSchema as q, asBoolean as r, FrameHandleFormatSchema as rt, asString as s, ProfileSlotStatusSchema as st, sleep as t, DecodedFrameSchema as tt, parseJsonUnknown as u, SubscribeAudioChunksInputSchema as ut, createSliceHandle as v, DATAPLANE_SECRET_HEADER as vt, DeviceFeature as w, createEvent as wt, viewerUiCapability as x, BaseAddon as xt, RawStateResultSchema as y, SHARE_VIEW_KINDS as yt, readNodePin as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/types",
3
- "version": "1.2.177",
3
+ "version": "1.2.179",
4
4
  "description": "Shared types, interfaces, and model catalogs for the CamStack detection ecosystem",
5
5
  "keywords": [
6
6
  "camstack",