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

Harmonic.Lib

Description

Overview

One import gives a performance script everything it needs: the generative engine, the music-theory primitives it reasons over, and the TidalCycles bridge that turns its output into sound.

import Harmonic.Lib

Quick start

Generation reads as a chain of modifiers applied to a generator. Each modifier narrows what the engine may choose; the generator at the end of the chain runs the walk:

tempo = 87

ctx = invSkip 1
    $ hcOvertones "E A D G"
    $ hcKey "2#"
    $ hContext

start <- lead "C maj"

s <- seek "*" $ cue start $ tonal ctx $ len 8 $ entropy 0.4 $ attempt 3 12 $ gen

seek picks the corpus — "*" for all composers, "bach" for one, "bach:30 debussy:70" for a weighted blend, "none" to run offline with no Neo4j. attempt generates several candidates and keeps the best.

The result is then played by describing a form and handing it to instruments:

form = [ at 0 1.0 1.0 s ]

do
  let k = iK tempo form (warp "[1 2 3 4]/4")
  mapM_ id [ hush, setbpm tempo
           , p "strings" $ stack
               [ violin1    T (0,1) k voiceLines flow Soprano
               , cello      T (0,1) k voiceLines flow Tenor8vb
               , contrabass T (0,1) k voiceLines grid Bass8vb
               ]
           ]

Verbosity

Every generator has three tiers, marked by the prime suffix — the same convention used throughout the library for tiers and variants:

  • gen — the chord grid only
  • gen' — per-step musical context and the grid
  • gen'' — full traces, the grid, and the multi-attempt scoreboard

The tiers have identical types, so switching verbosity never changes the surrounding code. The same holds for genP / genP' / genP'', genFrom / genFrom' / genFrom'', and the Roman numeral aliases.

For time-unit pairs the prime means the unit: unprimed = bars, primed = seconds (display / display', mark / mark'). Form nodes are a different axis — rh / at name the unit and the prime is the Snap transition.

Where to go next

Legacy positional interface

Predating the modifier chain, genSilent, genStandard and genVerbose take their arguments positionally and share one signature:

genSilent :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression

They remain supported, but note that initCadenceState silently truncates chords of more than three pitch classes to a triad; lead is the better-behaved way to build a starting state.

Synopsis

Primary interface for live coding

The modifier-based generation API. Each generator comes in three verbosity tiers, marked by the prime suffix:

  • gen — header + grid output
  • gen' — compact summary
  • gen'' — verbose traces

Modifiers compose right-to-left onto a generator:

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ gen

Triadic generation

gen :: GenConfig Source #

Generation config with header + grid output (default).

s <- seek "*" $ gen
s <- seek "*" $ cue start $ tonal ctx $ len 4 $ entropy 0.3 $ gen

gen' :: GenConfig Source #

Generation config with compact musical summary.

gen'' :: GenConfig Source #

Generation config with verbose diagnostic traces.

genGrid :: GenConfig Source #

Static grid: repeats the cue chord for len bars. No database access.

s <- seek "*" $ cue start $ len 4 $ genGrid

genGrid' :: GenConfig Source #

genGrid with compact musical summary.

genGrid'' :: GenConfig Source #

genGrid with verbose diagnostic traces.

genFrom :: ProgressionContext -> Int -> Int -> GenConfig Source #

Regenerate a range of bars within an existing progression. The cue is inferred from the bar before the start position (wrapping). A cue override applies to every family EXCEPT FStrata: the strata regen must seed from the source's own bar to keep the walk-graph seam valid, so it ignores the override.

FAMILY-AWARE: regeneration produces states of the family the source progression already is — families never mix. The one downgrade is deliberate: hand-built extended (FExtended) sources regenerate as triads (with a printed notice), so the result re-infers as mixed material rather than pretending the fusion family still exists.

  • pcProvenance = Just _ — strata-aware path: regenerates all three layers + provenance in lockstep, with one-step lookahead at the e → e+1 seam to keep the spliced bar sequence walk-graph valid under allowedNext.
  • FPoly (genE source) — polytonal path: regenerates the foundation range plus both partner chains, seeded from the kept partner bars before the range; the S/M labelling of the source is preserved (chains are never reordered by a partial regen).
  • uniform 4-note hand-built material — regenerated bars come back as plain triads with a printed notice.
  • uniform 3-note (gen source) — plain triad regen.
  • hand-mixed cardinalities — regenerated as plain triads with a printed notice (hand-mixed material is the human aberration channel; regen does not amplify it).

s' <- seek "*" $ entropy 0.3 $ genFrom s 2 3 s' <- seek "*" $ cue start $ genFrom s 2 3 -- override inferred cue s' <- seek "*" $ len 6 $ genFrom s 2 3 -- expand range s' <- seek "*" $ genFrom' s 2 3 -- Standard per-step trace s' <- seek "*" $ genFrom'' s 2 3 -- Verbose trace (+ scoreboard with attempt)

genFrom' :: ProgressionContext -> Int -> Int -> GenConfig Source #

Standard-verbosity alias of genFrom. Mirrors gen', genP' and genI'.

genFrom'' :: ProgressionContext -> Int -> Int -> GenConfig Source #

Verbose-verbosity alias of genFrom. Mirrors gen'', genP'' and genI''.

Chordscale layers (gen / genJ)

gen and genJ contexts carry derived S/M layers: whole-progression key-area analysis assigns every bar a key (major, or composite minor) and realises it as the mode on the bar's root (M, 7 tones) and the best-fitting anhemitonic pentatonic (S, 5 tones). Automatic at generation; apply chordscale by hand to lead'-built contexts. chordscaleReport prints the per-bar key / form / mode / pentatonic.

chordscale :: ProgressionContext -> ProgressionContext Source #

Fill the S (pentatonic) and M (mode) layers of a context from the key-area analysis of its triad layer. Identity on FStrata and FPoly contexts, whose layers are already meaningful. Layer bars are built exactly like genP's aux layers (Unison movement, chroma expressed as intervals from the bar root), so they voice through the same chroma engine (strataModeFlow) and print as mode names via the 7-PC display path. The M bar is rooted on the bar's harmonic root (the mode OF the bar — every tier of the override ladder contains it); the S bar is rooted on the PENTATONIC's own root — a pentatonic legitimately excludes the chord root (the pent-on-the-fifth over a maj7 does so by design), and the set is named from its own root. (Bar roots seed only bar 0's lattice in the chroma engine; pattern indices then track their lattice slot, not the bar root.) Provenance stays Nothing (provenance is strata-specific and gates strata regen / scoring).

The derived bars are forced to normal form as part of the result's WHNF: ProgressionContext fields are lazy, and the first thing to inspect an unforced layer can be the derived Eq inside an arrange / lineHarmony cache lookup — i.e. the audio thread. Generated contexts are forced when the generator prints them, so their analysis always lands at generation time; a hand-applied chordscale evaluates at its own first use — print it (or run chordscaleReport on it) at build time to keep the work off the performance path.

renderChordscaleReport :: ProgressionContext -> Maybe String Source #

Render the per-bar key-area analysis of a gen / genJ (or hand-built) context. Returns Nothing for FStrata / FPoly contexts, whose layers carry their own semantics and their own reports.

chordscaleReport :: ProgressionContext -> IO () Source #

Live-coding helper: print the chordscale report for a gen / genJ result alongside the standard Show output.

The genE paradigm (polytonal)

Three simultaneous triad progressions from one walk: a foundation (T) plus two partner chains (S/M) sharing 2 pitch classes with it per bar and unioning to 5. Read pairs, the pentad and the pivot tones through the Layer selectors (TS/TM/SM/TSM/PT); genEReport prints every view. See USER_GUIDE section 20 and documents/POLYTONAL.md.

genE :: GenConfig Source #

The genE paradigm: polytonal three-layer generation. The T layer is a foundation walk byte-identical to gen (all R constraints apply to it alone — the foundation owns the bass); the S/M layers are partner triad chains, each a corpus-valid walk of its own, sharing exactly 2 pitch classes with the foundation per bar and unioning to exactly 5. The traversal chooses freely per bar between the two admitted geometries — common-dyad (every layer pair sounds 4 tones) and base-anchored (pairs with the foundation sound 4, S+M sounds the pentad). Partners honour key/roots/overtones but never direction specs or strata machinery. Combine layers at the pattern surface via the Layer selectors (TS/TM/SM/TSM/PT). Cues are triadic.

s <- seek "*" $ len 8 $ entropy 0.3 $ genE'

genE' :: GenConfig Source #

genE with compact musical summary (per-bar layer table).

genE'' :: GenConfig Source #

genE with verbose diagnostic traces (adds partner pool/tier trace).

polyLayerViews :: ProgressionContext -> Maybe [(String, Progression)] Source #

Every layer view of a polytonal context, labelled: single layers, pair unions, the pentad, the pivot tones. Returns Nothing for other families — the labels are genE-specific (partner chains, pentad, pivot dyad). gen/genJ layer content is printed by chordscaleReport, genP's by genPReport.

genEReport :: ProgressionContext -> IO () Source #

Live-coding helper: print every layer view of a polytonal context as progression grids. Use at the REPL between takes to see what each pattern-level layer selection will sound.

The genJ paradigm (jazz Change graph)

Generation over the jazz corpus graph: variable-arity leadsheet harmony (3-6 tones), cued by leadJ, walked by the same modifier chain as every family. See USER_GUIDE section 21.

genJ :: GenConfig Source #

Generate over the jazz (Change) graph: the genJ family. Same modifier chain as gen (seek "*" $ cue start $ len 8 $ entropy 0.3 $ genJ); the seek spec resolves against BOTH corpora (see Harmonic.Framework.Builder.JazzGen for the full semantics: jazz names blend, classical names steer via the steer dial, "*" walks the whole corpus, "none" is refused — the jazz graph has no offline mode). Progressions carry variable-arity chords (3-6 tones) straight from the corpus vocabulary, stamped FJazz so genFrom regenerates them jazz-natively and attempt ranks them against the Change graph. tonal constraints apply through the same R filter as every family; a step they empty is relaxed with a notice (there is deliberately no fallback pool).

genJ' :: GenConfig Source #

genJ with Standard per-step trace (walk steps, pool sizes, picks).

genJ'' :: GenConfig Source #

genJ with Verbose trace (adds seek resolution and steer notices).

The genP paradigm (strata-first)

Three-layer generation (triad / strata / mode). The roman-numeral aliases pin the starting tristrata; genPReport prints the per-bar provenance.

genP :: StrataLabel -> GenConfig Source #

Strata-first generation entrypoint. Seeded by a StrataLabel; produces a ProgressionContext with distinct triad, strata, and mode layers and pcProvenance = Just ….

Uncued, the starting chord is drawn from inside the stratum itself — a five-tone set admits few triads, and the ordinary whole-corpus cue almost never lands in one. A cue you pass with cue is honoured as given; one that escapes the stratum is reported with the triads that would have fitted, rather than silently replaced.

s <- seek "none" $ len 6 $ genP VI s <- seek "none" $ cue start $ len 6 $ genP VI

genP' :: StrataLabel -> GenConfig Source #

Standard-verbosity variant of genP.

genP'' :: StrataLabel -> GenConfig Source #

Verbose-verbosity variant of genP.

renderTristrataReport :: ProgressionContext -> Maybe String Source #

Render a multi-line report of the per-bar provenance of a ProgressionContext: for each bar, the tristrata index, its three strata, and the selected strata for that bar. Returns Nothing when the context has no provenance (e.g., a legacy gen result).

