-- |
-- Module      : Harmonic.Interface.Tidal.Form
-- Description : Kinetics framework for form-driven range gating
--
-- 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    0      0.0   0.0   s
--   , rh   16      0.2   0.3   s    -- elements layer in
--   , rh'  28      0.35  0.55  s    -- riser: sparse but loud, then snap
--   , rh   32      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 ]@

module Harmonic.Interface.Tidal.Form
  ( -- * Types
    FormNode(..)
  , FormTime(..)
  , Transition(..)
  , Kinetics(..)
  , IK

    -- * Construction
  , at
  , at'
  , rh
  , rh'
  , iK
  , lK

    -- * Realization
  , formK

    -- * Primitives
  , ki
  , slate
  , kinPick
  , withForm

  ) where

import qualified Harmonic.Rules.Types.ProgressionContext as PC
import Sound.Tidal.Context

-------------------------------------------------------------------------------
-- Types
-------------------------------------------------------------------------------

-- |A node's position in time — wall-clock 'Secs' or musical 'Bars' (4\/4).
-- Resolved to Tidal cycles at realization (see 'formK'). Mix freely in one form.
data FormTime = Secs Double | Bars Double
  deriving (Int -> FormTime -> ShowS
[FormTime] -> ShowS
FormTime -> String
(Int -> FormTime -> ShowS)
-> (FormTime -> String) -> ([FormTime] -> ShowS) -> Show FormTime
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> FormTime -> ShowS
showsPrec :: Int -> FormTime -> ShowS
$cshow :: FormTime -> String
show :: FormTime -> String
$cshowList :: [FormTime] -> ShowS
showList :: [FormTime] -> ShowS
Show, FormTime -> FormTime -> Bool
(FormTime -> FormTime -> Bool)
-> (FormTime -> FormTime -> Bool) -> Eq FormTime
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: FormTime -> FormTime -> Bool
== :: FormTime -> FormTime -> Bool
$c/= :: FormTime -> FormTime -> Bool
/= :: FormTime -> FormTime -> Bool
Eq)

-- |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).
data Transition = Smooth | Snap
  deriving (Int -> Transition -> ShowS
[Transition] -> ShowS
Transition -> String
(Int -> Transition -> ShowS)
-> (Transition -> String)
-> ([Transition] -> ShowS)
-> Show Transition
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Transition -> ShowS
showsPrec :: Int -> Transition -> ShowS
$cshow :: Transition -> String
show :: Transition -> String
$cshowList :: [Transition] -> ShowS
showList :: [Transition] -> ShowS
Show, Transition -> Transition -> Bool
(Transition -> Transition -> Bool)
-> (Transition -> Transition -> Bool) -> Eq Transition
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Transition -> Transition -> Bool
== :: Transition -> Transition -> Bool
$c/= :: Transition -> Transition -> Bool
/= :: Transition -> Transition -> Bool
Eq)

-- |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.
data FormNode = FormNode
  { FormNode -> FormTime
fnTime     :: FormTime                -- ^ Position (seconds or bars)
  , FormNode -> Double
fnKinetics :: Double                  -- ^ 0.0-1.0 kinetics level
  , FormNode -> Double
fnDynamic  :: Double                  -- ^ 0.0-1.0 dynamic level
  , FormNode -> ProgressionContext
fnProg     :: PC.ProgressionContext   -- ^ Active 3-layer progression at this node
  , FormNode -> Transition
fnTrans    :: Transition              -- ^ Transition of the segment starting at this node
  } deriving (Int -> FormNode -> ShowS
[FormNode] -> ShowS
FormNode -> String
(Int -> FormNode -> ShowS)
-> (FormNode -> String) -> ([FormNode] -> ShowS) -> Show FormNode
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> FormNode -> ShowS
showsPrec :: Int -> FormNode -> ShowS
$cshow :: FormNode -> String
show :: FormNode -> String
$cshowList :: [FormNode] -> ShowS
showList :: [FormNode] -> ShowS
Show, FormNode -> FormNode -> Bool
(FormNode -> FormNode -> Bool)
-> (FormNode -> FormNode -> Bool) -> Eq FormNode
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: FormNode -> FormNode -> Bool
== :: FormNode -> FormNode -> Bool
$c/= :: FormNode -> FormNode -> Bool
/= :: FormNode -> FormNode -> Bool
Eq)

