Class: HeadMusic::Notation::MusicXML::NoteWriter

Inherits:
Object
  • Object
show all
Includes:
XmlText, PlacementValidation
Defined in:
lib/head_music/notation/music_xml/note_writer.rb

Overview

Serializes the elements a placement occupies.

A placement becomes one per tied component per sounding pitch: the components come from the render plan's duration split, and a chord renders as a lead note followed by its members.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(plan) ⇒ NoteWriter

Returns a new instance of NoteWriter.



14
15
16
17
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 14

def initialize(plan)
  @plan = plan
  @lyric_writer = LyricWriter.new
end

Instance Attribute Details

#lyric_writer ⇒ Object (readonly, private)

Returns the value of attribute lyric_writer.



53
54
55
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 53

def lyric_writer
  @lyric_writer
end

#plan ⇒ Object (readonly, private)

Returns the value of attribute plan.



53
54
55
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 53

def plan
  @plan
end

Instance Method Details

#beam_lines(beams) ⇒ Object (private)



93
94
95
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 93

def beam_lines(beams)
  beams.map { |beam| %(#{INDENT * 4}<beam number="#{beam.number}">#{beam.type}</beam>) }
end

#element_lines(placement, component, pitch: nil, chord: false, beams: [], voice_number: nil, staff_number: nil) ⇒ Object (private)

A chord note carries as its first child, before , marking it as sounding with the preceding note; the lead note (and every single note and rest) omits it, so this path stays byte-identical for those. Element order inside is fixed by the DTD: follows the ties and precedes , and follows the dots and precedes the beams. Both are omitted entirely for the one-voice, one-staff part that every existing document is made of, which is what keeps this byte-identical.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 75

def element_lines(placement, component, pitch: nil, chord: false, beams: [], voice_number: nil, staff_number: nil)
  [
    "#{INDENT * 3}<note>",
    *(chord ? ["#{INDENT * 4}<chord/>"] : []),
    *(pitch ? pitch_lines(pitch) : ["#{INDENT * 4}<rest/>"]),
    "#{INDENT * 4}<duration>#{component.duration}</duration>",
    *tie_lines(placement, component),
    voice_number && "#{INDENT * 4}<voice>#{voice_number}</voice>",
    "#{INDENT * 4}<type>#{component.type}</type>",
    *Array.new(component.dots) { "#{INDENT * 4}<dot/>" },
    staff_number && "#{INDENT * 4}<staff>#{staff_number}</staff>",
    *beam_lines(beams),
    *notation_lines(placement, component),
    *lyric_writer.lines(placement, component, chord: chord),
    "#{INDENT * 3}</note>"
  ].compact
end

#ensure_pitched_sounds(placement) ⇒ Object (private) Originally defined in module PlacementValidation

Raises:

  • (render_error_class)

#escape(text) ⇒ Object (private) Originally defined in module XmlText

#lines(placement, voice_number: nil, staff_number: nil) ⇒ Object

Parameters:

  • voice_number (Integer, nil) (defaults to: nil) —

    the a part's notes belong to, omitted for a part holding one voice

  • staff_number (Integer, nil) (defaults to: nil) —

    the the note is written on, omitted for a part on one staff



23
24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 23

def lines(placement, voice_number: nil, staff_number: nil)
  ensure_pitched_sounds(placement)

  components_by_placement[placement].each_with_index.flat_map do |component, component_index|
    beams = beam_annotations[[placement, component_index]] || []
    note_slots(placement).each_with_index.flat_map do |pitch, index|
      element_lines(
        placement, component, pitch: pitch, chord: index.positive?, beams: index.zero? ? beams : [],
        voice_number: voice_number, staff_number: staff_number
      )
    end
  end
end

#notation_lines(placement, component) ⇒ Object (private)



119
120
121
122
123
124
125
126
127
128
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 119

def notation_lines(placement, component)
  return [] if placement.rest? || (!component.tie_start && !component.tie_stop)

  [
    "#{INDENT * 4}<notations>",
    component.tie_stop ? %(#{INDENT * 5}<tied type="stop"/>) : nil,
    component.tie_start ? %(#{INDENT * 5}<tied type="start"/>) : nil,
    "#{INDENT * 4}</notations>"
  ].compact
end

#note_slots(placement) ⇒ Object (private)

A rest emits one empty slot; a sounded placement emits its pitches low to high, so the lowest note leads and the rest carry . ensure_pitched_sounds has already rejected any unpitched sound, so pitches covers every sound here.



60
61
62
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 60

def note_slots(placement)
  placement.rest? ? [nil] : placement.pitches.sort
end

#pitch_lines(pitch) ⇒ Object (private)



97
98
99
100
101
102
103
104
105
106
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 97

def pitch_lines(pitch)
  attributes = PitchWriter.attributes(pitch)
  [
    "#{INDENT * 4}<pitch>",
    "#{INDENT * 5}<step>#{attributes[:step]}</step>",
    attributes[:alter] && "#{INDENT * 5}<alter>#{attributes[:alter]}</alter>",
    "#{INDENT * 5}<octave>#{attributes[:octave]}</octave>",
    "#{INDENT * 4}</pitch>"
  ].compact
end

#render_error_class ⇒ Object (private)



64
65
66
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 64

def render_error_class
  RenderError
end

#tie_lines(placement, component) ⇒ Object (private)

Rests take no tie elements; the links of a rest's tied chain render as consecutive independent rests.



110
111
112
113
114
115
116
117
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 110

def tie_lines(placement, component)
  return [] if placement.rest?

  [
    component.tie_stop ? %(#{INDENT * 4}<tie type="stop"/>) : nil,
    component.tie_start ? %(#{INDENT * 4}<tie type="start"/>) : nil
  ].compact
end

#whole_measure_rest_lines(bar_number, voice_number: nil, staff_number: nil) ⇒ Object

The whole-measure rest that stands in for a bar the voice places nothing in. It belongs to its voice and staff as much as a note does, or a reader stacks it onto voice 1 of staff 1.



40
41
42
43
44
45
46
47
48
49
# File 'lib/head_music/notation/music_xml/note_writer.rb', line 40

def whole_measure_rest_lines(bar_number, voice_number: nil, staff_number: nil)
  [
    "#{INDENT * 3}<note>",
    %(#{INDENT * 4}<rest measure="yes"/>),
    "#{INDENT * 4}<duration>#{plan.whole_measure_duration(bar_number)}</duration>",
    voice_number && "#{INDENT * 4}<voice>#{voice_number}</voice>",
    staff_number && "#{INDENT * 4}<staff>#{staff_number}</staff>",
    "#{INDENT * 3}</note>"
  ].compact
end