head_music 6.0.0 → 7.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,24 @@
1
+ # Accepts the letter name count between two notes and categorizes the interval
2
+ class HeadMusic::DiatonicInterval::Category
3
+ attr_reader :number
4
+
5
+ def initialize(number)
6
+ @number = number
7
+ end
8
+
9
+ def step?
10
+ number == 2
11
+ end
12
+
13
+ def skip?
14
+ number == 3
15
+ end
16
+
17
+ def leap?
18
+ number >= 3
19
+ end
20
+
21
+ def large_leap?
22
+ number > 3
23
+ end
24
+ end
@@ -0,0 +1,73 @@
1
+ # Accepts a number and number of semitones and privides the naming methods.
2
+ class HeadMusic::DiatonicInterval::Naming
3
+ QUALITY_SEMITONES = HeadMusic::DiatonicInterval::QUALITY_SEMITONES
4
+ NUMBER_NAMES = HeadMusic::DiatonicInterval::NUMBER_NAMES
5
+ NAME_SUFFIXES = HeadMusic::DiatonicInterval::NAME_SUFFIXES
6
+
7
+ attr_reader :number, :semitones
8
+
9
+ def initialize(number:, semitones:)
10
+ @number = number
11
+ @semitones = semitones
12
+ end
13
+
14
+ def simple_number
15
+ @simple_number ||= octave_equivalent? ? 8 : (number - 1) % 7 + 1
16
+ end
17
+
18
+ def simple_name
19
+ [quality_name, simple_number_name].join(" ")
20
+ end
21
+
22
+ def quality_name
23
+ starting_quality = QUALITY_SEMITONES[simple_number_name.to_sym].keys.first
24
+ delta = simple_semitones - (QUALITY_SEMITONES[simple_number_name.to_sym][starting_quality] % 12)
25
+ delta -= 12 while delta >= 6
26
+ HeadMusic::Quality.from(starting_quality, delta)
27
+ end
28
+
29
+ def simple_number_name
30
+ NUMBER_NAMES[simple_number - 1]
31
+ end
32
+
33
+ def number_name
34
+ NUMBER_NAMES[number - 1] || (number.to_s + NAME_SUFFIXES[number % 10])
35
+ end
36
+
37
+ def name
38
+ if named_number?
39
+ [quality_name, number_name].join(" ")
40
+ elsif simple_name == "perfect octave"
41
+ "#{octaves.humanize} octaves"
42
+ else
43
+ "#{octaves.humanize} octaves and #{quality.article} #{simple_name}"
44
+ end
45
+ end
46
+
47
+ def shorthand
48
+ step_shorthand = (number == 1) ? "U" : number
49
+ [quality.shorthand, step_shorthand].join
50
+ end
51
+
52
+ private
53
+
54
+ def simple_semitones
55
+ @simple_semitones ||= semitones % 12
56
+ end
57
+
58
+ def named_number?
59
+ number < NUMBER_NAMES.length
60
+ end
61
+
62
+ def quality
63
+ @quality ||= HeadMusic::Quality.get(quality_name)
64
+ end
65
+
66
+ def octaves
67
+ @octaves ||= semitones / 12
68
+ end
69
+
70
+ def octave_equivalent?
71
+ number > 1 && ((number - 1) % 7).zero?
72
+ end
73
+ end
@@ -0,0 +1,47 @@
1
+ class HeadMusic::DiatonicInterval::Parser
2
+ NUMBER_NAMES = HeadMusic::DiatonicInterval::NUMBER_NAMES
3
+
4
+ attr_reader :identifier
5
+
6
+ def initialize(identifier)
7
+ @identifier = expand(identifier)
8
+ end
9
+
10
+ def words
11
+ identifier.to_s.split(/[_ ]+/)
12
+ end
13
+
14
+ def quality_name
15
+ words[0..-2].join(" ").to_sym
16
+ end
17
+
18
+ def degree_name
19
+ words.last
20
+ end
21
+
22
+ def steps
23
+ NUMBER_NAMES.index(degree_name)
24
+ end
25
+
26
+ def higher_letter
27
+ HeadMusic::Pitch.middle_c.letter_name.steps_up(steps)
28
+ end
29
+
30
+ def expand(identifier)
31
+ if /[A-Z]\d{1,2}/i.match?(identifier)
32
+ number = NUMBER_NAMES[identifier.gsub(/[A-Z]/i, "").to_i - 1]
33
+ return [quality_for(identifier[0]), number].join("_").to_sym
34
+ end
35
+ identifier
36
+ end
37
+
38
+ def quality_abbreviations
39
+ HeadMusic::DiatonicInterval::QUALITY_ABBREVIATIONS
40
+ end
41
+
42
+ def quality_for(abbreviation)
43
+ quality_abbreviations[abbreviation.to_sym] ||
44
+ quality_abbreviations[abbreviation.upcase.to_sym] ||
45
+ quality_abbreviations[abbreviation.downcase.to_sym]
46
+ end
47
+ end
@@ -0,0 +1,36 @@
1
+ # Accepts a name and a quality and returns the number of semitones
2
+ class HeadMusic::DiatonicInterval::Semitones
3
+ QUALITY_SEMITONES = HeadMusic::DiatonicInterval::QUALITY_SEMITONES
4
+
5
+ attr_reader :count
6
+
7
+ def initialize(name, quality_name)
8
+ @count = self.class.degree_quality_semitones.dig(name, quality_name)
9
+ end
10
+
11
+ def self.degree_quality_semitones
12
+ @degree_quality_semitones ||= {}.tap do |degree_quality_semitones|
13
+ QUALITY_SEMITONES.each do |degree_name, qualities|
14
+ default_quality = qualities.keys.first
15
+ default_semitones = qualities[default_quality]
16
+ degree_quality_semitones[degree_name] = _semitones_for_degree(default_quality, default_semitones)
17
+ end
18
+ end
19
+ end
20
+
21
+ def self._semitones_for_degree(quality, default_semitones)
22
+ {}.tap do |semitones|
23
+ _degree_quality_modifications(quality).each do |current_quality, delta|
24
+ semitones[current_quality] = default_semitones + delta
25
+ end
26
+ end
27
+ end
28
+
29
+ def self._degree_quality_modifications(quality)
30
+ if quality == :perfect
31
+ HeadMusic::Quality::PERFECT_INTERVAL_MODIFICATION.invert
32
+ else
33
+ HeadMusic::Quality::MAJOR_INTERVAL_MODIFICATION.invert
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,50 @@
1
+ # Encapsulate the distance methods of the interval
2
+ class HeadMusic::DiatonicInterval::Size
3
+ attr_reader :low_pitch, :high_pitch
4
+
5
+ def initialize(pitch1, pitch2)
6
+ @low_pitch, @high_pitch = *[pitch1, pitch2].sort
7
+ end
8
+
9
+ def number
10
+ @number ||= @low_pitch.steps_to(@high_pitch) + 1
11
+ end
12
+
13
+ def simple_number
14
+ @simple_number ||= octave_equivalent? ? 8 : (number - 1) % 7 + 1
15
+ end
16
+
17
+ def octaves
18
+ @octaves ||= number / 8
19
+ end
20
+
21
+ def simple?
22
+ number <= 8
23
+ end
24
+
25
+ def compound?
26
+ !simple?
27
+ end
28
+
29
+ def simple_semitones
30
+ @simple_semitones ||= semitones % 12
31
+ end
32
+
33
+ def semitones
34
+ (high_pitch - low_pitch).to_i
35
+ end
36
+
37
+ def steps
38
+ number - 1
39
+ end
40
+
41
+ def simple_steps
42
+ steps % 7
43
+ end
44
+
45
+ private
46
+
47
+ def octave_equivalent?
48
+ number > 1 && ((number - 1) % 7).zero?
49
+ end
50
+ end
@@ -43,237 +43,13 @@ class HeadMusic::DiatonicInterval
43
43
  delegate :to_s, to: :name
