@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.
- package/bin/plugin-qualify-sync.mjs +1 -0
- package/catalogs/ca.json +251 -0
- package/catalogs/de.json +49 -0
- package/catalogs/es.json +49 -0
- package/catalogs/fr.json +49 -0
- package/catalogs/it.json +49 -0
- package/catalogs/pt.json +49 -0
- package/dist/clap-loader.js +2 -1
- package/dist/clap-loader.js.map +1 -1
- package/dist/clap-qualify.d.ts +11 -0
- package/dist/clap-qualify.js +16 -0
- package/dist/clap-qualify.js.map +1 -1
- package/dist/compare.d.ts +12 -11
- package/dist/compare.js +71 -38
- package/dist/compare.js.map +1 -1
- package/dist/host-admission.d.ts +25 -0
- package/dist/host-admission.js +41 -5
- package/dist/host-admission.js.map +1 -1
- package/dist/hostd-pin-files.d.ts +6 -2
- package/dist/hostd-pin-files.js +10 -6
- package/dist/hostd-pin-files.js.map +1 -1
- package/dist/hostd-search-path.d.ts +38 -0
- package/dist/hostd-search-path.js +58 -0
- package/dist/hostd-search-path.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/jack-registry.js +1 -1
- package/dist/lv2-loader.d.ts +33 -1
- package/dist/lv2-loader.js +82 -19
- package/dist/lv2-loader.js.map +1 -1
- package/dist/measurement/vocabulary.js +3 -3
- package/dist/measurement/vocabulary.js.map +1 -1
- package/dist/measurement-grid.d.ts +1 -1
- package/dist/measurement-grid.js +5 -3
- package/dist/measurement-grid.js.map +1 -1
- package/dist/pins-file.d.ts +3 -2
- package/dist/pins-file.js +3 -2
- package/dist/pins-file.js.map +1 -1
- package/dist/plugin-db-cli.d.ts +28 -0
- package/dist/plugin-db-cli.js +43 -0
- package/dist/plugin-db-cli.js.map +1 -0
- package/dist/plugin-db-pin.d.ts +8 -0
- package/dist/plugin-db-pin.js +29 -0
- package/dist/plugin-db-pin.js.map +1 -0
- package/dist/probe-judge.js +5 -0
- package/dist/probe-judge.js.map +1 -1
- package/dist/probe-output.d.ts +10 -0
- package/dist/probe-output.js +16 -0
- package/dist/probe-output.js.map +1 -1
- package/dist/probe-runner.js +24 -5
- package/dist/probe-runner.js.map +1 -1
- package/dist/qualification-record.d.ts +5 -0
- package/dist/qualification-record.js +13 -1
- package/dist/qualification-record.js.map +1 -1
- package/dist/qualify-adapter-pass.d.ts +1 -2
- package/dist/qualify-adapter-pass.js +6 -6
- package/dist/qualify-adapter-pass.js.map +1 -1
- package/dist/qualify-declarations.d.ts +23 -6
- package/dist/qualify-declarations.js +25 -6
- package/dist/qualify-declarations.js.map +1 -1
- package/dist/qualify-isolated.d.ts +9 -0
- package/dist/qualify-isolated.js +16 -5
- package/dist/qualify-isolated.js.map +1 -1
- package/dist/qualify-pool.js +4 -3
- package/dist/qualify-pool.js.map +1 -1
- package/dist/qualify-rejudge.js +3 -3
- package/dist/qualify-rejudge.js.map +1 -1
- package/dist/qualify-sync.d.ts +12 -5
- package/dist/qualify-sync.js +51 -30
- package/dist/qualify-sync.js.map +1 -1
- package/dist/record-figures.d.ts +52 -0
- package/dist/record-figures.js +58 -0
- package/dist/record-figures.js.map +1 -0
- package/dist/refused-stderr-kept.d.ts +12 -0
- package/dist/refused-stderr-kept.js +67 -0
- package/dist/refused-stderr-kept.js.map +1 -0
- package/dist/run-home.d.ts +4 -0
- package/dist/run-home.js +26 -0
- package/dist/run-home.js.map +1 -0
- package/dist/text-catalog.d.ts +41 -0
- package/dist/text-catalog.js +97 -0
- package/dist/text-catalog.js.map +1 -0
- package/dist/text-codes.d.ts +29 -0
- package/dist/text-codes.generated.d.ts +88 -0
- package/dist/text-codes.generated.js +96 -0
- package/dist/text-codes.generated.js.map +1 -0
- package/dist/text-codes.js +19 -0
- package/dist/text-codes.js.map +1 -0
- package/package.json +8 -6
- package/schema/codes.json +386 -0
- package/src/clap-loader.ts +2 -1
- package/src/clap-qualify.ts +25 -0
- package/src/compare.ts +85 -45
- package/src/host-admission.ts +52 -5
- package/src/hostd-pin-files.ts +13 -8
- package/src/hostd-search-path.ts +85 -0
- package/src/index.ts +4 -0
- package/src/jack-registry.ts +1 -1
- package/src/lv2-loader.ts +89 -17
- package/src/measurement/vocabulary.ts +3 -3
- package/src/measurement-grid.ts +5 -3
- package/src/pins-file.ts +3 -2
- package/src/plugin-db-cli.ts +78 -0
- package/src/plugin-db-pin.ts +33 -0
- package/src/probe-judge.ts +5 -0
- package/src/probe-output.ts +23 -0
- package/src/probe-runner.ts +25 -4
- package/src/qualification-record.ts +13 -1
- package/src/qualify-adapter-pass.ts +6 -5
- package/src/qualify-declarations.ts +27 -6
- package/src/qualify-isolated.ts +18 -5
- package/src/qualify-pool.ts +4 -3
- package/src/qualify-rejudge.ts +3 -3
- package/src/qualify-sync.ts +55 -33
- package/src/record-figures.ts +114 -0
- package/src/refused-stderr-kept.ts +84 -0
- package/src/run-home.ts +28 -0
- package/src/text-catalog.ts +116 -0
- package/src/text-codes.generated.ts +97 -0
- package/src/text-codes.ts +35 -0
- package/tools/_declarations.py +2 -1
- package/tools/catalogs-gate.sh +22 -0
- package/tools/compare-host.mjs +1 -1
- package/tools/compare-run.mjs +10 -6
- package/tools/descriptors.py +40 -3
- package/tools/gen-python-declarations.mjs +1 -0
- package/tools/gen-text-codes.d.mts +8 -0
- package/tools/gen-text-codes.mjs +77 -0
- package/tools/isolated-step.mjs +24 -3
- package/tools/mod-host-admit.mjs +57 -1
- 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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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?:
|
|
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:
|
|
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:
|
|
245
|
-
if (!p.installed) return [{ host: h, reason: p.reason ??
|
|
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 ??
|
|
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?:
|
|
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?:
|
|
341
|
+
readonly detail?: ReasonText;
|
|
339
342
|
}
|
|
340
343
|
export interface LatencyFigure {
|
|
341
344
|
readonly result: FigureResult;
|
|
342
345
|
readonly frames?: number;
|
|
343
|
-
readonly detail?:
|
|
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?:
|
|
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:
|
|
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:
|
|
417
|
-
if (armed === 0) return { result: "not-observable", detail:
|
|
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
|
|
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
|
|
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 ??
|
|
442
|
-
if (obs.started === false) return { ...base, status: "skipped", reason: obs.problem ??
|
|
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
|
-
: {
|
|
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
|
|
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(
|
|
455
|
-
else if (ports.audioOut === 0) output = { result: "not-observable", detail:
|
|
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)
|
|
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)
|
|
460
|
-
|
|
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:
|
|
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
|
-
: {
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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?:
|
|
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:
|
|
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 ??
|
|
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)}
|
|
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))
|
|
697
|
-
|
|
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
|
}
|
package/src/host-admission.ts
CHANGED
|
@@ -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
|
-
|
|
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",
|
|
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,
|
|
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: "",
|
|
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 ?? "" };
|
package/src/hostd-pin-files.ts
CHANGED
|
@@ -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:
|
|
46
|
+
| { readonly ok: false; readonly detail: ReasonText };
|
|
45
47
|
|
|
46
|
-
/**
|
|
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:
|
|
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
|
-
?
|
|
59
|
-
: r.stderr.trim().split("\n").pop() ||
|
|
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:
|
|
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:
|
|
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";
|
package/src/jack-registry.ts
CHANGED
|
@@ -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
|
*/
|