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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e36a8220009cf3f4982c50b114d288841da16f32e79baa8657f2e4a9f12c1ac8
4
- data.tar.gz: 924bf36d40bf9c12049b2f0e5deab9650bb2ec6994316ac8c782e56dc275f965
3
+ metadata.gz: bc114e39cee0bcffd5b39e0e22cdc572ae7671d2ab5f5f7cfeccfe0015eb9e98
4
+ data.tar.gz: 4cb0a21f72168e3d7c9949642866498f85fde445a7f2cf3b5ad8022008f4c8b8
5
5
  SHA512:
6
- metadata.gz: 41d3c29bc92a809485fb36aba2ebe4bd43a443d33ccaac337e305d815b8117787ab3802ffd824c5e157917f74fc36244f5644042d1081bbba85cd2b402a974dc
7
- data.tar.gz: 144334ceb98fba4bd5e5d8fdbd211e31c1fa9da4af0342fb4ff9b3be117167acc1f388d0e971b13a6ae36590bc89a0ecf4a9b4ea793165fa731e2b934c2d777c
6
+ metadata.gz: c1558d6093e5b624e53e1ab474695d68d1b07338663c9dcf6752fd89760b9c5a4f7651b82298dda850cc79dcdca9697b99079ba37f81f607b5079ec0bce0ede2
7
+ data.tar.gz: 0f744bd6f5f153a2cd3e3ca6434ee5dae5c0da5b02e58eea2f3e89b4abee0af52e419afbe0b69bfa45cfe61e5c0c343f80f7235be426b80637cae4fed809fee0
@@ -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
- DEFAULT_FRACTION_TREE_DEPTH = 10
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
- # => (4771397596969315/4503599627370496): [(17/16), (18/17), (89/84), (196/185), (1461/1379), (1657/1564), (3118/2943), (7893/7450), (18904/17843)]
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.max_prime_within?(max_prime)
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(max_prime: 17)
47
- # => (4771397596969315/4503599627370496): [(17/16), (18/17), (35/33)]
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: DEFAULT_FRACTION_TREE_DEPTH, max_prime: DEFAULT_MAX_PRIME)
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.max_prime_within?(max_prime)
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
- # => (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)]
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.max_prime_within?(max_prime) && ratio2 != ratio
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(max_prime: 23, cents_tolerance: 5, max_boundary: 10, max_scale: 60)
90
- # => (3/2): [(175/117), (176/117)]
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.max_prime_within?(max_prime) && neighbor != ratio
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
- # => [(768/514), (766/512), (768/513), (767/512), (768/512), (769/512), (768/511), (770/512), (768/510)]
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
- 1.upto(boundary) do |away|
130
- ratio_list << self.class.neighbors(away: away, vacinity: vacinity)
131
- end
132
- end.flatten).to_a
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
- # => [(768/513), (767/512), (768/512), (769/512), (768/511)]
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|
@@ -1,4 +1,4 @@
1
1
  module Tonal
2
2
  TOOLS_PRODUCER = "mTonal"
3
- TOOLS_VERSION = "7.7.0"
3
+ TOOLS_VERSION = "8.3.1"
4
4
  end
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/
@@ -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: equave) : Tonal::Ratio.new(self, equave: 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
- # {4/5r}.to_reduced_ratio => 8/5
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: 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.17609125905568124
66
+ # (3/2r).log(10) => 0.18
67
67
  #
68
- def log(base) = Tonal::Log.new(logarithmand: self, base: 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: 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: reduced, equave: equave).wilson_height(prime_rejects: prime_rejects)
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
- # We want the efficiency from the ratio (self)
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
- # (133).interval_with(3/2r) => 133/96 (133/128 / 3/2)
153
- # @param other_ratio
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::Interval.new(ratio, other_ratio)
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: reduced).prime_vector
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).pr(3,2) => 1.8371173070873836
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 pr(power, root=nil)
238
- if root
239
- self**(Rational(power, root))
242
+ def power(p, r=nil)
243
+ if r
244
+ (self.root(r))**p
240
245
  else
241
- self**power
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.edo(12) => 1.06
265
+ # 1.ed(12) => 1.06
252
266
  # @param modulo
267
+ # @param equave
253
268
  #
254
- def edo(modulo) = (2**(Rational(self,modulo))).to_reduced_ratio
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: equave) : Tonal::Ratio.new(*self, equave: 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)
@@ -2,61 +2,112 @@ class Tonal::Interval
2
2
  extend Forwardable
3
3
  include Comparable
4
4
 
5
- def_delegators :@interval, :to_r, :antecedent, :consequent, :to_cents
5
+ def_delegators :@intervalic_ratio, :to_r, :antecedent, :consequent, :to_cents
6
6
 
7
- attr_reader :lower_ratio, :upper_ratio, :interval
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(2,3) => (3/2) ((3/2) / (1/1))
13
+ # Tonal::Interval.new(5) => 5/4 (5/4 / 1/1)
14
14
  # @example
15
- # Tonal::Interval.new(2,3,3,4) => (9/8) ((3/2) / (4/3))
15
+ # Tonal::Interval.new(3/2r, 5/4r) => 6/5 (3/2 / 5/4)
16
16
  # @example
17
- # Tonal::Interval.new(3) => (3/1) ((3/1) / (1/1))
18
- # @param args two arguments representing ratios or four arguments representing two pairs of numerator/denominator
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
- args = [1/1r, args[0]] if args.length == 1
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[0].antecedent, args[0].consequent), klass.new(args[1].antecedent, args[1].consequent)]
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[0],args[1]), klass.new(args[2], args[3])]
29
+ [klass.new(args[2], args[3]), klass.new(args[0], args[1])]
30
30
  end
31
- @interval = @upper_ratio / @lower_ratio
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
- [lower_ratio, upper_ratio]
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
- "#{interval.label} (#{upper.label} / #{lower.label})"
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
- interval.to_r <=> rhs.interval.to_r
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
- module Interval
59
- def self.[](l, u, reduced=true)
60
- Tonal::Interval.new(l, u, reduced:)
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
@@ -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) => (3/3)
6
- # @param arg1 the ratio if only argument provided, or the numerator if two argments are provided
7
- # @param arg2 the denominator when two arguments are provided
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(arg1, arg2=nil)
10
- Tonal::Ratio.new(arg1, arg2)
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) => (1/1)
16
- # @param arg1 the ratio if only argument provided, or the numerator if two argments are provided
17
- # @param arg2 the denominator when two arguments are provided
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(arg1, arg2=nil)
20
- Tonal::ReducedRatio.new(arg1, arg2)
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,3) => (3/2) ((3/2) / (1/1))
27
+ # i(3,2) => 3/2 (3/2 / 1/1)
26
28
  # @example
27
- # i(2,3,3,4) => (9/8) ((3/2) / (4/3))
29
+ # i(3,2,4,3) => 9/8 (3/2 / 4/3)
28
30
  # @example
29
- # i(3) => (3/1) ((3/1) / (1/1))
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
-