Class: HeadMusic::Style::Guideline

Inherits:
Object
  • Object
show all
Extended by:
Strength, Wording
Includes:
HarmonicContext, MelodicContext, VoiceContext
Defined in:
lib/head_music/style/guideline.rb,
lib/head_music/style/guideline/wording.rb,
lib/head_music/style/guideline/strength.rb,
lib/head_music/style/guideline/voice_context.rb,
lib/head_music/style/guideline/melodic_context.rb,
lib/head_music/style/guideline/harmonic_context.rb

Overview

A Guideline is one rule of craft: subclasses find faults in a voice and report them as marks. The class is the rule; instances are the analysis context, private to .assess, which returns a frozen GuideItemAssessment.

The rule’s own work – marks and the fitness they add up to – is here. What a subclass reads to do that work comes from the context mixins, what the rule says about a fault comes from Wording, and how much it weighs against its siblings comes from Strength.

Direct Known Subclasses

HeadMusic::Style::Guidelines::AllowFifthSpeciesRhythmicValues, HeadMusic::Style::Guidelines::AllowWholeHalfQuarterNotes, HeadMusic::Style::Guidelines::AlwaysMove, HeadMusic::Style::Guidelines::ApproachPerfectionContrarily, HeadMusic::Style::Guidelines::AvoidCrossingVoices, HeadMusic::Style::Guidelines::AvoidOverlappingVoices, HeadMusic::Style::Guidelines::ConsonantClimax, HeadMusic::Style::Guidelines::ConsonantDownbeats, HeadMusic::Style::Guidelines::Contoured, HeadMusic::Style::Guidelines::Diatonic, HeadMusic::Style::Guidelines::DirectionChanges, HeadMusic::Style::Guidelines::DirectionalStepToFinalNote, HeadMusic::Style::Guidelines::EndOnPerfectConsonance, HeadMusic::Style::Guidelines::EndOnTonic, HeadMusic::Style::Guidelines::FirstBarEntry, HeadMusic::Style::Guidelines::FloridDissonanceTreatment, HeadMusic::Style::Guidelines::LargeLeaps, HeadMusic::Style::Guidelines::LimitOctaveLeaps, HeadMusic::Style::Guidelines::MaximumNotes, HeadMusic::Style::Guidelines::MinimumThreshold, HeadMusic::Style::Guidelines::MixedRhythmicValues, HeadMusic::Style::Guidelines::MostlyConjunct, HeadMusic::Style::Guidelines::NoParallelPerfect, HeadMusic::Style::Guidelines::NoParallelPerfectAcrossBarline, HeadMusic::Style::Guidelines::NoRests, HeadMusic::Style::Guidelines::NoRestsAfterNote, HeadMusic::Style::Guidelines::NoStrongBeatUnisons, HeadMusic::Style::Guidelines::NoUnisonsInMiddle, HeadMusic::Style::Guidelines::NoteCountPerBar, HeadMusic::Style::Guidelines::NoteFillsFinalBar, HeadMusic::Style::Guidelines::NotesSameLength, HeadMusic::Style::Guidelines::OneToOne, HeadMusic::Style::Guidelines::OneToOneWithTies, HeadMusic::Style::Guidelines::PreferContraryMotion, HeadMusic::Style::Guidelines::PreferImperfect, HeadMusic::Style::Guidelines::PrepareOctaveLeaps, HeadMusic::Style::Guidelines::SetAgainstAnotherVoice, HeadMusic::Style::Guidelines::SingableIntervals, HeadMusic::Style::Guidelines::SingableRange, HeadMusic::Style::Guidelines::StartOnPerfectConsonance, HeadMusic::Style::Guidelines::StartOnTonic, HeadMusic::Style::Guidelines::StepOutOfUnison, HeadMusic::Style::Guidelines::StepToFinalNote, HeadMusic::Style::Guidelines::SuspensionTreatment, HeadMusic::Style::Guidelines::WeakBeatDissonanceTreatment

Defined Under Namespace

