head_music 8.0.2 → 8.1.1

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
  SHA256:
3
- metadata.gz: 59fd761fa9c441ada6674ebabb1e83c81991f21b20d2b29812160cf7d1bfd1f6
4
- data.tar.gz: 6d7edeb8c3c8fbcd2abf3069860c7a31bc2c40cc95e38ef718d94ce2821a655d
3
+ metadata.gz: 1d1ca0a4ee8274a955259afdd1c915e09fba454522157a1ef687f8760ec064ed
4
+ data.tar.gz: 902a3b6f27f50b2a8f354adb22cbcc460798f1916f760f475e9f6c1d6583cc93
5
5
  SHA512:
6
- metadata.gz: 605e82fb95ba7e5c91ca2f861bafbf9dd54e94ab02404382a2ae3293a943a85455dd26c40a46d6725fd26c0caeb89c898220d9b572c5a0b22c0bac0a172e31ce
7
- data.tar.gz: 9a920c87fad466335f8b02cff76cd1a202c3a20d9d6e0bc53e91a93648406587e652f0dc47296063d40a26c2fa385a7250f3b6e955c9e99820c513ce97788540
6
+ metadata.gz: e80f4d2337cabdf0cedd69bc0cb9ad54e73bd0f1a9774ccbfc2bcbe70b28d90899ff55debe9917dc5f62776e90ee7d09fd28530ea10fa28892ffc46e0d430c56
7
+ data.tar.gz: 3d278d05d015f73e25633e3de2ea47fc90c8d8c8377bd4dff6ba13772039e8cce6824288a98e05e2cf31080dfac4f3b8010d84895dc62f24080699574958f8df
@@ -66,10 +66,14 @@ class HeadMusic::Analysis::DiatonicInterval
66
66
  new(HeadMusic::Rudiment::Pitch.middle_c, higher_pitch)
67
67
  end
68
68
 
69
- def initialize(pitch1, pitch2)
70
- pitch1 = HeadMusic::Rudiment::Pitch.get(pitch1)
71
- pitch2 = HeadMusic::Rudiment::Pitch.get(pitch2)
72
- @lower_pitch, @higher_pitch = [pitch1, pitch2].sort
69
+ def initialize(first_pitch, second_pitch)
70
+ first_pitch = HeadMusic::Rudiment::Pitch.get(first_pitch)
71
+ second_pitch = HeadMusic::Rudiment::Pitch.get(second_pitch)
72
+ @lower_pitch, @higher_pitch = [first_pitch, second_pitch].sort
73
+ end
74
+
75
+ def spans?(pitch)
76
+ pitch >= lower_pitch && pitch <= higher_pitch
73
77
  end
74
78
 
75
79
  def quality
@@ -1,43 +1,22 @@
1
1
  # A module for musical analysis
2
2
  module HeadMusic::Analysis; end
3
3
 
4
- # A melodic interval is the distance between one note and the next.
4
+ # A melodic interval is the distance between two sequential pitches.
5
5
  class HeadMusic::Analysis::MelodicInterval
6
- attr_reader :first_note, :second_note
6
+ attr_reader :first_pitch, :second_pitch
7
7
 
8
- def initialize(note1, note2)
9
- @first_note = note1
10
- @second_note = note2
8
+ def initialize(first, second)
9
+ @first_pitch, @second_pitch = extract_pitches(first, second)
11
10
  end
12
11
 
13
12
  def diatonic_interval
14
13
  @diatonic_interval ||= HeadMusic::Analysis::DiatonicInterval.new(first_pitch, second_pitch)
15
14
  end
16
15
 
17
- def position_start
18
- first_note.position
19
- end
20
-
21
- def position_end
22
- second_note.next_position
23
- end
24
-
25
- def notes
26
- [first_note, second_note]
27
- end
28
-
29
16
  def pitches
30
17
  [first_pitch, second_pitch]
31
18
  end
32
19
 
33
- def first_pitch
34
- @first_pitch ||= first_note.pitch
35
- end
36
-
37
- def second_pitch
38
- @second_pitch ||= second_note.pitch
39
- end
40
-
41
20
  def to_s
42
21
  [direction, diatonic_interval].join(" ")
43
22
  end
@@ -58,10 +37,6 @@ class HeadMusic::Analysis::MelodicInterval
58
37
  !moving?
59
38
  end
60
39
 
