coltrane 1.0.26 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|