SPL Meter: measure the room, not just watch it #10
@@ -14,9 +14,10 @@
|
|||||||
* { error: '<what is wrong and how to fix it>' } not measuring
|
* { error: '<what is wrong and how to fix it>' } not measuring
|
||||||
*
|
*
|
||||||
* The capture binary is per-machine and gitignored, exactly like the Timers
|
* The capture binary is per-machine and gitignored, exactly like the Timers
|
||||||
* module's ltc-capture: if it is missing this tries one swiftc build of the
|
* module's ltc-capture. It normally arrives already built — compiled beside
|
||||||
* source next to it, and otherwise says how to build it elsewhere and copy
|
* the app, or copied from any Mac of the same architecture. When it is
|
||||||
* it over.
|
* missing this will build it, but in the background and never on the event
|
||||||
|
* loop: see ensureBinary for what that cost the first time it was wrong.
|
||||||
*
|
*
|
||||||
* The same macOS gotcha applies as for LTC, and is worth repeating because
|
* The same macOS gotcha applies as for LTC, and is worth repeating because
|
||||||
* it costs an afternoon every time: a ProdDash launched over SSH or from a
|
* it costs an afternoon every time: a ProdDash launched over SSH or from a
|
||||||
@@ -27,7 +28,7 @@
|
|||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { spawn, spawnSync } = require('child_process');
|
const { spawn } = require('child_process');
|
||||||
|
|
||||||
const RESTART_MS = 5000;
|
const RESTART_MS = 5000;
|
||||||
/** An immediate exit is config-shaped (bad device, channel out of range) —
|
/** An immediate exit is config-shaped (bad device, channel out of range) —
|
||||||
@@ -52,21 +53,64 @@ function createSplListener(opts) {
|
|||||||
let stallTimer = null;
|
let stallTimer = null;
|
||||||
let startedAt = 0;
|
let startedAt = 0;
|
||||||
let meta = null;
|
let meta = null;
|
||||||
|
let building = false;
|
||||||
|
let buildFailed = false;
|
||||||
let lastFrameWall = 0;
|
let lastFrameWall = 0;
|
||||||
|
|
||||||
|
const BUILD_HELP = 'Sound level capture tool missing — build it on any Apple Silicon Mac with '
|
||||||
|
+ '`swiftc -O -o modules/spl-meter/spl-capture modules/spl-meter/spl-capture.swift` '
|
||||||
|
+ 'and copy it next to spl-capture.swift';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build the capture tool if it is missing, WITHOUT blocking.
|
||||||
|
*
|
||||||
|
* This used to be a spawnSync, copied from the LTC listener where the
|
||||||
|
* source is a third the size and builds in a few seconds. Here it took
|
||||||
|
* nearly two minutes on the production Mac and froze the entire ProdDash
|
||||||
|
* server for every second of it — one module's optional helper took the
|
||||||
|
* whole dashboard down, then did it again on every retry. Nothing that
|
||||||
|
* runs while a service might be on is allowed to hold the event loop.
|
||||||
|
*
|
||||||
|
* The build is a fallback for a module installed from the catalog on a
|
||||||
|
* machine with a Swift toolchain. The normal path is a binary built
|
||||||
|
* alongside the app, or copied from any same-architecture Mac.
|
||||||
|
*
|
||||||
|
* @returns {string} '' when the binary is ready, otherwise what to say.
|
||||||
|
*/
|
||||||
function ensureBinary() {
|
function ensureBinary() {
|
||||||
if (fs.existsSync(bin)) return '';
|
if (fs.existsSync(bin)) return '';
|
||||||
const src = bin + '.swift';
|
const src = bin + '.swift';
|
||||||
if (!fs.existsSync(src)) return 'spl-capture.swift is missing from the module folder';
|
if (!fs.existsSync(src)) return 'spl-capture.swift is missing from the module folder';
|
||||||
log('spl-capture binary missing — trying a one-time swiftc build');
|
if (building) return 'Building the capture tool — this takes a minute or two';
|
||||||
const built = spawnSync('swiftc', ['-O', '-o', bin, src], { timeout: 180000 });
|
if (buildFailed) return BUILD_HELP;
|
||||||
if (built.status === 0 && fs.existsSync(bin)) {
|
|
||||||
log('spl-capture compiled');
|
building = true;
|
||||||
return '';
|
log('spl-capture binary missing — building it in the background');
|
||||||
}
|
const build = spawn('swiftc', ['-O', '-o', bin, src], { stdio: ['ignore', 'ignore', 'pipe'] });
|
||||||
return 'Sound level capture tool missing — build it on any Apple Silicon Mac with '
|
let buildErr = '';
|
||||||
+ '`swiftc -O -o modules/spl-meter/spl-capture modules/spl-meter/spl-capture.swift` '
|
build.stderr.setEncoding('utf8');
|
||||||
+ 'and copy it here';
|
build.stderr.on('data', (t) => { buildErr = (buildErr + t).slice(-800); });
|
||||||
|
const giveUp = setTimeout(() => {
|
||||||
|
try { build.kill('SIGKILL'); } catch { /* already gone */ }
|
||||||
|
}, 300000);
|
||||||
|
build.on('error', (err) => {
|
||||||
|
clearTimeout(giveUp);
|
||||||
|
building = false; buildFailed = true;
|
||||||
|
log(`swiftc could not be run: ${err.message}`);
|
||||||
|
});
|
||||||
|
build.on('exit', (code) => {
|
||||||
|
clearTimeout(giveUp);
|
||||||
|
building = false;
|
||||||
|
if (code === 0 && fs.existsSync(bin)) {
|
||||||
|
log('spl-capture built');
|
||||||
|
buildFailed = false;
|
||||||
|
if (!stopped) { clearTimeout(restartTimer); restartTimer = null; start(); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
buildFailed = true;
|
||||||
|
log(`spl-capture build failed (exit ${code})` + (buildErr ? `: ${buildErr.trim().split('\n').pop()}` : ''));
|
||||||
|
});
|
||||||
|
return 'Building the capture tool — this takes a minute or two';
|
||||||
}
|
}
|
||||||
|
|
||||||
function scheduleRestart(ms) {
|
function scheduleRestart(ms) {
|
||||||
@@ -91,7 +135,9 @@ function createSplListener(opts) {
|
|||||||
const missing = ensureBinary();
|
const missing = ensureBinary();
|
||||||
if (missing) {
|
if (missing) {
|
||||||
onUpdate({ error: missing });
|
onUpdate({ error: missing });
|
||||||
scheduleRestart(BAD_CONFIG_RETRY_MS);
|
// A build in flight calls start() itself when it lands; anything else
|
||||||
|
// is config-shaped and should back off hard rather than spin.
|
||||||
|
if (!building) scheduleRestart(BAD_CONFIG_RETRY_MS);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user