tonal-tools 1.3.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 850c95dbdd71dec89c67eb9b1d709a4f009c4ab48b90d8c3ee7d7553bb27515b
4
- data.tar.gz: 450b9ee2401a947857beedb052933d567f1321bef3d6c16e26f3120604105c12
3
+ metadata.gz: 340dc7220c5ab24c65486eefc2ebb457d76c7c1ac64fd91a428b94a4fd84cd7d
4
+ data.tar.gz: 5721937407c18951a5b72eb075ffdbbfe8765abb3ae98695fc2af40e55277c3a
5
5
  SHA512:
6
- metadata.gz: d5d7e3540e77d04fe847cf8da7de9766481bc6e74183438466ab6d1e42b3b156ff60397ca319ad3d7a2cf5780dbe94e7c01c1da10518b22de2e4c96ff7e9ea7d
7
- data.tar.gz: 075bab07ea64ce20d70b3e131d74d39349c5e15f89f00e44df16c3a3de95e691a795230fed9225433e56a7aeec159b43ec4b46766d96e6714cd8206912fb752c
6
+ metadata.gz: def0ab4830c3f5b3e82b919fcf8242899539a32e554acd2b260d3f37f87df6dbd7899cb8a6f9c045c98805bba69a10efc1f974d88e7f3107f641033c862acdc8
7
+ data.tar.gz: 72601d465253ab599188b0f15a241b30e9824754c076bf260f69d4fa5b92309ac85dbb677a1e9e03bff0c29b42302a064b8d73a14ac92e0c0f5f7c9c8c84af8c
@@ -0,0 +1,189 @@
1
+ class Tonal::Ratio
2
+ class Approximation
3
+ DEFAULT_MAX_PRIME = Float::INFINITY
4
+ DEFAULT_MAX_GRID_SCALE = 100
5
+ DEFAULT_MAX_GRID_BOUNDARY = 5
6
+ DEFAULT_DEPTH = Float::INFINITY
7
+ DEFAULT_COMPLEXITY_AMOUNT = 50.0
8
+ CONVERGENT_LIMIT = 10
9
+
10
+ extend Forwardable
11
+ def_delegators :@ratio, :antecedent, :consequent, :to_cents, :to_basic_ratio, :to_f
12
+
13
+ attr_reader :ratio
14
+
15
+ def initialize(ratio:)
16
+ raise ArgumentError, "Tonal::Ratio required" unless ratio.kind_of?(Tonal::Ratio)
17
+ @ratio = ratio
18
+ end
19
+
20
+ # @return [Tonal::Ratio::Approximation::Set] of ratios within cent tolerance of self found using continued fraction approximation
21
+ # @example
22
+ # Tonal::Ratio.ed(12,1).approximate.by_continued_fraction
23
+ # => (4771397596969315/4503599627370496): [(18/17), (196/185), (1657/1564), (7893/7450), (18904/17843), (3118/2943), (1461/1379), (89/84), (17/16)]
24
+ # @param cents_tolerance the cents tolerance used to scope the collection
25
+ # @param depth the maximum number of ratios in the collection
26
+ # @param max_prime the maximum prime number to allow in the collection
27
+ # @param conv_limit the number of convergents to limit the ContinuedFraction method
28
+ #
29
+ def by_continued_fraction(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME, conv_limit: CONVERGENT_LIMIT)
30
+ self_in_cents = to_cents
31
+ within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
32
+ Set.new(ratio: ratio).tap do |set|
33
+ ContinuedFraction.new(antecedent.to_f/consequent, conv_limit).convergents.each do |convergent|
34
+ ratio2 = ratio.class.new(convergent.numerator,convergent.denominator)
35
+ set.ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && ratio2.within_prime?(max_prime)
36
+ break if set.length >= depth
37
+ end
38
+ end
39
+ end
40
+
41
+ # @return [Tonal::Ratio::Approximation::Set] of ratios within cent tolerance of self found using a quotient walk on the fraction tree
42
+ # @example
43
+ # Tonal::Ratio.ed(12,1).approximate.by_quotient_walk(max_prime: 89)
44
+ # => (4771397596969315/4503599627370496): [(18/17), (196/185), (89/84), (71/67), (53/50), (35/33), (17/16)]
45
+ # @param cents_tolerance the cents tolerance used to scope the collection
46
+ # @param depth the maximum number of ratios in the collection
47
+ # @param max_prime the maximum prime number to allow in the collection
48
+ # @param conv_limit the number of convergents to limit the ContinuedFraction method
49
+ #
50
+ def by_quotient_walk(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME, conv_limit: CONVERGENT_LIMIT)
51
+ self_in_cents = to_cents
52
+ within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
53
+
54
+ Set.new(ratio: ratio).tap do |set|
55
+ FractionTree.quotient_walk(to_f, limit: conv_limit).each do |node|
56
+ ratio2 = ratio.class.new(node.weight)
57
+ set.ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && ratio2.within_prime?(max_prime)
58
+ break if set.length >= depth
59
+ end
60
+ end
61
+ end
62
+
63
+ # @return [Tonal::Ratio::Approximation::Set] of fraction tree ratios within cent tolerance of self
64
+ # @example
65
+ # Tonal::Ratio.ed(12,1).approximate.by_tree_path(max_prime: 17)
66
+ # => (4771397596969315/4503599627370496): [(18/17), (35/33), (17/16)]
67
+ # @param cents_tolerance the cents tolerance used to scope the collection
68
+ # @param depth the maximum number of ratios in the collection
69
+ # @param max_prime the maximum prime number to allow in the collection
70
+ #
71
+ def by_tree_path(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME)
72
+ self_in_cents = to_cents
73
+ within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
74
+ Set.new(ratio: ratio).tap do |set|
75
+ FractionTree.path_to(to_f).each do |node|
76
+ ratio2 = ratio.class.new(node.weight)
77
+ set.ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && ratio2.within_prime?(max_prime)
78
+ break if set.length >= depth
79
+ end
80
+ end
81
+ end
82
+
83
+ # @return [Tonal::Ratio::Approximation::Set] of superparticular approximations within cent tolerance of self
84
+ # @example
85
+ # Tonal::Ratio.new(3/2r).approximate.by_superparticular
86
+ # => (3/2): [(1098/730), (1095/728), (1092/726), (1089/724), (1086/722), (1083/720), (1080/718), (1077/716), (1074/714), (1071/712), (1068/710), (1065/708), (1062/706), (1059/704), (1056/702), (1053/700), (1050/698), (1047/696), (1044/694), (1041/692)]
87
+ # @param cents_tolerance the cents tolerance used to scope the collection
88
+ # @param depth the maximum number of ratios in the collection
89
+ # @param max_prime the maximum prime number to allow in the collection
90
+ # @param superpart if the superior part is the numerator or denominator
91
+ #
92
+ def by_superparticular(cents_tolerance: Tonal::Cents::TOLERANCE, depth: 20, max_prime: DEFAULT_MAX_PRIME, superpart: :upper)
93
+ self_in_cents = to_cents
94
+ within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
95
+ Set.new(ratio: ratio).tap do |set|
96
+ n = 1
97
+ while true do
98
+ ratio2 = ratio.class.superparticular(n, factor: ratio.to_r, superpart:)
99
+ set.ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && ratio2.within_prime?(max_prime) && ratio2 != ratio
100
+ break if set.length >= depth
101
+ n += 1
102
+ end
103
+ end
104
+ end
105
+
106
+ # @return [Array] of ratios within cent tolerance of self found on the ratio grid
107
+ # @example
108
+ # Tonal::Ratio.new(3,2).approximate.by_neighborhood(max_prime: 23, cents_tolerance: 5, max_boundary: 10, max_scale: 60)
109
+ # => (3/2): [(175/117), (176/117)]
110
+ # @param cents_tolerance the cents tolerance used to scope the collection
111
+ # @param depth the maximum number of ratios in the collection
112
+ # @param max_prime the maximum prime number to allow in the collection
113
+ # @param max_boundary the maximum distance grid ratios will be from the scaled ratio
114
+ # @param max_scale the maximum self will be scaled
115
+ #
116
+ def by_neighborhood(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME, max_boundary: DEFAULT_MAX_GRID_BOUNDARY, max_scale: DEFAULT_MAX_GRID_SCALE)
117
+ self_in_cents = to_cents
118
+ within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
119
+ Set.new(ratio: ratio).tap do |set|
120
+ scale = 1
121
+ boundary = 1
122
+
123
+ while set.length <= depth && scale <= max_scale do
124
+ while boundary <= max_boundary
125
+ vacinity = ratio.respond_to?(:to_basic_ratio) ? to_basic_ratio.scale(scale) : ratio.scale(scale)
126
+ self.class.neighbors(away: boundary, vacinity: vacinity).each do |neighbor|
127
+ set.ratios << neighbor if ratio.class.within_cents?(self_in_cents, neighbor.to_cents, within) && neighbor.within_prime?(max_prime) && neighbor != ratio
128
+ end
129
+ boundary += 1
130
+ end
131
+ boundary = 1
132
+ scale += 1
133
+ end
134
+ end
135
+ end
136
+
137
+ # @return [Array] of bounding ratios in the ratio grid vacinity of antecedent/consequent scaled by scale
138
+ # @example
139
+ # Tonal::ReducedRatio.new(3,2).neighborhood(scale: 256, boundary: 2)
140
+ # => [(768/514), (766/512), (768/513), (767/512), (768/512), (769/512), (768/511), (770/512), (768/510)]
141
+ # @param scale [Integer] used to scale antecedent/consequent on coordinate system
142
+ # @param boundary [Integer] limit within which to calculate neighboring ratios
143
+ #
144
+ def neighborhood(scale: 2**0, boundary: 1)
145
+ scale = scale.round
146
+ vacinity = ratio.respond_to?(:to_basic_ratio) ? to_basic_ratio.scale(scale) : ratio.scale(scale)
147
+ SortedSet.new([].tap do |ratio_list|
148
+ 1.upto(boundary) do |away|
149
+ ratio_list << self.class.neighbors(away: away, vacinity: vacinity)
150
+ end
151
+ end.flatten).to_a
152
+ end
153
+
154
+ # @return [Array] an array of Tonal::Ratio neighbors in the scaled ratio's grid neighborhood
155
+ # @example
156
+ # Tonal::Ratio::Approximation.neighbors(vacinity: (3/2r).ratio(reduced:false).scale(256), away: 1)
157
+ # => [(768/513), (767/512), (768/512), (769/512), (768/511)]
158
+ # @param away [Integer] the neighbors distance away from self's antecedent and consequent
159
+ #
160
+ def self.neighbors(vacinity:, away: 1)
161
+ [vacinity,
162
+ vacinity.class.new(vacinity.antecedent+away, vacinity.consequent),
163
+ vacinity.class.new(vacinity.antecedent-away, vacinity.consequent),
164
+ vacinity.class.new(vacinity.antecedent, vacinity.consequent+away),
165
+ vacinity.class.new(vacinity.antecedent, vacinity.consequent-away),
166
+ vacinity.class.new(vacinity.antecedent+away, vacinity.consequent+away),
167
+ vacinity.class.new(vacinity.antecedent+away, vacinity.consequent-away),
168
+ vacinity.class.new(vacinity.antecedent-away, vacinity.consequent+away),
169
+ vacinity.class.new(vacinity.antecedent-away, vacinity.consequent-away)]
170
+ end
171
+
172
+ class Set
173
+ extend Forwardable
174
+ def_delegators :@ratios, :count, :length, :min, :max, :entries, :all?, :any?, :reject, :map
175
+
176
+ attr_reader :ratios, :ratio
177
+
178
+ def initialize(ratio:)
179
+ @ratio = ratio
180
+ @ratios = SortedSet.new
181
+ end
182
+ alias :approximations :entries
183
+
184
+ def inspect
185
+ "#{ratio}: #{entries}"
186
+ end
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,4 @@
1
+ module Tonal
2
+ TOOLS_PRODUCER = "mTonal"
3
+ TOOLS_VERSION = "2.0.1"
4
+ end
data/lib/tonal/cents.rb CHANGED
@@ -9,7 +9,6 @@ class Tonal::Cents
9
9
  CENT_SCALE = 1200.0
