zwirn-0.2.2.0: src/zwirn-lang/Zwirn/Stream/Process.hs
{-# LANGUAGE BangPatterns #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use mapMaybe" #-}
module Zwirn.Stream.Process where
import Control.Concurrent.MVar (MVar, modifyMVar_, readMVar)
import Data.Bifunctor (first)
import Data.List (mapAccumL)
import qualified Data.Map as Map
import Data.Maybe (catMaybes)
import qualified Data.Text as T
import Data.Tuple (swap)
import qualified Sound.Osc as O
import qualified Sound.Osc.Transport.Fd.Udp as O
import Sound.Tidal.Clock
import qualified Sound.Tidal.Clock as Clock
import Sound.Tidal.Link
import Zwirn.Core.Lib.Core (apply)
import Zwirn.Core.Query
import qualified Zwirn.Core.Time as Z
import Zwirn.Language.Evaluate.Expression
import Zwirn.Stream.Target
import Zwirn.Stream.Types
tickAction ::
MVar PlayMap -> -- maps from channels to expressions
MVar ActionMap -> -- maps from channels to expressions
MVar BusMap -> -- maps from busses to expressions
MVar ExpressionMap -> -- state map
MVar [Int] -> -- bus mapping
TargetMap -> -- targets
O.Udp -> -- local address
Time -> -- precision
(Time, Time) -> -- arc of the current tick
Double -> -- nudge
ClockConfig -> -- configuration of the clock
ClockRef -> -- reference to the clock
(SessionState, SessionState) ->
IO ()
tickAction zMV actionMapMV busMapMV stMV bussesMV targetMap local prec (star, end) nudge cconf cref (ss, _) = do
cps <- Clock.getCPS cconf cref
vs <- processPlayMap prec (star, end) cps zMV stMV
bs <- processBusMap prec (star, end) busMapMV stMV bussesMV
processActionMap prec (star, end) cps actionMapMV stMV
mapM_ (stampAndSend targetMap False local nudge cconf cref ss) vs
mapM_ (stampAndSend targetMap True local nudge cconf cref ss . (\(Targeted ts (t, m)) -> Targeted ts (t, Just m))) bs
processPlayMap :: Time -> (Time, Time) -> Time -> MVar PlayMap -> MVar ExpressionMap -> IO [Targeted (Z.Time, Maybe (T.Text -> O.Message))]
processPlayMap prec (star, end) cps zMV stMV = do
pm <- readMVar zMV
let ps = resolvePlayMap pm
st <- readMVar stMV
let (enst, vs) = mapAccumL (\ !s (Targeted ts p) -> swap $ first (Targeted ts) $ findAllValuesWithTimeStatePrec (Z.Time prec 0) (Z.Time (align prec star) 1, Z.Time (align prec end) 1) s p) st ps
modifyMVar_ stMV (const $ return enst)
let func (t, ex) = expressionToMessage (fromIntegral (floor t :: Int)) (realToFrac cps) ex >>= \m -> return (t, m)
concat <$> mapM (\targ -> (\(Targeted ts xs) -> mapM (fmap (Targeted ts) . func) xs) targ) vs
processActionMap :: Time -> (Time, Time) -> Time -> MVar ActionMap -> MVar ExpressionMap -> IO ()
processActionMap prec (star, end) cps zMV stMV = do
pm <- readMVar zMV
let ps = Map.elems pm
st <- readMVar stMV
let (enst, vs) = mapAccumL (\ !s p -> swap $ findAllValuesWithTimeStatePrec (Z.Time prec 0) (Z.Time (align prec star) 1, Z.Time (align prec end) 1) s p) st ps
modifyMVar_ stMV (const $ return enst)
mapM_ (\(t, ex) -> expressionToMessage (fromIntegral (floor t :: Int)) (realToFrac cps) ex >>= \m -> return (t, m)) (concat vs)
processBusMap :: Time -> (Time, Time) -> MVar BusMap -> MVar ExpressionMap -> MVar [Int] -> IO [Targeted (Z.Time, T.Text -> O.Message)]
processBusMap prec (star, end) busMV stMV bussesMV = do
bm <- readMVar busMV
let bs = Map.toList bm
busses <- readMVar bussesMV
st <- readMVar stMV
concat <$> mapM (\(i, Targeted ts x) -> map (Targeted ts) <$> busToMessage prec (star, end) busses st (i, x)) bs
busToMessage :: Time -> (Time, Time) -> [Int] -> ExpressionMap -> (Int, Zwirn Expression) -> IO [(Z.Time, T.Text -> O.Message)]
busToMessage prec (star, end) busses st (i, p) = do
let vs = findAllValuesWithTimePrec (Z.Time prec 0) (Z.Time (align prec star) 1, Z.Time (align prec end) 1) st p
mapM (\(t, ex) -> busExpressionToMessage (toBus busses i) ex >>= \m -> return (t, m)) vs
toBus :: [Int] -> Int -> Int
toBus [] i = i
toBus xs i = xs !! (i `mod` length xs)
applyFx :: Targeted (PlayState, Zwirn Expression, Maybe (Zwirn (Zwirn Expression -> Zwirn Expression))) -> Targeted (Zwirn Expression)
applyFx (Targeted ts (_, x, Nothing)) = Targeted ts x
applyFx (Targeted ts (_, x, Just fx)) = Targeted ts (apply fx x)
resolvePlayMap :: PlayMap -> [Targeted (Zwirn Expression)]
resolvePlayMap pm = if null ss then map applyFx rs else map applyFx ss
where
ps = Map.elems pm
ss = filter (\(Targeted _ (x, _, _)) -> x == Solo) ps
rs = filter (\(Targeted _ (x, _, _)) -> x == Normal) ps
align :: Time -> Time -> Time
align prec t = fromIntegral (floor $ t / prec :: Int) * prec
----------------------------------------------------------
-------------- expressions --> osc messages --------------
----------------------------------------------------------
expressionToMessage :: Double -> Double -> Expression -> IO (Maybe (T.Text -> O.Message))
expressionToMessage cyc cps ex = do
os <- expressionToOSC ex
let additionalData = [O.string "cps", O.float cps, O.string "cycle", O.float cyc]
if null os
then return Nothing
else return $ Just $ \pat -> O.message (T.unpack pat) (additionalData ++ os)
busExpressionToMessage :: Int -> Expression -> IO (T.Text -> O.Message)
busExpressionToMessage bus ex = do
os <- expressionToOSC ex
return $ \path -> O.message (T.unpack path) (O.int32 bus : os)
expressionToOSC :: Expression -> IO [O.Datum]
expressionToOSC (ENum n) = return [O.float n]
expressionToOSC (EText n) = return [O.string $ T.unpack n]
expressionToOSC (EMap m) = concat <$> mapM (\(k, v) -> expressionToOSC v >>= \xs -> return $ O.string (T.unpack k) : xs) (Map.toList m)
expressionToOSC (EAction a) = a >> return []
expressionToOSC _ = return []
----------------------------------------------
-------------- sending messages --------------
----------------------------------------------
defaultLatency :: Double
defaultLatency = 0.2
stampAndSend :: TargetMap -> Bool -> O.Udp -> Double -> ClockConfig -> ClockRef -> SessionState -> Targeted (Z.Time, Maybe (T.Text -> O.Message)) -> IO ()
stampAndSend _ _ _ _ _ _ _ (Targeted _ (_, Nothing)) = return ()
stampAndSend targetMap bus local nudge cconf cref ss (Targeted ts (t, Just msg)) = do
let onBeat = Clock.cyclesToBeat cconf ((\(Z.Time r _) -> fromRational r :: Double) t)
let targs = catMaybes $ map (`Map.lookup` targetMap) ts
let getBusTargets (Target _ path _ (Just addr)) = [(path, addr)]
getBusTargets (Target _ _ _ Nothing) = []
let addrs = if bus then concatMap getBusTargets targs else map (\x -> (tOSCPath x, tAddress x)) targs
on <- Clock.timeAtBeat cconf ss onBeat
onOSC <- Clock.linkToOscTime cref on
mapM_ (\(path, addr) -> sendMessage addr local defaultLatency nudge (onOSC, msg path)) addrs
sendMessage :: RemoteAddress -> O.Udp -> Double -> Double -> (Double, O.Message) -> IO ()
sendMessage remote local latency extraLatency (time, m) = sendBndl $ O.Bundle timeWithLatency [m]
where
timeWithLatency = time - latency + extraLatency
sendBndl bndl = O.sendTo local (O.Packet_Bundle bndl) remote
-------------------------------------------------
------------------- utilities -------------------
-------------------------------------------------
updateState :: MVar ExpressionMap -> [ExpressionMap] -> IO ()
updateState _ [] = return ()
updateState stmv (st : _) = modifyMVar_ stmv (const $ return st)