tonal-tools 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,464 @@
1
+ class Prime
2
+ # @return [Array] the primes within lower and upper bounds
3
+ # @example
4
+ # Prime.within(6, 14) => [7, 11, 13]
5
+ # @param lower bound
6
+ # @param upper bound
7
+ #
8
+ def self.within(lower, upper)
9
+ self.each(upper).reject{|p| p < lower}
10
+ end
11
+ end
12
+
13
+ class Numeric
14
+ # @return [Array], a tuple of self offset positively/negatively
15
+ # @example
16
+ # Math::PI.plus_minus(3)
17
+ # => [6.141592653589793, 0.14159265358979312]
18
+ # @param offset plus and minus distance from self
19
+ #
20
+ def plus_minus(offset)
21
+ [self + offset, self - offset]
22
+ end
23
+
24
+ # @return [Tonal::ReducedRatio] the octave reduced ratio of self
25
+ # @example
26
+ # (2**(1.0/12)).ratio => (4771397596969315/4503599627370496)
27
+ #
28
+ def to_ratio(reduced: true, equave: 2/1r)
29
+ reduced ? Tonal::ReducedRatio.new(self, equave: equave) : Tonal::Ratio.new(self, equave: equave)
30
+ end
31
+ alias :ratio :to_ratio
32
+
33
+ # @return [Float], the degrees on a circle of self
34
+ # @example
35
+ # (2**(6.0/12)).period_degrees => 180.0
36
+ #
37
+ def period_degrees
38
+ self.ratio.period_degrees
39
+ end
40
+
41
+ # @return [Tonal::Log] the log of self to the given base
42
+ # @example
43
+ # (3/2r).log(10) => 0.17609125905568124
44
+ #
45
+ def log(base)
46
+ Tonal::Log.new(logarithmand: self, base: base)
47
+ end
48
+
49
+ # @return [Tonal::Log2] the log2 of self
50
+ # @example
51
+ # (3/2r).log2 => 0.5849625007211562
52
+ #
53
+ def log2
54
+ Tonal::Log2.new(logarithmand: self)
55
+ end
56
+ alias :to_log2 :log2
57
+ alias :span :log2
58
+
59
+ # @return [Tonal::Cents] of self interpreted as a cents quantity
60
+ # @example
61
+ # 700.0.cents => 700.0
62
+ #
63
+ def cents
64
+ Tonal::Cents.new(cents: self)
65
+ end
66
+
67
+ # @return [Tonal::Cents] of self interpreted as a cents quantity
68
+ # @example
69
+ # 700.0.¢ => 700.0
70
+ #
71
+ def ¢
72
+ cents
73
+ end
74
+
75
+ # @return [Tonal::Cents] of self interpreted as a ratio
76
+ # @example
77
+ # (3/2r).cents => 701.96
78
+ #
79
+ def to_cents
80
+ self.log2.to_cents
81
+ end
82
+
83
+ # @return [Tonal::Hertz] of self
84
+ #
85
+ def hz
86
+ Tonal::Hertz.new(self)
87
+ end
88
+ alias :to_hz :hz
89
+
90
+ # @return [Step] the step of self in the given modulo
91
+ # @example
92
+ # (5/4r).step(12) => 4\12
93
+ # @param modulo
94
+ #
95
+ def step(modulo=12)
96
+ to_log2.step(modulo)
97
+ end
98
+
99
+ # @return [Float] the log product complexity of self
100
+ # @example
101
+ # (3/2r).tenney_height => 2.584962500721156
102
+ #
103
+ def benedetti_height
104
+ self.ratio.benedetti_height
105
+ end
106
+ alias :product_complexity :benedetti_height
107
+
108
+ # @return [Integer] the product complexity of self
109
+ # @example
110
+ # (3/2r).benedetti_height => 6
111
+ #
112
+ def tenney_height
113
+ self.ratio.tenney_height
114
+ end
115
+ alias :log_product_complexity :tenney_height
116
+
117
+ # @return [Integer] the Weil height
118
+ # @example
119
+ # (3/2r).weil_height => 3
120
+ #
121
+ def weil_height
122
+ self.ratio.weil_height
123
+ end
124
+
125
+ # @return [Tonal::Log2] the log of Weil height
126
+ # @example
127
+ # (3/2r).log_weil_height => 1.5849625007211563
128
+ #
129
+ def log_weil_height
130
+ self.ratio.log_weil_height
131
+ end
132
+
133
+ # @return [Integer] the Wilson height
134
+ # @example (14/9r).wilson_height => 13
135
+ #
136
+ def wilson_height(reduced: true, equave: 2/1r, prime_rejects: [2])
137
+ self.ratio(reduced: reduced, equave: equave).wilson_height(prime_rejects: prime_rejects)
138
+ end
139
+
140
+ # @return [Float] the cents difference between self and its step in the given modulo
141
+ # @example
142
+ # (3/2r).efficiency(12) => -1.955000865387433
143
+ # @param modulo
144
+ #
145
+ def efficiency(modulo)
146
+ (Tonal::Cents::CENT_SCALE * step(modulo).step / modulo.to_f) - to_cents
147
+ end
148
+
149
+ # @return [Interval] beween self (upper) and ratio (lower)
150
+ # @example
151
+ # (133).interval_with(3/2r) => 133/96 (133/128 / 3/2)
152
+ # @param ratio
153
+ #
154
+ def interval_with(ratio)
155
+ Tonal::Interval.new(self.ratio, ratio)
156
+ end
157
+
158
+ # @return [Vector], self represented as a prime vector
159
+ # @example
160
+ # (3/2r).prime_vector => Vector[-1, 1]
161
+ #
162
+ def prime_vector
163
+ self.ratio.prime_vector
164
+ end
165
+ alias :monzo :prime_vector
166
+
167
+ # @return [Array], self decomposed into its prime factors
168
+ # @example
169
+ # (31/30r).prime_divisions => [[[31, 1]], [[2, 1], [3, 1], [5, 1]]]
170
+ #
171
+ def prime_divisions
172
+ self.ratio.prime_divisions
173
+ end
174
+
175
+ # @return [Integer] the maximum prime factor of self
176
+ # @example
177
+ # (31/30r).max_prime => 31
178
+ #
179
+ def max_prime
180
+ prime_divisions.flatten(1).map(&:first).max
181
+ end
182
+
183
+ # @return [Integer] the minimum prime factor of self
184
+ # @example
185
+ # (31/30r).min_prime => 2
186
+ #
187
+ def min_prime
188
+ prime_divisions.flatten(1).map(&:first).min
189
+ end
190
+
191
+ # @return [Integer] the product complexity of self
192
+ # @example
193
+ # (3/2r).benedetti_height => 6
194
+ #
195
+ def benedetti_height
196
+ numerator * denominator
197
+ end
198
+
199
+ # @return [Float] the log product complexity of self
200
+ # @example
201
+ # (3/2r).tenney_height => 2.584962500721156
202
+ #
203
+ def tenney_height
204
+ Tonal::Log2.new(logarithmand: benedetti_height)
205
+ end
206
+
207
+ # @return [Tonal::ReducedRatio], the Ernst Levy negative of self
208
+ # @example
209
+ # (7/4r).negative => (12/7)
210
+ #
211
+ def negative
212
+ self.ratio.negative
213
+ end
214
+
215
+ # @return [Tonal::ReducedRatio], the ratio rotated on the given axis, default 1/1
216
+ # @example
217
+ # (3/2r).mirror => (4/3)
218
+ #
219
+ def mirror(axis=1/1r)
220
+ self.ratio.mirror(axis)
221
+ end
222
+ end
223
+
224
+ class Rational
225
+ # @return [Vector], self expressed as a Vector
226
+ # @example
227
+ # (3/2r).to_vector => Vector[3, 2]
228
+ #
229
+ def to_vector
230
+ Vector[self.numerator, self.denominator]
231
+ end
232
+ alias :vector :to_vector
233
+
234
+ # @return [Array], self decomposed into its prime factors
235
+ # @example
236
+ # (31/30r).prime_divisions => [[[31, 1]], [[2, 1], [3, 1], [5, 1]]]
237
+ #
238
+ def prime_divisions
239
+ self.ratio.prime_divisions
240
+ end
241
+
242
+ # @return [Integer] the maximum prime factor of self
243
+ # @example
244
+ # (31/30r).max_prime => 31
245
+ #
246
+ def max_prime
247
+ self.ratio.max_prime
248
+ end
249
+
250
+ # @return [Integer] the minimum prime factor of self
251
+ # @example
252
+ # (31/30r).min_prime => 2
253
+ #
254
+ def min_prime
255
+ self.ratio.min_prime
256
+ end
257
+ end
258
+
259
+ class Integer
260
+ alias :prime_factors :prime_division
261
+
262
+ # @return [Integer] the maximum prime factor of self
263
+ # @example
264
+ # 72.max_prime => 3
265
+ #
266
+ def max_prime
267
+ self.prime_division.map(&:first).max
268
+ end
269
+
270
+ # @return [Integer] the minimum prime factor of self
271
+ # @example
272
+ # 72.min_prime => 2
273
+ #
274
+ def min_prime
275
+ self.prime_division.map(&:first).min
276
+ end
277
+
278
+ # @return [Integer] the factorial of self
279
+ # @example
280
+ # 5.factorial => 120
281
+ #
282
+ def factorial
283
+ (2..self).reduce(1, :*)
284
+ end
285
+
286
+ # @return [Boolean] if self is coprime with i
287
+ # @example
288
+ # 25.coprime?(7) => true
289
+ # 25.coprime?(5) => false
290
+ #
291
+ def coprime?(i)
292
+ self.gcd(i) == 1
293
+ end
294
+
295
+ # @return [Array] list of integers that are coprime with self, up to the value of self
296
+ # @example
297
+ # 10.coprimes => [1, 3, 7, 9]
298
+ #
299
+ def coprimes
300
+ [].tap do |coprime_set|
301
+ 1.upto(self) do |i|
302
+ coprime_set << i if i.coprime?(self)
303
+ end
304
+ end
305
+ end
306
+
307
+ # @return [Integer] the count of coprimes less than self
308
+ # @example
309
+ # 10.phi => 4
310
+ #
311
+ def phi
312
+ coprimes.count
313
+ end
314
+ alias :totient :phi
315
+
316
+ # @return [Array] of integers that are n-smooth with self
317
+ # Adapted from https://rosettacode.org/wiki/N-smooth_numbers#Ruby
318
+ # @example
319
+ # 5.nsmooth(25)
320
+ # => [1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, 16, 18, 20, 24, 25, 27, 30, 32, 36, 40, 45, 48, 50, 54]
321
+ # @param limit
322
+ #
323
+ def nsmooth(limit=2)
324
+ ([0] * limit).tap do |ns|
325
+ primes = Prime.each(self).to_a
326
+ ns[0] = 1
327
+ nextp = primes[0..primes.index(self)]
328
+
329
+ indices = [0] * nextp.size
330
+ (1...limit).each do |m|
331
+ ns[m] = nextp.min
332
+ (0...indices.size).each do |i|
333
+ if ns[m] == nextp[i]
334
+ indices[i] += 1
335
+ nextp[i] = primes[i] * ns[indices[i]]
336
+ end
337
+ end
338
+ end
339
+ end
340
+ end
341
+ end
342
+
343
+ class Array
344
+ # @return [Array] self replaced by array padded to the right up to n, with value. value default is nil
345
+ # @example
346
+ # [3,2].rpad!(3, 12) => [3, 2, 12]
347
+ # @param min_size
348
+ # @param value
349
+ #
350
+ def rpad!(min_size, value = nil)
351
+ self.length > min_size ? self : (min_size - self.length).times { self << value }
352
+ self
353
+ end
354
+
355
+ # @return [Array] padded to the right up to n, with value. value default is nil
356
+ # @example
357
+ # [3,2].rpad(3, 12) => [3, 2, 12]
358
+ # @param min_size
359
+ # @param value
360
+ #
361
+ def rpad(min_size, value = nil)
362
+ self.dup.rpad!(min_size, value)
363
+ end
364
+
365
+ # @return [Vector] self converted to a vector
366
+ # @example
367
+ # [3,2].to_vector => Vector[3, 2]
368
+ #
369
+ def to_vector
370
+ Vector[*self]
371
+ end
372
+ alias :vector :to_vector
373
+
374
+ # @return [Integer] least common multiple of elements of self
375
+ # @example TODO
376
+ #
377
+ def lcm
378
+ self.reduce(1, :lcm)
379
+ end
380
+
381
+ # @return [Array] of numerators of elements of self
382
+ # @example
383
+ # [3/2r, 5/4r].numerators => [3, 5]
384
+ #
385
+ def numerators
386
+ self.map(&:numerator)
387
+ end
388
+ alias :antecedents :numerators
389
+
390
+ # @return [Array] of denominators of elements of self
391
+ # @example
392
+ # [Tonal::Ratio.new(3,2), Tonal::Ratio.new(5,4)].denominators => [2, 4]
393
+ #
394
+ def denominators
395
+ self.map(&:denominator)
396
+ end
397
+ alias :consequents :denominators
398
+
399
+ # @return [Array] an array of normalized ratios
400
+ # @example
401
+ # [4/3r, 3/2r].normalize => [(8/6), (9/6)]
402
+ #
403
+ def normalize
404
+ l = denominators.lcm
405
+ map{|r| Tonal::Ratio.new(l / r.denominator * r.numerator, l)}
406
+ end
407
+
408
+ # @return [Array] of cent values for ratio or rational elements of self
409
+ # @example
410
+ # [3/2r, 4/3r].to_cents => [701.96, 498.04]
411
+ #
412
+ def to_cents
413
+ self.map{|r| r.to_cents}
414
+ end
415
+ alias :cents :to_cents
416
+
417
+ # TODO: Consider removing
418
+ #def cons_diff(cons=2)
419
+ # self.each_cons(cons).map{|a,b| (a - b).abs }
420
+ #end
421
+
422
+ # @return [Float] the mean of the elements of self
423
+ # @example
424
+ # [1, 2].mean => 1.5
425
+ #
426
+ def mean
427
+ self.sum / self.count.to_f
428
+ end
429
+
430
+ # @return [Tonal::ReducedRatio] ratio reconstructed from the result of a prime factor decomposition
431
+ # @example
432
+ # [[[3, 1]], [[2, 1]]].ratio_from_prime_divisions => (3/2)
433
+ #
434
+ def ratio_from_prime_divisions
435
+ Tonal::Ratio.new(Prime.int_from_prime_division(self.first), Prime.int_from_prime_division(self.last))
436
+ end
437
+ end
438
+
439
+ class Vector
440
+ # @return [Tonal::Ratio]
441
+ # @example
442
+ # Vector[3,2].ratio => (3/2)
443
+ # @param reduced
444
+ # @param equave
445
+ #
446
+ def to_ratio(reduced: true, equave: 2/1r)
447
+ reduced ? Tonal::ReducedRatio.new(*self, equave: equave) : Tonal::Ratio.new(*self, equave: equave)
448
+ end
449
+ alias :ratio :to_ratio
450
+ end
451
+
452
+ module Math
453
+ # @return [Integer] the factorial of n
454
+ # @example
455
+ # Math.factorial(10) => 3628800
456
+ # @param limit
457
+ #
458
+ def self.factorial(limit)
459
+ (2..limit).reduce(1, :*)
460
+ end
461
+
462
+ PHI = (1 + 5**(1.0/2))/2
463
+ end
464
+
@@ -0,0 +1,61 @@
1
+ class Tonal::Hertz
2
+ include Comparable
3
+
4
+ attr_reader :value
5
+
6
+ # @return [Hertz]
7
+ # @example
8
+ # Hertz.new(1000.0) => 1000.0
9
+ # @param arg [Numeric, Tonal::Hertz]
10
+ #
11
+ def initialize(arg)
12
+ raise ArgumentError, "Argument is not Numeric or Tonal::Hertz" unless arg.kind_of?(Numeric) || arg.kind_of?(self.class)
13
+ @value = arg.kind_of?(self.class) ? arg.inspect : arg
14
+ end
15
+
16
+ # @return [Hertz] 440 Hz
17
+ # @example
18
+ # Tonal::Hertz.a440 => 440.0
19
+ #
20
+ def self.a440
21
+ self.new(440.0)
22
+ end
23
+
24
+ # @return [Rational] self as a rational
25
+ # @example
26
+ # Tonal::Hertz.new(440).to_r => (440/1)
27
+ #
28
+ def to_r
29
+ Rational(value)
30
+ end
31
+
32
+
33
+ # @return [Rational] self as a float
34
+ # @example
35
+ # Tonal::Hertz.new(440).to_f => 440.0
36
+ #
37
+ def to_f
38
+ value.to_f
39
+ end
40
+
41
+ # @return [String] the string representation of Hertz
42
+ # @example
43
+ # Hertz(1000.0).inspect => "1000.0"
44
+ #
45
+ def inspect
46
+ "#{value}"
47
+ end
48
+
49
+ def <=>(rhs)
50
+ rhs.kind_of?(self.class) ? value <=> rhs.value : value <=> rhs
51
+ end
52
+
53
+ def method_missing(op, *args, &blk)
54
+ rhs = args.collect do |arg|
55
+ arg.kind_of?(self.class) ? arg.value : arg
56
+ end
57
+ result = value.send(op, *rhs)
58
+ return result if op == :coerce
59
+ result.kind_of?(Numeric) ? self.class.new(result) : result
60
+ end
61
+ end
@@ -0,0 +1,38 @@
1
+ class Tonal::Interval
2
+ extend Forwardable
3
+ include Comparable
4
+
5
+ def_delegators :@interval, :to_r, :antecedent, :consequent
6
+
7
+ attr_reader :lower_ratio, :upper_ratio, :interval
8
+
9
+ INTERVAL_OF_EQUIVALENCE = 2/1r
10
+
11
+ def initialize(upper_ratio, lower_ratio)
12
+ @lower_ratio = lower_ratio.ratio
13
+ @upper_ratio = upper_ratio.ratio
14
+ @interval = @upper_ratio / @lower_ratio
15
+ end
16
+ alias :lower :lower_ratio
17
+ alias :upper :upper_ratio
18
+ alias :numerator :antecedent
19
+ alias :denominator :consequent
20
+
21
+ def to_a
22
+ [lower_ratio, upper_ratio]
23
+ end
24
+
25
+ def normalize
26
+ ratios = to_a
27
+ lcm = ratios.denominators.lcm
28
+ ratios.map{|r| Tonal::Ratio.new(lcm / r.denominator * r.numerator, lcm)}
29
+ end
30
+
31
+ def inspect
32
+ "#{self.to_r} (#{upper.to_r} / #{lower.to_r})"
33
+ end
34
+
35
+ def <=>(rhs)
36
+ interval.to_r <=> rhs.interval.to_r
37
+ end
38
+ end
data/lib/tonal/log.rb ADDED
@@ -0,0 +1,121 @@
1
+ class Tonal::Log
2
+ extend Forwardable
3
+ include Comparable
4
+
5
+ def_delegators :@logarithmand, :ratio, :to_ratio
6
+
7
+ attr_reader :logarithmand, :logarithm, :base
8
+
9
+ # @return [Tonal::Log]
10
+ # @example
11
+ # Tonal::Log.new(logarithmand: 3/2r, base: 2) => 0.5849625007211562
12
+ # @param logarithmand
13
+ # @param logarithm
14
+ # @param base
15
+ #
16
+ def initialize(logarithmand: nil, logarithm: nil, base: nil)
17
+ raise ArgumentError, "logarithmand or logarithm must be provided" if logarithmand.nil? && logarithm.nil?
18
+
19
+ if logarithmand && logarithm && base
20
+ @logarithmand = logarithmand
21
+ @logarithm = logarithm
22
+ @base = derive_base(logarithmand: logarithmand, logarithm: logarithm)
23
+ puts "Provided base (#{base}) does not align with logarithmand and logarithm. Using calculated base (#{@base}) instead" if @base != base
24
+ elsif logarithmand && logarithm
25
+ @logarithmand = logarithmand
26
+ @logarithm = logarithm
27
+ @base = derive_base(logarithmand: logarithmand, logarithm: logarithm)
28
+ elsif logarithmand && base
29
+ @logarithmand = logarithmand
30
+ @base = base
31
+ @logarithm = derive_logarithm(logarithmand: logarithmand, base: @base)
32
+ elsif logarithm && base
33
+ @base = base
34
+ @logarithm = logarithm
35
+ @logarithmand = derive_logarithmand(logarithm: logarithm, base: @base)
36
+ elsif logarithmand
37
+ @logarithmand = logarithmand
38
+ @base = self.class.base
39
+ @logarithm = derive_logarithm(logarithmand: logarithmand, base: @base)
40
+ elsif logarithm
41
+ @base = self.class.base
42
+ @logarithm = logarithm
43
+ @logarithmand = derive_logarithmand(logarithm: logarithm, base: @base)
44
+ end
45
+ end
46
+
47
+ def self.base
48
+ Math::E
49
+ end
50
+
51
+ # @return [Tonal::Cents] the cents scale logarithm
52
+ # @example
53
+ # Tonal::Log.new(logarithmand: 3/2r, base: 2).to_cents => 701.9550008653874
54
+ # @see Cents
55
+ #
56
+ def to_cents(precision: Tonal::Cents::PRECISION)
57
+ Tonal::Cents.new(log: self, precision: precision)
58
+ end
59
+
60
+ # @return [Step] the nearest step in the given modulo
61
+ # @example
62
+ # Tonal::Log.new(3/2r, base: 2).step(12) => 7\12
63
+ #
64
+ def step(modulo)
65
+ Tonal::Step.new(modulo: modulo, log: self)
66
+ end
67
+
68
+ # @return [String] the string representation of Tonal::Log
69
+ # @example
70
+ # Tonal::Log.new(3/2r, base: 2).inspect => "0.5849625007211562"
71
+ #
72
+ def inspect
73
+ "#{logarithm}"
74
+ end
75
+
76
+ def <=>(rhs)
77
+ rhs.kind_of?(self.class) ? logarithm <=> rhs.logarithm : logarithm <=> rhs
78
+ end
79
+
80
+ private
81
+ def derive_base(logarithmand:, logarithm:)
82
+ logarithmand**(1.0/logarithm)
83
+ end
84
+
85
+ def derive_logarithmand(logarithm:, base:)
86
+ base**logarithm
87
+ end
88
+
89
+ def derive_logarithm(logarithmand:, base:)
90
+ case logarithmand
91
+ when Tonal::Cents
92
+ logarithmand.value / Tonal::Cents::CENT_SCALE
93
+ when Tonal::Ratio
94
+ Math.log(logarithmand.antecedent.to_f / logarithmand.consequent) / Math.log(base)
95
+ else
96
+ Math.log(logarithmand) / Math.log(base)
97
+ end
98
+ end
99
+
100
+ # TODO Explain how this works
101
+ #
102
+ def method_missing(op, *args, &blk)
103
+ rhs = args.collect do |arg|
104
+ arg.kind_of?(self.class) ? arg.inspect : arg
105
+ end
106
+ result = logarithm.send(op, *rhs)
107
+ return result if op == :coerce
108
+ case result
109
+ when Numeric
110
+ self.class.new(result)
111
+ # Work this case out
112
+ #when Array
113
+ # result.collect do |e|
114
+ # e.kind_of?(Numeric) ? Cents.new(e) : e
115
+ # end
116
+ else
117
+ result
118
+ end
119
+ end
120
+ end
121
+
data/lib/tonal/log2.rb ADDED
@@ -0,0 +1,6 @@
1
+ class Tonal::Log2 < Tonal::Log
2
+
3
+ def self.base
4
+ 2.0
5
+ end
6
+ end