10
10
  TOLERANCE = 5
11
11
  PRECISION = 2
12
- DEFAULT_ROUNDING_PRECISION = 2
13
12
 
14
13
  attr_reader :log, :ratio
15
14
 
@@ -100,7 +99,7 @@ class Tonal::Cents
100
99
  # https://embeddeduse.com/2019/08/26/qt-compare-two-floats/
101
100
  #
102
101
  def <=>(rhs)
103
- rhs.kind_of?(self.class) ? value.round(2) <=> rhs.value.round(2) : value.round(2) <=> rhs.round(2)
102
+ rhs.kind_of?(self.class) ? value.round(PRECISION) <=> rhs.value.round(PRECISION) : value.round(PRECISION) <=> rhs.round(PRECISION)
104
103
  end
105
104
 
106
105
  private
@@ -324,11 +324,11 @@ class Array
324
324
  def denominators = self.map(&:denominator)
325
325
  alias :consequents :denominators
326
326
 
327
- # @return [Array] an array of normalized ratios
327
+ # @return [Array] an array of ratios with equalized denominators
328
328
  # @example
329
- # [4/3r, 3/2r].normalize => [(8/6), (9/6)]
329
+ # [4/3r, 3/2r].denominize => [(8/6), (9/6)]
330
330
  #
