sox 0.0.1 → 0.2.3.2
raw patch · 15 files changed
Files
- sox.cabal +26/−26
- src/Sound/Sox/Convert.hs +2/−2
- src/Sound/Sox/Format.hs +14/−7
- src/Sound/Sox/Frame.hs +31/−15
- src/Sound/Sox/Frame/Stereo.hs +3/−39
- src/Sound/Sox/Information.hs +97/−0
- src/Sound/Sox/Option/Format.hs +4/−0
- src/Sound/Sox/Play.hs +8/−2
- src/Sound/Sox/Private/Arguments.hs +6/−2
- src/Sound/Sox/Private/Information.hs +99/−0
- src/Sound/Sox/Private/Option.hs +6/−2
- src/Sound/Sox/Read.hs +2/−2
- src/Sound/Sox/Signal/List.hs +5/−4
- src/Sound/Sox/StorableUtility.hs +0/−43
- src/Sound/Sox/Write.hs +77/−21
sox.cabal view
@@ -1,11 +1,11 @@+Cabal-Version: 2.2 Name: sox-Version: 0.0.1-License: GPL+Version: 0.2.3.2+License: GPL-3.0-only License-File: LICENSE Author: Henning Thielemann <haskell@henning-thielemann.de> Maintainer: Henning Thielemann <haskell@henning-thielemann.de> Homepage: http://www.haskell.org/haskellwiki/Sox-Package-URL: http://code.haskell.org/~thielema/sox/ Category: Sound Synopsis: Play, write, read, convert audio signals using Sox Description:@@ -19,44 +19,44 @@ and in the path to the executables must be set. . In the past this was part of the synthesizer package.- .- There is some ancient code,- which allows interactive playback using the shell-pipe package on GHC-6.2,- where no runInteractiveProcess was available.-Tested-With: GHC==6.8.2-Cabal-Version: >=1.2+Tested-With: GHC==6.8.2, GHC==6.10.4, GHC==6.12.1+Tested-With: GHC==7.0.4, GHC==7.2.1, GHC==7.4.2, GHC==7.6.1 Build-Type: Simple Extra-Source-Files: Makefile unix/src/Sound/Sox/System.hs windows/src/Sound/Sox/System.hs -Flag splitBase- description: Choose the new smaller, split-up base package.+Source-Repository this+ Tag: 0.2.3.2+ Type: darcs+ Location: https://hub.darcs.net/thielema/sox/ -Flag executeShell- description: use ShellPipe package instead of process, preserved for historical reasons- default: False+Source-Repository head+ Type: darcs+ Location: https://hub.darcs.net/thielema/sox/ Library Build-Depends:- explicit-exception >= 0.1.3 && < 0.2,- transformers >= 0.0.1 && <0.2- If flag(splitBase)- Build-Depends:- base >= 2 && <4,- process >= 1.0 && < 1.1,- containers >= 0.1 && <0.3- Else- Build-Depends: base >= 1.0 && < 2+ sample-frame >= 0.0.1 && <0.1,+ explicit-exception >= 0.1.3 && < 0.3,+ -- that's the way to get compatibility between GHC 6.10 and 6.12+ extensible-exceptions >=0.1.1 && <0.2,+ transformers >=0.2 && <0.7,+ semigroups >=0.1 && <1.0,+ utility-ht >=0.0.5 && <0.1,+ process >=1.0 && <1.7,+ containers >=0.1 && <0.7,+ base >=2 && <5 Hs-Source-Dirs: src If os(windows) Hs-Source-Dirs: windows/src Else Hs-Source-Dirs: unix/src- Build-Depends: unix >=2.3 && <2.4+ Build-Depends: unix >=2.3 && <2.9 + Default-Language: Haskell98 GHC-Options: -Wall Exposed-Modules:@@ -69,10 +69,10 @@ Sound.Sox.Frame Sound.Sox.Frame.Stereo Sound.Sox.Signal.List+ Sound.Sox.Information Other-Modules: Sound.Sox.Private.Option Sound.Sox.Private.Arguments Sound.Sox.Private.Format--- Sound.Sox.Private.Information- Sound.Sox.StorableUtility+ Sound.Sox.Private.Information Sound.Sox.System
src/Sound/Sox/Convert.hs view
@@ -4,7 +4,7 @@ import qualified Sound.Sox.Private.Arguments as Args import Data.Monoid (mconcat, ) -import qualified System.Cmd as Cmd+import qualified System.Process as Cmd -- import qualified System.IO as IO import System.Exit (ExitCode, ) @@ -13,7 +13,7 @@ {- | > :load Sound.Sox.Convert >-> generic Option.none "test.aiff" Option.none "test.wav"+> simple Option.none "test.aiff" Option.none "test.wav" -} simple :: Option.T {- ^ source options -} ->
src/Sound/Sox/Format.hs view
@@ -24,15 +24,22 @@ iff8svx :: T iff8svx = custom "8svx" +muLaw :: T+muLaw = custom "ul" + signedByte, unsignedByte :: T-signedByte = custom "sb"-unsignedByte = custom "ub"+signedByte = custom "s8"+unsignedByte = custom "u8" signedWord, unsignedWord :: T-signedWord = custom "sw"-unsignedWord = custom "uw"+signedWord = custom "s16"+unsignedWord = custom "u16" -signedLong {-, unsignedLong-} :: T-signedLong = custom "sl"--- unsignedLong = custom "ul"+signedLong, unsignedLong :: T+signedLong = custom "s32"+unsignedLong = custom "u32"++ieeeSinglePrecision, ieeeDoublePrecision :: T+ieeeSinglePrecision = custom "f32"+ieeeDoublePrecision = custom "f64"
src/Sound/Sox/Frame.hs view
@@ -1,42 +1,58 @@-module Sound.Sox.Frame where+module Sound.Sox.Frame (C(..), Frame.withSignal, Frame.numberOfChannels, ) where +import qualified Sound.Frame as Frame+import qualified Sound.Frame.Stereo as Stereo+import qualified Sound.Frame.MuLaw as MuLaw+ import qualified Sound.Sox.Format as Format -import Data.Word (Word8, Word16, )+import Data.Word (Word8, Word16, Word32, ) import Data.Int (Int8, Int16, Int32, ) -class C y where- {- | The argument is not touched and can be undefined -}- numberOfChannels :: y -> Int+class Frame.C y => C y where format :: y -> Format.T + instance C Word8 where- numberOfChannels _ = 1 format _ = Format.unsignedByte instance C Int8 where- numberOfChannels _ = 1 format _ = Format.signedByte instance C Word16 where- numberOfChannels _ = 1 format _ = Format.unsignedWord instance C Int16 where- numberOfChannels _ = 1 format _ = Format.signedWord -{- instance C Word32 where- numberOfChannels _ = 1 format _ = Format.unsignedLong--} instance C Int32 where- numberOfChannels _ = 1 format _ = Format.signedLong +{- |+The floating point instances are dangerous,+because Storable Float may not use IEEE format+that sox uses according to its man page.+This is strange since sox uses the host's endianess for multi-byte values.+So, why does it not use the machine's floating point format?+-}+instance C Float where+ format _ = Format.ieeeSinglePrecision -withSignal :: (y -> a) -> (sig y -> a)-withSignal f _ = f undefined+instance C Double where+ format _ = Format.ieeeDoublePrecision++instance C MuLaw.T where+ format _ = Format.muLaw++{-+Shall we add instances for Float and Double?+Sox requires floating point numbers in IEEE formats,+but we cannot warrant that the Storable instances uses those formats.+-}++instance C a => C (Stereo.T a) where+ format y = format (Stereo.left y)
src/Sound/Sox/Frame/Stereo.hs view
@@ -1,42 +1,6 @@-{--This data type can be used as sample type for stereo signals.+{- |+We just re-export the type from sample-frame:Sound.Frame.Stereo. -} module Sound.Sox.Frame.Stereo (T, left, right, cons, ) where -import qualified Sound.Sox.Frame as Frame--import Control.Monad (liftM2, )--import Foreign.Storable (Storable (..), )-import qualified Sound.Sox.StorableUtility as Store---data T a = Cons {left, right :: !a}---{-# INLINE cons #-}-cons :: a -> a -> T a-cons = Cons--instance Functor T where- {-# INLINE fmap #-}- fmap f (Cons l r) = Cons (f l) (f r)----- cf. StorableInstances-instance (Storable a) => Storable (T a) where- sizeOf y = Store.sizeOfArray 2 $ left y- alignment y = alignment $ left y- peek ptr =- Store.run ptr $- liftM2 Cons- Store.peekNext- Store.peekNext- poke ptr (Cons l r) =- Store.run ptr $- do Store.pokeNext l- Store.pokeNext r--instance Frame.C a => Frame.C (T a) where- numberOfChannels y = 2 * Frame.numberOfChannels (left y)- format y = Frame.format (left y)+import Sound.Frame.Stereo
+ src/Sound/Sox/Information.hs view
@@ -0,0 +1,97 @@+{- |+This module calls the 'soxi' command+which is available since 'sox' version 14.++We have to call 'soxi' for every option.+However we hide this in our interface,+such that we could do more efficiently,+if 'soxi' supports multiple outputs in future.+-}+module Sound.Sox.Information (+ T(Cons),+ simple,+ format,+ sampleRate,+ numberOfChannels,+ length,+ bitsPerSample,+ get,+ exampleMulti,+ exampleSingle,+ ) where++import qualified Control.Monad.Trans.Reader as MR+import Control.Applicative (Applicative, pure, liftA3, (<*>), )+import Data.String.HT (trim, )+import Text.Read.HT (maybeRead, )++import qualified System.Process as Proc+import qualified System.IO as IO+import Control.Exception (bracket, )+-- import System.IO.Error (ioError, userError, )+-- import System.Exit (ExitCode, )++import Prelude hiding (length, )+++newtype T a =+ Cons (MR.ReaderT FilePath IO a)++instance Functor T where+ {-# INLINE fmap #-}+ fmap f (Cons m) = Cons (fmap f m)++instance Applicative T where+ {-# INLINE pure #-}+ {-# INLINE (<*>) #-}+ pure = Cons . pure+ (Cons f) <*> (Cons x) = Cons (f <*> x)+++simple :: Read a => (String -> Maybe a) -> String -> T a+simple rd option =+ Cons $ MR.ReaderT $ \fileName ->+ bracket+ (Proc.runInteractiveProcess "soxi"+ (option : fileName : [])+ Nothing Nothing)+ (\(input,output,err,proc) ->+ mapM_ IO.hClose [input, output, err] >>+ Proc.terminateProcess proc)+ (\(_,output,_,_) ->+ maybe+ (ioError (userError "soxi returned rubbish"))+ return .+ rd =<<+ IO.hGetContents output)++format :: T String+format = simple Just "-t"++simpleRead :: String -> T Int+simpleRead = simple (maybeRead . trim)++sampleRate :: T Int+sampleRate = simpleRead "-r"++numberOfChannels :: T Int+numberOfChannels = simpleRead "-c"++length :: T Int+length = simpleRead "-s"++bitsPerSample :: T Int+bitsPerSample = simpleRead "-b"++++get :: T a -> FilePath -> IO a+get (Cons act) = MR.runReaderT act++exampleMulti :: IO (String, Int, Int)+exampleMulti =+ get (liftA3 (,,) format sampleRate bitsPerSample) "test.aiff"++exampleSingle :: IO Int+exampleSingle =+ get sampleRate "test.aiff"
src/Sound/Sox/Option/Format.hs view
@@ -26,6 +26,10 @@ sampleRate r = single "-r" [show r] +bitsPerSample :: Int -> T+bitsPerSample b =+ single "-b" [show b]+ format :: Format.T -> T format fmt = single "-t" [Format.decons fmt]
src/Sound/Sox/Play.hs view
@@ -18,7 +18,7 @@ {- | > :load Sound.Sox.Play Sound.Sox.Signal.List >-> generic Sound.Sox.Signal.List.put Option.none 11025 (iterate (1000+) (0::Data.Int.Int16))+> simple Sound.Sox.Signal.List.put Option.none 11025 (iterate (1000+) (0::Data.Int.Int16)) -} simple :: (Frame.C y) =>@@ -50,7 +50,12 @@ IO ExitCode extended write srcOpts dstOpts sampleRate stream = bracket- (Proc.runInteractiveProcess "play"+ {-+ Formerly we called 'play' here.+ On Windows the SoX package does not install a 'play' command.+ However using the '-d' argument for the destination always works.+ -}+ (Proc.runInteractiveProcess "sox" (Args.decons $ mconcat $ OptPriv.toArguments (mconcat $@@ -62,6 +67,7 @@ []) : Args.pipe : OptPriv.toArguments dstOpts :+ Args.single "-d" : []) Nothing Nothing) (\(input,output,err,_proc) ->
src/Sound/Sox/Private/Arguments.hs view
@@ -1,12 +1,16 @@ module Sound.Sox.Private.Arguments where -import Data.Monoid (Monoid(..), )+import Data.Monoid (Monoid(mempty, mappend), )+import Data.Semigroup (Semigroup((<>)), ) newtype T = Cons {decons :: [String]} +instance Semigroup T where+ Cons x <> Cons y = Cons (x++y)+ instance Monoid T where mempty = Cons mempty- mappend (Cons x) (Cons y) = Cons (mappend x y)+ mappend = (<>) single :: String -> T single x = Cons [x]
+ src/Sound/Sox/Private/Information.hs view
@@ -0,0 +1,99 @@+{- |+This module calls the 'soxi' command+which is available since 'sox' version 14.++This module is very clever as it calls 'soxi' once+with one option per requested piece of information+and parses the results in a correctly typed tuple.+Unfortunately, 'soxi' does not work this way.+It accepts only one option per call.+-}+module Sound.Sox.Private.Information where++import Control.Monad.Trans.Writer (Writer, writer, runWriter, )+import Control.Monad.Trans.State (StateT(StateT), runStateT, )+import Control.Monad.Trans.Class (lift, )+import Control.Applicative (Applicative, pure, liftA3, (<*>), )+import Data.Functor.Compose (Compose(Compose), )+import Data.List.HT (viewL, )+import Data.String.HT (trim, )+import Text.Read.HT (maybeRead, )++import qualified System.Process as Proc+import qualified System.IO as IO+import Control.Exception (bracket, )+-- import System.IO.Error (ioError, userError, )+-- import System.Exit (ExitCode, )++import Prelude hiding (length, )+++{-+Cf. Synthesizer.Basic.Interpolation.PrefixReader.+-}+newtype T a =+ Cons (Compose (Writer [String]) (StateT [String] Maybe) a)++instance Functor T where+ {-# INLINE fmap #-}+ fmap f (Cons m) = Cons (fmap f m)++instance Applicative T where+ {-# INLINE pure #-}+ {-# INLINE (<*>) #-}+ pure = Cons . pure+ (Cons f) <*> (Cons x) = Cons (f <*> x)+++simple :: Read a => (String -> Maybe a) -> String -> T a+simple rd option =+ Cons $ Compose $ writer+ (lift . rd =<< StateT viewL, [option])++format :: T String+format = simple Just "-t"++simpleRead :: String -> T Int+simpleRead = simple (maybeRead . trim)++sampleRate :: T Int+sampleRate = simpleRead "-r"++numberOfChannels :: T Int+numberOfChannels = simpleRead "-c"++length :: T Int+length = simpleRead "-s"++bitsPerSample :: T Int+bitsPerSample = simpleRead "-b"++++get :: T a -> FilePath -> IO a+get (Cons (Compose w)) fileName =+ let (parser, opts) = runWriter w+ in bracket+ (Proc.runInteractiveProcess "soxi"+ (opts ++ fileName : [])+ Nothing Nothing)+ (\(input,output,err,proc) ->+ mapM_ IO.hClose [input, output, err] >>+ Proc.terminateProcess proc)+ (\(_,output,_,_) ->+ maybe+ (ioError (userError "soxi returned rubbish"))+ (\(x,str) ->+ if null str+ then return x+ else ioError (userError "soxi returned more lines than expected")) .+ runStateT parser . lines =<<+ IO.hGetContents output)++exampleMulti :: IO (String, Int, Int)+exampleMulti =+ get (liftA3 (,,) format sampleRate bitsPerSample) "test.aiff"++exampleSingle :: IO Int+exampleSingle =+ get sampleRate "test.aiff"
src/Sound/Sox/Private/Option.hs view
@@ -2,7 +2,8 @@ import qualified Sound.Sox.Private.Arguments as Args import qualified Data.Map as Map-import Data.Monoid (Monoid(..), )+import Data.Monoid (Monoid(mappend, mempty), )+import Data.Semigroup (Semigroup((<>)), ) {- | You can combine options using the 'Monoid' functions 'mappend' and 'mconcat'.@@ -11,9 +12,12 @@ -} newtype T = Cons {decons :: Map.Map String [String]} +instance Semigroup T where+ Cons x <> Cons y = Cons (Map.union x y)+ instance Monoid T where mempty = Cons mempty- mappend (Cons x) (Cons y) = Cons (Map.union x y)+ mappend = (<>) toArguments :: T -> Args.T toArguments =
src/Sound/Sox/Read.hs view
@@ -44,8 +44,8 @@ However there is @soxi@ for this purpose, which we may support in future. > :load Sound.Sox.Read Sound.Sox.Signal.List->-> open Option.none "test.aiff" >>= withHandle2 Sound.Sox.Signal.List.getContents >>= (\x -> print (Control.Monad.Exception.Asynchronous.result x :: [Data.Int.Int16]))+> :module + Control.Exception+> bracket (open Option.none "test.aiff") close $ \h -> withHandle2 Sound.Sox.Signal.List.getContents h >>= \x -> print (Control.Monad.Exception.Asynchronous.result x :: [Data.Int.Int16]) -} open :: (Frame.C y) =>
src/Sound/Sox/Signal/List.hs view
@@ -17,13 +17,13 @@ import qualified Control.Monad.Exception.Synchronous as Sync import qualified Control.Monad.Exception.Asynchronous as Async -import Control.Monad.Trans (lift, )+import Control.Monad.Trans.Class (lift, ) import Foreign.Storable (Storable (..), ) -import Foreign (Ptr, alloca, sizeOf, poke, peek, )+import Foreign (Ptr, alloca, ) import System.IO (withBinaryFile, IOMode(WriteMode,ReadMode), Handle, hPutBuf, hGetBuf, )-import Control.Exception (Exception, try, )+import Control.Exception.Extensible (SomeException, try, ) import Control.Monad (liftM) import System.IO.Unsafe (unsafeInterleaveIO, )@@ -51,7 +51,7 @@ deriving (Show, Eq, Enum) type IOReadException =- Either ReadException Exception+ Either ReadException SomeException withReadFile :: Storable a => FilePath ->@@ -66,6 +66,7 @@ getContents h = alloca $ \p ->+-- Async.eatNothingT $ liftM (\(Async.Exceptional (Just e) a) -> Async.Exceptional e a) $ Async.manySynchronousT unsafeInterleaveIO
− src/Sound/Sox/StorableUtility.hs
@@ -1,43 +0,0 @@-{- |-support for Storable instances--Cf. synthesizer package--}-module Sound.Sox.StorableUtility where--import Control.Monad.Trans.State (StateT, evalStateT, get, put, )-import Control.Monad.Trans (lift, )--import Foreign.Ptr (Ptr, castPtr, )-import Foreign.Storable (Storable(..))-import Foreign.Marshal.Array (advancePtr, )---{-# INLINE roundUp #-}-roundUp :: Int -> Int -> Int-roundUp m x = x + mod (-x) m--{-# INLINE sizeOfArray #-}-sizeOfArray :: Storable a => Int -> a -> Int-sizeOfArray n x =- n * roundUp (alignment x) (sizeOf x)--{-# INLINE pokeNext #-}-pokeNext :: (Storable a) => a -> StateT (Ptr a) IO ()-pokeNext x =- do ptr <- get- lift $ poke ptr x- put (ptr `advancePtr` 1)--- put (ptr `plusPtr` size x + div (- size x) (alignment x))--{-# INLINE peekNext #-}-peekNext :: (Storable a) => StateT (Ptr a) IO a-peekNext =- do ptr <- get- a <- lift $ peek ptr- put (ptr `advancePtr` 1)- return a--run :: Ptr (t a) -> StateT (Ptr a) IO c -> IO c-run ptr act =- evalStateT act (castPtr ptr)
src/Sound/Sox/Write.hs view
@@ -1,4 +1,4 @@-module Sound.Sox.Write where+module Sound.Sox.Write (simple, extended, manyExtended, ) where import qualified Sound.Sox.Frame as Frame import Sound.Sox.System (catchCtrlC, )@@ -8,6 +8,9 @@ import qualified Sound.Sox.Private.Arguments as Args import Data.Monoid (mconcat, ) +import qualified Data.Traversable as Trav+import qualified Data.Foldable as Fold+ import qualified System.Process as Proc import qualified System.IO as IO import Control.Exception (bracket, )@@ -21,7 +24,7 @@ > :load Sound.Sox.Write Sound.Sox.Signal.List >-> generic Sound.Sox.Signal.List.put Option.none "test.aiff" 11025 (take 100 $ iterate (1000+) (0::Data.Int.Int16))+> simple Sound.Sox.Signal.List.put Option.none "test.aiff" 11025 (take 100 $ iterate (1000+) (0::Data.Int.Int16)) -} simple :: (Frame.C y) =>@@ -53,28 +56,81 @@ {- ^ sample rate -} -> sig y -> IO ExitCode-extended write srcOpts dstOpts fileName sampleRate stream =+extended write srcOpts dstOpts fileName sampleRate signal = bracket- (Proc.runInteractiveProcess "sox"- (Args.decons $ mconcat $- OptPriv.toArguments- (mconcat $- srcOpts :- Option.numberOfChannels- (Frame.withSignal Frame.numberOfChannels stream) :- Option.sampleRate sampleRate :- Option.format (Frame.withSignal Frame.format stream) :- []) :- Args.pipe :- OptPriv.toArguments dstOpts :- Args.fileName fileName :- [])- Nothing Nothing)- (\(input,output,err,_proc) ->- mapM_ IO.hClose [input, output, err])+ (open srcOpts dstOpts fileName sampleRate signal)+ close (\(input,_,_,proc) -> catchCtrlC >>- write input stream >>+ write input signal >> return proc) -- get exit code, e.g. when options were wrong >>= Proc.waitForProcess++{- |+The traversable functor @f@ might be 'Maybe' or '[]'.+It allows you to write to many files simultaneously+and returns the exit codes of all writing processes.+-}+manyExtended ::+ (Frame.C y, Trav.Traversable f) =>+ (f IO.Handle -> sig y -> IO ())+ {- ^ Writer routine -+ e.g. 'Sound.Sox.Signal.List.put'+ or 'Data.StorableVector.hPut' -} ->+ Option.T+ {- ^ source options, usually none -} ->+ Option.T+ {- ^ target options -} ->+ f FilePath ->+ Int+ {- ^ sample rate -} ->+ sig y ->+ IO (f ExitCode)+manyExtended write srcOpts dstOpts fileNames sampleRate signal =+ bracket+ (Trav.traverse+ (\fileName -> open srcOpts dstOpts fileName sampleRate signal)+ fileNames)+ (Fold.traverse_ close)+ (\handles ->+ catchCtrlC >>+ write (fmap (\(input,_,_,_) -> input) handles) signal >>+ return (fmap (\(_,_,_,proc) -> proc) handles))+ -- get exit code, e.g. when options were wrong+ >>= Trav.traverse Proc.waitForProcess+++type Handle = (IO.Handle, IO.Handle, IO.Handle, Proc.ProcessHandle)++open ::+ (Frame.C y) =>+ Option.T+ {- ^ source options, usually none -} ->+ Option.T+ {- ^ target options -} ->+ FilePath ->+ Int+ {- ^ sample rate -} ->+ sig y ->+ IO Handle+open srcOpts dstOpts fileName sampleRate signal =+ Proc.runInteractiveProcess "sox"+ (Args.decons $ mconcat $+ OptPriv.toArguments+ (mconcat $+ srcOpts :+ Option.numberOfChannels+ (Frame.withSignal Frame.numberOfChannels signal) :+ Option.sampleRate sampleRate :+ Option.format (Frame.withSignal Frame.format signal) :+ []) :+ Args.pipe :+ OptPriv.toArguments dstOpts :+ Args.fileName fileName :+ [])+ Nothing Nothing++close :: Handle -> IO ()+close (input,output,err,_proc) =+ mapM_ IO.hClose [input, output, err]