Class: HeadMusic::Style::Annotation

Inherits:
Object
  • Object
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

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

#optionsObject (readonly, protected)

Returns the value of attribute options.



92
93
94
# File 'lib/head_music/style/annotation.rb', line 92

def options
  @options
end

#voiceObject (readonly)

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

Returns:

  • (Boolean)


43
44
45
# File 'lib/head_music/style/annotation.rb', line 43

def self.default_gate?
  false
end

.default_weightObject



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

Returns:

  • (Boolean)


54
55
56
# File 'lib/head_music/style/annotation.rb', line 54

def adherent?
  fitness == 1
end

#bass_voice?Boolean (protected)

Returns:

  • (Boolean)


133
134
135
# File 'lib/head_music/style/annotation.rb', line 133

def bass_voice?
  lower_voices.empty?
end

#cantus_firmusObject (protected)



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 (protected)



127
128
129
130
131
# File 'lib/head_music/style/annotation.rb', line 127

def diatonic_interval_from_tonic(note)
  tonic_to_use = tonic_pitch
  tonic_to_use -= HeadMusic::Rudiment::ChromaticInterval.get(:perfect_octave) while tonic_to_use > note.pitch
  HeadMusic::Analysis::DiatonicInterval.new(tonic_to_use, note.pitch)
end

#downbeat_harmonic_intervalsObject (protected)



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_positionObject



74
75
76
# File 'lib/head_music/style/annotation.rb', line 74

def end_position
  flattened_marks.map(&:end_position).max
end

#first_noteObject



82
83
84
# File 'lib/head_music/style/annotation.rb', line 82

def first_note
  notes.first
end

#fitnessObject



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_denominatorObject (protected)

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_marksObject (protected)

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 (protected)



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

Returns:

  • (Boolean)


62
63
64
# File 'lib/head_music/style/annotation.rb', line 62

def gate?
  options.fetch(:gate, self.class.default_gate?)
end

#harmonic_intervalsObject (protected)



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

Returns:

  • (Boolean)


66
67
68
# File 'lib/head_music/style/annotation.rb', line 66

def has_notes?
  !!first_note
end

#higher_voicesObject (protected)



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_noteObject



86
87
88
# File 'lib/head_music/style/annotation.rb', line 86

def last_note
  notes.last
end

#lower_voicesObject (protected)



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

#messageObject



78
79
80
# File 'lib/head_music/style/annotation.rb', line 78

def message
  self.class::MESSAGE
end

#motionsObject (protected)



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_voicesObject (protected)



111
112
113
# File 'lib/head_music/style/annotation.rb', line 111

def other_voices
  @other_voices ||= voices.reject { |part| part == voice }
end

#positionsObject (protected)



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 (protected)



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 (protected)

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_positionObject



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 (protected)

Returns:

  • (Boolean)


137
138
139
# File 'lib/head_music/style/annotation.rb', line 137

def starts_on_tonic?
  tonic_spelling == first_note.spelling
end

#tonic_pitchObject (protected)



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_voicesObject (protected)



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_voicesObject (protected)



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

#voicesObject (protected)



107
108
109
# File 'lib/head_music/style/annotation.rb', line 107

def voices
  @voices ||= voice.composition.voices
end

#weightObject



58
59
60
# File 'lib/head_music/style/annotation.rb', line 58

def weight
  options.fetch(:weight, self.class.default_weight)
end