Class: HeadMusic::Analysis::Sonority

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

Overview

A Sonority describes a set of pitch class intervalic relationships. For example, a minor triad, or a major-minor seventh chord. The Sonority class is a factory for returning one of its subclasses.

Constant Summary collapse

SONORITIES =
{
  major_triad: %w[M3 P5],
  minor_triad: %w[m3 P5],
  diminished_triad: %w[m3 d5],
  augmented_triad: %w[M3 A5],
  major_minor_seventh_chord: %w[M3 P5 m7],
  major_major_seventh_chord: %w[M3 P5 M7],
  minor_minor_seventh_chord: %w[m3 P5 m7],
  minor_major_seventh_chord: %w[m3 P5 M7],
  half_diminished_seventh_chord: %w[m3 d5 m7],
  diminished_seventh_chord: %w[m3 d5 d7],
  dominant_ninth_chord: %w[M2 M3 P5 m7],
  dominant_minor_ninth_chord: %w[m2 M3 P5 m7],
  minor_ninth_chord: %w[M2 m3 P5 m7],
  major_ninth_chord: %w[M2 M3 P5 M7],
  six_nine_chord: %w[M2 M3 P5 M6],
  minor_six_nine_chord: %w[M2 m3 P5 M6],
  suspended_four_chord: %w[P4 P5],
  suspended_two_chord: %w[M2 P5],
  quartal_chord: %w[P4 m7]
}.freeze
DEFAULT_ROOT =
"C4"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pitch_collection) ⇒ Sonority

Returns a new instance of Sonority.



77
78
79
80
# File 'lib/head_music/analysis/sonority.rb', line 77

def initialize(pitch_collection)
  @pitch_collection = pitch_collection
  identifier
end

Instance Attribute Details

#pitch_collectionObject (readonly)

Returns the value of attribute pitch_collection.



68
69
70
# File 'lib/head_music/analysis/sonority.rb', line 68

def pitch_collection
  @pitch_collection
end

Class Method Details

.get(identifier, root: DEFAULT_ROOT, inversion: 0) ⇒ Sonority?

Factory method to get a sonority by identifier Returns a Sonority with pitches starting at the default root (C4)

Parameters:

  • identifier (Symbol, String)

    the sonority identifier (e.g., :major_triad)

  • root (String) (defaults to: DEFAULT_ROOT)

    the root pitch (default: “C4”)

  • inversion (Integer) (defaults to: 0)

    the inversion number (default: 0 for root position)

Returns:

  • (Sonority, nil)

    the sonority object, or nil if identifier not found



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/head_music/analysis/sonority.rb', line 39

def self.get(identifier, root: DEFAULT_ROOT, inversion: 0)
  identifier = identifier.to_sym
  return nil unless SONORITIES.key?(identifier)

  root_pitch = HeadMusic::Rudiment::Pitch.get(root)
  interval_shorthands = SONORITIES[identifier]

  # Build pitches: root + intervals above root
  pitches = [root_pitch] + interval_shorthands.map do |shorthand|
    interval = HeadMusic::Analysis::DiatonicInterval.get(shorthand)
    interval.above(root_pitch)
  end

  pitch_collection = HeadMusic::Analysis::PitchCollection.new(pitches)

  # Apply inversions if requested
  inversion.times do
    pitch_collection = pitch_collection.invert
  end

  new(pitch_collection)
end

.identifiersArray<Symbol>

Returns all available sonority identifiers

Returns:

  • (Array<Symbol>)

    array of sonority identifiers



64
65
66
# File 'lib/head_music/analysis/sonority.rb', line 64

def self.identifiers
  SONORITIES.keys
end

Instance Method Details

#==(other) ⇒ Object



157
158
159
160
161
# File 'lib/head_music/analysis/sonority.rb', line 157

def ==(other)
  other = HeadMusic::Analysis::PitchCollection.new(other) if other.is_a?(Array)
  other = self.class.new(other) if other.is_a?(HeadMusic::Analysis::PitchCollection)
  identifier == other.identifier
end

#consonant?Boolean

Returns:

  • (Boolean)


114
115
116
117
118
# File 'lib/head_music/analysis/sonority.rb', line 114

def consonant?
  @consonant ||=
    pitch_collection.reduction_diatonic_intervals.all?(&:consonant?) &&
    root_position.diatonic_intervals_above_bass_pitch.all?(&:consonant?)
end

#diatonic_intervals_above_bass_pitchObject



150
151
152
153
154
155
# File 'lib/head_music/analysis/sonority.rb', line 150

def diatonic_intervals_above_bass_pitch
  return [] unless identifier

  @diatonic_intervals_above_bass_pitch ||=
    SONORITIES[identifier].map { |shorthand| HeadMusic::Analysis::DiatonicInterval.get(shorthand) }
end

#identifierObject



82
83
84
85
86
87
88
89
90
# File 'lib/head_music/analysis/sonority.rb', line 82

def identifier
  return @identifier if defined?(@identifier)

  @identifier = SONORITIES.keys.detect do |key|
    inversions.map do |inversion|
      inversion.diatonic_intervals_above_bass_pitch.map(&:shorthand)
    end.include?(SONORITIES[key])
  end
end

#inversionObject



92
93
94
95
96
# File 'lib/head_music/analysis/sonority.rb', line 92

def inversion
  @inversion ||= inversions.index do |inversion|
    SONORITIES[identifier] == inversion.diatonic_intervals_above_bass_pitch.map(&:shorthand)
  end
end

#inversionsObject



98
99
100
101
102
103
104
105
106
107
108
# File 'lib/head_music/analysis/sonority.rb', line 98

def inversions
  @inversions ||= begin
    inversion = reduction
    inversions = []
    inversion.pitches.length.times do |_i|
      inversions << inversion
      inversion = inversion.uninvert
    end
    inversions
  end
end

#quartal?Boolean Also known as: quintal?

Returns:

  • (Boolean)


141
142
143
144
145
146
147
# File 'lib/head_music/analysis/sonority.rb', line 141

def quartal?
  @quartal ||= inversions.detect do |inversion|
    inversion.diatonic_intervals.count do |interval|
      interval.fourth? || interval.fifth?
    end.to_f / inversion.diatonic_intervals.length > 0.5
  end
end

#root_positionObject



110
111
112
# File 'lib/head_music/analysis/sonority.rb', line 110

def root_position
  @root_position ||= inversions[inversion]
end

#secundal?Boolean

Returns:

  • (Boolean)


135
136
137
138
139
# File 'lib/head_music/analysis/sonority.rb', line 135

def secundal?
  @secundal ||= inversions.detect do |inversion|
    inversion.diatonic_intervals.count(&:second?).to_f / inversion.diatonic_intervals.length > 0.5
  end
end

#seventh_chord?Boolean

Returns:

  • (Boolean)


124
125
126
# File 'lib/head_music/analysis/sonority.rb', line 124

def seventh_chord?
  @seventh_chord ||= tetrachord? && tertian?
end

#tertian?Boolean

Returns:

  • (Boolean)


128
129
130
131
132
133
# File 'lib/head_music/analysis/sonority.rb', line 128

def tertian?
  @tertian ||= inversions.detect do |inversion|
    inversion.diatonic_intervals.count(&:third?).to_f / inversion.diatonic_intervals.length > 0.5 ||
      (scale_degrees_above_bass_pitch && [3, 5, 7]).length == 3
  end
end

#triad?Boolean

Returns:

  • (Boolean)


120
121
122
# File 'lib/head_music/analysis/sonority.rb', line 120

def triad?
  @triad ||= trichord? && tertian?
end