wtf_chord 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6b17613170273223d591f45e88733cf89449d8b872088496c7c569a7e053f4e0
4
- data.tar.gz: 10460d448a476b45fd1ea0c96b6b418458f7c123b83f9bc8f8d45593e6fd1b36
3
+ metadata.gz: 8d764a30038a11df351e6d3d9dfce09084aa89af352d92b36677ed5eac31e425
4
+ data.tar.gz: 0c713ee0f4e3b36720014f56c1c55ca6686981b49e228402d8c2f826463affce
5
5
  SHA512:
6
- metadata.gz: 37dc9a3b6f74eeb4615f8ff897982c0a4dff8577316415ce50cd9278968298d2c9a18d24ca885fde1f599c919b6547884983ab38e68f5ae49e33f6671436e8a9
7
- data.tar.gz: 8d326d7047187a13e778af13928a3e5ac847e56c57109817feab09b17d00a29c2d56a33c9d98d23946ccde52fa5cc65e158f14ee71e59470bdb57ec319514ccb
6
+ metadata.gz: e83f9de2c3913c4206d7d4f424f46f1082fcd16a50f8f19d4d78f2d83f3a66f610d23852ac66b890190f444db14e74d3fc9249514e6ece64626685c4c7434de2
7
+ data.tar.gz: 3ac9226adb773858a51c474c0f1b299e3bf2601ce567d9967c96b297ba4b0895da08c6b50e404221ba636be01d83740fea20b3f5684ebb24a2053b4b81235425
data/README.md CHANGED
@@ -91,6 +91,28 @@ You can get simpler output, using the `--output` option:
91
91
  [ 10 8 7 7 6 10 ]
92
92
  [ 10 8 7 7 10 10 ]
93
93
 
94
+ Starting with 0.7.0, we have the very nice piano formatting:
95
+
96
+ $ wtfchord Fdim -n 2 --output=piano
97
+ F1 - G♯1 - B1
98
+ ┌─┬─┬┬─┬─┬─┬─┬┬─┬┬─┬─┐
99
+ │ │ ││ │ │ │ ││█││ │ │
100
+ │ └┬┘└┬┘ │ └┬┘└┬┘└┬┘ │
101
+ │ │ │ │▐▌│ │ │▐▌│
102
+ └──┴──┴──┴──┴──┴──┴──┘
103
+
104
+
105
+
106
+
107
+ G♯1 - B1 - F2
108
+ ┌─┬─┬┬─┬─┬─┬─┬┬─┬┬─┬─┬─┬─┬┬─┬─┬─┬─┬┬─┬┬─┬─┐
109
+ │ │ ││ │ │ │ ││█││ │ │ │ ││ │ │ │ ││ ││ │ │
110
+ │ └┬┘└┬┘ │ └┬┘└┬┘└┬┘ │ └┬┘└┬┘ │ └┬┘└┬┘└┬┘ │
111
+ │ │ │ │ │ │ │▐▌│ │ │ │▐▌│ │ │ │
112
+ └──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┴──┘
113
+ ↑ ↑
114
+ Ⅰ Ⅱ
115
+
94
116
  ## Development
95
117
 
96
118
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. Run `bundle exec wtf_chord` to use the gem in this directory, ignoring other installed copies of this gem.
data/lib/wtf_chord.rb CHANGED
@@ -1,8 +1,13 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "wtf_chord/scale"
2
4
  require "wtf_chord/rules"
3
5
  require "wtf_chord/version"
4
6
 
5
7
  module WTFChord
8
+ autoload :Fretboard, "wtf_chord/fretboard"
9
+ autoload :Keyboard, "wtf_chord/keyboard"
10
+
6
11
  DEFAULTS = {
7
12
  :rules_file => File.expand_path("../wtf_chord.yml", __FILE__)
8
13
  }.freeze
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'wtf_chord/fingering'
2
- require 'wtf_chord/fingerings_generator'
4
+ require 'wtf_chord/collectors'
3
5
 
