11module Sound.Tidal.Stream.Target where
22
33import qualified Sound.Osc.Fd as O
4+ import qualified Sound.Osc.Time.Timeout as O
45import qualified Network.Socket as N
5- import Data.Maybe (fromJust , isJust )
6- import Control.Concurrent (forkOS , threadDelay )
6+ import Data.Maybe (fromJust , isJust , catMaybes )
7+ import Control.Concurrent (newMVar , readMVar , swapMVar , forkIO , forkOS , threadDelay )
8+ import Control.Monad (when )
79import Foreign (Word8 )
810
911import Sound.Tidal.Pattern
@@ -31,33 +33,65 @@ import Sound.Tidal.Stream.Config
3133
3234getCXs :: Config -> [(Target , [OSC ])] -> IO [Cx ]
3335getCXs config oscmap = mapM (\ (target, os) -> do
34- remote_addr <- resolve (oAddress target) (show $ oPort target)
35- remote_bus_addr <- if isJust $ oBusPort target
36- then Just <$> resolve (oAddress target) ( show $ fromJust $ oBusPort target )
37- else return Nothing
36+ remote_addr <- resolve (oAddress target) (oPort target)
37+ remote_bus_addr <- mapM (resolve (oAddress target)) ( oBusPort target)
38+ remote_busses <- sequence (oBusPort target >> Just (newMVar [] ) )
39+
3840 let broadcast = if cCtrlBroadcast config then 1 else 0
39- u <- O. udp_socket (\ sock sockaddr -> do N. setSocketOption sock N. Broadcast broadcast
40- N. connect sock sockaddr
41+ u <- O. udp_socket (\ sock _ -> do N. setSocketOption sock N. Broadcast broadcast
4142 ) (oAddress target) (oPort target)
42- return $ Cx {cxUDP = u, cxAddr = remote_addr, cxBusAddr = remote_bus_addr, cxTarget = target, cxOSCs = os}
43+ let cx = Cx {cxUDP = u, cxAddr = remote_addr, cxBusAddr = remote_bus_addr, cxBusses = remote_busses, cxTarget = target, cxOSCs = os}
44+ _ <- forkIO $ handshake cx config
45+ return cx
4346 ) oscmap
4447
45- resolve :: String -> String -> IO N. AddrInfo
48+ resolve :: String -> Int -> IO N. AddrInfo
4649resolve host port = do let hints = N. defaultHints { N. addrSocketType = N. Stream }
47- addr: _ <- N. getAddrInfo (Just hints) (Just host) (Just port)
50+ addr: _ <- N. getAddrInfo (Just hints) (Just host) (Just $ show port)
4851 return addr
4952
53+ handshake :: Cx -> Config -> IO ()
54+ handshake Cx { cxUDP = udp, cxBusses = Just bussesMV, cxAddr = addr } c = sendHandshake >> listen 0
55+ where
56+ sendHandshake :: IO ()
57+ sendHandshake = O. sendTo udp (O. Packet_Message $ O. Message " /dirt/handshake" [] ) (N. addrAddress addr)
58+ listen :: Int -> IO ()
59+ listen waits = do ms <- recvMessagesTimeout 2 udp
60+ if null ms
61+ then do checkHandshake waits -- there was a timeout, check handshake
62+ listen (waits+ 1 )
63+ else do mapM_ respond ms
64+ listen 0
65+ checkHandshake :: Int -> IO ()
66+ checkHandshake waits = do busses <- readMVar bussesMV
67+ when (null busses) $ do when (waits == 0 ) $ verbose c $ " Waiting for SuperDirt (v.1.7.2 or higher).."
68+ sendHandshake
69+ respond :: O. Message -> IO ()
70+ respond (O. Message " /dirt/hello" _) = sendHandshake
71+ respond (O. Message " /dirt/handshake/reply" xs) = do prev <- swapMVar bussesMV $ bufferIndices xs
72+ -- Only report the first time..
73+ when (null prev) $ verbose c $ " Connected to SuperDirt."
74+ respond _ = return ()
75+ bufferIndices :: [O. Datum ] -> [Int ]
76+ bufferIndices [] = []
77+ bufferIndices (x: xs') | x == O. AsciiString (O. ascii " &controlBusIndices" ) = catMaybes $ takeWhile isJust $ map O. datum_integral xs'
78+ | otherwise = bufferIndices xs'
79+ handshake _ _ = return ()
80+
81+ recvMessagesTimeout :: (O. Transport t ) => Double -> t -> IO [O. Message ]
82+ recvMessagesTimeout n sock = fmap (maybe [] O. packetMessages) $ O. recvPacketTimeout n sock
83+
5084-- send has three modes:
5185-- Send events early using timestamp in the OSC bundle - used by Superdirt
5286-- Send events early by adding timestamp to the OSC message - used by Dirt
5387-- Send events live by delaying the thread
54- send :: Maybe O. Udp -> Cx -> Double -> Double -> (Double , Bool , O. Message ) -> IO ()
55- send listen cx latency extraLatency (time, isBusMsg, m)
56- | oSchedule target == Pre BundleStamp = sendBndl isBusMsg listen cx $ O. Bundle timeWithLatency [m]
57- | oSchedule target == Pre MessageStamp = sendO isBusMsg listen cx $ addtime m
88+ send :: Cx -> Double -> Double -> (Double , Bool , O. Message ) -> IO ()
89+ send cx latency extraLatency (time, isBusMsg, m)
90+ | oSchedule target == Pre BundleStamp = sendBndl isBusMsg cx $ O. Bundle timeWithLatency [m]
91+ | oSchedule target == Pre MessageStamp = sendO isBusMsg cx $ addtime m
5892 | otherwise = do _ <- forkOS $ do now <- O. time
5993 threadDelay $ floor $ (timeWithLatency - now) * 1000000
60- sendO isBusMsg listen cx m
94+ sendO isBusMsg cx m
6195 return ()
6296 where addtime (O. Message mpath params) = O. Message mpath ((O. int32 sec): ((O. int32 usec): params))
6397 ut = O. ntpr_to_posix timeWithLatency
@@ -68,18 +102,15 @@ send listen cx latency extraLatency (time, isBusMsg, m)
68102 target = cxTarget cx
69103 timeWithLatency = time - latency + extraLatency
70104
71- sendBndl :: Bool -> ( Maybe O. Udp ) -> Cx -> O. Bundle -> IO ()
72- sendBndl isBusMsg ( Just listen) cx bndl = O. sendTo listen (O. Packet_Bundle bndl) (N. addrAddress addr)
105+ sendBndl :: Bool -> Cx -> O. Bundle -> IO ()
106+ sendBndl isBusMsg cx bndl = O. sendTo (cxUDP cx) (O. Packet_Bundle bndl) (N. addrAddress addr)
73107 where addr | isBusMsg && isJust (cxBusAddr cx) = fromJust $ cxBusAddr cx
74108 | otherwise = cxAddr cx
75- sendBndl _ Nothing cx bndl = O. sendBundle (cxUDP cx) bndl
76-
77- sendO :: Bool -> (Maybe O. Udp ) -> Cx -> O. Message -> IO ()
78- sendO isBusMsg (Just listen) cx msg = O. sendTo listen (O. Packet_Message msg) (N. addrAddress addr)
79- where addr | isBusMsg && isJust (cxBusAddr cx) = fromJust $ cxBusAddr cx
80- | otherwise = cxAddr cx
81- sendO _ Nothing cx msg = O. sendMessage (cxUDP cx) msg
82109
110+ sendO :: Bool -> Cx -> O. Message -> IO ()
111+ sendO isBusMsg cx msg = O. sendTo (cxUDP cx) (O. Packet_Message msg) (N. addrAddress addr)
112+ where addr | isBusMsg && isJust (cxBusAddr cx) = fromJust $ cxBusAddr cx
113+ | otherwise = cxAddr cx
83114
84115superdirtTarget :: Target
85116superdirtTarget = Target {oName = " SuperDirt" ,
0 commit comments