Class: HeadMusic::Notation::ABC::Writer
- Inherits:
-
Object
- Object
- HeadMusic::Notation::ABC::Writer
- Defined in:
- lib/head_music/notation/abc/writer.rb
Overview
Renders a HeadMusic::Content::Composition as an ABC tune string.
Whole-composition problems (multiple voices, mid-piece meter or key changes, positional gaps) raise before any string assembly, and #to_s only returns a fully assembled document, so callers never receive a truncated tune.
Repeat barlines and voltas are deliberately not rendered; bars carrying repeat flags degrade to plain bar lines.
Constant Summary collapse
- UNIT_NOTE_LENGTH =
A fixed unit note length keeps the L: field and the duration multiplier arithmetic in sync.
Rational(1, 8)
- BARS_PER_LINE =
4
Instance Attribute Summary collapse
-
#composition ⇒ Object
readonly
Returns the value of attribute composition.
-
#reference_number ⇒ Object
readonly
Returns the value of attribute reference_number.
Instance Method Summary collapse
-
#bar_strings ⇒ Object
private
-
#body_lines ⇒ Object
private
-
#build_bar_strings ⇒ Object
private
-
#ensure_contiguous_placements ⇒ Object
private
-
#ensure_no_mid_piece_changes ⇒ Object
private
-
#ensure_placement_starts_bar(placement) ⇒ Object
private
-
#ensure_single_voice ⇒ Object
private
-
#header_lines ⇒ Object
private
-
#initialize(composition, reference_number: 1) ⇒ Writer
constructor
A new instance of Writer.
-
#placements ⇒ Object
private
-
#to_s ⇒ Object
-
#token(placement, pitch_writer, duration_writer) ⇒ Object
private
-
#validate! ⇒ Object
private
Constructor Details
#initialize(composition, reference_number: 1) ⇒ Writer
Returns a new instance of Writer.
20 21 22 23 |
# File 'lib/head_music/notation/abc/writer.rb', line 20 def initialize(composition, reference_number: 1) @composition = composition @reference_number = reference_number end |
Instance Attribute Details
#composition ⇒ Object (readonly)
Returns the value of attribute composition.
18 19 20 |
# File 'lib/head_music/notation/abc/writer.rb', line 18 def composition @composition end |
#reference_number ⇒ Object (readonly)
Returns the value of attribute reference_number.
18 19 20 |
# File 'lib/head_music/notation/abc/writer.rb', line 18 def reference_number @reference_number end |
Instance Method Details
#bar_strings ⇒ Object (private)
105 106 107 |
# File 'lib/head_music/notation/abc/writer.rb', line 105 def @bar_strings ||= end |
#body_lines ⇒ Object (private)
95 96 97 98 99 100 101 102 103 |
# File 'lib/head_music/notation/abc/writer.rb', line 95 def body_lines return [] if .empty? lines = .each_slice(BARS_PER_LINE).map do || .join("|") + "|" end lines[-1] = lines[-1].sub(/\|\z/, "|]") lines end |
#build_bar_strings ⇒ Object (private)
109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/head_music/notation/abc/writer.rb', line 109 def pitch_writer = PitchWriter.new(composition.key_signature) duration_writer = DurationWriter.new(UNIT_NOTE_LENGTH) = placements.chunk_while do |previous, current| previous.position. == current.position. end .each_with_index.map do |, index| # Accidental state must mirror what a re-parse accumulates bar by bar. pitch_writer. if index.positive? .map { |placement| token(placement, pitch_writer, duration_writer) }.join(" ") end end |
#ensure_contiguous_placements ⇒ Object (private)
56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/head_music/notation/abc/writer.rb', line 56 def ensure_contiguous_placements first = placements.first return unless first (first) placements.each_cons(2) do |previous, current| next if current.position == previous.next_position raise RenderError, "expected a placement at #{previous.next_position}, " \ "found one at #{current.position}; insert explicit rests to fill gaps" end end |
#ensure_no_mid_piece_changes ⇒ Object (private)
44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/head_music/notation/abc/writer.rb', line 44 def ensure_no_mid_piece_changes composition..each_with_index do |, index| = composition. + index if .meter raise RenderError, "cannot render the meter change at bar #{} in ABC output" end next unless .key_signature raise RenderError, "cannot render the key signature change at bar #{} in ABC output" end end |
#ensure_placement_starts_bar(placement) ⇒ Object (private)
69 70 71 72 73 74 75 |
# File 'lib/head_music/notation/abc/writer.rb', line 69 def (placement) position = placement.position return if position.count == 1 && position.tick.zero? raise RenderError, "the first placement must start its bar " \ "(found #{position}); insert explicit rests to fill the gap" end |
#ensure_single_voice ⇒ Object (private)
38 39 40 41 42 |
# File 'lib/head_music/notation/abc/writer.rb', line 38 def ensure_single_voice return if composition.voices.length <= 1 raise RenderError, "multi-voice ABC output is not supported" end |
#header_lines ⇒ Object (private)
82 83 84 85 86 87 88 89 90 91 92 93 |
# File 'lib/head_music/notation/abc/writer.rb', line 82 def header_lines [ "X:#{reference_number}", "T:#{composition.name}", composition.composer && "C:#{composition.composer}", composition.origin && "O:#{composition.origin}", "M:#{composition.meter}", "L:#{UNIT_NOTE_LENGTH.numerator}/#{UNIT_NOTE_LENGTH.denominator}", # The parser requires K: to terminate the header. "K:#{KeyMapper.abc_value(composition.key_signature)}" ].compact end |
#placements ⇒ Object (private)
77 78 79 80 |
# File 'lib/head_music/notation/abc/writer.rb', line 77 def placements voice = composition.voices.first voice ? voice.placements : [] end |
#to_s ⇒ Object
25 26 27 28 |
# File 'lib/head_music/notation/abc/writer.rb', line 25 def to_s validate! (header_lines + body_lines).join("\n") + "\n" end |
#token(placement, pitch_writer, duration_writer) ⇒ Object (private)
122 123 124 125 126 127 |
# File 'lib/head_music/notation/abc/writer.rb', line 122 def token(placement, pitch_writer, duration_writer) multiplier = duration_writer.multiplier_string(placement.rhythmic_value) return "z#{multiplier}" if placement.rest? "#{pitch_writer.token(placement.pitch)}#{multiplier}" end |
#validate! ⇒ Object (private)
32 33 34 35 36 |
# File 'lib/head_music/notation/abc/writer.rb', line 32 def validate! ensure_single_voice ensure_no_mid_piece_changes ensure_contiguous_placements end |