4
6
  module WTFChord
5
7
  class Chord
@@ -9,7 +11,7 @@ module WTFChord
9
11
 
10
12
  def initialize(note, name)
11
13
  @pitch = note.is_a?(Pitch) ? note : WTFChord.note(note)
12
- @name = "#{@pitch.key}#{name}".freeze
14
+ @name = "#{@pitch.key}#{name}"
13
15
  @steps = Array(WTFChord.rules[name])
14
16
  @notes = @steps.map { |dist| (@pitch + dist).note }.tap(&:uniq!)
15
17
 
@@ -18,8 +20,12 @@ module WTFChord
18
20
  end
19
21
  end
20
22
 
21
- def fingerings(limit = nil)
22
- FingeringsGenerator.new(self).call[0, limit || 5]
23
+ def fingerings(limit = nil, collector: Collectors::Guitar)
24
+ collector.new(self).call[0, limit || 5]
25
+ end
26
+
27
+ def tone
28
+ bass || original_bass
23
29
  end
24
30
 
25
31
  def third_tone
@@ -30,10 +36,25 @@ module WTFChord
30
36
  !!@bass
31
37
  end
32
38
 
39
+ def size
40
+ bass? ? -~@notes.size : @notes.size
41
+ end
42
+
33
43
  def original_bass
34
44
  @notes[0]
35
45
  end
36
46
 
47
+ def === other
48
+ case other
49
+ when InstrumentPitch
50
+ self === other.note
51
+ when Note
52
+ notes.include?(other) || (bass? && bass == other)
53
+ else
54
+ super
55
+ end
56
+ end
57
+
37
58
  def inspect
38
59
  "#{name} (#{@notes.map(&:key) * ' - '})"
39
60
  end
data/lib/wtf_chord/cli.rb CHANGED
@@ -12,10 +12,17 @@ module WTFChord
12
12
 
13
13
  debug { "Output using formatter: #{formatter.formatter.to_s}\n" }
14
14
 
15
- options['amount'] = 1 if options['output'] == 'piano'
15
+ options['amount'] = 1 if options['output'] == 'piano1'
16
16
 
17
- puts chord.inspect, nil
18
- puts formatter[*chord.fingerings(options['amount'])] * formatter.separator
17
+ collector =
18
+ case formatter.name when 'piano', 'piano1'
19
+ Collectors::Piano
20
+ else
21
+ Collectors::Guitar
22
+ end
23
+
24
+ fingerings = chord.fingerings(options['amount'], collector: collector)
25
+ puts formatter[*fingerings].uniq * formatter.separator
19
26
  end
20
27
 
