An open API service indexing awesome lists of open source software.

https://github.com/madebysan/wave-ios

Local speech-to-text keyboard for iOS. Your voice is processed entirely on your iPhone.
https://github.com/madebysan/wave-ios

ios keyboard-extension speech-to-text swift swiftui

Last synced: about 1 month ago
JSON representation

Local speech-to-text keyboard for iOS. Your voice is processed entirely on your iPhone.

Awesome Lists containing this project

README

          

Wave app icon

Wave

Local speech-to-text keyboard for iOS.

Your voice is processed entirely on your iPhone. Nothing leaves your device.

Version 1.0.0 · iOS 17.0+ · Apple Silicon


Swift
SwiftUI
iOS
WhisperKit

Also available for macOS

Wave is a custom iOS keyboard for local speech-to-text. Tap the mic, speak, and the transcribed text gets inserted into whatever app you're using. Runs [WhisperKit](https://github.com/argmaxinc/WhisperKit) on-device, so your voice never leaves your phone.

## How it works

Wave is a custom iOS keyboard with a dictation button. Tap the mic, speak, and your words appear as text in any app: Messages, Notes, Mail, anything with a text field.

Unlike cloud-based dictation, Wave runs [WhisperKit](https://github.com/argmaxinc/WhisperKit) (OpenAI's Whisper model) entirely on-device. No internet required, no data sent anywhere.

### The flow

1. Switch to the Wave keyboard (globe icon)
2. Tap the mic button
3. Wave app opens and starts recording
4. Speak, then tap Done
5. Switch back to your app. Text is inserted automatically.

### Why the app handoff?

Apple blocks microphone access in all keyboard extensions (privacy policy, not a bug). So Wave uses the same pattern as Wispr Flow and other dictation keyboards: the keyboard launches the main app for recording, then passes the result back through shared storage.

## Architecture

```
┌─────────────────────┐ wave://dictate ┌─────────────────────┐
│ Keyboard Extension │ ────────────────────▶ │ Wave App │
│ │ │ │
│ • Mic button │ App Group │ • AVAudioEngine │
│ • Status display │ ◀──────────────────── │ • WhisperKit │
│ • Polls for results│ (transcription) │ • Filler filter │
│ │ │ • History │
│ │ │ • Settings │
└─────────────────────┘ └─────────────────────┘
```

**Shared via App Group** (`group.com.santiagoalonso.wave`):
- Transcription results
- Dictation state (idle → recording → transcribing → done)
- Settings (model, language, filler toggle)
- History

## Project structure

File tree

```
WaveKeyboard/
├── Shared/ # Code shared between both targets
│ ├── AudioRecorder.swift # AVAudioEngine recording (main app only)
│ ├── DictationState.swift # State enum for the pipeline
│ ├── FillerFilter.swift # Removes um, uh, you know, etc.
│ ├── SharedStorage.swift # App Group read/write bridge
│ ├── Transcriber.swift # WhisperKit wrapper (main app only)
│ └── TranscriptionHistory.swift # JSON history in shared container

├── WaveApp/ # Main app target
│ ├── WaveApp.swift # @main, deep link handler
│ ├── ContentView.swift # Home screen + setup instructions
│ ├── DictationView.swift # Recording + transcription UI
│ ├── OnboardingView.swift # First-run setup wizard
│ ├── SettingsView.swift # Model, language, filler, history
│ └── HistoryView.swift # Past transcriptions

├── WaveKeyboardExtension/ # Keyboard extension target
│ ├── KeyboardViewController.swift # UIInputViewController + polling
│ └── KeyboardView.swift # SwiftUI keyboard UI

└── project.yml # XcodeGen spec
```

## Setup

### Requirements

- Xcode 16+
- iOS 17.0+ device (Simulator works for UI, but keyboard extensions need a real device)
- [XcodeGen](https://github.com/yonaskolb/XcodeGen) (`brew install xcodegen`)

### Build

```bash
cd WaveKeyboard
xcodegen generate
open WaveKeyboard.xcodeproj
```

Set your development team in Signing & Capabilities for both targets, then build and run on your iPhone.

### Enable the keyboard

1. **Settings → General → Keyboard → Keyboards → Add New Keyboard → Wave**
2. Tap Wave in the list → enable **Allow Full Access**
3. In any text field, long-press the globe / emoji button → select **Wave**

## Configuration

| Setting | Default | Options |
|---------|---------|---------|
| Speech model | base (~140 MB) | tiny, base, small |
| Detect language | Auto-detect | auto + 9 languages |
| Output language | Original | Original, English (translate) |
| Filler removal | On | On / Off |
| Copy to clipboard | On | On / Off |
| History | On | On / Off |

## Shared with macOS Wave

This project reuses core logic from the [macOS Wave](https://github.com/madebysan/wave-mac) menu bar app:

| File | Reuse | Changes |
|------|-------|---------|
| FillerFilter.swift | ~100% | Reads settings from SharedStorage instead of UserDefaults |
| Transcriber.swift | ~90% | os.Logger, parameterized model / language, @Published state |
| AudioRecorder.swift | ~85% | Added AVAudioSession setup, audio level publishing |
| TranscriptionHistory.swift | Logic | Rewritten for App Group container |

## Feedback

Found a bug or have a feature request? [Open an issue](https://github.com/madebysan/wave-ios/issues).

## License

[MIT](LICENSE)

---

Made by [santiagoalonso.com](https://santiagoalonso.com)