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_MAX_PRIME =
Float::INFINITY
DEFAULT_MAX_GRID_SCALE =
100
DEFAULT_MAX_GRID_BOUNDARY =
5
DEFAULT_DEPTH =
Float::INFINITY
DEFAULT_FRACTION_TREE_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)


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

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.



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

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/513), (767/512), (768/512), (769/512), (768/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



141
142
143
144
145
146
147
148
149
150
151
# File 'lib/tonal/approximation.rb', line 141

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, 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
=> (4771397596969315/4503599627370496): [(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

  • conv_limit (defaults to: CONVERGENT_LIMIT)

    the number of convergents to limit the ContinuedFraction method

Returns:



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

def by_continued_fraction(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME, conv_limit: CONVERGENT_LIMIT)
  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.within_prime?(max_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, 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(max_prime: 23, cents_tolerance: 5, max_boundary: 10, max_scale: 60)
=> (3/2): [(175/117), (176/117)]

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

  • 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



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/tonal/approximation.rb', line 97

def by_neighborhood(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_NEIGHBORHOOD_DEPTH, max_prime: DEFAULT_MAX_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 ratio.class.within_cents?(self_in_cents, neighbor.to_cents, within) && neighbor.within_prime?(max_prime) && neighbor != ratio
        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, 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

  • superpart (defaults to: :upper)

    if the superior part is the numerator or denominator

Returns:



73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/tonal/approximation.rb', line 73

def by_superparticular(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_SUPERPART_DEPTH, max_prime: DEFAULT_MAX_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.within_prime?(max_prime) && ratio2 != ratio
      break if ratios.length >= depth
      n += 1
    end
  end
end

#by_tree_path(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_FRACTION_TREE_DEPTH, max_prime: DEFAULT_MAX_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(max_prime: 17)
=> (4771397596969315/4503599627370496): [(17/16), (18/17), (35/33)]

Parameters:

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

    the cents tolerance used to scope the collection

  • depth (defaults to: DEFAULT_FRACTION_TREE_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

Returns:



52
53
54
55
56
57
58
59
60
61
62
# File 'lib/tonal/approximation.rb', line 52

def by_tree_path(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_FRACTION_TREE_DEPTH, max_prime: DEFAULT_MAX_PRIME)
  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|
      ratio2 = ratio.class.new(node.number)
      ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && ratio2.within_prime?(max_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)
  => [(768/514), (766/512), (768/513), (767/512), (768/512), (769/512), (768/511), (770/512), (768/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



125
126
127
128
129
130
131
132
133
# File 'lib/tonal/approximation.rb', line 125

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