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
@@ -1,148 +0,0 @@
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
- include Comparable
16
-
17
- NOTATION = %w[C C# D D# E F F# G G# A A# B].freeze
18
-
19
- def self.all_letters
20
- %w[C D E F G A B]
21
- end
22
-
23
- def self.all
24
- NOTATION.map { |n| new(n) }
25
- end
26
-
27
- def initialize(arg = nil, frequency: nil)
28
- @integer = case arg
29
- when String then NOTATION.index(arg)
30
- when Frequency then frequency_to_integer(Frequency.new(arg))
31
- when Numeric then (arg % 12)
32
- when nil then frequency_to_integer(Frequency.new(frequency))
33
- when PitchClass then arg.integer
34
- else raise(WrongArgumentsError)
35
- end
36
- end
37
-
38
- def self.[](arg, frequency: nil)
39
- new(arg, frequency: nil)
40
- end
41
-
42
- def ==(other)
43
- integer == other.integer
44
- end
45
-
46
- alias eql? ==
47
- alias hash integer
48
-
49
- def true_notation
50
- NOTATION[integer]
51
- end
52
-
53
- def letter
54
- name[0]
55
- end
56
-
57
- def ascending_interval_to(other)
58
- Interval.new(self, (other.is_a?(PitchClass) ? other : Note.new(other)))
59
- end
60
-
61
- alias interval_to ascending_interval_to
62
-
63
- def descending_interval_to(other)
64
- Interval.new(
65
- self,
66
- (other.is_a?(PitchClass) ? other : Note.new(other)),
67
- ascending: false
68
- )
69
- end
70
-
71
- alias name true_notation
72
-
73
- def pretty_name
74
- name.tr('b', "\u266D").tr('#', "\u266F")
75
- end
76
-
77
- def accidental?
78
- notation.match? /#|b/
79
- end
80
-
81
- def sharp?
82
- notation.match? /#/
83
- end
84
-
85
- def flat?
86
- notation.match? /b/
87
- end
88
-
89
- alias notation true_notation
90
- alias to_s true_notation
91
-
92
- def +(other)
93
- case other
94
- when Interval then self.class[integer + other.semitones]
95
- when Integer then self.class[integer + other]
96
- when PitchClass then self.class[integer + other.integer]
97
- when Frequency then self.class.new(frequency: frequency + other)
98
- end
99
- end
100
-
101
- def -(other)
102
- case other
103
- when Interval then self.class[integer - other.semitones]
104
- when Integer then self.class[integer - other]
105
- when PitchClass then Interval.new(self, other)
106
- when Frequency then self.class.new(frequency: frequency - other)
107
- end
108
- end
109
-
110
- def <=>(other)
111
- integer <=> other.integer
112
- end
113
-
114
- def fundamental_frequency
115
- @fundamental_frequency ||=
116
- Frequency[
117
- Coltrane.base_tuning *
118
- (2**((integer - Coltrane::BASE_PITCH_INTEGER.to_f) / 12))
119
- ]
120
- end
121
-
122
- alias frequency fundamental_frequency
123
-
124
- def self.size
125
- NOTATION.size
126
- end
127
-
128
- def size
129
- self.class.size
130
- end
131
-
132
- def enharmonic?(other)
133
- case other
134
- when String then integer == Note[other].integer
135
- when Note then integer == other.integer
136
- end
137
- end
138
-
139
- private
140
-
141
- def frequency_to_integer(f)
142
- begin
143
- (Coltrane::BASE_PITCH_INTEGER +
144
- size * Math.log(f.to_f / Coltrane.base_tuning.to_f, 2)) % size
145
- end.round
146
- end
147
- end
148
- end
@@ -1,74 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- # Allows the creation of chord progressions using standard notations.
5
- # Ex: Progression.new('I-IV-V', key: 'Am')
6
- class Progression
7
- extend NotableProgressions
8
- include Changes
9
- attr_reader :scale, :chords, :notation
10
-
11
- def self.find(*chords)
12
- chords.map! { |c| Chord.new(name: c) } if chords[0].is_a?(String)
13
-
14
- root_notes = NoteSet[*chords.map(&:root_note)]
15
-
16
- scales = Scale.having_notes(*root_notes)[:scales]
17
- progressions = scales.reduce([]) do |memo, scale|
18
- memo + [Progression.new(chords: chords, scale: scale)]
19
- end
20
-
21
- progressions.sort_by(&:notes_out_size)
22
- end
23
-
24
- def initialize(notation = nil, chords: nil, roman_chords: nil, key: nil, scale: nil)
25
- if notation.nil? && chords.nil? && roman_chords.nil? || key.nil? && scale.nil?
26
- raise WrongKeywordsError,
27
- '[chords:, [scale: || key:]] '\
28
- '[roman_chords:, [scale: || key:]] '\
29
- '[notation:, [scale: || key:]] '\
30
- end
31
-
32
- @scale = scale || Key[key]
33
- @chords =
34
- if !chords.nil?
35
- chords
36
- elsif !roman_chords.nil?
37
- roman_chords.map(&:chord)
38
- elsif !notation.nil?
39
- @notation = notation
40
- notation.split('-').map { |c| RomanChord.new(c, scale: @scale).chord }
41
- end
42
- end
43
-
44
- def interval_sequence
45
- @interval_sequence ||= IntervalSequence(notes: @root_notes)
46
- end
47
-
48
- def root_notes
49
- @root_notes ||= @chords.map(&:root_note)
50
- end
51
-
52
- def roman_chords
53
- @roman_chords ||= chords.map do |c|
54
- RomanChord.new(chord: c, scale: scale)
55
- end
56
- end
57
-
58
- def notation
59
- roman_chords.map(&:notation).join('-')
60
- end
61
-
62
- def notes
63
- NoteSet[*chords.map(&:notes).map(&:notes).flatten]
64
- end
65
-
66
- def notes_out
67
- notes - scale.notes
68
- end
69
-
70
- def notes_out_size
71
- notes_out.size
72
- end
73
- end
74
- end
@@ -1,112 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- # This class deals with chords in roman notation. Ex: IVº.
5
- class RomanChord
6
- DIGITS = %w[I II III IV V VI VII].freeze
7
- NOTATION_REGEX = /(b?[ivIV]*)(.*)/
8
-
9
- NOTATION_SUBSTITUTIONS = [
10
- %w[º dim],
11
- %w[o dim],
12
- %w[ø m7b5]
13
- ].freeze
14
-
15
- def initialize(notation = nil, chord: nil, key: nil, scale: nil)
16
- if notation.nil? && chord.nil? || key.nil? && scale.nil?
17
- raise WrongKeywordsError,
18
- '[notation, [scale: || key:]] '\
19
- '[chord:, [scale: || key:]] '\
20
- end
21
- @scale = scale || Key[key]
22
- if notation
23
- @notation = notation
24
- elsif chord
25
- @chord = chord.is_a?(String) ? Chord.new(name: chord) : chord
26
- end
27
- end
28
-
29
- def degree
30
- return @scale.degree_of_note(root_note) unless @chord.nil?
31
- d = regexed_notation[:degree]
32
- @flats = d.count('b')
33
- d = d.delete('b')
34
- @degree ||= DIGITS.index(d.upcase) + 1
35
- end
36
-
37
- def roman_numeral
38
- r = DIGITS[degree]
39
- minor? ? r.downcase : r
40
- end
41
-
42
- def upcase?
43
- !!(regexed_notation[:degree][0].match /[[:upper:]]/)
44
- end
45
-
46
- def chord
47
- @chord ||= Chord.new root_note: root_note,
48
- quality: quality
49
- end
50
-
51
- def minor?
52
- quality.has_minor_third?
53
- end
54
-
55
- def major?
56
- quality.has_major_third?
57
- end
58
-
59
- def quality_name
60
- return @chord.quality.name unless @chord.nil?
61
- q = normalize_quality_name(regexed_notation[:quality])
62
- minor = 'm' if (!q.match? /dim|m7b5/) && !upcase?
63
- q = [minor, q].join
64
- q.empty? ? 'M' : q
65
- end
66
-
67
- def quality
68
- return @chord.quality unless @chord.nil?
69
- ChordQuality.new(name: quality_name) if quality_name
70
- end
71
-
72
- def root_note
73
- return @chord.root_note unless @chord.nil?
74
- @scale[degree] - @flats
75
- end
76
-
77
- def notation
78
- q = case quality_name
79
- when 'm', 'M' then ''
80
- when 'm7', 'M' then '7'
81
- else quality_name
82
- end
83
-
84
- @notation ||= [
85
- roman_numeral,
86
- q
87
- ].join
88
- end
89
-
90
- def function
91
- return if @scale.name != 'Major'
92
- %w[Tonic Supertonic Mediant Subdominant
93
- Dominant Submediant Leading][degree - 1]
94
- end
95
-
96
- private
97
-
98
- def regexed_notation
99
- @regexed_notation ||= begin
100
- matchdata = @notation.match(NOTATION_REGEX)
101
- { degree: matchdata[1], quality: matchdata[2] }
102
- end
103
- end
104
-
105
- def normalize_quality_name(quality_name)
106
- NOTATION_SUBSTITUTIONS.reduce(quality_name) do |memo, subs|
107
- break memo if memo.empty?
108
- memo.gsub(*subs)
109
- end
110
- end
111
- end
112
- end
@@ -1,154 +0,0 @@
1
- # frozen_string_literal: true
2
- # frozen_string_literal: true
3
-
4
- module Coltrane
5
- # Musical scale creation and manipulation
6
- class Scale
7
- extend ClassicScales
8
- extend Forwardable
9
-
10
- def_delegators :notes, :accidentals, :sharps, :flats
11
-
12
- attr_reader :interval_sequence, :tone
13
-
14
- def initialize(*relative_intervals, tone: 'C',
15
- mode: 1,
16
- name: nil,
17
- notes: nil)
18
- @name = name
19
- if relative_intervals.any? && tone
20
- @tone = Note[tone]
21
- relative_intervals = relative_intervals.rotate(mode - 1)
22
- @interval_sequence = IntervalSequence.new(
23
- relative_intervals: relative_intervals
24
- )
25
- elsif notes
26
- @notes = NoteSet[*notes]
27
- @tone = @notes.first
28
- ds = @notes.interval_sequence.relative_intervals
29
- @interval_sequence = IntervalSequence.new(relative_intervals: ds)
30
- else
31
- raise WrongKeywordsError,
32
- '[*relative_intervals, tone: "C", mode: 1] || [notes:]'
33
- end
34
- end
35
-
36
- def id
37
- [(name || @interval_sequence), tone.number]
38
- end
39
-
40
- def name
41
- @name = begin
42
- is = interval_sequence.relative_intervals
43
- (0...is.size).each do |i|
44
- if (scale_name = Coltrane::ClassicScales::SCALES.key(is.rotate(i)))
45
- return scale_name
46
- end
47
- end
48
- nil
49
- end
50
- end
51
-
52
- def to_s
53
- "#{tone} #{name}"
54
- end
55
-
56
- def pretty_name
57
- "#{tone.name} #{name}"
58
- end
59
-
60
- alias full_name pretty_name
61
-
62
- def degree(d)
63
- raise WrongDegreeError, d if d < 1 || d > size
64
- tone + interval_sequence[d - 1].semitones
65
- end
66
-
67
- alias [] degree
68
-
69
- def degrees
70
- (1..size)
71
- end
72
-
73
- def degree_of_chord(chord)
74
- return if chords(chord.size).map(&:name).include?(chord.name)
75
- degree_of_note(chord.root_note)
76
- end
77
-
78
- def degree_of_note(note)
79
- notes.index(note)
80
- end
81
-
82
- def &(other)
83
- raise HasNoNotesError unless other.respond_to?(:notes)
84
- notes & other
85
- end
86
-
87
- def include_notes?(arg)
88
- noteset = arg.is_a?(Note) ? NoteSet[arg] : arg
89
- (self & noteset).size == noteset.size
90
- end
91
-
92
- alias include? include_notes?
93
-
94
- def notes
95
- @notes ||= NoteSet[*degrees.map { |d| degree(d) }]
96
- end
97
-
98
- def interval(i)
99
- interval_sequence[(i - 1) % size]
100
- end
101
-
102
- def size
103
- interval_sequence.size
104
- end
105
-
106
- def tertians(n = 3)
107
- degrees.size.times.reduce([]) do |memo, d|
108
- ns = NoteSet[ *Array.new(n) { |i| notes[(d + (i * 2)) % size] } ]
109
- begin
110
- chord = Chord.new(notes: ns)
111
- rescue ChordNotFoundError
112
- memo
113
- else
114
- memo + [chord]
115
- end
116
- end
117
- end
118
-
119
- def triads
120
- tertians(3)
121
- end
122
-
123
- def sevenths
124
- tertians(4)
125
- end
126
-
127
- def pentads
128
- tertians(5)
129
- end
130
-
131
- def progression(*degrees)
132
- Progression.new(self, degrees)
133
- end
134
-
135
- def chords(size = 3..12)
136
- size = (size..size) if size.is_a?(Integer)
137
- scale_rotations = interval_sequence.inversions
138
- ChordQuality.intervals_per_name.reduce([]) do |memo1, (qname, qintervals)|
139
- next memo1 unless size.include?(qintervals.size)
140
- memo1 + scale_rotations.each_with_index
141
- .reduce([]) do |memo2, (rot, index)|
142
- if (rot & qintervals).size == qintervals.size
143
- memo2 + [Chord.new(root_note: degree(index + 1),
144
- quality: ChordQuality.new(name: qname))]
145
- else
146
- memo2
147
- end
148
- end
149
- end
150
- end
151
-
152
- alias all_chords chords
153
- end
154
- end