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

Harmonic.Interface.Tidal.Form

Description

Encodes macro-level compositional arc as programmable structure, realised as TidalCycles patterns, looping endlessly.

A form is a list of nodes. Each node fixes a point in time and the two signals that drive everything downstream: kinetics (how active the music is) and dynamics (how loud). Instruments gate themselves on kinetics, so the arc is written once and every line follows it.

Nodes come in two time bases — at takes wall-clock seconds, rh takes bars (rehearsal marks). Primed variants (at', rh') snap rather than interpolate, for a hard cut into a new section:

form =           -- time    k     d
  [ rh    1      0.0   0.0   s
  , rh   17      0.2   0.3   s    -- elements layer in
  , rh'  29      0.35  0.55  s    -- riser: sparse but loud, then snap
  , rh   33      1.0   1.0   s    -- drop
  ]

The form is compiled into an IK — the performance context every instrument reads — by pairing it with a tempo and a chord-selection pattern:

k = iK tempo form (warp "[1 2 3 4]/4")

The simplest useful form is a single constant node, which holds one progression at full kinetics forever:

form = [ at 0 1.0 1.0 s ]
Synopsis

Types

data FormNode Source #

A node in a form definition: a point in time with kinetics level, dynamic level, active progression, and the transition style of the section starting here.

Constructors

FormNode 

Fields

Instances

Instances details
Show FormNode Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

Eq FormNode Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

data FormTime Source #

A node's position in time — wall-clock Secs elapsed (0 = the start) or musical Bars (4/4), numbered from 1 as the 12 Step counts them (bar 1 = the start). Resolved to Tidal cycles at realization (see formK). Mix freely in one form.

Constructors

Secs Double 
Bars Double 

Instances

Instances details
Show FormTime Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

Eq FormTime Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

data Transition Source #

How a section moves to the next node: Smooth (ramped) or Snap (hold this node's value, then jump on the next node's exact time).

Constructors

Smooth 
Snap 

Instances

Instances details
Show Transition Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

Eq Transition Source # 
Instance details

Defined in Harmonic.Interface.Tidal.Form

data Kinetics Source #

Realized form: continuous and discrete signals for live performance.

Constructors

Kinetics 

Fields

  • kSignal :: Pattern Double

    Kinetics level 0-1 (continuous interpolated)

  • kDynamic :: Pattern Double

    Dynamic envelope 0-1 (continuous interpolated)

  • kProg :: Pattern ProgressionContext

    Active 3-layer progression (step function)

  • kProgs :: [ProgressionContext]

    The distinct progressions kProg can emit — finite and known at construction (form nodes, or the single lK progression). Voicing caches derive from THIS list, exactly and horizon-free, instead of sampling kProg over an arbitrary time window.

  • kLoopSecs :: Double

    Form total duration in seconds; 0 = atemporal (single-node iK or lK). Consumers like the 4-char-display helper read this to drive a wall-clock counter that wraps every kLoopSecs.

  • kCps :: Double

    Cycles per second at form construction (= bpm/60). Used by the display broadcaster to convert cycle time → seconds. Stays coherent with Tidal's actual cps because both are derived from the same bpm on every launcher re-evaluation.

  • kDegrees :: Int -> Bool

    Launcher-wide degree filter, applied by arrange on top of each line's own range (isolate, isolate'); const True = all.

type IK = (Kinetics, Pattern Int) Source #

Performance context: Kinetics bundled with chord selection pattern. Reduces parameter threading — r and k are always passed together.

Construction

at :: Double -> Double -> Double -> ProgressionContext -> FormNode Source #

Form node builders. Time unit and transition are orthogonal: at/at' take wall-clock seconds elapsed, rh/rh' take musical bar numbers (rehearsal marks, 4/4, bar 1 = the start of the form); unprimed = smooth transition, primed = snap.

at 0 0 0 s seconds, smooth rh 10 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 18 0.9 0.9 s bars, snap

at' :: Double -> Double -> Double -> ProgressionContext -> FormNode Source #

Form node builders. Time unit and transition are orthogonal: at/at' take wall-clock seconds elapsed, rh/rh' take musical bar numbers (rehearsal marks, 4/4, bar 1 = the start of the form); unprimed = smooth transition, primed = snap.

at 0 0 0 s seconds, smooth rh 10 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 18 0.9 0.9 s bars, snap

rh :: Double -> Double -> Double -> ProgressionContext -> FormNode Source #

Form node builders. Time unit and transition are orthogonal: at/at' take wall-clock seconds elapsed, rh/rh' take musical bar numbers (rehearsal marks, 4/4, bar 1 = the start of the form); unprimed = smooth transition, primed = snap.

at 0 0 0 s seconds, smooth rh 10 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 18 0.9 0.9 s bars, snap

rh' :: Double -> Double -> Double -> ProgressionContext -> FormNode Source #

Form node builders. Time unit and transition are orthogonal: at/at' take wall-clock seconds elapsed, rh/rh' take musical bar numbers (rehearsal marks, 4/4, bar 1 = the start of the form); unprimed = smooth transition, primed = snap.

at 0 0 0 s seconds, smooth rh 10 0.5 0.5 s bars, smooth at' 60 1 1 s seconds, snap rh' 18 0.9 0.9 s bars, snap

iK :: Double -> [FormNode] -> Pattern Int -> IK Source #

Construct performance context from BPM, form nodes, and chord selection.

k = iK tempo [at 0 0 0 s, at 30 1 1 s] (warp "[1 2 3 4]/8")

lK Source #

Arguments

:: Pattern Double

