module Vivid.SCServer (
call
, callBS
, quit
, cmdPeriod
, NodeId(..)
, newNodeId
, BufferId(..)
, newBufferId
, setMaxBufferIds
, makeBuffer
, makeBufferFromFile
, saveBuffer
, createSCServerConnection
, callAndWaitForDone
, SCServerState(..)
, scServerState
) where
import Vivid.OSC
import Vivid.SynthDef.Types
import Network.Socket (SocketType(Datagram), defaultProtocol, socket, AddrInfo(..), getAddrInfo, Socket, HostName, ServiceName, connect)
import Network.Socket.ByteString
import Control.Concurrent (threadDelay)
import Data.ByteString (ByteString)
import Data.Int (Int32)
import Control.Concurrent.STM as STM
import qualified Data.Set as Set
import Data.Set (Set)
import qualified Data.ByteString.Char8 as BS8
import System.IO.Unsafe (unsafePerformIO)
defaultSCServerPort :: String
defaultSCServerPort = "57110"
scServerState :: SCServerState
scServerState = unsafePerformIO makeEmptySCServerState
newtype NodeId
= NodeId { unNodeId :: Int32 }
deriving (Show, Eq)
newtype BufferId
= BufferId { unBufferId :: Int32 }
deriving (Show, Eq)
data SCServerState
= SCServerState
{ scServer_socket :: !(TVar (Maybe Socket))
, scServer_availableBufferIds :: !(TVar [BufferId])
, scServer_maxBufIds :: !(TVar Int32)
, scServer_availableNodeIds :: !(TVar [NodeId])
, scServer_availableSyncIds :: !(TVar [SyncId])
, scServer_definedSDs :: !(TVar (Set (SDName, Int)))
}
quit :: IO ()
quit = call $ OSC "/quit" []
createSCServerConnection :: HostName -> ServiceName -> IO Socket
createSCServerConnection hostName port = do
let !_ = scServerState
readTVarIO (scServer_socket scServerState) >>= \case
Nothing -> do
s <- connectToSCServer hostName port
(atomically . (writeTVar $ scServer_socket scServerState) . Just) s
return s
Just _ -> error "Too late -- connection already established. Disconnect first."
connectToSCServer :: HostName -> ServiceName -> IO Socket
connectToSCServer hostName port = do
(serverAddr:_) <- getAddrInfo Nothing (Just hostName) (Just port)
s <- socket (addrFamily serverAddr) Datagram defaultProtocol
connect s (addrAddress serverAddr)
_ <- send s $ encodeOSC $ OSC "/dumpOSC" [OSC_I 1]
_ <- send s $ encodeOSC $ OSC "/g_new" [OSC_I 1, OSC_I 0, OSC_I 0]
threadDelay $ fromEnum 1e3
return s
getSCServerSocket :: IO Socket
getSCServerSocket = getSCServerSocket' scServerState
getSCServerSocket' :: SCServerState -> IO Socket
getSCServerSocket' scServerState' = do
let !_ = scServerState'
readTVarIO (scServer_socket scServerState') >>= \case
Nothing -> do
s <- connectToSCServer "localhost" defaultSCServerPort
(atomically . (writeTVar $ scServer_socket scServerState') . Just) s
return s
Just s -> return s
makeEmptySCServerState :: IO SCServerState
makeEmptySCServerState = do
sockTVar <- newTVarIO Nothing
availBufIds <- newTVarIO $ drop 512 $ map BufferId $ cycle [0..]
availNodeIds <- newTVarIO $ map NodeId [10000..]
maxBufIds <- newTVarIO 1024
syncIds <- newTVarIO $ drop 10000 $ map SyncId $ cycle [0..]
definedSDs <- newTVarIO $ Set.empty
return $ SCServerState
{ scServer_socket = sockTVar
, scServer_availableBufferIds = availBufIds
, scServer_maxBufIds = maxBufIds
, scServer_availableNodeIds = availNodeIds
, scServer_availableSyncIds = syncIds
, scServer_definedSDs = definedSDs
}
call :: OSC -> IO ()
call message = do
let !_ = scServerState
callBS (encodeOSC message)
callAndWaitForDone :: OSC -> IO ()
callAndWaitForDone message@(OSC _cmd _) = do
s <- getSCServerSocket
call message
threadDelay $ fromEnum 1e4
sid@(SyncId syncId) <- newSyncId
call $ OSC "/sync" [OSC_I syncId]
getDoneMessage s sid
where
getDoneMessage :: Socket -> SyncId -> IO ()
getDoneMessage s sid@(SyncId syncId) = recvFrom s 1024 >>= \(msg, _) ->
case decodeOSC msg of
OSC "/synced" [OSC_I syncFinished] | syncFinished == syncId -> return ()
_ -> getDoneMessage s sid
newtype SyncId
= SyncId Int32
deriving (Show, Read, Eq, Ord)
callBS :: ByteString -> IO ()
callBS message = do
let !_ = scServerState
sock <- getSCServerSocket
_ <- send sock message
return ()
cmdPeriod :: IO ()
cmdPeriod = do
call $ OSC "/g_freeAll" [OSC_I 0]
call $ OSC "/clearSched" []
call $ OSC "/g_new" [OSC_I 1, OSC_I 0, OSC_I 0]
newBufferId :: IO BufferId
newBufferId = do
maxBufIds <- readTVarIO (scServer_maxBufIds scServerState)
BufferId nn <- getNextAvailable scServer_availableBufferIds
return . BufferId $ nn `mod` maxBufIds
getNextAvailable :: (SCServerState -> TVar [a]) -> IO a
getNextAvailable getter = do
let !_ = scServerState
atomically $ do
let avail = getter scServerState
(n:rest) <- readTVar avail
writeTVar avail rest
return n
newNodeId :: IO NodeId
newNodeId =
getNextAvailable scServer_availableNodeIds
newSyncId :: IO SyncId
newSyncId =
getNextAvailable scServer_availableSyncIds
setMaxBufferIds :: Int32 -> IO ()
setMaxBufferIds newMax = atomically $
writeTVar (scServer_maxBufIds scServerState) newMax
makeBuffer :: Int32 -> IO BufferId
makeBuffer bufferLength = do
bufId@(BufferId bufIdInt) <- newBufferId
call $ OSC "/b_alloc" [
OSC_I bufIdInt
,OSC_I bufferLength
,OSC_I 1
,OSC_I 0
]
return bufId
makeBufferFromFile :: FilePath -> IO BufferId
makeBufferFromFile fPath = do
bufId@(BufferId bufIdInt) <- newBufferId
call $ OSC "/b_allocRead" [
OSC_I bufIdInt
, OSC_S (BS8.pack fPath)
, OSC_I 0
, OSC_I (1)
]
return bufId
saveBuffer :: BufferId -> FilePath -> IO ()
saveBuffer (BufferId theBufId) fPath =
call $ OSC "/b_write" [
OSC_I theBufId
,OSC_S (BS8.pack fPath)
,OSC_S "wav"
,OSC_S "float"
]