coltrane 1.0.26 → 1.1.0
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/CHANGELOG.md +30 -4
- data/Gemfile.lock +1 -1
- data/bin/console +3 -8
- data/data/qualities.yml +83 -0
- data/lib/cli/chord.rb +0 -1
- data/lib/coltrane.rb +1 -1
- data/lib/coltrane/chord.rb +3 -9
- data/lib/coltrane/chord_quality.rb +93 -4
- data/lib/coltrane/classic_scales.rb +6 -1
- data/lib/coltrane/errors.rb +9 -0
- data/lib/coltrane/interval.rb +93 -22
- data/lib/coltrane/interval_sequence.rb +60 -5
- data/lib/coltrane/note.rb +2 -1
- data/lib/coltrane/note_set.rb +5 -1
- data/lib/coltrane/progression.rb +46 -4
- data/lib/coltrane/roman_chord.rb +65 -27
- data/lib/coltrane/scale.rb +15 -10
- data/lib/coltrane/version.rb +1 -1
- data/lib/core_ext.rb +87 -0
- metadata +3 -3
- data/lib/coltrane/qualities.rb +0 -118
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9c5035abfa591229d5cb00c1a55f95cbb7904fe4cb6fb02c3da0147ddb28713b
|
4
|
+
data.tar.gz: c9d0b5de4c65af3f8bcb59b202720eb84c8ad16ce8f422b03536c6382db4e606
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 62c750bd90a06738d6e1cbae6cfd15a2c1b45e0b0771f888aaad11a34a48234d28338315a62cf664afb5ba364589e609c77390f3fe1905c721845ad5e1f686d1
|
7
|
+
data.tar.gz: 2dc63fb47d4f39df88daf56b24f0d0e229f274fe2807f57295461c4a26f05659932d5b8599f3285073a2a88cbf2fd4e1c3c6ae49d268900b2f643c7aa6645c13
|
data/CHANGELOG.md
CHANGED
@@ -10,12 +10,38 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
10
10
|
- Refactor notes and add pitch frequencies, pitch classes
|
11
11
|
|
12
12
|
|
13
|
-
## [1.1.
|
13
|
+
## [1.1.0]
|
14
|
+
|
15
|
+
### Changes
|
16
|
+
- The qualities are now more procedural, less hardcoded as possible. The refactor
|
17
|
+
has caused the chord list shorter. That may be an issue for some users, and if
|
18
|
+
so, it should be addressed in next versions. However, I believe that reduces
|
19
|
+
the spam, since we are focusing on the more relevant chords, as they're the
|
20
|
+
basis for building more "exotic" chords.
|
21
|
+
|
22
|
+
- Intervals are now registered and displayed in the english language order.
|
23
|
+
Ex: Minor Third was `3m` and now is `m3`.
|
24
|
+
|
25
|
+
### Adds
|
26
|
+
- A lot of helper methods for obtaining interval information! `Interval` has
|
27
|
+
methods like `#minor_third?`, `#major_second?`, which are pretty self-explanatory.
|
28
|
+
`#full_name` and `#full_names` are also added, the latter returning intervals that
|
29
|
+
are pretty much the same, such as Major Second and Major Ninth.
|
30
|
+
Interval sequences have methods like `#has_minor_third?` and `#third`, which will
|
31
|
+
return the third it has, no matter if its major, or minor.
|
32
|
+
|
33
|
+
- Roman Chords and Chord Progressions are finally here! It's a bit experimental yet,
|
34
|
+
the latter definitely needs more specs.
|
35
|
+
|
36
|
+
### Fixes
|
37
|
+
- The changelog
|
38
|
+
|
39
|
+
## [1.0.25]
|
14
40
|
|
15
41
|
### Fixes
|
16
42
|
- Ruby version on the gemspec
|
17
43
|
|
18
|
-
## [1.
|
44
|
+
## [1.0.25]
|
19
45
|
|
20
46
|
### Removes
|
21
47
|
- Caching, as after some refactoring it made no sense anymore
|
@@ -24,7 +50,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
24
50
|
- Progressions and Roman Chords
|
25
51
|
- Adds Progression command to the CLI
|
26
52
|
|
27
|
-
## [1.
|
53
|
+
## [1.0.24]
|
28
54
|
|
29
55
|
### Adds
|
30
56
|
- Some coloring on guitar output
|
@@ -37,7 +63,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
|
|
37
63
|
- Removes Natural sign ~padding~ for guitar instruments
|
38
64
|
|
39
65
|
|
40
|
-
## [1.
|
66
|
+
## [1.0.23]
|
41
67
|
|
42
68
|
### Adds
|
43
69
|
- A changelog
|
data/Gemfile.lock
CHANGED
data/bin/console
CHANGED
@@ -3,12 +3,7 @@
|
|
3
3
|
require "bundler/setup"
|
4
4
|
require "coltrane"
|
5
5
|
|
6
|
-
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
6
|
+
require "pry"
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require "irb"
|
14
|
-
IRB.start(__FILE__)
|
8
|
+
include Coltrane
|
9
|
+
Pry.start
|
data/data/qualities.yml
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# NOTE: suspended chords are added programatically
|
2
|
+
|
3
|
+
Perfect Unison:
|
4
|
+
Minor Third:
|
5
|
+
Diminished Fifth:
|
6
|
+
name: dim
|
7
|
+
Diminished Seventh:
|
8
|
+
name: dim7
|
9
|
+
Minor Ninth:
|
10
|
+
name: dim9
|
11
|
+
Major Ninth:
|
12
|
+
name: dim(b9)
|
13
|
+
Minor Seventh:
|
14
|
+
name: m7b5
|
15
|
+
Minor Ninth:
|
16
|
+
name: m7b5b9
|
17
|
+
Perfect Eleventh:
|
18
|
+
name: m7b5b11
|
19
|
+
Major Thirteenth:
|
20
|
+
name: m7b5b13
|
21
|
+
Major Ninth:
|
22
|
+
name: m7b5(9)
|
23
|
+
Perfect Fifth:
|
24
|
+
name: m
|
25
|
+
Major Sixth:
|
26
|
+
name: m6
|
27
|
+
Minor Seventh:
|
28
|
+
name: m7
|
29
|
+
Minor Ninth:
|
30
|
+
name: m9
|
31
|
+
Perfect Eleventh:
|
32
|
+
name: m11
|
33
|
+
Major Seventh:
|
34
|
+
name: m(M7)
|
35
|
+
Major Ninth:
|
36
|
+
name: m(M9)
|
37
|
+
Perfect Eleventh:
|
38
|
+
name: m(M11)
|
39
|
+
Major Thirteenth:
|
40
|
+
name: m(M13)
|
41
|
+
Major Third:
|
42
|
+
Perfect Fifth:
|
43
|
+
name: M
|
44
|
+
Major Sixth:
|
45
|
+
name: M6
|
46
|
+
Major Ninth:
|
47
|
+
name: 6/9
|
48
|
+
Perfect Eleventh:
|
49
|
+
name: 6/9(add11)
|
50
|
+
Minor Seventh:
|
51
|
+
name: "7"
|
52
|
+
Major Ninth:
|
53
|
+
name: "9"
|
54
|
+
Perfect Eleventh:
|
55
|
+
name: "11"
|
56
|
+
Major Thirteenth:
|
57
|
+
name: "13"
|
58
|
+
Major Seventh:
|
59
|
+
name: M7
|
60
|
+
Major Ninth:
|
61
|
+
name: M9
|
62
|
+
Perfect Eleventh:
|
63
|
+
name: M11
|
64
|
+
Major Thirteenth:
|
65
|
+
name: M13
|
66
|
+
Augmented Fifth:
|
67
|
+
name: +
|
68
|
+
Minor Seventh:
|
69
|
+
name: "+7"
|
70
|
+
Major Ninth:
|
71
|
+
name: "+9"
|
72
|
+
Perfect Eleventh:
|
73
|
+
name: "+11"
|
74
|
+
Major Thirteenth:
|
75
|
+
name: "+13"
|
76
|
+
Major Seventh:
|
77
|
+
name: +M7
|
78
|
+
Major Ninth:
|
79
|
+
name: +M9
|
80
|
+
Perfect Eleventh:
|
81
|
+
name: +M11
|
82
|
+
Major Thirteenth:
|
83
|
+
name: +M13
|
data/lib/cli/chord.rb
CHANGED
data/lib/coltrane.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
require 'yaml'
|
2
3
|
|
3
4
|
require 'forwardable'
|
4
5
|
require 'facets/multiton'
|
@@ -16,7 +17,6 @@ require 'coltrane/note_set'
|
|
16
17
|
require 'coltrane/interval'
|
17
18
|
require 'coltrane/interval_sequence'
|
18
19
|
|
19
|
-
require 'coltrane/qualities'
|
20
20
|
require 'coltrane/chord_quality'
|
21
21
|
|
22
22
|
require 'coltrane/chord'
|
data/lib/coltrane/chord.rb
CHANGED
@@ -24,21 +24,15 @@ module Coltrane
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def name
|
27
|
-
|
28
|
-
"#{root_note.name}#{quality.name}"
|
27
|
+
"#{root_note}#{quality}"
|
29
28
|
end
|
30
29
|
|
30
|
+
alias to_s name
|
31
|
+
|
31
32
|
def pretty_name
|
32
|
-
return @notes.names.join('/') unless named?
|
33
33
|
"#{root_note.pretty_name}#{quality.name}"
|
34
34
|
end
|
35
35
|
|
36
|
-
def named?
|
37
|
-
notes.size >= 3 &&
|
38
|
-
!root_note.nil? &&
|
39
|
-
!quality&.name.nil?
|
40
|
-
end
|
41
|
-
|
42
36
|
def intervals
|
43
37
|
IntervalSequence.new(NoteSet.new(notes))
|
44
38
|
end
|
@@ -4,19 +4,108 @@ module Coltrane
|
|
4
4
|
# It describe the quality of a chord, like maj7 or dim.
|
5
5
|
class ChordQuality < IntervalSequence
|
6
6
|
attr_reader :name
|
7
|
-
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def self.chord_trie
|
11
|
+
trie = YAML.load_file(
|
12
|
+
File.expand_path("#{'../'*3}data/qualities.yml", __FILE__)
|
13
|
+
)
|
14
|
+
|
15
|
+
trie.clone_values from_keys: ['Perfect Unison', 'Major Third'],
|
16
|
+
to_keys: ['Perfect Unison', 'Major Second'],
|
17
|
+
suffix: 'sus2'
|
18
|
+
|
19
|
+
trie.clone_values from_keys: ['Perfect Unison', 'Major Third'],
|
20
|
+
to_keys: ['Perfect Unison', 'Perfect Fourth'],
|
21
|
+
suffix: 'sus4'
|
22
|
+
trie.deep_dup
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.intervals_per_name(quality_names: {}, intervals: [], hash: nil)
|
26
|
+
hash ||= chord_trie
|
27
|
+
return quality_names if hash.empty?
|
28
|
+
if hash['name']
|
29
|
+
quality_names.merge! hash.delete('name') => intervals.map {|n| Interval[n] }
|
30
|
+
end
|
31
|
+
hash.reduce(quality_names) do |memo, (interval, values)|
|
32
|
+
memo.merge intervals_per_name(hash: values,
|
33
|
+
quality_names: quality_names,
|
34
|
+
intervals: intervals + [interval])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
NAMES = intervals_per_name
|
39
|
+
|
40
|
+
def find_chord(given_interval_names, trie: self.class.chord_trie, last_name: nil)
|
41
|
+
return if trie.nil?
|
42
|
+
if given_interval_names.empty?
|
43
|
+
@found = true
|
44
|
+
return trie['name']
|
45
|
+
end
|
46
|
+
interval = given_interval_names.shift
|
47
|
+
new_trie = trie[interval]
|
48
|
+
find_chord given_interval_names, last_name: (trie['name'] || last_name),
|
49
|
+
trie: new_trie
|
50
|
+
end
|
51
|
+
|
52
|
+
def normal_sequence
|
53
|
+
%i[unison third! fifth sixth! seventh ninth eleventh! thirteenth]
|
54
|
+
end
|
55
|
+
|
56
|
+
def sus2_sequence
|
57
|
+
%i[unison second! fifth sixth! seventh ninth eleventh! thirteenth]
|
58
|
+
end
|
59
|
+
|
60
|
+
def sus4_sequence
|
61
|
+
%i[unison fourth! fifth sixth! seventh ninth eleventh! thirteenth]
|
62
|
+
end
|
63
|
+
|
64
|
+
def retrieve_chord_intervals(chord_sequence = normal_sequence)
|
65
|
+
ints = IntervalSequence.new(intervals: self)
|
66
|
+
chord_sequence.map do |int_sym|
|
67
|
+
next unless interval_name = ints.public_send(int_sym)
|
68
|
+
ints.delete(Interval[interval_name])
|
69
|
+
interval_name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
public
|
74
|
+
|
75
|
+
def get_name
|
76
|
+
if result = find_chord([*retrieve_chord_intervals].compact)
|
77
|
+
return result
|
78
|
+
elsif result = find_chord([*retrieve_chord_intervals(sus2_sequence)].compact)
|
79
|
+
return result
|
80
|
+
elsif result = find_chord([*retrieve_chord_intervals(sus4_sequence)].compact)
|
81
|
+
return result
|
82
|
+
else
|
83
|
+
binding.pry
|
84
|
+
raise ChordNotFoundError
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
|
89
|
+
def suspension_type
|
90
|
+
if has_major_second?
|
91
|
+
'sus2'
|
92
|
+
else has_fourth?
|
93
|
+
'sus4'
|
94
|
+
end
|
95
|
+
end
|
8
96
|
|
9
97
|
def initialize(name: nil, notes: nil)
|
10
98
|
if !name.nil?
|
11
|
-
raise ChordNotFoundError unless (intervals = CHORD_QUALITIES[name])
|
12
99
|
@name = name
|
13
|
-
super(intervals:
|
100
|
+
super(intervals: NAMES[name])
|
14
101
|
elsif !notes.nil?
|
15
102
|
super(notes: notes)
|
16
|
-
@name =
|
103
|
+
@name = get_name
|
17
104
|
else
|
18
105
|
raise WrongKeywordsError, '[name:] || [notes:]'
|
19
106
|
end
|
20
107
|
end
|
108
|
+
|
109
|
+
alias to_s name
|
21
110
|
end
|
22
111
|
end
|
@@ -47,6 +47,11 @@ module Coltrane
|
|
47
47
|
SCALES.keys
|
48
48
|
end
|
49
49
|
|
50
|
+
# All but the chromatic
|
51
|
+
def standard_scales
|
52
|
+
SCALES.reject { |k,v| k == 'Chromatic' }
|
53
|
+
end
|
54
|
+
|
50
55
|
def fetch(name, tone = nil)
|
51
56
|
Coltrane::Scale.public_send(name, tone)
|
52
57
|
end
|
@@ -71,7 +76,7 @@ module Coltrane
|
|
71
76
|
def having_notes(notes)
|
72
77
|
format = { scales: [], results: {} }
|
73
78
|
OpenStruct.new(
|
74
|
-
|
79
|
+
standard_scales.each_with_object(format) do |(name, intervals), output|
|
75
80
|
Note.all.each.map do |tone|
|
76
81
|
scale = new(*intervals, tone: tone, name: scale)
|
77
82
|
output[:results][name] ||= {}
|
data/lib/coltrane/errors.rb
CHANGED
@@ -56,6 +56,15 @@ module Coltrane
|
|
56
56
|
"\n\nA tip tho: always include the letter M for major"
|
57
57
|
end
|
58
58
|
end
|
59
|
+
|
60
|
+
class IntervalNotFoundError < ColtraneError
|
61
|
+
def initialize(arg)
|
62
|
+
super "The interval \"#{arg}\" that was provided wasn't found. "\
|
63
|
+
"If you're sure this interval exists, "\
|
64
|
+
"would you mind to suggest it's inclusion here: "\
|
65
|
+
'https://github.com/pedrozath/coltrane/issues '\
|
66
|
+
end
|
67
|
+
end
|
59
68
|
end
|
60
69
|
|
61
70
|
# rubocop:enable Style/Documentation
|
data/lib/coltrane/interval.rb
CHANGED
@@ -3,40 +3,111 @@
|
|
3
3
|
module Coltrane
|
4
4
|
# It describes a interval between 2 pitches
|
5
5
|
class Interval
|
6
|
+
include Multiton
|
6
7
|
attr_reader :semitones
|
7
8
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
9
|
+
INTERVALS = %w[
|
10
|
+
P1
|
11
|
+
m2
|
12
|
+
M2
|
13
|
+
m3
|
14
|
+
M3
|
15
|
+
P4
|
16
|
+
A4
|
17
|
+
P5
|
18
|
+
m6
|
19
|
+
M6
|
20
|
+
m7
|
21
|
+
M7
|
21
22
|
].freeze
|
22
23
|
|
23
|
-
def
|
24
|
-
|
25
|
-
when Interval then arg.semitones
|
26
|
-
when String then NAMES.index(arg)
|
27
|
-
when Numeric then arg
|
28
|
-
end) % 12
|
24
|
+
def self.split(interval)
|
25
|
+
interval.scan(/(\w)(\d\d?)/)[0]
|
29
26
|
end
|
30
27
|
|
28
|
+
def self.full_name(interval)
|
29
|
+
q,n = split(interval)
|
30
|
+
"#{q.interval_quality} #{n.to_i.interval_name}"
|
31
|
+
end
|
32
|
+
|
33
|
+
# Create full names and methods such as major_third? minor_seventh?
|
34
|
+
# TODO: It's a mess and it really needs a refactor one day
|
35
|
+
NAMES = INTERVALS.each_with_index.reduce({}) do |memo, (interval, index)|
|
36
|
+
memo[interval] ||= []
|
37
|
+
2.times do |o|
|
38
|
+
q,i = split(interval)
|
39
|
+
num = o * 7 + i.to_i
|
40
|
+
prev_q = split(INTERVALS[(index - 1) % 12])[0]
|
41
|
+
next_q = split(INTERVALS[(index + 1) % 12])[0]
|
42
|
+
memo[interval] << full_name("#{q}#{num}")
|
43
|
+
memo[interval] << full_name("d#{(num - 1 + 1) % 14 + 1}") if next_q.match? /m|P/
|
44
|
+
next if q == 'A'
|
45
|
+
memo[interval] << full_name("A#{(num - 1 - 1) % 14 + 1}") if prev_q.match? /M|P/
|
46
|
+
end
|
47
|
+
memo
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.[](arg)
|
51
|
+
new(case arg
|
52
|
+
when Interval then arg.semitones
|
53
|
+
when String then INTERVALS.index(arg) || interval_by_full_name(arg)
|
54
|
+
when Numeric then arg
|
55
|
+
end % 12)
|
56
|
+
end
|
57
|
+
|
58
|
+
ALL_FULL_NAMES = NAMES.values.flatten
|
59
|
+
|
60
|
+
NAMES.each do |interval_name, full_names|
|
61
|
+
full_names.each do |the_full_name|
|
62
|
+
define_method "#{the_full_name.underscore}?" do
|
63
|
+
name == interval_name
|
64
|
+
end
|
65
|
+
self.class.define_method "#{the_full_name.underscore}" do
|
66
|
+
self[interval_name]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def initialize(semitones)
|
72
|
+
@semitones = semitones
|
73
|
+
end
|
74
|
+
|
75
|
+
private_class_method :new
|
76
|
+
|
77
|
+
def all_full_names
|
78
|
+
ALL_FULL_NAMES
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
|
31
83
|
def name
|
32
|
-
|
84
|
+
INTERVALS[semitones]
|
85
|
+
end
|
86
|
+
|
87
|
+
def full_name
|
88
|
+
self.class.full_name(name)
|
89
|
+
end
|
90
|
+
|
91
|
+
def full_names
|
92
|
+
NAMES[name]
|
33
93
|
end
|
34
94
|
|
35
95
|
def +(other)
|
36
96
|
case other
|
37
|
-
when Numeric then Interval
|
38
|
-
when Interval then Interval
|
97
|
+
when Numeric then Interval[semitones + other]
|
98
|
+
when Interval then Interval[semitones + other.semitones]
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def self.interval_by_full_name(arg)
|
105
|
+
NAMES.invert.each do |full_names, interval_name|
|
106
|
+
if full_names.include?(arg)
|
107
|
+
return INTERVALS.index(interval_name)
|
108
|
+
end
|
39
109
|
end
|
110
|
+
raise IntervalNotFoundError, arg
|
40
111
|
end
|
41
112
|
end
|
42
113
|
end
|
@@ -6,14 +6,50 @@ module Coltrane
|
|
6
6
|
extend Forwardable
|
7
7
|
attr_reader :intervals
|
8
8
|
|
9
|
-
def_delegators :@intervals, :map, :each, :[], :size,
|
9
|
+
def_delegators :@intervals, :map, :each, :[], :size,
|
10
|
+
:reduce, :delete
|
11
|
+
|
12
|
+
Interval::ALL_FULL_NAMES.each do |full_name|
|
13
|
+
define_method "has_#{full_name.underscore}?" do
|
14
|
+
!!(intervals.detect {|i| i.public_send("#{full_name.underscore}?")})
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
(1..15).each do |i|
|
19
|
+
# defines methods like :fifth, :third, eleventh:
|
20
|
+
define_method i.interval_name.underscore do
|
21
|
+
priority = send("#{i.interval_name.underscore}!")
|
22
|
+
return priority unless priority.nil?
|
23
|
+
@intervals.each do |ix|
|
24
|
+
ix.full_names.detect do |ixx|
|
25
|
+
return ixx if ixx.match(/#{i.interval_name}/)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
|
31
|
+
define_method "#{i.interval_name.underscore}!" do
|
32
|
+
@intervals.each do |ix|
|
33
|
+
ix.full_names.detect do |ixx|
|
34
|
+
next if ixx.match(/Diminished|Augmented/)
|
35
|
+
return ixx if ixx.match? /#{i.interval_name}/
|
36
|
+
end
|
37
|
+
end
|
38
|
+
nil
|
39
|
+
end
|
40
|
+
|
41
|
+
# defines methods like :has_fifth?, :has_third?, has_eleventh?:
|
42
|
+
define_method "has_#{i.interval_name.underscore}?" do
|
43
|
+
!!@intervals.detect {|ix| ix.full_name.match(/#{i.interval_name}/) }
|
44
|
+
end
|
45
|
+
end
|
10
46
|
|
11
47
|
def initialize(notes: nil, intervals: nil, distances: nil)
|
12
48
|
if !notes.nil?
|
13
49
|
notes = NoteSet[*notes] if notes.is_a?(Array)
|
14
50
|
@intervals = intervals_from_notes(notes)
|
15
51
|
elsif !intervals.nil?
|
16
|
-
@intervals = intervals.map { |i| Interval
|
52
|
+
@intervals = intervals.map { |i| Interval[i] }
|
17
53
|
elsif !distances.nil?
|
18
54
|
@distances = distances
|
19
55
|
@intervals = intervals_from_distances(distances)
|
@@ -32,6 +68,16 @@ module Coltrane
|
|
32
68
|
end + [12 - intervals_semitones.last]
|
33
69
|
end
|
34
70
|
|
71
|
+
def names
|
72
|
+
intervals.map(&:name)
|
73
|
+
end
|
74
|
+
|
75
|
+
def has?(interval_name)
|
76
|
+
@intervals.include?(Interval[interval_name])
|
77
|
+
end
|
78
|
+
|
79
|
+
alias interval_names names
|
80
|
+
|
35
81
|
def all
|
36
82
|
intervals
|
37
83
|
end
|
@@ -68,8 +114,6 @@ module Coltrane
|
|
68
114
|
end
|
69
115
|
end
|
70
116
|
|
71
|
-
def quality; end
|
72
|
-
|
73
117
|
def intervals_semitones
|
74
118
|
map(&:semitones)
|
75
119
|
end
|
@@ -78,6 +122,10 @@ module Coltrane
|
|
78
122
|
map(&:name)
|
79
123
|
end
|
80
124
|
|
125
|
+
def full_names
|
126
|
+
map(&:full_name)
|
127
|
+
end
|
128
|
+
|
81
129
|
def notes_for(root_note)
|
82
130
|
NoteSet[
|
83
131
|
*intervals.reduce([]) do |memo, interval|
|
@@ -86,10 +134,17 @@ module Coltrane
|
|
86
134
|
]
|
87
135
|
end
|
88
136
|
|
137
|
+
def &(other)
|
138
|
+
case other
|
139
|
+
when Array then intervals & other
|
140
|
+
when IntervalSequence then intervals & other.semitones
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
89
144
|
private
|
90
145
|
|
91
146
|
def intervals_from_distances(distances)
|
92
|
-
distances[0..-2].reduce([Interval
|
147
|
+
distances[0..-2].reduce([Interval[0]]) do |memo, d|
|
93
148
|
memo + [memo.last + d]
|
94
149
|
end
|
95
150
|
end
|
data/lib/coltrane/note.rb
CHANGED
@@ -7,6 +7,7 @@ module Coltrane
|
|
7
7
|
|
8
8
|
attr_reader :name, :number
|
9
9
|
alias id number
|
10
|
+
alias to_s name
|
10
11
|
|
11
12
|
NOTES = {
|
12
13
|
'C' => 0,
|
@@ -78,7 +79,7 @@ module Coltrane
|
|
78
79
|
def -(other)
|
79
80
|
case other
|
80
81
|
when Numeric then Note[number - other]
|
81
|
-
when Note then Interval
|
82
|
+
when Note then Interval[other.number - number]
|
82
83
|
end
|
83
84
|
end
|
84
85
|
|
data/lib/coltrane/note_set.rb
CHANGED
@@ -21,7 +21,7 @@ module Coltrane
|
|
21
21
|
@notes =
|
22
22
|
case arg
|
23
23
|
when NoteSet then arg.notes
|
24
|
-
when Array then arg.map { |n| n.is_a?(Note) ? n : Note[n] }
|
24
|
+
when Array then arg.map { |n| n.is_a?(Note) ? n : Note[n] }.uniq
|
25
25
|
else raise InvalidNotesError, arg
|
26
26
|
end
|
27
27
|
end
|
@@ -46,6 +46,10 @@ module Coltrane
|
|
46
46
|
map(&:name)
|
47
47
|
end
|
48
48
|
|
49
|
+
def numbers
|
50
|
+
map(&:number)
|
51
|
+
end
|
52
|
+
|
49
53
|
def transpose_to(note_name)
|
50
54
|
transpose_by(root_note.interval_to(note_name).number)
|
51
55
|
end
|
data/lib/coltrane/progression.rb
CHANGED
@@ -6,12 +6,54 @@ module Coltrane
|
|
6
6
|
# Ex: Progression.new('I-IV-V', key: 'Am')
|
7
7
|
class Progression
|
8
8
|
extend ClassicProgressions
|
9
|
-
attr_reader :scale, :chords
|
9
|
+
attr_reader :scale, :chords, :notation
|
10
|
+
|
11
|
+
def self.find(*chords)
|
12
|
+
# chords.map! { |c| Chord.new(name: c) } if chords[0].is_a?(String)
|
13
|
+
# scales = Scale.having_chords(*chords).scales.map(&:pretty_name)
|
14
|
+
# scales.reduce([]) do |memo, scale|
|
15
|
+
# memo
|
16
|
+
# end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(notation=nil, chords: nil, roman_chords: nil, key: nil, scale: nil)
|
20
|
+
if notation.nil? && chords.nil? && roman_chords.nil? || key.nil? && scale.nil?
|
21
|
+
raise WrongKeywordsError,
|
22
|
+
'[chords:, [scale: || key:]] '\
|
23
|
+
'[roman_chords:, [scale: || key:]] '\
|
24
|
+
'[notation:, [scale: || key:]] '\
|
25
|
+
end
|
10
26
|
|
11
|
-
def initialize(roman_notation, roman_chords: [], key: nil, scale: nil)
|
12
27
|
@scale = scale || Scale.from_key(key)
|
13
|
-
|
14
|
-
|
28
|
+
@chords =
|
29
|
+
if !chords.nil?
|
30
|
+
chords
|
31
|
+
elsif !roman_chords.nil?
|
32
|
+
roman_chords.map(&:chord)
|
33
|
+
elsif !notation.nil?
|
34
|
+
@notation = notation
|
35
|
+
notation.split('-').map {|c| RomanChord.new(c, scale: @scale).chord }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def interval_sequence
|
40
|
+
@interval_sequence ||= IntervalSequence(notes: @root_notes)
|
41
|
+
end
|
42
|
+
|
43
|
+
def root_notes
|
44
|
+
@root_notes ||= @chords.map(&:root_note)
|
45
|
+
end
|
46
|
+
|
47
|
+
def roman_chords
|
48
|
+
@roman_chords ||= RomanChord.new()
|
49
|
+
end
|
50
|
+
|
51
|
+
def notation
|
52
|
+
@notation || @scale
|
53
|
+
end
|
54
|
+
|
55
|
+
def rotate
|
56
|
+
|
15
57
|
end
|
16
58
|
end
|
17
59
|
end
|
data/lib/coltrane/roman_chord.rb
CHANGED
@@ -1,8 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Coltrane
|
4
|
-
attr_reader :degree, :quality
|
5
|
-
|
6
4
|
# This class deals with chords in roman notation. Ex: IVº.
|
7
5
|
class RomanChord
|
8
6
|
DIGITS = %w[I II III IV V VI VII].freeze
|
@@ -17,52 +15,92 @@ module Coltrane
|
|
17
15
|
%w[ø m7b5]
|
18
16
|
]
|
19
17
|
|
20
|
-
def initialize(notation, key: nil, scale: nil)
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
18
|
+
def initialize(notation=nil, chord: nil, key: nil, scale: nil)
|
19
|
+
if notation.nil? && chord.nil? || key.nil? && scale.nil?
|
20
|
+
raise WrongKeywordsError,
|
21
|
+
'[notation, [scale: || key:]] '\
|
22
|
+
'[chord:, [scale: || key:]] '\
|
23
|
+
end
|
24
|
+
@scale = scale || Scale.from_key(key)
|
25
|
+
if !notation.nil?
|
26
|
+
@notation = notation
|
27
|
+
elsif !chord.nil?
|
28
|
+
@chord = chord.is_a?(String) ? Chord.new(name: chord) : chord
|
29
|
+
end
|
30
30
|
end
|
31
31
|
|
32
32
|
def degree
|
33
|
-
|
33
|
+
return @scale.degree_of_note(root_note) unless @chord.nil?
|
34
|
+
d = regexed_notation['degree']
|
34
35
|
@flats = d.count('b')
|
35
36
|
d = d.delete('b')
|
36
37
|
@degree ||= DIGITS.index(d.upcase) + 1
|
37
38
|
end
|
38
39
|
|
39
|
-
def
|
40
|
-
[
|
41
|
-
|
42
|
-
@notation['quality']
|
43
|
-
].join
|
44
|
-
end
|
45
|
-
|
46
|
-
def minor_notation
|
47
|
-
return 'm' if !@notation['quality'].match?((/dim|m7b5/)) && !upcase?
|
40
|
+
def roman_numeral
|
41
|
+
r = DIGITS[degree]
|
42
|
+
minor? ? r.downcase : r
|
48
43
|
end
|
49
44
|
|
50
45
|
def upcase?
|
51
|
-
!!(
|
46
|
+
!!(regexed_notation['degree'][0].match /[[:upper:]]/)
|
52
47
|
end
|
53
48
|
|
54
49
|
def chord
|
55
|
-
Chord.new root_note: root_note,
|
56
|
-
|
50
|
+
@chord ||= Chord.new root_note: root_note,
|
51
|
+
quality: quality
|
52
|
+
end
|
53
|
+
|
54
|
+
def minor?
|
55
|
+
quality.has_minor_third?
|
56
|
+
end
|
57
|
+
|
58
|
+
def major?
|
59
|
+
quality.has_major_third?
|
60
|
+
end
|
61
|
+
|
62
|
+
def quality_name
|
63
|
+
return @chord.quality.name unless @chord.nil?
|
64
|
+
q = normalize_quality_name(regexed_notation['quality'])
|
65
|
+
minor = 'm' if !q.match?((/dim|m7b5/)) && !upcase?
|
66
|
+
q = [minor, q].join
|
67
|
+
q.empty? ? 'M' : q
|
57
68
|
end
|
58
69
|
|
59
70
|
def quality
|
60
|
-
|
61
|
-
ChordQuality.new(name:
|
71
|
+
return @chord.quality unless @chord.nil?
|
72
|
+
ChordQuality.new(name: quality_name) if quality_name
|
62
73
|
end
|
63
74
|
|
64
75
|
def root_note
|
76
|
+
return @chord.root_note unless @chord.nil?
|
65
77
|
@scale[degree] - @flats
|
66
78
|
end
|
79
|
+
|
80
|
+
def notation
|
81
|
+
q = case quality_name
|
82
|
+
when 'm', 'M' then ''
|
83
|
+
when 'm7', 'M' then '7'
|
84
|
+
else quality_name
|
85
|
+
end
|
86
|
+
|
87
|
+
@notation ||= [
|
88
|
+
roman_numeral,
|
89
|
+
q,
|
90
|
+
].join
|
91
|
+
end
|
92
|
+
|
93
|
+
private
|
94
|
+
|
95
|
+
def regexed_notation
|
96
|
+
@regexed_notation ||= @notation.match(NOTATION_REGEX).named_captures
|
97
|
+
end
|
98
|
+
|
99
|
+
def normalize_quality_name(quality_name)
|
100
|
+
NOTATION_SUBSTITUTIONS.reduce(quality_name) do |memo, subs|
|
101
|
+
break memo if memo.empty?
|
102
|
+
memo.gsub(*subs)
|
103
|
+
end
|
104
|
+
end
|
67
105
|
end
|
68
106
|
end
|
data/lib/coltrane/scale.rb
CHANGED
@@ -59,8 +59,7 @@ module Coltrane
|
|
59
59
|
end
|
60
60
|
|
61
61
|
def degree_of_note(note)
|
62
|
-
|
63
|
-
return note + 1 unless note.nil?
|
62
|
+
notes.index(note)
|
64
63
|
end
|
65
64
|
|
66
65
|
def &(other)
|
@@ -90,8 +89,13 @@ module Coltrane
|
|
90
89
|
def tertians(n = 3)
|
91
90
|
degrees.size.times.reduce([]) do |memo, d|
|
92
91
|
ns = NoteSet[ *Array.new(n) { |i| notes[(d + (i * 2)) % size] } ]
|
93
|
-
|
94
|
-
|
92
|
+
begin
|
93
|
+
chord = Chord.new(notes: ns)
|
94
|
+
rescue ChordNotFoundError
|
95
|
+
memo
|
96
|
+
else
|
97
|
+
memo + [chord]
|
98
|
+
end
|
95
99
|
end
|
96
100
|
end
|
97
101
|
|
@@ -112,16 +116,17 @@ module Coltrane
|
|
112
116
|
end
|
113
117
|
|
114
118
|
def all_chords
|
115
|
-
|
119
|
+
chords
|
116
120
|
end
|
117
121
|
|
118
|
-
def chords(size)
|
122
|
+
def chords(size = 3..12)
|
123
|
+
size = (size..size) if size.is_a?(Integer)
|
119
124
|
included_names = []
|
120
|
-
scale_rotations = interval_sequence.inversions
|
121
|
-
ChordQuality
|
122
|
-
next memo1
|
125
|
+
scale_rotations = interval_sequence.inversions
|
126
|
+
ChordQuality.intervals_per_name.reduce([]) do |memo1, (qname, qintervals)|
|
127
|
+
next memo1 unless size.include?(qintervals.size)
|
123
128
|
memo1 + scale_rotations.each_with_index.reduce([]) do |memo2, (rot, index)|
|
124
|
-
if (rot & qintervals).size == size
|
129
|
+
if (rot & qintervals).size == qintervals.size
|
125
130
|
memo2 + [ Chord.new(root_note: degree(index+1),
|
126
131
|
quality: ChordQuality.new(name: qname)) ]
|
127
132
|
else
|
data/lib/coltrane/version.rb
CHANGED
data/lib/core_ext.rb
CHANGED
@@ -12,4 +12,91 @@ class String
|
|
12
12
|
word.downcase!
|
13
13
|
word
|
14
14
|
end
|
15
|
+
|
16
|
+
def interval_quality
|
17
|
+
{
|
18
|
+
'P' => 'Perfect',
|
19
|
+
'm' => 'Minor',
|
20
|
+
'M' => 'Major',
|
21
|
+
'A' => 'Augmented',
|
22
|
+
'd' => 'Diminished'
|
23
|
+
}[self]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Integer
|
28
|
+
def interval_name
|
29
|
+
{
|
30
|
+
1 => 'Unison',
|
31
|
+
2 => 'Second',
|
32
|
+
3 => 'Third',
|
33
|
+
4 => 'Fourth',
|
34
|
+
5 => 'Fifth',
|
35
|
+
6 => 'Sixth',
|
36
|
+
7 => 'Seventh',
|
37
|
+
8 => 'Octave',
|
38
|
+
9 => 'Ninth',
|
39
|
+
10 => 'Tenth',
|
40
|
+
11 => 'Eleventh',
|
41
|
+
12 => 'Twelfth',
|
42
|
+
13 => 'Thirteenth',
|
43
|
+
14 => 'Fourteenth',
|
44
|
+
15 => 'Double Octave'
|
45
|
+
}[self % 16]
|
46
|
+
end
|
15
47
|
end
|
48
|
+
|
49
|
+
class Hash
|
50
|
+
def clone_values(from_keys: nil, to_keys: nil, suffix: nil, branch_a: nil, branch_b: nil)
|
51
|
+
branch_a ||= self.dig(*from_keys)
|
52
|
+
if branch_b.nil?
|
53
|
+
self.create_branch!(*to_keys)
|
54
|
+
branch_b = self.dig(*to_keys)
|
55
|
+
end
|
56
|
+
|
57
|
+
branch_a.each do |key, val|
|
58
|
+
if val.is_a?(Hash)
|
59
|
+
clone_values branch_a: branch_a[key],
|
60
|
+
branch_b: (branch_b[key] ||= {}),
|
61
|
+
suffix: suffix
|
62
|
+
else
|
63
|
+
branch_b[key] = "#{val.dup}#{suffix}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def create_branch!(*keys)
|
69
|
+
return nil if keys.empty?
|
70
|
+
key = keys.shift
|
71
|
+
(self[key] ||= {}).create_branch!(*keys)
|
72
|
+
end
|
73
|
+
|
74
|
+
def deep_dup
|
75
|
+
dup_hash = {}
|
76
|
+
self.each do |k,v|
|
77
|
+
if v.is_a?(Hash)
|
78
|
+
dup_hash[k] = v.deep_dup
|
79
|
+
else
|
80
|
+
dup_hash[k] = v.dup
|
81
|
+
end
|
82
|
+
end
|
83
|
+
dup_hash
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
100
|
+
|
101
|
+
|
102
|
+
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: coltrane
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pedro Maciel
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-02-
|
11
|
+
date: 2018-02-12 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: facets
|
@@ -146,6 +146,7 @@ files:
|
|
146
146
|
- bin/thor
|
147
147
|
- bin/tilt
|
148
148
|
- coltrane.gemspec
|
149
|
+
- data/qualities.yml
|
149
150
|
- db/cache.sqlite3
|
150
151
|
- db/cache_test.sqlite3
|
151
152
|
- db/config.yml
|
@@ -189,7 +190,6 @@ files:
|
|
189
190
|
- lib/coltrane/piano_representation.rb
|
190
191
|
- lib/coltrane/pitch.rb
|
191
192
|
- lib/coltrane/progression.rb
|
192
|
-
- lib/coltrane/qualities.rb
|
193
193
|
- lib/coltrane/roman_chord.rb
|
194
194
|
- lib/coltrane/scale.rb
|
195
195
|
- lib/coltrane/version.rb
|
data/lib/coltrane/qualities.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Coltrane
|
4
|
-
module Qualities
|
5
|
-
CHORD_QUALITIES = {
|
6
|
-
'5' => [0, 7],
|
7
|
-
'Msus2' => [0, 2, 7],
|
8
|
-
'dim' => [0, 3, 6],
|
9
|
-
'm' => [0, 3, 7],
|
10
|
-
'm#5' => [0, 3, 8],
|
11
|
-
'Mb5' => [0, 4, 6],
|
12
|
-
'M' => [0, 4, 7],
|
13
|
-
'M#5' => [0, 4, 8],
|
14
|
-
'Mb6' => [0, 4, 8],
|
15
|
-
'7ndim5' => [0, 4, 10],
|
16
|
-
'Msus4' => [0, 5, 7],
|
17
|
-
'mb6b9' => [0, 1, 3, 8],
|
18
|
-
'addb9' => [0, 1, 4, 7],
|
19
|
-
'madd9' => [0, 2, 3, 7],
|
20
|
-
'Madd9' => [0, 2, 4, 7],
|
21
|
-
'sus24' => [0, 2, 5, 7],
|
22
|
-
'M#5add9' => [0, 2, 4, 8],
|
23
|
-
'9ndim5' => [0, 2, 4, 10],
|
24
|
-
'+add#9' => [0, 3, 4, 8],
|
25
|
-
'madd4' => [0, 3, 5, 7],
|
26
|
-
'4' => [0, 3, 5, 10],
|
27
|
-
'dim7' => [0, 3, 6, 9],
|
28
|
-
'm7' => [0, 3, 7, 10],
|
29
|
-
'mM7' => [0, 3, 7, 11],
|
30
|
-
'm7#5' => [0, 3, 8, 10],
|
31
|
-
'7b5' => [0, 4, 6, 10],
|
32
|
-
'7' => [0, 4, 7, 10],
|
33
|
-
'7#5' => [0, 4, 8, 10],
|
34
|
-
'7b13' => [0, 4, 8, 10],
|
35
|
-
'M7#5' => [0, 4, 8, 11],
|
36
|
-
'M7b6' => [0, 4, 8, 11],
|
37
|
-
'M7b5' => [0, 4, 6, 11],
|
38
|
-
'M7#5sus4' => [0, 5, 8, 11],
|
39
|
-
'7sus4' => [0, 5, 7, 10],
|
40
|
-
'7#5sus4' => [0, 5, 8, 10],
|
41
|
-
'M7sus4' => [0, 5, 7, 11],
|
42
|
-
'M6' => [0, 4, 7, 9],
|
43
|
-
'm7b5' => [0, 3, 6, 10],
|
44
|
-
'M7' => [0, 4, 7, 11],
|
45
|
-
'maj7' => [0, 4, 7, 11],
|
46
|
-
'mb6M7' => [0, 3, 8, 11],
|
47
|
-
'dimM7' => [0, 3, 6, 11],
|
48
|
-
'm6' => [0, 3, 5, 7, 9],
|
49
|
-
'm6/9' => [0, 2, 3, 7, 9],
|
50
|
-
'M6/9' => [0, 2, 4, 7, 9],
|
51
|
-
'M6#11' => [0, 4, 6, 7, 9],
|
52
|
-
'm7add11' => [0, 3, 5, 7, 10],
|
53
|
-
'dim7M7' => [0, 3, 6, 9, 11],
|
54
|
-
'm9' => [0, 2, 3, 7, 10],
|
55
|
-
'm9#5' => [0, 2, 3, 8, 10],
|
56
|
-
'm9b5' => [0, 2, 3, 6, 10],
|
57
|
-
'mM7b6' => [0, 3, 7, 8, 11],
|
58
|
-
'mM9' => [0, 2, 3, 7, 11],
|
59
|
-
'M7b9' => [0, 1, 4, 7, 11],
|
60
|
-
'M9' => [0, 2, 4, 7, 11],
|
61
|
-
'M9#5' => [0, 2, 4, 8, 11],
|
62
|
-
'M9#5sus4' => [0, 2, 5, 8, 11],
|
63
|
-
'M9b5' => [0, 2, 4, 6, 11],
|
64
|
-
'M9sus4' => [0, 2, 5, 7, 11],
|
65
|
-
'M7#11' => [0, 4, 6, 7, 11],
|
66
|
-
'7#9' => [0, 3, 4, 7, 10],
|
67
|
-
'7#11' => [0, 4, 6, 7, 10],
|
68
|
-
'11' => [0, 2, 5, 7, 10],
|
69
|
-
'11b9' => [0, 1, 5, 7, 10],
|
70
|
-
'13ndim5' => [0, 2, 4, 9, 10],
|
71
|
-
'7#5#9' => [0, 3, 4, 8, 10],
|
72
|
-
'7#5b9' => [0, 1, 4, 8, 10],
|
73
|
-
'7add6' => [0, 4, 7, 9, 10],
|
74
|
-
'7b6' => [0, 4, 7, 8, 10],
|
75
|
-
'7b9' => [0, 1, 4, 7, 10],
|
76
|
-
'7sus4b9' => [0, 1, 5, 7, 10],
|
77
|
-
'9' => [0, 2, 4, 7, 10],
|
78
|
-
'9#5' => [0, 2, 4, 8, 10],
|
79
|
-
'9b13' => [0, 2, 4, 8, 10],
|
80
|
-
'9b5' => [0, 2, 4, 6, 10],
|
81
|
-
'9sus4' => [0, 2, 5, 7, 10],
|
82
|
-
'M6/9#11' => [0, 2, 4, 6, 7, 9],
|
83
|
-
'6/9#11' => [0, 2, 4, 6, 7, 9],
|
84
|
-
'9#5#11' => [0, 2, 4, 6, 8, 10],
|
85
|
-
'Blues' => [0, 3, 5, 6, 7, 10],
|
86
|
-
'm11' => [0, 2, 3, 5, 7, 10],
|
87
|
-
'm11#5' => [0, 2, 3, 5, 8, 10],
|
88
|
-
'm11b5' => [0, 2, 3, 5, 6, 10],
|
89
|
-
'13b5' => [0, 2, 4, 6, 9, 10],
|
90
|
-
'13b9' => [0, 1, 4, 7, 9, 10],
|
91
|
-
'13sus4' => [0, 2, 5, 7, 9, 10],
|
92
|
-
'7#11b13' => [0, 4, 6, 7, 8, 10],
|
93
|
-
'7#5b9#11' => [0, 1, 4, 6, 8, 10],
|
94
|
-
'7#9#11' => [0, 3, 4, 6, 7, 10],
|
95
|
-
'7#9b13' => [0, 3, 4, 7, 8, 10],
|
96
|
-
'13#9' => [0, 3, 4, 7, 9, 10],
|
97
|
-
'13' => [0, 2, 4, 7, 9, 10],
|
98
|
-
'7b9#11' => [0, 1, 4, 6, 7, 10],
|
99
|
-
'7b9#9' => [0, 1, 3, 4, 7, 10],
|
100
|
-
'7b9b13' => [0, 1, 4, 7, 8, 10],
|
101
|
-
'7sus4b9b13' => [0, 1, 5, 7, 8, 10],
|
102
|
-
'9#11' => [0, 2, 4, 6, 7, 10],
|
103
|
-
'M13' => [0, 2, 4, 7, 9, 11],
|
104
|
-
'M7#9#11' => [0, 3, 4, 6, 7, 11],
|
105
|
-
'M7add13' => [0, 2, 4, 7, 9, 11],
|
106
|
-
'M9#11' => [0, 2, 4, 6, 7, 11],
|
107
|
-
'mM9b6' => [0, 2, 3, 7, 8, 11],
|
108
|
-
'7b9b13#11' => [0, 1, 4, 6, 7, 8, 10],
|
109
|
-
'13b9#11' => [0, 1, 4, 6, 7, 9, 10],
|
110
|
-
'9#11b13' => [0, 2, 4, 6, 7, 8, 10],
|
111
|
-
'13#11' => [0, 2, 4, 6, 7, 9, 10],
|
112
|
-
'M13#11' => [0, 2, 4, 6, 7, 9, 11],
|
113
|
-
'm13' => [0, 2, 3, 5, 7, 9, 10],
|
114
|
-
'13#9#11' => [0, 3, 4, 6, 7, 9, 10],
|
115
|
-
'7#9#11b13' => [0, 3, 4, 6, 7, 8, 10]
|
116
|
-
}.freeze
|
117
|
-
end
|
118
|
-
end
|