Class: Musa::Chords::Chord

Inherits:
Object show all
Defined in:
lib/musa-dsl/music/chords.rb

Overview

Instantiated chord with specific root and scale context.

Chord represents an actual chord instance with a root note, scale context, and chord definition. It provides access to chord tones, voicing modifications, and navigation between related chords.

Creation

Chords are typically created from scale notes rather than directly:

scale = Scales::Scales.default_system.default_tuning.major[60]
chord = scale.tonic.chord              # C major triad
chord = scale.tonic.chord :seventh     # C major seventh
chord = scale.dominant.chord :ninth    # G ninth chord

Accessing Chord Tones

Chord tones are accessed by their position name (root, third, fifth, etc.):

chord.root      # Returns NoteInScale for root
chord.third     # Returns NoteInScale for third
chord.fifth     # Returns NoteInScale for fifth
chord.seventh   # Returns NoteInScale for seventh (if exists)

When notes are duplicated, use all: true to get all instances:

chord.root(all: true)  # Returns array of all root notes

Features and Navigation

Chords have features (quality, size) and can navigate to related chords:

chord.features          # => { quality: :major, size: :triad }
chord.quality           # => :major (dynamic method)
chord.size              # => :triad (dynamic method)

chord.with_quality(:minor)     # Change to minor
chord.with_size(:seventh)      # Add seventh
chord.featuring(size: :ninth)  # Change multiple features

Voicing Modifications

Move - Relocate specific chord tones to different octaves:

chord.with_move(root: -1, seventh: 1)
# Root down one octave, seventh up one octave
chord.move  # => { root: -1, seventh: 1 } (current settings)

Duplicate - Add copies of chord tones in other octaves:

chord.with_duplicate(root: -2, third: [-1, 1])
# Add root 2 octaves down, third 1 octave down and 1 up
chord.duplicate  # => { root: -2, third: [-1, 1] } (current settings)

Octave - Transpose entire chord:

chord.octave(-1)  # Move entire chord down one octave

Pitch Extraction

chord.pitches                    # All pitches sorted by pitch
chord.pitches(:root, :third)     # Only specified chord tones
chord.notes                      # Sorted ChordGradeNote structs

Scale Context

Chords maintain their scale context. When navigating to chords with non-diatonic notes (e.g., major to minor), the scale may become nil:

major_chord = c_major.tonic.chord
major_chord.scale  # => C major scale

minor_chord = major_chord.with_quality(:minor)
minor_chord.scale  # => nil (Eb not in C major)

Examples:

Basic triad creation

scale = Scales::Scales.default_system.default_tuning.major[60]
chord = scale.tonic.chord
chord.root.pitch   # => 60 (C)
chord.third.pitch  # => 64 (E)
chord.fifth.pitch  # => 67 (G)

Seventh chord

chord = scale.tonic.chord :seventh
chord.seventh.pitch  # => 71 (B)

Voicing with move and duplicate

scale = Scales::Scales.default_system.default_tuning.major[60]
chord = scale.dominant.chord(:seventh)
  .with_move(root: -1, third: -1)
  .with_duplicate(fifth: [0, 1])

Feature navigation

scale = Scales::Scales.default_system.default_tuning.major[60]
maj_triad = scale.tonic.chord
min_triad = maj_triad.with_quality(:minor)
maj_seventh = maj_triad.with_size(:seventh)

See Also:

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#chord_definitionChordDefinition (readonly)

Chord definition template.

Returns:



323
324
325
# File 'lib/musa-dsl/music/chords.rb', line 323

def chord_definition
  @chord_definition
end

#duplicateHash{Symbol => Integer, Array<Integer>} (readonly)

Octave duplications applied to positions.

Returns:



331
332
333
# File 'lib/musa-dsl/music/chords.rb', line 331

def duplicate
  @duplicate
end

#moveHash{Symbol => Integer} (readonly)

Octave moves applied to positions.

Returns:

  • (Hash{Symbol => Integer})


327
328
329
# File 'lib/musa-dsl/music/chords.rb', line 327

def move
  @move
end

#scaleScale? (readonly)

Scale context (nil if chord contains non-diatonic notes).

Returns:

  • (Scale, nil)


