Class: HeadMusic::Notation::MusicXML::Preflight
- Inherits:
-
Object
- Object
- HeadMusic::Notation::MusicXML::Preflight
- Defined in:
- lib/head_music/notation/music_xml/preflight.rb
Overview
Rejects compositions that cannot be expressed in the supported MusicXML subset, and normalizes each bar’s meter and key-signature markers in place so later positional arithmetic and assembly can rely on coerced values.
Whole-composition problems (no voices, positional gaps, barline-crossing notes, forbidden control characters) raise RenderError here, before the Writer assembles any output — so a successful check! is the Writer’s guarantee that assembly cannot fail on these grounds.
Constant Summary collapse
- FORBIDDEN_TEXT_CHARACTERS =
XML 1.0 forbids the C0 control characters other than tab, newline, and carriage return, even as character references.
/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/
Instance Attribute Summary collapse
-
#composition ⇒ Object
readonly
private
Returns the value of attribute composition.
Class Method Summary collapse
Instance Method Summary collapse
-
#check! ⇒ Object
-
#ensure_contiguous_voices ⇒ Object
private
-
#ensure_notes_within_barlines ⇒ Object
private
-
#ensure_renderable_text ⇒ Object
private
-
#ensure_voices ⇒ Object
private
-
#initialize(composition) ⇒ Preflight
constructor
A new instance of Preflight.
-
#normalize_bar_markers ⇒ Object
private
change_meter and change_key_signature store the caller’s raw value (Bar’s accessors are bare attr_accessors), and Position arithmetic breaks on an un-coerced meter string, so markers are normalized in place before any placement’s next_position is computed.
-
#raise_gap_error(voice, expected_position, found_placement) ⇒ Object
private
Constructor Details
#initialize(composition) ⇒ Preflight
Returns a new instance of Preflight.
20 21 22 |
# File 'lib/head_music/notation/music_xml/preflight.rb', line 20 def initialize(composition) @composition = composition end |
Instance Attribute Details
#composition ⇒ Object (readonly, private)
Returns the value of attribute composition.
34 35 36 |
# File 'lib/head_music/notation/music_xml/preflight.rb', line 34 def composition @composition end |
Class Method Details
.check!(composition) ⇒ Object
16 17 18 |
# File 'lib/head_music/notation/music_xml/preflight.rb', line 16 def self.check!(composition) new(composition).check! end |
Instance Method Details
#check! ⇒ Object
24 25 26 27 28 29 30 |
# File 'lib/head_music/notation/music_xml/preflight.rb', line 24 def check! ensure_voices ensure_renderable_text ensure_contiguous_voices end |
#ensure_contiguous_voices ⇒ Object (private)
62 63 64 65 66 67 |
# File 'lib/head_music/notation/music_xml/preflight.rb', line 62 def ensure_contiguous_voices composition.voices.each do |voice| gap = voice.first_gap raise_gap_error(voice, *gap) if gap end end |
#ensure_notes_within_barlines ⇒ Object (private)
79 80 81 82 83 84 85 86 87 88 |
# File 'lib/head_music/notation/music_xml/preflight.rb', line 79 def composition.voices.each do |voice| voice.placements.each do |placement| next unless placement.next_position > placement.position. raise RenderError, "the note at #{placement.position} crosses its barline; " \ "splitting notes across barlines is not supported" end end end |
#ensure_renderable_text ⇒ Object (private)
53 54 55 56 57 58 59 60 |
# File 'lib/head_music/notation/music_xml/preflight.rb', line 53 def ensure_renderable_text texts = [composition.name, composition.composer] + composition.voices.map(&:role) texts.compact.each do |text| next unless text.to_s.match?(FORBIDDEN_TEXT_CHARACTERS) raise RenderError, "cannot render control characters in #{text.to_s.inspect} as XML text" end end |
#ensure_voices ⇒ Object (private)
36 37 38 39 40 |
# File 'lib/head_music/notation/music_xml/preflight.rb', line 36 def ensure_voices return unless composition.voices.empty? raise RenderError, "cannot render a composition with no voices as MusicXML" end |
#normalize_bar_markers ⇒ Object (private)
change_meter and change_key_signature store the caller’s raw value (Bar’s accessors are bare attr_accessors), and Position arithmetic breaks on an un-coerced meter string, so markers are normalized in place before any placement’s next_position is computed.
46 47 48 49 50 51 |
# File 'lib/head_music/notation/music_xml/preflight.rb', line 46 def composition..each do || .meter = HeadMusic::Rudiment::Meter.get(.meter) if .meter .key_signature = HeadMusic::Rudiment::KeySignature.get(.key_signature) if .key_signature end end |
#raise_gap_error(voice, expected_position, found_placement) ⇒ Object (private)
69 70 71 72 73 74 75 76 77 |
# File 'lib/head_music/notation/music_xml/preflight.rb', line 69 def raise_gap_error(voice, expected_position, found_placement) if found_placement.equal?(voice.placements.first) raise RenderError, "the first placement must start its bar " \ "(found #{found_placement.position}); insert explicit rests to fill the gap" end raise RenderError, "expected a placement at #{expected_position}, " \ "found one at #{found_placement.position}; insert explicit rests to fill gaps" end |