Use your Mac microphone in Windows—even after you close Chrome.
A small native macOS menu bar app streams live microphone audio over your private Tailscale network to a Windows virtual microphone. Built for dictating into Windows apps while using a remote desktop. RustDesk and Typeless work with it, but neither is a protocol dependency.
Mac microphone → native menu bar app → private HTTPS/WebSocket
↓
Windows receiver → VB-CABLE → Windows default microphone → your app
- Native Mac sender with Start, Stop, connection status, and automatic reconnect.
- App icon for Finder/login items and a microphone symbol in the menu bar.
- Close Chrome and Terminal while the menu bar app keeps running.
- Save the connection once in macOS Keychain; no repeated token entry.
- Check GitHub releases from the menu and choose to download/install inside the app.
- Convert microphone audio to 48 kHz mono Float32 with bounded buffering.
- Switch Windows default microphone roles on first audio, then restore them when the session ends.
- Browser sender included as an alternative; its tab must remain open.
- Normal streaming does not record audio files or transcripts. The optional local diagnostic recorder described below saves audio only when explicitly run. Your dictation application's own data policy still applies.
Mac: macOS 12+ and Tailscale. A universal prebuilt app supports Intel and Apple Silicon. Apple Command Line Tools (xcode-select --install) are needed only for the optional source build.
Windows: Python 3.12 is the reference runtime, VB-CABLE, and Tailscale. Install the virtual audio driver using its official installer and follow its reboot instructions. Select a working physical microphone as the Windows default before setup. The virtual endpoint must be named CABLE Output (VB-Audio Virtual Cable).
Both devices must be on a tailnet with access between them. Enable Tailscale HTTPS/Serve for the Windows machine. Do not use Funnel or publicly expose the receiver.
Clone this repository and run PowerShell from its directory:
py -3.12 -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.lock.txt
.\.venv\Scripts\python.exe setup_receiver.py --origin 'https://YOUR-WINDOWS-HOST.YOUR-TAILNET.ts.net:8443'Use the actual HTTPS name from your Tailscale machine, not the example. Setup creates a random connection token and saves the current physical microphone roles for restoration. Keep config.json and audio-defaults.json private; they are ignored by Git. Setup does not replace an existing configuration.
Start the receiver in the foreground first:
.\.venv\Scripts\python.exe server.pyIn a second PowerShell window, enable a private HTTPS proxy:
tailscale serve --bg --https=8443 http://127.0.0.1:8765The receiver listens only on loopback. The configured origin must exactly match the HTTPS address, including the port. Build your private pairing link from config.json: ORIGIN/#token=TOKEN. Treat that link like a password; do not put it in an issue, screenshot, or commit.
After testing, stop the foreground process with Ctrl+C, then launch a hidden receiver:
powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File .\Start-Receiver.ps1To start it after Windows login, open shell:startup with Win+R and create a shortcut with this Target (replace the repository path):
powershell.exe -NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File "C:\path\stan-mic-bridge\Start-Receiver.ps1"
Set the shortcut's Start in to the repository folder. This project does not install a Windows system service; microphone forwarding requires the receiver to run in your logged-in session. RustDesk's separate system service does not launch this receiver.
Download StanMicBridge-macOS.zip from the latest release, extract it, and move Stan Mic Bridge.app into ~/Applications. In Finder, Command+Shift+G opens that folder. Quit an older running version before replacing it. Launch the installed app. The prebuilt app is ad-hoc signed, not Developer ID notarized; if macOS blocks it, review the app's origin and use the system's per-app approval in Privacy & Security → Open Anyway. No security settings are changed by the app. Apple's explanation.
Alternatively, clone or download this repository and build locally:
bash mac/install.commandThe installer builds with Apple Swift, locally signs the app, and installs ~/Applications/Stan Mic Bridge.app. It preserves an older installation as a dated backup and asks you to quit a running copy before upgrading. No Homebrew or third-party Mac runtime is needed. A rebuilt app may prompt again for microphone or Keychain access.
The app checks the existing microphone authorization before starting. It requests access only when the system reports that permission has not been decided; an already-authorized Start goes directly to connection. Login startup, microphone permission, and Keychain access are separate settings. Stopping and starting the same installation should reuse its permission. Ad-hoc builds have a version-specific code identity and may prompt again after upgrades; the optional fixed local signing setup below keeps a persistent identity instead. Apple's code-signing requirements.
The icon is generated reproducibly from mac/GenerateIcon.swift using AppKit and packaged as AppIcon.icns before signing. No external image download is required.
- Click Mic in the menu bar and choose 設定連線… (Configure connection).
- Paste your private
https://…/#token=…link and save it in Keychain. - Choose 開始傳送 (Start) and allow microphone access.
- In Windows, use an app that follows the default microphone. For Typeless, choose automatic microphone detection and disable its option to mute other audio during dictation.
- Close Chrome and dictate a sentence to confirm actual delivery.
Choose 停止傳送 (Stop) to stop recording and restore Windows microphone defaults, or quit the app. Only one sender may connect at a time; stop a browser sender before using the native app.
In System Settings → General → Login Items (or Login Items & Extensions), add ~/Applications/Stan Mic Bridge.app to Open at Login. In the file picker, press Command+Shift+G and enter ~/Applications if needed. On macOS Monterey, use System Preferences → Users & Groups → Login Items instead. Apple's instructions.
This opens the menu bar app after login. Normal launches, including login and manual reopening, start stopped; click Start to transmit. Automatic restoration after an update does not change login behavior. An enabled session can reconnect after sleep/wake, but the Mac cannot send while asleep or powered off.
The Mic menu shows the installed version. Choose 檢查更新… (Check for updates) to check independently of microphone transmission. The menu automatically reopens when the check finishes, showing the result without another click. When a newer version is available, it offers installation; release history remains a separate action. The app contacts this project's public GitHub Releases API only when clicked. Checking does not stop recording and does not send your microphone connection credentials to GitHub.
For a newer version, choose 下載並安裝更新 (Download and install update). The app downloads the prebuilt ZIP, verifies the GitHub-provided SHA-256 digest, archive structure, bundle identity/version, and code signature, then stops transmission, installs, and reopens. Clicking Download and install starts the update directly, with no extra confirmation or download window. A single menu status row shows progress. An active, ready transmission is restored after updating when saved credentials and microphone authorization remain available; an already-stopped session stays stopped. There is no Terminal or local compilation involved. Downloads and validation leave existing transmission running; quitting the app before installation cancels the update. Only installation at ~/Applications/Stan Mic Bridge.app is supported.
Starting in 1.3.1, restoring transmission after an update is the default and has no checkbox. The decision uses the actual state at installation handoff: transmission must still be active with a ready receiver. Stopping during download prevents restoration; a session still connecting, retrying, or awaiting permission does not qualify. The request applies only to that update launch, requires saved connection credentials and existing microphone authorization, and is consumed once. Normal relaunch, login, and crash recovery remain stopped. If macOS requires microphone authorization again, the app stays stopped and asks you to use Start. The old version controls the update into 1.3.1, so its previous resume setting still governs that one transition.
An independent helper waits for the old app to exit, preserves its bundle as a backup, and verifies that the new app reaches its UI startup checkpoint. Replacement and startup failures trigger restoration where safe; if an unresponsive new process cannot be stopped, the helper leaves the backup intact and explains how to restore it instead of modifying a running app. Startup readiness does not prove microphone or network functionality. Connection credentials remain in Keychain. With fixed local signing enabled, the updater also uses the local signing identity in Keychain; it does not rewrite the connection credentials.
Versions before 1.2.0 need one manual upgrade (Finder replacement or source installer) before in-app installation is available. There are no scheduled checks or silent installations. Automatic restoration after an update requires the conditions above. Missing or invalid release assets are refused rather than installed.
Recovery from the 1.2.0–1.2.2 symbolic-link error: those versions' installed update helper can mistake macOS's normal /var alias for an unsafe installation path. The failure occurs before replacing the installed app. Install 1.2.3 or newer once through the source installer or manual app replacement to activate the repaired helper; retrying the old in-app updater cannot replace that helper. Version 1.2.3 canonicalizes only OS-provided home/temp anchors and still rejects symbolic links within the installation and staging directories.
Public release packages use ad-hoc signing. Their designated requirements are tied to a particular build, so updates can cause macOS to ask again for microphone or Keychain access. An auto-resume preference cannot grant those permissions.
A paid Developer ID is not required for the optional fixed local signing mode introduced in 1.3.0. After quitting the running app, use the source installer once:
bash mac/install.command --local-signingThis creates or reuses a persistent self-signed code-signing identity on this Mac and signs the new app before installation. The private key stays in this Mac's Keychain; only a public certificate fingerprint is saved in the app's local configuration. The first transition from ad-hoc signing can require microphone and Keychain approval again. Fixed local signing does not guarantee Keychain approval survives later updates: macOS can use a per-build code hash as an additional Keychain partition check for self-signed apps, independently of the stable designated requirement. Selecting Always Allow can therefore apply to that build without covering the next one. See Apple's partition identity selection and additional partition validation. Do not delete the signing identity or its configuration between updates.
When enabled, future in-app updates still validate the original release download, then locally sign the app and helper with the same pinned identity and check designated-requirement continuity before installation. Missing keys or identity mismatches stop the update instead of silently reverting to ad-hoc signing. The menu displays local signing status. This is personal-use signing, not Developer ID notarization; it does not disable Gatekeeper or change microphone privacy settings. Permission retention must still be confirmed across updates on the target Mac, especially after migration or OS policy changes.
Fixed credential helper (1.3.6+): run the source installer with --local-signing once to enable the helper, including when upgrading from 1.3.5. The installer reuses your existing signing identity, installs a private helper under ~/Library/Application Support/StanMicBridge/CredentialBroker, and binds its exact identity into the app's signed resources. The first migration can ask you to allow StanMicCredentialBroker to read the previously saved receiver connection. This is separate from microphone permission.
After setup, normal in-app updates carry the authenticated helper pin into the new app and leave the installed helper's signed bytes unchanged. The helper starts briefly for a credential operation and then exits. It is not a login service and has no network listener. A fresh public ZIP alone does not contain your Mac-specific pin; use the source installer for this initial transition. A locked Keychain, cancelled first authorization, a lost local identity or an operating-system policy change can still require user action; the update mechanism does not unlock the Keychain or grant permissions.
The receiver token remains in Keychain. It does not appear in settings files, process arguments, environment variables or diagnostic logs. The app and helper authenticate each other before exchanging credential data over private pipes. The helper only accepts the actual locally signed app at ~/Applications/Stan Mic Bridge.app, with hardened runtime enabled. The app checks the helper against its signed resource pin; a changed or missing helper fails closed instead of being silently replaced. This is a personal installation trust model, not protection after compromise of the installed app or the user's local signing identity.
Migration verifies the new item before using it and retains the legacy item for rollback. Clearing a connection stores an empty removal marker before attempting legacy cleanup, preventing the retained old connection from returning. If cleanup needs approval or fails, the app stays stopped and lets you retry. Restoring a pre-helper app uses the retained legacy connection, which can differ from a connection changed after migration. Use the in-app updater or the source installer to preserve this identity. Replacing the app directly with the public ad-hoc ZIP loses the local signature and can cause another permission prompt.
If Keychain is locked or signing authorization was cancelled, unlock it and retry. If the original signing key or configuration was lost, restore the original data; rerunning the installer does not recreate the same private key. Changing to a new identity would be a separate recovery action and can require permission again. The updater never rotates identities automatically.
References: Apple TN2206: Code signing in depth and Apple TN3127: Code signing requirements.
音訊狀態… (Audio status) displays local session counters to compare captured, converted, sent, and discarded audio frames. It does not record audio, persist speech, or include connection credentials. These counters help identify missing audio; they are not a speech-recognition accuracy score. The transport uses uncompressed 48 kHz mono float32 PCM. Different microphones, source processing, network jitter, and queue starvation can affect the result heard by a Windows dictation app.
For an explicitly requested listening test on Windows, capture_audio.py records the WASAPI CABLE Output input in shared mode, without restarting the receiver or changing default microphones:
New-Item -ItemType Directory -Force recordings | Out-Null
.\.venv\Scripts\python.exe capture_audio.py --seconds 20 --output recordings\cable-test.wavThis opt-in tool saves a local WAV and a metadata-only JSON sidecar, refuses to overwrite existing files, and limits each capture to 60 seconds. The recordings/ directory is ignored by Git. Speak only material you want saved; delete the local test files after listening when no longer needed. The recorder does not upload audio or run transcription.
The recording captures the virtual microphone signal available to a dictation app, including the receiver and VB-CABLE path. It does not capture the Mac microphone before transmission or reveal the dictation app's internal processing. Exact-zero runs, clipping, and capture overflows are diagnostic observations; silence alone does not prove network loss. Compare the playback, Mac Audio Status counters, and the dictation result before assigning a cause.
Version 1.3.9 gives the sender queue room for overlapping 100 ms capture bursts: a 250 ms byte ceiling and a 16-packet ceiling replace the former 100 ms/eight-packet limits. Packets are still sent immediately when the connection can accept them, and queued packets aged 150 ms are still discarded. The larger capacity does not introduce a deliberate playback delay. This addresses observed sender overflow when a 44.1 kHz device delivered 4,410-frame callbacks; it does not guarantee lossless delivery during network or application stalls. Compare the overflow and expired counters before and after an update, then verify actual audio.
Open the same private pairing link in a microphone-capable browser, press Start, and keep that tab open. The browser stores its token only in page memory, so a reload may require the link again. The native app uses Keychain and does not need Chrome.
- Native transport failures retry with capped backoff; Stop cancels retries. Audio is not queued indefinitely during outages.
- The receiver restores defaults after disconnect or roughly eight seconds without audio. A restoration journal is used after a receiver crash; it is applied on the next receiver start.
- A different microphone manually selected during a session is preserved when restoring.
- All three Windows default capture roles change while forwarding. Some applications cache their device and need recording restarted.
- The native sender currently has no acoustic echo cancellation or noise suppression. Headphones help prevent the remote speaker output from re-entering the microphone.
- Mac UI is currently Traditional Chinese. Prebuilt binaries are ad-hoc signed, not Developer ID notarized.
- Devices producing very large audio batches can lose older samples under the low-latency queue limit.
The original deployment passed synthetic Windows virtual-audio round trips and microphone-role restoration checks. The author also reported successful native Mac installation/use. Hardware combinations, Mac login startup, and native sleep/wake behavior are not comprehensively tested.
On 2026-10-13, the author reported that the in-app upgrade from locally installed 1.3.7 to 1.3.8 did not request permission or a Keychain password, and the app remained open after updating. This is target-Mac user confirmation, separate from CI; it does not establish permission retention on every Mac or resolve the ongoing audio-quality investigation.
Lightweight checks (Node.js is needed only for the browser simulation):
python -m unittest discover -s tests -p "test_*.py"
node tests/sender-recovery.cjs
bash -n mac/install.command
verify_audio.py and verify_routes.py are opt-in Windows integration checks. They require a running configured receiver and VB-CABLE, send synthetic audio, and temporarily change microphone defaults. Do not run them during a live session. To verify the actual experience, dictate with Chrome closed, Stop, then Start and dictate another sentence; a connection indicator alone is not proof of audio delivery.
TLS is provided by Tailscale Serve. The receiver requires an exact Origin and a random bearer token supplied in the WebSocket URL. Authentication happens before any audio device is opened. There is one active sender, a message-size bound, and finite-sample validation. Native credentials are stored in Keychain; no token is bundled in source. The sender uses normal system TLS validation and refuses redirects.
The first WebSocket message is JSON {"sampleRate":48000} (the receiver also accepts 44100 for browsers). After {"ready":true}, send binary mono IEEE-754 little-endian Float32 PCM. The native app uses 1024-frame chunks. The receiver can log operational timestamps, frame counters, and error categories, but disables request access logs and does not save audio.
MIT licensed. VB-CABLE, Tailscale, and any dictation software are separate products with their own terms; no affiliation is implied.
Maintained by Stan Shih (施博瀚 / Po-Han Shih), an AI product developer in Taipei, Taiwan. More software and tools.