Class: HeadMusic::Instruments::Instrument

Inherits:
Object
  • Object
show all
Includes:
Named
Defined in:
lib/head_music/instruments/instrument.rb

Overview

A musical instrument with parent-based inheritance.

Instruments can inherit from parent instruments, allowing for a clean hierarchy where child instruments override specific attributes while inheriting others from their parents.

Examples: trumpet = HeadMusic::Instruments::Instrument.get(“trumpet”) clarinet_in_a = HeadMusic::Instruments::Instrument.get(“clarinet_in_a”) clarinet_in_a.parent # => clarinet clarinet_in_a.pitch_key # => “a” (own attribute) clarinet_in_a.family_key # => “clarinet” (inherited from parent)

Attributes: name_key: the primary identifier for the instrument parent_key: optional key referencing the parent instrument family_key: the instrument family (e.g., “clarinet”, “trumpet”) pitch_key: the pitch designation (e.g., “b_flat”, “a”, “c”) alias_name_keys: alternative names for the instrument range_categories: size/range classifications

Constant Summary collapse

INSTRUMENTS =
YAML.load_file(File.expand_path("instruments.yml", __dir__)).freeze
VARIANT_PATTERN =

Convert shorthand variant names to full form e.g., “trumpet_in_eb” -> “trumpet_in_e_flat” e.g., “clarinet_in_bb” -> “clarinet_in_b_flat”

