# SendMIDI: sending MIDI messages from the command line

> SendMIDI is a command line tool for sending MIDI messages to MIDI output ports, built on the JUCE library and meant for configuration work: setting a device with Control Change, RPN or NRPN messages, and also handling notes, pitch bend, program change, SysEx, timing clock and MPE. A verbose command language with notes, hex and decimal prefixes and timestamps covers the cases where a hardware device has no other way to be configured.

**gbevin/SendMIDI** — Multi-platform command-line tool to send out MIDI messages

- Repository: https://github.com/gbevin/SendMIDI
- Website: https://uwyn.com
- Stars: 855 · Forks: 56
- Language: C++
- License: GPL-3.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/gbevin-sendmidi

## What the tool is for and what it is built on

SendMIDI is a multi-platform command-line tool that makes it very easy to quickly send MIDI messages to MIDI devices from your computer. The heavy lifting is done by the JUCE library, which is the audio and MIDI framework that many audio applications are built on.

The purpose statement is more specific than the summary, and it is worth quoting because it explains the design. The tool is mainly intended for configuration or setup through Control Change, RPN and NRPN messages, but many other MIDI messages can be sent.

That is the whole idea. A synthesizer, a MIDI controller or a lighting desk often has settings that live behind a MIDI message and nothing else. The device ships with a manual telling you to send a CC with a particular number and value, and if you do not have the controller that sends it, you need a way to send it by hand. A GUI utility for that is a small program, but a command line one can be driven from a script, from a shell loop, from a dotfile, or from another program.

The interface is designed around that. To use it, you type sendmidi or sendmidi.exe and follow it with a series of commands to execute. The commands have purposefully been chosen to be concise and easy to remember, so that it is extremely fast and intuitive to quickly shoot out a few MIDI messages.

The README also links a tutorial video about both SendMIDI and ReceiveMIDI, including some tips and tricks of how to use the command line on macOS

## Installing: binaries, Homebrew and building from source

Pre-built binaries are available from the release section on GitHub, and the releases are recent and versioned: 1.4.4 from 2026-09-17, 1.4.3 from 2026-07-19 and 1.4.2 from 2026-07-18.

Since SendMIDI is free and open-source, you can also easily build it yourself, and the instruction is to look in the Builds directory when you download the sources. The repository layout explains what that means: a Builds/ directory holding the per-platform project files, a JuceLibraryCode/ directory for the JUCE source, a Patches/ directory, a Source/ directory, a Scripts/ directory, a Tests/ directory, and sendmidi.jucer, which is the JUCE project file that ties them together.

That is a JUCE application project rather than a conventional CMake or Make build, which is a direct consequence of building on JUCE. The consequence for a user is that building it means a C++ toolchain and the JUCE source tree, which is a heavier requirement than a typical small utility, and it is why the pre-built binaries matter more here than they would for a plain command line tool.

On macOS the Homebrew route is a single command:

```
brew install gbevin/tools/sendmidi
```

That is a tap on the author's own repository rather than homebrew-core, so it tracks new releases faster than the core formula would.

The project is GPL-3.0 licensed with the text in COPYING.md, the homepage is uwyn.com, and the last push to the repository was on 2026-09-22.

## The command language, and the long forms

The command list is long, and it is the reference you will actually use.

The output side starts with dev name to set the name of the MIDI output port, virt for a virtual MIDI port with an optional name on Linux and macOS, and list to list the MIDI output ports.

The transport commands include panic, which sends Note Offs, panic CCs and resets controllers and bend, and file path to load commands from a specified program file.

The number interpretation is controlled by dec and hex, which interpret the next numbers as decimals or hexadecimals by default. The channel is set with ch number, valid from 1 to 16 and defaulting to 1, and the octave for middle C is set with omc number, defaulting to 3.

The message commands cover on and off for Note On and Note Off with note and velocity from 0 to 127, pp for Poly Pressure, cc for Control Change, cc14 for the 14-bit variant with number from 0 to 31 and value from 0 to 16383, pc for Program Change, cp for Channel Pressure, pb for Pitch Bend, rpn and nrpn for the registered and non-registered parameter numbers, both with number and value up to 16383.

