Class: HeadMusic::Style::Annotation
- Inherits:
-
Object
- Object
- HeadMusic::Style::Annotation
show all
- Defined in:
- lib/head_music/style/annotation.rb,
lib/head_music/style/annotation/configured.rb
Overview
An Annotation encapsulates an issue with or comment on a voice
Direct Known Subclasses
Guidelines::AllowedRhythmicValuesForCombined123, Guidelines::AllowedRhythmicValuesForFifthSpecies, Guidelines::AlwaysMove, Guidelines::ApproachPerfectionContrarily, Guidelines::AvoidCrossingVoices, Guidelines::AvoidOverlappingVoices, Guidelines::ConsonantClimax, Guidelines::ConsonantDownbeats, Guidelines::Contoured, Guidelines::Diatonic, Guidelines::DirectionChanges, Guidelines::DirectionalStepToFinalNote, Guidelines::EndOnPerfectConsonance, Guidelines::EndOnTonic, Guidelines::FirstBarEntry, Guidelines::FloridDissonanceTreatment, Guidelines::LargeLeaps, Guidelines::LimitOctaveLeaps, Guidelines::MaximumNotes, Guidelines::MinimumThreshold, Guidelines::MixedRhythmicValues, Guidelines::MostlyConjunct, Guidelines::NoParallelPerfect, Guidelines::NoParallelPerfectAcrossBarline, Guidelines::NoRests, Guidelines::NoRestsAfterNote, Guidelines::NoStrongBeatUnisons, Guidelines::NoUnisonsInMiddle, Guidelines::NoteCountPerBar, Guidelines::NoteFillsFinalBar, Guidelines::NotesSameLength, Guidelines::OneToOne, Guidelines::OneToOneWithTies, Guidelines::PreferContraryMotion, Guidelines::PreferImperfect, Guidelines::PrepareOctaveLeaps, Guidelines::SingableIntervals, Guidelines::SingableRange, Guidelines::StartOnPerfectConsonance, Guidelines::StartOnTonic, Guidelines::StepOutOfUnison, Guidelines::StepToFinalNote, Guidelines::SuspensionTreatment, Guidelines::WeakBeatDissonanceTreatment
Defined Under Namespace
Classes: Configured
Constant Summary
collapse
- MESSAGE =
"Write music."
- DEFAULT_WEIGHT =
1.0
Instance Attribute Summary collapse
Class Method Summary
collapse
Instance Method Summary
collapse
Constructor Details
#initialize(voice, **options) ⇒ Annotation
Returns a new instance of Annotation.
28
29
30
31
|
# File 'lib/head_music/style/annotation.rb', line 28
def initialize(voice, **options)
@voice = voice
@options = options
end
|
Instance Attribute Details
#options ⇒ Object
Returns the value of attribute options.
92
93
94
|
# File 'lib/head_music/style/annotation.rb', line 92
def options
@options
end
|
#voice ⇒ Object
Returns the value of attribute voice.
7
8
9
|
# File 'lib/head_music/style/annotation.rb', line 7
def voice
@voice
end
|
Class Method Details
.default_gate? ⇒ Boolean
43
44
45
|
# File 'lib/head_music/style/annotation.rb', line 43
def self.default_gate?
false
end
|
.default_weight ⇒ Object
39
40
41
|
# File 'lib/head_music/style/annotation.rb', line 39
def self.default_weight
DEFAULT_WEIGHT
end
|
.with(**options) ⇒ Object
Wraps a guideline class with preset options so it can live in a RULESET
and still be instantiated with just a voice, e.g. MinimumNotes.with(5).
35
36
37
|
# File 'lib/head_music/style/annotation.rb', line 35
def self.with(**options)
Configured.new(self, options)
end
|
Instance Method Details
#adherent? ⇒ Boolean
54
55
56
|
# File 'lib/head_music/style/annotation.rb', line 54
def adherent?
fitness == 1
end
|
#bass_voice? ⇒ Boolean
133
134
135
|
# File 'lib/head_music/style/annotation.rb', line 133
def bass_voice?
lower_voices.empty?
end
|
#cantus_firmus ⇒ Object
115
116
117
|
# File 'lib/head_music/style/annotation.rb', line 115
def cantus_firmus
@cantus_firmus ||= other_voices.detect(&:cantus_firmus?) || other_voices.first
end
|
#diatonic_interval_from_tonic(note) ⇒ Object
#downbeat_harmonic_intervals ⇒ Object
147
148
149
150
151
152
|
# File 'lib/head_music/style/annotation.rb', line 147
def downbeat_harmonic_intervals
@downbeat_harmonic_intervals ||=
sounding_together(
cantus_firmus.notes.map { |note| HeadMusic::Analysis::HarmonicInterval.new(note.voice, voice, note.position) }
)
end
|
#end_position ⇒ Object
74
75
76
|
# File 'lib/head_music/style/annotation.rb', line 74
def end_position
flattened_marks.map(&:end_position).max
end
|
#first_note ⇒ Object
82
83
84
|
# File 'lib/head_music/style/annotation.rb', line 82
def first_note
notes.first
end
|
#fitness ⇒ Object
47
48
49
50
51
52
|
# File 'lib/head_music/style/annotation.rb', line 47
def fitness
mark_fitnesses = flattened_marks.map(&:fitness)
return 1.0 if mark_fitnesses.empty?
mark_fitnesses.reduce(1, :*)**(1.0 / [fitness_denominator, 1].max)
end
|
#fitness_denominator ⇒ Object
Normalization rate for the product of mark fitnesses. Subclasses override
(e.g. with an opportunity count) to score by violation rate rather than
raw violation count. The default of 1 preserves the raw product.
103
104
105
|
# File 'lib/head_music/style/annotation.rb', line 103
def fitness_denominator
1
end
|
#flattened_marks ⇒ Object
Marks may be a single mark, an array, or nil depending on the guideline;
normalizing here keeps fitness and position scans uniform.
96
97
98
|
# File 'lib/head_music/style/annotation.rb', line 96
def flattened_marks
[marks].flatten.compact
end
|
#following_note(note) ⇒ Object
189
190
191
192
|
# File 'lib/head_music/style/annotation.rb', line 189
def following_note(note)
index = notes.index(note)
notes[index + 1] if index && index < notes.length - 1
end
|
#gate? ⇒ Boolean
62
63
64
|
# File 'lib/head_music/style/annotation.rb', line 62
def gate?
options.fetch(:gate, self.class.default_gate?)
end
|
#harmonic_intervals ⇒ Object
154
155
156
157
158
159
|
# File 'lib/head_music/style/annotation.rb', line 154
def harmonic_intervals
@harmonic_intervals ||=
sounding_together(
positions.map { |position| HeadMusic::Analysis::HarmonicInterval.new(cantus_firmus, voice, position) }
)
end
|
#has_notes? ⇒ Boolean
66
67
68
|
# File 'lib/head_music/style/annotation.rb', line 66
def has_notes?
!!first_note
end
|
#higher_voices ⇒ Object
119
120
121
|
# File 'lib/head_music/style/annotation.rb', line 119
def higher_voices
@higher_voices ||= unsorted_higher_voices.sort_by(&:highest_pitch).reverse
end
|
#last_note ⇒ Object
86
87
88
|
# File 'lib/head_music/style/annotation.rb', line 86
def last_note
notes.last
end
|
#lower_voices ⇒ Object
123
124
125
|
# File 'lib/head_music/style/annotation.rb', line 123
def lower_voices
@lower_voices ||= unsorted_lower_voices.sort_by(&:lowest_pitch).reverse
end
|
#message ⇒ Object
78
79
80
|
# File 'lib/head_music/style/annotation.rb', line 78
def message
self.class::MESSAGE
end
|
#motions ⇒ Object
141
142
143
144
145
|
# File 'lib/head_music/style/annotation.rb', line 141
def motions
downbeat_harmonic_intervals.each_cons(2).map do |harmonic_interval_pair|
HeadMusic::Analysis::Motion.new(*harmonic_interval_pair)
end
end
|
#other_voices ⇒ Object
111
112
113
|
# File 'lib/head_music/style/annotation.rb', line 111
def other_voices
@other_voices ||= voices.reject { |part| part == voice }
end
|
#positions ⇒ Object
167
168
169
170
|
# File 'lib/head_music/style/annotation.rb', line 167
def positions
@positions ||=
voices.map(&:notes).flatten.map(&:position).sort.uniq(&:to_s)
end
|
#preceding_note(note) ⇒ Object
184
185
186
187
|
# File 'lib/head_music/style/annotation.rb', line 184
def preceding_note(note)
index = notes.index(note)
notes[index - 1] if index && index > 0
end
|
#sounding_together(intervals) ⇒ Object
Keeps only intervals where both voices actually sound, dropping any where
one voice is silent (fewer than two notes).
163
164
165
|
# File 'lib/head_music/style/annotation.rb', line 163
def sounding_together(intervals)
intervals.reject { |interval| interval.notes.length < 2 }
end
|
#start_position ⇒ Object
70
71
72
|
# File 'lib/head_music/style/annotation.rb', line 70
def start_position
flattened_marks.map(&:start_position).min
end
|
#starts_on_tonic? ⇒ Boolean
137
138
139
|
# File 'lib/head_music/style/annotation.rb', line 137
def starts_on_tonic?
tonic_spelling == first_note.spelling
end
|
#tonic_pitch ⇒ Object
180
181
182
|
# File 'lib/head_music/style/annotation.rb', line 180
def tonic_pitch
@tonic_pitch ||= HeadMusic::Rudiment::Pitch.get(tonic_spelling)
end
|
#unsorted_higher_voices ⇒ Object
172
173
174
|
# File 'lib/head_music/style/annotation.rb', line 172
def unsorted_higher_voices
other_voices.select { |part| part.highest_pitch && highest_pitch && part.highest_pitch > highest_pitch }
end
|
#unsorted_lower_voices ⇒ Object
176
177
178
|
# File 'lib/head_music/style/annotation.rb', line 176
def unsorted_lower_voices
other_voices.select { |part| part.lowest_pitch && lowest_pitch && part.lowest_pitch < lowest_pitch }
end
|
#voices ⇒ Object
107
108
109
|
# File 'lib/head_music/style/annotation.rb', line 107
def voices
@voices ||= voice.composition.voices
end
|
#weight ⇒ Object
58
59
60
|
# File 'lib/head_music/style/annotation.rb', line 58
def weight
options.fetch(:weight, self.class.default_weight)
end
|