319
320
321
# File 'lib/musa-dsl/music/chords.rb', line 319

def scale
  @scale
end

Class Method Details

.with_root(root_note_or_pitch_or_symbol, scale: nil, allow_chromatic: false, name: nil, move: nil, duplicate: nil, **features) ⇒ Chord

Creates a chord with specified root.

Factory method for creating chords by specifying the root note and either a chord definition name or features. The root can be a NoteInScale, pitch number, or scale degree symbol.

Examples:

With note from scale

Chord.with_root(scale.tonic, name: :maj7)

With MIDI pitch and scale

Chord.with_root(60, scale: c_major, name: :min)

With scale degree

Chord.with_root(:dominant, scale: c_major, quality: :dominant, size: :seventh)

With features instead of name

Chord.with_root(60, scale: c_major, quality: :major, size: :triad)

With voicing parameters

Chord.with_root(60, scale: c_major, name: :maj7,
                move: {root: -1}, duplicate: {fifth: 1})

Parameters:

  • root_note_or_pitch_or_symbol (NoteInScale, Integer, Symbol)

    chord root

    • NoteInScale: use note directly
    • Integer (MIDI pitch): find note in scale, or create C major if no scale
    • Symbol (scale degree): requires scale parameter (e.g., :tonic, :dominant)
  • scale (Scale, nil) (defaults to: nil)

    scale context for finding notes

  • allow_chromatic (Boolean) (defaults to: false)

    allow non-diatonic notes

  • name (Symbol, nil) (defaults to: nil)

    chord definition name (:maj, :min7, etc.)

  • move (Hash{Symbol => Integer}, nil) (defaults to: nil)

    initial octave moves (e.g., {root: -1})

  • duplicate (Hash{Symbol => Integer, Array<Integer>}, nil) (defaults to: nil)

    initial duplications

  • features (Hash)

    chord features if not using name (quality:, size:, etc.)

Returns:

  • (Chord)

    new chord instance



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/musa-dsl/music/chords.rb', line 144

def self.with_root(root_note_or_pitch_or_symbol, scale: nil, allow_chromatic: false, name: nil, move: nil, duplicate: nil, **features)
  root =
    case root_note_or_pitch_or_symbol
    when Scales::NoteInScale
      root_note_or_pitch_or_symbol
    when Numeric
      if scale
        scale.note_of_pitch(root_note_or_pitch_or_symbol, allow_chromatic: allow_chromatic)
      else
        scale = Musa::Scales::Scales.default_system.default_tuning[root_note_or_pitch_or_symbol].major
        scale.note_of_pitch(root_note_or_pitch_or_symbol)
      end
    when Symbol
      raise ArgumentError, "Missing scale parameter to calculate root note for #{root_note_or_pitch_or_symbol}" unless scale

      scale[root_note_or_pitch_or_symbol]
    else
      raise ArgumentError, "Unexpected #{root_note_or_pitch_or_symbol}"
    end

  scale ||= root.scale

  if name
    raise ArgumentError, "Received name parameter with value #{name}: features parameter is not allowed" if features.any?

    chord_definition = ChordDefinition[name]

  elsif features.any?
    chord_definition = Helper.find_definition_by_features(root.pitch, features, scale, allow_chromatic: allow_chromatic)

  else
    raise ArgumentError, "Don't know how to find a chord definition without name or features parameters"
  end

  unless chord_definition
    raise ArgumentError,
          "Unable to find chord definition for root #{root}" \
          "#{" with name #{name}" if name}" \
          "#{" with features #{features}" if features.any?}"
  end

  source_notes_map = Helper.compute_source_notes_map(root, chord_definition, scale)

  Chord.new(root, scale, chord_definition, move, duplicate, source_notes_map)
end

Instance Method Details

#==(other) ⇒ Boolean

Checks chord equality.

Chords are equal if they have the same notes and chord definition.

Parameters:

  • other (Chord)

    chord to compare

Returns:

  • (Boolean)

    true if chords are equal



544
545
546
547
548
# File 'lib/musa-dsl/music/chords.rb', line 544

def ==(other)
  self.class == other.class &&
    @sorted_notes == other.notes &&
    @chord_definition == other.chord_definition
end

#as_chord_in_scale(scale) ⇒ Chord?

