Class: HeadMusic::Style::Guides::ContourMelody
- Inherits:
-
SpeciesMelody
- Object
- Base
- SpeciesMelody
- HeadMusic::Style::Guides::ContourMelody
- 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
-
.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”.
-
.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.
-
.with(contour:, minimum_melodic_intervals: nil) ⇒ Object
Normalizes eagerly so an invalid contour raises HERE, at configuration time, rather than at analysis.
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.
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 |