head_music 0.14.8 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/lib/head_music/interval.rb +4 -7
  3. data/lib/head_music/key_signature.rb +1 -1
  4. data/lib/head_music/melodic_interval.rb +19 -0
  5. data/lib/head_music/placement.rb +1 -1
  6. data/lib/head_music/rhythmic_value.rb +1 -1
  7. data/lib/head_music/style/analysis.rb +6 -1
  8. data/lib/head_music/style/annotation.rb +2 -2
  9. data/lib/head_music/style/annotations/approach_perfection_contrarily.rb +3 -0
  10. data/lib/head_music/style/annotations/avoid_overlapping_voices.rb +8 -12
  11. data/lib/head_music/style/annotations/consonant_climax.rb +55 -1
  12. data/lib/head_music/style/annotations/direction_changes.rb +1 -5
  13. data/lib/head_music/style/annotations/frequent_direction_changes.rb +10 -0
  14. data/lib/head_music/style/annotations/moderate_direction_changes.rb +10 -0
  15. data/lib/head_music/style/annotations/mostly_conjunct.rb +6 -3
  16. data/lib/head_music/style/annotations/notes_same_length.rb +28 -6
  17. data/lib/head_music/style/annotations/prefer_imperfect.rb +7 -3
  18. data/lib/head_music/style/annotations/prepare_octave_leaps.rb +30 -0
  19. data/lib/head_music/style/annotations/recover_large_leaps.rb +6 -8
  20. data/lib/head_music/style/annotations/single_large_leaps.rb +16 -0
  21. data/lib/head_music/style/annotations/{up_to_thirteen_notes.rb → up_to_fourteen_notes.rb} +3 -3
  22. data/lib/head_music/style/mark.rb +4 -0
  23. data/lib/head_music/style/rulesets/davis_lybbert_cantus_firmus.rb +28 -0
  24. data/lib/head_music/style/rulesets/first_species_melody.rb +2 -2
  25. data/lib/head_music/style/rulesets/{cantus_firmus.rb → fux_cantus_firmus.rb} +3 -3
  26. data/lib/head_music/version.rb +1 -1
  27. data/lib/head_music.rb +14 -9
  28. metadata +9 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8dc54a35d0ca69398e7352fe88520e30115b024e
4
- data.tar.gz: 10f9dcfd37919dd40d23af0f4b411f727917cbcd
3
+ metadata.gz: 445b3a56d6b47014d6baa3d5a194f2721aebd04d
4
+ data.tar.gz: 5eade45431e7167069a5f669713dab209c70529c
5
5
  SHA512:
6
- metadata.gz: 152d346e22b19611840ffcf664cd846b27c1ffa1f75352ac0c9a4f9e7be0433c03fbf506e43a4b9169e627d1092a7cba13f21680269b1089d3c0ac4c312d802f
7
- data.tar.gz: c8441b1f2eabb346daadbdd0c331c742f22ca2937dd86c53056b20bebb629eb0aca678ba91ee0c6590d6979609c59b2257ac792644a09c1da2779a9eb5663bc2
6
+ metadata.gz: dfaf16a40716354c6364ddcdc749046a8af9800d838f190e829a56a38e9a5ae110757dc2aab69154c0290a279bcd2a7db6a9b7d10f56afc7d99e54f5c3b8a3b7
7
+ data.tar.gz: 8736a3fb3564809f62b64a5a7e502563ba08a484b15b1b80c8d4540c668b8ebbff1fa8ce077bb4f5be4b921ac53141d497e79bdaec25c0cbcbe170ea58b2cb94
@@ -7,14 +7,11 @@ class HeadMusic::Interval
7
7
 
8
8
  attr_reader :semitones
9
9
 
10
- def self.get(semitones)
10
+ def self.get(identifier)
11
11
  @intervals ||= {}
12
- @intervals[semitones.to_i] ||= new(semitones.to_i)
13
- end
14
-
15
- def self.named(name)
16
- name = name.to_s
17
- get(NAMES.index(name)) if NAMES.include?(name)
12
+ candidate = identifier.to_s.downcase.gsub(/\W+/, '_')
13
+ semitones = NAMES.index(candidate) || identifier.to_i
14
+ @intervals[semitones] ||= new(semitones.to_i)
18
15
  end
19
16
 
20
17
  def initialize(semitones)
@@ -14,7 +14,7 @@ class HeadMusic::KeySignature
14
14
  return identifier if identifier.is_a?(HeadMusic::KeySignature)
15
15
  @key_signatures ||= {}
16
16
  tonic_spelling, scale_type_name = identifier.strip.split(/\s/)
17
- hash_key = HeadMusic::Utilities::HashKey.for(identifier)
17
+ hash_key = HeadMusic::Utilities::HashKey.for(identifier.gsub(/#/, 'sharp').gsub(/b/, 'flat'))
18
18
  @key_signatures[hash_key] ||= new(tonic_spelling, scale_type_name)
19
19
  end
20
20
 
@@ -55,6 +55,18 @@ class HeadMusic::MelodicInterval
55
55
  !moving?
56
56
  end
57
57
 
58
+ def spans?(pitch)
59
+ pitch >= low_pitch && pitch <= high_pitch
60
+ end
61
+
62
+ def high_pitch
63
+ pitches.sort.last
64
+ end
65
+
66
+ def low_pitch
67
+ pitches.sort.first
68
+ end
69
+
58
70
  def direction
59
71
  @direction ||=
60
72
  if first_pitch < second_pitch
@@ -66,6 +78,13 @@ class HeadMusic::MelodicInterval
66
78
  end
67
79
  end
68
80
 
81
+ def spells_consonant_triad_with?(other_interval)
82
+ return false if step? || other_interval.step?
83
+ combined_pitches = (pitches + other_interval.pitches).uniq
84
+ return false if combined_pitches.length < 3
85
+ HeadMusic::Chord.new(combined_pitches).consonant_triad?
86
+ end
87
+
69
88
  def method_missing(method_name, *args, &block)
70
89
  functional_interval.send(method_name, *args, &block)
71
90
  end
@@ -40,7 +40,7 @@ class HeadMusic::Placement
40
40
  def ensure_attributes(voice, position, rhythmic_value, pitch)
41
41
  @voice = voice
42
42
  ensure_position(position)
43
- @rhythmic_value = rhythmic_value
43
+ @rhythmic_value = HeadMusic::RhythmicValue.get(rhythmic_value)
44
44
  @pitch = HeadMusic::Pitch.get(pitch)
45
45
  end
46
46
 
@@ -56,7 +56,7 @@ class HeadMusic::RhythmicValue
56
56
  end
57
57
 
58
58
  def multiplier
59
- (0..dots).reduce(0) { |sum, i| sum + (1.0/2)**i }
59
+ (0..dots).reduce(0) { |sum, i| sum + 0.5**i }
60
60
  end
61
61
 
62
62
  def ticks
@@ -9,8 +9,9 @@ module HeadMusic
9
9
  end
10
10
 
11
11
  def messages
12
- annotations.reject(&:perfect?).map(&:message)
12
+ annotations.reject(&:adherent?).map(&:message)
13
13
  end
14
+ alias_method :annotation_messages, :messages
14
15
 
15
16
  def annotations
16
17
  @annotations ||= @ruleset.analyze(subject)
@@ -21,6 +22,10 @@ module HeadMusic
21
22
  fitness_scores.inject(:+).to_f / fitness_scores.length
22
23
  end
23
24
 
25
+ def adherent?
26
+ fitness == 1
27
+ end
28
+
24
29
  private
25
30
 
26
31
  def fitness_scores
@@ -29,7 +29,7 @@ class HeadMusic::Style::Annotation
29
29
  [marks].flatten.compact.map(&:fitness).reduce(1, :*)
30
30
  end
31
31
 
32
- def perfect?
32
+ def adherent?
33
33
  fitness == 1
34
34
  end
35
35
 
@@ -86,7 +86,7 @@ class HeadMusic::Style::Annotation
86
86
  def functional_interval_from_tonic(note)
87
87
  tonic_to_use = tonic_pitch
88
88
  while tonic_to_use > note.pitch
89
- tonic_to_use -= HeadMusic::Interval.named(:perfect_octave)
89
+ tonic_to_use -= HeadMusic::Interval.get(:perfect_octave)
90
90
  end
91
91
  HeadMusic::FunctionalInterval.new(tonic_to_use, note.pitch)
92
92
  end
@@ -21,4 +21,7 @@ class HeadMusic::Style::Annotations::ApproachPerfectionContrarily < HeadMusic::S
21
21
  motion.second_harmonic_interval.perfect_consonance?
22
22
  end
23
23
  end
24
+
25
+ # Side effect is that you can't enter a perfect consonance by skip,
26
+ # which is a rule.
24
27
  end
@@ -15,25 +15,21 @@ class HeadMusic::Style::Annotations::AvoidOverlappingVoices < HeadMusic::Style::
15
15
  end
16
16
 
17
17
  def overlappings_of_lower_voices
18
- [].tap do |marks|
19
- lower_voices.each do |lower_voice|
20
- overlapped_notes = voice.notes.select do |note|
21
- preceding_note = lower_voice.note_preceding(note.position)
22
- following_note = lower_voice.note_following(note.position)
23
- (preceding_note && preceding_note.pitch > note.pitch) || (following_note && following_note.pitch > note.pitch)
24
- end
25
- marks << HeadMusic::Style::Mark.for_each(overlapped_notes)
26
- end
27
- end.flatten.compact
18
+ overlappings_for_voices(lower_voices, :>)
28
19
  end
29
20
 
30
21
  def overlappings_of_higher_voices
22
+ overlappings_for_voices(higher_voices, :<)
23
+ end
24
+
25
+ def overlappings_for_voices(voices, comparison_operator)
31
26
  [].tap do |marks|
32
- higher_voices.each do |higher_voice|
27
+ voices.each do |higher_voice|
33
28
  overlapped_notes = voice.notes.select do |note|
34
29
  preceding_note = higher_voice.note_preceding(note.position)
35
30
  following_note = higher_voice.note_following(note.position)
36
- (preceding_note && preceding_note.pitch < note.pitch) || (following_note && following_note.pitch < note.pitch)
31
+ (preceding_note && preceding_note.pitch.send(comparison_operator, note.pitch)) ||
32
+ (following_note && following_note.pitch.send(comparison_operator, note.pitch))
37
33
  end
38
34
  marks << HeadMusic::Style::Mark.for_each(overlapped_notes)
39
35
  end
@@ -11,43 +11,97 @@ class HeadMusic::Style::Annotations::ConsonantClimax < HeadMusic::Style::Annotat
11
11
  private
12
12
 
13
13
  def adherent_climax?
14
+ adherent_high_pitch? || adherent_low_pitch?
15
+ end
16
+
17
+ def adherent_high_pitch?
14
18
  notes? && highest_pitch_consonant_with_tonic? &&
15
19
  ( highest_pitch_appears_once? || highest_pitch_appears_twice_with_step_between? )
16
20
  end
17
21
 
22
+ def adherent_low_pitch?
23
+ notes? &&
24
+ only_goes_down? &&
25
+ lowest_pitch_consonant_with_tonic? &&
26
+ ( lowest_pitch_appears_once? || lowest_pitch_appears_twice_with_step_between? )
27
+ end
28
+
18
29
  def highest_pitch_consonant_with_tonic?
19
30
  functional_interval_to_highest_pitch.consonance?(:melodic)
20
31
  end
21
32
 
33
+ def lowest_pitch_consonant_with_tonic?
34
+ functional_interval_to_lowest_pitch.consonance?(:melodic)
35
+ end
36
+
22
37
  def functional_interval_to_highest_pitch
23
38
  @functional_interval_to_highest_pitch ||=
24
39
  HeadMusic::FunctionalInterval.new(tonic_pitch, highest_pitch)
25
40
  end
26
41
 
42
+ def functional_interval_to_lowest_pitch
43
+ @functional_interval_to_lowest_pitch ||=
44
+ HeadMusic::FunctionalInterval.new(tonic_pitch, lowest_pitch)
45
+ end
46
+
27
47
  def highest_pitch_appears_once?
28
48
  highest_notes.length == 1
29
49
  end
30
50
 
51
+ def lowest_pitch_appears_once?
52
+ lowest_notes.length == 1
53
+ end
54
+
31
55
  def highest_pitch_appears_twice_with_step_between?
32
56
  highest_pitch_appears_twice? &&
33
57
  single_note_between_highest_notes? &&
34
58
  step_between_highest_notes?
35
59
  end
36
60
 
61
+ def lowest_pitch_appears_twice_with_step_between?
62
+ lowest_pitch_appears_twice? &&
63
+ single_note_between_lowest_notes? &&
64
+ step_between_lowest_notes?
65
+ end
66
+
37
67
  def highest_pitch_appears_twice?
38
68
  highest_notes.length == 2
39
69
  end
40
70
 
71
+ def lowest_pitch_appears_twice?
72
+ lowest_notes.length == 2
73
+ end
74
+
41
75
  def step_between_highest_notes?
42
76
  HeadMusic::MelodicInterval.new(voice, highest_notes.first, notes_between_highest_notes.first).step?
43
77
  end
44
78
 
79
+ def step_between_lowest_notes?
80
+ HeadMusic::MelodicInterval.new(voice, lowest_notes.first, notes_between_lowest_notes.first).step?
81
+ end
82
+
45
83
  def single_note_between_highest_notes?
46
84
  notes_between_highest_notes.length == 1
47
85
  end
48
86
 
87
+ def single_note_between_lowest_notes?
88
+ notes_between_lowest_notes.length == 1
89
+ end
90
+
49
91
  def notes_between_highest_notes
50
- indexes = highest_notes.map { |note| notes.index(note) }
92
+ notes_between(highest_notes)
93
+ end
94
+
95
+ def notes_between_lowest_notes
96
+ notes_between(lowest_notes)
97
+ end
98
+
99
+ def notes_between(edge_notes)
100
+ indexes = edge_notes.map { |note| notes.index(note) }
51
101
  notes[(indexes.first + 1)..(indexes.last - 1)] || []
52
102
  end
103
+
104
+ def only_goes_down?
105
+ first_note && first_note.pitch == highest_pitch
106
+ end
53
107
  end
@@ -2,10 +2,6 @@ module HeadMusic::Style::Annotations
2
2
  end
3
3
 
4
4
  class HeadMusic::Style::Annotations::DirectionChanges < HeadMusic::Style::Annotation
5
- MAXIMUM_NOTES_PER_DIRECTION = 3
6
-
7
- MESSAGE = "Balance ascending and descending motion."
8
-
9
5
  def marks
10
6
  if overage > 0
11
7
  penalty_exponent = overage**0.5
@@ -17,7 +13,7 @@ class HeadMusic::Style::Annotations::DirectionChanges < HeadMusic::Style::Annota
17
13
 
18
14
  def overage
19
15
  return 0 if notes.length < 2
20
- [notes_per_direction - MAXIMUM_NOTES_PER_DIRECTION, 0].max
16
+ [notes_per_direction - self.class.maximum_notes_per_direction, 0].max
21
17
  end
22
18
 
23
19
  def notes_per_direction
@@ -0,0 +1,10 @@
1
+ module HeadMusic::Style::Annotations
2
+ end
3
+
4
+ class HeadMusic::Style::Annotations::FrequentDirectionChanges < HeadMusic::Style::Annotations::DirectionChanges
5
+ MESSAGE = "Change melodic direction frequently."
6
+
7
+ def self.maximum_notes_per_direction
8
+ 3
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module HeadMusic::Style::Annotations
2
+ end
3
+
4
+ class HeadMusic::Style::Annotations::ModerateDirectionChanges < HeadMusic::Style::Annotations::DirectionChanges
5
+ MESSAGE = "Change melodic direction occasionally."
6
+
7
+ def self.maximum_notes_per_direction
8
+ 5
9
+ end
10
+ end
@@ -4,8 +4,12 @@ end
4
4
  class HeadMusic::Style::Annotations::MostlyConjunct < HeadMusic::Style::Annotation
5
5
  MESSAGE = "Use mostly conjunct motion."
6
6
 
7
+ MINIMUM_CONJUNCT_PORTION = HeadMusic::GOLDEN_RATIO_INVERSE ** 2
8
+ # ~38%
9
+ # Fux is 5/13 for lydian cantus firmus
10
+
7
11
  def marks
8
- marks_for_skips_and_leaps if conjunct_ratio <= 0.5
12
+ marks_for_skips_and_leaps if conjunct_ratio < MINIMUM_CONJUNCT_PORTION
9
13
  end
10
14
 
11
15
  private
@@ -18,7 +22,6 @@ class HeadMusic::Style::Annotations::MostlyConjunct < HeadMusic::Style::Annotati
18
22
 
19
23
  def conjunct_ratio
20
24
  return 1 if melodic_intervals.empty?
21
- steps = melodic_intervals.count { |interval| interval.step? }
22
- steps.to_f / melodic_intervals.length
25
+ melodic_intervals.count(&:step?).to_f / melodic_intervals.length
23
26
  end
24
27
  end
@@ -5,13 +5,33 @@ class HeadMusic::Style::Annotations::NotesSameLength < HeadMusic::Style::Annotat
5
5
  MESSAGE = 'Use consistent rhythmic unit.'
6
6
 
7
7
  def marks
8
- preferred_value = first_most_common_rhythmic_value
9
- wrong_length_notes = all_but_last_note.select { |note| note.rhythmic_value != preferred_value }
10
- HeadMusic::Style::Mark.for_each(wrong_length_notes)
8
+ HeadMusic::Style::Mark.for_each(all_wrong_length_notes)
11
9
  end
12
10
 
13
11
  private
14
12
 
13
+ def all_wrong_length_notes
14
+ (wrong_length_notes + [wrong_length_last_note]).compact
15
+ end
16
+
17
+ def wrong_length_notes
18
+ all_but_last_note.select.with_index do |note|
19
+ note.rhythmic_value != first_most_common_rhythmic_value
20
+ end
21
+ end
22
+
23
+ def wrong_length_last_note
24
+ last_note unless acceptable_duration_of_last_note?
25
+ end
26
+
27
+ def acceptable_duration_of_last_note?
28
+ last_note.nil? ||
29
+ [
30
+ first_most_common_rhythmic_value.total_value,
31
+ first_most_common_rhythmic_value.total_value * 2
32
+ ].include?(last_note.rhythmic_value.total_value)
33
+ end
34
+
15
35
  def all_but_last_note
16
36
  notes[0..-2]
17
37
  end
@@ -21,9 +41,11 @@ class HeadMusic::Style::Annotations::NotesSameLength < HeadMusic::Style::Annotat
21
41
  end
22
42
 
23
43
  def first_most_common_rhythmic_value
24
- candidates = most_common_rhythmic_values
25
- first_match = notes.detect { |note| candidates.include?(note.rhythmic_value) }
26
- first_match ? first_match.rhythmic_value : nil
44
+ @first_most_common_rhythmic_value ||= begin
45
+ candidates = most_common_rhythmic_values
46
+ first_match = notes.detect { |note| candidates.include?(note.rhythmic_value) }
47
+ first_match ? first_match.rhythmic_value : nil
48
+ end
27
49
  end
28
50
 
29
51
  def most_common_rhythmic_values
@@ -5,7 +5,7 @@ class HeadMusic::Style::Annotations::PreferImperfect < HeadMusic::Style::Annotat
5
5
  MESSAGE = "Prefer imperfect harmonic intervals."
6
6
 
7
7
  def marks
8
- if ratio_of_perfect_intervals > 0.5
8
+ if ratio_of_perfect_intervals >= 0.5
9
9
  HeadMusic::Style::Mark.for_all(perfect_intervals.map(&:notes).flatten)
10
10
  end
11
11
  end
@@ -13,11 +13,15 @@ class HeadMusic::Style::Annotations::PreferImperfect < HeadMusic::Style::Annotat
13
13
  private
14
14
 
15
15
  def ratio_of_perfect_intervals
16
- return 0 if downbeat_harmonic_intervals.empty?
16
+ return 0 if downbeat_harmonic_intervals_in_middle.nil?
17
17
  perfect_intervals.length.to_f / downbeat_harmonic_intervals.length
18
18
  end
19
19
 
20
20
  def perfect_intervals
21
- downbeat_harmonic_intervals.select { |interval| interval.perfect_consonance?(:two_part_harmony) }
21
+ downbeat_harmonic_intervals_in_middle.select { |interval| interval.perfect_consonance?(:two_part_harmony) }
22
+ end
23
+
24
+ def downbeat_harmonic_intervals_in_middle
25
+ downbeat_harmonic_intervals[1..-2]
22
26
  end
23
27
  end
@@ -0,0 +1,30 @@
1
+ module HeadMusic::Style::Annotations
2
+ end
3
+
4
+ class HeadMusic::Style::Annotations::PrepareOctaveLeaps < HeadMusic::Style::Annotation
5
+ MESSAGE = "Enter and exit an octave leap from within."
6
+
7
+ def marks
8
+ (external_entries + external_exits).map do |trouble_spot|
9
+ HeadMusic::Style::Mark.for_all(trouble_spot)
10
+ end
11
+ end
12
+
13
+ private
14
+
15
+ 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
20
+ end.compact
21
+ end
22
+
23
+ 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
28
+ end.compact
29
+ end
30
+ end
@@ -11,7 +11,7 @@ class HeadMusic::Style::Annotations::RecoverLargeLeaps < HeadMusic::Style::Annot
11
11
  def marks
12
12
  melodic_intervals.drop(1).to_a.map.with_index do |interval, i|
13
13
  previous_interval = melodic_intervals[i]
14
- if unrecovered_leap?(previous_interval, interval)
14
+ if unrecovered_leap?(previous_interval, interval, melodic_intervals[i+2])
15
15
  HeadMusic::Style::Mark.for_all((previous_interval.notes + interval.notes).uniq)
16
16
  end
17
17
  end.compact
@@ -19,20 +19,18 @@ class HeadMusic::Style::Annotations::RecoverLargeLeaps < HeadMusic::Style::Annot
19
19
 
20
20
  private
21
21
 
22
- def unrecovered_leap?(first_interval, second_interval)
22
+ def unrecovered_leap?(first_interval, second_interval, third_interval)
23
23
  first_interval.large_leap? &&
24
- !spelling_consonant_triad?(first_interval, second_interval) &&
24
+ !spelling_consonant_triad?(first_interval, second_interval, third_interval) &&
25
25
  (
26
26
  !direction_changed?(first_interval, second_interval) ||
27
27
  !second_interval.step?
28
28
  )
29
29
  end
30
30
 
31
- def spelling_consonant_triad?(first_interval, second_interval)
32
- return false if first_interval.step? || second_interval.step?
33
- pitches = (first_interval.pitches + second_interval.pitches).uniq
34
- return false if pitches.length < 3
35
- HeadMusic::Chord.new(pitches).consonant_triad?
31
+ def spelling_consonant_triad?(first_interval, second_interval, third_interval)
32
+ first_interval.spells_consonant_triad_with?(second_interval) ||
33
+ second_interval.spells_consonant_triad_with?(third_interval)
36
34
  end
37
35
 
38
36
  def direction_changed?(first_interval, second_interval)
@@ -0,0 +1,16 @@
1
+ module HeadMusic::Style::Annotations
2
+ end
3
+
4
+ class HeadMusic::Style::Annotations::SingleLargeLeaps < HeadMusic::Style::Annotations::RecoverLargeLeaps
5
+ MESSAGE = "Recover leaps by step, repetition, opposite direction, or spelling triad."
6
+
7
+ private
8
+
9
+ def unrecovered_leap?(first_interval, second_interval, third_interval)
10
+ return false unless first_interval.large_leap?
11
+ return false if spelling_consonant_triad?(first_interval, second_interval, third_interval)
12
+ return false if second_interval.step?
13
+ return false if second_interval.repetition?
14
+ !direction_changed?(first_interval, second_interval) && second_interval.leap?
15
+ end
16
+ end
@@ -1,14 +1,14 @@
1
1
  module HeadMusic::Style::Annotations
2
2
  end
3
3
 
4
- class HeadMusic::Style::Annotations::UpToThirteenNotes < HeadMusic::Style::Annotation
5
- MAXIMUM_NOTES = 13
4
+ class HeadMusic::Style::Annotations::UpToFourteenNotes < HeadMusic::Style::Annotation
5
+ MAXIMUM_NOTES = 14
6
6
 
7
7
  MESSAGE = 'Write up to thirteen notes.'
8
8
 
9
9
  def marks
10
10
  if overage > 0
11
- HeadMusic::Style::Mark.for_each(notes[13..-1])
11
+ HeadMusic::Style::Mark.for_each(notes[MAXIMUM_NOTES..-1])
12
12
  end
13
13
  end
14
14
 
@@ -28,4 +28,8 @@ class HeadMusic::Style::Mark
28
28
  def code
29
29
  [start_position, end_position].join(' to ')
30
30
  end
31
+
32
+ def to_s
33
+ code
34
+ end
31
35
  end
@@ -0,0 +1,28 @@
1
+ module HeadMusic::Style::Rulesets
2
+ end
3
+
4
+ class HeadMusic::Style::Rulesets::DavisLybbertCantusFirmus
5
+ RULESET = [
6
+ HeadMusic::Style::Annotations::AlwaysMove,
7
+ HeadMusic::Style::Annotations::AtLeastEightNotes,
8
+ HeadMusic::Style::Annotations::ConsonantClimax,
9
+ HeadMusic::Style::Annotations::Diatonic,
10
+ HeadMusic::Style::Annotations::EndOnTonic,
11
+ HeadMusic::Style::Annotations::LimitOctaveLeaps,
12
+ HeadMusic::Style::Annotations::ModerateDirectionChanges,
13
+ HeadMusic::Style::Annotations::MostlyConjunct,
14
+ HeadMusic::Style::Annotations::NoRests,
15
+ HeadMusic::Style::Annotations::NotesSameLength,
16
+ HeadMusic::Style::Annotations::PrepareOctaveLeaps,
17
+ HeadMusic::Style::Annotations::SingableIntervals,
18
+ HeadMusic::Style::Annotations::SingableRange,
19
+ HeadMusic::Style::Annotations::SingleLargeLeaps,
20
+ HeadMusic::Style::Annotations::StartOnTonic,
21
+ HeadMusic::Style::Annotations::StepDownToFinalNote,
22
+ HeadMusic::Style::Annotations::UpToFourteenNotes,
23
+ ]
24
+
25
+ def self.analyze(voice)
26
+ RULESET.map { |rule| rule.new(voice) }
27
+ end
28
+ end
@@ -5,13 +5,13 @@ class HeadMusic::Style::Rulesets::FirstSpeciesMelody
5
5
  RULESET = [
6
6
  HeadMusic::Style::Annotations::ConsonantClimax,
7
7
  HeadMusic::Style::Annotations::Diatonic,
8
- HeadMusic::Style::Annotations::DirectionChanges,
9
8
  HeadMusic::Style::Annotations::EndOnTonic,
9
+ HeadMusic::Style::Annotations::FrequentDirectionChanges,
10
10
  HeadMusic::Style::Annotations::LimitOctaveLeaps,
11
11
  HeadMusic::Style::Annotations::MostlyConjunct,
12
12
  HeadMusic::Style::Annotations::NoRests,
13
13
  HeadMusic::Style::Annotations::NotesSameLength,
14
- HeadMusic::Style::Annotations::RecoverLargeLeaps,
14
+ HeadMusic::Style::Annotations::PrepareOctaveLeaps,
15
15
  HeadMusic::Style::Annotations::SingableIntervals,
16
16
  HeadMusic::Style::Annotations::SingableRange,
17
17
  HeadMusic::Style::Annotations::StartOnPerfectConsonance,
@@ -1,14 +1,14 @@
1
1
  module HeadMusic::Style::Rulesets
2
2
  end
3
3
 
4
- class HeadMusic::Style::Rulesets::CantusFirmus
4
+ class HeadMusic::Style::Rulesets::FuxCantusFirmus
5
5
  RULESET = [
6
6
  HeadMusic::Style::Annotations::AlwaysMove,
7
7
  HeadMusic::Style::Annotations::AtLeastEightNotes,
8
8
  HeadMusic::Style::Annotations::ConsonantClimax,
9
9
  HeadMusic::Style::Annotations::Diatonic,
10
- HeadMusic::Style::Annotations::DirectionChanges,
11
10
  HeadMusic::Style::Annotations::EndOnTonic,
11
+ HeadMusic::Style::Annotations::FrequentDirectionChanges,
12
12
  HeadMusic::Style::Annotations::LimitOctaveLeaps,
13
13
  HeadMusic::Style::Annotations::MostlyConjunct,
14
14
  HeadMusic::Style::Annotations::NoRests,
@@ -18,7 +18,7 @@ class HeadMusic::Style::Rulesets::CantusFirmus
18
18
  HeadMusic::Style::Annotations::SingableRange,
19
19
  HeadMusic::Style::Annotations::StartOnTonic,
20
20
  HeadMusic::Style::Annotations::StepDownToFinalNote,
21
- HeadMusic::Style::Annotations::UpToThirteenNotes,
21
+ HeadMusic::Style::Annotations::UpToFourteenNotes,
22
22
  ]
23
23
 
24
24
  def self.analyze(voice)
@@ -1,3 +1,3 @@
1
1
  module HeadMusic
2
- VERSION = "0.14.8"
2
+ VERSION = "0.16.0"
3
3
  end
data/lib/head_music.rb CHANGED
@@ -1,3 +1,10 @@
1
+ module HeadMusic
2
+ GOLDEN_RATIO = (1 + 5**0.5) / 2.0
3
+ GOLDEN_RATIO_INVERSE = 1 / GOLDEN_RATIO
4
+ PENALTY_FACTOR = GOLDEN_RATIO_INVERSE
5
+ SMALL_PENALTY_FACTOR = GOLDEN_RATIO_INVERSE**0.5
6
+ end
7
+
1
8
  require 'head_music/version'
2
9
 
3
10
  require 'active_support/core_ext/module/delegation'
@@ -54,7 +61,9 @@ require 'head_music/style/annotations/diatonic'
54
61
  require 'head_music/style/annotations/direction_changes'
55
62
  require 'head_music/style/annotations/end_on_perfect_consonance'
56
63
  require 'head_music/style/annotations/end_on_tonic'
64
+ require 'head_music/style/annotations/frequent_direction_changes'
57
65
  require 'head_music/style/annotations/limit_octave_leaps'
66
+ require 'head_music/style/annotations/moderate_direction_changes'
58
67
  require 'head_music/style/annotations/mostly_conjunct'
59
68
  require 'head_music/style/annotations/no_rests'
60
69
  require 'head_music/style/annotations/no_unisons_in_middle'
@@ -62,26 +71,22 @@ require 'head_music/style/annotations/notes_same_length'
62
71
  require 'head_music/style/annotations/one_to_one'
63
72
  require 'head_music/style/annotations/prefer_contrary_motion'
64
73
  require 'head_music/style/annotations/prefer_imperfect'
74
+ require 'head_music/style/annotations/prepare_octave_leaps'
65
75
  require 'head_music/style/annotations/recover_large_leaps'
66
76
  require 'head_music/style/annotations/singable_intervals'
67
77
  require 'head_music/style/annotations/singable_range'
78
+ require 'head_music/style/annotations/single_large_leaps'
68
79
  require 'head_music/style/annotations/start_on_perfect_consonance'
69
80
  require 'head_music/style/annotations/start_on_tonic'
70
81
  require 'head_music/style/annotations/step_down_to_final_note'
71
82
  require 'head_music/style/annotations/step_out_of_unison'
72
83
  require 'head_music/style/annotations/step_up_to_final_note'
73
- require 'head_music/style/annotations/up_to_thirteen_notes'
84
+ require 'head_music/style/annotations/up_to_fourteen_notes'
74
85
 
75
- require 'head_music/style/rulesets/cantus_firmus'
86
+ require 'head_music/style/rulesets/fux_cantus_firmus'
87
+ require 'head_music/style/rulesets/davis_lybbert_cantus_firmus'
76
88
  require 'head_music/style/rulesets/first_species_melody'
77
89
  require 'head_music/style/rulesets/first_species_harmony'
78
90
 
79
91
  require 'head_music/utilities/hash_key'
80
92
  require 'head_music/voice'
81
-
82
- module HeadMusic
83
- GOLDEN_RATIO = (1 + 5**0.5) / 2.0
84
- GOLDEN_RATIO_INVERSE = 1 / GOLDEN_RATIO
85
- PENALTY_FACTOR = GOLDEN_RATIO_INVERSE
86
- SMALL_PENALTY_FACTOR = GOLDEN_RATIO_INVERSE**0.5
87
- end
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.14.8
4
+ version: 0.16.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: 2017-06-12 00:00:00.000000000 Z
11
+ date: 2017-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -162,7 +162,9 @@ files:
162
162
  - lib/head_music/style/annotations/direction_changes.rb
163
163
  - lib/head_music/style/annotations/end_on_perfect_consonance.rb
164
164
  - lib/head_music/style/annotations/end_on_tonic.rb
165
+ - lib/head_music/style/annotations/frequent_direction_changes.rb
165
166
  - lib/head_music/style/annotations/limit_octave_leaps.rb
167
+ - lib/head_music/style/annotations/moderate_direction_changes.rb
166
168
  - lib/head_music/style/annotations/mostly_conjunct.rb
167
169
  - lib/head_music/style/annotations/no_rests.rb
168
170
  - lib/head_music/style/annotations/no_unisons_in_middle.rb
@@ -170,19 +172,22 @@ files:
170
172
  - lib/head_music/style/annotations/one_to_one.rb
171
173
  - lib/head_music/style/annotations/prefer_contrary_motion.rb
172
174
  - lib/head_music/style/annotations/prefer_imperfect.rb
175
+ - lib/head_music/style/annotations/prepare_octave_leaps.rb
173
176
  - lib/head_music/style/annotations/recover_large_leaps.rb
174
177
  - lib/head_music/style/annotations/singable_intervals.rb
175
178
  - lib/head_music/style/annotations/singable_range.rb
179
+ - lib/head_music/style/annotations/single_large_leaps.rb
176
180
  - lib/head_music/style/annotations/start_on_perfect_consonance.rb
177
181
  - lib/head_music/style/annotations/start_on_tonic.rb
178
182
  - lib/head_music/style/annotations/step_down_to_final_note.rb
179
183
  - lib/head_music/style/annotations/step_out_of_unison.rb
180
184
  - lib/head_music/style/annotations/step_up_to_final_note.rb
181
- - lib/head_music/style/annotations/up_to_thirteen_notes.rb
185
+ - lib/head_music/style/annotations/up_to_fourteen_notes.rb
182
186
  - lib/head_music/style/mark.rb
183
- - lib/head_music/style/rulesets/cantus_firmus.rb
187
+ - lib/head_music/style/rulesets/davis_lybbert_cantus_firmus.rb
184
188
  - lib/head_music/style/rulesets/first_species_harmony.rb
185
189
  - lib/head_music/style/rulesets/first_species_melody.rb
190
+ - lib/head_music/style/rulesets/fux_cantus_firmus.rb
186
191
  - lib/head_music/utilities/hash_key.rb
187
192
  - lib/head_music/version.rb
188
193
  - lib/head_music/voice.rb