Clef
Clef is a lightweight Ruby toolkit for building small music scores, parsing a practical LilyPond-style syntax subset, and exporting PDF, SVG, or MIDI.
It is intended for concise notation workflows, examples, and application embedding. It is not a full replacement for LilyPond, MusicXML, or a publishing-grade engraving engine.
Requirements
- Ruby 3.1 or newer
- Bundler for development
- Optional:
fonts/bravura/Bravura.otffor higher quality PDF/SVG music glyphs
Installation
Add Clef to your application's Gemfile:
gem "clef"
For local development:
git clone https://github.com/ydah/clef.git
cd clef
bin/setup
Quick Start
require "clef"
score = Clef.score do
title "Twinkle Twinkle Little Star"
composer "Traditional"
staff :piano, clef: :treble do
key :c, :major
time 4, 4
play "c'4 c'4 g'4 g'4 | a'4 a'4 g'2 | f'4 f'4 e'4 e'4 | d'4 d'4 c'2"
end
end
score.to_pdf("twinkle.pdf")
score.to_svg("twinkle.svg")
score.to_midi("twinkle.mid")
score.to_format("twinkle.svg") chooses PDF, SVG, or MIDI from the file extension.
DSL Essentials
Use Clef.score with one or more staff blocks. Inside a staff you can set notation state and add music with play:
score = Clef.score do
title "Example"
tempo beat_unit: :quarter, bpm: 96
staff :violin, clef: :treble do
key :d, :major
time 3, 4
instrument 40
play "fis'4 g'4 a'4"
lyrics :default, "one two three"
end
end
Common play tokens:
- Notes:
c'4,fis'8,bes2 - Rests:
r4,r8 - Chords:
<c' e' g'>2 - Durations:
1,2,4,8,16, with dotted values such as4. - Measure separators:
| - Ties, slurs, articulations, and dynamics are supported in the lightweight DSL subset
For multi-part scores, use multiple staff blocks or staff_group:
score = Clef.score do
title "Piano Sketch"
staff_group :brace do
staff :right, clef: :treble do
key :c, :major
time 4, 4
play "c''4 e''4 g''4 c'''4"
end
staff :left, clef: :bass do
key :c, :major
time 4, 4
play "c2 g,2"
end
end
end
LilyPond-Style Input
Clef can parse a practical subset of LilyPond-style input:
parser = Clef::Parser::LilypondParser.new
score = parser.parse(<<~'LILYPOND')
\tempo 4 = 96
\new Staff {
\clef treble
\key c \major
\time 4/4
{ c'4 d'4 e'4 f'4 | }
}
LILYPOND
warn parser.warnings.join("\n") unless parser.warnings.empty?
The importer supports tempo, staves, staff groups, clefs, key and time signatures, notes, rests, chords, a small relative pitch subset, simultaneous voices, dynamics, articulations, ties, slurs, beams, and measure splitting. Unsupported commands are skipped and reported as warnings with source locations.
Plugins
Plugins can inspect or modify scores before rendering, and can observe rendering lifecycle events:
class PreparedFlagPlugin < Clef::Plugins::Base
def on_before_layout(score)
score.(:prepared, true)
end
end
Clef.plugins.register(PreparedFlagPlugin)
Common hooks are on_before_layout, on_after_layout, on_before_render, on_after_render, on_after_parse, on_before_midi, and register_glyphs.
Development
bin/setup
bundle exec rake
bundle exec ruby examples/twinkle.rb
bundle exec ruby exe/clef examples/twinkle.rb twinkle.svg
bundle exec rake runs specs, lint, syntax checks, and a gem build smoke test.
License
Clef is available as open source under the terms of the MIT License.