"제일 위험한 두 순간은 첫 밀리초와 마지막 밀리초야. 나머지는 다 기록 관리야."
누르고, 말하고, 놓고
받아쓰기 한 번은 단축키를 기준으로 깔끔하게 쪼개져. 키를 누르는 순간 두 가지를 동시에 잡아. 타깃 스냅샷(지금 누가 포커스를 쥐고 있나) 이랑 오디오 스트림(마이크는 이미 데워져 있고). 키를 놓으면 전사를 확정하고, 정리하고, 아까 잡아둔 타깃에 삽입해. 컨트롤러는 일부러 작게 뒀어.
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초쯤 둬. 앞뒤로 살짝씩 시간을 밀어 준 이 둘 때문에 '전사가 고장 났어' 가 '전사가 완벽해' 로 바뀌는 거야.