Voice dictation, sotto voce — голосовая диктовка для macOS, полностью на устройстве.
![]() |
![]() |
| Кнопки управления записью · Recording controls | Меню в menu bar · Menu bar dropdown |
📝 Этот проект основан на simicvm/whisper (MIT). Сохранена оригинальная архитектура (state machine, MLX-инференс, smart paste), но интерфейс, UX-логика записи и языковая поддержка существенно переработаны. Подробности — в разделе Благодарности.
Sotto (от итал. sotto voce — «вполголоса») — приложение для голосовой диктовки на macOS, которое работает полностью на вашем компьютере. Никаких облаков, никакой отправки звука куда-либо. Аудио → текст происходит локально через Qwen3 ASR на базе Apple MLX.
Принцип работы простой: нажали хоткей → надиктовали → нажали ещё раз (или зелёную кнопку «Отправить») → текст автоматически вставляется в активное приложение через симуляцию Cmd+V.
- Tap-to-toggle хоткей — одно нажатие начинает запись, второе — заканчивает и транскрибирует. Удерживать ничего не нужно.
- Интерактивная пилюля во время записи — три кнопки прямо в оверлее:
- ❌ Отмена (красная) — выбросить запись и вернуться в idle
- ⏸️ Пауза / возобновление (белая) — приостановить захват без потери уже записанного
- 📤 Отправить (зелёная) — закончить запись досрочно и транскрибировать
- Двуязычная диктовка — переключатель Русский / English в menu bar. Применяется к следующей записи без перезагрузки модели.
- Дизайн «Siri Aura» — пилюля с угловым градиентом (синий → пурпурный → оранжевый), вращающимся со скоростью 360° за 6 секунд, и шестиполосным эквалайзером, реагирующим на громкость микрофона в реальном времени.
- Перетаскиваемый оверлей — потяните пилюлю мышью, чтобы поставить её в удобное место.
- Три модели на выбор — Qwen3 ASR
0.6B (8-bit),1.7B (8-bit),1.7B (4-bit). Скачиваются по требованию из HuggingFace, удаляются одной кнопкой в меню. - 5 пресетов хоткея — комбинации двух модификаторов с различием левых и правых клавиш (чтобы не пересекаться с системными шорткатами).
- Smart paste — текст вставляется через
Cmd+Vсимуляцией Accessibility API; оригинальный буфер обмена сохраняется и восстанавливается; пробел добавляется автоматически, если курсор стоит после непробельного символа. - Автозапуск при старте Mac — переключатель в меню (через
SMAppService). - Полная локализация UI на русский язык.
- Работает офлайн — после однократной загрузки модели интернет вообще не нужен.
- macOS 15.0+ (Sequoia или новее)
- Apple Silicon настоятельно рекомендуется — на Intel MLX работает медленно
- Разрешения Микрофон и Универсальный доступ
- ~1 GB свободного места на модель по умолчанию (
Qwen3 ASR 0.6B 8-bit)
- Скачайте
sotto.zipиз раздела Releases (или по прямой ссылке на последний релиз:sotto.zip). - Дважды кликните по архиву — macOS распакует
sotto.app. - Перетащите
sotto.appв/Applications. - Если macOS заблокирует первый запуск (это нормально для не-нотаризованных приложений) — кликните правой кнопкой → Открыть → подтвердите. Альтернатива — снять карантин в терминале:
xattr -dr com.apple.quarantine /Applications/sotto.app
- Запустите:
open /Applications/sotto.app— иконка волны появится в menu bar справа. - Кликните по иконке → выдайте разрешения Микрофон и Универсальный доступ (откроется System Settings).
- В меню выберите модель → нажмите ⬇️ → дождитесь окончания загрузки (статус «Downloading…»).
- Когда статус сменится на «Sotto Ready» (зелёный) — всё готово.
- Нажмите хоткей (по умолчанию Left ⌘ + Left ⌃), надиктуйте, нажмите хоткей ещё раз — текст вставится в активное окно.
Почему
/Applications— автозапуск черезSMAppService.mainAppкорректно работает только когда приложение лежит в/Applicationsи подписано Apple ID.
Если хотите подписать своим Apple ID, изучить код или внести правки:
- Откройте
sotto.xcodeprojв Xcode 16+. - Target sotto → Signing & Capabilities → включите Automatically manage signing → выберите ваш Team (Personal Team тоже подходит для локального использования).
- Соберите Release-бандл:
xcodebuild -project sotto.xcodeproj -scheme sotto -configuration Release -derivedDataPath build clean build
- Скопируйте в
/Applications:cp -R "build/Build/Products/Release/sotto.app" /Applications/ - Дальше как в шагах 5–9 выше.
В меню Sotto, поле Хоткей записи, выберите один из 5 пресетов:
| Пресет | По умолчанию |
|---|---|
Left ⌘ + Left ⌃ |
✅ |
Left ⌘ + Left ⇧ |
|
Left ⌘ + Left ⌥ |
|
Left ⌃ + Left ⌥ |
|
Left ⌥ + Left ⇧ |
Right ⌘ + Left ⌃ не сработает. Это сделано специально, чтобы комбинация не конфликтовала с системными.
- Нажмите хоткей — внизу экрана появится пилюля.
- Говорите. Эквалайзер на пилюле реагирует на громкость; градиент за капсулой пульсирует.
- Дальше любой из вариантов:
- Хоткей повторно или 📤 зелёная кнопка — закончить запись и вставить текст
- ⏸️ белая кнопка — пауза; повторно — возобновить (звук не пишется во время паузы, но накопленный буфер сохраняется)
- ❌ красная кнопка — отменить, выбросить всё
- Через 90 секунд запись автоматически останавливается (
AudioRecorder.defaultMaximumDuration).
В меню Sotto, поле Язык диктовки — Русский или English. Применяется на следующей записи. Модель Qwen3 ASR мультиязычная, перезагрузка не требуется.
В разделе Модель нейросети:
- Выпадающий список с тремя моделями
- ⬇️ — скачать модель (загрузка идёт в фоне, прогресс виден в статусе)
- 🗑️ — удалить локальную копию модели (если она занимает место)
Sotto требует:
- Микрофон — для захвата звука
- Универсальный доступ (Accessibility) — для двух вещей:
- Симуляция
Cmd+V, чтобы вставить транскрибированный текст в активное приложение - Чтение символа перед курсором (нужно, чтобы решить, добавлять ли пробел перед текстом)
- Симуляция
Если разрешения отозвали — кликните «Проверить разрешения заново» в меню.
sottoApp.swift Точка входа SwiftUI App, привязка хоткея, lifecycle
AppState.swift State machine: idle / loading / recording / paused
/ transcribing / pasting / error
Services/
TranscriptionService Actor с MLX-инференсом, скачивание модели и кеш
AudioRecorder Захват через AVAudioEngine, RMS-уровень, ресэмпл в 16 кГц
Поддерживает pause/resume без потери буфера
PasteController Snapshot/restore буфера, симуляция Cmd+V
Views/
MenuBarView Выпадающее меню (модели, разрешения, настройки, язык)
RecordingOverlay Пилюля «Siri Aura» с кнопками и эквалайзером
OverlayManager Жизненный цикл оверлея + позиционирование
OverlayPanel Невыделяющаяся прозрачная NSPanel (FloatingPanel)
с поддержкой перетаскивания
Models/
STTModelDefinition Реестр моделей (имя, репо HF, квантизация)
Hotkey/
HotkeyDefinitions CGEvent tap, 5 пресетов, persistence в UserDefaults,
различие левых/правых модификаторов
| Компонент | Что |
|---|---|
| Speech-to-Text модель | Qwen3 ASR (mlx-community) |
| ML-фреймворк | Apple MLX |
| Аудио-обвязка | mlx-audio-swift |
| Скачивание моделей | swift-transformers (HuggingFace Hub) |
| UI | SwiftUI + AppKit (NSPanel для оверлея) |
| Захват звука | AVFoundation (AVAudioEngine) |
| Глобальный хоткей | Core Graphics CGEvent tap |
| Симуляция Cmd+V | Accessibility API |
MIT. Оригинальный код © 2026 Marko Simic. Модификации © 2026 Ералы Надыров.
Проект построен на основе simicvm/whisper — Marko Simic заложил архитектуру (state machine, MLX-инференс, smart paste с восстановлением буфера, плавающий оверлей).
В этом форке существенно переработаны:
- Модель взаимодействия с хоткеем: с push-to-talk (удерживать) на tap-to-toggle (одно нажатие).
- Состояние
pausedв state machine +AudioRecorder.pause()/resume(), сохраняющие накопленный буфер. - Дизайн оверлея: вместо круга с MeshGradient — пилюля «Siri Aura» с угловым градиентом, эквалайзером и тремя интерактивными кнопками (отмена/пауза/отправка).
- Перетаскивание оверлея — добавлены
move(by:)иclamp(origin:)для удобного позиционирования. - Picker языка диктовки RU/EN, читаемый из
UserDefaultsкаждой транскрибацией. - Полная локализация menu bar UI на русский.
- Bundle ID и брендинг — приложение переименовано из
whisperвsotto.
Sotto (from Italian sotto voce — "in a low voice") is a macOS voice dictation app that runs entirely on your machine. No cloud, no audio leaves your computer. Speech-to-text happens locally via Qwen3 ASR on top of Apple MLX.
Workflow: press a hotkey → speak → press it again (or hit the green Send button) → the transcribed text is pasted into the active application via simulated Cmd+V.
- Tap-to-toggle hotkey — one press starts recording, another stops and transcribes. No need to hold anything.
- Interactive pill overlay with three buttons:
- ❌ Cancel (red) — discard the recording and return to idle
- ⏸️ Pause / Resume (white) — pause capture without losing the accumulated buffer
- 📤 Send (green) — finish recording early and transcribe
- Bilingual dictation — Russian / English picker in the menu bar. Applies to the next recording without reloading the model.
- "Siri Aura" design — pill overlay with an angular gradient (blue → purple → orange) rotating 360° every 6 seconds, plus a six-bar equalizer that reacts to mic level in real time.
- Draggable overlay — drag the pill anywhere on screen.
- Three model options — Qwen3 ASR
0.6B (8-bit),1.7B (8-bit),1.7B (4-bit). Downloaded on demand from HuggingFace, removable per-model. - 5 hotkey presets — two-modifier combinations with left/right modifier discrimination (so they don't clash with system shortcuts).
- Smart paste — text is pasted via simulated
Cmd+Vthrough the Accessibility API; the original clipboard contents are preserved and restored; a leading space is added automatically when the cursor is right after a non-whitespace character. - Run on startup — toggle in the menu (uses
SMAppService). - Russian-localized UI (English available via in-app strings, but the menu copy is currently RU-only).
- Fully offline after a one-time model download.
- macOS 15.0+ (Sequoia or later)
- Apple Silicon strongly recommended — MLX is much slower on Intel
- Microphone and Accessibility permissions
- ~1 GB of free space for the default model (
Qwen3 ASR 0.6B 8-bit)
- Download
sotto.zipfrom the Releases page (direct link to the latest:sotto.zip). - Double-click the archive — macOS unpacks
sotto.app. - Drag
sotto.appinto/Applications. - If macOS blocks the first launch (normal for un-notarized apps) — right-click → Open → confirm. Alternatively, remove quarantine via terminal:
xattr -dr com.apple.quarantine /Applications/sotto.app
- Launch it:
open /Applications/sotto.app— a waveform icon appears in the menu bar. - Click the icon → grant Microphone and Accessibility permissions (System Settings opens automatically).
- Pick a model from the menu → click ⬇️ → wait for the download to finish.
- Once the status turns "Sotto Ready" (green) — you're set.
- Press the hotkey (default: Left ⌘ + Left ⌃), speak, press it again — text gets pasted into the active window.
Why
/Applications— Run on Startup usesSMAppService.mainApp, which works reliably only when the app lives in/Applicationsand is properly signed.
If you want to sign with your own Apple ID, study the code, or modify it:
- Open
sotto.xcodeprojin Xcode 16+. - Target sotto → Signing & Capabilities → enable Automatically manage signing → pick your Team (Personal Team works for local use).
- Build a Release bundle:
xcodebuild -project sotto.xcodeproj -scheme sotto -configuration Release -derivedDataPath build clean build
- Copy into
/Applications:cp -R "build/Build/Products/Release/sotto.app" /Applications/ - Continue from steps 5–9 above.
In the Sotto menu, Хоткей записи (Recording hotkey) field, pick one of 5 presets:
| Preset | Default |
|---|---|
Left ⌘ + Left ⌃ |
✅ |
Left ⌘ + Left ⇧ |
|
Left ⌘ + Left ⌥ |
|
Left ⌃ + Left ⌥ |
|
Left ⌥ + Left ⇧ |
Right ⌘ + Left ⌃ won't trigger anything. This is intentional, to avoid colliding with system shortcuts.
- Press the hotkey — a pill appears at the bottom of the screen.
- Speak. The equalizer on the pill reacts to mic level; the gradient pulses.
- To finish, any of:
- Press the hotkey again or 📤 green button — finish recording and paste the text
- ⏸️ white button — pause; press again to resume (audio is not captured during pause, but the accumulated buffer is preserved)
- ❌ red button — cancel and discard
- After 90 seconds the recording stops automatically (
AudioRecorder.defaultMaximumDuration).
In the Sotto menu, Язык диктовки (Dictation language) field — Русский or English. Applies to the next recording. The Qwen3 ASR model is multilingual, so no reload is needed.
Under Модель нейросети (Neural model):
- Dropdown with three models
- ⬇️ — download the model (progress is shown in the status line)
- 🗑️ — remove the local copy
Sotto needs:
- Microphone — to capture audio
- Accessibility — for two things:
- Simulate
Cmd+Vto paste the transcribed text into the active app - Read the character before the cursor, so it knows whether to prepend a space
- Simulate
If you revoke a permission, click «Проверить разрешения заново» (Re-check permissions) in the menu.
sottoApp.swift SwiftUI App entry point, hotkey wiring, lifecycle
AppState.swift State machine: idle / loading / recording / paused
/ transcribing / pasting / error
Services/
TranscriptionService Actor wrapping MLX inference, model download and cache
AudioRecorder AVAudioEngine capture, RMS level, 16 kHz resampling
Supports pause/resume without losing the buffer
PasteController Pasteboard snapshot/restore, Cmd+V simulation
Views/
MenuBarView Dropdown menu (models, permissions, settings, language)
RecordingOverlay "Siri Aura" pill with buttons and equalizer
OverlayManager Overlay lifecycle and positioning
OverlayPanel Non-activating transparent NSPanel (FloatingPanel)
with drag support
Models/
STTModelDefinition Model registry (name, HF repo, quantization)
Hotkey/
HotkeyDefinitions CGEvent tap, 5 presets, UserDefaults persistence,
left/right modifier discrimination
| Component | What |
|---|---|
| Speech-to-text model | Qwen3 ASR (mlx-community) |
| ML framework | Apple MLX |
| Audio bindings | mlx-audio-swift |
| Model download | swift-transformers (HuggingFace Hub) |
| UI | SwiftUI + AppKit (NSPanel for the overlay) |
| Audio capture | AVFoundation (AVAudioEngine) |
| Global hotkey | Core Graphics CGEvent tap |
| Cmd+V simulation | Accessibility API |
MIT. Original code © 2026 Marko Simic. Modifications © 2026 Yeraly Nadyrov.
Built on top of simicvm/whisper — Marko Simic laid down the architecture (state machine, MLX inference, smart paste with clipboard restoration, floating overlay).
In this fork the following has been substantially reworked:
- Hotkey interaction model: from push-to-talk (hold) to tap-to-toggle (one press).
pausedstate in the state machine +AudioRecorder.pause()/resume()that preserve the accumulated buffer.- Overlay design: from a circular MeshGradient to a "Siri Aura" pill with an angular gradient, equalizer, and three interactive buttons (Cancel / Pause / Send).
- Draggable overlay — added
move(by:)andclamp(origin:)for repositioning. - Russian / English language picker, read from
UserDefaultson every transcription. - Full Russian localization of the menu bar UI.
- Bundle ID and branding — renamed from
whispertosotto.


