fcbnerd

Voorbeeld van directe binding: $ fcbnerd -q --bind '1:20:127=open ~/Downloads' --bind 'pc:1:0=say hello'

Voorbeeld van streaming voor externe programma's: $ 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"}

Hoewel fcbnerd is gebouwd voor de Behringer FCB1010, is niets in de software specifiek voor de FCB1010: elke CoreMIDI-bron werkt.

Waarom een command-line tool in plaats van een app?

Alles wat acties uitvoert op je Mac, zoals het indrukken van toetsen of het runnen van scripts, vereist permissies die sandboxed apps niet kunnen krijgen. Bovendien wil elke gebruiker een andere set acties. fcbnerd leest alleen MIDI, waarvoor geen permissies nodig zijn. De acties behoren tot je shell, of tot een tool die al over de nodige toegang beschikt, zoals Hammerspoon of Keyboard Maestro.

Installatie

Via Homebrew

brew trust --tap jamesryanatx/tap   # Homebrew 7+ laadt geen third-party taps totdat je ze vertrouwt
brew install JamesRyanATX/tap/fcbnerd

Vanaf broncode (zonder Homebrew)

Vereist Xcode of de Swift toolchain, macOS 13+:

swift build -c release
cp .build/release/fcbnerd /usr/local/bin/

Gebruik

Commando's

fcbnerd [listen] [--source NAME] [--format json|text] [--bind BINDING]... [--quiet] [--shell PATH] fcbnerd list [--format json|text] fcbnerd simulate

  • listen (standaard): Maakt verbinding met elke MIDI-bron, of alleen met bronnen waarvan de naam --source bevat, en streamt gebeurtenissen tot het programma wordt onderbroken. Het ondersteunt hotplugging: als je de interface tijdens gebruik loskoppelt en weer inplugt, gaat de stream door met meldingen over het disconnecten en connecten.
  • list: Print de bronnen die op dit moment beschikbaar zijn.
  • simulate: Publiceert een virtuele MIDI-bron genaamd "fcbnerd simulator" die in een loop synthetische indrukken, een pedaalbeweging en een sysex-bericht afspeelt. Draai dit in één terminal en fcbnerd in een andere om een consumer te bouwen zonder dat er een fysiek pedaal is aangesloten.

Opties

  • --format text: Print uitgelijnde kolommen voor visuele inspectie, inclusief een bind= patroon voor elk bericht dat je kunt koppelen. Scripts moeten de standaard JSON gebruiken, omdat de tekstlay-out kan wijzigen.
  • --bind: Voert een commando uit wanneer een bericht overeenkomt met het patroon (zie hieronder).
  • --quiet: Stopt het printen van gebeurtenissen, zodat alleen de gekoppelde commando's overblijven.
  • --shell PATH: Bepaalt welke shell de gekoppelde commando's uitvoert (standaard /bin/sh).

Statusberichten gaan naar stderr; stdout bevat alleen gebeurtenissen. Elke regel wordt direct geflusht, zodat pipes gebeurtenissen onmiddellijk ontvangen.

Commando's koppelen (Binding)

Stel eerst vast wat je pedaal verzendt. Draai fcbnerd -f text en druk op de schakelaar:

$ 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]

Koppel vervolgens een commando aan dat patroon: fcbnerd --bind '1:20:127=open ~/Downloads'

Een koppeling is PATROON=COMMANDO. Alles na het eerste = teken is het commando, dus dit kan zelf ook = en : bevatten. Je kunt --bind zo vaak gebruiken als je wilt. Elke koppeling die overeenkomt met een bericht wordt gestart in de opgegeven volgorde en ze draaien gelijktijdig.

Patronen

PatroonMatches
CHANNEL:CONTROLLER:VALUEControl change, bijv. 1:20:127. Ook cc:1:20:127 werkt.
pc:CHANNEL:PROGRAMProgram change, bijv. pc:1:7.
* (wildcard)Elk getal kan een zijn. Bijv. 1:30: is elke waarde van controller 30 op kanaal 1; dit is hoe je een expressiepedaal koppelt.

Commando's worden op de achtergrond uitgevoerd via /bin/sh -c, of de shell die je opgeeft met --shell. Hun stdin is /dev/null. Hun stdout gaat naar de stderr van fcbnerd om de event-stream niet te corrumperen; bij gebruik van --quiet gaat dit naar stdout.

