diff --git a/Help/timer.hs b/Help/timer.hs
deleted file mode 100644
--- a/Help/timer.hs
+++ /dev/null
@@ -1,17 +0,0 @@
--- hosc does not directly support a timeout at recv, however the
--- standard libraries have the 'System.Timeout.timeout' function that
--- works for interupting socket reads.
-
-import Sound.OpenSoundControl
-import qualified System.Timeout as T
-
-timeout :: Double -> IO a -> IO (Maybe a)
-timeout t =
-    let i = floor (t * 1000000)
-    in T.timeout i
-
-main :: IO ()
-main = do
-  fd <- openUDP "127.0.0.1" 57110
-  r <- timeout 0.5 (recv fd)
-  print r
diff --git a/README b/README
--- a/README
+++ b/README
@@ -8,9 +8,8 @@
   http://haskell.org/
   http://opensoundcontrol.org/
 
-(c) rohan drape and others, 2006-2011
+(c) rohan drape, stefan kersten and others, 2006-2011
     gpl, http://gnu.org/copyleft/
     with contributions by alex mclean
-                        & stefan kersten
                         & henning thielemann
     see darcs history for details
diff --git a/Sound/OpenSoundControl.hs b/Sound/OpenSoundControl.hs
--- a/Sound/OpenSoundControl.hs
+++ b/Sound/OpenSoundControl.hs
@@ -1,17 +1,32 @@
 -- | hosc implements a subset of the Open Sound Control byte protocol.
 --   The protocol is documented at <http://opensoundcontrol.org/>.
-module Sound.OpenSoundControl (module Sound.OpenSoundControl.OSC,
-                               module Sound.OpenSoundControl.Time,
-                               module Sound.OpenSoundControl.Cast,
-                               module Sound.OpenSoundControl.Byte,
-                               module Sound.OpenSoundControl.Transport,
-                               module Sound.OpenSoundControl.Transport.UDP,
-                               module Sound.OpenSoundControl.Transport.TCP) where
+module Sound.OpenSoundControl (module O
+                              ,C.encodeOSC,C.decodeOSC
+                              ,openUDP,udpServer
+                              ,openTCP,tcpServer) where
 
