head_music 0.17.0 → 0.18.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +12 -1145
- data/Gemfile +8 -2
- data/Rakefile +7 -5
- data/bin/console +4 -3
- data/circle.yml +1 -1
- data/head_music.gemspec +19 -17
- data/lib/head_music/bar.rb +2 -0
- data/lib/head_music/chord.rb +24 -6
- data/lib/head_music/circle.rb +5 -0
- data/lib/head_music/clef.rb +5 -2
- data/lib/head_music/composition.rb +3 -0
- data/lib/head_music/consonance.rb +5 -2
- data/lib/head_music/functional_interval.rb +84 -47
- data/lib/head_music/grand_staff.rb +11 -11
- data/lib/head_music/harmonic_interval.rb +13 -7
- data/lib/head_music/instrument.rb +9 -6
- data/lib/head_music/interval.rb +13 -7
- data/lib/head_music/key_signature.rb +8 -5
- data/lib/head_music/language.rb +21 -14
- data/lib/head_music/letter_name.rb +13 -10
- data/lib/head_music/melodic_interval.rb +10 -4
- data/lib/head_music/meter.rb +12 -12
- data/lib/head_music/motion.rb +12 -9
- data/lib/head_music/named_rudiment.rb +22 -20
- data/lib/head_music/note.rb +7 -1
- data/lib/head_music/octave.rb +9 -7
- data/lib/head_music/pitch.rb +54 -27
- data/lib/head_music/pitch_class.rb +17 -12
- data/lib/head_music/placement.rb +22 -9
- data/lib/head_music/position.rb +8 -9
- data/lib/head_music/quality.rb +9 -6
- data/lib/head_music/rhythm.rb +2 -0
- data/lib/head_music/rhythmic_unit.rb +29 -19
- data/lib/head_music/rhythmic_value.rb +5 -2
- data/lib/head_music/scale.rb +65 -45
- data/lib/head_music/scale_degree.rb +9 -6
- data/lib/head_music/scale_type.rb +70 -30
- data/lib/head_music/sign.rb +18 -13
- data/lib/head_music/spelling.rb +14 -10
- data/lib/head_music/staff.rb +4 -1
- data/lib/head_music/style/analysis.rb +36 -34
- data/lib/head_music/style/annotation.rb +14 -13
- data/lib/head_music/style/annotations/always_move.rb +7 -6
- data/lib/head_music/style/annotations/approach_perfection_contrarily.rb +5 -2
- data/lib/head_music/style/annotations/at_least_eight_notes.rb +10 -8
- data/lib/head_music/style/annotations/avoid_crossing_voices.rb +11 -8
- data/lib/head_music/style/annotations/avoid_overlapping_voices.rb +17 -10
- data/lib/head_music/style/annotations/consonant_climax.rb +18 -15
- data/lib/head_music/style/annotations/consonant_downbeats.rb +6 -3
- data/lib/head_music/style/annotations/diatonic.rb +8 -5
- data/lib/head_music/style/annotations/direction_changes.rb +8 -6
- data/lib/head_music/style/annotations/end_on_perfect_consonance.rb +5 -5
- data/lib/head_music/style/annotations/end_on_tonic.rb +7 -6
- data/lib/head_music/style/annotations/frequent_direction_changes.rb +6 -3
- data/lib/head_music/style/annotations/limit_octave_leaps.rb +9 -7
- data/lib/head_music/style/annotations/moderate_direction_changes.rb +6 -3
- data/lib/head_music/style/annotations/mostly_conjunct.rb +8 -5
- data/lib/head_music/style/annotations/no_rests.rb +6 -3
- data/lib/head_music/style/annotations/no_unisons_in_middle.rb +6 -3
- data/lib/head_music/style/annotations/notes_same_length.rb +9 -6
- data/lib/head_music/style/annotations/one_to_one.rb +10 -7
- data/lib/head_music/style/annotations/prefer_contrary_motion.rb +6 -3
- data/lib/head_music/style/annotations/prefer_imperfect.rb +7 -6
- data/lib/head_music/style/annotations/prepare_octave_leaps.rb +21 -12
- data/lib/head_music/style/annotations/recover_large_leaps.rb +17 -12
- data/lib/head_music/style/annotations/singable_intervals.rb +8 -5
- data/lib/head_music/style/annotations/singable_range.rb +7 -6
- data/lib/head_music/style/annotations/single_large_leaps.rb +6 -3
- data/lib/head_music/style/annotations/start_on_perfect_consonance.rb +6 -5
- data/lib/head_music/style/annotations/start_on_tonic.rb +6 -5
- data/lib/head_music/style/annotations/step_down_to_final_note.rb +12 -12
- data/lib/head_music/style/annotations/step_out_of_unison.rb +10 -7
- data/lib/head_music/style/annotations/step_to_final_note.rb +6 -3
- data/lib/head_music/style/annotations/step_up_to_final_note.rb +12 -12
- data/lib/head_music/style/annotations/up_to_fourteen_notes.rb +6 -5
- data/lib/head_music/style/mark.rb +10 -4
- data/lib/head_music/style/rulesets/first_species_harmony.rb +6 -3
- data/lib/head_music/style/rulesets/first_species_melody.rb +6 -3
- data/lib/head_music/style/rulesets/fux_cantus_firmus.rb +6 -3
- data/lib/head_music/style/rulesets/modern_cantus_firmus.rb +6 -3
- data/lib/head_music/utilities/hash_key.rb +9 -7
- data/lib/head_music/version.rb +3 -1
- data/lib/head_music/voice.rb +7 -3
- data/lib/head_music.rb +2 -0
- metadata +4 -4
@@ -1,15 +1,18 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::OneToOne < HeadMusic::Style::Annotation
|
5
8
|
MESSAGE = 'Place a note for each note in the other voice.'
|
6
9
|
|
7
10
|
def marks
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
)
|
12
|
-
|
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
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::PreferContraryMotion < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
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
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::PreferImperfect < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
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
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::PrepareOctaveLeaps < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
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
|
17
|
-
|
18
|
-
|
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
|
25
|
-
|
26
|
-
|
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
|
-
|
2
|
-
|
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 =
|
11
|
+
MESSAGE = 'Recover large leaps by step in the opposite direction.'
|
10
12
|
|
11
13
|
def marks
|
12
|
-
melodic_intervals.
|
13
|
-
|
14
|
-
|
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
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::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 =
|
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
|
-
|
2
|
-
|
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
|
-
|
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.
|
19
|
+
!notes.empty? ? [range.number - MAXIMUM_RANGE, 0].max : 0
|
19
20
|
end
|
20
21
|
|
21
22
|
def extremes
|
@@ -1,8 +1,11 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::SingleLargeLeaps < HeadMusic::Style::Annotations::RecoverLargeLeaps
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Recover leaps by step, repetition, opposite direction, or spelling triad.'
|
6
9
|
|
7
10
|
private
|
8
11
|
|
@@ -1,14 +1,15 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
4
6
|
# marks the voice if the first note is not the first or fifth scale degree of the key.
|
5
7
|
class HeadMusic::Style::Annotations::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
|
-
|
10
|
-
|
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
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::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
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::StepDownToFinalNote < HeadMusic::Style::Annotation
|
5
8
|
MESSAGE = 'Step down to the final note.'
|
6
9
|
|
7
10
|
def marks
|
8
|
-
if
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
21
|
+
last_melodic_interval&.descending?
|
22
22
|
end
|
23
23
|
|
24
24
|
def step?
|
25
|
-
last_melodic_interval
|
25
|
+
last_melodic_interval&.step?
|
26
26
|
end
|
27
27
|
|
28
28
|
def last_melodic_interval
|
@@ -1,19 +1,22 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::StepOutOfUnison < HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
8
|
+
MESSAGE = 'Exit a unison by step.'
|
6
9
|
|
7
10
|
def marks
|
8
|
-
|
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
|
16
|
-
melodic_intervals_following_unisons.select(&:
|
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(
|
27
|
+
HeadMusic::MelodicInterval.new(note1, note2) if note1 && note2
|
25
28
|
end.compact
|
26
29
|
end
|
27
30
|
|
@@ -1,6 +1,9 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::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
|
17
|
+
last_melodic_interval&.step?
|
15
18
|
end
|
16
19
|
|
17
20
|
def last_melodic_interval
|
@@ -1,28 +1,28 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
4
7
|
class HeadMusic::Style::Annotations::StepUpToFinalNote < HeadMusic::Style::Annotation
|
5
8
|
MESSAGE = 'Step up to final note.'
|
6
9
|
|
7
10
|
def marks
|
8
|
-
if
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
21
|
+
last_melodic_interval&.ascending?
|
22
22
|
end
|
23
23
|
|
24
24
|
def step?
|
25
|
-
last_melodic_interval
|
25
|
+
last_melodic_interval&.step?
|
26
26
|
end
|
27
27
|
|
28
28
|
def last_melodic_interval
|
@@ -1,15 +1,16 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Module for Annotations.
|
4
|
+
module HeadMusic::Style::Annotations; end
|
3
5
|
|
6
|
+
# A counterpoint guideline
|
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
|
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.
|
11
|
-
start_position = placements.map
|
12
|
-
end_position = placements.map
|
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
|
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
|
-
|
2
|
-
|
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
|
-
|
2
|
-
|
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
|
-
|
2
|
-
|
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
|
-
|
2
|
-
|
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
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
data/lib/head_music/version.rb
CHANGED
data/lib/head_music/voice.rb
CHANGED
@@ -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
|
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(
|
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
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.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-
|
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.
|
208
|
+
version: 2.4.0
|
209
209
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
210
210
|
requirements:
|
211
211
|
- - ">="
|