Class: MTK::Groups::PitchClassSet

Inherits:
Object
  • Object
show all
Includes:
PitchCollection
Defined in:
lib/mtk/groups/pitch_class_set.rb

Overview

An ordered collection of PitchClasses.

Unlike a mathematical Set, a PitchClassSet is ordered and may contain duplicates.

See Also:

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from PitchCollection

#invert, #transpose

Methods included from Collection

#[], #clone, #concat, #each, #empty?, #enumerable_map, #first, #last, #map, #partition, #permute, #repeat, #reverse, #rotate, #size, #to_a

Constructor Details

#initialize(pitch_classes) ⇒ PitchClassSet

Returns a new instance of PitchClassSet.

Parameters:

  • pitch_classes (#to_a)

    the collection of pitch classes

See Also:

  • MTK#PitchClassSet


29
30
31
# File 'lib/mtk/groups/pitch_class_set.rb', line 29

def initialize(pitch_classes)
  @pitch_classes = pitch_classes.to_a.clone.freeze
end

Instance Attribute Details

#pitch_classesObject (readonly)

Returns the value of attribute pitch_classes.



15
16
17
# File 'lib/mtk/groups/pitch_class_set.rb', line 15

def pitch_classes
  @pitch_classes
end

Class Method Details

.allObject



21
22
23
# File 'lib/mtk/groups/pitch_class_set.rb', line 21

def self.all
  @all ||= new(MTK::Lang::PitchClasses::PITCH_CLASSES)
end

.from_a(enumerable) ⇒ Object



42
43
44
# File 'lib/mtk/groups/pitch_class_set.rb', line 42

def self.from_a enumerable
  new enumerable
end

.random_rowObject



17
18
19
# File 'lib/mtk/groups/pitch_class_set.rb', line 17

def self.random_row
  new(MTK::Lang::PitchClasses::PITCH_CLASSES.shuffle)
end

.span_between(pc1, pc2) ⇒ Object



149
150
151
# File 'lib/mtk/groups/pitch_class_set.rb', line 149

def self.span_between(pc1, pc2)
  (pc2.to_i - pc1.to_i) % 12
end

Instance Method Details

#==(other) ⇒ Object

Parameters:



119
120
121
122
123
124
125
126
127
# File 'lib/mtk/groups/pitch_class_set.rb', line 119

def == other
  if other.respond_to? :pitch_classes
    @pitch_classes == other.pitch_classes
  elsif other.respond_to? :to_a
    @pitch_classes == other.to_a
  else
    @pitch_classes == other
  end
end

#=~(other) ⇒ Object

Compare for equality, ignoring order and duplicates

Parameters:



131
132
133
134
135
136
137
138
139
# File 'lib/mtk/groups/pitch_class_set.rb', line 131

def =~ other
  @normalized_pitch_classes ||= @pitch_classes.uniq.sort
  @normalized_pitch_classes == case
    when other.respond_to?(:pitch_classes) then other.pitch_classes.uniq.sort
    when (other.is_a? Array and other.frozen?) then other
    when other.respond_to?(:to_a) then other.to_a.uniq.sort
    else other
  end
end

#complementObject

the collection of elements that are not members of this set



114
115
116
# File 'lib/mtk/groups/pitch_class_set.rb', line 114

def complement
  self.class.all.difference(self)
end

#difference(other) ⇒ Object

the collection of elements from this set with any elements from the other set removed



104
105
106
# File 'lib/mtk/groups/pitch_class_set.rb', line 104

def difference(other)
  self.class.from_a(to_a - other.to_a)
end

#elementsObject

See Also:

  • Helper::Collection


34
35
36
# File 'lib/mtk/groups/pitch_class_set.rb', line 34

def elements
  @pitch_classes
end

#inspectObject



145
146
147
# File 'lib/mtk/groups/pitch_class_set.rb', line 145

def inspect
  @pitch_classes.inspect
end

#intersection(other) ⇒ Object

the collection of elements present in both sets



94
95
96
# File 'lib/mtk/groups/pitch_class_set.rb', line 94

def intersection(other)
  self.class.from_a(to_a & other.to_a)
end

#normal_formObject



87
88
89
90
91
# File 'lib/mtk/groups/pitch_class_set.rb', line 87

def normal_form
  norder = normal_order
  first_pc_val = norder.first.to_i
  norder.map{|pitch_class| (pitch_class.to_i - first_pc_val) % 12 }
end

#normal_orderObject



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/mtk/groups/pitch_class_set.rb', line 46

def normal_order
  ordering = Array.new(@pitch_classes.uniq.sort)
  min_span, start_index_for_normal_order = nil, nil

  # check every rotation for the minimal span:
  size.times do |index|
    span = self.class.span_between ordering.first, ordering.last

    if min_span.nil? or span < min_span
      # best so far
      min_span = span
      start_index_for_normal_order = index

    elsif span == min_span
      # handle ties, minimize distance between first and second-to-last note, then first and third-to-last, etc
      span1, span2 = nil, nil
      tie_breaker = 1
      while span1 == span2 and tie_breaker < size
        span1 = self.class.span_between( ordering[0], ordering[-1 - tie_breaker] )
        span2 = self.class.span_between( ordering[start_index_for_normal_order], ordering[start_index_for_normal_order - tie_breaker] )
        tie_breaker -= 1
      end
      if span1 != span2
        # tie cannot be broken, pick the one starting with the lowest pitch class
        if ordering[0].to_i < ordering[start_index_for_normal_order].to_i
          start_index_for_normal_order = index
        end
      elsif span1 < span2
        start_index_for_normal_order = index
      end

    end
    ordering << ordering.shift  # rotate
  end

  # we've rotated all the way around, so we now need to rotate back to the start index we just found:
  start_index_for_normal_order.times{ ordering << ordering.shift }

  ordering
end

#symmetric_difference(other) ⇒ Object

the collection of elements that are members of exactly one of the sets



109
110
111
# File 'lib/mtk/groups/pitch_class_set.rb', line 109

def symmetric_difference(other)
  union(other).difference( intersection(other) )
end

#to_sObject



141
142
143
# File 'lib/mtk/groups/pitch_class_set.rb', line 141

def to_s
  @pitch_classes.join(' ')
end

#union(other) ⇒ Object

the collection of all elements present in either set



99
100
101
# File 'lib/mtk/groups/pitch_class_set.rb', line 99

def union(other)
  self.class.from_a(to_a | other.to_a)
end