guitar 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: