feedBack/tests/js/audio_effects_host.test.js
Bret Mogilefsky af2949677a
rename: slopsmith → feedBack, byron → got-feedBack (#537)
* 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>
2026-06-23 11:03:01 +02:00

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);
});