diff --git a/essence-of-live-coding-pulse.cabal b/essence-of-live-coding-pulse.cabal
--- a/essence-of-live-coding-pulse.cabal
+++ b/essence-of-live-coding-pulse.cabal
@@ -1,5 +1,5 @@
 name:                essence-of-live-coding-pulse
-version:             0.2.0.1
+version:             0.2.1
 synopsis: General purpose live coding framework - pulse backend
 description:
   essence-of-live-coding is a general purpose and type safe live coding framework.
@@ -28,10 +28,9 @@
 library
   exposed-modules:
     LiveCoding.Pulse
-    LiveCoding.Pulse.GHCi
   build-depends:
       base >= 4.11 && < 5
-    , essence-of-live-coding >= 0.2.0.1
+    , essence-of-live-coding >= 0.2.1
     , transformers >= 0.5
     , pulse-simple >= 0.1
     , foreign-store >= 0.2
diff --git a/src/LiveCoding/Pulse.hs b/src/LiveCoding/Pulse.hs
--- a/src/LiveCoding/Pulse.hs
+++ b/src/LiveCoding/Pulse.hs
@@ -6,9 +6,12 @@
 import Control.Concurrent
 import Control.Monad (forever)
 import Control.Monad.Fix
+import Data.Monoid (getSum, Sum(Sum))
 
 -- transformers
+import Control.Monad.Trans.Class (MonadTrans(lift))
 import Control.Monad.Trans.Reader
+import Control.Monad.Trans.Writer.Strict
 
 -- pulse-simple
 import Sound.Pulse.Simple
@@ -16,63 +19,110 @@
 -- essence-of-live-coding
 import LiveCoding
 
-type PulseCell = Cell IO () Float
+type PulseT m = WriterT (Sum Float) m
 
