packages feed

hsc3-forth-0.15: hs/hsc3-forth.hs

import Control.Concurrent {- base -}
import Control.Monad {- base -}
import Control.Monad.Except {- mtl -}
import Control.Monad.State {- mtl -}
import Data.Char {- hash -}
import Data.Hashable {- hash -}
import qualified Data.Map as M {- containers -}
import Data.Maybe {- base -}
import System.IO {- base -}

import Sound.OSC {- hosc -}
import Sound.SC3 {- hsc3 -}
import Sound.SC3.Common {- hsc3 -}
import Sound.SC3.UGen.Plain {- hsc3 -}
import Sound.SC3.UGen.PP {- hsc3 -}

import qualified Sound.SC3.UGen.DB as DB {- hsc3-db -}
import qualified Sound.SC3.UGen.DB.Record as DB {- hsc3-db -}
import qualified Sound.SC3.UGen.DB.PP as DB {- hsc3-db -}

import Sound.SC3.UGen.Dot {- hsc3-dot -}

import Forth

-- * Forth

-- | hsc3-forth word.
type U_Forth r = Forth Int UGen r

-- | 'replicateM' of 'pop'.
pop_n :: Int -> Forth w a [a]
pop_n n = replicateM n pop

-- | 'mapM_' of 'push'.
push_l :: [a] -> Forth w a ()
push_l = mapM_ push

pop_double :: String -> Forth w UGen Double
pop_double msg =
    let f u = case constant_opt u of
                Nothing -> throw_error ("POP_DOUBLE: " ++ msg ++ ": " ++ show u)
                Just n -> return n
    in pop >>= f

-- | Get UId counter.
get_uid :: Forth Int a Int
get_uid = with_vm (\vm -> (vm,world vm))

-- | Store UId counter.
set_uid :: Int -> Forth Int a ()
set_uid k = with_vm (\vm -> (vm {world = k},()))

-- | Get UId counter and store increment.
incr_uid :: Forth Int a Int
incr_uid = with_vm (\vm -> let k = world vm in (vm {world = k + 1},k))

-- | Assert that the stack is empty.
fw_assert_empty :: Forth_Type a => Forth w a ()
fw_assert_empty = do
  vm <- get
  case stack vm of
    [] -> return ()
    l -> throw_error ("STACK NOT EMPTY: " ++ unwords (map show l))

ugen_sep :: String -> Forth w a (String,Maybe Rate)
ugen_sep = maybe (throw_error "UGEN NAME RATE SEPARATOR FAILED") return . sc3_ugen_name_sep

-- * UForth

-- DEMAND makes this two stage, since inputs are known until we know if to pop NC.
get_nc :: DB.U -> Maybe Int -> U_Forth (Maybe Int)
get_nc u nc =
    case nc of
      Just n -> return (Just n)
      Nothing -> case DB.ugen_nc_mce u of
                   Just _ -> return Nothing
                   Nothing -> fmap Just (pop_int "GET_NC")

-- > fmap ugen_io (DB.uLookup CI "DSEQ")
-- > fmap ugen_io (DB.uLookup CI "DEMAND")
ugen_io :: DB.U -> (Int,Maybe Int)
ugen_io u = (length (DB.ugen_inputs u),DB.u_fixed_outputs u)

