Class: HeadMusic::Notation::ABC::DurationResolver
- Inherits:
-
Object
- Object
- HeadMusic::Notation::ABC::DurationResolver
- Defined in:
- lib/head_music/notation/abc/duration_resolver.rb
Overview
Converts the tune’s unit note length and a per-note multiplier string (e.g. “2”, “3/2”, “/”, “//”) into a HeadMusic::Rudiment::RhythmicValue.
Constant Summary collapse
- MAX_FRACTION =
Longest supported duration: a maxima (8 whole notes).
Rational(8)
- DOTS_BY_ODD_FACTOR =
A reduced binary fraction’s odd factor determines the dot count: 1 -> plain, 3 -> dotted, 7 -> double-dotted, 15 -> triple-dotted.
{1 => 0, 3 => 1, 7 => 2, 15 => 3}.freeze
- UNIT_NAMES_BY_MULTIPLE =
{1 => "whole", 2 => "double whole", 4 => "longa", 8 => "maxima"}.freeze
- MULTIPLIER_PATTERN =
%r{\A(\d+)?(?:(/+)(\d+)?)?\z}
Instance Attribute Summary collapse
-
#unit_note_length ⇒ Object
readonly
Returns the value of attribute unit_note_length.
Instance Method Summary collapse
-
#build_rhythmic_value(fraction, source) ⇒ Object
private
Fractions whose odd factor is one less than a power of two map onto a single (possibly dotted) note; anything else becomes a chain of tied notes, peeling off the largest dotted-expressible head each pass.
-
#explicit_ratio(numerator, slashes, denominator, source) ⇒ Object
private
-
#greedy_head(fraction) ⇒ Object
private
The largest leading run of set bits (capped at four, i.e. triple-dotted) forms a dotted-expressible head for the tied-value decomposition.
-
#initialize(unit_note_length) ⇒ DurationResolver
constructor
A new instance of DurationResolver.
-
#leading_set_bits(numerator, bits) ⇒ Object
private
Length of the leading run of set bits, capped at four (triple-dotted).
-
#length_fraction(multiplier_string) ⇒ Object
The bare fraction a length string denotes (e.g. “2” -> 2, “/2” -> 1/2, “” -> 1), without the unit note length applied.
-
#multiplier(multiplier_string) ⇒ Object
private
-
#odd_factor(integer) ⇒ Object
private
-
#power_of_two?(integer) ⇒ Boolean
private
-
#raise_error(message, source) ⇒ Object
private
-
#rhythmic_value(multiplier_string, scale: Rational(1)) ⇒ Object
scale: an extra multiplier applied outside the note’s own length string, used for broken-rhythm pairs (3/2 and 1/2).
-
#single_value(fraction, dots, source, tied_value: nil) ⇒ Object
private
-
#unit_for(unit_fraction, source) ⇒ Object
private
-
#validate_fraction!(fraction, source) ⇒ Object
private
Constructor Details
#initialize(unit_note_length) ⇒ DurationResolver
Returns a new instance of DurationResolver.
19 20 21 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 19 def initialize(unit_note_length) @unit_note_length = Rational(unit_note_length) end |
Instance Attribute Details
#unit_note_length ⇒ Object (readonly)
Returns the value of attribute unit_note_length.
17 18 19 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 17 def unit_note_length @unit_note_length end |
Instance Method Details
#build_rhythmic_value(fraction, source) ⇒ Object (private)
Fractions whose odd factor is one less than a power of two map onto a single (possibly dotted) note; anything else becomes a chain of tied notes, peeling off the largest dotted-expressible head each pass.
72 73 74 75 76 77 78 79 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 72 def build_rhythmic_value(fraction, source) dots = DOTS_BY_ODD_FACTOR[odd_factor(fraction.numerator)] return single_value(fraction, dots, source) if dots head = greedy_head(fraction) tail = build_rhythmic_value(fraction - head, source) single_value(head, DOTS_BY_ODD_FACTOR.fetch(odd_factor(head.numerator)), source, tied_value: tail) end |
#explicit_ratio(numerator, slashes, denominator, source) ⇒ Object (private)
54 55 56 57 58 59 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 54 def explicit_ratio(numerator, slashes, denominator, source) # An explicit denominator pairs with exactly one slash ("3/2", not "3//2"). raise_error("malformed note length multiplier", source) if slashes.length > 1 raise_error("note length denominator cannot be zero", source) if denominator.to_i.zero? Rational(numerator, denominator.to_i) end |
#greedy_head(fraction) ⇒ Object (private)
The largest leading run of set bits (capped at four, i.e. triple-dotted) forms a dotted-expressible head for the tied-value decomposition.
89 90 91 92 93 94 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 89 def greedy_head(fraction) numerator = fraction.numerator bits = numerator.bit_length run = leading_set_bits(numerator, bits) Rational(((1 << run) - 1) << (bits - run), fraction.denominator) end |
#leading_set_bits(numerator, bits) ⇒ Object (private)
Length of the leading run of set bits, capped at four (triple-dotted).
97 98 99 100 101 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 97 def leading_set_bits(numerator, bits) run = 0 run += 1 while run < 4 && run < bits && numerator[bits - 1 - run] == 1 run end |
#length_fraction(multiplier_string) ⇒ Object
The bare fraction a length string denotes (e.g. “2” -> 2, “/2” -> 1/2, “” -> 1), without the unit note length applied. Used to compare a chord’s per-note lengths for uniformity.
34 35 36 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 34 def length_fraction(multiplier_string) multiplier(multiplier_string) end |
#multiplier(multiplier_string) ⇒ Object (private)
40 41 42 43 44 45 46 47 48 49 50 51 52 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 40 def multiplier(multiplier_string) source = multiplier_string.to_s match = MULTIPLIER_PATTERN.match(source) raise_error("malformed note length multiplier", source) unless match numerator = (match[1] || 1).to_i slashes = match[2] denominator = match[3] return Rational(numerator) unless slashes return Rational(numerator, 2**slashes.length) unless denominator explicit_ratio(numerator, slashes, denominator, source) end |
#odd_factor(integer) ⇒ Object (private)
114 115 116 117 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 114 def odd_factor(integer) integer >>= 1 while integer.even? integer end |
#power_of_two?(integer) ⇒ Boolean (private)
119 120 121 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 119 def power_of_two?(integer) (integer & (integer - 1)).zero? end |
#raise_error(message, source) ⇒ Object (private)
123 124 125 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 123 def raise_error(, source) raise HeadMusic::Notation::ABC::ParseError.new(, snippet: source) end |
#rhythmic_value(multiplier_string, scale: Rational(1)) ⇒ Object
scale: an extra multiplier applied outside the note’s own length string, used for broken-rhythm pairs (3/2 and 1/2).
25 26 27 28 29 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 25 def rhythmic_value(multiplier_string, scale: Rational(1)) fraction = unit_note_length * multiplier(multiplier_string) * scale validate_fraction!(fraction, multiplier_string) build_rhythmic_value(fraction, multiplier_string) end |
#single_value(fraction, dots, source, tied_value: nil) ⇒ Object (private)
81 82 83 84 85 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 81 def single_value(fraction, dots, source, tied_value: nil) # A value with d dots spans (2^(d+1) - 1) / 2^d of its unit. unit_fraction = fraction * Rational(2**dots, (2**(dots + 1)) - 1) HeadMusic::Rudiment::RhythmicValue.new(unit_for(unit_fraction, source), dots: dots, tied_value: tied_value) end |
#unit_for(unit_fraction, source) ⇒ Object (private)
103 104 105 106 107 108 109 110 111 112 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 103 def unit_for(unit_fraction, source) unit = if unit_fraction >= 1 name = UNIT_NAMES_BY_MULTIPLE[unit_fraction.numerator] name && HeadMusic::Rudiment::RhythmicUnit.get(name) else HeadMusic::Rudiment::RhythmicUnit.for_denominator_value(unit_fraction.denominator) end raise_error("no rhythmic unit for a note length of #{unit_fraction}", source) unless unit unit end |
#validate_fraction!(fraction, source) ⇒ Object (private)
61 62 63 64 65 66 67 |
# File 'lib/head_music/notation/abc/duration_resolver.rb', line 61 def validate_fraction!(fraction, source) raise_error("note length must be positive", source) if fraction <= 0 raise_error("note length exceeds #{MAX_FRACTION.to_i} whole notes", source) if fraction > MAX_FRACTION return if power_of_two?(fraction.denominator) raise_error("note length #{fraction} is not expressible in binary note values", source) end |