/^(.+)_in_([a-g])([b#])$/i

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name) ⇒ Instrument (private)

Returns a new instance of Instrument.



181
182
183
184
185
186
187
188
189
190
# File 'lib/head_music/instruments/instrument.rb', line 181

def initialize(name)
  record = HeadMusic::Instruments::InstrumentCatalog.new(INSTRUMENTS).record_for(name)
  if record
    initialize_data_from_record(record)
  else
    # Mark as invalid - will be filtered out by get_by_name
    @name_key = nil
    self.name = name.to_s
  end
end

Instance Attribute Details

#alias_name_keysObject (readonly)

Returns the value of attribute alias_name_keys.



29
30
31
# File 'lib/head_music/instruments/instrument.rb', line 29

def alias_name_keys
  @alias_name_keys
end

#alias_name_keysObject (readonly) Originally defined in module Named

Returns the value of attribute alias_name_keys.

#name_keyObject (readonly)

Returns the value of attribute name_key.



29
30
31
# File 'lib/head_music/instruments/instrument.rb', line 29

def name_key
  @name_key
end

#name_keyObject (readonly) Originally defined in module Named

Returns the value of attribute name_key.

#parent_keyObject (readonly)

Returns the value of attribute parent_key.



29
30
31
# File 'lib/head_music/instruments/instrument.rb', line 29

def parent_key
  @parent_key
end

#range_categoriesObject (readonly)

Returns the value of attribute range_categories.



29
30
31
# File 'lib/head_music/instruments/instrument.rb', line 29

def range_categories
  @range_categories
end

Class Method Details

.allObject



52
53
54
55
56
# File 'lib/head_music/instruments/instrument.rb', line 52

def all
  HeadMusic::Instruments::InstrumentFamily.all # Ensure families are loaded first
  INSTRUMENTS.map { |key, _data| get(key) }
  @all ||= @instances.values.compact.sort_by { |instrument| instrument.name.downcase }
end

.find_valid_instrument(name) ⇒ Object



47
48
49
50
# File 'lib/head_music/instruments/instrument.rb', line 47

def find_valid_instrument(name)
  instrument = get_by_name(name)
  instrument&.name_key ? instrument : nil
end

.get(name, variant_key = nil) ⇒ Instrument?

Factory method to get an Instrument instance

Parameters:

  • name (String, Symbol)

    instrument name (e.g., “clarinet”, “clarinet_in_a”)

  • variant_key (String, Symbol, nil) (defaults to: nil)

    DEPRECATED: variant key (for backward compatibility)

Returns:

  • (Instrument, nil)

    instrument instance or nil if not found



36
37
38
39
40
41
42
43
44
45
# File 'lib/head_music/instruments/instrument.rb', line 36

def get(name, variant_key = nil)
  return name if name.is_a?(self)

  name_str = name.to_s
  if variant_key
    find_valid_instrument("#{name_str}_#{variant_key}") || find_valid_instrument(name_str)
  else
    find_valid_instrument(name_str) || find_valid_instrument(normalize_variant_name(name_str))
  end
end

.normalize_variant_name(name_str) ⇒ Object (private)



65
66
67
68
69
70
71
# File 'lib/head_music/instruments/instrument.rb', line 65

def normalize_variant_name(name_str)
  match = VARIANT_PATTERN.match(name_str.to_s)
  return name_str.to_s unless match

  suffix = (match[3] == "b") ? "flat" : "sharp"
  "#{match[1]}_in_#{match[2].downcase}_#{suffix}"
end

Instance Method Details

#==(other) ⇒ Object



140
141
142
143
144
# File 'lib/head_music/instruments/instrument.rb', line 140

def ==(other)
  return false unless other.is_a?(self.class)

  name_key == other.name_key
end

#alternate_tuningsObject



170
171
172
173
174
175
# File 'lib/head_music/instruments/instrument.rb', line 170

def alternate_tunings
  own_tunings = HeadMusic::Instruments::AlternateTuning.for_instrument(name_key)
  return own_tunings if own_tunings.any?

  parent&.alternate_tunings || []
end

#classification_keysObject



101
102
103
# File 'lib/head_music/instruments/instrument.rb', line 101

def classification_keys
  family&.classification_keys || []
end

#default_variantObject



155
156
157
# File 'lib/head_music/instruments/instrument.rb', line 155

def default_variant
  nil
end

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

#familyObject



91
92
93
94
95
# File 'lib/head_music/instruments/instrument.rb', line 91

def family
  return unless family_key

  HeadMusic::Instruments::InstrumentFamily.get(family_key)
end

#family_keyObject

Attributes with parent chain resolution



83
84
85
# File 'lib/head_music/instruments/instrument.rb', line 83

def family_key
  @family_key || parent&.family_key
end

#initialize_data_from_record(record) ⇒ Object (private)



192
193
194
195
196
197
198
199
200
201
# File 'lib/head_music/instruments/instrument.rb', line 192

def initialize_data_from_record(record)
  @name_key = record["name_key"].to_sym
  @parent_key = record["parent_key"]&.to_sym
  @family_key = record["family_key"]
  @pitch_key = record["pitch_key"]
  @alias_name_keys = record["alias_name_keys"] || []
  @range_categories = record["range_categories"] || []

  initialize_name
end

#initialize_nameObject (private)



203
204
205
206
207
# File 'lib/head_music/instruments/instrument.rb', line 203

def initialize_name
  self.name = HeadMusic::Instruments::InstrumentName.new(
    name_key: name_key, parent_key: parent_key, pitch_designation: pitch_key_to_designation
  ).to_s
end

#instrument_configurationsObject

Collect all instrument_configurations from self and ancestors



160
161
162
163
164
# File 'lib/head_music/instruments/instrument.rb', line 160

def instrument_configurations
  own_configs = HeadMusic::Instruments::InstrumentConfiguration.for_instrument(name_key)
  parent_configs = parent&.instrument_configurations || []
  own_configs + parent_configs
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.

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

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

#notation(style: :default) ⇒ Object

Notation for this instrument in the given style (defaults to :default).



113
114
115
# File 'lib/head_music/instruments/instrument.rb', line 113

def notation(style: :default)
  HeadMusic::Notation::NotationStyle.get(style).notation_for(self)
end

#orchestra_section_keyObject



97
98
99
# File 'lib/head_music/instruments/instrument.rb', line 97

def orchestra_section_key
  family&.orchestra_section_key
end

#parentObject

Parent instrument (for inheritance)



75
76
77
78
79
# File 'lib/head_music/instruments/instrument.rb', line 75

def parent
  return nil unless parent_key

  @parent ||= self.class.get(parent_key)
end

#pitch_designationObject

Pitch designation as a Spelling object (for backward compatibility)



106
107
108
109
110
# File 'lib/head_music/instruments/instrument.rb', line 106

def pitch_designation
  return nil unless pitch_key

  @pitch_designation ||= HeadMusic::Rudiment::Spelling.get(pitch_key_to_designation)
end

#pitch_keyObject



87
88
89
# File 'lib/head_music/instruments/instrument.rb', line 87

def pitch_key
  @pitch_key || parent&.pitch_key
end

#pitch_key_to_designationObject (private)

Convert pitch_key (e.g., “b_flat”) to designation format (e.g., “Bb”)



210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/head_music/instruments/instrument.rb', line 210

def pitch_key_to_designation
  return nil unless pitch_key

  pitch_key_str = pitch_key.to_s
  first_letter = pitch_key_str[0].upcase
  if pitch_key_str.end_with?("_flat")
    "#{first_letter}b"
  elsif pitch_key_str.end_with?("_sharp")
    "#{first_letter}#"
  else
    pitch_key_str.upcase
  end
end

#staff_profileObject



130
131
132
# File 'lib/head_music/instruments/instrument.rb', line 130

def staff_profile
  @staff_profile ||= HeadMusic::Instruments::StaffProfile.new(self)
end

#stringingObject



166
167
168
# File 'lib/head_music/instruments/instrument.rb', line 166

def stringing
  @stringing ||= HeadMusic::Instruments::Stringing.for_instrument(self) || parent&.stringing
end

#to_sObject



146
147
148
# File 'lib/head_music/instruments/instrument.rb', line 146

def to_s
  name
end

#translation(locale = :en) ⇒ Object



134
135
136
137
138
# File 'lib/head_music/instruments/instrument.rb', line 134

def translation(locale = :en)
  return name unless name_key

  HeadMusic::Instruments::InstrumentName.translate(name_key, locale: locale, default: name)
end

#variantsObject

For backward compatibility with code that expects variants



151
152
153
# File 'lib/head_music/instruments/instrument.rb', line 151

def variants
  []
end