GITHUB 레포 딥다이브 · 2026-07-30 · XIKHAR/PERSONA · 음성 대화에 얼굴을 붙이는 3D 아바타 오버레이

xikhar/persona 딥다이브
음성 앱 소리에 반응하는 프라이버시 우선 3D 마스코트

Persona는 데스크톱 음성 대화에 시각적 정체성을 부여하는 크로스플랫폼 3D 캐릭터 오버레이다. 화면 한쪽에 투명 배경의 VRM 아바타를 항상 위에 띄워 놓고, Codex·ChatGPT 데스크톱 같은 음성 앱이 스피커로 내보내는 소리를 실시간으로 엿들어, 그 음량에 맞춰 입모양(립싱크)과 몸동작을 재생한다.

가장 흔한 오해부터 정정하자 — 이건 음성 AI가 아니다. STT(음성 인식)도, TTS(음성 합성)도, LLM 파이프라인도 없다. README가 못 박는다: "마이크를 캡처하지 않고, 오디오를 저장하지 않고, 말을 만들지 않고, 전사하지 않고, 네트워크로 보내지 않는다." 오직 다른 앱의 출력 음량(RMS 진폭)만 메모리에서 계산하고 즉시 버린다. "real-time voice"란 음성을 만드는 게 아니라, 이미 벌어지는 대화에 반응하는 아바타 프레즌스를 뜻한다.

(저장소 xikhar/persona · 언어 TS 렌더러 + CJS Electron + ObjC++/C++ 네이티브 · 라이선스 MIT(에셋 제외) · 버전 0.1.0-beta.0 · 별 430 · 포크 39 · 생성 2026-07-28 · 파일 83개 · Node 24+)
목차
  1. 한 줄 정체
  2. 왜 주목받는가 — 에이전트 마스코트의 탄생
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 — 4개의 좁은 계층
  5. 디렉토리 구조 해부
  6. 학습 포인트 — 기술별 배울 것
  7. 시스템 / 실행 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 로드맵 (주차별)
  10. 핵심 키워드 사전
  11. 참고 링크

1한 줄 정체

이 프로젝트가 정확히 무엇인지 한 문장으로

Persona는 "음성 앱 위에 얹는 살아있는 아바타 스티커"다. Codex 데스크톱과 목소리로 대화하면, 화면 구석에 떠 있는 3D 캐릭터가 그 목소리의 크기에 맞춰 입을 뻐끔거리고 고개를 끄덕이며 반응한다. AI가 말하는 동안엔 아바타가 말하는 것처럼 보이고, 조용해지면 가만히 숨 쉬는 대기 자세로 돌아간다. 이게 전부지만, 이걸 마이크도 안 켜고·소리도 저장 안 하고·네트워크도 안 쓰고 해낸다는 게 핵심이다.

한 장의 비유

"라디오 앞에서 춤추는 인형 — 단, 방송을 녹음하진 않는다"

태엽 인형을 스피커 옆에 두면, 소리의 진동을 느껴 리듬에 맞춰 움직인다. Persona가 딱 그것이다. 스피커에서 나오는 음량만 느껴서 아바타를 움직인다 — 무슨 말인지 알아듣거나, 녹음하거나, 어딘가로 보내지 않는다.

차이는 "무엇을 안 하느냐"에 있다. 보통의 음성 캐릭터 앱은 마이크를 켜고 클라우드로 음성을 보낸다. Persona는 그 문을 아키텍처 레벨에서 아예 막아 두었다. 아바타를 그리는 화면 코드는 파일도, 프로세스도, 원본 오디오도 만질 수 없다.

용어
VRM / VRMA
VRM은 3D 휴머노이드 아바타를 위한 표준 파일 포맷(.vrm). 표정·뼈대·재질이 한 파일에 담겨, VTuber·메타버스에서 널리 쓰인다. VRMA(.vrma)는 그 아바타에 입히는 모션 클립(춤·인사·말하기 동작 등) 포맷이다. Persona는 이 둘을 조합해 "말하는 캐릭터"를 만든다.
용어
오버레이 / always-on-top 투명 창
다른 창들 위에 항상 떠 있는, 배경이 비치는(투명) 창. Persona의 아바타는 이런 창에 렌더되어, 어떤 앱을 쓰든 화면 구석에 계속 존재한다. 게임의 HUD, 화상회의의 자막 위젯과 같은 종류다.

