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.move(root: -1, seventh: 1)
# Root down one octave, seventh up one octave

Duplicate - Add copies of chord tones in other octaves:

chord.duplicate(root: -2, third: [-1, 1])
# Add root 2 octaves down, third 1 octave down and 1 up

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)
  .move(root: -1, third: -1)
  .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:



321
322
323
# File 'lib/musa-dsl/music/chords.rb', line 321

def chord_definition
  @chord_definition
end

#duplicate(**octaves) ⇒ Chord (readonly)

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.duplicate(root: -2)

Duplicate third in multiple octaves

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

Duplicate multiple positions

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

Parameters:

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

    position to octave(s)

Returns:

  • (Chord)

    new chord with duplicated positions



329
330
331
# File 'lib/musa-dsl/music/chords.rb', line 329

def duplicate
  @duplicate
end

#move(**octaves) ⇒ Chord (readonly)

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.move(root: -1, seventh: 1)

Drop voicing (move third and seventh down)

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

Parameters:

  • octaves (Hash{Symbol => Integer})

    position to octave offset mapping

Returns:

  • (Chord)

    new chord with moved positions



325
326
327
# File 'lib/musa-dsl/music/chords.rb', line 325

def move
  @move
end

#scaleScale? (readonly)

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

Returns:

  • (Scale, nil)


317
318
319
# File 'lib/musa-dsl/music/chords.rb', line 317

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



142
143
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
# File 'lib/musa-dsl/music/chords.rb', line 142

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



477
478
479
480
481
# File 'lib/musa-dsl/music/chords.rb', line 477

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

#featuresHash{Symbol => Symbol}

Returns chord features.

Examples:

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

Returns:

  • (Hash{Symbol => Symbol})

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



367
368
369
# File 'lib/musa-dsl/music/chords.rb', line 367

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



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

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:



486
487
488
# File 'lib/musa-dsl/music/chords.rb', line 486

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



339
340
341
# File 'lib/musa-dsl/music/chords.rb', line 339

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



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

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

  Chord.new(@root.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



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

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