coltrane 2.2.1 → 3.0.0.pre

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.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +16 -1
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile.lock +25 -2
  5. data/README.md +3 -0
  6. data/bin/console +6 -2
  7. data/coltrane.gemspec +2 -1
  8. data/exe/coltrane +14 -224
  9. data/lib/coltrane.rb +3 -50
  10. data/lib/coltrane/commands.rb +14 -0
  11. data/lib/coltrane/commands/chords.rb +77 -0
  12. data/lib/coltrane/commands/command.rb +45 -0
  13. data/lib/coltrane/commands/common_chords.rb +33 -0
  14. data/lib/coltrane/commands/errors.rb +44 -0
  15. data/lib/coltrane/commands/find_progression.rb +28 -0
  16. data/lib/coltrane/commands/find_scale.rb +39 -0
  17. data/lib/coltrane/commands/notes.rb +44 -0
  18. data/lib/coltrane/commands/progression.rb +27 -0
  19. data/lib/{cli → coltrane/commands}/representation.rb +0 -0
  20. data/lib/coltrane/commands/scale.rb +46 -0
  21. data/lib/coltrane/renderers.rb +6 -0
  22. data/lib/coltrane/renderers/renderer.rb +42 -0
  23. data/lib/coltrane/renderers/text_renderer.rb +23 -0
  24. data/lib/coltrane/renderers/text_renderer/array_drawer.rb +45 -0
  25. data/lib/coltrane/renderers/text_renderer/base_drawer.rb +20 -0
  26. data/lib/coltrane/renderers/text_renderer/hash_drawer.rb +16 -0
  27. data/lib/coltrane/renderers/text_renderer/representation_guitar_chord_drawer.rb +95 -0
  28. data/lib/coltrane/renderers/text_renderer/representation_guitar_note_set_drawer.rb +76 -0
  29. data/lib/coltrane/renderers/text_renderer/representation_piano_note_set_drawer.rb +49 -0
  30. data/lib/coltrane/renderers/text_renderer/theory_chord_drawer.rb +14 -0
  31. data/lib/coltrane/renderers/text_renderer/theory_note_set_drawer.rb +17 -0
  32. data/lib/coltrane/renderers/text_renderer/theory_progression_drawer.rb +13 -0
  33. data/lib/coltrane/renderers/text_renderer/theory_progression_set_drawer.rb +22 -0
  34. data/lib/coltrane/renderers/text_renderer/theory_scale_drawer.rb +13 -0
  35. data/lib/coltrane/renderers/text_renderer/theory_scale_set_drawer.rb +27 -0
  36. data/lib/coltrane/representation.rb +12 -0
  37. data/lib/coltrane/representation/guitar.rb +34 -0
  38. data/lib/coltrane/representation/guitar/chord.rb +180 -0
  39. data/lib/coltrane/representation/guitar/note.rb +26 -0
  40. data/lib/coltrane/representation/guitar/note_set.rb +35 -0
  41. data/lib/coltrane/representation/guitar/string.rb +31 -0
  42. data/lib/coltrane/representation/guitar_like_instruments.rb +21 -0
  43. data/lib/coltrane/representation/piano.rb +36 -0
  44. data/lib/coltrane/representation/piano/note_set.rb +22 -0
  45. data/lib/{coltrane_synth.rb → coltrane/synth.rb.rb} +0 -0
  46. data/lib/{coltrane_synth → coltrane/synth}/base.rb +0 -0
  47. data/lib/{coltrane_synth → coltrane/synth}/synth.rb +0 -0
  48. data/lib/coltrane/theory.rb +54 -0
  49. data/lib/coltrane/theory/cadence.rb +9 -0
  50. data/lib/coltrane/{changes.rb → theory/changes.rb} +0 -0
  51. data/lib/coltrane/theory/chord.rb +101 -0
  52. data/lib/coltrane/theory/chord_quality.rb +113 -0
  53. data/lib/coltrane/theory/chord_substitutions.rb +11 -0
  54. data/lib/coltrane/theory/circle_of_fifths.rb +33 -0
  55. data/lib/coltrane/theory/classic_scales.rb +113 -0
  56. data/lib/coltrane/theory/diatonic_scale.rb +38 -0
  57. data/lib/coltrane/theory/errors.rb +97 -0
  58. data/lib/coltrane/theory/frequency.rb +52 -0
  59. data/lib/coltrane/theory/frequency_interval.rb +83 -0
  60. data/lib/coltrane/theory/interval.rb +209 -0
  61. data/lib/coltrane/theory/interval_class.rb +212 -0
  62. data/lib/coltrane/theory/interval_sequence.rb +157 -0
  63. data/lib/coltrane/theory/key.rb +18 -0
  64. data/lib/coltrane/{mode.rb → theory/mode.rb} +0 -0
  65. data/lib/coltrane/theory/notable_progressions.rb +30 -0
  66. data/lib/coltrane/theory/note.rb +104 -0
  67. data/lib/coltrane/theory/note_set.rb +101 -0
  68. data/lib/coltrane/theory/pitch.rb +94 -0
  69. data/lib/coltrane/theory/pitch_class.rb +154 -0
  70. data/lib/coltrane/theory/progression.rb +81 -0
  71. data/lib/coltrane/theory/progression_set.rb +18 -0
  72. data/lib/coltrane/{qualities.rb → theory/qualities.rb} +0 -0
  73. data/lib/coltrane/theory/roman_chord.rb +114 -0
  74. data/lib/coltrane/theory/scale.rb +161 -0
  75. data/lib/coltrane/theory/scale_search_result.rb +6 -0
  76. data/lib/coltrane/theory/scale_set.rb +40 -0
  77. data/lib/coltrane/theory/voicing.rb +41 -0
  78. data/lib/coltrane/version.rb +1 -1
  79. metadata +88 -63
  80. data/bin/rails +0 -17
  81. data/data/qualities.yml +0 -83
  82. data/db/cache.sqlite3 +0 -0
  83. data/db/cache_test.sqlite3 +0 -0
  84. data/db/config.yml +0 -11
  85. data/lib/cli.rb +0 -24
  86. data/lib/cli/bass_guitar.rb +0 -12
  87. data/lib/cli/chord.rb +0 -39
  88. data/lib/cli/config.rb +0 -39
  89. data/lib/cli/errors.rb +0 -46
  90. data/lib/cli/guitar.rb +0 -67
  91. data/lib/cli/guitar_chords.rb +0 -122
  92. data/lib/cli/notes.rb +0 -20
  93. data/lib/cli/piano.rb +0 -57
  94. data/lib/cli/scale.rb +0 -53
  95. data/lib/cli/text.rb +0 -16
  96. data/lib/cli/ukulele.rb +0 -14
  97. data/lib/coltrane/cache.rb +0 -43
  98. data/lib/coltrane/cadence.rb +0 -7
  99. data/lib/coltrane/chord.rb +0 -89
  100. data/lib/coltrane/chord_quality.rb +0 -111
  101. data/lib/coltrane/chord_substitutions.rb +0 -9
  102. data/lib/coltrane/circle_of_fifths.rb +0 -31
  103. data/lib/coltrane/classic_scales.rb +0 -94
  104. data/lib/coltrane/diatonic_scale.rb +0 -36
  105. data/lib/coltrane/errors.rb +0 -95
  106. data/lib/coltrane/frequency.rb +0 -50
  107. data/lib/coltrane/frequency_interval.rb +0 -81
  108. data/lib/coltrane/interval.rb +0 -208
  109. data/lib/coltrane/interval_class.rb +0 -210
  110. data/lib/coltrane/interval_sequence.rb +0 -155
  111. data/lib/coltrane/key.rb +0 -16
  112. data/lib/coltrane/notable_progressions.rb +0 -28
  113. data/lib/coltrane/note.rb +0 -98
  114. data/lib/coltrane/note_set.rb +0 -89
  115. data/lib/coltrane/pitch.rb +0 -92
  116. data/lib/coltrane/pitch_class.rb +0 -148
  117. data/lib/coltrane/progression.rb +0 -74
  118. data/lib/coltrane/roman_chord.rb +0 -112
  119. data/lib/coltrane/scale.rb +0 -154
  120. data/lib/coltrane/unordered_interval_class.rb +0 -7
  121. data/lib/coltrane/voicing.rb +0 -39
  122. data/lib/coltrane_game/question.rb +0 -7
  123. data/lib/coltrane_instruments.rb +0 -7
  124. data/lib/coltrane_instruments/guitar.rb +0 -10
  125. data/lib/coltrane_instruments/guitar/base.rb +0 -29
  126. data/lib/coltrane_instruments/guitar/chord.rb +0 -170
  127. data/lib/coltrane_instruments/guitar/note.rb +0 -24
  128. data/lib/coltrane_instruments/guitar/string.rb +0 -29
  129. data/lib/os.rb +0 -21