-- |Realized form: continuous and discrete signals for live performance.
data Kinetics = Kinetics
  { Kinetics -> Pattern Double
kSignal   :: Pattern Double                -- ^ Kinetics level 0-1 (continuous interpolated)
  , Kinetics -> Pattern Double
kDynamic  :: Pattern Double                -- ^ Dynamic envelope 0-1 (continuous interpolated)
  , Kinetics -> Pattern ProgressionContext
kProg     :: Pattern PC.ProgressionContext -- ^ Active 3-layer progression (step function)
  , Kinetics -> Double
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.
  , Kinetics -> Double
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.
  }

-- |Performance context: Kinetics bundled with chord selection pattern.
-- Reduces parameter threading — @r@ and @k@ are always passed together.
type IK = (Kinetics, Pattern Int)

-------------------------------------------------------------------------------
-- Construction
-------------------------------------------------------------------------------

-- |Form node builders. Time unit and transition are orthogonal:
-- @at@\/@at'@ take wall-clock seconds, @rh@\/@rh'@ take bars (rehearsal marks, 4\/4);
-- unprimed = smooth transition, primed = snap. @at@ is unchanged from before.
--
-- @at  0 0 0 s@   seconds, smooth   @rh  8 0.5 0.5 s@   bars, smooth
-- @at' 60 1 1 s@  seconds, snap     @rh' 16 0.9 0.9 s@  bars, snap
at, at', rh, rh' :: Double -> Double -> Double -> PC.ProgressionContext -> FormNode
at :: Double -> Double -> Double -> ProgressionContext -> FormNode
at  Double
t Double
k Double
d ProgressionContext
pc = FormTime
-> Double -> Double -> ProgressionContext -> Transition -> FormNode
FormNode (Double -> FormTime
Secs Double
t) Double
k Double
d ProgressionContext
pc Transition
Smooth
at' :: Double -> Double -> Double -> ProgressionContext -> FormNode
at' Double
t Double
k Double
d ProgressionContext
pc = FormTime
-> Double -> Double -> ProgressionContext -> Transition -> FormNode
FormNode (Double -> FormTime
Secs Double
t) Double
k Double
d ProgressionContext
pc Transition
Snap
rh :: Double -> Double -> Double -> ProgressionContext -> FormNode
rh  Double
b Double
k Double
d ProgressionContext
pc = FormTime
-> Double -> Double -> ProgressionContext -> Transition -> FormNode
FormNode (Double -> FormTime
Bars Double
b) Double
k Double
d ProgressionContext
pc Transition
Smooth
rh' :: Double -> Double -> Double -> ProgressionContext -> FormNode
rh' Double
b Double
k Double
d ProgressionContext
pc = FormTime
-> Double -> Double -> ProgressionContext -> Transition -> FormNode
FormNode (Double -> FormTime
Bars Double
b) Double
k Double
d ProgressionContext
pc Transition
Snap

-- |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\")@
iK :: Double -> [FormNode] -> Pattern Int -> IK
iK :: Double -> [FormNode] -> Pattern Int -> IK
iK Double
bpm [FormNode]
nodes Pattern Int
chordPat = (Double -> [FormNode] -> Kinetics
formK Double
bpm [FormNode]
nodes, Pattern Int
chordPat)

-- |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
lK :: Pattern Double          -- ^ Kinetics signal (0-1, live)
   -> Pattern Double          -- ^ Dynamics signal (0-1, live)
   -> PC.ProgressionContext   -- ^ Active 3-layer progression
   -> Pattern Int             -- ^ Chord-selection pattern
   -> IK
lK :: Pattern Double
-> Pattern Double -> ProgressionContext -> Pattern Int -> IK
lK Pattern Double
sig Pattern Double
dyn ProgressionContext
pc Pattern Int
chordPat = (Pattern Double
-> Pattern Double
-> Pattern ProgressionContext
-> Double
-> Double
-> Kinetics
Kinetics Pattern Double
sig Pattern Double
dyn (ProgressionContext -> Pattern ProgressionContext
forall a. a -> Pattern a
forall (f :: * -> *) a. Applicative f => a -> f a
pure ProgressionContext
pc) Double
0 Double
0, Pattern Int
chordPat)

-------------------------------------------------------------------------------
-- Realization
-------------------------------------------------------------------------------

-- |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).
beatsPerBar :: Double
beatsPerBar :: Double
beatsPerBar = Double
4

-- |Resolve a node's position to Tidal cycles from form start.
nodeCycles :: Double -> FormNode -> Double
nodeCycles :: Double -> FormNode -> Double
nodeCycles Double
cps FormNode
n = case FormNode -> FormTime
fnTime FormNode
n of
  Secs Double
s -> Double
s Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
cps
  Bars Double
b -> Double
b Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
beatsPerBar