genPReport :: ProgressionContext -> IO () Source #

Live-coding helper: print a pretty tristrata report for a genP result alongside the standard Show output. Takes the bound context directly, mirroring genEReport. Useful at the REPL for sanity-checking a strata walk.

genI :: GenConfig Source #

Silent-verbosity genP aliases, one per Roman numeral — genI pins the starting tristrata to I, genII to II, and so on through genXI.

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ genI

Three verbosities throughout, by the usual prime convention: genI silent, genI' standard, genI'' verbose.

genII :: GenConfig Source #

Silent-verbosity genP aliases, one per Roman numeral — genI pins the starting tristrata to I, genII to II, and so on through genXI.

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ genI

Three verbosities throughout, by the usual prime convention: genI silent, genI' standard, genI'' verbose.

genIII :: GenConfig Source #

Silent-verbosity genP aliases, one per Roman numeral — genI pins the starting tristrata to I, genII to II, and so on through genXI.

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ genI

Three verbosities throughout, by the usual prime convention: genI silent, genI' standard, genI'' verbose.

genIV :: GenConfig Source #

Silent-verbosity genP aliases, one per Roman numeral — genI pins the starting tristrata to I, genII to II, and so on through genXI.

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ genI

Three verbosities throughout, by the usual prime convention: genI silent, genI' standard, genI'' verbose.

genV :: GenConfig Source #

Silent-verbosity genP aliases, one per Roman numeral — genI pins the starting tristrata to I, genII to II, and so on through genXI.

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ genI

Three verbosities throughout, by the usual prime convention: genI silent, genI' standard, genI'' verbose.

genVI :: GenConfig Source #

Silent-verbosity genP aliases, one per Roman numeral — genI pins the starting tristrata to I, genII to II, and so on through genXI.

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ genI

Three verbosities throughout, by the usual prime convention: genI silent, genI' standard, genI'' verbose.

genVII :: GenConfig Source #

Silent-verbosity genP aliases, one per Roman numeral — genI pins the starting tristrata to I, genII to II, and so on through genXI.

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ genI

Three verbosities throughout, by the usual prime convention: genI silent, genI' standard, genI'' verbose.

genVIII :: GenConfig Source #

Silent-verbosity genP aliases, one per Roman numeral — genI pins the starting tristrata to I, genII to II, and so on through genXI.

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ genI

Three verbosities throughout, by the usual prime convention: genI silent, genI' standard, genI'' verbose.

genIX :: GenConfig Source #

Silent-verbosity genP aliases, one per Roman numeral — genI pins the starting tristrata to I, genII to II, and so on through genXI.

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ genI

Three verbosities throughout, by the usual prime convention: genI silent, genI' standard, genI'' verbose.

genX :: GenConfig Source #

Silent-verbosity genP aliases, one per Roman numeral — genI pins the starting tristrata to I, genII to II, and so on through genXI.

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ genI

Three verbosities throughout, by the usual prime convention: genI silent, genI' standard, genI'' verbose.

genXI :: GenConfig Source #

Silent-verbosity genP aliases, one per Roman numeral — genI pins the starting tristrata to I, genII to II, and so on through genXI.

s <- seek "*" $ attempt 3 12 $ entropy 0.4 $ genI

Three verbosities throughout, by the usual prime convention: genI silent, genI' standard, genI'' verbose.

genI' :: GenConfig Source #

Standard-verbosity Roman numeral aliases: per-step musical context plus the grid. See genI.

genII' :: GenConfig Source #

Standard-verbosity Roman numeral aliases: per-step musical context plus the grid. See genI.

genIII' :: GenConfig Source #

Standard-verbosity Roman numeral aliases: per-step musical context plus the grid. See genI.

genIV' :: GenConfig Source #

Standard-verbosity Roman numeral aliases: per-step musical context plus the grid. See genI.

genV' :: GenConfig Source #

Standard-verbosity Roman numeral aliases: per-step musical context plus the grid. See genI.

genVI' :: GenConfig Source #

Standard-verbosity Roman numeral aliases: per-step musical context plus the grid. See genI.

genVII' :: GenConfig Source #

Standard-verbosity Roman numeral aliases: per-step musical context plus the grid. See genI.

genVIII' :: GenConfig Source #

Standard-verbosity Roman numeral aliases: per-step musical context plus the grid. See genI.

genIX' :: GenConfig Source #

Standard-verbosity Roman numeral aliases: per-step musical context plus the grid. See genI.

genX' :: GenConfig Source #

Standard-verbosity Roman numeral aliases: per-step musical context plus the grid. See genI.

genXI' :: GenConfig Source #

Standard-verbosity Roman numeral aliases: per-step musical context plus the grid. See genI.

genI'' :: GenConfig Source #

Verbose-verbosity Roman numeral aliases: full traces, the grid, and the multi-attempt scoreboard when paired with attempt. See genI.

genII'' :: GenConfig Source #

Verbose-verbosity Roman numeral aliases: full traces, the grid, and the multi-attempt scoreboard when paired with attempt. See genI.

genIII'' :: GenConfig Source #

Verbose-verbosity Roman numeral aliases: full traces, the grid, and the multi-attempt scoreboard when paired with attempt. See genI.

genIV'' :: GenConfig Source #

Verbose-verbosity Roman numeral aliases: full traces, the grid, and the multi-attempt scoreboard when paired with attempt. See genI.

genV'' :: GenConfig Source #

Verbose-verbosity Roman numeral aliases: full traces, the grid, and the multi-attempt scoreboard when paired with attempt. See genI.

genVI'' :: GenConfig Source #

Verbose-verbosity Roman numeral aliases: full traces, the grid, and the multi-attempt scoreboard when paired with attempt. See genI.

genVII'' :: GenConfig Source #

Verbose-verbosity Roman numeral aliases: full traces, the grid, and the multi-attempt scoreboard when paired with attempt. See genI.

genVIII'' :: GenConfig Source #

Verbose-verbosity Roman numeral aliases: full traces, the grid, and the multi-attempt scoreboard when paired with attempt. See genI.

genIX'' :: GenConfig Source #

Verbose-verbosity Roman numeral aliases: full traces, the grid, and the multi-attempt scoreboard when paired with attempt. See genI.

genX'' :: GenConfig Source #

Verbose-verbosity Roman numeral aliases: full traces, the grid, and the multi-attempt scoreboard when paired with attempt. See genI.

genXI'' :: GenConfig Source #

Verbose-verbosity Roman numeral aliases: full traces, the grid, and the multi-attempt scoreboard when paired with attempt. See genI.

Generation modifiers

cue :: CadenceState -> GenConfig -> GenConfig Source #

Set starting state.

s <- seek "*" $ cue start $ gen

len :: Int -> GenConfig -> GenConfig Source #

Set progression length (number of chords).

s <- seek "*" $ len 8 $ gen

seek :: String -> GenConfig -> IO ProgressionContext Source #

Set composer blend and execute. Terminal modifier — produces IO ProgressionContext. Every family returns distinct layers: genP carries curated strata/mode chroma with Just provenance, genE partner triads, and gen/genJ chordscale-derived pentatonic/mode layers (chordscale is applied in the dispatch fallthrough) with pcProvenance Nothing.

s <- seek "*" $ gen s <- seek "bach:70 debussy:30" $ cue start $ len 4 $ gen s <- seek "none" $ cue start $ len 6 $ genVI

entropy :: Double -> GenConfig -> GenConfig Source #

Set entropy (>= 0) — the gamma sampler targets rank entropy * 10 in the scored pool, so 0 usually takes the top candidate, 0.5 wanders around the 5th, 1 around the 10th; values above 1 reach deeper still.

s <- seek "*" $ entropy 0.5 $ gen

tonal :: HarmonicContext -> GenConfig -> GenConfig Source #

Set harmonic context (R constraints).

s <- seek "*" $ tonal (hcKey "0#" $ hContext) $ gen

relStrata :: String -> GenConfig -> GenConfig Source #

Per-bar position within the dynamically-changing active tristrata. Elements ∈ {1,2,3} cycle circularly. Sets _gcLenOverride to the parsed list length so _gcLen doesn't need to be set explicitly; len applied later clears the override (last-writer-wins).

s <- seek "none" $ relStrata "1 1 2 2 3 3" $ genVI -- 6 bars

absStrata :: String -> GenConfig -> GenConfig Source #

Per-bar absolute strata label across all tristratas. Elements are Roman numerals I..XI cycling circularly. Sets _gcLenOverride to the parsed list length.

s <- seek "none" $ absStrata "I V X" $ genI -- 3 bars

sameBoost :: Double -> GenConfig -> GenConfig Source #

Override the same-strata continuity boost multiplier. Values below 1.0 favour candidates whose strata matches the previous bar's. Default 0.90. Pass 1.0 to disable the bias.

s <- seek "none" $ sameBoost 0.5 $ genVI -- strong same-strata pull

flipBoost :: Double -> GenConfig -> GenConfig Source #

Override the flip-flop boost multiplier (candidates matching the grandparent strata when the current /= previous). Default 0.80.

triBoost :: Double -> GenConfig -> GenConfig Source #

Override the same-tristrata continuity boost multiplier. Values below 1.0 favour candidates whose active tristrata matches the previous bar's. Default 0.70 (strongest of the three).

attempt :: Int -> Int -> GenConfig -> GenConfig Source #

Run multi-attempt rank-and-select generation: produce up to maxAttempts candidate progressions, stop early once viableTarget viable attempts (all bars ModeOk) have been collected, then return the highest-scoring one. Scoring blends root motion, voice leading, and mode validity via defaultWeightsOffline.

s <- seek "*" $ attempt 3 24 $ entropy 0.4 $ gen -- best of up to 24

Polytonal (genE) attempts rank differently, because they are three progressions rather than one: each layer is scored in its own right (half the weight on the foundation, a quarter on each partner) and a divergence axis rewards the layers standing apart — the point of the family. See PolyScore. Raising maxAttempts raises both the quality and the divergence of what comes back.

Defaults are attempt 1 1 — i.e. the modifier is a no-op when omitted, preserving legacy single-pass behaviour.

viability :: Double -> GenConfig -> GenConfig Source #

Set the viability quality floor used by attempt. An attempt is viable iff psModeValidity >= 1.0 (structural invariant) and totalScore >= floor. Default is 0.5; passing 0.0 recovers the original structural-only viability.

s <- seek "*" $ viability 0.65 $ attempt 3 24 $ gen

Tune downward if attempt N K frequently fails to collect N viable within K (raise K or lower the floor); tune upward if K is being hit consistently with mediocre-quality picks (lower K or raise the floor).

steer :: Double -> GenConfig -> GenConfig Source #

Set the genJ classical-steer boost strength (default 3.0). Applies only when the seek spec names classical composers: a jazz candidate containing one of the steer blend's top recommended triads has its score multiplied by up to (1 + strength). 0 disables steering influence entirely. Initial calibration — tune by ear.

s <- seek "debussy" $ steer 6 $ len 8 $ genJ'

Generation types

data GenConfig Source #

Configuration for the modifier-based generation API.

Built via modifier chains:

s <- seek "*" $ cue start $ tonal ctx $ len 4 $ entropy 0.3 $ gen

Constructors

GenConfig 

Fields

data GenMode Source #

Generation mode.

Constructors

Fresh

Standard gen (new progression)

FromProg Progression !Int !Int

Regenerate range in existing triad layer

FromProgPC ProgressionContext !Int !Int

