summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2015-06-09 22:32:15 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2015-06-09 22:32:15 +0200
commit60cc6cf218d8f1a12360d0188450f83e04d92c1a (patch)
treeb4c642c837277dd37bb02a6e437fae7d19772052 /src
parentefe79077888f5f22dae9aeb9e1e82745748b2f15 (diff)
downloadtrivmix-60cc6cf218d8f1a12360d0188450f83e04d92c1a.tar
trivmix-60cc6cf218d8f1a12360d0188450f83e04d92c1a.tar.gz
trivmix-60cc6cf218d8f1a12360d0188450f83e04d92c1a.tar.bz2
trivmix-60cc6cf218d8f1a12360d0188450f83e04d92c1a.tar.xz
trivmix-60cc6cf218d8f1a12360d0188450f83e04d92c1a.zip
decibel levels && multiple level files
Diffstat (limited to 'src')
-rw-r--r--src/Trivmix.hs133
1 files changed, 96 insertions, 37 deletions
diff --git a/src/Trivmix.hs b/src/Trivmix.hs
index c1fbe8a..abb7c32 100644
--- a/src/Trivmix.hs
+++ b/src/Trivmix.hs
@@ -17,6 +17,7 @@ import System.Process
17 17
18import Control.Concurrent 18import Control.Concurrent
19import Control.Concurrent.MVar 19import Control.Concurrent.MVar
20import Control.Concurrent.Chan
20 21
21import qualified Control.Monad.Trans.Class as Trans 22import qualified Control.Monad.Trans.Class as Trans
22import qualified Control.Monad.Exception.Synchronous as Sync 23import qualified Control.Monad.Exception.Synchronous as Sync
@@ -27,16 +28,40 @@ import System.IO.Error
27import System.INotify 28import System.INotify
28 29
29import Data.Char 30import Data.Char
31import Data.Function
32
33import Control.Monad
34
35import Data.CaseInsensitive ( CI )
36import qualified Data.CaseInsensitive as CI
30 37
31data Options = Options 38data Options = Options
32 { input :: String 39 { input :: String
33 , output :: String 40 , output :: String
34 , client :: String 41 , client :: String
35 , run :: Maybe String 42 , run :: Maybe String
36 , initialLevel :: Float 43 , levelFiles :: [FilePath]
37 , stateDir :: FilePath
38 } 44 }
39 45
46data Level = Lin Float | DB Float
47
48instance Show Level where
49 show (Lin x) = show x
50 show (DB x) = (show x) ++ "dB"
51
52instance Read Level where
53 readsPrec i = map toL . readsPrec i
54 where
55 toL :: (Float, String) -> (Level, String)
56 toL (f, str)
57 | ((==) `on` CI.mk) prec unit = (DB f, rest)
58 | otherwise = (Lin f, str)
59 where
60 prec = take lU str
61 rest = drop lU str
62 unit = "dB"
63 lU = length unit
64
40optionParser :: Parser Options 65optionParser :: Parser Options
41optionParser = Options <$> 66optionParser = Options <$>
42 (fromMaybe "in" <$> optional (strOption ( long "input" 67 (fromMaybe "in" <$> optional (strOption ( long "input"
@@ -56,14 +81,24 @@ optionParser = Options <$>
56 <> metavar "FILE" 81 <> metavar "FILE"
57 ) 82 )
58 ) 83 )
59 <*> (fromMaybe 0 <$> optional (option auto ( long "level" 84 <*> some (strOption ( long "level"
60 <> metavar "FLOAT" 85 <> metavar "FILE"
61 ) 86 )
62 ) 87 )
63 ) 88
64 <*> strOption ( long "dir" 89initialLevel :: Level
65 <> metavar "DIRECTORY" 90initialLevel = Lin 0
66 ) 91
92watchedAttrs :: [EventVariety]
93watchedAttrs = [ Modify
94 , Move
95 , MoveIn
96 , MoveOut
97 , MoveSelf
98 , Create
99 , Delete
100 , DeleteSelf
101 ]
67 102
68main :: IO () 103main :: IO ()
69main = execParser opts >>= trivmix 104main = execParser opts >>= trivmix
@@ -75,11 +110,10 @@ main = execParser opts >>= trivmix
75 ) 110 )
76 111
77trivmix :: Options -> IO () 112trivmix :: Options -> IO ()
78trivmix Options{..} = onDirectory stateDir $ do 113trivmix Options{..} = do
79 level <- newMVar initialLevel 114 level <- newMVar initialLevel
80 let levelFile = stateDir </> "level" 115 withINotify $ \inotify -> do
81 onLevelFile levelFile initialLevel $ withINotify $ \n -> do 116 handleFiles inotify level levelFiles
82 addWatch n [Modify] levelFile (const $ handleLevel level levelFile)
83 Jack.handleExceptions $ 117 Jack.handleExceptions $
84 Jack.withClientDefault client $ \client' -> 118 Jack.withClientDefault client $ \client' ->
85 Jack.withPort client' input $ \input' -> 119 Jack.withPort client' input $ \input' ->
@@ -93,42 +127,67 @@ trivmix Options{..} = onDirectory stateDir $ do
93 Audio.withProcessMono client' input' (mix level) output' $ 127 Audio.withProcessMono client' input' (mix level) output' $
94 Jack.withActivation client' $ Trans.lift Jack.waitForBreak 128 Jack.withActivation client' $ Trans.lift Jack.waitForBreak
95 129
96onDirectory :: FilePath -> IO () -> IO () 130mix :: MVar Level -> CFloat -> IO CFloat
97onDirectory stateDir io = do
98 exists <- doesDirectoryExist stateDir
99 createDirectoryIfMissing True stateDir
100 finally io $ if exists then removeDirectory stateDir else return ()
101
102mix :: MVar Float -> CFloat -> IO CFloat
103mix level input = do 131mix level input = do
104 level' <- readMVar level 132 level' <- readMVar level
105 return $ (CFloat level') * input 133 return $ (CFloat $ toFloat level') * input
106 134 where
107onLevelFile :: FilePath -> Float -> IO a -> IO a 135 toFloat (Lin x) = x
108onLevelFile file initial action = do 136 toFloat (DB x) = 10 ** (0.05 * x)
137
138handleFiles :: INotify -> MVar Level -> [FilePath] -> IO ()
139handleFiles inotify level files = do
140 initLevel <- readMVar level
141 levelChanges <- (newChan :: IO (Chan Level))
142 let
143 handleFiles' = mapM handleFile files
144 handleFile file = do
145 levelChanges' <- dupChan levelChanges
146 forkIO $ forever $ do -- Broadcast level changes and update all files
147 readChan levelChanges' >>= writeLevel file
148 addWatch inotify watchedAttrs file (const $ readLevel levelChanges level file)
149 foldl (.) id [onStateFile f (show initLevel ++ "\n") | f <- files] $ handleFiles'
150 forkIO $ forever $ do
151 readChan levelChanges >>= swapMVar level
152 return ()
153 return ()
154
155onStateFile :: FilePath -> String -> IO a -> IO a
156onStateFile file initial action = do
157 let directory = takeDirectory file
158 dirExists <- doesDirectoryExist directory
109 exists <- doesFileExist file 159 exists <- doesFileExist file
110 let acquire = case exists of 160 createDirectoryIfMissing True directory
161 let acquireFile = case exists of
111 True -> return () 162 True -> return ()
112 False -> createFile file mode >>= closeFd >> writeFile file (show initial ++ "\n") 163 False -> createFile file fileMode >>= closeFd >> writeFile file initial
113 mode = foldl unionFileModes nullFileMode [ ownerReadMode 164 fileMode = foldl unionFileModes nullFileMode [ ownerReadMode
114 , ownerWriteMode 165 , ownerWriteMode
115 , groupReadMode 166 , groupReadMode
116 , groupWriteMode 167 , groupWriteMode
117 ] 168 ]
118 release = case exists of 169 releaseFile = case exists of
119 True -> return () 170 True -> return ()
120 False -> removeFile file 171 False -> removeFile file
172 releaseDir = case dirExists of
173 True -> return ()
174 False -> removeFile directory
175 acquire = acquireFile
176 release = releaseFile >> releaseDir
121 bracket_ acquire release action 177 bracket_ acquire release action
122 178
123handleLevel :: MVar Float -> FilePath -> IO () 179readLevel :: Chan Level -> MVar Level -> FilePath -> IO ()
124handleLevel level file = catch action handler 180readLevel levelChan current file = catch action handler
125 where 181 where
126 action = readFile file >>= readIO . stripSpace >>= swapMVar level >>= const (return ()) 182 action = readFile file >>= readIO . stripSpace >>= writeChan levelChan
127 handler e = if isUserError e 183 handler e = if isUserError e
128 then readMVar level >>= \l -> writeFile file (show l ++ "\n") 184 then readMVar current >>= writeLevel file
129 else throw e 185 else throw e
130 stripSpace = reverse . stripSpace' . reverse . stripSpace' 186 stripSpace = reverse . stripSpace' . reverse . stripSpace'
131 stripSpace' [] = [] 187 stripSpace' [] = []
132 stripSpace' l@(x:xs) = if isSpace x 188 stripSpace' l@(x:xs) = if isSpace x
133 then stripSpace' xs 189 then stripSpace' xs
134 else l 190 else l
191
192writeLevel :: FilePath -> Level -> IO ()
193writeLevel file level = writeFile file (show level ++ "\n")