44
44
  delegate :perfect?, :major?, :minor?, :diminished?, :augmented?, :doubly_diminished?, :doubly_augmented?, to: :quality
45
45
 
46
- # Interprets a string or symbol
47
- class Parser
48
- attr_reader :identifier
49
-
50
- def initialize(identifier)
51
- @identifier = expand(identifier)
52
- end
53
-
54
- def words
55
- identifier.to_s.split(/[_ ]+/)
56
- end
57
-
58
- def quality_name
59
- words[0..-2].join(" ").to_sym
60
- end
61
-
62
- def degree_name
63
- words.last
64
- end
65
-
66
- def steps
67
- NUMBER_NAMES.index(degree_name)
68
- end
69
-
70
- def higher_letter
71
- HeadMusic::Pitch.middle_c.letter_name.steps_up(steps)
72
- end
73
-
74
- def expand(identifier)
75
- if /[A-Z]\d{1,2}/i.match?(identifier)
76
- number = NUMBER_NAMES[identifier.gsub(/[A-Z]/i, "").to_i - 1]
77
- return [quality_for(identifier[0]), number].join("_").to_sym
78
- end
79
- identifier
80
- end
81
-
82
- def quality_for(abbreviation)
83
- QUALITY_ABBREVIATIONS[abbreviation.to_sym] ||
84
- QUALITY_ABBREVIATIONS[abbreviation.upcase.to_sym] ||
85
- QUALITY_ABBREVIATIONS[abbreviation.downcase.to_sym]
86
- end
87
- end
88
-
89
- # Accepts a name and a quality and returns the number of semitones
90
- class Semitones
91
- attr_reader :count
92
-
93
- def initialize(name, quality_name)
94
- @count = Semitones.degree_quality_semitones.dig(name, quality_name)
95
- end
96
-
97
- def self.degree_quality_semitones
98
- @degree_quality_semitones ||= {}.tap do |degree_quality_semitones|
99
- QUALITY_SEMITONES.each do |degree_name, qualities|
100
- default_quality = qualities.keys.first
101
- default_semitones = qualities[default_quality]
102
- degree_quality_semitones[degree_name] = _semitones_for_degree(default_quality, default_semitones)
103
- end
104
- end
105
- end
106
-
107
- def self._semitones_for_degree(quality, default_semitones)
108
- {}.tap do |semitones|
109
- _degree_quality_modifications(quality).each do |current_quality, delta|
110
- semitones[current_quality] = default_semitones + delta
111
- end
112
- end
113
- end
114
-
115
- def self._degree_quality_modifications(quality)
116
- if quality == :perfect
117
- HeadMusic::Quality::PERFECT_INTERVAL_MODIFICATION.invert
118
- else
119
- HeadMusic::Quality::MAJOR_INTERVAL_MODIFICATION.invert
120
- end
121
- end
122
- end
123
-
124
- # Accepts the letter name count between two notes and categorizes the interval
125
- class Category
126
- attr_reader :number
127
-
128
- def initialize(number)
129
- @number = number
130
- end
131
-
132
- def step?
133
- number == 2
134
- end
135
-
136
- def skip?
137
- number == 3
138
- end
139
-
140
- def leap?
141
- number >= 3
142
- end
143
-
144
- def large_leap?
145
- number > 3
146
- end
147
- end
148
-
149
- # Encapsulate the distance methods of the interval
150
- class Size
151
- attr_reader :low_pitch, :high_pitch
152
-
153
- def initialize(pitch1, pitch2)
154
- @low_pitch, @high_pitch = *[pitch1, pitch2].sort
155
- end
156
-
157
- def number
158
- @number ||= @low_pitch.steps_to(@high_pitch) + 1
159
- end
160
-
161
- def simple_number
162
- @simple_number ||= octave_equivalent? ? 8 : (number - 1) % 7 + 1
163
- end
164
-
165
- def octaves
166
- @octaves ||= number / 8
167
- end
168
-
169
- def simple?
170
- number <= 8
171
- end
172
-
173
- def compound?
174
- !simple?
175
- end
176
-
177
- def simple_semitones
178
- @simple_semitones ||= semitones % 12
179
- end
180
-
181
- def semitones
182
- (high_pitch - low_pitch).to_i
183
- end
184
-
185
- def steps
186
- number - 1
187
- end
188
-
189
- def simple_steps
190
- steps % 7
191
- end
192
-
193
- private
194
-
195
- def octave_equivalent?
196
- number > 1 && ((number - 1) % 7).zero?
197
- end
198
- end
199
-
200
- # Accepts a number and number of semitones and privides the naming methods.
201
- class Naming
202
- attr_reader :number, :semitones
203
-
204
- def initialize(number:, semitones:)
205
- @number = number
206
- @semitones = semitones
207
- end
208
-
209
- def simple_semitones
210
- @simple_semitones ||= semitones % 12
211
- end
212
-
213
- def simple_number
214
- @simple_number ||= octave_equivalent? ? 8 : (number - 1) % 7 + 1
215
- end
216
-
217
- def simple_name
218
- [quality_name, simple_number_name].join(" ")
219
- end
220
-
221
- def quality_name
222
- starting_quality = QUALITY_SEMITONES[simple_number_name.to_sym].keys.first
223
- delta = simple_semitones - (QUALITY_SEMITONES[simple_number_name.to_sym][starting_quality] % 12)
224
- delta -= 12 while delta >= 6
225
- HeadMusic::Quality.from(starting_quality, delta)
226
- end
227
-
228
- def simple_number_name
229
- NUMBER_NAMES[simple_number - 1]
230
- end
231
-
232
- def number_name
233
- NUMBER_NAMES[number - 1] || (number.to_s + NAME_SUFFIXES[number % 10])
234
- end
235
-
236
- def name
237
- if named_number?
238
- [quality_name, number_name].join(" ")
239
- elsif simple_name == "perfect octave"
240
- "#{octaves.humanize} octaves"
241
- else
242
- "#{octaves.humanize} octaves and #{quality.article} #{simple_name}"
243
- end
244
- end
245
-
246
- def shorthand
247
- step_shorthand = (number == 1) ? "U" : number
248
- [quality.shorthand, step_shorthand].join
249
- end
250
-
251
- private
252
-
253
- def named_number?
254
- number < NUMBER_NAMES.length
255
- end
256
-
257
- def quality
258
- @quality ||= HeadMusic::Quality.get(quality_name)
259
- end
260
-
261
- def octaves
262
- @octaves ||= semitones / 12
263
- end
264
-
265
- def octave_equivalent?
266
- number > 1 && ((number - 1) % 7).zero?
267
- end
268
- end
269
-
270
46
  delegate :step?, :skip?, :leap?, :large_leap?, to: :category
