head_music 0.17.0 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -1145
- data/Gemfile +8 -2
- data/Rakefile +7 -5
- data/bin/console +4 -3
- data/circle.yml +1 -1
- data/head_music.gemspec +19 -17
- data/lib/head_music/bar.rb +2 -0
- data/lib/head_music/chord.rb +24 -6
- data/lib/head_music/circle.rb +5 -0
- data/lib/head_music/clef.rb +5 -2
- data/lib/head_music/composition.rb +3 -0
- data/lib/head_music/consonance.rb +5 -2
- data/lib/head_music/functional_interval.rb +84 -47
- data/lib/head_music/grand_staff.rb +11 -11
- data/lib/head_music/harmonic_interval.rb +13 -7
- data/lib/head_music/instrument.rb +9 -6
- data/lib/head_music/interval.rb +13 -7
- data/lib/head_music/key_signature.rb +8 -5
- data/lib/head_music/language.rb +21 -14
- data/lib/head_music/letter_name.rb +13 -10
- data/lib/head_music/melodic_interval.rb +10 -4
- data/lib/head_music/meter.rb +12 -12
- data/lib/head_music/motion.rb +12 -9
- data/lib/head_music/named_rudiment.rb +22 -20
- data/lib/head_music/note.rb +7 -1
- data/lib/head_music/octave.rb +9 -7
- data/lib/head_music/pitch.rb +54 -27
- data/lib/head_music/pitch_class.rb +17 -12
- data/lib/head_music/placement.rb +22 -9
- data/lib/head_music/position.rb +8 -9
- data/lib/head_music/quality.rb +9 -6
- data/lib/head_music/rhythm.rb +2 -0
- data/lib/head_music/rhythmic_unit.rb +29 -19
- data/lib/head_music/rhythmic_value.rb +5 -2
- data/lib/head_music/scale.rb +65 -45
- data/lib/head_music/scale_degree.rb +9 -6
- data/lib/head_music/scale_type.rb +70 -30
- data/lib/head_music/sign.rb +18 -13
- data/lib/head_music/spelling.rb +14 -10
- data/lib/head_music/staff.rb +4 -1
- data/lib/head_music/style/analysis.rb +36 -34
- data/lib/head_music/style/annotation.rb +14 -13
- data/lib/head_music/style/annotations/always_move.rb +7 -6
- data/lib/head_music/style/annotations/approach_perfection_contrarily.rb +5 -2
- data/lib/head_music/style/annotations/at_least_eight_notes.rb +10 -8
- data/lib/head_music/style/annotations/avoid_crossing_voices.rb +11 -8
- data/lib/head_music/style/annotations/avoid_overlapping_voices.rb +17 -10
- data/lib/head_music/style/annotations/consonant_climax.rb +18 -15
- data/lib/head_music/style/annotations/consonant_downbeats.rb +6 -3
- data/lib/head_music/style/annotations/diatonic.rb +8 -5
- data/lib/head_music/style/annotations/direction_changes.rb +8 -6
- data/lib/head_music/style/annotations/end_on_perfect_consonance.rb +5 -5
- data/lib/head_music/style/annotations/end_on_tonic.rb +7 -6
- data/lib/head_music/style/annotations/frequent_direction_changes.rb +6 -3
- data/lib/head_music/style/annotations/limit_octave_leaps.rb +9 -7
- data/lib/head_music/style/annotations/moderate_direction_changes.rb +6 -3
- data/lib/head_music/style/annotations/mostly_conjunct.rb +8 -5
- data/lib/head_music/style/annotations/no_rests.rb +6 -3
- data/lib/head_music/style/annotations/no_unisons_in_middle.rb +6 -3
- data/lib/head_music/style/annotations/notes_same_length.rb +9 -6
- data/lib/head_music/style/annotations/one_to_one.rb +10 -7
- data/lib/head_music/style/annotations/prefer_contrary_motion.rb +6 -3
- data/lib/head_music/style/annotations/prefer_imperfect.rb +7 -6
- data/lib/head_music/style/annotations/prepare_octave_leaps.rb +21 -12
- data/lib/head_music/style/annotations/recover_large_leaps.rb +17 -12
- data/lib/head_music/style/annotations/singable_intervals.rb +8 -5
- data/lib/head_music/style/annotations/singable_range.rb +7 -6
- data/lib/head_music/style/annotations/single_large_leaps.rb +6 -3
- data/lib/head_music/style/annotations/start_on_perfect_consonance.rb +6 -5
- data/lib/head_music/style/annotations/start_on_tonic.rb +6 -5
- data/lib/head_music/style/annotations/step_down_to_final_note.rb +12 -12
- data/lib/head_music/style/annotations/step_out_of_unison.rb +10 -7
- data/lib/head_music/style/annotations/step_to_final_note.rb +6 -3
- data/lib/head_music/style/annotations/step_up_to_final_note.rb +12 -12
- data/lib/head_music/style/annotations/up_to_fourteen_notes.rb +6 -5
- data/lib/head_music/style/mark.rb +10 -4
- data/lib/head_music/style/rulesets/first_species_harmony.rb +6 -3
- data/lib/head_music/style/rulesets/first_species_melody.rb +6 -3
- data/lib/head_music/style/rulesets/fux_cantus_firmus.rb +6 -3
- data/lib/head_music/style/rulesets/modern_cantus_firmus.rb +6 -3
- data/lib/head_music/utilities/hash_key.rb +9 -7
- data/lib/head_music/version.rb +3 -1
- data/lib/head_music/voice.rb +7 -3
- data/lib/head_music.rb +2 -0
- metadata +4 -4
@@ -1,36 +1,38 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# A module for style analysis and rules.
|
4
|
+
module HeadMusic::Style; end
|
5
|
+
|
6
|
+
# An analysis of music according to a ruleset.
|
7
|
+
class HeadMusic::Style::Analysis
|
8
|
+
attr_reader :ruleset, :subject
|
9
|
+
|
10
|
+
def initialize(ruleset, subject)
|
11
|
+
@ruleset = ruleset
|
12
|
+
@subject = subject
|
13
|
+
end
|
14
|
+
|
15
|
+
def messages
|
16
|
+
annotations.reject(&:adherent?).map(&:message)
|
17
|
+
end
|
18
|
+
alias annotation_messages messages
|
19
|
+
|
20
|
+
def annotations
|
21
|
+
@annotations ||= @ruleset.analyze(subject)
|
22
|
+
end
|
23
|
+
|
24
|
+
def fitness
|
25
|
+
return 1.0 if annotations.empty?
|
26
|
+
@fitness ||= fitness_scores.inject(:+).to_f / fitness_scores.length
|
27
|
+
end
|
28
|
+
|
29
|
+
def adherent?
|
30
|
+
fitness == 1
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def fitness_scores
|
36
|
+
@fitness_scores ||= annotations.map(&:fitness)
|
35
37
|
end
|
36
38
|
end
|
@@ -1,3 +1,6 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# An Annotation encapsulates an issue with or comment on a voice
|
1
4
|
class HeadMusic::Style::Annotation
|
2
5
|
MESSAGE = 'Write music.'
|
3
6
|
|
@@ -56,11 +59,11 @@ class HeadMusic::Style::Annotation
|
|
56
59
|
protected
|
57
60
|
|
58
61
|
def first_note
|
59
|
-
notes
|
62
|
+
notes&.first
|
60
63
|
end
|
61
64
|
|
62
65
|
def last_note
|
63
|
-
notes
|
66
|
+
notes&.last
|
64
67
|
end
|
65
68
|
|
66
69
|
def voices
|
@@ -68,7 +71,7 @@ class HeadMusic::Style::Annotation
|
|
68
71
|
end
|
69
72
|
|
70
73
|
def other_voices
|
71
|
-
@other_voices ||= voices.
|
74
|
+
@other_voices ||= voices.reject { |part| part == voice }
|
72
75
|
end
|
73
76
|
|
74
77
|
def cantus_firmus
|
@@ -85,9 +88,7 @@ class HeadMusic::Style::Annotation
|
|
85
88
|
|
86
89
|
def functional_interval_from_tonic(note)
|
87
90
|
tonic_to_use = tonic_pitch
|
88
|
-
while tonic_to_use > note.pitch
|
89
|
-
tonic_to_use -= HeadMusic::Interval.get(:perfect_octave)
|
90
|
-
end
|
91
|
+
tonic_to_use -= HeadMusic::Interval.get(:perfect_octave) while tonic_to_use > note.pitch
|
91
92
|
HeadMusic::FunctionalInterval.new(tonic_to_use, note.pitch)
|
92
93
|
end
|
93
94
|
|
@@ -101,23 +102,23 @@ class HeadMusic::Style::Annotation
|
|
101
102
|
|
102
103
|
def motions
|
103
104
|
downbeat_harmonic_intervals.map.with_index do |harmonic_interval, i|
|
104
|
-
next_harmonic_interval = downbeat_harmonic_intervals[i+1]
|
105
|
+
next_harmonic_interval = downbeat_harmonic_intervals[i + 1]
|
105
106
|
HeadMusic::Motion.new(harmonic_interval, next_harmonic_interval) if next_harmonic_interval
|
106
107
|
end.compact
|
107
108
|
end
|
108
109
|
|
109
110
|
def downbeat_harmonic_intervals
|
110
111
|
@downbeat_harmonic_intervals ||=
|
111
|
-
cantus_firmus.notes.
|
112
|
-
|
113
|
-
|
112
|
+
cantus_firmus.notes.
|
113
|
+
map { |note| HeadMusic::HarmonicInterval.new(note.voice, voice, note.position) }.
|
114
|
+
reject { |interval| interval.notes.length < 2 }
|
114
115
|
end
|
115
116
|
|
116
117
|
def harmonic_intervals
|
117
118
|
@harmonic_intervals ||=
|
118
|
-
positions.
|
119
|
-
|
120
|
-
|
119
|
+
positions.
|
120
|
+
map { |position| HeadMusic::HarmonicInterval.new(cantus_firmus, voice, position) }.
|
121
|
+
reject { |harmonic_interval| harmonic_interval.notes.length < 2 }
|
121
122
|
end
|
122
123
|
|
123
124
|
def positions
|
@@ -1,14 +1,15 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::AlwaysMove < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Always move to a different note.'
|
6
9
|
|
7
10
|
def marks
|
8
11
|
melodic_intervals.map.with_index do |interval, i|
|
9
|
-
if interval.shorthand == 'PU'
|
10
|
-
HeadMusic::Style::Mark.for_all(notes[i..i+1])
|
11
|
-
end
|
12
|
+
HeadMusic::Style::Mark.for_all(notes[i..i + 1]) if interval.shorthand == 'PU'
|
12
13
|
end.compact
|
13
14
|
end
|
14
15
|
end
|
@@ -1,6 +1,9 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::ApproachPerfectionContrarily < HeadMusic::Style::Annotation
|
5
8
|
MESSAGE = 'Approach perfect consonances by contrary motion.'
|
6
9
|
|
@@ -1,10 +1,13 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::AtLeastEightNotes < HeadMusic::Style::Annotation
|
5
8
|
MINIMUM_NOTES = 8
|
6
9
|
|
7
|
-
MESSAGE =
|
10
|
+
MESSAGE = 'Write at least eight notes.'
|
8
11
|
|
9
12
|
def marks
|
10
13
|
placements.empty? ? no_placements_mark : deficiency_mark
|
@@ -14,15 +17,14 @@ class HeadMusic::Style::Annotations::AtLeastEightNotes < HeadMusic::Style::Annot
|
|
14
17
|
|
15
18
|
def no_placements_mark
|
16
19
|
HeadMusic::Style::Mark.new(
|
17
|
-
HeadMusic::Position.new(composition,
|
18
|
-
HeadMusic::Position.new(composition,
|
20
|
+
HeadMusic::Position.new(composition, '1:1'),
|
21
|
+
HeadMusic::Position.new(composition, '2:1'),
|
19
22
|
fitness: 0
|
20
23
|
)
|
21
24
|
end
|
22
25
|
|
23
26
|
def deficiency_mark
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
+
return unless notes.length < MINIMUM_NOTES
|
28
|
+
HeadMusic::Style::Mark.for_all(placements, fitness: notes.length.to_f / MINIMUM_NOTES)
|
27
29
|
end
|
28
30
|
end
|
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::AvoidCrossingVoices < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Avoid crossing voices. Maintain the high-low relationship between voices.'
|
6
9
|
|
7
10
|
def marks
|
8
11
|
crossings.map do |crossing|
|
@@ -19,11 +22,11 @@ class HeadMusic::Style::Annotations::AvoidCrossingVoices < HeadMusic::Style::Ann
|
|
19
22
|
end
|
20
23
|
|
21
24
|
def predominant_pitch_orientation
|
22
|
-
pitch_orientations
|
23
|
-
.
|
24
|
-
|
25
|
-
|
26
|
-
|
25
|
+
pitch_orientations.
|
26
|
+
compact.
|
27
|
+
group_by { |orientation| orientation }.
|
28
|
+
max { |a, b| a[1].length <=> b[1].length }.
|
29
|
+
first
|
27
30
|
end
|
28
31
|
|
29
32
|
def pitch_orientations
|
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::AvoidOverlappingVoices < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Avoid overlapping voices. Maintain the high-low relationship between voices even for adjacent notes.'
|
6
9
|
|
7
10
|
def marks
|
8
11
|
overlappings
|
@@ -24,15 +27,19 @@ class HeadMusic::Style::Annotations::AvoidOverlappingVoices < HeadMusic::Style::
|
|
24
27
|
|
25
28
|
def overlappings_for_voices(voices, comparison_operator)
|
26
29
|
[].tap do |marks|
|
27
|
-
voices.each do |
|
28
|
-
overlapped_notes =
|
29
|
-
preceding_note = higher_voice.note_preceding(note.position)
|
30
|
-
following_note = higher_voice.note_following(note.position)
|
31
|
-
(preceding_note && preceding_note.pitch.send(comparison_operator, note.pitch)) ||
|
32
|
-
(following_note && following_note.pitch.send(comparison_operator, note.pitch))
|
33
|
-
end
|
30
|
+
voices.each do |a_voice|
|
31
|
+
overlapped_notes = overlappings_with_voice(a_voice, comparison_operator)
|
34
32
|
marks << HeadMusic::Style::Mark.for_each(overlapped_notes)
|
35
33
|
end
|
36
34
|
end.flatten.compact
|
37
35
|
end
|
36
|
+
|
37
|
+
def overlappings_with_voice(other_voice, comparison_operator)
|
38
|
+
voice.notes.drop(1).select do |note|
|
39
|
+
preceding_note = other_voice.note_preceding(note.position)
|
40
|
+
following_note = other_voice.note_following(note.position)
|
41
|
+
preceding_note&.pitch&.send(comparison_operator, note.pitch) ||
|
42
|
+
following_note&.pitch&.send(comparison_operator, note.pitch)
|
43
|
+
end
|
44
|
+
end
|
38
45
|
end
|
@@ -1,11 +1,14 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::ConsonantClimax < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Peak on a consonant high or low note one time or twice with a step between.'
|
6
9
|
|
7
10
|
def marks
|
8
|
-
HeadMusic::Style::Mark.for_each(highest_notes)
|
11
|
+
HeadMusic::Style::Mark.for_each(highest_notes) unless adherent_climax?
|
9
12
|
end
|
10
13
|
|
11
14
|
private
|
@@ -16,13 +19,13 @@ class HeadMusic::Style::Annotations::ConsonantClimax < HeadMusic::Style::Annotat
|
|
16
19
|
|
17
20
|
def adherent_high_pitch?
|
18
21
|
notes? && highest_pitch_consonant_with_tonic? &&
|
19
|
-
(
|
22
|
+
(highest_pitch_appears_once? || highest_pitch_appears_twice_with_step_between?)
|
20
23
|
end
|
21
24
|
|
22
25
|
def adherent_low_pitch?
|
23
26
|
notes? &&
|
24
|
-
|
25
|
-
(
|
27
|
+
lowest_pitch_consonant_with_tonic? &&
|
28
|
+
(lowest_pitch_appears_once? || lowest_pitch_appears_twice_with_step_between?)
|
26
29
|
end
|
27
30
|
|
28
31
|
def highest_pitch_consonant_with_tonic?
|
@@ -53,14 +56,14 @@ class HeadMusic::Style::Annotations::ConsonantClimax < HeadMusic::Style::Annotat
|
|
53
56
|
|
54
57
|
def highest_pitch_appears_twice_with_step_between?
|
55
58
|
highest_pitch_appears_twice? &&
|
56
|
-
|
57
|
-
|
59
|
+
single_note_between_highest_notes? &&
|
60
|
+
step_between_highest_notes?
|
58
61
|
end
|
59
62
|
|
60
63
|
def lowest_pitch_appears_twice_with_step_between?
|
61
64
|
lowest_pitch_appears_twice? &&
|
62
|
-
|
63
|
-
|
65
|
+
single_note_between_lowest_notes? &&
|
66
|
+
step_between_lowest_notes?
|
64
67
|
end
|
65
68
|
|
66
69
|
def highest_pitch_appears_twice?
|
@@ -72,11 +75,11 @@ class HeadMusic::Style::Annotations::ConsonantClimax < HeadMusic::Style::Annotat
|
|
72
75
|
end
|
73
76
|
|
74
77
|
def step_between_highest_notes?
|
75
|
-
HeadMusic::MelodicInterval.new(
|
78
|
+
HeadMusic::MelodicInterval.new(highest_notes.first, notes_between_highest_notes.first).step?
|
76
79
|
end
|
77
80
|
|
78
81
|
def step_between_lowest_notes?
|
79
|
-
HeadMusic::MelodicInterval.new(
|
82
|
+
HeadMusic::MelodicInterval.new(lowest_notes.first, notes_between_lowest_notes.first).step?
|
80
83
|
end
|
81
84
|
|
82
85
|
def single_note_between_highest_notes?
|
@@ -103,8 +106,8 @@ class HeadMusic::Style::Annotations::ConsonantClimax < HeadMusic::Style::Annotat
|
|
103
106
|
def descending_melody?
|
104
107
|
# account for the possibility of opening with an octave leap
|
105
108
|
notes.length > 1 &&
|
106
|
-
|
107
|
-
|
109
|
+
[first_note.pitch, second_note.pitch].include?(highest_pitch) &&
|
110
|
+
highest_pitch.spelling == tonic_spelling
|
108
111
|
end
|
109
112
|
|
110
113
|
def second_note
|
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::ConsonantDownbeats < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Use consonant harmonic intervals on every downbeat.'
|
6
9
|
|
7
10
|
def marks
|
8
11
|
dissonant_pairs.map do |dissonant_pair|
|
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::Diatonic < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Use only notes in the key signature.'
|
6
9
|
|
7
10
|
def marks
|
8
11
|
HeadMusic::Style::Mark.for_each(notes_not_in_key_excluding_penultimate_leading_tone)
|
@@ -13,8 +16,8 @@ class HeadMusic::Style::Annotations::Diatonic < HeadMusic::Style::Annotation
|
|
13
16
|
def notes_not_in_key_excluding_penultimate_leading_tone
|
14
17
|
notes_not_in_key.reject do |note|
|
15
18
|
penultimate_note &&
|
16
|
-
|
17
|
-
|
19
|
+
note == penultimate_note &&
|
20
|
+
HeadMusic::ScaleDegree.new(key_signature, note.pitch.spelling).sign == '#'
|
18
21
|
end
|
19
22
|
end
|
20
23
|
|
@@ -1,12 +1,14 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A melodic line should have direction changes.
|
4
7
|
class HeadMusic::Style::Annotations::DirectionChanges < HeadMusic::Style::Annotation
|
5
8
|
def marks
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
end
|
9
|
+
return unless overage.positive?
|
10
|
+
penalty_exponent = overage**0.5
|
11
|
+
HeadMusic::Style::Mark.for_all(notes, fitness: HeadMusic::PENALTY_FACTOR**penalty_exponent)
|
10
12
|
end
|
11
13
|
|
12
14
|
private
|
@@ -1,14 +1,14 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
4
6
|
# marks the voice if the first note is not the first or fifth scale degree of the key.
|
5
7
|
class HeadMusic::Style::Annotations::EndOnPerfectConsonance < HeadMusic::Style::Annotation
|
6
8
|
MESSAGE = 'End on the first or the fifth scale degree.'
|
7
9
|
|
8
10
|
def marks
|
9
|
-
if last_note && !ends_on_perfect_consonance?
|
10
|
-
HeadMusic::Style::Mark.for(last_note)
|
11
|
-
end
|
11
|
+
HeadMusic::Style::Mark.for(last_note) if last_note && !ends_on_perfect_consonance?
|
12
12
|
end
|
13
13
|
|
14
14
|
private
|
@@ -1,13 +1,14 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::EndOnTonic < HeadMusic::Style::Annotation
|
5
8
|
MESSAGE = 'End on the first scale degree.'
|
6
9
|
|
7
10
|
def marks
|
8
|
-
if !notes.empty? && !ends_on_tonic?
|
9
|
-
HeadMusic::Style::Mark.for(notes.last)
|
10
|
-
end
|
11
|
+
HeadMusic::Style::Mark.for(notes.last) if !notes.empty? && !ends_on_tonic?
|
11
12
|
end
|
12
13
|
|
13
14
|
private
|
@@ -17,6 +18,6 @@ class HeadMusic::Style::Annotations::EndOnTonic < HeadMusic::Style::Annotation
|
|
17
18
|
end
|
18
19
|
|
19
20
|
def last_note_spelling
|
20
|
-
last_note
|
21
|
+
last_note&.spelling
|
21
22
|
end
|
22
23
|
end
|
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::FrequentDirectionChanges < HeadMusic::Style::Annotations::DirectionChanges
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Change melodic direction frequently.'
|
6
9
|
|
7
10
|
def self.maximum_notes_per_direction
|
8
11
|
3
|
@@ -1,14 +1,16 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline: Use a maximum of one octave leap.
|
4
7
|
class HeadMusic::Style::Annotations::LimitOctaveLeaps < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Use a maximum of one octave leap.'
|
6
9
|
|
7
10
|
def marks
|
8
|
-
if octave_leaps.length
|
9
|
-
|
10
|
-
|
11
|
-
end
|
11
|
+
return if octave_leaps.length <= 1
|
12
|
+
octave_leaps.map do |leap|
|
13
|
+
HeadMusic::Style::Mark.for_all(leap.notes)
|
12
14
|
end
|
13
15
|
end
|
14
16
|
|
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::ModerateDirectionChanges < HeadMusic::Style::Annotations::DirectionChanges
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Change melodic direction occasionally.'
|
6
9
|
|
7
10
|
def self.maximum_notes_per_direction
|
8
11
|
5
|
@@ -1,10 +1,13 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::MostlyConjunct < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Use mostly conjunct motion.'
|
6
9
|
|
7
|
-
MINIMUM_CONJUNCT_PORTION = HeadMusic::GOLDEN_RATIO_INVERSE
|
10
|
+
MINIMUM_CONJUNCT_PORTION = HeadMusic::GOLDEN_RATIO_INVERSE**2
|
8
11
|
# ~38%
|
9
12
|
# Fux is 5/13 for lydian cantus firmus
|
10
13
|
|
@@ -16,7 +19,7 @@ class HeadMusic::Style::Annotations::MostlyConjunct < HeadMusic::Style::Annotati
|
|
16
19
|
|
17
20
|
def marks_for_skips_and_leaps
|
18
21
|
melodic_intervals.map.with_index do |interval, i|
|
19
|
-
HeadMusic::Style::Mark.for_all(notes[i..i+1], fitness: HeadMusic::SMALL_PENALTY_FACTOR) unless interval.step?
|
22
|
+
HeadMusic::Style::Mark.for_all(notes[i..i + 1], fitness: HeadMusic::SMALL_PENALTY_FACTOR) unless interval.step?
|
20
23
|
end.compact
|
21
24
|
end
|
22
25
|
|
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::NoRests < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Place a note in each measure.'
|
6
9
|
|
7
10
|
def marks
|
8
11
|
HeadMusic::Style::Mark.for_each(rests)
|
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::NoUnisonsInMiddle < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Unisons may only be used in the first and last note.'
|
6
9
|
|
7
10
|
def marks
|
8
11
|
unison_pairs.map do |notes|
|
@@ -1,6 +1,9 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::NotesSameLength < HeadMusic::Style::Annotation
|
5
8
|
MESSAGE = 'Use consistent rhythmic unit.'
|
6
9
|
|
@@ -15,8 +18,8 @@ class HeadMusic::Style::Annotations::NotesSameLength < HeadMusic::Style::Annotat
|
|
15
18
|
end
|
16
19
|
|
17
20
|
def wrong_length_notes
|
18
|
-
all_but_last_note.
|
19
|
-
note.rhythmic_value
|
21
|
+
all_but_last_note.reject do |note|
|
22
|
+
note.rhythmic_value == first_most_common_rhythmic_value
|
20
23
|
end
|
21
24
|
end
|
22
25
|
|
@@ -28,7 +31,7 @@ class HeadMusic::Style::Annotations::NotesSameLength < HeadMusic::Style::Annotat
|
|
28
31
|
last_note.nil? ||
|
29
32
|
[
|
30
33
|
first_most_common_rhythmic_value.total_value,
|
31
|
-
first_most_common_rhythmic_value.total_value * 2
|
34
|
+
first_most_common_rhythmic_value.total_value * 2,
|
32
35
|
].include?(last_note.rhythmic_value.total_value)
|
33
36
|
end
|
34
37
|
|
@@ -56,7 +59,7 @@ class HeadMusic::Style::Annotations::NotesSameLength < HeadMusic::Style::Annotat
|
|
56
59
|
end
|
57
60
|
|
58
61
|
def occurrences_by_rhythmic_value
|
59
|
-
rhythmic_values.
|
62
|
+
rhythmic_values.each_with_object(Hash.new(0)) { |value, hash| hash[value] += 1; }
|
60
63
|
end
|
61
64
|
|
62
65
|
def rhythmic_values
|