Class: HeadMusic::Notation::MusicXML::Writer
- Inherits:
-
Object
- Object
- HeadMusic::Notation::MusicXML::Writer
- Includes:
- XmlText
- Defined in:
- lib/head_music/notation/music_xml/writer.rb
Overview
Renders a HeadMusic::Content::Composition as a score-partwise MusicXML 4.0 document string.
Assembles the document down to the measure; NoteWriter serializes what goes inside each measure.
Whole-composition problems (no voices, positional gaps, barline-crossing notes, unmappable keys or durations, forbidden control characters) raise before any assembly, so #to_s only ever returns a complete document.
Instance Attribute Summary collapse
-
#composition ⇒ Object
readonly
Returns the value of attribute composition.
Instance Method Summary collapse
-
#attribute_lines(voice, bar_number) ⇒ Object
private
-
#clef_lines(voice) ⇒ Object
private
-
#document_lines ⇒ Object
private
-
#escape(text) ⇒ Object
included
from XmlText
private
-
#first_measure_attribute_lines(voice) ⇒ Object
private
-
#identification_lines ⇒ Object
private
-
#initialize(composition) ⇒ Writer
constructor
A new instance of Writer.
-
#key_lines(key) ⇒ Object
private
-
#measure_content_lines(voice, bar_number) ⇒ Object
private
-
#measure_lines(voice, bar_number) ⇒ Object
private
-
#measure_open_tag(bar_number) ⇒ Object
private
A bar before bar 1 — a pickup written out in full with leading rests — is marked implicit by convention.
-
#note_writer ⇒ Object
private
-
#part_lines ⇒ Object
private
-
#part_list_lines ⇒ Object
private
-
#part_name(voice, index) ⇒ Object
private
-
#plan ⇒ Object
private
The computed rendering facts.
-
#time_lines(meter) ⇒ Object
private
-
#to_s ⇒ Object
-
#whole_measure_rest_lines(bar_number) ⇒ Object
private
-
#work_lines ⇒ Object
private
Constructor Details
#initialize(composition) ⇒ Writer
Returns a new instance of Writer.
28 29 30 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 28 def initialize(composition) @composition = composition end |
Instance Attribute Details
#composition ⇒ Object (readonly)
Returns the value of attribute composition.
17 18 19 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 17 def composition @composition end |
Instance Method Details
#attribute_lines(voice, bar_number) ⇒ Object (private)
124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 124 def attribute_lines(voice, ) return first_measure_attribute_lines(voice) if == .first key = measure_key_changes[] meter = measure_time_changes[] return [] unless key || meter [ "#{INDENT * 3}<attributes>", *(key ? key_lines(key) : []), *(meter ? time_lines(meter) : []), "#{INDENT * 3}</attributes>" ] end |
#clef_lines(voice) ⇒ Object (private)
168 169 170 171 172 173 174 175 176 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 168 def clef_lines(voice) clef = ClefSelector.for(voice) [ "#{INDENT * 4}<clef>", "#{INDENT * 5}<sign>#{clef.pitch.letter_name}</sign>", "#{INDENT * 5}<line>#{clef.line}</line>", "#{INDENT * 4}</clef>" ] end |
#document_lines ⇒ Object (private)
50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 50 def document_lines [ %(<?xml version="1.0" encoding="UTF-8"?>), %(<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">), %(<score-partwise version="4.0">), *work_lines, *identification_lines, *part_list_lines, *part_lines, "</score-partwise>" ] end |
#escape(text) ⇒ Object (private) Originally defined in module XmlText
#first_measure_attribute_lines(voice) ⇒ Object (private)
139 140 141 142 143 144 145 146 147 148 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 139 def first_measure_attribute_lines(voice) [ "#{INDENT * 3}<attributes>", "#{INDENT * 4}<divisions>#{divisions}</divisions>", *key_lines(first_measure_key), *time_lines(first_measure_meter), *clef_lines(voice), "#{INDENT * 3}</attributes>" ] end |
#identification_lines ⇒ Object (private)
71 72 73 74 75 76 77 78 79 80 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 71 def identification_lines [ "#{INDENT}<identification>", composition.composer && %(#{INDENT * 2}<creator type="composer">#{escape(composition.composer)}</creator>), "#{INDENT * 2}<encoding>", "#{INDENT * 3}<software>head_music #{HeadMusic::VERSION}</software>", "#{INDENT * 2}</encoding>", "#{INDENT}</identification>" ].compact end |
#key_lines(key) ⇒ Object (private)
150 151 152 153 154 155 156 157 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 150 def key_lines(key) [ "#{INDENT * 4}<key>", "#{INDENT * 5}<fifths>#{key[:fifths]}</fifths>", "#{INDENT * 5}<mode>#{key[:mode]}</mode>", "#{INDENT * 4}</key>" ] end |
#measure_content_lines(voice, bar_number) ⇒ Object (private)
178 179 180 181 182 183 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 178 def measure_content_lines(voice, ) placements = (voice)[] return whole_measure_rest_lines() unless placements placements.flat_map { |placement| note_writer.lines(placement) } end |
#measure_lines(voice, bar_number) ⇒ Object (private)
107 108 109 110 111 112 113 114 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 107 def measure_lines(voice, ) [ measure_open_tag(), *attribute_lines(voice, ), *measure_content_lines(voice, ), "#{INDENT * 2}</measure>" ] end |
#measure_open_tag(bar_number) ⇒ Object (private)
A bar before bar 1 — a pickup written out in full with leading rests — is marked implicit by convention. A partially filled first bar is rejected as a gap in Preflight, so only complete pickup bars reach here.
119 120 121 122 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 119 def measure_open_tag() implicit = ( < 1) ? %( implicit="yes") : "" %(#{INDENT * 2}<measure number="#{}"#{implicit}>) end |
#note_writer ⇒ Object (private)
46 47 48 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 46 def note_writer @note_writer ||= NoteWriter.new(plan) end |
#part_lines ⇒ Object (private)
97 98 99 100 101 102 103 104 105 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 97 def part_lines composition.voices.each_with_index.flat_map do |voice, index| [ %(#{INDENT}<part id="P#{index + 1}">), *.flat_map { || measure_lines(voice, ) }, "#{INDENT}</part>" ] end end |
#part_list_lines ⇒ Object (private)
82 83 84 85 86 87 88 89 90 91 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 82 def part_list_lines score_part_lines = composition.voices.each_with_index.flat_map do |voice, index| [ %(#{INDENT * 2}<score-part id="P#{index + 1}">), "#{INDENT * 3}<part-name>#{escape(part_name(voice, index))}</part-name>", "#{INDENT * 2}</score-part>" ] end ["#{INDENT}<part-list>", *score_part_lines, "#{INDENT}</part-list>"] end |
#part_name(voice, index) ⇒ Object (private)
93 94 95 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 93 def part_name(voice, index) voice.role || "Voice #{index + 1}" end |
#plan ⇒ Object (private)
The computed rendering facts. Built here — before assembly — so an unmappable key or duration raises before any output is produced.
42 43 44 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 42 def plan @plan ||= RenderPlan.new(composition) end |
#time_lines(meter) ⇒ Object (private)
159 160 161 162 163 164 165 166 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 159 def time_lines(meter) [ "#{INDENT * 4}<time>", "#{INDENT * 5}<beats>#{meter.top_number}</beats>", "#{INDENT * 5}<beat-type>#{meter.bottom_number}</beat-type>", "#{INDENT * 4}</time>" ] end |
#to_s ⇒ Object
32 33 34 35 36 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 32 def to_s Preflight.check!(composition) plan document_lines.join("\n") + "\n" end |
#whole_measure_rest_lines(bar_number) ⇒ Object (private)
185 186 187 188 189 190 191 192 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 185 def whole_measure_rest_lines() [ "#{INDENT * 3}<note>", %(#{INDENT * 4}<rest measure="yes"/>), "#{INDENT * 4}<duration>#{whole_measure_duration()}</duration>", "#{INDENT * 3}</note>" ] end |