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

Harmonic.Interface.Tidal.Bridge

Description

Bridge between the harmonic generation engine and TidalCycles live coding. Chord selection via mininotation patterns (Pattern Int).

Two arrangement strategies:

  • arrange — onset-join with kinetics range gating: each note maps through the chord active at its onset time, masked by kinetics signal.
  • arrange' — squeeze with kinetics range gating: each chord slot gets the full input pattern compressed to fit.

Both take a progression modifier (P.Progression -> P.Progression) and read the base progression from kProg k via innerJoin.

Synopsis

Voice Functions

type VoiceFunction = Progression -> [[Int]] Source #

Voice function type: extracts integer pitch sequences from progression

voiceRange :: (Int, Int) -> Pattern Int -> Pattern Int Source #

Filter pattern events by DEGREE-INDEX range — the raw Pattern Int values before the degree→pitch mapping, NOT MIDI notes. Instrument range clipping in MIDI space happens later, via clip.

layerForVoicing :: Layer -> ProgressionContext -> (Bool, Progression) Source #

Map notes through chords using onset-time lookup, with kinetics range gating.

The base progression and chord selection are read from IK. The modifier function transforms the progression (e.g. overlapF 0, id). Events are masked by the kinetics signal: only active when kSignal is within the (lo, hi) range. Form-driven dynamics (kDynamic) are applied automatically.

Parameter order: context first (kinetics range, IK, MIDI range), then interactive (voice function, modifier, patterns). |Project the requested layer from a context together with its voicing route. Chroma routing: the S/M layers of a genP-provenance context are THE curated 5/7-PC chroma, and the S/M layers of a chordscale-derived gen / genJ context are the analysis pentatonic / mode chroma — both are always voiced by strataModeFlow, lattice semantics: pattern index i addresses the i-th slot of a fixed pitch lattice grounded on bar 0 and inflected per bar — a held index pedals its pitch across key areas rather than transposing with each bar's root. The T layer always honours the user's VoiceFunction, as do the S/M layers of genE contexts (independent partner triads — ordinary harmony, so never chroma-routed) and of contexts whose layers are still literal duplicates (hand-built material without chordscale).

Routing by provenance / derived-chroma detection replaces the old first-bar cardinality sniff (isOctaSM), which mis-routed mixed-cardinality material in both directions (triad-first-bar chroma escaped to the DP; 4-note-first-bar harmony was captured by the chroma engine). Derived detection requires BOTH a distinct mode layer AND every mode bar at chroma cardinality (≥5) — so a subst-downgraded genE context (distinct but triadic layers) and a bar-substituted derived context (mixed cardinality after the edit) both fall back to the user's VoiceFunction rather than half-claiming lattice semantics. A genE context is excluded whole, combination selectors included. Its S/M are partner triads, and its SM/TSM unions are polytonal SONORITIES — three stacked triads, five tones — not scale forms, so the cyclic DP is the right tool for them and hasBigChroma correctly declines to reroute a uniform 5-PC progression. Deliberate, not a gap.

Chord Selection Helpers

warp :: String -> Pattern Int Source #

Parse a mininotation chord selection pattern (bar-relative). The /N divisor specifies the number of bars the pattern spans.

let r = warp "[1 2 3 4]/4"   -- 4 chords over 4 bars (1 per bar)
let r = warp "[1 2]/8"       -- 2 chords over 8 bars (4 bars each)

rep :: ProgressionContext -> Pattern Time -> Pattern Int Source #

Generate a sequential chord selection pattern from a progression. Auto-derives length from the progression. Timing is bar-relative.

let r = rep s4 1     -- 4 chords over 4 bars (1 bar each)
let r = rep s4 0.5   -- 4 chords over 2 bars (half bar each)

Arrangement

arrange Source #

Arguments

:: (Double, Double)

Kinetics gate: events pass only while kSignal sits inside (lo, hi) — the same predicate as ki, applied here so every instrument line carries its own activation band

-> IK

Performance context (kinetics + chord selection)

-> (Int, Int)

Degree-index trim for the input patterns (scale degrees, not MIDI; instrument-range clipping happens later via clip)

