Installing and Using Swara
A Tree-sitter grammar, a Rust language server and a Neovim plugin for LilyPond, the plain-text music engraver, built from the ground up for speed.
01.What Swara Needs
Swara's colours, instant checks and tidying need nothing but Neovim and the swara program, which the plugin fetches for itself. Everything that engraves, previews or plays a score calls an outside program, and each one is optional in the sense that a missing tool is reported once and the rest keeps working.
Table 1. What each feature needs
| Program | Needed for | Notes |
|---|---|---|
| Neovim 0.12 or later | Everything | The tests run on Neovim 0.12.6 |
| LilyPond | The LilyPond check, builds, preview and playback | Tested with LilyPond 2.26.0. Other versions should work, because the server reads LilyPond's commands from whichever LilyPond is installed |
| FluidSynth and a soundfont | Playback | MuseScore 4's MS Basic.sf3 is used when MuseScore 4 is installed in /Applications, and the soundfont setting names any other |
| MuseScore 4 | :Swara musescore | Opens the score's MIDI for slower practice |
| Skim | A PDF that reloads after each build, on macOS | Without it the PDF opens in the default viewer |
| snacks.nvim | Drawing the preview inside the terminal | Optional. Without it the preview opens in the system's image viewer |
Two of these terms deserve a definition. FluidSynth is a software synthesiser: it reads a MIDI file, which says only which notes play and when, and turns it into sound. To do that it needs a soundfont, a file of recorded instrument samples that decides what each note sounds like. A soundfont file ends in .sf2, or .sf3 for the compressed form.
On macOS, Homebrew, the common package manager for the Mac, installs every program in the table except snacks.nvim, which is a Neovim plugin.
| brew install neovim lilypond fluid-synth |
| brew install --cask musescore skim |
Playback keeps a single FluidSynth running from the first play until Neovim quits, and sends it commands through a pipe, a private channel between two programs on the same machine. So playback opens no network port.
02.Installing the Plugin
With the lazy.nvim plugin manager, one line installs Swara.
| { 'anirbanchakraborty-dev/swara', ft = 'lilypond', opts = {} } |
The first string is the plugin's GitHub repository. ft = 'lilypond' tells lazy.nvim to load the plugin only when a LilyPond file opens, so it costs nothing at startup, and opts = {} runs the plugin's setup with every setting at its default.
The plugin needs the swara program, which holds the language server and the grammar. It uses a swara found on PATH, the list of folders the shell searches for programs, when there is one. Otherwise, the first time a LilyPond file opens, it downloads the release archive for its own version from the repository's GitHub releases into Neovim's data folder. It unpacks the archive only once its SHA-256 checksum matches the one listed in the release's SHA256SUMS file. A checksum is a short fingerprint computed from every byte of a file, so any change to the archive, whether a corrupted download or a substituted file, gives a different fingerprint and the archive is refused.
Release archives are built for five platforms: macOS on Apple silicon and on Intel, Linux on x86-64 and on ARM64, and Windows on x86-64. The tests run on macOS and Linux.
03.Installing the Program with Homebrew
On macOS and Linux, Homebrew can install the swara program instead of letting the plugin download it. Homebrew installs from taps, which are GitHub repositories of install recipes, and Swara's recipe lives in its own tap.
| brew install anirbanchakraborty-dev/tap/swara |
The plugin does not compare its own version with a swara it finds on PATH, so run brew upgrade swara after each plugin update to keep the two on the same release.
04.Letting nvim-treesitter Manage the Grammar
nvim-treesitter is not needed, because the plugin loads its grammar from the swara program. To have nvim-treesitter manage the grammar instead, for example so that other plugins can use it, register it in a User TSUpdate autocommand, as nvim-treesitter's main branch documents.
| vim.api.nvim_create_autocmd('User', { pattern = 'TSUpdate', | |
| callback = function() | |
| require('nvim-treesitter.parsers').lilypond = { | |
| install_info = { | |
| url = 'https://github.com/anirbanchakraborty-dev/tree-sitter-lilypond', | |
| revision = 'v0.1.0', | |
| queries = 'queries', | |
| }, | |
| } | |
| end, | |
| }) |
Then run :TSInstall lilypond once. Keep the revision at the grammar tag that matches the plugin's release, since Swara's queries are written for that grammar. A different LilyPond grammar installed under the name lilypond stops Swara's colours, and :checkhealth swara names the conflict.
05.Commands
Every command is a word after :Swara. A build works from the text in the editor, so a score can be engraved before it is saved.
Table 2. The Swara commands
| Command | What it does |
|---|---|
:Swara new guitar <name> | Writes a guitar lesson <name>.ly in the current folder, with the sargam include beside it, and opens it |
:Swara new piano <name> | Writes a piano lesson the same way |
:Swara new song <name> | Writes a song lesson with its lyrics file and the lyrics include |
:Swara build | Builds the PDF and MIDI from the text in the editor, saved or not |
:Swara open | Opens the last build's PDF |
:Swara preview | Shows the score as an image inside the editor |
:Swara play | Plays the last build's MIDI through FluidSynth |
:Swara stop | Stops playing |
:Swara musescore | Opens the last build's MIDI in MuseScore |
06.Keys
Neovim reserves a key called the local leader for shortcuts that belong to one kind of file. It is a backslash unless the maplocalleader setting changes it, and a shortcut is the local leader followed by one more key. Swara sets six of them, in LilyPond files only, and the keys setting turns them all off.
Table 3. The Swara keys, with the default local leader
| Keys | What they do |
|---|---|
\o | Opens the PDF |
\v | Previews the score in the editor |
\p | Plays the score |
\s | Stops playing |
\m | Opens the score in MuseScore |
\b | Builds the PDF and MIDI |
07.Settings
Every setting goes in opts for lazy.nvim, or in require('swara').setup({ ... }) otherwise. The defaults suit most uses.
Table 4. The Swara settings and their defaults
| Setting | Default | What it does |
|---|---|---|
auto_check | true | Runs LilyPond's own check each time typing pauses |
format_on_save | true | Tidies the file each time it is saved |
keys | true | Sets the keys above in LilyPond files |
soundfont | MuseScore 4's MS Basic.sf3 | The .sf2 or .sf3 soundfont that playback uses |
debounce_ms | 10 | How long Neovim waits after a change before sending it to the server, in milliseconds |
preview_dpi | 120 | The resolution of the preview image |
lilypond | the lilypond on PATH | The LilyPond program to use |
include_paths | {} | More folders to search for \include files, after the file's own folder |
release_base_url | the repository's GitHub releases | Where the plugin downloads swara from |
08.Checking the Setup
:checkhealth swara reports the swara program and where it came from, the grammar, LilyPond, the command cache, FluidSynth and the soundfont, MuseScore and, on macOS, whether Skim reloads a changed PDF by itself. Each thing that is missing or switched off comes with its fix. The check only reads and changes nothing. Where Skim's own reload is off, it shows the two defaults write commands that turn it on, and Swara has Skim reload the PDF after each build in the meantime.
09.Building from Source
Building Swara needs Rust 1.98 and the grammar checked out beside it, as a folder named tree-sitter-lilypond next to the swara folder. The first command builds the program, the second runs the Rust tests and the third runs the plugin's tests in a clean, headless Neovim.
| cargo build --release -p swara -p swara-parser |
| cargo test --workspace |
| nvim --clean --headless -l tests/run.lua |
— Anirban
Hardware–software at the seam where compilers meet microcode.