Then the sequencing and system commands: clock bpm with an optional number of beats, defaulting to 2 and 0 meaning until stopped, mc for a single MIDI Timing Clock, start, stop and cont for the transport, as for Active Sensing, rst for Reset, syx for raw System Exclusive bytes without the F0 and F7 delimiters, syf for a SysEx file, nowait to avoid waiting for SysEx to be sent at worst-case MIDI speed, tc for MIDI Time Code, spp for Song Position Pointer, ss for Song Select and tun for Tune Request.

Finally the MPE and raw commands: mpe for MPE Configuration with a zone from 1 to 2 and a range from 0 to 15, mpp to configure an MPE Profile initiator with a MIDI input port, a manager channel, a member channel count and a member name, mpetest to send a sequence of MPE messages for testing a receiver, and raw for raw MIDI bytes.

Every short command has a long form, and the long forms are listed as well: device, virtual, decimal, hexadecimal, channel, octave-middle-c, note-on, note-off, poly-pressure, control-change, control-change-14, program-change, channel-pressure, pitch-bend, midi-clock, continue, active-sensing, reset, system-exclusive, system-exclusive-file, no-wait, time-code, song-position, song-select, tune-request, mpe-profile, mpe-test and raw-midi.

There are also three command line options: help, version, and a bare double dash which reads commands from standard input until it is closed.

## Number formats, note names and device matching

Three conventions make the command language practical, and each is a small decision that removes a lot of friction.

The first is number base. By default, numbers are interpreted in the decimal system, and this can be changed to hexadecimal by sending the hex command. Additionally, by suffixing a number with M or H, it is interpreted as a decimal or hexadecimal respectively, so you can override the base for a single value rather than switching modes for a run.

The second is note names. Where notes can be provided as arguments, they can also be written as note names, by default from C-2 to G8, which corresponds to note numbers 0 to 127. Setting the octave for middle C changes that range, sharps are added with the number sign after the note letter, and flats by using the letter b. So a program change on a specific pitch is written the way a musician would say it rather than as a number to look up.

The third is device matching. The MIDI device name does not have to be an exact match. If SendMIDI cannot find the exact name that was specified, it picks the first MIDI output port that contains the provided text, irrespective of case. Ports that share the same name are listed with a number, like Port (2), and that numbered name can be used to select that specific port.

The substring matching matters more than it sounds. MIDI port names on macOS, Linux and Windows follow different conventions and a given device will not be named the same way on each, so a substring is often the only thing that works across platforms. The numbered disambiguation is the detail that makes it usable when you have two identical devices, which is common with audio interfaces.

## Timestamps, the relative form and why they exist

Between commands, timestamps can be added in the format HH:MM:SS.MIL, standing for hours, minutes, seconds and milliseconds, for example 08:10:17.056. All the digits need to be present, possibly requiring leading zeros. When a timestamp is detected, SendMIDI ensures that the time difference since the previous timestamp has elapsed.

So a timestamp is an absolute point on a clock, and the tool waits until that point arrives before running the next command. If the previous timestamp has already passed, the wait is zero and the command runs immediately.

When a timestamp is prefixed with a plus sign, it is considered relative and is processed as a time offset instead of an absolute time, so +00:00:01.060 executes the next command one second and 60 milliseconds later. For convenience, a relative timestamp can also be shortened to +SS.MIL, for example +01.060.

That combination is what makes the tool scriptable. A sequence of device configuration messages with absolute timestamps is a reproducible procedure: the same file always produces the same result, with the same timing, on any machine. A sequence with relative offsets is a performance: send this, wait a beat, send that.

The clock command is the third timing mechanism. It sends MIDI Timing Clock for a BPM from 1 to 999, optionally for a number of beats, defaulting to 2, with 0 meaning until stopped, and the separate mc command sends a single clock tick. Together with start, stop and cont, that is a minimal transport for driving a device that follows MIDI clock, without the tool becoming a sequencer.

The panic command is the safety valve. Sending Note Offs, panic CCs and resetting controllers and bend is what you need when a script has left a device in a stuck state, and having it as one command means a recovery script does not have to know what state the device is in.

## Limits: a configuration tool, platform port naming and the build weight

Several limits are worth knowing.

The first is scope. The tool is mainly intended for configuration or setup, and while it can send notes and sequence events, it is not a sequencer, a MIDI monitor or a virtual instrument. There is no file format for patterns beyond the program file of commands, no piano roll, no looping structure and no recording. If you want to play material, a different tool is the right one.

