Lily#

Publication-quality sheet music from plain text — engraved by a LilyPond port, edited in an IDE that keeps up with your keystrokes. Read the language manual →

The source and the engraving, side by side. The preview follows your keystrokes — it does not wait for a save.

Install

Install in two steps

Nothing else to install. Each platform's package brings its own .NET runtime and the Emmentaler and TeX Gyre fonts, so there is no toolchain to assemble.

  1. Install Visual Studio Code Version 1.90 or newer.
  2. Install the Lily# extension In VS Code, press Ctrl+Shift+X (Cmd+Shift+X on macOS), search for “Lily#” and click Install on the one by yotsuda. (Or install it from its Marketplace page.)
  3. Open any .lys file The score opens beside the source, with diagnostics and completion as you type. For batch work there is also a lysc command-line build.

Why Lily#

Six things it is built to do

Most are shown further down, with the evidence.

01

LilyPond's engraving

Beam quanting, slur and tie scoring, skylines, springs and page breaking are transliterated from LilyPond's own source — not approximated from its output.

02

A grammar you can read

One canonical form per idea, no backslash constructs, and octaves that can be absolute so a mistake never cascades.

03

Preview that keeps up

A keystroke reparses incrementally and reuses the systems it did not disturb, so the score redraws in milliseconds.

04

AI that compiles first

Ask in words, and every candidate is compiled and repaired before you see it. A candidate that adds errors is never shown. It uses your GitHub Copilot models or your own API key.

05

An editor that knows the grammar

Completion offers what can actually come next — after lyrics NAME it proposes sings, then the part names that exist in your file. Rename a part and every reference to it moves with it. Hover a chord and it names itself — symbol, degree and pitches. More than a dozen language-server features in all: diagnostics, hover, go to definition, find references, rename, document symbols, folding, formatting, code actions, CodeLens, signature help, semantic highlighting.

06

One source, every score

A file can carry more than one score and more than one form. The full score, the separate parts, a staff-less chord grid and a practice excerpt all come out of the same notes — so they cannot drift apart. Write a progression once and print it both above the melody and as its own chart.

01 — Engraving

What comes out

Six pages, one engine. Each is a plain .lys file rendered with lysc svg; the source is under every one.

Lineage

What Lily# owes LilyPond

LilyPond has set the standard for computer-engraved music for decades. Lily# does not try to out-engrave it. It carries LilyPond's engraving decisions across — with attribution, and under the same licence.

The engine is a port, not an imitation

Beam quanting, slur and tie scoring, skylines, springs and page breaking are modified translations of LilyPond's own C++ and Scheme. Every ported file carries the copyright notice of the LilyPond file it came from, and LILYPOND-ATTRIBUTION.md lists all of them.

The house rule is that layout code is transliterated from LilyPond's source rather than reverse-engineered from its pictures — and that nothing may be tuned merely to make the output match byte for byte. Elsewhere, most LILYPOND-REF comments are citations rather than ports: they mark where LilyPond decides something, so this code can be checked against it.

It is an independent project

Lily# is not affiliated with, endorsed by, or a release of the LilyPond project. The name is a nod, not a badge. If something here engraves badly, that is Lily#'s bug — please don't take it to the LilyPond maintainers.

The language is deliberately not LilyPond's: \relative, \new Staff, \version and << … \\ … >> are rejected outright, and a chord symbol is typed the way it prints. What the two share is engraving knowledge, not syntax.

Licence

Lily# is free software under the GNU General Public License v3.0 or later. It contains modified code from LilyPond, which is under that same licence; the modifications are Lily#'s own and are marked in the files that carry them.

The fonts ship with it too. Emmentaler, the music face, comes from LilyPond (GPL-3.0-or-later / SIL OFL dual licensed, redistributed here under the GPL). TeX Gyre Schola and TeX Gyre Heros set every piece of text — and provide the metrics it is spaced by — under the GUST Font Licence (LPPL 1.3c). They are the same faces LilyPond sets text in.

02 — Grammar

