coltrane 0.0.2 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +2 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +15 -10
  5. data/Gemfile.lock +47 -128
  6. data/README.md +30 -8
  7. data/Rakefile +0 -4
  8. data/bin/bundle +105 -0
  9. data/bin/byebug +29 -0
  10. data/bin/coderay +12 -0
  11. data/bin/coltrane +25 -7
  12. data/bin/htmldiff +12 -0
  13. data/bin/kill-pry-rescue +12 -0
  14. data/bin/ldiff +12 -0
  15. data/bin/pry +12 -0
  16. data/bin/rake +12 -0
  17. data/bin/rescue +12 -0
  18. data/bin/rspec +12 -0
  19. data/bin/rubocop +12 -0
  20. data/bin/ruby-parse +12 -0
  21. data/bin/ruby-rewrite +12 -0
  22. data/exe/coltrane +173 -0
  23. data/lib/cli/bass_guitar.rb +9 -0
  24. data/lib/cli/chord.rb +24 -0
  25. data/lib/cli/errors.rb +28 -0
  26. data/lib/cli/guitar.rb +55 -0
  27. data/lib/cli/notes.rb +18 -0
  28. data/lib/cli/piano.rb +54 -0
  29. data/lib/cli/representation.rb +47 -0
  30. data/lib/cli/scale.rb +48 -0
  31. data/lib/cli/text.rb +13 -0
  32. data/lib/cli/ukulele.rb +11 -0
  33. data/lib/coltrane-cli.rb +12 -0
  34. data/lib/coltrane.rb +16 -31
  35. data/lib/coltrane/cache.rb +34 -0
  36. data/lib/coltrane/chord.rb +28 -41
  37. data/lib/coltrane/chord_quality.rb +14 -39
  38. data/lib/coltrane/classic_progressions.rb +37 -0
  39. data/lib/coltrane/classic_scales.rb +92 -118
  40. data/lib/coltrane/errors.rb +54 -0
  41. data/lib/coltrane/interval.rb +25 -17
  42. data/lib/coltrane/interval_sequence.rb +57 -27
  43. data/lib/coltrane/note.rb +44 -30
  44. data/lib/coltrane/note_set.rb +37 -22
  45. data/lib/coltrane/piano_representation.rb +0 -58
  46. data/lib/coltrane/pitch.rb +12 -3
  47. data/lib/coltrane/progression.rb +31 -0
  48. data/lib/coltrane/qualities.rb +115 -114
  49. data/lib/coltrane/roman_chord.rb +36 -0
  50. data/lib/coltrane/scale.rb +85 -77
  51. data/lib/coltrane/version.rb +1 -1
  52. data/lib/core_ext.rb +12 -0
  53. data/pkg/coltrane-0.0.2.gem +0 -0
  54. metadata +27 -14
  55. data/lib/coltrane/essential_guitar_chords.rb +0 -82
  56. data/lib/coltrane/fret_set.rb +0 -0
  57. data/lib/coltrane/guitar.rb +0 -15
  58. data/lib/coltrane/guitar_chord.rb +0 -50
  59. data/lib/coltrane/guitar_chord_finder.rb +0 -98
  60. data/lib/coltrane/guitar_note.rb +0 -50
  61. data/lib/coltrane/guitar_note_set.rb +0 -61
  62. data/lib/coltrane/guitar_representation.rb +0 -96
  63. data/lib/coltrane/guitar_string.rb +0 -52
  64. data/lib/coltrane/scale_cache.rb +0 -4
