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.
- 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
|