Explicit, and only one way to say it

  • No backslashes. \relative, \new Staff and << … \\ … >> are rejected outright, with a diagnostic that names the Lily# spelling instead of failing quietly.
  • Music lives in a part. A stray note at the top level is an error, which is what lets a bare key or time always mean the file default.
  • Order carries meaning, not clauses. A score is a stack of bands: a lyrics row under a staff is its verse; a chords row above it aligns over it.
  • Octaves can be absolute. octave absolute anchors a bare c to C4 (or to the part's octave N), so a wrong octave stays one wrong note instead of cascading.
  • Repeats go where order is written. |: and :| live in the form, because a repeat changes the playing order.
  • A chord symbol is its own spelling. You write F#m7-5 and Bb7/D — the glyphs you would read off a chart — rather than encoding them as fis:m7.5- and bes:7/d.
// one canonical form per idea
time 6/8
key a minor
octave absolute            // a bare c is C4 here

part gt { clef treble_8 tuning guitar }

section Verse {
  gt     { a,8 e a c' a e | }
  chords prog { Am | }
}

form main { |: Verse :| }  // the repeat lives here

score main {
  chords prog              // a row above ...
  staff  gt                // ... the staff it belongs to
  tab    gt                // and its tablature
}

Every construct, with the corners spelled out, is in the language manual.

03 — Speed

The preview redraws while you type

The compiler keeps a Roslyn-style red-green tree, so a keystroke reparses only the part you touched; the renderer then reuses every system whose width did not change. The parse is the cheap half — the time is in layout, and layout is what gets skipped.

04 — AI

It compiles the answer before showing it to you

Select a few bars, press Ctrl+I (Cmd+I on macOS), and ask in words — "harmonise this in thirds", "transpose up a fourth", "add a crescendo". What comes back is not a text diff to squint at: it is a rendered score, and it has already been compiled.

flowchart LR
    sel["bars you selected"] --> ask["your prompt"]
    ask --> model["language model"]
    model --> chk{"compiles?"}
    chk -- "no" --> repair["feed the diagnostics back<br/>up to 2 rounds"]
    repair --> model
    chk -- "yes" --> draw["render the candidate score"]
    draw --> you{"accept?"}
    you -- "iterate" --> model
    you -- "yes" --> apply["one WorkspaceEdit<br/>one Ctrl+Z undoes it"]
  • Broken candidates never reach you. Each one is compiled in-process; its own diagnostics go back to the model for up to two repair rounds.
  • You judge on the notation. The candidate renders beside the original with an After/Before toggle.
  • Nothing is touched until you accept. One WorkspaceEdit, and one Ctrl+Z undoes it.
  • The model always has the grammar. docs/GRAMMAR_FOR_LLM.md is copied into the extension at build time, so the canon ships with it and cannot drift.
  • Pick bars on the score. Shift-click a range in the preview and transform it — the same loop, whether the selection began in text or on the page.
  • Your model, no telemetry. It runs on your GitHub Copilot models or your own API key, and outcomes go to an output channel on your machine.

Architecture

How it is put together

The same diagram as in README.md.

flowchart TD
    src["score.lys"] --> Core

    subgraph Core["LilySharp.Core"]
        direction TB
        A["<b>Parser · Syntax</b><br/>lexer, recursive-descent parser,<br/>red-green tree"]
        B["<b>Semantics · Music · Harmony</b><br/>durations, measure validation,<br/>keys, chords"]
        C["<b>Svg · Rendering</b><br/>beam quanting, slur and tie scoring,<br/>skylines, springs, page breaking<br/><i>— ported from LilyPond</i>"]
        D["<b>Back ends</b><br/>Pdf · Png · Midi · MusicXml ·<br/>LilyPond · Vocaloid · Tablature"]
        A --> B --> C --> D
    end

    Core --> Cli["<b>LilySharp.Cli</b><br/>the lysc command"]
    Core --> Lsp["<b>LilySharp.Lsp</b><br/>language server"]
    Lsp --> Ext["<b>editors/vscode</b><br/>extension — bundles the server"]

    Tests["<b>LilySharp.Tests</b><br/>unit + SVG snapshots"] -.->|guards| Core
    Audit["<b>audit/</b><br/>LilyPond-fidelity ledger<br/>+ regression corpus"] -.->|measures| Core
    Bench["<b>LilySharp.Benchmarks</b><br/>layout and parse timing"] -.->|times| Core