head_music 12.4.0 → 12.5.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fc52f4684dac9eebefdc696f1d7f74094c98b26994905f7436cd757800880764
4
- data.tar.gz: 3087c3c68254f16768f875538d54028db26498b8f7dfb8e543e7d402582a4581
3
+ metadata.gz: 4a02a5ef281eecc76bcf953aa51c04078858b4588f2b85c0b1a4d1f4d88464d6
4
+ data.tar.gz: 5a31a94133ffbb97944524d75aedcbbfcbf7ce769fe6049559818a1940438442
5
5
  SHA512:
6
- metadata.gz: 6344e623f21eaad3c5c1dab093980062701d33ebca7f73050e211a7665afc2f81e49ef0de69e99f6a5c14d0d7797608cf35a88ca2c54c94590bbf404e575db9f
7
- data.tar.gz: d28d37afdaae4bb89cbe3de6e1b9957fc5d19d3e54a03f1d9145092e7e7727545751365bad050b96a2f26a5b8f56972ebcbd99f9ad985c11a27e6e95636c174b
6
+ metadata.gz: 369c959ae6f69cd609a65e4dee6b4aace0283d107132fec61f3b35afadfeb27d5386c60e969d88cfe626bdc0873e3c5dbed736513d93523e68c5dfca4127b6f1
7
+ data.tar.gz: 0f04cf766ce824188270ad00fe29866f652befcb0ff0d5c3eeaadd21e286b088bfb8883fe8687f94c0eaf2e216c5a2ee0377afc91756df18e9e587b5fef0eb41
data/CLAUDE.md CHANGED
@@ -93,17 +93,33 @@ The codebase follows a domain-driven design with clear module boundaries:
93
93
  4. **HeadMusic::Content** - Musical composition representation
94
94
  - Compositions, voices, bars, positions
95
95
  - Notes in context (pitch + duration + placement)
96
- - Temporal organization
97
-
98
- 5. **HeadMusic::Analysis** - Musical analysis tools
99
- - Intervals, chords, motion analysis
100
- - Harmonic and melodic analysis
96
+ - Uses HeadMusic::Time for temporal positioning
97
+
98
+ 5. **HeadMusic::Time** - Temporal infrastructure
99
+ - Clock positions (elapsed nanoseconds as source of truth)
100
+ - Musical positions (bars:beats:ticks:subticks notation)
101
+ - SMPTE timecode (hours:minutes:seconds:frames for video/audio sync)
102
+ - Conductor: converts between clock time and musical position
103
+ - Tempo and meter maps for tracking changes over time
104
+
105
+ 6. **HeadMusic::Analysis** - Musical analysis tools
106
+ - Diatonic intervals (quality + size)
107
+ - Harmonic and melodic interval analysis
108
+ - Consonance classification
109
+ - Motion analysis (parallel, similar, contrary, oblique)
101
110
  - Pitch class sets and collections
111
+ - Sonority and dyad analysis
112
+ - Circle (of fifths) and interval cycles
102
113
 
103
- 6. **HeadMusic::Style** - Composition rules and guidelines
104
- - Counterpoint rules
114
+ 7. **HeadMusic::Style** - Composition rules and guidelines
115
+ - Counterpoint rules (species counterpoint guides)
105
116
  - Voice leading guidelines
106
- - Style analysis
117
+ - Style analysis and annotations
118
+ - Historical traditions (medieval, renaissance, modern)
119
+
120
+ 8. **HeadMusic::Utilities** - Shared helpers
121
+ - Case conversion
122
+ - Hash key utilities
107
123
 
108
124
  ### Key Design Patterns
109
125
 
@@ -171,7 +187,7 @@ This project deliberately deprioritizes formal documentation in favor of clear,
171
187
  Domain reference materials live in `references/`.
172
188
  Consult these when implementing or modifying style guidelines:
173
189
 
174
- - `references/second-species-counterpoint.md` Pedagogical survey of second-species counterpoint rules from Fux through contemporary sources, with cross-source comparison and mapping to head_music's style guideline architecture.
190
+ Consult the files in `references/` when implementing or modifying style guidelines. These contain pedagogical surveys with cross-source comparisons mapped to head_music's style guideline architecture.
175
191
 
