diff --git a/README b/README
--- a/README
+++ b/README
@@ -8,8 +8,9 @@
 See also:
 
 - [hosc-json](?t=hosc-json): JSON text encoding of OSC
+- [hosc-util](?t=hosc-util): non-core OSC functions
 
-© [rohan drape][rd], [stefan kersten][sk] and others, 2007-2018,
+© [rohan drape][rd], [stefan kersten][sk] and others, 2007-2020,
 [gpl][gpl]. with contributions by:
 
 - [alex mclean][am]
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
@@ -20,37 +20,75 @@
 
 -- * Encode
 
--- | Encode a signed 8-bit integer.
-encode_i8 :: Int -> L.ByteString
-encode_i8 = Binary.encode . int_to_int8
+-- | Type specialised 'Binary.encode' (big-endian).
+encode_int8 :: Int8 -> L.ByteString
+encode_int8 = Binary.encode
 
--- | Encode an un-signed 8-bit integer.
-encode_u8 :: Int -> L.ByteString
-encode_u8 = Binary.encode . int_to_word8
+-- | Type specialised 'Binary.encode' (big-endian).
+--
+-- > encode_int16 0x0102 == L.pack [0x01,0x02]
+encode_int16 :: Int16 -> L.ByteString
+encode_int16 = Binary.encode
 
--- | Type specialised 'Binary.encode'.
+-- | Little-endian.
 --
--- > encode_w16 0x0102 == L.pack [1,2]
-encode_w16 :: Word16 -> L.ByteString
-encode_w16 = Binary.encode
+-- > encode_int16_le 0x0102 == L.pack [0x02,0x01]
+encode_int16_le :: Int16 -> L.ByteString
+encode_int16_le = Put.runPut . Put.putInt16le
 
+-- | Encode a signed 64-bit integer (big-endian).
+encode_int64 :: Int64 -> L.ByteString
+encode_int64 = Binary.encode
+
+-- | Type specialised 'Binary.encode' (big-endian).
+encode_word8 :: Word8 -> L.ByteString
+encode_word8 = Binary.encode
+
+-- | Type specialised 'Binary.encode' (big-endian).
+--
+-- > encode_word16 0x0102 == L.pack [0x01,0x02]
+encode_word16 :: Word16 -> L.ByteString
+encode_word16 = Binary.encode
+
 -- | Little-endian.
 --
--- > encode_w16_le 0x0102 == L.pack [2,1]
-encode_w16_le :: Word16 -> L.ByteString
-encode_w16_le = Put.runPut . Put.putWord16le
+-- > encode_word16_le 0x0102 == L.pack [0x02,0x01]
+encode_word16_le :: Word16 -> L.ByteString
+encode_word16_le = Put.runPut . Put.putWord16le
 
+-- | Type specialised 'Binary.encode'.
+encode_word32 :: Word32 -> L.ByteString
+encode_word32 = Binary.encode
+
+-- | Little-endian variant of 'encode_word32'.
+encode_word32_le :: Word32 -> L.ByteString
+encode_word32_le = Put.runPut . Put.putWord32le
+
+-- | Encode an unsigned 64-bit integer.
+encode_word64 :: Word64 -> L.ByteString
+encode_word64 = Binary.encode
+
+-- * Encode/Int
+
+-- | Encode a signed 8-bit integer.
+encode_i8 :: Int -> L.ByteString
+encode_i8 = encode_int8 . int_to_int8
+
+-- | Encode an un-signed 8-bit integer.
+encode_u8 :: Int -> L.ByteString
+encode_u8 = encode_word8 . int_to_word8
+
 -- | Encode an un-signed 16-bit integer.
 --
 -- > encode_u16 0x0102 == L.pack [1,2]
 encode_u16 :: Int -> L.ByteString
-encode_u16 = encode_w16 . int_to_word16
+encode_u16 = encode_word16 . int_to_word16
 
 -- | Little-endian.
 --
 -- > encode_u16_le 0x0102 == L.pack [2,1]
 encode_u16_le :: Int -> L.ByteString
-encode_u16_le = encode_w16_le . int_to_word16
+encode_u16_le = encode_word16_le . int_to_word16
 
 -- | Encode a signed 16-bit integer.
 encode_i16 :: Int -> L.ByteString
@@ -60,33 +98,19 @@
 encode_i32 :: Int -> L.ByteString
 encode_i32 = Binary.encode . int_to_int32
 
--- | Type specialised 'Binary.encode'.
-encode_w32 :: Word32 -> L.ByteString
-encode_w32 = Binary.encode
-
 -- | Encode an unsigned 32-bit integer.
 --
 -- > encode_u32 0x01020304 == L.pack [1,2,3,4]
 encode_u32 :: Int -> L.ByteString
-encode_u32 = encode_w32 . int_to_word32
-
--- | Little-endian variant of 'encode_w32'.
-encode_w32_le :: Word32 -> L.ByteString
-encode_w32_le = Put.runPut . Put.putWord32le
+encode_u32 = encode_word32 . int_to_word32
 
 -- | Little-endian.
 --
 -- > encode_u32_le 0x01020304 == L.pack [4,3,2,1]
 encode_u32_le :: Int -> L.ByteString
-encode_u32_le = encode_w32_le . int_to_word32
-
--- | Encode a signed 64-bit integer.
-encode_i64 :: Int64 -> L.ByteString
-encode_i64 = Binary.encode
+encode_u32_le = encode_word32_le . int_to_word32
 
--- | Encode an unsigned 64-bit integer.
-encode_u64 :: Word64 -> L.ByteString
-encode_u64 = Binary.encode
+-- * Encode/Float
 
 -- | Encode a 32-bit IEEE floating point number.
 encode_f32 :: Float -> L.ByteString