Regenerate range in a strata-aware context (preserves all three layers + provenance)

GridMode

Static repetition of cue chord

StrataMode StrataLabel

genP (strata-first, produces ProgressionContext)

JazzMode

genJ (walk over the jazz Change graph)

FromProgJ ProgressionContext !Int !Int

Regenerate range in a jazz-family context

PolyMode

genE (polytonal: foundation walk + two partner triad chains)

FromProgPoly ProgressionContext !Int !Int

Regenerate range in a polytonal context (foundation + partner chains, seam-aware)

data Verbosity Source #

Verbosity level for generation output.

Constructors

Silent 
Standard 
Verbose 

Instances

Instances details
Show Verbosity Source # 
Instance details

Defined in Harmonic.Framework.Builder.Types

Eq Verbosity Source # 
Instance details

Defined in Harmonic.Framework.Builder.Types

defaultGenConfig :: GenConfig Source #

Default generation configuration.

cue:     random root, major triad — but an uncued run resolves its cue
         context-aware at run time: inside the tonal context for gen /
         genE / grid (tonalStartCue),
         from the stratum for genP, from the Change graph for genJ
len:     4
seek:    "*" (all composers)
entropy: 0.2
tonal:   hContext (chromatic)

execGenConfig :: GenConfig -> IO Progression Source #

Execute a GenConfig, producing a progression.

Progression-typed view of execGenConfigPC: one dispatcher and one emission path for every family, and the attempt loop (_gcMaxAttempts) is honoured rather than silently ignored.

execGenConfigPC :: GenConfig -> IO ProgressionContext Source #

Terminal executor producing a ProgressionContext.

When _gcMaxAttempts > 1, dispatches through generateBest for rank-and-select multi-attempt generation; otherwise runs a single pass.

The single-pass dispatch reads _gcMode: StrataMode runs the strata- first traversal producing distinct layers; all other modes fall through to execGenConfig and wrap the resulting triad Progression via fromProgression.

Positional generation (legacy)

genSilent :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression Source #

Generate with custom configuration and maximum diagnostics (internal).

Generate a progression with NO diagnostic output (verbosity 0 - silent mode).

genStandard :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression Source #

Generate a progression with STANDARD diagnostic output (verbosity 1).

genVerbose :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression Source #

Generate a progression with VERBOSE diagnostic output (verbosity 2).

genPrint :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression Source #

Positional generate with header + grid output (internal).

genPrint' :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression Source #

Positional generate with compact musical summary (internal).

genPrint'' :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression Source #

Positional generate with verbose traces (internal).

Context and configuration

A HarmonicContext constrains what the generator may choose. Build one by chaining modifiers onto hContext:

ctx = invSkip 1 $ hcOvertones "E A D G" $ hcPedal "E?" $ hContext

data HarmonicContext Source #

Harmonic context defines the Rules (R) that constrain the generative space.

These filters are applied BEFORE database evaluation (R in R→E→T pipeline), limiting which cadences can even be considered as candidates.

Three-part filtering system: * overtones: Pitch candidate set (e.g., "E A D G" for bass tuning overtones) * key: Key filter applied to candidates (e.g., C, "#", "bb" for key signature) * roots: Root/bass candidate set (e.g., "E F# G" for valid root notes)

Filters use "*" as wildcard (match all). Format matches legacy Overtone.hs notation.

Constructors

HarmonicContext 

Fields

harmonicContext :: String -> String -> String -> HarmonicContext Source #

Constructor for HarmonicContext.

Arguments: * overtones: Pitch set filter ("E A D G", C, "*") * key: Key signature filter (C, "#", "bb", Am, "*") * roots: Root notes filter ("E F# G", "1#", "*")

Example: harmonicContext "*" "*" "*" -- No filtering (all candidates) harmonicContext "E A D G" C "*" -- Bass tuning, C major key harmonicContext "*" "#" "E G" -- G major key, E/G roots only String (not Text) so editor plugins that wrap literals in Tidal's deltaContext (which has a Stringy String instance but no Text one) can pass filter strings straight through.

hContext :: HarmonicContext Source #

Default harmonic context for Tidal live coding: all wildcards (chromatic). Named hContext to avoid collision with TidalCycles' EventF.context field.

Use modifier functions to constrain the context:

ctx = invSkip 2
    $ consonant
    $ hcRoots "C E G"
    $ hcKey "0#"
    $ hcOvertones "E A D G"
    $ hContext

data Drift Source #

Direction of dissonance drift across a generated progression.

When applied to a HarmonicContext, the generation engine filters the candidate pool at each step so that only chords with equal or greater (Dissonant) or equal or lesser (Consonant) dissonance than the current chord are preferred. Free imposes no constraint (default).

Advisory, not hard: if the drift predicate would empty the pool at a step, the filter relaxes and that step proceeds unconstrained rather than reaching an absorbing state (applyDriftFilter in Builder.Core). Note the predicate is dissonanceScore — an evaluation function acting as a filter, the documented E-inside-R leak (see ARCHITECTURE §2).

Constructors

Dissonant 
Consonant 
Free 

Instances

Instances details
Show Drift Source # 
Instance details

Defined in Harmonic.Framework.Builder.Types

Methods

showsPrec :: Int -> Drift -> ShowS #

show :: Drift -> String #

showList :: [Drift] -> ShowS #

Eq Drift Source # 
Instance details

Defined in Harmonic.Framework.Builder.Types

Methods

(==) :: Drift -> Drift -> Bool #

(/=) :: Drift -> Drift -> Bool #

hcOvertones :: String -> HarmonicContext -> HarmonicContext Source #

Set overtone filter. Default: "*" (all pitches).

hcOvertones "E A D G" $ hContext — bass tuning overtones

hcKey :: String -> HarmonicContext -> HarmonicContext Source #

Set key filter. Default: "*" (chromatic).

hcKey "0#" $ hContext — C major

hcRoots :: String -> HarmonicContext -> HarmonicContext Source #

Set roots/bass filter. Default: "*" (all roots).

hcRoots "C E G" $ hContext — only C, E, G as bass notes

dissonant :: HarmonicContext -> HarmonicContext Source #

Modify context to trend toward increasing dissonance. Each subsequent chord should have dissonance >= the current chord; advisory — relaxes at any step where it would empty the pool.

consonant :: HarmonicContext -> HarmonicContext Source #

Modify context to trend toward decreasing dissonance. Each subsequent chord should have dissonance <= the current chord; advisory — relaxes at any step where it would empty the pool.

invSkip :: Int -> HarmonicContext -> HarmonicContext Source #

Set minimum number of non-inversion states between inversions.

invSkip 0 allows inversions at any step (default, current behaviour). invSkip 1 requires at least 1 non-inversion between inversions. invSkip 2 requires at least 2 non-inversions between inversions. The starting state counts toward the counter (a non-inversion start means the first generated step may already be an inversion with invSkip 1).

Advisory, not hard: if excluding inversions would empty the pool at a step, the spacing constraint relaxes for that step rather than halting generation.

hcPedal :: String -> HarmonicContext -> HarmonicContext Source #

Require specific pitch classes to be present in every generated chord.

Tokens are note names (C, G, Bb). A trailing ? marks a tone as preferred rather than required — it is applied when it does not reduce the candidate pool below a minimum viable size, and relaxed otherwise.

Advisory at the limit: the relaxation chain is preferred → required → unfiltered (applyPedalFilter in Builder.Core), so even required tones are dropped as a last resort at a step where enforcing them would leave no candidates — generation never reaches an absorbing state through a pedal constraint.

hcPedal C $ hContext — C must appear in every chord hcPedal "C G" $ hContext — C and G must both appear hcPedal "C G?" $ hContext — C required, G preferred

hcTristrata :: String -> HarmonicContext -> HarmonicContext Source #

Restrict the active tristrata pool for genP.

"" (default) — all 12 tristrata allowed. "5" — lock to a single tristrata (here #5, IV-VI-X). "1 2 5" — whitelist multiple tristrata. "[1,2,5]" — bracket form accepted.

Parsed via parseTristrataList; unknown tokens are silently discarded.

Core music types

data Movement Source #

Movement represents bass motion by a musical interval

Constructors

Asc PitchClass

Ascending by n semitones (1-5)

Desc PitchClass

Descending by n semitones (1-5)

Unison

Pedal (no movement)

Tritone

Movement by 6 semitones

Empty

Placeholder for invalid/missing movement

Instances

Instances details
Generic Movement Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep Movement 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep Movement = D1 ('MetaData "Movement" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) ((C1 ('MetaCons "Asc" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 PitchClass)) :+: C1 ('MetaCons "Desc" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 PitchClass))) :+: (C1 ('MetaCons "Unison" 'PrefixI 'False) (U1 :: Type -> Type) :+: (C1 ('MetaCons "Tritone" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Empty" 'PrefixI 'False) (U1 :: Type -> Type))))

Methods

from :: Movement -> Rep Movement x #

to :: Rep Movement x -> Movement #

Read Movement Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Show Movement Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq Movement Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Ord Movement Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep Movement Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep Movement = D1 ('MetaData "Movement" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) ((C1 ('MetaCons "Asc" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 PitchClass)) :+: C1 ('MetaCons "Desc" 'PrefixI 'False) (S1 ('MetaSel ('Nothing :: Maybe Symbol) 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 PitchClass))) :+: (C1 ('MetaCons "Unison" 'PrefixI 'False) (U1 :: Type -> Type) :+: (C1 ('MetaCons "Tritone" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "Empty" 'PrefixI 'False) (U1 :: Type -> Type))))

type Functionality = String Source #

Chord quality/functionality as a string (e.g., "maj", "min7", "dim") Preserved as String for compatibility with legacy naming conventions

toFunctionality :: [PitchClass] -> Functionality Source #

Derive functionality name from a pitch set using the legacy triad nameFunc This is for TRIADS (exactly 3 pitch classes)

toFunctionalityChord :: [PitchClass] -> Functionality Source #

Derive functionality name from a pitch set using the chord nameFunc This is for CHORDS (any number of pitch classes, extended harmonies)

data Chord Source #

A Chord is an abstract pitch structure: root note name, functionality, and pitch content as integers, bass first (register-capable). NOTE: toTriad's base case stores ABSOLUTE pitch classes here, not root-relative intervals (F major reads back as [5, 9, 0] — pinned by BridgeSpec); treat the field as "pitches as constructed", never assume root-relative.

Instances

Instances details
Generic Chord Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep Chord 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep Chord = D1 ('MetaData "Chord" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) (C1 ('MetaCons "Chord" 'PrefixI 'True) (S1 ('MetaSel ('Just "chordNoteName") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NoteName) :*: (S1 ('MetaSel ('Just "chordFunctionality") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Functionality) :*: S1 ('MetaSel ('Just "chordIntervals") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 [Integer]))))

Methods

from :: Chord -> Rep Chord x #

to :: Rep Chord x -> Chord #

Show Chord Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Methods

showsPrec :: Int -> Chord -> ShowS #

show :: Chord -> String #

showList :: [Chord] -> ShowS #

Eq Chord Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Methods

(==) :: Chord -> Chord -> Bool #

(/=) :: Chord -> Chord -> Bool #

Ord Chord Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Methods

compare :: Chord -> Chord -> Ordering #

(<) :: Chord -> Chord -> Bool #

(<=) :: Chord -> Chord -> Bool #

(>) :: Chord -> Chord -> Bool #

(>=) :: Chord -> Chord -> Bool #

max :: Chord -> Chord -> Chord #

