Standalone tool reading input from a MIDI device and converting them into LilyPond notes, with integration into other tools as a strong focus.
This is a tool specifically targeted at writing LilyPond scores. Entering notes using a MIDI keyboard is very handy and can greatly speed up the process, for which I always used Frescobaldi. There was an issue however — I already had a fully personalized setup for writing LilyPond in my text editor of choice, yet always found myself going back to Frescobaldi for the MIDI input; as a result, I ended up writing my scores in Frescobaldi, even beyond the MIDI input. (Frescobaldi is great though!)
lilypond-midi-input
aims to bridge the gap between MIDI input for LilyPond notes, and any arbitrary text editor which supports async inputs. The idea is that this tool will listen for MIDI inputs from a device, and will transform them into corresponding LilyPond notes that can directly be inserted into your LilyPond files!
This is a standalone program which does just that: Read MIDI inputs from a device, and spit out LilyPond notes onto stdout. This will hopefully make integration into other editors easier. Basic usage walks through how the program works. For those wishing to integrate this into their editors, please take a look at the specifications on how to handle the input and output streams.
Fully automate text input for LilyPond notes is not an objective for this tool. This means for example that adding note durations will not be handled here. Automatically detecting rhythm during playback is therefore also not an objective of this tool. Such features should be provided/created by wrappers.
Again, the main goal here is to provide translation of MIDI notes into LilyPond notes, and as a result make MIDI input easier to integrate into other editors.
-
All notes on a keyboard are translated to LilyPond notes with absolute octave entry
Demo Video 🎬
A chromatic scale being played across the entire piano, with their corresponding LilyPond notes being output.
notes-to-lilypond-feature.mp4
-
Specify musical key signatures to influence how accidentals (black keys) are interpreted
Demo Video 🎬
Shows the following keys
-
C major
-
A minor (harmonic minor), note the G sharp note
-
B major, note all black keys being sharps
-
G sharp minor (harmonic minor), note the G natural being output as F double-sharp
-
C flat major, note all black keys being flats
-
B flat minor (harmonic minor)
musical-key-feature.mp4
-
-
Specify how to handle accidentals outside a key signature (fall back to sharps or flats)
Demo Video 🎬
-
Example in F major which has a B flat
accidentals-fM-feature.mp4
-
Example in G major which has an F sharp
accidentals-gM-feature.mp4
-
-
Different input modes
-
Single: Input one note at a time
Demo Video 🎬
-
Shows a scale being played
-
Shows a chord being played and how it inserts only single notes (even if all are held)
-
Shows long held notes to highlight that notes are inserted as soon as key is pressed
mode-single-feature.mp4
-
-
Chord: Allow inputting chords by holding down multiple keys at once
Demo Video 🎬
-
Shows a chord being played and how it is inserted after releasing the keys
-
Shows notes being held, while pressing new ones and releasing others, highlighting that notes will be aggregated until everything is released
-
Shows long held notes to highlight notes are inserted as soon as all keys are released
mode-chord-feature.mp4
-
-
PedalChord: Behave like Chord when any piano pedal is pressed, otherwise behave like Single
Demo Video 🎬
-
Shows chord being played without pedal, behaving like Single
-
Shows chord being with pedal, behaving like *Chord
mode-pedal-chord-feature.mp4
-
-
PedalSingle: Behave like Single when any piano pedal is pressed, otherwise behave like Chord (the opposite of how PedalChord behaves)
Demo Video 🎬
-
Shows chord being played without pedal, behaving like Chord
-
Shows chord being played with pedal, behaving like Single
mode-pedal-single-feature.mp4
-
-
-
Specify custom alterations for notes within a scale/octave
Demo Video 🎬
-
Shows every C being replaced by
YO
-
Shows every B being replaced by
BYE
alterations-feature.mp4
-
-
Specify custom alterations across all notes of the MIDI device
Demo Video 🎬
-
Shows one specific C being replaced by
YO
-
Shows one specific B being replaced by
BYE
global-alteration-feature.mp4
-
-
List all available MIDI input devices
-
Specific handling of input/output for integration into other editors
-
stdout for relevant ouptut
-
stderr for sharing messages from the tool
-
stdin to asynchronously take options to change settings on-the-fly
-
You will need PortMidi installed, regardless of the installation method (well, except for Nix). Note the libportmidi-dev
package should only be needed for Ubuntu when building from source.
pacman -S portmidi # for arch
apt install libportmidi0 libportmidi-dev # for debian/ubuntu
The latest release will contain pre-built binaries (different versions due to the PortMidi system library).
-
Debian, should also work on Ubuntu
Note
|
Be sure to make the binaries available as lilypond-midi-input on your system, without the _* extension. That one was only useful to distinguish the different versions in the release assets.
|
You will need cargo and PortMidi installed to build the project. The binary will be installed as lilypond-midi-input
.
cargo install --path . # inside this repository
This project also comes with a flake.nix, meaning that you can use this program without any additional hassle. For example, with flakes you can add it as follows to a dev shell.
{
inputs = {
nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
utils.url = "github:numtide/flake-utils";
lmi.url = "github:niveK77pur/lilypond-midi-input";
};
outputs = {
nixpkgs,
utils,
lmi,
...
}:
utils.lib.eachDefaultSystem (system: let
pkgs = nixpkgs.legacyPackages.${system};
in {
devShell = pkgs.mkShell {
packages = [
lmi.defaultPackage.${system}
];
};
});
}
A comprehensive overview of settings and features can be found using the help page. More information can be found in a later section.
lilypond-midi-input --help
First, you need to specify which MIDI input device this tool should listen to. You can use the following command to get a list of available input devices. Take note of the name for the device of interest, we need to give it to the program to actually run it.
$ lilypond-midi-input --list-devices
1) Input: Midi Through Port-0
3) Input: USB-MIDI MIDI 1
4) Input: out
Let’s say we are interested in the input device listed as number 3 here. You can finally run the tool as follows.
lilypond-midi-input "USB-MIDI MIDI 1"
Note
|
The name must be an exact match! Leading and trailing spaces in the name are ignored. |
To exit, you can simply press Ctrl+C
.
As indicated by the --help
page, you can pass various options via command line flags, which shall not be elaborated on further. It should be mentioned that using command line flags will set the options on start-up and also provides a bit more helpful error messages if arguments are invalid.
The next method discussed will launch the program (with its default values), and allow changing options later. Practically speaking, there really is no major difference between the two methods. If your editor cannot write to this program’s stdin stream, you can use these flags as a workaround to relaunch with new settings.
This tool also allows changing/setting the options on-the-fly without restarting the program. To do this, you can directly type into the program’s stdin! Meaning that while the program is running, you can simply type commands into the terminal.
Upon successful parsing and execution of the given setting, the program will write a message to stderr, either indicating success or possibly indicating errors. As far as possible, the program tries to inform what has happened (through stderr), as otherwise it is difficult to judge whether the provided settings in stdin where handled correctly or not.
All options here have long and short versions, which the latter are particularly useful when manually typing in the commands into the terminal. A list of options and their values can be found in a later section.
The settings are given in the following form. You can specify one option at a time, or you can provide multiple options at once. A key that takes nested key-value pairs has its value given as SUBKEY:SUBVALUE
and are comma separated (without spaces). Here are some examples to hopefully clarify.
Note
|
Different options are space separated; so currently the values may not contain any spaces. |
KEY1=VALUE1 KEY3=SUBKEY1:SUBVALUE1,SUBKEY2:SUBVALUE2 KEY1=VALUE1 KEY2=VALUE2 KEY1=VALUE1 KEY2=VALUE2 KEY3=SUBKEY1:SUBVALUE1,SUBKEY2:SUBVALUE2
The interaction with this tool happens fully through stdin, stdout and stderr. Here is how each of these streams are used by this tool, allowing you to properly integrate it into your editor.
Spawning the process is ideally done by your editor, so that it can properly manage all the input and output streams.
Specifics on how to interact with each stream is of course dependent on the editor and its capabilities. You can have a look at existing integrations for some examples and inspiration.
Important
|
The tool is not capable of exiting by itself (i.e. there is no exit command for example). That said, you should try to kill the process in question, which should ideally be done by your editor.
|
As mentioned in Changing options, the stdin solely takes settings as key-value pairs. Upon successful parsing, the corresponding option will be set/updated internally. A corresponding message will also be written to stderr.
For options and their values, please check the following section; for usage examples please check the section Changing options.
Important
|
If the program is not responding to inputs being sent through stdin, it is possible that you have provided an invalid option which is simply not being parsed and captured. Or, it is possible that your editor also needs to add a newline at the end of the message, in order to trigger Rust to actually read the input line. |
This stream should only output data relevant to the task at hand. In the case of --list-devices
, it will be the list of devices. In the case of a normal execution, stdout will only have LilyPond notes printed as you input notes through your MIDI keyboard.
That said, stdout can be taken as-is. A user could for example be prompted to pick a MIDI device based on the output of --list-devices
. Most importantly, during normal execution the outputted LilyPond notes can be taken as-is in order to have them inserted into your text editor.
This stream contains any other message/information that the tool wants to share but should not be taken as text input by the editor. Currently, this counts general information such as a startup message, and indications that values were updated correctly via stdin. In case an option via stdin was invalid, an error message will also be written to stderr.
Errors are printed using the echoerr!
macro, while other information is printed using the echoinfo!
macro, the definition of both are found in this file. They prefix each line with a !!
and ::
respectively. This allows your client/editor to filter the messages from stderr according to actual errors or simple information.
The program also provides a --list-options
flag, which lists all available values for a given argument to stdout. The options are space separated, and no particular effort is made towards providing a well typeset output (i.e. as a tabular); the editors should decide how to treat the information.
The first value in the line corresponds to the actual enum variant’s name in the Rust code. The second value corresponds to the primary string from which the variant can be created. All following values are additional strings — usually shorthands — which can also be used to describe an enum variant. (See also the table).
All the values (without any "
or '
) can be used as-is to set an option via stdin. The second value can be used to set options via the command line arguments.
Using this method to display choices in the editor should be preferred as it avoids hardcoding the values. Further, if values should change, be added, or removed, it will require no intervention in the editor, as this tool can list its own options.
All flags and the values they can take are shown when running the program with the --help
flag. Thus, they will not be further discussed.
Of importance to point out are the values expected by --alerations
and --global-alterations
. Both of these take a list of comma-separated subkey-subvalue pairs, which are mentioned in a previous section. More concrete details are given in the table.
The option keys are the exact same as the command line flags but without the leading dashes. There are a few additional shorthands though. Also, the values it can take are a bit more broad compared to what the command line flags allow. Some of the values also allow shorthands. The following table describes the current options and their values. See also Changing options for examples on how to actually set them.
Options |
Values |
Description |
Example |
|
---|---|---|---|---|
Long |
Short |
|||
k |
Can take all strings and enum variant names in the list of available keysignatures |
Affects how accidentals will be printed depending on the given key signature. In GMajor, an F♯/G♭ will always be printed as |
|
|
a |
Can take all strings and enum variant names in the list of accidentals |
How to print accidentals that are not within the musical key? In the key of FMajor, |
|
|
m |
Can take all strings and enum variant names in the list of input modes |
How to handle MIDI input? |
|
|
alt |
Subkey-subvalue pairs. I.e. |
Set custom alterations within an octave; overrides special considerations for |
|
|
galt |
Same as |
Set custom alterations over all MIDI notes; further overrides |
|
|
pc |
Colon ( |
Explicitly specify a chord which will yield |
|
|
Long or short version of all other options. Alternatively |
Not exactly an option, but allows listing values for options. Useful to see what the current state is. |
|
I have written my own Neovim plugin which uses this tool to allow inputting notes asynchronously using a MIDI keyboard in Neovim! It also follows Vim’s modal philosophy and only inserts notes in Insert mode, and allows replacing notes in Replace mode!
-
MIDI input for LilyPond, able to quantize notes on the fly using a metronome: https://directory.fsf.org/wiki/Rumor
-
Another LilyPond MIDI input tool: https://github.com/nicolodavis/lilypond-midi-input
-
A proper CLI midi player: https://gitlab.com/dajoha/midiplay
-
❏ Generate notes for relative octave entry
-
✓ Repeated chords should return
q
-
✓ List all currently set (global) alterations
-
✓ List all options for a setting (avoids hardcoding them into editors)
-
✓ Simple screencast to show how this looks in action (under [features](#features))
-
✓ Debug option/mode to see raw midi events
-
✓ Specify ottavation for alterations (i.e.
0=bis
will cause the note to always be one octave too high)