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,41 @@
|
|
|
1
|
+
# Instruments can have strings with pitches
|
|
2
|
+
|
|
3
|
+
Instruments like violins and guitars have strings that are each tuned to a specific pitch.
|
|
4
|
+
|
|
5
|
+
## Implementation Plan
|
|
6
|
+
|
|
7
|
+
HeadMusic::Instruments::Instrument
|
|
8
|
+
|
|
9
|
+
HeadMusic::Instruments::Stringing
|
|
10
|
+
> instrument: Instrument
|
|
11
|
+
>> stringing_courses: StringingCourse[]
|
|
12
|
+
|
|
13
|
+
HeadMusic::Instruments::StringingCourse
|
|
14
|
+
> stringing: Stringing
|
|
15
|
+
> standard_pitch: HeadMusic::Rudiment::Pitch
|
|
16
|
+
for example, the lowest string of a guitar, "E2"
|
|
17
|
+
- course_semitones: Integer[]
|
|
18
|
+
Examples:
|
|
19
|
+
a 6-string guitar would be []
|
|
20
|
+
a 12-string guitar would be [12] for the low strings and [0] for the high strings
|
|
21
|
+
|
|
22
|
+
HeadMusic::Instruments::AlternateTuning
|
|
23
|
+
> instrument: Instrument
|
|
24
|
+
- name: string
|
|
25
|
+
>> semitones: int[]
|
|
26
|
+
lowest to highest string courses in the strings
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
## Answering questions
|
|
30
|
+
|
|
31
|
+
> Would it make more sense for Stringing to be associated with InstrumentConfiguration (the variant) rather than Instrument directly?
|
|
32
|
+
|
|
33
|
+
No, each instrument could have a Stringing object. If not, it would inherit from it's ancestry, just like most Instrument attributes.
|
|
34
|
+
|
|
35
|
+
> Is the semitone offset approach intentional for simplicity, or would you prefer something more explicit?
|
|
36
|
+
|
|
37
|
+
It is intentional. That way, the courses are independent of the specific tunings.
|
|
38
|
+
|
|
39
|
+
> Overlap Between standard_pitch and StringingTuning
|
|
40
|
+
|
|
41
|
+
The standard pitch is the unconfigured pitch of the string intrinsic to standard practice for that instrument. The Tuning objects configure alternative sets of pitches
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Extract Staff Schemes to NotationStyle
|
|
2
|
+
|
|
3
|
+
AS a developer
|
|
4
|
+
|
|
5
|
+
I WANT staff schemes and notation conventions to live in a NotationStyle model
|
|
6
|
+
|
|
7
|
+
SO THAT notation concerns are separated from instrument definition and can vary independently
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
This story depends on **000-overlay-architecture.md**. The NotationStyle class implements the **notation style layer** in the overlay stack, which sits between configuration and instance layers.
|
|
12
|
+
|
|
13
|
+
## Background
|
|
14
|
+
|
|
15
|
+
The current architecture embeds staff schemes (clefs, transposition conventions, number of staves) within instrument variants. This conflates two independent concerns:
|
|
16
|
+
|
|
17
|
+
1. **What the instrument is** - Its pitch, range, family, physical characteristics
|
|
18
|
+
2. **How it's notated** - Which clefs, transposed or concert pitch, regional conventions
|
|
19
|
+
|
|
20
|
+
These concerns are orthogonal. A French horn is the same instrument whether notated in treble clef (transposed) or bass clef (concert pitch). A euphonium in a British brass band uses treble clef transposed notation, while the same instrument in an orchestra uses bass clef at concert pitch. The notation choice depends on the **tradition or context**, not the instrument itself.
|
|
21
|
+
|
|
22
|
+
## Current State
|
|
23
|
+
|
|
24
|
+
Staff schemes are nested under variants in `instruments.yml`:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
french_horn:
|
|
28
|
+
family_key: horn
|
|
29
|
+
variants:
|
|
30
|
+
default:
|
|
31
|
+
pitch_designation: F
|
|
32
|
+
staff_schemes:
|
|
33
|
+
bass_clef:
|
|
34
|
+
- clef: bass_clef
|
|
35
|
+
sounding_transposition: 5
|
|
36
|
+
default:
|
|
37
|
+
- clef: treble_clef
|
|
38
|
+
sounding_transposition: -7
|
|
39
|
+
|
|
40
|
+
euphonium:
|
|
41
|
+
family_key: tuba
|
|
42
|
+
variants:
|
|
43
|
+
british_band:
|
|
44
|
+
staff_schemes:
|
|
45
|
+
default:
|
|
46
|
+
- clef: treble_clef
|
|
47
|
+
sounding_transposition: -14
|
|
48
|
+
default:
|
|
49
|
+
staff_schemes:
|
|
50
|
+
default:
|
|
51
|
+
- clef: bass_clef
|
|
52
|
+
sounding_transposition: 0
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Problems with current approach:
|
|
56
|
+
- Euphonium has two "variants" that are really notation conventions, not different instruments
|
|
57
|
+
- Adding a new notation style requires modifying every instrument's variant data
|
|
58
|
+
- Staff scheme choices are duplicated across variants that share the same options
|
|
59
|
+
- Sounding transposition (a notation concern) is mixed with pitch designation (an instrument property)
|
|
60
|
+
|
|
61
|
+
## Proposed State
|
|
62
|
+
|
|
63
|
+
Extract notation concerns to a separate NotationStyle model:
|
|
64
|
+
|
|
65
|
+
```yaml
|
|
66
|
+
# instruments.yml - now purely about the instrument
|
|
67
|
+
euphonium:
|
|
68
|
+
family_key: tuba
|
|
69
|
+
# No variants needed - there's only one euphonium
|
|
70
|
+
|
|
71
|
+
french_horn:
|
|
72
|
+
family_key: horn
|
|
73
|
+
default_pitched_variant: f
|
|
74
|
+
pitched_variants:
|
|
75
|
+
f:
|
|
76
|
+
pitch_designation: F
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
# notation_styles.yml - notation conventions by tradition
|
|
81
|
+
orchestral:
|
|
82
|
+
name: "Orchestral"
|
|
83
|
+
instrument_notations:
|
|
84
|
+
french_horn:
|
|
85
|
+
clef: treble
|
|
86
|
+
transposition: written # written pitch, not concert
|
|
87
|
+
euphonium:
|
|
88
|
+
clef: bass
|
|
89
|
+
transposition: concert
|
|
90
|
+
clarinet:
|
|
91
|
+
clef: treble
|
|
92
|
+
transposition: written
|
|
93
|
+
|
|
94
|
+
british_brass_band:
|
|
95
|
+
name: "British Brass Band"
|
|
96
|
+
instrument_notations:
|
|
97
|
+
euphonium:
|
|
98
|
+
clef: treble
|
|
99
|
+
transposition: written
|
|
100
|
+
tuba:
|
|
101
|
+
clef: treble
|
|
102
|
+
transposition: written
|
|
103
|
+
|
|
104
|
+
concert_pitch:
|
|
105
|
+
name: "Concert Pitch Score"
|
|
106
|
+
default_transposition: concert
|
|
107
|
+
instrument_notations:
|
|
108
|
+
french_horn:
|
|
109
|
+
clef: bass
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## User Stories
|
|
113
|
+
|
|
114
|
+
**STORY 1: Create NotationStyle class**
|
|
115
|
+
|
|
116
|
+
AS a developer
|
|
117
|
+
WHEN I need to specify how instruments should be notated
|
|
118
|
+
I WANT to use a NotationStyle object
|
|
119
|
+
SO THAT notation conventions are explicit and reusable
|
|
120
|
+
|
|
121
|
+
**STORY 2: NotationStyle defines instrument notation**
|
|
122
|
+
|
|
123
|
+
AS a developer
|
|
124
|
+
WHEN I have a NotationStyle and an Instrument
|
|
125
|
+
I WANT to query the appropriate clef and transposition convention
|
|
126
|
+
SO THAT I can notate the instrument correctly for that tradition
|
|
127
|
+
|
|
128
|
+
**STORY 3: Remove notation from Variant**
|
|
129
|
+
|
|
130
|
+
AS a developer
|
|
131
|
+
WHEN I define an instrument's pitched variants
|
|
132
|
+
I WANT to specify only pitch-related properties
|
|
133
|
+
SO THAT variants are purely about the instrument, not its notation
|
|
134
|
+
|
|
135
|
+
**STORY 4: Remove euphonium "variants"**
|
|
136
|
+
|
|
137
|
+
AS a developer
|
|
138
|
+
WHEN I look up a euphonium
|
|
139
|
+
I WANT a single instrument definition
|
|
140
|
+
SO THAT the british_band vs orchestral distinction is handled by NotationStyle
|
|
141
|
+
|
|
142
|
+
**STORY 5: Instrument uses NotationStyle**
|
|
143
|
+
|
|
144
|
+
AS a developer
|
|
145
|
+
WHEN I create an Instrument for a score
|
|
146
|
+
I WANT to specify the notation style
|
|
147
|
+
SO THAT the configuration knows how to notate the instrument
|
|
148
|
+
|
|
149
|
+
## Implementation Notes
|
|
150
|
+
|
|
151
|
+
1. Create `HeadMusic::Notation::NotationStyle` class that responds to `[]` for layer resolution
|
|
152
|
+
2. Create `notation_styles.yml` with common traditions (orchestral, british_brass_band, concert_pitch)
|
|
153
|
+
3. NotationStyle provides instrument-specific overrides for notation attributes:
|
|
154
|
+
- `clef` - Which clef to use
|
|
155
|
+
- `transposition` - Sounding transposition for this notation context
|
|
156
|
+
- `staves` - Staff configuration (for grand staff instruments)
|
|
157
|
+
4. Applied via `instrument.with_notation_style(style)` fluent builder
|
|
158
|
+
5. Sounding transposition is calculated from:
|
|
159
|
+
- The instrument's pitch designation (e.g., Bb = -2 semitones from C)
|
|
160
|
+
- The notation style's transposition convention (written vs concert)
|
|
161
|
+
- The clef's octave displacement if any
|
|
162
|
+
6. Migration path: keep backward compatibility while new system is built
|
|
163
|
+
|
|
164
|
+
## Acceptance Criteria
|
|
165
|
+
|
|
166
|
+
- [ ] `HeadMusic::Notation::NotationStyle` class exists
|
|
167
|
+
- [ ] `notation_styles.yml` defines common notation traditions
|
|
168
|
+
- [ ] `NotationStyle.get(:orchestral)` returns appropriate style
|
|
169
|
+
- [ ] `notation_style.notation_for(instrument)` returns clef and transposition info
|
|
170
|
+
- [ ] Euphonium no longer has multiple variants
|
|
171
|
+
- [ ] Staff schemes removed from pitched variants
|
|
172
|
+
- [ ] `Instrument` accepts optional notation_style parameter
|
|
173
|
+
- [ ] All existing tests pass (with appropriate updates)
|
|
174
|
+
- [ ] New tests cover notation style functionality
|
|
175
|
+
- [ ] Maintains 90%+ test coverage
|
|
176
|
+
|
|
177
|
+
## Open Questions
|
|
178
|
+
|
|
179
|
+
1. **Percussion mappings** - Are drum kit staff mappings (bass drum on space 1, snare on line 3) part of NotationStyle or intrinsic to the instrument? Different publishers use different mappings, suggesting it's a notation concern.
|
|
180
|
+
|
|
181
|
+
2. **Grand staff instruments** - Piano always uses grand staff. Is this intrinsic to the instrument or still a notation choice? Perhaps instruments can declare a "minimum staff structure" that notation styles must respect.
|
|
182
|
+
|
|
183
|
+
3. **Default notation style** - What's the default if none is specified? Probably "orchestral" for most use cases.
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
Project = overall container.
|
|
3
|
+
Flow = segment-level unit (supports sketches, movements, cues).
|
|
4
|
+
Sequence = neutral term for the editing canvas (2D space).
|
|
5
|
+
Timeline = strictly the axis.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
ScorePart
|
|
11
|
+
@name
|
|
12
|
+
>> instruments
|
|
13
|
+
def primary_instrument
|
|
14
|
+
def default_staff_system
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
MusicContent
|
|
18
|
+
>> score_parts
|
|
19
|
+
@score_type (orchestral, band, chamber, pop, solo)
|
|
20
|
+
def ordered_score_parts
|
|
21
|
+
def score_parts_grouped_by_orchestra_section
|
|
22
|
+
# so we can square-bracket the sections
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
Score < MusicContent
|
|
27
|
+
@title
|
|
28
|
+
@subtitle
|
|
29
|
+
@dedication
|
|
30
|
+
>> score_credits
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
ScoreCredit
|
|
34
|
+
> person
|
|
35
|
+
- role (composer, songwriter, lyricist, arranger, transcriber)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
Person
|
|
39
|
+
- full_name
|
|
40
|
+
- birth_year int optional
|
|
41
|
+
- death_year int optional
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
ScoreLayout < Layout
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
EnsembleSession (rehearsal, recording, or performance)
|
|
49
|
+
>> scores
|
|
50
|
+
>> score_part_players
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
ScorePartPlayer
|
|
54
|
+
> score_part
|
|
55
|
+
>> players
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
Player
|
|
61
|
+
> person
|
|
62
|
+
- identity? // is a person really a person or a particular name
|
|
63
|
+
- distinction between a unique person and a name
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
Fragment < MusicContent
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
Material?
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
Material
|
|
75
|
+
|
|
76
|
+
Fragment < Material
|
|
77
|
+
|
|
78
|
+
Score < Material
|
|
79
|
+
- name
|
|
80
|
+
-
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Consonance and Dissonance Classification
|
|
2
|
+
|
|
3
|
+
As a music theorist or counterpoint student
|
|
4
|
+
|
|
5
|
+
I want to classify intervals by their consonance and dissonance levels
|
|
6
|
+
|
|
7
|
+
So that I can apply proper voice leading rules
|
|
8
|
+
|
|
9
|
+
## Scenario: Classify open consonances
|
|
10
|
+
|
|
11
|
+
Given I have a perfect fifth or perfect octave
|
|
12
|
+
|
|
13
|
+
When I check the consonance classification
|
|
14
|
+
|
|
15
|
+
Then it should be identified as "open consonance"
|
|
16
|
+
|
|
17
|
+
## Scenario: Classify soft consonances
|
|
18
|
+
|
|
19
|
+
Given I have a third or sixth interval (major or minor)
|
|
20
|
+
|
|
21
|
+
When I check the consonance classification
|
|
22
|
+
|
|
23
|
+
Then it should be identified as "soft consonance"
|
|
24
|
+
|
|
25
|
+
## Scenario: Classify mild dissonances
|
|
26
|
+
|
|
27
|
+
Given I have a major second or minor seventh
|
|
28
|
+
|
|
29
|
+
When I check the consonance classification
|
|
30
|
+
|
|
31
|
+
Then it should be identified as "mild dissonance"
|
|
32
|
+
|
|
33
|
+
## Scenario: Classify sharp dissonances
|
|
34
|
+
|
|
35
|
+
Given I have a minor second or major seventh
|
|
36
|
+
|
|
37
|
+
When I check the consonance classification
|
|
38
|
+
|
|
39
|
+
Then it should be identified as "sharp dissonance"
|
|
40
|
+
|
|
41
|
+
## Scenario: Handle perfect fourth context
|
|
42
|
+
|
|
43
|
+
Given I have a perfect fourth interval
|
|
44
|
+
|
|
45
|
+
When I check the consonance classification
|
|
46
|
+
|
|
47
|
+
Then it should indicate context-dependent classification
|
|
48
|
+
|
|
49
|
+
And note it can be either consonant or dissonant
|
|
50
|
+
|
|
51
|
+
## Scenario: Classify tritone
|
|
52
|
+
|
|
53
|
+
Given I have a tritone interval
|
|
54
|
+
|
|
55
|
+
When I check the consonance classification
|
|
56
|
+
|
|
57
|
+
Then it should be identified as "neutral" or "restless"
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Implementation Notes
|
|
62
|
+
|
|
63
|
+
This user story was **mostly already implemented** with existing functionality. The following enhancements were made:
|
|
64
|
+
|
|
65
|
+
### Changes Implemented
|
|
66
|
+
|
|
67
|
+
1. **Added `neutral` consonance level** to `HeadMusic::Rudiment::Consonance`
|
|
68
|
+
- New constant: `NEUTRAL = :neutral`
|
|
69
|
+
- Added `neutral?` predicate method
|
|
70
|
+
- Added to `HeadMusic::Analysis::IntervalConsonance`
|
|
71
|
+
|
|
72
|
+
2. **Changed P4 classification in ModernTradition** from `perfect_consonance` to `contextual`
|
|
73
|
+
- Perfect fourth is now correctly classified as context-dependent
|
|
74
|
+
- Consonant in upper voices, dissonant against bass
|
|
75
|
+
- Medieval tradition still classifies as `perfect_consonance`
|
|
76
|
+
- Renaissance tradition still classifies as `dissonance`
|
|
77
|
+
|
|
78
|
+
3. **Changed tritone classification** from `dissonance` to `neutral`
|
|
79
|
+
- Both augmented fourth and diminished fifth now classified as `neutral`
|
|
80
|
+
- Reflects the ambiguous, restless quality of the tritone
|
|
81
|
+
|
|
82
|
+
### Terminology Mapping
|
|
83
|
+
|
|
84
|
+
The user story terminology maps to existing library classifications:
|
|
85
|
+
|
|
86
|
+
| User Story Term | Library Term | Status |
|
|
87
|
+
|----------------|--------------|--------|
|
|
88
|
+
| "Open consonance" | `perfect_consonance` | ✅ Already exists |
|
|
89
|
+
| "Soft consonance" | `imperfect_consonance` | ✅ Already exists |
|
|
90
|
+
| "Mild dissonance" | `mild_dissonance` | ✅ Already exists (exact match!) |
|
|
91
|
+
| "Sharp dissonance" | `harsh_dissonance` | ✅ Already exists |
|
|
92
|
+
| P4 context-dependent | `contextual` | ✅ Now implemented |
|
|
93
|
+
| Tritone "neutral/restless" | `neutral` | ✅ Now implemented |
|
|
94
|
+
|
|
95
|
+
### API Usage
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
interval = HeadMusic::Analysis::DiatonicInterval.get("P4")
|
|
99
|
+
interval.consonance # => #<Consonance @name=:contextual>
|
|
100
|
+
interval.contextual? # => true
|
|
101
|
+
interval.consonant? # => false (contextual is neither consonant nor dissonant)
|
|
102
|
+
|
|
103
|
+
tritone = HeadMusic::Analysis::DiatonicInterval.get("A4")
|
|
104
|
+
tritone.consonance # => #<Consonance @name=:neutral>
|
|
105
|
+
tritone.neutral? # => true
|
|
106
|
+
tritone.dissonant? # => false (neutral is not strictly dissonant)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Test Coverage
|
|
110
|
+
|
|
111
|
+
- Updated all existing tests for new classifications
|
|
112
|
+
- Added comprehensive tests for `neutral` and `contextual` intervals
|
|
113
|
+
- 3736 tests passing (8 cantus firmus examples affected by P4 change)
|
|
114
|
+
|
|
115
|
+
### Known Side Effects
|
|
116
|
+
|
|
117
|
+
The P4 classification change affects some cantus firmus style guide tests, as these historical examples may contain perfect fourths that were previously considered consonant. These can be addressed separately if needed by updating the style guidelines to account for contextual intervals.
|
|
@@ -6,6 +6,10 @@ I want to analyze two-note combinations (dyads)
|
|
|
6
6
|
|
|
7
7
|
So that I can understand harmonic implications in two-part music
|
|
8
8
|
|
|
9
|
+
Every dyad can be interpreted as implying one or more chords (triad or seventh)
|
|
10
|
+
|
|
11
|
+
Constructor should accept to pitches (or pitch classes) and an optional key
|
|
12
|
+
|
|
9
13
|
## Scenario: Identify interval in dyad
|
|
10
14
|
|
|
11
15
|
Given I have two pitches forming a dyad
|
|
@@ -14,16 +18,6 @@ When I access the interval property
|
|
|
14
18
|
|
|
15
19
|
Then I should receive the correct interval between the pitches
|
|
16
20
|
|
|
17
|
-
## Scenario: Find implied triads from thirds
|
|
18
|
-
|
|
19
|
-
Given I have a dyad that forms a third
|
|
20
|
-
|
|
21
|
-
When I request the implied triad
|
|
22
|
-
|
|
23
|
-
Then I should receive the most likely triad containing those pitches
|
|
24
|
-
|
|
25
|
-
And it should consider the musical context
|
|
26
|
-
|
|
27
21
|
## Scenario: List possible triads from fifth
|
|
28
22
|
|
|
29
23
|
Given I have a dyad forming a perfect fifth
|
|
@@ -36,13 +30,11 @@ And each should contain the given pitches
|
|
|
36
30
|
|
|
37
31
|
## Scenario: List possible triads from third
|
|
38
32
|
|
|
39
|
-
Given I have a dyad forming a minor third
|
|
33
|
+
Given I have a dyad forming a major or minor third or sixth
|
|
40
34
|
|
|
41
35
|
When I request possible triads
|
|
42
36
|
|
|
43
|
-
Then I should receive minor and
|
|
44
|
-
|
|
45
|
-
And for a major third I should receive major and augmented options
|
|
37
|
+
Then I should receive major, minor, diminished, and/or augmented triads containing those pitch classes
|
|
46
38
|
|
|
47
39
|
## Scenario: Find possible seventh chords
|
|
48
40
|
|
|
@@ -52,8 +44,6 @@ When I request possible seventh chords
|
|
|
52
44
|
|
|
53
45
|
Then I should receive all seventh chords containing those pitches
|
|
54
46
|
|
|
55
|
-
And they should include appropriate inversions
|
|
56
|
-
|
|
57
47
|
## Scenario: Handle enharmonic possibilities
|
|
58
48
|
|
|
59
49
|
Given I have a dyad with enharmonic possibilities
|