jack-0.5: src/Sound/JACK.hs
{-
JACK bindings for Haskell
Copyright (C) 2007 Soenke Hahn
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-}
{-|
The Jack module defines types and functions that allows you to
use the JACK Audio Connection Kit.
-}
module Sound.JACK
(
CFloat,
CUInt,
Client,
Input,
Output,
newClient,
newClientSimple,
newInput,
newOutput,
activate,
clientClose,
getPorts,
connect,
setProcessMono,
setProcessStereo,
mainMono,
mainStereo,
)
where
import qualified Sound.JACK.FFI as JackFFI
import Sound.JACK.FFI (flagsToWord, wordToFlags)
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 Control.Concurrent (MVar, putMVar, newEmptyMVar, takeMVar, threadDelay)
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)
-- | Handles of Jack clients
newtype Client = Client (Ptr ())
-- | Handles of Jack input ports
newtype Input = Input (Ptr ())
-- | Handles of Jack output ports
newtype Output = Output (Ptr ())
-- | Constructs a new Jack client.
newClient :: 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)
-- | Creates a new JACK client with the "default" server
newClientSimple :: String -- ^ name of the client
-> IO Client
newClientSimple name = newClient "default" 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]))
failStatus :: CULong -> IO ()
failStatus errCode =
fail ("jack_client_open returned a nullPointer. Returned errorcodes: "
++ show (wordToFlags errCode :: [JackFFI.Status]))
-- | 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
-- | 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
-- | activates the given Jack client
activate :: Client -> IO ()
activate client = do
JackFFI.activate $ getClient client
return ()
-- | 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)
getClient :: Client -> Ptr ()
getClient (Client x) = x
getInput :: Input -> Ptr ()
getInput (Input x) = x
getOutput :: Output -> Ptr ()
getOutput (Output x) = x
-- | 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
connect :: Client -> String -> String -> IO()
connect client outport inport = do
outCString <- newCString outport
inCString <- newCString inport
JackFFI.connect (getClient client) outCString inCString
return ()
quit :: MVar () -> Client -> [Input] -> [Output] -> IO ()
quit mvar client ins outs = do
putStrLn "quitting..."
clientClose client ins outs
threadDelay 1000000
putMVar mvar ()
mainMono :: (CFloat -> IO CFloat) -> IO ()
mainMono fun = do
name <- getProgName
client <- newClientSimple name
input <- newInput client "input"
output <- newOutput client "output"
setProcessMono client input fun output
activate client
putStrLn $ "started " ++ name ++ "..."
mvar <- newEmptyMVar
installHandler keyboardSignal (Catch (quit mvar client [input] [output])) Nothing
takeMVar mvar
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 -- ???
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))
-- Stereo
mainStereo :: ((CFloat, CFloat) -> IO (CFloat, CFloat)) -> IO ()
mainStereo fun = do
name <- getProgName
client <- newClientSimple name
inputLeft <- newInput client "inputLeft"
inputRight <- newInput client "inputRight"
outputLeft <- newOutput client "outputLeft"
outputRight <- newOutput client "outputRight"
seq (map fun (replicate 100 (0, 0))) (return ())
setProcessStereo client inputLeft inputRight fun outputLeft outputRight
activate client
putStrLn $ "started " ++ name ++ "..."
mvar <- newEmptyMVar
installHandler
keyboardSignal
(Catch (quit mvar client [inputLeft, inputRight] [outputLeft, outputRight]))
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
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'
-- 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
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)