min :: Chord -> Chord -> Chord #

type Rep Chord Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep Chord = D1 ('MetaData "Chord" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) (C1 ('MetaCons "Chord" 'PrefixI 'True) (S1 ('MetaSel ('Just "chordNoteName") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NoteName) :*: (S1 ('MetaSel ('Just "chordFunctionality") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Functionality) :*: S1 ('MetaSel ('Just "chordIntervals") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 [Integer]))))

data Cadence Source #

A Cadence is a movement type combined with a target chord quality. Abstract: defines "approach by descending 5th to a major chord" without specifying which root pitch.

Instances

Instances details
Generic Cadence Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep Cadence 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep Cadence = D1 ('MetaData "Cadence" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) (C1 ('MetaCons "Cadence" 'PrefixI 'True) (S1 ('MetaSel ('Just "cadenceFunctionality") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Functionality) :*: (S1 ('MetaSel ('Just "cadenceMovement") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Movement) :*: S1 ('MetaSel ('Just "cadenceIntervals") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 [PitchClass]))))

Methods

from :: Cadence -> Rep Cadence x #

to :: Rep Cadence x -> Cadence #

Show Cadence Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq Cadence Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Methods

(==) :: Cadence -> Cadence -> Bool #

(/=) :: Cadence -> Cadence -> Bool #

Ord Cadence Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep Cadence Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep Cadence = D1 ('MetaData "Cadence" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) (C1 ('MetaCons "Cadence" 'PrefixI 'True) (S1 ('MetaSel ('Just "cadenceFunctionality") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Functionality) :*: (S1 ('MetaSel ('Just "cadenceMovement") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Movement) :*: S1 ('MetaSel ('Just "cadenceIntervals") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 [PitchClass]))))

toMovement :: PitchClass -> PitchClass -> Movement Source #

Convert two pitch classes to a Movement (bass motion direction/distance) Ported from legacy MusicData.hs toMovement

Movement direction is determined by the shorter path around the pitch class circle. Example: C(0) to G(7) = descending by 5 (shorter path is 5 semitones down via P 12) G(7) to C(0) = ascending by 5 (shorter path is 5 semitones up to P 12 ≡ P 0)

fromMovement :: Movement -> PitchClass Source #

Convert Movement back to PitchClass interval

data EnharmonicSpelling Source #

Enharmonic spelling preference (sharp or flat) Stored in CadenceState to enable consistent spelling throughout a progression

Instances

Instances details
Generic EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep EnharmonicSpelling 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep EnharmonicSpelling = D1 ('MetaData "EnharmonicSpelling" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) (C1 ('MetaCons "SharpSpelling" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "FlatSpelling" 'PrefixI 'False) (U1 :: Type -> Type))
Read EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Show EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Ord EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep EnharmonicSpelling Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep EnharmonicSpelling = D1 ('MetaData "EnharmonicSpelling" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) (C1 ('MetaCons "SharpSpelling" 'PrefixI 'False) (U1 :: Type -> Type) :+: C1 ('MetaCons "FlatSpelling" 'PrefixI 'False) (U1 :: Type -> Type))

data EnharmonicPreference Source #

Convert EnharmonicSpelling to the actual function (PitchClass -> NoteName) |Classify a pitch class by its natural enharmonic preference. C (PC 0) is AMBIGUOUS - it adopts the pairing's preference (prior or posterior). All other pitch classes have definite preferences.

enharmonicPreference :: PitchClass -> EnharmonicPreference Source #

Get the natural enharmonic preference for a pitch class. C (0) is ambiguous and should be handled specially. F#/Gb (6) is always treated as sharp (F#).

selectEnharm :: EnharmonicSpelling -> PitchClass -> PitchClass -> EnharmonicSpelling Source #

Determine enharmonic spelling for posterior root given prior's actual spelling and both pitch classes.

Rules: (1) SAME PITCH CLASS: Always use prior's actual spelling (the root hasn't changed!) - Example: D# → D# keep Sharp, Eb → Eb keep Flat (2) C IS FLEXIBLE: When C is prior or posterior, the OTHER pitch's preference can override - Prior is C + posterior is definite: adopt posterior's preference - Posterior is C + prior is definite: adopt prior's actual spelling - Both C: persist current (no preference to guide) (3) BOTH DEFINITE (different pitch classes): Consensus-based switching a) If prior's actual spelling matches posterior's preference → persist (consensus on choice) b) If both have SAME preference AND it differs from prior's actual → switch (consensus to switch) c) Otherwise: persist prior's actual spelling (disagreement or already aligned)

Examples: D# → D# (same PC) → persist prior's actual spelling (Sharp or Flat, doesn't matter which) D# (actual Sharp) → E (SharpPref) → both prefer Sharp, actual differs → switch to Sharp!

defaultEnharm :: PitchClass -> EnharmonicSpelling Source #

Default enharmonic spelling for initial state based on root pitch class. C defaults to flat; all others use their natural preference.

spellingToPreference :: EnharmonicSpelling -> EnharmonicPreference Source #

Convert enharmonic spelling to the corresponding preference (for symmetry).

enharmonicFunc :: EnharmonicSpelling -> PitchClass -> NoteName Source #

Select the spelling function for a given EnharmonicSpelling — sharp or flat. Used to render a pitch class as a note name consistently across a progression, so a single chord grid does not mix F# and Gb.

inferSpelling :: [Int] -> EnharmonicSpelling Source #

Infer enharmonic spelling from absolute pitches (bass first). Three-layer system: Layer 1: 3-set match — bass matches AND remaining 2 PCs are subset of chord Layer 2: 2-set match — bass matches AND remaining 1 PC is subset of chord Layer 3: Root fallback — defaultEnharm on bass PC

noteNameImpliesSpelling :: NoteName -> Maybe EnharmonicSpelling Source #

Detect if a NoteName carries an explicit sharp/flat preference. Sharp variants (C', D', F', G', A') → Just SharpSpelling Flat variants (Db, Eb, Gb, Ab, Bb) → Just FlatSpelling Natural notes (C, D, E, F, G, A, B) → Nothing (use inference)

isAmbiguousPattern :: [Int] -> Bool Source #

Check if a chord's absolute pitches match an ambiguous pattern.

data ChordState Source #

A ChordState is a Chord with a concrete root pitch (anchored in pitch space).

Constructors

ChordState 

Instances

Instances details
Generic ChordState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep ChordState 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep ChordState = D1 ('MetaData "ChordState" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) (C1 ('MetaCons "ChordState" 'PrefixI 'True) (S1 ('MetaSel ('Just "stateChord") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Chord) :*: S1 ('MetaSel ('Just "stateRoot") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NoteName)))
Show ChordState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq ChordState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep ChordState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep ChordState = D1 ('MetaData "ChordState" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) (C1 ('MetaCons "ChordState" 'PrefixI 'True) (S1 ('MetaSel ('Just "stateChord") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Chord) :*: S1 ('MetaSel ('Just "stateRoot") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NoteName)))

data CadenceState Source #

A CadenceState is a Cadence with a concrete root pitch and enharmonic spelling. This represents "descend by 5th to G major (flat spelling)" rather than abstract. The enharmonic spelling ensures consistent note naming throughout a progression.

Instances

Instances details
Generic CadenceState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Associated Types

type Rep CadenceState 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep CadenceState = D1 ('MetaData "CadenceState" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) (C1 ('MetaCons "CadenceState" 'PrefixI 'True) (S1 ('MetaSel ('Just "stateCadence") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Cadence) :*: (S1 ('MetaSel ('Just "stateCadenceRoot") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NoteName) :*: S1 ('MetaSel ('Just "stateSpelling") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 EnharmonicSpelling))))
Show CadenceState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq CadenceState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep CadenceState Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

