head_music 8.3.0 → 11.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.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -3
  3. data/CHANGELOG.md +71 -0
  4. data/CLAUDE.md +62 -25
  5. data/Gemfile +7 -1
  6. data/Gemfile.lock +91 -3
  7. data/MUSIC_THEORY.md +120 -0
  8. data/README.md +18 -0
  9. data/Rakefile +7 -2
  10. data/head_music.gemspec +1 -1
  11. data/lib/head_music/analysis/diatonic_interval.rb +29 -27
  12. data/lib/head_music/analysis/dyad.rb +229 -0
  13. data/lib/head_music/analysis/interval_consonance.rb +51 -0
  14. data/lib/head_music/analysis/melodic_interval.rb +1 -1
  15. data/lib/head_music/analysis/pitch_class_set.rb +111 -14
  16. data/lib/head_music/analysis/{pitch_set.rb → pitch_collection.rb} +11 -5
  17. data/lib/head_music/analysis/sonority.rb +50 -12
  18. data/lib/head_music/content/note.rb +1 -1
  19. data/lib/head_music/content/placement.rb +1 -1
  20. data/lib/head_music/content/position.rb +1 -1
  21. data/lib/head_music/content/voice.rb +1 -1
  22. data/lib/head_music/instruments/alternate_tuning.rb +102 -0
  23. data/lib/head_music/instruments/alternate_tunings.yml +78 -0
  24. data/lib/head_music/instruments/instrument.rb +231 -72
  25. data/lib/head_music/instruments/instrument_configuration.rb +66 -0
  26. data/lib/head_music/instruments/instrument_configuration_option.rb +38 -0
  27. data/lib/head_music/instruments/instrument_configurations.yml +288 -0
  28. data/lib/head_music/instruments/instrument_families.yml +77 -0
  29. data/lib/head_music/instruments/instrument_family.rb +15 -5
  30. data/lib/head_music/instruments/instruments.yml +795 -965
  31. data/lib/head_music/instruments/playing_technique.rb +75 -0
  32. data/lib/head_music/instruments/playing_techniques.yml +826 -0
  33. data/lib/head_music/instruments/score_order.rb +136 -0
  34. data/lib/head_music/instruments/score_orders.yml +130 -0
  35. data/lib/head_music/instruments/staff.rb +61 -1
  36. data/lib/head_music/instruments/staff_scheme.rb +6 -4
  37. data/lib/head_music/instruments/stringing.rb +115 -0
  38. data/lib/head_music/instruments/stringing_course.rb +58 -0
  39. data/lib/head_music/instruments/stringings.yml +168 -0
  40. data/lib/head_music/instruments/variant.rb +6 -1
  41. data/lib/head_music/locales/de.yml +29 -0
  42. data/lib/head_music/locales/en.yml +106 -0
  43. data/lib/head_music/locales/es.yml +29 -0
  44. data/lib/head_music/locales/fr.yml +29 -0
  45. data/lib/head_music/locales/it.yml +29 -0
  46. data/lib/head_music/locales/ru.yml +29 -0
  47. data/lib/head_music/{rudiment → notation}/musical_symbol.rb +3 -3
  48. data/lib/head_music/notation/staff_mapping.rb +70 -0
  49. data/lib/head_music/notation/staff_position.rb +62 -0
  50. data/lib/head_music/notation.rb +7 -0
  51. data/lib/head_music/rudiment/alteration.rb +34 -49
  52. data/lib/head_music/rudiment/alterations.yml +32 -0
  53. data/lib/head_music/rudiment/base.rb +9 -0
  54. data/lib/head_music/rudiment/chromatic_interval.rb +4 -7
  55. data/lib/head_music/rudiment/clef.rb +2 -2
  56. data/lib/head_music/rudiment/consonance.rb +39 -5
  57. data/lib/head_music/rudiment/diatonic_context.rb +25 -0
  58. data/lib/head_music/rudiment/key.rb +77 -0
  59. data/lib/head_music/rudiment/key_signature/enharmonic_equivalence.rb +1 -1
  60. data/lib/head_music/rudiment/key_signature.rb +21 -8
  61. data/lib/head_music/rudiment/letter_name.rb +3 -3
  62. data/lib/head_music/rudiment/meter.rb +19 -9
  63. data/lib/head_music/rudiment/mode.rb +92 -0
  64. data/lib/head_music/rudiment/note.rb +112 -0
  65. data/lib/head_music/rudiment/pitch/parser.rb +52 -0
  66. data/lib/head_music/rudiment/pitch.rb +5 -6
  67. data/lib/head_music/rudiment/pitch_class.rb +1 -1
  68. data/lib/head_music/rudiment/quality.rb +1 -1
  69. data/lib/head_music/rudiment/reference_pitch.rb +1 -1
  70. data/lib/head_music/rudiment/register.rb +4 -1
  71. data/lib/head_music/rudiment/rest.rb +36 -0
  72. data/lib/head_music/rudiment/rhythmic_element.rb +53 -0
  73. data/lib/head_music/rudiment/rhythmic_unit/parser.rb +86 -0
  74. data/lib/head_music/rudiment/rhythmic_unit.rb +13 -5
  75. data/lib/head_music/rudiment/rhythmic_units.yml +80 -0
  76. data/lib/head_music/rudiment/rhythmic_value/parser.rb +77 -0
  77. data/lib/head_music/{content → rudiment}/rhythmic_value.rb +23 -5
  78. data/lib/head_music/rudiment/scale.rb +4 -5
  79. data/lib/head_music/rudiment/scale_degree.rb +1 -1
  80. data/lib/head_music/rudiment/scale_type.rb +9 -3
  81. data/lib/head_music/rudiment/solmization.rb +1 -1
  82. data/lib/head_music/rudiment/spelling.rb +8 -4
  83. data/lib/head_music/rudiment/tempo.rb +85 -0
  84. data/lib/head_music/rudiment/tonal_context.rb +35 -0
  85. data/lib/head_music/rudiment/tuning/just_intonation.rb +0 -39
  86. data/lib/head_music/rudiment/tuning/meantone.rb +0 -39
  87. data/lib/head_music/rudiment/tuning/pythagorean.rb +0 -39
  88. data/lib/head_music/rudiment/tuning.rb +21 -1
  89. data/lib/head_music/rudiment/unpitched_note.rb +62 -0
  90. data/lib/head_music/style/guidelines/consonant_climax.rb +2 -2
  91. data/lib/head_music/style/medieval_tradition.rb +26 -0
  92. data/lib/head_music/style/modern_tradition.rb +31 -0
  93. data/lib/head_music/style/renaissance_tradition.rb +26 -0
  94. data/lib/head_music/style/tradition.rb +21 -0
  95. data/lib/head_music/time/clock_position.rb +84 -0
  96. data/lib/head_music/time/conductor.rb +264 -0
  97. data/lib/head_music/time/meter_event.rb +37 -0
  98. data/lib/head_music/time/meter_map.rb +173 -0
  99. data/lib/head_music/time/musical_position.rb +188 -0
  100. data/lib/head_music/time/smpte_timecode.rb +164 -0
  101. data/lib/head_music/time/tempo_event.rb +40 -0
  102. data/lib/head_music/time/tempo_map.rb +187 -0
  103. data/lib/head_music/time.rb +32 -0
  104. data/lib/head_music/utilities/case.rb +27 -0
  105. data/lib/head_music/utilities/hash_key.rb +34 -2
  106. data/lib/head_music/version.rb +1 -1
  107. data/lib/head_music.rb +71 -22
  108. data/user_stories/active/string-pitches.md +41 -0
  109. data/user_stories/backlog/notation-style.md +183 -0
  110. data/user_stories/backlog/organizing-content.md +80 -0
  111. data/user_stories/done/consonance-dissonance-classification.md +117 -0
  112. data/user_stories/{backlog → done}/dyad-analysis.md +6 -16
  113. data/user_stories/done/epic--score-order/PLAN.md +244 -0
  114. data/user_stories/done/expand-playing-techniques.md +38 -0
  115. data/user_stories/done/handle-time.md +7 -0
  116. data/user_stories/done/handle-time.rb +163 -0
  117. data/user_stories/done/instrument-architecture.md +238 -0
  118. data/user_stories/done/instrument-variant.md +65 -0
  119. data/user_stories/done/move-musical-symbol-to-notation.md +161 -0
  120. data/user_stories/done/move-staff-mapping-to-notation.md +158 -0
  121. data/user_stories/done/move-staff-position-to-notation.md +141 -0
  122. data/user_stories/done/notation-module-foundation.md +102 -0
  123. data/user_stories/done/percussion_set.md +260 -0
  124. data/user_stories/done/sonority-identification.md +37 -0
  125. data/user_stories/done/superclass-for-note.md +30 -0
  126. data/user_stories/epics/notation-module.md +135 -0
  127. data/user_stories/visioning/agentic-daw.md +2 -0
  128. metadata +84 -18
  129. data/TODO.md +0 -109
  130. data/check_instrument_consistency.rb +0 -0
  131. data/test_translations.rb +0 -15
  132. data/user_stories/backlog/consonance-dissonance-classification.md +0 -57
  133. data/user_stories/backlog/pitch-set-classification.md +0 -62
  134. data/user_stories/backlog/sonority-identification.md +0 -47
  135. /data/user_stories/{backlog → done/epic--score-order}/band-score-order.md +0 -0
  136. /data/user_stories/{backlog → done/epic--score-order}/chamber-ensemble-score-order.md +0 -0
  137. /data/user_stories/{backlog → done/epic--score-order}/orchestral-score-order.md +0 -0
  138. /data/user_stories/{backlog → done}/pitch-class-set-analysis.md +0 -0
