summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGregor Kleen <gkleen@yggdrasil.li>2018-05-15 12:50:42 +0200
committerGregor Kleen <gkleen@yggdrasil.li>2018-05-15 12:50:42 +0200
commit86ce943d5a49982246ab83e4acc72ffb7c22567c (patch)
tree9a73ed3f112c302e5a1ed980c6505fa31fcf9287
parent31a88f4dd0800caeeb56d785b1876a9c2b88fb93 (diff)
downloadtrivmix-86ce943d5a49982246ab83e4acc72ffb7c22567c.tar
trivmix-86ce943d5a49982246ab83e4acc72ffb7c22567c.tar.gz
trivmix-86ce943d5a49982246ab83e4acc72ffb7c22567c.tar.bz2
trivmix-86ce943d5a49982246ab83e4acc72ffb7c22567c.tar.xz
trivmix-86ce943d5a49982246ab83e4acc72ffb7c22567c.zip
Implement balance & refine types
-rw-r--r--src/Trivmix/Types.hs79
-rw-r--r--trivmix.cabal5
-rw-r--r--trivmix.nix17
-rw-r--r--trivmix/Trivmix.hs54
4 files changed, 100 insertions, 55 deletions
diff --git a/src/Trivmix/Types.hs b/src/Trivmix/Types.hs
index a6a41b9..f01e023 100644
--- a/src/Trivmix/Types.hs
+++ b/src/Trivmix/Types.hs
@@ -1,7 +1,8 @@
1{-# LANGUAGE FlexibleInstances, UndecidableInstances #-} 1{-# LANGUAGE FlexibleInstances, UndecidableInstances, ViewPatterns, TemplateHaskell, PatternGuards #-}
2 2
3module Trivmix.Types 3module Trivmix.Types
4 ( Level 4 ( Level'
5 , Level(Lin), toLin
5 , toFloat 6 , toFloat
6 , asFloat 7 , asFloat
7 , Adjustment(..) 8 , Adjustment(..)
@@ -13,27 +14,37 @@ import Data.Fixed
13import Data.CaseInsensitive ( CI ) 14import Data.CaseInsensitive ( CI )
14import qualified Data.CaseInsensitive as CI 15import qualified Data.CaseInsensitive as CI
15 16
17import Text.ParserCombinators.ReadPrec
18import Control.Applicative
19import Control.Monad
20
16import Data.Default 21import Data.Default
17 22
18import Data.Function (on) 23import Data.Function (on)
19 24
20data Level = Lin Float | DB Float 25import Refined
21 26
22instance Num Level where 27type Level' = Refined NonNegative Float
23 (+) = asFloat (+) 28data Level = Lin { toLin :: Level' } | DB { toLin :: Level' }
24 (-) = asFloat (-)
25 (*) = asFloat (*)
26 abs = Lin . abs . toFloat
27 signum = Lin . signum . toFloat
28 fromInteger = Lin . fromInteger
29 29
30asFloat :: (Float -> Float -> Float) -> Level -> Level -> Level 30instance Num Level where
31asFloat f (Lin x) (Lin y) = Lin $ f x y 31 (+) = fmap (either error id) . asFloat (+)
32asFloat f x y = DB $ (f `on` toFloat) x y 32 (-) = fmap (either error id) . asFloat (-)
33 (*) = fmap (either error id) . asFloat (*)
34 abs = Lin . toLin
35 signum = Lin . either error id . refine . signum . toFloat
36 fromInteger = Lin . either error id . refine . fromInteger
37
38asFloat :: (Float -> Float -> Float) -> Level -> Level -> Either String Level
39asFloat ((`on` toFloat) -> f) x y = toLvl <$> refine (f x y)
40 where
41 toLvl
42 | DB _ <- x = DB
43 | DB _ <- y = DB
44 | otherwise = Lin
33 45
34toFloat :: Level -> Float 46toFloat :: Level -> Float
35toFloat (Lin x) = x 47toFloat = unrefine . toLin
36toFloat (DB x) = x
37 48
38withPrec :: (Num a, RealFrac a, HasResolution b) => a -> Fixed b 49withPrec :: (Num a, RealFrac a, HasResolution b) => a -> Fixed b
39withPrec x = withResolution $ \p' -> MkFixed $ toInteger $ round $ x * (fromInteger p') 50withPrec x = withResolution $ \p' -> MkFixed $ toInteger $ round $ x * (fromInteger p')
@@ -44,39 +55,33 @@ withType f = f undefined
44withResolution :: (HasResolution a) => (Integer -> f a) -> f a 55withResolution :: (HasResolution a) => (Integer -> f a) -> f a
45withResolution f = withType (f . resolution) 56withResolution f = withType (f . resolution)
46 57
47linToDb :: Float -> Float 58linToDb :: Level' -> Float
48linToDb x = 20 * (logBase 10 x) 59linToDb (unrefine -> x) = 20 * (logBase 10 x)
49 60
50dBToLin :: Float -> Float 61dBToLin :: Float -> Level'
51dBToLin x = 10 ** (0.05 * x) 62dBToLin x = either error id . refine $ 10 ** (0.05 * x)
52 63
53instance Show Level where 64instance Show Level where
54 show (Lin x) = show x 65 show (Lin x) = show x
55 show (DB x) = (show $ (withPrec x' :: Milli)) ++ "dB" 66 show (DB (linToDb -> x)) = (show $ (withPrec x :: Milli)) ++ "dB"
56 where
57 x' = linToDb x
58 67
59instance Read Level where 68instance Read Level where
60 readsPrec i = map toL . readsPrec i 69 readsPrec = readPrec_to_S $ parseDb <|> parseLin
61 where 70 where
62 toL :: (Float, String) -> (Level, String) 71 parseDb = do
63 toL (f, str) 72 db <- readS_to_Prec readsPrec
64 | ((==) `on` CI.mk) prec unit = (DB $ dBToLin f, rest) 73 let
65 | otherwise = (Lin f, str) 74 unit@(length -> lU) = "dB"
66 where 75 unit' <- forM [1..lU] $ const get
67 prec = take lU str 76 guard $ ((==) `on` CI.mk) unit unit'
68 rest = drop lU str 77 return . DB $ dBToLin db
69 unit = "dB" 78 parseLin = Lin <$> readS_to_Prec readsPrec
70 lU = length unit
71 79
72instance Eq Level where 80instance Eq Level where
73 (Lin a) == (Lin b) = a == b 81 (==) = (==) `on` toLin
74 (Lin a) == (DB b) = a == b
75 (DB a) == (Lin b) = a == b
76 (DB a) == (DB b) = a == b
77 82
78instance Default Level where 83instance Default Level where
79 def = Lin 0 84 def = Lin $$(refineTH 0)
80 85
81data Adjustment a = Set a 86data Adjustment a = Set a
82 | Add a 87 | Add a
diff --git a/trivmix.cabal b/trivmix.cabal
index 1a2ef21..d075871 100644
--- a/trivmix.cabal
+++ b/trivmix.cabal
@@ -2,7 +2,7 @@
2-- documentation, see http://haskell.org/cabal/users-guide/ 2-- documentation, see http://haskell.org/cabal/users-guide/
3 3
4name: trivmix 4name: trivmix
5version: 2.7.6 5version: 3.0.0
6-- synopsis: 6-- synopsis:
7-- description: 7-- description:
8license: PublicDomain 8license: PublicDomain
@@ -22,6 +22,7 @@ library
22 build-depends: base >=4.8 && <5 22 build-depends: base >=4.8 && <5
23 , data-default >=0.5 && <1 23 , data-default >=0.5 && <1
24 , case-insensitive >=1.2 && <2 24 , case-insensitive >=1.2 && <2
25 , refined >=0.1.2.1 && <1
25 26
26executable trivmix 27executable trivmix
27 main-is: Trivmix.hs 28 main-is: Trivmix.hs
@@ -39,6 +40,8 @@ executable trivmix
39 , process >=1.2 && <2 40 , process >=1.2 && <2
40 , filelock >=0.1 && <1 41 , filelock >=0.1 && <1
41 , systemd >=1.1.2 && <2 42 , systemd >=1.1.2 && <2
43 , heredoc >=0.2.0.0 && <1
44 , refined >=0.1.2.1 && <1
42 , trivmix 45 , trivmix
43 hs-source-dirs: trivmix 46 hs-source-dirs: trivmix
44 default-language: Haskell2010 47 default-language: Haskell2010
diff --git a/trivmix.nix b/trivmix.nix
index dcc06c0..a823793 100644
--- a/trivmix.nix
+++ b/trivmix.nix
@@ -1,18 +1,21 @@
1{ mkDerivation, base, case-insensitive, data-default, directory 1{ mkDerivation, base, case-insensitive, data-default, directory
2, explicit-exception, filelock, filepath, hinotify, jack 2, explicit-exception, filelock, filepath, heredoc, hinotify, jack
3, optparse-applicative, process, stdenv, systemd, transformers 3, optparse-applicative, process, refined, stdenv, systemd
4, unix 4, transformers, unix
5}: 5}:
6mkDerivation { 6mkDerivation {
7 pname = "trivmix"; 7 pname = "trivmix";
8 version = "2.7.6"; 8 version = "3.0.0";
9 src = ./.; 9 src = ./.;
10 isLibrary = true; 10 isLibrary = true;
11 isExecutable = true; 11 isExecutable = true;
12 libraryHaskellDepends = [ base case-insensitive data-default ]; 12 libraryHaskellDepends = [
13 base case-insensitive data-default refined
14 ];
13 executableHaskellDepends = [ 15 executableHaskellDepends = [
14 base directory explicit-exception filelock filepath hinotify jack 16 base directory explicit-exception filelock filepath heredoc
15 optparse-applicative process systemd transformers unix 17 hinotify jack optparse-applicative process refined systemd
18 transformers unix
16 ]; 19 ];
17 license = stdenv.lib.licenses.publicDomain; 20 license = stdenv.lib.licenses.publicDomain;
18} 21}
diff --git a/trivmix/Trivmix.hs b/trivmix/Trivmix.hs
index 084da7f..4d7c6f7 100644
--- a/trivmix/Trivmix.hs
+++ b/trivmix/Trivmix.hs
@@ -1,10 +1,12 @@
1{-# LANGUAGE RecordWildCards #-} 1{-# LANGUAGE RecordWildCards #-}
2{-# LANGUAGE ViewPatterns #-}
3{-# LANGUAGE TemplateHaskell, QuasiQuotes #-}
2 4
3import Foreign.C.Types (CFloat(..)) 5import Foreign.C.Types (CFloat(..))
4import qualified Sound.JACK as Jack 6import qualified Sound.JACK as Jack
5import qualified Sound.JACK.Audio as Audio 7import qualified Sound.JACK.Audio as Audio
6 8
7import Options.Applicative 9import Options.Applicative hiding (str)
8 10
9import Data.Maybe 11import Data.Maybe
10 12
@@ -16,7 +18,7 @@ import System.Posix.Types
16import System.Environment 18import System.Environment
17import System.Process 19import System.Process
18 20
19import System.Systemd.Daemon (notifyReady) 21import System.Systemd.Daemon (notifyReady, notifyWatchdog)
20 22
21import Control.Concurrent 23import Control.Concurrent
22import Control.Concurrent.MVar 24import Control.Concurrent.MVar
@@ -38,15 +40,22 @@ import Data.Char
38import Data.Function 40import Data.Function
39 41
40import Control.Monad 42import Control.Monad
43
44import Text.Heredoc (str)
45import Refined
41 46
42import Trivmix.Types 47import Trivmix.Types
43 48
49type Balance = Refined ZeroToOne Float
50
44data Options = Options 51data Options = Options
45 { input :: String 52 { input :: String
46 , output :: String 53 , output :: String
47 , client :: String 54 , client :: String
48 , initialLevel :: Level 55 , initialLevel :: Level
56 , initialBalance :: Balance
49 , run :: [FilePath] 57 , run :: [FilePath]
58 , balanceFiles :: [FilePath]
50 , levelFiles :: [FilePath] 59 , levelFiles :: [FilePath]
51 } 60 }
52 61
@@ -74,13 +83,33 @@ optionParser = Options
74 <> value def 83 <> value def
75 <> showDefault 84 <> showDefault
76 ) 85 )
86 <*> option auto ( long "balance"
87 <> metavar "BALANCE"
88 <> help "Initial value for balance"
89 <> value ($$(refineTH 1.0) :: Balance)
90 <> showDefault
91 )
77 <*> many ( strOption ( long "run" 92 <*> many ( strOption ( long "run"
78 <> metavar "FILE" 93 <> metavar "FILE"
79 <> help "Execute a file once setup of jacks is done (use this to autoconnect)\nThe executable gets passed the input port (including client name) as its first argument and the output as its second." 94 <> help [str|Execute a file once setup of jacks is done (use this to autoconnect)
95 |The executable gets passed the input port (including client name) as its first argument and the output as its second.
96 |]
80 ) 97 )
81 ) 98 )
99 <*> many ( strOption ( long "balance"
100 <> metavar "FILE"
101 <> help [str|Files that contain factors in the interval [0,1] to multiply with each other and the current level.
102 |For deterministic behaviour use flock(2).
103 |The format used in these files is a float using ‘.’ as a decimal point.
104 |]
105 )
106 )
82 <*> many (strArgument ( metavar "FILE..." 107 <*> many (strArgument ( metavar "FILE..."
83 <> help "Files that contain levels to assume and synchronize\nFor deterministic behaviour use flock(2).\nThe format used in these files is either a signed float, using ‘.’ as a decimal point or a signed float postfixed with ‘dB’.\nCaveat: ‘-InfinitydB’ exists and works as expected (i.e.: it is equal to ‘0.0’)" 108 <> help [str|Files that contain levels to assume and synchronize
109 |For deterministic behaviour use flock(2).
110 |The format used in these files is either a signed float, using ‘.’ as a decimal point or a signed float postfixed with ‘dB’.
111 |Caveat: ‘-InfinitydB’ exists and works as expected (i.e.: it is equal to ‘0.0’)
112 |]
84 ) 113 )
85 ) 114 )
86 115
@@ -122,6 +151,7 @@ main = execParser opts >>= trivmix
122trivmix :: Options -> IO () 151trivmix :: Options -> IO ()
123trivmix Options{..} = do 152trivmix Options{..} = do
124 level <- newMVar initialLevel 153 level <- newMVar initialLevel
154 balance <- newMVar initialBalance
125 level' <- newMVar initialLevel 155 level' <- newMVar initialLevel
126 forkIO $ forever $ do -- Smooth out discontinuity 156 forkIO $ forever $ do -- Smooth out discontinuity
127 let 157 let
@@ -130,12 +160,15 @@ trivmix Options{..} = do
130 frames = interval * fps 160 frames = interval * fps
131 delay = round $ recip fps * 1e6 161 delay = round $ recip fps * 1e6
132 linInt x a b = a * (1 - x) + b * x 162 linInt x a b = a * (1 - x) + b * x
133 newLevel <- readMVar level 163 linInt' x a b = either error id $ asFloat (linInt x) a b
164 mulBalance (unrefine -> b) x = either error id $ asFloat (*) (Lin . either error id $ refine b) x
165 newLevel <- mulBalance <$> readMVar balance <*> readMVar level
134 currentLevel <- readMVar level' 166 currentLevel <- readMVar level'
135 mapM_ (\x -> swapMVar level' (asFloat (linInt x) currentLevel newLevel) >> threadDelay delay) ([0,recip frames..1] :: [Float]) 167 mapM_ (\x -> swapMVar level' (linInt' x currentLevel newLevel) >> threadDelay delay) ([0,recip frames..1] :: [Float])
136 let withFiles = foldl (.) id $ map (\f -> onStateFile f (show initialLevel ++ "\n")) levelFiles 168 let withFiles = foldl (.) id $ map (\f -> onStateFile f (show initialLevel ++ "\n")) levelFiles
137 withFiles $ withINotify $ \inotify -> do 169 withFiles $ withINotify $ \inotify -> do
138 handleFiles inotify level levelFiles 170 handleFiles inotify level levelFiles
171 handleFiles inotify balance levelFiles
139 Jack.handleExceptions $ 172 Jack.handleExceptions $
140 Jack.withClientDefault client $ \client' -> 173 Jack.withClientDefault client $ \client' ->
141 Jack.withPort client' input $ \input' -> 174 Jack.withPort client' input $ \input' ->
@@ -149,13 +182,14 @@ trivmix Options{..} = do
149 182
150mix :: MVar Level -> CFloat -> IO CFloat 183mix :: MVar Level -> CFloat -> IO CFloat
151mix level input = do 184mix level input = do
185 notifyWatchdog
152 level' <- readMVar level 186 level' <- readMVar level
153 return $ (CFloat $ toFloat level') * input 187 return $ (CFloat $ toFloat level') * input
154 188
155handleFiles :: INotify -> MVar Level -> [FilePath] -> IO () 189handleFiles :: (Read l, Show l, Eq l) => INotify -> MVar l -> [FilePath] -> IO ()
156handleFiles inotify level files = do 190handleFiles inotify level files = do
157 initLevel <- readMVar level 191 initLevel <- readMVar level
158 levelChanges <- (newChan :: IO (Chan Level)) 192 levelChanges <- newChan
159 stderrLock <- newEmptyMVar 193 stderrLock <- newEmptyMVar
160 let 194 let
161 handleFile file = addWatch inotify watchedAttrs file (const $ readLevel levelChanges level file stderrLock) 195 handleFile file = addWatch inotify watchedAttrs file (const $ readLevel levelChanges level file stderrLock)
@@ -206,7 +240,7 @@ takeWhileM pred (x:xs) = do
206 False -> do 240 False -> do
207 return [] 241 return []
208 242
209readLevel :: Chan Level -> MVar Level -> FilePath -> MVar () -> IO () 243readLevel :: (Read l, Show l, Eq l) => Chan l -> MVar l -> FilePath -> MVar () -> IO ()
210readLevel levelChan current file stderrLock = catch action handler 244readLevel levelChan current file stderrLock = catch action handler
211 where 245 where
212 action = do 246 action = do
@@ -228,7 +262,7 @@ readLevel levelChan current file stderrLock = catch action handler
228 then stripSpace' xs 262 then stripSpace' xs
229 else l 263 else l
230 264
231writeLevel :: FilePath -> MVar () -> Level -> IO () 265writeLevel :: Show l => FilePath -> MVar () -> l -> IO ()
232writeLevel file stderrLock level = withFileLock file Exclusive $ const $ do 266writeLevel file stderrLock level = withFileLock file Exclusive $ const $ do
233 withMVarLock stderrLock $ 267 withMVarLock stderrLock $
234 hPutStrLn stderr $ "Writing out level ‘" ++ (show level) ++ "’ to ‘" ++ file ++ "’" 268 hPutStrLn stderr $ "Writing out level ‘" ++ (show level) ++ "’ to ‘" ++ file ++ "’"