61
- def spans?(pitch)
62
- pitch.between?(low_pitch, high_pitch)
63
- end
64
-
65
40
  def high_pitch
66
41
  pitches.max
67
42
  end
@@ -91,10 +66,18 @@ class HeadMusic::Analysis::MelodicInterval
91
66
  end
92
67
 
93
68
  def method_missing(method_name, *args, &block)
94
- respond_to_missing?(method_name) ? diatonic_interval.send(method_name, *args, &block) : super
69
+ diatonic_interval.respond_to?(method_name) ? diatonic_interval.send(method_name, *args, &block) : super
95
70
  end
96
71
 
97
- def respond_to_missing?(method_name, *_args)
98
- diatonic_interval.respond_to?(method_name)
72
+ def respond_to_missing?(method_name, include_private = false)
73
+ diatonic_interval.respond_to?(method_name, include_private) || super
74
+ end
75
+
76
+ private
77
+
78
+ def extract_pitches(first, second)
79
+ first_pitch = first.respond_to?(:pitch) ? first.pitch : first
80
+ second_pitch = second.respond_to?(:pitch) ? second.pitch : second
81
+ [first_pitch, second_pitch]
99
82
  end
100
83
  end
@@ -23,7 +23,7 @@ class HeadMusic::Content::Voice
23
23
  end
24
24
 
25
25
  def notes
26
- @placements.select(&:note?)
26
+ @placements.select(&:note?).sort_by(&:position)
27
27
  end
28
28
 
29
29
  def notes_not_in_key
@@ -59,17 +59,23 @@ class HeadMusic::Content::Voice
59
59
  HeadMusic::Analysis::DiatonicInterval.new(lowest_pitch, highest_pitch)
60
60
  end
61
61
 
62
+ def melodic_note_pairs
63
+ @melodic_note_pairs ||= notes.each_cons(2).map do |note1, note2|
64
+ HeadMusic::Content::Voice::MelodicNotePair.new(note1, note2)
65
+ end
66
+ end
67
+
62
68
  def melodic_intervals
63
69
  @melodic_intervals ||=
64
- notes.each_cons(2).map { |note_pair| HeadMusic::Analysis::MelodicInterval.new(*note_pair) }
70
+ melodic_note_pairs.map { |note_pair| HeadMusic::Analysis::MelodicInterval.new(*note_pair.notes) }
65
71
  end
66
72
 
67
73
  def leaps
68
- melodic_intervals.select(&:leap?)
74
+ melodic_note_pairs.select(&:leap?)
69
75
  end
70
76
 
71
77
  def large_leaps
72
- melodic_intervals.select(&:large_leap?)
78
+ melodic_note_pairs.select(&:large_leap?)
73
79
  end
74
80
 
75
81
  def cantus_firmus?
@@ -120,4 +126,43 @@ class HeadMusic::Content::Voice
120
126
  def pitches_string
121
127
  pitches.first(10).map(&:to_s).join(" ")
122
128
  end
129
+
130
+ class MelodicNotePair
131
+ attr_reader :first_note, :second_note
132
+
133
+ delegate(
134
+ :octave?, :unison?,
135
+ :perfect?,
136
+ :step?, :leap?, :large_leap?,
137
+ :ascending?, :descending?, :repetition?,
138
+ :spans?,
139
+ to: :melodic_interval
140
+ )
141
+
142
+ def initialize(first_note, second_note)
143
+ @first_note = first_note
144
+ @second_note = second_note
145
+ end
146
+
147
+ def notes
148
+ @notes ||= [first_note, second_note]
149
+ end
150
+
151
+ def pitches
152
+ @pitches ||= notes.map(&:pitch)
153
+ end
154
+
155
+ def melodic_interval
156
+ @melodic_interval ||= HeadMusic::Analysis::MelodicInterval.new(*notes)
157
+ end
158
+
159
+ def spells_consonant_triad_with?(other_note_pair)
160
+ return false if step? || other_note_pair.step?
161
+
162
+ combined_pitches = (pitches + other_note_pair.pitches).uniq
163
+ return false if combined_pitches.length < 3
164
+
165
+ HeadMusic::Analysis::PitchSet.new(combined_pitches).consonant_triad?
166
+ end
167
+ end
123
168
  end
@@ -291,6 +291,7 @@ de:
291
291
  scale: Tonleiter
292
292
  scale_degree: Tonleiterstufe