Ze hebben toegang tot de volgende omgevingsvariabelen:

  • MIDI_TYPE: cc of pc
  • MIDI_CHANNEL: 1–16
  • MIDICONTROLLER, MIDIVALUE: Voor cc
  • MIDI_PROGRAM: Voor pc
  • MIDI_SOURCE: Naam van de MIDI-bron

Voorbeeld: Expressiepedaal stelt het uitgangsvolume in fcbnerd -q --bind '1:30:=osascript -e "set volume output volume $((MIDI_VALUE 100 / 127))"'

Elke druk op een schakelaar voert het commando uit, dus twee snelle drukken voeren het twee keer uit, zelfs als de eerste run nog niet is voltooid. Dit betekent ook dat elk overeenkomend bericht een shell start. Houd brede patronen zoals ::127 of pc:: weg van "ruisgevoelige" apparaten.

Pedaalbewegingen (sweeps) zijn de uitzondering. Een sweep verzendt tientallen waarden per seconde. Voor een koppeling met een * waarde wordt daarom slechts één kopie van het commando tegelijk uitgevoerd voor elke controle (kanaal en controller). Terwijl het commando draait, bewaart fcbnerd alleen de nieuwste waarde van die controle en voert deze daarna uit. Dit houdt het aantal shells laag en zorgt er toch voor dat de actie eindigt op de uiteindelijke positie van het pedaal. Als een commando na 5 seconden nog steeds draait, meldt fcbnerd dit op stderr.

Een commando dat met een non-zero status afsluit, krijgt zijn koppeling en exit-status geprint op stderr. Het stoppen van fcbnerd (Ctrl+C, kill, het sluiten van de terminal of een gesloten stdout) verzendt een SIGTERM naar elk commando dat nog draait, inclusief de processen die het heeft gestart.

Shell-functies

Functies en aliassen van je interactieve shell worden niet geladen in sh -c. In bash kun je een functie exporteren om deze zichtbaar te maken (de /bin/sh van macOS is bash, dus de standaard shell ziet deze):

greet() { say "preset $MIDI_PROGRAM"; }
export -f greet
fcbnerd -q --bind 'pc:1:*=greet'

zsh kan geen functies exporteren. Plaats ze in een bestand en source dit met zsh: --shell /bin/zsh --bind 'pc:1:*=source ~/.fcbnerd.zsh && greet'

Aan/uit-schakelaars

De FCB1010 verzendt niets wanneer je een schakelaar loslaat, dus een koppeling wordt alleen geactiveerd bij het indrukken. Voor aan/uit-gedrag moet de status in het commando zelf worden bijgehouden, bijvoorbeeld door een bestand in /tmp te toggelen.

Output

fcbnerd listen print één JSON-object per regel. Elk object bevat type, source (de weergavenaam van de MIDI-bron) en time (wanneer fcbnerd het bericht ontving: ISO 8601, UTC, milliseconden). Kanalen zijn 1–16; note, controller, program, velocity en pressure waarden zijn de ruwe MIDI-waarden van 0–127.

typeExtra veldenNotities
pcchannel, programProgram change. program is 0-based op de lijn.
ccchannel, controller, valueControl change: schakelaars en expressiepedalen.
note_onchannel, note, velocity
note_offchannel, note, velocityWordt ook verzonden voor note_on met velocity 0.
poly_pressurechannel, note, pressure
channel_pressurechannel, pressure
pitch_bendchannel, value0–16383, midden is 8192.
sysexlength, datadata is lowercase hex inclusief de f0…f7 framing; length telt deze bytes.
connectedEen bron is verschenen en wordt beluisterd. Gaat altijd vooraf aan de events van die bron.
disconnectedEen bron is verdwenen. Een bericht dat al onderweg was, kan hier nog op volgen.

Systeem real-time berichten (zoals MIDI clock) en systeem common berichten (song position, MTC) worden niet verzonden. Nieuwe event-types of velden kunnen in toekomstige versies worden toegevoegd; bestaande zullen niet van betekenis veranderen. Consumers moeten types en velden die ze niet herkennen negeren.

Lees de stream prompt. Als een consumer stopt met lezen, plaatst fcbnerd events in het geheugen en levert deze allemaal af zodra het lezen wordt hervat; een gestagneerde consumer zal dus reageren op een burst van oude indrukken.

