module Sound.Tidal.Stream where
import Sound.Tidal.Pattern
import Sound.Tidal.Core (stack, silence)
import qualified Sound.Tidal.Tempo as T
import qualified Sound.OSC.FD as O
import Sound.OSC.Datum as O
import Control.Concurrent.MVar
import Control.Concurrent
import qualified Data.Map.Strict as Map
import Data.Maybe (fromJust)
data TimeStamp = BundleStamp | MessageStamp | NoStamp
deriving Eq
data OSCTarget = OSCTarget {oAddress :: String,
oPort :: Int,
oPath :: String,
oShape :: Maybe [(String, Maybe Value)],
oLatency :: Double,
oPreamble :: [O.Datum],
oTimestamp :: TimeStamp
}
superdirtTarget :: OSCTarget
superdirtTarget = OSCTarget {oAddress = "127.0.0.1",
oPort = 57120,
oPath = "/play2",
oShape = Nothing,
oLatency = 0.2,
oPreamble = [],
oTimestamp = BundleStamp
}
stream :: MVar ControlMap -> OSCTarget -> IO (ControlPattern -> IO (ControlPattern), MVar T.Tempo)
stream cMapMV target = do u <- O.openUDP (oAddress target) (oPort target)
mp <- newMVar empty
(tempoMV, _) <- T.clocked $ onTick cMapMV mp target u
return $ (\p -> swapMVar mp p >> return p, tempoMV)
type PatId = String
data PlayState = PlayState {pattern :: ControlPattern,
mute :: Bool,
solo :: Bool
}
type PlayMap = Map.Map PatId PlayState
listenCMap cMapMV = do sock <- O.udpServer "127.0.0.1" (6011)
_ <- forkIO $ loop sock
return ()
where loop sock =
do ms <- O.recvMessages sock
mapM_ r ms
loop sock
r (O.Message path (O.ASCII_String k:v@(O.Float _):[])) = add cMapMV (ascii_to_string k) (VF $ fromJust $ datum_floating v)
r (O.Message path (O.ASCII_String k:O.ASCII_String v:[])) = add cMapMV (ascii_to_string k) (VS $ ascii_to_string v)
r (O.Message path (O.ASCII_String k:O.Int32 v:[])) = add cMapMV (ascii_to_string k) (VI $ fromIntegral v)
add :: MVar ControlMap -> String -> Value -> IO ()
add cMapMV k v = do cMap <- takeMVar cMapMV
putMVar cMapMV $ Map.insert k v cMap
return ()
stream5 :: OSCTarget -> IO (MVar T.Tempo,
MVar ControlMap,
PatId -> ControlPattern -> IO (),
IO (),
IO ()
)
stream5 target = do pMapMV <- newMVar (Map.empty :: Map.Map PatId PlayState)
cMapMV <- newMVar (Map.empty :: ControlMap)
listenCMap cMapMV
(set, tempoMV) <- stream cMapMV target
return (tempoMV,
cMapMV,
swap set pMapMV,
hush set pMapMV,
list pMapMV
)
where
swap :: (ControlPattern -> IO ControlPattern) -> MVar PlayMap -> PatId -> ControlPattern -> IO ()
swap set pMapMV k p
= do pMap <- takeMVar pMapMV
let pMap' = Map.insert k (PlayState p False False) pMap
update set pMap'
putMVar pMapMV pMap'
return ()
update :: (ControlPattern -> IO ControlPattern) -> PlayMap -> IO ()
update set pMap = do set $ stack $ map pattern $ filter (\pState -> if hasSolo pMap then solo pState else not (mute pState)) (Map.elems pMap)
return ()
hasSolo = (>= 1) . length . filter solo . Map.elems
hush set pMapMV = do set silence
swapMVar pMapMV Map.empty
return ()
list :: MVar PlayMap -> IO ()
list pMapMV = do pMap <- readMVar pMapMV
let hs = hasSolo pMap
putStrLn $ concatMap (showKV hs) $ Map.toList pMap
where showKV :: Bool -> (PatId, PlayState) -> String
showKV True (k, (PlayState _ _ True)) = k ++ " - solo\n"
showKV True (k, _) = "(" ++ k ++ ")\n"
showKV False (k, (PlayState _ False _)) = k ++ "\n"
showKV False (k, _) = "(" ++ k ++ ") - muted\n"
toDatum :: Value -> O.Datum
toDatum (VF x) = float x
toDatum (VI x) = int32 x
toDatum (VS x) = string x
toData :: Event ControlMap -> [O.Datum]
toData e = concatMap (\(n,v) -> [string n, toDatum v]) $ Map.toList $ eventValue e
onTick :: MVar ControlMap -> MVar ControlPattern -> OSCTarget -> O.UDP -> MVar T.Tempo -> T.State -> IO ()
onTick cMapMV pMV target u tempoMV st =
do p <- readMVar pMV
cMap <- readMVar cMapMV
tempo <- readMVar tempoMV
now <- O.time
let es = filter eventHasOnset $ query p (State {arc = T.nowArc st, controls = cMap})
at e = sched tempo $ fst $ eventWhole e
messages = map (\e -> (at e, toMessage e)) es
cpsChanges = map (\e -> (at e - now, Map.lookup "cps" $ eventValue e)) es
toMessage e = O.Message (oPath target) $ oPreamble target ++ toData e
mapM_ send messages
mapM_ (doCps now) cpsChanges
return ()
where send (time, m) = O.sendOSC u $ O.Bundle (time + (oLatency target)) [m]
sched :: T.Tempo -> Rational -> Double
sched tempo c = ((fromRational $ c - (T.atCycle tempo)) / T.cps tempo) + (T.atTime tempo)
doCps _ (_, Nothing) = return ()
doCps t (d, Just (VF cps)) = do forkIO $ do threadDelay $ floor $ d * 1000000
T.setCps tempoMV cps
return ()
return ()