Class: HeadMusic::Notation::LilyPond::Writer
- Inherits:
-
Object
- Object
- HeadMusic::Notation::LilyPond::Writer
- Defined in:
- lib/head_music/notation/lily_pond/writer.rb
Overview
Renders a HeadMusic::Content::Composition as a complete LilyPond document string: a \version line, a \header carrying the composition’s identity, and a \score with one staff per voice in absolute pitch mode, one line per bar with a trailing bar check.
Whole-composition problems (no voices, positional gaps, barline-crossing notes, unmappable keys, durations, or alterations) raise before any assembly, so #to_s only ever returns a complete document.
Constant Summary collapse
- LILYPOND_VERSION =
"2.24.0"- INDENT =
" "
Instance Attribute Summary collapse
-
#composition ⇒ Object
readonly
Returns the value of attribute composition.
Instance Method Summary collapse
-
#bar_line(voice, bar_number) ⇒ Object
private
\key is per-staff inside « », so a mid-piece change is emitted in every voice’s stream; \time propagates score-wide, and the duplicate commands are harmless.
-
#bar_tokens(voice, bar_number) ⇒ Object
private
A bar with no placements for this voice — a voice that ended early, or an empty voice — fills with a whole-bar rest in the effective meter.
-
#clef_name(voice) ⇒ Object
private
The selector only returns the treble or bass clef.
-
#document_lines ⇒ Object
private
-
#header_lines ⇒ Object
private
-
#initialize(composition) ⇒ Writer
constructor
A new instance of Writer.
-
#music_lines(voice) ⇒ Object
private
-
#plan ⇒ Object
private
The computed rendering facts.
-
#score_lines ⇒ Object
private
-
#staff_lines(voice) ⇒ Object
private
-
#staff_open(voice) ⇒ Object
private
-
#time_command(meter) ⇒ Object
private
-
#to_s ⇒ Object
-
#whole_bar_rest(bar_number) ⇒ Object
private
Constructor Details
#initialize(composition) ⇒ Writer
Returns a new instance of Writer.
17 18 19 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 17 def initialize(composition) @composition = composition end |
Instance Attribute Details
#composition ⇒ Object (readonly)
Returns the value of attribute composition.
15 16 17 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 15 def composition @composition end |
Instance Method Details
#bar_line(voice, bar_number) ⇒ Object (private)
\key is per-staff inside « », so a mid-piece change is emitted in every voice’s stream; \time propagates score-wide, and the duplicate commands are harmless.
101 102 103 104 105 106 107 108 109 110 111 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 101 def (voice, ) tokens = [] unless == plan..first change_key = plan.measure_key_changes[] tokens << change_key if change_key change_meter = plan.measure_time_changes[] tokens << time_command(change_meter) if change_meter end tokens.concat((voice, )) tokens.join(" ") + " |" end |
#bar_tokens(voice, bar_number) ⇒ Object (private)
A bar with no placements for this voice — a voice that ended early, or an empty voice — fills with a whole-bar rest in the effective meter.
115 116 117 118 119 120 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 115 def (voice, ) placements = plan.(voice)[] return [()] unless placements placements.map { |placement| plan.tokens_by_placement[placement] } end |
#clef_name(voice) ⇒ Object (private)
The selector only returns the treble or bass clef.
89 90 91 92 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 89 def clef_name(voice) clef = HeadMusic::Notation::ClefSelector.for(voice) (clef == HeadMusic::Rudiment::Clef.get(:bass_clef)) ? "bass" : "treble" end |
#document_lines ⇒ Object (private)
35 36 37 38 39 40 41 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 35 def document_lines [ %(\\version "#{LILYPOND_VERSION}"), *header_lines, *score_lines ] end |
#header_lines ⇒ Object (private)
43 44 45 46 47 48 49 50 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 43 def header_lines [ "\\header {", %(#{INDENT}title = "#{StringText.escape(composition.name)}"), composition.composer && %(#{INDENT}composer = "#{StringText.escape(composition.composer)}"), "}" ].compact end |
#music_lines(voice) ⇒ Object (private)
79 80 81 82 83 84 85 86 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 79 def music_lines(voice) [ "\\clef #{clef_name(voice)}", plan.first_measure_key, time_command(plan.first_measure_meter), *plan..map { || (voice, ) } ] end |
#plan ⇒ Object (private)
The computed rendering facts. Built here — before assembly — so an unmappable key or duration raises before any output is produced.
31 32 33 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 31 def plan @plan ||= RenderPlan.new(composition) end |
#score_lines ⇒ Object (private)
52 53 54 55 56 57 58 59 60 61 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 52 def score_lines [ "\\score {", "#{INDENT}<<", *composition.voices.flat_map { |voice| staff_lines(voice) }, "#{INDENT}>>", "#{INDENT}\\layout { }", "}" ] end |
#staff_lines(voice) ⇒ Object (private)
63 64 65 66 67 68 69 70 71 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 63 def staff_lines(voice) [ "#{INDENT * 2}#{staff_open(voice)}", "#{INDENT * 3}\\new Voice {", *music_lines(voice).map { |line| INDENT * 4 + line }, "#{INDENT * 3}}", "#{INDENT * 2}}" ] end |
#staff_open(voice) ⇒ Object (private)
73 74 75 76 77 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 73 def staff_open(voice) return "\\new Staff {" unless voice.role %(\\new Staff \\with { instrumentName = "#{StringText.escape(voice.role)}" } {) end |
#time_command(meter) ⇒ Object (private)
94 95 96 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 94 def time_command(meter) "\\time #{meter.top_number}/#{meter.bottom_number}" end |
#to_s ⇒ Object
21 22 23 24 25 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 21 def to_s Preflight.check!(composition) plan document_lines.join("\n") + "\n" end |
#whole_bar_rest(bar_number) ⇒ Object (private)
122 123 124 125 |
# File 'lib/head_music/notation/lily_pond/writer.rb', line 122 def () meter = plan.effective_meter() "R1*#{meter.top_number}/#{meter.bottom_number}" end |