diff --git a/README b/README
--- a/README
+++ b/README
@@ -3,22 +3,29 @@
 
 [hosc][hosc] provides `Sound.OSC`, a [haskell][hs]
 module implementing a subset of the [Open Sound Control][osc] byte protocol.
+hosc is required by the [hsc3][hsc3] haskell [supercollider][sc3] bindings.
 
-© [rohan drape][rd], [stefan kersten][sk] and others, 2006-2013,
+There are a number of related projects:
+
+- [hosc-json](?t=hosc-json): JSON text encoding of OSC
+- [hosc-utils](?t=hosc-utils): command line utilities
+
+© [rohan drape][rd], [stefan kersten][sk] and others, 2007-2014,
 [gpl][gpl]. with contributions by:
 
 - [alex mclean][am]
 - [henning thielemann][ht]
 
-see the [darcs][darcs] [history][hosc-history] for details
+see the [darcs][darcs] [history](?t=hosc&q=history) for details
 
 [hosc]: http://rd.slavepianos.org/?t=hosc
 [hs]: http://haskell.org/
 [osc]: http://opensoundcontrol.org/
+[hsc3]: http://rd.slavepianos.org/?t=hsc3
+[sc3]: http://audiosynth.com/
 [rd]:  http://rd.slavepianos.org/
 [sk]: http://space.k-hornz.de/
 [am]: http://yaxu.org/
 [ht]: http://www.henning-thielemann.de/Research.html
 [darcs]: http://darcs.net/
 [gpl]: http://gnu.org/copyleft/
-[hosc-history]:  http://rd.slavepianos.org/r/d/darcsweb.cgi?r=hosc
diff --git a/Sound/OSC/Coding/Byte.hs b/Sound/OSC/Coding/Byte.hs
--- a/Sound/OSC/Coding/Byte.hs
+++ b/Sound/OSC/Coding/Byte.hs
@@ -3,97 +3,105 @@
 
 import Data.Binary {- base -}
 import Data.Bits {- base -}
-import qualified Data.ByteString as S.B {- bytestring -}
+import qualified Data.ByteString as S {- bytestring -}
 import qualified Data.ByteString.Char8 as S.C {- bytestring -}
-import qualified Data.ByteString.Lazy as B {- bytestring -}
-import qualified Data.ByteString.Lazy.Char8 as C {- bytestring -}
+import qualified Data.ByteString.Lazy as L {- bytestring -}
+import qualified Data.ByteString.Lazy.Char8 as L.C {- bytestring -}
 import Data.Int {- base -}
 
 import Sound.OSC.Coding.Cast
 import Sound.OSC.Type
 
 -- | Encode a signed 8-bit integer.
-encode_i8 :: Int -> B.ByteString
+encode_i8 :: Int -> L.ByteString
 encode_i8 n = encode (fromIntegral n :: Int8)
 
+-- | Encode an un-signed 8-bit integer.
+encode_u8 :: Int -> L.ByteString
+encode_u8 n = encode (fromIntegral n :: Word8)
+
 -- | Encode a signed 16-bit integer.
-encode_i16 :: Int -> B.ByteString
+encode_i16 :: Int -> L.ByteString
 encode_i16 n = encode (fromIntegral n :: Int16)
 
 -- | Encode a signed 32-bit integer.
-encode_i32 :: Int -> B.ByteString
+encode_i32 :: Int -> L.ByteString
 encode_i32 n = encode (fromIntegral n :: Int32)
 
 -- | Encode an unsigned 16-bit integer.
-encode_u32 :: Int -> B.ByteString
+encode_u32 :: Int -> L.ByteString
 encode_u32 n = encode (fromIntegral n :: Word32)
 
 -- | Encode a signed 64-bit integer.
-encode_i64 :: Int64 -> B.ByteString
+encode_i64 :: Int64 -> L.ByteString
 encode_i64 = encode
 
 -- | Encode an unsigned 64-bit integer.
-encode_u64 :: Word64 -> B.ByteString
+encode_u64 :: Word64 -> L.ByteString
 encode_u64 = encode
 
 -- | Encode a 32-bit IEEE floating point number.