21
28
  version VERSION
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "wtf_chord/collectors/generic"
4
+
5
+ module WTFChord
6
+ module Collectors
7
+ autoload :Guitar, "wtf_chord/collectors/guitar"
8
+ autoload :Piano, "wtf_chord/collectors/piano"
9
+ end
10
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WTFChord
4
+ class Chord
5
+ end
6
+
7
+ module Collectors
8
+ class Generic < DelegateClass(Chord)
9
+ singleton_class.attr_accessor :max_dist
10
+
11
+ def fingerings
12
+ @fingerings ||= []
13
+ end
14
+
15
+ def call
16
+ reset!
17
+ collect!
18
+ end
19
+
20
+ def reset!
21
+ fingerings.clear
22
+ end
23
+
24
+ def collect!
25
+ raise NotImplementedError
26
+ end
27
+
28
+ def max_dist
29
+ self.class.max_dist
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WTFChord
4
+ module Collectors
5
+ class Guitar < Generic
6
+ self.max_dist = 5
7
+
8
+ MAX_FRET = 7
9
+
10
+ def collect!
11
+ (0...MAX_FRET).each do |from|
12
+ to = from + max_dist
13
+ generate(from...to) do |variant|
14
+ fingerings << set_bass(variant) if filter_variant(variant)
15
+ end
16
+ end
17
+
18
+ fingerings.uniq!
19
+ fingerings.sort_by!(&:complexity)
20
+ end
21
+
22
+ private
23
+
24
+ def generate(fret_range)
25
+ combinations = WTFChord.guitar.strings.map.with_index do |s, index|
26
+ fret_range.
27
+ select { |dist| pitch = s.original + dist; has_note?(pitch) }.
28
+ tap { |frets| frets << nil if frets.size == 0 }
29
+ end
30
+
31
+ combinations.inject(&:product).each do |fingers|
32
+ fingers.flatten!
33
+
34
+ Fingering.new(WTFChord.guitar, fingers) do |variant|
35
+ adjust(variant)
36
+ yield(variant)
37
+ end
38
+ end
39
+ end
40
+
41
+ def filter_variant(variant)
42
+ used_strings = variant.used_strings
43
+ used_notes = used_strings.map(&:note)
44
+ tones_count = notes.map { |n| used_notes.count(n) }
45
+
46
+ !fingerings.include?(variant) &&
47
+ basetone?(used_strings[0]) &&
48
+ (
49
+ (third?(used_strings[1]) || third?(used_strings[2])) ||
50
+ (last?(used_strings[1]) || last?(used_strings[2]))
51
+ ) &&
52
+ all_notes?(used_notes) &&
53
+ used_notes.each_cons(2).none? { |(l, r)| l == r } &&
54
+ tones_count.all? { |n| tones_count[0] >= n || tones_count[notes.index(third_tone)] >= n } &&
55
+ variant.complexity <= 2.25
56
+ end
57
+
58
+ def adjust(fingering)
59
+ while (string = fingering.used_strings[0]) && !basetone?(string)
60
+ string.dead if !string.dead?
61
+ break if fingering.used_strings.size == 4
62
+ end
63
+ end
64
+
65
+ def set_bass(variant)
66
+ if bass?
67
+ variant.find_used_string_for(original_bass) do |idx, bass_string|
68
+ try_set_bass_on(variant, idx) or begin
69
+ 4.times do |i|
70
+ next if i == idx
71
+ if try_set_bass_on(variant, i)
72
+ bass_string.dead
73
+ break
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+
80
+ variant
81
+ end
82
+
83
+ def try_set_bass_on(variant, idx)
84
+ bass_string = variant.strings[idx].dup
85
+ distance = bass_string.distance_to(bass)
86
+
87
+ if distance >= 0 && distance < 5 && (distance - variant.min_fret) > -3
88
+ variant.extra_complexity += 0.5 if distance < variant.min_fret
89
+ variant.strings[idx].hold_on(distance)
90
+ true
91
+ end
92
+ end
93
+
94
+ def all_notes?(used_notes)
95
+ notes.all? { |n| used_notes.include?(n) }
96
+ end
97
+
98
+ def has_note?(value)
99
+ value = value.note if !value.is_a?(Note) && value.respond_to?(:note)
100
+ notes.include?(value)
101
+ end
102
+
103
+ def basetone?(string)
104
+ string && string.note == notes[0]
105
+ end
106
+
107
+ def third?(string)
108
+ string && (string.note == third_tone)
109
+ end
110
+
111
+ def last?(string)
112
+ string && (string.note == notes[-1])
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WTFChord
4
+ module Collectors
5
+ class Piano < Generic
6
+ self.max_dist = 12
7
+ MAX_OCTAVE = 5
8
+
9
+ CheckDist = -> ((a, b)) { (a.to_i - b.to_i).abs <= max_dist }
10
+
11
+ FilterProc = -> (actual_size, notes) {
12
+ notes.uniq(&:key).size == actual_size && notes.sort.each_cons(2).all?(&CheckDist)
13
+ }.curry(2).freeze
14
+
15
+ Distances = -> (notes) {
16
+ notes.each_cons(2).map { |(a, b)| (a.to_i - b.to_i).abs }
17
+ }
18
+
19
+ FirstTone = -> (tone, notes) {
20
+ Distances[notes]
21
+ }.curry(2).freeze
22
+
23
+ def collect!
24
+ @keyboard = Keyboard.new(1..MAX_OCTAVE)
25
+
26
+ @fingerings = @keyboard.keys.
27
+ grep(__getobj__).
28
+ combination(size).
29
+ filter(&FilterProc[size])
30
+ @fingerings.uniq! { |x| x.map(&:key) }
31
+ @fingerings.sort_by!(&FirstTone[tone])
32
+ @fingerings
33
+ end
34
+ end
35
+ end
36
+ end
@@ -34,6 +34,10 @@ module WTFChord
34
34
  strings.map(&:code).hash
