From 6197ceb1321ccbb4cd24b61819cc63f8093b1572 Mon Sep 17 00:00:00 2001 From: Yun Chan Date: Wed, 8 Apr 2026 09:07:29 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20=EC=98=A4=EB=94=94=EC=98=A4=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=8B=A4=EC=8B=9C=EA=B0=84=20?= =?UTF-8?q?=EB=A0=88=EB=B2=A8=20=EB=AF=B8=ED=84=B0=EB=A1=9C=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 단발성 평균값 → 100ms 간격 실시간 RMS 스트리밍. AudioCaptureService 직접 시작/5초 자동 중지. 테스트 버튼 누르면 레벨 바가 왔다갔다 반응. --- src/main/ipc/audio-handlers.ts | 62 +++++++++++++++++++++-- src/preload/index.ts | 4 +- src/renderer/components/SettingsModal.tsx | 19 +++---- 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/src/main/ipc/audio-handlers.ts b/src/main/ipc/audio-handlers.ts index 51d7446..ef8f4d4 100644 --- a/src/main/ipc/audio-handlers.ts +++ b/src/main/ipc/audio-handlers.ts @@ -3,10 +3,37 @@ import { ipcMain } from 'electron' import { IPC_CHANNELS } from '@shared/ipc-channels' import { ipcSuccess, ipcError, ErrorCode } from '@shared/errors' -import { getAudioCaptureService } from '../services/AudioCaptureService' +import { getAudioCaptureService, calculateRMS } from '../services/AudioCaptureService' +import { getMainWindow } from '../windows/WindowManager' import { configGet, configSet } from '../services/ConfigService' +import { getLogger } from '../services/LoggerService' import type { SetDeviceParams } from '@shared/types' +const logger = getLogger('audio-handlers') + +let testTimer: ReturnType | null = null +let testAudioHandler: ((payload: { buffer: Buffer; timestamp: number }) => void) | null = null +let testLevelInterval: ReturnType | null = null +let testLastRms = 0 + +function stopAudioTest(): void { + const service = getAudioCaptureService() + if (testAudioHandler) { + service.off('audio-data', testAudioHandler) + testAudioHandler = null + } + if (testLevelInterval) { + clearInterval(testLevelInterval) + testLevelInterval = null + } + if (testTimer) { + clearTimeout(testTimer) + testTimer = null + } + testLastRms = 0 + service.stop().catch(() => { /* ignore */ }) +} + export function registerAudioHandlers(): void { ipcMain.handle(IPC_CHANNELS.AUDIO.GET_DEVICES, async () => { try { @@ -32,10 +59,39 @@ export function registerAudioHandlers(): void { ipcMain.handle(IPC_CHANNELS.AUDIO.TEST_DEVICE, async () => { try { + // 이전 테스트가 진행 중이면 정리 + stopAudioTest() + const service = getAudioCaptureService() - const result = await service.testCapture(2000) - return ipcSuccess(result) + await service.start() + + // 오디오 데이터에서 RMS 추적 + testAudioHandler = (payload: { buffer: Buffer; timestamp: number }) => { + testLastRms = calculateRMS(payload.buffer) + } + service.on('audio-data', testAudioHandler) + + // 100ms 간격으로 레벨을 렌더러에 전송 + testLevelInterval = setInterval(() => { + const level = testLastRms > 0 ? Math.min(1.0, Math.pow(testLastRms, 0.28)) : 0 + const win = getMainWindow() + if (win && !win.isDestroyed()) { + win.webContents.send('audio:testLevel', { level }) + } + }, 100) + + // 5초 후 자동 중지 + testTimer = setTimeout(() => { + stopAudioTest() + const win = getMainWindow() + if (win && !win.isDestroyed()) { + win.webContents.send('audio:testLevel', { level: 0 }) + } + }, 5000) + + return ipcSuccess({ averageLevel: 0, peakLevel: 0, hasAudio: true }) } catch (err) { + stopAudioTest() return ipcError( ErrorCode.AudioDeviceNotFound, `Audio test failed: ${err instanceof Error ? err.message : String(err)}`, diff --git a/src/preload/index.ts b/src/preload/index.ts index 4964fe8..7cee286 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -178,7 +178,9 @@ const electronAPI = { testDevice: (params: TestDeviceParams) => invoke(IPC_CHANNELS.AUDIO.TEST_DEVICE, params), onDeviceChanged: (cb: (e: AudioDeviceChangedEvent) => void): Unsubscribe => - on(IPC_CHANNELS.AUDIO.DEVICE_CHANGED, cb) + on(IPC_CHANNELS.AUDIO.DEVICE_CHANGED, cb), + onTestLevel: (cb: (e: { level: number }) => void): Unsubscribe => + on('audio:testLevel', cb), }, // ── Config ───────────────────────────────────────────── diff --git a/src/renderer/components/SettingsModal.tsx b/src/renderer/components/SettingsModal.tsx index 9bbde14..15c85bb 100644 --- a/src/renderer/components/SettingsModal.tsx +++ b/src/renderer/components/SettingsModal.tsx @@ -539,18 +539,15 @@ export function SettingsModal({ open, onClose }: SettingsModalProps): React.Reac setMicLevel(0) } else { setMicTesting(true) - setMicLevel(0.05) - const resp = await window.electronAPI.audio.testDevice({ deviceId: 'default' }) - if (resp.success) { - setMicLevel(resp.data.averageLevel) - setTimeout(() => { + setMicLevel(0) + const unsub = window.electronAPI.audio.onTestLevel((e) => { + setMicLevel(e.level) + if (e.level === 0) { setMicTesting(false) - setMicLevel(0) - }, 3000) - } else { - setMicTesting(false) - setMicLevel(0) - } + unsub() + } + }) + await window.electronAPI.audio.testDevice({ deviceId: 'default' }) } }} sx={{ fontFamily: d3roFontMono, fontSize: '11px', minWidth: 80 }}