293
293
  scale_type: Tonleiterart
294
+ solfege: Solfège
294
295
  solmization: Solmisation
295
296
  sonority: Klangfarbe
296
297
  spelling: Schreibweise
@@ -286,6 +286,7 @@ es:
286
286
  scale: escala
287
287
  scale_degree: grado de la escala
288
288
  scale_type: tipo de escala
289
+ solfege: solfeo
289
290
  solmization: solmización
290
291
  sonority: sonoridad
291
292
  spelling: notación
@@ -292,6 +292,7 @@ fr:
292
292
  scale: gamme
293
293
  scale_degree: degré de la gamme
294
294
  scale_type: type de gamme
295
+ solfege: solfège
295
296
  solmization: solmisation
296
297
  sonority: sonorité
297
298
  spelling: notation
@@ -284,6 +284,7 @@ it:
284
284
  scale: scala
285
285
  scale_degree: grado della scala
286
286
  scale_type: tipo di scala
287
+ solfege: solfeggio
287
288
  solmization: solmizzazione
288
289
  sonority: sonorità
289
290
  spelling: notazione
@@ -232,6 +232,7 @@ ru:
232
232
  scale: гамма
233
233
  scale_degree: ступень гаммы
234
234
  scale_type: тип гаммы
235
+ solfege: сольфеджио
235
236
  solmization: сольмизация
236
237
  sonority: звучность
237
238
  spelling: обозначение
@@ -1,8 +1,7 @@
1
1
  # A module for music rudiments
2
2
  module HeadMusic::Rudiment; end
3
3
 
4
- # A scale degree is a number indicating the ordinality of the spelling in the key signature.
5
- # TODO: Rewrite to accept a tonal_center and a scale type.
4
+ # A solmization is the rendering of scale degrees as syllables.
6
5
  class HeadMusic::Rudiment::Solmization
7
6
  include HeadMusic::Named
8
7
 
@@ -34,20 +33,27 @@ class HeadMusic::Rudiment::Solmization
34
33
  def record_for_name(name)
35
34
  key = HeadMusic::Utilities::HashKey.for(name)
36
35
  RECORDS.detect do |record|
37
- name_strings = record[:localized_names].map { |localized_name| localized_name[:name] }
36
+ name_strings = [record[:name]] + (record[:aliases] || []) + translation_aliases
38
37
  name_keys = name_strings.map { |name_string| HeadMusic::Utilities::HashKey.for(name_string) }
39
38
  name_keys.include?(key)
40
39
  end
41
40
  end
42
41
 
43
42
  def initialize_data_from_record(record)
43
+ self.name = record[:name]
44
44
  @syllables = record[:syllables]
45
- initialize_localized_names(record[:localized_names])
46
45
  end
47
46
 
48
- def initialize_localized_names(list)
49
- @localized_names = (list || []).map do |name_attributes|
50
- HeadMusic::Named::LocalizedName.new(**name_attributes.slice(:name, :locale_code, :abbreviation))
47
+ def translation_aliases
48
+ @translation_aliases ||= load_translation_aliases
49
+ end
50
+
51
+ def load_translation_aliases
52
+ aliases = []
53
+ I18n.config.available_locales.each do |locale|
54
+ translation = I18n.translate("head_music.rudiments.solfege", locale: locale, default: nil)
55
+ aliases << translation if translation && translation != 'solfege'
51
56
  end
57
+ aliases.compact.uniq
52
58
  end
53
59
  end
@@ -1,5 +1,6 @@
1
1
  ---
2
- - :syllables:
2
+ - :name: solfège
3
+ :syllables:
3
4
  - do
4
5
  - re
5
6
  - mi
@@ -7,14 +8,7 @@
7
8
  - sol
8
9
  - la
9
10
  - ti
10
- :localized_names:
11
- - :name: solfège
12
- - :name: solfège
13
- :locale_code: en
14
- - :name: solfège
15
- :locale_code: fr
16
- - :name: solfeggio
17
- :locale_code: it
18
- - :name: sol-fa
19
- - :name: solfa
20
- - :name: solfeo
11
+ :aliases:
12
+ - solfege
13
+ - sol-fa
14
+ - solfa
@@ -10,6 +10,7 @@ class HeadMusic::Style::Annotation
10
10
  :lowest_pitch,
11
11
  :highest_notes,
12
12
  :lowest_notes,
