This NeoVim plugin is effectively a wrapperβββalbeit a pretty advanced oneβββaround lilypond-midi-input, inspired by how Frescobaldi and Denemo handle MIDI input. The main goal is to allow using a MIDI keyboard to insert notes into your lilypond scores.
The main reason a MIDI handler was not implemented in Lua and thus directly into NeoVim, is because of a seeming lack of comprehensive libraries. There are a few MIDI related libraries, but by their descriptions they only appear to work on MIDI files, not real-time MIDI input from a device. (Also it was a good excuse for me to use Rust again, and the resulting backend could in theory be used outside NeoVim)
-
All features from lilypond-midi-input, reflected during inserting/replacing of notes
-
Respects vim modes (see
:help vim-modes
)-
Notes are only inserted when in Insert mode
-
Notes are replaced when in Replace mode
-
Nothing will happen in any other mode
Demo Video π¬
Screencast shows
-
Notes in normal mode are ignored
-
Notes are inserted in insert mode
-
More notes in normal mode being ignored
-
Replace mode where existing notes are exchanged with new ones
-
Last batch of notes being ignored in normal mode
mode_respecting_feature.mp4
-
-
Always puts cursor right after inserted/replaced note for quick editing (i.e. add articulations, etc.)
Demo Video π¬
-
Shows inserting notes and how cursor is always placed right after insertion
-
Shows taking advantage of cursorβs placement to add articulations and fingerings to notes
cursor_after_note_feature.mp4
-
-
Puts appropriate spacing around inserted notes
Demo Video π¬
Shows inserting note when cursor is located
-
right after a character
-
right before a character
-
inside a word
-
already surrounded by spaces
spacing_feature.mp4
-
-
Options to change behaviour
-
See all options from lilypond-midi-input
-
(Global) alterations can be given as lua tables in the config
-
replace_q
on whether to replace aq
. If disabled, behaves like Frescobaldi's replace.Demo Video π¬
Shows the following:
-
Inserting notes and repeated chords (inserted as
q
) -
Replacing notes with setting turned off:
q
s are being skipped -
Replacing with setting turned on:
q
s can be replaced
replace_q_feature.mp4
-
-
debug
for debugging issues or undesired behaviour; will disable note input
-
-
List of MIDI devices to select from when none is specified, or when the specified one is not available
Demo Video π¬
list_device_feature.mp4
-
Comprehensive update menu for discoverable options (
:MidiInputUpdateOptions
) -
Autocommands to make for a more seamless experience
-
Stop MIDI input upon closing vim (if forgotten to stop manually with
:MidiInputStop
) -
Find the previous chord upon entering insert/replace mode (and sets
previous-chord
)Demo Video π¬
Shows the following
-
Entering chords (on multiple lines)
-
A repeated chord inserts
q
(a feature from the backend) -
The same chord wonβt be inserted as
q
if it is not also the previous chord -
Repeating the previous chord at cursor position being inserted as
q
-
Repeating the previous chord between chords in the same line inserts
q
-
Searching previous chord is not restricted to the current line
previous_chord_feature.mp4
-
-
Find the previous key signature upon entering insert/replace mode (and sets
key
)Demo Video π¬
Shows notes being inserted
-
after a
\key b \major
(note the black keys as sharps) -
after a
\key ces \major
(note the black keys as flats) -
after going back to the
\key b \major
(sharps again) -
after going back to the
\key ces \major
(flats again)
previous_key_feature.mp4
-
-
Finds arbitrary options in the lilypond source file for lilypond-midi-input which are passed as-is to the backend
Demo Video π¬
Shows inserting notes:
-
after accidentals were set to flats
-
after accidentals were set to sharps
-
after going back to where they were set as flats
lmi_options_feature.mp4
-
-
The lilypond-midi-input must be available in the PATH
. Please see its installation instructions.
Once the program and its dependencies are set in place, you can install this plugin with the following using lazy.nvim, for example. Note that no options/configurations are required. It is further of interest to lazy load the plugin, either by filetype and/or by command.
{
'niveK77pur/midi-input.nvim',
ft = { 'lilypond' },
cmd = { 'MidiInputStart' },
}
You can run :checkhealth nvim-midi-input
to see if everything is set up accordingly (assuming the plugin is loaded).
Note
|
Unfortunately, it cannot check if the PortMidi library is available, so check this if the backend is not working |
When the plugin is loaded, you can start the MIDI input using the following command. Note that when device
was not set as an option, or it is not available, you will be prompted with a list of available devices. You can also append the device name to the command.
:MidiInputStart
:MidiInputStart my-midi-device
If successful, you can go into Insert mode and enter notes using your MIDI keyboard. In Replace you can replace existing notes, it will not add or insert notes.
When finished, you can stop the MIDI input using the following command; it will terminate the lilypond-midi-input
process. In case you forget, an autocommand will also handle this for you upon exiting NeoVim.
:MidiInputStop
You can manually change options with the following command. A sequence of menus will be shown to guide you towards the option you are willing to change and its values. See also the options section below.
:MidiInputUpdateOptions
There are three ways to set options for the plugin and the lilypond-midi-input
backend. For a list of all available options, see the List of options further down.
A setup
function is provided to initialize the plugin with user defined values. The setup function does only that, set initial values, nothing else.
Note
|
These options will only be set once during initialization; the other methods will overwrite these values. |
require('nvim-midi-input').setup({
device = 'My device name',
})
In the case of lazy.nvim you can therefore set the options either using the config
or opts
field; both will yield identical results.
{
'niveK77pur/midi-input.nvim',
ft = { 'lilypond' },
cmd = { 'MidiInputStart' },
config = function()
require('nvim-midi-input').setup({
device = 'My device name',
})
end,
}
Or alternatively in a shorter fashion:
{
'niveK77pur/midi-input.nvim',
ft = { 'lilypond' },
cmd = { 'MidiInputStart' },
opts = {
device = 'My device name',
},
}
The :MidiInputUpdateOptions
command should be quite self-explanatory. It uses vim.ui.select()
to provide the menu, hence any other plugin providing UIs for this function can be used to make it look and function nicer, such as fzf-lua.
A note should be made on the (global) alterations, which will request for user input. Here, you insert the alterations, just like for the modeline-like alternative (the part after the alt=
and galt=
); i.e. as if you would input them directly into lilypond-midi-input
's stdin stream. See also lilypond-midi-input
's options for available keys and values, there you will also find shorthand notations for quicker input.
Anywhere in the lilypond file, you can add the following comment to set options that will be set in lilypond-midi-input
.
% lmi: accidentals=Flats
<some music> % lmi: a=f
Important
|
You MUST have a % comment character, followed by one or more spaces, followed by exactly lmi: , followed by one or more spaces, and the desired options. The options will be provided as-is to lilypond-midi-input 's stdin stream. This also means that anything following % lmi: will be passed to the backend, regardless of its content; no sanitizing or filtering is performed.
|
An autocommand will search backwards from the current cursor position for such comments, upon entering insert mode. If options are found, they will be sent and thus set in lilypond-midi-input
.
If no options are found searching backwards, then the currently or last set options (either form the plugin config, or the update menu) will be restored.
Warning
|
If an option has not been specified, its default value will be nil (due to how Lua works); you will see an error by the backend saying that nil is an invalid value. This error can be ignored, but it also means that the corresponding option cannot be reset. If you always want a default fallback value, it is encouraged to specify all relevant options in the plugin config.
|
A special first value of disable
allows disabling this modeline-like functionality and explicitly using the previous config values (same as those if no options were found). Anything after this point will behave as if no % lmi:
options were ever given.
% lmi: disable
% lmi: disable these options here will be ignored
Note
|
The disable value MUST be the first value among the provided options; any following options will of course be ignored then.
|
Many options actually correspond to the backend lilypond-midi-input, so to avoid duplicate documentation you will often find references to the options table there.
Note
|
The options here are presented as if you were to put them into the plugin config. |
The name of the device to be used. If set and available, :MidiInputStart
will directly launch the backend without asking to select a device. Also see here.
device = 'USB-MIDI MIDI 1'
Set the input mode for the backend. See lilypond-midi-input
's options table.
mode = 'pedal-chord'
Whether a q
should be replaced in Replace mode. A value of false
will make it behave like Frescobaldi's replacement mode. Default is true
.
replace_q = true
Currently, the plugin has a very rudimentary and not fully functional way to detect comments. This option allows notes to be replaced within a comment. Default is false
.
replace_in_comment = false
How to handle out-of-key accidental notes by the backend. See lilypond-midi-input
's options table.
accidentals = 'flats'
Specify a key signature for the backend. See lilypond-midi-input
's options table. Default is cM
.
key = 'besM'
Specify (global) alterations within an octave for the backend. See lilypond-midi-input
's options table on alterations and global alterations.
Note
|
You can also pass in a Lua table instead of a string when defined in the setup function. The key must be given as a string, however, due to Lua shenanigans.
|
alterations = {
['0'] = 'YO',
['4'] = 'BYE',
}
global_alterations = '80:SIKE'
Debugging this plugin can be done by setting either of the following (they are mutually exclusive, so only one of them can be set). MIDI note input will be disabled, and the corresponding action will be debugged. This includes printing relevant information, as well as setting extmarks to see which regions were matched/found when searching backwards by the corresponding autocommand.
debug = 'input options'
debug = 'key signature'
debug = 'previous chord'
debug = 'replace mode'
-
NeoVim plugin written in Python with
rtmidi
dependency: https://github.com/ripxorip/midi.nvim -
A proper CLI midi player: https://gitlab.com/dajoha/midiplay
-
β Plugin options are not taken into account
-
β MIDI start does not check if already running (creates an orphaned process)
-
β Pedal modes do not seem to work?
-
β Starting replacement inside ~chord~ last note causes error
-
β Replacement inside last chord before closing bracket
}
does not work (no error though) -
β Find last chord and tell it to the backend (allows improved addition of
q
) -
β Add debug option to highlight start and end of found regions (replace, find last note/chord, etc)
-
β Repeated notes could insert duration as shorthand (similar to
q
) -
β Option to have
q
s be replaced as well -
β Remove/Replace prints from development
-
β Find previously set key
-
β Place config options into the lilypond file at specific points (similar to bar line counting)
-
β Add/Create health checks (backend is installed? Portmidi installed? Necessary options are provided?)
:h health-dev
-
β Update option for changing
q
replacement -
β Option to toggle automatic key setting (previously found key)
-
β Option to toggle automatic config options setting?
-
β Option to automatically reset options when reading a new
% lmi:
(avoids an explicit% lmi: disable
) -
β Refactor debugging
-
β Do not replace within comments
-
β Completely ignore comments, i.e. pretend commented regions do not exist (for searching)
-
β Create help page? (avialable options? other useful information for on-the-fly look up)
-
β Add
build.lua
to install backend? (for lazy.nvim) -
β
MidiInputUpdateOptions
should also change internal values -
β `% lmi: ` should revert to default options if not found (but do not set if found)
-
β `% lmi: ` should have a special key to revert to using default options
-
β Appears to sometimes randomly exit job