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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6e822498b88eaac759d7a60a16da3fc580fc58cf
4
- data.tar.gz: 3de594c37f1be97abe2e5f4949608248e5888c93
3
+ metadata.gz: 9f253f238418bab4eb199b92e06e4c9f1ec7c318
4
+ data.tar.gz: 9744d485dc9ef0a77cfbd883feebf7d87715a325
5
5
  SHA512:
6
- metadata.gz: b7659c74f7b4aec9ed7cceeb98b6ee76615cfb987b48f9ae4f8e5ff4510e0dcc2a0f2c940dfa6ed00f515e349680209256b5008f5c973909c98c68f1d873341e
7
- data.tar.gz: e377a4713a19a16aee5837be3360edb490e25d2a9bdc404a43dda66f616a7664e898aa879f5f0385f53875006d85eccb641c4beec290a1fdfba5a2fe1d438998
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.2.0)
5
- colorize (>= 0.8.0)
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
- require 'colorize'
1
+ # load to_n first
2
+ require 'guitar/music_theory/note'
2
3
 
3
- require 'guitar/version'
4
- require 'guitar/note'
5
- require 'guitar/scale'
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
- require 'guitar/core_ext/object'
8
+ autoload :Scale, 'guitar/music_theory/scale'
11
9
 
12
- module Guitar
13
- ASCII_ARRAY = '
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 count
20
+ # Fretboard size
24
21
  DEFAULT_SIZE = 13
25
- # Text count in one fret
26
- WIDTH = 6
27
22
 
28
- def self.get_frets(string_number = 2, size = DEFAULT_SIZE)
29
- ASCII_ARRAY[string_number - 1].scan(/.{#{WIDTH}}/)[0...size]
23
+ def self.debug?
24
+ !!(ENV['DEBUG'])
30
25
  end
31
26
  end
@@ -2,4 +2,7 @@ require 'irb'
2
2
  require 'guitar'
3
3
 
4
4
  include Guitar
5
+ include Guitar::Play
6
+
7
+ Guitar::Server.start
5
8
  IRB.start(__FILE__)
@@ -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(:E5, Guitar.get_frets(1, size)),
18
- String.new(:B4, Guitar.get_frets(2, size)),
19
- String.new(:G4, Guitar.get_frets(3, size)),
20
- String.new(:D4, Guitar.get_frets(4, size)),
21
- String.new(:A3, Guitar.get_frets(5, size)),
22
- String.new(:E3, Guitar.get_frets(6, size))
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
- point_lines = ASCII_ARRAY.last[0...(WIDTH * @size)]
74
+ baseline = Graphic.get_baseline(@size)
67
75
 
68
- "#@desc\n#{string_lines}\n #{point_lines}"
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 } + 1
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 } + 1
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 } + 1
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 } + 1
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 } + 1
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♯♭]?)(\d)?\z/
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?(:to_i)
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('#', '♯').sub('b', '♭')
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
@@ -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: 13, interval: 1, natural: false, color: nil,
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) { 'X' }.inspect
38
- print 'Please input the current note: '
36
+ puts f.show_notes(note, string: string, color: color) { '' }.inspect
37
+ print prompt
39
38
 
40
39
  begin
41
- correct = Note.new(STDIN.gets.strip) === note
40
+ answer = STDIN.gets.strip
42
41
 
43
- if correct
42
+ if Note.new(answer) === note
44
43
  clear_screen
45
- print f.show_notes(note, string: string, color: :green).inspect
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
- print_error(f, note, string)
94
+ print "\nPress ENTER to continue ..."
95
+ STDIN.gets
49
96
  end
50
97
  rescue
51
- print_error(f, note, string)
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
- print fretboard.show_notes(note, string: string, color: :red).inspect
78
- print "\nPress ENTER to continue ..."
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
@@ -1,3 +1,3 @@
1
1
  module Guitar
2
- VERSION = '0.2.0'
2
+ VERSION = '0.3.0'
3
3
  end
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.2.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-19 00:00:00.000000000 Z
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/core_ext/object.rb
95
- - lib/guitar/fretboard.rb
96
- - lib/guitar/note.rb
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/scale.rb
99
- - lib/guitar/string.rb
100
- - lib/guitar/tonic_shape.rb
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:
@@ -1,5 +0,0 @@
1
- class Object
2
- def to_n
3
- self.is_a?(Guitar::Note) ? self : Guitar::Note.new(self)
4
- end
5
- end
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