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.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +12 -1145
  3. data/Gemfile +8 -2
  4. data/Rakefile +7 -5
  5. data/bin/console +4 -3
  6. data/circle.yml +1 -1
  7. data/head_music.gemspec +19 -17
  8. data/lib/head_music/bar.rb +2 -0
  9. data/lib/head_music/chord.rb +24 -6
  10. data/lib/head_music/circle.rb +5 -0
  11. data/lib/head_music/clef.rb +5 -2
  12. data/lib/head_music/composition.rb +3 -0
  13. data/lib/head_music/consonance.rb +5 -2
  14. data/lib/head_music/functional_interval.rb +84 -47
  15. data/lib/head_music/grand_staff.rb +11 -11
  16. data/lib/head_music/harmonic_interval.rb +13 -7
  17. data/lib/head_music/instrument.rb +9 -6
  18. data/lib/head_music/interval.rb +13 -7
  19. data/lib/head_music/key_signature.rb +8 -5
  20. data/lib/head_music/language.rb +21 -14
  21. data/lib/head_music/letter_name.rb +13 -10
  22. data/lib/head_music/melodic_interval.rb +10 -4
  23. data/lib/head_music/meter.rb +12 -12
  24. data/lib/head_music/motion.rb +12 -9
  25. data/lib/head_music/named_rudiment.rb +22 -20
  26. data/lib/head_music/note.rb +7 -1
  27. data/lib/head_music/octave.rb +9 -7
  28. data/lib/head_music/pitch.rb +54 -27
  29. data/lib/head_music/pitch_class.rb +17 -12
  30. data/lib/head_music/placement.rb +22 -9
  31. data/lib/head_music/position.rb +8 -9
  32. data/lib/head_music/quality.rb +9 -6
  33. data/lib/head_music/rhythm.rb +2 -0
  34. data/lib/head_music/rhythmic_unit.rb +29 -19
  35. data/lib/head_music/rhythmic_value.rb +5 -2
  36. data/lib/head_music/scale.rb +65 -45
  37. data/lib/head_music/scale_degree.rb +9 -6
  38. data/lib/head_music/scale_type.rb +70 -30
  39. data/lib/head_music/sign.rb +18 -13
  40. data/lib/head_music/spelling.rb +14 -10
  41. data/lib/head_music/staff.rb +4 -1
  42. data/lib/head_music/style/analysis.rb +36 -34
  43. data/lib/head_music/style/annotation.rb +14 -13
  44. data/lib/head_music/style/annotations/always_move.rb +7 -6
  45. data/lib/head_music/style/annotations/approach_perfection_contrarily.rb +5 -2
  46. data/lib/head_music/style/annotations/at_least_eight_notes.rb +10 -8
  47. data/lib/head_music/style/annotations/avoid_crossing_voices.rb +11 -8
  48. data/lib/head_music/style/annotations/avoid_overlapping_voices.rb +17 -10
  49. data/lib/head_music/style/annotations/consonant_climax.rb +18 -15
  50. data/lib/head_music/style/annotations/consonant_downbeats.rb +6 -3
  51. data/lib/head_music/style/annotations/diatonic.rb +8 -5
  52. data/lib/head_music/style/annotations/direction_changes.rb +8 -6
  53. data/lib/head_music/style/annotations/end_on_perfect_consonance.rb +5 -5
  54. data/lib/head_music/style/annotations/end_on_tonic.rb +7 -6
  55. data/lib/head_music/style/annotations/frequent_direction_changes.rb +6 -3
  56. data/lib/head_music/style/annotations/limit_octave_leaps.rb +9 -7
  57. data/lib/head_music/style/annotations/moderate_direction_changes.rb +6 -3
  58. data/lib/head_music/style/annotations/mostly_conjunct.rb +8 -5
  59. data/lib/head_music/style/annotations/no_rests.rb +6 -3
  60. data/lib/head_music/style/annotations/no_unisons_in_middle.rb +6 -3
  61. data/lib/head_music/style/annotations/notes_same_length.rb +9 -6
  62. data/lib/head_music/style/annotations/one_to_one.rb +10 -7
  63. data/lib/head_music/style/annotations/prefer_contrary_motion.rb +6 -3
  64. data/lib/head_music/style/annotations/prefer_imperfect.rb +7 -6
  65. data/lib/head_music/style/annotations/prepare_octave_leaps.rb +21 -12
  66. data/lib/head_music/style/annotations/recover_large_leaps.rb +17 -12
  67. data/lib/head_music/style/annotations/singable_intervals.rb +8 -5
  68. data/lib/head_music/style/annotations/singable_range.rb +7 -6
  69. data/lib/head_music/style/annotations/single_large_leaps.rb +6 -3
  70. data/lib/head_music/style/annotations/start_on_perfect_consonance.rb +6 -5
  71. data/lib/head_music/style/annotations/start_on_tonic.rb +6 -5
  72. data/lib/head_music/style/annotations/step_down_to_final_note.rb +12 -12
  73. data/lib/head_music/style/annotations/step_out_of_unison.rb +10 -7
  74. data/lib/head_music/style/annotations/step_to_final_note.rb +6 -3
  75. data/lib/head_music/style/annotations/step_up_to_final_note.rb +12 -12
  76. data/lib/head_music/style/annotations/up_to_fourteen_notes.rb +6 -5
  77. data/lib/head_music/style/mark.rb +10 -4
  78. data/lib/head_music/style/rulesets/first_species_harmony.rb +6 -3
  79. data/lib/head_music/style/rulesets/first_species_melody.rb +6 -3
  80. data/lib/head_music/style/rulesets/fux_cantus_firmus.rb +6 -3
  81. data/lib/head_music/style/rulesets/modern_cantus_firmus.rb +6 -3
  82. data/lib/head_music/utilities/hash_key.rb +9 -7
  83. data/lib/head_music/version.rb +3 -1
  84. data/lib/head_music/voice.rb +7 -3
  85. data/lib/head_music.rb +2 -0
  86. metadata +4 -4