The second is platform port naming, which the substring matching papers over but does not remove. The dev command takes a name, and on a machine with several outputs the first substring match wins unless you use the numbered form. On Windows in particular the port list is populated differently from macOS, so a script written on one platform can select the wrong device on another even when the substring is unambiguous on the first.

The third is the build weight. This is a JUCE application, so building from source means compiling JUCE, which is a large C++ dependency, and the tool itself carries the JUCE runtime into the binary. For a utility whose whole job is sending a few bytes to a MIDI port, that is a large footprint, and it is why the pre-built binaries and the Homebrew tap are the practical install paths.

The fourth is the MIDI 1.0 assumption underneath most of the commands. The channel-based model with 16 channels and 7-bit velocities is MIDI 1.0 throughout, with the MPE commands being the extension into per-zone expression rather than a change of paradigm. A modern MPE controller is supported well enough to configure, but SendMIDI is not a tool for working in the MPE data model as such.

The fifth is SysEx handling. The syx command takes the bytes without the F0 and F7 delimiters, and syf reads a file, and there is a nowait option to avoid blocking for the worst-case MIDI rate. That last option exists because SysEx transfers over MIDI 1.0 at 31.25 kbit per second are slow enough that waiting for them is noticeable, and the option trades reliability of timing for responsiveness.

## SendMIDI against a MIDI control surface and against a plugin-based control panel

Two comparisons make the choice clearer.

The first is against using a MIDI control surface. A hardware controller with knobs and faders can send any CC, RPN or NRPN message you want, and it does so with the timing and feel of a musical instrument. The difference in approach is that it requires the device, it occupies your hands, and it cannot be scripted. SendMIDI is for the cases the controller cannot cover, which is exactly the case the README describes: configuring a device that needs a message you do not have a control for.

The second comparison is against a plugin-based MIDI control panel, of which there are many, typically running inside a DAW or as a standalone application with a grid of sliders. The difference in approach is that those tools are interactive and visual, with a UI where you pick a CC number from a list and drag a value. That is faster for a human doing one-off configuration and hopeless for automation, because the value lives in a widget rather than in a script.

SendMIDI occupies the space between the two: it is as automatable as a script and as terse as a command, at the cost of discoverability. There is a help command, but a person who does not know the CC number they need will not find it by guessing, whereas a GUI with a CC list would tell them.

The pattern across both comparisons is the same. Use a control surface or a GUI panel when a human is doing the configuring once. Use SendMIDI when the configuration has to be repeatable, scripted, checked into a repository, or triggered by something else.

## Conclusion

Adopt SendMIDI if you configure MIDI hardware from a script or a terminal and want to send Control Change, RPN or NRPN messages without a control surface in front of you. Do not expect it to replace a sequencer for playing material, since it is built for configuration rather than performance. Verify first that your device appears in the output list on your platform, since MIDI port naming differs between macOS, Linux and Windows, and that the numeric defaults the tool uses match your expectations, since the MIDI channel defaults to 1 and the octave for middle C to 3.

## FAQ

### How do I install SendMIDI?

Pre-built binaries are on the repository's releases page, which currently has 1.4.4 from 2026-09-17. On macOS you can install it with brew install gbevin/tools/sendmidi, using the author's own tap rather than homebrew-core. You can also build it yourself from the sources, looking in the Builds directory, since the project is a JUCE application.

### What is SendMIDI mainly used for?

The README says the tool is mainly intended for configuration or setup through Control Change, RPN and NRPN messages, though many other MIDI messages can be sent. The full command list also covers note on and off, poly pressure, 14-bit control change, program change, channel pressure, pitch bend, MIDI timing clock, the transport, system exclusive, MIDI time code and MPE configuration.

### How do I write notes and control the timing of SendMIDI commands?

Notes can be given as note names, by default from C-2 to G8 which corresponds to note numbers 0 to 127, with sharps written after the note letter and flats as the letter b, and the range changes with the octave set for middle C. Between commands you can add timestamps in the form HH:MM:SS.MIL, and a plus sign makes the timestamp relative rather than absolute.

## Sources

- [gbevin/SendMIDI on GitHub](https://github.com/gbevin/SendMIDI)
- [License: GPL-3.0](https://github.com/gbevin/SendMIDI/blob/master/LICENSE)
- [Project website](https://uwyn.com)
- [README](https://github.com/gbevin/SendMIDI/blob/master/README.md)
- [Releases](https://github.com/gbevin/SendMIDI/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/gbevin-sendmidi
