C.W.K.
Stream
Lesson 02 of 04 · published

받아쓰기 하나의 해부

~12 min · loop-anatomy, target-snapshot, pre-roll, capture

Level 0불씨 없음
0 XP0/36 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"제일 위험한 두 순간은 첫 밀리초와 마지막 밀리초야. 나머지는 다 기록 관리야."

누르고, 말하고, 놓고

받아쓰기 하나는 단축키에서 깔끔히 쪼개져. 키-누름에 두 개를 동시에 잡아: 타깃 스냅샷(지금 누가 포커스인가)과 오디오 스트림(마이크가 이미 데워진 채로). 키-놓음에 전사를 확정하고, 정리하고, 스냅샷한 타깃에 삽입해. 컨트롤러는 일부러 작아:

DictationController.start(mode):
    snapshot = TargetCapture.snapshot()   # 최전면 앱, 포커스된 요소, 선택
    Audio.start()                          # 짧은 프리롤과 함께 캡처 시작

DictationController.stop():
    audio = Audio.finish()                 # 마지막 음절 살리게 짧은 꼬리 추가
    text  = STT.finalize(audio)
    clean = Cleanup.run(text, snapshot)
    Insertion.insert(clean, into: snapshot)

첫 밀리초: 프리롤

사람은 손가락이 다 누르기 전에 말하기 시작해. 캡처가 키-누름 정확히 그 순간에 시작하면 첫 음절이 이미 사라졌어 — "보내" 가 "내" 가 돼. 해법은 프리롤: 짧은 롤링 버퍼를 유지해서 확정하는 오디오가 키가 등록되기 ~반 초를 포함하게. 근데 프리롤은 프라이버시랑 싸워 — 진짜 롤링 버퍼는 받아쓰기 사이에 마이크가 늘 살아있다는 뜻이야. Firekeeper 의 솔직한 트레이드오프는 늘-켜진 버퍼 대신 실행 시 마이크 권한을 요청하고 엔진을 빨리 시작하는 거야, 왜냐면 보이는-녹음-만 규칙이 숨은-늘-듣기 보다 나으니까.

마지막 밀리초: 꼬리

거울 문제가 끝에 있어. 사람은 마지막 단어를 끝내는 순간 키를 놓아 — 때론 살짝 일찍. 오디오를 키-놓음 정확히 그 순간에 자르면 마지막 음절이 잘려. 그래서 캡처는 확정 전에 키-놓음 후 짧은 꼬리(Firekeeper 는 대략 4분의 1초) 동안 계속 돌아. 앞뒤 두 개의 아주 작은 시간-이동이 '전사가 고장 났어' 와 '전사가 완벽해' 의 차이야.

녹음의 가운데만 말고 가장자리를 지켜. 발화 가운데의 STT 품질은 모델 일이야. 첫 음절과 마지막 음절은 일이야 — 프리롤이 시작을 지키고, 놓음 꼬리가 끝을 지켜. 가장자리를 틀리면 유저는 네가 만든 버그를 모델 탓해.
침묵 감지기가 첫 단어를 먹게 절대 두지 마. 음성 활동 감지(VAD)는 죽은 공기를 다듬고 끝점 찾는 데 좋아 — 근데 프리롤 전에 돌면, 조용한 도입부를 '아무것도 아님' 으로 판단해 네가 필요했던 바로 그 음절을 버릴 수 있어. 순서가 중요해: 프리롤 유지 후 침묵 다듬기, 절대 반대로 하지 마.

Code

코어 컨트롤러: 타깃을 먼저 잡고, 그다음 오디오·swift
func start(mode: DictationMode) {
    // 오버레이가 포커스를 옮기기 전에 타깃을 스냅샷.
    self.snapshot = targetCapture.snapshot()   // 앱, 요소, 선택
    audio.start()                              // 실행 때부터 엔진 이미 데워짐
    phase = .recording
}

func stop() async {
    // 마지막 음절 안 잘리게 짧은 꼬리 유지.
    let clip = await audio.finish(tail: .milliseconds(280))
    guard clip.hasSpeech else { return }       // 침묵 게이트: 아무것도 없으면 STT 안 부름
    let raw = await stt.finalize(clip)
    let result = await cleanup.run(raw, context: snapshot)
    await insertion.insert(result, into: snapshot)
}

External links

Exercise

짧은 문구를 말하면서 마지막 단어를 끝내는 순간 일부러 '정지' 를 놓아 녹음해봐. 다시 들어봐. 마지막 자음이 살았어? 이제 코드에 놓음 꼬리가 없다고 상상해 — 유저가 신고할 정확한 버그를 묘사하고, 왜 엉뚱한 컴포넌트를 탓할지 설명해.
Hint
꼬리가 없으면 유저는 '자꾸 마지막 단어를 잘라' 라고 신고하고 음성 모델을 탓해. 근데 모델은 마지막 단어를 들은 적이 없어 — 앱이 마이크를 너무 일찍 멈춘 거야. 그 오귀속이 가장자리-지키기가 모델 튜닝이 아니라 앱 책임인 이유야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.