wtf_chord 0.3 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|