type Rep CadenceState = D1 ('MetaData "CadenceState" "Harmonic.Rules.Types.Harmony" "theHarmonicAlgorithm-3.1.1-JBKSxRG0idi9DGVvI173xL" 'False) (C1 ('MetaCons "CadenceState" 'PrefixI 'True) (S1 ('MetaSel ('Just "stateCadence") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 Cadence) :*: (S1 ('MetaSel ('Just "stateCadenceRoot") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 NoteName) :*: S1 ('MetaSel ('Just "stateSpelling") 'NoSourceUnpackedness 'NoSourceStrictness 'DecidedLazy) (Rec0 EnharmonicSpelling))))

toTriad :: (PitchClass -> NoteName) -> [Int] -> Chord Source #

Build a Triad from an enharmonic function and integer list TRIADS: exactly 3 pitch classes. If >3 input, reduces to best triad. Uses elaborate inversion detection from legacy MusicData.hs

flatTriad :: [Int] -> Chord Source #

Shortcut with flat spelling for triads

sharpTriad :: [Int] -> Chord Source #

Shortcut with sharp spelling for triads

toChord :: (PitchClass -> NoteName) -> [Int] -> Chord Source #

Build a Chord from an enharmonic function and integer list CHORDS: preserves all pitches, uses chord-specific naming for extended harmonies Ported from legacy MusicData.hs toChord (lines 896-941)

flatChord :: [Int] -> Chord Source #

Shortcut with flat spelling for chords

sharpChord :: [Int] -> Chord Source #

Shortcut with sharp spelling for chords

toCadence :: (Chord, Chord) -> Cadence Source #

Convert a pair of Chords to a Cadence

fromChordState :: ChordState -> Chord Source #

Convert ChordState to the underlying Chord

fromCadenceState :: CadenceState -> Chord Source #

Convert CadenceState back to a Chord (applying the root and spelling)

initCadenceState :: Int -> String -> [Int] -> CadenceState Source #

Initialize a CadenceState from movement, note name, and quality intervals. Enharmonic spelling is inferred from the chord's absolute pitch content.

mkCadenceStatePCs :: NoteName -> Movement -> [Int] -> CadenceState Source #

Build a CadenceState from a root, movement, and root-relative intervals, preserving full cardinality. The non-truncating counterpart to initCadenceState, which routes through flatTriad / toCadence and silently reduces >3 PCs to a triad. Intervals are root-relative (0 = the root; inserted if missing); stored in zero form (sorted, deduped mod 12). Functionality dispatches on cardinality: <=3 uses the triad namer, 4+ the chord namer. Spelling from the typed root when it implies one (Eb → flat), otherwise inferred from absolute pitch content.

walkTriadCadence :: Cadence -> Cadence Source #

Project a Cadence onto its most consonant ROOTED embedded triad. Identity for intervals (every corpus-generated cadence). For3: enumerates the 3-subsets that keep the root ([0,a,b] over the nonzero intervals), picks the least dissonant via the Layer-B mostConsonant replica, and renames with the triad namer. Root, movement, and (via walkTriadState) spelling are preserved, so the projected cadence's show is always a corpus-shaped graph key. This is the walk shadow for >3-note material (regen over hand-built extended progressions, the genJ classical steer): a hand-built added tone can reinterpret the harmony (e.g. [0,3,6]+7 projects to [0,3,7] minor) and thereby steer the next step.

walkTriadState :: CadenceState -> CadenceState Source #

State-level walk projection: walkTriadCadence on the cadence, root and spelling untouched. Identity for triad states.

data ToTriadTrace Source #

Trace of toTriad transformation steps

Constructors

ToTriadTrace 

Fields

Instances

Instances details
Show ToTriadTrace Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

Eq ToTriadTrace Source # 
Instance details

Defined in Harmonic.Rules.Types.Harmony

fromCadenceStateTraced :: CadenceState -> (Chord, ToTriadTrace) Source #

Convert CadenceState to Chord with full transformation trace. Used for maximum verbosity diagnostics (gen''). Includes raw DB data plus all transformation stages.

constructCadence :: (String, String) -> Cadence Source #

Construct a Cadence from DB text format (movement string, chord string)

Example: constructCadence ("desc 3", "[P 0,P 3,P 8]") => Cadence { cadenceFunctionality = "maj_1stInv", cadenceMovement = Desc (P 3), ... }

corpusFunctionality :: [PitchClass] -> Functionality Source #

Name a zero-form triad exactly as the corpus stores it. The graph's node keys (show = movement + functionality) carry the names below; any cadence whose show is used as a fetch key MUST be named through here, or the walk silently drops to fallback-only after landing on that form (a latent bug fixed 2026-08-19: constructCadence previously named via bare toFunctionality, so every graph-selected inversion candidate — [0,3,8] read back as min#5 instead of the stored maj_1stInv — produced a keyless next fetch).

The table is the complete 55-form vocabulary transcribed verbatim from a live corpus dump (2026-08-19, MATCH (c:Cadence) RETURN DISTINCT c.chord, c.show). It reflects the LEGACY naming rules the database was ingested under — including the forms where the modernised nameFuncTriad deliberately diverges ([0,2,7] is stored sus4_1stInv, modern says sus2; [0,5,10] is stored sus4_2ndInv, modern says 7sus4) — so it cannot be derived from the current namers. If the corpus is ever re-ingested, regenerate this table from the same query.

Non-triad input (fewer/more than 3 PCs) falls back to the current namers — such sets are never corpus keys.

deconstructCadence :: Cadence -> (Movement, [PitchClass]) Source #

Deconstruct a Cadence to DB text format (movement, pitch class list) Returns the Movement and [PitchClass] for serialization

rootNote :: Chord -> NoteName Source #

Extract the root note from a Chord

inversions :: [PitchClass] -> [[PitchClass]] Source #

Generate all cyclic inversions of a pitch set (in zero form) Each inversion is transformed to zero form for proper comparison

isInversion :: Cadence -> Bool Source #

Check if a cadence is an inversion (1st or 2nd) based on zero-form intervals. Patterns match those in detectInversion.

normalForm :: [PitchClass] -> [PitchClass] Source #

Normalize a pitch set to its most compact form Ported from legacy MusicData.hs normalForm

primeForm :: [PitchClass] -> [PitchClass] Source #

Compute prime form (transposition and inversion invariant) Ported from legacy MusicData.hs primeForm

zeroFormPC :: [PitchClass] -> [PitchClass] Source #

Zero-form for pitch classes Subtracts the FIRST element (not minimum) then sorts, matching legacy behavior |Normalize pitch class set to zero form by subtracting first element then sorting. Examples: [P 4, P 7, P 11] → [P 0, P 3, P 7] (maj7 in root position) [P 8, P 11, P 3] → [P 0, P 3, P 7] (maj7 in 2nd inversion) Used by all Cadence construction paths (DB, toCadence, fallback) for semantic consistency.

Voice leading (cyclic DP paradigms)

voiceLeadingCost :: [Int] -> [Int] -> Int Source #

Calculate the voice leading cost between two chords.

Cost components: * Base: sum of absolute MIDI movements per voice. * Parallel penalty: +3 for each parallel perfect 5th / octave between ANY voice pair (not just adjacent), when at least one voice moves. * Large leap penalty: +2 per voice moving > 4 semitones. * Register-exchange penalty: +4 per adjacent voice pair where both voices move ≥5 semitones in opposite directions (split-leap pattern producing register inversion). Note: not classical "voice crossing" — sorted MIDI voicings have no voice identity to cross — but the same musical effect of register-swapping leaps. * Contrary motion bonus: −1 per voice pair (any pair) where both voices move ≤4 semitones in opposite directions (modest divergence). * Stepwise motion bonus: −1 per stepping voice (movement ∈ {1, 2}) when ≥2 voices step. Single-voice steps contribute 0.

Magnitudes calibrated to compose: contrary motion and register exchange are deliberately disjoint by magnitude (≤4 vs ≥5 thresholds), aligning with the leap-penalty trigger so the same motion is never both rewarded and penalised. They also differ in pair scope by design: register exchange scans ADJACENT pairs only (a register swap is a neighbouring-voices phenomenon), while contrary motion rewards ANY pair.

The total is floored at 1 for any actual motion (from /= to): bonuses could otherwise exceed base + penalties and drive a moving transition below the held-chord cost of 0 — inverting the musical preference. A held chord ("available static movement") is always strictly cheapest; bonuses still discriminate among positive-cost alternatives. The floor applies to the TOTAL only — component bonuses stay un-clamped inside the sum (e.g. [0,4,7]→[-1,2,7] = base 3 + stepwise −1 = 2). Cross-cardinality transitions (mixed-set seams: lead' cues, hand-built fromChords material) are costed by optimal monotone padding: the smaller sorted voicing is expanded by duplicating tones per the minimal-distance non-crossing alignment in which every voice of BOTH chords participates (alignVoices), then the full cost above runs verbatim on the aligned pair. An extra voice pays exactly its distance to the tone it splits from (a literal unison doubling is free); no voice can appear or vanish unpenalised. Historical note: this branch was a flat 999 sentinel, which in the DP acted as "ignore this edge" — seam registers were decided by downstream edges alone and the cyclic wrap objective was silently disabled on mixed material. Voicings must be SORTED ascending — list position is voice order for both the alignment and the parallel-perfect scan. Every production caller sorts before calling.

totalCost :: [[Int]] -> Int Source #

Calculate total voice leading cost for a sequence of chords

cyclicCost :: [[Int]] -> Int Source #

Calculate cyclic cost: total cost including wrap-around from last to first. This is essential for loop-aware optimization.

From the evaluation document: "Adding wrap-around cost to the voice leading solver solves the drift issue elegantly. It forces the algorithm to find a path that is not just locally optimal, but topologically closed."

voiceMovement :: Int -> Int -> Int Source #

Calculate the movement for a single voice between two concrete pitches. Movement is measured in absolute semitones (not mod 12 since we're working with concrete pitches in the [0,36] range).

minimalMovement :: PitchClass -> PitchClass -> Int Source #

Calculate minimal movement between two pitch class values (mod 12). Exported API; currently unused inside the engine (kept for REPL and downstream use).

allVoicings :: [Int] -> [[Int]] Source #

Generate all valid voicings for a pitch-class set of any cardinality. Each pitch class is placed at its pitchPlacements octaves (PCs 0-6 get 2 placements in [12, 30]; PCs 7-11 get 3 in [7, 35]) — candidate count is the per-PC placement product (2^a·3^b: typically 12 for a triad, 36 for a tetrad, 72 for a 5-PC set), NOT 3^N. The key-dependent window asymmetry is a known calibration; widening it changes every solved voicing globally and is deferred behind a before/after listening protocol. Results are sorted low-to-high and deduplicated.

Historical note: this was hard-coded to 3 notes, with non-triads falling back to the single candidate [sort pcs] pinned in octave [0,11] — which collapsed bars 2..n of any multi-bar 4+-note progression ~2 octaves below bar 1 under flow/grid (bar 1 goes through initialCompact, the rest through here, then normalizeByFirstRoot applies one uniform shift).

initialCompact :: Int -> [Int] -> [Int] Source #

Create initial compact voicing: root in bass in target octave (12-23), upper voices stacked compactly above.

solveRoot :: [[Int]] -> [[Int]] Source #

Solve with root always in bass (root paradigm). Filters candidates at each position to only those where bass note mod 12 == root PC. Result is normalized so first chord's root is in [-12,-1]. Memoised: see memoSolve.

solveFlow :: [[Int]] -> [[Int]] Source #

Solve FLOW paradigm using cyclic DP: Smoothest voice leading with any inversion allowed for bars 1..n-1. Bar 0 is anchored to the compact root-position voicing (initialCompact) so the progression's starting register is predictable and normalizeByFirstRoot has a stable anchor. Voice crossings permitted in subsequent bars for optimal smoothness. Result is normalized so first chord's root is in [-12, -1]. Memoised: see memoSolve.

Progressions and scales

Interactive behaviour

Filter functions

String-friendly versions for TidalCycles

overtones :: String -> [PitchClass] Source #

Parse overtones from a String (Tidal-friendly) Example: overtones "E A D G" -> bass tuning overtones

key :: String -> [PitchClass] Source #

Parse key from a String (Tidal-friendly) Example: key "#" -> G major, key "bb" -> Bb major

funds :: String -> [PitchClass] Source #

Parse fundamentals from a String (Tidal-friendly) Example: funds "E F# G" -> [4, 6, 7]

tuning :: String -> [PitchClass] Source #

Parse tuning from a String (Tidal-friendly)

wildcard :: String -> Bool Source #

Check if a string is a wildcard (Tidal-friendly)

Text versions

parseOvertones :: Text -> [PitchClass] Source #

Parse overtones with 3 overtones (default: root, P5, M3)

parseKey :: Text -> [PitchClass] Source #

Parse key (overtone count not used for keys)

parseFunds :: Text -> [PitchClass] Source #

Parse fundamentals with 3 overtones (default)

parseTuning :: Text -> [PitchClass] Source #

Parse tuning with 3 overtones (default: root, P5, M3 — the distinct pitch classes of the playable tapped-harmonic domain)

isWildcard :: Text -> Bool Source #

Check if a filter string is a wildcard (matches everything)

Overtone annotation support

parseTuningNamed :: Text -> [(String, Int)] Source #

Parse a tuning string preserving string names for overtone annotation. Case is preserved for string identification (uppercase = lower octave, lowercase = higher octave per thesis convention).

Examples: "E A D G" → [(E,4), (A,9), (D,2), (G,7)] "E A e G B" → [(E,4), (A,9), ("e",4), (G,7), (B,11)] "*" → [] (wildcard has no named strings)

Database interface

connectNeo4j :: IO DbConn Source #

Connect to the local Neo4j from Harmonic.Config (override with the HA_NEO4J_URL environment variable, e.g. http://localhost:7477 to point a REPL at a scratch container). Probes the server with RETURN 1 so an unreachable database surfaces the error here, at connect time — matching what every online generation path expects.

Ingestion pipeline

Corpus ingestion. Not needed to play — see Harmonic.Rules.Import.CSV.

TidalCycles interface

Pattern-level operations

type VoiceFunction = Progression -> [[Int]] Source #

Voice function type: extracts integer pitch sequences from progression

voiceRange :: (Int, Int) -> Pattern Int -> Pattern Int Source #

Filter pattern events by DEGREE-INDEX range — the raw Pattern Int values before the degree→pitch mapping, NOT MIDI notes. Instrument range clipping in MIDI space happens later, via clip.

arrange Source #

Arguments

:: (Double, Double)

Kinetics gate: events pass only while kSignal sits inside (lo, hi) — the same predicate as ki, applied here so every instrument line carries its own activation band

-> IK

Performance context (kinetics + chord selection)

-> (Int, Int)

Degree-index trim for the input patterns (scale degrees, not MIDI; instrument-range clipping happens later via clip)

-> Layer

Progression layer to voice — single (T | S | M) or synthesized combination (TS | TM | SM | TSM | PT)

-> VoiceFunction

Voice function (flow, root, etc.)

-> (Progression -> Progression)

Progression modifier (overlapF 0, id, etc.)

-> [Pattern Int]

Input patterns to harmonize

-> Pattern ValueMap 

Render scale-degree patterns into a playable ControlPattern, reading pitches from the progression under the given voicing strategy.

The workhorse of the Tidal interface: every orchestral instrument in Harmonic.Interface.Tidal.Orchestra is a thin wrapper around it.

d1 $ arrange (0,1) k (-9,9) T flow id ["0 1 2 3"]

arrange' Source #

Arguments

:: (Double, Double)

Kinetics range

-> IK

Performance context

-> (Int, Int)

Degree-index trim for the input patterns (scale degrees, not MIDI; instrument-range clipping happens later via clip)

-> Layer

Progression layer (T | S | M)

-> VoiceFunction

Voice function

-> (Progression -> Progression)

Progression modifier

-> [Pattern Int]

Input patterns to harmonize

-> Pattern ValueMap 

Map notes through chords using squeeze, with kinetics range gating.

Same kinetics/modifier pattern as arrange, but uses squeeze strategy: each chord slot gets the full input pattern compressed to fit.

parallel :: Pattern Note -> ControlPattern -> ControlPattern Source #

Stack fixed-interval parallel voices over an arranged ControlPattern. The offset pattern is the FULL voice spec in absolute semitones: each note of pat is replaced by one copy per simultaneous offset, shifted by that offset. Include 0 to retain the original note; omit it to drop the root.

Comma = simultaneous voices, space = time-sequenced offsets (standard mininotation, natively evaluated). Applied post-voicing/post-range-filter, so offsets are not gated by the arrange MIDI range.

parallel "0 7"      $ arrange ... -- root + perfect fifth above
parallel "7"        $ arrange ... -- fifth only (root dropped)
parallel "[0,-5,4]" $ arrange ... -- root, fourth below, major third above

harmonicState :: Layer -> IK -> ControlPattern Source #

The chosen layer's pitch classes as held MIDI notes, for a device that locks a played keyboard to the harmony (the rig's MiDiNiNjA X3).

One note per distinct pitch class, MIDI 108-119 (pitch class = note mod 12), on MIDI channel 10, each held for the length of its chord: the structure follows the chord grid (snd k), so two chords in a bar give two held sets. Any layer or combination (T, S, M, TS, ...) and any set size. Degree indices 0-11 cover the largest set; the indices past a chord's size fold back onto pitch classes already sent and are dropped, so each chord change costs one message per pitch class on a DIN line shared with the music.

Each set goes out stateLead ahead of its chord: on the wire before the chord's own notes, and in place for keys played just before the downbeat.

Channel 10, notes 108-119 sits above everything else on that channel in both rigs: the JV-map kit (0-9, 60-80), metronome and count, the MPC sub (35-48). The X3 consumes these notes before they reach the MPC.

kmap f k lyr = p "kmap" $ f $ harmonicState lyr k (BootTidal)

warp :: String -> Pattern Int Source #

Parse a mininotation chord selection pattern (bar-relative). The /N divisor specifies the number of bars the pattern spans.

let r = warp "[1 2 3 4]/4"   -- 4 chords over 4 bars (1 per bar)
let r = warp "[1 2]/8"       -- 2 chords over 8 bars (4 bars each)

rep :: ProgressionContext -> Pattern Time -> Pattern Int Source #

Generate a sequential chord selection pattern from a progression. Auto-derives length from the progression. Timing is bar-relative.

let r = rep s4 1     -- 4 chords over 4 bars (1 bar each)
let r = rep s4 0.5   -- 4 chords over 2 bars (half bar each)

lookupChordAt :: Time -> Pattern Int -> Int Source #

Point-query a chord selection pattern at a specific time. Returns the chord index (0-indexed) active at time t. Falls back to chord 0 if no events found.

lookupChord :: ProgressionContext -> Int -> Chord Source #

Lookup a chord from a progression context by index with modulo wrap. Operates on the triad layer (the harmonic content).

lookupProgression :: ProgressionContext -> Pattern Int -> Pattern [Int] Source #

Lookup progression (triad layer) as a pattern of voicings via flow.

overlapF :: Int -> Progression -> Progression Source #

Forward overlap: merge pitches from n bars ahead

type Tuning = [(NoteName, Int)] Source #

Open strings of an overtone instrument as (name, scientific octave): (A,1) is A1 = MIDI 33.

ecbc :: Tuning Source #

The instrument behind hmnx: the Electric Contrabass Cittern as the MPC harmonics keymap holds it — open strings A2 E3 G3 (thesis §2.1: EAeGB is E2 A2 E3 G3 B3) and the top string in both Hipshot lever positions, B3 and C4. B and C are one physical string, but the keygroup carries both sets of samples, so both are always available to a pattern; the harmonic context is what limits the space. Fixed on purpose: the keymap never changes, so no launcher states a tuning.

composite Source #

Arguments

:: (Double, Double)

Kinetics gate, as arrange

-> IK

Performance context

-> Layer

Harmonic state: T (chord tones) or S (pentatonic)

-> [Pattern Int]

Contours: ints into the bar's viable series

-> Pattern ValueMap 

The whole overtone instrument. Pattern ints index the bar's viable composite series — the harmonics of ecbc whose pitch class the layer voices — compressed to 0, 1, 2 … ascending and looped by floor-mod, so "[0,1,2]" is the lowest three available tones from the bottom up and "[-1,-2,-3]" the highest three from the top down, whatever the size of the structure in play. A pitch class present in two octaves is two ints. Never an octave wrap: these are fixed sample pitches. A bar whose voicing has no instrument tone rests. Polyphonic; harmonics is the per-string filter over the same mapping. The harmonic context is the only limit on the space: generating inside the instrument's palette (hcOvertones) keeps every tone reachable, and overtoneMap shows what each int hits.

, composite (0,1) k T ["~", "[0,1,2]/4"] # ch 11

harmonics :: NoteName -> (Double, Double) -> IK -> Layer -> [Pattern Int] -> Pattern ValueMap Source #

One string of ecbc, named by its open note (A E G B C; enharmonics collapse): the SAME int → pitch mapping as composite, but only the pitches that string can sound pass — the rest are silent, not remapped — so a string block isolates or mutes a string and owns its sustain. Within the string the result is mono' (latest-note priority: a new harmonic damps the previous, as on the instrument).

, harmonics A (0,1) k T ["~", pat] # ch 11

overtoneMap :: ProgressionContext -> Layer -> [Pattern Int] -> IO () Source #

Print, per chord of a progression, what the ints of the basic series map to on the instrument — pitch, MIDI number and sources in thesis notation (E2 = string E, overtone 2; / = alternative sources) — with the top-down alias beside each int; then the distinct ints of the contour over its first bar, resolved per chord. Takes the progression context and the layer the block plays, so what it prints is what composite sounds.

overtoneMap s T ["[0,1,2]/4"]

overtoneTable :: [Int] -> [(Int, Int, [(NoteName, Int)])] Source #

The basic series over a set of voiced pitch classes, on ecbc: (int, MIDI pitch, sources), a source being (string, overtone number) in the thesis's numbering — OT1 the fundamental's class (partials 2 and 4), OT2 the fifth, OT3 the third. Pure core of overtoneMap.

compositeSeries :: Tuning -> [Int] Source #

The composite overtone series (thesis §2.2): every pitch any string of a tuning can sound as a natural harmonic, ascending, once each. compositeSeries ecbc is the keymap: 57 64 67 69 71 72 73 74 76 78 79 80 83 84 87 88.

stringPitches :: (NoteName, Int) -> [Int] Source #

The four playable partials of the 2016 thesis — octave, fifth, double octave, third (partials 2 3 4 5) — of one open string, as MIDI pitches.

partialOffset :: Int -> Int Source #

Semitones from a string's fundamental to its n-th partial: round (12 * logBase 2 n), so partials 2..5 sit at +12 +19 +24 +28.

Form and kinetics

data FormNode Source #

A node in a form definition: a point in time with kinetics level, dynamic level, active progression, and the transition style of the section starting here.

Constructors

FormNode 

Fields

Instances

Instances details
Show FormNode Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

Eq FormNode Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

data FormTime Source #

A node's position in time — wall-clock Secs elapsed (0 = the start) or musical Bars (4/4), numbered from 1 as the 12 Step counts them (bar 1 = the start). Resolved to Tidal cycles at realization (see formK). Mix freely in one form.

Constructors

Secs Double 
Bars Double 

Instances

Instances details
Show FormTime Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

Eq FormTime Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

data Transition Source #

How a section moves to the next node: Smooth (ramped) or Snap (hold this node's value, then jump on the next node's exact time).

Constructors

Smooth 
Snap 

Instances

Instances details
Show Transition Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

Eq Transition Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

data Kinetics Source #

Realized form: continuous and discrete signals for live performance.

Constructors

Kinetics 

Fields

  • kSignal :: Pattern Double

    Kinetics level 0-1 (continuous interpolated)

  • kDynamic :: Pattern Double

    Dynamic envelope 0-1 (continuous interpolated)

  • kProg :: Pattern ProgressionContext

    Active 3-layer progression (step function)

  • kProgs :: [ProgressionContext]

    The distinct progressions kProg can emit — finite and known at construction (form nodes, or the single lK progression). Voicing caches derive from THIS list, exactly and horizon-free, instead of sampling kProg over an arbitrary time window.

  • kLoopSecs :: Double

    Form total duration in seconds; 0 = atemporal (single-node iK or lK). Consumers like the 4-char-display helper read this to drive a wall-clock counter that wraps every kLoopSecs.

  • kCps :: Double

    Cycles per second at form construction (= bpm/60). Used by the display broadcaster to convert cycle time → seconds. Stays coherent with Tidal's actual cps because both are derived from the same bpm on every launcher re-evaluation.

  • kDegrees :: Int -> Bool

    Launcher-wide degree filter, applied by arrange on top of each line's own range (isolate, isolate'); const True = all.

type IK = (Kinetics, Pattern Int) Source #

Performance context: Kinetics bundled with chord selection pattern. Reduces parameter threading — r and k are always passed together.

at :: Double -> Double -> Double -> ProgressionContext -> FormNode Source #

Form node builders. Time unit and transition are orthogonal: at/at' take wall-clock seconds elapsed, rh/rh' take musical bar numbers (rehearsal marks, 4/4, bar 1 = the start of the form); unprimed = smooth transition, primed = snap.

at 0 0 0 s seconds, smooth rh 10 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 18 0.9 0.9 s bars, snap

at' :: Double -> Double -> Double -> ProgressionContext -> FormNode Source #

Form node builders. Time unit and transition are orthogonal: at/at' take wall-clock seconds elapsed, rh/rh' take musical bar numbers (rehearsal marks, 4/4, bar 1 = the start of the form); unprimed = smooth transition, primed = snap.

at 0 0 0 s seconds, smooth rh 10 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 18 0.9 0.9 s bars, snap

rh :: Double -> Double -> Double -> ProgressionContext -> FormNode Source #

Form node builders. Time unit and transition are orthogonal: at/at' take wall-clock seconds elapsed, rh/rh' take musical bar numbers (rehearsal marks, 4/4, bar 1 = the start of the form); unprimed = smooth transition, primed = snap.

at 0 0 0 s seconds, smooth rh 10 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 18 0.9 0.9 s bars, snap

rh' :: Double -> Double -> Double -> ProgressionContext -> FormNode Source #

Form node builders. Time unit and transition are orthogonal: at/at' take wall-clock seconds elapsed, rh/rh' take musical bar numbers (rehearsal marks, 4/4, bar 1 = the start of the form); unprimed = smooth transition, primed = snap.

at 0 0 0 s seconds, smooth rh 10 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 18 0.9 0.9 s bars, snap

iK :: Double -> [FormNode] -> Pattern Int -> IK Source #

Construct performance context from BPM, form nodes, and chord selection.

k = iK tempo [at 0 0 0 s, at 30 1 1 s] (warp "[1 2 3 4]/8")

lK Source #

Arguments

:: Pattern Double

Kinetics signal (0-1, live)

-> Pattern Double

Dynamics signal (0-1, live)

-> ProgressionContext

Active 3-layer progression

-> Pattern Int

Chord-selection pattern

-> IK 

Live kinetics: build IK from reactive kinetics/dynamics signals. Bypasses form interpolation — use when the envelope is driven by live input (e.g. MIDI CC) rather than a static keyframed form.

k = lK exP exP s r -- pedal drives both kinetics and dynamics

formK :: Double -> [FormNode] -> Kinetics Source #

Realize a form definition into Kinetics signals at a given BPM. Single-node forms produce constant signals (global state). Multi-node forms produce per-segment signals — smooth (ramp) or snap (step) per each node's fnTrans — and a step-function progression, looping at the form's total duration. Time is resolved from each node's FormTime.

Contract details:

  • The LAST node is the loop terminator, not a playable section: segments run between consecutive node pairs, and the final node's time sets kLoopSecs. Its kinetics/dynamic values only shape the ramp INTO it.
  • Nodes are sorted by resolved time before use, so a moved rehearsal mark cannot produce negative-width segments. Nodes sharing a time yield a zero-width segment (the earlier one is inaudible).
  • formK bpm [] yields silence on every signal — safe to evaluate mid-edit; nothing sounds until a node exists.
  • Smooth ramps render at 16 steps per SEGMENT regardless of length (see formSignal) — a long segment crosses kinetics thresholds on a correspondingly coarse grid.

ki :: (Double, Double) -> IK -> Pattern a -> Pattern a Source #

Range gate: mask a pattern by kinetics signal level. Events pass only when kSignal is within the (lo, hi) range.

slate :: (Double, Double) -> IK -> [Pattern a] -> Pattern a Source #

Gated stack: stack patterns and gate by kinetics range.

withForm :: IK -> (ProgressionContext -> Pattern ValueMap) -> Pattern ValueMap Source #

Bridge helper: apply a function taking ProgressionContext to a Kinetics context. Uses innerJoin to reactively switch when the form changes progressions.

isolate :: (Int, Int) -> IK -> IK Source #

A degree range for every line the launcher starts, applied in k:

k = isolate (-4,2) $ iK tempo form (rep s 4)

isolate keeps degree indices lo..hi (inclusive), as a line's own (-9,9) does; isolate' keeps only those outside it (lo and hi dropped). Each line's own range still applies, so the two intersect, and calls compose: isolate (-9,4) . isolate' (0,0) is -9..4 without degree 0. Lines that don't voice degree indices (drums, walking bass, subKick, harmonics, kmap) are unaffected.

isolate' :: (Int, Int) -> IK -> IK Source #

A degree range for every line the launcher starts, applied in k:

k = isolate (-4,2) $ iK tempo form (rep s 4)

isolate keeps degree indices lo..hi (inclusive), as a line's own (-9,9) does; isolate' keeps only those outside it (lo and hi dropped). Each line's own range still applies, so the two intersect, and calls compose: isolate (-9,4) . isolate' (0,0) is -9..4 without degree 0. Lines that don't voice degree indices (drums, walking bass, subKick, harmonics, kmap) are unaffected.

mark :: Double -> IK -> [Pattern a] -> Pattern a Source #

markAt in bars (see the doctrine there).

mark' :: Double -> IK -> [Pattern a] -> Pattern a Source #

markAt in seconds.

markbar :: Double -> IK -> [Pattern a] -> Pattern a Source #

markAt in bars (see the doctrine there).

marksec :: Double -> IK -> [Pattern a] -> Pattern a Source #

markAt in seconds.

markAt :: FormTime -> IK -> [Pattern a] -> Pattern a Source #

Form-relative cue: place patterns ONCE per form loop at a form time. The list is stacked (the shape of slate); each pattern's first cycle is squeezed into the one-beat window starting at that time, so a plain one-note pattern fires exactly on the beat and "1 1" gives two hits within it. Loops in lockstep with kSignal — the same slow loopCycles wrap as formSignal — so a cue and the kinetics can never drift apart. NOT kinetics-gated: a placement is absolute (the launcher's |* vel d still applies).

Time-unit doctrine: unprimed = bars, primed = seconds. mark takes musical bar numbers as written for rh nodes (bar 1 = cycle 0, the number the 12 Step shows); mark' takes seconds elapsed as for at nodes (the 12 Step's seconds counter reads the same) and, like them, assumes cps has been constant since cycle 0. markbar / marksec are synonyms.

