{- |
This program creates a number of input ports
according to a list of given port names.
It captures the audio data from these ports
and writes the raw audio data in an interleaved way
to the file @captured.f32@.
You may convert it to an audio format with header information using @sox@.

Example:
> $ capture left right
> $ sox -c 2 -r 44100 captured.f32 captured.wav
-}
module Main where

import qualified Sound.JACK as Jack
import qualified Sound.JACK.Audio as JA

import qualified Sound.JACK.Exception as JackExc
import Sound.JACK (Process, Client, Port)

import qualified Control.Monad.Exception.Synchronous as Sync
import qualified Control.Monad.Trans.Cont as MC
import qualified Control.Monad.Trans.Class as Trans
import Data.Foldable (forM_, )

import Foreign.Storable (sizeOf, peek, )
import Foreign.Ptr (nullPtr, )
import Foreign.C.Error (eOK, )

import qualified System.Environment as Env
import qualified System.Console.GetOpt as GetOpt

import qualified System.IO as IO
import Data.Array.Base (getNumElements, )
import Data.Array.Storable
           (newArray_, readArray, writeArray, withStorableArray, )


main :: IO ()
main = do
    name <- Env.getProgName
    args <- Env.getArgs
    case args of
        [] ->
            putStrLn $
            GetOpt.usageInfo ("Usage: " ++ name ++ " JACK-PORT-NAME...") []
        _ -> capture name args

capture :: String -> [String] -> IO ()
capture name portNames =
    IO.withBinaryFile "captured.f32" IO.WriteMode $ \st ->
        Jack.handleExceptions $ flip MC.runContT return $ do
            client <- MC.ContT $ Jack.withClientDefault name
            inputs <- mapM (MC.ContT . Jack.withPort client) portNames
            Trans.lift $ setProcess st client inputs
            Trans.lift $ Jack.withActivation client $ Trans.lift $ do
                putStrLn $ "started " ++ name ++ "..."
                Jack.waitForBreak

setProcess ::
    (JackExc.ThrowsErrno e) =>
    IO.Handle ->
    Client ->
    [Port JA.Sample Jack.Input] ->
    Sync.ExceptionalT e IO ()
setProcess st client input =
    flip (Jack.setProcess client) nullPtr =<<
    (Trans.lift $ Jack.makeProcess $
     wrapFun st input)

wrapFun ::
    IO.Handle ->
    [Port JA.Sample Jack.Input] ->
    Process a
wrapFun st inputs nframes _args = do
    inArrs <- mapM (flip JA.getBufferArray nframes) inputs
    let (a,b) = Jack.nframesBounds nframes
        numChannels = length inputs
    output <- newArray_ ((a,0), (b, numChannels - 1))
    forM_ (Jack.nframesIndices nframes) $ \i ->
        forM_ (zip inArrs [0..]) $ \(inArr,c) ->
            writeArray output (i,c) =<< readArray inArr i
    withStorableArray output $ \p -> do
        n <- getNumElements output
        dummy <- peek p
        IO.hPutBuf st p $ sizeOf dummy * n
    return eOK
