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