guitar 0.2.0 → 0.3.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 +4 -4
- data/Gemfile.lock +10 -2
- data/guitar.gemspec +2 -0
- data/lib/guitar.rb +18 -23
- data/lib/guitar/commands/console.rb +3 -0
- data/lib/guitar/commands/practice.rb +8 -0
- data/lib/guitar/{common.rb → fretboard/common.rb} +0 -0
- data/lib/guitar/{fretboard.rb → fretboard/fretboard.rb} +19 -11
- data/lib/guitar/fretboard/graphic.rb +26 -0
- data/lib/guitar/fretboard/string.rb +122 -0
- data/lib/guitar/{tonic_shape.rb → fretboard/tonic_shape.rb} +5 -5
- data/lib/guitar/{note.rb → music_theory/note.rb} +20 -4
- data/lib/guitar/{scale.rb → music_theory/scale.rb} +5 -1
- data/lib/guitar/practice.rb +67 -14
- data/lib/guitar/sonic_pi/client.rb +34 -0
- data/lib/guitar/sonic_pi/play.rb +59 -0
- data/lib/guitar/sonic_pi/server.rb +88 -0
- data/lib/guitar/version.rb +1 -1
- metadata +40 -9
- data/lib/guitar/core_ext/object.rb +0 -5
- data/lib/guitar/string.rb +0 -102
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9f253f238418bab4eb199b92e06e4c9f1ec7c318
|
|
4
|
+
data.tar.gz: 9744d485dc9ef0a77cfbd883feebf7d87715a325
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3e76ac281246a54a03b6ddcd34f4e3ed0a43a9fed1f6516852f9e33418f736fd5d1e3761975cc66ba80056e7b2543dc86f42a2247a7b62ffe3439fa516817f8
|
|
7
|
+
data.tar.gz: 11e029482d350bcde0d2f3882a924b509fbfdf64349264ad64c016f89ca5937faf1061101002e151c0afa57fc0595f39afccec349e97ec928a7827a1acf09610
|
data/Gemfile.lock
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
guitar (0.
|
|
5
|
-
colorize (
|
|
4
|
+
guitar (0.3.0)
|
|
5
|
+
colorize (~> 0.8.1)
|
|
6
|
+
fast_osc (~> 0.0.12)
|
|
6
7
|
|
|
7
8
|
GEM
|
|
8
9
|
remote: https://rubygems.org/
|
|
9
10
|
specs:
|
|
11
|
+
coderay (1.1.1)
|
|
10
12
|
colorize (0.8.1)
|
|
13
|
+
fast_osc (0.0.12)
|
|
14
|
+
method_source (0.9.0)
|
|
11
15
|
minitest (5.9.1)
|
|
16
|
+
pry (0.11.3)
|
|
17
|
+
coderay (~> 1.1.0)
|
|
18
|
+
method_source (~> 0.9.0)
|
|
12
19
|
rake (10.4.2)
|
|
13
20
|
|
|
14
21
|
PLATFORMS
|
|
@@ -18,6 +25,7 @@ DEPENDENCIES
|
|
|
18
25
|
bundler (~> 1.16)
|
|
19
26
|
guitar!
|
|
20
27
|
minitest (~> 5.0)
|
|
28
|
+
pry (~> 0.11.3)
|
|
21
29
|
rake (~> 10.0)
|
|
22
30
|
|
|
23
31
|
BUNDLED WITH
|
data/guitar.gemspec
CHANGED
|
@@ -21,7 +21,9 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
spec.require_paths = ["lib"]
|
|
22
22
|
|
|
23
23
|
spec.add_dependency('colorize', '~> 0.8.1')
|
|
24
|
+
spec.add_dependency('fast_osc', '~> 0.0.12')
|
|
24
25
|
|
|
26
|
+
spec.add_development_dependency "pry", "~> 0.11.3"
|
|
25
27
|
spec.add_development_dependency "bundler", "~> 1.16"
|
|
26
28
|
spec.add_development_dependency "rake", "~> 10.0"
|
|
27
29
|
spec.add_development_dependency "minitest", "~> 5.0"
|
data/lib/guitar.rb
CHANGED
|
@@ -1,31 +1,26 @@
|
|
|
1
|
-
|
|
1
|
+
# load to_n first
|
|
2
|
+
require 'guitar/music_theory/note'
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
require 'guitar/string'
|
|
7
|
-
require 'guitar/fretboard'
|
|
8
|
-
require 'guitar/practice'
|
|
4
|
+
module Guitar
|
|
5
|
+
autoload :VERSION, 'guitar/version'
|
|
6
|
+
autoload :Practice, 'guitar/practice'
|
|
9
7
|
|
|
10
|
-
|
|
8
|
+
autoload :Scale, 'guitar/music_theory/scale'
|
|
11
9
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
'.split("\n")[1..-2].freeze
|
|
10
|
+
autoload :Graphic, 'guitar/fretboard/graphic'
|
|
11
|
+
autoload :Common, 'guitar/fretboard/common'
|
|
12
|
+
autoload :String, 'guitar/fretboard/string'
|
|
13
|
+
autoload :Fretboard, 'guitar/fretboard/fretboard'
|
|
14
|
+
autoload :TonicShape, 'guitar/fretboard/tonic_shape'
|
|
15
|
+
|
|
16
|
+
autoload :Server, 'guitar/sonic_pi/server'
|
|
17
|
+
autoload :Client, 'guitar/sonic_pi/client'
|
|
18
|
+
autoload :Play, 'guitar/sonic_pi/play.rb'
|
|
22
19
|
|
|
23
|
-
# Fretboard
|
|
20
|
+
# Fretboard size
|
|
24
21
|
DEFAULT_SIZE = 13
|
|
25
|
-
# Text count in one fret
|
|
26
|
-
WIDTH = 6
|
|
27
22
|
|
|
28
|
-
def self.
|
|
29
|
-
|
|
23
|
+
def self.debug?
|
|
24
|
+
!!(ENV['DEBUG'])
|
|
30
25
|
end
|
|
31
26
|
end
|
|
@@ -46,6 +46,9 @@ module Guitar
|
|
|
46
46
|
opts.separator ''
|
|
47
47
|
opts.separator ' 9. Random NOTE(natural) in FRETBOARD.'
|
|
48
48
|
opts.separator ' 10. Random NOTE in FRETBOARD.'
|
|
49
|
+
opts.separator ''
|
|
50
|
+
opts.separator ' 11. Mark random NOTE(natural) in FRETBOARD.'
|
|
51
|
+
opts.separator ' 12. Mark random NOTE in FRETBOARD.'
|
|
49
52
|
end
|
|
50
53
|
parser.parse!(args)
|
|
51
54
|
|
|
@@ -76,6 +79,11 @@ module Guitar
|
|
|
76
79
|
Guitar::Practice.new(options).random_note_in_fretboard
|
|
77
80
|
when 10
|
|
78
81
|
Guitar::Practice.new(options).random_note_in_fretboard
|
|
82
|
+
when 11
|
|
83
|
+
options.merge!(natural: true)
|
|
84
|
+
Guitar::Practice.new(options).mark_random_note_in_fretboard
|
|
85
|
+
when 12
|
|
86
|
+
Guitar::Practice.new(options).mark_random_note_in_fretboard
|
|
79
87
|
else
|
|
80
88
|
puts parser
|
|
81
89
|
end
|
|
File without changes
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
require 'guitar/common'
|
|
2
|
-
require 'guitar/tonic_shape'
|
|
3
|
-
|
|
4
1
|
module Guitar
|
|
5
2
|
class Fretboard
|
|
6
3
|
include Common
|
|
@@ -14,12 +11,12 @@ module Guitar
|
|
|
14
11
|
@desc = desc
|
|
15
12
|
@changes = []
|
|
16
13
|
@strings = [
|
|
17
|
-
String.new(:
|
|
18
|
-
String.new(:
|
|
19
|
-
String.new(:
|
|
20
|
-
String.new(:
|
|
21
|
-
String.new(:
|
|
22
|
-
String.new(:
|
|
14
|
+
String.new(:E4, Graphic.get_frets(1, size)),
|
|
15
|
+
String.new(:B3, Graphic.get_frets(2, size)),
|
|
16
|
+
String.new(:G3, Graphic.get_frets(3, size)),
|
|
17
|
+
String.new(:D3, Graphic.get_frets(4, size)),
|
|
18
|
+
String.new(:A2, Graphic.get_frets(5, size)),
|
|
19
|
+
String.new(:E2, Graphic.get_frets(6, size))
|
|
23
20
|
]
|
|
24
21
|
end
|
|
25
22
|
|
|
@@ -47,6 +44,17 @@ module Guitar
|
|
|
47
44
|
end
|
|
48
45
|
end
|
|
49
46
|
|
|
47
|
+
def play(note = nil, string: nil, fret: nil, color: nil)
|
|
48
|
+
make_change do
|
|
49
|
+
if string
|
|
50
|
+
@strings[string - 1].play(note, fret: fret, color: color)
|
|
51
|
+
else
|
|
52
|
+
# TODO find string to play note
|
|
53
|
+
fail 'no string or note to play'
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
50
58
|
def undo
|
|
51
59
|
if last_change = changes.pop
|
|
52
60
|
last_change.each { |i| @strings[i].undo }
|
|
@@ -63,9 +71,9 @@ module Guitar
|
|
|
63
71
|
|
|
64
72
|
def inspect
|
|
65
73
|
string_lines = @strings.collect(&:inspect).join("\n")
|
|
66
|
-
|
|
74
|
+
baseline = Graphic.get_baseline(@size)
|
|
67
75
|
|
|
68
|
-
"#@desc\n#{string_lines}\n #{
|
|
76
|
+
"#@desc\n#{string_lines}\n #{baseline}"
|
|
69
77
|
end
|
|
70
78
|
|
|
71
79
|
private
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
require 'colorize'
|
|
2
|
+
|
|
3
|
+
module Guitar
|
|
4
|
+
module Graphic
|
|
5
|
+
STRINGS_AND_BASELINE = '
|
|
6
|
+
╓─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────╥─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────╖
|
|
7
|
+
╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╫─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╢
|
|
8
|
+
╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╫─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╢
|
|
9
|
+
╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╫─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╢
|
|
10
|
+
╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╫─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╢
|
|
11
|
+
╙─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────╨─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────╜
|
|
12
|
+
𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈
|
|
13
|
+
'.split("\n")[1..-2].freeze
|
|
14
|
+
|
|
15
|
+
# Text size in one fret
|
|
16
|
+
FRET_WIDTH = 6
|
|
17
|
+
|
|
18
|
+
def self.get_frets(string = 2, size = DEFAULT_SIZE)
|
|
19
|
+
STRINGS_AND_BASELINE[string - 1].scan(/.{#{FRET_WIDTH}}/)[0...size]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.get_baseline(size)
|
|
23
|
+
STRINGS_AND_BASELINE.last[0...(FRET_WIDTH * size)]
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
module Guitar
|
|
2
|
+
class String
|
|
3
|
+
include Common
|
|
4
|
+
|
|
5
|
+
attr_reader :tonic, :notes, :frets, :changes
|
|
6
|
+
|
|
7
|
+
def initialize(tonic, frets = Graphic.get_frets)
|
|
8
|
+
@tonic = tonic.to_n
|
|
9
|
+
@frets = frets
|
|
10
|
+
@notes = size.times.inject([@tonic]) { |r, i| r << (@tonic + i + 1) }
|
|
11
|
+
@changes = []
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def size
|
|
15
|
+
frets.size
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def show_notes(notes = nil, fret: nil, color: nil, &b)
|
|
19
|
+
make_change do
|
|
20
|
+
if notes
|
|
21
|
+
show_given_notes(notes, fret, color, &b)
|
|
22
|
+
else
|
|
23
|
+
show_all_notes(fret, color, &b)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def mark(content = nil, fret: nil, color: nil)
|
|
29
|
+
make_change do
|
|
30
|
+
size.times { |i| mark_fret(i + 1, content, color) if in?(i + 1, fret) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
PLAY_MARK = '◉ '
|
|
35
|
+
|
|
36
|
+
def play(note = nil, fret: nil, color: nil)
|
|
37
|
+
if fret
|
|
38
|
+
Play.play(notes[fret])
|
|
39
|
+
mark(PLAY_MARK, fret: fret, color: color)
|
|
40
|
+
else
|
|
41
|
+
note ||= tonic
|
|
42
|
+
Play.play(note)
|
|
43
|
+
|
|
44
|
+
fret = notes.find_index { |n| n == note }
|
|
45
|
+
fret ||= notes.find_index { |n| n === note }
|
|
46
|
+
mark(PLAY_MARK, fret: fret, color: color)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def undo
|
|
51
|
+
if changes.size > 0
|
|
52
|
+
@frets = changes.pop
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def clear
|
|
59
|
+
if changes.size > 0
|
|
60
|
+
@frets = changes[0]
|
|
61
|
+
changes.clear
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def inspect
|
|
68
|
+
"#{@tonic.name} #{@frets.join('')}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
def mark_fret(i, e, color = nil)
|
|
74
|
+
if i > 0
|
|
75
|
+
i -= 1
|
|
76
|
+
|
|
77
|
+
if e
|
|
78
|
+
e = e.to_s
|
|
79
|
+
s = e.uncolorize.size
|
|
80
|
+
if s > Graphic::FRET_WIDTH
|
|
81
|
+
fail "#{e} is too long, the size should < #{Graphic::FRET_WIDTH}"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
start_index = (Graphic::FRET_WIDTH - s + 2) / 2
|
|
85
|
+
end_index = (-Graphic::FRET_WIDTH + s) / 2
|
|
86
|
+
|
|
87
|
+
@frets[i] = (@changes[0] ? @changes[0][i] : @frets[i]).dup
|
|
88
|
+
@frets[i][start_index..end_index] = colorize(e, color)
|
|
89
|
+
else
|
|
90
|
+
@frets[i] = colorize(@frets[i], color)
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def record_change(&b)
|
|
96
|
+
old = @frets.dup
|
|
97
|
+
b.call
|
|
98
|
+
ensure
|
|
99
|
+
@changes << old if @frets != old
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def show_all_notes(fret, color, &b)
|
|
103
|
+
@notes.each_with_index do |e, i|
|
|
104
|
+
if in?(i, fret)
|
|
105
|
+
m = block_given? ? b.call(e, i) : e.name
|
|
106
|
+
mark_fret(i, m, color)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def show_given_notes(notes, fret, color, &b)
|
|
112
|
+
notes = notes.respond_to?(:map) ? notes.map(&:to_n) : [notes.to_n]
|
|
113
|
+
|
|
114
|
+
@notes.each_with_index do |e, i|
|
|
115
|
+
if in?(i, fret) && note = notes.find { |n| e === n }
|
|
116
|
+
m = block_given? ? b.call(note, i) : note.name
|
|
117
|
+
mark_fret(i, m, color)
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -81,7 +81,7 @@ module Guitar
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def show_tonic_shape_right_triangle(as, scale, color)
|
|
84
|
-
fret = strings[5].notes.find_index { |n| n === scale.tonic }
|
|
84
|
+
fret = strings[5].notes.find_index { |n| n === scale.tonic }
|
|
85
85
|
fret += 12 if fret < 2
|
|
86
86
|
|
|
87
87
|
show_notes(scale.notes, fret: (fret - 1)..(fret + 2)) do |n, _, f|
|
|
@@ -90,7 +90,7 @@ module Guitar
|
|
|
90
90
|
end
|
|
91
91
|
|
|
92
92
|
def show_tonic_shape_left_triangle(as, scale, color)
|
|
93
|
-
fret = strings[5].notes.find_index { |n| n === scale.tonic }
|
|
93
|
+
fret = strings[5].notes.find_index { |n| n === scale.tonic }
|
|
94
94
|
fret += 12 if fret < 5
|
|
95
95
|
|
|
96
96
|
show_notes(scale.notes, fret: (fret - 4)..fret) do |n, s, f|
|
|
@@ -108,7 +108,7 @@ module Guitar
|
|
|
108
108
|
end
|
|
109
109
|
|
|
110
110
|
def show_tonic_shape_backslash(as, scale, color)
|
|
111
|
-
fret = strings[4].notes.find_index { |n| n === scale.tonic }
|
|
111
|
+
fret = strings[4].notes.find_index { |n| n === scale.tonic }
|
|
112
112
|
fret += 12 if fret < 4
|
|
113
113
|
|
|
114
114
|
show_notes(scale.notes, fret: (fret - 3)..fret) do |n, _, f|
|
|
@@ -117,7 +117,7 @@ module Guitar
|
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
def show_tonic_shape_slash_before_triangle(as, scale, color)
|
|
120
|
-
fret = strings[4].notes.find_index { |n| n === scale.tonic }
|
|
120
|
+
fret = strings[4].notes.find_index { |n| n === scale.tonic }
|
|
121
121
|
fret += 12 if fret < 2
|
|
122
122
|
|
|
123
123
|
show_notes(scale.notes, fret: (fret - 1)..(fret + 3)) do |n, s, f|
|
|
@@ -135,7 +135,7 @@ module Guitar
|
|
|
135
135
|
end
|
|
136
136
|
|
|
137
137
|
def show_tonic_shape_slash_before_backslash(as, scale, color)
|
|
138
|
-
fret = strings[3].notes.find_index { |n| n === scale.tonic }
|
|
138
|
+
fret = strings[3].notes.find_index { |n| n === scale.tonic }
|
|
139
139
|
fret += 12 if fret < 2
|
|
140
140
|
|
|
141
141
|
show_notes(scale.notes, fret: (fret - 1)..(fret + 3)) do |n, s, f|
|
|
@@ -28,7 +28,7 @@ module Guitar
|
|
|
28
28
|
attr_reader :name, :octave, :number
|
|
29
29
|
alias_method :to_i, :number
|
|
30
30
|
|
|
31
|
-
NOTE_PATTERN = /\A([a-gA-G][#b
|
|
31
|
+
NOTE_PATTERN = /\A([a-gA-G][#s♯b♭]?)(\d)?\z/
|
|
32
32
|
NATURAL_NOTES = %w(A B C D E F G).freeze
|
|
33
33
|
|
|
34
34
|
# A0..C8
|
|
@@ -76,8 +76,8 @@ module Guitar
|
|
|
76
76
|
end
|
|
77
77
|
|
|
78
78
|
def <=>(other)
|
|
79
|
-
if other.respond_to?(:
|
|
80
|
-
to_i <=> other.to_i
|
|
79
|
+
if other.respond_to?(:to_n)
|
|
80
|
+
to_i <=> other.to_n.to_i
|
|
81
81
|
else
|
|
82
82
|
super
|
|
83
83
|
end
|
|
@@ -126,10 +126,18 @@ module Guitar
|
|
|
126
126
|
Scale.new(self, name)
|
|
127
127
|
end
|
|
128
128
|
|
|
129
|
+
def to_n
|
|
130
|
+
self
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def play
|
|
134
|
+
Play.play(self)
|
|
135
|
+
end
|
|
136
|
+
|
|
129
137
|
private
|
|
130
138
|
|
|
131
139
|
def init_with_name_and_octave(name, octave)
|
|
132
|
-
@name = name.to_s.capitalize.sub(
|
|
140
|
+
@name = name.to_s.capitalize.sub(/[#s]/, '♯').sub('b', '♭')
|
|
133
141
|
@octave = octave.to_i
|
|
134
142
|
|
|
135
143
|
if number = MIDI_NUMBERS[@name]
|
|
@@ -147,3 +155,11 @@ module Guitar
|
|
|
147
155
|
end
|
|
148
156
|
end
|
|
149
157
|
end
|
|
158
|
+
|
|
159
|
+
[Integer, Symbol, String].each do |klass|
|
|
160
|
+
klass.class_eval do
|
|
161
|
+
def to_n
|
|
162
|
+
Guitar::Note.new(self)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -26,7 +26,7 @@ module Guitar
|
|
|
26
26
|
current = last + s
|
|
27
27
|
current.alias! if current.name.to_s[0] == last.name.to_s[0]
|
|
28
28
|
ns << current
|
|
29
|
-
end
|
|
29
|
+
end.freeze
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def degree(note)
|
|
@@ -38,5 +38,9 @@ module Guitar
|
|
|
38
38
|
def note_names
|
|
39
39
|
@notes.map(&:name)
|
|
40
40
|
end
|
|
41
|
+
|
|
42
|
+
def play
|
|
43
|
+
Play.play(*(notes + [tonic + 12]))
|
|
44
|
+
end
|
|
41
45
|
end
|
|
42
46
|
end
|
data/lib/guitar/practice.rb
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
require 'io/console'
|
|
2
|
-
|
|
3
1
|
module Guitar
|
|
4
2
|
class Practice
|
|
5
3
|
attr_reader :size, :interval, :natural, :color, :string, :fret
|
|
6
4
|
|
|
7
|
-
def initialize(size:
|
|
5
|
+
def initialize(size: DEFAULT_SIZE, interval: 1, natural: false, color: nil,
|
|
8
6
|
string: 1..6, fret: 1..size)
|
|
9
7
|
@size = size
|
|
10
8
|
@interval = interval
|
|
@@ -27,29 +25,83 @@ module Guitar
|
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
def random_note_in_fretboard
|
|
30
|
-
f = Guitar::Fretboard.new
|
|
28
|
+
f = Guitar::Fretboard.new(size)
|
|
29
|
+
prompt = 'Please input the current note: '
|
|
31
30
|
|
|
32
31
|
loop do
|
|
33
32
|
clear_screen
|
|
34
33
|
f.clear
|
|
35
34
|
|
|
36
35
|
note, string = Note.random(natural), self.string.sample
|
|
37
|
-
puts f.show_notes(note, string: string, color: color) { '
|
|
38
|
-
print
|
|
36
|
+
puts f.show_notes(note, string: string, color: color) { '◉ ' }.inspect
|
|
37
|
+
print prompt
|
|
39
38
|
|
|
40
39
|
begin
|
|
41
|
-
|
|
40
|
+
answer = STDIN.gets.strip
|
|
42
41
|
|
|
43
|
-
if
|
|
42
|
+
if Note.new(answer) === note
|
|
44
43
|
clear_screen
|
|
45
|
-
|
|
44
|
+
puts f.show_notes(note, string: string, color: :green).inspect
|
|
45
|
+
print prompt, answer
|
|
46
|
+
sleep interval
|
|
47
|
+
else
|
|
48
|
+
print_error(f, note, string, prompt + answer)
|
|
49
|
+
end
|
|
50
|
+
rescue
|
|
51
|
+
print_error(f, note, string, prompt + answer)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def mark_random_note_in_fretboard
|
|
57
|
+
f = Guitar::Fretboard.new(size)
|
|
58
|
+
|
|
59
|
+
loop do
|
|
60
|
+
clear_screen
|
|
61
|
+
f.clear
|
|
62
|
+
|
|
63
|
+
note = Note.random(natural)
|
|
64
|
+
prompt = "Please input the position of #{note.name}: "
|
|
65
|
+
puts f.inspect
|
|
66
|
+
print prompt
|
|
67
|
+
|
|
68
|
+
# Show notes, if we get the right answer, they will be covered.
|
|
69
|
+
f.show_notes(note, color: color)
|
|
70
|
+
|
|
71
|
+
begin
|
|
72
|
+
answer = STDIN.gets.strip
|
|
73
|
+
correct = answer != ''
|
|
74
|
+
|
|
75
|
+
answer.split(/[\s,;]+/).each do |position|
|
|
76
|
+
string, fret = position.split(':').map(&:to_i)
|
|
77
|
+
f.show_notes(string: string, fret: fret) do |n|
|
|
78
|
+
if n === note
|
|
79
|
+
n.name.green
|
|
80
|
+
else
|
|
81
|
+
correct = false
|
|
82
|
+
n.name.red
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
clear_screen
|
|
88
|
+
puts f.inspect
|
|
89
|
+
print prompt, answer
|
|
90
|
+
|
|
91
|
+
if correct
|
|
46
92
|
sleep interval
|
|
47
93
|
else
|
|
48
|
-
|
|
94
|
+
print "\nPress ENTER to continue ..."
|
|
95
|
+
STDIN.gets
|
|
49
96
|
end
|
|
50
97
|
rescue
|
|
51
|
-
|
|
98
|
+
clear_screen
|
|
99
|
+
puts f.inspect
|
|
100
|
+
print prompt, answer
|
|
101
|
+
print "\nPress ENTER to continue ..."
|
|
102
|
+
STDIN.gets
|
|
52
103
|
end
|
|
104
|
+
|
|
53
105
|
end
|
|
54
106
|
end
|
|
55
107
|
|
|
@@ -72,10 +124,11 @@ module Guitar
|
|
|
72
124
|
end
|
|
73
125
|
end
|
|
74
126
|
|
|
75
|
-
def print_error(fretboard, note, string)
|
|
127
|
+
def print_error(fretboard, note, string, answer)
|
|
76
128
|
clear_screen
|
|
77
|
-
|
|
78
|
-
|
|
129
|
+
puts fretboard.show_notes(note, string: string, color: :red).inspect
|
|
130
|
+
puts answer
|
|
131
|
+
print 'Press ENTER to continue ...'
|
|
79
132
|
STDIN.gets
|
|
80
133
|
end
|
|
81
134
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'fast_osc'
|
|
3
|
+
|
|
4
|
+
module Guitar
|
|
5
|
+
class Client
|
|
6
|
+
GUI_ID = 'GUITAR'
|
|
7
|
+
METHOD_RUN_CODE = '/run-code'
|
|
8
|
+
METHOD_STOP_ALL_JOBS = '/stop-all-jobs'
|
|
9
|
+
|
|
10
|
+
def run_code(code)
|
|
11
|
+
call_server_method(METHOD_RUN_CODE, code)
|
|
12
|
+
end
|
|
13
|
+
alias_method :run, :run_code
|
|
14
|
+
|
|
15
|
+
def stop_all_jobs
|
|
16
|
+
call_server_method(METHOD_STOP_ALL_JOBS)
|
|
17
|
+
end
|
|
18
|
+
alias_method :stop, :stop_all_jobs
|
|
19
|
+
|
|
20
|
+
def call_server_method(method, *args)
|
|
21
|
+
m = FastOsc.encode_single_message(method, args.unshift(GUI_ID))
|
|
22
|
+
socket.send(m, 0)
|
|
23
|
+
end
|
|
24
|
+
alias_method :call, :call_server_method
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def socket
|
|
29
|
+
@socket ||= UDPSocket.new.tap do |s|
|
|
30
|
+
s.connect(Server::HOST, Server::PORT)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module Guitar
|
|
2
|
+
module Play
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
def play(*args)
|
|
6
|
+
code = get_code(*args)
|
|
7
|
+
puts code if Guitar.debug?
|
|
8
|
+
client.run_code(code)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def run(code)
|
|
12
|
+
client.run_code(code)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def use_bpm(bpm)
|
|
16
|
+
@bpm = bpm
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def use_synth(synth)
|
|
20
|
+
@synth = synth
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def stop
|
|
24
|
+
client.stop_all_jobs
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def client
|
|
28
|
+
@@client ||= Client.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def get_code(*args)
|
|
34
|
+
options = ", #{args.pop}" if args.last.is_a?(Hash)
|
|
35
|
+
|
|
36
|
+
play_and_sleep = args.map do |x|
|
|
37
|
+
if x.respond_to?(:to_a)
|
|
38
|
+
"play #{x.to_a.map { |y| y.to_n.to_i }}#{options}"
|
|
39
|
+
else
|
|
40
|
+
"play #{x.to_n.to_i}#{options}"
|
|
41
|
+
end
|
|
42
|
+
end.join("\nsleep 1\n")
|
|
43
|
+
|
|
44
|
+
uses = ["use_synth :#{@synth || 'pluck'}"]
|
|
45
|
+
uses << "use_bpm #@bpm" if @bpm
|
|
46
|
+
uses = uses.join("\n")
|
|
47
|
+
|
|
48
|
+
"#{uses}\n#{play_and_sleep}"
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
[Integer, Symbol, String].each do |klass|
|
|
54
|
+
klass.class_eval do
|
|
55
|
+
def play(options = nil)
|
|
56
|
+
options ? Guitar::Play.play(to_n, options) : Guitar::Play.play(to_n)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
|
|
3
|
+
module Guitar
|
|
4
|
+
module Server
|
|
5
|
+
HOST = 'localhost'
|
|
6
|
+
PORT = 4557
|
|
7
|
+
# Sonic Pi uses more than one ports. We nust ensure all the ports are
|
|
8
|
+
# available, or else it can not be started.
|
|
9
|
+
# For example, 4556 (PORT - 1) is scsynth_port,
|
|
10
|
+
# and 4562 (PORT + 5) is osc_midi_in_port.
|
|
11
|
+
PORTS = (PORT - 1)..(PORT + 5)
|
|
12
|
+
|
|
13
|
+
SCRIPT_PATHS = [
|
|
14
|
+
'/Applications/Sonic Pi.app/server/bin/sonic-pi-server.rb',
|
|
15
|
+
'./app/server/bin/sonic-pi-server.rb',
|
|
16
|
+
'/opt/sonic-pi/app/server/bin/sonic-pi-server.rb',
|
|
17
|
+
'/usr/lib/sonic-pi/server/bin/sonic-pi-server.rb',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
class << self
|
|
21
|
+
attr_reader :pid
|
|
22
|
+
|
|
23
|
+
def start
|
|
24
|
+
if !sonic_pi_installed?
|
|
25
|
+
# TODO Sonic Pi installation instruction
|
|
26
|
+
warn 'Sonic Pi is not installed.'
|
|
27
|
+
elsif running?
|
|
28
|
+
warn 'The Sonic Pi server is running.'
|
|
29
|
+
elsif !ports_available?
|
|
30
|
+
warn 'The Sonic Pi server can not be started, for some ports '\
|
|
31
|
+
'required are in use. You could run Server.stop to close them.'
|
|
32
|
+
else
|
|
33
|
+
@pid = Process.spawn(script, spawn_options)
|
|
34
|
+
print 'Starting sonic-pi server... '
|
|
35
|
+
sleep 1
|
|
36
|
+
|
|
37
|
+
success = Process.getpgid(@pid) rescue false
|
|
38
|
+
puts(success ? 'Success' : 'Failure')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def stop
|
|
43
|
+
PORTS.each do |port|
|
|
44
|
+
`lsof -i :#{port} -t`.strip.split("\n").each do |p|
|
|
45
|
+
Process.kill(:INT, p.to_i)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
@pid = nil
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def sonic_pi_installed?
|
|
52
|
+
SCRIPT_PATHS.any? { |p| File.file?(p) }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def running?
|
|
56
|
+
!port_available?(PORT)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def ports_available?
|
|
60
|
+
PORTS.all? { |p| port_available?(p) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def script
|
|
64
|
+
"'#{SCRIPT_PATHS.find { |p| File.file?(p) }}'"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def port_available?(port)
|
|
70
|
+
socket = UDPSocket.new
|
|
71
|
+
socket.bind(HOST, port)
|
|
72
|
+
true
|
|
73
|
+
rescue Errno::EADDRINUSE
|
|
74
|
+
false
|
|
75
|
+
ensure
|
|
76
|
+
socket.close
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def spawn_options
|
|
80
|
+
if Guitar.debug?
|
|
81
|
+
{}
|
|
82
|
+
else
|
|
83
|
+
{in: File::NULL, out: File::NULL, err: File::NULL}
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
data/lib/guitar/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: guitar
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Xin Luo
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2017-12-
|
|
11
|
+
date: 2017-12-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: colorize
|
|
@@ -24,6 +24,34 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: 0.8.1
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: fast_osc
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 0.0.12
|
|
34
|
+
type: :runtime
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 0.0.12
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: pry
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: 0.11.3
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: 0.11.3
|
|
27
55
|
- !ruby/object:Gem::Dependency
|
|
28
56
|
name: bundler
|
|
29
57
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -90,14 +118,17 @@ files:
|
|
|
90
118
|
- lib/guitar/commands/console.rb
|
|
91
119
|
- lib/guitar/commands/practice.rb
|
|
92
120
|
- lib/guitar/commands/tonic_shape.rb
|
|
93
|
-
- lib/guitar/common.rb
|
|
94
|
-
- lib/guitar/
|
|
95
|
-
- lib/guitar/fretboard.rb
|
|
96
|
-
- lib/guitar/
|
|
121
|
+
- lib/guitar/fretboard/common.rb
|
|
122
|
+
- lib/guitar/fretboard/fretboard.rb
|
|
123
|
+
- lib/guitar/fretboard/graphic.rb
|
|
124
|
+
- lib/guitar/fretboard/string.rb
|
|
125
|
+
- lib/guitar/fretboard/tonic_shape.rb
|
|
126
|
+
- lib/guitar/music_theory/note.rb
|
|
127
|
+
- lib/guitar/music_theory/scale.rb
|
|
97
128
|
- lib/guitar/practice.rb
|
|
98
|
-
- lib/guitar/
|
|
99
|
-
- lib/guitar/
|
|
100
|
-
- lib/guitar/
|
|
129
|
+
- lib/guitar/sonic_pi/client.rb
|
|
130
|
+
- lib/guitar/sonic_pi/play.rb
|
|
131
|
+
- lib/guitar/sonic_pi/server.rb
|
|
101
132
|
- lib/guitar/version.rb
|
|
102
133
|
homepage: http://luoxin.net
|
|
103
134
|
licenses:
|
data/lib/guitar/string.rb
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
require 'guitar/common'
|
|
2
|
-
|
|
3
|
-
module Guitar
|
|
4
|
-
class String
|
|
5
|
-
include Common
|
|
6
|
-
|
|
7
|
-
attr_reader :note, :notes, :frets, :changes
|
|
8
|
-
|
|
9
|
-
def initialize(note, frets = Guitar.get_frets)
|
|
10
|
-
@note = note.to_n
|
|
11
|
-
@frets = frets
|
|
12
|
-
@notes = size.times.inject([]) { |r, i| r << (@note + i + 1) }
|
|
13
|
-
@changes = []
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def size
|
|
17
|
-
frets.size
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def show_notes(notes = nil, fret: nil, color: nil, &b)
|
|
21
|
-
make_change do
|
|
22
|
-
if notes
|
|
23
|
-
show_given_notes(notes, fret, color, &b)
|
|
24
|
-
else
|
|
25
|
-
show_all_notes(fret, color, &b)
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def mark(content = nil, fret: nil, color: nil)
|
|
31
|
-
make_change do
|
|
32
|
-
size.times { |i| mark_fret(i, content, color) if in?(i + 1, fret) }
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def undo
|
|
37
|
-
if changes.size > 0
|
|
38
|
-
@frets = changes.pop
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
self
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def clear
|
|
45
|
-
if changes.size > 0
|
|
46
|
-
@frets = changes[0]
|
|
47
|
-
changes.clear
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
self
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def inspect
|
|
54
|
-
"#{@note.name} #{@frets.join('')}"
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
private
|
|
58
|
-
|
|
59
|
-
def mark_fret(i, e, color = nil)
|
|
60
|
-
if e
|
|
61
|
-
e = e.to_s
|
|
62
|
-
s = e.uncolorize.size
|
|
63
|
-
fail "#{e} is too long, the size should < #{WIDTH}" if s >= WIDTH
|
|
64
|
-
|
|
65
|
-
start_index = (WIDTH - s + 2) / 2
|
|
66
|
-
end_index = (-WIDTH + s) / 2
|
|
67
|
-
|
|
68
|
-
@frets[i] = (@changes[0] ? @changes[0][i] : @frets[i]).dup
|
|
69
|
-
@frets[i][start_index..end_index] = colorize(e, color)
|
|
70
|
-
else
|
|
71
|
-
@frets[i] = colorize(@frets[i], color)
|
|
72
|
-
end
|
|
73
|
-
end
|
|
74
|
-
|
|
75
|
-
def record_change(&b)
|
|
76
|
-
old = @frets.dup
|
|
77
|
-
b.call
|
|
78
|
-
ensure
|
|
79
|
-
@changes << old if @frets != old
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def show_all_notes(fret, color, &b)
|
|
83
|
-
@notes.each_with_index do |e, i|
|
|
84
|
-
if in?(i + 1, fret)
|
|
85
|
-
m = block_given? ? b.call(e, i + 1) : e.name
|
|
86
|
-
mark_fret(i, m, color)
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def show_given_notes(notes, fret, color, &b)
|
|
92
|
-
notes = notes.respond_to?(:map) ? notes.map(&:to_n) : [notes.to_n]
|
|
93
|
-
|
|
94
|
-
@notes.each_with_index do |e, i|
|
|
95
|
-
if in?(i + 1, fret) && note = notes.find { |n| e === n }
|
|
96
|
-
m = block_given? ? b.call(note, i + 1) : note.name
|
|
97
|
-
mark_fret(i, m, color)
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|
|
102
|
-
end
|