module Csound.Tfm.RateGraph( grate, KrateSet ) where import Data.List(sort, sortBy, nub, find) import qualified Data.Map as M import qualified Data.IntMap as IM import qualified Data.Set as S import Data.Ord(comparing) import Data.Maybe(fromJust, catMaybes) import Data.Either import Data.Default import Data.STRef import Control.Monad.ST import Data.Array import Data.Array.ST import Data.Array.MArray import Csound.Exp import Debug.Trace echo :: Show a => String -> a -> a echo msg a = trace (msg ++ ": " ++ show a) a type AgentId = Int data Agent = Agent { agentResponses :: [Response] , agentQueries :: [Query] , agentRate :: Rate , agentConversions :: [Conversion] } type Conversion = (RatedVar, Exp RatedVar) type KrateSet = S.Set Name instance Default Agent where def = Agent [] [] Xr [] data Addr = Addr { addrLine :: Int , addrArg :: Int } deriving (Show) data Query = Query { queryAddr :: Addr , queryRate :: Rate } deriving (Show) data Response = Response { responseAddr :: Addr , responseRatedVar :: RatedVar } deriving (Show) newtype MsgBox s = MsgBox { unMsgBox :: STArray s Int Agent } modifyArray :: Ix i => STArray s i a -> i -> (a -> a) -> ST s () modifyArray arr i f = writeArray arr i . f =<< readArray arr i msgBox :: Int -> ST s (MsgBox s) msgBox size = fmap MsgBox $ newArray (0, size - 1) def sendQuery :: AgentId -> Query -> MsgBox s -> ST s () sendQuery pid q box = modifyArray (unMsgBox box) pid $ \x -> x{ agentQueries = q : agentQueries x } sendResponse :: AgentId -> Response -> MsgBox s -> ST s () sendResponse pid r box = modifyArray (unMsgBox box) pid $ \x -> x{ agentResponses = r : agentResponses x } loadAgent :: AgentId -> MsgBox s -> ST s Agent loadAgent pid box = readArray (unMsgBox box) pid saveAgent :: AgentId -> MsgBox s -> Agent -> ST s () saveAgent pid box e = writeArray (unMsgBox box) pid e discussLine :: KrateSet -> MsgBox s -> STRef s Int -> (Int, RatedExp Int) -> ST s () discussLine krateSet box freshIds (pid, exp) = do ag <- loadAgent pid box let desiredRates = nub $ map queryRate $ agentQueries ag curRate = deduceRate krateSet desiredRates exp notifyChildren pid curRate (ratedExpExp exp) box convTab <- conversionTable freshIds pid curRate desiredRates notifyParents box convTab (agentQueries ag) saveAgent pid box $ ag{ agentRate = curRate, agentConversions = getConversions pid curRate convTab } deduceRate :: KrateSet -> [Rate] -> RatedExp Int -> Rate deduceRate krateSet desiredRates exp = case ratedExpExp exp of ExpPrim p -> case desiredRates of [Sr] -> Sr _ -> Ir Tfm info as | isProcedure info -> Xr Tfm info as -> case infoSignature info of MultiRate _ _ -> Xr SingleRate tab -> let r1 = tfmNoRate (infoName info) desiredRates tab in case ratedExpRate exp of Just r | M.member r tab -> r Just r -> r1 Nothing -> r1 ExpNum _ -> case maximum desiredRates of Xr -> Ar r -> r Select rate _ _ -> rate If info a b -> head $ filter (/= Xr) $ sort desiredRates ReadVar v -> varRate v WriteVar _ _ -> Xr where tfmNoRate name desiredRates tab = case sort desiredRates of [Xr] -> let newDesiredRates = if S.member name krateSet then [Kr] else [Ar] in tfmNoRate name newDesiredRates tab Xr:as -> tfmNoRate name as tab as -> fromJust $ find (flip M.member tab) (as ++ [minBound .. maxBound]) notifyChildren :: AgentId -> Rate -> Exp Int -> MsgBox s -> ST s () notifyChildren pid curRate exp box = mapM_ (\(to, query) -> sendQuery to query box) $ catMaybes $ fmap wrapFromEither $ case exp of Tfm info xs -> notifyTfm curRate (infoSignature info) xs WriteVar v a -> [(a, mkQuery 0 $ varRate v)] If info a b -> (a, mkQuery (-2) curRate) : (b, mkQuery (-1) curRate) : encodeIfEnv (max Kr curRate) (inlineEnv info) ExpNum (PreInline op xs) -> queryList xs (repeat curRate) _ -> [] where notifyTfm r signature xs = case signature of SingleRate table -> queryList xs $ table M.! r MultiRate _ rs -> queryList xs rs queryList args rates = zipWith3 (\n a r -> (a, mkQuery n r)) [0 .. ] args rates mkQuery n r = Query (Addr pid n) r encodeIfEnv rate info = map (\(port, arg) -> (arg, mkQuery port rate)) $ IM.toList info wrapFromEither (to, query) = either (const Nothing) (Just . \x -> (x, query)) (unPrimOr to) notifyParents :: MsgBox s -> M.Map Rate RatedVar -> [Query] -> ST s () notifyParents box convTab qs = mapM_ (notifyParent box convTab) qs notifyParent :: MsgBox s -> M.Map Rate RatedVar -> Query -> ST s () notifyParent box convTab q = sendResponse (addrLine $ queryAddr q) (Response (queryAddr q) (convTab M.! queryRate q)) box conversionTable :: STRef s Int -> Int -> Rate -> [Rate] -> ST s (M.Map Rate RatedVar) conversionTable freshIds curId curRate desiredRates = fmap M.fromList $ mapM (flip mkRatedVar curRate) desiredRates where coherentRates to from = case (to, from) of (Xr, a) -> True (a, b) | a == b -> True (Kr, Ir) -> True _ -> False mkRatedVar to from | coherentRates to from = return $ (to, RatedVar curRate curId) | otherwise = do n <- newId freshIds return $ (to, RatedVar to n) newId x = do n <- readSTRef x modifySTRef x succ return n getConversions :: AgentId -> Rate -> M.Map Rate RatedVar -> [Conversion] getConversions pid curRate convTable = uncurry phi =<< M.toList convTable where phi rate var@(RatedVar r n) | n == pid = [] | otherwise = [(var, ConvertRate r curRate $ PrimOr $ Right $ RatedVar curRate pid)] processLine :: MsgBox s -> (Int, RatedExp Int) -> ST s [(RatedVar, Exp RatedVar)] processLine box (pid, exp) = fmap phi $ loadAgent pid box where phi a = agentConversions a ++ return (RatedVar (agentRate a) pid, rateExp (agentRate a) (agentResponses a) (ratedExpExp exp)) rateExp :: Rate -> [Response] -> Exp Int -> Exp RatedVar rateExp curRate rs exp = case exp of ExpPrim (P n) | curRate == Sr -> ExpPrim (PString n) ExpPrim p -> ExpPrim p Tfm i vsOld -> Tfm i $ mergeWithPrimOr vsOld vs Select rate pid a -> Select rate pid (fmap (RatedVar Xr) a) If condInfo a' b' -> case mergeWithPrimOr (encodeIfEnv condInfo a' b') vs of a:b:rest -> If (decodeIfEnv condInfo rest) a b ExpNum (PreInline op vsOld) -> ExpNum (PreInline op $ mergeWithPrimOr vsOld vs) ReadVar v -> ReadVar v WriteVar v vsOld -> WriteVar v (substPrimOr vsOld $ head vs) where vs = map responseRatedVar $ sortBy (comparing $ addrArg . responseAddr) rs decodeIfEnv info xs = info{ inlineEnv = IM.fromList $ zip [0..] xs } encodeIfEnv info a b = a:b:(IM.elems $ inlineEnv info) substPrimOr :: PrimOr a -> b -> PrimOr b substPrimOr p val = PrimOr $ case unPrimOr p of Left a -> Left a Right _ -> Right val mergeWithPrimOr :: [PrimOr a] -> [b] -> [PrimOr b] mergeWithPrimOr xs ys = case (xs, ys) of ([], _) -> [] (PrimOr (Left a):as, bs) -> PrimOr (Left a) : mergeWithPrimOr as bs (PrimOr (Right _):as, b:bs) -> PrimOr (Right b) : mergeWithPrimOr as bs findRate :: [Rate] -> Rate findRate [x] = x findRate xs = case sort $ nub xs of [a] -> a [] -> Xr Xr:as -> minimum as as -> minimum as grate :: KrateSet -> [(Int, RatedExp Int)] -> ([(RatedVar, Exp RatedVar)], Int) grate krateSet as = runST $ do freshIds <- newSTRef n box <- msgBox n mapM_ (discussLine krateSet box freshIds) lines graph <- fmap (reverse . concat) $ mapM (processLine box) lines lastFreshId <- readSTRef freshIds return (graph, lastFreshId) where n = length as lines = reverse as