mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-09-12 21:38:11 +00:00
test(contracts): Phase 0.a contract snapshots for audio surface
Snapshot the three public surfaces the audio-engine decomposition must not change (docs/audio-engine-tlc.md Part IV §4): addon export table, audio-bridge IPC channels, preload audio/audioEffects API keys. contract-check.test.js diffs regenerated surfaces against the committed snapshots. result-shapes.json (golden result key/type shapes) is deferred until the engine_units harness can run the addon against a null device. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
745e360c89
commit
bbb3b58db8
@@ -0,0 +1,34 @@
|
|||||||
|
// Phase 0.a gate (docs/audio-engine-tlc.md §4): the public audio surface —
|
||||||
|
// addon exports, IPC channels, preload API keys — must not change during the
|
||||||
|
// decomposition phases. Removals/renames fail here; deliberate additions
|
||||||
|
// require regenerating the snapshots in the same commit:
|
||||||
|
// node tests/contracts/extract.js
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const test = require('node:test');
|
||||||
|
const assert = require('node:assert');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const { extractAddonExports, extractIpcChannels, extractPreloadApi } = require('./contracts/extract.js');
|
||||||
|
|
||||||
|
function loadSnapshot(name) {
|
||||||
|
return JSON.parse(fs.readFileSync(path.join(__dirname, 'contracts', name), 'utf8'));
|
||||||
|
}
|
||||||
|
|
||||||
|
test('addon export table matches snapshot', (t) => {
|
||||||
|
const current = extractAddonExports();
|
||||||
|
if (!current) {
|
||||||
|
t.skip('slopsmith_audio.node not built');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
assert.deepStrictEqual(current, loadSnapshot('addon-exports.json'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('audio-bridge IPC channels match snapshot', () => {
|
||||||
|
assert.deepStrictEqual(extractIpcChannels(), loadSnapshot('ipc-channels.json'));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('preload audio/audioEffects API keys match snapshot', () => {
|
||||||
|
assert.deepStrictEqual(extractPreloadApi(), loadSnapshot('preload-audio-api.json'));
|
||||||
|
});
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
[
|
||||||
|
"addSource",
|
||||||
|
"bindInputDevice",
|
||||||
|
"clearChain",
|
||||||
|
"clearStreamOutput",
|
||||||
|
"closePluginEditor",
|
||||||
|
"detectNotes",
|
||||||
|
"enableFileLogging",
|
||||||
|
"getBackingDuration",
|
||||||
|
"getBackingLevel",
|
||||||
|
"getBackingPosition",
|
||||||
|
"getBufferSizes",
|
||||||
|
"getChainState",
|
||||||
|
"getCurrentDevice",
|
||||||
|
"getDeviceMetrics",
|
||||||
|
"getDeviceTypes",
|
||||||
|
"getKnownPlugins",
|
||||||
|
"getLevels",
|
||||||
|
"getNoteVerdicts",
|
||||||
|
"getParameters",
|
||||||
|
"getPitchDetection",
|
||||||
|
"getRawAudioFrame",
|
||||||
|
"getRawPitchDetection",
|
||||||
|
"getRendererBusMetrics",
|
||||||
|
"getSampleRate",
|
||||||
|
"getSampleRates",
|
||||||
|
"getSourceLevels",
|
||||||
|
"getSourceNoteVerdicts",
|
||||||
|
"getSourcePitchDetection",
|
||||||
|
"getSourceRawAudioFrame",
|
||||||
|
"getSourceRawPitchDetection",
|
||||||
|
"getStreamOverflowCount",
|
||||||
|
"getStreamSinkLevel",
|
||||||
|
"getStreamUnderflowCount",
|
||||||
|
"init",
|
||||||
|
"isAudioRunning",
|
||||||
|
"isBackingPlaying",
|
||||||
|
"isMlNoteDetection",
|
||||||
|
"isMonitorMuted",
|
||||||
|
"isStreamOutputActive",
|
||||||
|
"listInputDevices",
|
||||||
|
"listSources",
|
||||||
|
"loadBackingTrack",
|
||||||
|
"loadIR",
|
||||||
|
"loadNAMModel",
|
||||||
|
"loadNoteModel",
|
||||||
|
"loadPluginList",
|
||||||
|
"loadPreset",
|
||||||
|
"loadVST",
|
||||||
|
"moveProcessor",
|
||||||
|
"openPluginEditor",
|
||||||
|
"probeDeviceOptions",
|
||||||
|
"pushRendererAudio",
|
||||||
|
"removeProcessor",
|
||||||
|
"removeSource",
|
||||||
|
"replaceIR",
|
||||||
|
"resetPeaks",
|
||||||
|
"savePluginList",
|
||||||
|
"savePreset",
|
||||||
|
"scanPlugins",
|
||||||
|
"scoreChord",
|
||||||
|
"scoreSourceChord",
|
||||||
|
"seekBacking",
|
||||||
|
"sendMidiToSlot",
|
||||||
|
"setBackingSpeed",
|
||||||
|
"setBranch",
|
||||||
|
"setBranchSrc",
|
||||||
|
"setBypass",
|
||||||
|
"setChart",
|
||||||
|
"setCrashedPlugins",
|
||||||
|
"setDevice",
|
||||||
|
"setDeviceType",
|
||||||
|
"setGain",
|
||||||
|
"setInputChannel",
|
||||||
|
"setInputDeviceType",
|
||||||
|
"setMonitorKill",
|
||||||
|
"setMonitorMute",
|
||||||
|
"setMonitorMuteSuppressed",
|
||||||
|
"setMultiBypass",
|
||||||
|
"setNoiseGate",
|
||||||
|
"setNoteDetectionEnabled",
|
||||||
|
"setOutputDeviceType",
|
||||||
|
"setPan",
|
||||||
|
"setParameter",
|
||||||
|
"setPostGain",
|
||||||
|
"setRendererBus",
|
||||||
|
"setSlotState",
|
||||||
|
"setSourceChart",
|
||||||
|
"setSourceInputChannel",
|
||||||
|
"setSourceMonitorMute",
|
||||||
|
"setSourceVerifierOffset",
|
||||||
|
"setStreamBus",
|
||||||
|
"setStreamBusGain",
|
||||||
|
"setStreamOutputDevice",
|
||||||
|
"setTonePolish",
|
||||||
|
"setVstCrashSentinelPath",
|
||||||
|
"shutdown",
|
||||||
|
"startAudio",
|
||||||
|
"startBacking",
|
||||||
|
"stopAudio",
|
||||||
|
"stopBacking",
|
||||||
|
"unbindInputDevice"
|
||||||
|
]
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
// Contract-surface extraction for the audio engine TLC refactor (Phase 0.a,
|
||||||
|
// docs/audio-engine-tlc.md §4). Each extractor returns a sorted, stable JSON
|
||||||
|
// snapshot of one public surface. contract-check.test.js diffs these against
|
||||||
|
// the committed snapshots so a decomposition phase cannot silently change the
|
||||||
|
// public API. Regenerate deliberately with: node tests/contracts/extract.js
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const repoRoot = path.join(__dirname, '..', '..');
|
||||||
|
|
||||||
|
// Export table of slopsmith_audio.node. Loads the real binary; returns null
|
||||||
|
// when it hasn't been built (contract-check skips with a warning then).
|
||||||
|
function extractAddonExports() {
|
||||||
|
const addonPath = path.join(repoRoot, 'build', 'Release', 'slopsmith_audio.node');
|
||||||
|
if (!fs.existsSync(addonPath)) return null;
|
||||||
|
const addon = require(addonPath);
|
||||||
|
return Object.keys(addon).sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Every ipcMain.handle / ipcMain.on channel registered in audio-bridge.ts.
|
||||||
|
function extractIpcChannels() {
|
||||||
|
const src = fs.readFileSync(path.join(repoRoot, 'src', 'main', 'audio-bridge.ts'), 'utf8');
|
||||||
|
const channels = new Set();
|
||||||
|
const re = /ipcMain\.(?:handle|on)\(\s*'([^']+)'/g;
|
||||||
|
let m;
|
||||||
|
while ((m = re.exec(src)) !== null) channels.add(m[1]);
|
||||||
|
return [...channels].sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Top-level method keys of the `audio:` and `audioEffects:` object literals in
|
||||||
|
// preload.ts — the surface the renderer (and every plugin) programs against.
|
||||||
|
// Brace-depth walk: record keys only at depth 1 inside the target literal.
|
||||||
|
function extractPreloadKeys(objectName, src) {
|
||||||
|
const start = src.indexOf(`${objectName}: {`);
|
||||||
|
if (start === -1) throw new Error(`preload.ts: '${objectName}: {' not found`);
|
||||||
|
let i = src.indexOf('{', start);
|
||||||
|
let depth = 0;
|
||||||
|
let parenDepth = 0; // multi-line parameter lists must not yield keys
|
||||||
|
const keys = [];
|
||||||
|
let lineStart = i;
|
||||||
|
for (; i < src.length; i++) {
|
||||||
|
const c = src[i];
|
||||||
|
if (c === '{') depth++;
|
||||||
|
else if (c === '}') {
|
||||||
|
depth--;
|
||||||
|
if (depth === 0) break;
|
||||||
|
} else if (c === '(') parenDepth++;
|
||||||
|
else if (c === ')') parenDepth--;
|
||||||
|
else if (c === '\n') {
|
||||||
|
lineStart = i + 1;
|
||||||
|
} else if (depth === 1 && parenDepth === 0) {
|
||||||
|
// At a key position: line begins (after whitespace) with `name:`
|
||||||
|
if (i === lineStart) {
|
||||||
|
const line = src.slice(lineStart, src.indexOf('\n', lineStart));
|
||||||
|
const km = line.match(/^\s*([A-Za-z_$][\w$]*)\s*:/);
|
||||||
|
if (km) keys.push(km[1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return keys.sort();
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractPreloadApi() {
|
||||||
|
const src = fs.readFileSync(path.join(repoRoot, 'src', 'main', 'preload.ts'), 'utf8');
|
||||||
|
return {
|
||||||
|
audio: extractPreloadKeys('audio', src),
|
||||||
|
audioEffects: extractPreloadKeys('audioEffects', src),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeSnapshot(name, data) {
|
||||||
|
fs.writeFileSync(path.join(__dirname, name), JSON.stringify(data, null, 2) + '\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { extractAddonExports, extractIpcChannels, extractPreloadApi };
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
const addonExports = extractAddonExports();
|
||||||
|
if (addonExports) writeSnapshot('addon-exports.json', addonExports);
|
||||||
|
else console.warn('addon not built — skipping addon-exports.json');
|
||||||
|
writeSnapshot('ipc-channels.json', extractIpcChannels());
|
||||||
|
writeSnapshot('preload-audio-api.json', extractPreloadApi());
|
||||||
|
console.log('contract snapshots written to tests/contracts/');
|
||||||
|
}
|
||||||
@@ -0,0 +1,107 @@
|
|||||||
|
[
|
||||||
|
"audio-effects:activateSegment",
|
||||||
|
"audio-effects:inspectRoute",
|
||||||
|
"audio-effects:loadChainPlan",
|
||||||
|
"audio-effects:releaseRoute",
|
||||||
|
"audio-effects:setRouteGain",
|
||||||
|
"audio-effects:setStageBypass",
|
||||||
|
"audio-effects:setStageParameter",
|
||||||
|
"audio:addSource",
|
||||||
|
"audio:bindInputDevice",
|
||||||
|
"audio:clearChain",
|
||||||
|
"audio:clearStreamOutput",
|
||||||
|
"audio:closePluginEditor",
|
||||||
|
"audio:detectNotes",
|
||||||
|
"audio:getBackingDuration",
|
||||||
|
"audio:getBackingLevel",
|
||||||
|
"audio:getBackingPosition",
|
||||||
|
"audio:getBufferSizes",
|
||||||
|
"audio:getChainState",
|
||||||
|
"audio:getCurrentDevice",
|
||||||
|
"audio:getDeviceMetrics",
|
||||||
|
"audio:getDeviceTypes",
|
||||||
|
"audio:getKnownPlugins",
|
||||||
|
"audio:getLevels",
|
||||||
|
"audio:getNoteVerdicts",
|
||||||
|
"audio:getParameters",
|
||||||
|
"audio:getPitchDetection",
|
||||||
|
"audio:getRawAudioFrame",
|
||||||
|
"audio:getRawPitch",
|
||||||
|
"audio:getRendererBusMetrics",
|
||||||
|
"audio:getSampleRate",
|
||||||
|
"audio:getSampleRates",
|
||||||
|
"audio:getSourceLevels",
|
||||||
|
"audio:getSourceNoteVerdicts",
|
||||||
|
"audio:getSourcePitchDetection",
|
||||||
|
"audio:getSourceRawAudioFrame",
|
||||||
|
"audio:getSourceRawPitch",
|
||||||
|
"audio:getStreamOverflowCount",
|
||||||
|
"audio:getStreamSinkLevel",
|
||||||
|
"audio:getStreamUnderflowCount",
|
||||||
|
"audio:isAudioRunning",
|
||||||
|
"audio:isAvailable",
|
||||||
|
"audio:isBackingPlaying",
|
||||||
|
"audio:isMlNoteDetection",
|
||||||
|
"audio:isMonitorMuted",
|
||||||
|
"audio:isStreamOutputActive",
|
||||||
|
"audio:listInputDevices",
|
||||||
|
"audio:listSources",
|
||||||
|
"audio:loadBackingTrack",
|
||||||
|
"audio:loadDeviceSettings",
|
||||||
|
"audio:loadIR",
|
||||||
|
"audio:loadNAMModel",
|
||||||
|
"audio:loadPluginList",
|
||||||
|
"audio:loadPreset",
|
||||||
|
"audio:loadVST",
|
||||||
|
"audio:moveProcessor",
|
||||||
|
"audio:openPluginEditor",
|
||||||
|
"audio:probeDeviceOptions",
|
||||||
|
"audio:pushRendererAudio",
|
||||||
|
"audio:removeProcessor",
|
||||||
|
"audio:removeSource",
|
||||||
|
"audio:replaceIR",
|
||||||
|
"audio:resetPeaks",
|
||||||
|
"audio:saveDeviceSettings",
|
||||||
|
"audio:savePluginList",
|
||||||
|
"audio:savePreset",
|
||||||
|
"audio:scanPlugins",
|
||||||
|
"audio:scoreChord",
|
||||||
|
"audio:scoreSourceChord",
|
||||||
|
"audio:seekBacking",
|
||||||
|
"audio:sendMidiToSlot",
|
||||||
|
"audio:setBackingSpeed",
|
||||||
|
"audio:setBranch",
|
||||||
|
"audio:setBranchSrc",
|
||||||
|
"audio:setBypass",
|
||||||
|
"audio:setChart",
|
||||||
|
"audio:setDevice",
|
||||||
|
"audio:setDeviceType",
|
||||||
|
"audio:setGain",
|
||||||
|
"audio:setInputChannel",
|
||||||
|
"audio:setMonitorKill",
|
||||||
|
"audio:setMonitorMute",
|
||||||
|
"audio:setMonitorMuteSuppressed",
|
||||||
|
"audio:setMultiBypass",
|
||||||
|
"audio:setNoiseGate",
|
||||||
|
"audio:setNoteDetectionEnabled",
|
||||||
|
"audio:setOutputDeviceType",
|
||||||
|
"audio:setPan",
|
||||||
|
"audio:setParameter",
|
||||||
|
"audio:setPostGain",
|
||||||
|
"audio:setRendererBus",
|
||||||
|
"audio:setSlotState",
|
||||||
|
"audio:setSourceChart",
|
||||||
|
"audio:setSourceInputChannel",
|
||||||
|
"audio:setSourceMonitorMute",
|
||||||
|
"audio:setSourceVerifierOffset",
|
||||||
|
"audio:setStreamBus",
|
||||||
|
"audio:setStreamBusGain",
|
||||||
|
"audio:setStreamOutputDevice",
|
||||||
|
"audio:setTonePolish",
|
||||||
|
"audio:startAudio",
|
||||||
|
"audio:startBacking",
|
||||||
|
"audio:stopAudio",
|
||||||
|
"audio:stopBacking",
|
||||||
|
"audio:unbindInputDevice",
|
||||||
|
"debug:isEnabled"
|
||||||
|
]
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
{
|
||||||
|
"audio": [
|
||||||
|
"addSource",
|
||||||
|
"bindInputDevice",
|
||||||
|
"clearChain",
|
||||||
|
"clearStreamOutput",
|
||||||
|
"closePluginEditor",
|
||||||
|
"debugEnabled",
|
||||||
|
"detectNotes",
|
||||||
|
"getBackingDuration",
|
||||||
|
"getBackingLevel",
|
||||||
|
"getBackingPosition",
|
||||||
|
"getBufferSizes",
|
||||||
|
"getChainState",
|
||||||
|
"getCurrentDevice",
|
||||||
|
"getDeviceMetrics",
|
||||||
|
"getDeviceTypes",
|
||||||
|
"getKnownPlugins",
|
||||||
|
"getLevels",
|
||||||
|
"getNoteVerdicts",
|
||||||
|
"getParameters",
|
||||||
|
"getPitchDetection",
|
||||||
|
"getRawAudioFrame",
|
||||||
|
"getRawPitch",
|
||||||
|
"getRendererBusMetrics",
|
||||||
|
"getSampleRate",
|
||||||
|
"getSampleRates",
|
||||||
|
"getSourceLevels",
|
||||||
|
"getSourceNoteVerdicts",
|
||||||
|
"getSourcePitchDetection",
|
||||||
|
"getSourceRawAudioFrame",
|
||||||
|
"getSourceRawPitch",
|
||||||
|
"getStreamOverflowCount",
|
||||||
|
"getStreamSinkLevel",
|
||||||
|
"getStreamUnderflowCount",
|
||||||
|
"isAudioRunning",
|
||||||
|
"isAvailable",
|
||||||
|
"isBackingPlaying",
|
||||||
|
"isMlNoteDetection",
|
||||||
|
"isMonitorMuted",
|
||||||
|
"isStreamOutputActive",
|
||||||
|
"listInputDevices",
|
||||||
|
"listSources",
|
||||||
|
"loadBackingTrack",
|
||||||
|
"loadDeviceSettings",
|
||||||
|
"loadIR",
|
||||||
|
"loadNAMModel",
|
||||||
|
"loadPluginList",
|
||||||
|
"loadPreset",
|
||||||
|
"loadVST",
|
||||||
|
"moveProcessor",
|
||||||
|
"openPluginEditor",
|
||||||
|
"probeDeviceOptions",
|
||||||
|
"pushRendererAudio",
|
||||||
|
"removeProcessor",
|
||||||
|
"removeSource",
|
||||||
|
"replaceIR",
|
||||||
|
"resetPeaks",
|
||||||
|
"saveDeviceSettings",
|
||||||
|
"savePluginList",
|
||||||
|
"savePreset",
|
||||||
|
"scanPlugins",
|
||||||
|
"scoreChord",
|
||||||
|
"scoreSourceChord",
|
||||||
|
"seekBacking",
|
||||||
|
"sendMidiToSlot",
|
||||||
|
"setBackingSpeed",
|
||||||
|
"setBranch",
|
||||||
|
"setBranchSrc",
|
||||||
|
"setBypass",
|
||||||
|
"setChart",
|
||||||
|
"setDevice",
|
||||||
|
"setDeviceType",
|
||||||
|
"setGain",
|
||||||
|
"setInputChannel",
|
||||||
|
"setMonitorKill",
|
||||||
|
"setMonitorMute",
|
||||||
|
"setMonitorMuteSuppressed",
|
||||||
|
"setMultiBypass",
|
||||||
|
"setNoiseGate",
|
||||||
|
"setNoteDetectionEnabled",
|
||||||
|
"setOutputDeviceType",
|
||||||
|
"setPageMuted",
|
||||||
|
"setPan",
|
||||||
|
"setParameter",
|
||||||
|
"setPostGain",
|
||||||
|
"setRendererBus",
|
||||||
|
"setSlotState",
|
||||||
|
"setSourceChart",
|
||||||
|
"setSourceInputChannel",
|
||||||
|
"setSourceMonitorMute",
|
||||||
|
"setSourceVerifierOffset",
|
||||||
|
"setStreamBus",
|
||||||
|
"setStreamBusGain",
|
||||||
|
"setStreamOutputDevice",
|
||||||
|
"setTonePolish",
|
||||||
|
"startAudio",
|
||||||
|
"startBacking",
|
||||||
|
"stopAudio",
|
||||||
|
"stopBacking",
|
||||||
|
"unbindInputDevice"
|
||||||
|
],
|
||||||
|
"audioEffects": [
|
||||||
|
"activateSegment",
|
||||||
|
"inspectRoute",
|
||||||
|
"loadChainPlan",
|
||||||
|
"releaseRoute",
|
||||||
|
"setRouteGain",
|
||||||
|
"setStageBypass",
|
||||||
|
"setStageParameter"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user