271
47
  delegate(
272
48
  :simple_number, :octaves, :number, :simple?, :compound?, :semitones, :simple_semitones, :steps, :simple_steps,
273
49
  to: :size
274
50
  )
275
51
  delegate(
276
- :simple_semitones, :simple_name, :quality_name, :simple_number_name, :number_name, :name, :shorthand,
52
+ :simple_name, :quality_name, :simple_number_name, :number_name, :name, :shorthand,
277
53
  to: :naming
278
54
  )
279
55
 
@@ -378,10 +154,6 @@ class HeadMusic::DiatonicInterval
378
154
  @naming ||= Naming.new(number: number, semitones: semitones)
379
155
  end
380
156
 
381
- def named_number?
382
- number < NUMBER_NAMES.length
383
- end
384
-
385
157
  def consonance_for_perfect(style = :standard_practice)
386
158
  HeadMusic::Consonance.get(dissonant_fourth?(style) ? :dissonant : :perfect) if perfect?
387
159
  end
@@ -21,7 +21,7 @@ class HeadMusic::Instrument::Staff
21
21
  end
22
22
 
23
23
  def name_key
24
- attributes["name_key"]
24
+ attributes["name_key"] || ""
25
25
  end
26
26
 
27
27
  def name
@@ -1,8 +1,8 @@
1
1
  class HeadMusic::Instrument::StaffScheme
