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
Constant Summary collapse
- INSTRUMENTS =
YAML.load_file(File.("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
-
#alias_name_keys ⇒ Object
readonly
Returns the value of attribute alias_name_keys.
-
#alias_name_keys ⇒ Object
included
from Named
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.
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_str) ⇒ Object
private
Instance Method Summary collapse
-
#==(other) ⇒ Object
-
#alternate_tunings ⇒ Object
-
#classification_keys ⇒ Object
-
#default_clefs ⇒ Object
-
#default_notation_staves_data ⇒ Object
private
The raw staff-attribute list for this instrument’s default notation, resolved from the default NotationStyle.
-
#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
-
#notation(style: :default) ⇒ Object
Notation for this instrument in the given style (defaults to :default).
-
#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 are a notation concern; they now live in NotationStyle.
-
#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.
216 217 218 219 220 221 222 223 224 225 |
# File 'lib/head_music/instruments/instrument.rb', line 216 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)
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_keys ⇒ Object (readonly) Originally defined in module Named
Returns the value of attribute alias_name_keys.
#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 |
Class Method Details
.all ⇒ Object
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
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
175 176 177 178 179 |
# File 'lib/head_music/instruments/instrument.rb', line 175 def ==(other) return false unless other.is_a?(self.class) name_key == other.name_key end |
#alternate_tunings ⇒ Object
205 206 207 208 209 210 |
# File 'lib/head_music/instruments/instrument.rb', line 205 def alternate_tunings own_tunings = HeadMusic::Instruments::AlternateTuning.for_instrument(name_key) return own_tunings if own_tunings.any? parent&.alternate_tunings || [] end |
#classification_keys ⇒ Object
101 102 103 |
# File 'lib/head_music/instruments/instrument.rb', line 101 def classification_keys family&.classification_keys || [] end |
#default_clefs ⇒ Object
137 138 139 |
# File 'lib/head_music/instruments/instrument.rb', line 137 def default_clefs default_staves&.map(&:clef) || [] end |
#default_notation_staves_data ⇒ Object (private)
The raw staff-attribute list for this instrument’s default notation, resolved from the default NotationStyle.
319 320 321 322 |
# File 'lib/head_music/instruments/instrument.rb', line 319 def default_notation_staves_data notation = HeadMusic::Notation::NotationStyle.default.notation_for(self) (notation&.staves || []).map(&:attributes) end |
#default_staff_scheme ⇒ Object
125 126 127 128 129 130 131 |
# File 'lib/head_music/instruments/instrument.rb', line 125 def default_staff_scheme @default_staff_scheme ||= HeadMusic::Instruments::StaffScheme.new( key: "default", instrument: self, list: default_notation_staves_data ) end |
#default_staves ⇒ Object
133 134 135 |
# File 'lib/head_music/instruments/instrument.rb', line 133 def default_staves default_staff_scheme.staves end |
#default_variant ⇒ Object
190 191 192 |
# File 'lib/head_music/instruments/instrument.rb', line 190 def default_variant nil end |
#ensure_localized_name(name:, locale_code: Locale::DEFAULT_CODE, abbreviation: nil) ⇒ Object Originally defined in module Named
#family ⇒ Object
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_key ⇒ Object
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 |
#format_pitch_name(pitch_designation) ⇒ Object (private)
298 299 300 |
# File 'lib/head_music/instruments/instrument.rb', line 298 def format_pitch_name(pitch_designation) pitch_designation.to_s.tr("b", "♭").tr("#", "♯") end |
#inferred_name ⇒ Object (private)
294 295 296 |
# File 'lib/head_music/instruments/instrument.rb', line 294 def inferred_name name_key.to_s.tr("_", " ") end |
#initialize_data_from_record(record) ⇒ Object (private)
260 261 262 263 264 265 266 267 268 269 |
# File 'lib/head_music/instruments/instrument.rb', line 260 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_name ⇒ Object (private)
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/head_music/instruments/instrument.rb', line 271 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
195 196 197 198 199 |
# File 'lib/head_music/instruments/instrument.rb', line 195 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)
233 234 235 236 237 238 239 240 241 |
# File 'lib/head_music/instruments/instrument.rb', line 233 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
159 160 161 |
# File 'lib/head_music/instruments/instrument.rb', line 159 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
#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_key ⇒ Object
97 98 99 |
# File 'lib/head_music/instruments/instrument.rb', line 97 def orchestra_section_key family&.orchestra_section_key end |
#parent ⇒ Object
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 |
#parent_translation ⇒ Object (private)
288 289 290 291 292 |
# File 'lib/head_music/instruments/instrument.rb', line 288 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)
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_key ⇒ Object
87 88 89 |
# File 'lib/head_music/instruments/instrument.rb', line 87 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”)
303 304 305 306 307 308 309 310 311 312 313 314 315 |
# File 'lib/head_music/instruments/instrument.rb', line 303 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 |
#pitched? ⇒ Boolean
163 164 165 166 167 |
# File 'lib/head_music/instruments/instrument.rb', line 163 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)
250 251 252 253 254 255 256 257 258 |
# File 'lib/head_music/instruments/instrument.rb', line 250 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)
243 244 245 246 247 248 |
# File 'lib/head_music/instruments/instrument.rb', line 243 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)
227 228 229 230 231 |
# File 'lib/head_music/instruments/instrument.rb', line 227 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
155 156 157 |
# File 'lib/head_music/instruments/instrument.rb', line 155 def single_staff? default_staves.length == 1 end |
#sounding_transposition ⇒ Object Also known as: default_sounding_transposition
141 142 143 |
# File 'lib/head_music/instruments/instrument.rb', line 141 def sounding_transposition default_staves&.first&.sounding_transposition || 0 end |
#staff_schemes ⇒ Object
Staff schemes are a notation concern; they now live in NotationStyle. These methods remain for backward compatibility and delegate to the default style. Referenced only inside method bodies, because the Notation module loads after Instruments (see head_music.rb load order).
121 122 123 |
# File 'lib/head_music/instruments/instrument.rb', line 121 def staff_schemes [default_staff_scheme] end |
#stringing ⇒ Object
201 202 203 |
# File 'lib/head_music/instruments/instrument.rb', line 201 def stringing @stringing ||= HeadMusic::Instruments::Stringing.for_instrument(self) || parent&.stringing end |
#to_s ⇒ Object
181 182 183 |
# File 'lib/head_music/instruments/instrument.rb', line 181 def to_s name end |
#translation(locale = :en) ⇒ Object
169 170 171 172 173 |
# File 'lib/head_music/instruments/instrument.rb', line 169 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
147 148 149 |
# File 'lib/head_music/instruments/instrument.rb', line 147 def transposing? sounding_transposition != 0 end |
#transposing_at_the_octave? ⇒ Boolean
151 152 153 |
# File 'lib/head_music/instruments/instrument.rb', line 151 def transposing_at_the_octave? transposing? && sounding_transposition % 12 == 0 end |
#variants ⇒ Object
For backward compatibility with code that expects variants
186 187 188 |
# File 'lib/head_music/instruments/instrument.rb', line 186 def variants [] end |