guitar 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  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