hsndfile-0.3.3: Sound/File/Sndfile/Interface.chs
module Sound.File.Sndfile.Interface where
import C2HS
import Control.Monad (liftM, when)
import Data.Bits
import qualified Sound.File.Sndfile.Exception as E
import System.IO.Unsafe (unsafePerformIO)
#include <sndfile.h>
{#context lib="libsndfile" prefix="sf"#}
-- ====================================================================
-- Basic types
-- | Type for expressing sample counts.
type Count = Int
-- | Type for expressing sample indices.
type Index = Int
-- ====================================================================
-- Format
-- | Header format.
{#enum HeaderFormat {underscoreToCase} deriving (Eq, Show)#}
#c
enum HeaderFormat
{
HEADER_FORMAT_NONE = 0,
HEADER_FORMAT_WAV = SF_FORMAT_WAV,
HEADER_FORMAT_AIFF = SF_FORMAT_AIFF,
HEADER_FORMAT_AU = SF_FORMAT_AU,
HEADER_FORMAT_RAW = SF_FORMAT_RAW,
HEADER_FORMAT_PAF = SF_FORMAT_PAF,
HEADER_FORMAT_SVX = SF_FORMAT_SVX,
HEADER_FORMAT_NIST = SF_FORMAT_NIST,
HEADER_FORMAT_VOC = SF_FORMAT_VOC,
HEADER_FORMAT_IRCAM = SF_FORMAT_IRCAM,
HEADER_FORMAT_W64 = SF_FORMAT_W64,
HEADER_FORMAT_MAT4 = SF_FORMAT_MAT4,
HEADER_FORMAT_MAT5 = SF_FORMAT_MAT5,
HEADER_FORMAT_PVF = SF_FORMAT_PVF,
HEADER_FORMAT_XI = SF_FORMAT_XI,
HEADER_FORMAT_HTK = SF_FORMAT_HTK,
HEADER_FORMAT_SDS = SF_FORMAT_SDS,
HEADER_FORMAT_AVR = SF_FORMAT_AVR,
HEADER_FORMAT_WAVEX = SF_FORMAT_WAVEX,
HEADER_FORMAT_SD2 = SF_FORMAT_SD2,
HEADER_FORMAT_FLAC = SF_FORMAT_FLAC,
HEADER_FORMAT_CAF = SF_FORMAT_CAF
};
#endc
-- | Sample format.
{#enum SampleFormat {underscoreToCase} deriving (Eq, Show)#}
#c
enum SampleFormat
{
SAMPLE_FORMAT_NONE = 0,
SAMPLE_FORMAT_PCM_S8 = SF_FORMAT_PCM_S8,
SAMPLE_FORMAT_PCM_16 = SF_FORMAT_PCM_16,
SAMPLE_FORMAT_PCM_24 = SF_FORMAT_PCM_24,
SAMPLE_FORMAT_PCM_32 = SF_FORMAT_PCM_32,
SAMPLE_FORMAT_PCM_U8 = SF_FORMAT_PCM_U8,
SAMPLE_FORMAT_FLOAT = SF_FORMAT_FLOAT,
SAMPLE_FORMAT_DOUBLE = SF_FORMAT_DOUBLE,
SAMPLE_FORMAT_ULAW = SF_FORMAT_ULAW,
SAMPLE_FORMAT_ALAW = SF_FORMAT_ALAW,
SAMPLE_FORMAT_IMA_ADPCM = SF_FORMAT_IMA_ADPCM,
SAMPLE_FORMAT_MS_ADPCM = SF_FORMAT_MS_ADPCM,
SAMPLE_FORMAT_GSM610 = SF_FORMAT_GSM610,
SAMPLE_FORMAT_VOX_ADPCM = SF_FORMAT_VOX_ADPCM,
SAMPLE_FORMAT_G721_32 = SF_FORMAT_G721_32,
SAMPLE_FORMAT_G723_24 = SF_FORMAT_G723_24,
SAMPLE_FORMAT_G723_40 = SF_FORMAT_G723_40,
SAMPLE_FORMAT_DWVW_12 = SF_FORMAT_DWVW_12,
SAMPLE_FORMAT_DWVW_16 = SF_FORMAT_DWVW_16,
SAMPLE_FORMAT_DWVW_24 = SF_FORMAT_DWVW_24,
SAMPLE_FORMAT_DWVW_N = SF_FORMAT_DWVW_N,
SAMPLE_FORMAT_FORMAT_DPCM_8 = SF_FORMAT_DPCM_8,
SAMPLE_FORMAT_FORMAT_DPCM_16 = SF_FORMAT_DPCM_16
};
#endc
-- | Endianness.
{#enum EndianFormat {underscoreToCase} deriving (Eq, Show)#}
#c
enum EndianFormat
{
ENDIAN_FILE = SF_ENDIAN_FILE,
ENDIAN_LITTLE = SF_ENDIAN_LITTLE,
ENDIAN_BIG = SF_ENDIAN_BIG,
ENDIAN_CPU = SF_ENDIAN_CPU
};
#endc
-- only used internally
{#enum FormatMask {underscoreToCase} deriving (Eq)#}
#c
enum FormatMask
{
FORMAT_SUB_MASK = SF_FORMAT_SUBMASK,
FORMAT_TYPE_MASK = SF_FORMAT_TYPEMASK,
FORMAT_END_MASK = SF_FORMAT_ENDMASK
};
#endc
-- |Stream format specification, consisting of header, sample and endianness
-- formats.
--
-- Not all combinations of header, sample and endianness formats are valid;
-- valid combinamtions can be checked with the 'checkFormat' function.
data Format = Format {
headerFormat :: HeaderFormat,
sampleFormat :: SampleFormat,
endianFormat :: EndianFormat
} deriving (Eq, Show)
-- |Default \'empty\' format, useful when opening files for reading with 'ReadMode'.
defaultFormat :: Format
defaultFormat = Format HeaderFormatNone SampleFormatNone EndianFile
-- Convert CInt to Format
hsFormat :: CInt -> Format
hsFormat i =
let hf = cToEnum (i .&. (cFromEnum FormatTypeMask) .&. complement (cFromEnum FormatEndMask))
sf = cToEnum (i .&. (cFromEnum FormatSubMask))
ef = cToEnum (i .&. (cFromEnum FormatEndMask))
in
Format {
headerFormat = hf,
sampleFormat = sf,
endianFormat = ef
}
-- Convert Format to CInt
cFormat :: Format -> CInt
cFormat (Format hf sf ef) = (cFromEnum hf) .|. (cFromEnum sf) .|. (cFromEnum ef)
-- ====================================================================
-- Info
-- |The 'Info' structure is for passing data between the calling function and
-- the library when opening a stream for reading or writing.
data Info = Info {
frames :: Count, -- ^Number of frames in file
samplerate :: Int, -- ^Audio sample rate
channels :: Int, -- ^Number of channels
format :: Format, -- ^Header and sample format
sections :: Int, -- ^Number of sections
seekable :: Bool -- ^'True' when stream is seekable (e.g. local files)
} deriving (Eq, Show)
-- |Return soundfile duration in seconds computed via the 'Info' fields
-- 'frames' and 'samplerate'.
duration :: Info -> Double
duration info = (fromIntegral $ frames info) / (fromIntegral $ samplerate info)
-- |Default \'empty\' info, useful when opening files for reading with 'ReadMode'.
defaultInfo :: Info
defaultInfo = Info 0 0 0 defaultFormat 0 False
-- |This function allows the caller to check if a set of parameters in the
-- 'Info' struct is valid before calling 'openFile' ('WriteMode').
--
-- 'checkFormat' returns 'True' if the parameters are valid and 'False' otherwise.
{-# NOINLINE checkFormat #-}
checkFormat :: Info -> Bool
checkFormat info =
unsafePerformIO (with info (liftM cToBool . {#call unsafe sf_format_check#} . castPtr))
-- Storable instance for Info
instance Storable (Info) where
alignment _ = alignment (undefined :: CInt) -- hmm
sizeOf _ = {#sizeof INFO#}
-- Unmarshall Info from C representation
peek ptr = do
frames <- liftM fromIntegral $ {#get SF_INFO.frames#} ptr
samplerate <- liftM fromIntegral $ {#get SF_INFO.samplerate#} ptr
channels <- liftM fromIntegral $ {#get SF_INFO.channels#} ptr
format <- liftM hsFormat $ {#get SF_INFO.format#} ptr
sections <- liftM fromIntegral $ {#get SF_INFO.sections#} ptr
seekable <- liftM toBool $ {#get SF_INFO.seekable#} ptr
return $ Info {
frames = frames,
samplerate = samplerate,
channels = channels,
format = format,
sections = sections,
seekable = seekable
}
-- Marshall Info to C representation
poke ptr info =
do
{#set SF_INFO.frames#} ptr $ fromIntegral $ frames info
{#set SF_INFO.samplerate#} ptr $ fromIntegral $ samplerate info
{#set SF_INFO.channels#} ptr $ fromIntegral $ channels info
{#set SF_INFO.format#} ptr $ cFormat $ format info
{#set SF_INFO.sections#} ptr $ fromIntegral $ sections info
{#set SF_INFO.seekable#} ptr $ fromBool $ seekable info
-- ====================================================================
-- Exceptions
checkHandle :: HandlePtr -> IO HandlePtr
checkHandle handle = do
code <- liftM fromIntegral $ {#call unsafe sf_error#} handle
when (code /= 0) $
peekCString ({#call pure sf_strerror#} handle) >>= E.throw code
return handle
-- ====================================================================
-- Handle operations
-- | Abstract file handle.
data Handle = Handle {
hInfo :: Info, -- ^Return the stream 'Info' associated with the 'Handle'.
hPtr :: HandlePtr
}
type HandlePtr = Ptr ()
-- | I\/O mode.
{#enum IOMode {} deriving (Eq, Show)#}
#c
enum IOMode
{
ReadMode = SFM_READ,
WriteMode = SFM_WRITE,
ReadWriteMode = SFM_RDWR
};
#endc
-- |When opening a file for read ('ReadMode'), the format field should be set
-- to 'defaultFormat' before calling 'openFile'. The only exception to this is
-- the case of RAW files, where the caller has to set the samplerate, channels
-- and format fields to valid values. All other fields of the structure are
-- filled in by the library.
--
-- When opening a file for write ('WriteMode'), the caller must fill in the
-- structure members samplerate, channels, and format.
--
-- Every call to 'openFile' should be matched with a call to 'hClose' to free
-- up memory allocated during the call to 'openFile'.
--
-- On success, the 'openFile' function returns a 'Handle' which should be
-- passed as the first parameter to all subsequent libsndfile calls dealing
-- with that audio stream. On fail, the 'openFile' function signals an
-- 'Exception'.
openFile :: FilePath -> IOMode -> Info -> IO Handle
openFile filePath ioMode info =
withCString filePath (\cFilePath ->
with info (\cInfo -> do
cHandle <- {#call unsafe sf_open#}
cFilePath (cFromEnum ioMode) (castPtr cInfo)
>>= checkHandle
newInfo <- peek cInfo
return $ Handle newInfo cHandle))
-- |The 'hClose' function closes the stream, deallocates its internal buffers
-- and returns () on success or signals an 'Exception' otherwise.
hClose :: Handle -> IO ()
hClose handle = do
{#call unsafe sf_close#} $ hPtr handle
checkHandle nullPtr
return ()
-- |If the stream is opened with 'WriteMode' or 'ReadWriteMode', call the
-- operating system\'s function to force the writing of all file cache buffers
-- to disk. If the file is opened with 'ReadMode' no action is taken.
hFlush :: Handle -> IO ()
hFlush (Handle _ handle) = {#call unsafe sf_write_sync#} handle
-- |Get header format information associated with file.
getFileInfo :: FilePath -> IO Info
getFileInfo filePath = do
h <- openFile filePath ReadMode defaultInfo
let info = hInfo h
hClose h
return info
-- ====================================================================
-- seeking
hIsSeekable :: Handle -> IO Bool
hIsSeekable = return . seekable . hInfo
{#enum SeekMode {} deriving (Eq, Show)#}
#c
enum SeekMode
{
AbsoluteSeek = SEEK_SET,
RelativeSeek = SEEK_CUR,
SeekFromEnd = SEEK_END
};
#endc
-- Helper function for seeking, modifying either the read pointer, the write
-- pointer, or both.
{-# INLINE hSeek' #-}
hSeek' :: Maybe IOMode -> Handle -> SeekMode -> Count -> IO Count
hSeek' ioMode (Handle _ handle) seekMode frames = do
n <- liftM fromIntegral $
{#call unsafe sf_seek#}
handle
(cIntConv frames)
((cFromEnum seekMode) .|. (case ioMode of
Nothing -> 0
Just m -> cFromEnum m))
checkHandle handle
return n
-- |The file seek functions work much like 'System.IO.hseek' with the
-- exception that the non-audio data is ignored and the seek only moves within
-- the audio data section of the file. In addition, seeks are defined in
-- number of (multichannel) frames. Therefore, a seek in a stereo file from
-- the current position forward with an offset of 1 would skip forward by one
-- sample of both channels.
--
-- like lseek(), the whence parameter can be any one of the following three
-- values:
--
-- * 'AbsoluteSeek' - The offset is set to the start of the audio data plus
-- offset (multichannel) frames.
--
-- * 'RelativeSeek' - The offset is set to its current location plus offset
-- (multichannel) frames.
--
-- * 'SeekFromEnd' - The offset is set to the end of the data plus offset
-- (multichannel) frames.
--
-- Internally, libsndfile keeps track of the read and write locations using
-- separate read and write pointers. If a file has been opened with a mode of
-- 'ReadWriteMode', calling either 'hSeekRead' or 'hSeekWrite' allows the read
-- and write pointers to be modified separately. 'hSeek' modifies both the
-- read and the write pointer.
--
-- Note that the frames offset can be negative and in fact should be when
-- SeekFromEnd is used for the whence parameter.
--
-- 'hSeek' will return the offset in (multichannel) frames from the start of
-- the audio data, or signal an error when an attempt is made to seek beyond
-- the start or end of the file.
hSeek :: Handle -> SeekMode -> Count -> IO Count
hSeek = hSeek' Nothing
--hSeek (Handle _ handle) seekMode frames = do
-- n <- liftM fromIntegral $ {#call unsafe sf_seek#} handle (cIntConv frames) (cFromEnum seekMode)
-- checkHandle handle
-- return n
-- |Like 'hSeek', but only the read pointer is modified.
hSeekRead :: Handle -> SeekMode -> Count -> IO Count
hSeekRead = hSeek' (Just ReadMode)
-- |Like 'hSeek', but only the write pointer is modified.
hSeekWrite :: Handle -> SeekMode -> Count -> IO Count
hSeekWrite = hSeek' (Just WriteMode)
-- ====================================================================
-- string access
-- |Header string field types.
{#enum StringType {underscoreToCase} deriving (Eq, Show)#}
#c
enum StringType
{
STR_TITLE = SF_STR_TITLE,
STR_COPYRIGHT = SF_STR_COPYRIGHT,
STR_SOFTWARE = SF_STR_SOFTWARE,
STR_ARTIST = SF_STR_ARTIST,
STR_COMMENT = SF_STR_COMMENT,
STR_DATE = SF_STR_DATE
};
#endc
-- |The 'getString' function returns the specificed string from the stream
-- header in the 'Maybe' monad if it exists and 'Nothing' otherwise.
getString :: Handle -> StringType -> IO (Maybe String)
getString (Handle _ handle) t = do
ptr <- {#call unsafe sf_get_string#} handle (cFromEnum t)
if ptr == (nullPtr :: Ptr CChar)
then return Nothing
else liftM Just $ peekCString =<< (return ptr)
-- |The 'setString' function sets the string data associated with the
-- respective 'StringType'.
setString :: Handle -> StringType -> String -> IO ()
setString (Handle _ handle) t s =
withCString s (\cs -> do
{#call unsafe sf_set_string#} handle (cFromEnum t) cs
checkHandle handle
return ())
-- EOF