2
- attr_reader :pitch_variant, :key, :list
2
+ attr_reader :variant, :key, :list
3
3
 
4
- def initialize(pitch_variant:, key:, list:)
5
- @pitch_variant = pitch_variant
4
+ def initialize(variant:, key:, list:)
5
+ @variant = variant
6
6
  @key = key || "default"
7
7
  @list = list
8
8
  end
@@ -1,4 +1,4 @@
1
- class HeadMusic::Instrument::PitchVariant
1
+ class HeadMusic::Instrument::Variant
2
2
  attr_reader :key, :attributes
3
3
 
4
4
  def initialize(key, attributes = {})
@@ -6,11 +6,11 @@ class HeadMusic::Instrument::PitchVariant
6
6
  @attributes = attributes
7
7
  end
8
8
 
9
- def fundamental_pitch_spelling
10
- return unless attributes["fundamental_pitch_spelling"].to_s != ""
9
+ def pitch_designation
10
+ return unless attributes["pitch_designation"].to_s != ""
11
11
 
12
- @fundamental_pitch_spelling ||=
13
- HeadMusic::Spelling.get(attributes["fundamental_pitch_spelling"])
12
+ @pitch_designation ||=
13
+ HeadMusic::Spelling.get(attributes["pitch_designation"])
14
14
  end
