{- | .scsyndef file encoded in plain text. There are reader and writer functions. -} module Sound.Sc3.Server.Graphdef.Text where import Data.Char {- base -} import Data.Functor.Identity {- base -} import Data.List {- base -} import qualified Numeric {- base -} import qualified Control.Monad.State as S {- mtl -} import qualified Sound.Osc.Datum as Datum {- hosc -} import Sound.Sc3.Server.Graphdef {- hsc3 -} -- | * Printer -- | Print string. Strings must not have internal whitespace or semi-colons. A quoting system could allow these if required. print_string :: Datum.Ascii -> String print_string a = let s = Datum.ascii_to_string a in if any isSpace s || ';' `elem` s then error "print_string" else s -- | 'Encode_Functions' for plain text output. enc_text :: (String -> String) -> Encode_Functions String enc_text com_f = ( unwords . filter (not . null) , print_string , show , show , show , \n -> Numeric.showFFloat Nothing n "" , com_f ) {- | 'encode_graphdef_f' of 'enc_text' with optional semi-colon delimited comments. > dir = "/home/rohan/sw/rsc3-disassembler/scsyndef/" > pp nm = Sound.Sc3.Server.Graphdef.Io.read_graphdef_file (dir ++ nm) >>= putStrLn . print_graphdef True > pp "simple.scsyndef" > pp "with-ctl.scsyndef" > pp "mce.scsyndef" > pp "mrg.scsyndef" -} print_graphdef :: Bool -> Graphdef -> String print_graphdef with_com = let com_f = if with_com then \c -> concat ["\n; ", c, "\n"] else const "" in encode_graphdef_f (enc_text com_f) -- * List Input -- | Read the next value from a list. list_read_f :: (t -> u) -> S.State [t] u list_read_f f = do l <- S.get case uncons l of Nothing -> error "list_read_f" Just (h, t) -> S.put t >> return (f h) -- | Read function for floating point that admits inf and infinity. read_float :: (Fractional p, Read p) => String -> p read_float txt = case map toLower txt of "inf" -> 1 / 0 "infinity" -> 1 / 0 _ -> read txt -- | Get_Functions for text representation of Graphdef. text_get_f :: Get_Functions (S.StateT [String] Identity) text_get_f = (list_read_f Datum.ascii, list_read_f read, list_read_f read, list_read_f read, list_read_f read_float) -- | Is line empty or starts with ';' is_nil_or_comment :: String -> Bool is_nil_or_comment txt = case uncons txt of Nothing -> True Just (h, _) -> h == ';' {- | Read text representation of Graphdef, as written by 'print_graphdef'. > read_graphdef "1396926310 0 1 simple 2 0.0 440.0 0 0 2 SinOsc 2 2 1 0 -1 1 -1 0 2 Out 2 2 0 0 -1 0 0 0" -} read_graphdef :: String -> Graphdef read_graphdef txt = let delete_comments = filter (not . is_nil_or_comment) in S.evalState (get_graphdef text_get_f) (concatMap words (delete_comments (lines txt))) read_graphdef_file :: FilePath -> IO Graphdef read_graphdef_file = fmap read_graphdef . readFile