{- |
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'