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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +9 -3
- data/CHANGELOG.md +71 -0
- data/CLAUDE.md +62 -25
- data/Gemfile +7 -1
- data/Gemfile.lock +91 -3
- data/MUSIC_THEORY.md +120 -0
- data/README.md +18 -0
- data/Rakefile +7 -2
- data/head_music.gemspec +1 -1
- data/lib/head_music/analysis/diatonic_interval.rb +29 -27
- data/lib/head_music/analysis/dyad.rb +229 -0
- data/lib/head_music/analysis/interval_consonance.rb +51 -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/note.rb +1 -1
- data/lib/head_music/content/placement.rb +1 -1
- data/lib/head_music/content/position.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 +231 -72
- 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 +15 -5
- 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 +136 -0
- data/lib/head_music/instruments/score_orders.yml +130 -0
- 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 +6 -1
- data/lib/head_music/locales/de.yml +29 -0
- data/lib/head_music/locales/en.yml +106 -0
- data/lib/head_music/locales/es.yml +29 -0
- data/lib/head_music/locales/fr.yml +29 -0
- data/lib/head_music/locales/it.yml +29 -0
- data/lib/head_music/locales/ru.yml +29 -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 +34 -49
- data/lib/head_music/rudiment/alterations.yml +32 -0
- data/lib/head_music/rudiment/base.rb +9 -0
- data/lib/head_music/rudiment/chromatic_interval.rb +4 -7
- data/lib/head_music/rudiment/clef.rb +2 -2
- data/lib/head_music/rudiment/consonance.rb +39 -5
- data/lib/head_music/rudiment/diatonic_context.rb +25 -0
- data/lib/head_music/rudiment/key.rb +77 -0
- data/lib/head_music/rudiment/key_signature/enharmonic_equivalence.rb +1 -1
- data/lib/head_music/rudiment/key_signature.rb +21 -8
- data/lib/head_music/rudiment/letter_name.rb +3 -3
- data/lib/head_music/rudiment/meter.rb +19 -9
- data/lib/head_music/rudiment/mode.rb +92 -0
- data/lib/head_music/rudiment/note.rb +112 -0
- data/lib/head_music/rudiment/pitch/parser.rb +52 -0
- data/lib/head_music/rudiment/pitch.rb +5 -6
- data/lib/head_music/rudiment/pitch_class.rb +1 -1
- data/lib/head_music/rudiment/quality.rb +1 -1
- data/lib/head_music/rudiment/reference_pitch.rb +1 -1
- data/lib/head_music/rudiment/register.rb +4 -1
- data/lib/head_music/rudiment/rest.rb +36 -0
- data/lib/head_music/rudiment/rhythmic_element.rb +53 -0
- data/lib/head_music/rudiment/rhythmic_unit/parser.rb +86 -0
- data/lib/head_music/rudiment/rhythmic_unit.rb +13 -5
- data/lib/head_music/rudiment/rhythmic_units.yml +80 -0
- data/lib/head_music/rudiment/rhythmic_value/parser.rb +77 -0
- data/lib/head_music/{content → rudiment}/rhythmic_value.rb +23 -5
- data/lib/head_music/rudiment/scale.rb +4 -5
- data/lib/head_music/rudiment/scale_degree.rb +1 -1
- data/lib/head_music/rudiment/scale_type.rb +9 -3
- data/lib/head_music/rudiment/solmization.rb +1 -1
- data/lib/head_music/rudiment/spelling.rb +8 -4
- data/lib/head_music/rudiment/tempo.rb +85 -0
- data/lib/head_music/rudiment/tonal_context.rb +35 -0
- 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 +21 -1
- data/lib/head_music/rudiment/unpitched_note.rb +62 -0
- data/lib/head_music/style/guidelines/consonant_climax.rb +2 -2
- data/lib/head_music/style/medieval_tradition.rb +26 -0
- data/lib/head_music/style/modern_tradition.rb +31 -0
- data/lib/head_music/style/renaissance_tradition.rb +26 -0
- data/lib/head_music/style/tradition.rb +21 -0
- 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 +34 -2
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +71 -22
- data/user_stories/active/string-pitches.md +41 -0
- data/user_stories/backlog/notation-style.md +183 -0
- data/user_stories/backlog/organizing-content.md +80 -0
- data/user_stories/done/consonance-dissonance-classification.md +117 -0
- data/user_stories/{backlog → done}/dyad-analysis.md +6 -16
- data/user_stories/done/epic--score-order/PLAN.md +244 -0
- data/user_stories/done/expand-playing-techniques.md +38 -0
- data/user_stories/done/handle-time.md +7 -0
- data/user_stories/done/handle-time.rb +163 -0
- data/user_stories/done/instrument-architecture.md +238 -0
- data/user_stories/done/instrument-variant.md +65 -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/done/sonority-identification.md +37 -0
- data/user_stories/done/superclass-for-note.md +30 -0
- data/user_stories/epics/notation-module.md +135 -0
- data/user_stories/visioning/agentic-daw.md +2 -0
- metadata +84 -18
- data/TODO.md +0 -109
- data/check_instrument_consistency.rb +0 -0
- data/test_translations.rb +0 -15
- data/user_stories/backlog/consonance-dissonance-classification.md +0 -57
- data/user_stories/backlog/pitch-set-classification.md +0 -62
- data/user_stories/backlog/sonority-identification.md +0 -47
- /data/user_stories/{backlog → done/epic--score-order}/band-score-order.md +0 -0
- /data/user_stories/{backlog → done/epic--score-order}/chamber-ensemble-score-order.md +0 -0
- /data/user_stories/{backlog → done/epic--score-order}/orchestral-score-order.md +0 -0
- /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.
|