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

Harmonic.Traversal.WalkingBass

Description

Produces a walking-bass line for a cyclic Progression as a pure function of (progression, voiceFn) via three sequential passes:

  1. Pass 1 — beat 1s. Every bar's beat 1 is a singleton PC derived from the user-supplied VoiceFunction (fund or root). Octave placement is greedy left-to-right by smoothness to the previous beat 1, with a soft direction-persistence bias, a half-weight loop-closure pull on the final bar, and a root-fifth alternation option inside runs of consecutive identical chords. Bar 0 anchors at the register centre.
  2. Pass 2 — beat 3s. Per bar, choose a chord tone minimising smoothness to b1_i plus smoothness to b1_{i+1} plus consonance-to-fund cost (via beat3ConsTable). Unison repeats carry a dedicated penalty so a non-repeat chord tone wins when nearby. Symmetric chords (dim / aug / dim7 / whole-tone) bypass the consonance term since no chord tone is privileged in a rotation-invariant shape.
  3. Pass 3 — beats 2 and 4. Per beat, choose from a (cyclic) local-scale pool. Connector heuristics favour chord tones adjacent to the next beat 1, penalise copying the next beat 1 outright on beat 4, reward chromatic approaches to the next beat 1 regardless of scale/chord membership (so root-as-leading-tone can win), resolve static (b1==b3) bars via the chord's P5 or — on symmetric chords — any non-root chord tone, and reward root-on-b4 (full weight) or P5-on-b4 (half weight) when that tone sits 1–2 semitones from the next b1. If b3 already used the root / P5 at tone distance, the approach bonus shifts to the chromatic in-between tone so the line avoids a b3→b4 unison. Stranded connectors (neither flank within a whole step) pay a sandwich penalty; quality-defining chord tones are reserved for strong beats (only root/P5 earn the beat-4 chord-tone bonus).

The entropy parameter that used to be user-facing is now derived from the progression's root-motion angularity and chord-internal dissonance. Calm diatonic progressions land near 0 (more settled repeats allowed); angular tritone-heavy progressions land near 1 (fewer repeats, more chromatic motion). Progression-level consonance (progConsonance) independently scales strong-beat strictness and connector tension licence. Pure-function guarantees are preserved: same progression and voiceFn always produce the same line.

Synopsis

Main entry

walkLine :: VoiceFunction -> Progression -> [[Int]] Source #

Generate a walking-bass line. Entropy is derived from the progression's harmonic character; the caller supplies only the progression and a voice function (fund or root) defining each bar's beat 1.

walkLineP :: VoiceFunction -> Progression -> [ChromaSources] -> [[Int]] Source #

Octatripentatonic-aware walking-bass line. Pass 1 (beat 1s from voiceFn) and Pass 2 (beat 3s) are unchanged from walkLine; Pass 3 (beats 2 & 4) swaps the chromatic-±1 candidate path for tier-scored strata / overlap / mode candidates supplied per bar via ChromaSources. Caller is responsible for matching length chromas to progLength prog (mismatch falls back to the legacy walkLine path for safety).

walkLineDyn :: Maybe [Double] -> VoiceFunction -> Progression -> [[Int]] Source #

walkLine with an optional per-bar dynamic vector coupling the beat-1 register arc to the piece's dynamics (see kappaDynArc). Nothing is byte-identical to walkLine.

walkLinePDyn :: Maybe [Double] -> VoiceFunction -> Progression -> [ChromaSources] -> [[Int]] Source #

walkLineP with the optional dynamic vector of walkLineDyn.

data ChromaSources Source #

Per-bar chroma sources for the walkLineP Pass-3 connector pool. csStrata is the bar's full 5-PC strata chroma; csMode is the full 7-PC mode chroma. Both are supplied by the caller (typically derived from strataLayer / modeLayer of a genP-origin ProgressionContext).

Constructors

ChromaSources 

Fields

Derived entropy (exported for tests / diagnostics)

progressionEntropy :: Progression -> Double Source #

Entropy derived from the progression's harmonic character. Calm diatonic progressions land near 0; angular / tritone-heavy progressions approach 1. Deterministic: same progression always yields the same value.

progConsonance :: Progression -> Double Source #

Mean per-bar consonance of the progression in [0, 1], 1 = consonant. dissonanceScore grows with chord cardinality (major/minor triads score 6 while the most consonant tetrads score 19), so each bar is normalised against anchors for its own chord size before averaging — otherwise every tetrad progression would read as maximally dissonant. Consumed by the walk to scale strong-beat strictness and connector tension licence; orthogonal to progressionEntropy, which is dominated by root motion.

inferKeyCentre :: Progression -> [PitchClass] Source #

Per-bar regional key centre, inferred from chord qualities over a local window.

BOUNDARY: this is a PURE, DERIVED quantity computed over a finished progression, consumed only inside the walk's connector selection. The generation system deliberately operates without key awareness — each state is abstract and deterministic, upholding the Markov property — so this function must never feed back into generation, become part of any state, or appear in a generation-path signature.

Heuristics (weights in votes): a dominant-quality chord is V of its key; a major-quality chord is I (strong) or IV (weak), plain major triads also V (weak); a minor-quality chord is ii (strong), vi, or iii (weak); half-diminished is vii. Relative major/minor are treated as one pool and reported as the major-pool pitch class. Each bar takes the key with the highest vote total over the surrounding window (cyclic, +/- 2 bars); ties resolve to the lowest pitch class.

Utilities (exported for tests)

hashProgEntropy :: Progression -> Double -> Int Source #

Deterministic mixing of progression shape and entropy into a seed.

closestLowMidi :: Int -> Int Source #

Lowest MIDI in [lowestMidi, highestMidi] whose pitch class equals pc. Always in range: the register spans more than an octave, so the lowest PC-matching value sits at most 11 semitones above lowestMidi.

closestMidMidi :: Int -> Int Source #

In-register instance of a pitch class nearest registerCenter (lower on tie). Anchoring bar 0 here gives the greedy beat-1 chain headroom in both directions and evens out the single-instance asymmetry of the PCs that occur only once in the register.

lowestMidi :: Int Source #

Walking-bass register and metre constants. lowestMidi 28 and highestMidi 48 bound the line to a double-bass register; beatsPerBar is 4 (the walking idiom is quarter-note based); registerCenter 38 is the pitch the line is drawn back toward when free to choose.

highestMidi :: Int Source #

Walking-bass register and metre constants. lowestMidi 28 and highestMidi 48 bound the line to a double-bass register; beatsPerBar is 4 (the walking idiom is quarter-note based); registerCenter 38 is the pitch the line is drawn back toward when free to choose.

beatsPerBar :: Int Source #

Walking-bass register and metre constants. lowestMidi 28 and highestMidi 48 bound the line to a double-bass register; beatsPerBar is 4 (the walking idiom is quarter-note based); registerCenter 38 is the pitch the line is drawn back toward when free to choose.

isSymmetricChord :: Set Int -> Bool Source #

True iff the chord contains no perfect-fourth/fifth (5 or 7 semitones) between any pair of tones. Covers diminished triads ([0,3,6]), augmented triads ([0,4,8]), diminished sevenths ([0,3,6,9]), and whole-tone hexachords — synthetic shapes with no privileged fifth, where every chord tone is equally anchor-worthy.