diff --git a/src/Sound/Tidal/ParseBP.hs b/src/Sound/Tidal/ParseBP.hs
--- a/src/Sound/Tidal/ParseBP.hs
+++ b/src/Sound/Tidal/ParseBP.hs
@@ -685,7 +685,10 @@
         ]
 
 fromNote :: (Num a) => Pattern String -> Pattern a
-fromNote pat = fromRight 0 . runParser parseNote 0 "" <$> pat
+fromNote pat = _fromNote <$> pat
+
+_fromNote :: (Num a) => String -> a
+_fromNote str = fromRight 0 . runParser parseNote 0 "" $ str
 
 pColour :: MyParser (TPat ColourD)
 pColour = wrapPos $ do
diff --git a/src/Sound/Tidal/Pattern.hs b/src/Sound/Tidal/Pattern.hs
--- a/src/Sound/Tidal/Pattern.hs
+++ b/src/Sound/Tidal/Pattern.hs
@@ -31,7 +31,10 @@
   )
 where
 
-import Control.Applicative (liftA2)
+-- Backward compatibility for liftA2
+--- https://github.com/haskell/core-libraries-committee/blob/main/guides/export-lifta2-prelude.md
+
+import Control.Applicative (Applicative (..))
 import Control.DeepSeq (NFData)
 import Control.Monad ((>=>))
 import Data.Data (Data)
@@ -43,7 +46,7 @@
 import Data.Word (Word8)
 import GHC.Generics (Generic)
 import Sound.Tidal.Time
-import Prelude hiding ((*>), (<*))
+import Prelude hiding (Applicative (..), (*>), (<*))
 
 ------------------------------------------------------------------------
 
@@ -56,7 +59,7 @@
   }
 
 -- | A datatype representing events taking place over time
-data Pattern a = Pattern {query :: State -> [Event a], steps :: Maybe (Rational), pureValue :: Maybe a}
+data Pattern a = Pattern {query :: State -> [Event a], steps :: Maybe Rational, pureValue :: Maybe a}
   deriving (Generic, Functor)
 
 instance (NFData a) => NFData (Pattern a)
@@ -73,6 +76,9 @@
 withSteps :: (Rational -> Rational) -> Pattern a -> Pattern a
 withSteps f p = p {steps = fmap f $ steps p}
 
+hasSteps :: Pattern a -> Bool
+hasSteps = isJust . steps
+
 pace :: Rational -> Pattern a -> Pattern a
 pace target p@(Pattern _ (Just t) _) = setSteps (Just target) $ _fast (target / t) p
 -- raise error?
@@ -613,6 +619,9 @@
 withEvent :: (Event a -> Event b) -> Pattern a -> Pattern b
 withEvent f p = p {query = map f . query p, pureValue = Nothing}
 
+withEventArc :: (Arc -> Arc) -> Pattern a -> Pattern a
+withEventArc f pat = withEvent (\(Event c w p v) -> Event c (f <$> w) (f p) v) pat
+
 -- | @withEvent f p@ returns a new @Pattern@ with each value mapped over
 -- function @f@.
 withValue :: (a -> b) -> Pattern a -> Pattern b
@@ -850,6 +859,12 @@
             as' = as $ start $ wholeOrPart ex
         as s = query pa $ fQuery s
         fQuery s = st {arc = Arc s s}
+
+-- Like `rev`, but reverses the whole pattern, rather than every cycle in the pattern.
+revv :: Pattern a -> Pattern a
+revv pat = withEventArc negateArc $ withQueryArc negateArc pat
+  where
+    negateArc (Arc s e) = Arc (negate e) (negate s)
 
 -- ** Event filters
 
diff --git a/src/Sound/Tidal/Stepwise.hs b/src/Sound/Tidal/Stepwise.hs
--- a/src/Sound/Tidal/Stepwise.hs
+++ b/src/Sound/Tidal/Stepwise.hs
@@ -18,14 +18,14 @@
 
 module Sound.Tidal.Stepwise where
 
-import Data.List (sort, sortOn, transpose)
-import Data.Maybe (catMaybes, fromJust, fromMaybe, isJust, mapMaybe)
-import Sound.Tidal.Core (stack, timecat, zoom, zoompat)
+import Data.List (sort, transpose)
+import Data.Maybe (fromMaybe, isJust, mapMaybe)
+import Sound.Tidal.Core (slowcat, stack, timecat, zoom)
 import Sound.Tidal.Pattern
