Class: Tonal::Ratio::Approximation

Inherits:
Object
  • Object
show all
Extended by:
Forwardable
Defined in:
lib/tonal/approximation.rb

Defined Under Namespace

Classes: Set

Constant Summary collapse

DEFAULT_MIN_PRIME =
2
DEFAULT_MAX_PRIME =
Float::INFINITY
DEFAULT_MAX_GRID_SCALE =
100
DEFAULT_MAX_GRID_BOUNDARY =
5
DEFAULT_DEPTH =
Float::INFINITY
DEFAULT_TREE_PATH_DEPTH =
10
DEFAULT_SUPERPART_DEPTH =
20
DEFAULT_NEIGHBORHOOD_DEPTH =
10
DEFAULT_COMPLEXITY_AMOUNT =
50.0
CONVERGENT_LIMIT =
10

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(ratio:) ⇒ Approximation

Returns a new instance of Approximation.

Raises:

  • (ArgumentError)


19
20
21
22
# File 'lib/tonal/approximation.rb', line 19

def initialize(ratio:)
  raise ArgumentError, "Tonal::Ratio required" unless ratio.kind_of?(Tonal::Ratio)
  @ratio = ratio
end

Instance Attribute Details

#ratioObject (readonly)

Returns the value of attribute ratio.



17
18
19
# File 'lib/tonal/approximation.rb', line 17

def ratio
  @ratio
end

Class Method Details

.neighbors(vacinity:, away: 1) ⇒ Array

Returns an array of Tonal::Ratio neighbors in the scaled ratio’s grid neighborhood.

Examples:

Tonal::Ratio::Approximation.neighbors(vacinity: (3/2r).ratio(reduced:false).scale(256), away: 1)
  => [768/512, 769/512, 767/512, 768/513, 768/511, 769/513, 769/511, 767/513, 767/511]

Parameters:

  • away (Integer) (defaults to: 1)

    the neighbors distance away from self’s antecedent and consequent

Returns:

  • (Array)

    an array of Tonal::Ratio neighbors in the scaled ratio’s grid neighborhood



149
150
151
152
153
154
155
156
157
158
159
# File 'lib/tonal/approximation.rb', line 149

def self.neighbors(vacinity:, away: 1)
  [vacinity,
   vacinity.class.new(vacinity.antecedent+away, vacinity.consequent),
   vacinity.class.new(vacinity.antecedent-away, vacinity.consequent),
   vacinity.class.new(vacinity.antecedent, vacinity.consequent+away),
   vacinity.class.new(vacinity.antecedent, vacinity.consequent-away),
   vacinity.class.new(vacinity.antecedent+away, vacinity.consequent+away),
   vacinity.class.new(vacinity.antecedent+away, vacinity.consequent-away),
   vacinity.class.new(vacinity.antecedent-away, vacinity.consequent+away),
   vacinity.class.new(vacinity.antecedent-away, vacinity.consequent-away)]
end

Instance Method Details

#by_continued_fraction(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME, conv_limit: CONVERGENT_LIMIT) ⇒ Tonal::Ratio::Approximation::Set

Returns of ratios within cent tolerance of self found using continued fraction approximation.

Examples:

Tonal::Ratio.ed(12,1).approximate.by_continued_fraction
=> 1.06: [17/16, 18/17, 89/84, 196/185, 1461/1379, 1657/1564, 3118/2943, 7893/7450, 18904/17843]

Parameters:

  • cents_tolerance (defaults to: Tonal::Cents::TOLERANCE)

    the cents tolerance used to scope the collection

  • depth (defaults to: DEFAULT_DEPTH)

    the maximum number of ratios in the collection

  • max_prime (defaults to: DEFAULT_MAX_PRIME)

    the maximum prime number to allow in the collection

  • min_prime (defaults to: DEFAULT_MIN_PRIME)

    the minimum prime number to allow in the collection

  • conv_limit (defaults to: CONVERGENT_LIMIT)

    the number of convergents to limit the ContinuedFraction method

