Class: HeadMusic::Style::Guideline
- Inherits:
-
Object
- Object
- HeadMusic::Style::Guideline
- 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
-
#options ⇒ Object
readonly
protected
Returns the value of attribute options.
-
#voice ⇒ Object
readonly
Returns the value of attribute voice.
Class Method Summary collapse
-
.assess(voice, guide_item, tier) ⇒ Object
-
.declared_strength ⇒ Object
extended
from 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_key ⇒ Object
extended
from Wording
-
.name_key ⇒ Object
extended
from Wording
-
.render_instruction(config) ⇒ Object
extended
from 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
extended
from Wording
Where a locale has nothing to say, the class name read as a sentence beats nothing.
-
.render_template(key, config, extra_values = {}) ⇒ Object
extended
from 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
extended
from Wording
-
.render_violations(config) ⇒ Object
extended
from Wording
-
.strength(value = nil, because: nil) ⇒ Object
extended
from 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.
-
.template_key ⇒ Object
extended
from 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
extended
from 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
extended
from Strength
private
-
.violation_key(_config = {}) ⇒ Object
extended
from Wording
Takes the configuration: a guide may name a different template.
-
.violation_keys(config = {}) ⇒ Object
extended
from Wording
Every key an analysis can choose between, not only the one a bare config names.
-
.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.
Instance Method Summary collapse
-
#adherent? ⇒ Boolean
-
#bass_voice? ⇒ Boolean
included
from VoiceContext
protected
-
#cantus_firmus ⇒ Object
included
from VoiceContext
protected
-
#diatonic_interval_from_tonic(note) ⇒ Object
included
from MelodicContext
protected
From the tonic below the note, so the interval is the one a singer hears rather than a compound of it.
-
#downbeat_harmonic_intervals ⇒ Object
included
from HarmonicContext
protected
-
#end_position ⇒ Object
-
#first_note ⇒ Object
included
from MelodicContext
-
#fitness ⇒ Object
-
#fitness_denominator ⇒ Object
protected
Subclasses override with an opportunity count to score by violation rate rather than raw count.
-
#flattened_marks ⇒ Object
protected
Marks may be one, many, or nil depending on the guideline.
-
#following_note(note) ⇒ Object
included
from MelodicContext
protected
Indexing past the end returns nil, which is the answer for the last note.
-
#harmonic_intervals ⇒ Object
included
from HarmonicContext
protected
-
#has_notes? ⇒ Boolean
-
#higher_voices ⇒ Object
included
from VoiceContext
protected
-
#initialize(voice, **options) ⇒ Guideline
constructor
A new instance of Guideline.
-
#last_note ⇒ Object
included
from MelodicContext
-
#lower_voices ⇒ Object
included
from VoiceContext
protected
-
#motions ⇒ Object
included
from HarmonicContext
protected
-
#no_placements_mark ⇒ Object
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.
-
#other_voices ⇒ Object
included
from VoiceContext
protected
-
#positions ⇒ Object
included
from HarmonicContext
protected
Every moment any voice attacks, so an interval is measured wherever one could have been heard.
-
#preceding_note(note) ⇒ Object
included
from MelodicContext
protected
-
#sounding_together(intervals) ⇒ Object
included
from HarmonicContext
protected
Fewer than two notes means one voice is silent, so nothing sounds together.
-
#start_position ⇒ Object
-
#starts_on_tonic? ⇒ Boolean
included
from MelodicContext
protected
-
#tonic_pitch ⇒ Object
included
from MelodicContext
protected
-
#unsorted_higher_voices ⇒ Object
included
from VoiceContext
protected
-
#unsorted_lower_voices ⇒ Object
included
from VoiceContext
protected
-
#violation_key ⇒ Object
Decided during analysis, so a guideline with more than one way to fail names the one it found – see ConsonantClimax.
-
#violation_values ⇒ Object
Only what this violation adds.
-
#voices ⇒ Object
included
from VoiceContext
protected
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, **) @voice = voice @options = end |
Instance Attribute Details
#options ⇒ Object (readonly, protected)
Returns the value of attribute options.
106 107 108 |
# File 'lib/head_music/style/guideline.rb', line 106 def @options end |
#voice ⇒ Object (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_strength ⇒ Object 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_key ⇒ Object Originally defined in module Wording
.name_key ⇒ Object 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_key ⇒ Object 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
.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, **) HeadMusic::Style::GuideItem.new(self, , strength: strength) end |
Instance Method Details
#adherent? ⇒ 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
#cantus_firmus ⇒ Object (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_intervals ⇒ Object (protected) Originally defined in module HarmonicContext
#end_position ⇒ Object
84 85 86 |
# File 'lib/head_music/style/guideline.rb', line 84 def end_position flattened_marks.map(&:end_position).max end |
#first_note ⇒ Object Originally defined in module MelodicContext
#fitness ⇒ Object
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_denominator ⇒ Object (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_marks ⇒ Object (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_intervals ⇒ Object (protected) Originally defined in module HarmonicContext
#has_notes? ⇒ Boolean
76 77 78 |
# File 'lib/head_music/style/guideline.rb', line 76 def has_notes? !!first_note end |
#higher_voices ⇒ Object (protected) Originally defined in module VoiceContext
#last_note ⇒ Object Originally defined in module MelodicContext
#lower_voices ⇒ Object (protected) Originally defined in module VoiceContext
#motions ⇒ Object (protected) Originally defined in module HarmonicContext
#no_placements_mark ⇒ Object (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_voices ⇒ Object (protected) Originally defined in module VoiceContext
#positions ⇒ Object (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_position ⇒ Object
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
#tonic_pitch ⇒ Object (protected) Originally defined in module MelodicContext
#unsorted_higher_voices ⇒ Object (protected) Originally defined in module VoiceContext
#unsorted_lower_voices ⇒ Object (protected) Originally defined in module VoiceContext
#violation_key ⇒ Object
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() end |
#violation_values ⇒ Object
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 |