Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 63 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# KlangLadder
# KlangLadder: automatic audio device switcher for macOS

KlangLadder is a macOS menu bar app that picks your default audio devices for you.
KlangLadder is a free, open-source macOS menu bar app that automatically switches your Mac's default audio output and input device to your highest-priority connected device, for macOS 14 Sonoma and later.

You rank your devices once: one list for output (speakers, headphones) and one for input (microphones). When a device connects or disconnects, KlangLadder switches to the highest-ranked device that is connected.
You rank your devices once: one list for output (speakers, headphones, AirPods, displays) and one for input (microphones). When a device connects or disconnects, KlangLadder switches to the highest-ranked device that is connected.

macOS picks the default device on its own. It often jumps to whatever you just plugged in, and it doesn't remember your preferences. KlangLadder fixes this.

## What it does
## Features

- **Switches when a better device connects.** If a device with a higher rank than the current one connects, KlangLadder makes it the default.
- **Undoes unwanted switches.** If macOS switches to a newly connected device that ranks lower, KlangLadder switches back.
Expand All @@ -15,15 +15,28 @@ macOS picks the default device on its own. It often jumps to whatever you just p
- **Applies your ranking once per login.** On the first start after you log in, KlangLadder switches to your best connected device. A restart later in the same session doesn't override your choice.
- **Remembers disconnected devices.** They stay in the list, dimmed, so their rank is kept for next time.
- **Lets you disable devices.** Disabled devices, like virtual devices from Zoom or Teams, are never chosen by KlangLadder. If only disabled devices are connected, macOS decides.
- **Separate output and input lists.** Keep AirPods as your speakers but your USB microphone as your input.

KlangLadder reacts to system events. It does not poll, and it needs no special permissions.
KlangLadder reacts to system events. It does not poll, and it needs no special permissions (no microphone access, no accessibility access).

## Requirements
## KlangLadder compared to macOS

| | macOS alone | KlangLadder |
|---|---|---|
| Default device after plugging something in | Usually the new device | Your highest-ranked connected device |
| Default device after a disconnect | Chosen by macOS | Your next-best connected device |
| Remembers a device priority order | No | Yes, one list for output and one for input |
| Ignore virtual devices (Zoom, Teams, BlackHole) | No | Yes, disable them |
| Keeps your manual choice | Until the next device change | Until the next connect or disconnect |
| Price and license | Built in | Free, open source |

## System requirements

- macOS 14 (Sonoma) or later
- Xcode or the Xcode Command Line Tools with Swift 5.10 or later
- The prebuilt release is built for Apple silicon. On an Intel Mac, install with Homebrew or from source.
- Homebrew and source builds need the Xcode Command Line Tools with Swift 5.10 or later.

## Install
## How to install

### Homebrew

Expand All @@ -39,11 +52,11 @@ brew services start klangladder

Update with `brew upgrade klangladder`, then `brew services restart klangladder`.

### GitHub release
### Download from GitHub releases

