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

Harmonic.Interface.Tidal.Arranger

Description

Shorthand for manipulating progressions in a TidalCycles performance context. All functions are designed for live-coding ergonomics: short names, intuitive parameter order, and no IO.

Two families, both wrapping the more verbose Harmonic.Rules.Types.Progression functions:

Rearranging
rotate, excerpt, insert, switch, clone, extract, transposeP, reverse, fuse, fuse2, interleave, expandP, progOverlap, progOverlapF, progOverlapB.
Voicing
five strategies that turn a progression into concrete pitches — grid, flow, lite, literal and root. grid and flow solve a cyclic DP for smooth voice leading; the others are literal or bass-only.

Rearranging composes on the progression before it reaches a voicing:

s  <- seek "*" $ len 8 $ entropy 0.4 $ gen
s' = fuse (excerpt 0 4 s) (rotate 2 s)

The voicing is then chosen per instrument at the point of play, so two lines can read the same progression differently:

, cello      T (0,1) k vl flow Tenor8vb
, contrabass T (0,1) k vl grid Bass8vb
Synopsis

Position/Range Operations

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

Transformation Operations

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

Overlap Operations (Progression-level)

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

Voicing Extractors (Voicing paradigms)

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.

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

STRATA-MODE-FLOW paradigm: each bar is the bar's chroma in sorted-ascending compressed form rooted on its harmonic root, with the whole voicing octave-shifted to minimise voice movement against the bar 0 anchor. Functions as a "key signature": pattern index i in any bar plays the i-th scale degree of that bar's strata / mode, so pattern increments of 1 always ascend by one set member and decrements descend by one.

Bar 0: initialCompact + normalizeByFirstRoot anchors the harmonic root in the standard window ([-12, -1] note range). Span ≤ 12 semitones from root upward.

Bar n+1: initialCompact rooted on bar n+1's harmonic root produces a "natural" compressed-ascending voicing; the whole voicing is then shifted by the octave (k·12 for k ∈ [-3..3]) that minimises total |placed_MIDI - anchor_MIDI| across voices, where anchor is bar 0's voicing. The root is allowed to migrate octaves freely if that makes the line closer to the anchor. Voicing remains sorted ascending after the shift (uniform shift preserves order), so "ascend by 1 with idx+1" holds.

Anchoring to bar 0 (rather than the previous bar) guarantees: * No drift over long walks — every bar stays within ~6 semitones of the anchor. * Cyclic return to anchor at the pattern wrap (bar N-1 → bar 0). * When chroma cycles back to bar 0's chroma (e.g. tristrata II-VI-X repeating), the bar lands on bar 0's exact MIDI (shift = 0).

O(n × k) per bar where n = chroma cardinality and k = number of octave candidates (~7). Sub-microsecond per bar; eager forcing in arrange still hoists the work to REPL evaluation time.

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)

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