13
+ :melodic_note_pairs,
13
14
  :melodic_intervals,
14
15
  :notes,
15
16
  :notes_not_in_key,
@@ -6,8 +6,8 @@ class HeadMusic::Style::Guidelines::AlwaysMove < HeadMusic::Style::Annotation
6
6
  MESSAGE = "Always move to a different note."
7
7
 
8
8
  def marks
9
- melodic_intervals
10
- .select { |interval| interval.perfect? && interval.unison? }
11
- .map { |interval| HeadMusic::Style::Mark.for_all(interval.notes) }
9
+ melodic_note_pairs
10
+ .select { |pair| pair.perfect? && pair.unison? }
11
+ .map { |pair| HeadMusic::Style::Mark.for_all(pair.notes) }
12
12
  end
13
13
  end
@@ -6,7 +6,7 @@ class HeadMusic::Style::Guidelines::EndOnTonic < HeadMusic::Style::Annotation
6
6
  MESSAGE = "End on the first scale degree."
7
7
 
8
8
  def marks
9
- HeadMusic::Style::Mark.for(notes.last) if notes.any? && !ends_on_tonic?
9
+ HeadMusic::Style::Mark.for(last_note) if notes.any? && !ends_on_tonic?
10
10
  end
11
11
 
12
12
  private
@@ -16,6 +16,6 @@ class HeadMusic::Style::Guidelines::LimitOctaveLeaps < HeadMusic::Style::Annotat
16
16
  private
17
17
 
18
18
  def octave_leaps
19
- melodic_intervals.select(&:octave?)
19
+ melodic_note_pairs.select(&:octave?)
20
20
  end
21
21
  end
@@ -16,14 +16,14 @@ class HeadMusic::Style::Guidelines::MostlyConjunct < HeadMusic::Style::Annotatio
16
16
  private
17
17
 
18
18
  def marks_for_skips_and_leaps
19
- melodic_intervals
19
+ melodic_note_pairs
20
20
  .reject(&:step?)
21
- .map { |interval| HeadMusic::Style::Mark.for_all(interval.notes, fitness: HeadMusic::SMALL_PENALTY_FACTOR) }
21
+ .map { |note_pair| HeadMusic::Style::Mark.for_all(note_pair.notes, fitness: HeadMusic::SMALL_PENALTY_FACTOR) }
22
22
  end
23
23
 
24
24
  def conjunct_ratio
25
- return 1 if melodic_intervals.empty?
25
+ return 1 if melodic_note_pairs.empty?
26
26
 
27
- melodic_intervals.count(&:step?).to_f / melodic_intervals.length
27
+ melodic_note_pairs.count(&:step?).to_f / melodic_note_pairs.length
28
28
  end
29
29
  end
@@ -6,32 +6,48 @@ class HeadMusic::Style::Guidelines::PrepareOctaveLeaps < HeadMusic::Style::Annot
6
6
  MESSAGE = "Enter and exit an octave leap from within."
7
7
 
8
8
  def marks
9
- (external_entries + external_exits + octave_ending).map do |trouble_spot|
9
+ external_entries_marks + external_exits_marks + octave_ending_marks
10
+ end
11
+
12
+ private
13
+
14
+ def external_entries_marks
15
+ external_entries.map do |trouble_spot|
10
16
  HeadMusic::Style::Mark.for_all(trouble_spot)
11
17
  end
12
18
  end
13
19
 
14
- private
20
+ def external_exits_marks
21
+ external_exits.map do |trouble_spot|
22
+ HeadMusic::Style::Mark.for_all(trouble_spot)
23
+ end
24
+ end
25
+
26
+ def octave_ending_marks
27
+ return [] unless octave_ending?
28
+
29
+ [HeadMusic::Style::Mark.for_all(octave_ending)]
30
+ end
15
31
 
16
32
  def external_entries
17
- melodic_intervals.each_cons(2).map do |pair|
33
+ melodic_note_pairs.each_cons(2).map do |pair|
18
34
  first, second = *pair
19
- pair.map(&:notes).uniq if second.octave? && !second.spans?(first.first_note.pitch)
35
+ pair.map(&:notes).flatten.uniq if second.octave? && !second.spans?(first.pitches.first)
20
36
  end.compact
21
37
  end
22
38
 
23
39
  def external_exits
