From 854a53e8f7d71cb591a5ab19a2348c54e103d07d Mon Sep 17 00:00:00 2001 From: OmikronApex Date: Wed, 15 Jul 2026 14:10:08 +0200 Subject: [PATCH] fix(audio): sync capability input selection when the user applies a device MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A stale feedBack.audioInput.selectedLogicalSourceKey (persisted by the audio-session capability) survived device changes made in the audio settings screen. The next plugin to open the 'selected input' (tuner / note_detect on song start) re-applied the stale device via audioInputOpenHandler, clobbering the engine's input mid-session — seen in the field as guitar input going dead when starting a song (vorrin logs: ASIO M-Audio input replaced by 'Microphone (WO Mic Device)' ~0.5s after playSong, input level 0.0005). Fix: after a successful device apply (Apply button and init auto- apply), select the matching source in the audio-session capability so its in-memory selection and persisted key follow the user's choice; fall back to rewriting/removing the localStorage key directly when the capability or source isn't available yet. Co-Authored-By: Claude Fable 5 --- src/renderer/screen.js | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/renderer/screen.js b/src/renderer/screen.js index b28daab..bb5c565 100644 --- a/src/renderer/screen.js +++ b/src/renderer/screen.js @@ -395,6 +395,37 @@ window.__feedBackDesktopAudioHooks = window.__feedBackDesktopAudioHooks || {}; return { outcome: 'handled', status: 'closed' }; } + // Keep the audio-session capability's persisted input selection + // (feedBack.audioInput.selectedLogicalSourceKey) in lockstep with the + // device the user applies here. Without this, a stale stored selection + // (e.g. an old virtual mic) survives a settings change, and the next + // plugin that opens the "selected input" (tuner/note_detect on song + // start) calls audioInputOpenHandler with the stale key — clobbering + // the engine back to the dead device mid-session. + function syncSelectedInputSource(inputType, inputDevice) { + const realName = String(inputDevice || '').trim(); + const key = realName + ? `desktop-audio:${safeKeyPart(inputType)}:input:name:${encodeURIComponent(realName)}` + : ''; + const audioSession = window.slopsmith && window.slopsmith.audioSession; + if (key && audioSession && typeof audioSession.selectInputSource === 'function') { + try { + const res = audioSession.selectInputSource({ logicalSourceKey: key }, 'audio_engine'); + if (res && res.outcome === 'handled') return; + } catch (e) { + console.warn('[audio-engine] selectInputSource sync failed:', e); + } + } + // The capability isn't loaded (or the source isn't registered yet) — + // rewrite the persisted key directly so the stale selection still + // can't clobber the device at the next open. A nameless device has + // no stable key, so invalidate rather than guess. + try { + if (key) window.localStorage.setItem('feedBack.audioInput.selectedLogicalSourceKey', key); + else window.localStorage.removeItem('feedBack.audioInput.selectedLogicalSourceKey'); + } catch (_) { /* storage unavailable — nothing to invalidate */ } + } + function registerAudioSessionInputSources() { const audioSession = window.slopsmith && window.slopsmith.audioSession; if (!audioSession || typeof audioSession.registerInputSource !== 'function') return; @@ -975,6 +1006,9 @@ window.__feedBackDesktopAudioHooks = window.__feedBackDesktopAudioHooks || {}; aeApplyNoiseGateToEngine(); rememberAppliedDeviceSettings(); aeApplyTonePolishToEngine(); + // Converge a stale capability-side selection to the saved + // device on startup too — not just on a manual re-apply. + syncSelectedInputSource(deviceTypeSelect.value, inputDeviceSelect.value); } } } @@ -1617,6 +1651,7 @@ window.__feedBackDesktopAudioHooks = window.__feedBackDesktopAudioHooks || {}; statusText.textContent = 'Audio running' + modeLabel; aeApplyNoiseGateToEngine(); aeApplyTonePolishToEngine(); + syncSelectedInputSource(inputType, inputDeviceSelect.value); const applied = rememberAppliedDeviceSettings(); await saveDeviceSettings(applied); } else {