-- {-# LANGUAGE Strict #-}
module Resource.Opus
( Config(..)
, Source
, load
) where
import RIO
import Foreign qualified
import Foreign.C.Types (CFloat(..))
import RIO.ByteString qualified as ByteString
import Sound.OpenAL qualified as OpenAL
import Sound.OpusFile qualified as OpusFile
data Config = Config
{ gain :: Float
, loopingMode :: Bool
, filePath :: FilePath
}
type Source = (Double, OpenAL.Source)
load
:: (MonadIO m, MonadReader env m, HasLogFunc env)
=> OpenAL.Device
-> Config
-> m Source
load _device Config{..} = do
logInfo $ "Loading " <> displayShow filePath
!pcm <- liftIO do
Right file <- ByteString.readFile filePath >>= OpusFile.openMemoryBS
pcmInt16 <- OpusFile.decodeInt16 file
OpusFile.free file
pure pcmInt16
alFormat <-
case OpusFile.pcmChannels pcm of
Right OpusFile.Stereo ->
pure OpenAL.Stereo16
Right OpusFile.Mono ->
pure OpenAL.Mono16
Left n -> do
logError $ mconcat
[ "unexpected channels in "
, displayShow filePath
, ": "
, displayShow n
]
exitFailure
!buffer <- liftIO OpenAL.genObjectName
liftIO $
Foreign.withForeignPtr (OpusFile.pcmData pcm) \ptr -> do
let
mreg =
OpenAL.MemoryRegion ptr (fromIntegral $ OpusFile.pcmSize pcm)
OpenAL.bufferData buffer OpenAL.$=! OpenAL.BufferData mreg alFormat 48000
!source <- liftIO $ OpenAL.genObjectName
liftIO do
OpenAL.buffer source OpenAL.$=! Just buffer
OpenAL.loopingMode source OpenAL.$=! loopingMode'
OpenAL.sourceGain source OpenAL.$=! gain'
OpenAL.rolloffFactor source OpenAL.$=! 0 -- XXX: exempt from distance attenuation
pure (OpusFile.pcmTime pcm, source)
where
loopingMode' =
if loopingMode then
OpenAL.Looping
else
OpenAL.OneShot
gain' = CFloat gain