hsc3-0.21: Sound/Sc3/Server/Graphdef/Text.hs
{- | .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