Class: HeadMusic::Analysis::PitchClassSet

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

Overview

A PitchClassSet represents a pitch-class set or pitch collection. See also: PitchCollection, PitchClass

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(identifiers) ⇒ PitchClassSet

Returns a new instance of PitchClassSet.



12
13
14
# File 'lib/head_music/analysis/pitch_class_set.rb', line 12

def initialize(identifiers)
  @pitch_classes = identifiers.map { |identifier| HeadMusic::Rudiment::PitchClass.get(identifier) }.uniq.sort
end

Instance Attribute Details

#pitch_classesObject (readonly)

Returns the value of attribute pitch_classes.



7
8
9
# File 'lib/head_music/analysis/pitch_class_set.rb', line 7

def pitch_classes
  @pitch_classes
end

Instance Method Details

#==(other) ⇒ Object



21
22
23
# File 'lib/head_music/analysis/pitch_class_set.rb', line 21

def ==(other)
  pitch_classes == other.pitch_classes
end

#compare_forms(form1, form2) ⇒ Object (private)

Compare two normal forms and return the more compact one If equal, return the first one



162
163
164
165
166
167
168
169
# File 'lib/head_music/analysis/pitch_class_set.rb', line 162

def compare_forms(form1, form2)
  normalized1 = transpose_to_zero(form1).map(&:to_i)
  normalized2 = transpose_to_zero(form2).map(&:to_i)

  # Compare lexicographically element by element
  comparison = normalized1 <=> normalized2
  (comparison && comparison <= 0) ? form1 : form2
end

#decachord?Boolean

Returns:

  • (Boolean)


71
72
73
# File 'lib/head_music/analysis/pitch_class_set.rb', line 71

def decachord?
  size == 10
end

#dichord?Boolean Also known as: dyad?

Returns:

  • (Boolean)


38
39
40
# File 'lib/head_music/analysis/pitch_class_set.rb', line 38

def dichord?
  size == 2
end

#dodecachord?Boolean

Returns:

  • (Boolean)


79
80
81
# File 'lib/head_music/analysis/pitch_class_set.rb', line 79

def dodecachord?
  size == 12
end

#equivalent?(other) ⇒ Boolean

Returns:

  • (Boolean)


25
26
27
# File 'lib/head_music/analysis/pitch_class_set.rb', line 25

def equivalent?(other)
  pitch_classes == other.pitch_classes
end

#find_most_compact_rotation(rotations) ⇒ Object (private)

Find the most compact rotation Returns the rotation with the smallest span In case of tie, prefer the one with smaller intervals from the left



153
154
155
156
157
158
# File 'lib/head_music/analysis/pitch_class_set.rb', line 153

def find_most_compact_rotation(rotations)
  rotations.min_by do |rotation|
    # Create a comparison key: [span, intervals from left]
    [rotation.last] + rotation
  end
end

#generate_rotationsObject (private)

Generate all rotations of the pitch class set



138
139
140
141
142
143
144
145
146
147
148
# File 'lib/head_music/analysis/pitch_class_set.rb', line 138

def generate_rotations
  return [pitch_classes] if size <= 1

  numbers = pitch_classes.map(&:to_i)
  (0...size).map do |i|
    rotation = numbers.rotate(i)
    # Normalize each rotation to start from the first element
    first = rotation.first
    rotation.map { |n| (n - first) % 12 }
  end
end

#heptachord?Boolean

Returns:

  • (Boolean)


59
60
61
# File 'lib/head_music/analysis/pitch_class_set.rb', line 59

def heptachord?
  size == 7
end

#hexachord?Boolean

Returns:

  • (Boolean)


55
56
57
# File 'lib/head_music/analysis/pitch_class_set.rb', line 55

def hexachord?
  size == 6
end

#inversionObject

Returns the inversion of the pitch class set Inversion maps each pitch class to its inverse around 0 For pitch class n, the inversion is (12 - n) mod 12