@@ -1,36 +1,38 @@
1
- module HeadMusic
2
- module Style
3
- class Analysis
4
- attr_reader :ruleset, :subject, :annotations
5
-
6
- def initialize(ruleset, subject)
7
- @ruleset = ruleset
8
- @subject = subject
9
- end
10
-
11
- def messages
12
- annotations.reject(&:adherent?).map(&:message)
13
- end
14
- alias_method :annotation_messages, :messages
15
-
16
- def annotations
17
- @annotations ||= @ruleset.analyze(subject)
18
- end
19
-
20
- def fitness
21
- return 1.0 if annotations.length == 0
22
- @fitness ||= fitness_scores.inject(:+).to_f / fitness_scores.length
23
- end
24
-
25
- def adherent?
26
- fitness == 1
27
- end
28
-
29
- private
30
-
31
- def fitness_scores
32
- @fitness_scores ||= annotations.map(&:fitness)
33
- end
34
- end
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 && notes.first
62
+ notes&.first
60
63
  end
61
64
 
62
65
  def last_note
63
- notes && notes.last
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.select { |part| part != voice }
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.map { |cantus_firmus_note|
112
- HeadMusic::HarmonicInterval.new(cantus_firmus_note.voice, voice, cantus_firmus_note.position)
113
- }.reject { |interval| interval.notes.length < 2 }
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.map { |position|
119
- HeadMusic::HarmonicInterval.new(cantus_firmus, voice, position)
120
- }.reject { |harmonic_interval| harmonic_interval.notes.length < 2 }
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
- module HeadMusic::Style::Annotations
2
- end
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 = "Always move to a different note."
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
- module HeadMusic::Style::Annotations
2
- end
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
- module HeadMusic::Style::Annotations
2
- end
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 = "Write at least eight notes."
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, "1:1"),
18
- HeadMusic::Position.new(composition, "2:1"),
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
- if notes.length < MINIMUM_NOTES
25
- HeadMusic::Style::Mark.for_all(placements, fitness: notes.length.to_f / MINIMUM_NOTES)
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
- module HeadMusic::Style::Annotations
2
- end
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 = "Avoid crossing voices. Maintain the high-low relationship between voices."
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
- .compact
24
- .group_by { |orientation| orientation }
25
- .max { |a, b| a[1].length <=> b[1].length }
26
- .first
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
- module HeadMusic::Style::Annotations
2
- end
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 = "Avoid overlapping voices. Maintain the high-low relationship between voices even for adjacent notes."
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 |higher_voice|
28
- overlapped_notes = voice.notes.select do |note|
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
- module HeadMusic::Style::Annotations
2
- end
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 = "Peak on a consonant high or low note one time or twice with a step between."
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) if !adherent_climax?
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
- ( highest_pitch_appears_once? || highest_pitch_appears_twice_with_step_between? )
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
- lowest_pitch_consonant_with_tonic? &&
25
- ( lowest_pitch_appears_once? || lowest_pitch_appears_twice_with_step_between? )
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
- single_note_between_highest_notes? &&
57
- step_between_highest_notes?
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
- single_note_between_lowest_notes? &&
63
- step_between_lowest_notes?
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(voice, highest_notes.first, notes_between_highest_notes.first).step?
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(voice, lowest_notes.first, notes_between_lowest_notes.first).step?
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
- [first_note.pitch, second_note.pitch].include?(highest_pitch) &&
107
- highest_pitch.spelling == tonic_spelling
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
- module HeadMusic::Style::Annotations
2
- end
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 = "Use consonant harmonic intervals on every downbeat."
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
- module HeadMusic::Style::Annotations
2
- end
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 = "Use only notes in the key signature."
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
- note == penultimate_note &&
17
- HeadMusic::ScaleDegree.new(key_signature, note.pitch.spelling).sign == '#'
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
- module HeadMusic::Style::Annotations
2
- end
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
- if overage > 0
7
- penalty_exponent = overage**0.5
8
- HeadMusic::Style::Mark.for_all(notes, fitness: HeadMusic::PENALTY_FACTOR**penalty_exponent)
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
- module HeadMusic::Style::Annotations
2
- end
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
- module HeadMusic::Style::Annotations
2
- end
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 && last_note.spelling
21
+ last_note&.spelling
21
22
  end