Creates an equivalent chord with the given scale as its context.

Returns a new Chord object representing the same chord but with the specified scale as its harmonic context. Returns nil if the chord is not contained in the scale.

Examples:

c_major = Scales.et12[440.0].major[60]
g7 = c_major.dominant.chord :seventh

g_mixolydian = Scales.et12[440.0].mixolydian[67]
g7_in_mixolydian = g7.as_chord_in_scale(g_mixolydian)
g7_in_mixolydian.scale  # => G Mixolydian scale

Parameters:

Returns:

  • (Chord, nil)

    new chord with target scale, or nil if not contained

See Also:



523
524
525
526
527
528
529
530
531
532
533
534
535
536
# File 'lib/musa-dsl/music/chords.rb', line 523

def as_chord_in_scale(scale)
  return nil unless scale.contains_chord?(self)

  root_note = scale.note_of_pitch(@root.pitch, allow_chromatic: false)
  return nil unless root_note

  Chord.with_root(
    root_note,
    scale: scale,
    name: @chord_definition.name,
    move: @move.empty? ? nil : @move,
    duplicate: @duplicate.empty? ? nil : @duplicate
  )
end

#featuresHash{Symbol => Symbol}

Returns chord features.

Examples:

chord.features  # => { quality: :major, size: :triad }

Returns:

  • (Hash{Symbol => Symbol})

    features hash (quality:, size:, etc.)



369
370
371
# File 'lib/musa-dsl/music/chords.rb', line 369

def features
  @chord_definition.features
end

#featuring(*values, allow_chromatic: false, **hash) ⇒ Chord

Creates new chord with modified features.

Returns a new chord with the same root but different features. Features can be specified as values (converted to feature hash) or as keyword arguments.

Examples:

Change size

chord.featuring(size: :seventh)

Change quality

chord.featuring(quality: :minor)

Change multiple features

chord.featuring(quality: :dominant, size: :ninth)

Parameters:

  • values (Array<Symbol>)

    feature values to change

  • allow_chromatic (Boolean) (defaults to: false)

    allow non-diatonic result

  • hash (Hash)

    feature key-value pairs to change

Returns:

  • (Chord)

    new chord with modified features

Raises:

  • (ArgumentError)

    if no matching chord definition found



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'lib/musa-dsl/music/chords.rb', line 393

def featuring(*values, allow_chromatic: false, **hash)
  # create a new list of features based on current features but
  # replacing the values for the new ones and adding the new features
  #
  features = @chord_definition.features.dup
  ChordDefinition.features_from(values, hash).each { |k, v| features[k] = v }

  chord_definition = Helper.find_definition_by_features(@root.pitch, features, @scale, allow_chromatic: allow_chromatic)

  raise ArgumentError, "Unable to find a chord definition for #{features}" unless chord_definition

  source_notes_map = Helper.compute_source_notes_map(@root, chord_definition, @scale)

  Chord.new(@root,
            (@scale if chord_definition.in_scale?(@scale, chord_root_pitch: @root.pitch)),
            chord_definition,
            @move, @duplicate,
            source_notes_map)
end

#inspectString Also known as: to_s

Returns string representation.

Returns:



553
554
555
# File 'lib/musa-dsl/music/chords.rb', line 553

def inspect
  "<Chord #{@name} root #{@root} notes #{@sorted_notes.collect { |_| "#{_.grade}=#{_.note.grade}|#{_.note.pitch} "} }>"
end

#notesArray<ChordGradeNote>

Returns chord notes sorted by pitch.

Examples:

chord.notes.each do |chord_grade_note|
  puts "#{chord_grade_note.grade}: #{chord_grade_note.note.pitch}"
end

Returns:

  • (Array<ChordGradeNote>)

    sorted array of grade-note pairs



341
342
343
# File 'lib/musa-dsl/music/chords.rb', line 341

def notes
  @sorted_notes
end

#octave(octave) ⇒ Chord

Transposes entire chord to a different octave.

Moves all chord notes by the specified octave offset, preserving internal voicing structure (moves and duplications).

Examples:

Move chord down one octave

chord.octave(-1)

Move chord up two octaves

chord.octave(2)

