module Resource.Opus ( Config(..) , Source , load , loadPCM ) where import RIO import Foreign qualified import Foreign.C.Types (CFloat(..)) import GHC.Stack (withFrozenCallStack) import Sound.OpenAL qualified as OpenAL import Sound.OpusFile qualified as OpusFile import Resource.Source qualified as Resource data Config = Config { gain :: Float , loopingMode :: Bool , byteSource :: Resource.Source } type Source = (Double, OpenAL.Source) load :: ( MonadIO m , MonadReader env m , HasLogFunc env , HasCallStack ) => OpenAL.Device -> Config -> m Source load _device Config{..} = do pcm <- withFrozenCallStack $ Resource.load loadPCM byteSource 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 byteSource , ": " , 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 -- TODO: extract to `opusfile` loadPCM :: MonadIO m => ByteString -> m (OpusFile.Pcm Int16) loadPCM bytes = do !opusBytes <- liftIO $ OpusFile.openMemoryBS bytes >>= \case Left err -> throwString $ "Opus loader error: " <> show err Right res -> pure res !pcmInt16 <- liftIO $ OpusFile.decodeInt16 opusBytes liftIO $ OpusFile.free opusBytes pure pcmInt16