coltrane 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +15 -10
  5. data/Gemfile.lock +47 -128
  6. data/README.md +30 -8
  7. data/Rakefile +0 -4
  8. data/bin/bundle +105 -0
  9. data/bin/byebug +29 -0
  10. data/bin/coderay +12 -0
  11. data/bin/coltrane +25 -7
  12. data/bin/htmldiff +12 -0
  13. data/bin/kill-pry-rescue +12 -0
  14. data/bin/ldiff +12 -0
  15. data/bin/pry +12 -0
  16. data/bin/rake +12 -0
  17. data/bin/rescue +12 -0
  18. data/bin/rspec +12 -0
  19. data/bin/rubocop +12 -0
  20. data/bin/ruby-parse +12 -0
  21. data/bin/ruby-rewrite +12 -0
  22. data/exe/coltrane +173 -0
  23. data/lib/cli/bass_guitar.rb +9 -0
  24. data/lib/cli/chord.rb +24 -0
  25. data/lib/cli/errors.rb +28 -0
  26. data/lib/cli/guitar.rb +55 -0
  27. data/lib/cli/notes.rb +18 -0
  28. data/lib/cli/piano.rb +54 -0
  29. data/lib/cli/representation.rb +47 -0
  30. data/lib/cli/scale.rb +48 -0
  31. data/lib/cli/text.rb +13 -0
  32. data/lib/cli/ukulele.rb +11 -0
  33. data/lib/coltrane-cli.rb +12 -0
  34. data/lib/coltrane.rb +16 -31
  35. data/lib/coltrane/cache.rb +34 -0
  36. data/lib/coltrane/chord.rb +28 -41
  37. data/lib/coltrane/chord_quality.rb +14 -39
  38. data/lib/coltrane/classic_progressions.rb +37 -0
  39. data/lib/coltrane/classic_scales.rb +92 -118
  40. data/lib/coltrane/errors.rb +54 -0
  41. data/lib/coltrane/interval.rb +25 -17
  42. data/lib/coltrane/interval_sequence.rb +57 -27
  43. data/lib/coltrane/note.rb +44 -30
  44. data/lib/coltrane/note_set.rb +37 -22
  45. data/lib/coltrane/piano_representation.rb +0 -58
  46. data/lib/coltrane/pitch.rb +12 -3
  47. data/lib/coltrane/progression.rb +31 -0
  48. data/lib/coltrane/qualities.rb +115 -114
  49. data/lib/coltrane/roman_chord.rb +36 -0
  50. data/lib/coltrane/scale.rb +85 -77
  51. data/lib/coltrane/version.rb +1 -1
  52. data/lib/core_ext.rb +12 -0
  53. data/pkg/coltrane-0.0.2.gem +0 -0
  54. metadata +27 -14
  55. data/lib/coltrane/essential_guitar_chords.rb +0 -82
  56. data/lib/coltrane/fret_set.rb +0 -0
  57. data/lib/coltrane/guitar.rb +0 -15
  58. data/lib/coltrane/guitar_chord.rb +0 -50
  59. data/lib/coltrane/guitar_chord_finder.rb +0 -98
  60. data/lib/coltrane/guitar_note.rb +0 -50
  61. data/lib/coltrane/guitar_note_set.rb +0 -61
  62. data/lib/coltrane/guitar_representation.rb +0 -96
  63. data/lib/coltrane/guitar_string.rb +0 -52
  64. 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(interval_sequence)
10
- if interval_sequence.class != IntervalSequence
11
- interval_sequence = IntervalSequence.new(interval_sequence)
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
- require 'ostruct'
2
-
3
- module ClassicScales
4
-
5
- SCALES = {
6
- 'Major' => [2,2,1,2,2,2,1],
7
- 'Natural Minor' => [2,1,2,2,1,2,2],
8
- 'Harmonic Minor' => [2,1,2,2,1,3,1],
9
- 'Hungarian Minor' => [2,1,2,1,1,3,1],
10
- 'Pentatonic Major' => [2,2,3,2,3],
11
- 'Pentatonic Minor' => [3,2,2,3,2],
12
- 'Blues Major' => [2,1,1,3,2,3],
13
- 'Blues Minor' => [3,2,1,1,3,2],
14
- 'Whole Tone' => [2,2,2,2,2,2]
15
- }
16
-
17
- def major(tone='C', mode=1)
18
- new(*SCALES['Major'], tone: tone, mode: mode)
19
- end
20
-
21
- def natural_minor(tone='C', mode=1)
22
- new(*SCALES['Natural Minor'], tone: tone, mode: mode)
23
- end
24
-
25
- def harmonic_minor(tone='C', mode=1)
26
- new(*SCALES['Harmonic Minor'], tone: tone, mode: mode)
27
- end
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
- def chromatic(tone='C', mode=1)
54
- new(*([1]*12), tone: tone, mode: mode)
55
- end
29
+ public
56
30
 
