head_music 0.27.0 → 0.28.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.circleci/config.yml +1 -1
- data/.rubocop.yml +11 -37
- data/.ruby-version +1 -1
- data/.travis.yml +1 -1
- data/Gemfile +8 -6
- data/Rakefile +5 -4
- data/bin/console +3 -3
- data/head_music.gemspec +21 -22
- data/lib/head_music/chromatic_interval.rb +3 -3
- data/lib/head_music/circle.rb +18 -14
- data/lib/head_music/clef.rb +3 -3
- data/lib/head_music/content/bar.rb +1 -1
- data/lib/head_music/content/composition.rb +1 -1
- data/lib/head_music/content/note.rb +1 -1
- data/lib/head_music/content/placement.rb +5 -5
- data/lib/head_music/content/position.rb +3 -3
- data/lib/head_music/content/rhythmic_value.rb +8 -8
- data/lib/head_music/content/voice.rb +1 -1
- data/lib/head_music/diatonic_interval.rb +41 -43
- data/lib/head_music/grand_staff.rb +8 -8
- data/lib/head_music/instrument.rb +8 -8
- data/lib/head_music/interval_cycle.rb +10 -12
- data/lib/head_music/key_signature.rb +12 -13
- data/lib/head_music/letter_name.rb +7 -7
- data/lib/head_music/melodic_interval.rb +1 -1
- data/lib/head_music/meter.rb +17 -12
- data/lib/head_music/named.rb +3 -3
- data/lib/head_music/pitch/enharmonic_equivalence.rb +2 -2
- data/lib/head_music/pitch/octave_equivalence.rb +1 -1
- data/lib/head_music/pitch.rb +9 -8
- data/lib/head_music/pitch_class.rb +18 -4
- data/lib/head_music/pitch_class_set.rb +5 -5
- data/lib/head_music/pitch_set.rb +9 -6
- data/lib/head_music/quality.rb +15 -14
- data/lib/head_music/reference_pitch.rb +23 -23
- data/lib/head_music/register.rb +2 -2
- data/lib/head_music/rhythmic_unit.rb +9 -9
- data/lib/head_music/scale.rb +4 -4
- data/lib/head_music/scale_degree.rb +2 -2
- data/lib/head_music/scale_type.rb +13 -13
- data/lib/head_music/sign.rb +12 -11
- data/lib/head_music/solmization.rb +3 -3
- data/lib/head_music/sonority.rb +2 -2
- data/lib/head_music/spelling.rb +4 -6
- data/lib/head_music/staff.rb +1 -1
- data/lib/head_music/style/analysis.rb +1 -1
- data/lib/head_music/style/annotation.rb +7 -7
- data/lib/head_music/style/guidelines/always_move.rb +4 -4
- data/lib/head_music/style/guidelines/approach_perfection_contrarily.rb +1 -1
- data/lib/head_music/style/guidelines/at_least_eight_notes.rb +3 -3
- data/lib/head_music/style/guidelines/avoid_crossing_voices.rb +6 -6
- data/lib/head_music/style/guidelines/avoid_overlapping_voices.rb +1 -1
- data/lib/head_music/style/guidelines/consonant_climax.rb +1 -1
- data/lib/head_music/style/guidelines/consonant_downbeats.rb +1 -1
- data/lib/head_music/style/guidelines/diatonic.rb +2 -2
- data/lib/head_music/style/guidelines/end_on_perfect_consonance.rb +1 -1
- data/lib/head_music/style/guidelines/end_on_tonic.rb +1 -1
- data/lib/head_music/style/guidelines/frequent_direction_changes.rb +1 -1
- data/lib/head_music/style/guidelines/limit_octave_leaps.rb +1 -1
- data/lib/head_music/style/guidelines/moderate_direction_changes.rb +1 -1
- data/lib/head_music/style/guidelines/mostly_conjunct.rb +4 -4
- data/lib/head_music/style/guidelines/no_rests.rb +1 -1
- data/lib/head_music/style/guidelines/no_unisons_in_middle.rb +1 -1
- data/lib/head_music/style/guidelines/notes_same_length.rb +4 -4
- data/lib/head_music/style/guidelines/one_to_one.rb +1 -1
- data/lib/head_music/style/guidelines/prefer_contrary_motion.rb +1 -1
- data/lib/head_music/style/guidelines/prefer_imperfect.rb +1 -1
- data/lib/head_music/style/guidelines/prepare_octave_leaps.rb +1 -1
- data/lib/head_music/style/guidelines/recover_large_leaps.rb +1 -1
- data/lib/head_music/style/guidelines/singable_intervals.rb +1 -1
- data/lib/head_music/style/guidelines/singable_range.rb +1 -1
- data/lib/head_music/style/guidelines/single_large_leaps.rb +1 -1
- data/lib/head_music/style/guidelines/start_on_perfect_consonance.rb +1 -1
- data/lib/head_music/style/guidelines/start_on_tonic.rb +1 -1
- data/lib/head_music/style/guidelines/step_down_to_final_note.rb +2 -2
- data/lib/head_music/style/guidelines/step_out_of_unison.rb +1 -1
- data/lib/head_music/style/guidelines/step_to_final_note.rb +2 -2
- data/lib/head_music/style/guidelines/step_up_to_final_note.rb +2 -2
- data/lib/head_music/style/guidelines/up_to_fourteen_notes.rb +2 -2
- data/lib/head_music/style/guides/first_species_harmony.rb +9 -1
- data/lib/head_music/style/guides/first_species_melody.rb +1 -1
- data/lib/head_music/style/guides/fux_cantus_firmus.rb +1 -1
- data/lib/head_music/style/guides/modern_cantus_firmus.rb +1 -1
- data/lib/head_music/style/mark.rb +1 -1
- data/lib/head_music/utilities/hash_key.rb +1 -1
- data/lib/head_music/version.rb +1 -1
- data/lib/head_music.rb +91 -91
- metadata +6 -20
data/lib/head_music/register.rb
CHANGED
@@ -4,10 +4,10 @@
|
|
4
4
|
class HeadMusic::RhythmicUnit
|
5
5
|
include HeadMusic::Named
|
6
6
|
|
7
|
-
MULTIPLES = [
|
7
|
+
MULTIPLES = ["whole", "double whole", "longa", "maxima"].freeze
|
8
8
|
FRACTIONS = [
|
9
|
-
|
10
|
-
|
9
|
+
"whole", "half", "quarter", "eighth", "sixteenth", "thirty-second",
|
10
|
+
"sixty-fourth", "hundred twenty-eighth", "two hundred fifty-sixth"
|
11
11
|
].freeze
|
12
12
|
|
13
13
|
BRITISH_MULTIPLE_NAMES = %w[semibreve breve longa maxima].freeze
|
@@ -80,26 +80,26 @@ class HeadMusic::RhythmicUnit
|
|
80
80
|
end
|
81
81
|
|
82
82
|
def numerator_exponent
|
83
|
-
multiples_keys.index(name.gsub(/\W+/,
|
83
|
+
multiples_keys.index(name.gsub(/\W+/, "_")) || british_multiples_keys.index(name.gsub(/\W+/, "_")) || 0
|
84
84
|
end
|
85
85
|
|
86
86
|
def multiples_keys
|
87
|
-
MULTIPLES.map { |multiple| multiple.gsub(/\W+/,
|
87
|
+
MULTIPLES.map { |multiple| multiple.gsub(/\W+/, "_") }
|
88
88
|
end
|
89
89
|
|
90
90
|
def british_multiples_keys
|
91
|
-
BRITISH_MULTIPLE_NAMES.map { |multiple| multiple.gsub(/\W+/,
|
91
|
+
BRITISH_MULTIPLE_NAMES.map { |multiple| multiple.gsub(/\W+/, "_") }
|
92
92
|
end
|
93
93
|
|
94
94
|
def denominator_exponent
|
95
|
-
fractions_keys.index(name.gsub(/\W+/,
|
95
|
+
fractions_keys.index(name.gsub(/\W+/, "_")) || british_fractions_keys.index(name.gsub(/\W+/, "_")) || 0
|
96
96
|
end
|
97
97
|
|
98
98
|
def fractions_keys
|
99
|
-
FRACTIONS.map { |fraction| fraction.gsub(/\W+/,
|
99
|
+
FRACTIONS.map { |fraction| fraction.gsub(/\W+/, "_") }
|
100
100
|
end
|
101
101
|
|
102
102
|
def british_fractions_keys
|
103
|
-
BRITISH_DIVISION_NAMES.map { |fraction| fraction.gsub(/\W+/,
|
103
|
+
BRITISH_DIVISION_NAMES.map { |fraction| fraction.gsub(/\W+/, "_") }
|
104
104
|
end
|
105
105
|
end
|
data/lib/head_music/scale.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# A scale contains ordered pitches starting at a tonal center.
|
4
4
|
class HeadMusic::Scale
|
5
|
-
SCALE_REGEX = /^[A-G][#b]?\s+\w
|
5
|
+
SCALE_REGEX = /^[A-G][#b]?\s+\w+$/
|
6
6
|
|
7
7
|
def self.get(root_pitch, scale_type = nil)
|
8
8
|
root_pitch, scale_type = root_pitch.split(/\s+/) if root_pitch.is_a?(String) && scale_type =~ SCALE_REGEX
|
@@ -10,7 +10,7 @@ class HeadMusic::Scale
|
|
10
10
|
scale_type = HeadMusic::ScaleType.get(scale_type || :major)
|
11
11
|
@scales ||= {}
|
12
12
|
hash_key = HeadMusic::Utilities::HashKey.for(
|
13
|
-
[root_pitch, scale_type].join(
|
13
|
+
[root_pitch, scale_type].join(" ").gsub(/#|♯/, "sharp").gsub(/(\w)[b♭]/, '\\1flat')
|
14
14
|
)
|
15
15
|
@scales[hash_key] ||= new(root_pitch, scale_type)
|
16
16
|
end
|
@@ -70,7 +70,7 @@ class HeadMusic::Scale
|
|
70
70
|
end
|
71
71
|
|
72
72
|
def direction_sign(direction)
|
73
|
-
direction == :descending ? -1 : 1
|
73
|
+
(direction == :descending) ? -1 : 1
|
74
74
|
end
|
75
75
|
|
76
76
|
def direction_intervals(direction)
|
@@ -103,7 +103,7 @@ class HeadMusic::Scale
|
|
103
103
|
def diatonic_letter_for_step(direction, step)
|
104
104
|
return unless scale_type.diatonic?
|
105
105
|
|
106
|
-
direction == :ascending ? letter_name_series_ascending[step % 7] : letter_name_series_descending[step % 7]
|
106
|
+
(direction == :ascending) ? letter_name_series_ascending[step % 7] : letter_name_series_descending[step % 7]
|
107
107
|
end
|
108
108
|
|
109
109
|
def child_scale_letter_for_step(semitones_from_root)
|
@@ -5,7 +5,7 @@
|
|
5
5
|
class HeadMusic::ScaleDegree
|
6
6
|
include Comparable
|
7
7
|
|
8
|
-
NAME_FOR_DIATONIC_DEGREE = [nil,
|
8
|
+
NAME_FOR_DIATONIC_DEGREE = [nil, "tonic", "supertonic", "mediant", "subdominant", "dominant", "submediant"].freeze
|
9
9
|
|
10
10
|
attr_reader :key_signature, :spelling
|
11
11
|
|
@@ -44,7 +44,7 @@ class HeadMusic::ScaleDegree
|
|
44
44
|
return unless scale_type.diatonic?
|
45
45
|
|
46
46
|
NAME_FOR_DIATONIC_DEGREE[degree] ||
|
47
|
-
(scale_type.intervals.last == 1 || sign ==
|
47
|
+
((scale_type.intervals.last == 1 || sign == "#") ? "leading tone" : "subtonic")
|
48
48
|
end
|
49
49
|
|
50
50
|
private
|
@@ -25,7 +25,7 @@ class HeadMusic::ScaleType
|
|
25
25
|
iv: [:lydian],
|
26
26
|
v: [:mixolydian],
|
27
27
|
vi: %i[aeolian minor natural_minor],
|
28
|
-
vii: [:locrian]
|
28
|
+
vii: [:locrian]
|
29
29
|
}.freeze
|
30
30
|
|
31
31
|
CHROMATIC = ([H] * 12)
|
@@ -35,7 +35,7 @@ class HeadMusic::ScaleType
|
|
35
35
|
def self._modes
|
36
36
|
{}.tap do |modes|
|
37
37
|
MODE_NAMES.each do |roman_numeral, aliases|
|
38
|
-
intervals = {
|
38
|
+
intervals = {ascending: const_get(roman_numeral.upcase)}
|
39
39
|
modes[roman_numeral] = intervals
|
40
40
|
aliases.each { |name| modes[name] = intervals }
|
41
41
|
end
|
@@ -44,30 +44,30 @@ class HeadMusic::ScaleType
|
|
44
44
|
|
45
45
|
def self._minor_scales
|
46
46
|
{
|
47
|
-
harmonic_minor: {
|
48
|
-
melodic_minor: {
|
47
|
+
harmonic_minor: {ascending: HARMONIC_MINOR},
|
48
|
+
melodic_minor: {ascending: MELODIC_MINOR_ASCENDING, descending: VI.reverse}
|
49
49
|
}
|
50
50
|
end
|
51
51
|
|
52
52
|
def self._chromatic_scales
|
53
|
-
{
|
53
|
+
{chromatic: {ascending: CHROMATIC}}
|
54
54
|
end
|
55
55
|
|
56
56
|
def self._pentatonic_scales
|
57
57
|
{
|
58
|
-
minor_pentatonic: {
|
59
|
-
major_pentatonic: {
|
60
|
-
egyptian_pentatonic: {
|
61
|
-
blues_minor_pentatonic: {
|
62
|
-
blues_major_pentatonic: {
|
58
|
+
minor_pentatonic: {ascending: MINOR_PENTATONIC, parent_name: :minor},
|
59
|
+
major_pentatonic: {ascending: MINOR_PENTATONIC.rotate, parent_name: :major},
|
60
|
+
egyptian_pentatonic: {ascending: MINOR_PENTATONIC.rotate(2), parent_name: :minor},
|
61
|
+
blues_minor_pentatonic: {ascending: MINOR_PENTATONIC.rotate(3), parent_name: :minor},
|
62
|
+
blues_major_pentatonic: {ascending: MINOR_PENTATONIC.rotate(4), parent_name: :major}
|
63
63
|
}
|
64
64
|
end
|
65
65
|
|
66
66
|
def self._exotic_scales
|
67
67
|
# 'octatonic' is also called the 'whole-half diminished scale'
|
68
68
|
{
|
69
|
-
octatonic: {
|
70
|
-
whole_tone: {
|
69
|
+
octatonic: {ascending: [W, H, W, H, W, H, W, H]},
|
70
|
+
whole_tone: {ascending: [W, W, W, W, W, W]}
|
71
71
|
}
|
72
72
|
end
|
73
73
|
|
@@ -99,7 +99,7 @@ class HeadMusic::ScaleType
|
|
99
99
|
end
|
100
100
|
|
101
101
|
attr_reader :name, :ascending_intervals, :descending_intervals, :parent_name
|
102
|
-
|
102
|
+
alias_method :intervals, :ascending_intervals
|
103
103
|
|
104
104
|
delegate :to_s, to: :name
|
105
105
|
|
data/lib/head_music/sign.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require
|
3
|
+
require "head_music/musical_symbol"
|
4
4
|
|
5
5
|
# A Sign is a symbol that modifies pitch, such as a sharp, flat, or natural.
|
6
|
+
# In French, sharps and flats in the key signature are called "altérations".
|
6
7
|
class HeadMusic::Sign
|
7
8
|
include Comparable
|
8
9
|
|
@@ -13,24 +14,24 @@ class HeadMusic::Sign
|
|
13
14
|
SIGN_RECORDS = [
|
14
15
|
{
|
15
16
|
identifier: :sharp, cents: 100,
|
16
|
-
symbols: [{
|
17
|
+
symbols: [{ascii: "#", unicode: "♯", html_entity: "♯"}]
|
17
18
|
},
|
18
19
|
{
|
19
20
|
identifier: :flat, cents: -100,
|
20
|
-
symbols: [{
|
21
|
+
symbols: [{ascii: "b", unicode: "♭", html_entity: "♭"}]
|
21
22
|
},
|
22
23
|
{
|
23
24
|
identifier: :natural, cents: 0,
|
24
|
-
symbols: [{
|
25
|
+
symbols: [{ascii: "", unicode: "♮", html_entity: "♮"}]
|
25
26
|
},
|
26
27
|
{
|
27
28
|
identifier: :double_sharp, cents: 200,
|
28
|
-
symbols: [{
|
29
|
+
symbols: [{ascii: "x", unicode: "𝄪", html_entity: "𝄪"}]
|
29
30
|
},
|
30
31
|
{
|
31
32
|
identifier: :double_flat, cents: -200,
|
32
|
-
symbols: [{
|
33
|
-
}
|
33
|
+
symbols: [{ascii: "bb", unicode: "𝄫", html_entity: "𝄫"}]
|
34
|
+
}
|
34
35
|
].freeze
|
35
36
|
|
36
37
|
SIGN_IDENTIFIERS = SIGN_RECORDS.map { |attributes| attributes[:identifier] }.freeze
|
@@ -44,7 +45,7 @@ class HeadMusic::Sign
|
|
44
45
|
end
|
45
46
|
|
46
47
|
def self.matcher
|
47
|
-
@matcher ||= Regexp.new symbols.join(
|
48
|
+
@matcher ||= Regexp.new symbols.join("|")
|
48
49
|
end
|
49
50
|
|
50
51
|
def self.symbol?(candidate)
|
@@ -66,12 +67,12 @@ class HeadMusic::Sign
|
|
66
67
|
end
|
67
68
|
|
68
69
|
def name
|
69
|
-
identifier.to_s.tr(
|
70
|
+
identifier.to_s.tr("_", " ")
|
70
71
|
end
|
71
72
|
|
72
73
|
def representions
|
73
|
-
[identifier, identifier.to_s, name, ascii, unicode, html_entity]
|
74
|
-
reject { |representation| representation.to_s.strip ==
|
74
|
+
[identifier, identifier.to_s, name, ascii, unicode, html_entity]
|
75
|
+
.reject { |representation| representation.to_s.strip == "" }
|
75
76
|
end
|
76
77
|
|
77
78
|
def semitones
|
@@ -5,9 +5,9 @@
|
|
5
5
|
class HeadMusic::Solmization
|
6
6
|
include HeadMusic::Named
|
7
7
|
|
8
|
-
DEFAULT_SOLMIZATION =
|
8
|
+
DEFAULT_SOLMIZATION = "solfège"
|
9
9
|
|
10
|
-
RECORDS = YAML.load_file(File.expand_path(
|
10
|
+
RECORDS = YAML.load_file(File.expand_path("solmizations.yml", __dir__)).freeze
|
11
11
|
|
12
12
|
attr_reader :syllables
|
13
13
|
|
@@ -46,7 +46,7 @@ class HeadMusic::Solmization
|
|
46
46
|
|
47
47
|
def initialize_localized_names(list)
|
48
48
|
@localized_names = (list || []).map do |name_attributes|
|
49
|
-
HeadMusic::Named::LocalizedName.new(name_attributes.slice(:name, :locale_code, :abbreviation))
|
49
|
+
HeadMusic::Named::LocalizedName.new(**name_attributes.slice(:name, :locale_code, :abbreviation))
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
data/lib/head_music/sonority.rb
CHANGED
@@ -23,7 +23,7 @@ class HeadMusic::Sonority
|
|
23
23
|
minor_six_nine_chord: %w[M2 m3 P5 M6],
|
24
24
|
suspended_four_chord: %w[P4 P5],
|
25
25
|
suspended_two_chord: %w[M2 P5],
|
26
|
-
quartal_chord: %w[P4 m7]
|
26
|
+
quartal_chord: %w[P4 m7]
|
27
27
|
}.freeze
|
28
28
|
|
29
29
|
attr_reader :pitch_set
|
@@ -106,7 +106,7 @@ class HeadMusic::Sonority
|
|
106
106
|
end.to_f / inversion.diatonic_intervals.length > 0.5
|
107
107
|
end
|
108
108
|
end
|
109
|
-
|
109
|
+
alias_method :quintal?, :quartal?
|
110
110
|
|
111
111
|
def diatonic_intervals_above_bass_pitch
|
112
112
|
return nil unless identifier
|
data/lib/head_music/spelling.rb
CHANGED
@@ -4,11 +4,9 @@
|
|
4
4
|
# Composite of a LetterName and an optional Sign.
|
5
5
|
# Does not include the octave. See Pitch for that.
|
6
6
|
class HeadMusic::Spelling
|
7
|
-
MATCHER = /^\s*([A-G])(#{HeadMusic::Sign.matcher}?)(-?\d+)?\s*$/i
|
7
|
+
MATCHER = /^\s*([A-G])(#{HeadMusic::Sign.matcher}?)(-?\d+)?\s*$/i
|
8
8
|
|
9
|
-
attr_reader :pitch_class
|
10
|
-
attr_reader :letter_name
|
11
|
-
attr_reader :sign
|
9
|
+
attr_reader :pitch_class, :letter_name, :sign
|
12
10
|
|
13
11
|
delegate :number, to: :pitch_class, prefix: true
|
14
12
|
delegate :to_i, to: :pitch_class_number
|
@@ -114,8 +112,8 @@ class HeadMusic::Spelling
|
|
114
112
|
spelling != other && spelling.pitch_class_number == other.pitch_class_number
|
115
113
|
end
|
116
114
|
|
117
|
-
|
118
|
-
|
115
|
+
alias_method :enharmonic?, :enharmonic_equivalent?
|
116
|
+
alias_method :equivalent?, :enharmonic_equivalent?
|
119
117
|
|
120
118
|
private_class_method :new
|
121
119
|
end
|
data/lib/head_music/staff.rb
CHANGED
@@ -5,7 +5,7 @@ class HeadMusic::Staff
|
|
5
5
|
DEFAULT_LINE_COUNT = 5
|
6
6
|
|
7
7
|
attr_reader :default_clef, :line_count, :instrument
|
8
|
-
|
8
|
+
alias_method :clef, :default_clef
|
9
9
|
|
10
10
|
def initialize(default_clef, instrument: nil, line_count: nil)
|
11
11
|
@default_clef = HeadMusic::Clef.get(default_clef)
|
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
# An Annotation encapsulates an issue with or comment on a voice
|
4
4
|
class HeadMusic::Style::Annotation
|
5
|
-
MESSAGE =
|
5
|
+
MESSAGE = "Write music."
|
6
6
|
|
7
7
|
attr_reader :voice
|
8
8
|
|
@@ -104,16 +104,16 @@ class HeadMusic::Style::Annotation
|
|
104
104
|
|
105
105
|
def downbeat_harmonic_intervals
|
106
106
|
@downbeat_harmonic_intervals ||=
|
107
|
-
cantus_firmus.notes
|
108
|
-
|
109
|
-
|
107
|
+
cantus_firmus.notes
|
108
|
+
.map { |note| HeadMusic::HarmonicInterval.new(note.voice, voice, note.position) }
|
109
|
+
.reject { |interval| interval.notes.length < 2 }
|
110
110
|
end
|
111
111
|
|
112
112
|
def harmonic_intervals
|
113
113
|
@harmonic_intervals ||=
|
114
|
-
positions
|
115
|
-
|
116
|
-
|
114
|
+
positions
|
115
|
+
.map { |position| HeadMusic::HarmonicInterval.new(cantus_firmus, voice, position) }
|
116
|
+
.reject { |harmonic_interval| harmonic_interval.notes.length < 2 }
|
117
117
|
end
|
118
118
|
|
119
119
|
def positions
|
@@ -5,11 +5,11 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::AlwaysMove < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Always move to a different note."
|
9
9
|
|
10
10
|
def marks
|
11
|
-
melodic_intervals
|
12
|
-
select { |interval| interval.perfect? && interval.unison? }
|
13
|
-
map { |interval| HeadMusic::Style::Mark.for_all(interval.notes) }
|
11
|
+
melodic_intervals
|
12
|
+
.select { |interval| interval.perfect? && interval.unison? }
|
13
|
+
.map { |interval| HeadMusic::Style::Mark.for_all(interval.notes) }
|
14
14
|
end
|
15
15
|
end
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::ApproachPerfectionContrarily < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Approach perfect consonances by contrary motion."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
motions_to_perfect_consonance_approached_directly.map do |bad_motion|
|
@@ -7,7 +7,7 @@ module HeadMusic::Style::Guidelines; end
|
|
7
7
|
class HeadMusic::Style::Guidelines::AtLeastEightNotes < HeadMusic::Style::Annotation
|
8
8
|
MINIMUM_NOTES = 8
|
9
9
|
|
10
|
-
MESSAGE =
|
10
|
+
MESSAGE = "Write at least eight notes."
|
11
11
|
|
12
12
|
def marks
|
13
13
|
placements.empty? ? no_placements_mark : deficiency_mark
|
@@ -17,8 +17,8 @@ class HeadMusic::Style::Guidelines::AtLeastEightNotes < HeadMusic::Style::Annota
|
|
17
17
|
|
18
18
|
def no_placements_mark
|
19
19
|
HeadMusic::Style::Mark.new(
|
20
|
-
HeadMusic::Position.new(composition,
|
21
|
-
HeadMusic::Position.new(composition,
|
20
|
+
HeadMusic::Position.new(composition, "1:1"),
|
21
|
+
HeadMusic::Position.new(composition, "2:1"),
|
22
22
|
fitness: 0
|
23
23
|
)
|
24
24
|
end
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::AvoidCrossingVoices < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Avoid crossing voices. Maintain the high-low relationship between voices."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
crossings.map do |crossing|
|
@@ -22,11 +22,11 @@ class HeadMusic::Style::Guidelines::AvoidCrossingVoices < HeadMusic::Style::Anno
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def predominant_pitch_orientation
|
25
|
-
pitch_orientations
|
26
|
-
compact
|
27
|
-
group_by { |orientation| orientation }
|
28
|
-
max { |a, b| a[1].length <=> b[1].length }
|
29
|
-
first
|
25
|
+
pitch_orientations
|
26
|
+
.compact
|
27
|
+
.group_by { |orientation| orientation }
|
28
|
+
.max { |a, b| a[1].length <=> b[1].length }
|
29
|
+
.first
|
30
30
|
end
|
31
31
|
|
32
32
|
def pitch_orientations
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::AvoidOverlappingVoices < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Avoid overlapping voices. Maintain the high-low relationship between voices even for adjacent notes."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
overlappings
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::ConsonantClimax < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Peak on a consonant high or low note one time or twice with a step between."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
HeadMusic::Style::Mark.for_each(highest_notes) unless adherent_climax?
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::ConsonantDownbeats < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Use consonant harmonic intervals on every downbeat."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
dissonant_pairs.map do |dissonant_pair|
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::Diatonic < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Use only notes in the key signature."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
HeadMusic::Style::Mark.for_each(notes_not_in_key_excluding_penultimate_leading_tone)
|
@@ -17,7 +17,7 @@ class HeadMusic::Style::Guidelines::Diatonic < HeadMusic::Style::Annotation
|
|
17
17
|
notes_not_in_key.reject do |note|
|
18
18
|
penultimate_note &&
|
19
19
|
note == penultimate_note &&
|
20
|
-
HeadMusic::ScaleDegree.new(key_signature, note.pitch.spelling).sign ==
|
20
|
+
HeadMusic::ScaleDegree.new(key_signature, note.pitch.spelling).sign == "#"
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# marks the voice if the first note is not the first or fifth scale degree of the key.
|
7
7
|
class HeadMusic::Style::Guidelines::EndOnPerfectConsonance < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "End on the first or the fifth scale degree."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
HeadMusic::Style::Mark.for(last_note) if last_note && !ends_on_perfect_consonance?
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::EndOnTonic < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "End on the first scale degree."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
HeadMusic::Style::Mark.for(notes.last) if notes.any? && !ends_on_tonic?
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::FrequentDirectionChanges < HeadMusic::Style::Guidelines::DirectionChanges
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Change melodic direction frequently."
|
9
9
|
|
10
10
|
def self.maximum_notes_per_direction
|
11
11
|
3
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline: Use a maximum of one octave leap.
|
7
7
|
class HeadMusic::Style::Guidelines::LimitOctaveLeaps < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Use a maximum of one octave leap."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
return if octave_leaps.length <= 1
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::ModerateDirectionChanges < HeadMusic::Style::Guidelines::DirectionChanges
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Change melodic direction occasionally."
|
9
9
|
|
10
10
|
def self.maximum_notes_per_direction
|
11
11
|
5
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::MostlyConjunct < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Use mostly conjunct motion."
|
9
9
|
|
10
10
|
MINIMUM_CONJUNCT_PORTION = HeadMusic::GOLDEN_RATIO_INVERSE**2
|
11
11
|
# ~38%
|
@@ -18,9 +18,9 @@ class HeadMusic::Style::Guidelines::MostlyConjunct < HeadMusic::Style::Annotatio
|
|
18
18
|
private
|
19
19
|
|
20
20
|
def marks_for_skips_and_leaps
|
21
|
-
melodic_intervals
|
22
|
-
reject(&:step?)
|
23
|
-
map { |interval| HeadMusic::Style::Mark.for_all(interval.notes, fitness: HeadMusic::SMALL_PENALTY_FACTOR) }
|
21
|
+
melodic_intervals
|
22
|
+
.reject(&:step?)
|
23
|
+
.map { |interval| HeadMusic::Style::Mark.for_all(interval.notes, fitness: HeadMusic::SMALL_PENALTY_FACTOR) }
|
24
24
|
end
|
25
25
|
|
26
26
|
def conjunct_ratio
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::NoRests < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Place a note in each measure."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
HeadMusic::Style::Mark.for_each(rests)
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::NoUnisonsInMiddle < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Unisons may only be used in the first and last note."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
unison_pairs.map do |notes|
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::NotesSameLength < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Use consistent rhythmic unit."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
HeadMusic::Style::Mark.for_each(all_wrong_length_notes)
|
@@ -31,7 +31,7 @@ class HeadMusic::Style::Guidelines::NotesSameLength < HeadMusic::Style::Annotati
|
|
31
31
|
last_note.nil? ||
|
32
32
|
[
|
33
33
|
first_most_common_rhythmic_value.total_value,
|
34
|
-
first_most_common_rhythmic_value.total_value * 2
|
34
|
+
first_most_common_rhythmic_value.total_value * 2
|
35
35
|
].include?(last_note.rhythmic_value.total_value)
|
36
36
|
end
|
37
37
|
|
@@ -47,7 +47,7 @@ class HeadMusic::Style::Guidelines::NotesSameLength < HeadMusic::Style::Annotati
|
|
47
47
|
@first_most_common_rhythmic_value ||= begin
|
48
48
|
candidates = most_common_rhythmic_values
|
49
49
|
first_match = notes.detect { |note| candidates.include?(note.rhythmic_value) }
|
50
|
-
first_match
|
50
|
+
first_match&.rhythmic_value
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
@@ -60,7 +60,7 @@ class HeadMusic::Style::Guidelines::NotesSameLength < HeadMusic::Style::Annotati
|
|
60
60
|
end
|
61
61
|
|
62
62
|
def occurrences_by_rhythmic_value
|
63
|
-
rhythmic_values.each_with_object(Hash.new(0)) { |value, hash| hash[value] += 1
|
63
|
+
rhythmic_values.each_with_object(Hash.new(0)) { |value, hash| hash[value] += 1 }
|
64
64
|
end
|
65
65
|
|
66
66
|
def rhythmic_values
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::OneToOne < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Place a note for each note in the other voice."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
return unless cantus_firmus&.notes
|
@@ -5,7 +5,7 @@ module HeadMusic::Style::Guidelines; end
|
|
5
5
|
|
6
6
|
# A counterpoint guideline
|
7
7
|
class HeadMusic::Style::Guidelines::PreferContraryMotion < HeadMusic::Style::Annotation
|
8
|
-
MESSAGE =
|
8
|
+
MESSAGE = "Prefer contrary motion. Move voices in different melodic directions."
|
9
9
|
|
10
10
|
def marks
|
11
11
|
return nil if notes.length < 2
|