Render professional sheet music directly inside Typst documents using SMuFL-aware glyph placement and CeTZ drawing primitives.
Features
- Pure Typst - no WASM plugin, no external binary dependency (no LilyPond, no MuseScore CLI)
- SMuFL/Bravura-aware glyph placement with precise bounding-box anchors
- Notes, rests, chords, accidentals, key signatures, time signatures, clefs
- Dynamics, articulations, fingerings, and chord symbols - all inline in the music string
- Beams, ties, slurs, repeat barlines, dotted notes
- Grand staff and multi-staff layout with vertical beat alignment
- System/line breaks via measures-per-line, literal
\n, or automatic width-based breaking - Header block with title, subtitle, composer, arranger, lyricist
- Produces crisp, resolution-independent vector PDF output
Quick Start
Via Typst Package Manager (recommended)
Add the import to your document and start writing music:
#import "@preview/scorify:0.1.1": score, melody
#melody(
title: "Scale",
key: "C",
time: "4/4",
music: "c4 d e f | g a b c'",
)
Then compile (point --font-path at the directory containing Bravura.otf - see Font Setup):
typst compile your-file.typ --font-path /path/to/bravura/
Manual Installation
For local development, copy lib.typ, src/, and data/ into your project and import with a relative path:
#import "lib.typ": score, melody
Compile with:
typst compile your-file.typ --font-path /path/to/bravura/ --root .
Font Setup
scorify uses the Bravura SMuFL font for music glyph rendering. Typst packages cannot embed fonts, so you must install Bravura separately before compiling any document that uses this package.
- Download the latest Bravura release from the Bravura GitHub releases page. The
.ziparchive containsBravura.otf. - Either:
- System install (easiest): Copy
Bravura.otfinto your system fonts folder. Typst will find it automatically and no extra flag is needed. - Project-local: Keep
Bravura.otfin a folder of your choice and pass--font-path /path/to/that/folder/when compiling.
- System install (easiest): Copy
API Reference
score()
The primary entry point. Renders one or more staves with full layout control.
#score(
staves: (
(clef: "treble", music: "c4 d e f | g a b c'"),
(clef: "bass", music: "c2 g | c1"),
),
key: "C",
time: "4/4",
title: "My Piece",
composer: "Composer Name",
staff-group: "grand",
)
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
staves |
array | () |
Array of staff dicts (see below) |
key |
string | "C" |
Key signature ("C", "G", "D", "Bb", "f#", etc.) |
time |
string | "4/4" |
Time signature ("4/4", "3/4", "6/8", "C", "C|") |
title |
string | none |
Piece title |
subtitle |
string | none |
Subtitle |
composer |
string | none |
Composer name |
arranger |
string | none |
Arranger name |
lyricist |
string | none |
Lyricist name |
staff-group |
string | "none" |
"none", "grand" (brace + spanning barlines) |
staff-size |
length | 1.75mm |
Staff space distance |
system-spacing |
length | 12mm |
Vertical space between systems |
staff-spacing |
length | 8mm |
Vertical space between staves within a system |
width |
length/auto | auto |
Explicit width or auto (fills page) |
measures-per-line |
int | none |
Force this many measures per system |
Staff dict fields:
| Field | Type | Default | Description |
|---|---|---|---|
clef |
string | "treble" |
"treble", "bass", "alto", "tenor", "treble-8", "percussion" |
music |
string | "" |
Music string (see syntax above) |
fingering-position |
string | "above" |
Default fingering position: "above" or "below" |
melody()
Convenience wrapper for a single-staff score.
#melody(
music: "c4 d e f | g a b c'",
key: "C",
time: "4/4",
clef: "treble",
title: "My Melody",
composer: "Composer",
)
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
music |
string | "" |
Music string |
key |
string | "C" |
Key signature |
time |
string | "4/4" |
Time signature |
clef |
string | "treble" |
Clef |
title |
string | none |
Title |
composer |
string | none |
Composer |
staff-size |
length | 1.75mm |
Staff space |
width |
length/auto | auto |
Width |
measures-per-line |
int | none |
Measures per system |
lead-sheet()
Melody with lyrics (lyrics not yet implemented).
#lead-sheet(
music: "c4 d e f | g a b c'",
key: "C",
time: "4/4",
title: "Song Title",
)
Full Example
Here is the Ode to Joy example demonstrating a grand staff with fingerings, chord symbols, dynamics, and articulations:
#import "@preview/scorify:0.1.1": score
#set page(margin: 1.5cm)
#score(
title: "Ode to Joy",
composer: "L. van Beethoven",
key: "D",
time: "4/4",
staff-group: "grand",
staves: (
(
clef: "treble",
music: "
f#4n[3][D] f# g a | a8[D/A] b g4 f#[A] e |
d[D] d e f# | f#4.[A] e8 e2 |
f#4[D] f# g a | a[A] g f# e |
d[D] d e f# | e4.[A] d8 d2[D] |
",
),
(
clef: "bass",
fingering-position: "below",
music: "
d1n[1] | a, | d | a, |
d | a, | d | a,2 d4 r |
",
),
),
)
Music String Syntax
This section documents the inline music-string syntax accepted by score(), melody(), and related helpers. The parser is intentionally compact and expressive - here are the primary constructs and examples.
-
Notes:
name+ optional accidentals + octave markers + duration + optional dots.- Examples:
c4,d8.,f#4,g'2,a,16(comma lowers octave, apostrophe raises it). - Accidentals:
#(sharp),##(double-sharp),&(flat),&&(double-flat),=(natural). - Duration is sticky: if omitted the note uses the last explicit duration (e.g.,
c4 d e fuses quarter notes for all).
- Examples:
-
Rests and spacers:
- Rest:
r4,r8. - Spacer (invisible rest):
s4
- Rest:
-
Chords (simultaneous notes):
- Syntax:
<c e g>4produces a C major chord as a quarter note. - Chord-level articulations, dynamics, ties, slurs, and fingering can be applied to the chord as a whole (see examples below).
- Syntax:
-
Articulations: appended as single-character markers after the duration
>= accent,*= staccato,-= tenuto,_= fermata- Example:
c4>*(accent + staccato)
-
Ties and slurs:
- Tie:
~connects the note to the following note of the same pitch (e.g.,c4~ c4). - Slurs:
(and)mark slur start/end. They may appear immediately after a note, or on their own to attach to the previous note.- Example:
c4( d e) forc4 d( e f ) g
- Example:
- Tie:
-
Dynamics:
- Inline dynamic markers use
v[...]where[...]is a dynamic text likemf,f,pp. - Example:
c4v[pp] d4 v[ff]- dynamics are rendered below the staff.
- Inline dynamic markers use
-
Fingering:
- Fingering numbers are attached with
n[...](above) orn_[...](below). - Multiple fingerings are supported by separating numbers with a space inside the brackets:
n[1 3]. - Example:
c4n[3]ord4n_[2](below the staff).
- Fingering numbers are attached with
-
Inline chord symbols:
- Append a bracketed symbol after a note or chord:
[C],[Am7],[D/F#]. - Example:
c4[C]or<c e g>2[Dm].
- Append a bracketed symbol after a note or chord:
-
Beams and grouping:
- Square brackets
[and]can be used to force beam starts/ends when not interpreted as a chord symbol.
- Square brackets
-
Grand staff / multi-staff layout:
- Use the
stavesarray passed to#scoreand setstaff-group: "grand"to request grand-staff rendering (brace and shared barlines). - Each staff can set
clef,music, andfingering-position("above"or"below"). See the full example above for a grand-staff sample.
- Use the
Examples (combined):
#melody(music: "<c e g>4n[1][C] d4>* f#4v[mp] g4~ g4")
// chord with fingering and chord symbol, accent + staccato on next note,
// dynamic marking, tie on the last two notes
Refer to the parser logic in src/parser.typ for the complete, definitive syntax and edge cases.
Notes
- Scorify uses the Bravura SMuFL font for accurate music glyph placement. See Font Setup above for installation instructions.
- Spacing parameters (note spacing base, duration factors, accidental padding, dot size) are tunable in
src/constants.typ. - The library requires Typst 0.14.0+ and CeTZ 0.4.2 (CeTZ is declared as a package dependency and is resolved automatically when using the Typst package manager).
Contributing
Bug reports, feature requests, and pull requests are welcome in the official repository.
License
MIT - see LICENSE.