@@ -100,13 +124,48 @@
 encode_f64 :: Double -> L.ByteString
 encode_f64 = Binary.encode . Cast.f64_w64
 
+-- | Little-endian variant of 'encode_f64'.
+encode_f64_le :: Double -> L.ByteString
+encode_f64_le = Put.runPut . Put.putWord64le . Cast.f64_w64
+
+-- * Encode/ASCII
+
 -- | Encode an ASCII string (ASCII at Datum is an alias for a Char8 Bytetring).
-encode_str :: S.C.ByteString -> L.ByteString
-{-# INLINE encode_str #-}
-encode_str = L.pack . S.unpack
+encode_ascii :: S.C.ByteString -> L.ByteString
+encode_ascii = L.pack . S.unpack
 
 -- * Decode
 
+-- | Type specialised 'Binary.decode'.
+decode_word16 :: L.ByteString -> Word16
+decode_word16 = Binary.decode
+
+-- | Little-endian variant of 'decode_word16'.
+decode_word16_le :: L.ByteString -> Word16
+decode_word16_le = Get.runGet Get.getWord16le
+
+-- | Type specialised 'Binary.decode'.
+decode_int16 :: L.ByteString -> Int16
+decode_int16 = Binary.decode
+
+-- | Type specialised 'Binary.decode'.
+decode_word32 :: L.ByteString -> Word32
+decode_word32 = Binary.decode
+
+-- | Little-endian variant of 'decode_word32'.
+decode_word32_le :: L.ByteString -> Word32
+decode_word32_le = Get.runGet Get.getWord32le
+
+-- | Type specialised 'Binary.decode'.
+decode_int64 :: L.ByteString -> Int64
+decode_int64 = Binary.decode
+
+-- | Type specialised 'Binary.decode'.
+decode_word64 :: L.ByteString -> Word64
+decode_word64 = Binary.decode
+
+-- * Decode/Int
+
 -- | Decode an un-signed 8-bit integer.
 decode_u8 :: L.ByteString -> Int
 decode_u8 = word8_to_int . L.head
@@ -115,26 +174,14 @@
 decode_i8 :: L.ByteString -> Int
 decode_i8 = int8_to_int . Binary.decode
 
--- | Type specialised 'Binary.decode'.
-decode_word16 :: L.ByteString -> Word16
-decode_word16 = Binary.decode
-
 -- | Decode an unsigned 8-bit integer.
 decode_u16 :: L.ByteString -> Int
 decode_u16 = word16_to_int . decode_word16
 
--- | Little-endian variant of 'decode_word16'.
-decode_word16_le :: L.ByteString -> Word16
-decode_word16_le = Get.runGet Get.getWord16le
-
 -- | Little-endian variant of 'decode_u16'.
 decode_u16_le :: L.ByteString -> Int
 decode_u16_le = word16_to_int . decode_word16_le
 
--- | Type specialised 'Binary.decode'.
-decode_int16 :: L.ByteString -> Int16
-decode_int16 = Binary.decode
-
 -- | Decode a signed 16-bit integer.
 decode_i16 :: L.ByteString -> Int
 decode_i16 = int16_to_int . decode_int16
@@ -144,12 +191,16 @@
 decode_i16_le = decode_i16 . L.reverse
 
 -- | Decode a signed 32-bit integer.
+--
+-- > decode_i32 (L.pack [0x00,0x00,0x03,0xe7]) == 0x03e7
 decode_i32 :: L.ByteString -> Int
 decode_i32 = int32_to_int . Binary.decode
 
--- | Type specialised 'Binary.decode'.
-decode_word32 :: L.ByteString -> Word32
-decode_word32 = Binary.decode
+-- | Little-endian variant of 'decode_i32'.
+--
+-- > decode_i32_le (L.pack [0xe7,0x03,0x00,0x00]) == 0x03e7
+decode_i32_le :: L.ByteString -> Int
+decode_i32_le = decode_i32 . L.reverse
 
 -- | Decode an unsigned 32-bit integer.
 --
@@ -157,23 +208,13 @@
 decode_u32 :: L.ByteString -> Int
 decode_u32 = word32_to_int . decode_word32
 
--- | Little-endian variant of 'decode_word32'.
-decode_word32_le :: L.ByteString -> Word32
-decode_word32_le = Get.runGet Get.getWord32le
-
 -- | Little-endian variant of decode_u32.
 --
 -- > decode_u32_le (L.pack [1,2,3,4]) == 0x04030201
 decode_u32_le :: L.ByteString -> Int
 decode_u32_le = word32_to_int . decode_word32_le
 
--- | Type specialised 'Binary.decode'.
-decode_i64 :: L.ByteString -> Int64
-decode_i64 = Binary.decode
-
--- | Type specialised 'Binary.decode'.
-decode_u64 :: L.ByteString -> Word64
-decode_u64 = Binary.decode
+-- * Decode/Float
 
 -- | Decode a 32-bit IEEE floating point number.
 decode_f32 :: L.ByteString -> Float
@@ -187,50 +228,88 @@
 decode_f64 :: L.ByteString -> Double
 decode_f64 b = Cast.w64_f64 (Binary.decode b :: Word64)
 
--- | Decode an ASCII string, inverse of 'encode_str'.
-decode_str :: L.ByteString -> S.C.ByteString
-{-# INLINE decode_str #-}
-decode_str = S.C.pack . L.C.unpack
+-- * Decode/ASCII
 
+-- | Decode an ASCII string, inverse of 'encode_ascii'.
+decode_ascii :: L.ByteString -> S.C.ByteString
+{-# INLINE decode_ascii #-}
+decode_ascii = S.C.pack . L.C.unpack
+
 -- * IO
 
+-- | Read /n/ bytes from /h/ and run /f/.
+read_decode :: (L.ByteString -> t) -> Int -> Handle -> IO t
+read_decode f n = fmap f . flip L.hGet n
+
+-- | Type-specialised reader for 'Binary.decode'.
+read_word32 :: Handle -> IO Word32
+read_word32 = read_decode Binary.decode 4
+
+-- | 'read_decode' of 'decode_word32_le'.
+read_word32_le :: Handle -> IO Word32
+read_word32_le = read_decode decode_word32_le 4
+
+-- | 'L.hPut' of 'encode_word32'.
+write_word32 :: Handle -> Word32 -> IO ()
+write_word32 h = L.hPut h . encode_word32
+
+-- | 'L.hPut' of 'encode_word32_le'.
+write_word32_le :: Handle -> Word32 -> IO ()
+write_word32_le h = L.hPut h . encode_word32_le
+
+-- * IO/Int
+
 -- | 'decode_i8' of 'L.hGet'.
 read_i8 :: Handle -> IO Int
-read_i8 = fmap decode_i8 . flip L.hGet 1
+read_i8 = read_decode decode_i8 1
 
 -- | 'decode_i16' of 'L.hGet'.
 read_i16 :: Handle -> IO Int
-read_i16 = fmap decode_i16 . flip L.hGet 2
+read_i16 = read_decode decode_i16 2
 
 -- | 'decode_i32' of 'L.hGet'.
 read_i32 :: Handle -> IO Int
-read_i32 = fmap decode_i32 . flip L.hGet 4
+read_i32 = read_decode decode_i32 4
 
+-- | 'decode_i32_le' of 'L.hGet'.
+read_i32_le :: Handle -> IO Int
+read_i32_le = read_decode decode_i32_le 4
+
 -- | 'decode_u32' of 'L.hGet'.
 read_u32 :: Handle -> IO Int
-read_u32 = fmap decode_u32 . flip L.hGet 4
+read_u32 = read_decode decode_u32 4
 
 -- | 'decode_u32_le' of 'L.hGet'.
 read_u32_le :: Handle -> IO Int
-read_u32_le = fmap decode_u32_le . flip L.hGet 4
+read_u32_le = read_decode decode_u32_le 4
 
+-- | 'L.hPut' of 'encode_u32'.
+write_u32 :: Handle -> Int -> IO ()
+write_u32 h = L.hPut h . encode_u32
+
+-- | 'L.hPut' of 'encode_u32_le'.
+write_u32_le :: Handle -> Int -> IO ()
+write_u32_le h = L.hPut h . encode_u32_le
+
+-- * IO/Float
+
 -- | 'decode_f32' of 'L.hGet'.
 read_f32 :: Handle -> IO Float
-read_f32 = fmap decode_f32 . flip L.hGet 4
+read_f32 = read_decode decode_f32 4
 
+-- | 'decode_f32_le' of 'L.hGet'.
+read_f32_le :: Handle -> IO Float
+read_f32_le = read_decode decode_f32_le 4
+
+-- * IO/ASCII
+
 -- | Read u8 length prefixed ASCII string (pascal string).
 read_pstr :: Handle -> IO S.C.ByteString
 read_pstr h = do
   n <- fmap decode_u8 (L.hGet h 1)
-  fmap decode_str (L.hGet h n)
-
--- | 'L.hPut' of 'encode_u32'.
-write_u32 :: Handle -> Int -> IO ()
-write_u32 h = L.hPut h . encode_u32
+  fmap decode_ascii (L.hGet h n)
 
--- | 'L.hPut' of 'encode_u32_le'.
-write_u32_le :: Handle -> Int -> IO ()
-write_u32_le h = L.hPut h . encode_u32_le
+-- * Util
 
 -- | Bundle header as a (strict) 'S.C.ByteString'.
 bundleHeader_strict :: S.C.ByteString
diff --git a/Sound/OSC/Coding/Cast.hs b/Sound/OSC/Coding/Cast.hs
--- a/Sound/OSC/Coding/Cast.hs
+++ b/Sound/OSC/Coding/Cast.hs
@@ -6,6 +6,8 @@
 
 import qualified Data.Binary.IEEE754 as I {- data-binary-ieee754 -}
 
+import Sound.OSC.Coding.Convert {- hosc -}
+
 -- | The IEEE byte representation of a float.
 f32_w32 :: Float -> Word32
 f32_w32 = I.floatToWord
@@ -24,16 +26,16 @@
 
 -- | 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]
+str_cstr s = map (int_to_word8 . ord) s ++ [0]
 
 -- | Inverse of 'str_cstr'.
 cstr_str :: [Word8] -> String
-cstr_str = map (chr . fromIntegral) . takeWhile (/= 0)
+cstr_str = map (chr . word8_to_int) . 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
+str_pstr s = int_to_word8 (length s) : map (int_to_word8 . ord) s
 
 -- | Inverse of 'str_pstr'.
 pstr_str :: [Word8] -> String
-pstr_str = map (chr . fromIntegral) . drop 1
+pstr_str = map (chr . word8_to_int) . drop 1
diff --git a/Sound/OSC/Coding/Convert.hs b/Sound/OSC/Coding/Convert.hs
--- a/Sound/OSC/Coding/Convert.hs
+++ b/Sound/OSC/Coding/Convert.hs
@@ -4,6 +4,8 @@
 import Data.Int {- base -}
 import Data.Word {- base -}
 
+-- * Int -> N
+
 -- | Type specialised 'fromIntegral'
 int_to_word8 :: Int -> Word8
 int_to_word8 = fromIntegral
@@ -29,6 +31,12 @@
 int_to_int32 = fromIntegral
 
 -- | Type specialised 'fromIntegral'
+int_to_int64 :: Int -> Int64
+int_to_int64 = fromIntegral
+
+-- * N -> Int
+
+-- | Type specialised 'fromIntegral'
 int8_to_int :: Int8 -> Int
 int8_to_int = fromIntegral
 
@@ -41,6 +49,10 @@
 int32_to_int = fromIntegral
 
 -- | Type specialised 'fromIntegral'
+int64_to_int :: Int64 -> Int
+int64_to_int = fromIntegral
+
+-- | Type specialised 'fromIntegral'
 word8_to_int :: Word8 -> Int
 word8_to_int = fromIntegral
 
@@ -52,3 +64,66 @@
 word32_to_int :: Word32 -> Int
 word32_to_int = fromIntegral
 
+-- * N -> N
+
+-- | Type specialised 'fromIntegral'
+word16_to_word32 :: Word16 -> Word32
+word16_to_word32 = fromIntegral
+
+-- | Type specialised 'fromIntegral'
+word32_to_word16 :: Word32 -> Word16
+word32_to_word16 = fromIntegral
+
+-- | Type specialised 'fromIntegral'
+word32_to_int32 :: Word32 -> Int32
+word32_to_int32 = fromIntegral
+
+-- | Type specialised 'fromIntegral'
+word32_to_int64 :: Word32 -> Int64
+word32_to_int64 = fromIntegral
+
+-- | Type specialised 'fromIntegral'
+word64_to_int64 :: Word64 -> Int64
+word64_to_int64 = fromIntegral
+
+-- | Type specialised 'fromIntegral'
+int64_to_int32 :: Int64 -> Int32
+int64_to_int32 = fromIntegral
+
+-- | Type specialised 'fromIntegral'
+int64_to_word32 :: Int64 -> Word32
+int64_to_word32 = fromIntegral
+
+-- * N -> Real
+
+-- | Type specialised 'fromIntegral'
+word64_to_double :: Word64 -> Double
+word64_to_double = fromIntegral
+
+-- * Enum
+
+-- | Type-specialised 'toEnum' of 'fromIntegral'
+word8_to_enum :: Enum e => Word8 -> e
+word8_to_enum = toEnum . fromIntegral
+
+-- | Type-specialised 'toEnum' of 'fromIntegral'
+word16_to_enum :: Enum e => Word16 -> e
+word16_to_enum = toEnum . fromIntegral
+
+-- | Type-specialised 'fromIntegral' of 'fromEnum'.
+enum_to_word8 :: Enum e => e -> Word8
+enum_to_word8 = fromIntegral . fromEnum
+
+-- | Type-specialised 'fromIntegral' of 'fromEnum'.
+enum_to_word16 :: Enum e => e -> Word16
+enum_to_word16 = fromIntegral . fromEnum
+
+-- * Enum/Char
+
+-- | Type-specialised 'word8_to_enum'.
+word8_to_char :: Word8 -> Char
+word8_to_char = word8_to_enum
+
+-- | Type-specialised 'enum_to_word8'.
+char_to_word8 :: Char -> Word8
+char_to_word8 = enum_to_word8
diff --git a/Sound/OSC/Coding/Decode/Base.hs b/Sound/OSC/Coding/Decode/Base.hs
--- a/Sound/OSC/Coding/Decode/Base.hs
+++ b/Sound/OSC/Coding/Decode/Base.hs
@@ -11,6 +11,7 @@
 import Data.Maybe {- base -}
 
 import Sound.OSC.Coding.Byte {- hosc -}
+import Sound.OSC.Coding.Convert {- hosc -}
 import Sound.OSC.Datum {- hosc -}
 import Sound.OSC.Packet {- hosc -}
 import Sound.OSC.Time {- hosc -}
@@ -24,7 +25,7 @@
       'd' -> 8
       't' -> 8 -- timetag
       'm' -> 4 -- MIDI message
-      's' -> fromIntegral (fromMaybe
+      's' -> int64_to_int (fromMaybe
                            (error ("size: no terminating zero: " ++ show b))
                            (B.elemIndex 0 b))
       'b' -> decode_i32 (B.take 4 b)
@@ -46,9 +47,9 @@
       'h' -> Int64 (decode b)
       'f' -> Float (decode_f32 b)
       'd' -> Double (decode_f64 b)
-      's' -> ASCII_String (decode_str (b_take (size 's' b) b))
+      's' -> ASCII_String (decode_ascii (b_take (size 's' b) b))
       'b' -> Blob (b_take (size 'b' b) (B.drop 4 b))
-      't' -> TimeStamp (ntpi_to_ntpr (decode_u64 b))
+      't' -> TimeStamp (ntpi_to_ntpr (decode_word64 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] ++ ")")
@@ -58,7 +59,7 @@
 decode_datum_seq cs b =
     let swap (x,y) = (y,x)
         cs' = C.unpack cs
-        f b' c = swap (B.splitAt (fromIntegral (storage c b')) b')
+        f b' c = swap (B.splitAt (int_to_int64 (storage c b')) b')
     in zipWith decode_datum cs' (snd (mapAccumL f b cs'))
 
 -- | Decode an OSC 'Message'.
