theHarmonicAlgorithm-3.1.1: Real-time harmonic progression generation for TidalCycles live performance
Safe HaskellNone
LanguageGHC2021

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

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)

buildChainWith 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).

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 dissonance
  • Consonant: keep only candidates with dissonance <= current state's dissonance
  • Free: 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.