@@ -0,0 +1,9 @@
1
+ module Coltrane
2
+ module Cli
3
+ class BassGuitar < Guitar
4
+ def initialize(notes, flavor, tuning: %w[E A D G])
5
+ super
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ module Coltrane
2
+ module Cli
3
+ class Chord
4
+ def initialize(*chords, on: :text, flavor: 'intervals', notes: nil)
5
+ @chords =
6
+ if !chords.empty?
7
+ if chords[0].is_a?(String)
8
+ chords.map { |c| Coltrane::Chord.new(name: c) }
9
+ else
10
+ chords
11
+ end
12
+ elsif !notes.nil?
13
+ [Coltrane::Chord.new(notes: notes)]
14
+ end
15
+
16
+ @chords.each do |chord|
17
+ raise ChordNotFoundError unless chord.named?
18
+ desc = "#{chord.name} chord:"
19
+ Coltrane::Cli::Notes.new(chord.notes, on: on, desc: desc, flavor: flavor)
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,28 @@
1
+ module Coltrane
2
+ module Cli
3
+ class ColtraneCliError < StandardError
4
+ def initialize(msg)
5
+ super msg
6
+ end
7
+ end
8
+
9
+ class WrongFlavorError < ColtraneCliError
10
+ def initialize(msg=nil)
11
+ super msg || 'Wrong flavor. Check possible flavors with `coltrane list flavors`.'
12
+ end
13
+ end
14
+
15
+ class BadFindScales < ColtraneCliError
16
+ def initialize(msg=nil)
17
+ super msg || 'Provide --notes or --chords. Ex: `coltrane find-scale --notes C E G`.'
18
+ end
19
+ end
20
+
21
+ class WrongRepresentationTypeError < ColtraneCliError
22
+ def initialize(type)
23
+ super "The provided representation type (#{type}) "\
24
+ "is not available at the moment."
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,55 @@
1
+ module Coltrane
2
+ module Cli
3
+ SPECIAL_FRETS = [3, 5, 7, 9, 12, 15, 17, 19]
4
+
5
+ class Guitar < Representation
6
+ def initialize(notes, flavor, tuning: %w[E A D G B E], frets: 22)
7
+ @notes = notes
8
+ @tuning = tuning.reverse
9
+ @frets = frets
10
+ @flavor = flavor
11
+ @ref_note = @notes.first
12
+ end
13
+
14
+ def render
15
+ [render_notes, render_special_frets, hint].join("\n"*2)
16
+ end
17
+
18
+ def render_notes
19
+ @tuning.map do |string|
20
+ string_note = Note[string]
21
+ (@frets+2).times.map do |i|
22
+ if i.zero?
23
+ string
24
+ else
25
+ fret = i - 1
26
+ note = string_note + fret
27
+ m = (@notes.include?(note) ? place_mark(note) : "--")
28
+ fret.zero? ? (m + " |") : m
29
+ end
30
+ end.join(' ')
31
+ end.join("\n")
32
+ end
33
+
34
+ def render_special_frets
35
+ (@frets+1).times.map do |fret|
36
+ m = SPECIAL_FRETS.include?(fret) ? fret.to_s.rjust(2, 0.to_s) : ' '
37
+ "#{m}#{' ' if fret.zero?}"
38
+ end.join(' ')
39
+ end
40
+
41
+ def render_dotted_frets
42
+ end
43
+
44
+ def place_mark(note)
45
+ case @flavor
46
+ when :notes then note.pretty_name.ljust(2, "\u266E")
47
+ when :intervals then (@ref_note - note).name.ljust(2, '-')
48
+ when :degrees then @notes.degree(note).to_s.rjust(2, '0')
49
+ when :marks then '◼◼'
50
+ else raise WrongFlavorError.new
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,18 @@
1
+ module Coltrane
2
+ module Cli
3
+ class Notes
4
+ def initialize(notes, on: 'text', desc: 'The notes you supplied:', flavor: 'notes')
5
+ @desc = desc
6
+ flavor = flavor.underscore.to_sym
7
+ on = on.to_sym
8
+ notes = Coltrane::NoteSet.new(notes)
9
+ @representation = Representation.build(on, notes, flavor)
10
+ render
11
+ end
12
+
13
+ def render
14
+ puts "\n"+[@desc, @representation.render].join("\n"*2)
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,54 @@
1
+ module Coltrane
2
+ module Cli
3
+ class Piano < Representation
4
+ PIANO_TEMPLATE = <<~ASCII
5
+ ┌─┬─┬┬─┬─╥─┬─┬┬─┬┬─┬─╥─┬─┬┬─┬─╥─┬─┬┬─┬┬─┬─┐
6
+ │ │ ││ │ ║ │ ││ ││ │ ║ │ ││ │ ║ │ ││ ││ │ │
7
+ │ │X││X│ ║ │X││X││X│ ║ │X││X│ ║ │X││X││X│ │
8
+ │ │X││X│ ║ │X││X││X│ ║ │X││X│ ║ │X││X││X│ │
9
+ │ ┕╥┙┕╥┙ ║ ┕╥┙┕╥┙┕╥┙ ║ ┕╥┙┕╥┙ ║ ┕╥┙┕╥┙┕╥┙ │
10
+ │ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ ║ │
11
+ │XX║XX║XX║XX║XX║XX║XX║XX║XX║XX║XX║XX║XX║XX│
12
+ └──╨──╨──╨──╨──╨──╨──╨──╨──╨──╨──╨──╨──╨──┘
13
+ ASCII
14
+
15
+ def render
16
+ PIANO_TEMPLATE.each_line.map.each_with_index do |l, ln|
17
+ case ln
18
+ when 2, 3 then replace_x(l, black_notes, 1, ln - 2)
19
+ when 6 then replace_x(l, white_notes, 2)
20
+ else l
21
+ end
22
+ end.join + "\n" + hint
23
+ end
24
+
25
+ private
26
+
27
+ def replace_x(line, notes, size, index=0)
28
+ line.gsub('X'*size).with_index do |match, i|
29
+ note = notes[i%notes.size]
30
+ next ' '*size unless @notes.include?(note)
31
+ Paint[replacer(note)[size == 2 ? 0..2 : index ], 'red']
32
+ end
33
+ end
34
+
35
+ def replacer(note)
36
+ # TODO: Maybe extract this method into its own class/module
37
+ case @flavor
38
+ when :intervals then (@ref_note - note).name
39
+ when :marks then '◼ '
40
+ when :degrees then @notes.degree(note).to_s.rjust(2,'0')
41
+ when :notes then note.pretty_name.to_s.ljust(2, "\u266E")
42
+ end
43
+ end
44
+
45
+ def white_notes
46
+ Coltrane::Scale.major.notes
47
+ end
48
+
49
+ def black_notes
50
+ Coltrane::Scale.pentatonic_major('C#',4).notes
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,47 @@
1
+ module Coltrane
2
+ module Cli
3
+ class Representation
4
+ ACCEPTED_FLAVORS = %i[marks notes intervals degrees]
5
+
6
+ def self.inherited(subclass)
7
+ @@types ||= {}
8
+ @@types[subclass.to_s.split('::').last.underscore.to_sym] = subclass
9
+ end
10
+
11
+ def self.build(type, notes, flavor)
12
+ raise WrongFlavorError.new unless ACCEPTED_FLAVORS.include?(flavor)
13
+ type = case type
14
+ when :ukelele then :ukulele
15
+ when :bass then :bass_guitar
16
+ else type
17
+ end
18
+
19
+ if (the_class = @@types[type])
20
+ the_class.new(notes, flavor)
21
+ else
22
+ raise WrongRepresentationTypeError.new(type)
23
+ end
24
+ end
25
+
26
+ def initialize(notes, flavor)
27
+ @notes = notes
28
+ @flavor = flavor
29
+ @ref_note = notes.first
30
+ end
31
+
32
+ def hint
33
+ case @flavor
34
+ when :marks then ""
35
+ when :notes then "(\u266E means the note is natural, not flat nor sharp)"
36
+ when :intervals
37
+ <<~DESC
38
+ The letters represent the intervals relative to the root tone
39
+ Ex: 1P = Perfect First / 3m = Minor Third / 4A = Augmented Fourth
40
+ DESC
41
+
42
+ when :degrees then "(The numbers represent the degree of the note in the scale)"
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,48 @@
1
+ module Coltrane
2
+ module Cli
3
+ class Scale
4
+ def self.parse(str)
5
+ *scale_name, tone = str.split('-')
6
+ Coltrane::Scale.fetch(scale_name.join('_'), tone)
7
+ end
8
+
9
+ def self.find(notes: [], chords: [])
10
+ if notes.any?
11
+ puts "\nSearching for scales containing #{notes.join(', ')}:\n\n"
12
+ notes = NoteSet[*notes]
13
+ elsif chords.any?
14
+ puts "\nSearching for scales containing #{chords.join(', ')}:\n\n"
15
+ notes = chords.reduce(NoteSet[]) {|memo, c| memo + Coltrane::Chord.new(name: c).notes }
16
+ else raise BadFindScales.new
17
+ end
18
+ render_search(notes)
19
+ puts "\nUnderlined means the scale has all notes"
20
+ end
21
+
22
+ def self.render_search(searched_notes)
23
+ search = Coltrane::Scale.having_notes(searched_notes)
24
+ output = []
25
+ scale_width = search.results.keys.map(&:size).max
26
+ search.results.each do |name, scales_by_tone|
27
+ output << name.ljust(scale_width+1, ' ')
28
+ scales_by_tone.each do |tone_number, notes|
29
+ p = notes.size.to_f / searched_notes.size
30
+ l = p == 1 ? p : (p + 0.2) * 0.4
31
+ hue, val, sat = 30, val = (l * 100).round, sat = p
32
+ und = p == 1 ? :underline : nil
33
+ color = "hsv(#{hue},#{sat},#{val})".paint.to_hex
34
+ output << Paint["#{Note[tone_number].name}(#{notes.size})", color, und]
35
+ output << " "
36
+ end
37
+ output << "\n"
38
+ end
39
+ puts output.join
40
+ end
41
+
42
+ def initialize(scale, on: :text, flavor: 'degrees', notes: [], chords: [])
43
+ desc = "This is the #{scale.tone.name} #{scale.name} scale:"
44
+ Coltrane::Cli::Notes.new(scale.notes, on: on, desc: desc, flavor: flavor)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ module Coltrane
2
+ module Cli
3
+ class Text < Representation
4
+ def render
5
+ case @flavor
6
+ when :marks, :notes, :degrees then @notes.pretty_names.join(' ')
7
+ when :intervals then @notes.map {|n| (@notes.first - n).name}.join(' ')
8
+ else raise WrongFlavorError.new
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,11 @@
1
+ module Coltrane
2
+ module Cli
3
+ class Ukulele < Guitar
4
+ SPECIAL_FRETS = [5, 7, 9, 12]
5
+
6
+ def initialize(notes, flavor, tuning: %w[G C E A], frets: 12)
7
+ super
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ require 'cli/errors'
2
+
3
+ require 'cli/representation'
4
+ require 'cli/text'
5
+ require 'cli/piano'
6
+ require 'cli/guitar'
7
+ require 'cli/bass_guitar'
8
+ require 'cli/ukulele'
9
+
10
+ require 'cli/notes'
11
+ require 'cli/chord'
12
+ require 'cli/scale'
@@ -1,48 +1,33 @@
1
- # $LOAD_PATH << __dir__
1
+ require 'bundler'
2
+ Bundler.require
2
3
 
