@openmixer/plugin-qualify 0.20.1 → 0.20.4

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 (132) hide show
  1. package/bin/plugin-qualify-sync.mjs +1 -0
  2. package/catalogs/ca.json +251 -0
  3. package/catalogs/de.json +49 -0
  4. package/catalogs/es.json +49 -0
  5. package/catalogs/fr.json +49 -0
  6. package/catalogs/it.json +49 -0
  7. package/catalogs/pt.json +49 -0
  8. package/dist/clap-loader.js +2 -1
  9. package/dist/clap-loader.js.map +1 -1
  10. package/dist/clap-qualify.d.ts +11 -0
  11. package/dist/clap-qualify.js +16 -0
  12. package/dist/clap-qualify.js.map +1 -1
  13. package/dist/compare.d.ts +12 -11
  14. package/dist/compare.js +71 -38
  15. package/dist/compare.js.map +1 -1
  16. package/dist/host-admission.d.ts +25 -0
  17. package/dist/host-admission.js +41 -5
  18. package/dist/host-admission.js.map +1 -1
  19. package/dist/hostd-pin-files.d.ts +6 -2
  20. package/dist/hostd-pin-files.js +10 -6
  21. package/dist/hostd-pin-files.js.map +1 -1
  22. package/dist/hostd-search-path.d.ts +38 -0
  23. package/dist/hostd-search-path.js +58 -0
  24. package/dist/hostd-search-path.js.map +1 -0
  25. package/dist/index.d.ts +4 -0
  26. package/dist/index.js +4 -0
  27. package/dist/index.js.map +1 -1
  28. package/dist/jack-registry.js +1 -1
  29. package/dist/lv2-loader.d.ts +33 -1
  30. package/dist/lv2-loader.js +82 -19
  31. package/dist/lv2-loader.js.map +1 -1
  32. package/dist/measurement/vocabulary.js +3 -3
  33. package/dist/measurement/vocabulary.js.map +1 -1
  34. package/dist/measurement-grid.d.ts +1 -1
  35. package/dist/measurement-grid.js +5 -3
  36. package/dist/measurement-grid.js.map +1 -1
  37. package/dist/pins-file.d.ts +3 -2
  38. package/dist/pins-file.js +3 -2
  39. package/dist/pins-file.js.map +1 -1
  40. package/dist/plugin-db-cli.d.ts +28 -0
  41. package/dist/plugin-db-cli.js +43 -0
  42. package/dist/plugin-db-cli.js.map +1 -0
  43. package/dist/plugin-db-pin.d.ts +8 -0
  44. package/dist/plugin-db-pin.js +29 -0
  45. package/dist/plugin-db-pin.js.map +1 -0
  46. package/dist/probe-judge.js +5 -0
  47. package/dist/probe-judge.js.map +1 -1
  48. package/dist/probe-output.d.ts +10 -0
  49. package/dist/probe-output.js +16 -0
  50. package/dist/probe-output.js.map +1 -1
  51. package/dist/probe-runner.js +24 -5
  52. package/dist/probe-runner.js.map +1 -1
  53. package/dist/qualification-record.d.ts +5 -0
  54. package/dist/qualification-record.js +13 -1
  55. package/dist/qualification-record.js.map +1 -1
  56. package/dist/qualify-adapter-pass.d.ts +1 -2
  57. package/dist/qualify-adapter-pass.js +6 -6
  58. package/dist/qualify-adapter-pass.js.map +1 -1
  59. package/dist/qualify-declarations.d.ts +23 -6
  60. package/dist/qualify-declarations.js +25 -6
  61. package/dist/qualify-declarations.js.map +1 -1
  62. package/dist/qualify-isolated.d.ts +9 -0
  63. package/dist/qualify-isolated.js +16 -5
  64. package/dist/qualify-isolated.js.map +1 -1
  65. package/dist/qualify-pool.js +4 -3
  66. package/dist/qualify-pool.js.map +1 -1
  67. package/dist/qualify-rejudge.js +3 -3
  68. package/dist/qualify-rejudge.js.map +1 -1
  69. package/dist/qualify-sync.d.ts +12 -5
  70. package/dist/qualify-sync.js +51 -30
  71. package/dist/qualify-sync.js.map +1 -1
  72. package/dist/record-figures.d.ts +52 -0
  73. package/dist/record-figures.js +58 -0
  74. package/dist/record-figures.js.map +1 -0
  75. package/dist/refused-stderr-kept.d.ts +12 -0
  76. package/dist/refused-stderr-kept.js +67 -0
  77. package/dist/refused-stderr-kept.js.map +1 -0
  78. package/dist/run-home.d.ts +4 -0
  79. package/dist/run-home.js +26 -0
  80. package/dist/run-home.js.map +1 -0
  81. package/dist/text-catalog.d.ts +41 -0
  82. package/dist/text-catalog.js +97 -0
  83. package/dist/text-catalog.js.map +1 -0
  84. package/dist/text-codes.d.ts +29 -0
  85. package/dist/text-codes.generated.d.ts +88 -0
  86. package/dist/text-codes.generated.js +96 -0
  87. package/dist/text-codes.generated.js.map +1 -0
  88. package/dist/text-codes.js +19 -0
  89. package/dist/text-codes.js.map +1 -0
  90. package/package.json +8 -6
  91. package/schema/codes.json +386 -0
  92. package/src/clap-loader.ts +2 -1
  93. package/src/clap-qualify.ts +25 -0
  94. package/src/compare.ts +85 -45
  95. package/src/host-admission.ts +52 -5
  96. package/src/hostd-pin-files.ts +13 -8
  97. package/src/hostd-search-path.ts +85 -0
  98. package/src/index.ts +4 -0
  99. package/src/jack-registry.ts +1 -1
  100. package/src/lv2-loader.ts +89 -17
  101. package/src/measurement/vocabulary.ts +3 -3
  102. package/src/measurement-grid.ts +5 -3
  103. package/src/pins-file.ts +3 -2
  104. package/src/plugin-db-cli.ts +78 -0
  105. package/src/plugin-db-pin.ts +33 -0
  106. package/src/probe-judge.ts +5 -0
  107. package/src/probe-output.ts +23 -0
  108. package/src/probe-runner.ts +25 -4
  109. package/src/qualification-record.ts +13 -1
  110. package/src/qualify-adapter-pass.ts +6 -5
  111. package/src/qualify-declarations.ts +27 -6
  112. package/src/qualify-isolated.ts +18 -5
  113. package/src/qualify-pool.ts +4 -3
  114. package/src/qualify-rejudge.ts +3 -3
  115. package/src/qualify-sync.ts +55 -33
  116. package/src/record-figures.ts +114 -0
  117. package/src/refused-stderr-kept.ts +84 -0
  118. package/src/run-home.ts +28 -0
  119. package/src/text-catalog.ts +116 -0
  120. package/src/text-codes.generated.ts +97 -0
  121. package/src/text-codes.ts +35 -0
  122. package/tools/_declarations.py +2 -1
  123. package/tools/catalogs-gate.sh +22 -0
  124. package/tools/compare-host.mjs +1 -1
  125. package/tools/compare-run.mjs +10 -6
  126. package/tools/descriptors.py +40 -3
  127. package/tools/gen-python-declarations.mjs +1 -0
  128. package/tools/gen-text-codes.d.mts +8 -0
  129. package/tools/gen-text-codes.mjs +77 -0
  130. package/tools/isolated-step.mjs +24 -3
  131. package/tools/mod-host-admit.mjs +57 -1
  132. package/tools/scan.py +29 -5
