tonal-tools 7.3.0 → 7.4.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/attributions.rb +1 -1
- data/lib/tonal/extended_ratio.rb +131 -0
- data/lib/tonal/extensions.rb +45 -1
- data/lib/tonal/interval.rb +6 -1
- data/lib/tonal/irb_helpers.rb +4 -0
- data/lib/tonal/ratio.rb +7 -7
- data/lib/tonal/tools.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 769baf7d488e059f0f8072fdcf39dcf33567d14a631e6a55eece8c7c9ff864d1
|
|
4
|
+
data.tar.gz: 1e7ef0b95e95df63a2667ac04f2062ce0a62502ea577fd7c6d529e407ecc04da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 88ec036a5b4f17503d905c25bacf74a22d6d0c00b4a792d6196c5faecfcdebae3836711deb363a65863df4334a61f35d0b974de0fa42816390c0548e538a0a1f
|
|
7
|
+
data.tar.gz: 8f004f602f9e8780a7c0a64dbb1132ba323f7032411b8bd5617b89973c14047f5f607ea3c67d30287c29922719c3a3c580759463242fba73a3feac8280cee104
|
data/lib/tonal/attributions.rb
CHANGED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
module Tonal::ExtendableRatio
|
|
2
|
+
extend Forwardable
|
|
3
|
+
include Comparable
|
|
4
|
+
def_delegators :@partials, :size, :length, :each, :map, :select, :first, :last, :[], :count
|
|
5
|
+
|
|
6
|
+
attr_reader :partials
|
|
7
|
+
|
|
8
|
+
# @return [Tonal::ExtendedRatio, Tonal::SubharmonicExtendedRatio]
|
|
9
|
+
# @example
|
|
10
|
+
# Tonal::ExtendedRatio.new(partials: [1, 2, 3])
|
|
11
|
+
# Tonal::SubharmonicExtendedRatio.new(ratios: [Rational(1,1), Rational(1,2), Rational(1,3)])
|
|
12
|
+
# @param partials [Array<Numeric>] the partials to initialize the extended ratio
|
|
13
|
+
# @param ratios [Array<Rational>] the ratios to initialize the extended ratio
|
|
14
|
+
#
|
|
15
|
+
def initialize(partials: nil, ratios: nil)
|
|
16
|
+
raise(ArgumentError, "Provide either partials or ratios, not both", caller[0]) if !partials.nil? && !ratios.nil?
|
|
17
|
+
raise(ArgumentError, "Provide either partials or ratios", caller[0]) if partials.nil? && ratios.nil?
|
|
18
|
+
|
|
19
|
+
if partials
|
|
20
|
+
during_initialize(*partials)
|
|
21
|
+
elsif ratios
|
|
22
|
+
first = ratios.first
|
|
23
|
+
partials = ratios.map{|r| r * first}
|
|
24
|
+
during_initialize(*partials)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Tonal::Interval] the interval between two partials
|
|
29
|
+
# @example
|
|
30
|
+
# er = Tonal::ExtendedRatio.new(partials: [1, 2, 3, 4])
|
|
31
|
+
# er.interval_between(0, 2) => Tonal::Interval representing 3/2
|
|
32
|
+
#
|
|
33
|
+
def interval_between(index1, index2, reduced: true)
|
|
34
|
+
return nil if self[index1].nil? || self[index2].nil?
|
|
35
|
+
first = partials.first
|
|
36
|
+
r1 = reduced ? Tonal::ReducedRatio.new(self[index1], first) : Tonal::Ratio.new(self[index1], first)
|
|
37
|
+
r2 = reduced ? Tonal::ReducedRatio.new(self[index2], first) : Tonal::Ratio.new(self[index2], first)
|
|
38
|
+
Tonal::Interval.new(r1, r2, reduced:)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def inspect
|
|
42
|
+
display.join(":")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
def switch_domain(domain)
|
|
47
|
+
case domain
|
|
48
|
+
when :harmonic
|
|
49
|
+
Tonal::ExtendedRatio
|
|
50
|
+
when :subharmonic
|
|
51
|
+
Tonal::SubharmonicExtendedRatio
|
|
52
|
+
else
|
|
53
|
+
raise(ArgumentError, "Unknown domain: #{domain}", caller[0])
|
|
54
|
+
end.new(ratios: ratios.map(&:to_r))
|
|
55
|
+
end
|
|
56
|
+
alias :switch_to :switch_domain
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
class Tonal::ExtendedRatio
|
|
60
|
+
include Tonal::ExtendableRatio
|
|
61
|
+
|
|
62
|
+
# @return [Array<Tonal::Ratio, Tonal::ReducedRatio>] the ratios of the extended ratio
|
|
63
|
+
# @example
|
|
64
|
+
# er = Tonal::ExtendedRatio.new(partials: [4, 5, 6])
|
|
65
|
+
# er.ratios => [1/1, 5/4, 3/2]
|
|
66
|
+
# @param reduced [Boolean] whether to return reduced ratios or not
|
|
67
|
+
#
|
|
68
|
+
def ratios(reduced: true)
|
|
69
|
+
first = partials.first
|
|
70
|
+
partials.map do |n|
|
|
71
|
+
reduced ? Tonal::ReducedRatio.new(n, first) : Tonal::Ratio.new(n, first)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @return [Tonal::SubharmonicExtendedRatio] the subharmonic extended ratio
|
|
76
|
+
# @example
|
|
77
|
+
# er = Tonal::ExtendedRatio.new(partials: [4, 5, 6])
|
|
78
|
+
# er.to_subharmonic_extended_ratio => Tonal::SubharmonicExtendedRatio with partials [(1/15), (1/12), (1/10)]
|
|
79
|
+
#
|
|
80
|
+
def to_subharmonic_extended_ratio
|
|
81
|
+
switch_to(:subharmonic)
|
|
82
|
+
end
|
|
83
|
+
alias :to_sefr :to_subharmonic_extended_ratio
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
def during_initialize(*args)
|
|
87
|
+
lcm = args.denominators.lcm
|
|
88
|
+
@partials = Array.new(args.map{|n| n * lcm}.numerators).sort
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def display
|
|
92
|
+
@display ||= partials.map{|r| r.round(2)}
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
class Tonal::SubharmonicExtendedRatio
|
|
97
|
+
include Tonal::ExtendableRatio
|
|
98
|
+
|
|
99
|
+
# @return [Array<Tonal::Ratio, Tonal::ReducedRatio>] the ratios of the subharmonic extended ratio
|
|
100
|
+
# @example
|
|
101
|
+
# ser = Tonal::SubharmonicExtendedRatio.new(partials: [6,5,4])
|
|
102
|
+
# ser.ratios => [1/1, 6/5, 3/2]
|
|
103
|
+
# @param reduced [Boolean] whether to return reduced ratios or not
|
|
104
|
+
#
|
|
105
|
+
def ratios(reduced: true)
|
|
106
|
+
first = partials.first
|
|
107
|
+
partials.map do |n|
|
|
108
|
+
reduced ? Tonal::ReducedRatio.new(n, first) : Tonal::Ratio.new(n, first)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# @return [Tonal::ExtendedRatio] the harmonic extended ratio
|
|
113
|
+
# @example
|
|
114
|
+
# ser = Tonal::SubharmonicExtendedRatio.new(partials: [6,5,4])
|
|
115
|
+
# ser.to_extended_ratio => Tonal::ExtendedRatio with partials [10, 12, 15]
|
|
116
|
+
#
|
|
117
|
+
def to_extended_ratio
|
|
118
|
+
switch_to(:harmonic)
|
|
119
|
+
end
|
|
120
|
+
alias :to_efr :to_extended_ratio
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
def during_initialize(*args)
|
|
124
|
+
lcm = args.numerators.lcm
|
|
125
|
+
@partials = args.map{|n| n.kind_of?(Rational) ? n / lcm : n.reciprocal}.sort
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def display
|
|
129
|
+
@display ||= partials.map(&:reciprocal).map{|r| (r % 1).zero? ? r.to_i : r}.map{|r| r.round(2)}
|
|
130
|
+
end
|
|
131
|
+
end
|
data/lib/tonal/extensions.rb
CHANGED
|
@@ -158,7 +158,7 @@ class Numeric
|
|
|
158
158
|
# @example
|
|
159
159
|
# (3/2r).to_interval => 3/2 (3/2 / 1/1)
|
|
160
160
|
#
|
|
161
|
-
def to_interval = Tonal::Interval.new(
|
|
161
|
+
def to_interval = Tonal::Interval.new(self)
|
|
162
162
|
|
|
163
163
|
# @return [Tonal::Cents] difference between ratio (upper) and self (lower)
|
|
164
164
|
# @example
|
|
@@ -221,6 +221,12 @@ class Numeric
|
|
|
221
221
|
# @param base of the log
|
|
222
222
|
#
|
|
223
223
|
def log_floor(base=10) = Math.log(self, base).floor
|
|
224
|
+
|
|
225
|
+
# @return [Rational] the reciprocal of self
|
|
226
|
+
# @example
|
|
227
|
+
# (3/2r).reciprocal => (2/3)
|
|
228
|
+
#
|
|
229
|
+
def reciprocal = Rational(1,self)
|
|
224
230
|
end
|
|
225
231
|
|
|
226
232
|
class Integer
|
|
@@ -398,6 +404,16 @@ class Array
|
|
|
398
404
|
def to_cents = self.map{|r| r.to_cents}
|
|
399
405
|
alias :cents :to_cents
|
|
400
406
|
|
|
407
|
+
# @return [Tonal::Interval]
|
|
408
|
+
# @example
|
|
409
|
+
# [3/2r, 4/3r].to_interval => 16/9 (4/3 / 3/2)
|
|
410
|
+
# @example
|
|
411
|
+
# [5].to_interval => 5/4 (5/4 / 1/1)
|
|
412
|
+
# @example
|
|
413
|
+
# [2,3,3,4].to_interval => 9/8 (3/2 / 4/3)
|
|
414
|
+
#
|
|
415
|
+
def to_interval = Tonal::Interval.new(*self)
|
|
416
|
+
|
|
401
417
|
# @return [Float] the mean of the elements of self
|
|
402
418
|
# @example
|
|
403
419
|
# [1, 2].mean => 1.5
|
|
@@ -474,6 +490,34 @@ class Array
|
|
|
474
490
|
# [4,3].to_r => (4/3)
|
|
475
491
|
#
|
|
476
492
|
def to_r = Rational(numerator, denominator)
|
|
493
|
+
|
|
494
|
+
# @return [Tonal::ExtendedRatio]
|
|
495
|
+
# @example
|
|
496
|
+
# [4/1r, 5/1r, 6/1r].to_efr => ExtendedRatio "4:5:6"
|
|
497
|
+
# @param as :ratios or :partials
|
|
498
|
+
#
|
|
499
|
+
def to_efr(as: :ratios) = as == :ratios ? Tonal::ExtendedRatio.new(ratios: self) : Tonal::ExtendedRatio.new(partials: self)
|
|
500
|
+
|
|
501
|
+
# @return [Tonal::SubharmonicExtendedRatio]
|
|
502
|
+
# @example
|
|
503
|
+
# [4,5,6].to_sefr => SubharmonicExtendedRatio "4:5:6"
|
|
504
|
+
# @param as :ratios or :partials
|
|
505
|
+
#
|
|
506
|
+
def to_sefr(as: :ratios) = as == :ratios ? Tonal::SubharmonicExtendedRatio.new(ratios: self) : Tonal::SubharmonicExtendedRatio.new(partials: self)
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
class Range
|
|
510
|
+
# @return [Tonal::ExtendedRatio]
|
|
511
|
+
# @example
|
|
512
|
+
# (4..7).to_efr => 4:5:6:7
|
|
513
|
+
#
|
|
514
|
+
def to_efr = Tonal::ExtendedRatio.new(partials: self)
|
|
515
|
+
|
|
516
|
+
# @return [Tonal::SubharmonicExtendedRatio]
|
|
517
|
+
# @example
|
|
518
|
+
# (4..7).to_sefr => 7:6:5:4
|
|
519
|
+
#
|
|
520
|
+
def to_sefr = Tonal::SubharmonicExtendedRatio.new(partials: self)
|
|
477
521
|
end
|
|
478
522
|
|
|
479
523
|
class Vector
|
data/lib/tonal/interval.rb
CHANGED
|
@@ -11,10 +11,15 @@ class Tonal::Interval
|
|
|
11
11
|
# @return [Tonal::Interval] the interval of the given ratios
|
|
12
12
|
# @example
|
|
13
13
|
# Tonal::Interval.new(2,3) => (3/2) ((3/2) / (1/1))
|
|
14
|
+
# @example
|
|
15
|
+
# Tonal::Interval.new(2,3,3,4) => (9/8) ((3/2) / (4/3))
|
|
16
|
+
# @example
|
|
17
|
+
# Tonal::Interval.new(3) => (3/1) ((3/1) / (1/1))
|
|
14
18
|
# @param args two arguments representing ratios or four arguments representing two pairs of numerator/denominator
|
|
15
19
|
# @param reduced boolean determining whether to use Tonal::ReducedRatio or Tonal::Ratio
|
|
16
20
|
#
|
|
17
21
|
def initialize(*args, reduced: true)
|
|
22
|
+
args = [1/1r, args[0]] if args.length == 1
|
|
18
23
|
klass = reduced ? Tonal::ReducedRatio : Tonal::Ratio
|
|
19
24
|
raise(ArgumentError, "Two or four arguments required. Either two ratios, or two pairs of numerator, denominator", caller[0]) unless [2, 4].include?(args.size)
|
|
20
25
|
@lower_ratio, @upper_ratio = case args.size
|
|
@@ -42,7 +47,7 @@ class Tonal::Interval
|
|
|
42
47
|
end
|
|
43
48
|
|
|
44
49
|
def inspect
|
|
45
|
-
"#{interval} (#{upper} / #{lower})"
|
|
50
|
+
"#{interval.label} (#{upper.label} / #{lower.label})"
|
|
46
51
|
end
|
|
47
52
|
|
|
48
53
|
def <=>(rhs)
|
data/lib/tonal/irb_helpers.rb
CHANGED
|
@@ -23,6 +23,10 @@ module Tonal
|
|
|
23
23
|
# @return [Tonal::Interval] the interval between the given args
|
|
24
24
|
# @example
|
|
25
25
|
# i(2,3) => (3/2) ((3/2) / (1/1))
|
|
26
|
+
# @example
|
|
27
|
+
# i(2,3,3,4) => (9/8) ((3/2) / (4/3))
|
|
28
|
+
# @example
|
|
29
|
+
# i(3) => (3/1) ((3/1) / (1/1))
|
|
26
30
|
# @param args two arguments representing ratios or four arguments representing two pairs of numerator/denominator
|
|
27
31
|
# @param reduced boolean determining whether to use Tonal::ReducedRatio or Tonal::Ratio
|
|
28
32
|
#
|
data/lib/tonal/ratio.rb
CHANGED
|
@@ -233,7 +233,7 @@ class Tonal::Ratio
|
|
|
233
233
|
# @return [Tonal::ReducedRatio] of self
|
|
234
234
|
# @example
|
|
235
235
|
# Tonal::Ratio.new(1,9).to_reduced_ratio => (16/9)
|
|
236
|
-
#
|
|
236
|
+
#
|
|
237
237
|
def to_reduced_ratio
|
|
238
238
|
Tonal::ReducedRatio.new(reduced_antecedent, reduced_consequent, equave: equave)
|
|
239
239
|
end
|
|
@@ -247,6 +247,7 @@ class Tonal::Ratio
|
|
|
247
247
|
self.class.new(consequent, antecedent)
|
|
248
248
|
end
|
|
249
249
|
alias :reflect :invert
|
|
250
|
+
alias :reciprocal :invert
|
|
250
251
|
|
|
251
252
|
# @return [Tonal::Ratio] with antecedent and precedent switched
|
|
252
253
|
# @example
|
|
@@ -472,10 +473,8 @@ class Tonal::Ratio
|
|
|
472
473
|
# @return [String] symbolic representation of Tonal::Ratio
|
|
473
474
|
#
|
|
474
475
|
def label
|
|
475
|
-
# Return label, if defined; or
|
|
476
|
-
|
|
477
|
-
# Return the floating point representation rounded to 2 digits of precision
|
|
478
|
-
(@label || ((Math.log10(antecedent).to_i + 1) <= 6 ? "#{antecedent}/#{consequent}" : to_f.round(PRECISION))).to_s
|
|
476
|
+
# Return label, if defined; or see inspect
|
|
477
|
+
@label || inspect
|
|
479
478
|
end
|
|
480
479
|
|
|
481
480
|
# @return [String] the string representation of Tonal::Ratio
|
|
@@ -483,7 +482,9 @@ class Tonal::Ratio
|
|
|
483
482
|
# Tonal::Ratio.new(3, 2).inspect => "(3/2)"
|
|
484
483
|
#
|
|
485
484
|
def inspect
|
|
486
|
-
"
|
|
485
|
+
# Return the "antecedent/consequent", if antecedent is less than 7 digits long; or
|
|
486
|
+
# Return the floating point representation rounded to PRECISION digits
|
|
487
|
+
((Math.log10(antecedent).to_i + 1) <= 6 ? "#{antecedent}/#{consequent}" : "#{to_f.round(PRECISION)}")
|
|
487
488
|
end
|
|
488
489
|
alias :to_s :inspect
|
|
489
490
|
|
|
@@ -642,4 +643,3 @@ module Ratio
|
|
|
642
643
|
Tonal::Ratio.new(u, l)
|
|
643
644
|
end
|
|
644
645
|
end
|
|
645
|
-
|
data/lib/tonal/tools.rb
CHANGED
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: tonal-tools
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 7.
|
|
4
|
+
version: 7.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jose Hales-Garcia
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2025-12-
|
|
10
|
+
date: 2025-12-13 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: yaml
|
|
@@ -161,6 +161,7 @@ files:
|
|
|
161
161
|
- lib/tonal/attributions.rb
|
|
162
162
|
- lib/tonal/cents.rb
|
|
163
163
|
- lib/tonal/comma.rb
|
|
164
|
+
- lib/tonal/extended_ratio.rb
|
|
164
165
|
- lib/tonal/extensions.rb
|
|
165
166
|
- lib/tonal/hertz.rb
|
|
166
167
|
- lib/tonal/interval.rb
|