Returns:



34
35
36
37
38
39
40
41
42
43
44
45
# File 'lib/tonal/approximation.rb', line 34

def by_continued_fraction(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME, conv_limit: CONVERGENT_LIMIT)
  return Set.new(ratio: ratio){|ratios| ratios << ratio} if (antecedent == 1) && (consequent == 1)
  self_in_cents = to_cents
  within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
  Set.new(ratio: ratio) do |ratios|
    ContinuedFraction.new(antecedent.to_f/consequent, conv_limit).convergents.each do |convergent|
      ratio2 = ratio.class.new(convergent.numerator,convergent.denominator)
      ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && (ratio2.max_prime <= max_prime) && (ratio2.min_prime >= min_prime)
      break if ratios.length >= depth
    end
  end
end

#by_neighborhood(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_NEIGHBORHOOD_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME, max_boundary: DEFAULT_MAX_GRID_BOUNDARY, max_scale: DEFAULT_MAX_GRID_SCALE) ⇒ Array

Returns of ratios within cent tolerance of self found on the ratio grid.

Examples:

Tonal::Ratio.new(3,2).approximate.by_neighborhood
=> 3/2: [175/117, 178/119, 176/117, 181/121, 179/119, 184/123, 182/121, 187/125, 185/123, 190/127, 188/125]

Parameters:

  • cents_tolerance (defaults to: Tonal::Cents::TOLERANCE)

    the cents tolerance used to scope the collection

  • depth (defaults to: DEFAULT_NEIGHBORHOOD_DEPTH)

    the maximum number of ratios in the collection

  • max_prime (defaults to: DEFAULT_MAX_PRIME)

    the maximum prime number to allow in the collection

  • min_prime (defaults to: DEFAULT_MIN_PRIME)

    the minimum prime number to allow in the collection

  • max_boundary (defaults to: DEFAULT_MAX_GRID_BOUNDARY)

    the maximum distance grid ratios will be from the scaled ratio

  • max_scale (defaults to: DEFAULT_MAX_GRID_SCALE)

    the maximum self will be scaled

Returns:

  • (Array)

    of ratios within cent tolerance of self found on the ratio grid



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/tonal/approximation.rb', line 105

def by_neighborhood(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_NEIGHBORHOOD_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME, max_boundary: DEFAULT_MAX_GRID_BOUNDARY, max_scale: DEFAULT_MAX_GRID_SCALE)
  self_in_cents = to_cents
  within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
  Set.new(ratio: ratio) do |ratios|
    scale = 1
    boundary = 1

    while ratios.length <= depth && scale <= max_scale do
      while boundary <= max_boundary
        vacinity = ratio.respond_to?(:to_basic_ratio) ? to_basic_ratio.scale(scale) : ratio.scale(scale)
        self.class.neighbors(away: boundary, vacinity: vacinity).each do |neighbor|
          ratios << neighbor if (neighbor != ratio) && ratio.class.within_cents?(self_in_cents, neighbor.to_cents, within) && (neighbor.max_prime <= max_prime) && (neighbor.min_prime >= min_prime)
        end
        boundary += 1
      end
      boundary = 1
      scale += 1
    end
  end
end

#by_superparticular(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_SUPERPART_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME, superpart: :upper) ⇒ Tonal::Ratio::Approximation::Set

Returns of superparticular approximations within cent tolerance of self.

Examples:

Tonal::Ratio.new(3/2r).approximate.by_superparticular
=> 3/2: [1041/692, 1044/694, 1047/696, 1050/698, 1053/700, 1056/702, 1059/704, 1062/706, 1065/708, 1068/710, 1071/712, 1074/714, 1077/716, 1080/718, 1083/720, 1086/722, 1089/724, 1092/726, 1095/728, 1098/730]