Modules: HarmonicContext, MelodicContext, Strength, VoiceContext, Wording

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(voice, **options) ⇒ Guideline

Returns a new instance of Guideline.



38
39
40
41
# File 'lib/head_music/style/guideline.rb', line 38

def initialize(voice, **options)
  @voice = voice
  @options = options
end

Instance Attribute Details

#optionsObject (readonly, protected)

Returns the value of attribute options.



106
107
108
# File 'lib/head_music/style/guideline.rb', line 106

def options
  @options
end

#voiceObject (readonly)

Returns the value of attribute voice.



17
18
19
# File 'lib/head_music/style/guideline.rb', line 17

def voice
  @voice
end

Class Method Details

.assess(voice, guide_item, tier) ⇒ Object



52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/head_music/style/guideline.rb', line 52

def self.assess(voice, guide_item, tier)
  analyzer = new(voice, **guide_item.config)
  HeadMusic::Style::GuideItemAssessment.new(
    voice: voice,
    guide_item: guide_item,
    tier: tier,
    marks: analyzer.marks,
    fitness: analyzer.fitness,
    violation_key: analyzer.violation_key,
    violation_values: analyzer.violation_values
  )
end

.declared_strengthObject Originally defined in module Strength

Not memoized: a lazy write to a class ivar during grading would reopen the race that Guide::ALL.each(&:guide_items) exists to close.

.instruction_keyObject Originally defined in module Wording

.name_keyObject Originally defined in module Wording

.render_instruction(config) ⇒ Object Originally defined in module Wording

Violations are already phrased imperatively, so a guideline with nothing more specific to say instructs with the sentence it would complain with.

.render_name(config) ⇒ Object Originally defined in module Wording

Where a locale has nothing to say, the class name read as a sentence beats nothing.

The first letter is upcased here rather than in the data because a name can lead with an interpolation – “%contour contour” – whose value has to stay lowercase for the violation sentence that embeds it mid-sentence.

.render_template(key, config, extra_values = {}) ⇒ Object Originally defined in module Wording

Takes the configuration rather than finished values, so template_values – where numbers humanize – runs inside the locale the sentence renders in.

.render_violation(config) ⇒ Object Originally defined in module Wording

.render_violations(config) ⇒ Object Originally defined in module Wording

.strength(value = nil, because: nil) ⇒ Object Originally defined in module Strength

Required for :weak and rejected for :strong, so “every weak declaration carries a reason” is true by construction rather than asserted by a spec.

Never inherited: WeakBeatDissonanceTreatment bases the third-species and triple-meter treatments, which are the taught rule of their own guides, and MinimumThreshold bases both a gate and a rubric item. An inheriting macro would let one careless declaration on a shared analysis base demote several taught rules silently. This matches the choice Guides::Base makes for declarations.

.template_keyObject Originally defined in module Wording

Addressed in the locale files by the snake_case of the class name, so a new guideline needs no declaration.

.template_values(config) ⇒ Object Originally defined in module Wording

Not the config itself: seven items declare none and read their values from class constants, and I18n returns a template untouched when given no values.

.validate_reason!(normalized, because) ⇒ Object (private) Originally defined in module Strength

Raises:

  • (ArgumentError)

.violation_key(_config = {}) ⇒ Object Originally defined in module Wording

Takes the configuration: a guide may name a different template.

.violation_keys(config = {}) ⇒ Object Originally defined in module Wording

Every key an analysis can choose between, not only the one a bare config names. Load-time verification renders all of them: a branch reached by one kind of failure is otherwise unrendered until a student causes it.

.with(strength: nil, **options) ⇒ Object

Strength rides beside the configuration rather than inside it: config is splatted into the analyzer, returned as an I18n interpolation value, and – worst – decides item equality, so an overridden ApproachPerfectionContrarily inside config would fall out of the core-membership partition and be graded as a taught rule at full primary weight.



48
49
50
# File 'lib/head_music/style/guideline.rb', line 48

