head_music 8.3.0 → 11.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ci.yml +9 -3
  3. data/CHANGELOG.md +71 -0
  4. data/CLAUDE.md +62 -25
  5. data/Gemfile +7 -1
  6. data/Gemfile.lock +91 -3
  7. data/MUSIC_THEORY.md +120 -0
  8. data/README.md +18 -0
  9. data/Rakefile +7 -2
  10. data/head_music.gemspec +1 -1
  11. data/lib/head_music/analysis/diatonic_interval.rb +29 -27
  12. data/lib/head_music/analysis/dyad.rb +229 -0
  13. data/lib/head_music/analysis/interval_consonance.rb +51 -0
  14. data/lib/head_music/analysis/melodic_interval.rb +1 -1
  15. data/lib/head_music/analysis/pitch_class_set.rb +111 -14
  16. data/lib/head_music/analysis/{pitch_set.rb → pitch_collection.rb} +11 -5
  17. data/lib/head_music/analysis/sonority.rb +50 -12
  18. data/lib/head_music/content/note.rb +1 -1
  19. data/lib/head_music/content/placement.rb +1 -1
  20. data/lib/head_music/content/position.rb +1 -1
  21. data/lib/head_music/content/voice.rb +1 -1
  22. data/lib/head_music/instruments/alternate_tuning.rb +102 -0
  23. data/lib/head_music/instruments/alternate_tunings.yml +78 -0
  24. data/lib/head_music/instruments/instrument.rb +231 -72
  25. data/lib/head_music/instruments/instrument_configuration.rb +66 -0
  26. data/lib/head_music/instruments/instrument_configuration_option.rb +38 -0
  27. data/lib/head_music/instruments/instrument_configurations.yml +288 -0
  28. data/lib/head_music/instruments/instrument_families.yml +77 -0
  29. data/lib/head_music/instruments/instrument_family.rb +15 -5
  30. data/lib/head_music/instruments/instruments.yml +795 -965
  31. data/lib/head_music/instruments/playing_technique.rb +75 -0
  32. data/lib/head_music/instruments/playing_techniques.yml +826 -0
  33. data/lib/head_music/instruments/score_order.rb +136 -0
  34. data/lib/head_music/instruments/score_orders.yml +130 -0
  35. data/lib/head_music/instruments/staff.rb +61 -1
  36. data/lib/head_music/instruments/staff_scheme.rb +6 -4
  37. data/lib/head_music/instruments/stringing.rb +115 -0
  38. data/lib/head_music/instruments/stringing_course.rb +58 -0
  39. data/lib/head_music/instruments/stringings.yml +168 -0
  40. data/lib/head_music/instruments/variant.rb +6 -1
  41. data/lib/head_music/locales/de.yml +29 -0
  42. data/lib/head_music/locales/en.yml +106 -0
  43. data/lib/head_music/locales/es.yml +29 -0
  44. data/lib/head_music/locales/fr.yml +29 -0
  45. data/lib/head_music/locales/it.yml +29 -0
  46. data/lib/head_music/locales/ru.yml +29 -0
  47. data/lib/head_music/{rudiment → notation}/musical_symbol.rb +3 -3
  48. data/lib/head_music/notation/staff_mapping.rb +70 -0
  49. data/lib/head_music/notation/staff_position.rb +62 -0
  50. data/lib/head_music/notation.rb +7 -0
  51. data/lib/head_music/rudiment/alteration.rb +34 -49
  52. data/lib/head_music/rudiment/alterations.yml +32 -0
  53. data/lib/head_music/rudiment/base.rb +9 -0
  54. data/lib/head_music/rudiment/chromatic_interval.rb +4 -7
  55. data/lib/head_music/rudiment/clef.rb +2 -2
  56. data/lib/head_music/rudiment/consonance.rb +39 -5
  57. data/lib/head_music/rudiment/diatonic_context.rb +25 -0
  58. data/lib/head_music/rudiment/key.rb +77 -0
  59. data/lib/head_music/rudiment/key_signature/enharmonic_equivalence.rb +1 -1
  60. data/lib/head_music/rudiment/key_signature.rb +21 -8
  61. data/lib/head_music/rudiment/letter_name.rb +3 -3
  62. data/lib/head_music/rudiment/meter.rb +19 -9
  63. data/lib/head_music/rudiment/mode.rb +92 -0
  64. data/lib/head_music/rudiment/note.rb +112 -0
  65. data/lib/head_music/rudiment/pitch/parser.rb +52 -0
  66. data/lib/head_music/rudiment/pitch.rb +5 -6
  67. data/lib/head_music/rudiment/pitch_class.rb +1 -1
  68. data/lib/head_music/rudiment/quality.rb +1 -1
  69. data/lib/head_music/rudiment/reference_pitch.rb +1 -1
  70. data/lib/head_music/rudiment/register.rb +4 -1
  71. data/lib/head_music/rudiment/rest.rb +36 -0
  72. data/lib/head_music/rudiment/rhythmic_element.rb +53 -0
  73. data/lib/head_music/rudiment/rhythmic_unit/parser.rb +86 -0
  74. data/lib/head_music/rudiment/rhythmic_unit.rb +13 -5
  75. data/lib/head_music/rudiment/rhythmic_units.yml +80 -0
  76. data/lib/head_music/rudiment/rhythmic_value/parser.rb +77 -0
  77. data/lib/head_music/{content → rudiment}/rhythmic_value.rb +23 -5
  78. data/lib/head_music/rudiment/scale.rb +4 -5
  79. data/lib/head_music/rudiment/scale_degree.rb +1 -1
  80. data/lib/head_music/rudiment/scale_type.rb +9 -3
  81. data/lib/head_music/rudiment/solmization.rb +1 -1
  82. data/lib/head_music/rudiment/spelling.rb +8 -4
  83. data/lib/head_music/rudiment/tempo.rb +85 -0
  84. data/lib/head_music/rudiment/tonal_context.rb +35 -0
  85. data/lib/head_music/rudiment/tuning/just_intonation.rb +0 -39
  86. data/lib/head_music/rudiment/tuning/meantone.rb +0 -39
  87. data/lib/head_music/rudiment/tuning/pythagorean.rb +0 -39
  88. data/lib/head_music/rudiment/tuning.rb +21 -1
  89. data/lib/head_music/rudiment/unpitched_note.rb +62 -0
  90. data/lib/head_music/style/guidelines/consonant_climax.rb +2 -2
  91. data/lib/head_music/style/medieval_tradition.rb +26 -0
  92. data/lib/head_music/style/modern_tradition.rb +31 -0
  93. data/lib/head_music/style/renaissance_tradition.rb +26 -0
  94. data/lib/head_music/style/tradition.rb +21 -0
  95. data/lib/head_music/time/clock_position.rb +84 -0
  96. data/lib/head_music/time/conductor.rb +264 -0
  97. data/lib/head_music/time/meter_event.rb +37 -0
  98. data/lib/head_music/time/meter_map.rb +173 -0
  99. data/lib/head_music/time/musical_position.rb +188 -0
  100. data/lib/head_music/time/smpte_timecode.rb +164 -0
  101. data/lib/head_music/time/tempo_event.rb +40 -0
  102. data/lib/head_music/time/tempo_map.rb +187 -0
  103. data/lib/head_music/time.rb +32 -0
  104. data/lib/head_music/utilities/case.rb +27 -0
  105. data/lib/head_music/utilities/hash_key.rb +34 -2
  106. data/lib/head_music/version.rb +1 -1
  107. data/lib/head_music.rb +71 -22
  108. data/user_stories/active/string-pitches.md +41 -0
  109. data/user_stories/backlog/notation-style.md +183 -0
  110. data/user_stories/backlog/organizing-content.md +80 -0
  111. data/user_stories/done/consonance-dissonance-classification.md +117 -0
  112. data/user_stories/{backlog → done}/dyad-analysis.md +6 -16
  113. data/user_stories/done/epic--score-order/PLAN.md +244 -0
  114. data/user_stories/done/expand-playing-techniques.md +38 -0
  115. data/user_stories/done/handle-time.md +7 -0
  116. data/user_stories/done/handle-time.rb +163 -0
  117. data/user_stories/done/instrument-architecture.md +238 -0
  118. data/user_stories/done/instrument-variant.md +65 -0
  119. data/user_stories/done/move-musical-symbol-to-notation.md +161 -0
  120. data/user_stories/done/move-staff-mapping-to-notation.md +158 -0
  121. data/user_stories/done/move-staff-position-to-notation.md +141 -0
  122. data/user_stories/done/notation-module-foundation.md +102 -0
  123. data/user_stories/done/percussion_set.md +260 -0
  124. data/user_stories/done/sonority-identification.md +37 -0
  125. data/user_stories/done/superclass-for-note.md +30 -0
  126. data/user_stories/epics/notation-module.md +135 -0
  127. data/user_stories/visioning/agentic-daw.md +2 -0
  128. metadata +84 -18
  129. data/TODO.md +0 -109
  130. data/check_instrument_consistency.rb +0 -0
  131. data/test_translations.rb +0 -15
  132. data/user_stories/backlog/consonance-dissonance-classification.md +0 -57
  133. data/user_stories/backlog/pitch-set-classification.md +0 -62
  134. data/user_stories/backlog/sonority-identification.md +0 -47
  135. /data/user_stories/{backlog → done/epic--score-order}/band-score-order.md +0 -0
  136. /data/user_stories/{backlog → done/epic--score-order}/chamber-ensemble-score-order.md +0 -0
  137. /data/user_stories/{backlog → done/epic--score-order}/orchestral-score-order.md +0 -0
  138. /data/user_stories/{backlog → done}/pitch-class-set-analysis.md +0 -0
@@ -0,0 +1,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 diminished triad options
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