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,15 +1,18 @@
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::OneToOne < HeadMusic::Style::Annotation
5
8
  MESSAGE = 'Place a note for each note in the other voice.'
6
9
 
7
10
  def marks
8
- if cantus_firmus && cantus_firmus.notes.length > 0
9
- HeadMusic::Style::Mark.for_each(
10
- notes_without_match(voice, cantus_firmus) + notes_without_match(cantus_firmus, voice)
11
- )
12
- end
11
+ return unless cantus_firmus&.notes
12
+ return if cantus_firmus.notes.empty?
13
+ HeadMusic::Style::Mark.for_each(
14
+ notes_without_match(voice, cantus_firmus) + notes_without_match(cantus_firmus, voice)
15
+ )
13
16
  end
14
17
 
15
18
  private
@@ -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::PreferContraryMotion < HeadMusic::Style::Annotation
5
- MESSAGE = "Prefer contrary motion. Move voices in different melodic directions."
8
+ MESSAGE = 'Prefer contrary motion. Move voices in different melodic directions.'
6
9
 
7
10
  def marks
8
11
  return nil if notes.length < 2
@@ -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::PreferImperfect < HeadMusic::Style::Annotation
5
- MESSAGE = "Prefer imperfect harmonic intervals."
8
+ MESSAGE = 'Prefer imperfect harmonic intervals.'
6
9
 
7
10
  def marks
8
- if ratio_of_perfect_intervals >= 0.5
9
- HeadMusic::Style::Mark.for_all(perfect_intervals.map(&:notes).flatten)
10
- end
11
+ HeadMusic::Style::Mark.for_all(perfect_intervals.map(&:notes).flatten) if ratio_of_perfect_intervals >= 0.5
11
12
  end
12
13
 
13
14
  private
@@ -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::PrepareOctaveLeaps < HeadMusic::Style::Annotation
5
- MESSAGE = "Enter and exit an octave leap from within."
8
+ MESSAGE = 'Enter and exit an octave leap from within.'
6
9
 
7
10
  def marks
8
- (external_entries + external_exits).map do |trouble_spot|
11
+ (external_entries + external_exits + octave_ending).map do |trouble_spot|
9
12
  HeadMusic::Style::Mark.for_all(trouble_spot)
10
13
  end
11
14
  end
@@ -13,18 +16,24 @@ class HeadMusic::Style::Annotations::PrepareOctaveLeaps < HeadMusic::Style::Anno
13
16
  private
14
17
 
15
18
  def external_entries
16
- melodic_intervals.map.with_index do |melodic_interval, i|
17
- if melodic_interval.octave? && i > 0 && !melodic_interval.spans?(notes[i-1].pitch)
18
- notes[[i-1, 0].max..(i+1)]
19
- end
19
+ melodic_intervals.each_cons(2).map do |pair|
20
+ first, second = *pair
21
+ pair.map(&:notes).uniq if second.octave? && !second.spans?(first.first_note.pitch)
20
22
  end.compact
21
23
  end
22
24
 
23
25
  def external_exits
24
- melodic_intervals.map.with_index do |melodic_interval, i|
25
- if melodic_interval.octave? && (i == (melodic_intervals.length - 1) || !melodic_interval.spans?(notes[i+2].pitch))
26
- notes[i..[i+2, notes.length - 1].min]
27
- end
26
+ melodic_intervals.each_cons(2).map do |pair|
27
+ first, second = *pair
28
+ pair.map(&:notes).uniq if first.octave? && !first.spans?(second.second_note.pitch)
28
29
  end.compact
29
30
  end
31
+
32
+ def octave_ending
33
+ octave_ending? ? [melodic_intervals.last.notes] : []
34
+ end
35
+
36
+ def octave_ending?
37
+ melodic_intervals.last&.octave?
38
+ end
30
39
  end
@@ -1,31 +1,36 @@
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
  # Ok, so a rule might be that after the first leap (after previous steps)
5
7
  # one should normally move by step in the opposite direction
6
8
  # unless another leap (in either direction) creates a consonant triad.
7
9
  # - Brian
8
10
  class HeadMusic::Style::Annotations::RecoverLargeLeaps < HeadMusic::Style::Annotation
9
- MESSAGE = "Recover large leaps by step in the opposite direction."
11
+ MESSAGE = 'Recover large leaps by step in the opposite direction.'
10
12
 
11
13
  def marks