-- |Node position in wall-clock seconds (for 'kLoopSecs' \/ the display).
nodeSecs :: Double -> FormNode -> Double
nodeSecs :: Double -> FormNode -> Double
nodeSecs Double
cps FormNode
n = case FormNode -> FormTime
fnTime FormNode
n of
  Secs Double
s -> Double
s
  Bars Double
b -> Double
b Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
beatsPerBar Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
cps

-- |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'.
formK :: Double -> [FormNode] -> Kinetics
formK :: Double -> [FormNode] -> Kinetics
formK Double
bpm [FormNode]
nodes = Kinetics
  { kSignal :: Pattern Double
kSignal   = Double -> [FormNode] -> (FormNode -> Double) -> Pattern Double
formSignal Double
cps [FormNode]
nodes FormNode -> Double
fnKinetics
  , kDynamic :: Pattern Double
kDynamic  = Double -> [FormNode] -> (FormNode -> Double) -> Pattern Double
formSignal Double
cps [FormNode]
nodes FormNode -> Double
fnDynamic
  , kProg :: Pattern ProgressionContext
kProg     = Double
-> [FormNode]
-> (FormNode -> ProgressionContext)
-> Pattern ProgressionContext
forall a. Double -> [FormNode] -> (FormNode -> a) -> Pattern a
formStep   Double
cps [FormNode]
nodes FormNode -> ProgressionContext
fnProg
  , kLoopSecs :: Double
kLoopSecs = case [FormNode]
nodes of
                  (FormNode
_:FormNode
_:[FormNode]
_) -> Double -> FormNode -> Double
nodeSecs Double
cps ([FormNode] -> FormNode
forall a. HasCallStack => [a] -> a
last [FormNode]
nodes)   -- multi-node: form duration (seconds)
                  [FormNode]
_       -> Double
0                           -- single-node or empty: atemporal
  , kCps :: Double
kCps      = Double
cps
  }
  where cps :: Double
cps = Double
bpm Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Double
60

-- |Kinetics\/dynamic signal: piecewise per segment, ramped when the segment's
-- start node is 'Smooth', held (stepped) when 'Snap'. Single node: constant.
formSignal :: Double -> [FormNode] -> (FormNode -> Double) -> Pattern Double
formSignal :: Double -> [FormNode] -> (FormNode -> Double) -> Pattern Double
formSignal Double
_   [FormNode
node] FormNode -> Double
accessor = Double -> Pattern Double
forall a. a -> Pattern a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Double -> Double
forall a b. (Real a, Fractional b) => a -> b
realToFrac (Double -> Double) -> Double -> Double
forall a b. (a -> b) -> a -> b
$ FormNode -> Double
accessor FormNode
node)
formSignal Double
cps [FormNode]
nodes  FormNode -> Double
accessor =
  let totalCycles :: Time
totalCycles = Double -> Time
forall a b. (Real a, Fractional b) => a -> b
realToFrac (Double -> FormNode -> Double
nodeCycles Double
cps ([FormNode] -> FormNode
forall a. HasCallStack => [a] -> a
last [FormNode]
nodes)) :: Time
      pairs :: [(FormNode, FormNode)]
pairs       = [FormNode] -> [FormNode] -> [(FormNode, FormNode)]
forall a b. [a] -> [b] -> [(a, b)]
zip [FormNode]
nodes ([FormNode] -> [FormNode]
forall a. HasCallStack => [a] -> [a]
tail [FormNode]
nodes)
      segments :: [(Time, Pattern Double)]
segments    = [ ( Double -> Time
forall a b. (Real a, Fractional b) => a -> b
realToFrac (Double -> FormNode -> Double
nodeCycles Double
cps FormNode
n2 Double -> Double -> Double
forall a. Num a => a -> a -> a
- Double -> FormNode -> Double
nodeCycles Double
cps FormNode
n1)
                      , case FormNode -> Transition
fnTrans FormNode
n1 of
                          Transition
Snap   -> Double -> Pattern Double
forall a. a -> Pattern a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (Double -> Double
forall a b. (Real a, Fractional b) => a -> b
realToFrac (Double -> Double) -> Double -> Double
forall a b. (a -> b) -> a -> b
$ FormNode -> Double
accessor FormNode
n1)
                          Transition
Smooth -> Pattern Time -> Pattern Double -> Pattern Double
forall a. Pattern Time -> Pattern a -> Pattern a
segment Pattern Time
16 (Pattern Double -> Pattern Double)
-> Pattern Double -> Pattern Double
forall a b. (a -> b) -> a -> b
$ Pattern Double
-> Pattern Double -> Pattern Double -> Pattern Double
forall a. Num a => Pattern a -> Pattern a -> Pattern a -> Pattern a
range (Double -> Pattern Double
forall a b. (Real a, Fractional b) => a -> b
realToFrac (Double -> Pattern Double) -> Double -> Pattern Double
forall a b. (a -> b) -> a -> b
$ FormNode -> Double
accessor FormNode
n1)
                                                       (Double -> Pattern Double
forall a b. (Real a, Fractional b) => a -> b
realToFrac (Double -> Pattern Double) -> Double -> Pattern Double
forall a b. (a -> b) -> a -> b
$ FormNode -> Double
accessor FormNode
n2) Pattern Double
forall a. (Fractional a, Real a) => Pattern a
saw
                      )
                    | (FormNode
n1, FormNode
n2) <- [(FormNode, FormNode)]
pairs
                    ]
  in Pattern Time -> Pattern Double -> Pattern Double
