mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-09-10 23:04:09 +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