diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/Trivmix.hs | 103 |
1 files changed, 103 insertions, 0 deletions
diff --git a/src/Trivmix.hs b/src/Trivmix.hs new file mode 100644 index 0000000..019ee32 --- /dev/null +++ b/src/Trivmix.hs | |||
| @@ -0,0 +1,103 @@ | |||
| 1 | {-# LANGUAGE RecordWildCards #-} | ||
| 2 | |||
| 3 | import Foreign.C.Types (CFloat(..)) | ||
| 4 | import qualified Sound.JACK as Jack | ||
| 5 | import qualified Sound.JACK.Audio as Audio | ||
| 6 | |||
| 7 | import Options.Applicative | ||
| 8 | |||
| 9 | import Data.Maybe | ||
| 10 | |||
| 11 | import System.Directory | ||
| 12 | import System.FilePath | ||
| 13 | import System.Posix.Files | ||
| 14 | import System.Posix.IO | ||
| 15 | import System.Environment | ||
| 16 | |||
| 17 | import Control.Concurrent | ||
| 18 | import Control.Concurrent.MVar | ||
| 19 | |||
| 20 | import qualified Control.Monad.Trans.Class as Trans | ||
| 21 | |||
| 22 | import Control.Exception | ||
| 23 | import System.IO.Error | ||
| 24 | |||
| 25 | import System.INotify | ||
| 26 | |||
| 27 | data Options = Options | ||
| 28 | { input :: String | ||
| 29 | , output :: String | ||
| 30 | , initialLevel :: Float | ||
| 31 | , stateDir :: FilePath | ||
| 32 | } | ||
| 33 | |||
| 34 | optionParser :: Parser Options | ||
| 35 | optionParser = Options <$> | ||
| 36 | strOption ( long "input" | ||
| 37 | <> metavar "JACK" | ||
| 38 | ) | ||
| 39 | <*> strOption ( long "output" | ||
| 40 | <> metavar "JACK" | ||
| 41 | ) | ||
| 42 | <*> (fromMaybe 0 <$> optional (option auto ( long "level" | ||
| 43 | <> metavar "FLOAT" | ||
| 44 | ) | ||
| 45 | ) | ||
| 46 | ) | ||
| 47 | <*> strOption ( long "dir" | ||
| 48 | <> metavar "DIRECTORY" | ||
| 49 | ) | ||
| 50 | |||
| 51 | main :: IO () | ||
| 52 | main = execParser opts >>= trivmix | ||
| 53 | where | ||
| 54 | opts = info (helper <*> optionParser) | ||
| 55 | ( fullDesc | ||
| 56 | <> progDesc "Setup a JACK mixing input/output pair controlled by fifos in a state directory" | ||
| 57 | <> header "Trivmix - A trivial mixer" | ||
| 58 | ) | ||
| 59 | |||
| 60 | trivmix :: Options -> IO () | ||
| 61 | trivmix Options{..} = do | ||
| 62 | name <- getProgName | ||
| 63 | createDirectoryIfMissing True stateDir | ||
| 64 | level <- newMVar initialLevel | ||
| 65 | let levelFile = stateDir </> "level" | ||
| 66 | onLevelFile levelFile initialLevel $ withINotify $ \n -> do | ||
| 67 | addWatch n [Modify] levelFile (const $ handleLevel level levelFile) | ||
| 68 | Jack.handleExceptions $ | ||
| 69 | Jack.withClientDefault name $ \client -> | ||
| 70 | Jack.withPort client input $ \input' -> | ||
| 71 | Jack.withPort client output $ \output' -> | ||
| 72 | Audio.withProcessMono client input' (mix level) output' $ | ||
| 73 | Jack.withActivation client $ Trans.lift $ do | ||
| 74 | Jack.waitForBreak | ||
| 75 | |||
| 76 | mix :: MVar Float -> CFloat -> IO CFloat | ||
| 77 | mix level input = do | ||
| 78 | level' <- readMVar level | ||
| 79 | return $ (CFloat level') * input | ||
| 80 | |||
| 81 | onLevelFile :: FilePath -> Float -> IO a -> IO a | ||
| 82 | onLevelFile file initial action = do | ||
| 83 | exists <- doesFileExist file | ||
| 84 | let acquire = case exists of | ||
| 85 | True -> return () | ||
| 86 | False -> createFile file mode >>= closeFd | ||
| 87 | mode = foldl unionFileModes nullFileMode [ ownerReadMode | ||
| 88 | , ownerWriteMode | ||
| 89 | , groupReadMode | ||
| 90 | , groupWriteMode | ||
| 91 | ] | ||
| 92 | release = case exists of | ||
| 93 | True -> return () | ||
| 94 | False -> removeFile file | ||
| 95 | bracket_ acquire release action | ||
| 96 | |||
| 97 | handleLevel :: MVar Float -> FilePath -> IO () | ||
| 98 | handleLevel level file = catch action handler | ||
| 99 | where | ||
| 100 | action = readFile file >>= readIO >>= swapMVar level >>= const (return ()) | ||
| 101 | handler e = if isUserError e | ||
| 102 | then readMVar level >>= \l -> writeFile file (show l) | ||
| 103 | else throw e | ||