24
- melodic_intervals.each_cons(2).map do |pair|
40
+ melodic_note_pairs.each_cons(2).map do |pair|
25
41
  first, second = *pair
26
- pair.map(&:notes).uniq if first.octave? && !first.spans?(second.second_note.pitch)
42
+ pair.map(&:notes).flatten.uniq if first.octave? && !first.spans?(second.pitches.last)
27
43
  end.compact
28
44
  end
29
45
 
30
46
  def octave_ending
31
- octave_ending? ? [melodic_intervals.last.notes] : []
47
+ octave_ending? ? melodic_note_pairs.last.notes : []
32
48
  end
33
49
 
34
50
  def octave_ending?
35
- melodic_intervals.last&.octave?
51
+ melodic_note_pairs.last&.octave?
36
52
  end
37
53
  end
@@ -9,35 +9,35 @@ class HeadMusic::Style::Guidelines::RecoverLargeLeaps < HeadMusic::Style::Annota
9
9
  MESSAGE = "Recover large leaps by step in the opposite direction."
10
10
 
11
11
  def marks
12
- melodic_intervals.each_cons(3).map do |intervals|
13
- if unrecovered_leap?(intervals[0], intervals[1], intervals[2])
14
- HeadMusic::Style::Mark.for_all(notes_in_intervals(intervals))
12
+ melodic_note_pairs.each_cons(3).map do |note_pairs|
13
+ if unrecovered_leap?(note_pairs[0], note_pairs[1], note_pairs[2])
14
+ HeadMusic::Style::Mark.for_all(notes_in_note_pairs(note_pairs))
15
15
  end
16
16
  end.compact
17
17
  end
18
18
 
19
19
  private
20
20
 
21
- def notes_in_intervals(intervals)
22
- (intervals[0].notes + intervals[1].notes).uniq
21
+ def notes_in_note_pairs(note_pairs)
22
+ (note_pairs[0].notes + note_pairs[1].notes).uniq
23
23
  end
24
24
 
25
- def unrecovered_leap?(first_interval, second_interval, third_interval)
26
- first_interval.large_leap? &&
27
- !spelling_consonant_triad?(first_interval, second_interval, third_interval) &&
25
+ def unrecovered_leap?(first_note_pair, second_note_pair, third_note_pair)
26
+ first_note_pair.large_leap? &&
27
+ !spelling_consonant_triad?(first_note_pair, second_note_pair, third_note_pair) &&
28
28
  (
29
- !direction_changed?(first_interval, second_interval) ||
30
- !second_interval.step?
29
+ !direction_changed?(first_note_pair, second_note_pair) ||
30
+ !second_note_pair.step?
31
31
  )
32
32
  end
33
33
 
34
- def spelling_consonant_triad?(first_interval, second_interval, third_interval)
35
- first_interval.spells_consonant_triad_with?(second_interval) ||
36
- second_interval.spells_consonant_triad_with?(third_interval)
34
+ def spelling_consonant_triad?(first_note_pair, second_note_pair, third_note_pair)
35
+ first_note_pair.spells_consonant_triad_with?(second_note_pair) ||
36
+ second_note_pair.spells_consonant_triad_with?(third_note_pair)
37
37
  end
38
38
 
39
- def direction_changed?(first_interval, second_interval)
40
- first_interval.ascending? && second_interval.descending? ||
41
- first_interval.descending? && second_interval.ascending?
39
+ def direction_changed?(first_note_pair, second_note_pair)
40
+ first_note_pair.ascending? && second_note_pair.descending? ||
41
+ first_note_pair.descending? && second_note_pair.ascending?
42
42
  end
43
43
  end
@@ -9,14 +9,15 @@ class HeadMusic::Style::Guidelines::SingableIntervals < HeadMusic::Style::Annota
9
9
  MESSAGE = "Use only PU, m2, M2, m3, M3, P4, P5, m6 (ascending), P8 in the melodic line."
10
10
 
11
11
  def marks
12
- melodic_intervals.reject { |interval| permitted?(interval) }.map do |unpermitted_interval|
13
- HeadMusic::Style::Mark.for_all([unpermitted_interval.first_note, unpermitted_interval.second_note])
12
+ melodic_note_pairs.reject { |note_pair| permitted?(note_pair) }.map do |pair_with_unpermitted_interval|
13
+ HeadMusic::Style::Mark.for_all(pair_with_unpermitted_interval.notes)
14
14
  end
15
15
  end
16
16
 
17
17
  private
18
18
 
19
- def permitted?(melodic_interval)
19
+ def permitted?(note_pair)
20
+ melodic_interval = note_pair.melodic_interval
20
21
  whitelist_for_interval(melodic_interval).include?(melodic_interval.shorthand)
21
22
  end
22
23
 
@@ -7,12 +7,12 @@ class HeadMusic::Style::Guidelines::SingleLargeLeaps < HeadMusic::Style::Guideli
7
7
 
8
8
  private
9
9
 
10
- def unrecovered_leap?(first_interval, second_interval, third_interval)
11
- return false unless first_interval.large_leap?
12
- return false if spelling_consonant_triad?(first_interval, second_interval, third_interval)
13
- return false if second_interval.step?
14
- return false if second_interval.repetition?
10
+ def unrecovered_leap?(first_note_pair, second_note_pair, third_note_pair)
11
+ return false unless first_note_pair.large_leap?
12
+ return false if spelling_consonant_triad?(first_note_pair, second_note_pair, third_note_pair)
13
+ return false if second_note_pair.step?
14
+ return false if second_note_pair.repetition?
15
15
 
16
- !direction_changed?(first_interval, second_interval) && second_interval.leap?
16
+ !direction_changed?(first_note_pair, second_note_pair) && second_note_pair.leap?
17
17
  end
18
18
  end
@@ -6,23 +6,23 @@ class HeadMusic::Style::Guidelines::StepOutOfUnison < HeadMusic::Style::Annotati
6
6
  MESSAGE = "Exit a unison by step."
7
7
 
8
8
  def marks
9
- leaps_following_unisons.map do |skip|
10
- HeadMusic::Style::Mark.for_all(skip.notes)
9
+ leaps_following_unisons.map do |note_pair|
10
+ HeadMusic::Style::Mark.for_all(note_pair.notes)
11
11
  end.flatten
12
12
  end
13
13
 
14
14
  private
15
15
 
16
16
  def leaps_following_unisons
17
- melodic_intervals_following_unisons.select(&:leap?)
17
+ melodic_note_pairs_following_unisons.select(&:leap?)
18
18
  end
19
19
 
20
- def melodic_intervals_following_unisons
21
- @melodic_intervals_following_unisons ||=
20
+ def melodic_note_pairs_following_unisons
21
+ @melodic_note_pairs_following_unisons ||=
22
22
  perfect_unisons.map do |unison|
23
23
  note1 = voice.note_at(unison.position)
24
24
  note2 = voice.note_following(unison.position)
25
- HeadMusic::Analysis::MelodicInterval.new(note1, note2) if note1 && note2
25
+ HeadMusic::Content::Voice::MelodicNotePair.new(note1, note2) if note1 && note2
26
26
  end.compact
27
27
  end
28
28
 
@@ -1,3 +1,3 @@
1
1
  module HeadMusic
2
- VERSION = "8.0.2"
2
+ VERSION = "8.1.1"
3
3
  end
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Test the translation loading functionality
4
+
5
+ # First test - German translation of solfege
6
+ result = system('bundle exec ruby -e "require \'head_music\'; puts HeadMusic::Rudiment::Solmization.get(\'Solfège\')&.name || \'nil\'"')
7
+ puts "German 'Solfège' test: #{result}"
8
+
9
+ # Second test - Italian translation
10
+ result = system('bundle exec ruby -e "require \'head_music\'; puts HeadMusic::Rudiment::Solmization.get(\'solfeggio\')&.name || \'nil\'"')
11
+ puts "Italian 'solfeggio' test: #{result}"
12
+
13
+ # Third test - Russian translation
14
+ result = system('bundle exec ruby -e "require \'head_music\'; puts HeadMusic::Rudiment::Solmization.get(\'сольфеджио\')&.name || \'nil\'"')
15
+ puts "Russian 'сольфеджио' test: #{result}"
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: 8.0.2
4
+ version: 8.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Head
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-05-04 00:00:00.000000000 Z
11
+ date: 2025-05-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -225,6 +225,7 @@ files:
225
225
  - lib/head_music/style/mark.rb
226
226
  - lib/head_music/utilities/hash_key.rb
227
227
  - lib/head_music/version.rb
228
+ - test_translations.rb
228
229
  homepage: https://github.com/roberthead/head_music
229
230
  licenses:
230
231
  - MIT