35
35
  end
36
36
 
37
+ def used_keys
38
+ used_strings.map(&:note).map(&:key).uniq.join
39
+ end
40
+
37
41
  def complexity
38
42
  complexity_counter.rate
39
43
  end
@@ -2,6 +2,8 @@ require 'wtf_chord/formatters/base'
2
2
 
3
3
  module WTFChord
4
4
  class Formatter
5
+ attr_reader :name
6
+
5
7
  def initialize(name, with_rates = false)
6
8
  @name = name
7
9
  @with_rates = with_rates
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
 
3
5
  module WTFChord
@@ -9,7 +11,7 @@ module WTFChord
9
11
  def_delegators :@fret, :strings
10
12
 
11
13
  def self.separator
12
- "\n".freeze
14
+ "\n"
13
15
  end
14
16
 
15
17
  def self.to_proc
@@ -31,8 +33,8 @@ module WTFChord
31
33
  def draw
32
34
  end
33
35
 
34
- def keys
35
- strings.reject(&:dead?).map(&:key)
36
+ def actual_strings
37
+ strings.reject(&:dead?)
36
38
  end
37
39
 
38
40
  def with_rates?
@@ -1,24 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "wtf_chord/formatters/piano1"
3
4
  require "wtf_chord/keyboard"
4
5
 
5
6
  module WTFChord
6
7
  module Formatters
7
- class Piano < Base
8
+ class Piano < Piano1
8
9
  def draw
9
10
  [
10
- unique_notes.sort.join(" - "),
11
- Keyboard.press(*unique_notes.map(&:position))
12
- ].join("\n\n")
11
+ pressed_keys.map(&:to_s).join(" - "),
12
+ keyboard_presentation
13
+ ].join("\n")
13
14
  end
14
15
 
15
- def unique_notes
16
- strings.reject(&:dead?).each_with_object([[], []]) do |string, (positions, notes)|
17
- unless positions.include?(string.note.position)
18
- positions << string.note.position
19
- notes << string.note
20
- end
21
- end[1]
16
+ def keyboard_presentation
17
+ KeyboardPresentation.press(*pressed_keys)
22
18
  end
23
19
  end
24
20
  end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "wtf_chord/keyboard_presentation"
4
+
5
+ module WTFChord
6
+ module Formatters
7
+ class Piano1
8
+ def self.separator
9
+ "\n"
10
+ end
11
+
12
+ def self.to_proc
13
+ proc { |keys| new(keys).draw }
14
+ end
15
+
16
+ def self.with_rates(*)
17
+ yield(self)
18
+ end
19
+
20
+ attr_reader :pressed_keys
21
+
22
+ def initialize(keys)
23
+ @pressed_keys = keys
24
+ end
25
+
26
+ def draw
27
+ [
28
+ unique_notes.sort.join(" - "),
29
+ keyboard_presentation
30
+ ].join("\n\n")
31
+ end
32
+
33
+ def actual_notes
34
+ actual_strings.map(&:note)
35
+ end
36
+
37
+ def unique_notes
38
+ actual_notes.uniq(&:position)
39
+ end
40
+
41
+ def keyboard_presentation
42
+ KeyboardPresentation.press(*unique_notes.map(&:position))
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,14 +1,16 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'wtf_chord/guitar_string'
2
4
 
3
5
  module WTFChord