@@ -91,7 +92,7 @@
 -- | Decode an OSC 'Packet'.
 --
 -- > let b = B.pack [47,103,95,102,114,101,101,0,44,105,0,0,0,0,0,0]
--- > in decodePacket b == Message "/g_free" [Int 0]
+-- > decodePacket b == Packet_Message (Message "/g_free" [Int32 0])
 decodePacket :: B.ByteString -> Packet
 decodePacket b =
     if bundleHeader `B.isPrefixOf` b
@@ -102,8 +103,8 @@
 
 -- | 'B.take' with 'Int' count.
 b_take :: Int -> B.ByteString -> B.ByteString
-b_take = B.take . fromIntegral
+b_take = B.take . int_to_int64
 
 -- | 'B.drop' with 'Int' count.
 b_drop :: Int -> B.ByteString -> B.ByteString
-b_drop = B.drop . fromIntegral
+b_drop = B.drop . int_to_int64
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
@@ -17,48 +17,49 @@
 import Data.Word {- base -}
 
 import qualified Sound.OSC.Coding.Byte as Byte {- hosc -}
+import Sound.OSC.Coding.Convert {- hosc -}
 import Sound.OSC.Datum {- hosc -}
 import Sound.OSC.Packet {- hosc -}
 import qualified Sound.OSC.Time as Time {- hosc -}
 
 -- | Get a 32 bit integer in big-endian byte order.
 getInt32be :: G.Get Int32
