Class: HeadMusic::Rudiment::RhythmicUnit

Inherits:
Base
  • Object
show all
Includes:
Comparable, Named
Defined in:
lib/head_music/rudiment/rhythmic_unit.rb

Overview

A rhythmic unit is a rudiment of duration consisting of doublings and divisions of a whole note.

Defined Under Namespace

Classes: Parser

Constant Summary collapse

RHYTHMIC_UNITS_DATA =
YAML.load_file(File.expand_path("rhythmic_units.yml", __dir__)).freeze
AMERICAN_MULTIPLES_NAMES =
[
  "whole", "double whole", "longa", "maxima"
].freeze
AMERICAN_DIVISIONS_NAMES =
[
  "whole", "half", "quarter", "eighth", "sixteenth", "thirty-second",
  "sixty-fourth", "hundred twenty-eighth", "two hundred fifty-sixth"
].freeze
AMERICAN_DURATIONS =
(AMERICAN_MULTIPLES_NAMES + AMERICAN_DIVISIONS_NAMES).freeze
PATTERN =
/#{Regexp.union(AMERICAN_DURATIONS)}/i
BRITISH_MULTIPLES_NAMES =

British terminology for note values longer than a whole note

%w[semibreve breve longa maxima].freeze
BRITISH_DIVISIONS_NAMES =

British terminology for standard note divisions

%w[
  semibreve minim crotchet quaver semiquaver demisemiquaver
  hemidemisemiquaver semihemidemisemiquaver demisemihemidemisemiquaver
].freeze
NOTEHEADS =

Notehead symbols used for different note values

{
  maxima: 8.0,
  longa: 4.0,
  breve: 2.0,
  open: [0.5, 1.0],
  closed: :default
}.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(canonical_name) ⇒ RhythmicUnit

Returns a new instance of RhythmicUnit.

Raises:

  • (ArgumentError)


81
82
83
84
85
86
87
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 81

def initialize(canonical_name)
  raise ArgumentError, "Name cannot be nil or empty" if canonical_name.to_s.strip.empty?

  self.name = canonical_name
  @numerator = 2**numerator_exponent
  @denominator = 2**denominator_exponent
end

Instance Attribute Details

#alias_name_keysObject (readonly) Originally defined in module Named

Returns the value of attribute alias_name_keys.

#denominatorObject (readonly)

Returns the value of attribute denominator.



52
53
54
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 52

def denominator
  @denominator
end

#name_keyObject (readonly) Originally defined in module Named

Returns the value of attribute name_key.

#numeratorObject (readonly)

Returns the value of attribute numerator.



52
53
54
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 52

def numerator
  @numerator
end

Class Method Details

.allObject



62
63
64
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 62

def self.all
  @all ||= (AMERICAN_MULTIPLES_NAMES.reverse + AMERICAN_DIVISIONS_NAMES).uniq.map { |name| get(name) }.compact
end

.all_normalized_namesObject



72
73
74
75
76
77
78
79
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 72

def self.all_normalized_names
  @all_normalized_names ||= (
    AMERICAN_MULTIPLES_NAMES.map { |n| normalize_name(n) } +
    AMERICAN_DIVISIONS_NAMES.map { |n| normalize_name(n) } +
    BRITISH_MULTIPLES_NAMES.map { |n| normalize_name(n) } +
    BRITISH_DIVISIONS_NAMES.map { |n| normalize_name(n) }
  ).uniq
end

.for_denominator_value(denominator) ⇒ Object



42
43
44
45
46
47
48
49
50
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 42

def self.for_denominator_value(denominator)
  return nil unless denominator.is_a?(Numeric) && denominator > 0
  return nil unless (denominator & (denominator - 1)) == 0  # Check if power of 2

  index = Math.log2(denominator).to_i
  return nil if index >= AMERICAN_DIVISIONS_NAMES.length

  get(AMERICAN_DIVISIONS_NAMES[index])
end

.get(name) ⇒ Object



54
55
56
57
58
59
60
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 54

def self.get(name)
  # Use the parser to handle tempo shorthand and other formats
  parsed_name = HeadMusic::Rudiment::RhythmicUnit::Parser.parse(name)
  return nil unless parsed_name

  get_by_name(parsed_name)
end

.normalize_name(name) ⇒ Object



142
143
144
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 142

def self.normalize_name(name)
  name.to_s.gsub(/\W+/, "_")
end

.valid_name?(name) ⇒ Boolean

Check if a name represents a valid rhythmic unit

Returns:

  • (Boolean)


67
68
69
70
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 67

def self.valid_name?(name)
  normalized = normalize_name(name)
  all_normalized_names.include?(normalized)
