summaryrefslogtreecommitdiff
path: root/trivmix
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2015-06-21 19:40:40 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2015-06-21 19:40:40 +0200
commit4658cc95745dbdffd7bc1be2e61fa463b28b4a16 (patch)
tree0656da1577123f9f4eb05b72d66ad6c4682c5661 /trivmix
parent5aeef88338cd761066ba196472e22f2c55fc846a (diff)
downloadtrivmix-4658cc95745dbdffd7bc1be2e61fa463b28b4a16.tar
trivmix-4658cc95745dbdffd7bc1be2e61fa463b28b4a16.tar.gz
trivmix-4658cc95745dbdffd7bc1be2e61fa463b28b4a16.tar.bz2
trivmix-4658cc95745dbdffd7bc1be2e61fa463b28b4a16.tar.xz
trivmix-4658cc95745dbdffd7bc1be2e61fa463b28b4a16.zip
Added adjmix
Diffstat (limited to 'trivmix')
-rw-r--r--trivmix/Trivmix.hs215
1 files changed, 215 insertions, 0 deletions
diff --git a/trivmix/Trivmix.hs b/trivmix/Trivmix.hs
new file mode 100644
index 0000000..9f0cf22
--- /dev/null
+++ b/trivmix/Trivmix.hs
@@ -0,0 +1,215 @@
1{-# LANGUAGE RecordWildCards #-}
2
3import Foreign.C.Types (CFloat(..))
4import qualified Sound.JACK as Jack
5import qualified Sound.JACK.Audio as Audio
6
7import Options.Applicative
8
9import Data.Maybe
10
11import System.Directory
12import System.FilePath
13import System.Posix.Files
14import System.Posix.IO
15import System.Posix.Types
16import System.Environment
17import System.Process
18
19import Control.Concurrent
20import Control.Concurrent.MVar
21import Control.Concurrent.Chan
22
23import qualified Control.Monad.Trans.Class as Trans
24import qualified Control.Monad.Exception.Synchronous as Sync
25
26import Control.Exception
27import System.IO.Error
28import System.IO
29
30import System.FileLock
31import System.INotify
32
33import Data.Char
34import Data.Function
35
36import Control.Monad
37
38import Trivmix.Types
39
40data Options = Options
41 { input :: String
42 , output :: String
43 , client :: String
44 , run :: Maybe String
45 , levelFiles :: [FilePath]
46 }
47
48optionParser :: Parser Options
49optionParser = Options <$>
50 (fromMaybe "in" <$> optional (strOption ( long "input"
51 <> metavar "STRING"
52 )
53 )
54 )
55 <*> (fromMaybe "out" <$> optional (strOption ( long "output"
56 <> metavar "STRING"
57 )
58 )
59 )
60 <*> strOption ( long "client"
61 <> metavar "STRING"
62 )
63 <*> optional ( strOption ( long "run"
64 <> metavar "FILE"
65 )
66 )
67 <*> some (strArgument ( metavar "FILE..."
68 <> help "Files that contain levels to assume and synchronize"
69 )
70 )
71
72watchedAttrs :: [EventVariety]
73watchedAttrs = [ Modify
74 , Move
75 , MoveIn
76 , MoveOut
77 , MoveSelf
78 , Create
79 , Delete
80 , DeleteSelf
81 ]
82
83initialLevel :: Level
84initialLevel = def
85
86defFileMode :: FileMode
87defFileMode = foldl unionFileModes nullFileMode [ ownerReadMode
88 , ownerWriteMode
89 , groupReadMode
90 , groupWriteMode
91 , otherReadMode
92 ]
93
94defDirectoryMode :: FileMode
95defDirectoryMode = foldl unionFileModes nullFileMode [ ownerModes
96 , groupModes
97 , otherReadMode
98 , otherExecuteMode
99 ]
100main :: IO ()
101main = execParser opts >>= trivmix
102 where
103 opts = info (helper <*> optionParser)
104 ( fullDesc
105 <> progDesc "Setup a JACK mixing input/output pair controlled by files"
106 <> header "Trivmix - A trivial mixer"
107 )
108
109trivmix :: Options -> IO ()
110trivmix Options{..} = do
111 level <- newMVar initialLevel
112 let withFiles = foldl (.) id $ map (\f -> onStateFile f (show initialLevel ++ "\n")) levelFiles
113 withFiles $ withINotify $ \inotify -> do
114 handleFiles inotify level levelFiles
115 Jack.handleExceptions $
116 Jack.withClientDefault client $ \client' ->
117 Jack.withPort client' input $ \input' ->
118 Jack.withPort client' output $ \output' -> do
119 Trans.lift $ do
120 case run of
121 Nothing -> return ()
122 Just run' -> do
123 (_, _, _, ph) <- createProcess $ (proc run' [client ++ ":" ++ input, client ++ ":" ++ output]) { delegate_ctlc = True }
124 return ()
125 Audio.withProcessMono client' input' (mix level) output' $
126 Jack.withActivation client' $ Trans.lift Jack.waitForBreak
127
128mix :: MVar Level -> CFloat -> IO CFloat
129mix level input = do
130 level' <- readMVar level
131 return $ (CFloat $ toFloat level') * input
132
133handleFiles :: INotify -> MVar Level -> [FilePath] -> IO ()
134handleFiles inotify level files = do
135 initLevel <- readMVar level
136 levelChanges <- (newChan :: IO (Chan Level))
137 stderrLock <- newEmptyMVar
138 let
139 handleFile file = addWatch inotify watchedAttrs file (const $ readLevel levelChanges level file stderrLock)
140 mapM_ handleFile files
141 forkIO $ forever $ do -- Broadcast level changes and update all files
142 levelState <- readChan levelChanges
143 swapMVar level levelState
144 mapM_ (\f -> writeLevel f stderrLock levelState) files
145 return ()
146 return ()
147
148onStateFile :: FilePath -> String -> IO a -> IO a
149onStateFile file initial action = do
150 let directory = takeDirectory file
151 directories = iterate takeDirectory directory
152 createDirs <- takeWhileM (\d -> not <$> doesDirectoryExist d) directories
153 exists <- doesFileExist file
154 let acquireFile = case exists of
155 True -> return ()
156 False -> do
157 hPutStrLn stderr $ "Creating ‘" ++ file ++ "’ (file)"
158 createFile file defFileMode >>= closeFd >> writeFile file initial
159 releaseFile = case exists of
160 True -> return ()
161 False -> do
162 hPutStrLn stderr $ "Removing ‘" ++ file ++ "’ (file)"
163 removeFile file
164 acquireDir = (flip mapM) (reverse createDirs) $ \directory -> do
165 hPutStrLn stderr $ "Creating ‘" ++ directory ++ "’ (dir)"
166 createDirectory directory
167 setFileMode directory defDirectoryMode
168 releaseDir = (flip mapM) createDirs $ \directory -> do
169 hPutStrLn stderr $ "Removing ‘" ++ directory ++ "’ (dir)"
170 removeDirectory directory
171 acquire = acquireDir >> acquireFile
172 release = releaseFile >> releaseDir
173 bracket_ acquire release action
174
175takeWhileM :: Monad m => (a -> m Bool) -> [a] -> m [a]
176takeWhileM _ [] = return []
177takeWhileM pred (x:xs) = do
178 take <- pred x
179 case take of
180 True -> do
181 rest <- takeWhileM pred xs
182 return $ x:rest
183 False -> do
184 return []
185
186readLevel :: Chan Level -> MVar Level -> FilePath -> MVar () -> IO ()
187readLevel levelChan current file stderrLock = catch action handler
188 where
189 action = do
190 level <- withFileLock file Shared $ const $ readFile file >>= readIO . stripSpace
191 oldLevel <- readMVar current
192 when (oldLevel /= level) $ do
193 writeChan levelChan level
194 withMVarLock stderrLock $
195 hPutStrLn stderr $ "Detected new level: " ++ (show level)
196 handler e = if isUserError e
197 then do
198 withMVarLock stderrLock $
199 hPutStrLn stderr $ "Could not parse new level from ‘" ++ file ++ "’ - overwriting."
200 readMVar current >>= writeLevel file stderrLock
201 else throw e
202 stripSpace = reverse . stripSpace' . reverse . stripSpace'
203 stripSpace' [] = []
204 stripSpace' l@(x:xs) = if isSpace x
205 then stripSpace' xs
206 else l
207
208writeLevel :: FilePath -> MVar () -> Level -> IO ()
209writeLevel file stderrLock level = withFileLock file Exclusive $ const $ do
210 withMVarLock stderrLock $
211 hPutStrLn stderr $ "Writing out level ‘" ++ (show level) ++ "’ to ‘" ++ file ++ "’"
212 writeFile file (show level ++ "\n")
213
214withMVarLock :: MVar () -> IO a -> IO a
215withMVarLock lock = bracket_ (putMVar lock ()) (takeMVar lock)