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,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
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Sonority Identification
|
|
2
|
+
|
|
3
|
+
As a music theorist or composer
|
|
4
|
+
|
|
5
|
+
I want to identify and work with named sonorities
|
|
6
|
+
|
|
7
|
+
So that I can analyze and create harmonic structures
|
|
8
|
+
|
|
9
|
+
## Scenario: Get sonority by identifier
|
|
10
|
+
|
|
11
|
+
Given I need a specific sonority
|
|
12
|
+
|
|
13
|
+
When I call Sonority.get with an identifier like "major_triad"
|
|
14
|
+
|
|
15
|
+
Then I should receive the corresponding sonority object
|
|
16
|
+
|
|
17
|
+
And it should contain the correct interval structure
|
|
18
|
+
|
|
19
|
+
## Scenario: Generate pitch collection from sonority
|
|
20
|
+
|
|
21
|
+
Given I have a sonority identifier and a root pitch
|
|
22
|
+
|
|
23
|
+
When I call a method to generate pitches
|
|
24
|
+
|
|
25
|
+
Then I should receive the correct pitches for that sonority
|
|
26
|
+
|
|
27
|
+
And they should be in the specified inversion
|
|
28
|
+
|
|
29
|
+
## Scenario: Access sonority from pitch collection
|
|
30
|
+
|
|
31
|
+
Given I have a PitchCollection object
|
|
32
|
+
|
|
33
|
+
When I call the sonority method
|
|
34
|
+
|
|
35
|
+
Then I should receive the corresponding Sonority object
|
|
36
|
+
|
|
37
|
+
And it should correctly identify the harmonic content
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
IN ORDER TO accurately model sound events
|
|
2
|
+
AS a developer
|
|
3
|
+
I WANT a clear way to group the notion of a Note (pitch + rhythmic value) and an unpitched note.
|
|
4
|
+
|
|
5
|
+
We need a hierarchy of classes
|
|
6
|
+
|
|
7
|
+
class RhythmicEvent
|
|
8
|
+
attr_accessor :rhythmic_value
|
|
9
|
+
|
|
10
|
+
class Note < RhythmicEvent
|
|
11
|
+
attr_accessor :pitch
|
|
12
|
+
def sounded?
|
|
13
|
+
true
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class UnpitchedNote < RhythmicEvent
|
|
17
|
+
def sounded?
|
|
18
|
+
true
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
class Rest < RhythmicEvent
|
|
22
|
+
def sounded?
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
acceptance criteria
|
|
28
|
+
- the above class hierarchy and implementation requirements
|
|
29
|
+
- full test coverage
|
|
30
|
+
- use NotImplementedError instead of NotImplementedError in RhythmicEvent if and where appropriate.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
# EPIC: Notation Module
|
|
2
|
+
|
|
3
|
+
AS a developer
|
|
4
|
+
|
|
5
|
+
I WANT to organize notation-related features in a dedicated HeadMusic::Notation module
|
|
6
|
+
|
|
7
|
+
SO THAT I can clearly separate visual representation concerns from music theory, instrument properties, and musical content
|
|
8
|
+
|
|
9
|
+
## Vision
|
|
10
|
+
|
|
11
|
+
Music notation is about visual representation - how musical concepts appear on paper, screen, or other media. This is conceptually distinct from:
|
|
12
|
+
- **Music theory** (abstract concepts like pitch, interval, harmony)
|
|
13
|
+
- **Instrument properties** (range, transposition, acoustic characteristics)
|
|
14
|
+
- **Musical content** (compositions, temporal organization)
|
|
15
|
+
|
|
16
|
+
A dedicated Notation module provides a clear home for all visual representation concerns, making the codebase more organized and enabling future expansion into comprehensive music engraving capabilities.
|
|
17
|
+
|
|
18
|
+
## Background
|
|
19
|
+
|
|
20
|
+
Currently, notation-related code is scattered across multiple modules:
|
|
21
|
+
- **HeadMusic::Instruments** contains `StaffPosition`, `Staff`, `StaffMapping`, and `StaffScheme`
|
|
22
|
+
- **HeadMusic::Rudiment** contains `Clef`, `MusicalSymbol`, and notation aspects of `Alteration` and `RhythmicUnit`
|
|
23
|
+
- **HeadMusic::Content** has its own minimal `Staff` class
|
|
24
|
+
|
|
25
|
+
A TODO comment in `lib/head_music/instruments/staff_position.rb:5` suggests: "consider moving to a HeadMusic::Notation module"
|
|
26
|
+
|
|
27
|
+
This scattered organization makes it unclear where new notation features should live and conflates distinct concerns.
|
|
28
|
+
|
|
29
|
+
## Module Boundaries
|
|
30
|
+
|
|
31
|
+
### HeadMusic::Notation (visual representation)
|
|
32
|
+
- Staff positions, lines, spaces, ledger lines
|
|
33
|
+
- Musical symbols (ASCII, Unicode, HTML entities)
|
|
34
|
+
- Notehead shapes and placement on staff
|
|
35
|
+
- Stem direction and flags
|
|
36
|
+
- Clef symbols and their staff placement
|
|
37
|
+
- Accidental symbol placement
|
|
38
|
+
- Future: beaming, ties, slurs, articulations, dynamics
|
|
39
|
+
|
|
40
|
+
### HeadMusic::Rudiment (music theory)
|
|
41
|
+
- Abstract concepts: pitch, interval, scale, chord
|
|
42
|
+
- Duration concepts (without visual representation)
|
|
43
|
+
- Keys, meters (as musical concepts, not visual symbols)
|
|
44
|
+
- Core theory that notation represents visually
|
|
45
|
+
|
|
46
|
+
### HeadMusic::Instruments (instrument properties)
|
|
47
|
+
- Instrument families and classification
|
|
48
|
+
- Pitch ranges and transposition
|
|
49
|
+
- Playing techniques
|
|
50
|
+
- Score ordering
|
|
51
|
+
- References to default clef (but doesn't own clef rendering)
|
|
52
|
+
|
|
53
|
+
### HeadMusic::Content (musical content)
|
|
54
|
+
- Compositions, voices, bars
|
|
55
|
+
- Notes in context (pitch + duration + placement)
|
|
56
|
+
- Temporal organization
|
|
57
|
+
- Uses Notation for visual representation
|
|
58
|
+
|
|
59
|
+
## Scope
|
|
60
|
+
|
|
61
|
+
### Phase 1: Foundation & Core Moves (Current Focus)
|
|
62
|
+
- Establish module infrastructure
|
|
63
|
+
- Move `StaffPosition` from Instruments to Notation
|
|
64
|
+
- Move `MusicalSymbol` from Rudiment to Notation
|
|
65
|
+
- Move `StaffMapping` from Instruments to Notation
|
|
66
|
+
|
|
67
|
+
**User Stories:**
|
|
68
|
+
- [Notation Module Foundation](../backlog/notation-module-foundation.md)
|
|
69
|
+
- [Move StaffPosition to Notation](../backlog/move-staff-position-to-notation.md)
|
|
70
|
+
- [Move MusicalSymbol to Notation](../backlog/move-musical-symbol-to-notation.md)
|
|
71
|
+
- [Move StaffMapping to Notation](../backlog/move-staff-mapping-to-notation.md)
|
|
72
|
+
|
|
73
|
+
### Phase 2: Extract Notation Aspects (Future)
|
|
74
|
+
- Create `ClefPlacement` - extract visual clef positioning from `Rudiment::Clef`
|
|
75
|
+
- Create `AccidentalPlacement` - extract accidental display rules from `Rudiment::Alteration`
|
|
76
|
+
- Create `RhythmicNotation` - extract notehead, stem, flag logic from `Rudiment::RhythmicUnit`
|
|
77
|
+
- Refactor `StaffScheme` to `Notation::StaffSystem`
|
|
78
|
+
|
|
79
|
+
### Phase 3: Advanced Notation Features (Long-term Vision)
|
|
80
|
+
- Beaming (connecting note stems across beats)
|
|
81
|
+
- Stems (direction and length calculation)
|
|
82
|
+
- Ties (connecting same pitches across bars)
|
|
83
|
+
- Slurs (phrase markings)
|
|
84
|
+
- Articulations (staccato, accent, tenuto, etc.)
|
|
85
|
+
- Dynamics (forte, piano, crescendo, etc.)
|
|
86
|
+
- Tuplets (triplets, quintuplets, etc.)
|
|
87
|
+
- Barlines (single, double, repeat)
|
|
88
|
+
- Ornaments (trills, mordents, turns)
|
|
89
|
+
- Fermatas (pause markings)
|
|
90
|
+
|
|
91
|
+
### Beyond Western Staff Notation (Future Exploration)
|
|
92
|
+
|
|
93
|
+
The Notation module could eventually support multiple notation systems:
|
|
94
|
+
|
|
95
|
+
**Traditional notation systems:**
|
|
96
|
+
- Western staff notation (current focus)
|
|
97
|
+
- Tablature (tab) - finger positions for guitar, bass, lute
|
|
98
|
+
- Shaped note notation - different note head shapes for scale degrees
|
|
99
|
+
- Drum notation - modified staff notation for percussion
|
|
100
|
+
|
|
101
|
+
**Alphanumeric and shorthand systems:**
|
|
102
|
+
- Lead sheet notation - melody with chord symbols
|
|
103
|
+
- Nashville Number System - scale degrees instead of chord names
|
|
104
|
+
- Roman numeral analysis - functional harmony labels
|
|
105
|
+
- ABC notation - text-based system for folk music
|
|
106
|
+
- Figured bass - Baroque-era interval shorthand
|
|
107
|
+
|
|
108
|
+
**Digital/computer formats:**
|
|
109
|
+
- MIDI - note events with pitch, velocity, timing
|
|
110
|
+
- MusicXML - XML-based interchange format
|
|
111
|
+
- Lilypond - text-based music engraving
|
|
112
|
+
- MEI (Music Encoding Initiative) - scholarly encoding
|
|
113
|
+
|
|
114
|
+
## Success Criteria
|
|
115
|
+
|
|
116
|
+
**Module Organization:**
|
|
117
|
+
- Clear separation between theory (Rudiment), notation (Notation), instruments (Instruments), and content (Content)
|
|
118
|
+
- All visual representation concerns organized under `HeadMusic::Notation`
|
|
119
|
+
- Documentation clearly explains what belongs in each module
|
|
120
|
+
|
|
121
|
+
**Code Quality:**
|
|
122
|
+
- All moved classes maintain existing functionality
|
|
123
|
+
- 90%+ test coverage maintained throughout
|
|
124
|
+
- No breaking changes for internal usage
|
|
125
|
+
- All TODO comments resolved
|
|
126
|
+
|
|
127
|
+
**Extensibility:**
|
|
128
|
+
- Module designed for future expansion (beams, ties, dynamics, etc.)
|
|
129
|
+
- Support for multiple output formats (Unicode, MusicXML, SVG)
|
|
130
|
+
- Clear patterns established for adding new notation features
|
|
131
|
+
|
|
132
|
+
**Developer Experience:**
|
|
133
|
+
- Clear guidance in CLAUDE.md about where notation features belong
|
|
134
|
+
- Easy to find and use notation-related classes
|
|
135
|
+
- Logical organization that matches mental model of music notation
|
metadata
CHANGED
|
@@ -1,29 +1,35 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: head_music
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 11.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Rob Head
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-12 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - ">="
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
19
|
version: '7.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '10'
|
|
20
23
|
type: :runtime
|
|
21
24
|
prerelease: false
|
|
22
25
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
26
|
requirements:
|
|
24
|
-
- - "
|
|
27
|
+
- - ">="
|
|
25
28
|
- !ruby/object:Gem::Version
|
|
26
29
|
version: '7.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '10'
|
|
27
33
|
- !ruby/object:Gem::Dependency
|
|
28
34
|
name: humanize
|
|
29
35
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -150,13 +156,12 @@ files:
|
|
|
150
156
|
- Gemfile
|
|
151
157
|
- Gemfile.lock
|
|
152
158
|
- LICENSE.txt
|
|
159
|
+
- MUSIC_THEORY.md
|
|
153
160
|
- README.md
|
|
154
161
|
- Rakefile
|
|
155
|
-
- TODO.md
|
|
156
162
|
- bin/check_instrument_consistency.rb
|
|
157
163
|
- bin/console
|
|
158
164
|
- bin/setup
|
|
159
|
-
- check_instrument_consistency.rb
|
|
160
165
|
- head_music.gemspec
|
|
161
166
|
- lib/head_music.rb
|
|
162
167
|
- lib/head_music/analysis/circle.rb
|
|
@@ -166,27 +171,40 @@ files:
|
|
|
166
171
|
- lib/head_music/analysis/diatonic_interval/parser.rb
|
|
167
172
|
- lib/head_music/analysis/diatonic_interval/semitones.rb
|
|
168
173
|
- lib/head_music/analysis/diatonic_interval/size.rb
|
|
174
|
+
- lib/head_music/analysis/dyad.rb
|
|
169
175
|
- lib/head_music/analysis/harmonic_interval.rb
|
|
176
|
+
- lib/head_music/analysis/interval_consonance.rb
|
|
170
177
|
- lib/head_music/analysis/interval_cycle.rb
|
|
171
178
|
- lib/head_music/analysis/melodic_interval.rb
|
|
172
179
|
- lib/head_music/analysis/motion.rb
|
|
173
180
|
- lib/head_music/analysis/pitch_class_set.rb
|
|
174
|
-
- lib/head_music/analysis/
|
|
181
|
+
- lib/head_music/analysis/pitch_collection.rb
|
|
175
182
|
- lib/head_music/analysis/sonority.rb
|
|
176
183
|
- lib/head_music/content/bar.rb
|
|
177
184
|
- lib/head_music/content/composition.rb
|
|
178
185
|
- lib/head_music/content/note.rb
|
|
179
186
|
- lib/head_music/content/placement.rb
|
|
180
187
|
- lib/head_music/content/position.rb
|
|
181
|
-
- lib/head_music/content/rhythmic_value.rb
|
|
182
188
|
- lib/head_music/content/staff.rb
|
|
183
189
|
- lib/head_music/content/voice.rb
|
|
190
|
+
- lib/head_music/instruments/alternate_tuning.rb
|
|
191
|
+
- lib/head_music/instruments/alternate_tunings.yml
|
|
184
192
|
- lib/head_music/instruments/instrument.rb
|
|
193
|
+
- lib/head_music/instruments/instrument_configuration.rb
|
|
194
|
+
- lib/head_music/instruments/instrument_configuration_option.rb
|
|
195
|
+
- lib/head_music/instruments/instrument_configurations.yml
|
|
185
196
|
- lib/head_music/instruments/instrument_families.yml
|
|
186
197
|
- lib/head_music/instruments/instrument_family.rb
|
|
187
198
|
- lib/head_music/instruments/instruments.yml
|
|
199
|
+
- lib/head_music/instruments/playing_technique.rb
|
|
200
|
+
- lib/head_music/instruments/playing_techniques.yml
|
|
201
|
+
- lib/head_music/instruments/score_order.rb
|
|
202
|
+
- lib/head_music/instruments/score_orders.yml
|
|
188
203
|
- lib/head_music/instruments/staff.rb
|
|
189
204
|
- lib/head_music/instruments/staff_scheme.rb
|
|
205
|
+
- lib/head_music/instruments/stringing.rb
|
|
206
|
+
- lib/head_music/instruments/stringing_course.rb
|
|
207
|
+
- lib/head_music/instruments/stringings.yml
|
|
190
208
|
- lib/head_music/instruments/variant.rb
|
|
191
209
|
- lib/head_music/locales/de.yml
|
|
192
210
|
- lib/head_music/locales/en.yml
|
|
@@ -196,35 +214,54 @@ files:
|
|
|
196
214
|
- lib/head_music/locales/it.yml
|
|
197
215
|
- lib/head_music/locales/ru.yml
|
|
198
216
|
- lib/head_music/named.rb
|
|
217
|
+
- lib/head_music/notation.rb
|
|
218
|
+
- lib/head_music/notation/musical_symbol.rb
|
|
219
|
+
- lib/head_music/notation/staff_mapping.rb
|
|
220
|
+
- lib/head_music/notation/staff_position.rb
|
|
199
221
|
- lib/head_music/rudiment/alteration.rb
|
|
222
|
+
- lib/head_music/rudiment/alterations.yml
|
|
223
|
+
- lib/head_music/rudiment/base.rb
|
|
200
224
|
- lib/head_music/rudiment/chromatic_interval.rb
|
|
201
225
|
- lib/head_music/rudiment/clef.rb
|
|
202
226
|
- lib/head_music/rudiment/clefs.yml
|
|
203
227
|
- lib/head_music/rudiment/consonance.rb
|
|
228
|
+
- lib/head_music/rudiment/diatonic_context.rb
|
|
229
|
+
- lib/head_music/rudiment/key.rb
|
|
204
230
|
- lib/head_music/rudiment/key_signature.rb
|
|
205
231
|
- lib/head_music/rudiment/key_signature/enharmonic_equivalence.rb
|
|
206
232
|
- lib/head_music/rudiment/letter_name.rb
|
|
207
233
|
- lib/head_music/rudiment/meter.rb
|
|
208
|
-
- lib/head_music/rudiment/
|
|
234
|
+
- lib/head_music/rudiment/mode.rb
|
|
235
|
+
- lib/head_music/rudiment/note.rb
|
|
209
236
|
- lib/head_music/rudiment/pitch.rb
|
|
210
237
|
- lib/head_music/rudiment/pitch/enharmonic_equivalence.rb
|
|
211
238
|
- lib/head_music/rudiment/pitch/octave_equivalence.rb
|
|
239
|
+
- lib/head_music/rudiment/pitch/parser.rb
|
|
212
240
|
- lib/head_music/rudiment/pitch_class.rb
|
|
213
241
|
- lib/head_music/rudiment/quality.rb
|
|
214
242
|
- lib/head_music/rudiment/reference_pitch.rb
|
|
215
243
|
- lib/head_music/rudiment/register.rb
|
|
244
|
+
- lib/head_music/rudiment/rest.rb
|
|
216
245
|
- lib/head_music/rudiment/rhythm.rb
|
|
246
|
+
- lib/head_music/rudiment/rhythmic_element.rb
|
|
217
247
|
- lib/head_music/rudiment/rhythmic_unit.rb
|
|
248
|
+
- lib/head_music/rudiment/rhythmic_unit/parser.rb
|
|
249
|
+
- lib/head_music/rudiment/rhythmic_units.yml
|
|
250
|
+
- lib/head_music/rudiment/rhythmic_value.rb
|
|
251
|
+
- lib/head_music/rudiment/rhythmic_value/parser.rb
|
|
218
252
|
- lib/head_music/rudiment/scale.rb
|
|
219
253
|
- lib/head_music/rudiment/scale_degree.rb
|
|
220
254
|
- lib/head_music/rudiment/scale_type.rb
|
|
221
255
|
- lib/head_music/rudiment/solmization.rb
|
|
222
256
|
- lib/head_music/rudiment/solmizations.yml
|
|
223
257
|
- lib/head_music/rudiment/spelling.rb
|
|
258
|
+
- lib/head_music/rudiment/tempo.rb
|
|
259
|
+
- lib/head_music/rudiment/tonal_context.rb
|
|
224
260
|
- lib/head_music/rudiment/tuning.rb
|
|
225
261
|
- lib/head_music/rudiment/tuning/just_intonation.rb
|
|
226
262
|
- lib/head_music/rudiment/tuning/meantone.rb
|
|
227
263
|
- lib/head_music/rudiment/tuning/pythagorean.rb
|
|
264
|
+
- lib/head_music/rudiment/unpitched_note.rb
|
|
228
265
|
- lib/head_music/style/analysis.rb
|
|
229
266
|
- lib/head_music/style/annotation.rb
|
|
230
267
|
- lib/head_music/style/guidelines/always_move.rb
|
|
@@ -265,17 +302,46 @@ files:
|
|
|
265
302
|
- lib/head_music/style/guides/fux_cantus_firmus.rb
|
|
266
303
|
- lib/head_music/style/guides/modern_cantus_firmus.rb
|
|
267
304
|
- lib/head_music/style/mark.rb
|
|
305
|
+
- lib/head_music/style/medieval_tradition.rb
|
|
306
|
+
- lib/head_music/style/modern_tradition.rb
|
|
307
|
+
- lib/head_music/style/renaissance_tradition.rb
|
|
308
|
+
- lib/head_music/style/tradition.rb
|
|
309
|
+
- lib/head_music/time.rb
|
|
310
|
+
- lib/head_music/time/clock_position.rb
|
|
311
|
+
- lib/head_music/time/conductor.rb
|
|
312
|
+
- lib/head_music/time/meter_event.rb
|
|
313
|
+
- lib/head_music/time/meter_map.rb
|
|
314
|
+
- lib/head_music/time/musical_position.rb
|
|
315
|
+
- lib/head_music/time/smpte_timecode.rb
|
|
316
|
+
- lib/head_music/time/tempo_event.rb
|
|
317
|
+
- lib/head_music/time/tempo_map.rb
|
|
318
|
+
- lib/head_music/utilities/case.rb
|
|
268
319
|
- lib/head_music/utilities/hash_key.rb
|
|
269
320
|
- lib/head_music/version.rb
|
|
270
|
-
-
|
|
271
|
-
- user_stories/backlog/
|
|
272
|
-
- user_stories/backlog/
|
|
273
|
-
- user_stories/
|
|
274
|
-
- user_stories/
|
|
275
|
-
- user_stories/
|
|
276
|
-
- user_stories/
|
|
277
|
-
- user_stories/
|
|
278
|
-
- user_stories/
|
|
321
|
+
- user_stories/active/string-pitches.md
|
|
322
|
+
- user_stories/backlog/notation-style.md
|
|
323
|
+
- user_stories/backlog/organizing-content.md
|
|
324
|
+
- user_stories/done/consonance-dissonance-classification.md
|
|
325
|
+
- user_stories/done/dyad-analysis.md
|
|
326
|
+
- user_stories/done/epic--score-order/PLAN.md
|
|
327
|
+
- user_stories/done/epic--score-order/band-score-order.md
|
|
328
|
+
- user_stories/done/epic--score-order/chamber-ensemble-score-order.md
|
|
329
|
+
- user_stories/done/epic--score-order/orchestral-score-order.md
|
|
330
|
+
- user_stories/done/expand-playing-techniques.md
|
|
331
|
+
- user_stories/done/handle-time.md
|
|
332
|
+
- user_stories/done/handle-time.rb
|
|
333
|
+
- user_stories/done/instrument-architecture.md
|
|
334
|
+
- user_stories/done/instrument-variant.md
|
|
335
|
+
- user_stories/done/move-musical-symbol-to-notation.md
|
|
336
|
+
- user_stories/done/move-staff-mapping-to-notation.md
|
|
337
|
+
- user_stories/done/move-staff-position-to-notation.md
|
|
338
|
+
- user_stories/done/notation-module-foundation.md
|
|
339
|
+
- user_stories/done/percussion_set.md
|
|
340
|
+
- user_stories/done/pitch-class-set-analysis.md
|
|
341
|
+
- user_stories/done/sonority-identification.md
|
|
342
|
+
- user_stories/done/superclass-for-note.md
|
|
343
|
+
- user_stories/epics/notation-module.md
|
|
344
|
+
- user_stories/visioning/agentic-daw.md
|
|
279
345
|
homepage: https://github.com/roberthead/head_music
|
|
280
346
|
licenses:
|
|
281
347
|
- MIT
|