packages feed

soxlib-0.0.2: src/Sound/SoxLib/FFI.hsc

{-# LANGUAGE ForeignFunctionInterface #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
module Sound.SoxLib.FFI where

#include <sox.h>
#include <Sound/SoxLib/Template.h>

import qualified Foreign.C.String as CStr
import qualified Foreign.C.Types as C
import qualified Foreign.C.Error as E
import Foreign.Storable (Storable, sizeOf, alignment, peek, peekByteOff, poke, pokeByteOff, )
import Foreign.Ptr (FunPtr, Ptr, nullPtr, plusPtr, )

import Control.Applicative (pure, (<*>), )

import Data.Maybe.HT (toMaybe, )
import Data.Maybe (fromMaybe, )

import qualified Data.Word as Word
-- import qualified Data.Int as Int
-- we must import it unqualified, because Hsc calls it that way
import Data.Word
import Data.Int

import qualified Prelude as P
import Prelude hiding (Bool, init, length, )



newtype IOType = IOType #{type lsx_io_type}
   deriving (Storable)

#{enum IOType, IOType
   , ioFile = lsx_io_file
   , ioPipe = lsx_io_pipe
   , ioURL = lsx_io_url
   }

instance Show IOType where
   show (IOType #{const lsx_io_file}) = "ioFile"
   show (IOType #{const lsx_io_pipe}) = "ioPipe"
   show (IOType #{const lsx_io_url }) = "ioURL"
   show (IOType n) = error $ "SoxLib.IOType.show: invalid number " ++ show n


data Format mode =
   Format {
      filename :: FilePath,
      signalInfo :: SignalInfo,
      encodingInfo :: EncodingInfo,
      filetype :: FileType,
      -- oob :: OOB,
      seekable :: P.Bool,
      olength :: Int,
      clips :: Int,
      soxErrno :: E.Errno,
      soxErrStr :: String,
      fp :: Ptr C.CFile,
      ioType :: IOType,
      tellOff :: C.CLong,
      dataStart :: C.CLong,
      -- handler :: FormatHandler,
      priv :: Ptr ()
   }

instance Mode mode => Storable (Format mode) where
   sizeOf _    = #{size sox_format_t}
   alignment _ = #{alignment sox_format_t}
   peek p =
      pure Format
         <*> (peekEmptyCString =<< #{peek sox_format_t, filename} p)
         <*> #{peek sox_format_t, signal} p
         <*> #{peek sox_format_t, encoding} p
         <*> (fmap FileType . peekEmptyCString =<<
              #{peek sox_format_t, filetype} p)
         -- <*> #{peek sox_format_t, oob} p
         <*> peekBool (#{ptr sox_format_t, seekable} p)
         <*> (#{peek_int sox_format_t, olength} p)
         <*> (#{peek_int sox_format_t, clips} p)
         <*> fmap E.Errno (#{peek sox_format_t, sox_errno} p)
         <*> (CStr.peekCStringLen (#{ptr sox_format_t, sox_errstr} p, 256))
         <*> #{peek sox_format_t, fp} p
         <*> #{peek sox_format_t, io_type} p
         <*> #{peek sox_format_t, tell_off} p
         <*> #{peek sox_format_t, data_start} p
         -- <*> #{peek sox_format_t, handler} p
         <*> #{peek sox_format_t, priv} p
   poke = error "SoxLib.Format.poke cannot be implemented because it requires temporary memory"


peekEmptyCString :: CStr.CString -> IO String
peekEmptyCString ptr =
   if ptr == nullPtr
     then return ""
     else CStr.peekCString ptr


class Mode mode where
   getModeChar :: Format mode -> C.CChar

data ReadMode  = ReadMode
data WriteMode = WriteMode

instance Mode ReadMode where
   getModeChar _ = CStr.castCharToCChar 'r'

instance Mode WriteMode where
   getModeChar _ = CStr.castCharToCChar 'w'


type Rate = Double

ignoreLength :: Int
ignoreLength = -1

data SignalInfo =
   SignalInfo {
      rate :: Maybe Rate,
      channels :: Maybe Int,
      precision :: Maybe Int,
      length :: Maybe Int,
      mult :: Maybe Double
   }
   deriving (Show)

defaultSignalInfo :: SignalInfo
defaultSignalInfo =
   SignalInfo Nothing Nothing Nothing Nothing Nothing

readSINumber :: (Num a, Eq a) => a -> Maybe a
readSINumber n =
   toMaybe (n /= #{const SOX_UNSPEC}) n

writeSINumber :: (Num a, Eq a) => Maybe a -> a
writeSINumber =
   fromMaybe (#{const SOX_UNSPEC})


instance Storable SignalInfo where
   sizeOf _    = #{size sox_signalinfo_t}
   alignment _ = #{alignment sox_signalinfo_t}
   peek p =
      pure SignalInfo
         <*> (fmap readSINumber $ peekDouble (#{ptr sox_signalinfo_t, rate} p))
         <*> (fmap readSINumber $ #{peek_int sox_signalinfo_t, channels} p)
         <*> (fmap readSINumber $ #{peek_int sox_signalinfo_t, precision} p)
         <*> (fmap readSINumber $ #{peek_int sox_signalinfo_t, length} p)
         <*> (do pd <- #{peek sox_signalinfo_t, mult} p
                 if pd == nullPtr
                   then return Nothing
                   else fmap Just $ peekDouble pd)
   poke p v =
      pokeDouble (#{ptr sox_signalinfo_t, rate} p) (writeSINumber $ rate v) >>
      #{poke sox_signalinfo_t, channels } p (writeSINumber $ channels  v) >>
      #{poke sox_signalinfo_t, precision} p (writeSINumber $ precision v) >>
      #{poke sox_signalinfo_t, length   } p (writeSINumber $ length    v) >>
      case mult v of
         Nothing -> #{poke sox_signalinfo_t, mult} p nullPtr
         Just _ ->
            error "SoxLib.SignalInfo.poke: Just-mult cannot simply be poked because it requires temporary memory"


newtype Option = Option #{type sox_option_t}
   deriving (Storable)

#{enum Option, Option
   , optionNo = SOX_OPTION_NO
   , optionYes = SOX_OPTION_YES
   , optionDefault = SOX_OPTION_DEFAULT
 }

instance Show Option where
   show (Option #{const SOX_OPTION_NO     }) = "optionNo"
   show (Option #{const SOX_OPTION_YES    }) = "optionYes"
   show (Option #{const SOX_OPTION_DEFAULT}) = "optionDefault"
   show (Option n) = error $ "SoxLib.Option.show: invalid number " ++ show n


data EncodingInfo =
   EncodingInfo {
      encoding :: Encoding,
      bitsPerSample :: Int,
      compression :: Double,

      reverseBytes, reverseNibbles, reverseBits :: Option,
      oppositeEndian :: P.Bool
   }
   deriving (Show)

instance Storable EncodingInfo where
   sizeOf _    = #{size sox_encodinginfo_t}
   alignment _ = #{alignment sox_encodinginfo_t}
   peek p =
      pure EncodingInfo
         <*> (#{peek sox_encodinginfo_t, encoding} p)
         <*> (#{peek_int sox_encodinginfo_t, bits_per_sample} p)
         <*> (peekDouble $ #{ptr sox_encodinginfo_t, compression} p)
         <*> (#{peek sox_encodinginfo_t, reverse_bytes} p)
         <*> (#{peek sox_encodinginfo_t, reverse_nibbles} p)
         <*> (#{peek sox_encodinginfo_t, reverse_bits} p)
         <*> (peekBool $ #{ptr sox_encodinginfo_t, opposite_endian} p)
   poke p v =
      #{poke     sox_encodinginfo_t, encoding       } p (encoding      v) >>
      #{poke_int sox_encodinginfo_t, bits_per_sample} p (bitsPerSample v) >>
      pokeDouble (#{ptr sox_encodinginfo_t, compression} p) (compression v) >>
      #{poke     sox_encodinginfo_t, reverse_bytes  } p (reverseBytes   v) >>
      #{poke     sox_encodinginfo_t, reverse_nibbles} p (reverseNibbles v) >>
      #{poke     sox_encodinginfo_t, reverse_bits   } p (reverseBits    v) >>
      pokeBool (#{ptr sox_encodinginfo_t, opposite_endian} p) (oppositeEndian v)


newtype Bool = Bool #{inttype sox_bool}
   deriving (Show, Eq, Storable)

#{enum Bool, Bool
   , false = sox_false
   , true = sox_true
   }

packBool :: P.Bool -> Bool
packBool False = false
packBool True = true

unpackBool :: Bool -> P.Bool
unpackBool x = x/=false


peekBool :: Ptr Bool -> IO P.Bool
peekBool p = fmap unpackBool $ peek p

pokeBool :: Ptr Bool -> P.Bool -> IO ()
pokeBool p b = poke p $ packBool b


peekDouble :: Ptr C.CDouble -> IO Double
peekDouble p = fmap realToFrac $ peek p

pokeDouble :: Ptr C.CDouble -> Double -> IO ()
pokeDouble p x = poke p $ realToFrac x


newtype Encoding = Encoding #{inttype sox_encoding_t}
   deriving (Storable)

instance Bounded Encoding where
   minBound = Encoding #{const SOX_ENCODING_UNKNOWN}
   maxBound = Encoding $ pred #{const SOX_ENCODINGS}

#{enum Encoding, Encoding
   , encodingUnknown    = SOX_ENCODING_UNKNOWN
   , encodingSign2      = SOX_ENCODING_SIGN2
   , encodingUnsigned   = SOX_ENCODING_UNSIGNED
   , encodingFloat      = SOX_ENCODING_FLOAT
   , encodingFloatText  = SOX_ENCODING_FLOAT_TEXT
   , encodingFlac       = SOX_ENCODING_FLAC
   , encodingHcom       = SOX_ENCODING_HCOM
   , encodingWavpack    = SOX_ENCODING_WAVPACK
   , encodingWavpackf   = SOX_ENCODING_WAVPACKF
   , encodingUlaw       = SOX_ENCODING_ULAW
   , encodingAlaw       = SOX_ENCODING_ALAW
   , encodingG721       = SOX_ENCODING_G721
   , encodingG723       = SOX_ENCODING_G723
   , encodingClADPCM    = SOX_ENCODING_CL_ADPCM
   , encodingClADPCM16  = SOX_ENCODING_CL_ADPCM16
   , encodingMsADPCM    = SOX_ENCODING_MS_ADPCM
   , encodingImaADPCM   = SOX_ENCODING_IMA_ADPCM
   , encodingOkiADPCM   = SOX_ENCODING_OKI_ADPCM
   , encodingDPCM       = SOX_ENCODING_DPCM
   , encodingDWVW       = SOX_ENCODING_DWVW
   , encodingDWVWN      = SOX_ENCODING_DWVWN
   , encodingGSM        = SOX_ENCODING_GSM
   , encodingMP3        = SOX_ENCODING_MP3
   , encodingVorbis     = SOX_ENCODING_VORBIS
   , encodingAmrWB      = SOX_ENCODING_AMR_WB
   , encodingAmrNB      = SOX_ENCODING_AMR_NB
   , encodingCVSD       = SOX_ENCODING_CVSD
   , encodingLPC10      = SOX_ENCODING_LPC10
}

instance Show Encoding where
   show (Encoding #{const SOX_ENCODING_UNKNOWN   }) = "encodingUnknown"
   show (Encoding #{const SOX_ENCODING_SIGN2     }) = "encodingSign2"
   show (Encoding #{const SOX_ENCODING_UNSIGNED  }) = "encodingUnsigned"
   show (Encoding #{const SOX_ENCODING_FLOAT     }) = "encodingFloat"
   show (Encoding #{const SOX_ENCODING_FLOAT_TEXT}) = "encodingFloatText"
   show (Encoding #{const SOX_ENCODING_FLAC      }) = "encodingFlac"
   show (Encoding #{const SOX_ENCODING_HCOM      }) = "encodingHcom"
   show (Encoding #{const SOX_ENCODING_WAVPACK   }) = "encodingWavpack"
   show (Encoding #{const SOX_ENCODING_WAVPACKF  }) = "encodingWavpackf"
   show (Encoding #{const SOX_ENCODING_ULAW      }) = "encodingUlaw"
   show (Encoding #{const SOX_ENCODING_ALAW      }) = "encodingAlaw"
   show (Encoding #{const SOX_ENCODING_G721      }) = "encodingG721"
   show (Encoding #{const SOX_ENCODING_G723      }) = "encodingG723"
   show (Encoding #{const SOX_ENCODING_CL_ADPCM  }) = "encodingClADPCM"
   show (Encoding #{const SOX_ENCODING_CL_ADPCM16}) = "encodingClADPCM16"
   show (Encoding #{const SOX_ENCODING_MS_ADPCM  }) = "encodingMsADPCM"
   show (Encoding #{const SOX_ENCODING_IMA_ADPCM }) = "encodingImaADPCM"
   show (Encoding #{const SOX_ENCODING_OKI_ADPCM }) = "encodingOkiADPCM"
   show (Encoding #{const SOX_ENCODING_DPCM      }) = "encodingDPCM"
   show (Encoding #{const SOX_ENCODING_DWVW      }) = "encodingDWVW"
   show (Encoding #{const SOX_ENCODING_DWVWN     }) = "encodingDWVWN"
   show (Encoding #{const SOX_ENCODING_GSM       }) = "encodingGSM"
   show (Encoding #{const SOX_ENCODING_MP3       }) = "encodingMP3"
   show (Encoding #{const SOX_ENCODING_VORBIS    }) = "encodingVorbis"
   show (Encoding #{const SOX_ENCODING_AMR_WB    }) = "encodingAmrWB"
   show (Encoding #{const SOX_ENCODING_AMR_NB    }) = "encodingAmrNB"
   show (Encoding #{const SOX_ENCODING_CVSD      }) = "encodingCVSD"
   show (Encoding #{const SOX_ENCODING_LPC10     }) = "encodingLPC10"
   show (Encoding n) = "(Encoding " ++ show n ++ ")"



newtype FileType = FileType {unFileType :: String}

instance Show FileType where
   showsPrec prec (FileType str) =
      showParen (prec>=10) $ showString "FileType " . showString str

type CFileType = CStr.CString
type Sample = #{type sox_sample_t}
type OOB = ()
-- type FormatHandler = ()
type Whence = C.CInt


foreign import ccall unsafe "sox.h sox_init"
   init :: IO C.CInt

foreign import ccall unsafe "sox.h sox_quit"
   quit :: IO C.CInt

foreign import ccall unsafe "sox.h sox_format_init"
   formatInit :: IO C.CInt

foreign import ccall unsafe "sox.h sox_format_quit"
   formatQuit :: IO ()

foreign import ccall unsafe "sox.h sox_open_read"
   openRead :: CStr.CString -> Ptr SignalInfo -> Ptr EncodingInfo -> CFileType -> IO (Ptr (Format ReadMode))

foreign import ccall safe "sox.h sox_open_write"
   openWrite :: CStr.CString -> Ptr SignalInfo -> Ptr EncodingInfo -> CFileType -> Ptr OOB -> FunPtr (CStr.CString -> IO Bool) -> IO (Ptr (Format WriteMode))

foreign import ccall unsafe "sox.h sox_read"
   read :: Ptr (Format ReadMode) -> Ptr Sample -> C.CSize -> IO C.CSize

foreign import ccall unsafe "sox.h sox_write"
   write :: Ptr (Format WriteMode) -> Ptr Sample -> C.CSize -> IO C.CSize

foreign import ccall unsafe "sox.h sox_close"
   close :: Ptr (Format mode) -> IO C.CInt

foreign import ccall unsafe "sox.h sox_seek"
   seek :: Ptr (Format mode) -> C.CSize -> Whence -> IO C.CInt