Class: HeadMusic::Notation::ABC::DurationWriter

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/notation/abc/duration_writer.rb

Overview

Converts a HeadMusic::Rudiment::RhythmicValue back into the ABC note-length multiplier string relative to the tune’s unit note length — the inverse of DurationResolver.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(unit_note_length) ⇒ DurationWriter

Returns a new instance of DurationWriter.



9
10
11
# File 'lib/head_music/notation/abc/duration_writer.rb', line 9

def initialize(unit_note_length)
  @unit_note_length = Rational(unit_note_length)
end

Instance Attribute Details

#unit_note_lengthObject (readonly)

Returns the value of attribute unit_note_length.



7
8
9
# File 'lib/head_music/notation/abc/duration_writer.rb', line 7

def unit_note_length
  @unit_note_length
end

Instance Method Details

#format_multiplier(multiplier) ⇒ Object (private)



48
49
50
51
52
53
# File 'lib/head_music/notation/abc/duration_writer.rb', line 48

def format_multiplier(multiplier)
  return "" if multiplier == 1
  return multiplier.numerator.to_s if multiplier.denominator == 1

  "#{multiplier.numerator}/#{multiplier.denominator}"
end

#multiplier_string(rhythmic_value) ⇒ Object



13
14
15
16
17
# File 'lib/head_music/notation/abc/duration_writer.rb', line 13

def multiplier_string(rhythmic_value)
  fraction = total_fraction(rhythmic_value)
  validate_fraction!(fraction, rhythmic_value)
  format_multiplier(fraction / unit_note_length)
end

#power_of_two?(integer) ⇒ Boolean (private)

Returns:

  • (Boolean)


55
56
57
# File 'lib/head_music/notation/abc/duration_writer.rb', line 55

def power_of_two?(integer)
  (integer & (integer - 1)).zero?
end

#raise_error(message, rhythmic_value) ⇒ Object (private)



59
60
61
# File 'lib/head_music/notation/abc/duration_writer.rb', line 59

def raise_error(message, rhythmic_value)
  raise HeadMusic::Notation::ABC::RenderError, "#{message}: #{rhythmic_value}"
end

#single_fraction(rhythmic_value) ⇒ Object (private)



31
32
33
34
35
36
# File 'lib/head_music/notation/abc/duration_writer.rb', line 31

def single_fraction(rhythmic_value)
  unit = rhythmic_value.unit
  dots = rhythmic_value.dots
  # A value with d dots spans (2^(d+1) - 1) / 2^d of its unit.
  Rational(unit.numerator, unit.denominator) * Rational((2**(dots + 1)) - 1, 2**dots)
end

#total_fraction(rhythmic_value) ⇒ Object (private)

RhythmicValue’s own value methods return Floats; rebuild the fraction from integer parts so the multiplier arithmetic stays exact. A tied chain collapses to one multiplier, round-tripping tokens like “A5”.



24
25
26
27
28
29
# File 'lib/head_music/notation/abc/duration_writer.rb', line 24

def total_fraction(rhythmic_value)
  fraction = single_fraction(rhythmic_value)
  tied_value = rhythmic_value.tied_value
  fraction += total_fraction(tied_value) if tied_value
  fraction
end

#validate_fraction!(fraction, rhythmic_value) ⇒ Object (private)



38
39
40
41
42
43
44
45
46
# File 'lib/head_music/notation/abc/duration_writer.rb', line 38

def validate_fraction!(fraction, rhythmic_value)
  max_fraction = DurationResolver::MAX_FRACTION
  if fraction > max_fraction
    raise_error("note length exceeds #{max_fraction.to_i} whole notes", rhythmic_value)
  end
  return if power_of_two?(fraction.denominator)

  raise_error("note length #{fraction} is not expressible in binary note values", rhythmic_value)
end