Atemporal forms (single node, lK) fire once at absolute cycles. A time at or beyond the loop end, or no cps, is silence.

, mark 34 k [ pad 1 |* vel 0.9 ]      -- speech sample on the drop

Arranger functions (voicing paradigms)

rotate :: Int -> ProgressionContext -> ProgressionContext Source #

Rotate a progression by n bars (positive = left, negative = right). Bar-aligned: provenance and family travel with the bars.

excerpt :: Int -> Int -> ProgressionContext -> ProgressionContext Source #

Extract bars start to end (1-indexed, inclusive). Bar-aligned: provenance and family travel with the bars.

insert :: CadenceState -> Int -> ProgressionContext -> ProgressionContext Source #

Insert a CadenceState at position (1-indexed), replacing the existing one. Bar substitution: the foreign bar breaks strata/polytonal invariants, so provenance drops and those families downgrade.

switch :: Int -> Int -> ProgressionContext -> ProgressionContext Source #

Switch two bars at positions m and n (1-indexed). Treated as substitution (drops provenance, downgrades strata/polytonal families); promotable to the aligned tier via a lockstep update pair if per-bar identity is ever wanted here.

clone :: Int -> Int -> ProgressionContext -> ProgressionContext Source #

Clone bar m to position n (overwrites n with contents of m). Treated as substitution — see switch.

