Class: HeadMusic::Style::Annotation

Inherits:
Object
  • Object
show all
Defined in:
lib/head_music/style/annotation.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::MinimumMelodicIntervals, Guidelines::MinimumNotes, Guidelines::MixedRhythmicValues, Guidelines::MostlyConjunct, Guidelines::NoParallelPerfectAcrossBarline, Guidelines::NoParallelPerfectOnDownbeats, Guidelines::NoParallelPerfectWithSyncopation, 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

#optionsObject (readonly, protected)

Returns the value of attribute options.



126
127
128
# File 'lib/head_music/style/annotation.rb', line 126

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(minimum: 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)


88
89
90
# File 'lib/head_music/style/annotation.rb', line 88

def adherent?
  fitness == 1
end

#bass_voice?Boolean (protected)

Returns:

  • (Boolean)


161
162
163
# File 'lib/head_music/style/annotation.rb', line 161

def bass_voice?
  lower_voices.empty?
end

#cantus_firmusObject (protected)



143
144
145
# File 'lib/head_music/style/annotation.rb', line 143

def cantus_firmus
  @cantus_firmus ||= other_voices.detect(&:cantus_firmus?) || other_voices.first
end

#diatonic_interval_from_tonic(note) ⇒ Object (protected)



155
156
157
158
159
# File 'lib/head_music/style/annotation.rb', line 155

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)



175
176
177
178
179
180
# File 'lib/head_music/style/annotation.rb', line 175

def downbeat_harmonic_intervals
  @downbeat_harmonic_intervals ||=
    cantus_firmus.notes
      .map { |note| HeadMusic::Analysis::HarmonicInterval.new(note.voice, voice, note.position) }
      .reject { |interval| interval.notes.length < 2 }
end

#end_positionObject



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

def end_position
  [marks].flatten.compact.map(&:end_position).max
end

#first_noteObject



116
117
118
# File 'lib/head_music/style/annotation.rb', line 116

def first_note
  notes.first
end

#fitnessObject



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

def fitness
  mark_fitnesses = [marks].flatten.compact.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.



131
132
133
# File 'lib/head_music/style/annotation.rb', line 131

def fitness_denominator
  1
end

#gate?Boolean

Returns:

  • (Boolean)


96
97
98
# File 'lib/head_music/style/annotation.rb', line 96

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

#harmonic_intervalsObject (protected)



182
183
184
185
186
187
# File 'lib/head_music/style/annotation.rb', line 182

def harmonic_intervals
  @harmonic_intervals ||=
    positions
      .map { |position| HeadMusic::Analysis::HarmonicInterval.new(cantus_firmus, voice, position) }
      .reject { |harmonic_interval| harmonic_interval.notes.length < 2 }
end

#has_notes?Boolean

Returns:

  • (Boolean)


100
101
102
# File 'lib/head_music/style/annotation.rb', line 100

def has_notes?
  !!first_note
end

#higher_voicesObject (protected)



147
148
149
# File 'lib/head_music/style/annotation.rb', line 147

def higher_voices
  @higher_voices ||= unsorted_higher_voices.sort_by(&:highest_pitch).reverse
end

#last_noteObject



120
121
122
# File 'lib/head_music/style/annotation.rb', line 120

def last_note
  notes.last
end

#lower_voicesObject (protected)



151
152
153
# File 'lib/head_music/style/annotation.rb', line 151

def lower_voices
  @lower_voices ||= unsorted_lower_voices.sort_by(&:lowest_pitch).reverse
end

#messageObject



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

def message
  self.class::MESSAGE
end

#motionsObject (protected)



169
170
171
172
173
# File 'lib/head_music/style/annotation.rb', line 169

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)



139
140
141
# File 'lib/head_music/style/annotation.rb', line 139

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

#positionsObject (protected)



189
190
191
192
# File 'lib/head_music/style/annotation.rb', line 189

def positions
  @positions ||=
    voices.map(&:notes).flatten.map(&:position).sort.uniq(&:to_s)
end

#start_positionObject



104
105
106
# File 'lib/head_music/style/annotation.rb', line 104

def start_position
  [marks].flatten.compact.map(&:start_position).min
end

#starts_on_tonic?Boolean (protected)

Returns:

  • (Boolean)


165
166
167
# File 'lib/head_music/style/annotation.rb', line 165

def starts_on_tonic?
  tonic_spelling == first_note.spelling
end

#tonic_pitchObject (protected)



202
203
204
# File 'lib/head_music/style/annotation.rb', line 202

def tonic_pitch
  @tonic_pitch ||= HeadMusic::Rudiment::Pitch.get(tonic_spelling)
end

#unsorted_higher_voicesObject (protected)



194
195
196
# File 'lib/head_music/style/annotation.rb', line 194

def unsorted_higher_voices
  other_voices.select { |part| part.highest_pitch && highest_pitch && part.highest_pitch > highest_pitch }
end

#unsorted_lower_voicesObject (protected)



198
199
200
# File 'lib/head_music/style/annotation.rb', line 198

def unsorted_lower_voices
  other_voices.select { |part| part.lowest_pitch && lowest_pitch && part.lowest_pitch < lowest_pitch }
end

#voicesObject (protected)



135
136
137
# File 'lib/head_music/style/annotation.rb', line 135

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

#weightObject



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

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