tonal-tools 1.3.1 → 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
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