Use the timeBuzzer® hardware without the vendor software: documented MIDI
protocol, a debug tool, and buzzerd — a macOS daemon that maps the wheel
and button to keyboard shortcuts and shell commands, with per-app profiles
and LED feedback.
This project is not affiliated with Ideas in Logic GbR. timeBuzzer® is a registered trademark of its respective owner; the name is used here solely to describe device compatibility.
Native menu bar app that bundles the daemon with a UI: connection status, pause toggle, launch-at-login, and a graphical profile editor (with an app picker, action fields, and an LED color picker).
-
Download the latest
Buzzctl-*.zipfrom Releases, unzip, and moveBuzzctl.appto/Applications. -
The app is ad-hoc signed (no Apple Developer ID). On first launch macOS will refuse it once — allow it via System Settings → Privacy & Security → "Open Anyway", or clear the quarantine flag:
xattr -dr com.apple.quarantine /Applications/Buzzctl.app
The app's config lives at ~/Library/Application Support/Buzzctl/buzzerd.json
— edit it in the UI or as JSON (hot-reloaded either way). Building from
source instead: ./build.sh produces both Buzzctl.app and the CLI.
./build.sh
./buzzerd buzzerd.json- Hot-plug: unplugging/replugging the buzzer is detected.
- Hot-reload: config changes are picked up at runtime (mtime poll, ~2 s).
- Verbose mode:
./buzzerd -v buzzerd.jsonlogs every raw MIDI event. - Autostart: edit the two
/PATH/TOentries inbuzzerd.plist, thencp buzzerd.plist ~/Library/LaunchAgents/ && launchctl load ~/Library/LaunchAgents/buzzerd.plist. - Self-check:
./buzzerd selftest
Planned features: see ROADMAP.md.
buzzerd.json maps app bundle IDs to profiles (plus optional global
timing). The profile of the frontmost app is active; default is the
fallback for every app not listed:
{
"profiles": {
"default": {
"led": [0, 40, 127],
"press": { "key": "mute" },
"wheelUp": { "key": "volumeup" },
"wheelDown": { "key": "volumedown" }
},
"com.apple.QuickTimePlayerX": {
"led": [0, 127, 127],
"press": { "key": "space" },
"wheelUp": { "key": "right" },
"wheelDown": { "key": "left" }
},
"com.microsoft.teams2": {
"led": [127, 0, 0],
"press": { "key": "cmd+shift+m" }
}
}
}To find an app's bundle ID:
osascript -e 'id of app "QuickTime Player"'Every profile entry maps events to actions. All events are optional:
| Event | Fires when |
|---|---|
press / release |
the buzzer is pressed down / let go |
doublePress / longPress |
two quick presses / held for ≥0.5 s |
wheelUp / wheelDown |
the wheel is turned (once per step) |
touch / untouch |
a hand approaches/hovers over the buzzer / moves away (proximity — no contact needed) |
longTouch |
hand hovering or resting for ≥0.5 s |
Gesture timing: with no double/long variant configured, press and touch
fire immediately (raw behavior). Configuring longPress moves the single
press to the release; configuring doublePress additionally delays it by
the double-tap window — the price of disambiguation. release and
untouch always fire on the physical transition.
Timings are global (they describe the user, not the app) and adjustable in
the settings next to each gesture row, or via an optional top-level timing
block. With one present, the file format is
{ "timing": { "doublePressWindow": 0.3, "longPressHold": 0.5, "longTouchHold": 0.5 }, "profiles": { … } } —
bare profile dictionaries (the old format) still load fine.
The settings window also has a test mode (toggle above the events table): buzzer inputs flash the matching event row instead of running their actions — handy for tuning gestures without side effects.
An action is an object with exactly one of two keys:
{"shell": "…"}— run a shell command (/bin/sh -c). Works without any permissions. This covers almost everything:osascript,open, andshortcuts run "Name"to trigger automations built in the macOS Shortcuts app.{"key": "…"}— synthesize a key press. Requires a one-time Accessibility grant (System Settings → Privacy & Security → Accessibility); buzzerd prompts for it at startup if key actions are configured. Two forms:- Shortcut combos like
cmd+shift+m— modifierscmd,shift,alt/opt,ctrl, plus one key: a letter, digit,space,return,tab,delete,esc,f1–f12, or arrow keysleft/right/up/down. Key codes are US-physical; on QWERTZ layouts z/y are swapped (see ROADMAP). - Named media keys
volumeup,volumedown,mute,playpause,next,previous— sent as native system events, so the macOS volume/playback HUD appears and playback controls reach whatever app is currently playing.
- Shortcut combos like
"led" sets the buzzer's LED color while the profile is active — glanceable
feedback for which mode you're in. Values are 0–127 per channel:
[r, g, b]— all three LEDs the same color[r, g, b, r, g, b, r, g, b]— left, middle, right LED individually
The device is not HID — it is a standard USB MIDI device
(USB 16D0:1170, audio class with a MIDI streaming interface). macOS binds
it to the MIDIServer automatically; it shows up as a MIDI source and
destination named "timeBuzzer". No driver required.
Everything is Control Change on MIDI channel 12 (status byte 0xBB):
| CC | Direction | Meaning |
|---|---|---|
| 70–72 | → device | Left LED: R, G, B (0–127) |
| 73–75 | → device | Middle LED: R, G, B |
| 76–78 | → device | Right LED: R, G, B |
| 80 | ← device | Wheel, absolute position 0–127 (wraps) |
| 81 | ← device | Proximity sensor, active-low: < 64 = hand near/on the buzzer (triggers shortly before contact, binary threshold — only 0/127 observed); state is repeated every ~1.5 s (looks like a heartbeat) |
| 82 | ← device | Button: 127 = pressed, 0 = released |
The device does not respond to the standard SysEx identity request
(F0 7E 7F 06 01 F7).
swift buzzer.swift listen # dump incoming events (hex + decoded)
swift buzzer.swift led 127 0 0 # all 3 LEDs red
swift buzzer.swift led 127 0 0 0 127 0 0 0 127 # left red, middle green, right blue
swift buzzer.swift send BB 46 7F # send raw MIDI bytes
swift buzzer.swift scan [from] [to] [ms] # step through CCs one by one
led-ui.html — browser UI (Web MIDI, open in Chrome) with buttons for the
LED CCs.
Since this is a standard MIDI device, any MIDI library works out of the box:
Python (mido), Node (easymidi), the browser (Web MIDI API) — no driver
needed.
Engine.swift shared engine: MIDI, profiles, gestures, actions, LED
buzzerd.swift CLI entry point + selftest
BuzzctlApp.swift SwiftUI menu bar app and settings window
buzzer.swift standalone protocol debug tool (no build step)
build.sh builds ./buzzerd and ./Buzzctl.app, runs the selftest
./build.sh compiles both targets and runs ./buzzerd selftest, which
covers the pure logic (wheel wrap-around, key combo parsing, and the
gesture state machine with shortened timers). Keep it green — it is the
only test.
./buzzerd -v buzzerd.json # log every raw MIDI event and fired action
swift buzzer.swift listen # raw protocol dump, no config involvedOnly run one of them at a time, and quit Buzzctl.app while using the
CLI: every running instance reacts to the same device, so two of them
execute every action twice — with different configs, which looks like the
app ignoring its own settings.
Key actions (including media keys) are silently dropped by macOS unless
the binary has Accessibility access, and that grant is tied to the code
signature. Without a signing identity, build.sh falls back to an ad-hoc
signature, which changes on every rebuild — so macOS treats each build
as a new app and the previous grant no longer applies. Symptom: keystrokes
stop working after a rebuild while shell actions keep running, and stale
Buzzctl entries pile up in the Accessibility list.
Clear those entries and re-grant once:
tccutil reset Accessibility com.github.a-und-b.buzzctlThen use the menu bar item "build.sh picks
up the first identity it finds, keeping the signature — and the grant —
stable across rebuilds.
Note that the app prompts for the permission only on first launch; afterwards a missing grant surfaces as a menu bar item rather than a dialog.
Bump the version in Info.plist, then:
./build.sh && ditto -c -k --keepParent Buzzctl.app Buzzctl-<version>-arm64.zip
gh release create v<version> Buzzctl-<version>-arm64.zip --title "Buzzctl <version>"Releases are ad-hoc signed and Apple Silicon only; Developer ID signing, notarization and a universal binary are on the roadmap.
- Ideas in Logic GbR — makers of the timeBuzzer® hardware this project builds on. If you like the device, buy one from them.
- vertexitde/OpenBuzzer — Electron app for LED animations (Pomodoro, media keys, plugin system) using the same MIDI protocol; also provided the hint that CC 81 is a sensor reading rather than a heartbeat.