Skip to content

Commit

Permalink
Rename Server -> Env; consolidate related modules
Browse files Browse the repository at this point in the history
  • Loading branch information
JBetz committed Apr 28, 2024
1 parent a5c8be0 commit 76ee09f
Show file tree
Hide file tree
Showing 12 changed files with 87 additions and 98 deletions.
22 changes: 11 additions & 11 deletions app/Main.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Data.IORef
import Data.Maybe
import Ensemble.Config
import Ensemble.Handler
import Ensemble.Server
import Ensemble.Env
import Network.HTTP.Types (status400)
import Network.Wai (responseLBS)
import qualified Network.Wai.Handler.WebSockets as WaiWs
Expand All @@ -25,16 +25,16 @@ main :: IO ()
main = do
hSetBuffering stdout NoBuffering
config <- getRecord "Ensemble Audio Engine"
server <- createServer config
runWebSocketInterface server (fromMaybe 3000 $ port config)
env <- createEnv config
runWebSocketInterface env (fromMaybe 3000 $ port config)

where
runWebSocketInterface server port' = do
runWebSocketInterface env port' = do
let warpSettings = Warp.setPort port' Warp.defaultSettings
let websocketApp pendingConnection = do
connection <- WS.acceptRequest pendingConnection
sendThread <- forkIO $ forever $ do
outgoingMessage <- readChan $ server_messageChannel server
outgoingMessage <- readChan $ env_messageChannel env
WS.sendTextData connection (A.encode outgoingMessage)
isOpen <- newIORef True
whileM $ do
Expand All @@ -53,20 +53,20 @@ main = do
WS.UnicodeException message -> do
putStrLn $ "UNICODE EXCEPTION: " <> message
pure Nothing
whenJust incomingMessage $ handleIncomingMessage server
whenJust incomingMessage $ handleIncomingMessage env
readIORef isOpen
let backupApp _ respond = respond $ responseLBS status400 [] "Not a WebSocket request"
Warp.runSettings warpSettings $ WaiWs.websocketsOr WS.defaultConnectionOptions websocketApp backupApp

handleIncomingMessage server message =
handleIncomingMessage env message =
case A.eitherDecodeStrict message of
Right incomingMessage -> do
outgoingMessage <- receiveMessage server incomingMessage
writeChan (server_messageChannel server) outgoingMessage
outgoingMessage <- receiveMessage env incomingMessage
writeChan (env_messageChannel env) outgoingMessage
Left parseError ->
hPutStrLn stderr $ "Parse error: " <> parseError

handleOutgoingMessages server =
handleOutgoingMessages env =
void $ forkIO $ forever $ do
outgoingMessage <- readChan $ server_messageChannel server
outgoingMessage <- readChan $ env_messageChannel env
putStrLn $ LC8.unpack (A.encode outgoingMessage)
4 changes: 1 addition & 3 deletions ensemble.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ library
exposed-modules: Ensemble,
Ensemble.API,
Ensemble.Config,
Ensemble.Effects,
Ensemble.Engine,
Ensemble.Env,
Ensemble.Error,
Ensemble.Event,
Ensemble.Handler,
Expand All @@ -56,9 +56,7 @@ library
Ensemble.Schema.TaggedJSON,
Ensemble.Schema.TH,
Ensemble.Sequencer,
Ensemble.Server,
Ensemble.Tick,
Ensemble.Type,
Ensemble.Util,
Ensemble.Window
if os(windows)
Expand Down
4 changes: 2 additions & 2 deletions src/Ensemble.hs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
module Ensemble
( module Ensemble.API
, module Ensemble.Server
, module Ensemble.Env
) where

import Ensemble.API
import Ensemble.Server
import Ensemble.Env
39 changes: 19 additions & 20 deletions src/Ensemble/API.hs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,8 @@ import Ensemble.Node
import Ensemble.Event (SequencerEvent(..))
import Ensemble.Schema.TH
import qualified Ensemble.Sequencer as Sequencer
import Ensemble.Server
import Ensemble.Env
import Ensemble.Tick
import Ensemble.Type
import Ensemble.Window
import Foreign.Ptr

