{- | Uses @libsamplerate@ to resample a stream of audio. -} module Data.Conduit.Audio.SampleRate ( resample, resampleTo , SRC.ConverterType(..), SRC.SRCError(..) ) where import Data.Conduit.Audio import qualified Data.Conduit.Audio.SampleRate.Binding as SRC import Control.Monad.IO.Class (liftIO) import qualified Data.Vector.Storable as V import qualified Data.Conduit as C import Control.Monad.Fix (fix) import Control.Monad (when) import Foreign import Control.Monad.Trans.Resource (MonadResource) resample :: (MonadResource m) => Double -- ^ the ratio of new sample rate to old sample rate -> SRC.ConverterType -> AudioSource m Float -> AudioSource m Float resample rat ctype src = resampleTo (rat * rate src) ctype src resampleTo :: (MonadResource m) => Rate -- ^ the new sample rate -> SRC.ConverterType -> AudioSource m Float -> AudioSource m Float resampleTo r' ctype (AudioSource s r c l) = let rat = r' / r l' = round $ fromIntegral l * rat s' = s C.=$= C.bracketP (SRC.new ctype c) SRC.delete (\lsr -> fix $ \loop -> C.await >>= \mx -> case mx of Nothing -> return () Just v -> do isEnd <- C.await >>= \my -> case my of Nothing -> return True Just v' -> do C.leftover v' return False let inLen = vectorFrames v c outLen = round $ fromIntegral inLen * rat * 1.1 outPtr <- liftIO $ mallocArray $ outLen * c dout <- liftIO $ V.unsafeWith v $ \inPtr -> do SRC.process lsr $ SRC.DataIn { SRC.data_in = castPtr inPtr , SRC.data_out = castPtr outPtr , SRC.input_frames = fromIntegral inLen , SRC.output_frames = fromIntegral outLen , SRC.src_ratio = rat , SRC.end_of_input = isEnd } outFP <- liftIO $ newForeignPtr finalizerFree outPtr let v' = V.unsafeFromForeignPtr0 outFP $ fromIntegral (SRC.output_frames_gen dout) * c when (V.length v' /= 0) $ C.yield v' let inUsed = fromIntegral $ SRC.input_frames_used dout when (inUsed /= inLen) $ -- we want to make sure we don't keep giving SRC too small a chunk forever if SRC.output_frames_gen dout /= 0 then C.leftover $ V.drop (inUsed * c) v -- SRC did produce some output this time, so we're fine else C.await >>= \mz -> case mz of Nothing -> return () -- this should never happen, right? -- that would mean v was the last chunk, we told SRC it was the -- last chunk, but then it didn't use it all Just v'' -> C.leftover $ V.drop (inUsed * c) v V.++ v'' -- if v was too small to produce anything, -- glue it onto v' to make a bigger chunk loop ) in AudioSource s' r' c l'