331
- def normalize
331
+ def denominize
332
332
  l = denominators.lcm
333
333
  map{|r| Tonal::Ratio.new(l / r.denominator * r.numerator, l)}
334
334
  end
@@ -349,8 +349,9 @@ class Array
349
349
  # @return [Tonal::ReducedRatio] ratio reconstructed from the result of a prime factor decomposition
350
350
  # @example
351
351
  # [[[3, 1]], [[2, 1]]].ratio_from_prime_divisions => (3/2)
352
+ # @reduced [Boolean] if a reduced or unreduced ratio is returned
352
353
  #
353
- def ratio_from_prime_divisions = Tonal::Ratio.new(Prime.int_from_prime_division(self.first), Prime.int_from_prime_division(self.last))
354
+ def ratio_from_prime_divisions(reduced: false) = reduced ? Tonal::ReducedRatio.new(Prime.int_from_prime_division(self.first), Prime.int_from_prime_division(self.last)) : Tonal::Ratio.new(Prime.int_from_prime_division(self.first), Prime.int_from_prime_division(self.last))
354
355
 
355
356
  # @return [Array] translated by value
356
357
  # @example
@@ -2,7 +2,7 @@ class Tonal::Interval
2
2
  extend Forwardable
3
3
  include Comparable
4
4
 