forall a. Pattern Time -> Pattern a -> Pattern a
slow (Time -> Pattern Time
forall a. a -> Pattern a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Time
totalCycles) (Pattern Double -> Pattern Double)
-> Pattern Double -> Pattern Double
forall a b. (a -> b) -> a -> b
$ [(Time, Pattern Double)] -> Pattern Double
forall a. [(Time, Pattern a)] -> Pattern a
timecat [(Time, Pattern Double)]
segments

-- |Step signal: hold each node's value until the next (progression can't ramp).
-- Independent of 'fnTrans'. Single node: constant value.
formStep :: Double -> [FormNode] -> (FormNode -> a) -> Pattern a
formStep :: forall a. Double -> [FormNode] -> (FormNode -> a) -> Pattern a
formStep Double
_   [FormNode
node] FormNode -> a
accessor = a -> Pattern a
forall a. a -> Pattern a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (FormNode -> a
accessor FormNode
node)
formStep Double
cps [FormNode]
nodes  FormNode -> a
accessor =
  let totalCycles :: Time
totalCycles = Double -> Time
forall a b. (Real a, Fractional b) => a -> b
realToFrac (Double -> FormNode -> Double
nodeCycles Double
cps ([FormNode] -> FormNode
forall a. HasCallStack => [a] -> a
last [FormNode]
nodes)) :: Time
      pairs :: [(FormNode, FormNode)]
pairs       = [FormNode] -> [FormNode] -> [(FormNode, FormNode)]
forall a b. [a] -> [b] -> [(a, b)]
zip [FormNode]
nodes ([FormNode] -> [FormNode]
forall a. HasCallStack => [a] -> [a]
tail [FormNode]
nodes)
      segments :: [(Time, Pattern a)]
segments    = [ ( Double -> Time
forall a b. (Real a, Fractional b) => a -> b
realToFrac (Double -> FormNode -> Double
nodeCycles Double
cps FormNode
n2 Double -> Double -> Double
forall a. Num a => a -> a -> a
- Double -> FormNode -> Double
nodeCycles Double
cps FormNode
n1)
                      , a -> Pattern a
forall a. a -> Pattern a
forall (f :: * -> *) a. Applicative f => a -> f a
pure (FormNode -> a
accessor FormNode
n1)
                      )
                    | (FormNode
n1, FormNode
n2) <- [(FormNode, FormNode)]
pairs
                    ]
  in Pattern Time -> Pattern a -> Pattern a
forall a. Pattern Time -> Pattern a -> Pattern a
slow (Time -> Pattern Time
forall a. a -> Pattern a
forall (f :: * -> *) a. Applicative f => a -> f a
pure Time
totalCycles) (Pattern a -> Pattern a) -> Pattern a -> Pattern a
forall a b. (a -> b) -> a -> b
$ [(Time, Pattern a)] -> Pattern a
forall a. [(Time, Pattern a)] -> Pattern a
timecat [(Time, Pattern a)]
segments

-------------------------------------------------------------------------------
-- Primitives
-------------------------------------------------------------------------------

-- |Range gate: mask a pattern by kinetics signal level.
-- Events pass only when kSignal is within the (lo, hi) range.
ki :: (Double, Double) -> IK -> Pattern a -> Pattern a
ki :: forall a. (Double, Double) -> IK -> Pattern a -> Pattern a
ki (Double
lo, Double
hi) (Kinetics
kin, Pattern Int
_) = Pattern Bool -> Pattern a -> Pattern a
forall a. Pattern Bool -> Pattern a -> Pattern a
mask ((Double -> Bool) -> Pattern Double -> Pattern Bool
forall a b. (a -> b) -> Pattern a -> Pattern b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap (\Double
x -> Double
x Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
>= Double
lo Bool -> Bool -> Bool
&& Double
x Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
<= Double
hi) (Kinetics -> Pattern Double
kSignal Kinetics
kin))

