tonal-tools 8.8.3 → 8.9.0
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 +22 -4
- data/lib/tonal/attributions.rb +1 -1
- data/lib/tonal/extensions.rb +11 -19
- data/lib/tonal/ratio.rb +38 -13
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ff37192dbedd21f500728f844d7d684053161434a23dbe5625fcf0f7de39da4e
|
|
4
|
+
data.tar.gz: 94670893a608e9fd758a769b34513d31fc8cfa7c19220dd77d887b2d504be7b5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e2d30aa302feae75b7efd5e225a7a3363a6dd1fe3b04929c5ceaeaec6daf69dfbc703af3c8710525ddc062145a10a7cae35e72d815d2bd996315445538892242
|
|
7
|
+
data.tar.gz: f57a435920ae18d8541e5eea3053f56c18d61468383b5a9eb006f9bb35595c06941c5442b7d6146fbb2de33efd05a87700285b902096ec32959b6581e202edb4
|
data/lib/tonal/approximation.rb
CHANGED
|
@@ -38,7 +38,7 @@ class Tonal::Ratio
|
|
|
38
38
|
Set.new(ratio: ratio) do |ratios|
|
|
39
39
|
ContinuedFraction.new(antecedent.to_f/consequent, conv_limit).convergents.each do |convergent|
|
|
40
40
|
ratio2 = ratio.class.new(convergent.numerator,convergent.denominator)
|
|
41
|
-
ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && (ratio2
|
|
41
|
+
ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && max_prime_at_most?(ratio2, max_prime) && !prime_factor_at_most?(ratio2, min_prime - 1)
|
|
42
42
|
break if ratios.length >= depth
|
|
43
43
|
end
|
|
44
44
|
end
|
|
@@ -61,7 +61,7 @@ class Tonal::Ratio
|
|
|
61
61
|
FractionTree.node(to_f).path.each do |node|
|
|
62
62
|
next if node.number.infinite?
|
|
63
63
|
ratio2 = ratio.class.new(node.number)
|
|
64
|
-
ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && (ratio2
|
|
64
|
+
ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && max_prime_at_most?(ratio2, max_prime) && !prime_factor_at_most?(ratio2, min_prime - 1)
|
|
65
65
|
break if ratios.length >= depth
|
|
66
66
|
end
|
|
67
67
|
end
|
|
@@ -78,13 +78,15 @@ class Tonal::Ratio
|
|
|
78
78
|
# @param superpart if the superior part is the numerator or denominator
|
|
79
79
|
#
|
|
80
80
|
def by_superparticular(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_SUPERPART_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME, superpart: :upper)
|
|
81
|
+
# One of n and n+1 is even, so every candidate has 2 as a factor and none can meet a higher min_prime
|
|
82
|
+
return Set.new(ratio: ratio) if min_prime > 2
|
|
81
83
|
self_in_cents = to_cents
|
|
82
84
|
within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
|
|
83
85
|
Set.new(ratio: ratio) do |ratios|
|
|
84
86
|
n = 1
|
|
85
87
|
while true do
|
|
86
88
|
ratio2 = ratio.class.superparticular(n, factor: ratio.to_r, superpart:)
|
|
87
|
-
ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && (ratio2 != ratio) && (ratio2
|
|
89
|
+
ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && (ratio2 != ratio) && max_prime_at_most?(ratio2, max_prime) && !prime_factor_at_most?(ratio2, min_prime - 1)
|
|
88
90
|
break if ratios.length >= depth
|
|
89
91
|
n += 1
|
|
90
92
|
end
|
|
@@ -113,7 +115,7 @@ class Tonal::Ratio
|
|
|
113
115
|
while boundary <= max_boundary
|
|
114
116
|
vacinity = ratio.respond_to?(:to_basic_ratio) ? to_basic_ratio.scale(scale) : ratio.scale(scale)
|
|
115
117
|
self.class.neighbors(away: boundary, vacinity: vacinity).each do |neighbor|
|
|
116
|
-
ratios << neighbor if (neighbor != ratio) && ratio.class.within_cents?(self_in_cents, neighbor.to_cents, within) && (neighbor
|
|
118
|
+
ratios << neighbor if (neighbor != ratio) && ratio.class.within_cents?(self_in_cents, neighbor.to_cents, within) && max_prime_at_most?(neighbor, max_prime) && !prime_factor_at_most?(neighbor, min_prime - 1)
|
|
117
119
|
end
|
|
118
120
|
boundary += 1
|
|
119
121
|
end
|
|
@@ -140,6 +142,22 @@ class Tonal::Ratio
|
|
|
140
142
|
end.flatten).to_a
|
|
141
143
|
end
|
|
142
144
|
|
|
145
|
+
# Filters approximations without fully factoring them (a Float's convergents
|
|
146
|
+
# can have huge prime factors). Float::INFINITY skips the factoring.
|
|
147
|
+
def max_prime_at_most?(candidate, max_prime)
|
|
148
|
+
return true if max_prime.infinite?
|
|
149
|
+
Prime.each(max_prime).inject([candidate.antecedent, candidate.consequent]) do |(ante, cons), p|
|
|
150
|
+
ante /= p while (ante % p).zero?
|
|
151
|
+
cons /= p while (cons % p).zero?
|
|
152
|
+
[ante, cons]
|
|
153
|
+
end.all?{|n| n == 1 }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def prime_factor_at_most?(candidate, prime)
|
|
157
|
+
Prime.each(prime).any?{|p| (candidate.antecedent % p).zero? || (candidate.consequent % p).zero? }
|
|
158
|
+
end
|
|
159
|
+
private :max_prime_at_most?, :prime_factor_at_most?
|
|
160
|
+
|
|
143
161
|
# @return [Array] an array of Tonal::Ratio neighbors in the scaled ratio's grid neighborhood
|
|
144
162
|
# @example
|
|
145
163
|
# Tonal::Ratio::Approximation.neighbors(vacinity: (3/2r).ratio(reduced:false).scale(256), away: 1)
|
data/lib/tonal/attributions.rb
CHANGED
data/lib/tonal/extensions.rb
CHANGED
|
@@ -180,28 +180,32 @@ class Numeric
|
|
|
180
180
|
# @example
|
|
181
181
|
# (3/2r).prime_vector => Vector[-1, 1]
|
|
182
182
|
# @param reduced
|
|
183
|
+
# @param limit [Integer] the largest prime to factor by, see Tonal::Ratio#prime_divisions
|
|
183
184
|
#
|
|
184
|
-
def prime_vector(reduced: false) = ratio(reduced:).prime_vector
|
|
185
|
+
def prime_vector(reduced: false, limit: Tonal::Ratio::PRIME_LIMIT) = ratio(reduced:).prime_vector(limit:)
|
|
185
186
|
alias :monzo :prime_vector
|
|
186
187
|
alias :prime_exponent_vector :prime_vector
|
|
187
188
|
|
|
188
|
-
# @return [Array], self decomposed into its prime factors
|
|
189
|
+
# @return [Array, nil], self decomposed into its prime factors, or nil if self has a prime factor above limit
|
|
189
190
|
# @example
|
|
190
191
|
# (31/30r).prime_divisions => [[[31, 1]], [[2, 1], [3, 1], [5, 1]]]
|
|
192
|
+
# @param limit [Integer] the largest prime to factor by, see Tonal::Ratio#prime_divisions
|
|
191
193
|
#
|
|
192
|
-
def prime_divisions =
|
|
194
|
+
def prime_divisions(limit: Tonal::Ratio::PRIME_LIMIT) = ratio.prime_divisions(limit:)
|
|
193
195
|
|
|
194
|
-
# @return [Integer] the maximum prime factor of self
|
|
196
|
+
# @return [Integer, nil] the maximum prime factor of self, or nil if self has a prime factor above limit
|
|
195
197
|
# @example
|
|
196
198
|
# (31/30r).max_prime => 31
|
|
199
|
+
# @param limit [Integer] the largest prime to factor by, see Tonal::Ratio#prime_divisions
|
|
197
200
|
#
|
|
198
|
-
def max_prime =
|
|
201
|
+
def max_prime(limit: Tonal::Ratio::PRIME_LIMIT) = ratio.max_prime(limit:)
|
|
199
202
|
|
|
200
|
-
# @return [Integer] the minimum prime factor of self
|
|
203
|
+
# @return [Integer, nil] the minimum prime factor of self, or nil if self has a prime factor above limit
|
|
201
204
|
# @example
|
|
202
205
|
# (31/30r).min_prime => 2
|
|
206
|
+
# @param limit [Integer] the largest prime to factor by, see Tonal::Ratio#prime_divisions
|
|
203
207
|
#
|
|
204
|
-
def min_prime =
|
|
208
|
+
def min_prime(limit: Tonal::Ratio::PRIME_LIMIT) = ratio.min_prime(limit:)
|
|
205
209
|
|
|
206
210
|
# @return [Vector], self expressed as a Vector
|
|
207
211
|
# @example
|
|
@@ -271,18 +275,6 @@ class Integer
|
|
|
271
275
|
#
|
|
272
276
|
def ed(modulo, equave: 2/1r) = Tonal::ReducedRatio.ed(self, modulo, equave:)
|
|
273
277
|
|
|
274
|
-
# @return [Integer] the maximum prime factor of self
|
|
275
|
-
# @example
|
|
276
|
-
# 72.max_prime => 3
|
|
277
|
-
#
|
|
278
|
-
def max_prime = self.prime_division.map(&:first).max
|
|
279
|
-
|
|
280
|
-
# @return [Integer] the minimum prime factor of self
|
|
281
|
-
# @example
|
|
282
|
-
# 72.min_prime => 2
|
|
283
|
-
#
|
|
284
|
-
def min_prime = self.prime_division.map(&:first).min
|
|
285
|
-
|
|
286
278
|
# @return [Integer] the factorial of self
|
|
287
279
|
# @example
|
|
288
280
|
# 5.factorial => 120
|
data/lib/tonal/ratio.rb
CHANGED
|
@@ -6,6 +6,7 @@ class Tonal::Ratio
|
|
|
6
6
|
|
|
7
7
|
PRECISION = 2
|
|
8
8
|
DEFAULT_EQUAVE = 2/1r
|
|
9
|
+
PRIME_LIMIT = 10_000
|
|
9
10
|
|
|
10
11
|
attr_reader :antecedent, :consequent, :equave, :reduced_antecedent, :reduced_consequent
|
|
11
12
|
|
|
@@ -356,21 +357,25 @@ class Tonal::Ratio
|
|
|
356
357
|
Math.atan2(consequent, antecedent).round(round)
|
|
357
358
|
end
|
|
358
359
|
|
|
359
|
-
# @return [Array], self decomposed into its prime factors
|
|
360
|
+
# @return [Array, nil], self decomposed into its prime factors, or nil if self has a prime factor above limit (e.g. an irrational ratio built from a Float)
|
|
360
361
|
# @example
|
|
361
362
|
# Tonal::Ratio.new(31/30r).prime_divisions => [[[31, 1]], [[2, 1], [3, 1], [5, 1]]]
|
|
363
|
+
# @param limit [Integer] the largest prime to factor by
|
|
362
364
|
#
|
|
363
|
-
def prime_divisions
|
|
364
|
-
|
|
365
|
+
def prime_divisions(limit: PRIME_LIMIT)
|
|
366
|
+
ante = prime_division_within(antecedent, limit)
|
|
367
|
+
cons = prime_division_within(consequent, limit)
|
|
368
|
+
[ante, cons] if ante && cons
|
|
365
369
|
end
|
|
366
370
|
|
|
367
|
-
# @return [Vector], self represented as a prime vector
|
|
371
|
+
# @return [Vector, nil], self represented as a prime vector, or nil if self is 1/1 or has a prime factor above limit
|
|
368
372
|
# @example
|
|
369
373
|
# Tonal::Ratio.new(3/2r).prime_vector => Vector[-1, 1]
|
|
374
|
+
# @param limit [Integer] the largest prime to factor by
|
|
370
375
|
#
|
|
371
|
-
def prime_vector
|
|
372
|
-
pds = prime_divisions
|
|
373
|
-
return nil if pds.all?(&:empty?)
|
|
376
|
+
def prime_vector(limit: PRIME_LIMIT)
|
|
377
|
+
pds = prime_divisions(limit:)
|
|
378
|
+
return nil if pds.nil? || pds.all?(&:empty?)
|
|
374
379
|
|
|
375
380
|
max = [pds.first.max{|p| p.first}, pds.last.max{|p| p.first}].compact.max.first
|
|
376
381
|
|
|
@@ -384,20 +389,22 @@ class Tonal::Ratio
|
|
|
384
389
|
alias :monzo :prime_vector
|
|
385
390
|
alias :prime_exponent_vector :prime_vector
|
|
386
391
|
|
|
387
|
-
# @return [Integer] the maximum prime factor of self
|
|
392
|
+
# @return [Integer, nil] the maximum prime factor of self, or nil if self has a prime factor above limit
|
|
388
393
|
# @example
|
|
389
394
|
# Tonal::Ratio.new(31/30r).max_prime => 31
|
|
395
|
+
# @param limit [Integer] the largest prime to factor by
|
|
390
396
|
#
|
|
391
|
-
def max_prime
|
|
392
|
-
prime_divisions
|
|
397
|
+
def max_prime(limit: PRIME_LIMIT)
|
|
398
|
+
prime_divisions(limit:)&.flatten(1)&.map(&:first)&.max
|
|
393
399
|
end
|
|
394
400
|
|
|
395
|
-
# @return [Integer] the minimum prime factor of self
|
|
401
|
+
# @return [Integer, nil] the minimum prime factor of self, or nil if self has a prime factor above limit
|
|
396
402
|
# @example
|
|
397
403
|
# Tonal::Ratio.new(31/30r).min_prime => 2
|
|
404
|
+
# @param limit [Integer] the largest prime to factor by
|
|
398
405
|
#
|
|
399
|
-
def min_prime
|
|
400
|
-
prime_divisions
|
|
406
|
+
def min_prime(limit: PRIME_LIMIT)
|
|
407
|
+
prime_divisions(limit:)&.flatten(1)&.map(&:first)&.min
|
|
401
408
|
end
|
|
402
409
|
|
|
403
410
|
# =================================
|
|
@@ -684,6 +691,24 @@ class Tonal::Ratio
|
|
|
684
691
|
@approximation = Approximation.new(ratio: self)
|
|
685
692
|
end
|
|
686
693
|
|
|
694
|
+
# Trial-divides n by the primes up to limit, so a Float's huge binary
|
|
695
|
+
# fraction is rejected without a full (and very slow) factorization.
|
|
696
|
+
# A cofactor left below limit**2 has no factor up to limit, so it is prime.
|
|
697
|
+
def prime_division_within(n, limit)
|
|
698
|
+
division = []
|
|
699
|
+
Prime.each(limit) do |prime|
|
|
700
|
+
break if n <= 1
|
|
701
|
+
exponent = 0
|
|
702
|
+
while (n % prime).zero?
|
|
703
|
+
n /= prime
|
|
704
|
+
exponent += 1
|
|
705
|
+
end
|
|
706
|
+
division << [prime, exponent] if exponent > 0
|
|
707
|
+
end
|
|
708
|
+
return division if n <= 1
|
|
709
|
+
division << [n, 1] if n < limit**2
|
|
710
|
+
end
|
|
711
|
+
|
|
687
712
|
def raise_if_negative(*args)
|
|
688
713
|
raise ArgumentError, "Arguments must be greater than zero" if args.any?{|i| i < 0 }
|
|
689
714
|
end
|