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.
Files changed (129) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +16 -1
  3. data/CHANGELOG.md +11 -0
  4. data/Gemfile.lock +25 -2
  5. data/README.md +3 -0
  6. data/bin/console +6 -2
  7. data/coltrane.gemspec +2 -1
  8. data/exe/coltrane +14 -224
  9. data/lib/coltrane.rb +3 -50
  10. data/lib/coltrane/commands.rb +14 -0
  11. data/lib/coltrane/commands/chords.rb +77 -0
  12. data/lib/coltrane/commands/command.rb +45 -0
  13. data/lib/coltrane/commands/common_chords.rb +33 -0
  14. data/lib/coltrane/commands/errors.rb +44 -0
  15. data/lib/coltrane/commands/find_progression.rb +28 -0
  16. data/lib/coltrane/commands/find_scale.rb +39 -0
  17. data/lib/coltrane/commands/notes.rb +44 -0
  18. data/lib/coltrane/commands/progression.rb +27 -0
  19. data/lib/{cli → coltrane/commands}/representation.rb +0 -0
  20. data/lib/coltrane/commands/scale.rb +46 -0
  21. data/lib/coltrane/renderers.rb +6 -0
  22. data/lib/coltrane/renderers/renderer.rb +42 -0
  23. data/lib/coltrane/renderers/text_renderer.rb +23 -0
  24. data/lib/coltrane/renderers/text_renderer/array_drawer.rb +45 -0
  25. data/lib/coltrane/renderers/text_renderer/base_drawer.rb +20 -0
  26. data/lib/coltrane/renderers/text_renderer/hash_drawer.rb +16 -0
  27. data/lib/coltrane/renderers/text_renderer/representation_guitar_chord_drawer.rb +95 -0
  28. data/lib/coltrane/renderers/text_renderer/representation_guitar_note_set_drawer.rb +76 -0
  29. data/lib/coltrane/renderers/text_renderer/representation_piano_note_set_drawer.rb +49 -0
  30. data/lib/coltrane/renderers/text_renderer/theory_chord_drawer.rb +14 -0
  31. data/lib/coltrane/renderers/text_renderer/theory_note_set_drawer.rb +17 -0
  32. data/lib/coltrane/renderers/text_renderer/theory_progression_drawer.rb +13 -0
  33. data/lib/coltrane/renderers/text_renderer/theory_progression_set_drawer.rb +22 -0
  34. data/lib/coltrane/renderers/text_renderer/theory_scale_drawer.rb +13 -0
  35. data/lib/coltrane/renderers/text_renderer/theory_scale_set_drawer.rb +27 -0
  36. data/lib/coltrane/representation.rb +12 -0
  37. data/lib/coltrane/representation/guitar.rb +34 -0
  38. data/lib/coltrane/representation/guitar/chord.rb +180 -0
  39. data/lib/coltrane/representation/guitar/note.rb +26 -0
  40. data/lib/coltrane/representation/guitar/note_set.rb +35 -0
  41. data/lib/coltrane/representation/guitar/string.rb +31 -0
  42. data/lib/coltrane/representation/guitar_like_instruments.rb +21 -0
  43. data/lib/coltrane/representation/piano.rb +36 -0
  44. data/lib/coltrane/representation/piano/note_set.rb +22 -0
  45. data/lib/{coltrane_synth.rb → coltrane/synth.rb.rb} +0 -0
  46. data/lib/{coltrane_synth → coltrane/synth}/base.rb +0 -0
  47. data/lib/{coltrane_synth → coltrane/synth}/synth.rb +0 -0
  48. data/lib/coltrane/theory.rb +54 -0
  49. data/lib/coltrane/theory/cadence.rb +9 -0
  50. data/lib/coltrane/{changes.rb → theory/changes.rb} +0 -0
  51. data/lib/coltrane/theory/chord.rb +101 -0
  52. data/lib/coltrane/theory/chord_quality.rb +113 -0
  53. data/lib/coltrane/theory/chord_substitutions.rb +11 -0
  54. data/lib/coltrane/theory/circle_of_fifths.rb +33 -0
  55. data/lib/coltrane/theory/classic_scales.rb +113 -0
  56. data/lib/coltrane/theory/diatonic_scale.rb +38 -0
  57. data/lib/coltrane/theory/errors.rb +97 -0
  58. data/lib/coltrane/theory/frequency.rb +52 -0
  59. data/lib/coltrane/theory/frequency_interval.rb +83 -0
  60. data/lib/coltrane/theory/interval.rb +209 -0
  61. data/lib/coltrane/theory/interval_class.rb +212 -0
  62. data/lib/coltrane/theory/interval_sequence.rb +157 -0
  63. data/lib/coltrane/theory/key.rb +18 -0
  64. data/lib/coltrane/{mode.rb → theory/mode.rb} +0 -0
  65. data/lib/coltrane/theory/notable_progressions.rb +30 -0
  66. data/lib/coltrane/theory/note.rb +104 -0
  67. data/lib/coltrane/theory/note_set.rb +101 -0
  68. data/lib/coltrane/theory/pitch.rb +94 -0
  69. data/lib/coltrane/theory/pitch_class.rb +154 -0
  70. data/lib/coltrane/theory/progression.rb +81 -0
  71. data/lib/coltrane/theory/progression_set.rb +18 -0
  72. data/lib/coltrane/{qualities.rb → theory/qualities.rb} +0 -0
  73. data/lib/coltrane/theory/roman_chord.rb +114 -0
  74. data/lib/coltrane/theory/scale.rb +161 -0
  75. data/lib/coltrane/theory/scale_search_result.rb +6 -0
  76. data/lib/coltrane/theory/scale_set.rb +40 -0
  77. data/lib/coltrane/theory/voicing.rb +41 -0
  78. data/lib/coltrane/version.rb +1 -1
  79. metadata +88 -63
  80. data/bin/rails +0 -17
  81. data/data/qualities.yml +0 -83
  82. data/db/cache.sqlite3 +0 -0
  83. data/db/cache_test.sqlite3 +0 -0
  84. data/db/config.yml +0 -11
  85. data/lib/cli.rb +0 -24
  86. data/lib/cli/bass_guitar.rb +0 -12
  87. data/lib/cli/chord.rb +0 -39
  88. data/lib/cli/config.rb +0 -39
  89. data/lib/cli/errors.rb +0 -46
  90. data/lib/cli/guitar.rb +0 -67
  91. data/lib/cli/guitar_chords.rb +0 -122
  92. data/lib/cli/notes.rb +0 -20
  93. data/lib/cli/piano.rb +0 -57
  94. data/lib/cli/scale.rb +0 -53
  95. data/lib/cli/text.rb +0 -16
  96. data/lib/cli/ukulele.rb +0 -14
  97. data/lib/coltrane/cache.rb +0 -43
  98. data/lib/coltrane/cadence.rb +0 -7
  99. data/lib/coltrane/chord.rb +0 -89
  100. data/lib/coltrane/chord_quality.rb +0 -111
  101. data/lib/coltrane/chord_substitutions.rb +0 -9
  102. data/lib/coltrane/circle_of_fifths.rb +0 -31
  103. data/lib/coltrane/classic_scales.rb +0 -94
  104. data/lib/coltrane/diatonic_scale.rb +0 -36
  105. data/lib/coltrane/errors.rb +0 -95
  106. data/lib/coltrane/frequency.rb +0 -50
  107. data/lib/coltrane/frequency_interval.rb +0 -81
  108. data/lib/coltrane/interval.rb +0 -208
  109. data/lib/coltrane/interval_class.rb +0 -210
  110. data/lib/coltrane/interval_sequence.rb +0 -155
  111. data/lib/coltrane/key.rb +0 -16
  112. data/lib/coltrane/notable_progressions.rb +0 -28
  113. data/lib/coltrane/note.rb +0 -98
  114. data/lib/coltrane/note_set.rb +0 -89
  115. data/lib/coltrane/pitch.rb +0 -92
  116. data/lib/coltrane/pitch_class.rb +0 -148
  117. data/lib/coltrane/progression.rb +0 -74
  118. data/lib/coltrane/roman_chord.rb +0 -112
  119. data/lib/coltrane/scale.rb +0 -154
  120. data/lib/coltrane/unordered_interval_class.rb +0 -7
  121. data/lib/coltrane/voicing.rb +0 -39
  122. data/lib/coltrane_game/question.rb +0 -7
  123. data/lib/coltrane_instruments.rb +0 -7
  124. data/lib/coltrane_instruments/guitar.rb +0 -10
  125. data/lib/coltrane_instruments/guitar/base.rb +0 -29
  126. data/lib/coltrane_instruments/guitar/chord.rb +0 -170
  127. data/lib/coltrane_instruments/guitar/note.rb +0 -24
  128. data/lib/coltrane_instruments/guitar/string.rb +0 -29
  129. data/lib/os.rb +0 -21
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ # It describes a sequence of intervals
6
+ class IntervalSequence
7
+ extend Forwardable
8
+ attr_reader :intervals
9
+
10
+ def_delegators :@intervals, :map, :each, :[], :size,
11
+ :reduce, :delete, :reject!, :delete_if, :detect, :to_a
12
+
13
+ def initialize(*intervals, notes: nil, relative_intervals: nil)
14
+ if intervals.any?
15
+ @intervals = if intervals.first.is_a?(Interval)
16
+ intervals
17
+ else
18
+ intervals.map { |i| Interval[i] }
19
+ end
20
+
21
+ elsif notes
22
+ notes = NoteSet[*notes] if notes.is_a?(Array)
23
+ @intervals = intervals_from_notes(notes)
24
+ elsif relative_intervals
25
+ @relative_intervals = relative_intervals
26
+ @intervals = intervals_from_relative_intervals(relative_intervals)
27
+ else
28
+ raise WrongKeywordsError,
29
+ 'Provide: [notes:] || [intervals:] || [relative_intervals:]'
30
+ end
31
+ end
32
+
33
+ Interval.all_including_compound_and_altered.each do |interval|
34
+ # Creates methods such as major_third, returning it if it finds
35
+ define_method(interval.full_name.underscore.to_s) { find(interval) }
36
+ # Creates methods such as has_major_third?, returning a boolean
37
+ define_method("has_#{interval.full_name.underscore}?") { has?(interval) }
38
+ end
39
+
40
+ Interval.distances_names.map(&:underscore).each_with_index do |distance, i|
41
+ # Creates methods such as has_third?, returning a boolean
42
+ define_method("has_#{distance}?") { !!find_by_distance(i + 1) }
43
+ # Creates methods such third, returning any third it finds
44
+ define_method(distance.to_s) { find_by_distance(i + 1) }
45
+ # Creates methods such third!, returning thirds that arent aug or dim
46
+ define_method("#{distance}!") { find_by_distance(i + 1, false) }
47
+ end
48
+
49
+ instance_eval { alias [] new }
50
+
51
+ def relative_intervals
52
+ intervals_semitones[1..-1].each_with_index.map do |n, i|
53
+ if i.zero?
54
+ n
55
+ elsif i < intervals_semitones.size
56
+ n - intervals_semitones[i]
57
+ end
58
+ end + [12 - intervals_semitones.last]
59
+ end
60
+
61
+ def names
62
+ intervals.map(&:name)
63
+ end
64
+
65
+ def find(interval)
66
+ interval.clone if detect { |i| interval == i }
67
+ end
68
+
69
+ def has?(interval)
70
+ !!find(interval)
71
+ end
72
+
73
+ def find_by_distance(n, accept_altered = true)
74
+ strategy = (accept_altered ? :as : :as!)
75
+ map { |interval| interval.send(strategy, n) }
76
+ .compact
77
+ .sort_by { |i| i.alteration.abs }
78
+ .first
79
+ end
80
+
81
+ alias interval_names names
82
+
83
+ def all
84
+ intervals
85
+ end
86
+
87
+ def [](x)
88
+ intervals[x]
89
+ end
90
+
91
+ def shift(ammount)
92
+ self.class.new(*intervals.map do |i|
93
+ (i.semitones + ammount) % 12
94
+ end)
95
+ end
96
+
97
+ def zero_it
98
+ shift(-intervals.first.semitones)
99
+ end
100
+
101
+ def inversion(index)
102
+ self.class.new(*intervals.rotate(index)).zero_it
103
+ end
104
+
105
+ def next_inversion
106
+ inversion(index + 1)
107
+ end
108
+
109
+ def previous_inversion
110
+ inversion(index - 1)
111
+ end
112
+
113
+ def inversions
114
+ Array.new(intervals.length) { |i| inversion(i) }
115
+ end
116
+
117
+ def intervals_semitones
118
+ map(&:semitones)
119
+ end
120
+
121
+ def names
122
+ map(&:name)
123
+ end
124
+
125
+ def full_names
126
+ map(&:full_name)
127
+ end
128
+
129
+ def notes_for(root_note)
130
+ NoteSet[
131
+ *intervals.reduce([]) do |memo, interval|
132
+ memo + [root_note + interval]
133
+ end
134
+ ]
135
+ end
136
+
137
+ def &(other)
138
+ case other
139
+ when Array then intervals & other
140
+ when IntervalSequence then intervals & other.semitones
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def intervals_from_relative_intervals(relative_intervals)
147
+ relative_intervals[0..-2].reduce([Interval[0]]) do |memo, d|
148
+ memo + [memo.last + d]
149
+ end
150
+ end
151
+
152
+ def intervals_from_notes(notes)
153
+ notes.map { |n| notes.root - n }.sort_by(&:semitones)
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ class Key < DiatonicScale
6
+ KEY_REGEX = /([A-G][#b]?)([mM]?)/
7
+
8
+ def initialize(notation)
9
+ _, note, s = *notation.match(KEY_REGEX)
10
+ super(note, major: s != 'm')
11
+ end
12
+
13
+ def self.[](notation)
14
+ new(notation)
15
+ end
16
+ end
17
+ end
18
+ end
File without changes
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ # This module takes care of adding to progressions knowledge that is more
6
+ # based on common standards and practices.
7
+ module NotableProgressions
8
+ PROGRESSIONS = {
9
+ 'Pop' => ['I-V-vi-IV', :major],
10
+ 'Jazzy Pop' => ['IM7-V7-vi7-IVM7', :major],
11
+ 'Jazz' => ['ii7-V7-I7', :major],
12
+ 'Jazz Minor' => ['ii7-V7-i7', :major],
13
+ 'Blues' => ['IM7-IV7-I7-V7-IV7-I7', :major],
14
+ 'Jazz Blues' => ['I7-IV7-I7-V7-IV7-I7', :major],
15
+ 'Fifties' => ['I-vi-IV-V', :major],
16
+ 'Circle' => ['vi-ii-V-I', :major],
17
+ 'Tune Up' => ['ii7-V7-IM7-i7-IV7-IVM7-VIIM7', :minor]
18
+ }.freeze
19
+
20
+ PROGRESSIONS.each do |name, values|
21
+ notation, scale_name = values
22
+ define_method name.underscore do |note|
23
+ note = note.is_a?(Note) ? note : Note[note]
24
+ scale = Scale.public_send(scale_name, note)
25
+ new(notation, scale: scale)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ # Notes are different ways of calling pitch classes. In the context of equal
6
+ # tempered scales, they're more like a conceptual subject for
7
+ # matters of convention than an actual thing.
8
+ #
9
+ # Take for example A# and Bb. Those are different notes. Nevertheless, in the
10
+ # context of equal tempered scales they represent pretty much the same
11
+ # frequency.
12
+ #
13
+ # The theory of notes have changed too much in the course of time, which
14
+ # lead us with a lot of conventions and strategies when dealing with music.
15
+ # That's what this class is for.
16
+ class Note < PitchClass
17
+ attr_reader :alteration
18
+
19
+ ALTERATIONS = {
20
+ 'b' => -1,
21
+ '#' => 1
22
+ }.freeze
23
+
24
+ def initialize(arg)
25
+ note_name = case arg
26
+ when String then arg
27
+ when PitchClass then arg.true_notation
28
+ when Numeric, Frequency then PitchClass.new(arg).true_notation
29
+ else raise(WrongArgumentsError, arg)
30
+ end
31
+
32
+ chars = note_name.chars
33
+ letter = chars.shift
34
+ raise InvalidNoteLetterError, arg unless ('A'..'G').cover?(letter)
35
+ @alteration = chars.reduce(0) do |alt, symbol|
36
+ raise InvalidNoteLetterError, arg unless ALTERATIONS.include?(symbol)
37
+ alt + ALTERATIONS[symbol]
38
+ end
39
+ super((PitchClass[letter].integer + @alteration) % PitchClass.size)
40
+ end
41
+
42
+ def self.[](arg)
43
+ new(arg)
44
+ end
45
+
46
+ def name
47
+ "#{base_pitch_class}#{accidents}".gsub(/#b|b#/, '')
48
+ end
49
+
50
+ def base_pitch_class
51
+ PitchClass[integer - alteration]
52
+ end
53
+
54
+ def pitch_class
55
+ PitchClass.new(self)
56
+ end
57
+
58
+ def alteration=(a)
59
+ @alteration = a unless PitchClass[integer - a].accidental?
60
+ end
61
+
62
+ def alter(x)
63
+ Note.new(name).tap { |n| n.alteration = x }
64
+ end
65
+
66
+ def sharp?
67
+ alteration == 1
68
+ end
69
+
70
+ def double_sharp?
71
+ alteration == 2
72
+ end
73
+
74
+ def flat?
75
+ alteration == -1
76
+ end
77
+
78
+ def double_flat?
79
+ alteration == -2
80
+ end
81
+
82
+ def natural?
83
+ alteration == 0
84
+ end
85
+
86
+ def accidents
87
+ (@alteration > 0 ? '#' : 'b') * alteration.abs
88
+ end
89
+
90
+ def -(other)
91
+ super(other).yield_self { |r| r.is_a?(Note) ? r.alter(alteration) : r }
92
+ end
93
+
94
+ def +(other)
95
+ super(other).yield_self { |r| r.is_a?(Note) ? r.alter(alteration) : r }
96
+ end
97
+
98
+ def as(letter)
99
+ a = (Note[letter] - self)
100
+ alter([a.semitones, -(-a).semitones].min_by(&:abs))
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ # It describes a set of notes
6
+ class NoteSet
7
+ extend Forwardable
8
+
9
+ def_delegators :@notes, :first, :each, :size, :map, :reduce, :index,
10
+ :[], :index, :empty?, :permutation, :include?, :<<, :any?,
11
+ :count, :rotate
12
+
13
+ attr_reader :notes
14
+
15
+ alias root first
16
+ alias all notes
17
+
18
+ def self.[](*notes)
19
+ new(notes)
20
+ end
21
+
22
+ def initialize(arg)
23
+ @notes =
24
+ case arg
25
+ when NoteSet then arg.notes
26
+ when Array then arg.compact.map { |n| n.is_a?(PitchClass) ? n : Note[n] }.uniq
27
+ else raise InvalidNotesError, arg
28
+ end
29
+ end
30
+
31
+ def ==(other)
32
+ (self & other).size == self.size
33
+ end
34
+
35
+ alias eql? ==
36
+
37
+ def &(other)
38
+ NoteSet[*(notes & other.notes)]
39
+ end
40
+
41
+ def degree(note)
42
+ index(note) + 1
43
+ end
44
+
45
+ def +(other)
46
+ case other
47
+ when Note then NoteSet[*(notes + [other])]
48
+ when NoteSet then NoteSet[*notes, *other.notes]
49
+ when Interval then NoteSet[*notes.map { |n| n + other }]
50
+ end
51
+ end
52
+
53
+ def -(other)
54
+ case other
55
+ when NoteSet then NoteSet[*(notes - other.notes)]
56
+ when Interval then NoteSet[*notes.map { |n| n - other }]
57
+ end
58
+ end
59
+
60
+ def pretty_names
61
+ map(&:pretty_name)
62
+ end
63
+
64
+ def names
65
+ map(&:name)
66
+ end
67
+
68
+ def hash
69
+ integers.join.to_i
70
+ end
71
+
72
+ def integers
73
+ map(&:integer)
74
+ end
75
+
76
+ def accidentals
77
+ count(&:accidental?)
78
+ end
79
+
80
+ def sharps
81
+ count(&:sharp?)
82
+ end
83
+
84
+ def flats
85
+ count(&:flat?)
86
+ end
87
+
88
+ def alter(alteration)
89
+ NoteSet[*map { |n| n.alter(alteration) }]
90
+ end
91
+
92
+ def alter_accidentals(alteration)
93
+ NoteSet[*map { |n| n.alter(alteration) if n.accidental? }]
94
+ end
95
+
96
+ def interval_sequence
97
+ IntervalSequence.new(notes: self)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coltrane
4
+ module Theory
5
+ # It describes a pitch, like E4 or Bb5. It's like a note, but it has an octave
6
+ class Pitch
7
+ attr_reader :integer
8
+
9
+ def initialize(notation_arg = nil,
10
+ note: nil,
11
+ octave: nil,
12
+ notation: nil,
13
+ frequency: nil)
14
+
15
+ @integer = begin
16
+ if (n = notation_arg || notation)
17
+ n.is_a?(Integer) ? n : integer_from_notation(n)
18
+ elsif note && octave
19
+ integer_from_note_and_octave(Note[note], octave)
20
+ elsif frequency
21
+ integer_from_frequency(frequency)
22
+ else
23
+ raise(InvalidArgumentsError)
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.[](*args)
29
+ new *args
30
+ end
31
+
32
+ def scientific_notation
33
+ "#{pitch_class}#{octave}"
34
+ end
35
+
36
+ def pitch_class
37
+ PitchClass[integer]
38
+ end
39
+
40
+ def octave
41
+ (integer / 12) - 1
42
+ end
43
+
44
+ alias notation scientific_notation
45
+ alias name scientific_notation
46
+ alias to_s scientific_notation
47
+
48
+ alias hash integer
49
+ alias midi integer
50
+
51
+ def ==(other)
52
+ integer == other.integer
53
+ end
54
+
55
+ def frequency
56
+ pitch_class.frequency.octave(octave)
57
+ end
58
+
59
+ alias eql? ==
60
+ alias eq ==
61
+
62
+ def +(other)
63
+ case other
64
+ when Integer then Pitch[integer + other]
65
+ end
66
+ end
67
+
68
+ def -(other)
69
+ case other
70
+ when Integer then Pitch[integer - other]
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def integer_from_notation(the_notation)
77
+ _, n, o = *the_notation.match(/(\D+)(\d+)/)
78
+ integer_from_note_and_octave(Note[n], o)
79
+ end
80
+
81
+ def integer_from_note_and_octave(p, o)
82
+ 12 * (o.to_i + 1) + p.integer
83
+ end
84
+
85
+ def integer_from_frequency(f)
86
+ octave_from_frequency(f) * 12 + PitchClass.new(frequency: f).integer
87
+ end
88
+
89
+ def octave_from_frequency(f)
90
+ Math.log(f.to_f / PitchClass['C'].frequency.to_f, 2).ceil
91
+ end
92
+ end
93
+ end
94
+ end