diff options
| author | Gregor Kleen <gkleen@yggdrasil.li> | 2018-05-15 12:50:42 +0200 |
|---|---|---|
| committer | Gregor Kleen <gkleen@yggdrasil.li> | 2018-05-15 12:50:42 +0200 |
| commit | 86ce943d5a49982246ab83e4acc72ffb7c22567c (patch) | |
| tree | 9a73ed3f112c302e5a1ed980c6505fa31fcf9287 | |
| parent | 31a88f4dd0800caeeb56d785b1876a9c2b88fb93 (diff) | |
| download | trivmix-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.hs | 79 | ||||
| -rw-r--r-- | trivmix.cabal | 5 | ||||
| -rw-r--r-- | trivmix.nix | 17 | ||||
| -rw-r--r-- | trivmix/Trivmix.hs | 54 |
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 | ||
| 3 | module Trivmix.Types | 3 | module 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 | |||
| 13 | import Data.CaseInsensitive ( CI ) | 14 | import Data.CaseInsensitive ( CI ) |
| 14 | import qualified Data.CaseInsensitive as CI | 15 | import qualified Data.CaseInsensitive as CI |
| 15 | 16 | ||
| 17 | import Text.ParserCombinators.ReadPrec | ||
| 18 | import Control.Applicative | ||
| 19 | import Control.Monad | ||
| 20 | |||
| 16 | import Data.Default | 21 | import Data.Default |
| 17 | 22 | ||
| 18 | import Data.Function (on) | 23 | import Data.Function (on) |
| 19 | 24 | ||
| 20 | data Level = Lin Float | DB Float | 25 | import Refined |
| 21 | 26 | ||
| 22 | instance Num Level where | 27 | type Level' = Refined NonNegative Float |
| 23 | (+) = asFloat (+) | 28 | data 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 | ||
| 30 | asFloat :: (Float -> Float -> Float) -> Level -> Level -> Level | 30 | instance Num Level where |
| 31 | asFloat f (Lin x) (Lin y) = Lin $ f x y | 31 | (+) = fmap (either error id) . asFloat (+) |
| 32 | asFloat 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 | |||
| 38 | asFloat :: (Float -> Float -> Float) -> Level -> Level -> Either String Level | ||
| 39 | asFloat ((`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 | ||
| 34 | toFloat :: Level -> Float | 46 | toFloat :: Level -> Float |
| 35 | toFloat (Lin x) = x | 47 | toFloat = unrefine . toLin |
| 36 | toFloat (DB x) = x | ||
| 37 | 48 | ||
| 38 | withPrec :: (Num a, RealFrac a, HasResolution b) => a -> Fixed b | 49 | withPrec :: (Num a, RealFrac a, HasResolution b) => a -> Fixed b |
| 39 | withPrec x = withResolution $ \p' -> MkFixed $ toInteger $ round $ x * (fromInteger p') | 50 | withPrec x = withResolution $ \p' -> MkFixed $ toInteger $ round $ x * (fromInteger p') |
| @@ -44,39 +55,33 @@ withType f = f undefined | |||
| 44 | withResolution :: (HasResolution a) => (Integer -> f a) -> f a | 55 | withResolution :: (HasResolution a) => (Integer -> f a) -> f a |
| 45 | withResolution f = withType (f . resolution) | 56 | withResolution f = withType (f . resolution) |
| 46 | 57 | ||
| 47 | linToDb :: Float -> Float | 58 | linToDb :: Level' -> Float |
| 48 | linToDb x = 20 * (logBase 10 x) | 59 | linToDb (unrefine -> x) = 20 * (logBase 10 x) |
| 49 | 60 | ||
| 50 | dBToLin :: Float -> Float | 61 | dBToLin :: Float -> Level' |
| 51 | dBToLin x = 10 ** (0.05 * x) | 62 | dBToLin x = either error id . refine $ 10 ** (0.05 * x) |
| 52 | 63 | ||
| 53 | instance Show Level where | 64 | instance 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 | ||
| 59 | instance Read Level where | 68 | instance 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 | ||
| 72 | instance Eq Level where | 80 | instance 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 | ||
| 78 | instance Default Level where | 83 | instance Default Level where |
| 79 | def = Lin 0 | 84 | def = Lin $$(refineTH 0) |
| 80 | 85 | ||
| 81 | data Adjustment a = Set a | 86 | data 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 | ||
| 4 | name: trivmix | 4 | name: trivmix |
| 5 | version: 2.7.6 | 5 | version: 3.0.0 |
| 6 | -- synopsis: | 6 | -- synopsis: |
| 7 | -- description: | 7 | -- description: |
| 8 | license: PublicDomain | 8 | license: 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 | ||
| 26 | executable trivmix | 27 | executable 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 | }: |
| 6 | mkDerivation { | 6 | mkDerivation { |
| 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 | ||
| 3 | import Foreign.C.Types (CFloat(..)) | 5 | import Foreign.C.Types (CFloat(..)) |
| 4 | import qualified Sound.JACK as Jack | 6 | import qualified Sound.JACK as Jack |
| 5 | import qualified Sound.JACK.Audio as Audio | 7 | import qualified Sound.JACK.Audio as Audio |
| 6 | 8 | ||
| 7 | import Options.Applicative | 9 | import Options.Applicative hiding (str) |
| 8 | 10 | ||
| 9 | import Data.Maybe | 11 | import Data.Maybe |
| 10 | 12 | ||
| @@ -16,7 +18,7 @@ import System.Posix.Types | |||
| 16 | import System.Environment | 18 | import System.Environment |
| 17 | import System.Process | 19 | import System.Process |
| 18 | 20 | ||
| 19 | import System.Systemd.Daemon (notifyReady) | 21 | import System.Systemd.Daemon (notifyReady, notifyWatchdog) |
| 20 | 22 | ||
| 21 | import Control.Concurrent | 23 | import Control.Concurrent |
| 22 | import Control.Concurrent.MVar | 24 | import Control.Concurrent.MVar |
| @@ -38,15 +40,22 @@ import Data.Char | |||
| 38 | import Data.Function | 40 | import Data.Function |
| 39 | 41 | ||
| 40 | import Control.Monad | 42 | import Control.Monad |
| 43 | |||
| 44 | import Text.Heredoc (str) | ||
| 45 | import Refined | ||
| 41 | 46 | ||
| 42 | import Trivmix.Types | 47 | import Trivmix.Types |
| 43 | 48 | ||
| 49 | type Balance = Refined ZeroToOne Float | ||
| 50 | |||
| 44 | data Options = Options | 51 | data 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 | |||
| 122 | trivmix :: Options -> IO () | 151 | trivmix :: Options -> IO () |
| 123 | trivmix Options{..} = do | 152 | trivmix 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 | ||
| 150 | mix :: MVar Level -> CFloat -> IO CFloat | 183 | mix :: MVar Level -> CFloat -> IO CFloat |
| 151 | mix level input = do | 184 | mix 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 | ||
| 155 | handleFiles :: INotify -> MVar Level -> [FilePath] -> IO () | 189 | handleFiles :: (Read l, Show l, Eq l) => INotify -> MVar l -> [FilePath] -> IO () |
| 156 | handleFiles inotify level files = do | 190 | handleFiles 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 | ||
| 209 | readLevel :: Chan Level -> MVar Level -> FilePath -> MVar () -> IO () | 243 | readLevel :: (Read l, Show l, Eq l) => Chan l -> MVar l -> FilePath -> MVar () -> IO () |
| 210 | readLevel levelChan current file stderrLock = catch action handler | 244 | readLevel 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 | ||
| 231 | writeLevel :: FilePath -> MVar () -> Level -> IO () | 265 | writeLevel :: Show l => FilePath -> MVar () -> l -> IO () |
| 232 | writeLevel file stderrLock level = withFileLock file Exclusive $ const $ do | 266 | writeLevel 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 ++ "’" |
