@camstack/addon-pipeline 0.1.19 → 0.1.20

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 (66) hide show
  1. package/dist/audio-analyzer/index.js +5 -2
  2. package/dist/audio-analyzer/index.js.map +1 -1
  3. package/dist/audio-analyzer/index.mjs +5 -2
  4. package/dist/audio-analyzer/index.mjs.map +1 -1
  5. package/dist/audio-codec-nodeav/index.js +1 -1
  6. package/dist/audio-codec-nodeav/index.mjs +1 -1
  7. package/dist/decoder-nodeav/index.js +2 -2
  8. package/dist/decoder-nodeav/index.mjs +2 -2
  9. package/dist/detection-pipeline/index.js +25 -25
  10. package/dist/detection-pipeline/index.js.map +1 -1
  11. package/dist/detection-pipeline/index.mjs +25 -25
  12. package/dist/detection-pipeline/index.mjs.map +1 -1
  13. package/dist/{index-Bmlkm0Fd.mjs → index-5aYef068.mjs} +3601 -770
  14. package/dist/index-5aYef068.mjs.map +1 -0
  15. package/dist/{index-BbPPvoCx.js → index-B36NMAdu.js} +3577 -746
  16. package/dist/index-B36NMAdu.js.map +1 -0
  17. package/dist/{index-D_cl0Qqb.js → index-CMcx_k6Y.js} +48 -48
  18. package/dist/{index-D_cl0Qqb.js.map → index-CMcx_k6Y.js.map} +1 -1
  19. package/dist/{index-UbcdLS7a.mjs → index-CYb7cFrv.mjs} +46 -46
  20. package/dist/{index-UbcdLS7a.mjs.map → index-CYb7cFrv.mjs.map} +1 -1
  21. package/dist/motion-wasm/index.js +1 -1
  22. package/dist/motion-wasm/index.mjs +1 -1
  23. package/dist/pipeline-runner/index.js +74 -77
  24. package/dist/pipeline-runner/index.js.map +1 -1
  25. package/dist/pipeline-runner/index.mjs +74 -77
  26. package/dist/pipeline-runner/index.mjs.map +1 -1
  27. package/dist/recorder/index.js +2209 -0
  28. package/dist/recorder/index.js.map +1 -0
  29. package/dist/recorder/index.mjs +2209 -0
  30. package/dist/recorder/index.mjs.map +1 -0
  31. package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/FfmpegParamsField.d.ts +41 -0
  32. package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/GeometryBuilder.d.ts +54 -0
  33. package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/format-ua.d.ts +13 -0
  34. package/dist/stream-broker/@mf-types/compiled-types/stream-broker/widgets/index.d.ts +2 -0
  35. package/dist/stream-broker/@mf-types.zip +0 -0
  36. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-h5aXOPSA.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_sdk__loadShare__.mjs-lantnv8e.mjs} +1 -1
  37. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-DJ3UNg7O.mjs +30 -0
  38. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-CYXy_bhS.mjs +21 -0
  39. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-BsB2G7oY.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs-CaDEYBIU.mjs} +8 -7
  40. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-xrRiPUpA.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react__loadShare__.mjs_commonjs-proxy-D6EROtlA.mjs} +1 -1
  41. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-gBEZsQrp.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_1_jsx_mf_2_runtime__loadShare__.mjs-x6pP3Ghk.mjs} +2 -2
  42. package/dist/stream-broker/{__mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-C0E2yCzO.mjs → __mfe_internal__addon_stream_broker_widgets__loadShare__react_mf_2_dom__loadShare__.mjs_commonjs-proxy-CcnN6sbA.mjs} +1 -1
  43. package/dist/stream-broker/_stub.js +963 -333
  44. package/dist/stream-broker/{_virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CupRlwqG.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_stream_broker_widgets-CL9DR49k.mjs} +6 -6
  45. package/dist/stream-broker/{client-NPZqorv9.mjs → client-BvTmMOQu.mjs} +2 -2
  46. package/dist/stream-broker/{hostInit-Bh4w7o5_.mjs → hostInit-ChmiMPS0.mjs} +12 -12
  47. package/dist/stream-broker/{index-D_1p2K9B.mjs → index-BxsFuFmE.mjs} +24 -24
  48. package/dist/stream-broker/{index-mX3Kgiv1.mjs → index-C-248uOU.mjs} +2 -2
  49. package/dist/stream-broker/{index-2Qp8vT3w.mjs → index-C05B6jqp.mjs} +1 -1
  50. package/dist/stream-broker/index-DOJoSShD.mjs +67784 -0
  51. package/dist/stream-broker/{index-Dy2V7VOm.mjs → index-DtOI1aTU.mjs} +10112 -5987
  52. package/dist/stream-broker/{index-Cc6QBqMk.mjs → index-oMq6ilgR.mjs} +253 -267
  53. package/dist/stream-broker/{index-BBcZvb5t.mjs → index-vIWZQBIL.mjs} +1 -1
  54. package/dist/stream-broker/index.js +3168 -543
  55. package/dist/stream-broker/index.js.map +1 -1
  56. package/dist/stream-broker/index.mjs +3172 -546
  57. package/dist/stream-broker/index.mjs.map +1 -1
  58. package/dist/stream-broker/{jsx-runtime-lb0mH5st.mjs → jsx-runtime-BRT_HL0A.mjs} +1 -1
  59. package/dist/stream-broker/remoteEntry.js +1 -1
  60. package/dist/stream-broker/{schemas-ClCuS4qa.mjs → schemas-B7L0qZtq.mjs} +411 -406
  61. package/package.json +51 -3
  62. package/dist/index-BbPPvoCx.js.map +0 -1
  63. package/dist/index-Bmlkm0Fd.mjs.map +0 -1
  64. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.mjs-NjF4kxzW.mjs +0 -19
  65. package/dist/stream-broker/__mfe_internal__addon_stream_broker_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.mjs-BAv_5ISf.mjs +0 -20
  66. package/dist/stream-broker/index-CIJue-4t.mjs +0 -37880
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":["../../src/audio-analyzer/audio-pipeline.ts","../../src/audio-analyzer/addons/analyzer/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access -- existing audio pipeline carries unsafe ONNX runtime calls; pre-existing tech debt, not introduced by Phase E. */\n/**\n * Audio classification pipeline.\n *\n * - macOS: Apple SoundAnalysis framework (303 built-in sound categories)\n * - Cross-platform: YAMNet ONNX via onnxruntime-node\n *\n * This is independent from the video pipeline — different input (audio samples),\n * different engine (no shared pool), different output cadence.\n */\nimport type { IScopedLogger } from '@camstack/types'\nimport * as path from 'node:path'\nimport * as fs from 'node:fs'\nimport { errMsg, HF_BASE_URL } from '@camstack/types'\n// eslint-disable-next-line no-restricted-imports -- downloadFile is a build-time dep used to materialise the YAMNet model on first boot. Mirrors the waiver detection-pipeline + addon-embedding-encoder use for the same reason. Single source of truth lives in `@camstack/core/download/model-downloader`; consolidating it via `ctx.deps.downloadFile` would require extending `IAddonDepsManager` across every kernel context — out of scope for this addon's first auto-download wiring.\nimport { downloadFile } from '@camstack/core'\n\n// `__dirname` is shimmed by tsup (`shims: true`) for both CJS and ESM\n// output, so it works whether this bundle is loaded by `require` or by\n// `await import(...)` (the agent path).\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface AudioClassificationResult {\n readonly classifications: readonly {\n readonly className: string\n readonly score: number\n }[]\n readonly inferenceMs: number\n}\n\nexport interface AudioChunk {\n readonly data: Float32Array\n readonly sampleRate: number\n readonly channels: number\n}\n\nexport interface IAudioPipeline {\n initialize(): Promise<void>\n classify(chunk: AudioChunk): Promise<AudioClassificationResult>\n dispose(): Promise<void>\n}\n\nexport type AudioBackend = 'yamnet-onnx' | 'apple-soundanalysis'\n\nexport interface AudioPipelineOptions {\n readonly backend?: AudioBackend\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the appropriate audio pipeline.\n *\n * - 'yamnet-onnx': Cross-platform YAMNet ONNX (requires model download)\n * - 'apple-soundanalysis': macOS 12+ Apple SoundAnalysis (zero model download, Neural Engine)\n * - undefined: auto-detect (Apple SA on macOS, YAMNet on Linux)\n */\nexport async function createAudioPipeline(\n modelsDir: string,\n logger: IScopedLogger,\n options?: AudioPipelineOptions,\n): Promise<IAudioPipeline> {\n const backend = options?.backend ?? (process.platform === 'darwin' ? 'apple-soundanalysis' : 'yamnet-onnx')\n\n if (backend === 'apple-soundanalysis') {\n return new AppleSoundAnalysisPipeline(logger)\n }\n return new YamnetOnnxPipeline(modelsDir, logger)\n}\n\n// ---------------------------------------------------------------------------\n// YAMNet ONNX (cross-platform)\n// ---------------------------------------------------------------------------\n\n/**\n * Canonical model URLs on the camstack HuggingFace mirror. Mirrors the\n * convention every detection model follows (single point of truth =\n * `HF_BASE_URL` from `@camstack/types`); the auto-download path uses\n * `downloadFile` from `@camstack/core`, the SAME helper detection-\n * pipeline uses to materialise its YOLO/face/plate models. Missing\n * model on disk → fetch from HF; cached file → no-op.\n *\n * Repo layout follows the detection-pipeline pattern:\n * {domain}/{family}/{format}/{filename}\n * For YAMNet that's `audioClassification/yamnet/onnx/camstack-yamnet.onnx`,\n * with the labels JSON sitting one level up (`audioClassification/yamnet/`)\n * because they're format-agnostic (same 521 AudioSet class names whether\n * the runtime is ONNX, OpenVINO, or TF).\n */\nconst YAMNET_MODEL_URL = `${HF_BASE_URL}/audioClassification/yamnet/onnx/camstack-yamnet.onnx`\nconst YAMNET_LABELS_URL = `${HF_BASE_URL}/audioClassification/yamnet/camstack-yamnet-labels.json`\n\nclass YamnetOnnxPipeline implements IAudioPipeline {\n private session: import('onnxruntime-node').InferenceSession | null = null\n private inputName = ''\n private labels: readonly string[] = []\n private readonly log: IScopedLogger\n\n constructor(\n private readonly modelsDir: string,\n logger: IScopedLogger,\n ) {\n this.log = logger\n }\n\n async initialize(): Promise<void> {\n const ort = await import('onnxruntime-node')\n const modelPath = path.join(this.modelsDir, 'camstack-yamnet.onnx')\n const labelsPath = path.join(this.modelsDir, 'camstack-yamnet-labels.json')\n\n // Auto-download model + labels from the camstack HF mirror — same\n // pattern as detection-pipeline's `ensureModel`. `downloadFile` is\n // a no-op when the file is already on disk; on first boot it\n // streams atomically (`.downloading` tmp + rename on success).\n if (!fs.existsSync(modelPath)) {\n this.log.info('YAMNet ONNX model not found locally — downloading from HuggingFace', {\n meta: { url: YAMNET_MODEL_URL, dest: modelPath },\n })\n await downloadFile(YAMNET_MODEL_URL, modelPath)\n this.log.info('YAMNet ONNX model downloaded', {\n meta: { sizeBytes: fs.statSync(modelPath).size },\n })\n }\n if (!fs.existsSync(labelsPath)) {\n this.log.info('YAMNet labels not found locally — downloading from HuggingFace', {\n meta: { url: YAMNET_LABELS_URL, dest: labelsPath },\n })\n await downloadFile(YAMNET_LABELS_URL, labelsPath)\n }\n\n this.session = await ort.InferenceSession.create(modelPath)\n this.inputName = this.session.inputNames[0] ?? 'waveform'\n\n if (fs.existsSync(labelsPath)) {\n this.labels = JSON.parse(fs.readFileSync(labelsPath, 'utf8')) as string[]\n } else {\n this.log.warn('YAMNet labels file not found — classifications will use numeric indices')\n }\n\n this.log.info(`YAMNet ONNX pipeline initialized (${this.labels.length} labels)`)\n }\n\n async classify(chunk: AudioChunk): Promise<AudioClassificationResult> {\n if (!this.session) {\n throw new Error('YAMNet pipeline not initialized')\n }\n\n const start = Date.now()\n const ort = await import('onnxruntime-node')\n\n // Resample to 16kHz mono if needed\n const waveform = chunk.sampleRate === 16000 && chunk.channels === 1\n ? chunk.data\n : resampleMono16k(chunk)\n\n // Create ONNX tensor — YAMNet expects 1D float32 waveform\n const tensor = new ort.Tensor('float32', waveform, [waveform.length])\n const feeds: Record<string, import('onnxruntime-node').Tensor> = { [this.inputName]: tensor }\n\n const results = await this.session.run(feeds)\n\n // output_0: [N_frames, 521] scores\n const scoresData = results[this.session.outputNames[0]!]\n if (!scoresData) {\n throw new Error('YAMNet returned no output')\n }\n\n const scores = scoresData.data as Float32Array\n const numClasses = 521\n const numFrames = scores.length / numClasses\n\n // Average across frames\n const avgScores = new Float32Array(numClasses)\n for (let f = 0; f < numFrames; f++) {\n for (let c = 0; c < numClasses; c++) {\n avgScores[c]! += scores[f * numClasses + c]!\n }\n }\n for (let c = 0; c < numClasses; c++) {\n avgScores[c] = avgScores[c]! / numFrames\n }\n\n // Collect above threshold, sorted\n const minScore = 0.05\n const classifications: { className: string; score: number }[] = []\n for (let c = 0; c < numClasses; c++) {\n const score = avgScores[c]!\n if (score >= minScore) {\n const label = c < this.labels.length ? this.labels[c]! : String(c)\n classifications.push({ className: label, score: Math.round(score * 1000) / 1000 })\n }\n }\n classifications.sort((a, b) => b.score - a.score)\n\n return {\n classifications: classifications.slice(0, 10),\n inferenceMs: Date.now() - start,\n }\n }\n\n async dispose(): Promise<void> {\n if (this.session) {\n await this.session.release()\n this.session = null\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Apple SoundAnalysis (macOS)\n// ---------------------------------------------------------------------------\n\nclass AppleSoundAnalysisPipeline implements IAudioPipeline {\n private readonly log: IScopedLogger\n private process: import('node:child_process').ChildProcess | null = null\n private receiveBuffer: Buffer = Buffer.alloc(0)\n private pendingResolve: ((value: Record<string, unknown>) => void) | null = null\n private pendingReject: ((reason: Error) => void) | null = null\n private binaryPath: string | null = null\n private debugCount = 0\n\n constructor(logger: IScopedLogger) {\n this.log = logger\n }\n\n async initialize(): Promise<void> {\n this.binaryPath = await this.resolveSwiftBinary()\n if (!this.binaryPath) {\n throw new Error('Apple SoundAnalysis: Swift CLI not found and compilation failed. macOS with Xcode CLI tools required.')\n }\n\n const { spawn } = await import('node:child_process')\n this.process = spawn(this.binaryPath, ['--sample-rate=16000', '--top-k=10'], {\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n\n this.process.stderr?.on('data', (chunk: Buffer) => {\n const lines = chunk.toString().split('\\n')\n for (const line of lines) {\n const trimmed = line.trim()\n if (trimmed) this.log.warn(trimmed)\n }\n })\n\n this.process.on('error', (err) => {\n this.log.error('Swift process error', { meta: { error: err.message } })\n this.pendingReject?.(err)\n this.pendingReject = null\n this.pendingResolve = null\n })\n\n this.process.on('exit', (code) => {\n if (code !== 0 && code !== null) {\n this.log.error('Swift process exited', { meta: { code } })\n const err = new Error(`Apple SoundAnalysis: process exited with code ${code}`)\n this.pendingReject?.(err)\n this.pendingReject = null\n this.pendingResolve = null\n }\n })\n\n this.process.stdout!.on('data', (chunk: Buffer) => {\n this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk])\n this.tryReceive()\n })\n\n const ready = await this.receiveMessage()\n if (ready['status'] !== 'ready') {\n throw new Error(`Apple SoundAnalysis: unexpected init response: ${JSON.stringify(ready)}`)\n }\n this.log.info('Apple SoundAnalysis pipeline initialized (macOS built-in, Swift CLI bridge)')\n }\n\n async classify(chunk: AudioChunk): Promise<AudioClassificationResult> {\n if (!this.process?.stdin) {\n throw new Error('Apple SoundAnalysis: process not initialized')\n }\n\n const waveform = chunk.sampleRate === 16000 && chunk.channels === 1\n ? chunk.data\n : resampleMono16k(chunk)\n\n const audioBuffer = Buffer.from(waveform.buffer, waveform.byteOffset, waveform.byteLength)\n const lengthBuf = Buffer.allocUnsafe(4)\n lengthBuf.writeUInt32LE(audioBuffer.length, 0)\n this.process.stdin.write(Buffer.concat([lengthBuf, audioBuffer]))\n\n const result = await this.receiveMessage()\n const classifications = (result['classifications'] as Array<{ className: string; score: number }>) ?? []\n const inferenceMs = (result['inferenceMs'] as number) ?? 0\n\n if (this.debugCount < 3) {\n const keys = Object.keys(result)\n this.log.info('classify debug sample', {\n meta: {\n phase: 'apple-sa',\n index: this.debugCount,\n keys,\n classifications: classifications.length,\n inferenceMs,\n audioBytes: Buffer.from(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength).length,\n sampleRate: chunk.sampleRate,\n channels: chunk.channels,\n },\n })\n if (result['error']) {\n this.log.error('Swift error', { meta: { phase: 'apple-sa', error: result['error'] } })\n }\n this.debugCount++\n }\n\n return { classifications, inferenceMs }\n }\n\n async dispose(): Promise<void> {\n const proc = this.process\n if (!proc) return\n this.process = null\n proc.stdin?.end()\n proc.kill('SIGTERM')\n\n const exited = await new Promise<boolean>((resolve) => {\n const timer = setTimeout(() => resolve(false), 5_000)\n proc.once('exit', () => { clearTimeout(timer); resolve(true) })\n })\n if (!exited) {\n try { proc.kill('SIGKILL') } catch { /* already dead */ }\n this.log.warn('Swift process did not exit gracefully — sent SIGKILL')\n }\n }\n\n private receiveMessage(): Promise<Record<string, unknown>> {\n return new Promise<Record<string, unknown>>((resolve, reject) => {\n this.pendingResolve = resolve\n this.pendingReject = reject\n })\n }\n\n private tryReceive(): void {\n if (this.receiveBuffer.length < 4) return\n const length = this.receiveBuffer.readUInt32LE(0)\n if (this.receiveBuffer.length < 4 + length) return\n\n const jsonBytes = this.receiveBuffer.subarray(4, 4 + length)\n this.receiveBuffer = this.receiveBuffer.subarray(4 + length)\n\n const resolve = this.pendingResolve\n const reject = this.pendingReject\n this.pendingResolve = null\n this.pendingReject = null\n\n if (!resolve) return\n try {\n const parsed = JSON.parse(jsonBytes.toString('utf8')) as Record<string, unknown>\n resolve(parsed)\n } catch (err) {\n reject?.(err instanceof Error ? err : new Error(String(err)))\n }\n }\n\n /** Find pre-compiled binary or compile from Swift source. */\n private async resolveSwiftBinary(): Promise<string | null> {\n // Phase D bundle merge: audio-analyzer ships inside\n // `@camstack/addon-pipeline/dist/audio-analyzer/`, with swift\n // sources at the bundle's `swift/audio-analyzer/` sub-folder.\n // From `dist/audio-analyzer/index.js`, that's `../../swift/audio-analyzer`.\n const candidates = [\n path.join(__dirname, '../../swift/audio-analyzer/apple-sound-classifier'),\n // Fallback for in-tree dev (src/<id>/swift/) and pre-merge layouts.\n path.join(__dirname, '../swift/apple-sound-classifier'),\n path.join(__dirname, '../../swift/apple-sound-classifier'),\n path.join(__dirname, '../../../swift/apple-sound-classifier'),\n ]\n\n for (const p of candidates) {\n if (fs.existsSync(p)) {\n this.log.info('Found pre-compiled Swift CLI', { meta: { path: p } })\n return p\n }\n }\n\n const sourceCandidates = [\n path.join(__dirname, '../../swift/audio-analyzer/apple-sound-classifier.swift'),\n path.join(__dirname, '../swift/apple-sound-classifier.swift'),\n path.join(__dirname, '../../swift/apple-sound-classifier.swift'),\n path.join(__dirname, '../../../swift/apple-sound-classifier.swift'),\n ]\n const sourcePath = sourceCandidates.find(p => fs.existsSync(p))\n if (!sourcePath) {\n this.log.error('Swift source not found', { meta: { searched: sourceCandidates } })\n return null\n }\n\n const outputPath = sourcePath.replace('.swift', '')\n this.log.info('Compiling Swift CLI...', { meta: { source: sourcePath, output: outputPath } })\n\n const { execFileSync } = await import('node:child_process')\n try {\n execFileSync('swiftc', ['-O', '-o', outputPath, sourcePath], {\n timeout: 60_000,\n stdio: 'pipe',\n })\n this.log.info('Swift CLI compiled successfully')\n return outputPath\n } catch (err) {\n this.log.error('Swift compilation failed — install Xcode Command Line Tools', {\n meta: { error: errMsg(err) },\n })\n return null\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Audio helpers\n// ---------------------------------------------------------------------------\n\n/** Simple resample to 16kHz mono by linear interpolation. */\nfunction resampleMono16k(chunk: AudioChunk): Float32Array {\n const { data, sampleRate, channels } = chunk\n const numSamples = data.length / channels\n\n // Mix to mono\n const mono = new Float32Array(numSamples)\n for (let i = 0; i < numSamples; i++) {\n let sum = 0\n for (let c = 0; c < channels; c++) {\n sum += data[i * channels + c]!\n }\n mono[i] = sum / channels\n }\n\n // Resample to 16kHz\n const ratio = 16000 / sampleRate\n const outLen = Math.floor(numSamples * ratio)\n const out = new Float32Array(outLen)\n for (let i = 0; i < outLen; i++) {\n const srcIdx = i / ratio\n const lo = Math.floor(srcIdx)\n const hi = Math.min(lo + 1, numSamples - 1)\n const frac = srcIdx - lo\n out[i] = mono[lo]! * (1 - frac) + mono[hi]! * frac\n }\n return out\n}\n","import type {\n AudioAnalyzerGlobalConfig,\n AudioBackendChoice,\n AudioChunkInput,\n AudioAnalysisResult,\n AudioAnalysisSettings,\n IAudioAnalyzer,\n ConfigUISchema,\n ConfigUISchemaWithValues,\n ProviderRegistration,\n} from '@camstack/types'\nimport {\n AUDIO_BACKEND_CHOICES,\n BaseAddon,\n DEFAULT_AUDIO_ANALYZER_CONFIG,\n audioAnalysisCapability,\n audioAnalyzerCapability,\n errMsg,\n hydrateSchema,\n mapAudioLabelToMacro,\n} from '@camstack/types'\nimport type { AudioClassificationResult } from '@camstack/types'\nimport type { IScopedLogger } from '@camstack/types'\nimport type { AudioBackend, IAudioPipeline } from '../../audio-pipeline.js'\nimport { createAudioPipeline } from '../../audio-pipeline.js'\n\n/**\n * Choices presented in the Audio Model dropdown. YAMNet runs via ONNX\n * when backend=yamnet-onnx; Apple SoundAnalysis is a built-in macOS\n * model and has no swappable modelId — the backend IS the model.\n */\nconst AUDIO_MODEL_OPTIONS = [\n { value: '', label: 'Auto (matches backend)' },\n { value: 'yamnet-onnx', label: 'YAMNet (ONNX)' },\n { value: 'apple-soundanalysis', label: 'Apple SoundAnalysis (built-in)' },\n] as const\n\n/**\n * AudioAnalyzerProvider — implements IAudioAnalyzer.\n *\n * Computes dB/RMS on every chunk and classifies via the in-process\n * IAudioPipeline (YAMNet ONNX / Apple SoundAnalysis). No tRPC roundtrip\n * to a separate audio-classifier addon.\n */\n\n// Suppress repeated classify-error logs. Timeouts are expected during\n// pipeline startup or under CPU pressure — log once, then silence for 30s.\nconst CLASSIFY_ERROR_SUPPRESS_MS = 30_000\n\n// Minimum ms between inference calls per camera. Prevents a single camera\n// from immediately re-queuing on the same pipeline after finishing.\nconst CLASSIFY_MIN_INTERVAL_MS = 500\n\n// Sentinel key used when no deviceId is supplied (legacy callers).\nconst GLOBAL_DEVICE_KEY = -1\n\ninterface CameraClassifyState {\n inProgress: boolean\n lastEndMs: number\n}\n\n/**\n * Reconstruct a Float32 view over f32le PCM bytes carried in a Uint8Array.\n *\n * `@msgpack/msgpack` decodes a binary blob into a Uint8Array that is a subview\n * of its internal decode buffer, whose `byteOffset` is NOT guaranteed to be\n * 4-byte aligned (observed e.g. 9). `new Float32Array(buf, offset, …)` then\n * throws \"start offset of Float32Array should be a multiple of 4\". When the\n * offset is misaligned we copy into a fresh 0-offset buffer; the aligned\n * fast-path reuses the existing view with no copy.\n */\nfunction float32FromBytes(raw: Uint8Array): Float32Array {\n const bytes = raw.byteOffset % 4 === 0 ? raw : new Uint8Array(raw)\n return new Float32Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 4))\n}\n\nexport class AudioAnalyzerProvider implements IAudioAnalyzer {\n private readonly log: IScopedLogger\n private classifyCallCount = 0\n private lastClassifyErrorMs = 0\n private suppressedClassifyErrors = 0\n private classifyCount = 0\n private backendName = 'unknown'\n /** Per-camera in-flight state. Key = deviceId (or GLOBAL_DEVICE_KEY for legacy callers). */\n private readonly cameraState = new Map<number, CameraClassifyState>()\n /** Global pipeline lock — Apple SA and ONNX are single-channel: only one classify() can\n * run at a time. Without this, concurrent calls from different cameras overwrite the\n * single pendingResolve slot in AppleSoundAnalysisPipeline, causing 30s timeouts. */\n private pipelineBusy = false\n\n constructor(\n logger: IScopedLogger,\n private readonly pipeline: IAudioPipeline,\n /** The backend that ACTUALLY backs `pipeline` — passed in by the\n * addon's `onInitialize` after `resolveAudioBackend()` has picked\n * the effective choice (operator override or platform default).\n * Used only for `engine:` log tagging on classify samples. */\n backendName: string,\n private readonly deviceSettingsResolver: (deviceId: number) => Promise<AudioAnalysisSettings | null>,\n private readonly deviceContributionResolver: (deviceId: number) => Promise<ConfigUISchemaWithValues | null>,\n private readonly deviceSettingsPatcher: (deviceId: number, patch: Record<string, unknown>) => Promise<void>,\n private readonly reprobeImpl: () => Promise<{ backend: string }>,\n ) {\n this.log = logger\n this.backendName = backendName\n }\n\n // ── Device-details aggregator contribution ──────────────────────────────\n\n async getDeviceSettingsContribution(input: { deviceId: number }): Promise<ConfigUISchemaWithValues | null> {\n return this.deviceContributionResolver(input.deviceId)\n }\n\n async getDeviceLiveContribution(_input: { deviceId: number }): Promise<ConfigUISchemaWithValues | null> {\n return null\n }\n\n async applyDeviceSettingsPatch(input: { deviceId: number; patch: Record<string, unknown> }): Promise<{ success: true }> {\n await this.deviceSettingsPatcher(input.deviceId, input.patch)\n return { success: true as const }\n }\n\n /**\n * Return the effective per-device audio-analyzer settings, resolved via\n * the kernel's 3-level settings resolver (schema default → global →\n * device override). Orchestrator consumers call this method so they\n * never need to know the audio-analyzer schema field names.\n */\n async resolveDeviceSettings({ deviceId }: { deviceId: number }): Promise<AudioAnalysisSettings | null> {\n return this.deviceSettingsResolver(deviceId)\n }\n\n async analyseChunk({\n chunk,\n settings,\n }: {\n chunk: AudioChunkInput\n settings: AudioAnalysisSettings\n }): Promise<AudioAnalysisResult | null> {\n // AudioChunkInput.data carries raw f32le bytes (4 bytes per sample).\n // Reconstruct the Float32Array view here — do NOT iterate the Uint8Array\n // directly, because each byte (0–255) is NOT a sample value.\n // MsgPack decodes the blob into a Uint8Array that is a subview of its\n // internal buffer, whose byteOffset is NOT guaranteed 4-byte aligned\n // (e.g. 9) — `new Float32Array(buf, offset, …)` then throws \"start offset\n // … multiple of 4\". Copy into a fresh 0-offset buffer when misaligned\n // (fast-path reuses the view when already aligned).\n const samples = float32FromBytes(chunk.data)\n\n // ── Compute dB/RMS levels (always) ──\n let sumSquares = 0\n for (let i = 0; i < samples.length; i++) {\n sumSquares += samples[i]! * samples[i]!\n }\n const rms = Math.sqrt(sumSquares / samples.length)\n const dbfs = rms > 0 ? 20 * Math.log10(rms) : -96\n\n const level = {\n rms: Math.round(rms * 10000) / 10000,\n dbfs: Math.round(dbfs * 10) / 10,\n }\n\n // ── Classification (in-process pipeline) ──\n let classification: AudioAnalysisResult['classification'] | undefined\n\n try {\n const result = await this.classify(chunk)\n if (this.classifyCallCount < 3) {\n const topRaw = result.labels.slice(0, 5).map(l => `${l.className}(${(l.score * 100).toFixed(0)}%)`).join(', ')\n this.log.info('classify debug sample', {\n tags: chunk.deviceId !== undefined ? { deviceId: chunk.deviceId } : undefined,\n meta: {\n index: this.classifyCallCount,\n labelCount: result.labels.length,\n top: topRaw,\n inferenceMs: result.inferenceMs,\n minConf: settings.minConfidence,\n allowedClasses: settings.allowedClasses,\n },\n })\n }\n this.classifyCallCount++\n\n if (result.inferenceMs > 0) {\n // Apply filtering (analyzer responsibility — settings come from per-device config)\n const minConf = settings.minConfidence\n const allowedSet = settings.allowedClasses.length > 0\n ? new Set(settings.allowedClasses.map(c => c.toLowerCase()))\n : null\n\n let filtered = result.labels.filter(c => c.score >= minConf)\n if (allowedSet) {\n filtered = filtered.filter(c => allowedSet.has(c.className.toLowerCase()))\n }\n\n if (filtered.length > 0) {\n classification = {\n labels: filtered,\n inferenceMs: result.inferenceMs,\n }\n }\n }\n } catch (err: unknown) {\n const now = Date.now()\n const sinceLastMs = now - this.lastClassifyErrorMs\n if (sinceLastMs >= CLASSIFY_ERROR_SUPPRESS_MS) {\n const suppressed = this.suppressedClassifyErrors\n this.suppressedClassifyErrors = 0\n this.lastClassifyErrorMs = now\n const msg = errMsg(err)\n const stack = err instanceof Error ? err.stack : undefined\n this.log.warn('Audio classification failed', {\n tags: chunk.deviceId !== undefined ? { deviceId: chunk.deviceId } : undefined,\n meta: { error: msg, stack, suppressedSince: suppressed > 0 ? suppressed : undefined },\n })\n } else {\n this.suppressedClassifyErrors++\n }\n }\n\n return { level, classification, timestamp: chunk.timestamp }\n }\n\n async classify(chunk: AudioChunkInput): Promise<AudioClassificationResult> {\n // Per-camera concurrency guard: at most 1 chunk in flight per device.\n // Drops the incoming call immediately if that camera already has one\n // in progress or hasn't waited the minimum interval since its last call.\n // Cameras are isolated — camera A classifying does NOT block camera B.\n const camKey = chunk.deviceId ?? GLOBAL_DEVICE_KEY\n const now = Date.now()\n const state = this.cameraState.get(camKey)\n if (state?.inProgress || (state !== undefined && (now - state.lastEndMs) < CLASSIFY_MIN_INTERVAL_MS)) {\n return { labels: [], rawLabels: [], inferenceMs: 0 }\n }\n\n if (this.pipelineBusy) {\n return { labels: [], rawLabels: [], inferenceMs: 0 }\n }\n\n this.cameraState.set(camKey, { inProgress: true, lastEndMs: state?.lastEndMs ?? 0 })\n this.pipelineBusy = true\n // AudioChunkInput.data carries raw f32le bytes — reconstruct Float32Array\n // before passing to the pipeline (YAMNet/Apple SoundAnalysis need floats).\n // See float32FromBytes: msgpack-decoded Uint8Arrays may be 4-byte-misaligned.\n const f32Data = float32FromBytes(chunk.data)\n const result = await this.pipeline.classify({\n data: f32Data,\n sampleRate: chunk.sampleRate,\n channels: chunk.channels,\n }).finally(() => {\n this.pipelineBusy = false\n this.cameraState.set(camKey, { inProgress: false, lastEndMs: Date.now() })\n })\n\n // Log raw labels at regular intervals during debug\n if (this.classifyCount < 3 || this.classifyCount % 100 === 0) {\n const rawTop = result.classifications.slice(0, 5).map(c => `\"${c.className}\"(${(c.score * 100).toFixed(0)}%)`).join(', ')\n this.log.info('classify debug sample', {\n tags: chunk.deviceId !== undefined ? { deviceId: chunk.deviceId } : undefined,\n meta: {\n index: this.classifyCount,\n engine: this.backendName,\n rawLabelCount: result.classifications.length,\n top: rawTop,\n inferenceMs: result.inferenceMs,\n },\n })\n }\n this.classifyCount++\n\n // Map raw labels → macro classes, aggregate scores per macro class.\n const macroAccum = new Map<string, { score: number; rawTop: string }>()\n for (const c of result.classifications) {\n const macro = mapAudioLabelToMacro(c.className)\n if (!macro) continue\n const prev = macroAccum.get(macro)\n if (!prev || c.score > prev.score) {\n macroAccum.set(macro, { score: c.score, rawTop: c.className })\n }\n }\n\n // Sort by score descending\n const labels = [...macroAccum.entries()]\n .sort((a, b) => b[1].score - a[1].score)\n .map(([className, { score, rawTop }]) => ({ className, originalClass: rawTop, score }))\n\n // Raw backend-native labels (no macro aggregation), sorted by score descending.\n const rawLabels = [...result.classifications]\n .sort((a, b) => b.score - a.score)\n .map((c) => ({ className: c.className, originalClass: c.className, score: c.score }))\n\n return { labels, rawLabels, inferenceMs: result.inferenceMs }\n }\n\n isReady(): boolean {\n return this.pipeline !== null\n }\n\n async dispose(): Promise<void> {\n await this.pipeline.dispose()\n }\n\n // Expose via the cap so the reprobe button in the UI reaches the\n // right worker. Delegates to the addon-owned reprobe (it touches\n // `ctx.settings.writeAddonStore` which only the addon has access to).\n async reprobeAudioEngine(): Promise<{ backend: string }> {\n return this.reprobeImpl()\n }\n}\n\n/**\n * Audio Analyzer addon — provides the `audio-analyzer` capability.\n *\n * Owns the IAudioPipeline directly — no tRPC roundtrip to a separate\n * audio-classifier addon.\n */\nexport class AudioAnalyzerAddon extends BaseAddon<AudioAnalyzerGlobalConfig> {\n readonly id = 'audio-analyzer'\n\n private provider: AudioAnalyzerProvider | null = null\n private pipeline: IAudioPipeline | null = null\n\n constructor() { super(DEFAULT_AUDIO_ANALYZER_CONFIG) }\n\n protected globalSettingsSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'audio-engine',\n title: 'Audio',\n // Co-located with detection-pipeline's `engine` section under\n // a single \"Engine\" tab. Both sections handle inference-engine\n // selection but for different modalities — distinct purposes\n // are conveyed through section titles (\"Detection engine\" vs\n // \"Audio inference engine\") and descriptions, not separate\n // tabs.\n tab: 'engine',\n // Renders after detection-pipeline's `engine` section\n // (`order: 0`) on the \"Inference Engine\" tab.\n order: 10,\n description:\n 'Audio classification backend (Apple SoundAnalysis or YAMNet ONNX). Independent from the vision-detection engine above. \"Auto\" picks Apple SoundAnalysis on macOS, YAMNet on Linux. Click the refresh icon next to \"Probed best\" to re-run the probe.',\n // Field order — `probedBest*` lives at the top so the operator\n // sees the auto-detected hint first and can compare it against\n // their override below at a glance. Same convention as the\n // detection-pipeline section.\n fields: [\n {\n type: 'text' as const,\n key: 'probedBestAudioBackend',\n label: 'Probed best',\n description: 'Auto-detected best audio backend on this host. Click the refresh icon to re-run the probe.',\n readonlyField: true,\n default: '',\n actions: [\n { action: 'reprobe-audio-engine', icon: 'refresh-cw', tooltip: 'Re-probe audio engine' },\n ],\n },\n {\n type: 'select' as const,\n key: 'audioBackend',\n label: 'Audio backend',\n options: AUDIO_BACKEND_CHOICES.map(o => ({ value: o.value, label: o.label })),\n default: DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend,\n immediate: true,\n requiresRestart: true,\n },\n {\n type: 'select' as const,\n key: 'selectedAudioModel',\n label: 'Classification model',\n description:\n 'Empty = auto (matches backend). Device-level settings can only inherit / enable / disable this step; model + class filters live here at the node level.',\n options: AUDIO_MODEL_OPTIONS.map(o => ({ value: o.value, label: o.label })),\n default: DEFAULT_AUDIO_ANALYZER_CONFIG.selectedAudioModel,\n immediate: true,\n requiresRestart: true,\n },\n ],\n },\n ],\n }\n }\n\n /**\n * Cascade override — narrow the `selectedAudioModel` options to the\n * subset compatible with the currently-selected `audioBackend`.\n *\n * Same pattern as detection-pipeline's `engineRuntime → engineBackend\n * → engineDevice` cascade: the base schema ships every option\n * (Auto + YAMNet + Apple SA); this override drops the rows that\n * belong to a backend the operator didn't pick. With `immediate:\n * true` on the `audioBackend` select, the UI refetches schema after\n * every flip and the model dropdown updates instantly.\n *\n * `overlay` carries the operator's tentative choices for benchmark/\n * preview mode (operator typed but didn't save yet) — same\n * semantics detection-pipeline relies on.\n */\n override async getGlobalSettings(overlay?: Record<string, unknown>): Promise<ConfigUISchemaWithValues> {\n const ctx = this.ctxIfReady\n const stored = ctx?.settings ? ((await ctx.settings.readAddonStore()) ?? {}) : {}\n const merged = overlay ? { ...stored, ...overlay } : stored\n\n // Resolve the effective backend the same way the runtime would.\n const operatorChoice = typeof merged.audioBackend === 'string' ? merged.audioBackend : DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend\n const effectiveBackend: AudioBackend =\n operatorChoice === 'apple-soundanalysis' ? 'apple-soundanalysis'\n : operatorChoice === 'yamnet-onnx' ? 'yamnet-onnx'\n : process.platform === 'darwin' ? 'apple-soundanalysis' : 'yamnet-onnx'\n\n // The \"Auto\" entry always passes through — it just resolves to the\n // effective backend at runtime. The other rows are filtered to\n // match the current backend so the UI doesn't suggest impossible\n // combos (e.g. picking \"Apple SoundAnalysis (built-in)\" while\n // backend=yamnet-onnx).\n const filteredModels = AUDIO_MODEL_OPTIONS.filter(\n (o) => o.value === '' || o.value === effectiveBackend,\n )\n\n // Re-project the persisted `selectedAudioModel` onto the narrowed\n // option list — if the previous backend's model is no longer valid\n // for the new backend, snap to \"Auto\" so the dropdown doesn't\n // render an empty value.\n const storedModel = typeof merged.selectedAudioModel === 'string' ? merged.selectedAudioModel : ''\n const validModel = filteredModels.find((o) => o.value === storedModel)?.value ?? ''\n const raw = { ...merged, selectedAudioModel: validModel }\n\n const schema = this.globalSettingsSchema()\n const patched: ConfigUISchema = {\n ...schema,\n sections: schema.sections.map((section) => ({\n ...section,\n fields: section.fields.map((field) => {\n if (field.type === 'select' && field.key === 'selectedAudioModel') {\n return { ...field, options: filteredModels.map((o) => ({ value: o.value, label: o.label })) }\n }\n return field\n }),\n })),\n }\n return hydrateSchema(patched, raw)\n }\n\n /**\n * Re-run the platform probe and persist the detected backend into\n * `probedBestAudioBackend`. Operator `audioBackend` setting is not\n * touched — only the hint.\n */\n async reprobeAudioEngine(): Promise<{ backend: string }> {\n const backend: AudioBackend = process.platform === 'darwin' ? 'apple-soundanalysis' : 'yamnet-onnx'\n await this.ctx.settings?.writeAddonStore({ probedBestAudioBackend: backend })\n this.ctx.logger.info('reprobeAudioEngine: wrote probedBestAudioBackend', { meta: { backend } })\n return { backend }\n }\n\n /** Resolve the effective backend from the operator choice, falling back to the platform heuristic when 'auto'. */\n private resolveAudioBackend(): AudioBackend {\n const choice: AudioBackendChoice = this.config.audioBackend\n if (choice === 'apple-soundanalysis') return 'apple-soundanalysis'\n if (choice === 'yamnet-onnx') return 'yamnet-onnx'\n return process.platform === 'darwin' ? 'apple-soundanalysis' : 'yamnet-onnx'\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const logger = this.ctx.logger as IScopedLogger\n\n // Enable/disable is driven by the binding system (per-device\n // `audio-analysis` wrapper) + the pipeline tree\n // (`addonDefaults['audio-classifier'].enabled`). The addon itself\n // always boots its pipeline; consumers not bound / not in the tree\n // simply don't invoke `classify`.\n\n // Initialize the audio pipeline (YAMNet ONNX / Apple SoundAnalysis)\n const modelsDir = await this.ctx.api.storage.resolve.query({ location: 'models', relativePath: '' })\n .catch(() => 'camstack-data/models')\n const backend = this.resolveAudioBackend()\n logger.info('audio-analyzer: resolving pipeline', {\n meta: { operatorChoice: this.config.audioBackend, effectiveBackend: backend, selectedModel: this.config.selectedAudioModel || null },\n })\n const p = await createAudioPipeline(modelsDir, logger, { backend })\n await p.initialize()\n this.pipeline = p\n\n // Auto-seed probedBestAudioBackend so the UI shows the effective\n // platform default on first boot (matches what we just booted).\n if (!this.config.probedBestAudioBackend) {\n this.reprobeAudioEngine().catch((err: unknown) => {\n logger.warn('audio: auto-reprobe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n\n // Device settings resolver — pulls the audio-classifier step's\n // `settings` map from the orchestrator's resolved pipeline. The\n // settings live on the audio-classifier step (in\n // `agent.addonDefaults['audio-classifier'].settings` overlaid with\n // any per-device patch). No per-device store on this addon.\n const self = this\n const deviceSettingsResolver = async (deviceId: number): Promise<AudioAnalysisSettings | null> => {\n try {\n const resolved = await self.ctx.api.pipelineOrchestrator.resolvePipeline.query({ deviceId })\n const stepSettings = resolved.audio?.settings ?? {}\n const minConfidence = typeof stepSettings['minConfidence'] === 'number'\n ? (stepSettings['minConfidence'] as number)\n : 0.3\n const allowedClasses = Array.isArray(stepSettings['enabledAudioClasses'])\n ? (stepSettings['enabledAudioClasses'] as string[])\n : []\n return { minConfidence, allowedClasses }\n } catch (err) {\n logger.warn('audio: resolveDeviceSettings via orchestrator failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return null\n }\n }\n\n const deviceContributionResolver = async (_deviceId: number): Promise<ConfigUISchemaWithValues | null> => {\n // No device-level surface — settings live on the audio-classifier\n // pipeline step (Orchestrator tab).\n return null\n }\n const deviceSettingsPatcher = async (_deviceId: number, _patch: Record<string, unknown>): Promise<void> => {\n // No-op: per-device audio settings moved to the audio-classifier step.\n }\n\n this.provider = new AudioAnalyzerProvider(\n logger,\n this.pipeline,\n backend,\n deviceSettingsResolver,\n deviceContributionResolver,\n deviceSettingsPatcher,\n () => this.reprobeAudioEngine(),\n )\n // Split registration mirrors stream-broker/camera-streams + snapshot/snapshot-provider:\n // - audio-analyzer (system): compute path (analyseChunk / classify / isReady / dispose)\n // - audio-analysis (device, wrapper defaultActive): per-device surface — resolveDeviceSettings,\n // the three DeviceSettingsContribution methods and the onAudioLevel event.\n // Every camera gets a binding row; operators disable per-device via setWrapperActive.\n return [\n { capability: audioAnalyzerCapability, provider: this.provider },\n {\n capability: audioAnalysisCapability,\n provider: this.provider,\n },\n ]\n }\n\n protected async onShutdown(): Promise<void> {\n if (this.provider) {\n await this.provider.dispose()\n this.provider = null\n }\n this.pipeline = null\n }\n\n // ── Standard ICamstackAddon — three-level settings API ─────\n //\n // Per-device audio settings (audio class filter + minConfidence) moved\n // to the audio-classifier pipeline step's `getConfigSchema()` and the\n // orchestrator owns the audio node assignment (`audioNodeId`). No\n // device-level settings remain on this addon.\n\n buildDeviceSchema(): ConfigUISchema {\n return { sections: [] }\n }\n\n async getDeviceSettings(deviceId: number): Promise<ConfigUISchemaWithValues> {\n const raw = (await this.ctx?.settings?.readDeviceStore(deviceId)) ?? {}\n return hydrateSchema(this.buildDeviceSchema(), raw)\n }\n\n async updateDeviceSettings(deviceId: number, patch: Record<string, unknown>): Promise<void> {\n await this.ctx?.settings?.writeDeviceStore(deviceId, patch)\n }\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sources":["../../src/audio-analyzer/audio-pipeline.ts","../../src/audio-analyzer/addons/analyzer/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access -- existing audio pipeline carries unsafe ONNX runtime calls; pre-existing tech debt, not introduced by Phase E. */\n/**\n * Audio classification pipeline.\n *\n * - macOS: Apple SoundAnalysis framework (303 built-in sound categories)\n * - Cross-platform: YAMNet ONNX via onnxruntime-node\n *\n * This is independent from the video pipeline — different input (audio samples),\n * different engine (no shared pool), different output cadence.\n */\nimport type { IScopedLogger } from '@camstack/types'\nimport * as path from 'node:path'\nimport * as fs from 'node:fs'\nimport { errMsg, HF_BASE_URL } from '@camstack/types'\n// eslint-disable-next-line no-restricted-imports -- downloadFile is a build-time dep used to materialise the YAMNet model on first boot. Mirrors the waiver detection-pipeline + addon-embedding-encoder use for the same reason. Single source of truth lives in `@camstack/core/download/model-downloader`; consolidating it via `ctx.deps.downloadFile` would require extending `IAddonDepsManager` across every kernel context — out of scope for this addon's first auto-download wiring.\nimport { downloadFile } from '@camstack/core'\n\n// `__dirname` is shimmed by tsup (`shims: true`) for both CJS and ESM\n// output, so it works whether this bundle is loaded by `require` or by\n// `await import(...)` (the agent path).\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface AudioClassificationResult {\n readonly classifications: readonly {\n readonly className: string\n readonly score: number\n }[]\n readonly inferenceMs: number\n}\n\nexport interface AudioChunk {\n readonly data: Float32Array\n readonly sampleRate: number\n readonly channels: number\n}\n\nexport interface IAudioPipeline {\n initialize(): Promise<void>\n classify(chunk: AudioChunk): Promise<AudioClassificationResult>\n dispose(): Promise<void>\n}\n\nexport type AudioBackend = 'yamnet-onnx' | 'apple-soundanalysis'\n\nexport interface AudioPipelineOptions {\n readonly backend?: AudioBackend\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\n/**\n * Create the appropriate audio pipeline.\n *\n * - 'yamnet-onnx': Cross-platform YAMNet ONNX (requires model download)\n * - 'apple-soundanalysis': macOS 12+ Apple SoundAnalysis (zero model download, Neural Engine)\n * - undefined: auto-detect (Apple SA on macOS, YAMNet on Linux)\n */\nexport async function createAudioPipeline(\n modelsDir: string,\n logger: IScopedLogger,\n options?: AudioPipelineOptions,\n): Promise<IAudioPipeline> {\n const backend = options?.backend ?? (process.platform === 'darwin' ? 'apple-soundanalysis' : 'yamnet-onnx')\n\n if (backend === 'apple-soundanalysis') {\n return new AppleSoundAnalysisPipeline(logger)\n }\n return new YamnetOnnxPipeline(modelsDir, logger)\n}\n\n// ---------------------------------------------------------------------------\n// YAMNet ONNX (cross-platform)\n// ---------------------------------------------------------------------------\n\n/**\n * Canonical model URLs on the camstack HuggingFace mirror. Mirrors the\n * convention every detection model follows (single point of truth =\n * `HF_BASE_URL` from `@camstack/types`); the auto-download path uses\n * `downloadFile` from `@camstack/core`, the SAME helper detection-\n * pipeline uses to materialise its YOLO/face/plate models. Missing\n * model on disk → fetch from HF; cached file → no-op.\n *\n * Repo layout follows the detection-pipeline pattern:\n * {domain}/{family}/{format}/{filename}\n * For YAMNet that's `audioClassification/yamnet/onnx/camstack-yamnet.onnx`,\n * with the labels JSON sitting one level up (`audioClassification/yamnet/`)\n * because they're format-agnostic (same 521 AudioSet class names whether\n * the runtime is ONNX, OpenVINO, or TF).\n */\nconst YAMNET_MODEL_URL = `${HF_BASE_URL}/audioClassification/yamnet/onnx/camstack-yamnet.onnx`\nconst YAMNET_LABELS_URL = `${HF_BASE_URL}/audioClassification/yamnet/camstack-yamnet-labels.json`\n\nclass YamnetOnnxPipeline implements IAudioPipeline {\n private session: import('onnxruntime-node').InferenceSession | null = null\n private inputName = ''\n private labels: readonly string[] = []\n private readonly log: IScopedLogger\n\n constructor(\n private readonly modelsDir: string,\n logger: IScopedLogger,\n ) {\n this.log = logger\n }\n\n async initialize(): Promise<void> {\n const ort = await import('onnxruntime-node')\n const modelPath = path.join(this.modelsDir, 'camstack-yamnet.onnx')\n const labelsPath = path.join(this.modelsDir, 'camstack-yamnet-labels.json')\n\n // Auto-download model + labels from the camstack HF mirror — same\n // pattern as detection-pipeline's `ensureModel`. `downloadFile` is\n // a no-op when the file is already on disk; on first boot it\n // streams atomically (`.downloading` tmp + rename on success).\n if (!fs.existsSync(modelPath)) {\n this.log.info('YAMNet ONNX model not found locally — downloading from HuggingFace', {\n meta: { url: YAMNET_MODEL_URL, dest: modelPath },\n })\n await downloadFile(YAMNET_MODEL_URL, modelPath)\n this.log.info('YAMNet ONNX model downloaded', {\n meta: { sizeBytes: fs.statSync(modelPath).size },\n })\n }\n if (!fs.existsSync(labelsPath)) {\n this.log.info('YAMNet labels not found locally — downloading from HuggingFace', {\n meta: { url: YAMNET_LABELS_URL, dest: labelsPath },\n })\n await downloadFile(YAMNET_LABELS_URL, labelsPath)\n }\n\n this.session = await ort.InferenceSession.create(modelPath)\n this.inputName = this.session.inputNames[0] ?? 'waveform'\n\n if (fs.existsSync(labelsPath)) {\n this.labels = JSON.parse(fs.readFileSync(labelsPath, 'utf8')) as string[]\n } else {\n this.log.warn('YAMNet labels file not found — classifications will use numeric indices')\n }\n\n this.log.info(`YAMNet ONNX pipeline initialized (${this.labels.length} labels)`)\n }\n\n async classify(chunk: AudioChunk): Promise<AudioClassificationResult> {\n if (!this.session) {\n throw new Error('YAMNet pipeline not initialized')\n }\n\n const start = Date.now()\n const ort = await import('onnxruntime-node')\n\n // Resample to 16kHz mono if needed\n const waveform = chunk.sampleRate === 16000 && chunk.channels === 1\n ? chunk.data\n : resampleMono16k(chunk)\n\n // Create ONNX tensor — YAMNet expects 1D float32 waveform\n const tensor = new ort.Tensor('float32', waveform, [waveform.length])\n const feeds: Record<string, import('onnxruntime-node').Tensor> = { [this.inputName]: tensor }\n\n const results = await this.session.run(feeds)\n\n // output_0: [N_frames, 521] scores\n const scoresData = results[this.session.outputNames[0]!]\n if (!scoresData) {\n throw new Error('YAMNet returned no output')\n }\n\n const scores = scoresData.data as Float32Array\n const numClasses = 521\n const numFrames = scores.length / numClasses\n\n // Average across frames\n const avgScores = new Float32Array(numClasses)\n for (let f = 0; f < numFrames; f++) {\n for (let c = 0; c < numClasses; c++) {\n avgScores[c]! += scores[f * numClasses + c]!\n }\n }\n for (let c = 0; c < numClasses; c++) {\n avgScores[c] = avgScores[c]! / numFrames\n }\n\n // Collect above threshold, sorted\n const minScore = 0.05\n const classifications: { className: string; score: number }[] = []\n for (let c = 0; c < numClasses; c++) {\n const score = avgScores[c]!\n if (score >= minScore) {\n const label = c < this.labels.length ? this.labels[c]! : String(c)\n classifications.push({ className: label, score: Math.round(score * 1000) / 1000 })\n }\n }\n classifications.sort((a, b) => b.score - a.score)\n\n return {\n classifications: classifications.slice(0, 10),\n inferenceMs: Date.now() - start,\n }\n }\n\n async dispose(): Promise<void> {\n if (this.session) {\n await this.session.release()\n this.session = null\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Apple SoundAnalysis (macOS)\n// ---------------------------------------------------------------------------\n\nclass AppleSoundAnalysisPipeline implements IAudioPipeline {\n private readonly log: IScopedLogger\n private process: import('node:child_process').ChildProcess | null = null\n private receiveBuffer: Buffer = Buffer.alloc(0)\n private pendingResolve: ((value: Record<string, unknown>) => void) | null = null\n private pendingReject: ((reason: Error) => void) | null = null\n private binaryPath: string | null = null\n private debugCount = 0\n\n constructor(logger: IScopedLogger) {\n this.log = logger\n }\n\n async initialize(): Promise<void> {\n this.binaryPath = await this.resolveSwiftBinary()\n if (!this.binaryPath) {\n throw new Error('Apple SoundAnalysis: Swift CLI not found and compilation failed. macOS with Xcode CLI tools required.')\n }\n\n const { spawn } = await import('node:child_process')\n this.process = spawn(this.binaryPath, ['--sample-rate=16000', '--top-k=10'], {\n stdio: ['pipe', 'pipe', 'pipe'],\n })\n\n this.process.stderr?.on('data', (chunk: Buffer) => {\n const lines = chunk.toString().split('\\n')\n for (const line of lines) {\n const trimmed = line.trim()\n if (trimmed) this.log.warn(trimmed)\n }\n })\n\n this.process.on('error', (err) => {\n this.log.error('Swift process error', { meta: { error: err.message } })\n this.pendingReject?.(err)\n this.pendingReject = null\n this.pendingResolve = null\n })\n\n this.process.on('exit', (code) => {\n if (code !== 0 && code !== null) {\n this.log.error('Swift process exited', { meta: { code } })\n const err = new Error(`Apple SoundAnalysis: process exited with code ${code}`)\n this.pendingReject?.(err)\n this.pendingReject = null\n this.pendingResolve = null\n }\n })\n\n this.process.stdout!.on('data', (chunk: Buffer) => {\n this.receiveBuffer = Buffer.concat([this.receiveBuffer, chunk])\n this.tryReceive()\n })\n\n const ready = await this.receiveMessage()\n if (ready['status'] !== 'ready') {\n throw new Error(`Apple SoundAnalysis: unexpected init response: ${JSON.stringify(ready)}`)\n }\n this.log.info('Apple SoundAnalysis pipeline initialized (macOS built-in, Swift CLI bridge)')\n }\n\n async classify(chunk: AudioChunk): Promise<AudioClassificationResult> {\n if (!this.process?.stdin) {\n throw new Error('Apple SoundAnalysis: process not initialized')\n }\n\n const waveform = chunk.sampleRate === 16000 && chunk.channels === 1\n ? chunk.data\n : resampleMono16k(chunk)\n\n const audioBuffer = Buffer.from(waveform.buffer, waveform.byteOffset, waveform.byteLength)\n const lengthBuf = Buffer.allocUnsafe(4)\n lengthBuf.writeUInt32LE(audioBuffer.length, 0)\n this.process.stdin.write(Buffer.concat([lengthBuf, audioBuffer]))\n\n const result = await this.receiveMessage()\n const classifications = (result['classifications'] as Array<{ className: string; score: number }>) ?? []\n const inferenceMs = (result['inferenceMs'] as number) ?? 0\n\n if (this.debugCount < 3) {\n const keys = Object.keys(result)\n this.log.info('classify debug sample', {\n meta: {\n phase: 'apple-sa',\n index: this.debugCount,\n keys,\n classifications: classifications.length,\n inferenceMs,\n audioBytes: Buffer.from(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength).length,\n sampleRate: chunk.sampleRate,\n channels: chunk.channels,\n },\n })\n if (result['error']) {\n this.log.error('Swift error', { meta: { phase: 'apple-sa', error: result['error'] } })\n }\n this.debugCount++\n }\n\n return { classifications, inferenceMs }\n }\n\n async dispose(): Promise<void> {\n const proc = this.process\n if (!proc) return\n this.process = null\n proc.stdin?.end()\n proc.kill('SIGTERM')\n\n const exited = await new Promise<boolean>((resolve) => {\n const timer = setTimeout(() => resolve(false), 5_000)\n proc.once('exit', () => { clearTimeout(timer); resolve(true) })\n })\n if (!exited) {\n try { proc.kill('SIGKILL') } catch { /* already dead */ }\n this.log.warn('Swift process did not exit gracefully — sent SIGKILL')\n }\n }\n\n private receiveMessage(): Promise<Record<string, unknown>> {\n return new Promise<Record<string, unknown>>((resolve, reject) => {\n this.pendingResolve = resolve\n this.pendingReject = reject\n })\n }\n\n private tryReceive(): void {\n if (this.receiveBuffer.length < 4) return\n const length = this.receiveBuffer.readUInt32LE(0)\n if (this.receiveBuffer.length < 4 + length) return\n\n const jsonBytes = this.receiveBuffer.subarray(4, 4 + length)\n this.receiveBuffer = this.receiveBuffer.subarray(4 + length)\n\n const resolve = this.pendingResolve\n const reject = this.pendingReject\n this.pendingResolve = null\n this.pendingReject = null\n\n if (!resolve) return\n try {\n const parsed = JSON.parse(jsonBytes.toString('utf8')) as Record<string, unknown>\n resolve(parsed)\n } catch (err) {\n reject?.(err instanceof Error ? err : new Error(String(err)))\n }\n }\n\n /** Find pre-compiled binary or compile from Swift source. */\n private async resolveSwiftBinary(): Promise<string | null> {\n // Phase D bundle merge: audio-analyzer ships inside\n // `@camstack/addon-pipeline/dist/audio-analyzer/`, with swift\n // sources at the bundle's `swift/audio-analyzer/` sub-folder.\n // From `dist/audio-analyzer/index.js`, that's `../../swift/audio-analyzer`.\n const candidates = [\n path.join(__dirname, '../../swift/audio-analyzer/apple-sound-classifier'),\n // Fallback for in-tree dev (src/<id>/swift/) and pre-merge layouts.\n path.join(__dirname, '../swift/apple-sound-classifier'),\n path.join(__dirname, '../../swift/apple-sound-classifier'),\n path.join(__dirname, '../../../swift/apple-sound-classifier'),\n ]\n\n for (const p of candidates) {\n if (fs.existsSync(p)) {\n this.log.info('Found pre-compiled Swift CLI', { meta: { path: p } })\n return p\n }\n }\n\n const sourceCandidates = [\n path.join(__dirname, '../../swift/audio-analyzer/apple-sound-classifier.swift'),\n path.join(__dirname, '../swift/apple-sound-classifier.swift'),\n path.join(__dirname, '../../swift/apple-sound-classifier.swift'),\n path.join(__dirname, '../../../swift/apple-sound-classifier.swift'),\n ]\n const sourcePath = sourceCandidates.find(p => fs.existsSync(p))\n if (!sourcePath) {\n this.log.error('Swift source not found', { meta: { searched: sourceCandidates } })\n return null\n }\n\n const outputPath = sourcePath.replace('.swift', '')\n this.log.info('Compiling Swift CLI...', { meta: { source: sourcePath, output: outputPath } })\n\n const { execFileSync } = await import('node:child_process')\n try {\n execFileSync('swiftc', ['-O', '-o', outputPath, sourcePath], {\n timeout: 60_000,\n stdio: 'pipe',\n })\n this.log.info('Swift CLI compiled successfully')\n return outputPath\n } catch (err) {\n this.log.error('Swift compilation failed — install Xcode Command Line Tools', {\n meta: { error: errMsg(err) },\n })\n return null\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Audio helpers\n// ---------------------------------------------------------------------------\n\n/** Simple resample to 16kHz mono by linear interpolation. */\nfunction resampleMono16k(chunk: AudioChunk): Float32Array {\n const { data, sampleRate, channels } = chunk\n const numSamples = data.length / channels\n\n // Mix to mono\n const mono = new Float32Array(numSamples)\n for (let i = 0; i < numSamples; i++) {\n let sum = 0\n for (let c = 0; c < channels; c++) {\n sum += data[i * channels + c]!\n }\n mono[i] = sum / channels\n }\n\n // Resample to 16kHz\n const ratio = 16000 / sampleRate\n const outLen = Math.floor(numSamples * ratio)\n const out = new Float32Array(outLen)\n for (let i = 0; i < outLen; i++) {\n const srcIdx = i / ratio\n const lo = Math.floor(srcIdx)\n const hi = Math.min(lo + 1, numSamples - 1)\n const frac = srcIdx - lo\n out[i] = mono[lo]! * (1 - frac) + mono[hi]! * frac\n }\n return out\n}\n","import type {\n AudioAnalyzerGlobalConfig,\n AudioBackendChoice,\n AudioChunkInput,\n AudioAnalysisResult,\n AudioAnalysisSettings,\n IAudioAnalyzer,\n ConfigUISchema,\n ConfigUISchemaWithValues,\n ProviderRegistration,\n} from '@camstack/types'\nimport {\n AUDIO_BACKEND_CHOICES,\n BaseAddon,\n DEFAULT_AUDIO_ANALYZER_CONFIG,\n audioAnalysisCapability,\n audioAnalyzerCapability,\n errMsg,\n hydrateSchema,\n mapAudioLabelToMacro,\n} from '@camstack/types'\nimport type { AudioClassificationResult } from '@camstack/types'\nimport type { IScopedLogger } from '@camstack/types'\nimport type { AudioBackend, IAudioPipeline } from '../../audio-pipeline.js'\nimport { createAudioPipeline } from '../../audio-pipeline.js'\n\n/**\n * Choices presented in the Audio Model dropdown. YAMNet runs via ONNX\n * when backend=yamnet-onnx; Apple SoundAnalysis is a built-in macOS\n * model and has no swappable modelId — the backend IS the model.\n */\nconst AUDIO_MODEL_OPTIONS = [\n { value: '', label: 'Auto (matches backend)' },\n { value: 'yamnet-onnx', label: 'YAMNet (ONNX)' },\n { value: 'apple-soundanalysis', label: 'Apple SoundAnalysis (built-in)' },\n] as const\n\n/**\n * AudioAnalyzerProvider — implements IAudioAnalyzer.\n *\n * Computes dB/RMS on every chunk and classifies via the in-process\n * IAudioPipeline (YAMNet ONNX / Apple SoundAnalysis). No tRPC roundtrip\n * to a separate audio-classifier addon.\n */\n\n// Suppress repeated classify-error logs. Timeouts are expected during\n// pipeline startup or under CPU pressure — log once, then silence for 30s.\nconst CLASSIFY_ERROR_SUPPRESS_MS = 30_000\n\n// Minimum ms between inference calls per camera. Prevents a single camera\n// from immediately re-queuing on the same pipeline after finishing.\nconst CLASSIFY_MIN_INTERVAL_MS = 500\n\n// Sentinel key used when no deviceId is supplied (legacy callers).\nconst GLOBAL_DEVICE_KEY = -1\n\ninterface CameraClassifyState {\n inProgress: boolean\n lastEndMs: number\n}\n\n/**\n * Reconstruct a Float32 view over f32le PCM bytes carried in a Uint8Array.\n *\n * `@msgpack/msgpack` decodes a binary blob into a Uint8Array that is a subview\n * of its internal decode buffer, whose `byteOffset` is NOT guaranteed to be\n * 4-byte aligned (observed e.g. 9). `new Float32Array(buf, offset, …)` then\n * throws \"start offset of Float32Array should be a multiple of 4\". When the\n * offset is misaligned we copy into a fresh 0-offset buffer; the aligned\n * fast-path reuses the existing view with no copy.\n */\nfunction float32FromBytes(raw: Uint8Array): Float32Array {\n const bytes = raw.byteOffset % 4 === 0 ? raw : new Uint8Array(raw)\n return new Float32Array(bytes.buffer, bytes.byteOffset, Math.floor(bytes.byteLength / 4))\n}\n\nexport class AudioAnalyzerProvider implements IAudioAnalyzer {\n private readonly log: IScopedLogger\n private classifyCallCount = 0\n private lastClassifyErrorMs = 0\n private suppressedClassifyErrors = 0\n private classifyCount = 0\n private backendName = 'unknown'\n /** When true, logs a raw-label sample every 100 classifications (opt-in\n * debug aid). Off by default — the watchdog heartbeat covers liveness. */\n private readonly debugClassifySamples = false\n /** Per-camera in-flight state. Key = deviceId (or GLOBAL_DEVICE_KEY for legacy callers). */\n private readonly cameraState = new Map<number, CameraClassifyState>()\n /** Global pipeline lock — Apple SA and ONNX are single-channel: only one classify() can\n * run at a time. Without this, concurrent calls from different cameras overwrite the\n * single pendingResolve slot in AppleSoundAnalysisPipeline, causing 30s timeouts. */\n private pipelineBusy = false\n\n constructor(\n logger: IScopedLogger,\n private readonly pipeline: IAudioPipeline,\n /** The backend that ACTUALLY backs `pipeline` — passed in by the\n * addon's `onInitialize` after `resolveAudioBackend()` has picked\n * the effective choice (operator override or platform default).\n * Used only for `engine:` log tagging on classify samples. */\n backendName: string,\n private readonly deviceSettingsResolver: (deviceId: number) => Promise<AudioAnalysisSettings | null>,\n private readonly deviceContributionResolver: (deviceId: number) => Promise<ConfigUISchemaWithValues | null>,\n private readonly deviceSettingsPatcher: (deviceId: number, patch: Record<string, unknown>) => Promise<void>,\n private readonly reprobeImpl: () => Promise<{ backend: string }>,\n ) {\n this.log = logger\n this.backendName = backendName\n }\n\n // ── Device-details aggregator contribution ──────────────────────────────\n\n async getDeviceSettingsContribution(input: { deviceId: number }): Promise<ConfigUISchemaWithValues | null> {\n return this.deviceContributionResolver(input.deviceId)\n }\n\n async getDeviceLiveContribution(_input: { deviceId: number }): Promise<ConfigUISchemaWithValues | null> {\n return null\n }\n\n async applyDeviceSettingsPatch(input: { deviceId: number; patch: Record<string, unknown> }): Promise<{ success: true }> {\n await this.deviceSettingsPatcher(input.deviceId, input.patch)\n return { success: true as const }\n }\n\n /**\n * Return the effective per-device audio-analyzer settings, resolved via\n * the kernel's 3-level settings resolver (schema default → global →\n * device override). Orchestrator consumers call this method so they\n * never need to know the audio-analyzer schema field names.\n */\n async resolveDeviceSettings({ deviceId }: { deviceId: number }): Promise<AudioAnalysisSettings | null> {\n return this.deviceSettingsResolver(deviceId)\n }\n\n async analyseChunk({\n chunk,\n settings,\n }: {\n chunk: AudioChunkInput\n settings: AudioAnalysisSettings\n }): Promise<AudioAnalysisResult | null> {\n // AudioChunkInput.data carries raw f32le bytes (4 bytes per sample).\n // Reconstruct the Float32Array view here — do NOT iterate the Uint8Array\n // directly, because each byte (0–255) is NOT a sample value.\n // MsgPack decodes the blob into a Uint8Array that is a subview of its\n // internal buffer, whose byteOffset is NOT guaranteed 4-byte aligned\n // (e.g. 9) — `new Float32Array(buf, offset, …)` then throws \"start offset\n // … multiple of 4\". Copy into a fresh 0-offset buffer when misaligned\n // (fast-path reuses the view when already aligned).\n const samples = float32FromBytes(chunk.data)\n\n // ── Compute dB/RMS levels (always) ──\n let sumSquares = 0\n for (let i = 0; i < samples.length; i++) {\n sumSquares += samples[i]! * samples[i]!\n }\n const rms = Math.sqrt(sumSquares / samples.length)\n const dbfs = rms > 0 ? 20 * Math.log10(rms) : -96\n\n const level = {\n rms: Math.round(rms * 10000) / 10000,\n dbfs: Math.round(dbfs * 10) / 10,\n }\n\n // ── Classification (in-process pipeline) ──\n let classification: AudioAnalysisResult['classification'] | undefined\n\n try {\n const result = await this.classify(chunk)\n if (this.classifyCallCount < 3) {\n const topRaw = result.labels.slice(0, 5).map(l => `${l.className}(${(l.score * 100).toFixed(0)}%)`).join(', ')\n this.log.info('classify debug sample', {\n tags: chunk.deviceId !== undefined ? { deviceId: chunk.deviceId } : undefined,\n meta: {\n index: this.classifyCallCount,\n labelCount: result.labels.length,\n top: topRaw,\n inferenceMs: result.inferenceMs,\n minConf: settings.minConfidence,\n allowedClasses: settings.allowedClasses,\n },\n })\n }\n this.classifyCallCount++\n\n if (result.inferenceMs > 0) {\n // Apply filtering (analyzer responsibility — settings come from per-device config)\n const minConf = settings.minConfidence\n const allowedSet = settings.allowedClasses.length > 0\n ? new Set(settings.allowedClasses.map(c => c.toLowerCase()))\n : null\n\n let filtered = result.labels.filter(c => c.score >= minConf)\n if (allowedSet) {\n filtered = filtered.filter(c => allowedSet.has(c.className.toLowerCase()))\n }\n\n if (filtered.length > 0) {\n classification = {\n labels: filtered,\n inferenceMs: result.inferenceMs,\n }\n }\n }\n } catch (err: unknown) {\n const now = Date.now()\n const sinceLastMs = now - this.lastClassifyErrorMs\n if (sinceLastMs >= CLASSIFY_ERROR_SUPPRESS_MS) {\n const suppressed = this.suppressedClassifyErrors\n this.suppressedClassifyErrors = 0\n this.lastClassifyErrorMs = now\n const msg = errMsg(err)\n const stack = err instanceof Error ? err.stack : undefined\n this.log.warn('Audio classification failed', {\n tags: chunk.deviceId !== undefined ? { deviceId: chunk.deviceId } : undefined,\n meta: { error: msg, stack, suppressedSince: suppressed > 0 ? suppressed : undefined },\n })\n } else {\n this.suppressedClassifyErrors++\n }\n }\n\n return { level, classification, timestamp: chunk.timestamp }\n }\n\n async classify(chunk: AudioChunkInput): Promise<AudioClassificationResult> {\n // Per-camera concurrency guard: at most 1 chunk in flight per device.\n // Drops the incoming call immediately if that camera already has one\n // in progress or hasn't waited the minimum interval since its last call.\n // Cameras are isolated — camera A classifying does NOT block camera B.\n const camKey = chunk.deviceId ?? GLOBAL_DEVICE_KEY\n const now = Date.now()\n const state = this.cameraState.get(camKey)\n if (state?.inProgress || (state !== undefined && (now - state.lastEndMs) < CLASSIFY_MIN_INTERVAL_MS)) {\n return { labels: [], rawLabels: [], inferenceMs: 0 }\n }\n\n if (this.pipelineBusy) {\n return { labels: [], rawLabels: [], inferenceMs: 0 }\n }\n\n this.cameraState.set(camKey, { inProgress: true, lastEndMs: state?.lastEndMs ?? 0 })\n this.pipelineBusy = true\n // AudioChunkInput.data carries raw f32le bytes — reconstruct Float32Array\n // before passing to the pipeline (YAMNet/Apple SoundAnalysis need floats).\n // See float32FromBytes: msgpack-decoded Uint8Arrays may be 4-byte-misaligned.\n const f32Data = float32FromBytes(chunk.data)\n const result = await this.pipeline.classify({\n data: f32Data,\n sampleRate: chunk.sampleRate,\n channels: chunk.channels,\n }).finally(() => {\n this.pipelineBusy = false\n this.cameraState.set(camKey, { inProgress: false, lastEndMs: Date.now() })\n })\n\n // Log raw labels at regular intervals — opt-in debug only.\n if (this.debugClassifySamples && (this.classifyCount < 3 || this.classifyCount % 100 === 0)) {\n const rawTop = result.classifications.slice(0, 5).map(c => `\"${c.className}\"(${(c.score * 100).toFixed(0)}%)`).join(', ')\n this.log.info('classify debug sample', {\n tags: chunk.deviceId !== undefined ? { deviceId: chunk.deviceId } : undefined,\n meta: {\n index: this.classifyCount,\n engine: this.backendName,\n rawLabelCount: result.classifications.length,\n top: rawTop,\n inferenceMs: result.inferenceMs,\n },\n })\n }\n this.classifyCount++\n\n // Map raw labels → macro classes, aggregate scores per macro class.\n const macroAccum = new Map<string, { score: number; rawTop: string }>()\n for (const c of result.classifications) {\n const macro = mapAudioLabelToMacro(c.className)\n if (!macro) continue\n const prev = macroAccum.get(macro)\n if (!prev || c.score > prev.score) {\n macroAccum.set(macro, { score: c.score, rawTop: c.className })\n }\n }\n\n // Sort by score descending\n const labels = [...macroAccum.entries()]\n .sort((a, b) => b[1].score - a[1].score)\n .map(([className, { score, rawTop }]) => ({ className, originalClass: rawTop, score }))\n\n // Raw backend-native labels (no macro aggregation), sorted by score descending.\n const rawLabels = [...result.classifications]\n .sort((a, b) => b.score - a.score)\n .map((c) => ({ className: c.className, originalClass: c.className, score: c.score }))\n\n return { labels, rawLabels, inferenceMs: result.inferenceMs }\n }\n\n isReady(): boolean {\n return this.pipeline !== null\n }\n\n async dispose(): Promise<void> {\n await this.pipeline.dispose()\n }\n\n // Expose via the cap so the reprobe button in the UI reaches the\n // right worker. Delegates to the addon-owned reprobe (it touches\n // `ctx.settings.writeAddonStore` which only the addon has access to).\n async reprobeAudioEngine(): Promise<{ backend: string }> {\n return this.reprobeImpl()\n }\n}\n\n/**\n * Audio Analyzer addon — provides the `audio-analyzer` capability.\n *\n * Owns the IAudioPipeline directly — no tRPC roundtrip to a separate\n * audio-classifier addon.\n */\nexport class AudioAnalyzerAddon extends BaseAddon<AudioAnalyzerGlobalConfig> {\n readonly id = 'audio-analyzer'\n\n private provider: AudioAnalyzerProvider | null = null\n private pipeline: IAudioPipeline | null = null\n\n constructor() { super(DEFAULT_AUDIO_ANALYZER_CONFIG) }\n\n protected globalSettingsSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'audio-engine',\n title: 'Audio',\n // Co-located with detection-pipeline's `engine` section under\n // a single \"Engine\" tab. Both sections handle inference-engine\n // selection but for different modalities — distinct purposes\n // are conveyed through section titles (\"Detection engine\" vs\n // \"Audio inference engine\") and descriptions, not separate\n // tabs.\n tab: 'engine',\n // Renders after detection-pipeline's `engine` section\n // (`order: 0`) on the \"Inference Engine\" tab.\n order: 10,\n description:\n 'Audio classification backend (Apple SoundAnalysis or YAMNet ONNX). Independent from the vision-detection engine above. \"Auto\" picks Apple SoundAnalysis on macOS, YAMNet on Linux. Click the refresh icon next to \"Probed best\" to re-run the probe.',\n // Field order — `probedBest*` lives at the top so the operator\n // sees the auto-detected hint first and can compare it against\n // their override below at a glance. Same convention as the\n // detection-pipeline section.\n fields: [\n {\n type: 'text' as const,\n key: 'probedBestAudioBackend',\n label: 'Probed best',\n description: 'Auto-detected best audio backend on this host. Click the refresh icon to re-run the probe.',\n readonlyField: true,\n default: '',\n actions: [\n { action: 'reprobe-audio-engine', icon: 'refresh-cw', tooltip: 'Re-probe audio engine' },\n ],\n },\n {\n type: 'select' as const,\n key: 'audioBackend',\n label: 'Audio backend',\n options: AUDIO_BACKEND_CHOICES.map(o => ({ value: o.value, label: o.label })),\n default: DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend,\n immediate: true,\n requiresRestart: true,\n },\n {\n type: 'select' as const,\n key: 'selectedAudioModel',\n label: 'Classification model',\n description:\n 'Empty = auto (matches backend). Device-level settings can only inherit / enable / disable this step; model + class filters live here at the node level.',\n options: AUDIO_MODEL_OPTIONS.map(o => ({ value: o.value, label: o.label })),\n default: DEFAULT_AUDIO_ANALYZER_CONFIG.selectedAudioModel,\n immediate: true,\n requiresRestart: true,\n },\n ],\n },\n ],\n }\n }\n\n /**\n * Cascade override — narrow the `selectedAudioModel` options to the\n * subset compatible with the currently-selected `audioBackend`.\n *\n * Same pattern as detection-pipeline's `engineRuntime → engineBackend\n * → engineDevice` cascade: the base schema ships every option\n * (Auto + YAMNet + Apple SA); this override drops the rows that\n * belong to a backend the operator didn't pick. With `immediate:\n * true` on the `audioBackend` select, the UI refetches schema after\n * every flip and the model dropdown updates instantly.\n *\n * `overlay` carries the operator's tentative choices for benchmark/\n * preview mode (operator typed but didn't save yet) — same\n * semantics detection-pipeline relies on.\n */\n override async getGlobalSettings(overlay?: Record<string, unknown>): Promise<ConfigUISchemaWithValues> {\n const ctx = this.ctxIfReady\n const stored = ctx?.settings ? ((await ctx.settings.readAddonStore()) ?? {}) : {}\n const merged = overlay ? { ...stored, ...overlay } : stored\n\n // Resolve the effective backend the same way the runtime would.\n const operatorChoice = typeof merged.audioBackend === 'string' ? merged.audioBackend : DEFAULT_AUDIO_ANALYZER_CONFIG.audioBackend\n const effectiveBackend: AudioBackend =\n operatorChoice === 'apple-soundanalysis' ? 'apple-soundanalysis'\n : operatorChoice === 'yamnet-onnx' ? 'yamnet-onnx'\n : process.platform === 'darwin' ? 'apple-soundanalysis' : 'yamnet-onnx'\n\n // The \"Auto\" entry always passes through — it just resolves to the\n // effective backend at runtime. The other rows are filtered to\n // match the current backend so the UI doesn't suggest impossible\n // combos (e.g. picking \"Apple SoundAnalysis (built-in)\" while\n // backend=yamnet-onnx).\n const filteredModels = AUDIO_MODEL_OPTIONS.filter(\n (o) => o.value === '' || o.value === effectiveBackend,\n )\n\n // Re-project the persisted `selectedAudioModel` onto the narrowed\n // option list — if the previous backend's model is no longer valid\n // for the new backend, snap to \"Auto\" so the dropdown doesn't\n // render an empty value.\n const storedModel = typeof merged.selectedAudioModel === 'string' ? merged.selectedAudioModel : ''\n const validModel = filteredModels.find((o) => o.value === storedModel)?.value ?? ''\n const raw = { ...merged, selectedAudioModel: validModel }\n\n const schema = this.globalSettingsSchema()\n const patched: ConfigUISchema = {\n ...schema,\n sections: schema.sections.map((section) => ({\n ...section,\n fields: section.fields.map((field) => {\n if (field.type === 'select' && field.key === 'selectedAudioModel') {\n return { ...field, options: filteredModels.map((o) => ({ value: o.value, label: o.label })) }\n }\n return field\n }),\n })),\n }\n return hydrateSchema(patched, raw)\n }\n\n /**\n * Re-run the platform probe and persist the detected backend into\n * `probedBestAudioBackend`. Operator `audioBackend` setting is not\n * touched — only the hint.\n */\n async reprobeAudioEngine(): Promise<{ backend: string }> {\n const backend: AudioBackend = process.platform === 'darwin' ? 'apple-soundanalysis' : 'yamnet-onnx'\n await this.ctx.settings?.writeAddonStore({ probedBestAudioBackend: backend })\n this.ctx.logger.info('reprobeAudioEngine: wrote probedBestAudioBackend', { meta: { backend } })\n return { backend }\n }\n\n /** Resolve the effective backend from the operator choice, falling back to the platform heuristic when 'auto'. */\n private resolveAudioBackend(): AudioBackend {\n const choice: AudioBackendChoice = this.config.audioBackend\n if (choice === 'apple-soundanalysis') return 'apple-soundanalysis'\n if (choice === 'yamnet-onnx') return 'yamnet-onnx'\n return process.platform === 'darwin' ? 'apple-soundanalysis' : 'yamnet-onnx'\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const logger = this.ctx.logger as IScopedLogger\n\n // Enable/disable is driven by the binding system (per-device\n // `audio-analysis` wrapper) + the pipeline tree\n // (`addonDefaults['audio-classifier'].enabled`). The addon itself\n // always boots its pipeline; consumers not bound / not in the tree\n // simply don't invoke `classify`.\n\n // Initialize the audio pipeline (YAMNet ONNX / Apple SoundAnalysis)\n const modelsDir = await this.ctx.api.storage.resolve.query({ location: 'models', relativePath: '' })\n .catch(() => 'camstack-data/models')\n const backend = this.resolveAudioBackend()\n logger.info('audio-analyzer: resolving pipeline', {\n meta: { operatorChoice: this.config.audioBackend, effectiveBackend: backend, selectedModel: this.config.selectedAudioModel || null },\n })\n const p = await createAudioPipeline(modelsDir, logger, { backend })\n await p.initialize()\n this.pipeline = p\n\n // Auto-seed probedBestAudioBackend so the UI shows the effective\n // platform default on first boot (matches what we just booted).\n if (!this.config.probedBestAudioBackend) {\n this.reprobeAudioEngine().catch((err: unknown) => {\n logger.warn('audio: auto-reprobe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n\n // Device settings resolver — pulls the audio-classifier step's\n // `settings` map from the orchestrator's resolved pipeline. The\n // settings live on the audio-classifier step (in\n // `agent.addonDefaults['audio-classifier'].settings` overlaid with\n // any per-device patch). No per-device store on this addon.\n const self = this\n const deviceSettingsResolver = async (deviceId: number): Promise<AudioAnalysisSettings | null> => {\n try {\n const resolved = await self.ctx.api.pipelineOrchestrator.resolvePipeline.query({ deviceId })\n const stepSettings = resolved.audio?.settings ?? {}\n const minConfidence = typeof stepSettings['minConfidence'] === 'number'\n ? (stepSettings['minConfidence'] as number)\n : 0.3\n const allowedClasses = Array.isArray(stepSettings['enabledAudioClasses'])\n ? (stepSettings['enabledAudioClasses'] as string[])\n : []\n return { minConfidence, allowedClasses }\n } catch (err) {\n logger.warn('audio: resolveDeviceSettings via orchestrator failed', {\n tags: { deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return null\n }\n }\n\n const deviceContributionResolver = async (_deviceId: number): Promise<ConfigUISchemaWithValues | null> => {\n // No device-level surface — settings live on the audio-classifier\n // pipeline step (Orchestrator tab).\n return null\n }\n const deviceSettingsPatcher = async (_deviceId: number, _patch: Record<string, unknown>): Promise<void> => {\n // No-op: per-device audio settings moved to the audio-classifier step.\n }\n\n this.provider = new AudioAnalyzerProvider(\n logger,\n this.pipeline,\n backend,\n deviceSettingsResolver,\n deviceContributionResolver,\n deviceSettingsPatcher,\n () => this.reprobeAudioEngine(),\n )\n // Split registration mirrors stream-broker/camera-streams + snapshot/snapshot-provider:\n // - audio-analyzer (system): compute path (analyseChunk / classify / isReady / dispose)\n // - audio-analysis (device, wrapper defaultActive): per-device surface — resolveDeviceSettings,\n // the three DeviceSettingsContribution methods and the onAudioLevel event.\n // Every camera gets a binding row; operators disable per-device via setWrapperActive.\n return [\n { capability: audioAnalyzerCapability, provider: this.provider },\n {\n capability: audioAnalysisCapability,\n provider: this.provider,\n },\n ]\n }\n\n protected async onShutdown(): Promise<void> {\n if (this.provider) {\n await this.provider.dispose()\n this.provider = null\n }\n this.pipeline = null\n }\n\n // ── Standard ICamstackAddon — three-level settings API ─────\n //\n // Per-device audio settings (audio class filter + minConfidence) moved\n // to the audio-classifier pipeline step's `getConfigSchema()` and the\n // orchestrator owns the audio node assignment (`audioNodeId`). No\n // device-level settings remain on this addon.\n\n buildDeviceSchema(): ConfigUISchema {\n return { sections: [] }\n }\n\n async getDeviceSettings(deviceId: number): Promise<ConfigUISchemaWithValues> {\n const raw = (await this.ctx?.settings?.readDeviceStore(deviceId)) ?? {}\n return hydrateSchema(this.buildDeviceSchema(), raw)\n }\n\n async updateDeviceSettings(deviceId: number, patch: Record<string, unknown>): Promise<void> {\n await this.ctx?.settings?.writeDeviceStore(deviceId, patch)\n }\n}\n"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
3
  const crypto = require("node:crypto");