5
- def_delegators :@interval, :to_r, :antecedent, :consequent
5
+ def_delegators :@interval, :to_r, :antecedent, :consequent, :to_cents
6
6
 
7
7
  attr_reader :lower_ratio, :upper_ratio, :interval
8
8
 
@@ -13,6 +13,7 @@ class Tonal::Interval
13
13
  @upper_ratio = upper_ratio.ratio
14
14
  @interval = @upper_ratio / @lower_ratio
15
15
  end
16
+ alias :ratio :interval
16
17
  alias :lower :lower_ratio
17
18
  alias :upper :upper_ratio
18
19
  alias :numerator :antecedent
@@ -22,7 +23,7 @@ class Tonal::Interval
22
23
  [lower_ratio, upper_ratio]
23
24
  end
24
25
 
25
- def normalize
26
+ def denominize
26
27
  ratios = to_a
27
28
  lcm = ratios.denominators.lcm
28
29
  ratios.map{|r| Tonal::Ratio.new(lcm / r.denominator * r.numerator, lcm)}
data/lib/tonal/ratio.rb CHANGED
@@ -2,7 +2,7 @@ class Tonal::Ratio
2
2
  extend Forwardable
3
3
  include Comparable
4
4
 
5
- def_delegators :@approximations, :neighborhood
5
+ def_delegators :@approximation, :neighborhood
6
6
 