4
6
  class Fretboard
5
- DEAD = "×".freeze
7
+ DEAD = "×"
6
8
 
7
9
  attr_reader :strings, :capo
8
10
 
9
11
  def initialize(*strings)
10
12
  @capo = 0
11
- @strings = strings.map! { |string| GuitarString.new(string, @capo) }
13
+ @strings = strings.map { |string| GuitarString.new(string, @capo) }
12
14
  end
13
15
 
14
16
  def [] idx
@@ -1,14 +1,19 @@
1
- require 'wtf_chord/pitch'
1
+ # frozen_string_literal: true
2
+
3
+ require 'wtf_chord/instrument_pitch'
2
4
 
3
5
  module WTFChord
4
- class GuitarString < DelegateClass(WTFChord::Pitch)
6
+ class GuitarString < InstrumentPitch
5
7
  attr_reader :capo, :fret, :original
6
8
 
7
9
  def initialize(note, capo = 0)
8
- @original = WTFChord.note(note)
9
10
  @fret = nil
10
11
  @capo = capo
11
- super(@original + capo)
12
+ super(note)
13
+ end
14
+
15
+ def init_pitch
16
+ @original + capo
12
17
  end
13
18
 
14
19
  def initialize_dup(other)
@@ -58,8 +63,8 @@ module WTFChord
58
63
  def <=> other
59
64
  return if dead?
60
65
  case other
61
- when Integer then @fret <=> other
62
- when GuitarString then @fret <=> other.fret
66
+ when Integer then @fret <=> other
67
+ when GuitarString then @fret <=> other.fret
63
68
  end
64
69
  end
65
70
 
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'wtf_chord/pitch'
4
+
5
+ module WTFChord
6
+ class InstrumentPitch < DelegateClass(Pitch)
7
+ attr_reader :interval, :original
8
+
9
+ def initialize(note)
10
+ @original = WTFChord.note(note)
11
+ super(init_pitch)
12
+ end
13
+
14
+ def init_pitch
15
+ @original
16
+ end
17
+
18
+ def initialize_dup(other)
19
+ super
20
+ __setobj__(@original.dup)
21
+ end
22
+
23
+ def dead
24
+ end
25
+
26
+ def dead?
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def holded?
31
+ raise NotImplementedError
32
+ end
33
+
34
+ def distance_to(pitch)
35
+ pos = 0
36
+ pos += 1 while (original + pos) != pitch
37
+ pos
38
+ end
39
+
40
+ def code
41
+ to_i
42
+ end
43
+
44
+ def <=> other
45
+ case other
46
+ when Integer
47
+ to_i <=> other
48
+ when InstrumentPitch
49
+ to_i <=> other.to_i
50
+ end
51
+ end
52
+ end
53
+ end
@@ -1,47 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "wtf_chord/piano_key"
4
+
3
5
  module WTFChord
4
6
  class Keyboard
5
- FRAME = <<~EOF
6
- ┌─┬─┬┬─┬─┬─┬─┬┬─┬┬─┬─┐
7
- │ │ ││ │ │ │ ││ ││ │ │
8
- │ └┬┘└┬┘ │ └┬┘└┬┘└┬┘ │
9
- │ │ │ │ │ │ │ │
10
- └──┴──┴──┴──┴──┴──┴──┘
11
- EOF
7
+ attr_reader :keys
12
8
 