-import Sound.Tidal.Utils (enumerate, nubOrd, pairs)
+import Sound.Tidal.Utils (nubOrd, pairs)
 
--- _lcmsteps :: [Pattern a] -> Maybe Time
--- _lcmsteps pats = foldl1 lcmr <$> (sequence $ map steps pats)
+-- _lcmsteps :: [Pattern a] -> Pattern Time
+-- _lcmsteps pats = foldl1 (liftA2 lcmr) $ mapMaybe steps pats
 
 s_patternify :: (a -> Pattern b -> Pattern c) -> (Pattern a -> Pattern b -> Pattern c)
 s_patternify f (Pattern _ _ (Just a)) b = f a b
@@ -37,6 +37,7 @@
 stepJoin :: Pattern (Pattern a) -> Pattern a
 stepJoin pp = Pattern q first_t Nothing
   where
+    -- TODO - use context?
     q st@(State a c) = query (timecat $ retime $ slices $ query (rotL (sam $ start a) pp) (st {arc = Arc 0 1})) st
     first_t :: Maybe Rational
     first_t = steps $ timecat $ retime $ slices $ queryArc pp (Arc 0 1)
@@ -89,9 +90,9 @@
 _stepdrop _ pat@(Pattern _ Nothing _) = pat
 _stepdrop n pat@(Pattern _ (Just t) _) = steptake (pure $ f t) pat
   where