fcbnerd list --format json print een andere structuur, één regel per bron: {"type":"source","name":"UM-ONE","id":-1234567}. id is de unieke CoreMIDI ID.

Voorbeelden

examples/developer.sh is een complete, becommentarieerde setup voor software engineers: tien schakelaars voor het runnen van tests, wachten op CI, syncen van de branch, het muten van de microfoon en meer, plus een expressiepedaal voor het uitgangsvolume. Draai dit eerst met DRY_RUN=1 om te zien wat elke schakelaar zou doen.

Shell en jq

Programma 0 schakelt naar de volgende "Space", en programma 1 naar de vorige. Dit vereist meerdere Spaces, dat de sneltoetsen "Move left/right a space" zijn ingeschakeld (standaard) in Systeeminstellingen → Toetsenbord → Toetsenbordsneltoetsen → Mission Control, en dat je terminal-app zowel Accessibility- als Automation-permissies heeft om Systeemgebeurtenissen te besturen.

fcbnerd | jq --unbuffered -r 'select(.type == "pc") | .program' |
while read -r program; do
case "$program" in
0) osascript -e 'tell application "System Events" to key code 124 using control down' ;;
1) osascript -e 'tell application "System Events" to key code 123 using control down' ;;
esac
done

Hammerspoon

Programma 0 toggelt play/pause, en een expressiepedaal op CC 30 stelt het uitgangsvolume in. Omdat output in gedeeltelijke chunks kan arriveren, wordt er gebufferd tot een nieuwe regel. Het pad in het voorbeeld is voor Apple Silicon; Homebrew op Intel installeert in /usr/local/bin.

local buffer = ""
fcbnerd = hs.task.new("/opt/homebrew/bin/fcbnerd", nil, function(_, stdout, _)
buffer = buffer .. stdout
for line in buffer:gmatch("([^\n]*)\n") do
local event = hs.json.decode(line)
if event and event.type == "pc" and event.program == 0 then
hs.eventtap.event.newSystemKeyEvent("PLAY", true):post()
hs.eventtap.event.newSystemKeyEvent("PLAY", false):post()
elseif event and event.type == "cc" and event.controller == 30 then
hs.audiodevice.defaultOutputDevice():setVolume(event.value / 127 * 100)
end
end
buffer = buffer:match("[^\n]*$")
return true
end)
fcbnerd:start()

Opmerkingen over de FCB1010

Zaken waar consumers rekening mee moeten houden bij dit pedaal:

  • Een druk verzendt één bericht en het loslaten verzendt niets. Aan/uit-gedrag (eerste druk "aan", tweede "uit") moet door de consumer worden bijgehouden.
  • De fabrieksinstellingen verzenden verschillende CC-nummers van dezelfde schakelaar, afhankelijk van welke preset actief is. Draai fcbnerd -f text, druk op elke schakelaar die je wilt gebruiken, en noteer wat er verzonden wordt.
  • Het indrukken van een schakelaar verzendt ook de huidige waarden van de expressiepedalen van die preset opnieuw; dus niet elke CC op een pedal-controller betekent dat de voet bewogen is.
  • De expressiepedalen bereiken niet het volledige 0–127 bereik. Een deel van de beweging verzendt niets en de sweep bestrijkt ongeveer twee derde van de waarden; schaal dit dus om naar het bereik dat je daadwerkelijk ziet.
  • Het pedaal heeft alleen 5-pins DIN MIDI. Je hebt een USB MIDI-interface nodig, die verschijnt als de bronnaam.

Ontwikkeling

swift build
swift test                                 # decoder, formatter en binding tests
.build/debug/fcbnerd simulate &            # fake pedaal
.build/debug/fcbnerd --format text         # watch it

Sources/FCBNerdCore decodeert CoreMIDI's Universal MIDI Packets, formateert de output en parseert bindings. Het heeft geen CoreMIDI-afhankelijkheid, dus de tests kunnen zonder hardware draaien.

Sources/fcbnerd is de CLI: CoreMIDI-verbindingen, hotplug en de simulator.

Om een release te maken, verhoog je de versie in Sources/fcbnerd/main.swift, commit je deze, en push je een bijbehorende tag: git tag -a v1.2.3 -m "fcbnerd 1.2.3" && git push origin v1.2.3

De release workflow test, publiceert een GitHub Release met een universal binary, en update de formula in JamesRyanATX/homebrew-tap.

Licentie

MIT