13
- Key = Struct.new(:offset)
14
- class Key
15
- singleton_class.attr_accessor :select_symbol
16
- singleton_class.alias_method :[], :new
9
+ KEYS = %w(C C# D D# E F F# G G# A Bb B)
17
10
 
18
- def select(frame)
19
- frame[offset] = self.class.select_symbol
20
- frame
21
- end
11
+ def initialize(octaves = 1..5)
12
+ @keys = octaves.to_a.product(KEYS).map { |(o, k)| PianoKey.new("#{k}#{o}") }
22
13
  end
23
14
 
24
- W = Class.new(Key) { self.select_symbol = "▐▌" }
25
- B = Class.new(Key) { self.select_symbol = "█" }
26
-
27
- KEYS = {
28
- 1 => W[70...72],
29
- 2 => B[26...27],
30
- 3 => W[73...75],
31
- 4 => B[29...30],
32
- 5 => W[76...78],
33
- 6 => W[79...81],
34
- 7 => B[35...36],
35
- 8 => W[82...84],
36
- 9 => B[38...39],
37
- 10 => W[85...87],
38
- 11 => B[41...42],
39
- 12 => W[88...90]
40
- }
41
-
42
- def self.press(*positions)
43
- positions.each_with_object(FRAME.dup) do |pos, frame|
44
- KEYS.fetch(pos).select(frame)
15
+ def [] idx
16
+ case idx
17
+ when 1..@keys.size then @keys[~-idx]
45
18
  end
46
19
  end
47
20
  end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'wtf_chord/helpers/roman_numbers_helper'
4
+
5
+ module WTFChord
6
+ class KeyboardPresentation
7
+ FRAME = <<~EOF
8
+ ┌─┬─┬┬─┬─┬─┬─┬┬─┬┬─┬─┐┬
9
+ │ │ ││ │ │ │ ││ ││ │ ││
10
+ │ └┬┘└┬┘ │ └┬┘└┬┘└┬┘ ││
11
+ │ │ │ │ │ │ │ ││
12
+ └──┴──┴──┴──┴──┴──┴──┘┴
13
+ ↑ |
14
+ %s |
15
+ |
16
+ EOF
17
+
18
+ include RomanNumbersHelper
19
+
20
+ KeyStruct = Struct.new(:index) do
21
+ singleton_class.attr_accessor :mark
22
+ singleton_class.alias_method :[], :new
23
+
24
+ def call(frame)
25
+ frame[index, mark.length] = mark
26
+ end
27
+
28
+ private def mark
29
+ self.class.mark
30
+ end
31
+ end
32
+
33
+ Key = -> (mark) { Class.new(KeyStruct).tap { |c| c.mark = mark } }
34
+ W, B = Key["▐▌"], Key["█"]
35
+
36
+ KEYS = [W[73], B[27], W[76], B[30], W[79], W[82], B[36], W[85], B[39], W[88], B[42], W[91]].
37
+ map.with_index(1).to_h.invert.freeze
38
+
39
+ def self.press(*positions)
40
+ if positions.all? { |v| Integer === v }
41
+ new.press(*positions)
42
+ else
43
+ positions.uniq(&:key).group_by(&:octave).inject(nil) do |a, (octave, strings)|
44
+ pos = strings.map { |s| s.note.position }
45
+ kb = new(octave).press(*pos)
46
+ a.nil? ? kb : a + kb
47
+ end
48
+ end
49
+ end
50
+
51
+ attr_reader :frame
52
+
53
+ def initialize(octave = 1)
54
+ @octave = octave
55
+ @frame = FRAME.dup % romanize(octave)
56
+ end
57
+
58
+ def +@
59
+ shift!
60
+ self
61
+ end
62
+
63
+ def +(other)
64
+ pop!
65
+ @frame.replace(@frame.lines(chomp: true).zip((+other).frame.lines).map(&:join).join)
66
+ self
67
+ end
68
+
69
+ def press(*positions)
70
+ positions.each { |pos| KEYS.fetch(pos).(@frame) }
71
+ self
72
+ end
73
+
74
+ def to_s
75
+ finalize!
76
+ end
77
+
78
+ def finalize!
79
+ @frame.gsub("|", " ").gsub(/[^\s](?=\n)/, "")
80
+ end
81
+
82
+ def pop!
83
+ @frame.replace(@frame.lines.each { |line| line[-3, 1] = "" }.join)
84
+ end
85
+
86
+ def shift!
87
+ @frame.replace(@frame.lines.each { |line| line[0, 1] = "" }.join)
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "wtf_chord/instrument_pitch"
4
+
5
+ module WTFChord
6
+ class PianoKey < InstrumentPitch
7
+ def pressed?
8
+ !!@pressed
9
+ end
10
+
11
+ def dead?
12
+ !pressed?
13
+ end
14
+
15
+ def press!
16
+ @pressed = true
17
+ end
18
+ end
19
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'wtf_chord/note'
2
4
  require 'wtf_chord/pitch'
3
5
  require 'wtf_chord/fretboard'
@@ -30,9 +32,9 @@ module WTFChord
30
32
 
31
33
  private_constant :ScaleArray
32
34
 
33
- FLAT ||= "b".freeze
34
- SHARP ||= "#".freeze
35
- SIGNS ||= { FLAT => "\u266D".freeze, SHARP => "\u266F".freeze }.freeze
35
+ FLAT ||= "b"
36
+ SHARP ||= "#"
37
+ SIGNS ||= { FLAT => "\u266D", SHARP => "\u266F" }.freeze
36
38
 
37
39
  DIATONIC = {
38
40
  "C" => "Do",
@@ -43,7 +45,7 @@ module WTFChord
43
45
  "A" => "La",
44
46
  "B" => "Si",
45
47
  "H" => "Si"
46
- }.freeze
48
+ }
47
49
 
