Skip to content

niveK77pur/midi-input.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Async and Modal MIDI Input in NeoVim

About

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)

Features

  • 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 a q. 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: qs are being skipped

      • Replacing with setting turned on: qs 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)

    Demo Video 🎬

    Shows the following actions:

    • Setting the key signature to B major

    • Changing the input mode to Chord for inserting chords

    • Changing the key signature to D major

    • Changing accidentals to insert flats for out of key black notes

    options_menu_feature.mp4
  • 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

Installation

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

Usage

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

Options

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.

At plugin initialization

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',
    },
}

Using the update menus

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.

Vim modeline-like settings in the file

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.

List of options

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.

MIDI input device

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'

MIDI input mode

Set the input mode for the backend. See lilypond-midi-input's options table.

mode = 'pedal-chord'

Whether to replace_q

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

Should notes replace_in_comment

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

Sharp or flat accidentals

How to handle out-of-key accidental notes by the backend. See lilypond-midi-input's options table.

accidentals = 'flats'

Which key are we in

Specify a key signature for the backend. See lilypond-midi-input's options table. Default is cM.

key = 'besM'

Custom (global) alterations

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'

We need to debug

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'

See also

TODO

  • βœ“ 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 qs 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