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.

Parameters:

  • name (Symbol)

    chord name

  • offsets (Hash{Symbol => Integer})

    semitone offsets

  • features (Hash)

    chord features



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

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.).

Returns:

  • (Hash{Symbol => Symbol})


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

def features
  @features
end

#nameSymbol (readonly)

Chord name.

Returns:

  • (Symbol)


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

def name
  @name
end

#pitch_namesHash{Integer => Symbol} (readonly)

Position names by semitone offset.

Returns:

  • (Hash{Integer => Symbol})


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

def pitch_names
  @pitch_names
end

#pitch_offsetsHash{Symbol => Integer} (readonly)

Semitone offsets by position name.

Returns:

  • (Hash{Symbol => Integer})


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

def pitch_offsets
  @pitch_offsets
end

Class Method Details

.feature_key_of(feature_value) ⇒ Symbol

Returns feature key for a feature value.

Parameters:

  • feature_value (Symbol)

    feature value

Returns:

  • (Symbol)

    feature key



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

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

.feature_keysSet<Symbol>

Returns all registered feature keys.

Returns:

  • (Set<Symbol>)

    feature keys



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

def self.feature_keys
  @feature_keys
end

.feature_valuesArray<Symbol>

Returns all registered feature values.

Returns:

  • (Array<Symbol>)

    feature values



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

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 }

Parameters:

  • values (Array<Symbol>) (defaults to: nil)

    feature values

  • hash (Hash) (defaults to: nil)

    feature key-value pairs

Returns:

  • (Hash)

    combined features



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

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>]

Parameters:

  • values (Array<Symbol>)

    feature values

  • hash (Hash)

    feature key-value pairs

Returns:



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

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

Parameters:

  • pitches (Array<Integer>)

    MIDI pitch numbers

Returns:



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

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

.get(name) ⇒ ChordDefinition? Also known as: []

Retrieves a registered chord definition by name.

Examples:

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

Parameters:

  • name (Symbol)

    chord definition name

Returns:



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

def self.get(name)
  @definitions[name]
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 }

Parameters:

  • name (Symbol)

    unique chord identifier

  • offsets (Hash{Symbol => Integer})

    semitone intervals from root

  • features (Hash)

    chord characteristics (quality, size, etc.)

Returns:

  • (self)


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

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.each_key { |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

Parameters:

  • scale (Scales::Scale)

    scale to check against

  • chord_root_pitch (Integer)

    chord root pitch

Returns:

  • (Boolean)

    true if all chord notes are in scale



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

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.

Returns:



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

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)

Parameters:

  • pitches (Array<Integer>)

    MIDI pitches to check

Returns:

  • (Boolean)

    true if pitches match this chord



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

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.

Parameters:

  • elements_or_pitches (Array)

    elements to map

Yields:

  • (element)

    block to extract pitch from element

Returns:

  • (Hash{Symbol => Array})

    position names to elements



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

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

Parameters:

  • root_pitch (Integer)

    MIDI root pitch

Returns:

  • (Array<Integer>)

    chord pitches



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

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