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 +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
|