diff options
author | Gregor Kleen <gkleen@yggdrasil.li> | 2015-06-07 19:33:45 +0200 |
---|---|---|
committer | Gregor Kleen <gkleen@yggdrasil.li> | 2015-06-07 19:33:45 +0200 |
commit | bd7874ef606ae78bb8b626bd01906481feb784d6 (patch) | |
tree | 5990e8498393e9344be47f722543668e54081b18 /src | |
parent | 9921cfd56ceca0cff91c9df018538a04f5776123 (diff) | |
download | trivmix-bd7874ef606ae78bb8b626bd01906481feb784d6.tar trivmix-bd7874ef606ae78bb8b626bd01906481feb784d6.tar.gz trivmix-bd7874ef606ae78bb8b626bd01906481feb784d6.tar.bz2 trivmix-bd7874ef606ae78bb8b626bd01906481feb784d6.tar.xz trivmix-bd7874ef606ae78bb8b626bd01906481feb784d6.zip |
Rewrite in haskell
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 | ||