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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 21362801eb7f0b63d3c05d4f694a4288bf3b2c0adf5cc3548953f25971a441d7
4
- data.tar.gz: 6279ffac59c2af3bedcf794e945fbb4654599b87620df95badaa3431ee995f0a
3
+ metadata.gz: 769baf7d488e059f0f8072fdcf39dcf33567d14a631e6a55eece8c7c9ff864d1
4
+ data.tar.gz: 1e7ef0b95e95df63a2667ac04f2062ce0a62502ea577fd7c6d529e407ecc04da
5
5
  SHA512:
6
- metadata.gz: b502514508150824355941fad9c009aef56f7edc8e4246963e151389ca60b3acaf56c90434b7a52a27af8633668b00c581ea335078f5356c2c3d319d5fd60470
7
- data.tar.gz: f5e9614f05e070fe8642fdd7df7caacfde96b52f6963bc3c8a69c8821cc8b7bc61f84da613f877ddb3699acb5df6ac5b086d12eb6fe2ebf5a018df54f603179f
6
+ metadata.gz: 88ec036a5b4f17503d905c25bacf74a22d6d0c00b4a792d6196c5faecfcdebae3836711deb363a65863df4334a61f35d0b974de0fa42816390c0548e538a0a1f
7
+ data.tar.gz: 8f004f602f9e8780a7c0a64dbb1132ba323f7032411b8bd5617b89973c14047f5f607ea3c67d30287c29922719c3a3c580759463242fba73a3feac8280cee104
@@ -1,4 +1,4 @@
1
1
  module Tonal
2
2
  TOOLS_PRODUCER = "mTonal"
3
- TOOLS_VERSION = "7.3.0"
3
+ TOOLS_VERSION = "7.4.0"
4
4
  end
@@ -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
@@ -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(1/1r, self)
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
@@ -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)
@@ -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
- # Return the "antecedent/consequent", if antecedent is less than 7 digits long; or
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
- "(#{antecedent}/#{consequent})"
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
@@ -14,6 +14,7 @@ module Tonal
14
14
  require "tonal/approximation"
15
15
  require "tonal/ratio"
16
16
  require "tonal/reduced_ratio"
17
+ require "tonal/extended_ratio"
17
18
  require "tonal/interval"
18
19
  require "tonal/step"
19
20
  require "tonal/extensions"
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.3.0
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-04 00:00:00.000000000 Z
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