Class: HeadMusic::Notation::LilyPond::DurationReader

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/notation/lily_pond/duration_reader.rb

Overview

Converts a token's duration text into a rhythmic value, carrying the last explicit duration forward for notes that omit theirs. LilyPond's carry-over is global to the input rather than per voice, so one reader serves a whole parse.

Constant Summary collapse

DEFAULT_DURATION =
"4"
MAX_DOTS =
3

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize ⇒ DurationReader

Returns a new instance of DurationReader.



17
18
19
# File 'lib/head_music/notation/lily_pond/duration_reader.rb', line 17

def initialize
  @last_duration = DEFAULT_DURATION
end

Class Method Details

.unit_names_by_duration ⇒ Object

The inverse of the writer's table, built lazily because the reader loads before the writer.



13
14
15
# File 'lib/head_music/notation/lily_pond/duration_reader.rb', line 13

def self.unit_names_by_duration
  @unit_names_by_duration ||= DurationWriter::DURATIONS_BY_UNIT_NAME.invert.freeze
end

Instance Method Details

#build(duration, token) ⇒ Object (private)



44
45
46
47
48
49
50
51
52
53
# File 'lib/head_music/notation/lily_pond/duration_reader.rb', line 44

def build(duration, token)
  base = duration.delete(".")
  dots = duration.count(".")
  unit_name = self.class.unit_names_by_duration.fetch(base) do
    raise error(%(Unrecognized duration "#{base}"), token)
  end
  raise error("Too many dots in #{duration}", token) if dots > MAX_DOTS

  HeadMusic::Rudiment::RhythmicValue.new(unit_name, dots: dots)
end

#error(message, token) ⇒ Object (private)



63
64
65
# File 'lib/head_music/notation/lily_pond/duration_reader.rb', line 63

def error(message, token)
  ParseError.new(message, line_number: token.line, column: token.column, snippet: token.lexeme)
end

#multiplier(token) ⇒ Object (private)



55
56
57
58
59
60
61
# File 'lib/head_music/notation/lily_pond/duration_reader.rb', line 55

def multiplier(token)
  numerator, denominator = token.multiplier.split("/").map(&:to_i)
  denominator ||= 1
  raise error("Zero denominator in duration multiplier", token) if denominator.zero?

  Rational(numerator, denominator)
end

#rhythmic_value(token) ⇒ Object



21
22
23
24
25
26
27
28
29
30
# File 'lib/head_music/notation/lily_pond/duration_reader.rb', line 21

def rhythmic_value(token)
  duration = token.duration
  if duration
    value = build(duration, token)
    @last_duration = duration
    value
  else
    build(@last_duration, token)
  end
end

#whole_bar_fraction(token) ⇒ Object

The fraction of a whole note a whole-bar rest spans: its duration times any multiplier (R1*3/4 spans three quarters).



34
35
36
37
38
39
40
# File 'lib/head_music/notation/lily_pond/duration_reader.rb', line 34

def whole_bar_fraction(token)
  fraction = HeadMusic::Notation::DottedDuration.dotted_unit_fraction(rhythmic_value(token))
  fraction *= multiplier(token) if token.multiplier
  raise error("A whole-bar rest must span a positive duration", token) unless fraction.positive?

  fraction
end