-> Layer

Progression layer to voice — single (T | S | M) or synthesized combination (TS | TM | SM | TSM | PT)

-> VoiceFunction

Voice function (flow, root, etc.)

-> (Progression -> Progression)

Progression modifier (overlapF 0, id, etc.)

-> [Pattern Int]

Input patterns to harmonize

-> Pattern ValueMap 

Render scale-degree patterns into a playable ControlPattern, reading pitches from the progression under the given voicing strategy.

The workhorse of the Tidal interface: every orchestral instrument in Harmonic.Interface.Tidal.Orchestra is a thin wrapper around it.

d1 $ arrange (0,1) k (-9,9) T flow id ["0 1 2 3"]

arrange' Source #

Arguments

:: (Double, Double)

Kinetics range

-> IK

Performance context

-> (Int, Int)

Degree-index trim for the input patterns (scale degrees, not MIDI; instrument-range clipping happens later via clip)

-> Layer

Progression layer (T | S | M)

-> VoiceFunction

Voice function

-> (Progression -> Progression)

Progression modifier

-> [Pattern Int]

Input patterns to harmonize

-> Pattern ValueMap 

Map notes through chords using squeeze, with kinetics range gating.

Same kinetics/modifier pattern as arrange, but uses squeeze strategy: each chord slot gets the full input pattern compressed to fit.

Natural harmonics (the composite overtone series)

type Tuning = [(NoteName, Int)] Source #

Open strings of an overtone instrument as (name, scientific octave): (A,1) is A1 = MIDI 33.

ecbc :: Tuning Source #

The instrument behind hmnx: the Electric Contrabass Cittern as the MPC harmonics keymap holds it — open strings A2 E3 G3 (thesis §2.1: EAeGB is E2 A2 E3 G3 B3) and the top string in both Hipshot lever positions, B3 and C4. B and C are one physical string, but the keygroup carries both sets of samples, so both are always available to a pattern; the harmonic context is what limits the space. Fixed on purpose: the keymap never changes, so no launcher states a tuning.

composite Source #

Arguments

:: (Double, Double)

Kinetics gate, as arrange

-> IK

Performance context

-> Layer

Harmonic state: T (chord tones) or S (pentatonic)

-> [Pattern Int]

Contours: ints into the bar's viable series

-> Pattern ValueMap 

The whole overtone instrument. Pattern ints index the bar's viable composite series — the harmonics of ecbc whose pitch class the layer voices — compressed to 0, 1, 2 … ascending and looped by floor-mod, so "[0,1,2]" is the lowest three available tones from the bottom up and "[-1,-2,-3]" the highest three from the top down, whatever the size of the structure in play. A pitch class present in two octaves is two ints. Never an octave wrap: these are fixed sample pitches. A bar whose voicing has no instrument tone rests. Polyphonic; harmonics is the per-string filter over the same mapping. The harmonic context is the only limit on the space: generating inside the instrument's palette (hcOvertones) keeps every tone reachable, and overtoneMap shows what each int hits.

, composite (0,1) k T ["~", "[0,1,2]/4"] # ch 11

harmonics :: NoteName -> (Double, Double) -> IK -> Layer -> [Pattern Int] -> Pattern ValueMap Source #

One string of ecbc, named by its open note (A E G B C; enharmonics collapse): the SAME int → pitch mapping as composite, but only the pitches that string can sound pass — the rest are silent, not remapped — so a string block isolates or mutes a string and owns its sustain. Within the string the result is mono' (latest-note priority: a new harmonic damps the previous, as on the instrument).

, harmonics A (0,1) k T ["~", pat] # ch 11

overtoneMap :: ProgressionContext -> Layer -> [Pattern Int] -> IO () Source #

Print, per chord of a progression, what the ints of the basic series map to on the instrument — pitch, MIDI number and sources in thesis notation (E2 = string E, overtone 2; / = alternative sources) — with the top-down alias beside each int; then the distinct ints of the contour over its first bar, resolved per chord. Takes the progression context and the layer the block plays, so what it prints is what composite sounds.

overtoneMap s T ["[0,1,2]/4"]

