coltrane 1.2.4 → 2.0.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 +14 -1
- data/Gemfile +3 -0
- data/Gemfile.lock +48 -3
- data/Rakefile +1 -1
- data/bin/erubis +12 -0
- data/bin/flay +29 -0
- data/bin/gitlab +29 -0
- data/bin/httparty +29 -0
- data/bin/pronto +29 -0
- data/bin/ruby_parse +29 -0
- data/bin/ruby_parse_extract_error +29 -0
- data/bin/thor +12 -0
- data/exe/coltrane +8 -6
- data/lib/cli/guitar.rb +7 -7
- data/lib/cli/representation.rb +1 -1
- data/lib/coltrane.rb +22 -1
- data/lib/coltrane/cadence.rb +0 -1
- data/lib/coltrane/changes.rb +5 -7
- data/lib/coltrane/chord.rb +7 -7
- data/lib/coltrane/chord_quality.rb +17 -17
- data/lib/coltrane/chord_substitutions.rb +3 -1
- data/lib/coltrane/classic_scales.rb +7 -7
- data/lib/coltrane/errors.rb +26 -1
- data/lib/coltrane/frequency.rb +50 -0
- data/lib/coltrane/interval.rb +23 -86
- data/lib/coltrane/interval_class.rb +106 -0
- data/lib/coltrane/interval_sequence.rb +14 -13
- data/lib/coltrane/notable_progressions.rb +8 -3
- data/lib/coltrane/note.rb +44 -73
- data/lib/coltrane/note_set.rb +4 -4
- data/lib/coltrane/pitch.rb +43 -22
- data/lib/coltrane/pitch_class.rb +113 -0
- data/lib/coltrane/progression.rb +6 -9
- data/lib/coltrane/roman_chord.rb +14 -14
- data/lib/coltrane/scale.rb +8 -10
- data/lib/coltrane/unordered_interval_class.rb +7 -0
- data/lib/coltrane/version.rb +1 -1
- data/lib/coltrane_instruments.rb +4 -0
- data/lib/coltrane_instruments/guitar.rb +7 -0
- data/lib/coltrane_instruments/guitar/base.rb +14 -0
- data/lib/coltrane_instruments/guitar/chord.rb +41 -0
- data/lib/coltrane_instruments/guitar/note.rb +8 -0
- data/lib/coltrane_instruments/guitar/string.rb +8 -0
- data/lib/core_ext.rb +16 -27
- metadata +18 -2
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Coltrane
|
4
|
+
#
|
5
|
+
# Pitch classes, and by classes here we don't mean in the sense of a ruby class
|
6
|
+
# are all the classes of pitches (frequencies) that are in a whole number of
|
7
|
+
# octaves apart.
|
8
|
+
#
|
9
|
+
# For example, C1, C2, C3 are all pitches from the C pitch class. Take a look into
|
10
|
+
# Notes description if you somehow feel this is confuse and that it could just be
|
11
|
+
# called as notes instead.
|
12
|
+
#
|
13
|
+
class PitchClass
|
14
|
+
attr_reader :integer
|
15
|
+
|
16
|
+
NOTATION = %w[C C# D D# E F F# G G# A A# B].freeze
|
17
|
+
|
18
|
+
def self.all
|
19
|
+
NOTATION.map { |n| new(n) }
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize(arg, frequency: nil)
|
23
|
+
@integer = case arg
|
24
|
+
when String then NOTATION.index(arg)
|
25
|
+
when Frequency, Float then frequency_to_integer(Frequency.new(arg))
|
26
|
+
when Integer then (arg % 12)
|
27
|
+
when NilClass then frequency_to_integer(Frequency.new(frequency))
|
28
|
+
when PitchClass then arg.integer
|
29
|
+
else raise(InvalidArgumentError)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.[](arg, frequency: nil)
|
34
|
+
new(arg, frequency: nil)
|
35
|
+
end
|
36
|
+
|
37
|
+
def ==(other)
|
38
|
+
integer == other.integer
|
39
|
+
end
|
40
|
+
|
41
|
+
alias eql? ==
|
42
|
+
alias hash integer
|
43
|
+
|
44
|
+
def true_notation
|
45
|
+
NOTATION[integer]
|
46
|
+
end
|
47
|
+
|
48
|
+
alias name true_notation
|
49
|
+
|
50
|
+
def pretty_name
|
51
|
+
name.tr('b', "\u266D").tr('#', "\u266F")
|
52
|
+
end
|
53
|
+
|
54
|
+
def accidental?
|
55
|
+
notation.match? /#|b/
|
56
|
+
end
|
57
|
+
|
58
|
+
alias notation true_notation
|
59
|
+
alias to_s true_notation
|
60
|
+
|
61
|
+
def +(other)
|
62
|
+
case other
|
63
|
+
when Interval then PitchClass[integer + other.semitones]
|
64
|
+
when Integer then PitchClass[integer + other]
|
65
|
+
when PitchClass then Note.new(integer + other.integer)
|
66
|
+
when Frequency then PitchClass.new(frequency: frequency + other)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def -(other)
|
71
|
+
case other
|
72
|
+
when Interval then PitchClass[integer - other.semitones]
|
73
|
+
when Integer then PitchClass[integer - other]
|
74
|
+
when PitchClass then IntervalClass[frequency / other.frequency]
|
75
|
+
when Frequency then PitchClass.new(frequency: frequency - other)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def fundamental_frequency
|
80
|
+
@fundamental_frequency ||=
|
81
|
+
Frequency[
|
82
|
+
Coltrane.base_tuning *
|
83
|
+
(2**((integer - Coltrane::BASE_PITCH_INTEGER.to_f) / 12))
|
84
|
+
]
|
85
|
+
end
|
86
|
+
|
87
|
+
alias frequency fundamental_frequency
|
88
|
+
|
89
|
+
def self.size
|
90
|
+
NOTATION.size
|
91
|
+
end
|
92
|
+
|
93
|
+
def size
|
94
|
+
self.class.size
|
95
|
+
end
|
96
|
+
|
97
|
+
def enharmonic?(other)
|
98
|
+
case other
|
99
|
+
when String then integer == Note[other].integer
|
100
|
+
when Note then integer == other.integer
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def frequency_to_integer(f)
|
107
|
+
begin
|
108
|
+
(Coltrane::BASE_PITCH_INTEGER +
|
109
|
+
size * Math.log(f.to_f / Coltrane.base_tuning.to_f, 2)) % size
|
110
|
+
end.round
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
data/lib/coltrane/progression.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Coltrane
|
4
|
-
|
5
4
|
# Allows the creation of chord progressions using standard notations.
|
6
5
|
# Ex: Progression.new('I-IV-V', key: 'Am')
|
7
6
|
class Progression
|
@@ -10,9 +9,7 @@ module Coltrane
|
|
10
9
|
attr_reader :scale, :chords, :notation
|
11
10
|
|
12
11
|
def self.find(*chords)
|
13
|
-
if chords[0].is_a?(String)
|
14
|
-
chords.map! { |c| Chord.new(name: c) }
|
15
|
-
end
|
12
|
+
chords.map! { |c| Chord.new(name: c) } if chords[0].is_a?(String)
|
16
13
|
|
17
14
|
root_notes = NoteSet[*chords.map(&:root_note)]
|
18
15
|
|
@@ -24,12 +21,12 @@ module Coltrane
|
|
24
21
|
progressions.sort_by(&:notes_out_size)
|
25
22
|
end
|
26
23
|
|
27
|
-
def initialize(notation=nil, chords: nil, roman_chords: nil, key: nil, scale: nil)
|
24
|
+
def initialize(notation = nil, chords: nil, roman_chords: nil, key: nil, scale: nil)
|
28
25
|
if notation.nil? && chords.nil? && roman_chords.nil? || key.nil? && scale.nil?
|
29
26
|
raise WrongKeywordsError,
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
'[chords:, [scale: || key:]] '\
|
28
|
+
'[roman_chords:, [scale: || key:]] '\
|
29
|
+
'[notation:, [scale: || key:]] '\
|
33
30
|
end
|
34
31
|
|
35
32
|
@scale = scale || Scale.from_key(key)
|
@@ -40,7 +37,7 @@ module Coltrane
|
|
40
37
|
roman_chords.map(&:chord)
|
41
38
|
elsif !notation.nil?
|
42
39
|
@notation = notation
|
43
|
-
notation.split('-').map {|c| RomanChord.new(c, scale: @scale).chord }
|
40
|
+
notation.split('-').map { |c| RomanChord.new(c, scale: @scale).chord }
|
44
41
|
end
|
45
42
|
end
|
46
43
|
|
data/lib/coltrane/roman_chord.rb
CHANGED
@@ -4,27 +4,27 @@ module Coltrane
|
|
4
4
|
# This class deals with chords in roman notation. Ex: IVº.
|
5
5
|
class RomanChord
|
6
6
|
DIGITS = %w[I II III IV V VI VII].freeze
|
7
|
-
NOTATION_REGEX =
|
7
|
+
NOTATION_REGEX = /
|
8
8
|
(?<degree>b?[ivIV]*)
|
9
9
|
(?<quality>.*)
|
10
|
-
|
10
|
+
/x
|
11
11
|
|
12
12
|
NOTATION_SUBSTITUTIONS = [
|
13
13
|
%w[º dim],
|
14
14
|
%w[o dim],
|
15
15
|
%w[ø m7b5]
|
16
|
-
]
|
16
|
+
].freeze
|
17
17
|
|
18
|
-
def initialize(notation=nil, chord: nil, key: nil, scale: nil)
|
18
|
+
def initialize(notation = nil, chord: nil, key: nil, scale: nil)
|
19
19
|
if notation.nil? && chord.nil? || key.nil? && scale.nil?
|
20
20
|
raise WrongKeywordsError,
|
21
|
-
|
22
|
-
|
21
|
+
'[notation, [scale: || key:]] '\
|
22
|
+
'[chord:, [scale: || key:]] '\
|
23
23
|
end
|
24
24
|
@scale = scale || Scale.from_key(key)
|
25
|
-
if
|
25
|
+
if notation
|
26
26
|
@notation = notation
|
27
|
-
elsif
|
27
|
+
elsif chord
|
28
28
|
@chord = chord.is_a?(String) ? Chord.new(name: chord) : chord
|
29
29
|
end
|
30
30
|
end
|
@@ -62,7 +62,7 @@ module Coltrane
|
|
62
62
|
def quality_name
|
63
63
|
return @chord.quality.name unless @chord.nil?
|
64
64
|
q = normalize_quality_name(regexed_notation['quality'])
|
65
|
-
minor = 'm' if !q.match?(
|
65
|
+
minor = 'm' if !q.match?(/dim|m7b5/) && !upcase?
|
66
66
|
q = [minor, q].join
|
67
67
|
q.empty? ? 'M' : q
|
68
68
|
end
|
@@ -79,21 +79,21 @@ module Coltrane
|
|
79
79
|
|
80
80
|
def notation
|
81
81
|
q = case quality_name
|
82
|
-
|
83
|
-
|
84
|
-
|
82
|
+
when 'm', 'M' then ''
|
83
|
+
when 'm7', 'M' then '7'
|
84
|
+
else quality_name
|
85
85
|
end
|
86
86
|
|
87
87
|
@notation ||= [
|
88
88
|
roman_numeral,
|
89
|
-
q
|
89
|
+
q
|
90
90
|
].join
|
91
91
|
end
|
92
92
|
|
93
93
|
def function
|
94
94
|
return if @scale.name != 'Major'
|
95
95
|
%w[Tonic Supertonic Mediant Subdominant
|
96
|
-
|
96
|
+
Dominant Submediant Leading][degree - 1]
|
97
97
|
end
|
98
98
|
|
99
99
|
private
|
data/lib/coltrane/scale.rb
CHANGED
@@ -8,11 +8,11 @@ module Coltrane
|
|
8
8
|
|
9
9
|
def initialize(*distances, tone: 'C', mode: 1, name: nil, notes: nil)
|
10
10
|
@name = name
|
11
|
-
if
|
11
|
+
if distances.any? && tone
|
12
12
|
@tone = Note[tone]
|
13
13
|
distances = distances.rotate(mode - 1)
|
14
14
|
@interval_sequence = IntervalSequence.new(distances: distances)
|
15
|
-
elsif
|
15
|
+
elsif notes
|
16
16
|
ds = NoteSet[*notes].interval_sequence.distances
|
17
17
|
new(*ds, tone: notes.first)
|
18
18
|
else
|
@@ -119,25 +119,23 @@ module Coltrane
|
|
119
119
|
Progression.new(self, degrees)
|
120
120
|
end
|
121
121
|
|
122
|
-
def all_chords
|
123
|
-
chords
|
124
|
-
end
|
125
|
-
|
126
122
|
def chords(size = 3..12)
|
127
123
|
size = (size..size) if size.is_a?(Integer)
|
128
|
-
included_names = []
|
129
124
|
scale_rotations = interval_sequence.inversions
|
130
125
|
ChordQuality.intervals_per_name.reduce([]) do |memo1, (qname, qintervals)|
|
131
126
|
next memo1 unless size.include?(qintervals.size)
|
132
|
-
memo1 + scale_rotations.each_with_index
|
127
|
+
memo1 + scale_rotations.each_with_index
|
128
|
+
.reduce([]) do |memo2, (rot, index)|
|
133
129
|
if (rot & qintervals).size == qintervals.size
|
134
|
-
memo2 + [
|
135
|
-
|
130
|
+
memo2 + [Chord.new(root_note: degree(index + 1),
|
131
|
+
quality: ChordQuality.new(name: qname))]
|
136
132
|
else
|
137
133
|
memo2
|
138
134
|
end
|
139
135
|
end
|
140
136
|
end
|
141
137
|
end
|
138
|
+
|
139
|
+
alias all_chords chords
|
142
140
|
end
|
143
141
|
end
|
data/lib/coltrane/version.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ColtraneInstruments
|
4
|
+
module Guitar
|
5
|
+
# A base class for operations involving Guitars
|
6
|
+
class Base
|
7
|
+
def initialize; end
|
8
|
+
|
9
|
+
def find_chord(target_chord)
|
10
|
+
Chord.new(target_chord, guitar: self).fetch_descendant_chords
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ColtraneInstruments
|
4
|
+
module Guitar
|
5
|
+
# This class represents a group of guitar notes, strummed at the same time
|
6
|
+
class Chord
|
7
|
+
def initialize(target_chord, guitar_notes: [],
|
8
|
+
free_fingers: 4,
|
9
|
+
barre: nil,
|
10
|
+
guitar:)
|
11
|
+
@target_chord = target_chord
|
12
|
+
@guitar_notes = guitar_notes
|
13
|
+
@free_fingers = free_fingers
|
14
|
+
@barre = barre
|
15
|
+
end
|
16
|
+
|
17
|
+
def barre?
|
18
|
+
!@barre.nil?
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_descendant_chords
|
22
|
+
return self if guitar_notes.size < guitar.strings
|
23
|
+
possible_new_notes.reduce([]) do |memo, n|
|
24
|
+
fingers_change = n.fret == @barre ? 0 : 1
|
25
|
+
return self unless (@free_fingers - fingers_change).negative?
|
26
|
+
guitar.create_chord target_chord,
|
27
|
+
guitar_notes: @guitar_notes + n,
|
28
|
+
free_fingers: @free_fingers - fingers_change,
|
29
|
+
barre: @barre
|
30
|
+
.fetch_descendant_chords + memo
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def possible_new_notes; end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_writer :barre
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/core_ext.rb
CHANGED
@@ -24,6 +24,7 @@ class String
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
+
# Here we add some syntax sugar to make the code more understandable later
|
27
28
|
class Integer
|
28
29
|
def interval_name
|
29
30
|
{
|
@@ -46,12 +47,17 @@ class Integer
|
|
46
47
|
end
|
47
48
|
end
|
48
49
|
|
50
|
+
# Here we add some methods better work with Tries
|
49
51
|
class Hash
|
50
|
-
def clone_values(from_keys: nil,
|
51
|
-
|
52
|
+
def clone_values(from_keys: nil,
|
53
|
+
to_keys: nil,
|
54
|
+
suffix: nil,
|
55
|
+
branch_a: nil,
|
56
|
+
branch_b: nil)
|
57
|
+
branch_a ||= dig(*from_keys)
|
52
58
|
if branch_b.nil?
|
53
|
-
|
54
|
-
branch_b =
|
59
|
+
create_branch!(*to_keys)
|
60
|
+
branch_b = dig(*to_keys)
|
55
61
|
end
|
56
62
|
|
57
63
|
branch_a.each do |key, val|
|
@@ -73,30 +79,13 @@ class Hash
|
|
73
79
|
|
74
80
|
def deep_dup
|
75
81
|
dup_hash = {}
|
76
|
-
|
77
|
-
if v.is_a?(Hash)
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
+
each do |k, v|
|
83
|
+
dup_hash[k] = if v.is_a?(Hash)
|
84
|
+
v.deep_dup
|
85
|
+
else
|
86
|
+
v.dup
|
87
|
+
end
|
82
88
|
end
|
83
89
|
dup_hash
|
84
90
|
end
|
85
91
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|