tonal-tools 0.1.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 +7 -0
- data/lib/tonal/approximations.rb +148 -0
- data/lib/tonal/cents.rb +145 -0
- data/lib/tonal/extensions.rb +464 -0
- data/lib/tonal/hertz.rb +61 -0
- data/lib/tonal/interval.rb +38 -0
- data/lib/tonal/log.rb +121 -0
- data/lib/tonal/log2.rb +6 -0
- data/lib/tonal/ratio.rb +600 -0
- data/lib/tonal/reduced_ratio.rb +37 -0
- data/lib/tonal/step.rb +67 -0
- data/lib/tonal/tools.rb +17 -0
- metadata +167 -0
data/lib/tonal/ratio.rb
ADDED
@@ -0,0 +1,600 @@
|
|
1
|
+
class Tonal::Ratio
|
2
|
+
extend Forwardable
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
def_delegators :@approximations, :neighborhood
|
6
|
+
|
7
|
+
attr_reader :antecedent, :consequent, :equave, :reduced_antecedent, :reduced_consequent
|
8
|
+
|
9
|
+
# @return [Tonal::Ratio]
|
10
|
+
# @example
|
11
|
+
# Tonal::Ratio.new(3,2) => (3/2)
|
12
|
+
# @param antecedent [Numeric, Tonal::Ratio]
|
13
|
+
# @param consequent [Numeric, Tonal::Ratio]
|
14
|
+
#
|
15
|
+
def initialize(antecedent, consequent=nil, label: nil, equave: 2/1r)
|
16
|
+
raise ArgumentError, "Antecedent must be Numeric" unless antecedent.kind_of?(Numeric)
|
17
|
+
raise ArgumentError, "Consequent must be Numeric or nil" unless (consequent.kind_of?(Numeric) || consequent.nil?)
|
18
|
+
|
19
|
+
if consequent
|
20
|
+
@antecedent = antecedent.abs
|
21
|
+
@consequent = consequent.abs
|
22
|
+
else
|
23
|
+
antecedent = antecedent.abs
|
24
|
+
@antecedent = antecedent.numerator
|
25
|
+
@consequent = antecedent.denominator
|
26
|
+
end
|
27
|
+
@equave = equave
|
28
|
+
@reduced_antecedent, @reduced_consequent = _equave_reduce(equave)
|
29
|
+
@label = label
|
30
|
+
@approximations = Approximations.new(ratio: self)
|
31
|
+
end
|
32
|
+
|
33
|
+
alias :numerator :antecedent
|
34
|
+
alias :denominator :consequent
|
35
|
+
|
36
|
+
# @return [Tonal::Ratio] ratio who's numerator and denominator are 1 apart
|
37
|
+
# @example
|
38
|
+
# Tonal::Ratio.superparticular(100) = (100/99)
|
39
|
+
# @param n numerator of ratio
|
40
|
+
#
|
41
|
+
def self.superparticular(n)
|
42
|
+
superpartient(n, 1)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Tonal::Ratio] ratio who's numerator and denominator are a partient apart
|
46
|
+
# @example
|
47
|
+
# Tonal::Ratio.superpartient(100, 5) => (100/95)
|
48
|
+
# @param n numerator of ratio
|
49
|
+
# @param part partient separating the numerator and denominator
|
50
|
+
#
|
51
|
+
def self.superpartient(n, part)
|
52
|
+
self.new(n, n-part)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @return [Tonal::Ratio] a randomly generated ratio
|
56
|
+
# @example
|
57
|
+
# Tonal::Ratio.random_ratio => (169/1)
|
58
|
+
# @param number_of_factors
|
59
|
+
# @param within
|
60
|
+
#
|
61
|
+
def self.random_ratio(number_of_factors = 2, within: 100)
|
62
|
+
primes = Prime.each(within).to_a
|
63
|
+
nums = []
|
64
|
+
dens = []
|
65
|
+
1.upto(number_of_factors) do
|
66
|
+
nums << [primes[rand(10)], rand(3)]
|
67
|
+
dens << [primes[rand(10)], rand(3)]
|
68
|
+
end
|
69
|
+
[nums, dens].ratio_from_prime_divisions
|
70
|
+
end
|
71
|
+
|
72
|
+
# @return [Tonal::Ratio] the ratio of step in the modulo
|
73
|
+
# @example
|
74
|
+
# Tonal::Ratio.ed(12, 7)
|
75
|
+
# => (4771397596969315/4503599627370496)
|
76
|
+
# @param modulo
|
77
|
+
# @param step
|
78
|
+
# @param equave
|
79
|
+
#
|
80
|
+
def self.ed(modulo, step, equave: 2/1r)
|
81
|
+
self.new(2**(step.to_f/modulo), equave: equave)
|
82
|
+
end
|
83
|
+
|
84
|
+
# @return [Boolean] if pair of ratios are within the given cents limit
|
85
|
+
# @example
|
86
|
+
# Tonal::Ratio.within_cents?(100, 105, 2) => true
|
87
|
+
# @param cents1
|
88
|
+
# @param cents2
|
89
|
+
# @param within
|
90
|
+
#
|
91
|
+
def self.within_cents?(cents1, cents2, within)
|
92
|
+
(cents1 - cents2).abs <= within
|
93
|
+
end
|
94
|
+
|
95
|
+
# @return [Tonal::Ratio] convenience method returning self
|
96
|
+
#
|
97
|
+
def ratio
|
98
|
+
self
|
99
|
+
end
|
100
|
+
|
101
|
+
# @return [Tonal::Ratio::Approximations] self's approximation instance
|
102
|
+
#
|
103
|
+
def approx
|
104
|
+
@approximations
|
105
|
+
end
|
106
|
+
|
107
|
+
# ==================================
|
108
|
+
# Conversions
|
109
|
+
# ==================================
|
110
|
+
|
111
|
+
# @return [Array] antecedent and consequent as elements of Array
|
112
|
+
# @example
|
113
|
+
# Tonal::Ratio.new(3,2).to_a => [3, 2]
|
114
|
+
#
|
115
|
+
def to_a
|
116
|
+
[antecedent, consequent]
|
117
|
+
end
|
118
|
+
|
119
|
+
# @return [Vector] antecedent and consequent as elements of Vector
|
120
|
+
# @example
|
121
|
+
# Tonal::Ratio.new(3,2).to_v => Vector[3, 2]
|
122
|
+
#
|
123
|
+
def to_v
|
124
|
+
Vector[antecedent, consequent]
|
125
|
+
end
|
126
|
+
|
127
|
+
# @return [Rational] self as a Rational
|
128
|
+
# @example
|
129
|
+
# Tonal::Ratio.new(3,2).to_r => (3/2)
|
130
|
+
#
|
131
|
+
def to_r
|
132
|
+
return Float::INFINITY if consequent.zero? || !antecedent.finite?
|
133
|
+
Rational(antecedent, consequent)
|
134
|
+
end
|
135
|
+
|
136
|
+
# @return [Float] self as a Float
|
137
|
+
# @example
|
138
|
+
# Tonal::Ratio.new(3,2).to_f => 1.5
|
139
|
+
#
|
140
|
+
def to_f
|
141
|
+
antecedent.to_f / consequent.to_f
|
142
|
+
end
|
143
|
+
|
144
|
+
# @return [Tonal::Log] Math.log of self in given base
|
145
|
+
# @example
|
146
|
+
# Tonal::Ratio.new(3,2).log(3) => 0.3690702464285425
|
147
|
+
# @param base
|
148
|
+
#
|
149
|
+
def to_log(base=2)
|
150
|
+
Tonal::Log.new(logarithmand: self, base: base)
|
151
|
+
end
|
152
|
+
alias :log :to_log
|
153
|
+
|
154
|
+
# @return [Tonal::Log2] Math.log2 of self
|
155
|
+
# @example
|
156
|
+
# Tonal::ReducedRatio.new(3,2).to_log2 => 0.5849625007211562
|
157
|
+
#
|
158
|
+
def to_log2
|
159
|
+
Tonal::Log2.new(logarithmand: self)
|
160
|
+
end
|
161
|
+
alias :log2 :to_log2
|
162
|
+
|
163
|
+
# @return [Tonal::Cents] cents value of self
|
164
|
+
# @example
|
165
|
+
# Tonal::Ratio.new(3,2).to_cents => 701.96
|
166
|
+
#
|
167
|
+
def to_cents
|
168
|
+
Tonal::Cents.new(ratio: self)
|
169
|
+
end
|
170
|
+
alias :cents :to_cents
|
171
|
+
|
172
|
+
# @return [Integer] the step of self in the given modulo
|
173
|
+
# @example
|
174
|
+
# Tonal::ReducedRatio.new(3,2).step(12) => 7
|
175
|
+
#
|
176
|
+
def step(modulo=12)
|
177
|
+
to_log2.step(modulo)
|
178
|
+
end
|
179
|
+
|
180
|
+
# @return [Float] degrees
|
181
|
+
# @example
|
182
|
+
# Tonal::Ratio.new(3,2).period_degrees => 210.59
|
183
|
+
# @param round
|
184
|
+
#
|
185
|
+
def period_degrees(round: 2)
|
186
|
+
(360.0 * Math.log(to_f, equave)).round(round)
|
187
|
+
end
|
188
|
+
|
189
|
+
# @return [Float] radians
|
190
|
+
# @example
|
191
|
+
# Tonal::Ratio.new(3,2).period_radians => 3.68
|
192
|
+
# @param round
|
193
|
+
#
|
194
|
+
def period_radians(round: 2)
|
195
|
+
(2 * Math::PI * Math.log(to_f, equave)).round(round)
|
196
|
+
end
|
197
|
+
|
198
|
+
# @return [Tonal::Ratio] copy of self rationally reduced
|
199
|
+
# @example
|
200
|
+
# Tonal::Ratio.new(16,14).fraction_reduce => (8/7)
|
201
|
+
# @see to_r
|
202
|
+
#
|
203
|
+
def fraction_reduce
|
204
|
+
self.class.new(to_r)
|
205
|
+
end
|
206
|
+
|
207
|
+
# @return [Tonal::Ratio] copy of self reduced to the given equave
|
208
|
+
# @example
|
209
|
+
# Tonal::Ratio.new(48,14).equave_reduce(3) => (8/7)
|
210
|
+
# @param equave Numeric
|
211
|
+
#
|
212
|
+
def equave_reduce(equave=2/1r)
|
213
|
+
self.class.new(*_equave_reduce(equave))
|
214
|
+
end
|
215
|
+
alias :reduce :equave_reduce
|
216
|
+
alias :reduced :equave_reduce
|
217
|
+
|
218
|
+
# @return [Tonal::Ratio] self reduced to the given equave
|
219
|
+
# @example
|
220
|
+
# Tonal::Ratio.new(48,14).equave_reduce!(3) => (8/7)
|
221
|
+
# @param equave Numeric
|
222
|
+
#
|
223
|
+
def equave_reduce!(equave=2/1r)
|
224
|
+
@antecedent, @consequent = _equave_reduce(equave)
|
225
|
+
self
|
226
|
+
end
|
227
|
+
alias :reduce! :equave_reduce!
|
228
|
+
alias :reduced! :equave_reduce!
|
229
|
+
|
230
|
+
# @return [Tonal::ReducedRatio] of self
|
231
|
+
# @example
|
232
|
+
# Tonal::Ratio.new(1,9).to_reduced_ratio => (16/9)
|
233
|
+
#
|
234
|
+
def to_reduced_ratio
|
235
|
+
Tonal::ReducedRatio.new(reduced_antecedent, reduced_consequent, equave: equave)
|
236
|
+
end
|
237
|
+
alias :reduced_ratio :to_reduced_ratio
|
238
|
+
|
239
|
+
# @return [Tonal::Ratio] copy of self with the antecedent and precedent switched
|
240
|
+
# @example
|
241
|
+
# Tonal::Ratio.new(3,2).invert => (2/3)
|
242
|
+
#
|
243
|
+
def invert
|
244
|
+
self.class.new(consequent, antecedent)
|
245
|
+
end
|
246
|
+
alias :reflect :invert
|
247
|
+
|
248
|
+
# @return [self] with antecedent and precedent switched
|
249
|
+
# @example
|
250
|
+
# Tonal::Ratio.new(3,2).invert! => (2/3)
|
251
|
+
#
|
252
|
+
def invert!
|
253
|
+
tmp = antecedent
|
254
|
+
@antecedent, @consequent = consequent, tmp
|
255
|
+
self
|
256
|
+
end
|
257
|
+
|
258
|
+
# @return [Tonal::ReducedRatio] the mirror of self along the axis (default 1/1)
|
259
|
+
# @example
|
260
|
+
# Tonal::ReducedRatio.new(4,3).mirror => (3/2)
|
261
|
+
# @param axis
|
262
|
+
#
|
263
|
+
def mirror(axis=1/1r)
|
264
|
+
(self.class.new(axis) ** 2) / self
|
265
|
+
end
|
266
|
+
|
267
|
+
# TODO: Justify or remove
|
268
|
+
#
|
269
|
+
def mirror2(ratio)
|
270
|
+
self.class.new(invert.to_r / ratio)
|
271
|
+
end
|
272
|
+
|
273
|
+
# @return Tonal::ReducedRatio the Ernst Levy negative of self
|
274
|
+
# @example
|
275
|
+
# Tonal::ReducedRatio.new(7/4r).negative => (12/7)
|
276
|
+
#
|
277
|
+
def negative
|
278
|
+
self.class.new(3/2r) / self
|
279
|
+
end
|
280
|
+
|
281
|
+
# ==================================
|
282
|
+
# Ratio grid transformations
|
283
|
+
# numerator mapped on x-axis,
|
284
|
+
# denominator mapped on y-axis
|
285
|
+
# ==================================
|
286
|
+
#
|
287
|
+
# @return [Tonal::Ratio] with the antecedent and consequent translated by x and y
|
288
|
+
# @example
|
289
|
+
# Tonal::Ratio.new(3,2).translate(3,3) => (6/5)
|
290
|
+
# @param x [Numeric]
|
291
|
+
# @param y [Numeric]
|
292
|
+
#
|
293
|
+
def translate(x=1, y=0)
|
294
|
+
raise_if_negative(x,y)
|
295
|
+
self.class.new(*(Vector[antecedent, consequent] + Vector[x, y]))
|
296
|
+
end
|
297
|
+
|
298
|
+
# @return [Tonal::Ratio] self scaled by given arguments
|
299
|
+
# @example
|
300
|
+
# Tonal::Ratio.new(3,2).scale(2**5) => (96/64)
|
301
|
+
# @param a [Numeric]
|
302
|
+
# @param b [Numeric]
|
303
|
+
#
|
304
|
+
def scale(a, b=a)
|
305
|
+
raise_if_negative(a,b)
|
306
|
+
self.class.new(*(Matrix[[a, 0],[0, b]] * Vector[antecedent, consequent]))
|
307
|
+
end
|
308
|
+
|
309
|
+
# @return [Tonal::Ratio] self sheared by given arguments
|
310
|
+
# @example
|
311
|
+
# Tonal::Ratio.new(3,2).shear(1, 3) => (9/11)
|
312
|
+
# @param a [Numeric]
|
313
|
+
# @param b [Numeric]
|
314
|
+
#
|
315
|
+
def shear(a, b=a)
|
316
|
+
raise_if_negative(a,b)
|
317
|
+
self.class.new(*((Matrix[[1,a],[0,1]] * Matrix[[1,0], [b,1]]) * Vector[antecedent, consequent]))
|
318
|
+
end
|
319
|
+
|
320
|
+
# @return [Float] degrees of antecedent (x) and consequent (y) on a 2D plane
|
321
|
+
# @example
|
322
|
+
# Tonal::Ratio.new(3,2).planar_degrees => 33.69
|
323
|
+
# @param round
|
324
|
+
#
|
325
|
+
def planar_degrees(round: 2)
|
326
|
+
(Math.atan2(consequent, antecedent) * 180/Math::PI).round(round)
|
327
|
+
end
|
328
|
+
|
329
|
+
# @return [Float] radians
|
330
|
+
# @example
|
331
|
+
# Tonal::Ratio.new(3,2).planar_radians => 0.59
|
332
|
+
# @param round
|
333
|
+
#
|
334
|
+
def planar_radians(round: 2)
|
335
|
+
Math.atan2(consequent, antecedent).round(round)
|
336
|
+
end
|
337
|
+
|
338
|
+
# @return [Array], self decomposed into its prime factors
|
339
|
+
# @example
|
340
|
+
# Tonal::Ratio.new(31/30r).prime_divisions => [[[31, 1]], [[2, 1], [3, 1], [5, 1]]]
|
341
|
+
#
|
342
|
+
def prime_divisions
|
343
|
+
return [[[2, 1]], [[2, 1]]] if antecedent == 1
|
344
|
+
[antecedent.prime_division, consequent.prime_division]
|
345
|
+
end
|
346
|
+
|
347
|
+
# @return [Vector], self represented as a prime vector
|
348
|
+
# @example
|
349
|
+
# Tonal::Ratio.new(3/2r).prime_vector => Vector[-1, 1]
|
350
|
+
#
|
351
|
+
def prime_vector
|
352
|
+
pds = prime_divisions
|
353
|
+
max = [pds.first.max{|p| p.first}, pds.last.max{|p| p.first}].max.first
|
354
|
+
|
355
|
+
pds.last.collect!{|i| [i.first, -i.last]}
|
356
|
+
|
357
|
+
p_arr = Prime.each(max).to_a
|
358
|
+
Array.new(p_arr.count, 0).tap do |arr|
|
359
|
+
pds.flatten(1).each{|e| arr[p_arr.find_index(e.first)] = e.last}
|
360
|
+
end.to_vector
|
361
|
+
end
|
362
|
+
alias :monzo :prime_vector
|
363
|
+
|
364
|
+
# @return [Integer] the maximum prime factor of self
|
365
|
+
# @example
|
366
|
+
# Tonal::Ratio.new(31/30r).max_prime => 31
|
367
|
+
#
|
368
|
+
def max_prime
|
369
|
+
prime_divisions.flatten(1).map(&:first).max
|
370
|
+
end
|
371
|
+
|
372
|
+
# @return [Integer] the minimum prime factor of self
|
373
|
+
# @example
|
374
|
+
# Tonal::Ratio.new(31/30r).min_prime => 2
|
375
|
+
#
|
376
|
+
def min_prime
|
377
|
+
prime_divisions.flatten(1).map(&:first).min
|
378
|
+
end
|
379
|
+
|
380
|
+
def within_prime?(prime)
|
381
|
+
max_prime <= prime
|
382
|
+
end
|
383
|
+
|
384
|
+
# @return [Integer] the product complexity of self
|
385
|
+
# @example
|
386
|
+
# Tonal::ReducedRatio.new(3/2r).benedetti_height => 6
|
387
|
+
#
|
388
|
+
def benedetti_height
|
389
|
+
reduced_antecedent * reduced_consequent
|
390
|
+
end
|
391
|
+
alias :product_complexity :benedetti_height
|
392
|
+
|
393
|
+
# @return [Tonal::Log2] the log product complexity of self
|
394
|
+
# @example
|
395
|
+
# Tonal::ReducedRatio.new(3/2r).tenney_height => 2.584962500721156
|
396
|
+
#
|
397
|
+
def tenney_height
|
398
|
+
Tonal::Log2.new(logarithmand: benedetti_height)
|
399
|
+
end
|
400
|
+
alias :log_product_complexity :tenney_height
|
401
|
+
alias :harmonic_distance :tenney_height
|
402
|
+
|
403
|
+
# @return [Integer] the Weil height
|
404
|
+
# @example
|
405
|
+
# Tonal::ReducedRatio.new(3/2r).weil_height => 3
|
406
|
+
#
|
407
|
+
def weil_height
|
408
|
+
[reduced_antecedent, reduced_consequent].max
|
409
|
+
end
|
410
|
+
|
411
|
+
|
412
|
+
# @return [Tonal::Log2] the log of Weil height
|
413
|
+
# @example
|
414
|
+
# Tonal::ReducedRatio.new(3/2r).log_weil_height => 1.5849625007211563
|
415
|
+
#
|
416
|
+
def log_weil_height
|
417
|
+
Tonal::Log2.new(logarithmand: weil_height)
|
418
|
+
end
|
419
|
+
|
420
|
+
# @return [Integer] the Wilson height. The sum of self's prime factors (greater than 2) times the absolute values of their exponents
|
421
|
+
# @example
|
422
|
+
# Tonal::ReducedRatio.new(14/9r).wilson_height => 13
|
423
|
+
#
|
424
|
+
def wilson_height(prime_rejects: [2])
|
425
|
+
benedetti_height.prime_division.reject{|p| prime_rejects.include?(p.first) }.sum{|p| p.first * p.last }
|
426
|
+
end
|
427
|
+
|
428
|
+
# @return [Float] the cents difference between self and its step in the given modulo
|
429
|
+
# @example
|
430
|
+
# Tonal::ReducedRatio.new(3,2).efficiency(12) => -1.955000865387433
|
431
|
+
# @param modulo
|
432
|
+
#
|
433
|
+
def efficiency(modulo)
|
434
|
+
(Tonal::Cents::CENT_SCALE * step(modulo).step / modulo.to_f) - to_cents
|
435
|
+
end
|
436
|
+
|
437
|
+
# @return [Array] the results of ratio dividing and multiplying self
|
438
|
+
# @example
|
439
|
+
# Tonal::ReducedRatio.new(3/2r).div_times(5/4r) => [(6/5), (15/8)]
|
440
|
+
# @param ratio
|
441
|
+
#
|
442
|
+
def div_times(ratio)
|
443
|
+
ratio = ratio.ratio
|
444
|
+
[self / ratio, self * ratio]
|
445
|
+
end
|
446
|
+
|
447
|
+
# @return [Array] the results of ratio subtracted and added to self
|
448
|
+
# @example
|
449
|
+
# Tonal::ReducedRatio.new(3/2r).plus_minus(5/4r) => [(1/1), (11/8)]
|
450
|
+
# @param ratio
|
451
|
+
#
|
452
|
+
def plus_minus(ratio)
|
453
|
+
ratio = ratio.ratio
|
454
|
+
[self - ratio, self + ratio]
|
455
|
+
end
|
456
|
+
|
457
|
+
# @return [Cents] cent difference between self and other ratio
|
458
|
+
# @example
|
459
|
+
# Tonal::ReducedRatio.new(3,2).cent_diff(4/3r) => 203.92
|
460
|
+
# @param other_ratio [Tonal::ReducedRatio, Numeric] from which self's cents is measured
|
461
|
+
#
|
462
|
+
def cent_diff(other_ratio)
|
463
|
+
cents - other_ratio.ratio.cents
|
464
|
+
end
|
465
|
+
|
466
|
+
# @return [String] symbolic representation of Tonal::Ratio
|
467
|
+
#
|
468
|
+
def label
|
469
|
+
# Return label, if defined; or,
|
470
|
+
# Return the "antecedent/consequent", if antecedent is less than 7 digits long; or
|
471
|
+
# Return the floating point representation rounded to 2 digits of precision
|
472
|
+
@label || ((Math.log10(antecedent).to_i + 1) <= 6 ? "#{antecedent}/#{consequent}" : to_f.round(2))
|
473
|
+
end
|
474
|
+
|
475
|
+
# @return [String] the string representation of Tonal::Ratio
|
476
|
+
# @example
|
477
|
+
# Tonal::Ratio.new(3, 2).inspect => "[3, 2]"
|
478
|
+
#
|
479
|
+
def inspect
|
480
|
+
"(#{antecedent}/#{consequent})"
|
481
|
+
end
|
482
|
+
|
483
|
+
def +(rhs)
|
484
|
+
operate(rhs, :+)
|
485
|
+
end
|
486
|
+
|
487
|
+
def -(rhs)
|
488
|
+
operate(rhs, :-)
|
489
|
+
end
|
490
|
+
|
491
|
+
def *(rhs)
|
492
|
+
self.class.new(antecedent * rhs.antecedent, consequent * rhs.consequent)
|
493
|
+
end
|
494
|
+
|
495
|
+
def /(rhs)
|
496
|
+
self.class.new(antecedent * rhs.consequent, consequent * rhs.antecedent)
|
497
|
+
end
|
498
|
+
|
499
|
+
def **(rhs)
|
500
|
+
operate(rhs, :**)
|
501
|
+
end
|
502
|
+
|
503
|
+
# @return [Tonal::Ratio] the mediant (Farey) sum of self and another number
|
504
|
+
# @example
|
505
|
+
# Tonal::Ratio.new(3,2).mediant_sum(4/3r) => (7/5)
|
506
|
+
# @param number [Numeric, Tonal::Ratio]
|
507
|
+
#
|
508
|
+
def mediant_sum(number)
|
509
|
+
self.class.new(antecedent + number.numerator, consequent + number.denominator)
|
510
|
+
end
|
511
|
+
alias :mediant :mediant_sum
|
512
|
+
alias :farey_sum :mediant_sum
|
513
|
+
|
514
|
+
# ==================================
|
515
|
+
# Measurements
|
516
|
+
# ==================================
|
517
|
+
|
518
|
+
# @return [Integer] the least common multiple with self's denominator and the given number's denominator
|
519
|
+
# @example
|
520
|
+
# Tonal::Ratio.new(3/2r).lcm(5/4r) => 4
|
521
|
+
# @param lhs [Numeric, Tonal::Ratio] the number with which the lcm with self is computed
|
522
|
+
#
|
523
|
+
def lcm(lhs)
|
524
|
+
[self.denominator, lhs.denominator].lcm
|
525
|
+
end
|
526
|
+
|
527
|
+
# @return [Integer] the difference between antecedent and consequent
|
528
|
+
# @example
|
529
|
+
# Tonal::ReducedRatio.new(3,2).difference => 1
|
530
|
+
#
|
531
|
+
def difference
|
532
|
+
antecedent - consequent
|
533
|
+
end
|
534
|
+
alias :diff :difference
|
535
|
+
|
536
|
+
# @return [Integer] the sum of antecedent and consequent
|
537
|
+
# @example
|
538
|
+
# Tonal::ReducedRatio.new(3,2).combination => 5
|
539
|
+
#
|
540
|
+
def combination
|
541
|
+
antecedent + consequent
|
542
|
+
end
|
543
|
+
alias :comb :combination
|
544
|
+
|
545
|
+
def <=>(rhs)
|
546
|
+
left = consequent == 0 ? Float::INFINITY : Rational(antecedent, consequent)
|
547
|
+
right = rhs.denominator == 0 ? Float::INFINITY : Rational(rhs.numerator, rhs.denominator)
|
548
|
+
left <=> right
|
549
|
+
end
|
550
|
+
|
551
|
+
private
|
552
|
+
def raise_if_negative(*args)
|
553
|
+
raise ArgumentError, "Arguments must be greater than zero" if args.any?{|i| i < 0 }
|
554
|
+
end
|
555
|
+
|
556
|
+
def _equave_reduce(equave=2/1r)
|
557
|
+
case to_r
|
558
|
+
when Float::INFINITY
|
559
|
+
ante, cons = antecedent, 0
|
560
|
+
when 1
|
561
|
+
ante, cons = 1, 1
|
562
|
+
when 0
|
563
|
+
ante, cons = 0, consequent
|
564
|
+
else
|
565
|
+
power = (equave == Tonal::ReducedRatio::IDENTITY_RATIO) ? Tonal::ReducedRatio::IDENTITY_RATIO : Math.log(to_f.abs, equave)
|
566
|
+
r = Rational(self, equave**(power - 1).ceil)
|
567
|
+
r = 1/1r if r == Tonal::Interval::INTERVAL_OF_EQUIVALENCE
|
568
|
+
ante, cons = r.numerator, r.denominator
|
569
|
+
end
|
570
|
+
end
|
571
|
+
|
572
|
+
def operate(rhs, op)
|
573
|
+
case op
|
574
|
+
when :+, :-
|
575
|
+
case rhs
|
576
|
+
when Tonal::Ratio
|
577
|
+
self.class.new((antecedent * rhs.consequent).send(op, rhs.antecedent * consequent).abs, consequent * rhs.consequent)
|
578
|
+
when Rational
|
579
|
+
self.class.new((antecedent * rhs.denominator).send(op, rhs.numerator * consequent).abs, consequent * rhs.denominator)
|
580
|
+
when Array
|
581
|
+
self.class.new((antecedent * rhs[1]).send(op, rhs[0] * consequent), consequent * rhs[1])
|
582
|
+
else
|
583
|
+
r = Rational(rhs)
|
584
|
+
self.class.new((antecedent * r.denominator).send(op, r.numerator * consequent), consequent * r.denominator)
|
585
|
+
end
|
586
|
+
when :*
|
587
|
+
case rhs
|
588
|
+
when Rational
|
589
|
+
self.class.new(antecedent.send(op, rhs.numerator), consequent.send(op, rhs.denominator))
|
590
|
+
when Array
|
591
|
+
self.class.new(antecedent.send(op, rhs[0]), consequent.send(op, rhs[1]))
|
592
|
+
else
|
593
|
+
r = Rational(rhs)
|
594
|
+
self.class.new(antecedent.send(op, r.numerator), consequent.send(op, r.denominator))
|
595
|
+
end
|
596
|
+
when :**
|
597
|
+
self.class.new(antecedent.send(op, rhs), consequent.send(op, rhs))
|
598
|
+
end
|
599
|
+
end
|
600
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
class Tonal::ReducedRatio < Tonal::Ratio
|
2
|
+
IDENTITY_RATIO = 1/1r
|
3
|
+
|
4
|
+
# @return [Tonal::ReducedRatio]
|
5
|
+
# @example
|
6
|
+
# Tonal::ReducedRatio.new(12,2) => (3/2)
|
7
|
+
# @param antecedent [Numeric, Tonal::Ratio]
|
8
|
+
# @param consequent [Numeric, Tonal::Ratio]
|
9
|
+
# @param equave the interval of equivalence, default 2/1
|
10
|
+
#
|
11
|
+
def initialize(antecedent, consequent=1, label: nil, equave: 2/1r)
|
12
|
+
super(antecedent, consequent, label: label, equave: equave)
|
13
|
+
@antecedent, @consequent = @reduced_antecedent, @reduced_consequent
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.identity
|
17
|
+
self.new(IDENTITY_RATIO)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Tonal::Ratio] self as an instance of unreduced ratio
|
21
|
+
# @example
|
22
|
+
# Tonal::ReducedRatio.new(3,2).to_basic_ratio => (3/2)
|
23
|
+
#
|
24
|
+
def to_basic_ratio
|
25
|
+
Tonal::Ratio.new(antecedent, consequent)
|
26
|
+
end
|
27
|
+
|
28
|
+
# @return [Interval] between self (upper) and ratio (lower)
|
29
|
+
# @example
|
30
|
+
# Tonal::ReducedRatio.new(133).interval_with(3/2r) => 133/96 (133/128 / 3/2)
|
31
|
+
# @param ratio
|
32
|
+
#
|
33
|
+
def interval_with(ratio)
|
34
|
+
r = ratio.is_a?(self.class) ? ratio : self.class.new(ratio)
|
35
|
+
Tonal::Interval.new(self, r)
|
36
|
+
end
|
37
|
+
end
|
data/lib/tonal/step.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
class Tonal::Step
|
2
|
+
extend Forwardable
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
def_delegators :@log, :logarithmand
|
6
|
+
|
7
|
+
attr_reader :modulo, :log, :step, :ratio, :tempered
|
8
|
+
|
9
|
+
def initialize(modulo: nil, log: nil, step: nil, ratio: nil)
|
10
|
+
raise ArgumentError, "modulo: required" unless modulo
|
11
|
+
raise ArgumentError, "One of log:, step: or ratio: must be provided" unless [log, step, ratio].compact.count == 1
|
12
|
+
@modulo = modulo.round
|
13
|
+
|
14
|
+
if ratio
|
15
|
+
@ratio, @log = derive_ratio_and_log(ratio: ratio)
|
16
|
+
elsif step
|
17
|
+
@ratio, @log = derive_ratio_and_log(step: step)
|
18
|
+
elsif log
|
19
|
+
@ratio, @log = derive_ratio_and_log(log: log)
|
20
|
+
end
|
21
|
+
|
22
|
+
@step = (modulo * @log).round
|
23
|
+
@tempered = 2**(@step.to_f/@modulo)
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
"#{step}\\#{modulo}"
|
28
|
+
end
|
29
|
+
alias :to_s :inspect
|
30
|
+
|
31
|
+
def convert(new_modulo)
|
32
|
+
self.class.new(log: log, modulo: new_modulo)
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_r
|
36
|
+
ratio.to_r
|
37
|
+
end
|
38
|
+
|
39
|
+
def cents
|
40
|
+
ratio.to_cents
|
41
|
+
end
|
42
|
+
|
43
|
+
def +(rhs)
|
44
|
+
self.class.new(step: (rhs % modulo), modulo: modulo)
|
45
|
+
end
|
46
|
+
alias :% :+
|
47
|
+
|
48
|
+
def <=>(rhs)
|
49
|
+
rhs.kind_of?(self.class) && modulo <=> rhs.modulo && log <=> rhs.log && step <=> rhs.step
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
def derive_ratio_and_log(ratio: nil, log: nil, step: nil)
|
54
|
+
if ratio
|
55
|
+
[Tonal::ReducedRatio.new(ratio), Tonal::Log2.new(logarithmand: ratio)]
|
56
|
+
elsif log
|
57
|
+
if log.kind_of?(Tonal::Log2)
|
58
|
+
[log.logarithmand, log]
|
59
|
+
else
|
60
|
+
lg = Tonal::Log2.new(logarithm: log)
|
61
|
+
[Tonal::ReducedRatio.new(lg.logarithmand), lg]
|
62
|
+
end
|
63
|
+
elsif step
|
64
|
+
[Tonal::ReducedRatio.new(2.0**(step.to_f/@modulo)), Tonal::Log2.new(logarithmand: (2.0 ** (step.to_f/@modulo)))]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/tonal/tools.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module Tonal
|
2
|
+
require "prime"
|
3
|
+
require "matrix"
|
4
|
+
require "sorted_set"
|
5
|
+
require "continued_fractions"
|
6
|
+
require "fraction_tree"
|
7
|
+
require "tonal/cents"
|
8
|
+
require "tonal/hertz"
|
9
|
+
require "tonal/log"
|
10
|
+
require "tonal/log2"
|
11
|
+
require "tonal/approximations"
|
12
|
+
require "tonal/ratio"
|
13
|
+
require "tonal/reduced_ratio"
|
14
|
+
require "tonal/interval"
|
15
|
+
require "tonal/step"
|
16
|
+
require "tonal/extensions"
|
17
|
+
end
|