-encode_f32 :: Float -> B.ByteString
+encode_f32 :: Float -> L.ByteString
 encode_f32 = encode . f32_w32
 
 -- | Encode a 64-bit IEEE floating point number.
-encode_f64 :: Double -> B.ByteString
+encode_f64 :: Double -> L.ByteString
 encode_f64 = encode . f64_w64
 
 -- | Encode an ASCII string.
-encode_str :: ASCII -> B.ByteString
+encode_str :: ASCII -> L.ByteString
 {-# INLINE encode_str #-}
-encode_str = B.pack . S.B.unpack
+encode_str = L.pack . S.unpack
 
+-- | Decode an un-signed 8-bit integer.
+decode_u8 :: L.ByteString -> Int
+decode_u8 = fromIntegral . L.head
+
 -- | Decode a signed 8-bit integer.
-decode_i8 :: B.ByteString -> Int
+decode_i8 :: L.ByteString -> Int
 decode_i8 b = fromIntegral (decode b :: Int8)
 
 -- | Decode a signed 16-bit integer.
-decode_i16 :: B.ByteString -> Int
+decode_i16 :: L.ByteString -> Int
 decode_i16 b = fromIntegral (decode b :: Int16)
 
 -- | Decode a signed 32-bit integer.
-decode_i32 :: B.ByteString -> Int
+decode_i32 :: L.ByteString -> Int
 decode_i32 b = fromIntegral (decode b :: Int32)
 
 -- | Decode an unsigned 32-bit integer.
-decode_u32 :: B.ByteString -> Int
+decode_u32 :: L.ByteString -> Int
 decode_u32 b = fromIntegral (decode b :: Word32)
 
 -- | Decode a signed 64-bit integer.
-decode_i64 :: B.ByteString -> Int64
+decode_i64 :: L.ByteString -> Int64
 decode_i64 = decode
 
 -- | Decode an unsigned 64-bit integer.
-decode_u64 :: B.ByteString -> Word64
+decode_u64 :: L.ByteString -> Word64
 decode_u64 = decode
 
 -- | Decode a 32-bit IEEE floating point number.
-decode_f32 :: B.ByteString -> Float
+decode_f32 :: L.ByteString -> Float
 decode_f32 b = w32_f32 (decode b :: Word32)
 
 -- | Decode a 64-bit IEEE floating point number.
-decode_f64 :: B.ByteString -> Double
+decode_f64 :: L.ByteString -> Double
 decode_f64 b = w64_f64 (decode b :: Word64)
 
 -- | Decode an ASCII string.
-decode_str :: B.ByteString -> ASCII
+decode_str :: L.ByteString -> ASCII
 {-# INLINE decode_str #-}
-decode_str = S.C.pack . C.unpack
+decode_str = S.C.pack . L.C.unpack
 
 -- | Bundle header as a (strict) 'S.C.ByteString'.
 bundleHeader_strict :: S.C.ByteString
 bundleHeader_strict = S.C.pack "#bundle\0"
 
 -- | Bundle header as a lazy ByteString.
-bundleHeader :: B.ByteString
+bundleHeader :: L.ByteString
 {-# INLINE bundleHeader #-}
-bundleHeader = C.fromChunks [bundleHeader_strict]
+bundleHeader = L.C.fromChunks [bundleHeader_strict]
 
 -- | The number of bytes required to align an OSC value to the next
 --   4-byte boundary.
diff --git a/Sound/OSC/Coding/Decode/Binary.hs b/Sound/OSC/Coding/Decode/Binary.hs
--- a/Sound/OSC/Coding/Decode/Binary.hs
+++ b/Sound/OSC/Coding/Decode/Binary.hs
@@ -6,7 +6,7 @@
 
 import Control.Applicative {- base -}
 import Control.Monad (when) {- base -}
-import Data.Binary.Get {- binary -}
+import qualified Data.Binary.Get as G {- binary -}
 import qualified Data.Binary.IEEE754 as I {- data-binary-ieee754 -}
 import qualified Data.ByteString.Char8 as S.C {- bytestring -}
 import qualified Data.ByteString.Lazy as B {- bytestring -}
@@ -18,51 +18,39 @@
 import Sound.OSC.Time
 import Sound.OSC.Type
 
--- | Isolate an action to operating within a fixed block of bytes. The
--- action is required to consume all the bytes that it is isolated to.
-isolate' :: Word32 -> Get a -> Get a
-isolate' n m = do
-    s <- get_bytes n
-    case runGetOrFail m s of
-        Left (_, _, e) -> fail e
-        Right (s', _, a) ->
-            if B.null s'
-                then return a
-                else fail "isolate': not all bytes consumed"
-
 -- | Get a 32 bit integer in big-endian byte order.
-getInt32be :: Get Int32
-getInt32be = fromIntegral <$> getWord32be
+getInt32be :: G.Get Int32
+getInt32be = fromIntegral <$> G.getWord32be
 
 -- | Get a 64 bit integer in big-endian byte order.
-getInt64be :: Get Int64
-getInt64be = fromIntegral <$> getWord64be
+getInt64be :: G.Get Int64
+getInt64be = fromIntegral <$> G.getWord64be
 
 -- | Get an aligned OSC string.
-get_string :: Get String
+get_string :: G.Get String
 get_string = do
-    s <- getLazyByteStringNul
-    skip (fromIntegral (align (B.length s + 1)))
+    s <- G.getLazyByteStringNul
+    G.skip (fromIntegral (align (B.length s + 1)))
     return $ C.unpack s
 
 -- | Get an aligned OSC string.
-get_ascii :: Get ASCII
+get_ascii :: G.Get ASCII
 get_ascii = do
-    s <- getLazyByteStringNul
-    skip (fromIntegral (align (B.length s + 1)))
+    s <- G.getLazyByteStringNul
+    G.skip (fromIntegral (align (B.length s + 1)))
     return (S.C.pack (C.unpack s))
 
 -- | Get binary data prefixed by byte count.
-get_bytes :: Word32 -> Get B.ByteString
+get_bytes :: Word32 -> G.Get B.ByteString
 get_bytes n = do
-    b <- getLazyByteString (fromIntegral n)
+    b <- G.getLazyByteString (fromIntegral n)
     if n /= fromIntegral (B.length b)
         then fail "get_bytes: end of stream"
-        else skip (fromIntegral (align n))
+        else G.skip (fromIntegral (align n))
     return b
 
 -- | Get an OSC datum.
-get_datum :: Datum_Type -> Get Datum
+get_datum :: Datum_Type -> G.Get Datum
 get_datum ty =
     case ty of
       'i' -> Int32  <$> fromIntegral <$> getInt32be
@@ -70,17 +58,17 @@
       'f' -> Float  <$> realToFrac <$> I.getFloat32be
       'd' -> Double <$> I.getFloat64be
       's' -> ASCII_String <$> get_ascii
-      'b' -> Blob   <$> (get_bytes =<< getWord32be)
-      't' -> TimeStamp <$> ntpi_to_ntpr <$> getWord64be
-      'm' -> do b0 <- getWord8
-                b1 <- getWord8
-                b2 <- getWord8
-                b3 <- getWord8
+      'b' -> Blob   <$> (get_bytes =<< G.getWord32be)
+      't' -> TimeStamp <$> ntpi_to_ntpr <$> G.getWord64be
+      'm' -> do b0 <- G.getWord8
+                b1 <- G.getWord8
+                b2 <- G.getWord8
+                b3 <- G.getWord8
                 return $ Midi (MIDI b0 b1 b2 b3)
       _ -> fail ("get_datum: illegal type " ++ show ty)
 
 -- | Get an OSC 'Message'.
-get_message :: Get Message
+get_message :: G.Get Message
 get_message = do
     cmd <- get_string
     dsc <- get_ascii
@@ -91,27 +79,27 @@
         _ -> fail "get_message: invalid type descriptor string"
 
 -- | Get a sequence of OSC 'Message's, each one headed by its length.
-get_message_seq :: Get [Message]
+get_message_seq :: G.Get [Message]
 get_message_seq = do
-    b <- isEmpty
+    b <- G.isEmpty
     if b
         then return []
         else do
-            p <- flip isolate' get_message =<< getWord32be
+            p <- flip G.isolate get_message . fromIntegral =<< G.getWord32be
             ps <- get_message_seq
             return (p:ps)
 
 -- | Get a bundle. Fail if bundle header is not found in packet.
-get_bundle :: Get Bundle
+get_bundle :: G.Get Bundle
 get_bundle = do
-    h <- getByteString (S.C.length bundleHeader_strict)
+    h <- G.getByteString (S.C.length bundleHeader_strict)
     when (h /= bundleHeader_strict) (fail "get_bundle: not a bundle")
-    t <- ntpi_to_ntpr <$> getWord64be
+    t <- ntpi_to_ntpr <$> G.getWord64be
     ps <- get_message_seq
     return $ Bundle t ps
 
 -- | Get an OSC 'Packet'.
-getPacket :: Get Packet
+getPacket :: G.Get Packet
 getPacket = (Packet_Bundle <$> get_bundle) <|> (Packet_Message <$> get_message)
 
 -- | Decode an OSC packet from a lazy ByteString.
@@ -120,9 +108,9 @@
 -- > in decodeOSC b == Message "/g_free" [Int 0]
 decodePacket :: B.ByteString -> Packet
 {-# INLINE decodePacket #-}
-decodePacket = runGet getPacket
+decodePacket = G.runGet getPacket
 
 -- | Decode an OSC packet from a strict ByteString.
 decodePacket_strict :: S.C.ByteString -> Packet
 {-# INLINE decodePacket_strict #-}
-decodePacket_strict = runGet getPacket . B.fromChunks . (:[])
+decodePacket_strict = G.runGet getPacket . B.fromChunks . (:[])
diff --git a/Sound/OSC/Datum.hs b/Sound/OSC/Datum.hs
--- a/Sound/OSC/Datum.hs
+++ b/Sound/OSC/Datum.hs
@@ -35,6 +35,7 @@
 -- | 'C.unpack' of 'd_get'.
 --
 -- > datum_string (d_put (C.pack "string")) == Just "string"
+-- > map datum_string [string "string",Int32 5] == [Just "string",Nothing]
 datum_string :: Datum -> Maybe String
 datum_string = fmap C.unpack . datum_ascii
 
diff --git a/Sound/OSC/Type.hs b/Sound/OSC/Type.hs
--- a/Sound/OSC/Type.hs
+++ b/Sound/OSC/Type.hs
@@ -25,6 +25,14 @@
 -- | Type for ASCII strings (strict 'Char'8 'C.ByteString').
 type ASCII = C.ByteString
 
+-- | Type-specialised 'C.pack'.
+ascii :: String -> ASCII
+ascii = C.pack
+
+-- | Type-specialised 'C.unpack'.
+ascii_to_string :: ASCII -> String
+ascii_to_string = C.unpack
+
 -- | Four-byte midi message.
 data MIDI = MIDI Word8 Word8 Word8 Word8
     deriving (Eq,Show,Read)
@@ -298,12 +306,26 @@
 
 -- * Pretty printing
 
--- | Pretty printer for 'Time' (truncate to 4 decimal places).
+-- | Perhaps a precision value for floating point numbers.
+type FP_Precision = Maybe Int
+
+-- | Variant of 'showFFloat' that deletes trailing zeros.
 --
--- > timePP (1/3) == "0.3333"
-timePP :: Time -> String
-timePP t = showGFloat (Just 4) t ""
+-- > map (floatPP (Just 4)) [1,pi] == ["1.0","3.1416"]
+floatPP :: RealFloat n => Maybe Int -> n -> String
+floatPP p n =
+    let s = showFFloat p n ""
+        s' = dropWhile (== '0') (reverse s)
+    in case s' of
+         '.':_ -> reverse ('0' : s')
+         _ -> reverse s'
 
+-- | Pretty printer for 'Time'.
+--
+-- > timePP (Just 4) (1/3) == "0.3333"
+timePP :: FP_Precision -> Time -> String
+timePP = floatPP
+
 -- | Pretty printer for vectors.
 --
 -- > vecPP [1::Int,2,3] == "<1,2,3>"
@@ -314,36 +336,36 @@
 --
 -- > let d = [Int32 1,Float 1.2,string "str",midi (0,0x90,0x40,0x60)]
 -- > in map datumPP d ==  ["1","1.2","\"str\"","<0,144,64,96>"]
-datumPP :: Datum -> String
-datumPP d =
+datumPP :: FP_Precision -> Datum -> String
+datumPP p d =
     case d of
       Int32 n -> show n
       Int64 n -> show n
-      Float n -> show n
-      Double n -> show n
+      Float n -> floatPP p n
+      Double n -> floatPP p n
       ASCII_String s -> show (C.unpack s)
       Blob s -> show s
-      TimeStamp t -> timePP t
-      Midi (MIDI p q r s) -> vecPP [p,q,r,s]
+      TimeStamp t -> timePP p t
+      Midi (MIDI b1 b2 b3 b4) -> vecPP [b1,b2,b3,b4]
 
 -- | Pretty printer for 'Message'.
-messagePP :: Message -> String
-messagePP (Message a d) =
-    let d' = map datumPP d
+messagePP :: FP_Precision -> Message -> String
+messagePP p (Message a d) =
+    let d' = map (datumPP p) d
     in unwords ("#message" : a : d')
 
 -- | Pretty printer for 'Bundle'.
-bundlePP :: Bundle -> String
-bundlePP (Bundle t m) =
-    let m' = intersperse ";" (map messagePP m)
-    in unwords ("#bundle" : timePP t : m')
+bundlePP :: FP_Precision -> Bundle -> String
+bundlePP p (Bundle t m) =
+    let m' = intersperse ";" (map (messagePP p) m)
+    in unwords ("#bundle" : timePP p t : m')
 
 -- | Pretty printer for 'Packet'.
-packetPP :: Packet -> String
-packetPP p =
-    case p of
-      Packet_Message m -> messagePP m
-      Packet_Bundle b -> bundlePP b
+packetPP :: FP_Precision -> Packet -> String
+packetPP p pkt =
+    case pkt of
+      Packet_Message m -> messagePP p m
+      Packet_Bundle b -> bundlePP p b
 
 -- * Parser
 
diff --git a/hosc.cabal b/hosc.cabal
--- a/hosc.cabal
+++ b/hosc.cabal
@@ -1,5 +1,5 @@
 Name:              hosc
-Version:           0.14.1
+Version:           0.15
 Synopsis:          Haskell Open Sound Control
 Description:       @hosc@ implements a subset of the /Open Sound Control/
                    byte protocol, <http://opensoundcontrol.org/>.
@@ -16,19 +16,19 @@
                    Composite modules are at "Sound.OSC" and "Sound.OSC.FD".
 License:           GPL
 Category:          Sound
-Copyright:         (c) Rohan Drape, Stefan Kersten and others, 2006-2013
+Copyright:         (c) Rohan Drape, Stefan Kersten and others, 2007-2014
 Author:            Rohan Drape, Stefan Kersten
 Maintainer:        rd@slavepianos.org
 Stability:         Experimental
-Homepage:          http://rd.slavepianos.org/?t=hosc
-Tested-With:       GHC == 7.6.1
+Homepage:          http://rd.slavepianos.org/t/hosc
+Tested-With:       GHC == 7.8.2
 Build-Type:        Simple
 Cabal-Version:     >= 1.8
 Data-Files:        README
 
 Library
   Build-Depends:   base == 4.*,
-                   binary >= 0.6,
+                   binary >=  0.7.2,
                    blaze-builder >= 0.3,
                    bytestring,
                    data-binary-ieee754,
@@ -69,7 +69,7 @@
       Sound.OSC.NFData
   Build-Depends:
       base == 4.*
-    , hosc == 0.14.*
+    , hosc == 0.15.*
     , bytestring
     , criterion
     , deepseq
@@ -85,7 +85,7 @@
   Build-Depends:
       base == 4.*
     , bytestring >= 0.10
-    , hosc == 0.14.*
+    , hosc == 0.15.*
     , QuickCheck >= 2
     , test-framework >= 0.2
     , test-framework-quickcheck2 >= 0.2
