head_music 8.2.0 → 8.3.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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +1 -1
  3. data/.github/workflows/release.yml +1 -1
  4. data/CHANGELOG.md +14 -0
  5. data/CLAUDE.md +134 -0
  6. data/Gemfile.lock +25 -25
  7. data/Rakefile +2 -2
  8. data/TODO.md +41 -150
  9. data/bin/check_instrument_consistency.rb +86 -0
  10. data/check_instrument_consistency.rb +0 -0
  11. data/head_music.gemspec +1 -1
  12. data/lib/head_music/analysis/diatonic_interval/naming.rb +1 -1
  13. data/lib/head_music/analysis/diatonic_interval.rb +28 -7
  14. data/lib/head_music/analysis/sonority.rb +1 -1
  15. data/lib/head_music/content/staff.rb +13 -3
  16. data/lib/head_music/content/voice.rb +8 -0
  17. data/lib/head_music/instruments/instrument.rb +1 -3
  18. data/lib/head_music/instruments/instrument_families.yml +37 -2
  19. data/lib/head_music/instruments/instruments.yml +352 -367
  20. data/lib/head_music/instruments/variant.rb +1 -1
  21. data/lib/head_music/locales/de.yml +20 -0
  22. data/lib/head_music/locales/en.yml +92 -0
  23. data/lib/head_music/locales/es.yml +19 -0
  24. data/lib/head_music/locales/fr.yml +19 -0
  25. data/lib/head_music/locales/it.yml +20 -0
  26. data/lib/head_music/locales/ru.yml +21 -2
  27. data/lib/head_music/rudiment/rhythmic_unit.rb +93 -26
  28. data/lib/head_music/rudiment/scale_degree.rb +8 -3
  29. data/lib/head_music/rudiment/tuning/just_intonation.rb +85 -0
  30. data/lib/head_music/rudiment/tuning/meantone.rb +87 -0
  31. data/lib/head_music/rudiment/tuning/pythagorean.rb +91 -0
  32. data/lib/head_music/rudiment/tuning.rb +17 -3
  33. data/lib/head_music/style/annotation.rb +6 -6
  34. data/lib/head_music/style/guidelines/consonant_climax.rb +2 -2
  35. data/lib/head_music/style/guidelines/notes_same_length.rb +16 -16
  36. data/lib/head_music/version.rb +1 -1
  37. data/lib/head_music.rb +3 -0
  38. data/user_stories/backlog/band-score-order.md +38 -0
  39. data/user_stories/backlog/chamber-ensemble-score-order.md +33 -0
  40. data/user_stories/backlog/consonance-dissonance-classification.md +57 -0
  41. data/user_stories/backlog/dyad-analysis.md +65 -0
  42. data/user_stories/backlog/orchestral-score-order.md +43 -0
  43. data/user_stories/backlog/pitch-class-set-analysis.md +39 -0
  44. data/user_stories/backlog/pitch-set-classification.md +62 -0
  45. data/user_stories/backlog/sonority-identification.md +47 -0
  46. metadata +18 -4
@@ -4,8 +4,8 @@ module HeadMusic::Analysis; end
4
4
  # A diatonic interval is the distance between two spelled pitches.
5
5
  class HeadMusic::Analysis::DiatonicInterval
6
6
  include Comparable
7
+ include HeadMusic::Named
7
8
 
8
- # TODO: include Named module
9
9
  NUMBER_NAMES = %w[
10
10
  unison second third fourth fifth sixth seventh octave
11
11
  ninth tenth eleventh twelfth thirteenth fourteenth fifteenth
@@ -43,7 +43,6 @@ class HeadMusic::Analysis::DiatonicInterval
43
43
 
44
44
  attr_reader :lower_pitch, :higher_pitch
45
45
 
46
- delegate :to_s, to: :name
47
46
  delegate :perfect?, :major?, :minor?, :diminished?, :augmented?, :doubly_diminished?, :doubly_augmented?, to: :quality
48
47
 
49
48
  delegate :step?, :skip?, :leap?, :large_leap?, to: :category
@@ -52,18 +51,40 @@ class HeadMusic::Analysis::DiatonicInterval
52
51
  to: :size
53
52
  )
54
53
  delegate(
55
- :simple_name, :quality_name, :simple_number_name, :number_name, :name, :shorthand,
54
+ :simple_name, :quality_name, :simple_number_name, :number_name, :shorthand,
56
55
  to: :naming
57
56
  )
58
57
 
59
58
  alias_method :to_i, :semitones
60
59
 
