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
data/lib/cli/text.rb DELETED
@@ -1,16 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- module Cli
5
- # A text representation
6
- class Text < Representation
7
- def render
8
- case Cli.config.flavor
9
- when :marks, :notes, :degrees then @notes.pretty_names.join(' ')
10
- when :intervals then @notes.map { |n| (@notes.first - n).name }.join(' ')
11
- else raise WrongFlavorError
12
- end
13
- end
14
- end
15
- end
16
- end
data/lib/cli/ukulele.rb DELETED
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- module Cli
5
- # Renders notes in a common most popular ukulele scheme
6
- class Ukulele < Guitar
7
- SPECIAL_FRETS = [5, 7, 9, 12].freeze
8
-
9
- def initialize(notes, tuning: %w[G C E A], frets: 12)
10
- super
11
- end
12
- end
13
- end
14
- end
@@ -1,43 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- # A simple caching based on serializing objects into files
5
- # maybe this should changed to save in a single json file
6
- class Cache
7
- class << self
8
- def find_or_record(key, &block)
9
- if @disabled || !(cached = fetch(key))
10
- cached = yield block
11
- record(key, cached)
12
- end
13
- cached
14
- end
15
-
16
- def disable
17
- @disabled = true
18
- end
19
-
20
- def enable
21
- @disabled = false
22
- end
23
-
24
- private
25
-
26
- def dir
27
- dir = File.expand_path('../../../', __FILE__) + '/cache/'
28
- Dir.mkdir(dir) unless Dir.exist?(dir)
29
- dir
30
- end
31
-
32
- def fetch(key)
33
- Marshal.load File.read(dir + key) if File.file?(dir + key)
34
- end
35
-
36
- def record(key, contents)
37
- File.open(dir + key, 'w') do |f|
38
- f.write Marshal.dump(contents)
39
- end
40
- end
41
- end
42
- end
43
- end
@@ -1,7 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- # Not done yet
5
- class Cadence
6
- end
7
- end
@@ -1,89 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- # It describe a chord
5
-
6
- class Chord
7
- attr_reader :root_note, :quality, :notes
8
- include ChordSubstitutions
9
-
10
- def initialize(notes: nil, root_note: nil, quality: nil, name: nil)
11
- if notes
12
- notes = NoteSet[*notes] if notes.is_a?(Array)
13
- @notes = notes
14
- @root_note = notes.first
15
- @quality = ChordQuality.new(notes: notes)
16
- elsif root_note && quality
17
- @notes = quality.notes_for(root_note)
18
- @root_note = root_note
19
- @quality = quality
20
- elsif name
21
- @root_note, @quality, @notes = parse_from_name(name)
22
- else
23
- raise WrongKeywordsError,
24
- '[notes:] || [root_note:, quality:] || [name:]'
25
- end
26
- end
27
-
28
- def name
29
- "#{root_note}#{quality}"
30
- end
31
-
32
- alias to_s name
33
-
34
- def pretty_name
35
- "#{root_note.pretty_name}#{quality.name}"
36
- end
37
-
38
- def intervals
39
- IntervalSequence.new(NoteSet.new(notes))
40
- end
41
-
42
- def size
43
- notes.size
44
- end
45
-
46
- def scales
47
- Scale.having_chord(name)
48
- end
49
-
50
- def next_inversion
51
- Chord.new(notes: notes.rotate(1))
52
- end
53
-
54
- def invert(n = 1)
55
- Chord.new(notes: notes.rotate(n))
56
- end
57
-
58
- def previous_inversion
59
- Chord.new(notes: notes.rotate(-1))
60
- end
61
-
62
- def +(other)
63
- case other
64
- when Note, NoteSet, Interval then Chord.new(notes: notes + other)
65
- when Chord then Chord.new(notes: notes + other.notes)
66
- end
67
- end
68
-
69
- def -(other)
70
- case other
71
- when Note, NoteSet, Interval, Numeric then Chord.new(notes: notes - other)
72
- when Chord then Chord.new(notes: notes - other.notes)
73
- end
74
- end
75
-
76
- protected
77
-
78
- def parse_from_name(name)
79
- chord_name, bass = name.match?(/\/9/) ? [name, nil] : name.split('/')
80
- chord_regex = /([A-Z](?:#|b)?)(.*)/
81
- _, root_name, quality_name = chord_name.match(chord_regex).to_a
82
- root = Note[root_name]
83
- quality = ChordQuality.new(name: quality_name, bass: bass)
84
- notes = quality.notes_for(root)
85
- notes << Note[bass] unless bass.nil?
86
- [root, quality, notes]
87
- end
88
- end
89
- end
@@ -1,111 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- # It describe the quality of a chord, like maj7 or dim.
5
- class ChordQuality < IntervalSequence
6
- include Qualities
7
- attr_reader :name
8
- # QUALITIES_FILE = File.expand_path("#{'../' * 3}data/qualities.json", __FILE__)
9
-
10
- private
11
-
12
- def self.chord_trie
13
- File.expand_path("#{'../' * 3}data/qualities.json", __FILE__)
14
- trie = QUALITIES
15
-
16
- trie.clone_values from_keys: ['Perfect Unison', 'Major Third'],
17
- to_keys: ['Perfect Unison', 'Major Second'],
18
- suffix: 'sus2'
19
-
20
- trie.clone_values from_keys: ['Perfect Unison', 'Major Third'],
21
- to_keys: ['Perfect Unison', 'Perfect Fourth'],
22
- suffix: 'sus4'
23
- trie.deep_dup
24
- end
25
-
26
- def self.intervals_per_name(quality_names: {}, intervals: [], hash: nil)
27
- hash ||= chord_trie
28
- return quality_names if hash.empty?
29
- if hash['name']
30
- quality_names[hash.delete('name')] = intervals.map { |n| Interval.public_send(n.underscore) }
31
- end
32
- hash.reduce(quality_names) do |memo, (interval, values)|
33
- memo.merge intervals_per_name(hash: values,
34
- quality_names: quality_names,
35
- intervals: intervals + [interval])
36
- end
37
- end
38
-
39
- NAMES = intervals_per_name
40
-
41
- def find_chord(given_interval_names, trie: self.class.chord_trie, last_name: nil)
42
- return if trie.nil?
43
- if given_interval_names.empty?
44
- @found = true
45
- return trie['name']
46
- end
47
- interval = given_interval_names.shift
48
- new_trie = trie[interval.full_name]
49
- find_chord given_interval_names, last_name: (trie['name'] || last_name),
50
- trie: new_trie
51
- end
52
-
53
- def normal_sequence
54
- %i[unison third! fifth sixth! seventh ninth eleventh! thirteenth]
55
- end
56
-
57
- def sus2_sequence
58
- %i[unison second! fifth sixth! seventh ninth eleventh! thirteenth]
59
- end
60
-
61
- def sus4_sequence
62
- %i[unison fourth! fifth sixth! seventh ninth eleventh! thirteenth]
63
- end
64
-
65
- def retrieve_chord_intervals(chord_sequence = normal_sequence)
66
- ints = IntervalSequence.new(*self)
67
- chord_sequence.map do |int_sym|
68
- next unless interval_name = ints.public_send(int_sym)
69
- ints.delete_if { |i| i.cents == IntervalClass.new(interval_name).cents }
70
- interval_name
71
- end
72
- end
73
-
74
- public
75
-
76
- def get_name
77
- find_chord(retrieve_chord_intervals.compact) ||
78
- find_chord(retrieve_chord_intervals(sus2_sequence).compact) ||
79
- find_chord(retrieve_chord_intervals(sus4_sequence).compact) ||
80
- raise(ChordNotFoundError)
81
- end
82
-
83
- def suspension_type
84
- if has_major_second?
85
- 'sus2'
86
- else has_fourth?
87
- 'sus4'
88
- end
89
- end
90
-
91
- def initialize(name: nil, notes: nil, bass: nil)
92
- if name
93
- @name = bass.nil? ? name : [name, bass].join('/')
94
- super(*intervals_from_name(name))
95
- elsif notes
96
- super(notes: notes)
97
- @name = get_name
98
- else
99
- raise WrongKeywordsError, '[name:] || [notes:]'
100
- end
101
- end
102
-
103
- alias to_s name
104
-
105
- private
106
-
107
- def intervals_from_name(name)
108
- NAMES[name] || NAMES["M#{name}"] || raise(ChordNotFoundError)
109
- end
110
- end
111
- end
@@ -1,9 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- module ChordSubstitutions
5
- def tritone_substitution
6
- self + Interval.augmented_fourth
7
- end
8
- end
9
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- class CircleOfFifths
5
- attr_reader :notes
6
-
7
- LETTER_SEQUENCE = %w[C G D A E B F].freeze
8
-
9
- def initialize(start_note = Note['C'], size = Float::INFINITY)
10
- index = letters.index(start_note.name[0])
11
- @notes = fifths(note: start_note - Interval.perfect_fifth,
12
- size: size,
13
- index: (index - 1) % LETTER_SEQUENCE.size)
14
- end
15
-
16
- private
17
-
18
- def fifths(note:, index: nil, notes: [], size:)
19
- return notes if size == 0
20
- return notes if notes.any? && note.as(letters(index)).name == notes.first.name
21
- fifths note: note + Interval.perfect_fifth,
22
- notes: notes + [note.as(letters(index))],
23
- index: index + 1,
24
- size: size - 1
25
- end
26
-
27
- def letters(i = nil)
28
- i.nil? ? LETTER_SEQUENCE : LETTER_SEQUENCE[i % LETTER_SEQUENCE.size]
29
- end
30
- end
31
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- # This module deals with well known scales on music
5
- module ClassicScales
6
- SCALES = {
7
- 'Pentatonic Major' => [2, 2, 3, 2, 3],
8
- 'Blues Major' => [2, 1, 1, 3, 2, 3],
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
- 'Chromatic' => [1] * 12
16
- }.freeze
17
-
18
- GREEK_MODES = %w[
19
- Ionian
20
- Dorian
21
- Phrygian
22
- Lydian
23
- Mixolydian
24
- Aeolian
25
- Locrian
26
- ].freeze
27
-
28
- # Creates factories for scales
29
- SCALES.each do |name, distances|
30
- define_method name.underscore do |tone = 'C', mode = 1|
31
- new(*distances, tone: tone, mode: mode, name: name)
32
- end
33
- end
34
-
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))
41
- end
42
- end
43
-
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
55
- alias pentatonic pentatonic_major
56
- alias blues blues_major
57
-
58
- def known_scales
59
- SCALES.keys + ['Major', 'Natural Minor']
60
- end
61
-
62
- # List of scales appropriate for search
63
- def standard_scales
64
- known_scales - ['Chromatic']
65
- end
66
-
67
- def fetch(name, tone = nil)
68
- Coltrane::Scale.public_send(name, tone)
69
- end
70
-
71
- def having_notes(notes)
72
- format = { scales: [], results: {} }
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
80
- end
81
- end
82
- end
83
-
84
- def having_chords(*chords)
85
- should_create = !chords.first.is_a?(Chord)
86
- notes = chords.reduce(NoteSet[]) do |memo, c|
87
- memo + (should_create ? Chord.new(name: c) : c).notes
88
- end
89
- having_notes(notes)
90
- end
91
-
92
- alias having_chord having_chords
93
- end
94
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Coltrane
4
- class DiatonicScale < Scale
5
- def initialize(tone, major: true)
6
- @major = major
7
- tone = Note[tone]
8
- notes = CircleOfFifths.new(tone - (major? ? 0 : 9), 7).notes.sort
9
- super notes: notes.rotate(notes.index(tone))
10
- end
11
-
12
- def name
13
- major? ? 'Major' : 'Natural Minor'
14
- end
15
-
16
- def relative_minor
17
- minor? ? self : self.class.new(@tone + 9, major: false)
18
- end
19
-
20
- def relative_major
21
- major? ? self : self.class.new(@tone - 9, major: true)
22
- end
23
-
24
- def relative
25
- major? ? relative_minor : relative_major
26
- end
27
-
28
- def major?
29
- !!@major
30
- end
31
-
32
- def minor?
33
- !@major
34
- end
35
- end
36
- end