48
50
  SCALE ||= begin
49
51
  chromatic_scale = %W(
@@ -60,7 +62,7 @@ module WTFChord
60
62
  Bb|A#
61
63
  B|H
62
64
  )
63
- ScaleArray.build(*chromatic_scale).freeze
65
+ ScaleArray.build(*chromatic_scale)
64
66
  end
65
67
 
66
68
  def self.guitar=(guitar)
@@ -70,4 +72,12 @@ module WTFChord
70
72
  def self.guitar
71
73
  Thread.current[:wtf_guitar] ||= Fretboard.new(*%w(E2 A2 D3 G3 B3 E4))
72
74
  end
75
+
76
+ def self.piano=(piano)
77
+ Thread.current[:wtf_piano] = piano
78
+ end
79
+
80
+ def self.piano
81
+ Thread.current[:wtf_piano] ||= Keyboard.new
82
+ end
73
83
  end
@@ -1,3 +1,3 @@
1
1
  module WTFChord
2
- VERSION = "0.6.0"
2
+ VERSION = "0.7.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wtf_chord
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anton
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-28 00:00:00.000000000 Z
11
+ date: 2021-03-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: methadone
@@ -94,19 +94,26 @@ files:
94
94
  - lib/wtf_chord.yml
95
95
  - lib/wtf_chord/chord.rb
96
96
  - lib/wtf_chord/cli.rb
97
+ - lib/wtf_chord/collectors.rb
98
+ - lib/wtf_chord/collectors/generic.rb
99
+ - lib/wtf_chord/collectors/guitar.rb
100
+ - lib/wtf_chord/collectors/piano.rb
97
101
  - lib/wtf_chord/complexity_counter.rb
98
102
  - lib/wtf_chord/fingering.rb
99
- - lib/wtf_chord/fingerings_generator.rb
100
103
  - lib/wtf_chord/formatter.rb
101
104
  - lib/wtf_chord/formatters/base.rb
102
105
  - lib/wtf_chord/formatters/default.rb
103
106
  - lib/wtf_chord/formatters/piano.rb
107
+ - lib/wtf_chord/formatters/piano1.rb
104
108
  - lib/wtf_chord/formatters/simple.rb
105
109
  - lib/wtf_chord/fretboard.rb
106
110
  - lib/wtf_chord/guitar_string.rb
107
111
  - lib/wtf_chord/helpers/roman_numbers_helper.rb
112
+ - lib/wtf_chord/instrument_pitch.rb
108
113
  - lib/wtf_chord/keyboard.rb
114
+ - lib/wtf_chord/keyboard_presentation.rb
109
115
  - lib/wtf_chord/note.rb