package/src/compare.ts CHANGED
@@ -15,6 +15,9 @@
15
15
  */
16
16
  import { createHash } from "node:crypto";
17
17
 
18
+ import { TEXT_CODE, type ReasonText } from "./text-codes.js";
19
+ import { reasonText } from "./text-catalog.js";
20
+
18
21
  export const COMPARISON_RECORD_SCHEMA = "plugin-qualify.comparison-record/1";
19
22
  export const COMPARISON_SUMMARY_SCHEMA = "plugin-qualify.comparison-summary/1";
20
23
  export const COMPARISON_RECORD_SUFFIX = ".comparison.json";
@@ -33,7 +36,7 @@ export interface HostTraits {
33
36
  readonly drive: "protocol" | "process";
34
37
  readonly formats: readonly ("lv2" | "clap")[];
35
38
  /** Why the plugin latency it reports is or is not observable. */
36
- readonly latency: { readonly observable: boolean; readonly how: string };
39
+ readonly latency: { readonly observable: boolean; readonly how: ReasonText };
37
40
  readonly rt: string;
38
41
  }
39
42
 
@@ -42,28 +45,28 @@ export const HOST_TRAITS: Readonly<Record<CompareHost, HostTraits>> = {
42
45
  program: "omx-clap-host",
43
46
  drive: "protocol",
44
47
  formats: ["lv2", "clap"],
45
- latency: { observable: true, how: "the host sets a JACK latency callback: the plugin's latency is on its output port's capture range" },
48
+ latency: { observable: true, how: { code: TEXT_CODE.compareLatencyPortObservable } },
46
49
  rt: "its JACK process callback, counted by rt_watch.so",
47
50
  },
48
51
  "plugin-hostd": {
49
52
  program: "plugin-hostd",
50
53
  drive: "protocol",
51
54
  formats: ["lv2", "clap"],
52
- latency: { observable: true, how: "its worker (omx-clap-host) sets a JACK latency callback" },
55
+ latency: { observable: true, how: { code: TEXT_CODE.compareLatencyHostdPortObservable } },
53
56
  rt: "the JACK process callback of every process it starts, counted by rt_watch.so",
54
57
  },
55
58
  "mod-host": {
56
59
  program: "mod-host",
57
60
  drive: "protocol",
58
61
  formats: ["lv2"],
59
- latency: { observable: false, how: "mod-host sets no JACK latency callback, so a plugin's latency is not on its ports" },
62
+ latency: { observable: false, how: { code: TEXT_CODE.compareLatencyNotObservableModHost } },
60
63
  rt: "its JACK process callback, counted by rt_watch.so",
61
64
  },
62
65
  jalv: {
63
66
  program: "jalv",
64
67
  drive: "process",
65
68
  formats: ["lv2"],
66
- latency: { observable: true, how: "jalv sets a JACK latency callback: the plugin's latency is on its output port's capture range" },
69
+ latency: { observable: true, how: { code: TEXT_CODE.compareLatencyPortObservable } },
67
70
  rt: "its JACK process callback, counted by rt_watch.so",
68
71
  },
69
72
  };
@@ -234,15 +237,15 @@ export interface HostPresence {
234
237
  readonly installed: boolean;
235
238
  readonly version: string | null;
236
239
  /** Why it is skipped, when it is. */
237
- readonly reason?: string;
240
+ readonly reason?: ReasonText;
238
241
  }
239
242
 
240
243
  /** The reference hosts a publishable summary lacks, by name, with the reason each is missing. */
241
- export function missingReferences(present: readonly HostPresence[]): readonly { host: CompareHost; reason: string }[] {
244
+ export function missingReferences(present: readonly HostPresence[]): readonly { host: CompareHost; reason: ReasonText }[] {
242
245
  return REFERENCE_HOSTS.flatMap((h) => {
243
246
  const p = present.find((x) => x.host === h);
244
- if (p === undefined) return [{ host: h, reason: "not asked for with --hosts" }];
245
- if (!p.installed) return [{ host: h, reason: p.reason ?? `${HOST_TRAITS[h].program} is not installed` }];
247
+ if (p === undefined) return [{ host: h, reason: { code: TEXT_CODE.compareHostNotAsked } }];
248
+ if (!p.installed) return [{ host: h, reason: p.reason ?? { code: TEXT_CODE.compareHostNotInstalled, args: { program: HOST_TRAITS[h].program } } }];
246
249
  return [];
247
250
  });
248
251
  }
@@ -256,7 +259,7 @@ export function presenceFromRecords(records: readonly ComparisonRecord[]): HostP
256
259
  if (ran !== undefined) return [{ host: h, installed: true, version: ran.version }];
257
260
  if (rows.every((x) => x.status === "not-applicable")) return [{ host: h, installed: true, version: rows[0]?.version ?? null }];
258
261
  const skipped = rows.find((x) => x.status === "skipped");
259
- return [{ host: h, installed: false, version: null, reason: skipped?.reason ?? `${HOST_TRAITS[h].program} did not run` }];
262
+ return [{ host: h, installed: false, version: null, reason: skipped?.reason ?? { code: TEXT_CODE.compareHostDidNotRun, args: { program: HOST_TRAITS[h].program } } }];
260
263
  });
261
264
  }
262
265
 
@@ -308,7 +311,7 @@ export type FigureResult = "measured" | "not-observable" | "not-run";
308
311
 