Download `KlangLadder-v<version>.zip` from the [latest release](https://github.com/janthoXO/KlangLadder/releases/latest), unzip it and move `KlangLadder.app` to `~/Applications`. The app is not notarized, so Gatekeeper blocks the first launch. Allow it in **System Settings → Privacy & Security**.

### From source
### Build from source

```sh
git clone https://github.com/janthoXO/KlangLadder.git
Expand All @@ -53,13 +66,9 @@ cp -R build/KlangLadder.app ~/Applications/
open ~/Applications/KlangLadder.app
```

The app appears as a speaker icon in the menu bar. It has no Dock icon.

A locally built app is not quarantined, so macOS should open it without a Gatekeeper prompt.

On its first run, KlangLadder puts your current default device at the top of each list. It doesn't switch your audio until you change the order.

### Raycast
### Raycast extension

The extension in [`raycast/`](raycast/) adds an "Open KlangLadder" command to Raycast. It bundles the app: the first time you run the command, it installs KlangLadder into `~/Applications` and keeps it updated after that. If you already have a standalone copy (built from source, a Homebrew keg, or a manual copy in `~/Applications`), the extension uses that copy as-is instead.

Expand All @@ -69,7 +78,13 @@ To load it locally (needs Xcode 16.3+):
cd raycast && npm install && npm run dev
```

## Use
### First launch

The app appears as a speaker icon in the menu bar. It has no Dock icon.

On its first run, KlangLadder puts your current default device at the top of each list. It doesn't switch your audio until you change the order.

## How to use

Left-click the menu bar icon to open the device lists. Right-click it for settings.

Expand Down Expand Up @@ -98,16 +113,46 @@ Reordering, disabling, enabling and deleting never switch devices by themselves.

`open klangladder://open` opens the popover. KlangLadder starts first if it isn't running.

## FAQ

### How do I stop my Mac from switching audio to AirPods or Bluetooth headphones?

Rank your speakers or wired headphones above the AirPods in the Output tab. When the AirPods connect and macOS switches to them, KlangLadder switches back to the higher-ranked device.

### How do I keep my USB microphone as the default input?

Put the USB microphone at the top of the Input tab. To stop a headset's microphone from being picked at all, disable it.

### How do I set a default audio device priority on macOS?

macOS has no built-in priority list for audio devices. KlangLadder adds one: drag your devices into the order you want, separately for output and input.

### Does KlangLadder record audio or need microphone permission?

No. It only reads the device list and sets the default device through Core Audio. It never opens an audio stream.

### Does KlangLadder change audio for each app separately?

No. It sets the system default output and input device. For per-app routing, use a tool like SoundSource.

### What happens if I pick a device by hand?

KlangLadder keeps your choice until the next device connects or disconnects.

## Alternatives

Other macOS apps that manage audio devices include AudioPriorityBar, SoundAnchor, AudioWrangler and SoundSource (per-app audio routing). KlangLadder focuses on one job: priority-based switching of the default device, with separate output and input lists, undoing macOS auto-switches, and no special permissions.

## Uninstall

If you installed with Homebrew, run `brew services stop klangladder`, `brew uninstall klangladder` and `brew untap janthoXO/klangladder`.

Otherwise:

1. Turn off **Launch at login** in the popover, then click **Quit**.
1. Right-click the menu bar icon, turn off **Launch at Login**, then click **Quit KlangLadder**.
2. Delete `~/Applications/KlangLadder.app`.
3. Optionally, delete your settings: `~/Library/Application Support/KlangLadder`.

## Development

See [README_DEV.md](README_DEV.md).
KlangLadder is written in Swift with SwiftUI and AppKit, and uses the Core Audio HAL. See [README_DEV.md](README_DEV.md) for the architecture and the switching rules, and [DESIGN.md](DESIGN.md) for the full design.
18 changes: 9 additions & 9 deletions README_DEV.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# KlangLadder – Developer Guide
# KlangLadder developer guide: Swift, SwiftUI and Core Audio

This guide explains how KlangLadder is built and how it works. For the full product design and its reasoning, see [DESIGN.md](DESIGN.md). Section numbers such as (4.4) and goal numbers such as (G6) refer to that document.
KlangLadder is a macOS menu bar app, written in Swift 5.10 with SwiftPM, SwiftUI and AppKit, that sets the default audio device through the Core Audio HAL based on a per-scope priority list. This guide explains how KlangLadder is built and how it works. For the full product design and its reasoning, see [DESIGN.md](DESIGN.md). Section numbers such as (4.4) and goal numbers such as (G6) refer to that document.

## Build, run, test
## How to build, run and test

```sh
swift build # debug build
Expand All @@ -28,7 +28,7 @@ Test them with the bundled app.
| `CFBundleURLTypes` | scheme `klangladder` |
| `LSMinimumSystemVersion` | `14.0` |

## Layout
## Project layout

```
Package.swift
Expand Down Expand Up @@ -237,7 +237,7 @@ The width is 320. The height follows the content: rows are exactly 28 points tal

There is no committed snapshot test. To review layout changes, add a temporary test target that hosts `PopoverView` in an offscreen `NSWindow` and captures it with `CGWindowListCreateImage`; `cacheDisplay` leaves list and control text blank.

## Tests
## Unit tests

`Tests/KlangLadderCoreTests/RulesTests.swift` uses swift-testing and covers:

Expand Down Expand Up @@ -283,11 +283,11 @@ The target uses only `RaycastTypeScriptPlugin`, not `RaycastSwiftPlugin`, becaus
- The standalone-version compatibility check from 9.2 is skipped: the only command sent is `klangladder://open`, which every version supports.
- Uninstalling the extension does not remove the installed app.

## Homebrew formula
## Homebrew formula and tap

`Formula/klangladder.rb` makes this repository its own tap. `brew tap janthoXO/klangladder <repo URL>` is needed because the repository is not named `homebrew-klangladder`.

- The formula is head-only for now. After the first tag (#3), add `url` with `tag:` and `revision:` so `brew install` and `brew upgrade` work without `--HEAD`.
- Stable builds use the `url` with `tag:` and `revision:`, which the release job rewrites (see below). `head` builds `main`.
- `install` runs `bundle.sh --disable-sandbox`. SwiftPM's own sandbox can't run inside Homebrew's build sandbox, so `bundle.sh` passes its arguments on to `swift build`.
- The app lands in the keg, at `$(brew --prefix)/opt/klangladder/KlangLadder.app`.
- `service` runs the app binary through a LaunchAgent (`sh.brew.klangladder`). If another copy is already running, the single instance rule makes the new one quit.
Expand All @@ -304,7 +304,7 @@ brew audit --strict --formula local/klangtest/klangladder
brew test local/klangtest/klangladder
```

## CI and releases
## CI and release workflows (GitHub Actions)

Three workflows in `.github/workflows` call each other: Release calls Package, and Package calls Build.

Expand Down Expand Up @@ -333,6 +333,6 @@ Without them the step logs a warning and skips. See #21 for what else the Store

See the GitHub issues and DESIGN.md sections 13–14. Main open items:

- Raycast extension (#2) — the extension bundles and installs the app (9.2, S1); Raycast Store acceptance (S2) and switching the path dependency to a tagged release are still open
- Raycast extension (#2) — the extension bundles and installs the app (9.2, S1); Raycast Store acceptance (S2) is still open
- CLI mode for reads (#11)
- URL write commands (#12)
30 changes: 30 additions & 0 deletions llms.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# KlangLadder

> KlangLadder is a free, open-source macOS 14+ menu bar app that automatically switches the default audio output and input device to the highest-ranked connected device from a user-defined priority list. It is written in Swift (SwiftUI, AppKit, Core Audio HAL) and installs with Homebrew, from a GitHub release zip, or through a Raycast extension.

Key facts:

- Separate priority lists for output (speakers, headphones, AirPods) and input (microphones).
- Switches only when a device connects or disconnects, and on the first start per login session. It undoes a macOS auto-switch to a lower-ranked new device.
- A default device chosen by hand in System Settings or Control Center is kept until the next connect or disconnect.
- Devices can be disabled (for example virtual devices from Zoom, Teams or BlackHole) so they are never chosen.
- Event-driven, no polling, no microphone or accessibility permission. It sets the system default device only; it does not route audio per app.
- Install: `brew tap janthoXO/klangladder https://github.com/janthoXO/KlangLadder && brew install klangladder && brew services start klangladder`.
- `open klangladder://open` opens the popover from other apps or scripts.

## Docs

- [README](https://github.com/janthoXO/KlangLadder/blob/main/README.md): features, installation, usage, FAQ and uninstall
- [Developer guide](https://github.com/janthoXO/KlangLadder/blob/main/README_DEV.md): architecture, switching rules, Core Audio adapter, persistence, Raycast bridge, Homebrew formula, CI
- [Design](https://github.com/janthoXO/KlangLadder/blob/main/DESIGN.md): full product design, goals and decision table

## Source

- [Switching rules](https://github.com/janthoXO/KlangLadder/blob/main/Sources/KlangLadderCore/Model.swift): `ScopeConfig` and the pure `Rules.target` function
- [Engine](https://github.com/janthoXO/KlangLadder/blob/main/Sources/KlangLadderCore/Engine.swift): Core Audio events, debouncing, config storage
- [Homebrew formula](https://github.com/janthoXO/KlangLadder/blob/main/Formula/klangladder.rb)

## Optional

- [Releases](https://github.com/janthoXO/KlangLadder/releases)
- [Raycast extension](https://github.com/janthoXO/KlangLadder/tree/main/raycast)
Loading