60
+ # Override Named module methods to use computed name from naming
61
+ def name(locale_code: nil)
62
+ if locale_code
63
+ # Try to get translation from locale files
64
+ name_key = naming.name.downcase.gsub(" ", "_").to_sym
65
+ translation = I18n.translate(name_key, scope: "head_music.diatonic_intervals", locale: locale_code, default: nil)
66
+ translation || naming.name
67
+ else
68
+ naming.name
69
+ end
70
+ end
71
+
72
+ def to_s
73
+ name
74
+ end
75
+
61
76
  # Accepts a name and returns the interval with middle c on the bottom
62
77
  def self.get(identifier)
63
- name = Parser.new(identifier)
64
- semitones = Semitones.new(name.degree_name.to_sym, name.quality_name).count
65
- higher_pitch = HeadMusic::Rudiment::Pitch.from_number_and_letter(HeadMusic::Rudiment::Pitch.middle_c + semitones, name.higher_letter)
66
- new(HeadMusic::Rudiment::Pitch.middle_c, higher_pitch)
78
+ if identifier.is_a?(String) || identifier.is_a?(Symbol)
79
+ name = Parser.new(identifier)
80
+ semitones = Semitones.new(name.degree_name.to_sym, name.quality_name).count
81
+ higher_pitch = HeadMusic::Rudiment::Pitch.from_number_and_letter(HeadMusic::Rudiment::Pitch.middle_c + semitones, name.higher_letter)
82
+ interval = new(HeadMusic::Rudiment::Pitch.middle_c, higher_pitch)
83
+ interval.ensure_localized_name(name: identifier.to_s)
84
+ interval
85
+ else
86
+ identifier
87
+ end
67
88
  end
68
89
 
69
90
  def initialize(first_pitch, second_pitch)
@@ -110,7 +110,7 @@ class HeadMusic::Analysis::Sonority
110
110
  alias_method :quintal?, :quartal?
111
111
 
112
112
  def diatonic_intervals_above_bass_pitch
113
- return nil unless identifier
113
+ return [] unless identifier
114
114
 
115
115
  @diatonic_intervals_above_bass_pitch ||=
116
116
  SONORITIES[identifier].map { |shorthand| HeadMusic::Analysis::DiatonicInterval.get(shorthand) }
@@ -8,12 +8,22 @@ class HeadMusic::Content::Staff
8
8
  attr_reader :default_clef, :line_count, :instrument
9
9
 
10
10
  def initialize(default_clef_key, instrument: nil, line_count: nil)
11
- @default_clef = HeadMusic::Rudiment::Clef.get(default_clef_key)
12
- @line_count = line_count || DEFAULT_LINE_COUNT
13
11
  @instrument = HeadMusic::Instruments::Instrument.get(instrument) if instrument
12
+ begin
13
+ @default_clef = HeadMusic::Rudiment::Clef.get(default_clef_key)
14
+ rescue KeyError, NoMethodError
15
+ puts("Warning: Clef '#{default_clef_key}' not found.")
16
+ if @instrument
17
+ puts("Using instrument clef.")
18
+ @default_clef = @instrument.default_staves.first.clef
19
+ else
20
+ @default_clef = HeadMusic::Rudiment::Clef.get(:treble_clef)
21
+ end
22
+ end
23
+ @line_count = line_count || DEFAULT_LINE_COUNT
14
24
  end
15
25
 
16
26
  def clef
17
- default_clef || instrument&.default_staves&.first&.clef
27
+ default_clef
18
28
  end
19
29
  end
@@ -110,6 +110,14 @@ class HeadMusic::Content::Voice
110
110
  placements.last.position.bar_number
111
111
  end
112
112
 
113
+ def last_placement
114
+ placements.last
115
+ end
116
+
117
+ def next_position
118
+ last_placement ? last_placement.next_position : HeadMusic::Content::Position.new(composition, 1, 1, 0)
119
+ end
120
+
113
121
  def to_s
114
122
  return pitches_string if role.to_s.strip == ""
115
123
 
@@ -82,9 +82,7 @@ class HeadMusic::Instruments::Instrument
82
82
  variants.find(&:default?) || variants.first
83
83
  end
84
84
 
85
- def default_staff_scheme
86
- default_variant&.default_staff_scheme
87
- end
85
+ delegate :default_staff_scheme, to: :default_variant
88
86
 
89
87
  def default_staves
90
88
  default_staff_scheme&.staves || []
@@ -14,6 +14,12 @@ bagpipe:
14
14
  - wind
15
15
  - woodwind
16
16
  orchestra_section_key: woodwind
17
+ bass_drum:
18
+ classification_keys:
19
+ - percussion
20
+ - membranophone
21
+ - unpitched
22
+ orchestra_section_key: percussion
17
23
  bassoon:
