module Option where import qualified Feature import qualified Parameters as Params import qualified HiddenMarkovModel as HMM import qualified Distribution.Verbosity as Verbosity import qualified Distribution.ReadE as ReadE import Distribution.Verbosity (Verbosity) import qualified Options.Applicative as OP import qualified SignalProcessingMethods as SPMethods import qualified Data.StorableVector.Lazy as SVL import qualified Sound.SoxLib as SoxLib import qualified System.Path.PartClass as PathClass import qualified System.Path as Path import qualified System.IO as IO import Text.Printf (printf, ) import Control.Monad (when, ) import Control.Applicative (pure, (<*>), (<$>), (<|>), ) import Control.Applicative.HT (liftA5, ) import qualified Data.Map as Map import qualified Data.List as List import Data.Monoid ((<>), ) data Flags = Flags { verbosity :: Verbosity, chunkSize :: SVL.ChunkSize, sampleRate :: Maybe SoxLib.Rate } deriving (Show) optionVerbosity :: OP.ReadM Verbosity optionVerbosity = OP.eitherReader $ ReadE.runReadE Verbosity.flagToVerbosity -- cf. Distribution.Simple.Utils noticeAction :: Flags -> IO () -> IO () noticeAction flags emit = when (verbosity flags >= Verbosity.normal) emit notice :: Flags -> String -> IO () notice flags msg = noticeAction flags (putStrLn msg) infoAction :: Flags -> IO () -> IO () infoAction flags emit = when (verbosity flags >= Verbosity.verbose) emit infoMsg :: Flags -> String -> IO () infoMsg flags msg = infoAction flags (putStrLn msg) warn :: Flags -> String -> IO () warn flags msg = when (verbosity flags >= Verbosity.normal) $ do IO.hFlush IO.stdout IO.hPutStr IO.stderr msg parseFlags :: OP.Parser Flags parseFlags = pure Flags <*> OP.option optionVerbosity ( OP.value Verbosity.normal <> OP.short 'v' <> OP.long "verbose" <> OP.metavar "0..3" <> OP.help "verbosity" ) <*> OP.option (fmap SVL.chunkSize OP.auto) ( OP.value (SVL.chunkSize 65536) -- crashes SoX' FLAC writer <> OP.long "chunksize" <> OP.metavar "NUMSAMPLES" <> OP.help "size of processing units" ) <*> OP.option (fmap Just OP.auto) ( OP.value Nothing <> OP.long "samplerate" <> OP.metavar "HERTZ" <> OP.help "override sample rate of input files" ) model :: OP.Parser (IO Feature.HMM) model = OP.option (Feature.readHMM <$> path) $ OP.long "model" <> OP.metavar "PATH" <> OP.value (return Feature.hmmHardwired) <> OP.help "CSV file containing HMM parameters" path :: (PathClass.FileDir fd) => OP.ReadM (Path.AbsRel fd) path = OP.eitherReader Path.parse numStates :: OP.Parser Int numStates = OP.option (OP.eitherReader $ \str -> case reads str of [(n, "")] -> if n>0 then Right n else Left "not positive" _ -> Left "not an integer") $ OP.long "numstates" <> OP.metavar "NUMBER" <> OP.help "number of states for Hidden Markov Model" <> OP.value 6 data TrainingFlags = TrainingFlags { trainingSignalProcessing :: SPMethods.T, trainingFeature :: Feature.Class, trainingConvergence :: HMM.Convergence, trainingMLPack :: Bool, trainingPlot :: Bool } formatBool :: Bool -> String formatBool b = if b then "yes" else "no" switch :: Bool -> String -> String -> OP.Parser Bool switch deflt name helpMsg = OP.flag' False (OP.long ("no-" ++ name)) <|> OP.flag deflt True (OP.long name <> OP.help (printf "%s (default: %s)" helpMsg (formatBool deflt))) feature :: Params.T -> OP.Parser (IO Feature.Class) feature params = let lookupFeature name = case Map.lookup name Feature.dictionaryMerged of Just feat -> return feat Nothing -> ioError $ userError $ unlines $ printf "unknown feature \"%s\"" name : "known features:" : Map.keys Feature.dictionaryMerged in OP.option (fmap lookupFeature OP.str) $ OP.long "feature" <> OP.metavar "NAME" <> OP.value (return $ Feature.lowRateSqrt $ Params.featureSampleRate params) <> OP.help ("one of " ++ List.intercalate ", " (Map.keys Feature.dictionaryMerged)) plot :: OP.Parser Bool plot = OP.switch $ OP.long "plot" <> OP.help "show feature vectors grouped by model states" mlpack :: OP.Parser Bool mlpack = OP.switch $ OP.long "mlpack" <> OP.help "emit files that let you perform training using mlpack" trainingFlags :: OP.Parser SPMethods.T -> Params.T -> OP.Parser (IO TrainingFlags) trainingFlags signalProcessing params = liftA5 (\sigProc lookupFeature cvg mlp plt -> do feat <- lookupFeature return $ TrainingFlags sigProc feat cvg mlp plt) signalProcessing (feature params) HMM.convergenceOptions mlpack plot type Commands = OP.Mod OP.CommandFields simpleAction :: String -> String -> OP.Parser a -> Commands a simpleAction name helpText act = OP.command name $ OP.info (OP.helper <*> act) (OP.progDesc helpText) withInOutPaths :: (PathClass.FileDir fd0, PathClass.FileDir fd1) => (Flags -> Path.AbsRel fd0 -> Path.AbsRel fd1 -> IO ()) -> (FilePath -> FilePath -> Flags -> IO ()) withInOutPaths act = \inputStr outputStr flags -> do let ioExc = either (ioError . userError) return input <- ioExc $ Path.parse inputStr output <- ioExc $ Path.parse outputStr act flags input output transferActionApp :: (PathClass.FileDir fd0, PathClass.FileDir fd1) => String -> String -> OP.Parser (Flags -> Path.AbsRel fd0 -> Path.AbsRel fd1 -> IO ()) -> Commands (Flags -> IO ()) transferActionApp name msg parser = simpleAction name msg $ OP.liftA3 withInOutPaths parser (OP.strArgument (OP.metavar "SRC")) (OP.strArgument (OP.metavar "DST")) transferAction :: (PathClass.FileDir fd0, PathClass.FileDir fd1) => String -> String -> (Flags -> Path.AbsRel fd0 -> Path.AbsRel fd1 -> IO ()) -> Commands (Flags -> IO ()) transferAction name msg = transferActionApp name msg . pure multiAction :: (PathClass.FileDir fd0, PathClass.FileDir fd1) => String -> String -> OP.Parser (Bool -> [Path.AbsRel fd0] -> Path.AbsRel fd1 -> a) -> Commands a multiAction name msg parse = simpleAction name msg $ parse <*> plot <*> OP.some (OP.argument path (OP.metavar "SRC")) <*> OP.option path (OP.long "output" <> OP.metavar "DST") info :: OP.Parser a -> OP.ParserInfo a info parser = OP.info (OP.helper <*> parser) (OP.fullDesc <> OP.progDesc "Classify sounds of xenopus laevis")