packages feed

vivid-0.5.0.0: Vivid/Actions/Scheduled.hs

-- | This is for timing of actions that's more precise than IO
-- 
--   It tells the server when to perform the actions, so musical timing won't
--   be affected by e.g. network latency or the time it took to compute a value
-- 
--   If you're running vivid on a different computer than the SC synth, make
--   sure the clocks agree

-- {-# LANGUAGE BangPatterns #-}
{-# LANGUAGE
     FlexibleInstances
   , InstanceSigs
   , KindSignatures
   , TypeSynonymInstances
   #-}
-- {-# LANGUAGE ViewPatterns #-}

{-# LANGUAGE NoIncoherentInstances #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE NoUndecidableInstances #-}

module Vivid.Actions.Scheduled (
     Scheduled

   , doScheduledInWith

   , doScheduledAtWith

   , doScheduledNowWith
   ) where

import Vivid.Actions.Class
import Vivid.Actions.IO () -- Just until we remove MonadIO
import Vivid.OSC
import Vivid.SCServer
import Vivid.SynthDef (SynthDef)

import Control.Concurrent
import Control.Monad
import Control.Monad.IO.Class (liftIO)
import Control.Monad.Trans.Reader (ReaderT, runReaderT, mapReaderT)
import Control.Monad.State (evalStateT, put, get, modify, StateT)
import Data.ByteString (ByteString)
import Prelude

type Scheduled = ReaderT SCServerState (StateT Timestamp IO)


instance VividAction Scheduled where
   callOSC :: OSC -> Scheduled ()
   callOSC message = do
      now <- getTime
      mapReaderT liftIO . callBS . encodeOSCBundle $ OSCBundle now [Right message]

   callBS :: ByteString -> Scheduled ()
   callBS message = do
      now <- getTime
      mapReaderT liftIO . callBS . encodeOSCBundle $ OSCBundle now [Left message]

   sync :: Scheduled ()
   sync = return () -- always right?

   waitForSync :: SyncId -> Scheduled ()
   waitForSync _ = return () -- always right?

   wait :: Real n => n -> Scheduled ()
   wait t = modify (`addSecs` realToFrac t)

   getTime :: Scheduled Timestamp
   getTime = get

   newBufferId :: Scheduled BufferId
   newBufferId = mapReaderT liftIO (newBufferId :: ReaderT SCServerState IO BufferId)

   newNodeId :: Scheduled NodeId
   newNodeId = mapReaderT liftIO (newNodeId :: ReaderT SCServerState IO NodeId)

   newSyncId :: Scheduled SyncId
   newSyncId = mapReaderT liftIO (newSyncId :: ReaderT SCServerState IO SyncId)

   fork :: Scheduled () -> Scheduled ()
   fork action = do
      timeOfFork <- get
      action
      put timeOfFork

   defineSD :: SynthDef a -> Scheduled ()
   defineSD = mapReaderT (liftIO . void . forkIO) . defineSD

-- | Schedule an action to happen at the given time
doScheduledAtWith :: SCServerState -> Timestamp -> Scheduled a -> IO a
doScheduledAtWith serverState startTime action =
   let stateAction = runReaderT action serverState
   in evalStateT stateAction startTime

-- | Schedule an action to happen n seconds from now
doScheduledInWith :: SCServerState -> Double -> Scheduled x -> IO x
doScheduledInWith serverState numSecs action = do
   now <- runReaderT (getTime :: ReaderT SCServerState IO Timestamp) serverState -- Not that the server state matters here
   doScheduledAtWith serverState (addSecs now numSecs) action

-- | Schedule an action to happen right now. Because of server latency this
--   could arrive late, so you might want to do something like
--   @doScheduledIn 0.01@ instead:
doScheduledNowWith :: SCServerState -> Scheduled x -> IO x
doScheduledNowWith serverState action = do
   now <- runReaderT (getTime :: ReaderT SCServerState IO Timestamp) serverState -- Not that the server state matters here
   doScheduledAtWith serverState now action