summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--LICENSE24
-rw-r--r--Makefile8
-rw-r--r--Setup.hs2
-rw-r--r--default.nix7
-rw-r--r--src/Trivmix.hs103
-rw-r--r--trivmix.c296
-rw-r--r--trivmix.cabal35
-rw-r--r--trivmix.h21
-rw-r--r--trivmix.nix20
10 files changed, 192 insertions, 325 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..e2f5dd2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
result \ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..68a49da
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,24 @@
1This is free and unencumbered software released into the public domain.
2
3Anyone is free to copy, modify, publish, use, compile, sell, or
4distribute this software, either in source code form or as a compiled
5binary, for any purpose, commercial or non-commercial, and by any
6means.
7
8In jurisdictions that recognize copyright laws, the author or authors
9of this software dedicate any and all copyright interest in the
10software to the public domain. We make this dedication for the benefit
11of the public at large and to the detriment of our heirs and
12successors. We intend this dedication to be an overt act of
13relinquishment in perpetuity of all present and future rights to this
14software under copyright law.
15
16THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22OTHER DEALINGS IN THE SOFTWARE.
23
24For more information, please refer to <http://unlicense.org/>
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 91f8d09..0000000
--- a/Makefile
+++ /dev/null
@@ -1,8 +0,0 @@
1.PHONY: all test
2
3all: trivmix
4test: all
5 valgrind ./trivmix testDir
6
7trivmix: trivmix.c
8 gcc -lm -Wall `pkg-config --cflags --libs jack` -o trivmix trivmix.c
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
index 0000000..9a994af
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
1import Distribution.Simple
2main = defaultMain
diff --git a/default.nix b/default.nix
new file mode 100644
index 0000000..2c2a8cc
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,7 @@
1let
2 pkgs = import <nixpkgs> {};
3in rec {
4 trivmix = pkgs.stdenv.lib.overrideDerivation (pkgs.myHaskellPackages.callPackage ./trivmix.nix {}) (attrs : {
5 src = ./.;
6 });
7}
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
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.Environment
16
17import Control.Concurrent
18import Control.Concurrent.MVar
19
20import qualified Control.Monad.Trans.Class as Trans
21
22import Control.Exception
23import System.IO.Error
24
25import System.INotify
26
27data Options = Options
28 { input :: String
29 , output :: String
30 , initialLevel :: Float
31 , stateDir :: FilePath
32 }
33
34optionParser :: Parser Options
35optionParser = 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
51main :: IO ()
52main = 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
60trivmix :: Options -> IO ()
61trivmix 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
76mix :: MVar Float -> CFloat -> IO CFloat
77mix level input = do
78 level' <- readMVar level
79 return $ (CFloat level') * input
80
81onLevelFile :: FilePath -> Float -> IO a -> IO a
82onLevelFile 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
97handleLevel :: MVar Float -> FilePath -> IO ()
98handleLevel 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
diff --git a/trivmix.c b/trivmix.c
deleted file mode 100644
index 720c409..0000000
--- a/trivmix.c
+++ /dev/null
@@ -1,296 +0,0 @@
1#include <stdio.h>
2#include <errno.h>
3#include <math.h>
4#include <string.h>
5#include <stdlib.h>
6#include <unistd.h>
7#include <libgen.h>
8#include <sys/stat.h>
9#include <sys/inotify.h>
10#include <signal.h>
11
12#include "trivmix.h"
13
14#define PIDFILE "pid"
15#define INPUTFILE "input"
16#define OUTPUTFILE "output"
17#define NAMEFILE "name"
18#define GAINFILE "gain"
19#define DIRMODE S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
20#define FILEMODE S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH
21#define INOTIFYBUFLEN 1024 * (sizeof(struct inotify_event) + 16)
22#define NOTIFYMASK IN_MODIFY | IN_CREATE | IN_DELETE | IN_ONESHOT
23
24mixState state = { .input = NULL, .output = NULL, .name = NULL, .dBGain = -90 };
25char *workdir;
26char *inotifyBuffer;
27bool changedDir = false;
28
29void initState()
30{
31 const char *defaultName = "mixer";
32
33 state.input = malloc(sizeof(char));
34 *(state.input) = '\0';
35 state.output = malloc(sizeof(char));
36 *(state.output) = '\0';
37 state.name = malloc(sizeof(char) * (1 + strlen(defaultName)));
38 strcpy(state.name, defaultName);
39}
40
41void signalHandler(int signal)
42{
43 fprintf(stderr, "Received signal: %d\n", signal);
44 cleanExit(1);
45}
46
47void setupSignalHandler()
48{
49 signal(SIGABRT, signalHandler);
50 signal(SIGFPE, SIG_IGN);
51 signal(SIGILL, SIG_IGN);
52 signal(SIGINT, signalHandler);
53 signal(SIGTERM, signalHandler);
54 signal(SIGSEGV, signalHandler);
55}
56
57void parseArgs(int argc, char *argv[])
58{
59 char opt;
60
61 while ((opt = getopt(argc, argv, "g:n:i:o:")) != -1)
62 {
63 switch (opt)
64 {
65 case 'g':
66 sscanf(optarg, "%f", &(state.dBGain));
67 break;
68 case 'n':
69 state.name = realloc(state.name, sizeof(char) * (1 + strlen(optarg)));
70 strcpy(state.name, optarg);
71 break;
72 case 'i':
73 state.input = realloc(state.input, sizeof(char) * (1 + strlen(optarg)));
74 strcpy(state.input, optarg);
75 break;
76 case 'o':
77 state.output = realloc(state.output, sizeof(char) * (1 + strlen(optarg)));
78 strcpy(state.output, optarg);
79 break;
80 }
81 }
82
83 if (optind > argc - 1)
84 {
85 fprintf(stderr, "Usage: %s [-g {gain}] [-n {name}] [-i {input}] [-o {output}] {working directory}\n", argv[0]);
86 cleanExit(2);
87 }
88
89 workdir = argv[optind++];
90}
91
92void setWorkdir()
93{
94 if (!(mkdir(workdir, DIRMODE) == 0 || errno == EEXIST) || chdir(workdir) == -1)
95 errMsg(1, &errno, "Failed to change to workdir", workdir);
96 changedDir = true;
97}
98
99void writePid()
100{
101 FILE *pidFile;
102
103 openSyncFile(&pidFile, PIDFILE, "wx", false);
104 if (fprintf(pidFile, "%d\n", getpid()) <= 1)
105 errMsg(1, &errno, "Failed to write to pidfile", PIDFILE);
106 fclose(pidFile);
107}
108
109void syncState()
110{
111 readState();
112 writeState();
113}
114
115void readState()
116{
117 FILE *pidFile;
118 FILE *inputFile;
119 FILE *outputFile;
120 FILE *nameFile;
121 FILE *gainFile;
122 int newInt;
123 char newString[64];
124 float newFloat;
125 int ret;
126
127 if (openSyncFile(&pidFile, PIDFILE, "r", true) == -1)
128 {
129 ret = fscanf(pidFile, "%d", &newInt);
130 fclose(pidFile);
131
132 if (ret == 0 || ret == EOF || newInt != getpid())
133 cleanExit(0);
134 }
135 else
136 cleanExit(0);
137
138 if (openSyncFile(&inputFile, INPUTFILE, "r", true) == -1)
139 {
140 ret = fscanf(inputFile, "%63s", newString);
141 fclose(inputFile);
142
143 if (ret != 0 && ret != EOF)
144 {
145 // TODO Try to set new jack input here
146 state.input = realloc(state.input, sizeof(char) * (1 + strlen(newString)));
147 strcpy(state.input, newString);
148 }
149 }
150 if (openSyncFile(&outputFile, OUTPUTFILE, "r", true) == -1)
151 {
152 ret = fscanf(outputFile, "%63s", newString);
153 fclose(outputFile);
154
155 if (ret != 0 && ret != EOF)
156 {
157 // TODO Try to set new jack output here
158 state.output = realloc(state.output, sizeof(char) * (1 + strlen(newString)));
159 strcpy(state.output, newString);
160 }
161 }
162
163 if (openSyncFile(&gainFile, GAINFILE, "r", true) == -1)
164 {
165 ret = fscanf(gainFile, "%f", &newFloat);
166 fclose(gainFile);
167
168 if (ret != 0 && ret != EOF)
169 {
170 if (newFloat < -90)
171 newFloat = -90;
172 state.dBGain = newFloat;
173 }
174 }
175}
176
177void writeState()
178{
179 FILE *pidFile;
180 FILE *inputFile;
181 FILE *outputFile;
182 FILE *nameFile;
183 FILE *gainFile;
184 int ret;
185
186 openSyncFile(&pidFile, PIDFILE, "w", false);
187 ret = fprintf(pidFile, "%d\n", getpid());
188 if (ret < 1)
189 errMsg(1, &errno, "Failed to write pidfile", PIDFILE);
190 fclose(pidFile);
191
192 openSyncFile(&inputFile, INPUTFILE, "w", false);
193 ret = fprintf(inputFile, "%s\n", state.input);
194 if (ret < 1)
195 errMsg(1, &errno, "Failed to write file", INPUTFILE);
196 fclose(inputFile);
197
198 openSyncFile(&outputFile, OUTPUTFILE, "w", false);
199 ret = fprintf(outputFile, "%s\n", state.output);
200 if (ret < 1)
201 errMsg(1, &errno, "Failed to write file", OUTPUTFILE);
202 fclose(outputFile);
203
204 openSyncFile(&nameFile, NAMEFILE, "w", false);
205 ret = fprintf(nameFile, "%s\n", state.name);
206 if (ret < 1)
207 errMsg(1, &errno, "Failed to write file", NAMEFILE);
208 fclose(nameFile);
209
210 openSyncFile(&gainFile, GAINFILE, "w", false);
211 ret = fprintf(gainFile, "%.2f\n", state.dBGain);
212 if (ret < 1)
213 errMsg(1, &errno, "Failed to write file", GAINFILE);
214 fclose(gainFile);
215}
216
217int openSyncFile(FILE **file, char *fileName, char *fileMode, bool errOK)
218{
219 int ret = -1;
220
221 if ((*file = fopen(fileName, fileMode)) == NULL)
222 {
223 if (errOK == false)
224 errMsg(1, &errno, "Failed to open file", fileName);
225 else
226 {
227 ret = errno;
228 }
229 }
230
231 return ret;
232}
233
234int main(int argc, char *argv[])
235{
236 int inotifyFD;
237
238 initState();
239 inotifyBuffer = malloc(INOTIFYBUFLEN);
240 setupSignalHandler();
241
242 parseArgs(argc, argv);
243 setWorkdir();
244
245 writePid();
246 writeState();
247
248 inotifyFD = inotify_init();
249 if (inotifyFD < 0)
250 errMsg(1, NULL, "Failed to setup inotify", NULL);
251
252 do
253 {
254 syncState();
255 printf("State:\n input = %s\n output = %s\n name = %s\n gain = %.2fdB\n", state.input, state.output, state.name, state.dBGain);
256 inotify_add_watch(inotifyFD, ".", NOTIFYMASK);
257 }
258 while (read(inotifyFD, inotifyBuffer, INOTIFYBUFLEN) >= 0);
259
260 exit(0);
261}
262
263void cleanExit(int r)
264{
265 if (changedDir == true)
266 {
267 if (unlink(PIDFILE) != 0 && errno != ENOENT)
268 errMsg(-1, &errno, "Failed to delete pidfile", PIDFILE);
269 }
270 free(state.input);
271 free(state.output);
272 free(state.name);
273 free(inotifyBuffer);
274 exit(r);
275}
276
277void errMsg(int r, int *errno, char *head, char *detail)
278{
279 int err;
280 bool error = false;
281
282 if (errno != NULL)
283 {
284 error = true;
285 err = *errno;
286 }
287
288 fprintf(stderr, "%s\n", head);
289 if (detail != NULL)
290 fprintf(stderr, " %s\n", detail);
291 if (error == true)
292 fprintf(stderr, " (%d) %s\n", err, strerror(err));
293
294 if (r >= 0)
295 cleanExit(r);
296}
diff --git a/trivmix.cabal b/trivmix.cabal
new file mode 100644
index 0000000..ae9a72e
--- /dev/null
+++ b/trivmix.cabal
@@ -0,0 +1,35 @@
1-- Initial trivmix.cabal generated by cabal init. For further
2-- documentation, see http://haskell.org/cabal/users-guide/
3
4name: trivmix
5version: 0.0.0
6-- synopsis:
7-- description:
8license: PublicDomain
9license-file: LICENSE
10author: Gregor Kleen
11maintainer: aethoago@141.li
12-- copyright:
13category: Sound
14build-type: Simple
15-- extra-source-files:
16cabal-version: >=1.10
17
18executable trivmix
19 main-is: Trivmix.hs
20 -- other-modules:
21 -- other-extensions:
22 build-depends: base >=4.7 && <4.8
23 , jack >=0.7 && <1
24 , optparse-applicative >=0.11 && <1
25 , directory >=1.2 && <2
26 , filepath >=1.3 && <2
27 , unix >=2.7 && <3
28 , hinotify >=0.3 && <1
29 , transformers >=0.3 && <1
30 hs-source-dirs: src
31 default-language: Haskell2010
32
33-- Local Variables:
34-- firestarter: "nix-shell -p haskellPackages.cabal2nix --command 'cabal2nix ./.' | tee trivmix.nix"
35-- End:
diff --git a/trivmix.h b/trivmix.h
deleted file mode 100644
index 03b5860..0000000
--- a/trivmix.h
+++ /dev/null
@@ -1,21 +0,0 @@
1typedef struct mixState {
2 char *input;
3 char *output;
4 char *name;
5 float dBGain;
6} mixState;
7
8typedef enum bool {
9 false,
10 true
11} bool;
12
13void parseArgs(int argc, char *argv[]);
14void setWorkdir();
15void syncState();
16void readState();
17void writeState();
18int openSyncFile(FILE **file, char *fileName, char *fileMode, bool errOK);
19
20void cleanExit(int r);
21void errMsg(int r, int *errno, char *head, char *detail);
diff --git a/trivmix.nix b/trivmix.nix
new file mode 100644
index 0000000..746d548
--- /dev/null
+++ b/trivmix.nix
@@ -0,0 +1,20 @@
1# This file was auto-generated by cabal2nix. Please do NOT edit manually!
2
3{ cabal, filepath, hinotify, jack, optparseApplicative
4, transformers
5}:
6
7cabal.mkDerivation (self: {
8 pname = "trivmix";
9 version = "0.0.0";
10 src = ./.;
11 isLibrary = false;
12 isExecutable = true;
13 buildDepends = [
14 filepath hinotify jack optparseApplicative transformers
15 ];
16 meta = {
17 license = self.stdenv.lib.licenses.publicDomain;
18 platforms = self.ghc.meta.platforms;
19 };
20})