Expand All @@ -39,24 +38,24 @@ getMidiDevices = liftIO Engine.getMidiDevices

startEngine :: Ensemble Ok
startEngine = do
engine <- asks server_engine
engine <- asks env_engine
liftIO $ Engine.start engine
pure Ok

stopEngine :: Ensemble Ok
stopEngine = do
engine <- asks server_engine
engine <- asks env_engine
liftIO $ Engine.stop engine
pure Ok

createMidiDeviceNode :: Argument "deviceId" Int -> Ensemble NodeId
createMidiDeviceNode (Argument deviceId) = do
engine <- asks server_engine
engine <- asks env_engine
liftIO $ Engine.createMidiDeviceNode engine deviceId

deleteNode :: Argument "nodeId" NodeId -> Ensemble Ok
deleteNode (Argument nodeId) = do
engine <- asks server_engine
engine <- asks env_engine
liftIO $ Engine.deleteNode engine nodeId
pure Ok

Expand All @@ -71,7 +70,7 @@ scanForPlugins (Argument filePaths) =

createPluginNode :: Argument "filePath" Text -> Argument "pluginIndex" Int -> Ensemble NodeId
createPluginNode (Argument filePath) (Argument pluginIndex) = do
engine <- asks server_engine
engine <- asks env_engine
liftIO $ Engine.createPluginNode engine $ Clap.PluginLocation (unpack filePath) pluginIndex

data Size = Size
Expand All @@ -88,7 +87,7 @@ data WindowInfo = WindowInfo

