diff --git a/BootTidal.hs b/BootTidal.hs
--- a/BootTidal.hs
+++ b/BootTidal.hs
@@ -77,3 +77,5 @@
 
 :set prompt "tidal> "
 :set prompt-cont ""
+
+default (Pattern String, Integer, Double)
diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,39 @@
 # TidalCycles log of changes
 
+## 1.7.9 - Tama
+
+### What's Changed
+* Show lists of values by @polymorphicengine in https://github.com/tidalcycles/Tidal/pull/838
+* Port listener wiki content as README by @ndr-brt in https://github.com/tidalcycles/Tidal/pull/851
+* Chords - consistent naming cont'd by @cleary in https://github.com/tidalcycles/Tidal/pull/840
+* Formalise pattern IDs by @mindofmatthew in https://github.com/tidalcycles/Tidal/pull/807
+* Switch to non-blocking pMap updates for transitions by @bgold-cosmos in https://github.com/tidalcycles/Tidal/pull/858
+* Add listener build workflows by @ndr-brt in https://github.com/tidalcycles/Tidal/pull/852
+* Fix tidal-listener install process by @ndr-brt in https://github.com/tidalcycles/Tidal/pull/861
+* Fix mod by @bgold-cosmos in https://github.com/tidalcycles/Tidal/pull/860
+* Fix stack ci using last lts resolver by @ndr-brt in https://github.com/tidalcycles/Tidal/pull/862
+* Make Pattern instance Monoid by @fbous in https://github.com/tidalcycles/Tidal/pull/865
+* Added OSC playback control for silencing individual patterns by @njanssen in https://github.com/tidalcycles/Tidal/pull/863
+* Use better show instances for notes & rationals by @mindofmatthew in https://github.com/tidalcycles/Tidal/pull/857
+* tidal-listener: Separate build from release by @ndr-brt in https://github.com/tidalcycles/Tidal/pull/866
+* Exponential double by @ndr-brt in https://github.com/tidalcycles/Tidal/pull/871
+* Comments by @JeffreyBenjaminBrown in https://github.com/tidalcycles/Tidal/pull/877
+* Fix for classic dirt support by @yaxu https://github.com/tidalcycles/Tidal/commit/15b5b8b91af08ebad39efe2a7e0712b21f606ca4
+* New alias `number` for `n` by @yaxu https://github.com/tidalcycles/Tidal/commit/ac0be63d686ab37f7b2dcd440d4bd8f3898453e8
+* Allow "0..8" to be parsed as a range of doubles in mininotation by @yaxu https://github.com/tidalcycles/Tidal/commit/55f8ad9b0091b43fdd364eced25bc9c655d157cc
+* Bugfix for `timeLoop` by @yaxu in https://github.com/tidalcycles/Tidal/commit/29f28ed637a7c17ad2b22558d097a694da604e2d
+* Retire pre ghc 8.4 support by @yaxu 
+* Pattern first parameter of `wedge` by @yaxu
+* Allow `|**|` et al to work on control patterns by @yaxu in https://github.com/tidalcycles/Tidal/commit/7142775c2039cae4cde9bdd6f68b3e0cbae2de9d
+* Add alias `timecat` for timeCat` by @yaxu in https://github.com/tidalcycles/Tidal/commit/363889bdc963d9357daf1893d18ab9dfc33ca5ac
+
+### New Contributors
+* @fbous made their first contribution in https://github.com/tidalcycles/Tidal/pull/865
+* @njanssen made their first contribution in https://github.com/tidalcycles/Tidal/pull/863
+* @JeffreyBenjaminBrown made their first contribution in https://github.com/tidalcycles/Tidal/pull/877
+
+**Full Changelog**: https://github.com/tidalcycles/Tidal/compare/1.7.8...1.7.9
+
 ## 1.7.8 - Ayatakedai
    * Add pattern id to patterns as _id_, and send it with /code/highlights
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -7,8 +7,8 @@
 For documentation, mailing list and more info see here:  
   https://tidalcycles.org/
 
-You can help speed up Tidal development by sending coffee here:  
-  https://ko-fi.com/yaxulive#
+You can help speed up Tidal development by contributing to the collective fund here:  
+  https://opencollective.com/tidalcycles
 
 (c) Alex McLean and contributors, 2021
 
diff --git a/bench/Memory/Tidal/Inputs.hs b/bench/Memory/Tidal/Inputs.hs
--- a/bench/Memory/Tidal/Inputs.hs
+++ b/bench/Memory/Tidal/Inputs.hs
@@ -1,38 +1,45 @@
 {-# LANGUAGE OverloadedStrings #-}
 
-module Tidal.Inputs where 
+module Tidal.Inputs where
 
 import Sound.Tidal.Pattern
 import Sound.Tidal.Core
-import Sound.Tidal.ParseBP
-import Sound.Tidal.Core
+import Sound.Tidal.ParseBP()
 import Sound.Tidal.Params
 import Sound.Tidal.Control
 import Sound.Tidal.UI
-import Weigh 
+import Weigh
 
-columns :: Weigh () 
-columns = setColumns [Case, Allocated, Max, Live, GCs] 
+columns :: Weigh ()
+columns = setColumns [Case, Allocated, Max, Live, GCs]
 
-{- Pattern inputs -} 
-xs3 = [1..10^3]
-xs4 = [1..10^4]
-xs5 = [1..10^5]
-xs6 = [1..10^6]
+{- Pattern inputs -}
+xs3 :: [Time]
+xs3 = [1..10000]
 
+xs4 :: [Time]
+xs4 = [1..100000]
+
+xs5 :: [Time]
+xs5 = [1..1000000]
+
+xs6 :: [Time]
+xs6 = [1..10000000]
+
+xsA :: [Time]
 xsA = [500000..1500000]
 
 catPattSmall :: [Pattern Time]
 catPattSmall = pure <$> xs3
 
 catPattMed :: [Pattern Time]
-catPattMed = pure <$> xs4 
+catPattMed = pure <$> xs4
 
 catPattMedB :: [Pattern Time]
 catPattMedB = pure <$> xs5
 
 catPattBig :: [Pattern Time]
-catPattBig = pure <$> xs6 
+catPattBig = pure <$> xs6
 
 timeCatMed :: [(Time, Pattern Time)]
 timeCatMed = zip xs5 catPattMed
@@ -41,23 +48,26 @@
 timeCatBig = zip xs6 catPattBig
 
 appendBig :: [Pattern Time]
-appendBig = pure <$> xsA 
+appendBig = pure <$> xsA
 
 pattApp1 :: Pattern [Time]
-pattApp1 = sequence catPattBig 
+pattApp1 = sequence catPattBig
 
 pattApp2 :: Pattern [Time]
 pattApp2 = sequence appendBig
 
 {- Arc Inputs -}
-arcFunc :: Arc -> Arc 
-arcFunc (Arc s e) = Arc (s * 2) (e * 4) 
+arcFunc :: Arc -> Arc
+arcFunc (Arc st en) = Arc (st * 2) (en * 4)
 
-wqaMed = fromList xs5 
+wqaMed :: Pattern Time
+wqaMed = fromList xs5
+
+wqaBig :: Pattern Time
 wqaBig = fromList xs6
 
-{- fix inputs -} 
-fixArg1 :: ControlPattern 
+{- fix inputs -}
+fixArg1 :: ControlPattern
 fixArg1 = pF "cc64" 1
 
 fixArg2 :: ControlPattern
@@ -76,8 +86,8 @@
     #  pF "cc69" (cF 0 "69")
 
 {- Euclid inputs -}
-ecA1 :: [Pattern Int] 
-ecA1 = [1, 10^5]
+ecA1 :: [Pattern Int]
+ecA1 = [1, 1000000]
 
-ecA2 :: Pattern String 
+ecA2 :: Pattern String
 ecA2 = "x"
diff --git a/bench/Speed/Tidal/Inputs.hs b/bench/Speed/Tidal/Inputs.hs
--- a/bench/Speed/Tidal/Inputs.hs
+++ b/bench/Speed/Tidal/Inputs.hs
@@ -1,34 +1,40 @@
 {-# LANGUAGE OverloadedStrings #-}
 
-module Tidal.Inputs where 
+module Tidal.Inputs where
 
 import Sound.Tidal.Pattern
 import Sound.Tidal.Core
-import Sound.Tidal.ParseBP
-import Sound.Tidal.Core
+import Sound.Tidal.ParseBP()
 import Sound.Tidal.Params
-import Sound.Tidal.Control
 import Sound.Tidal.UI
 
-{- Pattern inputs -} 
-xs3 = [1..10^3]
-xs4 = [1..10^4]
-xs5 = [1..10^5]
-xs6 = [1..10^6]
+{- Pattern inputs -}
+xs3 :: [Time]
+xs3 = [1..10000]
 
+xs4 :: [Time]
+xs4 = [1..100000]
+
+xs5 :: [Time]
+xs5 = [1..1000000]
+
+xs6 :: [Time]
+xs6 = [1..10000000]
+
+xsA :: [Time]
 xsA = [500000..1500000]
 
 catPattSmall :: [Pattern Time]
 catPattSmall = pure <$> xs3
 
 catPattMed :: [Pattern Time]
-catPattMed = pure <$> xs4 
+catPattMed = pure <$> xs4
 
 catPattMedB :: [Pattern Time]
 catPattMedB = pure <$> xs5
 
 catPattBig :: [Pattern Time]
-catPattBig = pure <$> xs6 
+catPattBig = pure <$> xs6
 
 timeCatMed :: [(Time, Pattern Time)]
 timeCatMed = zip xs5 catPattMed
@@ -37,32 +43,32 @@
 timeCatBig = zip xs6 catPattBig
 
 appendBig :: [Pattern Time]
-appendBig = pure <$> xsA 
+appendBig = pure <$> xsA
 
 pattApp1 :: Pattern [Time]
-pattApp1 = sequence catPattBig 
+pattApp1 = sequence catPattBig
 
 pattApp2 :: Pattern [Time]
 pattApp2 = sequence appendBig
 
 {- Arc Inputs -}
-arcFunc :: Arc -> Arc 
-arcFunc (Arc s e) = Arc (s * 2) (e * 4) 
+arcFunc :: Arc -> Arc
+arcFunc (Arc st en) = Arc (st * 2) (en * 4)
 
 wqaMed :: Pattern Time
-wqaMed = fromList xs5 
+wqaMed = fromList xs5
 
 wqaBig :: Pattern Time
 wqaBig = fromList xs6
 
-{- fix inputs -} 
-fixArg1 :: ControlPattern 
+{- fix inputs -}
+fixArg1 :: ControlPattern
 fixArg1 = pF "cc64" 1
 
 fixArg2 :: ControlPattern
 fixArg2 =
       fix ( # crush 4 ) (pF "cc65" 1)
-    $ fix ( stut' 4 (0.125/4) ( + up "1" )) (pF "cc66" 1)
+    -- $ fix ( stut' 4 (0.125/4) ( + up "1" )) (pF "cc66" 1)
     $ fix ( |*| speed "-1" ) (pF "cc67" 1)
     $ fix ( (# delaytime 0.125).(# delay 0.5)) (pF "cc68" 1)
     $ fix ( # coarse 12) (pF "cc69" 1)
@@ -75,8 +81,8 @@
     #  pF "cc69" (cF 0 "69")
 
 {- Euclid inputs -}
-ecA1 :: [Pattern Int] 
+ecA1 :: [Pattern Int]
 ecA1 = [1, 100]
 
-ecA2 :: Pattern String 
+ecA2 :: Pattern String
 ecA2 = "x"
diff --git a/src/Sound/Tidal/Chords.hs b/src/Sound/Tidal/Chords.hs
--- a/src/Sound/Tidal/Chords.hs
+++ b/src/Sound/Tidal/Chords.hs
@@ -115,16 +115,16 @@
 sevenFlat10 = [0,4,7,10,15]
 nineSharp5 :: Num a => [a]
 nineSharp5 = [0,1,13]
-m9sharp5 :: Num a => [a]
-m9sharp5 = [0,1,14]
+minor9sharp5 :: Num a => [a]
+minor9sharp5 = [0,1,14]
 sevenSharp5flat9 :: Num a => [a]
 sevenSharp5flat9 = [0,4,8,10,13]
-m7sharp5flat9 :: Num a => [a]
-m7sharp5flat9 = [0,3,8,10,13]
+minor7sharp5flat9 :: Num a => [a]
+minor7sharp5flat9 = [0,3,8,10,13]
 elevenSharp :: Num a => [a]
 elevenSharp = [0,4,7,10,14,18]
-m11sharp :: Num a => [a]
-m11sharp = [0,3,7,10,14,18]
+minor11sharp :: Num a => [a]
+minor11sharp = [0,3,7,10,14,18]
 
 -- | @chordate cs m n@ selects the @n@th "chord" (a chord is a list of Ints)
 -- from a list of chords @cs@ and transposes it by @m@
@@ -141,6 +141,7 @@
 chordTable :: Num a => [(String, [a])]
 chordTable = [("major", major),
               ("maj", major),
+              ("M", major),
               ("aug", aug),
               ("plus", aug),
               ("sharp5", aug),
@@ -178,6 +179,7 @@
               ("13", thirteen),
               ("minor", minor),
               ("min", minor),
+              ("m", minor),
               ("diminished", diminished),
               ("dim", diminished),
               ("minorSharp5", minorSharp5),
@@ -194,22 +196,30 @@
               ("mSixNine", minorSixNine),
               ("m6by9", minorSixNine),
               ("minor7flat5", minor7flat5),
+              ("minor7f5", minor7flat5),
               ("min7flat5", minor7flat5),
+              ("min7f5", minor7flat5),
               ("m7flat5", minor7flat5),
               ("m7f5", minor7flat5),
               ("minor7", minor7),
               ("min7", minor7),
               ("m7", minor7),
               ("minor7sharp5", minor7sharp5),
+              ("minor7s5", minor7sharp5),
               ("min7sharp5", minor7sharp5),
+              ("min7s5", minor7sharp5),
               ("m7sharp5", minor7sharp5),
               ("m7s5", minor7sharp5),
               ("minor7flat9", minor7flat9),
+              ("minor7f9", minor7flat9),
               ("min7flat9", minor7flat9),
+              ("min7f9", minor7flat9),
               ("m7flat9", minor7flat9),
               ("m7f9", minor7flat9),
               ("minor7sharp9", minor7sharp9),
+              ("minor7s9", minor7sharp9),
               ("min7sharp9", minor7sharp9),
+              ("min7s9", minor7sharp9),
               ("m7sharp9", minor7sharp9),
               ("m7s9", minor7sharp9),
               ("diminished7", diminished7),
@@ -239,16 +249,23 @@
               ("sevenFlat10", sevenFlat10),
               ("7f10", sevenFlat10),
               ("nineSharp5", nineSharp5),
+              ("9sharp5", nineSharp5),
               ("9s5", nineSharp5),
-              ("m9sharp5", m9sharp5),
-              ("m9s5", m9sharp5),
+              ("minor9sharp5", minor9sharp5),
+              ("minor9s5", minor9sharp5),
+              ("min9sharp5", minor9sharp5),
+              ("min9s5", minor9sharp5),
+              ("m9sharp5", minor9sharp5),
+              ("m9s5", minor9sharp5),
               ("sevenSharp5flat9", sevenSharp5flat9),
               ("7s5f9", sevenSharp5flat9),
-              ("m7sharp5flat9", m7sharp5flat9),
+              ("minor7sharp5flat9", minor7sharp5flat9),
+              ("m7sharp5flat9", minor7sharp5flat9),
               ("elevenSharp", elevenSharp),
               ("11s", elevenSharp),
-              ("m11sharp", m11sharp),
-              ("m11s", m11sharp)
+              ("minor11sharp", minor11sharp),
+              ("m11sharp", minor11sharp),
+              ("m11s", minor11sharp)
              ]
 
 chordL :: Num a => Pattern String -> Pattern [a]
diff --git a/src/Sound/Tidal/Core.hs b/src/Sound/Tidal/Core.hs
--- a/src/Sound/Tidal/Core.hs
+++ b/src/Sound/Tidal/Core.hs
@@ -179,12 +179,12 @@
 ( -|) :: Num a => Pattern a -> Pattern a -> Pattern a
 a  -| b = (-) <$> a *> b
 
-(|%|) :: (Applicative a, Real b) => a b -> a b -> a b
-a |%| b = mod' <$> a <*> b
-(|% ) :: Real a => Pattern a -> Pattern a -> Pattern a
-a |%  b = mod' <$> a <* b
-( %|) :: Real a => Pattern a -> Pattern a -> Pattern a
-a  %| b = mod' <$> a *> b
+(|%|) :: (Applicative a, Moddable b) => a b -> a b -> a b
+a |%| b = gmod <$> a <*> b
+(|% ) :: Moddable a => Pattern a -> Pattern a -> Pattern a
+a |%  b = gmod <$> a <* b
+( %|) :: Moddable a => Pattern a -> Pattern a -> Pattern a
+a  %| b = gmod <$> a *> b
 
 (|**|) :: (Applicative a, Floating b) => a b -> a b -> a b
 a |**| b = (**) <$> a <*> b
@@ -292,6 +292,7 @@
 fastCat :: [Pattern a] -> Pattern a
 fastCat ps = _fast (toTime $ length ps) $ cat ps
 
+-- | Alias for @fastCat@
 fastcat :: [Pattern a] -> Pattern a
 fastcat = fastCat
 
@@ -303,21 +304,19 @@
           arrange _ [] = []
           arrange t ((t',p):tps') = (t,t+t',p) : arrange (t+t') tps'
 
+-- | Alias for @timeCat@
+timecat :: [(Time, Pattern a)] -> Pattern a
+timecat = timeCat
+
 -- | 'overlay' combines two 'Pattern's into a new pattern, so that
 -- their events are combined over time. 
 overlay :: Pattern a -> Pattern a -> Pattern a
-overlay !p !p' = Pattern $ \st -> query p st ++ query p' st
-
--- | An infix alias of @overlay@
-(<>) :: Pattern a -> Pattern a -> Pattern a
-(<>) = overlay
+overlay = (<>)
 
 -- | 'stack' combines a list of 'Pattern's into a new pattern, so that
 -- their events are combined over time.
 stack :: [Pattern a] -> Pattern a
 stack = foldr overlay silence
-
-
 
 -- ** Manipulating time
 
diff --git a/src/Sound/Tidal/ID.hs b/src/Sound/Tidal/ID.hs
new file mode 100644
--- /dev/null
+++ b/src/Sound/Tidal/ID.hs
@@ -0,0 +1,40 @@
+module Sound.Tidal.ID (ID(..)) where
+
+{-
+    ID.hs - Polymorphic pattern identifiers
+    Copyright (C) 2020, Alex McLean and contributors
+
+    This library is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This library is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this library.  If not, see <http://www.gnu.org/licenses/>.
+-}
+
+import GHC.Exts ( IsString(..) )
+
+-- | Wrapper for literals that can be coerced to a string and used as an identifier.
+-- | Similar to Show typeclass, but constrained to strings and integers and designed
+-- | so that similar cases (such as 1 and "1") convert to the same value.
+newtype ID = ID { fromID :: String }
+
+noOv :: String -> a
+noOv meth = error $ meth ++ ": not supported for ids"
+
+instance Num ID where
+  fromInteger = ID . show
+  (+) = noOv "+"
+  (*) = noOv "*"
+  abs = noOv "abs"
+  signum = noOv "signum"
+  (-) = noOv "-"
+
+instance IsString ID where
+  fromString = ID
diff --git a/src/Sound/Tidal/Params.hs b/src/Sound/Tidal/Params.hs
--- a/src/Sound/Tidal/Params.hs
+++ b/src/Sound/Tidal/Params.hs
@@ -3128,6 +3128,9 @@
 ohdecayrecv :: Pattern Int -> ControlPattern
 ohdecayrecv = ophatdecayrecv
 
+number :: Pattern Note -> ControlPattern
+number = n
+
 lsn :: Pattern Double -> ControlPattern
 lsn = lsnare
 lsnbus :: Pattern Int -> Pattern Double -> ControlPattern
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
@@ -177,7 +177,10 @@
     toE (Right tp) = toPat tp
 
 parseTPat :: Parseable a => String -> Either ParseError (TPat a)
-parseTPat = parseRhythm tPatParser
+parseTPat = runParser (pSequence f' Prelude.<* eof) (0 :: Int) ""
+  where f' = do tPatParser
+             <|> do symbol "~" <?> "rest"
+                    return TPat_Silence
 
 cP :: (Enumerable a, Parseable a) => String -> Pattern a
 cP s = innerJoin $ parseBP_E <$> _cX_ getS s
@@ -321,19 +324,11 @@
          <|> return Positive
 
 intOrFloat :: MyParser Double
-intOrFloat = do num <- naturalOrFloat
-                return (case num of
-                  Right x -> x
-                  Left  x -> fromIntegral x
-                  )
-
-parseRhythm :: Parseable a => MyParser (TPat a) -> String -> Either ParseError (TPat a)
-parseRhythm f = runParser (pSequence f' Prelude.<* eof) (0 :: Int) ""
-  where f' = do f
-                <|> do symbol "~" <?> "rest"
-                       return TPat_Silence
+intOrFloat = do -- use 'try' to avoid consuming the first '.' in a '..' range.
+                try float
+                <|> fromIntegral <$> integer
 
-pSequence :: Parseable a => MyParser (TPat a) -> GenParser Char Int (TPat a)
+pSequence :: Parseable a => MyParser (TPat a) -> MyParser (TPat a)
 pSequence f = do
   spaces
   s <- many $ do
@@ -439,11 +434,11 @@
 
 pDouble :: MyParser (TPat Double)
 pDouble = wrapPos $ do s <- sign
-                       f <- choice [fromRational <$> pRatio, intOrFloat, parseNote] <?> "float"
+                       f <- choice [fromRational <$> pRatio, parseNote] <?> "float"
                        let v = applySign s f
                        do TPat_Stack . map (TPat_Atom Nothing . (+ v)) <$> parseChord
                          <|> return (TPat_Atom Nothing v)
-                      <|>
+                       <|>
                          do TPat_Stack . map (TPat_Atom Nothing) <$> parseChord
 
 pNote :: MyParser (TPat Note)
@@ -468,8 +463,6 @@
                   if isInt d
                     then return $ applySign s $ round d
                     else fail "not an integer"
-                where
-                  isInt x = x == fromInteger (round x)
 
 pIntegral :: Integral a => MyParser (TPat a)
 pIntegral = wrapPos $ do i <- parseIntNote
@@ -569,38 +562,37 @@
 pRatio :: MyParser Rational
 pRatio = do 
   s <- sign
-  r <- do n <- read <$> many1 digit
-          result <- do char '%'
-                       d <- decimal
-                       return (n%d)
-                    <|>
-                    do char '.'
-                       frac <- many1 digit
-                       -- A hack, but not sure if doing this
-                       -- numerically would be any faster..
-                       return (toRational ((read $ show n ++ "." ++ frac) :: Double))
-                    <|>
-                    return (n%1)
-          c <- pRatioChar <|> return 1
-          return (result * c)
-        <|> 
-        pRatioChar
+  r <- do n <- try intOrFloat
+          v <- pFraction n <|> return (toRational n)
+          r <- pRatioChar <|> return 1
+          return (v * r)
+       <|>
+       pRatioChar
   return $ applySign s r
 
+pFraction :: RealFrac a => a -> MyParser Rational
+pFraction n = do
+  char '%'
+  d <- integer
+  if (isInt n)
+    then return ((round n) % d)
+    else fail "fractions need int numerator and denominator"
+
 pRatioChar :: Fractional a => MyParser a
-pRatioChar = do char 'w';
-                return 1
-             <|> do char 'h'
-                    return 0.5
-             <|> do char 'q'
-                    return 0.25
-             <|> do char 'e'
-                    return 0.125
-             <|> do char 's'
-                    return 0.0625
-             <|> do char 't'
-                    return (1/3)
-             <|> do char 'f'
-                    return 0.2
-             <|> do char 'x'
-                    return (1/6)
+pRatioChar = pRatioSingleChar 'w' 1
+             <|> pRatioSingleChar 'h' 0.5
+             <|> pRatioSingleChar 'q' 0.25
+             <|> pRatioSingleChar 'e' 0.125
+             <|> pRatioSingleChar 's' 0.0625
+             <|> pRatioSingleChar 't' (1/3)
+             <|> pRatioSingleChar 'f' 0.2
+             <|> pRatioSingleChar 'x' (1/6)
+
+pRatioSingleChar :: Fractional a => Char -> a -> MyParser a
+pRatioSingleChar c v = try $ do
+  char c
+  notFollowedBy (letter)
+  return v
+
+isInt :: RealFrac a => a -> Bool
+isInt x = x == fromInteger (round x)
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
@@ -1,3 +1,4 @@
+{-# LANGUAGE BangPatterns #-}
 {-# LANGUAGE FlexibleInstances #-}
 {-# LANGUAGE DeriveGeneric #-}
 {-# LANGUAGE DeriveDataTypeable #-}
@@ -40,6 +41,7 @@
 import           Data.Word (Word8)
 import           Data.Data (Data) -- toConstr
 import           Data.Typeable (Typeable)
+import           Data.Fixed (mod')
 
 import           Sound.Tidal.Time
 
@@ -230,6 +232,12 @@
   enumFromTo     = noOv "enumFromTo"
   enumFromThenTo = noOv "enumFromThenTo"
 
+instance Monoid (Pattern a) where
+  mempty = empty
+
+instance Semigroup (Pattern a) where
+  (<>) !p !p' = Pattern $ \st -> query p st ++ query p' st
+
 instance (Num a, Ord a) => Real (Pattern a) where
   toRational = noOv "toRational"
 
@@ -297,6 +305,35 @@
   recip        = fmap (applyFIS recip id id)
   fromRational r = Map.singleton "speed" $ VF (fromRational r)
 
+class Moddable a where
+  gmod :: a -> a -> a
+
+instance Moddable Double where
+  gmod = mod'
+instance Moddable Rational where
+  gmod = mod'
+instance Moddable Note where
+  gmod (Note a) (Note b) = Note (mod' a b)
+instance Moddable Int where
+  gmod = mod
+instance Moddable ValueMap where
+  gmod = Map.unionWith (fNum2 mod mod')
+
+instance Floating ValueMap
+  where pi = noOv "pi"
+        exp _ = noOv "exp"
+        log _ = noOv "log"
+        sin _ = noOv "sin"
+        cos _ = noOv "cos"
+        asin _ = noOv "asin"
+        acos _ = noOv "acos"
+        atan _ = noOv "atan"
+        sinh _ = noOv "sinh"
+        cosh _ = noOv "cosh"
+        asinh _ = noOv "asinh"
+        acosh _ = noOv "acosh"
+        atanh _ = noOv "atanh"
+
 ------------------------------------------------------------------------
 -- * Internal functions
 
@@ -471,11 +508,39 @@
 withContext :: (Context -> Context) -> Pattern a -> Pattern a
 withContext f pat = withEvents (map (\e -> e {context = f $ context e})) pat
 
-deltaContext :: Int -> Int -> Pattern a -> Pattern a
-deltaContext column line pat = withEvents (map (\e -> e {context = f $ context e})) pat
-  where f :: Context -> Context
-        f (Context xs) = Context $ map (\((bx,by), (ex,ey)) -> ((bx+column,by+line), (ex+column,ey+line))) xs
+-- A hack to add to manipulate source code to add calls to
+-- 'deltaContext' around strings, so events from mininotation know
+-- where they are within a whole tidal pattern
+deltaMini :: String -> String
+deltaMini = outside 0 0
+  where outside :: Int -> Int -> String -> String
+        outside _ _ [] = []
+        outside column line ('"':xs) = "(deltaContext "
+                                         ++ show column
+                                         ++ " "
+                                         ++ show line
+                                         ++ " \""
+                                         ++ inside (column+1) line xs
+        outside _ line ('\n':xs) = '\n':outside 0 (line+1) xs
+        outside column line (x:xs) = x:outside (column+1) line xs
+        inside :: Int -> Int -> String -> String
+        inside _ _ [] = []
+        inside column line ('"':xs) = '"':')':outside (column+1) line xs
+        inside _ line ('\n':xs) = '\n':inside 0 (line+1) xs
+        inside column line (x:xs) = x:inside (column+1) line xs
 
+class Stringy a where
+  deltaContext :: Int -> Int -> a -> a
+
+instance Stringy (Pattern a) where
+  deltaContext column line pat = withEvents (map (\e -> e {context = f $ context e})) pat
+    where f :: Context -> Context
+          f (Context xs) = Context $ map (\((bx,by), (ex,ey)) -> ((bx+column,by+line), (ex+column,ey+line))) xs
+
+-- deltaContext on an actual (non overloaded) string is a no-op
+instance Stringy String where
+  deltaContext _ _ = id
+
 -- ** Events
 
 -- | Some context for an event, currently just position within sourcecode
@@ -604,8 +669,16 @@
 type ValueMap = Map.Map String Value
 
 -- | Note is Double, but with a different parser
-newtype Note = Note { unNote :: Double } deriving (Typeable, Data, Generic, Eq, Ord, Show, Enum, Num, Fractional, Floating, Real)
+newtype Note = Note { unNote :: Double } deriving (Typeable, Data, Generic, Eq, Ord, Enum, Num, Fractional, Floating, Real)
 instance NFData Note
+
+instance Show Note where
+  show n = (show . unNote $ n) ++ "n (" ++ pitchClass ++ octave ++ ")"
+    where
+      pitchClass = pcs !! mod noteInt 12
+      octave = show $ div noteInt 12 + 5
+      noteInt = round . unNote $ n
+      pcs = ["c", "cs", "d", "ds", "e", "f", "fs", "g", "gs", "a", "as", "b"]
 
 instance Valuable String where
   toValue a = VS a
diff --git a/src/Sound/Tidal/Show.hs b/src/Sound/Tidal/Show.hs
--- a/src/Sound/Tidal/Show.hs
+++ b/src/Sound/Tidal/Show.hs
@@ -78,13 +78,13 @@
   show (VS s)  = ('"':s) ++ "\""
   show (VI i)  = show i
   show (VF f)  = show f ++ "f"
-  show (VN n)  = show n ++ "n"
-  show (VR r)  = show r ++ "r"
+  show (VN n)  = show n
+  show (VR r)  = prettyRat r ++ "r"
   show (VB b)  = show b
   show (VX xs) = show xs
   show (VPattern pat) = "(" ++ show pat ++ ")"
   show (VState f) = show $ f Map.empty
-  show (VList _) =  "<list>"
+  show (VList vs) = show $ map show vs
 
 instance {-# OVERLAPPING #-} Show ValueMap where
   show m = intercalate ", " $ map (\(name, v) -> name ++ ": " ++ show v) $ Map.toList m
diff --git a/src/Sound/Tidal/Stream.hs b/src/Sound/Tidal/Stream.hs
--- a/src/Sound/Tidal/Stream.hs
+++ b/src/Sound/Tidal/Stream.hs
@@ -36,6 +36,7 @@
 
 import           Sound.Tidal.Config
 import           Sound.Tidal.Core (stack, silence, (#))
+import           Sound.Tidal.ID
 import           Sound.Tidal.Params (pS)
 import           Sound.Tidal.Pattern
 import qualified Sound.Tidal.Tempo as T
@@ -150,9 +151,7 @@
                     }
 
 dirtShape :: OSC
-dirtShape = OSC "/play" $ ArgList [("sec", iDefault 0),
-                                   ("usec", iDefault 0),
-                                   ("cps", fDefault 0),
+dirtShape = OSC "/play" $ ArgList [("cps", fDefault 0),
                                    ("s", required),
                                    ("offset", fDefault 0),
                                    ("begin", fDefault 0),
@@ -183,8 +182,8 @@
                                    ("attack", fDefault (-1)),
                                    ("hold", fDefault 0),
                                    ("release", fDefault (-1)),
-                                   ("orbit", iDefault 0),
-                                   ("id", iDefault 0)
+                                   ("orbit", iDefault 0) -- ,
+                                   -- ("id", iDefault 0)
                                   ]
 
 startStream :: Config -> [(Target, [OSC])] -> IO Stream
@@ -535,7 +534,7 @@
 
 -- Evaluation of pat is forced so exceptions are picked up here, before replacing the existing pattern.
 
-streamReplace :: Show a => Stream -> a -> ControlPattern -> IO ()
+streamReplace :: Stream -> ID -> ControlPattern -> IO ()
 streamReplace s k !pat
   = E.catch (do let x = queryArc pat (Arc 0 0)
                 tempo <- readMVar $ sTempoMV s
@@ -544,42 +543,38 @@
                 now <- O.time
                 let cyc = T.timeToCycles tempo now
                 putMVar (sStateMV s) $
-                  Map.insert ("_t_all") (VR cyc) $ Map.insert ("_t_" ++ show k) (VR cyc) input
+                  Map.insert ("_t_all") (VR cyc) $ Map.insert ("_t_" ++ fromID k) (VR cyc) input
                 -- update the pattern itself
                 pMap <- seq x $ takeMVar $ sPMapMV s
-                let playState = updatePS $ Map.lookup (show k) pMap
-                putMVar (sPMapMV s) $ Map.insert (show k) playState pMap
+                let playState = updatePS $ Map.lookup (fromID k) pMap
+                putMVar (sPMapMV s) $ Map.insert (fromID k) playState pMap
                 return ()
           )
     (\(e :: E.SomeException) -> hPutStrLn stderr $ "Error in pattern: " ++ show e
     )
   where updatePS (Just playState) = do playState {pattern = pat', history = pat:(history playState)}
         updatePS Nothing = PlayState pat' False False [pat']
-        pat' = pat # pS "_id_" (pure $ show k)
-
-
-streamMute :: Show a => Stream -> a -> IO ()
-streamMute s k = withPatId s (show k) (\x -> x {mute = True})
+        pat' = pat # pS "_id_" (pure $ fromID k)
 
-streamMutes :: Show a => Stream -> [a] -> IO ()
-streamMutes s ks = withPatIds s (map show ks) (\x -> x {mute = True})
+streamMute :: Stream -> ID -> IO ()
+streamMute s k = withPatIds s [k] (\x -> x {mute = True})
 
-streamUnmute :: Show a => Stream -> a -> IO ()
-streamUnmute s k = withPatId s (show k) (\x -> x {mute = False})
+streamMutes :: Stream -> [ID] -> IO ()
+streamMutes s ks = withPatIds s ks (\x -> x {mute = True})
 
-streamSolo :: Show a => Stream -> a -> IO ()
-streamSolo s k = withPatId s (show k) (\x -> x {solo = True})
+streamUnmute :: Stream -> ID -> IO ()
+streamUnmute s k = withPatIds s [k] (\x -> x {mute = False})
 
-streamUnsolo :: Show a => Stream -> a -> IO ()
-streamUnsolo s k = withPatId s (show k) (\x -> x {solo = False})
+streamSolo :: Stream -> ID -> IO ()
+streamSolo s k = withPatIds s [k] (\x -> x {solo = True})
 
-withPatId :: Stream -> PatId -> (PlayState -> PlayState) -> IO ()
-withPatId s k f = withPatIds s [k] f
+streamUnsolo :: Stream -> ID -> IO ()
+streamUnsolo s k = withPatIds s [k] (\x -> x {solo = False})
 
-withPatIds :: Stream -> [PatId] -> (PlayState -> PlayState) -> IO ()
+withPatIds :: Stream -> [ID] -> (PlayState -> PlayState) -> IO ()
 withPatIds s ks f
   = do playMap <- takeMVar $ sPMapMV s
-       let pMap' = foldr (Map.update (\x -> Just $ f x)) playMap ks
+       let pMap' = foldr (Map.update (\x -> Just $ f x)) playMap (map fromID ks)
        putMVar (sPMapMV s) pMap'
        return ()
 
@@ -596,6 +591,9 @@
 streamUnsoloAll :: Stream -> IO ()
 streamUnsoloAll s = modifyMVar_ (sPMapMV s) $ return . fmap (\x -> x {solo = False})
 
+streamSilence :: Stream -> ID -> IO ()
+streamSilence s k = withPatIds s [k] (\x -> x {pattern = silence, history = silence:history x})
+
 streamAll :: Stream -> (ControlPattern -> ControlPattern) -> IO ()
 streamAll s f = do _ <- swapMVar (sGlobalFMV s) f
                    return ()
@@ -668,22 +666,14 @@
         act (O.Message "/ctrl" (O.ASCII_String k:O.Int32 v:[]))
           = add (O.ascii_to_string k) (VI (fromIntegral v))
         -- Stream playback commands
-        act (O.Message "/mute" (O.Int32 k:[]))
-          = streamMute stream k
-        act (O.Message "/mute" (O.ASCII_String k:[]))
-          = streamMute stream (O.ascii_to_string k)
-        act (O.Message "/unmute" (O.Int32 k:[]))
-          = streamUnmute stream k
-        act (O.Message "/unmute" (O.ASCII_String k:[]))
-          = streamUnmute stream (O.ascii_to_string k)
-        act (O.Message "/solo" (O.Int32 k:[]))
-          = streamSolo stream k
-        act (O.Message "/solo" (O.ASCII_String k:[]))
-          = streamSolo stream (O.ascii_to_string k)
-        act (O.Message "/unsolo" (O.Int32 k:[]))
-          = streamUnsolo stream k
-        act (O.Message "/unsolo" (O.ASCII_String k:[]))
-          = streamUnsolo stream (O.ascii_to_string k)
+        act (O.Message "/mute" (k:[]))
+          = withID k $ streamMute stream
+        act (O.Message "/unmute" (k:[]))
+          = withID k $ streamUnmute stream
+        act (O.Message "/solo" (k:[]))
+          = withID k $ streamSolo stream
+        act (O.Message "/unsolo" (k:[]))
+          = withID k $ streamUnsolo stream
         act (O.Message "/muteAll" [])
           = streamMuteAll stream
         act (O.Message "/unmuteAll" [])
@@ -692,11 +682,17 @@
           = streamUnsoloAll stream
         act (O.Message "/hush" [])
           = streamHush stream
+        act (O.Message "/silence" (k:[]))
+          = withID k $ streamSilence stream
         act m = hPutStrLn stderr $ "Unhandled OSC: " ++ show m
         add :: String -> Value -> IO ()
         add k v = do sMap <- takeMVar (sStateMV stream)
                      putMVar (sStateMV stream) $ Map.insert k v sMap
                      return ()
+        withID :: O.Datum -> (ID -> IO ()) -> IO ()
+        withID (O.ASCII_String k) func = func $ (ID . O.ascii_to_string) k
+        withID (O.Int32 k) func = func $ (ID . show) k
+        withID _ _ = return ()
 ctrlResponder _ _ _ = return ()
 
 
diff --git a/src/Sound/Tidal/Time.hs b/src/Sound/Tidal/Time.hs
--- a/src/Sound/Tidal/Time.hs
+++ b/src/Sound/Tidal/Time.hs
@@ -38,7 +38,8 @@
 
 -- Utility functions - Time
 
--- | The 'sam' (start of cycle) for the given time value
+-- | The 'sam' (start of cycle) for the given time value.
+-- Cycles have duration 1, so every integer Time value divides two cycles.
 sam :: Time -> Time
 sam = fromIntegral . (floor :: Time -> Int)
 
@@ -87,23 +88,39 @@
 sect :: Arc -> Arc -> Arc
 sect (Arc s e) (Arc s' e') = Arc (max s s') (min e e')
 
-
--- | The arc of the whole cycle that the given time value falls within
+-- | The Arc returned is the cycle that the Time falls within.
+--
+-- Edge case: If the Time is an integer,
+-- the Arc claiming it is the one starting at that Time,
+-- not the previous one ending at that Time.
 timeToCycleArc :: Time -> Arc
 timeToCycleArc t = Arc (sam t) (sam t + 1)
 
--- | Shifts an arc to the equivalent one that starts during cycle zero
+-- | Shifts an Arc to one of equal duration that starts within cycle zero.
+-- (Note that the output Arc probably does not start *at* Time 0 --
+-- that only happens when the input Arc starts at an integral Time.)
 cycleArc :: Arc -> Arc
 cycleArc (Arc s e) = Arc (cyclePos s) (cyclePos s + (e-s))
 
--- | A list of cycle numbers which are included in the given arc
+-- | Returns the numbers of the cycles that the input Arc overlaps
+-- (excluding the input Arc's endpoint, unless it has duration 0 --
+-- see "Edge cases" below).
+-- The "cycle number" of an Arc is equal to its start value.
+-- Thus, for instance, `cyclesInArc (Arc 0 1.5) == [0,1]`.
+-- * Edge cases:
+--     `cyclesInArc $ Arc 0 1.0001 == [0,1]`
+--     `cyclesInArc $ Arc 0 1      == [0]` -- the endpoint is excluded
+--     `cyclesInArc $ Arc 1 1      == [1]` -- unless the Arc has duration 0
 cyclesInArc :: Integral a => Arc -> [a]
 cyclesInArc (Arc s e)
   | s > e = []
   | s == e = [floor s]
   | otherwise = [floor s .. ceiling e-1]
 
--- | A list of arcs of the whole cycles which are included in the given arc
+-- | The whole cycles that overlap the input Arc,
+-- (excluding its endpoint, unless it has duration 0,
+-- similarly to `cyclesInArc` --
+-- see that function's description for details).
 cycleArcsInArc :: Arc -> [Arc]
 cycleArcsInArc = map (timeToCycleArc . (toTime :: Int -> Time)) . cyclesInArc
 
diff --git a/src/Sound/Tidal/Transition.hs b/src/Sound/Tidal/Transition.hs
--- a/src/Sound/Tidal/Transition.hs
+++ b/src/Sound/Tidal/Transition.hs
@@ -4,7 +4,7 @@
 
 import Prelude hiding ((<*), (*>))
 
-import Control.Concurrent.MVar (readMVar, takeMVar, putMVar)
+import Control.Concurrent.MVar (readMVar, swapMVar)
 
 import qualified Sound.OSC.FD as O
 import qualified Data.Map.Strict as Map
@@ -12,6 +12,7 @@
 
 import Sound.Tidal.Control
 import Sound.Tidal.Core
+import Sound.Tidal.ID
 import Sound.Tidal.Params (gain, pan)
 import Sound.Tidal.Pattern
 import Sound.Tidal.Stream
@@ -39,13 +40,13 @@
 
 -- Evaluation of pat is forced so exceptions are picked up here, before replacing the existing pattern.
 -- the "historyFlag" determines if the new pattern should be placed on the history stack or not
-transition :: Show a => Stream -> Bool -> (Time -> [ControlPattern] -> ControlPattern) -> a -> ControlPattern -> IO ()
+transition :: Stream -> Bool -> (Time -> [ControlPattern] -> ControlPattern) -> ID -> ControlPattern -> IO ()
 transition stream historyFlag f patId !pat =
-  do pMap <- takeMVar (sPMapMV stream)
-     let playState = updatePS $ Map.lookup (show patId) pMap
+  do pMap <- readMVar (sPMapMV stream)
+     let playState = updatePS $ Map.lookup (fromID patId) pMap
      pat' <- transition' $ appendPat (not historyFlag) (history playState)
-     let pMap' = Map.insert (show patId) (playState {pattern = pat'}) pMap
-     putMVar (sPMapMV stream) pMap'
+     let pMap' = Map.insert (fromID patId) (playState {pattern = pat'}) pMap
+     _ <- swapMVar (sPMapMV stream) pMap'
      return ()
   where
     appendPat flag = if flag then (pat:) else id
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
@@ -142,14 +142,14 @@
 repeat every cycle (because the saw does)
 The `perlin` function uses the cycle count as input and can be used much like @rand@.
 -}
-perlinWith :: Pattern Double -> Pattern Double
-perlinWith p = interp <$> (p-pa) <*> (timeToRand <$> pa) <*> (timeToRand <$> pb) where
+perlinWith :: Fractional a => Pattern Double -> Pattern a
+perlinWith p = fmap realToFrac $ (interp) <$> (p-pa) <*> (timeToRand <$> pa) <*> (timeToRand <$> pb) where
   pa = (fromIntegral :: Int -> Double) . floor <$> p
   pb = (fromIntegral :: Int -> Double) . (+1) . floor <$> p
   interp x a b = a + smootherStep x * (b-a)
   smootherStep x = 6.0 * x**5 - 15.0 * x**4 + 10.0 * x**3
 
-perlin :: Pattern Double
+perlin :: Fractional a => Pattern a
 perlin = perlinWith (sig fromRational)
 
 {- `perlin2With` is Perlin noise with a 2-dimensional input. This can be
@@ -564,11 +564,15 @@
 -- | @wedge t p p'@ combines patterns @p@ and @p'@ by squashing the
 -- @p@ into the portion of each cycle given by @t@, and @p'@ into the
 -- remainer of each cycle.
-wedge :: Time -> Pattern a -> Pattern a -> Pattern a
-wedge 0 _ p' = p'
-wedge 1 p _ = p
-wedge t p p' = overlay (_fastGap (1/t) p) (t `rotR` _fastGap (1/(1-t)) p')
+wedge :: Pattern Time -> Pattern a -> Pattern a -> Pattern a
+wedge pt pa pb = innerJoin $ (\t -> _wedge t pa pb) <$> pt
 
+_wedge :: Time -> Pattern a -> Pattern a -> Pattern a
+_wedge 0 _ p' = p'
+_wedge 1 p _ = p
+_wedge t p p' = overlay (_fastGap (1/t) p) (t `rotR` _fastGap (1/(1-t)) p')
+
+
 {- | @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,
@@ -1250,11 +1254,17 @@
 chunk' :: Integral a1 => Pattern a1 -> (Pattern a2 -> Pattern a2) -> Pattern a2 -> Pattern a2
 chunk' npat f p = innerJoin $ (\n -> _chunk' n f p) <$> npat
 
+_inside :: Time -> (Pattern a1 -> Pattern a) -> Pattern a1 -> Pattern a
+_inside n f p = _fast n $ f (_slow n p)
+
 inside :: Pattern Time -> (Pattern a1 -> Pattern a) -> Pattern a1 -> Pattern a
-inside n f p = density n $ f (slow n p)
+inside np f p = innerJoin $ (\n -> _inside n f p) <$> np
 
+_outside :: Time -> (Pattern a1 -> Pattern a) -> Pattern a1 -> Pattern a
+_outside n = _inside (1/n)
+
 outside :: Pattern Time -> (Pattern a1 -> Pattern a) -> Pattern a1 -> Pattern a
-outside n = inside (1/n)
+outside np f p = innerJoin $ (\n -> _outside n f p) <$> np
 
 loopFirst :: Pattern a -> Pattern a
 loopFirst p = splitQueries $ p {query = f}
@@ -1961,3 +1971,13 @@
 grain s w = P.begin b # P.end e
   where b = s
         e = s + w
+
+-- | For specifying a boolean pattern according to a list of offsets
+-- (aka inter-onset intervals).  For example `necklace 12 [4,2]` is
+-- the same as "t f f f t f t f f f t f". That is, 12 steps per cycle,
+-- with true values alternating between every 4 and every 2 steps.
+necklace :: Rational -> [Int] -> Pattern Bool
+necklace perCycle xs = _slow ((toRational $ sum xs) / perCycle) $ listToPat $ list xs
+  where list :: [Int] -> [Bool]
+        list [] = []
+        list (x:xs') = (True:(replicate (x-1) False)) ++ list xs'
diff --git a/src/Sound/Tidal/Utils.hs b/src/Sound/Tidal/Utils.hs
--- a/src/Sound/Tidal/Utils.hs
+++ b/src/Sound/Tidal/Utils.hs
@@ -93,27 +93,6 @@
    s':rest -> (s':w) : wordsBy p (drop 1 s'')
           where (w, s'') = break p rest
 
--- A hack to add to the source code context for mini-notation, so
--- events know where they are within a whole tidal pattern
-deltaMini :: String -> String
-deltaMini = outside 0 0
-  where outside :: Int -> Int -> String -> String
-        outside _ _ [] = []
-        outside column line ('"':xs) = "(deltaContext "
-                                         ++ show column
-                                         ++ " "
-                                         ++ show line
-                                         ++ " \""
-                                         ++ inside (column+1) line xs
-        outside _ line ('\n':xs) = '\n':outside 0 (line+1) xs
-        outside column line (x:xs) = x:outside (column+1) line xs
-        inside :: Int -> Int -> String -> String
-        inside _ _ [] = []
-        inside column line ('"':xs) = '"':')':outside (column+1) line xs
-        inside _ line ('\n':xs) = '\n':inside 0 (line+1) xs
-        inside column line (x:xs) = x:inside (column+1) line xs
-
-
 matchMaybe :: Maybe a -> Maybe a -> Maybe a
 matchMaybe Nothing y = y
 matchMaybe x       _ = x
diff --git a/src/Sound/Tidal/Version.hs b/src/Sound/Tidal/Version.hs
--- a/src/Sound/Tidal/Version.hs
+++ b/src/Sound/Tidal/Version.hs
@@ -21,7 +21,7 @@
 -}
 
 tidal_version :: String
-tidal_version = "1.7.8"
+tidal_version = "1.7.9"
 
 tidal_status :: IO ()
 tidal_status = tidal_status_string >>= putStrLn 
diff --git a/test/Sound/Tidal/ParseTest.hs b/test/Sound/Tidal/ParseTest.hs
--- a/test/Sound/Tidal/ParseTest.hs
+++ b/test/Sound/Tidal/ParseTest.hs
@@ -97,6 +97,22 @@
         compareP (Arc 0 2)
           ("0 .. 8" :: Pattern Rational)
           ("0 1 2 3 4 5 6 7 8")
+      it "can parse .. with doubles" $ do
+        compareP (Arc 0 2)
+          ("0.0 .. 8.0" :: Pattern Double)
+          ("0 1 2 3 4 5 6 7 8")
+      it "can parse .. with doubles, without spaces" $ do
+        compareP (Arc 0 2)
+          ("0.0..8.0" :: Pattern Double)
+          ("0 1 2 3 4 5 6 7 8")
+      it "can parse .. with notes" $ do
+        compareP (Arc 0 2)
+          ("0.0 .. 8.0" :: Pattern Note)
+          ("0 1 2 3 4 5 6 7 8")
+      it "can parse .. with notes, without spaces" $ do
+        compareP (Arc 0 2)
+          ("0..8" :: Pattern Note)
+          ("0 1 2 3 4 5 6 7 8")
       it "can handle repeats (!) and durations (@) with <>" $ do
         compareP (Arc 0 31)
           ("<a!3 b ! c@5>" :: Pattern String)
@@ -113,6 +129,17 @@
         compareP (Arc 0 2)
           ("1%3@0.5 3%4@1%6 3%4@1%6 3%4@1%6" :: Pattern Rational)
           ("1%3 0.75*3")
+      it "can handle ratio shortands on a fraction" $ do
+        compareP (Arc 0 1)
+          ("1%3t" :: Pattern Rational)
+          ("1%9" :: Pattern Rational)
+      it "can handle ratio shortands on a floating point number" $ do
+        compareP (Arc 0 1)
+          ("3.33t" :: Pattern Double)
+          ("1.11" :: Pattern Double)
+      it "cannot handle fractional with floating point numerator or denominator" $ do
+        evaluate ("1.2%5.3" :: Pattern Time)
+          `shouldThrow` anyException
       it "can parse a chord" $ do
         compareP (Arc 0 2)
           ("'major" :: Pattern Int)
@@ -145,6 +172,26 @@
         compareP (Arc 0 1)
           ("3h -2q 1.5q" :: Pattern Double)
           ("1.5 -0.5 0.375" :: Pattern Double)
+      it "can parse exponential notation value for pattern double" $ do
+        compareP (Arc 0 1)
+          ("1e3" :: Pattern Double)
+          ("1000" :: Pattern Double)
+      it "can parse negative exponential notation value for pattern double" $ do
+        compareP (Arc 0 1)
+          ("400e-3" :: Pattern Double)
+          ("0.4" :: Pattern Double)
+      it "can parse ratio shortand on exponential notation value" $ do
+        compareP (Arc 0 1)
+          ("4e2q" :: Pattern Double)
+          ("100" :: Pattern Double)
+      it "can parse euclid pattern" $ do
+        compareP (Arc 0 1)
+          ("bd(3,8,1)" :: Pattern String)
+          ("~ ~ bd ~ ~ bd ~ bd")
+      it "can parse euclid bool pattern" $ do
+        compareP (Arc 0 1)
+          ("t(3,8,1)" :: Pattern Bool)
+          ("f f t f f t f t")
       it "doesn't crash on zeroes (1)" $ do
         compareP (Arc 0 2)
           ("cp/0" :: Pattern String)
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
@@ -344,3 +344,29 @@
         compareP (Arc 0 1)
           (off "-e" id $ s "0")
           (superimpose ("e" <~) $ s "0")
+    describe "loopFirst" $ do
+      it "plays the first cycle" $ do
+        compareP (Arc 0 1)
+          (loopFirst $ rotL 3 $ slow 8 $ "0 .. 7" :: Pattern Int)
+          ("3")
+    describe "loopCycles" $ do
+      it "plays the first n cycles" $ do
+        compareP (Arc 0 1)
+          (loopFirst $ rotL 3 $ slow 8 $ "0 .. 7" :: Pattern Int)
+          ("3")
+    describe "timeLoop" $ do
+      it "can loop time" $ do
+        compareP (Arc 0 1)
+          ((3 <~) $ (timeLoop 3 $ sound "<a b c d>"))
+          (sound "a")
+    describe "timeLoop" $ do
+      it "can pattern time" $ do
+        compareP (Arc 0 1)
+          ((1 <~) $ timeLoop "<2 1>" $ sound "b")
+          (sound "b")
+    describe "necklace" $ do
+      it "can specify rhythm by IOI" $ do
+        compareP (Arc 0 1)
+          (necklace 12 [4,2])
+          ("t f f f t f t f f f t f")
+      
diff --git a/tidal.cabal b/tidal.cabal
--- a/tidal.cabal
+++ b/tidal.cabal
@@ -1,5 +1,5 @@
 name:                tidal
-version:             1.7.8
+version:             1.7.9
 synopsis:            Pattern language for improvised music
 description:         Tidal is a domain specific language for live coding patterns.
 homepage:            http://tidalcycles.org/
@@ -33,6 +33,7 @@
                        Sound.Tidal.Control
                        Sound.Tidal.Context
                        Sound.Tidal.Core
+                       Sound.Tidal.ID
                        Sound.Tidal.Params
                        Sound.Tidal.ParseBP
                        Sound.Tidal.Pattern
