theHarmonicAlgorithm-3.0.0: Real-time harmonic progression generation for TidalCycles live performance
Safe HaskellSafe-Inferred
LanguageHaskell2010

Harmonic.Framework.Builder.Strata

Description

Helpers that decide which (strata, tristrata) pair bar i is drawn from, given the allowed tristrata list, the previous bar's assignment, and optional per-bar constraints (relStrata / absStrata). Pure functions; no IO, no randomness.

Synopsis

Documentation

adjacentInTristrata :: Tristrata -> StrataLabel -> [StrataLabel] Source #

In-tristrata circular neighbourhood of a strata: positions {p-1, p, p+1} mod 3 in the tristrata t. The current strata is included so a bar may repeat the previous strata within the same tristrata.

allowedNext :: [Tristrata] -> (StrataLabel, Tristrata) -> [(StrataLabel, Tristrata)] Source #

Complete viable (strata', tristrata') candidate set for a given (strata, tristrata) step, respecting the allowed-tristrata allow-list.

For every (t', p') in tristrataOf s where t' is allowed, emit every (s', t') with s' drawn from adjacentInTristrata t' s.

initialPlacement :: [Tristrata] -> StrataLabel -> (StrataLabel, Tristrata) Source #

Initial (strata, tristrata) placement: use the supplied starting strata, and pick the lowest-dissonance allowed tristrata that contains it. If no allowed tristrata contains the strata, fall back to the first allowed one (edge case — caller should usually avoid this via a sensible default).

modeForTriad Source #

Arguments

:: [(StrataLabel, Tristrata)] 
-> Int 
-> Int

triad's harmonic root PC (0..11)

-> ModeResult 

Triad-anchored, history-aware mode selector for bar i.

Walks barSeq backward from i-1 to find the most recent strata ≠ barSeq[i].strata; if none, uses positionalPartner on barSeq[i].tristrata. Unions strataChroma sCurr with the partner's chroma, then classifies via classifyModeAt pinned to triadRootPC.

Returns ModeOk for a normal classification; ModeInvalid when the union doesn't have exactly 7 unique pitch classes (only reachable via absStrata overrides that violate tristrata adjacency). When the 7-PC union doesn't classify under any of the 28 mode patterns pinned to triadRootPC, returns ModeOk (Mode Aeolian (P triadRootPC)) as a benign fallback.

pickPartner Source #

Arguments

:: [(StrataLabel, Tristrata)]

full bar sequence (0-indexed)

-> Int

current bar index

-> StrataLabel

current strata

-> Tristrata

current tristrata (used for fallback)

-> StrataLabel 

Choose a partner strata for the union construction at bar i.

Preference order:

  1. The most recent prior bar whose strata ≠ s_curr — "last different" (preserves the user's intended behaviour for mid-run bars where the mode reflects the incoming transition).
  2. If no different prior bar exists (bar 0, or the entire history so far shares s_curr), look forward for the first future bar whose strata ≠ s_curr — the "first next different" rule. This lets bar 0's mode reflect the outgoing transition to the next distinct strata, which is musically more informative than the positional fallback.
  3. Otherwise (single-strata run), fall back to positionalPartner inside the current bar's tristrata.

positionalPartner :: Tristrata -> StrataLabel -> StrataLabel Source #

Bar-0 / no-prior starter rule: pick a fixed partner strata inside the given tristrata based on the current strata's position.

  • pos 1 → ts2
  • pos 2 → ts1
  • pos 3 → ts1

Yields a 7-PC pair-union (which is what the framework guarantees inside a single tristrata).

selectNext :: (StrataLabel, Tristrata) -> [(StrataLabel, Tristrata)] -> Maybe (StrataLabel, Tristrata) Source #

Select the next (s', t') from a candidate pool using the tie-break rule:

  1. Prefer s' == s_{prev} (strata continuity); within that, prefer t' == t_{prev} if still valid, otherwise lowest-dissonance tristrata.
  2. Prefer t' == t_{prev} (tristrata continuity) — lowest-dissonance option inside the previous tristrata wins over a more-consonant strata in another tristrata.
  3. Otherwise pick by (strataDissonance s', tristrataDissonance t').

Returns Nothing on empty pool.

selectNextSeeded Source #

Arguments

:: Int

rng sample (any Int)

-> (StrataLabel, Tristrata)

prior bar

-> [(StrataLabel, Tristrata)]

candidate pool

-> Maybe (StrataLabel, Tristrata) 

Seeded stochastic variant of selectNext. Permits the walk to actually traverse — the categorical same-strata-first rule of selectNext locks the walk to its starting strata indefinitely, which is musically sterile.

Given an Int sample drawn from an RNG by the caller, this picks a candidate from pool with the following preference order:

  1. Prefer candidates whose strata ≠ sPrev (forces exploration). Among those, prefer ones sharing tPrev (tristrata continuity) over those in other tristrata.
  2. Fall back to the self-loop only when the pool contains nothing else (rare — happens under extreme hcTristrata / relStrata narrowing).

Within the preferred group, the sample index (mod group size) picks the concrete candidate. This gives run-to-run variety while respecting genPs continuity intent (staying inside the current tristrata's harmonic region when possible).