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 +4 -4
- data/lib/tonal/approximation.rb +189 -0
- data/lib/tonal/attributions.rb +4 -0
- data/lib/tonal/cents.rb +1 -2
- data/lib/tonal/extensions.rb +5 -4
- data/lib/tonal/interval.rb +3 -2
- data/lib/tonal/ratio.rb +29 -29
- data/lib/tonal/tools.rb +1 -1
- metadata +5 -4
- data/lib/tonal/approximations.rb +0 -148
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 340dc7220c5ab24c65486eefc2ebb457d76c7c1ac64fd91a428b94a4fd84cd7d
|
4
|
+
data.tar.gz: 5721937407c18951a5b72eb075ffdbbfe8765abb3ae98695fc2af40e55277c3a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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(
|
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
|
data/lib/tonal/extensions.rb
CHANGED
@@ -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
|
327
|
+
# @return [Array] an array of ratios with equalized denominators
|
328
328
|
# @example
|
329
|
-
# [4/3r, 3/2r].
|
329
|
+
# [4/3r, 3/2r].denominize => [(8/6), (9/6)]
|
330
330
|
#
|
331
|
-
def
|
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
|
data/lib/tonal/interval.rb
CHANGED
@@ -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
|
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 :@
|
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
|
-
@
|
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
|
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
|
39
|
-
# @param n
|
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
|
47
|
+
# @return [Tonal::Ratio] ratio who's numerator and denominator are separated by a summand difference
|
46
48
|
# @example
|
47
|
-
# Tonal::Ratio.superpartient(
|
48
|
-
# @param n
|
49
|
-
# @param
|
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,
|
52
|
-
|
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::
|
110
|
+
# @return [Tonal::Ratio::Approximation] self's approximation instance
|
102
111
|
#
|
103
|
-
def
|
104
|
-
@
|
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(
|
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
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:
|
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-
|
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/
|
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.
|
195
|
+
rubygems_version: 3.5.5
|
195
196
|
signing_key:
|
196
197
|
specification_version: 4
|
197
198
|
summary: Tonal tools
|
data/lib/tonal/approximations.rb
DELETED
@@ -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
|