3
- # require 'bundler'
4
+ require 'facets/multiton'
5
+ require 'ostruct'
6
+ require 'paint'
4
7
 
5
- # Bundler.require
6
-
7
- # ActiveSupport::Inflector.inflections do |inflect|
8
- # inflect.irregular 'cache', 'caches'
9
- # end
10
-
11
- # ActiveRecord::Base.establish_connection YAML.load_file("#{__dir__}/../db/config.yml")['production']
12
- # require "#{__dir__}/../db/schema.rb"
8
+ require 'coltrane/version'
9
+ require 'coltrane/errors'
13
10
 
11
+ require 'coltrane/cache'
14
12
  require 'coltrane/cadence'
15
13
 
14
+ require 'coltrane/interval_sequence'
15
+ require 'coltrane/interval_set'
16
+ require 'coltrane/interval'
16
17
  require 'coltrane/qualities'
17
18
  require 'coltrane/chord_quality'
19
+ require 'coltrane/note_set'
18
20
  require 'coltrane/chord'
21
+ require 'coltrane/roman_chord'
19
22
 
20
23
  require 'coltrane/classic_scales'
21
- require 'coltrane/fret_set'
22
-
23
- # require 'coltrane/piano_representation'
24
- # require 'coltrane/guitar_representation'
25
- require 'coltrane/essential_guitar_chords'
26
- require 'coltrane/guitar_chord_finder'
27
- require 'coltrane/guitar_note_set'
28
- require 'coltrane/guitar_chord'
29
- require 'coltrane/guitar_note'
30
- require 'coltrane/guitar_string'
31
- require 'coltrane/guitar'
24
+ require 'coltrane/classic_progressions'
32
25
 