def self.with(strength: nil, **options)
  HeadMusic::Style::GuideItem.new(self, options, strength: strength)
end

Instance Method Details

#adherent?Boolean

Returns:

  • (Boolean)


72
73
74
# File 'lib/head_music/style/guideline.rb', line 72

def adherent?
  fitness == 1
end

#bass_voice?Boolean (protected) Originally defined in module VoiceContext

Returns:

  • (Boolean)

#cantus_firmusObject (protected) Originally defined in module VoiceContext

#diatonic_interval_from_tonic(note) ⇒ Object (protected) Originally defined in module MelodicContext

From the tonic below the note, so the interval is the one a singer hears rather than a compound of it.

#downbeat_harmonic_intervalsObject (protected) Originally defined in module HarmonicContext

#end_positionObject



84
85
86
# File 'lib/head_music/style/guideline.rb', line 84

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

#first_noteObject Originally defined in module MelodicContext

#fitnessObject



65
66
67
68
69
70
# File 'lib/head_music/style/guideline.rb', line 65

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)

Subclasses override with an opportunity count to score by violation rate rather than raw count.



125
126
127
# File 'lib/head_music/style/guideline.rb', line 125

def fitness_denominator
  1
end

#flattened_marksObject (protected)

Marks may be one, many, or nil depending on the guideline.



109
110
111
# File 'lib/head_music/style/guideline.rb', line 109

def flattened_marks
  [marks].flatten.compact
end

#following_note(note) ⇒ Object (protected) Originally defined in module MelodicContext

Indexing past the end returns nil, which is the answer for the last note.

#harmonic_intervalsObject (protected) Originally defined in module HarmonicContext

#has_notes?Boolean

Returns:

  • (Boolean)


76
77
78
# File 'lib/head_music/style/guideline.rb', line 76

def has_notes?
  !!first_note
end

#higher_voicesObject (protected) Originally defined in module VoiceContext

#last_noteObject Originally defined in module MelodicContext

#lower_voicesObject (protected) Originally defined in module VoiceContext

#motionsObject (protected) Originally defined in module HarmonicContext

#no_placements_markObject (protected)

An empty voice has nowhere to put a mark, and no marks means a fitness of 1.0 – which is how an empty voice used to grade perfectly.



115
116
117
118
119
120
121
# File 'lib/head_music/style/guideline.rb', line 115

def no_placements_mark
  HeadMusic::Style::Mark.new(
    HeadMusic::Content::Position.new(composition, "1:1"),
    HeadMusic::Content::Position.new(composition, "2:1"),
    fitness: 0
  )
end

#other_voicesObject (protected) Originally defined in module VoiceContext

#positionsObject (protected) Originally defined in module HarmonicContext

Every moment any voice attacks, so an interval is measured wherever one could have been heard.

#preceding_note(note) ⇒ Object (protected) Originally defined in module MelodicContext

#sounding_together(intervals) ⇒ Object (protected) Originally defined in module HarmonicContext

Fewer than two notes means one voice is silent, so nothing sounds together.

#start_positionObject



80
81
82
# File 'lib/head_music/style/guideline.rb', line 80

def start_position
  flattened_marks.map(&:start_position).min
end

#starts_on_tonic?Boolean (protected) Originally defined in module MelodicContext

Returns:

  • (Boolean)

#tonic_pitchObject (protected) Originally defined in module MelodicContext

#unsorted_higher_voicesObject (protected) Originally defined in module VoiceContext

#unsorted_lower_voicesObject (protected) Originally defined in module VoiceContext

#violation_keyObject

Decided during analysis, so a guideline with more than one way to fail names the one it found – see ConsonantClimax.



90
91
92
# File 'lib/head_music/style/guideline.rb', line 90

def violation_key
  self.class.violation_key(options)
end

#violation_valuesObject

Only what this violation adds. The item’s own interpolations are rebuilt at render time, so an assessment made under one locale reads right in another.



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

def violation_values
  {}
end

#voicesObject (protected) Originally defined in module VoiceContext