diff --git a/bindings-codec2.cabal b/bindings-codec2.cabal
--- a/bindings-codec2.cabal
+++ b/bindings-codec2.cabal
@@ -1,5 +1,5 @@
 name:                bindings-codec2
-version:             0.1.0.0
+version:             0.1.1.0
 synopsis:            Very low-level FFI bindings for Codec2
 description:         Bindings for Codec2 generated by jwiegley's c2hsc/bindings-dsl
 homepage:            https://github.com/relrod/Codec2-hs
@@ -18,7 +18,7 @@
   branch: master
 
 library
-  exposed-modules:     
+  exposed-modules:
     Bindings.Codec2
   default-extensions:  ForeignFunctionInterface
   build-depends:       base >= 4 && < 5
@@ -27,3 +27,15 @@
   hs-source-dirs: src
   default-language: Haskell2010
   cc-options: -fPIC
+
+executable bindings-codec2-demo
+  main-is:             Demo.hs
+  hs-source-dirs:      src
+  build-depends:       base >= 4 && < 5
+                     , binary
+                     , bindings-codec2
+                     , bytestring
+                     , split >= 0.2 && < 0.3
+
+  default-language:    Haskell2010
+  ghc-options:         -Wall -O2
diff --git a/src/Bindings/Codec2.hsc b/src/Bindings/Codec2.hsc
--- a/src/Bindings/Codec2.hsc
+++ b/src/Bindings/Codec2.hsc
@@ -16,3 +16,10 @@
 #ccall codec2_set_lpc_post_filter , Ptr <struct CODEC2> -> CInt -> CInt -> CFloat -> CFloat -> IO ()
 #ccall codec2_get_spare_bit_index , Ptr <struct CODEC2> -> IO CInt
 #ccall codec2_rebuild_spare_bit , Ptr <struct CODEC2> -> Ptr CInt -> IO CInt
+
+#num CODEC2_MODE_3200
+#num CODEC2_MODE_2400
+#num CODEC2_MODE_1600
+#num CODEC2_MODE_1400
+#num CODEC2_MODE_1300
+#num CODEC2_MODE_1200
diff --git a/src/Demo.hs b/src/Demo.hs
new file mode 100644
--- /dev/null
+++ b/src/Demo.hs
@@ -0,0 +1,66 @@
+module Main where
+
+import Data.Binary
+import Data.Binary.Get
+import Data.Binary.Put
+import qualified Data.ByteString.Lazy.Char8 as L
+import Data.List.Split (chunksOf)
+import Foreign.C.Types
+import Foreign.Marshal.Alloc
+import Foreign.Marshal.Array
+import Foreign.Ptr
+import Foreign.Storable
+import Bindings.Codec2
+import System.Environment
+
+main :: IO ()
+main = do
+  (fin:fout:_) <- getArgs
+  let mode = c'CODEC2_MODE_3200
+  codec2 <- c'codec2_create mode
+  nsam <- c'codec2_samples_per_frame codec2
+  nbit <- c'codec2_bits_per_frame codec2
+  buf <- mallocBytes $ sizeOf (0 :: CShort) * fromIntegral nsam :: IO (Ptr CShort)
+  let nbyte = floor $ (fromIntegral (nbit + 7) / 8 :: Float) :: Int
+  bits <- mallocBytes $ sizeOf (0 :: CUChar) * fromIntegral nbyte :: IO (Ptr CUChar)
+  input <- L.readFile fin
+  let samps = runGet getSamples input
+  mapM_ (\x -> c2encode codec2 x buf bits nbyte fout) (chunksOf (fromIntegral nsam) samps)
+
+getSample :: Get CShort
+getSample = do
+  s <- getWord16le
+  return $! CShort (fromIntegral s)
+
+getSamples :: Get [CShort]
+getSamples = do
+  empty <- isEmpty
+  if empty
+    then return []
+    else do
+      samp <- getSample
+      remaining' <- getSamples
+      return (samp:remaining')
+
+-- | TODO: Is there a better way to do this?
+putCUChar :: [CUChar] -> Put
+putCUChar input = do
+  mapM_ (put . inner) input
+  return ()
+  where
+    inner (CUChar i) = i
+
+c2encode :: Ptr C'CODEC2
+         -> [CShort]     -- ^ A list that is @nsam@ elements in length.
+         -> Ptr CShort   -- ^ The pointer that the ['Word8'] above gets poked to.
+         -> Ptr CUChar   -- ^ The pointer that codec2_encode should store in.
+         -> Int          -- ^ How big the result is
+         -> String       -- ^ Output filename
+         -> IO ()
+c2encode codec2 frame poke' store nbyte fout = do
+  pokeArray poke' frame
+  c'codec2_encode codec2 store poke'
+  -- And, for testing:
+  written <- peekArray nbyte store
+  let bs = runPut $ putCUChar written
+  L.appendFile fout bs
