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 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
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ require "rubocop/rake_task"
9
+
10
+ RuboCop::RakeTask.new
11
+
12
+ task default: %i[spec rubocop]
@@ -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
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "dsl/theory"
4
+ require_relative "dsl/instrument"
5
+
6
+ module Tetsujin
7
+ module DSL
8
+ include Tetsujin::DSL::Theory
9
+ include Tetsujin::DSL::Instrument
10
+ end
11
+ end
@@ -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,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tetsujin::Instrument
4
+ 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
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tetsujin::Theory
4
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tetsujin
4
+ VERSION = "0.1.0"
5
+ 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
@@ -0,0 +1,4 @@
1
+ module Tetsujin
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
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: []