4
- const index = require("../index-BbPPvoCx.js");
4
+ const index = require("../index-B36NMAdu.js");
5
5
  const nodeAv = require("node-av");
6
6
  const CODEC_ID_BY_NAME = {
7
7
  aac: nodeAv.AV_CODEC_ID_AAC,
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { B as BaseAddon, w as audioCodecCapability, e as errMsg } from "../index-Bmlkm0Fd.mjs";
2
+ import { B as BaseAddon, y as audioCodecCapability, e as errMsg } from "../index-5aYef068.mjs";
3
3
  import { Codec, CodecContext, AV_SAMPLE_FMT_FLT, SoftwareResampleContext, Packet, Frame, AVERROR_EAGAIN, AVERROR_EOF, AV_CODEC_ID_PCM_MULAW, AV_CODEC_ID_PCM_ALAW, AV_CODEC_ID_OPUS, AV_CODEC_ID_AAC_LATM, AV_CODEC_ID_AAC, AV_CH_LAYOUT_MONO, AV_CHANNEL_ORDER_NATIVE, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16 } from "node-av";
4
4
  const CODEC_ID_BY_NAME = {
5
5
  aac: AV_CODEC_ID_AAC,
@@ -23,7 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  ));
24
24
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
25
25
  const crypto = require("node:crypto");
