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

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.

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.

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'

genGrid :: GenConfig Source #

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

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

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

The genP paradigm (strata-first)

Three-layer generation (triad / strata / mode). The roman-numeral aliases pin the starting tristrata.

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

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)

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

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

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.

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.

Core music types

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.

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

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

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 Pipe Source #

Open a Bolt connection to the local Neo4j on port 7687, using the credentials in Harmonic.Config. Every online generation path needs one.

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 MIDI note range

arrange Source #

Arguments

:: (Double, Double)

Kinetics range

-> 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 (T | S | M)

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

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

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 or musical Bars (4/4). 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)

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

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, rh/rh' take bars (rehearsal marks, 4/4); unprimed = smooth transition, primed = snap. at is unchanged from before.

at 0 0 0 s seconds, smooth rh 8 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 16 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, rh/rh' take bars (rehearsal marks, 4/4); unprimed = smooth transition, primed = snap. at is unchanged from before.

at 0 0 0 s seconds, smooth rh 8 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 16 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, rh/rh' take bars (rehearsal marks, 4/4); unprimed = smooth transition, primed = snap. at is unchanged from before.

at 0 0 0 s seconds, smooth rh 8 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 16 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, rh/rh' take bars (rehearsal marks, 4/4); unprimed = smooth transition, primed = snap. at is unchanged from before.

at 0 0 0 s seconds, smooth rh 8 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 16 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.

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.

Arranger functions (voicing paradigms)

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

Rotate a progression by n bars (positive = left, negative = right)

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

Extract bars start to end (1-indexed, inclusive)

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

Insert a CadenceState at position (1-indexed), replacing the existing one

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

Switch two bars at positions m and n (1-indexed)

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

Clone bar m to position n (overwrites n with contents of m)

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

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 each chord n times

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 Source #

Arguments

:: Pattern Double

Dynamics pattern (> 0 = sub active)

-> IK

Performance context (kinetics + chord selection)

-> (Progression -> [[Int]])

Voice strategy (fund or bass)

-> (Time, String, String, String)

Kick placement pattern string

-> Pattern ValueMap 

Groove interface using patterned chord selection with kinetics gating.

CC64 sustain mechanism with chord selection from IK. Sub on/off patterns and kick pattern are bar-relative: "[1]/2" = one onset every 2 bars, "1*4" = 4 kicks per bar.

Chord selection uses innerJoin — we WANT new note-ons when the chord changes (unlike melodic instruments where sustain across boundaries is desirable).

The progression is read from kProg via innerJoin. Sub is gated at (0.1, 1) and kick at (0.2, 1) via ki.

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

Extract harmonic roots regardless of inversion. Always returns the fundamental root note from CadenceState.

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"

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)

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

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 gen (legacy) progressions the line is byte-identical to the previous behaviour.

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 4-note cues for gen4 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"

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

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 :: IO ProgressionContext -> IO () Source #

Live-coding helper: execute a genP-style 'IO ProgressionContext' and print a pretty tristrata report alongside the standard Show output. Useful at the REPL for sanity-checking a strata walk.

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

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

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