33
- require 'coltrane/interval_sequence'
34
- require 'coltrane/interval_set'
35
- require 'coltrane/interval'
26
+ require 'coltrane/piano_representation'
36
27
 
37
- require 'coltrane/note_set'
38
28
  require 'coltrane/note'
39
29
 
40
30
  require 'coltrane/pitch'
41
31
  require 'coltrane/progression'
42
32
  require 'coltrane/scale'
43
- require 'coltrane/mode'
44
-
45
- # require 'coltrane/scale_chord'
46
- # require 'coltrane/chord_cache'
47
- # require 'coltrane/scale_cache'
48
- # require 'terminal_input'
33
+ require 'coltrane/mode'
@@ -0,0 +1,34 @@
1
+ module Coltrane
2
+ class Cache
3
+ class << self
4
+ def find_or_record(key, &block)
5
+ if (cached = fetch(key))
6
+ return cached
7
+ else
8
+ cached = yield block
9
+ record(key, cached)
10
+ cached
11
+ end
12
+ end
13
+
14
+ private
15
+
16
+ def dir
17
+ dir = File.expand_path('../../../', __FILE__) + "/cache/"
18
+ Dir.mkdir(dir) unless Dir.exist?(dir)
19
+ dir
20
+ end
21
+
22
+ def fetch(key)
23
+ return unless File.file?(dir+key)
24
+ Marshal.load File.read(dir+key)
25
+ end
26
+
27
+ def record(key, contents)
28
+ File.open(dir+key, "w") do |f|
29
+ f.write Marshal.dump(contents)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -1,52 +1,40 @@
1
1
  module Coltrane
