silero-vad-0.1.0.0: src/Silero/Model.hs
{-# LANGUAGE CPP #-}
module Silero.Model (
SileroModel (..),
detectSpeech,
windowLength,
resetModel,
sampleRate,
withModel,
) where
import Data.Int (Int64)
import Data.Vector.Storable (Vector)
import qualified Data.Vector.Storable as Vector
import Foreign.Storable (Storable (..))
import GHC.Generics (Generic)
import GHC.IO (unsafeDupablePerformIO, unsafePerformIO)
import Paths_silero_vad (getDataFileName)
import UnliftIO (MonadIO (liftIO), MonadUnliftIO, bracket)
#if defined (linux_HOST_OS) || defined (darwin_HOST_OS)
import System.Posix (RTLDFlags (RTLD_NOW), dlsym, dlopen )
import Foreign (FunPtr, Ptr, castPtr)
import Foreign.C (CString, withCString)
#else
import System.Win32 (getProcAddress, loadLibrary)
import Foreign (FunPtr, Ptr, castPtr, castPtrToFunPtr)
import Foreign.C (CWString, withCWString)
#endif
foreign import ccall "model.h get_window_length" c_get_window_length :: IO Int64
foreign import ccall "model.h get_sample_rate" c_get_sample_rate :: IO Int64
foreign import ccall "model.h release_model" c_release_model :: Ptr () -> IO ()
foreign import ccall "model.h reset_model" c_reset_model :: Ptr () -> IO ()
foreign import ccall "model.h detect_speech" c_detect_speech :: Ptr () -> Ptr Float -> IO Float
#if defined (linux_HOST_OS) || defined (darwin_HOST_OS)
foreign import ccall "model.h load_model" c_load_model :: FunPtr () -> CString -> IO (Ptr ())
#else
foreign import ccall "model.h load_model" c_load_model :: FunPtr () -> CWString -> IO (Ptr ())
#endif
windowLength :: Int
windowLength = fromIntegral $ unsafeDupablePerformIO c_get_window_length
sampleRate :: Int
sampleRate = fromIntegral $ unsafeDupablePerformIO c_get_sample_rate
-- |
-- Holds state to be used for voice activity detection.
-- **Warning**: This is **NOT** thread-safe due to this mutating state internally.
newtype SileroModel = SileroModel
{ api :: Ptr ()
}
deriving (Generic)
instance Storable SileroModel where
sizeOf _ = sizeOf (undefined :: Ptr ())
alignment _ = alignment (undefined :: Ptr ())
peek ptr = do
apiPtr <- peek (castPtr ptr)
return $ SileroModel apiPtr
poke ptr (SileroModel apiPtr) = poke (castPtr ptr) apiPtr
libraryPath :: FilePath
#if defined(linux_HOST_OS)
libraryPath = "lib/onnxruntime/linux-x64/libonnxruntime.so"
#elif defined(darwin_HOST_OS) && defined(aarch64_HOST_ARCH)
libraryPath = "lib/onnxruntime/mac-arm64/libonnxruntime.dylib"
#elif defined(darwin_HOST_OS) && !defined(aarch64_HOST_ARCH)
libraryPath = "lib/onnxruntime/mac-x64/libonnxruntime.dylib"
#else
libraryPath = "lib/onnxruntime/windows-x64/onnxruntime.dll"
#endif
{-# NOINLINE onnxruntime #-}
onnxruntime :: FunPtr ()
#if defined (linux_HOST_OS) || defined (darwin_HOST_OS)
onnxruntime =
unsafePerformIO $
getDataFileName libraryPath
>>= flip dlopen [RTLD_NOW]
>>= (liftIO . flip dlsym "OrtGetApiBase")
#else
onnxruntime =
unsafePerformIO $
getDataFileName libraryPath
>>= loadLibrary
>>= (fmap castPtrToFunPtr . flip getProcAddress "OrtGetApiBase")
#endif
getModelPath :: IO String
getModelPath = getDataFileName "lib/silero_vad.onnx"
#if defined (linux_HOST_OS) || defined (darwin_HOST_OS)
withModelPath :: (CString -> IO a) -> IO a
withModelPath runModelPath = do
modelPath <- getModelPath
withCString modelPath $ runModelPath
#else
withModelPath :: (CWString -> IO a) -> IO a
withModelPath runModelPath = do
modelPath <- getModelPath
withCWString modelPath runModelPath
#endif
-- | **Warning: SileroModel holds internal state and is NOT thread safe.**
withModel :: (MonadUnliftIO m) => (SileroModel -> m a) -> m a
withModel runModel = do
bracket
(SileroModel <$> liftIO (withModelPath $ c_load_model onnxruntime))
(liftIO . c_release_model . api)
runModel
-- | **Warning: SileroModel holds internal state and is NOT thread safe.**
resetModel :: (MonadIO m) => SileroModel -> m ()
resetModel = liftIO . c_reset_model . api
-- |
-- Detect if speech is found within the given audio samples.
-- This has the following requirements:
-- - Must be 16khz sample rate.
-- - Must be mono-channel.
-- - Must be 16-bit audio.
-- - Must contain exactly 512 samples.
--
-- | **Warning: SileroModel holds internal state and is NOT thread safe.**
detectSpeech :: (MonadIO m) => SileroModel -> Vector Float -> m Float
detectSpeech (SileroModel api) samples
| Vector.length samples /= windowLength =
return 0.0
| otherwise =
liftIO . Vector.unsafeWith samples $
c_detect_speech api