coltrane 2.2.1 → 3.0.0.pre

Sign up to get free protection for your applications and to get access to all the features.
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