Kinetics signal (0-1, live)

-> Pattern Double

Dynamics signal (0-1, live)

-> ProgressionContext

Active 3-layer progression

-> Pattern Int

Chord-selection pattern

-> IK 

Live kinetics: build IK from reactive kinetics/dynamics signals. Bypasses form interpolation — use when the envelope is driven by live input (e.g. MIDI CC) rather than a static keyframed form.

k = lK exP exP s r -- pedal drives both kinetics and dynamics

Realization

formK :: Double -> [FormNode] -> Kinetics Source #

Realize a form definition into Kinetics signals at a given BPM. Single-node forms produce constant signals (global state). Multi-node forms produce per-segment signals — smooth (ramp) or snap (step) per each node's fnTrans — and a step-function progression, looping at the form's total duration. Time is resolved from each node's FormTime.

Contract details:

  • The LAST node is the loop terminator, not a playable section: segments run between consecutive node pairs, and the final node's time sets kLoopSecs. Its kinetics/dynamic values only shape the ramp INTO it.
  • Nodes are sorted by resolved time before use, so a moved rehearsal mark cannot produce negative-width segments. Nodes sharing a time yield a zero-width segment (the earlier one is inaudible).
  • formK bpm [] yields silence on every signal — safe to evaluate mid-edit; nothing sounds until a node exists.
  • Smooth ramps render at 16 steps per SEGMENT regardless of length (see formSignal) — a long segment crosses kinetics thresholds on a correspondingly coarse grid.

Primitives

beatsPerBar :: Double Source #

Beats per bar for Bars resolution. 4/4, matching BootTidal's bar helper (1 bar = 4 cycles, since cps = bpm/60 makes a cycle one beat).

ki :: (Double, Double) -> IK -> Pattern a -> Pattern a Source #

Range gate: mask a pattern by kinetics signal level. Events pass only when kSignal is within the (lo, hi) range.

slate :: (Double, Double) -> IK -> [Pattern a] -> Pattern a Source #

Gated stack: stack patterns and gate by kinetics range.

mark :: Double -> IK -> [Pattern a] -> Pattern a Source #

markAt in bars (see the doctrine there).

mark' :: Double -> IK -> [Pattern a] -> Pattern a Source #

markAt in seconds.

markbar :: Double -> IK -> [Pattern a] -> Pattern a Source #

markAt in bars (see the doctrine there).

marksec :: Double -> IK -> [Pattern a] -> Pattern a Source #

markAt in seconds.

markAt :: FormTime -> IK -> [Pattern a] -> Pattern a Source #

Form-relative cue: place patterns ONCE per form loop at a form time. The list is stacked (the shape of slate); each pattern's first cycle is squeezed into the one-beat window starting at that time, so a plain one-note pattern fires exactly on the beat and "1 1" gives two hits within it. Loops in lockstep with kSignal — the same slow loopCycles wrap as formSignal — so a cue and the kinetics can never drift apart. NOT kinetics-gated: a placement is absolute (the launcher's |* vel d still applies).

Time-unit doctrine: unprimed = bars, primed = seconds. mark takes musical bar numbers as written for rh nodes (bar 1 = cycle 0, the number the 12 Step shows); mark' takes seconds elapsed as for at nodes (the 12 Step's seconds counter reads the same) and, like them, assumes cps has been constant since cycle 0. markbar / marksec are synonyms.

Atemporal forms (single node, lK) fire once at absolute cycles. A time at or beyond the loop end, or no cps, is silence.

, mark 34 k [ pad 1 |* vel 0.9 ]      -- speech sample on the drop

kinPick :: IK -> [Pattern a] -> Pattern a Source #

Kinetics-windowed dispatch: partition [0,1] into N equal windows of width 1/N, where N = length pats, and play only the pattern whose window contains the current kSignal. Windows are derived at call time; any N >= 1 works (N=1 collapses to "always play this pattern").

Boundaries belong to the lower window: window i covers (i/N, (i+1)/N], with window 0 also including 0. So at the boundary between window i and window i+1, the lower window plays.

N=2 → [0, 1/2], (1/2, 1] N=3 → [0, 1/3], (1/3, 2/3], (2/3, 1]

Empty list → silence. Outside [0,1] → silence (no window matches).

isolate :: (Int, Int) -> IK -> IK Source #

A degree range for every line the launcher starts, applied in k:

k = isolate (-4,2) $ iK tempo form (rep s 4)

isolate keeps degree indices lo..hi (inclusive), as a line's own (-9,9) does; isolate' keeps only those outside it (lo and hi dropped). Each line's own range still applies, so the two intersect, and calls compose: isolate (-9,4) . isolate' (0,0) is -9..4 without degree 0. Lines that don't voice degree indices (drums, walking bass, subKick, harmonics, kmap) are unaffected.

isolate' :: (Int, Int) -> IK -> IK Source #

A degree range for every line the launcher starts, applied in k:

k = isolate (-4,2) $ iK tempo form (rep s 4)

isolate keeps degree indices lo..hi (inclusive), as a line's own (-9,9) does; isolate' keeps only those outside it (lo and hi dropped). Each line's own range still applies, so the two intersect, and calls compose: isolate (-9,4) . isolate' (0,0) is -9..4 without degree 0. Lines that don't voice degree indices (drums, walking bass, subKick, harmonics, kmap) are unaffected.

withForm :: IK -> (ProgressionContext -> Pattern ValueMap) -> Pattern ValueMap Source #

Bridge helper: apply a function taking ProgressionContext to a Kinetics context. Uses innerJoin to reactively switch when the form changes progressions.