@@ -1,95 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # rubocop:disable Style/Documentation
4
-
5
- module Coltrane
6
- class ColtraneError < StandardError
7
- def initialize(msg)
8
- super msg
9
- end
10
- end
11
-
12
- class BadConstructorError < ColtraneError
13
- def initialize(msg = nil)
14
- super "Bad constructor. #{msg}"
15
- end
16
- end
17
-
18
- class WrongKeywordsError < BadConstructorError
19
- def initialize(msg)
20
- super "Use one of the following set of keywords: #{msg}"
21
- end
22
- end
23
-
24
- class WrongArgumentsError < BadConstructorError
25
- def initialize(_msg)
26
- super 'Wrong argument(s).'
27
- end
28
- end
29
-
30
- class InvalidNoteError < BadConstructorError
31
- def initialize(note)
32
- super "#{note} is not a valid note"
33
- end
34
- end
35
-
36
- class InvalidNotesError < BadConstructorError
37
- def initialize(notes)
38
- super "#{notes} are not a valid set of notes"
39
- end
40
- end
41
-
42
- class HasNoNotesError < BadConstructorError
43
- def initialize
44
- super 'The given object does not respond to :notes, '\
45
- "thereby it can't be used for this operation)"
46
- end
47
- end
48
-
49
- class WrongDegreeError
50
- def initialize(degree)
51
- super "#{degree} is not a valid degree. Degrees for this scale must be"\
52
- "between 1 and #{degrees}"
53
- end
54
- end
55
-
56
- class ChordNotFoundError < ColtraneError
57
- def initialize
58
- super "The chord you provided wasn't found. "\
59
- "If you're sure this chord exists, "\
60
- "would you mind to suggest it's inclusion here: "\
61
- 'https://github.com/pedrozath/coltrane/issues '\
62
- "\n\nA tip tho: always include the letter M for major"
63
- end
64
- end
65
-
66
- class IntervalNotFoundError < ColtraneError
67
- def initialize(arg)
68
- super "The interval \"#{arg}\" that was provided wasn't found. "\
69
- "If you're sure this interval exists, "\
70
- "would you mind to suggest it's inclusion here: "\
71
- 'https://github.com/pedrozath/coltrane/issues '\
72
- end
73
- end
74
-
75
- class InvalidPitchClassError < ColtraneError
76
- def initialize(arg)
77
- super "The given frequency(#{arg}) is not considered "\
78
- 'part of a pitch class'\
79
- end
80
- end
81
-
82
- class InvalidNoteSymbolError < ColtraneError
83
- def initialize(arg)
84
- super "The musical notation included an unrecognizable symbol (#{arg})."
85
- end
86
- end
87
-
88
- class InvalidNoteLetterError < ColtraneError
89
- def initialize(arg)
90
- super "The musical notation included an unrecognizable letter (#{arg})."
91
- end
92
- end
93
- end
94
-
95
- # rubocop:enable Style/Documentation
@@ -1,50 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- class Frequency
5
- attr_reader :frequency
6
-
7
- def initialize(frequency)
8
- @frequency = frequency.to_f
9
- end
10
-
11
- class << self
12
- alias [] new
13
- end
14
-
15
- def to_s
16
- "#{frequency}hz"
17
- end
18
-
19
- def to_f
20
- frequency
21
- end
22
-
23
- def octave(n)
24
- Frequency[frequency * 2**n]
25
- end
26
-
27
- def ==(other)
28
- frequency == (other.is_a?(Frequency) ? other.frequency : other)
29
- end
30
-
31
- def octave_up(n = 1)
32
- octave(n)
33
- end
34
-
35
- def octave_down(n = 1)
36
- octave(-n)
37
- end
38
-
39
- def /(other)
40
- case other
41
- when Frequency then FrequencyInterval[1200 * Math.log2(other.frequency / frequency)]
42
- when Numeric then Frequency[frequency / other]
43
- end
44
- end
45
-
46
- def method_missing(method, *args)
47
- Frequency[frequency.send(method, args[0].to_f)]
48
- end
49
- end
50
- end
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- # Interval describe the logarithmic distance between 2 frequencies.
5
- # It's measured in cents.
6
- class FrequencyInterval
7
- include Comparable
8
-
9
- attr_reader :cents
10
-
11
- class << self
12
- alias [] new
13
- end
14
-
15
- def initialize(cents)
16
- @cents = cents.round
17
- end
18
-
19
- def semitones
20
- (cents.to_f / 100).round
21
- end
22
-
23
- def ascending
24
- self.class[cents.abs]
25
- end
26
-
27
- def descending
28
- self.class[-cents.abs]
29
- end
30
-
31
- def ascending?
32
- cents > 0
33
- end
34
-
35
- def descending?
36
- cents < 0
37
- end
38
-
39
- def inversion
40
- self.class[(-cents.abs % 1200) * (ascending? ? +1 : -1)]
41
- end
42
-
43
- def opposite
44
- self.class.new(-cents)
45
- end
46
-
47
- def interval_class
48
- IntervalClass.new(semitones)
49
- end
50
-
51
- def ==(other)
52
- return false unless other.is_a? FrequencyInterval
53
- cents == other.cents
54
- end
55
-
56
- alias eql? ==
57
- alias hash cents
58
-
59
- def +(other)
60
- case other
61
- when Numeric then FrequencyInterval[cents + other]
62
- when Interval then FrequencyInterval[cents + other.cents]
63
- end
64
- end
65
-
66
- def -(other)
67
- case other
68
- when Numeric then FrequencyInterval[cents - other]
69
- when Interval then FrequencyInterval[cents - other.cents]
70
- end
71
- end
72
-
73
- def -@
74
- FrequencyInterval[-cents]
75
- end
76
-
77
- def <=>(other)
78
- cents <=> other.cents
79
- end
80
- end
81
- end
@@ -1,208 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- class Interval < IntervalClass
5
- attr_reader :letter_distance, :cents
6
- alias compound? compound
7
-
8
- class << self
9
- def all
10
- @all ||= super.map(&:interval)
11
- end
12
-
13
- def all_compound
14
- @all_compound ||= all.map(&:compound)
15
- end
16
-
17
- def all_including_compound
18
- @all_including_compound ||= all + all_compound
19
- end
20
-
21
- def all_augmented
22
- @all_augmented ||= all_including_compound.select(&:has_augmented?)
23
- .map(&:augmented)
24
- end
25
-
26
- def all_diminished
27
- @all_diminished ||= all_including_compound.select(&:has_diminished?)
28
- .map(&:diminished)
29
- end
30
-
31
- def all_including_compound_and_altered
32
- @all_including_compound_and_altered ||=
33
- all_including_compound +
34
- all_diminished +
35
- all_augmented
36
- end
37
- end
38
-
39
- def initialize(arg_1 = nil, arg_2 = nil, ascending: true,
40
- letter_distance: nil,
41
- semitones: nil,
42
- compound: false)
43
- if arg_1 && !arg_2 # assumes arg_1 is a letter
44
- @compound = compound
45
- IntervalClass[arg_1].interval.yield_self do |interval|
46
- @letter_distance = interval.letter_distance
47
- @cents = interval.cents
48
- end
49
- elsif arg_1 && arg_2 # assumes those are notes
50
- if ascending
51
- @compound = compound
52
- @cents =
53
- (arg_1.frequency / arg_2.frequency)
54
- .interval_class
55
- .cents
56
-
57
- @letter_distance = calculate_letter_distance arg_1.letter,
58
- arg_2.letter,
59
- ascending
60
- else
61
- self.class.new(arg_1, arg_2).descending.yield_self do |base_interval|
62
- @compound = base_interval.compound?
63
- @cents = base_interval.cents
64
- @letter_distance = base_interval.letter_distance
65
- end
66
- end
67
- elsif letter_distance && semitones
68
- @compound = compound || letter_distance > 8
69
- @cents = semitones * 100
70
- @letter_distance = letter_distance
71
- else
72
- raise WrongKeywordsError,
73
- '[interval_class_name]' \
74
- 'Provide: [first_note, second_note] || ' \
75
- '[letter_distance:, semitones:]'
76
- end
77
- end
78
-
79
- def self.[](arg)
80
- new(arg)
81
- end
82
-
83
- def interval_class
84
- FrequencyInterval[cents].interval_class
85
- end
86
-
87
- def compound?
88
- @compound
89
- end
90
-
91
- def has_augmented?
92
- name.match?(%r{M|P|A})
93
- end
94
-
95
- def has_diminished?
96
- name.match?(%r{m|P|d})
97
- end
98
-
99
- def accidentals
100
- case
101
- when distance_to_starting.positive? then 'A'
102
- when distance_to_starting.negative? then 'd'
103
- else return ''
104
- end * distance_to_starting.abs
105
- end
106
-
107
- def name
108
- @name ||= begin
109
- if distance_to_starting.zero? || distance_to_starting.abs > 2
110
- compound? ? interval_class.compound_name : interval_class.name
111
- else
112
- "#{accidentals}#{starting_interval.distance + (compound? ? 7 : 0)}"
113
- end
114
- end
115
- end
116
-
117
- def as(n)
118
- i = clone(letter_distance: n)
119
- i if i.name.match?(n.to_s)
120
- end
121
-
122
- def as!(n)
123
- i = as(n)
124
- i unless i&.name&.match?(/d|A/)
125
- end
126
-
127
- def as_diminished(n = 1)
128
- as(letter_distance + n)
129
- end
130
-
131
- def as_augmented(n = 1)
132
- as(letter_distance - n)
133
- end
134
-
135
- def clone(override_args = {})
136
- self.class.new({
137
- semitones: semitones,
138
- letter_distance: letter_distance,
139
- compound: compound?
140
- }.merge(override_args))
141
- end
142
-
143
- def diminish(n = 1)
144
- clone(semitones: semitones - n)
145
- end
146
-
147
- alias diminished diminish
148
-
149
- def augment(n = 1)
150
- clone(semitones: semitones + n)
151
- end
152
-
153
- alias augmented augment
154
-
155
- def opposite
156
- clone(semitones: -semitones, letter_distance: (-letter_distance % 8) + 1)
157
- end
158
-
159
- def ascending
160
- ascending? ? self : opposite
161
- end
162
-
163
- def descending
164
- descending? ? self : opposite
165
- end
166
-
167
- private
168
-
169
- def starting_interval # select the closest interval possible to start from
170
- @starting_interval ||= begin
171
- IntervalClass.all
172
- .select { |i| i.distance == normalized_letter_distance }
173
- .sort_by do |i|
174
- (cents - i.cents)
175
- .yield_self { |d| [(d % 1200), (d % -1200)].min_by(&:abs) }
176
- .abs
177
- end
178
- .first
179
- end
180
- end
181
-
182
- def normalized_letter_distance
183
- return letter_distance if letter_distance < 8
184
- (letter_distance % 8) + 1
185
- end
186
-
187
- def distance_to_starting # calculate the closts distance to it
188
- d = semitones - starting_interval.semitones
189
- [(d % 12), (d % -12)].min_by(&:abs)
190
- end
191
-
192
- def all_letters
193
- PitchClass.all_letters
194
- end
195
-
196
- def calculate_letter_distance(a, b, asc)
197
- all_letters
198
- .rotate(all_letters.index(asc ? a : b))
199
- .index(b) + 1
200
- end
201
-
202
- public
203
-
204
- all_including_compound_and_altered.each do |interval|
205
- self.class.define_method(interval.full_name.underscore) { interval.clone }
206
- end
207
- end
208
- end
@@ -1,210 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- # Interval class here is not related to the Object Oriented Programming context
5
- # but to the fact that there is a class of intervals that can all be categorized
6
- # as having the same quality.
7
- #
8
- # This class in specific still takes into account the order of intervals.
9
- # C to D is a major second, but D to C is a minor seventh.
10
- class IntervalClass < FrequencyInterval
11
- QUALITY_SEQUENCE = [
12
- %w[P],
13
- %w[m M],
14
- %w[m M],
15
- %w[P A],
16
- %w[P],
17
- %w[m M],
18
- %w[m M]
19
- ].freeze
20
-
21
- ALTERATIONS = {
22
- 'A' => +1,
23
- 'd' => -1
24
- }.freeze
25
-
26
- SINGLE_DISTANCES_NAMES = %w[
27
- Unison
28
- Second
29
- Third
30
- Fourth
31
- Fifth
32
- Sixth
33
- Seventh
34
- ].freeze
35
-
36
- COMPOUND_DISTANCES_NAMES = [
37
- 'Octave',
38
- 'Ninth',
39
- 'Tenth',
40
- 'Eleventh',
41
- 'Twelfth',
42
- 'Thirteenth',
43
- 'Fourteenth',
44
- 'Double Octave'
45
- ].freeze
46
-
47
- DISTANCES_NAMES = (SINGLE_DISTANCES_NAMES + COMPOUND_DISTANCES_NAMES).freeze
48
-
49
- QUALITY_NAMES = {
50
- 'P' => 'Perfect',
51
- 'm' => 'Minor',
52
- 'M' => 'Major',
53
- 'A' => 'Augmented',
54
- 'd' => 'Diminished'
55
- }.freeze
56
-
57
- class << self
58
- def distances_names
59
- DISTANCES_NAMES
60
- end
61
-
62
- def distance_name(n)
63
- DISTANCES_NAMES[n - 1]
64
- end
65
-
66
- def quality_name(q)
67
- QUALITY_NAMES[q]
68
- end
69
-
70
- def names
71
- @names ||= begin
72
- SINGLE_DISTANCES_NAMES.each_with_index.reduce([]) do |i_names, (_d, i)|
73
- i_names + QUALITY_SEQUENCE[i % 7].reduce([]) do |qs, q|
74
- qs + ["#{q}#{i + 1}"]
75
- end
76
- end
77
- end
78
- end
79
-
80
- def compound_names
81
- @compound_names ||= all.map(&:compound_name)
82
- end
83
-
84
- def all_names_including_compound
85
- @all_names_including_compound ||= names + compound_names
86
- end
87
-
88
- def full_names
89
- @full_names ||= names.map { |n| expand_name(n) }
90
- end
91
-
92
- def all
93
- @all ||= names.map { |n| IntervalClass[n] }
94
- end
95
-
96
- def full_names_including_compound
97
- @full_names_including_compound ||=
98
- all_names_including_compound.map { |n| expand_name(n) }
99
- end
100
-
101
- def split(interval)
102
- interval.scan(/(\w)(\d\d?)/)[0]
103
- end
104
-
105
- def expand_name(name)
106
- q, n = split(name)
107
- (
108
- case name
109
- when /AA|dd/ then 'Double '
110
- when /AAA|ddd/ then 'Triple '
111
- else ''
112
- end
113
- ) + "#{quality_name(q)} #{distance_name(n.to_i)}"
114
- end
115
- end
116
-
117
- def initialize(arg)
118
- super case arg
119
- when FrequencyInterval then arg.semitones
120
- when String
121
- self.class.names.index(arg) ||
122
- self.class.full_names.index(arg) ||
123
- self.class.all_names_including_compound.index(arg) ||
124
- self.class.full_names_including_compound.index(arg)
125
- when Numeric then arg
126
- else
127
- raise WrongArgumentsError,
128
- 'Provide: [interval] || [name] || [number of semitones]'
129
- end % 12 * 100
130
- end
131
-
132
- instance_eval { alias [] new }
133
-
134
- def interval
135
- Interval.new(letter_distance: distance, semitones: semitones)
136
- end
137
-
138
- def compound_interval
139
- Interval.new(
140
- letter_distance: distance,
141
- semitones: semitones,
142
- compound: true
143
- )
144
- end
145
-
146
- alias compound compound_interval
147
-
148
- def ==(other)
149
- return false unless other.is_a? FrequencyInterval
150
- (semitones % 12) == (other.semitones % 12)
151
- end
152
-
153
- def alteration
154
- name.chars.reduce(0) { |a, s| a + (ALTERATIONS[s] || 0) }
155
- end
156
-
157
- def ascending
158
- self.class[semitones.abs]
159
- end
160
-
161
- def descending
162
- self.class[-semitones.abs]
163
- end
164
-
165
- def inversion
166
- self.class[-semitones % 12]
167
- end
168
-
169
- def full_name
170
- self.class.expand_name(name)
171
- end
172
-
173
- def name
174
- self.class.names[semitones % 12]
175
- end
176
-
177
- def compound_name
178
- "#{quality}#{distance + 7}"
179
- end
180
-
181
- def distance
182
- self.class.split(name)[1].to_i
183
- end
184
-
185
- def quality
186
- self.class.split(name)[0]
187
- end
188
-
189
- def +(other)
190
- IntervalClass[semitones + other]
191
- end
192
-
193
- def -(other)
194
- IntervalClass[semitones - other]
195
- end
196
-
197
- def -@
198
- IntervalClass[-semitones]
199
- end
200
-
201
- private
202
-
203
- def self.interval_by_full_name(arg)
204
- NAMES.invert.each do |full_names, interval_name|
205
- return INTERVALS.index(interval_name) if full_names.include?(arg)
206
- end
207
- raise IntervalNotFoundError, arg
208
- end
209
- end
210
- end