7
7
  attr_reader :antecedent, :consequent, :equave, :reduced_antecedent, :reduced_consequent
8
8
 
@@ -27,29 +27,38 @@ class Tonal::Ratio
27
27
  @equave = equave
28
28
  @reduced_antecedent, @reduced_consequent = _equave_reduce(equave)
29
29
  @label = label
30
- @approximations = Approximations.new(ratio: self)
30
+ @approximation = Approximation.new(ratio: self)
31
31
  end
32
32
 
33
33
  alias :numerator :antecedent
34
34
  alias :denominator :consequent
35
35
 
36
- # @return [Tonal::Ratio] ratio who's numerator and denominator are 1 apart
36
+ # @return [Tonal::Ratio] ratio who's numerator and denominator are seperated by a difference of 1
37
37
  # @example
38
- # Tonal::Ratio.superparticular(100) = (100/99)
39
- # @param n numerator of ratio
38
+ # Tonal::Ratio.superparticular(100) = (101/100)
39
+ # @param n [Integer] number from which the superior part is calculated
40
+ # @param factor [Rational] multiplied into the resulting ratio, default 1/1
41
+ # @param superpart [Symbol] assigning the superior part to the antecedent or consequent
40
42
  #
41
- def self.superparticular(n)
42
- superpartient(n, 1)
43
+ def self.superparticular(n, factor: 1/1r, superpart: :upper)
44
+ superpartient(n, summand: 1, factor:, superpart:)
43
45
  end
44
46
 
45
- # @return [Tonal::Ratio] ratio who's numerator and denominator are a partient apart
47
+ # @return [Tonal::Ratio] ratio who's numerator and denominator are separated by a summand difference
46
48
  # @example
47
- # Tonal::Ratio.superpartient(100, 5) => (100/95)
48
- # @param n numerator of ratio
49
- # @param part partient separating the numerator and denominator
49
+ # Tonal::Ratio.superpartient(23, summand: 3) => (26/23)
50
+ # @param n [Integer] number from which the superior part is calculated
51
+ # @param summand [Integer] term added to the superior part
52
+ # @param factor [Rational] multiplied into the resulting ratio, default 1/1
53
+ # @param superpart [Symbol] assigning the superior part to the antecedent or consequent
50
54
  #
51
- def self.superpartient(n, part)
52
- self.new(n, n-part)
55
+ def self.superpartient(n, summand:, factor: 1/1r, superpart: :upper)
56
+ case superpart.to_sym.downcase
57
+ when :lower, :consequent, :denominator
58
+ self.new(n*factor.numerator, (n+summand)*factor.denominator)
59
+ else
60
+ self.new((n+summand)*factor.numerator, n*factor.denominator)
61
+ end
53
62
  end
54
63
 
55
64
  # @return [Tonal::Ratio] a randomly generated ratio
@@ -58,7 +67,7 @@ class Tonal::Ratio
58
67
  # @param number_of_factors
59
68
  # @param within
60
69
  #
61
- def self.random_ratio(number_of_factors = 2, within: 100)
70
+ def self.random_ratio(number_of_factors = 2, within: 100, reduced: false)
62
71
  primes = Prime.each(within).to_a