-- |Gated stack: stack patterns and gate by kinetics range.
slate :: (Double, Double) -> IK -> [Pattern a] -> Pattern a
slate :: forall a. (Double, Double) -> IK -> [Pattern a] -> Pattern a
slate (Double, Double)
range IK
k [Pattern a]
pats = (Double, Double) -> IK -> Pattern a -> Pattern a
forall a. (Double, Double) -> IK -> Pattern a -> Pattern a
ki (Double, Double)
range IK
k (Pattern a -> Pattern a) -> Pattern a -> Pattern a
forall a b. (a -> b) -> a -> b
$ [Pattern a] -> Pattern a
forall a. [Pattern a] -> Pattern a
stack [Pattern a]
pats

-- |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).
kinPick :: IK -> [Pattern a] -> Pattern a
kinPick :: forall a. IK -> [Pattern a] -> Pattern a
kinPick IK
_ [] = Pattern a
forall a. Pattern a
silence
kinPick (Kinetics
kin, Pattern Int
_) [Pattern a]
pats =
  let n :: Int
n      = [Pattern a] -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length [Pattern a]
pats
      step :: Double
step   = Double
1 Double -> Double -> Double
forall a. Fractional a => a -> a -> a
/ Int -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral Int
n
      window :: p -> Pattern a -> Pattern a
window p
i Pattern a
p =
        let lo :: Double
lo = p -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral p
i       Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
step
            hi :: Double
hi = p -> Double
forall a b. (Integral a, Num b) => a -> b
fromIntegral (p
i p -> p -> p
forall a. Num a => a -> a -> a
+ p
1) Double -> Double -> Double
forall a. Num a => a -> a -> a
* Double
step
            inWin :: Double -> Bool
inWin Double
x | p
i p -> p -> Bool
forall a. Eq a => a -> a -> Bool
== p
0    = Double
x Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
>= Double
lo Bool -> Bool -> Bool
&& Double
x Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
<= Double
hi
                    | Bool
otherwise = Double
x Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
>  Double
lo Bool -> Bool -> Bool
&& Double
x Double -> Double -> Bool
forall a. Ord a => a -> a -> Bool
<= Double
hi
        in Pattern Bool -> Pattern a -> Pattern a
forall a. Pattern Bool -> Pattern a -> Pattern a
mask ((Double -> Bool) -> Pattern Double -> Pattern Bool
forall a b. (a -> b) -> Pattern a -> Pattern b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap Double -> Bool
inWin (Kinetics -> Pattern Double
kSignal Kinetics
kin)) Pattern a
p
  in [Pattern a] -> Pattern a
forall a. [Pattern a] -> Pattern a
stack ((Integer -> Pattern a -> Pattern a)
-> [Integer] -> [Pattern a] -> [Pattern a]
forall a b c. (a -> b -> c) -> [a] -> [b] -> [c]
zipWith Integer -> Pattern a -> Pattern a
forall {p} {a}. Integral p => p -> Pattern a -> Pattern a
window [Integer
0..] [Pattern a]
pats)

-- |Bridge helper: apply a function taking 'Harmonic.Rules.Types.ProgressionContext.ProgressionContext' to a Kinetics context.
-- Uses innerJoin to reactively switch when the form changes progressions.
withForm :: IK -> (PC.ProgressionContext -> Pattern ValueMap) -> Pattern ValueMap
withForm :: IK -> (ProgressionContext -> Pattern ValueMap) -> Pattern ValueMap
withForm (Kinetics
kin, Pattern Int
_) ProgressionContext -> Pattern ValueMap
f = Pattern (Pattern ValueMap) -> Pattern ValueMap
forall b. Pattern (Pattern b) -> Pattern b
innerJoin (Pattern (Pattern ValueMap) -> Pattern ValueMap)
-> Pattern (Pattern ValueMap) -> Pattern ValueMap
forall a b. (a -> b) -> a -> b
$ (ProgressionContext -> Pattern ValueMap)
-> Pattern ProgressionContext -> Pattern (Pattern ValueMap)
forall a b. (a -> b) -> Pattern a -> Pattern b
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
fmap ProgressionContext -> Pattern ValueMap
f (Kinetics -> Pattern ProgressionContext
kProg Kinetics
kin)