end

Instance Method Details

#<=>(other) ⇒ Object



120
121
122
123
124
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 120

def <=>(other)
  return nil unless other.is_a?(self.class)

  relative_value <=> other.relative_value
end

#british_fractions_keysObject (private)



178
179
180
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 178

def british_fractions_keys
  BRITISH_DIVISIONS_NAMES.map { |fraction| self.class.normalize_name(fraction) }
end

#british_multiples_keysObject (private)



165
166
167
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 165

def british_multiples_keys
  BRITISH_MULTIPLES_NAMES.map { |multiple| self.class.normalize_name(multiple) }
end

#british_nameObject



126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 126

def british_name
  if has_american_multiple_name?
    index = AMERICAN_MULTIPLES_NAMES.index(name)
    return BRITISH_MULTIPLES_NAMES[index] unless index.nil?
  elsif has_american_division_name?
    index = AMERICAN_DIVISIONS_NAMES.index(name)
    return BRITISH_DIVISIONS_NAMES[index] unless index.nil?
  elsif BRITISH_MULTIPLES_NAMES.include?(name) || BRITISH_DIVISIONS_NAMES.include?(name)
    return name
  end

  nil # Return nil if no British equivalent found
end

#common?Boolean

Returns true if this note value is commonly used in modern notation

Returns:

  • (Boolean)


116
117
118
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 116

def common?
  AMERICAN_DIVISIONS_NAMES[0..6].include?(name) || BRITISH_DIVISIONS_NAMES[0..6].include?(name)
end

#denominator_exponentObject (private)



169
170
171
172
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 169

def denominator_exponent
  normalized_name = self.class.normalize_name(name)
  fractions_keys.index(normalized_name) || british_fractions_keys.index(normalized_name) || 0
end

#ensure_localized_name(name:, locale_code: Locale::DEFAULT_CODE, abbreviation: nil) ⇒ Object Originally defined in module Named

#flagsObject



107
108
109
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 107

def flags
  AMERICAN_DIVISIONS_NAMES.include?(name) ? [AMERICAN_DIVISIONS_NAMES.index(name) - 2, 0].max : 0
end

#fractions_keysObject (private)



174
175
176
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 174

def fractions_keys
  AMERICAN_DIVISIONS_NAMES.map { |fraction| self.class.normalize_name(fraction) }
end

#has_american_division_name?Boolean (private)

Returns:

  • (Boolean)


152
153
154
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 152

def has_american_division_name?
  AMERICAN_DIVISIONS_NAMES.include?(name)
end

#has_american_multiple_name?Boolean (private)

Returns:

  • (Boolean)


148
149
150
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 148

def has_american_multiple_name?
  AMERICAN_MULTIPLES_NAMES.include?(name)
end

#localized_name(locale_code: Locale::DEFAULT_CODE) ⇒ Object Originally defined in module Named

#localized_name_in_default_localeObject (private) Originally defined in module Named

#localized_name_in_locale_matching_language(locale) ⇒ Object (private) Originally defined in module Named

#localized_name_in_matching_locale(locale) ⇒ Object (private) Originally defined in module Named

#localized_namesObject Originally defined in module Named

Returns an array of LocalizedName instances that are synonymous with the name.

#multiples_keysObject (private)



161
162
163
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 161

def multiples_keys
  AMERICAN_MULTIPLES_NAMES.map { |multiple| self.class.normalize_name(multiple) }
end

#name(locale_code: Locale::DEFAULT_CODE) ⇒ Object Originally defined in module Named

#name=(name) ⇒ Object Originally defined in module Named

#noteheadObject



97
98
99
100
101
102
103
104
105
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 97

def notehead
  value = relative_value
  return :maxima if value == NOTEHEADS[:maxima]
  return :longa if value == NOTEHEADS[:longa]
  return :breve if value == NOTEHEADS[:breve]
  return :open if NOTEHEADS[:open].include?(value)

  :closed
end

#numerator_exponentObject (private)



156
157
158
159
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 156

def numerator_exponent
  normalized_name = self.class.normalize_name(name)
  multiples_keys.index(normalized_name) || british_multiples_keys.index(normalized_name) || 0
end

#relative_valueObject



89
90
91
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 89

def relative_value
  @numerator.to_f / @denominator
end

#stemmed?Boolean

Returns:

  • (Boolean)


111
112
113
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 111

def stemmed?
  relative_value < 1
end

#ticksObject



93
94
95
# File 'lib/head_music/rudiment/rhythmic_unit.rb', line 93

def ticks
  (HeadMusic::Rudiment::Rhythm::PPQN * 4 * relative_value).to_i
end