86
87
88
89
90
91
# File 'lib/head_music/analysis/pitch_class_set.rb', line 86

def inversion
  @inversion ||=
    self.class.new(
      pitch_classes.map { |pc| (12 - pc.to_i) % 12 }
    )
end

#monochord?Boolean Also known as: monad?

Returns:

  • (Boolean)


33
34
35
# File 'lib/head_music/analysis/pitch_class_set.rb', line 33

def monochord?
  size == 1
end

#nonachord?Boolean

Returns:

  • (Boolean)


67
68
69
# File 'lib/head_music/analysis/pitch_class_set.rb', line 67

def nonachord?
  size == 9
end

#normal_formObject

Returns the normal form of the pitch class set The normal form is the most compact rotation of the set Algorithm:

  1. Generate all rotations of the pitch class set
  2. For each rotation, calculate the span (difference between first and last)
  3. Choose the rotation with the smallest span
  4. If there's a tie, choose the one with the smallest intervals from the left


100
101
102
103
104
105
106
107
# File 'lib/head_music/analysis/pitch_class_set.rb', line 100

def normal_form
  return self if size <= 1

  @rotations ||= generate_rotations
  @most_compact_rotation ||= find_most_compact_rotation(@rotations)

  self.class.new(@most_compact_rotation)
end

#octachord?Boolean

Returns:

  • (Boolean)


63
64
65
# File 'lib/head_music/analysis/pitch_class_set.rb', line 63

def octachord?
  size == 8
end

#pentachord?Boolean

Returns:

  • (Boolean)


51
52
53
# File 'lib/head_music/analysis/pitch_class_set.rb', line 51

def pentachord?
  size == 5
end

#prime_formObject

Returns the prime form of the pitch class set The prime form is the most compact form among the normal form and its inversion Algorithm:

  1. Find the normal form of the original set
  2. Find the normal form of the inverted set
  3. Compare and choose the most compact one
  4. Transpose to start at 0


116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/head_music/analysis/pitch_class_set.rb', line 116

def prime_form
  @prime_form ||= begin
    # Handle edge cases
    return self if size.zero?
    return self.class.new([0]) if size == 1

    normal = normal_form.pitch_classes
    inverted_normal = inversion.normal_form.pitch_classes

    # Compare which is more compact
    chosen = compare_forms(normal, inverted_normal)

    # Transpose to start at 0
    transposed = transpose_to_zero(chosen)

    self.class.new(transposed)
  end
end

#sizeObject



29
30
31
# File 'lib/head_music/analysis/pitch_class_set.rb', line 29

def size
  @size ||= pitch_classes.length
end

#tetrachord?Boolean

Returns:

  • (Boolean)


47
48
49
# File 'lib/head_music/analysis/pitch_class_set.rb', line 47

def tetrachord?
  size == 4
end

#to_sObject Also known as: inspect



16
17
18
# File 'lib/head_music/analysis/pitch_class_set.rb', line 16

def to_s
  pitch_classes.map(&:to_i).inspect
end

#transpose_to_zero(pcs) ⇒ Object (private)

Transpose a set of pitch classes to start at 0



172
173
174
175
176
177
178
# File 'lib/head_music/analysis/pitch_class_set.rb', line 172

def transpose_to_zero(pcs)
  return pcs if pcs.empty?

  numbers = pcs.map(&:to_i)
  first = numbers.first
  numbers.map { |n| HeadMusic::Rudiment::PitchClass.get((n - first) % 12) }
end

#trichord?Boolean

Returns:

  • (Boolean)


43
44
45
# File 'lib/head_music/analysis/pitch_class_set.rb', line 43

def trichord?
  size == 3
end

#undecachord?Boolean

Returns:

  • (Boolean)


75
76
77
# File 'lib/head_music/analysis/pitch_class_set.rb', line 75

def undecachord?
  size == 11
end