{-# OPTIONS_HADDOCK show-extensions #-} {-# LANGUAGE NoRebindableSyntax #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE LambdaCase #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ScopedTypeVariables #-} -- | Library for interacting with the SuperCollider server. -- -- You don't need to use much of this day-to-day -- -- There's a toplevel 'scServerState' that stores the current state of the SC server 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 qualified Data.ByteString as B hiding (find, elem) import Data.ByteString (ByteString) import Data.Int (Int32) import Control.Concurrent.STM as STM {- import qualified Data.Map as Map import Data.Map (Map) -} import qualified Data.Set as Set import Data.Set (Set) import qualified Data.ByteString.Char8 as BS8 -- We use this only for "the unsafePerformIO hack" -- (https://wiki.haskell.org/Top_level_mutable_state) so that functions can -- refer to the state without being passed the state explicitly. This should -- still be safe: import System.IO.Unsafe (unsafePerformIO) -- SETTINGS: defaultSCServerPort :: String defaultSCServerPort = "57110" -- defaultSCLangPort = "57120" {-# NOINLINE scServerState #-} scServerState :: SCServerState -- see the above note about this use of unsafePerformIO: 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))) -- Int is the hash } -- | Stop the SuperCollider server quit :: IO () quit = call $ OSC "/quit" [] -- | __You usually don't need to call this function__ -- -- Use this if to connect on a non-default port or to a server not at localhost -- -- Otherwise the connection is created when it's needed. -- You can also use this to explicitly create the connection, so the -- computation is done upfront -- -- The 'HostName' is the ip address or "localhost". The 'ServiceName' is the port 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..] -- sclang starts at 2000 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 } -- | Send an 'OSC' message to the SuperCollider server call :: OSC -> IO () call message = do let !_ = scServerState callBS (encodeOSC message) -- | Async messages to the sc server get responded to with \"\/done\" -- so this calls those functions and waits for the \"\/done\" before continuing 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 "/done" [OSC_S cmdFinished] | cmd == cmdFinished -> return () OSC "/synced" [OSC_I syncFinished] | syncFinished == syncId -> return () _ -> getDoneMessage s sid newtype SyncId = SyncId Int32 deriving (Show, Read, Eq, Ord) -- | Send a ByteString to the SuperCollider server. -- You usually want to use 'call' instead. May be removed in future versions. callBS :: ByteString -> IO () callBS message = do let !_ = scServerState sock <- getSCServerSocket _ <- send sock message return () {- call' :: SCServerState -> OSC -> IO () call' scServerState' message = do let !_ = scServerState' sock <- getSCServerSocket' scServerState' _ <- send sock (encodeOSC message) return () -} -- | Your \"emergency\" button. Run this and everything playing on the SC server -- will be freed -- silence! -- -- Corresponds to the cmd-. \/ ctrl-. key command in the SuperCollider IDE 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 -- | If you've started the SC server with a non-default number of buffer ids, -- (e.g. with the \"-b\" argument), you can reflect that here -- -- Note that the buffer ids start at 512, to not clash with any that -- sclang has allocated setMaxBufferIds :: Int32 -> IO () setMaxBufferIds newMax = atomically $ writeTVar (scServer_maxBufIds scServerState) newMax -- | Make an empty buffer -- -- The Int32 is the buffer length /in samples/. Multiply seconds by -- the default sample rate of the server (usually 48000) to get the number -- of samples 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 -- | Make a buffer and fill it with sound data from a file 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 -- | Write a buffer to a file 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" ]