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 flow as a score-partwise MusicXML 4.0 document, assembled down to the measure; AttributesWriter serializes a measure's attributes and NoteWriter its notes. Whole-flow problems raise before any assembly, so #to_s only ever returns a complete document.
Instance Attribute Summary collapse
-
#arranger ⇒ Object
readonly
Returns the value of attribute arranger.
-
#flow ⇒ Object
readonly
Returns the value of attribute flow.
-
#movement_number ⇒ Object
readonly
Returns the value of attribute movement_number.
-
#transposed ⇒ Object
readonly
Returns the value of attribute transposed.
-
#work_title ⇒ Object
readonly
Returns the value of attribute work_title.
Instance Method Summary collapse
- #attributes_writer ⇒ Object private
- #backup_lines(duration) ⇒ Object private
- #document_lines ⇒ Object private
- #escape(text) ⇒ Object included from XmlText private
- #identification_lines ⇒ Object private
-
#initialize(flow, work_title: nil, movement_number: nil, transposed: false, arranger: nil) ⇒ Writer
constructor
A new instance of Writer.
-
#measure_content_lines(part, voice, bar_number) ⇒ Object
private
A voice of nil is a part nobody plays in, which renders as whole-measure rests on its first staff so the chair keeps its line in the score.
- #measure_lines(part, bar_number) ⇒ Object private
-
#measure_open_tag(bar_number) ⇒ Object
private
A bar before bar 1 is marked implicit by convention.
-
#movement_lines ⇒ Object
private
A movement titles itself only where a work titles the whole; a document standing alone would otherwise say the same name twice.
- #note_writer ⇒ Object private
-
#part_content_lines(part, bar_number) ⇒ Object
private
A
before each voice after the first is how MusicXML writes simultaneous voices in one part. - #part_lines ⇒ Object private
-
#part_list_lines ⇒ Object
private
One
per part, not per voice. -
#part_name(part, index) ⇒ Object
private
A voice's role names the part only where the part holds a single voice, so that one voice's role does not stand for several.
-
#plan ⇒ Object
private
The computed rendering facts.
-
#staff_number(part, voice, bar_number) ⇒ Object
private
Where a crossing shows up: the same voice reports a different staff on either side of it.
- #to_s ⇒ Object
- #work_lines ⇒ Object private
Constructor Details
#initialize(flow, work_title: nil, movement_number: nil, transposed: false, arranger: nil) ⇒ Writer
Returns a new instance of Writer.
16 17 18 19 20 21 22 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 16 def initialize(flow, work_title: nil, movement_number: nil, transposed: false, arranger: nil) @flow = flow @work_title = work_title @movement_number = movement_number @transposed = transposed @arranger = arranger end |
Instance Attribute Details
#arranger ⇒ Object (readonly)
Returns the value of attribute arranger.
12 13 14 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 12 def arranger @arranger end |
#flow ⇒ Object (readonly)
Returns the value of attribute flow.
12 13 14 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 12 def flow @flow end |
#movement_number ⇒ Object (readonly)
Returns the value of attribute movement_number.
12 13 14 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 12 def movement_number @movement_number end |
#transposed ⇒ Object (readonly)
Returns the value of attribute transposed.
12 13 14 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 12 def transposed @transposed end |
#work_title ⇒ Object (readonly)
Returns the value of attribute work_title.
12 13 14 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 12 def work_title @work_title end |
Instance Method Details
#attributes_writer ⇒ Object (private)
42 43 44 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 42 def attributes_writer @attributes_writer ||= AttributesWriter.new(plan) end |
#backup_lines(duration) ⇒ Object (private)
144 145 146 147 148 149 150 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 144 def backup_lines(duration) [ "#{INDENT * 3}<backup>", "#{INDENT * 4}<duration>#{duration}</duration>", "#{INDENT * 3}</backup>" ] end |
#document_lines ⇒ Object (private)
46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 46 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, *movement_lines, *identification_lines, *part_list_lines, *part_lines, "</score-partwise>" ] end |
#escape(text) ⇒ Object (private) Originally defined in module XmlText
#identification_lines ⇒ Object (private)
77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 77 def identification_lines [ "#{INDENT}<identification>", flow.composer && %(#{INDENT * 2}<creator type="composer">#{escape(flow.composer)}</creator>), arranger && %(#{INDENT * 2}<creator type="arranger">#{escape(arranger)}</creator>), "#{INDENT * 2}<encoding>", "#{INDENT * 3}<software>head_music #{HeadMusic::VERSION}</software>", "#{INDENT * 2}</encoding>", "#{INDENT}</identification>" ].compact end |
#measure_content_lines(part, voice, bar_number) ⇒ Object (private)
A voice of nil is a part nobody plays in, which renders as whole-measure rests on its first staff so the chair keeps its line in the score.
161 162 163 164 165 166 167 168 169 170 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 161 def measure_content_lines(part, voice, ) voice_number = (part.voices.length > 1) ? part.voices.index(voice) + 1 : nil staff_number = staff_number(part, voice, ) placements = voice && (voice)[] return note_writer.whole_measure_rest_lines(, voice_number: voice_number, staff_number: staff_number) unless placements placements.flat_map do |placement| note_writer.lines(placement, voice_number: voice_number, staff_number: staff_number) end end |
#measure_lines(part, bar_number) ⇒ Object (private)
121 122 123 124 125 126 127 128 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 121 def measure_lines(part, ) [ measure_open_tag(), *attributes_writer.lines(part, ), *part_content_lines(part, ), "#{INDENT * 2}</measure>" ] end |
#measure_open_tag(bar_number) ⇒ Object (private)
A bar before bar 1 is marked implicit by convention. A partially filled first bar is rejected as a gap in Preflight.
154 155 156 157 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 154 def measure_open_tag() implicit = ( < 1) ? %( implicit="yes") : "" %(#{INDENT * 2}<measure number="#{}"#{implicit}>) end |
#movement_lines ⇒ Object (private)
A movement titles itself only where a work titles the whole; a document standing alone would otherwise say the same name twice.
70 71 72 73 74 75 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 70 def movement_lines [ movement_number && "#{INDENT}<movement-number>#{escape(movement_number.to_s)}</movement-number>", work_title && "#{INDENT}<movement-title>#{escape(flow.name)}</movement-title>" ].compact end |
#note_writer ⇒ Object (private)
38 39 40 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 38 def note_writer @note_writer ||= NoteWriter.new(plan) end |
#part_content_lines(part, bar_number) ⇒ Object (private)
A
133 134 135 136 137 138 139 140 141 142 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 133 def part_content_lines(part, ) return measure_content_lines(part, part.voices.first, ) if part.voices.length <= 1 part.voices.each_with_index.flat_map do |voice, index| [ *(index.positive? ? backup_lines(written_duration(part.voices[index - 1], )) : []), *measure_content_lines(part, voice, ) ] end end |
#part_lines ⇒ Object (private)
111 112 113 114 115 116 117 118 119 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 111 def part_lines flow.parts.each_with_index.flat_map do |part, index| [ %(#{INDENT}<part id="P#{index + 1}">), *.flat_map { || measure_lines(part, ) }, "#{INDENT}</part>" ] end end |
#part_list_lines ⇒ Object (private)
One
91 92 93 94 95 96 97 98 99 100 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 91 def part_list_lines score_part_lines = flow.parts.each_with_index.flat_map do |part, index| [ %(#{INDENT * 2}<score-part id="P#{index + 1}">), "#{INDENT * 3}<part-name>#{escape(part_name(part, index))}</part-name>", "#{INDENT * 2}</score-part>" ] end ["#{INDENT}<part-list>", *score_part_lines, "#{INDENT}</part-list>"] end |
#part_name(part, index) ⇒ Object (private)
A voice's role names the part only where the part holds a single voice, so that one voice's role does not stand for several.
104 105 106 107 108 109 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 104 def part_name(part, index) part.player&.name || part.instrument&.name || (part.voices.one? ? part.voices.first.role : nil) || "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.
34 35 36 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 34 def plan @plan ||= RenderPlan.new(flow, transposed: transposed) end |
#staff_number(part, voice, bar_number) ⇒ Object (private)
Where a crossing shows up: the same voice reports a different staff on either side of it.
174 175 176 177 178 179 180 |
# File 'lib/head_music/notation/music_xml/writer.rb', line 174 def staff_number(part, voice, ) staves = part.staff_system_at().staves return nil if staves.length <= 1 || voice.nil? index = staves.index { |staff| staff.equal?(voice.staff_at()) } index && index + 1 end |