overtoneTable :: [Int] -> [(Int, Int, [(NoteName, Int)])] Source #

The basic series over a set of voiced pitch classes, on ecbc: (int, MIDI pitch, sources), a source being (string, overtone number) in the thesis's numbering — OT1 the fundamental's class (partials 2 and 4), OT2 the fifth, OT3 the third. Pure core of overtoneMap.

compositeSeries :: Tuning -> [Int] Source #

The composite overtone series (thesis §2.2): every pitch any string of a tuning can sound as a natural harmonic, ascending, once each. compositeSeries ecbc is the keymap: 57 64 67 69 71 72 73 74 76 78 79 80 83 84 87 88.

stringPitches :: (NoteName, Int) -> [Int] Source #

The four playable partials of the 2016 thesis — octave, fifth, double octave, third (partials 2 3 4 5) — of one open string, as MIDI pitches.

partialOffset :: Int -> Int Source #

Semitones from a string's fundamental to its n-th partial: round (12 * logBase 2 n), so partials 2..5 sit at +12 +19 +24 +28.

Parallelism Harmoniser

parallel :: Pattern Note -> ControlPattern -> ControlPattern Source #

Stack fixed-interval parallel voices over an arranged ControlPattern. The offset pattern is the FULL voice spec in absolute semitones: each note of pat is replaced by one copy per simultaneous offset, shifted by that offset. Include 0 to retain the original note; omit it to drop the root.

Comma = simultaneous voices, space = time-sequenced offsets (standard mininotation, natively evaluated). Applied post-voicing/post-range-filter, so offsets are not gated by the arrange MIDI range.

parallel "0 7"      $ arrange ... -- root + perfect fifth above
parallel "7"        $ arrange ... -- fifth only (root dropped)
parallel "[0,-5,4]" $ arrange ... -- root, fourth below, major third above

Harmonic state for an external note-mapper

harmonicState :: Layer -> IK -> ControlPattern Source #

The chosen layer's pitch classes as held MIDI notes, for a device that locks a played keyboard to the harmony (the rig's MiDiNiNjA X3).

One note per distinct pitch class, MIDI 108-119 (pitch class = note mod 12), on MIDI channel 10, each held for the length of its chord: the structure follows the chord grid (snd k), so two chords in a bar give two held sets. Any layer or combination (T, S, M, TS, ...) and any set size. Degree indices 0-11 cover the largest set; the indices past a chord's size fold back onto pitch classes already sent and are dropped, so each chord change costs one message per pitch class on a DIN line shared with the music.

Each set goes out stateLead ahead of its chord: on the wire before the chord's own notes, and in place for keys played just before the downbeat.

Channel 10, notes 108-119 sits above everything else on that channel in both rigs: the JV-map kit (0-9, 60-80), metronome and count, the MPC sub (35-48). The X3 consumes these notes before they reach the MPC.

kmap f k lyr = p "kmap" $ f $ harmonicState lyr k (BootTidal)

Chord Lookup

lookupChordAt :: Time -> Pattern Int -> Int Source #

Point-query a chord selection pattern at a specific time. Returns the chord index (0-indexed) active at time t. Falls back to chord 0 if no events found.

lookupChord :: ProgressionContext -> Int -> Chord Source #

Lookup a chord from a progression context by index with modulo wrap. Operates on the triad layer (the harmonic content).

lookupProgression :: ProgressionContext -> Pattern Int -> Pattern [Int] Source #

Lookup progression (triad layer) as a pattern of voicings via flow.

Progression Overlap (Re-exports from Arranger)

overlapF :: Int -> Progression -> Progression Source #

Forward overlap: merge pitches from n bars ahead

overlapB :: Int -> Progression -> Progression Source #

Backward overlap: merge pitches from n bars behind

overlap :: Int -> Progression -> Progression Source #

Bidirectional overlap: merge pitches from n bars in both directions

Eager-forcing helper (shared with LineHarmony)

forceAll :: [[Note]] -> () Source #

Force every element of a nested Note list to WHNF. Used to hoist the per-bar voicing computation (which can be expensive for large mode chroma) from the audio query thread to REPL evaluation time. Returns () so callers can compose via seq.