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.
@@ -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