-getInt32be = fromIntegral <$> G.getWord32be
+getInt32be = word32_to_int32 <$> G.getWord32be
 
 -- | Get a 64 bit integer in big-endian byte order.
 getInt64be :: G.Get Int64
-getInt64be = fromIntegral <$> G.getWord64be
+getInt64be = word64_to_int64 <$> G.getWord64be
 
 -- | Get an aligned OSC string.
 get_string :: G.Get String
 get_string = do
     s <- G.getLazyByteStringNul
-    G.skip (fromIntegral (Byte.align (B.length s + 1)))
+    G.skip (int64_to_int (Byte.align (B.length s + 1)))
     return $ C.unpack s
 
 -- | Get an aligned OSC string.
 get_ascii :: G.Get ASCII
 get_ascii = do
     s <- G.getLazyByteStringNul
-    G.skip (fromIntegral (Byte.align (B.length s + 1)))
+    G.skip (int64_to_int (Byte.align (B.length s + 1)))
     return (S.C.pack (C.unpack s))
 
 -- | Get binary data prefixed by byte count.
 get_bytes :: Word32 -> G.Get B.ByteString
 get_bytes n = do
-    b <- G.getLazyByteString (fromIntegral n)
-    if n /= fromIntegral (B.length b)
+    b <- G.getLazyByteString (word32_to_int64 n)
+    if n /= int64_to_word32 (B.length b)
         then fail "get_bytes: end of stream"
