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
File without changes
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
# It describe a chord
|
6
|
+
|
7
|
+
class Chord
|
8
|
+
attr_reader :root_note, :quality, :notes
|
9
|
+
include ChordSubstitutions
|
10
|
+
|
11
|
+
def initialize(notes: nil, root_note: nil, quality: nil, name: nil)
|
12
|
+
if notes
|
13
|
+
notes = NoteSet[*notes] if notes.is_a?(Array)
|
14
|
+
@notes = notes
|
15
|
+
@root_note = notes.first
|
16
|
+
@quality = ChordQuality.new(notes: notes)
|
17
|
+
elsif root_note && quality
|
18
|
+
@notes = quality.notes_for(root_note)
|
19
|
+
@root_note = root_note
|
20
|
+
@quality = quality
|
21
|
+
elsif name
|
22
|
+
@root_note, @quality, @notes = parse_from_name(name)
|
23
|
+
else
|
24
|
+
raise WrongKeywordsError,
|
25
|
+
'[notes:] || [root_note:, quality:] || [name:]'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def ==(other)
|
30
|
+
(notes & other.notes).size == notes.size
|
31
|
+
end
|
32
|
+
|
33
|
+
alias eql? ==
|
34
|
+
|
35
|
+
def name
|
36
|
+
"#{root_note}#{quality}"
|
37
|
+
end
|
38
|
+
|
39
|
+
alias to_s name
|
40
|
+
|
41
|
+
def hash
|
42
|
+
notes.hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def pretty_name
|
46
|
+
"#{root_note.pretty_name}#{quality.name}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def intervals
|
50
|
+
IntervalSequence.new(NoteSet.new(notes))
|
51
|
+
end
|
52
|
+
|
53
|
+
def size
|
54
|
+
notes.size
|
55
|
+
end
|
56
|
+
|
57
|
+
def scales
|
58
|
+
Scale.having_chord(name)
|
59
|
+
end
|
60
|
+
|
61
|
+
def next_inversion
|
62
|
+
Chord.new(notes: notes.rotate(1))
|
63
|
+
end
|
64
|
+
|
65
|
+
def invert(n = 1)
|
66
|
+
Chord.new(notes: notes.rotate(n))
|
67
|
+
end
|
68
|
+
|
69
|
+
def previous_inversion
|
70
|
+
Chord.new(notes: notes.rotate(-1))
|
71
|
+
end
|
72
|
+
|
73
|
+
def +(other)
|
74
|
+
case other
|
75
|
+
when Note, NoteSet, Interval then Chord.new(notes: notes + other)
|
76
|
+
when Chord then Chord.new(notes: notes + other.notes)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def -(other)
|
81
|
+
case other
|
82
|
+
when Note, NoteSet, Interval, Numeric then Chord.new(notes: notes - other)
|
83
|
+
when Chord then Chord.new(notes: notes - other.notes)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
|
89
|
+
def parse_from_name(name)
|
90
|
+
chord_name, bass = name.match?(/\/9/) ? [name, nil] : name.split('/')
|
91
|
+
chord_regex = /([A-Z](?:#|b)?)(.*)/
|
92
|
+
_, root_name, quality_name = chord_name.match(chord_regex).to_a
|
93
|
+
root = Note[root_name]
|
94
|
+
quality = ChordQuality.new(name: quality_name, bass: bass)
|
95
|
+
notes = quality.notes_for(root)
|
96
|
+
notes << Note[bass] unless bass.nil?
|
97
|
+
[root, quality, notes]
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
# It describe the quality of a chord, like maj7 or dim.
|
6
|
+
class ChordQuality < IntervalSequence
|
7
|
+
include Qualities
|
8
|
+
attr_reader :name
|
9
|
+
# QUALITIES_FILE = File.expand_path("#{'../' * 3}data/qualities.json", __FILE__)
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def self.chord_trie
|
14
|
+
File.expand_path("#{'../' * 3}data/qualities.json", __FILE__)
|
15
|
+
trie = QUALITIES
|
16
|
+
|
17
|
+
trie.clone_values from_keys: ['Perfect Unison', 'Major Third'],
|
18
|
+
to_keys: ['Perfect Unison', 'Major Second'],
|
19
|
+
suffix: 'sus2'
|
20
|
+
|
21
|
+
trie.clone_values from_keys: ['Perfect Unison', 'Major Third'],
|
22
|
+
to_keys: ['Perfect Unison', 'Perfect Fourth'],
|
23
|
+
suffix: 'sus4'
|
24
|
+
trie.deep_dup
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.intervals_per_name(quality_names: {}, intervals: [], hash: nil)
|
28
|
+
hash ||= chord_trie
|
29
|
+
return quality_names if hash.empty?
|
30
|
+
if hash['name']
|
31
|
+
quality_names[hash.delete('name')] = intervals.map { |n| Interval.public_send(n.underscore) }
|
32
|
+
end
|
33
|
+
hash.reduce(quality_names) do |memo, (interval, values)|
|
34
|
+
memo.merge intervals_per_name(hash: values,
|
35
|
+
quality_names: quality_names,
|
36
|
+
intervals: intervals + [interval])
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
NAMES = intervals_per_name
|
41
|
+
|
42
|
+
def find_chord(given_interval_names, trie: self.class.chord_trie, last_name: nil)
|
43
|
+
return if trie.nil?
|
44
|
+
if given_interval_names.empty?
|
45
|
+
@found = true
|
46
|
+
return trie['name']
|
47
|
+
end
|
48
|
+
interval = given_interval_names.shift
|
49
|
+
new_trie = trie[interval.full_name]
|
50
|
+
find_chord given_interval_names, last_name: (trie['name'] || last_name),
|
51
|
+
trie: new_trie
|
52
|
+
end
|
53
|
+
|
54
|
+
def normal_sequence
|
55
|
+
%i[unison third! fifth sixth! seventh ninth eleventh! thirteenth]
|
56
|
+
end
|
57
|
+
|
58
|
+
def sus2_sequence
|
59
|
+
%i[unison second! fifth sixth! seventh ninth eleventh! thirteenth]
|
60
|
+
end
|
61
|
+
|
62
|
+
def sus4_sequence
|
63
|
+
%i[unison fourth! fifth sixth! seventh ninth eleventh! thirteenth]
|
64
|
+
end
|
65
|
+
|
66
|
+
def retrieve_chord_intervals(chord_sequence = normal_sequence)
|
67
|
+
ints = IntervalSequence.new(*self)
|
68
|
+
chord_sequence.map do |int_sym|
|
69
|
+
next unless interval_name = ints.public_send(int_sym)
|
70
|
+
ints.delete_if { |i| i.cents == IntervalClass.new(interval_name).cents }
|
71
|
+
interval_name
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
public
|
76
|
+
|
77
|
+
def get_name
|
78
|
+
find_chord(retrieve_chord_intervals.compact) ||
|
79
|
+
find_chord(retrieve_chord_intervals(sus2_sequence).compact) ||
|
80
|
+
find_chord(retrieve_chord_intervals(sus4_sequence).compact) ||
|
81
|
+
raise(ChordNotFoundError)
|
82
|
+
end
|
83
|
+
|
84
|
+
def suspension_type
|
85
|
+
if has_major_second?
|
86
|
+
'sus2'
|
87
|
+
else has_fourth?
|
88
|
+
'sus4'
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def initialize(name: nil, notes: nil, bass: nil)
|
93
|
+
if name
|
94
|
+
@name = bass.nil? ? name : [name, bass].join('/')
|
95
|
+
super(*intervals_from_name(name))
|
96
|
+
elsif notes
|
97
|
+
super(notes: notes)
|
98
|
+
@name = get_name
|
99
|
+
else
|
100
|
+
raise WrongKeywordsError, '[name:] || [notes:]'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
alias to_s name
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def intervals_from_name(name)
|
109
|
+
NAMES[name] || NAMES["M#{name}"] || raise(ChordNotFoundError)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
class CircleOfFifths
|
6
|
+
attr_reader :notes
|
7
|
+
|
8
|
+
LETTER_SEQUENCE = %w[C G D A E B F].freeze
|
9
|
+
|
10
|
+
def initialize(start_note = Note['C'], size = Float::INFINITY)
|
11
|
+
index = letters.index(start_note.name[0])
|
12
|
+
@notes = fifths(note: start_note - Interval.perfect_fifth,
|
13
|
+
size: size,
|
14
|
+
index: (index - 1) % LETTER_SEQUENCE.size)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def fifths(note:, index: nil, notes: [], size:)
|
20
|
+
return notes if size == 0
|
21
|
+
return notes if notes.any? && note.as(letters(index)).name == notes.first.name
|
22
|
+
fifths note: note + Interval.perfect_fifth,
|
23
|
+
notes: notes + [note.as(letters(index))],
|
24
|
+
index: index + 1,
|
25
|
+
size: size - 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def letters(i = nil)
|
29
|
+
i.nil? ? LETTER_SEQUENCE : LETTER_SEQUENCE[i % LETTER_SEQUENCE.size]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
# This module deals with well known scales on music
|
6
|
+
module ClassicScales
|
7
|
+
SCALES = {
|
8
|
+
'Pentatonic Major' => [2, 2, 3, 2, 3],
|
9
|
+
'Blues Major' => [2, 1, 1, 3, 2, 3],
|
10
|
+
'Harmonic Minor' => [2, 1, 2, 2, 1, 3, 1],
|
11
|
+
'Hungarian Minor' => [2, 1, 2, 1, 1, 3, 1],
|
12
|
+
'Pentatonic Minor' => [3, 2, 2, 3, 2],
|
13
|
+
'Blues Minor' => [3, 2, 1, 1, 3, 2],
|
14
|
+
'Whole Tone' => [2, 2, 2, 2, 2, 2],
|
15
|
+
'Flamenco' => [1, 3, 1, 2, 1, 2, 2],
|
16
|
+
'Chromatic' => [1] * 12
|
17
|
+
}.freeze
|
18
|
+
|
19
|
+
GREEK_MODES = %w[
|
20
|
+
Ionian
|
21
|
+
Dorian
|
22
|
+
Phrygian
|
23
|
+
Lydian
|
24
|
+
Mixolydian
|
25
|
+
Aeolian
|
26
|
+
Locrian
|
27
|
+
].freeze
|
28
|
+
|
29
|
+
# Creates factories for scales
|
30
|
+
SCALES.each do |name, distances|
|
31
|
+
define_method name.underscore do |tone = 'C', mode = 1|
|
32
|
+
new(*distances, tone: tone, mode: mode, name: name)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Creates factories for Greek Modes
|
37
|
+
GREEK_MODES.each_with_index do |mode, index|
|
38
|
+
mode_name = mode
|
39
|
+
mode_n = index + 1
|
40
|
+
define_method mode.underscore do |tone = 'C'|
|
41
|
+
new(notes: DiatonicScale.new(tone).notes.rotate(index))
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Factories for the diatonic scale
|
46
|
+
def major(note = 'C')
|
47
|
+
DiatonicScale.new(note)
|
48
|
+
end
|
49
|
+
|
50
|
+
def minor(note = 'A')
|
51
|
+
DiatonicScale.new(note, major: false)
|
52
|
+
end
|
53
|
+
|
54
|
+
alias diatonic major
|
55
|
+
alias natural_minor minor
|
56
|
+
alias pentatonic pentatonic_major
|
57
|
+
alias blues blues_major
|
58
|
+
|
59
|
+
def known_scales
|
60
|
+
SCALES.keys + ['Major', 'Natural Minor']
|
61
|
+
end
|
62
|
+
|
63
|
+
# List of scales appropriate for search
|
64
|
+
def standard_scales
|
65
|
+
known_scales - ['Chromatic']
|
66
|
+
end
|
67
|
+
|
68
|
+
def fetch(name, tone = nil)
|
69
|
+
public_send(name.underscore, tone)
|
70
|
+
end
|
71
|
+
|
72
|
+
# def having_notes(notes)
|
73
|
+
# format = { scales: [], results: {} }
|
74
|
+
# standard_scales.each_with_object(format) do |name, output|
|
75
|
+
# PitchClass.all.each.map do |tone|
|
76
|
+
# scale = send(name.underscore, tone)
|
77
|
+
# output[:results][name] ||= {}
|
78
|
+
# next if output[:results][name].key?(tone.integer)
|
79
|
+
# output[:scales] << scale if scale.include?(notes)
|
80
|
+
# output[:results][name][tone.integer] = scale.notes & notes
|
81
|
+
# end
|
82
|
+
# end
|
83
|
+
# end
|
84
|
+
|
85
|
+
def having_notes(notes)
|
86
|
+
PitchClass.all
|
87
|
+
.reduce([]) { |scales, tone|
|
88
|
+
standard_scales
|
89
|
+
.reduce([]) { |tone_scales, scale|
|
90
|
+
fetch(scale, tone)
|
91
|
+
.yield_self { |scale|
|
92
|
+
(scale & notes).size > 0 ? tone_scales + [scale] : tone_scales
|
93
|
+
}
|
94
|
+
}.yield_self { |scales_for_tone|
|
95
|
+
scales + scales_for_tone
|
96
|
+
}
|
97
|
+
}
|
98
|
+
.yield_self { |scales| ScaleSet.new(*scales, searched_notes: notes) } # and convert to a set
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
def having_chords(*chords)
|
103
|
+
should_create = !chords.first.is_a?(Chord)
|
104
|
+
notes = chords.reduce(NoteSet[]) do |memo, c|
|
105
|
+
memo + (should_create ? Chord.new(name: c) : c).notes
|
106
|
+
end
|
107
|
+
having_notes(notes)
|
108
|
+
end
|
109
|
+
|
110
|
+
alias having_chord having_chords
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
class DiatonicScale < Scale
|
6
|
+
def initialize(tone, major: true)
|
7
|
+
@major = major
|
8
|
+
tone = Note[tone]
|
9
|
+
notes = CircleOfFifths.new(tone - (major? ? 0 : 9), 7).notes.sort
|
10
|
+
super notes: notes.rotate(notes.index(tone))
|
11
|
+
end
|
12
|
+
|
13
|
+
def name
|
14
|
+
major? ? 'Major' : 'Natural Minor'
|
15
|
+
end
|
16
|
+
|
17
|
+
def relative_minor
|
18
|
+
minor? ? self : self.class.new(@tone + 9, major: false)
|
19
|
+
end
|
20
|
+
|
21
|
+
def relative_major
|
22
|
+
major? ? self : self.class.new(@tone - 9, major: true)
|
23
|
+
end
|
24
|
+
|
25
|
+
def relative
|
26
|
+
major? ? relative_minor : relative_major
|
27
|
+
end
|
28
|
+
|
29
|
+
def major?
|
30
|
+
!!@major
|
31
|
+
end
|
32
|
+
|
33
|
+
def minor?
|
34
|
+
!@major
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# rubocop:disable Style/Documentation
|
4
|
+
|
5
|
+
module Coltrane
|
6
|
+
module Theory
|
7
|
+
class TheoryError < StandardError
|
8
|
+
def initialize(msg)
|
9
|
+
super msg
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class BadConstructorError < TheoryError
|
14
|
+
def initialize(msg = nil)
|
15
|
+
super "Bad constructor. #{msg}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class WrongKeywordsError < BadConstructorError
|
20
|
+
def initialize(msg)
|
21
|
+
super "Use one of the following set of keywords: #{msg}"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
class WrongArgumentsError < BadConstructorError
|
26
|
+
def initialize(_msg)
|
27
|
+
super 'Wrong argument(s).'
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class InvalidNoteError < BadConstructorError
|
32
|
+
def initialize(note)
|
33
|
+
super "#{note} is not a valid note"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class InvalidNotesError < BadConstructorError
|
38
|
+
def initialize(notes)
|
39
|
+
super "#{notes} are not a valid set of notes"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class HasNoNotesError < BadConstructorError
|
44
|
+
def initialize
|
45
|
+
super 'The given object does not respond to :notes, '\
|
46
|
+
"thereby it can't be used for this operation)"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
class WrongDegreeError
|
51
|
+
def initialize(degree)
|
52
|
+
super "#{degree} is not a valid degree. Degrees for this scale must be"\
|
53
|
+
"between 1 and #{degrees}"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
class ChordNotFoundError < TheoryError
|
58
|
+
def initialize
|
59
|
+
super "The chord you provided wasn't found. "\
|
60
|
+
"If you're sure this chord exists, "\
|
61
|
+
"would you mind to suggest it's inclusion here: "\
|
62
|
+
'https://github.com/pedrozath/coltrane/issues '\
|
63
|
+
"\n\nA tip tho: always include the letter M for major"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
class IntervalNotFoundError < TheoryError
|
68
|
+
def initialize(arg)
|
69
|
+
super "The interval \"#{arg}\" that was provided wasn't found. "\
|
70
|
+
"If you're sure this interval exists, "\
|
71
|
+
"would you mind to suggest it's inclusion here: "\
|
72
|
+
'https://github.com/pedrozath/coltrane/issues '\
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class InvalidPitchClassError < TheoryError
|
77
|
+
def initialize(arg)
|
78
|
+
super "The given frequency(#{arg}) is not considered "\
|
79
|
+
'part of a pitch class'\
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
class InvalidNoteSymbolError < TheoryError
|
84
|
+
def initialize(arg)
|
85
|
+
super "The musical notation included an unrecognizable symbol (#{arg})."
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class InvalidNoteLetterError < TheoryError
|
90
|
+
def initialize(arg)
|
91
|
+
super "The musical notation included an unrecognizable letter (#{arg})."
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# rubocop:enable Style/Documentation
|
97
|
+
end
|