176
192
  ## Music theory and concepts
177
193
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- head_music (12.4.0)
4
+ head_music (12.5.0)
5
5
  activesupport (>= 7.0, < 10)
6
6
  humanize (~> 2.0)
7
7
  i18n (~> 1.8)
data/MUSIC_THEORY.md CHANGED
@@ -8,7 +8,7 @@ The rudiments of music theory are built up piece-by-piece.
8
8
  letter name
9
9
  - C, D, E, F, G, A, B, …
10
10
  - equivalent to:
11
- - do, re, mi, fa, sol, la, si/ti, …
11
+ - do, re, mi, fa, sol, la, si/ti, …
12
12
 
13
13
  alteration
14
14
  - sharp, flat, double sharp, double flat
@@ -44,7 +44,7 @@ rhythmic value (duration)
44
44
  - augmentation dots extend the duration of the rhythmic unit
45
45
  - one dot extends the rhythmic value 50%
46
46
  - two dots extend the rhythmic value 75%
47
- - two dots extend the rhythmic value 87.5%
47
+ - three dots extend the rhythmic value 87.5%
48
48
 
49
49
  rest
50
50
  - a rhythmic value that passes in silence
@@ -64,9 +64,9 @@ tied duration
64
64
  articulation
65
65
  - a category of expressions that modify how one or more notes are performed.
66
66
  - level of connection
67
- - staccatissimo - the most staccato, even shorted and more clipped
68
- - staccato – detatched (shortened to lease space between adjacent notes)
69
- - tenuto - held of the full value
67
+ - staccatissimo - the most staccato, even shorter and more clipped
68
+ - staccato – detached (shortened to leave space between adjacent notes)
69
+ - tenuto - held for the full value
70
70
  - legato - smoothly connected
71
71
  - emphasis
72
72
  - accent - play with emphasis or a stronger attack
@@ -76,45 +76,108 @@ articulation
76
76
  - breath mark
77
77
  - roll mark
78
78
 
79
- ## Instrument Families
79
+ ## Intervals
80
+
81
+ interval
82
+ - the distance between two pitches
83
+ - described by quality + size
84
+ - size: unison, second, third, fourth, fifth, sixth, seventh, octave, etc.
85
+ - quality: perfect, major, minor, augmented, diminished
86
+ - perfect intervals: unison, fourth, fifth, octave
87
+ - major/minor intervals: second, third, sixth, seventh
88
+ - a chromatic interval measures distance in half steps (semitones)
89
+ - a diatonic interval considers the letter names and key context
90
+
91
+ harmonic interval
92
+ - two pitches sounding simultaneously
93
+
94
+ melodic interval
95
+ - two pitches sounding in succession
96
+ - ascending or descending
97
+
98
+ ## Consonance and Dissonance
99
+
100
+ consonance
101
+ - intervals that sound stable and resolved
102
+ - perfect consonances: unison, perfect fifth, octave
103
+ - imperfect consonances: major/minor third, major/minor sixth
104
+
105
+ dissonance
106
+ - intervals that sound tense and seek resolution
107
+ - seconds, sevenths, tritone (augmented fourth / diminished fifth)
108
+ - perfect fourth considered "dissonance" requiring resolution in traditional counterpoint
80
109
 
81
- There are several ways that people talk about families and other categorizations of instruments. For example, the "string family" or "woodwind family", but also more specifically, the word family is applied more specifically (e.g. the "oboe family").
110
+ ## Scales and Modes
82
111
 
83
- ### Text Conversation with Brian Head (May 7, 2023)
112
+ scale
113
+ - an ordered collection of pitches spanning an octave
114
+ - defined by a pattern of whole steps and half steps
115
+ - named by its tonic (starting pitch) and type
84
116
 
85
- Brian Head, brother and faculty at USC Thornton School of Music
86
- https://music.usc.edu/brian-head/
117
+ major scale
118
+ - pattern: W-W-H-W-W-W-H (W = whole step, H = half step)
87
119
 
88
- Robert Head:
89
- Hey, Brian. You have a sec for a music question? I’m trying to come up with different terms for the way “family” is used. “Woodwind family” vs. “Oboe family”.
120
+ natural minor scale
121
+ - pattern: W-H-W-W-H-W-W
90
122
 
