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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +9 -3
- data/CHANGELOG.md +18 -0
- data/CLAUDE.md +35 -15
- data/Gemfile +7 -1
- data/Gemfile.lock +91 -3
- data/README.md +18 -0
- data/Rakefile +7 -2
- data/head_music.gemspec +1 -1
- data/lib/head_music/analysis/dyad.rb +229 -0
- data/lib/head_music/analysis/melodic_interval.rb +1 -1
- data/lib/head_music/analysis/pitch_class_set.rb +111 -14
- data/lib/head_music/analysis/{pitch_set.rb → pitch_collection.rb} +11 -5
- data/lib/head_music/analysis/sonority.rb +50 -12
- data/lib/head_music/content/cantus_firmus_examples.rb +58 -0
- data/lib/head_music/content/staff.rb +1 -1
- data/lib/head_music/content/voice.rb +1 -1
- data/lib/head_music/instruments/alternate_tuning.rb +102 -0
- data/lib/head_music/instruments/alternate_tunings.yml +78 -0
- data/lib/head_music/instruments/instrument.rb +251 -82
- data/lib/head_music/instruments/instrument_configuration.rb +66 -0
- data/lib/head_music/instruments/instrument_configuration_option.rb +38 -0
- data/lib/head_music/instruments/instrument_configurations.yml +288 -0
- data/lib/head_music/instruments/instrument_families.yml +77 -0
- data/lib/head_music/instruments/instrument_family.rb +3 -4
- data/lib/head_music/instruments/instruments.yml +795 -965
- data/lib/head_music/instruments/playing_technique.rb +75 -0
- data/lib/head_music/instruments/playing_techniques.yml +826 -0
- data/lib/head_music/instruments/score_order.rb +2 -5
- data/lib/head_music/instruments/staff.rb +61 -1
- data/lib/head_music/instruments/staff_scheme.rb +6 -4
- data/lib/head_music/instruments/stringing.rb +115 -0
- data/lib/head_music/instruments/stringing_course.rb +58 -0
- data/lib/head_music/instruments/stringings.yml +168 -0
- data/lib/head_music/instruments/variant.rb +0 -1
- data/lib/head_music/locales/de.yml +23 -0
- data/lib/head_music/locales/en.yml +100 -0
- data/lib/head_music/locales/es.yml +23 -0
- data/lib/head_music/locales/fr.yml +23 -0
- data/lib/head_music/locales/it.yml +23 -0
- data/lib/head_music/locales/ru.yml +23 -0
- data/lib/head_music/{rudiment → notation}/musical_symbol.rb +3 -3
- data/lib/head_music/notation/staff_mapping.rb +70 -0
- data/lib/head_music/notation/staff_position.rb +62 -0
- data/lib/head_music/notation.rb +7 -0
- data/lib/head_music/rudiment/alteration.rb +17 -47
- data/lib/head_music/rudiment/alterations.yml +32 -0
- data/lib/head_music/rudiment/chromatic_interval.rb +1 -1
- data/lib/head_music/rudiment/clef.rb +1 -1
- data/lib/head_music/rudiment/consonance.rb +14 -13
- data/lib/head_music/rudiment/key_signature.rb +0 -26
- data/lib/head_music/rudiment/rhythmic_unit/parser.rb +2 -2
- data/lib/head_music/rudiment/rhythmic_value/parser.rb +1 -1
- data/lib/head_music/rudiment/rhythmic_value.rb +1 -1
- data/lib/head_music/rudiment/spelling.rb +3 -0
- data/lib/head_music/rudiment/tempo.rb +1 -1
- data/lib/head_music/rudiment/tuning/just_intonation.rb +0 -39
- data/lib/head_music/rudiment/tuning/meantone.rb +0 -39
- data/lib/head_music/rudiment/tuning/pythagorean.rb +0 -39
- data/lib/head_music/rudiment/tuning.rb +20 -0
- data/lib/head_music/style/guidelines/consonant_climax.rb +2 -2
- data/lib/head_music/style/modern_tradition.rb +8 -11
- data/lib/head_music/style/tradition.rb +1 -1
- data/lib/head_music/time/clock_position.rb +84 -0
- data/lib/head_music/time/conductor.rb +264 -0
- data/lib/head_music/time/meter_event.rb +37 -0
- data/lib/head_music/time/meter_map.rb +173 -0
- data/lib/head_music/time/musical_position.rb +188 -0
- data/lib/head_music/time/smpte_timecode.rb +164 -0
- data/lib/head_music/time/tempo_event.rb +40 -0
- data/lib/head_music/time/tempo_map.rb +187 -0
- data/lib/head_music/time.rb +32 -0
- data/lib/head_music/utilities/case.rb +27 -0
- data/lib/head_music/utilities/hash_key.rb +1 -1
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +42 -13
- data/user_stories/backlog/notation-style.md +183 -0
- data/user_stories/{todo → backlog}/organizing-content.md +9 -1
- data/user_stories/done/consonance-dissonance-classification.md +117 -0
- data/user_stories/{todo → done}/dyad-analysis.md +4 -6
- data/user_stories/done/expand-playing-techniques.md +38 -0
- data/user_stories/{active → done}/handle-time.rb +5 -19
- data/user_stories/done/instrument-architecture.md +238 -0
- data/user_stories/done/move-musical-symbol-to-notation.md +161 -0
- data/user_stories/done/move-staff-mapping-to-notation.md +158 -0
- data/user_stories/done/move-staff-position-to-notation.md +141 -0
- data/user_stories/done/notation-module-foundation.md +102 -0
- data/user_stories/done/percussion_set.md +260 -0
- data/user_stories/{todo → done}/pitch-class-set-analysis.md +0 -40
- data/user_stories/done/sonority-identification.md +37 -0
- data/user_stories/done/string-pitches.md +41 -0
- data/user_stories/epics/notation-module.md +135 -0
- data/user_stories/{todo → visioning}/agentic-daw.md +0 -1
- metadata +56 -20
- data/check_instrument_consistency.rb +0 -0
- data/lib/head_music/instruments/instrument_type.rb +0 -188
- data/test_translations.rb +0 -15
- data/user_stories/todo/consonance-dissonance-classification.md +0 -57
- data/user_stories/todo/material-and-scores.md +0 -10
- data/user_stories/todo/percussion_set.md +0 -1
- data/user_stories/todo/pitch-set-classification.md +0 -72
- data/user_stories/todo/sonority-identification.md +0 -67
- /data/user_stories/{active → done}/handle-time.md +0 -0
|
@@ -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.
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Percussion Set Staff
|
|
2
|
+
|
|
3
|
+
As a composer or arranger
|
|
4
|
+
|
|
5
|
+
I want to create percussion set or drum kit parts in a score
|
|
6
|
+
|
|
7
|
+
So that I can notate multiple unpitched percussion instruments on a single staff
|
|
8
|
+
|
|
9
|
+
## Background
|
|
10
|
+
|
|
11
|
+
Individual percussion instruments (timpani, snare_drum, bass_drum, etc.) already exist in the codebase and use the `neutral_clef` (also known as percussion_clef). However, there's no way to represent a **percussion set** or **drum kit** where multiple unpitched instruments are notated on a single staff with each instrument assigned to a specific line or space.
|
|
12
|
+
|
|
13
|
+
This is distinct from a clef. The `neutral_clef` already exists in `HeadMusic::Rudiment::Clef`. What's needed is:
|
|
14
|
+
1. A new instrument definition for percussion sets (e.g., `drum_kit`, `percussion_set`)
|
|
15
|
+
2. A staff scheme that uses the `neutral_clef`
|
|
16
|
+
3. A percussion mapping system that defines which instruments appear on which staff lines/spaces
|
|
17
|
+
|
|
18
|
+
## Scenario: Create a standard drum kit staff
|
|
19
|
+
|
|
20
|
+
Given I am composing a piece with drum kit
|
|
21
|
+
|
|
22
|
+
When I create a drum_kit instrument
|
|
23
|
+
|
|
24
|
+
Then it should use the neutral_clef
|
|
25
|
+
|
|
26
|
+
And it should have a default percussion mapping for standard drum kit instruments
|
|
27
|
+
|
|
28
|
+
## Scenario: Map percussion instruments to staff positions
|
|
29
|
+
|
|
30
|
+
Given I have a drum_kit instrument
|
|
31
|
+
|
|
32
|
+
When I request the percussion mapping
|
|
33
|
+
|
|
34
|
+
Then I should see which line or space each percussion instrument occupies
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
- Crash cymbal → line 5
|
|
38
|
+
- Hi-hat → line 4 (or space above)
|
|
39
|
+
- Snare drum → line 3 (center line)
|
|
40
|
+
- Bass drum → line 1 (or space below)
|
|
41
|
+
- Floor tom → line 2
|
|
42
|
+
|
|
43
|
+
## Scenario: Create custom percussion set configurations
|
|
44
|
+
|
|
45
|
+
Given I want to notate a non-standard percussion ensemble
|
|
46
|
+
|
|
47
|
+
When I define a custom percussion_set
|
|
48
|
+
|
|
49
|
+
Then I should be able to specify which percussion instruments map to which staff positions
|
|
50
|
+
|
|
51
|
+
And the system should use the neutral_clef for the staff
|
|
52
|
+
|
|
53
|
+
## Technical Notes
|
|
54
|
+
|
|
55
|
+
### Architecture
|
|
56
|
+
|
|
57
|
+
A percussion set is:
|
|
58
|
+
- **NOT a clef** - it uses the existing `neutral_clef`
|
|
59
|
+
- **An instrument** with a special staff scheme configuration
|
|
60
|
+
- **A mapping system** that assigns percussion instruments to staff positions
|
|
61
|
+
|
|
62
|
+
### Proposed Implementation
|
|
63
|
+
|
|
64
|
+
Add to `lib/head_music/instruments/instruments.yml`:
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
drum_kit:
|
|
68
|
+
family_key: percussion_set
|
|
69
|
+
variants:
|
|
70
|
+
default:
|
|
71
|
+
staff_schemes:
|
|
72
|
+
default:
|
|
73
|
+
- clef: neutral_clef
|
|
74
|
+
percussion_mapping:
|
|
75
|
+
line_5: crash_cymbal
|
|
76
|
+
space_4: ride_cymbal
|
|
77
|
+
line_4: hi_hat
|
|
78
|
+
space_3: high_tom
|
|
79
|
+
line_3: snare_drum
|
|
80
|
+
space_2: mid_tom
|
|
81
|
+
line_2: floor_tom
|
|
82
|
+
space_1: bass_drum
|
|
83
|
+
line_1: bass_drum
|
|
84
|
+
|
|
85
|
+
percussion_set:
|
|
86
|
+
family_key: percussion_set
|
|
87
|
+
variants:
|
|
88
|
+
default:
|
|
89
|
+
staff_schemes:
|
|
90
|
+
default:
|
|
91
|
+
- clef: neutral_clef
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### New Concepts to Implement
|
|
95
|
+
|
|
96
|
+
1. **Percussion mapping attribute** on `HeadMusic::Instruments::Staff`
|
|
97
|
+
2. **Percussion family or category** to identify instruments as unpitched/percussion
|
|
98
|
+
3. **Position resolution** to determine which line/space a percussion instrument should use
|
|
99
|
+
|
|
100
|
+
### Related Components
|
|
101
|
+
|
|
102
|
+
- `HeadMusic::Rudiment::Clef` - neutral_clef already exists (line 115-128 in clefs.yml)
|
|
103
|
+
- `HeadMusic::Instruments::Staff` - would need to handle percussion_mapping attribute
|
|
104
|
+
- `HeadMusic::Instruments::StaffScheme` - provides the staff configuration
|
|
105
|
+
- Individual percussion instruments (timpani, snare_drum, etc.) already exist
|
|
106
|
+
|
|
107
|
+
## Acceptance Criteria
|
|
108
|
+
|
|
109
|
+
- Can create a drum_kit instrument
|
|
110
|
+
- drum_kit uses neutral_clef by default
|
|
111
|
+
- Can query which percussion instrument is assigned to each staff position
|
|
112
|
+
- Can create custom percussion_set configurations with custom mappings
|
|
113
|
+
- Maintains 90%+ test coverage
|
|
114
|
+
- Follows existing HeadMusic patterns (factory methods, YAML data-driven)
|
|
115
|
+
|
|
116
|
+
## Implementation Plan
|
|
117
|
+
|
|
118
|
+
### Design Decisions
|
|
119
|
+
|
|
120
|
+
Based on research and discussion:
|
|
121
|
+
|
|
122
|
+
1. **Naming**: Use `instrument_mapping` (not `percussion_mapping`) for flexibility
|
|
123
|
+
2. **Mapping Format**: Use instrument keys (strings/symbols), not objects
|
|
124
|
+
3. **Validation**:
|
|
125
|
+
- Mapped instruments must exist
|
|
126
|
+
- No requirement that they be percussion instruments
|
|
127
|
+
- Valid positions: `line_0` through `line_6`, `space_0` through `space_5`
|
|
128
|
+
4. **Flexibility**: Mappings are immutable (YAML-defined only) for now
|
|
129
|
+
5. **Stem Direction**: Rendering concern, not part of infrastructure
|
|
130
|
+
6. **Composite Instruments**: No special type needed - just regular instruments with `instrument_mapping`
|
|
131
|
+
7. **Family**: Use `drum_kit` family (not generic `percussion_set`)
|
|
132
|
+
|
|
133
|
+
### Standard Drum Kit Notation Mapping
|
|
134
|
+
|
|
135
|
+
Research shows there is no single universal standard, but most common convention:
|
|
136
|
+
|
|
137
|
+
```yaml
|
|
138
|
+
instrument_mapping:
|
|
139
|
+
space_0: hi_hat_pedal # below staff
|
|
140
|
+
line_1: bass_drum
|
|
141
|
+
line_2: floor_tom
|
|
142
|
+
line_3: snare_drum # middle line - most consistent
|
|
143
|
+
line_4: mid_tom
|
|
144
|
+
space_4: high_tom
|
|
145
|
+
line_5: ride_cymbal
|
|
146
|
+
space_5: hi_hat # above staff
|
|
147
|
+
line_6: crash_cymbal # first ledger line above
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Implementation Steps
|
|
151
|
+
|
|
152
|
+
#### 1. Add New Instrument Families
|
|
153
|
+
To `lib/head_music/instruments/instrument_families.yml`:
|
|
154
|
+
- `tom_tom` family
|
|
155
|
+
- `hi_hat` family
|
|
156
|
+
- `drum_kit` family
|
|
157
|
+
|
|
158
|
+
#### 2. Add New Individual Instruments
|
|
159
|
+
To `lib/head_music/instruments/instruments.yml`:
|
|
160
|
+
- `hi_hat` (family: hi_hat)
|
|
161
|
+
- `hi_hat_pedal` (family: hi_hat)
|
|
162
|
+
- `crash_cymbal` (family: cymbal)
|
|
163
|
+
- `ride_cymbal` (family: cymbal)
|
|
164
|
+
- `high_tom` (family: tom_tom)
|
|
165
|
+
- `mid_tom` (family: tom_tom)
|
|
166
|
+
- `floor_tom` (family: tom_tom)
|
|
167
|
+
|
|
168
|
+
Each with:
|
|
169
|
+
- Appropriate aliases
|
|
170
|
+
- `family_key` reference
|
|
171
|
+
- Single `default` variant
|
|
172
|
+
- `neutral_clef` staff scheme
|
|
173
|
+
|
|
174
|
+
#### 3. Add Composite Instrument
|
|
175
|
+
To `lib/head_music/instruments/instruments.yml`:
|
|
176
|
+
- `drum_kit` (alias: `drum_set`) with standard instrument_mapping
|
|
177
|
+
|
|
178
|
+
#### 4. Enhance Staff Class
|
|
179
|
+
To `lib/head_music/instruments/staff.rb`:
|
|
180
|
+
- Add `instrument_mapping` attribute (hash)
|
|
181
|
+
- Add `#instrument_for_position(position_key)` method
|
|
182
|
+
- Add `#position_for_instrument(instrument_name)` method
|
|
183
|
+
- Add `#components` method (derives instruments from mapping)
|
|
184
|
+
- Handle parsing `instrument_mapping` from YAML
|
|
185
|
+
- Validate:
|
|
186
|
+
- Position keys match pattern (line_0-6, space_0-5)
|
|
187
|
+
- Referenced instruments exist
|
|
188
|
+
|
|
189
|
+
#### 5. Tests
|
|
190
|
+
- Specs for all new instrument families
|
|
191
|
+
- Specs for all new individual instruments
|
|
192
|
+
- Specs for `Staff#instrument_mapping` functionality
|
|
193
|
+
- Specs for `drum_kit` composite instrument
|
|
194
|
+
- Maintain 90%+ code coverage
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Refactoring Notes (Post-Implementation)
|
|
199
|
+
|
|
200
|
+
After the initial implementation, the design was refactored to better model playing techniques as a first-class concept rather than treating them as separate instruments.
|
|
201
|
+
|
|
202
|
+
### Key Changes:
|
|
203
|
+
|
|
204
|
+
1. **Introduced `PlayingTechnique` class**
|
|
205
|
+
- Playing techniques (pedal, stick, mallet, etc.) are now modeled as objects
|
|
206
|
+
- Includes `HeadMusic::Named` for potential future i18n support
|
|
207
|
+
- Common techniques defined: stick, pedal, mallet, hand, brush, rim_shot, cross_stick, open, closed, etc.
|
|
208
|
+
|
|
209
|
+
2. **Created `StaffMapping` class** (replaces simple hash-based approach)
|
|
210
|
+
- Represents the mapping of an instrument + optional playing technique to a staff position
|
|
211
|
+
- Uses `StaffPosition` with index-based positions (even = lines, odd = spaces)
|
|
212
|
+
- Attributes: `staff_position`, `instrument_key`, `playing_technique_key`
|
|
213
|
+
- Methods: `#instrument`, `#playing_technique`, `#position_index`, `#to_s`
|
|
214
|
+
|
|
215
|
+
3. **Removed `hi_hat_pedal` as separate instrument**
|
|
216
|
+
- Hi-hat pedal is now correctly modeled as a playing technique of the hi-hat instrument
|
|
217
|
+
- The hi-hat appears at two positions in drum_kit mapping:
|
|
218
|
+
- Position -1 (space below staff): hi_hat with pedal technique
|
|
219
|
+
- Position 9 (space above staff): hi_hat with stick technique
|
|
220
|
+
|
|
221
|
+
4. **Updated `Staff` class API**
|
|
222
|
+
- `#mappings` → returns array of `StaffMapping` objects
|
|
223
|
+
- `#mapping_for_position(index)` → get full mapping at position
|
|
224
|
+
- `#instrument_for_position(index)` → get instrument at position
|
|
225
|
+
- `#positions_for_instrument(key)` → returns all positions (handles multiple mappings)
|
|
226
|
+
- `#components` → returns unique instruments
|
|
227
|
+
|
|
228
|
+
5. **Changed YAML format** from hash to array:
|
|
229
|
+
```yaml
|
|
230
|
+
# Old format:
|
|
231
|
+
instrument_mapping:
|
|
232
|
+
line_3: snare_drum
|
|
233
|
+
space_5: hi_hat
|
|
234
|
+
|
|
235
|
+
# New format:
|
|
236
|
+
mappings:
|
|
237
|
+
- staff_position: 4 # index 4 = line 3
|
|
238
|
+
instrument: snare_drum
|
|
239
|
+
- staff_position: 9 # index 9 = space above staff
|
|
240
|
+
instrument: hi_hat
|
|
241
|
+
playing_technique: stick
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
6. **Added comprehensive YARD documentation**
|
|
245
|
+
- All new classes and public methods documented
|
|
246
|
+
- Examples provided for common use cases
|
|
247
|
+
|
|
248
|
+
### Benefits of Refactoring:
|
|
249
|
+
|
|
250
|
+
- **Semantic clarity**: Hi-hat pedal is a technique, not an instrument
|
|
251
|
+
- **Extensibility**: Easy to add new playing techniques (bow techniques for strings, breath techniques for winds, etc.)
|
|
252
|
+
- **Flexibility**: Instruments can appear at multiple positions with different techniques
|
|
253
|
+
- **Type safety**: StaffPosition validates position indices, StaffMapping provides structured access
|
|
254
|
+
|
|
255
|
+
### Test Results:
|
|
256
|
+
|
|
257
|
+
- **1040 examples, 0 failures** ✅
|
|
258
|
+
- **91.54% line coverage, 84.44% branch coverage** ✅
|
|
259
|
+
- All existing functionality preserved
|
|
260
|
+
- New tests verify multiple technique mappings
|