wtf_chord 0.3 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +10 -0
- data/README.md +34 -12
- data/lib/wtf_chord.rb +5 -0
- data/lib/wtf_chord/chord.rb +43 -9
- data/lib/wtf_chord/cli.rb +13 -5
- data/lib/wtf_chord/collectors.rb +10 -0
- data/lib/wtf_chord/collectors/generic.rb +33 -0
- data/lib/wtf_chord/collectors/guitar.rb +116 -0
- data/lib/wtf_chord/collectors/piano.rb +36 -0
- data/lib/wtf_chord/complexity_counter.rb +1 -1
- data/lib/wtf_chord/fingering.rb +24 -1
- data/lib/wtf_chord/formatter.rb +9 -5
- data/lib/wtf_chord/formatters/base.rb +7 -1
- data/lib/wtf_chord/formatters/default.rb +28 -27
- data/lib/wtf_chord/formatters/piano.rb +21 -0
- data/lib/wtf_chord/formatters/piano1.rb +46 -0
- data/lib/wtf_chord/fretboard.rb +12 -2
- data/lib/wtf_chord/guitar_string.rb +18 -6
- data/lib/wtf_chord/helpers/roman_numbers_helper.rb +11 -0
- data/lib/wtf_chord/instrument_pitch.rb +53 -0
- data/lib/wtf_chord/keyboard.rb +21 -0
- data/lib/wtf_chord/keyboard_presentation.rb +90 -0
- data/lib/wtf_chord/note.rb +1 -1
- data/lib/wtf_chord/piano_key.rb +19 -0
- data/lib/wtf_chord/pitch.rb +4 -0
- data/lib/wtf_chord/scale.rb +22 -6
- data/lib/wtf_chord/version.rb +1 -1
- data/wtf_chord.gemspec +2 -2
- metadata +19 -22
- data/lib/wtf_chord/fingerings_generator.rb +0 -85
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8d764a30038a11df351e6d3d9dfce09084aa89af352d92b36677ed5eac31e425
|
4
|
+
data.tar.gz: 0c713ee0f4e3b36720014f56c1c55ca6686981b49e228402d8c2f826463affce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e83f9de2c3913c4206d7d4f424f46f1082fcd16a50f8f19d4d78f2d83f3a66f610d23852ac66b890190f444db14e74d3fc9249514e6ece64626685c4c7434de2
|
7
|
+
data.tar.gz: 3ac9226adb773858a51c474c0f1b299e3bf2601ce567d9967c96b297ba4b0895da08c6b50e404221ba636be01d83740fea20b3f5684ebb24a2053b4b81235425
|
data/CHANGELOG.md
ADDED
data/README.md
CHANGED
@@ -31,13 +31,13 @@ Or install it yourself as:
|
|
31
31
|
|
32
32
|
Finds fingerings of the requested chord.
|
33
33
|
|
34
|
-
v0.
|
34
|
+
v0.3.1
|
35
35
|
|
36
36
|
Options:
|
37
37
|
-h, --help Show command line help
|
38
38
|
--version Show help/version info
|
39
39
|
-n, --amount N Amount of fingering variants to output.
|
40
|
-
(default:
|
40
|
+
(default: 5)
|
41
41
|
-o, --output FORMAT Output format.
|
42
42
|
(default: default)
|
43
43
|
-R, --[no-]rates Output fingering complexity rates
|
@@ -57,25 +57,25 @@ For example, to print two fingering variants of the `Dm` chord, just run:
|
|
57
57
|
And you'll get the visual presentation of chords' fingerings:
|
58
58
|
|
59
59
|
[ × × 0 2 3 1 ]
|
60
|
-
|
61
|
-
| | | | |
|
62
|
-
| | |
|
63
|
-
| | | |
|
60
|
+
——————————————————
|
61
|
+
| | | | | ◉
|
62
|
+
| | | ◉ | |
|
63
|
+
| | | | ◉ |
|
64
64
|
| | | | | |
|
65
65
|
| | | | | |
|
66
66
|
| | | | | |
|
67
|
-
|
67
|
+
——————————————————
|
68
68
|
D A D F
|
69
69
|
|
70
70
|
[ × 5 7 7 6 5 ]
|
71
|
-
|
72
|
-
|
|
73
|
-
| | | |
|
74
|
-
| |
|
71
|
+
——————————————————
|
72
|
+
| ◉ | | | ◉ ← V
|
73
|
+
| | | | ◉ |
|
74
|
+
| | ◉ ◉ | |
|
75
75
|
| | | | | |
|
76
76
|
| | | | | |
|
77
77
|
| | | | | |
|
78
|
-
|
78
|
+
——————————————————
|
79
79
|
D A D F A
|
80
80
|
|
81
81
|
You can get simpler output, using the `--output` option:
|
@@ -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
|
data/lib/wtf_chord/chord.rb
CHANGED
@@ -1,28 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'wtf_chord/fingering'
|
2
|
-
require 'wtf_chord/
|
4
|
+
require 'wtf_chord/collectors'
|
3
5
|
|
4
6
|
module WTFChord
|
5
7
|
class Chord
|
6
|
-
|
8
|
+
BASS_MATCH = /(?<=[\\\/])[A-H][b#]?(?=$)/
|
9
|
+
|
10
|
+
attr_reader :pitch, :steps, :notes, :name, :bass
|
7
11
|
|
8
12
|
def initialize(note, name)
|
9
13
|
@pitch = note.is_a?(Pitch) ? note : WTFChord.note(note)
|
10
|
-
@name = "#{@pitch.key}#{name}"
|
14
|
+
@name = "#{@pitch.key}#{name}"
|
11
15
|
@steps = Array(WTFChord.rules[name])
|
12
16
|
@notes = @steps.map { |dist| (@pitch + dist).note }.tap(&:uniq!)
|
17
|
+
|
18
|
+
BASS_MATCH.match(@name) do |m|
|
19
|
+
@bass = WTFChord.note(m[0]).note
|
20
|
+
end
|
13
21
|
end
|
14
22
|
|
15
|
-
def
|
16
|
-
|
23
|
+
def fingerings(limit = nil, collector: Collectors::Guitar)
|
24
|
+
collector.new(self).call[0, limit || 5]
|
17
25
|
end
|
18
26
|
|
19
|
-
def
|
20
|
-
|
21
|
-
FingeringsGenerator.new(self).call[0, limit]
|
27
|
+
def tone
|
28
|
+
bass || original_bass
|
22
29
|
end
|
23
30
|
|
24
31
|
def third_tone
|
25
|
-
@third_tone ||= @notes[@steps.
|
32
|
+
@third_tone ||= @notes[@steps.size > 3 ? 2 : -1]
|
33
|
+
end
|
34
|
+
|
35
|
+
def bass?
|
36
|
+
!!@bass
|
37
|
+
end
|
38
|
+
|
39
|
+
def size
|
40
|
+
bass? ? -~@notes.size : @notes.size
|
41
|
+
end
|
42
|
+
|
43
|
+
def original_bass
|
44
|
+
@notes[0]
|
45
|
+
end
|
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
|
+
|
58
|
+
def inspect
|
59
|
+
"#{name} (#{@notes.map(&:key) * ' - '})"
|
26
60
|
end
|
27
61
|
end
|
28
62
|
end
|
data/lib/wtf_chord/cli.rb
CHANGED
@@ -7,14 +7,22 @@ module WTFChord
|
|
7
7
|
include Methadone::CLILogging
|
8
8
|
|
9
9
|
main do |name|
|
10
|
-
chord
|
11
|
-
|
12
|
-
formatter = WTFChord::Formatter.new(options['output'], options['rates'])
|
10
|
+
chord = WTFChord.chord(name)
|
11
|
+
formatter = WTFChord::Formatter.new(options['output'], options['rates'])
|
13
12
|
|
14
13
|
debug { "Output using formatter: #{formatter.formatter.to_s}\n" }
|
15
14
|
|
16
|
-
|
17
|
-
|
15
|
+
options['amount'] = 1 if options['output'] == 'piano1'
|
16
|
+
|
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
|
18
26
|
end
|
19
27
|
|
20
28
|
version VERSION
|
@@ -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
|
data/lib/wtf_chord/fingering.rb
CHANGED
@@ -4,15 +4,18 @@ require 'wtf_chord/formatter'
|
|
4
4
|
|
5
5
|
module WTFChord
|
6
6
|
class Fingering < Fretboard
|
7
|
+
attr_accessor :extra_complexity
|
8
|
+
|
7
9
|
def initialize(guitar, fingers = nil)
|
8
10
|
@capo = guitar.capo
|
9
11
|
@strings = guitar.strings.map(&:dup)
|
12
|
+
@extra_complexity = 0.0
|
10
13
|
set_fingers(fingers) if fingers.is_a?(Array)
|
11
14
|
yield(self) if block_given?
|
12
15
|
end
|
13
16
|
|
14
17
|
def code
|
15
|
-
|
18
|
+
strings.map(&:code).pack("c*")
|
16
19
|
end
|
17
20
|
|
18
21
|
def == other
|
@@ -23,6 +26,18 @@ module WTFChord
|
|
23
26
|
end
|
24
27
|
end
|
25
28
|
|
29
|
+
def eql?(other)
|
30
|
+
super || self == other
|
31
|
+
end
|
32
|
+
|
33
|
+
def hash
|
34
|
+
strings.map(&:code).hash
|
35
|
+
end
|
36
|
+
|
37
|
+
def used_keys
|
38
|
+
used_strings.map(&:note).map(&:key).uniq.join
|
39
|
+
end
|
40
|
+
|
26
41
|
def complexity
|
27
42
|
complexity_counter.rate
|
28
43
|
end
|
@@ -43,6 +58,14 @@ module WTFChord
|
|
43
58
|
@strings.reject(&:dead?)
|
44
59
|
end
|
45
60
|
|
61
|
+
def find_used_string_for(note)
|
62
|
+
used = used_strings
|
63
|
+
if idx = used.index(note)
|
64
|
+
string = used[idx]
|
65
|
+
yield(strings.index(string), string)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
46
69
|
def min_fret
|
47
70
|
holded_strings.min.fret
|
48
71
|
end
|
data/lib/wtf_chord/formatter.rb
CHANGED
@@ -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
|
@@ -9,19 +11,21 @@ module WTFChord
|
|
9
11
|
end
|
10
12
|
|
11
13
|
def call(*fingerings)
|
12
|
-
formatter.with_rates(@with_rates)
|
13
|
-
puts (fingerings.map(&f) * f.separator)
|
14
|
-
end
|
14
|
+
formatter.with_rates(@with_rates) { |f| fingerings.map(&f) }
|
15
15
|
end
|
16
16
|
|
17
17
|
alias :[] :call
|
18
18
|
|
19
|
+
def separator
|
20
|
+
formatter.separator
|
21
|
+
end
|
22
|
+
|
19
23
|
def formatter
|
20
|
-
|
24
|
+
Formatters.const_get(formatter_name)
|
21
25
|
end
|
22
26
|
|
23
27
|
def formatter?
|
24
|
-
|
28
|
+
Formatters.const_defined?(formatter_name)
|
25
29
|
end
|
26
30
|
|
27
31
|
def formatter_name
|
@@ -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"
|
14
|
+
"\n"
|
13
15
|
end
|
14
16
|
|
15
17
|
def self.to_proc
|
@@ -31,6 +33,10 @@ module WTFChord
|
|
31
33
|
def draw
|
32
34
|
end
|
33
35
|
|
36
|
+
def actual_strings
|
37
|
+
strings.reject(&:dead?)
|
38
|
+
end
|
39
|
+
|
34
40
|
def with_rates?
|
35
41
|
!!@with_rates
|
36
42
|
end
|
@@ -1,25 +1,28 @@
|
|
1
|
+
require 'wtf_chord/helpers/roman_numbers_helper'
|
2
|
+
|
1
3
|
module WTFChord
|
2
4
|
module Formatters
|
3
5
|
class Default < Base
|
4
6
|
OPEN = "|".freeze
|
5
|
-
HORIZ = "
|
7
|
+
HORIZ = "—".freeze
|
6
8
|
SPACE = " ".freeze
|
7
|
-
BULL = "\
|
8
|
-
LATIN = %w(0 I II III IV V VI VII VIII IX X XII XIII).freeze
|
9
|
+
BULL = "\u25C9".freeze
|
9
10
|
NEWLINE = "\n".freeze
|
11
|
+
COMPLEXITY_FORMAT = " (complexity: %.2f)".freeze
|
12
|
+
EMPTY_STRING = Array.new(FRETS, OPEN).freeze
|
10
13
|
|
11
|
-
|
12
|
-
"\n\n".freeze
|
13
|
-
end
|
14
|
+
include RomanNumbersHelper
|
14
15
|
|
15
16
|
def draw
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
"
|
22
|
-
|
17
|
+
<<-EOF.gsub(/^\s+\|/, '')
|
18
|
+
|[ #{head} ] #{capo}
|
19
|
+
| #{border}
|
20
|
+
#{fret_rows.map.with_index { |row, index|
|
21
|
+
" | #{row} #{roman_min_fret if index == 0}"
|
22
|
+
}.join("\n")}
|
23
|
+
| #{border}
|
24
|
+
| #{string_keys}#{rate}
|
25
|
+
EOF
|
23
26
|
end
|
24
27
|
|
25
28
|
private
|
@@ -29,11 +32,15 @@ module WTFChord
|
|
29
32
|
end
|
30
33
|
|
31
34
|
def capo
|
32
|
-
"
|
35
|
+
"(capo #{romanize(@fret.capo)})" if @fret.capo > 0
|
36
|
+
end
|
37
|
+
|
38
|
+
def roman_min_fret
|
39
|
+
" ← #{romanize(min_fret)}" if min_fret > 1
|
33
40
|
end
|
34
41
|
|
35
42
|
def rate
|
36
|
-
|
43
|
+
COMPLEXITY_FORMAT % @fret.complexity if with_rates?
|
37
44
|
end
|
38
45
|
|
39
46
|
def border
|
@@ -41,27 +48,21 @@ module WTFChord
|
|
41
48
|
end
|
42
49
|
|
43
50
|
def string_rows
|
44
|
-
strings.map { |string| draw_string(string
|
51
|
+
strings.map { |string| draw_string(string) }
|
45
52
|
end
|
46
53
|
|
47
54
|
def fret_rows
|
48
|
-
|
49
|
-
|
50
|
-
end
|
55
|
+
return to_enum(__method__) unless block_given?
|
56
|
+
string_rows.transpose.each { |row| yield(row * SPACE) }
|
51
57
|
end
|
52
58
|
|
53
59
|
def string_keys
|
54
60
|
strings.map { |s| s.dead? ? SPACE : "%-2s" % s.key } * " "
|
55
61
|
end
|
56
62
|
|
57
|
-
def
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
def draw_string(fret)
|
62
|
-
Array.new(FRETS, OPEN).tap do |rows|
|
63
|
-
rows[(fret - min_fret.pred).pred] = BULL if fret.to_i > 0
|
64
|
-
end
|
63
|
+
def draw_string(string)
|
64
|
+
EMPTY_STRING.dup.
|
65
|
+
tap { |rows| rows[string.fret - min_fret] = BULL if string.holded? }
|
65
66
|
end
|
66
67
|
end
|
67
68
|
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "wtf_chord/formatters/piano1"
|
4
|
+
require "wtf_chord/keyboard"
|
5
|
+
|
6
|
+
module WTFChord
|
7
|
+
module Formatters
|
8
|
+
class Piano < Piano1
|
9
|
+
def draw
|
10
|
+
[
|
11
|
+
pressed_keys.map(&:to_s).join(" - "),
|
12
|
+
keyboard_presentation
|
13
|
+
].join("\n")
|
14
|
+
end
|
15
|
+
|
16
|
+
def keyboard_presentation
|
17
|
+
KeyboardPresentation.press(*pressed_keys)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
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
|
data/lib/wtf_chord/fretboard.rb
CHANGED
@@ -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 = "×"
|
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
|
13
|
+
@strings = strings.map { |string| GuitarString.new(string, @capo) }
|
12
14
|
end
|
13
15
|
|
14
16
|
def [] idx
|
@@ -23,6 +25,14 @@ module WTFChord
|
|
23
25
|
self
|
24
26
|
end
|
25
27
|
|
28
|
+
def with_capo(capo)
|
29
|
+
capo_was = @capo
|
30
|
+
set_capo(capo)
|
31
|
+
yield
|
32
|
+
ensure
|
33
|
+
set_capo(capo_was)
|
34
|
+
end
|
35
|
+
|
26
36
|
def fingers
|
27
37
|
@strings.map { |string| string.dead? ? DEAD : string.fret }
|
28
38
|
end
|
@@ -1,14 +1,19 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'wtf_chord/instrument_pitch'
|
2
4
|
|
3
5
|
module WTFChord
|
4
|
-
class GuitarString <
|
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(
|
12
|
+
super(note)
|
13
|
+
end
|
14
|
+
|
15
|
+
def init_pitch
|
16
|
+
@original + capo
|
12
17
|
end
|
13
18
|
|
14
19
|
def initialize_dup(other)
|
@@ -48,11 +53,18 @@ module WTFChord
|
|
48
53
|
dead? ? -1 : to_i
|
49
54
|
end
|
50
55
|
|
56
|
+
def distance_to(pitch)
|
57
|
+
pos = 0
|
58
|
+
opened = dup.open
|
59
|
+
pos += 1 while (opened + pos) != pitch
|
60
|
+
pos
|
61
|
+
end
|
62
|
+
|
51
63
|
def <=> other
|
52
64
|
return if dead?
|
53
65
|
case other
|
54
|
-
when Integer
|
55
|
-
when GuitarString
|
66
|
+
when Integer then @fret <=> other
|
67
|
+
when GuitarString then @fret <=> other.fret
|
56
68
|
end
|
57
69
|
end
|
58
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
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "wtf_chord/piano_key"
|
4
|
+
|
5
|
+
module WTFChord
|
6
|
+
class Keyboard
|
7
|
+
attr_reader :keys
|
8
|
+
|
9
|
+
KEYS = %w(C C# D D# E F F# G G# A Bb B)
|
10
|
+
|
11
|
+
def initialize(octaves = 1..5)
|
12
|
+
@keys = octaves.to_a.product(KEYS).map { |(o, k)| PianoKey.new("#{k}#{o}") }
|
13
|
+
end
|
14
|
+
|
15
|
+
def [] idx
|
16
|
+
case idx
|
17
|
+
when 1..@keys.size then @keys[~-idx]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
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
|
data/lib/wtf_chord/note.rb
CHANGED
@@ -25,7 +25,7 @@ module WTFChord
|
|
25
25
|
|
26
26
|
def == other
|
27
27
|
case other
|
28
|
-
when String then other.casecmp(@key).zero?
|
28
|
+
when String then other.casecmp(@key).zero? || aliases.any? { |a| other.casecmp(a.key).zero? }
|
29
29
|
when Integer then other == @position
|
30
30
|
when Note then other.position == @position
|
31
31
|
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
|
data/lib/wtf_chord/pitch.rb
CHANGED
data/lib/wtf_chord/scale.rb
CHANGED
@@ -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"
|
34
|
-
SHARP ||= "#"
|
35
|
-
SIGNS ||= { FLAT => "\u266D"
|
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
|
-
}
|
48
|
+
}
|
47
49
|
|
48
50
|
SCALE ||= begin
|
49
51
|
chromatic_scale = %W(
|
@@ -60,8 +62,22 @@ module WTFChord
|
|
60
62
|
Bb|A#
|
61
63
|
B|H
|
62
64
|
)
|
63
|
-
ScaleArray.build(*chromatic_scale)
|
65
|
+
ScaleArray.build(*chromatic_scale)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.guitar=(guitar)
|
69
|
+
Thread.current[:wtf_guitar] = guitar
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.guitar
|
73
|
+
Thread.current[:wtf_guitar] ||= Fretboard.new(*%w(E2 A2 D3 G3 B3 E4))
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.piano=(piano)
|
77
|
+
Thread.current[:wtf_piano] = piano
|
64
78
|
end
|
65
79
|
|
66
|
-
|
80
|
+
def self.piano
|
81
|
+
Thread.current[:wtf_piano] ||= Keyboard.new
|
82
|
+
end
|
67
83
|
end
|
data/lib/wtf_chord/version.rb
CHANGED
data/wtf_chord.gemspec
CHANGED
@@ -22,7 +22,7 @@ Gem::Specification.new do |spec|
|
|
22
22
|
|
23
23
|
spec.add_runtime_dependency "methadone"
|
24
24
|
|
25
|
-
spec.add_development_dependency "bundler"
|
26
|
-
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "bundler"
|
26
|
+
spec.add_development_dependency "rake"
|
27
27
|
spec.add_development_dependency "rspec", ">= 3.0", "< 4"
|
28
28
|
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:
|
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:
|
11
|
+
date: 2021-03-29 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: methadone
|
@@ -30,40 +30,28 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
34
|
-
- - "<"
|
35
|
-
- !ruby/object:Gem::Version
|
36
|
-
version: '2'
|
33
|
+
version: '0'
|
37
34
|
type: :development
|
38
35
|
prerelease: false
|
39
36
|
version_requirements: !ruby/object:Gem::Requirement
|
40
37
|
requirements:
|
41
38
|
- - ">="
|
42
39
|
- !ruby/object:Gem::Version
|
43
|
-
version: '
|
44
|
-
- - "<"
|
45
|
-
- !ruby/object:Gem::Version
|
46
|
-
version: '2'
|
40
|
+
version: '0'
|
47
41
|
- !ruby/object:Gem::Dependency
|
48
42
|
name: rake
|
49
43
|
requirement: !ruby/object:Gem::Requirement
|
50
44
|
requirements:
|
51
45
|
- - ">="
|
52
46
|
- !ruby/object:Gem::Version
|
53
|
-
version: '
|
54
|
-
- - "<"
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: '12'
|
47
|
+
version: '0'
|
57
48
|
type: :development
|
58
49
|
prerelease: false
|
59
50
|
version_requirements: !ruby/object:Gem::Requirement
|
60
51
|
requirements:
|
61
52
|
- - ">="
|
62
53
|
- !ruby/object:Gem::Version
|
63
|
-
version: '
|
64
|
-
- - "<"
|
65
|
-
- !ruby/object:Gem::Version
|
66
|
-
version: '12'
|
54
|
+
version: '0'
|
67
55
|
- !ruby/object:Gem::Dependency
|
68
56
|
name: rspec
|
69
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -95,6 +83,7 @@ files:
|
|
95
83
|
- ".gitignore"
|
96
84
|
- ".rspec"
|
97
85
|
- ".travis.yml"
|
86
|
+
- CHANGELOG.md
|
98
87
|
- Gemfile
|
99
88
|
- README.md
|
100
89
|
- Rakefile
|
@@ -105,16 +94,26 @@ files:
|
|
105
94
|
- lib/wtf_chord.yml
|
106
95
|
- lib/wtf_chord/chord.rb
|
107
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
|
108
101
|
- lib/wtf_chord/complexity_counter.rb
|
109
102
|
- lib/wtf_chord/fingering.rb
|
110
|
-
- lib/wtf_chord/fingerings_generator.rb
|
111
103
|
- lib/wtf_chord/formatter.rb
|
112
104
|
- lib/wtf_chord/formatters/base.rb
|
113
105
|
- lib/wtf_chord/formatters/default.rb
|
106
|
+
- lib/wtf_chord/formatters/piano.rb
|
107
|
+
- lib/wtf_chord/formatters/piano1.rb
|
114
108
|
- lib/wtf_chord/formatters/simple.rb
|
115
109
|
- lib/wtf_chord/fretboard.rb
|
116
110
|
- lib/wtf_chord/guitar_string.rb
|
111
|
+
- lib/wtf_chord/helpers/roman_numbers_helper.rb
|
112
|
+
- lib/wtf_chord/instrument_pitch.rb
|
113
|
+
- lib/wtf_chord/keyboard.rb
|
114
|
+
- lib/wtf_chord/keyboard_presentation.rb
|
117
115
|
- lib/wtf_chord/note.rb
|
116
|
+
- lib/wtf_chord/piano_key.rb
|
118
117
|
- lib/wtf_chord/pitch.rb
|
119
118
|
- lib/wtf_chord/rules.rb
|
120
119
|
- lib/wtf_chord/scale.rb
|
@@ -138,10 +137,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
138
137
|
- !ruby/object:Gem::Version
|
139
138
|
version: '0'
|
140
139
|
requirements: []
|
141
|
-
|
142
|
-
rubygems_version: 2.5.1
|
140
|
+
rubygems_version: 3.0.3
|
143
141
|
signing_key:
|
144
142
|
specification_version: 4
|
145
143
|
summary: "‘WTF Chord?’ is the Ruby guitar chords generator library."
|
146
144
|
test_files: []
|
147
|
-
has_rdoc:
|
@@ -1,85 +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 << variant if filter_variant(variant)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
fingerings.sort_by!(&:complexity)
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def generate(fret_range)
|
29
|
-
combinations = GUITAR.strings.map.with_index do |s, index|
|
30
|
-
fret_range.select do |dist|
|
31
|
-
pitch = s.original + dist
|
32
|
-
has_note?(pitch)
|
33
|
-
end.tap do |frets|
|
34
|
-
frets << nil if frets.size == 0
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
combinations.inject(&:product).each do |fingers|
|
39
|
-
fingers.flatten!
|
40
|
-
Fingering.new(GUITAR, fingers) do |variant|
|
41
|
-
adjust(variant)
|
42
|
-
yield(variant)
|
43
|
-
end
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def filter_variant(variant)
|
48
|
-
used_strings = variant.used_strings
|
49
|
-
used_notes = used_strings.map(&:note)
|
50
|
-
tones_count = notes.map { |n| used_notes.count(n) }
|
51
|
-
|
52
|
-
!fingerings.include?(variant) &&
|
53
|
-
basetone?(used_strings[0]) &&
|
54
|
-
(third?(used_strings[1]) || third?(used_strings[2])) &&
|
55
|
-
all_notes?(used_notes) &&
|
56
|
-
used_notes.each_cons(2).none? { |(l, r)| l == r } &&
|
57
|
-
tones_count.all? { |n| tones_count[0] >= n || tones_count[notes.index(third_tone)] >= n } &&
|
58
|
-
variant.complexity <= 2.25
|
59
|
-
end
|
60
|
-
|
61
|
-
def adjust(fingering)
|
62
|
-
while (string = fingering.used_strings[0]) && !basetone?(string)
|
63
|
-
string.dead if !string.dead?
|
64
|
-
break if fingering.used_strings.size == 4
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
def all_notes?(used_notes)
|
69
|
-
notes.all? { |n| used_notes.include?(n) }
|
70
|
-
end
|
71
|
-
|
72
|
-
def has_note?(value)
|
73
|
-
value = value.note if !value.is_a?(Note) && value.respond_to?(:note)
|
74
|
-
notes.include?(value)
|
75
|
-
end
|
76
|
-
|
77
|
-
def basetone?(string)
|
78
|
-
string && string.note == notes[0]
|
79
|
-
end
|
80
|
-
|
81
|
-
def third?(string)
|
82
|
-
string && (string.note == third_tone)
|
83
|
-
end
|
84
|
-
end
|
85
|
-
end
|