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,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'coltrane'
4
+ require 'coltrane/representation'
5
+ require 'coltrane/renderers'
6
+
7
+ require 'coltrane/commands/command'
8
+ require 'coltrane/commands/notes'
9
+ require 'coltrane/commands/chords'
10
+ require 'coltrane/commands/scale'
11
+ require 'coltrane/commands/progression'
12
+ require 'coltrane/commands/common_chords'
13
+ require 'coltrane/commands/find_scale'
14
+ require 'coltrane/commands/find_progression'
@@ -0,0 +1,77 @@
1
+ module Coltrane
2
+ module Commands
3
+ class Chords < Command
4
+ attr_reader :chord, :flavor, :on, :preface
5
+
6
+ def initialize(chord = nil,
7
+ flavor: :notes,
8
+ on: :text,
9
+ notes: nil,
10
+ preface: nil,
11
+ **options)
12
+ if chord
13
+ @chord = chord.is_a?(Theory::Chord) ? chord : Theory::Chord.new(name: chord)
14
+ elsif notes
15
+ @chord = Theory::Chord.new(notes: notes&.split('-'))
16
+ else
17
+ raise 'Provide chord names or notes. Ex: coltrane chords Cm7-Db7, ' \
18
+ 'or coltrane chords --notes C-E-G'
19
+ end
20
+
21
+ @flavor = flavor.to_sym
22
+ @preface = preface || @chord.name
23
+ @on = on.to_sym
24
+ end
25
+
26
+ def representation
27
+ return on_model if on == :text
28
+ { preface => on_model }
29
+ end
30
+
31
+ def on_model
32
+ case on
33
+ when :text then chord
34
+ when :guitar then Representation::Guitar.find_chords(chord).first(6)
35
+ when :ukulele, :ukelele then Representation::Ukulele.find_chords(chord).first(6)
36
+ when :bass then Representation::Bass.find_chords(chord).first(6)
37
+ when :piano then Representation::Piano.find_notes(chord.notes)
38
+ end
39
+ end
40
+
41
+ def layout_horizontal?
42
+ [:guitar, :ukulele, :ukelele, :bass, :piano].include?(on)
43
+ end
44
+
45
+ def renderer_options
46
+ [
47
+ { flavor: flavor },
48
+ (
49
+ {
50
+ layout: :horizontal,
51
+ per_row: 6,
52
+ } if layout_horizontal?
53
+ )
54
+ ]
55
+ .compact
56
+ .reduce({}, :merge)
57
+ end
58
+
59
+ def self.mercenary_init(program)
60
+ program.command(:chords) do |c|
61
+ c.alias(:chord)
62
+ c.syntax 'chords [<chord-name>] [--on <instrument>]'
63
+ c.description 'Shows the given chord. Ex: coltrane chord Cmaj7 --on piano'
64
+ c.alias(:chords)
65
+ c.option :notes, '--notes C-D-E', 'finds chords with those notes, ' \
66
+ 'provided they are separated by dashes'
67
+
68
+ add_shared_option(:flavor, c)
69
+ add_shared_option(:on, c)
70
+ c.action { |(chords), **options|
71
+ (chords&.split('-') || [nil]).each { |chord| new(chord, **options).render }
72
+ }
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,45 @@
1
+ module Coltrane
2
+ module Commands
3
+ class Command
4
+ COMMON_OPTIONS = {
5
+ on: [
6
+ '--on guitar INSTRUMENT',
7
+ 'Shows the notes on the given instrument/representation type. ' \
8
+ 'Can be piano, guitar, ukulele, bass or text'
9
+ ],
10
+
11
+ flavor: [
12
+ '--flavor FLAVOR',
13
+ 'Chooses which additional information to display: ' \
14
+ 'marks, notes, intervals or degrees'
15
+ ]
16
+ }
17
+
18
+ def render
19
+ puts "\n" + Renderers::TextRenderer.render(
20
+ representation, **renderer_options
21
+ )
22
+ end
23
+
24
+ def renderer_options
25
+ {}
26
+ end
27
+
28
+ class << self
29
+
30
+ def subclasses
31
+ @subclasses ||= []
32
+ end
33
+
34
+ def inherited(base)
35
+ subclasses << base
36
+ super(base)
37
+ end
38
+
39
+ def add_shared_option(option_name, mercenary_command)
40
+ mercenary_command.option(option_name, *COMMON_OPTIONS[option_name])
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,33 @@
1
+ module Coltrane
2
+ module Commands
3
+ class CommonChords < Command
4
+ def self.parse(scale_notation)
5
+ scale_notation
6
+ .split(' ', 2)
7
+ .yield_self { |(tone, scale_name)|
8
+ Theory::Scale.fetch(scale_name.gsub(' ', '_'), tone)
9
+ }
10
+ end
11
+
12
+ def self.mercenary_init(program)
13
+ program.command(:'common-chords') do |c|
14
+ c.alias(:'common-chord')
15
+ add_shared_option(:flavor, c)
16
+ add_shared_option(:on, c)
17
+ c.syntax 'common-chords [TONE_1 SCALE_NAME_1], [TONE_2 SCALE_NAME_2], [...]'
18
+ c.description 'Finds chords that are shared between the given scales'
19
+ c.action do |(*scale_strings), **options|
20
+ scale_strings
21
+ .join(' ')
22
+ .split(',')
23
+ .map { |scale_notation|
24
+ Commands::Scale.parse(scale_notation).all_chords
25
+ }
26
+ .reduce(:&)
27
+ .each { |chord| Commands::Chords.new(chord, **options).render }
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ # rubocop:disable Style/Documentation
4
+
5
+ module Coltrane
6
+ class CommandError < StandardError
7
+ def initialize(msg)
8
+ super msg
9
+ end
10
+ end
11
+
12
+ class WrongFlavorError < CommandError
13
+ def initialize(msg = nil)
14
+ super msg || 'Wrong flavor. Check possible flavors with `coltrane list flavors`.'
15
+ end
16
+ end
17
+
18
+ class BadFindScales < CommandError
19
+ def initialize(msg = nil)
20
+ super msg || 'Provide --notes or --chords. Ex: `coltrane find-scale --notes C-E-G`.'
21
+ end
22
+ end
23
+
24
+ class WrongRepresentationTypeError < CommandError
25
+ def initialize(type)
26
+ super "The provided representation type (#{type}) "\
27
+ 'is not available at the moment.'
28
+ end
29
+ end
30
+
31
+ class BadScaleError < CommandError
32
+ def initialize(msg = nil)
33
+ super msg || 'Incorrect scale, please specify scale and root separated by `-`. Ex: `coltrane scale major-C'
34
+ end
35
+ end
36
+
37
+ class BadChordError < CommandError
38
+ def initialize(msg = nil)
39
+ super msg || 'Incorrect chord, please specify a set of chords separated by `-`. Ex: coltrane chord CM7'
40
+ end
41
+ end
42
+ end
43
+
44
+ # rubocop:enable Style/Documentation
@@ -0,0 +1,28 @@
1
+ module Coltrane
2
+ module Commands
3
+ class FindProgression < Command
4
+ attr_reader :progression_set
5
+
6
+ def initialize(progression_set, **options)
7
+ @progression_set = progression_set
8
+ end
9
+
10
+ def representation
11
+ progression_set
12
+ end
13
+
14
+ def self.mercenary_init(program)
15
+ program.command(:'find-progression') do |c|
16
+ c.syntax 'find-progression <list of chords>'
17
+ c.description 'Find progressions in scales. Ex: coltrane find-progression AM-DM-F#m-EM'
18
+ c.action do |(chord_notation)|
19
+ chord_notation
20
+ .split('-')
21
+ .yield_self { |chords| Theory::Progression.find(*chords) }
22
+ .yield_self { |progression_set| new(progression_set).render }
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,39 @@
1
+ module Coltrane
2
+ module Commands
3
+ class FindScale < Command
4
+ attr_reader :scale_set
5
+
6
+ def initialize(scale_set)
7
+ @scale_set = scale_set
8
+ end
9
+
10
+ def representation
11
+ scale_set
12
+ end
13
+
14
+ def self.mercenary_init(program)
15
+ program.command(:'find-scale') do |c|
16
+ c.syntax 'find-scale --notes C-D-E-...] OR --chord Cmaj7-Db7'
17
+ c.description 'finds scales with the provided --notes or --chord'
18
+ c.option :notes, '--notes C-D-E', 'Find scales with those notes'
19
+ c.option :chords, '--chords Cmaj7-D11', 'find scales with those chords'
20
+ c.action do |(_), notes: nil, chords: nil|
21
+ begin
22
+ if notes
23
+ Theory::Scale.having_notes(
24
+ Theory::NoteSet[*notes.to_s.split('-')]
25
+ )
26
+ elsif chords
27
+ Theory::Scale.having_chords(*chords.to_s.split('-'))
28
+ else
29
+ raise 'Provide --notes or --chords separated by dashes.' \
30
+ 'For example coltrane find-scale --notes C-E-F#'
31
+ end
32
+ end
33
+ .yield_self { |scale_set| new(scale_set).render }
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,44 @@
1
+ module Coltrane
2
+ module Commands
3
+ class Notes < Command
4
+ attr_reader :notes, :flavor, :on, :preface
5
+
6
+ def initialize(notes, flavor: :notes, on: :text, preface: nil)
7
+ raise 'Provide some notes. Ex: coltrane notes C-D-Gb' if notes.nil?
8
+ @notes = notes.is_a?(Theory::NoteSet) ? notes : Theory::NoteSet[*notes.split('-')]
9
+ @flavor = flavor.to_sym
10
+ @preface = preface || 'Here are the notes you asked for'
11
+ @on = on
12
+ end
13
+
14
+ def representation
15
+ { preface => on_model }
16
+ end
17
+
18
+ def renderer_options
19
+ { flavor: flavor }
20
+ end
21
+
22
+ def on_model
23
+ case on.to_sym
24
+ when :text then notes
25
+ when :guitar then Representation::Guitar.find_notes(notes)
26
+ when :ukulele, :ukelele then Representation::Ukulele.find_notes(notes)
27
+ when :bass then Representation::Bass.find_notes(notes)
28
+ when :piano then Representation::Piano.find_notes(notes)
29
+ end
30
+ end
31
+
32
+ def self.mercenary_init(program)
33
+ program.command(:notes) do |c|
34
+ c.alias(:note)
35
+ c.syntax 'notes <notes separated by space> [--on <instrument>]'
36
+ c.description 'Shows the given notes.'
37
+ add_shared_option(:flavor, c)
38
+ add_shared_option(:on, c)
39
+ c.action { |(notes), **options| new(notes, **options).render }
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,27 @@
1
+ module Coltrane
2
+ module Commands
3
+ class Progression < Command
4
+ def self.mercenary_init(program)
5
+ program.command(:progression) do |c|
6
+ c.syntax 'progression <roman numeral notation> in <key> [--on <instrument>]'
7
+ c.description 'Gives you chords of a progression in a key. Ex: coltrane progression I-IV-iv-V in Am --on guitar'
8
+ add_shared_option(:flavor, c)
9
+ add_shared_option(:on, c)
10
+ c.action do |(prog, _, key), **options|
11
+ prog
12
+ .tr('-', '_')
13
+ .yield_self { |possible_method|
14
+ if Theory::Progression.respond_to?(possible_method)
15
+ Theory::Progression.send(possible_method, key)
16
+ else
17
+ Theory::Progression.new(prog, key: key)
18
+ end
19
+ }
20
+ .chords
21
+ .each { |chord| Commands::Chords.new(chord, **options).render }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
File without changes
@@ -0,0 +1,46 @@
1
+ module Coltrane
2
+ module Commands
3
+ class Scale < Command
4
+ def representation
5
+ return on_model if on == :text
6
+ { preface => on_model }
7
+ end
8
+
9
+ def self.parse(scale_notation)
10
+ scale_notation
11
+ .split(' ', 2)
12
+ .yield_self { |(tone, scale_name)|
13
+ Theory::Scale.fetch(scale_name.gsub(' ', '_'), tone)
14
+ }
15
+ end
16
+
17
+ def self.mercenary_init(program)
18
+ program.command(:scale) do |c|
19
+ c.syntax 'scale <root_note> <scale name> [--on <instrument>]'
20
+ c.description 'Gives you information about a scale. Ex: coltrane scale D Natural Minor --on guitar'
21
+ c.option :tertians, '--tertians SIZE', 'Outputs all tertian chords from the given size from the scale'
22
+ c.option :chords, '--chords [SIZE]', 'Outputs all chords from given size from the scale. Leave size empty to retrieve all'
23
+ add_shared_option(:flavor, c)
24
+ add_shared_option(:on, c)
25
+
26
+ c.action { |scale_notation, **options|
27
+ parse(scale_notation.join(' '))
28
+ .yield_self { |scale|
29
+ if options[:tertians]
30
+ scale
31
+ .tertians(options[:tertians].to_i)
32
+ .each { |chord| Commands::Chords.new(chord, **options).render }
33
+ elsif options[:chords]
34
+ scale.chords(options[:chords].to_i)
35
+ .each { |chord| Commands::Chords.new(chord, **options).render }
36
+ else
37
+ scale.notes
38
+ .yield_self {|notes| Commands::Notes.new(notes, **options.merge(preface: scale.full_name)).render }
39
+ end
40
+ }
41
+ }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'coltrane'
4
+ require 'coltrane/representation'
5
+ require 'coltrane/renderers/renderer'
6
+ require 'coltrane/renderers/text_renderer'
@@ -0,0 +1,42 @@
1
+ module Coltrane
2
+ module Renderers
3
+ module Renderer
4
+ include Dry::Monads::Try::Mixin
5
+
6
+ def render(model, **options)
7
+ model
8
+ .yield_self { |model| model_class_list(model) }
9
+ .yield_self { |model_classes| renderer_class(*model_classes) }
10
+ .value_or { raise("Renderer doesn't implements #{model.class}") }
11
+ .new(model, **options)
12
+ .render
13
+ end
14
+
15
+ private
16
+
17
+ def model_class_list(model)
18
+ model
19
+ .class
20
+ .ancestors
21
+ .yield_self { |classes| classes[0...classes.index(Object)] }
22
+ end
23
+
24
+ def renderer_class(*classes)
25
+ return if classes.empty?
26
+ Try() { classes }
27
+ .fmap { |classes|
28
+ classes
29
+ .first
30
+ .to_s
31
+ .gsub('Coltrane::', '')
32
+ .gsub('::', '')
33
+ .prepend("#{self.name}::")
34
+ .concat('Drawer')
35
+ .yield_self {|class_name| Object.const_get(class_name) }
36
+ }
37
+ .to_maybe
38
+ .or(renderer_class(*classes[1..-1]))
39
+ end
40
+ end
41
+ end
42
+ end