diff --git a/Sound/Tidal/Dirt.hs b/Sound/Tidal/Dirt.hs
--- a/Sound/Tidal/Dirt.hs
+++ b/Sound/Tidal/Dirt.hs
@@ -12,6 +12,7 @@
 import Data.Hashable
 import Data.Bits
 import Data.Maybe
+import Data.Fixed
 import System.Process
 
 import Sound.Tidal.Stream
@@ -44,7 +45,8 @@
                             F "hresonance" (Just 0),
                             F "bandf" (Just 0),
                             F "bandq" (Just 0),
-                            S "unit" (Just "rate")
+                            S "unit" (Just "rate"),
+                            I "loop" (Just 1)
                           ],
                  cpsStamp = True,
                  timestamp = MessageStamp,
@@ -137,6 +139,7 @@
 bandf        = makeF dirt "bandf"
 bandq        = makeF dirt "bandq"
 unit         = makeS dirt "unit"
+loop         = makeI dirt "loop"
 
 cut :: Pattern Int -> OscPattern
 cut = makeI dirt "cut"
@@ -162,5 +165,9 @@
 striateO :: OscPattern -> Int -> Double -> OscPattern
 striateO p n o = cat $ map (\x -> off (fromIntegral x) p) [0 .. n-1]
   where off i p = p |+| begin ((atom $ (fromIntegral i / fromIntegral n) + o) :: Pattern Double) |+| end ((atom $ (fromIntegral (i+1) / fromIntegral n) + o) :: Pattern Double)
+
+striateL :: Int -> Int -> OscPattern -> OscPattern
+striateL n l p = striate n p |+| loop (atom $ fromIntegral l)
+striateL' n f l p = striate' n f p |+| loop (atom $ fromIntegral l)
 
 metronome = slow 2 $ sound (p "[odx, [hh]*8]")
diff --git a/Sound/Tidal/Pattern.hs b/Sound/Tidal/Pattern.hs
--- a/Sound/Tidal/Pattern.hs
+++ b/Sound/Tidal/Pattern.hs
@@ -27,26 +27,37 @@
 -- | @show (p :: Pattern)@ returns a text string representing the
 -- event values active during the first cycle of the given pattern.
 instance (Show a) => Show (Pattern a) where
-  show p@(Pattern _) = show $ arc p (0, 1)
+  show p@(Pattern _) = intercalate " " $ map showEvent $ arc p (0, 1)
 
