Class: HeadMusic::Instruments::Instrument
- Inherits:
-
Object
- Object
- HeadMusic::Instruments::Instrument
- 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 staff_schemes: notation schemes (to be moved to NotationStyle later)
Constant Summary collapse
- INSTRUMENTS =
YAML.load_file(File.("instruments.yml", __dir__)).freeze
Instance Attribute Summary collapse
-
#alias_name_keys ⇒ Object
included
from Named
readonly
Returns the value of attribute alias_name_keys.
-
#alias_name_keys ⇒ Object
readonly
Returns the value of attribute alias_name_keys.
-
#name_key ⇒ Object
readonly
Returns the value of attribute name_key.
-
#name_key ⇒ Object
included
from Named
readonly
Returns the value of attribute name_key.
-
#parent_key ⇒ Object
readonly
Returns the value of attribute parent_key.
-
#range_categories ⇒ Object
readonly
Returns the value of attribute range_categories.
-
#staff_schemes_data ⇒ Object
readonly
Returns the value of attribute staff_schemes_data.
Class Method Summary collapse
- .all ⇒ Object
- .find_valid_instrument(name) ⇒ Object
-
.get(name, variant_key = nil) ⇒ Instrument?
Factory method to get an Instrument instance.
-
.normalize_variant_name(name) ⇒ Object
private
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".
Instance Method Summary collapse
- #==(other) ⇒ Object
- #alternate_tunings ⇒ Object
- #build_staff_schemes ⇒ Object private
- #classification_keys ⇒ Object
- #default_clefs ⇒ Object
- #default_staff_scheme ⇒ Object
- #default_staves ⇒ Object
- #default_variant ⇒ Object
- #ensure_localized_name(name:, locale_code: Locale::DEFAULT_CODE, abbreviation: nil) ⇒ Object included from Named
- #family ⇒ Object
-
#family_key ⇒ Object
Attributes with parent chain resolution.
- #format_pitch_name(pitch_designation) ⇒ Object private
- #inferred_name ⇒ Object private
-
#initialize(name) ⇒ Instrument
constructor
private
A new instance of Instrument.
- #initialize_data_from_record(record) ⇒ Object private
- #initialize_name ⇒ Object private
-
#instrument_configurations ⇒ Object
Collect all instrument_configurations from self and ancestors.
- #key_for_name(name) ⇒ Object private
- #localized_name(locale_code: Locale::DEFAULT_CODE) ⇒ Object included from Named
- #localized_name_in_default_locale ⇒ Object included from Named private
- #localized_name_in_locale_matching_language(locale) ⇒ Object included from Named private
- #localized_name_in_matching_locale(locale) ⇒ Object included from Named private
-
#localized_names ⇒ Object
included
from Named
Returns an array of LocalizedName instances that are synonymous with the name.
- #multiple_staves? ⇒ Boolean
- #name(locale_code: Locale::DEFAULT_CODE) ⇒ Object included from Named
- #name=(name) ⇒ Object included from Named
- #orchestra_section_key ⇒ Object
-
#parent ⇒ Object
Parent instrument (for inheritance).
- #parent_translation ⇒ Object private
-
#pitch_designation ⇒ Object
Pitch designation as a Spelling object (for backward compatibility).
- #pitch_key ⇒ Object
-
#pitch_key_to_designation ⇒ Object
private
Convert pitch_key (e.g., "b_flat") to designation format (e.g., "Bb").
- #pitched? ⇒ Boolean
- #record_for_alias(name) ⇒ Object private
- #record_for_key(key) ⇒ Object private
- #record_for_name(name) ⇒ Object private
- #single_staff? ⇒ Boolean
- #sounding_transposition ⇒ Object (also: #default_sounding_transposition)
-
#staff_schemes ⇒ Object
Staff schemes (notation concern - kept for backward compatibility).
- #stringing ⇒ Object
- #to_s ⇒ Object
- #translation(locale = :en) ⇒ Object
- #transposing? ⇒ Boolean
- #transposing_at_the_octave? ⇒ Boolean
-
#variants ⇒ Object
For backward compatibility with code that expects variants.
Constructor Details
#initialize(name) ⇒ Instrument (private)
Returns a new instance of Instrument.
218 219 220 221 222 223 224 225 226 227 |
# File 'lib/head_music/instruments/instrument.rb', line 218 def initialize(name) record = record_for_name(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_keys ⇒ Object (readonly) Originally defined in module Named
Returns the value of attribute alias_name_keys.
#alias_name_keys ⇒ Object (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 |
#name_key ⇒ Object (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_key ⇒ Object (readonly) Originally defined in module Named
Returns the value of attribute name_key.
#parent_key ⇒ Object (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_categories ⇒ Object (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 |
#staff_schemes_data ⇒ Object (readonly)
Returns the value of attribute staff_schemes_data.
29 30 31 |
# File 'lib/head_music/instruments/instrument.rb', line 29 def staff_schemes_data @staff_schemes_data end |
Class Method Details
.all ⇒ Object
55 56 57 58 59 |
# File 'lib/head_music/instruments/instrument.rb', line 55 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
50 51 52 53 |
# File 'lib/head_music/instruments/instrument.rb', line 50 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
36 37 38 39 40 41 42 43 44 45 46 47 48 |
# File 'lib/head_music/instruments/instrument.rb', line 36 def get(name, variant_key = nil) return name if name.is_a?(self) # Handle two-argument form for backward compatibility if variant_key combined_name = "#{name}_#{variant_key}" result = find_valid_instrument(combined_name) || find_valid_instrument(name.to_s) else result = find_valid_instrument(name.to_s) || find_valid_instrument(normalize_variant_name(name)) end result end |
.normalize_variant_name(name) ⇒ Object (private)
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"
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/head_music/instruments/instrument.rb', line 66 def normalize_variant_name(name) name_str = name.to_s # Match patterns like "_in_eb" or "_in_bb" at the end (flat) flat_pattern = /^(.+)_in_([a-g])b$/i sharp_pattern = %r{^(.+)_in_([a-g])\#$}i if name_str =~ flat_pattern instrument = Regexp.last_match(1) note = Regexp.last_match(2).downcase "#{instrument}_in_#{note}_flat" elsif name_str =~ sharp_pattern instrument = Regexp.last_match(1) note = Regexp.last_match(2).downcase "#{instrument}_in_#{note}_sharp" else name_str end end |
Instance Method Details
#==(other) ⇒ Object
177 178 179 180 181 |
# File 'lib/head_music/instruments/instrument.rb', line 177 def ==(other) return false unless other.is_a?(self.class) name_key == other.name_key end |
#alternate_tunings ⇒ Object
207 208 209 210 211 212 |
# File 'lib/head_music/instruments/instrument.rb', line 207 def alternate_tunings own_tunings = HeadMusic::Instruments::AlternateTuning.for_instrument(name_key) return own_tunings if own_tunings.any? parent&.alternate_tunings || [] end |
#build_staff_schemes ⇒ Object (private)
319 320 321 322 323 324 325 326 327 328 329 |
# File 'lib/head_music/instruments/instrument.rb', line 319 def build_staff_schemes return parent&.staff_schemes || [] if staff_schemes_data.empty? staff_schemes_data.map do |key, list| HeadMusic::Instruments::StaffScheme.new( key: key, instrument: self, list: list ) end end |
#classification_keys ⇒ Object
114 115 116 |
# File 'lib/head_music/instruments/instrument.rb', line 114 def classification_keys family&.classification_keys || [] end |
#default_clefs ⇒ Object
139 140 141 |
# File 'lib/head_music/instruments/instrument.rb', line 139 def default_clefs default_staves&.map(&:clef) || [] end |
#default_staff_scheme ⇒ Object
130 131 132 133 |
# File 'lib/head_music/instruments/instrument.rb', line 130 def default_staff_scheme @default_staff_scheme ||= staff_schemes.find(&:default?) || staff_schemes.first end |
#default_staves ⇒ Object
135 136 137 |
# File 'lib/head_music/instruments/instrument.rb', line 135 def default_staves default_staff_scheme&.staves || [] end |
#default_variant ⇒ Object
192 193 194 |
# File 'lib/head_music/instruments/instrument.rb', line 192 def default_variant nil end |
#ensure_localized_name(name:, locale_code: Locale::DEFAULT_CODE, abbreviation: nil) ⇒ Object Originally defined in module Named
#family ⇒ Object
104 105 106 107 108 |
# File 'lib/head_music/instruments/instrument.rb', line 104 def family return unless family_key HeadMusic::Instruments::InstrumentFamily.get(family_key) end |
#family_key ⇒ Object
Attributes with parent chain resolution
96 97 98 |
# File 'lib/head_music/instruments/instrument.rb', line 96 def family_key @family_key || parent&.family_key end |
#format_pitch_name(pitch_designation) ⇒ Object (private)
301 302 303 |
# File 'lib/head_music/instruments/instrument.rb', line 301 def format_pitch_name(pitch_designation) pitch_designation.to_s.tr("b", "♭").tr("#", "♯") end |
#inferred_name ⇒ Object (private)
297 298 299 |
# File 'lib/head_music/instruments/instrument.rb', line 297 def inferred_name name_key.to_s.tr("_", " ") end |
#initialize_data_from_record(record) ⇒ Object (private)
262 263 264 265 266 267 268 269 270 271 272 |
# File 'lib/head_music/instruments/instrument.rb', line 262 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"] || [] @staff_schemes_data = record["staff_schemes"] || {} initialize_name end |
#initialize_name ⇒ Object (private)
274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 |
# File 'lib/head_music/instruments/instrument.rb', line 274 def initialize_name # Try to get a translation first base_name = I18n.translate(name_key, scope: "head_music.instruments", locale: "en", default: nil) if base_name # Use the translation as-is self.name = base_name elsif parent_key && pitch_key # Build name from parent + pitch for child instruments pitch_name = format_pitch_name(pitch_key_to_designation) self.name = "#{parent_translation} in #{pitch_name}" else # Fall back to inferred name self.name = inferred_name end end |
#instrument_configurations ⇒ Object
Collect all instrument_configurations from self and ancestors
197 198 199 200 201 |
# File 'lib/head_music/instruments/instrument.rb', line 197 def instrument_configurations own_configs = HeadMusic::Instruments::InstrumentConfiguration.for_instrument(name_key) parent_configs = parent&.instrument_configurations || [] own_configs + parent_configs end |
#key_for_name(name) ⇒ Object (private)
235 236 237 238 239 240 241 242 243 |
# File 'lib/head_music/instruments/instrument.rb', line 235 def key_for_name(name) INSTRUMENTS.each do |key, _data| I18n.config.available_locales.each do |locale| translation = I18n.t("head_music.instruments.#{key}", locale: locale) return key if translation.downcase == name.downcase end end nil end |
#localized_name(locale_code: Locale::DEFAULT_CODE) ⇒ Object Originally defined in module Named
#localized_name_in_default_locale ⇒ Object (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_names ⇒ Object Originally defined in module Named
Returns an array of LocalizedName instances that are synonymous with the name.
#multiple_staves? ⇒ Boolean
161 162 163 |
# File 'lib/head_music/instruments/instrument.rb', line 161 def multiple_staves? default_staves.length > 1 end |
#name(locale_code: Locale::DEFAULT_CODE) ⇒ Object Originally defined in module Named
#name=(name) ⇒ Object Originally defined in module Named
#orchestra_section_key ⇒ Object
110 111 112 |
# File 'lib/head_music/instruments/instrument.rb', line 110 def orchestra_section_key family&.orchestra_section_key end |
#parent ⇒ Object
Parent instrument (for inheritance)
88 89 90 91 92 |
# File 'lib/head_music/instruments/instrument.rb', line 88 def parent return nil unless parent_key @parent ||= self.class.get(parent_key) end |
#parent_translation ⇒ Object (private)
291 292 293 294 295 |
# File 'lib/head_music/instruments/instrument.rb', line 291 def parent_translation return nil unless parent_key I18n.translate(parent_key, scope: "head_music.instruments", locale: "en", default: parent_key.to_s.tr("_", " ")) end |
#pitch_designation ⇒ Object
Pitch designation as a Spelling object (for backward compatibility)
119 120 121 122 123 |
# File 'lib/head_music/instruments/instrument.rb', line 119 def pitch_designation return nil unless pitch_key @pitch_designation ||= HeadMusic::Rudiment::Spelling.get(pitch_key_to_designation) end |
#pitch_key ⇒ Object
100 101 102 |
# File 'lib/head_music/instruments/instrument.rb', line 100 def pitch_key @pitch_key || parent&.pitch_key end |
#pitch_key_to_designation ⇒ Object (private)
Convert pitch_key (e.g., "b_flat") to designation format (e.g., "Bb")
306 307 308 309 310 311 312 313 314 315 316 317 |
# File 'lib/head_music/instruments/instrument.rb', line 306 def pitch_key_to_designation return nil unless pitch_key key = pitch_key.to_s if key.end_with?("_flat") "#{key[0].upcase}b" elsif key.end_with?("_sharp") "#{key[0].upcase}#" else key.upcase end end |
#pitched? ⇒ Boolean
165 166 167 168 169 |
# File 'lib/head_music/instruments/instrument.rb', line 165 def pitched? return false if default_clefs.compact.uniq == [HeadMusic::Rudiment::Clef.get("neutral_clef")] default_clefs.any? end |
#record_for_alias(name) ⇒ Object (private)
252 253 254 255 256 257 258 259 260 |
# File 'lib/head_music/instruments/instrument.rb', line 252 def record_for_alias(name) normalized_name = HeadMusic::Utilities::HashKey.for(name).to_s INSTRUMENTS.each do |name_key, data| data["alias_name_keys"]&.each do |alias_key| return data.merge("name_key" => name_key) if HeadMusic::Utilities::HashKey.for(alias_key).to_s == normalized_name end end nil end |
#record_for_key(key) ⇒ Object (private)
245 246 247 248 249 250 |
# File 'lib/head_music/instruments/instrument.rb', line 245 def record_for_key(key) INSTRUMENTS.each do |name_key, data| return data.merge("name_key" => name_key) if name_key.to_s == key.to_s end nil end |
#record_for_name(name) ⇒ Object (private)
229 230 231 232 233 |
# File 'lib/head_music/instruments/instrument.rb', line 229 def record_for_name(name) record_for_key(HeadMusic::Utilities::HashKey.for(name)) || record_for_key(key_for_name(name)) || record_for_alias(name) end |
#single_staff? ⇒ Boolean
157 158 159 |
# File 'lib/head_music/instruments/instrument.rb', line 157 def single_staff? default_staves.length == 1 end |
#sounding_transposition ⇒ Object Also known as: default_sounding_transposition
143 144 145 |
# File 'lib/head_music/instruments/instrument.rb', line 143 def sounding_transposition default_staves&.first&.sounding_transposition || 0 end |
#staff_schemes ⇒ Object
Staff schemes (notation concern - kept for backward compatibility)
126 127 128 |
# File 'lib/head_music/instruments/instrument.rb', line 126 def staff_schemes @staff_schemes ||= build_staff_schemes end |
#stringing ⇒ Object
203 204 205 |
# File 'lib/head_music/instruments/instrument.rb', line 203 def stringing @stringing ||= HeadMusic::Instruments::Stringing.for_instrument(self) || parent&.stringing end |
#to_s ⇒ Object
183 184 185 |
# File 'lib/head_music/instruments/instrument.rb', line 183 def to_s name end |
#translation(locale = :en) ⇒ Object
171 172 173 174 175 |
# File 'lib/head_music/instruments/instrument.rb', line 171 def translation(locale = :en) return name unless name_key I18n.translate(name_key, scope: i[head_music instruments], locale: locale, default: name) end |
#transposing? ⇒ Boolean
149 150 151 |
# File 'lib/head_music/instruments/instrument.rb', line 149 def transposing? sounding_transposition != 0 end |
#transposing_at_the_octave? ⇒ Boolean
153 154 155 |
# File 'lib/head_music/instruments/instrument.rb', line 153 def transposing_at_the_octave? transposing? && sounding_transposition % 12 == 0 end |
#variants ⇒ Object
For backward compatibility with code that expects variants
188 189 190 |
# File 'lib/head_music/instruments/instrument.rb', line 188 def variants [] end |