Class: HeadMusic::Instruments::ScoreOrder

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

Constant Summary collapse

SCORE_ORDERS =
YAML.load_file(File.expand_path("score_orders.yml", __dir__)).freeze
DEFAULT_ENSEMBLE_TYPE_KEY =
:orchestral

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ensemble_type_key = DEFAULT_ENSEMBLE_TYPE_KEY) ⇒ ScoreOrder (private)

Returns a new instance of ScoreOrder.



42
43
44
45
46
47
48
# File 'lib/head_music/instruments/score_order.rb', line 42

def initialize(ensemble_type_key = DEFAULT_ENSEMBLE_TYPE_KEY)
  @ensemble_type_key = ensemble_type_key.to_sym
  data = SCORE_ORDERS[ensemble_type_key.to_s]

  @sections = data["sections"] || []
  self.name = data["name"] || ensemble_type_key.to_s.tr("_", " ").capitalize
end

Instance Attribute Details

#alias_name_keysObject (readonly) Originally defined in module Named

Returns the value of attribute alias_name_keys.

#ensemble_type_keyObject (readonly)

Returns the value of attribute ensemble_type_key.



10
11
12
# File 'lib/head_music/instruments/score_order.rb', line 10

def ensemble_type_key
  @ensemble_type_key
end

#name_keyObject (readonly) Originally defined in module Named

Returns the value of attribute name_key.

#sectionsObject (readonly)

Returns the value of attribute sections.



10
11
12
# File 'lib/head_music/instruments/score_order.rb', line 10

def sections
  @sections
end

Class Method Details

.get(ensemble_type) ⇒ Object

Factory method to get a ScoreOrder instance for a specific ensemble type



13
14
15
16
17
18
19
# File 'lib/head_music/instruments/score_order.rb', line 13

def self.get(ensemble_type)
  @instances ||= {}
  key = HeadMusic::Utilities::HashKey.for(ensemble_type)
  return unless SCORE_ORDERS.key?(key.to_s)

  @instances[key] ||= new(key)
end

.in_band_order(instruments) ⇒ Object

Convenience method to order instruments in concert band order



27
28
29
# File 'lib/head_music/instruments/score_order.rb', line 27

def self.in_band_order(instruments)
  get(:band).order(instruments)
end

.in_orchestral_order(instruments) ⇒ Object

Convenience method to order instruments in orchestral order



22
23
24
# File 'lib/head_music/instruments/score_order.rb', line 22

def self.in_orchestral_order(instruments)
  get(:orchestral).order(instruments)
end

Instance Method Details

#build_ordering_indexObject (private)

Builds an index mapping instrument names to their position in the order



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/head_music/instruments/score_order.rb', line 88

def build_ordering_index
  index = {}
  position = 0

  sections.each do |section|
    instruments = section["instruments"] || []
    instruments.each do |instrument_key|
      # Store position for this instrument key
      index[instrument_key.to_s] = position
      position += 1
    end
  end

  index
end

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

#find_position(instrument, ordering_index) ⇒ Object (private)

Finds the position of an instrument in the ordering. Positions are non-negative integers, so a nil lookup safely means “absent”.



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

def find_position(instrument, ordering_index)
  position_by_name_key(instrument, ordering_index) ||
    position_by_family(instrument, ordering_index) ||
    position_by_normalized_name(instrument, ordering_index)
end

#find_position_with_transposition(instrument, ordering_index) ⇒ Object (private)

Finds the position and transposition information for an instrument



137
138
139
140
141
142
143
144
145
# File 'lib/head_music/instruments/score_order.rb', line 137

def find_position_with_transposition(instrument, ordering_index)
  position = find_position(instrument, ordering_index)
  return nil unless position

  # Get the sounding transposition for secondary sorting
  transposition = instrument.default_sounding_transposition || 0

  {position: position, transposition: transposition}
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

#normalize_inputs(instruments) ⇒ Object (private)

Discards blank inputs and converts the rest to Instrument objects



51
52
53
54
# File 'lib/head_music/instruments/score_order.rb', line 51

def normalize_inputs(instruments)
  valid_inputs = instruments.compact.reject { |i| i.respond_to?(:empty?) && i.empty? }
  valid_inputs.map { |i| normalize_to_instrument(i) }.compact
end

#normalize_to_instrument(input) ⇒ Object (private)



76
77
78
79
80
81
82
83
84
85
# File 'lib/head_music/instruments/score_order.rb', line 76

def normalize_to_instrument(input)
  # Return if already an Instrument instance
  return input if input.is_a?(HeadMusic::Instruments::Instrument)

  # Return other objects that respond to required methods (mock objects, etc.)
  return input if input.respond_to?(:name_key) && input.respond_to?(:family_key)

  # Create an Instrument instance for string inputs
  HeadMusic::Instruments::Instrument.get(input)
end

#order(instruments) ⇒ Object

Accepts a list of instruments and orders them according to this ensemble type’s conventions



32
33
34
35
36
# File 'lib/head_music/instruments/score_order.rb', line 32

def order(instruments)
  ordering_index = build_ordering_index
  known, unknown = partition_by_known_position(normalize_inputs(instruments), ordering_index)
  sort_known(known) + unknown.sort_by(&:to_s)
end

#partition_by_known_position(instrument_objects, ordering_index) ⇒ Object (private)

Splits instruments into those with a known score position and those without



57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/head_music/instruments/score_order.rb', line 57

def partition_by_known_position(instrument_objects, ordering_index)
  known = []
  unknown = []
  instrument_objects.each do |instrument|
    position_info = find_position_with_transposition(instrument, ordering_index)
    if position_info
      known << [instrument, position_info]
    else
      unknown << instrument
    end
  end
  [known, unknown]
end

#position_by_family(instrument, ordering_index) ⇒ Object (private)

Match a family variant (e.g., alto_saxophone -> saxophone family)



120
121
122
123
124
125
126
127
128
129
# File 'lib/head_music/instruments/score_order.rb', line 120

def position_by_family(instrument, ordering_index)
  return nil unless instrument.family_key

  family_base = instrument.family_key.to_s
  instrument_key = instrument.name_key.to_s
  return nil unless instrument_key.include?(family_base)

  # Prefer the specific variant, then fall back to the generic family instrument
  ordering_index[instrument_key] || ordering_index[family_base]
end

#position_by_name_key(instrument, ordering_index) ⇒ Object (private)

Exact match on the instrument’s name_key



113
114
115
116
117
# File 'lib/head_music/instruments/score_order.rb', line 113

def position_by_name_key(instrument, ordering_index)
  return nil unless instrument.name_key

  ordering_index[instrument.name_key.to_s]
end

#position_by_normalized_name(instrument, ordering_index) ⇒ Object (private)

Match the normalized (lowercase, underscored) display name



132
133
134
# File 'lib/head_music/instruments/score_order.rb', line 132

def position_by_normalized_name(instrument, ordering_index)
  ordering_index[HeadMusic::Utilities::Case.to_snake_case(instrument.name)]
end

#sort_known(known) ⇒ Object (private)

Sorts known instruments by position (primary) and transposition (secondary)



72
73
74
# File 'lib/head_music/instruments/score_order.rb', line 72

def sort_known(known)
  known.sort_by { |_, pos_info| [pos_info[:position], -pos_info[:transposition]] }.map(&:first)
end