jack 0.5 → 0.6
raw patch · 13 files changed
+1220/−352 lines, 13 filesdep +arraydep +bytestringdep +enumsetdep ~basesetup-changednew-component:exe:impulse-trainnew-component:exe:midimon
Dependencies added: array, bytestring, enumset, explicit-exception, midi, transformers, unix
Dependency ranges changed: base
Files
- Setup.hs +0/−27
- Setup.lhs +3/−0
- examples/Amplify.hs +6/−4
- examples/ImpulseTrain.hs +35/−0
- examples/Midimon.hs +19/−0
- jack.cabal +91/−26
- src/Sound/JACK.hs +292/−244
- src/Sound/JACK/Audio.hs +152/−0
- src/Sound/JACK/Exception.hs +110/−0
- src/Sound/JACK/FFI.hs +113/−51
- src/Sound/JACK/FFI/MIDI.chs +147/−0
- src/Sound/JACK/MIDI.hs +156/−0
- src/Sound/JACK/Private.hs +96/−0
− Setup.hs
@@ -1,27 +0,0 @@--import Distribution.Simple-import Distribution.Simple.Utils-import Directory-import Monad-import System--neededHeaders = ["jack/jack.h", "st.h"]--main = do- args <- getArgs- when ((head args) == "configure") (checkHeaders neededHeaders)- defaultMainWithHooks defaultUserHooks---checkHeaders ll = do- mapM checkHeader ll- return ()--includeDirs = ["/usr/local/include", "/usr/include"]--checkHeader name = do- putStr ("Searching for " ++ name ++ "...")- bools <- mapM (\ p -> doesFileExist (p ++ "/" ++ name)) includeDirs- when (not $ or bools) (fail ("ERROR: " ++ name ++ " not found"))- putStrLn "found"-
+ Setup.lhs view
@@ -0,0 +1,3 @@+#! /usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
examples/Amplify.hs view
@@ -1,11 +1,13 @@-{-# options -O2 #-} module Main where import Foreign.C.Types (CFloat)-import qualified Sound.JACK as Jack+import qualified Sound.JACK.Audio as Audio main :: IO ()-main = Jack.mainStereo foo+main = Audio.mainStereo foo foo :: (CFloat, CFloat) -> IO (CFloat, CFloat)-foo (a, b) = return (a, b)+foo (a, b) = return (a * fac, b * fac)++fac :: CFloat+fac = 2
+ examples/ImpulseTrain.hs view
@@ -0,0 +1,35 @@+module Main where++import qualified Sound.JACK.Audio as Audio+import qualified Sound.JACK as JACK++import qualified Control.Monad.Trans.Class as Trans+import qualified Foreign.C.Error as E++import System.Environment (getProgName)++import Data.Array.Storable (writeArray, )+++main :: IO ()+main = do+ name <- getProgName+ JACK.handleExceptions $+ JACK.withClientDefault name $ \client ->+ JACK.withPort client "output" $ \output -> do+ JACK.setProcess client =<<+ (Trans.lift $ JACK.mkProcess $ process output)+ JACK.withActivation client $ Trans.lift $ do+ putStrLn $ "started " ++ name ++ "..."+ JACK.waitForBreak++process ::+ Audio.Port JACK.Output -> JACK.Process+process output nframes _buffer = do+ outArr <- Audio.getBufferArray output nframes+ case JACK.nframesIndices nframes of+ [] -> return ()+ zero:rest -> do+ writeArray outArr zero 1+ mapM_ (\i -> writeArray outArr i 0) rest+ return E.eOK
+ examples/Midimon.hs view
@@ -0,0 +1,19 @@+module Main where++import qualified Sound.JACK.MIDI as MIDI+import Sound.JACK (NFrames(NFrames), )++import qualified Sound.MIDI.Message as Msg+++main :: IO ()+main = MIDI.main foo++foo :: NFrames -> (NFrames, Msg.T) -> IO (NFrames, Msg.T)+foo (NFrames cycleStart) (tf@(NFrames t), e) = do+ putStrLn $ "Time: " ++ show (cycleStart + t) ++ " " +++ case e of+ Msg.Channel b -> "MidiMsg.Channel " ++ show b+ Msg.System _ -> "MidiMsg.System ..."+ return (tf, e)+
jack.cabal view
@@ -1,33 +1,98 @@ Name: jack-Version: 0.5+Version: 0.6 License: GPL License-File: LICENSE-Author: Soenke Hahn+Author: Soenke Hahn, Henning Thielemann Maintainer: Henning Thielemann <haskell@henning-thielemann.de>-Homepage: http://open-projects.net/~shahn/index.php?seite=code-Repository: http://darcs.haskell.org/jack/-Category: Sound--- base package version needed for unsafeForeignPtrToStorableArray,--- base-2.1.1 is shipped with GHC-6.6--- in base-1.0 (GHC-6.4) this function is missing-Build-Type: Custom-Build-Depends: base>=2.1 Synopsis: Bindings for the JACK Audio Connection Kit Description: Very basic bindings for the JACK Audio Connection Kit-Hs-Source-Dirs: src-Exposed-Modules: Sound.JACK-Other-Modules: Sound.JACK.FFI-Tested-With: GHC==6.6-ghc-options: -Wall -threaded-Extensions: ForeignFunctionInterface-Extra-Libraries: jack-Includes: jack/jack.h-Include-Dirs: /usr/local/include, /usr/include-Extra-Source-Files: README, INSTALL+Homepage: http://www.haskell.org/haskellwiki/JACK+Category: Sound+Build-Type: Simple+Cabal-Version: >=1.6+Tested-With: GHC == 6.8.2, GHC == 6.10.4, GHC == 6.12.3, GHC == 7.0.2, GHC == 7.2.1+Extra-Source-Files:+ README+ INSTALL -Executable: amplify-GHC-Options: -Wall -threaded-Extensions: ForeignFunctionInterface-Extra-Libraries: jack-Hs-Source-Dirs: src, .-Main-Is: examples/Amplify.hs+Source-Repository head+ type: darcs+ location: http://code.haskell.org/jack/++Source-Repository this+ type: darcs+ location: http://code.haskell.org/jack/+ tag: 0.6++Flag buildExamples+ description: Build example executables+ default: False++Library+ Build-Depends:+ midi >=0.1.5.2 && <0.2,+ bytestring >=0.9.1.4 && <0.10,+ explicit-exception >=0.1.5 && <0.2,+ transformers >=0.2 && <0.3,+ enumset >=0.0 && <0.1,+ array >=0.2 && <0.4,+ unix >=2.3 && <2.6,+ base >=4.0 && <5.0+ Exposed-Modules:+ Sound.JACK+ Sound.JACK.Audio+ Sound.JACK.MIDI+ Sound.JACK.Exception+ Other-Modules:+ Sound.JACK.Private+ Sound.JACK.FFI+ Sound.JACK.FFI.MIDI+ Build-Tools: c2hs>=0.15.1+ Hs-Source-Dirs: src+ GHC-Options: -threaded -Wall -fwarn-tabs -fwarn-incomplete-record-updates+ If impl(ghc >= 6.12)+ GHC-Options: -fwarn-unused-do-bind+ If os(darwin)+ CC-Options: -U__BLOCKS__++ Extensions: ForeignFunctionInterface+ -- use extra-libraries, if pkg-config fails+ -- http://hackage.haskell.org/trac/hackage/ticket/170+ -- Extra-Libraries: jack+ --+ -- There seem to be two lines of JACK:+ -- JACK1 as used by Kubuntu, e.g. jack-0.118.0+ -- JACK2 as used by Suse, e.g. jack-1.9.0+ -- Thus I just omit the jack version here+ PkgConfig-depends: jack+ Includes: jack/jack.h, jack/midiport.h++Executable amplify+ If !flag(buildExamples)+ Buildable: False+ GHC-Options: -threaded -Wall -fwarn-tabs -fwarn-incomplete-record-updates+ Extensions: ForeignFunctionInterface+ Extra-Libraries: jack+ Other-Modules: Sound.JACK.FFI.MIDI+ Hs-Source-Dirs: src, .+ Main-Is: examples/Amplify.hs++Executable impulse-train+ If !flag(buildExamples)+ Buildable: False+ GHC-Options: -threaded -Wall -fwarn-tabs -fwarn-incomplete-record-updates+ Extensions: ForeignFunctionInterface+ Extra-Libraries: jack+ Other-Modules: Sound.JACK.FFI.MIDI+ Hs-Source-Dirs: src, .+ Main-Is: examples/ImpulseTrain.hs++Executable midimon+ If !flag(buildExamples)+ Buildable: False+ GHC-Options: -threaded -Wall -fwarn-tabs -fwarn-incomplete-record-updates+ Extensions: ForeignFunctionInterface+ Extra-Libraries: jack+ Other-Modules: Sound.JACK.FFI.MIDI+ Hs-Source-Dirs: src, .+ Main-Is: examples/Midimon.hs
src/Sound/JACK.hs view
@@ -1,5 +1,7 @@+{-# LANGUAGE ForeignFunctionInterface #-} {- JACK bindings for Haskell+ Copyright (C) 2011 Henning Thielemann Copyright (C) 2007 Soenke Hahn This program is free software; you can redistribute it and/or modify@@ -24,314 +26,360 @@ -} module Sound.JACK- (- CFloat,- CUInt,-+ ( -- * general stuff Client,- Input,- Output, newClient,- newClientSimple,- newInput,- newOutput,- activate,+ newClientDefault,+ disposeClient,+ withClient,+ withClientDefault, clientClose, getPorts,++ activate,+ deactivate,+ withActivation,++ PortType,+ Direction, Input, Output,++ Port,+ newPort,+ disposePort,+ withPort,++ PortSet,+ setOfPort,+ setOfPorts,++ Process, connect,- setProcessMono,- setProcessStereo,+ mkProcess,+ setProcess,+ JackFFI.CallbackArg, - mainMono,- mainStereo,- )+ getSampleRate,++ JackFFI.NFrames(JackFFI.NFrames),+ nframesIndices, nframesBounds,+ quit, waitForBreakAndClose,+ waitForBreak,++ -- * Exceptions+ handleExceptions,+) where +import Sound.JACK.Private++import qualified Sound.JACK.Exception as JackExc import qualified Sound.JACK.FFI as JackFFI-import Sound.JACK.FFI (flagsToWord, wordToFlags)+import Sound.JACK.FFI (Process, nframesIndices, nframesBounds, ) -import Foreign (newForeignPtr_, malloc, peek, peekArray0)-import Foreign.Ptr (Ptr, FunPtr, nullPtr)-import Foreign.C.String (newCString, peekCString)-import Foreign.C.Types (CUInt, CInt, CChar, CFloat, CULong)+import qualified Control.Monad.Exception.Synchronous as Sync+import qualified Control.Monad.Trans.Class as Trans++import qualified Foreign.Marshal.Array as Array+import qualified Foreign.C.String as CString+import Foreign.Storable (Storable, peek, )+import Foreign.Ptr (Ptr, FunPtr, nullPtr, castPtr, )+import Foreign.C.String (peekCString, )+import Foreign.C.Error (Errno(Errno), )+import qualified Data.EnumSet as ES+ import Control.Concurrent (MVar, putMVar, newEmptyMVar, takeMVar, threadDelay) +import qualified System.IO as IO import System.Posix.Signals (installHandler, keyboardSignal, Handler(Catch))-import System.Environment (getProgName)-import Control.Monad (when)-import System.Random hiding (split)-import Data.Array.Storable- (StorableArray, Ix(range, index, inRange), readArray, writeArray,- unsafeForeignPtrToStorableArray)+import Data.Monoid (Monoid, mempty, mappend, ) --- | Handles of Jack clients-newtype Client = Client (Ptr ())++class Direction dir where+ dirToFlags :: dir -> JackFFI.PortFlagSet+ -- | Handles of Jack input ports-newtype Input = Input (Ptr ())+data Input = Input++instance Direction Input where+ dirToFlags ~Input = JackFFI.portIsInput+ -- | Handles of Jack output ports-newtype Output = Output (Ptr ())+data Output = Output +instance Direction Output where+ dirToFlags ~Output = JackFFI.portIsOutput+++ -- | Constructs a new Jack client.-newClient :: String -- ^ name of the JACK server+newClient ::+ (JackExc.ThrowsStatus e) =>+ String -- ^ name of the JACK server -> String -- ^ name of the client- -> IO Client-newClient server name = do- cserverS <- newCString server- cclientS <- newCString name- status <- malloc- let opt = flagsToWord [JackFFI.ServerName, JackFFI.NoStartServer]- client <- JackFFI.client_open cclientS opt status cserverS- checkStatus client status- return (Client client)+ -> Sync.ExceptionalT e IO Client+newClient server name =+ withCString server $ \cserverS ->+ withCString name $ \cclientS ->+ alloca $ \status -> do+ let opt = ES.fromEnums [JackFFI.ServerName, JackFFI.NoStartServer]+ client <- Trans.lift $ JackFFI.client_open cclientS opt status cserverS+ _ <- checkStatus client status+ return (Client client) -- | Creates a new JACK client with the "default" server-newClientSimple :: String -- ^ name of the client- -> IO Client-newClientSimple name = newClient "default" name+newClientDefault ::+ (JackExc.ThrowsStatus e) =>+ String -- ^ name of the client+ -> Sync.ExceptionalT e IO Client+newClientDefault name = newClient defaultServer name -checkStatus :: Ptr a -> Ptr CULong -> IO ()-checkStatus c s = do- errCode <- peek s- when (c == nullPtr) (failStatus errCode)- when (errCode /= 0)- (putStrLn $ "warning: " ++ show (wordToFlags errCode :: [JackFFI.Status]))+defaultServer :: String+defaultServer = "default" -failStatus :: CULong -> IO ()-failStatus errCode =- fail ("jack_client_open returned a nullPointer. Returned errorcodes: "- ++ show (wordToFlags errCode :: [JackFFI.Status]))+disposeClient ::+ (JackExc.ThrowsErrno e) =>+ Client -> Sync.ExceptionalT e IO ()+disposeClient client =+ liftErrno $ JackFFI.client_close (getClient client) --- | creates a new input port for the given client-newInput :: Client -- ^ Jack client- -> String -- ^ name of the input port- -> IO Input-newInput (Client client) portName = do- cstring <- newCString portName- audio_type <- newCString "32 bit float mono audio"--- putStrLn ("register..." ++ show (client, cstring, audio_type, 1, 0))- ret <- JackFFI.port_register client cstring audio_type 1 0--- putStrLn "register..."- return $ Input ret+{- |+Run a block of code with a newly allocated client.+Do not use the client outside the block.+-}+withClient ::+ (JackExc.ThrowsStatus e) =>+ String -- ^ name of the JACK server+ -> String -- ^ name of the client+ -> (Client -> Sync.ExceptionalT e IO a)+ -> Sync.ExceptionalT e IO a+withClient server name =+ bracket+ (newClient server name)+ (Trans.lift . fmap (const ()) .+ JackFFI.client_close . getClient) --- | creates a new output port-newOutput :: Client -- ^ Jack client- -> String -- ^ name of the output port- -> IO Output-newOutput (Client client) portName = do- cstring <- newCString portName- audio_type <- newCString "32 bit float mono audio"- ret <- JackFFI.port_register client cstring audio_type 2 0- return $ Output ret+withClientDefault ::+ (JackExc.ThrowsStatus e) =>+ String -- ^ name of the client+ -> (Client -> Sync.ExceptionalT e IO a)+ -> Sync.ExceptionalT e IO a+withClientDefault =+ withClient defaultServer --- | activates the given Jack client-activate :: Client -> IO ()-activate client = do- JackFFI.activate $ getClient client- return ()+checkStatus ::+ (JackExc.ThrowsStatus e) =>+ Ptr a+ -> Ptr JackFFI.StatusSet+ -> Sync.ExceptionalT e IO JackFFI.StatusSet+checkStatus c s = do+ errCode <- Trans.lift $ peek s+ Sync.assertT (JackExc.status errCode) (c /= nullPtr)+ return errCode --- | closes the given Jack client without causing any trouble (hopefully)-clientClose :: Client -> [Input] -> [Output] -> IO ()-clientClose client inports outports = do- mapM (JackFFI.port_unregister (getClient client)) (map getInput inports)- mapM (JackFFI.port_unregister (getClient client)) (map getOutput outports)- JackFFI.deactivate (getClient client)- JackFFI.client_close (getClient client)- return () -type Process = CUInt -> Ptr (CChar) -> IO (CInt)-foreign import ccall "wrapper" mkProcess :: Process -> IO (FunPtr Process)+newPortByType ::+ (PortType typ, Direction dir,+ JackExc.ThrowsPortRegister e) =>+ JackFFI.PortFlagSet -> Client -> String ->+ Sync.ExceptionalT e IO (Port typ dir)+newPortByType flags (Client client) portName =+ let aux ::+ (PortType typ, Direction dir,+ JackExc.ThrowsPortRegister e) =>+ typ -> dir -> Sync.ExceptionalT e IO (Port typ dir)+ aux portType dir =+ withPortName portName $ \cPortName ->+ withCString (portTypeToCString portType) $ \pType -> do+ -- putStrLn ("register..." ++ show (client, cstring, pType, inout, 0))+ port <- Trans.lift $+ JackFFI.port_register client cPortName pType (dirToFlags dir ES..|. flags) 0+ Sync.assertT JackExc.portRegister (port/=nullPtr)+ return $ Port port+ in aux undefined undefined +{- |+Better use 'withPort' that also handles freeing the port.+-}+newPort ::+ (PortType typ, Direction dir,+ JackExc.ThrowsPortRegister e) =>+ Client -- ^ Jack client+ -> String -- ^ name of the input port+ -> Sync.ExceptionalT e IO (Port typ dir)+newPort client name =+ newPortByType ES.empty client name -getClient :: Client -> Ptr ()-getClient (Client x) = x -getInput :: Input -> Ptr ()-getInput (Input x) = x+disposePort ::+ (PortType typ, Direction dir,+ JackExc.ThrowsErrno e) =>+ Client -> Port typ dir -> Sync.ExceptionalT e IO ()+disposePort client =+ liftErrno . JackFFI.port_unregister (getClient client) . getPort -getOutput :: Output -> Ptr ()-getOutput (Output x) = x+{- |+Creates a new port for the given client and delete it after usage.+The port manages audio or MIDI data in input or output direction+depending on the Port type.+Usually the required port type can be inferred from following actions+that use that port. --- | returns the names of all existing ports of the given Jack client-getPorts :: Client -- ^ the Jack client- -> IO [String] -- ^ the names as a list of strings-getPorts client = do- empty <- newCString ""- strArray <- JackFFI.get_ports (getClient client) empty empty 0- peekArray0 nullPtr strArray >>= mapM peekCString+Do not use the port outside the enclosed block.+-}+withPort ::+ (PortType typ, Direction dir,+ JackExc.ThrowsPortRegister e,+ JackExc.ThrowsErrno e) =>+ Client -- ^ Jack client+ -> String -- ^ name of the input port+ -> (Port typ dir -> Sync.ExceptionalT e IO a)+ -> Sync.ExceptionalT e IO a+withPort client name =+ bracket (newPort client name) (disposePort client) -connect :: Client -> String -> String -> IO()-connect client outport inport = do- outCString <- newCString outport- inCString <- newCString inport- JackFFI.connect (getClient client) outCString inCString- return () +-- | activates the given Jack client+activate ::+ (JackExc.ThrowsErrno e) =>+ Client -> Sync.ExceptionalT e IO ()+activate client =+ liftErrno $ JackFFI.activate $ getClient client -quit :: MVar () -> Client -> [Input] -> [Output] -> IO ()-quit mvar client ins outs = do- putStrLn "quitting..."- clientClose client ins outs- threadDelay 1000000- putMVar mvar ()+deactivate ::+ (JackExc.ThrowsErrno e) =>+ Client -> Sync.ExceptionalT e IO ()+deactivate client =+ liftErrno $ JackFFI.deactivate $ getClient client +withActivation ::+ (JackExc.ThrowsErrno e) =>+ Client ->+ Sync.ExceptionalT e IO () ->+ Sync.ExceptionalT e IO ()+withActivation client act =+ bracket (activate client) (\() -> deactivate client) (\() -> act) +-- | closes the given Jack client without causing any trouble (hopefully)+clientClose ::+ (JackExc.ThrowsErrno e) =>+ Client -> PortSet ->+ Sync.ExceptionalT e IO ()+clientClose client (PortSet ports) = do+ mapM_ (liftErrno . JackFFI.port_unregister (getClient client)) ports+ deactivate client+ disposeClient client -mainMono :: (CFloat -> IO CFloat) -> IO ()-mainMono fun = do- name <- getProgName- client <- newClientSimple name+foreign import ccall "wrapper" mkProcess :: Process -> IO (FunPtr Process) - input <- newInput client "input"- output <- newOutput client "output" +getClient :: Client -> Ptr JackFFI.Client+getClient (Client x) = x - setProcessMono client input fun output+getPort :: Port typ dir -> Ptr (JackFFI.Port typ)+getPort (Port x) = x - activate client- putStrLn $ "started " ++ name ++ "..."+withPortName ::+ String -> (JackFFI.PortName -> Sync.ExceptionalT e IO a) ->+ Sync.ExceptionalT e IO a+withPortName name f =+ withCString name $ f . JackFFI.PortName - mvar <- newEmptyMVar- installHandler keyboardSignal (Catch (quit mvar client [input] [output])) Nothing- takeMVar mvar+-- | returns the names of all existing ports of the given Jack client+getPorts :: Client -- ^ the Jack client+ -> IO [String] -- ^ the names as a list of strings+getPorts client =+ CString.withCString "" $ \empty -> do+ mapM peekCString+ =<< Array.peekArray0 nullPtr+ =<< JackFFI.get_ports (getClient client) empty empty 0 -setProcessMono ::- Client -> Input -> (CFloat -> IO CFloat) -> Output -> IO CInt-setProcessMono client input fun output = do- procPtr <- mkProcess $ wrapMonoFun input fun output- JackFFI.set_process_callback (getClient client) procPtr nullPtr -wrapMonoFun :: Input -> (CFloat -> IO CFloat) -> Output- -> (CUInt -> Ptr CChar -> IO CInt) -- what JACK expects-wrapMonoFun input fun output nframes _args = do- inArr <- getBufferArray (getInput input) nframes- outArr <- getBufferArray (getOutput output) nframes- mapM (applyToArraysMono inArr fun outArr) [0..(nframes - 1)]- return 0 -- ???+connect ::+ (JackExc.ThrowsErrno e) =>+ Client -> String -> String ->+ Sync.ExceptionalT e IO ()+connect client outport inport =+ withPortName outport $ \ outCString ->+ withPortName inport $ \ inCString ->+ liftErrno $ JackFFI.connect (getClient client) outCString inCString -applyToArraysMono :: StorableArray CUInt CFloat -> (CFloat -> IO CFloat)- -> StorableArray CUInt CFloat- -> CUInt -> IO ()-applyToArraysMono inArr fun outArr i =- readArray inArr i >>= fun >>= writeArray outArr i -getBufferArray :: Ptr () -> CUInt -> IO (StorableArray CUInt CFloat)-getBufferArray bptr nframes = do- ptr <- JackFFI.port_get_buffer bptr nframes- fptr <- newForeignPtr_ ptr- unsafeForeignPtrToStorableArray fptr (0, (nframes - 1))+{- |+A collection of mixed types of ports.+It is mainly needed for freeing all allocated ports.+-}+newtype PortSet = PortSet [Ptr (JackFFI.Port ())] --- Stereo-mainStereo :: ((CFloat, CFloat) -> IO (CFloat, CFloat)) -> IO ()-mainStereo fun = do- name <- getProgName- client <- newClientSimple name+instance Monoid PortSet where+ mempty = PortSet mempty+ mappend (PortSet a) (PortSet b) = PortSet (mappend a b) - inputLeft <- newInput client "inputLeft"- inputRight <- newInput client "inputRight"- outputLeft <- newOutput client "outputLeft"- outputRight <- newOutput client "outputRight"+setOfPort ::+ (PortType typ, Direction dir) =>+ Port typ dir -> PortSet+setOfPort =+ PortSet . (:[]) . castPtr . getPort - seq (map fun (replicate 100 (0, 0))) (return ())+setOfPorts ::+ (PortType typ, Direction dir) =>+ [Port typ dir] -> PortSet+setOfPorts =+ PortSet . map (castPtr . getPort) - setProcessStereo client inputLeft inputRight fun outputLeft outputRight - activate client- putStrLn $ "started " ++ name ++ "..." +quit ::+ MVar () -> Client -> PortSet -> IO ()+quit mvar client ports = do+ putStrLn "quitting..."+ Sync.resolveT+ (\(Errno e) ->+ IO.hPutStrLn IO.stderr $+ "exception when closing with errno: " ++ show e)+ (clientClose client ports)+ threadDelay 1000000+ putMVar mvar ()++waitForBreakAndClose ::+ Client -> PortSet -> IO ()+waitForBreakAndClose client ports = do mvar <- newEmptyMVar- installHandler- keyboardSignal- (Catch (quit mvar client [inputLeft, inputRight] [outputLeft, outputRight]))+ _ <- installHandler keyboardSignal+ (Catch $ quit mvar client ports) Nothing takeMVar mvar --setProcessStereo ::- Client -> Input -> Input ->- ((CFloat, CFloat) -> IO (CFloat, CFloat)) ->- Output -> Output -> IO CInt-setProcessStereo client inputLeft inputRight fun outputLeft outputRight = do- procPtr <- mkProcess $ wrapStereoFun inputLeft inputRight fun outputLeft outputRight- JackFFI.set_process_callback (getClient client) procPtr nullPtr+waitForBreak :: IO ()+waitForBreak = do+ mvar <- newEmptyMVar+ _ <- installHandler keyboardSignal+ (Catch $ do+ putStrLn "quitting..."+ threadDelay 1000000+ putMVar mvar ())+ Nothing+ takeMVar mvar -wrapStereoFun :: Input -> Input- -> ((CFloat, CFloat) -> IO (CFloat, CFloat))- -> Output -> Output- -> (CUInt -> Ptr CChar -> IO CInt) -- what JACK expects-wrapStereoFun iL iR fun oL oR nframes _args = do- inLArr <- getBufferArray (getInput iL) nframes- inRArr <- getBufferArray (getInput iR) nframes- outLArr <- getBufferArray (getOutput oL) nframes- outRArr <- getBufferArray (getOutput oR) nframes- mapM (applyToArraysStereo inLArr inRArr fun outLArr outRArr) [0..(nframes - 1)]- return 0 -- ???--applyToArraysStereo :: StorableArray CUInt CFloat- -> StorableArray CUInt CFloat- -> ((CFloat, CFloat) -> IO (CFloat, CFloat))- -> StorableArray CUInt CFloat- -> StorableArray CUInt CFloat- -> CUInt -> IO ()-applyToArraysStereo iL iR fun oL oR i = do- l <- readArray iL i- r <- readArray iR i- (l', r') <- fun (l, r)- writeArray oL i l'- writeArray oR i r'-+handleExceptions ::+ Sync.ExceptionalT JackExc.All IO () ->+ IO ()+handleExceptions =+ Sync.resolveT $ IO.hPutStrLn IO.stderr . JackExc.toStringWithHead --- Useful instances-instance Ix CUInt where- range (a, b) = [a..b]- index (start, _end) i = fromEnum (i - start)- inRange (start, end) i = start <= i && i <= end+setProcess ::+ (JackExc.ThrowsErrno e) =>+ Client ->+ FunPtr Process ->+ Sync.ExceptionalT e IO ()+setProcess client procPtr =+ liftErrno $ JackFFI.set_process_callback (getClient client) procPtr nullPtr -instance Random CFloat where- random g = randomIvalDouble (0::Double,1) realToFrac g- randomR (a,b) g = randomIvalDouble (realToFrac a, realToFrac b) realToFrac g--randomIvalInteger :: (RandomGen g, Num a) => (Integer, Integer) -> g -> (a, g)-randomIvalInteger (l,h) rng =- if l > h- then randomIvalInteger (h,l) rng- else case (f n 1 rng) of (v, rng') -> (fromInteger (l + v `mod` k), rng')- where- k = h - l + 1- b = 2147483561- n = iLogBase b k-- f 0 acc g = (acc, g)- f nn acc g = - let- (x,g') = next g- in- f (nn-1) (fromIntegral x + acc * b) g'--randomIvalDouble :: (RandomGen g, Fractional a) =>- (Double, Double) -> (Double -> a) -> g -> (a, g)-randomIvalDouble (l,h) fromDouble rng =- if l > h- then randomIvalDouble (h,l) fromDouble rng- else- case (randomIvalInteger (toInteger (minBound::Int), toInteger (maxBound::Int)) rng) of- (x, rng') -> - let- scaled_x = - fromDouble ((l+h)/2) + - fromDouble ((h-l) / realToFrac intRange) *- fromIntegral (x::Int)- in- (scaled_x, rng')--intRange :: Integer-intRange = toInteger (maxBound::Int) - toInteger (minBound::Int)--iLogBase :: Integer -> Integer -> Integer-iLogBase b i = if i < b then 1 else 1 + iLogBase b (i `div` b)-+getSampleRate :: Client -> IO Int+getSampleRate (Client ptr) =+ fmap fromIntegral $ JackFFI.get_sample_rate ptr
+ src/Sound/JACK/Audio.hs view
@@ -0,0 +1,152 @@+module Sound.JACK.Audio (+ Sample, Port, withPort,++ setProcessMono,+ setProcessStereo,++ getBufferArray,++ mainMono,+ mainStereo,+ ) where++import qualified Sound.JACK.Private as Priv+import qualified Sound.JACK as Jack+import Sound.JACK.Private (Client, )+import Sound.JACK (Direction, Input, Output, )++import qualified Sound.JACK.Exception as JackExc+import Sound.JACK.FFI (NFrames, nframesIndices, nframesBounds, Process, )++import qualified Control.Monad.Exception.Synchronous as Sync+import qualified Control.Monad.Trans.Class as Trans++import Foreign.ForeignPtr (newForeignPtr_, )+import Foreign.C.Error (eOK, )+import Foreign.C.Types (CFloat, )++import System.Environment (getProgName)++import Data.Array.Storable+ (StorableArray, readArray, writeArray,+ unsafeForeignPtrToStorableArray)+++type Sample = CFloat++type Port = Priv.Port Sample+++withPort ::+ (Direction dir,+ JackExc.ThrowsPortRegister e,+ JackExc.ThrowsErrno e) =>+ Client -- ^ Jack client+ -> String -- ^ name of the input port+ -> (Port dir -> Sync.ExceptionalT e IO a)+ -> Sync.ExceptionalT e IO a+withPort = Jack.withPort+++getBufferArray ::+ (Direction dir) =>+ Port dir -> NFrames -> IO (StorableArray NFrames Sample)+getBufferArray port nframes = do+ flip unsafeForeignPtrToStorableArray (nframesBounds nframes)+ =<< newForeignPtr_+ =<< Priv.portGetBuffer port nframes+++mainMono :: (Sample -> IO Sample) -> IO ()+mainMono fun = do+ name <- getProgName+ Jack.handleExceptions $+ Jack.withClientDefault name $ \client ->+ Jack.withPort client "input" $ \input ->+ Jack.withPort client "output" $ \output -> do+ setProcessMono client input fun output+ Jack.withActivation client $ Trans.lift $ do+ putStrLn $ "started " ++ name ++ "..."+ Jack.waitForBreak++setProcessMono ::+ (JackExc.ThrowsErrno e) =>+ Client ->+ Port Input -> (Sample -> IO Sample) ->+ Port Output ->+ Sync.ExceptionalT e IO ()+setProcessMono client input fun output =+ Jack.setProcess client =<<+ (Trans.lift $ Jack.mkProcess $ wrapMonoFun input fun output)++wrapMonoFun ::+ Port Input -> (Sample -> IO Sample) ->+ Port Output -> Process+wrapMonoFun input fun output nframes _args = do+ inArr <- getBufferArray input nframes+ outArr <- getBufferArray output nframes+ mapM_ (applyToArraysMono inArr fun outArr) (nframesIndices nframes)+ return eOK++applyToArraysMono ::+ StorableArray NFrames Sample -> (Sample -> IO Sample)+ -> StorableArray NFrames Sample+ -> NFrames -> IO ()+applyToArraysMono inArr fun outArr i =+ readArray inArr i >>= fun >>= writeArray outArr i++mainStereo :: ((Sample, Sample) -> IO (Sample, Sample)) -> IO ()+mainStereo fun = do+ name <- getProgName+ Jack.handleExceptions $+ Jack.withClientDefault name $ \client ->+ Jack.withPort client "inputLeft" $ \inputLeft ->+ Jack.withPort client "inputRight" $ \inputRight ->+ Jack.withPort client "outputLeft" $ \outputLeft ->+ Jack.withPort client "outputRight" $ \outputRight -> do+ setProcessStereo client+ inputLeft inputRight fun+ outputLeft outputRight+ Jack.withActivation client $ Trans.lift $ do+ putStrLn $ "started " ++ name ++ "..."+ Jack.waitForBreak+++setProcessStereo ::+ (JackExc.ThrowsErrno e) =>+ Client ->+ Port Input -> Port Input ->+ ((Sample, Sample) -> IO (Sample, Sample)) ->+ Port Output -> Port Output ->+ Sync.ExceptionalT e IO ()+setProcessStereo client inputLeft inputRight fun outputLeft outputRight =+ Jack.setProcess client =<<+ (Trans.lift $ Jack.mkProcess $+ wrapStereoFun inputLeft inputRight fun outputLeft outputRight)+++wrapStereoFun ::+ Port Input -> Port Input+ -> ((Sample, Sample) -> IO (Sample, Sample))+ -> Port Output -> Port Output+ -> Process+wrapStereoFun iL iR fun oL oR nframes _args = do+ inLArr <- getBufferArray iL nframes+ inRArr <- getBufferArray iR nframes+ outLArr <- getBufferArray oL nframes+ outRArr <- getBufferArray oR nframes+ mapM_ (applyToArraysStereo inLArr inRArr fun outLArr outRArr) (nframesIndices nframes)+ return eOK++applyToArraysStereo :: StorableArray NFrames Sample+ -> StorableArray NFrames Sample+ -> ((Sample, Sample) -> IO (Sample, Sample))+ -> StorableArray NFrames Sample+ -> StorableArray NFrames Sample+ -> NFrames -> IO ()+applyToArraysStereo iL iR fun oL oR i = do+ l <- readArray iL i+ r <- readArray iR i+ (l', r') <- fun (l, r)+ writeArray oL i l'+ writeArray oR i r'
+ src/Sound/JACK/Exception.hs view
@@ -0,0 +1,110 @@+module Sound.JACK.Exception (+ ToString(toString), toStringWithHead,+ All,+ ThrowsStatus (status), Status(..),+ ThrowsPortRegister (portRegister), PortRegister(..),+ ThrowsErrno (errno), Errno(..),+ ) where++import qualified Sound.JACK.FFI as JackFFI++import qualified Foreign.C.Error as FE+import qualified Data.EnumSet as ES+import Data.Bits (Bits, )++++type All = Status (PortRegister (FE.Errno))++toStringWithHead ::+ All -> String+toStringWithHead e =+ "JACK exception: " ++ toString e++class ToString e where+ toString :: e -> String++instance (Bits w, Enum a, Show a) => ToString (ES.T w a) where+ toString = show . ES.toEnums+++class ThrowsStatus e where+ status :: JackFFI.StatusSet -> e++data Status e =+ Status JackFFI.StatusSet+ | NoStatus e++instance ToString e => ToString (Status e) where+ toString e0 =+ case e0 of+ Status st -> toString st+ NoStatus e1 -> toString e1+++instance ThrowsStatus (Status e) where+ status = Status++instance ThrowsPortRegister e => ThrowsPortRegister (Status e) where+ portRegister = NoStatus portRegister++instance ThrowsErrno e => ThrowsErrno (Status e) where+ errno = NoStatus . errno++{-+instance ThrowsStatus (ES.T w a) where+ status = id+-}+++class ThrowsPortRegister e where+ portRegister :: e++data PortRegister e =+ PortRegister+ | NoPortRegister e++instance ToString e => ToString (PortRegister e) where+ toString e0 =+ case e0 of+ PortRegister -> "could not register port"+ NoPortRegister e1 -> toString e1++instance ThrowsStatus e => ThrowsStatus (PortRegister e) where+ status = NoPortRegister . status++instance ThrowsPortRegister (PortRegister e) where+ portRegister = PortRegister++instance ThrowsErrno e => ThrowsErrno (PortRegister e) where+ errno = NoPortRegister . errno++++class ThrowsErrno e where+ errno :: FE.Errno -> e++data Errno e =+ Errno FE.Errno+ | NoErrno e++instance ToString e => ToString (Errno e) where+ toString e0 =+ case e0 of+ Errno en -> toString en+ NoErrno e1 -> toString e1++instance ToString FE.Errno where+ toString (FE.Errno en) = "error code " ++ show en++instance ThrowsStatus e => ThrowsStatus (Errno e) where+ status = NoErrno . status++instance ThrowsPortRegister e => ThrowsPortRegister (Errno e) where+ portRegister = NoErrno portRegister++instance ThrowsErrno (Errno e) where+ errno = Errno++instance ThrowsErrno FE.Errno where+ errno = id
src/Sound/JACK/FFI.hs view
@@ -1,7 +1,9 @@-{-# OPTIONS -fffi #-}+-- -*-haskell-*-+{-# LANGUAGE ForeignFunctionInterface #-} {- JACK bindings for Haskell+ Copyright (C) 2011 Henning Thielemann Copyright (C) 2007 Soenke Hahn This program is free software; you can redistribute it and/or modify@@ -23,34 +25,45 @@ import Foreign.Ptr (Ptr, FunPtr) import Foreign.C.String (CString)-import Foreign.C.Types (CUInt, CInt, CChar, CFloat, CULong)--import Data.Bits (setBit, bit)+import Foreign.C.Error (Errno, )+import Foreign.C.Types (CUInt, CInt, CULong, ) -import Data.Array(Ix)+import qualified Data.EnumSet as ES+import Data.Word (Word, )+import Data.Ix (Ix(range, inRange, rangeSize, index))+import Data.Monoid (Monoid, mempty, mappend, ) +data Client = Client+data Port typ = Port foreign import ccall "static jack/jack.h jack_client_open"- client_open :: CString -> CULong -> Ptr CULong -> CString -> IO (Ptr ())+ client_open :: CString -> OpenOptionSet -> Ptr StatusSet -> CString -> IO (Ptr Client) +{-# DEPRECATED client_new "use client_open instead" #-} foreign import ccall "static jack/jack.h jack_client_new"- client_new :: CString -> IO (Ptr ())+ client_new :: CString -> IO (Ptr Client) +foreign import ccall "static jack/jack.h jack_get_sample_rate"+ get_sample_rate :: Ptr Client -> IO CInt ++type OpenOptionSet = ES.T CULong OpenOptions+ data OpenOptions = NoStartServer | UseExactName | ServerName deriving (Enum, Eq, Ord, Show, Ix) -wordNullOption, wordNoStartServer, wordUseExactName, wordServerName :: CULong-wordNullOption = 0-wordNoStartServer = flagToWord NoStartServer-wordUseExactName = flagToWord UseExactName-wordServerName = flagToWord ServerName+wordNullOption, wordNoStartServer, wordUseExactName, wordServerName :: OpenOptionSet+wordNullOption = ES.empty+wordNoStartServer = ES.fromEnum NoStartServer+wordUseExactName = ES.fromEnum UseExactName+wordServerName = ES.fromEnum ServerName +type StatusSet = ES.T CULong Status data Status = Failure@@ -68,64 +81,113 @@ wordFailure, wordInvalidOption, wordNameNotUnique, wordServerStarted, wordServerFailed, wordServerError, wordNoSuchClient, wordLoadFailure,- wordInitFailure, wordShmFailure, wordVersionError :: CULong-wordFailure = flagToWord Failure-wordInvalidOption = flagToWord InvalidOption-wordNameNotUnique = flagToWord NameNotUnique-wordServerStarted = flagToWord ServerStarted-wordServerFailed = flagToWord ServerFailed-wordServerError = flagToWord ServerError-wordNoSuchClient = flagToWord NoSuchClient-wordLoadFailure = flagToWord LoadFailure-wordInitFailure = flagToWord InitFailure-wordShmFailure = flagToWord ShmFailure-wordVersionError = flagToWord VersionError+ wordInitFailure, wordShmFailure, wordVersionError :: StatusSet+wordFailure = ES.fromEnum Failure+wordInvalidOption = ES.fromEnum InvalidOption+wordNameNotUnique = ES.fromEnum NameNotUnique+wordServerStarted = ES.fromEnum ServerStarted+wordServerFailed = ES.fromEnum ServerFailed+wordServerError = ES.fromEnum ServerError+wordNoSuchClient = ES.fromEnum NoSuchClient+wordLoadFailure = ES.fromEnum LoadFailure+wordInitFailure = ES.fromEnum InitFailure+wordShmFailure = ES.fromEnum ShmFailure+wordVersionError = ES.fromEnum VersionError +type PortFlagSet = ES.T CULong PortFlag +data PortFlag =+ PortIsInput+ | PortIsOutput+ | PortIsPhysical+ | PortCanMonitor+ | PortIsTerminal+ deriving (Enum, Eq, Ord, Show, Ix)+++newtype PortName = PortName {deconsPortName :: CString}++portIsInput, portIsOutput :: PortFlagSet+portIsInput = ES.fromEnum PortIsInput+portIsOutput = ES.fromEnum PortIsOutput++ foreign import ccall "static jack/jack.h jack_port_register"- port_register :: Ptr () -> Ptr (CChar) -> Ptr (CChar) -> - CULong -> CULong -> IO (Ptr ())+ port_register :: Ptr Client -> PortName -> CString ->+ PortFlagSet -> CULong -> IO (Ptr (Port a)) ++-- | represents absolute frame time+newtype NFrames = NFrames CUInt+ deriving (Show, Eq, Ord)++nframesToWord :: NFrames -> Word+nframesToWord (NFrames n) = fromIntegral n++instance Ix NFrames where+ range (a,b) =+ map (NFrames . fromIntegral) $+ range (nframesToWord a, nframesToWord b)+ index (a,b) i =+ index (nframesToWord a, nframesToWord b) (nframesToWord i)+ inRange (a,b) i =+ inRange (nframesToWord a, nframesToWord b) (nframesToWord i)+ rangeSize (a,b) =+ rangeSize (nframesToWord a, nframesToWord b)++instance Monoid NFrames where+ mempty = NFrames 0+ mappend (NFrames x) (NFrames y) = NFrames (x+y)+++nframesIndices :: NFrames -> [NFrames]+nframesIndices (NFrames n) =+ take (fromIntegral n) $ map NFrames $ iterate (1+) 0++nframesBounds :: NFrames -> (NFrames,NFrames)+nframesBounds (NFrames n) =+ (NFrames 0, NFrames $ n - 1)+++data CallbackArg = CallbackArg+++type Process = NFrames -> Ptr CallbackArg -> IO Errno+ foreign import ccall "static jack/jack.h jack_set_process_callback"- set_process_callback :: - Ptr () -> FunPtr (CUInt -> Ptr (CChar) -> IO (CInt)) -> Ptr (CChar) -> IO (CInt)+ set_process_callback ::+ Ptr Client -> FunPtr Process -> Ptr CallbackArg -> IO Errno +foreign import ccall "static jack/jack.h jack_last_frame_time"+ last_frame_time :: Ptr Client -> IO NFrames++ foreign import ccall "static jack/jack.h jack_port_get_buffer"- port_get_buffer :: Ptr () -> CUInt -> IO (Ptr CFloat)+ port_get_buffer :: Ptr (Port a) -> NFrames -> IO (Ptr a) foreign import ccall "static jack/jack.h jack_get_buffer_size"- get_buffer_size :: Ptr () -> IO (CUInt)+ get_buffer_size :: Ptr Client -> IO (CUInt) foreign import ccall "static jack/jack.h jack_activate"- activate :: Ptr () -> IO (CInt)+ activate :: Ptr Client -> IO Errno foreign import ccall "static jack/jack.h jack_client_close"- client_close :: Ptr () -> IO (CInt)+ client_close :: Ptr Client -> IO Errno foreign import ccall "static jack/jack.h jack_get_ports"- get_ports :: Ptr () -> Ptr (CChar) -> Ptr (CChar) -> CULong -> IO (Ptr (Ptr (CChar)))+ get_ports :: Ptr Client -> CString -> CString -> CULong -> IO (Ptr CString)+-- get_ports :: Ptr Client -> CString -> CString -> CULong -> IO (Ptr PortName) +-- may return eEXIST foreign import ccall "static jack/jack.h jack_connect"- connect :: Ptr () -> Ptr (CChar) -> Ptr (CChar) -> IO (CInt)+ connect :: Ptr Client -> PortName -> PortName -> IO Errno +foreign import ccall "static jack/jack.h jack_disconnect"+ disconnect :: Ptr Client -> PortName -> PortName -> IO Errno+ foreign import ccall "static jack/jack.h jack_port_unregister"- port_unregister :: Ptr () -> Ptr () -> IO (CInt)+ port_unregister :: Ptr Client -> Ptr (Port a) -> IO Errno foreign import ccall "static jack/jack.h jack_deactivate"- deactivate :: Ptr () -> IO (CInt)------ * Utility functions--flagToWord :: (Enum a) => a -> CULong-flagToWord = bit . fromEnum--flagsToWord :: (Enum a) => [a] -> CULong-flagsToWord = foldl setBit 0 . map fromEnum--wordToFlags :: (Enum a) => CULong -> [a]-wordToFlags =- map fst . filter (odd . snd) .- zip [toEnum 0 ..] . iterate (flip div 2)+ deactivate :: Ptr Client -> IO Errno
+ src/Sound/JACK/FFI/MIDI.chs view
@@ -0,0 +1,147 @@+{-# LANGUAGE ForeignFunctionInterface #-}++#include "jack/midiport.h"++module Sound.JACK.FFI.MIDI+ (+ EventBuffer(EventBuffer),++ RawEvent(RawEvent),+ time,+ buffer,++ toRawEventFunction,++ get_event_count,+ event_get,+ clear_buffer,+ event_reserve,+ event_write,++ -- internal+ withByteStringPtr,+ )++where++import Sound.JACK.FFI (NFrames(NFrames), )++import Foreign.Marshal.Array (copyArray, advancePtr, )+import Foreign.ForeignPtr (withForeignPtr, )+import Foreign.Ptr (Ptr, castPtr, )+import Foreign.Storable (Storable, peekByteOff, pokeByteOff, sizeOf, alignment, peek, poke)+import Foreign.C.Types (CULong, CUInt, CUChar, CSize)+import Foreign.C.Error (Errno, )+import Data.Word (Word8, )++import qualified Data.ByteString as B+import qualified Data.ByteString.Internal as BI+import qualified Data.ByteString.Lazy as BL+import qualified Sound.MIDI.Message as Msg+import qualified Sound.MIDI.Parser.Report as Report++import System.IO (hPutStrLn, stderr, )+++-- could also be an empty data declaration+data EventBuffer = EventBuffer++-- | Represents a raw JACK MIDI event+data RawEvent = RawEvent {+ time :: NFrames -- ^ Sample index at which event is valid (relative to cycle start)+ , buffer :: B.ByteString -- ^ Raw MIDI data+} deriving (Eq, Ord)++-- | Converts high level MIDI Event transformation functions into raw MIDI Event transformation functions+toRawEventFunction ::+ (NFrames -> (NFrames, Msg.T) -> IO (NFrames, Msg.T))+ -- ^ transforms Sound.MIDI.File.Event+ ->+ (NFrames -> RawEvent -> IO RawEvent)+ -- ^ transforms Sound.JACK.MIDI.RawEvent+toRawEventFunction fun cycleStart rawEvent =+ case Msg.maybeFromByteString $ BL.fromChunks [buffer rawEvent] of+ Report.Cons warnings result -> do+ mapM_ (hPutStrLn stderr) warnings+ case result of+ Left _ -> do+ putStrLn $ "Warning: Did not understand Event: " ++ show rawEvent+ return rawEvent+ Right e -> do+ (time_, event_) <- fun cycleStart (time rawEvent, e)+ return $ RawEvent time_ $ B.concat $+ BL.toChunks $ Msg.toByteString event_++instance Storable RawEvent where+ sizeOf _ = {# sizeof jack_midi_event_t #}++ alignment _ = alignment (undefined :: CUInt)++ peek pointer = do+ time_ <- {# get jack_midi_event_t-> time #} pointer+ size_ <- {# get jack_midi_event_t-> size #} pointer++ bufferPtr <- {# get jack_midi_event_t-> buffer #} pointer+ let sizeInt = fromIntegral size_+ buffer_ <-+ BI.create sizeInt $ \dest ->+ copyArray dest (castPtr bufferPtr) sizeInt++ return $ RawEvent (NFrames time_) buffer_++ {- |+ This implementation expects that port buffer pointer is already initialized.+ This is dangerous, but currently we do not know, how to do it better.+ -}+ poke pointer (RawEvent (NFrames time_) buffer_) = do+ {# set jack_midi_event_t-> time #} pointer time_+ {# set jack_midi_event_t-> size #} pointer (fromIntegral $ B.length buffer_)++ bufferPtr <- {# get jack_midi_event_t-> buffer #} pointer+ withByteStringPtr buffer_ $ \ptr len ->+ copyArray (castPtr bufferPtr) ptr len++ {# set jack_midi_event_t-> buffer #} pointer bufferPtr++withByteStringPtr :: B.ByteString -> (Ptr Word8 -> Int -> IO a) -> IO a+withByteStringPtr bstr act =+ case BI.toForeignPtr bstr of+ (fptr, start, len) ->+ withForeignPtr fptr $ \ptr ->+ act (advancePtr ptr start) len+++instance Show RawEvent where+ show rawEvent =+ "MIDIEvent @ " ++ show (time rawEvent) +++ "\t: " ++ showEvent (buffer rawEvent)++showEvent :: B.ByteString -> String+showEvent buffer_ =+ case Msg.maybeFromByteString $ BL.fromChunks [buffer_] of+ Report.Cons warnings result ->+ unlines warnings +++ case result of+ Left errMsg -> "Warning: " ++ errMsg+ Right e ->+ case e of+ Msg.Channel b -> "MidiMsg.Channel " ++ show b+ Msg.System _ -> "MidiMsg.System ..."++++foreign import ccall "static jack/midiport.h jack_midi_get_event_count"+ get_event_count :: Ptr EventBuffer -> IO NFrames++foreign import ccall "static jack/midiport.h jack_midi_event_get"+ event_get :: Ptr RawEvent -> Ptr EventBuffer -> NFrames -> IO Errno++foreign import ccall "static jack/midiport.h jack_midi_clear_buffer"+ clear_buffer :: Ptr EventBuffer -> IO ()++-- nullPtr may be mapped to eNOBUFS exception as in event_write+foreign import ccall "static jack/midiport.h jack_midi_event_reserve"+ event_reserve :: Ptr EventBuffer -> NFrames -> CSize -> IO (Ptr Word8)++foreign import ccall "static jack/midiport.h jack_midi_event_write"+ event_write :: Ptr EventBuffer -> NFrames -> Ptr Word8 -> CULong -> IO Errno
+ src/Sound/JACK/MIDI.hs view
@@ -0,0 +1,156 @@+module Sound.JACK.MIDI (+ RawEvent,+ rawEvent,+ rawEventTime,+ rawEventBuffer,+ toRawEventFunction,++ Port, withPort,++ setProcess,++ readRawEvents,+ writeRawEvent,++ main,+ mainRaw,+ ) where++import qualified Sound.JACK.Private as Priv+import qualified Sound.JACK as Jack+import Sound.JACK.Private (Client(Client), liftErrno, alloca, )+import Sound.JACK (Direction, Input, Output, )++import qualified Sound.JACK.Exception as JackExc+import qualified Sound.JACK.FFI as JackFFI+import qualified Sound.JACK.FFI.MIDI as JackMIDI+import Sound.JACK.FFI (NFrames, nframesIndices, Process, )+import Sound.JACK.FFI.MIDI (RawEvent, EventBuffer, time, buffer, toRawEventFunction, )++import qualified Data.ByteString as B++import qualified Sound.MIDI.Message as Msg++import qualified Control.Monad.Exception.Synchronous as Sync+import qualified Control.Monad.Trans.Class as Trans++import Foreign.Storable (Storable, peek, )+import Foreign.Ptr (Ptr, )+import Foreign.C.Error (eOK, )++import System.Environment (getProgName)+import Control.Monad (when, forM, )+++type Port = Priv.Port EventBuffer++withPort ::+ (Direction dir,+ JackExc.ThrowsPortRegister e,+ JackExc.ThrowsErrno e) =>+ Client -- ^ Jack client+ -> String -- ^ name of the input port+ -> (Port dir -> Sync.ExceptionalT e IO a)+ -> Sync.ExceptionalT e IO a+withPort = Jack.withPort+++{- |+Smart constructor for a raw MIDI event.+-}+rawEvent ::+ NFrames -- ^ Sample index at which event is valid (relative to cycle start)+ -> B.ByteString -- ^ Raw MIDI data+ -> RawEvent+rawEvent = JackMIDI.RawEvent++rawEventTime :: RawEvent -> NFrames+rawEventTime = time++rawEventBuffer :: RawEvent -> B.ByteString+rawEventBuffer = buffer+++-- | Creates an input and an output, and transforms all raw input events into raw output+-- events using the given function+mainRaw :: (NFrames -> RawEvent -> IO RawEvent) -- ^ transforms raw input to output events+ -> IO ()+mainRaw fun = do+ name <- getProgName+ Jack.handleExceptions $+ Jack.withClientDefault name $ \client ->+ Jack.withPort client "input" $ \input ->+ Jack.withPort client "output" $ \output -> do+ setProcess client input fun output+ Jack.withActivation client $ Trans.lift $ do+ putStrLn $ "started " ++ name ++ "..."+ Jack.waitForBreak++-- | Creates an input and an output, and transforms all input events into output+-- events using the given function+main :: (NFrames -> (NFrames, Msg.T) -> IO (NFrames, Msg.T)) -- ^ transforms input to output events+ -> IO ()+main fun = mainRaw (toRawEventFunction fun)+++-- | sets the process loop of the JACK Client+setProcess ::+ (JackExc.ThrowsErrno e) =>+ Client -- ^ the JACK Client, whose process loop will be set+ -> Port Input -- ^ where to get events from+ -> (NFrames -> RawEvent -> IO RawEvent)+ -- ^ transforms input to output events+ -> Port Output -- ^ where to put events+ -> Sync.ExceptionalT e IO ()+ -- ^ exception causing JACK to remove that client from the process() graph.+setProcess client input fun output =+ Jack.setProcess client =<<+ (Trans.lift $ Jack.mkProcess $ wrapFun client input fun output)++-- | reads all available MIDI Events on the given PortBuffer+readRawEvents ::+ (JackExc.ThrowsErrno e) =>+ Ptr EventBuffer -- ^ the PortBuffer to read from+ -> Sync.ExceptionalT e IO [RawEvent]+ -- ^ pointers to newly allocated events, must be freed later!+readRawEvents buffer_ = do+ nEvents <- Trans.lift $ JackMIDI.get_event_count buffer_+ forM (nframesIndices nEvents) $ \n ->+ alloca $ \eventPtr -> do+ liftErrno $ JackMIDI.event_get eventPtr buffer_ n+ Trans.lift $ peek eventPtr+ -- putStrLn $ "Got MIDI Event number " ++ (show n) ++ " with result " ++ (show result) ++ " at time " ++ (show $ time event) ++ " size: " ++ (show $ size event) ++ " buffer: " ++ (show $ buffer event)+++-- | writes a MIDI event to the PortBuffer of a MIDI output or throws eNOBUFS if buffer is full+writeRawEvent ::+ (JackExc.ThrowsErrno e) =>+ Ptr EventBuffer -- ^ the PortBuffer of the MIDI output to write to+ -> RawEvent -- ^ the RawEvent to write+ -> Sync.ExceptionalT e IO ()+writeRawEvent portBuffer event =+ liftErrno $ do+ JackMIDI.clear_buffer portBuffer+ JackMIDI.withByteStringPtr (buffer event) $ \ptr len ->+ JackMIDI.event_write portBuffer (time event)+ ptr (fromIntegral len)+ -- putStrLn $ "Writing MIDI Event: buffer: " ++ (show $ buffer event)++wrapFun ::+ Client ->+ Port Input -> (NFrames -> RawEvent -> IO RawEvent) ->+ Port Output -> Process+wrapFun (Client client) input fun output nframes _args = do+ inputPortBuffer <- Priv.portGetBuffer input nframes+ outputPortBuffer <- Priv.portGetBuffer output nframes+ lastCycle <- JackFFI.last_frame_time client+ Sync.resolveT return $ do+ inEvents <- readRawEvents inputPortBuffer+ -- when (not (null inEvents)) $ putStrLn $ "wrapFun: Got " ++ (show $ length inEvents) ++ " input events"+ outEvents <- mapM (Trans.lift . fun lastCycle) inEvents+ -- when (not (null outEvents)) $ putStrLn $ "wrapFun: Got " ++ (show $ length outEvents) ++ " output events"+ mapM_ (writeRawEvent outputPortBuffer) outEvents+ Trans.lift $ when (null outEvents) $+ JackMIDI.clear_buffer outputPortBuffer+ return eOK+
+ src/Sound/JACK/Private.hs view
@@ -0,0 +1,96 @@+module Sound.JACK.Private where++import qualified Sound.JACK.Exception as JackExc+import qualified Sound.JACK.FFI as JackFFI+import Sound.JACK.FFI.MIDI (EventBuffer, )+import Sound.JACK.FFI (NFrames, )++import qualified Control.Monad.Exception.Synchronous as Sync+import qualified Control.Exception as Exc++import qualified Foreign.Marshal.Alloc as Alloc+import qualified Foreign.C.String as CString+import Foreign.Storable (Storable, )+import Foreign.Ptr (Ptr, )+import Foreign.C.Types (CFloat, )+import Foreign.C.Error (Errno, eOK, )+++-- | Handles of Jack clients+newtype Client = Client (Ptr JackFFI.Client)+++-- | Jack Port Type+class PortType typ where+ portTypeToCString :: typ -> String++instance PortType CFloat where+ portTypeToCString _ = "32 bit float mono audio"++instance PortType EventBuffer where+ portTypeToCString _ = "8 bit raw midi"+++newtype Port typ dir = Port (Ptr (JackFFI.Port typ))+++portGetBuffer ::+ (PortType typ) =>+ Port typ dir -> NFrames -> IO (Ptr typ)+portGetBuffer (Port port) nframes =+ JackFFI.port_get_buffer port nframes++withCString ::+ String ->+ (CString.CString -> Sync.ExceptionalT e IO b) ->+ Sync.ExceptionalT e IO b+withCString str f =+ Sync.ExceptionalT $+ CString.withCString str (Sync.runExceptionalT . f)++alloca ::+ (Storable a) =>+ (Ptr a -> Sync.ExceptionalT e IO b) ->+ Sync.ExceptionalT e IO b+alloca f =+ Sync.ExceptionalT $+ Alloc.alloca (Sync.runExceptionalT . f)++{-+Mixing of explicit exceptions+with "unchecked" exceptions embedded in IO monad is really not nice.+We should switch to an exception-free IO monad+like explicit-exception:SIO.+-}+bracket ::+ Sync.ExceptionalT e IO a ->+ (a -> Sync.ExceptionalT e IO ()) ->+ (a -> Sync.ExceptionalT e IO b) ->+ Sync.ExceptionalT e IO b+bracket open close act =+ Sync.ExceptionalT $+ Exc.bracket+ (Sync.runExceptionalT open)+ {-+ Exception of 'close' cannot be maintained,+ since we can only propagate an exception+ or generate a new one.+ Generally exceptions in closing functions are a bad idea.+ -}+ (\r ->+ case r of+ Sync.Success a -> Sync.runExceptionalT $ close a+ Sync.Exception e -> return $ Sync.Exception e)+ (\r ->+ case r of+ Sync.Success a -> Sync.runExceptionalT $ act a+ Sync.Exception e -> return $ Sync.Exception e)+++liftErrno ::+ (JackExc.ThrowsErrno e) =>+ IO Errno -> Sync.ExceptionalT e IO ()+liftErrno =+ Sync.ExceptionalT .+ fmap (\err ->+ Sync.assert (JackExc.errno err) $ err == eOK)