-playPulseCell :: PulseCell -> IO (MVar PulseCell)
-playPulseCell pulseCell = do
-  var <- newMVar pulseCell
-  pulseClient <- simpleNew
-    Nothing
-    "example"
-    Play
-    Nothing
-    "this is an example application"
-    (SampleSpec (F32 LittleEndian) 44100 1)
-    Nothing
-    Nothing
-  forkIO $ forever $ do
-    cell <- takeMVar var
-    (samples, cell') <- steps cell $ replicate 1024 ()
-    simpleWrite pulseClient samples
-    putMVar var cell'
-  return var
+type PulseCell m a b = Cell (PulseT m) a b
 
--- TODO Generalisable
-updatePulse :: MVar PulseCell -> PulseCell -> IO ()
-updatePulse var newCell = do
-  oldCell <- takeMVar var
-  putMVar var $ hotCodeSwapCell newCell oldCell
+-- | Compose with this cell to play a sound sample.
+addSample :: Monad m => PulseCell m Float ()
+addSample = arr Sum >>> arrM tell
 
--- Returns the sum between -1 and 1
+-- | Globally fix the sample rate to 48000 samples per second.
+sampleRate :: Num a => a
+sampleRate = 48000
+
+{- | Create a pulse server backend handle.
+
+Currently, this is always mono,
+but with a future release of @pulse-simple@,
+this might be configurable.
+-}
+pulseHandle :: Handle IO Simple
+pulseHandle = Handle
+  { create = simpleNew
+      Nothing
+      "example"
+      Play
+      Nothing
+      "this is an example application"
+      (SampleSpec (F32 LittleEndian) sampleRate 1)
+      Nothing
+      Nothing
+  , destroy = simpleFree
+  }
+
+{- | Run a 'PulseCell' with a started pulse backend.
+
+Currently, this is synchronous and blocking,
+i.e. the resulting cell will block until the backend buffer is nearly empty.
+
+This performs several steps of your cell at a time,
+replicating the input so many times.
+-}
+pulseWrapC
+  :: Int
+  -- ^ Specifies how many steps of your 'PulseCell' should be performed in one step of 'pulseWrapC'.
+  -> PulseCell IO a b
+  -- ^ Your cell that produces samples.
+  -> Cell (HandlingStateT IO) a [b]
+pulseWrapC bufferSize cell = proc a -> do
+  simple <- handling pulseHandle -< ()
+  samplesAndBs <- resampleList $ liftCell $ runWriterC cell -< replicate bufferSize a
+  let (samples, bs) = unzip samplesAndBs
+      samples' = getSum <$> samples
+  arrM $ lift . uncurry simpleWrite -< samples' `seq` bs `seq` (simple, samples')
+  returnA -< bs
+
+{- | Returns the sum of all incoming values,
+and wraps it between -1 and 1.
+
+This is to prevent floating number imprecision when the sum gets too large.
+-}
 wrapSum :: (Monad m, Data a, RealFloat a) => Cell m a a
 wrapSum = Cell
   { cellState = 0
   , cellStep  = \accum a ->
     let
-        (_, accum')  = properFraction $ accum + a
+        (_, accum') = properFraction $ accum + a
     in return (accum', accum')
   }
 
+-- | Like 'wrapSum', but as an integral, assuming the PulseAudio 'sampleRate'.
+wrapIntegral :: (Monad m, Data a, RealFloat a) => Cell m a a
+wrapIntegral = arr (/ sampleRate) >>> wrapSum
+
 modSum :: (Monad m, Data a, Integral a) => a -> Cell m a a
 modSum denominator = Cell
   { cellState = 0
   , cellStep  = \accum a -> let accum' = (accum + a) `mod` denominator in return (accum', accum')
   }
 
-
 clamp :: (Ord a, Num a) => a -> a -> a -> a
 clamp lower upper a = min upper $ max lower a
 
-osc :: (Data a, RealFloat a, MonadFix m) => Cell (ReaderT a m) () a
+-- | A sine oscillator.
+--   Supply the frequency via the 'ReaderT' environment.
+--   See 'osc'' and 'oscAt'.
+osc :: (Data a, RealFloat a, Monad m) => Cell (ReaderT a m) () a
 osc = proc _ -> do
   f <- constM ask -< ()
-  phase <- wrapSum -< f / 44100
+  phase <- wrapIntegral -< f
   returnA -< sin $ 2 * pi * phase
 
-osc' :: (Data a, RealFloat a, MonadFix m) => Cell m a a
+-- | A sine oscillator, at a fixed frequency.
+oscAt :: (Data a, RealFloat a, Monad m) => a -> Cell m () a
+oscAt = flip runReaderC osc
+
+-- | A sine oscillator, at a frequency that can be specified live.
+osc' :: (Data a, RealFloat a, Monad m) => Cell m a a
 osc' = proc a -> do
   runReaderC' osc -< (a, ())
 
+{- | A basic musical note (western traditional notation, german nomenclature).
+
+Assumes equal temperament and removes enharmonic equivalents,
+i.e. there is only Dis (= D sharp) but not Eb (= E flat).
+-}
 data Note
   = A
   | Bb
@@ -88,9 +138,15 @@
   | Gis
   deriving (Enum, Show)
 
+-- | Calculate the frequency of a note,
+--   with 'A' corresponding to 220 Hz.
 f :: Note -> Float
 f note = 220 * (2 ** (fromIntegral (fromEnum note) / 12))
 
+-- | Transpose a frequency an octave higher, i.e. multiply by 2.
 o :: Float -> Float
 o = (* 2)
 
+-- | Transpose a frequency an octave lower, i.e. divide by 2.
+oB :: Float -> Float
+oB = (/ 2)
diff --git a/src/LiveCoding/Pulse/GHCi.hs b/src/LiveCoding/Pulse/GHCi.hs
deleted file mode 100644
--- a/src/LiveCoding/Pulse/GHCi.hs
+++ /dev/null
@@ -1,29 +0,0 @@
-module LiveCoding.Pulse.GHCi where
-
--- base
-import Control.Concurrent
-
--- foreign-store
-import Foreign.Store
-
--- essence-of-live-coding
-import LiveCoding.LiveProgram
-
--- essence-of-live-coding-pulse
-import LiveCoding.Pulse
-
-livepulse "" = livepulse "pulseCell"
-livepulse pulseCell = return $ unlines
-  [ "var <- playPulseCell " ++ pulseCell
-  , "savePulse var"
-  ]
-livereloadpulse "" = livereloadpulse "pulseCell"
-livereloadpulse pulseCell  = return $ unlines
-  [ ":reload"
-  , "var <- loadPulse"
-  , "updatePulse var " ++ pulseCell
-  ]
-loadPulse :: IO (MVar PulseCell)
-loadPulse = readStore (Store 0)
-savePulse :: MVar PulseCell -> IO ()
-savePulse var = writeStore (Store 0) var
