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.



63
64
65
66
67
68
69
# File 'lib/head_music/instruments/score_order.rb', line 63

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



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

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



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/head_music/instruments/score_order.rb', line 103

def find_position(instrument, ordering_index)
  # Try exact match with name_key
  return ordering_index[instrument.name_key.to_s] if instrument.name_key && ordering_index.key?(instrument.name_key.to_s)

  # Try matching by family + range category (e.g., alto_saxophone -> saxophone family)
  if instrument.family_key
    family_base = instrument.family_key.to_s
    instrument_key = instrument.name_key.to_s

    # Check if this is a variant of a family (e.g., alto_saxophone)
    if instrument_key.include?(family_base)
      # Look for the specific variant first
      return ordering_index[instrument_key] if ordering_index.key?(instrument_key)

      # Fall back to generic family instrument if listed
      return ordering_index[family_base] if ordering_index.key?(family_base)
    end
  end

  # Try normalized name (lowercase, underscored)
  normalized = instrument.name.downcase.tr(" ", "_").tr("-", "_")
  return ordering_index[normalized] if ordering_index.key?(normalized)

  nil
end

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

Finds the position and transposition information for an instrument



130
131
132
133
134
135
136
137
138
# File 'lib/head_music/instruments/score_order.rb', line 130

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_to_instrument(input) ⇒ Object (private)



71
72
73
74
75
76
77
78
79
80
81
82
83
# File 'lib/head_music/instruments/score_order.rb', line 71

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

  # Return InstrumentType instances as-is for backward compatibility (duck typing)
  return input.default_instrument if input.is_a?(HeadMusic::Instruments::InstrumentType)

  # 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) || HeadMusic::Instruments::InstrumentType.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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/head_music/instruments/score_order.rb', line 32

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

  # Build ordering index
  ordering_index = build_ordering_index

  # Separate known and unknown instruments
  known_instruments = []
  unknown_instruments = []

  instrument_objects.each do |instrument|
    position_info = find_position_with_transposition(instrument, ordering_index)
    if position_info
      known_instruments << [instrument, position_info]
    else
      unknown_instruments << instrument
    end
  end

  # Sort known instruments by position (primary) and transposition (secondary)
  sorted_known = known_instruments.sort_by { |_, pos_info|
    [pos_info[:position], -pos_info[:transposition]]
  }.map(&:first)
  sorted_known + unknown_instruments.sort_by(&:to_s)
end