swift-lilypond.git · README.md
swift-lilypond.git / README.md
revision 0900dc846f23d4f7e7d4a389615fc6aa793114bf · complete file
# swift-lilypond
A drop‑in **Swift Package** that wraps **LilyPond** to render **PDF/SVG/PNG** and **MIDI** — designed for the Fountain‑Coach Swift family.
- **No external installs for consumers**: LilyPond is embedded via SPM `binaryTarget`s. During local development, if no embedded binary is present, the package falls back to your system `lilypond`.
- **Ergonomic Swift API** (async/await), structured diagnostics, sandboxed runs.
- **MIDI supported** via LilyPond’s native `\midi {}` block (inside `\score { ... }`).
- **Aligned with official upstream on GitLab** (canonical): <https://gitlab.com/lilypond/lilypond/-/releases>.
> LilyPond remains the authoritative engraver; this package orchestrates it predictably and safely.
---
## Status & Scope
- **Outputs:** PDF, SVG, PNG, and (optionally) MIDI (when your score includes a `\midi {}` block).
- **Upstream alignment:** We pin to LilyPond tags from GitLab and record provenance (exact tag + checksums).
- **Non‑goals:** Audio rendering. MIDI → audio belongs in sibling packages such as **`swift-csound`**.
---
## Quick Start
Add the dependency to your `Package.swift`:
// Development (before first tag)
```swift
.package(url: "https://github.com/Fountain-Coach/swift-lilypond.git", branch: "main")
```
// After releases start (example)
```swift
.package(url: "https://github.com/Fountain-Coach/swift-lilypond.git", from: "0.1.0")
```
Basic usage:
```swift
import LilyPondKit
let ly = [
"\\version \"2.24.4\"",
"\\score {",
" \\new Staff \\with { midiInstrument = \"glockenspiel\" }",
" { c'4 d' e' f' | g'1 }",
" \\layout {}",
" \\midi { \\tempo 4 = 96 }",
"}"
].joined(separator: "\n")
let kit = LilyPond()
let art = try await kit.render(source: ly)
// art.visualData -> PDF/SVG/PNG (default PDF)
// art.midiData -> MIDI if \midi{} present
```
To render **SVG** or **PNG**, set the option accordingly; SVG uses a dedicated backend and should be rendered in a separate run.
Note: For development builds before official releases, the package uses your system `lilypond` if available. Official releases ship embedded binaries verified via provenance.
To force using the embedded bundle at runtime, set `LILYPOND_PATH` to the wrapper executable inside the artifact bundle for your platform (e.g., `.../LilyPondBinaries.artifactbundle/macos-x86_64/lilypond`).
---
## Design Principles
- **Hermetic runtime**: embedded binaries, fonts/configs, and environment bootstrap for consistent results.
- **Provenance**: every release documents upstream tag, checksums, and build recipe (`PROVENANCE.md`).
---
## CI
- GitHub Actions builds and tests on macOS and Linux with Swift 5.9 and 6.0.
- Integration test uses system `lilypond` if present; unit tests mock the runner to avoid external deps.
---
## Release Engineering
- Binaries are packaged under `Binaries/LilyPondBinaries.artifactbundle/`.
- Use `tools/release/assemble_artifactbundle.sh` to place per‑arch executables and update checksums and version.
- Publish the zipped artifact bundle and checksums in GitHub Releases; record details in `PROVENANCE.md`.
---
## Tests
- Unit tests cover:
- PDF/SVG/PNG runs (mocked) with include paths, DPI, verbosity
- Multi‑page artifact collation and natural ordering
- MIDI presence detection when `\midi {}` is in the score
- Error propagation on non‑zero exit
- Locator env var fallback (`LILYPOND_PATH`)
- Integration test renders a tiny score if system `lilypond` is installed.
### Golden Corpus
- Generate goldens for the latest stable (v2.24.4): `tools/golden/build_golden.sh`.
- The script downloads the official generic package if `lilypond` is not installed and writes expected artifacts to `Tests/Golden/expected/v2.24.4/`.
- Golden tests compare checksums when the corpus is present; otherwise they skip.
---
## "Works On My Machine" (Local Bundle)
During development you can use a locally assembled LilyPond bundle without committing any large files.
- Generate the local bundle (downloads official v2.24.4 tarballs):
tools/release/assemble_artifactbundle.sh --version v2.24.4 \
--macos-x86_64 https://gitlab.com/lilypond/lilypond/-/releases/v2.24.4/downloads/lilypond-2.24.4-darwin-x86_64.tar.gz \
--linux-x86_64 https://gitlab.com/lilypond/lilypond/-/releases/v2.24.4/downloads/lilypond-2.24.4-linux-x86_64.tar.gz
This populates `Binaries/LilyPondBinaries.artifactbundle/**/pkg/`. These paths are git‑ignored.
- Use the embedded wrapper via `LILYPOND_PATH` so `lpkit` and the library resolve LilyPond predictably:
macOS (x86_64 on Apple Silicon via Rosetta):
- Ensure Rosetta (Apple Silicon only):
softwareupdate --install-rosetta --agree-to-license
- Point to the local wrapper:
export LILYPOND_PATH="$PWD/Binaries/LilyPondBinaries.artifactbundle/macos-x86_64/lilypond"
Linux (x86_64):
- export LILYPOND_PATH="$PWD/Binaries/LilyPondBinaries.artifactbundle/linux-x86_64/lilypond"
- Render:
swift run lpkit --format pdf --input score.ly --output out.pdf
Notes
- The local bundle is ignored by git (see `.gitignore`). Other clones won’t have it until they run the script above.
- On tagged releases, consumers don’t need any of this — they get the remote artifact bundle automatically through SPM.
- The SwiftPM build plugin also attempts to auto‑discover the embedded wrapper from the artifact bundle; setting `LILYPOND_PATH` makes behavior explicit and is recommended for dev.
Shortcut
- Set the env var for your current shell automatically:
eval "$(tools/dev/use-local-bundle.sh)"
Troubleshooting
- Exec format error on macOS Apple Silicon: install Rosetta, then re‑run.
- "LilyPond not found": verify `LILYPOND_PATH` is set to the wrapper path or regenerate the local bundle.
- Reclaim space: remove `Binaries/LilyPondBinaries.artifactbundle/**/pkg/` to free disk; they’re regenerated by the assemble script.
- **Safety**: strict artifact verification before publishing.
- **Clarity**: small Swift API, thorough README and troubleshooting.
---
## MIDI Notes
LilyPond only writes a MIDI file **if a `\midi {}` block appears inside a `\score { ... }`**. Tempo can be set by `\tempo` or via properties in the `\midi` block (e.g., `tempoWholesPerMinute`) without printing a metronome mark in the score.
Instrument program selection is controlled via `midiInstrument` on staves; channel mapping follows LilyPond’s standard rules.
---
## Official Upstream
- **Canonical project**: <https://gitlab.com/lilypond/lilypond>
- **Releases hub**: <https://gitlab.com/lilypond/lilypond/-/releases>
- **Development overview**: <https://lilypond.org/development>
We avoid GitHub mirrors for release artifacts and always cite the GitLab tag used.
---
## Roadmap
- Live log streaming (via `AsyncStream`) during engraving
- Richer diagnostics (filename:line:column)
- Golden tests across platforms for PDF/SVG/PNG/MIDI
- SwiftUI demo views for PDF/SVG preview
---
## Licensing
- **LilyPond** is **GPL**. Embedding it as a binary artifact requires:
- Shipping GPL text and notices
- Providing a **source‑offer** to the exact LilyPond sources used
- Fonts/configs carry their own licenses (documented in `LICENSES/`).
---
## Sibling Repos & Interop
- **`swift-csound`** (MIDI → audio rendering) — keep repo boundaries clean; this package exports MIDI only.
- Other Fountain‑Coach Swift repos share conventions: semver, provenance, AGENTS, CI parity.
### Teatro Integration (SPM)
- Add dependency in Teatro (before first tag, use branch; after release, use from:):
.package(url: "https://github.com/Fountain-Coach/swift-lilypond.git", branch: "main")
// or, once releases are published:
// .package(url: "https://github.com/Fountain-Coach/swift-lilypond.git", from: "0.1.0")
- Depend on `LilyPondKit` in a Teatro target and use the library directly (no servers or pid/log plumbing needed). See `Docs/Teatro-Integration.md` for a complete example and migration guidance from `Process()` usage.
---
## Contributing
See **CONTRIBUTING.md** for branching, commit style, CI, and release steps.