Show HN: Macros with a Behringer FCB1010 MIDI Pedalboard in | Coderz Club

Show HN: Macros with a Behringer FCB1010 MIDI Pedalboard in macOS JamesRyanATX / fcbnerd Public Notifications You must be signed in to change notification settings Fork 0 Star 4 mainBranchesTagsGo to

Show HN: Macros with a Behringer FCB1010 MIDI Pedalboard in macOS JamesRyanATX / fcbnerd Public Notifications You must be signed in to change notification settings Fork 0 Star 4 mainBranchesTagsGo to

By Coderz Club · 2026-09-15 · Tags: git

Show HN: Macros with a Behringer FCB1010 MIDI Pedalboard in macOS

JamesRyanATX / fcbnerd Public Notifications You must be signed in to change notification settings Fork 0 Star 4 mainBranchesTagsGo to fileCodeOpen more actions menuLatest commit History9 Commits9 CommitsFolders and filesNameNameLast commit messageLast commit date.claude/commands.claude/commands .github.github SourcesSources Tests/FCBNerdCoreTestsTests/FCBNerdCoreTests docsdocs examplesexamples .gitignore.gitignore LICENSELICENSE Package.swiftPackage.swift README.mdREADME.md View all filesRepository files navigationfcbnerd Use a MIDI foot controller as an extra keyboard for your Mac. fcbnerd connects to your MIDI sources and either runs a shell command when a footswitch or pedal sends a message you've bound, or prints one JSON object per line for every message so another program can decide what a stomp means. $ fcbnerd -q --bind '1:20:127=open ~/Downloads' --bind 'pc:1:0=say hello' Or stream everything for another program to handle: $ fcbnerd {"type":"connected","source":"UM-ONE","time":"2026-09-14T20:01:00.120Z"} {"type":"pc","channel":1,"program":0,"source":"UM-ONE","time":"2026-09-14T20:01:02.345Z"} {"type":"cc","channel":1,"controller":30,"value":84,"source":"UM-ONE","time":"2026-09-14T20:01:03.910Z"} Built for the Behringer FCB1010, but nothing in it is FCB1010-specific: any CoreMIDI source works. Why a command-line tool instead of an app Anything that acts on your Mac, like pressing keys or running scripts, needs permissions that sandboxed apps can't get, and every user wants a different set of actions anyway. fcbnerd only reads MIDI, which needs no permissions. The actions belong to your shell, or to a tool that already has the access, such as Hammerspoon or Keyboard Maestro. Install brew trust --tap jamesryanatx/tap # Homebrew 7+ won't load third-party taps until you trust them brew install JamesRyanATX/tap/fcbnerd Or from source without Homebrew (Xcode or the Swift toolchain, macOS 13+): swift build -c release cp .build/release/fcbnerd /usr/local/bin/ Usage fcbnerd [listen] [--source NAME] [--format json|text] [--bind BINDING]... [--quiet] [--shell PATH] fcbnerd list [--format json|text] fcbnerd simulate listen (default) connects to every MIDI source, or only those whose name contains --source, and streams events until interrupted. It follows hotplug: unplug the interface mid-set and plug it back in, and the stream carries on with disconnected / connected lines. list prints the sources available right now. simulate publishes a virtual MIDI source named fcbnerd simulator that plays synthetic presses, a pedal sweep and a sysex message on a loop. Run it in one terminal and fcbnerd in another to build a consumer with no pedal attached. --format text prints aligned columns for eyeballing, including a bind= pattern for each message you can bind. Scripts should use the default JSON; the text layout may change. --bind runs a command when a message matches; see below. --quiet stops printing events, leaving only the bound commands. --shell PATH picks the shell that runs bound commands (default /bin/sh). Status messages go to stderr; stdout carries only events. Each line is flushed as soon as it's written, so pipes see events immediately. Binding commands First find out what your pedal sends. Run fcbnerd -f text and press the switch: $ fcbnerd -f text 16:30:41.115 pc channel=1 program=7 bind=pc:1:7 [USB MIDI Interface] 16:30:41.115 cc channel=1 controller=20 value=127 bind=1:20:127 [USB MIDI Interface] Then bind a command to that pattern: fcbnerd --bind '1:20:127=open ~/Downloads' A binding is PATTERN=COMMAND. Everything after the first = is the command, so it can contain = and : itself. Use --bind as many times as you like. Every binding that matches a message starts, in the order given, and they run at the same time. Pattern Matches CHANNEL:CONTROLLER:VALUE Control change, e.g. 1:20:127. cc:1:20:127 also works. pc:CHANNEL:PROGRAM Program change, e.g. pc:1:7. Any number can be *: 1:30:* is every value of controller 30 on channel 1, which is how you bind an expression pedal. Commands run in the background through /bin/sh -c, or the shell you give with --shell. Their stdin is /dev/null. Their stdout goes to fcbnerd's stderr, so it can't corrupt the event stream; with --quiet it goes to stdout. They see these environment variables: Variable MIDI_TYPE cc or pc MIDI_CHANNEL 1–16 MIDI_CONTROLLER, MIDI_VALUE For cc MIDI_PROGRAM For pc MIDI_SOURCE MIDI source name # Expression pedal sets output volume fcbnerd -q --bind '1:30:*=osascript -e "set volume output volume $((MIDI_VALUE * 100 / 127))"' Every stomp runs the command, so two quick presses run it twice even if the first run hasn't finished. That also means every matching message starts a shell. Keep broad patterns like *:*:127 or pc:*:* away from noisy devices. Pedal sweeps are the exception. A sweep sends dozens of values a second, so for a binding with a * value, only one copy of the command ru

View this page on Coderz Club