음성 시스템

푸시투톡 파이프라인, STT WebSocket 프로토콜, 홀드투톡 역학, 포커스 모드.

01

개요

Claude Code의 음성 시스템은 푸시투톡(Push-to-Talk) 파이프라인을 통해 음성 입력을 텍스트로 변환합니다. 전체 흐름은 다음과 같습니다:

  1. 키 입력 — 사용자가 음성 키를 누름
  2. 녹음 — 오디오 백엔드가 마이크 입력을 캡처
  3. WebSocket STT — 실시간 스트리밍으로 음성을 텍스트로 변환
  4. 텍스트 — 변환된 텍스트를 프롬프트 입력란에 삽입
  5. 프롬프트 제출 — 사용자 확인 후 제출
4+1 핵심 소스 파일: 음성 녹음 백엔드, STT WebSocket 클라이언트, 세션 상태 머신, 홀드투톡 키 핸들러, 포커스 모드 컨트롤러
02

기능 게이팅 & 인증

3계층 게이트

음성 기능은 세 단계의 게이트를 통과해야 활성화됩니다:

  1. GrowthBook 컴파일타임 — 기능 플래그가 빌드 시점에 활성화되어 있어야 음성 관련 코드가 포함됩니다
  2. OAuth 토큰 — 유효한 인증 토큰이 있어야 STT 서비스에 접근 가능
  3. /voice 사전 검사 (5단계) — 런타임에 마이크 권한, 오디오 백엔드 가용성, 네트워크 연결, 서버 상태, 사용자 자격을 순차적으로 확인

마이크 권한 타이밍

마이크 권한 요청은 사전 검사의 일부로 수행됩니다. 사용자가 처음 음성 기능을 활성화할 때 OS 수준의 권한 대화 상자가 표시되며, 이후 세션에서는 캐시된 권한이 사용됩니다.

03

오디오 녹음 백엔드

우선순위 체인

오디오 녹음 백엔드는 플랫폼과 가용성에 따라 우선순위 체인으로 선택됩니다:

우선순위 백엔드 플랫폼 특징
1 NAPI 네이티브 애드온 지원 시 가장 낮은 지연 시간
2 arecord Linux ALSA 직접 접근
3 SoX 크로스 플랫폼 침묵 감지 내장
4 Windows 네이티브 Windows Win32 API 사용
// 녹음 백엔드 우선순위 체인
const RECORDING_BACKENDS = [
  { name: 'napi',    probe: () => hasNativeAddon('audio-recorder') },
  { name: 'arecord', probe: () => raceWithTimeout(spawn('arecord', ['--version']), 150) },
  { name: 'sox',     probe: () => commandExists('sox') },
  { name: 'windows', probe: () => process.platform === 'win32' },
]

런타임 arecord 프로브 (150ms 경쟁)

arecord 프로브는 150ms 타임아웃이 있는 경쟁(race)으로 실행됩니다. arecord --version을 spawn하되, 150ms 내에 응답하지 않으면 다음 백엔드로 넘어갑니다. arecord가 행(hang)에 걸릴 수 있는 환경(예: ALSA 디바이스 없음)에서 무한 대기를 방지합니다.

SoX 침묵 감지 설정

SoX 백엔드는 내장 침묵 감지 기능을 활용하여 녹음 종료 시점을 자동 판별할 수 있습니다. 설정 가능한 임계값과 지속 시간으로 환경 소음과 실제 침묵을 구분합니다.

04

WebSocket STT 프로토콜

연결 엔드포인트

STT 서비스는 wss://api.anthropic.com/api/ws/speech_to_text/voice_stream에 WebSocket으로 연결합니다.

와이어 프로토콜

WebSocket 프레임은 다음 메시지 타입을 사용합니다:

api.anthropic.com 사용 이유

별도 STT 서비스 대신 api.anthropic.com을 사용하는 이유는 JA3 핑거프린트 호환성입니다. 엔터프라이즈 환경에서 방화벽이나 프록시가 알려지지 않은 도메인을 차단할 수 있으므로, 이미 허용된 API 도메인을 통해 STT 트래픽을 전달합니다.

finalize() 해석 경로

finalize()는 4가지 소스에서 전사 결과를 받을 수 있으며, 가장 빠른 소스를 사용합니다:

소스 지연 시간 정확도
스트리밍 중간 결과 가장 낮음 중간
Endpoint 이벤트 낮음 높음
CloseStream 응답 중간 높음
타임아웃 폴백 가장 높음 가용한 최선

가장 빠른 소스를 사용하는 이유는 체감 지연 시간 최소화입니다 — 사용자가 말을 멈춘 후 텍스트가 나타나기까지의 시간이 핵심 경험 지표이며, 가장 빠른 소스가 충분히 정확합니다.

