zwirn-0.2.3.1: app/zwirnmill/Config.hs
{-# LANGUAGE OverloadedStrings #-}
{-# OPTIONS_GHC -Wno-orphans #-}
module Config where
{-
Config.hs - configuration
Copyright (C) 2023, Martin Gius
This library is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this library. If not, see <http://www.gnu.org/licenses/>.
-}
import Brick.BChan (writeBChan)
import Brick.Types (EventM)
import Conferer (fetch, mkConfig')
import Conferer.Config ((/.))
import Conferer.FromConfig (DefaultConfig (..))
import qualified Conferer.FromConfig as Conf
import qualified Conferer.Source.CLIArgs as Cli
import qualified Conferer.Source.Env as Env
import qualified Conferer.Source.Yaml as Yaml
import Control.Concurrent (forkIO)
import Control.Monad (unless)
import Control.Monad.RWS
import qualified Data.ByteString.Lazy.UTF8 as BL
import Data.Functor (void)
import Data.Maybe (fromMaybe)
import Data.Ratio ((%))
import qualified Data.Text as T
import Data.Yaml (ToJSON (..), encodeFile, object, (.=))
import Editor.Core (OutputType (..))
import Graphics.UI.TinyFileDialogs (selectFolderDialog)
import Keymap
import System.Directory.OsPath
import System.File.OsPath as F
import System.OsPath
import UI.Core (AppEvent (..), AppState (..), Name)
import Zwirn.Doux.Types (defaultDouxClockConfig)
import qualified Zwirn.Doux.Types as Stream
import Zwirn.Language (StreamType (Doux))
import Zwirn.Language.Compiler (CIError (..), Environment (..), compilerInterpreterBoot, runCI)
import qualified Zwirn.Language.Compiler as Compiler
import Prelude hiding (log)
data StreamConfig = StreamConfig
{ streamConfigSamples :: Maybe FilePath,
streamConfigInput :: Maybe String,
streamConfigOutput :: Maybe String,
streamConfigHost :: Maybe String,
streamConfigBufferSize :: Int,
streamConfigChannels :: Int,
streamConfigBlockSize :: Int,
streamConfigMaxVoices :: Int
}
deriving (Show)
data CiConfig = CiConfig
{ ciConfigBootPath :: FilePath,
ciConfigOverwriteBuiltin :: Bool,
ciConfigDynamicTypes :: Bool,
ciConfigPrecision :: Int
}
deriving (Show)
data FullConfig = FullConfig
{ fullConfigCi :: CiConfig,
fullConfigStream :: StreamConfig
}
deriving (Show)
instance DefaultConfig CiConfig where
configDef = CiConfig "" False False 288
instance DefaultConfig StreamConfig where
configDef = StreamConfig Nothing Nothing Nothing Nothing 256 2 32 128
instance DefaultConfig FullConfig where
configDef = FullConfig configDef configDef
instance Conf.FromConfig StreamConfig where
fromConfig key configSource = do
path <- Conf.fetchFromConfig (key /. "samples") configSource
input <- Conf.fetchFromConfig (key /. "input") configSource
output <- Conf.fetchFromConfig (key /. "output") configSource
host <- Conf.fetchFromConfig (key /. "host") configSource
bs <- Conf.fetchFromConfig (key /. "buffersize") configSource
cs <- Conf.fetchFromConfig (key /. "channels") configSource
bl <- Conf.fetchFromConfig (key /. "blocksize") configSource
mx <- Conf.fetchFromConfig (key /. "maxvoices") configSource
return $ StreamConfig path input output host (fromMaybe 256 bs) (fromMaybe 2 cs) (fromMaybe 32 bl) (fromMaybe 128 mx)
instance Conf.FromConfig CiConfig where
fromConfig key configSource = do
path <- Conf.fetchFromConfig (key /. "bootpath") configSource
bp <- Conf.fetchFromConfig (key /. "overwritebuiltin") configSource
dt <- Conf.fetchFromConfig (key /. "dynamictypes") configSource
prec <- Conf.fetchFromConfig (key /. "precision") configSource
return $ CiConfig (fromMaybe "" path) (fromMaybe False bp) (fromMaybe False dt) (fromMaybe 288 prec)
instance Conf.FromConfig FullConfig where
fromConfig key configSource = do
ci <- Conf.fetchFromConfig (key /. "ci") configSource
str <- Conf.fetchFromConfig (key /. "stream") configSource
return $ FullConfig ci (fromMaybe configDef str)
instance ToJSON CiConfig where
toJSON (CiConfig p o d prec) =
object
[ "bootpath" .= p,
"overwritebuiltin" .= o,
"dynamictypes" .= d,
"precision" .= prec
]
instance ToJSON StreamConfig where
toJSON (StreamConfig sam inp out host buf chan block maxv) =
object
[ "samples" .= sam,
"host" .= host,
"input" .= inp,
"output" .= out,
"buffersize" .= buf,
"channels" .= chan,
"blocksize" .= block,
"maxvoices" .= maxv
]
instance ToJSON FullConfig where
toJSON (FullConfig ci str) =
object
[ "ci" .= toJSON ci,
"stream" .= toJSON str
]
getConfigPath :: IO OsPath
getConfigPath = do
appname <- encodeUtf "zwirnmill"
configname <- encodeUtf "config.yaml"
configDirPath <- getXdgDirectory XdgConfig appname
let path = configDirPath </> configname
createDirectoryIfMissing True configDirPath
return path
getKeymapPath :: IO OsPath
getKeymapPath = do
appname <- encodeUtf "zwirnmill"
configname <- encodeUtf "keymap.yaml"
configDirPath <- getXdgDirectory XdgConfig appname
return $ configDirPath </> configname
getConfig :: IO (FullConfig, CustomKeymap)
getConfig = do
path <- getConfigPath
kmpath <- getKeymapPath
exists <- doesFileExist path
existskm <- doesFileExist kmpath
unless exists encodeDefault
unless existskm (encodeKeymap configDef)
decoded <- decodeUtf path
decodedkm <- decodeUtf kmpath
keymapConf <- mkConfig' [] [Yaml.fromFilePath decodedkm]
conf <-
mkConfig'
[]
[ Cli.fromConfig,
Env.fromConfig "zwirnmill",
Yaml.fromFilePath decoded
]
full <- fetch conf
keymap <- fetch keymapConf
return (full, keymap)
toStream :: Rational -> StreamConfig -> Stream.StreamConfig
toStream prec (StreamConfig a b c d e f g h) = Stream.StreamConfig prec a b c d e f g h defaultDouxClockConfig
toCiConfig :: CiConfig -> Compiler.CiConfig
toCiConfig (CiConfig _ x y z) = Compiler.CiConfig x y (1 % fromIntegral z) Doux
configPath :: IO String
configPath = do
path <- getConfigPath
exists <- doesFileExist path
decoded <- decodeUtf path
if exists then return decoded else return "Config file not found!"
resetConfig :: IO String
resetConfig = encodeDefault >> return "Restored default config."
encodeDefault :: IO ()
encodeDefault = encodeConfig configDef
encodeConfig :: FullConfig -> IO ()
encodeConfig conf = do
path <- getConfigPath
strp <- decodeFS path
encodeFile strp $ toJSON conf
encodeKeymap :: CustomKeymap -> IO ()
encodeKeymap conf = do
path <- getKeymapPath
strp <- decodeFS path
encodeFile strp $ toJSON conf
getFile :: String -> IO String
getFile p = do
path <- encodeUtf p
f <- F.readFile path
return $ BL.toString f
setBootPath :: EventM Name AppState ()
setBootPath = do
mpath <- liftIO $ openFolder "choose a boot folder"
case mpath of
Just path -> do
env <- gets asEnvironment
chan <- gets asChan
menv <- liftIO $ checkBoot (\st -> void $ forkIO $ writeBChan chan (UpdateOutput (OutputInfo, st))) path env
case menv of
Just env' -> do
(FullConfig ci str, _) <- liftIO getConfig
modify $ \as -> as {asEnvironment = env'}
liftIO $ encodeConfig $ FullConfig ci {ciConfigBootPath = path} str
Nothing -> return ()
Nothing -> return ()
setSamplePath :: EventM Name AppState ()
setSamplePath = do
mpath <- liftIO $ openFolder "choose a sample folder"
case mpath of
Just path -> do
chan <- gets asChan
(FullConfig ci str, _) <- liftIO getConfig
liftIO $ encodeConfig $ FullConfig ci str {streamConfigSamples = Just path}
liftIO $ void $ forkIO $ writeBChan chan (UpdateOutput (OutputInfo, "Successfully set sample folder path. \nRestart for it to take effect."))
Nothing -> return ()
openFolder :: T.Text -> IO (Maybe FilePath)
openFolder msg = fmap T.unpack <$> selectFolderDialog msg ""
checkBoot :: (String -> IO ()) -> FilePath -> Environment -> IO (Maybe Environment)
checkBoot log "" _ = log "Starting without Bootfile." >> return Nothing
checkBoot log path env = do
ospath <- encodeUtf path
isfile <- doesFileExist ospath
ps <-
if isfile
then return $ decodeUtf ospath
else do
isfolder <- doesDirectoryExist ospath
if isfolder
then do
pss <- listDirectory ospath
fs <- mapM decodeUtf pss
return $ map (\f -> path ++ "/" ++ f) fs
else return []
res <- runCI env (compilerInterpreterBoot $ map T.pack ps)
case res of
Left (CIError err _) -> log ("Error in Bootfile: " ++ show err) >> return Nothing
Right newEnv ->
if ps /= []
then log ("Successfully loaded Bootfiles from " ++ path) >> return (Just newEnv)
else log ("No Bootfiles found at " ++ path) >> return Nothing