Parameters:

  • cents_tolerance (defaults to: Tonal::Cents::TOLERANCE)

    the cents tolerance used to scope the collection

  • depth (defaults to: DEFAULT_SUPERPART_DEPTH)

    the maximum number of ratios in the collection

  • max_prime (defaults to: DEFAULT_MAX_PRIME)

    the maximum prime number to allow in the collection

  • min_prime (defaults to: DEFAULT_MIN_PRIME)

    the minimum prime number to allow in the collection

  • superpart (defaults to: :upper)

    if the superior part is the numerator or denominator

Returns:



80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/tonal/approximation.rb', line 80

def by_superparticular(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_SUPERPART_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME, superpart: :upper)
  self_in_cents = to_cents
  within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
  Set.new(ratio: ratio) do |ratios|
    n = 1
    while true do
      ratio2 = ratio.class.superparticular(n, factor: ratio.to_r, superpart:)
      ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && (ratio2 != ratio) && (ratio2.max_prime <= max_prime) && (ratio2.min_prime <= min_prime)
      break if ratios.length >= depth
      n += 1
    end
  end
end

#by_tree_path(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_TREE_PATH_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME) ⇒ Tonal::Ratio::Approximation::Set

Returns of fraction tree ratios within cent tolerance of self.

Examples:

Tonal::Ratio.ed(12,1).approximate.by_tree_path
=> 1.06: [17/16, 18/17, 35/33, 53/50, 71/67, 89/84, 107/101, 196/185, 285/269, 481/454]

Parameters:

  • cents_tolerance (defaults to: Tonal::Cents::TOLERANCE)

    the cents tolerance used to scope the collection

  • depth (defaults to: DEFAULT_TREE_PATH_DEPTH)

    the maximum number of ratios in the collection

  • max_prime (defaults to: DEFAULT_MAX_PRIME)

    the maximum prime number to allow in the collection

  • min_prime (defaults to: DEFAULT_MIN_PRIME)

    the minimum prime number to allow in the collection

Returns:



56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/tonal/approximation.rb', line 56

def by_tree_path(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_TREE_PATH_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME)
  return Set.new(ratio: ratio){|ratios| ratios << ratio} if (antecedent == 1) && (consequent == 1)
  self_in_cents = to_cents
  within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
  Set.new(ratio: ratio) do |ratios|
    FractionTree.node(to_f).path.each do |node|
      next if node.number.infinite?
      ratio2 = ratio.class.new(node.number)
      ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && (ratio2.max_prime <= max_prime) && (ratio2.min_prime >= min_prime)
      break if ratios.length >= depth
    end
  end
end

#neighborhood(scale: 2**0, boundary: 1) ⇒ Array

Returns of bounding ratios in the ratio grid vacinity of antecedent/consequent scaled by scale.

Examples:

Tonal::ReducedRatio.new(3,2).neighborhood(scale: 256, boundary: 2)
  => [766/514, 768/514, 767/513, 766/512, 768/513, 767/512, 770/514, 769/513, 768/512, 767/511, 769/512, 766/510, 768/511, 770/512, 769/511, 768/510, 770/510]

Parameters:

  • scale (Integer) (defaults to: 2**0)

    used to scale antecedent/consequent on coordinate system

  • boundary (Integer) (defaults to: 1)

    limit within which to calculate neighboring ratios

Returns:

  • (Array)

    of bounding ratios in the ratio grid vacinity of antecedent/consequent scaled by scale



133
134
135
136
137
138
139
140
141
# File 'lib/tonal/approximation.rb', line 133

def neighborhood(scale: 2**0, boundary: 1)
  scale = scale.round
  vacinity = ratio.respond_to?(:to_basic_ratio) ? to_basic_ratio.scale(scale) : ratio.scale(scale)
  SortedSet.new([].tap do |ratio_list|
                     1.upto(boundary) do |away|
                       ratio_list << self.class.neighbors(away: away, vacinity: vacinity)
                     end
                   end.flatten).to_a
end