+showTime t | denominator t == 1 = show (numerator t)
+           | otherwise = show (numerator t) ++ ('/':show (denominator t))
+
+showArc a = concat[showTime $ fst a, (' ':showTime (snd a))]
+
+showEvent (a, b, v) | a == b = concat["(",show v,
+                                      (' ':showArc a),
+                                      ")"
+                                     ]
+                    | otherwise = show v
+
 instance Functor Pattern where
   fmap f (Pattern a) = Pattern $ fmap (fmap (mapThd' f)) a
 
 -- | @pure a@ returns a pattern with an event with value @a@, which
 -- has a duration of one cycle, and repeats every cycle.
 instance Applicative Pattern where
-  pure x = Pattern $ \(s, e) -> map 
-                                (\t -> ((t%1, (t+1)%1), 
+  pure x = Pattern $ \(s, e) -> map
+                                (\t -> ((t%1, (t+1)%1),
                                         (t%1, (t+1)%1),
                                         x
                                        )
-                                ) 
+                                )
                                 [floor s .. ((ceiling e) - 1)]
-  (Pattern fs) <*> (Pattern xs) = 
+  (Pattern fs) <*> (Pattern xs) =
     Pattern $ \a -> concatMap applyX (fs a)
-    where applyX ((s,e), (s', e'), f) = 
-            map (\(_, _, x) -> ((s,e), (s', e'), f x)) 
-                (filter 
+    where applyX ((s,e), (s', e'), f) =
+            map (\(_, _, x) -> ((s,e), (s', e'), f x))
+                (filter
                  (\(_, a', _) -> isIn a' s)
                  (xs (s',e'))
                 )
@@ -59,13 +70,15 @@
 
 instance Monad Pattern where
   return = pure
+  -- Pattern a -> (a -> Pattern b) -> Pattern b
+  -- Pattern Char -> (Char -> Pattern String) -> Pattern String
+  
   p >>= f = -- unwrap (f <$> p)
     Pattern (\a -> concatMap
-                   -- TODO - this is a total guess
-                   (\((s,e), (s',e'), x) -> mapSnds' (const (s',e')) $
+                   (\((s,e), (s',e'), x) -> map (\ev -> ((s,e), (s',e'), thd' ev)) $
                                             filter
-                                            (\(_, a', _) -> isIn a' s)
-                                            (arc (f x) (s',e'))
+                                            (\(a', _, _) -> isIn a' s)
+                                            (arc (f x) (s,e))
                    )
                    (arc p a)
              )
@@ -76,7 +89,7 @@
 -- Take a pattern, and function from elements in the pattern to another pattern,
 -- and then return that pattern
 --bind :: Pattern a -> (a -> Pattern b) -> Pattern b
---bind p f = 
+--bind p f =
 
 -- this is actually join
 unwrap :: Pattern (Pattern a) -> Pattern a
@@ -144,7 +157,7 @@
 
 
 splitAtSam :: Pattern a -> Pattern a
-splitAtSam p = 
+splitAtSam p =
   splitQueries $ Pattern $ \(s,e) -> mapSnds' (trimArc (sam s)) $ arc p (s,e)
   where trimArc s' (s,e) = (max (s') s, min (s'+1) e)
 
@@ -203,11 +216,11 @@
 -- return a pattern that is half the speed.
 slow :: Time -> Pattern a -> Pattern a
 slow 0 = id
-slow t = density (1/t) 
+slow t = density (1/t)
 
 -- | The @<~@ operator shifts (or rotates) a pattern to the left (or
--- counter-clockwise) by the given @Time@ value. For example 
--- @(1%16) <~ p@ will return a pattern with all the events moved 
+-- counter-clockwise) by the given @Time@ value. For example
+-- @(1%16) <~ p@ will return a pattern with all the events moved
 -- one 16th of a cycle to the left.
 (<~) :: Time -> Pattern a -> Pattern a
 (<~) t p = withResultTime (subtract t) $ withQueryTime (+ t) p
@@ -333,7 +346,7 @@
 -- todo - triangular waves again
 
 squarewave1 :: Pattern Double
-squarewave1 = sig $ 
+squarewave1 = sig $
               \t -> fromIntegral $ floor $ (mod' (fromRational t) 1) * 2
 square1 = squarewave1
 
@@ -371,12 +384,12 @@
 
 -- Filter out events that have had their onsets cut off
 filterOnsets :: Pattern a -> Pattern a
-filterOnsets (Pattern f) = 
+filterOnsets (Pattern f) =
   Pattern $ (filter (\e -> eventOnset e >= eventStart e)) . f
 
 -- Filter events which have onsets, which are within the given range
 filterStartInRange :: Pattern a -> Pattern a
-filterStartInRange (Pattern f) = 
+filterStartInRange (Pattern f) =
   Pattern $ \(s,e) -> filter ((>= s) . eventOnset) $ f (s,e)
 
 filterOnsetsInRange = filterOnsets . filterStartInRange
@@ -452,7 +465,7 @@
 superimpose f p = stack [p, f p]
 
 -- | @splitQueries p@ wraps `p` to ensure that it does not get
--- queries that | span arcs. For example `arc p (0.5, 1.5)` would be
+-- queries that span arcs. For example `arc p (0.5, 1.5)` would be
 -- turned into two queries, `(0.5,1)` and `(1,1.5)`, and the results
 -- combined. Being able to assume queries don't span cycles often
 -- makes transformations easier to specify.
@@ -481,8 +494,8 @@
 -- for example, @within (1%2) (3%4) ((1%8) <~) "bd sn bd cp"@ would shift only
 -- the second @bd@
 within :: Arc -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a
-within (s,e) f p = stack [sliceArc (0,s) p, 
-                           compress (s,e) $ f $ zoom (s,e) p, 
+within (s,e) f p = stack [sliceArc (0,s) p,
+                           compress (s,e) $ f $ zoom (s,e) p,
                            sliceArc (e,1) p
                           ]
 
@@ -498,3 +511,93 @@
 index :: Real b => b -> Pattern b -> Pattern c -> Pattern c
 index sz indexpat pat = spread' (zoom' $ toRational sz) (toRational . (*(1-sz)) <$> indexpat) pat
   where zoom' sz start = zoom (start, start+sz)
+
+-- | @prr rot (blen, vlen) beatPattern valuePattern@: pattern rotate/replace.
+prrw :: (a -> b -> c) -> Int -> (Time, Time) -> Pattern a -> Pattern b -> Pattern c
+prrw f rot (blen, vlen) beatPattern valuePattern =
+  let
+    ecompare (_,e1,_) (_,e2,_) = compare (fst e1) (fst e2)
+    beats  = sortBy ecompare $ arc beatPattern (0, blen)
+    values = fmap thd' . sortBy ecompare $ arc valuePattern (0, vlen)
+    cycles = blen * (fromIntegral $ lcm (length beats) (length values) `div` (length beats))
+  in
+    slow cycles $ stack $ zipWith
+    (\( _, (start, end), v') v -> (start ~>) $ densityGap (1 / (end - start)) $ pure (f v' v))
+    (sortBy ecompare $ arc (density cycles $ beatPattern) (0, blen))
+    (drop (rot `mod` length values) $ cycle values)
+
+-- | @prr rot (blen, vlen) beatPattern valuePattern@: pattern rotate/replace.
+prr :: Int -> (Time, Time) -> Pattern a -> Pattern a -> Pattern a
+prr = prrw $ flip const
+
+{-|
+@preplace (blen, plen) beats values@ combines the timing of @beats@ with the values
+of @values@. Other ways of saying this are:
+* sequential convolution
+* @values@ quantized to @beats@.
+
+Examples:
+@
+d1 $ sound $ preplace (1,1) "x [~ x] x x" "bd sn"
+d1 $ sound $ preplace (1,1) "x(3,8)" "bd sn"
+d1 $ sound $ "x(3,8)" <~> "bd sn"
+d1 $ sound "[jvbass jvbass:5]*3" |+| (shape $ "1 1 1 1 1" <~> "0.2 0.9")
+@
+
+It is assumed the pattern fits into a single cycle. This works well with
+pattern literals, but not always with patterns defined elsewhere. In those cases
+use @prr@ and provide desired pattern lengths:
+@
+let p = slow 2 $ "x x x"
+
+d1 $ sound $ prr 0 (2,1) p "bd sn"
+@
+-}
+preplace :: (Time, Time) -> Pattern a -> Pattern a -> Pattern a
+preplace = preplaceWith $ flip const
+
+prep = preplace
+
+preplace1 :: Pattern a -> Pattern a -> Pattern a
+preplace1 = prr 0 (1, 1)
+
+preplaceWith :: (a -> b -> c) -> (Time, Time) -> Pattern a -> Pattern b -> Pattern c
+preplaceWith f (blen, plen) = prrw f 0 (blen, plen)
+
+prw = preplaceWith
+
+preplaceWith1 :: (a -> b -> c) -> Pattern a -> Pattern b -> Pattern c
+preplaceWith1 f = prrw f 0 (1, 1)
+
+prw1 = preplaceWith1
+
+(<~>) :: Pattern a -> Pattern a -> Pattern a
+(<~>) = preplace (1, 1)
+
+-- | @protate len rot p@ rotates pattern @p@ by @rot@ beats to the left.
+-- @len@: length of the pattern, in cycles.
+-- Example: @d1 $ every 4 (protate 2 (-1)) $ slow 2 $ sound "bd hh hh hh"@
+protate :: Time -> Int -> Pattern a -> Pattern a
+protate len rot p = prr rot (len, len) p p
+
+prot = protate
+prot1 = protate 1
+
+{-| The @<<~@ operator rotates a unit pattern to the left, similar to @<~@,
+but by events rather than linear time. The timing of the pattern remains constant:
+
+@
+d1 $ (1 <<~) $ sound "bd ~ sn hh"
+-- will become
+d1 $ sound "sn ~ hh bd"
+@ -}
+
+(<<~) :: Int -> Pattern a -> Pattern a
+(<<~) = protate 1
+
+(~>>) :: Int -> Pattern a -> Pattern a
+(~>>) = (<<~) . (0-)
+
+-- | @pequal cycles p1 p2@: quickly test if @p1@ and @p2@ are the same.
+pequal :: Ord a => Time -> Pattern a -> Pattern a -> Bool
+pequal cycles p1 p2 = (sort $ arc p1 (0, cycles)) == (sort $ arc p2 (0, cycles))
diff --git a/Sound/Tidal/VolcaKeys.hs b/Sound/Tidal/VolcaKeys.hs
new file mode 100644
--- /dev/null
+++ b/Sound/Tidal/VolcaKeys.hs
@@ -0,0 +1,161 @@
+  
+module Sound.Tidal.VolcaKeys where
+
+import qualified Sound.ALSA.Sequencer.Address as Addr
+import qualified Sound.ALSA.Sequencer.Client as Client
+import qualified Sound.ALSA.Sequencer.Port as Port
+import qualified Sound.ALSA.Sequencer.Event as Event
+import qualified Sound.ALSA.Sequencer as SndSeq
+import qualified Sound.ALSA.Exception as AlsaExc
+import qualified Sound.ALSA.Sequencer.Connect as Connect
+import GHC.Word
+import GHC.Int
+
+import Sound.OSC.FD
+import qualified Data.Map as Map
+import Control.Applicative
+import Control.Concurrent.MVar
+--import Visual
+import Data.Hashable
+import Data.Bits
+import Data.Maybe
+import System.Process
+import Control.Concurrent
+
+import Sound.Tidal.Stream
+import Sound.Tidal.Pattern
+import Sound.Tidal.Parse
+
+channel = Event.Channel 0
+
+keys :: OscShape
+keys = OscShape {path = "/note",
+                 params = [ I "note" Nothing,
+                            F "dur" (Just (0.05)),
+                            F "portamento" (Just (-1)),
+                            F "expression" (Just (-1)),
+                            F "voice" (Just (-1)),
+                            F "octave" (Just (-1)),
+                            F "detune" (Just (-1)),
+                            F "vcoegint" (Just (-1)),
+                            F "kcutoff" (Just (-1)),
+                            F "vcfegint" (Just (-1)),
+                            F "lforate" (Just (-1)),
+                            F "lfopitchint" (Just (-1)),
+                            F "lfocutoffint" (Just (-1)),
+                            F "attack" (Just (-1)),
+                            F "decay" (Just (-1)),
+                            F "sustain" (Just (-1)),
+                            F "dtime" (Just (-1)),
+                            F "dfeedback" (Just (-1))
+                          ],
+                 cpsStamp = False,
+                 timestamp = NoStamp,
+                 latency = 0,
+                 namedParams = False,
+                 preamble = []
+                }
+
+keyStream = stream "127.0.0.1" 7303 keys
+
+note         = makeI keys "note"
+dur          = makeF keys "dur"
+portamento   = makeF keys "portamento"
+expression   = makeF keys "expression"
+octave       = makeF keys "octave"
+voice        = makeF keys "voice"
+detune       = makeF keys "detune"
+vcoegint     = makeF keys "vcoegint"
+kcutoff      = makeF keys "kcutoff"
+vcfegint     = makeF keys "vcfegint"
+lforate      = makeF keys "lforate"
+lfopitchint  = makeF keys "lfopitchint"
+lfocutoffint = makeF keys "lfocutoffint"
+attack       = makeF keys "attack"
+decay        = makeF keys "decay"
+sustain      = makeF keys "sustain"
+dtime        = makeF keys "dtime"
+dfeedback    = makeF keys "dfeedback"
+
+
+keynames = map name (tail $ tail $ params keys)
+
+keyproxy latency midiport = 
+   do h <- SndSeq.openDefault SndSeq.Block
+      Client.setName (h :: SndSeq.T SndSeq.OutputMode) "Tidal"
+      c <- Client.getId h
+      p <- Port.createSimple h "out"
+           (Port.caps [Port.capRead, Port.capSubsRead]) Port.typeMidiGeneric
+      conn <- Connect.createTo h p =<< Addr.parse h midiport
+      x <- udpServer "127.0.0.1" 7303
+      forkIO $ loop h conn x
+      return ()
+         where loop h conn x = do m <- recvMessage x
+                                  act h conn m
+                                  loop h conn x
+               act h conn (Just (Message "/note" (note:dur:ctrls))) = 
+                   do -- print $ "Got note " ++ show note
+                      let note' = (fromJust $ d_get note) :: Int
+                          dur' = (fromJust $ d_get dur) :: Float
+                          ctrls' = (map (fromJust . d_get) ctrls) :: [Float]
+                      sendmidi latency h conn (fromIntegral note', dur') ctrls'
+                      return ()
+
+
+sendmidi latency h conn (note,dur) ctrls = 
+  do forkIO $ do threadDelay latency
+                 Event.outputDirect h $ noteOn conn note 60
+                 threadDelay (floor $ 1000000 * dur)
+                 Event.outputDirect h $ noteOff conn note
+                 return ()
+     let ctrls' = map (floor . (* 127)) ctrls
+         ctrls'' = filter ((>=0) . snd) (zip keynames ctrls')
+         --ctrls''' = map (\x -> (x, fromJust $ lookup x ctrls'')) usectrls
+     --putStrLn $ show ctrls''
+     sequence_ $ map (\(name, ctrl) -> Event.outputDirect h $ makeCtrl conn (ctrlN name ctrl)) ctrls''
+     return ()
+
+ctrlN "portamento" v    = (5, v)
+ctrlN "expression" v    = (11, v)
+ctrlN "voice" v         = (40, v)
+ctrlN "octave" v        = (41, v)
+ctrlN "detune" v        = (42, v)
+ctrlN "vcoegint" v      = (43, v)
+ctrlN "kcutoff" v        = (44, v)
+ctrlN "vcfegint" v      = (45, v)
+ctrlN "lforate" v       = (46, v)
+ctrlN "lfopitchint" v   = (47, v)
+ctrlN "lfocutoffint" v  = (48, v)
+ctrlN "attack" v        = (49, v)
+ctrlN "decay" v         = (50, v)
+ctrlN "sustain" v       = (51, v)
+ctrlN "dtime" v         = (52, v)
+ctrlN "dfeedback" v     = (53, v)
+ctrlN s _               = error $ "no match for " ++ s
+
+
+
+
+noteOn :: Connect.T -> Word8 -> Word8 -> Event.T
+noteOn conn val vel = 
+  Event.forConnection conn 
+  $ Event.NoteEv Event.NoteOn
+  $ Event.simpleNote channel
+                     (Event.Pitch (val))
+                     (Event.Velocity vel)
+
+noteOff :: Connect.T -> Word8 -> Event.T
+noteOff conn val = 
+  Event.forConnection conn 
+  $ Event.NoteEv Event.NoteOff
+  $ Event.simpleNote channel
+                     (Event.Pitch (val))
+                     (Event.normalVelocity)
+
+makeCtrl :: Connect.T -> (Word32, GHC.Int.Int32) -> Event.T
+makeCtrl conn (c, n) = 
+  Event.forConnection conn 
+  $ Event.CtrlEv Event.Controller $ Event.Ctrl 
+                                    channel 
+                                    (Event.Parameter c) 
+                                    (Event.Value n)
diff --git a/doc/tidal.md b/doc/tidal.md
--- a/doc/tidal.md
+++ b/doc/tidal.md
@@ -1,6 +1,6 @@
 <img src="https://raw2.github.com/yaxu/Tidal/master/doc/tidal.png" />
 
-# Tidal: Domain specific language for live coding of pattern 
+# Tidal: Domain specific language for live coding of pattern
 
 Homepage and mailing list: <http://yaxu.org/tidal/>
 
@@ -274,7 +274,7 @@
 
 Note the use of parenthesis around `(density 4)`, this is needed, to
 group together the function `density` with its parameter `4`, before
-being passed as a parameter to the function `every`. 
+being passed as a parameter to the function `every`.
 
 Instead of putting transformations up front, separated by the pattern
 by the `$` symbol, you can put them inside the pattern, for example:
@@ -316,7 +316,7 @@
 
 # Synth Parameters
 
-Synth parameters generate or affect sample playback. These are the 
+Synth parameters generate or affect sample playback. These are the
 synthesis parameters you can use:
 
 * `accelerate` - a pattern of numbers that speed up (or slow down) samples while they play.
@@ -325,14 +325,14 @@
 * `begin` - a pattern of numbers from 0 to 1. Skips the beginning of each sample, e.g. `0.25` to cut off the first quarter from each sample.
 * `crush` - bit crushing, a pattern of numbers from 1 for drastic reduction in bit-depth to 16 for barely no reduction.
 * `coarse` - fake-resampling, a pattern of numbers for lowering the sample rate, i.e. 1 for original 2 for half, 3 for a third and so on.
-* `cutoff` - a pattern of numbers from 0 to 1. Applies the cutoff frequency of the low-pass filter. 
+* `cutoff` - a pattern of numbers from 0 to 1. Applies the cutoff frequency of the low-pass filter.
 * `delay` - a pattern of numbers from 0 to 1. Sets the level of the delay signal.
 * `delayfeedback` - a pattern of numbers from 0 to 1. Sets the amount of delay feedback.
 * `delaytime` - a pattern of numbers from 0 to 1. Sets the length of the delay.
 * `end` - the same as `begin`, but cuts the end off samples, shortening them;
   e.g. `0.75` to cut off the last quarter of each sample.
 * `gain` - a pattern of numbers that specify volume. Values less than 1 make the sound quieter. Values greater than 1 make the sound louder.
-* `hcutoff` - a pattern of numbers from 0 to 1. Applies the cutoff frequency of the high-pass filter. 
+* `hcutoff` - a pattern of numbers from 0 to 1. Applies the cutoff frequency of the high-pass filter.
 * `hresonance` - a pattern of numbers from 0 to 1. Applies the resonance of the high-pass filter.
 * `pan` - a pattern of numbers between 0 and 1, from left to right (assuming stereo)
 * `resonance` - a pattern of numbers from 0 to 1. Applies the resonance of the low-pass filter.
@@ -576,13 +576,13 @@
 slowspread :: (a -> t -> Pattern b) -> [a] -> t -> Pattern b
 ~~~~
 
-`slowspread` takes a list of pattern transforms and applies them one at a time, per cycle, 
+`slowspread` takes a list of pattern transforms and applies them one at a time, per cycle,
 then repeats.
 
 Example:
 
 ~~~~ {.haskell}
-d1 $ slowspread ($) [density 2, rev, slow 2, striate 3, (|+| speed "0.8")] 
+d1 $ slowspread ($) [density 2, rev, slow 2, striate 3, (|+| speed "0.8")]
     $ sound "[bd*2 [~ bd]] [sn future]*2 cp jvbass*4"
 ~~~~
 
@@ -632,7 +632,7 @@
 sometimesBy :: Double -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a
 ~~~~
 
-Use `sometimesBy` to apply a given function "sometimes". For example, the 
+Use `sometimesBy` to apply a given function "sometimes". For example, the
 following code results in `density 2` being applied about 25% of the time:
 
 ~~~~ {.haskell}
@@ -756,7 +756,7 @@
 stut :: Integer -> Double -> Rational -> OscPattern -> OscPattern
 ~~~~
 
-Stut applies a type of delay to a pattern. It has three parameters, 
+Stut applies a type of delay to a pattern. It has three parameters,
 which could be called depth, feedback and time. Depth is an integer
 and the others floating point. This adds a bit of echo:
 
@@ -764,7 +764,7 @@
 d1 $ stut 4 0.5 0.2 $ sound "bd sn"
 ~~~~
 
-The above results in 4 echos, each one 50% quieter than the last, 
+The above results in 4 echos, each one 50% quieter than the last,
 with 1/5th of a cycle between them. It is possible to reverse the echo:
 
 ~~~~ {.haskell}
@@ -777,7 +777,7 @@
 trunc :: Time -> Pattern a -> Pattern a
 ~~~~
 
-Truncates a pattern so that only a fraction of the pattern is played. 
+Truncates a pattern so that only a fraction of the pattern is played.
 The following example plays only the first three quarters of the pattern:
 
 ~~~~ {.haskell}
@@ -805,7 +805,7 @@
 whenmod :: Int -> Int -> (Pattern a -> Pattern a) -> Pattern a -> Pattern a
 ~~~~
 
-`whenmod` has a similar form and behavior to `every`, but requires an 
+`whenmod` has a similar form and behavior to `every`, but requires an
 additional number. Applies the function to the pattern, when the
 remainder of the current loop number divided by the first parameter,
 is less than the second parameter.
@@ -882,35 +882,35 @@
 playing all of the patterns in the list simultaneously.
 
 ~~~~ {.haskell}
-d1 $ stack [ 
-  sound "bd bd*2", 
-  sound "hh*2 [sn cp] cp future*4", 
+d1 $ stack [
+  sound "bd bd*2",
+  sound "hh*2 [sn cp] cp future*4",
   sound (samples "arpy*8", (run 16))
 ]
 ~~~~
 
-This is useful if you want to use a transform or synth parameter on the entire 
+This is useful if you want to use a transform or synth parameter on the entire
 stack:
 
 ~~~~ {.haskell}
-d1 $ whenmod 5 3 (striate 3) $ stack [ 
-  sound "bd bd*2", 
-  sound "hh*2 [sn cp] cp future*4", 
+d1 $ whenmod 5 3 (striate 3) $ stack [
+  sound "bd bd*2",
+  sound "hh*2 [sn cp] cp future*4",
   sound (samples "arpy*8", (run 16))
 ] |+| speed "[[1 0.8], [1.5 2]*2]/3"
 ~~~~
 
 There is a similar function named `seqP` which allows you to define when
 a sound within a list starts and ends. The code below contains three
-separate patterns in a "stack", but each has different start times 
+separate patterns in a "stack", but each has different start times
 (zero cycles, eight cycles, and sixteen cycles, respectively). All
 patterns stop after 128 cycles:
 
 ~~~~ {.haskell}
-d1 $ seqP [ 
-  (0, 128, sound "bd bd*2"), 
-  (8, 128, sound "hh*2 [sn cp] cp future*4"), 
-  (16, 128, sound (samples "arpy*8", (run 16)))
+d1 $ seqP [
+  (0, 128, sound "bd bd*2"),
+  (8, 128, sound "hh*2 [sn cp] cp future*4"),
+  (16, 128, sound (samples "arpy*8" (run 16)))
 ]
 ~~~~
 
@@ -933,6 +933,117 @@
 
 ~~~~ {.haskell}
 d1 $ slow 32 $ jux ((|+| speed "0.5") . rev) $ striate' 32 (1/16) $ sound "bev"
+~~~~
+
+
+# Quantization, event shifting, and patterns as sequences
+
+Patterns are not _really_ sequences, but sometimes you may wish they
+were. The functions `preplace` and `protate` let you treat them as
+sequences.
+
+Tidal patterns are formally infinite, but usually they repeat after some
+time (which is often some integer multiple of cycles). Since a pattern does not
+know how long it is, you'll have to specify. Each function comes with a
+companion operator which assumes your pattern repeats after one cycle. This
+is the case with pattern literals (i.e., `sound "bd bd"` is always a
+one cycle pattern).
+
+## preplace and `<~>`
+
+~~~~ {.haskell}
+preplace :: (Time, Time) -> Pattern a -> Pattern a -> Pattern a
+~~~~
+
+The `preplace` (shorthand `prep`) function combines the timing of one
+pattern (the trigger pattern) with event values of another (the sequence
+pattern). It does so by replacing the trigger pattern event values with
+values, repeating the latter until it has enough. Note that it does not
+matter what the values of the trigger pattern are, but the patterns must be
+of the same _type_.
+
+Additionally, `preplace` takes a `(Time, Time)` tuple of pattern
+lengths (doesn't have to be correct). If you are fine with constraining this
+effect to a single cycle you can use `<~>`:
+
+~~~~ {.haskell}
+(<~>) :: Pattern a -> Pattern a -> Pattern a
+~~~~
+
+Example replacements (`=>` means "becomes"):
+
+~~~~ {.haskell}
+"x x" <~> "bd"          => "bd bd"
+"x" <~> "bd sn"         => "bd"
+"x x ~ x" <~> "bd sn"   => "bd sn ~ bd sn bd ~ sn"
+"x(3,8)" <~> "bd"       => "bd ~ ~ bd ~ ~ b ~"
+"x(3,8)" <~> "bd bd sn" => "bd ~ ~ bd ~ ~ sn ~"
+
+preplace (1,1) "x [~ x] x x" "bd sn"
+  => "bd {~ sn] bd sn"
+
+preplace (1,3) "x x x ~" $ samples "bd sn" $ slow 3 $ run 3
+  => "bd:0 sn:0 bd:1 ~ sn:1  bd:2 sn:2 ~ bd:3  ..."
+~~~~
+
+Any pattern will do:
+
+~~~~ {.haskell}
+d1 $ sound "[jvbass jvbass:5]*3" |+| (shape $ "1 1 1 1 1" <~> "0.2 0.9")
+~~~~
+
+## preplaceWith (prw)
+
+`preplaceWith` (shorthand `prw`) is similar to `replace` but takes an
+additional combinator function:
+
+~~~~ {.haskell}
+preplaceWith :: (a -> b -> c) -> (Time, Time) -> Pattern a -> Pattern b -> Pattern c
+preplaceWith1 :: (a -> b -> c) -> Pattern a -> Pattern b -> Pattern c
+
+prw = preplaceWith
+prw1 = preplaceWith1
+~~~~
+
+`preplaceWith1` assumes both patterns are of length 1. Also note that
+patterns don't have to be of the same type anymore.
+
+This can be quite powerful:
+
+~~~~ {.haskell}
+prw1 (+) "1 2 3" "2"    => "3 4 5"
+prw1 (+) "1 2 3" "1 -1" => "2 1 4 0 3 2"
+~~~~
+
+## protate and `<<~`/`~>>`
+
+~~~~ {.haskell}
+protate :: Time -> Int -> Pattern a -> Pattern a
+protate len rot p = ...
+~~~~
+
+The `protate` (shorthand `prot`) function takes a pattern of length `len`
+and shifts the events `rot` times to the left without disturbing the time signature.
+
+~~~~ {.haskell}
+prot 1 "1 2 ~ 3"                => "2 3 ~ 1"
+1 <<~ "1 2 ~ 3"                 => "2 3 ~ 1"
+1 ~>> "1 2 ~ 3"                 => "3 1 ~ 2"
+2 ~>> "[bd bd] sn ~ hh [~ can]" => "[hh can] bd ~ bd [~ sn]"
+~~~~
+
+## prr and prrw
+
+All the above functions rely on `prrw` (pattern replate rotate with) and its
+alias `prr`. If you find yourself having to do combinations of the above,
+you can use these directly.
+
+~~~~ {.haskell}
+-- | @prr rot (blen, vlen) beatPattern valuePattern@: pattern rotate/replace.
+prrw :: (a -> b -> c) -> Int -> (Time, Time) -> Pattern a -> Pattern b -> Pattern c
+prrw f rot (blen, vlen) beatPattern valuePattern = ...
+
+prr :: Int -> (Time, Time) -> Pattern a -> Pattern a -> Pattern a
 ~~~~
 
 # Plus more to be discovered!
diff --git a/tidal.cabal b/tidal.cabal
--- a/tidal.cabal
+++ b/tidal.cabal
@@ -1,5 +1,5 @@
 name:                tidal
-version:         0.4.30
+version:         0.4.31
 synopsis:            Pattern language for improvised music
 -- description:         
 homepage:            http://tidal.lurk.org/
@@ -28,5 +28,6 @@
                        Sound.Tidal.Context
                        Sound.Tidal.Utils
                        Sound.Tidal.SuperCollider
+                       Sound.Tidal.VolcaKeys
 
-  Build-depends: base < 5, process, parsec, hosc > 0.13, hashable, colour, containers, time, websockets > 0.8, text, mtl >=2.1, transformers, mersenne-random-pure64,binary, bytestring, hmt
+  Build-depends: base < 5, process, parsec, hosc > 0.13, hashable, colour, containers, time, websockets > 0.8, text, mtl >=2.1, transformers, mersenne-random-pure64,binary, bytestring, hmt, alsa-seq, alsa-core
diff --git a/tidal.el b/tidal.el
--- a/tidal.el
+++ b/tidal.el
@@ -72,8 +72,8 @@
   (tidal-send-string "d8 <- dirtStream")
   (tidal-send-string "d9 <- dirtStream")
   (tidal-send-string "d10 <- dirtStream")
-;  (tidal-send-string "k1 <- keyStream")
-;  (tidal-send-string "keyproxy 150000 \"24:0\"")
+  (tidal-send-string "k1 <- keyStream")
+  (tidal-send-string "keyproxy 150000 \"24:0\"")
   (tidal-send-string "(cps, getNow) <- bpsUtils")
   (tidal-send-string "let bps x = cps (x/2)")
 ;  (tidal-send-string "let hush = mapM_ ($ silence) [d1,d2,d3,d4,d5,d6,d7,d8,d9,d10,k1]")