63
72
  nums = []
64
73
  dens = []
@@ -66,7 +75,7 @@ class Tonal::Ratio
66
75
  nums << [primes[rand(10)], rand(3)]
67
76
  dens << [primes[rand(10)], rand(3)]
68
77
  end
69
- [nums, dens].ratio_from_prime_divisions
78
+ [nums, dens].ratio_from_prime_divisions(reduced:)
70
79
  end
71
80
 
72
81
  # @return [Tonal::Ratio] the ratio of step in the modulo
@@ -98,10 +107,10 @@ class Tonal::Ratio
98
107
  self
99
108
  end
100
109
 
101
- # @return [Tonal::Ratio::Approximations] self's approximation instance
110
+ # @return [Tonal::Ratio::Approximation] self's approximation instance
102
111
  #
103
- def approx
104
- @approximations
112
+ def approximate
113
+ @approximation
105
114
  end
106
115
 
107
116
  # ==================================
@@ -264,15 +273,6 @@ class Tonal::Ratio
264
273
  (self.class.new(axis) ** 2) / self
265
274
  end
266
275
 
267
- # @return [Tonal::Ratio]
268
- # @example
269
- # Tonal::Ratio.new(4/3r).mirror2(4/2r) => (3/8)
270
- # @param ratio
271
- #
272
- def mirror2(ratio)
273
- self.class.new(invert.to_r / ratio)
274
- end
275
-
276
276
  # @return Tonal::ReducedRatio the Ernst Levy negative of self
277
277
  # @example
278
278
  # Tonal::ReducedRatio.new(7/4r).negative => (12/7)
@@ -434,7 +434,7 @@ class Tonal::Ratio
434
434
  # @param modulo
435
435
  #
436
436
  def efficiency(modulo)
437
- (Tonal::Cents::CENT_SCALE * step(modulo).step / modulo.to_f) - to_cents
437
+ ((Tonal::Cents::CENT_SCALE * step(modulo).step / modulo.to_f) - to_cents).round(Tonal::Cents::PRECISION)
438
438
  end
439
439
 
440
440
  # @return [Array] the results of ratio dividing and multiplying self
@@ -473,7 +473,7 @@ class Tonal::Ratio
473
473
  # Return label, if defined; or,
474
474
  # Return the "antecedent/consequent", if antecedent is less than 7 digits long; or
475
475
  # Return the floating point representation rounded to 2 digits of precision
476
- (@label || ((Math.log10(antecedent).to_i + 1) <= 6 ? "#{antecedent}/#{consequent}" : to_f.round(2))).to_s
476
+ (@label || ((Math.log10(antecedent).to_i + 1) <= 6 ? "#{antecedent}/#{consequent}" : to_f.round(Tonal::Cents::PRECISION))).to_s
477
477
  end
478
478
 
479
479
  # @return [String] the string representation of Tonal::Ratio
data/lib/tonal/tools.rb CHANGED
@@ -11,7 +11,7 @@ module Tonal
11
11
  require "tonal/hertz"
12
12
  require "tonal/log"
13
13
  require "tonal/log2"
14
- require "tonal/approximations"
14
+ require "tonal/approximation"
15
15
  require "tonal/ratio"
16
16
  require "tonal/reduced_ratio"
17
17
  require "tonal/interval"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: tonal-tools
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jose Hales-Garcia
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-01-23 00:00:00.000000000 Z
11
+ date: 2024-02-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: yaml
@@ -158,7 +158,8 @@ extensions: []
158
158
  extra_rdoc_files: []
159
159
  files:
160
160
  - data/commas.yml
161
- - lib/tonal/approximations.rb
161
+ - lib/tonal/approximation.rb
162
+ - lib/tonal/attributions.rb
162
163
  - lib/tonal/cents.rb
163
164
  - lib/tonal/comma.rb
164
165
  - lib/tonal/extensions.rb
