Class: HeadMusic::Notation::ABC::Header
- Inherits:
-
Object
- Object
- HeadMusic::Notation::ABC::Header
- Defined in:
- lib/head_music/notation/abc/header.rb
Overview
Splits an ABC tune string into header fields and tune body.
The K: (key) field is required and must be the last header field; everything after that line is the tune body. Requiring K: avoids silently defaulting the composition to C major.
Constant Summary collapse
- FIELD_PATTERN =
/\A([A-Za-z]):(.*)\z/- FRACTION_PATTERN =
%r{\A\d+/\d+\z}
Instance Attribute Summary collapse
-
#annotations ⇒ Object
readonly
Returns the value of attribute annotations.
-
#body ⇒ Object
readonly
Returns the value of attribute body.
-
#body_start_line ⇒ Object
readonly
Returns the value of attribute body_start_line.
-
#composer ⇒ Object
readonly
Returns the value of attribute composer.
-
#key_signature ⇒ Object
readonly
Returns the value of attribute key_signature.
-
#meter ⇒ Object
readonly
Returns the value of attribute meter.
-
#origin ⇒ Object
readonly
Returns the value of attribute origin.
-
#reference_number ⇒ Object
readonly
Returns the value of attribute reference_number.
-
#title ⇒ Object
readonly
Returns the value of attribute title.
-
#voice_ids ⇒ Object
readonly
Returns the value of attribute voice_ids.
Instance Method Summary collapse
-
#apply_defaults ⇒ Object
private
-
#assign_field(stripped_line, line_number) ⇒ Object
private
Returns true when the field is K:, which terminates the header.
-
#assign_key(value, line_number) ⇒ Object
private
-
#capture_body(lines, key_line_index) ⇒ Object
private
-
#default_unit_note_length ⇒ Object
private
ABC 2.1 default: when L: is absent, meters smaller than 3/4 imply sixteenth notes; 3/4 and larger imply eighth notes.
-
#ensure_fraction!(value, description, line_number) ⇒ Object
private
-
#initialize(abc_string, start_line: 1) ⇒ Header
constructor
start_line offsets reported line numbers, so a tune parsed out of a larger book raises errors with book-relative line numbers.
-
#parse(abc_string) ⇒ Object
private
-
#parse_field(stripped_line, line_number) ⇒ Object
private
-
#raise_missing_key_error ⇒ Object
private
-
#resolve_meter(value, line_number) ⇒ Object
private
-
#resolve_unit_note_length(value, line_number) ⇒ Object
private
-
#store_field(letter, value, line_number, stripped_line) ⇒ Object
private
-
#unit_note_length ⇒ Object
Constructor Details
#initialize(abc_string, start_line: 1) ⇒ Header
start_line offsets reported line numbers, so a tune parsed out of a larger book raises errors with book-relative line numbers.
16 17 18 19 20 21 |
# File 'lib/head_music/notation/abc/header.rb', line 16 def initialize(abc_string, start_line: 1) @annotations = [] @voice_ids = [] @start_line = start_line parse(abc_string) end |
Instance Attribute Details
#annotations ⇒ Object (readonly)
Returns the value of attribute annotations.
10 11 12 |
# File 'lib/head_music/notation/abc/header.rb', line 10 def annotations @annotations end |
#body ⇒ Object (readonly)
Returns the value of attribute body.
10 11 12 |
# File 'lib/head_music/notation/abc/header.rb', line 10 def body @body end |
#body_start_line ⇒ Object (readonly)
Returns the value of attribute body_start_line.
10 11 12 |
# File 'lib/head_music/notation/abc/header.rb', line 10 def body_start_line @body_start_line end |
#composer ⇒ Object (readonly)
Returns the value of attribute composer.
10 11 12 |
# File 'lib/head_music/notation/abc/header.rb', line 10 def composer @composer end |
#key_signature ⇒ Object (readonly)
Returns the value of attribute key_signature.
10 11 12 |
# File 'lib/head_music/notation/abc/header.rb', line 10 def key_signature @key_signature end |
#meter ⇒ Object (readonly)
Returns the value of attribute meter.
10 11 12 |
# File 'lib/head_music/notation/abc/header.rb', line 10 def meter @meter end |
#origin ⇒ Object (readonly)
Returns the value of attribute origin.
10 11 12 |
# File 'lib/head_music/notation/abc/header.rb', line 10 def origin @origin end |
#reference_number ⇒ Object (readonly)
Returns the value of attribute reference_number.
10 11 12 |
# File 'lib/head_music/notation/abc/header.rb', line 10 def reference_number @reference_number end |
#title ⇒ Object (readonly)
Returns the value of attribute title.
10 11 12 |
# File 'lib/head_music/notation/abc/header.rb', line 10 def title @title end |
#voice_ids ⇒ Object (readonly)
Returns the value of attribute voice_ids.
10 11 12 |
# File 'lib/head_music/notation/abc/header.rb', line 10 def voice_ids @voice_ids end |
Instance Method Details
#apply_defaults ⇒ Object (private)
115 116 117 |
# File 'lib/head_music/notation/abc/header.rb', line 115 def apply_defaults @meter ||= HeadMusic::Rudiment::Meter.common_time end |
#assign_field(stripped_line, line_number) ⇒ Object (private)
Returns true when the field is K:, which terminates the header.
46 47 48 49 50 51 52 |
# File 'lib/head_music/notation/abc/header.rb', line 46 def assign_field(stripped_line, line_number) letter, value = parse_field(stripped_line, line_number) return assign_key(value, line_number) if letter == "K" store_field(letter, value, line_number, stripped_line) false end |
#assign_key(value, line_number) ⇒ Object (private)
65 66 67 68 |
# File 'lib/head_music/notation/abc/header.rb', line 65 def assign_key(value, line_number) @key_signature = HeadMusic::Notation::ABC::KeyMapper.new(value, line_number: line_number).key_signature true end |
#capture_body(lines, key_line_index) ⇒ Object (private)
87 88 89 90 |
# File 'lib/head_music/notation/abc/header.rb', line 87 def capture_body(lines, key_line_index) @body = lines[(key_line_index + 1)..].join @body_start_line = key_line_index + 1 + @start_line end |
#default_unit_note_length ⇒ Object (private)
ABC 2.1 default: when L: is absent, meters smaller than 3/4 imply sixteenth notes; 3/4 and larger imply eighth notes.
121 122 123 124 |
# File 'lib/head_music/notation/abc/header.rb', line 121 def default_unit_note_length meter_fraction = Rational(meter.top_number, meter.bottom_number) (meter_fraction < Rational(3, 4)) ? Rational(1, 16) : Rational(1, 8) end |
#ensure_fraction!(value, description, line_number) ⇒ Object (private)
107 108 109 110 111 112 113 |
# File 'lib/head_music/notation/abc/header.rb', line 107 def ensure_fraction!(value, description, line_number) return if FRACTION_PATTERN.match?(value) raise HeadMusic::Notation::ABC::ParseError.new( "Invalid #{description} #{value.inspect}", line_number: line_number, snippet: value ) end |
#parse(abc_string) ⇒ Object (private)
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/head_music/notation/abc/header.rb', line 29 def parse(abc_string) lines = abc_string.to_s.lines lines.each_with_index do |raw_line, index| line_number = index + @start_line stripped = raw_line.strip next if stripped.empty? || stripped.start_with?("%") if assign_field(stripped, line_number) capture_body(lines, index) break end end raise_missing_key_error unless @key_signature apply_defaults end |
#parse_field(stripped_line, line_number) ⇒ Object (private)
54 55 56 57 58 59 60 61 62 63 |
# File 'lib/head_music/notation/abc/header.rb', line 54 def parse_field(stripped_line, line_number) match = FIELD_PATTERN.match(stripped_line) unless match raise HeadMusic::Notation::ABC::ParseError.new( "Expected a header field; the tune body may not begin before the K: (key) field", line_number: line_number, snippet: stripped_line ) end [match[1], match[2].strip] end |
#raise_missing_key_error ⇒ Object (private)
126 127 128 129 |
# File 'lib/head_music/notation/abc/header.rb', line 126 def raise_missing_key_error raise HeadMusic::Notation::ABC::ParseError, "Missing required K: (key) field" end |
#resolve_meter(value, line_number) ⇒ Object (private)
92 93 94 95 96 97 98 99 100 |
# File 'lib/head_music/notation/abc/header.rb', line 92 def resolve_meter(value, line_number) # "C" and "C|" must be translated before calling Meter.get, which # memoizes by identifier and would cache a meaningless "C" meter. return HeadMusic::Rudiment::Meter.common_time if value == "C" return HeadMusic::Rudiment::Meter.cut_time if value == "C|" ensure_fraction!(value, "meter", line_number) HeadMusic::Rudiment::Meter.get(value) end |
#resolve_unit_note_length(value, line_number) ⇒ Object (private)
102 103 104 105 |
# File 'lib/head_music/notation/abc/header.rb', line 102 def resolve_unit_note_length(value, line_number) ensure_fraction!(value, "unit note length", line_number) Rational(value) end |
#store_field(letter, value, line_number, stripped_line) ⇒ Object (private)
70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/head_music/notation/abc/header.rb', line 70 def store_field(letter, value, line_number, stripped_line) case letter when "X" then @reference_number = value when "T" then @title = value when "C" then @composer = value when "O" then @origin = value when "N" then @annotations << value when "M" then @meter = resolve_meter(value, line_number) when "L" then @unit_note_length = resolve_unit_note_length(value, line_number) when "V" then @voice_ids << value.split.first else raise HeadMusic::Notation::ABC::UnsupportedFeatureError.new( "Unsupported header field #{letter.inspect}", line_number: line_number, snippet: stripped_line ) end end |
#unit_note_length ⇒ Object
23 24 25 |
# File 'lib/head_music/notation/abc/header.rb', line 23 def unit_note_length @unit_note_length || default_unit_note_length end |