음성으로 코딩하기 (4편) — 멀쩡한 마이크를 죽었다고 오판한 이유
3편에서 핀마이크로 음질 문제를 잡았다고 좋아했는데, 바로 다음 지옥이 열렸어요. 이번엔 멀쩡한 마이크를 자꾸 "죽었다"고 우기는 문제였습니다.
지난 편 → 3편 — 인식이 안 되던 진짜 범인은 마이크였다
음질을 잡았더니 이번엔 "끊김"이었어요
핀마이크가 잘 되나 싶었는데, 화면에 자꾸 "⚠ 마이크 입력 없음" 경고가 떴어요. 5초쯤 조용히 있으면 "끊겼다"고 판단하고 마이크를 다시 여는 동작을 무한 반복했죠. 정작 마이크는 멀쩡히 꽂혀 있는데도요.
hands-free로 쓰려면 잠깐씩 말을 멈추는 게 당연한데, 그 침묵을 "장치가 죽었다"고 오해하니 쓸 수가 없었어요.
진폭으로 살아있는지 판단했는데…
끊김을 감지하는 방식은 이랬어요. 마이크에서 들어오는 소리의 크기(진폭)를 보고, "의미 있는 소리"가 일정 시간 안 들어오면 끊긴 걸로 판단한다.
문제는 여기 있었어요. 핀마이크는 조용할 때도 아주 작은 신호(진폭 레벨 1)를 흘리는데, 제가 "이 정도(레벨 2) 이상은 돼야 살아있는 소리로 친다"고 임계값을 잡아둔 거예요. 그래서 정상적인 침묵을 "소리 없음 = 끊김"으로 오판한 거죠.
임계값을 낮췄더니 해결… 인 줄 알았죠
원인을 알았으니 간단해 보였어요. 임계값을 2에서 0으로 낮췄죠. 레벨 1도 살아있는 걸로 쳐주도록. 설정을 바꾸자마자(핫리로드) 끊김 경고가 사라졌어요. "해결!" 하고 넘어갔죠.
그런데 며칠 뒤, 앱을 재시작했더니 끊김 지옥이 그대로 돌아왔습니다.
어제는 되고 오늘은 안 되는 미스터리
설정 파일엔 분명 0이 그대로 있었어요. 그런데 어제는 되고 오늘은 안 됐죠. 이게 제일 헷갈렸어요.
범인은 설정값을 읽는 코드가 두 군데였고, 둘의 동작이 달랐다는 거예요.
| 언제 | 0을 어떻게 처리? |
|---|---|
| 설정을 바꿔 즉시 반영할 때(핫리로드) | 0 허용 ✅ |
| 앱을 껐다 켤 때(재시작) | 0 → 2로 덮어씀 ❌ |
어제 "해결"됐던 건 핫리로드 경로였고, 오늘 재시작하니 다른 경로가 0을 무시한 거였어요.
범인은 or 2 한 줄이었어요
재시작 경로의 코드는 대략 이랬어요.
alive_min_level = int(설정에서_읽기("...", 기본값 2) or 2)
파이썬에서 0은 falsy(거짓 취급) 예요. 그래서 0 or 2는 2가 됩니다. 즉 설정에 0을 넣어도, 재시작할 때 코드가 "0은 빈 값이네" 하고 2로 덮어쓴 거였죠. 핫리로드 경로는 if 값 >= 0으로 검사해서 0을 제대로 받았고요.
or 2 한 줄을 빼서 0을 유효값으로 받게 고치니, 재시작해도 멀쩡했습니다.
교훈 — 0은 falsy다, 그리고 두 경로를 다 봐라
며칠을 잡아먹은 버그의 교훈은 두 개예요.
- 0을 유효값으로 다룰 땐
or·falsy 검사를 조심하라.값 or 기본값은 0을 삼켜버립니다. - 같은 설정을 읽는 경로가 여러 개면 전부 확인하라. 핫리로드로만 검증하고 재시작은 안 봤더니, 이 불일치가 숨어 있었죠.
AI(Claude)가 코드는 빠르게 짜줬지만, "어제는 되고 오늘은 안 되는" 이 미묘한 불일치는 결국 제가 직접 재현하고 로그를 파야 잡혔어요.
다음 편
이번엔 소리가 아니라 침묵이 문제였어요. Claude가 분명 답했는데 TTS가 입을 꾹 다무는 미스터리. → (5편 — TTS 침묵, 게시 후 링크 추가 예정)
(이전 편: 3편 — 인식이 안 되던 진짜 범인은 마이크였다)
댓글
댓글 쓰기