Class: Musa::Chords::ChordDefinition

Inherits:
Object
  • Object
show all
Defined in:
lib/musa-dsl/music/chord-definition.rb

Overview

Chord template defining structure and features.

ChordDefinition is a template that specifies the intervals and characteristics of a chord type. It's defined once and used to create many chord instances.

Components

  • Name: Unique identifier (:maj, :min, :dom7, etc.)
  • Offsets: Semitone intervals from root ({ root: 0, third: 4, fifth: 7 })
  • Features: Characteristics (quality: :major, size: :triad)

Registration

Chord definitions are registered globally:

ChordDefinition.register :maj,
  quality: :major,
  size: :triad,
  offsets: { root: 0, third: 4, fifth: 7 }

Finding Definitions

By name:

ChordDefinition[:maj]  # => <ChordDefinition :maj>

By features:

ChordDefinition.find_by_features(quality: :major, size: :triad)
# => [<ChordDefinition :maj>]

By pitches:

ChordDefinition.find_by_pitches([60, 64, 67])  # C E G
# => <ChordDefinition :maj>

Examples:

Defining a major triad

ChordDefinition.register :maj,
  quality: :major,
  size: :triad,
  offsets: { root: 0, third: 4, fifth: 7 }

Defining a dominant seventh

ChordDefinition.register :dom7,
  quality: :dominant,
  size: :seventh,
  offsets: { root: 0, third: 4, fifth: 7, seventh: 10 }

See Also:

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, offsets:, **features) ⇒ ChordDefinition

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Creates a chord definition.



233
234
235
236
237
238
239
# File 'lib/musa-dsl/music/chord-definition.rb', line 233

def initialize(name, offsets:, **features)
  @name = name.freeze
  @features = features.transform_values(&:dup).transform_values(&:freeze).freeze
  @pitch_offsets = offsets.dup.freeze
  @pitch_names = offsets.collect { |k, v| [v, k] }.to_h.freeze
  freeze
end

Instance Attribute Details

#featuresHash{Symbol => Symbol} (readonly)

Chord features (quality, size, etc.).



247
248
249
# File 'lib/musa-dsl/music/chord-definition.rb', line 247

def features
  @features
end

#nameSymbol (readonly)

Chord name.



243
244
245
# File 'lib/musa-dsl/music/chord-definition.rb', line 243

def name
  @name
end

#pitch_namesHash{Integer => Symbol} (readonly)

Position names by semitone offset.



255
256
257
# File 'lib/musa-dsl/music/chord-definition.rb', line 255

def pitch_names
  @pitch_names
end

#pitch_offsetsHash{Symbol => Integer} (readonly)

Semitone offsets by position name.



251
252
253
# File 'lib/musa-dsl/music/chord-definition.rb', line 251

def pitch_offsets
  @pitch_offsets
end

Class Method Details

.[](name) ⇒ ChordDefinition?

Retrieves a registered chord definition by name.

Examples:

ChordDefinition[:maj]   # => <ChordDefinition :maj>
ChordDefinition[:min7]  # => <ChordDefinition :min7>


118
119
120
# File 'lib/musa-dsl/music/chord-definition.rb', line 118

def self.[](name)
  @definitions[name]
end

.feature_key_of(feature_value) ⇒ Symbol

Returns feature key for a feature value.



208
209
210
# File 'lib/musa-dsl/music/chord-definition.rb', line 208

def self.feature_key_of(feature_value)
  @features_by_value[feature_value]
end

.feature_keysSet<Symbol>

Returns all registered feature keys.



222
223
224
# File 'lib/musa-dsl/music/chord-definition.rb', line 222

def self.feature_keys
  @feature_keys
end

.feature_valuesArray<Symbol>

Returns all registered feature values.



215
216
217
# File 'lib/musa-dsl/music/chord-definition.rb', line 215

def self.feature_values
  @features_by_value.keys
end

.features_from(values = nil, hash = nil) ⇒ Hash

Converts feature values to feature hash.

Examples:

features_from([:major, :triad])
# => { quality: :major, size: :triad }


180
181
182
183
184
185
186
187
188
# File 'lib/musa-dsl/music/chord-definition.rb', line 180

def self.features_from(values = nil, hash = nil)
  values ||= []
  hash ||= {}

  features = hash.dup
  values.each { |v| features[@features_by_value[v]] = v }

  features
end

.find_by_features(*values, **hash) ⇒ Array<ChordDefinition>

Finds definitions matching specified features.

Examples:

find_by_features(quality: :major, size: :triad)
# => [<ChordDefinition :maj>]