26
- const index = require("../index-BbPPvoCx.js");
26
+ const index = require("../index-B36NMAdu.js");
27
27
  const shmRing = require("@camstack/shm-ring");
28
28
  const RING_BUDGET_MB = (() => {
29
29
  const raw = Number(process.env["CAMSTACK_SHM_RING_BUDGET_MB"]);
@@ -307,7 +307,7 @@ async function getNodeAv() {
307
307
  return _nav;
308
308
  }
309
309
  async function getConstants() {
310
- if (!_consts) _consts = await Promise.resolve().then(() => require("../index-D_cl0Qqb.js"));
310
+ if (!_consts) _consts = await Promise.resolve().then(() => require("../index-CMcx_k6Y.js"));
311
311
  return _consts;
312
312
  }
313
313
  async function getSharp() {
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from "node:crypto";
2
- import { e as errMsg, B as BaseAddon, u as DEFAULT_DECODER_HWACCEL_CONFIG, H as HWACCEL_OPTIONS, v as decoderCapability, R as RingBuffer } from "../index-Bmlkm0Fd.mjs";
2
+ import { e as errMsg, B as BaseAddon, w as DEFAULT_DECODER_HWACCEL_CONFIG, H as HWACCEL_OPTIONS, x as decoderCapability, R as RingBuffer } from "../index-5aYef068.mjs";
3
3
  import { computeSlotByteLength, computeSegmentSize, deriveSlotCount, MIN_RING_SLOTS, createSegment, FrameRingWriter, FrameRingReaderCache } from "@camstack/shm-ring";
4
4
  const RING_BUDGET_MB = (() => {
5
5
  const raw = Number(process.env["CAMSTACK_SHM_RING_BUDGET_MB"]);
@@ -283,7 +283,7 @@ async function getNodeAv() {
283
283
  return _nav;
284
284
  }
285
285
  async function getConstants() {
286
- if (!_consts) _consts = await import("../index-UbcdLS7a.mjs");
286
+ if (!_consts) _consts = await import("../index-CYb7cFrv.mjs");
287
287
  return _consts;
288
288
  }
289
289
  async function getSharp() {
@@ -22,7 +22,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
22
  mod
23
23
  ));
24
24
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
25
- const index = require("../index-BbPPvoCx.js");
25
+ const index = require("../index-B36NMAdu.js");
26
26
  const fs = require("node:fs");
27
27
  const path = require("node:path");
28
28
  const os = require("node:os");
@@ -915,12 +915,12 @@ const OBJECT_DETECTION_MODELS = [
915
915
  openvino: { url: hf("objectDetection/yolo26/openvino/camstack-yolo26x.xml"), sizeMB: 213, runtimes: ["python"] }
916
916
  }
917
917
  },
918
- // ── Scrypted ReLU variants (optimized for int8 quantization) ──
919
- // Note: Scrypted CoreML models are compiled with 320x320 fixed input size
918
+ // ── ReLU variants (optimized for int8 quantization) ──
919
+ // Note: these CoreML models are compiled with 320x320 fixed input size
920
920
  {
921
921
  id: "scrypted-yolov9t-relu",
922
- name: "YOLOv9 Tiny ReLU (Scrypted)",
923
- description: "Scrypted YOLOv9t ReLU — optimized for int8 quantization",
922
+ name: "YOLOv9 Tiny ReLU",
923
+ description: "YOLOv9t ReLU — optimized for int8 quantization",
924
924
  inputSize: { width: 320, height: 320 },
925
925
  labels: [],
926
926
  preprocessMode: "letterbox",
@@ -932,8 +932,8 @@ const OBJECT_DETECTION_MODELS = [
932
932
  },
933
933
  {
934
934
  id: "scrypted-yolov9s-relu",
935
- name: "YOLOv9 Small ReLU (Scrypted)",
936
- description: "Scrypted YOLOv9s ReLU — improved efficiency, int8 ready",
935
+ name: "YOLOv9 Small ReLU",
936
+ description: "YOLOv9s ReLU — improved efficiency, int8 ready",
937
937
  inputSize: { width: 320, height: 320 },
938
938
  labels: [],
939
939
  preprocessMode: "letterbox",
@@ -945,9 +945,9 @@ const OBJECT_DETECTION_MODELS = [
945
945
  },
946
946
  {
947
947
  id: "scrypted-yolov9c-relu",
948
- name: "YOLOv9 C ReLU (Scrypted)",
949
- description: "Scrypted YOLOv9c ReLU — high-accuracy, int8 ready",
950
- // Scrypted ReLU variants are all compiled with fixed 320x320 input
948
+ name: "YOLOv9 C ReLU",
949
+ description: "YOLOv9c ReLU — high-accuracy, int8 ready",
950
+ // ReLU variants are all compiled with fixed 320x320 input
951
951
  // (see the section header above). Validated at engine load via
952
952
  // `validateOnnxInputShape` — mismatches warn, never silently miscompute.
953
953
  inputSize: { width: 320, height: 320 },
@@ -961,9 +961,9 @@ const OBJECT_DETECTION_MODELS = [
961
961
  },
962
962
  {
963
963
  id: "scrypted-yolov9m-relu",
964
- name: "YOLOv9 M ReLU (Scrypted)",
965
- description: "Scrypted YOLOv9m ReLU — medium, int8 ready",
966
- // Scrypted ReLU variants are all compiled with fixed 320x320 input
964
+ name: "YOLOv9 M ReLU",
965
+ description: "YOLOv9m ReLU — medium, int8 ready",
966
+ // ReLU variants are all compiled with fixed 320x320 input
967
967
  // (see the section header above). Validated at engine load via
968
968
  // `validateOnnxInputShape` — mismatches warn, never silently miscompute.
969
969
  inputSize: { width: 320, height: 320 },
@@ -992,9 +992,9 @@ const FACE_DETECTION_MODELS = [
992
992
  },
993
993
  {
994
994
  id: "scrypted-yolov9t-face",
995
- name: "YOLOv9t Face ReLU (Scrypted)",
996
- description: "Scrypted YOLOv9t face detection — YOLO-based, fast",
997
- // Scrypted YOLO face variants follow the 320x320 ReLU convention.
995
+ name: "YOLOv9t Face ReLU",
996
+ description: "YOLOv9t face detection — YOLO-based, fast, 320x320 ReLU",
997
+ // ReLU face variants follow the 320x320 fixed input convention.
998
998
  // Validated at engine load via `validateOnnxInputShape`.
999
999
  inputSize: { width: 320, height: 320 },
1000
1000
  labels: [{ id: "face", name: "Face" }],
@@ -1023,8 +1023,8 @@ const FACE_EMBEDDING_MODELS = [
1023
1023
  },
1024
1024
  {
1025
1025
  id: "inception-resnet-v1",
1026
- name: "Inception ResNet V1 (Scrypted)",
1027
- description: "FaceNet-style face recognition embeddings (512-d) — Scrypted model",
1026
+ name: "Inception ResNet V1",
1027
+ description: "FaceNet-style face recognition embeddings (512-d) — hosted on plugin-models HF repo",
1028
1028
  inputSize: { width: 160, height: 160 },
1029
1029
  labels: [{ id: "embedding", name: "Face Embedding" }],
1030
1030
  preprocessMode: "resize",
@@ -1837,8 +1837,8 @@ class EngineFactory {
1837
1837
  * single IPC round-trip. Returns the structured Python detection
1838
1838
  * dicts in input order. Throws when this factory has no pool path.
1839
1839
  *
1840
- * Used by `runPipelineBatch` for fair benchmarking against
1841
- * Scrypted's `detectObjects(media, {batch})`.
1840
+ * Used by `runPipelineBatch` for fair batched benchmarking — N frames
1841
+ * in one IPC round-trip, amortising the per-call envelope overhead.
1842
1842
  */
1843
1843
  async batchInferRaw(stepId, items, modelId) {
1844
1844
  if (!this.poolManager) {
@@ -4168,7 +4168,7 @@ class DetectionPipelineProvider {
4168
4168
  if (attempt < maxRetries) {
4169
4169
  const delayMs = 2e3 * Math.pow(2, attempt - 1);
4170
4170
  this.log.warn("Model download failed — retrying", { meta: { modelId: entry.id, attempt, maxRetries, error: msg, retryDelayMs: delayMs } });
4171
- await new Promise((resolve) => setTimeout(resolve, delayMs));
4171
+ await index.sleep(delayMs);
4172
4172
  } else {
4173
4173
  throw new Error(`Failed to download model "${entry.id}" after ${maxRetries} attempts: ${msg}`, { cause: err });
4174
4174
  }
@@ -4243,10 +4243,10 @@ class DetectionPipelineProvider {
4243
4243
  * factory). Returns one minimally-shaped `FrameResult` per input
4244
4244
  * frame in input order.
4245
4245
  *
4246
- * Used by `scripts/bench-scrypted-style.mts` for the
4247
- * Scrypted-equivalent benchmark Scrypted's `detectObjects(media,
4248
- * {batch})` collapses N requests into one call too, so bypassing
4249
- * the per-call IPC overhead is what makes the comparison fair.
4246
+ * Used by `scripts/bench-batch-style.mts` for the batch benchmark —
4247
+ * collapsing N requests into one call amortises the per-call IPC
4248
+ * envelope overhead, which is what makes the batched path measurably
4249
+ * faster than N sequential `runPipeline` calls.
4250
4250
  */
4251
4251
  async runPipelineBatch(input) {
4252
4252
  this.log.debug("runPipelineBatch entry", {