Class: Musa::Chords::Chord
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:
c_major = Scales.default_system.default_tuning.major[60]
chord = c_major.tonic.chord # C major triad
chord = c_major.tonic.chord :seventh # C major seventh
chord = c_major.dominant.chord :ninth # G ninth chord
c_major is the scale every example below is written against.
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)
Instance Attribute Summary collapse
-
#chord_definition ⇒ ChordDefinition
readonly
Chord definition template.
-
#duplicate ⇒ Hash{Symbol => Integer, Array<Integer>}
readonly
Octave duplications applied to positions.
-
#move ⇒ Hash{Symbol => Integer}
readonly
Octave moves applied to positions.
-
#scale ⇒ Scale?
readonly
Scale context (nil if chord contains non-diatonic notes).
Class Method Summary collapse
-
.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.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Checks chord equality.
-
#as_chord_in_scale(scale) ⇒ Chord?
Creates an equivalent chord with the given scale as its context.
-
#features ⇒ Hash{Symbol => Symbol}
Returns chord features.
-
#featuring(*values, allow_chromatic: false, **hash) ⇒ Chord
Creates new chord with modified features.
-
#inspect ⇒ String
(also: #to_s)
Returns string representation.
-
#notes ⇒ Array<ChordGradeNote>
Returns chord notes sorted by pitch.
-
#octave(octave) ⇒ Chord
Transposes entire chord to a different octave.
-
#pitches(*grades) ⇒ Array<Integer>
Returns MIDI pitches of chord notes.
-
#search_in_scales(roots: nil, **metadata) ⇒ Array<Chord>
Finds this chord in other scales.
-
#with_duplicate(**octaves) ⇒ Chord
Creates new chord with positions duplicated in other octaves.
-
#with_move(**octaves) ⇒ Chord
Creates new chord with positions moved to different octaves.
Instance Attribute Details
#chord_definition ⇒ ChordDefinition (readonly)
Chord definition template.
347 348 349 |
# File 'lib/musa-dsl/music/chords.rb', line 347 def chord_definition @chord_definition end |
#duplicate ⇒ Hash{Symbol => Integer, Array<Integer>} (readonly)
Octave duplications applied to positions.
355 356 357 |
# File 'lib/musa-dsl/music/chords.rb', line 355 def duplicate @duplicate end |
#move ⇒ Hash{Symbol => Integer} (readonly)
Octave moves applied to positions.
351 352 353 |
# File 'lib/musa-dsl/music/chords.rb', line 351 def move @move end |
#scale ⇒ Scale? (readonly)
Scale context (nil if chord contains non-diatonic notes).
343 344 345 |
# File 'lib/musa-dsl/music/chords.rb', line 343 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.
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 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/musa-dsl/music/chords.rb', line 161 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 # A major scale rooted on the pitch itself, which is the documented # fallback. The scale kind comes first and the pitch roots it -- # `tuning.major[pitch]`, not `tuning[pitch].major`, which asks the # tuning for a scale kind called 60. # # allow_chromatic has nothing to decide here: the pitch is this # scale's own tonic, so it is always in it. scale = Musa::Scales::Scales.default_system.default_tuning.major[root_note_or_pitch_or_symbol] 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.
575 576 577 578 579 |
# File 'lib/musa-dsl/music/chords.rb', line 575 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.
554 555 556 557 558 559 560 561 562 563 564 565 566 567 |
# File 'lib/musa-dsl/music/chords.rb', line 554 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 |
#features ⇒ Hash{Symbol => Symbol}
Returns chord features.
394 395 396 |
# File 'lib/musa-dsl/music/chords.rb', line 394 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.
423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 |
# File 'lib/musa-dsl/music/chords.rb', line 423 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 |
#inspect ⇒ String Also known as: to_s
Returns string representation.
584 585 586 |
# File 'lib/musa-dsl/music/chords.rb', line 584 def inspect "<Chord #{@name} root #{@root} notes #{@sorted_notes.collect { |_| "#{_.grade}=#{_.note.grade}|#{_.note.pitch} "} }>" end |
#notes ⇒ Array<ChordGradeNote>
Returns chord notes sorted by pitch.
365 366 367 |
# File 'lib/musa-dsl/music/chords.rb', line 365 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).
456 457 458 459 460 461 462 |
# File 'lib/musa-dsl/music/chords.rb', line 456 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.
383 384 385 386 |
# File 'lib/musa-dsl/music/chords.rb', line 383 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.
530 531 532 533 |
# File 'lib/musa-dsl/music/chords.rb', line 530 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.
499 500 501 |
# File 'lib/musa-dsl/music/chords.rb', line 499 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.
478 479 480 |
# File 'lib/musa-dsl/music/chords.rb', line 478 def with_move(**octaves) Chord.new(@root, @scale, @chord_definition, @move.merge(octaves), @duplicate, @source_notes_map) end |