coltrane 2.2.1 → 3.0.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +16 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +25 -2
- data/README.md +3 -0
- data/bin/console +6 -2
- data/coltrane.gemspec +2 -1
- data/exe/coltrane +14 -224
- data/lib/coltrane.rb +3 -50
- data/lib/coltrane/commands.rb +14 -0
- data/lib/coltrane/commands/chords.rb +77 -0
- data/lib/coltrane/commands/command.rb +45 -0
- data/lib/coltrane/commands/common_chords.rb +33 -0
- data/lib/coltrane/commands/errors.rb +44 -0
- data/lib/coltrane/commands/find_progression.rb +28 -0
- data/lib/coltrane/commands/find_scale.rb +39 -0
- data/lib/coltrane/commands/notes.rb +44 -0
- data/lib/coltrane/commands/progression.rb +27 -0
- data/lib/{cli → coltrane/commands}/representation.rb +0 -0
- data/lib/coltrane/commands/scale.rb +46 -0
- data/lib/coltrane/renderers.rb +6 -0
- data/lib/coltrane/renderers/renderer.rb +42 -0
- data/lib/coltrane/renderers/text_renderer.rb +23 -0
- data/lib/coltrane/renderers/text_renderer/array_drawer.rb +45 -0
- data/lib/coltrane/renderers/text_renderer/base_drawer.rb +20 -0
- data/lib/coltrane/renderers/text_renderer/hash_drawer.rb +16 -0
- data/lib/coltrane/renderers/text_renderer/representation_guitar_chord_drawer.rb +95 -0
- data/lib/coltrane/renderers/text_renderer/representation_guitar_note_set_drawer.rb +76 -0
- data/lib/coltrane/renderers/text_renderer/representation_piano_note_set_drawer.rb +49 -0
- data/lib/coltrane/renderers/text_renderer/theory_chord_drawer.rb +14 -0
- data/lib/coltrane/renderers/text_renderer/theory_note_set_drawer.rb +17 -0
- data/lib/coltrane/renderers/text_renderer/theory_progression_drawer.rb +13 -0
- data/lib/coltrane/renderers/text_renderer/theory_progression_set_drawer.rb +22 -0
- data/lib/coltrane/renderers/text_renderer/theory_scale_drawer.rb +13 -0
- data/lib/coltrane/renderers/text_renderer/theory_scale_set_drawer.rb +27 -0
- data/lib/coltrane/representation.rb +12 -0
- data/lib/coltrane/representation/guitar.rb +34 -0
- data/lib/coltrane/representation/guitar/chord.rb +180 -0
- data/lib/coltrane/representation/guitar/note.rb +26 -0
- data/lib/coltrane/representation/guitar/note_set.rb +35 -0
- data/lib/coltrane/representation/guitar/string.rb +31 -0
- data/lib/coltrane/representation/guitar_like_instruments.rb +21 -0
- data/lib/coltrane/representation/piano.rb +36 -0
- data/lib/coltrane/representation/piano/note_set.rb +22 -0
- data/lib/{coltrane_synth.rb → coltrane/synth.rb.rb} +0 -0
- data/lib/{coltrane_synth → coltrane/synth}/base.rb +0 -0
- data/lib/{coltrane_synth → coltrane/synth}/synth.rb +0 -0
- data/lib/coltrane/theory.rb +54 -0
- data/lib/coltrane/theory/cadence.rb +9 -0
- data/lib/coltrane/{changes.rb → theory/changes.rb} +0 -0
- data/lib/coltrane/theory/chord.rb +101 -0
- data/lib/coltrane/theory/chord_quality.rb +113 -0
- data/lib/coltrane/theory/chord_substitutions.rb +11 -0
- data/lib/coltrane/theory/circle_of_fifths.rb +33 -0
- data/lib/coltrane/theory/classic_scales.rb +113 -0
- data/lib/coltrane/theory/diatonic_scale.rb +38 -0
- data/lib/coltrane/theory/errors.rb +97 -0
- data/lib/coltrane/theory/frequency.rb +52 -0
- data/lib/coltrane/theory/frequency_interval.rb +83 -0
- data/lib/coltrane/theory/interval.rb +209 -0
- data/lib/coltrane/theory/interval_class.rb +212 -0
- data/lib/coltrane/theory/interval_sequence.rb +157 -0
- data/lib/coltrane/theory/key.rb +18 -0
- data/lib/coltrane/{mode.rb → theory/mode.rb} +0 -0
- data/lib/coltrane/theory/notable_progressions.rb +30 -0
- data/lib/coltrane/theory/note.rb +104 -0
- data/lib/coltrane/theory/note_set.rb +101 -0
- data/lib/coltrane/theory/pitch.rb +94 -0
- data/lib/coltrane/theory/pitch_class.rb +154 -0
- data/lib/coltrane/theory/progression.rb +81 -0
- data/lib/coltrane/theory/progression_set.rb +18 -0
- data/lib/coltrane/{qualities.rb → theory/qualities.rb} +0 -0
- data/lib/coltrane/theory/roman_chord.rb +114 -0
- data/lib/coltrane/theory/scale.rb +161 -0
- data/lib/coltrane/theory/scale_search_result.rb +6 -0
- data/lib/coltrane/theory/scale_set.rb +40 -0
- data/lib/coltrane/theory/voicing.rb +41 -0
- data/lib/coltrane/version.rb +1 -1
- metadata +88 -63
- data/bin/rails +0 -17
- data/data/qualities.yml +0 -83
- data/db/cache.sqlite3 +0 -0
- data/db/cache_test.sqlite3 +0 -0
- data/db/config.yml +0 -11
- data/lib/cli.rb +0 -24
- data/lib/cli/bass_guitar.rb +0 -12
- data/lib/cli/chord.rb +0 -39
- data/lib/cli/config.rb +0 -39
- data/lib/cli/errors.rb +0 -46
- data/lib/cli/guitar.rb +0 -67
- data/lib/cli/guitar_chords.rb +0 -122
- data/lib/cli/notes.rb +0 -20
- data/lib/cli/piano.rb +0 -57
- data/lib/cli/scale.rb +0 -53
- data/lib/cli/text.rb +0 -16
- data/lib/cli/ukulele.rb +0 -14
- data/lib/coltrane/cache.rb +0 -43
- data/lib/coltrane/cadence.rb +0 -7
- data/lib/coltrane/chord.rb +0 -89
- data/lib/coltrane/chord_quality.rb +0 -111
- data/lib/coltrane/chord_substitutions.rb +0 -9
- data/lib/coltrane/circle_of_fifths.rb +0 -31
- data/lib/coltrane/classic_scales.rb +0 -94
- data/lib/coltrane/diatonic_scale.rb +0 -36
- data/lib/coltrane/errors.rb +0 -95
- data/lib/coltrane/frequency.rb +0 -50
- data/lib/coltrane/frequency_interval.rb +0 -81
- data/lib/coltrane/interval.rb +0 -208
- data/lib/coltrane/interval_class.rb +0 -210
- data/lib/coltrane/interval_sequence.rb +0 -155
- data/lib/coltrane/key.rb +0 -16
- data/lib/coltrane/notable_progressions.rb +0 -28
- data/lib/coltrane/note.rb +0 -98
- data/lib/coltrane/note_set.rb +0 -89
- data/lib/coltrane/pitch.rb +0 -92
- data/lib/coltrane/pitch_class.rb +0 -148
- data/lib/coltrane/progression.rb +0 -74
- data/lib/coltrane/roman_chord.rb +0 -112
- data/lib/coltrane/scale.rb +0 -154
- data/lib/coltrane/unordered_interval_class.rb +0 -7
- data/lib/coltrane/voicing.rb +0 -39
- data/lib/coltrane_game/question.rb +0 -7
- data/lib/coltrane_instruments.rb +0 -7
- data/lib/coltrane_instruments/guitar.rb +0 -10
- data/lib/coltrane_instruments/guitar/base.rb +0 -29
- data/lib/coltrane_instruments/guitar/chord.rb +0 -170
- data/lib/coltrane_instruments/guitar/note.rb +0 -24
- data/lib/coltrane_instruments/guitar/string.rb +0 -29
- data/lib/os.rb +0 -21
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
#
|
6
|
+
# Pitch classes, and by classes here we don't mean in the sense of a ruby class
|
7
|
+
# are all the classes of pitches (frequencies) that are in a whole number of
|
8
|
+
# octaves apart.
|
9
|
+
#
|
10
|
+
# For example, C1, C2, C3 are all pitches from the C pitch class. Take a look into
|
11
|
+
# Notes description if you somehow feel this is confuse and that it could just be
|
12
|
+
# called as notes instead.
|
13
|
+
#
|
14
|
+
class PitchClass
|
15
|
+
attr_reader :integer
|
16
|
+
include Comparable
|
17
|
+
|
18
|
+
NOTATION = %w[C C# D D# E F F# G G# A A# B].freeze
|
19
|
+
|
20
|
+
def self.all_letters
|
21
|
+
%w[C D E F G A B]
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.all
|
25
|
+
NOTATION.map { |n| new(n) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(arg = nil, frequency: nil)
|
29
|
+
@integer = case arg
|
30
|
+
when String then NOTATION.index(arg)
|
31
|
+
when Frequency then frequency_to_integer(Frequency.new(arg))
|
32
|
+
when Numeric then (arg % 12)
|
33
|
+
when nil then frequency_to_integer(Frequency.new(frequency))
|
34
|
+
when PitchClass then arg.integer
|
35
|
+
else raise(WrongArgumentsError)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.[](arg, frequency: nil)
|
40
|
+
new(arg, frequency: nil)
|
41
|
+
end
|
42
|
+
|
43
|
+
def ==(other)
|
44
|
+
integer == other.integer
|
45
|
+
end
|
46
|
+
|
47
|
+
alias eql? ==
|
48
|
+
alias hash integer
|
49
|
+
|
50
|
+
def true_notation
|
51
|
+
NOTATION[integer]
|
52
|
+
end
|
53
|
+
|
54
|
+
def letter
|
55
|
+
name[0]
|
56
|
+
end
|
57
|
+
|
58
|
+
def ascending_interval_to(other)
|
59
|
+
Interval.new(self, (other.is_a?(PitchClass) ? other : Note.new(other)))
|
60
|
+
end
|
61
|
+
|
62
|
+
alias interval_to ascending_interval_to
|
63
|
+
|
64
|
+
def descending_interval_to(other)
|
65
|
+
Interval.new(
|
66
|
+
self,
|
67
|
+
(other.is_a?(PitchClass) ? other : Note.new(other)),
|
68
|
+
ascending: false
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
alias name true_notation
|
73
|
+
|
74
|
+
def pretty_name
|
75
|
+
name.tr('b', "\u266D").tr('#', "\u266F")
|
76
|
+
end
|
77
|
+
|
78
|
+
def accidental?
|
79
|
+
notation.match? /#|b/
|
80
|
+
end
|
81
|
+
|
82
|
+
def sharp?
|
83
|
+
notation.match? /#/
|
84
|
+
end
|
85
|
+
|
86
|
+
def flat?
|
87
|
+
notation.match? /b/
|
88
|
+
end
|
89
|
+
|
90
|
+
alias notation true_notation
|
91
|
+
alias to_s true_notation
|
92
|
+
|
93
|
+
def +(other)
|
94
|
+
case other
|
95
|
+
when Interval then self.class[integer + other.semitones]
|
96
|
+
when Integer then self.class[integer + other]
|
97
|
+
when PitchClass then self.class[integer + other.integer]
|
98
|
+
when Frequency then self.class.new(frequency: frequency + other)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def -(other)
|
103
|
+
case other
|
104
|
+
when Interval then self.class[integer - other.semitones]
|
105
|
+
when Integer then self.class[integer - other]
|
106
|
+
when PitchClass then Interval.new(self, other)
|
107
|
+
when Frequency then self.class.new(frequency: frequency - other)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def pitch_class
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
def <=>(other)
|
116
|
+
integer <=> other.integer
|
117
|
+
end
|
118
|
+
|
119
|
+
def fundamental_frequency
|
120
|
+
@fundamental_frequency ||=
|
121
|
+
Frequency[
|
122
|
+
Theory.base_tuning *
|
123
|
+
(2**((integer - Theory::BASE_PITCH_INTEGER.to_f) / 12))
|
124
|
+
]
|
125
|
+
end
|
126
|
+
|
127
|
+
alias frequency fundamental_frequency
|
128
|
+
|
129
|
+
def self.size
|
130
|
+
NOTATION.size
|
131
|
+
end
|
132
|
+
|
133
|
+
def size
|
134
|
+
self.class.size
|
135
|
+
end
|
136
|
+
|
137
|
+
def enharmonic?(other)
|
138
|
+
case other
|
139
|
+
when String then integer == Note[other].integer
|
140
|
+
when Note then integer == other.integer
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
private
|
145
|
+
|
146
|
+
def frequency_to_integer(f)
|
147
|
+
begin
|
148
|
+
(::BASE_PITCH_INTEGER +
|
149
|
+
size * Math.log(f.to_f / Theory.base_tuning.to_f, 2)) % size
|
150
|
+
end.round
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
# Allows the creation of chord progressions using standard notations.
|
6
|
+
# Ex: Progression.new('I-IV-V', key: 'Am')
|
7
|
+
class Progression
|
8
|
+
extend NotableProgressions
|
9
|
+
include Changes
|
10
|
+
attr_reader :scale, :chords, :notation
|
11
|
+
|
12
|
+
def self.find(*chords)
|
13
|
+
chords
|
14
|
+
.yield_self { |chords|
|
15
|
+
next chords if chords[0].is_a?(Chord)
|
16
|
+
chords.map {|c| Chord.new(name: c) }
|
17
|
+
}
|
18
|
+
.yield_self { |chords|
|
19
|
+
NoteSet[*chords.map(&:root_note)]
|
20
|
+
.yield_self { |root_notes|
|
21
|
+
Scale.having_notes(*root_notes).strict_scales
|
22
|
+
}
|
23
|
+
.reduce([]) { |memo, scale|
|
24
|
+
memo + [Progression.new(chords: chords, scale: scale)]
|
25
|
+
}
|
26
|
+
}
|
27
|
+
.yield_self { |progressions| ProgressionSet.new(*progressions) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(notation = nil, chords: nil, roman_chords: nil, key: nil, scale: nil)
|
31
|
+
if notation.nil? && chords.nil? && roman_chords.nil? || key.nil? && scale.nil?
|
32
|
+
raise WrongKeywordsError,
|
33
|
+
'[chords:, [scale: || key:]] '\
|
34
|
+
'[roman_chords:, [scale: || key:]] '\
|
35
|
+
'[notation:, [scale: || key:]] '\
|
36
|
+
end
|
37
|
+
|
38
|
+
@scale = scale || Key[key]
|
39
|
+
@chords =
|
40
|
+
if !chords.nil?
|
41
|
+
chords
|
42
|
+
elsif !roman_chords.nil?
|
43
|
+
roman_chords.map(&:chord)
|
44
|
+
elsif !notation.nil?
|
45
|
+
@notation = notation
|
46
|
+
notation.split('-').map { |c| RomanChord.new(c, scale: @scale).chord }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def interval_sequence
|
51
|
+
@interval_sequence ||= IntervalSequence(notes: @root_notes)
|
52
|
+
end
|
53
|
+
|
54
|
+
def root_notes
|
55
|
+
@root_notes ||= @chords.map(&:root_note)
|
56
|
+
end
|
57
|
+
|
58
|
+
def roman_chords
|
59
|
+
@roman_chords ||= chords.map do |c|
|
60
|
+
RomanChord.new(chord: c, scale: scale)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def notation
|
65
|
+
roman_chords.map(&:notation).join('-')
|
66
|
+
end
|
67
|
+
|
68
|
+
def notes
|
69
|
+
NoteSet[*chords.map(&:notes).map(&:notes).flatten]
|
70
|
+
end
|
71
|
+
|
72
|
+
def notes_out
|
73
|
+
notes - scale.notes
|
74
|
+
end
|
75
|
+
|
76
|
+
def notes_out_size
|
77
|
+
notes_out.size
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
class ProgressionSet
|
6
|
+
extend Forwardable
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
def_delegators :progressions, :each
|
10
|
+
|
11
|
+
attr_accessor :progressions
|
12
|
+
|
13
|
+
def initialize(*progressions)
|
14
|
+
@progressions = progressions.sort_by(&:notes_out_size)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
File without changes
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
# This class deals with chords in roman notation. Ex: IVº.
|
6
|
+
class RomanChord
|
7
|
+
DIGITS = %w[I II III IV V VI VII].freeze
|
8
|
+
NOTATION_REGEX = /(b?[ivIV]*)(.*)/
|
9
|
+
|
10
|
+
NOTATION_SUBSTITUTIONS = [
|
11
|
+
%w[º dim],
|
12
|
+
%w[o dim],
|
13
|
+
%w[ø m7b5]
|
14
|
+
].freeze
|
15
|
+
|
16
|
+
def initialize(notation = nil, chord: nil, key: nil, scale: nil)
|
17
|
+
if notation.nil? && chord.nil? || key.nil? && scale.nil?
|
18
|
+
raise WrongKeywordsError,
|
19
|
+
'[notation, [scale: || key:]] '\
|
20
|
+
'[chord:, [scale: || key:]] '\
|
21
|
+
end
|
22
|
+
@scale = scale || Key[key]
|
23
|
+
if notation
|
24
|
+
@notation = notation
|
25
|
+
elsif chord
|
26
|
+
@chord = chord.is_a?(String) ? Chord.new(name: chord) : chord
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def degree
|
31
|
+
return @scale.degree_of_note(root_note) unless @chord.nil?
|
32
|
+
d = regexed_notation[:degree]
|
33
|
+
@flats = d.count('b')
|
34
|
+
d = d.delete('b')
|
35
|
+
@degree ||= DIGITS.index(d.upcase) + 1
|
36
|
+
end
|
37
|
+
|
38
|
+
def roman_numeral
|
39
|
+
r = DIGITS[degree]
|
40
|
+
minor? ? r.downcase : r
|
41
|
+
end
|
42
|
+
|
43
|
+
def upcase?
|
44
|
+
!!(regexed_notation[:degree][0].match /[[:upper:]]/)
|
45
|
+
end
|
46
|
+
|
47
|
+
def chord
|
48
|
+
@chord ||= Chord.new root_note: root_note,
|
49
|
+
quality: quality
|
50
|
+
end
|
51
|
+
|
52
|
+
def minor?
|
53
|
+
quality.has_minor_third?
|
54
|
+
end
|
55
|
+
|
56
|
+
def major?
|
57
|
+
quality.has_major_third?
|
58
|
+
end
|
59
|
+
|
60
|
+
def quality_name
|
61
|
+
return @chord.quality.name unless @chord.nil?
|
62
|
+
q = normalize_quality_name(regexed_notation[:quality])
|
63
|
+
minor = 'm' if (!q.match? /dim|m7b5/) && !upcase?
|
64
|
+
q = [minor, q].join
|
65
|
+
q.empty? ? 'M' : q
|
66
|
+
end
|
67
|
+
|
68
|
+
def quality
|
69
|
+
return @chord.quality unless @chord.nil?
|
70
|
+
ChordQuality.new(name: quality_name) if quality_name
|
71
|
+
end
|
72
|
+
|
73
|
+
def root_note
|
74
|
+
return @chord.root_note unless @chord.nil?
|
75
|
+
@scale[degree] - @flats
|
76
|
+
end
|
77
|
+
|
78
|
+
def notation
|
79
|
+
q = case quality_name
|
80
|
+
when 'm', 'M' then ''
|
81
|
+
when 'm7', 'M' then '7'
|
82
|
+
else quality_name
|
83
|
+
end
|
84
|
+
|
85
|
+
@notation ||= [
|
86
|
+
roman_numeral,
|
87
|
+
q
|
88
|
+
].join
|
89
|
+
end
|
90
|
+
|
91
|
+
def function
|
92
|
+
return if @scale.name != 'Major'
|
93
|
+
%w[Tonic Supertonic Mediant Subdominant
|
94
|
+
Dominant Submediant Leading][degree - 1]
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def regexed_notation
|
100
|
+
@regexed_notation ||= begin
|
101
|
+
matchdata = @notation.match(NOTATION_REGEX)
|
102
|
+
{ degree: matchdata[1], quality: matchdata[2] }
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def normalize_quality_name(quality_name)
|
107
|
+
NOTATION_SUBSTITUTIONS.reduce(quality_name) do |memo, subs|
|
108
|
+
break memo if memo.empty?
|
109
|
+
memo.gsub(*subs)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
module Theory
|
5
|
+
# Musical scale creation and manipulation
|
6
|
+
class Scale
|
7
|
+
extend ClassicScales
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def_delegators :notes, :accidentals, :sharps, :flats
|
11
|
+
|
12
|
+
attr_reader :interval_sequence, :tone
|
13
|
+
|
14
|
+
def initialize(*relative_intervals, tone: 'C',
|
15
|
+
mode: 1,
|
16
|
+
name: nil,
|
17
|
+
notes: nil)
|
18
|
+
@name = name
|
19
|
+
if relative_intervals.any? && tone
|
20
|
+
@tone = Note[tone]
|
21
|
+
relative_intervals = relative_intervals.rotate(mode - 1)
|
22
|
+
@interval_sequence = IntervalSequence.new(
|
23
|
+
relative_intervals: relative_intervals
|
24
|
+
)
|
25
|
+
elsif notes
|
26
|
+
@notes = NoteSet[*notes]
|
27
|
+
@tone = @notes.first
|
28
|
+
ds = @notes.interval_sequence.relative_intervals
|
29
|
+
@interval_sequence = IntervalSequence.new(relative_intervals: ds)
|
30
|
+
else
|
31
|
+
raise WrongKeywordsError,
|
32
|
+
'[*relative_intervals, tone: "C", mode: 1] || [notes:]'
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def id
|
37
|
+
[(name || @interval_sequence), tone.number]
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(other)
|
41
|
+
id == other.id
|
42
|
+
end
|
43
|
+
|
44
|
+
def name
|
45
|
+
@name ||= begin
|
46
|
+
is = interval_sequence.relative_intervals
|
47
|
+
(0...is.size).each do |i|
|
48
|
+
if (scale_name = ::ClassicScales::SCALES.key(is.rotate(i)))
|
49
|
+
return scale_name
|
50
|
+
end
|
51
|
+
end
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def full_name
|
57
|
+
"#{tone} #{name}"
|
58
|
+
end
|
59
|
+
|
60
|
+
alias to_s full_name
|
61
|
+
|
62
|
+
def pretty_name
|
63
|
+
"#{tone.pretty_name} #{name}"
|
64
|
+
end
|
65
|
+
|
66
|
+
alias full_name pretty_name
|
67
|
+
|
68
|
+
def degree(d)
|
69
|
+
raise WrongDegreeError, d if d < 1 || d > size
|
70
|
+
tone + interval_sequence[d - 1].semitones
|
71
|
+
end
|
72
|
+
|
73
|
+
alias [] degree
|
74
|
+
|
75
|
+
def degrees
|
76
|
+
(1..size)
|
77
|
+
end
|
78
|
+
|
79
|
+
def degree_of_chord(chord)
|
80
|
+
return if chords(chord.size).map(&:name).include?(chord.name)
|
81
|
+
degree_of_note(chord.root_note)
|
82
|
+
end
|
83
|
+
|
84
|
+
def degree_of_note(note)
|
85
|
+
notes.index(note)
|
86
|
+
end
|
87
|
+
|
88
|
+
def &(other)
|
89
|
+
raise HasNoNotesError unless other.respond_to?(:notes)
|
90
|
+
notes & other
|
91
|
+
end
|
92
|
+
|
93
|
+
def include_notes?(arg)
|
94
|
+
noteset = arg.is_a?(Note) ? NoteSet[arg] : arg
|
95
|
+
(self & noteset).size == noteset.size
|
96
|
+
end
|
97
|
+
|
98
|
+
alias include? include_notes?
|
99
|
+
|
100
|
+
def notes
|
101
|
+
@notes ||= NoteSet[*degrees.map { |d| degree(d) }]
|
102
|
+
end
|
103
|
+
|
104
|
+
def interval(i)
|
105
|
+
interval_sequence[(i - 1) % size]
|
106
|
+
end
|
107
|
+
|
108
|
+
def size
|
109
|
+
interval_sequence.size
|
110
|
+
end
|
111
|
+
|
112
|
+
def tertians(n = 3)
|
113
|
+
degrees.size.times.reduce([]) do |memo, d|
|
114
|
+
ns = NoteSet[ *Array.new(n) { |i| notes[(d + (i * 2)) % size] } ]
|
115
|
+
begin
|
116
|
+
chord = Chord.new(notes: ns)
|
117
|
+
rescue ChordNotFoundError
|
118
|
+
memo
|
119
|
+
else
|
120
|
+
memo + [chord]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def triads
|
126
|
+
tertians(3)
|
127
|
+
end
|
128
|
+
|
129
|
+
def sevenths
|
130
|
+
tertians(4)
|
131
|
+
end
|
132
|
+
|
133
|
+
def pentads
|
134
|
+
tertians(5)
|
135
|
+
end
|
136
|
+
|
137
|
+
def progression(*degrees)
|
138
|
+
Progression.new(self, degrees)
|
139
|
+
end
|
140
|
+
|
141
|
+
def chords(size = 3..12)
|
142
|
+
size = (size..size) if size.is_a?(Integer)
|
143
|
+
scale_rotations = interval_sequence.inversions
|
144
|
+
ChordQuality.intervals_per_name.reduce([]) do |memo1, (qname, qintervals)|
|
145
|
+
next memo1 unless size.include?(qintervals.size)
|
146
|
+
memo1 + scale_rotations.each_with_index
|
147
|
+
.reduce([]) do |memo2, (rot, index)|
|
148
|
+
if (rot & qintervals).size == qintervals.size
|
149
|
+
memo2 + [Chord.new(root_note: degree(index + 1),
|
150
|
+
quality: ChordQuality.new(name: qname))]
|
151
|
+
else
|
152
|
+
memo2
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
alias all_chords chords
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|