head_music 6.0.1 → 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.
@@ -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
@@ -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
 
@@ -15,16 +15,12 @@ class HeadMusic::KeySignature::EnharmonicEquivalence
15
15
  def enharmonic_equivalent?(other)
16
16
  other = HeadMusic::KeySignature.get(other)
17
17
 
18
- p key_signature.pitch_classes.map(&:to_i).sort
19
- p other.pitch_classes.map(&:to_i).sort
20
- p key_signature.alterations.map(&:to_s).sort
21
- p other.alterations.map(&:to_s).sort
22
18
  key_signature.pitch_classes.map(&:to_i).sort == other.pitch_classes.map(&:to_i).sort &&
23
19
  key_signature.alterations.map(&:to_s).sort != other.alterations.map(&:to_s).sort
24
20
  end
25
21
 
26
- # alias_method :enharmonic?, :enharmonic_equivalent?
27
- # alias_method :equivalent?, :enharmonic_equivalent?
22
+ alias_method :enharmonic?, :enharmonic_equivalent?
23
+ alias_method :equivalent?, :enharmonic_equivalent?
28
24
 
29
25
  private_class_method :new
30
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
 
@@ -100,6 +100,7 @@ en:
100
100
  wind_instrument: wind instrument
101
101
  wood: wood
102
102
  woodwind: woodwind
103
+ instrument_with_pitch_designation: "%{instrument} in %{pitch_designation}"
103
104
  instrument_voices:
104
105
  piccolo: piccolo
105
106
  soprano: soprano
@@ -126,27 +127,17 @@ en:
126
127
  bass_oboe: bass oboe
127
128
  bass_trombone: bass trombone
128
129
  bass_trumpet: bass trumpet
129
- bass_trumpet_in_b_flat: bass trumpet in B♭
130
- bass_trumpet_in_c: bass trumpet in C
131
- bass_trumpet_in_e_flat: bass trumpet in E♭
132
130
  bass_tuba: bass tuba
133
- bass_tuba_in_e_flat: bass tuba in E♭
134
- bass_tuba_in_f: bass tuba in F
135
131
  bass_voice: bass
136
132
  basset_horn: basset horn
137
133
  bassoon: bassoon
138
134
  bugle: bugle
139
- bugle_in_b_flat: bugle in B♭
140
135
  castanets: castanets
141
136
  celesta: celesta
142
137
  cello: cello
143
138
  chimes: chimes
144
139
  cimbalom: cimbalom
145
140
  clarinet: clarinet
146
- clarinet_in_a: clarinet in A
147
- clarinet_in_c: clarinet in C
148
- clarinet_in_d: clarinet in D
149
- clarinet_in_e_flat: clarinet in E♭
150
141
  clash_cymbals: clash cymbals
151
142
  clavichord: clavichord
152
143
  contrabass_clarinet: contrabass clarinet
@@ -202,11 +193,7 @@ en:
202
193
  triangle: triangle
203
194
  trombone: trombone
204
195
  trumpet: trumpet
205
- trumpet_in_c: trumpet in C
206
- trumpet_in_d: trumpet in D
207
- trumpet_in_e_flat: trumpet in E♭
208
196
  tuba: tuba
209
- tuba_in_c: tuba in C
210
197
  tubular_bells: tubular bells
211
198
  vibraphone: vibraphone
212
199
  viola: viola
@@ -103,8 +103,4 @@ module HeadMusic::Named
103
103
  def localized_name_in_default_locale
104
104
  localized_names.detect { |name| name.locale_code == Locale::DEFAULT_CODE }
105
105
  end
106
-
107
- def hash_key
108
- HeadMusic::Utilities::HashKey.for(name)
109
- end
110
106
  end
@@ -1,4 +1,4 @@
1
- # A scale degree is a number indicating the ordinality of the spelling in the key signature.
1
+ # A scale degree is a number indicating the ordinality of the spelling in the key.
2
2
  # TODO: Rewrite to accept a tonal_center and a scale type.
3
3
  class HeadMusic::ScaleDegree
4
4
  include Comparable
@@ -20,20 +20,25 @@ class HeadMusic::ScaleDegree
20
20
  end
21
21
 
22
22
  def alteration
23
- alteration_semitones = spelling.alteration&.semitones || 0
23
+ spelling_alteration_semitones = spelling.alteration&.semitones || 0
24
24
  usual_sign_semitones = scale_degree_usual_spelling.alteration&.semitones || 0
25
- delta = alteration_semitones - usual_sign_semitones
25
+ delta = spelling_alteration_semitones - usual_sign_semitones
26
26
  HeadMusic::Alteration.by(:semitones, delta) if delta != 0
27
27
  end
28
28
 
29
+ def alteration_semitones
30
+ alteration&.semitones || 0
31
+ end
32
+
29
33
  def to_s
30
34
  "#{alteration}#{degree}"
31
35
  end
32
36
 
33
37
  def <=>(other)
34
38
  if other.is_a?(HeadMusic::ScaleDegree)
35
- [degree, alteration.semitones] <=> [other.degree, other.alteration.semitones]
39
+ [degree, alteration_semitones] <=> [other.degree, other.alteration_semitones]
36
40
  else
41
+ # TODO: Improve this
37
42
  to_s <=> other.to_s
38
43
  end
39
44
  end