22
23
  end
@@ -1,8 +1,11 @@
1
- module HeadMusic::Style::Annotations
2
- end
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 = "Change melodic direction frequently."
8
+ MESSAGE = 'Change melodic direction frequently.'
6
9
 
7
10
  def self.maximum_notes_per_direction
8
11
  3
@@ -1,14 +1,16 @@
1
- module HeadMusic::Style::Annotations
2
- end
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 = "Use a maximum of one octave leap."
8
+ MESSAGE = 'Use a maximum of one octave leap.'
6
9
 
7
10
  def marks
8
- if octave_leaps.length > 1
9
- octave_leaps.map do |leap|
10
- HeadMusic::Style::Mark.for_all(leap.notes)
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
- module HeadMusic::Style::Annotations
2
- end
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 = "Change melodic direction occasionally."
8
+ MESSAGE = 'Change melodic direction occasionally.'
6
9
 
7
10
  def self.maximum_notes_per_direction
8
11
  5
@@ -1,10 +1,13 @@
1
- module HeadMusic::Style::Annotations
2
- end
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 = "Use mostly conjunct motion."
8
+ MESSAGE = 'Use mostly conjunct motion.'
6
9
 
7
- MINIMUM_CONJUNCT_PORTION = HeadMusic::GOLDEN_RATIO_INVERSE ** 2
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
- module HeadMusic::Style::Annotations
2
- end
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 = "Place a note in each measure."
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
- module HeadMusic::Style::Annotations
2
- end
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 = "Unisons may only be used in the first and last note."
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
- module HeadMusic::Style::Annotations
2
- end
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.select.with_index do |note|
19
- note.rhythmic_value != first_most_common_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.inject(Hash.new(0)) { |hash, value| hash[value] += 1; hash }
62
+ rhythmic_values.each_with_object(Hash.new(0)) { |value, hash| hash[value] += 1; }
60
63
  end
61
64
 
62
65
  def rhythmic_values