199
200
201
202
# File 'lib/musa-dsl/music/chord-definition.rb', line 199

def self.find_by_features(*values, **hash)
  features = features_from(values, hash)
  @definitions.values.select { |d| features <= d.features }
end

.find_by_pitches(pitches) ⇒ ChordDefinition?

Finds chord definition matching a set of pitches.

Identifies chord by comparing pitch intervals, accounting for octave reduction.

Examples:

ChordDefinition.find_by_pitches([60, 64, 67])  # => :maj


167
168
169
# File 'lib/musa-dsl/music/chord-definition.rb', line 167

def self.find_by_pitches(pitches)
  @definitions.values.find { |d| d.matches(pitches) }
end

.register(name, offsets:, **features) ⇒ self

Registers a new chord definition.

Creates and registers a chord definition with specified intervals and features. The definition becomes available globally for chord creation.

Examples:

Major triad

ChordDefinition.register :maj,
  quality: :major,
  size: :triad,
  offsets: { root: 0, third: 4, fifth: 7 }

Minor seventh

ChordDefinition.register :min7,
  quality: :minor,
  size: :seventh,
  offsets: { root: 0, third: 3, fifth: 7, seventh: 11 }


143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/musa-dsl/music/chord-definition.rb', line 143

def self.register(name, offsets:, **features)
  definition = ChordDefinition.new(name, offsets: offsets, **features)

  @definitions ||= {}
  @definitions[definition.name] = definition

  @features_by_value ||= {}
  definition.features.each { |k, v| @features_by_value[v] = k }

  @feature_keys ||= Set[]
  features.keys.each { |feature_name| @feature_keys << feature_name }

  self
end

Instance Method Details

#in_scale?(scale, chord_root_pitch:) ⇒ Boolean

Checks if chord fits within a scale.

Examples:

maj_def.in_scale?(c_major, chord_root_pitch: 60)  # => true


276
277
278
# File 'lib/musa-dsl/music/chord-definition.rb', line 276

def in_scale?(scale, chord_root_pitch:)
  !pitches(chord_root_pitch).find { |chord_pitch| scale.note_of_pitch(chord_pitch).nil? }
end

#inspectString Also known as: to_s

Returns string representation.



330
331
332
# File 'lib/musa-dsl/music/chord-definition.rb', line 330

def inspect
  "<ChordDefinition: name = #{@name} features = #{@features} pitch_offsets = #{@pitch_offsets}>"
end

#matches(pitches) ⇒ Boolean

Checks if pitches match this chord definition.

Compares octave-reduced pitch sets to determine if they form this chord.

Examples:

maj_def.matches([60, 64, 67])  # => true (C major)
maj_def.matches([60, 63, 67])  # => false (C minor)


319
320
321
322
323
324
325
# File 'lib/musa-dsl/music/chord-definition.rb', line 319

def matches(pitches)
  reduced_pitches = octave_reduce(pitches).uniq

  !!reduced_pitches.find do |candidate_root_pitch|
    reduced_pitches.sort == octave_reduce(pitches(candidate_root_pitch)).uniq.sort
  end
end

#named_pitches(elements_or_pitches) {|element| ... } ⇒ Hash{Symbol => Array}

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Maps elements to named chord positions.

Yields:

  • (element)

    block to extract pitch from element



287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/musa-dsl/music/chord-definition.rb', line 287

def named_pitches(elements_or_pitches, &block)
  pitches = elements_or_pitches.collect do |element_or_pitch|
    [if block_given?
       yield element_or_pitch
     else
       element_or_pitch
     end,
     element_or_pitch]
  end.to_h

  root_pitch = pitches.keys.find do |candidate_root_pitch|
    candidate_pitches = pitches.keys.collect { |p| p - candidate_root_pitch }
    octave_reduce(candidate_pitches).uniq == octave_reduce(@pitch_offsets.values).uniq
  end

  # TODO: OJO: problema con las notas duplicadas, con la identificación de inversiones y con las notas a distancias de más de una octava

  pitches.collect do |pitch, element|
    [@pitch_names[pitch - root_pitch], [element]]
  end.to_h
end

#pitches(root_pitch) ⇒ Array<Integer>

Calculates chord pitches from root pitch.

Examples:

chord_def.pitches(60)  # => [60, 64, 67] for C major


264
265
266
# File 'lib/musa-dsl/music/chord-definition.rb', line 264

def pitches(root_pitch)
  @pitch_offsets.values.collect { |offset| root_pitch + offset }
end