openPluginGUI :: Argument "nodeId" NodeId -> Argument "name" Text -> Argument "parentWindow" (Maybe Int) -> Argument "scale" Double -> Argument "preferredSize" (Maybe Size) -> Ensemble Size
openPluginGUI (Argument nodeId) (Argument name) (Argument maybeParentWindow) (Argument scale) (Argument maybePreferredSize) = do
engine <- asks server_engine
engine <- asks env_engine
maybeNode <- liftIO $ Engine.lookupNode engine nodeId
case maybeNode of
Just (Node_Plugin pluginNode) -> do
Expand Down Expand Up @@ -120,7 +119,7 @@ openPluginGUI (Argument nodeId) (Argument name) (Argument maybeParentWindow) (Ar
unless setParentResult $ Engine.throwApiError "Error setting parent window of plugin GUI"
showResult <- liftIO $ Gui.show pluginGuiHandle pluginHandle
unless showResult $ Engine.throwApiError "Error showing plugin GUI"
pluginGuiThreadIdIORef <- asks server_pluginGuiThreadId
pluginGuiThreadIdIORef <- asks env_pluginGuiThreadId
pluginGuiThreadId <- liftIO $ readIORef pluginGuiThreadIdIORef
unless (isJust pluginGuiThreadId) $ do
newPluginGuiThreadId <- liftIO $ forkIO messagePump
Expand All @@ -132,7 +131,7 @@ openPluginGUI (Argument nodeId) (Argument name) (Argument maybeParentWindow) (Ar

getPluginParameters :: Argument "nodeId" NodeId -> Ensemble [ParameterInfo]
getPluginParameters (Argument nodeId) = do
engine <- asks server_engine
engine <- asks env_engine
maybeNode <- liftIO $ Engine.lookupNode engine nodeId
case maybeNode of
Just (Node_Plugin pluginNode) -> do
Expand All @@ -149,7 +148,7 @@ getPluginParameters (Argument nodeId) = do

getPluginParameterValue :: Argument "nodeId" NodeId -> Argument "parameterId" Int -> Ensemble (Maybe Double)
getPluginParameterValue (Argument nodeId) (Argument parameterId) = do
engine <- asks server_engine
engine <- asks env_engine
maybeNode <- liftIO $ Engine.lookupNode engine nodeId
case maybeNode of
Just (Node_Plugin pluginNode) -> do
Expand All @@ -164,20 +163,20 @@ getPluginParameterValue (Argument nodeId) (Argument parameterId) = do
-- Sequencer
sendEvent :: Argument "sequencerEvent" SequencerEvent -> Ensemble Ok
sendEvent (Argument event) = do
engine <- asks server_engine
engine <- asks env_engine
liftIO $ Engine.sendEventNow engine event
pure Ok

scheduleEvent :: Argument "tick" Tick -> Argument "sequencerEvent" SequencerEvent -> Ensemble Ok
scheduleEvent (Argument tick) (Argument event) = do
sequencer <- asks server_sequencer
sequencer <- asks env_sequencer
liftIO $ Sequencer.sendAt sequencer tick event
pure Ok

playSequence :: Argument "startTick" Tick -> Argument "endTick" (Maybe Tick) -> Argument "loop" Bool -> Ensemble Ok
playSequence (Argument startTick) (Argument maybeEndTick) (Argument loop) = do
sequencer <- asks server_sequencer
engine <- asks server_engine
sequencer <- asks env_sequencer
engine <- asks env_engine
void $ liftIO $ do
maybeThreadId <- readIORef (Engine.engine_playbackThread engine)
unless (isJust maybeThreadId) $ do
Expand All @@ -189,22 +188,22 @@ playSequence (Argument startTick) (Argument maybeEndTick) (Argument loop) = do

renderSequence :: Argument "startTick" Tick -> Argument "endTick" (Maybe Tick) -> Ensemble AudioOutput
renderSequence (Argument startTick) (Argument maybeEndTick) = do
sequencer <- asks server_sequencer
engine <- asks server_engine
sequencer <- asks env_sequencer
engine <- asks env_engine
endTick <- case maybeEndTick of
Just endTick -> pure endTick
Nothing -> liftIO $ Sequencer.getEndTick sequencer
liftIO $ Sequencer.renderSequence sequencer engine startTick endTick

clearSequence :: Ensemble Ok
clearSequence = do
eventQueue <- asks (Sequencer.sequencer_eventQueue . server_sequencer)
eventQueue <- asks (Sequencer.sequencer_eventQueue . env_sequencer)
liftIO $ writeIORef eventQueue []
pure Ok

stopPlayback :: Ensemble Ok
stopPlayback = do
engine <- asks server_engine
engine <- asks env_engine
liftIO $ do
maybePlaybackThreadId <- readIORef (Engine.engine_playbackThread engine)
whenJust maybePlaybackThreadId killThread
Expand All @@ -213,7 +212,7 @@ stopPlayback = do

getCurrentTick :: Ensemble Tick
getCurrentTick = do
engine <- asks server_engine
engine <- asks env_engine
liftIO $ Engine.getCurrentTick engine

ping :: Ensemble Ok
Expand Down
8 changes: 0 additions & 8 deletions src/Ensemble/Effects.hs

This file was deleted.

4 changes: 2 additions & 2 deletions src/Ensemble/Engine.hs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ sendEvents engine events = do
Just startTime -> pure startTime
Nothing -> do
let (PaTime currentTime) = PortAudio.currentTime undefined
let startTime = round $ currentTime * 1000 - (fromIntegral $ midiDeviceNode_latency midiDeviceNode)
let startTime = round $ currentTime * 1000 - fromIntegral (midiDeviceNode_latency midiDeviceNode)
writeIORef (midiDeviceNode_startTime midiDeviceNode) (Just startTime)
pure startTime
steadyTime <- readIORef (engine_steadyTime engine)
Expand Down Expand Up @@ -406,7 +406,7 @@ getAudioStream engine = do
maybeAudioStream <- liftIO $ readIORef (engine_audioStream engine)
case maybeAudioStream of
Just audioStream -> pure audioStream
Nothing -> throwApiError $ "Audio Stream not available"
Nothing -> throwApiError "Audio Stream not available"

getAudioStreamInfo :: Engine -> IO PaStreamInfo
getAudioStreamInfo engine = do
Expand Down
42 changes: 42 additions & 0 deletions src/Ensemble/Env.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@

{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}

module Ensemble.Env where

import Clap.Interface.Host
import Control.Concurrent
import Control.Monad.Reader
import Data.Aeson (Value)
import Data.IORef
import Ensemble.Config
import Ensemble.Engine
import Ensemble.Sequencer

newtype Ensemble a = Ensemble { unEnsemble :: ReaderT Env IO a }
deriving newtype (Monad, Applicative, Functor, MonadReader Env, MonadIO)

data Env = Env
{ env_config :: Config
, env_sequencer :: Sequencer
, env_engine :: Engine
, env_messageChannel :: Chan Value
, env_pluginGuiThreadId :: IORef (Maybe ThreadId)
}

createEnv :: Config -> IO Env
createEnv config = do
sequencer <- createSequencer
engine <- createEngine defaultHostConfig
messageChannel <- newChan
pluginGuiThreadId <- newIORef Nothing
pure $ Env
{ env_config = config
, env_sequencer = sequencer
, env_engine = engine
, env_messageChannel = messageChannel
, env_pluginGuiThreadId = pluginGuiThreadId
}

runEnsemble :: Env -> Ensemble a -> IO a
runEnsemble env action = runReaderT (unEnsemble action) env
13 changes: 6 additions & 7 deletions src/Ensemble/Handler.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,17 @@ import qualified Data.Aeson as A
import Data.Aeson.KeyMap (KeyMap)
import qualified Data.Aeson.KeyMap as KeyMap
import Data.Maybe
import Ensemble.Effects
import Ensemble.Error
import Ensemble.Schema (handleMessage)
import Ensemble.Schema.TH
import Ensemble.Schema.TaggedJSON (toTaggedJSON)
import Ensemble.Server
import Ensemble.Env
import GHC.Stack

deriveJSON ''ApiError

handler :: HasCallStack => Server -> KeyMap A.Value -> IO (KeyMap A.Value)
handler server object = runEnsemble server $
handler :: HasCallStack => Env -> KeyMap A.Value -> IO (KeyMap A.Value)
handler env object = runEnsemble env $
case KeyMap.lookup "@type" object of
Just (A.String messageType) ->
handleMessage messageType object
Expand All @@ -29,11 +28,11 @@ handler server object = runEnsemble server $
Nothing ->
throw $ ApiError "Message is missing '@type' field"

receiveMessage :: HasCallStack => Server -> A.Value -> IO A.Value
receiveMessage server = \case
receiveMessage :: HasCallStack => Env -> A.Value -> IO A.Value
receiveMessage env = \case
A.Object object -> do
let extraValue = KeyMap.lookup "@extra" object
outMessage <- handler server object
outMessage <- handler env object
pure $ A.Object $ KeyMap.insert "@extra" (fromMaybe A.Null extraValue) outMessage
_ -> pure $ makeError Nothing $ ApiError "Invalid JSON input, needs to be object"
where
Expand Down
2 changes: 1 addition & 1 deletion src/Ensemble/Schema.hs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import Ensemble.Engine (AudioDevice(..), AudioOutput(..), MidiDevice(..))
import Ensemble.Error
import Ensemble.Event
import Ensemble.Schema.TH
import Ensemble.Type
import Ensemble.Env

makeAPI
-- types
Expand Down
31 changes: 0 additions & 31 deletions src/Ensemble/Server.hs

This file was deleted.

10 changes: 0 additions & 10 deletions src/Ensemble/Type.hs

This file was deleted.

Loading

0 comments on commit 76ee09f

Please sign in to comment.