Class: HeadMusic::Notation::LilyPond::VoiceWriter
- Inherits:
-
Object
- Object
- HeadMusic::Notation::LilyPond::VoiceWriter
- Defined in:
- lib/head_music/notation/lily_pond/voice_writer.rb
Overview
The lines inside a \new Voice block. Writer decides which staves and voices exist; this decides what each one says.
Constant Summary collapse
- CLEF_NAMES =
By LilyPond’s name. The octave clefs carry characters LilyPond only accepts inside quotes.
{ treble_clef: "treble", french_violin_clef: "french", vocal_tenor_clef: %("treble_8"), double_treble_clef: %("treble^8"), soprano_clef: "soprano", mezzo_soprano_clef: "mezzosoprano", alto_clef: "alto", tenor_clef: "tenor", baritone_c_clef: "baritone", baritone_clef: "varbaritone", bass_clef: "bass", sub_bass_clef: "subbass", neutral_clef: "percussion" }.freeze
- MIDDLE_C =
\transposition names the sounding pitch of a written middle C.
"C4"
Instance Attribute Summary collapse
-
#plan ⇒ Object
readonly
private
Returns the value of attribute plan.
Instance Method Summary collapse
-
#bar_line(voice, bar_number, part_index) ⇒ Object
private
-
#bar_tokens(voice, bar_number) ⇒ Object
private
-
#change_commands(part, 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.
-
#clef_name(voice, staff) ⇒ Object
private
The selector is the fallback for a part whose staves were never authored.
-
#clef_word(clef) ⇒ Object
private
-
#first_bar_number ⇒ Object
private
-
#initialize(plan) ⇒ VoiceWriter
constructor
A new instance of VoiceWriter.
-
#lines(voice, part_index: nil, staff: nil) ⇒ Object
-
#opening_lines(clef_name, part) ⇒ Object
private
-
#silent_lines(staff, part: nil) ⇒ Object
A staff nobody is written on still has to appear, or a tacet part loses its line in the score.
-
#staff_change_command(voice, bar_number, part_index) ⇒ Object
private
Emitted at exactly the bars the voice’s staff-assignment map holds events for, so the crossings and the commands are the same thing.
-
#time_command(meter) ⇒ Object
private
-
#transposition_line(part) ⇒ Object
private
What the written pitches sound like.
-
#whole_bar_rest(bar_number) ⇒ Object
private
Constructor Details
#initialize(plan) ⇒ VoiceWriter
Returns a new instance of VoiceWriter.
27 28 29 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 27 def initialize(plan) @plan = plan end |
Instance Attribute Details
#plan ⇒ Object (readonly, private)
Returns the value of attribute plan.
46 47 48 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 46 def plan @plan end |
Instance Method Details
#bar_line(voice, bar_number, part_index) ⇒ Object (private)
89 90 91 92 93 94 95 96 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 89 def (voice, , part_index) tokens = [ *change_commands(voice.part, ), staff_change_command(voice, , part_index), *(voice, ) ] "#{tokens.compact.join(" ")} |" end |
#bar_tokens(voice, bar_number) ⇒ Object (private)
118 119 120 121 122 123 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 118 def (voice, ) placements = plan.(voice)[] return [()] unless placements placements.map { |placement| plan.tokens_by_placement[placement] } end |
#change_commands(part, 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 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 101 def change_commands(part, ) return [] if == meter = plan.measure_time_changes[] [plan.measure_key_changes(part)[], meter && time_command(meter)].compact end |
#clef_name(voice, staff) ⇒ Object (private)
The selector is the fallback for a part whose staves were never authored. It reads a voice’s pitch range, which is why the fallback lives here rather than on the staff.
64 65 66 67 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 64 def clef_name(voice, staff) clef = staff&.clef_at() || HeadMusic::Notation::ClefSelector.for(voice) clef_word(clef) end |
#clef_word(clef) ⇒ Object (private)
69 70 71 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 69 def clef_word(clef) CLEF_NAMES.fetch(clef.name_key.to_sym) end |
#first_bar_number ⇒ Object (private)
48 49 50 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 48 def plan..first end |
#lines(voice, part_index: nil, staff: nil) ⇒ Object
31 32 33 34 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 31 def lines(voice, part_index: nil, staff: nil) opening_lines(clef_name(voice, staff), voice.part) + plan..map { || (voice, , part_index) } end |
#opening_lines(clef_name, part) ⇒ Object (private)
52 53 54 55 56 57 58 59 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 52 def opening_lines(clef_name, part) [ "\\clef #{clef_name}", *transposition_line(part), plan.first_measure_key(part), time_command(plan.first_measure_meter) ] end |
#silent_lines(staff, part: nil) ⇒ Object
A staff nobody is written on still has to appear, or a tacet part loses its line in the score. Its rest-filled voice opens like any other so the key and time print and the bars line up.
39 40 41 42 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 39 def silent_lines(staff, part: nil) opening_lines(clef_name(nil, staff), part) + plan..map { || "#{()} |" } end |
#staff_change_command(voice, bar_number, part_index) ⇒ Object (private)
Emitted at exactly the bars the voice’s staff-assignment map holds events for, so the crossings and the commands are the same thing.
110 111 112 113 114 115 116 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 110 def staff_change_command(voice, , part_index) staff = voice.staff_assignments[] return if part_index.nil? || staff.nil? || == index = voice.part.staff_system_at().staves.index { |candidate| candidate.equal?(staff) } index && %(\\change Staff = "#{Writer.staff_id(part_index, index)}") end |
#time_command(meter) ⇒ Object (private)
73 74 75 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 73 def time_command(meter) "\\time #{meter.top_number}/#{meter.bottom_number}" end |
#transposition_line(part) ⇒ Object (private)
What the written pitches sound like. A part carries this rather than a \transpose wrapper, which would move notes that are already written.
79 80 81 82 83 84 85 86 87 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 79 def transposition_line(part) return [] unless plan.transposed? instrument = part&.instrument_at() return [] unless instrument&.transposing? sounding = HeadMusic::Content::Layout::Transposition.for(instrument).sounding(MIDDLE_C) ["\\transposition #{PitchWriter.token(sounding)}"] end |
#whole_bar_rest(bar_number) ⇒ Object (private)
125 126 127 128 |
# File 'lib/head_music/notation/lily_pond/voice_writer.rb', line 125 def () meter = plan.effective_meter() "R1*#{meter.top_number}/#{meter.bottom_number}" end |