116
+ - lib/wtf_chord/piano_key.rb
110
117
  - lib/wtf_chord/pitch.rb
111
118
  - lib/wtf_chord/rules.rb
112
119
  - lib/wtf_chord/scale.rb
@@ -1,120 +0,0 @@
1
- module WTFChord
2
- class Chord
3
- end
4
-
5
- class FingeringsGenerator < DelegateClass(Chord)
6
- MAX_DIST = 5
7
- MAX_FRET = 7
8
-
9
- def fingerings
10
- @fingerings ||= []
11
- end
12
-
13
- def call
14
- fingerings.clear
15
-
16
- (0...MAX_FRET).each do |from|
17
- to = from + MAX_DIST
18
- generate(from...to) do |variant|
19
- fingerings << set_bass(variant) if filter_variant(variant)
20
- end
21
- end
22
-
23
- fingerings.uniq!
24
- fingerings.sort_by!(&:complexity)
25
- end
26
-
27
- private
28
-
29
- def generate(fret_range)
30
- combinations = WTFChord.guitar.strings.map.with_index do |s, index|
31
- fret_range.
32
- select { |dist| pitch = s.original + dist; has_note?(pitch) }.
33
- tap { |frets| frets << nil if frets.size == 0 }
34
- end
35
-
36
- combinations.inject(&:product).each do |fingers|
37
- fingers.flatten!
38
-
39
- Fingering.new(WTFChord.guitar, fingers) do |variant|
40
- adjust(variant)
41
- yield(variant)
42
- end
43
- end
44
- end
45
-
46
- def filter_variant(variant)
47
- used_strings = variant.used_strings
48
- used_notes = used_strings.map(&:note)
49
- tones_count = notes.map { |n| used_notes.count(n) }
50
-
51
- !fingerings.include?(variant) &&
52
- basetone?(used_strings[0]) &&
53
- (
54
- (third?(used_strings[1]) || third?(used_strings[2])) ||
55
- (last?(used_strings[1]) || last?(used_strings[2]))
56
- ) &&
57
- all_notes?(used_notes) &&
58
- used_notes.each_cons(2).none? { |(l, r)| l == r } &&
59
- tones_count.all? { |n| tones_count[0] >= n || tones_count[notes.index(third_tone)] >= n } &&
60
- variant.complexity <= 2.25
61
- end
62
-
63
- def adjust(fingering)
64
- while (string = fingering.used_strings[0]) && !basetone?(string)
65
- string.dead if !string.dead?
66
- break if fingering.used_strings.size == 4
67
- end
68
- end
69
-
70
- def set_bass(variant)
71
- if bass?
72
- variant.find_used_string_for(original_bass) do |idx, bass_string|
73
- try_set_bass_on(variant, idx) or begin
74
- 4.times do |i|
75
- next if i == idx
76
- if try_set_bass_on(variant, i)
77
- bass_string.dead
78
- break
79
- end
80
- end
81
- end
82
- end
83
- end
84
-
85
- variant
86
- end
87
-
88
- def try_set_bass_on(variant, idx)
89
- bass_string = variant.strings[idx].dup
90
- distance = bass_string.distance_to(bass)
91
-
92
- if distance >= 0 && distance < 5 && (distance - variant.min_fret) > -3
93
- variant.extra_complexity += 0.5 if distance < variant.min_fret
94
- variant.strings[idx].hold_on(distance)
95
- true
96
- end
97
- end
98
-
99
- def all_notes?(used_notes)
100
- notes.all? { |n| used_notes.include?(n) }
101
- end
102
-
103
- def has_note?(value)
104
- value = value.note if !value.is_a?(Note) && value.respond_to?(:note)
105
- notes.include?(value)
106
- end
107
-
108
- def basetone?(string)
109
- string && string.note == notes[0]
110
- end
111
-
112
- def third?(string)
113
- string && (string.note == third_tone)
114
- end
115
-
116
- def last?(string)
117
- string && (string.note == notes[-1])
118
- end
119
- end
120
- end