Parameters:

  • octave (Integer)

    octave offset (positive = up, negative = down)

Returns:

  • (Chord)

    new chord in different octave



426
427
428
429
430
431
432
# File 'lib/musa-dsl/music/chords.rb', line 426

def octave(octave)
  source_notes_map = @source_notes_map.transform_values do |notes|
    notes.collect { |note| note.at_octave(octave) }.freeze
  end.freeze

  Chord.new(@root.at_octave(octave), @scale, chord_definition, @move, @duplicate, source_notes_map)
end

#pitches(*grades) ⇒ Array<Integer>

Returns MIDI pitches of chord notes.

Without arguments, returns all pitches sorted from low to high. With grade arguments, returns only pitches for those positions.

Examples:

All pitches

chord.pitches  # => [60, 64, 67]

Specific positions

chord.pitches(:root, :third)  # => [60, 64]

Parameters:

  • grades (Array<Symbol>)

    optional position names to filter

Returns:

  • (Array<Integer>)

    MIDI pitches sorted by pitch



358
359
360
361
# File 'lib/musa-dsl/music/chords.rb', line 358

def pitches(*grades)
  grades = @notes_map.keys if grades.empty?
  @sorted_notes.select { |_| grades.include?(_.grade) }.collect { |_| _.note.pitch }
end

#search_in_scales(roots: nil, **metadata) ⇒ Array<Chord>

Finds this chord in other scales.

Searches through scale kinds matching the given metadata criteria to find all scales that contain this chord. Returns new chord instances, each with its containing scale as context.

Examples:

Find G major triad in diatonic scales

g_triad = c_major.dominant.chord
g_triad.in_scales(family: :diatonic)

Find chord in scales with specific brightness

g7.in_scales(brightness: -1..1)

Iterate over results

g7.in_scales(family: :greek_modes).each do |chord|
  scale = chord.scale
  degree = scale.degree_of_chord(chord)
  puts "#{scale.kind.class.id} on #{scale.root_pitch}: degree #{degree}"
end

Parameters:

  • roots (Range, Array, nil) (defaults to: nil)

    pitch offsets to search (default: full octave)

  • metadata (Hash)

    metadata filters for scale kinds (family:, brightness:, etc.)

Returns:

  • (Array<Chord>)

    this chord in different scale contexts

See Also:



499
500
501
502
# File 'lib/musa-dsl/music/chords.rb', line 499

def search_in_scales(roots: nil, **)
  tuning = @scale&.kind&.tuning || @root.scale.kind.tuning
  tuning.search_chord_in_scales(self, roots: roots, **)
end

#with_duplicate(**octaves) ⇒ Chord

Creates new chord with positions duplicated in other octaves.

Adds copies of specific chord positions in different octaves. Original positions remain at their current octave. Merges with existing duplications.

Examples:

Duplicate root two octaves down

chord.with_duplicate(root: -2)

Duplicate third in multiple octaves

chord.with_duplicate(third: [-1, 1])

Duplicate multiple positions

chord.with_duplicate(root: -1, fifth: 1)

Parameters:

  • octaves (Hash{Symbol => Integer, Array<Integer>})

    position to octave(s)

Returns:

  • (Chord)

    new chord with duplicated positions



469
470
471
# File 'lib/musa-dsl/music/chords.rb', line 469

def with_duplicate(**octaves)
  Chord.new(@root, @scale, @chord_definition, @move, @duplicate.merge(octaves), @source_notes_map)
end

#with_move(**octaves) ⇒ Chord

Creates new chord with positions moved to different octaves.

Relocates specific chord positions to different octaves while keeping other positions unchanged. Multiple positions can be moved at once. Merges with existing moves.

Examples:

Move root down, seventh up

chord.with_move(root: -1, seventh: 1)

Drop voicing (move third and seventh down)

chord.with_move(third: -1, seventh: -1)

Parameters:

  • octaves (Hash{Symbol => Integer})

    position to octave offset mapping

Returns:

  • (Chord)

    new chord with moved positions



448
449
450
# File 'lib/musa-dsl/music/chords.rb', line 448

def with_move(**octaves)
  Chord.new(@root, @scale, @chord_definition, @move.merge(octaves), @duplicate, @source_notes_map)
end