15
15
 
16
16
  def staff_schemes
@@ -18,7 +18,7 @@ class HeadMusic::Instrument::PitchVariant
18
18
  (attributes["staff_schemes"] || {}).map do |key, list|
19
19
  HeadMusic::Instrument::StaffScheme.new(
20
20
  key: key,
21
- pitch_variant: self,
21
+ variant: self,
22
22
  list: list
23
23
  )
24
24
  end
@@ -9,8 +9,8 @@
9
9
  # default_clefs: the default clef or system of clefs for the instrument
10
10
  # - [treble] for instruments that use the treble clef
11
11
  # - [treble, bass] for instruments that use the grand staff
12
- # pitch_variants:
13
- # a hash of default and alternative fundamental pitches.
12
+ # variants:
13
+ # a hash of default and alternative pitch designations
14
14
  # Associations:
15
15
  # family: the family of the instrument (e.g. "saxophone")
16
16
  # orchestra_section: the section of the orchestra (e.g. "strings")
@@ -32,7 +32,7 @@ class HeadMusic::Instrument
32
32
  attr_reader(
33
33
  :name_key, :alias_name_keys,
34
34
  :family_key, :orchestra_section_key,
35
- :pitch_variants, :classification_keys
35
+ :variants, :classification_keys
36
36
  )
37
37
 
38
38
  def ==(other)
@@ -75,12 +75,12 @@ class HeadMusic::Instrument
75
75
  default_clefs.any?
76
76
  end
77
77
 
78
- def default_pitch_variant
79
- pitch_variants.find(&:default?) || pitch_variants.first
78
+ def default_variant
79
+ variants.find(&:default?) || variants.first
80
80
  end
81
81
 
82
82
  def default_staff_scheme
83
- default_pitch_variant&.default_staff_scheme
83
+ default_variant&.default_staff_scheme
84
84
  end
85
85
 
86
86
  def default_staves
@@ -158,9 +158,9 @@ class HeadMusic::Instrument
158
158
  def initialize_attributes(record)
159
159
  @orchestra_section_key ||= record["orchestra_section_key"]
160
160
  @classification_keys = [@classification_keys, record["classification_keys"]].flatten.compact.uniq
161
- @pitch_variants =
162
- (record["pitch_variants"] || {}).map do |key, attributes|
163
- HeadMusic::Instrument::PitchVariant.new(key, attributes)
161
+ @variants =
162
+ (record["variants"] || {}).map do |key, attributes|
163
+ HeadMusic::Instrument::Variant.new(key, attributes)
164
164
  end
165
165
  end
166
166
 