@@ -0,0 +1,65 @@
1
+ ## Instrument Variant Refactoring
2
+
3
+ ### Background
4
+ The current architecture conflates instrument catalog data with specific instrument instances. We need to separate these concerns to better represent how instruments are actually used in musical scores.
5
+
6
+ ### Hierarchy
7
+ The refactored hierarchy will provide three levels of abstraction:
8
+ - **InstrumentFamily** (existing): Broad category like "saxophone" or "trumpet"
9
+ - **InstrumentType** (renamed from Instrument): Catalog entry with all possible variants (e.g., "trumpet" which can be in Bb, C, D, Eb)
10
+ - **Instrument** (new): Specific instance with selected variant (e.g., "Trumpet in C")
11
+
12
+ ### User Stories
13
+
14
+ **STORY 1: Rename Instrument to InstrumentType**
15
+ AS a developer
16
+ WHEN I want to access instrument catalog data
17
+ I WANT to use InstrumentType.get("trumpet")
18
+ SO THAT it's clear I'm getting a type definition, not a specific instrument instance
19
+
20
+ **STORY 2: Create new Instrument class for specific variants**
21
+ AS a developer
22
+ WHEN I need a specific instrument for a score
23
+ I WANT to call Instrument.get("trumpet_in_c") or Instrument.get("trumpet", "in_c")
24
+ SO THAT I get a specific, usable instrument instance with proper transposition
25
+
26
+ **STORY 3: Instrument instances are sortable**
27
+ AS a developer
28
+ WHEN I have multiple Instrument instances in a score
29
+ I WANT them to sort properly by orchestral order and transposition
30
+ SO THAT "Trumpet in Eb" appears before "Trumpet in C" in the score
31
+
32
+ **STORY 4: Clear API for common use cases**
33
+ AS a developer
34
+ WHEN I create an Instrument without specifying a variant
35
+ I WANT to get the default variant automatically
36
+ SO THAT Instrument.get("clarinet") returns a Bb clarinet (the default)
37
+
38
+ **STORY 5: Instrument provides unified interface**
39
+ AS a developer
40
+ WHEN I have an Instrument instance
41
+ I WANT to access properties like name, transposition, clefs, and pitch_designation
42
+ SO THAT I don't need to navigate between instrument type and variant objects
43
+
44
+ ### Implementation Notes
45
+
46
+ 1. The Instrument class should:
47
+ - Wrap both an InstrumentType and a specific Variant
48
+ - Generate appropriate display names (e.g., "Clarinet in A")
49
+ - Provide methods for transposition, clefs, staff schemes
50
+ - Be directly usable in scores and parts
51
+
52
+ 2. Factory methods should support:
53
+ - `Instrument.get("trumpet_in_c")` - parse variant from name
54
+ - `Instrument.get("trumpet", "in_c")` - explicit variant
55
+ - `Instrument.get("trumpet")` - use default variant
56
+
57
+ 3. ScoreOrder should work with Instrument instances directly
58
+
59
+ ### Migration Path
60
+
61
+ 1. Rename existing Instrument class to InstrumentType
62
+ 2. Update all references to use InstrumentType where appropriate
63
+ 3. Create new Instrument class for variant instances
64
+ 4. Update ScoreOrder to work with new Instrument instances
65
+ 5. Update documentation and tests
@@ -0,0 +1,161 @@
1
+ # Move MusicalSymbol to Notation Module
2
+
3
+ AS a developer
4
+
5
+ I WANT MusicalSymbol in the HeadMusic::Notation module
6
+
7
+ SO THAT symbol representation logic (ASCII, Unicode, HTML) lives with other visual notation concerns rather than abstract music theory
8
+
9
+ ## Background
10
+
11
+ `MusicalSymbol` is a container class that holds multiple representations of a musical symbol:
12
+ - ASCII representation (plain text, e.g., "#" for sharp)
13
+ - Unicode representation (musical symbols, e.g., "♯" for sharp)
14
+ - HTML entity representation (e.g., "♯" for sharp)
15
+
16
+ It's currently located in `HeadMusic::Rudiment`, but it's purely about visual presentation, not music theory. Symbols are how we visually represent musical concepts, making this a notation concern.
17
+
18
+ The class is used by:
19
+ - `Clef` - for clef symbols (treble, bass, alto, etc.)
20
+ - `Alteration` - for accidental symbols (sharp, flat, natural, etc.)
21
+
22
+ Moving it to Notation clarifies that this is about rendering and display, separate from the theoretical concepts themselves.
23
+
24
+ ## Scenario: Getting symbol representations
25
+
26
+ Given a sharp accidental
27
+
28
+ When I access its musical symbol
29
+
30
+ Then I can get the ASCII representation "#"
31
+
32
+ And I can get the Unicode representation "♯"
33
+
34
+ And I can get the HTML entity "♯"
35
+
36
+ ## Scenario: Using symbols for text output
37
+
38
+ Given I need to display a clef in plain text
39
+
40
+ When I use the clef's MusicalSymbol
41
+
42
+ Then I get the appropriate ASCII character representation
43
+
44
+ ## Scenario: Using symbols for web display
45
+
46
+ Given I need to display an accidental on a web page
47
+
48
+ When I use the alteration's MusicalSymbol
49
+
50
+ Then I can choose between Unicode (for modern browsers) or HTML entity (for compatibility)
51
+
52
+ ## Technical Notes
53
+
54
+ ### Current State
55
+
56
+ **Location:** `lib/head_music/rudiment/musical_symbol.rb`
57
+ **Class:** `HeadMusic::Rudiment::MusicalSymbol`
58
+ **Tests:** `spec/head_music/rudiment/musical_symbol_spec.rb`
59
+ **Used by:**
60
+ - `lib/head_music/rudiment/clef.rb`
61
+ - `lib/head_music/rudiment/alteration.rb`
62
+
63
+ ### Proposed Changes
64
+
65
+ 1. **Move file:**
66
+ - From: `lib/head_music/rudiment/musical_symbol.rb`
67
+ - To: `lib/head_music/notation/musical_symbol.rb`
68
+
69
+ 2. **Update class definition:**
70
+ ```ruby
71
+ # lib/head_music/notation/musical_symbol.rb
72
+ module HeadMusic::Notation; end
73
+
74
+ class HeadMusic::Notation::MusicalSymbol
75
+ attr_reader :ascii, :unicode, :html_entity
76
+
77
+ def initialize(ascii: nil, unicode: nil, html_entity: nil)
78
+ @ascii = ascii
79
+ @unicode = unicode
80
+ @html_entity = html_entity
81
+ end
82
+
83
+ def to_s
84
+ unicode || ascii
85
+ end
86
+ end
87
+ ```
88
+
89
+ 3. **Move spec file:**
90
+ - From: `spec/head_music/rudiment/musical_symbol_spec.rb`
91
+ - To: `spec/head_music/notation/musical_symbol_spec.rb`
92
+
93
+ 4. **Update spec:**
94
+ ```ruby
95
+ describe HeadMusic::Notation::MusicalSymbol do
96
+ # All tests remain unchanged except the describe statement
97
+ ```
98
+
99
+ 5. **Update references in Clef:**
100
+ ```ruby
101
+ # lib/head_music/rudiment/clef.rb
102
+ # Update MusicalSymbol references to use HeadMusic::Notation::MusicalSymbol
103
+ ```
104
+
105
+ 6. **Update references in Alteration:**
106
+ ```ruby
107
+ # lib/head_music/rudiment/alteration.rb
108
+ # Update MusicalSymbol references to use HeadMusic::Notation::MusicalSymbol
109
+ ```
110
+
111
+ 7. **Update loading:**
112
+ ```ruby
113
+ # lib/head_music/notation.rb
114
+ module HeadMusic::Notation; end
115
+
116
+ require "head_music/notation/staff_position"
117
+ require "head_music/notation/musical_symbol"
118
+ ```
119
+
120
+ ### Files to Update
121
+
122
+ - Move: `lib/head_music/rudiment/musical_symbol.rb` → `lib/head_music/notation/musical_symbol.rb`
123
+ - Move: `spec/head_music/rudiment/musical_symbol_spec.rb` → `spec/head_music/notation/musical_symbol_spec.rb`
124
+ - Update: `lib/head_music/notation.rb` (add require)
125
+ - Update: `lib/head_music/rudiment/clef.rb` (update MusicalSymbol references)
126
+ - Update: `lib/head_music/rudiment/alteration.rb` (update MusicalSymbol references)
127
+ - Remove: `lib/head_music/rudiment.rb` require for musical_symbol
128
+
129
+ ## Acceptance Criteria
130
+
131
+ - [ ] `HeadMusic::Notation::MusicalSymbol` class exists
132
+ - [ ] Original file `lib/head_music/rudiment/musical_symbol.rb` removed
133
+ - [ ] Spec file at `spec/head_music/notation/musical_symbol_spec.rb`
134
+ - [ ] All existing MusicalSymbol tests pass
135
+ - [ ] `Clef` references to MusicalSymbol updated and working
136
+ - [ ] `Alteration` references to MusicalSymbol updated and working
137
+ - [ ] `lib/head_music/notation.rb` requires musical_symbol
138
+ - [ ] `lib/head_music/rudiment.rb` no longer requires musical_symbol
139
+ - [ ] All Clef tests pass
140
+ - [ ] All Alteration tests pass
141
+ - [ ] All existing tests across entire codebase still pass
142
+ - [ ] Maintains 90%+ test coverage
143
+ - [ ] No deprecation warnings or breaking changes for internal usage
144
+
145
+ ## Implementation Steps
146
+
147
+ 1. Create `lib/head_music/notation/musical_symbol.rb` with updated module path
148
+ 2. Copy class implementation unchanged
149
+ 3. Create `spec/head_music/notation/musical_symbol_spec.rb`
150
+ 4. Update describe statement in spec
151
+ 5. Update `lib/head_music/notation.rb` to require musical_symbol
152
+ 6. Update references in `lib/head_music/rudiment/clef.rb`
153
+ 7. Update references in `lib/head_music/rudiment/alteration.rb`
154
+ 8. Remove require from `lib/head_music/rudiment.rb`
155
+ 9. Run tests: `bundle exec rspec spec/head_music/notation/musical_symbol_spec.rb`
156
+ 10. Run tests: `bundle exec rspec spec/head_music/rudiment/clef_spec.rb`
157
+ 11. Run tests: `bundle exec rspec spec/head_music/rudiment/alteration_spec.rb`
158
+ 12. Run full test suite: `bundle exec rspec`
159
+ 13. Run linter: `bundle exec rubocop -a`
160
+ 14. Delete original files after verifying everything works
161
+ 15. Verify 90%+ coverage maintained
@@ -0,0 +1,158 @@
1
+ # Move StaffMapping to Notation Module
2
+
3
+ AS a developer
4
+
5
+ I WANT StaffMapping in the HeadMusic::Notation module
6
+
7
+ SO THAT percussion notation mapping logic lives with visual notation concerns rather than instrument properties
8
+
9
+ ## Background
10
+
11
+ `StaffMapping` maps percussion instruments (and playing techniques) to specific positions on a staff. For example, in a standard drum kit staff:
12
+ - Bass drum → space below staff
13
+ - Snare drum → space 2
14
+ - Hi-hat → space 4
15
+ - Ride cymbal → top line
16
+
17
+ This is purely about visual notation - where on the staff each drum appears - not about the acoustic properties of the instruments themselves. The drum's sound (pitch range, timbre) is an instrument property, but where it appears on a staff is a notation convention.
18
+
19
+ Currently located in `HeadMusic::Instruments`, this class should move to `HeadMusic::Notation` to properly separate visual layout concerns from instrument acoustics.
20
+
21
+ ## Scenario: Mapping drums to staff positions
22
+
23
+ Given a drum kit with a snare drum
24
+
25
+ When I need to notate the snare on a staff
26
+
27
+ Then StaffMapping tells me it appears on space 2
28
+
29
+ And I can use that position for rendering
30
+
31
+ ## Scenario: Getting all mappings for an instrument
32
+
33
+ Given a drum kit instrument
34
+
35
+ When I access its staff mapping
36
+
37
+ Then I get all the drum-to-position mappings for that kit
38
+
39
+ And each mapping includes the instrument, playing technique, and staff position
40
+
41
+ ## Scenario: Supporting different percussion notation schemes
42
+
43
+ Given different percussion instruments use different staff conventions
44
+
45
+ When I create a StaffMapping for a specific scheme (e.g., "standard drum kit")
46
+
47
+ Then instruments are mapped to their conventional positions for that notation style
48
+
49
+ ## Technical Notes
50
+
51
+ ### Current State
52
+
53
+ **Location:** `lib/head_music/instruments/staff_mapping.rb`
54
+ **Class:** `HeadMusic::Instruments::StaffMapping`
55
+ **Tests:** `spec/head_music/instruments/staff_mapping_spec.rb`
56
+ **Used by:**
57
+ - `lib/head_music/instruments/staff.rb`
58
+ - `lib/head_music/instruments/staff_scheme.rb`
59
+ - Percussion instrument configurations
60
+
61
+ ### Proposed Changes
62
+
63
+ 1. **Move file:**
64
+ - From: `lib/head_music/instruments/staff_mapping.rb`
65
+ - To: `lib/head_music/notation/staff_mapping.rb`
66
+
67
+ 2. **Update class definition:**
68
+ ```ruby
69
+ # lib/head_music/notation/staff_mapping.rb
70
+ module HeadMusic::Notation; end
71
+
72
+ class HeadMusic::Notation::StaffMapping
73
+ attr_reader :instrument, :playing_technique, :staff_position
74
+
75
+ def initialize(instrument:, playing_technique: nil, staff_position:)
76
+ @instrument = instrument
77
+ @playing_technique = playing_technique
78
+ @staff_position = HeadMusic::Notation::StaffPosition.new(staff_position)
79
+ end
80
+
81
+ # ... rest of class unchanged
82
+ end
83
+ ```
84
+
85
+ 3. **Move spec file:**
86
+ - From: `spec/head_music/instruments/staff_mapping_spec.rb`
87
+ - To: `spec/head_music/notation/staff_mapping_spec.rb`
88
+
89
+ 4. **Update spec:**
90
+ ```ruby
91
+ describe HeadMusic::Notation::StaffMapping do
92
+ # All tests remain unchanged except the describe statement
93
+ ```
94
+
95
+ 5. **Update references:**
96
+ - `lib/head_music/instruments/staff.rb` - Update StaffMapping references
97
+ - `lib/head_music/instruments/staff_scheme.rb` - Update StaffMapping references
98
+ - Instrument YAML files if they reference the class directly
99
+
100
+ 6. **Update loading:**
101
+ ```ruby
102
+ # lib/head_music/notation.rb
103
+ module HeadMusic::Notation; end
104
+
105
+ require "head_music/notation/staff_position"
106
+ require "head_music/notation/musical_symbol"
107
+ require "head_music/notation/staff_mapping"
108
+ ```
109
+
110
+ ### Files to Update
111
+
112
+ - Move: `lib/head_music/instruments/staff_mapping.rb` → `lib/head_music/notation/staff_mapping.rb`
113
+ - Move: `spec/head_music/instruments/staff_mapping_spec.rb` → `spec/head_music/notation/staff_mapping_spec.rb`
114
+ - Update: `lib/head_music/notation.rb` (add require)
115
+ - Update: `lib/head_music/instruments/staff.rb` (update references)
116
+ - Update: `lib/head_music/instruments/staff_scheme.rb` (update references)
117
+ - Remove: `lib/head_music/instruments.rb` require for staff_mapping
118
+
119
+ ### Dependencies
120
+
121
+ This story depends on `StaffPosition` already being in the Notation module, since `StaffMapping` uses `StaffPosition`. Recommended to complete the "Move StaffPosition to Notation" story first.
122
+
123
+ ## Acceptance Criteria
124
+
125
+ - [ ] `HeadMusic::Notation::StaffMapping` class exists
126
+ - [ ] Original file `lib/head_music/instruments/staff_mapping.rb` removed
127
+ - [ ] Spec file at `spec/head_music/notation/staff_mapping_spec.rb`
128
+ - [ ] All existing StaffMapping tests pass
129
+ - [ ] StaffMapping correctly references `Notation::StaffPosition`
130
+ - [ ] References in `Instruments::Staff` updated and working
131
+ - [ ] References in `Instruments::StaffScheme` updated and working
132
+ - [ ] `lib/head_music/notation.rb` requires staff_mapping
133
+ - [ ] `lib/head_music/instruments.rb` no longer requires staff_mapping
134
+ - [ ] All percussion instrument tests still pass
135
+ - [ ] All existing tests across entire codebase still pass
136
+ - [ ] Maintains 90%+ test coverage
137
+ - [ ] No deprecation warnings or breaking changes for internal usage
138
+
139
+ ## Implementation Steps
140
+
141
+ 1. Verify `StaffPosition` is already in `HeadMusic::Notation`
142
+ 2. Create `lib/head_music/notation/staff_mapping.rb` with updated module path
143
+ 3. Update StaffPosition reference to use `Notation::StaffPosition`
144
+ 4. Copy rest of class implementation unchanged
145
+ 5. Create `spec/head_music/notation/staff_mapping_spec.rb`
146
+ 6. Update describe statement in spec
147
+ 7. Update `lib/head_music/notation.rb` to require staff_mapping
148
+ 8. Update references in `lib/head_music/instruments/staff.rb`
149
+ 9. Update references in `lib/head_music/instruments/staff_scheme.rb`
150
+ 10. Remove require from `lib/head_music/instruments.rb`
151
+ 11. Run tests: `bundle exec rspec spec/head_music/notation/staff_mapping_spec.rb`
152
+ 12. Run tests: `bundle exec rspec spec/head_music/instruments/staff_spec.rb`
153
+ 13. Run tests: `bundle exec rspec spec/head_music/instruments/staff_scheme_spec.rb`
154
+ 14. Run percussion-related tests
155
+ 15. Run full test suite: `bundle exec rspec`
156
+ 16. Run linter: `bundle exec rubocop -a`
157
+ 17. Delete original files after verifying everything works
158
+ 18. Verify 90%+ coverage maintained
@@ -0,0 +1,141 @@
1
+ # Move StaffPosition to Notation Module
2
+
3
+ AS a developer
4
+
5
+ I WANT StaffPosition in the HeadMusic::Notation module
6
+
7
+ SO THAT staff positioning logic lives with other visual notation concerns rather than instrument properties
8
+
9
+ ## Background
10
+
11
+ `StaffPosition` represents positions on a 5-line musical staff (lines, spaces, and ledger lines). It's a pure visual notation concept with no connection to instrument acoustical properties.
12
+
13
+ Currently located in `HeadMusic::Instruments`, it has a TODO comment (line 5) suggesting it should move to a HeadMusic::Notation module.
14
+
15
+ The class:
16
+ - Maps integer indices to staff positions (even = lines, odd = spaces)
17
+ - Handles positions within the staff (0-8) and ledger lines above/below
18
+ - Provides named positions ("bottom line", "middle line", "top line", etc.)
19
+ - Has comprehensive test coverage (264 lines of tests)
20
+
21
+ This move resolves the TODO and establishes StaffPosition as the first concrete class in the Notation module.
22
+
23
+ ## Scenario: Accessing staff positions for rendering
24
+
25
+ Given I need to position a note on a musical staff
26
+
27
+ When I use `HeadMusic::Notation::StaffPosition.new(4)`
28
+
29
+ Then I get the middle line staff position
30
+
31
+ And I can query whether it's a line or space
32
+
33
+ And I can get its display name
34
+
35
+ ## Scenario: Using named staff positions
36
+
37
+ Given I need to reference a specific staff position
38
+
39
+ When I use `HeadMusic::Notation::StaffPosition.name_to_index("top line")`
40
+
41
+ Then I get the index 8
42
+
43
+ And I can create a StaffPosition from that index
44
+
45
+ ## Scenario: Handling ledger lines
46
+
47
+ Given I need a position above or below the standard staff
48
+
49
+ When I create `HeadMusic::Notation::StaffPosition.new(10)`
50
+
51
+ Then I get "ledger line above staff"
52
+
53
+ And when I create `HeadMusic::Notation::StaffPosition.new(-2)`
54
+
55
+ Then I get "ledger line below staff"
56
+
57
+ ## Technical Notes
58
+
59
+ ### Current State
60
+
61
+ **Location:** `lib/head_music/instruments/staff_position.rb`
62
+ **Class:** `HeadMusic::Instruments::StaffPosition`
63
+ **Tests:** `spec/head_music/instruments/staff_position_spec.rb` (264 lines)
64
+ **Dependencies:** Used by `Clef` for pitch-to-staff mapping
65
+
66
+ ### Proposed Changes
67
+
68
+ 1. **Move file:**
69
+ - From: `lib/head_music/instruments/staff_position.rb`
70
+ - To: `lib/head_music/notation/staff_position.rb`
71
+
72
+ 2. **Update class definition:**
73
+ ```ruby
74
+ # lib/head_music/notation/staff_position.rb
75
+ module HeadMusic::Notation; end
76
+
77
+ class HeadMusic::Notation::StaffPosition
78
+ # Remove TODO comment about moving to Notation module
79
+ # ... rest of class unchanged
80
+ ```
81
+
82
+ 3. **Move spec file:**
83
+ - From: `spec/head_music/instruments/staff_position_spec.rb`
84
+ - To: `spec/head_music/notation/staff_position_spec.rb`
85
+
86
+ 4. **Update spec:**
87
+ ```ruby
88
+ describe HeadMusic::Notation::StaffPosition do
89
+ # All tests remain unchanged except the describe statement
90
+ ```
91
+
92
+ 5. **Update references:**
93
+ - `lib/head_music/rudiment/clef.rb` - Update StaffPosition references
94
+ - `lib/head_music/notation.rb` - Add require for staff_position
95
+
96
+ 6. **Update loading:**
97
+ ```ruby
98
+ # lib/head_music/notation.rb
99
+ module HeadMusic::Notation; end
100
+
101
+ require "head_music/notation/staff_position"
102
+ ```
103
+
104
+ ### Files to Update
105
+
106
+ - Move: `lib/head_music/instruments/staff_position.rb` → `lib/head_music/notation/staff_position.rb`
107
+ - Move: `spec/head_music/instruments/staff_position_spec.rb` → `spec/head_music/notation/staff_position_spec.rb`
108
+ - Update: `lib/head_music/notation.rb` (add require)
109
+ - Update: `lib/head_music/rudiment/clef.rb` (update references)
110
+ - Remove: `lib/head_music/instruments.rb` require for staff_position
111
+ - Update: CLAUDE.md (note StaffPosition location)
112
+
113
+ ## Acceptance Criteria
114
+
115
+ - [ ] `HeadMusic::Notation::StaffPosition` class exists
116
+ - [ ] Original file `lib/head_music/instruments/staff_position.rb` removed
117
+ - [ ] Spec file at `spec/head_music/notation/staff_position_spec.rb`
118
+ - [ ] All 264 lines of existing tests pass unchanged (except describe statement)
119
+ - [ ] TODO comment removed from class
120
+ - [ ] All references in `Clef` updated and working
121
+ - [ ] `lib/head_music/notation.rb` requires staff_position
122
+ - [ ] `lib/head_music/instruments.rb` no longer requires staff_position
123
+ - [ ] All existing tests across entire codebase still pass
124
+ - [ ] Maintains 90%+ test coverage
125
+ - [ ] No deprecation warnings or breaking changes for internal usage
126
+
127
+ ## Implementation Steps
128
+
129
+ 1. Create `lib/head_music/notation/staff_position.rb` with updated module path
130
+ 2. Copy class implementation, removing TODO comment
131
+ 3. Create `spec/head_music/notation/staff_position_spec.rb`
132
+ 4. Update describe statement in spec
133
+ 5. Update `lib/head_music/notation.rb` to require staff_position
134
+ 6. Update references in `lib/head_music/rudiment/clef.rb`
135
+ 7. Remove require from `lib/head_music/instruments.rb`
136
+ 8. Run tests: `bundle exec rspec spec/head_music/notation/staff_position_spec.rb`
137
+ 9. Run tests: `bundle exec rspec spec/head_music/rudiment/clef_spec.rb`
138
+ 10. Run full test suite: `bundle exec rspec`
139
+ 11. Run linter: `bundle exec rubocop -a`
140
+ 12. Delete original files after verifying everything works
141
+ 13. Verify 90%+ coverage maintained
@@ -0,0 +1,102 @@
1
+ # Notation Module Foundation
2
+
3
+ AS a developer
4
+
5
+ I WANT a dedicated HeadMusic::Notation module structure
6
+
7
+ SO THAT I have a clear, organized place for notation-related features separate from music theory, instrument properties, and musical content
8
+
9
+ ## Background
10
+
11
+ The HeadMusic gem currently organizes code into four main modules:
12
+ - **Rudiment** - Abstract music theory concepts (pitch, interval, scale, chord)
13
+ - **Instruments** - Instrument properties (range, transposition, families)
14
+ - **Content** - Musical compositions and temporal organization
15
+ - **Analysis** - Music analysis tools
16
+
17
+ However, there's no dedicated module for visual notation or representational concerns. Notation-related code is scattered across Instruments and Rudiment, making it unclear where notation features should live.
18
+
19
+ A TODO comment in `lib/head_music/instruments/staff_position.rb:5` suggests creating a HeadMusic::Notation module. Before moving any classes, we need the foundational infrastructure in place.
20
+
21
+ ## Scenario: Loading the notation module
22
+
23
+ Given the HeadMusic gem is loaded
24
+
25
+ When I require 'head_music'
26
+
27
+ Then the HeadMusic::Notation module should be available
28
+
29
+ And it should not break any existing functionality
30
+
31
+ ## Scenario: Documenting module boundaries
32
+
33
+ Given a developer wants to add notation features
34
+
35
+ When they consult CLAUDE.md
36
+
37
+ Then they should see clear guidance on what belongs in Notation vs Rudiment vs Instruments vs Content
38
+
39
+ ## Technical Notes
40
+
41
+ ### Module Boundaries
42
+
43
+ **HeadMusic::Notation** (visual representation):
44
+ - Staff positions, lines, spaces, ledger lines
45
+ - Musical symbols (ASCII, Unicode, HTML entities)
46
+ - Clef placement and rendering
47
+ - Notehead shapes, stems, flags, beams
48
+ - Accidental placement rules
49
+ - Future: ties, slurs, articulations, dynamics
50
+
51
+ **HeadMusic::Rudiment** (music theory):
52
+ - Abstract concepts: pitch, interval, scale, chord
53
+ - Duration concepts (without visual representation)
54
+ - Keys, meters (as musical concepts)
55
+
56
+ **HeadMusic::Instruments** (instrument properties):
57
+ - Instrument families and classification
58
+ - Pitch ranges and transposition
59
+ - Playing techniques
60
+ - Score ordering
61
+
62
+ **HeadMusic::Content** (musical content):
63
+ - Compositions, voices, bars
64
+ - Notes in context (pitch + duration + placement)
65
+ - Temporal organization
66
+
67
+ ### Implementation
68
+
69
+ Create minimal module infrastructure:
70
+
71
+ **File: `lib/head_music/notation.rb`**
72
+ ```ruby
73
+ # A module for visual music notation
74
+ module HeadMusic::Notation; end
75
+
76
+ # Load notation classes
77
+ # (Initially empty - classes will be added in subsequent stories)
78
+ ```
79
+
80
+ **Update: `lib/head_music.rb`**
81
+ ```ruby
82
+ # Add after Content module loading:
83
+ require "head_music/notation"
84
+ ```
85
+
86
+ **Update: `CLAUDE.md`**
87
+ Add section documenting the Notation module and its boundaries.
88
+
89
+ ## Acceptance Criteria
90
+
91
+ - [ ] `lib/head_music/notation/` directory exists
92
+ - [ ] `lib/head_music/notation.rb` file exists and defines module
93
+ - [ ] `lib/head_music.rb` requires the notation module
94
+ - [ ] `HeadMusic::Notation` module is accessible after requiring 'head_music'
95
+ - [ ] All existing tests pass without modification
96
+ - [ ] Maintains 90%+ test coverage
97
+ - [ ] CLAUDE.md updated with Notation module boundaries
98
+ - [ ] No existing functionality broken
99
+
100
+ ## Implementation Notes
101
+
102
+ This is pure infrastructure - no classes are moved or created yet. This provides the foundation for subsequent stories that will move StaffPosition, MusicalSymbol, and StaffMapping into the Notation module.