2
2
  # It describe a chord
3
- class Chord
4
- attr_reader :root_note, :quality
5
-
6
- def initialize(arg)
7
- @root_note, @quality = case arg
8
- when String then note_and_quality_from_name(arg)
9
- when GuitarNoteSet then [arg.root_note, arg.chord_quality]
10
- when Array
11
- case arg[0]
12
- when String then note_and_quality_from_notes(*arg.map{|a| Note.new(a)})
13
- when Note then note_and_quality_from_notes(*arg)
14
- end
3
+ class Chord < NoteSet
4
+ attr_reader :root_note, :quality, :notes
5
+
6
+ def initialize(notes: nil, root_note: nil, quality: nil, name: nil)
7
+ if !notes.nil?
8
+ notes = NoteSet[*notes] if notes.is_a?(Array)
9
+ @notes = notes
10
+ @root_note = notes.first
11
+ @quality = ChordQuality.new(notes: notes)
12
+ elsif !root_note.nil? && !quality.nil?
13
+ @notes = quality.notes_for(root_note)
14
+ @root_note = root_note
15
+ @quality = quality
16
+ elsif !name.nil?
17
+ @root_note, @quality, @notes = parse_from_name(name)
18
+ else
19
+ raise WrongKeywords.new('[notes:] || [root_note:, quality:] || [name:]')
15
20
  end
16
21
  end
17
22
 
18
- def guitar_chords
19
- GuitarChordFinder.by_chord(self)
20
- end
21
-
22
- def guitar_notes_for_root
23
- root_note.guitar_notes
24
- end
25
-
26
23
  def name
27
- return '' if root_note.nil? || quality.nil?
24
+ return @notes.names.join('/') if !named?
28
25
  "#{root_note.name}#{quality.name}"
29
26
  end
30
27
 
31
- def notes
32
- quality.intervals.each_with_object([]) do |interval, notes|
33
- notes << Note.new(root_note.number + interval)
34
- end
28
+ def named?
29
+ notes.size >= 3 &&
30
+ !root_note.nil? &&
31
+ !quality&.name.nil?
35
32
  end
36
33
 
37
34
  def intervals
38
35
  IntervalSequence.new(NoteSet.new(notes))
39
36
  end
40
37
 
41
- def on_guitar
42
- name + "\n" +
43
- NoteSet.new(notes).guitar_notes.render(root_note) + "\n\n"
44
- end
45
-
46
- def on_piano
47
- PianoRepresentation.render_intervals(notes, root_note)
48
- end
49
-
50
38
  def size
51
39
  notes.size
52
40
  end
@@ -69,13 +57,12 @@ module Coltrane
69
57
 
70
58
  protected
71
59
 
72
- def note_and_quality_from_name(chord_name)
73
- _, name, quality = chord_name.match(/([A-Z]#?)(.*)/).to_a
74
- [Note.new(name), ChordQuality.new_from_string(quality)]
75
- end
76
-
77
- def note_and_quality_from_notes(*notes)
78
- [notes.first, ChordQuality.new_from_notes(notes)]
60
+ def parse_from_name(name)
61
+ _, name, quality_name = name.match(/([A-Z]#?)(.*)/).to_a
62
+ root = Note[name]
63
+ quality = ChordQuality.new(name: quality_name)
64
+ notes = quality.notes_for(root)
65
+ [root, quality, notes]
79
66
  end
80
67
  end
81
68
  end