extract :: Int -> ProgressionContext -> CadenceState Source #

Extract a single CadenceState at index (1-indexed, modulo wrap) from the triad layer

transposeP :: Int -> ProgressionContext -> ProgressionContext Source #

Transpose a progression by n semitones. Strata provenance is anchored to ABSOLUTE pitch classes (the octatripentatonic universe has no transposition closure), so provenance and family survive only octave shifts; any other interval drops them.

reverse :: ProgressionContext -> ProgressionContext Source #

Reverse a progression. Bar-aligned: provenance and family travel.

fuse :: [ProgressionContext] -> ProgressionContext Source #

Fuse multiple progressions into one (concatenation)

fuse2 :: ProgressionContext -> ProgressionContext -> ProgressionContext Source #

Binary fuse for convenience in live coding

interleave :: ProgressionContext -> ProgressionContext -> ProgressionContext Source #

Interleave two progressions (alternating chords) Example: interleave [A,B,C] [X,Y,Z] = [A,X,B,Y,C,Z]

expandP :: Int -> ProgressionContext -> ProgressionContext Source #

Expand a progression by repeating the WHOLE progression n times (expandP 2 on C F G A yields C F G A C F G A). Bar-aligned replication: provenance and family travel.

progOverlap :: Int -> Progression -> Progression Source #

Bidirectional overlap: merge pitches from n bars in both directions

progOverlapF :: Int -> Progression -> Progression Source #

Forward-only overlap: merge pitches from n bars ahead

progOverlapB :: Int -> Progression -> Progression Source #

Backward-only overlap: merge pitches from n bars behind

grid :: Progression -> [[Int]] Source #

GRID paradigm: Root locked in bass with smooth compact voice leading. Uses cyclic DP to find globally optimal voicings. First chord starts compact with root in bass; all subsequent chords maintain root in bass with minimal voice movement.

flow :: Progression -> [[Int]] Source #

FLOW paradigm: Smoothest voice leading with any inversion allowed. Uses cyclic DP to find globally optimal voicings. Voice crossings permitted for optimal smoothness; bass doesn't need to be the root if an inversion provides smoother voice leading.

lite :: Progression -> [[Int]] Source #

LITE paradigm: Literal voicings with first-root normalization. Returns pitches as stored, but normalized so first chord's root is in [-12,-1]. No voice leading optimization applied (only octave normalization).

literal :: Progression -> [[Int]] Source #

Alias for lite (legacy compatibility)

root :: Progression -> [[Int]] Source #

ROOT paradigm: Root note only (root pitch class per chord). Extracts the root note (first element, mod 12) from each chord. Returns as single-element lists in [0,11] range.

Explicit progression construction

fromChords :: [[Int]] -> ProgressionContext Source #

Construct a Progression from explicit pitch-class sets. This is the main function for composing/arranging workflow (not generation). Takes an enharmonic spelling and a list of chord pitch-class sets, returns a Progression ready for arrange.

Example: fromChords [[0,4,7], [5,9,0], [7,11,2]] --> C major → F major → G major

prog :: [[Int]] -> ProgressionContext Source #

Legacy alias for fromChords (matches legacy prog function)

Groove interface (drums and sub bass)

subKick :: Pattern Double -> IK -> (Progression -> [[Int]]) -> (Time, String, String, String) -> Pattern ValueMap Source #

Groove interface: kick and sub bass locked to the harmony, with chord selection and kinetics gating from IK.

Sub on/off patterns and the kick pattern are bar-relative: "[1]/2" = one onset every 2 bars, "1*4" = 4 kicks per bar.

Each sub note is held by its own duration: from its onset to the next kill boundary — the manual offs (subOffPat) and, for maxDur < 1, each onset shifted by maxDur*4 — emitted with legato 1 so the note-off lands on the boundary. The hold is by note length, not by a CC64 pedal: the MPC sub program does not treat CC64 as a damper (a MIDI note-off arriving under a held pedal is stranded), so no CC64 is emitted here at all.

Chord selection uses innerJoin — we WANT new note-ons when the chord changes. Sub is gated at (0.1, 1) and kick at (0.2, 1) via ki. The launcher dynamic is the only dynamic applied — subKick deliberately ignores the form's kDynamic envelope, so the sub holds its level while the orchestra swells and ducks around it.

holdToNext :: Time -> Pattern Bool -> Pattern a -> Pattern a Source #

Hold each onset of pat until the next onset in bounds, rewriting the event whole to [onset, nextBoundary) (continuation fragments across cycles are preserved). A boundary is where the note must stop, so the note stops exactly there. Pair with # legato 1 so the emitted MIDI note-off lands on the boundary. Values are sampled once at the onset and held for the whole note (no mid-note re-sampling). look is the lookahead/lookback in cycles; note lengths must not exceed it.

fund :: Progression -> [[Int]] Source #

Extract harmonic roots regardless of inversion — the harmonic FUNDAMENTAL uniformly, where root is uniformly the sounding bass. Triads go through inversion detection (a first-inversion bar reports its harmonic root, not its bass). Bars of more than three tones ask the jazz namer for the rotation: a slash structure (Bb7 over Ab) reports its true root (Bb), matching the chart-convention grid name; a root-position extended chord — and any set the namer cannot read — keeps its stored anchor. The walking bass is unaffected either way: it anchors on the stored root internally and reads a voice function only for beat-1 pitch choice.

