opusfile-0.1.0.0: src/Sound/OpusFile.hs
{-# LANGUAGE ForeignFunctionInterface #-}
module Sound.OpusFile
( Handle(..)
, OggOpusFile
, openMemoryBS
, openMemory
, free
, Channels(..)
, channelCount
, pcmTotal
, Pcm(..)
, decodeInt16
) where
import Data.ByteString (ByteString)
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import Data.Int (Int16, Int64)
import Foreign (ForeignPtr, Ptr)
import Foreign.C.Types (CChar, CInt(..))
import System.IO.Unsafe (unsafePerformIO)
import qualified Foreign
-- * Loading
newtype Handle = Handle (Ptr OggOpusFile)
deriving (Eq, Show)
data OggOpusFile
openMemoryBS :: ByteString -> IO (Either Int Handle)
openMemoryBS bs = unsafeUseAsCStringLen bs \(dataPtr, dataLen) ->
openMemory dataPtr (fromIntegral dataLen)
openMemory :: Ptr CChar -> CInt -> IO (Either Int Handle)
openMemory dataPtr dataLen =
Foreign.alloca \errorPtr -> do
oggOpusFile <- op_test_memory dataPtr dataLen errorPtr
Foreign.peek errorPtr >>= \case
0 -> do
op_test_open oggOpusFile >>= \case
0 ->
pure $ Right (Handle oggOpusFile)
err ->
pure $ Left (fromIntegral err)
err ->
pure $ Left (fromIntegral err)
free :: Handle -> IO ()
free (Handle handle) = op_free handle
-- * Information
data Channels
= Mono
| Stereo
deriving (Eq, Ord, Show, Enum, Bounded)
channelCount :: Handle -> Either Int Channels
channelCount (Handle handle) =
case unsafePerformIO (op_channel_count handle) of
1 -> Right Mono
2 -> Right Stereo
n -> Left (fromIntegral n)
pcmTotal :: Handle -> Int
pcmTotal (Handle handle) =
fromIntegral $ unsafePerformIO (op_pcm_total handle)
-- * Decoding
data Pcm a = Pcm
{ pcmData :: ForeignPtr a
, pcmSize :: Int
, pcmTime :: Double
, pcmChannels :: Either Int Channels
}
deriving (Eq, Show)
decodeInt16 :: Handle -> IO (Pcm Int16)
decodeInt16 h@(Handle handle) = do
fptr <- Foreign.mallocForeignPtrBytes byteSize
Foreign.withForeignPtr fptr (go 0) >>= \case
Left ret ->
error $ show ret
Right _samplesDone ->
pure Pcm
{ pcmData = fptr
, pcmSize = byteSize
, pcmTime = fromIntegral hPcmSize / 48000
, pcmChannels = hPcmChannels
}
where
hPcmChannels = channelCount h
hPcmSize = pcmTotal h
byteSize = hPcmSize * numChannels * sampleBytes
samples = hPcmSize * numChannels
numChannels = case hPcmChannels of
Right Mono -> 1
Right Stereo -> 2
Left n -> n
sampleBytes = Foreign.sizeOf (undefined :: Int16)
go samplesRead buf = do
ret <- op_read
handle
buf
(fromIntegral samples)
Foreign.nullPtr
if ret < 0 then
pure $ Left ret
else do
let samplesDone = samplesRead + fromIntegral ret
if samplesDone < hPcmSize then
go samplesDone $
Foreign.plusPtr buf (fromIntegral ret * numChannels * sampleBytes)
else
pure $ Right samplesDone
-- * FFI
foreign import ccall "op_free"
op_free :: Ptr OggOpusFile -> IO ()
foreign import ccall "op_test_memory"
op_test_memory :: Ptr CChar -> CInt -> Ptr CInt -> IO (Ptr OggOpusFile)
foreign import ccall "op_test_open"
op_test_open :: Ptr OggOpusFile -> IO CInt
foreign import ccall "op_channel_count"
op_channel_count :: Ptr OggOpusFile -> IO CInt
foreign import ccall "op_pcm_total"
op_pcm_total :: Ptr OggOpusFile -> IO Int64
foreign import ccall "op_read"
op_read
:: Ptr OggOpusFile -- _of
-> Ptr Int16 -- _pcm
-> CInt -- _buf_size
-> Ptr CInt -- _li
-> IO CInt