-        else G.skip (fromIntegral (Byte.align n))
+        else G.skip (word32_to_int (Byte.align n))
     return b
 
 -- | Get an OSC datum.
 get_datum :: Datum_Type -> G.Get Datum
 get_datum ty =
     case ty of
-      'i' -> Int32  <$> fromIntegral <$> getInt32be
-      'h' -> Int64  <$> fromIntegral <$> getInt64be
-      'f' -> Float  <$> realToFrac <$> I.getFloat32be
+      'i' -> Int32 <$> getInt32be
+      'h' -> Int64 <$> getInt64be
+      'f' -> Float <$> I.getFloat32be
       'd' -> Double <$> I.getFloat64be
       's' -> ASCII_String <$> get_ascii
       'b' -> Blob   <$> (get_bytes =<< G.getWord32be)
@@ -79,7 +80,7 @@
         ',':tags -> do
             arg <- mapM get_datum tags
             return $ Message cmd arg
-        _ -> fail "get_message: invalid type descriptor string"
+        e -> fail ("get_message: invalid type descriptor string: " ++ e)
 
 -- | Get a sequence of OSC 'Message's, each one headed by its length.
 get_message_seq :: G.Get [Message]
@@ -88,7 +89,7 @@
     if b
         then return []
         else do
-            p <- flip G.isolate get_message . fromIntegral =<< G.getWord32be
+            p <- flip G.isolate get_message . word32_to_int =<< G.getWord32be
             ps <- get_message_seq
             return (p:ps)
 
