guitar 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6e822498b88eaac759d7a60a16da3fc580fc58cf
4
+ data.tar.gz: 3de594c37f1be97abe2e5f4949608248e5888c93
5
+ SHA512:
6
+ metadata.gz: b7659c74f7b4aec9ed7cceeb98b6ee76615cfb987b48f9ae4f8e5ff4510e0dcc2a0f2c940dfa6ed00f515e349680209256b5008f5c973909c98c68f1d873341e
7
+ data.tar.gz: e377a4713a19a16aee5837be3360edb490e25d2a9bdc404a43dda66f616a7664e898aa879f5f0385f53875006d85eccb641c4beec290a1fdfba5a2fe1d438998
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.0
5
+ before_install: gem install bundler -v 1.16.0
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in guitar.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,24 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ guitar (0.2.0)
5
+ colorize (>= 0.8.0)
6
+
7
+ GEM
8
+ remote: https://rubygems.org/
9
+ specs:
10
+ colorize (0.8.1)
11
+ minitest (5.9.1)
12
+ rake (10.4.2)
13
+
14
+ PLATFORMS
15
+ ruby
16
+
17
+ DEPENDENCIES
18
+ bundler (~> 1.16)
19
+ guitar!
20
+ minitest (~> 5.0)
21
+ rake (~> 10.0)
22
+
23
+ BUNDLED WITH
24
+ 1.16.0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Xin Luo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Guitar
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/guitar`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'guitar'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install guitar
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/guitar.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList["test/**/*_test.rb"]
8
+ end
9
+
10
+ task :default => :test
data/bin/console ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'guitar/commands/console'
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/exe/guitar ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ git_path = File.expand_path('../../.git', __FILE__)
4
+
5
+ if File.exist?(git_path)
6
+ lib_path = File.expand_path('../../lib', __FILE__)
7
+ $:.unshift(lib_path)
8
+ end
9
+
10
+ require 'guitar/commands'
data/guitar.gemspec ADDED
@@ -0,0 +1,28 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "guitar/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "guitar"
8
+ spec.version = Guitar::VERSION
9
+ spec.authors = ["Xin Luo"]
10
+ spec.email = ["luoxin.net@gmail.com"]
11
+
12
+ spec.summary = %q{Guitar learning assistant.}
13
+ spec.description = %q{Learn guitar the programmer's way.}
14
+ spec.homepage = "http://luoxin.net"
15
+ spec.license = "MIT"
16
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
17
+ f.match(%r{^(test|spec|features)/})
18
+ end
19
+ spec.bindir = "exe"
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_dependency('colorize', '~> 0.8.1')
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.16"
26
+ spec.add_development_dependency "rake", "~> 10.0"
27
+ spec.add_development_dependency "minitest", "~> 5.0"
28
+ end
@@ -0,0 +1,5 @@
1
+ require 'irb'
2
+ require 'guitar'
3
+
4
+ include Guitar
5
+ IRB.start(__FILE__)
@@ -0,0 +1,85 @@
1
+ require 'optparse'
2
+
3
+ module Guitar
4
+ module Commands
5
+ class Practice
6
+ def run(args = ARGV)
7
+ options = {}
8
+
9
+ parser = OptionParser.new do |opts|
10
+ opts.banner = 'Usage: guitar practice LESSON [OPTIONS]'
11
+ opts.separator ''
12
+
13
+ opts.on(
14
+ '-s', '--size=size', Integer,
15
+ 'Specifies the guitar fret size.'
16
+ ) { |v| options[:size] = v }
17
+
18
+ opts.on(
19
+ '-c', '--color=color', ::String,
20
+ 'Specifies the show color.'
21
+ ) { |v| options[:color] = v }
22
+
23
+ opts.on(
24
+ '-i', '--interval=interval', Float,
25
+ 'Specifies the practice loop interval.'
26
+ ) { |v| options[:interval] = v }
27
+
28
+ opts.on(
29
+ '-h', '--help',
30
+ 'Show this help message.'
31
+ ) { puts opts; exit }
32
+
33
+ opts.separator ''
34
+ opts.separator 'Available LESSONS:'
35
+ opts.separator ''
36
+ opts.separator ' 1. Random NOTE(natural).'
37
+ opts.separator ' 2. Random NOTE.'
38
+ opts.separator ''
39
+ opts.separator ' 3. Random NOTE(natural) in random STRING.'
40
+ opts.separator ' 4. Random NOTE in random STRING.'
41
+ opts.separator ''
42
+ opts.separator ' 5. Random NOTE(natural) in random FRET(2/5/7/10/12).'
43
+ opts.separator ' 6. Random NOTE(natural) in random FRET.'
44
+ opts.separator ' 7. Random NOTE in random FRET(2/5/7/10/12).'
45
+ opts.separator ' 8. Random NOTE in random FRET.'
46
+ opts.separator ''
47
+ opts.separator ' 9. Random NOTE(natural) in FRETBOARD.'
48
+ opts.separator ' 10. Random NOTE in FRETBOARD.'
49
+ end
50
+ parser.parse!(args)
51
+
52
+ case args[0].to_i
53
+ when 1
54
+ options.merge!(natural: true)
55
+ Guitar::Practice.new(options).random_note
56
+ when 2
57
+ Guitar::Practice.new(options).random_note
58
+ when 3
59
+ options.merge!(natural: true)
60
+ Guitar::Practice.new(options).random_note_in_random_string
61
+ when 4
62
+ Guitar::Practice.new(options).random_note_in_random_string
63
+ when 5
64
+ options.merge!(natural: true, fret: [2, 5, 7, 10, 12])
65
+ Guitar::Practice.new(options).random_note_in_random_fret
66
+ when 6
67
+ options.merge!(natural: true)
68
+ Guitar::Practice.new(options).random_note_in_random_fret
69
+ when 7
70
+ options.merge!(fret: [2, 5, 7, 10, 12])
71
+ Guitar::Practice.new(options).random_note_in_random_fret
72
+ when 8
73
+ Guitar::Practice.new(options).random_note_in_random_fret
74
+ when 9
75
+ options.merge!(natural: true)
76
+ Guitar::Practice.new(options).random_note_in_fretboard
77
+ when 10
78
+ Guitar::Practice.new(options).random_note_in_fretboard
79
+ else
80
+ puts parser
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,49 @@
1
+ require 'optparse'
2
+
3
+ module Guitar
4
+ module Commands
5
+ class TonicShape
6
+ def run(args = ARGV)
7
+ size = 15
8
+ options = {}
9
+
10
+ parser = OptionParser.new do |opts|
11
+ opts.banner = 'Usage: guitar tonic_shape TONIC SHAPE [OPTIONS]'
12
+ opts.separator ''
13
+ opts.separator 'TONIC examples: ["C", "D#", "Ab"]'
14
+ opts.separator 'Available SHAPES: ["<", ">", "/\", "\", "/<"]'
15
+ opts.separator ''
16
+
17
+ opts.on(
18
+ '-s', '--size=size', Integer,
19
+ 'Specifies the guitar fret size.'
20
+ ) { |v| size = v }
21
+
22
+ opts.on(
23
+ '-a', '--as=as', ::String,
24
+ 'Specifies the show type [pretty_name, finger, degree].'
25
+ ) { |v| options[:as] = v }
26
+
27
+ opts.on(
28
+ '-c', '--color=color', ::String,
29
+ 'Specifies the show color.'
30
+ ) { |v| options[:color] = v.split(',') }
31
+
32
+ opts.on(
33
+ '-h', '--help',
34
+ 'Show this help message.'
35
+ ) { puts opts; exit }
36
+ end
37
+ parser.parse!(args)
38
+
39
+ if tonic = args[0]
40
+ f = Guitar::Fretboard.new(size)
41
+ f.desc = "The tonic shape of #{tonic.to_n.name} major".bold
42
+ puts f.show_tonic_shape(tonic, args[1], options).inspect
43
+ else
44
+ puts parser
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,45 @@
1
+ require 'guitar'
2
+
3
+ trap('INT') { exit }
4
+
5
+ commands = %w(
6
+ -help --help
7
+ -version --version
8
+ console
9
+ practice
10
+ tonic_shape
11
+ )
12
+
13
+ help = <<-HELP
14
+ Usage: guitar COMMAND [ARGS]
15
+
16
+ The available guitar commands are:
17
+
18
+ console Start the console (short-cut alias: "c")
19
+ practice Do practice (short-cut alias: "p")
20
+ tonic_shape Show tonic shapes (short-cut alias: "t")
21
+
22
+ All commands can be run with -h (or --help) for more information.
23
+ HELP
24
+
25
+ if command = commands.detect { |c| c =~ /^#{ARGV.first}/ }
26
+ ARGV.shift
27
+
28
+ case command
29
+ when '-help', '--help'
30
+ puts help
31
+ when '-version', '--version'
32
+ puts "guitar #{Guitar::VERSION}"
33
+ when 'console'
34
+ require 'guitar/commands/console'
35
+ when 'practice'
36
+ require 'guitar/commands/practice'
37
+ Guitar::Commands::Practice.new.run
38
+ when 'tonic_shape'
39
+ require 'guitar/commands/tonic_shape'
40
+ Guitar::Commands::TonicShape.new.run
41
+ end
42
+ else
43
+ puts "### Error: Command '#{ARGV.first}' can not be recognized ###"
44
+ puts help
45
+ end
@@ -0,0 +1,38 @@
1
+ module Guitar
2
+ module Common
3
+ def make_change(&b)
4
+ if @in_change
5
+ b.call
6
+ else
7
+ @in_change = true
8
+ record_change(&b)
9
+ @in_change = false
10
+ end
11
+
12
+ self
13
+ end
14
+
15
+ private
16
+
17
+ # index and range all start from 1, for they are fret or string
18
+ # in?(1, 1); in?(2, 1..3); in?(3, [3, 4, 5]); in?(1, nil)
19
+ def in?(index, range)
20
+ if range
21
+ range = [range] unless range.respond_to?(:include?)
22
+ range.include?(index)
23
+ else
24
+ true
25
+ end
26
+ end
27
+
28
+ def colorize(str, color)
29
+ if color.is_a?(Array)
30
+ color.inject(str) { |s, c| s.send(c) }
31
+ elsif color
32
+ str.send(color)
33
+ else
34
+ str
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ class Object
2
+ def to_n
3
+ self.is_a?(Guitar::Note) ? self : Guitar::Note.new(self)
4
+ end
5
+ end
@@ -0,0 +1,84 @@
1
+ require 'guitar/common'
2
+ require 'guitar/tonic_shape'
3
+
4
+ module Guitar
5
+ class Fretboard
6
+ include Common
7
+ include TonicShape
8
+
9
+ attr_reader :size, :strings, :changes
10
+ attr_accessor :desc
11
+
12
+ def initialize(size = DEFAULT_SIZE, desc: nil)
13
+ @size = size
14
+ @desc = desc
15
+ @changes = []
16
+ @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))
23
+ ]
24
+ end
25
+
26
+ def show_notes(notes = nil, string: nil, fret: nil, color: nil, &b)
27
+ make_change do
28
+ @strings.each_with_index do |s, i|
29
+ if in?(i + 1, string)
30
+ if block_given?
31
+ s.show_notes(notes, fret: fret, color: color) do |n, f|
32
+ b.call(n, i + 1, f)
33
+ end
34
+ else
35
+ s.show_notes(notes, fret: fret, color: color)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def mark(content = nil, string: nil, fret: nil, color: nil)
43
+ make_change do
44
+ @strings.each_with_index do |s, i|
45
+ s.mark(content, fret: fret, color: color) if in?(i + 1, string)
46
+ end
47
+ end
48
+ end
49
+
50
+ def undo
51
+ if last_change = changes.pop
52
+ last_change.each { |i| @strings[i].undo }
53
+ end
54
+
55
+ self
56
+ end
57
+
58
+ def clear
59
+ @strings.each(&:clear)
60
+ changes.clear
61
+ self
62
+ end
63
+
64
+ def inspect
65
+ string_lines = @strings.collect(&:inspect).join("\n")
66
+ point_lines = ASCII_ARRAY.last[0...(WIDTH * @size)]
67
+
68
+ "#@desc\n#{string_lines}\n #{point_lines}"
69
+ end
70
+
71
+ private
72
+
73
+ def record_change(&b)
74
+ before = @strings.collect { |s| s.changes.size }
75
+ b.call
76
+ ensure
77
+ after = @strings.collect { |s| s.changes.size }
78
+
79
+ change = []
80
+ after.each_with_index { |e, i| (e - before[i]).times { change << i } }
81
+ changes << change if change.size > 0
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,149 @@
1
+ module Guitar
2
+ class Note
3
+ include Comparable
4
+
5
+ DEFAULT_OCTAVE = 4
6
+
7
+ # http://newt.phys.unsw.edu.au/jw/notes.html
8
+ MIDI_NUMBERS = {
9
+ 'C' => 60,
10
+ 'C♯' => 61,
11
+ 'D♭' => 61,
12
+ 'D' => 62,
13
+ 'D♯' => 63,
14
+ 'E♭' => 63,
15
+ 'E' => 64,
16
+ 'F' => 65,
17
+ 'F♯' => 66,
18
+ 'G♭' => 66,
19
+ 'G' => 67,
20
+ 'G♯' => 68,
21
+ 'A♭' => 68,
22
+ 'A' => 69,
23
+ 'A♯' => 70,
24
+ 'B♭' => 70,
25
+ 'B' => 71
26
+ }
27
+
28
+ attr_reader :name, :octave, :number
29
+ alias_method :to_i, :number
30
+
31
+ NOTE_PATTERN = /\A([a-gA-G][#b♯♭]?)(\d)?\z/
32
+ NATURAL_NOTES = %w(A B C D E F G).freeze
33
+
34
+ # A0..C8
35
+ def self.random(natural = false)
36
+ if natural
37
+ new("#{NATURAL_NOTES.sample}#{rand(0..8)}")
38
+ else
39
+ new(rand(21..108)).tap { |n| n.alias! if rand(0..1) == 0 }
40
+ end
41
+ end
42
+
43
+ def initialize(note)
44
+ case note
45
+ when Integer
46
+ init_with_number(note)
47
+ else
48
+ note = note.to_s
49
+
50
+ if note =~ NOTE_PATTERN
51
+ init_with_name_and_octave($1, $2 || DEFAULT_OCTAVE)
52
+ else
53
+ fail "invalid note #{note}"
54
+ end
55
+ end
56
+ end
57
+
58
+ def -(other)
59
+ case other
60
+ when Note
61
+ @number - other.number
62
+ when Integer
63
+ self.class.new(@number - other)
64
+ else
65
+ fail "invalid operation with #{other}"
66
+ end
67
+ end
68
+
69
+ def +(other)
70
+ case
71
+ when Integer
72
+ self.- (-other)
73
+ else
74
+ fail "invalid operation with #{other}"
75
+ end
76
+ end
77
+
78
+ def <=>(other)
79
+ if other.respond_to?(:to_i)
80
+ to_i <=> other.to_i
81
+ else
82
+ super
83
+ end
84
+ end
85
+
86
+ def ===(other)
87
+ if other.is_a?(Note)
88
+ (self - other) % 12 == 0
89
+ else
90
+ super
91
+ end
92
+ end
93
+
94
+ def inspect
95
+ "#{@name}#{@octave}"
96
+ end
97
+
98
+ PRETTY_NAMES = {
99
+ 'C' => 'Ⓒ ',
100
+ 'D' => 'Ⓓ ',
101
+ 'E' => 'Ⓔ ',
102
+ 'F' => 'Ⓕ ',
103
+ 'G' => 'Ⓖ ',
104
+ 'A' => 'Ⓐ ',
105
+ 'B' => 'Ⓑ '
106
+ }
107
+
108
+ def pretty_name
109
+ if @name.size == 1
110
+ PRETTY_NAMES[@name]
111
+ else
112
+ "#{PRETTY_NAMES[@name[0]].strip}#{@name[1..-1]}"
113
+ end
114
+ end
115
+
116
+ def alias!
117
+ new_name, _ = MIDI_NUMBERS.find do |k, v|
118
+ n = 60 + (@number % 12)
119
+ v == n && k != name
120
+ end
121
+
122
+ @name = new_name if new_name
123
+ end
124
+
125
+ def scale(name = :major)
126
+ Scale.new(self, name)
127
+ end
128
+
129
+ private
130
+
131
+ def init_with_name_and_octave(name, octave)
132
+ @name = name.to_s.capitalize.sub('#', '♯').sub('b', '♭')
133
+ @octave = octave.to_i
134
+
135
+ if number = MIDI_NUMBERS[@name]
136
+ @number = number + (@octave - DEFAULT_OCTAVE) * 12
137
+ else
138
+ fail "the note name #{name} is not valid"
139
+ end
140
+ end
141
+
142
+ def init_with_number(number)
143
+ octave = ((number - 60) / 12) + DEFAULT_OCTAVE
144
+ name = MIDI_NUMBERS.key(60 + (number % 12))
145
+
146
+ init_with_name_and_octave(name, octave)
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,82 @@
1
+ require 'io/console'
2
+
3
+ module Guitar
4
+ class Practice
5
+ attr_reader :size, :interval, :natural, :color, :string, :fret
6
+
7
+ def initialize(size: 13, interval: 1, natural: false, color: nil,
8
+ string: 1..6, fret: 1..size)
9
+ @size = size
10
+ @interval = interval
11
+ @natural = natural
12
+ @color = color
13
+ @string = string.to_a
14
+ @fret = fret.to_a
15
+ end
16
+
17
+ def random_note
18
+ loop_and_print { Note.random(natural).name }
19
+ end
20
+
21
+ def random_note_in_random_string
22
+ loop_and_print { "#{Note.random(natural).name} in string #{string.sample}" }
23
+ end
24
+
25
+ def random_note_in_random_fret
26
+ loop_and_print { "#{Note.random(natural).name} in fret #{fret.sample}" }
27
+ end
28
+
29
+ def random_note_in_fretboard
30
+ f = Guitar::Fretboard.new
31
+
32
+ loop do
33
+ clear_screen
34
+ f.clear
35
+
36
+ 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: '
39
+
40
+ begin
41
+ correct = Note.new(STDIN.gets.strip) === note
42
+
43
+ if correct
44
+ clear_screen
45
+ print f.show_notes(note, string: string, color: :green).inspect
46
+ sleep interval
47
+ else
48
+ print_error(f, note, string)
49
+ end
50
+ rescue
51
+ print_error(f, note, string)
52
+ end
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def clear_screen
59
+ print "\x1b[2J\x1b[1;1H"
60
+ end
61
+
62
+ def loop_and_print(&b)
63
+ loop do
64
+ clear_screen
65
+ current = b.call
66
+ current = current.send(color) if color && current.respond_to?(color)
67
+
68
+ if current != @last
69
+ print @last = current
70
+ sleep interval
71
+ end
72
+ end
73
+ end
74
+
75
+ def print_error(fretboard, note, string)
76
+ clear_screen
77
+ print fretboard.show_notes(note, string: string, color: :red).inspect
78
+ print "\nPress ENTER to continue ..."
79
+ STDIN.gets
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,42 @@
1
+ module Guitar
2
+ class Scale
3
+ attr_reader :tonic, :type, :notes, :steps
4
+
5
+ STEPS = {
6
+ :major => [2, 2, 1, 2, 2, 2, 1].freeze,
7
+ :minor => [2, 1, 2, 2, 1, 2, 2].freeze
8
+ }
9
+
10
+ STEPS[:maj] = STEPS[:major]
11
+ STEPS[:m] = STEPS[:minor]
12
+
13
+ def initialize(tonic = :C, type = :major)
14
+ type = type.to_sym
15
+
16
+ if steps = STEPS[type]
17
+ @type = type
18
+ @steps = steps
19
+ @tonic = tonic.to_n
20
+ else
21
+ fail "the scale type #{type} is not valid"
22
+ end
23
+
24
+ @notes = @steps[0..-2].inject([@tonic]) do |ns, s|
25
+ last = ns.last
26
+ current = last + s
27
+ current.alias! if current.name.to_s[0] == last.name.to_s[0]
28
+ ns << current
29
+ end
30
+ end
31
+
32
+ def degree(note)
33
+ note = note.to_n
34
+ index = @notes.find_index { |n| note === n }
35
+ index && (index + 1)
36
+ end
37
+
38
+ def note_names
39
+ @notes.map(&:name)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,102 @@
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
@@ -0,0 +1,173 @@
1
+ module Guitar
2
+ module TonicShape
3
+ # E ╓─────┬─────┬─────┬─────┬─────┬─────┬─────┬──Ⓒ ─┬─────┬─────┬─────┬────
4
+ # B ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼────
5
+ # G ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼────
6
+ # D ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼──Ⓒ ─┼─────┼────
7
+ # A ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼────
8
+ # E ╙─────┴─────┴─────┴─────┴─────┴─────┴─────┴──Ⓒ ─┴─────┴─────┴─────┴────
9
+ # 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈
10
+ SHAPE_RIGHT_TRIANGLE = '>'
11
+
12
+ # E ╓─────┬─────┬─────┬─────┬─────┬─────┬─────┬──Ⓒ ─┬─────┬─────┬─────┬────
13
+ # B ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼────
14
+ # G ╟─────┼─────┼─────┼─────┼──Ⓒ ─┼─────┼─────┼─────┼─────┼─────┼─────┼────
15
+ # D ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼────
16
+ # A ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼────
17
+ # E ╙─────┴─────┴─────┴─────┴─────┴─────┴─────┴──Ⓒ ─┴─────┴─────┴─────┴────
18
+ # 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈
19
+ SHAPE_LEFT_TRIANGLE = '<'
20
+
21
+ # E ╓─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬────
22
+ # B ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼────
23
+ # G ╟─────┼─────┼─────┼─────┼──Ⓒ ─┼─────┼─────┼─────┼─────┼─────┼─────┼────
24
+ # D ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼────
25
+ # A ╟─────┼─────┼──Ⓒ ─┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼────
26
+ # E ╙─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴────
27
+ # 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈
28
+ SHAPE_SLASH_BEFORE_TRIANGLE = '/<'
29
+
30
+ # E ┄┄┬─────┬─────┬─────╥─────┬──
31
+ # B ┄┄┼─────┼─────┼─────╫──Ⓒ ─┼──
32
+ # G ┄┄┼─────┼─────┼─────╫─────┼──
33
+ # D ┄┄┼──Ⓒ ─┼─────┼─────╫─────┼──
34
+ # A ┄┄┼─────┼─────┼─────╫─────┼──
35
+ # E ┄┄┴─────┴─────┴─────╨─────┴──
36
+ # 𝇈 𝇈
37
+ SHAPE_SLASH_BEFORE_BACKSLASH = '/\\'
38
+
39
+ # E ┄┄┄┄┄╥─────┬─────┬─────┬─────
40
+ # B ┄┄┄┄┄╫──Ⓒ ─┼─────┼─────┼─────
41
+ # G ┄┄┄┄┄╫─────┼─────┼─────┼─────
42
+ # D ┄┄┄┄┄╫─────┼─────┼─────┼─────
43
+ # A ┄┄┄┄┄╫─────┼─────┼──Ⓒ ─┼─────
44
+ # E ┄┄┄┄┄╨─────┴─────┴─────┴─────
45
+ # 𝇈 𝇈 𝇈
46
+ SHAPE_BACKSLASH = '\\'
47
+
48
+ AS_PRETTY_NAME = :pretty_name
49
+ AS_DEGREE = :degree
50
+ AS_FINGER = :finger
51
+
52
+ DEGREES = %w(⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺)
53
+ FINGERS = %w(① ② ③ ④)
54
+
55
+ # TODO choose scale: major, minor ...
56
+ def show_tonic_shape(tonic, shape = nil, as: nil, color: [[:red, :bold], :yellow])
57
+ scale = tonic.to_n.scale
58
+
59
+ case shape.to_s
60
+ when SHAPE_RIGHT_TRIANGLE
61
+ show_tonic_shape_right_triangle(as, scale, color)
62
+ when SHAPE_LEFT_TRIANGLE
63
+ show_tonic_shape_left_triangle(as, scale, color)
64
+ when SHAPE_BACKSLASH
65
+ show_tonic_shape_backslash(as, scale, color)
66
+ when SHAPE_SLASH_BEFORE_TRIANGLE
67
+ show_tonic_shape_slash_before_triangle(as, scale, color)
68
+ when SHAPE_SLASH_BEFORE_BACKSLASH
69
+ show_tonic_shape_slash_before_backslash(as, scale, color)
70
+ else
71
+ show_all_tonic_shapes(as, scale, color)
72
+ end
73
+ end
74
+
75
+ private
76
+
77
+ def show_all_tonic_shapes(as, scale, color)
78
+ show_notes(scale.notes) do |n|
79
+ get_note_in_tonic_shape(n, as, scale, color)
80
+ end
81
+ end
82
+
83
+ def show_tonic_shape_right_triangle(as, scale, color)
84
+ fret = strings[5].notes.find_index { |n| n === scale.tonic } + 1
85
+ fret += 12 if fret < 2
86
+
87
+ show_notes(scale.notes, fret: (fret - 1)..(fret + 2)) do |n, _, f|
88
+ get_note_in_tonic_shape(n, as, scale, color, f - fret + 1)
89
+ end
90
+ end
91
+
92
+ def show_tonic_shape_left_triangle(as, scale, color)
93
+ fret = strings[5].notes.find_index { |n| n === scale.tonic } + 1
94
+ fret += 12 if fret < 5
95
+
96
+ show_notes(scale.notes, fret: (fret - 4)..fret) do |n, s, f|
97
+ get_note_in_tonic_shape(n, as, scale, color,
98
+ get_finger_in_left_triangle(s, fret, f))
99
+ end
100
+ end
101
+
102
+ def get_finger_in_left_triangle(string, root_fret, current_fret)
103
+ if string == 3
104
+ current_fret - root_fret + 4
105
+ else
106
+ current_fret - root_fret + 3
107
+ end
108
+ end
109
+
110
+ def show_tonic_shape_backslash(as, scale, color)
111
+ fret = strings[4].notes.find_index { |n| n === scale.tonic } + 1
112
+ fret += 12 if fret < 4
113
+
114
+ show_notes(scale.notes, fret: (fret - 3)..fret) do |n, _, f|
115
+ get_note_in_tonic_shape(n, as, scale, color, f - fret + 3)
116
+ end
117
+ end
118
+
119
+ def show_tonic_shape_slash_before_triangle(as, scale, color)
120
+ fret = strings[4].notes.find_index { |n| n === scale.tonic } + 1
121
+ fret += 12 if fret < 2
122
+
123
+ show_notes(scale.notes, fret: (fret - 1)..(fret + 3)) do |n, s, f|
124
+ get_note_in_tonic_shape(n, as, scale, color,
125
+ get_finger_in_slash_before_triangle(s, fret, f))
126
+ end
127
+ end
128
+
129
+ def get_finger_in_slash_before_triangle(string, root_fret, current_fret)
130
+ if string < 3
131
+ current_fret - root_fret
132
+ else
133
+ current_fret - root_fret + 1
134
+ end
135
+ end
136
+
137
+ def show_tonic_shape_slash_before_backslash(as, scale, color)
138
+ fret = strings[3].notes.find_index { |n| n === scale.tonic } + 1
139
+ fret += 12 if fret < 2
140
+
141
+ show_notes(scale.notes, fret: (fret - 1)..(fret + 3)) do |n, s, f|
142
+ get_note_in_tonic_shape(n, as, scale, color,
143
+ get_finger_in_slash_before_backslash(s, fret, f))
144
+ end
145
+ end
146
+
147
+ def get_finger_in_slash_before_backslash(string, root_fret, current_fret)
148
+ if string < 3 || string > 4
149
+ current_fret - root_fret
150
+ else
151
+ current_fret - root_fret + 1
152
+ end
153
+ end
154
+
155
+ def get_note_in_tonic_shape(note, as, scale, color, finger = nil)
156
+ str = case as && as.to_sym
157
+ when nil, AS_PRETTY_NAME
158
+ note.pretty_name
159
+ when AS_DEGREE
160
+ DEGREES[scale.degree(note) - 1] + ' '
161
+ when AS_FINGER
162
+ finger ? FINGERS[finger] + ' ' : '⓿ '
163
+ else
164
+ note.name
165
+ end
166
+
167
+ color = [color] unless color.is_a?(Array)
168
+ color = note === scale.tonic ? color[0] : color[1]
169
+
170
+ colorize(str, color)
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,3 @@
1
+ module Guitar
2
+ VERSION = '0.2.0'
3
+ end
data/lib/guitar.rb ADDED
@@ -0,0 +1,31 @@
1
+ require 'colorize'
2
+
3
+ require 'guitar/version'
4
+ require 'guitar/note'
5
+ require 'guitar/scale'
6
+ require 'guitar/string'
7
+ require 'guitar/fretboard'
8
+ require 'guitar/practice'
9
+
10
+ require 'guitar/core_ext/object'
11
+
12
+ module Guitar
13
+ ASCII_ARRAY = '
14
+ ╓─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────╥─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────┬─────╖
15
+ ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╫─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╢
16
+ ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╫─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╢
17
+ ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╫─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╢
18
+ ╟─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╫─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────┼─────╢
19
+ ╙─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────╨─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────┴─────╜
20
+ 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈 𝇈
21
+ '.split("\n")[1..-2].freeze
22
+
23
+ # Fretboard count
24
+ DEFAULT_SIZE = 13
25
+ # Text count in one fret
26
+ WIDTH = 6
27
+
28
+ def self.get_frets(string_number = 2, size = DEFAULT_SIZE)
29
+ ASCII_ARRAY[string_number - 1].scan(/.{#{WIDTH}}/)[0...size]
30
+ end
31
+ end
metadata ADDED
@@ -0,0 +1,127 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: guitar
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Xin Luo
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-12-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: colorize
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.8.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.8.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.0'
69
+ description: Learn guitar the programmer's way.
70
+ email:
71
+ - luoxin.net@gmail.com
72
+ executables:
73
+ - guitar
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".travis.yml"
79
+ - Gemfile
80
+ - Gemfile.lock
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - bin/console
85
+ - bin/setup
86
+ - exe/guitar
87
+ - guitar.gemspec
88
+ - lib/guitar.rb
89
+ - lib/guitar/commands.rb
90
+ - lib/guitar/commands/console.rb
91
+ - lib/guitar/commands/practice.rb
92
+ - 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
97
+ - lib/guitar/practice.rb
98
+ - lib/guitar/scale.rb
99
+ - lib/guitar/string.rb
100
+ - lib/guitar/tonic_shape.rb
101
+ - lib/guitar/version.rb
102
+ homepage: http://luoxin.net
103
+ licenses:
104
+ - MIT
105
+ metadata: {}
106
+ post_install_message:
107
+ rdoc_options: []
108
+ require_paths:
109
+ - lib
110
+ required_ruby_version: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - ">="
113
+ - !ruby/object:Gem::Version
114
+ version: '0'
115
+ required_rubygems_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ requirements: []
121
+ rubyforge_project:
122
+ rubygems_version: 2.6.8
123
+ signing_key:
124
+ specification_version: 4
125
+ summary: Guitar learning assistant.
126
+ test_files: []
127
+ has_rdoc: