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.
- checksums.yaml +4 -4
- data/lib/head_music/interval.rb +4 -7
- data/lib/head_music/key_signature.rb +1 -1
- data/lib/head_music/melodic_interval.rb +19 -0
- data/lib/head_music/placement.rb +1 -1
- data/lib/head_music/rhythmic_value.rb +1 -1
- data/lib/head_music/style/analysis.rb +6 -1
- data/lib/head_music/style/annotation.rb +2 -2
- data/lib/head_music/style/annotations/approach_perfection_contrarily.rb +3 -0
- data/lib/head_music/style/annotations/avoid_overlapping_voices.rb +8 -12
- data/lib/head_music/style/annotations/consonant_climax.rb +55 -1
- data/lib/head_music/style/annotations/direction_changes.rb +1 -5
- data/lib/head_music/style/annotations/frequent_direction_changes.rb +10 -0
- data/lib/head_music/style/annotations/moderate_direction_changes.rb +10 -0
- data/lib/head_music/style/annotations/mostly_conjunct.rb +6 -3
- data/lib/head_music/style/annotations/notes_same_length.rb +28 -6
- data/lib/head_music/style/annotations/prefer_imperfect.rb +7 -3
- data/lib/head_music/style/annotations/prepare_octave_leaps.rb +30 -0
- data/lib/head_music/style/annotations/recover_large_leaps.rb +6 -8
- data/lib/head_music/style/annotations/single_large_leaps.rb +16 -0
- data/lib/head_music/style/annotations/{up_to_thirteen_notes.rb → up_to_fourteen_notes.rb} +3 -3
- data/lib/head_music/style/mark.rb +4 -0
- data/lib/head_music/style/rulesets/davis_lybbert_cantus_firmus.rb +28 -0
- data/lib/head_music/style/rulesets/first_species_melody.rb +2 -2
- data/lib/head_music/style/rulesets/{cantus_firmus.rb → fux_cantus_firmus.rb} +3 -3
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +14 -9
- metadata +9 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 445b3a56d6b47014d6baa3d5a194f2721aebd04d
|
4
|
+
data.tar.gz: 5eade45431e7167069a5f669713dab209c70529c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dfaf16a40716354c6364ddcdc749046a8af9800d838f190e829a56a38e9a5ae110757dc2aab69154c0290a279bcd2a7db6a9b7d10f56afc7d99e54f5c3b8a3b7
|
7
|
+
data.tar.gz: 8736a3fb3564809f62b64a5a7e502563ba08a484b15b1b80c8d4540c668b8ebbff1fa8ce077bb4f5be4b921ac53141d497e79bdaec25c0cbcbe170ea58b2cb94
|
data/lib/head_music/interval.rb
CHANGED
@@ -7,14 +7,11 @@ class HeadMusic::Interval
|
|
7
7
|
|
8
8
|
attr_reader :semitones
|
9
9
|
|
10
|
-
def self.get(
|
10
|
+
def self.get(identifier)
|
11
11
|
@intervals ||= {}
|
12
|
-
|
13
|
-
|
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
|
data/lib/head_music/placement.rb
CHANGED
@@ -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
|
|
@@ -9,8 +9,9 @@ module HeadMusic
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def messages
|
12
|
-
annotations.reject(&:
|
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
|
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.
|
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
|
@@ -15,25 +15,21 @@ class HeadMusic::Style::Annotations::AvoidOverlappingVoices < HeadMusic::Style::
|
|
15
15
|
end
|
16
16
|
|
17
17
|
def overlappings_of_lower_voices
|
18
|
-
|
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
|
-
|
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
|
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
|
-
|
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 -
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
33
|
-
|
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::
|
5
|
-
MAXIMUM_NOTES =
|
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[
|
11
|
+
HeadMusic::Style::Mark.for_each(notes[MAXIMUM_NOTES..-1])
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
@@ -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::
|
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::
|
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::
|
21
|
+
HeadMusic::Style::Annotations::UpToFourteenNotes,
|
22
22
|
]
|
23
23
|
|
24
24
|
def self.analyze(voice)
|
data/lib/head_music/version.rb
CHANGED
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/
|
84
|
+
require 'head_music/style/annotations/up_to_fourteen_notes'
|
74
85
|
|
75
|
-
require 'head_music/style/rulesets/
|
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.
|
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-
|
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/
|
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/
|
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
|