05

홀드투톡 역학

터미널 키 이벤트 재구성

터미널은 "키 다운"과 "키 업" 이벤트를 구분하지 않습니다. 홀드투톡은 키 반복(repeat) 이벤트의 타이밍 패턴을 분석하여 키가 눌려 있는 상태를 재구성합니다.

홀드 임계값

stripTrailing()

stripTrailing()은 키 반복으로 입력된 후행 문자를 제거합니다. 전각 공백(일본어/한국어 IME)도 지원하여, 다양한 키보드 레이아웃에서 올바르게 동작합니다.

해제 감지 타이머

키 해제를 감지하기 위해 3단계 타이머를 사용합니다:

06

세션 상태 머신

3가지 상태

음성 세션은 idle, recording, processing 세 가지 상태로 동작합니다:

비동기 await 전 동기 상태 전환

상태 전환은 비동기 작업을 await하기 전에 동기적으로 수행됩니다. 이는 경합 조건을 방지합니다 — await 사이에 다른 이벤트가 상태를 변경할 수 있으므로, 먼저 상태를 전환한 후 비동기 작업을 시작합니다.

전체 세션 생명주기 타임라인 (8단계)

  1. 키 입력 감지 — 음성 키가 눌림
  2. 상태 전환: idle → recording — 동기적으로 상태 변경
  3. 오디오 백엔드 시작 — 선택된 녹음 백엔드 활성화
  4. WebSocket 연결 — STT 서버에 연결
  5. PCM 스트리밍 — 오디오 데이터를 실시간 전송
  6. 키 해제 감지 / 침묵 감지 — 녹음 종료 조건 충족
  7. 상태 전환: recording → processing — CloseStream 전송
  8. 상태 전환: processing → idle — 전사 결과 수신, 프롬프트에 삽입
07

무음 드롭 리플레이

6가지 감지 조건

무음으로 판단하여 오디오를 드롭하는 조건은 6가지입니다:

리플레이 코드

무음으로 드롭된 오디오가 실제로는 유효한 발화였을 수 있습니다. 리플레이는 32KB 슬라이스로 오디오를 재전송하며, 각 슬라이스 사이에 250ms 백오프를 적용하여 서버 부하를 제한합니다.

오디오 버퍼 바운딩

리플레이용 오디오 버퍼는 크기가 제한됩니다. 제한 없는 버퍼는 장시간 녹음 시 메모리를 소진할 수 있으므로, 최근 N초분의 오디오만 유지합니다.

08

포커스 모드

홀드투톡과의 비교

행동 홀드투톡 포커스 모드
녹음 시작 키를 누르고 있는 동안 토글 키로 시작
녹음 종료 키 해제 시 5초 침묵 타이머로 자동 종료
손 자유도 한 손이 키에 고정 양손 자유
정밀 제어 높음 — 즉시 종료 가능 낮음 — 침묵 감지에 의존
긴 발화 손이 피로해질 수 있음 적합
환경 소음 민감도 낮음 — 의도적 키 조작 높음 — 소음이 침묵 감지를 방해

5초 침묵 타이머

포커스 모드에서는 5초간 침묵이 지속되면 녹음이 자동 종료됩니다. 이는 사용자가 말을 멈추고 생각하는 짧은 멈춤과 실제 발화 종료를 구분하기 위한 임계값입니다.

09

언어 정규화 & 키텀

normalizeLanguageForSTT()

normalizeLanguageForSTT()는 다양한 로케일 형식을 BCP-47 표준으로 정규화합니다. 사용자 시스템의 로케일 설정이 ko_KR.UTF-8, ko-KR, Korean 등 다양한 형식일 수 있으므로, STT 서버가 요구하는 표준 형식으로 통일합니다.

getVoiceKeyterms()

getVoiceKeyterms()3가지 소스에서 최대 50개의 핵심 용어를 수집합니다:

이 핵심 용어는 STT 서버에 전달되어 도메인 특화 용어의 인식 정확도를 높입니다.

splitIdentifier()

splitIdentifier()는 코드 식별자를 단어로 분리합니다:

분리된 단어는 핵심 용어로 STT 서버에 전달되어, "카멜케이스"라고 발음해도 camelCase로 올바르게 인식되도록 돕습니다.

10

오디오 레벨 시각화

RMS 계산

오디오 레벨은 16비트 PCM 샘플에서 RMS(Root Mean Square)로 계산됩니다. 각 오디오 프레임의 샘플 값을 제곱하여 평균을 구한 후 제곱근을 취합니다.

sqrt 곡선