-    f t
-      | n >= 0 = t - n
-      | otherwise = negate (t + n)
+    f t'
+      | n >= 0 = t' - n
+      | otherwise = negate (t' + n)
 
 stepdrop :: Pattern Time -> Pattern a -> Pattern a
 stepdrop = s_patternify _stepdrop
@@ -114,11 +115,26 @@
 extend :: Pattern Rational -> Pattern a -> Pattern a
 extend = s_patternify _extend
 
+-- polymeter :: [Pattern a] -> Pattern a
+-- polymeter pats = stack $ map (pace targetSteps) pats'
+--   where
+--     targetSteps = _lcmsteps pats'
+--     pats' = filter hasSteps pats
+
+-- pm :: [Pattern a] -> Pattern a
+-- pm = polymeter
+
 -- | Successively plays a pattern from each group in turn
 stepalt :: [[Pattern a]] -> Pattern a
 stepalt groups = stepcat $ concat $ take (fromIntegral $ c * length groups) $ transpose $ map cycle groups
   where
     c = foldl1 lcm $ map length groups
+
+stepzip :: [Pattern a] -> Pattern a
+stepzip pats = setSteps (Just s) $ _fast s zipped
+  where
+    zipped = slowcat $ map (pace 1) $ filter hasSteps pats
+    s = foldl1 lcmr $ mapMaybe steps pats
 
 {-
 s_while :: Pattern Bool -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a
diff --git a/src/Sound/Tidal/UI.hs b/src/Sound/Tidal/UI.hs
--- a/src/Sound/Tidal/UI.hs
+++ b/src/Sound/Tidal/UI.hs
@@ -1434,9 +1434,9 @@
       where
         r = timeToRand seed'
     renorm :: IM.IntMap (Map.Map Double Int)
-    renorm = IM.fromList $ zip [0 ..] [Map.fromList $ zip (tail $ scanl (+) 0 x) [0 ..] | x <- tp]
+    renorm = IM.fromList $ zip [0 ..] [Map.fromListWith (\new old -> old) $ zip (tail $ scanl (+) 0 x) [0 ..] | x <- tp]
     findIndex :: Double -> Map.Map Double Int -> Int
-    findIndex x v = maybe 0 snd (Map.lookupGE x v)
+    findIndex x v = maybe (snd (Map.findMax v)) snd (Map.lookupGT x v)
     delta = 1 / fromIntegral n
 
 -- | @markovPat n xi tp@ generates a one-cycle pattern of @n@ steps in a Markov
@@ -1606,6 +1606,20 @@
 _chunk' :: (Integral a) => a -> (Pattern b -> Pattern b) -> Pattern b -> Pattern b
 _chunk' n f p = _chunk (-n) f p
 
+chunkinto :: Pattern Int -> (Pattern b -> Pattern b) -> Pattern b -> Pattern b
+chunkinto npat f p = innerJoin $ (\n -> _chunkinto n f p) <$> npat
+
+chunkInto :: Pattern Int -> (Pattern b -> Pattern b) -> Pattern b -> Pattern b
+chunkInto = chunkinto
+
+_chunkinto :: Int -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a
+_chunkinto 0 _ pat = pat
+_chunkinto n f pat = into (iterf n $ fastcat $ map pure (True : replicate (abs n - 1) False)) f pat
+  where
+    iterf
+      | n > 0 = _iter'
+      | otherwise = _iter
+
 -- |
 -- @inside@ carries out an operation /inside/ a cycle.
 -- For example, while @rev "0 1 2 3 4 5 6 7"@ is the same as @"7 6 5 4 3 2 1 0"@,
@@ -3000,14 +3014,17 @@
 rib = ribbon
 
 -- | Turns a pattern into a pattern of patterns, according to the structure of another given pattern.
-unjoin :: Pattern Bool -> Pattern b -> Pattern (Pattern b)
-unjoin pieces pat = withEvent snip pieces
+unjoinfunc :: Pattern Bool -> (Pattern a -> Pattern a) -> Pattern a -> Pattern (Pattern a)
+unjoinfunc pieces f pat = withEvent snip pieces
   where
     -- If true, set value to the part of pattern looped at event boundaries
-    snip e@Event {value = True} = e {value = _ribbon (wholeStart e) (wholeStop e - wholeStart e) pat}
+    snip e@Event {value = True} = e {value = f $ _ribbon (wholeStart e) (wholeStop e - wholeStart e) pat}
     -- Otherwise, set value to unchanged pattern
     snip e = e {value = pat}
 
+unjoin :: Pattern Bool -> Pattern a -> Pattern (Pattern a)
+unjoin pieces pat = unjoinfunc pieces id pat
+
 -- | Applies a function to subcycles of a pattern, as defined by the structure of another given pattern.
-into :: Pattern Bool -> (Pattern a -> Pattern b) -> Pattern a -> Pattern b
-into pieces func pat = innerJoin $ fmap func $ unjoin pieces pat
+into :: Pattern Bool -> (Pattern b -> Pattern b) -> Pattern b -> Pattern b
+into pieces func pat = innerJoin $ unjoinfunc pieces func pat
diff --git a/test/Sound/Tidal/StepwiseTest.hs b/test/Sound/Tidal/StepwiseTest.hs
--- a/test/Sound/Tidal/StepwiseTest.hs
+++ b/test/Sound/Tidal/StepwiseTest.hs
@@ -15,7 +15,7 @@
 import Sound.Tidal.Stepwise (expand, stepcat, stepdrop, steptake)
 import Sound.Tidal.UI (inv, iter, linger, segment)
 import Test.Hspec (Spec, describe, it, shouldBe)
-import TestUtils (compareP)
+import TestUtils (compareP, firstCycleValues)
 import Prelude hiding ((*>), (<*))
 
 run :: Spec
@@ -47,3 +47,9 @@
       it "invert" $ steps (inv "1 0 1" :: Pattern Bool) `shouldBe` Just 3
       it "chop" $ steps (chop 3 $ sound "a b") `shouldBe` Just 6
       it "chop" $ steps (striate 3 $ sound "a b") `shouldBe` Just 6
+
+-- describe "polymeter" $ do
+--   it "can repeat patterns to step count lcm" $
+--     compareP (Arc 0 8) (polymeter ["a b c" :: Pattern Char, "d e"]) "{a b c, d e}%6"
+--   it "can work with 3 patterns" $
+--     compareP (Arc 0 8) (polymeter ["a b c" :: Pattern Char, "d e", "f g h i"]) "{a b c, d e, f g h i}%12"
diff --git a/test/Sound/Tidal/UITest.hs b/test/Sound/Tidal/UITest.hs
--- a/test/Sound/Tidal/UITest.hs
+++ b/test/Sound/Tidal/UITest.hs
@@ -488,3 +488,7 @@
          (Arc 0 1)
          (_ribbon 0.25 0.25 ("a b c d" :: Pattern String))
          "b b b b"
+    describe "runMarkov" $ do
+      it "deterministic transition matrix" $ do
+        runMarkov 4 [[0,1],[1,0]] 0 0
+          `shouldBe` [0,1,0,1]
diff --git a/tidal-core.cabal b/tidal-core.cabal
--- a/tidal-core.cabal
+++ b/tidal-core.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.0
 
 name: tidal-core
-version: 1.10.1
+version: 1.10.2
 
 license: GPL-3
 copyright:           (c) Alex McLean and other contributors, 2025