91
- Brian Head:
92
- Those are good, I’d say. In what context?
123
+ modes
124
+ - scales derived by starting on different degrees of a parent scale
125
+ - Ionian (= major), Dorian, Phrygian, Lydian, Mixolydian, Aeolian (= natural minor), Locrian
93
126
 
94
- Robert Head:
95
- In my software project, I’m trying to define those relationships. One is a section of the orchestra and the other is species of instrument. But usually people, in my experience, just say “family”.
96
- Is there some more precise terminology or adjective that can disambiguate those two terms?
127
+ ## Keys and Key Signatures
97
128
 
98
- Brian Head:
99
- Hmmm. Already “family” is informal. Blatter distinguishes between “choir” and “family” as in the trumpet family” within the “brass choir”.
129
+ key
130
+ - a tonal center (tonic) combined with a scale type (major or minor)
131
+ - example: "E-flat major", "A minor"
100
132
 
101
- Robert Head:
102
- Ah, yes! I do like that.
103
- But does it apply to percussion?
104
- “Section” is another candidate, but that gets used for string parts as well.
133
+ key signature
134
+ - the set of sharps or flats applied to a key
135
+ - written at the beginning of each staff line
136
+ - determines the default alteration for each letter name
105
137
 
106
- Brian Head:
107
- Strings and percussion probably don’t think of themselves as a choir, but if you’re mainly looking for a taxonomically consistent word, that’s the feat I can think of at the moment.
108
- Section is a good word, too, which easily flows between smaller and larger meanings.
138
+ ## Chords
109
139
 
110
- Robert Head:
111
- Obviously, the four “families” is a garbage way to classify all instruments and it really is more applicable to the orchestral context, so maybe “orchestra family” or “orchestra section”.
140
+ chord
141
+ - three or more pitches sounding together
142
+ - built by stacking intervals (typically thirds)
112
143
 
113
- Brian Head:
114
- I’d say that “family” is best used at the instrument level, as in “saxophone family”. Choir or section are better for larger collections. Still, both of those words connote membership in an orchestra. Or large ensemble. “Woodwinds” or “percussion” describe the class of instruments themselves.
144
+ triad
145
+ - a three-note chord: root, third, fifth
146
+ - types: major, minor, diminished, augmented
115
147
 
116
- Robert Head:
117
- Cool
148
+ seventh chord
149
+ - a four-note chord: root, third, fifth, seventh
150
+ - types: major seventh, dominant seventh, minor seventh, half-diminished, diminished
151
+
152
+ inversion
153
+ - a reordering of chord tones so that a note other than the root is the lowest
154
+ - root position, first inversion, second inversion (third inversion for seventh chords)
155
+
156
+ ## Voice Leading and Counterpoint
157
+
158
+ voice leading
159
+ - the way individual melodic lines (voices) move from one chord to the next
160
+ - smooth voice leading minimizes the distance each voice moves
161
+
162
+ types of motion between two voices
163
+ - parallel: both voices move in the same direction by the same interval
164
+ - similar: both voices move in the same direction by different intervals
165
+ - contrary: voices move in opposite directions
166
+ - oblique: one voice stays on the same pitch while the other moves
167
+
168
+ counterpoint
169
+ - the art of combining independent melodic lines
170
+ - species counterpoint is a pedagogical method with five species of increasing complexity
171
+ - first species: note against note
172
+ - second species: two notes against one
173
+ - third species: four notes against one
174
+ - fourth species: suspensions — notes tied across the barline create dissonances that resolve by step
175
+ - fifth species: florid counterpoint, combining all species
176
+
177
+ ## Instrument Families
118
178
 
