이 앱이 무엇을 하는 물건인가.
FluidVoice는 macOS 전용 오프라인 받아쓰기 앱이다. 전역 단축키를 누르면 마이크가 켜지고, 말이 끝나면 AI 모델이 즉시 텍스트를 만들어 지금 쓰고 있는 앱 — 슬랙이든, 노션이든, 터미널이든 — 에 직접 타이핑해준다.
특이한 점은 이 모든 추론이 내 Mac 안에서만 일어난다는 것. Apple Silicon의 Neural Engine(ANE)을 CoreML로 직접 두드려 속도를 짜냈다.
STT (Speech-to-Text, 음성-텍스트 변환)는 오랫동안 클라우드 서비스의 영역이었다. 마이크 소리를 서버로 보내고, 서버가 텍스트를 돌려보내는 구조. 하지만 FluidVoice는 온디바이스 추론(on-device inference) — 즉 클라우드에 안 보내고 내 노트북 안에서 다 처리 — 을 선택했다. 2025년 말 오픈소스로 공개된 뒤 빠르게 스타를 모았고, 2026년에 Parakeet 실시간 스트리밍과 Fluid Intelligence(로컬 AI 후처리)를 추가하며 주목을 받았다.
트렌딩 이유 · 경쟁 대비 장점.
프라이버시 + 속도 + 무료 오픈소스 세 가지를 동시에 잡은 게 핵심이다. Whisper.app, Apple 기본 받아쓰기, Otter.ai 같은 경쟁 솔루션은 각각 하나씩 약점이 있다. FluidVoice는 이 빈틈을 파고들었다.
| 비교 대상 | 한계 | FluidVoice의 접근 |
|---|---|---|
| Apple 기본 받아쓰기 | 항상 Apple 서버 전송, 전역 단축키 제한적 | 100% 오프라인, 자유로운 전역 단축키 |
| Otter.ai / 클라우드 STT | 유료 구독, 음성·텍스트가 서버에 저장됨 | 무료 오픈소스, 데이터 완전 로컬 처리 |
| SuperWhisper (유료 앱) | 유료($49+), Whisper만 지원 | GPL-3.0 무료, 6가지 모델 선택 가능 |
| 직접 whisper.cpp 실행 | CLI, UI 없음, 앱 간 통합 불가 | GUI + 메뉴바 + Accessibility 타이핑 통합 |
회의 내용, 비밀 문서, 개인 메모를 말로 입력할 때마다 음성이 외부 서버로 전송된다. 의료·법률·기업 보안 환경에서는 사용 자체가 금지되는 경우가 많다.
마이크 → CoreML 추론 → 텍스트 삽입까지 네트워크 요청이 전혀 없다. 오프라인 환경에서도, 비행기 안에서도 작동한다. 음성 데이터는 옵션으로 로컬 히스토리에만 저장된다.
기술적으로도 차별화가 크다. FluidVoice는 Parakeet Flash 모델을 실시간 스트리밍으로 구현해 말하면서 동시에 텍스트가 화면에 나타난다. 내부 벤치마크 로그를 보면 RTF(Real-Time Factor, 음성 길이 대비 추론 시간)가 0.1 미만으로 측정되는 구간이 있다 — 즉 1초 말한 것을 0.1초 만에 처리한다는 의미다. Apple Silicon의 ANE(Apple Neural Engine, 애플 신경망 엔진)를 CoreML로 풀가동한 결과다.
Swift·AVFoundation·CoreML·단축키 프레임워크 한눈에.
| 레이어 | 기술 / 라이브러리 | 역할 |
|---|---|---|
| 앱 언어 | Swift 5.9+ | macOS 네이티브 앱 전체 구현. |
| UI 프레임워크 | SwiftUI + AppKit | 설정창·온보딩·녹음 오버레이 UI. AppKit은 메뉴바·NSStatusItem 등 low-level 제어. |
| 오디오 캡처 | AVFoundation (AVAudioEngine) | 마이크 입력 탭(tap), 16kHz PCM Float32 샘플 실시간 수집. |
| STT 모델 실행 | CoreML | Parakeet/Nemotron/Cohere 모델을 Apple Silicon ANE에서 실행. FluidAudio 라이브러리가 래핑. |
| STT 엔진(Whisper) | SwiftWhisper (whisper.cpp 래핑) | Intel Mac 포함 범용 Whisper 추론. ggml 포맷 모델 사용. |
| STT 엔진(Parakeet·Nemotron) | FluidAudio (자체 라이브러리) | CoreML 기반 Parakeet TDT / Nemotron 추론, 어휘 부스팅, 스트리밍 지원. |
| Apple 기본 STT | Speech 프레임워크 | Apple 내장 음성인식. 다운로드 불필요하지만 클라우드 전송 가능성 있음. |
| 텍스트 삽입 | Accessibility API (AX) | AXUIElement로 포커스된 앱 텍스트 필드에 직접 삽입. CGEvent 키보드 이벤트 폴백. |
| 전역 단축키 | Carbon.HIToolbox | 시스템 전역 단축키 등록. 어느 앱이 포커스여도 단축키 감지. |
| AI 후처리 | LLMClient (내부 구현) | OpenAI/Groq/커스텀 API + 로컬 Fluid Intelligence로 텍스트 품질 향상. |
| 로컬 API | Network.framework (NWListener) | 앱 내부 localhost HTTP API. 외부 도구가 받아쓰기 결과를 읽거나 트리거할 수 있음. |
| 모델 다운로드 | URLSession + HuggingFace | Parakeet/Nemotron은 HuggingFace에서, Whisper는 직접 CDN에서 다운로드. |
| 의존성 관리 | Swift Package Manager (SPM) | AppUpdater, PromiseKit, DynamicNotchKit, SwiftWhisper, PostHog 관리. |
| 분석 | PostHog (opt-in) | 익명 사용 통계. 음성·텍스트는 수집하지 않음. |
"단축키 → 녹음 → 추론 → 텍스트 삽입" 한 줄기를 끝까지 따라가기.
FluidVoice의 핵심 파이프라인은 단순하다. 사용자가 단축키를 누르면 ASRService가 깨어나고, 마이크 샘플이 쌓이고, 손을 떼면 모델이 텍스트를 뽑고, TypingService가 활성 앱에 타이핑해준다. 전역 독수리 시점으로 먼저 보자.
① GlobalHotkeyManager가 Carbon 이벤트를 감지 → ASRService.start() 호출
② AVAudioEngine의 inputNode에 installTap으로 콜백을 달아 마이크 오디오를 16kHz Float32 샘플 배열로 수집
③ 실시간 스트리밍 모드면 FluidAudioProvider.transcribeStreaming()에 샘플을 주기적으로 넘겨 라이브 자막 표시
④ 단축키를 떼면 ASRService.stop() → FluidAudioProvider.transcribeFinal()이 전체 샘플로 최종 추론
// ASRService.swift — 핵심 흐름 (단순화)
func start() async {
engine.prepare() // AVAudioEngine 준비
engine.inputNode.installTap(...) { buffer, _ in
self.audioBuffer.append(buffer) // PCM 샘플 누적
}
try engine.start()
}
func stop() async -> String {
engine.stop()
let samples = audioBuffer.allSamples()
let result = try await provider.transcribeFinal(samples)
return result.text
}
⑤ AI 후처리가 켜져 있으면 DictationPostProcessingService가 LLMClient를 통해 텍스트를 다듬음
⑥ 최종 텍스트가 TypingService로 전달 → AXUIElement API로 포커스된 텍스트 필드에 직접 삽입. AX 실패 시 클립보드 붙여넣기, 그것도 실패 시 CGEvent 키 이벤트로 폴백
TranscriptionProvider 프로토콜을 구현한다. ASRService는 어떤 엔진인지 알 필요 없이 provider.transcribe(samples)만 부른다. 새 모델을 추가할 때도 ASRService 코드를 건드릴 필요가 없다.TypingService의 3단 폴백은 호텔 체크인과 같다. 1순위: 체크인 카운터에서 직접 열쇠를 준다(AX API). 카운터가 닫혔으면 2순위: 열쇠 함에서 뽑아간다(클립보드 붙여넣기). 그것도 안 되면 3순위: 직원이 각 글자를 한 땀 한 땀 눌러준다(CGEvent 키 이벤트). 어떤 앱에서도 결국 텍스트가 들어간다.
어떤 폴더가 무슨 일을 하나.
| 파일 / 폴더 | 역할 요약 |
|---|---|
ASRService.swift | 앱의 심장. 녹음 시작/중지, 모델 선택, 스트리밍 미리보기, 상태 관리 모두 담당. |
FluidAudioProvider.swift | Parakeet TDT v2/v3·Flash CoreML 추론. 스트리밍용·최종용 AsrManager 두 개를 따로 유지해 vocab boosting 충돌 방지. |
TypingService.swift | 텍스트를 실제 앱에 넣는 유일한 경로. TIS 키보드 레이아웃 감지로 비-QWERTY에서도 정확히 타이핑. |
GlobalHotkeyManager.swift | Carbon HIToolbox로 시스템 전역 단축키 등록. 1847줄짜리 파일 — 다양한 엣지케이스 처리 포함. |
LocalAPI/ | localhost NWListener 기반 HTTP API. 외부 스크립트·앱이 FluidVoice와 통신 가능. |
SettingsStore.swift | 4000줄+ 설정 허브. SpeechModel 열거형으로 6가지 엔진을 통합 관리. |
이 레포에서 배울 만한 것 + 어디를 보면 되는지.
FluidVoice는 도크 아이콘이 없는 순수 메뉴바 앱이다. NSStatusItem(메뉴바 아이콘)으로만 접근하고, SwiftUI 창은 보조 역할을 한다. AppKit과 SwiftUI를 어떻게 섞는지 — NSHostingController, @MainActor, ObservableObject — 를 실제 코드로 배울 수 있다.
볼 파일: AppDelegate.swift, MenuBarManager.swift, fluidApp.swift
AVAudioEngine의 installTap으로 실시간 PCM 데이터를 가로채는 패턴, 16kHz 리샘플링, 스레드 안전한 버퍼(ThreadSafeAudioBuffer) 설계를 배울 수 있다. CoreAudio 장치 열거(AudioObjectGetPropertyData)도 포함된다.
볼 파일: ASRService.swift(오디오 탭 부분), AudioDeviceService.swift, ThreadSafeAudioBuffer.swift
// AVAudioEngine 탭 패턴 (개념 코드)
engine.inputNode.installTap(
onBus: 0,
bufferSize: 1024,
format: inputFormat
) { buffer, time in
self.audioBuffer.append(buffer) // 실시간 PCM 누적
}
HuggingFace에서 .mlpackage 파일을 다운로드하고, CoreML로 컴파일·로드해 추론하는 전체 과정이 FluidAudioProvider에 담겨 있다. Apple Silicon의 ANE를 활용하는 법, 추론 직렬화(TranscriptionExecutor actor)로 CoreML 경쟁 조건을 막는 법을 배울 수 있다.
볼 파일: FluidAudioProvider.swift, ExternalCoreMLModelRegistry.swift, NemotronProvider.swift
macOS Accessibility API(AXUIElement)는 외부 앱의 UI 요소를 코드로 제어하는 공식 통로다. FluidVoice의 TypingService는 이를 이용해 슬랙·VS Code·터미널 등 어떤 앱에도 텍스트를 직접 쓴다. 폴백 체인(AX → 클립보드 → CGEvent)도 함께 배울 수 있다.
볼 파일: TypingService.swift(3단 폴백 구현)
어떤 앱이 포커스여도 단축키를 잡으려면 macOS의 Accessibility 권한이 필요하다. AXIsProcessTrusted()로 권한을 확인하고, Carbon의 RegisterEventHotKey로 글로벌 단축키를 등록하는 패턴을 배울 수 있다.
볼 파일: GlobalHotkeyManager.swift, Fluid.entitlements
돌리려면 무엇이 필요한가 (README 근거, 추정은 명시).
| 항목 | 요구사항 |
|---|---|
| 운영체제 | macOS 15.0 Sequoia 이상 (Package.swift에서 .macOS(.v15) 명시) |
| CPU 아키텍처 | Apple Silicon (M1~M4) 권장 — Parakeet·Nemotron·Cohere 모델 사용 가능. Intel Mac은 Whisper 모델만 지원 (v1.5.1부터) |
| RAM | 공식 미명시. 추정: 최소 8GB (CoreML 추론 + 앱). 16GB 권장 (Nemotron 스트리밍 + Fluid Intelligence 동시 운용 시) |
| 디스크 공간 | STT 모델 1개: ~250MB(Parakeet Flash) ~ 1.4GB(Cohere). Fluid Intelligence 로컬 AI: 약 3.5GB 추가 (선택적) |
| 마이크 | 내장 또는 외장 마이크 (macOS 마이크 권한 필요) |
| Accessibility 권한 | 시스템 환경설정 → 개인 정보 및 보안 → 손쉬운 사용에서 FluidVoice 허용 필수 |
| 인터넷 | 최초 모델 다운로드 시 필요. 이후 완전 오프라인 작동 |
| Xcode (빌드 시) | Xcode 15+ (Swift 5.9 지원), SPM 의존성 자동 해결 |
STT 모델은 스마트폰 카메라 렌즈와 비슷하다. Parakeet Flash(250MB)는 광각 렌즈 — 빠르지만 영어 전용. Nemotron 3.5(670MB)는 줌 렌즈 — 40개 언어를 커버하지만 더 느림. Whisper Large(2.9GB)는 망원경 — 99개 언어, 최고 정확도, 느림. 상황에 맞게 고르면 된다.
난이도별로 손에 익히는 단계.
터미널에서 brew install --cask fluidvoice 실행 → 앱 실행 → 온보딩에서 Parakeet TDT v3 선택(다운로드 약 500MB) → 전역 단축키 설정 → 메모장(TextEdit)을 열고 단축키를 눌러 "안녕하세요 테스트입니다"라고 말해보기. 텍스트가 나타나면 성공.
확인 포인트: 마이크 권한 허용 여부, Accessibility 권한 시스템 설정에서 활성화.
레포를 클론하고 Xcode에서 열어 Personal Team으로 서명 설정 후 빌드·실행. 받아쓰기를 하면서 Xcode Debug Console에서 ASR_BENCH 키워드가 있는 로그를 찾아보자. rtf=0.08 같은 값이 Real-Time Factor — 음성 1초를 0.08초 만에 처리했다는 의미다. Apple Silicon ANE의 위력을 실측으로 확인하는 실습이다.
관련 로그 출력 위치: FluidAudioProvider.swift 내 logFinalBenchmark() 함수.
TranscriptionProvider 프로토콜을 읽고, MockTranscriptionProvider를 직접 작성해보자. prepare()는 빈 구현, transcribe()는 항상 "테스트 텍스트"를 반환하는 가짜 엔진. ASRService에 주입해서 모델 없이도 타이핑까지 이어지는지 확인한다. Swift의 프로토콜 추상화가 얼마나 테스트를 쉽게 만드는지 체감할 수 있다.
설정에서 Whisper Tiny → Whisper Base → Whisper Small을 각각 선택하고, 같은 문장을 5번씩 받아써서 정확도와 응답 속도를 비교 표로 만들어보자. 모델 크기·파라미터 수·품질·속도의 트레이드오프를 몸으로 이해하는 실습이다.
FluidVoice가 켜진 상태에서 LocalAPIServer가 localhost에서 HTTP 서버를 연다. 소스의 LocalAPIRouter.swift를 읽어 엔드포인트를 파악하고, Python이나 curl로 받아쓰기 결과를 폴링하는 스크립트를 작성해보자. 앱 간 통합의 가능성을 탐색하는 고급 실습이다.
한 주씩 따라가는 계획.
| 주차 | 주제 | 학습 자료 |
|---|---|---|
| 1주차 | Swift 기초 + macOS 앱 구조 이해 | Apple Swift 공식 투어, Hacking with Swift macOS 챕터 |
| 2주차 | SwiftUI + AppKit 혼합 패턴 | WWDC "Integrate SwiftUI" 세션, AppDelegate.swift 정독 |
| 3주차 | AVFoundation 오디오 캡처 | Apple AVAudioEngine 문서, ASRService.swift 오디오 탭 부분 |
| 4주차 | CoreML 기초 + 모델 실행 | Apple CoreML 문서, Create ML 앱 튜토리얼, FluidAudioProvider.swift |
| 5주차 | Accessibility API + 전역 단축키 | Apple Accessibility Programming Guide, TypingService.swift + GlobalHotkeyManager.swift |
| 6주차 | Whisper / Parakeet 모델 구조 이해 | OpenAI Whisper 논문, NVIDIA Parakeet HuggingFace 카드, SwiftWhisper README |
| 7주차 | Swift Concurrency (async/await, Actor) | WWDC "Swift concurrency" 세션, TranscriptionExecutor actor 코드 분석 |
| 8주차 | 나만의 macOS 유틸리티 앱 제작 | FluidVoice 포크 → 기능 하나 추가 or 미니 메뉴바 앱 직접 제작 |
본문에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| STT | Speech-to-Text. 음성을 텍스트로 변환하는 기술의 총칭. |
| ASR | Automatic Speech Recognition. STT와 거의 같은 의미로 혼용. |
| 온디바이스 추론 | 서버가 아닌 사용자 기기(Mac, 스마트폰 등)에서 AI 모델을 직접 실행하는 것. |
| CoreML | Apple의 기기 내 ML 모델 실행 프레임워크. .mlpackage 포맷 지원. |
| ANE | Apple Neural Engine. Apple Silicon 칩 내부의 AI 전용 연산 유닛. |
| Parakeet | NVIDIA가 만든 경량 고속 영어/다국어 ASR 모델. CTC/TDT 아키텍처. |
| Whisper | OpenAI의 오픈소스 다국어 STT 모델. ggml 포맷으로 PC에서 실행 가능. |
| Nemotron | NVIDIA의 멀티언어 스트리밍 ASR 모델 (3.5 버전). 약 40개 언어 지원. |
| AVAudioEngine | macOS/iOS의 오디오 처리 파이프라인 클래스. 실시간 마이크 탭 가능. |
| AXUIElement | macOS Accessibility API의 UI 요소 참조 타입. 외부 앱 텍스트 필드에 접근 가능. |
| Carbon HIToolbox | macOS의 구형 C API. 전역 단축키 등록에 아직도 사용됨(RegisterEventHotKey). |
| RTF | Real-Time Factor. 오디오 1초를 처리하는 데 걸린 시간. 1.0 미만이면 실시간 이상. |
| SPM | Swift Package Manager. Swift 프로젝트의 의존성 관리 도구. |
| NSStatusItem | macOS 메뉴바에 아이콘을 추가하는 AppKit 클래스. |
| Vocab Boosting | 사용자 정의 단어를 모델에 힌트로 주어 특정 단어 인식률을 높이는 기법. |
| Fluid Intelligence | FluidVoice의 로컬 AI 후처리 엔진(비공개). 스마트 포맷팅·대소문자 정규화. |
| Dictation | 받아쓰기. 말로 텍스트를 입력하는 것. |
| @MainActor | Swift Concurrency 수식어. 해당 타입/함수가 메인 스레드에서만 실행됨을 보장. |