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

Harmonic.Framework.Builder

Description

This module implements the main generation loop that connects:

  • R (Rules): HarmonicContext constraints via Filter module
  • E (Evaluation): Database-derived composer probabilities + dissonance
  • T (Traversal): gamma-distributed sampling over the graph walk

Academic Lineage

Data Science In The Creative Process (South, 2018): Wiggins' Creative Systems Framework <R,T,E> as the architectural blueprint. The Builder orchestrates the R→E→T pipeline where R constrains the search space, E scores candidates via database-derived probabilities, and T selects via gamma-distributed probabilistic traversal.

Unified Generation Interface

The module provides three public generation functions with identical type signatures:

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

All three functions: * Return IO Progression (NOT tuples) * Print diagnostics as side effects based on verbosity level * Enable seamless switching between verbosity levels without code changes

Verbosity Levels

0 - Silent
genSilent: No diagnostic output. Use when you only want the progression.
1 - Standard
genStandard: Prints per-step diagnostics including: * Prior and posterior cadence states * Candidate pool composition (graph candidates, fallback candidates) * Top candidates with scores * Selected candidate source (graph or fallback) * Rendered chord names
2 - Verbose
genVerbose: Prints everything from Standard plus: * TRANSFORM TRACE: Complete render pipeline (intervals, transposition, zero-form, naming) * ADVANCE TRACE: Root motion computation with pitch class arithmetic * Verification: DB stored name vs computed name

Legacy Diagnostic Functions

For backward compatibility, the module still exports: * generate', gen', genWith' - Returns (Progression, GenerationDiagnostics) tuple * generate'', gen'', genWith'' - Returns (Progression, GenerationDiagnostics) tuple with max diagnostics

Use these when you need to programmatically extract diagnostics rather than printing them.

Score Composition Details

Fallback candidates are scored using the formula:

  chordDiss     = Hindemith vertical dissonance (6-50 range)
  motionDiss    = Root motion dissonance (1-6 range, vector-based)
  gammaDraw     = Entropy-based random perturbation (~0-5, shape=1.01)

  badness = chordDiss × motionDiss × (gammaDraw + 1)
  score = 10000 - badness

The multiplicative formula spreads scores organically without artificial limits. Full 660-candidate pool (12 roots × C(11,2) pairs) ensures maximum variety.

The database is treated as abstract/pitch-agnostic. Root notes are computed at runtime based on movement intervals from a user-defined starting CadenceState.

Filter Notation (from original README)

Overtones/Pitch Set Filter

  • Fundamental pitches (derives overtones): "E A D G" (bass tuning)
  • Individual pitches with prime: "E'" "A'" A
  • Combined: "G E' A' A#'" (G overtones + E, A, A# pitches)
  • Wildcard: "*" (all pitches)

Tonality (Key) Filter

  • Key signature: "bb", "###", "4b", "0#"
  • Named key: C, G, F, Bb
  • Wildcard: "*" (no key filtering)

Root Notes Filter

  • Pitches: "E F# G"
  • Key signature: "1b", "#", "##" (D major)
  • Wildcard: "*" (all roots)
Synopsis

Modifier-Based Generation API

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

gen4 :: GenConfig Source #

gen4 family sugar: quad pre-applied to gen / gen' / gen''.

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

gen4' :: GenConfig Source #

gen4 with compact musical summary.

gen4'' :: GenConfig Source #

gen4 with verbose diagnostic traces.

quad :: GenConfig -> GenConfig Source #

Switch on the gen4 family: every generated bar carries a 4-note chord.

Each step first selects a triad exactly as plain gen (graph + fallback, R filters, gamma draw), then fuses in one more R-valid palette tone — ranked consonant-first by full-chord dissonance, drawn at the same entropy. The walk continues from the fused chord's most-consonant embedded triad, so the added tone can reinterpret the harmony and steer the next step, while every graph key stays corpus-shaped (generation stays online). A triad cue is fused once so output is uniformly 4-note; a 4-note lead' cue passes through untouched.

Composes with the usual modifier chain. Never applies to the strata family (genP) — strata progressions stay 3-5-7.

s <- seek "*" $ cue start $ entropy 0.4 $ quad gen'

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

FAMILY-AWARE: regeneration always produces uniform states of the family the source progression already is — families never mix.

  • 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.
  • uniform 4-note triad layer (gen4 source) — regen bars come out 4-note (_gcQuad set automatically).
  • 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''.

genP Paradigm (strata-first)

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 ….

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.

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. For legacy gen-family configs, all three layers duplicate the generated triad progression and pcProvenance is Nothing; the genP paradigm produces distinct strata/mode layers with Just provenance.

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 in [0, 1] — mapped affinely to the gamma sampler's shape (shape = 1 + entropy * 9). Higher values = more unusual choices.

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

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

Generation Configuration

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)

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
len:     4
seek:    "*" (all composers)
entropy: 0.2
tonal:   hContext (chromatic)

