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 +4 -4
- data/CLAUDE.md +25 -9
- data/Gemfile.lock +1 -1
- data/MUSIC_THEORY.md +97 -34
- data/lib/head_music/style/guidelines/allowed_rhythmic_values_for_fifth_species.rb +99 -4
- data/lib/head_music/style/guidelines/consonant_downbeats.rb +16 -3
- data/lib/head_music/style/guidelines/dissonance_figure_detection.rb +71 -0
- data/lib/head_music/style/guidelines/florid_dissonance_treatment.rb +11 -8
- data/lib/head_music/style/guidelines/mixed_rhythmic_values.rb +4 -4
- data/lib/head_music/style/guidelines/suspension_treatment.rb +3 -3
- data/lib/head_music/style/guidelines/third_species_dissonance_treatment.rb +2 -65
- data/lib/head_music/style/guides/fifth_species_harmony.rb +2 -0
- data/lib/head_music/style/guides/fifth_species_melody.rb +2 -0
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +1 -3
- data/references/fifth-species-counterpoint.md +578 -0
- metadata +4 -5
- data/lib/head_music/style/guidelines/allowed_rhythmic_values_for_combined234.rb +0 -23
- data/lib/head_music/style/guides/combined_second_third_fourth_species_harmony.rb +0 -18
- data/lib/head_music/style/guides/combined_second_third_fourth_species_melody.rb +0 -22
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4a02a5ef281eecc76bcf953aa51c04078858b4588f2b85c0b1a4d1f4d88464d6
|
|
4
|
+
data.tar.gz: 5a31a94133ffbb97944524d75aedcbbfcbf7ce769fe6049559818a1940438442
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
-
|
|
97
|
-
|
|
98
|
-
5. **HeadMusic::
|
|
99
|
-
-
|
|
100
|
-
-
|
|
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
|
-
|
|
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
|
-
|
|
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
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
|
-
|
|
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
|
-
-
|
|
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
|
|
68
|
-
- staccato –
|
|
69
|
-
- tenuto - held
|
|
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
|
-
##
|
|
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
|
-
|
|
110
|
+
## Scales and Modes
|
|
82
111
|
|
|
83
|
-
|
|
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
|
-
|
|
86
|
-
|
|
117
|
+
major scale
|
|
118
|
+
- pattern: W-W-H-W-W-W-H (W = whole step, H = half step)
|
|
87
119
|
|
|
88
|
-
|
|
89
|
-
|
|
120
|
+
natural minor scale
|
|
121
|
+
- pattern: W-H-W-W-H-W-W
|
|
90
122
|
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
99
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
111
|
-
|
|
140
|
+
chord
|
|
141
|
+
- three or more pitches sounding together
|
|
142
|
+
- built by stacking intervals (typically thirds)
|
|
112
143
|
|
|
113
|
-
|
|
114
|
-
|
|
144
|
+
triad
|
|
145
|
+
- a three-note chord: root, third, fifth
|
|
146
|
+
- types: major, minor, diminished, augmented
|
|
115
147
|
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
|
|
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
|
-
#
|
|
5
|
-
#
|
|
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 = "
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
49
|
+
melodic = melodic_interval_between(note, next_cp)
|
|
46
50
|
return false unless melodic.step?
|
|
47
51
|
|
|
48
|
-
|
|
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 =
|
|
83
|
-
departure =
|
|
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
|
|
5
|
-
#
|
|
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
|
|
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 >=
|
|
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
|
|
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,
|
data/lib/head_music/version.rb
CHANGED