| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Harmonic.Framework.Builder
Contents
- Modifier-Based Generation API
- genE Paradigm (polytonal)
- genJ Paradigm (jazz Change graph)
- genP Paradigm (strata-first)
- Generation Modifiers
- Generation Configuration
- Positional Generation (legacy/internal)
- Positional Generation with Diagnostics
- Positional Generation with Print Output
- Unified Positional Interface
- Diagnostics Types
- Harmonic Context (R constraints)
- Context Modifiers
- Internal functions (exposed for testing)
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' - Returns (Progression, GenerationDiagnostics) tuple
* generate'' - 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
Root Notes Filter
- Pitches:
"E F# G" - Key signature:
"1b","#","##"(D major) - Wildcard:
"*"(all roots)
Synopsis
- gen :: GenConfig
- gen' :: GenConfig
- gen'' :: GenConfig
- genGrid :: GenConfig
- genGrid' :: GenConfig
- genGrid'' :: GenConfig
- genFrom :: ProgressionContext -> Int -> Int -> GenConfig
- genFrom' :: ProgressionContext -> Int -> Int -> GenConfig
- genFrom'' :: ProgressionContext -> Int -> Int -> GenConfig
- genE :: GenConfig
- genE' :: GenConfig
- genE'' :: GenConfig
- genJ :: GenConfig
- genJ' :: GenConfig
- genJ'' :: GenConfig
- steer :: Double -> GenConfig -> GenConfig
- genP :: StrataLabel -> GenConfig
- genP' :: StrataLabel -> GenConfig
- genP'' :: StrataLabel -> GenConfig
- genI :: GenConfig
- genII :: GenConfig
- genIII :: GenConfig
- genIV :: GenConfig
- genV :: GenConfig
- genVI :: GenConfig
- genVII :: GenConfig
- genVIII :: GenConfig
- genIX :: GenConfig
- genX :: GenConfig
- genXI :: GenConfig
- genI' :: GenConfig
- genII' :: GenConfig
- genIII' :: GenConfig
- genIV' :: GenConfig
- genV' :: GenConfig
- genVI' :: GenConfig
- genVII' :: GenConfig
- genVIII' :: GenConfig
- genIX' :: GenConfig
- genX' :: GenConfig
- genXI' :: GenConfig
- genI'' :: GenConfig
- genII'' :: GenConfig
- genIII'' :: GenConfig
- genIV'' :: GenConfig
- genV'' :: GenConfig
- genVI'' :: GenConfig
- genVII'' :: GenConfig
- genVIII'' :: GenConfig
- genIX'' :: GenConfig
- genX'' :: GenConfig
- genXI'' :: GenConfig
- cue :: CadenceState -> GenConfig -> GenConfig
- len :: Int -> GenConfig -> GenConfig
- seek :: String -> GenConfig -> IO ProgressionContext
- entropy :: Double -> GenConfig -> GenConfig
- tonal :: HarmonicContext -> GenConfig -> GenConfig
- relStrata :: String -> GenConfig -> GenConfig
- absStrata :: String -> GenConfig -> GenConfig
- sameBoost :: Double -> GenConfig -> GenConfig
- flipBoost :: Double -> GenConfig -> GenConfig
- triBoost :: Double -> GenConfig -> GenConfig
- attempt :: Int -> Int -> GenConfig -> GenConfig
- viability :: Double -> GenConfig -> GenConfig
- data GenConfig = GenConfig {
- _gcCue :: IO CadenceState
- _gcCueExplicit :: Bool
- _gcLen :: Int
- _gcSeek :: String
- _gcEntropy :: Double
- _gcTonal :: HarmonicContext
- _gcVerbosity :: Verbosity
- _gcMode :: GenMode
- _gcLenOverride :: Maybe Int
- _gcRelStrata :: Maybe [Int]
- _gcAbsStrata :: Maybe [StrataLabel]
- _gcBoostSame :: Double
- _gcBoostFlip :: Double
- _gcBoostTri :: Double
- _gcSteer :: Double
- _gcMaxAttempts :: Int
- _gcViableTarget :: Int
- _gcViabilityFloor :: Double
- data GenMode
- data Verbosity
- defaultGenConfig :: GenConfig
- execGenConfig :: GenConfig -> IO Progression
- execGenConfigPC :: GenConfig -> IO ProgressionContext
- generate :: CadenceState -> Int -> Text -> Double -> HarmonicContext -> IO Progression
- generateWith :: CadenceState -> Int -> Text -> Double -> HarmonicContext -> IO Progression
- generate' :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO (Progression, GenerationDiagnostics)
- generate'' :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO (Progression, GenerationDiagnostics)
- genPrint :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression
- genPrint' :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression
- genPrint'' :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression
- genSilent :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression
- genStandard :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression
- genVerbose :: CadenceState -> Int -> String -> Double -> HarmonicContext -> IO Progression
- printDiagnostics :: Int -> GenerationDiagnostics -> IO ()
- data StepDiagnostic = StepDiagnostic {
- sdStepNumber :: Int
- sdPriorCadence :: String
- sdPriorRoot :: String
- sdPriorRootPC :: Int
- sdSelectedDbIntervals :: String
- sdSelectedDbMovement :: String
- sdSelectedDbFunctionality :: String
- sdGraphCount :: Int
- sdGraphTop6 :: [(String, Double)]
- sdFallbackCount :: Int
- sdFallbackTop6 :: [(String, Double, Double, Double, Double)]
- sdPoolSize :: Int
- sdEntropyUsed :: Double
- sdGammaIndex :: Int
- sdSelectedFrom :: String
- sdPosteriorRoot :: String
- sdPosteriorRootPC :: Int
- sdRenderedChord :: Maybe String
- sdTransformTrace :: Maybe TransformTrace
- sdAdvanceTrace :: Maybe AdvanceTrace
- sdTristrataIdx :: Maybe Int
- sdTristrata :: Maybe Tristrata
- sdStrataLabel :: Maybe StrataLabel
- sdMode :: Maybe Mode
- sdStrataChroma :: Maybe [PitchClass]
- sdModeChroma :: Maybe [PitchClass]
- sdSoftBoost :: Maybe Double
- sdHarmonicRootPC :: Maybe Int
- sdParentKey :: Maybe (PitchClass, ScaleFamily)
- sdModeResult :: Maybe ModeResult
- sdBarSpelling :: Maybe EnharmonicSpelling
- sdPoly :: Maybe PolyDiag
- data GenerationDiagnostics = GenerationDiagnostics {}
- data TransformTrace = TransformTrace {
- ttRawDbIntervals :: String
- ttRawDbMovement :: String
- ttRawDbFunctionality :: String
- ttRootPC :: Int
- ttRootNoteName :: String
- ttTones :: [Int]
- ttTransposedPitches :: [Int]
- ttNormalizedPs :: [Int]
- ttZeroForm :: [Int]
- ttDetectedRoot :: String
- ttFunctionality :: String
- ttFinalChord :: String
- ttStoredFunc :: String
- data AdvanceTrace = AdvanceTrace {}
- data HarmonicContext = HarmonicContext {
- _hcOvertones :: Text
- _hcKey :: Text
- _hcRoots :: Text
- _hcDrift :: Drift
- _hcInversionSpacing :: Int
- _hcPedal :: Text
- _hcTristrata :: Text
- harmonicContext :: String -> String -> String -> HarmonicContext
- hContext :: HarmonicContext
- data Drift
- hcOvertones :: String -> HarmonicContext -> HarmonicContext
- hcKey :: String -> HarmonicContext -> HarmonicContext
- hcRoots :: String -> HarmonicContext -> HarmonicContext
- dissonant :: HarmonicContext -> HarmonicContext
- consonant :: HarmonicContext -> HarmonicContext
- invSkip :: Int -> HarmonicContext -> HarmonicContext
- hcPedal :: String -> HarmonicContext -> HarmonicContext
- hcTristrata :: String -> HarmonicContext -> HarmonicContext
- parseComposersWithOrder :: Text -> [(Text, Double)]
- makePortmanteau :: Text -> Maybe Text
- extractByPosition :: Int -> Int -> Text -> Double -> Text
- takeFromBeginning :: Text -> Double -> Text
- takeFromEnd :: Text -> Double -> Text
- takeFromMiddle :: Text -> Double -> Text
- printHeader :: Text -> Double -> HarmonicContext -> IO ()
Modifier-Based Generation API
Generation config with header + grid output (default).
s <- seek "*" $ gen s <- seek "*" $ cue start $ tonal ctx $ len 4 $ entropy 0.3 $ gen
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).
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 thee → e+1seam to keep the spliced bar sequence walk-graph valid underallowedNext.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''.
genE Paradigm (polytonal)
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'
genJ Paradigm (jazz Change graph)
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).
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'
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 ….
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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Standard-verbosity Roman numeral aliases: per-step musical context plus
the grid. See genI.
Standard-verbosity Roman numeral aliases: per-step musical context plus
the grid. See genI.
Standard-verbosity Roman numeral aliases: per-step musical context plus
the grid. See genI.
Standard-verbosity Roman numeral aliases: per-step musical context plus
the grid. See genI.
Standard-verbosity Roman numeral aliases: per-step musical context plus
the grid. See genI.
Standard-verbosity Roman numeral aliases: per-step musical context plus
the grid. See genI.
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.
Standard-verbosity Roman numeral aliases: per-step musical context plus
the grid. See genI.
Standard-verbosity Roman numeral aliases: per-step musical context plus
the grid. See genI.
Standard-verbosity Roman numeral aliases: per-step musical context plus
the grid. See genI.
Verbose-verbosity Roman numeral aliases: full traces, the grid, and the
multi-attempt scoreboard when paired with attempt. See genI.
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.
Verbose-verbosity Roman numeral aliases: full traces, the grid, and the
multi-attempt scoreboard when paired with attempt. See genI.
Verbose-verbosity Roman numeral aliases: full traces, the grid, and the
multi-attempt scoreboard when paired with attempt. See genI.
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.
Verbose-verbosity Roman numeral aliases: full traces, the grid, and the
multi-attempt scoreboard when paired with attempt. See genI.
Verbose-verbosity Roman numeral aliases: full traces, the grid, and the
multi-attempt scoreboard when paired with attempt. See genI.
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).
Generation Configuration
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
| |
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 |
|
| JazzMode |
|
| FromProgJ ProgressionContext !Int !Int | Regenerate range in a jazz-family context |
| PolyMode |
|
| FromProgPoly ProgressionContext !Int !Int | Regenerate range in a polytonal context (foundation + partner chains, seam-aware) |
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/internal)
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)
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
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.
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 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).
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
| |
Instances
| Show StepDiagnostic Source # | |
Defined in Harmonic.Framework.Builder.Types Methods showsPrec :: Int -> StepDiagnostic -> ShowS # show :: StepDiagnostic -> String # showList :: [StepDiagnostic] -> ShowS # | |
| Eq StepDiagnostic Source # | |
Defined in Harmonic.Framework.Builder.Types Methods (==) :: StepDiagnostic -> StepDiagnostic -> Bool # (/=) :: StepDiagnostic -> StepDiagnostic -> Bool # | |
data GenerationDiagnostics Source #
Complete diagnostics for a generation run
Constructors
| GenerationDiagnostics | |
Fields
| |
Instances
| Show GenerationDiagnostics Source # | |
Defined in Harmonic.Framework.Builder.Types Methods showsPrec :: Int -> GenerationDiagnostics -> ShowS # show :: GenerationDiagnostics -> String # showList :: [GenerationDiagnostics] -> ShowS # | |
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
| |
Instances
| Show TransformTrace Source # | |
Defined in Harmonic.Framework.Builder.Types Methods showsPrec :: Int -> TransformTrace -> ShowS # show :: TransformTrace -> String # showList :: [TransformTrace] -> ShowS # | |
| Eq TransformTrace Source # | |
Defined in Harmonic.Framework.Builder.Types Methods (==) :: TransformTrace -> TransformTrace -> Bool # (/=) :: TransformTrace -> TransformTrace -> Bool # | |
data AdvanceTrace Source #
Advance trace captures intermediate values in root advancement. Used for maximum verbosity debugging (gen'').
Constructors
| AdvanceTrace | |
Fields
| |
Instances
| Show AdvanceTrace Source # | |
Defined in Harmonic.Framework.Builder.Types Methods showsPrec :: Int -> AdvanceTrace -> ShowS # show :: AdvanceTrace -> String # showList :: [AdvanceTrace] -> ShowS # | |
| Eq AdvanceTrace Source # | |
Defined in Harmonic.Framework.Builder.Types | |
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
| |
Instances
| Show HarmonicContext Source # | |
Defined in Harmonic.Framework.Builder.Types Methods showsPrec :: Int -> HarmonicContext -> ShowS # show :: HarmonicContext -> String # showList :: [HarmonicContext] -> ShowS # | |
| Eq HarmonicContext Source # | |
Defined in Harmonic.Framework.Builder.Types Methods (==) :: HarmonicContext -> HarmonicContext -> Bool # (/=) :: HarmonicContext -> HarmonicContext -> Bool # | |
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
Context Modifiers
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).
Instances
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.
Internal functions (exposed for testing)
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