coltrane 2.2.1 → 3.0.0.pre

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.
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