execGenConfig :: GenConfig -> IO Progression Source #

Execute a GenConfig, producing a progression.

Thin wrapper that calls execGenConfigWithDiag (pure compute) and then emits the appropriate diagnostics + header + grid via emitFinalised. Single-pass callers see byte-identical output to today.

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/internal)

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 :: GeneratorConfig -> 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)

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

String-friendly generateWith for TidalCycles live coding.

Positional Generation with Diagnostics

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

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

Generate with custom configuration, returning diagnostics tuple (internal).

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.

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

Generate with custom configuration and maximum diagnostics (internal).

Positional Generation with Print Output

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

Unified Positional Interface

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

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

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.

Diagnostics Types

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

Harmonic Context (R constraints)

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 :: Text -> Text -> Text -> 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

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

Context Modifiers

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.

Configuration

data GeneratorConfig Source #

Configuration for the progression generator.

gcQuad switches on the gen4 family: after each triad selection, the step fuses one R-valid palette tone into the chord (4-note output) and the walk continues from the fused chord's most-consonant embedded triad (see fuseState).

Historical note: the former gcPoolSize field was removed (2026-08-19) because no generation path ever read it — the candidate pool is deliberately unlimited (full 660-candidate fallback; see Core).

Constructors

GeneratorConfig 

Fields

  • gcQuad :: !Bool

    gen4: fuse a 4th tone into every generated bar (default False)

defaultConfig :: GeneratorConfig Source #

Default configuration.

Internal functions (exposed for testing)

matchesContext :: HarmonicContext -> CadenceState -> Cadence -> Bool Source #

Check if a cadence matches the harmonic context filters.

Filter logic (matching legacy behavior): 1. Compute effective overtones: key-filtered overtone palette 2. All chord pitches must be in effective overtones 3. Root must be in resolved roots (handles "key"/"tones" options)

parseComposersWithOrder :: Text -> [(Text, Double)] Source #

Parse composer string preserving input order Returns list of (name, normalized weight) tuples in order of appearance

makePortmanteau :: Text -> Maybe Text Source #

Generate portmanteau from composer string (preserving input order) Takes weighted portions from beginning/middle/end based on POSITION Returns Nothing for "*", empty input, or "none" (offline mode)

extractByPosition :: Int -> Int -> Text -> Double -> Text Source #

Extract characters from name based on position in list

takeFromBeginning :: Text -> Double -> Text Source #

Take a leading fragment of a name, sized by the composer's blend weight. Used for the first name in a blend. Always yields at least one character.

takeFromEnd :: Text -> Double -> Text Source #

Take a trailing fragment, sized by blend weight. Used for the last name in a blend, so the portmanteau ends on a real word ending.

takeFromMiddle :: Text -> Double -> Text Source #

Take a centred fragment, sized by blend weight. Used for names between the first and last in a blend. Ties favour earlier characters.

printHeader :: Text -> Double -> HarmonicContext -> IO () Source #

Print generation header based on composer selection Takes raw Text instead of parsed ComposerWeights, plus entropy