| Safe Haskell | None |
|---|---|
| Language | GHC2021 |
Harmonic.Rules.Import.Transform
Description
Transforms raw CSV corpus records into Cadence structures suitable for
Neo4j graph storage: fundamental extraction, triad generation via
Harmonic.Rules.Constraints.Overtone, and dissonance scoring.
Node-key naming contract ==
The LIVE database's node keys (show = movement + functionality) carry
functionality names produced under LEGACY naming rules — e.g. zero form
[0,3,8] is stored as maj_1stInv, [0,2,7] as sus4_1stInv. The
modern namers (toTriad via toCadence) deliberately diverge from
those rules (min#5, sus2 for the same forms). BOTH sides of the
pipeline therefore route through corpusFunctionality (the 55-form
corpusNameTable transcribed from the live database, 2026-08-19):
the read side when building fetch keys, and this module when stamping
the functionality that becomes a node's show key. Re-ingestion under
this contract reproduces the live keyspace exactly (verified: the node
set is the complete 55 zero-forms x 12 movements grid, and show is a
pure function of the zero form).
After any re-ingestion, verify with an online gen' run: graph counts
([nG/...]) must stay nonzero across steps that select inversion forms.
Synopsis
- buildTransitionCounts :: [ChordSlice] -> TransitionCounts
- buildTransitionCountsPerPiece :: [[ChordSlice]] -> TransitionCounts
- sliceInterpretations :: ChordSlice -> [(Chord, Double)]
Transition counting
buildTransitionCounts :: [ChordSlice] -> TransitionCounts Source #
Fold one piece's slice sequence into Markov transition counts over every CONSISTENT interpretation path.
A cadence is a movement onto a target chord, so an edge is built from a
slice TRIPLE (s1, s2, s3): the cadence a -> b followed by the cadence
b -> c, where the middle interpretation b is shared by both sides.
Each such edge accumulates weight w_a * w_b * w_c from the (per-slice
normalised) interpretation weights, so the total mass contributed by
each slice triple is exactly 1.
Sharing the middle reading is what keeps the expansion honest: every reasonable reading of an ambiguous vertical informs the model (recovering movement that triad reduction trims away), while two alternative readings of the SAME moment can never be counted as a transition between moments. Genuine corpus pedals (roughly a quarter of YCACL slices repeat their predecessor) produce self-edges via the same triple rule as every other transition.
Pieces with fewer than three slices contribute no edges (no complete triple exists).
buildTransitionCountsPerPiece :: [[ChordSlice]] -> TransitionCounts Source #
Sum buildTransitionCounts over a composer's pieces. Each piece is
counted independently, so no transition can be invented across the
boundary between the last slices of one piece and the first of the
next (the flattened pipeline leaked exactly such edges).
Interpretation expansion
sliceInterpretations :: ChordSlice -> [(Chord, Double)] Source #
Weighted triad interpretations of one slice, most consonant first.
The top three candidates (by Hindemith dissonance, Harmonic.Evaluation.Scoring.Dissonance)
carry a [3,2,1] preference profile, NORMALISED so every
slice's interpretation weights sum to 1 — an ambiguous 7-PC vertical
contributes exactly the same total probability mass as an unambiguous
triad, instead of shouting several times louder purely because it was
ambiguous. Ties keep possibleTriads
enumeration order (sortOn is stable).
Falls back to a single flat triad at weight 1 if ranking yields no candidates (e.g. the exporter filtered the slice below 3 unique PCs).