hdf-0.15: Sound/DF/Uniform/UDF.hs
-- | Untyped /DF/.
module Sound.DF.Uniform.UDF where
import qualified Data.Graph.Inductive as G {- fgl -}
import qualified Data.Graph.Inductive.Dot as G {- fgl-visualize -}
import Data.Maybe {- base -}
import Data.List {- base -}
import Data.Typeable {- base -}
import Sound.OSC {- hosc -}
import System.Directory {- directory -}
import System.Environment {- base -}
import System.FilePath {- filepath -}
import System.Process {- process -}
import Text.Printf {- base -}
import qualified Sound.DF.Uniform.LL.Audition as L
import Sound.DF.Uniform.LL.CGen
import Sound.DF.Uniform.LL.Dot
import Sound.DF.Uniform.LL.K
import Sound.DF.Uniform.LL.UId
-- | Recursion identifier.
data R_Id = R_Id Id deriving (Eq,Show)
from_r_id :: R_Id -> Id
from_r_id (R_Id n) = n
-- | Un-typed data-flow node.
-- K = constant, A = array, R = recursion, P = primitive, MRG = multiple root graph.
data UDF = UDF_K {udf_k :: K}
| UDF_A {udf_a :: Vec Float}
| UDF_R R_Id (Either K (UDF,UDF))
| UDF_P String TypeRep [UDF]
| UDF_MRG UDF UDF
deriving(Eq,Show)
-- | Concise pretty printer for 'UDF'.
udf_concise :: UDF -> String
udf_concise n =
case n of
UDF_K k -> k_concise k
UDF_A v -> vec_concise v
UDF_R _ (Left i) -> printf "recRd:%s" (k_concise i)
UDF_R _ (Right _) -> "recWr"
UDF_P nm ty _ -> printf "%s:%s" nm (show ty)
UDF_MRG l r -> printf "mrg(%s,%s)" (show l) (show r)
-- | Maybe variant of 'udf_k'.
udf_k' :: UDF -> Maybe K
udf_k' n =
case n of
UDF_K x -> Just x
_ -> Nothing
-- | List elements in left biased order.
udf_elem :: UDF -> [UDF]
udf_elem n =
case n of
UDF_K _ -> [n]
UDF_A _ -> [n]
UDF_P _ _ i -> n : concatMap udf_elem i
UDF_R _ (Left _) -> [n]
UDF_R _ (Right (l,r)) -> n : (udf_elem l ++ udf_elem r)
UDF_MRG l r -> n : (udf_elem l ++ udf_elem r)
-- | Output type of 'UDF'.
udf_typeOf :: UDF -> TypeRep
udf_typeOf df =
case df of
UDF_K k -> k_typeOf k
UDF_A _ -> vec_float_t
UDF_P _ t _ -> t
UDF_R _ (Left k) -> k_typeOf k
UDF_R _ (Right (n,_)) -> udf_typeOf n
UDF_MRG n _ -> udf_typeOf n
-- | Traversal with state, signature as 'mapAccumL'.
udf_traverse :: (st -> UDF -> (st,UDF)) -> st -> UDF -> (st,UDF)
udf_traverse f st udf =
case udf of
UDF_K _ -> f st udf
UDF_A _ -> f st udf
UDF_P nm ty i ->
let (st',i') = mapAccumL f st i
in f st' (UDF_P nm ty i')
UDF_R _ (Left _) -> f st udf
UDF_R r (Right (p,q)) ->
let (st',p') = f st p
(st'',q') = f st' q
in f st'' (UDF_R r (Right (p',q')))
UDF_MRG p q ->
let (st',p') = f st p
(st'',q') = f st' q
in f st'' (UDF_MRG p' q')
-- * Graph
-- | Index for input port.
type Port_Index = Int
-- | A node is a 'UDF' with associated 'Id'.
type Node = (Id,UDF)
-- | Enumeration of 'Edge' types.
data Edge_Ty = Normal_Edge
| Rec_Wr_Edge Id -- ^ Edge /to/ recWr node
| Rec_Rd_Edge Id -- ^ Edge /from/ recRd node
| Implicit_Edge Int -- ^ Edge /to/ recRd node (from recWr)
-- | Pretty printer for 'Edge_Ty', and 'Show' instance.
edge_ty_concise :: Edge_Ty -> String
edge_ty_concise e =
case e of
Normal_Edge -> "normal"
Rec_Rd_Edge _ -> "recRd"
Rec_Wr_Edge _ -> "recWr"
Implicit_Edge _ -> "implicit"
instance Show Edge_Ty where show = edge_ty_concise
-- | Edge from left hand side node to right hand side port.
type Edge = (Id,Id,(Port_Index,Edge_Ty))
-- | A graph is a list of 'Node's and 'Edge's.
type Graph = ([Node],[Edge])
-- | A variant graph form associating the list of /in/ edges with each
-- 'Node'.
type Analysis = [(Node,[Edge])]
-- | 'Id' of 'Node'.
node_id :: Node -> Id
node_id = fst
-- | 'UDF' of 'Node'.
node_udf :: Node -> UDF
node_udf = snd
-- | Read label of node.
label :: [Node] -> UDF -> Id
label ns n =
let r = find ((== n) . node_udf) ns
in maybe (error ("label: " ++ show n)) node_id r
-- | Transform node to source, see through 'UDF_R' (rec) and 'UDF_MRG' (mrg).
source :: [Node] -> UDF -> Id
source ns n =
case n of
UDF_K _ -> label ns n
UDF_A _ -> label ns n
UDF_P _ _ _ -> label ns n
UDF_R _ (Left _) -> label ns n
UDF_R _ (Right (n',_)) -> source ns n'
UDF_MRG l _ -> source ns l
-- | Type of /out/ edge of 'UDF'.
udf_edge_ty :: UDF -> Edge_Ty
udf_edge_ty u =
case u of
UDF_R (R_Id k) (Left _) -> Rec_Rd_Edge k
UDF_R (R_Id _) (Right (n,_)) -> udf_edge_ty n
_ -> Normal_Edge
-- | List /incoming/ node edges.
edges :: [Node] -> UDF -> [Edge]
edges ns u =
case u of
UDF_P _ _ is ->
let f i k = (source ns i,label ns u,(k,udf_edge_ty i))
in zipWith f is [0..]
UDF_R (R_Id k) (Right (_,r)) ->
[(source ns r,label ns u,(0,Rec_Wr_Edge k))]
_ -> []
-- | True if 'Node' is 'Right' form of 'UDF_R' with indicated 'R_Id'.
match_rec :: R_Id -> Node -> Bool
match_rec x n =
case n of
(_,UDF_R y (Right _)) -> x == y
_ -> False
-- | Implicit edge from wR to rW.
implicit_edge :: [Node] -> Node -> Maybe Edge
implicit_edge n nd =
case nd of
(i,UDF_R d (Left _)) ->
let (j,_) = fromMaybe
(error (show ("implicit_edge",nd)))
(find (match_rec d) n)
in Just (j,i,(0,Implicit_Edge 1))
_ -> Nothing
-- | Is 'Node' 'UDF_K'.
is_k_node :: Node -> Bool
is_k_node (_,udf) =
case udf of
UDF_K _ -> True
_ -> False
-- | An 'Edge' is orphaned if it refers to a 'Node' that is not in the
-- node list.
is_orphan_edge :: [Node] -> Edge -> Bool
is_orphan_edge n (i,j,_) =
let k = map fst n
in i `notElem` k || j `notElem` k
-- | Transform the actual graph into a viewing graph by adding
-- implicit edges from /recWr/ to /recRd/ nodes.
vgraph_impl :: Graph -> Graph
vgraph_impl (n,e) =
let n' = filter (not . is_k_node) n
e' = mapMaybe (implicit_edge n) n
e'' = filter (not . is_orphan_edge n') (e ++ e')
in (n',e'')
-- | Find edge with indicated right hand side port.
find_in_edge_m :: [Edge] -> (Id,Port_Index) -> Maybe Edge
find_in_edge_m e (r,p) =
let f (_,r',(p',_)) = r == r' && p == p'
in find f e
-- | Variant of 'find_in_edge_m' that 'error's.
find_in_edge :: [Edge] -> (Id,Port_Index) -> Edge
find_in_edge e rp =
let err = error (show ("find_in_edge",e,rp))
in fromMaybe err (find_in_edge_m e rp)
-- | Trace in edges until arrival at a 'Rec_Wr_Edge' that is not
-- proceeded by an 'Implicit_Edge'. This traces the /depth/ of the
-- chain, however that is not currently drawn.
solve_rec_edge :: Int -> [Edge] -> (Id,Port_Index) -> (Int,Id)
solve_rec_edge d e rp =
case find_in_edge e rp of
(l,_,(_,Rec_Wr_Edge _)) ->
case find_in_edge_m e (l,0) of
Just (l',_,(_,Implicit_Edge _)) -> solve_rec_edge (d + 1) e (l',0)
_ -> (d,l)
(l,_,(_,_)) -> solve_rec_edge (d + 1) e (l,0)
-- | Transform 'Rec_Rd_Edge' to resolved 'Implicit_Edge'.
implicit_edge' :: [Edge] -> Edge -> Maybe Edge
implicit_edge' es e =
let (l,r,(p,ty)) = e
in case ty of
Rec_Rd_Edge _ ->
let (d,l') = solve_rec_edge 1 es (l,0)
in Just (l',r,(p,Implicit_Edge d))
Rec_Wr_Edge _ -> Nothing
Implicit_Edge _ -> Nothing
Normal_Edge -> Just e
-- | Is 'Node' 'UDF_R'.
is_rec_node :: Node -> Bool
is_rec_node (_,udf) =
case udf of
UDF_R _ _ -> True
_ -> False
-- | Transform the actual graph into a viewing graph by deleting
-- /recWr/ and /recRd/ nodes and drawing a direct backward edge.
vgraph_direct :: Graph -> Graph
vgraph_direct (n,e) =
let n' = filter (not . is_rec_node) n
e' = mapMaybe (implicit_edge' e) e
in (n',e')
-- | Label nodes and list incoming edges. Multiple-root and
-- multiple-channel nodes are erased.
--
-- > analyse (udf_elem c)
analyse :: [UDF] -> Analysis
analyse ns =
let l_ns = zip [1..] ns
w_es (k,n) = ((k,n),edges l_ns n)
rem_m ((_,n),_) =
case n of
UDF_MRG _ _ -> False
_ -> True
in filter rem_m (map w_es l_ns)
-- | Generate graph (node list and edge list).
--
-- > import Sound.DF.Uniform.GADT
-- > import qualified Sound.DF.Uniform.UDF as U
--
-- > let g = iir1 (0.0::Float) (+) 1
-- > let c = df_erase g
--
-- > map U.udf_concise (U.udf_elem c)
-- > > [recWr,df_add:Float,1.0,recRd:0.0,df_add:Float,1.0,recRd:0.0]
--
-- > U.vgraph_direct (U.graph c)
-- > > ([(1,wR_1),(2,df_add:Float),(3,1.0),(4,rR_1:0.0)]
-- > > ,[(2,1,0),(3,2,0),(4,2,1)])
--
-- > U.draw c
graph :: UDF -> Graph
graph n =
let a = analyse (nub (udf_elem n))
(ns,es) = unzip a
in (ns,concat es)
-- * FGL Graph
-- | FGL graph with 'UDF' label.
type Gr = G.Gr UDF (Port_Index,Edge_Ty)
-- | FGL graph with pretty-printed 'UDF' label.
type Gr' = G.Gr String (Port_Index,Edge_Ty)
-- | Generate 'Gr'.
udf_gr :: Graph -> Gr
udf_gr (n,e) = G.mkGraph n e
-- | Generate 'Gr''.
udf_gr' :: Graph -> Gr'
udf_gr' (n,e) = G.mkGraph (map (fmap udf_concise) n) e
-- | Topological sort of nodes (via 'udf_gr').
tsort :: UDF -> [UDF]
tsort u =
let g = udf_gr (graph u)
in map (fromMaybe (error "tsort") . G.lab g) (G.topsort g)
-- * Code Gen
-- | List of required variable declarations.
node_vars :: Node -> [Var]
node_vars (k,df) =
case df of
UDF_K i -> [k_var k Std_Var i]
UDF_A i -> [buffer_var k i]
UDF_R (R_Id j) (Left i) ->
[k_var j Rec_Var i
,k_var k Std_Var i]
UDF_R (R_Id _) (Right (n,_)) ->
[(Std_Var,udf_typeOf n,k,Nothing)]
UDF_P _ ty _ ->
if ty == nil_t
then []
else [(Std_Var,ty,k,Nothing)]
UDF_MRG _ _ -> error "node_vars_n: MRG"
-- | Possible c-call code statement.
node_c_call :: (Node,[Edge]) -> Maybe C_Call
node_c_call ((k,n),es) =
let fc p q = Just (Nothing,p,q)
in case (n,es) of
(UDF_K _,[]) -> Nothing
(UDF_A _,[]) -> Nothing
(UDF_R (R_Id j) (Left _),[]) ->
fc "df_rec_r" [(Std_Var,k),(Rec_Var,j)]
(UDF_R (R_Id j) (Right _),[(s,_,_)]) ->
fc "df_rec_w" [(Rec_Var,j),(Std_Var,s)]
(UDF_P a t _,_) ->
let o_l = if t /= nil_t
then [(Std_Var,k)]
else []
i_l = map (\(l,_,_) -> (Std_Var,l)) es
in fc a (o_l ++ i_l)
_ -> error ("node_c_call: " ++ show (n,es))
-- | Constant nodes.
k_nodes :: [Node] -> [(Id,K)]
k_nodes ns =
let ks = filter (isJust . udf_k' . node_udf) ns
in map (fmap udf_k) ks
-- | Generate 'Instructions' from 'UDF'.
udf_instructions :: UDF -> Instructions
udf_instructions n =
let a = analyse (tsort n)
ns = map fst a
ks = k_nodes ns
vs = concatMap node_vars ns
cc = mapMaybe node_c_call a
in (ks,vs,cc)
-- | 'dl_gen' of 'udf_instructions'.
udf_dl_gen :: FilePath -> (Host,FilePath) -> UDF -> IO ()
udf_dl_gen fn hd = dl_gen fn hd . udf_instructions
-- * Graph Drawing
-- | Make 'dot_rec' /arguments/ input.
dot_ar :: [UDF] -> [Either Int K]
dot_ar =
let f (i,u) = case u of
UDF_K k -> Right k
_ -> Left i
in map f . zip [0..]
-- | Dot notation of 'Node'.
dot_node :: Node -> String
dot_node (k,u) =
case u of
UDF_K _ -> error "dot_node: UDF_K"
UDF_A v -> dot_rec' k (vec_concise v) [] vec_float_t
UDF_P nm ty i -> dot_rec' k nm (dot_ar i) ty
UDF_R _ (Left c) -> dot_rec' k (udf_concise u) [Right c] (k_typeOf c)
UDF_R _ (Right (u',_)) -> dot_rec' k (udf_concise u) (dot_ar [u']) (udf_typeOf u')
UDF_MRG _ _ -> error "dot_node: MRG"
-- | Edges are coloured according to their type.
edge_ty_colour :: Edge_Ty -> String
edge_ty_colour ty =
case ty of
Normal_Edge -> "black"
Rec_Rd_Edge _ -> "orange"
Rec_Wr_Edge _ -> "purple"
Implicit_Edge _ -> "red"
-- | Dot notation of 'Edge'.
dot_edge :: Edge -> String
dot_edge (j,k,(p,ty)) =
let a = [("color",edge_ty_colour ty)]
in printf "%d:o_0 -> %d:i_%d %s" j k p (dot_attr a)
-- | Dot notation of 'Graph'.
dot_graph :: Graph -> [String]
dot_graph (n,e) =
concat [["digraph Anonymous {"
,"graph [splines=false];"]
,map dot_node n
,map dot_edge e
,["}"]]
-- | View dot graph.
dot_draw :: String -> IO ()
dot_draw s = do
t <- getTemporaryDirectory
v <- fmap (fromMaybe "dotty") (lookupEnv "DOTVIEWER")
let fn = t </> "udf" <.> "dot"
writeFile fn s
_ <- rawSystem v [fn]
return ()
-- | Draw graph, transformed by `vgraph_direct`.
draw :: UDF -> IO ()
draw = dot_draw . unlines . dot_graph . vgraph_direct . vgraph_impl . graph
-- | Draw graph, transformed by `vgraph_impl`.
draw' :: UDF -> IO ()
draw' = dot_draw . unlines . dot_graph . vgraph_impl . graph
-- * Gr Drawing
-- | Make @dot@ rendering of graph at 'Node', via 'vgraph_direct'.
gr_dot :: UDF -> String
gr_dot = G.showDot . G.fglToDot . udf_gr' . vgraph_direct . vgraph_impl . graph
-- | Make @dot@ rendering of graph at 'Node', via 'vgraph_impl'.
gr_dot' :: UDF -> String
gr_dot' = G.showDot . G.fglToDot . udf_gr' . vgraph_impl . graph
-- | Draw graph, via 'gr_dot'.
gr_draw :: UDF -> IO ()
gr_draw = dot_draw . gr_dot
-- | Draw graph, via `gr_dot'`.
gr_draw' :: UDF -> IO ()
gr_draw' = dot_draw . gr_dot'
-- * Audition
-- | Audition graph after sending initialisation messages.
audition :: [Message] -> UDF -> IO ()
audition is n = L.audition_rju is (udf_instructions n)
-- | Audition graph after sending initialisation messages.
audition_sc3 :: [Message] -> UDF -> IO ()
audition_sc3 is n = L.audition_sc3 is (udf_instructions n)
-- | Audition at @text-dl@.
audition_text :: Int -> UDF -> IO ()
audition_text nf n = L.audition_text nf (udf_instructions n)