tetsujin 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +23 -0
- data/LICENSE.txt +21 -0
- data/README.md +35 -0
- data/Rakefile +12 -0
- data/lib/tetsujin/dsl/instrument.rb +34 -0
- data/lib/tetsujin/dsl/theory.rb +19 -0
- data/lib/tetsujin/dsl.rb +11 -0
- data/lib/tetsujin/instrument/guitar/displayer.rb +62 -0
- data/lib/tetsujin/instrument/guitar/factory.rb +17 -0
- data/lib/tetsujin/instrument/guitar/fret.rb +46 -0
- data/lib/tetsujin/instrument/guitar/frets.rb +56 -0
- data/lib/tetsujin/instrument/guitar/string.rb +64 -0
- data/lib/tetsujin/instrument/guitar.rb +62 -0
- data/lib/tetsujin/instrument.rb +4 -0
- data/lib/tetsujin/theory/interval.rb +19 -0
- data/lib/tetsujin/theory/note/factory.rb +56 -0
- data/lib/tetsujin/theory/note.rb +89 -0
- data/lib/tetsujin/theory/notes.rb +37 -0
- data/lib/tetsujin/theory/scale.rb +53 -0
- data/lib/tetsujin/theory.rb +4 -0
- data/lib/tetsujin/version.rb +5 -0
- data/lib/tetsujin.rb +37 -0
- data/sig/tetsujin.rbs +4 -0
- metadata +69 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 8387e5d1a33b5216b6c4e6582a177021364deca9993d38348fddc920746f72e6
|
|
4
|
+
data.tar.gz: 7da91bdede287a154db7540e21f7d9d94725cad16e8bb4d4263b830aa432312d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 0c80e2e2cc875565d6302f95ce9862cbea0578f5a9ce2e865e520bfd1fc13870188d566bf189f11bfbe63de6866a9750a7e96293e44c616026d29384e93d400a
|
|
7
|
+
data.tar.gz: '08a835992f845f166e9fb6be0148fa9f72b7a7956ed31e3894f4eb54d5efe898825a5ab87e0c35718d2eab509525af6d32c5ac624225448e08b47d87f417ab32'
|
data/.rspec
ADDED
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
AllCops:
|
|
2
|
+
TargetRubyVersion: 2.6
|
|
3
|
+
|
|
4
|
+
Style/StringLiterals:
|
|
5
|
+
Enabled: true
|
|
6
|
+
EnforcedStyle: double_quotes
|
|
7
|
+
|
|
8
|
+
Style/StringLiteralsInInterpolation:
|
|
9
|
+
Enabled: true
|
|
10
|
+
EnforcedStyle: double_quotes
|
|
11
|
+
|
|
12
|
+
Layout/LineLength:
|
|
13
|
+
Max: 120
|
|
14
|
+
|
|
15
|
+
Documentation:
|
|
16
|
+
Enabled: false
|
|
17
|
+
|
|
18
|
+
Naming/MemoizedInstanceVariableName:
|
|
19
|
+
EnforcedStyleForLeadingUnderscores: required
|
|
20
|
+
|
|
21
|
+
Metrics/BlockLength:
|
|
22
|
+
Exclude:
|
|
23
|
+
- 'spec/**/*'
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Takahashi-Riki
|
|
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,35 @@
|
|
|
1
|
+
# Tetsujin
|
|
2
|
+
|
|
3
|
+
TODO: Delete this and the text below, and describe your gem
|
|
4
|
+
|
|
5
|
+
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/tetsujin`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
|
10
|
+
|
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
12
|
+
|
|
13
|
+
$ bundle add UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
|
|
14
|
+
|
|
15
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
16
|
+
|
|
17
|
+
$ gem install UPDATE_WITH_YOUR_GEM_NAME_PRIOR_TO_RELEASE_TO_RUBYGEMS_ORG
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
TODO: Write usage instructions here
|
|
22
|
+
|
|
23
|
+
## Development
|
|
24
|
+
|
|
25
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
26
|
+
|
|
27
|
+
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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
28
|
+
|
|
29
|
+
## Contributing
|
|
30
|
+
|
|
31
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/tetsujin.
|
|
32
|
+
|
|
33
|
+
## License
|
|
34
|
+
|
|
35
|
+
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,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tetsujin
|
|
4
|
+
module DSL
|
|
5
|
+
module Instrument
|
|
6
|
+
# @param fretboard_length [Integer] フレット数
|
|
7
|
+
# @return [Tetsujin::Instrument::Guitar]
|
|
8
|
+
def regular_tuning_guitar(fretboard_length:)
|
|
9
|
+
tunings = [
|
|
10
|
+
Tetsujin::Theory::Note.new(pitch_class: 4, octave: 2),
|
|
11
|
+
Tetsujin::Theory::Note.new(pitch_class: 9, octave: 2),
|
|
12
|
+
Tetsujin::Theory::Note.new(pitch_class: 2, octave: 3),
|
|
13
|
+
Tetsujin::Theory::Note.new(pitch_class: 7, octave: 3),
|
|
14
|
+
Tetsujin::Theory::Note.new(pitch_class: 11, octave: 3),
|
|
15
|
+
Tetsujin::Theory::Note.new(pitch_class: 4, octave: 4)
|
|
16
|
+
]
|
|
17
|
+
guitar(tunings: tunings, fretboard_length: fretboard_length)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @param tunings [Array<Tetsujin::Theory::Note>] チューニング 低い方から順に
|
|
21
|
+
# @param fretboard_length [Integer] フレット数
|
|
22
|
+
def guitar(tunings:, fretboard_length:)
|
|
23
|
+
Tetsujin::Instrument::Guitar::Factory.create(tunings: tunings, fretboard_length: fretboard_length)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param guitar [Tetsujin::Instrument::Guitar]
|
|
27
|
+
# @return [void]
|
|
28
|
+
def display_guitar(guitar)
|
|
29
|
+
displayer = Tetsujin::Instrument::Guitar::Displayer.new
|
|
30
|
+
guitar.print_fretboard(displayer)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tetsujin
|
|
4
|
+
module DSL
|
|
5
|
+
module Theory
|
|
6
|
+
# @param name [String] 音名
|
|
7
|
+
# @param octave [Integer] オクターブ
|
|
8
|
+
def note(name, octave)
|
|
9
|
+
Tetsujin::Theory::Note::Factory.create_from_name(name: name, octave: octave)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# @param root [Tetsujin::Theory::Note] ルート音
|
|
13
|
+
# @param pattern [String | Symbol] スケールのパターン
|
|
14
|
+
def scale(root, pattern)
|
|
15
|
+
Tetsujin::Theory::Scale.new(root: root, pattern: pattern.to_sym)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/tetsujin/dsl.rb
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tetsujin::Instrument
|
|
4
|
+
class Guitar::Displayer
|
|
5
|
+
FRET_CHAR_WITDH = 7.freeze # 例えば "A♯4/B♭4" など最大7文字
|
|
6
|
+
STRING_NUMBER_WITDH = 2.freeze # 例えば "12" など最大2文字を想定
|
|
7
|
+
private_constant :FRET_CHAR_WITDH
|
|
8
|
+
|
|
9
|
+
# @param guitar [Tetsujin::Instrument::Guitar]
|
|
10
|
+
# @return [void]
|
|
11
|
+
def print_fretboard(guitar)
|
|
12
|
+
guitar.strings.sort_by(&:string_number).each{ |string| display_string(string) }
|
|
13
|
+
display_fretboard_numbers(guitar.fretboard_length)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
attr_reader :guitar
|
|
19
|
+
|
|
20
|
+
# @example
|
|
21
|
+
# " 1 : ------- F4 ------- G4 G♯4/A♭4 ------- A♯4/B♭4 ------- C5 C♯5/D♭5 ------- D♯5/E♭5 -------"
|
|
22
|
+
def display_string(string)
|
|
23
|
+
frets = string.frets.map{ |fret| fret_representation(fret) }.join(" ")
|
|
24
|
+
puts [string_number_representation(string.string_number), frets].join(" ")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @example
|
|
28
|
+
# " : 0 1 2 3 4 5 6 7 8 9 10 11 12"
|
|
29
|
+
def display_fretboard_numbers(fretboard_length)
|
|
30
|
+
fretnumbers = (0..fretboard_length).to_a.map { |fret_number| fretboard_number_representation(fret_number) }.join(" ")
|
|
31
|
+
puts [string_number_representation(""), fretnumbers].join(" ")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @example
|
|
35
|
+
# "A♯4/B♭4"
|
|
36
|
+
# "-------"
|
|
37
|
+
def fret_representation(fret)
|
|
38
|
+
fret_char_width = 7
|
|
39
|
+
fret.pressed? ? fret.note.to_s.rjust(fret_char_width) : "-" * fret_char_width
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @example
|
|
43
|
+
# 1 => " 1 :"
|
|
44
|
+
def string_number_representation(string_number)
|
|
45
|
+
"#{string_number.to_s.rjust(string_number_width)} :"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @example
|
|
49
|
+
# 1 => " 1"
|
|
50
|
+
def fretboard_number_representation(fret_number)
|
|
51
|
+
fret_number.to_s.rjust(fretboard_number_width)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def fretboard_number_width
|
|
55
|
+
FRET_CHAR_WITDH
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def string_number_width
|
|
59
|
+
STRING_NUMBER_WITDH
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Tetsujin::Instrument
|
|
2
|
+
class Guitar::Factory
|
|
3
|
+
# @param tunings [Array<Tetsujin::Theory::Note>] チューニング 低い方から順に
|
|
4
|
+
# @param fretboard_length [Integer] フレット数
|
|
5
|
+
# @return [Tetsujin::Instrument::Guitar]
|
|
6
|
+
def self.create(tunings:, fretboard_length:)
|
|
7
|
+
strings = tunings.reverse.map.with_index(1) do |tuning, string_number|
|
|
8
|
+
Tetsujin::Instrument::Guitar::String.new(
|
|
9
|
+
tuning: tuning,
|
|
10
|
+
string_number: string_number,
|
|
11
|
+
fretboard_length: fretboard_length
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
Tetsujin::Instrument::Guitar.new(strings: strings)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tetsujin::Instrument
|
|
4
|
+
class Guitar::Fret
|
|
5
|
+
attr_reader :note, :string_number, :fret_number
|
|
6
|
+
|
|
7
|
+
# @param note [Tetsujin::Theory::Note] 音
|
|
8
|
+
# @param string_number [Integer] 弦番号
|
|
9
|
+
# @param fret_number [Integer] フレット番号
|
|
10
|
+
def initialize(note:, string_number:, fret_number:)
|
|
11
|
+
raise TypeError unless note.is_a?(Tetsujin::Theory::Note)
|
|
12
|
+
raise TypeError unless string_number.is_a?(Integer)
|
|
13
|
+
raise TypeError unless fret_number.is_a?(Integer)
|
|
14
|
+
|
|
15
|
+
@note = note
|
|
16
|
+
@string_number = string_number
|
|
17
|
+
@fret_number = fret_number
|
|
18
|
+
@pressed = false
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param other [Tetsujin::Instrument::Guitar::Fret]
|
|
22
|
+
# @return [Boolean]
|
|
23
|
+
def ==(other)
|
|
24
|
+
note == other.note && string_number == other.string_number && fret_number == other.fret_number
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Boolean] フレットが押されているか
|
|
28
|
+
def pressed?
|
|
29
|
+
pressed
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [void]
|
|
33
|
+
def press!
|
|
34
|
+
@pressed = true
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @return [void]
|
|
38
|
+
def release!
|
|
39
|
+
@pressed = false
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
attr_reader :pressed
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tetsujin::Instrument
|
|
4
|
+
class Guitar::Frets
|
|
5
|
+
extend Forwardable
|
|
6
|
+
def_delegators :frets, :each, :map, :find, :size
|
|
7
|
+
|
|
8
|
+
attr_reader :frets
|
|
9
|
+
|
|
10
|
+
# @param frets [Array<Tetsujin::Instrument::Guitar::Fret>] フレットの配列
|
|
11
|
+
def initialize(frets:)
|
|
12
|
+
raise TypeError unless frets.all? { |fret| fret.is_a?(Tetsujin::Instrument::Guitar::Fret) }
|
|
13
|
+
@frets = frets
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @param note [Tetsujin::Theory::Note]
|
|
17
|
+
def ==(other)
|
|
18
|
+
self_sorted = frets.sort do |a, b|
|
|
19
|
+
[a.string_number, a.fret_number] <=> [b.string_number, b.fret_number]
|
|
20
|
+
end
|
|
21
|
+
other_sorted = other.frets.sort do |a, b|
|
|
22
|
+
[a.string_number, a.fret_number] <=> [b.string_number, b.fret_number]
|
|
23
|
+
end
|
|
24
|
+
self_sorted == other_sorted
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @param note [Tetsujin::Theory::Note]
|
|
28
|
+
# @return [void]
|
|
29
|
+
def play!(note)
|
|
30
|
+
find_by_note(note)&.press!
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# @param fret_number [Integer]
|
|
34
|
+
# @return [void]
|
|
35
|
+
def press!(fret_number)
|
|
36
|
+
find_by_fret_number(fret_number).press!
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# @return [void]
|
|
40
|
+
def release!
|
|
41
|
+
frets.each(&:release!)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @param fret_number [Integer] フレット番号
|
|
45
|
+
# @return [Tetsujin::Instrument::Guitar::Fret]
|
|
46
|
+
def find_by_fret_number(fret_number)
|
|
47
|
+
frets.find { |fret| fret.fret_number == fret_number }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# @param note [Tetsujin::Theory::Note]
|
|
51
|
+
# @return [Tetsujin::Instrument::Guitar::Fret]
|
|
52
|
+
def find_by_note(note)
|
|
53
|
+
frets.find { |fret| fret.note == note }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tetsujin::Instrument
|
|
4
|
+
class Guitar::String
|
|
5
|
+
attr_reader :tuning, :fretboard_length, :string_number
|
|
6
|
+
|
|
7
|
+
# @param tuning [Tetsujin::Theory::Note] チューニング
|
|
8
|
+
# @param string_number [Integer] 弦番号
|
|
9
|
+
# @param fretboard_length [Integer] フレット数
|
|
10
|
+
def initialize(tuning:, string_number:, fretboard_length: 22)
|
|
11
|
+
raise TypeError unless tuning.is_a?(Tetsujin::Theory::Note)
|
|
12
|
+
raise TypeError unless string_number.is_a?(Integer)
|
|
13
|
+
raise TypeError unless fretboard_length.is_a?(Integer)
|
|
14
|
+
|
|
15
|
+
@tuning = tuning
|
|
16
|
+
@string_number = string_number
|
|
17
|
+
@fretboard_length = fretboard_length
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Tetsujin::Instrument::Guitar::Frets]
|
|
21
|
+
def frets
|
|
22
|
+
@_frets ||= generate_frets
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @param fret_number [Integer]
|
|
26
|
+
# @return [Tetsujin::Instrument::Guitar::Fret]
|
|
27
|
+
def [](fret_number)
|
|
28
|
+
frets.find_by_fret_number(fret_number)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param notes [Enumerable<Tetsujin::Theory::Note>] each でループした際に Note オブジェクトが取得できる Enumerable
|
|
32
|
+
# @return [void]
|
|
33
|
+
def play!(notes)
|
|
34
|
+
notes.each { |note| frets.play!(note) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @param fret_number [Integer]
|
|
38
|
+
# @return [void]
|
|
39
|
+
def press!(fret_number)
|
|
40
|
+
frets.press!(fret_number)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @return [void]
|
|
44
|
+
def release!
|
|
45
|
+
frets.release!
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param other [Tetsujin::Instrument::Guitar::String]
|
|
49
|
+
# @return [Boolean]
|
|
50
|
+
def ==(other)
|
|
51
|
+
tuning == other.tuning && string_number == other.string_number && fretboard_length == other.fretboard_length
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def generate_frets
|
|
57
|
+
frets = (0..fretboard_length).map do |fret_number|
|
|
58
|
+
interval = Tetsujin::Theory::Interval.new(value: fret_number)
|
|
59
|
+
Tetsujin::Instrument::Guitar::Fret.new(note: tuning.add(interval), string_number: string_number, fret_number: fret_number)
|
|
60
|
+
end
|
|
61
|
+
Tetsujin::Instrument::Guitar::Frets.new(frets: frets)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tetsujin::Instrument
|
|
4
|
+
class Guitar
|
|
5
|
+
attr_reader :strings
|
|
6
|
+
|
|
7
|
+
def initialize(strings:)
|
|
8
|
+
raise TypeError unless strings.is_a?(Array)
|
|
9
|
+
raise TypeError unless strings.all? { |string| string.is_a?(Tetsujin::Instrument::Guitar::String) }
|
|
10
|
+
@strings = strings
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @param notes [Enumerable<Tetsujin::Theory::Note>] each でループした際に Note オブジェクトが取得できる Enumerable
|
|
14
|
+
# @return [void]
|
|
15
|
+
def play!(notes)
|
|
16
|
+
strings.each { |string| string.play!(notes) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @param string_number [Integer]
|
|
20
|
+
# @param fret_number [Integer]
|
|
21
|
+
# @return [void]
|
|
22
|
+
def press!(string_number, fret_number)
|
|
23
|
+
find_string(string_number).press!(fret_number)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [void]
|
|
27
|
+
def release!
|
|
28
|
+
strings.each(&:release!)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @param string_number [Integer]
|
|
32
|
+
# @return [Tetsujin::Instrument::Guitar::String]
|
|
33
|
+
def [](string_number)
|
|
34
|
+
strings.find { |string| string.string_number == string_number }
|
|
35
|
+
end
|
|
36
|
+
alias find_string []
|
|
37
|
+
|
|
38
|
+
# @return [Integer]
|
|
39
|
+
def fretboard_length
|
|
40
|
+
strings.map(&:fretboard_length).max
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param displayer [Tetsujin::Instrument::Guitar::Displayer] フレットボードを表示するための Displayer オブジェクト
|
|
44
|
+
# @example
|
|
45
|
+
# 1: E4 ------- F♯4/G♭4 G4 ------- A4 ------- B4 ------- C♯5/D♭5 ------- ------- -------
|
|
46
|
+
# 2: ------- ------- ------- D4 ------- E4 ------- F♯4/G♭4 G4 ------- A4 ------- B4
|
|
47
|
+
# 3: ------- ------- ------- ------- ------- ------- ------- D4 ------- E4 ------- F♯4/G♭4 G4
|
|
48
|
+
# 4: ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- D4
|
|
49
|
+
# 5: ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- -------
|
|
50
|
+
# 6: ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- ------- -------
|
|
51
|
+
# 0 1 2 3 4 5 6 7 8 9 10 11 12
|
|
52
|
+
def print_fretboard(displayer)
|
|
53
|
+
displayer.print_fretboard(self)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @param other [Tetsujin::Instrument::Guitar]
|
|
57
|
+
# @return [Boolean]
|
|
58
|
+
def ==(other)
|
|
59
|
+
strings.sort_by(&:string_number) == other.strings.sort_by(&:string_number)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tetsujin::Theory
|
|
4
|
+
class Interval
|
|
5
|
+
attr_reader :value
|
|
6
|
+
|
|
7
|
+
# @param value [Integer] 度数
|
|
8
|
+
def initialize(value:)
|
|
9
|
+
raise TypeError unless value.is_a?(Integer)
|
|
10
|
+
@value = value
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# @param other [Tetsujin::Theory::Interval]
|
|
14
|
+
# @return [Boolean]
|
|
15
|
+
def ==(other)
|
|
16
|
+
value == other.value
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tetsujin::Theory
|
|
4
|
+
class Note::Factory
|
|
5
|
+
NOTE_NAME_JA_TO_PITCH_CLASS = {
|
|
6
|
+
"ド" => 0,
|
|
7
|
+
"ド#" => 1, "ド♯" => 1, "レb" => 1, "レ♭" => 1,
|
|
8
|
+
"レ" => 2,
|
|
9
|
+
"レ#" => 3, "レ♯" => 3, "ミb" => 3, "ミ♭" => 3,
|
|
10
|
+
"ミ" => 4,
|
|
11
|
+
"ファ" => 5,
|
|
12
|
+
"ファ#" => 6, "ファ♯" => 6, "ソb" => 6, "ソ♭" => 6,
|
|
13
|
+
"ソ" => 7,
|
|
14
|
+
"ソ#" => 8, "ソ♯" => 8, "ラb" => 8, "ラ♭" => 8,
|
|
15
|
+
"ラ" => 9,
|
|
16
|
+
"ラ#" => 10, "ラ♯" => 10, "シb" => 10, "シ♭" => 10,
|
|
17
|
+
"シ" => 11
|
|
18
|
+
}.freeze
|
|
19
|
+
NOTE_NAME_EN_TO_PITCH_CLASS = {
|
|
20
|
+
"C" => 0,
|
|
21
|
+
"C#" => 1, "C♯" => 1, "Db" => 1, "D♭" => 1,
|
|
22
|
+
"D" => 2,
|
|
23
|
+
"D#" => 3, "D♯" => 3, "Eb" => 3, "E♭" => 3,
|
|
24
|
+
"E" => 4,
|
|
25
|
+
"F" => 5,
|
|
26
|
+
"F#" => 6, "F♯" => 6, "Gb" => 6, "G♭" => 6,
|
|
27
|
+
"G" => 7,
|
|
28
|
+
"G#" => 8, "G♯" => 8, "Ab" => 8, "A♭" => 8,
|
|
29
|
+
"A" => 9,
|
|
30
|
+
"A#" => 10, "A♯" => 10, "Bb" => 10, "B♭" => 10,
|
|
31
|
+
"B" => 11
|
|
32
|
+
}.freeze
|
|
33
|
+
NOTE_NAME_ALIAS_TO_PITCH_CLASS = NOTE_NAME_JA_TO_PITCH_CLASS.merge(NOTE_NAME_EN_TO_PITCH_CLASS).freeze
|
|
34
|
+
private_constant :NOTE_NAME_JA_TO_PITCH_CLASS, :NOTE_NAME_EN_TO_PITCH_CLASS, :NOTE_NAME_ALIAS_TO_PITCH_CLASS
|
|
35
|
+
|
|
36
|
+
# @param name [String | Symbol] 音名
|
|
37
|
+
# @param octave [Integer] オクターブ
|
|
38
|
+
# @return [Tetsujin::Theory::Note]
|
|
39
|
+
def self.create_from_name(name:, octave:)
|
|
40
|
+
raise TypeError unless name.is_a?(String) || name.is_a?(Symbol)
|
|
41
|
+
pitch_class = search_pitch_class(name.to_s)
|
|
42
|
+
raise ArgumentError unless pitch_class
|
|
43
|
+
Tetsujin::Theory::Note.new(pitch_class: pitch_class, octave: octave)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def self.search_pitch_class(name)
|
|
49
|
+
note_name_alias_to_pitch_class[name]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def self.note_name_alias_to_pitch_class
|
|
53
|
+
NOTE_NAME_ALIAS_TO_PITCH_CLASS
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tetsujin::Theory
|
|
4
|
+
class Note
|
|
5
|
+
attr_reader :pitch_class, :octave
|
|
6
|
+
|
|
7
|
+
PITCH_CLASS_TO_NOTE_NAME = {
|
|
8
|
+
0 => ["C"],
|
|
9
|
+
1 => ["C♯", "D♭"],
|
|
10
|
+
2 => ["D"],
|
|
11
|
+
3 => ["D♯", "E♭"],
|
|
12
|
+
4 => ["E"],
|
|
13
|
+
5 => ["F"],
|
|
14
|
+
6 => ["F♯", "G♭"],
|
|
15
|
+
7 => ["G"],
|
|
16
|
+
8 => ["G♯", "A♭"],
|
|
17
|
+
9 => ["A"],
|
|
18
|
+
10 => ["A♯", "B♭"],
|
|
19
|
+
11 => ["B"]
|
|
20
|
+
}.freeze
|
|
21
|
+
NOTES_IN_OCTAVE = 12
|
|
22
|
+
private_constant :PITCH_CLASS_TO_NOTE_NAME, :NOTES_IN_OCTAVE
|
|
23
|
+
|
|
24
|
+
# @param pitch_class [Integer] ピッチクラス
|
|
25
|
+
# @param octave [Integer] オクターブ
|
|
26
|
+
def initialize(pitch_class:, octave: 4)
|
|
27
|
+
raise TypeError unless pitch_class.is_a?(Integer)
|
|
28
|
+
raise TypeError unless octave.is_a?(Integer)
|
|
29
|
+
raise ArgumentError unless PITCH_CLASS_TO_NOTE_NAME.key?(pitch_class)
|
|
30
|
+
|
|
31
|
+
@pitch_class = pitch_class
|
|
32
|
+
@octave = octave
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [String] 音名とオクターブを結合した文字列 (例: "G♯4/A♭4")
|
|
36
|
+
def to_s
|
|
37
|
+
note_names = PITCH_CLASS_TO_NOTE_NAME[pitch_class]
|
|
38
|
+
note_names.map { |note_name| "#{note_name}#{octave}" }.join("/")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @param interval [Tetsujin::Theory::Interval] 移動する音程
|
|
42
|
+
# @return [Tetsujin::Theory::Note] 移動後の音
|
|
43
|
+
def add(interval)
|
|
44
|
+
added_octaves, new_pitch_class = (pitch_class + interval.value).divmod(notes_in_octave)
|
|
45
|
+
self.class.new(pitch_class: new_pitch_class, octave: octave + added_octaves)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# @param interval [Tetsujin::Theory::Interval] 移動する音程
|
|
49
|
+
# @return [Tetsujin::Theory::Note] 移動後の音
|
|
50
|
+
def subtract(interval)
|
|
51
|
+
subtracted_octaves, new_pitch_class = (pitch_class - interval.value).divmod(notes_in_octave)
|
|
52
|
+
self.class.new(pitch_class: new_pitch_class, octave: octave + subtracted_octaves)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @param other [Tetsujin::Theory::Note] 比較対象の音
|
|
56
|
+
# @return [Tetsujin::Theory::Interval] 2つの音の音程
|
|
57
|
+
def diff(other)
|
|
58
|
+
pitch_diff = (other.octave - octave) * notes_in_octave + other.pitch_class - pitch_class
|
|
59
|
+
Tetsujin::Theory::Interval.new(value: pitch_diff)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @param other [Tetsujin::Theory::Note] 比較対象の音
|
|
63
|
+
# @return [Boolean] 音名とオクターブが一致しているか
|
|
64
|
+
def ==(other)
|
|
65
|
+
return false unless other.is_a?(self.class)
|
|
66
|
+
pitch_class == other.pitch_class && octave == other.octave
|
|
67
|
+
end
|
|
68
|
+
alias eql? ==
|
|
69
|
+
|
|
70
|
+
# @return [Integer] ハッシュ値
|
|
71
|
+
def hash
|
|
72
|
+
[pitch_class, octave].hash
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @param other [Tetsujin::Theory::Note] 比較対象の音
|
|
76
|
+
# @return [Integer] 音名とオクターブの比較結果
|
|
77
|
+
def <=>(other)
|
|
78
|
+
return nil unless other.is_a?(self.class)
|
|
79
|
+
octave == other.octave ? pitch_class <=> other.pitch_class : octave <=> other.octave
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
# @return [Integer] 1オクターブあたりの音数
|
|
85
|
+
def notes_in_octave
|
|
86
|
+
NOTES_IN_OCTAVE
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
require "forwardable"
|
|
3
|
+
|
|
4
|
+
module Tetsujin::Theory
|
|
5
|
+
class Notes
|
|
6
|
+
extend Forwardable
|
|
7
|
+
def_delegators :notes, :each, :map
|
|
8
|
+
|
|
9
|
+
attr_reader :notes
|
|
10
|
+
|
|
11
|
+
# @param notes [Array<Tetsujin::Theory::Note>] 音の配列
|
|
12
|
+
def initialize(notes:)
|
|
13
|
+
raise TypeError unless notes.is_a?(Array)
|
|
14
|
+
raise ArgumentError unless notes.all? { |note| note.is_a?(Tetsujin::Theory::Note) }
|
|
15
|
+
@notes = notes
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# @param other [Tetsujin::Theory::Notes]
|
|
19
|
+
# @return [Boolean] 同じ音を持つかどうか
|
|
20
|
+
def ==(other)
|
|
21
|
+
self_sorted = notes.sort do |a, b|
|
|
22
|
+
[a.octave, a.pitch_class] <=> [b.octave, b.pitch_class]
|
|
23
|
+
end
|
|
24
|
+
other_sorted = other.notes.sort do |a, b|
|
|
25
|
+
[a.octave, a.pitch_class] <=> [b.octave, b.pitch_class]
|
|
26
|
+
end
|
|
27
|
+
self_sorted == other_sorted
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @param other [Tetsujin::Theory::Notes]
|
|
31
|
+
# @return [Tetsujin::Theory::Notes] 2つの音の和集合
|
|
32
|
+
def +(other)
|
|
33
|
+
new_notes = (notes + other.notes).uniq.sort
|
|
34
|
+
Tetsujin::Theory::Notes.new(notes: new_notes)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Tetsujin::Theory
|
|
4
|
+
class Scale < Notes
|
|
5
|
+
SCALE_PATTERNS = {
|
|
6
|
+
major: [2, 2, 1, 2, 2, 2, 1],
|
|
7
|
+
minor: [2, 1, 2, 2, 1, 2, 2],
|
|
8
|
+
harmonic_minor: [2, 1, 2, 2, 1, 3, 1],
|
|
9
|
+
melodic_minor: [2, 1, 2, 2, 2, 2, 1],
|
|
10
|
+
dorian: [2, 1, 2, 2, 2, 1, 2],
|
|
11
|
+
phrygian: [1, 2, 2, 2, 1, 2, 2],
|
|
12
|
+
lydian: [2, 2, 2, 1, 2, 2, 1],
|
|
13
|
+
mixolydian: [2, 2, 1, 2, 2, 1, 2],
|
|
14
|
+
aeolian: [2, 1, 2, 2, 1, 2, 2],
|
|
15
|
+
locrian: [1, 2, 2, 1, 2, 2, 2],
|
|
16
|
+
whole_tone: [2, 2, 2, 2, 2, 2],
|
|
17
|
+
chromatic: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
|
|
18
|
+
blues: [3, 2, 1, 1, 3, 2],
|
|
19
|
+
pentatonic_major: [2, 2, 3, 2, 3],
|
|
20
|
+
pentatonic_minor: [3, 2, 2, 3, 2],
|
|
21
|
+
diminished: [2, 1, 2, 1, 2, 1, 2, 1]
|
|
22
|
+
}.freeze
|
|
23
|
+
|
|
24
|
+
# @param root [Tetsujin::Theory::Note] スケールのルート音
|
|
25
|
+
# @param pattern [Symbol] スケールの構成パターン
|
|
26
|
+
def initialize(root:, pattern: :major)
|
|
27
|
+
raise TypeError unless root.is_a?(Tetsujin::Theory::Note)
|
|
28
|
+
raise TypeError unless pattern.is_a?(Symbol)
|
|
29
|
+
raise ArgumentError unless SCALE_PATTERNS.key?(pattern)
|
|
30
|
+
|
|
31
|
+
@root = root
|
|
32
|
+
@pattern = SCALE_PATTERNS[pattern]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Array<Tetsujin::Theory::Note>] スケールの音階
|
|
36
|
+
def notes
|
|
37
|
+
@_notes ||= generate_scale
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
attr_reader :root, :pattern
|
|
43
|
+
|
|
44
|
+
# @return [Array<Tetsujin::Theory::Note>] スケールの音階
|
|
45
|
+
def generate_scale
|
|
46
|
+
notes = [root]
|
|
47
|
+
pattern.each do |interval|
|
|
48
|
+
notes << notes.last.add(Tetsujin::Theory::Interval.new(value: interval))
|
|
49
|
+
end
|
|
50
|
+
notes
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/tetsujin.rb
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "tetsujin/version"
|
|
4
|
+
|
|
5
|
+
# ------------- Theory -------------
|
|
6
|
+
require_relative "tetsujin/theory"
|
|
7
|
+
|
|
8
|
+
# Note
|
|
9
|
+
require_relative "tetsujin/theory/note"
|
|
10
|
+
require_relative "tetsujin/theory/notes"
|
|
11
|
+
require_relative "tetsujin/theory/note/factory"
|
|
12
|
+
|
|
13
|
+
# Scale
|
|
14
|
+
require_relative "tetsujin/theory/scale"
|
|
15
|
+
|
|
16
|
+
# Interval
|
|
17
|
+
require_relative "tetsujin/theory/interval"
|
|
18
|
+
|
|
19
|
+
# ------------- Instrument -------------
|
|
20
|
+
require_relative "tetsujin/instrument"
|
|
21
|
+
|
|
22
|
+
# Guitar
|
|
23
|
+
require_relative "tetsujin/instrument/guitar"
|
|
24
|
+
require_relative "tetsujin/instrument/guitar/string"
|
|
25
|
+
require_relative "tetsujin/instrument/guitar/fret"
|
|
26
|
+
require_relative "tetsujin/instrument/guitar/frets"
|
|
27
|
+
require_relative "tetsujin/instrument/guitar/displayer"
|
|
28
|
+
require_relative "tetsujin/instrument/guitar/factory"
|
|
29
|
+
|
|
30
|
+
# ------------- DSL -------------
|
|
31
|
+
require_relative "tetsujin/dsl"
|
|
32
|
+
require_relative "tetsujin/dsl/theory"
|
|
33
|
+
require_relative "tetsujin/dsl/instrument"
|
|
34
|
+
|
|
35
|
+
module Tetsujin
|
|
36
|
+
include Tetsujin::DSL
|
|
37
|
+
end
|
data/sig/tetsujin.rbs
ADDED
metadata
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: tetsujin
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- mew3880
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2024-08-25 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description:
|
|
14
|
+
email:
|
|
15
|
+
- mew3880@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- ".rspec"
|
|
21
|
+
- ".rubocop.yml"
|
|
22
|
+
- LICENSE.txt
|
|
23
|
+
- README.md
|
|
24
|
+
- Rakefile
|
|
25
|
+
- lib/tetsujin.rb
|
|
26
|
+
- lib/tetsujin/dsl.rb
|
|
27
|
+
- lib/tetsujin/dsl/instrument.rb
|
|
28
|
+
- lib/tetsujin/dsl/theory.rb
|
|
29
|
+
- lib/tetsujin/instrument.rb
|
|
30
|
+
- lib/tetsujin/instrument/guitar.rb
|
|
31
|
+
- lib/tetsujin/instrument/guitar/displayer.rb
|
|
32
|
+
- lib/tetsujin/instrument/guitar/factory.rb
|
|
33
|
+
- lib/tetsujin/instrument/guitar/fret.rb
|
|
34
|
+
- lib/tetsujin/instrument/guitar/frets.rb
|
|
35
|
+
- lib/tetsujin/instrument/guitar/string.rb
|
|
36
|
+
- lib/tetsujin/theory.rb
|
|
37
|
+
- lib/tetsujin/theory/interval.rb
|
|
38
|
+
- lib/tetsujin/theory/note.rb
|
|
39
|
+
- lib/tetsujin/theory/note/factory.rb
|
|
40
|
+
- lib/tetsujin/theory/notes.rb
|
|
41
|
+
- lib/tetsujin/theory/scale.rb
|
|
42
|
+
- lib/tetsujin/version.rb
|
|
43
|
+
- sig/tetsujin.rbs
|
|
44
|
+
homepage: https://github.com/Takahashi-Riki/tetsujin
|
|
45
|
+
licenses:
|
|
46
|
+
- MIT
|
|
47
|
+
metadata:
|
|
48
|
+
homepage_uri: https://github.com/Takahashi-Riki/tetsujin
|
|
49
|
+
source_code_uri: https://github.com/Takahashi-Riki/tetsujin
|
|
50
|
+
post_install_message:
|
|
51
|
+
rdoc_options: []
|
|
52
|
+
require_paths:
|
|
53
|
+
- lib
|
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - ">="
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: 2.6.0
|
|
59
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
60
|
+
requirements:
|
|
61
|
+
- - ">="
|
|
62
|
+
- !ruby/object:Gem::Version
|
|
63
|
+
version: '0'
|
|
64
|
+
requirements: []
|
|
65
|
+
rubygems_version: 3.2.3
|
|
66
|
+
signing_key:
|
|
67
|
+
specification_version: 4
|
|
68
|
+
summary: ギターのスケールを生成するライブラリ
|
|
69
|
+
test_files: []
|