head_music 6.0.0 → 7.0.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 +4 -4
- data/.rubocop.yml +4 -1
- data/lib/head_music/consonance.rb +1 -1
- data/lib/head_music/content/composition.rb +3 -2
- data/lib/head_music/content/position.rb +0 -4
- data/lib/head_music/content/voice.rb +7 -1
- data/lib/head_music/data/instruments.yml +118 -127
- data/lib/head_music/diatonic_interval/category.rb +24 -0
- data/lib/head_music/diatonic_interval/naming.rb +73 -0
- data/lib/head_music/diatonic_interval/parser.rb +47 -0
- data/lib/head_music/diatonic_interval/semitones.rb +36 -0
- data/lib/head_music/diatonic_interval/size.rb +50 -0
- data/lib/head_music/diatonic_interval.rb +1 -229
- data/lib/head_music/instrument/staff.rb +1 -1
- data/lib/head_music/instrument/staff_scheme.rb +3 -3
- data/lib/head_music/instrument/{pitch_variant.rb → variant.rb} +6 -6
- data/lib/head_music/instrument.rb +9 -9
- data/lib/head_music/key_signature/enharmonic_equivalence.rb +26 -0
- data/lib/head_music/key_signature.rb +12 -35
- data/lib/head_music/locales/en.yml +1 -14
- data/lib/head_music/motion.rb +3 -1
- data/lib/head_music/named.rb +0 -4
- data/lib/head_music/pitch_class_set.rb +2 -5
- data/lib/head_music/scale.rb +4 -0
- data/lib/head_music/scale_degree.rb +9 -4
- data/lib/head_music/style/guidelines/notes_same_length.rb +0 -4
- data/lib/head_music/style/mark.rb +1 -4
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +7 -1
- metadata +9 -3
@@ -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
|
-
:
|
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
|
@@ -1,8 +1,8 @@
|
|
1
1
|
class HeadMusic::Instrument::StaffScheme
|
2
|
-
attr_reader :
|
2
|
+
attr_reader :variant, :key, :list
|
3
3
|
|
4
|
-
def initialize(
|
5
|
-
@
|
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::
|
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
|
10
|
-
return unless attributes["
|
9
|
+
def pitch_designation
|
10
|
+
return unless attributes["pitch_designation"].to_s != ""
|
11
11
|
|
12
|
-
@
|
13
|
-
HeadMusic::Spelling.get(attributes["
|
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
|
-
|
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
|
-
#
|
13
|
-
# a hash of default and alternative
|
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
|
-
:
|
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
|
79
|
-
|
78
|
+
def default_variant
|
79
|
+
variants.find(&:default?) || variants.first
|
80
80
|
end
|
81
81
|
|
82
82
|
def default_staff_scheme
|
83
|
-
|
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
|
-
@
|
162
|
-
(record["
|
163
|
-
HeadMusic::Instrument::
|
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
|
71
|
-
|
69
|
+
def num_alterations
|
70
|
+
num_sharps + num_flats
|
72
71
|
end
|
73
72
|
|
74
|
-
|
75
|
-
|
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
|
-
|
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
|
-
|
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
|