-import Sound.OpenSoundControl.OSC
-import Sound.OpenSoundControl.Time
-import Sound.OpenSoundControl.Cast
-import Sound.OpenSoundControl.Byte
-import Sound.OpenSoundControl.Transport
-import Sound.OpenSoundControl.Transport.UDP
-import Sound.OpenSoundControl.Transport.TCP
+import qualified Sound.OpenSoundControl.Coding.Decode.Binary as C
+import qualified Sound.OpenSoundControl.Coding.Encode.Builder as C
+import Sound.OpenSoundControl.Type as O
+import Sound.OpenSoundControl.Time as O
+import Sound.OpenSoundControl.Transport as O
+import Sound.OpenSoundControl.Transport.UDP as O
+import Sound.OpenSoundControl.Transport.TCP as O
+
+-- | Make a UDP connection.
+--
+-- > withTransport (openUDP "127.0.0.1" 57110) (\fd -> recvT 0.5 fd >>= print)
+openUDP :: String -> Int -> IO UDP
+openUDP = openUDP' (C.encodeOSC,C.decodeOSC)
+
+-- | Trivial udp server.
+udpServer :: String -> Int -> IO UDP
+udpServer = udpServer' (C.encodeOSC,C.decodeOSC)
+
+-- | Make a TCP connection.
+openTCP :: String -> Int -> IO TCP
+openTCP = openTCP' (C.encodeOSC,C.decodeOSC)
+
+-- | A trivial TCP OSC server.
+tcpServer :: Int -> (TCP -> IO ()) -> IO ()
+tcpServer = tcpServer' (C.encodeOSC,C.decodeOSC)
diff --git a/Sound/OpenSoundControl/Byte.hs b/Sound/OpenSoundControl/Byte.hs
deleted file mode 100644
--- a/Sound/OpenSoundControl/Byte.hs
+++ /dev/null
@@ -1,81 +0,0 @@
--- | Byte-level encoding and decoding functions.
-module Sound.OpenSoundControl.Byte where
-
-import Data.Binary
-import qualified Data.ByteString.Lazy as B
-import qualified Data.ByteString.Lazy.Char8 as BC
-import Data.Char
-import Data.Int
-import Sound.OpenSoundControl.Cast
-
--- | Encode a signed 8-bit integer.
-encode_i8 :: Int -> B.ByteString
-encode_i8 n = encode (fromIntegral n :: Int8)
-
--- | Encode a signed 16-bit integer.
-encode_i16 :: Int -> B.ByteString
-encode_i16 n = encode (fromIntegral n :: Int16)
-
--- | Encode a signed 32-bit integer.
-encode_i32 :: Int -> B.ByteString
-encode_i32 n = encode (fromIntegral n :: Int32)
-
--- | Encode an unsigned 16-bit integer.
-encode_u32 :: Int -> B.ByteString
-encode_u32 n = encode (fromIntegral n :: Word32)
-
--- | Encode a signed 64-bit integer.
-encode_i64 :: Integer -> B.ByteString
-encode_i64 n = encode (fromIntegral n :: Int64)
-
--- | Encode an unsigned 64-bit integer.
-encode_u64 :: Integer -> B.ByteString
-encode_u64 n = encode (fromIntegral n :: Word64)
-
--- | Encode a 32-bit IEEE floating point number.
-encode_f32 :: Double -> B.ByteString
-encode_f32 = encode . f32_i32 . realToFrac
-
--- | Encode a 64-bit IEEE floating point number.
-encode_f64 :: Double -> B.ByteString
-encode_f64 = encode . f64_i64
-
--- | Encode an ASCII string.
-encode_str :: String -> B.ByteString
-encode_str = B.pack . map (fromIntegral . ord)
-
--- | Decode a signed 8-bit integer.
-decode_i8 :: B.ByteString -> Int
-decode_i8 b = fromIntegral (decode b :: Int8)
-
--- | Decode a signed 16-bit integer.
-decode_i16 :: B.ByteString -> Int
-decode_i16 b = fromIntegral (decode b :: Int16)
-
--- | Decode a signed 32-bit integer.
-decode_i32 :: B.ByteString -> Int
-decode_i32 b = fromIntegral (decode b :: Int32)
-
--- | Decode an unsigned 32-bit integer.
-decode_u32 :: B.ByteString -> Int
-decode_u32 b = fromIntegral (decode b :: Word32)
-
--- | Decode a signed 64-bit integer.
-decode_i64 :: B.ByteString -> Integer
-decode_i64 b = fromIntegral (decode b :: Int64)
-
--- | Decode an unsigned 64-bit integer.
-decode_u64 :: B.ByteString -> Integer
-decode_u64 b = fromIntegral (decode b :: Word64)
-
--- | Decode a 32-bit IEEE floating point number.
-decode_f32 :: B.ByteString -> Double
-decode_f32 b = realToFrac (i32_f32 (decode b :: Int32))
-
--- | Decode a 64-bit IEEE floating point number.
-decode_f64 :: B.ByteString -> Double
-decode_f64 b = i64_f64 (decode b :: Int64)
-
--- | Decode an ASCII string.
-decode_str :: B.ByteString -> String
-decode_str = BC.unpack
diff --git a/Sound/OpenSoundControl/Cast.hs b/Sound/OpenSoundControl/Cast.hs
deleted file mode 100644
--- a/Sound/OpenSoundControl/Cast.hs
+++ /dev/null
@@ -1,62 +0,0 @@
--- | Bit-level type casts and byte layout string typecasts.
-module Sound.OpenSoundControl.Cast (f32_i32, i32_f32,
-                                    f64_i64, i64_f64,
-                                    str_cstr, cstr_str,
-                                    str_pstr, pstr_str) where
-
-import Control.Monad.ST
-import Data.Array.ST
-import Data.Char
-import Data.Int
-import Data.Word
-
--- | The IEEE byte representation of a float packed into an integer.
-f32_i32 :: Float -> Int32
-f32_i32 d = runST ((from_array =<< castSTUArray =<< unit_array d) :: ST s Int32)
-
--- | Inverse of 'f32_i32'.
-i32_f32 :: Int32 -> Float
-i32_f32 d = runST ((from_array =<< castSTUArray =<< unit_array d) :: ST s Float)
-
--- | The IEEE byte representation of a double packed into an integer.
-f64_i64 :: Double -> Int64
-f64_i64 d = runST ((from_array =<< castSTUArray =<< unit_array d) :: ST s Int64)
-
-{-
-http://www.haskell.org/pipermail/haskell-cafe/2008-February/040000.html
-
-{-# LANGUAGE MagicHash #-}
-import Data.Int
-import GHC.Exts
-
-f64_i64 :: Double -> Int64 --ghc only
-f64_i64 (D# x) = I64# (unsafeCoerce# x)
--}
-
--- | Inverse of 'f64_i64'.
-i64_f64 :: Int64 -> Double
-i64_f64 d = runST ((from_array =<< castSTUArray =<< unit_array d) :: ST s Double)
-
--- | Transform a haskell string into a C string (a null suffixed byte string).
-str_cstr :: String -> [Word8]
-str_cstr s = map (fromIntegral . ord) s ++ [0]
-
--- | Inverse of 'str_cstr'.
-cstr_str :: [Word8] -> String
-cstr_str = map (chr . fromIntegral) . takeWhile (/= 0)
-
--- | Transform a haskell string to a pascal string (a length prefixed byte string).
-str_pstr :: String -> [Word8]
-str_pstr s = fromIntegral (length s) : map (fromIntegral . ord) s
-
--- | Inverse of 'str_pstr'.
-pstr_str :: [Word8] -> String
-pstr_str = map (chr . fromIntegral) . drop 1
-
--- One element marray.
-unit_array :: (MArray a e m) => e -> m (a Int e)
-unit_array = newArray (0, 0::Int)
-
--- Extract first element from array.
-from_array :: (MArray a e m) => a Int e -> m e
-from_array = flip readArray 0
diff --git a/Sound/OpenSoundControl/Coding.hs b/Sound/OpenSoundControl/Coding.hs
new file mode 100644
--- /dev/null
+++ b/Sound/OpenSoundControl/Coding.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE TypeSynonymInstances #-}
+-- | A type-class to provide coding operations to different data types
+--   using the same function names.
+module Sound.OpenSoundControl.Coding (Coding(..) ) where
+
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Lazy as B
+import qualified Data.ByteString.Lazy.Char8 as C
+import           Sound.OpenSoundControl.Type (OSC)
+import qualified Sound.OpenSoundControl.Coding.Decode.Binary as Binary
+import qualified Sound.OpenSoundControl.Coding.Encode.Builder as Builder
+
+-- | Converting from and to binary packet representations.
+class Coding a where
+    -- | Decode an OSC packet.
+    encodeOSC :: OSC -> a
+    -- | Encode an OSC packet.
+    decodeOSC :: a -> OSC
+
+instance Coding S.ByteString where
+    encodeOSC = Builder.encodeOSC'
+    decodeOSC = Binary.decodeOSC'
+
+instance Coding B.ByteString where
+    encodeOSC = Builder.encodeOSC
+    decodeOSC = Binary.decodeOSC
+
+instance Coding String where
+    encodeOSC = C.unpack . encodeOSC
+    decodeOSC = decodeOSC . C.pack
diff --git a/Sound/OpenSoundControl/Coding/Byte.hs b/Sound/OpenSoundControl/Coding/Byte.hs
new file mode 100644
--- /dev/null
+++ b/Sound/OpenSoundControl/Coding/Byte.hs
@@ -0,0 +1,94 @@
+-- | Byte-level coding utility functions.
+module Sound.OpenSoundControl.Coding.Byte where
+
+import Data.Binary
+import Data.Bits
+import qualified Data.ByteString.Lazy as B
+import qualified Data.ByteString.Lazy.Char8 as C
+import Data.Int
+import Sound.OpenSoundControl.Coding.Cast
+
+-- | Encode a signed 8-bit integer.
+encode_i8 :: Int -> B.ByteString
+encode_i8 n = encode (fromIntegral n :: Int8)
+
+-- | Encode a signed 16-bit integer.
+encode_i16 :: Int -> B.ByteString
+encode_i16 n = encode (fromIntegral n :: Int16)
+
+-- | Encode a signed 32-bit integer.
+encode_i32 :: Int -> B.ByteString
+encode_i32 n = encode (fromIntegral n :: Int32)
+
+-- | Encode an unsigned 16-bit integer.
+encode_u32 :: Int -> B.ByteString
+encode_u32 n = encode (fromIntegral n :: Word32)
+
+-- | Encode a signed 64-bit integer.
+encode_i64 :: Int64 -> B.ByteString
+encode_i64 = encode
+
+-- | Encode an unsigned 64-bit integer.
+encode_u64 :: Word64 -> B.ByteString
+encode_u64 = encode
+
+-- | Encode a 32-bit IEEE floating point number.
+encode_f32 :: Double -> B.ByteString
+encode_f32 = encode . f32_w32 . realToFrac
+
+-- | Encode a 64-bit IEEE floating point number.
+encode_f64 :: Double -> B.ByteString
+encode_f64 = encode . f64_w64
+
+-- | Encode an ASCII string.
+encode_str :: String -> B.ByteString
+{-# INLINE encode_str #-}
+encode_str = C.pack
+
+-- | Decode a signed 8-bit integer.
+decode_i8 :: B.ByteString -> Int
+decode_i8 b = fromIntegral (decode b :: Int8)
+
+-- | Decode a signed 16-bit integer.
+decode_i16 :: B.ByteString -> Int
+decode_i16 b = fromIntegral (decode b :: Int16)
+
+-- | Decode a signed 32-bit integer.
+decode_i32 :: B.ByteString -> Int
+decode_i32 b = fromIntegral (decode b :: Int32)
+
+-- | Decode an unsigned 32-bit integer.
+decode_u32 :: B.ByteString -> Int
+decode_u32 b = fromIntegral (decode b :: Word32)
+
+-- | Decode a signed 64-bit integer.
+decode_i64 :: B.ByteString -> Int64
+decode_i64 = decode
+
+-- | Decode an unsigned 64-bit integer.
+decode_u64 :: B.ByteString -> Word64
+decode_u64 = decode
+
+-- | Decode a 32-bit IEEE floating point number.
+decode_f32 :: B.ByteString -> Double
+decode_f32 b = realToFrac (w32_f32 (decode b :: Word32))
+
+-- | Decode a 64-bit IEEE floating point number.
+decode_f64 :: B.ByteString -> Double
+decode_f64 b = w64_f64 (decode b :: Word64)
+
+-- | Decode an ASCII string.
+decode_str :: B.ByteString -> String
+{-# INLINE decode_str #-}
+decode_str = C.unpack
+
+-- | Bundle header string.
+bundleHeader :: B.ByteString
+{-# INLINE bundleHeader #-}
+bundleHeader = C.pack "#bundle\0"
+
+-- | The number of bytes required to align an OSC value to the next
+--   4-byte boundary.
+align :: Bits i => i -> i
+{-# INLINE align #-}
+align n = ((n + 3) .&. complement 3) - n
diff --git a/Sound/OpenSoundControl/Coding/Cast.hs b/Sound/OpenSoundControl/Coding/Cast.hs
new file mode 100644
--- /dev/null
+++ b/Sound/OpenSoundControl/Coding/Cast.hs
@@ -0,0 +1,43 @@
+-- | Bit-level type casts and byte layout string typecasts.
+module Sound.OpenSoundControl.Coding.Cast (f32_w32,w32_f32
+                                          ,f64_w64,w64_f64
+                                          ,str_cstr,cstr_str
+                                          ,str_pstr,pstr_str) where
+
+import qualified Data.Binary.IEEE754 as I
+import Data.Char
+import Data.Word
+
+-- | The IEEE byte representation of a float.
+f32_w32 :: Float -> Word32
+f32_w32 = I.floatToWord
+
+-- | Inverse of 'f32_w32'.
+w32_f32 :: Word32 -> Float
+w32_f32 = I.wordToFloat
+
+-- | The IEEE byte representation of a double.
+f64_w64 :: Double -> Word64
+f64_w64 = I.doubleToWord
+
+-- | Inverse of 'f64_i64'.
+w64_f64 :: Word64 -> Double
+w64_f64 = I.wordToDouble
+
+-- | Transform a haskell string into a C string (a null suffixed byte
+--   string).
+str_cstr :: String -> [Word8]
+str_cstr s = map (fromIntegral . ord) s ++ [0]
+
+-- | Inverse of 'str_cstr'.
+cstr_str :: [Word8] -> String
+cstr_str = map (chr . fromIntegral) . takeWhile (/= 0)
+
+-- | Transform a haskell string to a pascal string (a length prefixed
+--   byte string).
+str_pstr :: String -> [Word8]
+str_pstr s = fromIntegral (length s) : map (fromIntegral . ord) s
+
+-- | Inverse of 'str_pstr'.
+pstr_str :: [Word8] -> String
+pstr_str = map (chr . fromIntegral) . drop 1
diff --git a/Sound/OpenSoundControl/Coding/Coerce.hs b/Sound/OpenSoundControl/Coding/Coerce.hs
new file mode 100644
--- /dev/null
+++ b/Sound/OpenSoundControl/Coding/Coerce.hs
@@ -0,0 +1,38 @@
+-- | OSC packet coercion and normalization.
+module Sound.OpenSoundControl.Coding.Coerce where
+
+import Sound.OpenSoundControl.Type
+
+-- | Map a normalizing function over datum at an osc packet.
+coerce :: (Datum -> Datum) -> OSC -> OSC
+coerce f o =
+    case o of
+      Message s xs -> Message s (map f xs)
+      Bundle t xs -> Bundle t (map (coerce f) xs)
+
+-- | Coerce Float to Double.
+f_to_d :: Datum -> Datum
+f_to_d d =
+    case d of
+      Float n -> Double n
+      _ -> d
+
+-- | Coerce Int and Float to Double.
+if_to_d :: Datum -> Datum
+if_to_d d =
+    case d of
+      Int n -> Double (fromIntegral n)
+      Float n -> Double n
+      _ -> d
+
+-- | Coerce Float and Double to Int.
+fd_to_i :: Datum -> Datum
+fd_to_i d =
+    case d of
+      Float n -> Int (round n)
+      Double n -> Int (round n)
+      _ -> d
+
+-- | A normalized osc packet has only Int and Double numerical values.
+normalize :: OSC -> OSC
+normalize = coerce f_to_d
diff --git a/Sound/OpenSoundControl/Coding/Decode/Base.hs b/Sound/OpenSoundControl/Coding/Decode/Base.hs
new file mode 100644
--- /dev/null
+++ b/Sound/OpenSoundControl/Coding/Decode/Base.hs
@@ -0,0 +1,92 @@
+-- | Base-level decode function for OSC packets (slow).  For ordinary
+--   use see 'Sound.OpenSoundControl.Coding.Decode.Binary'.
+module Sound.OpenSoundControl.Coding.Decode.Base (decodeOSC) where
+
+import qualified Data.ByteString.Lazy as B
+import Data.List
+import Data.Maybe
+import Sound.OpenSoundControl.Coding.Byte
+import Sound.OpenSoundControl.Time
+import Sound.OpenSoundControl.Type
+
+-- The plain byte count of an OSC value.
+size :: Char -> B.ByteString -> Int
+size ty b =
+    case ty of
+      'i' -> 4
+      'f' -> 4
+      'd' -> 8
+      't' -> 8 -- timetag
+      'm' -> 4 -- MIDI message
+      's' -> fromIntegral (fromMaybe
+                           (error ("size: no terminating zero: " ++ show b))
+                           (B.elemIndex 0 b))
+      'b' -> decode_i32 (B.take 4 b)
+      _ -> error "size: illegal type"
+
+-- The storage byte count of an OSC value.
+storage :: Char -> B.ByteString -> Int
+storage ty b =
+    case ty of
+      's' -> let n = size 's' b + 1 in n + align n
+      'b' -> let n = size 'b' b in n + align n + 4
+      _ -> size ty B.empty
+
+-- Decode an OSC datum
+decode_datum :: Char -> B.ByteString -> Datum
+decode_datum ty b =
+    case ty of
+      'i' -> Int (decode_i32 b)
+      'f' -> Float (decode_f32 b)
+      'd' -> Double (decode_f64 b)
+      's' -> String (decode_str (b_take (size 's' b) b))
+      'b' -> Blob (b_take (size 'b' b) (B.drop 4 b))
+      't' -> TimeStamp $ NTPi (decode_u64 b)
+      'm' -> let [b0,b1,b2,b3] = B.unpack (B.take 4 b) in Midi (b0,b1,b2,b3)
+      _ -> error ("decode_datum: illegal type (" ++ [ty] ++ ")")
+
+-- Decode a sequence of OSC datum given a type descriptor string.
+decode_datum_seq :: String -> B.ByteString -> [Datum]
+decode_datum_seq cs b =
+    let swap (x,y) = (y,x)
+        f b' c = swap (B.splitAt (fromIntegral (storage c b')) b')
+    in zipWith decode_datum cs (snd (mapAccumL f b cs))
+
+-- Decode an OSC message.
+decode_message :: B.ByteString -> OSC
+decode_message b =
+    let n = storage 's' b
+        (String cmd) = decode_datum 's' b
+        m = storage 's' (b_drop n b)
+        (String dsc) = decode_datum 's' (b_drop n b)
+        arg = decode_datum_seq (drop 1 dsc) (b_drop (n + m) b)
+    in Message cmd arg
+
+-- Decode a sequence of OSC messages, each one headed by its length
+decode_message_seq :: B.ByteString -> [OSC]
+decode_message_seq b =
+    let s = decode_i32 b
+        m = decode_message $ b_drop 4 b
+        nxt = decode_message_seq $ b_drop (4+s) b
+    in if B.length b == 0 then [] else m:nxt
+
+decode_bundle :: B.ByteString -> OSC
+decode_bundle b =
+    let h = storage 's' b -- header (should be '#bundle')
+        t = storage 't' (b_drop h b) -- time tag
+        (TimeStamp timeStamp) = decode_datum 't' (b_drop h b)
+        ms = decode_message_seq $ b_drop (h+t) b
+    in Bundle timeStamp ms
+
+-- | Decode an OSC packet.
+decodeOSC :: B.ByteString -> OSC
+decodeOSC b =
+    if bundleHeader `B.isPrefixOf` b
+    then decode_bundle b
+    else decode_message b
+
+b_take :: Int -> B.ByteString -> B.ByteString
+b_take = B.take . fromIntegral
+
+b_drop :: Int -> B.ByteString -> B.ByteString
+b_drop = B.drop . fromIntegral
diff --git a/Sound/OpenSoundControl/Coding/Decode/Binary.hs b/Sound/OpenSoundControl/Coding/Decode/Binary.hs
new file mode 100644
--- /dev/null
+++ b/Sound/OpenSoundControl/Coding/Decode/Binary.hs
@@ -0,0 +1,113 @@
+-- | Optimised decode function for OSC packets.
+module Sound.OpenSoundControl.Coding.Decode.Binary (getOSC
+                                                   ,decodeOSC
+                                                   ,decodeOSC') where
+
+import Control.Applicative
+import Data.Binary.Get
+import qualified Data.Binary.IEEE754 as I
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Lazy as B
+import qualified Data.ByteString.Lazy.Char8 as C
+import Data.Int (Int32)
+import Data.Word (Word32)
+import Sound.OpenSoundControl.Coding.Byte
+import Sound.OpenSoundControl.Time
+import Sound.OpenSoundControl.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
+    let (a, s', _) = runGetState m s 0
+    if B.null s'
+        then return a
+        else fail "isolate: not all bytes consumed"
+
+-- | Get a 32 biut integer in big-endian byte order.
+getInt32be :: Get Int32
+getInt32be = fromIntegral <$> getWord32be
+
+-- | Get an aligned OSC string.
+get_string :: Get String
+get_string = do
+    s <- getLazyByteStringNul
+    skip (fromIntegral (align (B.length s + 1)))
+    return $ C.unpack s
+
+-- | Get binary data prefixed by byte count.
+get_bytes :: Word32 -> Get B.ByteString
+get_bytes n = do
+    b <- getLazyByteString (fromIntegral n)
+    if n /= fromIntegral (B.length b)
+        then fail "get_bytes: end of stream"
+        else skip (fromIntegral (align n))
+    return b
+
+-- | Get an OSC datum.
+get_datum :: Char -> Get Datum
+get_datum 'i' = Int    <$> fromIntegral <$> getInt32be
+get_datum 'f' = Float  <$> realToFrac <$> I.getFloat32be
+get_datum 'd' = Double <$> I.getFloat64be
+get_datum 's' = String <$> get_string
+get_datum 'b' = Blob   <$> (get_bytes =<< getWord32be)
+get_datum 't' = TimeStamp <$> NTPi <$> getWord64be
+get_datum 'm' = do
+    b0 <- getWord8
+    b1 <- getWord8
+    b2 <- getWord8
+    b3 <- getWord8
+    return $ Midi (b0,b1,b2,b3)
+get_datum t = fail ("get_datum: illegal type " ++ show t)
+
+-- | Get an OSC message.
+get_message :: Get OSC
+get_message = do
+    cmd <- get_string
+    dsc <- get_string
+    case dsc of
+        (',':tags) -> do
+            arg <- mapM get_datum tags
+            return $ Message cmd arg
+        _ -> fail "get_message: invalid type descriptor string"
+
+-- | Get an OSC packet.
+get_packet :: Get OSC
+get_packet = do
+    h <- uncheckedLookAhead (B.length bundleHeader)
+    if h == bundleHeader
+        then get_bundle
+        else get_message
+
+-- | Get a sequence of OSC messages, each one headed by its length.
+get_packet_seq :: Get [OSC]
+get_packet_seq = do
+    b <- isEmpty
+    if b
+        then return []
+        else do
+            p <- flip isolate get_packet =<< getWord32be
+            ps <- get_packet_seq
+            return (p:ps)
+
+get_bundle :: Get OSC
+get_bundle = do
+    skip (fromIntegral (B.length bundleHeader))
+    t <- NTPi <$> getWord64be
+    ps <- get_packet_seq
+    return $ Bundle t ps
+
+-- | Get an OSC packet.
+getOSC :: Get OSC
+getOSC = get_packet
+
+-- | Decode an OSC packet from a lazy ByteString.
+decodeOSC :: B.ByteString -> OSC
+{-# INLINE decodeOSC #-}
+decodeOSC = runGet getOSC
+
+-- | Decode an OSC packet from a strict ByteString.
+decodeOSC' :: S.ByteString -> OSC
+{-# INLINE decodeOSC' #-}
+decodeOSC' = runGet getOSC . B.fromChunks . (:[])
diff --git a/Sound/OpenSoundControl/Coding/Encode/Base.hs b/Sound/OpenSoundControl/Coding/Encode/Base.hs
new file mode 100644
--- /dev/null
+++ b/Sound/OpenSoundControl/Coding/Encode/Base.hs
@@ -0,0 +1,57 @@
+-- | Base-level encode function for OSC packets (slow).  For ordinary
+--   use see 'Sound.OpenSoundControl.Coding.Encode.Builder'.
+module Sound.OpenSoundControl.Coding.Encode.Base (encodeOSC) where
+
+import qualified Data.ByteString.Lazy as B
+import Data.Word
+import Sound.OpenSoundControl.Coding.Byte
+import Sound.OpenSoundControl.Type
+import Sound.OpenSoundControl.Time
+
+-- Command argument types are given by a descriptor.
+descriptor :: [Datum] -> Datum
+descriptor l = String (',' : map tag l)
+
+-- Align a byte string if required.
+extend :: Word8 -> B.ByteString -> B.ByteString
+extend p s = B.append s (B.replicate (align (B.length s)) p)
+
+-- Encode an OSC datum.
+encode_datum :: Datum -> B.ByteString
+encode_datum dt =
+    case dt of
+      Int i -> encode_i32 i
+      Float f -> encode_f32 f
+      Double d -> encode_f64 d
+      TimeStamp t -> encode_u64 $ as_ntpi t
+      String s -> extend 0 (B.snoc (encode_str s) 0)
+      Midi (b0,b1,b2,b3) -> B.pack [b0,b1,b2,b3]
+      Blob b -> let n = encode_i32 (fromIntegral (B.length b))
+                in B.append n (extend 0 b)
+
+-- Encode an OSC message.
+encode_message :: String -> [Datum] -> B.ByteString
+encode_message c l =
+    B.concat [encode_datum (String c)
+             ,encode_datum (descriptor l)
+             ,B.concat (map encode_datum l) ]
+
+-- Encode an OSC packet as an OSC blob.
+encode_osc_blob :: OSC -> Datum
+encode_osc_blob = Blob . encodeOSC
+
+-- Encode an OSC bundle.
+encode_bundle_ntpi :: NTPi -> [OSC] -> B.ByteString
+encode_bundle_ntpi t l =
+    B.concat [bundleHeader
+             ,encode_u64 t
+             ,B.concat (map (encode_datum . encode_osc_blob) l) ]
+
+-- | Encode an OSC packet.
+encodeOSC :: OSC -> B.ByteString
+encodeOSC o =
+    case o of
+      Message c l -> encode_message c l
+      Bundle (NTPi t) l -> encode_bundle_ntpi t l
+      Bundle (NTPr t) l -> encode_bundle_ntpi (ntpr_ntpi t) l
+      Bundle (UTCr t) l -> encode_bundle_ntpi (utcr_ntpi t) l
diff --git a/Sound/OpenSoundControl/Coding/Encode/Builder.hs b/Sound/OpenSoundControl/Coding/Encode/Builder.hs
new file mode 100644
--- /dev/null
+++ b/Sound/OpenSoundControl/Coding/Encode/Builder.hs
@@ -0,0 +1,74 @@
+-- | Optimised encode function for OSC packets.
+module Sound.OpenSoundControl.Coding.Encode.Builder (buildOSC
+                                                    ,encodeOSC
+                                                    ,encodeOSC') where
+
+import qualified Data.Binary.IEEE754 as I
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Lazy as L
+import qualified Blaze.ByteString.Builder as B
+import qualified Blaze.ByteString.Builder.Char8 as B
+import Data.Monoid (mappend, mconcat)
+import Data.Word (Word8)
+import Sound.OpenSoundControl.Coding.Byte (align, bundleHeader)
+import Sound.OpenSoundControl.Time
+import Sound.OpenSoundControl.Type (Datum(..), OSC(..), tag)
+
+-- Command argument types are given by a descriptor.
+descriptor :: [Datum] -> String
+descriptor l = ',' : map tag l
+
+-- Generate a list of zero bytes for padding.
+padding :: Integral i => i -> [Word8]
+padding n = replicate (fromIntegral n) 0
+
+-- Encode a string with zero padding.
+build_string :: String -> B.Builder
+build_string s = B.fromString s `mappend` B.fromWord8s (0:padding (align (length s + 1)))
+
+-- Encode a byte string with prepended length and zero padding.
+build_bytes :: L.ByteString -> B.Builder
+build_bytes s = B.fromInt32be (fromIntegral (L.length s))
+                `mappend` B.fromLazyByteString s
+                `mappend` B.fromWord8s (padding (align (L.length s)))
+
+-- Encode an OSC datum.
+build_datum :: Datum -> B.Builder
+build_datum (Int i)              = B.fromInt32be (fromIntegral i)
+build_datum (Float f)            = B.fromWord32be (I.floatToWord (realToFrac f))
+build_datum (Double d)           = B.fromWord64be (I.doubleToWord d)
+build_datum (TimeStamp t)        = B.fromWord64be (fromIntegral (as_ntpi t))
+build_datum (String s)           = build_string s
+build_datum (Midi (b0,b1,b2,b3)) = B.fromWord8s [b0,b1,b2,b3]
+build_datum (Blob b)             = build_bytes b
+
+-- Encode an OSC message.
+build_message :: String -> [Datum] -> B.Builder
+build_message c l =
+    mconcat [ build_string c
+            , build_string (descriptor l)
+            , mconcat $ map build_datum l ]
+
+-- Encode an OSC bundle.
+build_bundle_ntpi :: NTPi -> [OSC] -> B.Builder
+build_bundle_ntpi t l =
+    mconcat [ B.fromLazyByteString bundleHeader
+            , B.fromWord64be t
+            , mconcat $ map (build_bytes . B.toLazyByteString . buildOSC) l ]
+
+-- | Builder monoid for an OSC packet.
+buildOSC :: OSC -> B.Builder
+buildOSC (Message c l)       = build_message c l
+buildOSC (Bundle (NTPi t) l) = build_bundle_ntpi t l
+buildOSC (Bundle (NTPr t) l) = build_bundle_ntpi (ntpr_ntpi t) l
+buildOSC (Bundle (UTCr t) l) = build_bundle_ntpi (utcr_ntpi t) l
+
+-- | Encode an OSC packet to a lazy ByteString.
+encodeOSC :: OSC -> L.ByteString
+{-# INLINE encodeOSC #-}
+encodeOSC = B.toLazyByteString . buildOSC
+
+-- | Encode an OSC packet to a strict ByteString.
+encodeOSC' :: OSC -> S.ByteString
+{-# INLINE encodeOSC' #-}
+encodeOSC' = B.toByteString . buildOSC
diff --git a/Sound/OpenSoundControl/Coerce.hs b/Sound/OpenSoundControl/Coerce.hs
deleted file mode 100644
--- a/Sound/OpenSoundControl/Coerce.hs
+++ /dev/null
@@ -1,30 +0,0 @@
--- | OSC packet coercion and normalization.
-module Sound.OpenSoundControl.Coerce where
-
-import Sound.OpenSoundControl.OSC
-
--- | Map a normalizing function over datum at an osc packet.
-coerce :: (Datum -> Datum) -> OSC -> OSC
-coerce f (Message s xs) = Message s (map f xs)
-coerce f (Bundle t xs) = Bundle t (map (coerce f) xs)
-
--- | Coerce Float to Double.
-f_to_d :: Datum -> Datum
-f_to_d (Float n) = Double n
-f_to_d x = x
-
--- | Coerce Int and Float to Double.
-if_to_d :: Datum -> Datum
-if_to_d (Int n) = Double (fromIntegral n)
-if_to_d (Float n) = Double n
-if_to_d x = x
-
--- | Coerce Float and Double to Int.
-fd_to_i :: Datum -> Datum
-fd_to_i (Float n) = Int (round n)
-fd_to_i (Double n) = Int (round n)
-fd_to_i x = x
-
--- | A normalized osc packet has only Int and Double numerical values.
-normalize :: OSC -> OSC
-normalize = coerce f_to_d
diff --git a/Sound/OpenSoundControl/OSC.hs b/Sound/OpenSoundControl/OSC.hs
deleted file mode 100644
--- a/Sound/OpenSoundControl/OSC.hs
+++ /dev/null
@@ -1,178 +0,0 @@
--- | Alegbraic data types for OSC packets and encode and decode
---   functions.
-module Sound.OpenSoundControl.OSC ( OSC(..)
-                                  , Datum(..)
-                                  , message
-                                  , bundle
-                                  , encodeOSC
-                                  , decodeOSC ) where
-
-import qualified Data.ByteString.Lazy as B
-import Data.List
-import Data.Maybe
-import Data.Word
-import Sound.OpenSoundControl.Time
-import Sound.OpenSoundControl.Byte
-import Sound.OpenSoundControl.Cast
-
--- | The basic elements of OSC messages.
-data Datum = Int Int
-           | Float Double
-           | Double Double
-           | String String
-           | Blob [Word8]
-           | TimeStamp Time
-           | Midi (Word8,Word8,Word8,Word8)
-             deriving (Eq, Show)
-
--- | An OSC packet.
-data OSC = Message String [Datum]
-         | Bundle Time [OSC]
-           deriving (Eq, Show)
-
--- | OSC bundles can be ordered (time ascending).  Bundles and
---   messages compare EQ.
-instance Ord OSC where
-    compare (Bundle a _) (Bundle b _) = compare a b
-    compare _ _ = EQ
-
--- OSC types have single character identifiers.
-tag :: Datum -> Char
-tag (Int _) = 'i'
-tag (Float _) = 'f'
-tag (Double _) = 'd'
-tag (String _) = 's'
-tag (Blob _) = 'b'
-tag (TimeStamp _) = 't'
-tag (Midi _) = 'm'
-
--- Command argument types are given by a descriptor.
-descriptor :: [Datum] -> Datum
-descriptor l = String (',' : map tag l)
-
--- The number of bytes required to align an OSC value.
-align :: Int -> Int
-align n = (-n) `mod` 4
-
--- Align a byte string if required.
-extend :: a -> [a] -> [a]
-extend p s = s ++ replicate (align (length s)) p
-
--- Encode an OSC datum.
-encode_datum :: Datum -> B.ByteString
-encode_datum (Int i) = encode_i32 i
-encode_datum (Float f) = encode_f32 f
-encode_datum (Double d) = encode_f64 d
-encode_datum (TimeStamp t) = encode_u64 $ as_ntpi t
-encode_datum (String s) = B.pack (extend 0 (str_cstr s))
-encode_datum (Midi (b0,b1,b2,b3)) = B.pack [b0,b1,b2,b3]
-encode_datum (Blob b) = B.concat [encode_i32 (length b), B.pack (extend 0 b)]
-
--- Encode an OSC message.
-encode_message :: String -> [Datum] -> B.ByteString
-encode_message c l = 
-    B.concat [ encode_datum (String c)
-             , encode_datum (descriptor l)
-             , B.concat (map encode_datum l) ]
-
--- Encode an OSC packet as an OSC blob.
-encode_osc_blob :: OSC -> Datum
-encode_osc_blob = Blob . B.unpack . encodeOSC
-
--- Encode an OSC bundle.
-encode_bundle_ntpi :: Integer -> [OSC] -> B.ByteString
-encode_bundle_ntpi t l =
-    B.concat [ bundle_header
-             , encode_u64 t
-             , B.concat (map (encode_datum . encode_osc_blob) l) ]
-
--- | Encode an OSC packet.
-encodeOSC :: OSC -> B.ByteString
-encodeOSC (Message c l) = encode_message c l
-encodeOSC (Bundle (NTPi t) l) = encode_bundle_ntpi t l
-encodeOSC (Bundle (NTPr t) l) = encode_bundle_ntpi (ntpr_ntpi t) l
-encodeOSC (Bundle (UTCr t) l) = encode_bundle_ntpi (utcr_ntpi t) l
-
--- The plain byte count of an OSC value.
-size :: Char -> B.ByteString -> Int
-size 'i' _ = 4
-size 'f' _ = 4
-size 'd' _ = 8
-size 't' _ = 8 -- timetag
-size 's' b = fromIntegral (fromMaybe
-                           (error ("size: no terminating zero: " ++ show b))
-                           (B.elemIndex 0 b))
-size 'b' b = decode_i32 (B.take 4 b)
-size _ _ = error "size: illegal type"
-
--- The storage byte count of an OSC value.
-storage :: Char -> B.ByteString -> Int
-storage 's' b = n + align n where n = size 's' b + 1
-storage 'b' b = n + align n + 4 where n = size 'b' b
-storage c _ = size c B.empty
-
--- Decode an OSC datum
-decode_datum :: Char -> B.ByteString -> Datum
-decode_datum 'i' b = Int (decode_i32 b)
-decode_datum 'f' b = Float (decode_f32 b)
-decode_datum 'd' b = Double (decode_f64 b)
-decode_datum 's' b = String (decode_str (b_take n b)) where n = size 's' b
-decode_datum 'b' b = Blob (B.unpack (b_take n (B.drop 4 b))) where n = size 'b' b
-decode_datum 't' b = TimeStamp $ NTPi (decode_u64 b)
-decode_datum t _ = error ("decode_datum: illegal type (" ++ [t] ++ ")")
-
--- Decode a sequence of OSC datum given a type descriptor string.
-decode_datum_seq :: [Char] -> B.ByteString -> [Datum]
-decode_datum_seq cs b = zipWith decode_datum cs (snd (mapAccumL f b cs))
-    where swap (x,y) = (y,x)
-          f b' c = swap (B.splitAt (fromIntegral (storage c b')) b')
-
--- Decode an OSC message.
-decode_message :: B.ByteString -> OSC
-decode_message b = Message cmd arg
-    where n = storage 's' b
-          (String cmd) = decode_datum 's' b
-          m = storage 's' (b_drop n b)
-          (String dsc) = decode_datum 's' (b_drop n b)
-          arg = decode_datum_seq (drop 1 dsc) (b_drop (n + m) b)
-
--- Decode a sequence of OSC messages, each one headed by its length
-decode_message_seq :: B.ByteString -> [OSC]
-decode_message_seq b | B.length b == 0 = []
-                     | otherwise = m:nxt
-                     where s = decode_i32 b
-                           m = decode_message $ b_drop 4 b
-                           nxt = decode_message_seq $ b_drop (4+s) b
-
-decode_bundle :: B.ByteString -> OSC
-decode_bundle b = Bundle timeStamp ms
-    where h = storage 's' b -- header (should be '#bundle'
-          t = storage 't' (b_drop h b) -- time tag
-          (TimeStamp timeStamp) = decode_datum 't' (b_drop h b)
-          ms = decode_message_seq $ b_drop (h+t) b
-
--- | Decode an OSC packet.
-decodeOSC :: B.ByteString -> OSC
-decodeOSC b | bundle_header `B.isPrefixOf` b = decode_bundle b
-            | otherwise = decode_message b
-
-bundle :: Time -> [OSC] -> OSC
-bundle t xs =
-    case xs of
-      [] -> error "bundle: empty?"
-      _ -> Bundle t xs
-
-message :: String -> [Datum] -> OSC
-message a xs =
-    case a of
-      ('/':_) -> Message a xs
-      _ -> error "message: ill-formed address"
-
-b_take :: Int -> B.ByteString -> B.ByteString
-b_take = B.take . fromIntegral
-
-b_drop :: Int -> B.ByteString -> B.ByteString
-b_drop = B.drop . fromIntegral
-
-bundle_header :: B.ByteString
-bundle_header = encode_datum (String "#bundle")
diff --git a/Sound/OpenSoundControl/Time.hs b/Sound/OpenSoundControl/Time.hs
--- a/Sound/OpenSoundControl/Time.hs
+++ b/Sound/OpenSoundControl/Time.hs
@@ -4,75 +4,117 @@
 
 import Control.Concurrent
 import Control.Monad
+import Data.Word
 import qualified Data.Time as T
 
--- | Time is represented in either UTC or NTP form.
-data Time = UTCr Double | NTPr Double | NTPi Integer
-            deriving (Eq, Show)
+-- | Type for integer representation of NTP time.
+type NTPi = Word64
 
+-- | Time is represented in either UTC or NTP form.  The NTP form may
+--   be either integral or real.
+data Time = UTCr Double | NTPr Double | NTPi NTPi
+            deriving (Read, Show)
+
+instance Eq Time where
+    a == b = as_ntpi a == as_ntpi b
+    a /= b = as_ntpi a /= as_ntpi b
+
 -- | Coerce to NTPi form.
-as_ntpi :: Time -> Integer
-as_ntpi (UTCr t) = utcr_ntpi t
-as_ntpi (NTPr t) = ntpr_ntpi t
-as_ntpi (NTPi t) = t
+as_ntpi :: Time -> NTPi
+as_ntpi x =
+    case x of
+      UTCr t -> utcr_ntpi t
+      NTPr t -> ntpr_ntpi t
+      NTPi t -> t
 
 -- | Coerce to UTCr form.
 as_utcr :: Time -> Double
-as_utcr (UTCr t) = t
-as_utcr (NTPr t) = ntpr_utcr t
-as_utcr (NTPi t) = ntpi_utcr t
+as_utcr x =
+    case x of
+      UTCr t -> t
+      NTPr t -> ntpr_utcr t
+      NTPi t -> ntpi_utcr t
 
 -- | Times can be ordered, avoid coercion if not required.
 instance Ord Time where
-    compare (UTCr p) (UTCr q) = compare p q
-    compare (NTPr p) (NTPr q) = compare p q
-    compare (NTPi p) (NTPi q) = compare p q
-    compare p q = compare (as_ntpi p) (as_ntpi q)
+    compare p q =
+        case (p,q) of
+          (UTCr p',UTCr q') -> compare p' q'
+          (NTPr p',NTPr q') -> compare p' q'
+          (NTPi p',NTPi q') -> compare p' q'
+          _ -> compare (as_ntpi p) (as_ntpi q)
 
 -- | Convert a real-valued NTP timestamp to an NTP timestamp.
-ntpr_ntpi :: Double -> Integer
+ntpr_ntpi :: Double -> NTPi
 ntpr_ntpi t = round (t * 2^(32::Int))
 
 -- | Convert an NTP timestamp to a real-valued NTP timestamp.
-ntpi_ntpr :: Integer -> Double
+ntpi_ntpr :: NTPi -> Double
 ntpi_ntpr t = fromIntegral t / 2^(32::Int)
 
 -- | Convert UTC timestamp to NTP timestamp.
-utcr_ntpi :: Double -> Integer
-utcr_ntpi t = ntpr_ntpi (t + secdif)
-    where secdif = (70 * 365 + 17) * 24 * 60 * 60
+utcr_ntpi :: Double -> NTPi
+utcr_ntpi t =
+    let secdif = (70 * 365 + 17) * 24 * 60 * 60
+    in ntpr_ntpi (t + secdif)
 
 -- | Convert NTP timestamp to UTC timestamp.
 ntpr_utcr :: Double -> Double
-ntpr_utcr t = t - secdif
-    where secdif = (70 * 365 + 17) * 24 * 60 * 60
+ntpr_utcr t =
+    let secdif = (70 * 365 + 17) * 24 * 60 * 60
+    in t - secdif
 
 -- | Convert NTP timestamp to UTC timestamp.
-ntpi_utcr :: Integer -> Double
+ntpi_utcr :: NTPi -> Double
 ntpi_utcr = ntpr_utcr . ntpi_ntpr
 
 -- | The time at 1970-01-01:00:00:00.
 utc_base :: T.UTCTime
-utc_base = T.UTCTime d s
-    where d = T.fromGregorian 1970 1 1
-          s = T.secondsToDiffTime 0
+utc_base =
+    let d = T.fromGregorian 1970 1 1
+        s = T.secondsToDiffTime 0
+    in T.UTCTime d s
 
 -- | Read current UTCr timestamp.
 utcr :: IO Double
-utcr = do t <- T.getCurrentTime
-          return (realToFrac (T.diffUTCTime t utc_base))
+utcr = do
+  t <- T.getCurrentTime
+  return (realToFrac (T.diffUTCTime t utc_base))
 
 -- | Read current NTP timestamp.
-ntpi :: IO Integer
+ntpi :: IO NTPi
 ntpi = liftM utcr_ntpi utcr
 
--- | Pause current thread for the indicated duration, given in seconds.
+-- | The pauseThread limit (in seconds).  Values larger than this
+--   require a different thread delay mechanism, see sleepThread.  The
+--   value is the number of microseconds in maxBound::Int.
+pauseThreadLimit :: Double
+pauseThreadLimit = fromIntegral (maxBound::Int) / 1e6
+
+-- | Pause current thread for the indicated duration (in seconds), see
+--   pauseThreadLimit.  Note also that this function does not attempt
+--   pauses less than 1e-4.
 pauseThread :: Double -> IO ()
 pauseThread n = when (n > 1e-4) (threadDelay (floor (n * 1e6)))
 
--- | Pause current thread until the given utcr time.
+-- | Pause current thread until the given utcr time, see
+--   pauseThreadLimit.
 pauseThreadUntil :: Double -> IO ()
 pauseThreadUntil t = pauseThread . (t -) =<< utcr
+
+-- | Sleep current thread for the indicated duration (in seconds).
+--   Divides long sleeps into parts smaller than pauseThreadLimit.
+sleepThread :: Double -> IO ()
+sleepThread n =
+    if n >= pauseThreadLimit
+    then let n' = pauseThreadLimit - 1
+         in pauseThread n >> sleepThread (n - n')
+    else pauseThread n
+
+-- | Sleep current thread until the given utcr time.  Divides long
+--   sleeps into parts smaller than pauseThreadLimit.
+sleepThreadUntil :: Double -> IO ()
+sleepThreadUntil t = sleepThread . (t -) =<< utcr
 
 -- | Execute the bundle immediately.
 immediately :: Time
diff --git a/Sound/OpenSoundControl/Transport.hs b/Sound/OpenSoundControl/Transport.hs
--- a/Sound/OpenSoundControl/Transport.hs
+++ b/Sound/OpenSoundControl/Transport.hs
@@ -1,11 +1,13 @@
--- | An abstract transport layer with implementations for UDP and TCP
---   transport.
-module Sound.OpenSoundControl.Transport ( Transport(..)
-                                        , withTransport
-                                        , waitFor, wait ) where
+-- | An abstract transport layer. hosc provides implementations for
+--   UDP and TCP transport.
+module Sound.OpenSoundControl.Transport (Transport(..)
+                                        ,withTransport
+                                        ,recvT
+                                        ,waitFor,wait) where
 
 import Control.Exception
-import Sound.OpenSoundControl.OSC
+import Sound.OpenSoundControl.Type
+import System.Timeout
 
 -- | Abstract over the underlying transport protocol.
 class Transport t where
@@ -18,22 +20,34 @@
 
 -- Does the OSC message have the specified address.
 has_address :: String -> OSC -> Bool
-has_address x (Message y _) = x == y
-has_address _ _ = False
+has_address x o =
+    case o of
+      Message y _ -> x == y
+      _ -> False
 
--- Repeat action until function does not give Nothing when applied to result.
+-- Repeat action until `f' does not give Nothing when applied to result.
 untilM :: Monad m => (a -> Maybe b) -> m a -> m b
-untilM f act = recurse
-    where g p = let q = f p in case q of { Nothing -> recurse
-                                         ; Just r -> return r }
-          recurse = act >>= g
+untilM f act =
+    let g p = let q = f p in case q of { Nothing -> rec
+                                       ; Just r -> return r }
+        rec = act >>= g
+    in rec
 
+-- | Real valued variant of 'timeout'.
+timeout_r :: Double -> IO a -> IO (Maybe a)
+timeout_r t = timeout (floor (t * 1000000))
+
+-- | Variant that wraps 'recv' in an /n/ second 'timeout'.
+recvT :: Transport t => Double -> t -> IO (Maybe OSC)
+recvT n fd = timeout_r n (recv fd)
+
 -- | Wait for an OSC message where the supplied function does not give
 --   Nothing, discarding intervening messages.
 waitFor :: Transport t => t -> (OSC -> Maybe a) -> IO a
 waitFor t f = untilM f (recv t)
 
--- | A 'waitFor' for variant matching on address string of messages.
+-- | A 'waitFor' for variant matching on the address string of
+--   incoming messages.
 wait :: Transport t => t -> String -> IO OSC
 wait t s = waitFor t (\o -> if has_address s o then Just o else Nothing)
 
diff --git a/Sound/OpenSoundControl/Transport/TCP.hs b/Sound/OpenSoundControl/Transport/TCP.hs
--- a/Sound/OpenSoundControl/Transport/TCP.hs
+++ b/Sound/OpenSoundControl/Transport/TCP.hs
@@ -1,38 +1,47 @@
 -- | OSC over TCP implementation.
-module Sound.OpenSoundControl.Transport.TCP (TCP, openTCP, tcpServer) where
+module Sound.OpenSoundControl.Transport.TCP (TCP(..)
+                                            ,openTCP'
+                                            ,tcpServer') where
 
 import qualified Data.ByteString.Lazy as B
 import Control.Monad
 import Network
+import Sound.OpenSoundControl.Coding.Byte
 import Sound.OpenSoundControl.Transport
-import Sound.OpenSoundControl.Byte
-import Sound.OpenSoundControl.OSC
+import Sound.OpenSoundControl.Type
 import System.IO
 
 -- | The TCP transport handle data type.
-data TCP = TCP Handle deriving (Eq, Show)
+data TCP = TCP {tcpEncode :: OSC -> B.ByteString
+               ,tcpDecode :: B.ByteString -> OSC
+               ,tcpHandle :: Handle}
 
 instance Transport TCP where
-   send (TCP fd) msg =
-      do let b = encodeOSC msg
+   send (TCP enc _ fd) msg =
+      do let b = enc msg
              n = fromIntegral (B.length b)
          B.hPut fd (B.append (encode_u32 n) b)
          hFlush fd
-
-   recv (TCP fd) =
+   recv (TCP _ dec fd) =
       do b0 <- B.hGet fd 4
          b1 <- B.hGet fd (fromIntegral (decode_u32 b0))
-         return (decodeOSC b1)
+         return (dec b1)
+   close (TCP _ _ fd) = hClose fd
 
-   close (TCP fd) = hClose fd
+type Coder = (OSC -> B.ByteString,B.ByteString -> OSC)
 
--- | Make a TCP connection.
-openTCP :: String -> Int -> IO TCP
-openTCP host = liftM TCP . connectTo host . PortNumber . fromIntegral
+-- | Make a TCP connection using specified coder.
+openTCP' :: Coder -> String -> Int -> IO TCP
+openTCP' (enc,dec) host =
+    liftM (TCP enc dec) .
+    connectTo host .
+    PortNumber .
+    fromIntegral
 
--- | A trivial TCP OSC server.
-tcpServer :: Int -> (TCP -> IO ()) -> IO ()
-tcpServer p f = do s <- listenOn (PortNumber (fromIntegral p))
-                   (sequence_ . repeat) (do (fd, _, _) <- accept s
-                                            f (TCP fd)
-                                            return ())
+-- | A trivial TCP OSC server using specified coder.
+tcpServer' :: Coder -> Int -> (TCP -> IO ()) -> IO ()
+tcpServer' (enc,dec) p f = do
+  s <- listenOn (PortNumber (fromIntegral p))
+  (sequence_ . repeat) (do (fd, _, _) <- accept s
+                           f (TCP enc dec fd)
+                           return ())
diff --git a/Sound/OpenSoundControl/Transport/UDP.hs b/Sound/OpenSoundControl/Transport/UDP.hs
--- a/Sound/OpenSoundControl/Transport/UDP.hs
+++ b/Sound/OpenSoundControl/Transport/UDP.hs
@@ -1,54 +1,63 @@
 -- | OSC over UDP implementation.
-module Sound.OpenSoundControl.Transport.UDP ( UDP(udpSocket)
-                                            , openUDP
-                                            , udpServer
-                                            , udpPort
-                                            , sendTo, recvFrom ) where
+module Sound.OpenSoundControl.Transport.UDP (UDP(..),udpPort
+                                            ,openUDP'
+                                            ,udpServer'
+                                            ,sendTo,recvFrom) where
 
 import Control.Monad
+import qualified Data.ByteString as S
+import qualified Data.ByteString.Lazy as B
 import qualified Network.Socket as N
-import Sound.OpenSoundControl.Byte
-import Sound.OpenSoundControl.OSC
+import qualified Network.Socket.ByteString as C (sendTo,recvFrom)
+import qualified Network.Socket.ByteString.Lazy as C (send,recv)
 import Sound.OpenSoundControl.Transport
+import Sound.OpenSoundControl.Type
 
 -- | The UDP transport handle data type.
-data UDP = UDP { udpSocket :: N.Socket }
-           deriving (Eq, Show)
+data UDP = UDP {udpEncode :: OSC -> B.ByteString
+               ,udpDecode :: B.ByteString -> OSC
+               ,udpSocket :: N.Socket}
 
+-- | Return the port number associated with the UDP socket.
+udpPort :: Integral n => UDP -> IO n
+udpPort (UDP _ _ fd) = fmap fromIntegral (N.socketPort fd)
+
 instance Transport UDP where
-   send  (UDP fd) msg = N.send fd (decode_str (encodeOSC msg)) >> return ()
-   recv  (UDP fd) = liftM (decodeOSC . encode_str) (N.recv fd 8192)
-   close (UDP fd) = N.sClose fd
+   send  (UDP enc _ fd) msg = C.send fd (enc msg) >> return ()
+   recv  (UDP _ dec fd) = liftM dec (C.recv fd 8192)
+   close (UDP _ _ fd) = N.sClose fd
 
--- | Make a UDP connection.
-openUDP :: String -> Int -> IO UDP
-openUDP host port = do
+type Coder = (OSC -> B.ByteString,B.ByteString -> OSC)
+
+-- | Make a UDP connection with specified coder.
+openUDP' :: Coder -> String -> Int -> IO UDP
+openUDP' (enc,dec) host port = do
   fd <- N.socket N.AF_INET N.Datagram 0
-  a  <- N.inet_addr host
-  let sa = N.SockAddrInet (fromIntegral port) a
-  N.connect fd sa
+  a <- N.inet_addr host
+  N.connect fd (N.SockAddrInet (fromIntegral port) a)
   -- N.setSocketOption fd N.RecvTimeOut 1000
-  return (UDP fd)
+  return (UDP enc dec fd)
 
--- | Trivial udp server.
-udpServer :: String -> Int -> IO UDP
-udpServer host port = do
+-- | Trivial udp server with specified coder.
+udpServer' :: Coder -> String -> Int -> IO UDP
+udpServer' (enc,dec) host port = do
   fd <- N.socket N.AF_INET N.Datagram 0
   a  <- N.inet_addr host
   let sa = N.SockAddrInet (fromIntegral port) a
   N.bindSocket fd sa
-  return (UDP fd)
+  return (UDP enc dec fd)
 
+-- | Send variant to send to specified address.
 sendTo :: UDP -> OSC -> N.SockAddr -> IO ()
-sendTo (UDP fd) o a = do
-  _ <- N.sendTo fd (decode_str (encodeOSC o)) a
-  return ()
+sendTo (UDP enc _ fd) o a = do
+  -- Network.Socket.ByteString.Lazy.sendTo does not exist
+  let o' = S.pack (B.unpack (enc o))
+  C.sendTo fd o' a >> return ()
 
+-- | Recv variant to collect message source address.
 recvFrom :: UDP -> IO (OSC, N.SockAddr)
-recvFrom (UDP fd) = do
-  (s, _, a) <- N.recvFrom fd 8192
-  let o = (decodeOSC . encode_str) s
-  return (o, a)
-
-udpPort :: Integral n => UDP -> IO n
-udpPort (UDP fd) = fmap fromIntegral (N.socketPort fd)
+recvFrom (UDP _ dec fd) = do
+  -- Network.Socket.ByteString.Lazy.recvFrom does not exist
+  (s,a) <- C.recvFrom fd 8192
+  let s' = B.pack (S.unpack s)
+  return (dec s',a)
diff --git a/Sound/OpenSoundControl/Type.hs b/Sound/OpenSoundControl/Type.hs
new file mode 100644
--- /dev/null
+++ b/Sound/OpenSoundControl/Type.hs
@@ -0,0 +1,62 @@
+-- | Alegbraic data types for OSC datum and packets.
+module Sound.OpenSoundControl.Type ( OSC(..)
+                                   , Datum(..)
+                                   , message
+                                   , bundle
+                                   , tag ) where
+
+import qualified Data.ByteString.Lazy as B
+import Data.Word
+import Sound.OpenSoundControl.Time
+
+-- | The basic elements of OSC messages.
+data Datum = Int Int
+           | Float Double
+           | Double Double
+           | String String
+           | Blob B.ByteString
+           | TimeStamp Time
+           | Midi (Word8,Word8,Word8,Word8)
+             deriving (Eq, Read, Show)
+
+-- | An OSC packet.
+data OSC = Message String [Datum]
+         | Bundle Time [OSC]
+           deriving (Eq, Read, Show)
+
+-- | OSC bundles can be ordered (time ascending).  Bundles and
+--   messages compare EQ.
+instance Ord OSC where
+    compare (Bundle a _) (Bundle b _) = compare a b
+    compare _ _ = EQ
+
+-- | Single character identifier of an OSC datum.
+tag :: Datum -> Char
+tag dt =
+    case dt of
+      Int _ -> 'i'
+      Float _ -> 'f'
+      Double _ -> 'd'
+      String _ -> 's'
+      Blob _ -> 'b'
+      TimeStamp _ -> 't'
+      Midi _ -> 'm'
+
+-- | Bundle constructor.
+--
+-- Signals an error when @xs@ is empty.
+bundle :: Time -> [OSC] -> OSC
+bundle t xs =
+    case xs of
+      [] -> error "bundle: empty?"
+      _ -> Bundle t xs
+
+-- | Message constructor
+--
+-- Signals an error when the address @a@ doesn't conform to the OSC
+-- specification.
+message :: String -> [Datum] -> OSC
+message a xs =
+    case a of
+      ('/':_) -> Message a xs
+      _ -> error "message: ill-formed address"
diff --git a/hosc.cabal b/hosc.cabal
--- a/hosc.cabal
+++ b/hosc.cabal
@@ -1,35 +1,40 @@
 Name:              hosc
-Version:           0.9
+Version:           0.10
 Synopsis:          Haskell Open Sound Control
 Description:       hosc provides Sound.OpenSoundControl, a haskell
                    module implementing a subset of the Open Sound
                    Control byte protocol.
 License:           GPL
 Category:          Sound
-Copyright:         (c) Rohan Drape and others, 2006-2011
-Author:            Rohan Drape
+Copyright:         (c) Rohan Drape, Stefan Kersten and others, 2006-2011
+Author:            Rohan Drape, Stefan Kersten
 Maintainer:        rd@slavepianos.org
 Stability:         Experimental
 Homepage:          http://slavepianos.org/rd/?t=hosc
-Tested-With:       GHC == 6.12.1
+Tested-With:       GHC == 7.0.4
 Build-Type:        Simple
-Cabal-Version:     >= 1.6
+Cabal-Version:     >= 1.8
 Data-Files:        README
-                   Help/timer.hs
 
 Library
-  Build-Depends:   array,
-                   base == 4.*,
-                   bytestring,
+  Build-Depends:   base == 4.*,
                    binary,
-                   network,
+                   blaze-builder >= 0.3,
+                   bytestring,
+                   data-binary-ieee754,
+                   network >= 2.3,
                    time
   GHC-Options:     -Wall -fwarn-tabs
   Exposed-modules: Sound.OpenSoundControl
-                   Sound.OpenSoundControl.Byte
-                   Sound.OpenSoundControl.Cast
-                   Sound.OpenSoundControl.Coerce
-                   Sound.OpenSoundControl.OSC
+                   Sound.OpenSoundControl.Coding.Byte
+                   Sound.OpenSoundControl.Coding.Cast
+                   Sound.OpenSoundControl.Coding.Coerce
+                   Sound.OpenSoundControl.Coding.Decode.Base
+                   Sound.OpenSoundControl.Coding.Decode.Binary
+                   Sound.OpenSoundControl.Coding.Encode.Base
+                   Sound.OpenSoundControl.Coding.Encode.Builder
+                   Sound.OpenSoundControl.Type
+                   Sound.OpenSoundControl.Coding
                    Sound.OpenSoundControl.Time
                    Sound.OpenSoundControl.Transport
                    Sound.OpenSoundControl.Transport.TCP
