fix(desktop): Voice Conversation Mac 파이프라인 복구 — TTS 플랫폼 분기 + finishListening 스냅샷 순서 + Whisper 사전 로드 (빅뱅 Phase 5 Part 5)
Voice Conversation이 Mac에서 한 번도 end-to-end 동작한 적 없었음을 발견. 3개 독립 버그가
중첩돼 있었고, 진단 로그를 추가해 단계적으로 분해하여 모두 해결.
Bug 10.5 — TTSPlaybackService 'spawn powershell ENOENT' (Mac)
- 원인: _speakOne이 spawn('powershell', ...) 하드코딩. 파일 주석부터 "Windows SAPI (PowerShell)
기반". V2-5 플랫폼 감사 때 이 파일 누락.
- Fix: _speakOne을 platform dispatcher로 변경, _speakOneMac(/usr/bin/say -r <wpm> --) +
_speakOneWindows(기존 PowerShell) 분리. 공통 close/error 핸들러는 _bindProcessHandlers로
추출. rate 매핑은 _getMacRate(180*speed, WPM 기준) / _getWindowsRate(-10~10, SAPI) 분리.
- 파급: 음성 대화뿐 아니라 TTS를 쓰는 모든 경로가 Mac에서 벙어리였음.
Bug 11 — VoiceConversationService.finishListening 오디오 버퍼 스냅샷 순서 (진짜 root cause)
- 증상: finishListening 호출 시 audioBuffer가 항상 0 bytes → "audio too short"로 조용히
listening 복귀. STT가 한 번도 안 탐. Windows에서도 동일 버그였을 가능성 매우 높음.
- 원인: _stopListening() 내부에 this._audioBuffers = []로 리셋하는 라인이 있는데,
finishListening이 concat을 stop 호출 **이후**에 수행 → 이미 빈 배열에서 concat.
19초짜리 1.7MB 오디오가 매번 증발.
- Fix: _stopListening() 호출 **전**에 const audioBuffer = Buffer.concat(this._audioBuffers)
스냅샷 저장. finishListening 내 redundant한 this._audioBuffers = [] 재설정도 제거(이미
_stopListening이 수행).
Bug 12 — Whisper 모델 사전 로드 누락
- 증상: Bug 11 fix 후 STT 경로로는 진입하지만 LocalSTTService가 "모델 로딩 중, 오디오 버퍼에
적재"로 pending 큐에 쌓기만 하고 아무도 로드를 트리거하지 않아 영원히 대기.
- 원인: MeetingMode/CaptionService는 startRecording 시 LocalSTTService.initialize()를 명시적으로
호출하지만 VoiceConversationService는 이 호출 누락.
- Fix: startSession에서 void getLocalSTTService().initialize().catch(...) fire-and-forget.
initialize는 같은 모델이 Ready면 즉시 return하므로 idempotent.
검증:
- desktop tsc --noEmit EXIT=0
- /usr/bin/say -r 180 "TTS 분기 테스트 성공" 직접 호출 → 스피커 소리 확인
- 사용자 재시연: Mic → 말함 → ⏹ Stop → 전사 → LLM 스트리밍 → TTS 소리 → 자동 listening
복귀 연속 대화까지 end-to-end 통과
남은 과제 (다음 세션 U6/U8):
- Voice Conversation UX 몰입 패널 (9바 waveform + REC LED + 타이머) — 설계 완료
- Bug 13: 빈 STT 결과 시 사용자 피드백 부재 (현재는 조용히 listening 복귀 → 버튼 먹통처럼 보임)
This commit is contained in:
parent
3b77be01bc
commit
ee6ac3dbfc
2 changed files with 98 additions and 22 deletions
|
|
@ -80,6 +80,18 @@ class VoiceConversationService extends EventEmitter {
|
|||
this._isActive = true
|
||||
this._setState('listening')
|
||||
this._startListening()
|
||||
|
||||
// Whisper 모델 사전 로드 (fire-and-forget).
|
||||
// finishListening → transcribe 호출 시점에 모델이 Ready 상태여야
|
||||
// 전사가 바로 실행됨. 이미 로드됐거나 로딩 중이면 내부 가드로 no-op.
|
||||
// (Bug 12: Voice Conversation이 Meeting/Caption 과 달리 loadModel을
|
||||
// 명시적으로 호출하지 않아 첫 transcribe가 영원히 pending하던 문제)
|
||||
void getLocalSTTService()
|
||||
.initialize()
|
||||
.catch((err) => {
|
||||
logger.warn('STT 모델 사전 로드 실패 (transcribe 시 재시도):', err)
|
||||
})
|
||||
|
||||
logger.info('Voice conversation session started')
|
||||
}
|
||||
|
||||
|
|
@ -172,14 +184,17 @@ class VoiceConversationService extends EventEmitter {
|
|||
* 수집된 오디오를 STT로 전사 후 LLM 대화 진행.
|
||||
*/
|
||||
async finishListening(): Promise<void> {
|
||||
if (this._state !== 'listening' || this._audioBuffers.length === 0) return
|
||||
if (this._state !== 'listening' || this._audioBuffers.length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
// _stopListening()이 내부적으로 this._audioBuffers = []로 리셋하므로,
|
||||
// concat은 반드시 stop 호출 **전**에 끝내야 한다. (Bug 11)
|
||||
const audioBuffer = Buffer.concat(this._audioBuffers)
|
||||
|
||||
this._stopListening()
|
||||
this._setState('thinking')
|
||||
|
||||
const audioBuffer = Buffer.concat(this._audioBuffers)
|
||||
this._audioBuffers = []
|
||||
|
||||
// 최소 오디오 길이 체크 (500ms @ 16kHz 16bit mono)
|
||||
const minBytes = 16000 * 2 * 0.5
|
||||
if (audioBuffer.length < minBytes) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue