head_music 9.0.1 → 11.1.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 (103) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -3
  3. data/CHANGELOG.md +18 -0
  4. data/CLAUDE.md +35 -15
  5. data/Gemfile +7 -1
  6. data/Gemfile.lock +91 -3
  7. data/README.md +18 -0
  8. data/Rakefile +7 -2
  9. data/head_music.gemspec +1 -1
  10. data/lib/head_music/analysis/dyad.rb +229 -0
  11. data/lib/head_music/analysis/melodic_interval.rb +1 -1
  12. data/lib/head_music/analysis/pitch_class_set.rb +111 -14
  13. data/lib/head_music/analysis/{pitch_set.rb → pitch_collection.rb} +11 -5
  14. data/lib/head_music/analysis/sonority.rb +50 -12
  15. data/lib/head_music/content/cantus_firmus_examples.rb +58 -0
  16. data/lib/head_music/content/staff.rb +1 -1
  17. data/lib/head_music/content/voice.rb +1 -1
  18. data/lib/head_music/instruments/alternate_tuning.rb +102 -0
  19. data/lib/head_music/instruments/alternate_tunings.yml +78 -0
  20. data/lib/head_music/instruments/instrument.rb +251 -82
  21. data/lib/head_music/instruments/instrument_configuration.rb +66 -0
  22. data/lib/head_music/instruments/instrument_configuration_option.rb +38 -0
  23. data/lib/head_music/instruments/instrument_configurations.yml +288 -0
  24. data/lib/head_music/instruments/instrument_families.yml +77 -0
  25. data/lib/head_music/instruments/instrument_family.rb +3 -4
  26. data/lib/head_music/instruments/instruments.yml +795 -965
  27. data/lib/head_music/instruments/playing_technique.rb +75 -0
  28. data/lib/head_music/instruments/playing_techniques.yml +826 -0
  29. data/lib/head_music/instruments/score_order.rb +2 -5
  30. data/lib/head_music/instruments/staff.rb +61 -1
  31. data/lib/head_music/instruments/staff_scheme.rb +6 -4
  32. data/lib/head_music/instruments/stringing.rb +115 -0
  33. data/lib/head_music/instruments/stringing_course.rb +58 -0
  34. data/lib/head_music/instruments/stringings.yml +168 -0
  35. data/lib/head_music/instruments/variant.rb +0 -1
  36. data/lib/head_music/locales/de.yml +23 -0
  37. data/lib/head_music/locales/en.yml +100 -0
  38. data/lib/head_music/locales/es.yml +23 -0
  39. data/lib/head_music/locales/fr.yml +23 -0
  40. data/lib/head_music/locales/it.yml +23 -0
  41. data/lib/head_music/locales/ru.yml +23 -0
  42. data/lib/head_music/{rudiment → notation}/musical_symbol.rb +3 -3
  43. data/lib/head_music/notation/staff_mapping.rb +70 -0
  44. data/lib/head_music/notation/staff_position.rb +62 -0
  45. data/lib/head_music/notation.rb +7 -0
  46. data/lib/head_music/rudiment/alteration.rb +17 -47
  47. data/lib/head_music/rudiment/alterations.yml +32 -0
  48. data/lib/head_music/rudiment/chromatic_interval.rb +1 -1
  49. data/lib/head_music/rudiment/clef.rb +1 -1
  50. data/lib/head_music/rudiment/consonance.rb +14 -13
  51. data/lib/head_music/rudiment/key_signature.rb +0 -26
  52. data/lib/head_music/rudiment/rhythmic_unit/parser.rb +2 -2
  53. data/lib/head_music/rudiment/rhythmic_value/parser.rb +1 -1
  54. data/lib/head_music/rudiment/rhythmic_value.rb +1 -1
  55. data/lib/head_music/rudiment/spelling.rb +3 -0
  56. data/lib/head_music/rudiment/tempo.rb +1 -1
  57. data/lib/head_music/rudiment/tuning/just_intonation.rb +0 -39
  58. data/lib/head_music/rudiment/tuning/meantone.rb +0 -39
  59. data/lib/head_music/rudiment/tuning/pythagorean.rb +0 -39
  60. data/lib/head_music/rudiment/tuning.rb +20 -0
  61. data/lib/head_music/style/guidelines/consonant_climax.rb +2 -2
  62. data/lib/head_music/style/modern_tradition.rb +8 -11
  63. data/lib/head_music/style/tradition.rb +1 -1
  64. data/lib/head_music/time/clock_position.rb +84 -0
  65. data/lib/head_music/time/conductor.rb +264 -0
  66. data/lib/head_music/time/meter_event.rb +37 -0
  67. data/lib/head_music/time/meter_map.rb +173 -0
  68. data/lib/head_music/time/musical_position.rb +188 -0
  69. data/lib/head_music/time/smpte_timecode.rb +164 -0
  70. data/lib/head_music/time/tempo_event.rb +40 -0
  71. data/lib/head_music/time/tempo_map.rb +187 -0
  72. data/lib/head_music/time.rb +32 -0
  73. data/lib/head_music/utilities/case.rb +27 -0
  74. data/lib/head_music/utilities/hash_key.rb +1 -1
  75. data/lib/head_music/version.rb +1 -1
  76. data/lib/head_music.rb +42 -13
  77. data/user_stories/backlog/notation-style.md +183 -0
  78. data/user_stories/{todo → backlog}/organizing-content.md +9 -1
  79. data/user_stories/done/consonance-dissonance-classification.md +117 -0
  80. data/user_stories/{todo → done}/dyad-analysis.md +4 -6
  81. data/user_stories/done/expand-playing-techniques.md +38 -0
  82. data/user_stories/{active → done}/handle-time.rb +5 -19
  83. data/user_stories/done/instrument-architecture.md +238 -0
  84. data/user_stories/done/move-musical-symbol-to-notation.md +161 -0
  85. data/user_stories/done/move-staff-mapping-to-notation.md +158 -0
  86. data/user_stories/done/move-staff-position-to-notation.md +141 -0
  87. data/user_stories/done/notation-module-foundation.md +102 -0
  88. data/user_stories/done/percussion_set.md +260 -0
  89. data/user_stories/{todo → done}/pitch-class-set-analysis.md +0 -40
  90. data/user_stories/done/sonority-identification.md +37 -0
  91. data/user_stories/done/string-pitches.md +41 -0
  92. data/user_stories/epics/notation-module.md +135 -0
  93. data/user_stories/{todo → visioning}/agentic-daw.md +0 -1
  94. metadata +56 -20
  95. data/check_instrument_consistency.rb +0 -0
  96. data/lib/head_music/instruments/instrument_type.rb +0 -188
  97. data/test_translations.rb +0 -15
  98. data/user_stories/todo/consonance-dissonance-classification.md +0 -57
  99. data/user_stories/todo/material-and-scores.md +0 -10
  100. data/user_stories/todo/percussion_set.md +0 -1
  101. data/user_stories/todo/pitch-set-classification.md +0 -72
  102. data/user_stories/todo/sonority-identification.md +0 -67
  103. /data/user_stories/{active → done}/handle-time.md +0 -0