57
- def ionian(tone='C')
58
- new(*SCALES['Major'], tone: tone, mode: 1)
59
- end
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
- def dorian(tone='C')
62
- new(*SCALES['Major'], tone: tone, mode: 2)
63
- end
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
- def phrygian(tone='C')
66
- new(*SCALES['Major'], tone: tone, mode: 3)
67
- end
50
+ alias_method :minor, :natural_minor
51
+ alias_method :pentatonic, :pentatonic_major
52
+ alias_method :blues, :blues_major
68
53
 
69
- def lydian(tone='C')
70
- new(*SCALES['Major'], tone: tone, mode: 4)
71
- end
54
+ def known_scales
55
+ SCALES.keys
56
+ end
72
57
 
73
- def mixolydian(tone='C')
74
- new(*SCALES['Major'], tone: tone, mode: 5)
75
- end
58
+ def fetch(name, tone=nil)
59
+ Coltrane::Scale.public_send(name, tone)
60
+ end
76
61
 
77
- def aeolian(tone='C')
78
- new(*SCALES['Major'], tone: tone, mode: 6)
79
- end
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
- def having_chord(chord)
86
- puts "\nSearching #{chord} in scales:\n\n"
87
- scale_name_size = SCALES.keys.map(&:size).max
88
- chord = Chord.new(chord)
89
- SCALES.each_with_object([]) do |scale_obj, results|
90
- scale_name, intervals = scale_obj
91
- print Paint[scale_name.ljust(scale_name_size), 'yellow']
92
- Note.all.each do |note|
93
- found_sth = false
94
- print ' '
95
- scale = Scale.new(*intervals, tone: note.name)
96
- degree = scale.degree_of_chord(chord)
97
- unless degree.nil?
98
- found_sth = true
99
- results << OpenStruct.new({ name: "#{note.name} #{scale_name}",
100
- degree: degree,
101
- scale: scale})
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
- print Paint[note.name, found_sth ? 'yellow' : 'black']
104
- end
105
- print "\n"
95
+ )
106
96
  end
107
- end
108
97
 
109
- def having_notes(*notes)
110
- puts "\nSearching #{notes.join(', ')} in scales:\n\n"
111
- scale_name_size = SCALES.keys.map(&:size).max
112
- notes = notes.map { |n| Note.new(n) }
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
- print "\n"
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
@@ -1,33 +1,41 @@
1
1
  module Coltrane
2
2
  # It describes a interval between 2 pitches
3
3
  class Interval
4
- attr_reader :number
4
+ attr_reader :semitones
5
5
 
6
6
  NAMES = [
7
7
  '1P',
8
- '2m', '2M',
9
- '3m', '3M',
10
- '4P', '4A',
8
+ '2m',
9
+ '2M',
10
+ '3m',
11
+ '3M',
12
+ '4P',
13
+ '4A',
11
14
  '5P',
12
- '6m', '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(number)
26
- @number = number
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[number]
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
- def initialize(arg)
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 intervals_from_note_set(note_set)
18
- note_numbers = note_set.notes.collect(&:number)
19
- root = note_numbers.shift
20
- note_numbers.reduce([Interval.new(0)]) do |memo, number|
21
- number += 12 if number < root
22
- memo << Interval.new(number - root)
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 reordered
27
- IntervalSequence.new @intervals.sort_by!(&:number)
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.number + ammount) % 12
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.number)
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
- IntervalSequence.new(intervals.rotate(+1))
56
+ inversion(index+1)
50
57
  end
51
58
 
52
59
  def previous_inversion
53
- IntervalSequence.new(intervals.rotate(-1))
60
+ inversion(index-1)
54
61
  end
55
62
 
56
63
  def inversions
57
64
  Array.new(intervals.length) do |index|
58
- IntervalSequence.new(interval.rotate(index))
65
+ inversion(index)
59
66
  end
60
67
  end
61
68
 
62
- def numbers
63
- intervals.collect(&:number)
69
+ def quality
70
+ end
71
+
72
+ def intervals_semitones
73
+ map(&:semitones)
64
74
  end
65
75
 
66
76
  def names
67
- intervals.collect(&:name)
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