From d791552261c34566f8d96396cf8a849cd3f1853b Mon Sep 17 00:00:00 2001 From: OmikronApex Date: Wed, 15 Jul 2026 14:28:02 +0200 Subject: [PATCH] fix(audio): skip redundant device rebind when opening the selected input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit audioInputOpenHandler unconditionally called setDevice, so opening the selected input (tuner/note_detect on song start) restarted the engine even when the requested device was already bound — an audible dropout and the 'Audio paused unexpectedly' seen in tester logs. Now the handler reads the engine's actual binding (isAudioRunning + getCurrentDevice) and returns the bound identity without touching the device when input name and type already match; any read failure falls through to the normal rebind path. Co-Authored-By: Claude Fable 5 --- src/renderer/screen.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/renderer/screen.js b/src/renderer/screen.js index bb5c565..d9453e3 100644 --- a/src/renderer/screen.js +++ b/src/renderer/screen.js @@ -360,6 +360,26 @@ window.__feedBackDesktopAudioHooks = window.__feedBackDesktopAudioHooks || {}; return { outcome: 'failed', status: 'failed', reason: 'No input device selected' }; } + // If the engine is already running with exactly the requested input, + // don't tear it down just to rebind the same device — a redundant + // setDevice stops and restarts the engine mid-session (audible dropout, + // "Audio paused unexpectedly" on song start). Compare against what the + // engine ACTUALLY has bound, not the Settings dropdowns, so a stale UI + // can't fake a match. + try { + const running = typeof api.isAudioRunning === 'function' ? await api.isAudioRunning() : false; + if (running && typeof api.getCurrentDevice === 'function') { + const cur = await api.getCurrentDevice(); + const curType = cur && (cur.inputType || cur.type) ? String(cur.inputType || cur.type) : ''; + const curInput = cur && cur.input ? String(cur.input) : ''; + const wantType = typeInfo && typeInfo.name ? String(typeInfo.name) : ''; + if (curInput && curInput.trim() === String(inputDevice).trim() + && (!wantType || curType === wantType)) { + return { outcome: 'handled', status: 'open', payload: { boundType: curType || wantType, boundName: curInput, requestedName: inputDevice, alreadyBound: true } }; + } + } + } catch (_) { /* engine state unreadable — fall through to the normal (re)bind path */ } + const snapshot = currentAudioDeviceSnapshot(); const result = await api.setDevice({ inputType: typeInfo && typeInfo.name ? typeInfo.name : snapshot.inputType,