packages feed

synthesizer-core-0.4.2: src/Synthesizer/CausalIO/Process.hs

{-# LANGUAGE ExistentialQuantification #-}
{- |
Process chunks of data in the IO monad.
Typical inputs are strict storable vectors and piecewise constant values,
and typical outputs are strict storable vectors.
You may also combine several of these types using the Zip type constructor.

We may substitute IO by ST in the future, but I am uncertain about that.
-}
module Synthesizer.CausalIO.Process where

import qualified Synthesizer.Causal.Process as Causal

import qualified Synthesizer.Generic.Signal as SigG
import qualified Synthesizer.Generic.Cut as CutG
import qualified Synthesizer.Zip as Zip

import qualified Data.StorableVector.Lazy as SVL
import qualified Data.StorableVector as SV

import Foreign.Storable (Storable, )

import Control.Monad.Trans.State (runStateT, )

import qualified Control.Arrow    as Arr
import qualified Control.Category as Cat

import Control.Arrow ((^<<), (&&&), )
import Control.Monad (mplus, )

import Data.Monoid (Monoid, mempty, mappend, )

import Data.Tuple.HT (mapSnd, )

import System.IO.Unsafe (unsafePerformIO, unsafeInterleaveIO, )


data T p a b =
   forall state context.
   Cons
      {-
      If the transition function returns a chunk
      that is shorter than the input,
      then this is the last chunk.
      This way we do not need a MaybeT IO.
      -}
      (a -> state -> IO (b, state))
      (p -> IO state)
      {-
      The delete function must not do anything serious,
      e.g. close files,
      because it might not be called.
      Something like 'touchForeignPtr' is reasonable.
      -}
      (state -> IO ())


instance Cat.Category (T p) where
   id = Arr.arr id
   (Cons nextB createB deleteB) .
          (Cons nextA createA deleteA) = Cons
      (\a (sa0,sb0) -> do
         (b,sa1) <- nextA a sa0
         (c,sb1) <- nextB b sb0
         return (c,(sa1,sb1)))
      (\p -> do
         sa <- createA p
         sb <- createB p
         return (sa,sb))
      (\(sa,sb) ->
         deleteA sa >> deleteB sb)

instance Arr.Arrow (T p) where
   arr f = Cons
      (\ a () -> return (f a, ()))
      (\ _p -> return ())
      (\ () -> return ())
   first (Cons next create delete) = Cons
      (\(b,d) sa0 ->
         do (c,sa1) <- next b sa0
            return ((c,d), sa1))
      create
      delete

fromCausal ::
   (Monoid b) =>
   Causal.T a b -> T p a b
fromCausal (Causal.Cons next start) = Cons
   (\a s0 ->
      return $
      case runStateT (next a) s0 of
         Nothing -> (mempty, s0)
         Just (b,s1) -> (b,s1))
   (\ _p -> return $ start)
   (\ _ -> return ())

mapAccum ::
   (p -> a -> state -> (b, state)) ->
   (p -> state) ->
   T p a b
mapAccum next start =
   Cons
      (\a (p,s) -> return $ mapSnd ((,) p) $ next p a s)
      (\p -> return (p, start p))
      (\ _p -> return ())


runStorableChunkyCont ::
   (Storable a, Storable b) =>
   T p (SV.Vector a) (SV.Vector b) ->
   IO ((SVL.Vector a -> SVL.Vector b) ->
       p ->
       SVL.Vector a -> SVL.Vector b)
runStorableChunkyCont (Cons next create delete) =
   return $
      \ procRest p sig ->
      SVL.fromChunks $ unsafePerformIO $ do
         state <- create p

         let go xt s0 =
               unsafeInterleaveIO $
               case xt of
                  [] -> delete s0 >> return []
                  x:xs -> do
                     (y,s1) <- next x s0
                     (if SV.length y > 0
                        then fmap (y:)
                        else id) $
                        (if SV.length y < SV.length x
                           then return $ SVL.chunks $
                                procRest $ SVL.fromChunks $
                                SV.drop (SV.length y) x : xs
                           else go xs s1)
         go (SVL.chunks sig) state


zip ::
   (Arr.Arrow arrow) =>
   arrow a b -> arrow a c -> arrow a (Zip.T b c)
zip ab ac =
   uncurry Zip.Cons ^<< ab &&& ac


{- |
@mappend@ should be used sparingly.
In a loop it will have to construct types at runtime
which is rather expensive.
-}
instance (CutG.Transform a, CutG.Read b, Monoid b) => Monoid (T p a b) where
   mempty = Cons
      (\ _a () -> return (mempty, ()))
      (\ _p -> return ())
      (\() -> return ())
   mappend
         (Cons nextB createB deleteB)
         (Cons nextA createA deleteA) = Cons
      (\a s ->
         case s of
            Left (p,s0) -> do
               (b1,s1) <- nextA a s0
               let lenA = CutG.length a
                   lenB = CutG.length b1
               case compare lenA lenB of
                  LT -> error "CausalIO.Process.mappend: output chunk is larger than input chunk"
                  EQ -> return (b1, Left (p,s1))
                  GT -> do
                     deleteA s1
                     s2 <- createB p
                     (b3,s3) <- nextB (CutG.drop lenB a) s2
                     return (b3, Right s3)
            Right s0 -> do
               (b1,s1) <- nextB a s0
               return (b1, Right s1))
      (\p -> do
         sa <- createA p
         return (Left (p,sa)))
      (\s ->
         case s of
            Left (_p,s0) -> deleteA s0
            Right s0 -> deleteB s0)


continue ::
   (CutG.Transform a, SigG.Transform sig b) =>
   T p a (sig b) -> T (p,b) a (sig b) -> T p a (sig b)
continue
      (Cons nextA createA deleteA)
      (Cons nextB createB deleteB) = Cons
   (\a s ->
      case s of
         Left (p, lastB0, s0) -> do
            (b1,s1) <- nextA a s0
            let lenA = CutG.length a
                lenB = CutG.length b1
                lastB1 =
                   mplus (fmap snd $ SigG.viewR b1) lastB0
            case compare lenA lenB of
               LT -> error "CausalIO.Process.mappend: output chunk is larger than input chunk"
               EQ -> return (b1, Left (p,lastB1,s1))
               GT ->
                  case lastB1 of
                     Nothing -> return (mempty, Left (p,lastB1,s1))
                     Just lastB -> do
                        deleteA s1
                        s2 <- createB (p, lastB)
                        (b3,s3) <- nextB (CutG.drop lenB a) s2
                        return (b3, Right s3)
         Right s0 -> do
            (b1,s1) <- nextB a s0
            return (b1, Right s1))
   (\p -> do
      sa <- createA p
      return (Left (p, Nothing, sa)))
   (\s ->
      case s of
         Left (_p,_lastB,s0) -> deleteA s0
         Right s0 -> deleteB s0)