2왜 주목받는가 — 에이전트 마스코트의 탄생

트렌딩 이유와 비슷한 도구 대비 강점

Persona가 급부상한 배경엔 두 흐름의 교차가 있다. 하나는 음성으로 대화하는 코딩 에이전트(Codex 데스크톱 등)의 등장, 다른 하나는 MCP(Model Context Protocol)로 에이전트가 도구를 조종하는 생태계의 확산이다. Persona는 이 둘을 엮어 "에이전트가 조종하는, 목소리에 반응하는 데스크톱 마스코트"라는 신선한 조합을 내놓았다.

세일즈 포인트는 IRIS와 마찬가지로 "무엇을 했는가"다. 마이크 안 씀, 저장 안 함, 전송 안 함, 계정 없음. 그런데 이 절제가 마케팅 문구에 그치지 않고 코드 구조로 강제된다는 게 진짜 차별점이다.

비슷한 것들과의 비교

항목Persona흔한 음성 아바타 앱
음성 입력마이크 미사용 — 앱 출력만 엿들음마이크 캡처
데이터진폭만 계산 후 즉시 폐기클라우드 전송·저장 흔함
플랫폼mac · Windows · Linux단일 OS가 많음
에이전트 제어MCP 서버 내장(애니메이션 지시)대개 없음
렌더러 권한fs·process·raw오디오 전부 차단느슨한 경우 많음

"그냥 아바타 위젯 하나 띄우면 되잖아?"에 대한 답

맞다, 아바타를 띄우는 건 쉽다. 어려운 건 (1) 특정 앱의 소리만 골라 엿듣기, (2) 그러면서 프라이버시를 지키기, (3) 세 OS에서 똑같이 동작시키기다. Persona는 macOS Core Audio Process Tap, Windows WASAPI Process Loopback, Linux PipeWire라는 각 OS의 최신 시스템 오디오 API를 정공법으로 써서, "Codex가 내는 소리에만" 스코프를 건다. 유튜브·음악은 무시된다.

비유

파티장에서 한 사람 목소리만 듣기. 방 안엔 여러 소리(여러 앱)가 섞여 있다. 아마추어는 방 전체 소음을 녹음한다(마이크). Persona는 특정 화자(Codex 프로세스)의 입에만 지향성 마이크를 대는 것과 같다 — OS의 프로세스별 오디오 탭으로, 그 앱의 출력 스트림에만 귀를 붙인다.

3기술 스택 전체 지도

package.json과 소스에서 읽어낸 실제 의존성

Persona는 세 층의 언어가 겹쳐 있다 — 화면을 그리는 TS 렌더러, 앱을 지휘하는 CJS Electron 메인, 소리를 엿듣는 네이티브 헬퍼(ObjC++/C++). 이 분리 자체가 보안 설계다.

① 렌더러 — 화면에 아바타 그리기 (src/, TypeScript)