12
- melodic_intervals.drop(1).to_a.map.with_index do |interval, i|
13
- previous_interval = melodic_intervals[i]
14
- if unrecovered_leap?(previous_interval, interval, melodic_intervals[i+2])
15
- HeadMusic::Style::Mark.for_all((previous_interval.notes + interval.notes).uniq)
14
+ melodic_intervals.each_cons(3).map do |intervals|
15
+ if unrecovered_leap?(intervals[0], intervals[1], intervals[2])
16
+ HeadMusic::Style::Mark.for_all(notes_in_intervals(intervals))
16
17
  end
17
18
  end.compact
18
19
  end
19
20
 
20
21
  private
21
22
 
23
+ def notes_in_intervals(intervals)
24
+ (intervals[0].notes + intervals[1].notes).uniq
25
+ end
26
+
22
27
  def unrecovered_leap?(first_interval, second_interval, third_interval)
23
28
  first_interval.large_leap? &&
24
- !spelling_consonant_triad?(first_interval, second_interval, third_interval) &&
25
- (
26
- !direction_changed?(first_interval, second_interval) ||
27
- !second_interval.step?
28
- )
29
+ !spelling_consonant_triad?(first_interval, second_interval, third_interval) &&
30
+ (
31
+ !direction_changed?(first_interval, second_interval) ||
32
+ !second_interval.step?
33
+ )
29
34
  end
30
35
 
31
36
  def spelling_consonant_triad?(first_interval, second_interval, third_interval)
@@ -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::SingableIntervals < HeadMusic::Style::Annotation
5
- PERMITTED_ASCENDING = %w[PU m2 M2 m3 M3 P4 P5 m6 P8]
6
- PERMITTED_DESCENDING = %w[PU m2 M2 m3 M3 P4 P5 P8]
8
+ PERMITTED_ASCENDING = %w[PU m2 M2 m3 M3 P4 P5 m6 P8].freeze
9
+ PERMITTED_DESCENDING = %w[PU m2 M2 m3 M3 P4 P5 P8].freeze
7
10
 
8
- MESSAGE = "Use only PU, m2, M2, m3, M3, P4, P5, m6 (ascending), P8 in the melodic line."
11
+ MESSAGE = 'Use only PU, m2, M2, m3, M3, P4, P5, m6 (ascending), P8 in the melodic line.'
9
12
 
10
13
  def marks
11
14
  melodic_intervals.reject { |interval| permitted?(interval) }.map do |unpermitted_interval|
@@ -1,21 +1,22 @@
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 voice shouldn't expend the range of a 10th.
4
7
  class HeadMusic::Style::Annotations::SingableRange < HeadMusic::Style::Annotation
5
8
  MAXIMUM_RANGE = 10
6
9
 
7
10
  MESSAGE = 'Limit melodic range to a 10th.'
8
11
 
9
12
  def marks
10
- if overage > 0
11
- HeadMusic::Style::Mark.for_each(extremes, fitness: HeadMusic::PENALTY_FACTOR**overage)
12
- end
13
+ HeadMusic::Style::Mark.for_each(extremes, fitness: HeadMusic::PENALTY_FACTOR**overage) if overage.positive?
13
14
  end
14
15
 
15
16
  private
16
17
 
17
18
  def overage
18
- notes.length > 0 ? [range.number - MAXIMUM_RANGE, 0].max : 0
19
+ !notes.empty? ? [range.number - MAXIMUM_RANGE, 0].max : 0
19
20
  end
20
21
 
21
22
  def extremes
@@ -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::SingleLargeLeaps < HeadMusic::Style::Annotations::RecoverLargeLeaps
5
- MESSAGE = "Recover leaps by step, repetition, opposite direction, or spelling triad."
8
+ MESSAGE = 'Recover leaps by step, repetition, opposite direction, or spelling triad.'
6
9
 
7
10
  private
8
11
 
@@ -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
 
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::StartOnPerfectConsonance < HeadMusic::Style::Annotation
6
8
  MESSAGE = 'Start on the tonic or a perfect consonance above the tonic (unless bass voice).'
7
9
 
8
10
  def marks
9
- if first_note && ((bass_voice? && !starts_on_tonic?) || !starts_on_perfect_consonance?)
10
- HeadMusic::Style::Mark.for(first_note)
11
- end
11
+ return unless first_note && ((bass_voice? && !starts_on_tonic?) || !starts_on_perfect_consonance?)
12
+ HeadMusic::Style::Mark.for(first_note)
12
13
  end
13
14
 
14
15
  private
@@ -1,12 +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::StartOnTonic < HeadMusic::Style::Annotation
5
8
  MESSAGE = 'Start on the first scale degree.'
6
9
 
7
10
  def marks
8
- if first_note && !starts_on_tonic?
9
- HeadMusic::Style::Mark.for(first_note)
10
- end
11
+ HeadMusic::Style::Mark.for(first_note) if first_note && !starts_on_tonic?
11
12
  end
12
13
  end
@@ -1,28 +1,28 @@
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::StepDownToFinalNote < HeadMusic::Style::Annotation
5
8
  MESSAGE = 'Step down to the final note.'
6
9
 
7
10
  def marks
8
- if !last_melodic_interval.nil?
9
- fitness = 1
10
- fitness *= HeadMusic::PENALTY_FACTOR unless step?
11
- fitness *= HeadMusic::PENALTY_FACTOR unless descending?
12
- if fitness < 1
13
- HeadMusic::Style::Mark.for_all(notes[-2..-1], fitness: fitness)
14
- end
15
- end
11
+ return if last_melodic_interval.nil?
12
+ fitness = 1
13
+ fitness *= HeadMusic::PENALTY_FACTOR unless step?
14
+ fitness *= HeadMusic::PENALTY_FACTOR unless descending?
15
+ HeadMusic::Style::Mark.for_all(notes[-2..-1], fitness: fitness) if fitness < 1
16
16
  end
17
17
 
18
18
  private
19
19
 
20
20
  def descending?
21
- last_melodic_interval && last_melodic_interval.descending?
21
+ last_melodic_interval&.descending?
22
22
  end
23
23
 
24
24
  def step?
25
- last_melodic_interval && last_melodic_interval.step?
25
+ last_melodic_interval&.step?
26
26
  end
27
27
 
28
28
  def last_melodic_interval
@@ -1,19 +1,22 @@
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::StepOutOfUnison < HeadMusic::Style::Annotation
5
- MESSAGE = "Exit a unison by step."
8
+ MESSAGE = 'Exit a unison by step.'
6
9
 
7
10
  def marks
8
- skips_following_unisons.map do |skip|
11
+ leaps_following_unisons.map do |skip|
9
12
  HeadMusic::Style::Mark.for_all(skip.notes)
10
13
  end.flatten
11
14
  end
12
15
 
13
16
  private
14
17
 
15
- def skips_following_unisons
16
- melodic_intervals_following_unisons.select(&:skip?)
18
+ def leaps_following_unisons
19
+ melodic_intervals_following_unisons.select(&:leap?)
17
20
  end
18
21
 
19
22
  def melodic_intervals_following_unisons
@@ -21,7 +24,7 @@ class HeadMusic::Style::Annotations::StepOutOfUnison < HeadMusic::Style::Annotat
21
24
  perfect_unisons.map do |unison|
22
25
  note1 = voice.note_at(unison.position)
23
26
  note2 = voice.note_following(unison.position)
24
- HeadMusic::MelodicInterval.new(voice, note1, note2) if note1 && note2
27
+ HeadMusic::MelodicInterval.new(note1, note2) if note1 && note2
25
28
  end.compact
26
29
  end
27
30
 
@@ -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::StepToFinalNote < HeadMusic::Style::Annotation
5
8
  MESSAGE = 'Step to the final note.'
6
9
 
@@ -11,7 +14,7 @@ class HeadMusic::Style::Annotations::StepToFinalNote < HeadMusic::Style::Annotat
11
14
  private
12
15
 
13
16
  def step_to_final_note?
14
- last_melodic_interval && last_melodic_interval.step?
17
+ last_melodic_interval&.step?
15
18
  end
16
19
 
17
20
  def last_melodic_interval
@@ -1,28 +1,28 @@
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::StepUpToFinalNote < HeadMusic::Style::Annotation
5
8
  MESSAGE = 'Step up to final note.'
6
9
 
7
10
  def marks
8
- if !last_melodic_interval.nil?
9
- fitness = 1
10
- fitness *= HeadMusic::PENALTY_FACTOR unless step?
11
- fitness *= HeadMusic::PENALTY_FACTOR unless ascending?
12
- if fitness < 1
13
- HeadMusic::Style::Mark.for_all(notes[-2..-1], fitness: fitness)
14
- end
15
- end
11
+ return if last_melodic_interval.nil?
12
+ fitness = 1
13
+ fitness *= HeadMusic::PENALTY_FACTOR unless step?
14
+ fitness *= HeadMusic::PENALTY_FACTOR unless ascending?
15
+ HeadMusic::Style::Mark.for_all(notes[-2..-1], fitness: fitness) if fitness < 1
16
16
  end
17
17
 
18
18
  private
19
19
 
20
20
  def ascending?
21
- last_melodic_interval && last_melodic_interval.ascending?
21
+ last_melodic_interval&.ascending?
22
22
  end
23
23
 
24
24
  def step?
25
- last_melodic_interval && last_melodic_interval.step?
25
+ last_melodic_interval&.step?
26
26
  end
27
27
 
28
28
  def last_melodic_interval
@@ -1,15 +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
4
7
  class HeadMusic::Style::Annotations::UpToFourteenNotes < HeadMusic::Style::Annotation
5
8
  MAXIMUM_NOTES = 14
6
9
 
7
10
  MESSAGE = 'Write up to fourteen notes.'
8
11
 
9
12
  def marks
10
- if overage > 0
11
- HeadMusic::Style::Mark.for_each(notes[MAXIMUM_NOTES..-1])
12
- end
13
+ HeadMusic::Style::Mark.for_each(notes[MAXIMUM_NOTES..-1]) if overage.positive?
13
14
  end
14
15
 
15
16
  private
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A mark is a fragment of music with an optional fitness score assigned.
4
+ # Marks are collected into annotations which comment on the fragments.
1
5
  class HeadMusic::Style::Mark
2
6
  attr_reader :start_position, :end_position, :placements, :fitness
3
7
 
@@ -7,15 +11,17 @@ class HeadMusic::Style::Mark
7
11
 
8
12
  def self.for_all(placements, fitness: nil)
9
13
  placements = [placements].flatten.compact
10
- return [] if placements.length == 0
11
- start_position = placements.map { |placement| placement.position }.sort.first
12
- end_position = placements.map { |placement| placement.next_position }.sort.last
14
+ return [] if placements.empty?
15
+ start_position = placements.map(&:position).sort.first
16
+ end_position = placements.map(&:next_position).sort.last
13
17
  new(start_position, end_position, placements: placements, fitness: fitness)
14
18
  end
15
19
 
16
20
  def self.for_each(placements, fitness: nil)
17
21
  placements = [placements].flatten
18
- placements.map { |placement| new(placement.position, placement.next_position, placements: placement, fitness: fitness) }
22
+ placements.map do |placement|
23
+ new(placement.position, placement.next_position, placements: placement, fitness: fitness)
24
+ end
19
25
  end
20
26
 
21
27
  def initialize(start_position, end_position, placements: [], fitness: nil)
@@ -1,6 +1,9 @@
1
- module HeadMusic::Style::Rulesets
2
- end
1
+ # frozen_string_literal: true
2
+
3
+ # Module for rulesets
4
+ module HeadMusic::Style::Rulesets; end
3
5
 
6
+ # Rules for first species harmony
4
7
  class HeadMusic::Style::Rulesets::FirstSpeciesHarmony
5
8
  RULESET = [
6
9
  HeadMusic::Style::Annotations::ApproachPerfectionContrarily,
@@ -11,7 +14,7 @@ class HeadMusic::Style::Rulesets::FirstSpeciesHarmony
11
14
  HeadMusic::Style::Annotations::OneToOne,
12
15
  HeadMusic::Style::Annotations::PreferContraryMotion,
13
16
  HeadMusic::Style::Annotations::PreferImperfect,
14
- ]
17
+ ].freeze
15
18
 
16
19
  def self.analyze(voice)
17
20
  RULESET.map { |rule| rule.new(voice) }
@@ -1,6 +1,9 @@
1
- module HeadMusic::Style::Rulesets
2
- end
1
+ # frozen_string_literal: true
2
+
3
+ # Module for rulesets
4
+ module HeadMusic::Style::Rulesets; end
3
5
 
6
+ # Rules for first species melodies
4
7
  class HeadMusic::Style::Rulesets::FirstSpeciesMelody
5
8
  RULESET = [
6
9
  HeadMusic::Style::Annotations::ConsonantClimax,
@@ -17,7 +20,7 @@ class HeadMusic::Style::Rulesets::FirstSpeciesMelody
17
20
  HeadMusic::Style::Annotations::StartOnPerfectConsonance,
18
21
  HeadMusic::Style::Annotations::StepOutOfUnison,
19
22
  HeadMusic::Style::Annotations::StepUpToFinalNote,
20
- ]
23
+ ].freeze
21
24
 
22
25
  def self.analyze(voice)
23
26
  RULESET.map { |rule| rule.new(voice) }
@@ -1,6 +1,9 @@
1
- module HeadMusic::Style::Rulesets
2
- end
1
+ # frozen_string_literal: true
2
+
3
+ # Module for rulesets
4
+ module HeadMusic::Style::Rulesets; end
3
5
 
6
+ # Rules for the cantus firmus according to Fux.
4
7
  class HeadMusic::Style::Rulesets::FuxCantusFirmus
5
8
  RULESET = [
6
9
  HeadMusic::Style::Annotations::AlwaysMove,
@@ -19,7 +22,7 @@ class HeadMusic::Style::Rulesets::FuxCantusFirmus
19
22
  HeadMusic::Style::Annotations::StartOnTonic,
20
23
  HeadMusic::Style::Annotations::StepDownToFinalNote,
21
24
  HeadMusic::Style::Annotations::UpToFourteenNotes,
22
- ]
25
+ ].freeze
23
26
 
24
27
  def self.analyze(voice)
25
28
  RULESET.map { |rule| rule.new(voice) }
@@ -1,6 +1,9 @@
1
- module HeadMusic::Style::Rulesets
2
- end
1
+ # frozen_string_literal: true
2
+
3
+ # Module for rulesets
4
+ module HeadMusic::Style::Rulesets; end
3
5
 
6
+ # Modern rules for the cantus firmus
4
7
  class HeadMusic::Style::Rulesets::ModernCantusFirmus
5
8
  RULESET = [
6
9
  HeadMusic::Style::Annotations::AlwaysMove,
@@ -20,7 +23,7 @@ class HeadMusic::Style::Rulesets::ModernCantusFirmus
20
23
  HeadMusic::Style::Annotations::StartOnTonic,
21
24
  HeadMusic::Style::Annotations::StepToFinalNote,
22
25
  HeadMusic::Style::Annotations::UpToFourteenNotes,
23
- ]
26
+ ].freeze
24
27
 
25
28
  def self.analyze(voice)
26
29
  RULESET.map { |rule| rule.new(voice) }
@@ -1,9 +1,11 @@
1
- module HeadMusic
2
- module Utilities
3
- class HashKey
4
- def self.for(identifier)
5
- identifier.to_s.downcase.gsub(/\W+/, '_').to_sym
6
- end
7
- end
1
+ # frozen_string_literal: true
2
+
3
+ # A namespace for utilities classes and modules
4
+ module HeadMusic::Utilities; end
5
+
6
+ # Util for converting an object to a consistent hash key
7
+ module HeadMusic::Utilities::HashKey
8
+ def self.for(identifier)
9
+ identifier.to_s.downcase.gsub(/\W+/, '_').to_sym
8
10
  end
9
11
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HeadMusic
2
- VERSION = "0.17.0"
4
+ VERSION = '0.18.0'
3
5
  end
@@ -1,3 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # A Voice is a stream of music with some indepedence that is conceptually one part or for one performer.
4
+ # The melodic lines in counterpoint are each a voice.
1
5
  class HeadMusic::Voice
2
6
  include Comparable
3
7
 
@@ -11,9 +15,9 @@ class HeadMusic::Voice
11
15
  end
12
16
 
13
17
  def place(position, rhythmic_value, pitch = nil)
14
- HeadMusic::Placement.new(self, position, rhythmic_value, pitch).tap { |placement|
18
+ HeadMusic::Placement.new(self, position, rhythmic_value, pitch).tap do |placement|
15
19
  insert_into_placements(placement)
16
- }
20
+ end
17
21
  end
18
22
 
19
23
  def notes
@@ -56,7 +60,7 @@ class HeadMusic::Voice
56
60
  def melodic_intervals
57
61
  @melodic_intervals ||=
58
62
  notes.map.with_index do |note, i|
59
- HeadMusic::MelodicInterval.new(self, notes[i-1], note) if i > 0
63
+ HeadMusic::MelodicInterval.new(notes[i - 1], note) if i.positive?
60
64
  end.compact
61
65
  end
62
66
 
data/lib/head_music.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module HeadMusic
2
4
  GOLDEN_RATIO = (1 + 5**0.5) / 2.0
3
5
  GOLDEN_RATIO_INVERSE = 1 / GOLDEN_RATIO
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: head_music
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.0
4
+ version: 0.18.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rob Head
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-02-18 00:00:00.000000000 Z
11
+ date: 2018-02-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -203,9 +203,9 @@ require_paths:
203
203
  - lib
204
204
  required_ruby_version: !ruby/object:Gem::Requirement
205
205
  requirements:
206
- - - ">"
206
+ - - ">="
207
207
  - !ruby/object:Gem::Version
208
- version: 2.3.0
208
+ version: 2.4.0
209
209
  required_rubygems_version: !ruby/object:Gem::Requirement
210
210
  requirements:
211
211
  - - ">="