119
- Action Item: Call them orchestra_section
120
- DONE
179
+ instrument family
180
+ - instruments grouped by shared characteristics
181
+ - broad families: strings, woodwinds, brass, percussion, keyboards, voices
182
+ - specific families: e.g. the oboe family (oboe, English horn, oboe d'amore)
183
+ - instruments within a family share similar tone production and playing technique
@@ -1,12 +1,107 @@
1
1
  # Module for style guidelines.
2
2
  module HeadMusic::Style::Guidelines; end
3
3
 
4
- # All rhythmic values are allowed in fifth species counterpoint.
5
- # This guideline is always adherent.
4
+ # Validates that counterpoint notes use only permitted rhythmic values for fifth species.
5
+ # Whole notes (final bar only), half notes, quarter notes, and paired stepwise eighth notes
6
+ # on weak beats are allowed. No dotted rhythms.
6
7
  class HeadMusic::Style::Guidelines::AllowedRhythmicValuesForFifthSpecies < HeadMusic::Style::Annotation
7
- MESSAGE = "All rhythmic values are allowed in fifth species."
8
+ MESSAGE = "Use only permitted rhythmic values: whole (final bar only), half, quarter, " \
9
+ "or paired stepwise eighth notes on weak beats."
10
+
11
+ PERMITTED_UNIT_NAMES = %w[whole half quarter eighth].freeze
8
12
 
9
13
  def marks
10
- []
14
+ [
15
+ *disallowed_unit_marks,
16
+ *dotted_rhythm_marks,
17
+ *whole_note_not_in_final_bar_marks,
18
+ *unpaired_eighth_note_marks,
19
+ *non_stepwise_eighth_note_marks,
20
+ *excess_eighth_note_pair_marks
21
+ ]
22
+ end
23
+
24
+ private
25
+
26
+ def disallowed_unit_marks
27
+ mark_each(notes.reject { |note| PERMITTED_UNIT_NAMES.include?(note.rhythmic_value.unit_name) })
28
+ end
29
+
30
+ def dotted_rhythm_marks
31
+ mark_each(notes.select { |note| note.rhythmic_value.dots > 0 })
32
+ end
33
+
34
+ def whole_note_not_in_final_bar_marks
35
+ mark_each(
36
+ notes
37
+ .select { |note| note.rhythmic_value.unit_name == "whole" }
38
+ .reject { |note| note.position.bar_number == final_bar_number }
39
+ )
40
+ end
41
+
42
+ def unpaired_eighth_note_marks
43
+ mark_each(eighth_notes.reject { |note| paired_eighth?(note) })
44
+ end
45
+
46
+ def non_stepwise_eighth_note_marks
47
+ mark_each(
48
+ eighth_notes
49
+ .select { |note| paired_eighth?(note) }
50
+ .reject { |note| stepwise_eighth?(note) }
51
+ )
52
+ end
53
+
54
+ def excess_eighth_note_pair_marks
55
+ bars_with_excess_eighth_pairs.flat_map do |bar_number|
56
+ mark_each(eighth_notes_in_bar(bar_number)[2..])
57
+ end
58
+ end
59
+
60
+ def mark_each(violating_notes)
61
+ violating_notes.map { |note| HeadMusic::Style::Mark.for(note) }
62
+ end
63
+
64
+ def eighth_notes
65
+ @eighth_notes ||= notes.select { |note| note.rhythmic_value.unit_name == "eighth" }
66
+ end
67
+
68
+ def paired_eighth?(note)
69
+ prev = preceding_note(note)
70
+ foll = following_note(note)
71
+ (prev && prev.rhythmic_value.unit_name == "eighth") ||
72
+ (foll && foll.rhythmic_value.unit_name == "eighth")
73
+ end
74
+
75
+ def stepwise_eighth?(note)
76
+ prev = preceding_note(note)
77
+ foll = following_note(note)
78
+ stepwise_from_prev = prev && HeadMusic::Analysis::MelodicInterval.new(prev, note).step?
79
+ stepwise_to_foll = foll && HeadMusic::Analysis::MelodicInterval.new(note, foll).step?
80
+ stepwise_from_prev || stepwise_to_foll
81
+ end
82
+
83
+ def bars_with_excess_eighth_pairs
84
+ eighth_notes
85
+ .group_by { |note| note.position.bar_number }
86
+ .select { |_bar, bar_eighths| bar_eighths.length > 2 }
87
+ .keys
88
+ end
89
+
90
+ def eighth_notes_in_bar(bar_number)
91
+ eighth_notes.select { |note| note.position.bar_number == bar_number }
92
+ end
93
+
94
+ def final_bar_number
95
+ last_note&.position&.bar_number
96
+ end
97
+
98
+ def preceding_note(note)
99
+ index = notes.index(note)
100
+ notes[index - 1] if index && index > 0
101
+ end
102
+
103
+ def following_note(note)
104
+ index = notes.index(note)
105
+ notes[index + 1] if index && index < notes.length - 1
11
106
  end
12
107
  end
@@ -1,9 +1,11 @@
1
1
  # Module for style guidelines.
2
2
  module HeadMusic::Style::Guidelines; end
3
3
 
4
- # A counterpoint guideline
4
+ # A counterpoint guideline.
5
+ # Dissonant downbeats are permitted only when the counterpoint note is a
6
+ # properly tied-over suspension (i.e., it began before the current CF note).
5
7
  class HeadMusic::Style::Guidelines::ConsonantDownbeats < HeadMusic::Style::Annotation
6
- MESSAGE = "Use consonant harmonic intervals on every downbeat."
8
+ MESSAGE = "Use consonant harmonic intervals on every downbeat (unless a tied suspension)."
7
9
 
8
10
  def marks
9
11
  dissonant_pairs.map do |dissonant_pair|
@@ -14,10 +16,21 @@ class HeadMusic::Style::Guidelines::ConsonantDownbeats < HeadMusic::Style::Annot
14
16
  private
15
17
 
16
18
  def dissonant_pairs
17
- dissonant_intervals.map(&:notes).compact
19
+ non_suspension_dissonant_intervals.map(&:notes).compact
20
+ end
21
+
22
+ def non_suspension_dissonant_intervals
23
+ dissonant_intervals.reject { |interval| tied_suspension?(interval) }
18
24
  end
19
25
 
20
26
  def dissonant_intervals
21
27
  downbeat_harmonic_intervals.select { |interval| interval.dissonance?(:two_part_harmony) }
22
28
  end
29
+
30
+ def tied_suspension?(interval)
31
+ cp_note = voice.note_at(interval.position)
32
+ return false unless cp_note
33
+
34
+ cp_note.position < interval.position
35
+ end
23
36
  end
@@ -0,0 +1,71 @@
1
+ # Module for style guidelines.
2
+ module HeadMusic::Style::Guidelines; end
3
+
4
+ # Shared detection of ornamental dissonance figures (nota cambiata, double neighbor).
5
+ # Include in any dissonance treatment guideline that needs to recognize these figures.
6
+ # Expects the including class to provide: #notes, #dissonant_with_cantus?(note)
7
+ module HeadMusic::Style::Guidelines::DissonanceFigureDetection
8
+ private
9
+
10
+ # Nota cambiata: a five-note figure where note 2 is dissonant,
11
+ # approached by step from note 1, leaps a third in the same direction to note 3,
12
+ # then notes 3-4-5 proceed stepwise in the opposite direction.
13
+ # Notes 1, 3, and 5 must be consonant with the CF.
14
+ def cambiata_dissonance?(note)
15
+ index = notes.index(note)
16
+ return false unless index
17
+
18
+ cambiata_as_note_2?(index)
19
+ end
20
+
21
+ def double_neighbor_member?(note)
22
+ index = notes.index(note)
23
+ return false unless index
24
+
25
+ double_neighbor_figure?(index, offset: 1) || double_neighbor_figure?(index, offset: 2)
26
+ end
27
+
28
+ def cambiata_as_note_2?(index)
29
+ return false if index < 1 || index + 3 > notes.length - 1
30
+
31
+ n1, n2, n3, n4, n5 = notes[index - 1, 5]
32
+
33
+ approach = melodic_interval_between(n1, n2)
34
+ leap = melodic_interval_between(n2, n3)
35
+ step_back_1 = melodic_interval_between(n3, n4)
36
+ step_back_2 = melodic_interval_between(n4, n5)
37
+
38
+ approach.step? &&
39
+ leap.number == 3 && approach.direction == leap.direction &&
40
+ step_back_1.step? && step_back_2.step? &&
41
+ step_back_1.direction != leap.direction &&
42
+ step_back_2.direction != leap.direction &&
43
+ consonant_with_cantus?(n1) && consonant_with_cantus?(n3) && consonant_with_cantus?(n5)
44
+ end
45
+
46
+ # Double neighbor: a four-note figure within one bar.
47
+ # Beats 1 and 4 are the same pitch (consonant), beats 2 and 3 are
48
+ # upper and lower neighbors connected by a leap of a third.
49
+ def double_neighbor_figure?(index, offset:)
50
+ start = index - offset
51
+ return false if start < 0 || start + 3 > notes.length - 1
52
+
53
+ n1, n2, n3, n4 = notes[start, 4]
54
+
55
+ approach = melodic_interval_between(n1, n2)
56
+ middle = melodic_interval_between(n2, n3)
57
+ departure = melodic_interval_between(n3, n4)
58
+
59
+ approach.step? && middle.number == 3 && departure.step? &&
60
+ n1.pitch == n4.pitch &&
61
+ consonant_with_cantus?(n1) && consonant_with_cantus?(n4)
62
+ end
63
+
64
+ def consonant_with_cantus?(note)
65
+ !dissonant_with_cantus?(note)
66
+ end
67
+
68
+ def melodic_interval_between(note1, note2)
69
+ HeadMusic::Analysis::MelodicInterval.new(note1, note2)
70
+ end
71
+ end
@@ -3,10 +3,13 @@ module HeadMusic::Style::Guidelines; end
3
3
 
4
4
  # Unified dissonance handling for mixed-species (florid) contexts.
5
5
  # - Strong beat dissonances must be properly prepared suspensions from a tie.
6
- # - Weak beat dissonances must be passing tones or neighbor tones.
6
+ # - Weak beat dissonances must be passing tones, neighbor tones, nota cambiata, or double neighbor figures.
7
7
  # - Tied notes dissonant at the new CF note must resolve by step to a consonance.
8
8
  class HeadMusic::Style::Guidelines::FloridDissonanceTreatment < HeadMusic::Style::Annotation
9
- MESSAGE = "Treat dissonances appropriately: passing tones on weak beats, proper suspension treatment for tied notes."
9
+ include HeadMusic::Style::Guidelines::DissonanceFigureDetection
10
+
11
+ MESSAGE = "Treat dissonances appropriately: passing tones, cambiata, or double neighbor " \
12
+ "on weak beats; proper suspension treatment for tied notes."
10
13
 
11
14
  def marks
12
15
  return [] unless cantus_firmus&.notes&.any?
@@ -24,7 +27,8 @@ class HeadMusic::Style::Guidelines::FloridDissonanceTreatment < HeadMusic::Style
24
27
  if on_strong_beat?(note)
25
28
  properly_treated_suspension?(note)
26
29
  else
27
- passing_tone?(note) || neighbor_tone?(note)
30
+ passing_tone?(note) || neighbor_tone?(note) ||
31
+ cambiata_dissonance?(note) || double_neighbor_member?(note)
28
32
  end
29
33
  end
30
34
 
@@ -42,11 +46,10 @@ class HeadMusic::Style::Guidelines::FloridDissonanceTreatment < HeadMusic::Style
42
46
  next_cp = following_note(note)
43
47
  return false unless next_cp
44
48
 
45
- melodic = HeadMusic::Analysis::MelodicInterval.new(note, next_cp)
49
+ melodic = melodic_interval_between(note, next_cp)
46
50
  return false unless melodic.step?
47
51
 
48
- # Resolution must be consonant
49
- !dissonant_with_cantus?(next_cp)
52
+ consonant_with_cantus?(next_cp)
50
53
  end
51
54
 
52
55
  def on_strong_beat?(note)
@@ -79,8 +82,8 @@ class HeadMusic::Style::Guidelines::FloridDissonanceTreatment < HeadMusic::Style
79
82
  next_note = following_note(note)
80
83
  return false unless prev_note && next_note
81
84
 
82
- approach = HeadMusic::Analysis::MelodicInterval.new(prev_note, note)
83
- departure = HeadMusic::Analysis::MelodicInterval.new(note, next_note)
85
+ approach = melodic_interval_between(prev_note, note)
86
+ departure = melodic_interval_between(note, next_note)
84
87
  approach.step? && departure.step? && (approach.direction == departure.direction) == same_direction
85
88
  end
86
89
 
@@ -1,14 +1,14 @@
1
1
  # Module for style guidelines.
2
2
  module HeadMusic::Style::Guidelines; end
3
3
 
4
- # Checks that the voice uses at least 2 different rhythmic value durations.
5
- # For full fifth species counterpoint.
4
+ # Checks that the voice uses at least 3 different rhythmic value durations.
5
+ # Florid counterpoint requires a genuine mixture of species textures.
6
6
  class HeadMusic::Style::Guidelines::MixedRhythmicValues < HeadMusic::Style::Annotation
7
- MESSAGE = "Use a variety of rhythmic values for an expressive florid line."
7
+ MESSAGE = "Use at least three different rhythmic values for a truly florid line."
8
8
 
9
9
  def marks
10
10
  return [] if notes.length < 2
11
- return [] if distinct_durations_count >= 2
11
+ return [] if distinct_durations_count >= 3
12
12
 
13
13
  [HeadMusic::Style::Mark.for(notes.first)]
14
14
  end
@@ -5,9 +5,9 @@ module HeadMusic::Style::Guidelines; end
5
5
  # A suspension has three parts:
6
6
  # 1. Preparation: The note is consonant with the current cantus firmus note.
7
7
  # 2. Suspension: The cantus firmus moves; the counterpoint sustains, becoming dissonant.
8
- # 3. Resolution: The counterpoint resolves by step (usually down) to a consonance.
8
+ # 3. Resolution: The counterpoint resolves by step down to a consonance.
9
9
  class HeadMusic::Style::Guidelines::SuspensionTreatment < HeadMusic::Style::Annotation
10
- MESSAGE = "Treat suspensions with proper preparation and stepwise resolution."
10
+ MESSAGE = "Treat suspensions with proper preparation and downward stepwise resolution."
11
11
 
12
12
  def marks
13
13
  return [] unless cantus_firmus&.notes&.any?
@@ -52,7 +52,7 @@ class HeadMusic::Style::Guidelines::SuspensionTreatment < HeadMusic::Style::Anno
52
52
  return false unless next_cp
53
53
 
54
54
  melodic = HeadMusic::Analysis::MelodicInterval.new(cp_note, next_cp)
55
- return false unless melodic.step?
55
+ return false unless melodic.step? && melodic.descending?
56
56
 
57
57
  resolution_interval = HeadMusic::Analysis::HarmonicInterval.new(cantus_firmus, voice, next_cp.position)
58
58
  resolution_interval.notes.length == 2 && resolution_interval.consonance?(:two_part_harmony)
@@ -5,6 +5,8 @@ module HeadMusic::Style::Guidelines; end
5
5
  # Every dissonant note on beats 2, 3, or 4 must be treated as a passing tone,
6
6
  # neighbor tone, nota cambiata, or double neighbor figure.
7
7
  class HeadMusic::Style::Guidelines::ThirdSpeciesDissonanceTreatment < HeadMusic::Style::Guidelines::WeakBeatDissonanceTreatment
8
+ include HeadMusic::Style::Guidelines::DissonanceFigureDetection
9
+
8
10
  MESSAGE = "Treat dissonances as passing tones, neighbor tones, cambiata, or double neighbor figures."
9
11
 
10
12
  private
@@ -17,69 +19,4 @@ class HeadMusic::Style::Guidelines::ThirdSpeciesDissonanceTreatment < HeadMusic:
17
19
  def neighbor_tone?(note)
18
20
  stepwise_figure?(note, same_direction: false)
19
21
  end
20
-
21
- # Nota cambiata: a five-note figure where note 2 is dissonant,
22
- # approached by step from note 1, leaps a third in the same direction to note 3,
23
- # then notes 3-4-5 proceed stepwise in the opposite direction.
24
- # Notes 1, 3, and 5 must be consonant with the CF.
25
- def cambiata_dissonance?(note)
26
- index = notes.index(note)
27
- return false unless index
28
-
29
- cambiata_as_note_2?(index)
30
- end
31
-
32
- def cambiata_as_note_2?(index)
33
- return false if index < 1 || index + 3 > notes.length - 1
34
-
35
- n1 = notes[index - 1]
36
- n2 = notes[index]
37
- n3 = notes[index + 1]
38
- n4 = notes[index + 2]
39
- n5 = notes[index + 3]
40
-
41
- approach = melodic_interval_between(n1, n2)
42
- leap = melodic_interval_between(n2, n3)
43
- step_back_1 = melodic_interval_between(n3, n4)
44
- step_back_2 = melodic_interval_between(n4, n5)
45
-
46
- approach.step? &&
47
- leap.number == 3 && approach.direction == leap.direction &&
48
- step_back_1.step? && step_back_2.step? &&
49
- step_back_1.direction != leap.direction &&
50
- step_back_2.direction != leap.direction &&
51
- consonant_with_cantus?(n1) && consonant_with_cantus?(n3) && consonant_with_cantus?(n5)
52
- end
53
-
54
- # Double neighbor: a four-note figure within one bar.
55
- # Beats 1 and 4 are the same pitch (consonant), beats 2 and 3 are
56
- # upper and lower neighbors connected by a leap of a third.
57
- def double_neighbor_member?(note)
58
- index = notes.index(note)
59
- return false unless index
60
-
61
- double_neighbor_figure?(index, offset: 1) || double_neighbor_figure?(index, offset: 2)
62
- end
63
-
64
- # Check for a double-neighbor four-note figure where the given index
65
- # is note number (offset + 1) in the figure. offset=1 means note 2,
66
- # offset=2 means note 3.
67
- def double_neighbor_figure?(index, offset:)
68
- start = index - offset
69
- return false if start < 0 || start + 3 > notes.length - 1
70
-
71
- n1, n2, n3, n4 = notes[start, 4]
72
-
73
- approach = melodic_interval_between(n1, n2)
74
- middle = melodic_interval_between(n2, n3)
75
- departure = melodic_interval_between(n3, n4)
76
-
77
- approach.step? && middle.number == 3 && departure.step? &&
78
- n1.pitch == n4.pitch &&
79
- consonant_with_cantus?(n1) && consonant_with_cantus?(n4)
80
- end
81
-
82
- def consonant_with_cantus?(note)
83
- !dissonant_with_cantus?(note)
84
- end
85
22
  end
@@ -8,8 +8,10 @@ class HeadMusic::Style::Guides::FifthSpeciesHarmony < HeadMusic::Style::Guides::
8
8
  HeadMusic::Style::Guidelines::AvoidCrossingVoices,
9
9
  HeadMusic::Style::Guidelines::AvoidOverlappingVoices,
10
10
  HeadMusic::Style::Guidelines::ConsonantDownbeats,
11
+ HeadMusic::Style::Guidelines::NoParallelPerfectAcrossBarline,
11
12
  HeadMusic::Style::Guidelines::NoParallelPerfectOnDownbeats,
12
13
  HeadMusic::Style::Guidelines::NoParallelPerfectWithSyncopation,
14
+ HeadMusic::Style::Guidelines::NoStrongBeatUnisons,
13
15
  HeadMusic::Style::Guidelines::PreferContraryMotion,
14
16
  HeadMusic::Style::Guidelines::PreferImperfect,
15
17
  HeadMusic::Style::Guidelines::FloridDissonanceTreatment,
@@ -11,10 +11,12 @@ class HeadMusic::Style::Guides::FifthSpeciesMelody < HeadMusic::Style::Guides::S
11
11
  HeadMusic::Style::Guidelines::FrequentDirectionChanges,
12
12
  HeadMusic::Style::Guidelines::LimitOctaveLeaps,
13
13
  HeadMusic::Style::Guidelines::MostlyConjunct,
14
+ HeadMusic::Style::Guidelines::NoteFillsFinalBar,
14
15
  HeadMusic::Style::Guidelines::PrepareOctaveLeaps,
15
16
  HeadMusic::Style::Guidelines::SingableIntervals,
16
17
  HeadMusic::Style::Guidelines::SingableRange,
17
18
  HeadMusic::Style::Guidelines::StartOnPerfectConsonance,
19
+ HeadMusic::Style::Guidelines::StepOutOfUnison,
18
20
  HeadMusic::Style::Guidelines::StepUpToFinalNote,
19
21
  HeadMusic::Style::Guidelines::AllowedRhythmicValuesForFifthSpecies,
20
22
  HeadMusic::Style::Guidelines::MixedRhythmicValues,
@@ -1,3 +1,3 @@
1
1
  module HeadMusic
2
- VERSION = "12.4.0"
2
+ VERSION = "12.5.0"
3
3
  end