@@ -6,46 +6,6 @@ I want to analyze pitch class sets
6
6
 
7
7
  So that I can identify set relationships and transformations
8
8
 
9
- ## Scenario: Get size of pitch class set
10
-
11
- Given I have a pitch class set
12
-
13
- When I call the size method
14
-
15
- Then it should return the number of unique pitch classes
16
-
17
- ## Scenario: Check if monad
18
-
19
- Given I have a pitch class set with one pitch class
20
-
21
- When I call monad?
22
-
23
- Then it should return true
24
-
25
- ## Scenario: Check if dyad
26
-
27
- Given I have a pitch class set with two pitch classes
28
-
29
- When I call dyad?
30
-
31
- Then it should return true
32
-
33
- ## Scenario: Check if triad
34
-
35
- Given I have a pitch class set with three pitch classes
36
-
37
- When I call triad?
38
-
39
- Then it should return true only if they form stacked thirds
40
-
41
- ## Scenario: Check if trichord
42
-
43
- Given I have a pitch class set with three pitch classes
44
-
45
- When I call trichord?
46
-
47
- Then it should return true for any 3-pitch class set
48
-
49
9
  ## Scenario: Find normal form
50
10
 
51
11
  Given I have a pitch class set
@@ -0,0 +1,37 @@
1
+ # Sonority Identification
2
+
3
+ As a music theorist or composer
4
+
5
+ I want to identify and work with named sonorities
6
+
7
+ So that I can analyze and create harmonic structures
8
+
9
+ ## Scenario: Get sonority by identifier
10
+
11
+ Given I need a specific sonority
12
+
13
+ When I call Sonority.get with an identifier like "major_triad"
14
+
15
+ Then I should receive the corresponding sonority object
16
+
17
+ And it should contain the correct interval structure
18
+
19
+ ## Scenario: Generate pitch collection from sonority
20
+
21
+ Given I have a sonority identifier and a root pitch
22
+
23
+ When I call a method to generate pitches
24
+
25
+ Then I should receive the correct pitches for that sonority
26
+
27
+ And they should be in the specified inversion
28
+
29
+ ## Scenario: Access sonority from pitch collection
30
+
31
+ Given I have a PitchCollection object
32
+
33
+ When I call the sonority method
34
+
35
+ Then I should receive the corresponding Sonority object
36
+
37
+ And it should correctly identify the harmonic content
@@ -0,0 +1,41 @@
1
+ # Instruments can have strings with pitches
2
+
3
+ Instruments like violins and guitars have strings that are each tuned to a specific pitch.
4
+
5
+ ## Implementation Plan
6
+
7
+ HeadMusic::Instruments::Instrument
8
+
9
+ HeadMusic::Instruments::Stringing
10
+ > instrument: Instrument
11
+ >> stringing_courses: StringingCourse[]
12
+
13
+ HeadMusic::Instruments::StringingCourse
14
+ > stringing: Stringing
15
+ > standard_pitch: HeadMusic::Rudiment::Pitch
16
+ for example, the lowest string of a guitar, "E2"
17
+ - course_semitones: Integer[]
18
+ Examples:
19
+ a 6-string guitar would be []
20
+ a 12-string guitar would be [12] for the low strings and [0] for the high strings
21
+
22
+ HeadMusic::Instruments::AlternateTuning
23
+ > instrument: Instrument
24
+ - name: string
25
+ >> semitones: int[]
26
+ lowest to highest string courses in the strings
27
+
28
+
29
+ ## Answering questions
30
+
31
+ > Would it make more sense for Stringing to be associated with InstrumentConfiguration (the variant) rather than Instrument directly?
32
+
33
+ No, each instrument could have a Stringing object. If not, it would inherit from it's ancestry, just like most Instrument attributes.
34
+
35
+ > Is the semitone offset approach intentional for simplicity, or would you prefer something more explicit?
36
+
37
+ It is intentional. That way, the courses are independent of the specific tunings.
38
+
39
+ > Overlap Between standard_pitch and StringingTuning
40
+
41
+ The standard pitch is the unconfigured pitch of the string intrinsic to standard practice for that instrument. The Tuning objects configure alternative sets of pitches
@@ -0,0 +1,135 @@
1
+ # EPIC: Notation Module
2
+
3
+ AS a developer
4
+
5
+ I WANT to organize notation-related features in a dedicated HeadMusic::Notation module
6
+
7
+ SO THAT I can clearly separate visual representation concerns from music theory, instrument properties, and musical content
8
+
9
+ ## Vision
10
+
11
+ Music notation is about visual representation - how musical concepts appear on paper, screen, or other media. This is conceptually distinct from:
12
+ - **Music theory** (abstract concepts like pitch, interval, harmony)
13
+ - **Instrument properties** (range, transposition, acoustic characteristics)
14
+ - **Musical content** (compositions, temporal organization)
15
+
16
+ A dedicated Notation module provides a clear home for all visual representation concerns, making the codebase more organized and enabling future expansion into comprehensive music engraving capabilities.
17
+
18
+ ## Background
19
+
20
+ Currently, notation-related code is scattered across multiple modules:
21
+ - **HeadMusic::Instruments** contains `StaffPosition`, `Staff`, `StaffMapping`, and `StaffScheme`
22
+ - **HeadMusic::Rudiment** contains `Clef`, `MusicalSymbol`, and notation aspects of `Alteration` and `RhythmicUnit`
23
+ - **HeadMusic::Content** has its own minimal `Staff` class
24
+
25
+ A TODO comment in `lib/head_music/instruments/staff_position.rb:5` suggests: "consider moving to a HeadMusic::Notation module"
26
+
27
+ This scattered organization makes it unclear where new notation features should live and conflates distinct concerns.
28
+
29
+ ## Module Boundaries
30
+
31
+ ### HeadMusic::Notation (visual representation)
32
+ - Staff positions, lines, spaces, ledger lines
33
+ - Musical symbols (ASCII, Unicode, HTML entities)
34
+ - Notehead shapes and placement on staff
35
+ - Stem direction and flags
36
+ - Clef symbols and their staff placement
37
+ - Accidental symbol placement
38
+ - Future: beaming, ties, slurs, articulations, dynamics
39
+
40
+ ### HeadMusic::Rudiment (music theory)
41
+ - Abstract concepts: pitch, interval, scale, chord
42
+ - Duration concepts (without visual representation)
43
+ - Keys, meters (as musical concepts, not visual symbols)
44
+ - Core theory that notation represents visually
45
+
46
+ ### HeadMusic::Instruments (instrument properties)
47
+ - Instrument families and classification
48
+ - Pitch ranges and transposition
49
+ - Playing techniques
50
+ - Score ordering
51
+ - References to default clef (but doesn't own clef rendering)
52
+
53
+ ### HeadMusic::Content (musical content)
54
+ - Compositions, voices, bars
55
+ - Notes in context (pitch + duration + placement)
56
+ - Temporal organization
57
+ - Uses Notation for visual representation
58
+
59
+ ## Scope
60
+
61
+ ### Phase 1: Foundation & Core Moves (Current Focus)
62
+ - Establish module infrastructure
63
+ - Move `StaffPosition` from Instruments to Notation
64
+ - Move `MusicalSymbol` from Rudiment to Notation
65
+ - Move `StaffMapping` from Instruments to Notation
66
+
67
+ **User Stories:**
68
+ - [Notation Module Foundation](../backlog/notation-module-foundation.md)
69
+ - [Move StaffPosition to Notation](../backlog/move-staff-position-to-notation.md)
70
+ - [Move MusicalSymbol to Notation](../backlog/move-musical-symbol-to-notation.md)
71
+ - [Move StaffMapping to Notation](../backlog/move-staff-mapping-to-notation.md)
72
+
73
+ ### Phase 2: Extract Notation Aspects (Future)
74
+ - Create `ClefPlacement` - extract visual clef positioning from `Rudiment::Clef`
75
+ - Create `AccidentalPlacement` - extract accidental display rules from `Rudiment::Alteration`
76
+ - Create `RhythmicNotation` - extract notehead, stem, flag logic from `Rudiment::RhythmicUnit`
77
+ - Refactor `StaffScheme` to `Notation::StaffSystem`
78
+
79
+ ### Phase 3: Advanced Notation Features (Long-term Vision)
80
+ - Beaming (connecting note stems across beats)
81
+ - Stems (direction and length calculation)
82
+ - Ties (connecting same pitches across bars)
83
+ - Slurs (phrase markings)
84
+ - Articulations (staccato, accent, tenuto, etc.)
85
+ - Dynamics (forte, piano, crescendo, etc.)
86
+ - Tuplets (triplets, quintuplets, etc.)
87
+ - Barlines (single, double, repeat)
88
+ - Ornaments (trills, mordents, turns)
89
+ - Fermatas (pause markings)
90
+
91
+ ### Beyond Western Staff Notation (Future Exploration)
92
+
93
+ The Notation module could eventually support multiple notation systems:
94
+
95
+ **Traditional notation systems:**
96
+ - Western staff notation (current focus)
97
+ - Tablature (tab) - finger positions for guitar, bass, lute
98
+ - Shaped note notation - different note head shapes for scale degrees
99
+ - Drum notation - modified staff notation for percussion
100
+
101
+ **Alphanumeric and shorthand systems:**
102
+ - Lead sheet notation - melody with chord symbols
103
+ - Nashville Number System - scale degrees instead of chord names
104
+ - Roman numeral analysis - functional harmony labels
105
+ - ABC notation - text-based system for folk music
106
+ - Figured bass - Baroque-era interval shorthand
107
+
108
+ **Digital/computer formats:**
109
+ - MIDI - note events with pitch, velocity, timing
110
+ - MusicXML - XML-based interchange format
111
+ - Lilypond - text-based music engraving
112
+ - MEI (Music Encoding Initiative) - scholarly encoding
113
+
114
+ ## Success Criteria
115
+
116
+ **Module Organization:**
117
+ - Clear separation between theory (Rudiment), notation (Notation), instruments (Instruments), and content (Content)
118
+ - All visual representation concerns organized under `HeadMusic::Notation`
119
+ - Documentation clearly explains what belongs in each module
120
+
121
+ **Code Quality:**
122
+ - All moved classes maintain existing functionality
123
+ - 90%+ test coverage maintained throughout
124
+ - No breaking changes for internal usage
125
+ - All TODO comments resolved
126
+
127
+ **Extensibility:**
128
+ - Module designed for future expansion (beams, ties, dynamics, etc.)
129
+ - Support for multiple output formats (Unicode, MusicXML, SVG)
130
+ - Clear patterns established for adding new notation features
131
+
132
+ **Developer Experience:**
133
+ - Clear guidance in CLAUDE.md about where notation features belong
134
+ - Easy to find and use notation-related classes
135
+ - Logical organization that matches mental model of music notation
@@ -1,3 +1,2 @@
1
-
2
1
  agent-centric multimedia scoring
3
2
  (A/V DAW)
metadata CHANGED
@@ -1,29 +1,35 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: head_music
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.0.1
4
+ version: 11.1.0
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-10-24 00:00:00.000000000 Z
11
+ date: 2026-01-25 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '7.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '10'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
- - - "~>"
27
+ - - ">="
25
28
  - !ruby/object:Gem::Version
26
29
  version: '7.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '10'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: humanize
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -156,7 +162,6 @@ files:
156
162
  - bin/check_instrument_consistency.rb
157
163
  - bin/console
158
164
  - bin/setup
159
- - check_instrument_consistency.rb
160
165
  - head_music.gemspec
161
166
  - lib/head_music.rb
162
167
  - lib/head_music/analysis/circle.rb
@@ -166,30 +171,41 @@ files:
166
171
  - lib/head_music/analysis/diatonic_interval/parser.rb
167
172
  - lib/head_music/analysis/diatonic_interval/semitones.rb
168
173
  - lib/head_music/analysis/diatonic_interval/size.rb
174
+ - lib/head_music/analysis/dyad.rb
169
175
  - lib/head_music/analysis/harmonic_interval.rb
170
176
  - lib/head_music/analysis/interval_consonance.rb
171
177
  - lib/head_music/analysis/interval_cycle.rb
172
178
  - lib/head_music/analysis/melodic_interval.rb
173
179
  - lib/head_music/analysis/motion.rb
174
180
  - lib/head_music/analysis/pitch_class_set.rb
175
- - lib/head_music/analysis/pitch_set.rb
181
+ - lib/head_music/analysis/pitch_collection.rb
176
182
  - lib/head_music/analysis/sonority.rb
177
183
  - lib/head_music/content/bar.rb
184
+ - lib/head_music/content/cantus_firmus_examples.rb
178
185
  - lib/head_music/content/composition.rb
179
186
  - lib/head_music/content/note.rb
180
187
  - lib/head_music/content/placement.rb
181
188
  - lib/head_music/content/position.rb
182
189
  - lib/head_music/content/staff.rb
183
190
  - lib/head_music/content/voice.rb
191
+ - lib/head_music/instruments/alternate_tuning.rb
192
+ - lib/head_music/instruments/alternate_tunings.yml
184
193
  - lib/head_music/instruments/instrument.rb
194
+ - lib/head_music/instruments/instrument_configuration.rb
195
+ - lib/head_music/instruments/instrument_configuration_option.rb
196
+ - lib/head_music/instruments/instrument_configurations.yml
185
197
  - lib/head_music/instruments/instrument_families.yml
186
198
  - lib/head_music/instruments/instrument_family.rb
187
- - lib/head_music/instruments/instrument_type.rb
188
199
  - lib/head_music/instruments/instruments.yml
200
+ - lib/head_music/instruments/playing_technique.rb
201
+ - lib/head_music/instruments/playing_techniques.yml
189
202
  - lib/head_music/instruments/score_order.rb
190
203
  - lib/head_music/instruments/score_orders.yml
191
204
  - lib/head_music/instruments/staff.rb
192
205
  - lib/head_music/instruments/staff_scheme.rb
206
+ - lib/head_music/instruments/stringing.rb
207
+ - lib/head_music/instruments/stringing_course.rb
208
+ - lib/head_music/instruments/stringings.yml
193
209
  - lib/head_music/instruments/variant.rb
194
210
  - lib/head_music/locales/de.yml
195
211
  - lib/head_music/locales/en.yml
@@ -199,7 +215,12 @@ files:
199
215
  - lib/head_music/locales/it.yml
200
216
  - lib/head_music/locales/ru.yml
201
217
  - lib/head_music/named.rb
218
+ - lib/head_music/notation.rb
219
+ - lib/head_music/notation/musical_symbol.rb
220
+ - lib/head_music/notation/staff_mapping.rb
221
+ - lib/head_music/notation/staff_position.rb
202
222
  - lib/head_music/rudiment/alteration.rb
223
+ - lib/head_music/rudiment/alterations.yml
203
224
  - lib/head_music/rudiment/base.rb
204
225
  - lib/head_music/rudiment/chromatic_interval.rb
205
226
  - lib/head_music/rudiment/clef.rb
@@ -212,7 +233,6 @@ files:
212
233
  - lib/head_music/rudiment/letter_name.rb
213
234
  - lib/head_music/rudiment/meter.rb
214
235
  - lib/head_music/rudiment/mode.rb
215
- - lib/head_music/rudiment/musical_symbol.rb
216
236
  - lib/head_music/rudiment/note.rb
217
237
  - lib/head_music/rudiment/pitch.rb
218
238
  - lib/head_music/rudiment/pitch/enharmonic_equivalence.rb
@@ -287,26 +307,42 @@ files:
287
307
  - lib/head_music/style/modern_tradition.rb
288
308
  - lib/head_music/style/renaissance_tradition.rb
289
309
  - lib/head_music/style/tradition.rb
310
+ - lib/head_music/time.rb
311
+ - lib/head_music/time/clock_position.rb
312
+ - lib/head_music/time/conductor.rb
313
+ - lib/head_music/time/meter_event.rb
314
+ - lib/head_music/time/meter_map.rb
315
+ - lib/head_music/time/musical_position.rb
316
+ - lib/head_music/time/smpte_timecode.rb
317
+ - lib/head_music/time/tempo_event.rb
318
+ - lib/head_music/time/tempo_map.rb
319
+ - lib/head_music/utilities/case.rb
290
320
  - lib/head_music/utilities/hash_key.rb
291
321
  - lib/head_music/version.rb
292
- - test_translations.rb
293
- - user_stories/active/handle-time.md
294
- - user_stories/active/handle-time.rb
322
+ - user_stories/backlog/notation-style.md
323
+ - user_stories/backlog/organizing-content.md
324
+ - user_stories/done/consonance-dissonance-classification.md
325
+ - user_stories/done/dyad-analysis.md
295
326
  - user_stories/done/epic--score-order/PLAN.md
296
327
  - user_stories/done/epic--score-order/band-score-order.md
297
328
  - user_stories/done/epic--score-order/chamber-ensemble-score-order.md
298
329
  - user_stories/done/epic--score-order/orchestral-score-order.md
330
+ - user_stories/done/expand-playing-techniques.md
331
+ - user_stories/done/handle-time.md
332
+ - user_stories/done/handle-time.rb
333
+ - user_stories/done/instrument-architecture.md
299
334
  - user_stories/done/instrument-variant.md
335
+ - user_stories/done/move-musical-symbol-to-notation.md
336
+ - user_stories/done/move-staff-mapping-to-notation.md
337
+ - user_stories/done/move-staff-position-to-notation.md
338
+ - user_stories/done/notation-module-foundation.md
339
+ - user_stories/done/percussion_set.md
340
+ - user_stories/done/pitch-class-set-analysis.md
341
+ - user_stories/done/sonority-identification.md
342
+ - user_stories/done/string-pitches.md
300
343
  - user_stories/done/superclass-for-note.md
301
- - user_stories/todo/agentic-daw.md
302
- - user_stories/todo/consonance-dissonance-classification.md
303
- - user_stories/todo/dyad-analysis.md
304
- - user_stories/todo/material-and-scores.md
305
- - user_stories/todo/organizing-content.md
306
- - user_stories/todo/percussion_set.md
307
- - user_stories/todo/pitch-class-set-analysis.md
308
- - user_stories/todo/pitch-set-classification.md
309
- - user_stories/todo/sonority-identification.md
344
+ - user_stories/epics/notation-module.md
345
+ - user_stories/visioning/agentic-daw.md
310
346
  homepage: https://github.com/roberthead/head_music
311
347
  licenses:
312
348
  - MIT
File without changes
@@ -1,188 +0,0 @@
1
- # Namespace for instrument definitions, categorization, and configuration
2
- module HeadMusic::Instruments; end
3
-
4
- # A musical instrument type representing a catalog entry.
5
- # An instrument type defines the base characteristics and available variants for an instrument.
6
- # Attributes:
7
- # name_key: the name of the instrument type
8
- # alias_name_keys: an array of alternative names for the instrument type
9
- # orchestra_section_key: the section of the orchestra (e.g. "strings")
10
- # family_key: the key for the family of the instrument (e.g. "saxophone")
11
- # classification_keys: an array of classification_keys
12
- # default_clefs: the default clef or system of clefs for the instrument type
13
- # - [treble] for instruments that use the treble clef
14
- # - [treble, bass] for instruments that use the grand staff
15
- # variants:
16
- # a hash of default and alternative pitch designations
17
- # Associations:
18
- # family: the family of the instrument (e.g. "saxophone")
19
- # orchestra_section: the section of the orchestra (e.g. "strings")
20
- class HeadMusic::Instruments::InstrumentType
21
- include HeadMusic::Named
22
-
23
- INSTRUMENTS = YAML.load_file(File.expand_path("instruments.yml", __dir__)).freeze
24
-
25
- def self.get(name)
26
- get_by_name(name)
27
- end
28
-
29
- def self.all
30
- HeadMusic::Instruments::InstrumentFamily.all
31
- @all ||=
32
- INSTRUMENTS.map { |key, _data| get(key) }.sort_by { |instrument| instrument.name.downcase }
33
- end
34
-
35
- attr_reader(
36
- :name_key, :alias_name_keys,
37
- :family_key, :orchestra_section_key,
38
- :variants, :classification_keys
39
- )
40
-
41
- def ==(other)
42
- to_s == other.to_s
43
- end
44
-
45
- def translation(locale = :en)
46
- return name unless name_key
47
-
48
- I18n.translate(name_key, scope: %i[head_music instruments], locale: locale, default: name)
49
- end
50
-
51
- def family
52
- return unless family_key
53
-
54
- HeadMusic::Instruments::InstrumentFamily.get(family_key)
55
- end
56
-
57
- # Returns true if the instrument sounds at a different pitch than written.
58
- def transposing?
59
- default_sounding_transposition != 0
60
- end
61
-
62
- # Returns true if the instrument sounds at a different register than written.
63
- def transposing_at_the_octave?
64
- transposing? && default_sounding_transposition % 12 == 0
65
- end
66
-
67
- def single_staff?
68
- default_staves.length == 1
69
- end
70
-
71
- def multiple_staves?
72
- default_staves.length > 1
73
- end
74
-
75
- def pitched?
76
- return false if default_clefs.compact.uniq == [HeadMusic::Rudiment::Clef.get("neutral_clef")]
77
-
78
- default_clefs.any?
79
- end
80
-
81
- def default_variant
82
- variants&.find(&:default?) || variants&.first
83
- end
84
-
85
- def default_instrument
86
- @default_instrument ||= HeadMusic::Instruments::Instrument.new(self, default_variant)
87
- end
88
-
89
- def default_staff_scheme
90
- default_variant&.default_staff_scheme
91
- end
92
-
93
- def default_staves
94
- default_staff_scheme&.staves || []
95
- end
96
-
97
- def default_clefs
98
- default_staves&.map(&:clef) || []
99
- end
100
-
101
- def default_sounding_transposition
102
- default_staves&.first&.sounding_transposition || 0
103
- end
104
-
105
- private_class_method :new
106
-
107
- private
108
-
109
- def initialize(name)
110
- record = record_for_name(name)
111
- if record
112
- initialize_data_from_record(record)
113
- else
114
- self.name = name.to_s
115
- end
116
- end
117
-
118
- def record_for_name(name)
119
- record_for_key(HeadMusic::Utilities::HashKey.for(name)) ||
120
- record_for_key(key_for_name(name)) ||
121
- record_for_alias(name)
122
- end
123
-
124
- def key_for_name(name)
125
- INSTRUMENTS.each do |key, _data|
126
- I18n.config.available_locales.each do |locale|
127
- translation = I18n.t("head_music.instruments.#{key}", locale: locale)
128
- return key if translation.downcase == name.downcase
129
- end
130
- end
131
- nil
132
- end
133
-
134
- def record_for_key(key)
135
- INSTRUMENTS.each do |name_key, data|
136
- return data.merge!("name_key" => name_key) if name_key.to_s == key.to_s
137
- end
138
- nil
139
- end
140
-
141
- def record_for_alias(name)
142
- normalized_name = HeadMusic::Utilities::HashKey.for(name).to_s
143
- INSTRUMENTS.each do |name_key, data|
144
- data["alias_name_keys"]&.each do |alias_key|
145
- return data.merge!("name_key" => name_key) if HeadMusic::Utilities::HashKey.for(alias_key).to_s == normalized_name
146
- end
147
- end
148
- nil
149
- end
150
-
151
- def initialize_data_from_record(record)
152
- initialize_family(record)
153
- inherit_family_attributes(record)
154
- initialize_names(record)
155
- initialize_attributes(record)
156
- end
157
-
158
- def initialize_family(record)
159
- @family_key = record["family_key"]
160
- @family = HeadMusic::Instruments::InstrumentFamily.get(family_key)
161
- end
162
-
163
- def inherit_family_attributes(record)
164
- return unless family
165
-
166
- @orchestra_section_key = family.orchestra_section_key
167
- @classification_keys = family.classification_keys || []
168
- end
169
-
170
- def initialize_names(record)
171
- @name_key = record["name_key"].to_sym
172
- self.name = I18n.translate(name_key, scope: "head_music.instruments", locale: "en", default: inferred_name)
173
- @alias_name_keys = record["alias_name_keys"] || []
174
- end
175
-
176
- def initialize_attributes(record)
177
- @orchestra_section_key ||= record["orchestra_section_key"]
178
- @classification_keys = [@classification_keys, record["classification_keys"]].flatten.compact.uniq
179
- @variants =
180
- (record["variants"] || {}).map do |key, attributes|
181
- HeadMusic::Instruments::Variant.new(key, attributes)
182
- end
183
- end
184
-
185
- def inferred_name
186
- name_key.to_s.tr("_", " ")
187
- end
188
- end
data/test_translations.rb DELETED
@@ -1,15 +0,0 @@
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}"