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

Harmonic.Evaluation.Database.Query

Description

This module implements the Evaluation (E) component of the Creative Systems Framework. It fetches transition probabilities from Neo4j and resolves composer-weighted scores for candidate cadences.

The database is treated as abstract/pitch-agnostic. Root notes and voicings are computed at runtime from user-defined starting conditions.

Synopsis

Composer Weight Parsing

type ComposerWeights = Map Text Double Source #

Map from composer name to weight (e.g., "bach" -> 0.7)

parseComposerWeights :: Text -> ComposerWeights Source #

Parse a composer selection string into normalized weights.

Composer names are matched case-insensitively against the corpus — Bach, "bach", BACH, "bAcH" all collapse to the same key. Names are lower-cased here at parse time; resolveWeights lowercases corpus edge keys at lookup time so the match is robust regardless of the case convention used during corpus ingestion.

Supported formats: "bach debussy" -> equal weights, normalized to sum 1.0 "Bach:30 Debussy:70" -> weighted, normalized; case-insensitive "bach:0.3, debussy:0.7" -> already normalized (or re-normalized if needed)

Examples: parseComposerWeights "bach debussy" == Map.fromList [("bach", 0.5), ("debussy", 0.5)] parseComposerWeights "Bach:30 DEBUSSY:70" == Map.fromList [("bach", 0.3), ("debussy", 0.7)]

normalizeWeights :: ComposerWeights -> ComposerWeights Source #

Normalize weights so they sum to 1.0

parseWeightsJson :: Text -> ComposerWeights Source #

Parse the weights JSON string from Neo4j. Format: '{Debussy:3.0,Stravinsky:2.0}'

Graph Queries

fetchTransitions :: Text -> DbActionT [(Cadence, ComposerWeights)] Source #

Fetch all outgoing transitions from a cadence node.

Returns: List of (Cadence, ComposerWeights) pairs for all [:NEXT] edges. The Cadence is reconstructed from Neo4j node properties (movement, chord).

Query: MATCH (c:Cadence {show: $show})-[r:NEXT]->(n:Cadence) RETURN n.movement, n.chord, r.weights

fetchTransitionsAggregate :: Text -> DbActionT [(Cadence, Double)] Source #

Fetch outgoing transitions with the pre-aggregated corpus score.

Fast path for the wildcard composer spec ("*", i.e. an empty blend): every edge stores r.confidence, written at ingestion as exactly sum (Map.elems weights) (Harmonic.Rules.Import.Graph), which is the score resolveWeights computes for an empty blend. Projecting it directly skips the r.weights JSON payload (all corpus composers per edge, ~1MB/ fetch on high-degree nodes) — measured ~30x faster and ~115x less allocation per generation step.

Result contract matches applyComposerBlend Map.empty $ fetchTransitions exactly: positive scores only (always true by the ingestion invariant — zero-total edges are never written), sorted highest first.

Query: MATCH (c:Cadence {show: $show})-[r:NEXT]->(n:Cadence) RETURN n.movement, n.chord, r.confidence

Jazz (Change) Graph Queries

data ChangeCandidate Source #

One candidate next node in the jazz graph: its show key (the walk state), plus the movement and anchor-relative zero-form set parsed back from node properties.

Constructors

ChangeCandidate 

Fields

fetchChangeTransitions :: Text -> DbActionT [(ChangeCandidate, ComposerWeights)] Source #

Outgoing jazz transitions with full per-composer weights — the blend path, mirroring fetchTransitions for the Change label.

fetchChangeAggregate :: Text -> DbActionT [(ChangeCandidate, Double)] Source #

Outgoing jazz transitions on the pre-aggregated corpus score — the wildcard fast path, mirroring fetchTransitionsAggregate. Sorted highest first.

fetchChangeStarts :: DbActionT [(ChangeCandidate, Double)] Source #

Every jazz start candidate with its outgoing corpus mass — the popularity-weighted pool the uncued genJ cue draws from (structure only: Change nodes are zero-form, the root comes from the caller's tonal context). Nodes with no outgoing edges are excluded — a walk could not leave them.

resolveChangeCue :: Text -> DbActionT (Maybe Text) Source #

Resolve a cue functionality to a jazz walk-start node. Exact movement+name keys are the caller's own lookup; this finds the best node for a bare functionality: prefer the pedal arrival, then the node with the most outgoing corpus mass.

fetchJazzComposers :: DbActionT (Set Text) Source #

Every composer key present in the jazz graph — the corpus membership test that splits a mixed seek spec into its jazz-blend and classical-steer halves. One scan of the (small) Change edge set, keys unioned client-side.

splitSeekByCorpus :: Set Text -> ComposerWeights -> (ComposerWeights, ComposerWeights) Source #

Split a parsed seek blend into its (jazz, classical-steer) halves against the jazz graph's key set. A token names a jazz composer when it matches a key exactly or as a substring ("monk" matches "theloniousmonk" and co-credits); its weight is split equally across all matching keys. Unmatched tokens are classical steer names. Both halves are renormalised. Pure — callers fetch the key set once (fetchJazzComposers) and reuse it.

Weight Resolution

resolveWeights :: ComposerWeights -> [(a, ComposerWeights)] -> [(a, Double)] Source #

Resolve transition weights using the active composer blend.

For each candidate (Cadence, ComposerWeights), compute a single score by multiplying each composer's edge weight by the user's blend weight and summing.

Example: candidate weights: {"bach": 5, "debussy": 3} user blend: {"bach": 0.7, "debussy": 0.3} score = 5 * 0.7 + 3 * 0.3 = 4.4

applyComposerBlend :: ComposerWeights -> [(a, ComposerWeights)] -> [(a, Double)] Source #

Apply composer blend to filter transitions, keeping only those with score > 0