noteoff :: Time -> Pattern Bool -> Pattern Bool Source #

Truncate each gate onset's note length to at most 1/n of a bar (bar = 4 cycles), else extend it to the next onset. Only True onsets sound; a truncated tail is a rest; onsets are not moved. Pair with # legato 1 on sustaining instruments to hear the length. Precondition: n > 0.

Bar patterns are written "/4" (1 cycle = 1 beat, 1 bar = 4 cycles), so e.g. noteoff 4 caps each hit at a quarter note (1 cycle):

noteoff 4 "[[1 0 0 0] [0 0 0 0] [1 0 0 0] [1 0 0 0]]\/4"  ==  "[1 0 1 1]\/4"

son32 :: Pattern Bool Source #

Explicit 16-step (2-bar) boolean grids, one stroke per position, for clave and cascara feels. Use with struct or mask on any instrument or drum part.

32 is the 3-2 orientation; 23 is the 2-3 rotation (2 <~ over the /4 two-bar span swaps the halves). Onset positions are 1-indexed.

son23 :: Pattern Bool Source #

Explicit 16-step (2-bar) boolean grids, one stroke per position, for clave and cascara feels. Use with struct or mask on any instrument or drum part.

32 is the 3-2 orientation; 23 is the 2-3 rotation (2 <~ over the /4 two-bar span swaps the halves). Onset positions are 1-indexed.

rumba32 :: Pattern Bool Source #

Explicit 16-step (2-bar) boolean grids, one stroke per position, for clave and cascara feels. Use with struct or mask on any instrument or drum part.

32 is the 3-2 orientation; 23 is the 2-3 rotation (2 <~ over the /4 two-bar span swaps the halves). Onset positions are 1-indexed.

rumba23 :: Pattern Bool Source #

Explicit 16-step (2-bar) boolean grids, one stroke per position, for clave and cascara feels. Use with struct or mask on any instrument or drum part.

32 is the 3-2 orientation; 23 is the 2-3 rotation (2 <~ over the /4 two-bar span swaps the halves). Onset positions are 1-indexed.

bossa32 :: Pattern Bool Source #

Explicit 16-step (2-bar) boolean grids, one stroke per position, for clave and cascara feels. Use with struct or mask on any instrument or drum part.

32 is the 3-2 orientation; 23 is the 2-3 rotation (2 <~ over the /4 two-bar span swaps the halves). Onset positions are 1-indexed.

bossa23 :: Pattern Bool Source #

Explicit 16-step (2-bar) boolean grids, one stroke per position, for clave and cascara feels. Use with struct or mask on any instrument or drum part.

32 is the 3-2 orientation; 23 is the 2-3 rotation (2 <~ over the /4 two-bar span swaps the halves). Onset positions are 1-indexed.

bellpat32 :: Pattern Bool Source #

Explicit 16-step (2-bar) boolean grids, one stroke per position, for clave and cascara feels. Use with struct or mask on any instrument or drum part.

32 is the 3-2 orientation; 23 is the 2-3 rotation (2 <~ over the /4 two-bar span swaps the halves). Onset positions are 1-indexed.

bellpat23 :: Pattern Bool Source #

Explicit 16-step (2-bar) boolean grids, one stroke per position, for clave and cascara feels. Use with struct or mask on any instrument or drum part.

32 is the 3-2 orientation; 23 is the 2-3 rotation (2 <~ over the /4 two-bar span swaps the halves). Onset positions are 1-indexed.

Walking-bass line interface

lineHarmony Source #

Arguments

:: Pattern Double

Dynamics scalar (amp multiplier)

-> IK

Performance context (kinetics + chord-selection)

-> VoiceFunction

Beat-1 voicing (fund or root). Only the bass pitch class of each bar is read, so a solving strategy buys nothing here: grid returns the root by its own invariant, at the price of a voicing solve

-> [Pattern Int]

Polyphonic layers (1-indexed beat positions)

-> Pattern ValueMap 

Walking-bass arrangement with kinetics gating.

Fixed to the double-bass register (E1..C3, MIDI 28..48) inside walkLine; the emitted Tidal note values are pre-shifted by tidalNoteOffset so this range is audibly true at default synth tuning — no |- oct n compensation needed. Runtime register shifts via |+ oct n / |- oct n on the launcher side still compose normally.

The walk adapts to the generation family of the context it is given. For octatripentatonic progressions (pcProvenance = Just), the Pass-3 connector pool is reweighted: strata pitches (5 PCs) are most preferred, overlap (cyclic union of adjacent chord-PCs) is neutral, mode pitches (7 PCs) are admissible with a mild penalty, and chromatic ±1 approaches outside any of those sets are removed entirely. For jazz progressions (pcFamily = FJazz) each bar carries a BassVocab derived from its chord symbol, so the line reads the harmony as a bass player does — the fifth a 13th chord omits is restored, an altered dominant's #5 replaces the natural 5, and notated colour alterations stay off strong beats. gen and genE progressions walk their plain per-bar tone sets (for genE that is the foundation layer — the layer that owns the bass); connector tones draw on the key-area palette (barPalettes) — the same analysis behind the chordscale S/M layers.

Entropy is derived internally from the progression's harmonic character.

Scale source (switch mechanism)

data ScaleSource Source #

Scale source for melody mapping. Enables flexible melody construction by allowing harmony (with optional overlap) to serve as the scale source instead of explicit scale definitions.

Constructors

ExplicitScale [[Int]]

User-defined scale per chord

HarmonyAsScale Progression

Use harmony chords as scales

HarmonyWithOverlap Progression (Int -> Progression -> Progression)

Use harmony with overlap function applied

melodyStateFrom :: ScaleSource -> Progression Source #

Create melody state from scale source. Converts a ScaleSource into a Progression suitable for melody arrangement.

Starting state construction

lead :: String -> IO CadenceState Source #

Construct a CadenceState from a human-readable string.

Parses root, quality, and movement from space-separated tokens. Unspecified components fall through to randomness. Prints "root quality" to the console after construction.

Examples: start <- lead "E min (5)" -- E minor, ascending 5th start <- lead "E min" -- E minor, random movement start <- lead "min" -- random root, minor quality, random movement start <- lead E -- E, random quality, random movement start <- lead "" -- fully random start <- lead "(5)" -- random root and quality, fixed movement 5

lead' :: String -> IO CadenceState Source #

Construct a CadenceState from an explicit list of note names — the arbitrary-cardinality counterpart to lead. The first note is the root/bass; the rest become root-relative intervals (any count, so hand-built 4-note cues and beyond are first-class). Never truncates: builds via mkCadenceStatePCs, so all pitch content survives into the cue. Enharmonics follow the typed accidentals (Eb spells flat, D sharp; double accidentals accepted and resolved). An optional (N) token fixes the approach movement, otherwise it is randomized exactly like lead. Unrecognized tokens are reported and skipped; with no valid notes at all, falls back to fully random lead.

Examples: start <- lead' "Eb Gb Bb Db" -- Eb m7, random movement start <- lead' "A C E G (5)" -- A m7, ascending 5th approach start <- lead' "C E G" -- plain triad, same as lead "C maj"

leadJ :: String -> IO CadenceState Source #

Construct a CadenceState from a leadsheet chord symbol — the jazz counterpart to lead, for cueing genJ. Accepts the Bunks corpus grammar: root, quality, optional slash bass (Cm7, EbM7, G7b9, "Dm7/G"). Qualities occupy the jazz namespace (qualityIntervals), entirely separate from lead's triadic quality table, so triadic behaviour is untouched. A notated slash bass is honoured exactly: unioned into the pitch-class set and made the anchor, so "Dm7/G" cues from G with the full Dm7 sounding above it. An optional (N) token fixes the approach movement, otherwise it is randomized exactly like lead; either way the movement shapes bar 1's stored metadata only — a fresh walk departs from the functionality's hub node regardless. An unparseable symbol is reported and falls back to the corpus workhorse C m7.

Examples: start <- leadJ Cm7 -- C m7, random movement start <- leadJ "Dm7/G" -- G anchor, Dm7 above (a 9sus4 set) start <- leadJ "EbM7 (5)" -- Eb maj7, ascending 5th approach

parseLeadTokens :: String -> [LeadToken] Source #

Parse a lead string into a list of typed tokens. Each space-separated token is independently classified as root, quality, or movement.

data LeadToken Source #

Token type for parseLeadTokens

Instances

Instances details
Show LeadToken Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Arranger

Eq LeadToken Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Arranger

Instruments and orchestra

Internal (advanced use only)

Tuple-returning versions for manual diagnostics extraction.

generate Source #

Arguments

:: CadenceState

Starting state (root + quality)

-> Int

Number of chords

-> Text

Composer blend string

-> Double

Entropy (gamma shape)

-> HarmonicContext

R constraints

-> IO Progression 

Generate a harmonic progression from a starting state.

Arguments: * start: Initial CadenceState (defines starting root and quality) * len: Number of chords to generate * composerStr: Composer blend string ("bach:70 debussy:30") * entropy: Gamma shape parameter (higher = more unusual choices) * context: HarmonicContext filters (R constraints)

Algorithm: 1. Parse composer weights 2. For each step: query graph, apply R filter, apply E weights, gamma select 3. Apply voice leading optimization to the complete chain

Returns: Progression type (Phase B)

generateWith :: CadenceState -> Int -> Text -> Double -> HarmonicContext -> IO Progression Source #

Generate with custom configuration

Simplified algorithm: 1. Start with user-provided CadenceState 2. For each step: build candidate pool, gamma-select next cadence 3. Candidate pool = graph transitions (filtered) + consonanceFallback (unlimited — the pool is never truncated)

generate' :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO (Progression, GenerationDiagnostics) Source #

Generate a progression returning both result and diagnostics (internal).

This is the core internal function that generates progressions and collects standard-level diagnostics (per-step candidate pools, selections, rendered chords).

For most users, use the unified interface instead: * genSilent - for silent generation * genStandard - for standard diagnostics * genVerbose - for verbose diagnostics

generate'' :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO (Progression, GenerationDiagnostics) Source #

Generate a progression with maximum diagnostic traces (internal).

Like 'generate'' but populates full transform and advance traces for debugging. This function collects: * All standard diagnostics (per-step candidate pools, selections) * Full transform traces (DB intervals, transposition, normalization, zero-form) * Full advance traces (root motion PC arithmetic, enharmonic spelling)

This is SLOWER than 'generate'' due to extra tracing computation. Use only for debugging chord name discrepancies or voice leading issues.

printDiagnostics :: Int -> GenerationDiagnostics -> IO () Source #

Print diagnostics collected during generation.

Selects output level based on verbosity parameter:

0 - Silent
No output
1 - Standard
Per-step candidate pools, selections, rendered chords
2 - Verbose
Standard plus transform and advance traces

Useful for manually reprinting diagnostics after extraction from tuple, or batch processing multiple results at different verbosity levels.

data StepDiagnostic Source #

Diagnostic information for a single generation step

Constructors

StepDiagnostic 

Fields

data GenerationDiagnostics Source #

Complete diagnostics for a generation run

Constructors

GenerationDiagnostics 

Fields

data TransformTrace Source #

Transform trace captures intermediate values in fromCadenceState → toTriad pipeline. Used for maximum verbosity debugging (gen''). Includes raw DB data plus all transformation stages.

Constructors

TransformTrace 

Fields

data AdvanceTrace Source #

Advance trace captures intermediate values in root advancement. Used for maximum verbosity debugging (gen'').

Constructors

AdvanceTrace 

Fields