coltrane 0.0.2 → 1.0.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 +5 -5
- data/.gitignore +2 -0
- data/.ruby-version +1 -0
- data/Gemfile +15 -10
- data/Gemfile.lock +47 -128
- data/README.md +30 -8
- data/Rakefile +0 -4
- data/bin/bundle +105 -0
- data/bin/byebug +29 -0
- data/bin/coderay +12 -0
- data/bin/coltrane +25 -7
- data/bin/htmldiff +12 -0
- data/bin/kill-pry-rescue +12 -0
- data/bin/ldiff +12 -0
- data/bin/pry +12 -0
- data/bin/rake +12 -0
- data/bin/rescue +12 -0
- data/bin/rspec +12 -0
- data/bin/rubocop +12 -0
- data/bin/ruby-parse +12 -0
- data/bin/ruby-rewrite +12 -0
- data/exe/coltrane +173 -0
- data/lib/cli/bass_guitar.rb +9 -0
- data/lib/cli/chord.rb +24 -0
- data/lib/cli/errors.rb +28 -0
- data/lib/cli/guitar.rb +55 -0
- data/lib/cli/notes.rb +18 -0
- data/lib/cli/piano.rb +54 -0
- data/lib/cli/representation.rb +47 -0
- data/lib/cli/scale.rb +48 -0
- data/lib/cli/text.rb +13 -0
- data/lib/cli/ukulele.rb +11 -0
- data/lib/coltrane-cli.rb +12 -0
- data/lib/coltrane.rb +16 -31
- data/lib/coltrane/cache.rb +34 -0
- data/lib/coltrane/chord.rb +28 -41
- data/lib/coltrane/chord_quality.rb +14 -39
- data/lib/coltrane/classic_progressions.rb +37 -0
- data/lib/coltrane/classic_scales.rb +92 -118
- data/lib/coltrane/errors.rb +54 -0
- data/lib/coltrane/interval.rb +25 -17
- data/lib/coltrane/interval_sequence.rb +57 -27
- data/lib/coltrane/note.rb +44 -30
- data/lib/coltrane/note_set.rb +37 -22
- data/lib/coltrane/piano_representation.rb +0 -58
- data/lib/coltrane/pitch.rb +12 -3
- data/lib/coltrane/progression.rb +31 -0
- data/lib/coltrane/qualities.rb +115 -114
- data/lib/coltrane/roman_chord.rb +36 -0
- data/lib/coltrane/scale.rb +85 -77
- data/lib/coltrane/version.rb +1 -1
- data/lib/core_ext.rb +12 -0
- data/pkg/coltrane-0.0.2.gem +0 -0
- metadata +27 -14
- data/lib/coltrane/essential_guitar_chords.rb +0 -82
- data/lib/coltrane/fret_set.rb +0 -0
- data/lib/coltrane/guitar.rb +0 -15
- data/lib/coltrane/guitar_chord.rb +0 -50
- data/lib/coltrane/guitar_chord_finder.rb +0 -98
- data/lib/coltrane/guitar_note.rb +0 -50
- data/lib/coltrane/guitar_note_set.rb +0 -61
- data/lib/coltrane/guitar_representation.rb +0 -96
- data/lib/coltrane/guitar_string.rb +0 -52
- data/lib/coltrane/scale_cache.rb +0 -4
@@ -1,48 +1,23 @@
|
|
1
|
-
|
2
|
-
|
3
1
|
module Coltrane
|
4
2
|
# It describe the quality of a chord, like maj7 or dim.
|
5
|
-
class ChordQuality
|
3
|
+
class ChordQuality < IntervalSequence
|
6
4
|
attr_reader :name
|
7
5
|
include Qualities
|
8
6
|
|
9
|
-
def initialize(
|
10
|
-
if
|
11
|
-
|
7
|
+
def initialize(name: nil, notes: nil)
|
8
|
+
if !name.nil?
|
9
|
+
if(intervals = CHORD_QUALITIES[name])
|
10
|
+
@name = name
|
11
|
+
super(intervals: intervals)
|
12
|
+
else
|
13
|
+
raise ChordNotFoundError.new
|
14
|
+
end
|
15
|
+
elsif !notes.nil?
|
16
|
+
super(notes: notes)
|
17
|
+
@name = CHORD_QUALITIES.key(intervals_semitones)
|
18
|
+
else
|
19
|
+
raise WrongKeywords.new('[name:] || [notes:]')
|
12
20
|
end
|
13
|
-
@name = find_chord_name(interval_sequence)
|
14
|
-
end
|
15
|
-
|
16
|
-
def self.new_from_notes(notes)
|
17
|
-
note_set = NoteSet.new(notes) unless notes.class == NoteSet
|
18
|
-
new(note_set.interval_sequence.reordered)
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.new_from_pitches(*pitches)
|
22
|
-
notes = pitches.sort_by(&:number)
|
23
|
-
.collect(&:note)
|
24
|
-
.collect(&:name)
|
25
|
-
.uniq
|
26
|
-
|
27
|
-
new_from_notes(notes)
|
28
|
-
end
|
29
|
-
|
30
|
-
def self.new_from_string(quality_string)
|
31
|
-
new(CHORD_QUALITIES[quality_string])
|
32
|
-
end
|
33
|
-
|
34
|
-
def intervals
|
35
|
-
CHORD_QUALITIES[name]
|
36
|
-
end
|
37
|
-
|
38
|
-
protected
|
39
|
-
|
40
|
-
def find_chord_name(note_intervals, inversion = 0)
|
41
|
-
inversion >= note_intervals.all.size && return
|
42
|
-
CHORD_QUALITIES.key(note_intervals.numbers) ||
|
43
|
-
CHORD_QUALITIES.key(note_intervals.numbers.sort) ||
|
44
|
-
find_chord_name(note_intervals.next_inversion, inversion + 1) ||
|
45
|
-
nil
|
46
21
|
end
|
47
22
|
end
|
48
23
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Coltrane
|
2
|
+
module ClassicProgressions
|
3
|
+
PROGRESSIONS = {
|
4
|
+
pop: [:major, [1,5,6,4]],
|
5
|
+
fifties: [:major, [1,6,4,5]],
|
6
|
+
blues: [:major, [1,4,1,5,4,1]],
|
7
|
+
jazz: [:major, [2,5,1]],
|
8
|
+
jazz_minor: [:minor, [2,5,1]],
|
9
|
+
andalusian: [:minor, [1,7,6,5]]
|
10
|
+
}
|
11
|
+
|
12
|
+
def pop(tone)
|
13
|
+
scale, degrees = PROGRESSIONS[:pop]
|
14
|
+
Scale.public_send(scale, tone).progression(*degrees)
|
15
|
+
end
|
16
|
+
|
17
|
+
def fifties(tone)
|
18
|
+
scale, degrees = PROGRESSIONS[:fifties]
|
19
|
+
Scale.public_send(scale, tone).progression(*degrees)
|
20
|
+
end
|
21
|
+
|
22
|
+
def blues(tone)
|
23
|
+
scale, degrees = PROGRESSIONS[:blues]
|
24
|
+
Scale.public_send(scale, tone).progression(*degrees)
|
25
|
+
end
|
26
|
+
|
27
|
+
def jazz(tone)
|
28
|
+
scale, degrees = PROGRESSIONS[:jazz]
|
29
|
+
Scale.public_send(scale, tone).progression(*degrees)
|
30
|
+
end
|
31
|
+
|
32
|
+
def andalusian(tone)
|
33
|
+
scale, degrees = PROGRESSIONS[:andalusian]
|
34
|
+
Scale.public_send(scale, tone).progression(*degrees)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -1,134 +1,108 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
def hungarian_minor(tone='C', mode=1)
|
30
|
-
new(*SCALES['Hungarian Minor'], tone: tone, mode: mode)
|
31
|
-
end
|
32
|
-
|
33
|
-
def pentatonic_major(tone='C', mode=1)
|
34
|
-
new(*SCALES['Pentatonic Major'], tone: tone, mode: mode)
|
35
|
-
end
|
36
|
-
|
37
|
-
def pentatonic_minor(tone='C', mode=1)
|
38
|
-
new(*SCALES['Pentatonic Minor'], tone: tone, mode: mode)
|
39
|
-
end
|
40
|
-
|
41
|
-
def blues_major(tone='C', mode=1)
|
42
|
-
new(*SCALES['Blues Major'], tone: tone, mode: mode)
|
43
|
-
end
|
44
|
-
|
45
|
-
def blues_minor(tone='C', mode=1)
|
46
|
-
new(*SCALES['Blues Minor'], tone: tone, mode: mode)
|
47
|
-
end
|
48
|
-
|
49
|
-
def whole_tone(tone='C', mode=1)
|
50
|
-
new(*SCALES['Blues Minor'], tone: tone, mode: mode)
|
51
|
-
end
|
1
|
+
module Coltrane
|
2
|
+
module ClassicScales
|
3
|
+
|
4
|
+
SCALES = {
|
5
|
+
'Major' => [2,2,1,2,2,2,1],
|
6
|
+
'Pentatonic Major' => [2,2,3,2,3],
|
7
|
+
'Blues Major' => [2,1,1,3,2,3],
|
8
|
+
'Natural Minor' => [2,1,2,2,1,2,2],
|
9
|
+
'Harmonic Minor' => [2,1,2,2,1,3,1],
|
10
|
+
'Hungarian Minor' => [2,1,2,1,1,3,1],
|
11
|
+
'Pentatonic Minor' => [3,2,2,3,2],
|
12
|
+
'Blues Minor' => [3,2,1,1,3,2],
|
13
|
+
'Whole Tone' => [2,2,2,2,2,2],
|
14
|
+
'Flamenco' => [1,3,1,2,1,2,2]
|
15
|
+
}
|
16
|
+
|
17
|
+
MODES = {
|
18
|
+
'Major' => %w[Ionian Dorian Phrygian Lydian Mixolydian Aeolian Locrian]
|
19
|
+
}
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
# A little helper to build method names
|
24
|
+
# just make the code more clear
|
25
|
+
def self.methodize(string)
|
26
|
+
string.downcase.gsub(' ', '_')
|
27
|
+
end
|
52
28
|
|
53
|
-
|
54
|
-
new(*([1]*12), tone: tone, mode: mode)
|
55
|
-
end
|
29
|
+
public
|
56
30
|
|
57
|
-
|
58
|
-
|
59
|
-
|
31
|
+
# Creates factories for scales
|
32
|
+
SCALES.each do |name, distances|
|
33
|
+
define_method methodize(name) do |tone='C', mode=1|
|
34
|
+
new(*distances, tone: tone, mode: mode, name: name)
|
35
|
+
end
|
36
|
+
end
|
60
37
|
|
61
|
-
|
62
|
-
|
63
|
-
|
38
|
+
# Creates factories for Greek Modes and possibly others
|
39
|
+
MODES.each do |scale, modes|
|
40
|
+
modes.each_with_index do |mode, index|
|
41
|
+
scale_method = methodize(scale)
|
42
|
+
mode_name = mode
|
43
|
+
mode_n = index + 1
|
44
|
+
define_method methodize(mode) do |tone='C'|
|
45
|
+
new(*SCALES[scale], tone: tone, mode: mode_n, name: mode_name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
64
49
|
|
65
|
-
|
66
|
-
|
67
|
-
|
50
|
+
alias_method :minor, :natural_minor
|
51
|
+
alias_method :pentatonic, :pentatonic_major
|
52
|
+
alias_method :blues, :blues_major
|
68
53
|
|
69
|
-
|
70
|
-
|
71
|
-
|
54
|
+
def known_scales
|
55
|
+
SCALES.keys
|
56
|
+
end
|
72
57
|
|
73
|
-
|
74
|
-
|
75
|
-
|
58
|
+
def fetch(name, tone=nil)
|
59
|
+
Coltrane::Scale.public_send(name, tone)
|
60
|
+
end
|
76
61
|
|
77
|
-
|
78
|
-
|
79
|
-
|
62
|
+
def from_key(key)
|
63
|
+
scale = key.delete!('m') ? :minor : :major
|
64
|
+
note = key
|
65
|
+
Scale.public_send(scale.nil? || scale == 'M' ? :major : :minor, note)
|
66
|
+
end
|
80
67
|
|
81
|
-
def locrian(tone='C')
|
82
|
-
new(*SCALES['Major'], tone: tone, mode: 7)
|
83
|
-
end
|
84
68
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
69
|
+
# Will output a OpenStruct like the following:
|
70
|
+
# {
|
71
|
+
# scales: [array of scales]
|
72
|
+
# results: {
|
73
|
+
# scale_name: {
|
74
|
+
# note_number => found_notes
|
75
|
+
# }
|
76
|
+
# }
|
77
|
+
# }
|
78
|
+
|
79
|
+
def having_notes(notes)
|
80
|
+
|
81
|
+
format = { scales: [], results: {} }
|
82
|
+
OpenStruct.new(
|
83
|
+
SCALES.each_with_object(format) do |(name, intervals), output|
|
84
|
+
Note.all.each.map do |tone|
|
85
|
+
scale = new(*intervals, tone: tone, name: scale)
|
86
|
+
output[:results][name] ||= {}
|
87
|
+
if output[:results][name].has_key?(tone.number)
|
88
|
+
next
|
89
|
+
else
|
90
|
+
output[:scales] << scale if scale.include?(notes)
|
91
|
+
output[:results][name][tone.number] = scale.notes & notes
|
92
|
+
end
|
93
|
+
end
|
102
94
|
end
|
103
|
-
|
104
|
-
end
|
105
|
-
print "\n"
|
95
|
+
)
|
106
96
|
end
|
107
|
-
end
|
108
97
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
SCALES.each_with_object([]) do |scale_obj, results|
|
114
|
-
scale_name, intervals = scale_obj
|
115
|
-
print Paint[scale_name.ljust(scale_name_size), 'yellow']
|
116
|
-
Note.all.each do |note|
|
117
|
-
found_sth = false
|
118
|
-
print ' '
|
119
|
-
scale = Scale.new(*intervals, tone: note.name)
|
120
|
-
notes_included = scale.include_notes?(*notes)
|
121
|
-
if notes_included == notes
|
122
|
-
found_sth = true
|
123
|
-
results << OpenStruct.new({
|
124
|
-
name: "#{note.name} #{scale_name}",
|
125
|
-
notes: notes_included
|
126
|
-
})
|
127
|
-
end
|
128
|
-
print Paint[note.name, found_sth ? 'yellow' : 'black']
|
129
|
-
print Paint["(#{notes_included.count || 0})", found_sth ? 'gray' : 'black']
|
98
|
+
def having_chords(*chords)
|
99
|
+
should_create = !chords.first.is_a?(Chord)
|
100
|
+
notes = chords.reduce(NoteSet[]) do |memo, c|
|
101
|
+
memo + (should_create ? Chord.new(name: c) : c).notes
|
130
102
|
end
|
131
|
-
|
103
|
+
having_notes(notes)
|
132
104
|
end
|
105
|
+
|
106
|
+
alias_method :having_chord, :having_chords
|
133
107
|
end
|
134
108
|
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Coltrane
|
2
|
+
class ColtraneError < StandardError
|
3
|
+
def initialize(msg)
|
4
|
+
super msg
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class BadConstructor < ColtraneError
|
9
|
+
def initialize(msg=nil)
|
10
|
+
super "Bad constructor. #{msg}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class WrongKeywords < BadConstructor
|
15
|
+
def initialize(msg)
|
16
|
+
super "Use one of the following set of keywords: #{msg}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class InvalidNote < BadConstructor
|
21
|
+
def initialize(note)
|
22
|
+
super "#{note} is not a valid note"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
class InvalidNotes < BadConstructor
|
27
|
+
def initialize(notes)
|
28
|
+
super "#{notes} are not a valid set of notes"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
class HasNoNotes < BadConstructor
|
33
|
+
def initialize(obj)
|
34
|
+
super "The given object (#{obj.inspect} does not respond to :notes, "\
|
35
|
+
"thereby it can't be used for this operation)"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class WrongDegree
|
40
|
+
def initialize(degree)
|
41
|
+
super "#{degree} is not a valid degree. Degrees for this scale must be between 1 and #{degrees}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class ChordNotFoundError < ColtraneError
|
46
|
+
def initialize
|
47
|
+
super "The chord you provided wasn't found. "\
|
48
|
+
"If you're sure this chord exists, "\
|
49
|
+
"would you mind to suggest it's inclusion here: "\
|
50
|
+
"https://github.com/pedrozath/coltrane/issues "\
|
51
|
+
"\n\nA tip tho: always include the letter M for major"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/coltrane/interval.rb
CHANGED
@@ -1,33 +1,41 @@
|
|
1
1
|
module Coltrane
|
2
2
|
# It describes a interval between 2 pitches
|
3
3
|
class Interval
|
4
|
-
attr_reader :
|
4
|
+
attr_reader :semitones
|
5
5
|
|
6
6
|
NAMES = [
|
7
7
|
'1P',
|
8
|
-
'2m',
|
9
|
-
'
|
10
|
-
'
|
8
|
+
'2m',
|
9
|
+
'2M',
|
10
|
+
'3m',
|
11
|
+
'3M',
|
12
|
+
'4P',
|
13
|
+
'4A',
|
11
14
|
'5P',
|
12
|
-
'6m',
|
15
|
+
'6m',
|
16
|
+
'6M',
|
13
17
|
'7m',
|
14
|
-
'7M'
|
15
|
-
'8P',
|
16
|
-
'9m', '9M',
|
17
|
-
'10m', '10M',
|
18
|
-
'11P',
|
19
|
-
'12P',
|
20
|
-
'13m', '13M',
|
21
|
-
'14m', '14M',
|
22
|
-
'15P', '15A'
|
18
|
+
'7M'
|
23
19
|
].freeze
|
24
20
|
|
25
|
-
def initialize(
|
26
|
-
@
|
21
|
+
def initialize(arg)
|
22
|
+
@semitones = (case arg
|
23
|
+
when Interval then arg.semitones
|
24
|
+
when String then NAMES.index(arg)
|
25
|
+
when Numeric then arg
|
26
|
+
end) % 12
|
27
27
|
end
|
28
28
|
|
29
29
|
def name
|
30
|
-
NAMES[
|
30
|
+
NAMES[semitones]
|
31
31
|
end
|
32
|
+
|
33
|
+
def +(x)
|
34
|
+
case x
|
35
|
+
when Numeric then Interval.new(semitones + x)
|
36
|
+
when Interval then Interval.new(semitones + x.semitones)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
32
40
|
end
|
33
41
|
end
|
@@ -1,30 +1,33 @@
|
|
1
1
|
module Coltrane
|
2
2
|
# It describes a sequence of intervals
|
3
3
|
class IntervalSequence
|
4
|
+
extend Forwardable
|
4
5
|
attr_reader :intervals
|
5
6
|
|
6
|
-
|
7
|
-
arg = [arg] if arg.class != Array
|
8
|
-
@intervals = arg.reduce([]) do |memo, arg_item|
|
9
|
-
case arg_item
|
10
|
-
when Numeric then memo << Interval.new(arg_item)
|
11
|
-
when Interval then memo << arg_item
|
12
|
-
when NoteSet then memo + intervals_from_note_set(arg_item)
|
13
|
-
end
|
14
|
-
end
|
15
|
-
end
|
7
|
+
def_delegators :@intervals, :map, :each, :[], :size, :reduce
|
16
8
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
9
|
+
def initialize(notes: nil, intervals: nil, distances: nil)
|
10
|
+
if !notes.nil?
|
11
|
+
notes = NoteSet[*notes] if notes.is_a?(Array)
|
12
|
+
@intervals = intervals_from_notes(notes)
|
13
|
+
elsif !intervals.nil?
|
14
|
+
@intervals = intervals.map { |i| Interval.new(i) }
|
15
|
+
elsif !distances.nil?
|
16
|
+
@distances = distances
|
17
|
+
@intervals = intervals_from_distances(distances)
|
18
|
+
else
|
19
|
+
raise 'Provide: [notes:] || [intervals:] || [distances:]'
|
23
20
|
end
|
24
21
|
end
|
25
22
|
|
26
|
-
def
|
27
|
-
|
23
|
+
def distances
|
24
|
+
intervals_semitones[1..-1].each_with_index.map do |n, i|
|
25
|
+
if i == 0
|
26
|
+
n
|
27
|
+
elsif i < intervals_semitones.size
|
28
|
+
n - intervals_semitones[i]
|
29
|
+
end
|
30
|
+
end + [12 - intervals_semitones.last]
|
28
31
|
end
|
29
32
|
|
30
33
|
def all
|
@@ -36,35 +39,62 @@ module Coltrane
|
|
36
39
|
end
|
37
40
|
|
38
41
|
def shift(ammount)
|
39
|
-
IntervalSequence.new(intervals.map do |i|
|
40
|
-
(i.
|
42
|
+
IntervalSequence.new(intervals: intervals.map do |i|
|
43
|
+
(i.semitones + ammount) % 12
|
41
44
|
end)
|
42
45
|
end
|
43
46
|
|
44
47
|
def zero_it
|
45
|
-
self.shift(-intervals.first.
|
48
|
+
self.shift(-intervals.first.semitones)
|
49
|
+
end
|
50
|
+
|
51
|
+
def inversion(index)
|
52
|
+
IntervalSequence.new(intervals: intervals.rotate(index)).zero_it
|
46
53
|
end
|
47
54
|
|
48
55
|
def next_inversion
|
49
|
-
|
56
|
+
inversion(index+1)
|
50
57
|
end
|
51
58
|
|
52
59
|
def previous_inversion
|
53
|
-
|
60
|
+
inversion(index-1)
|
54
61
|
end
|
55
62
|
|
56
63
|
def inversions
|
57
64
|
Array.new(intervals.length) do |index|
|
58
|
-
|
65
|
+
inversion(index)
|
59
66
|
end
|
60
67
|
end
|
61
68
|
|
62
|
-
def
|
63
|
-
|
69
|
+
def quality
|
70
|
+
end
|
71
|
+
|
72
|
+
def intervals_semitones
|
73
|
+
map(&:semitones)
|
64
74
|
end
|
65
75
|
|
66
76
|
def names
|
67
|
-
|
77
|
+
map(&:name)
|
78
|
+
end
|
79
|
+
|
80
|
+
def notes_for(root_note)
|
81
|
+
NoteSet[
|
82
|
+
*intervals.reduce([]) do |memo, interval|
|
83
|
+
memo + [root_note + interval]
|
84
|
+
end
|
85
|
+
]
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def intervals_from_distances(distances)
|
91
|
+
distances[0..-2].reduce([Interval.new(0)]) do |memo, d|
|
92
|
+
memo + [memo.last + d]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def intervals_from_notes(notes)
|
97
|
+
notes.map { |n| notes.root - n }.sort_by(&:semitones)
|
68
98
|
end
|
69
99
|
end
|
70
100
|
end
|