mirror of
https://github.com/got-feedBack/feedBack.git
synced 2026-07-21 12:21:49 +00:00
* Update GitHub repo references from feedback* to feedBack* * rename: slopsmith -> feedBack, byron -> got-feedBack Renames across the entire codebase: - slopsmith/Slopsmith/SLOPSMITH/SlopSmith -> feedBack/FeedBack/FEEDBACK/FeedBack - byron/Byron/Byrongamatos -> got-feedBack/got-feedBack/got-feedBack - /home/byron/ -> /opt/got-feedBack/ - byron@ougsoft.com -> hi@got-feedBack.org - github.com/byrongamatos/ -> github.com/got-feedback/ - com.byron. -> com.got-feedback. - SLOPSMITH_ env vars -> FEEDBACK_ with backward-compat fallback - Protocol/storage strings migrated with read-old/write-new pattern - window.slopsmith JS API -> window.feedBack (canonical) + backward-compat alias Refs: #rename-slopsmith * rename: complete regen against current main + fix backward-compat alias Regenerated the slopsmith->feedBack / byron->got-feedBack rename on top of current main (3 commits had landed since the branch: #572/#554/#574), resolving the four content conflicts in favour of main's newer content (autoplay/auto-exit, accuracy-badge, Virtuoso re-home, feedpak badge). Completion fixes on top of the mechanical rename: - Re-apply rename to post-branch content the original rename never saw: window.slopsmith(.Tour) consumers in lessons.js / notifications.js / onboarding-tour.js, and the matching JS + python tests (autoplay_exit, progression_*, test_feedpak_extension FEEDBACK_* env vars). The test env vars now match server.py (which reads FEEDBACK_SYNC_STARTUP / FEEDBACK_SKIP_STARTUP_TASKS), so the sync-startup test exercises the real path again. - Restore the window.slopsmith backward-compat alias dropped during conflict resolution, and move the bus aliases to AFTER the _feedBackExisting merge block so they reference the fully-assembled object (also fixes the loop_api.test.js API-surface regex, which the original PR latently broke). - Drop the stray empty data/web_library.db (runtime DB lives in CONFIG_DIR) and gitignore it. - Fix stale tone-source test: feed[dB]ack -> fee[dB]ack to match shipped source labels. Verified locally (org CI billing-blocked): JS 819/819 pass; pytest 1669 passed / 1683 collected with 0 import errors; zero residual slopsmith/byron except the two intentional window.slopsmith aliases. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * rename: implement advertised backward-compat + prune dead community plugins Address gaps where PR #537's "Backward compatibility" section was advertised but not implemented, and clean up the community plugin list. Env vars (FEEDBACK_* canonical, legacy SLOPSMITH_* honoured): - New lib/env_compat.py (getenv_compat / env_flag_compat) + tests. server.py (_env_flag + all FEEDBACK_* reads), diagnostics_hardware, gp2midi and tailwind_rebuild now resolve the legacy alias, so existing SLOPSMITH_UI / SLOPSMITH_PLUGINS_DIR / etc. deployments keep working. - Fix the rename collapsing plugins/__init__.py and minigames/routes.py from `FEEDBACK_PLUGINS_DIR or SLOPSMITH_PLUGINS_DIR` into a redundant `FEEDBACK_ or FEEDBACK_` (the fallback was silently lost). Storage (app.js update-channel): - Read feedBack-update-channel, fall back to legacy slopsmith-update-channel, and clear the legacy key on write — so a user's update-channel preference survives the rename instead of resetting to "stable". Community plugin list (README): the rename rewrote third-party repo URLs we don't own. Probed every one; their owners never renamed, so: - Restore the 13 live community plugins to their real slopsmith-* names. - Prune 6 that are 404 to the public (topkoa splitscreen/stems, OmikronApex tuner, Jafz2001 nam-rig-builder, DeathlySin song-preview, Erikcb91 shuffle). - Fix a pre-existing Guitar Theory clone-command typo (nam-tone -> guitar-theory). Verified: env_compat 7/7, JS 819/819, pytest 1690 collected / 0 import errors, rename-sensitive + startup suites green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: byrongamatos <xasiklas@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
922 lines
42 KiB
JavaScript
922 lines
42 KiB
JavaScript
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const { loadAudioEffects, diagnosticsSnapshot } = require('./audio_effects_test_harness');
|
|
|
|
function registerProvider(api, overrides = {}) {
|
|
return api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-provider',
|
|
source: 'test',
|
|
payload: {
|
|
providerId: overrides.providerId || 'rig-builder',
|
|
pluginId: overrides.pluginId || 'rig_builder',
|
|
routeKey: overrides.routeKey || 'desktop-main',
|
|
priority: overrides.priority == null ? 40 : overrides.priority,
|
|
operations: overrides.operations || ['chain.resolve', 'segment.activate', 'stage.set-bypass', 'stage.set-parameter'],
|
|
requests: overrides.requests || [],
|
|
operationHandlers: overrides.operationHandlers || {
|
|
'chain.resolve': () => ({
|
|
outcome: 'handled',
|
|
plan: {
|
|
schema: 'feedBack.audio_effects.chain_plan.v1',
|
|
planId: 'plan-1',
|
|
routeKey: 'desktop-main',
|
|
providerId: overrides.providerId || 'rig-builder',
|
|
stages: [
|
|
{ stageId: 'pre-1', kind: 'nam', role: 'pre-pedal', assetRef: 'provider:nam:pre', bypassed: false },
|
|
{ stageId: 'amp-1', kind: 'nam', role: 'amp', assetRef: 'provider:nam:amp', bypassed: false },
|
|
{ stageId: 'cab-1', kind: 'ir', role: 'cab', assetRef: 'provider:ir:cab', bypassed: false },
|
|
],
|
|
segments: [{ segmentId: 'ToneA', stageIds: ['pre-1', 'amp-1', 'cab-1'], stageBypass: { 'pre-1': true, 'amp-1': false } }],
|
|
summary: { stageCount: 3, kinds: ['nam', 'ir'], assetRefs: ['provider:nam:amp'] },
|
|
},
|
|
summary: { stageCount: 3, categoryCount: 3, assetRefs: ['provider:nam:amp'] },
|
|
}),
|
|
'segment.activate': () => ({ outcome: 'handled', summary: { active: true } }),
|
|
'stage.set-bypass': () => ({ outcome: 'handled', summary: { bypassed: true } }),
|
|
'stage.set-parameter': () => ({ outcome: 'handled', summary: { changed: true } }),
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
test('audio-effects host registers active domain and contributes diagnostics', () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
const pipeline = api.inspect('audio-effects');
|
|
const diagnostics = window.feedBack.diagnostics.snapshotContributions();
|
|
|
|
assert.equal(pipeline.review.lifecycle, 'active');
|
|
assert.equal(pipeline.participants.some(p => p.pluginId === 'core.audio.effects' && p.roles.includes('owner')), true);
|
|
assert.equal(diagnostics['audio-effects'].schema, 'feedBack.audio_effects.diagnostics.v1');
|
|
});
|
|
|
|
test('audio-effects runtime providers and executors are capability participants', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
|
|
await registerProvider(api, {
|
|
providerId: 'nam-tone',
|
|
pluginId: 'nam-tone',
|
|
routeKey: 'live-guitar',
|
|
operations: ['chain.resolve', 'chain.inspect', 'stage.set-bypass'],
|
|
requests: ['list-mappings', 'upsert-mapping'],
|
|
});
|
|
const executor = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'test',
|
|
payload: {
|
|
executorId: 'nam-tone-browser-wasm',
|
|
pluginId: 'nam-tone',
|
|
routeKey: 'live-guitar',
|
|
providerIds: ['nam-tone'],
|
|
supportedKinds: ['nam', 'ir'],
|
|
operations: ['loadChainPlan'],
|
|
},
|
|
});
|
|
const pipeline = api.inspect('audio-effects');
|
|
const participant = pipeline.participants.find(item => item.pluginId === 'nam-tone');
|
|
|
|
assert.equal(executor.outcome, 'handled');
|
|
assert.ok(participant);
|
|
assert.deepEqual([...participant.roles].sort(), ['executor', 'provider', 'requester']);
|
|
assert.equal(participant.runtime, true);
|
|
assert.equal(participant.safety, 'sensitive');
|
|
assert.equal(participant.ownership, 'multi-provider');
|
|
assert.deepEqual(Array.from(participant.requests), ['list-mappings', 'upsert-mapping']);
|
|
assert.equal(participant.operations.includes('chain.resolve'), true);
|
|
assert.equal(participant.operations.includes('executor.load-chain-plan'), true);
|
|
});
|
|
|
|
test('unregistering a provider drops its role/operations and clearing all removes the participant', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
|
|
await registerProvider(api, {
|
|
providerId: 'nam-tone',
|
|
pluginId: 'nam-tone',
|
|
routeKey: 'live-guitar',
|
|
operations: ['chain.resolve', 'chain.inspect', 'stage.set-bypass'],
|
|
requests: ['list-mappings'],
|
|
});
|
|
await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'test',
|
|
payload: {
|
|
executorId: 'nam-tone-browser-wasm',
|
|
pluginId: 'nam-tone',
|
|
routeKey: 'live-guitar',
|
|
providerIds: ['nam-tone'],
|
|
supportedKinds: ['nam', 'ir'],
|
|
operations: ['loadChainPlan'],
|
|
},
|
|
});
|
|
|
|
// Unregister only the provider; the executor for the same plugin remains.
|
|
await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'unregister-provider',
|
|
source: 'test',
|
|
payload: { providerId: 'nam-tone', pluginId: 'nam-tone' },
|
|
});
|
|
|
|
const afterProvider = api.inspect('audio-effects').participants.find(item => item.pluginId === 'nam-tone');
|
|
assert.ok(afterProvider, 'participant should survive while the executor is still registered');
|
|
// The merge-only registry would otherwise keep the stale provider role/operations/requests.
|
|
assert.deepEqual([...afterProvider.roles].sort(), ['executor']);
|
|
assert.equal(afterProvider.operations.includes('chain.resolve'), false);
|
|
assert.equal(afterProvider.operations.includes('chain.inspect'), false);
|
|
assert.equal(afterProvider.operations.includes('executor.load-chain-plan'), true);
|
|
assert.deepEqual(Array.from(afterProvider.requests), []);
|
|
|
|
// Removing the last executor must remove the participant entirely.
|
|
await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'unregister-executor',
|
|
source: 'test',
|
|
payload: { executorId: 'nam-tone-browser-wasm', pluginId: 'nam-tone' },
|
|
});
|
|
const afterAll = api.inspect('audio-effects').participants.find(item => item.pluginId === 'nam-tone');
|
|
assert.equal(afterAll, undefined, 'participant should be removed once no providers or executors remain');
|
|
});
|
|
|
|
test('host runtime overlay preserves a plugin-declared audio-effects manifest entry', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
|
|
// The plugin declares its own audio-effects participancy in its manifest before any runtime
|
|
// registration goes through the host.
|
|
api.registerParticipant('rig_builder', {
|
|
'audio-effects': {
|
|
roles: ['observer'],
|
|
operations: ['chain.inspect'],
|
|
description: 'Manifest-declared audio-effects observer.',
|
|
},
|
|
});
|
|
|
|
await registerProvider(api, { providerId: 'rig-builder', pluginId: 'rig_builder', routeKey: 'desktop-main' });
|
|
|
|
const withProvider = api.inspect('audio-effects').participants.find(item => item.pluginId === 'rig_builder');
|
|
assert.ok(withProvider);
|
|
// Manifest role/operation survive alongside the host-added provider overlay.
|
|
assert.equal(withProvider.roles.includes('observer'), true, 'manifest role must survive host sync');
|
|
assert.equal(withProvider.roles.includes('provider'), true);
|
|
assert.equal(withProvider.operations.includes('chain.inspect'), true);
|
|
|
|
// Unregistering the runtime provider must leave the manifest declaration intact, not wipe it.
|
|
await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'unregister-provider',
|
|
source: 'test',
|
|
payload: { providerId: 'rig-builder', pluginId: 'rig_builder' },
|
|
});
|
|
const afterProvider = api.inspect('audio-effects').participants.find(item => item.pluginId === 'rig_builder');
|
|
assert.ok(afterProvider, 'manifest-declared participant must remain after the host overlay is removed');
|
|
assert.deepEqual([...afterProvider.roles].sort(), ['observer']);
|
|
assert.equal(afterProvider.operations.includes('chain.inspect'), true);
|
|
assert.equal(afterProvider.roles.includes('provider'), false, 'stale host role must be cleared');
|
|
});
|
|
|
|
test('select-chain requires user action and records selected provider', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api);
|
|
|
|
const denied = await api.dispatch({ capability: 'audio-effects', command: 'select-chain', source: 'test', payload: { routeKey: 'desktop-main' } });
|
|
const selected = await api.dispatch({ capability: 'audio-effects', command: 'select-chain', source: 'test', payload: { routeKey: 'desktop-main', authorization: 'user-action', requesterId: 'spoofed-plugin' } });
|
|
const route = await api.dispatch({ capability: 'audio-effects', command: 'inspect-route', source: 'test', payload: { routeKey: 'desktop-main' } });
|
|
|
|
assert.equal(denied.outcome, 'user-action-required');
|
|
assert.equal(selected.outcome, 'handled');
|
|
assert.equal(selected.payload.route.providerId, 'rig-builder');
|
|
assert.equal(route.payload.route.state, 'selected');
|
|
assert.equal(JSON.stringify(diagnosticsSnapshot(window)).includes('spoofed-plugin'), false);
|
|
});
|
|
|
|
test('resolve-plan calls selected provider and returns constrained plan without storing raw payload in diagnostics', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api);
|
|
await api.dispatch({ capability: 'audio-effects', command: 'select-chain', source: 'test', payload: { routeKey: 'desktop-main', authorization: 'user-action' } });
|
|
|
|
const resolved = await api.dispatch({ capability: 'audio-effects', command: 'resolve-plan', source: 'nam_tone', payload: { routeKey: 'desktop-main', target: { settingsKey: 'settings-v1-abc1234' } } });
|
|
const snapshot = diagnosticsSnapshot(window);
|
|
const encoded = JSON.stringify(snapshot);
|
|
|
|
assert.equal(resolved.outcome, 'handled');
|
|
assert.equal(resolved.payload.plan.schema, 'feedBack.audio_effects.chain_plan.v1');
|
|
assert.equal(resolved.payload.plan.stages.length, 3);
|
|
assert.deepEqual(JSON.parse(JSON.stringify(resolved.payload.plan.segments[0].stageBypass)), { 'pre-1': true, 'amp-1': false });
|
|
assert.equal(snapshot.routes[0].state, 'resolved');
|
|
assert.equal(snapshot.routes[0].activePlanId, 'plan-1');
|
|
assert.equal(encoded.includes('provider:nam:amp'), false);
|
|
assert.equal(encoded.includes('assetRef'), false);
|
|
assert.equal(encoded.includes('categoryCount'), false);
|
|
});
|
|
|
|
test('resolve-plan rejects raw file paths and records fallback state', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api, {
|
|
operationHandlers: {
|
|
'chain.resolve': () => ({
|
|
outcome: 'handled',
|
|
plan: {
|
|
schema: 'feedBack.audio_effects.chain_plan.v1',
|
|
planId: 'bad-plan',
|
|
routeKey: 'desktop-main',
|
|
providerId: 'rig-builder',
|
|
stages: [{ stageId: 'amp', kind: 'nam', role: 'amp', assetRef: '/Users/example/private/amp.nam' }],
|
|
},
|
|
}),
|
|
},
|
|
});
|
|
|
|
const resolved = await api.dispatch({ capability: 'audio-effects', command: 'resolve-plan', source: 'nam_tone', payload: { routeKey: 'desktop-main' } });
|
|
const route = await api.dispatch({ capability: 'audio-effects', command: 'inspect-route', source: 'test', payload: { routeKey: 'desktop-main' } });
|
|
const encoded = JSON.stringify(diagnosticsSnapshot(window));
|
|
|
|
assert.equal(resolved.outcome, 'failed');
|
|
assert.match(resolved.reason, /invalid/i);
|
|
assert.equal(route.payload.route.state, 'fallback');
|
|
assert.equal(encoded.includes('/Users/example'), false);
|
|
assert.equal(encoded.includes('amp.nam'), false);
|
|
});
|
|
|
|
test('load-plan calls trusted executor with provider-private assets without diagnostic leakage', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
let executorRequest = null;
|
|
window.feedBackDesktop = {
|
|
audioEffects: {
|
|
loadChainPlan(request) {
|
|
executorRequest = request;
|
|
return { outcome: 'handled', status: 'loaded', payload: { slotsLoaded: 1 } };
|
|
},
|
|
},
|
|
};
|
|
await registerProvider(api, {
|
|
operationHandlers: {
|
|
'chain.resolve': () => ({
|
|
outcome: 'handled',
|
|
plan: {
|
|
schema: 'feedBack.audio_effects.chain_plan.v1',
|
|
planId: 'private-plan',
|
|
routeKey: 'desktop-main',
|
|
providerId: 'rig-builder',
|
|
stages: [{ stageId: 'amp', kind: 'nam', role: 'amp', assetRef: 'provider:asset:amp' }],
|
|
},
|
|
assets: {
|
|
'provider:asset:amp': { kind: 'nam', path: '/Users/example/private/amp.nam', stateBase64: 'secret-state' },
|
|
},
|
|
summary: { stageCount: 1 },
|
|
}),
|
|
},
|
|
});
|
|
|
|
const loaded = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'load-plan',
|
|
source: 'nam_tone',
|
|
payload: {
|
|
routeKey: 'desktop-main',
|
|
authorization: 'playback-session',
|
|
target: { presetRef: 'safe-ref' },
|
|
options: { preloadMute: { targetGain: 4, holdMs: 25 }, gains: { input: 8, chain: 4 }, startAudio: true },
|
|
},
|
|
});
|
|
const snapshot = diagnosticsSnapshot(window);
|
|
const encoded = JSON.stringify(snapshot);
|
|
|
|
assert.equal(loaded.outcome, 'handled');
|
|
assert.equal(executorRequest.authorization, 'playback-session');
|
|
assert.equal(executorRequest.plan.planId, 'private-plan');
|
|
assert.equal(executorRequest.assets['provider:asset:amp'].path, '/Users/example/private/amp.nam');
|
|
assert.deepEqual(JSON.parse(JSON.stringify(executorRequest.options)), { preloadMute: { enabled: true, dryDuringLoad: true, targetGain: 4, holdMs: 25 }, gains: { input: 8, chain: 4 }, startAudio: true });
|
|
assert.equal(snapshot.routes[0].state, 'loaded');
|
|
assert.equal(encoded.includes('/Users/example'), false);
|
|
assert.equal(encoded.includes('secret-state'), false);
|
|
assert.equal(encoded.includes('provider:asset:amp'), false);
|
|
});
|
|
|
|
test('route gain and release delegate to the selected executor', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
const calls = [];
|
|
window.feedBackDesktop = {
|
|
audioEffects: {
|
|
loadChainPlan() { calls.push(['load']); return { outcome: 'handled', status: 'loaded', payload: { slotsLoaded: 1 } }; },
|
|
setRouteGain(request) { calls.push(['gain', request.gains]); return { outcome: 'handled', payload: { gains: request.gains } }; },
|
|
releaseRoute(request) { calls.push(['release', request.routeKey]); return { outcome: 'handled', payload: { released: true } }; },
|
|
},
|
|
};
|
|
await registerProvider(api);
|
|
const loaded = await api.dispatch({ capability: 'audio-effects', command: 'load-plan', source: 'test', payload: { routeKey: 'desktop-main', authorization: 'playback-session' } });
|
|
const gained = await window.feedBack.audioEffects.setRouteGain({ routeKey: 'desktop-main', authorization: 'playback-session', gains: { input: 3, chain: 2 } });
|
|
const released = await window.feedBack.audioEffects.releaseRoute({ routeKey: 'desktop-main', authorization: 'playback-session' });
|
|
const inspected = await window.feedBack.audioEffects.inspectRoute({ routeKey: 'desktop-main' });
|
|
|
|
assert.equal(loaded.outcome, 'handled');
|
|
assert.equal(gained.outcome, 'handled');
|
|
assert.equal(released.outcome, 'handled');
|
|
assert.equal(inspected.payload.route, null);
|
|
assert.deepEqual(JSON.parse(JSON.stringify(calls)), [['load'], ['gain', { input: 3, chain: 2 }], ['release', 'desktop-main']]);
|
|
});
|
|
|
|
test('provider unregister clears route plan and executor state', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
window.feedBackDesktop = {
|
|
audioEffects: {
|
|
loadChainPlan() { return { outcome: 'handled', status: 'loaded', payload: { slotsLoaded: 1 } }; },
|
|
},
|
|
};
|
|
await registerProvider(api);
|
|
const loaded = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'load-plan',
|
|
source: 'nam_tone',
|
|
payload: { routeKey: 'desktop-main', authorization: 'playback-session' },
|
|
});
|
|
assert.equal(loaded.outcome, 'handled');
|
|
|
|
const unregistered = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'unregister-provider',
|
|
source: 'rig_builder',
|
|
payload: { pluginId: 'rig_builder', providerId: 'rig-builder' },
|
|
});
|
|
const route = await api.dispatch({ capability: 'audio-effects', command: 'inspect-route', source: 'test', payload: { routeKey: 'desktop-main' } });
|
|
|
|
assert.equal(unregistered.outcome, 'handled');
|
|
assert.equal(route.payload.route.state, 'provider-unavailable');
|
|
assert.equal(route.payload.route.activePlanId, '');
|
|
assert.equal(route.payload.route.activeSegmentId, '');
|
|
assert.equal(route.payload.route.executorId, '');
|
|
assert.equal(JSON.stringify(route.payload.route.planSummary), '{}');
|
|
});
|
|
|
|
test('registry caps still allow provider and executor refreshes', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
for (let i = 0; i < 50; i++) {
|
|
const result = await registerProvider(api, { providerId: `provider-${i}`, pluginId: `plugin-${i}` });
|
|
assert.equal(result.outcome, 'handled');
|
|
}
|
|
const newProvider = await registerProvider(api, { providerId: 'provider-overflow', pluginId: 'plugin-overflow' });
|
|
const refreshedProvider = await registerProvider(api, { providerId: 'provider-0', pluginId: 'plugin-0', priority: 99 });
|
|
assert.equal(newProvider.outcome, 'failed');
|
|
assert.equal(refreshedProvider.outcome, 'handled');
|
|
assert.equal(refreshedProvider.payload.provider.priority, 99);
|
|
|
|
for (let i = 0; i < 20; i++) {
|
|
const result = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'test',
|
|
payload: { executorId: `executor-${i}`, pluginId: `executor-plugin-${i}`, routeKey: 'desktop-main', supportedKinds: ['nam'] },
|
|
});
|
|
assert.equal(result.outcome, 'handled');
|
|
}
|
|
const newExecutor = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'test',
|
|
payload: { executorId: 'executor-overflow', pluginId: 'executor-plugin-overflow', routeKey: 'desktop-main' },
|
|
});
|
|
const refreshedExecutor = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'test',
|
|
payload: { executorId: 'executor-0', pluginId: 'executor-plugin-0', routeKey: 'desktop-main', priority: 77, supportedKinds: ['ir'] },
|
|
});
|
|
assert.equal(newExecutor.outcome, 'failed');
|
|
assert.equal(refreshedExecutor.outcome, 'handled');
|
|
assert.equal(refreshedExecutor.payload.executor.priority, 77);
|
|
assert.equal(JSON.stringify(refreshedExecutor.payload.executor.supportedKinds), '["ir"]');
|
|
});
|
|
|
|
test('route restore preserves loaded state when an executor remains active', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
window.feedBackDesktop = {
|
|
audioEffects: {
|
|
loadChainPlan() { return { outcome: 'handled', status: 'loaded', payload: { slotsLoaded: 1 } }; },
|
|
},
|
|
};
|
|
await registerProvider(api);
|
|
const loaded = await api.dispatch({ capability: 'audio-effects', command: 'load-plan', source: 'nam_tone', payload: { routeKey: 'desktop-main', authorization: 'playback-session' } });
|
|
const bypassed = await api.dispatch({ capability: 'audio-effects', command: 'bypass', source: 'test', payload: { routeKey: 'desktop-main', authorization: 'user-action' } });
|
|
const restored = await api.dispatch({ capability: 'audio-effects', command: 'restore', source: 'test', payload: { routeKey: 'desktop-main', authorization: 'user-action' } });
|
|
|
|
assert.equal(loaded.payload.route.state, 'loaded');
|
|
assert.equal(bypassed.payload.route.state, 'bypassed');
|
|
assert.equal(restored.payload.route.state, 'loaded');
|
|
assert.equal(restored.payload.route.executorId, loaded.payload.route.executorId);
|
|
});
|
|
|
|
test('load-plan uses a registered compatible executor when Desktop is unavailable', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
let executorRequest = null;
|
|
await registerProvider(api, {
|
|
providerId: 'nam-tone',
|
|
pluginId: 'nam_tone',
|
|
operationHandlers: {
|
|
'chain.resolve': request => ({
|
|
outcome: 'handled',
|
|
plan: {
|
|
schema: 'feedBack.audio_effects.chain_plan.v1',
|
|
planId: 'browser-plan',
|
|
routeKey: request.routeKey,
|
|
providerId: 'nam-tone',
|
|
stages: [{ stageId: 'amp', kind: 'nam', role: 'amp', assetRef: 'provider:asset:browser-amp' }],
|
|
},
|
|
assets: {
|
|
'provider:asset:browser-amp': { kind: 'nam', browserFile: 'clean.json', path: '/Users/example/private/clean.nam' },
|
|
},
|
|
summary: { stageCount: 1 },
|
|
}),
|
|
},
|
|
});
|
|
await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'nam_tone',
|
|
payload: {
|
|
executorId: 'nam-tone-browser-wasm',
|
|
pluginId: 'nam_tone',
|
|
routeKey: 'desktop-main',
|
|
sourceMode: 'browser',
|
|
providerIds: ['nam-tone'],
|
|
operations: ['loadChainPlan'],
|
|
loadChainPlan(request) {
|
|
executorRequest = request;
|
|
return { outcome: 'handled', status: 'loaded', payload: { engineMode: 'wasm', slotsLoaded: 1 } };
|
|
},
|
|
},
|
|
});
|
|
|
|
const loaded = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'load-plan',
|
|
source: 'nam_tone',
|
|
payload: { routeKey: 'desktop-main', authorization: 'playback-session', target: { presetId: 42 } },
|
|
});
|
|
const snapshot = diagnosticsSnapshot(window);
|
|
const encoded = JSON.stringify(snapshot);
|
|
|
|
assert.equal(loaded.outcome, 'handled');
|
|
assert.equal(loaded.payload.executor.executorId, 'nam-tone-browser-wasm');
|
|
assert.equal(executorRequest.target.presetId, 42);
|
|
assert.equal(executorRequest.assets['provider:asset:browser-amp'].browserFile, 'clean.json');
|
|
assert.equal(snapshot.routes[0].state, 'loaded');
|
|
assert.equal(snapshot.routes[0].executorId, 'nam-tone-browser-wasm');
|
|
assert.equal(snapshot.executors.some(executor => executor.executorId === 'nam-tone-browser-wasm' && executor.sourceMode === 'browser'), true);
|
|
assert.equal(encoded.includes('/Users/example'), false);
|
|
assert.equal(encoded.includes('clean.nam'), false);
|
|
assert.equal(encoded.includes('provider:asset:browser-amp'), false);
|
|
});
|
|
|
|
test('load-plan redacts circular executor payloads without overflowing', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
const circular = { loaded: true };
|
|
circular.self = circular;
|
|
await registerProvider(api);
|
|
await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'test',
|
|
payload: {
|
|
executorId: 'cycle-executor',
|
|
pluginId: 'cycle_executor',
|
|
routeKey: 'desktop-main',
|
|
providerIds: ['rig-builder'],
|
|
operations: ['loadChainPlan'],
|
|
loadChainPlan() {
|
|
return { outcome: 'handled', status: 'loaded', payload: circular };
|
|
},
|
|
},
|
|
});
|
|
|
|
const loaded = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'load-plan',
|
|
source: 'test',
|
|
payload: { routeKey: 'desktop-main', executorId: 'cycle-executor', authorization: 'playback-session' },
|
|
});
|
|
|
|
assert.equal(loaded.outcome, 'handled');
|
|
assert.equal(loaded.payload.result.self, '[circular]');
|
|
assert.doesNotThrow(() => diagnosticsSnapshot(window));
|
|
});
|
|
|
|
test('load-plan does not use an executor registered for a different provider', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
await registerProvider(api, {
|
|
providerId: 'rig-builder',
|
|
pluginId: 'rig_builder',
|
|
operationHandlers: {
|
|
'chain.resolve': () => ({
|
|
outcome: 'handled',
|
|
plan: {
|
|
schema: 'feedBack.audio_effects.chain_plan.v1',
|
|
planId: 'rig-plan',
|
|
routeKey: 'desktop-main',
|
|
providerId: 'rig-builder',
|
|
stages: [{ stageId: 'amp', kind: 'nam', role: 'amp', assetRef: 'provider:asset:rig-amp' }],
|
|
},
|
|
assets: { 'provider:asset:rig-amp': { kind: 'nam', path: '/Users/example/private/rig.nam' } },
|
|
}),
|
|
},
|
|
});
|
|
await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'nam_tone',
|
|
payload: {
|
|
executorId: 'nam-tone-browser-wasm',
|
|
pluginId: 'nam_tone',
|
|
providerIds: ['nam-tone'],
|
|
loadChainPlan() { return { outcome: 'handled' }; },
|
|
},
|
|
});
|
|
|
|
const loaded = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'load-plan',
|
|
source: 'rig_builder',
|
|
payload: { routeKey: 'desktop-main', providerId: 'rig-builder', authorization: 'playback-session' },
|
|
});
|
|
const encoded = JSON.stringify(diagnosticsSnapshot(window));
|
|
|
|
assert.equal(loaded.outcome, 'unavailable');
|
|
assert.match(loaded.reason, /No compatible/);
|
|
assert.equal(encoded.includes('/Users/example'), false);
|
|
assert.equal(encoded.includes('rig.nam'), false);
|
|
});
|
|
|
|
test('load-plan rejects executors that cannot support the resolved stage kinds', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
let called = false;
|
|
await registerProvider(api, {
|
|
providerId: 'rig-builder',
|
|
pluginId: 'rig_builder',
|
|
operationHandlers: {
|
|
'chain.resolve': () => ({
|
|
outcome: 'handled',
|
|
plan: {
|
|
schema: 'feedBack.audio_effects.chain_plan.v1',
|
|
planId: 'rig-vst-plan',
|
|
routeKey: 'desktop-main',
|
|
providerId: 'rig-builder',
|
|
stages: [{ stageId: 'drive', kind: 'vst', role: 'pre-pedal', assetRef: 'provider:asset:drive' }],
|
|
},
|
|
assets: { 'provider:asset:drive': { kind: 'vst', path: '/Users/example/private/drive.vst3' } },
|
|
}),
|
|
},
|
|
});
|
|
await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'nam_tone',
|
|
payload: {
|
|
executorId: 'nam-only-test',
|
|
pluginId: 'nam_tone',
|
|
providerIds: ['rig-builder'],
|
|
supportedKinds: ['nam', 'ir'],
|
|
loadChainPlan() { called = true; return { outcome: 'handled' }; },
|
|
},
|
|
});
|
|
|
|
const loaded = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'load-plan',
|
|
source: 'rig_builder',
|
|
payload: { routeKey: 'desktop-main', providerId: 'rig-builder', authorization: 'playback-session' },
|
|
});
|
|
const encoded = JSON.stringify(diagnosticsSnapshot(window));
|
|
|
|
assert.equal(loaded.outcome, 'unavailable');
|
|
assert.match(loaded.payload.reason, /vst/);
|
|
assert.equal(called, false);
|
|
assert.equal(encoded.includes('/Users/example'), false);
|
|
assert.equal(encoded.includes('drive.vst3'), false);
|
|
});
|
|
|
|
test('load-plan can fall back to a compatible provider executor when the selected provider has none', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
let loadedProviderId = '';
|
|
await registerProvider(api, {
|
|
providerId: 'rig-builder',
|
|
pluginId: 'rig_builder',
|
|
priority: 40,
|
|
operationHandlers: {
|
|
'chain.resolve': () => ({
|
|
outcome: 'handled',
|
|
plan: {
|
|
schema: 'feedBack.audio_effects.chain_plan.v1',
|
|
planId: 'rig-plan',
|
|
routeKey: 'desktop-main',
|
|
providerId: 'rig-builder',
|
|
stages: [{ stageId: 'amp', kind: 'nam', role: 'amp', assetRef: 'provider:asset:rig-amp' }],
|
|
},
|
|
}),
|
|
},
|
|
});
|
|
await registerProvider(api, {
|
|
providerId: 'nam-tone',
|
|
pluginId: 'nam_tone',
|
|
priority: 10,
|
|
operationHandlers: {
|
|
'chain.resolve': () => ({
|
|
outcome: 'handled',
|
|
plan: {
|
|
schema: 'feedBack.audio_effects.chain_plan.v1',
|
|
planId: 'nam-plan',
|
|
routeKey: 'desktop-main',
|
|
providerId: 'nam-tone',
|
|
stages: [{ stageId: 'amp', kind: 'nam', role: 'amp', assetRef: 'provider:asset:nam-amp' }],
|
|
},
|
|
assets: { 'provider:asset:nam-amp': { kind: 'nam', browserFile: 'fallback.json' } },
|
|
}),
|
|
},
|
|
});
|
|
await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'nam_tone',
|
|
payload: {
|
|
executorId: 'nam-tone-browser-wasm',
|
|
pluginId: 'nam_tone',
|
|
providerIds: ['nam-tone'],
|
|
loadChainPlan(request) {
|
|
loadedProviderId = request.plan.providerId;
|
|
return { outcome: 'handled', payload: { slotsLoaded: 1 } };
|
|
},
|
|
},
|
|
});
|
|
|
|
const loaded = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'load-plan',
|
|
source: 'nam_tone',
|
|
payload: { routeKey: 'desktop-main', authorization: 'playback-session', fallbackProviderId: 'nam-tone' },
|
|
});
|
|
|
|
assert.equal(loaded.outcome, 'handled');
|
|
assert.equal(loaded.payload.route.providerId, 'nam-tone');
|
|
assert.equal(loaded.payload.executor.executorId, 'nam-tone-browser-wasm');
|
|
assert.equal(loadedProviderId, 'nam-tone');
|
|
});
|
|
|
|
test('load-plan can fall back from a selected provider when its executor cannot load the plan', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
let loadedProviderId = '';
|
|
await registerProvider(api, {
|
|
providerId: 'rig-builder',
|
|
pluginId: 'rig_builder',
|
|
priority: 40,
|
|
operationHandlers: {
|
|
'chain.resolve': () => ({
|
|
outcome: 'handled',
|
|
plan: {
|
|
schema: 'feedBack.audio_effects.chain_plan.v1',
|
|
planId: 'rig-vst-plan',
|
|
routeKey: 'desktop-main',
|
|
providerId: 'rig-builder',
|
|
stages: [{ stageId: 'drive', kind: 'vst', role: 'pre-pedal', assetRef: 'provider:asset:drive' }],
|
|
},
|
|
}),
|
|
},
|
|
});
|
|
await registerProvider(api, {
|
|
providerId: 'nam-tone',
|
|
pluginId: 'nam_tone',
|
|
priority: 10,
|
|
operationHandlers: {
|
|
'chain.resolve': () => ({
|
|
outcome: 'handled',
|
|
plan: {
|
|
schema: 'feedBack.audio_effects.chain_plan.v1',
|
|
planId: 'nam-plan',
|
|
routeKey: 'desktop-main',
|
|
providerId: 'nam-tone',
|
|
stages: [{ stageId: 'amp', kind: 'nam', role: 'amp', assetRef: 'provider:asset:nam-amp' }],
|
|
},
|
|
assets: { 'provider:asset:nam-amp': { kind: 'nam', browserFile: 'fallback.json' } },
|
|
}),
|
|
},
|
|
});
|
|
await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'nam_tone',
|
|
payload: {
|
|
executorId: 'rig-nam-only',
|
|
pluginId: 'nam_tone',
|
|
providerIds: ['rig-builder'],
|
|
supportedKinds: ['nam', 'ir'],
|
|
loadChainPlan() { throw new Error('should not load VST rig plan'); },
|
|
},
|
|
});
|
|
await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'register-executor',
|
|
source: 'nam_tone',
|
|
payload: {
|
|
executorId: 'nam-tone-browser-wasm',
|
|
pluginId: 'nam_tone',
|
|
providerIds: ['nam-tone'],
|
|
supportedKinds: ['nam', 'ir'],
|
|
loadChainPlan(request) {
|
|
loadedProviderId = request.plan.providerId;
|
|
return { outcome: 'handled', payload: { slotsLoaded: 1 } };
|
|
},
|
|
},
|
|
});
|
|
await api.dispatch({ capability: 'audio-effects', command: 'select-chain', source: 'user', payload: { routeKey: 'desktop-main', providerId: 'rig-builder', authorization: 'user-action' } });
|
|
|
|
const loaded = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'load-plan',
|
|
source: 'playback',
|
|
payload: { routeKey: 'desktop-main', authorization: 'playback-session', fallbackProviderId: 'nam-tone' },
|
|
});
|
|
|
|
assert.equal(loaded.outcome, 'handled');
|
|
assert.equal(loaded.payload.route.providerId, 'nam-tone');
|
|
assert.equal(loaded.payload.executor.executorId, 'nam-tone-browser-wasm');
|
|
assert.equal(loadedProviderId, 'nam-tone');
|
|
});
|
|
|
|
test('provider operations route stage and segment changes through selected provider', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
const calls = [];
|
|
await registerProvider(api, {
|
|
operationHandlers: {
|
|
'chain.resolve': () => ({
|
|
outcome: 'handled',
|
|
plan: {
|
|
schema: 'feedBack.audio_effects.chain_plan.v1',
|
|
planId: 'switch-plan',
|
|
routeKey: 'desktop-main',
|
|
providerId: 'rig-builder',
|
|
stages: [{ stageId: 'amp', kind: 'nam', role: 'amp', assetRef: 'provider:nam:amp' }],
|
|
segments: [{ segmentId: 'dist', stageIds: ['amp'] }],
|
|
},
|
|
}),
|
|
'segment.activate': request => { calls.push(['segment.activate', request.segmentId]); return { outcome: 'handled' }; },
|
|
'stage.set-bypass': request => { calls.push(['stage.set-bypass', request.stageId, request.bypassed]); return { outcome: 'handled' }; },
|
|
'stage.set-parameter': request => { calls.push(['stage.set-parameter', request.stageId, request.parameterId]); return { outcome: 'handled' }; },
|
|
},
|
|
});
|
|
await api.dispatch({ capability: 'audio-effects', command: 'resolve-plan', source: 'nam_tone', payload: { routeKey: 'desktop-main' } });
|
|
|
|
const segment = await api.dispatch({ capability: 'audio-effects', command: 'activate-segment', source: 'playback', payload: { routeKey: 'desktop-main', segmentId: 'dist' } });
|
|
const bypass = await api.dispatch({ capability: 'audio-effects', command: 'set-stage-bypass', source: 'rig_builder', payload: { routeKey: 'desktop-main', stageId: 'amp', bypassed: true } });
|
|
const parameter = await api.dispatch({ capability: 'audio-effects', command: 'set-stage-parameter', source: 'rig_builder', payload: { routeKey: 'desktop-main', stageId: 'amp', parameterId: 'gain', value: 0.7 } });
|
|
|
|
assert.equal(segment.outcome, 'handled');
|
|
assert.equal(bypass.outcome, 'handled');
|
|
assert.equal(parameter.outcome, 'handled');
|
|
assert.deepEqual(calls, [['segment.activate', 'dist'], ['stage.set-bypass', 'amp', true], ['stage.set-parameter', 'amp', 'gain']]);
|
|
});
|
|
|
|
test('mapping helpers call core mapping API with provider-tagged payloads', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
const calls = [];
|
|
window.fetch = async (url, options = {}) => {
|
|
calls.push({ url: String(url), options });
|
|
return {
|
|
ok: true,
|
|
status: 200,
|
|
async json() {
|
|
if (String(url).includes('/activate')) {
|
|
return { ok: true, mapping: { id: 7, provider_id: 'rig-builder', active: true } };
|
|
}
|
|
if ((options.method || 'GET') === 'DELETE') {
|
|
return { ok: true, cleared: true };
|
|
}
|
|
if ((options.method || 'GET') === 'POST') {
|
|
return { ok: true, mapping: { id: 7, song_key: 'settings-v1-song', tone_key: 'Dist', provider_id: 'rig-builder', provider_ref: 'chain:99', active: true } };
|
|
}
|
|
return { mappings: [{ id: 7, song_key: 'settings-v1-song', tone_key: 'Dist', provider_id: 'rig-builder', provider_ref: 'chain:99', active: true }] };
|
|
},
|
|
};
|
|
};
|
|
|
|
const saved = await window.feedBack.audioEffects.upsertMapping({
|
|
song_key: 'settings-v1-song',
|
|
filename: 'Artist - Song_p.archive',
|
|
tone_key: 'Dist',
|
|
provider_id: 'rig-builder',
|
|
provider_ref: 'chain:99',
|
|
active: true,
|
|
});
|
|
const listed = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'list-mappings',
|
|
source: 'rig_builder',
|
|
payload: { song_key: 'settings-v1-song', provider_id: 'rig-builder' },
|
|
});
|
|
const activated = await window.feedBack.audioEffects.activateMapping({ mappingId: 7, providerId: 'rig-builder' });
|
|
const cleared = await window.feedBack.audioEffects.clearActiveMapping({ songKey: 'settings-v1-song', toneKey: 'Dist' });
|
|
|
|
assert.equal(saved.outcome, 'handled');
|
|
assert.equal(saved.payload.mapping.provider_ref, 'chain:99');
|
|
assert.equal(listed.outcome, 'handled');
|
|
assert.equal(listed.payload.mappings[0].provider_id, 'rig-builder');
|
|
assert.equal(activated.payload.mapping.active, true);
|
|
assert.equal(cleared.payload.cleared, true);
|
|
assert.equal(calls[0].url, '/api/audio-effects/mappings');
|
|
assert.equal(JSON.parse(calls[0].options.body).provider_id, 'rig-builder');
|
|
assert.match(calls[1].url, /song_key=settings-v1-song/);
|
|
assert.match(calls[1].url, /provider_id=rig-builder/);
|
|
assert.equal(calls[2].url, '/api/audio-effects/mappings/7/activate');
|
|
assert.match(calls[3].url, /\/api\/audio-effects\/active-mapping\?/);
|
|
});
|
|
|
|
test('mapping helpers forward present falsey fields to the server instead of swallowing them', async () => {
|
|
const window = loadAudioEffects();
|
|
const calls = [];
|
|
window.fetch = async (url, options = {}) => {
|
|
calls.push({ url: String(url), options });
|
|
return { ok: true, status: 200, async json() { return { ok: true }; } };
|
|
};
|
|
|
|
// A falsey non-string provider_id must reach the server (which rejects it) rather than being
|
|
// coerced to '' client-side, which would become a silent unscoped activate.
|
|
await window.feedBack.audioEffects.activateMapping({ mappingId: 7, providerId: false });
|
|
assert.equal(JSON.parse(calls[0].options.body).provider_id, false);
|
|
|
|
// A present falsey query filter is forwarded (stringified) rather than dropped.
|
|
await window.feedBack.audioEffects.listMappings({ provider_id: 0 });
|
|
assert.match(calls[1].url, /provider_id=0/);
|
|
|
|
// An omitted filter stays omitted (no spurious empty filter).
|
|
await window.feedBack.audioEffects.listMappings({});
|
|
assert.equal(calls[2].url, '/api/audio-effects/mappings');
|
|
});
|
|
|
|
test('bridge hits are safe and diagnosable', async () => {
|
|
const window = loadAudioEffects();
|
|
const api = window.feedBack.capabilities;
|
|
|
|
const result = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'record-bridge-hit',
|
|
source: 'rig_builder',
|
|
payload: {
|
|
routeKey: 'desktop-main',
|
|
bridgeId: 'audio-effects.legacy-nam-routing',
|
|
pluginId: 'rig_builder',
|
|
legacySurface: 'fetch /Users/example/song.archive token=abc123',
|
|
},
|
|
});
|
|
const dbResult = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'record-bridge-hit',
|
|
source: 'nam_tone',
|
|
payload: {
|
|
routeKey: 'desktop-main',
|
|
bridgeId: 'audio-effects.legacy-tone-db',
|
|
pluginId: 'nam_tone',
|
|
legacySurface: 'nam_tone.db tone_mappings /Users/example/nam_tone.db',
|
|
},
|
|
});
|
|
const nativeResult = await api.dispatch({
|
|
capability: 'audio-effects',
|
|
command: 'record-bridge-hit',
|
|
source: 'nam_tone',
|
|
payload: {
|
|
routeKey: 'desktop-main',
|
|
bridgeId: 'audio-effects.legacy-native-load',
|
|
pluginId: 'nam_tone',
|
|
legacySurface: 'window.feedBackDesktop.audio.loadPreset /Users/example/model.nam',
|
|
},
|
|
});
|
|
const encoded = JSON.stringify(diagnosticsSnapshot(window));
|
|
|
|
assert.equal(result.outcome, 'handled');
|
|
assert.equal(dbResult.outcome, 'handled');
|
|
assert.equal(nativeResult.outcome, 'handled');
|
|
const sharedShim = window.feedBack.capabilities.snapshotDiagnostics().compatibilityShims.find(entry => entry.shimId === 'audio-effects.legacy-nam-routing');
|
|
assert.equal(sharedShim.status, 'used');
|
|
assert.equal(sharedShim.hitCount >= 1, true);
|
|
assert.equal(encoded.includes('audio-effects.legacy-nam-routing'), true);
|
|
assert.equal(encoded.includes('audio-effects.legacy-tone-db'), true);
|
|
assert.equal(encoded.includes('audio-effects.legacy-native-load'), true);
|
|
assert.equal(encoded.includes('/Users/example'), false);
|
|
assert.equal(encoded.includes('token=abc123'), false);
|
|
});
|