@@ -0,0 +1,26 @@
1
+ # Key signatures are enharmonic when all pitch classes in one are respellings of the pitch classes in the other.
2
+ class HeadMusic::KeySignature::EnharmonicEquivalence
3
+ attr_reader :key_signature
4
+
5
+ def self.get(key_signature)
6
+ key_signature = HeadMusic::KeySignature.get(key_signature)
7
+ @enharmonic_equivalences ||= {}
8
+ @enharmonic_equivalences[key_signature.to_s] ||= new(key_signature)
9
+ end
10
+
11
+ def initialize(key_signature)
12
+ @key_signature = HeadMusic::KeySignature.get(key_signature)
13
+ end
14
+
15
+ def enharmonic_equivalent?(other)
16
+ other = HeadMusic::KeySignature.get(other)
17
+
18
+ key_signature.pitch_classes.map(&:to_i).sort == other.pitch_classes.map(&:to_i).sort &&
19
+ key_signature.alterations.map(&:to_s).sort != other.alterations.map(&:to_s).sort
20
+ end
21
+
22
+ alias_method :enharmonic?, :enharmonic_equivalent?
23
+ alias_method :equivalent?, :enharmonic_equivalent?
24
+
25
+ private_class_method :new
26
+ end
@@ -1,5 +1,4 @@
1
1
  # Represents a key signature.
2
- # In French, sharps and flats in the key signature are called "altérations".
3
2
  class HeadMusic::KeySignature
4
3
  attr_reader :tonic_spelling, :scale_type, :scale
5
4
 
@@ -21,7 +20,7 @@ class HeadMusic::KeySignature
21
20
 
22
21
  delegate :pitch_class, to: :tonic_spelling, prefix: :tonic
23
22
  delegate :to_s, to: :name
24
- delegate :pitches, to: :scale
23
+ delegate :pitches, :pitch_classes, to: :scale
25
24
 
26
25
  def initialize(tonic_spelling, scale_type = nil)
27
26
  @tonic_spelling = HeadMusic::Spelling.get(tonic_spelling)
@@ -67,19 +66,23 @@ class HeadMusic::KeySignature
67
66
  flats.length + double_flats.length * 2
68
67
  end
69
68
 
70
- def signs
71
- flats.any? ? flats : sharps
69
+ def num_alterations
70
+ num_sharps + num_flats
72
71
  end
73
72
 
74
- alias_method :sharps_and_flats, :signs
75
- alias_method :accidentals, :signs
73
+ def alterations
74
+ flats.any? ? (double_flats + flats) : (double_sharps + sharps)
75
+ end
76
+
77
+ alias_method :sharps_and_flats, :alterations
78
+ alias_method :accidentals, :alterations
76
79
 
77
80
  def name
78
81
  [tonic_spelling, scale_type].join(" ")
79
82
  end
80
83
 
81
84
  def ==(other)
82
- signs == self.class.get(other).signs
85
+ alterations == self.class.get(other).alterations
83
86
  end
84
87
 
85
88
  def to_s
@@ -93,38 +96,12 @@ class HeadMusic::KeySignature
93
96
  end
94
97
 
95
98
  def enharmonic_equivalent?(other)
96
- other = KeySignature.get(other)
97
- enharmonic_equivalence.equivalent?(other)
99
+ enharmonic_equivalence.enharmonic_equivalent?(other)
98
100
  end
99
101
 
100
102
  private
101
103
 
102
104
  def enharmonic_equivalence
103
- @enharmonic_equivalence ||= EnharmonicEquivalence.get(self)
104
- end
105
-
106
- # Key signatures are enharmonic when all pitch classes in one are respellings of the pitch classes in the other.
107
- class EnharmonicEquivalence
108
- def self.get(key_signature)
109
- key_signature = HeadMusic::KeySignature.get(key_signature)
110
- @enharmonic_equivalences ||= {}
111
- @enharmonic_equivalences[key_signature.to_s] ||= new(key_signature)
112
- end
113
-
114
- attr_reader :key_signature
115
-
116
- def initialize(key_signature)
117
- @key_signature = HeadMusic::KeySignature.get(key_signature)
118
- end
119
-
120
- def enharmonic_equivalent?(other)
121
- other = HeadMusic::KeySignature.get(other)
122
- (key_signature.signs | other.signs).map(&:to_s).uniq.length == 12
123
- end
124
-
125
- alias_method :enharmonic?, :enharmonic_equivalent?
126
- alias_method :equivalent?, :enharmonic_equivalent?
127
-
128
- private_class_method :new
105
+ @enharmonic_equivalence ||= HeadMusic::KeySignature::EnharmonicEquivalence.get(self)
129
106
  end
130
107
  end