@@ -110,10 +111,11 @@
 {-# INLINE decodePacket #-}
 {-# INLINE decodePacket_strict #-}
 
--- | Decode an OSC 'Message' from a lazy ByteString.
---
--- > let b = B.pack [47,103,95,102,114,101,101,0,44,105,0,0,0,0,0,0]
--- > decodeMessage b == Message "/g_free" [Int32 0]
+{- | Decode an OSC 'Message' from a lazy ByteString.
+
+> let b = B.pack [47,103,95,102,114,101,101,0,44,105,0,0,0,0,0,0]
+> decodeMessage b == Message "/g_free" [Int32 0]
+-}
 decodeMessage :: B.ByteString -> Message
 decodeMessage = G.runGet get_message
 
@@ -121,10 +123,11 @@
 decodeBundle :: B.ByteString -> Bundle
 decodeBundle = G.runGet get_bundle
 
--- | Decode an OSC packet from a lazy ByteString.
---
--- > let b = B.pack [47,103,95,102,114,101,101,0,44,105,0,0,0,0,0,0]
--- > decodePacket b == Packet_Message (Message "/g_free" [Int32 0])
+{- | Decode an OSC packet from a lazy ByteString.
+
+> let b = B.pack [47,103,95,102,114,101,101,0,44,105,0,0,0,0,0,0]
+> decodePacket b == Packet_Message (Message "/g_free" [Int32 0])
+-}
 decodePacket :: B.ByteString -> Packet
 decodePacket = G.runGet get_packet
 
diff --git a/Sound/OSC/Coding/Encode/Base.hs b/Sound/OSC/Coding/Encode/Base.hs
--- a/Sound/OSC/Coding/Encode/Base.hs
+++ b/Sound/OSC/Coding/Encode/Base.hs
@@ -7,6 +7,7 @@
 import qualified Data.ByteString.Lazy as B {- bytestring -}
 
 import Sound.OSC.Coding.Byte {- hosc -}
+import Sound.OSC.Coding.Convert {- hosc -}
 import Sound.OSC.Datum {- hosc -}
 import Sound.OSC.Packet {- hosc -}
 import Sound.OSC.Time {- hosc -}
@@ -16,6 +17,8 @@
 extend p s = B.append s (B.replicate (align (B.length s)) p)
 
 -- | Encode OSC 'Datum'.
+--
+-- MIDI: Bytes from MSB to LSB are: port id, status byte, data1, data2.
 encode_datum :: Datum -> B.ByteString
 encode_datum dt =
     case dt of
@@ -23,10 +26,10 @@
       Int64 i -> encode i
       Float f -> encode_f32 f
       Double d -> encode_f64 d
-      TimeStamp t -> encode_u64 $ ntpr_to_ntpi t
-      ASCII_String s -> extend 0 (B.snoc (encode_str s) 0)
+      TimeStamp t -> encode_word64 $ ntpr_to_ntpi t
+      ASCII_String s -> extend 0 (B.snoc (encode_ascii s) 0)
       Midi (MIDI b0 b1 b2 b3) -> B.pack [b0,b1,b2,b3]
-      Blob b -> let n = encode_i32 (fromIntegral (B.length b))
+      Blob b -> let n = encode (int64_to_int32 (B.length b))
                 in B.append n (extend 0 b)
 
 -- | Encode OSC 'Message'.
@@ -44,7 +47,7 @@
 encodeBundle :: Bundle -> B.ByteString
 encodeBundle (Bundle t m) =
     B.concat [bundleHeader
-             ,encode_u64 (ntpr_to_ntpi t)
+             ,encode_word64 (ntpr_to_ntpi t)
              ,B.concat (map (encode_datum . encode_message_blob) m)]
 
 -- | Encode OSC 'Packet'.
diff --git a/Sound/OSC/Coding/Encode/Builder.hs b/Sound/OSC/Coding/Encode/Builder.hs
--- a/Sound/OSC/Coding/Encode/Builder.hs
+++ b/Sound/OSC/Coding/Encode/Builder.hs
@@ -15,13 +15,14 @@
 import qualified Blaze.ByteString.Builder.Char8 as B {- bytestring -}
 
 import qualified Sound.OSC.Coding.Byte as Byte {- hosc -}
+import qualified Sound.OSC.Coding.Convert as Convert {- hosc -}
 import Sound.OSC.Datum {- hosc -}
 import Sound.OSC.Packet {- hosc -}
 import Sound.OSC.Time {- hosc -}
 
 -- | Generate a list of zero bytes for padding.
-padding :: Integral i => i -> [Word8]
-padding n = replicate (fromIntegral n) 0
+padding :: Int -> [Word8]
+padding n = replicate n 0
 
 -- | Nul byte (0) and then zero padding.
 nul_and_padding :: Int -> B.Builder
@@ -29,27 +30,28 @@
 
 -- Encode a string with zero padding.
 build_ascii :: ASCII -> B.Builder
-build_ascii s = B.fromByteString s `mappend` nul_and_padding (S.length s + 1)
+build_ascii s = B.fromByteString s <> nul_and_padding (S.length s + 1)
 
 -- Encode a string with zero padding.
 build_string :: String -> B.Builder
-build_string s = B.fromString s `mappend` nul_and_padding (length s + 1)
+build_string s = B.fromString s <> nul_and_padding (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 (Byte.align (L.length s)))
+build_bytes s =
+  B.fromInt32be (Convert.int64_to_int32 (L.length s)) <>
+  B.fromLazyByteString s <>
+  B.fromWord8s (padding (Convert.int64_to_int (Byte.align (L.length s))))
 
 -- Encode an OSC datum.
 build_datum :: Datum -> B.Builder
 build_datum d =
     case d of
-      Int32 i -> B.fromInt32be (fromIntegral i)
-      Int64 i -> B.fromInt64be (fromIntegral i)
-      Float n -> B.fromWord32be (I.floatToWord (realToFrac n))
+      Int32 i -> B.fromInt32be i
+      Int64 i -> B.fromInt64be i
+      Float n -> B.fromWord32be (I.floatToWord n)
       Double n -> B.fromWord64be (I.doubleToWord n)
-      TimeStamp t -> B.fromWord64be (fromIntegral (ntpr_to_ntpi t))
+      TimeStamp t -> B.fromWord64be (ntpr_to_ntpi t)
       ASCII_String s -> build_ascii s
       Midi (MIDI b0 b1 b2 b3) -> B.fromWord8s [b0,b1,b2,b3]
       Blob b -> build_bytes b
diff --git a/Sound/OSC/Datum.hs b/Sound/OSC/Datum.hs
--- a/Sound/OSC/Datum.hs
+++ b/Sound/OSC/Datum.hs
@@ -6,6 +6,7 @@
 import Data.Maybe {- base -}
 import Data.Word {- base -}
 import Numeric {- base -}
+import Text.Printf {- base -}
 import Text.Read {- base -}
 
 import qualified Data.ByteString.Lazy as Lazy {- bytestring -}
@@ -41,18 +42,18 @@
 blob_unpack = Lazy.unpack
 
 -- | Four-byte midi message: port-id, status-byte, data, data.
-data MIDI = MIDI Word8 Word8 Word8 Word8
+data MIDI = MIDI !Word8 !Word8 !Word8 !Word8
     deriving (Eq,Show,Read)
 
 -- | The basic elements of OSC messages.
-data Datum = Int32 {d_int32 :: Int32}
-           | Int64 {d_int64 :: Int64}
-           | Float {d_float :: Float}
-           | Double {d_double :: Double}
-           | ASCII_String {d_ascii_string :: ASCII}
-           | Blob {d_blob :: BLOB}
-           | TimeStamp {d_timestamp :: Time.Time} -- ie. NTPr
-           | Midi {d_midi :: MIDI}
+data Datum = Int32 {d_int32 :: !Int32}
+           | Int64 {d_int64 :: !Int64}
+           | Float {d_float :: !Float}
+           | Double {d_double :: !Double}
+           | ASCII_String {d_ascii_string :: !ASCII}
+           | Blob {d_blob :: !BLOB}
+           | TimeStamp {d_timestamp :: !Time.Time} -- ie. NTPr
+           | Midi {d_midi :: !MIDI}
              deriving (Eq,Read,Show)
 
 -- * Datum types
@@ -224,10 +225,14 @@
 vecPP :: Show a => [a] -> String
 vecPP v = '<' : intercalate "," (map show v) ++ ">"
 
+-- | Pretty printer for blobs, two-digit zero-padded hexadecimal.
+blobPP :: BLOB -> String
+blobPP = unwords . map (printf "%02X") . Lazy.unpack
+
 {- | Pretty printer for 'Datum'.
 
 > let d = [Int32 1,Float 1.2,string "str",midi (0,0x90,0x40,0x60)]
-> in map (datumPP (Just 5)) d ==  ["1","1.2","\"str\"","<0,144,64,96>"]
+> map (datumPP (Just 5)) d ==  ["1","1.2","\"str\"","<0,144,64,96>"]
 
 -}
 datumPP :: FP_Precision -> Datum -> String
@@ -238,7 +243,7 @@
       Float n -> floatPP p n
       Double n -> floatPP p n
       ASCII_String s -> show (Char8.unpack s)
-      Blob s -> show s
+      Blob s -> blobPP s
       TimeStamp t -> timePP p t
       Midi (MIDI b1 b2 b3 b4) -> vecPP [b1,b2,b3,b4]
 
diff --git a/Sound/OSC/Packet.hs b/Sound/OSC/Packet.hs
--- a/Sound/OSC/Packet.hs
+++ b/Sound/OSC/Packet.hs
@@ -3,8 +3,8 @@
 
 import Data.List {- base -}
 
-import Sound.OSC.Datum {- hosc3 -}
-import Sound.OSC.Time {- hosc3 -}
+import Sound.OSC.Datum {- hosc -}
+import Sound.OSC.Time {- hosc -}
 
 -- * Message
 
@@ -14,8 +14,8 @@
 type Address_Pattern = String
 
 -- | An OSC message, an 'Address_Pattern' and a sequence of 'Datum'.
-data Message = Message {messageAddress :: Address_Pattern
-                       ,messageDatum :: [Datum]}
+data Message = Message {messageAddress :: !Address_Pattern
+                       ,messageDatum :: ![Datum]}
                deriving (Eq,Read,Show)
 
 -- | 'Message' constructor.  It is an 'error' if the 'Address_Pattern'
@@ -29,8 +29,8 @@
 -- * Bundle
 
 -- | An OSC bundle, a 'Time' and a sequence of 'Message's.
-data Bundle = Bundle {bundleTime :: Time
-                     ,bundleMessages :: [Message]}
+data Bundle = Bundle {bundleTime :: !Time
+                     ,bundleMessages :: ![Message]}
               deriving (Eq,Read,Show)
 
 -- | OSC 'Bundle's can be ordered (time ascending).
@@ -48,8 +48,8 @@
 -- * Packet
 
 -- | An OSC 'Packet' is either a 'Message' or a 'Bundle'.
-data Packet = Packet_Message {packetMessage :: Message}
-            | Packet_Bundle {packetBundle :: Bundle}
+data Packet = Packet_Message {packetMessage :: !Message}
+            | Packet_Bundle {packetBundle :: !Bundle}
               deriving (Eq,Read,Show)
 
 -- | 'Packet_Bundle' of 'bundle'.
diff --git a/Sound/OSC/Time.hs b/Sound/OSC/Time.hs
--- a/Sound/OSC/Time.hs
+++ b/Sound/OSC/Time.hs
@@ -10,6 +10,8 @@
 import qualified Data.Time as T {- time -}
 import qualified Data.Time.Clock.POSIX as T {- time -}
 
+import Sound.OSC.Coding.Convert {- hosc -}
+
 -- * Temporal types
 
 -- | Type for binary (integeral) representation of a 64-bit @NTP@ timestamp (ie. @ntpi@).
@@ -36,12 +38,12 @@
 --
 -- > ntpr_to_ntpi immediately == 1
 -- > fmap ntpr_to_ntpi time
-ntpr_to_ntpi :: RealFrac n => n -> NTP64
+ntpr_to_ntpi :: Time -> NTP64
 ntpr_to_ntpi t = round (t * (2 ^ (32::Int)))
 
 -- | Convert an 'NTPi' timestamp to a real-valued NTP timestamp.
-ntpi_to_ntpr :: Fractional n => NTP64 -> n
-ntpi_to_ntpr t = fromIntegral t / 2^(32::Int)
+ntpi_to_ntpr :: NTP64 -> Time
+ntpi_to_ntpr t = word64_to_double t / 2^(32::Int)
 
 -- | Difference (in seconds) between /NTP/ and /UT/ epochs.
 --
diff --git a/Sound/OSC/Transport/FD/TCP.hs b/Sound/OSC/Transport/FD/TCP.hs
--- a/Sound/OSC/Transport/FD/TCP.hs
+++ b/Sound/OSC/Transport/FD/TCP.hs
@@ -9,6 +9,7 @@
 import qualified Sound.OSC.Coding.Decode.Binary as Binary {- hosc -}
 import qualified Sound.OSC.Coding.Encode.Builder as Builder {- hosc -}
 import qualified Sound.OSC.Coding.Byte as Byte {- hosc -}
+import qualified Sound.OSC.Coding.Convert as Convert {- hosc -}
 import qualified Sound.OSC.Packet as Packet {- hosc -}
 import qualified Sound.OSC.Transport.FD as FD {- hosc -}
 
@@ -19,15 +20,15 @@
 tcp_send_packet :: TCP -> Packet.Packet -> IO ()
 tcp_send_packet (TCP fd) p = do
   let b = Builder.encodePacket p
-      n = fromIntegral (B.length b)
-  B.hPut fd (B.append (Byte.encode_u32 n) b)
+      n = Convert.int64_to_word32 (B.length b)
+  B.hPut fd (B.append (Byte.encode_word32 n) b)
   IO.hFlush fd
 
 -- | Receive packet over TCP.
 tcp_recv_packet :: TCP -> IO Packet.Packet
 tcp_recv_packet (TCP fd) = do
   b0 <- B.hGet fd 4
-  b1 <- B.hGet fd (fromIntegral (Byte.decode_u32 b0))
+  b1 <- B.hGet fd (Convert.word32_to_int (Byte.decode_word32 b0))
   return (Binary.decodePacket b1)
 
 -- | Close TCP.
diff --git a/Sound/OSC/Transport/FD/UDP.hs b/Sound/OSC/Transport/FD/UDP.hs
--- a/Sound/OSC/Transport/FD/UDP.hs
+++ b/Sound/OSC/Transport/FD/UDP.hs
@@ -25,7 +25,7 @@
 
 -- | Receive packet over UDP.
 udp_recv_packet :: UDP -> IO Packet.Packet
-udp_recv_packet (UDP fd) = liftM Binary.decodePacket_strict (C.recv fd 8192)
+udp_recv_packet (UDP fd) = fmap Binary.decodePacket_strict (C.recv fd 8192)
 
 -- | Close UDP.
 udp_close :: UDP -> IO ()
@@ -66,11 +66,11 @@
 
 > import Control.Concurrent {- base -}
 
-> let t0 = udpServer "127.0.0.1" 57300
-> forkIO (FD.withTransport t0 (\fd -> forever (FD.recvMessage fd >>= print)))
+> let u0 = udpServer "127.0.0.1" 57300
+> t0 <- forkIO (FD.withTransport u0 (\fd -> forever (FD.recvMessage fd >>= print)))
 
-> let t1 = openUDP "127.0.0.1" 57300
-> FD.withTransport t1 (\fd -> FD.sendMessage fd (Packet.message "/n" []))
+> let u1 = openUDP "127.0.0.1" 57300
+> FD.withTransport u1 (\fd -> FD.sendMessage fd (Packet.message "/n" []))
 -}
 udpServer :: String -> Int -> IO UDP
 udpServer = udp_socket N.bind
diff --git a/Sound/OSC/Transport/Monad.hs b/Sound/OSC/Transport/Monad.hs
--- a/Sound/OSC/Transport/Monad.hs
+++ b/Sound/OSC/Transport/Monad.hs
@@ -47,8 +47,12 @@
 type Connection t a = R.ReaderT t IO a
 
 -- | Bracket Open Sound Control communication.
-withTransport :: FD.Transport t => IO t -> Connection t a -> IO a
+withTransport :: FD.Transport t => IO t -> Connection t r -> IO r
 withTransport u = FD.withTransport u . R.runReaderT
+
+-- | 'void' of 'withTransport'.
+withTransport_ :: FD.Transport t => IO t -> Connection t r -> IO ()
+withTransport_ u = void . withTransport u
 
 -- * Send
 
diff --git a/hosc.cabal b/hosc.cabal
--- a/hosc.cabal
+++ b/hosc.cabal
@@ -1,5 +1,5 @@
 Name:              hosc
-Version:           0.17
+Version:           0.18
 Synopsis:          Haskell Open Sound Control
 Description:       @hosc@ implements a subset of the Open Sound Control
                    byte protocol, <http://opensoundcontrol.org/>.
@@ -7,14 +7,14 @@
                    See "Sound.OSC.Core" or "Sound.OSC" or "Sound.OSC.FD".
 License:           GPL-3
 Category:          Sound
-Copyright:         (c) Rohan Drape, Stefan Kersten and others, 2007-2018
+Copyright:         (c) Rohan Drape, Stefan Kersten and others, 2007-2020
 Author:            Rohan Drape, Stefan Kersten
 Maintainer:        rd@rohandrape.net
 Stability:         Experimental
 Homepage:          http://rohandrape.net/t/hosc
-Tested-With:       GHC == 8.4.3
+Tested-With:       GHC == 8.6.5
 Build-Type:        Simple
-Cabal-Version:     >= 1.8
+Cabal-Version:     >= 1.10
 Data-Files:        README
 
 Library
@@ -26,6 +26,7 @@
                    network >= 2.3,
                    time >= 1.5,
                    transformers
+  Default-Language:Haskell2010
   GHC-Options:     -Wall -fwarn-tabs
   Exposed-modules: Sound.OSC
                    Sound.OSC.Coding.Byte