gen_plain :: String -> U_Forth ()
gen_plain w = do
  (nm,rt) <- ugen_sep w
  let (nm',sp) = case rt of
                   Nothing -> resolve_operator CI nm
                   _ -> (nm,Nothing)
      sp' = Special (fromMaybe 0 sp)
  u <- case DB.uLookup CI nm' of
         Nothing -> throw_error ("DYNAMIC FAILED: UNKNOWN UGEN: " ++ tick_quotes nm')
         Just r -> return r
  when (isNothing rt && isNothing (DB.ugen_filter u))
       (throw_error ("OSC: NO RATE?: " ++ tick_quotes nm'))
  let (inp,nc) = ugen_io u
  z <- if DB.ugen_nondet u then fmap UId incr_uid else return NoId
  nc' <- get_nc u nc
  i <- fmap reverse (pop_n inp)
  let nc'' = fromMaybe (length (mceChannels (last i))) nc'
  let rt' = fromMaybe (maximum (map rateOf i)) rt
      i' = (if DB.ugen_std_mce u then halt_mce_transform else id) i
  push (ugen_optimise_const_operator (mk_plain rt' (DB.ugen_name u) i' nc'' sp' z))

gen_nm :: UGen -> String
gen_nm = show . hash . show

sched :: Time -> UGen -> IO ()
sched t u =
    let nm = gen_nm u
        sy = synthdef nm (out 0 u)
        b0 = bundle immediately [d_recv sy]
        b1 = bundle t [s_new nm (-1) AddToHead 1 []]
    in withSC3 (sendBundle b0 >> sendBundle b1)

fw_help :: Forth_Type a => Forth w a ()
fw_help = do
  (nm,_) <- ugen_sep =<< pop_string "HELP: NAME"
  case DB.ugenSummary' CI nm of
    Nothing -> throw_error ("?: NO HELP: " ++ nm)
    Just h -> liftIO (putStrLn h)

fw_manual :: Forth_Type a => Forth w a ()
fw_manual = do
  (nm,_) <- ugen_sep =<< pop_string "MANUAL: NAME"
  case DB.uLookup CI nm of
    Nothing -> throw_error ("MANUAL: NO ENTRY: " ++ nm)
    Just u -> liftIO (viewSC3Help (DB.ugen_name u))

fw_play_at :: U_Forth ()
fw_play_at = do
  grp <- pop_int "PLAY-AT: GRP"
  act <- pop_int "PLAY-AT: ACT"
  nid <- pop_int "PLAY-AT: NID"
  u <- pop
  fw_assert_empty
  liftIO (audition_at (nid,toEnum act,grp) u)

fw_pretty_print :: U_Forth ()
fw_pretty_print = do
  k <- pop_int "PRETTY-PRINT"
  u <- pop
  liftIO (putStrLn (DB.ugen_graph_forth_pp (toEnum k) u))

fw_load_datum :: Char -> U_Forth Datum
fw_load_datum c =
    case c of
      'i' -> pop_int "LOAD-DATUM: I" >>= return . int32
      'f' -> pop_double "LOAD-DATUM: F" >>= return . float
      's' -> pop_string "LOAD-DATUM: S" >>= return . string
      _ -> throw_error ("LOAD-DATUM: UNKNOWN TYPE: " ++ tick_quotes [c])

fw_async :: U_Forth ()
fw_async = do
  nm <- pop_string "ASYNC: MESSAGE-NAME"
  ty <- pop_string "ASYNC: OSC-TYPE-SIG"
  param <- mapM fw_load_datum (reverse (tail ty))
  _ <- liftIO (withSC3 (async (message nm (reverse param))))
  return ()

ugen_dict :: Dict Int UGen
ugen_dict =
    M.fromList $ map (\(nm,en) -> (map toLower nm,en))
    [("clone",pop_int "CLONE" >>= \n -> pop >>= \u -> incr_uid >>= \z -> push (uclone z n u))
    ,("draw",pop >>= \u -> fw_assert_empty >> liftIO (draw (out 0 u)))
    ,("mce",pop_int "MCE" >>= \n -> pop_n n >>= push . mce . reverse)
    ,("mix",pop >>= push . mix) -- here rather hsc3.fs to get sum_opt for graph comparisons...
    ,("mrg",pop_int "MRG" >>= \n -> pop_n n >>= push . mrg . reverse)
    ,("play-at",fw_play_at)
    ,("sched",pop_double "SCHED" >>= \t -> pop >>= \u -> fw_assert_empty >> liftIO (sched t u))
    ,("stop",liftIO (withSC3 reset))
    ,("unmce",pop >>= push_l . mceChannels)
    ,("async",fw_async)
    ,("pause",pop_double "PAUSE" >>= pauseThread)
    ,("time",liftIO time >>= push . constant)
    ,("label",pop_string "LABEL" >>= push . label)
    ,("get-uid",get_uid >>= push . constant)
    ,("set-uid",pop_int "SET-UID" >>= set_uid)
    ,("unrand",pop >>= push . ugen_optimise_ir_rand)
    ,("chan",pop >>= push . constant . length . mceChannels)
    ,("sc3-status",liftIO (withSC3 serverStatus >>= mapM_ putStrLn))
    ,("pretty-print",fw_pretty_print)
    ,("?",fw_help)
    ,("manual",fw_manual)]

instance Forth_Type UGen where
    ty_show = ugen_concise_pp
    ty_to_int = fmap floor . u_constant
    ty_from_int = fromIntegral
    ty_from_bool t = if t then -1 else 0

main :: IO ()
main = do
  sig <- newMVar False
  let d :: Dict Int UGen
      d = M.unions [core_dict,ugen_dict]
      vm = (empty_vm 0 parse_constant sig) {dynamic = Just gen_plain
                                           ,dict = d
                                           ,input_port = Just stdin}
      init_f = load_files ["stdlib.fs","hsc3.fs","overlap-texture.fs"]
  putStrLn "HSC3-FORTH"
  repl vm init_f