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.
- checksums.yaml +4 -4
- data/.travis.yml +16 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +25 -2
- data/README.md +3 -0
- data/bin/console +6 -2
- data/coltrane.gemspec +2 -1
- data/exe/coltrane +14 -224
- data/lib/coltrane.rb +3 -50
- data/lib/coltrane/commands.rb +14 -0
- data/lib/coltrane/commands/chords.rb +77 -0
- data/lib/coltrane/commands/command.rb +45 -0
- data/lib/coltrane/commands/common_chords.rb +33 -0
- data/lib/coltrane/commands/errors.rb +44 -0
- data/lib/coltrane/commands/find_progression.rb +28 -0
- data/lib/coltrane/commands/find_scale.rb +39 -0
- data/lib/coltrane/commands/notes.rb +44 -0
- data/lib/coltrane/commands/progression.rb +27 -0
- data/lib/{cli → coltrane/commands}/representation.rb +0 -0
- data/lib/coltrane/commands/scale.rb +46 -0
- data/lib/coltrane/renderers.rb +6 -0
- data/lib/coltrane/renderers/renderer.rb +42 -0
- data/lib/coltrane/renderers/text_renderer.rb +23 -0
- data/lib/coltrane/renderers/text_renderer/array_drawer.rb +45 -0
- data/lib/coltrane/renderers/text_renderer/base_drawer.rb +20 -0
- data/lib/coltrane/renderers/text_renderer/hash_drawer.rb +16 -0
- data/lib/coltrane/renderers/text_renderer/representation_guitar_chord_drawer.rb +95 -0
- data/lib/coltrane/renderers/text_renderer/representation_guitar_note_set_drawer.rb +76 -0
- data/lib/coltrane/renderers/text_renderer/representation_piano_note_set_drawer.rb +49 -0
- data/lib/coltrane/renderers/text_renderer/theory_chord_drawer.rb +14 -0
- data/lib/coltrane/renderers/text_renderer/theory_note_set_drawer.rb +17 -0
- data/lib/coltrane/renderers/text_renderer/theory_progression_drawer.rb +13 -0
- data/lib/coltrane/renderers/text_renderer/theory_progression_set_drawer.rb +22 -0
- data/lib/coltrane/renderers/text_renderer/theory_scale_drawer.rb +13 -0
- data/lib/coltrane/renderers/text_renderer/theory_scale_set_drawer.rb +27 -0
- data/lib/coltrane/representation.rb +12 -0
- data/lib/coltrane/representation/guitar.rb +34 -0
- data/lib/coltrane/representation/guitar/chord.rb +180 -0
- data/lib/coltrane/representation/guitar/note.rb +26 -0
- data/lib/coltrane/representation/guitar/note_set.rb +35 -0
- data/lib/coltrane/representation/guitar/string.rb +31 -0
- data/lib/coltrane/representation/guitar_like_instruments.rb +21 -0
- data/lib/coltrane/representation/piano.rb +36 -0
- data/lib/coltrane/representation/piano/note_set.rb +22 -0
- data/lib/{coltrane_synth.rb → coltrane/synth.rb.rb} +0 -0
- data/lib/{coltrane_synth → coltrane/synth}/base.rb +0 -0
- data/lib/{coltrane_synth → coltrane/synth}/synth.rb +0 -0
- data/lib/coltrane/theory.rb +54 -0
- data/lib/coltrane/theory/cadence.rb +9 -0
- data/lib/coltrane/{changes.rb → theory/changes.rb} +0 -0
- data/lib/coltrane/theory/chord.rb +101 -0
- data/lib/coltrane/theory/chord_quality.rb +113 -0
- data/lib/coltrane/theory/chord_substitutions.rb +11 -0
- data/lib/coltrane/theory/circle_of_fifths.rb +33 -0
- data/lib/coltrane/theory/classic_scales.rb +113 -0
- data/lib/coltrane/theory/diatonic_scale.rb +38 -0
- data/lib/coltrane/theory/errors.rb +97 -0
- data/lib/coltrane/theory/frequency.rb +52 -0
- data/lib/coltrane/theory/frequency_interval.rb +83 -0
- data/lib/coltrane/theory/interval.rb +209 -0
- data/lib/coltrane/theory/interval_class.rb +212 -0
- data/lib/coltrane/theory/interval_sequence.rb +157 -0
- data/lib/coltrane/theory/key.rb +18 -0
- data/lib/coltrane/{mode.rb → theory/mode.rb} +0 -0
- data/lib/coltrane/theory/notable_progressions.rb +30 -0
- data/lib/coltrane/theory/note.rb +104 -0
- data/lib/coltrane/theory/note_set.rb +101 -0
- data/lib/coltrane/theory/pitch.rb +94 -0
- data/lib/coltrane/theory/pitch_class.rb +154 -0
- data/lib/coltrane/theory/progression.rb +81 -0
- data/lib/coltrane/theory/progression_set.rb +18 -0
- data/lib/coltrane/{qualities.rb → theory/qualities.rb} +0 -0
- data/lib/coltrane/theory/roman_chord.rb +114 -0
- data/lib/coltrane/theory/scale.rb +161 -0
- data/lib/coltrane/theory/scale_search_result.rb +6 -0
- data/lib/coltrane/theory/scale_set.rb +40 -0
- data/lib/coltrane/theory/voicing.rb +41 -0
- data/lib/coltrane/version.rb +1 -1
- metadata +88 -63
- data/bin/rails +0 -17
- data/data/qualities.yml +0 -83
- data/db/cache.sqlite3 +0 -0
- data/db/cache_test.sqlite3 +0 -0
- data/db/config.yml +0 -11
- data/lib/cli.rb +0 -24
- data/lib/cli/bass_guitar.rb +0 -12
- data/lib/cli/chord.rb +0 -39
- data/lib/cli/config.rb +0 -39
- data/lib/cli/errors.rb +0 -46
- data/lib/cli/guitar.rb +0 -67
- data/lib/cli/guitar_chords.rb +0 -122
- data/lib/cli/notes.rb +0 -20
- data/lib/cli/piano.rb +0 -57
- data/lib/cli/scale.rb +0 -53
- data/lib/cli/text.rb +0 -16
- data/lib/cli/ukulele.rb +0 -14
- data/lib/coltrane/cache.rb +0 -43
- data/lib/coltrane/cadence.rb +0 -7
- data/lib/coltrane/chord.rb +0 -89
- data/lib/coltrane/chord_quality.rb +0 -111
- data/lib/coltrane/chord_substitutions.rb +0 -9
- data/lib/coltrane/circle_of_fifths.rb +0 -31
- data/lib/coltrane/classic_scales.rb +0 -94
- data/lib/coltrane/diatonic_scale.rb +0 -36
- data/lib/coltrane/errors.rb +0 -95
- data/lib/coltrane/frequency.rb +0 -50
- data/lib/coltrane/frequency_interval.rb +0 -81
- data/lib/coltrane/interval.rb +0 -208
- data/lib/coltrane/interval_class.rb +0 -210
- data/lib/coltrane/interval_sequence.rb +0 -155
- data/lib/coltrane/key.rb +0 -16
- data/lib/coltrane/notable_progressions.rb +0 -28
- data/lib/coltrane/note.rb +0 -98
- data/lib/coltrane/note_set.rb +0 -89
- data/lib/coltrane/pitch.rb +0 -92
- data/lib/coltrane/pitch_class.rb +0 -148
- data/lib/coltrane/progression.rb +0 -74
- data/lib/coltrane/roman_chord.rb +0 -112
- data/lib/coltrane/scale.rb +0 -154
- data/lib/coltrane/unordered_interval_class.rb +0 -7
- data/lib/coltrane/voicing.rb +0 -39
- data/lib/coltrane_game/question.rb +0 -7
- data/lib/coltrane_instruments.rb +0 -7
- data/lib/coltrane_instruments/guitar.rb +0 -10
- data/lib/coltrane_instruments/guitar/base.rb +0 -29
- data/lib/coltrane_instruments/guitar/chord.rb +0 -170
- data/lib/coltrane_instruments/guitar/note.rb +0 -24
- data/lib/coltrane_instruments/guitar/string.rb +0 -29
- data/lib/os.rb +0 -21
data/lib/coltrane/errors.rb
DELETED
@@ -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
|
data/lib/coltrane/frequency.rb
DELETED
@@ -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
|
data/lib/coltrane/interval.rb
DELETED
@@ -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
|