② 데스크톱 셸 — 앱 지휘 (electron/*.cjs, CommonJS)

③ 네이티브 오디오 헬퍼 (native/)

④ 빌드 / 배포

용어
RMS 진폭 (amplitude)
RMS(Root Mean Square)는 오디오 파형의 "평균적인 세기"를 나타내는 값. 파형을 제곱해 평균 내고 제곱근을 씌운다. Persona는 소리의 내용이 아니라 이 세기만 뽑아 0~1로 정규화해 아바타 입 크기에 쓴다. 그래서 "무슨 말인지"는 전혀 알 필요가 없다.

4아키텍처 심화 — 4개의 좁은 계층

소리가 나서 아바타가 입을 벌리기까지, 내부에서 벌어지는 일

공식 문서(docs/DEVELOPMENT.md)는 Persona를 "의도적으로 좁게 만든 네 개의 계층"으로 설명한다. 각 계층은 바로 아래 계층에게 정규화된 이벤트만 넘기고, 그 이상의 권한을 주지 않는다. 이 "좁음"이 곧 보안이다.

┌──────────────────────────────────────────────────────────────┐ │ 지원 음성 앱 (Codex / ChatGPT 데스크톱) │ │ └─ 스피커로 오디오 출력 │ └───────────────────────────┬──────────────────────────────────┘ OS 오디오 그래프에서 │ "그 프로세스 트리"만 탭(tap) ▼ ┌──────────────────────────────────────────────────────────────┐ │ ① 네이티브 리스너 (native/ + electron/*-listener.cjs) │ │ 대상 프로세스 발견 → 오디오 출력 tap → RMS 계산 → 0..1 정규화 │ │ mac/Win: 헬퍼 바이너리가 NDJSON을 stdout으로 │ │ Linux : pw-record 원시 PCM을 파이프 │ └───────────────────────────┬──────────────────────────────────┘ onLevel(0..1) · onActivity(speaking|listening) · onStatus ▼ ┌──────────────────────────────────────────────────────────────┐ │ ② Electron 메인 (electron/main.cjs) │ │ 투명 always-on-top 창 · 트레이 · 글로벌 단축키(Ctrl+Shift+A) │ │ persona:// URL 프로토콜 · 로컬 HTTP 브리지(47831) · MCP 서버 │ │ AudioActivityGate 로 짧은 침묵 스무딩 │ └───────────────────────────┬──────────────────────────────────┘ IPC "persona:event" (state / audio-level / animation) ▼ ┌──────────────────────────────────────────────────────────────┐ │ ③ 샌드박스 preload (electron/preload.cjs) │ │ contextBridge 로 personaBridge.{getSnapshot,subscribe,hide} │ │ 만 노출 — 렌더러는 fs / process / raw-audio 접근 불가 │ └───────────────────────────┬──────────────────────────────────┘ window.personaBridge ▼ ┌──────────────────────────────────────────────────────────────┐ │ ④ React + Three.js 렌더러 (src/) │ │ VRM 모델 렌더 · VRMA 모션 크로스페이드 │ │ 진폭 기반 립싱크 · 랜덤 눈깜빡임 │ └──────────────────────────────────────────────────────────────┘

① 네이티브 리스너 — "그 앱 소리만" 골라 듣기

가장 저수준이자 OS별로 갈리는 부분. 놀라운 점은 세 OS가 완전히 다른 API를 쓰면서도 똑같은 정규화 공식을 공유한다는 것이다: (rms − 0.0025) × 7.5를 0~1로 clamp. 노이즈 플로어 0.0025, 게인 7.5가 세 구현(JS normalizeRms, macOS meterIOProc, Windows packetSquareSum)에 동일하게 박혀 있다. 이식성의 교과서적 예다.

"Codex 소리만" 골라내는 프로세스 트리 타겟팅도 정교하다. Codex가 자식 프로세스로 오디오를 낼 수 있어서, macOS/Windows는 부모→자식 트리를 따라가고(Windows는 PROCESS_LOOPBACK_MODE_INCLUDE_TARGET_PROCESS_TREE 플래그), Linux는 /proc/<pid>의 cmdline/comm/exe를 최대 10단계 거슬러 올라가 CODEX_IDENTITY 정규식으로 매칭한다. PERSONA_TARGET_PROCESS_PATTERN 환경변수를 바꾸면 다른 음성 앱도 타겟이 된다.

② Electron 메인 — 지휘자와 침묵 스무딩

창·트레이·단축키를 관리하는 오케스트레이터. 여기서 AudioActivityGate(electron/audio-activity-gate.cjs)가 핵심 상태머신을 돌린다. 레벨이 0.018을 넘으면 즉시 speaking으로 바꾸되, 임계 이하로 떨어져도 900ms 동안(DEFAULT_SPEECH_RELEASE_MS) speaking을 유지한다.

비유

말하다 숨 쉬는 순간마다 자세가 튀면 이상하다. 사람이 "안녕하세요 ...(0.3초 숨)... 반갑습니다"라고 할 때, 그 짧은 공백마다 아바타가 대기 자세로 돌아갔다 다시 말하기 자세로 튀면 로봇 같다. 그래서 입은 음량을 즉시 따라가되, 몸은 900ms 지연을 둔다 — 문장 사이 공백을 자연스럽게 이어 붙이는 것이다.

③ 샌드박스 preload — 권한의 벽

Persona 보안의 심장. 렌더러(화면 코드)는 파일시스템도, 프로세스도, 원본 오디오도 만질 수 없다. preload가 contextBridge로 오직 personaBridge.getSnapshot / subscribe / hide 세 가지 정규화된 함수만 노출하기 때문이다. 렌더러는 "지금 speaking이고 레벨은 0.4"라는 결론만 받을 뿐, 그 결론이 어떤 오디오에서 나왔는지 원본에 접근할 방법이 없다.

배울 점
Electron 보안 3원칙 + 최소 노출

contextIsolation:true, nodeIntegration:false, sandbox:true 세 스위치를 켜고, preload로 딱 필요한 API만 화이트리스트한다. 여기에 setWindowOpenHandler(deny), will-navigate 화이트리스트(navigation-policy.cjs), 엄격한 CSP까지. "권한은 기본적으로 없고, 필요한 것만 명시적으로 연다"는 원칙의 실전판.

④ 렌더러 — 진폭을 입모양으로 바꾸는 트릭

이 프로젝트에서 가장 재미있는 부분. 원본 오디오 파형이 없는데 어떻게 입모양을 만들까? 실제 음소(phoneme) 분석이 아니라, 진폭 기반 가짜 비세임(viseme) 순환이다(src/hooks/useAmplitudeLipSync.ts).

# useAmplitudeLipSync의 아이디어 (의사코드)
visemes = ['aa','ee','ih','oh','ou']      # VRM 표정 5개
phase   = (phase + dt * speed) % len      # 시간에 따라 순환
mouth   = visemes[floor(phase)]
# smoothed: 상승 0.055s / 하강 0.1s 비대칭 지수 스무딩
weight  = smooth(amplitude, up=0.055, down=0.1)
setExpression(mouth, weight)              # 진폭이 크면 입 크게

즉 소리가 클수록 입을 크게 벌리고, 어떤 입모양을 쓸지는 시간에 따라 5개를 그럴듯하게 순환시킨다. 실제 발음과 일치하진 않지만, 사람 눈엔 충분히 "말하는 것처럼" 보인다. 상승은 빠르게(0.055s) 하강은 느리게(0.1s) 하는 비대칭 스무딩이 자연스러움의 비결이다.

애니메이션 우선순위 — 음성 vs MCP 지시의 충돌 해결

최신 커밋(#2)의 핵심. 아바타는 두 곳에서 명령을 받는다 — 음성 구동 몸동작(말하면 TALK)과 MCP 요청 애니메이션(에이전트가 "춤춰!"). 둘이 부딪히면? src/animation-priority.tsresolveBodyAnimation이 정리한다. MCP 애니메이션은 requestId로 override되어 한 번 재생(once) 후 자동으로 음성 상태로 복귀한다. useVrmAnimation.tsrequestGeneration 카운터로 경쟁 상태(race condition)를 막고, crossFadeAnimationActions로 부드럽게 블렌딩한다(TALK→IDLE 1.15s, →TALK 0.85s, 기타 0.7s).

애니메이션 카탈로그 = 바뀌지 않는 계약

IDLE / GREETING / TALK / HAPPY / FINGER_GUN / DANCE는 파일 경로가 아니라 제품 계약명이다(src/animation-catalog.ts). TALK는 talk1/2/3 중 nextAnimation으로 라운드로빈된다. 이렇게 이름으로 추상화해 두면, 캐릭터 팩을 통째로 바꿔도 MCP 설정이나 파일시스템 접근 없이 새 모션으로 교체된다.

용어
MCP (Model Context Protocol)
AI 에이전트가 외부 도구를 표준 방식으로 호출하게 해 주는 프로토콜. Persona는 MCP 서버가 되어, Codex 같은 에이전트가 codex mcp add persona --url http://127.0.0.1:47831/mcp로 등록하면 "애니메이션 재생 / 창 표시·숨김 / 상태 조회" 툴을 에이전트에게 제공한다. 즉 에이전트가 아바타의 리모컨을 쥐는 셈.
배울 점
로컬 브리지 서버의 방어선

electron/bridge-server.cjs는 로컬 서버지만 방어가 촘촘하다: Host 헤더가 loopback이 아니면 403, Origin은 정규식 TRUSTED_ORIGIN(127.0.0.1/localhost/codex-app://)만 허용, 바디 64KB 제한, MCP는 요청마다 stateless transport를 새로 생성(세션·서버푸시 없음). "로컬이니까 괜찮겠지"를 경계하는 좋은 습관.

5디렉토리 구조 해부

83개 파일이 어디에 어떻게 나뉘어 있나
persona/ ├── src/ ← ④ React+Three.js 렌더러 (TS) │ ├── main.tsx / App.tsx 진입점 · 이벤트 구독→애니메이션 상태 결정 │ ├── components/ │ │ ├── Scene.tsx Canvas · 조명 · HDRI · 전신 프레이밍 카메라 │ │ └── Avatar.tsx VRM 모델 + 매 프레임 훅 업데이트(useFrame) │ ├── hooks/ │ │ ├── useVrmLoader.ts GLTFLoader+VRMLoaderPlugin, VRMUtils 최적화 │ │ ├── useVrmAnimation.ts AnimationMixer · VRMA 로드/캐시/크로스페이드 │ │ ├── useAmplitudeLipSync.ts 진폭→비세임 립싱크 │ │ └── useBlink.ts 2~6초 랜덤 눈깜빡임(sin 곡선) │ ├── animation-catalog.ts / -priority.ts / -action.ts / camera-framing.ts │ └── (각 모듈에 짝지어진 *.test.ts) ├── electron/ ← ②③ Electron 메인·preload (CJS) │ ├── main.cjs (421줄) 오케스트레이터 │ ├── bridge-server.cjs 로컬 HTTP + /events + /mcp + /health │ ├── mcp-server.cjs MCP 툴 3종 정의 │ ├── audio-listener.cjs 팩토리(platform→적절한 리스너) │ ├── linux-pipewire-listener.cjs │ ├── native-process-audio-listener.cjs mac/win 헬퍼 spawn+NDJSON │ ├── audio-activity-gate.cjs 공유 침묵 스무딩 │ ├── process-discovery.cjs ps / Get-CimInstance 파싱 │ ├── protocol-actions.cjs persona:// URL 파서 │ ├── hyprland-window.cjs / navigation-policy.cjs / preload.cjs │ └── (거의 모든 파일에 *.test.cjs 동반) ├── native/ ← ① 네이티브 오디오 캡처 │ ├── macos/PersonaAudioListener.mm (280줄, Core Audio tap) │ └── windows/PersonaAudioListener.cpp (255줄, WASAPI loopback) ├── scripts/ 빌드·에셋검사·체크섬 (전부 *.test.cjs 동반) ├── public/assets/ manifest.json(라이선스 슬롯); .vrm/.vrma는 gitignore ├── build/ 아이콘, mac entitlements ├── docs/ DEVELOPMENT / INTEGRATIONS / RELEASING ├── .github/workflows/ ci.yml, release.yml └── package.json · vite/vitest/tsconfig · eslint.config.js

구조에서 읽히는 의도는 "권한별 격리"다. 위험한 것(오디오 캡처)은 native/에 가두고, 그 결과만 electron/이 받아 정규화하고, 화면(src/)은 결론만 본다. 그리고 거의 모든 모듈에 짝지어진 테스트 파일이 있다 — node --test가 CJS를, Vitest가 렌더러를 검증한다.

주의
에셋은 리포에 없다 · fail-closed 게이트

캐릭터 모델(.vrm/.vrma)은 리포에 커밋되지 않는다(테스트 전용이고 별도 라이선스). 애플리케이션 소스는 MIT지만 번들 에셋은 제외되며, 배포 워크플로는 distributionAllowed:true + 라이선스 메타데이터가 완성되기 전엔 빌드를 막는다(fail-closed). 그래서 GitHub API가 라이선스를 "Other/NOASSERTION"으로 잡는다. 직접 돌리려면 본인 VRM 아바타를 넣어야 한다.

6학습 포인트 — 기술별 배울 것

이 레포를 뜯으며 실제로 손에 남는 것

1) OS 시스템 오디오 캡처 API 3종 (가장 희귀한 것)

특정 앱의 소리만 캡처하는 API는 흔히 다루지 않는 저수준 영역이다. macOS Core Audio Process Tap(AudioHardwareCreateProcessTap, CATapDescription), Windows WASAPI Process Loopback(ActivateAudioInterfaceAsync + AUDIOCLIENT_ACTIVATION_TYPE_PROCESS_LOOPBACK), Linux PipeWire(pw-dump 그래프 조회 / pw-record 캡처) — 셋을 한 프로젝트에서 비교하며 배울 기회는 드물다.

2) Electron 보안을 제대로 하는 법

많은 Electron 앱이 편의를 위해 nodeIntegration을 켠다. Persona는 정반대로, contextIsolation·sandbox·preload 최소 노출·CSP·네비게이션 화이트리스트까지 "보안 기본형"을 전부 적용한 살아있는 예제다. preload.cjsnavigation-policy.cjs가 교과서.

3) MCP 서버 직접 만들기

@modelcontextprotocol/sdkMcpServer.registerTool + zod 스키마 + annotations(readOnlyHint 등) + Streamable HTTP stateless 패턴. "내 앱을 에이전트가 조종하게 만드는" 최소 구현을 mcp-server.cjs 한 파일에서 볼 수 있다.

4) VRM/VRMA 3D 아바타 파이프라인

three-vrm의 expressionManager(비세임·blink), AnimationMixer 크로스페이드, VRMUtils 최적화(combineSkeletons, combineMorphs, rotateVRM0). "3D 캐릭터를 웹 기술로 실시간 구동"하는 실전 코드다.

5) 크로스플랫폼 추상화 설계

세 OS를 하나의 리스너 계약(onLevel/onActivity/onSession/onStatus)으로 통일하고, RMS 정규화 공식을 공유하는 법. "구현은 달라도 인터페이스는 하나"라는 원칙의 모범.

용어
viseme (비세임)
phoneme(음소, 소리 단위)에 대응하는 입모양 단위. "아/이/우"를 발음할 때의 입 모양들. VRM 표정 시스템에 aa/ih/ou 등으로 정의돼 있어, 이걸 순환시키면 말하는 입모양이 된다. Persona는 소리를 분석하지 않고 진폭+시간으로 비세임을 가짜로 순환시킨다.

7시스템 / 실행 요구사항

돌리려면 무엇이 필요한가
항목요구 / 권장
런타임Node.js 24+, npm, 하드웨어 가속 그래픽 세션
macOS14.2+ · System Audio Recording 권한 1회 승인 · Xcode CLT · arm64/x64 DMG+ZIP
Windowsbuild 20348+(프로세스 루프백) · VS Build Tools C++ 워크로드(빌드 시) · NSIS
Linuxpw-dump/pw-record가 PATH에(PipeWire) · AppImage/DEB
캐릭터 에셋본인 VRM/VRMA 필요 — 리포에 미포함
에이전트 연동(선택)Codex 등 MCP 클라이언트 — codex mcp add persona --url .../mcp
주의
beta 단계 · 시스템 오디오 권한

Persona는 0.1.0-beta.0, 2026-07-28 생성된 초기 프로젝트다. CHANGELOG도 "active development" 상태. macOS는 시스템 오디오 녹음 권한을 요구하는데(민감 권한), 이 권한은 원리상 앱이 시스템 소리를 들을 수 있음을 뜻한다 — Persona는 그걸 진폭 계산에만 쓰고 즉시 버린다고 코드로 보장하지만, 권한 부여 전 소스를 한 번 확인하는 습관이 좋다.

8직접 해볼 수 있는 실습 과제

읽기만 하지 말고 손으로 — 난이도별
LV.1 — 입문 30분

persona:// URL로 상태 흉내내기

앱을 띄운 뒤 xdg-open 'persona://speaking?level=0.5'(mac/win은 해당 URL 열기)로 아바타가 입을 벌리는지 관찰하자. 트레이 메뉴의 "Preview dance"로 MCP 없이 애니메이션을 미리 볼 수도 있다. protocol-actions.cjs가 이 URL을 어떻게 파싱하는지 읽어 보라.

LV.2 — 기초 1~2시간

로컬 브리지에 직접 이벤트 쏘기

curlhttp://127.0.0.1:47831/events에 state/audio-level/animation JSON을 POST해서(INTEGRATIONS.md 예시 참고), 아바타를 코드 없이 움직여 보자. Origin/Host 검사에 걸려 403이 나는 것도 일부러 재현해 보안 로직을 체감하라.

LV.3 — 중급 반나절

립싱크 파라미터 튜닝

useAmplitudeLipSync.ts의 순환 속도·상승/하강 스무딩(0.055s/0.1s)을 바꿔 가며, 입모양이 소리에 얼마나 붙거나 늘어지는지 관찰하자. "더 수다스러운" 느낌과 "차분한" 느낌을 각각 만들어 보라.

LV.4 — 심화 1~2일

새 애니메이션 계약을 end-to-end로 추가

animation-catalog.ts에 새 타입(예: THINK)을 추가하고, mcp-server.cjsANIMATION_EVENT_NAMESprotocol-actions.cjs에 연결해 에이전트가 부를 수 있게 하자. 대응 테스트도 추가해 계약이 깨지지 않게 하라.

LV.5 — 도전 주말 프로젝트

다른 음성 앱을 타겟으로 붙이기

PERSONA_TARGET_PROCESS_PATTERN 정규식을 바꿔 Codex 대신 다른 음성 앱(예: 특정 미디어 플레이어·회의 앱)의 출력에 아바타를 반응시켜 보자. process-discovery.cjs와 프로세스 트리 매칭 로직을 추적하며 왜 그 앱만 잡히는지 이해하라.

9관련 기술 심화 로드맵 (주차별)

Persona를 발판 삼아 데스크톱·오디오·3D로
주차주제무엇을 / 왜
1주차디지털 오디오 기초PCM·샘플레이트·RMS 진폭·노이즈 플로어. "소리가 사실은 숫자 배열"이라는 관점 잡기.
2주차시스템 오디오 캡처macOS Core Audio Tap / Windows WASAPI Loopback / Linux PipeWire. 프로세스별 오디오 스코프의 원리.
3주차Electron 아키텍처·보안메인/렌더러/preload 3분할, contextIsolation·sandbox·IPC. Persona의 4계층 재현.
4주차WebGL / Three.js씬·카메라·조명·머티리얼, react-three-fiber 선언적 렌더링. Scene.tsx/Avatar.tsx 분석.
5주차VRM 아바타·애니메이션three-vrm expressionManager, AnimationMixer 크로스페이드, VRMA 클립. 립싱크·blink 구현.
6주차MCP 서버 구축@modelcontextprotocol/sdk, zod 스키마, Streamable HTTP stateless. 내 앱을 에이전트 도구로.
7주차크로스플랫폼 배포electron-builder, 코드사이닝·entitlements, 체크섬·릴리스 게이트. fail-closed 파이프라인.
8주차종합 프로젝트나만의 "소리에 반응하는 데스크톱 위젯"을 처음부터 빌드해 3 OS로 배포까지.

10핵심 키워드 사전

이 문서에 나온 용어 총정리
용어
VRM / VRMA
VRM=3D 휴머노이드 아바타 표준 파일(.vrm, 표정·뼈대 포함). VRMA=그 아바타용 모션 클립(.vrma). VTuber·메타버스 표준.
용어
RMS 진폭
오디오 파형의 평균 세기. Persona는 소리 내용이 아니라 이 세기만 0~1로 정규화해 아바타 입 크기에 쓴다.
용어
viseme (비세임)
음소에 대응하는 입모양 단위. VRM 표정 aa/ih/ou 등. 순환시키면 말하는 입모양이 된다.
용어
Core Audio Process Tap
macOS 14.2+에서 특정 프로세스의 오디오 출력만 가로채는 API(CATapDescription). Persona의 mac 리스너.
용어
WASAPI Process Loopback
Windows에서 특정 프로세스 트리의 재생 오디오를 캡처하는 API(PROCESS_LOOPBACK_MODE). Persona의 win 리스너.
용어
PipeWire (pw-dump/pw-record)
리눅스 최신 오디오/비디오 서버. pw-dump로 그래프를 조회하고 pw-record로 스트림을 캡처. Persona는 CLI를 spawn해 쓴다.
용어
MCP (Model Context Protocol)
AI 에이전트가 외부 도구를 표준으로 호출하는 프로토콜. Persona는 MCP 서버가 되어 에이전트에게 애니메이션·창 제어 툴을 제공.
용어
contextIsolation / sandbox / preload
Electron 보안 3요소. 렌더러를 격리(isolation)·샌드박스화하고, preload로 최소 API만 화이트리스트해 노출.
용어
AudioActivityGate
짧은 침묵을 스무딩하는 상태머신. 레벨>0.018이면 speaking, 임계 이하로 떨어져도 900ms 유지해 몸동작이 튀는 것을 방지.
용어
fail-closed 게이트
조건(라이선스 메타데이터·distributionAllowed)이 충족되기 전엔 기본적으로 빌드를 막는 방식. "허용을 명시하기 전엔 금지".

11참고 링크

더 파고들 때