class MTK::Groups::PitchClassSet

An ordered collection of {PitchClass}es.

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

@see MTK::Groups::Melody @see MTK::Groups::Chord

Attributes

pitch_classes[R]

Public Class Methods

all() click to toggle source
# File lib/mtk/groups/pitch_class_set.rb, line 21
def self.all
  @all ||= new(MTK::Lang::PitchClasses::PITCH_CLASSES)
end
from_a(enumerable) click to toggle source
# File lib/mtk/groups/pitch_class_set.rb, line 42
def self.from_a enumerable
  new enumerable
end
new(pitch_classes) click to toggle source

@param pitch_classes [#to_a] the collection of pitch classes

@see MTK#PitchClassSet

# File lib/mtk/groups/pitch_class_set.rb, line 29
def initialize(pitch_classes)
  @pitch_classes = pitch_classes.to_a.clone.freeze
end
random_row() click to toggle source
# 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) click to toggle source
# File lib/mtk/groups/pitch_class_set.rb, line 149
def self.span_between(pc1, pc2)
  (pc2.to_i - pc1.to_i) % 12
end

Public Instance Methods

==(other) click to toggle source

@param other [#pitch_classes, to_a, Array]

# 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) click to toggle source

Compare for equality, ignoring order and duplicates @param other [#pitch_classes, Array, to_a]

# 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
complement() click to toggle source

the collection of elements that are not members of this set

# File lib/mtk/groups/pitch_class_set.rb, line 114
def complement
  self.class.all.difference(self)
end
difference(other) click to toggle source

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

# File lib/mtk/groups/pitch_class_set.rb, line 104
def difference(other)
  self.class.from_a(to_a - other.to_a)
end
elements() click to toggle source

@see Helper::Collection

# File lib/mtk/groups/pitch_class_set.rb, line 34
def elements
  @pitch_classes
end
inspect() click to toggle source
# File lib/mtk/groups/pitch_class_set.rb, line 145
def inspect
  @pitch_classes.inspect
end
intersection(other) click to toggle source

the collection of elements present in both sets

# File lib/mtk/groups/pitch_class_set.rb, line 94
def intersection(other)
  self.class.from_a(to_a & other.to_a)
end
normal_form() click to toggle source
# 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_order() click to toggle source
# 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) click to toggle source

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

# File lib/mtk/groups/pitch_class_set.rb, line 109
def symmetric_difference(other)
  union(other).difference( intersection(other) )
end
to_s() click to toggle source
# File lib/mtk/groups/pitch_class_set.rb, line 141
def to_s
  @pitch_classes.join(' ')
end
union(other) click to toggle source

the collection of all elements present in either set

# File lib/mtk/groups/pitch_class_set.rb, line 99
def union(other)
  self.class.from_a(to_a | other.to_a)
end