coltrane 2.0.0 → 2.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 +27 -1
- data/Gemfile +1 -0
- data/Gemfile.lock +21 -3
- data/README.md +0 -7
- data/Rakefile +5 -0
- data/bin/console +2 -0
- data/bin/opal +29 -0
- data/bin/opal-build +29 -0
- data/bin/opal-mspec +29 -0
- data/bin/opal-repl +29 -0
- data/bin/rackup +12 -0
- data/bin/sprockets +12 -0
- data/bin/tilt +12 -0
- data/coltrane.gemspec +1 -1
- data/config.ru +10 -0
- data/exe/coltrane +44 -13
- data/lib/cli.rb +7 -1
- data/lib/cli/bass_guitar.rb +1 -1
- data/lib/cli/chord.rb +11 -4
- data/lib/cli/config.rb +38 -0
- data/lib/cli/guitar.rb +8 -10
- data/lib/cli/guitar_chords.rb +122 -0
- data/lib/cli/notes.rb +3 -4
- data/lib/cli/piano.rb +1 -1
- data/lib/cli/representation.rb +6 -12
- data/lib/cli/scale.rb +4 -4
- data/lib/cli/text.rb +1 -1
- data/lib/cli/ukulele.rb +1 -1
- data/lib/coltrane.rb +35 -32
- data/lib/coltrane/chord_quality.rb +4 -3
- data/lib/coltrane/circle_of_fifths.rb +29 -0
- data/lib/coltrane/classic_scales.rb +36 -43
- data/lib/coltrane/diatonic_scale.rb +34 -0
- data/lib/coltrane/frequency.rb +1 -1
- data/lib/coltrane/interval.rb +14 -0
- data/lib/coltrane/interval_class.rb +6 -0
- data/lib/coltrane/interval_sequence.rb +0 -1
- data/lib/coltrane/key.rb +14 -0
- data/lib/coltrane/note.rb +37 -2
- data/lib/coltrane/note_set.rb +23 -2
- data/lib/coltrane/pitch.rb +65 -31
- data/lib/coltrane/pitch_class.rb +25 -12
- data/lib/coltrane/progression.rb +2 -2
- data/lib/coltrane/qualities.rb +257 -0
- data/lib/coltrane/roman_chord.rb +10 -10
- data/lib/coltrane/scale.rb +9 -3
- data/lib/coltrane/version.rb +1 -1
- data/lib/coltrane/voicing.rb +38 -0
- data/lib/coltrane_game/question.rb +6 -0
- data/lib/coltrane_instruments.rb +3 -0
- data/lib/coltrane_instruments/guitar.rb +5 -2
- data/lib/coltrane_instruments/guitar/base.rb +18 -3
- data/lib/coltrane_instruments/guitar/chord.rb +139 -12
- data/lib/coltrane_instruments/guitar/note.rb +16 -0
- data/lib/coltrane_instruments/guitar/string.rb +21 -0
- data/lib/coltrane_synth.rb +7 -0
- data/lib/coltrane_synth/base.rb +47 -0
- data/lib/coltrane_synth/synth.rb +24 -0
- data/lib/core_ext.rb +25 -7
- data/lib/os.rb +19 -0
- metadata +24 -9
- data/dist/coltrane +0 -0
- data/lib/coltrane/piano_representation.rb +0 -0
@@ -3,14 +3,15 @@
|
|
3
3
|
module Coltrane
|
4
4
|
# It describe the quality of a chord, like maj7 or dim.
|
5
5
|
class ChordQuality < IntervalSequence
|
6
|
+
include Qualities
|
6
7
|
attr_reader :name
|
8
|
+
# QUALITIES_FILE = File.expand_path("#{'../' * 3}data/qualities.json", __FILE__)
|
7
9
|
|
8
10
|
private
|
9
11
|
|
10
12
|
def self.chord_trie
|
11
|
-
|
12
|
-
|
13
|
-
)
|
13
|
+
File.expand_path("#{'../' * 3}data/qualities.json", __FILE__)
|
14
|
+
trie = QUALITIES
|
14
15
|
|
15
16
|
trie.clone_values from_keys: ['Perfect Unison', 'Major Third'],
|
16
17
|
to_keys: ['Perfect Unison', 'Major Second'],
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Coltrane
|
2
|
+
class CircleOfFifths
|
3
|
+
attr_reader :notes
|
4
|
+
|
5
|
+
LETTER_SEQUENCE = %w[C G D A E B F].freeze
|
6
|
+
|
7
|
+
def initialize(start_note = Note['C'], size = Float::INFINITY)
|
8
|
+
index = letters.index(start_note.name[0])
|
9
|
+
@notes = fifths(note: start_note - Interval.perfect_fifth,
|
10
|
+
size: size,
|
11
|
+
index: (index - 1) % LETTER_SEQUENCE.size)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def fifths(note:, index: nil, notes: [], size:)
|
17
|
+
return notes if size == 0
|
18
|
+
return notes if notes.any? && note.as(letters(index)).name == notes.first.name
|
19
|
+
fifths note: note + Interval.perfect_fifth,
|
20
|
+
notes: notes + [note.as(letters(index))],
|
21
|
+
index: index + 1,
|
22
|
+
size: size - 1
|
23
|
+
end
|
24
|
+
|
25
|
+
def letters(i=nil)
|
26
|
+
i.nil? ? LETTER_SEQUENCE : LETTER_SEQUENCE[i % LETTER_SEQUENCE.size]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -4,10 +4,8 @@ module Coltrane
|
|
4
4
|
# This module deals with well known scales on music
|
5
5
|
module ClassicScales
|
6
6
|
SCALES = {
|
7
|
-
'Major' => [2, 2, 1, 2, 2, 2, 1],
|
8
7
|
'Pentatonic Major' => [2, 2, 3, 2, 3],
|
9
8
|
'Blues Major' => [2, 1, 1, 3, 2, 3],
|
10
|
-
'Natural Minor' => [2, 1, 2, 2, 1, 2, 2],
|
11
9
|
'Harmonic Minor' => [2, 1, 2, 2, 1, 3, 1],
|
12
10
|
'Hungarian Minor' => [2, 1, 2, 1, 1, 3, 1],
|
13
11
|
'Pentatonic Minor' => [3, 2, 2, 3, 2],
|
@@ -17,9 +15,15 @@ module Coltrane
|
|
17
15
|
'Chromatic' => [1] * 12
|
18
16
|
}.freeze
|
19
17
|
|
20
|
-
|
21
|
-
|
22
|
-
|
18
|
+
GREEK_MODES = %w[
|
19
|
+
Ionian
|
20
|
+
Dorian
|
21
|
+
Phrygian
|
22
|
+
Lydian
|
23
|
+
Mixolydian
|
24
|
+
Aeolian
|
25
|
+
Locrian
|
26
|
+
].freeze
|
23
27
|
|
24
28
|
# Creates factories for scales
|
25
29
|
SCALES.each do |name, distances|
|
@@ -28,62 +32,51 @@ module Coltrane
|
|
28
32
|
end
|
29
33
|
end
|
30
34
|
|
31
|
-
# Creates factories for Greek Modes
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
new(*SCALES[scale], tone: tone, mode: mode_n, name: mode_name)
|
38
|
-
end
|
35
|
+
# Creates factories for Greek Modes
|
36
|
+
GREEK_MODES.each_with_index do |mode, index|
|
37
|
+
mode_name = mode
|
38
|
+
mode_n = index + 1
|
39
|
+
define_method mode.underscore do |tone = 'C'|
|
40
|
+
new(notes: DiatonicScale.new(tone).notes.rotate(index))
|
39
41
|
end
|
40
42
|
end
|
41
43
|
|
42
|
-
|
44
|
+
# Factories for the diatonic scale
|
45
|
+
def major(note='C')
|
46
|
+
DiatonicScale.new(note)
|
47
|
+
end
|
48
|
+
|
49
|
+
def minor(note='A')
|
50
|
+
DiatonicScale.new(note, major: false)
|
51
|
+
end
|
52
|
+
|
53
|
+
alias diatonic major
|
54
|
+
alias natural_minor minor
|
43
55
|
alias pentatonic pentatonic_major
|
44
56
|
alias blues blues_major
|
45
57
|
|
46
58
|
def known_scales
|
47
|
-
SCALES.keys
|
59
|
+
SCALES.keys + ['Major', 'Natural Minor']
|
48
60
|
end
|
49
61
|
|
50
|
-
#
|
62
|
+
# List of scales appropriate for search
|
51
63
|
def standard_scales
|
52
|
-
|
64
|
+
known_scales - ['Chromatic']
|
53
65
|
end
|
54
66
|
|
55
67
|
def fetch(name, tone = nil)
|
56
68
|
Coltrane::Scale.public_send(name, tone)
|
57
69
|
end
|
58
70
|
|
59
|
-
def from_key(key)
|
60
|
-
key_regex = /([A-G][#b]?)([mM]?)/
|
61
|
-
_, note, s = *key.match(key_regex)
|
62
|
-
scale = s == 'm' ? :minor : :major
|
63
|
-
Scale.public_send(scale, note)
|
64
|
-
end
|
65
|
-
|
66
|
-
# Will output a OpenStruct like the following:
|
67
|
-
# {
|
68
|
-
# scales: [array of scales]
|
69
|
-
# results: {
|
70
|
-
# scale_name: {
|
71
|
-
# note_number => found_notes
|
72
|
-
# }
|
73
|
-
# }
|
74
|
-
# }
|
75
|
-
|
76
71
|
def having_notes(notes)
|
77
72
|
format = { scales: [], results: {} }
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
output[:results][name][tone.integer] = scale.notes & notes
|
86
|
-
end
|
73
|
+
standard_scales.each_with_object(format) do |name, output|
|
74
|
+
PitchClass.all.each.map do |tone|
|
75
|
+
scale = send(name.underscore, tone)
|
76
|
+
output[:results][name] ||= {}
|
77
|
+
next if output[:results][name].key?(tone.integer)
|
78
|
+
output[:scales] << scale if scale.include?(notes)
|
79
|
+
output[:results][name][tone.integer] = scale.notes & notes
|
87
80
|
end
|
88
81
|
end
|
89
82
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Coltrane
|
2
|
+
class DiatonicScale < Scale
|
3
|
+
def initialize(tone, major: true)
|
4
|
+
@major = major
|
5
|
+
tone = Note[tone]
|
6
|
+
notes = CircleOfFifths.new(tone - (major? ? 0 : 9), 7).notes.sort
|
7
|
+
super notes: notes.rotate(notes.index(tone))
|
8
|
+
end
|
9
|
+
|
10
|
+
def name
|
11
|
+
major? ? 'Major' : 'Natural Minor'
|
12
|
+
end
|
13
|
+
|
14
|
+
def relative_minor
|
15
|
+
minor? ? self : self.class.new(@tone + 9, major: false)
|
16
|
+
end
|
17
|
+
|
18
|
+
def relative_major
|
19
|
+
major? ? self : self.class.new(@tone - 9, major: true)
|
20
|
+
end
|
21
|
+
|
22
|
+
def relative
|
23
|
+
major? ? relative_minor : relative_major
|
24
|
+
end
|
25
|
+
|
26
|
+
def major?
|
27
|
+
!!@major
|
28
|
+
end
|
29
|
+
|
30
|
+
def minor?
|
31
|
+
!@major
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/coltrane/frequency.rb
CHANGED
data/lib/coltrane/interval.rb
CHANGED
@@ -4,10 +4,16 @@ module Coltrane
|
|
4
4
|
# Interval describe the logarithmic distance between 2 frequencies.
|
5
5
|
# It's measured in cents.
|
6
6
|
class Interval
|
7
|
+
include Comparable
|
8
|
+
|
7
9
|
attr_reader :cents
|
8
10
|
|
9
11
|
class << self
|
10
12
|
alias [] new
|
13
|
+
|
14
|
+
def method_missing(method, *args)
|
15
|
+
IntervalClass.send(method, *args)
|
16
|
+
end
|
11
17
|
end
|
12
18
|
|
13
19
|
def initialize(cents)
|
@@ -46,5 +52,13 @@ module Coltrane
|
|
46
52
|
when Interval then Interval[cents - other.cents]
|
47
53
|
end
|
48
54
|
end
|
55
|
+
|
56
|
+
def -@
|
57
|
+
Interval[-cents]
|
58
|
+
end
|
59
|
+
|
60
|
+
def <=>(other)
|
61
|
+
cents <=> other.cents
|
62
|
+
end
|
49
63
|
end
|
50
64
|
end
|
@@ -19,6 +19,8 @@ module Coltrane
|
|
19
19
|
"#{q.interval_quality} #{n.to_i.interval_name}"
|
20
20
|
end
|
21
21
|
|
22
|
+
def self.method_missing; end
|
23
|
+
|
22
24
|
# Create full names and methods such as major_third? minor_seventh?
|
23
25
|
# TODO: It's a mess and it really needs a refactor someday
|
24
26
|
NAMES = INTERVALS.each_with_index.each_with_object({}) do |(interval, index), memo|
|
@@ -94,6 +96,10 @@ module Coltrane
|
|
94
96
|
IntervalClass[semitones - other]
|
95
97
|
end
|
96
98
|
|
99
|
+
def -@
|
100
|
+
IntervalClass[-semitones]
|
101
|
+
end
|
102
|
+
|
97
103
|
private
|
98
104
|
|
99
105
|
def self.interval_by_full_name(arg)
|
data/lib/coltrane/key.rb
ADDED
data/lib/coltrane/note.rb
CHANGED
@@ -30,9 +30,9 @@ module Coltrane
|
|
30
30
|
|
31
31
|
chars = note_name.chars
|
32
32
|
letter = chars.shift
|
33
|
-
raise
|
33
|
+
raise InvalidNoteLetterError, arg unless ('A'..'G').cover?(letter)
|
34
34
|
@alteration = chars.reduce(0) do |alt, symbol|
|
35
|
-
raise
|
35
|
+
raise InvalidNoteLetterError, arg unless ALTERATIONS.include?(symbol)
|
36
36
|
alt + ALTERATIONS[symbol]
|
37
37
|
end
|
38
38
|
super((PitchClass[letter].integer + @alteration) % PitchClass.size)
|
@@ -58,12 +58,47 @@ module Coltrane
|
|
58
58
|
Note.new(name).tap { |n| n.alteration = x }
|
59
59
|
end
|
60
60
|
|
61
|
+
def sharp?
|
62
|
+
alteration == 1
|
63
|
+
end
|
64
|
+
|
65
|
+
def double_sharp?
|
66
|
+
alteration == 2
|
67
|
+
end
|
68
|
+
|
69
|
+
def flat?
|
70
|
+
alteration == -1
|
71
|
+
end
|
72
|
+
|
73
|
+
def double_flat?
|
74
|
+
alteration == -2
|
75
|
+
end
|
76
|
+
|
77
|
+
def natural?
|
78
|
+
alteration == 0
|
79
|
+
end
|
80
|
+
|
61
81
|
def accidents
|
62
82
|
(@alteration > 0 ? '#' : 'b') * alteration.abs
|
63
83
|
end
|
64
84
|
|
85
|
+
def -(other)
|
86
|
+
result = super(other)
|
87
|
+
result.is_a?(Note) ? result.alter(alteration) : result
|
88
|
+
end
|
89
|
+
|
90
|
+
def +(other)
|
91
|
+
result = super(other)
|
92
|
+
result.is_a?(Note) ? result.alter(alteration) : result
|
93
|
+
end
|
94
|
+
|
65
95
|
def interval_to(note_name)
|
66
96
|
Note[note_name] - self
|
67
97
|
end
|
98
|
+
|
99
|
+
def as(letter)
|
100
|
+
a = (self - Note[letter])
|
101
|
+
alter([a.semitones, -((-a).semitones)].min_by(&:abs))
|
102
|
+
end
|
68
103
|
end
|
69
104
|
end
|
data/lib/coltrane/note_set.rb
CHANGED
@@ -6,7 +6,8 @@ module Coltrane
|
|
6
6
|
extend Forwardable
|
7
7
|
|
8
8
|
def_delegators :@notes, :first, :each, :size, :map, :reduce, :index,
|
9
|
-
:[], :index, :empty?, :permutation, :include?,
|
9
|
+
:[], :index, :empty?, :permutation, :include?, :<<, :any?,
|
10
|
+
:count, :rotate
|
10
11
|
|
11
12
|
attr_reader :notes
|
12
13
|
|
@@ -21,7 +22,7 @@ module Coltrane
|
|
21
22
|
@notes =
|
22
23
|
case arg
|
23
24
|
when NoteSet then arg.notes
|
24
|
-
when Array then arg.map { |n| n.is_a?(
|
25
|
+
when Array then arg.compact.map { |n| n.is_a?(PitchClass) ? n : Note[n] }.uniq
|
25
26
|
else raise InvalidNotesError, arg
|
26
27
|
end
|
27
28
|
end
|
@@ -61,6 +62,26 @@ module Coltrane
|
|
61
62
|
map(&:integer)
|
62
63
|
end
|
63
64
|
|
65
|
+
def accidentals
|
66
|
+
count(&:accidental?)
|
67
|
+
end
|
68
|
+
|
69
|
+
def sharps
|
70
|
+
count(&:sharp?)
|
71
|
+
end
|
72
|
+
|
73
|
+
def flats
|
74
|
+
count(&:flat?)
|
75
|
+
end
|
76
|
+
|
77
|
+
def alter(alteration)
|
78
|
+
NoteSet[*map {|n| n.alter(alteration)}]
|
79
|
+
end
|
80
|
+
|
81
|
+
def alter_accidentals(alteration)
|
82
|
+
NoteSet[*map {|n| n.alter(alteration) if n.accidental?}]
|
83
|
+
end
|
84
|
+
|
64
85
|
def interval_sequence
|
65
86
|
IntervalSequence.new(notes: self)
|
66
87
|
end
|
data/lib/coltrane/pitch.rb
CHANGED
@@ -3,56 +3,90 @@
|
|
3
3
|
module Coltrane
|
4
4
|
# It describes a pitch, like E4 or Bb5. It's like a note, but it has an octave
|
5
5
|
class Pitch
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :integer
|
7
7
|
|
8
8
|
def initialize(notation_arg = nil,
|
9
|
-
|
9
|
+
note: nil,
|
10
10
|
octave: nil,
|
11
11
|
notation: nil,
|
12
12
|
frequency: nil)
|
13
13
|
|
14
|
-
@
|
15
|
-
if notation_arg || notation
|
16
|
-
|
17
|
-
elsif
|
18
|
-
[
|
14
|
+
@integer = begin
|
15
|
+
if (n = notation_arg || notation)
|
16
|
+
n.is_a?(Integer) ? n : integer_from_notation(n)
|
17
|
+
elsif note && octave
|
18
|
+
integer_from_note_and_octave(Note[note], octave)
|
19
19
|
elsif frequency
|
20
|
-
|
20
|
+
integer_from_frequency(frequency)
|
21
21
|
else
|
22
|
-
raise
|
22
|
+
raise(InvalidArgumentsError)
|
23
23
|
end
|
24
|
+
end
|
24
25
|
end
|
25
26
|
|
26
|
-
|
27
|
+
def self.[](*args)
|
28
|
+
new *args
|
29
|
+
end
|
30
|
+
|
31
|
+
def scientific_notation
|
32
|
+
"#{pitch_class}#{octave}"
|
33
|
+
end
|
34
|
+
|
35
|
+
def pitch_class
|
36
|
+
PitchClass[integer]
|
37
|
+
end
|
38
|
+
|
39
|
+
def octave
|
40
|
+
(integer / 12) - 1
|
41
|
+
end
|
42
|
+
|
43
|
+
alias notation scientific_notation
|
44
|
+
alias name scientific_notation
|
45
|
+
alias to_s scientific_notation
|
46
|
+
|
47
|
+
alias hash integer
|
48
|
+
alias midi integer
|
27
49
|
|
28
|
-
def
|
29
|
-
|
30
|
-
[PitchClass.new(pitch_class_notation), octaves.to_f]
|
50
|
+
def ==(other)
|
51
|
+
integer == other.integer
|
31
52
|
end
|
32
53
|
|
33
|
-
def
|
34
|
-
|
54
|
+
def frequency
|
55
|
+
pitch_class.frequency.octave(octave)
|
35
56
|
end
|
36
57
|
|
37
|
-
|
38
|
-
|
39
|
-
# end
|
58
|
+
alias eql? ==
|
59
|
+
alias eq ==
|
40
60
|
|
41
|
-
|
42
|
-
|
43
|
-
|
61
|
+
def +(other)
|
62
|
+
case other
|
63
|
+
when Integer then Pitch[integer + other]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def -(other)
|
68
|
+
case other
|
69
|
+
when Integer then Pitch[integer - other]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
44
74
|
|
45
|
-
|
46
|
-
|
47
|
-
|
75
|
+
def integer_from_notation(the_notation)
|
76
|
+
_, n, o = *the_notation.match(/(\D+)(\d+)/)
|
77
|
+
integer_from_note_and_octave(Note[n], o)
|
78
|
+
end
|
48
79
|
|
49
|
-
|
50
|
-
|
51
|
-
|
80
|
+
def integer_from_note_and_octave(p, o)
|
81
|
+
12 * (o.to_i + 1) + p.integer
|
82
|
+
end
|
52
83
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
84
|
+
def integer_from_frequency(f)
|
85
|
+
octave_from_frequency(f) * 12 + PitchClass.new(frequency: f).integer
|
86
|
+
end
|
87
|
+
|
88
|
+
def octave_from_frequency(f)
|
89
|
+
Math.log(f.to_f / PitchClass['C'].frequency.to_f, 2).ceil
|
90
|
+
end
|
57
91
|
end
|
58
92
|
end
|