Module: HeadMusic::Style::Guideline::Strength
- Included in:
- HeadMusic::Style::Guideline
- Defined in:
- lib/head_music/style/guideline/strength.rb
Overview
How much a guideline weighs against its siblings in the same tier: a prohibition weighs twice a preference. Extended rather than included, for the reason Wording gives – the severity belongs to the rule, not to one analysis.
Orthogonal to tier. Tier is the list a guide declares an item in and cannot be a property of the item, because ContourMelody treats as background exactly what DiatonicMelody teaches. Strength has no such conflict: PreferContraryMotion is a preference in every guide that declares it. It is therefore declared on the class and overridable per item for the tradition-dependent case.
Constant Summary collapse
- UNITS =
{strong: 2, weak: 1}.freeze
- VALUES =
UNITS.keys.freeze
- DEFAULT =
:strong
Class Method Summary collapse
-
.normalized(value, source) ⇒ Object
Validated in one place and called from both the macro and GuideItem#initialize – belt and braces, as Contoured already does for its contour, so a value reaching the second seam without the first still raises rather than multiplying by nil.
-
.units(value) ⇒ Object
Instance Method Summary collapse
-
#declared_strength ⇒ Object
Not memoized: a lazy write to a class ivar during grading would reopen the race that Guide::ALL.each(&:guide_items) exists to close.
-
#strength(value = nil, because: nil) ⇒ Object
Required for :weak and rejected for :strong, so “every weak declaration carries a reason” is true by construction rather than asserted by a spec.
-
#validate_reason!(normalized, because) ⇒ Object
private
Class Method Details
.normalized(value, source) ⇒ Object
Validated in one place and called from both the macro and GuideItem#initialize – belt and braces, as Contoured already does for its contour, so a value reaching the second seam without the first still raises rather than multiplying by nil.
26 27 28 29 30 31 |
# File 'lib/head_music/style/guideline/strength.rb', line 26 def self.normalized(value, source) normalized = value.to_s.downcase.to_sym return normalized if VALUES.include?(normalized) raise ArgumentError, "#{source} strength must be one of: #{VALUES.join(", ")} (got #{value.inspect})" end |
.units(value) ⇒ Object
33 34 35 |
# File 'lib/head_music/style/guideline/strength.rb', line 33 def self.units(value) UNITS.fetch(value) end |
Instance Method Details
#declared_strength ⇒ Object
Not memoized: a lazy write to a class ivar during grading would reopen the race that Guide::ALL.each(&:guide_items) exists to close.
56 57 58 |
# File 'lib/head_music/style/guideline/strength.rb', line 56 def declared_strength defined?(@declared_strength) ? @declared_strength : HeadMusic::Style::Guideline::Strength::DEFAULT end |
#strength(value = nil, because: nil) ⇒ Object
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.
46 47 48 49 50 51 52 |
# File 'lib/head_music/style/guideline/strength.rb', line 46 def strength(value = nil, because: nil) return declared_strength if value.nil? normalized = HeadMusic::Style::Guideline::Strength.normalized(value, name) validate_reason!(normalized, because) @declared_strength = normalized end |
#validate_reason!(normalized, because) ⇒ Object (private)
62 63 64 65 66 67 68 69 |
# File 'lib/head_music/style/guideline/strength.rb', line 62 def validate_reason!(normalized, because) if normalized == :weak && because.to_s.strip.empty? raise ArgumentError, "#{name} declares strength :weak and must say why with because:" end return unless normalized == :strong && because raise ArgumentError, "#{name} declares strength :strong, which is the default and takes no because:" end |