18
24
  classification_keys:
19
25
  - aerophone
@@ -22,6 +28,15 @@ bassoon:
22
28
  - wind
23
29
  - woodwind
24
30
  orchestra_section_key: woodwind
31
+ celesta:
32
+ classification_keys:
33
+ - idiophone
34
+ - keyboard
35
+ - percussion
36
+ - struck
37
+ - metal
38
+ - pitched
39
+ orchestra_section_key: keyboard
25
40
  clarinet:
26
41
  classification_keys:
27
42
  - aerophone
@@ -34,13 +49,13 @@ clavichord:
34
49
  classification_keys:
35
50
  - chordophone
36
51
  - keyboard
37
- - percussion
38
52
  - string
39
53
  orchestra_section_key: keyboard
40
54
  cornet:
41
55
  classification_keys:
42
56
  - aerophone
43
57
  - brass
58
+ - valve
44
59
  - wind
45
60
  orchestra_section_key: brass
46
61
  cymbal:
@@ -57,6 +72,7 @@ double_bass:
57
72
  classification_keys:
58
73
  - bowed
59
74
  - chordophone
75
+ - unfretted
60
76
  - string
61
77
  orchestra_section_key: string
62
78
  flute:
@@ -86,6 +102,7 @@ guitar:
86
102
  classification_keys:
87
103
  - chordophone
88
104
  - plucked
105
+ - fretted
89
106
  - string
90
107
  orchestra_section_key: string
91
108
  harmonica:
@@ -105,13 +122,13 @@ harpsichord:
105
122
  classification_keys:
106
123
  - chordophone
107
124
  - keyboard
108
- - percussion
109
125
  - string
110
126
  orchestra_section_key: keyboard
111
127
  horn:
112
128
  classification_keys:
113
129
  - aerophone
114
130
  - brass
131
+ - valve
115
132
  - wind
116
133
  orchestra_section_key: brass
117
134
  kettledrum:
@@ -124,12 +141,14 @@ lute:
124
141
  classification_keys:
125
142
  - chordophone
126
143
  - plucked
144
+ - fretted
127
145
  - string
128
146
  orchestra_section_key: string
129
147
  mandolin:
130
148
  classification_keys:
131
149
  - chordophone
132
150
  - plucked
151
+ - fretted
133
152
  - string
134
153
  orchestra_section_key: string
135
154
  marimba:
@@ -141,6 +160,7 @@ marimba:
141
160
  - wood
142
161
  - mallet
143
162
  - pitched
163
+ - resonator
144
164
  orchestra_section_key: percussion
145
165
  oboe:
146
166
  classification_keys:
@@ -172,6 +192,7 @@ saxhorn:
172
192
  classification_keys:
173
193
  - aerophone
174
194
  - brass
195
+ - valve
175
196
  - wind
176
197
  orchestra_section_key: brass
177
198
  saxophone:
@@ -186,6 +207,7 @@ sitar:
186
207
  classification_keys:
187
208
  - chordophone
188
209
  - plucked
210
+ - fretted
189
211
  - string
190
212
  orchestra_section_key: string
191
213
  snare_drum:
@@ -213,6 +235,14 @@ tabla:
213
235
  - membranophone
214
236
  - pitched
215
237
  orchestra_section_key: percussion
238
+ tambourine:
239
+ classification_keys:
240
+ - percussion
241
+ - membranophone
242
+ - shaken
243
+ - idiophone
244
+ - unpitched
245
+ orchestra_section_key: percussion
216
246
  theremin:
217
247
  classification_keys:
218
248
  - electronic
@@ -226,18 +256,21 @@ trumpet:
226
256
  classification_keys:
227
257
  - aerophone
228
258
  - brass
259
+ - valve
229
260
  - wind
230
261
  orchestra_section_key: brass
231
262
  tuba:
232
263
  classification_keys:
233
264
  - aerophone
234
265
  - brass
266
+ - valve
235
267
  - wind
236
268
  orchestra_section_key: brass
237
269
  ukulele:
238
270
  classification_keys:
239
271
  - chordophone
240
272
  - plucked
273
+ - fretted
241
274
  - string
242
275
  orchestra_section_key: string
243
276
  vibraphone:
@@ -249,11 +282,13 @@ vibraphone:
249
282
  - mallet
250
283
  - metal
251
284
  - pitched
285
+ - resonator
252
286
  orchestra_section_key: percussion
253
287
  violin:
254
288
  classification_keys:
255
289
  - bowed
256
290
  - chordophone
291
+ - unfretted
257
292
  - string
258
293
  orchestra_section_key: string
259
294
  voice: