head_music 0.16.2 → 0.17.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3eff7c01e7dbb6e69538d9cdcbad34c55fc9f05e
4
- data.tar.gz: e78e38ddd5ec3e8c77aae6a9dc371df7183ef29d
3
+ metadata.gz: 3ff78a490907bb272ceebc6b8d7b635ec7ddc629
4
+ data.tar.gz: a0dc85d1b1862519ea0ef6fb2469778180e86bab
5
5
  SHA512:
6
- metadata.gz: 98da361ccd151df8d8b8d31688a783cbf3586845e06bf5584e6131b0eb657f18f820f0111e86cdd6a38e65770a99a64588e5693d938aa364b317f590f01db330
7
- data.tar.gz: 67bfc70343e9c9733671aea1e9d1d80291ee4233e70fe60899e22efe61f8f6132cff06e54804aa34e3b098080db22919ba38a695a615d42219ba04ee2a3f1c74
6
+ metadata.gz: 0bd80123c3cb687b3bdf9feae1bf82bbfbe71dcfd0a40714d5e698d4859e1578db55e2cf447f0c4515547053ac0d2f46379ab7593c038a221f84679f6c7bd263
7
+ data.tar.gz: 6fd59458857b94183c868f3c303f237bab322d455b9bb485a64282e6799ac188fd35bb30329ff903ae1a8bb5bf4bddcf9d1471290570085b44875ec854236c27
@@ -45,6 +45,14 @@ class HeadMusic::Composition
45
45
  [voices.map(&:earliest_bar_number), 1].flatten.max
46
46
  end
47
47
 
48
+ def cantus_firmus_voice
49
+ voices.detect(&:cantus_firmus?)
50
+ end
51
+
52
+ def counterpoint_voice
53
+ voices.reject(&:cantus_firmus?).first
54
+ end
55
+
48
56
  private
49
57
 
50
58
  def ensure_attributes(name, key_signature, meter)
@@ -3,8 +3,8 @@ class HeadMusic::KeySignature
3
3
  attr_reader :scale_type
4
4
  attr_reader :scale
5
5
 
6
- SHARPS = %w{F# C# G# D# A# E# B#}
7
- FLATS = %w{Bb Eb Ab Db Gb Cb Fb}
6
+ SHARPS = %w{F C G D A E B}
7
+ FLATS = %w{B♭ E♭ A♭ D♭ G♭ C♭ F♭}
8
8
 
9
9
  def self.default
10
10
  @default ||= new('C', :major)
@@ -14,7 +14,7 @@ class HeadMusic::KeySignature
14
14
  return identifier if identifier.is_a?(HeadMusic::KeySignature)
15
15
  @key_signatures ||= {}
16
16
  tonic_spelling, scale_type_name = identifier.strip.split(/\s/)
17
- hash_key = HeadMusic::Utilities::HashKey.for(identifier.gsub(/#/, 'sharp').gsub(/b/, 'flat'))
17
+ hash_key = HeadMusic::Utilities::HashKey.for(identifier.gsub(/#|♯/, 'sharp').gsub(/b|♭/, 'flat'))
18
18
  @key_signatures[hash_key] ||= new(tonic_spelling, scale_type_name)
19
19
  end
20
20
 
@@ -55,7 +55,7 @@ class HeadMusic::KeySignature
55
55
  end
56
56
 
57
57
  def name
58
- [tonic_spelling.to_s, scale_type.to_s].join(' ')
58
+ [tonic_spelling, scale_type].join(' ')
59
59
  end
60
60
 
61
61
  def ==(other)
@@ -0,0 +1,54 @@
1
+ class HeadMusic::Language
2
+ include Comparable
3
+ include HeadMusic::NamedRudiment
4
+
5
+ LANGUAGES = [
6
+ { name: 'American English', short_name: 'English', abbreviation: 'en-US' },
7
+ { name: 'British English', abbreviation: 'en-GB' },
8
+ { name: 'French', native_name: 'Français', abbreviation: 'fr' },
9
+ { name: 'German', native_name: 'Deutsche', abbreviation: 'de' },
10
+ { name: 'Italian', native_name: 'Italiano', abbreviation: 'it' },
11
+ { name: 'Spanish', native_name: 'Español', abbreviation: 'es' },
12
+ { name: 'Russian', native_name: 'русский', abbreviation: 'ru' },
13
+ ]
14
+
15
+ LANGUAGES.map { |language| ::HeadMusic::Utilities::HashKey.for(language[:name]) }.each do |language_key|
16
+ define_singleton_method(language_key) { HeadMusic::Language.get(language_key) }
17
+ end
18
+
19
+ LANGUAGES.map { |language| ::HeadMusic::Utilities::HashKey.for(language[:native_name]) }.reject(&:nil?).each do |language_key|
20
+ define_singleton_method(language_key) { HeadMusic::Language.get(language_key) }
21
+ end
22
+
23
+ LANGUAGES.map { |language| ::HeadMusic::Utilities::HashKey.for(language[:short_name]) }.reject(&:nil?).each do |language_key|
24
+ define_singleton_method(language_key) { HeadMusic::Language.get(language_key) }
25
+ end
26
+
27
+ def self.default
28
+ self.english
29
+ end
30
+
31
+ def self.get(name)
32
+ get_by_name(name) || default
33
+ end
34
+
35
+ attr_accessor :name, :native_name, :short_name
36
+
37
+ def initialize(identifier)
38
+ identifier_key = HeadMusic::Utilities::HashKey.for(identifier)
39
+ language_data = LANGUAGES.detect { |data|
40
+ [:name, :native_name, :short_name].map { |key| HeadMusic::Utilities::HashKey.for(data[key]) }.include?(identifier_key)
41
+ }
42
+ @name = language_data[:name]
43
+ @native_name = language_data[:native_name]
44
+ @short_name = language_data[:short_name]
45
+ end
46
+
47
+ def <=>(other)
48
+ name.to_s <=> other.to_s
49
+ end
50
+
51
+ def inspect
52
+ [name, native_name, short_name].reject(&:nil?).join(' / ')
53
+ end
54
+ end
@@ -3,8 +3,6 @@ class HeadMusic::Octave
3
3
 
4
4
  DEFAULT = 4
5
5
 
6
- MATCHER = /(-?\d+)$/
7
-
8
6
  def self.get(identifier)
9
7
  from_number(identifier) || from_name(identifier) || default
10
8
  end
@@ -18,7 +16,7 @@ class HeadMusic::Octave
18
16
 
19
17
  def self.from_name(string)
20
18
  if string.to_s.match(HeadMusic::Spelling::MATCHER)
21
- _letter, _accidental, octave_string = string.to_s.match(HeadMusic::Spelling::MATCHER).captures
19
+ _letter, _sign, octave_string = string.to_s.match(HeadMusic::Spelling::MATCHER).captures
22
20
  @octaves ||= {}
23
21
  @octaves[octave_string.to_i] ||= new(octave_string.to_i) if octave_string
24
22
  end
@@ -5,9 +5,9 @@ class HeadMusic::Pitch
5
5
  attr_reader :octave
6
6
 
7
7
  delegate :letter_name, :letter_name_cycle, to: :spelling
8
- delegate :accidental, :sharp?, :flat?, to: :spelling
8
+ delegate :sign, :sharp?, :flat?, to: :spelling
9
9
  delegate :pitch_class, to: :spelling
10
- delegate :semitones, to: :accidental, prefix: true, allow_nil: true
10
+ delegate :semitones, to: :sign, prefix: true, allow_nil: true
11
11
 
12
12
  delegate :smallest_interval_to, to: :pitch_class
13
13
 
@@ -32,17 +32,17 @@ class HeadMusic::Pitch
32
32
  natural_letter_pitch = get(HeadMusic::LetterName.get(letter_name).pitch_class)
33
33
  natural_letter_pitch += 12 while (number - natural_letter_pitch.to_i).to_i >= 11
34
34
  natural_letter_pitch = get(natural_letter_pitch)
35
- accidental_interval = natural_letter_pitch.smallest_interval_to(HeadMusic::PitchClass.get(number))
36
- accidental = HeadMusic::Accidental.for_interval(accidental_interval)
37
- spelling = HeadMusic::Spelling.fetch_or_create(letter_name, accidental)
35
+ sign_interval = natural_letter_pitch.smallest_interval_to(HeadMusic::PitchClass.get(number))
36
+ sign = HeadMusic::Sign.by(:semitones, sign_interval) if sign_interval != 0
37
+ spelling = HeadMusic::Spelling.fetch_or_create(letter_name, sign)
38
38
  fetch_or_create(spelling, natural_letter_pitch.octave)
39
39
  end
40
40
 
41
41
  def self.fetch_or_create(spelling, octave)
42
42
  @pitches ||= {}
43
43
  if spelling && (-1..9).include?(octave)
44
- key = [spelling, octave].join
45
- @pitches[key] ||= new(spelling, octave)
44
+ hash_key = [spelling, octave].join
45
+ @pitches[hash_key] ||= new(spelling, octave)
46
46
  end
47
47
  end
48
48
 
@@ -56,7 +56,7 @@ class HeadMusic::Pitch
56
56
  end
57
57
 
58
58
  def midi_note_number
59
- (octave + 1) * 12 + letter_name.pitch_class.to_i + accidental_semitones.to_i
59
+ (octave + 1) * 12 + letter_name.pitch_class.to_i + sign_semitones.to_i
60
60
  end
61
61
 
62
62
  alias_method :midi, :midi_note_number
@@ -93,7 +93,8 @@ class HeadMusic::Pitch
93
93
  end
94
94
 
95
95
  def ==(value)
96
- to_s == value.to_s
96
+ other = HeadMusic::Pitch.get(value)
97
+ to_s == other.to_s
97
98
  end
98
99
 
99
100
  def <=>(other)
@@ -8,7 +8,7 @@ class HeadMusic::Scale
8
8
  root_pitch = HeadMusic::Pitch.get(root_pitch)
9
9
  scale_type = HeadMusic::ScaleType.get(scale_type || :major)
10
10
  @scales ||= {}
11
- name = [root_pitch.to_s, scale_type].join(' ')
11
+ name = [root_pitch, scale_type].join(' ')
12
12
  hash_key = HeadMusic::Utilities::HashKey.for(name)
13
13
  @scales[hash_key] ||= new(root_pitch, scale_type)
14
14
  end
@@ -16,21 +16,20 @@ class HeadMusic::ScaleDegree
16
16
  scale.letter_name_cycle.index(spelling.letter_name.to_s) + 1
17
17
  end
18
18
 
19
- def accidental
20
- spelling.accidental
21
- scale_degree_usual_spelling.accidental
22
- accidental_semitones = spelling.accidental && spelling.accidental.semitones || 0
23
- usual_accidental_semitones = scale_degree_usual_spelling.accidental && scale_degree_usual_spelling.accidental.semitones || 0
24
- HeadMusic::Accidental.for_interval(accidental_semitones - usual_accidental_semitones)
19
+ def sign
20
+ sign_semitones = spelling.sign && spelling.sign.semitones || 0
21
+ usual_sign_semitones = scale_degree_usual_spelling.sign && scale_degree_usual_spelling.sign.semitones || 0
22
+ delta = sign_semitones - usual_sign_semitones
23
+ HeadMusic::Sign.by(:semitones, delta) if delta != 0
25
24
  end
26
25
 
27
26
  def to_s
28
- "#{accidental}#{degree}"
27
+ "#{sign}#{degree}"
29
28
  end
30
29
 
31
30
  def <=>(other)
32
31
  if other.is_a?(HeadMusic::ScaleDegree)
33
- [degree, accidental.semitones] <=> [other.degree, other.accidental.semitones]
32
+ [degree, sign.semitones] <=> [other.degree, other.sign.semitones]
34
33
  else
35
34
  to_s <=> other.to_s
36
35
  end
@@ -39,7 +38,7 @@ class HeadMusic::ScaleDegree
39
38
  def name_for_degree
40
39
  if scale_type.diatonic?
41
40
  NAME_FOR_DIATONIC_DEGREE[degree] ||
42
- (scale_type.intervals.last == 1 || accidental == '#' ? 'leading tone' : 'subtonic')
41
+ (scale_type.intervals.last == 1 || sign == '#' ? 'leading tone' : 'subtonic')
43
42
  end
44
43
  end
45
44
 
@@ -0,0 +1,72 @@
1
+ class HeadMusic::Sign
2
+ include Comparable
3
+
4
+ attr_reader :identifier, :name, :ascii, :unicode, :html_entity, :cents
5
+
6
+ def self.all
7
+ @all ||= [
8
+ new(identifier: :sharp, name: 'sharp', ascii: '#', unicode: '♯', html_entity: '&#9839;', cents: 100),
9
+ new(identifier: :flat, name: 'flat', ascii: 'b', unicode: "♭", html_entity: '&#9837;', cents: -100),
10
+ new(identifier: :natural, name: 'natural', ascii: '', unicode: '♮', html_entity: '&#9838;', cents: 0),
11
+ new(identifier: :double_sharp, name: 'double sharp', ascii: '##', unicode: '𝄪', html_entity: '&#119082;', cents: 200),
12
+ new(identifier: :double_flat, name: 'double flat', ascii: 'bb', unicode: '𝄫', html_entity: '&#119083;', cents: -200),
13
+ ]
14
+ end
15
+
16
+ def self.symbols
17
+ @sign_symbols ||= all.map { |sign| [sign.ascii, sign.unicode] }.flatten.reject { |s| s.nil? || s.empty? }
18
+ end
19
+
20
+ def self.matcher
21
+ @matcher ||= Regexp.new symbols.join('|')
22
+ end
23
+
24
+ def self.symbol?(candidate)
25
+ /^(#{matcher})$/.match?(candidate)
26
+ end
27
+
28
+ def self.get(identifier)
29
+ return identifier if identifier.is_a?(HeadMusic::Sign)
30
+ all.detect do |sign|
31
+ sign.representions.include?(identifier)
32
+ end
33
+ end
34
+
35
+ def self.by(key, value)
36
+ all.detect do |sign|
37
+ if %i[cents semitones].include?(key.to_sym)
38
+ sign.send(key) == value
39
+ end
40
+ end
41
+ end
42
+
43
+ def representions
44
+ [identifier, identifier.to_s, name, ascii, unicode, html_entity].reject { |representation| representation.to_s.strip == '' }
45
+ end
46
+
47
+ def semitones
48
+ cents / 100.0
49
+ end
50
+
51
+ def to_s
52
+ unicode
53
+ end
54
+
55
+ def <=>(other)
56
+ other = HeadMusic::Sign.get(other)
57
+ self.cents <=> other.cents
58
+ end
59
+
60
+ private
61
+
62
+ def initialize(attributes)
63
+ @identifier = attributes[:identifier]
64
+ @name = attributes[:name]
65
+ @ascii = attributes[:ascii]
66
+ @unicode = attributes[:unicode]
67
+ @html_entity = attributes[:html_entity]
68
+ @cents = attributes[:cents]
69
+ end
70
+
71
+ private_class_method :new
72
+ end
@@ -1,9 +1,9 @@
1
1
  class HeadMusic::Spelling
2
- MATCHER = /^\s*([A-G])([b#]*)(\-?\d+)?\s*$/i
2
+ MATCHER = /^\s*([A-G])(#{HeadMusic::Sign.matcher}?)(\-?\d+)?\s*$/i
3
3
 
4
4
  attr_reader :pitch_class
5
5
  attr_reader :letter_name
6
- attr_reader :accidental
6
+ attr_reader :sign
7
7
 
8
8
  delegate :number, to: :pitch_class, prefix: true
9
9
  delegate :to_i, to: :pitch_class_number
@@ -11,6 +11,7 @@ class HeadMusic::Spelling
11
11
  delegate :enharmonic?, to: :pitch_class
12
12
 
13
13
  def self.get(identifier)
14
+ return identifier if identifier.is_a?(HeadMusic::Spelling)
14
15
  from_name(identifier) || from_number(identifier)
15
16
  end
16
17
 
@@ -20,11 +21,11 @@ class HeadMusic::Spelling
20
21
 
21
22
  def self.from_name(name)
22
23
  if match(name)
23
- letter_name, accidental_string, _octave = match(name).captures
24
+ letter_name, sign_string, _octave = match(name).captures
24
25
  letter_name = HeadMusic::LetterName.get(letter_name)
25
26
  return nil unless letter_name
26
- accidental = HeadMusic::Accidental.get(accidental_string)
27
- fetch_or_create(letter_name, accidental)
27
+ sign = HeadMusic::Sign.get(sign_string)
28
+ fetch_or_create(letter_name, sign)
28
29
  end
29
30
  end
30
31
 
@@ -38,26 +39,26 @@ class HeadMusic::Spelling
38
39
  def self.from_number_and_letter(number, letter_name)
39
40
  letter_name = HeadMusic::LetterName.get(letter_name)
40
41
  natural_letter_pitch_class = letter_name.pitch_class
41
- accidental_interval = natural_letter_pitch_class.smallest_interval_to(HeadMusic::PitchClass.get(number))
42
- accidental = HeadMusic::Accidental.for_interval(accidental_interval)
43
- fetch_or_create(letter_name, accidental)
42
+ sign_interval = natural_letter_pitch_class.smallest_interval_to(HeadMusic::PitchClass.get(number))
43
+ sign = HeadMusic::Sign.by(:semitones, sign_interval) if sign_interval != 0
44
+ fetch_or_create(letter_name, sign)
44
45
  end
45
46
 
46
- def self.fetch_or_create(letter_name, accidental)
47
+ def self.fetch_or_create(letter_name, sign)
47
48
  @spellings ||= {}
48
- key = [letter_name, accidental].join
49
- @spellings[key] ||= new(letter_name, accidental)
49
+ hash_key = [letter_name, sign].join
50
+ @spellings[hash_key] ||= new(letter_name, sign)
50
51
  end
51
52
 
52
- def initialize(letter_name, accidental = nil)
53
+ def initialize(letter_name, sign = nil)
53
54
  @letter_name = HeadMusic::LetterName.get(letter_name.to_s)
54
- @accidental = HeadMusic::Accidental.get(accidental.to_s)
55
- accidental_semitones = @accidental ? @accidental.semitones : 0
56
- @pitch_class = HeadMusic::PitchClass.get(letter_name.pitch_class + accidental_semitones)
55
+ @sign = HeadMusic::Sign.get(sign)
56
+ sign_semitones = @sign ? @sign.semitones : 0
57
+ @pitch_class = HeadMusic::PitchClass.get(letter_name.pitch_class + sign_semitones)
57
58
  end
58
59
 
59
60
  def name
60
- [letter_name, accidental].join
61
+ [letter_name, sign].join
61
62
  end
62
63
 
63
64
  def to_s
@@ -65,15 +66,16 @@ class HeadMusic::Spelling
65
66
  end
66
67
 
67
68
  def sharp?
68
- accidental && accidental == '#'
69
+ sign && sign == '#'
69
70
  end
70
71
 
71
72
  def flat?
72
- accidental && accidental == 'b'
73
+ sign && sign == 'b'
73
74
  end
74
75
 
75
76
  def ==(value)
76
- to_s == value.to_s
77
+ other = HeadMusic::Spelling.get(value)
78
+ to_s == other.to_s
77
79
  end
78
80
 
79
81
  def scale(scale_type_name = nil)
@@ -14,7 +14,7 @@ class HeadMusic::Style::Annotations::Diatonic < HeadMusic::Style::Annotation
14
14
  notes_not_in_key.reject do |note|
15
15
  penultimate_note &&
16
16
  note == penultimate_note &&
17
- HeadMusic::ScaleDegree.new(key_signature, note.pitch.spelling).accidental == '#'
17
+ HeadMusic::ScaleDegree.new(key_signature, note.pitch.spelling).sign == '#'
18
18
  end
19
19
  end
20
20
 
@@ -1,3 +1,3 @@
1
1
  module HeadMusic
2
- VERSION = "0.16.2"
2
+ VERSION = "0.17.0"
3
3
  end
data/lib/head_music.rb CHANGED
@@ -11,9 +11,13 @@ require 'active_support/core_ext/module/delegation'
11
11
  require 'active_support/core_ext/string/access'
12
12
  require 'humanize'
13
13
 
14
+ # utilities
15
+ require 'head_music/utilities/hash_key'
16
+
17
+ # modules
14
18
  require 'head_music/named_rudiment'
15
19
 
16
- require 'head_music/accidental'
20
+ # rudiments
17
21
  require 'head_music/bar'
18
22
  require 'head_music/chord'
19
23
  require 'head_music/circle'
@@ -26,6 +30,7 @@ require 'head_music/harmonic_interval'
26
30
  require 'head_music/instrument'
27
31
  require 'head_music/interval'
28
32
  require 'head_music/key_signature'
33
+ require 'head_music/language'
29
34
  require 'head_music/letter_name'
30
35
  require 'head_music/melodic_interval'
31
36
  require 'head_music/meter'
@@ -43,13 +48,17 @@ require 'head_music/rhythmic_value'
43
48
  require 'head_music/scale'
44
49
  require 'head_music/scale_degree'
45
50
  require 'head_music/scale_type'
51
+ require 'head_music/sign'
46
52
  require 'head_music/spelling'
47
53
  require 'head_music/staff'
54
+ require 'head_music/voice'
48
55
 
56
+ # analysis
49
57
  require 'head_music/style/analysis'
50
58
  require 'head_music/style/annotation'
51
59
  require 'head_music/style/mark'
52
60
 
61
+ # annotations
53
62
  require 'head_music/style/annotations/always_move'
54
63
  require 'head_music/style/annotations/approach_perfection_contrarily'
55
64
  require 'head_music/style/annotations/at_least_eight_notes'
@@ -84,10 +93,8 @@ require 'head_music/style/annotations/step_to_final_note'
84
93
  require 'head_music/style/annotations/step_up_to_final_note'
85
94
  require 'head_music/style/annotations/up_to_fourteen_notes'
86
95
 
96
+ # rulesets
87
97
  require 'head_music/style/rulesets/fux_cantus_firmus'
88
98
  require 'head_music/style/rulesets/modern_cantus_firmus'
89
99
  require 'head_music/style/rulesets/first_species_melody'
90
100
  require 'head_music/style/rulesets/first_species_harmony'
91
-
92
- require 'head_music/utilities/hash_key'
93
- require 'head_music/voice'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: head_music
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.2
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Head
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2017-06-16 00:00:00.000000000 Z
11
+ date: 2018-02-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -116,7 +116,6 @@ files:
116
116
  - circle.yml
117
117
  - head_music.gemspec
118
118
  - lib/head_music.rb
119
- - lib/head_music/accidental.rb
120
119
  - lib/head_music/bar.rb
121
120
  - lib/head_music/chord.rb
122
121
  - lib/head_music/circle.rb
@@ -129,6 +128,7 @@ files:
129
128
  - lib/head_music/instrument.rb
130
129
  - lib/head_music/interval.rb
131
130
  - lib/head_music/key_signature.rb
131
+ - lib/head_music/language.rb
132
132
  - lib/head_music/letter_name.rb
133
133
  - lib/head_music/melodic_interval.rb
134
134
  - lib/head_music/meter.rb
@@ -147,6 +147,7 @@ files:
147
147
  - lib/head_music/scale.rb
148
148
  - lib/head_music/scale_degree.rb
149
149
  - lib/head_music/scale_type.rb
150
+ - lib/head_music/sign.rb
150
151
  - lib/head_music/spelling.rb
151
152
  - lib/head_music/staff.rb
152
153
  - lib/head_music/style/analysis.rb
@@ -1,42 +0,0 @@
1
- class HeadMusic::Accidental
2
- ACCIDENTAL_SEMITONES = {
3
- '#' => 1,
4
- '##' => 2,
5
- 'b' => -1,
6
- 'bb' => -2
7
- }
8
-
9
- attr_reader :string
10
-
11
- def self.get(identifier)
12
- for_symbol(identifier) || for_interval(identifier)
13
- end
14
-
15
- def self.for_symbol(identifier)
16
- @accidentals ||= {}
17
- @accidentals[identifier.to_s] ||= new(identifier.to_s) if ACCIDENTAL_SEMITONES.keys.include?(identifier.to_s)
18
- end
19
-
20
- def self.for_interval(semitones)
21
- @accidentals ||= {}
22
- @accidentals[semitones.to_i] ||= ACCIDENTAL_SEMITONES.key(semitones.to_i)
23
- end
24
-
25
- def initialize(string)
26
- @string = string
27
- end
28
-
29
- def to_s
30
- string
31
- end
32
-
33
- def ==(value)
34
- to_s == value.to_s
35
- end
36
-
37
- def semitones
38
- ACCIDENTAL_SEMITONES.fetch(string, 0)
39
- end
40
-
41
- private_class_method :new
42
- end