Class: HeadMusic::Style::Guides::ContourMelody

Inherits:
SpeciesMelody show all
Defined in:
lib/head_music/style/guides/contour_melody.rb

Overview

A free diatonic melody targeting a specific contour. Configured rather than subclassed: ContourMelody.with(contour: :arch, minimum_melodic_intervals: 2).

Deliberately NOT a subclass of DiatonicMelody, even though it builds on that guide’s ruleset. Ruby resolves ::RULESET up the ancestor chain, so inheriting from DiatonicMelody would leave ContourMelody::RULESET silently returning the plain diatonic ruleset – no contour rule, no motion gate, unweighted peers, and a plausible-looking fitness. The ruleset is reached fully-qualified below instead, which keeps the constant genuinely absent here.

Constant Summary collapse

PEER_WEIGHT_BUDGET =

The non-gate peers of a contour guide share phi^-2 of rubric weight, so that with Contoured at its default weight of phi^-1 (and phi^-1 + phi^-2 = 1), a wrong contour on an otherwise perfect line grades exactly phi^-1.

HeadMusic::GOLDEN_RATIO_INVERSE**2

Class Method Summary collapse

Class Method Details

.analyze(voice) ⇒ Object

Guide.get passes through anything answering analyze, so naming this class instead of a registry key reaches Analysis and would otherwise fail at the first annotation with a bare “missing keyword: :contour”. Now that the six contour subclasses are gone, that is the likeliest way to hold this wrong, so the error names both ways to hold it right.

Raises:

  • (ArgumentError)


41
42
43
44
45
46
# File 'lib/head_music/style/guides/contour_melody.rb', line 41

def self.analyze(voice)
  raise ArgumentError,
    "#{name} requires configuration. " \
    "Use #{name}.with(contour: :arch, minimum_melodic_intervals: 2) " \
    'or HeadMusic::Style::Guide.get("arch_contour_melody").'
end

.ruleset(contour:, minimum_melodic_intervals: nil) ⇒ Object

An optional motion gate excludes non-attempts; nil omits it, so a static contour can legitimately repeat a single pitch.



50
51
52
53
54
55
56
57
58
59
60
# File 'lib/head_music/style/guides/contour_melody.rb', line 50

def self.ruleset(contour:, minimum_melodic_intervals: nil)
  motion_gate =
    minimum_melodic_intervals &&
    HeadMusic::Style::Guidelines::MinimumMelodicIntervals.with(minimum_melodic_intervals)
  [
    *GATES,
    motion_gate,
    *WEIGHTED_PEERS,
    HeadMusic::Style::Guidelines::Contoured.with(contour)
  ].compact.freeze
end

.with(contour:, minimum_melodic_intervals: nil) ⇒ Object

Normalizes eagerly so an invalid contour raises HERE, at configuration time, rather than at analysis. Required keyword, so an omitted or misspelled option name raises too.



29
30
31
32
33
34
# File 'lib/head_music/style/guides/contour_melody.rb', line 29

def self.with(contour:, minimum_melodic_intervals: nil)
  super(
    contour: HeadMusic::Style::Guidelines::Contoured.normalized_contour(contour),
    minimum_melodic_intervals: minimum_melodic_intervals
  )
end