| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Harmonic.Framework.Builder.Core
Description
Internal chain building, candidate pool construction, R-constraint filtering,
consonance fallback generation, state advancement, and progression conversion.
Online vs offline is a TransitionSource argument, not a code path:
one chain builder serves every family and both modes.
Synopsis
- type TransitionSource = Text -> IO [(Cadence, Double)]
- offlineSource :: TransitionSource
- classicalSource :: DbConn -> ComposerWeights -> TransitionSource
- sourceFor :: Text -> IO TransitionSource
- buildChainWith :: TransitionSource -> GenIO -> Maybe Int -> Double -> HarmonicContext -> (Int -> ParsedContext) -> CadenceState -> Int -> IO ([CadenceState], [StepDiagnostic])
- stepChainWith :: TransitionSource -> GenIO -> Maybe Int -> Double -> HarmonicContext -> ParsedContext -> ((CadenceState, [CadenceState], Int), [StepDiagnostic]) -> Int -> IO ((CadenceState, [CadenceState], Int), [StepDiagnostic])
- chainToProgression :: [CadenceState] -> Progression
- extractCadence :: CadenceState -> Cadence
- matchesContextWithTarget :: Maybe Int -> ParsedContext -> CadenceState -> Cadence -> Bool
- applyDriftFilter :: Drift -> CadenceState -> [(Cadence, a)] -> [(Cadence, a)]
- applyPedalFilter :: ParsedContext -> CadenceState -> [(Cadence, a)] -> [(Cadence, a)]
- resolveBassDirection :: GenIO -> Int -> Maybe BassDirectionSpec -> IO (Maybe BassDirection)
- tonalStartCue :: GenConfig -> IO CadenceState
- rootPositionCue :: CadenceState -> Bool
- directionViolations :: ParsedContext -> Progression -> Int
The transition seam
type TransitionSource = Text -> IO [(Cadence, Double)] Source #
How a walk key resolves to scored transitions: THE seam between the
step engine and any particular graph (or none). Sources return
positive scores sorted highest first — exactly the contract
stepChainBody documents for its transitions argument.
offlineSource :: TransitionSource Source #
The no-graph source: every step's pool is the consonance fallback alone. This IS offline mode — nothing else distinguishes it.
classicalSource :: DbConn -> ComposerWeights -> TransitionSource Source #
The classical Cadence graph under a composer blend: the aggregate
fast path for the wildcard (empty) blend, the full weights fetch and
blend resolution otherwise.
sourceFor :: Text -> IO TransitionSource Source #
Resolve a seek string to its TransitionSource: "none" (case-
insensitive) is offlineSource; anything else opens the database and
blends per parseComposerWeights.
A named blend is additionally spell-checked against the graph as the walk runs: a name that has not appeared on any fetched edge after a few steps is reported once (a typo'd composer otherwise zeroes its share silently and the walk quietly degrades toward fallback).
Chain building (one builder, any source)
Arguments
| :: TransitionSource | |
| -> GenIO | Shared random generator |
| -> Maybe Int | Diagnostics verbosity (Nothing / Just 1 / Just 2) |
| -> Double | Entropy (>= 0; rank-target dial) |
| -> HarmonicContext | |
| -> (Int -> ParsedContext) | Bar index (1-based) -> R context for that bar |
| -> CadenceState | Starting state |
| -> Int | Number of steps to generate |
| -> IO ([CadenceState], [StepDiagnostic]) |
Build the cadence chain step by step against any TransitionSource.
The one chain builder: online/offline is the source argument, fixed
vs per-bar R-context is the supplier argument (const pctx for the
plain families; genP narrows overtones
per bar), diagnostics collection is the verbosity argument (Nothing
skips construction entirely; Just 1 standard, Just 2 maximum).
Algorithm per step: fetch the walk key's transitions from the source
(the key is the current state's most-consonant rooted embedded triad —
identity for triads, so the walk never silently goes offline on
cardinality), then delegate the full R-filter/scoring/selection
logic to stepChainBody (pool = graph candidates + unlimited
consonance fallback).
stepChainWith :: TransitionSource -> GenIO -> Maybe Int -> Double -> HarmonicContext -> ParsedContext -> ((CadenceState, [CadenceState], Int), [StepDiagnostic]) -> Int -> IO ((CadenceState, [CadenceState], Int), [StepDiagnostic]) Source #
One step against a TransitionSource: fetch, then stepChainBody.
Conversion
chainToProgression :: [CadenceState] -> Progression Source #
Convert a chain of CadenceStates to a Progression
extractCadence :: CadenceState -> Cadence Source #
Extract Cadence from CadenceState
Filtering (exposed for testing)
matchesContextWithTarget :: Maybe Int -> ParsedContext -> CadenceState -> Cadence -> Bool Source #
Core filter with optional bass target override from rise/fall direction. When bassTarget is Just, the bass note must equal the target exactly. When Nothing, falls back to the standard set-membership check.
applyDriftFilter :: Drift -> CadenceState -> [(Cadence, a)] -> [(Cadence, a)] Source #
Filter the candidate pool by dissonance drift direction.
Dissonant: keep only candidates with dissonance >= current state's dissonanceConsonant: keep only candidates with dissonance <= current state's dissonanceFree: no filtering (return pool unchanged)
Safety fallback: if filtering empties the pool, returns the original unfiltered pool so generation never fails.
applyPedalFilter :: ParsedContext -> CadenceState -> [(Cadence, a)] -> [(Cadence, a)] Source #
Filter the candidate pool by pedal tone constraints.
Required tones must be present in every candidate chord (as absolute pitch
classes, anywhere in the chord — root or upper voices).
Preferred tones (? suffix in input) are applied when doing so leaves at
least minPedalPool candidates; otherwise they are relaxed and only required
tones are enforced. Safety fallback: never returns an empty pool.
resolveBassDirection :: GenIO -> Int -> Maybe BassDirectionSpec -> IO (Maybe BassDirection) Source #
Resolve a BassDirectionSpec into a concrete BassDirection for a
single generation step. Returns Nothing when no spec is active, or
when the spec's optional ? flag caused the coin flip to come up tails.
Rotation (BDRotate) cycles through the choices by stepNum (1-based).
Random pick (BDRandomPick) samples uniformly from the choices.
Context-aware starting cue
tonalStartCue :: GenConfig -> IO CadenceState Source #
Draw a random starting chord INSIDE the caller's tonal context: the
root from the resolved roots filter, the tones from the key-filtered
overtone set (via possibleTriads, the same pool builder the strata
cue uses), required pedal tones honoured where the pool allows —
and filtered to root-position structures via rootPositionCue. A
fully wildcard context defers to the caller's _gcCue — plain
seek "*" $ gen is unchanged. The gen / genE / grid counterpart of
strataStartCue: without it an
uncued walk under hcKey "3b" opens out of key three draws in four
(only three major triads fit a seven-tone key set) and snaps into key
from bar 2 — the R constraints filter transitions, never bar 1.
The root keeps the walk's own bass exemption (pcStrictContainment
False): a root named outside the key still cues, voiced over key
tones. An unsatisfiable context (empty pool even after the pedal
filter relaxes) falls back to the legacy draw rather than erroring —
bar 1 is the human aberration channel, never a wall.
rootPositionCue :: CadenceState -> Bool Source #
True when a state names as a root-position structure — its bass IS
its named root. Triad-sized states re-name through the display seam's
own chord builder (fromCadenceState), whose functionality carries
an Inv tag for every recognised inversion shape (maj/min/dim first
and second inversions, the sus4 inversions) — the same tags that make
showTriad render a slash chord. A
bare '/' test would misfire: sus2/4 names carry the character
without being slash chords. Extended states pass — no triad-inversion
vocabulary applies; the jazz cue filters its slash structures by node
key instead. Every random-cue pool filters on this: an uncued bar 1
never opens on a slash chord, and inversions enter a progression only
by the walk's choice or the caller's explicit cue.
directionViolations :: ParsedContext -> Progression -> Int Source #
Count the bars of a finished progression that move AGAINST an active
rise/fall spec — the post-hoc compliance measure the multi-attempt
ranker prefers on. Judged from each bar's stored arrival movement
(cadenceMovement of bars 2..N), which is exact where pitch-class
distance is ambiguous (a desc-7 arrival IS a fall, though its PC lands
a 4th up). Unison/pedal and tritone arrivals count as violations
under either kind — neither moves in the spec's direction. No spec,
or an optional (?) spec whose steps may legitimately skip, counts
nothing. The walk's relaxation ladder still runs per step; this only
lets attempt N K prefer the walks where relaxation never had to
surrender the direction — K buys rule character, not just score.