tonal-tools 7.7.0 → 8.3.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 +4 -4
- data/lib/tonal/approximation.rb +50 -23
- data/lib/tonal/attributions.rb +1 -1
- data/lib/tonal/cents.rb +37 -0
- data/lib/tonal/extensions.rb +42 -27
- data/lib/tonal/hertz.rb +4 -4
- data/lib/tonal/interval.rb +72 -21
- data/lib/tonal/irb_helpers.rb +15 -13
- data/lib/tonal/log.rb +2 -3
- data/lib/tonal/ratio.rb +154 -93
- data/lib/tonal/reduced_ratio.rb +1 -7
- data/lib/tonal/step.rb +95 -93
- metadata +21 -21
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: bc114e39cee0bcffd5b39e0e22cdc572ae7671d2ab5f5f7cfeccfe0015eb9e98
|
|
4
|
+
data.tar.gz: 4cb0a21f72168e3d7c9949642866498f85fde445a7f2cf3b5ad8022008f4c8b8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c1558d6093e5b624e53e1ab474695d68d1b07338663c9dcf6752fd89760b9c5a4f7651b82298dda850cc79dcdca9697b99079ba37f81f607b5079ec0bce0ede2
|
|
7
|
+
data.tar.gz: 0f744bd6f5f153a2cd3e3ca6434ee5dae5c0da5b02e58eea2f3e89b4abee0af52e419afbe0b69bfa45cfe61e5c0c343f80f7235be426b80637cae4fed809fee0
|
data/lib/tonal/approximation.rb
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
class Tonal::Ratio
|
|
2
2
|
class Approximation
|
|
3
|
+
DEFAULT_MIN_PRIME = 2
|
|
3
4
|
DEFAULT_MAX_PRIME = Float::INFINITY
|
|
4
5
|
DEFAULT_MAX_GRID_SCALE = 100
|
|
5
6
|
DEFAULT_MAX_GRID_BOUNDARY = 5
|
|
6
7
|
DEFAULT_DEPTH = Float::INFINITY
|
|
7
|
-
|
|
8
|
+
DEFAULT_TREE_PATH_DEPTH = 10
|
|
8
9
|
DEFAULT_SUPERPART_DEPTH = 20
|
|
9
10
|
DEFAULT_NEIGHBORHOOD_DEPTH = 10
|
|
10
11
|
DEFAULT_COMPLEXITY_AMOUNT = 50.0
|
|
@@ -23,19 +24,21 @@ class Tonal::Ratio
|
|
|
23
24
|
# @return [Tonal::Ratio::Approximation::Set] of ratios within cent tolerance of self found using continued fraction approximation
|
|
24
25
|
# @example
|
|
25
26
|
# Tonal::Ratio.ed(12,1).approximate.by_continued_fraction
|
|
26
|
-
# =>
|
|
27
|
+
# => 1.06: [17/16, 18/17, 89/84, 196/185, 1461/1379, 1657/1564, 3118/2943, 7893/7450, 18904/17843]
|
|
27
28
|
# @param cents_tolerance the cents tolerance used to scope the collection
|
|
28
29
|
# @param depth the maximum number of ratios in the collection
|
|
29
30
|
# @param max_prime the maximum prime number to allow in the collection
|
|
31
|
+
# @param min_prime the minimum prime number to allow in the collection
|
|
30
32
|
# @param conv_limit the number of convergents to limit the ContinuedFraction method
|
|
31
33
|
#
|
|
32
|
-
def by_continued_fraction(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME, conv_limit: CONVERGENT_LIMIT)
|
|
34
|
+
def by_continued_fraction(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME, conv_limit: CONVERGENT_LIMIT)
|
|
35
|
+
return Set.new(ratio: ratio){|ratios| ratios << ratio} if (antecedent == 1) && (consequent == 1)
|
|
33
36
|
self_in_cents = to_cents
|
|
34
37
|
within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
|
|
35
38
|
Set.new(ratio: ratio) do |ratios|
|
|
36
39
|
ContinuedFraction.new(antecedent.to_f/consequent, conv_limit).convergents.each do |convergent|
|
|
37
40
|
ratio2 = ratio.class.new(convergent.numerator,convergent.denominator)
|
|
38
|
-
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) && (ratio2.max_prime <= max_prime) && (ratio2.min_prime >= min_prime)
|
|
39
42
|
break if ratios.length >= depth
|
|
40
43
|
end
|
|
41
44
|
end
|
|
@@ -43,19 +46,22 @@ class Tonal::Ratio
|
|
|
43
46
|
|
|
44
47
|
# @return [Tonal::Ratio::Approximation::Set] of fraction tree ratios within cent tolerance of self
|
|
45
48
|
# @example
|
|
46
|
-
# Tonal::Ratio.ed(12,1).approximate.by_tree_path
|
|
47
|
-
# =>
|
|
49
|
+
# Tonal::Ratio.ed(12,1).approximate.by_tree_path
|
|
50
|
+
# => 1.06: [17/16, 18/17, 35/33, 53/50, 71/67, 89/84, 107/101, 196/185, 285/269, 481/454]
|
|
48
51
|
# @param cents_tolerance the cents tolerance used to scope the collection
|
|
49
52
|
# @param depth the maximum number of ratios in the collection
|
|
50
53
|
# @param max_prime the maximum prime number to allow in the collection
|
|
54
|
+
# @param min_prime the minimum prime number to allow in the collection
|
|
51
55
|
#
|
|
52
|
-
def by_tree_path(cents_tolerance: Tonal::Cents::TOLERANCE, depth:
|
|
56
|
+
def by_tree_path(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_TREE_PATH_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME)
|
|
57
|
+
return Set.new(ratio: ratio){|ratios| ratios << ratio} if (antecedent == 1) && (consequent == 1)
|
|
53
58
|
self_in_cents = to_cents
|
|
54
59
|
within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
|
|
55
60
|
Set.new(ratio: ratio) do |ratios|
|
|
56
61
|
FractionTree.node(to_f).path.each do |node|
|
|
62
|
+
next if node.number.infinite?
|
|
57
63
|
ratio2 = ratio.class.new(node.number)
|
|
58
|
-
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) && (ratio2.max_prime <= max_prime) && (ratio2.min_prime >= min_prime)
|
|
59
65
|
break if ratios.length >= depth
|
|
60
66
|
end
|
|
61
67
|
end
|
|
@@ -64,20 +70,21 @@ class Tonal::Ratio
|
|
|
64
70
|
# @return [Tonal::Ratio::Approximation::Set] of superparticular approximations within cent tolerance of self
|
|
65
71
|
# @example
|
|
66
72
|
# Tonal::Ratio.new(3/2r).approximate.by_superparticular
|
|
67
|
-
# =>
|
|
73
|
+
# => 3/2: [1041/692, 1044/694, 1047/696, 1050/698, 1053/700, 1056/702, 1059/704, 1062/706, 1065/708, 1068/710, 1071/712, 1074/714, 1077/716, 1080/718, 1083/720, 1086/722, 1089/724, 1092/726, 1095/728, 1098/730]
|
|
68
74
|
# @param cents_tolerance the cents tolerance used to scope the collection
|
|
69
75
|
# @param depth the maximum number of ratios in the collection
|
|
70
76
|
# @param max_prime the maximum prime number to allow in the collection
|
|
77
|
+
# @param min_prime the minimum prime number to allow in the collection
|
|
71
78
|
# @param superpart if the superior part is the numerator or denominator
|
|
72
79
|
#
|
|
73
|
-
def by_superparticular(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_SUPERPART_DEPTH, max_prime: DEFAULT_MAX_PRIME, superpart: :upper)
|
|
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)
|
|
74
81
|
self_in_cents = to_cents
|
|
75
82
|
within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
|
|
76
83
|
Set.new(ratio: ratio) do |ratios|
|
|
77
84
|
n = 1
|
|
78
85
|
while true do
|
|
79
86
|
ratio2 = ratio.class.superparticular(n, factor: ratio.to_r, superpart:)
|
|
80
|
-
ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && ratio2.
|
|
87
|
+
ratios << ratio2 if ratio.class.within_cents?(self_in_cents, ratio2.to_cents, within) && (ratio2 != ratio) && (ratio2.max_prime <= max_prime) && (ratio2.min_prime <= min_prime)
|
|
81
88
|
break if ratios.length >= depth
|
|
82
89
|
n += 1
|
|
83
90
|
end
|
|
@@ -86,15 +93,16 @@ class Tonal::Ratio
|
|
|
86
93
|
|
|
87
94
|
# @return [Array] of ratios within cent tolerance of self found on the ratio grid
|
|
88
95
|
# @example
|
|
89
|
-
# Tonal::Ratio.new(3,2).approximate.by_neighborhood
|
|
90
|
-
# =>
|
|
96
|
+
# Tonal::Ratio.new(3,2).approximate.by_neighborhood
|
|
97
|
+
# => 3/2: [175/117, 178/119, 176/117, 181/121, 179/119, 184/123, 182/121, 187/125, 185/123, 190/127, 188/125]
|
|
91
98
|
# @param cents_tolerance the cents tolerance used to scope the collection
|
|
92
99
|
# @param depth the maximum number of ratios in the collection
|
|
93
100
|
# @param max_prime the maximum prime number to allow in the collection
|
|
101
|
+
# @param min_prime the minimum prime number to allow in the collection
|
|
94
102
|
# @param max_boundary the maximum distance grid ratios will be from the scaled ratio
|
|
95
103
|
# @param max_scale the maximum self will be scaled
|
|
96
104
|
#
|
|
97
|
-
def by_neighborhood(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_NEIGHBORHOOD_DEPTH, max_prime: DEFAULT_MAX_PRIME, max_boundary: DEFAULT_MAX_GRID_BOUNDARY, max_scale: DEFAULT_MAX_GRID_SCALE)
|
|
105
|
+
def by_neighborhood(cents_tolerance: Tonal::Cents::TOLERANCE, depth: DEFAULT_NEIGHBORHOOD_DEPTH, max_prime: DEFAULT_MAX_PRIME, min_prime: DEFAULT_MIN_PRIME, max_boundary: DEFAULT_MAX_GRID_BOUNDARY, max_scale: DEFAULT_MAX_GRID_SCALE)
|
|
98
106
|
self_in_cents = to_cents
|
|
99
107
|
within = cents_tolerance.kind_of?(Tonal::Cents) ? cents_tolerance : Tonal::Cents.new(cents: cents_tolerance)
|
|
100
108
|
Set.new(ratio: ratio) do |ratios|
|
|
@@ -105,7 +113,7 @@ class Tonal::Ratio
|
|
|
105
113
|
while boundary <= max_boundary
|
|
106
114
|
vacinity = ratio.respond_to?(:to_basic_ratio) ? to_basic_ratio.scale(scale) : ratio.scale(scale)
|
|
107
115
|
self.class.neighbors(away: boundary, vacinity: vacinity).each do |neighbor|
|
|
108
|
-
ratios << neighbor if ratio.class.within_cents?(self_in_cents, neighbor.to_cents, within) && neighbor.
|
|
116
|
+
ratios << neighbor if (neighbor != ratio) && ratio.class.within_cents?(self_in_cents, neighbor.to_cents, within) && (neighbor.max_prime <= max_prime) && (neighbor.min_prime >= min_prime)
|
|
109
117
|
end
|
|
110
118
|
boundary += 1
|
|
111
119
|
end
|
|
@@ -118,7 +126,7 @@ class Tonal::Ratio
|
|
|
118
126
|
# @return [Array] of bounding ratios in the ratio grid vacinity of antecedent/consequent scaled by scale
|
|
119
127
|
# @example
|
|
120
128
|
# Tonal::ReducedRatio.new(3,2).neighborhood(scale: 256, boundary: 2)
|
|
121
|
-
# => [
|
|
129
|
+
# => [766/514, 768/514, 767/513, 766/512, 768/513, 767/512, 770/514, 769/513, 768/512, 767/511, 769/512, 766/510, 768/511, 770/512, 769/511, 768/510, 770/510]
|
|
122
130
|
# @param scale [Integer] used to scale antecedent/consequent on coordinate system
|
|
123
131
|
# @param boundary [Integer] limit within which to calculate neighboring ratios
|
|
124
132
|
#
|
|
@@ -126,16 +134,16 @@ class Tonal::Ratio
|
|
|
126
134
|
scale = scale.round
|
|
127
135
|
vacinity = ratio.respond_to?(:to_basic_ratio) ? to_basic_ratio.scale(scale) : ratio.scale(scale)
|
|
128
136
|
SortedSet.new([].tap do |ratio_list|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
137
|
+
1.upto(boundary) do |away|
|
|
138
|
+
ratio_list << self.class.neighbors(away: away, vacinity: vacinity)
|
|
139
|
+
end
|
|
140
|
+
end.flatten).to_a
|
|
133
141
|
end
|
|
134
142
|
|
|
135
143
|
# @return [Array] an array of Tonal::Ratio neighbors in the scaled ratio's grid neighborhood
|
|
136
144
|
# @example
|
|
137
145
|
# Tonal::Ratio::Approximation.neighbors(vacinity: (3/2r).ratio(reduced:false).scale(256), away: 1)
|
|
138
|
-
# => [
|
|
146
|
+
# => [768/512, 769/512, 767/512, 768/513, 768/511, 769/513, 769/511, 767/513, 767/511]
|
|
139
147
|
# @param away [Integer] the neighbors distance away from self's antecedent and consequent
|
|
140
148
|
#
|
|
141
149
|
def self.neighbors(vacinity:, away: 1)
|
|
@@ -149,10 +157,9 @@ class Tonal::Ratio
|
|
|
149
157
|
vacinity.class.new(vacinity.antecedent-away, vacinity.consequent+away),
|
|
150
158
|
vacinity.class.new(vacinity.antecedent-away, vacinity.consequent-away)]
|
|
151
159
|
end
|
|
152
|
-
|
|
153
160
|
class Set
|
|
154
161
|
extend Forwardable
|
|
155
|
-
def_delegators :@ratios, :count, :length, :min, :max, :entries, :all?, :any?, :reject, :map, :find_index, :to_a
|
|
162
|
+
def_delegators :@ratios, :count, :length, :min, :max, :entries, :all?, :any?, :reject, :map, :find_index, :to_a, :include?
|
|
156
163
|
|
|
157
164
|
attr_reader :ratios, :ratio
|
|
158
165
|
|
|
@@ -167,6 +174,26 @@ class Tonal::Ratio
|
|
|
167
174
|
"#{ratio}: #{entries}"
|
|
168
175
|
end
|
|
169
176
|
|
|
177
|
+
def to_cents
|
|
178
|
+
entries.map(&:to_cents)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def max_primes
|
|
182
|
+
entries.map(&:max_prime)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def min_primes
|
|
186
|
+
entries.map(&:min_prime)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def prime_divisions
|
|
190
|
+
entries.map(&:prime_divisions)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def [](index)
|
|
194
|
+
entries[index]
|
|
195
|
+
end
|
|
196
|
+
|
|
170
197
|
def sort_by(&)
|
|
171
198
|
self.class.new(ratio: ratio) do |ratios|
|
|
172
199
|
entries.sort_by(&).each do |ratio|
|
data/lib/tonal/attributions.rb
CHANGED
data/lib/tonal/cents.rb
CHANGED
|
@@ -95,7 +95,44 @@ class Tonal::Cents
|
|
|
95
95
|
end
|
|
96
96
|
alias :to_s :inspect
|
|
97
97
|
|
|
98
|
+
# Operator overloads
|
|
98
99
|
#
|
|
100
|
+
# @return [Tonal::Cents, Numeric] result of operation
|
|
101
|
+
# @example
|
|
102
|
+
# Tonal::Cents.new(cents: 200) - Tonal::Cents.new(cents: 100) => 100.0
|
|
103
|
+
# @param rhs [Tonal::Cents, Numeric]
|
|
104
|
+
#
|
|
105
|
+
def -(rhs)
|
|
106
|
+
method_missing(:-, rhs)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @return [Tonal::Cents, Numeric] result of operation
|
|
110
|
+
# @example
|
|
111
|
+
# Tonal::Cents.new(cents: 100) + Tonal::Cents.new(cents: 200) => 300.0
|
|
112
|
+
# @param rhs [Tonal::Cents, Numeric]
|
|
113
|
+
#
|
|
114
|
+
def +(rhs)
|
|
115
|
+
method_missing(:+, rhs)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @return [Tonal::Cents, Numeric] result of operation
|
|
119
|
+
# @example
|
|
120
|
+
# Tonal::Cents.new(cents: 200) * 2 => 400.0
|
|
121
|
+
# @param rhs [Tonal::Cents, Numeric]
|
|
122
|
+
#
|
|
123
|
+
def *(rhs)
|
|
124
|
+
method_missing(:*, rhs)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# @return [Tonal::Cents, Numeric] result of operation
|
|
128
|
+
# @example
|
|
129
|
+
# Tonal::Cents.new(cents: 400) / 2 => 200.0
|
|
130
|
+
# @param rhs [Tonal::Cents, Numeric]
|
|
131
|
+
#
|
|
132
|
+
def /(rhs)
|
|
133
|
+
method_missing(:/, rhs)
|
|
134
|
+
end
|
|
135
|
+
|
|
99
136
|
# Challenges to comparing floats
|
|
100
137
|
# https://www.rubydoc.info/gems/rubocop/RuboCop/Cop/Lint/FloatComparison
|
|
101
138
|
# https://embeddeduse.com/2019/08/26/qt-compare-two-floats/
|
data/lib/tonal/extensions.rb
CHANGED
|
@@ -45,15 +45,15 @@ class Numeric
|
|
|
45
45
|
# @param reduced
|
|
46
46
|
# @param equave
|
|
47
47
|
#
|
|
48
|
-
def to_ratio(reduced: false, equave: 2/1r) = reduced ? Tonal::ReducedRatio.new(self, equave:
|
|
48
|
+
def to_ratio(reduced: false, equave: 2/1r) = reduced ? Tonal::ReducedRatio.new(self, equave:) : Tonal::Ratio.new(self, equave:)
|
|
49
49
|
alias :ratio :to_ratio
|
|
50
50
|
|
|
51
51
|
# @return [Tonal::ReducedRatio]
|
|
52
52
|
# @example
|
|
53
|
-
#
|
|
53
|
+
# (4/5r).to_reduced_ratio => 8/5
|
|
54
54
|
# @param equave
|
|
55
55
|
#
|
|
56
|
-
def to_reduced_ratio(equave: 2/1r) = to_ratio(reduced: true, equave:
|
|
56
|
+
def to_reduced_ratio(equave: 2/1r) = to_ratio(reduced: true, equave:)
|
|
57
57
|
|
|
58
58
|
# @return [Float], the degrees on a circle of self
|
|
59
59
|
# @example
|
|
@@ -63,9 +63,10 @@ class Numeric
|
|
|
63
63
|
|
|
64
64
|
# @return [Tonal::Log] the log of self to the given base
|
|
65
65
|
# @example
|
|
66
|
-
# (3/2r).log(10) => 0.
|
|
66
|
+
# (3/2r).log(10) => 0.18
|
|
67
67
|
#
|
|
68
|
-
def log(base) = Tonal::Log.new(logarithmand: self, base:
|
|
68
|
+
def log(base) = Tonal::Log.new(logarithmand: self, base:)
|
|
69
|
+
alias :to_log :log
|
|
69
70
|
|
|
70
71
|
# @return [Tonal::Log2] the log2 of self
|
|
71
72
|
# @example
|
|
@@ -94,16 +95,18 @@ class Numeric
|
|
|
94
95
|
def to_cents = Tonal::Cents.new(ratio: self)
|
|
95
96
|
|
|
96
97
|
# @return [Tonal::Hertz] of self
|
|
98
|
+
# @example
|
|
99
|
+
# (440.0).hz => 440.0 Hz
|
|
97
100
|
#
|
|
98
101
|
def hz = Tonal::Hertz.new(self)
|
|
99
102
|
alias :to_hz :hz
|
|
100
103
|
|
|
101
|
-
# @return [Tonal::Step] the step of self in the given modulo
|
|
104
|
+
# @return [Tonal::Scale::Step] the step of self in the given modulo
|
|
102
105
|
# @example
|
|
103
106
|
# (5/4r).scale_step(12) => 4\12
|
|
104
107
|
# @param modulo
|
|
105
108
|
#
|
|
106
|
-
def scale_step(modulo=12) = Tonal::Step.new(ratio: self, modulo:
|
|
109
|
+
def scale_step(modulo=12) = Tonal::Scale::Step.new(ratio: self, modulo:)
|
|
107
110
|
|
|
108
111
|
# @return [Float] the log product complexity of self
|
|
109
112
|
# @example
|
|
@@ -137,22 +140,25 @@ class Numeric
|
|
|
137
140
|
# @param equave
|
|
138
141
|
# @param prime_rejects
|
|
139
142
|
#
|
|
140
|
-
def wilson_height(reduced: false, equave: 2/1r, prime_rejects: [2]) = ratio(reduced
|
|
143
|
+
def wilson_height(reduced: false, equave: 2/1r, prime_rejects: [2]) = ratio(reduced:, equave:).wilson_height(prime_rejects:)
|
|
141
144
|
|
|
142
145
|
# @return [Float] the cents difference between self and its step in the given modulo
|
|
143
146
|
# @example
|
|
144
147
|
# (3/2r).efficiency(12) => -1.96
|
|
145
148
|
# @param modulo
|
|
149
|
+
# @param reduced
|
|
146
150
|
#
|
|
147
|
-
|
|
148
|
-
def efficiency(modulo, reduced: false) = ratio(reduced: reduced).efficiency(modulo)
|
|
151
|
+
def efficiency(modulo, reduced: false) = ratio(reduced:).efficiency(modulo)
|
|
149
152
|
|
|
150
153
|
# @return [Tonal::Interval] beween self (upper) and ratio (lower)
|
|
151
154
|
# @example
|
|
152
|
-
# (
|
|
153
|
-
# @
|
|
155
|
+
# (3/2r).interval_with(4/3r) => 9/8 (3/2 / 4/3)
|
|
156
|
+
# @example
|
|
157
|
+
# (3/2r).interval_with(4/3r, is_lower: false) => 16/9 (4/3 / 3/2)
|
|
158
|
+
# @param other_ratio the other ratio to form the interval with
|
|
159
|
+
# @param is_lower [Boolean] if other_ratio is the lower (true) or upper (false) ratio
|
|
154
160
|
#
|
|
155
|
-
def interval_with(other_ratio) = Tonal::
|
|
161
|
+
def interval_with(other_ratio, is_lower: true) = Tonal::Ratio.new(self).interval_with(other_ratio, is_lower:)
|
|
156
162
|
|
|
157
163
|
# @return [Tonal::Interval] between 1/1 (lower) and self (upper)
|
|
158
164
|
# @example
|
|
@@ -162,8 +168,7 @@ class Numeric
|
|
|
162
168
|
|
|
163
169
|
# @return [Tonal::Cents] difference between ratio (upper) and self (lower)
|
|
164
170
|
# @example
|
|
165
|
-
# (133).cents_difference_with(3/2r)
|
|
166
|
-
# => 635.62
|
|
171
|
+
# (133).cents_difference_with(3/2r) => 635.62
|
|
167
172
|
# @param other_ratio
|
|
168
173
|
#
|
|
169
174
|
def cents_difference_with(other_ratio) = interval_with(other_ratio).to_cents
|
|
@@ -173,7 +178,7 @@ class Numeric
|
|
|
173
178
|
# (3/2r).prime_vector => Vector[-1, 1]
|
|
174
179
|
# @param reduced
|
|
175
180
|
#
|
|
176
|
-
def prime_vector(reduced: false) = ratio(reduced:
|
|
181
|
+
def prime_vector(reduced: false) = ratio(reduced:).prime_vector
|
|
177
182
|
alias :monzo :prime_vector
|
|
178
183
|
alias :prime_exponent_vector :prime_vector
|
|
179
184
|
|
|
@@ -226,21 +231,30 @@ class Numeric
|
|
|
226
231
|
# @example
|
|
227
232
|
# (3/2r).reciprocal => (2/3)
|
|
228
233
|
#
|
|
229
|
-
def reciprocal = Rational(1,self)
|
|
234
|
+
def reciprocal = Rational(1, self)
|
|
230
235
|
|
|
231
236
|
# @return [Numeric] self raised to the given power/root
|
|
232
237
|
# @example
|
|
233
|
-
# (3/2r).
|
|
234
|
-
# @param power
|
|
235
|
-
# @param root
|
|
238
|
+
# (3/2r).power(3,2) => 1.8371173070873832
|
|
239
|
+
# @param p the power
|
|
240
|
+
# @param r the root
|
|
236
241
|
#
|
|
237
|
-
def
|
|
238
|
-
if
|
|
239
|
-
self
|
|
242
|
+
def power(p, r=nil)
|
|
243
|
+
if r
|
|
244
|
+
(self.root(r))**p
|
|
240
245
|
else
|
|
241
|
-
self**
|
|
246
|
+
self**p
|
|
242
247
|
end
|
|
243
248
|
end
|
|
249
|
+
|
|
250
|
+
# @return [Numeric] self raised to the given root
|
|
251
|
+
# @example
|
|
252
|
+
# (3/2r).root(2) => 1.224744871391589
|
|
253
|
+
# @param r the root
|
|
254
|
+
#
|
|
255
|
+
def root(r)
|
|
256
|
+
self**Rational(1, r)
|
|
257
|
+
end
|
|
244
258
|
end
|
|
245
259
|
|
|
246
260
|
class Integer
|
|
@@ -248,10 +262,11 @@ class Integer
|
|
|
248
262
|
|
|
249
263
|
# @return [Tonal::ReducedRatio] the ratio 2**(self/modulo)
|
|
250
264
|
# @example
|
|
251
|
-
# 1.
|
|
265
|
+
# 1.ed(12) => 1.06
|
|
252
266
|
# @param modulo
|
|
267
|
+
# @param equave
|
|
253
268
|
#
|
|
254
|
-
def
|
|
269
|
+
def ed(modulo, equave: 2/1r) = Tonal::ReducedRatio.ed(self, modulo, equave:)
|
|
255
270
|
|
|
256
271
|
# @return [Integer] the maximum prime factor of self
|
|
257
272
|
# @example
|
|
@@ -548,7 +563,7 @@ class Vector
|
|
|
548
563
|
# @param reduced
|
|
549
564
|
# @param equave
|
|
550
565
|
#
|
|
551
|
-
def to_ratio(reduced: false, equave: 2/1r) = reduced ? Tonal::ReducedRatio.new(*self, equave:
|
|
566
|
+
def to_ratio(reduced: false, equave: 2/1r) = reduced ? Tonal::ReducedRatio.new(*self, equave:) : Tonal::Ratio.new(*self, equave:)
|
|
552
567
|
alias :ratio :to_ratio
|
|
553
568
|
end
|
|
554
569
|
|
data/lib/tonal/hertz.rb
CHANGED
|
@@ -5,7 +5,7 @@ class Tonal::Hertz
|
|
|
5
5
|
|
|
6
6
|
# @return [Tonal::Hertz]
|
|
7
7
|
# @example
|
|
8
|
-
# Tonal::Hertz.new(1000.0) => 1000.0
|
|
8
|
+
# Tonal::Hertz.new(1000.0) => 1000.0 Hz
|
|
9
9
|
# @param arg [Numeric, Tonal::Hertz]
|
|
10
10
|
#
|
|
11
11
|
def initialize(arg)
|
|
@@ -15,7 +15,7 @@ class Tonal::Hertz
|
|
|
15
15
|
|
|
16
16
|
# @return [Tonal::Hertz] 440 Hz
|
|
17
17
|
# @example
|
|
18
|
-
# Tonal::Hertz.a440 => 440.0
|
|
18
|
+
# Tonal::Hertz.a440 => 440.0 Hz
|
|
19
19
|
#
|
|
20
20
|
def self.a440
|
|
21
21
|
self.new(440.0)
|
|
@@ -40,10 +40,10 @@ class Tonal::Hertz
|
|
|
40
40
|
|
|
41
41
|
# @return [String] the string representation of Tonal::Hertz
|
|
42
42
|
# @example
|
|
43
|
-
# Tonal::Hertz(1000.0).inspect => "1000.0"
|
|
43
|
+
# Tonal::Hertz(1000.0).inspect => "1000.0 Hz"
|
|
44
44
|
#
|
|
45
45
|
def inspect
|
|
46
|
-
"#{value}"
|
|
46
|
+
"#{value} Hz"
|
|
47
47
|
end
|
|
48
48
|
|
|
49
49
|
def <=>(rhs)
|
data/lib/tonal/interval.rb
CHANGED
|
@@ -2,61 +2,112 @@ class Tonal::Interval
|
|
|
2
2
|
extend Forwardable
|
|
3
3
|
include Comparable
|
|
4
4
|
|
|
5
|
-
def_delegators :@
|
|
5
|
+
def_delegators :@intervalic_ratio, :to_r, :antecedent, :consequent, :to_cents
|
|
6
6
|
|
|
7
|
-
attr_reader :lower_ratio, :upper_ratio, :
|
|
7
|
+
attr_reader :lower_ratio, :upper_ratio, :intervalic_ratio
|
|
8
8
|
|
|
9
9
|
INTERVAL_OF_EQUIVALENCE = 2/1r
|
|
10
10
|
|
|
11
11
|
# @return [Tonal::Interval] the interval of the given ratios
|
|
12
12
|
# @example
|
|
13
|
-
# Tonal::Interval.new(
|
|
13
|
+
# Tonal::Interval.new(5) => 5/4 (5/4 / 1/1)
|
|
14
14
|
# @example
|
|
15
|
-
# Tonal::Interval.new(
|
|
15
|
+
# Tonal::Interval.new(3/2r, 5/4r) => 6/5 (3/2 / 5/4)
|
|
16
16
|
# @example
|
|
17
|
-
# Tonal::Interval.new(3) =>
|
|
18
|
-
# @param args two arguments representing ratios or four arguments representing two pairs
|
|
17
|
+
# Tonal::Interval.new(3,2,4,3) => 9/8 (3/2 / 4/3)
|
|
18
|
+
# @param args one argument representing a ratio, with 1/1r implied, or two arguments representing lower and upper ratios, or four arguments representing two numerator/denominator pairs for the lower and upper ratios
|
|
19
19
|
# @param reduced boolean determining whether to use Tonal::ReducedRatio or Tonal::Ratio
|
|
20
20
|
#
|
|
21
21
|
def initialize(*args, reduced: true)
|
|
22
|
-
|
|
22
|
+
raise(ArgumentError, "One, two or four arguments required. Either one ratio (the other defaulting to 1/1), two ratios, or two pairs of numerator, denominator", caller[0]) unless [1, 2, 4].include?(args.size)
|
|
23
|
+
args = [args[0],1/1r] if args.length == 1
|
|
23
24
|
klass = reduced ? Tonal::ReducedRatio : Tonal::Ratio
|
|
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)
|
|
25
25
|
@lower_ratio, @upper_ratio = case args.size
|
|
26
26
|
when 2
|
|
27
|
-
[klass.new(args[
|
|
27
|
+
[klass.new(args[1].antecedent, args[1].consequent), klass.new(args[0].antecedent, args[0].consequent)]
|
|
28
28
|
when 4
|
|
29
|
-
[klass.new(args[
|
|
29
|
+
[klass.new(args[2], args[3]), klass.new(args[0], args[1])]
|
|
30
30
|
end
|
|
31
|
-
@
|
|
31
|
+
@intervalic_ratio = @upper_ratio / @lower_ratio
|
|
32
32
|
end
|
|
33
|
-
alias :ratio :interval
|
|
34
|
-
alias :lower :lower_ratio
|
|
35
|
-
alias :upper :upper_ratio
|
|
36
33
|
alias :numerator :antecedent
|
|
37
34
|
alias :denominator :consequent
|
|
38
35
|
|
|
36
|
+
# @return [Array<Tonal::Ratio>] the upper and lower ratios as an array
|
|
37
|
+
# @example
|
|
38
|
+
# interval = Tonal::Interval.new(3,2) => 3/2 (3/2 / 1/1)
|
|
39
|
+
# interval.to_a => [3/2, 1/1]
|
|
40
|
+
#
|
|
39
41
|
def to_a
|
|
40
|
-
[
|
|
42
|
+
[upper_ratio, lower_ratio]
|
|
41
43
|
end
|
|
42
44
|
|
|
45
|
+
# @return [Array<Tonal::Ratio>] the ratios with common denominators
|
|
46
|
+
# @example
|
|
47
|
+
# interval = Tonal::Interval.new(3,4) => 4/3 (4/3 / 1/1)
|
|
48
|
+
# interval.denominize => [3/2, 2/2]
|
|
49
|
+
#
|
|
43
50
|
def denominize
|
|
44
51
|
ratios = to_a
|
|
45
52
|
lcm = ratios.denominators.lcm
|
|
46
53
|
ratios.map{|r| Tonal::Ratio.new(lcm / r.denominator * r.numerator, lcm)}
|
|
47
54
|
end
|
|
48
55
|
|
|
56
|
+
# @return [Tonal::Interval] the interval representing the root of self raised to a power
|
|
57
|
+
# @example
|
|
58
|
+
# interval = Tonal::Interval.new(4/3r) => 4/3 (4/3 / 1/1)
|
|
59
|
+
# interval.root_interval(power: 1, root: 2) => 1.15 (1.15 / 1/1)
|
|
60
|
+
# @param power [Integer] the power to which the root is raised
|
|
61
|
+
# @param root [Integer] the root to be taken
|
|
62
|
+
# @param approximant [Integer, nil] the index of the approximant to use
|
|
63
|
+
# @param by_method [Symbol] the method to use for approximation (:continued_fraction or :tree_path)
|
|
64
|
+
# @param from [Symbol] whether to return the interval calculated from the lower or upper ratio (:lower_ratio or :upper_ratio) of the interval
|
|
65
|
+
#
|
|
66
|
+
def root_interval(power: 1, root: 2, approximant: nil, by_method: :continued_fraction, from: :lower_ratio)
|
|
67
|
+
root_ratio = intervalic_ratio.class.new(intervalic_ratio.to_r.power(power, root))
|
|
68
|
+
resulting_ratio = approximant.nil? ? root_ratio : _approximate(root_ratio, by_method, approximant)
|
|
69
|
+
_returning_interval(resulting_ratio, from:)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @return [Tonal::Interval] the interval selected by the approximant key, from a set of approximations generated by the by_method algorithm
|
|
73
|
+
# @example
|
|
74
|
+
# interval = Tonal::Interval.new(1.2) => 1.2 (1.2 / 1/1)
|
|
75
|
+
# interval.approximate => 6/5 (6/5 / 1/1)
|
|
76
|
+
# @param approximant [Integer] the index of the approximant to use
|
|
77
|
+
# @param by_method [Symbol] the method to use for approximation (:continued_fraction or :tree_path)
|
|
78
|
+
# @param from [Symbol] whether to return the interval calculated from the lower or upper ratio (:lower_ratio or :upper_ratio) of the interval
|
|
79
|
+
#
|
|
80
|
+
def approximate(approximant=0, by_method: :continued_fraction, from: :lower_ratio)
|
|
81
|
+
_returning_interval(_approximate(intervalic_ratio, by_method, approximant), from:)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# @return [String] a string representation of the interval
|
|
85
|
+
# @example
|
|
86
|
+
# interval = Tonal::Interval.new(4,3) => 4/3 (4/3 / 1/1)
|
|
87
|
+
# interval.inspect => "4/3 (4/3 / 1/1)"
|
|
88
|
+
#
|
|
49
89
|
def inspect
|
|
50
|
-
"#{
|
|
90
|
+
"#{intervalic_ratio.label} (#{upper_ratio.label} / #{lower_ratio.label})"
|
|
51
91
|
end
|
|
52
92
|
|
|
93
|
+
# @return [Integer] -1, 0, or 1 depending on whether this interval is less than, equal to, or greater than the other interval
|
|
94
|
+
# @example
|
|
95
|
+
# interval1 = Tonal::Interval.new(4,3) => 4/3 (4/3 / 1/1)
|
|
96
|
+
# interval2 = Tonal::Interval.new(3,2) => 3/2 (3/2 / 1/1)
|
|
97
|
+
# interval1 <=> interval2 => -1
|
|
98
|
+
#
|
|
53
99
|
def <=>(rhs)
|
|
54
|
-
|
|
100
|
+
intervalic_ratio.to_r <=> rhs.intervalic_ratio.to_r
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
private
|
|
104
|
+
def _returning_interval(resulting_ratio, from:)
|
|
105
|
+
from = [:lower_ratio, :upper_ratio].include?(from) ? from : :lower_ratio
|
|
106
|
+
from == :lower_ratio ? Tonal::Interval.new(resulting_ratio, lower_ratio) : Tonal::Interval.new(upper_ratio, resulting_ratio)
|
|
55
107
|
end
|
|
56
|
-
end
|
|
57
108
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
109
|
+
def _approximate(ratio, by_method, approximant)
|
|
110
|
+
by_method = [:continued_fraction, :tree_path].include?(by_method) ? "by_#{by_method}".to_sym : :by_continued_fraction
|
|
111
|
+
ratio.approximate.send(by_method)[approximant]
|
|
61
112
|
end
|
|
62
113
|
end
|
data/lib/tonal/irb_helpers.rb
CHANGED
|
@@ -2,31 +2,33 @@ module Tonal
|
|
|
2
2
|
module IRBHelpers
|
|
3
3
|
# @return [Tonal::Ratio] an unreduced ratio
|
|
4
4
|
# @example
|
|
5
|
-
# r(3,3) =>
|
|
6
|
-
# @
|
|
7
|
-
#
|
|
5
|
+
# r(3,3) => 3/3
|
|
6
|
+
# @example
|
|
7
|
+
# r.ed(12,2) => 1.12
|
|
8
|
+
# @param args the ratio if only argument provided, or the numerator and denominator if two arguments are provided
|
|
8
9
|
#
|
|
9
|
-
def r(
|
|
10
|
-
Tonal::Ratio.new(
|
|
10
|
+
def r(*args)
|
|
11
|
+
args.empty? ? Tonal::Ratio : Tonal::Ratio.new(*args)
|
|
11
12
|
end
|
|
12
13
|
|
|
13
14
|
# @return [Tonal::ReducedRatio] a reduced ratio
|
|
14
15
|
# @example
|
|
15
|
-
# rr(3,3) =>
|
|
16
|
-
# @
|
|
17
|
-
#
|
|
16
|
+
# rr(3,3) => 1/1
|
|
17
|
+
# @example
|
|
18
|
+
# rr.ed(12,2) => 1.12
|
|
19
|
+
# @param args the ratio if only argument provided, or the numerator and denominator if two arguments are provided
|
|
18
20
|
#
|
|
19
|
-
def rr(
|
|
20
|
-
Tonal::ReducedRatio.new(
|
|
21
|
+
def rr(*args)
|
|
22
|
+
args.empty? ? Tonal::ReducedRatio : Tonal::ReducedRatio.new(*args)
|
|
21
23
|
end
|
|
22
24
|
|
|
23
25
|
# @return [Tonal::Interval] the interval between the given args
|
|
24
26
|
# @example
|
|
25
|
-
# i(2
|
|
27
|
+
# i(3,2) => 3/2 (3/2 / 1/1)
|
|
26
28
|
# @example
|
|
27
|
-
# i(2,
|
|
29
|
+
# i(3,2,4,3) => 9/8 (3/2 / 4/3)
|
|
28
30
|
# @example
|
|
29
|
-
# i(3) =>
|
|
31
|
+
# i(3) => 3/2 (3/2 / 1/1)
|
|
30
32
|
# @param args two arguments representing ratios or four arguments representing two pairs of numerator/denominator
|
|
31
33
|
# @param reduced boolean determining whether to use Tonal::ReducedRatio or Tonal::Ratio
|
|
32
34
|
#
|
data/lib/tonal/log.rb
CHANGED
|
@@ -61,12 +61,12 @@ class Tonal::Log
|
|
|
61
61
|
Tonal::Cents.new(log: self, precision: precision)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
# @return [Tonal::Step] the nearest step in the given modulo
|
|
64
|
+
# @return [Tonal::Scale::Step] the nearest step in the given modulo
|
|
65
65
|
# @example
|
|
66
66
|
# Tonal::Log.new(logarithmand: 3/2r, base: 2).step(12) => 7\12
|
|
67
67
|
#
|
|
68
68
|
def step(modulo)
|
|
69
|
-
Tonal::Step.new(modulo: modulo, log: self)
|
|
69
|
+
Tonal::Scale::Step.new(modulo: modulo, log: self)
|
|
70
70
|
end
|
|
71
71
|
|
|
72
72
|
# @return [String] the string representation of Tonal::Log
|
|
@@ -115,4 +115,3 @@ class Tonal::Log
|
|
|
115
115
|
end
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
|
-
|