RMS 값은 제곱근 곡선으로 변환되어 시각화에 사용됩니다. 선형 매핑보다 제곱근 곡선이 인간의 음량 인식에 더 가깝습니다 — 조용한 소리의 변화가 더 뚜렷하게 표시되고, 큰 소리의 변화는 압축됩니다.

16바 파형

터미널에 16개의 바로 실시간 파형이 표시됩니다. 각 바의 높이는 해당 주파수 대역의 에너지를 반영하며, 녹음 중 시각적 피드백을 제공합니다.

11

핵심 요약

핵심 포인트

  • 음성 파이프라인은 키 입력 → 녹음 → WebSocket STT → 텍스트 → 프롬프트 제출의 5단계로 구성
  • 3계층 게이트(GrowthBook 컴파일타임 → OAuth 토큰 → /voice 사전 검사)를 모두 통과해야 음성 기능이 활성화
  • 녹음 백엔드는 NAPI → arecord → SoX → Windows 순으로 프로브하며, arecord는 150ms 경쟁으로 행 감지
  • finalize()는 4가지 소스 중 가장 빠른 전사 결과를 사용하여 체감 지연 시간을 최소화
  • 홀드투톡의 워밍업은 2번째 누르기부터 시작 — 첫 누르기 시점에는 홀드인지 탭인지 구분 불가
  • 세션 상태 머신은 비동기 await 전에 동기적으로 상태를 전환하여 경합 조건을 방지
  • 포커스 모드는 5초 침묵 타이머로 자동 종료되어 양손 자유를 제공하지만, 환경 소음에 더 민감
  • getVoiceKeyterms()는 3소스에서 최대 50개 용어를 수집하여 도메인 특화 STT 정확도를 향상
12

지식 확인

퀴즈 — 5문제

Q1. 음성 시스템의 3계층 게이트 순서는?

  • A) OAuth → GrowthBook → preflight
  • B) GrowthBook 컴파일타임 → OAuth 토큰 → /voice 사전 검사
  • C) preflight → OAuth → GrowthBook
  • D) 전부 동시에 실행
게이트는 순서대로 적용됩니다: 먼저 GrowthBook 컴파일타임 플래그로 코드 포함 여부가 결정되고, OAuth 토큰으로 인증 상태를 확인한 후, /voice 사전 검사 5단계로 런타임 조건을 점검합니다.

Q2. arecord 사용 가능 여부 판단 시 150ms 경쟁을 하는 이유는?

  • A) 속도 최적화
  • B) API 제한
  • C) arecord가 행에 걸릴 수 있어 타임아웃 필요
  • D) 배터리 절약
arecord --version은 ALSA 디바이스가 없거나 잘못 구성된 환경에서 행에 걸릴 수 있습니다. 150ms 타임아웃으로 무한 대기를 방지하고 다음 백엔드로 폴백합니다.

Q3. finalize()가 4가지 소스 중 가장 빠른 것을 사용하는 이유는?

  • A) 정확도가 가장 높아서
  • B) 순서 보장이 필요해서
  • C) 체감 지연 시간 최소화 — 가장 빠른 소스가 충분히 정확
  • D) API가 요구해서
사용자가 말을 멈춘 후 텍스트가 나타나기까지의 시간이 핵심 경험 지표입니다. 가장 빠른 소스의 정확도가 충분하므로, 최종 확인 결과를 기다리지 않고 즉시 텍스트를 표시합니다.

Q4. 홀드투톡 워밍업이 2번째 누르기부터 시작하는 이유는?

  • A) 첫 누르기는 무시되므로
  • B) 디바운싱 목적
  • C) 첫 누르기 시점에는 아직 홀드인지 탭인지 구분 불가
  • D) 하드웨어 제한
첫 번째 키 이벤트만으로는 사용자가 키를 길게 누르고 있는 것(홀드)인지 짧게 탭한 것인지 알 수 없습니다. 2번째 키 반복 이벤트가 도착해야 "누르고 있다"고 판단하고 오디오 백엔드 워밍업을 시작합니다.

Q5. 포커스 모드와 홀드투톡의 가장 큰 차이는?

  • A) 녹음 품질
  • B) 포커스는 5초 침묵 타이머로 자동 종료, 홀드는 키 해제 시 종료
  • C) 지원 플랫폼
  • D) 모델 선택
홀드투톡은 키를 누르고 있는 동안만 녹음하여 정밀한 제어가 가능하지만 한 손이 고정됩니다. 포커스 모드는 5초 침묵 타이머로 자동 종료되어 양손이 자유롭지만, 환경 소음이 침묵 감지를 방해할 수 있습니다.
0 / 5