309
312
  export interface Check {
310
313
  readonly result: CheckResult;
311
- readonly detail?: string;
314
+ readonly detail?: ReasonText;
312
315
  }
313
316
  export interface OutputCheck extends Check {
314
317
  readonly peak?: number;
@@ -335,12 +338,12 @@ export interface CpuFigure {
335
338
  readonly result: FigureResult;
336
339
  /** utime + stime of the host's processes over the soak, in percent of one core. */
337
340
  readonly percent?: number;
338
- readonly detail?: string;
341
+ readonly detail?: ReasonText;
339
342
  }
340
343
  export interface LatencyFigure {
341
344
  readonly result: FigureResult;
342
345
  readonly frames?: number;
343
- readonly detail?: string;
346
+ readonly detail?: ReasonText;
344
347
  }
345
348
 
346
349
  export interface HostChecks {
@@ -358,7 +361,7 @@ export interface HostRow {
358
361
  readonly host: CompareHost;
359
362
  readonly version: string | null;
360
363
  readonly status: "ran" | "skipped" | "not-applicable";
361
- readonly reason?: string;
364
+ readonly reason?: ReasonText;
362
365
  readonly checks?: HostChecks;
363
366
  /** The step the host died at and how, when it did. */
364
367
  readonly crash?: { readonly step: string; readonly signal: string };
@@ -410,11 +413,11 @@ const audible = (p: number | null | undefined): boolean => typeof p === "number"
410
413
 
411
414
  /** The real-time verdict from the reports of the host's processes. */
412
415
  export function rtCheckOf(reports: readonly RtReport[] | undefined): RtCheck {
413
- if (reports === undefined || reports.length === 0) return { result: "not-observable", detail: "no process of the host reported (rt_watch.so was not loaded, or the host was gone)" };
416
+ if (reports === undefined || reports.length === 0) return { result: "not-observable", detail: { code: TEXT_CODE.compareRtNotReported } };
414
417
  const watched = reports.filter((r) => r.callbacks > 0);
415
418
  const armed = watched.reduce((s, r) => s + r.armedCycles, 0);
416
- if (watched.length === 0) return { result: "not-observable", detail: "no process of the host set a JACK process callback that rt_watch.so could wrap" };
417
- if (armed === 0) return { result: "not-observable", detail: "the process callback never ran past its warm-up" };
419
+ if (watched.length === 0) return { result: "not-observable", detail: { code: TEXT_CODE.compareRtNoCallback } };
420
+ if (armed === 0) return { result: "not-observable", detail: { code: TEXT_CODE.compareRtNoWarmUp } };
418
421
  const allocations = watched.reduce((s, r) => s + r.allocations, 0);
419
422
  const locks = watched.reduce((s, r) => s + r.locks, 0);
420
423
  const syscalls = watched.reduce((s, r) => s + r.syscalls, 0);
@@ -429,65 +432,92 @@ export function rtCheckOf(reports: readonly RtReport[] | undefined): RtCheck {
429
432
  syscalls,
430
433
  cycles: armed,
431
434
  ...(kinds.length ? { kinds } : {}),
432
- ...(clean ? (trylocks > 0 ? { detail: `${String(trylocks)} try-locks on the audio thread over ${String(armed)} cycles; a try-lock never waits` } : {}) : { detail: `on the audio thread over ${String(armed)} cycles: ${String(allocations)} allocations, ${String(locks)} locks, ${String(syscalls)} system calls` }),
435
+ ...(clean
436
+ ? trylocks > 0
437
+ ? { detail: { code: TEXT_CODE.compareRtTrylocksOnly, args: { trylocks, cycles: armed } } }
438
+ : {}
439
+ : { detail: { code: TEXT_CODE.compareRtUnsafe, args: { cycles: armed, allocations, locks, syscalls } } }),
433
440
  };
434
441
  }
435
442
 
436
- const NOT_RUN = (why: string): Check => ({ result: "not-run", detail: why });
443
+ const NOT_RUNNING: ReasonText = { code: TEXT_CODE.compareNotRunning };
444
+ const NOT_RUN = (why: ReasonText): Check => ({ result: "not-run", detail: why });
437
445
 
438
446
  /** One host's observation of one plugin as its row. */
439
447
  export function hostRowOf(host: CompareHost, version: string | null, obs: HostObservation, conditions: { cycles: number; soakS: number }): HostRow {
440
448
  const base = { host, version };
441
- if (!obs.installed) return { ...base, status: "skipped", reason: obs.problem ?? `${HOST_TRAITS[host].program} is not installed` };
442
- if (obs.started === false) return { ...base, status: "skipped", reason: obs.problem ?? `${HOST_TRAITS[host].program} did not start` };
449
+ if (!obs.installed) return { ...base, status: "skipped", reason: obs.problem ?? { code: TEXT_CODE.compareHostNotInstalled, args: { program: HOST_TRAITS[host].program } } };
450
+ if (obs.started === false) return { ...base, status: "skipped", reason: obs.problem ?? { code: TEXT_CODE.compareHostDidNotStart, args: { program: HOST_TRAITS[host].program } } };
443
451
  const load = obs.load;
444
452
  const loaded = load?.ok === true;
445
453
  const loads: Check = loaded
446
454
  ? { result: "pass" }
447
- : { result: "fail", detail: load?.died ? `the host ended (${load.died})` : load?.resp !== undefined ? `resp ${String(load.resp)}` : load?.detail ?? "no answer in time" };
455
+ : {
456
+ result: "fail",
457
+ detail: load?.died
458
+ ? { code: TEXT_CODE.compareLoadHostEnded, args: { died: load.died } }
459
+ : load?.resp !== undefined
460
+ ? { code: TEXT_CODE.compareLoadResp, args: { resp: load.resp } }
461
+ : (load?.detail ?? { code: TEXT_CODE.compareLoadTimeout }),
462
+ };
448
463
  const ports = obs.ports;
449
464
  const anyPort = ports !== undefined && ports.audioIn + ports.audioOut + ports.midiIn > 0;
450
- const instantiates: Check = !loaded ? NOT_RUN("it did not load") : anyPort ? { result: "pass" } : { result: "fail", detail: "no port of the plugin appeared on the graph" };
465
+ const instantiates: Check = !loaded
466
+ ? NOT_RUN({ code: TEXT_CODE.compareNotLoaded })
467
+ : anyPort
468
+ ? { result: "pass" }
469
+ : { result: "fail", detail: { code: TEXT_CODE.compareNoPort } };
451
470
  const running = loaded && anyPort;
452
471
 
453
472
  let output: OutputCheck;
454
- if (!running) output = { ...NOT_RUN("it is not running") };
455
- else if (ports.audioOut === 0) output = { result: "not-observable", detail: "the plugin has no audio output" };
473
+ if (!running) output = { ...NOT_RUN(NOT_RUNNING) };
474
+ else if (ports.audioOut === 0) output = { result: "not-observable", detail: { code: TEXT_CODE.compareNoAudioOutput } };
456
475
  else if (obs.peakProblem !== undefined) output = { result: "not-observable", detail: obs.peakProblem };
457
- else if ((obs.nonFinite ?? 0) > 0) output = { result: "fail", detail: `the output is not finite (${String(obs.nonFinite)} NaN or infinite samples)`, peak: obs.peak ?? 0, stimulus: obs.stimulus ?? "none" };
476
+ else if ((obs.nonFinite ?? 0) > 0)
477
+ output = { result: "fail", detail: { code: TEXT_CODE.compareOutputNotFinite, args: { count: obs.nonFinite ?? 0 } }, peak: obs.peak ?? 0, stimulus: obs.stimulus ?? "none" };
458
478
  else if (audible(obs.peak)) output = { result: "pass", peak: obs.peak as number, stimulus: obs.stimulus ?? "none" };
459
- else if (obs.stimulus === "none" || obs.stimulus === undefined) output = { result: "not-observable", detail: "no stimulus this plugin takes (no audio or MIDI input), and its output was silent", peak: obs.peak ?? 0, stimulus: "none" };
460
- else output = { result: "fail", detail: `silent under ${obs.stimulus}`, peak: obs.peak ?? 0, stimulus: obs.stimulus };
479
+ else if (obs.stimulus === "none" || obs.stimulus === undefined)
480
+ output = { result: "not-observable", detail: { code: TEXT_CODE.compareNoStimulusSilent }, peak: obs.peak ?? 0, stimulus: "none" };
481
+ else output = { result: "fail", detail: { code: TEXT_CODE.compareSilentUnderStimulus, args: { stimulus: obs.stimulus } }, peak: obs.peak ?? 0, stimulus: obs.stimulus };
461
482
 
462
483
  const c = obs.cycles;
463
484
  const cycles: CyclesCheck =
464
485
  c === undefined
465
- ? { result: "not-run", done: 0, of: conditions.cycles, detail: "the cycles did not run" }
486
+ ? { result: "not-run", done: 0, of: conditions.cycles, detail: { code: TEXT_CODE.compareCyclesNotRun } }
466
487
  : c.done === c.of
467
488
  ? { result: "pass", done: c.done, of: c.of }
468
- : { result: "fail", done: c.done, of: c.of, detail: c.died ? `the host ended at cycle ${String(c.done + 1)} (${c.died})` : `cycle ${String(c.done + 1)} failed${c.failedAt ? ` at ${c.failedAt}` : ""}` };
489
+ : {
490
+ result: "fail",
491
+ done: c.done,
492
+ of: c.of,
493
+ detail: c.died
494
+ ? { code: TEXT_CODE.compareCycleHostEnded, args: { cycle: c.done + 1, died: c.died } }
495
+ : c.failedAt
496
+ ? { code: TEXT_CODE.compareCycleFailedAt, args: { cycle: c.done + 1, step: c.failedAt } }
497
+ : { code: TEXT_CODE.compareCycleFailed, args: { cycle: c.done + 1 } },
498
+ };
469
499
 
470
500
  const s = obs.soak;
471
501
  const soak: SoakCheck = !running || s === undefined
472
- ? { result: "not-run", seconds: 0, detail: "it is not running" }
502
+ ? { result: "not-run", seconds: 0, detail: NOT_RUNNING }
473
503
  : s.alive
474
504
  ? { result: "pass", seconds: s.seconds, ...(typeof s.peakAtEnd === "number" ? { peakAtEnd: s.peakAtEnd } : {}) }
475
- : { result: "fail", seconds: s.seconds, detail: `the host ended during the soak${s.died ? ` (${s.died})` : ""}` };
505
+ : { result: "fail", seconds: s.seconds, detail: s.died ? { code: TEXT_CODE.compareSoakHostEnded, args: { died: s.died } } : { code: TEXT_CODE.compareSoakEnded } };
476
506
 
477
- const rtSafe: RtCheck = !running ? { result: "not-run", detail: "it is not running" } : rtCheckOf(obs.rt);
507
+ const rtSafe: RtCheck = !running ? { result: "not-run", detail: NOT_RUNNING } : rtCheckOf(obs.rt);
478
508
  const cpu: CpuFigure = !running || obs.cpu === undefined
479
- ? { result: "not-run", detail: "it is not running" }
509
+ ? { result: "not-run", detail: NOT_RUNNING }
480
510
  : "percent" in obs.cpu
481
511
  ? { result: "measured", percent: Math.round(obs.cpu.percent * 100) / 100 }
482
512
  : { result: "not-observable", detail: obs.cpu.problem };
483
513
  const traits = HOST_TRAITS[host];
484
514
  const latency: LatencyFigure = !running
485
- ? { result: "not-run", detail: "it is not running" }
515
+ ? { result: "not-run", detail: NOT_RUNNING }
486
516
  : !traits.latency.observable
487
517
  ? { result: "not-observable", detail: traits.latency.how }
488
518
  : typeof obs.latencyFrames === "number"
489
519
  ? { result: "measured", frames: obs.latencyFrames }
490
- : { result: "not-observable", detail: "the output port's latency could not be read" };
520
+ : { result: "not-observable", detail: { code: TEXT_CODE.compareLatencyUnreadable } };
491
521
 
492
522
  const crashed = obs.died !== undefined;
493
523
  const runsClean = !crashed && loaded && running && cycles.result === "pass" && soak.result === "pass" && output.result !== "fail";
@@ -542,7 +572,7 @@ export function comparisonRecordOf(
542
572
 
543
573
  /** The row of a host that does not take this plugin's format. */
544
574
  export function notApplicableRow(host: CompareHost, version: string | null, format: string): HostRow {
545
- return { host, version, status: "not-applicable", reason: `${HOST_TRAITS[host].program} takes no ${format.toUpperCase()} plugin` };
575
+ return { host, version, status: "not-applicable", reason: { code: TEXT_CODE.compareFormatNotTaken, args: { program: HOST_TRAITS[host].program, format: format.toUpperCase() } } };
546
576
  }
547
577
 
548
578
  /** Problems with a record's shape, empty when it is one: the records repository's check holds the same rules. */
@@ -567,7 +597,7 @@ export interface HostTotals {
567
597
  readonly host: CompareHost;
568
598
  readonly version: string | null;
569
599
  readonly status: "ran" | "skipped";
570
- readonly reason?: string;
600
+ readonly reason?: ReasonText;
571
601
  /** Plugins of a format the host takes. */
572
602
  readonly plugins: number;
573
603
  readonly loads: number;
@@ -597,7 +627,7 @@ export interface ComparisonSummary {
597
627
  readonly set: string | null;
598
628
  readonly measured: string | null;
599
629
  readonly publishable: boolean;
600
- readonly missing: readonly { host: CompareHost; reason: string }[];
630
+ readonly missing: readonly { host: CompareHost; reason: ReasonText }[];
601
631
  readonly conditions: ComparisonConditions | null;
602
632
  readonly records: number;
603
633
  /** Plugins every host that ran takes and ran: the like-for-like set the headline counts. */
@@ -636,7 +666,7 @@ export function summarise(
636
666
  host: h,
637
667
  version,
638
668
  status: skipped ? "skipped" : "ran",
639
- ...(skipped ? { reason: p?.reason ?? `${HOST_TRAITS[h].program} is not installed` } : {}),
669
+ ...(skipped ? { reason: p?.reason ?? { code: TEXT_CODE.compareHostNotInstalled, args: { program: HOST_TRAITS[h].program } } } : {}),
640
670
  plugins: rows.length,
641
671
  loads: count((r) => r.checks?.loads.result === "pass"),
642
672
  instantiates: count((r) => r.checks?.instantiates.result === "pass"),
@@ -681,19 +711,29 @@ export function summarise(
681
711
  };
682
712
  }
683
713
 
684
- /** The text table of a summary, for the terminal. */
685
- export function summaryTable(s: ComparisonSummary): string {
714
+ /** The text table of a summary, for the terminal, in `lang` (the catalog's sentence, else the English source). */
715
+ export function summaryTable(s: ComparisonSummary, lang?: string): string {
686
716
  const lines = [`host version plugins loads clean rt-safe/observed crashes cpu%`];
687
717
  for (const t of s.hosts) {
688
718
  if (t.status === "skipped") {
689
- lines.push(`${t.host.padEnd(15)} skipped: ${t.reason ?? ""}`);
719
+ lines.push(`${t.host.padEnd(15)} ${reasonText({ code: TEXT_CODE.compareSkippedReason, args: { reason: t.reason === undefined ? "" : reasonText(t.reason, lang) } }, lang)}`);
690
720
  continue;
691
721
  }
692
722
  lines.push(
693
723
  `${t.host.padEnd(15)} ${(t.version ?? "?").padEnd(27)} ${String(t.plugins).padStart(7)} ${String(t.loads).padStart(5)} ${String(t.runsClean).padStart(5)} ${`${String(t.rtSafe)}/${String(t.rtObserved)}`.padStart(16)} ${String(t.crashes).padStart(7)} ${t.cpuMedianPercent === null ? "-" : String(t.cpuMedianPercent)}`,
694
724
  );
695
725
  }
696
- for (const [h, list] of Object.entries(s.beats)) if (list.length) lines.push(`${h} does better on ${String(list.length)}: ${list.map((b) => `${b.id} (${b.on} over ${b.over.join(",")})`).join("; ")}`);
697
- if (!s.publishable) lines.push(`not publishable${s.missing.length ? `: ${s.missing.map((m) => `${m.host}: ${m.reason}`).join("; ")}` : " (no --publish)"}`);
726
+ for (const [h, list] of Object.entries(s.beats)) {
727
+ if (!list.length) continue;
728
+ const detail = list.map((b) => reasonText({ code: TEXT_CODE.compareBeatDescription, args: { id: b.id, on: b.on, over: b.over.join(",") } }, lang)).join("; ");
729
+ lines.push(reasonText({ code: TEXT_CODE.compareDoesBetterOn, args: { host: h, count: list.length, detail } }, lang));
730
+ }
731
+ if (!s.publishable) {
732
+ lines.push(
733
+ s.missing.length
734
+ ? reasonText({ code: TEXT_CODE.compareNotPublishableWithMissing, args: { missing: s.missing.map((m) => `${m.host}: ${reasonText(m.reason, lang)}`).join("; ") } }, lang)
735
+ : reasonText({ code: TEXT_CODE.compareNotPublishableNoFlag }, lang),
736
+ );
737
+ }
698
738
  return `${lines.join("\n")}\n`;
699
739
  }
@@ -11,6 +11,7 @@
11
11
  import { spawnSync } from "node:child_process";
12
12
  import { fileURLToPath } from "node:url";
13
13
 
14
+ import { withRunHome } from "./run-home.js";
14
15
  import type { HostProfile } from "./host-profiles.js";
15
16
  import { ADMIT_STEP_MS_ENV } from "./qualify-declarations.js";
16
17
 
@@ -83,7 +84,17 @@ export interface ModHostRun {
83
84
  * not be read. Absent for a plugin that is no instrument, and for any plugin under mod-host.
84
85
  */
85
86
  /** `notes`: how many keys were played; a run from when one key was played has none. */
86
- readonly sound?: { readonly peak?: number; readonly notes?: number; readonly problem?: string };
87
+ readonly sound?: { readonly peak?: number; readonly notes?: number; readonly problem?: string; readonly content?: AdapterContent };
88
+ }
89
+
90
+ /**
91
+ * The content an instrument declares it plays from (its `patch:writable` parameters of range `atom:Path`, `lv2ContentParams`)
92
+ * and what came of giving each a short sample before the notes: `loaded` the host took, `refused` it did not (with its answer).
93
+ */
94
+ export interface AdapterContent {
95
+ readonly params: readonly string[];
96
+ readonly loaded: readonly string[];
97
+ readonly refused?: readonly string[];
87
98
  }
88
99
 
89
100
  /** At or under this magnitude (-120 dBFS) a held note is taken as digital silence: clap-qualify's own SILENCE. */
@@ -97,6 +108,22 @@ export const SILENT_PEAK = 1e-6;
97
108
  export const ADAPTER_SILENT_CODE = `${ADAPTER_HOST}.silent-under-note`;
98
109
  export const ADAPTER_LEVEL_UNMEASURED_CODE = `${ADAPTER_HOST}.level-unmeasured`;
99
110
  const UNPROVEN_SOUND: readonly string[] = [ADAPTER_SILENT_CODE, ADAPTER_LEVEL_UNMEASURED_CODE];
111
+ /**
112
+ * An instrument that declares content it plays from and stayed silent under the notes even so: the content could not be
113
+ * given (the host refused the sample) or the sample did not reach the keys played. Its silence says nothing of the plugin,
114
+ * so the note test is not observable for it: the run admits it, and the record names this code and the content (#318).
115
+ * No other host is asked instead.
116
+ */
117
+ export const ADAPTER_CONTENT_UNOBSERVED_CODE = `${ADAPTER_HOST}.silent-needs-content`;
118
+
119
+ /** The run's content, as a reason's params carry it: the declared parameters, the ones loaded, the ones refused. */
120
+ function contentParams(content: AdapterContent): Record<string, string> {
121
+ return {
122
+ content: content.params.join(" "),
123
+ loaded: content.loaded.join(" "),
124
+ ...(content.refused === undefined || content.refused.length === 0 ? {} : { refused: content.refused.join("; ") }),
125
+ };
126
+ }
100
127
 
101
128
  /** The rating a run that did not admit the plugin gives its host's verdict. */
102
129
  export function admissionRating(admission: HostAdmission): "unsuitable" | "unknown" {
@@ -109,6 +136,19 @@ export function admissionRating(admission: HostAdmission): "unsuitable" | "unkno
109
136
  */
110
137
  const JACK_CLIENT_RESPS: readonly number[] = [-201, -202];
111
138
 
139
+ /** The name of the reason a run stops on when no JACK server answers: the admission cannot say anything of any plugin. */
140
+ export const NO_JACK_SERVER = "no JACK server";
141
+
142
+ /**
143
+ * The refusal to go on when an admission found no JACK client to have (`<host>.no-jack-client`): the server the host is
144
+ * pointed at (the default one, or `JACK_DEFAULT_SERVER`) is not there, and every plugin would be recorded with the same
145
+ * resp -201 as if it were the plugin's. `undefined` for any other run.
146
+ */
147
+ export function noJackServerProblem(admission: HostAdmission | undefined): string | undefined {
148
+ if (admission === undefined || admission.measured || !admission.code?.endsWith(".no-jack-client")) return undefined;
149
+ return `${NO_JACK_SERVER}: ${admission.host} could not open a JACK client (resp ${String(admission.params["resp"] ?? "-201")}), so nothing is learned of any plugin; start the JACK server it is pointed at (JACK_DEFAULT_SERVER names it) and run again`;
150
+ }
151
+
112
152
  /** The verdict of a run of a host that speaks mod-host's protocol: the first step that did not answer `resp 0` decides. */
113
153
  export function classifyProtocolRun(host: ProtocolHost, run: ModHostRun): HostAdmission {
114
154
  if (!run.installed) return notInstalled(host);
@@ -128,7 +168,14 @@ export function classifyProtocolRun(host: ProtocolHost, run: ModHostRun): HostAd
128
168
  if (run.sound !== undefined) {
129
169
  const { peak, problem } = run.sound;
130
170
  if (peak === undefined) return { host, measured: true, admitted: false, code: ADAPTER_LEVEL_UNMEASURED_CODE, params: { problem: problem ?? "no figure" } };
131
- if (!(peak > SILENT_PEAK)) return { host, measured: true, admitted: false, code: ADAPTER_SILENT_CODE, params: { peak, ...(run.sound.notes === undefined ? {} : { notes: run.sound.notes }) } };
171
+ const notes: Record<string, number> = run.sound.notes === undefined ? {} : { notes: run.sound.notes };
172
+ const content = run.sound.content;
173
+ if (!(peak > SILENT_PEAK)) {
174
+ if (content !== undefined && content.params.length > 0) return { host, measured: true, admitted: true, code: ADAPTER_CONTENT_UNOBSERVED_CODE, params: { peak, ...notes, ...contentParams(content) } };
175
+ return { host, measured: true, admitted: false, code: ADAPTER_SILENT_CODE, params: { peak, ...notes } };
176
+ }
177
+ // it sounded on the content it was given: the record says which
178
+ if (content !== undefined && content.loaded.length > 0) return { ...admitted(host), params: { peak, ...notes, ...contentParams(content) } };
132
179
  }
133
180
  return admitted(host);
134
181
  }
@@ -186,14 +233,14 @@ export const SPAWNED_ADMISSIONS: AdmissionRuns = {
186
233
  // two commands at the limit the pass gives each, and the tool's own start and end: it kills the host itself first
187
234
  const stepMs = Number(env?.[ADMIT_STEP_MS_ENV] ?? 0);
188
235
  const timeout = stepMs > 0 ? 2 * stepMs + 15000 : MOD_HOST_ADMISSION_MS;
189
- const r = spawnSync(process.execPath, [MOD_HOST_ADMIT, uri, ADAPTER_HOST], { encoding: "utf8", timeout, killSignal: "SIGKILL", ...(env === undefined ? {} : { env: { ...env } }) });
236
+ const r = spawnSync(process.execPath, [MOD_HOST_ADMIT, uri, ADAPTER_HOST], { encoding: "utf8", timeout, killSignal: "SIGKILL", env: withRunHome(env ?? process.env) });
190
237
  if (r.error !== undefined || r.status !== 0) {
191
238
  throw new Error(`${ADAPTER_HOST} admission of ${uri} gave no answer (${r.error?.message ?? `exit ${String(r.status)}`}): ${(r.stderr ?? "").trim()}`);
192
239
  }
193
240
  return JSON.parse(r.stdout) as ModHostRun;
194
241
  },
195
242
  modHost(uri, env) {
196
- const r = spawnSync(process.execPath, [MOD_HOST_ADMIT, uri], { encoding: "utf8", timeout: MOD_HOST_ADMISSION_MS, ...(env === undefined ? {} : { env: { ...env } }) });
243
+ const r = spawnSync(process.execPath, [MOD_HOST_ADMIT, uri], { encoding: "utf8", timeout: MOD_HOST_ADMISSION_MS, env: withRunHome(env ?? process.env) });
197
244
  if (r.error !== undefined || r.status !== 0) {
198
245
  throw new Error(`mod-host admission of ${uri} gave no answer (${r.error?.message ?? `exit ${String(r.status)}`}): ${(r.stderr ?? "").trim()}`);
199
246
  }
@@ -203,7 +250,7 @@ export const SPAWNED_ADMISSIONS: AdmissionRuns = {
203
250
  // SIGKILL at the deadline: jalv hosting some plugins ignores SIGTERM, and one stuck in jack_client_open never reaches
204
251
  // the loop that reads the flag TERM sets, so a TERM left spawnSync waiting with it (#187). Alive at the deadline is
205
252
  // all the verdict asks, so nothing is lost by not letting it exit cleanly.
206
- const r = spawnSync("jalv", ["-n", "plugin-qualify", uri], { encoding: "utf8", timeout: JALV_HOSTED_MS, killSignal: "SIGKILL", input: "", ...(env === undefined ? {} : { env: { ...env } }) });
253
+ const r = spawnSync("jalv", ["-n", "plugin-qualify", uri], { encoding: "utf8", timeout: JALV_HOSTED_MS, killSignal: "SIGKILL", input: "", env: withRunHome(env ?? process.env) });
207
254
  const code = (r.error as NodeJS.ErrnoException | undefined)?.code;
208
255
  if (code === "ENOENT") return { installed: false, stillRunning: false, status: null, signal: null, stderr: "" };
209
256
  return { installed: true, stillRunning: code === "ETIMEDOUT", status: r.status, signal: code === "ETIMEDOUT" ? null : r.signal, stderr: r.stderr ?? "" };
@@ -12,6 +12,8 @@ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
12
12
  import { tmpdir } from "node:os";
13
13
  import { join } from "node:path";
14
14
 
15
+ import { TEXT_CODE, type ReasonText } from "./text-codes.js";
16
+
15
17
  /** The daemon binary, else `plugin-hostd` on the PATH. */
16
18
  export const HOSTD_ENV = "PLUGIN_HOSTD";
17
19
 
@@ -41,9 +43,12 @@ export const SPAWNED_PIN_FILES: PinFilesRun = (uri, lv2Dirs, env) => {
41
43
 
42
44
  export type PinFiles =
43
45
  | { readonly ok: true; readonly root: string; readonly files: readonly { readonly path: string; readonly sha256: string }[] }
44
- | { readonly ok: false; readonly detail: string };
46
+ | { readonly ok: false; readonly detail: ReasonText };
45
47
 
46
- /** The files plugin-hostd collects for `uri`, each hashed; why not, when it cannot say. */
48
+ /**
49
+ * The files plugin-hostd collects for `uri`, each hashed; why not, when it cannot say: one of this package's codes
50
+ * (`reasonText` renders it), or the daemon's own last line as it wrote it.
51
+ */
47
52
  export function hostdPinFiles(
48
53
  uri: string,
49
54
  lv2Dirs: readonly string[],
@@ -52,11 +57,11 @@ export function hostdPinFiles(
52
57
  read: (path: string) => Buffer = (p) => readFileSync(p),
53
58
  ): PinFiles {
54
59
  const r = run(uri, lv2Dirs, env);
55
- if (r.missing) return { ok: false, detail: "plugin-hostd is not installed: the files of a pin are plugin-hostd's to name" };
60
+ if (r.missing) return { ok: false, detail: { code: TEXT_CODE.pinHostdNotInstalled } };
56
61
  if (r.status !== 0) {
57
- const why = r.stdout.startsWith("Usage:")
58
- ? "this plugin-hostd has no --pin-files (plugin-hostd 0.15.0 or later names the files of a pin)"
59
- : r.stderr.trim().split("\n").pop() || `plugin-hostd --pin-files exited ${String(r.status)}`;
62
+ const why: ReasonText = r.stdout.startsWith("Usage:")
63
+ ? { code: TEXT_CODE.pinHostdNoPinFiles }
64
+ : r.stderr.trim().split("\n").pop() || { code: TEXT_CODE.pinHostdExited, args: { status: String(r.status) } };
60
65
  return { ok: false, detail: why };
61
66
  }
62
67
  let root: string | undefined;
@@ -65,13 +70,13 @@ export function hostdPinFiles(
65
70
  if (line.startsWith("root ")) root = line.slice(5);
66
71
  else if (line.startsWith("file ")) names.push(line.slice(5));
67
72
  }
68
- if (root === undefined || names.length === 0) return { ok: false, detail: "plugin-hostd --pin-files printed no root or no file" };
73
+ if (root === undefined || names.length === 0) return { ok: false, detail: { code: TEXT_CODE.pinHostdNoRootOrFile } };
69
74
  try {
70
75
  // a name is the word of a pin, percent-encoded when the file's name needs it (Surge XT's libSurge%20XT.so): the pin
71
76
  // keeps it as written, the file is the name it decodes to, as the daemon opens it
72
77
  const files = names.map((path) => ({ path, sha256: createHash("sha256").update(read(join(root!, decodeURIComponent(path)))).digest("hex") }));
73
78
  return { ok: true, root, files: files.sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0)) };
74
79
  } catch (e) {
75
- return { ok: false, detail: `a file plugin-hostd names cannot be read: ${e instanceof Error ? e.message : String(e)}` };
80
+ return { ok: false, detail: { code: TEXT_CODE.pinFileUnreadable, args: { error: e instanceof Error ? e.message : String(e) } } };
76
81
  }
77
82
  }
@@ -0,0 +1,85 @@
1
+ // SPDX-License-Identifier: GPL-3.0-or-later
2
+ // Copyright (C) 2026 Pau Aliagas <linuxnow@gmail.com>
3
+ /**
4
+ * The directories the sync scans, as plugin-hostd searches them (plugin-qualify#9). One definition of the search path:
5
+ * the daemon's, which `plugin-hostd --search-path [-c <file>]` prints from the code it searches with (plugin-hostd 0.20.3
6
+ * and later). This module never works the lists out itself: it runs the daemon on its settings file and reads the
7
+ * `lv2` and `clap` lines, and where each list came from (`file`, `env` or `default`).
8
+ */
9
+ import { spawnSync } from "node:child_process";
10
+ import { HOSTD_ENV } from "./hostd-pin-files.js";
11
+
12
+ /** Where plugin-hostd took a list from: its settings file, its environment (`LV2_PATH`, `CLAP_PATH`) or its default. */
13
+ export type SearchPathSource = "file" | "env" | "default";
14
+
15
+ export interface HostdSearchPath {
16
+ /** The LV2 directories, in the order the daemon searches them: `lv2_path` then `lv2_extra`. */
17
+ readonly lv2: readonly string[];
18
+ /** The CLAP directories, in the order the daemon searches them. */
19
+ readonly clap: readonly string[];
20
+ /** Where each of `lv2_path`, `lv2_extra` and `clap_path` came from. */
21
+ readonly sources: Readonly<Record<"lv2_path" | "lv2_extra" | "clap_path", SearchPathSource>>;
22
+ }
23
+
24
+ export interface SearchPathRunResult {
25
+ readonly status: number | null;
26
+ readonly stdout: string;
27
+ readonly stderr: string;
28
+ /** The daemon is not installed. */
29
+ readonly missing?: boolean;
30
+ }
31
+
32
+ /** One run of `plugin-hostd --search-path`, with `-c <conf>` when a settings file is named. */
33
+ export type SearchPathRun = (conf: string | undefined, env: Readonly<Record<string, string | undefined>>) => SearchPathRunResult;
34
+
35
+ export const SPAWNED_SEARCH_PATH: SearchPathRun = (conf, env) => {
36
+ const r = spawnSync(env[HOSTD_ENV] ?? "plugin-hostd", [...(conf === undefined ? [] : ["-c", conf]), "--search-path"], { encoding: "utf8", timeout: 30000, env: { ...env } });
37
+ const missing = (r.error as NodeJS.ErrnoException | undefined)?.code === "ENOENT";
38
+ return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "", ...(missing ? { missing } : {}) };
39
+ };
40
+
41
+ const SOURCES: readonly string[] = ["file", "env", "default"];
42
+ const dirList = (text: string): string[] => text.split(":").filter((d) => d !== "");
43
+
44
+ /** The lines `plugin-hostd --search-path` prints, read: `<key> TAB <source> TAB <value>`; why not, when one is missing. */
45
+ export function parseSearchPath(stdout: string): { ok: true; path: HostdSearchPath } | { ok: false; error: string } {
46
+ const rows = new Map<string, { source: string; value: string }>();
47
+ for (const line of stdout.split("\n")) {
48
+ const f = line.split("\t");
49
+ if (f.length >= 2 && f[0] !== undefined && f[1] !== undefined) rows.set(f[0], { source: f[1], value: f.slice(2).join("\t") });
50
+ }
51
+ for (const key of ["lv2_path", "lv2_extra", "clap_path"]) {
52
+ const r = rows.get(key);
53
+ if (r === undefined || !SOURCES.includes(r.source)) return { ok: false, error: `plugin-hostd --search-path printed no ${key} with its source` };
54
+ }
55
+ const lv2 = rows.get("lv2");
56
+ const clap = rows.get("clap");
57
+ if (lv2?.source !== "effective" || clap?.source !== "effective") return { ok: false, error: "plugin-hostd --search-path printed no effective lv2 and clap lists" };
58
+ const src = (key: string) => rows.get(key)!.source as SearchPathSource;
59
+ return { ok: true, path: { lv2: dirList(lv2.value), clap: dirList(clap.value), sources: { lv2_path: src("lv2_path"), lv2_extra: src("lv2_extra"), clap_path: src("clap_path") } } };
60
+ }
61
+
62
+ /** The directories plugin-hostd searches with the settings file `conf` (its own default when undefined); why not, when it cannot say. */
63
+ export function hostdSearchPath(
64
+ conf: string | undefined,
65
+ env: Readonly<Record<string, string | undefined>> = process.env,
66
+ run: SearchPathRun = SPAWNED_SEARCH_PATH,
67
+ ): { ok: true; path: HostdSearchPath } | { ok: false; error: string } {
68
+ const r = run(conf, env);
69
+ if (r.missing) return { ok: false, error: "plugin-hostd is not installed: the directories scanned are the ones plugin-hostd searches" };
70
+ if (r.status !== 0) {
71
+ const why = /unrecognized option|^Usage:/m.test(r.stdout + r.stderr)
72
+ ? "this plugin-hostd has no --search-path (plugin-hostd 0.20.3 or later names the directories it searches)"
73
+ : r.stderr.trim().split("\n").pop() || `plugin-hostd --search-path exited ${String(r.status)}`;
74
+ return { ok: false, error: why };
75
+ }
76
+ return parseSearchPath(r.stdout);
77
+ }
78
+
79
+ /** What the run says when a list is not the settings file's: the running daemon, started with another environment, may search other directories. */
80
+ export function searchPathWarnings(path: HostdSearchPath, conf: string | undefined): string[] {
81
+ const where = conf ?? "plugin-hostd's own settings file";
82
+ return (["lv2_path", "clap_path"] as const)
83
+ .filter((k) => path.sources[k] !== "file")
84
+ .map((k) => `${k} is plugin-hostd's ${path.sources[k] === "env" ? `environment (${k === "lv2_path" ? "LV2_PATH" : "CLAP_PATH"})` : "default"}, not ${where}: the running daemon may search other directories; write ${k} in ${where}`);
85
+ }
package/src/index.ts CHANGED
@@ -24,6 +24,7 @@ export * from "./cost-fields.js";
24
24
  export * from "./probe-output.js";
25
25
  export * from "./probe-judge.js";
26
26
  export * from "./qualification-record.js";
27
+ export * from "./record-figures.js";
27
28
  export * from "./judgement-line.js";
28
29
  export * from "./judged-layer.js";
29
30
  export * from "./probe-runner.js";
@@ -42,3 +43,6 @@ export * from "./history-file.js";
42
43
  export * from "./history-adjust.js";
43
44
  export * from "./adjust-cli.js";
44
45
  export * from "./compare.js";
46
+ export * from "./text-codes.js";
47
+ export * from "./text-catalog.js";
48
+ export * from "./refused-stderr-kept.js";
@@ -3,7 +3,7 @@
3
3
  /**
4
4
  * JACK's table of its servers, /dev/shm/jack-shm-registry: a header, then eight rows of a pid and a name
5
5
  * (`jack-<uid>:<server>:`). A server that is killed never clears its row, and once the eight are taken no server starts.
6
- * The adapter pass starts and stops servers of its own, `omx-qualify-<pid>-<quantum>`; a row of one of those whose
6
+ * The adapter pass starts and stops servers of its own, `omx-qualify-<pid>-<rate>-<quantum>`; a row of one of those whose
7
7
  * process is gone is freed (pid 0, name empty) before the next start, as jackd frees its own row when it ends well.
8
8
  * Rows of any other server are never touched, and a file of another layout is left as it is.
9
9
  */