Class: HeadMusic::Analysis::Dyad::ChordImplication

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/analysis/dyad/chord_implication.rb

Overview

Enumerates the chords (as PitchCollections) that contain a given dyad. Trichords and seventh chords are built at every chromatic root from a fixed table of interval stacks, then kept only when they actually span the dyad. An optional key narrows the results to diatonic chords and ranks the survivors by how many of their pitches the key contains.

Constant Summary collapse

TRICHORD_INTERVALS =
[
  %w[M3 P5],   # major triad
  %w[m3 P5],   # minor triad
  %w[m3 d5],   # diminished triad
  %w[M3 A5],   # augmented triad
  %w[P4 P5],   # sus4 (not a triad)
  %w[M2 P5]    # sus2 (not a triad)
].freeze
SEVENTH_CHORD_INTERVALS =
[
  %w[M3 P5 M7],   # major seventh
  %w[M3 P5 m7],   # dominant seventh (major-minor)
  %w[m3 P5 m7],   # minor seventh
  %w[m3 P5 M7],   # minor-major seventh
  %w[m3 d5 m7],   # half-diminished seventh
  %w[m3 d5 d7],   # diminished seventh
  %w[M2 M3 P5 m7], # dominant ninth
  %w[m2 M3 P5 m7], # dominant minor ninth
  %w[M2 m3 P5 m7], # minor ninth
  %w[M2 M3 P5 M7]  # major ninth
].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(dyad_pitch_classes, key) ⇒ ChordImplication

Returns a new instance of ChordImplication.



32
33
34
35
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 32

def initialize(dyad_pitch_classes, key)
  @dyad_pitch_classes = dyad_pitch_classes
  @key = key
end

Instance Attribute Details

#dyad_pitch_classesObject (readonly)

Returns the value of attribute dyad_pitch_classes.



30
31
32
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 30

def dyad_pitch_classes
  @dyad_pitch_classes
end

#keyObject (readonly)

Returns the value of attribute key.



30
31
32
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 30

def key
  @key
end

Instance Method Details

#build_chord(root_pitch, intervals) ⇒ Object (private)



74
75
76
77
78
79
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 74

def build_chord(root_pitch, intervals)
  chord_pitches = [root_pitch] + intervals.map do |name|
    HeadMusic::Analysis::DiatonicInterval.get(name).above(root_pitch)
  end
  HeadMusic::Analysis::PitchCollection.new(chord_pitches)
end

#candidate_chords(interval_sets) ⇒ Object (private)



62
63
64
65
66
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 62

def candidate_chords(interval_sets)
  candidate_roots.flat_map do |root_pitch|
    interval_sets.map { |intervals| build_chord(root_pitch, intervals) }
  end
end

#candidate_rootsObject (private)



68
69
70
71
72
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 68

def candidate_roots
  HeadMusic::Rudiment::Spelling::CHROMATIC_SPELLINGS.map do |root_spelling|
    HeadMusic::Rudiment::Pitch.get("#{root_spelling}4")
  end
end

#chords_from(interval_sets) ⇒ Object (private)



56
57
58
59
60
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 56

def chords_from(interval_sets)
  candidate_chords(interval_sets)
    .select { |chord| includes_dyad?(chord) }
    .uniq { |chord| chord.pitch_classes.sort.map(&:to_i) }
end

#diatonic_spellingsObject (private)



97
98
99
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 97

def diatonic_spellings
  @diatonic_spellings ||= key.scale.spellings
end

#filter_by_key(pitch_collections) ⇒ Object (private)



85
86
87
88
89
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 85

def filter_by_key(pitch_collections)
  pitch_collections.select do |pitch_collection|
    pitch_collection.pitches.all? { |pitch| diatonic_spellings.include?(pitch.spelling) }
  end
end

#includes_dyad?(pitch_collection) ⇒ Boolean (private)

Returns:

  • (Boolean)


81
82
83
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 81

def includes_dyad?(pitch_collection)
  dyad_pitch_classes.all? { |pitch_class| pitch_collection.pitch_classes.include?(pitch_class) }
end

#ranked(chords) ⇒ Object (private)

Without a key every chord stands; with one, keep the diatonic chords and order them by how much they agree with the key. Concentrating the key check here keeps filter and sort free of their own guards.



50
51
52
53
54
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 50

def ranked(chords)
  return chords unless key

  sort_by_diatonic_agreement(filter_by_key(chords))
end

#seventh_chordsObject



41
42
43
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 41

def seventh_chords
  @seventh_chords ||= ranked(chords_from(SEVENTH_CHORD_INTERVALS))
end

#sort_by_diatonic_agreement(pitch_collections) ⇒ Object (private)



91
92
93
94
95
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 91

def sort_by_diatonic_agreement(pitch_collections)
  pitch_collections.sort_by do |pitch_collection|
    -pitch_collection.pitches.count { |pitch| diatonic_spellings.include?(pitch.spelling) }
  end
end

#trichordsObject



37
38
39
# File 'lib/head_music/analysis/dyad/chord_implication.rb', line 37

def trichords
  @trichords ||= ranked(chords_from(TRICHORD_INTERVALS))
end