@@ -191,7 +192,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
191
192
  - !ruby/object:Gem::Version
192
193
  version: '3.1'
193
194
  requirements: []
194
- rubygems_version: 3.5.4
195
+ rubygems_version: 3.5.5
195
196
  signing_key:
196
197
  specification_version: 4
197
198
  summary: Tonal tools
@@ -1,148 +0,0 @@
1
- class Tonal::Ratio
2
- class Approximations
3
- DEFAULT_MAX_PRIME = Float::INFINITY
4
- DEFAULT_MAX_GRID_SCALE = 100
5
- DEFAULT_MAX_GRID_BOUNDARY = 5
6
- DEFAULT_DEPTH = Float::INFINITY
7
- DEFAULT_COMPLEXITY_AMOUNT = 50.0
8
- CONVERGENT_LIMIT = 10
9
-
10
- extend Forwardable
11
- def_delegators :@ratio, :antecedent, :consequent, :to_cents, :to_basic_ratio, :to_f
12
-
13
- attr_reader :ratio
14
-
15
- def initialize(ratio:)
16
- raise ArgumentError, "Tonal::Ratio required" unless ratio.kind_of?(Tonal::Ratio)
17
- @ratio = ratio
18
- end
19
-
20
- # @return [Array] of ratios within cent tolerance of self found using continued fraction approximation
21
- # @example
22
- # Tonal::Ratio.ed(12,1).by_continued_fraction
23
- # => [(18/17), (196/185), (1657/1564), (7893/7450), (18904/17843), (3118/2943), (1461/1379), (89/84), (17/16)]
24
- # @param cents_tolerance
25
- # @param depth
26
- # @param max_prime
27
- # @param conv_limit
28
- #
29
- def by_continued_fraction(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME, conv_limit: CONVERGENT_LIMIT)
30
- self_in_cents = to_cents
31
- within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
32
- [].tap do |results|
33
- ContinuedFraction.new(antecedent.to_f/consequent, conv_limit).convergents.each do |convergent|
34
- ratio2 = ratio.class.new(convergent.numerator,convergent.denominator)
35
- results << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && ratio2.within_prime?(max_prime)
36
- break if results.length >= depth
37
- end
38
- end.sort
39
- end
40
-
41
- # @return [Array] of ratios within cent tolerance of self found using a quotient walk on the fraction tree
42
- # @example
43
- # Tonal::Ratio.ed(12,1).by_quotient_walk(max_prime: 89)
44
- # => [(18/17), (196/185), (89/84), (71/67), (53/50), (35/33), (17/16)]
45
- # @param cents_tolerance
46
- # @param depth
47
- # @param max_prime
48
- #
49
- def by_quotient_walk(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME, conv_limit: CONVERGENT_LIMIT)
50
- self_in_cents = to_cents
51
- within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
52
-
53
- [].tap do |results|
54
- FractionTree.quotient_walk(to_f, limit: conv_limit).each do |node|
55
- ratio2 = ratio.class.new(node.weight)
56
- results << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && ratio2.within_prime?(max_prime)
57
- break if results.length >= depth
58
- end
59
- end.sort
60
- end
61
-
62
- # @return [Array] of fraction tree ratios within cent tolerance of self
63
- # @example
64
- # Tonal::Ratio.ed(12,1).by_tree_path(max_prime: 17)
65
- # => [(18/17), (35/33), (17/16)]
66
- # @param cents_tolerance
67
- # @param depth
68
- # @param max_prime
69
- #
70
- def by_tree_path(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME)
71
- self_in_cents = to_cents
72
- within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
73
- [].tap do |results|
74
- FractionTree.path_to(to_f).each do |node|
75
- ratio2 = ratio.class.new(node.weight)
76
- results << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && ratio2.within_prime?(max_prime)
77
- break if results.length >= depth
78
- end
79
- end.sort
80
- end
81
-
82
- # @return [Array] of ratios within cent tolerance of self found on the ratio grid
83
- # @example
84
- # Tonal::Ratio.new(3,2).by_neighborhood(max_prime: 23, cents_tolerance: 5, max_boundary: 10, max_scale: 60)
85
- # => [(175/117), (176/117)]
86
- # @param cents_tolerance the maximum cents self is allowed from grid ratios
87
- # @param depth the maximum depth the array will get
88
- # @param max_prime the maximum prime the grid ratios will contain
89
- # @param max_boundary the maximum distance grid ratios will be from the scaled ratio
90
- # @param max_scale the maximum self will be scaled
91
- #
92
- def by_neighborhood(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME, max_boundary: DEFAULT_MAX_GRID_BOUNDARY, max_scale: DEFAULT_MAX_GRID_SCALE)
93
- self_in_cents = to_cents
94
- within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
95
- [].tap do |results|
96
- scale = 1
97
- boundary = 1
98
-
99
- while results.length <= depth && scale <= max_scale do
100
- while boundary <= max_boundary
101
- vacinity = ratio.respond_to?(:to_basic_ratio) ? to_basic_ratio.scale(scale) : ratio.scale(scale)
102
- self.class.neighbors(away: boundary, vacinity: vacinity).each do |neighbor|
103
- results << neighbor if ratio.class.within_cents?(self_in_cents, neighbor.to_cents, within) && neighbor.within_prime?(max_prime)
104
- end
105
- boundary += 1
106
- end
107
- boundary = 1
108
- scale += 1
109
- end
110
- end.uniq(&:to_r).reject{|r| r == ratio}.sort
111
- end
112
-
113
- # @return [Array] of bounding ratios in the ratio grid vacinity of antecedent/consequent scaled by scale
114
- # @example
115
- # Tonal::ReducedRatio.new(3,2).neighborhood(scale: 256, boundary: 2)
116
- # => [(768/514), (766/512), (768/513), (767/512), (768/512), (769/512), (768/511), (770/512), (768/510)]
117
- # @param scale [Integer] used to scale antecedent/consequent on coordinate system
118
- # @param boundary [Integer] limit within which to calculate neighboring ratios
119
- #
120
- def neighborhood(scale: 2**0, boundary: 1)
121
- scale = scale.round
122
- vacinity = ratio.respond_to?(:to_basic_ratio) ? to_basic_ratio.scale(scale) : ratio.scale(scale)
123
- SortedSet.new([].tap do |ratio_list|
124
- 1.upto(boundary) do |away|
125
- ratio_list << self.class.neighbors(away: away, vacinity: vacinity)
126
- end
127
- end.flatten).to_a
128
- end
129
-
130
- # @return [Array] an array of Tonal::Ratio neighbors in the scaled ratio's grid neighborhood
131
- # @example
132
- # Tonal::Ratio::Approximations.neighbors(vacinity: (3/2r).ratio(reduced:false).scale(256), away: 1)
133
- # => [(768/513), (767/512), (768/512), (769/512), (768/511)]
134
- # @param away [Integer] the neighbors distance away from self's antecedent and consequent
135
- #
136
- def self.neighbors(vacinity:, away: 1)
137
- [vacinity,
138
- vacinity.class.new(vacinity.antecedent+away, vacinity.consequent),
139
- vacinity.class.new(vacinity.antecedent-away, vacinity.consequent),
140
- vacinity.class.new(vacinity.antecedent, vacinity.consequent+away),
141
- vacinity.class.new(vacinity.antecedent, vacinity.consequent-away),
142
- vacinity.class.new(vacinity.antecedent+away, vacinity.consequent+away),
143
- vacinity.class.new(vacinity.antecedent+away, vacinity.consequent-away),
144
- vacinity.class.new(vacinity.antecedent-away, vacinity.consequent+away),
145
- vacinity.class.new(vacinity.antecedent-away, vacinity.consequent-away)]
146
- end
147
- end
148
- end